dotpals Docs

Set up

Agents and integrations

dotpals works with any coding agent. Each one connects through a small adapter, and every adapter feeds the same activity feed, so every agent gets the same story view, pal and dashboard.

At a glance

AgentConnects throughSetup
Claude CodePlugin hooks, plus every session's transcriptThe one-command setup, or the plugin
Codex (CLI, IDE extension, app)Its session logs in ~/.codex/sessionsNone
Cursor (editor and CLI)Hooks in ~/.cursor/hooks.jsonConnect on the Agents page
Gemini CLIHooks in ~/.gemini/settings.jsonConnect on the Agents page
OpenCodeA plugin in ~/.config/opencode/plugins/Connect, then restart OpenCode
GitHub Copilot CLIHooks in ~/.copilot/hooks/dotpals.jsonConnect on the Agents page
Any other agentPOST http://127.0.0.1:5175/eventA few lines in your agent loop, a hook script or a wrapper

The hook-based integrations run node ~/.dotpals/app/bridge/hook.js <agent>, so Node has to be on your PATH. The command forwards each event to the bridge and always exits successfully and quickly, so it never blocks or slows the agent.

The Agents page

Open the dashboard and go to Agents. Each card shows whether the agent is installed on this computer, whether it's connected, and when its last event arrived.

  • Connect adds one small command (or, for OpenCode, a plugin) to that agent's own config. It backs up the original first (as <file>.dotpals-backup), merges instead of overwriting, and leaves a file it can't read untouched. It's available once the agent is found on this computer.
  • Disconnect takes out only what dotpals added.
  • Send a test event runs the real hook command (or loads the real plugin). If it reaches dotpals, a pal says “Hello from the dashboard”. If it doesn't, the card says why, such as a wrong path or a missing node.
  • The switch on each card turns an agent off without disconnecting it: its events are ignored until you switch it back on. The same setting is { "agents": { "cursor": false } } in config.json.
AgentWhat Connect changes
CursorAdds a command to ~/.cursor/hooks.json for 12 hook events. Cursor reloads the file on save.
Gemini CLIAdds a hook group to ~/.gemini/settings.json for 8 hook events.
OpenCodeAdds ~/.config/opencode/plugins/dotpals.js (or under $XDG_CONFIG_HOME/opencode). Restart OpenCode to load it.
GitHub Copilot CLIAdds its own file, ~/.copilot/hooks/dotpals.json (or under $COPILOT_HOME/hooks), so your other hooks are never touched.

Claude Code

Connect

The one-command setup adds the plugin for you. Or add it from inside Claude Code (details):

/plugin marketplace add rikinshah787/dotpals
/plugin install dotpals@dotpals

Restart Claude Code to load it. The plugin registers a hook for session start and end, prompts, every tool call before and after (and failures), permission requests, notifications, helper agents, compaction and the end of each turn.

What it reports

  • Your prompts. Slash commands and skills show by name, as /name args; context the editor adds to a prompt is left out.
  • Every tool call, live: files read, edits and new files with their diffs, commands with their output and duration, searches, web lookups, helper agents, skills and MCP tools, and whether each one worked.
  • Permission prompts (the pal waits for you, and you can answer from the pal), compaction, and Claude's own summary at the end of each turn.
  • Its plan, from TodoWrite or tasks, and how full the context window is.
  • Its helper agents, what each one is doing, and when each finishes, even in the background.

Claude Code is also the one agent that can receive notes from dotpals: with Share with your agents on, each session hears what your other agents did in the same project. The plugin delivers them with a second, short hook on session start and on each prompt.

Sessions without the plugin

dotpals also follows every session's transcript in ~/.claude/projects. So sessions that started before dotpals was installed, or without the plugin, show up too, a second or two behind. It picks up transcripts written in the last three hours. When a session sends hook events, the hooks take over and nothing shows twice. When the pal opens partway through a session, its history is filled in from the transcript.

Limits

  • Usage limits, and the context window as a percentage, need dotpals statusline (see Usage limits).
  • Sessions followed only through their transcripts don't report permission prompts, so they can't be answered from the pal.
  • DOTPALS_CLAUDE_LOGS=0 stops following transcripts. The switch on the Agents page turns Claude Code off entirely.

Codex

Connect

Nothing to install. dotpals follows Codex's session logs in ~/.codex/sessions, so the Codex CLI, the IDE extension and the app all show up while the pal is running. The one-command setup starts dotpals when you log in, so it's always there.

What it reports

  • Your prompts, commands (with Codex's own reason for running them), patches with their diffs and the files they touched, images viewed, web searches, helper agents, MCP tools and its plan.
  • Codex's closing message for each turn, how full the context window is (always as a percentage, because Codex logs the window size), and your usage limits.

Limits

  • Codex doesn't log whether a command failed, so dotpals guesses from the output (“exited with code 1”, “error: …”).
  • It follows logs from today and yesterday that changed in the last 12 hours, and gives a live pal only to sessions active in the last 10 minutes.
  • Permission prompts can't be answered from the pal.
  • DOTPALS_CODEX=0 (or "codex": false) stops following Codex. DOTPALS_CODEX_DIR points dotpals at another sessions folder.

Cursor

Connect

Press Connect on the Cursor card. It adds node ".../bridge/hook.js" cursor to ~/.cursor/hooks.json for session start and end, prompts, shell commands, file edits, MCP calls, other tool calls and failures, helper agents, replies, compaction and stop. Cursor reloads the file when it changes. See Cursor's hooks docs.

What it reports

  • Prompts, shell commands with their output and duration, file edits with their diffs, MCP calls, file reads, searches, deletes, web lookups and helper agents.
  • The agent's reply as the turn's summary, compaction, and whether the turn finished, was stopped or failed.

Limits

  • dotpals uses only hooks that watch, never the ones that can approve or block (so it can't change what the agent is allowed to do). That's why tool calls show up once they've finished, rather than while they run, and why permission prompts don't show.
  • No context window or usage limits.

Gemini CLI

Connect

Press Connect on the Gemini CLI card. It adds a hook (named dotpals, matching every tool) to ~/.gemini/settings.json for session start and end, before and after each prompt, before and after each tool, notifications and compression. See Gemini CLI's hooks docs.

Gemini CLI runs hooks from version 0.26, and only in folders you've trusted. If hooks are turned off in your Gemini settings (hooksConfig.enabled), Connect tells you, and nothing arrives until you turn them on.

What it reports

  • Prompts; every tool call live (shell commands, file writes and replacements with diffs, reads, searches, folder listings, web fetches and searches, MCP tools); and its to-do list as the plan.
  • Permission prompts (the pal waits for you; answer in Gemini CLI), compression, and Gemini's final response as the summary.

Limits

  • Gemini's tool events carry no call id, so a tool's start and end are paired by the tool's name and input.
  • No context window or usage limits.

OpenCode

Connect

Press Connect on the OpenCode card, then restart OpenCode. Connect writes a small plugin to ~/.config/opencode/plugins/dotpals.js (or $XDG_CONFIG_HOME/opencode/plugins/), which OpenCode loads when it starts. If a different file was already there, it's backed up and put back on Disconnect. See OpenCode's plugin docs.

The plugin only reports what it sees to http://127.0.0.1:5175. It never changes what OpenCode does, and it's written so it can't throw (an error in a plugin could block a tool).

What it reports

  • Prompts; every tool call live (shell commands, reads, edits and writes with diffs, patches, searches, web fetches and searches, helper agents, skills) and tool errors; its to-do list as the plan.
  • Permission prompts (the pal waits for you; answer in OpenCode), compaction, session errors, and the last reply as the turn's summary.

Limits

  • OpenCode loads plugins only at start, so restart it after Connect or Disconnect.
  • No context window or usage limits.

GitHub Copilot CLI

Connect

Press Connect on the GitHub Copilot CLI card. It writes a hooks file of its own, ~/.copilot/hooks/dotpals.json (or under $COPILOT_HOME/hooks), for session start and end, prompts, tool calls and failures, notifications, errors and the agent stopping. Each command names its event: node ".../bridge/hook.js" copilot <event>. See Copilot CLI's hooks reference.

What it reports

  • Prompts; tool calls (shell commands, file views, creates and edits, searches, web fetches and searches, helper agents) with their output; permission prompts (answer in Copilot CLI); errors that stop the agent; and the end of each turn.

Limits

  • The hook that runs before a tool can block tools, so dotpals leaves it alone. Tool calls show up once they've finished.
  • No closing summary, live plan, context window or usage limits.

Any other agent

Send JSON to the bridge from your agent loop, a hook script or a wrapper:

POST http://127.0.0.1:5175/event
content-type: application/json

Send the pal's state, activity rows, or both. The bridge always answers 200 with {}, and if the pal isn't running your request simply fails, so wrap it in a try/catch and carry on.

The event format

FieldValues
sessionAny id (session_id works too). Each session gets its own pal and tab. Default: "default".
harnessYour agent's name, shown on the tab and the dashboard (up to 30 characters).
labelUsually the project, shown next to the name, as in “My-agent · my-project”. Or send cwd and the folder's name is used.
stateThe pal's state: idle, listening, thinking, working, speaking, waiting, done, error or sleeping (which ends the session).
textThe pal's speech bubble, such as “Running tests”.
activityOne row, or an array of rows (below).
helperA helper agent (subagent) your agent started, and how it's doing. See Helpers.

An activity row:

FieldValues
idYour id for the row. Rows with the same id (in the same session) are merged, so you can send a tool call when it starts and again when it finishes. Without an id, every event adds a new row.
kindprompt · read · edit · write · run · search · web · agent · mcp · skill · plan · tool · done · error · compact. Default: tool.
statusrunning · waiting · ok · failed · stopped · info. Default for a new row: ok. Once a row is ok or failed, it stays that way.
titleOne line: the prompt, a file, a command's description. Default: tool or kind.
tool, detailYour tool's name, and a second line.
files[{ "path": "/abs/path", "change": "read" | "edit" | "write" | "delete" }]. These fill the Files tab and the Map.
body{ "command", "patch", "output", "args" }, plain text, shown when the row is opened. A patch is lines starting with - and +.
ms, at, errorHow long it took, when it happened (ms since 1970; default now), and an error message.
summaryOn a done row: what the agent says it did. It's shown on the request's card.
planOn a plan row: [{ "text", "status": "pending" | "in_progress" | "completed" }], shown as the live plan.

A request is a prompt row, the rows after it, and a done (or error) row that ends it. Chapters, flags and tallies are worked out from kind, files, body.command and body.patch, so fill those in and your agent gets the full story view.

A request, start to finish

# 1. You asked for something
curl -s localhost:5175/event -H "content-type: application/json" -d '{
  "session": "run-42", "harness": "my-agent", "label": "my-project", "state": "thinking",
  "activity": { "id": "p1", "kind": "prompt", "title": "Fix the failing date test", "status": "info" }
}'

# 2. A tool call starts…
curl -s localhost:5175/event -H "content-type: application/json" -d '{
  "session": "run-42", "state": "working", "text": "Running tests",
  "activity": { "id": "call-1", "kind": "run", "tool": "shell", "title": "Run the tests",
                "status": "running", "body": { "command": "npm test" } }
}'

# 3. …and finishes (same id: only what changed)
curl -s localhost:5175/event -H "content-type: application/json" -d '{
  "session": "run-42", "state": "thinking",
  "activity": { "id": "call-1", "status": "ok", "ms": 5120, "body": { "output": "42 passing" } }
}'

# 4. A file edit, with its diff
curl -s localhost:5175/event -H "content-type: application/json" -d '{
  "session": "run-42",
  "activity": { "id": "call-2", "kind": "edit", "tool": "write_file", "title": "src/date.js", "status": "ok",
                "files": [{ "path": "/work/my-project/src/date.js", "change": "edit" }],
                "body": { "patch": "-const days = 30;\n+const days = 31;" } }
}'

# 5. Done, with what the agent says it did
curl -s localhost:5175/event -H "content-type: application/json" -d '{
  "session": "run-42", "state": "done", "text": "All green!",
  "activity": { "id": "end-1", "kind": "done", "title": "Finished", "summary": "Fixed the off-by-one in date.js. Tests pass." }
}'

Don't put a hook_event_name field in your own events: the bridge reads anything with hook_event_name and session_id as a Claude Code hook.

Helpers

If your agent starts helper agents, report each one with helper, and it's listed on the pal and under your agent in the notch (see Helpers):

# a helper starts, and says what it's doing
curl -s localhost:5175/event -H "content-type: application/json" -d '{
  "session": "run-42", "harness": "my-agent",
  "helper": { "id": "w1", "name": "tester", "task": "Run the e2e tests", "state": "working", "text": "Running playwright" }
}'

# …and finishes
curl -s localhost:5175/event -H "content-type: application/json" -d '{
  "session": "run-42", "helper": { "id": "w1", "state": "done" }
}'
FieldValues
idRequired. Your id for the helper; send it again to update the same one.
nameA short name, such as “tester” (up to 40 characters).
taskWhat it was asked to do.
stateworking, done or error. Anything but done and error counts as working.
textWhat it's doing right now.

An activity row with kind: "agent" also counts as a helper, finishing when the row does.

From your language

The Any agent card on the Agents page has these ready to copy, with the right address filled in.

Node (18 or newer)

// If the pal isn't running, carry on.
const send = (event) => fetch('http://127.0.0.1:5175/event', {
  method: 'POST',
  headers: { 'content-type': 'application/json' },
  body: JSON.stringify(event),
}).catch(() => {});

await send({ session: 'run-1', harness: 'my-agent', label: 'my-project', state: 'working', text: 'Running tests',
  activity: { id: 'call-1', kind: 'run', title: 'npm test', status: 'running' } });
// …later, the same id finishes the row:
await send({ session: 'run-1', state: 'done', text: 'Tests pass',
  activity: { id: 'call-1', status: 'ok', ms: 5120, body: { output: '42 passing' } } });

Python 3 (no packages needed)

import json, urllib.request

def send(event):
    req = urllib.request.Request("http://127.0.0.1:5175/event", data=json.dumps(event).encode(),
                                 headers={"content-type": "application/json"})
    try:
        urllib.request.urlopen(req, timeout=1)
    except OSError:
        pass  # the pal isn't running: carry on

send({"session": "run-1", "harness": "my-agent", "label": "my-project", "state": "working", "text": "Running tests",
      "activity": {"id": "call-1", "kind": "run", "title": "npm test", "status": "running"}})
send({"session": "run-1", "state": "done", "text": "Tests pass",
      "activity": {"id": "call-1", "status": "ok", "ms": 5120}})

PowerShell

Invoke-RestMethod http://127.0.0.1:5175/event -Method Post -ContentType application/json -Body (@{
  session = "run-1"; harness = "my-agent"; label = "my-project"
  state = "working"; text = "Running tests"
  activity = @{ id = "call-1"; kind = "run"; title = "npm test"; status = "running" }
} | ConvertTo-Json)

The shell

# Run any command, then tell the pal how it went.
npm test && s=done || s=error; curl -s http://127.0.0.1:5175/event -d "{\"session\":\"sh\",\"harness\":\"shell\",\"state\":\"$s\",\"text\":\"npm test: $s\"}" >/dev/null

Events the pal already understands

Instead of state, you can post events straight from a model's stream. They set the pal's state (not activity rows): Anthropic Messages API stream events, Claude Agent SDK messages, OpenAI Responses API stream events, and generic { "type": "tool_call" | "permission_request" | "error" | … } events. The full mapping is on the web component page.

Add a first-class adapter

To support an agent properly, with Connect and a card on the Agents page, add a module in bridge/adapters/ and list it in bridge/adapters/index.js. bridge/adapters/codex.js is a good template for an agent that writes a session log, and cursor.js for one with hooks. The steps are in Architecture.

Edit this page on GitHub