See what your coding agent actually did.
A small floating pal that watches Claude Code, Codex or any agent and checks its work: which files changed, which commands ran, and whether the tests really passed, read from their output, not the agent's word. When Claude breaks the tests, dotpals sends it back to fix them. And any AI tool can ask dotpals what really happened.
npx dotpals@latest setup
<sub>One command on Windows, macOS or Linux. Free, open source, and everything stays on your computer. Or straight from GitHub: npx --allow-git=all github:rikinshah787/dotpals setup</sub>
<sub>🔊 Watch the launch video with sound (47 s, 1080p) · square cut</sub>
Install · Make your own pal · Plug in any agent · What's new
⭐ Star dotpals if your agent ever said "Done!" and you weren't sure · Tell us what's confusing
dotpals used to show you what your agent did. Now it checks the work, and every AI tool can ask it.
- Make agents fix failing tests. When Claude Code's tests fail, it's told at once, with why ("expected 3, got -1 (test/math.test.js:5)"), and sent back if it tries to finish, commit or push anyway. On by default; one switch in Settings, or the gear in the notch, turns it off.
- Catches the faked pass. Green only because a test lost an assertion, got a skip or now expects the wrong thing? Claude is sent back to put the test back and fix the code, and you're told.
- Ask dotpals from any agent (MCP). Claude Code, Codex, Cursor and other assistants ask
check_my_work,ready_to_merge,todayand more, and get the evidence instead of the agent's word.
All of it in the changelog.
Coding agents do a lot in a single request. They read dozens of files, edit a handful, run tests, retry and search. The chat scrolls by and the diff is spread across files. dotpals keeps a live, plain-language record next to your editor, so at any moment you can answer:
-
What did it change? Every file edited, created or deleted, with the diff one click away.
-
What did it run, and did it work? Every command, with its output, duration and ✓ or ✕. A step that failed and was retried saysfixed on try 2 orstill failing after 3 tries .
-
Did it fix what it broke? When Claude Code's tests fail, it's sent back to fix them before it finishes or commits, and a test changed just to make it pass is caught.
-
Was the code as it is now tested? "Changed 2 files after the tests passed: not tested since" is impossible to miss, so an old green result doesn't pass for a check of the latest edits.
-
What is it doing right now? The pal thinks, works, asks for your OK and celebrates, live.
-
What did I get done today? A running tally, and one click copies it as Markdown for a standup or PR.
-
Make agents fix failing tests (Claude Code, on by default): when a test run fails, Claude is told right away. When it tries to finish, commit or push while the tests fail or weren't run after its last change, it's sent back to fix them, at most twice per request; then it may stop and the pal tells you. A request that changed no code ("run the tests and tell me") just reports the failure, and a test that was already failing before Claude changed anything is reported, not forced on it. A result nobody can read (output cut by
| tail): Claude runs the tests again; if it still can't be read, the pal asks you to look. And if the tests only went green because Claude changed them (took out an assertion, added a skip, changed what one expects), dotpals catches it: Claude is sent back and you're told. Projects without tests are never held up. Turn it off in Settings, or from the gear in the notch. -
Ask dotpals from any agent :
dotpals mcplets Claude Code, Codex, Cursor and other assistants ask what your agents really did, with the evidence:check_my_workbefore an agent says "done",ready_to_merge,today,recap,risky_stepsand more. Seebelow . -
The story, not the log : each request reads as a few chapters, such asChanged 5 files +42 −7 · Tests failed twice, then passed · Committed and pushed , instead of hundreds of tool calls. Anything worth a second look is flagged:
.envchanged, a force-push, the same command failing 3 times, two agents editing the same file, or code changed without testing it. -
Two agents, one file : when an agent is about to change a file another agent changed in the last few minutes, Claude Code asks you first ("Codex (api) changed billing.ts 2 minutes ago. Edit anyway?"), or tells Claude to re-read it. Other agents can't be stopped beforehand: the notch and the pal tell you as it happens.
-
Hand-off :Continue in ▾ hands a session to Codex, Claude Code or Gemini CLI, in a new terminal in the same project, with a note on what was asked, what was done, how the tests stand and what's left. Or copy the note and paste it anywhere.
-
Chat with your agents (optional, off by default): type to an agent from the pal, and answer its permission requests and questions right there. OpenCode for now; Claude Code and others are coming. Turn it on in Dashboard → Settings.
-
Retries : when a step fails and the agent tries the same thing again, the tries are linked:fixed on try 2 ,still failing after 3 tries . Click a try to jump to it. On the dashboard, paste a step's ID (
toolu_…) to open it. -
Was it tested? One line per session saysTests passed · 48 passed · 7:08 PM, after the last change ,Tests passed at 7:08 PM · 3 files changed since orNo tests run by the agent , and whether the last commit was tested. Each result says where it came from: the test output's own summary (jest, vitest, mocha, node:test, pytest, go, cargo, dotnet, Maven, Gradle, PHPUnit, RSpec and more), or*(exit code only)* . Zero tests or only skipped ones read asTests unclear: no tests actually ran , never as passed. Optionally, an unclear result can be double-checked byLaya on your computer (one click sets it up:Settings → Set up Laya , or
dotpals laya; needs Python 3.10+) or TypeSafe's Jev in the cloud (off by default). Only tests the agent ran count. -
Simple or Detailed :Simple (the default) sums up each request in one plain sentence, such asChanged billing.ts, the tests passed after one retry, and committed and pushed. , plus only the warnings that matter.Detailed shows every chapter, with small steps folded away. Switch in the pal, the notch or the dashboard.
-
Setup asks, in the terminal : your pal, the notch, Simple or Detailed, approvals, test double-checks (Off, Local Laya or Cloud Jev, with your key typed hidden) and more. Press Enter for the defaults, or run
setup --yes. -
The notch : an island at the top of your screen with every agent, a live diff of the file it's editing, its plan ("2/4 · Detecting the system setting"), its context window and your Claude and Codex usage limits. It opens by itself when an agent needs you, and you can allow or deny from the keyboard. Hide the pal and the notch takes over;– minimizes it, so nothing sits at the top while agents work.
-
Context and limits : the pal gets worried as a session's context window fills up and cheers after it compacts. Usage bars show your 5-hour and weekly limits with reset times (for Claude, run
dotpals statuslineonce). -
Summary : one card per request, with what you asked, what the agent said it did, and a tally such asChanged 3 files · Ran 5 commands, 1 failed · Used 1 skill .Show steps lists every step as a short sentence.
-
Tools : every tool call as it happens. Click one to see the exact command and output, or the lines an edit changed.
-
Files : every file read, changed, created or deleted, with diffs. Click to open it in VS Code.
-
Today : requests, files changed, commands run and time the agent spent working.Copy today gives you a ready-made standup note.
-
Copy recap : copy any request as Markdown for a PR description or commit message. It keeps**✅ ran successfully** ,❌ failed ,❔ unclear and**⚪ not run** apart, and every claim carries its evidence: the command, the result it was read from ("48 passed", or the exit code) and the step's ID, which the dashboard's search opens. Quick look-ups like
greparen't counted as failures. -
One tab per session : Claude Code and Codex sessions never mix, and the window follows whichever is active.
-
Dashboard : every session with its requests, files and full log, with search and export to Markdown or JSON. It also shows requests per day, time by project and a live view of which agents are connected.
-
Settings : choose your pal, turn sounds and notifications on or off, and decide how long to keep history (or clear it). Settings are shared by the pal and the dashboard.
-
History : survives restarts, kept on your computer in
~/.dotpals/history.json(7 days by default). -
Notifications and sounds : a ping when the agent needs your OK, a chime when it's done, and a desktop notification if you've looked away.
-
Every session, every agent : all your Claude Code sessions show up, even ones started before dotpals was installed, next to Codex and anything else you plug in. In small mode each agent gets its own pal, with a round bar above them naming each one.
-
Make your own pal : pick a body, eyes, something on top, a color and a name. Seebelow .
-
A pal with personality : eight ready-made characters that think, work, talk, wait, celebrate and sulk. Drag it anywhere; it stays on top, and clicks on the empty space around it go through to your editor.
A small island that hangs from the top of your screen. It has four sizes:
- Hidden when nothing is running, or you've been away for 3 minutes: just a thin, invisible strip at the top edge. Hover it and a small islandpeeks out; rest there a moment and it opens.
- Bar while agents work: a mini pal for each agent, the current step, the plan step ("2/4") and a ring for your highest usage limit. Hover it for about 200 ms, or click, to open. Don't want it there?– in the open notch minimizes it (remembered): it stays hidden while agents work, still opens when one needs you, and the top edge still peeks.
- Open (640 px): the agent in focus as a big pal on the left, one card on the right, a column of mini pals for the other agents, and two tabs:
- Now : a live diff of the file it's editing (or a checklist of its steps), its plan, context window, helpers and your usage limits. When it needs your OK, an approval card withDeny andAllow , orCtrl+Alt+N andCtrl+Alt+Y (⌘⌥N and**⌘⌥Y** on macOS), which work only while the card is showing. When it's done or fails, a short card says what happened.
- Story : today's totals withCopy today , whether the code was tested since its last change, the plan, helpers, the context window withCopy /compact , what it's been using, a note when two agents changed the same file, and the last few requests as chapters you can expand.
Alerts open it by themselves, one at a time. One that needs you shows even if you've been away, and stays until you answer. Done and error cards close after about 5 and 8 seconds. When you open it yourself, it closes 8 seconds after the pointer leaves (a shrinking line shows the last seconds), or after a quiet minute with the pointer resting on it. Esc closes it while the pointer is over it. Its window lets clicks through everywhere except the island, and the peek never takes a click, so it doesn't get in the way of your browser tabs.
By default it appears when you hide the pal. You can keep it on always or never show it, from the tray or with dotpals notch --auto | --off.
Claude Code shares its usage limits only with a status line command, so run dotpals statusline once to see them. If you already have a status line, it keeps showing yours; dotpals statusline --off puts everything back. Codex's limits come straight from its logs.
Open the dashboard (▦ on the pal, or dotpals dashboard), go to Settings → Make your own pal, and mix:
- Body : round, boxy, fluffy, pointy, heart or frog
- Eyes : dots, button, googly, pixel, visor or shades
- On top : cat ears, horns, antenna, sprout, sparkle, bow, crown or beret
- Color : any color, fluffy or smooth
- Name : yours to pick
Try it thinking, working and celebrating right there, then Use this pal. The floating pal switches straight away. Surprise me rolls a random one. There are thousands of combinations.
In your own app it's one call: registerCustom({ name: 'Pip', shape: 'bean', eyes: 'googly', top: 'crown', color: '#16c6ae' }), then <dot-pal character="custom">.
npx dotpals@latest setup
(Needs Node 20 or newer. To install straight from GitHub instead: npx --allow-git=all github:rikinshah787/dotpals setup.)
That's all. It:
- installs the desktop pal in
~/.dotpals, and downloads its runtime (Electron, about 100 MB, once), - adds the Claude Code plugin, if Claude Code is installed,
- picks up Codex automatically, if it's installed,
- starts the pal, turns on open when I log in , and opens the dashboard.
Options: --no-claude (skip the plugin), --no-login (don't start at login) and --no-start. Run it again any time to update.
/plugin marketplace add rikinshah787/dotpals
/plugin install dotpals@dotpals
Restart Claude Code, then run /dotpals:pals. The first time, it offers to download the desktop window's runtime. After that, the pal opens by itself whenever a Claude Code session starts.
There's nothing to install on the Codex side. dotpals follows Codex's session logs (~/.codex/sessions), so the Codex CLI, IDE extension and app all show up while the pal is running. The one-command setup starts it at login.
Open the dashboard's Agents page and press Connect on the agent you use. Each card shows whether the agent is installed, whether it's connected, and when its last event arrived.
- Connect adds one small command, or a plugin for OpenCode, to that agent's own config. It backs up the original first, merges instead of overwriting, and leaves a file it can't read untouched.
- Disconnect takes out only what dotpals added.
- Send a test event runs the real command. If it reaches dotpals, a pal says hello.
- The switch on each card turns an agent off without disconnecting it.
The Connect button changes these files:
| Agent | What Connect changes |
|---|---|
| Cursor | ~/.cursor/hooks.json . Cursor reloads it on save |
| Gemini CLI | ~/.gemini/settings.json (Gemini CLI 0.26 or newer, in folders you've trusted) |
| OpenCode | adds ~/.config/opencode/plugins/dotpals.js . Restart OpenCode to load it |
| GitHub Copilot CLI | adds ~/.copilot/hooks/dotpals.json |
Send JSON to the local bridge from your agent loop, a hook script or a wrapper. See Plug in any agent.
| Ctrl+Alt+P (⌘⌥P on macOS) | Show or hide the pal from anywhere | | Drag the pal | Move the window; it remembers where you put it | | ▦ | Open the dashboard: sessions, logs, stats and settings | | ⤡ | Switch between just the pal and the full view | | × | Hide to the tray. The tray menu has Dashboard ,Just the pal ,Notifications ,Open when I log in andQuit | | 🔊 | Sounds on or off |
From a terminal, after setup (or with npx dotpals <command>):
dotpals start # open the floating pal
dotpals dashboard # open the dashboard
dotpals status # what's running and connected
dotpals doctor # no pal or notch? checks and fixes it (setup runs the same check at the end)
dotpals bridge # only the bridge, e.g. on a machine without a desktop; dashboard at http://127.0.0.1:5175/dashboard
dotpals mcp # an MCP server, so any assistant can ask dotpals what your agents did (see below)
dotpals mcp is an MCP server: Claude Code, Codex, Cursor and other assistants can ask dotpals what your agents really did, and get the evidence instead of the agent's word. It only reads, from the pal running on this computer, so keep dotpals running.
claude mcp add dotpals -- npx dotpals mcp
Codex, in ~/.codex/config.toml:
[mcp_servers.dotpals]
command = "npx"
args = ["dotpals", "mcp"]
Cursor and others: command npx, arguments dotpals mcp.
| Tool | Answers |
|---|---|
agents_now |
Which agents worked in the last 2 hours, on what, whether they're still at it, and how their tests stand |
test_status |
Is it really tested? Passed or failed (read from the output), why the last run failed ("expected 3, got -1 (test/math.test.js:5)"), and whether code changed since |
ready_to_merge |
The checks a reviewer would make: tests ran after the last change and passed (not because the tests were changed), nothing left failing, nothing risky, and the work is committed. Mid-request too, as it stands now. Pass branch to ask about a git branch |
recap |
What each agent was asked and did, request by request, with the files it changed, why its tests failed, and tests that passed only after they were changed (a project or a session, since a time) |
today |
Today's work per agent, for a standup: requests, files changed and test runs, then each request in a sentence |
risky_steps |
Force pushes, recursive deletes, changes to .env and other risky steps, each with when, which agent and the command |
handoff_note |
A note for another agent to pick up a session's work: the ask, what was done, the files, how the tests stand, what's left |
check_my_work |
For the agent itself, before it says "done": "Looks done", or what to fix first (with why the tests fail, or "put the test back and fix the code"). Tests that were failing before the session changed anything are its to report, not to fix |
Ask things like "What are my agents doing?", "Is this really tested?", "Is feat/login ready to merge?" or "What did Codex change in billing this morning?". The project is the folder the assistant runs in, unless you name another. dotpals matches it by the folder each agent really works in, so two folders with the same name, a monorepo's packages and git worktrees stay apart (a project's name alone matches by name). It notes the branch when an agent finishes a request in a git repository. Only tests an agent ran count: dotpals can't see the ones you run yourself, or CI.
Everything stays on your machine. The bridge listens only on 127.0.0.1. It reads Claude Code hook events and transcripts and Codex's session logs locally, and it sends nothing anywhere. The one exception is opt-in: if you choose Cloud (Jev) under Settings → Double-check unclear test results, the end of an unclear test run's output is sent to TypeSafe, after removing anything that looks like a password, key, email or IP address. Make agents fix failing tests (on by default) adds short notes about Claude's own test runs to its context, which Claude Code sends to its model like anything else there; turn it off in Settings. History is a plain JSON file in ~/.dotpals. Set DOTPALS_HISTORY=0 to turn it off, or DOTPALS_CODEX=0 to stop following Codex. See SECURITY.md.
We check. test/accuracy/cases holds 36 real requests (cleaned of paths, keys and prompts): 21 from building dotpals itself, and 15 from a small practice project with a planted bug, where Claude was asked to run the tests, add code, and fake a passing test. Every claim dotpals makes about them was checked by hand against what really happened: 255 claims.
| Claim | Claims | Right | Wrong | Unsure |
|---|---|---|---|---|
| Test results (passed, failed) | 99 | 91 | 1 | 7 |
| Risky steps | 14 | 14 | 0 | 0 |
| Retries ("fixed on try 2") | 15 | 14 | 1 | 0 |
| Ready to merge? | 36 | 35 | 1 | 0 |
| Why it stopped | 36 | 35 | 1 | 0 |
| What changed (git) | 10 | 9 | 1 | 0 |
| Tests changed to pass | 20 | 20 | 0 | 0 |
| Why tests failed | 25 | 25 | 0 | 0 |
| All | 255 | 243 (95.3%) | 5 | 7 |
Tests changed to pass is the faked pass: green only because an edit to a test took out an assertion, turned a test off or changed what it expects (3 fakes caught; rightly quiet on 17 others: honest test edits while building dotpals, a skip taken out again before a real fix, fixes that didn't touch the tests). Why tests failed is the reason dotpals reads from a failed run's output and tells the agent ("expected 3, got -1").
"Unsure" is a missing answer, not a false one: a test run dotpals called unclear though a person could tell (that's what the optional Jev or Laya check is for), or a failed run it gave no reason for though the output shows one. The 5 wrong claims are listed by npm run accuracy. One test result and one "Ready to merge?" are wrong because the bridge stored a long command cut off (fixed for new requests). A retry and a "Why it stopped" are wrong because a render that failed twice worked later with different arguments, and dotpals didn't link the two. Git can't tell who changed a file, so a file you reset during a request counts as changed by it. The test suite fails if a claim that's right turns wrong. Add your own cases with node scripts/accuracy-capture.mjs.
Claude Code ── hooks + transcripts ┐
Codex ──────── session logs ───────┼──▶ bridge (127.0.0.1:5175) ──▶ floating pal (Summary · Tools · Files)
your agent ─── POST /event ────────┘ one activity model or any browser tab
- Your agents report what they do. Claude Code sends hook events as it works, and dotpals also reads each session's transcript in
~/.claude/projects, so every session shows up, including ones that started before dotpals was installed. Codex writes session logs to~/.codex/sessions, which dotpals follows. Nothing to set up on the Codex side. Any other agent can POST JSON. - A small local server (the bridge) turns that into one activity feed. It runs on
127.0.0.1:5175, only answers your own computer, and keeps history in~/.dotpals. Nothing is sent anywhere. - The pal shows it. The desktop app (Electron, always on top) and the dashboard read the feed live: the pal's mood, the Summary, Tools and Files tabs, stats and history.
Each agent connects through an adapter in bridge/adapters/, and every adapter produces the same activity entries (bridge/activity.js). The pal itself is a dependency-free Web Component that you can also drop into your own app (see below).
Platforms: Windows, macOS and Linux (Node 20+). On macOS the pal lives in the menu bar instead of the Dock. On Linux the small window can't pass clicks through its empty space, because Linux doesn't support it.
The bridge is harness-agnostic. Each agent tool connects through an adapter in bridge/adapters/, and every adapter feeds the same activity model (bridge/activity.js).
| Harness | How it connects | Setup |
|---|---|---|
| Claude Code | Hooks for live state (including permission prompts), plus the session transcript, so the history is complete even if the pal opened late | The plugin |
| Codex (CLI, IDE extension, app) | Follows Codex's session logs in ~/.codex/sessions |
None. Keep the pal running (tray: Open when I log in ).DOTPALS_CODEX=0 turns it off |
| Cursor (editor and CLI) | Hooks in~/.cursor/hooks.json : prompts, commands, file edits, MCP calls, replies, stop. Only hooks that watch are used; none of them can approve or block anything |
Connect on the dashboard's Agents page |
| Gemini CLI | Hooks in~/.gemini/settings.json : prompts, every tool call, permission prompts, replies |
Connect on the Agents page |
| OpenCode | A plugin in~/.config/opencode/plugins/ : prompts, tool calls, permission prompts, the end of each turn |
Connect on the Agents page, then restart OpenCode |
| GitHub Copilot CLI | Hooks in~/.copilot/hooks/dotpals.json : prompts, tool calls, permission prompts, stop |
Connect on the Agents page |
| Anything else | POST JSON to http://127.0.0.1:5175/event |
A few lines in your agent loop, a hook script or a wrapper. The Agents page has copy-paste snippets for curl, PowerShell, Node, Python and the shell |
Every integration can be switched off on the Agents page, or in ~/.dotpals/config.json with { "agents": { "cursor": false } }. The ids are claude, codex, cursor, gemini, opencode, copilot and generic.
The hook-based integrations run node ~/.dotpals/app/bridge/hook.js <agent>, so Node has to be on your PATH. The command posts to POST /hook?agent=<id>, and the matching module in bridge/adapters/ turns the events into activity. To add another agent, write a module there with the same shape (id, name, detect(), connect(), disconnect(), apply(); see bridge/adapters/index.js) and list it in index.js.
Send the pal's state, activity rows, or both. Rows with the same id are merged, so you can send a tool call when it starts and again when it finishes:
curl -s localhost:5175/event -d '{
"session": "run-42", "harness": "my-agent", "label": "my-project",
"state": "working", "text": "Running tests",
"activity": { "id": "call-1", "kind": "run", "tool": "shell", "title": "Run the tests",
"status": "running", "body": { "command": "npm test" } }
}'
curl -s localhost:5175/event -d '{
"session": "run-42", "harness": "my-agent", "state": "thinking",
"activity": { "id": "call-1", "status": "ok", "ms": 5120, "body": { "output": "42 passing" } }
}'
curl -s localhost:5175/event -d '{
"session": "run-42", "harness": "my-agent",
"activity": { "id": "call-2", "kind": "edit", "tool": "write_file", "title": "src/app.js", "status": "ok",
"files": [{ "path": "/abs/path/src/app.js", "change": "edit" }],
"body": { "patch": "-const a = 1;\n+const a = 2;" } }
}'
| Field | Values |
|---|---|
session |
any id; each session gets its own pal and tab |
harness ,label |
shown on the tab, e.g. "My-agent · my-project" |
state ,text |
the pal's state (see Agent states ) and bubble text |
activity.kind |
prompt ·read ·edit ·write ·run ·search ·web ·agent ·mcp ·skill ·plan ·tool ·done ·error |
activity.status |
running ·waiting ·ok ·failed ·stopped ·info |
activity.files |
`[{ path, change: "read" |
activity.body |
{ command?, patch?, output?, args? } , which you see when the row is opened |
Any event the pal already understands (Anthropic, OpenAI or Agent SDK stream events, or { "state", "text" }) works here too. To add a first-class adapter, see bridge/adapters/codex.js. It's a good template for any harness that writes a session log.
The pal is a dependency-free Web Component, <dot-pal>, for chat UIs, IDE panels and dashboards. Send it your agent's state and it shows thinking dots while the model reasons, a progress bubble while tools run, a talking mouth while text streams, a question bubble when it needs approval, a jump when it's done and a frown when something fails. It works in plain HTML, React, Vue, Svelte, Angular, Electron and VS Code webviews.
| Id | Pal | Click action |
|---|---|---|
blu |
Blu, a blue cloud in a beret | jump |
hop |
Hop, a green frog | jump |
sunny |
Sunny, a yellow gumdrop in glasses | wiggle |
lovi |
Lovi, a pink heart in sunglasses | love |
muse |
Muse, a violet flame with sparkles | spin |
grok |
Grok, a slate bot with a glowing visor | nod |
nova |
Nova, an orange bot with a light-bulb antenna | jump |
byte |
Byte, a teal cat with pixel eyes | wiggle |
<script type="module" src="https://unpkg.com/dotpals"></script>
<dot-pal id="agent" character="grok"></dot-pal>
<script type="module">
const pal = document.getElementById('agent');
pal.setState('thinking');
pal.setState('working', { text: 'Running tests…' });
pal.setState('done', { text: 'All green!' });
</script>
Or from npm:
npm install dotpals
import 'dotpals';
| State | What the pal does |
|---|---|
idle |
breathes, blinks and follows the cursor |
listening |
leans in with wide eyes, for while the user is typing |
thinking |
looks up, shows a bubble with bouncing dots |
working |
busy bob, eyes down, shows a progress bar or your text (e.g. the tool name); after 90 seconds, a sweat drop now and then |
speaking |
mouth moves, for while tokens stream in |
waiting |
hops, then keeps bouncing with wide eyes, shows a ? bubble or yourtext (e.g. "Allow edit?") |
done |
jumps with a burst of sparkles and happy eyes, then settles back to calm |
error |
jitters, then looks sad and desaturated, with × eyes |
sleeping |
eyes closed, floating z s |
A soft glow behind the pal follows the state (amber while waiting, red on errors, green when done), and moods and moves blend into each other instead of snapping.
You can set a state three ways:
<dot-pal character="muse" state="thinking"></dot-pal>
pal.state = 'speaking';
pal.setState('working', { text: 'web_search' });
connectAgent accepts an EventSource, a WebSocket, any EventTarget, or an async iterable (such as an SDK stream). It maps each event to a state automatically.
import { connectAgent } from 'dotpals';
// Server-Sent Events from your backend
connectAgent(pal, new EventSource('/agent/events'));
// WebSocket
connectAgent(pal, new WebSocket('wss://my-harness/agent'));
// An SDK stream (async iterable), e.g. the Anthropic TypeScript SDK
const stream = client.messages.stream({ model, max_tokens, messages, tools });
connectAgent(pal, stream);
It returns a function that disconnects.
import { agentHandler } from 'dotpals';
const onEvent = agentHandler(pal);
for await (const event of myAgent.run(prompt)) {
onEvent(event); // unknown events are ignored
render(event);
}
| Source | Events | State |
|---|---|---|
| Anthropic Messages API (streaming) | message_start |
thinking |
content_block_start with athinking block |
thinking | |
content_block_start with atool_use block |
working, with the tool name | |
content_block_start with atext block |
speaking | |
message_stop |
done | |
| Claude Agent SDK | system /init |
thinking |
assistant message with atool_use |
working, with the tool name | |
assistant message with text |
speaking | |
result |
done, or error if it failed | |
| OpenAI Responses API (streaming) | response.created |
thinking |
response.output_item.added with a function call |
working | |
response.output_text.* |
speaking | |
response.completed |
done | |
response.failed |
error | |
| Generic | { type: 'tool_call' | 'permission_request' | 'error' | … } |
the matching state |
| Your own | { state: 'working', text: 'Deploying…' } |
exactly what you send |
Plain strings work too: 'thinking', or a JSON string of any of the above.
connectAgent(pal, source, {
map: (e) => {
if (e.kind === 'plan') return { state: 'thinking', text: 'Planning…' };
if (e.kind === 'shell') return { state: 'working', text: `$ ${e.cmd}` };
return toAgentState(e); // fall back to the built-in mapping
},
});
npm run example:agent # opens a Server-Sent Events harness on http://localhost:5174
See examples/sse-harness. The server side is about 20 lines. Replace the fake runAgent with your real loop.
// feedback for any promise: thinking, then happy or sad
const data = await pal.during(fetch('/api/save'), { successText: 'Saved!' });
// A form companion: follows the caret, covers its eyes on passwords,
// frowns at invalid fields and cheers on submit
const stop = pal.watch('#login-form');
// Speech bubble
pal.say('Hi! Ask me anything.');
// Show a mood for a moment
pal.flash('surprised', 1500);
// One-shot actions: jump · squish · wiggle · shake · nod · spin · love · hop · jitter · hello · dizzy
await pal.play('love');
// Say hello: rise up from below, squint happily, hop and blink twice
await pal.greet();
// A face for a moment: happy · love · star · wide · closed · dizzy · oops · hey · sweat
await pal.emote('love', 1600);
// Throw particles: heart · sparkle · star · sweat · z, or any text or emoji
pal.burst('sparkle', 8);
- Expression eyes : pals swap in happy arcs, closed lids, wide eyes, × ("oops"), spinning spirals, hearts and sparkle-stars to match their mood (happy, sleepy, surprised or waiting, and the error state) or an
emote(). - Reactions : hover and it blinks; rest the mouse on it for 2 seconds and it gets heart eyes; click and it plays its tap action with a "hey" face; click 3 times quickly and it gets dizzy. Each click fires
dotpal-pokewith{ count }.staticturns these off. - Tiny pals : under 48 px a pal becomes an avatar (the
tinyattribute and the read-onlypal.tinyproperty): no fur, bigger eyes, no glow and no particles. - Pointing from outside the page :
DotPal.pointAt(x, y)tells every pal where the cursor is (viewport CSS px), for apps that track it themselves.DotPal.emoteslists every emote.
| Attribute | Values | Default |
|---|---|---|
character |
any id from the table above, or a registered name | blu |
state |
idle ·listening ·thinking ·working ·speaking ·waiting ·done ·error ·sleeping |
idle |
mood |
neutral ·happy ·sad ·surprised ·thinking ·sleepy ·shy ·listening ·working ·speaking ·waiting |
neutral |
size |
number (px) or any CSS length | 160px |
color |
any CSS color | the character's color |
idle |
breathe ·bounce ·float ·wobble ·sway ·none |
breathe |
look |
cursor ·none |
cursor |
lean |
none : the body doesn't lean toward the cursor |
leans a little |
static |
boolean: turns off the hover and click reactions | – |
label |
accessible name | the character's name |
tiny |
set by the pal itself while it's smaller than 48 px | – |
A state is the agent lifecycle; each state sets a mood. Use mood directly if you aren't driving an agent.
pal.addEventListener('dotpal-state', (e) => e.detail); // { state, text }
pal.addEventListener('dotpal-mood', (e) => e.detail); // { mood }
pal.addEventListener('dotpal-action', (e) => e.detail); // { action }
pal.addEventListener('dotpal-poke', (e) => e.detail); // { count }: quick clicks in a row
dot-pal {
--dp-size: 200px; /* same as the size attribute */
--dp-color: hotpink; /* same as the color attribute */
--dp-glow: transparent; /* turn off the glow behind the pal */
}
dot-pal::part(bubble) { background: #111; color: #fff; }
dot-pal::part(svg) { filter: drop-shadow(0 10px 20px rgb(0 0 0 / .4)); }
The parts you can style are root, idle, actor, svg and bubble.
React 19+: import 'dotpals', then <dot-pal character="grok" state={agentState} />.
Vue: set compilerOptions.isCustomElement = (tag) => tag === 'dot-pal'.
TypeScript: types are included, and document.querySelector('dot-pal') is typed as DotPal.
SSR: importing on the server is safe. The element renders once it reaches the browser.
Characters are plain SVG drawn in a 200×200 viewBox. They sit on the bottom edge and "peek" up over it.
import { registerCharacter } from 'dotpals';
registerCharacter('ghost', {
label: 'Ghost',
color: '#e8e8ff',
tap: 'spin',
look: 6, // how far the eyes follow the cursor
mouth: [100, 170], // where mood mouths are drawn
cheek: 34, // blush distance from the mouth
eyes: { at: [[80, 130], [120, 130]], r: 9 }, // where expression eyes go
render: ({ body }) => ({
body: `<rect fill="${body}" x="30" y="50" width="140" height="220" rx="70"/>`,
face: `
<g class="dp-look">
<g class="dp-blink"><circle cx="80" cy="130" r="9"/></g>
<g class="dp-blink"><circle cx="120" cy="130" r="9"/></g>
</g>`,
}),
});
- The body is automatically covered in fur and shaded.
- Put
class="dp-blink"on each eye so it blinks and reacts to moods. - Put
class="dp-look"on anything that should follow the cursor. eyes(optional) says where the eyes are, so the pal can swap in expression eyes:at(the two centres),r(their size), and optionallyink(their color),glow(trueor a color) andown(expressions your eyes already do well, e.g.['wide']). While they show, the parts markedclass="dp-eyes"hide (or the.dp-blinkparts). Withouteyes, the eyes just squint for moods.- Let bodies run below
y=200, so jumping reveals more body instead of a flat edge.
You can add actions too, with registerAction('pop', { keyframes, duration, particles }). particles is a shape (heart, sparkle, star, sweat or z, drawn as SVG) or any text or emoji.
- Each pal has
role="img"and anaria-labelthat includes its current mood, for example "Grok (working)". - With
prefers-reduced-motion: reduce, the pal keeps its faces, blinks and state changes, but skips the big moves: idle loops, eye wandering, leaning, particles, the floatingz s, state entry moves and the hover, click and dizzy moves. - Speech bubbles are decorative. Keep your own visible status text for screen-reader users.
The full guide is at rikinshah787.github.io/dotpals/guide: getting started, every feature, each agent integration, the CLI, configuration and environment variables, the bridge's HTTP API, the <dot-pal> component, privacy and security, and troubleshooting. Its source is in site/guide/ in this repository, so it's also published wherever the site is hosted.
For contributors, docs/ARCHITECTURE.md explains how the pieces fit together: adapters, the bridge, the activity model, the story engine, the desktop app, and how to add an adapter.
- "It's stuck" alerts : a gentle ping when an agent goes in circles (no progress, the same file back and forth, a test that won't pass).
- Morning brief and weekly recap : what your agents did, what's unfinished and what's failing, per project.
- Token use per request , from the agents' own logs.
- More agents : Windsurf, Cline, Aider and others, as each gets a documented way in.
- Signed installers for Windows and macOS, so Node isn't needed.
Ideas and pull requests are welcome. Open an issue to discuss.
npm install # dev only: Electron for the desktop window
npm test # node --test, no dependencies needed
npm run float # the desktop pal
npm run dashboard # the dashboard
npm run dev # the web component playground on http://localhost:5173
See CONTRIBUTING.md. To support a new agent, add an adapter next to bridge/adapters/codex.js, which is a good template for any agent that writes a session log.
- Reading test results and double-checking unclear ones builds on claude-referee by Ismail Dasci (MIT): its test-output parsers, redaction rules and "done" question are adapted in
bridge/ui/testout.js,bridge/redact.jsandbridge/checker.js. SeeTHIRD_PARTY_NOTICES . - The optional checkers are Laya by Convai Innovations (runs on your computer; not bundled, dotpals installs it from PyPI when you click Set up Laya) andTypeSafe 's Jev, through its MIT-licensed SDK
@typesafe-ai/sdk.
Character names are playful nicknames. dotpals is not affiliated with or endorsed by Anthropic, OpenAI or any other AI company, and the characters are original artwork, not logos.