# ADR-011: Native macOS agent-usage menu bar (Rust truth, Swift shell) (https://jackin.tailrocks.com/reference/adrs/adr-011-native-macos-usage-menu-bar/)



**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 [#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) [#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) [#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`.

| Surface    | Host credential roots                                                  | Env / other                                           | Container handoff (last resort)   |
| ---------- | ---------------------------------------------------------------------- | ----------------------------------------------------- | --------------------------------- |
| Claude     | `$CLAUDE_CONFIG_DIR` / `~/.claude/.credentials.json`, `~/.claude.json` | `ANTHROPIC_API_KEY`, `ANTHROPIC_AUTH_TOKEN`           | `/jackin/claude/credentials.json` |
| Codex      | `$CODEX_HOME/auth.json` / `~/.codex/auth.json`                         | —                                                     | `/jackin/codex/auth.json`         |
| Amp        | Amp secrets under home / handoff secrets                               | Amp API key loaders                                   | `/jackin/amp/secrets.json`        |
| Grok       | `~/.grok` auth + binary under `~/.grok/bin`                            | —                                                     | `/jackin/grok/auth.json`          |
| Kimi       | `~/.kimi-code`, `~/.kimi` local tokens                                 | `KIMI_AUTH_TOKEN`, `KIMI_CODE_API_KEY`                | `/jackin/kimi-code`               |
| OpenCode   | OpenCode home paths used by existing probe                             | —                                                     | —                                 |
| Z.AI / GLM | —                                                                      | `ZAI_API_KEY` / `Z_AI_API_KEY` (+ provider\_keys map) | —                                 |
| MiniMax    | —                                                                      | `MINIMAX_CODING_API_KEY` / `MINIMAX_API_KEY`          | —                                 |

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

Host processes and containers coordinate through &#x2A;*`~/.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`) [#public-probe-entry-points-jackin-usage]

| Entry                                                                   | Role                                                             |
| ----------------------------------------------------------------------- | ---------------------------------------------------------------- |
| `usage::{claude,codex,amp,grok,kimi,minimax,zai}_snapshot` (+ OpenCode) | Provider probes (crate-internal; host runtime calls via refresh) |
| `UsageCache::refresh_active_account_snapshots`                          | Parallel probes, locks, cooldown, shared snapshot write          |
| `UsageCache::focused_snapshot` / `focused_status_bar_label`             | Cached views + honest `refreshing` / `unavailable`               |
| `UsageCache::request_account_refresh`                                   | Force-due one surface                                            |
| `usage::host::HostUsageRuntime`                                         | Capsule-free host orchestration for menu bar + CLI               |
| `run_claude_usage_diagnostic`                                           | Existing Claude diagnostic helper                                |

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

## Architecture [#architecture]

```text
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 [#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 [#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.
