Architecture Decision Records

ADR-011: Native macOS agent-usage menu bar (Rust truth, Swift shell)

Host menu-bar usage app reuses jackin-usage probes via UniFFI; Swift is display-only; Agent::ALL + existing UsageSurface only.

Status: Accepted

The macOS agent-usage menu bar is a host process that shows account quota / spend windows for jackin-supported agents without requiring an open Capsule session. Rust owns all probing, cache, cooldown, severity, and view shaping (jackin-usage + jackin-protocol contracts). Swift owns only display and OS integration (menu bar, popover, Settings, a11y, signing). The UniFFI facade is TableRock-shaped: synchronous coarse commands over a staticlib → XCFramework, with Swift polling immutable snapshots.

Why this is recorded

CodexBar proves demand for a menu-bar usage product, but a pure-Swift probe stack would diverge from Capsule truth and double maintenance. TableRock already established the correct split for tailrocks native apps. This ADR freezes that split for jackin and the hard agent/surface scope so contributors do not reintroduce provider sprawl or second probe matrices.

Agent and surface scope (hard)

Only jackin_core::Agent::ALL: claude, codex, amp, kimi, opencode, grok.

Usage surfaces: existing jackin_usage surfaces — Claude, Codex, Amp, Grok, OpenCode, Kimi, plus Z.AI / GLM and MiniMax when modeled for routed providers. Never Cursor, Gemini, Copilot, or CodexBar’s provider zoo.

Host credential roots (Phase 0 inventory)

Probes already prefer host homes first, container handoff paths last. Menu-bar process runs with a normal operator HOME.

SurfaceHost credential rootsEnv / otherContainer handoff (last resort)
Claude$CLAUDE_CONFIG_DIR / ~/.claude/.credentials.json, ~/.claude.jsonANTHROPIC_API_KEY, ANTHROPIC_AUTH_TOKEN/jackin/claude/credentials.json
Codex$CODEX_HOME/auth.json / ~/.codex/auth.json/jackin/codex/auth.json
AmpAmp secrets under home / handoff secretsAmp API key loaders/jackin/amp/secrets.json
Grok~/.grok auth + binary under ~/.grok/bin/jackin/grok/auth.json
Kimi~/.kimi-code, ~/.kimi local tokensKIMI_AUTH_TOKEN, KIMI_CODE_API_KEY/jackin/kimi-code
OpenCodeOpenCode home paths used by existing probe
Z.AI / GLMZAI_API_KEY / Z_AI_API_KEY (+ provider_keys map)
MiniMaxMINIMAX_CODING_API_KEY / MINIMAX_API_KEY

Shared host coordination root (one tree for host processes and every container):

Host processes and containers coordinate through ~/.jackin/data/usage-shared/{snapshots,cooldowns,locks} (containers bind-mount the same root at /jackin/usage-shared and set JACKIN_USAGE_{SNAPSHOTS,COOLDOWN,LOCK}_DIR). At most one probe per account host-wide; every surface reads the same per-account snapshot. Env vars override the defaults for tests only.

  • ~/.jackin/data/usage-shared/cooldowns (JACKIN_USAGE_COOLDOWN_DIR)
  • ~/.jackin/data/usage-shared/snapshots (JACKIN_USAGE_SNAPSHOTS_DIR)
  • ~/.jackin/data/usage-shared/locks (JACKIN_USAGE_LOCK_DIR)

Menu-bar durable store (host-only, not container paths):

  • ~/.jackin/data/usage-menu-bar/snapshots.db
  • ~/.jackin/data/usage-menu-bar/accounts.json
  • Preferences: enable set + refresh floor under app Support / UserDefaults (policy defaults from Rust)

Public probe entry points (jackin-usage)

EntryRole
usage::{claude,codex,amp,grok,kimi,minimax,zai}_snapshot (+ OpenCode)Provider probes (crate-internal; host runtime calls via refresh)
UsageCache::refresh_active_account_snapshotsParallel probes, locks, cooldown, shared snapshot write
UsageCache::focused_snapshot / focused_status_bar_labelCached views + honest refreshing / unavailable
UsageCache::request_account_refreshForce-due one surface
usage::host::HostUsageRuntimeCapsule-free host orchestration for menu bar + CLI
run_claude_usage_diagnosticExisting Claude diagnostic helper

Presentation never invents percentages: only render FocusedUsageView / bucket fields; unavailable / refreshing / stale stay honest.

Architecture

Swift `LSUIElement` menu bar (display only)
  → @MainActor presentation from immutable DTOs
  → UniFFI jackin-usage-ffi (staticlib / XCFramework)
  → HostUsageRuntime + jackin-usage probes (Rust)

Swift must not own HTTP/RPC/OAuth/CLI scrapes or a second provider matrix.

Distribution

Immutable GitHub Release artifact jackin-desktop-<VERSION>-aarch64-apple-darwin.zip (exactly JackinDesktop.app), produced only after Developer ID codesign (hardened runtime + secure timestamp), notarytool Accepted, staple, and Gatekeeper assessment. Supply-chain sidecars match CLI archives: .sha256, cosign .bundle, CycloneDX .sbom.json, plus a separate GitHub build-provenance attestation. Homebrew cask jackin-desktop advances with the stable formula in one tap PR; the first cask is never auto-merged. Identity supersession (2026-07-22): product is jackin Desktop (Jackin Desktop / com.jackin-project.desktop); the previous pre-release names JackinUsageMenuBar / jackin-usage-menu-bar never shipped as a notarized release. Uninstall leaves no root daemon. v1 does not require the jackin daemon.

Consequences

  • Capsule TUI usage dialog remains; both consumers share Rust views.
  • New agents land via Agent / UsageSurface growth in Rust, then appear in the menu bar automatically through HostUsageRuntime surface lists.
  • Notarization secrets may be operator-gated residual; host library + UniFFI + app shell ship without them.

On this page