AI Agent Dashboard

A real-time desktop widget that tracks what your AI coding agents are doing.

View on GitHub
Home Claude Code HTTP API Development

The widget listens on http://127.0.0.1:9077 (default) for lifecycle events from external agents. One endpoint, one envelope shape, adapter-dispatched on the server side.

Endpoint

POST /api/event with Content-Type: application/json. Returns 204 No Content on success, 403 if the Origin header is a real web origin (blocks browser XHR), 400 on malformed JSON.

Envelope

{
  "client": "claude",
  "event": "UserPromptSubmit",
  "payload": { ... raw agent payload ... }
}

Claude Code events

The claude adapter recognizes five events. Other event names are silently ignored.

event Derived status Label source
SessionStart idle
UserPromptSubmit working payload.prompt (whitespace-collapsed, chrome-stripped)
Notification awaiting (usually); done if notification_type == "idle_prompt" and the last assistant turn doesn’t end with ? "needs approval: <tool>" / "plan approval" / the raw message (truncated to 60 chars)
Stop done; flips to awaiting if the last assistant turn ends with ? (minus configured benign closers) "has a question" when flipped
SessionEnd — (emits a clear, removing the row)

The adapter derives a friendly chat_id from payload.cwd and the projects_root config setting; see the Claude Code page for chat-id rules.

Sticky label state machine

A session’s display label is not always the latest label produced by the adapter:

A task boundary — transitioning from done or idle into working — resets the original prompt to whatever label the boundary event carried. An approval cycle — working → awaiting → working — preserves it. This is what keeps “fix foo.py” visible on screen while Claude asks for a bash approval, and what flips it back to a fresh prompt after the task finishes.

When an adapter emits label: None on a set, the row keeps its previous label. Useful when the adapter is just changing status without a new description.

Port

The widget listens on server_port from config.json (default 9077). The Claude hook resolves its URL from $TAURI_DASHBOARD_URL, falling back to http://127.0.0.1:9077.

Adding a new client

Writing a new adapter is a ~100 LOC pure Rust function: src-tauri/src/adapters/<your_client>.rs exposing dispatch(event, payload, cfg) -> AdapterOutput, plus a match arm in adapters::dispatch. See src-tauri/src/adapters/claude.rs for the reference implementation. No HTTP layer changes — the envelope already carries client as the discriminator.

Standard features