Rikinshah787/dotpals

See what your coding agent actually did. A floating dot style pal for Claude Code, Codex and any agent: every file changed, command run and step taken, in plain words.

HTML

2

17 commits

updated Oct 1, 2026

See the code

See what people are saying

SourceMessageScoreDate

[dotpals] - A desktop pal that tells you in plain words what your AI coding agent actually did (r/SideProject)

My friend codes almost entirely with AI agents. The agent says "Done!", he says "cool", and he has no idea what happened in between: which files changed, what failed, what got retried. So I built dotpals: a small animated pal that sits on your desktop and tells you in plain words what your coding…

1

Oct 1, 2026

README

dotpals

See what your coding agent actually did.

A small floating pal that watches Claude Code, Codex or any agent and tells you, in plain words, what happened: which files changed, which commands ran, what failed, and what the agent says it did.

npx --allow-git=all github:rikinshah787/dotpals setup

One command on Windows, macOS or Linux. Free, open source, and everything stays on your computer.

CI License: MIT Works with Claude Code and Codex

The dotpals notch: a live diff types in, an agent asks to run git push --force with a warning, Allow is clicked, and the pal celebrates

🔊 Watch the launch video with sound (47 s, 1080p) · square cut

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

Why

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 says fixed on try 2 or still failing after 3 tries.
  • 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.

Features

The dotpals window: a blue pal above a Summary card listing a request, Claude's own summary, and a tally of changed files, commands and skills

  • The story, not the log: each request reads as a few chapters, such as Changed 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: .env changed, a force-push, the same command failing 3 times, two agents editing the same file, or code changed without testing it.
  • 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 says Tests passed at 7:08 PM, after the last change, Tests passed at 7:08 PM · 3 files changed since or No tests run by the agent, and whether the last commit was tested. Test results come from the test output (fail 0, 5 passed), not just the exit code. Only tests the agent ran count.
  • 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 statusline once).
  • Summary: one card per request, with what you asked, what the agent said it did, and a tally such as Changed 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.
  • 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. See below.
  • 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.

The Tools tab with an Edit opened, showing its diff   The Files tab listing changed, new and read files

The dashboard's Sessions page: a session list, and one session's requests with the agent's summary, changed files, commands and skills

The notch

The notch, open on the Now tab: Claude's pal on the left, a live diff of Settings.tsx typing in, its plan at step 2 of 4, its context window, three helpers, and usage bars for Claude and Codex

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 island peeks 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 with Deny and Allow, or Ctrl+Alt+N and Ctrl+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 with Copy today, whether the code was tested since its last change, the plan, helpers, the context window with Copy /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.

Make your own pal

Eighteen home-made pals: round, boxy, fluffy, pointy, heart and frog bodies in different colors, with googly eyes, visors, pixel eyes and shades, and sprouts, crowns, horns, bows, antennas and berets on top

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">.

Install

One command

npx --allow-git=all github:rikinshah787/dotpals setup

That's all. It:

  1. installs the desktop pal in ~/.dotpals, and downloads its runtime (Electron, about 100 MB, once),
  2. adds the Claude Code plugin, if Claude Code is installed,
  3. picks up Codex automatically, if it's installed,
  4. starts the pal, turns on open when I log in, and opens the dashboard.

--allow-git=all lets npm 12 and newer install straight from GitHub; older npm ignores it. Options: --no-claude (skip the plugin), --no-login (don't start at login) and --no-start. Run it again any time to update.

Only the Claude Code plugin

/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.

Codex

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.

Cursor, Gemini CLI, OpenCode and GitHub Copilot CLI

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:

AgentWhat 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)
OpenCodeadds ~/.config/opencode/plugins/dotpals.js. Restart OpenCode to load it
GitHub Copilot CLIadds ~/.copilot/hooks/dotpals.json

Any other agent

Send JSON to the local bridge from your agent loop, a hook script or a wrapper. See Plug in any agent.

Using it

Ctrl+Alt+P (⌘⌥P on macOS)Show or hide the pal from anywhere
Drag the palMove 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 and Quit
🔊Sounds on or off

From a terminal, after setup (or with npx --allow-git=all github:rikinshah787/dotpals <command>):

dotpals start       # open the floating pal
dotpals dashboard   # open the dashboard
dotpals status      # what's running and connected
dotpals bridge      # only the bridge, e.g. on a machine without a desktop; dashboard at http://127.0.0.1:5175/dashboard

Privacy

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. 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.

How it works

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
  1. 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.
  2. 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.
  3. 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.

Plug in any agent

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).

HarnessHow it connectsSetup
Claude CodeHooks for live state (including permission prompts), plus the session transcript, so the history is complete even if the pal opened lateThe plugin
Codex (CLI, IDE extension, app)Follows Codex's session logs in ~/.codex/sessionsNone. 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 anythingConnect on the dashboard's Agents page
Gemini CLIHooks in ~/.gemini/settings.json: prompts, every tool call, permission prompts, repliesConnect on the Agents page
OpenCodeA plugin in ~/.config/opencode/plugins/: prompts, tool calls, permission prompts, the end of each turnConnect on the Agents page, then restart OpenCode
GitHub Copilot CLIHooks in ~/.copilot/hooks/dotpals.json: prompts, tool calls, permission prompts, stopConnect on the Agents page
Anything elsePOST JSON to http://127.0.0.1:5175/eventA 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.

The event format

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:

# a tool call starts…
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" } }
}'

# …and finishes
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" } }
}'

# a file edit, with its diff
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;" } }
}'
FieldValues
sessionany id; each session gets its own pal and tab
harness, labelshown on the tab, e.g. "My-agent · my-project"
state, textthe pal's state (see Agent states) and bubble text
activity.kindprompt · read · edit · write · run · search · web · agent · mcp · skill · plan · tool · done · error
activity.statusrunning · 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.

Use the pal in your own app

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.

Characters

IdPalClick action
bluBlu, a blue cloud in a beretjump
hopHop, a green frogjump
sunnySunny, a yellow gumdrop in glasseswiggle
loviLovi, a pink heart in sunglasseslove
museMuse, a violet flame with sparklesspin
grokGrok, a slate bot with a glowing visornod
novaNova, an orange bot with a light-bulb antennajump
byteByte, a teal cat with pixel eyeswiggle

Quick start

<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';

Agent states

StateWhat the pal does
idlebreathes, blinks and follows the cursor
listeningleans in with wide eyes, for while the user is typing
thinkinglooks up, shows a bubble with bouncing dots
workingbusy bob, eyes down, shows a progress bar or your text (e.g. the tool name); after 90 seconds, a sweat drop now and then
speakingmouth moves, for while tokens stream in
waitinghops, then keeps bouncing with wide eyes, shows a ? bubble or your text (e.g. "Allow edit?")
donejumps with a burst of sparkles and happy eyes, then settles back to calm
errorjitters, then looks sad and desaturated, with × eyes
sleepingeyes closed, floating zs

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' });

Plug into your harness

1. Stream events straight in

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.

2. Call it from your own event loop

import { agentHandler } from 'dotpals';

const onEvent = agentHandler(pal);

for await (const event of myAgent.run(prompt)) {
  onEvent(event); // unknown events are ignored
  render(event);
}

Events it understands

SourceEventsState
Anthropic Messages API (streaming)message_startthinking
content_block_start with a thinking blockthinking
content_block_start with a tool_use blockworking, with the tool name
content_block_start with a text blockspeaking
message_stopdone
Claude Agent SDKsystem / initthinking
assistant message with a tool_useworking, with the tool name
assistant message with textspeaking
resultdone, or error if it failed
OpenAI Responses API (streaming)response.createdthinking
response.output_item.added with a function callworking
response.output_text.*speaking
response.completeddone
response.failederror
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.

Custom mapping

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
  },
});

Runnable example

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.

More ways to use a pal

// Loading 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);

Faces and reactions

  • 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-poke with { count }. static turns these off.
  • Tiny pals: under 48 px a pal becomes an avatar (the tiny attribute and the read-only pal.tiny property): 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.emotes lists every emote.

Attributes

AttributeValuesDefault
characterany id from the table above, or a registered nameblu
stateidle · listening · thinking · working · speaking · waiting · done · error · sleepingidle
moodneutral · happy · sad · surprised · thinking · sleepy · shy · listening · working · speaking · waitingneutral
sizenumber (px) or any CSS length160px
colorany CSS colorthe character's color
idlebreathe · bounce · float · wobble · sway · nonebreathe
lookcursor · nonecursor
leannone: the body doesn't lean toward the cursorleans a little
staticboolean: turns off the hover and click reactions–
labelaccessible namethe character's name
tinyset 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.

Events

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

Styling

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.

Frameworks

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.

Add your own character

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 optionally ink (their color), glow (true or a color) and own (expressions your eyes already do well, e.g. ['wide']). While they show, the parts marked class="dp-eyes" hide (or the .dp-blink parts). Without eyes, 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.

Accessibility

  • Each pal has role="img" and an aria-label that 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 floating zs, state entry moves and the hover, click and dizzy moves.
  • Speech bubbles are decorative. Keep your own visible status text for screen-reader users.

Documentation

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.

Roadmap

  • "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.

Contributing

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.

Trademarks

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.

License

MIT

ai-agents
antropic
bot
claude-code
codex
coding-agent
developer-tools
dots
electron
grok
muse
openai
subagents
web-components

Rikinshah787/dotpals

See what your coding agent actually did. A floating dot style pal for Claude Code, Codex and any agent: every file changed, command run and step taken, in plain words.

HTML

2

17 commits

updated Oct 1, 2026

See the code

See what people are saying

SourceMessageScoreDate

[dotpals] - A desktop pal that tells you in plain words what your AI coding agent actually did (r/SideProject)

My friend codes almost entirely with AI agents. The agent says "Done!", he says "cool", and he has no idea what happened in between: which files changed, what failed, what got retried. So I built dotpals: a small animated pal that sits on your desktop and tells you in plain words what your coding…

1

Oct 1, 2026

README

dotpals

See what your coding agent actually did.

A small floating pal that watches Claude Code, Codex or any agent and tells you, in plain words, what happened: which files changed, which commands ran, what failed, and what the agent says it did.

npx --allow-git=all github:rikinshah787/dotpals setup

One command on Windows, macOS or Linux. Free, open source, and everything stays on your computer.

CI License: MIT Works with Claude Code and Codex

The dotpals notch: a live diff types in, an agent asks to run git push --force with a warning, Allow is clicked, and the pal celebrates

🔊 Watch the launch video with sound (47 s, 1080p) · square cut

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

Why

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 says fixed on try 2 or still failing after 3 tries.
  • 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.

Features

The dotpals window: a blue pal above a Summary card listing a request, Claude's own summary, and a tally of changed files, commands and skills

  • The story, not the log: each request reads as a few chapters, such as Changed 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: .env changed, a force-push, the same command failing 3 times, two agents editing the same file, or code changed without testing it.
  • 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 says Tests passed at 7:08 PM, after the last change, Tests passed at 7:08 PM · 3 files changed since or No tests run by the agent, and whether the last commit was tested. Test results come from the test output (fail 0, 5 passed), not just the exit code. Only tests the agent ran count.
  • 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 statusline once).
  • Summary: one card per request, with what you asked, what the agent said it did, and a tally such as Changed 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.
  • 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. See below.
  • 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.

The Tools tab with an Edit opened, showing its diff   The Files tab listing changed, new and read files

The dashboard's Sessions page: a session list, and one session's requests with the agent's summary, changed files, commands and skills

The notch

The notch, open on the Now tab: Claude's pal on the left, a live diff of Settings.tsx typing in, its plan at step 2 of 4, its context window, three helpers, and usage bars for Claude and Codex

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 island peeks 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 with Deny and Allow, or Ctrl+Alt+N and Ctrl+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 with Copy today, whether the code was tested since its last change, the plan, helpers, the context window with Copy /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.

Make your own pal

Eighteen home-made pals: round, boxy, fluffy, pointy, heart and frog bodies in different colors, with googly eyes, visors, pixel eyes and shades, and sprouts, crowns, horns, bows, antennas and berets on top

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">.

Install

One command

npx --allow-git=all github:rikinshah787/dotpals setup

That's all. It:

  1. installs the desktop pal in ~/.dotpals, and downloads its runtime (Electron, about 100 MB, once),
  2. adds the Claude Code plugin, if Claude Code is installed,
  3. picks up Codex automatically, if it's installed,
  4. starts the pal, turns on open when I log in, and opens the dashboard.

--allow-git=all lets npm 12 and newer install straight from GitHub; older npm ignores it. Options: --no-claude (skip the plugin), --no-login (don't start at login) and --no-start. Run it again any time to update.

Only the Claude Code plugin

/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.

Codex

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.

Cursor, Gemini CLI, OpenCode and GitHub Copilot CLI

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:

AgentWhat 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)
OpenCodeadds ~/.config/opencode/plugins/dotpals.js. Restart OpenCode to load it
GitHub Copilot CLIadds ~/.copilot/hooks/dotpals.json

Any other agent

Send JSON to the local bridge from your agent loop, a hook script or a wrapper. See Plug in any agent.

Using it

Ctrl+Alt+P (⌘⌥P on macOS)Show or hide the pal from anywhere
Drag the palMove 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 and Quit
🔊Sounds on or off

From a terminal, after setup (or with npx --allow-git=all github:rikinshah787/dotpals <command>):

dotpals start       # open the floating pal
dotpals dashboard   # open the dashboard
dotpals status      # what's running and connected
dotpals bridge      # only the bridge, e.g. on a machine without a desktop; dashboard at http://127.0.0.1:5175/dashboard

Privacy

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. 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.

How it works

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
  1. 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.
  2. 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.
  3. 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.

Plug in any agent

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).

HarnessHow it connectsSetup
Claude CodeHooks for live state (including permission prompts), plus the session transcript, so the history is complete even if the pal opened lateThe plugin
Codex (CLI, IDE extension, app)Follows Codex's session logs in ~/.codex/sessionsNone. 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 anythingConnect on the dashboard's Agents page
Gemini CLIHooks in ~/.gemini/settings.json: prompts, every tool call, permission prompts, repliesConnect on the Agents page
OpenCodeA plugin in ~/.config/opencode/plugins/: prompts, tool calls, permission prompts, the end of each turnConnect on the Agents page, then restart OpenCode
GitHub Copilot CLIHooks in ~/.copilot/hooks/dotpals.json: prompts, tool calls, permission prompts, stopConnect on the Agents page
Anything elsePOST JSON to http://127.0.0.1:5175/eventA 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.

The event format

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:

# a tool call starts…
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" } }
}'

# …and finishes
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" } }
}'

# a file edit, with its diff
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;" } }
}'
FieldValues
sessionany id; each session gets its own pal and tab
harness, labelshown on the tab, e.g. "My-agent · my-project"
state, textthe pal's state (see Agent states) and bubble text
activity.kindprompt · read · edit · write · run · search · web · agent · mcp · skill · plan · tool · done · error
activity.statusrunning · 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.

Use the pal in your own app

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.

Characters

IdPalClick action
bluBlu, a blue cloud in a beretjump
hopHop, a green frogjump
sunnySunny, a yellow gumdrop in glasseswiggle
loviLovi, a pink heart in sunglasseslove
museMuse, a violet flame with sparklesspin
grokGrok, a slate bot with a glowing visornod
novaNova, an orange bot with a light-bulb antennajump
byteByte, a teal cat with pixel eyeswiggle

Quick start

<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';

Agent states

StateWhat the pal does
idlebreathes, blinks and follows the cursor
listeningleans in with wide eyes, for while the user is typing
thinkinglooks up, shows a bubble with bouncing dots
workingbusy bob, eyes down, shows a progress bar or your text (e.g. the tool name); after 90 seconds, a sweat drop now and then
speakingmouth moves, for while tokens stream in
waitinghops, then keeps bouncing with wide eyes, shows a ? bubble or your text (e.g. "Allow edit?")
donejumps with a burst of sparkles and happy eyes, then settles back to calm
errorjitters, then looks sad and desaturated, with × eyes
sleepingeyes closed, floating zs

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' });

Plug into your harness

1. Stream events straight in

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.

2. Call it from your own event loop

import { agentHandler } from 'dotpals';

const onEvent = agentHandler(pal);

for await (const event of myAgent.run(prompt)) {
  onEvent(event); // unknown events are ignored
  render(event);
}

Events it understands

SourceEventsState
Anthropic Messages API (streaming)message_startthinking
content_block_start with a thinking blockthinking
content_block_start with a tool_use blockworking, with the tool name
content_block_start with a text blockspeaking
message_stopdone
Claude Agent SDKsystem / initthinking
assistant message with a tool_useworking, with the tool name
assistant message with textspeaking
resultdone, or error if it failed
OpenAI Responses API (streaming)response.createdthinking
response.output_item.added with a function callworking
response.output_text.*speaking
response.completeddone
response.failederror
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.

Custom mapping

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
  },
});

Runnable example

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.

More ways to use a pal

// Loading 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);

Faces and reactions

  • 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-poke with { count }. static turns these off.
  • Tiny pals: under 48 px a pal becomes an avatar (the tiny attribute and the read-only pal.tiny property): 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.emotes lists every emote.

Attributes

AttributeValuesDefault
characterany id from the table above, or a registered nameblu
stateidle · listening · thinking · working · speaking · waiting · done · error · sleepingidle
moodneutral · happy · sad · surprised · thinking · sleepy · shy · listening · working · speaking · waitingneutral
sizenumber (px) or any CSS length160px
colorany CSS colorthe character's color
idlebreathe · bounce · float · wobble · sway · nonebreathe
lookcursor · nonecursor
leannone: the body doesn't lean toward the cursorleans a little
staticboolean: turns off the hover and click reactions–
labelaccessible namethe character's name
tinyset 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.

Events

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

Styling

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.

Frameworks

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.

Add your own character

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 optionally ink (their color), glow (true or a color) and own (expressions your eyes already do well, e.g. ['wide']). While they show, the parts marked class="dp-eyes" hide (or the .dp-blink parts). Without eyes, 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.

Accessibility

  • Each pal has role="img" and an aria-label that 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 floating zs, state entry moves and the hover, click and dizzy moves.
  • Speech bubbles are decorative. Keep your own visible status text for screen-reader users.

Documentation

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.

Roadmap

  • "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.

Contributing

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.

Trademarks

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.

License

MIT

ai-agents
antropic
bot
claude-code
codex
coding-agent
developer-tools
dots
electron
grok
muse
openai
subagents
web-components

Languages

HTML

56.2%

JavaScript

39.1%

CSS

4.7%