Skip to content

Statusline Integration

Ivan Seredkin edited this page May 23, 2026 · 2 revisions

Statusline Integration

How the live cost line ends up in the engineer's Claude Code window, Cursor / VS Code status bar, and the cloud dashboard. The shared JSON shape across all three surfaces is pinned in docs/statusline-contract.md; this page is the integration narrative on top of it.

The same daemon endpoint — GET http://127.0.0.1:7878/analytics/statusline — serves the CLI statusline, the Cursor extension, the VS Code extension, and the cloud dashboard. Provider is an explicit query parameter rather than a per-surface schema. New agent coverage slots into the same shape; new surfaces consume the same JSON.

The shape

A single JSON document with cost windows, optional context-scoped fields, and provider-scope hints. The canonical version of the shape, with field-by-field semantics, is in docs/statusline-contract.md. Highlights:

{
  "cost_1d": 2.34,          // rolling last 24 h, dollars
  "cost_7d": 12.50,         // rolling last 7 d, dollars
  "cost_30d": 48.10,        // rolling last 30 d, dollars
  "provider_scope": "claude_code",   // present iff exactly one provider was requested
  "contributing_providers": [...],   // present iff multi-provider host scope
  "session_cost": 0.41,     // present iff session_id was passed
  "branch_cost": 8.70,      // present iff branch was passed
  "project_cost": 12.10,    // present iff project_dir was passed
  "active_provider": "claude_code",
  "cost_lag_hint": "..."    // present iff Cursor data is in the response
}

The windows are rolling, not calendar — cost_1d = spend in the last 24 hours, not "today". budi stats and the cloud dashboard's cost charts keep calendar semantics; rolling windows live only on the statusline surface and the editor extensions that render it.

Two scopes, one endpoint

Scope Query Where it's used
Provider-scoped ?provider=cursor (single) Cloud dashboard provider tiles, Claude Code statusline, per-provider drill-downs. No blending across providers.
Host-scoped ?provider=cursor,copilot_chat (comma-list) VS Code / Cursor extension status bar in editors where multiple agents run side-by-side. Aggregates the listed providers and populates contributing_providers for tooltip rendering and click-through routing.

The repeated-query form ?provider=a&provider=b is not supported (axum's default Query extractor keeps only the last value). Callers that need multi-provider must use the comma-list.

Slot vocabulary

STATUSLINE_SLOTS in crates/budi-core/src/config.rs is the canonical list:

1d  7d  30d  session  message  branch  project  provider

Default install slots are ["1d", "7d", "30d"]. The legacy today / week / month names are normalized to 1d / 7d / 30d at load time so older statusline.toml files keep rendering after an upgrade.

Legacy preset = "coach" / preset = "full" values in older configs are silently mapped to ["session", "message"] / ["session", "message", "1d"] at load time. The preset key itself has been removed from the documented surface; the legacy health slot (a vestigial alias for session_cost) has been removed entirely.

Why the daemon, not the cloud

Every statusline surface hits the local daemon at 127.0.0.1:7878, never the cloud endpoint directly. Three reasons:

  • Latency. Claude Code re-renders the statusline on every prompt; we cannot afford a cross-internet round-trip per render.
  • Offline. The engineer sees an honest number even on a plane. The local DB has every row; the cloud is an aggregation destination, not the source of truth.
  • Auth. The daemon already trusts the local user (loopback-only listener). Flashing the cloud API key on every render is unnecessary.

The cloud dashboard reads its own Postgres tables, populated by the sync worker. It MUST render windows labeled 1d / 7d / 30d and MUST filter by a single provider in provider-scoped tiles, but it serves its own surface — the cloud is not in the statusline hot path.

Install paths

budi init is the canonical install entry. It is idempotent and wires both integrations by default when their host is detected on PATH:

  1. Claude Code statusline. Merges the statusLine command into ~/.claude/settings.json. Re-running never duplicates the entry; preserves any user-configured fragment. The line invoked is budi statusline --format claude --provider claude_code.
  2. Cursor extension. Installs from the VS Code Marketplace when the Cursor CLI is on PATH. Skips silently if the extension is already present.

Opt-out:

budi init --no-integrations        # skip both at install time
budi integrations install --with claude-code-statusline   # add later
budi integrations install --with cursor-extension          # add later

budi doctor flags a missing Claude Code statusline when ~/.claude exists but ~/.claude/settings.json carries no Budi-backed statusLine. The doctor warn includes the exact budi integrations install command to repair it. Install paths that legitimately want no Claude integration (CI, containers, hand-rolled settings) suppress the nudge by running budi init --no-integrations from day one.

Render contract

The CLI renderer prints a single line, with a state indicator, the product name, and one or more cost slots. Default:

🟢 budi · $1.24 1d · $8.50 7d · $30.10 30d

State indicator semantics:

Indicator Meaning
🟢 Daemon healthy, data fresh
🟡 Daemon up, data stale (e.g. Cursor Usage API has not caught up yet — see cost_lag_hint)
🔴 Daemon down — script prints a nudge to run budi doctor and exits 0 (never blocks the host)
Daemon running but not enrolled — surfaced when the daemon is installed but budi init has not been completed

Failure modes always exit 0. We never block Claude Code, Cursor, or VS Code on a budi failure.

The Cursor extension (siropkin/budi-cursor) is a status-bar-only VS Code extension — no sidebar, no session list, no vitals panel. It spawns budi statusline --format json --provider cursor, mirrors the Claude Code line byte-for-byte, and click-through opens the same cloud URL the Claude Code statusline opens. The extension checks the daemon's api_version on startup (MIN_API_VERSION = 1) and warns if incompatible.

Stability guarantees

  • Field names in the contract are stable across minor releases. New optional fields may be added; existing fields are not renamed or re-typed.
  • Deprecated fields are kept populated for at least one minor release and removed no sooner than the next major release. Current deprecations: today_cost, week_cost, month_cost — populated with the same rolling values as cost_1d / cost_7d / cost_30d for backward compatibility.
  • Breaking changes to this contract require a design review.

Reference implementations

Out of scope here

Clone this wiki locally