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
| Agent | Connects through | Setup |
|---|---|---|
| Claude Code | Plugin hooks, plus every session's transcript | The one-command setup, or the plugin |
| Codex (CLI, IDE extension, app) | Its session logs in ~/.codex/sessions | None |
| Cursor (editor and CLI) | Hooks in ~/.cursor/hooks.json | Connect on the Agents page |
| Gemini CLI | Hooks in ~/.gemini/settings.json | Connect on the Agents page |
| OpenCode | A plugin in ~/.config/opencode/plugins/ | Connect, then restart OpenCode |
| GitHub Copilot CLI | Hooks in ~/.copilot/hooks/dotpals.json | Connect on the Agents page |
| Any other agent | POST http://127.0.0.1:5175/event | A 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.
| Agent | What Connect changes |
|---|---|
| Cursor | Adds a command to ~/.cursor/hooks.json for 12 hook events. Cursor reloads the file on save. |
| Gemini CLI | Adds a hook group to ~/.gemini/settings.json for 8 hook events. |
| OpenCode | Adds ~/.config/opencode/plugins/dotpals.js (or under $XDG_CONFIG_HOME/opencode). Restart OpenCode to load it. |
| GitHub Copilot CLI | Adds 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=0stops 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_DIRpoints 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
| Field | Values |
|---|---|
session | Any id (session_id works too). Each session gets its own pal and tab. Default: "default". |
harness | Your agent's name, shown on the tab and the dashboard (up to 30 characters). |
label | Usually the project, shown next to the name, as in “My-agent · my-project”. Or send cwd and the folder's name is used. |
state | The pal's state: idle, listening, thinking, working, speaking, waiting, done, error or sleeping (which ends the session). |
text | The pal's speech bubble, such as “Running tests”. |
activity | One row, or an array of rows (below). |
helper | A helper agent (subagent) your agent started, and how it's doing. See Helpers. |
An activity row:
| Field | Values |
|---|---|
id | Your 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. |
kind | prompt · read · edit · write · run · search · web · agent · mcp · skill · plan · tool · done · error · compact. Default: tool. |
status | running · waiting · ok · failed · stopped · info. Default for a new row: ok. Once a row is ok or failed, it stays that way. |
title | One line: the prompt, a file, a command's description. Default: tool or kind. |
tool, detail | Your 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, error | How long it took, when it happened (ms since 1970; default now), and an error message. |
summary | On a done row: what the agent says it did. It's shown on the request's card. |
plan | On 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" }
}'
| Field | Values |
|---|---|
id | Required. Your id for the helper; send it again to update the same one. |
name | A short name, such as “tester” (up to 40 characters). |
task | What it was asked to do. |
state | working, done or error. Anything but done and error counts as working. |
text | What 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.