{"owner":"steipete","repo":"CodexBar","hasSkills":true,"totalSkillsCount":12,"totalTokensCount":23033,"categories":["anthropic-skill","copilot-instructions","root-instruction","project-spec","claude-rule"],"hasMcp":false,"mcpConfig":null,"found":[".agents/skills/qa-test/SKILL.md",".agents/skills/codexbar/SKILL.md","docs/copilot.md","AGENTS.md",".agents/skills/release-codexbar/SKILL.md","docs/architecture.md","docs/claude.md","docs/llms.txt","docs/deepseek.md","docs/mistral.md",".agents/skills/qa-test/references/api-specs.md",".agents/skills/qa-test/agents/openai.yaml"],"skills":{".agents/skills/qa-test/SKILL.md":"---\nname: qa-test\ndescription: \"CodexBar live QA/e2e testing: run provider usage matrix checks, validate real app config, use Peekaboo for menu proof, use Browser Use/official docs for API spec or logged-in dashboard checks, and handle 1Password credentials safely.\"\n---\n\n# CodexBar Live QA\n\nUse for live provider testing, release smoke tests, menu verification, or debugging “provider works/fails” reports.\n\n## Rules\n\n- Work from the CodexBar repo checkout.\n- Use the packaged CLI first: `CodexBar.app/Contents/Helpers/CodexBarCLI`.\n- Do not use `CodexBar.app/Contents/MacOS/codexbar`; that is the app binary and may appear to hang as a CLI.\n- Never run broad `env`, `set`, or secret regex dumps.\n- Use `$one-password` for secrets: all `op` commands inside one persistent tmux session, service account first, no raw secret output.\n- Treat browser-cookie/keychain flows as prompt-risky. Prefer CLI/API-token checks and `KeychainNoUIQuery`-safe tests unless the user explicitly requested live UI.\n- For current API behavior, browse official provider docs only.\n\n## CLI Matrix\n\nRun the bundled script:\n\n```bash\n.agents/skills/qa-test/scripts/live_provider_matrix.sh --enabled\n```\n\nUseful modes:\n\n```bash\n.agents/skills/qa-test/scripts/live_provider_matrix.sh --provider all\n.agents/skills/qa-test/scripts/live_provider_matrix.sh --providers openai,zai,deepseek\n.agents/skills/qa-test/scripts/live_provider_matrix.sh --default\n```\n\nInterpretation:\n\n- `--enabled` asks `CodexBarCLI config providers` for enabled providers, honoring `CODEXBAR_CONFIG` and default toggles.\n- `--default` runs the app-facing default command with no provider override.\n- `--provider all` forces every registered provider and is expected to fail for providers without sessions/keys.\n- A green app config needs `--enabled` and `--default` clean; `--provider all` is a discovery/triage tool.\n\n## Config QA\n\nValidate config:\n\n```bash\nCodexBar.app/Contents/Helpers/CodexBarCLI config validate\nstat -f '%Lp %N' \"$HOME/.codexbar/config.json\"\n```\n\nRedact config shape:\n\n```bash\njq '(.providers // []) |= map(.apiKey = (if .apiKey then \"<redacted>\" else .apiKey end) |\n  .secretKey = (if .secretKey then \"<redacted>\" else .secretKey end) |\n  .cookieHeader = (if .cookieHeader then \"<redacted>\" else .cookieHeader end) |\n  (if .id == \"stepfun\" and has(\"region\") then .region = \"<redacted>\" else . end) |\n  .tokenAccounts = (if .tokenAccounts then (.tokenAccounts | .accounts = (.accounts | map(.token = \"<redacted>\"))) else .tokenAccounts end))' \\\n  \"$HOME/.codexbar/config.json\"\n```\n\nBefore editing config, make a backup:\n\n```bash\ncp \"$HOME/.codexbar/config.json\" \"$HOME/.codexbar/config.pre-qa-$(date +%Y%m%d%H%M%S).json\"\nchmod 600 \"$HOME/.codexbar\"/config.pre-qa-*.json\n```\n\n## Live Menu QA\n\nUse Peekaboo after CLI checks:\n\n```bash\npkill -x CodexBar || pkill -f 'CodexBar.app/Contents/MacOS/CodexBar' || true\nopen -n \"$PWD/CodexBar.app\"\npeekaboo menu list-all --json | rg -i 'codexbar'\npeekaboo menu click-extra --title codexbar-merged --json\nscreencapture -x /tmp/codexbar-live-menu.png\n```\n\nCrop top-right menu if needed:\n\n```bash\nsips --cropToHeightWidth 900 340 --cropOffset 20 2650 /tmp/codexbar-live-menu.png \\\n  --out /tmp/codexbar-live-menu-crop.png >/dev/null\n```\n\nVerify visually with `view_image`. Confirm provider tabs/rows match enabled config and no failing provider dominates the first screen.\n\n## Browser Use\n\nUse `$browser-use` only when a logged-in dashboard, API key page, or provider docs need browser/profile state.\n\nExisting Chrome path:\n\n```bash\nmcporter call chrome-devtools.list_pages --args '{}' --output text\nmcporter call chrome-devtools.navigate_page --args '{\"url\":\"https://provider.example\"}' --output text\nmcporter call chrome-devtools.take_snapshot --args '{}' --output text\n```\n\nIf Browser Use is unavailable, say so and use web search for public official docs; do not substitute isolated Playwright for login/profile-dependent pages.\n\n## Fix Triage\n\n- Missing auth/session: configure key/session if available; otherwise leave provider disabled or report blocked auth.\n- Wrong provider API/spec: inspect official docs, then patch fetcher/settings/tests.\n- Provider key exists but live API rejects it: keep key stored if useful, disable provider if the menu would show a persistent error.\n- User-facing behavior changes need `CHANGELOG.md`.\n- Code fixes need focused tests, `make check`, `$autoreview`, and live CLI proof before landing.\n\n## Known CodexBar QA Notes\n\n- OpenAI Admin API key is the useful usage provider key. Project `OPENAI_API_KEY` values can fail legacy credit-balance fallback with 403.\n- Deepgram usage requires a key/project with Management API permissions; transcription-only keys can return 403.\n- Groq usage uses the Prometheus metrics API, not ordinary inference endpoints.\n- MiniMax pay-as-you-go API keys and Token Plan/Coding Plan keys are different; wrong key kind can leave usage unavailable.\n",".agents/skills/codexbar/SKILL.md":"---\nname: codexbar\ndescription: \"CodexBar read. Provider usage, limits, credits, config health. JSON. No writes.\"\n---\n\n# CodexBar\n\nRead CodexBar. Never mutate config/auth.\n\n## Run\n\n```bash\nskill=\"${CODEX_HOME:-$HOME/.codex}/skills/codexbar\"\n\"$skill/scripts/codexbar\" doctor\n\"$skill/scripts/codexbar\" providers\n\"$skill/scripts/codexbar\" usage\n\"$skill/scripts/codexbar\" usage --provider codex\n\"$skill/scripts/codexbar\" usage --all\n```\n\nAll stdout: JSON. Upstream CodexBar shape kept. Less drift, fewer tokens.\n\n## Rules\n\n- Start `doctor` when install/config unknown.\n- `usage` reads enabled providers. Prefer this.\n- `usage --provider ID` reads one provider.\n- `usage --all` expensive; use only when needed.\n- Identities hidden by default. `--include-identities` only when user explicitly needs them.\n- Secrets always hidden.\n- Helper read-only: fixed allowlist only. No config writes, auth repair, enable/disable, key storage.\n- Timeout means upstream stuck. Narrow provider or raise `CODEXBAR_TIMEOUT` (default 120 seconds).\n\n## Binary\n\nAuto-find: `CODEXBAR_BIN`, PATH, app bundle, Homebrew cask. If missing: open CodexBar, Preferences > Advanced > Install CLI; or set `CODEXBAR_BIN`.\n\nEach stdout/stderr stream capped at 1 MiB while fully drained. Timeout kills process group.\n","docs/copilot.md":"---\nsummary: \"Copilot provider data sources: GitHub device flow, Copilot internal usage API, and optional GitHub web budgets.\"\nread_when:\n  - Debugging Copilot login or usage parsing\n  - Updating GitHub OAuth device flow behavior\n---\n\n# Copilot provider\n\nCopilot uses GitHub OAuth device flow and the Copilot internal usage API for primary usage. Optional budget extras use GitHub web cookies only when enabled.\n\nCLI-only setups can run `codexbar config login --provider copilot` (or add `--json` for a stream of login\nevents). It uses the same device flow, issuer-aware account matching, and config-backed token accounts as\nthe app, without opening a browser or prompting for Keychain access. See [CLI configuration](cli-configuration.md#copilot-device-login).\n\n## Data sources + fallback order\n\n1) **GitHub OAuth device flow** (user initiated)\n   - Device code request:\n     - `POST https://github.com/login/device/code`\n   - Token polling:\n     - `POST https://github.com/login/oauth/access_token`\n   - Optional enterprise host:\n     - set Copilot `enterpriseHost` in `~/.codexbar/config.json` or the provider settings UI\n     - CodexBar normalizes values such as `https://octocorp.ghe.com/login` to `octocorp.ghe.com`\n     - device flow uses `https://<enterpriseHost>/login/...`\n     - identity lookup uses `https://api.<enterpriseHost>/user`, matching the usage API host\n     - default HTTPS ports and trailing host dots normalize to the same issuer; nondefault ports remain distinct\n   - Scope: `read:user`.\n   - Token stored in config:\n     - `~/.codexbar/config.json` → `providers[].apiKey` for `copilot`\n     - token accounts use `providers[].tokenAccounts`\n     - Legacy token matching honors a resolved stable GitHub user ID; display-label fallback is used only when token identity is unavailable and no verified match exists.\n     - Enterprise accounts match by API host and numeric user ID. They never adopt a public or unidentified legacy account merely because its login or label matches. The configured host still controls requests; stored account identifiers do not select an endpoint.\n     - Enterprise sign-in requires a resolved identity. A cancelled sign-in or a host change while it is pending leaves saved accounts unchanged.\n\n2) **Usage fetch**\n   - `GET https://api.github.com/copilot_internal/user`\n   - With an enterprise host, the API host is `api.<enterpriseHost>`.\n   - Headers:\n     - `Authorization: token <github_oauth_token>`\n     - `Accept: application/json`\n     - `Editor-Version: vscode/1.96.2`\n     - `Editor-Plugin-Version: copilot-chat/0.26.7`\n     - `User-Agent: GitHubCopilotChat/0.26.7`\n     - `X-Github-Api-Version: 2025-04-01`\n\n3) **Budget fetch** (optional GitHub web endpoint, best-effort)\n   - Available only for the public GitHub host. Enterprise quota fetches skip public identity and browser-cookie budget enrichment.\n   - Disabled by default. The Copilot provider's \"Budget extras\" setting must be enabled before CodexBar imports\n     github.com cookies or renders budget bars.\n   - Automatic cookie imports follow the shared supported-browser order and access gate; background imports never request Keychain permission.\n   - CodexBar asks the logged-in GitHub web endpoint for customer-scope budgets:\n     - `GET https://github.com/settings/billing/budgets?page=<page>&page_size=10&scope=customer`\n   - Headers:\n     - `Cookie: <github.com browser cookies>`\n     - `Accept: application/json`\n     - `X-Requested-With: XMLHttpRequest`\n     - `GitHub-Verified-Fetch: true`\n     - `X-Fetch-Nonce: <fresh nonce when available>`\n   - CodexBar first tries to read a fresh nonce from `https://github.com/settings/billing/budgets`, then calls the JSON\n     endpoint. If GitHub rejects the web request, CodexBar keeps the normal Copilot quota bars and omits budget bars.\n   - This is intentionally not the public GitHub REST billing API. The REST API did not expose the personal budget list\n     for the tested individual account.\n\n## Snapshot mapping\n- Primary: `quotaSnapshots.premiumInteractions` percent remaining → used percent.\n- Secondary: `quotaSnapshots.chat` percent remaining → used percent.\n- Extra: positive Copilot billing budgets from the GitHub web endpoint → `extraRateWindows`, only when \"Budget extras\"\n  is enabled.\n  - Product budget: `copilot`\n  - SKU budgets: `copilot_premium_request`, `copilot_agent_premium_request`, `spark_premium_request`\n- Seat AI credits: `quota_snapshots.premium_interactions.credits_used` → the shared \"Credits used\" provider-detail\n  row, shown only when it carries real signal (token-based billing, unlimited quota, nonzero credits, or a\n  configured seat entitlement) so a metered Pro/Individual seat never grows a permanent \"0 credits used\" row.\n  Deliberately not summed with `chat`/`completions` credits — GitHub can report the same pool under multiple\n  snapshot keys, and summing would double-count.\n- Reset dates are not provided by the API.\n- Plan label from `copilotPlan`.\n\n## AI credit entitlements\nGitHub does not publish an included-credit entitlement on any documented endpoint — all 8 billing endpoints plus\n`budgets`, `cost-centers`, and `usage/summary` were probed, and none returns a ceiling for the seat. The denominator\nis therefore user-entered:\n- Preferences → Providers → Copilot → \"Included AI credits (per seat)\"\n\nA configured entitlement turns the row into a progress bar (\"31 / 3000\") via the shared provider-detail row's\noptional progress ratio. Without one, the row stays plain text, because a bar would imply a limit CodexBar cannot\nactually know. Either way the row also carries the numeric credits used (`usageValue`), so editing or clearing\nthe entitlement rewrites the cached row (text ↔ bar) immediately, even when the follow-up refresh never lands\n(offline, token lost, 401).\n\nIn Automatic mode, the provider tab uses this configured seat-credit ratio when no metered quota window is\navailable. It follows the used/remaining preference. Existing quota windows retain priority, explicit metric\nselections retain their meaning, and a missing allowance leaves the tab's progress bar absent.\n\n## Manual seat credit allowance\n\nFor token-billed seats, **Seat AI credit allowance** optionally turns the menu card's Credits used text into a progress\nbar. Enter a positive monthly credit allowance; no ceiling is inferred from the plan. This is a local display preference,\nseparate from Budget extras, and does not set a GitHub billing limit. Icon, widget, and CLI quota windows are unchanged.\n\nEach saved account can override the legacy global allowance. Clearing an override restores that fallback, or returns to\ntext if no fallback exists. **Clear default allowance** removes the legacy fallback while retaining explicit account\noverrides; accounts inheriting that default return to text immediately, including while offline. Edits update the matching cached account without\nchanging its usage timestamp. Switching accounts preserves each account's usage; changing a credential or enterprise\nhost still invalidates its cached data. A refresh already in flight uses the current allowance when it publishes, so a\nlate successful response cannot restore a cleared default.\n\n## Key files\n- `Sources/CodexBarCore/Providers/Copilot/CopilotUsageFetcher.swift`\n- `Sources/CodexBarCore/Providers/Copilot/CopilotDeviceFlow.swift`\n- `Sources/CodexBarCore/Providers/Copilot/CopilotCreditEntitlementParser.swift`\n- `Sources/CodexBar/Providers/Copilot/CopilotLoginFlow.swift`\n- `Sources/CodexBar/CopilotTokenStore.swift` (legacy migration helper)\n","AGENTS.md":"# Repository Guidelines\n\n## Project Structure & Modules\n- `Sources/CodexBar`: Swift 6 menu bar app (usage/credits probes, icon renderer, settings). Keep changes small and reuse existing helpers.\n- `Tests/CodexBarTests`: XCTest coverage for usage parsing, status probes, icon patterns; mirror new logic with focused tests.\n- `Scripts`: build/package helpers (`package_app.sh`, `sign-and-notarize.sh`, `build_icon.sh`, `compile_and_run.sh`). Read-only release helpers call `Scripts/mac-release`, which resolves `MAC_RELEASE_TOOL` or the shared `agent-scripts` checkout; CI owns publication.\n- `docs`: release notes and process (`docs/RELEASING.md`, screenshots). Root-level zips/appcast are generated artifacts—avoid editing except during releases.\n\n## Build, Test, Run\n- Dev loop: `./Scripts/compile_and_run.sh` kills old instances, builds, packages, relaunches `CodexBar.app`, and confirms it stays running; add `--test` for the sharded full suite.\n- Quick build/test: `swift build` (debug) or `swift build -c release`; `make test` for the sharded full suite.\n- Package locally: `./Scripts/package_app.sh` to refresh `CodexBar.app`, then restart with `pkill -x CodexBar || pkill -f CodexBar.app || true; cd /Users/steipete/Projects/codexbar && open -n /Users/steipete/Projects/codexbar/CodexBar.app`.\n- Release publication is owned by `.github/workflows/release.yml`; use its default dry-run dispatch to rehearse. Repo build/signing stays in `Scripts/sign-and-notarize.sh`; see `docs/RELEASING.md`.\n\n## Coding Style & Naming\n- Enforce SwiftFormat/SwiftLint: run `swiftformat Sources Tests` and `swiftlint --strict`. 4-space indent, 120-char lines, explicit `self` is intentional—do not remove.\n- Favor small, typed structs/enums; maintain existing `MARK` organization. Use descriptive symbols; match current commit tone.\n\n## Testing Guidelines\n- Add/extend XCTest cases under `Tests/CodexBarTests/*Tests.swift` (`FeatureNameTests` with `test_caseDescription` methods).\n- Swift Testing: prefer backticked sentence names; no camelCase.\n- Model names in tests/code: released models or clearly fictitious names only; never expose unreleased names.\n- Iterate with focused runs (`make test-fast FILTER='SuiteName'` or `swift test --filter ...`) covering the touched suites. For a local full run use `./Scripts/test.sh --direct-workers 8` (parallel isolated groups, ~10 min) instead of serial `make test` (~45 min). The full suite is not a handoff gate: run it in the background or leave it to PR CI; never block on it in the foreground.\n- While iterating, format/lint only touched files; run `make check` once before commit/handoff and fix reported issues.\n- Prefer CLI/focused tests over app-bundle live tests when behavior can be verified without relaunching CodexBar.\n- Never run tests/checks or ad-hoc validation that can display macOS Keychain prompts. Live provider probes, browser-cookie imports, `codexbar usage` against real accounts, and real SecItem reads must be explicitly requested; otherwise use parser tests, stubs, test stores, or `KeychainNoUIQuery`.\n- App-group migration tests must inject dictionary-backed defaults, both snapshot URLs, a synthetic home, and a contained recording FileManager. UUID defaults suites and Keychain isolation flags do not isolate defaults search domains or filesystem access. Ordinary SettingsStore tests must not discover shared defaults or run app-group migration.\n- macOS CI is brittle around headless AppKit status/menu tests. Prefer covering menu behavior through stable state/model seams (`MenuDescriptor`, `ProvidersPane`, `CodexAccountsSectionState`, etc.) instead of constructing live `NSStatusBar`/`NSMenu` flows unless the AppKit wiring itself is the thing under test.\n\n## CI Efficiency\n- Follow [the direct-test and SwiftPM cache contracts](docs/DEVELOPMENT.md#macos-direct-test-groups) when changing CI or test execution; preserve full test inventory, deadlines, failure handling, cache verification, and clean fallback.\n- Reuse incremental builds during iteration; never copy compiled products between worktrees. Clean `.build` for diagnosed contamination, a cold-build comparison, or maintainer-requested cleanup.\n- Run focused tests during development; the final code needs focused tests plus `make check`, with the full suite from PR CI or a background run. Repeat successful full checks when relevant inputs or the environment change, a failure or unresolved concern requires it, or a maintainer requests it; do not repeat them solely to prepare a report.\n\n## Commit & PR Guidelines\n- Commit messages: short imperative clauses (e.g., “Improve usage probe”, “Fix icon dimming”); keep commits scoped.\n- PRs/patches should list summary, commands run, screenshots/GIFs for UI changes, and linked issue/reference when relevant.\n\n## Agent Notes\n- Use the provided scripts and package manager (SwiftPM); avoid adding dependencies or tooling without confirmation.\n- Menu bar automation: capture the target screen first and verify the CodexBar icon is visibly onscreen. Reject `click-extra` success when coordinates fall outside display bounds; hidden menu extras are not click proof.\n- Validate UI/runtime behavior against the freshly built bundle; restart via the pkill+open command above to avoid running stale binaries.\n- To guarantee the right bundle is running after a rebuild, use: `pkill -x CodexBar || pkill -f CodexBar.app || true; cd /Users/steipete/Projects/codexbar && open -n /Users/steipete/Projects/codexbar/CodexBar.app`.\n- For CLI-testable provider/parser/settings behavior, use CLI/focused tests instead of `Scripts/package_app.sh` or `./Scripts/compile_and_run.sh`.\n- Run `./Scripts/compile_and_run.sh` only when UI/runtime behavior needs bundle-level validation; it builds, tests, packages, relaunches, and verifies the app stays running.\n- Widget/Tahoe UI issues: use Parallels macOS VM plus screenshots/clicks for autonomous verification.\n- Release workflow: wait for completion and verify the complete artifact/feed/tap handoff; do not publish locally.\n- Sparkle release key: CI `SPARKLE_PRIVATE_KEY` must match the existing AGCY public key in `.github/release.json`; never substitute another app’s key.\n- Swift concurrency: treat sibling `async let` tasks as a review red flag when one child is required and another is optional/best-effort. Prefer sequential awaits or a drained `withThrowingTaskGroup` that surfaces required failures and explicitly contains optional failures; crash stacks mentioning `swift_task_dealloc` or `asyncLet_finish_after_task_completion` should trigger an audit of nearby `async let` usage.\n- Prefer modern SwiftUI/Observation macros: use `@Observable` models with `@State` ownership and `@Bindable` in views; avoid `ObservableObject`, `@ObservedObject`, and `@StateObject`.\n- Favor modern macOS 15+ APIs over legacy/deprecated counterparts when refactoring (Observation, new display link APIs, updated menu item styling, etc.).\n- Keep provider data siloed: when rendering usage or account info for a provider (Claude vs Codex), never display identity/plan fields sourced from a different provider.\n- Claude CLI status line is custom + user-configurable; never rely on it for usage parsing by default. A user-enabled, opt-in statusLine JSON feed is permitted as an explicit data source (owner ruling, #2733): it must be off by default, clearly labeled as sourced from the user's own statusLine config, and fail soft when the format drifts.\n- Cookie imports: use the shared default browser order; `BrowserCookieAccessGate` ensures no background/settings prompts, host-matching cookie rows before Safe Storage access, at most one interactive Keychain read per manual action, denial cooldowns even on manual retries (only `codexbar cookie refresh --allow-keychain-prompt` overrides), and traversal stopping at the first validated session. Restrict a provider to a browser subset only with a documented technical reason next to the code.\n",".agents/skills/release-codexbar/SKILL.md":"---\nname: release-codexbar\ndescription: \"CodexBar release: versioning, notarization, appcast, Homebrew, post-release bump.\"\n---\n\n# CodexBar Release\n\nUse for releasing signed/notarized macOS apps, especially repos with Sparkle appcasts and Homebrew casks.\n\n## Start\n\n1. Work from the app repo unless asked otherwise.\n2. Check repo state, current version, latest tag/release, and release docs/scripts.\n3. Confirm `CHANGELOG.md` is complete, user-facing, deduped, and dated for the release.\n4. Prefer the repo release script; patch small script/test blockers instead of bypassing the release path.\n5. Never print key material. Keep 1Password references and local key paths as references only.\n6. Load `$release-private` if it exists before resolving Peter-owned credential locators.\n\n## Key Material\n\nUse `$one-password` for secret handling. `op` only in tmux/persistent shell; no broad `env`, `set`, `export -p`, or secret scans.\n\nKnown App Store Connect shape:\n\n- fields: `private_key_p8`, `key_id`, `issuer_id`\n- keep all three fields from the same 1Password item; do not mix with stale values from `~/.profile`\n- resolve Peter-owned item refs from `$release-private`\n\nKnown Sparkle key:\n\n- resolve the private key file from `$release-private`\n- pass as `SPARKLE_PRIVATE_KEY_FILE`\n\nSafe env file pattern:\n\n```text\nAPP_STORE_CONNECT_API_KEY_P8=<1Password ref from release-private>\nAPP_STORE_CONNECT_KEY_ID=<1Password ref from release-private>\nAPP_STORE_CONNECT_ISSUER_ID=<1Password ref from release-private>\nSPARKLE_PRIVATE_KEY_FILE=<path from release-private>\n```\n\nRun with `op run --account my.1password.com --env-file <file> -- <script>`, then delete the temp env file.\n\n## CodexBar\n\nPaths:\n\n- repo: `~/Projects/codexbar`\n- release script: `Scripts/release.sh`\n- signing/notarization: `Scripts/sign-and-notarize.sh`\n- appcast: `Scripts/make_appcast.sh`, `appcast.xml`\n- release assets: `CodexBar-macos-universal-<version>.zip`, `CodexBar-macos-universal-<version>.dSYM.zip`\n- packaged app: `CodexBar.app`\n- version file: `version.env`\n- changelog: `CHANGELOG.md`\n- Homebrew tap: `~/Projects/homebrew-tap`\n- cask: `~/Projects/homebrew-tap/Casks/codexbar.rb`\n- formula: `~/Projects/homebrew-tap/Formula/codexbar.rb`\n- CLI release workflow: `.github/workflows/release-cli.yml`\n\nNormal release:\n\n```bash\ntmux new-session -d -s codexbar-release 'op run --account my.1password.com --env-file /tmp/codexbar-release-op.env -- Scripts/release.sh'\ntmux attach -t codexbar-release\n```\n\nIf notarization fails with `401 Unauthenticated`, rerun using all three App Store Connect fields from the 1Password item above. Mismatched `key_id` / `issuer_id` from `~/.profile` can cause this.\n\nIf widget metadata generation times out, `CODEXBAR_WIDGET_METADATA_TIMEOUT_SECONDS=600` is a known-good floor.\n\nCodexBar CLI tarballs are not produced by `Scripts/release.sh` itself. The GitHub release event triggers `.github/workflows/release-cli.yml`, which builds and uploads:\n\n- `CodexBarCLI-v<version>-macos-arm64.tar.gz`\n- `CodexBarCLI-v<version>-macos-x86_64.tar.gz`\n- `CodexBarCLI-v<version>-linux-aarch64.tar.gz`\n- `CodexBarCLI-v<version>-linux-x86_64.tar.gz`\n- matching `.sha256` files\n\nIf the workflow fails only in `update-homebrew-tap` with GitHub API rate limiting, the CLI assets may already be uploaded. Verify assets live, then update `Formula/codexbar.rb` manually from the tarball checksums.\n\n## Verify\n\nRelease is not done until the published chain checks out:\n\n```bash\ngh release view v<VERSION> --json tagName,name,isDraft,isPrerelease,url,assets,body\nScripts/check-release-assets.sh v<VERSION>\npython3 - <<'PY'\nimport xml.etree.ElementTree as ET\nns={'sparkle':'http://www.andymatuschak.org/xml-namespaces/sparkle'}\nroot=ET.parse('appcast.xml').getroot()\nitem=root.find('channel').find('item')\nenc=item.find('enclosure')\nprint(item.findtext('title'))\nprint(item.findtext('sparkle:version', namespaces=ns))\nprint(item.findtext('sparkle:shortVersionString', namespaces=ns))\nprint(enc.attrib.get('url'))\nprint(enc.attrib.get('length'))\nprint(bool(enc.attrib.get('{http://www.andymatuschak.org/xml-namespaces/sparkle}edSignature')))\nPY\ncodesign --verify --deep --strict --verbose=2 CodexBar.app\nspctl --assess --type execute --verbose CodexBar.app\n```\n\nFor Homebrew:\n\n```bash\nshasum -a 256 CodexBar-macos-universal-<VERSION>.zip\ncd /Users/steipete/Projects/homebrew-tap\npython3 .github/scripts/update_formula.py --formula codexbar --tag v<VERSION> --repository steipete/CodexBar --artifact-template 'CodexBarCLI-{tag}-{target}.tar.gz' --target-aliases 'darwin_arm64=macos-arm64,darwin_amd64=macos-x86_64,linux_arm64=linux-aarch64,linux_amd64=linux-x86_64'\nbrew fetch --cask --force --retry codexbar\nbrew fetch --formula --force --retry steipete/tap/codexbar\n```\n\nUpdate the cask when app zip assets exist. Update the formula only when standalone CLI tarballs for that version exist.\n\nTap audit can be noisy from unrelated formulae; keep evidence specific to the app cask.\n\n## Closeout\n\n1. Create/push tag and GitHub release through the release script.\n2. Verify appcast points to the new GitHub release asset with signature and length.\n3. Update/push the Homebrew cask if the app zip changed.\n4. Bump the app repo to next patch `Unreleased`:\n   - `version.env`: next `MARKETING_VERSION`, next `BUILD_NUMBER`\n   - `CHANGELOG.md`: top `## <next> — Unreleased`\n5. Commit, push, then pull `--ff-only`.\n6. Restart the local app from the packaged bundle and verify the running bundle version.\n7. Check no release/notary/op temp sessions or temp env files remain.\n\nCodexBar restart:\n\n```bash\npkill -x CodexBar || pkill -f CodexBar.app || true\ncd \"$(git rev-parse --show-toplevel)\"\nopen -n CodexBar.app\n/usr/libexec/PlistBuddy -c 'Print :CFBundleShortVersionString' CodexBar.app/Contents/Info.plist\n/usr/libexec/PlistBuddy -c 'Print :CFBundleVersion' CodexBar.app/Contents/Info.plist\n```\n","docs/architecture.md":"---\nsummary: \"Architecture overview: modules, entry points, and data flow.\"\nread_when:\n  - Reviewing architecture before feature work\n  - Refactoring app structure, app lifecycle, or module boundaries\n---\n\n# Architecture overview\n\n## Modules\n- `Sources/CodexBarCore`: fetch + parse (Codex RPC, PTY runner, Claude probes, OpenAI web scraping, status polling).\n- `Sources/CodexBar`: state + UI (UsageStore, SettingsStore, StatusItemController, menus, icon rendering).\n- `Sources/CodexBarWidget`: WidgetKit extension wired to the shared snapshot.\n- `Sources/CodexBarCLI`: bundled CLI for `codexbar` usage/status output.\n- `Sources/CodexBarClaudeWatchdog`: helper process for stable Claude CLI PTY sessions.\n- `Sources/CodexBarClaudeWebProbe`: CLI helper to diagnose Claude web fetches.\n\n## Entry points\n- `CodexBarApp`: SwiftUI keepalive + Settings scene.\n- `AppDelegate`: wires status controller, Sparkle updater, notifications.\n- Status items are created on the main actor from `applicationDidFinishLaunching`; see\n  [status-item startup diagnostics](status-item-diagnostics.md) for opt-in lifecycle and Control Center traces.\n\n## Data flow\n- Background refresh → `UsageFetcher`/provider probes → `UsageStore` → menu/icon/widgets.\n- Settings toggles feed `SettingsStore` → `UsageStore` refresh cadence + feature flags.\n- Runtime-only provider settings flow through typed, descriptor-registered sections in `ProviderSettingsSnapshot`.\n- Codex cost conversation titles reuse SQLite database discovery per working directory and SQLite home within each\n  enrichment call. The next call resolves paths again, so config edits and new state database versions are picked up.\n\n## CLI login lifecycle\n- `CodexLoginRunner` and `KiroLoginRunner` resolve their own executable and environment, including Codex home scoping.\n- `CLILoginRunner` owns browser-waiting login processes, bounded output capture, timeout/cancellation, and optional\n  device-flow progress. It returns one shared result type; provider presentations retain their own recovery messages.\n- The login runner and `SubprocessRunner` share `ProcessTermination` and process-tree termination. Cancelling a login\n  stops its child process, joins its progress callback task, and produces no failure alert. Timeouts retain captured\n  diagnostic output, and inherited pipes cannot keep the caller waiting indefinitely.\n- Probe ownership cleanup reads exact environment markers, retaining PID identity checks before signaling. Environment\n  parsing searches NUL byte ranges so large unrelated values do not dominate timeout and cancellation cleanup.\n- Codex and Grok RPC clients share deadline selection through `RPCRequestTimeout`. The deadline wins before teardown\n  can report stdout EOF; each client keeps its protocol initialization, encoding, diagnostics, and error types.\n\n## Concurrency & platform\n- Swift 6 strict concurrency enabled; prefer Sendable state and explicit MainActor hops.\n- macOS 14+ targeting; avoid deprecated APIs when refactoring.\n- Cost stores share one utility serial executor per canonical database location, including symlinked cache roots and\n  database files. Separate database operations do not share SQLite lock waits. Executors are weakly registered and\n  released with their last store or queued job. Normal app fetches still use the separate serial scan queue;\n  WAL transactions are unchanged.\n\n## Shared policy ownership\n- `ProviderCatalog` indexes the immutable generated list of app implementations. JavaScript plugins use their own\n  runtime registry. Provider settings contexts bind directly to their store; the shared cookie picker owns mode\n  conversion, options, and live subtitle selection while providers retain labels, visibility, and cache scopes. Settings\n  link actions resolve their provider-supplied destinations when clicked, including region and account selection.\n  Cookie subtitles use explicit localization keys and arguments supplied by providers; the picker selects the current\n  mode and Keychain-disabled explanation without parsing English display text.\n- Descriptor-registered cookie sections preserve their concrete types after app policy resolves credentials. The app\n  keeps its automatic default, Keychain-disabled manual mode, and selected-account normalization; CLI inference stays\n  separate. Cookie-source writes share config persistence and logging, with provider-specific side effects retained.\n- Core's public descriptor registry keeps one ordered descriptor collection with an index for lookup. Its storage owns\n  synchronization, and replacing a descriptor updates metadata and CLI names without changing provider order.\n- `UsageStore.presentationSnapshot` owns subscription metadata selection. Live cards use that projection once; explicit\n  account cards use only their supplied snapshots, identity, and credits. Compact and stacked Codex account layouts share\n  the same account-card builder. Reordering visible providers preserves loaded config records for unavailable plugins;\n  config decoding retains its existing known-provider rules.\n- `UsageStore.menuCardInput` assembles cards for Settings, the live menu, and explicit account contexts. It owns common\n  quota, pace, warning-marker, and display-preference projection. Settings retains diagnostics and all usage lanes;\n  menus retain their cost display policy and account-scoped forecasts. An account context stays isolated even when empty.\n  Cards consume the reconciled Codex projection; the raw dashboard is not a separate model input.\n- App credential properties read directly from the config snapshot and delegate common string writes to the typed\n  `SettingsStore` config accessor, which owns normalization, persistence, and secret-update logging. Field activation\n  does not trigger credential loading. Legacy provider toggles are read only by the config migrator.\n- Token-cost publications own their snapshots, revisions, and source scope in one store. Raw and current-config readers\n  select from that same state while retaining confirmed-empty and unpublished distinctions. Provider settings and\n  plugin edits reuse Core's config upsert; their defaults and notification policies remain explicit.\n- The native status-item controller owns menu composition. Persistent refresh-row metrics are independent of menu\n  rendering, and screenshot fixtures exercise the active card views. Legacy menu-layout resolution retains its\n  rendering mode and projected layout without copying unused settings into a second state object.\n- `SettingsValue` owns whitespace and wrapping-quote normalization for config and provider settings. Readers retain\n  their credential precedence, endpoint validation, and provider-specific decoding.\n- `ISO8601DateParser` owns fractional-first internet timestamp parsing with a whole-second fallback. Provider readers\n  retain their text extraction, whitespace, numeric timestamp, and custom-format policies; each parse owns its formatter.\n- Kilo's CLI fetch strategy and organization discovery share `KiloBearerTokenResolver` for auth-file loading.\n  Vertex AI credential loading and renewal share display-only ID-token decoding; diagnostic fetch labels use\n  `ProviderDiagnosticFetchAttempt` across the app and CLI.\n- Core owns status feed fetching, decoding, and status models through `ProviderStatusFetcher`; the app supplies\n  localized labels and component UI, and the CLI supplies its existing status payload and English labels.\n- `KeychainStringStore` owns generic-password operations for legacy credential migration. Provider adapters retain\n  their existing item names, prompt kinds, cookie validation, and cache lifetimes. Structured OAuth/cache stores\n  retain their separate persistence and authorization contracts.\n- Simple API-token providers use `ProviderFetchPlan.apiToken`; provider loaders retain endpoint-specific behavior\n  and optional-data rules. Strategies with credential provenance, region selection, or fallback policies stay explicit.\n- `ProviderCostSnapshot.spendLimitWindow` owns spend-budget quota projection. Antigravity's shared family classifier\n  owns ID-before-title precedence; visibility and compact widget selection retain their distinct unknown-family rules.\n- `TestProcessSafety` owns test-runner recognition; callers retain explicit headless and fixture-path policies.\n- `CheckedSum` owns integer overflow handling. Callers still decide whether absent or empty counts mean zero,\n  unavailable, or incomplete; persistent accumulators retain their own overflow state.\n- `BrowserCookieProfiles` merges browser stores within each profile using the existing expiry and store-priority\n  rules. Providers still validate their own sessions. `ExpiringValueCache` supplies their short-lived import caches,\n  with a separate cache instance per provider. `CookiePropertyJSON` preserves the legacy Date/URL marker encoding;\n  session stores retain their own file envelopes, expiration policies, and secure writes.\n- `OpenCodeWebParsing` owns workspace discovery and quota-candidate traversal for OpenCode and OpenCode Go. Each\n  supplies its own numeric/window decoder and required-lane policy. Alibaba Token Plan and Qwen Cloud share\n  `OneConsoleTokenPlanSnapshot` projection while retaining distinct public snapshot types and provider identities.\n- `StreamScanBuffer` supplies bounded overlap for Codex and Claude terminal-marker matching; command handling and\n  session lifecycle remain provider-specific.\n- Codex's persistent and one-shot PTY readers share `CodexStatusMarkers`, including the marker lengths used for\n  bounded overlap. Cursor-query handling remains part of each terminal loop.\n- `TTYCommandRunner` reports the captured POSIX error number and description when PTY allocation fails, so\n  descriptor exhaustion can be distinguished from other terminal allocation failures.\n- `UsageSnapshot.withAccountLabel` applies token-account fallback labels for the app and CLI while preserving the\n  provider's account ID and other identity fields. Codex visible-account labels keep their surface-specific policy.\n- Raw Chromium local-storage consumers share `ChromiumLocalStorageDiscovery`, retaining their own browser lists and\n  origin decoders. Both plugin engines use the manifest's management-auth eligibility policy after adapting their\n  engine-specific option values.\n- Codex RPC and OAuth spend limits use `CodexSpendControlNumber` while keeping their wire-field aliases separate.\n  Reset values retain truncation toward zero, with unrepresentable values omitted instead of trapping.\n- Claude's persistent CLI operations and serialized browser fetches each own an `AsyncOperationGate`; the shared\n  implementation retains FIFO ownership and the CLI cleanup path's explicit cancellation exception. CLI usage and\n  cards validate account/provider selection through `TokenAccountCLISelection` before resolving credentials.\n\nSee also: `docs/providers.md`, `docs/refresh-loop.md`, `docs/ui.md`.\n","docs/claude.md":"---\nsummary: \"Claude provider data sources: OAuth API, web API (cookies), CLI PTY, and local cost usage.\"\nread_when:\n  - Debugging Claude usage/status parsing\n  - Updating Claude OAuth/web endpoints or cookie import\n  - Adjusting Claude CLI PTY automation\n  - Reviewing local cost usage scanning\n---\n\n# Claude provider\n\nEnable **Settings → Menu Bar → Show all accounts in Overview** to include this provider’s cached account\ncards in Overview. The existing account source and provider selection remain unchanged; see\n[Overview account rows](configuration.md#menu-bar-controls).\n\nThe **Plan Usage** submenu includes recorded remaining-quota burndown above utilization history,\nusing the same Session, Weekly, and Sonnet labels. See [recorded quota burndown](widgets/burndown-proof.md)\nfor capture-age semantics and the existing history retention/privacy behavior.\n\nOAuth history uses a stable account/profile identity after two stable credential observations corroborate that\nbinding. External token rotations therefore continue the same history after the new credential is corroborated.\nSaved token-scoped fragments with matching verified bindings migrate into that account's history on a successful\nsample. Unbound fragments, obsolete bindings, and other accounts stay separate; correcting a conflicting binding\npreserves its ambiguous old fragment without attributing it to the newly confirmed account. If that fragment was\nalready merged, the affected account bucket is preserved but hidden, and fresh samples start a new account scope.\nThis quarantine survives empty responses and restarts. Explicit OAuth tokens without Claude Code account evidence\nremain credential-scoped.\n\nClaude supports three usage data paths plus local cost usage. The main provider pipeline uses runtime-specific\nautomatic selection, but the codebase still has multiple active Claude `.auto` decision sites while the refactor is\npending. For the exact current-state parity contract, see\n[docs/refactor/claude-current-baseline.md](refactor/claude-current-baseline.md).\n\nWhen an Anthropic Admin API key is configured, Claude can also show organization-level spend/messages/tokens in the\nsame inline dashboard pattern used by the OpenAI API provider.\n\nIncomplete proxy records with an explicit null `stop_reason`, positive input, zero output, and no cache counters are excluded from token and cost totals until a completed record arrives. Their day and model stay visible with an **Incomplete** marker and an excluded-request count. Known usage remains a partial subtotal; an incomplete-only period stays unavailable rather than becoming zero. Older caches are rebuilt once to apply this distinction. Missing `stop_reason` alone retains compatibility with older complete logs.\n\nCompleted requests for models without a known price keep their token usage and report unpriced coverage. Usage & Spend retains the priced subtotal from the other requests and days as a lower bound; unknown prices do not become zero. Fully priced history keeps its usual estimated totals.\n\n## Data sources + selection order\n\n### Default selection (debug menu disabled)\n- If an Admin API key is configured, the Admin API strategy is used for Claude API spend/usage.\n- App runtime main pipeline: OAuth API → CLI PTY → Web API.\n- CLI runtime main pipeline: Web API → CLI PTY (Linux: Web API → OAuth API → CLI PTY).\n  Since 0.78.0, Auto `usage` first uses the active slot when the claude-swap integration is enabled;\n  explicit source/account selection bypasses it. See [CLI precedence](cli.md#command).\n- Explicit picker modes (OAuth/Web/CLI) bypass automatic fallback.\n- Explicit OAuth retains the last successful quota measurement through recognized temporary network failures,\n  including localized DNS/offline errors. Its original timestamp remains visible; authentication rejection still\n  follows the existing invalidation and credential-owner rules.\n- A lower-level direct Claude fetcher still contains a separate `.auto` order. That inconsistency is tracked in\n  [docs/refactor/claude-current-baseline.md](refactor/claude-current-baseline.md).\n\nUsage source picker:\n- Preferences → Providers → Claude → Usage source (Auto/OAuth/Web/CLI).\n\nWhen an account-widget identity check or successful web enrichment returns an OAuth profile, its same-token email is\nretained for display even if the profile lacks the organization needed to verify widget ownership. A failed\noptional profile check leaves successful usage available. Ordinary OAuth refreshes also start one background\nprofile lookup per credential, bounded to two seconds, without waiting for it to publish quota usage. The first\nrefresh may omit the email while that request is in flight; later refreshes reuse its result, including a failed\nlookup, until the token changes or CodexBar restarts. This process-local display cache cannot establish widget\nownership. Organization UUIDs are ownership evidence, not organization display names.\n\nAdmin API key setup:\n- Preferences → Providers → Claude → Admin API key, stored in `~/.codexbar/config.json`.\n- CLI/env: `printf '%s' \"$ANTHROPIC_ADMIN_KEY\" | codexbar config set-api-key --provider claude --stdin`.\n- Token accounts can also hold `sk-ant-admin...` keys; they route to the Admin API instead of cookie/OAuth usage.\n- Environment fallback: `ANTHROPIC_ADMIN_KEY`.\n\n## Usage Dashboard\n\nThe default and API-account Usage Dashboard action opens [Platform usage](https://platform.claude.com/usage). Accounts recognized as Claude subscriptions open [Settings → Usage](https://claude.ai/settings/usage). This action does not change the configured usage source.\n\n## Admin API\n- Key prefix: `sk-ant-admin...`.\n- Endpoints:\n  - `/v1/organizations/cost_report`\n  - `/v1/organizations/usage_report/messages`\n- Output:\n  - Today/7d/30d spend and message/token summaries.\n  - Inline 30-day dashboard chart when daily buckets are present.\n  - Identity login method: `Admin API`.\n\n### Optional workspace spend\n\nEnable **Show workspace spend** in Settings → Providers → Claude, set `claudeWorkspaceSpendEnabled: true` on the\nClaude provider config entry, or set `ANTHROPIC_ADMIN_WORKSPACE_SPEND=true`. It is off by default and applies only\nto the Admin API source.\n\nThe existing [cost report](https://platform.claude.com/docs/en/api/admin/cost_report/retrieve) request adds\n`group_by[]=workspace_id` alongside `group_by[]=description`; no extra request or credentials are required.\nThe organization totals, cost items, token summaries, and daily chart remain unchanged. When more than one workspace\nhas cost rows, **Workspace spend · 30d** shows up to 20 workspaces, highest spend first, over the same 30-day buckets\nas the organization total. Labels use workspace IDs; a null workspace is **Default**. Amounts are converted from\nAnthropic's USD cents to dollars. A single workspace keeps the existing organization view.\n\n## Recover usage when Claude is already signed in\n\nA working Claude Code login or Claude browser tab does not by itself confirm that CodexBar can read that\nsession. For example, OAuth can report missing credentials, the CLI probe can time out, and Web can report\n`No Claude session key found in browser cookies.` on the same machine. Diagnose the selected source before\nsigning out or replacing credentials.\n\nIf Claude works in Chrome but CodexBar cannot import its browser session:\n\n1. Open `https://claude.ai` in Chrome and confirm the intended account is signed in.\n2. In Settings → Providers → Claude, select **Web API (cookies)** and leave the cookie source on **Auto**.\n   This uses the browser session for session, weekly, and available model-specific quotas.\n3. In Settings → Advanced, confirm **Disable Keychain access** is off. Chromium cookie decryption needs\n   the browser's Safe Storage Keychain item; Claude's OAuth prompt policy controls a different credential.\n4. Explicitly retry the import from Terminal:\n\n   ```bash\n   codexbar cookie refresh --provider claude --allow-keychain-prompt\n   ```\n\n   Approve the expected macOS Keychain prompt for the installed CodexBarCLI and the browser's Safe Storage\n   item. The command never prints cookie values. A prior denial can suppress imports for six hours;\n   this explicit retry can bypass that cooldown, while ordinary refresh attempts may remain suppressed.\n   See [Keychain prompts](keychain-prompts.md) for permission details.\n5. Click **Refresh** in Claude's settings and confirm that **Updated just now** appears with current quota\n   bars and no fetch error. Local cost totals or last-known quota bars alone do not prove a successful refresh.\n\nThis recovers browser-session access; it does not repair missing OAuth credentials. If the refreshed session\ninstead reports a Cloudflare challenge, follow the network guidance under Web API below rather than repeating\nthe cookie import.\n\n## Passive CLI sessions and macOS sleep\n\nAfter each passive request in the reusable CLI session, and when that session closes, CodexBar sends SIGTERM\nonly to `/usr/bin/caffeinate` processes in the session's own process group. The session remains available for\nlater requests. There is no recurring process scan; other process groups and other executables are left alone.\nThis cleanup applies to the reusable PTY session, not the separate one-shot usage or auth-status commands.\n\n## Keychain prompt policy (Claude OAuth)\n\nIf CodexBar reports that its Claude OAuth cache is temporarily unavailable in macOS Keychain, check\nthat the login keychain is unlocked in Keychain Access, then retry Refresh. This guidance does not\nchange the existing prompt policy or switch to another credential source.\n\n- Preferences → Providers → Claude → Keychain prompt policy.\n- Options:\n  - `Never prompt`: never attempts interactive Claude OAuth Keychain prompts.\n  - `Only on user action` (default): interactive prompts are reserved for user-initiated repair flows.\n  - `Always allow prompts`: allows interactive prompts in both user and background flows.\n- Explicit Refresh can repair Claude OAuth Keychain access when direct-read consent is enabled and the policy\n  allows user prompts. Ordinary OAuth fetches remain noninteractive, including with `Always allow prompts`;\n  that policy still governs the existing delegated refresh and experimental reader paths.\n- After a missing-credentials failure, a newer Claude Code credential timestamp replaces the stale error with\n  **Claude Code credentials changed** and a **Refresh** instruction in the menu and OAuth CLI output. Click the\n  existing menu **Refresh** action to retry; direct-read consent and the prompt policy still control access.\n  The freshness probe reads only Keychain attributes with UI disabled and the credentials file's modification\n  time. It never requests the Keychain credential payload, launches Claude, or grants access in the background.\n  A changed timestamp can mean a login or token rotation; it does not prove the credential is valid. Custom\n  credential profiles use their own file timestamp, never the global Claude Code Keychain item. CodexBar saves\n  only the last failure time per profile so the guidance survives restarts and is shared with the bundled CLI;\n  a readable credential clears it. Auto continues to configured CLI/Web sources under their existing access gates.\n- This setting only affects Claude OAuth Keychain prompting behavior; it does not switch your Claude usage source.\n- The policy also applies to the experimental `/usr/bin/security` reader and delegated OAuth refresh through\n  `claude`: background operations that can prompt require `Always allow prompts`.\n- CodexBar's `Always allow prompts` permits future prompts; macOS's **Always Allow** grants access to the current\n  Keychain item. Claude Code can recreate `Claude Code-credentials` and reset that grant. An ACL entry still named\n  CodexBar does not prove that its stored code-signing requirement matches the running binary. `Only on user action`\n  reduces background interruptions but may require a manual Refresh to recover OAuth access. In #3798, a\n  before/after trace shows Claude Code preserving the decrypt ACL's CodexBar entry but removing CodexBar's Team ID\n  from the separate partition ACL. Decrypt-ACL preflight alone cannot establish partition authorization; repeated\n  manual grants therefore need not survive the next Claude Code refresh.\n- If Preferences → Advanced → Disable Keychain access is enabled, this policy remains visible but inactive until\n  Keychain access is re-enabled.\n\n### Debug selection (debug menu enabled)\n- The Debug pane can force OAuth / Web / CLI.\n- Web extras are internal-only (not exposed in the Providers pane).\n- CLI Web enrichment requires matching nonempty account emails; an organization display name alone cannot authorize\n  merging another session's optional usage or spend. OAuth enrichment retains verified organization-UUID matching.\n\n## OAuth API (preferred)\n- OAuth refresh form-encodes credential values, preserving literal plus signs and other reserved characters.\n- Expiry values outside the diagnostic integer range are reported as `out_of_range` without changing credential expiry or refresh decisions.\n- Credentials:\n  - Explicit OAuth environment override, when configured.\n  - CodexBar OAuth cache when available.\n  - File fallback: `~/.claude/.credentials.json`.\n  - Claude CLI Keychain bootstrap/repair fallback: `Claude Code-credentials`.\n- When a CodexBar-owned OAuth cache item's ACL rejects the current build, fresh credentials from an allowed source\n  can replace that cache item using no-UI deletion and creation. A locked or inconclusive Keychain is preserved;\n  failed ACL repairs back off for five minutes. This never deletes or recreates Claude Code's credential item.\n- If CodexBar's cache is temporarily unavailable, automatic refreshes can reuse an unexpired credential already in\n  memory beyond the normal 30-minute cache window, ahead of a stale credentials file. Each refresh retries the\n  persistent cache. Token expiry, profile changes, cache invalidation, and Never prompt still prevent reuse;\n  after a rejected cache write, the next refresh first clears the stale persistent entry, then reuses and persists\n  an unexpired in-memory credential even after 30 minutes once that cleanup succeeds. Extended reuse requires\n  evidence of that exact failed write and its original consent; an unrelated invalidation cannot authorize it.\n  Rejected writes during CodexBar-owned token refresh retain the same recovery, bound to the refreshed credential;\n  a delayed older write cannot replace a newer credential's recovery.\n  This does not discover an external login or enable additional background reads of Claude Code's Keychain item.\n- For the default CLI profile, expired cached or file credentials can adopt a fresh CLI Keychain token after file fallback, even when its fingerprint was already observed during an earlier repair. Existing direct-read consent, prompt policy, cooldown, one-minute freshness-check throttle, and noninteractive-read checks still apply. Custom profiles are not recovered from the unscoped global item, and CLI credentials are never rewritten by this synchronization. Background recovery still requires the Always allow prompts policy; the default Only on user action policy requires an explicit Refresh.\n- Credential selection does not rank unrelated sources by the largest `expiresAt`: expiry establishes validity,\n  not account identity or issuance order. A valid profile file remains ahead of Keychain bootstrap. Keychain candidates\n  are ordered by modification date (creation date as fallback); freshness sync reads only that newest item and never\n  rewrites Claude Code's credentials file. An expired default-profile record can be replaced even when the stored\n  Keychain fingerprint already matches, subject to the access gates above.\n- On Claude Code 2.1.x, `Claude Code-credentials` may contain only MCP server OAuth state (`mcpOAuth`) with no `claudeAiOauth`. CodexBar treats that as an OAuth configuration error, does not run background delegated `claude /status` refresh, and surfaces re-auth guidance. Use Web or CLI usage source, or restore a valid Claude OAuth keychain entry. See #1844.\n- Requires `user:profile` scope (CLI tokens with only `user:inference` cannot call usage).\n- Missing-scope errors require a Claude Code sign-in token with usage access. `claude setup-token` produces a token for model requests and is not a usage-scope recovery step ([Claude Code authentication](https://code.claude.com/docs/en/authentication#generate-a-long-lived-token)). Remove any configured OAuth token override before switching Claude Source to Web/CLI.\n- Endpoints:\n  - `GET https://api.anthropic.com/api/oauth/usage?cedar_ember=1` → usage and saved reset inventory.\n    HTTP 400/403 retries once without the optional query and with the legacy request identity; spending is never skipped.\n    Missing-profile-scope errors, 401, and 429 keep their normal handling without that retry.\n  - `GET https://api.anthropic.com/api/oauth/profile` → account identity used to verify that optional Web enrichment\n    belongs to the same Claude account.\n- Headers:\n  - `Authorization: Bearer <access_token>`\n  - `anthropic-beta: oauth-2025-04-20`\n  - Reset inventory uses `claude-cli/<detected-version> (external, cli)`; eligibility remains server-controlled.\n- Mapping:\n  - `five_hour` → session window.\n  - `seven_day` → weekly window; also becomes the primary fallback when `five_hour` is absent or has no utilization.\n  - `seven_day_sonnet` / `seven_day_opus` → model-specific weekly window.\n  - `limits[].weekly_scoped` → model-specific weekly windows; generic `All models` scopes stay in the main weekly row.\n    OAuth and Web share per-entry parsing: malformed siblings or optional fields do not hide valid model rows. Boolean and string percentages are ignored.\n  - The menu localizes scoped titles as a model name plus weekly duration; canonical snapshot and CLI titles remain unchanged.\n  - Automatic and Session + Weekly menu bar metrics fall back to the most constrained known scoped weekly window when the regular quota windows are missing. Unknown scoped measurements remain unavailable; Extra usage stays a spend-only fallback.\n  - `seven_day_routines` / `seven_day_cowork` → Daily Routines extra window.\n  - Claude Design/Omelette keys are ignored because Claude Design shares the main Claude usage limit.\n  - `extra_usage` → Extra usage cost (monthly spend/limit).\n- Preferences → Providers → Claude → Visible usage items lets you hide the Daily Routines row in menus, the Settings\n  preview, and Overview. The global optional credits and extra usage setting remains its master switch. Hiding this\n  row does not change fetching, history, notifications, widgets, model-scoped weekly limits, hooks, or CLI output.\n- Preferences → Providers → Claude → Show model-specific weekly usage in widgets controls model-scoped weekly quota\n  rows in desktop widgets. It is off by default; turning it on displays every known Claude window with a\n  `claude-weekly-scoped-` identifier (for example, Fable). Turning it back off also drops scoped rows that a previous\n  snapshot persisted. It does not change fetching, the menu, history, notifications, hooks, or CLI output.\n- Refreshing credentials for the same identified account preserves quota-threshold warning history. Verified\n  credential-owner/account bindings also preserve threshold history when active-account metadata temporarily\n  disappears or OAuth falls back to CLI. Threshold warnings re-arm after quota recovers above a threshold and fire\n  on a later downward crossing. Predictive warnings and quota-low hooks keep their existing source-scoped histories;\n  their baselines are not merged with threshold notification state. OAuth/CLI samples without a warning owner use\n  one stable unresolved-account scope, so credential rewrites preserve threshold crossings and predictive warnings\n  remain available. When a stable account identity or verified owner binding becomes available, its threshold scope\n  adopts the newest unresolved history. Later identity gaps reuse the last known account when the reset timestamp\n  is unchanged and remaining quota has not increased. Without that continuity, samples use the stable unresolved\n  scope; once its history has joined an account, subsequent gaps preserve already-fired thresholds rather than\n  starting a new warning episode on every refresh. The first independent unresolved sample can still issue an\n  initial warning. Unverified credential owners remain independent; changing such an owner can still\n  produce an initial warning because account continuity cannot be established.\n- Successful OAuth login enables Claude and preserves the selected usage source. With the default Auto source, OAuth\n  remains preferred when readable, while CLI/Web fallback stays available when OAuth credentials are not usable.\n- Claude Code periodically rotates its `Claude Code-credentials` Keychain item and can replace the ACL grant that\n  allowed CodexBar to read it. Auto treats that as a failed OAuth source, reuses a recent successful CLI result or\n  continues to CLI/Web, and does not misreport the existing credentials as missing. A manual Refresh can re-grant\n  Keychain access; selecting CLI or Web avoids the foreign-Keychain dependency.\n- When every live Auto source fails, CodexBar keeps the last captured session/weekly percentages from\n  `history/claude.json` visible as stale data and shows their capture age instead of blanking the quota bars.\n  Restored history and CLI-scraped percentages both show “Limited usage detail”: the warning describes reduced\n  fidelity, not the source of a historical capture. It does not change sign-in or refresh recovery actions.\n- Plan inference: `subscriptionType` is preferred when present; `rate_limit_tier` falls back to\n  Max/Pro/Team/Enterprise. When a Max `rate_limit_tier` carries a usage multiplier\n  (`default_claude_max_5x` / `default_claude_max_20x`), it is surfaced in the label as \"Max 5x\" / \"Max 20x\".\n\n## Web API (cookies)\n\n### Claude desktop app cookies (opt-in, macOS)\n\nChoose **Usage source → Web** and **Claude cookies → Automatic**, then enable **Use Claude desktop app cookies**.\nSign in to Claude.app first. This option is off by default. The equivalent provider config field is `\"claudeDesktopCookiesEnabled\": true`,\nalso honored by CLI usage. The desktop app source reads `~/Library/Application Support/Claude/Cookies` (or\n`Network/Cookies`) through SweetCookieKit, using `Claude Safe Storage` / `Claude Key` for encrypted values.\n\nDesktop cookie imports are exclusive: a missing or unreadable desktop session never falls back to a browser account.\nExplicitly selected token accounts keep their own credentials; this option applies to automatic-cookie fetches.\nUsage source **Auto** keeps its existing runtime-specific source order; choose **Web** to follow only the desktop session.\nCookies are reimported on each refresh so account switches made in Claude.app are observed; desktop sessions\ndo not read or write the browser cookie cache. The normal shared access gate still forbids prompts from background/settings\nwork, checks matching cookie hosts before Safe Storage access, permits at most one interactive read per manual\naction, and honors denial cooldowns. OAuth and Claude Code login sources are unchanged.\n\n- Optional subscription dates come from `GET /api/organizations/{org_id}/subscription_details` on `claude.ai`,\n  using an existing manual or cached session cookie. They do not come from the quota response or OAuth expiry.\n  `next_charge_at` / `next_charge_date` supply renewal; `plan_ending_at` / `plan_ending_before` supply paid-access\n  expiration and take precedence over renewal. Calendar-only dates stay calendar-only when displayed.\n- The menu and Settings preview reuse the shared subscription row. Missing, unavailable, or unrecognized billing\n  data does not invent a date or fail usage. CLI JSON exposes `subscriptionRenewsAt` / `subscriptionExpiresAt`\n  and a corresponding `...IsDateOnly: true` when the server only supplies a calendar date.\n- OAuth enrichment additionally verifies the OAuth profile's account and organization against the cookie session\n  before and after billing. OAuth credentials alone cannot fetch these dates. No cookie discovery, credential\n  repair, sign-in, or new Keychain access is performed for billing; cookie source Off disables it.\n- App and CLI use the same provider fetch and normal account-scoped publication. Billing has a separate two-second\n  total budget after quota succeeds; expiry, errors, or a changed verified owner leave successful quota intact.\n  No delayed billing task writes back into an already published snapshot, and dates are not carried across refreshes.\n- Availability is determined by the authenticated billing response, not the Pro/Max/Team/Enterprise plan label.\n  The fixtures cover the reported subscription schema, including cancellation and absent dates; they do not\n  establish that every plan or organization role can access this endpoint. Team/Enterprise billing access and\n  live cancelled subscriptions remain unverified. Quota resets and Extra usage balances are independent.\n- Session quota warnings ignore a weekly quota promoted into the primary field when the five-hour payload is missing. Existing session warning history stays tied to its account, and weekly warnings continue independently.\n- Preferences → Providers → Claude → Cookie source (Automatic or Manual).\n- Manual mode accepts a `Cookie:` header from a claude.ai request.\nEach Claude token account can optionally select a **Claude config directory** in Settings → Providers → Claude.\nWith Track costs enabled, the active account’s local token and API-price cost estimates scan only its `projects` history,\nwithout default, claude-swap, or Pi history. Selecting a directory declares that its history belongs to the account;\nCodexBar does not infer ownership or detect mixed/copied history beyond existing scanner deduplication. A missing or\nunreadable history directory leaves its estimate unavailable. Clear the mapping to restore the existing combined\nview; unmapped accounts and claude-swap retain their current behavior. The optional `claudeConfigDirectory` field\nis stored on the token account in the config file and does not change quota fetching or credential resolution.\n\n- Multi-account manual tokens: add entries to `~/.codexbar/config.json` (`tokenAccounts`) and set Claude cookies to\n  Manual. The menu can show all accounts stacked or a switcher bar (Preferences → Advanced → Display).\n- In Settings → Providers → Claude, **Edit** changes a saved account’s label and credential in place.\n  The credential field is masked and starts empty; leave it blank to keep the saved credential, or enter a\n  replacement and choose **Save**. **Cancel** discards the draft. Up/down controls persist account order in\n  the existing config file and menu switcher. Edits and moves preserve the selected account and account ID,\n  including its Claude config directory. Other providers with manual token accounts share the editor; editing\n  a dormant account does not select it.\n- Claude token accounts accept either `sessionKey` cookies or OAuth access tokens (`sk-ant-oat...`). OAuth-token\n  accounts route to the OAuth path and disable cookie mode; session-key or cookie-header accounts stay in manual\n  cookie mode. The exact edge-routing rules are documented in\n  [docs/refactor/claude-current-baseline.md](refactor/claude-current-baseline.md).\n- Cookie source order:\n  1) Safari: `~/Library/Cookies/Cookies.binarycookies`\n  2) Chrome/Chromium forks: `~/Library/Application Support/Google/Chrome/*/Cookies`\n  3) Firefox: `~/Library/Application Support/Firefox/Profiles/*/cookies.sqlite`\n- Domain: `claude.ai`.\n- Linux: the web source supports an explicitly configured manual `sessionKey` cookie. Automatic browser import,\n  including Firefox import, remains unavailable. This enables the same API request path as macOS; it does not\n  bypass Cloudflare challenges, refresh Claude Code OAuth credentials, or guarantee that a browser session will\n  work outside the browser. Use the OAuth source when the web request is challenged.\n- Linux CLI Auto mode: an existing valid manual cookie makes Web eligible ahead of CLI after upgrading.\n  Web success returns without launching Claude Code; authentication rejection or a Cloudflare challenge falls\n  back to an available CLI using the existing Auto policy. Cancellation stops without launching the CLI.\n  Explicit Web mode does not fall back. Use explicit OAuth mode for passive polling that must not launch the CLI.\n- Linux CLI Auto mode: the plan is Web → OAuth → CLI PTY. OAuth runs when Claude Code OAuth credentials\n  (`~/.claude/.credentials.json`) are usable without a prompt, so a logged-in user does not launch an interactive\n  `claude` session. Without usable OAuth credentials, the OAuth step is skipped and CLI remains the fallback.\n  OAuth request failures such as 401 or 429 also fall back to the CLI; cancellation stops without launching it.\n- Cookie name required:\n  - `sessionKey` (value prefix `sk-ant-...`).\n- Cached cookies: Keychain cache `com.steipete.codexbar.cache` (account `cookie.claude`, source + timestamp).\n  Reused before re-importing from browsers.\n- After replacing an expired cached cookie, failures from the recovered session retain their actual error type,\n  including temporary network failures, server outages, Cloudflare challenges, and cancellation. The original\n  sign-in error is retained only when browser recovery itself fails to find a usable session.\n- API calls (all include `Cookie: sessionKey=<value>`):\n  - `GET https://claude.ai/api/organizations` → org UUID.\n  - `GET https://claude.ai/api/organizations/{orgId}/usage?cedar_ember=1` → session/weekly/opus, plus limit-reset\n    grants in the `cedar_ember` block. Rejected requests, including ordinary 403 responses, retry once without\n    `cedar_ember=1`, so unsupported reset queries keep the usage windows.\n    Success, 401, 429, and recognized Cloudflare challenges retain their normal handling without a retry.\n  - `GET https://claude.ai/api/organizations/{orgId}/overage_spend_limit` → Extra usage spend/limit.\n  - `GET https://claude.ai/api/organizations/{orgId}/prepaid/credits` → remaining Usage credits balance.\n  - `GET https://claude.ai/api/account` → email + plan hints.\n- Outputs:\n  - Session + weekly + model-specific percent used.\n  - A missing Web session stays absent instead of becoming a synthetic 0% session; measured weekly and extra windows stay visible. Older cached placeholders remain readable.\n  - Daily Routines extra window when returned by the usage API.\n  - Extra usage spend/limit (if enabled). Compact Overview keeps this section when no measured quota bars exist, including Enterprise accounts with unavailable limits.\n  - Remaining Usage credits balance (if enabled). Custom menu bar layouts can show it with the **Balance** element\n    beside the quota windows; the default menu bar text keeps showing quota. Turning off **Show credits & extra\n    usage** hides it from the menu card and the Balance element immediately. Otherwise a fresh snapshot without a\n    successful prepaid credits response has no balance, while a failed refresh keeps the last snapshot and its balance.\n  - Account email + inferred plan.\n  - Limit Reset Credits (see below).\n- A Cloudflare challenge on `claude.ai` is a network-path restriction, not a stale-cookie signal. CodexBar keeps the\n  cached cookie and prior quota snapshot, identifies the challenge, and links to Settings. Select OAuth for live\n  quota windows on that network (the web-only Usage credits balance is unavailable), or try a different network.\n  Explicit Web mode remains terminal and never reads OAuth credentials as a fallback.\n- Limit Reset Credits (\"Reset for free\" in Claude Settings > Usage), Web and OAuth sources:\n  - These are saved resets a user can redeem, separate from the session and weekly reset timestamps already\n    supplied by Web, OAuth, and CLI. Existing cookie settings and source selection govern all Web access; this\n    feature does not enable cookies, broaden browser discovery, or initiate Web enrichment.\n  - Read from `cedar_ember` in the same usage response as the session and weekly windows, so they share its session\n    and organization. Observed on a personal Pro/Max account; Team and Enterprise organizations are not verified.\n  - The count sums `resets_left` over grants that, at refresh time, are not paused, have started, and have not\n    expired. An expiry that passes before the next refresh drops that reset from the display. `usable_now` is not\n    consulted, so a saved reset still counts while Claude gates its use.\n  - Requires `eligible: true`. A grant with an unreadable `resets_left`, `resets_total`, `paused`, `starts_at`, or\n    `ends_at` is dropped. More than 50 available resets or more than 200 grant records show nothing. The usage\n    windows are unaffected either way.\n  - Menu: a live `Limit Reset Credits` section with its own visibility choice. An upgrade preserves a previously\n    disabled global optional-usage preference by initially hiding this section; it can then be enabled independently.\n    CLI and `codexbar serve`: a `Limit Reset Credits` row in `usage.details` (`N available`, next expiry).\n  - Live-only: grant IDs are never decoded, the usage request skips the URL cache, and cached or synced snapshots do\n    not restore the inventory. A reset used on claude.ai disappears at the next successful refresh.\n  - Source precedence stays unchanged: credits appear only from the source supplying the primary usage snapshot. CLI\n    does not report saved reset credits, and optional Web enrichment never adds Web credits to another source,\n    even when the account matches. The menu replaces the generic details row with one shared reset-credit section.\n    CodexBar never redeems a reset; use Claude on the web or Claude Desktop.\n\nEnterprise spend details depend on the selected source. Auto stops at the first successful OAuth/CLI/Web result;\nit does not import a missing monthly spend cap from a different session. Select **Web API (cookies)** for\nbrowser-only billing details, and enable **Show credits and extra usage** to display them.\n\n## Extra usage rollover\n\nFor Claude spend-only accounts, positive monthly caps use the shared usage-window presentation: one Meter bar,\nwith Auto, Extra usage, and the card using the same cap percentage and reset. The card retains its spend/limit and\nprepaid-balance caption. Accounts with quota windows keep their separate Extra usage section, including in the\nmenu-card-sections layout. Uncapped balances keep their compact Credits row; Admin API spend is unchanged.\nExtra usage still follows its existing visibility choice and the global optional-usage switch. Pace follows the\nexisting pace setting, independently of redeemable Limit Reset Credits.\n\nClaude does not report the cap reset. CodexBar infers midnight UTC on the first of the next month from the cost\nobservation, applying [Anthropic's documented spend-limit period](https://platform.claude.com/docs/en/manage-claude/spend-limits-api)\nto all Claude monthly caps. Reset text uses local time (November 1 UTC can appear as October 31 in the Americas);\nthe metric tooltip identifies the inference. Shared calendar-month pace uses the actual 28–31-day month.\nChanging a cap changes the percentage, not its rollover. A stale observation does not advance into a new month.\n\n## Cloud-session credits\n\n- OAuth and Web usage responses can supply promotional cloud-session credit in `iguana_necktie`.\n  CodexBar shows a separate **Cloud credits** balance row in the menu and a detail section in the CLI when\n  optional credits/extra usage is enabled. The menu row matches the prepaid **Credits** row and shows only the\n  remaining balance; the allowance, progress, and expiry are available in CLI output and the widget snapshot's\n  optional `details` field. CLI JSON exposes the section through `usage.details`, including numeric progress and\n  remaining dollars. No additional request, login, or browser\n  discovery is needed for these credits.\n- `limit_dollars`, `used_dollars`, and `remaining_dollars` are already USD amounts. They are never divided by 100,\n  added to prepaid Extra usage, counted as local spending, or used for quota pacing. The reported remaining amount\n  wins when present; otherwise it is derived from the allowance and reported used dollars.\n- `resets_at` denotes expiration, not a recurring quota reset. The section shows an absolute UTC timestamp\n  (`Z`); cached details retain the last observation and its expiry. Exhausted credits retain a zero balance;\n  credits already expired at observation or locked by the provider are labeled expired/unavailable. The menu\n  also labels a cached balance expired once its stored expiry has passed.\n- Missing or malformed credit blocks omit this section without failing ordinary usage. The response determines\n  availability, without a Pro/Max plan-name gate. CLI-probe-only results do not include cloud credits, and optional\n  Web enrichment preserves the primary source's credits rather than importing Web credits into OAuth/CLI results.\n- Settings → Providers → Claude → Visible usage items can hide **Cloud credits** independently. The optional\n  credits/extra usage setting remains its master switch; the individual visibility choice does not change CLI output.\n- The response shape is based on [Pane's implementation and live-shape test](https://github.com/ItsJazii/pane/blob/beb4bbfd4e7c776d970a56d254e8cce4d61154d9/src-tauri/src/providers/claude.rs#L443).\n  CodexBar's fixtures validate parsing and presentation; they are not independent live Pro/Max verification.\n\n## claude-swap accounts (opt-in)\n\nThe accepted multi-account design in\n[claude-multi-account-and-status-items.md](claude-multi-account-and-status-items.md).\n\n- Setup: Preferences → Providers → Claude → \"Read accounts from claude-swap\", then set the path to the\n  [`cswap`](https://github.com/realiti4/claude-swap) executable (for example `~/.local/bin/cswap`) in the field\n  directly beneath the enabled toggle. The path field and its help are hidden while the integration is off.\n- tcr source: [tcr](https://github.com/dhkts1/teamclaude-rs) prints the same list schema v1 from `tcr list --json`.\n  Pick \"tcr\" in the Source picker shown while the integration is on (config key `claudeSwapSource: \"tcr\"`) and set\n  the path to the `tcr` executable. CodexBar then runs `tcr list --json` and `tcr --version` with the same bounds,\n  parser and isolation rules; cards are labelled `tcr` and identity is `tcr:<slot>`. tcr pools its accounts and\n  routes each request itself, so its list reports no active account and `supportsAccountSwitching: false`: no row\n  offers Switch or Re-authenticate, and CodexBar never sends tcr a switch command. Late list results from a\n  previously selected source are discarded before publishing cards, refresh times, or forecast history, including\n  when the initial history load is still pending.\n- tcr forecast history requires both a nonempty email and `organizationUuid` in the list row. When both are\n  supplied, it uses the same normalized email/organization hash as claude-swap; display names and slot numbers\n  never substitute for an organization UUID. The current tcr contract fixture omits `organizationUuid`, so those\n  accounts show usage without a history-based forecast.\n- Version detection retries after a failed or cancelled startup probe; replaced refreshes cannot overwrite a newer\n  result, and disabling the adapter or changing its executable clears the previous detected version.\n- Behavior: on each Claude refresh, CodexBar runs `cswap --list --json` independently of the ambient Claude fetch (no\n  shell, fixed arguments, bounded runtime and output), requires `schemaVersion == 1`, and parses only slot number,\n  active state, usage status, email, `organizationUuid`, display-only `organizationName` (always present, may be empty),\n  optional display-only `alias` when non-empty, the 5-hour/7-day windows, and optional display-only model-scoped\n  weekly windows from `usage.scoped`, optional `usage.spend`, and source measurement times. Identity stays\n  `claude-swap:<slot>`; organization name and alias are never\n  used as identity. When two or more slots share an email, cards append ` · organizationName` or ` · Account N`;\n  a user-chosen cswap alias replaces that label. Unique emails stay email-only.\n- Menu and terminal cards can show the source's `lastGoodUsage` when live usage is unavailable. Its required\n  `lastGoodFetchedAt` remains the measurement time, and the card shows a last-known marker and capture age alongside\n  the diagnostic. Terminal cards also show the capture timestamp. Malformed additive spend or last-good fields do not\n  discard valid live quota windows. The free-form row `message` is not parsed.\n  Brief terminal output keeps the diagnostic and excludes historical metrics from its warnings and next-reset summary.\n- Last-known usage keeps its provenance through quota retention and cache reload. It never drives the menu-bar icon,\n  whose compact display cannot show its age. Compact account rows show the capture age and do not recommend or fold\n  last-known rows into the ready-account group. Explicit source measurements take precedence over locally retained\n  quota windows.\n- Slots excluded from claude-swap's automatic rotation are marked `(disabled)` but remain valid explicit switch\n  targets. The active account label is emphasized.\n- Display: when claude-swap reports more than one account, its accounts replace ambient/token-account Claude cards.\n  The app honors **Menu → Multi-account layout**: Segmented shows account buttons and one active account card;\n  pending or failed switches show the requested account's details while the active marker stays source-owned.\n  Expired or otherwise unavailable accounts remain inspectable without activation; selecting the active account\n  returns to its card. If the adapter reports no active account, the menu says so instead of selecting the first row.\n  Buttons wrap into two rows above three accounts. Hide Personal Info uses stable `Account N` slot labels across\n  segmented buttons, native account cards, compact menu rows, and accessibility text, including unavailable accounts.\n  These numbers come from validated claude-swap slots and remain stable when accounts are reordered; aliases,\n  organization names, and email addresses stay hidden.\n  Stacked shows one card per account (active account first, then numeric slot). With four or more\n  accounts the stacked menu switches to a compact layout (`AccountMenuLayoutPlanner`): the active account keeps its full\n  card, inactive accounts become one-line rows sorted by remaining headroom (most constrained first, red/amber below\n  50%/10% left, a star on the healthiest activatable account), and healthy rows fold behind a \"N more accounts ready\"\n  summary row. Clicking a compact row expands that account's full card; clicking an inactive card collapses it again.\n  This choice is remembered locally across menu opens and app restarts. The summary row reveals the hidden rows for\n  the current menu session. `codexbar cards` keeps the full per-account output. The same compact layout applies to\n  every stacked multi-account list (token accounts on any provider, and flat Codex account lists; workspace-grouped\n  Codex lists keep their sectioned stacked layout). To use this\n  presentation with one account, enable “Show account card when only one account is available” or set\n  `claudeSwapShowSingleAccount: true` on the Claude provider in the resolved config file (normally\n  `~/.config/codexbar/config.json`; legacy installs may use `~/.codexbar/config.json`). The option defaults off,\n  zero accounts still use the ambient presentation, and account identity is `claude-swap:<slot>`, never the display\n  email.\n- Session forecasts use each account's own recorded usage. History keys hash the normalized email and organization\n  UUID from the existing list response, so moving slots preserves history and the same email in different workspaces\n  stays separate. Empty organization UUIDs identify personal accounts; an email change starts a new history.\n  Raw emails and organization UUIDs are not saved in history. Fresh samples retain their source timestamps and the\n  shared 17,520-sample limit per series; last-known or failed rows do not add samples. Ambient Claude history is separate.\n- Provider widgets follow the active claude-swap account under the same multiple-account/single-account presentation\n  rule. Successful list refreshes and clearing the adapter publish a widget snapshot even with account widgets off.\n  Retained quota keeps its original measurement time and is bound to the slot's opaque owner fingerprint; unavailable\n  or replaced accounts never borrow another account's quota. Local cost history remains combined across Claude homes.\n- Terminal scope: cards expands the account list, while Auto `codexbar usage` follows its active slot on every\n  supported CLI platform (including single-slot lists). An explicit\n  Claude provider or `--source auto` remains eligible, while `--account`, `--account-index`, `--all-accounts`, and\n  explicit non-auto source flags bypass the adapter. A configured non-auto source also bypasses it for `usage`.\n  Unavailable active-slot usage falls back to ordinary source selection with an explicit adapter fallback source label;\n  the returned identity belongs to that fallback source. Serve `/usage`/`/cost` remain unchanged,\n  while `codexbar dashboard` and `GET /dashboard/v1/snapshot` additionally nest one entry per swap account in the\n  Claude provider row, with full identity by default or redacted email local parts when `--identity redacted` is set.\n- Isolation: CodexBar never reads claude-swap or Claude Code credential storage for this feature; the\n  subprocess handles its own credential access. In the app, adapter failures keep the last successful accounts as\n  stale data, surface the error in provider settings, and never affect the ambient Claude usage card. In terminal\n  cards, a list failure retains the current ambient output, adds a distinct `Claude (claude-swap)` footer entry, and\n  exits non-zero.\n- Sentinel statuses (`token_expired`, `relogin_required`, `api_key`, `keychain_unavailable`, `no_credentials`,\n  `foreign_credential`, and unknown future values) retain per-account diagnostics. Full cards can show explicitly\n  reported last-known usage alongside its age; without it they remain notes-only. Brief terminal cards keep the\n  diagnostic. When no explicit last-good measurement is supplied and a window is still at 100%, CodexBar keeps that slot's last\n  projected usage bars and names the exhausted window (5-hour session, 7-day weekly, and/or a scoped model such as\n  Fable) plus its reset time — not \"Usage fetch failed.\" A first refresh that is already `unavailable` with no\n  retained windows says usage is unavailable, without assuming why the source could not fetch it. Active rows are marked `[active]`; no claude-swap row infers\n  a plan badge.\n- Read-only adapters may set top-level `supportsAccountSwitching: false` in their schema-v1 list response. Usage,\n  account details, and active markers remain visible, while switching and re-authentication actions are suppressed.\n  Omitting the capability preserves existing switching behavior; a present value must be a JSON boolean.\n- Switching: an inactive account with usable source credentials shows “Switch Account…”. Clicking it runs exactly\n  `cswap --switch-to <slot> --json`, validates the versioned result and requested slot, then refreshes both ambient\n  Claude usage and every claude-swap account card. Switches are serialized; no automatic switching occurs. While\n  reconciling, the ambient Claude refresh is given five seconds to finish; a stalled refresh continues in the\n  background while switching waits for the adapter's active-account list, so it cannot leave the account chips inert.\n  A later switch can refresh the adapter list even when its ambient refresh is queued behind an earlier probe. While\n  claude-swap owns account presentation, the separate ambient OAuth action reads “Sign in with Claude Code…” and does\n  not add or switch a claude-swap account.\n- Expired, missing, unknown, or Keychain-inaccessible credentials stay non-actionable. A failed switch remains visible\n  on that account without discarding its last successful usage. A running Claude Code process can take up to the\n  claude-swap Keychain cache interval to observe the new account.\n- A `foreign_credential` row explains that the live credential belongs to another account. An inactive row can use\n  the existing explicit slot switch. An active row offers **Re-authenticate**, which runs the same\n  `cswap --switch-to <slot> --json` command to let claude-swap reconcile its own credential state, without `--force`. Clicking the active segment\n  still only inspects it; repair requires its explicit button.\n- Multiple claude-swap accounts—and a single account when explicitly enabled—take precedence over Claude\n  token-account presentation (stacked cards and the segmented switcher).\n\nPackaged synthetic proof (fake `cswap` executable, no real accounts or credentials):\n\n![Stacked claude-swap account cards](screenshots/claude-swap-accounts-synthetic-proof.png)\n\nModel-scoped weekly-window proof (synthetic data, no real accounts or credentials):\n\n| Before | After |\n| --- | --- |\n| ![claude-swap card before scoped windows](screenshots/claude-swap-scoped-before.png) | ![claude-swap card with a Fable scoped weekly window](screenshots/claude-swap-scoped-after.png) |\n\n## CLI PTY (fallback)\n- Runs `claude` in a PTY session (`ClaudeCLISession`).\n- The PTY and replay screen share a 160-column, ","docs/llms.txt":"# CodexBar\n\nA tiny macOS menu bar app that tracks AI coding-provider usage windows, credits, costs, and resets across 108 providers — Codex, OpenAI, Claude, Cursor, Gemini, Copilot, LiteLLM, Helmcode, v0, TypeSafe, and more.\n\nCanonical documentation:\n- CodexBar — every AI coding limit, in your menu bar: https://codexbar.app/ - A tiny macOS menu bar app that tracks AI coding-provider usage windows, credits, costs, and resets across 108 providers — Codex, OpenAI, Claude, Cursor, Gemini, Copilot, LiteLLM, Helmcode, v0, TypeSafe, and more.\n\nSource: https://github.com/steipete/CodexBar\n\nGuidance for agents:\n- Prefer the canonical documentation URLs above over README excerpts or package metadata.\n- Fetch only the pages needed for the current task; this is an index, not a full-site corpus.\n","docs/deepseek.md":"---\nsummary: \"DeepSeek provider data sources: API key, balance, and optional detailed usage endpoints.\"\nread_when:\n  - Adding or tweaking DeepSeek balance parsing\n  - Adding or tweaking DeepSeek detailed usage parsing\n  - Updating API key handling\n  - Documenting new provider behavior\n---\n\n# DeepSeek provider\n\nCodexBar can use either a DeepSeek API key or a signed-in DeepSeek Platform session for the remaining credit balance.\nDetailed cost and token usage comes from the Platform session; an API key cannot authenticate the private dashboard\nendpoints.\n\n## Data sources\n\n1. **Optional API key** supplied via `DEEPSEEK_API_KEY` / `DEEPSEEK_KEY`, or selected from DeepSeek token accounts in `~/.codexbar/config.json`.\n2. **API-key balance endpoint**\n   - `GET https://api.deepseek.com/user/balance`\n   - Request headers: `Authorization: Bearer <api key>`, `Accept: application/json`\n   - Response contains `is_available`, and a `balance_infos` array with per-currency entries\n     (`total_balance`, `granted_balance`, `topped_up_balance`).\n3. **Platform-session balance endpoint**\n   - `GET https://platform.deepseek.com/api/v0/users/get_user_summary`\n   - Request headers: `Authorization: Bearer <platform userToken>`, `Accept: application/json`\n   - Used as the balance source when no API key is configured.\n4. **Optional detailed usage endpoints**\n   - Preferred: `GET https://platform.deepseek.com/api/v0/usage/by_api_key/amount?start=<unix>&end=<unix>&tz=<offset>`\n     and `.../by_api_key/cost?...` — the same per-key daily buckets the Platform usage page loads.\n   - Fallback: `GET https://platform.deepseek.com/api/v0/usage/amount?month=<month>&year=<year>`\n     and `.../usage/cost?...` if the per-key endpoints fail.\n   - Daily buckets use Gregorian dates at the current fixed UTC offset in seconds, matching the API across daylight-saving transitions. Monthly fallback keeps\n     Gregorian UTC month selection and date parsing, and is labeled **This month**, not **Last 30 days**.\n   - Cancellation stops enrichment without starting monthly fallback requests.\n   - Request headers: `Authorization: Bearer <platform userToken>`, `Accept: application/json`, `x-client-platform: web`\n   - These are private dashboard endpoints rather than documented public API endpoints and may change without notice.\n\n## Platform session\n\nCodexBar resolves the Platform `userToken` in this order:\n\n1. An explicitly supplied `DEEPSEEK_PLATFORM_TOKEN` / `DEEPSEEK_USER_TOKEN` or a legacy\n   `providers[].cookieHeader` value preserved from an existing config.\n2. A prompt-free read of `userToken` from the `https://platform.deepseek.com` local-storage origin in Chrome.\n\nThe legacy config value remains a compatibility fallback so upgrading cannot silently erase a working browser-only\nsession. An unscoped legacy or environment token is never combined with an API-key balance; API enrichment requires\na session saved for that credential scope. New automatic imports are never written back to config.\n\nCodexBar checks every Chrome profile containing a parseable `userToken` against DeepSeek. Rejected or expired\nsessions are omitted. Settings shows a **Chrome profile** picker containing only valid sessions. With no API key, one\nvalid session is selected automatically; with multiple valid sessions, CodexBar reuses the saved choice or asks once.\nWhen an API key supplies the balance, a new or changed API credential requires an explicit session selection before\nwebsite usage is combined with it. CodexBar persists a stable browser/profile identifier, not an absolute home-directory path, and\nkeeps automatically imported tokens in memory only. The choice is scoped to a non-reversible fingerprint of the\nactive API credential and saved-account slot, so replacing a key or switching accounts cannot silently reuse an old\nbrowser session. Validation results are cached briefly so normal\nrefreshes do not probe every profile, and a temporary network failure does not erase a previously validated profile.\nIf the selected session expires, CodexBar asks before switching to another valid profile.\n\nChrome-only balance refreshes also keep the last live balance and its original timestamp through temporary transport\nfailures when the failed request belongs to the same browser profile and token. This ownership proof stays in memory;\ndecoded snapshots, changed profiles or tokens, and API-key balances with optional browser enrichment cannot supply it.\nOnce DeepSeek rejects a session, a later network failure cannot preserve its old balance; successful validation must\nrestore that session first. A saved profile with unknown validity still supplies transport diagnostics when other profiles\nsucceed; a known-rejected selection keeps the profile picker and its valid alternatives.\nRecognized transport failures still participate in startup retries when no matching balance can be retained. A Chrome-resolution\ndeadline without an observed session fails closed; cancelled or superseded refresh tasks keep their existing behavior.\n\nIf no session is valid, the menu keeps the API-key balance when one exists; otherwise it asks the user to sign in to\nDeepSeek Platform in Chrome. Authentication failures returned as top-level or nested DeepSeek codes `40002` and\n`40003` are treated as expired sessions.\n\n## Usage details\n\n- The menu card shows total balance with the paid vs. granted breakdown:\n  e.g. `$50.00 (Paid: $40.00 / Granted: $10.00)`.\n- The API separates granted balance from topped-up balance; CodexBar labels these as granted vs. paid credit.\n- With optional extra usage enabled, the menu shows today's and last-30-days cost and tokens,\n  request counts, API-key count, the top model, per-model spend, a daily token chart, and a daily spend chart.\n- Per-model spend uses the same reporting period and currency as detailed usage: **Last 30 days** for the preferred\n  endpoints, or **This month** for monthly fallback. These are Platform-account totals across API keys. Missing or\n  invalid model costs are omitted; a reported zero is retained.\n- The amount and cost requests run concurrently. After balance arrives, CodexBar waits up to five seconds for\n  automatic Chrome resolution and detailed usage. The deadline remains bounded even if a local Chrome read does not\n  respond to cancellation. If the optional work fails or times out, the balance and previously validated profile list\n  remain available while the menu reports that detailed usage is unavailable.\n- With multiple configured API keys, browser-derived detailed usage is shown only with the active API-key account;\n  other account cards remain balance-only so website usage is never duplicated across accounts.\n- When multiple currencies are present, USD is shown preferentially.\n- If total balance is zero, CodexBar shows an add-credits message. If balance is nonzero but `is_available` is false, it shows \"Balance unavailable for API calls\".\n- There is no session or weekly window — DeepSeek does not expose per-window quota via API.\n- Token-account selection injects the selected key into the fetch environment; otherwise CodexBar reads `DEEPSEEK_API_KEY` / `DEEPSEEK_KEY`.\n\n## Key files\n\n- `Sources/CodexBarCore/Providers/DeepSeek/DeepSeekProviderDescriptor.swift` (descriptor + fetch strategy)\n- `Sources/CodexBarCore/Providers/DeepSeek/DeepSeekUsageFetcher.swift` (HTTP client + JSON parser)\n- `Sources/CodexBarCore/Providers/DeepSeek/DeepSeekPlatformTokenImporter.swift` (Chrome Platform session import)\n- `Sources/CodexBarCore/Providers/DeepSeek/DeepSeekSettingsReader.swift` (env var resolution)\n- `Sources/CodexBar/Providers/DeepSeek/DeepSeekProviderImplementation.swift` (provider activation and token-account visibility)\n- `Sources/CodexBarCore/TokenAccountSupportCatalog+Data.swift` (DeepSeek token-account injection)\n","docs/mistral.md":"---\nsummary: \"Mistral provider: browser cookie setup, billing usage, included API/Vibe allowances, and credits.\"\nread_when:\n  - Configuring Mistral usage\n  - Debugging Mistral billing or Vibe usage requests\n  - Adjusting Mistral cost, credit, or monthly-plan display\n---\n\n# Mistral Provider\n\nCodexBar reads Mistral billing usage and subscription allowances with the Mistral web session from\n`admin.mistral.ai`. It also fetches credit balance and falls back to the console Vibe endpoint when the subscription\npage does not expose a Vibe allowance.\n\n## Setup\n\n1. Open **Settings -> Providers**.\n2. Enable **Mistral**.\n3. Sign in to [Mistral Admin](https://admin.mistral.ai/organization/usage) in a supported browser.\n4. Leave Cookie source on **Automatic**, or switch to **Manual** and paste a `Cookie:` header from a request to\n   `admin.mistral.ai`.\n\nManual cookies must include an `ory_session_*` cookie. A `csrftoken` cookie enables fallback Vibe requests that\nrequire the `X-CSRFTOKEN` header.\n\nOn Linux, browser import is unavailable, so the CLI reads Mistral only in Manual mode: set the provider's\n`cookieSource` to `manual` and its `cookieHeader` to the pasted header in `~/.config/codexbar/config.json`.\n\nAutomatic import follows the shared supported-browser order. Safari requires Full Disk Access.\nThe shared access gate bounds Keychain prompts. Automatic import reads only unexpired cookies from\nthe documented Mistral domains.\n\n## Data Sources\n\nCodexBar requests the current UTC month, subscription allowances, and credits from Mistral Admin:\n\n- `GET https://admin.mistral.ai/api/billing/v2/usage?month=<month>&year=<year>`\n- `GET https://admin.mistral.ai/subscription` (best-effort included API and Vibe allowances)\n- `GET https://admin.mistral.ai/api/billing/credits` (best-effort credit balance)\n\nIf the subscription page has no Vibe allowance and a CSRF token is available, CodexBar makes a bounded best-effort\nfallback request:\n\n- `GET https://console.mistral.ai/api-ui/trpc/billing.vibeUsage?...`\n\nFor the console request, CodexBar forwards only the `csrftoken` and `ory_session_*` cookies. Other\n`admin.mistral.ai` cookies stay origin-bound.\n\n## Display\n\n- **Included API** shows the subscription allowance's used percentage, used / total / remaining amount, and reset time.\n- The optional **Monthly Plan** window shows the separate Vibe Code allowance with the same details.\n- API spend is computed from billed units (`value_paid`, falling back to `value`) and the pricing table. Each unit takes\n  the price with the same event type, metric, group, API zone, and service tier; the table lists one metric under\n  several of these, and audio-second and priority prices are far higher than standard token prices. Token totals\n  and daily buckets use consumed units (`value`, falling back to `value_paid`), so plan-covered usage still counts.\n  Legacy tables that omit both API zone and service tier use the unqualified price for the same event type, metric, and group.\n- Token totals include API completions, Le Chat, and Vibe Code completions from the billing usage response.\n- Daily usage buckets feed the inline usage dashboard.\n- The provider card can show credit balance when the credits endpoint returns it.\n- Allowance amounts derive from Mistral's reported percentage and allowance size, independently of billed API spend. Zero or malformed allowances are omitted without discarding a valid sibling allowance.\n- The Automatic menu bar selection retains API spend; Included API and Monthly Plan select their respective quota percentages.\n- Token-cost history is supported through the billing web session; no local log scan is used.\n- Unrepresentable billing token totals fail parsing instead of crashing. Display-only model rankings omit an\n  overflowing total while retaining valid cost data.\n- Final input, cached, and output totals allow signed adjustments in any lane while rejecting totals outside the\n  supported integer range.\n\n## Widgets\n\nUsage widgets follow the **Menu bar metric** picker in Mistral's provider settings. The picker appears in every menu bar\nstyle, so Critters and Meter bars users can still pick the widget allowance. Choosing a percentage metric pins\nMistral’s layout against later global layout edits. Without a percentage, the picker changes only the stored metric\nand keeps following the global layout:\n\n- **Automatic** and **Included API** show only the API allowance, preserving the existing default.\n- **Monthly Plan** shows only the Vibe allowance, falling back to Included API when the plan is missing or unknown.\n\nAutomatic still shows API spend in the menu bar. Changing the metric updates widget rows from the existing snapshot,\nwithout another request. After a failed refresh, widgets reselect from the last published usage snapshot and keep its\noriginal measurement time, including across repeated metric changes. This in-memory source is cleared when provider\nor account ownership is invalidated. Burn Down eligibility still requires a known window duration and reset.\n\n## CLI Usage\n\n```bash\ncodexbar usage --provider mistral --verbose\n```\n\nText output includes both Included API and the optional Monthly Plan, each with its percentage, reset date when\navailable, and used / total / remaining amounts. Amounts are shown as detail, never as a reset time. This uses the\nexisting snapshot; no additional requests are made. JSON output is unchanged: the Monthly Plan remains in\n`extraRateWindows` with the ID `mistral-monthly-plan`.\n\n## Troubleshooting\n\n### \"No Mistral session cookies found\"\n\nSign in to [Mistral Admin](https://admin.mistral.ai/organization/usage) in a supported browser, then refresh.\n\n### \"Mistral cookie header is invalid\"\n\nIn manual mode, paste a full `Cookie:` header from an `admin.mistral.ai` request. The header must include an\n`ory_session_*` cookie.\n\n### Included allowance, credits, or Vibe plan usage are missing\n\nThe billing usage request is required. Subscription allowances, credits, and Vibe usage are best-effort; if an\noptional source fails or does not expose data for the account, CodexBar keeps the main Mistral usage result.\n\n## Related Files\n\n- `Sources/CodexBarCore/Providers/Mistral/MistralProviderDescriptor.swift`\n- `Sources/CodexBarCore/Providers/Mistral/MistralUsageFetcher.swift`\n- `Sources/CodexBarCore/Providers/Mistral/MistralSubscriptionBudgetParser.swift`\n- `Sources/CodexBarCore/Providers/Mistral/MistralModels.swift`\n- `Sources/CodexBarCore/Providers/Mistral/MistralCookieImporter.swift`\n- `Sources/CodexBar/Providers/Mistral/MistralProviderImplementation.swift`\n",".agents/skills/qa-test/references/api-specs.md":"# References Skill\n\nPath: `.agents/skills/qa-test/references/api-specs.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/steipete/CodexBar/HEAD/.agents/skills/qa-test/references/api-specs.md)",".agents/skills/qa-test/agents/openai.yaml":"# Agents Skill\n\nPath: `.agents/skills/qa-test/agents/openai.yaml`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/steipete/CodexBar/HEAD/.agents/skills/qa-test/agents/openai.yaml)"},"files":{".agents/skills/qa-test/SKILL.md":"---\nname: qa-test\ndescription: \"CodexBar live QA/e2e testing: run provider usage matrix checks, validate real app config, use Peekaboo for menu proof, use Browser Use/official docs for API spec or logged-in dashboard checks, and handle 1Password credentials safely.\"\n---\n\n# CodexBar Live QA\n\nUse for live provider testing, release smoke tests, menu verification, or debugging “provider works/fails” reports.\n\n## Rules\n\n- Work from the CodexBar repo checkout.\n- Use the packaged CLI first: `CodexBar.app/Contents/Helpers/CodexBarCLI`.\n- Do not use `CodexBar.app/Contents/MacOS/codexbar`; that is the app binary and may appear to hang as a CLI.\n- Never run broad `env`, `set`, or secret regex dumps.\n- Use `$one-password` for secrets: all `op` commands inside one persistent tmux session, service account first, no raw secret output.\n- Treat browser-cookie/keychain flows as prompt-risky. Prefer CLI/API-token checks and `KeychainNoUIQuery`-safe tests unless the user explicitly requested live UI.\n- For current API behavior, browse official provider docs only.\n\n## CLI Matrix\n\nRun the bundled script:\n\n```bash\n.agents/skills/qa-test/scripts/live_provider_matrix.sh --enabled\n```\n\nUseful modes:\n\n```bash\n.agents/skills/qa-test/scripts/live_provider_matrix.sh --provider all\n.agents/skills/qa-test/scripts/live_provider_matrix.sh --providers openai,zai,deepseek\n.agents/skills/qa-test/scripts/live_provider_matrix.sh --default\n```\n\nInterpretation:\n\n- `--enabled` asks `CodexBarCLI config providers` for enabled providers, honoring `CODEXBAR_CONFIG` and default toggles.\n- `--default` runs the app-facing default command with no provider override.\n- `--provider all` forces every registered provider and is expected to fail for providers without sessions/keys.\n- A green app config needs `--enabled` and `--default` clean; `--provider all` is a discovery/triage tool.\n\n## Config QA\n\nValidate config:\n\n```bash\nCodexBar.app/Contents/Helpers/CodexBarCLI config validate\nstat -f '%Lp %N' \"$HOME/.codexbar/config.json\"\n```\n\nRedact config shape:\n\n```bash\njq '(.providers // []) |= map(.apiKey = (if .apiKey then \"<redacted>\" else .apiKey end) |\n  .secretKey = (if .secretKey then \"<redacted>\" else .secretKey end) |\n  .cookieHeader = (if .cookieHeader then \"<redacted>\" else .cookieHeader end) |\n  (if .id == \"stepfun\" and has(\"region\") then .region = \"<redacted>\" else . end) |\n  .tokenAccounts = (if .tokenAccounts then (.tokenAccounts | .accounts = (.accounts | map(.token = \"<redacted>\"))) else .tokenAccounts end))' \\\n  \"$HOME/.codexbar/config.json\"\n```\n\nBefore editing config, make a backup:\n\n```bash\ncp \"$HOME/.codexbar/config.json\" \"$HOME/.codexbar/config.pre-qa-$(date +%Y%m%d%H%M%S).json\"\nchmod 600 \"$HOME/.codexbar\"/config.pre-qa-*.json\n```\n\n## Live Menu QA\n\nUse Peekaboo after CLI checks:\n\n```bash\npkill -x CodexBar || pkill -f 'CodexBar.app/Contents/MacOS/CodexBar' || true\nopen -n \"$PWD/CodexBar.app\"\npeekaboo menu list-all --json | rg -i 'codexbar'\npeekaboo menu click-extra --title codexbar-merged --json\nscreencapture -x /tmp/codexbar-live-menu.png\n```\n\nCrop top-right menu if needed:\n\n```bash\nsips --cropToHeightWidth 900 340 --cropOffset 20 2650 /tmp/codexbar-live-menu.png \\\n  --out /tmp/codexbar-live-menu-crop.png >/dev/null\n```\n\nVerify visually with `view_image`. Confirm provider tabs/rows match enabled config and no failing provider dominates the first screen.\n\n## Browser Use\n\nUse `$browser-use` only when a logged-in dashboard, API key page, or provider docs need browser/profile state.\n\nExisting Chrome path:\n\n```bash\nmcporter call chrome-devtools.list_pages --args '{}' --output text\nmcporter call chrome-devtools.navigate_page --args '{\"url\":\"https://provider.example\"}' --output text\nmcporter call chrome-devtools.take_snapshot --args '{}' --output text\n```\n\nIf Browser Use is unavailable, say so and use web search for public official docs; do not substitute isolated Playwright for login/profile-dependent pages.\n\n## Fix Triage\n\n- Missing auth/session: configure key/session if available; otherwise leave provider disabled or report blocked auth.\n- Wrong provider API/spec: inspect official docs, then patch fetcher/settings/tests.\n- Provider key exists but live API rejects it: keep key stored if useful, disable provider if the menu would show a persistent error.\n- User-facing behavior changes need `CHANGELOG.md`.\n- Code fixes need focused tests, `make check`, `$autoreview`, and live CLI proof before landing.\n\n## Known CodexBar QA Notes\n\n- OpenAI Admin API key is the useful usage provider key. Project `OPENAI_API_KEY` values can fail legacy credit-balance fallback with 403.\n- Deepgram usage requires a key/project with Management API permissions; transcription-only keys can return 403.\n- Groq usage uses the Prometheus metrics API, not ordinary inference endpoints.\n- MiniMax pay-as-you-go API keys and Token Plan/Coding Plan keys are different; wrong key kind can leave usage unavailable.\n",".agents/skills/codexbar/SKILL.md":"---\nname: codexbar\ndescription: \"CodexBar read. Provider usage, limits, credits, config health. JSON. No writes.\"\n---\n\n# CodexBar\n\nRead CodexBar. Never mutate config/auth.\n\n## Run\n\n```bash\nskill=\"${CODEX_HOME:-$HOME/.codex}/skills/codexbar\"\n\"$skill/scripts/codexbar\" doctor\n\"$skill/scripts/codexbar\" providers\n\"$skill/scripts/codexbar\" usage\n\"$skill/scripts/codexbar\" usage --provider codex\n\"$skill/scripts/codexbar\" usage --all\n```\n\nAll stdout: JSON. Upstream CodexBar shape kept. Less drift, fewer tokens.\n\n## Rules\n\n- Start `doctor` when install/config unknown.\n- `usage` reads enabled providers. Prefer this.\n- `usage --provider ID` reads one provider.\n- `usage --all` expensive; use only when needed.\n- Identities hidden by default. `--include-identities` only when user explicitly needs them.\n- Secrets always hidden.\n- Helper read-only: fixed allowlist only. No config writes, auth repair, enable/disable, key storage.\n- Timeout means upstream stuck. Narrow provider or raise `CODEXBAR_TIMEOUT` (default 120 seconds).\n\n## Binary\n\nAuto-find: `CODEXBAR_BIN`, PATH, app bundle, Homebrew cask. If missing: open CodexBar, Preferences > Advanced > Install CLI; or set `CODEXBAR_BIN`.\n\nEach stdout/stderr stream capped at 1 MiB while fully drained. Timeout kills process group.\n","docs/copilot.md":"---\nsummary: \"Copilot provider data sources: GitHub device flow, Copilot internal usage API, and optional GitHub web budgets.\"\nread_when:\n  - Debugging Copilot login or usage parsing\n  - Updating GitHub OAuth device flow behavior\n---\n\n# Copilot provider\n\nCopilot uses GitHub OAuth device flow and the Copilot internal usage API for primary usage. Optional budget extras use GitHub web cookies only when enabled.\n\nCLI-only setups can run `codexbar config login --provider copilot` (or add `--json` for a stream of login\nevents). It uses the same device flow, issuer-aware account matching, and config-backed token accounts as\nthe app, without opening a browser or prompting for Keychain access. See [CLI configuration](cli-configuration.md#copilot-device-login).\n\n## Data sources + fallback order\n\n1) **GitHub OAuth device flow** (user initiated)\n   - Device code request:\n     - `POST https://github.com/login/device/code`\n   - Token polling:\n     - `POST https://github.com/login/oauth/access_token`\n   - Optional enterprise host:\n     - set Copilot `enterpriseHost` in `~/.codexbar/config.json` or the provider settings UI\n     - CodexBar normalizes values such as `https://octocorp.ghe.com/login` to `octocorp.ghe.com`\n     - device flow uses `https://<enterpriseHost>/login/...`\n     - identity lookup uses `https://api.<enterpriseHost>/user`, matching the usage API host\n     - default HTTPS ports and trailing host dots normalize to the same issuer; nondefault ports remain distinct\n   - Scope: `read:user`.\n   - Token stored in config:\n     - `~/.codexbar/config.json` → `providers[].apiKey` for `copilot`\n     - token accounts use `providers[].tokenAccounts`\n     - Legacy token matching honors a resolved stable GitHub user ID; display-label fallback is used only when token identity is unavailable and no verified match exists.\n     - Enterprise accounts match by API host and numeric user ID. They never adopt a public or unidentified legacy account merely because its login or label matches. The configured host still controls requests; stored account identifiers do not select an endpoint.\n     - Enterprise sign-in requires a resolved identity. A cancelled sign-in or a host change while it is pending leaves saved accounts unchanged.\n\n2) **Usage fetch**\n   - `GET https://api.github.com/copilot_internal/user`\n   - With an enterprise host, the API host is `api.<enterpriseHost>`.\n   - Headers:\n     - `Authorization: token <github_oauth_token>`\n     - `Accept: application/json`\n     - `Editor-Version: vscode/1.96.2`\n     - `Editor-Plugin-Version: copilot-chat/0.26.7`\n     - `User-Agent: GitHubCopilotChat/0.26.7`\n     - `X-Github-Api-Version: 2025-04-01`\n\n3) **Budget fetch** (optional GitHub web endpoint, best-effort)\n   - Available only for the public GitHub host. Enterprise quota fetches skip public identity and browser-cookie budget enrichment.\n   - Disabled by default. The Copilot provider's \"Budget extras\" setting must be enabled before CodexBar imports\n     github.com cookies or renders budget bars.\n   - Automatic cookie imports follow the shared supported-browser order and access gate; background imports never request Keychain permission.\n   - CodexBar asks the logged-in GitHub web endpoint for customer-scope budgets:\n     - `GET https://github.com/settings/billing/budgets?page=<page>&page_size=10&scope=customer`\n   - Headers:\n     - `Cookie: <github.com browser cookies>`\n     - `Accept: application/json`\n     - `X-Requested-With: XMLHttpRequest`\n     - `GitHub-Verified-Fetch: true`\n     - `X-Fetch-Nonce: <fresh nonce when available>`\n   - CodexBar first tries to read a fresh nonce from `https://github.com/settings/billing/budgets`, then calls the JSON\n     endpoint. If GitHub rejects the web request, CodexBar keeps the normal Copilot quota bars and omits budget bars.\n   - This is intentionally not the public GitHub REST billing API. The REST API did not expose the personal budget list\n     for the tested individual account.\n\n## Snapshot mapping\n- Primary: `quotaSnapshots.premiumInteractions` percent remaining → used percent.\n- Secondary: `quotaSnapshots.chat` percent remaining → used percent.\n- Extra: positive Copilot billing budgets from the GitHub web endpoint → `extraRateWindows`, only when \"Budget extras\"\n  is enabled.\n  - Product budget: `copilot`\n  - SKU budgets: `copilot_premium_request`, `copilot_agent_premium_request`, `spark_premium_request`\n- Seat AI credits: `quota_snapshots.premium_interactions.credits_used` → the shared \"Credits used\" provider-detail\n  row, shown only when it carries real signal (token-based billing, unlimited quota, nonzero credits, or a\n  configured seat entitlement) so a metered Pro/Individual seat never grows a permanent \"0 credits used\" row.\n  Deliberately not summed with `chat`/`completions` credits — GitHub can report the same pool under multiple\n  snapshot keys, and summing would double-count.\n- Reset dates are not provided by the API.\n- Plan label from `copilotPlan`.\n\n## AI credit entitlements\nGitHub does not publish an included-credit entitlement on any documented endpoint — all 8 billing endpoints plus\n`budgets`, `cost-centers`, and `usage/summary` were probed, and none returns a ceiling for the seat. The denominator\nis therefore user-entered:\n- Preferences → Providers → Copilot → \"Included AI credits (per seat)\"\n\nA configured entitlement turns the row into a progress bar (\"31 / 3000\") via the shared provider-detail row's\noptional progress ratio. Without one, the row stays plain text, because a bar would imply a limit CodexBar cannot\nactually know. Either way the row also carries the numeric credits used (`usageValue`), so editing or clearing\nthe entitlement rewrites the cached row (text ↔ bar) immediately, even when the follow-up refresh never lands\n(offline, token lost, 401).\n\nIn Automatic mode, the provider tab uses this configured seat-credit ratio when no metered quota window is\navailable. It follows the used/remaining preference. Existing quota windows retain priority, explicit metric\nselections retain their meaning, and a missing allowance leaves the tab's progress bar absent.\n\n## Manual seat credit allowance\n\nFor token-billed seats, **Seat AI credit allowance** optionally turns the menu card's Credits used text into a progress\nbar. Enter a positive monthly credit allowance; no ceiling is inferred from the plan. This is a local display preference,\nseparate from Budget extras, and does not set a GitHub billing limit. Icon, widget, and CLI quota windows are unchanged.\n\nEach saved account can override the legacy global allowance. Clearing an override restores that fallback, or returns to\ntext if no fallback exists. **Clear default allowance** removes the legacy fallback while retaining explicit account\noverrides; accounts inheriting that default return to text immediately, including while offline. Edits update the matching cached account without\nchanging its usage timestamp. Switching accounts preserves each account's usage; changing a credential or enterprise\nhost still invalidates its cached data. A refresh already in flight uses the current allowance when it publishes, so a\nlate successful response cannot restore a cleared default.\n\n## Key files\n- `Sources/CodexBarCore/Providers/Copilot/CopilotUsageFetcher.swift`\n- `Sources/CodexBarCore/Providers/Copilot/CopilotDeviceFlow.swift`\n- `Sources/CodexBarCore/Providers/Copilot/CopilotCreditEntitlementParser.swift`\n- `Sources/CodexBar/Providers/Copilot/CopilotLoginFlow.swift`\n- `Sources/CodexBar/CopilotTokenStore.swift` (legacy migration helper)\n","AGENTS.md":"# Repository Guidelines\n\n## Project Structure & Modules\n- `Sources/CodexBar`: Swift 6 menu bar app (usage/credits probes, icon renderer, settings). Keep changes small and reuse existing helpers.\n- `Tests/CodexBarTests`: XCTest coverage for usage parsing, status probes, icon patterns; mirror new logic with focused tests.\n- `Scripts`: build/package helpers (`package_app.sh`, `sign-and-notarize.sh`, `build_icon.sh`, `compile_and_run.sh`). Read-only release helpers call `Scripts/mac-release`, which resolves `MAC_RELEASE_TOOL` or the shared `agent-scripts` checkout; CI owns publication.\n- `docs`: release notes and process (`docs/RELEASING.md`, screenshots). Root-level zips/appcast are generated artifacts—avoid editing except during releases.\n\n## Build, Test, Run\n- Dev loop: `./Scripts/compile_and_run.sh` kills old instances, builds, packages, relaunches `CodexBar.app`, and confirms it stays running; add `--test` for the sharded full suite.\n- Quick build/test: `swift build` (debug) or `swift build -c release`; `make test` for the sharded full suite.\n- Package locally: `./Scripts/package_app.sh` to refresh `CodexBar.app`, then restart with `pkill -x CodexBar || pkill -f CodexBar.app || true; cd /Users/steipete/Projects/codexbar && open -n /Users/steipete/Projects/codexbar/CodexBar.app`.\n- Release publication is owned by `.github/workflows/release.yml`; use its default dry-run dispatch to rehearse. Repo build/signing stays in `Scripts/sign-and-notarize.sh`; see `docs/RELEASING.md`.\n\n## Coding Style & Naming\n- Enforce SwiftFormat/SwiftLint: run `swiftformat Sources Tests` and `swiftlint --strict`. 4-space indent, 120-char lines, explicit `self` is intentional—do not remove.\n- Favor small, typed structs/enums; maintain existing `MARK` organization. Use descriptive symbols; match current commit tone.\n\n## Testing Guidelines\n- Add/extend XCTest cases under `Tests/CodexBarTests/*Tests.swift` (`FeatureNameTests` with `test_caseDescription` methods).\n- Swift Testing: prefer backticked sentence names; no camelCase.\n- Model names in tests/code: released models or clearly fictitious names only; never expose unreleased names.\n- Iterate with focused runs (`make test-fast FILTER='SuiteName'` or `swift test --filter ...`) covering the touched suites. For a local full run use `./Scripts/test.sh --direct-workers 8` (parallel isolated groups, ~10 min) instead of serial `make test` (~45 min). The full suite is not a handoff gate: run it in the background or leave it to PR CI; never block on it in the foreground.\n- While iterating, format/lint only touched files; run `make check` once before commit/handoff and fix reported issues.\n- Prefer CLI/focused tests over app-bundle live tests when behavior can be verified without relaunching CodexBar.\n- Never run tests/checks or ad-hoc validation that can display macOS Keychain prompts. Live provider probes, browser-cookie imports, `codexbar usage` against real accounts, and real SecItem reads must be explicitly requested; otherwise use parser tests, stubs, test stores, or `KeychainNoUIQuery`.\n- App-group migration tests must inject dictionary-backed defaults, both snapshot URLs, a synthetic home, and a contained recording FileManager. UUID defaults suites and Keychain isolation flags do not isolate defaults search domains or filesystem access. Ordinary SettingsStore tests must not discover shared defaults or run app-group migration.\n- macOS CI is brittle around headless AppKit status/menu tests. Prefer covering menu behavior through stable state/model seams (`MenuDescriptor`, `ProvidersPane`, `CodexAccountsSectionState`, etc.) instead of constructing live `NSStatusBar`/`NSMenu` flows unless the AppKit wiring itself is the thing under test.\n\n## CI Efficiency\n- Follow [the direct-test and SwiftPM cache contracts](docs/DEVELOPMENT.md#macos-direct-test-groups) when changing CI or test execution; preserve full test inventory, deadlines, failure handling, cache verification, and clean fallback.\n- Reuse incremental builds during iteration; never copy compiled products between worktrees. Clean `.build` for diagnosed contamination, a cold-build comparison, or maintainer-requested cleanup.\n- Run focused tests during development; the final code needs focused tests plus `make check`, with the full suite from PR CI or a background run. Repeat successful full checks when relevant inputs or the environment change, a failure or unresolved concern requires it, or a maintainer requests it; do not repeat them solely to prepare a report.\n\n## Commit & PR Guidelines\n- Commit messages: short imperative clauses (e.g., “Improve usage probe”, “Fix icon dimming”); keep commits scoped.\n- PRs/patches should list summary, commands run, screenshots/GIFs for UI changes, and linked issue/reference when relevant.\n\n## Agent Notes\n- Use the provided scripts and package manager (SwiftPM); avoid adding dependencies or tooling without confirmation.\n- Menu bar automation: capture the target screen first and verify the CodexBar icon is visibly onscreen. Reject `click-extra` success when coordinates fall outside display bounds; hidden menu extras are not click proof.\n- Validate UI/runtime behavior against the freshly built bundle; restart via the pkill+open command above to avoid running stale binaries.\n- To guarantee the right bundle is running after a rebuild, use: `pkill -x CodexBar || pkill -f CodexBar.app || true; cd /Users/steipete/Projects/codexbar && open -n /Users/steipete/Projects/codexbar/CodexBar.app`.\n- For CLI-testable provider/parser/settings behavior, use CLI/focused tests instead of `Scripts/package_app.sh` or `./Scripts/compile_and_run.sh`.\n- Run `./Scripts/compile_and_run.sh` only when UI/runtime behavior needs bundle-level validation; it builds, tests, packages, relaunches, and verifies the app stays running.\n- Widget/Tahoe UI issues: use Parallels macOS VM plus screenshots/clicks for autonomous verification.\n- Release workflow: wait for completion and verify the complete artifact/feed/tap handoff; do not publish locally.\n- Sparkle release key: CI `SPARKLE_PRIVATE_KEY` must match the existing AGCY public key in `.github/release.json`; never substitute another app’s key.\n- Swift concurrency: treat sibling `async let` tasks as a review red flag when one child is required and another is optional/best-effort. Prefer sequential awaits or a drained `withThrowingTaskGroup` that surfaces required failures and explicitly contains optional failures; crash stacks mentioning `swift_task_dealloc` or `asyncLet_finish_after_task_completion` should trigger an audit of nearby `async let` usage.\n- Prefer modern SwiftUI/Observation macros: use `@Observable` models with `@State` ownership and `@Bindable` in views; avoid `ObservableObject`, `@ObservedObject`, and `@StateObject`.\n- Favor modern macOS 15+ APIs over legacy/deprecated counterparts when refactoring (Observation, new display link APIs, updated menu item styling, etc.).\n- Keep provider data siloed: when rendering usage or account info for a provider (Claude vs Codex), never display identity/plan fields sourced from a different provider.\n- Claude CLI status line is custom + user-configurable; never rely on it for usage parsing by default. A user-enabled, opt-in statusLine JSON feed is permitted as an explicit data source (owner ruling, #2733): it must be off by default, clearly labeled as sourced from the user's own statusLine config, and fail soft when the format drifts.\n- Cookie imports: use the shared default browser order; `BrowserCookieAccessGate` ensures no background/settings prompts, host-matching cookie rows before Safe Storage access, at most one interactive Keychain read per manual action, denial cooldowns even on manual retries (only `codexbar cookie refresh --allow-keychain-prompt` overrides), and traversal stopping at the first validated session. Restrict a provider to a browser subset only with a documented technical reason next to the code.\n",".agents/skills/release-codexbar/SKILL.md":"---\nname: release-codexbar\ndescription: \"CodexBar release: versioning, notarization, appcast, Homebrew, post-release bump.\"\n---\n\n# CodexBar Release\n\nUse for releasing signed/notarized macOS apps, especially repos with Sparkle appcasts and Homebrew casks.\n\n## Start\n\n1. Work from the app repo unless asked otherwise.\n2. Check repo state, current version, latest tag/release, and release docs/scripts.\n3. Confirm `CHANGELOG.md` is complete, user-facing, deduped, and dated for the release.\n4. Prefer the repo release script; patch small script/test blockers instead of bypassing the release path.\n5. Never print key material. Keep 1Password references and local key paths as references only.\n6. Load `$release-private` if it exists before resolving Peter-owned credential locators.\n\n## Key Material\n\nUse `$one-password` for secret handling. `op` only in tmux/persistent shell; no broad `env`, `set`, `export -p`, or secret scans.\n\nKnown App Store Connect shape:\n\n- fields: `private_key_p8`, `key_id`, `issuer_id`\n- keep all three fields from the same 1Password item; do not mix with stale values from `~/.profile`\n- resolve Peter-owned item refs from `$release-private`\n\nKnown Sparkle key:\n\n- resolve the private key file from `$release-private`\n- pass as `SPARKLE_PRIVATE_KEY_FILE`\n\nSafe env file pattern:\n\n```text\nAPP_STORE_CONNECT_API_KEY_P8=<1Password ref from release-private>\nAPP_STORE_CONNECT_KEY_ID=<1Password ref from release-private>\nAPP_STORE_CONNECT_ISSUER_ID=<1Password ref from release-private>\nSPARKLE_PRIVATE_KEY_FILE=<path from release-private>\n```\n\nRun with `op run --account my.1password.com --env-file <file> -- <script>`, then delete the temp env file.\n\n## CodexBar\n\nPaths:\n\n- repo: `~/Projects/codexbar`\n- release script: `Scripts/release.sh`\n- signing/notarization: `Scripts/sign-and-notarize.sh`\n- appcast: `Scripts/make_appcast.sh`, `appcast.xml`\n- release assets: `CodexBar-macos-universal-<version>.zip`, `CodexBar-macos-universal-<version>.dSYM.zip`\n- packaged app: `CodexBar.app`\n- version file: `version.env`\n- changelog: `CHANGELOG.md`\n- Homebrew tap: `~/Projects/homebrew-tap`\n- cask: `~/Projects/homebrew-tap/Casks/codexbar.rb`\n- formula: `~/Projects/homebrew-tap/Formula/codexbar.rb`\n- CLI release workflow: `.github/workflows/release-cli.yml`\n\nNormal release:\n\n```bash\ntmux new-session -d -s codexbar-release 'op run --account my.1password.com --env-file /tmp/codexbar-release-op.env -- Scripts/release.sh'\ntmux attach -t codexbar-release\n```\n\nIf notarization fails with `401 Unauthenticated`, rerun using all three App Store Connect fields from the 1Password item above. Mismatched `key_id` / `issuer_id` from `~/.profile` can cause this.\n\nIf widget metadata generation times out, `CODEXBAR_WIDGET_METADATA_TIMEOUT_SECONDS=600` is a known-good floor.\n\nCodexBar CLI tarballs are not produced by `Scripts/release.sh` itself. The GitHub release event triggers `.github/workflows/release-cli.yml`, which builds and uploads:\n\n- `CodexBarCLI-v<version>-macos-arm64.tar.gz`\n- `CodexBarCLI-v<version>-macos-x86_64.tar.gz`\n- `CodexBarCLI-v<version>-linux-aarch64.tar.gz`\n- `CodexBarCLI-v<version>-linux-x86_64.tar.gz`\n- matching `.sha256` files\n\nIf the workflow fails only in `update-homebrew-tap` with GitHub API rate limiting, the CLI assets may already be uploaded. Verify assets live, then update `Formula/codexbar.rb` manually from the tarball checksums.\n\n## Verify\n\nRelease is not done until the published chain checks out:\n\n```bash\ngh release view v<VERSION> --json tagName,name,isDraft,isPrerelease,url,assets,body\nScripts/check-release-assets.sh v<VERSION>\npython3 - <<'PY'\nimport xml.etree.ElementTree as ET\nns={'sparkle':'http://www.andymatuschak.org/xml-namespaces/sparkle'}\nroot=ET.parse('appcast.xml').getroot()\nitem=root.find('channel').find('item')\nenc=item.find('enclosure')\nprint(item.findtext('title'))\nprint(item.findtext('sparkle:version', namespaces=ns))\nprint(item.findtext('sparkle:shortVersionString', namespaces=ns))\nprint(enc.attrib.get('url'))\nprint(enc.attrib.get('length'))\nprint(bool(enc.attrib.get('{http://www.andymatuschak.org/xml-namespaces/sparkle}edSignature')))\nPY\ncodesign --verify --deep --strict --verbose=2 CodexBar.app\nspctl --assess --type execute --verbose CodexBar.app\n```\n\nFor Homebrew:\n\n```bash\nshasum -a 256 CodexBar-macos-universal-<VERSION>.zip\ncd /Users/steipete/Projects/homebrew-tap\npython3 .github/scripts/update_formula.py --formula codexbar --tag v<VERSION> --repository steipete/CodexBar --artifact-template 'CodexBarCLI-{tag}-{target}.tar.gz' --target-aliases 'darwin_arm64=macos-arm64,darwin_amd64=macos-x86_64,linux_arm64=linux-aarch64,linux_amd64=linux-x86_64'\nbrew fetch --cask --force --retry codexbar\nbrew fetch --formula --force --retry steipete/tap/codexbar\n```\n\nUpdate the cask when app zip assets exist. Update the formula only when standalone CLI tarballs for that version exist.\n\nTap audit can be noisy from unrelated formulae; keep evidence specific to the app cask.\n\n## Closeout\n\n1. Create/push tag and GitHub release through the release script.\n2. Verify appcast points to the new GitHub release asset with signature and length.\n3. Update/push the Homebrew cask if the app zip changed.\n4. Bump the app repo to next patch `Unreleased`:\n   - `version.env`: next `MARKETING_VERSION`, next `BUILD_NUMBER`\n   - `CHANGELOG.md`: top `## <next> — Unreleased`\n5. Commit, push, then pull `--ff-only`.\n6. Restart the local app from the packaged bundle and verify the running bundle version.\n7. Check no release/notary/op temp sessions or temp env files remain.\n\nCodexBar restart:\n\n```bash\npkill -x CodexBar || pkill -f CodexBar.app || true\ncd \"$(git rev-parse --show-toplevel)\"\nopen -n CodexBar.app\n/usr/libexec/PlistBuddy -c 'Print :CFBundleShortVersionString' CodexBar.app/Contents/Info.plist\n/usr/libexec/PlistBuddy -c 'Print :CFBundleVersion' CodexBar.app/Contents/Info.plist\n```\n","docs/architecture.md":"---\nsummary: \"Architecture overview: modules, entry points, and data flow.\"\nread_when:\n  - Reviewing architecture before feature work\n  - Refactoring app structure, app lifecycle, or module boundaries\n---\n\n# Architecture overview\n\n## Modules\n- `Sources/CodexBarCore`: fetch + parse (Codex RPC, PTY runner, Claude probes, OpenAI web scraping, status polling).\n- `Sources/CodexBar`: state + UI (UsageStore, SettingsStore, StatusItemController, menus, icon rendering).\n- `Sources/CodexBarWidget`: WidgetKit extension wired to the shared snapshot.\n- `Sources/CodexBarCLI`: bundled CLI for `codexbar` usage/status output.\n- `Sources/CodexBarClaudeWatchdog`: helper process for stable Claude CLI PTY sessions.\n- `Sources/CodexBarClaudeWebProbe`: CLI helper to diagnose Claude web fetches.\n\n## Entry points\n- `CodexBarApp`: SwiftUI keepalive + Settings scene.\n- `AppDelegate`: wires status controller, Sparkle updater, notifications.\n- Status items are created on the main actor from `applicationDidFinishLaunching`; see\n  [status-item startup diagnostics](status-item-diagnostics.md) for opt-in lifecycle and Control Center traces.\n\n## Data flow\n- Background refresh → `UsageFetcher`/provider probes → `UsageStore` → menu/icon/widgets.\n- Settings toggles feed `SettingsStore` → `UsageStore` refresh cadence + feature flags.\n- Runtime-only provider settings flow through typed, descriptor-registered sections in `ProviderSettingsSnapshot`.\n- Codex cost conversation titles reuse SQLite database discovery per working directory and SQLite home within each\n  enrichment call. The next call resolves paths again, so config edits and new state database versions are picked up.\n\n## CLI login lifecycle\n- `CodexLoginRunner` and `KiroLoginRunner` resolve their own executable and environment, including Codex home scoping.\n- `CLILoginRunner` owns browser-waiting login processes, bounded output capture, timeout/cancellation, and optional\n  device-flow progress. It returns one shared result type; provider presentations retain their own recovery messages.\n- The login runner and `SubprocessRunner` share `ProcessTermination` and process-tree termination. Cancelling a login\n  stops its child process, joins its progress callback task, and produces no failure alert. Timeouts retain captured\n  diagnostic output, and inherited pipes cannot keep the caller waiting indefinitely.\n- Probe ownership cleanup reads exact environment markers, retaining PID identity checks before signaling. Environment\n  parsing searches NUL byte ranges so large unrelated values do not dominate timeout and cancellation cleanup.\n- Codex and Grok RPC clients share deadline selection through `RPCRequestTimeout`. The deadline wins before teardown\n  can report stdout EOF; each client keeps its protocol initialization, encoding, diagnostics, and error types.\n\n## Concurrency & platform\n- Swift 6 strict concurrency enabled; prefer Sendable state and explicit MainActor hops.\n- macOS 14+ targeting; avoid deprecated APIs when refactoring.\n- Cost stores share one utility serial executor per canonical database location, including symlinked cache roots and\n  database files. Separate database operations do not share SQLite lock waits. Executors are weakly registered and\n  released with their last store or queued job. Normal app fetches still use the separate serial scan queue;\n  WAL transactions are unchanged.\n\n## Shared policy ownership\n- `ProviderCatalog` indexes the immutable generated list of app implementations. JavaScript plugins use their own\n  runtime registry. Provider settings contexts bind directly to their store; the shared cookie picker owns mode\n  conversion, options, and live subtitle selection while providers retain labels, visibility, and cache scopes. Settings\n  link actions resolve their provider-supplied destinations when clicked, including region and account selection.\n  Cookie subtitles use explicit localization keys and arguments supplied by providers; the picker selects the current\n  mode and Keychain-disabled explanation without parsing English display text.\n- Descriptor-registered cookie sections preserve their concrete types after app policy resolves credentials. The app\n  keeps its automatic default, Keychain-disabled manual mode, and selected-account normalization; CLI inference stays\n  separate. Cookie-source writes share config persistence and logging, with provider-specific side effects retained.\n- Core's public descriptor registry keeps one ordered descriptor collection with an index for lookup. Its storage owns\n  synchronization, and replacing a descriptor updates metadata and CLI names without changing provider order.\n- `UsageStore.presentationSnapshot` owns subscription metadata selection. Live cards use that projection once; explicit\n  account cards use only their supplied snapshots, identity, and credits. Compact and stacked Codex account layouts share\n  the same account-card builder. Reordering visible providers preserves loaded config records for unavailable plugins;\n  config decoding retains its existing known-provider rules.\n- `UsageStore.menuCardInput` assembles cards for Settings, the live menu, and explicit account contexts. It owns common\n  quota, pace, warning-marker, and display-preference projection. Settings retains diagnostics and all usage lanes;\n  menus retain their cost display policy and account-scoped forecasts. An account context stays isolated even when empty.\n  Cards consume the reconciled Codex projection; the raw dashboard is not a separate model input.\n- App credential properties read directly from the config snapshot and delegate common string writes to the typed\n  `SettingsStore` config accessor, which owns normalization, persistence, and secret-update logging. Field activation\n  does not trigger credential loading. Legacy provider toggles are read only by the config migrator.\n- Token-cost publications own their snapshots, revisions, and source scope in one store. Raw and current-config readers\n  select from that same state while retaining confirmed-empty and unpublished distinctions. Provider settings and\n  plugin edits reuse Core's config upsert; their defaults and notification policies remain explicit.\n- The native status-item controller owns menu composition. Persistent refresh-row metrics are independent of menu\n  rendering, and screenshot fixtures exercise the active card views. Legacy menu-layout resolution retains its\n  rendering mode and projected layout without copying unused settings into a second state object.\n- `SettingsValue` owns whitespace and wrapping-quote normalization for config and provider settings. Readers retain\n  their credential precedence, endpoint validation, and provider-specific decoding.\n- `ISO8601DateParser` owns fractional-first internet timestamp parsing with a whole-second fallback. Provider readers\n  retain their text extraction, whitespace, numeric timestamp, and custom-format policies; each parse owns its formatter.\n- Kilo's CLI fetch strategy and organization discovery share `KiloBearerTokenResolver` for auth-file loading.\n  Vertex AI credential loading and renewal share display-only ID-token decoding; diagnostic fetch labels use\n  `ProviderDiagnosticFetchAttempt` across the app and CLI.\n- Core owns status feed fetching, decoding, and status models through `ProviderStatusFetcher`; the app supplies\n  localized labels and component UI, and the CLI supplies its existing status payload and English labels.\n- `KeychainStringStore` owns generic-password operations for legacy credential migration. Provider adapters retain\n  their existing item names, prompt kinds, cookie validation, and cache lifetimes. Structured OAuth/cache stores\n  retain their separate persistence and authorization contracts.\n- Simple API-token providers use `ProviderFetchPlan.apiToken`; provider loaders retain endpoint-specific behavior\n  and optional-data rules. Strategies with credential provenance, region selection, or fallback policies stay explicit.\n- `ProviderCostSnapshot.spendLimitWindow` owns spend-budget quota projection. Antigravity's shared family classifier\n  owns ID-before-title precedence; visibility and compact widget selection retain their distinct unknown-family rules.\n- `TestProcessSafety` owns test-runner recognition; callers retain explicit headless and fixture-path policies.\n- `CheckedSum` owns integer overflow handling. Callers still decide whether absent or empty counts mean zero,\n  unavailable, or incomplete; persistent accumulators retain their own overflow state.\n- `BrowserCookieProfiles` merges browser stores within each profile using the existing expiry and store-priority\n  rules. Providers still validate their own sessions. `ExpiringValueCache` supplies their short-lived import caches,\n  with a separate cache instance per provider. `CookiePropertyJSON` preserves the legacy Date/URL marker encoding;\n  session stores retain their own file envelopes, expiration policies, and secure writes.\n- `OpenCodeWebParsing` owns workspace discovery and quota-candidate traversal for OpenCode and OpenCode Go. Each\n  supplies its own numeric/window decoder and required-lane policy. Alibaba Token Plan and Qwen Cloud share\n  `OneConsoleTokenPlanSnapshot` projection while retaining distinct public snapshot types and provider identities.\n- `StreamScanBuffer` supplies bounded overlap for Codex and Claude terminal-marker matching; command handling and\n  session lifecycle remain provider-specific.\n- Codex's persistent and one-shot PTY readers share `CodexStatusMarkers`, including the marker lengths used for\n  bounded overlap. Cursor-query handling remains part of each terminal loop.\n- `TTYCommandRunner` reports the captured POSIX error number and description when PTY allocation fails, so\n  descriptor exhaustion can be distinguished from other terminal allocation failures.\n- `UsageSnapshot.withAccountLabel` applies token-account fallback labels for the app and CLI while preserving the\n  provider's account ID and other identity fields. Codex visible-account labels keep their surface-specific policy.\n- Raw Chromium local-storage consumers share `ChromiumLocalStorageDiscovery`, retaining their own browser lists and\n  origin decoders. Both plugin engines use the manifest's management-auth eligibility policy after adapting their\n  engine-specific option values.\n- Codex RPC and OAuth spend limits use `CodexSpendControlNumber` while keeping their wire-field aliases separate.\n  Reset values retain truncation toward zero, with unrepresentable values omitted instead of trapping.\n- Claude's persistent CLI operations and serialized browser fetches each own an `AsyncOperationGate`; the shared\n  implementation retains FIFO ownership and the CLI cleanup path's explicit cancellation exception. CLI usage and\n  cards validate account/provider selection through `TokenAccountCLISelection` before resolving credentials.\n\nSee also: `docs/providers.md`, `docs/refresh-loop.md`, `docs/ui.md`.\n","docs/claude.md":"---\nsummary: \"Claude provider data sources: OAuth API, web API (cookies), CLI PTY, and local cost usage.\"\nread_when:\n  - Debugging Claude usage/status parsing\n  - Updating Claude OAuth/web endpoints or cookie import\n  - Adjusting Claude CLI PTY automation\n  - Reviewing local cost usage scanning\n---\n\n# Claude provider\n\nEnable **Settings → Menu Bar → Show all accounts in Overview** to include this provider’s cached account\ncards in Overview. The existing account source and provider selection remain unchanged; see\n[Overview account rows](configuration.md#menu-bar-controls).\n\nThe **Plan Usage** submenu includes recorded remaining-quota burndown above utilization history,\nusing the same Session, Weekly, and Sonnet labels. See [recorded quota burndown](widgets/burndown-proof.md)\nfor capture-age semantics and the existing history retention/privacy behavior.\n\nOAuth history uses a stable account/profile identity after two stable credential observations corroborate that\nbinding. External token rotations therefore continue the same history after the new credential is corroborated.\nSaved token-scoped fragments with matching verified bindings migrate into that account's history on a successful\nsample. Unbound fragments, obsolete bindings, and other accounts stay separate; correcting a conflicting binding\npreserves its ambiguous old fragment without attributing it to the newly confirmed account. If that fragment was\nalready merged, the affected account bucket is preserved but hidden, and fresh samples start a new account scope.\nThis quarantine survives empty responses and restarts. Explicit OAuth tokens without Claude Code account evidence\nremain credential-scoped.\n\nClaude supports three usage data paths plus local cost usage. The main provider pipeline uses runtime-specific\nautomatic selection, but the codebase still has multiple active Claude `.auto` decision sites while the refactor is\npending. For the exact current-state parity contract, see\n[docs/refactor/claude-current-baseline.md](refactor/claude-current-baseline.md).\n\nWhen an Anthropic Admin API key is configured, Claude can also show organization-level spend/messages/tokens in the\nsame inline dashboard pattern used by the OpenAI API provider.\n\nIncomplete proxy records with an explicit null `stop_reason`, positive input, zero output, and no cache counters are excluded from token and cost totals until a completed record arrives. Their day and model stay visible with an **Incomplete** marker and an excluded-request count. Known usage remains a partial subtotal; an incomplete-only period stays unavailable rather than becoming zero. Older caches are rebuilt once to apply this distinction. Missing `stop_reason` alone retains compatibility with older complete logs.\n\nCompleted requests for models without a known price keep their token usage and report unpriced coverage. Usage & Spend retains the priced subtotal from the other requests and days as a lower bound; unknown prices do not become zero. Fully priced history keeps its usual estimated totals.\n\n## Data sources + selection order\n\n### Default selection (debug menu disabled)\n- If an Admin API key is configured, the Admin API strategy is used for Claude API spend/usage.\n- App runtime main pipeline: OAuth API → CLI PTY → Web API.\n- CLI runtime main pipeline: Web API → CLI PTY (Linux: Web API → OAuth API → CLI PTY).\n  Since 0.78.0, Auto `usage` first uses the active slot when the claude-swap integration is enabled;\n  explicit source/account selection bypasses it. See [CLI precedence](cli.md#command).\n- Explicit picker modes (OAuth/Web/CLI) bypass automatic fallback.\n- Explicit OAuth retains the last successful quota measurement through recognized temporary network failures,\n  including localized DNS/offline errors. Its original timestamp remains visible; authentication rejection still\n  follows the existing invalidation and credential-owner rules.\n- A lower-level direct Claude fetcher still contains a separate `.auto` order. That inconsistency is tracked in\n  [docs/refactor/claude-current-baseline.md](refactor/claude-current-baseline.md).\n\nUsage source picker:\n- Preferences → Providers → Claude → Usage source (Auto/OAuth/Web/CLI).\n\nWhen an account-widget identity check or successful web enrichment returns an OAuth profile, its same-token email is\nretained for display even if the profile lacks the organization needed to verify widget ownership. A failed\noptional profile check leaves successful usage available. Ordinary OAuth refreshes also start one background\nprofile lookup per credential, bounded to two seconds, without waiting for it to publish quota usage. The first\nrefresh may omit the email while that request is in flight; later refreshes reuse its result, including a failed\nlookup, until the token changes or CodexBar restarts. This process-local display cache cannot establish widget\nownership. Organization UUIDs are ownership evidence, not organization display names.\n\nAdmin API key setup:\n- Preferences → Providers → Claude → Admin API key, stored in `~/.codexbar/config.json`.\n- CLI/env: `printf '%s' \"$ANTHROPIC_ADMIN_KEY\" | codexbar config set-api-key --provider claude --stdin`.\n- Token accounts can also hold `sk-ant-admin...` keys; they route to the Admin API instead of cookie/OAuth usage.\n- Environment fallback: `ANTHROPIC_ADMIN_KEY`.\n\n## Usage Dashboard\n\nThe default and API-account Usage Dashboard action opens [Platform usage](https://platform.claude.com/usage). Accounts recognized as Claude subscriptions open [Settings → Usage](https://claude.ai/settings/usage). This action does not change the configured usage source.\n\n## Admin API\n- Key prefix: `sk-ant-admin...`.\n- Endpoints:\n  - `/v1/organizations/cost_report`\n  - `/v1/organizations/usage_report/messages`\n- Output:\n  - Today/7d/30d spend and message/token summaries.\n  - Inline 30-day dashboard chart when daily buckets are present.\n  - Identity login method: `Admin API`.\n\n### Optional workspace spend\n\nEnable **Show workspace spend** in Settings → Providers → Claude, set `claudeWorkspaceSpendEnabled: true` on the\nClaude provider config entry, or set `ANTHROPIC_ADMIN_WORKSPACE_SPEND=true`. It is off by default and applies only\nto the Admin API source.\n\nThe existing [cost report](https://platform.claude.com/docs/en/api/admin/cost_report/retrieve) request adds\n`group_by[]=workspace_id` alongside `group_by[]=description`; no extra request or credentials are required.\nThe organization totals, cost items, token summaries, and daily chart remain unchanged. When more than one workspace\nhas cost rows, **Workspace spend · 30d** shows up to 20 workspaces, highest spend first, over the same 30-day buckets\nas the organization total. Labels use workspace IDs; a null workspace is **Default**. Amounts are converted from\nAnthropic's USD cents to dollars. A single workspace keeps the existing organization view.\n\n## Recover usage when Claude is already signed in\n\nA working Claude Code login or Claude browser tab does not by itself confirm that CodexBar can read that\nsession. For example, OAuth can report missing credentials, the CLI probe can time out, and Web can report\n`No Claude session key found in browser cookies.` on the same machine. Diagnose the selected source before\nsigning out or replacing credentials.\n\nIf Claude works in Chrome but CodexBar cannot import its browser session:\n\n1. Open `https://claude.ai` in Chrome and confirm the intended account is signed in.\n2. In Settings → Providers → Claude, select **Web API (cookies)** and leave the cookie source on **Auto**.\n   This uses the browser session for session, weekly, and available model-specific quotas.\n3. In Settings → Advanced, confirm **Disable Keychain access** is off. Chromium cookie decryption needs\n   the browser's Safe Storage Keychain item; Claude's OAuth prompt policy controls a different credential.\n4. Explicitly retry the import from Terminal:\n\n   ```bash\n   codexbar cookie refresh --provider claude --allow-keychain-prompt\n   ```\n\n   Approve the expected macOS Keychain prompt for the installed CodexBarCLI and the browser's Safe Storage\n   item. The command never prints cookie values. A prior denial can suppress imports for six hours;\n   this explicit retry can bypass that cooldown, while ordinary refresh attempts may remain suppressed.\n   See [Keychain prompts](keychain-prompts.md) for permission details.\n5. Click **Refresh** in Claude's settings and confirm that **Updated just now** appears with current quota\n   bars and no fetch error. Local cost totals or last-known quota bars alone do not prove a successful refresh.\n\nThis recovers browser-session access; it does not repair missing OAuth credentials. If the refreshed session\ninstead reports a Cloudflare challenge, follow the network guidance under Web API below rather than repeating\nthe cookie import.\n\n## Passive CLI sessions and macOS sleep\n\nAfter each passive request in the reusable CLI session, and when that session closes, CodexBar sends SIGTERM\nonly to `/usr/bin/caffeinate` processes in the session's own process group. The session remains available for\nlater requests. There is no recurring process scan; other process groups and other executables are left alone.\nThis cleanup applies to the reusable PTY session, not the separate one-shot usage or auth-status commands.\n\n## Keychain prompt policy (Claude OAuth)\n\nIf CodexBar reports that its Claude OAuth cache is temporarily unavailable in macOS Keychain, check\nthat the login keychain is unlocked in Keychain Access, then retry Refresh. This guidance does not\nchange the existing prompt policy or switch to another credential source.\n\n- Preferences → Providers → Claude → Keychain prompt policy.\n- Options:\n  - `Never prompt`: never attempts interactive Claude OAuth Keychain prompts.\n  - `Only on user action` (default): interactive prompts are reserved for user-initiated repair flows.\n  - `Always allow prompts`: allows interactive prompts in both user and background flows.\n- Explicit Refresh can repair Claude OAuth Keychain access when direct-read consent is enabled and the policy\n  allows user prompts. Ordinary OAuth fetches remain noninteractive, including with `Always allow prompts`;\n  that policy still governs the existing delegated refresh and experimental reader paths.\n- After a missing-credentials failure, a newer Claude Code credential timestamp replaces the stale error with\n  **Claude Code credentials changed** and a **Refresh** instruction in the menu and OAuth CLI output. Click the\n  existing menu **Refresh** action to retry; direct-read consent and the prompt policy still control access.\n  The freshness probe reads only Keychain attributes with UI disabled and the credentials file's modification\n  time. It never requests the Keychain credential payload, launches Claude, or grants access in the background.\n  A changed timestamp can mean a login or token rotation; it does not prove the credential is valid. Custom\n  credential profiles use their own file timestamp, never the global Claude Code Keychain item. CodexBar saves\n  only the last failure time per profile so the guidance survives restarts and is shared with the bundled CLI;\n  a readable credential clears it. Auto continues to configured CLI/Web sources under their existing access gates.\n- This setting only affects Claude OAuth Keychain prompting behavior; it does not switch your Claude usage source.\n- The policy also applies to the experimental `/usr/bin/security` reader and delegated OAuth refresh through\n  `claude`: background operations that can prompt require `Always allow prompts`.\n- CodexBar's `Always allow prompts` permits future prompts; macOS's **Always Allow** grants access to the current\n  Keychain item. Claude Code can recreate `Claude Code-credentials` and reset that grant. An ACL entry still named\n  CodexBar does not prove that its stored code-signing requirement matches the running binary. `Only on user action`\n  reduces background interruptions but may require a manual Refresh to recover OAuth access. In #3798, a\n  before/after trace shows Claude Code preserving the decrypt ACL's CodexBar entry but removing CodexBar's Team ID\n  from the separate partition ACL. Decrypt-ACL preflight alone cannot establish partition authorization; repeated\n  manual grants therefore need not survive the next Claude Code refresh.\n- If Preferences → Advanced → Disable Keychain access is enabled, this policy remains visible but inactive until\n  Keychain access is re-enabled.\n\n### Debug selection (debug menu enabled)\n- The Debug pane can force OAuth / Web / CLI.\n- Web extras are internal-only (not exposed in the Providers pane).\n- CLI Web enrichment requires matching nonempty account emails; an organization display name alone cannot authorize\n  merging another session's optional usage or spend. OAuth enrichment retains verified organization-UUID matching.\n\n## OAuth API (preferred)\n- OAuth refresh form-encodes credential values, preserving literal plus signs and other reserved characters.\n- Expiry values outside the diagnostic integer range are reported as `out_of_range` without changing credential expiry or refresh decisions.\n- Credentials:\n  - Explicit OAuth environment override, when configured.\n  - CodexBar OAuth cache when available.\n  - File fallback: `~/.claude/.credentials.json`.\n  - Claude CLI Keychain bootstrap/repair fallback: `Claude Code-credentials`.\n- When a CodexBar-owned OAuth cache item's ACL rejects the current build, fresh credentials from an allowed source\n  can replace that cache item using no-UI deletion and creation. A locked or inconclusive Keychain is preserved;\n  failed ACL repairs back off for five minutes. This never deletes or recreates Claude Code's credential item.\n- If CodexBar's cache is temporarily unavailable, automatic refreshes can reuse an unexpired credential already in\n  memory beyond the normal 30-minute cache window, ahead of a stale credentials file. Each refresh retries the\n  persistent cache. Token expiry, profile changes, cache invalidation, and Never prompt still prevent reuse;\n  after a rejected cache write, the next refresh first clears the stale persistent entry, then reuses and persists\n  an unexpired in-memory credential even after 30 minutes once that cleanup succeeds. Extended reuse requires\n  evidence of that exact failed write and its original consent; an unrelated invalidation cannot authorize it.\n  Rejected writes during CodexBar-owned token refresh retain the same recovery, bound to the refreshed credential;\n  a delayed older write cannot replace a newer credential's recovery.\n  This does not discover an external login or enable additional background reads of Claude Code's Keychain item.\n- For the default CLI profile, expired cached or file credentials can adopt a fresh CLI Keychain token after file fallback, even when its fingerprint was already observed during an earlier repair. Existing direct-read consent, prompt policy, cooldown, one-minute freshness-check throttle, and noninteractive-read checks still apply. Custom profiles are not recovered from the unscoped global item, and CLI credentials are never rewritten by this synchronization. Background recovery still requires the Always allow prompts policy; the default Only on user action policy requires an explicit Refresh.\n- Credential selection does not rank unrelated sources by the largest `expiresAt`: expiry establishes validity,\n  not account identity or issuance order. A valid profile file remains ahead of Keychain bootstrap. Keychain candidates\n  are ordered by modification date (creation date as fallback); freshness sync reads only that newest item and never\n  rewrites Claude Code's credentials file. An expired default-profile record can be replaced even when the stored\n  Keychain fingerprint already matches, subject to the access gates above.\n- On Claude Code 2.1.x, `Claude Code-credentials` may contain only MCP server OAuth state (`mcpOAuth`) with no `claudeAiOauth`. CodexBar treats that as an OAuth configuration error, does not run background delegated `claude /status` refresh, and surfaces re-auth guidance. Use Web or CLI usage source, or restore a valid Claude OAuth keychain entry. See #1844.\n- Requires `user:profile` scope (CLI tokens with only `user:inference` cannot call usage).\n- Missing-scope errors require a Claude Code sign-in token with usage access. `claude setup-token` produces a token for model requests and is not a usage-scope recovery step ([Claude Code authentication](https://code.claude.com/docs/en/authentication#generate-a-long-lived-token)). Remove any configured OAuth token override before switching Claude Source to Web/CLI.\n- Endpoints:\n  - `GET https://api.anthropic.com/api/oauth/usage?cedar_ember=1` → usage and saved reset inventory.\n    HTTP 400/403 retries once without the optional query and with the legacy request identity; spending is never skipped.\n    Missing-profile-scope errors, 401, and 429 keep their normal handling without that retry.\n  - `GET https://api.anthropic.com/api/oauth/profile` → account identity used to verify that optional Web enrichment\n    belongs to the same Claude account.\n- Headers:\n  - `Authorization: Bearer <access_token>`\n  - `anthropic-beta: oauth-2025-04-20`\n  - Reset inventory uses `claude-cli/<detected-version> (external, cli)`; eligibility remains server-controlled.\n- Mapping:\n  - `five_hour` → session window.\n  - `seven_day` → weekly window; also becomes the primary fallback when `five_hour` is absent or has no utilization.\n  - `seven_day_sonnet` / `seven_day_opus` → model-specific weekly window.\n  - `limits[].weekly_scoped` → model-specific weekly windows; generic `All models` scopes stay in the main weekly row.\n    OAuth and Web share per-entry parsing: malformed siblings or optional fields do not hide valid model rows. Boolean and string percentages are ignored.\n  - The menu localizes scoped titles as a model name plus weekly duration; canonical snapshot and CLI titles remain unchanged.\n  - Automatic and Session + Weekly menu bar metrics fall back to the most constrained known scoped weekly window when the regular quota windows are missing. Unknown scoped measurements remain unavailable; Extra usage stays a spend-only fallback.\n  - `seven_day_routines` / `seven_day_cowork` → Daily Routines extra window.\n  - Claude Design/Omelette keys are ignored because Claude Design shares the main Claude usage limit.\n  - `extra_usage` → Extra usage cost (monthly spend/limit).\n- Preferences → Providers → Claude → Visible usage items lets you hide the Daily Routines row in menus, the Settings\n  preview, and Overview. The global optional credits and extra usage setting remains its master switch. Hiding this\n  row does not change fetching, history, notifications, widgets, model-scoped weekly limits, hooks, or CLI output.\n- Preferences → Providers → Claude → Show model-specific weekly usage in widgets controls model-scoped weekly quota\n  rows in desktop widgets. It is off by default; turning it on displays every known Claude window with a\n  `claude-weekly-scoped-` identifier (for example, Fable). Turning it back off also drops scoped rows that a previous\n  snapshot persisted. It does not change fetching, the menu, history, notifications, hooks, or CLI output.\n- Refreshing credentials for the same identified account preserves quota-threshold warning history. Verified\n  credential-owner/account bindings also preserve threshold history when active-account metadata temporarily\n  disappears or OAuth falls back to CLI. Threshold warnings re-arm after quota recovers above a threshold and fire\n  on a later downward crossing. Predictive warnings and quota-low hooks keep their existing source-scoped histories;\n  their baselines are not merged with threshold notification state. OAuth/CLI samples without a warning owner use\n  one stable unresolved-account scope, so credential rewrites preserve threshold crossings and predictive warnings\n  remain available. When a stable account identity or verified owner binding becomes available, its threshold scope\n  adopts the newest unresolved history. Later identity gaps reuse the last known account when the reset timestamp\n  is unchanged and remaining quota has not increased. Without that continuity, samples use the stable unresolved\n  scope; once its history has joined an account, subsequent gaps preserve already-fired thresholds rather than\n  starting a new warning episode on every refresh. The first independent unresolved sample can still issue an\n  initial warning. Unverified credential owners remain independent; changing such an owner can still\n  produce an initial warning because account continuity cannot be established.\n- Successful OAuth login enables Claude and preserves the selected usage source. With the default Auto source, OAuth\n  remains preferred when readable, while CLI/Web fallback stays available when OAuth credentials are not usable.\n- Claude Code periodically rotates its `Claude Code-credentials` Keychain item and can replace the ACL grant that\n  allowed CodexBar to read it. Auto treats that as a failed OAuth source, reuses a recent successful CLI result or\n  continues to CLI/Web, and does not misreport the existing credentials as missing. A manual Refresh can re-grant\n  Keychain access; selecting CLI or Web avoids the foreign-Keychain dependency.\n- When every live Auto source fails, CodexBar keeps the last captured session/weekly percentages from\n  `history/claude.json` visible as stale data and shows their capture age instead of blanking the quota bars.\n  Restored history and CLI-scraped percentages both show “Limited usage detail”: the warning describes reduced\n  fidelity, not the source of a historical capture. It does not change sign-in or refresh recovery actions.\n- Plan inference: `subscriptionType` is preferred when present; `rate_limit_tier` falls back to\n  Max/Pro/Team/Enterprise. When a Max `rate_limit_tier` carries a usage multiplier\n  (`default_claude_max_5x` / `default_claude_max_20x`), it is surfaced in the label as \"Max 5x\" / \"Max 20x\".\n\n## Web API (cookies)\n\n### Claude desktop app cookies (opt-in, macOS)\n\nChoose **Usage source → Web** and **Claude cookies → Automatic**, then enable **Use Claude desktop app cookies**.\nSign in to Claude.app first. This option is off by default. The equivalent provider config field is `\"claudeDesktopCookiesEnabled\": true`,\nalso honored by CLI usage. The desktop app source reads `~/Library/Application Support/Claude/Cookies` (or\n`Network/Cookies`) through SweetCookieKit, using `Claude Safe Storage` / `Claude Key` for encrypted values.\n\nDesktop cookie imports are exclusive: a missing or unreadable desktop session never falls back to a browser account.\nExplicitly selected token accounts keep their own credentials; this option applies to automatic-cookie fetches.\nUsage source **Auto** keeps its existing runtime-specific source order; choose **Web** to follow only the desktop session.\nCookies are reimported on each refresh so account switches made in Claude.app are observed; desktop sessions\ndo not read or write the browser cookie cache. The normal shared access gate still forbids prompts from background/settings\nwork, checks matching cookie hosts before Safe Storage access, permits at most one interactive read per manual\naction, and honors denial cooldowns. OAuth and Claude Code login sources are unchanged.\n\n- Optional subscription dates come from `GET /api/organizations/{org_id}/subscription_details` on `claude.ai`,\n  using an existing manual or cached session cookie. They do not come from the quota response or OAuth expiry.\n  `next_charge_at` / `next_charge_date` supply renewal; `plan_ending_at` / `plan_ending_before` supply paid-access\n  expiration and take precedence over renewal. Calendar-only dates stay calendar-only when displayed.\n- The menu and Settings preview reuse the shared subscription row. Missing, unavailable, or unrecognized billing\n  data does not invent a date or fail usage. CLI JSON exposes `subscriptionRenewsAt` / `subscriptionExpiresAt`\n  and a corresponding `...IsDateOnly: true` when the server only supplies a calendar date.\n- OAuth enrichment additionally verifies the OAuth profile's account and organization against the cookie session\n  before and after billing. OAuth credentials alone cannot fetch these dates. No cookie discovery, credential\n  repair, sign-in, or new Keychain access is performed for billing; cookie source Off disables it.\n- App and CLI use the same provider fetch and normal account-scoped publication. Billing has a separate two-second\n  total budget after quota succeeds; expiry, errors, or a changed verified owner leave successful quota intact.\n  No delayed billing task writes back into an already published snapshot, and dates are not carried across refreshes.\n- Availability is determined by the authenticated billing response, not the Pro/Max/Team/Enterprise plan label.\n  The fixtures cover the reported subscription schema, including cancellation and absent dates; they do not\n  establish that every plan or organization role can access this endpoint. Team/Enterprise billing access and\n  live cancelled subscriptions remain unverified. Quota resets and Extra usage balances are independent.\n- Session quota warnings ignore a weekly quota promoted into the primary field when the five-hour payload is missing. Existing session warning history stays tied to its account, and weekly warnings continue independently.\n- Preferences → Providers → Claude → Cookie source (Automatic or Manual).\n- Manual mode accepts a `Cookie:` header from a claude.ai request.\nEach Claude token account can optionally select a **Claude config directory** in Settings → Providers → Claude.\nWith Track costs enabled, the active account’s local token and API-price cost estimates scan only its `projects` history,\nwithout default, claude-swap, or Pi history. Selecting a directory declares that its history belongs to the account;\nCodexBar does not infer ownership or detect mixed/copied history beyond existing scanner deduplication. A missing or\nunreadable history directory leaves its estimate unavailable. Clear the mapping to restore the existing combined\nview; unmapped accounts and claude-swap retain their current behavior. The optional `claudeConfigDirectory` field\nis stored on the token account in the config file and does not change quota fetching or credential resolution.\n\n- Multi-account manual tokens: add entries to `~/.codexbar/config.json` (`tokenAccounts`) and set Claude cookies to\n  Manual. The menu can show all accounts stacked or a switcher bar (Preferences → Advanced → Display).\n- In Settings → Providers → Claude, **Edit** changes a saved account’s label and credential in place.\n  The credential field is masked and starts empty; leave it blank to keep the saved credential, or enter a\n  replacement and choose **Save**. **Cancel** discards the draft. Up/down controls persist account order in\n  the existing config file and menu switcher. Edits and moves preserve the selected account and account ID,\n  including its Claude config directory. Other providers with manual token accounts share the editor; editing\n  a dormant account does not select it.\n- Claude token accounts accept either `sessionKey` cookies or OAuth access tokens (`sk-ant-oat...`). OAuth-token\n  accounts route to the OAuth path and disable cookie mode; session-key or cookie-header accounts stay in manual\n  cookie mode. The exact edge-routing rules are documented in\n  [docs/refactor/claude-current-baseline.md](refactor/claude-current-baseline.md).\n- Cookie source order:\n  1) Safari: `~/Library/Cookies/Cookies.binarycookies`\n  2) Chrome/Chromium forks: `~/Library/Application Support/Google/Chrome/*/Cookies`\n  3) Firefox: `~/Library/Application Support/Firefox/Profiles/*/cookies.sqlite`\n- Domain: `claude.ai`.\n- Linux: the web source supports an explicitly configured manual `sessionKey` cookie. Automatic browser import,\n  including Firefox import, remains unavailable. This enables the same API request path as macOS; it does not\n  bypass Cloudflare challenges, refresh Claude Code OAuth credentials, or guarantee that a browser session will\n  work outside the browser. Use the OAuth source when the web request is challenged.\n- Linux CLI Auto mode: an existing valid manual cookie makes Web eligible ahead of CLI after upgrading.\n  Web success returns without launching Claude Code; authentication rejection or a Cloudflare challenge falls\n  back to an available CLI using the existing Auto policy. Cancellation stops without launching the CLI.\n  Explicit Web mode does not fall back. Use explicit OAuth mode for passive polling that must not launch the CLI.\n- Linux CLI Auto mode: the plan is Web → OAuth → CLI PTY. OAuth runs when Claude Code OAuth credentials\n  (`~/.claude/.credentials.json`) are usable without a prompt, so a logged-in user does not launch an interactive\n  `claude` session. Without usable OAuth credentials, the OAuth step is skipped and CLI remains the fallback.\n  OAuth request failures such as 401 or 429 also fall back to the CLI; cancellation stops without launching it.\n- Cookie name required:\n  - `sessionKey` (value prefix `sk-ant-...`).\n- Cached cookies: Keychain cache `com.steipete.codexbar.cache` (account `cookie.claude`, source + timestamp).\n  Reused before re-importing from browsers.\n- After replacing an expired cached cookie, failures from the recovered session retain their actual error type,\n  including temporary network failures, server outages, Cloudflare challenges, and cancellation. The original\n  sign-in error is retained only when browser recovery itself fails to find a usable session.\n- API calls (all include `Cookie: sessionKey=<value>`):\n  - `GET https://claude.ai/api/organizations` → org UUID.\n  - `GET https://claude.ai/api/organizations/{orgId}/usage?cedar_ember=1` → session/weekly/opus, plus limit-reset\n    grants in the `cedar_ember` block. Rejected requests, including ordinary 403 responses, retry once without\n    `cedar_ember=1`, so unsupported reset queries keep the usage windows.\n    Success, 401, 429, and recognized Cloudflare challenges retain their normal handling without a retry.\n  - `GET https://claude.ai/api/organizations/{orgId}/overage_spend_limit` → Extra usage spend/limit.\n  - `GET https://claude.ai/api/organizations/{orgId}/prepaid/credits` → remaining Usage credits balance.\n  - `GET https://claude.ai/api/account` → email + plan hints.\n- Outputs:\n  - Session + weekly + model-specific percent used.\n  - A missing Web session stays absent instead of becoming a synthetic 0% session; measured weekly and extra windows stay visible. Older cached placeholders remain readable.\n  - Daily Routines extra window when returned by the usage API.\n  - Extra usage spend/limit (if enabled). Compact Overview keeps this section when no measured quota bars exist, including Enterprise accounts with unavailable limits.\n  - Remaining Usage credits balance (if enabled). Custom menu bar layouts can show it with the **Balance** element\n    beside the quota windows; the default menu bar text keeps showing quota. Turning off **Show credits & extra\n    usage** hides it from the menu card and the Balance element immediately. Otherwise a fresh snapshot without a\n    successful prepaid credits response has no balance, while a failed refresh keeps the last snapshot and its balance.\n  - Account email + inferred plan.\n  - Limit Reset Credits (see below).\n- A Cloudflare challenge on `claude.ai` is a network-path restriction, not a stale-cookie signal. CodexBar keeps the\n  cached cookie and prior quota snapshot, identifies the challenge, and links to Settings. Select OAuth for live\n  quota windows on that network (the web-only Usage credits balance is unavailable), or try a different network.\n  Explicit Web mode remains terminal and never reads OAuth credentials as a fallback.\n- Limit Reset Credits (\"Reset for free\" in Claude Settings > Usage), Web and OAuth sources:\n  - These are saved resets a user can redeem, separate from the session and weekly reset timestamps already\n    supplied by Web, OAuth, and CLI. Existing cookie settings and source selection govern all Web access; this\n    feature does not enable cookies, broaden browser discovery, or initiate Web enrichment.\n  - Read from `cedar_ember` in the same usage response as the session and weekly windows, so they share its session\n    and organization. Observed on a personal Pro/Max account; Team and Enterprise organizations are not verified.\n  - The count sums `resets_left` over grants that, at refresh time, are not paused, have started, and have not\n    expired. An expiry that passes before the next refresh drops that reset from the display. `usable_now` is not\n    consulted, so a saved reset still counts while Claude gates its use.\n  - Requires `eligible: true`. A grant with an unreadable `resets_left`, `resets_total`, `paused`, `starts_at`, or\n    `ends_at` is dropped. More than 50 available resets or more than 200 grant records show nothing. The usage\n    windows are unaffected either way.\n  - Menu: a live `Limit Reset Credits` section with its own visibility choice. An upgrade preserves a previously\n    disabled global optional-usage preference by initially hiding this section; it can then be enabled independently.\n    CLI and `codexbar serve`: a `Limit Reset Credits` row in `usage.details` (`N available`, next expiry).\n  - Live-only: grant IDs are never decoded, the usage request skips the URL cache, and cached or synced snapshots do\n    not restore the inventory. A reset used on claude.ai disappears at the next successful refresh.\n  - Source precedence stays unchanged: credits appear only from the source supplying the primary usage snapshot. CLI\n    does not report saved reset credits, and optional Web enrichment never adds Web credits to another source,\n    even when the account matches. The menu replaces the generic details row with one shared reset-credit section.\n    CodexBar never redeems a reset; use Claude on the web or Claude Desktop.\n\nEnterprise spend details depend on the selected source. Auto stops at the first successful OAuth/CLI/Web result;\nit does not import a missing monthly spend cap from a different session. Select **Web API (cookies)** for\nbrowser-only billing details, and enable **Show credits and extra usage** to display them.\n\n## Extra usage rollover\n\nFor Claude spend-only accounts, positive monthly caps use the shared usage-window presentation: one Meter bar,\nwith Auto, Extra usage, and the card using the same cap percentage and reset. The card retains its spend/limit and\nprepaid-balance caption. Accounts with quota windows keep their separate Extra usage section, including in the\nmenu-card-sections layout. Uncapped balances keep their compact Credits row; Admin API spend is unchanged.\nExtra usage still follows its existing visibility choice and the global optional-usage switch. Pace follows the\nexisting pace setting, independently of redeemable Limit Reset Credits.\n\nClaude does not report the cap reset. CodexBar infers midnight UTC on the first of the next month from the cost\nobservation, applying [Anthropic's documented spend-limit period](https://platform.claude.com/docs/en/manage-claude/spend-limits-api)\nto all Claude monthly caps. Reset text uses local time (November 1 UTC can appear as October 31 in the Americas);\nthe metric tooltip identifies the inference. Shared calendar-month pace uses the actual 28–31-day month.\nChanging a cap changes the percentage, not its rollover. A stale observation does not advance into a new month.\n\n## Cloud-session credits\n\n- OAuth and Web usage responses can supply promotional cloud-session credit in `iguana_necktie`.\n  CodexBar shows a separate **Cloud credits** balance row in the menu and a detail section in the CLI when\n  optional credits/extra usage is enabled. The menu row matches the prepaid **Credits** row and shows only the\n  remaining balance; the allowance, progress, and expiry are available in CLI output and the widget snapshot's\n  optional `details` field. CLI JSON exposes the section through `usage.details`, including numeric progress and\n  remaining dollars. No additional request, login, or browser\n  discovery is needed for these credits.\n- `limit_dollars`, `used_dollars`, and `remaining_dollars` are already USD amounts. They are never divided by 100,\n  added to prepaid Extra usage, counted as local spending, or used for quota pacing. The reported remaining amount\n  wins when present; otherwise it is derived from the allowance and reported used dollars.\n- `resets_at` denotes expiration, not a recurring quota reset. The section shows an absolute UTC timestamp\n  (`Z`); cached details retain the last observation and its expiry. Exhausted credits retain a zero balance;\n  credits already expired at observation or locked by the provider are labeled expired/unavailable. The menu\n  also labels a cached balance expired once its stored expiry has passed.\n- Missing or malformed credit blocks omit this section without failing ordinary usage. The response determines\n  availability, without a Pro/Max plan-name gate. CLI-probe-only results do not include cloud credits, and optional\n  Web enrichment preserves the primary source's credits rather than importing Web credits into OAuth/CLI results.\n- Settings → Providers → Claude → Visible usage items can hide **Cloud credits** independently. The optional\n  credits/extra usage setting remains its master switch; the individual visibility choice does not change CLI output.\n- The response shape is based on [Pane's implementation and live-shape test](https://github.com/ItsJazii/pane/blob/beb4bbfd4e7c776d970a56d254e8cce4d61154d9/src-tauri/src/providers/claude.rs#L443).\n  CodexBar's fixtures validate parsing and presentation; they are not independent live Pro/Max verification.\n\n## claude-swap accounts (opt-in)\n\nThe accepted multi-account design in\n[claude-multi-account-and-status-items.md](claude-multi-account-and-status-items.md).\n\n- Setup: Preferences → Providers → Claude → \"Read accounts from claude-swap\", then set the path to the\n  [`cswap`](https://github.com/realiti4/claude-swap) executable (for example `~/.local/bin/cswap`) in the field\n  directly beneath the enabled toggle. The path field and its help are hidden while the integration is off.\n- tcr source: [tcr](https://github.com/dhkts1/teamclaude-rs) prints the same list schema v1 from `tcr list --json`.\n  Pick \"tcr\" in the Source picker shown while the integration is on (config key `claudeSwapSource: \"tcr\"`) and set\n  the path to the `tcr` executable. CodexBar then runs `tcr list --json` and `tcr --version` with the same bounds,\n  parser and isolation rules; cards are labelled `tcr` and identity is `tcr:<slot>`. tcr pools its accounts and\n  routes each request itself, so its list reports no active account and `supportsAccountSwitching: false`: no row\n  offers Switch or Re-authenticate, and CodexBar never sends tcr a switch command. Late list results from a\n  previously selected source are discarded before publishing cards, refresh times, or forecast history, including\n  when the initial history load is still pending.\n- tcr forecast history requires both a nonempty email and `organizationUuid` in the list row. When both are\n  supplied, it uses the same normalized email/organization hash as claude-swap; display names and slot numbers\n  never substitute for an organization UUID. The current tcr contract fixture omits `organizationUuid`, so those\n  accounts show usage without a history-based forecast.\n- Version detection retries after a failed or cancelled startup probe; replaced refreshes cannot overwrite a newer\n  result, and disabling the adapter or changing its executable clears the previous detected version.\n- Behavior: on each Claude refresh, CodexBar runs `cswap --list --json` independently of the ambient Claude fetch (no\n  shell, fixed arguments, bounded runtime and output), requires `schemaVersion == 1`, and parses only slot number,\n  active state, usage status, email, `organizationUuid`, display-only `organizationName` (always present, may be empty),\n  optional display-only `alias` when non-empty, the 5-hour/7-day windows, and optional display-only model-scoped\n  weekly windows from `usage.scoped`, optional `usage.spend`, and source measurement times. Identity stays\n  `claude-swap:<slot>`; organization name and alias are never\n  used as identity. When two or more slots share an email, cards append ` · organizationName` or ` · Account N`;\n  a user-chosen cswap alias replaces that label. Unique emails stay email-only.\n- Menu and terminal cards can show the source's `lastGoodUsage` when live usage is unavailable. Its required\n  `lastGoodFetchedAt` remains the measurement time, and the card shows a last-known marker and capture age alongside\n  the diagnostic. Terminal cards also show the capture timestamp. Malformed additive spend or last-good fields do not\n  discard valid live quota windows. The free-form row `message` is not parsed.\n  Brief terminal output keeps the diagnostic and excludes historical metrics from its warnings and next-reset summary.\n- Last-known usage keeps its provenance through quota retention and cache reload. It never drives the menu-bar icon,\n  whose compact display cannot show its age. Compact account rows show the capture age and do not recommend or fold\n  last-known rows into the ready-account group. Explicit source measurements take precedence over locally retained\n  quota windows.\n- Slots excluded from claude-swap's automatic rotation are marked `(disabled)` but remain valid explicit switch\n  targets. The active account label is emphasized.\n- Display: when claude-swap reports more than one account, its accounts replace ambient/token-account Claude cards.\n  The app honors **Menu → Multi-account layout**: Segmented shows account buttons and one active account card;\n  pending or failed switches show the requested account's details while the active marker stays source-owned.\n  Expired or otherwise unavailable accounts remain inspectable without activation; selecting the active account\n  returns to its card. If the adapter reports no active account, the menu says so instead of selecting the first row.\n  Buttons wrap into two rows above three accounts. Hide Personal Info uses stable `Account N` slot labels across\n  segmented buttons, native account cards, compact menu rows, and accessibility text, including unavailable accounts.\n  These numbers come from validated claude-swap slots and remain stable when accounts are reordered; aliases,\n  organization names, and email addresses stay hidden.\n  Stacked shows one card per account (active account first, then numeric slot). With four or more\n  accounts the stacked menu switches to a compact layout (`AccountMenuLayoutPlanner`): the active account keeps its full\n  card, inactive accounts become one-line rows sorted by remaining headroom (most constrained first, red/amber below\n  50%/10% left, a star on the healthiest activatable account), and healthy rows fold behind a \"N more accounts ready\"\n  summary row. Clicking a compact row expands that account's full card; clicking an inactive card collapses it again.\n  This choice is remembered locally across menu opens and app restarts. The summary row reveals the hidden rows for\n  the current menu session. `codexbar cards` keeps the full per-account output. The same compact layout applies to\n  every stacked multi-account list (token accounts on any provider, and flat Codex account lists; workspace-grouped\n  Codex lists keep their sectioned stacked layout). To use this\n  presentation with one account, enable “Show account card when only one account is available” or set\n  `claudeSwapShowSingleAccount: true` on the Claude provider in the resolved config file (normally\n  `~/.config/codexbar/config.json`; legacy installs may use `~/.codexbar/config.json`). The option defaults off,\n  zero accounts still use the ambient presentation, and account identity is `claude-swap:<slot>`, never the display\n  email.\n- Session forecasts use each account's own recorded usage. History keys hash the normalized email and organization\n  UUID from the existing list response, so moving slots preserves history and the same email in different workspaces\n  stays separate. Empty organization UUIDs identify personal accounts; an email change starts a new history.\n  Raw emails and organization UUIDs are not saved in history. Fresh samples retain their source timestamps and the\n  shared 17,520-sample limit per series; last-known or failed rows do not add samples. Ambient Claude history is separate.\n- Provider widgets follow the active claude-swap account under the same multiple-account/single-account presentation\n  rule. Successful list refreshes and clearing the adapter publish a widget snapshot even with account widgets off.\n  Retained quota keeps its original measurement time and is bound to the slot's opaque owner fingerprint; unavailable\n  or replaced accounts never borrow another account's quota. Local cost history remains combined across Claude homes.\n- Terminal scope: cards expands the account list, while Auto `codexbar usage` follows its active slot on every\n  supported CLI platform (including single-slot lists). An explicit\n  Claude provider or `--source auto` remains eligible, while `--account`, `--account-index`, `--all-accounts`, and\n  explicit non-auto source flags bypass the adapter. A configured non-auto source also bypasses it for `usage`.\n  Unavailable active-slot usage falls back to ordinary source selection with an explicit adapter fallback source label;\n  the returned identity belongs to that fallback source. Serve `/usage`/`/cost` remain unchanged,\n  while `codexbar dashboard` and `GET /dashboard/v1/snapshot` additionally nest one entry per swap account in the\n  Claude provider row, with full identity by default or redacted email local parts when `--identity redacted` is set.\n- Isolation: CodexBar never reads claude-swap or Claude Code credential storage for this feature; the\n  subprocess handles its own credential access. In the app, adapter failures keep the last successful accounts as\n  stale data, surface the error in provider settings, and never affect the ambient Claude usage card. In terminal\n  cards, a list failure retains the current ambient output, adds a distinct `Claude (claude-swap)` footer entry, and\n  exits non-zero.\n- Sentinel statuses (`token_expired`, `relogin_required`, `api_key`, `keychain_unavailable`, `no_credentials`,\n  `foreign_credential`, and unknown future values) retain per-account diagnostics. Full cards can show explicitly\n  reported last-known usage alongside its age; without it they remain notes-only. Brief terminal cards keep the\n  diagnostic. When no explicit last-good measurement is supplied and a window is still at 100%, CodexBar keeps that slot's last\n  projected usage bars and names the exhausted window (5-hour session, 7-day weekly, and/or a scoped model such as\n  Fable) plus its reset time — not \"Usage fetch failed.\" A first refresh that is already `unavailable` with no\n  retained windows says usage is unavailable, without assuming why the source could not fetch it. Active rows are marked `[active]`; no claude-swap row infers\n  a plan badge.\n- Read-only adapters may set top-level `supportsAccountSwitching: false` in their schema-v1 list response. Usage,\n  account details, and active markers remain visible, while switching and re-authentication actions are suppressed.\n  Omitting the capability preserves existing switching behavior; a present value must be a JSON boolean.\n- Switching: an inactive account with usable source credentials shows “Switch Account…”. Clicking it runs exactly\n  `cswap --switch-to <slot> --json`, validates the versioned result and requested slot, then refreshes both ambient\n  Claude usage and every claude-swap account card. Switches are serialized; no automatic switching occurs. While\n  reconciling, the ambient Claude refresh is given five seconds to finish; a stalled refresh continues in the\n  background while switching waits for the adapter's active-account list, so it cannot leave the account chips inert.\n  A later switch can refresh the adapter list even when its ambient refresh is queued behind an earlier probe. While\n  claude-swap owns account presentation, the separate ambient OAuth action reads “Sign in with Claude Code…” and does\n  not add or switch a claude-swap account.\n- Expired, missing, unknown, or Keychain-inaccessible credentials stay non-actionable. A failed switch remains visible\n  on that account without discarding its last successful usage. A running Claude Code process can take up to the\n  claude-swap Keychain cache interval to observe the new account.\n- A `foreign_credential` row explains that the live credential belongs to another account. An inactive row can use\n  the existing explicit slot switch. An active row offers **Re-authenticate**, which runs the same\n  `cswap --switch-to <slot> --json` command to let claude-swap reconcile its own credential state, without `--force`. Clicking the active segment\n  still only inspects it; repair requires its explicit button.\n- Multiple claude-swap accounts—and a single account when explicitly enabled—take precedence over Claude\n  token-account presentation (stacked cards and the segmented switcher).\n\nPackaged synthetic proof (fake `cswap` executable, no real accounts or credentials):\n\n![Stacked claude-swap account cards](screenshots/claude-swap-accounts-synthetic-proof.png)\n\nModel-scoped weekly-window proof (synthetic data, no real accounts or credentials):\n\n| Before | After |\n| --- | --- |\n| ![claude-swap card before scoped windows](screenshots/claude-swap-scoped-before.png) | ![claude-swap card with a Fable scoped weekly window](screenshots/claude-swap-scoped-after.png) |\n\n## CLI PTY (fallback)\n- Runs `claude` in a PTY session (`ClaudeCLISession`).\n- The PTY and replay screen share a 160-column, ","docs/llms.txt":"# CodexBar\n\nA tiny macOS menu bar app that tracks AI coding-provider usage windows, credits, costs, and resets across 108 providers — Codex, OpenAI, Claude, Cursor, Gemini, Copilot, LiteLLM, Helmcode, v0, TypeSafe, and more.\n\nCanonical documentation:\n- CodexBar — every AI coding limit, in your menu bar: https://codexbar.app/ - A tiny macOS menu bar app that tracks AI coding-provider usage windows, credits, costs, and resets across 108 providers — Codex, OpenAI, Claude, Cursor, Gemini, Copilot, LiteLLM, Helmcode, v0, TypeSafe, and more.\n\nSource: https://github.com/steipete/CodexBar\n\nGuidance for agents:\n- Prefer the canonical documentation URLs above over README excerpts or package metadata.\n- Fetch only the pages needed for the current task; this is an index, not a full-site corpus.\n","docs/deepseek.md":"---\nsummary: \"DeepSeek provider data sources: API key, balance, and optional detailed usage endpoints.\"\nread_when:\n  - Adding or tweaking DeepSeek balance parsing\n  - Adding or tweaking DeepSeek detailed usage parsing\n  - Updating API key handling\n  - Documenting new provider behavior\n---\n\n# DeepSeek provider\n\nCodexBar can use either a DeepSeek API key or a signed-in DeepSeek Platform session for the remaining credit balance.\nDetailed cost and token usage comes from the Platform session; an API key cannot authenticate the private dashboard\nendpoints.\n\n## Data sources\n\n1. **Optional API key** supplied via `DEEPSEEK_API_KEY` / `DEEPSEEK_KEY`, or selected from DeepSeek token accounts in `~/.codexbar/config.json`.\n2. **API-key balance endpoint**\n   - `GET https://api.deepseek.com/user/balance`\n   - Request headers: `Authorization: Bearer <api key>`, `Accept: application/json`\n   - Response contains `is_available`, and a `balance_infos` array with per-currency entries\n     (`total_balance`, `granted_balance`, `topped_up_balance`).\n3. **Platform-session balance endpoint**\n   - `GET https://platform.deepseek.com/api/v0/users/get_user_summary`\n   - Request headers: `Authorization: Bearer <platform userToken>`, `Accept: application/json`\n   - Used as the balance source when no API key is configured.\n4. **Optional detailed usage endpoints**\n   - Preferred: `GET https://platform.deepseek.com/api/v0/usage/by_api_key/amount?start=<unix>&end=<unix>&tz=<offset>`\n     and `.../by_api_key/cost?...` — the same per-key daily buckets the Platform usage page loads.\n   - Fallback: `GET https://platform.deepseek.com/api/v0/usage/amount?month=<month>&year=<year>`\n     and `.../usage/cost?...` if the per-key endpoints fail.\n   - Daily buckets use Gregorian dates at the current fixed UTC offset in seconds, matching the API across daylight-saving transitions. Monthly fallback keeps\n     Gregorian UTC month selection and date parsing, and is labeled **This month**, not **Last 30 days**.\n   - Cancellation stops enrichment without starting monthly fallback requests.\n   - Request headers: `Authorization: Bearer <platform userToken>`, `Accept: application/json`, `x-client-platform: web`\n   - These are private dashboard endpoints rather than documented public API endpoints and may change without notice.\n\n## Platform session\n\nCodexBar resolves the Platform `userToken` in this order:\n\n1. An explicitly supplied `DEEPSEEK_PLATFORM_TOKEN` / `DEEPSEEK_USER_TOKEN` or a legacy\n   `providers[].cookieHeader` value preserved from an existing config.\n2. A prompt-free read of `userToken` from the `https://platform.deepseek.com` local-storage origin in Chrome.\n\nThe legacy config value remains a compatibility fallback so upgrading cannot silently erase a working browser-only\nsession. An unscoped legacy or environment token is never combined with an API-key balance; API enrichment requires\na session saved for that credential scope. New automatic imports are never written back to config.\n\nCodexBar checks every Chrome profile containing a parseable `userToken` against DeepSeek. Rejected or expired\nsessions are omitted. Settings shows a **Chrome profile** picker containing only valid sessions. With no API key, one\nvalid session is selected automatically; with multiple valid sessions, CodexBar reuses the saved choice or asks once.\nWhen an API key supplies the balance, a new or changed API credential requires an explicit session selection before\nwebsite usage is combined with it. CodexBar persists a stable browser/profile identifier, not an absolute home-directory path, and\nkeeps automatically imported tokens in memory only. The choice is scoped to a non-reversible fingerprint of the\nactive API credential and saved-account slot, so replacing a key or switching accounts cannot silently reuse an old\nbrowser session. Validation results are cached briefly so normal\nrefreshes do not probe every profile, and a temporary network failure does not erase a previously validated profile.\nIf the selected session expires, CodexBar asks before switching to another valid profile.\n\nChrome-only balance refreshes also keep the last live balance and its original timestamp through temporary transport\nfailures when the failed request belongs to the same browser profile and token. This ownership proof stays in memory;\ndecoded snapshots, changed profiles or tokens, and API-key balances with optional browser enrichment cannot supply it.\nOnce DeepSeek rejects a session, a later network failure cannot preserve its old balance; successful validation must\nrestore that session first. A saved profile with unknown validity still supplies transport diagnostics when other profiles\nsucceed; a known-rejected selection keeps the profile picker and its valid alternatives.\nRecognized transport failures still participate in startup retries when no matching balance can be retained. A Chrome-resolution\ndeadline without an observed session fails closed; cancelled or superseded refresh tasks keep their existing behavior.\n\nIf no session is valid, the menu keeps the API-key balance when one exists; otherwise it asks the user to sign in to\nDeepSeek Platform in Chrome. Authentication failures returned as top-level or nested DeepSeek codes `40002` and\n`40003` are treated as expired sessions.\n\n## Usage details\n\n- The menu card shows total balance with the paid vs. granted breakdown:\n  e.g. `$50.00 (Paid: $40.00 / Granted: $10.00)`.\n- The API separates granted balance from topped-up balance; CodexBar labels these as granted vs. paid credit.\n- With optional extra usage enabled, the menu shows today's and last-30-days cost and tokens,\n  request counts, API-key count, the top model, per-model spend, a daily token chart, and a daily spend chart.\n- Per-model spend uses the same reporting period and currency as detailed usage: **Last 30 days** for the preferred\n  endpoints, or **This month** for monthly fallback. These are Platform-account totals across API keys. Missing or\n  invalid model costs are omitted; a reported zero is retained.\n- The amount and cost requests run concurrently. After balance arrives, CodexBar waits up to five seconds for\n  automatic Chrome resolution and detailed usage. The deadline remains bounded even if a local Chrome read does not\n  respond to cancellation. If the optional work fails or times out, the balance and previously validated profile list\n  remain available while the menu reports that detailed usage is unavailable.\n- With multiple configured API keys, browser-derived detailed usage is shown only with the active API-key account;\n  other account cards remain balance-only so website usage is never duplicated across accounts.\n- When multiple currencies are present, USD is shown preferentially.\n- If total balance is zero, CodexBar shows an add-credits message. If balance is nonzero but `is_available` is false, it shows \"Balance unavailable for API calls\".\n- There is no session or weekly window — DeepSeek does not expose per-window quota via API.\n- Token-account selection injects the selected key into the fetch environment; otherwise CodexBar reads `DEEPSEEK_API_KEY` / `DEEPSEEK_KEY`.\n\n## Key files\n\n- `Sources/CodexBarCore/Providers/DeepSeek/DeepSeekProviderDescriptor.swift` (descriptor + fetch strategy)\n- `Sources/CodexBarCore/Providers/DeepSeek/DeepSeekUsageFetcher.swift` (HTTP client + JSON parser)\n- `Sources/CodexBarCore/Providers/DeepSeek/DeepSeekPlatformTokenImporter.swift` (Chrome Platform session import)\n- `Sources/CodexBarCore/Providers/DeepSeek/DeepSeekSettingsReader.swift` (env var resolution)\n- `Sources/CodexBar/Providers/DeepSeek/DeepSeekProviderImplementation.swift` (provider activation and token-account visibility)\n- `Sources/CodexBarCore/TokenAccountSupportCatalog+Data.swift` (DeepSeek token-account injection)\n","docs/mistral.md":"---\nsummary: \"Mistral provider: browser cookie setup, billing usage, included API/Vibe allowances, and credits.\"\nread_when:\n  - Configuring Mistral usage\n  - Debugging Mistral billing or Vibe usage requests\n  - Adjusting Mistral cost, credit, or monthly-plan display\n---\n\n# Mistral Provider\n\nCodexBar reads Mistral billing usage and subscription allowances with the Mistral web session from\n`admin.mistral.ai`. It also fetches credit balance and falls back to the console Vibe endpoint when the subscription\npage does not expose a Vibe allowance.\n\n## Setup\n\n1. Open **Settings -> Providers**.\n2. Enable **Mistral**.\n3. Sign in to [Mistral Admin](https://admin.mistral.ai/organization/usage) in a supported browser.\n4. Leave Cookie source on **Automatic**, or switch to **Manual** and paste a `Cookie:` header from a request to\n   `admin.mistral.ai`.\n\nManual cookies must include an `ory_session_*` cookie. A `csrftoken` cookie enables fallback Vibe requests that\nrequire the `X-CSRFTOKEN` header.\n\nOn Linux, browser import is unavailable, so the CLI reads Mistral only in Manual mode: set the provider's\n`cookieSource` to `manual` and its `cookieHeader` to the pasted header in `~/.config/codexbar/config.json`.\n\nAutomatic import follows the shared supported-browser order. Safari requires Full Disk Access.\nThe shared access gate bounds Keychain prompts. Automatic import reads only unexpired cookies from\nthe documented Mistral domains.\n\n## Data Sources\n\nCodexBar requests the current UTC month, subscription allowances, and credits from Mistral Admin:\n\n- `GET https://admin.mistral.ai/api/billing/v2/usage?month=<month>&year=<year>`\n- `GET https://admin.mistral.ai/subscription` (best-effort included API and Vibe allowances)\n- `GET https://admin.mistral.ai/api/billing/credits` (best-effort credit balance)\n\nIf the subscription page has no Vibe allowance and a CSRF token is available, CodexBar makes a bounded best-effort\nfallback request:\n\n- `GET https://console.mistral.ai/api-ui/trpc/billing.vibeUsage?...`\n\nFor the console request, CodexBar forwards only the `csrftoken` and `ory_session_*` cookies. Other\n`admin.mistral.ai` cookies stay origin-bound.\n\n## Display\n\n- **Included API** shows the subscription allowance's used percentage, used / total / remaining amount, and reset time.\n- The optional **Monthly Plan** window shows the separate Vibe Code allowance with the same details.\n- API spend is computed from billed units (`value_paid`, falling back to `value`) and the pricing table. Each unit takes\n  the price with the same event type, metric, group, API zone, and service tier; the table lists one metric under\n  several of these, and audio-second and priority prices are far higher than standard token prices. Token totals\n  and daily buckets use consumed units (`value`, falling back to `value_paid`), so plan-covered usage still counts.\n  Legacy tables that omit both API zone and service tier use the unqualified price for the same event type, metric, and group.\n- Token totals include API completions, Le Chat, and Vibe Code completions from the billing usage response.\n- Daily usage buckets feed the inline usage dashboard.\n- The provider card can show credit balance when the credits endpoint returns it.\n- Allowance amounts derive from Mistral's reported percentage and allowance size, independently of billed API spend. Zero or malformed allowances are omitted without discarding a valid sibling allowance.\n- The Automatic menu bar selection retains API spend; Included API and Monthly Plan select their respective quota percentages.\n- Token-cost history is supported through the billing web session; no local log scan is used.\n- Unrepresentable billing token totals fail parsing instead of crashing. Display-only model rankings omit an\n  overflowing total while retaining valid cost data.\n- Final input, cached, and output totals allow signed adjustments in any lane while rejecting totals outside the\n  supported integer range.\n\n## Widgets\n\nUsage widgets follow the **Menu bar metric** picker in Mistral's provider settings. The picker appears in every menu bar\nstyle, so Critters and Meter bars users can still pick the widget allowance. Choosing a percentage metric pins\nMistral’s layout against later global layout edits. Without a percentage, the picker changes only the stored metric\nand keeps following the global layout:\n\n- **Automatic** and **Included API** show only the API allowance, preserving the existing default.\n- **Monthly Plan** shows only the Vibe allowance, falling back to Included API when the plan is missing or unknown.\n\nAutomatic still shows API spend in the menu bar. Changing the metric updates widget rows from the existing snapshot,\nwithout another request. After a failed refresh, widgets reselect from the last published usage snapshot and keep its\noriginal measurement time, including across repeated metric changes. This in-memory source is cleared when provider\nor account ownership is invalidated. Burn Down eligibility still requires a known window duration and reset.\n\n## CLI Usage\n\n```bash\ncodexbar usage --provider mistral --verbose\n```\n\nText output includes both Included API and the optional Monthly Plan, each with its percentage, reset date when\navailable, and used / total / remaining amounts. Amounts are shown as detail, never as a reset time. This uses the\nexisting snapshot; no additional requests are made. JSON output is unchanged: the Monthly Plan remains in\n`extraRateWindows` with the ID `mistral-monthly-plan`.\n\n## Troubleshooting\n\n### \"No Mistral session cookies found\"\n\nSign in to [Mistral Admin](https://admin.mistral.ai/organization/usage) in a supported browser, then refresh.\n\n### \"Mistral cookie header is invalid\"\n\nIn manual mode, paste a full `Cookie:` header from an `admin.mistral.ai` request. The header must include an\n`ory_session_*` cookie.\n\n### Included allowance, credits, or Vibe plan usage are missing\n\nThe billing usage request is required. Subscription allowances, credits, and Vibe usage are best-effort; if an\noptional source fails or does not expose data for the account, CodexBar keeps the main Mistral usage result.\n\n## Related Files\n\n- `Sources/CodexBarCore/Providers/Mistral/MistralProviderDescriptor.swift`\n- `Sources/CodexBarCore/Providers/Mistral/MistralUsageFetcher.swift`\n- `Sources/CodexBarCore/Providers/Mistral/MistralSubscriptionBudgetParser.swift`\n- `Sources/CodexBarCore/Providers/Mistral/MistralModels.swift`\n- `Sources/CodexBarCore/Providers/Mistral/MistralCookieImporter.swift`\n- `Sources/CodexBar/Providers/Mistral/MistralProviderImplementation.swift`\n",".agents/skills/qa-test/references/api-specs.md":"# References Skill\n\nPath: `.agents/skills/qa-test/references/api-specs.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/steipete/CodexBar/HEAD/.agents/skills/qa-test/references/api-specs.md)",".agents/skills/qa-test/agents/openai.yaml":"# Agents Skill\n\nPath: `.agents/skills/qa-test/agents/openai.yaml`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/steipete/CodexBar/HEAD/.agents/skills/qa-test/agents/openai.yaml)"},"items":[{"name":"AGENTS.md","path":"AGENTS.md","rawUrl":"https://raw.githubusercontent.com/steipete/CodexBar/HEAD/AGENTS.md","title":"AI Agent Protocol & Instructions","category":"root-instruction","format":"markdown","content":"# Repository Guidelines\n\n## Project Structure & Modules\n- `Sources/CodexBar`: Swift 6 menu bar app (usage/credits probes, icon renderer, settings). Keep changes small and reuse existing helpers.\n- `Tests/CodexBarTests`: XCTest coverage for usage parsing, status probes, icon patterns; mirror new logic with focused tests.\n- `Scripts`: build/package helpers (`package_app.sh`, `sign-and-notarize.sh`, `build_icon.sh`, `compile_and_run.sh`). Read-only release helpers call `Scripts/mac-release`, which resolves `MAC_RELEASE_TOOL` or the shared `agent-scripts` checkout; CI owns publication.\n- `docs`: release notes and process (`docs/RELEASING.md`, screenshots). Root-level zips/appcast are generated artifacts—avoid editing except during releases.\n\n## Build, Test, Run\n- Dev loop: `./Scripts/compile_and_run.sh` kills old instances, builds, packages, relaunches `CodexBar.app`, and confirms it stays running; add `--test` for the sharded full suite.\n- Quick build/test: `swift build` (debug) or `swift build -c release`; `make test` for the sharded full suite.\n- Package locally: `./Scripts/package_app.sh` to refresh `CodexBar.app`, then restart with `pkill -x CodexBar || pkill -f CodexBar.app || true; cd /Users/steipete/Projects/codexbar && open -n /Users/steipete/Projects/codexbar/CodexBar.app`.\n- Release publication is owned by `.github/workflows/release.yml`; use its default dry-run dispatch to rehearse. Repo build/signing stays in `Scripts/sign-and-notarize.sh`; see `docs/RELEASING.md`.\n\n## Coding Style & Naming\n- Enforce SwiftFormat/SwiftLint: run `swiftformat Sources Tests` and `swiftlint --strict`. 4-space indent, 120-char lines, explicit `self` is intentional—do not remove.\n- Favor small, typed structs/enums; maintain existing `MARK` organization. Use descriptive symbols; match current commit tone.\n\n## Testing Guidelines\n- Add/extend XCTest cases under `Tests/CodexBarTests/*Tests.swift` (`FeatureNameTests` with `test_caseDescription` methods).\n- Swift Testing: prefer backticked sentence names; no camelCase.\n- Model names in tests/code: released models or clearly fictitious names only; never expose unreleased names.\n- Iterate with focused runs (`make test-fast FILTER='SuiteName'` or `swift test --filter ...`) covering the touched suites. For a local full run use `./Scripts/test.sh --direct-workers 8` (parallel isolated groups, ~10 min) instead of serial `make test` (~45 min). The full suite is not a handoff gate: run it in the background or leave it to PR CI; never block on it in the foreground.\n- While iterating, format/lint only touched files; run `make check` once before commit/handoff and fix reported issues.\n- Prefer CLI/focused tests over app-bundle live tests when behavior can be verified without relaunching CodexBar.\n- Never run tests/checks or ad-hoc validation that can display macOS Keychain prompts. Live provider probes, browser-cookie imports, `codexbar usage` against real accounts, and real SecItem reads must be explicitly requested; otherwise use parser tests, stubs, test stores, or `KeychainNoUIQuery`.\n- App-group migration tests must inject dictionary-backed defaults, both snapshot URLs, a synthetic home, and a contained recording FileManager. UUID defaults suites and Keychain isolation flags do not isolate defaults search domains or filesystem access. Ordinary SettingsStore tests must not discover shared defaults or run app-group migration.\n- macOS CI is brittle around headless AppKit status/menu tests. Prefer covering menu behavior through stable state/model seams (`MenuDescriptor`, `ProvidersPane`, `CodexAccountsSectionState`, etc.) instead of constructing live `NSStatusBar`/`NSMenu` flows unless the AppKit wiring itself is the thing under test.\n\n## CI Efficiency\n- Follow [the direct-test and SwiftPM cache contracts](docs/DEVELOPMENT.md#macos-direct-test-groups) when changing CI or test execution; preserve full test inventory, deadlines, failure handling, cache verification, and clean fallback.\n- Reuse incremental builds during iteration; never copy compiled products between worktrees. Clean `.build` for diagnosed contamination, a cold-build comparison, or maintainer-requested cleanup.\n- Run focused tests during development; the final code needs focused tests plus `make check`, with the full suite from PR CI or a background run. Repeat successful full checks when relevant inputs or the environment change, a failure or unresolved concern requires it, or a maintainer requests it; do not repeat them solely to prepare a report.\n\n## Commit & PR Guidelines\n- Commit messages: short imperative clauses (e.g., “Improve usage probe”, “Fix icon dimming”); keep commits scoped.\n- PRs/patches should list summary, commands run, screenshots/GIFs for UI changes, and linked issue/reference when relevant.\n\n## Agent Notes\n- Use the provided scripts and package manager (SwiftPM); avoid adding dependencies or tooling without confirmation.\n- Menu bar automation: capture the target screen first and verify the CodexBar icon is visibly onscreen. Reject `click-extra` success when coordinates fall outside display bounds; hidden menu extras are not click proof.\n- Validate UI/runtime behavior against the freshly built bundle; restart via the pkill+open command above to avoid running stale binaries.\n- To guarantee the right bundle is running after a rebuild, use: `pkill -x CodexBar || pkill -f CodexBar.app || true; cd /Users/steipete/Projects/codexbar && open -n /Users/steipete/Projects/codexbar/CodexBar.app`.\n- For CLI-testable provider/parser/settings behavior, use CLI/focused tests instead of `Scripts/package_app.sh` or `./Scripts/compile_and_run.sh`.\n- Run `./Scripts/compile_and_run.sh` only when UI/runtime behavior needs bundle-level validation; it builds, tests, packages, relaunches, and verifies the app stays running.\n- Widget/Tahoe UI issues: use Parallels macOS VM plus screenshots/clicks for autonomous verification.\n- Release workflow: wait for completion and verify the complete artifact/feed/tap handoff; do not publish locally.\n- Sparkle release key: CI `SPARKLE_PRIVATE_KEY` must match the existing AGCY public key in `.github/release.json`; never substitute another app’s key.\n- Swift concurrency: treat sibling `async let` tasks as a review red flag when one child is required and another is optional/best-effort. Prefer sequential awaits or a drained `withThrowingTaskGroup` that surfaces required failures and explicitly contains optional failures; crash stacks mentioning `swift_task_dealloc` or `asyncLet_finish_after_task_completion` should trigger an audit of nearby `async let` usage.\n- Prefer modern SwiftUI/Observation macros: use `@Observable` models with `@State` ownership and `@Bindable` in views; avoid `ObservableObject`, `@ObservedObject`, and `@StateObject`.\n- Favor modern macOS 15+ APIs over legacy/deprecated counterparts when refactoring (Observation, new display link APIs, updated menu item styling, etc.).\n- Keep provider data siloed: when rendering usage or account info for a provider (Claude vs Codex), never display identity/plan fields sourced from a different provider.\n- Claude CLI status line is custom + user-configurable; never rely on it for usage parsing by default. A user-enabled, opt-in statusLine JSON feed is permitted as an explicit data source (owner ruling, #2733): it must be off by default, clearly labeled as sourced from the user's own statusLine config, and fail soft when the format drifts.\n- Cookie imports: use the shared default browser order; `BrowserCookieAccessGate` ensures no background/settings prompts, host-matching cookie rows before Safe Storage access, at most one interactive Keychain read per manual action, denial cooldowns even on manual retries (only `codexbar cookie refresh --allow-keychain-prompt` overrides), and traversal stopping at the first validated session. Restrict a provider to a browser subset only with a documented technical reason next to the code.\n","isInternal":false,"tokens":1766,"sizeBytes":7953},{"name":"deepseek.md","path":"docs/deepseek.md","rawUrl":"https://raw.githubusercontent.com/steipete/CodexBar/HEAD/docs/deepseek.md","title":"docs - DeepSeek System & Reasoning Rules","category":"root-instruction","format":"markdown","content":"---\nsummary: \"DeepSeek provider data sources: API key, balance, and optional detailed usage endpoints.\"\nread_when:\n  - Adding or tweaking DeepSeek balance parsing\n  - Adding or tweaking DeepSeek detailed usage parsing\n  - Updating API key handling\n  - Documenting new provider behavior\n---\n\n# DeepSeek provider\n\nCodexBar can use either a DeepSeek API key or a signed-in DeepSeek Platform session for the remaining credit balance.\nDetailed cost and token usage comes from the Platform session; an API key cannot authenticate the private dashboard\nendpoints.\n\n## Data sources\n\n1. **Optional API key** supplied via `DEEPSEEK_API_KEY` / `DEEPSEEK_KEY`, or selected from DeepSeek token accounts in `~/.codexbar/config.json`.\n2. **API-key balance endpoint**\n   - `GET https://api.deepseek.com/user/balance`\n   - Request headers: `Authorization: Bearer <api key>`, `Accept: application/json`\n   - Response contains `is_available`, and a `balance_infos` array with per-currency entries\n     (`total_balance`, `granted_balance`, `topped_up_balance`).\n3. **Platform-session balance endpoint**\n   - `GET https://platform.deepseek.com/api/v0/users/get_user_summary`\n   - Request headers: `Authorization: Bearer <platform userToken>`, `Accept: application/json`\n   - Used as the balance source when no API key is configured.\n4. **Optional detailed usage endpoints**\n   - Preferred: `GET https://platform.deepseek.com/api/v0/usage/by_api_key/amount?start=<unix>&end=<unix>&tz=<offset>`\n     and `.../by_api_key/cost?...` — the same per-key daily buckets the Platform usage page loads.\n   - Fallback: `GET https://platform.deepseek.com/api/v0/usage/amount?month=<month>&year=<year>`\n     and `.../usage/cost?...` if the per-key endpoints fail.\n   - Daily buckets use Gregorian dates at the current fixed UTC offset in seconds, matching the API across daylight-saving transitions. Monthly fallback keeps\n     Gregorian UTC month selection and date parsing, and is labeled **This month**, not **Last 30 days**.\n   - Cancellation stops enrichment without starting monthly fallback requests.\n   - Request headers: `Authorization: Bearer <platform userToken>`, `Accept: application/json`, `x-client-platform: web`\n   - These are private dashboard endpoints rather than documented public API endpoints and may change without notice.\n\n## Platform session\n\nCodexBar resolves the Platform `userToken` in this order:\n\n1. An explicitly supplied `DEEPSEEK_PLATFORM_TOKEN` / `DEEPSEEK_USER_TOKEN` or a legacy\n   `providers[].cookieHeader` value preserved from an existing config.\n2. A prompt-free read of `userToken` from the `https://platform.deepseek.com` local-storage origin in Chrome.\n\nThe legacy config value remains a compatibility fallback so upgrading cannot silently erase a working browser-only\nsession. An unscoped legacy or environment token is never combined with an API-key balance; API enrichment requires\na session saved for that credential scope. New automatic imports are never written back to config.\n\nCodexBar checks every Chrome profile containing a parseable `userToken` against DeepSeek. Rejected or expired\nsessions are omitted. Settings shows a **Chrome profile** picker containing only valid sessions. With no API key, one\nvalid session is selected automatically; with multiple valid sessions, CodexBar reuses the saved choice or asks once.\nWhen an API key supplies the balance, a new or changed API credential requires an explicit session selection before\nwebsite usage is combined with it. CodexBar persists a stable browser/profile identifier, not an absolute home-directory path, and\nkeeps automatically imported tokens in memory only. The choice is scoped to a non-reversible fingerprint of the\nactive API credential and saved-account slot, so replacing a key or switching accounts cannot silently reuse an old\nbrowser session. Validation results are cached briefly so normal\nrefreshes do not probe every profile, and a temporary network failure does not erase a previously validated profile.\nIf the selected session expires, CodexBar asks before switching to another valid profile.\n\nChrome-only balance refreshes also keep the last live balance and its original timestamp through temporary transport\nfailures when the failed request belongs to the same browser profile and token. This ownership proof stays in memory;\ndecoded snapshots, changed profiles or tokens, and API-key balances with optional browser enrichment cannot supply it.\nOnce DeepSeek rejects a session, a later network failure cannot preserve its old balance; successful validation must\nrestore that session first. A saved profile with unknown validity still supplies transport diagnostics when other profiles\nsucceed; a known-rejected selection keeps the profile picker and its valid alternatives.\nRecognized transport failures still participate in startup retries when no matching balance can be retained. A Chrome-resolution\ndeadline without an observed session fails closed; cancelled or superseded refresh tasks keep their existing behavior.\n\nIf no session is valid, the menu keeps the API-key balance when one exists; otherwise it asks the user to sign in to\nDeepSeek Platform in Chrome. Authentication failures returned as top-level or nested DeepSeek codes `40002` and\n`40003` are treated as expired sessions.\n\n## Usage details\n\n- The menu card shows total balance with the paid vs. granted breakdown:\n  e.g. `$50.00 (Paid: $40.00 / Granted: $10.00)`.\n- The API separates granted balance from topped-up balance; CodexBar labels these as granted vs. paid credit.\n- With optional extra usage enabled, the menu shows today's and last-30-days cost and tokens,\n  request counts, API-key count, the top model, per-model spend, a daily token chart, and a daily spend chart.\n- Per-model spend uses the same reporting period and currency as detailed usage: **Last 30 days** for the preferred\n  endpoints, or **This month** for monthly fallback. These are Platform-account totals across API keys. Missing or\n  invalid model costs are omitted; a reported zero is retained.\n- The amount and cost requests run concurrently. After balance arrives, CodexBar waits up to five seconds for\n  automatic Chrome resolution and detailed usage. The deadline remains bounded even if a local Chrome read does not\n  respond to cancellation. If the optional work fails or times out, the balance and previously validated profile list\n  remain available while the menu reports that detailed usage is unavailable.\n- With multiple configured API keys, browser-derived detailed usage is shown only with the active API-key account;\n  other account cards remain balance-only so website usage is never duplicated across accounts.\n- When multiple currencies are present, USD is shown preferentially.\n- If total balance is zero, CodexBar shows an add-credits message. If balance is nonzero but `is_available` is false, it shows \"Balance unavailable for API calls\".\n- There is no session or weekly window — DeepSeek does not expose per-window quota via API.\n- Token-account selection injects the selected key into the fetch environment; otherwise CodexBar reads `DEEPSEEK_API_KEY` / `DEEPSEEK_KEY`.\n\n## Key files\n\n- `Sources/CodexBarCore/Providers/DeepSeek/DeepSeekProviderDescriptor.swift` (descriptor + fetch strategy)\n- `Sources/CodexBarCore/Providers/DeepSeek/DeepSeekUsageFetcher.swift` (HTTP client + JSON parser)\n- `Sources/CodexBarCore/Providers/DeepSeek/DeepSeekPlatformTokenImporter.swift` (Chrome Platform session import)\n- `Sources/CodexBarCore/Providers/DeepSeek/DeepSeekSettingsReader.swift` (env var resolution)\n- `Sources/CodexBar/Providers/DeepSeek/DeepSeekProviderImplementation.swift` (provider activation and token-account visibility)\n- `Sources/CodexBarCore/TokenAccountSupportCatalog+Data.swift` (DeepSeek token-account injection)\n","frontmatter":{"summary":"DeepSeek provider data sources: API key, balance, and optional detailed usage endpoints.","read_when":["Adding or tweaking DeepSeek balance parsing","Adding or tweaking DeepSeek detailed usage parsing","Updating API key handling","Documenting new provider behavior"]},"isInternal":false,"tokens":1662,"sizeBytes":7799},{"name":"llms.txt","path":"docs/llms.txt","rawUrl":"https://raw.githubusercontent.com/steipete/CodexBar/HEAD/docs/llms.txt","title":"docs - LLM Index & Context Digest","category":"root-instruction","format":"text","content":"# CodexBar\n\nA tiny macOS menu bar app that tracks AI coding-provider usage windows, credits, costs, and resets across 108 providers — Codex, OpenAI, Claude, Cursor, Gemini, Copilot, LiteLLM, Helmcode, v0, TypeSafe, and more.\n\nCanonical documentation:\n- CodexBar — every AI coding limit, in your menu bar: https://codexbar.app/ - A tiny macOS menu bar app that tracks AI coding-provider usage windows, credits, costs, and resets across 108 providers — Codex, OpenAI, Claude, Cursor, Gemini, Copilot, LiteLLM, Helmcode, v0, TypeSafe, and more.\n\nSource: https://github.com/steipete/CodexBar\n\nGuidance for agents:\n- Prefer the canonical documentation URLs above over README excerpts or package metadata.\n- Fetch only the pages needed for the current task; this is an index, not a full-site corpus.\n","isInternal":false,"tokens":199,"sizeBytes":800},{"name":"mistral.md","path":"docs/mistral.md","rawUrl":"https://raw.githubusercontent.com/steipete/CodexBar/HEAD/docs/mistral.md","title":"docs - Mistral Agent & Codestral Directives","category":"root-instruction","format":"markdown","content":"---\nsummary: \"Mistral provider: browser cookie setup, billing usage, included API/Vibe allowances, and credits.\"\nread_when:\n  - Configuring Mistral usage\n  - Debugging Mistral billing or Vibe usage requests\n  - Adjusting Mistral cost, credit, or monthly-plan display\n---\n\n# Mistral Provider\n\nCodexBar reads Mistral billing usage and subscription allowances with the Mistral web session from\n`admin.mistral.ai`. It also fetches credit balance and falls back to the console Vibe endpoint when the subscription\npage does not expose a Vibe allowance.\n\n## Setup\n\n1. Open **Settings -> Providers**.\n2. Enable **Mistral**.\n3. Sign in to [Mistral Admin](https://admin.mistral.ai/organization/usage) in a supported browser.\n4. Leave Cookie source on **Automatic**, or switch to **Manual** and paste a `Cookie:` header from a request to\n   `admin.mistral.ai`.\n\nManual cookies must include an `ory_session_*` cookie. A `csrftoken` cookie enables fallback Vibe requests that\nrequire the `X-CSRFTOKEN` header.\n\nOn Linux, browser import is unavailable, so the CLI reads Mistral only in Manual mode: set the provider's\n`cookieSource` to `manual` and its `cookieHeader` to the pasted header in `~/.config/codexbar/config.json`.\n\nAutomatic import follows the shared supported-browser order. Safari requires Full Disk Access.\nThe shared access gate bounds Keychain prompts. Automatic import reads only unexpired cookies from\nthe documented Mistral domains.\n\n## Data Sources\n\nCodexBar requests the current UTC month, subscription allowances, and credits from Mistral Admin:\n\n- `GET https://admin.mistral.ai/api/billing/v2/usage?month=<month>&year=<year>`\n- `GET https://admin.mistral.ai/subscription` (best-effort included API and Vibe allowances)\n- `GET https://admin.mistral.ai/api/billing/credits` (best-effort credit balance)\n\nIf the subscription page has no Vibe allowance and a CSRF token is available, CodexBar makes a bounded best-effort\nfallback request:\n\n- `GET https://console.mistral.ai/api-ui/trpc/billing.vibeUsage?...`\n\nFor the console request, CodexBar forwards only the `csrftoken` and `ory_session_*` cookies. Other\n`admin.mistral.ai` cookies stay origin-bound.\n\n## Display\n\n- **Included API** shows the subscription allowance's used percentage, used / total / remaining amount, and reset time.\n- The optional **Monthly Plan** window shows the separate Vibe Code allowance with the same details.\n- API spend is computed from billed units (`value_paid`, falling back to `value`) and the pricing table. Each unit takes\n  the price with the same event type, metric, group, API zone, and service tier; the table lists one metric under\n  several of these, and audio-second and priority prices are far higher than standard token prices. Token totals\n  and daily buckets use consumed units (`value`, falling back to `value_paid`), so plan-covered usage still counts.\n  Legacy tables that omit both API zone and service tier use the unqualified price for the same event type, metric, and group.\n- Token totals include API completions, Le Chat, and Vibe Code completions from the billing usage response.\n- Daily usage buckets feed the inline usage dashboard.\n- The provider card can show credit balance when the credits endpoint returns it.\n- Allowance amounts derive from Mistral's reported percentage and allowance size, independently of billed API spend. Zero or malformed allowances are omitted without discarding a valid sibling allowance.\n- The Automatic menu bar selection retains API spend; Included API and Monthly Plan select their respective quota percentages.\n- Token-cost history is supported through the billing web session; no local log scan is used.\n- Unrepresentable billing token totals fail parsing instead of crashing. Display-only model rankings omit an\n  overflowing total while retaining valid cost data.\n- Final input, cached, and output totals allow signed adjustments in any lane while rejecting totals outside the\n  supported integer range.\n\n## Widgets\n\nUsage widgets follow the **Menu bar metric** picker in Mistral's provider settings. The picker appears in every menu bar\nstyle, so Critters and Meter bars users can still pick the widget allowance. Choosing a percentage metric pins\nMistral’s layout against later global layout edits. Without a percentage, the picker changes only the stored metric\nand keeps following the global layout:\n\n- **Automatic** and **Included API** show only the API allowance, preserving the existing default.\n- **Monthly Plan** shows only the Vibe allowance, falling back to Included API when the plan is missing or unknown.\n\nAutomatic still shows API spend in the menu bar. Changing the metric updates widget rows from the existing snapshot,\nwithout another request. After a failed refresh, widgets reselect from the last published usage snapshot and keep its\noriginal measurement time, including across repeated metric changes. This in-memory source is cleared when provider\nor account ownership is invalidated. Burn Down eligibility still requires a known window duration and reset.\n\n## CLI Usage\n\n```bash\ncodexbar usage --provider mistral --verbose\n```\n\nText output includes both Included API and the optional Monthly Plan, each with its percentage, reset date when\navailable, and used / total / remaining amounts. Amounts are shown as detail, never as a reset time. This uses the\nexisting snapshot; no additional requests are made. JSON output is unchanged: the Monthly Plan remains in\n`extraRateWindows` with the ID `mistral-monthly-plan`.\n\n## Troubleshooting\n\n### \"No Mistral session cookies found\"\n\nSign in to [Mistral Admin](https://admin.mistral.ai/organization/usage) in a supported browser, then refresh.\n\n### \"Mistral cookie header is invalid\"\n\nIn manual mode, paste a full `Cookie:` header from an `admin.mistral.ai` request. The header must include an\n`ory_session_*` cookie.\n\n### Included allowance, credits, or Vibe plan usage are missing\n\nThe billing usage request is required. Subscription allowances, credits, and Vibe usage are best-effort; if an\noptional source fails or does not expose data for the account, CodexBar keeps the main Mistral usage result.\n\n## Related Files\n\n- `Sources/CodexBarCore/Providers/Mistral/MistralProviderDescriptor.swift`\n- `Sources/CodexBarCore/Providers/Mistral/MistralUsageFetcher.swift`\n- `Sources/CodexBarCore/Providers/Mistral/MistralSubscriptionBudgetParser.swift`\n- `Sources/CodexBarCore/Providers/Mistral/MistralModels.swift`\n- `Sources/CodexBarCore/Providers/Mistral/MistralCookieImporter.swift`\n- `Sources/CodexBar/Providers/Mistral/MistralProviderImplementation.swift`\n","frontmatter":{"summary":"Mistral provider: browser cookie setup, billing usage, included API/Vibe allowances, and credits.","read_when":["Configuring Mistral usage","Debugging Mistral billing or Vibe usage requests","Adjusting Mistral cost, credit, or monthly-plan display"]},"isInternal":false,"tokens":1457,"sizeBytes":6581},{"name":"architecture.md","path":"docs/architecture.md","rawUrl":"https://raw.githubusercontent.com/steipete/CodexBar/HEAD/docs/architecture.md","title":"docs - System Architecture & Module Boundaries","category":"project-spec","format":"markdown","content":"---\nsummary: \"Architecture overview: modules, entry points, and data flow.\"\nread_when:\n  - Reviewing architecture before feature work\n  - Refactoring app structure, app lifecycle, or module boundaries\n---\n\n# Architecture overview\n\n## Modules\n- `Sources/CodexBarCore`: fetch + parse (Codex RPC, PTY runner, Claude probes, OpenAI web scraping, status polling).\n- `Sources/CodexBar`: state + UI (UsageStore, SettingsStore, StatusItemController, menus, icon rendering).\n- `Sources/CodexBarWidget`: WidgetKit extension wired to the shared snapshot.\n- `Sources/CodexBarCLI`: bundled CLI for `codexbar` usage/status output.\n- `Sources/CodexBarClaudeWatchdog`: helper process for stable Claude CLI PTY sessions.\n- `Sources/CodexBarClaudeWebProbe`: CLI helper to diagnose Claude web fetches.\n\n## Entry points\n- `CodexBarApp`: SwiftUI keepalive + Settings scene.\n- `AppDelegate`: wires status controller, Sparkle updater, notifications.\n- Status items are created on the main actor from `applicationDidFinishLaunching`; see\n  [status-item startup diagnostics](status-item-diagnostics.md) for opt-in lifecycle and Control Center traces.\n\n## Data flow\n- Background refresh → `UsageFetcher`/provider probes → `UsageStore` → menu/icon/widgets.\n- Settings toggles feed `SettingsStore` → `UsageStore` refresh cadence + feature flags.\n- Runtime-only provider settings flow through typed, descriptor-registered sections in `ProviderSettingsSnapshot`.\n- Codex cost conversation titles reuse SQLite database discovery per working directory and SQLite home within each\n  enrichment call. The next call resolves paths again, so config edits and new state database versions are picked up.\n\n## CLI login lifecycle\n- `CodexLoginRunner` and `KiroLoginRunner` resolve their own executable and environment, including Codex home scoping.\n- `CLILoginRunner` owns browser-waiting login processes, bounded output capture, timeout/cancellation, and optional\n  device-flow progress. It returns one shared result type; provider presentations retain their own recovery messages.\n- The login runner and `SubprocessRunner` share `ProcessTermination` and process-tree termination. Cancelling a login\n  stops its child process, joins its progress callback task, and produces no failure alert. Timeouts retain captured\n  diagnostic output, and inherited pipes cannot keep the caller waiting indefinitely.\n- Probe ownership cleanup reads exact environment markers, retaining PID identity checks before signaling. Environment\n  parsing searches NUL byte ranges so large unrelated values do not dominate timeout and cancellation cleanup.\n- Codex and Grok RPC clients share deadline selection through `RPCRequestTimeout`. The deadline wins before teardown\n  can report stdout EOF; each client keeps its protocol initialization, encoding, diagnostics, and error types.\n\n## Concurrency & platform\n- Swift 6 strict concurrency enabled; prefer Sendable state and explicit MainActor hops.\n- macOS 14+ targeting; avoid deprecated APIs when refactoring.\n- Cost stores share one utility serial executor per canonical database location, including symlinked cache roots and\n  database files. Separate database operations do not share SQLite lock waits. Executors are weakly registered and\n  released with their last store or queued job. Normal app fetches still use the separate serial scan queue;\n  WAL transactions are unchanged.\n\n## Shared policy ownership\n- `ProviderCatalog` indexes the immutable generated list of app implementations. JavaScript plugins use their own\n  runtime registry. Provider settings contexts bind directly to their store; the shared cookie picker owns mode\n  conversion, options, and live subtitle selection while providers retain labels, visibility, and cache scopes. Settings\n  link actions resolve their provider-supplied destinations when clicked, including region and account selection.\n  Cookie subtitles use explicit localization keys and arguments supplied by providers; the picker selects the current\n  mode and Keychain-disabled explanation without parsing English display text.\n- Descriptor-registered cookie sections preserve their concrete types after app policy resolves credentials. The app\n  keeps its automatic default, Keychain-disabled manual mode, and selected-account normalization; CLI inference stays\n  separate. Cookie-source writes share config persistence and logging, with provider-specific side effects retained.\n- Core's public descriptor registry keeps one ordered descriptor collection with an index for lookup. Its storage owns\n  synchronization, and replacing a descriptor updates metadata and CLI names without changing provider order.\n- `UsageStore.presentationSnapshot` owns subscription metadata selection. Live cards use that projection once; explicit\n  account cards use only their supplied snapshots, identity, and credits. Compact and stacked Codex account layouts share\n  the same account-card builder. Reordering visible providers preserves loaded config records for unavailable plugins;\n  config decoding retains its existing known-provider rules.\n- `UsageStore.menuCardInput` assembles cards for Settings, the live menu, and explicit account contexts. It owns common\n  quota, pace, warning-marker, and display-preference projection. Settings retains diagnostics and all usage lanes;\n  menus retain their cost display policy and account-scoped forecasts. An account context stays isolated even when empty.\n  Cards consume the reconciled Codex projection; the raw dashboard is not a separate model input.\n- App credential properties read directly from the config snapshot and delegate common string writes to the typed\n  `SettingsStore` config accessor, which owns normalization, persistence, and secret-update logging. Field activation\n  does not trigger credential loading. Legacy provider toggles are read only by the config migrator.\n- Token-cost publications own their snapshots, revisions, and source scope in one store. Raw and current-config readers\n  select from that same state while retaining confirmed-empty and unpublished distinctions. Provider settings and\n  plugin edits reuse Core's config upsert; their defaults and notification policies remain explicit.\n- The native status-item controller owns menu composition. Persistent refresh-row metrics are independent of menu\n  rendering, and screenshot fixtures exercise the active card views. Legacy menu-layout resolution retains its\n  rendering mode and projected layout without copying unused settings into a second state object.\n- `SettingsValue` owns whitespace and wrapping-quote normalization for config and provider settings. Readers retain\n  their credential precedence, endpoint validation, and provider-specific decoding.\n- `ISO8601DateParser` owns fractional-first internet timestamp parsing with a whole-second fallback. Provider readers\n  retain their text extraction, whitespace, numeric timestamp, and custom-format policies; each parse owns its formatter.\n- Kilo's CLI fetch strategy and organization discovery share `KiloBearerTokenResolver` for auth-file loading.\n  Vertex AI credential loading and renewal share display-only ID-token decoding; diagnostic fetch labels use\n  `ProviderDiagnosticFetchAttempt` across the app and CLI.\n- Core owns status feed fetching, decoding, and status models through `ProviderStatusFetcher`; the app supplies\n  localized labels and component UI, and the CLI supplies its existing status payload and English labels.\n- `KeychainStringStore` owns generic-password operations for legacy credential migration. Provider adapters retain\n  their existing item names, prompt kinds, cookie validation, and cache lifetimes. Structured OAuth/cache stores\n  retain their separate persistence and authorization contracts.\n- Simple API-token providers use `ProviderFetchPlan.apiToken`; provider loaders retain endpoint-specific behavior\n  and optional-data rules. Strategies with credential provenance, region selection, or fallback policies stay explicit.\n- `ProviderCostSnapshot.spendLimitWindow` owns spend-budget quota projection. Antigravity's shared family classifier\n  owns ID-before-title precedence; visibility and compact widget selection retain their distinct unknown-family rules.\n- `TestProcessSafety` owns test-runner recognition; callers retain explicit headless and fixture-path policies.\n- `CheckedSum` owns integer overflow handling. Callers still decide whether absent or empty counts mean zero,\n  unavailable, or incomplete; persistent accumulators retain their own overflow state.\n- `BrowserCookieProfiles` merges browser stores within each profile using the existing expiry and store-priority\n  rules. Providers still validate their own sessions. `ExpiringValueCache` supplies their short-lived import caches,\n  with a separate cache instance per provider. `CookiePropertyJSON` preserves the legacy Date/URL marker encoding;\n  session stores retain their own file envelopes, expiration policies, and secure writes.\n- `OpenCodeWebParsing` owns workspace discovery and quota-candidate traversal for OpenCode and OpenCode Go. Each\n  supplies its own numeric/window decoder and required-lane policy. Alibaba Token Plan and Qwen Cloud share\n  `OneConsoleTokenPlanSnapshot` projection while retaining distinct public snapshot types and provider identities.\n- `StreamScanBuffer` supplies bounded overlap for Codex and Claude terminal-marker matching; command handling and\n  session lifecycle remain provider-specific.\n- Codex's persistent and one-shot PTY readers share `CodexStatusMarkers`, including the marker lengths used for\n  bounded overlap. Cursor-query handling remains part of each terminal loop.\n- `TTYCommandRunner` reports the captured POSIX error number and description when PTY allocation fails, so\n  descriptor exhaustion can be distinguished from other terminal allocation failures.\n- `UsageSnapshot.withAccountLabel` applies token-account fallback labels for the app and CLI while preserving the\n  provider's account ID and other identity fields. Codex visible-account labels keep their surface-specific policy.\n- Raw Chromium local-storage consumers share `ChromiumLocalStorageDiscovery`, retaining their own browser lists and\n  origin decoders. Both plugin engines use the manifest's management-auth eligibility policy after adapting their\n  engine-specific option values.\n- Codex RPC and OAuth spend limits use `CodexSpendControlNumber` while keeping their wire-field aliases separate.\n  Reset values retain truncation toward zero, with unrepresentable values omitted instead of trapping.\n- Claude's persistent CLI operations and serialized browser fetches each own an `AsyncOperationGate`; the shared\n  implementation retains FIFO ownership and the CLI cleanup path's explicit cancellation exception. CLI usage and\n  cards validate account/provider selection through `TokenAccountCLISelection` before resolving credentials.\n\nSee also: `docs/providers.md`, `docs/refresh-loop.md`, `docs/ui.md`.\n","frontmatter":{"summary":"Architecture overview: modules, entry points, and data flow.","read_when":["Reviewing architecture before feature work","Refactoring app structure, app lifecycle, or module boundaries"]},"isInternal":false,"tokens":2082,"sizeBytes":10958},{"name":"SKILL.md","path":".agents/skills/codexbar/SKILL.md","rawUrl":"https://raw.githubusercontent.com/steipete/CodexBar/HEAD/.agents/skills/codexbar/SKILL.md","title":"codexbar","category":"anthropic-skill","format":"markdown","content":"---\nname: codexbar\ndescription: \"CodexBar read. Provider usage, limits, credits, config health. JSON. No writes.\"\n---\n\n# CodexBar\n\nRead CodexBar. Never mutate config/auth.\n\n## Run\n\n```bash\nskill=\"${CODEX_HOME:-$HOME/.codex}/skills/codexbar\"\n\"$skill/scripts/codexbar\" doctor\n\"$skill/scripts/codexbar\" providers\n\"$skill/scripts/codexbar\" usage\n\"$skill/scripts/codexbar\" usage --provider codex\n\"$skill/scripts/codexbar\" usage --all\n```\n\nAll stdout: JSON. Upstream CodexBar shape kept. Less drift, fewer tokens.\n\n## Rules\n\n- Start `doctor` when install/config unknown.\n- `usage` reads enabled providers. Prefer this.\n- `usage --provider ID` reads one provider.\n- `usage --all` expensive; use only when needed.\n- Identities hidden by default. `--include-identities` only when user explicitly needs them.\n- Secrets always hidden.\n- Helper read-only: fixed allowlist only. No config writes, auth repair, enable/disable, key storage.\n- Timeout means upstream stuck. Narrow provider or raise `CODEXBAR_TIMEOUT` (default 120 seconds).\n\n## Binary\n\nAuto-find: `CODEXBAR_BIN`, PATH, app bundle, Homebrew cask. If missing: open CodexBar, Preferences > Advanced > Install CLI; or set `CODEXBAR_BIN`.\n\nEach stdout/stderr stream capped at 1 MiB while fully drained. Timeout kills process group.\n","frontmatter":{"name":"codexbar","description":"CodexBar read. Provider usage, limits, credits, config health. JSON. No writes."},"isInternal":false,"tokens":331,"sizeBytes":1278},{"name":"openai.yaml","path":".agents/skills/qa-test/agents/openai.yaml","rawUrl":"https://raw.githubusercontent.com/steipete/CodexBar/HEAD/.agents/skills/qa-test/agents/openai.yaml","title":"Agents Skill","category":"anthropic-skill","format":"yaml","content":"# Agents Skill\n\nPath: `.agents/skills/qa-test/agents/openai.yaml`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/steipete/CodexBar/HEAD/.agents/skills/qa-test/agents/openai.yaml)","tokens":300,"sizeBytes":195},{"name":"api-specs.md","path":".agents/skills/qa-test/references/api-specs.md","rawUrl":"https://raw.githubusercontent.com/steipete/CodexBar/HEAD/.agents/skills/qa-test/references/api-specs.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# References Skill\n\nPath: `.agents/skills/qa-test/references/api-specs.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/steipete/CodexBar/HEAD/.agents/skills/qa-test/references/api-specs.md)","tokens":300,"sizeBytes":209},{"name":"SKILL.md","path":".agents/skills/qa-test/SKILL.md","rawUrl":"https://raw.githubusercontent.com/steipete/CodexBar/HEAD/.agents/skills/qa-test/SKILL.md","title":"qa-test","category":"anthropic-skill","format":"markdown","content":"---\nname: qa-test\ndescription: \"CodexBar live QA/e2e testing: run provider usage matrix checks, validate real app config, use Peekaboo for menu proof, use Browser Use/official docs for API spec or logged-in dashboard checks, and handle 1Password credentials safely.\"\n---\n\n# CodexBar Live QA\n\nUse for live provider testing, release smoke tests, menu verification, or debugging “provider works/fails” reports.\n\n## Rules\n\n- Work from the CodexBar repo checkout.\n- Use the packaged CLI first: `CodexBar.app/Contents/Helpers/CodexBarCLI`.\n- Do not use `CodexBar.app/Contents/MacOS/codexbar`; that is the app binary and may appear to hang as a CLI.\n- Never run broad `env`, `set`, or secret regex dumps.\n- Use `$one-password` for secrets: all `op` commands inside one persistent tmux session, service account first, no raw secret output.\n- Treat browser-cookie/keychain flows as prompt-risky. Prefer CLI/API-token checks and `KeychainNoUIQuery`-safe tests unless the user explicitly requested live UI.\n- For current API behavior, browse official provider docs only.\n\n## CLI Matrix\n\nRun the bundled script:\n\n```bash\n.agents/skills/qa-test/scripts/live_provider_matrix.sh --enabled\n```\n\nUseful modes:\n\n```bash\n.agents/skills/qa-test/scripts/live_provider_matrix.sh --provider all\n.agents/skills/qa-test/scripts/live_provider_matrix.sh --providers openai,zai,deepseek\n.agents/skills/qa-test/scripts/live_provider_matrix.sh --default\n```\n\nInterpretation:\n\n- `--enabled` asks `CodexBarCLI config providers` for enabled providers, honoring `CODEXBAR_CONFIG` and default toggles.\n- `--default` runs the app-facing default command with no provider override.\n- `--provider all` forces every registered provider and is expected to fail for providers without sessions/keys.\n- A green app config needs `--enabled` and `--default` clean; `--provider all` is a discovery/triage tool.\n\n## Config QA\n\nValidate config:\n\n```bash\nCodexBar.app/Contents/Helpers/CodexBarCLI config validate\nstat -f '%Lp %N' \"$HOME/.codexbar/config.json\"\n```\n\nRedact config shape:\n\n```bash\njq '(.providers // []) |= map(.apiKey = (if .apiKey then \"<redacted>\" else .apiKey end) |\n  .secretKey = (if .secretKey then \"<redacted>\" else .secretKey end) |\n  .cookieHeader = (if .cookieHeader then \"<redacted>\" else .cookieHeader end) |\n  (if .id == \"stepfun\" and has(\"region\") then .region = \"<redacted>\" else . end) |\n  .tokenAccounts = (if .tokenAccounts then (.tokenAccounts | .accounts = (.accounts | map(.token = \"<redacted>\"))) else .tokenAccounts end))' \\\n  \"$HOME/.codexbar/config.json\"\n```\n\nBefore editing config, make a backup:\n\n```bash\ncp \"$HOME/.codexbar/config.json\" \"$HOME/.codexbar/config.pre-qa-$(date +%Y%m%d%H%M%S).json\"\nchmod 600 \"$HOME/.codexbar\"/config.pre-qa-*.json\n```\n\n## Live Menu QA\n\nUse Peekaboo after CLI checks:\n\n```bash\npkill -x CodexBar || pkill -f 'CodexBar.app/Contents/MacOS/CodexBar' || true\nopen -n \"$PWD/CodexBar.app\"\npeekaboo menu list-all --json | rg -i 'codexbar'\npeekaboo menu click-extra --title codexbar-merged --json\nscreencapture -x /tmp/codexbar-live-menu.png\n```\n\nCrop top-right menu if needed:\n\n```bash\nsips --cropToHeightWidth 900 340 --cropOffset 20 2650 /tmp/codexbar-live-menu.png \\\n  --out /tmp/codexbar-live-menu-crop.png >/dev/null\n```\n\nVerify visually with `view_image`. Confirm provider tabs/rows match enabled config and no failing provider dominates the first screen.\n\n## Browser Use\n\nUse `$browser-use` only when a logged-in dashboard, API key page, or provider docs need browser/profile state.\n\nExisting Chrome path:\n\n```bash\nmcporter call chrome-devtools.list_pages --args '{}' --output text\nmcporter call chrome-devtools.navigate_page --args '{\"url\":\"https://provider.example\"}' --output text\nmcporter call chrome-devtools.take_snapshot --args '{}' --output text\n```\n\nIf Browser Use is unavailable, say so and use web search for public official docs; do not substitute isolated Playwright for login/profile-dependent pages.\n\n## Fix Triage\n\n- Missing auth/session: configure key/session if available; otherwise leave provider disabled or report blocked auth.\n- Wrong provider API/spec: inspect official docs, then patch fetcher/settings/tests.\n- Provider key exists but live API rejects it: keep key stored if useful, disable provider if the menu would show a persistent error.\n- User-facing behavior changes need `CHANGELOG.md`.\n- Code fixes need focused tests, `make check`, `$autoreview`, and live CLI proof before landing.\n\n## Known CodexBar QA Notes\n\n- OpenAI Admin API key is the useful usage provider key. Project `OPENAI_API_KEY` values can fail legacy credit-balance fallback with 403.\n- Deepgram usage requires a key/project with Management API permissions; transcription-only keys can return 403.\n- Groq usage uses the Prometheus metrics API, not ordinary inference endpoints.\n- MiniMax pay-as-you-go API keys and Token Plan/Coding Plan keys are different; wrong key kind can leave usage unavailable.\n","frontmatter":{"name":"qa-test","description":"CodexBar live QA/e2e testing: run provider usage matrix checks, validate real app config, use Peekaboo for menu proof, use Browser Use/official docs for API spec or logged-in dashboard checks, and handle 1Password credentials safely."},"isInternal":false,"tokens":1227,"sizeBytes":4928},{"name":"SKILL.md","path":".agents/skills/release-codexbar/SKILL.md","rawUrl":"https://raw.githubusercontent.com/steipete/CodexBar/HEAD/.agents/skills/release-codexbar/SKILL.md","title":"release-codexbar","category":"anthropic-skill","format":"markdown","content":"---\nname: release-codexbar\ndescription: \"CodexBar release: versioning, notarization, appcast, Homebrew, post-release bump.\"\n---\n\n# CodexBar Release\n\nUse for releasing signed/notarized macOS apps, especially repos with Sparkle appcasts and Homebrew casks.\n\n## Start\n\n1. Work from the app repo unless asked otherwise.\n2. Check repo state, current version, latest tag/release, and release docs/scripts.\n3. Confirm `CHANGELOG.md` is complete, user-facing, deduped, and dated for the release.\n4. Prefer the repo release script; patch small script/test blockers instead of bypassing the release path.\n5. Never print key material. Keep 1Password references and local key paths as references only.\n6. Load `$release-private` if it exists before resolving Peter-owned credential locators.\n\n## Key Material\n\nUse `$one-password` for secret handling. `op` only in tmux/persistent shell; no broad `env`, `set`, `export -p`, or secret scans.\n\nKnown App Store Connect shape:\n\n- fields: `private_key_p8`, `key_id`, `issuer_id`\n- keep all three fields from the same 1Password item; do not mix with stale values from `~/.profile`\n- resolve Peter-owned item refs from `$release-private`\n\nKnown Sparkle key:\n\n- resolve the private key file from `$release-private`\n- pass as `SPARKLE_PRIVATE_KEY_FILE`\n\nSafe env file pattern:\n\n```text\nAPP_STORE_CONNECT_API_KEY_P8=<1Password ref from release-private>\nAPP_STORE_CONNECT_KEY_ID=<1Password ref from release-private>\nAPP_STORE_CONNECT_ISSUER_ID=<1Password ref from release-private>\nSPARKLE_PRIVATE_KEY_FILE=<path from release-private>\n```\n\nRun with `op run --account my.1password.com --env-file <file> -- <script>`, then delete the temp env file.\n\n## CodexBar\n\nPaths:\n\n- repo: `~/Projects/codexbar`\n- release script: `Scripts/release.sh`\n- signing/notarization: `Scripts/sign-and-notarize.sh`\n- appcast: `Scripts/make_appcast.sh`, `appcast.xml`\n- release assets: `CodexBar-macos-universal-<version>.zip`, `CodexBar-macos-universal-<version>.dSYM.zip`\n- packaged app: `CodexBar.app`\n- version file: `version.env`\n- changelog: `CHANGELOG.md`\n- Homebrew tap: `~/Projects/homebrew-tap`\n- cask: `~/Projects/homebrew-tap/Casks/codexbar.rb`\n- formula: `~/Projects/homebrew-tap/Formula/codexbar.rb`\n- CLI release workflow: `.github/workflows/release-cli.yml`\n\nNormal release:\n\n```bash\ntmux new-session -d -s codexbar-release 'op run --account my.1password.com --env-file /tmp/codexbar-release-op.env -- Scripts/release.sh'\ntmux attach -t codexbar-release\n```\n\nIf notarization fails with `401 Unauthenticated`, rerun using all three App Store Connect fields from the 1Password item above. Mismatched `key_id` / `issuer_id` from `~/.profile` can cause this.\n\nIf widget metadata generation times out, `CODEXBAR_WIDGET_METADATA_TIMEOUT_SECONDS=600` is a known-good floor.\n\nCodexBar CLI tarballs are not produced by `Scripts/release.sh` itself. The GitHub release event triggers `.github/workflows/release-cli.yml`, which builds and uploads:\n\n- `CodexBarCLI-v<version>-macos-arm64.tar.gz`\n- `CodexBarCLI-v<version>-macos-x86_64.tar.gz`\n- `CodexBarCLI-v<version>-linux-aarch64.tar.gz`\n- `CodexBarCLI-v<version>-linux-x86_64.tar.gz`\n- matching `.sha256` files\n\nIf the workflow fails only in `update-homebrew-tap` with GitHub API rate limiting, the CLI assets may already be uploaded. Verify assets live, then update `Formula/codexbar.rb` manually from the tarball checksums.\n\n## Verify\n\nRelease is not done until the published chain checks out:\n\n```bash\ngh release view v<VERSION> --json tagName,name,isDraft,isPrerelease,url,assets,body\nScripts/check-release-assets.sh v<VERSION>\npython3 - <<'PY'\nimport xml.etree.ElementTree as ET\nns={'sparkle':'http://www.andymatuschak.org/xml-namespaces/sparkle'}\nroot=ET.parse('appcast.xml').getroot()\nitem=root.find('channel').find('item')\nenc=item.find('enclosure')\nprint(item.findtext('title'))\nprint(item.findtext('sparkle:version', namespaces=ns))\nprint(item.findtext('sparkle:shortVersionString', namespaces=ns))\nprint(enc.attrib.get('url'))\nprint(enc.attrib.get('length'))\nprint(bool(enc.attrib.get('{http://www.andymatuschak.org/xml-namespaces/sparkle}edSignature')))\nPY\ncodesign --verify --deep --strict --verbose=2 CodexBar.app\nspctl --assess --type execute --verbose CodexBar.app\n```\n\nFor Homebrew:\n\n```bash\nshasum -a 256 CodexBar-macos-universal-<VERSION>.zip\ncd /Users/steipete/Projects/homebrew-tap\npython3 .github/scripts/update_formula.py --formula codexbar --tag v<VERSION> --repository steipete/CodexBar --artifact-template 'CodexBarCLI-{tag}-{target}.tar.gz' --target-aliases 'darwin_arm64=macos-arm64,darwin_amd64=macos-x86_64,linux_arm64=linux-aarch64,linux_amd64=linux-x86_64'\nbrew fetch --cask --force --retry codexbar\nbrew fetch --formula --force --retry steipete/tap/codexbar\n```\n\nUpdate the cask when app zip assets exist. Update the formula only when standalone CLI tarballs for that version exist.\n\nTap audit can be noisy from unrelated formulae; keep evidence specific to the app cask.\n\n## Closeout\n\n1. Create/push tag and GitHub release through the release script.\n2. Verify appcast points to the new GitHub release asset with signature and length.\n3. Update/push the Homebrew cask if the app zip changed.\n4. Bump the app repo to next patch `Unreleased`:\n   - `version.env`: next `MARKETING_VERSION`, next `BUILD_NUMBER`\n   - `CHANGELOG.md`: top `## <next> — Unreleased`\n5. Commit, push, then pull `--ff-only`.\n6. Restart the local app from the packaged bundle and verify the running bundle version.\n7. Check no release/notary/op temp sessions or temp env files remain.\n\nCodexBar restart:\n\n```bash\npkill -x CodexBar || pkill -f CodexBar.app || true\ncd \"$(git rev-parse --show-toplevel)\"\nopen -n CodexBar.app\n/usr/libexec/PlistBuddy -c 'Print :CFBundleShortVersionString' CodexBar.app/Contents/Info.plist\n/usr/libexec/PlistBuddy -c 'Print :CFBundleVersion' CodexBar.app/Contents/Info.plist\n```\n","frontmatter":{"name":"release-codexbar","description":"CodexBar release: versioning, notarization, appcast, Homebrew, post-release bump."},"isInternal":false,"tokens":1559,"sizeBytes":5874},{"name":"claude.md","path":"docs/claude.md","rawUrl":"https://raw.githubusercontent.com/steipete/CodexBar/HEAD/docs/claude.md","title":"docs - Claude Agent Guidelines & System Prompt","category":"claude-rule","format":"markdown","content":"---\nsummary: \"Claude provider data sources: OAuth API, web API (cookies), CLI PTY, and local cost usage.\"\nread_when:\n  - Debugging Claude usage/status parsing\n  - Updating Claude OAuth/web endpoints or cookie import\n  - Adjusting Claude CLI PTY automation\n  - Reviewing local cost usage scanning\n---\n\n# Claude provider\n\nEnable **Settings → Menu Bar → Show all accounts in Overview** to include this provider’s cached account\ncards in Overview. The existing account source and provider selection remain unchanged; see\n[Overview account rows](configuration.md#menu-bar-controls).\n\nThe **Plan Usage** submenu includes recorded remaining-quota burndown above utilization history,\nusing the same Session, Weekly, and Sonnet labels. See [recorded quota burndown](widgets/burndown-proof.md)\nfor capture-age semantics and the existing history retention/privacy behavior.\n\nOAuth history uses a stable account/profile identity after two stable credential observations corroborate that\nbinding. External token rotations therefore continue the same history after the new credential is corroborated.\nSaved token-scoped fragments with matching verified bindings migrate into that account's history on a successful\nsample. Unbound fragments, obsolete bindings, and other accounts stay separate; correcting a conflicting binding\npreserves its ambiguous old fragment without attributing it to the newly confirmed account. If that fragment was\nalready merged, the affected account bucket is preserved but hidden, and fresh samples start a new account scope.\nThis quarantine survives empty responses and restarts. Explicit OAuth tokens without Claude Code account evidence\nremain credential-scoped.\n\nClaude supports three usage data paths plus local cost usage. The main provider pipeline uses runtime-specific\nautomatic selection, but the codebase still has multiple active Claude `.auto` decision sites while the refactor is\npending. For the exact current-state parity contract, see\n[docs/refactor/claude-current-baseline.md](refactor/claude-current-baseline.md).\n\nWhen an Anthropic Admin API key is configured, Claude can also show organization-level spend/messages/tokens in the\nsame inline dashboard pattern used by the OpenAI API provider.\n\nIncomplete proxy records with an explicit null `stop_reason`, positive input, zero output, and no cache counters are excluded from token and cost totals until a completed record arrives. Their day and model stay visible with an **Incomplete** marker and an excluded-request count. Known usage remains a partial subtotal; an incomplete-only period stays unavailable rather than becoming zero. Older caches are rebuilt once to apply this distinction. Missing `stop_reason` alone retains compatibility with older complete logs.\n\nCompleted requests for models without a known price keep their token usage and report unpriced coverage. Usage & Spend retains the priced subtotal from the other requests and days as a lower bound; unknown prices do not become zero. Fully priced history keeps its usual estimated totals.\n\n## Data sources + selection order\n\n### Default selection (debug menu disabled)\n- If an Admin API key is configured, the Admin API strategy is used for Claude API spend/usage.\n- App runtime main pipeline: OAuth API → CLI PTY → Web API.\n- CLI runtime main pipeline: Web API → CLI PTY (Linux: Web API → OAuth API → CLI PTY).\n  Since 0.78.0, Auto `usage` first uses the active slot when the claude-swap integration is enabled;\n  explicit source/account selection bypasses it. See [CLI precedence](cli.md#command).\n- Explicit picker modes (OAuth/Web/CLI) bypass automatic fallback.\n- Explicit OAuth retains the last successful quota measurement through recognized temporary network failures,\n  including localized DNS/offline errors. Its original timestamp remains visible; authentication rejection still\n  follows the existing invalidation and credential-owner rules.\n- A lower-level direct Claude fetcher still contains a separate `.auto` order. That inconsistency is tracked in\n  [docs/refactor/claude-current-baseline.md](refactor/claude-current-baseline.md).\n\nUsage source picker:\n- Preferences → Providers → Claude → Usage source (Auto/OAuth/Web/CLI).\n\nWhen an account-widget identity check or successful web enrichment returns an OAuth profile, its same-token email is\nretained for display even if the profile lacks the organization needed to verify widget ownership. A failed\noptional profile check leaves successful usage available. Ordinary OAuth refreshes also start one background\nprofile lookup per credential, bounded to two seconds, without waiting for it to publish quota usage. The first\nrefresh may omit the email while that request is in flight; later refreshes reuse its result, including a failed\nlookup, until the token changes or CodexBar restarts. This process-local display cache cannot establish widget\nownership. Organization UUIDs are ownership evidence, not organization display names.\n\nAdmin API key setup:\n- Preferences → Providers → Claude → Admin API key, stored in `~/.codexbar/config.json`.\n- CLI/env: `printf '%s' \"$ANTHROPIC_ADMIN_KEY\" | codexbar config set-api-key --provider claude --stdin`.\n- Token accounts can also hold `sk-ant-admin...` keys; they route to the Admin API instead of cookie/OAuth usage.\n- Environment fallback: `ANTHROPIC_ADMIN_KEY`.\n\n## Usage Dashboard\n\nThe default and API-account Usage Dashboard action opens [Platform usage](https://platform.claude.com/usage). Accounts recognized as Claude subscriptions open [Settings → Usage](https://claude.ai/settings/usage). This action does not change the configured usage source.\n\n## Admin API\n- Key prefix: `sk-ant-admin...`.\n- Endpoints:\n  - `/v1/organizations/cost_report`\n  - `/v1/organizations/usage_report/messages`\n- Output:\n  - Today/7d/30d spend and message/token summaries.\n  - Inline 30-day dashboard chart when daily buckets are present.\n  - Identity login method: `Admin API`.\n\n### Optional workspace spend\n\nEnable **Show workspace spend** in Settings → Providers → Claude, set `claudeWorkspaceSpendEnabled: true` on the\nClaude provider config entry, or set `ANTHROPIC_ADMIN_WORKSPACE_SPEND=true`. It is off by default and applies only\nto the Admin API source.\n\nThe existing [cost report](https://platform.claude.com/docs/en/api/admin/cost_report/retrieve) request adds\n`group_by[]=workspace_id` alongside `group_by[]=description`; no extra request or credentials are required.\nThe organization totals, cost items, token summaries, and daily chart remain unchanged. When more than one workspace\nhas cost rows, **Workspace spend · 30d** shows up to 20 workspaces, highest spend first, over the same 30-day buckets\nas the organization total. Labels use workspace IDs; a null workspace is **Default**. Amounts are converted from\nAnthropic's USD cents to dollars. A single workspace keeps the existing organization view.\n\n## Recover usage when Claude is already signed in\n\nA working Claude Code login or Claude browser tab does not by itself confirm that CodexBar can read that\nsession. For example, OAuth can report missing credentials, the CLI probe can time out, and Web can report\n`No Claude session key found in browser cookies.` on the same machine. Diagnose the selected source before\nsigning out or replacing credentials.\n\nIf Claude works in Chrome but CodexBar cannot import its browser session:\n\n1. Open `https://claude.ai` in Chrome and confirm the intended account is signed in.\n2. In Settings → Providers → Claude, select **Web API (cookies)** and leave the cookie source on **Auto**.\n   This uses the browser session for session, weekly, and available model-specific quotas.\n3. In Settings → Advanced, confirm **Disable Keychain access** is off. Chromium cookie decryption needs\n   the browser's Safe Storage Keychain item; Claude's OAuth prompt policy controls a different credential.\n4. Explicitly retry the import from Terminal:\n\n   ```bash\n   codexbar cookie refresh --provider claude --allow-keychain-prompt\n   ```\n\n   Approve the expected macOS Keychain prompt for the installed CodexBarCLI and the browser's Safe Storage\n   item. The command never prints cookie values. A prior denial can suppress imports for six hours;\n   this explicit retry can bypass that cooldown, while ordinary refresh attempts may remain suppressed.\n   See [Keychain prompts](keychain-prompts.md) for permission details.\n5. Click **Refresh** in Claude's settings and confirm that **Updated just now** appears with current quota\n   bars and no fetch error. Local cost totals or last-known quota bars alone do not prove a successful refresh.\n\nThis recovers browser-session access; it does not repair missing OAuth credentials. If the refreshed session\ninstead reports a Cloudflare challenge, follow the network guidance under Web API below rather than repeating\nthe cookie import.\n\n## Passive CLI sessions and macOS sleep\n\nAfter each passive request in the reusable CLI session, and when that session closes, CodexBar sends SIGTERM\nonly to `/usr/bin/caffeinate` processes in the session's own process group. The session remains available for\nlater requests. There is no recurring process scan; other process groups and other executables are left alone.\nThis cleanup applies to the reusable PTY session, not the separate one-shot usage or auth-status commands.\n\n## Keychain prompt policy (Claude OAuth)\n\nIf CodexBar reports that its Claude OAuth cache is temporarily unavailable in macOS Keychain, check\nthat the login keychain is unlocked in Keychain Access, then retry Refresh. This guidance does not\nchange the existing prompt policy or switch to another credential source.\n\n- Preferences → Providers → Claude → Keychain prompt policy.\n- Options:\n  - `Never prompt`: never attempts interactive Claude OAuth Keychain prompts.\n  - `Only on user action` (default): interactive prompts are reserved for user-initiated repair flows.\n  - `Always allow prompts`: allows interactive prompts in both user and background flows.\n- Explicit Refresh can repair Claude OAuth Keychain access when direct-read consent is enabled and the policy\n  allows user prompts. Ordinary OAuth fetches remain noninteractive, including with `Always allow prompts`;\n  that policy still governs the existing delegated refresh and experimental reader paths.\n- After a missing-credentials failure, a newer Claude Code credential timestamp replaces the stale error with\n  **Claude Code credentials changed** and a **Refresh** instruction in the menu and OAuth CLI output. Click the\n  existing menu **Refresh** action to retry; direct-read consent and the prompt policy still control access.\n  The freshness probe reads only Keychain attributes with UI disabled and the credentials file's modification\n  time. It never requests the Keychain credential payload, launches Claude, or grants access in the background.\n  A changed timestamp can mean a login or token rotation; it does not prove the credential is valid. Custom\n  credential profiles use their own file timestamp, never the global Claude Code Keychain item. CodexBar saves\n  only the last failure time per profile so the guidance survives restarts and is shared with the bundled CLI;\n  a readable credential clears it. Auto continues to configured CLI/Web sources under their existing access gates.\n- This setting only affects Claude OAuth Keychain prompting behavior; it does not switch your Claude usage source.\n- The policy also applies to the experimental `/usr/bin/security` reader and delegated OAuth refresh through\n  `claude`: background operations that can prompt require `Always allow prompts`.\n- CodexBar's `Always allow prompts` permits future prompts; macOS's **Always Allow** grants access to the current\n  Keychain item. Claude Code can recreate `Claude Code-credentials` and reset that grant. An ACL entry still named\n  CodexBar does not prove that its stored code-signing requirement matches the running binary. `Only on user action`\n  reduces background interruptions but may require a manual Refresh to recover OAuth access. In #3798, a\n  before/after trace shows Claude Code preserving the decrypt ACL's CodexBar entry but removing CodexBar's Team ID\n  from the separate partition ACL. Decrypt-ACL preflight alone cannot establish partition authorization; repeated\n  manual grants therefore need not survive the next Claude Code refresh.\n- If Preferences → Advanced → Disable Keychain access is enabled, this policy remains visible but inactive until\n  Keychain access is re-enabled.\n\n### Debug selection (debug menu enabled)\n- The Debug pane can force OAuth / Web / CLI.\n- Web extras are internal-only (not exposed in the Providers pane).\n- CLI Web enrichment requires matching nonempty account emails; an organization display name alone cannot authorize\n  merging another session's optional usage or spend. OAuth enrichment retains verified organization-UUID matching.\n\n## OAuth API (preferred)\n- OAuth refresh form-encodes credential values, preserving literal plus signs and other reserved characters.\n- Expiry values outside the diagnostic integer range are reported as `out_of_range` without changing credential expiry or refresh decisions.\n- Credentials:\n  - Explicit OAuth environment override, when configured.\n  - CodexBar OAuth cache when available.\n  - File fallback: `~/.claude/.credentials.json`.\n  - Claude CLI Keychain bootstrap/repair fallback: `Claude Code-credentials`.\n- When a CodexBar-owned OAuth cache item's ACL rejects the current build, fresh credentials from an allowed source\n  can replace that cache item using no-UI deletion and creation. A locked or inconclusive Keychain is preserved;\n  failed ACL repairs back off for five minutes. This never deletes or recreates Claude Code's credential item.\n- If CodexBar's cache is temporarily unavailable, automatic refreshes can reuse an unexpired credential already in\n  memory beyond the normal 30-minute cache window, ahead of a stale credentials file. Each refresh retries the\n  persistent cache. Token expiry, profile changes, cache invalidation, and Never prompt still prevent reuse;\n  after a rejected cache write, the next refresh first clears the stale persistent entry, then reuses and persists\n  an unexpired in-memory credential even after 30 minutes once that cleanup succeeds. Extended reuse requires\n  evidence of that exact failed write and its original consent; an unrelated invalidation cannot authorize it.\n  Rejected writes during CodexBar-owned token refresh retain the same recovery, bound to the refreshed credential;\n  a delayed older write cannot replace a newer credential's recovery.\n  This does not discover an external login or enable additional background reads of Claude Code's Keychain item.\n- For the default CLI profile, expired cached or file credentials can adopt a fresh CLI Keychain token after file fallback, even when its fingerprint was already observed during an earlier repair. Existing direct-read consent, prompt policy, cooldown, one-minute freshness-check throttle, and noninteractive-read checks still apply. Custom profiles are not recovered from the unscoped global item, and CLI credentials are never rewritten by this synchronization. Background recovery still requires the Always allow prompts policy; the default Only on user action policy requires an explicit Refresh.\n- Credential selection does not rank unrelated sources by the largest `expiresAt`: expiry establishes validity,\n  not account identity or issuance order. A valid profile file remains ahead of Keychain bootstrap. Keychain candidates\n  are ordered by modification date (creation date as fallback); freshness sync reads only that newest item and never\n  rewrites Claude Code's credentials file. An expired default-profile record can be replaced even when the stored\n  Keychain fingerprint already matches, subject to the access gates above.\n- On Claude Code 2.1.x, `Claude Code-credentials` may contain only MCP server OAuth state (`mcpOAuth`) with no `claudeAiOauth`. CodexBar treats that as an OAuth configuration error, does not run background delegated `claude /status` refresh, and surfaces re-auth guidance. Use Web or CLI usage source, or restore a valid Claude OAuth keychain entry. See #1844.\n- Requires `user:profile` scope (CLI tokens with only `user:inference` cannot call usage).\n- Missing-scope errors require a Claude Code sign-in token with usage access. `claude setup-token` produces a token for model requests and is not a usage-scope recovery step ([Claude Code authentication](https://code.claude.com/docs/en/authentication#generate-a-long-lived-token)). Remove any configured OAuth token override before switching Claude Source to Web/CLI.\n- Endpoints:\n  - `GET https://api.anthropic.com/api/oauth/usage?cedar_ember=1` → usage and saved reset inventory.\n    HTTP 400/403 retries once without the optional query and with the legacy request identity; spending is never skipped.\n    Missing-profile-scope errors, 401, and 429 keep their normal handling without that retry.\n  - `GET https://api.anthropic.com/api/oauth/profile` → account identity used to verify that optional Web enrichment\n    belongs to the same Claude account.\n- Headers:\n  - `Authorization: Bearer <access_token>`\n  - `anthropic-beta: oauth-2025-04-20`\n  - Reset inventory uses `claude-cli/<detected-version> (external, cli)`; eligibility remains server-controlled.\n- Mapping:\n  - `five_hour` → session window.\n  - `seven_day` → weekly window; also becomes the primary fallback when `five_hour` is absent or has no utilization.\n  - `seven_day_sonnet` / `seven_day_opus` → model-specific weekly window.\n  - `limits[].weekly_scoped` → model-specific weekly windows; generic `All models` scopes stay in the main weekly row.\n    OAuth and Web share per-entry parsing: malformed siblings or optional fields do not hide valid model rows. Boolean and string percentages are ignored.\n  - The menu localizes scoped titles as a model name plus weekly duration; canonical snapshot and CLI titles remain unchanged.\n  - Automatic and Session + Weekly menu bar metrics fall back to the most constrained known scoped weekly window when the regular quota windows are missing. Unknown scoped measurements remain unavailable; Extra usage stays a spend-only fallback.\n  - `seven_day_routines` / `seven_day_cowork` → Daily Routines extra window.\n  - Claude Design/Omelette keys are ignored because Claude Design shares the main Claude usage limit.\n  - `extra_usage` → Extra usage cost (monthly spend/limit).\n- Preferences → Providers → Claude → Visible usage items lets you hide the Daily Routines row in menus, the Settings\n  preview, and Overview. The global optional credits and extra usage setting remains its master switch. Hiding this\n  row does not change fetching, history, notifications, widgets, model-scoped weekly limits, hooks, or CLI output.\n- Preferences → Providers → Claude → Show model-specific weekly usage in widgets controls model-scoped weekly quota\n  rows in desktop widgets. It is off by default; turning it on displays every known Claude window with a\n  `claude-weekly-scoped-` identifier (for example, Fable). Turning it back off also drops scoped rows that a previous\n  snapshot persisted. It does not change fetching, the menu, history, notifications, hooks, or CLI output.\n- Refreshing credentials for the same identified account preserves quota-threshold warning history. Verified\n  credential-owner/account bindings also preserve threshold history when active-account metadata temporarily\n  disappears or OAuth falls back to CLI. Threshold warnings re-arm after quota recovers above a threshold and fire\n  on a later downward crossing. Predictive warnings and quota-low hooks keep their existing source-scoped histories;\n  their baselines are not merged with threshold notification state. OAuth/CLI samples without a warning owner use\n  one stable unresolved-account scope, so credential rewrites preserve threshold crossings and predictive warnings\n  remain available. When a stable account identity or verified owner binding becomes available, its threshold scope\n  adopts the newest unresolved history. Later identity gaps reuse the last known account when the reset timestamp\n  is unchanged and remaining quota has not increased. Without that continuity, samples use the stable unresolved\n  scope; once its history has joined an account, subsequent gaps preserve already-fired thresholds rather than\n  starting a new warning episode on every refresh. The first independent unresolved sample can still issue an\n  initial warning. Unverified credential owners remain independent; changing such an owner can still\n  produce an initial warning because account continuity cannot be established.\n- Successful OAuth login enables Claude and preserves the selected usage source. With the default Auto source, OAuth\n  remains preferred when readable, while CLI/Web fallback stays available when OAuth credentials are not usable.\n- Claude Code periodically rotates its `Claude Code-credentials` Keychain item and can replace the ACL grant that\n  allowed CodexBar to read it. Auto treats that as a failed OAuth source, reuses a recent successful CLI result or\n  continues to CLI/Web, and does not misreport the existing credentials as missing. A manual Refresh can re-grant\n  Keychain access; selecting CLI or Web avoids the foreign-Keychain dependency.\n- When every live Auto source fails, CodexBar keeps the last captured session/weekly percentages from\n  `history/claude.json` visible as stale data and shows their capture age instead of blanking the quota bars.\n  Restored history and CLI-scraped percentages both show “Limited usage detail”: the warning describes reduced\n  fidelity, not the source of a historical capture. It does not change sign-in or refresh recovery actions.\n- Plan inference: `subscriptionType` is preferred when present; `rate_limit_tier` falls back to\n  Max/Pro/Team/Enterprise. When a Max `rate_limit_tier` carries a usage multiplier\n  (`default_claude_max_5x` / `default_claude_max_20x`), it is surfaced in the label as \"Max 5x\" / \"Max 20x\".\n\n## Web API (cookies)\n\n### Claude desktop app cookies (opt-in, macOS)\n\nChoose **Usage source → Web** and **Claude cookies → Automatic**, then enable **Use Claude desktop app cookies**.\nSign in to Claude.app first. This option is off by default. The equivalent provider config field is `\"claudeDesktopCookiesEnabled\": true`,\nalso honored by CLI usage. The desktop app source reads `~/Library/Application Support/Claude/Cookies` (or\n`Network/Cookies`) through SweetCookieKit, using `Claude Safe Storage` / `Claude Key` for encrypted values.\n\nDesktop cookie imports are exclusive: a missing or unreadable desktop session never falls back to a browser account.\nExplicitly selected token accounts keep their own credentials; this option applies to automatic-cookie fetches.\nUsage source **Auto** keeps its existing runtime-specific source order; choose **Web** to follow only the desktop session.\nCookies are reimported on each refresh so account switches made in Claude.app are observed; desktop sessions\ndo not read or write the browser cookie cache. The normal shared access gate still forbids prompts from background/settings\nwork, checks matching cookie hosts before Safe Storage access, permits at most one interactive read per manual\naction, and honors denial cooldowns. OAuth and Claude Code login sources are unchanged.\n\n- Optional subscription dates come from `GET /api/organizations/{org_id}/subscription_details` on `claude.ai`,\n  using an existing manual or cached session cookie. They do not come from the quota response or OAuth expiry.\n  `next_charge_at` / `next_charge_date` supply renewal; `plan_ending_at` / `plan_ending_before` supply paid-access\n  expiration and take precedence over renewal. Calendar-only dates stay calendar-only when displayed.\n- The menu and Settings preview reuse the shared subscription row. Missing, unavailable, or unrecognized billing\n  data does not invent a date or fail usage. CLI JSON exposes `subscriptionRenewsAt` / `subscriptionExpiresAt`\n  and a corresponding `...IsDateOnly: true` when the server only supplies a calendar date.\n- OAuth enrichment additionally verifies the OAuth profile's account and organization against the cookie session\n  before and after billing. OAuth credentials alone cannot fetch these dates. No cookie discovery, credential\n  repair, sign-in, or new Keychain access is performed for billing; cookie source Off disables it.\n- App and CLI use the same provider fetch and normal account-scoped publication. Billing has a separate two-second\n  total budget after quota succeeds; expiry, errors, or a changed verified owner leave successful quota intact.\n  No delayed billing task writes back into an already published snapshot, and dates are not carried across refreshes.\n- Availability is determined by the authenticated billing response, not the Pro/Max/Team/Enterprise plan label.\n  The fixtures cover the reported subscription schema, including cancellation and absent dates; they do not\n  establish that every plan or organization role can access this endpoint. Team/Enterprise billing access and\n  live cancelled subscriptions remain unverified. Quota resets and Extra usage balances are independent.\n- Session quota warnings ignore a weekly quota promoted into the primary field when the five-hour payload is missing. Existing session warning history stays tied to its account, and weekly warnings continue independently.\n- Preferences → Providers → Claude → Cookie source (Automatic or Manual).\n- Manual mode accepts a `Cookie:` header from a claude.ai request.\nEach Claude token account can optionally select a **Claude config directory** in Settings → Providers → Claude.\nWith Track costs enabled, the active account’s local token and API-price cost estimates scan only its `projects` history,\nwithout default, claude-swap, or Pi history. Selecting a directory declares that its history belongs to the account;\nCodexBar does not infer ownership or detect mixed/copied history beyond existing scanner deduplication. A missing or\nunreadable history directory leaves its estimate unavailable. Clear the mapping to restore the existing combined\nview; unmapped accounts and claude-swap retain their current behavior. The optional `claudeConfigDirectory` field\nis stored on the token account in the config file and does not change quota fetching or credential resolution.\n\n- Multi-account manual tokens: add entries to `~/.codexbar/config.json` (`tokenAccounts`) and set Claude cookies to\n  Manual. The menu can show all accounts stacked or a switcher bar (Preferences → Advanced → Display).\n- In Settings → Providers → Claude, **Edit** changes a saved account’s label and credential in place.\n  The credential field is masked and starts empty; leave it blank to keep the saved credential, or enter a\n  replacement and choose **Save**. **Cancel** discards the draft. Up/down controls persist account order in\n  the existing config file and menu switcher. Edits and moves preserve the selected account and account ID,\n  including its Claude config directory. Other providers with manual token accounts share the editor; editing\n  a dormant account does not select it.\n- Claude token accounts accept either `sessionKey` cookies or OAuth access tokens (`sk-ant-oat...`). OAuth-token\n  accounts route to the OAuth path and disable cookie mode; session-key or cookie-header accounts stay in manual\n  cookie mode. The exact edge-routing rules are documented in\n  [docs/refactor/claude-current-baseline.md](refactor/claude-current-baseline.md).\n- Cookie source order:\n  1) Safari: `~/Library/Cookies/Cookies.binarycookies`\n  2) Chrome/Chromium forks: `~/Library/Application Support/Google/Chrome/*/Cookies`\n  3) Firefox: `~/Library/Application Support/Firefox/Profiles/*/cookies.sqlite`\n- Domain: `claude.ai`.\n- Linux: the web source supports an explicitly configured manual `sessionKey` cookie. Automatic browser import,\n  including Firefox import, remains unavailable. This enables the same API request path as macOS; it does not\n  bypass Cloudflare challenges, refresh Claude Code OAuth credentials, or guarantee that a browser session will\n  work outside the browser. Use the OAuth source when the web request is challenged.\n- Linux CLI Auto mode: an existing valid manual cookie makes Web eligible ahead of CLI after upgrading.\n  Web success returns without launching Claude Code; authentication rejection or a Cloudflare challenge falls\n  back to an available CLI using the existing Auto policy. Cancellation stops without launching the CLI.\n  Explicit Web mode does not fall back. Use explicit OAuth mode for passive polling that must not launch the CLI.\n- Linux CLI Auto mode: the plan is Web → OAuth → CLI PTY. OAuth runs when Claude Code OAuth credentials\n  (`~/.claude/.credentials.json`) are usable without a prompt, so a logged-in user does not launch an interactive\n  `claude` session. Without usable OAuth credentials, the OAuth step is skipped and CLI remains the fallback.\n  OAuth request failures such as 401 or 429 also fall back to the CLI; cancellation stops without launching it.\n- Cookie name required:\n  - `sessionKey` (value prefix `sk-ant-...`).\n- Cached cookies: Keychain cache `com.steipete.codexbar.cache` (account `cookie.claude`, source + timestamp).\n  Reused before re-importing from browsers.\n- After replacing an expired cached cookie, failures from the recovered session retain their actual error type,\n  including temporary network failures, server outages, Cloudflare challenges, and cancellation. The original\n  sign-in error is retained only when browser recovery itself fails to find a usable session.\n- API calls (all include `Cookie: sessionKey=<value>`):\n  - `GET https://claude.ai/api/organizations` → org UUID.\n  - `GET https://claude.ai/api/organizations/{orgId}/usage?cedar_ember=1` → session/weekly/opus, plus limit-reset\n    grants in the `cedar_ember` block. Rejected requests, including ordinary 403 responses, retry once without\n    `cedar_ember=1`, so unsupported reset queries keep the usage windows.\n    Success, 401, 429, and recognized Cloudflare challenges retain their normal handling without a retry.\n  - `GET https://claude.ai/api/organizations/{orgId}/overage_spend_limit` → Extra usage spend/limit.\n  - `GET https://claude.ai/api/organizations/{orgId}/prepaid/credits` → remaining Usage credits balance.\n  - `GET https://claude.ai/api/account` → email + plan hints.\n- Outputs:\n  - Session + weekly + model-specific percent used.\n  - A missing Web session stays absent instead of becoming a synthetic 0% session; measured weekly and extra windows stay visible. Older cached placeholders remain readable.\n  - Daily Routines extra window when returned by the usage API.\n  - Extra usage spend/limit (if enabled). Compact Overview keeps this section when no measured quota bars exist, including Enterprise accounts with unavailable limits.\n  - Remaining Usage credits balance (if enabled). Custom menu bar layouts can show it with the **Balance** element\n    beside the quota windows; the default menu bar text keeps showing quota. Turning off **Show credits & extra\n    usage** hides it from the menu card and the Balance element immediately. Otherwise a fresh snapshot without a\n    successful prepaid credits response has no balance, while a failed refresh keeps the last snapshot and its balance.\n  - Account email + inferred plan.\n  - Limit Reset Credits (see below).\n- A Cloudflare challenge on `claude.ai` is a network-path restriction, not a stale-cookie signal. CodexBar keeps the\n  cached cookie and prior quota snapshot, identifies the challenge, and links to Settings. Select OAuth for live\n  quota windows on that network (the web-only Usage credits balance is unavailable), or try a different network.\n  Explicit Web mode remains terminal and never reads OAuth credentials as a fallback.\n- Limit Reset Credits (\"Reset for free\" in Claude Settings > Usage), Web and OAuth sources:\n  - These are saved resets a user can redeem, separate from the session and weekly reset timestamps already\n    supplied by Web, OAuth, and CLI. Existing cookie settings and source selection govern all Web access; this\n    feature does not enable cookies, broaden browser discovery, or initiate Web enrichment.\n  - Read from `cedar_ember` in the same usage response as the session and weekly windows, so they share its session\n    and organization. Observed on a personal Pro/Max account; Team and Enterprise organizations are not verified.\n  - The count sums `resets_left` over grants that, at refresh time, are not paused, have started, and have not\n    expired. An expiry that passes before the next refresh drops that reset from the display. `usable_now` is not\n    consulted, so a saved reset still counts while Claude gates its use.\n  - Requires `eligible: true`. A grant with an unreadable `resets_left`, `resets_total`, `paused`, `starts_at`, or\n    `ends_at` is dropped. More than 50 available resets or more than 200 grant records show nothing. The usage\n    windows are unaffected either way.\n  - Menu: a live `Limit Reset Credits` section with its own visibility choice. An upgrade preserves a previously\n    disabled global optional-usage preference by initially hiding this section; it can then be enabled independently.\n    CLI and `codexbar serve`: a `Limit Reset Credits` row in `usage.details` (`N available`, next expiry).\n  - Live-only: grant IDs are never decoded, the usage request skips the URL cache, and cached or synced snapshots do\n    not restore the inventory. A reset used on claude.ai disappears at the next successful refresh.\n  - Source precedence stays unchanged: credits appear only from the source supplying the primary usage snapshot. CLI\n    does not report saved reset credits, and optional Web enrichment never adds Web credits to another source,\n    even when the account matches. The menu replaces the generic details row with one shared reset-credit section.\n    CodexBar never redeems a reset; use Claude on the web or Claude Desktop.\n\nEnterprise spend details depend on the selected source. Auto stops at the first successful OAuth/CLI/Web result;\nit does not import a missing monthly spend cap from a different session. Select **Web API (cookies)** for\nbrowser-only billing details, and enable **Show credits and extra usage** to display them.\n\n## Extra usage rollover\n\nFor Claude spend-only accounts, positive monthly caps use the shared usage-window presentation: one Meter bar,\nwith Auto, Extra usage, and the card using the same cap percentage and reset. The card retains its spend/limit and\nprepaid-balance caption. Accounts with quota windows keep their separate Extra usage section, including in the\nmenu-card-sections layout. Uncapped balances keep their compact Credits row; Admin API spend is unchanged.\nExtra usage still follows its existing visibility choice and the global optional-usage switch. Pace follows the\nexisting pace setting, independently of redeemable Limit Reset Credits.\n\nClaude does not report the cap reset. CodexBar infers midnight UTC on the first of the next month from the cost\nobservation, applying [Anthropic's documented spend-limit period](https://platform.claude.com/docs/en/manage-claude/spend-limits-api)\nto all Claude monthly caps. Reset text uses local time (November 1 UTC can appear as October 31 in the Americas);\nthe metric tooltip identifies the inference. Shared calendar-month pace uses the actual 28–31-day month.\nChanging a cap changes the percentage, not its rollover. A stale observation does not advance into a new month.\n\n## Cloud-session credits\n\n- OAuth and Web usage responses can supply promotional cloud-session credit in `iguana_necktie`.\n  CodexBar shows a separate **Cloud credits** balance row in the menu and a detail section in the CLI when\n  optional credits/extra usage is enabled. The menu row matches the prepaid **Credits** row and shows only the\n  remaining balance; the allowance, progress, and expiry are available in CLI output and the widget snapshot's\n  optional `details` field. CLI JSON exposes the section through `usage.details`, including numeric progress and\n  remaining dollars. No additional request, login, or browser\n  discovery is needed for these credits.\n- `limit_dollars`, `used_dollars`, and `remaining_dollars` are already USD amounts. They are never divided by 100,\n  added to prepaid Extra usage, counted as local spending, or used for quota pacing. The reported remaining amount\n  wins when present; otherwise it is derived from the allowance and reported used dollars.\n- `resets_at` denotes expiration, not a recurring quota reset. The section shows an absolute UTC timestamp\n  (`Z`); cached details retain the last observation and its expiry. Exhausted credits retain a zero balance;\n  credits already expired at observation or locked by the provider are labeled expired/unavailable. The menu\n  also labels a cached balance expired once its stored expiry has passed.\n- Missing or malformed credit blocks omit this section without failing ordinary usage. The response determines\n  availability, without a Pro/Max plan-name gate. CLI-probe-only results do not include cloud credits, and optional\n  Web enrichment preserves the primary source's credits rather than importing Web credits into OAuth/CLI results.\n- Settings → Providers → Claude → Visible usage items can hide **Cloud credits** independently. The optional\n  credits/extra usage setting remains its master switch; the individual visibility choice does not change CLI output.\n- The response shape is based on [Pane's implementation and live-shape test](https://github.com/ItsJazii/pane/blob/beb4bbfd4e7c776d970a56d254e8cce4d61154d9/src-tauri/src/providers/claude.rs#L443).\n  CodexBar's fixtures validate parsing and presentation; they are not independent live Pro/Max verification.\n\n## claude-swap accounts (opt-in)\n\nThe accepted multi-account design in\n[claude-multi-account-and-status-items.md](claude-multi-account-and-status-items.md).\n\n- Setup: Preferences → Providers → Claude → \"Read accounts from claude-swap\", then set the path to the\n  [`cswap`](https://github.com/realiti4/claude-swap) executable (for example `~/.local/bin/cswap`) in the field\n  directly beneath the enabled toggle. The path field and its help are hidden while the integration is off.\n- tcr source: [tcr](https://github.com/dhkts1/teamclaude-rs) prints the same list schema v1 from `tcr list --json`.\n  Pick \"tcr\" in the Source picker shown while the integration is on (config key `claudeSwapSource: \"tcr\"`) and set\n  the path to the `tcr` executable. CodexBar then runs `tcr list --json` and `tcr --version` with the same bounds,\n  parser and isolation rules; cards are labelled `tcr` and identity is `tcr:<slot>`. tcr pools its accounts and\n  routes each request itself, so its list reports no active account and `supportsAccountSwitching: false`: no row\n  offers Switch or Re-authenticate, and CodexBar never sends tcr a switch command. Late list results from a\n  previously selected source are discarded before publishing cards, refresh times, or forecast history, including\n  when the initial history load is still pending.\n- tcr forecast history requires both a nonempty email and `organizationUuid` in the list row. When both are\n  supplied, it uses the same normalized email/organization hash as claude-swap; display names and slot numbers\n  never substitute for an organization UUID. The current tcr contract fixture omits `organizationUuid`, so those\n  accounts show usage without a history-based forecast.\n- Version detection retries after a failed or cancelled startup probe; replaced refreshes cannot overwrite a newer\n  result, and disabling the adapter or changing its executable clears the previous detected version.\n- Behavior: on each Claude refresh, CodexBar runs `cswap --list --json` independently of the ambient Claude fetch (no\n  shell, fixed arguments, bounded runtime and output), requires `schemaVersion == 1`, and parses only slot number,\n  active state, usage status, email, `organizationUuid`, display-only `organizationName` (always present, may be empty),\n  optional display-only `alias` when non-empty, the 5-hour/7-day windows, and optional display-only model-scoped\n  weekly windows from `usage.scoped`, optional `usage.spend`, and source measurement times. Identity stays\n  `claude-swap:<slot>`; organization name and alias are never\n  used as identity. When two or more slots share an email, cards append ` · organizationName` or ` · Account N`;\n  a user-chosen cswap alias replaces that label. Unique emails stay email-only.\n- Menu and terminal cards can show the source's `lastGoodUsage` when live usage is unavailable. Its required\n  `lastGoodFetchedAt` remains the measurement time, and the card shows a last-known marker and capture age alongside\n  the diagnostic. Terminal cards also show the capture timestamp. Malformed additive spend or last-good fields do not\n  discard valid live quota windows. The free-form row `message` is not parsed.\n  Brief terminal output keeps the diagnostic and excludes historical metrics from its warnings and next-reset summary.\n- Last-known usage keeps its provenance through quota retention and cache reload. It never drives the menu-bar icon,\n  whose compact display cannot show its age. Compact account rows show the capture age and do not recommend or fold\n  last-known rows into the ready-account group. Explicit source measurements take precedence over locally retained\n  quota windows.\n- Slots excluded from claude-swap's automatic rotation are marked `(disabled)` but remain valid explicit switch\n  targets. The active account label is emphasized.\n- Display: when claude-swap reports more than one account, its accounts replace ambient/token-account Claude cards.\n  The app honors **Menu → Multi-account layout**: Segmented shows account buttons and one active account card;\n  pending or failed switches show the requested account's details while the active marker stays source-owned.\n  Expired or otherwise unavailable accounts remain inspectable without activation; selecting the active account\n  returns to its card. If the adapter reports no active account, the menu says so instead of selecting the first row.\n  Buttons wrap into two rows above three accounts. Hide Personal Info uses stable `Account N` slot labels across\n  segmented buttons, native account cards, compact menu rows, and accessibility text, including unavailable accounts.\n  These numbers come from validated claude-swap slots and remain stable when accounts are reordered; aliases,\n  organization names, and email addresses stay hidden.\n  Stacked shows one card per account (active account first, then numeric slot). With four or more\n  accounts the stacked menu switches to a compact layout (`AccountMenuLayoutPlanner`): the active account keeps its full\n  card, inactive accounts become one-line rows sorted by remaining headroom (most constrained first, red/amber below\n  50%/10% left, a star on the healthiest activatable account), and healthy rows fold behind a \"N more accounts ready\"\n  summary row. Clicking a compact row expands that account's full card; clicking an inactive card collapses it again.\n  This choice is remembered locally across menu opens and app restarts. The summary row reveals the hidden rows for\n  the current menu session. `codexbar cards` keeps the full per-account output. The same compact layout applies to\n  every stacked multi-account list (token accounts on any provider, and flat Codex account lists; workspace-grouped\n  Codex lists keep their sectioned stacked layout). To use this\n  presentation with one account, enable “Show account card when only one account is available” or set\n  `claudeSwapShowSingleAccount: true` on the Claude provider in the resolved config file (normally\n  `~/.config/codexbar/config.json`; legacy installs may use `~/.codexbar/config.json`). The option defaults off,\n  zero accounts still use the ambient presentation, and account identity is `claude-swap:<slot>`, never the display\n  email.\n- Session forecasts use each account's own recorded usage. History keys hash the normalized email and organization\n  UUID from the existing list response, so moving slots preserves history and the same email in different workspaces\n  stays separate. Empty organization UUIDs identify personal accounts; an email change starts a new history.\n  Raw emails and organization UUIDs are not saved in history. Fresh samples retain their source timestamps and the\n  shared 17,520-sample limit per series; last-known or failed rows do not add samples. Ambient Claude history is separate.\n- Provider widgets follow the active claude-swap account under the same multiple-account/single-account presentation\n  rule. Successful list refreshes and clearing the adapter publish a widget snapshot even with account widgets off.\n  Retained quota keeps its original measurement time and is bound to the slot's opaque owner fingerprint; unavailable\n  or replaced accounts never borrow another account's quota. Local cost history remains combined across Claude homes.\n- Terminal scope: cards expands the account list, while Auto `codexbar usage` follows its active slot on every\n  supported CLI platform (including single-slot lists). An explicit\n  Claude provider or `--source auto` remains eligible, while `--account`, `--account-index`, `--all-accounts`, and\n  explicit non-auto source flags bypass the adapter. A configured non-auto source also bypasses it for `usage`.\n  Unavailable active-slot usage falls back to ordinary source selection with an explicit adapter fallback source label;\n  the returned identity belongs to that fallback source. Serve `/usage`/`/cost` remain unchanged,\n  while `codexbar dashboard` and `GET /dashboard/v1/snapshot` additionally nest one entry per swap account in the\n  Claude provider row, with full identity by default or redacted email local parts when `--identity redacted` is set.\n- Isolation: CodexBar never reads claude-swap or Claude Code credential storage for this feature; the\n  subprocess handles its own credential access. In the app, adapter failures keep the last successful accounts as\n  stale data, surface the error in provider settings, and never affect the ambient Claude usage card. In terminal\n  cards, a list failure retains the current ambient output, adds a distinct `Claude (claude-swap)` footer entry, and\n  exits non-zero.\n- Sentinel statuses (`token_expired`, `relogin_required`, `api_key`, `keychain_unavailable`, `no_credentials`,\n  `foreign_credential`, and unknown future values) retain per-account diagnostics. Full cards can show explicitly\n  reported last-known usage alongside its age; without it they remain notes-only. Brief terminal cards keep the\n  diagnostic. When no explicit last-good measurement is supplied and a window is still at 100%, CodexBar keeps that slot's last\n  projected usage bars and names the exhausted window (5-hour session, 7-day weekly, and/or a scoped model such as\n  Fable) plus its reset time — not \"Usage fetch failed.\" A first refresh that is already `unavailable` with no\n  retained windows says usage is unavailable, without assuming why the source could not fetch it. Active rows are marked `[active]`; no claude-swap row infers\n  a plan badge.\n- Read-only adapters may set top-level `supportsAccountSwitching: false` in their schema-v1 list response. Usage,\n  account details, and active markers remain visible, while switching and re-authentication actions are suppressed.\n  Omitting the capability preserves existing switching behavior; a present value must be a JSON boolean.\n- Switching: an inactive account with usable source credentials shows “Switch Account…”. Clicking it runs exactly\n  `cswap --switch-to <slot> --json`, validates the versioned result and requested slot, then refreshes both ambient\n  Claude usage and every claude-swap account card. Switches are serialized; no automatic switching occurs. While\n  reconciling, the ambient Claude refresh is given five seconds to finish; a stalled refresh continues in the\n  background while switching waits for the adapter's active-account list, so it cannot leave the account chips inert.\n  A later switch can refresh the adapter list even when its ambient refresh is queued behind an earlier probe. While\n  claude-swap owns account presentation, the separate ambient OAuth action reads “Sign in with Claude Code…” and does\n  not add or switch a claude-swap account.\n- Expired, missing, unknown, or Keychain-inaccessible credentials stay non-actionable. A failed switch remains visible\n  on that account without discarding its last successful usage. A running Claude Code process can take up to the\n  claude-swap Keychain cache interval to observe the new account.\n- A `foreign_credential` row explains that the live credential belongs to another account. An inactive row can use\n  the existing explicit slot switch. An active row offers **Re-authenticate**, which runs the same\n  `cswap --switch-to <slot> --json` command to let claude-swap reconcile its own credential state, without `--force`. Clicking the active segment\n  still only inspects it; repair requires its explicit button.\n- Multiple claude-swap accounts—and a single account when explicitly enabled—take precedence over Claude\n  token-account presentation (stacked cards and the segmented switcher).\n\nPackaged synthetic proof (fake `cswap` executable, no real accounts or credentials):\n\n![Stacked claude-swap account cards](screenshots/claude-swap-accounts-synthetic-proof.png)\n\nModel-scoped weekly-window proof (synthetic data, no real accounts or credentials):\n\n| Before | After |\n| --- | --- |\n| ![claude-swap card before scoped windows](screenshots/claude-swap-scoped-before.png) | ![claude-swap card with a Fable scoped weekly window](screenshots/claude-swap-scoped-after.png) |\n\n## CLI PTY (fallback)\n- Runs `claude` in a PTY session (`ClaudeCLISession`).\n- The PTY and replay screen share a 160-column, ","frontmatter":{"summary":"Claude provider data sources: OAuth API, web API (cookies), CLI PTY, and local cost usage.","read_when":["Debugging Claude usage/status parsing","Updating Claude OAuth/web endpoints or cookie import","Adjusting Claude CLI PTY automation","Reviewing local cost usage scanning"]},"isInternal":false,"tokens":10466,"sizeBytes":50163},{"name":"copilot.md","path":"docs/copilot.md","rawUrl":"https://raw.githubusercontent.com/steipete/CodexBar/HEAD/docs/copilot.md","title":"docs - GitHub Copilot Context & Rules","category":"copilot-instructions","format":"markdown","content":"---\nsummary: \"Copilot provider data sources: GitHub device flow, Copilot internal usage API, and optional GitHub web budgets.\"\nread_when:\n  - Debugging Copilot login or usage parsing\n  - Updating GitHub OAuth device flow behavior\n---\n\n# Copilot provider\n\nCopilot uses GitHub OAuth device flow and the Copilot internal usage API for primary usage. Optional budget extras use GitHub web cookies only when enabled.\n\nCLI-only setups can run `codexbar config login --provider copilot` (or add `--json` for a stream of login\nevents). It uses the same device flow, issuer-aware account matching, and config-backed token accounts as\nthe app, without opening a browser or prompting for Keychain access. See [CLI configuration](cli-configuration.md#copilot-device-login).\n\n## Data sources + fallback order\n\n1) **GitHub OAuth device flow** (user initiated)\n   - Device code request:\n     - `POST https://github.com/login/device/code`\n   - Token polling:\n     - `POST https://github.com/login/oauth/access_token`\n   - Optional enterprise host:\n     - set Copilot `enterpriseHost` in `~/.codexbar/config.json` or the provider settings UI\n     - CodexBar normalizes values such as `https://octocorp.ghe.com/login` to `octocorp.ghe.com`\n     - device flow uses `https://<enterpriseHost>/login/...`\n     - identity lookup uses `https://api.<enterpriseHost>/user`, matching the usage API host\n     - default HTTPS ports and trailing host dots normalize to the same issuer; nondefault ports remain distinct\n   - Scope: `read:user`.\n   - Token stored in config:\n     - `~/.codexbar/config.json` → `providers[].apiKey` for `copilot`\n     - token accounts use `providers[].tokenAccounts`\n     - Legacy token matching honors a resolved stable GitHub user ID; display-label fallback is used only when token identity is unavailable and no verified match exists.\n     - Enterprise accounts match by API host and numeric user ID. They never adopt a public or unidentified legacy account merely because its login or label matches. The configured host still controls requests; stored account identifiers do not select an endpoint.\n     - Enterprise sign-in requires a resolved identity. A cancelled sign-in or a host change while it is pending leaves saved accounts unchanged.\n\n2) **Usage fetch**\n   - `GET https://api.github.com/copilot_internal/user`\n   - With an enterprise host, the API host is `api.<enterpriseHost>`.\n   - Headers:\n     - `Authorization: token <github_oauth_token>`\n     - `Accept: application/json`\n     - `Editor-Version: vscode/1.96.2`\n     - `Editor-Plugin-Version: copilot-chat/0.26.7`\n     - `User-Agent: GitHubCopilotChat/0.26.7`\n     - `X-Github-Api-Version: 2025-04-01`\n\n3) **Budget fetch** (optional GitHub web endpoint, best-effort)\n   - Available only for the public GitHub host. Enterprise quota fetches skip public identity and browser-cookie budget enrichment.\n   - Disabled by default. The Copilot provider's \"Budget extras\" setting must be enabled before CodexBar imports\n     github.com cookies or renders budget bars.\n   - Automatic cookie imports follow the shared supported-browser order and access gate; background imports never request Keychain permission.\n   - CodexBar asks the logged-in GitHub web endpoint for customer-scope budgets:\n     - `GET https://github.com/settings/billing/budgets?page=<page>&page_size=10&scope=customer`\n   - Headers:\n     - `Cookie: <github.com browser cookies>`\n     - `Accept: application/json`\n     - `X-Requested-With: XMLHttpRequest`\n     - `GitHub-Verified-Fetch: true`\n     - `X-Fetch-Nonce: <fresh nonce when available>`\n   - CodexBar first tries to read a fresh nonce from `https://github.com/settings/billing/budgets`, then calls the JSON\n     endpoint. If GitHub rejects the web request, CodexBar keeps the normal Copilot quota bars and omits budget bars.\n   - This is intentionally not the public GitHub REST billing API. The REST API did not expose the personal budget list\n     for the tested individual account.\n\n## Snapshot mapping\n- Primary: `quotaSnapshots.premiumInteractions` percent remaining → used percent.\n- Secondary: `quotaSnapshots.chat` percent remaining → used percent.\n- Extra: positive Copilot billing budgets from the GitHub web endpoint → `extraRateWindows`, only when \"Budget extras\"\n  is enabled.\n  - Product budget: `copilot`\n  - SKU budgets: `copilot_premium_request`, `copilot_agent_premium_request`, `spark_premium_request`\n- Seat AI credits: `quota_snapshots.premium_interactions.credits_used` → the shared \"Credits used\" provider-detail\n  row, shown only when it carries real signal (token-based billing, unlimited quota, nonzero credits, or a\n  configured seat entitlement) so a metered Pro/Individual seat never grows a permanent \"0 credits used\" row.\n  Deliberately not summed with `chat`/`completions` credits — GitHub can report the same pool under multiple\n  snapshot keys, and summing would double-count.\n- Reset dates are not provided by the API.\n- Plan label from `copilotPlan`.\n\n## AI credit entitlements\nGitHub does not publish an included-credit entitlement on any documented endpoint — all 8 billing endpoints plus\n`budgets`, `cost-centers`, and `usage/summary` were probed, and none returns a ceiling for the seat. The denominator\nis therefore user-entered:\n- Preferences → Providers → Copilot → \"Included AI credits (per seat)\"\n\nA configured entitlement turns the row into a progress bar (\"31 / 3000\") via the shared provider-detail row's\noptional progress ratio. Without one, the row stays plain text, because a bar would imply a limit CodexBar cannot\nactually know. Either way the row also carries the numeric credits used (`usageValue`), so editing or clearing\nthe entitlement rewrites the cached row (text ↔ bar) immediately, even when the follow-up refresh never lands\n(offline, token lost, 401).\n\nIn Automatic mode, the provider tab uses this configured seat-credit ratio when no metered quota window is\navailable. It follows the used/remaining preference. Existing quota windows retain priority, explicit metric\nselections retain their meaning, and a missing allowance leaves the tab's progress bar absent.\n\n## Manual seat credit allowance\n\nFor token-billed seats, **Seat AI credit allowance** optionally turns the menu card's Credits used text into a progress\nbar. Enter a positive monthly credit allowance; no ceiling is inferred from the plan. This is a local display preference,\nseparate from Budget extras, and does not set a GitHub billing limit. Icon, widget, and CLI quota windows are unchanged.\n\nEach saved account can override the legacy global allowance. Clearing an override restores that fallback, or returns to\ntext if no fallback exists. **Clear default allowance** removes the legacy fallback while retaining explicit account\noverrides; accounts inheriting that default return to text immediately, including while offline. Edits update the matching cached account without\nchanging its usage timestamp. Switching accounts preserves each account's usage; changing a credential or enterprise\nhost still invalidates its cached data. A refresh already in flight uses the current allowance when it publishes, so a\nlate successful response cannot restore a cleared default.\n\n## Key files\n- `Sources/CodexBarCore/Providers/Copilot/CopilotUsageFetcher.swift`\n- `Sources/CodexBarCore/Providers/Copilot/CopilotDeviceFlow.swift`\n- `Sources/CodexBarCore/Providers/Copilot/CopilotCreditEntitlementParser.swift`\n- `Sources/CodexBar/Providers/Copilot/CopilotLoginFlow.swift`\n- `Sources/CodexBar/CopilotTokenStore.swift` (legacy migration helper)\n","frontmatter":{"summary":"Copilot provider data sources: GitHub device flow, Copilot internal usage API, and optional GitHub web budgets.","read_when":["Debugging Copilot login or usage parsing","Updating GitHub OAuth device flow behavior"]},"isInternal":false,"tokens":1684,"sizeBytes":7580}],"systemPromptSnippet":"<yakaai_skills repo=\"steipete/CodexBar\">\n<!-- File: AGENTS.md (Tokens: ~1766 | Category: root-instruction) -->\n# Repository Guidelines\n\n## Project Structure & Modules\n- `Sources/CodexBar`: Swift 6 menu bar app (usage/credits probes, icon renderer, settings). Keep changes small and reuse existing helpers.\n- `Tests/CodexBarTests`: XCTest coverage for usage parsing, status probes, icon patterns; mirror new logic with focused tests.\n- `Scripts`: build/package helpers (`package_app.sh`, `sign-and-notarize.sh`, `build_icon.sh`, `compile_and_run.sh`). Read-only release helpers call `Scripts/mac-release`, which resolves `MAC_RELEASE_TOOL` or the shared `agent-scripts` checkout; CI owns publication.\n- `docs`: release notes and process (`docs/RELEASING.md`, screenshots). Root-level zips/appcast are generated artifacts—avoid editing except during releases.\n\n## Build, Test, Run\n- Dev loop: `./Scripts/compile_and_run.sh` kills old instances, builds, packages, relaunches `CodexBar.app`, and confirms it stays running; add `--test` for the sharded full suite.\n- Quick build/test: `swift build` (debug) or `swift build -c release`; `make test` for the sharded full suite.\n- Package locally: `./Scripts/package_app.sh` to refresh `CodexBar.app`, then restart with `pkill -x CodexBar || pkill -f CodexBar.app || true; cd /Users/steipete/Projects/codexbar && open -n /Users/steipete/Projects/codexbar/CodexBar.app`.\n- Release publication is owned by `.github/workflows/release.yml`; use its default dry-run dispatch to rehearse. Repo build/signing stays in `Scripts/sign-and-notarize.sh`; see `docs/RELEASING.md`.\n\n## Coding Style & Naming\n- Enforce SwiftFormat/SwiftLint: run `swiftformat Sources Tests` and `swiftlint --strict`. 4-space indent, 120-char lines, explicit `self` is intentional—do not remove.\n- Favor small, typed structs/enums; maintain existing `MARK` organization. Use descriptive symbols; match current commit tone.\n\n## Testing Guidelines\n- Add/extend XCTest cases under `Tests/CodexBarTests/*Tests.swift` (`FeatureNameTests` with `test_caseDescription` methods).\n- Swift Testing: prefer backticked sentence names; no camelCase.\n- Model names in tests/code: released models or clearly fictitious names only; never expose unreleased names.\n- Iterate with focused runs (`make test-fast FILTER='SuiteName'` or `swift test --filter ...`) covering the touched suites. For a local full run use `./Scripts/test.sh --direct-workers 8` (parallel isolated groups, ~10 min) instead of serial `make test` (~45 min). The full suite is not a handoff gate: run it in the background or leave it to PR CI; never block on it in the foreground.\n- While iterating, format/lint only touched files; run `make check` once before commit/handoff and fix reported issues.\n- Prefer CLI/focused tests over app-bundle live tests when behavior can be verified without relaunching CodexBar.\n- Never run tests/checks or ad-hoc validation that can display macOS Keychain prompts. Live provider probes, browser-cookie imports, `codexbar usage` against real accounts, and real SecItem reads must be explicitly requested; otherwise use parser tests, stubs, test stores, or `KeychainNoUIQuery`.\n- App-group migration tests must inject dictionary-backed defaults, both snapshot URLs, a synthetic home, and a contained recording FileManager. UUID defaults suites and Keychain isolation flags do not isolate defaults search domains or filesystem access. Ordinary SettingsStore tests must not discover shared defaults or run app-group migration.\n- macOS CI is brittle around headless AppKit status/menu tests. Prefer covering menu behavior through stable state/model seams (`MenuDescriptor`, `ProvidersPane`, `CodexAccountsSectionState`, etc.) instead of constructing live `NSStatusBar`/`NSMenu` flows unless the AppKit wiring itself is the thing under test.\n\n## CI Efficiency\n- Follow [the direct-test and SwiftPM cache contracts](docs/DEVELOPMENT.md#macos-direct-test-groups) when changing CI or test execution; preserve full test inventory, deadlines, failure handling, cache verification, and clean fallback.\n- Reuse incremental builds during iteration; never copy compiled products between worktrees. Clean `.build` for diagnosed contamination, a cold-build comparison, or maintainer-requested cleanup.\n- Run focused tests during development; the final code needs focused tests plus `make check`, with the full suite from PR CI or a background run. Repeat successful full checks when relevant inputs or the environment change, a failure or unresolved concern requires it, or a maintainer requests it; do not repeat them solely to prepare a report.\n\n## Commit & PR Guidelines\n- Commit messages: short imperative clauses (e.g., “Improve usage probe”, “Fix icon dimming”); keep commits scoped.\n- PRs/patches should list summary, commands run, screenshots/GIFs for UI changes, and linked issue/reference when relevant.\n\n## Agent Notes\n- Use the provided scripts and package manager (SwiftPM); avoid adding dependencies or tooling without confirmation.\n- Menu bar automation: capture the target screen first and verify the CodexBar icon is visibly onscreen. Reject `click-extra` success when coordinates fall outside display bounds; hidden menu extras are not click proof.\n- Validate UI/runtime behavior against the freshly built bundle; restart via the pkill+open command above to avoid running stale binaries.\n- To guarantee the right bundle is running after a rebuild, use: `pkill -x CodexBar || pkill -f CodexBar.app || true; cd /Users/steipete/Projects/codexbar && open -n /Users/steipete/Projects/codexbar/CodexBar.app`.\n- For CLI-testable provider/parser/settings behavior, use CLI/focused tests instead of `Scripts/package_app.sh` or `./Scripts/compile_and_run.sh`.\n- Run `./Scripts/compile_and_run.sh` only when UI/runtime behavior needs bundle-level validation; it builds, tests, packages, relaunches, and verifies the app stays running.\n- Widget/Tahoe UI issues: use Parallels macOS VM plus screenshots/clicks for autonomous verification.\n- Release workflow: wait for completion and verify the complete artifact/feed/tap handoff; do not publish locally.\n- Sparkle release key: CI `SPARKLE_PRIVATE_KEY` must match the existing AGCY public key in `.github/release.json`; never substitute another app’s key.\n- Swift concurrency: treat sibling `async let` tasks as a review red flag when one child is required and another is optional/best-effort. Prefer sequential awaits or a drained `withThrowingTaskGroup` that surfaces required failures and explicitly contains optional failures; crash stacks mentioning `swift_task_dealloc` or `asyncLet_finish_after_task_completion` should trigger an audit of nearby `async let` usage.\n- Prefer modern SwiftUI/Observation macros: use `@Observable` models with `@State` ownership and `@Bindable` in views; avoid `ObservableObject`, `@ObservedObject`, and `@StateObject`.\n- Favor modern macOS 15+ APIs over legacy/deprecated counterparts when refactoring (Observation, new display link APIs, updated menu item styling, etc.).\n- Keep provider data siloed: when rendering usage or account info for a provider (Claude vs Codex), never display identity/plan fields sourced from a different provider.\n- Claude CLI status line is custom + user-configurable; never rely on it for usage parsing by default. A user-enabled, opt-in statusLine JSON feed is permitted as an explicit data source (owner ruling, #2733): it must be off by default, clearly labeled as sourced from the user's own statusLine config, and fail soft when the format drifts.\n- Cookie imports: use the shared default browser order; `BrowserCookieAccessGate` ensures no background/settings prompts, host-matching cookie rows before Safe Storage access, at most one interactive Keychain read per manual action, denial cooldowns even on manual retries (only `codexbar cookie refresh --allow-keychain-prompt` overrides), and traversal stopping at the first validated session. Restrict a provider to a browser subset only with a documented technical reason next to the code.\n\n<!-- File: docs/deepseek.md (Tokens: ~1662 | Category: root-instruction) -->\n---\nsummary: \"DeepSeek provider data sources: API key, balance, and optional detailed usage endpoints.\"\nread_when:\n  - Adding or tweaking DeepSeek balance parsing\n  - Adding or tweaking DeepSeek detailed usage parsing\n  - Updating API key handling\n  - Documenting new provider behavior\n---\n\n# DeepSeek provider\n\nCodexBar can use either a DeepSeek API key or a signed-in DeepSeek Platform session for the remaining credit balance.\nDetailed cost and token usage comes from the Platform session; an API key cannot authenticate the private dashboard\nendpoints.\n\n## Data sources\n\n1. **Optional API key** supplied via `DEEPSEEK_API_KEY` / `DEEPSEEK_KEY`, or selected from DeepSeek token accounts in `~/.codexbar/config.json`.\n2. **API-key balance endpoint**\n   - `GET https://api.deepseek.com/user/balance`\n   - Request headers: `Authorization: Bearer <api key>`, `Accept: application/json`\n   - Response contains `is_available`, and a `balance_infos` array with per-currency entries\n     (`total_balance`, `granted_balance`, `topped_up_balance`).\n3. **Platform-session balance endpoint**\n   - `GET https://platform.deepseek.com/api/v0/users/get_user_summary`\n   - Request headers: `Authorization: Bearer <platform userToken>`, `Accept: application/json`\n   - Used as the balance source when no API key is configured.\n4. **Optional detailed usage endpoints**\n   - Preferred: `GET https://platform.deepseek.com/api/v0/usage/by_api_key/amount?start=<unix>&end=<unix>&tz=<offset>`\n     and `.../by_api_key/cost?...` — the same per-key daily buckets the Platform usage page loads.\n   - Fallback: `GET https://platform.deepseek.com/api/v0/usage/amount?month=<month>&year=<year>`\n     and `.../usage/cost?...` if the per-key endpoints fail.\n   - Daily buckets use Gregorian dates at the current fixed UTC offset in seconds, matching the API across daylight-saving transitions. Monthly fallback keeps\n     Gregorian UTC month selection and date parsing, and is labeled **This month**, not **Last 30 days**.\n   - Cancellation stops enrichment without starting monthly fallback requests.\n   - Request headers: `Authorization: Bearer <platform userToken>`, `Accept: application/json`, `x-client-platform: web`\n   - These are private dashboard endpoints rather than documented public API endpoints and may change without notice.\n\n## Platform session\n\nCodexBar resolves the Platform `userToken` in this order:\n\n1. An explicitly supplied `DEEPSEEK_PLATFORM_TOKEN` / `DEEPSEEK_USER_TOKEN` or a legacy\n   `providers[].cookieHeader` value preserved from an existing config.\n2. A prompt-free read of `userToken` from the `https://platform.deepseek.com` local-storage origin in Chrome.\n\nThe legacy config value remains a compatibility fallback so upgrading cannot silently erase a working browser-only\nsession. An unscoped legacy or environment token is never combined with an API-key balance; API enrichment requires\na session saved for that credential scope. New automatic imports are never written back to config.\n\nCodexBar checks every Chrome profile containing a parseable `userToken` against DeepSeek. Rejected or expired\nsessions are omitted. Settings shows a **Chrome profile** picker containing only valid sessions. With no API key, one\nvalid session is selected automatically; with multiple valid sessions, CodexBar reuses the saved choice or asks once.\nWhen an API key supplies the balance, a new or changed API credential requires an explicit session selection before\nwebsite usage is combined with it. CodexBar persists a stable browser/profile identifier, not an absolute home-directory path, and\nkeeps automatically imported tokens in memory only. The choice is scoped to a non-reversible fingerprint of the\nactive API credential and saved-account slot, so replacing a key or switching accounts cannot silently reuse an old\nbrowser session. Validation results are cached briefly so normal\nrefreshes do not probe every profile, and a temporary network failure does not erase a previously validated profile.\nIf the selected session expires, CodexBar asks before switching to another valid profile.\n\nChrome-only balance refreshes also keep the last live balance and its original timestamp through temporary transport\nfailures when the failed request belongs to the same browser profile and token. This ownership proof stays in memory;\ndecoded snapshots, changed profiles or tokens, and API-key balances with optional browser enrichment cannot supply it.\nOnce DeepSeek rejects a session, a later network failure cannot preserve its old balance; successful validation must\nrestore that session first. A saved profile with unknown validity still supplies transport diagnostics when other profiles\nsucceed; a known-rejected selection keeps the profile picker and its valid alternatives.\nRecognized transport failures still participate in startup retries when no matching balance can be retained. A Chrome-resolution\ndeadline without an observed session fails closed; cancelled or superseded refresh tasks keep their existing behavior.\n\nIf no session is valid, the menu keeps the API-key balance when one exists; otherwise it asks the user to sign in to\nDeepSeek Platform in Chrome. Authentication failures returned as top-level or nested DeepSeek codes `40002` and\n`40003` are treated as expired sessions.\n\n## Usage details\n\n- The menu card shows total balance with the paid vs. granted breakdown:\n  e.g. `$50.00 (Paid: $40.00 / Granted: $10.00)`.\n- The API separates granted balance from topped-up balance; CodexBar labels these as granted vs. paid credit.\n- With optional extra usage enabled, the menu shows today's and last-30-days cost and tokens,\n  request counts, API-key count, the top model, per-model spend, a daily token chart, and a daily spend chart.\n- Per-model spend uses the same reporting period and currency as detailed usage: **Last 30 days** for the preferred\n  endpoints, or **This month** for monthly fallback. These are Platform-account totals across API keys. Missing or\n  invalid model costs are omitted; a reported zero is retained.\n- The amount and cost requests run concurrently. After balance arrives, CodexBar waits up to five seconds for\n  automatic Chrome resolution and detailed usage. The deadline remains bounded even if a local Chrome read does not\n  respond to cancellation. If the optional work fails or times out, the balance and previously validated profile list\n  remain available while the menu reports that detailed usage is unavailable.\n- With multiple configured API keys, browser-derived detailed usage is shown only with the active API-key account;\n  other account cards remain balance-only so website usage is never duplicated across accounts.\n- When multiple currencies are present, USD is shown preferentially.\n- If total balance is zero, CodexBar shows an add-credits message. If balance is nonzero but `is_available` is false, it shows \"Balance unavailable for API calls\".\n- There is no session or weekly window — DeepSeek does not expose per-window quota via API.\n- Token-account selection injects the selected key into the fetch environment; otherwise CodexBar reads `DEEPSEEK_API_KEY` / `DEEPSEEK_KEY`.\n\n## Key files\n\n- `Sources/CodexBarCore/Providers/DeepSeek/DeepSeekProviderDescriptor.swift` (descriptor + fetch strategy)\n- `Sources/CodexBarCore/Providers/DeepSeek/DeepSeekUsageFetcher.swift` (HTTP client + JSON parser)\n- `Sources/CodexBarCore/Providers/DeepSeek/DeepSeekPlatformTokenImporter.swift` (Chrome Platform session import)\n- `Sources/CodexBarCore/Providers/DeepSeek/DeepSeekSettingsReader.swift` (env var resolution)\n- `Sources/CodexBar/Providers/DeepSeek/DeepSeekProviderImplementation.swift` (provider activation and token-account visibility)\n- `Sources/CodexBarCore/TokenAccountSupportCatalog+Data.swift` (DeepSeek token-account injection)\n\n<!-- File: docs/llms.txt (Tokens: ~199 | Category: root-instruction) -->\n# CodexBar\n\nA tiny macOS menu bar app that tracks AI coding-provider usage windows, credits, costs, and resets across 108 providers — Codex, OpenAI, Claude, Cursor, Gemini, Copilot, LiteLLM, Helmcode, v0, TypeSafe, and more.\n\nCanonical documentation:\n- CodexBar — every AI coding limit, in your menu bar: https://codexbar.app/ - A tiny macOS menu bar app that tracks AI coding-provider usage windows, credits, costs, and resets across 108 providers — Codex, OpenAI, Claude, Cursor, Gemini, Copilot, LiteLLM, Helmcode, v0, TypeSafe, and more.\n\nSource: https://github.com/steipete/CodexBar\n\nGuidance for agents:\n- Prefer the canonical documentation URLs above over README excerpts or package metadata.\n- Fetch only the pages needed for the current task; this is an index, not a full-site corpus.\n\n<!-- File: docs/mistral.md (Tokens: ~1457 | Category: root-instruction) -->\n---\nsummary: \"Mistral provider: browser cookie setup, billing usage, included API/Vibe allowances, and credits.\"\nread_when:\n  - Configuring Mistral usage\n  - Debugging Mistral billing or Vibe usage requests\n  - Adjusting Mistral cost, credit, or monthly-plan display\n---\n\n# Mistral Provider\n\nCodexBar reads Mistral billing usage and subscription allowances with the Mistral web session from\n`admin.mistral.ai`. It also fetches credit balance and falls back to the console Vibe endpoint when the subscription\npage does not expose a Vibe allowance.\n\n## Setup\n\n1. Open **Settings -> Providers**.\n2. Enable **Mistral**.\n3. Sign in to [Mistral Admin](https://admin.mistral.ai/organization/usage) in a supported browser.\n4. Leave Cookie source on **Automatic**, or switch to **Manual** and paste a `Cookie:` header from a request to\n   `admin.mistral.ai`.\n\nManual cookies must include an `ory_session_*` cookie. A `csrftoken` cookie enables fallback Vibe requests that\nrequire the `X-CSRFTOKEN` header.\n\nOn Linux, browser import is unavailable, so the CLI reads Mistral only in Manual mode: set the provider's\n`cookieSource` to `manual` and its `cookieHeader` to the pasted header in `~/.config/codexbar/config.json`.\n\nAutomatic import follows the shared supported-browser order. Safari requires Full Disk Access.\nThe shared access gate bounds Keychain prompts. Automatic import reads only unexpired cookies from\nthe documented Mistral domains.\n\n## Data Sources\n\nCodexBar requests the current UTC month, subscription allowances, and credits from Mistral Admin:\n\n- `GET https://admin.mistral.ai/api/billing/v2/usage?month=<month>&year=<year>`\n- `GET https://admin.mistral.ai/subscription` (best-effort included API and Vibe allowances)\n- `GET https://admin.mistral.ai/api/billing/credits` (best-effort credit balance)\n\nIf the subscription page has no Vibe allowance and a CSRF token is available, CodexBar makes a bounded best-effort\nfallback request:\n\n- `GET https://console.mistral.ai/api-ui/trpc/billing.vibeUsage?...`\n\nFor the console request, CodexBar forwards only the `csrftoken` and `ory_session_*` cookies. Other\n`admin.mistral.ai` cookies stay origin-bound.\n\n## Display\n\n- **Included API** shows the subscription allowance's used percentage, used / total / remaining amount, and reset time.\n- The optional **Monthly Plan** window shows the separate Vibe Code allowance with the same details.\n- API spend is computed from billed units (`value_paid`, falling back to `value`) and the pricing table. Each unit takes\n  the price with the same event type, metric, group, API zone, and service tier; the table lists one metric under\n  several of these, and audio-second and priority prices are far higher than standard token prices. Token totals\n  and daily buckets use consumed units (`value`, falling back to `value_paid`), so plan-covered usage still counts.\n  Legacy tables that omit both API zone and service tier use the unqualified price for the same event type, metric, and group.\n- Token totals include API completions, Le Chat, and Vibe Code completions from the billing usage response.\n- Daily usage buckets feed the inline usage dashboard.\n- The provider card can show credit balance when the credits endpoint returns it.\n- Allowance amounts derive from Mistral's reported percentage and allowance size, independently of billed API spend. Zero or malformed allowances are omitted without discarding a valid sibling allowance.\n- The Automatic menu bar selection retains API spend; Included API and Monthly Plan select their respective quota percentages.\n- Token-cost history is supported through the billing web session; no local log scan is used.\n- Unrepresentable billing token totals fail parsing instead of crashing. Display-only model rankings omit an\n  overflowing total while retaining valid cost data.\n- Final input, cached, and output totals allow signed adjustments in any lane while rejecting totals outside the\n  supported integer range.\n\n## Widgets\n\nUsage widgets follow the **Menu bar metric** picker in Mistral's provider settings. The picker appears in every menu bar\nstyle, so Critters and Meter bars users can still pick the widget allowance. Choosing a percentage metric pins\nMistral’s layout against later global layout edits. Without a percentage, the picker changes only the stored metric\nand keeps following the global layout:\n\n- **Automatic** and **Included API** show only the API allowance, preserving the existing default.\n- **Monthly Plan** shows only the Vibe allowance, falling back to Included API when the plan is missing or unknown.\n\nAutomatic still shows API spend in the menu bar. Changing the metric updates widget rows from the existing snapshot,\nwithout another request. After a failed refresh, widgets reselect from the last published usage snapshot and keep its\noriginal measurement time, including across repeated metric changes. This in-memory source is cleared when provider\nor account ownership is invalidated. Burn Down eligibility still requires a known window duration and reset.\n\n## CLI Usage\n\n```bash\ncodexbar usage --provider mistral --verbose\n```\n\nText output includes both Included API and the optional Monthly Plan, each with its percentage, reset date when\navailable, and used / total / remaining amounts. Amounts are shown as detail, never as a reset time. This uses the\nexisting snapshot; no additional requests are made. JSON output is unchanged: the Monthly Plan remains in\n`extraRateWindows` with the ID `mistral-monthly-plan`.\n\n## Troubleshooting\n\n### \"No Mistral session cookies found\"\n\nSign in to [Mistral Admin](https://admin.mistral.ai/organization/usage) in a supported browser, then refresh.\n\n### \"Mistral cookie header is invalid\"\n\nIn manual mode, paste a full `Cookie:` header from an `admin.mistral.ai` request. The header must include an\n`ory_session_*` cookie.\n\n### Included allowance, credits, or Vibe plan usage are missing\n\nThe billing usage request is required. Subscription allowances, credits, and Vibe usage are best-effort; if an\noptional source fails or does not expose data for the account, CodexBar keeps the main Mistral usage result.\n\n## Related Files\n\n- `Sources/CodexBarCore/Providers/Mistral/MistralProviderDescriptor.swift`\n- `Sources/CodexBarCore/Providers/Mistral/MistralUsageFetcher.swift`\n- `Sources/CodexBarCore/Providers/Mistral/MistralSubscriptionBudgetParser.swift`\n- `Sources/CodexBarCore/Providers/Mistral/MistralModels.swift`\n- `Sources/CodexBarCore/Providers/Mistral/MistralCookieImporter.swift`\n- `Sources/CodexBar/Providers/Mistral/MistralProviderImplementation.swift`\n\n<!-- File: docs/architecture.md (Tokens: ~2082 | Category: project-spec) -->\n---\nsummary: \"Architecture overview: modules, entry points, and data flow.\"\nread_when:\n  - Reviewing architecture before feature work\n  - Refactoring app structure, app lifecycle, or module boundaries\n---\n\n# Architecture overview\n\n## Modules\n- `Sources/CodexBarCore`: fetch + parse (Codex RPC, PTY runner, Claude probes, OpenAI web scraping, status polling).\n- `Sources/CodexBar`: state + UI (UsageStore, SettingsStore, StatusItemController, menus, icon rendering).\n- `Sources/CodexBarWidget`: WidgetKit extension wired to the shared snapshot.\n- `Sources/CodexBarCLI`: bundled CLI for `codexbar` usage/status output.\n- `Sources/CodexBarClaudeWatchdog`: helper process for stable Claude CLI PTY sessions.\n- `Sources/CodexBarClaudeWebProbe`: CLI helper to diagnose Claude web fetches.\n\n## Entry points\n- `CodexBarApp`: SwiftUI keepalive + Settings scene.\n- `AppDelegate`: wires status controller, Sparkle updater, notifications.\n- Status items are created on the main actor from `applicationDidFinishLaunching`; see\n  [status-item startup diagnostics](status-item-diagnostics.md) for opt-in lifecycle and Control Center traces.\n\n## Data flow\n- Background refresh → `UsageFetcher`/provider probes → `UsageStore` → menu/icon/widgets.\n- Settings toggles feed `SettingsStore` → `UsageStore` refresh cadence + feature flags.\n- Runtime-only provider settings flow through typed, descriptor-registered sections in `ProviderSettingsSnapshot`.\n- Codex cost conversation titles reuse SQLite database discovery per working directory and SQLite home within each\n  enrichment call. The next call resolves paths again, so config edits and new state database versions are picked up.\n\n## CLI login lifecycle\n- `CodexLoginRunner` and `KiroLoginRunner` resolve their own executable and environment, including Codex home scoping.\n- `CLILoginRunner` owns browser-waiting login processes, bounded output capture, timeout/cancellation, and optional\n  device-flow progress. It returns one shared result type; provider presentations retain their own recovery messages.\n- The login runner and `SubprocessRunner` share `ProcessTermination` and process-tree termination. Cancelling a login\n  stops its child process, joins its progress callback task, and produces no failure alert. Timeouts retain captured\n  diagnostic output, and inherited pipes cannot keep the caller waiting indefinitely.\n- Probe ownership cleanup reads exact environment markers, retaining PID identity checks before signaling. Environment\n  parsing searches NUL byte ranges so large unrelated values do not dominate timeout and cancellation cleanup.\n- Codex and Grok RPC clients share deadline selection through `RPCRequestTimeout`. The deadline wins before teardown\n  can report stdout EOF; each client keeps its protocol initialization, encoding, diagnostics, and error types.\n\n## Concurrency & platform\n- Swift 6 strict concurrency enabled; prefer Sendable state and explicit MainActor hops.\n- macOS 14+ targeting; avoid deprecated APIs when refactoring.\n- Cost stores share one utility serial executor per canonical database location, including symlinked cache roots and\n  database files. Separate database operations do not share SQLite lock waits. Executors are weakly registered and\n  released with their last store or queued job. Normal app fetches still use the separate serial scan queue;\n  WAL transactions are unchanged.\n\n## Shared policy ownership\n- `ProviderCatalog` indexes the immutable generated list of app implementations. JavaScript plugins use their own\n  runtime registry. Provider settings contexts bind directly to their store; the shared cookie picker owns mode\n  conversion, options, and live subtitle selection while providers retain labels, visibility, and cache scopes. Settings\n  link actions resolve their provider-supplied destinations when clicked, including region and account selection.\n  Cookie subtitles use explicit localization keys and arguments supplied by providers; the picker selects the current\n  mode and Keychain-disabled explanation without parsing English display text.\n- Descriptor-registered cookie sections preserve their concrete types after app policy resolves credentials. The app\n  keeps its automatic default, Keychain-disabled manual mode, and selected-account normalization; CLI inference stays\n  separate. Cookie-source writes share config persistence and logging, with provider-specific side effects retained.\n- Core's public descriptor registry keeps one ordered descriptor collection with an index for lookup. Its storage owns\n  synchronization, and replacing a descriptor updates metadata and CLI names without changing provider order.\n- `UsageStore.presentationSnapshot` owns subscription metadata selection. Live cards use that projection once; explicit\n  account cards use only their supplied snapshots, identity, and credits. Compact and stacked Codex account layouts share\n  the same account-card builder. Reordering visible providers preserves loaded config records for unavailable plugins;\n  config decoding retains its existing known-provider rules.\n- `UsageStore.menuCardInput` assembles cards for Settings, the live menu, and explicit account contexts. It owns common\n  quota, pace, warning-marker, and display-preference projection. Settings retains diagnostics and all usage lanes;\n  menus retain their cost display policy and account-scoped forecasts. An account context stays isolated even when empty.\n  Cards consume the reconciled Codex projection; the raw dashboard is not a separate model input.\n- App credential properties read directly from the config snapshot and delegate common string writes to the typed\n  `SettingsStore` config accessor, which owns normalization, persistence, and secret-update logging. Field activation\n  does not trigger credential loading. Legacy provider toggles are read only by the config migrator.\n- Token-cost publications own their snapshots, revisions, and source scope in one store. Raw and current-config readers\n  select from that same state while retaining confirmed-empty and unpublished distinctions. Provider settings and\n  plugin edits reuse Core's config upsert; their defaults and notification policies remain explicit.\n- The native status-item controller owns menu composition. Persistent refresh-row metrics are independent of menu\n  rendering, and screenshot fixtures exercise the active card views. Legacy menu-layout resolution retains its\n  rendering mode and projected layout without copying unused settings into a second state object.\n- `SettingsValue` owns whitespace and wrapping-quote normalization for config and provider settings. Readers retain\n  their credential precedence, endpoint validation, and provider-specific decoding.\n- `ISO8601DateParser` owns fractional-first internet timestamp parsing with a whole-second fallback. Provider readers\n  retain their text extraction, whitespace, numeric timestamp, and custom-format policies; each parse owns its formatter.\n- Kilo's CLI fetch strategy and organization discovery share `KiloBearerTokenResolver` for auth-file loading.\n  Vertex AI credential loading and renewal share display-only ID-token decoding; diagnostic fetch labels use\n  `ProviderDiagnosticFetchAttempt` across the app and CLI.\n- Core owns status feed fetching, decoding, and status models through `ProviderStatusFetcher`; the app supplies\n  localized labels and component UI, and the CLI supplies its existing status payload and English labels.\n- `KeychainStringStore` owns generic-password operations for legacy credential migration. Provider adapters retain\n  their existing item names, prompt kinds, cookie validation, and cache lifetimes. Structured OAuth/cache stores\n  retain their separate persistence and authorization contracts.\n- Simple API-token providers use `ProviderFetchPlan.apiToken`; provider loaders retain endpoint-specific behavior\n  and optional-data rules. Strategies with credential provenance, region selection, or fallback policies stay explicit.\n- `ProviderCostSnapshot.spendLimitWindow` owns spend-budget quota projection. Antigravity's shared family classifier\n  owns ID-before-title precedence; visibility and compact widget selection retain their distinct unknown-family rules.\n- `TestProcessSafety` owns test-runner recognition; callers retain explicit headless and fixture-path policies.\n- `CheckedSum` owns integer overflow handling. Callers still decide whether absent or empty counts mean zero,\n  unavailable, or incomplete; persistent accumulators retain their own overflow state.\n- `BrowserCookieProfiles` merges browser stores within each profile using the existing expiry and store-priority\n  rules. Providers still validate their own sessions. `ExpiringValueCache` supplies their short-lived import caches,\n  with a separate cache instance per provider. `CookiePropertyJSON` preserves the legacy Date/URL marker encoding;\n  session stores retain their own file envelopes, expiration policies, and secure writes.\n- `OpenCodeWebParsing` owns workspace discovery and quota-candidate traversal for OpenCode and OpenCode Go. Each\n  supplies its own numeric/window decoder and required-lane policy. Alibaba Token Plan and Qwen Cloud share\n  `OneConsoleTokenPlanSnapshot` projection while retaining distinct public snapshot types and provider identities.\n- `StreamScanBuffer` supplies bounded overlap for Codex and Claude terminal-marker matching; command handling and\n  session lifecycle remain provider-specific.\n- Codex's persistent and one-shot PTY readers share `CodexStatusMarkers`, including the marker lengths used for\n  bounded overlap. Cursor-query handling remains part of each terminal loop.\n- `TTYCommandRunner` reports the captured POSIX error number and description when PTY allocation fails, so\n  descriptor exhaustion can be distinguished from other terminal allocation failures.\n- `UsageSnapshot.withAccountLabel` applies token-account fallback labels for the app and CLI while preserving the\n  provider's account ID and other identity fields. Codex visible-account labels keep their surface-specific policy.\n- Raw Chromium local-storage consumers share `ChromiumLocalStorageDiscovery`, retaining their own browser lists and\n  origin decoders. Both plugin engines use the manifest's management-auth eligibility policy after adapting their\n  engine-specific option values.\n- Codex RPC and OAuth spend limits use `CodexSpendControlNumber` while keeping their wire-field aliases separate.\n  Reset values retain truncation toward zero, with unrepresentable values omitted instead of trapping.\n- Claude's persistent CLI operations and serialized browser fetches each own an `AsyncOperationGate`; the shared\n  implementation retains FIFO ownership and the CLI cleanup path's explicit cancellation exception. CLI usage and\n  cards validate account/provider selection through `TokenAccountCLISelection` before resolving credentials.\n\nSee also: `docs/providers.md`, `docs/refresh-loop.md`, `docs/ui.md`.\n\n<!-- File: .agents/skills/codexbar/SKILL.md (Tokens: ~331 | Category: anthropic-skill) -->\n---\nname: codexbar\ndescription: \"CodexBar read. Provider usage, limits, credits, config health. JSON. No writes.\"\n---\n\n# CodexBar\n\nRead CodexBar. Never mutate config/auth.\n\n## Run\n\n```bash\nskill=\"${CODEX_HOME:-$HOME/.codex}/skills/codexbar\"\n\"$skill/scripts/codexbar\" doctor\n\"$skill/scripts/codexbar\" providers\n\"$skill/scripts/codexbar\" usage\n\"$skill/scripts/codexbar\" usage --provider codex\n\"$skill/scripts/codexbar\" usage --all\n```\n\nAll stdout: JSON. Upstream CodexBar shape kept. Less drift, fewer tokens.\n\n## Rules\n\n- Start `doctor` when install/config unknown.\n- `usage` reads enabled providers. Prefer this.\n- `usage --provider ID` reads one provider.\n- `usage --all` expensive; use only when needed.\n- Identities hidden by default. `--include-identities` only when user explicitly needs them.\n- Secrets always hidden.\n- Helper read-only: fixed allowlist only. No config writes, auth repair, enable/disable, key storage.\n- Timeout means upstream stuck. Narrow provider or raise `CODEXBAR_TIMEOUT` (default 120 seconds).\n\n## Binary\n\nAuto-find: `CODEXBAR_BIN`, PATH, app bundle, Homebrew cask. If missing: open CodexBar, Preferences > Advanced > Install CLI; or set `CODEXBAR_BIN`.\n\nEach stdout/stderr stream capped at 1 MiB while fully drained. Timeout kills process group.\n\n<!-- File: .agents/skills/qa-test/agents/openai.yaml (Tokens: ~300 | Category: anthropic-skill) -->\n# Agents Skill\n\nPath: `.agents/skills/qa-test/agents/openai.yaml`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/steipete/CodexBar/HEAD/.agents/skills/qa-test/agents/openai.yaml)\n\n<!-- File: .agents/skills/qa-test/references/api-specs.md (Tokens: ~300 | Category: anthropic-skill) -->\n# References Skill\n\nPath: `.agents/skills/qa-test/references/api-specs.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/steipete/CodexBar/HEAD/.agents/skills/qa-test/references/api-specs.md)\n\n<!-- File: .agents/skills/qa-test/SKILL.md (Tokens: ~1227 | Category: anthropic-skill) -->\n---\nname: qa-test\ndescription: \"CodexBar live QA/e2e testing: run provider usage matrix checks, validate real app config, use Peekaboo for menu proof, use Browser Use/official docs for API spec or logged-in dashboard checks, and handle 1Password credentials safely.\"\n---\n\n# CodexBar Live QA\n\nUse for live provider testing, release smoke tests, menu verification, or debugging “provider works/fails” reports.\n\n## Rules\n\n- Work from the CodexBar repo checkout.\n- Use the packaged CLI first: `CodexBar.app/Contents/Helpers/CodexBarCLI`.\n- Do not use `CodexBar.app/Contents/MacOS/codexbar`; that is the app binary and may appear to hang as a CLI.\n- Never run broad `env`, `set`, or secret regex dumps.\n- Use `$one-password` for secrets: all `op` commands inside one persistent tmux session, service account first, no raw secret output.\n- Treat browser-cookie/keychain flows as prompt-risky. Prefer CLI/API-token checks and `KeychainNoUIQuery`-safe tests unless the user explicitly requested live UI.\n- For current API behavior, browse official provider docs only.\n\n## CLI Matrix\n\nRun the bundled script:\n\n```bash\n.agents/skills/qa-test/scripts/live_provider_matrix.sh --enabled\n```\n\nUseful modes:\n\n```bash\n.agents/skills/qa-test/scripts/live_provider_matrix.sh --provider all\n.agents/skills/qa-test/scripts/live_provider_matrix.sh --providers openai,zai,deepseek\n.agents/skills/qa-test/scripts/live_provider_matrix.sh --default\n```\n\nInterpretation:\n\n- `--enabled` asks `CodexBarCLI config providers` for enabled providers, honoring `CODEXBAR_CONFIG` and default toggles.\n- `--default` runs the app-facing default command with no provider override.\n- `--provider all` forces every registered provider and is expected to fail for providers without sessions/keys.\n- A green app config needs `--enabled` and `--default` clean; `--provider all` is a discovery/triage tool.\n\n## Config QA\n\nValidate config:\n\n```bash\nCodexBar.app/Contents/Helpers/CodexBarCLI config validate\nstat -f '%Lp %N' \"$HOME/.codexbar/config.json\"\n```\n\nRedact config shape:\n\n```bash\njq '(.providers // []) |= map(.apiKey = (if .apiKey then \"<redacted>\" else .apiKey end) |\n  .secretKey = (if .secretKey then \"<redacted>\" else .secretKey end) |\n  .cookieHeader = (if .cookieHeader then \"<redacted>\" else .cookieHeader end) |\n  (if .id == \"stepfun\" and has(\"region\") then .region = \"<redacted>\" else . end) |\n  .tokenAccounts = (if .tokenAccounts then (.tokenAccounts | .accounts = (.accounts | map(.token = \"<redacted>\"))) else .tokenAccounts end))' \\\n  \"$HOME/.codexbar/config.json\"\n```\n\nBefore editing config, make a backup:\n\n```bash\ncp \"$HOME/.codexbar/config.json\" \"$HOME/.codexbar/config.pre-qa-$(date +%Y%m%d%H%M%S).json\"\nchmod 600 \"$HOME/.codexbar\"/config.pre-qa-*.json\n```\n\n## Live Menu QA\n\nUse Peekaboo after CLI checks:\n\n```bash\npkill -x CodexBar || pkill -f 'CodexBar.app/Contents/MacOS/CodexBar' || true\nopen -n \"$PWD/CodexBar.app\"\npeekaboo menu list-all --json | rg -i 'codexbar'\npeekaboo menu click-extra --title codexbar-merged --json\nscreencapture -x /tmp/codexbar-live-menu.png\n```\n\nCrop top-right menu if needed:\n\n```bash\nsips --cropToHeightWidth 900 340 --cropOffset 20 2650 /tmp/codexbar-live-menu.png \\\n  --out /tmp/codexbar-live-menu-crop.png >/dev/null\n```\n\nVerify visually with `view_image`. Confirm provider tabs/rows match enabled config and no failing provider dominates the first screen.\n\n## Browser Use\n\nUse `$browser-use` only when a logged-in dashboard, API key page, or provider docs need browser/profile state.\n\nExisting Chrome path:\n\n```bash\nmcporter call chrome-devtools.list_pages --args '{}' --output text\nmcporter call chrome-devtools.navigate_page --args '{\"url\":\"https://provider.example\"}' --output text\nmcporter call chrome-devtools.take_snapshot --args '{}' --output text\n```\n\nIf Browser Use is unavailable, say so and use web search for public official docs; do not substitute isolated Playwright for login/profile-dependent pages.\n\n## Fix Triage\n\n- Missing auth/session: configure key/session if available; otherwise leave provider disabled or report blocked auth.\n- Wrong provider API/spec: inspect official docs, then patch fetcher/settings/tests.\n- Provider key exists but live API rejects it: keep key stored if useful, disable provider if the menu would show a persistent error.\n- User-facing behavior changes need `CHANGELOG.md`.\n- Code fixes need focused tests, `make check`, `$autoreview`, and live CLI proof before landing.\n\n## Known CodexBar QA Notes\n\n- OpenAI Admin API key is the useful usage provider key. Project `OPENAI_API_KEY` values can fail legacy credit-balance fallback with 403.\n- Deepgram usage requires a key/project with Management API permissions; transcription-only keys can return 403.\n- Groq usage uses the Prometheus metrics API, not ordinary inference endpoints.\n- MiniMax pay-as-you-go API keys and Token Plan/Coding Plan keys are different; wrong key kind can leave usage unavailable.\n\n<!-- File: .agents/skills/release-codexbar/SKILL.md (Tokens: ~1559 | Category: anthropic-skill) -->\n---\nname: release-codexbar\ndescription: \"CodexBar release: versioning, notarization, appcast, Homebrew, post-release bump.\"\n---\n\n# CodexBar Release\n\nUse for releasing signed/notarized macOS apps, especially repos with Sparkle appcasts and Homebrew casks.\n\n## Start\n\n1. Work from the app repo unless asked otherwise.\n2. Check repo state, current version, latest tag/release, and release docs/scripts.\n3. Confirm `CHANGELOG.md` is complete, user-facing, deduped, and dated for the release.\n4. Prefer the repo release script; patch small script/test blockers instead of bypassing the release path.\n5. Never print key material. Keep 1Password references and local key paths as references only.\n6. Load `$release-private` if it exists before resolving Peter-owned credential locators.\n\n## Key Material\n\nUse `$one-password` for secret handling. `op` only in tmux/persistent shell; no broad `env`, `set`, `export -p`, or secret scans.\n\nKnown App Store Connect shape:\n\n- fields: `private_key_p8`, `key_id`, `issuer_id`\n- keep all three fields from the same 1Password item; do not mix with stale values from `~/.profile`\n- resolve Peter-owned item refs from `$release-private`\n\nKnown Sparkle key:\n\n- resolve the private key file from `$release-private`\n- pass as `SPARKLE_PRIVATE_KEY_FILE`\n\nSafe env file pattern:\n\n```text\nAPP_STORE_CONNECT_API_KEY_P8=<1Password ref from release-private>\nAPP_STORE_CONNECT_KEY_ID=<1Password ref from release-private>\nAPP_STORE_CONNECT_ISSUER_ID=<1Password ref from release-private>\nSPARKLE_PRIVATE_KEY_FILE=<path from release-private>\n```\n\nRun with `op run --account my.1password.com --env-file <file> -- <script>`, then delete the temp env file.\n\n## CodexBar\n\nPaths:\n\n- repo: `~/Projects/codexbar`\n- release script: `Scripts/release.sh`\n- signing/notarization: `Scripts/sign-and-notarize.sh`\n- appcast: `Scripts/make_appcast.sh`, `appcast.xml`\n- release assets: `CodexBar-macos-universal-<version>.zip`, `CodexBar-macos-universal-<version>.dSYM.zip`\n- packaged app: `CodexBar.app`\n- version file: `version.env`\n- changelog: `CHANGELOG.md`\n- Homebrew tap: `~/Projects/homebrew-tap`\n- cask: `~/Projects/homebrew-tap/Casks/codexbar.rb`\n- formula: `~/Projects/homebrew-tap/Formula/codexbar.rb`\n- CLI release workflow: `.github/workflows/release-cli.yml`\n\nNormal release:\n\n```bash\ntmux new-session -d -s codexbar-release 'op run --account my.1password.com --env-file /tmp/codexbar-release-op.env -- Scripts/release.sh'\ntmux attach -t codexbar-release\n```\n\nIf notarization fails with `401 Unauthenticated`, rerun using all three App Store Connect fields from the 1Password item above. Mismatched `key_id` / `issuer_id` from `~/.profile` can cause this.\n\nIf widget metadata generation times out, `CODEXBAR_WIDGET_METADATA_TIMEOUT_SECONDS=600` is a known-good floor.\n\nCodexBar CLI tarballs are not produced by `Scripts/release.sh` itself. The GitHub release event triggers `.github/workflows/release-cli.yml`, which builds and uploads:\n\n- `CodexBarCLI-v<version>-macos-arm64.tar.gz`\n- `CodexBarCLI-v<version>-macos-x86_64.tar.gz`\n- `CodexBarCLI-v<version>-linux-aarch64.tar.gz`\n- `CodexBarCLI-v<version>-linux-x86_64.tar.gz`\n- matching `.sha256` files\n\nIf the workflow fails only in `update-homebrew-tap` with GitHub API rate limiting, the CLI assets may already be uploaded. Verify assets live, then update `Formula/codexbar.rb` manually from the tarball checksums.\n\n## Verify\n\nRelease is not done until the published chain checks out:\n\n```bash\ngh release view v<VERSION> --json tagName,name,isDraft,isPrerelease,url,assets,body\nScripts/check-release-assets.sh v<VERSION>\npython3 - <<'PY'\nimport xml.etree.ElementTree as ET\nns={'sparkle':'http://www.andymatuschak.org/xml-namespaces/sparkle'}\nroot=ET.parse('appcast.xml').getroot()\nitem=root.find('channel').find('item')\nenc=item.find('enclosure')\nprint(item.findtext('title'))\nprint(item.findtext('sparkle:version', namespaces=ns))\nprint(item.findtext('sparkle:shortVersionString', namespaces=ns))\nprint(enc.attrib.get('url'))\nprint(enc.attrib.get('length'))\nprint(bool(enc.attrib.get('{http://www.andymatuschak.org/xml-namespaces/sparkle}edSignature')))\nPY\ncodesign --verify --deep --strict --verbose=2 CodexBar.app\nspctl --assess --type execute --verbose CodexBar.app\n```\n\nFor Homebrew:\n\n```bash\nshasum -a 256 CodexBar-macos-universal-<VERSION>.zip\ncd /Users/steipete/Projects/homebrew-tap\npython3 .github/scripts/update_formula.py --formula codexbar --tag v<VERSION> --repository steipete/CodexBar --artifact-template 'CodexBarCLI-{tag}-{target}.tar.gz' --target-aliases 'darwin_arm64=macos-arm64,darwin_amd64=macos-x86_64,linux_arm64=linux-aarch64,linux_amd64=linux-x86_64'\nbrew fetch --cask --force --retry codexbar\nbrew fetch --formula --force --retry steipete/tap/codexbar\n```\n\nUpdate the cask when app zip assets exist. Update the formula only when standalone CLI tarballs for that version exist.\n\nTap audit can be noisy from unrelated formulae; keep evidence specific to the app cask.\n\n## Closeout\n\n1. Create/push tag and GitHub release through the release script.\n2. Verify appcast points to the new GitHub release asset with signature and length.\n3. Update/push the Homebrew cask if the app zip changed.\n4. Bump the app repo to next patch `Unreleased`:\n   - `version.env`: next `MARKETING_VERSION`, next `BUILD_NUMBER`\n   - `CHANGELOG.md`: top `## <next> — Unreleased`\n5. Commit, push, then pull `--ff-only`.\n6. Restart the local app from the packaged bundle and verify the running bundle version.\n7. Check no release/notary/op temp sessions or temp env files remain.\n\nCodexBar restart:\n\n```bash\npkill -x CodexBar || pkill -f CodexBar.app || true\ncd \"$(git rev-parse --show-toplevel)\"\nopen -n CodexBar.app\n/usr/libexec/PlistBuddy -c 'Print :CFBundleShortVersionString' CodexBar.app/Contents/Info.plist\n/usr/libexec/PlistBuddy -c 'Print :CFBundleVersion' CodexBar.app/Contents/Info.plist\n```\n\n<!-- File: docs/claude.md (Tokens: ~10466 | Category: claude-rule) -->\n---\nsummary: \"Claude provider data sources: OAuth API, web API (cookies), CLI PTY, and local cost usage.\"\nread_when:\n  - Debugging Claude usage/status parsing\n  - Updating Claude OAuth/web endpoints or cookie import\n  - Adjusting Claude CLI PTY automation\n  - Reviewing local cost usage scanning\n---\n\n# Claude provider\n\nEnable **Settings → Menu Bar → Show all accounts in Overview** to include this provider’s cached account\ncards in Overview. The existing account source and provider selection remain unchanged; see\n[Overview account rows](configuration.md#menu-bar-controls).\n\nThe **Plan Usage** submenu includes recorded remaining-quota burndown above utilization history,\nusing the same Session, Weekly, and Sonnet labels. See [recorded quota burndown](widgets/burndown-proof.md)\nfor capture-age semantics and the existing history retention/privacy behavior.\n\nOAuth history uses a stable account/profile identity after two stable credential observations corroborate that\nbinding. External token rotations therefore continue the same history after the new credential is corroborated.\nSaved token-scoped fragments with matching verified bindings migrate into that account's history on a successful\nsample. Unbound fragments, obsolete bindings, and other accounts stay separate; correcting a conflicting binding\npreserves its ambiguous old fragment without attributing it to the newly confirmed account. If that fragment was\nalready merged, the affected account bucket is preserved but hidden, and fresh samples start a new account scope.\nThis quarantine survives empty responses and restarts. Explicit OAuth tokens without Claude Code account evidence\nremain credential-scoped.\n\nClaude supports three usage data paths plus local cost usage. The main provider pipeline uses runtime-specific\nautomatic selection, but the codebase still has multiple active Claude `.auto` decision sites while the refactor is\npending. For the exact current-state parity contract, see\n[docs/refactor/claude-current-baseline.md](refactor/claude-current-baseline.md).\n\nWhen an Anthropic Admin API key is configured, Claude can also show organization-level spend/messages/tokens in the\nsame inline dashboard pattern used by the OpenAI API provider.\n\nIncomplete proxy records with an explicit null `stop_reason`, positive input, zero output, and no cache counters are excluded from token and cost totals until a completed record arrives. Their day and model stay visible with an **Incomplete** marker and an excluded-request count. Known usage remains a partial subtotal; an incomplete-only period stays unavailable rather than becoming zero. Older caches are rebuilt once to apply this distinction. Missing `stop_reason` alone retains compatibility with older complete logs.\n\nCompleted requests for models without a known price keep their token usage and report unpriced coverage. Usage & Spend retains the priced subtotal from the other requests and days as a lower bound; unknown prices do not become zero. Fully priced history keeps its usual estimated totals.\n\n## Data sources + selection order\n\n### Default selection (debug menu disabled)\n- If an Admin API key is configured, the Admin API strategy is used for Claude API spend/usage.\n- App runtime main pipeline: OAuth API → CLI PTY → Web API.\n- CLI runtime main pipeline: Web API → CLI PTY (Linux: Web API → OAuth API → CLI PTY).\n  Since 0.78.0, Auto `usage` first uses the active slot when the claude-swap integration is enabled;\n  explicit source/account selection bypasses it. See [CLI precedence](cli.md#command).\n- Explicit picker modes (OAuth/Web/CLI) bypass automatic fallback.\n- Explicit OAuth retains the last successful quota measurement through recognized temporary network failures,\n  including localized DNS/offline errors. Its original timestamp remains visible; authentication rejection still\n  follows the existing invalidation and credential-owner rules.\n- A lower-level direct Claude fetcher still contains a separate `.auto` order. That inconsistency is tracked in\n  [docs/refactor/claude-current-baseline.md](refactor/claude-current-baseline.md).\n\nUsage source picker:\n- Preferences → Providers → Claude → Usage source (Auto/OAuth/Web/CLI).\n\nWhen an account-widget identity check or successful web enrichment returns an OAuth profile, its same-token email is\nretained for display even if the profile lacks the organization needed to verify widget ownership. A failed\noptional profile check leaves successful usage available. Ordinary OAuth refreshes also start one background\nprofile lookup per credential, bounded to two seconds, without waiting for it to publish quota usage. The first\nrefresh may omit the email while that request is in flight; later refreshes reuse its result, including a failed\nlookup, until the token changes or CodexBar restarts. This process-local display cache cannot establish widget\nownership. Organization UUIDs are ownership evidence, not organization display names.\n\nAdmin API key setup:\n- Preferences → Providers → Claude → Admin API key, stored in `~/.codexbar/config.json`.\n- CLI/env: `printf '%s' \"$ANTHROPIC_ADMIN_KEY\" | codexbar config set-api-key --provider claude --stdin`.\n- Token accounts can also hold `sk-ant-admin...` keys; they route to the Admin API instead of cookie/OAuth usage.\n- Environment fallback: `ANTHROPIC_ADMIN_KEY`.\n\n## Usage Dashboard\n\nThe default and API-account Usage Dashboard action opens [Platform usage](https://platform.claude.com/usage). Accounts recognized as Claude subscriptions open [Settings → Usage](https://claude.ai/settings/usage). This action does not change the configured usage source.\n\n## Admin API\n- Key prefix: `sk-ant-admin...`.\n- Endpoints:\n  - `/v1/organizations/cost_report`\n  - `/v1/organizations/usage_report/messages`\n- Output:\n  - Today/7d/30d spend and message/token summaries.\n  - Inline 30-day dashboard chart when daily buckets are present.\n  - Identity login method: `Admin API`.\n\n### Optional workspace spend\n\nEnable **Show workspace spend** in Settings → Providers → Claude, set `claudeWorkspaceSpendEnabled: true` on the\nClaude provider config entry, or set `ANTHROPIC_ADMIN_WORKSPACE_SPEND=true`. It is off by default and applies only\nto the Admin API source.\n\nThe existing [cost report](https://platform.claude.com/docs/en/api/admin/cost_report/retrieve) request adds\n`group_by[]=workspace_id` alongside `group_by[]=description`; no extra request or credentials are required.\nThe organization totals, cost items, token summaries, and daily chart remain unchanged. When more than one workspace\nhas cost rows, **Workspace spend · 30d** shows up to 20 workspaces, highest spend first, over the same 30-day buckets\nas the organization total. Labels use workspace IDs; a null workspace is **Default**. Amounts are converted from\nAnthropic's USD cents to dollars. A single workspace keeps the existing organization view.\n\n## Recover usage when Claude is already signed in\n\nA working Claude Code login or Claude browser tab does not by itself confirm that CodexBar can read that\nsession. For example, OAuth can report missing credentials, the CLI probe can time out, and Web can report\n`No Claude session key found in browser cookies.` on the same machine. Diagnose the selected source before\nsigning out or replacing credentials.\n\nIf Claude works in Chrome but CodexBar cannot import its browser session:\n\n1. Open `https://claude.ai` in Chrome and confirm the intended account is signed in.\n2. In Settings → Providers → Claude, select **Web API (cookies)** and leave the cookie source on **Auto**.\n   This uses the browser session for session, weekly, and available model-specific quotas.\n3. In Settings → Advanced, confirm **Disable Keychain access** is off. Chromium cookie decryption needs\n   the browser's Safe Storage Keychain item; Claude's OAuth prompt policy controls a different credential.\n4. Explicitly retry the import from Terminal:\n\n   ```bash\n   codexbar cookie refresh --provider claude --allow-keychain-prompt\n   ```\n\n   Approve the expected macOS Keychain prompt for the installed CodexBarCLI and the browser's Safe Storage\n   item. The command never prints cookie values. A prior denial can suppress imports for six hours;\n   this explicit retry can bypass that cooldown, while ordinary refresh attempts may remain suppressed.\n   See [Keychain prompts](keychain-prompts.md) for permission details.\n5. Click **Refresh** in Claude's settings and confirm that **Updated just now** appears with current quota\n   bars and no fetch error. Local cost totals or last-known quota bars alone do not prove a successful refresh.\n\nThis recovers browser-session access; it does not repair missing OAuth credentials. If the refreshed session\ninstead reports a Cloudflare challenge, follow the network guidance under Web API below rather than repeating\nthe cookie import.\n\n## Passive CLI sessions and macOS sleep\n\nAfter each passive request in the reusable CLI session, and when that session closes, CodexBar sends SIGTERM\nonly to `/usr/bin/caffeinate` processes in the session's own process group. The session remains available for\nlater requests. There is no recurring process scan; other process groups and other executables are left alone.\nThis cleanup applies to the reusable PTY session, not the separate one-shot usage or auth-status commands.\n\n## Keychain prompt policy (Claude OAuth)\n\nIf CodexBar reports that its Claude OAuth cache is temporarily unavailable in macOS Keychain, check\nthat the login keychain is unlocked in Keychain Access, then retry Refresh. This guidance does not\nchange the existing prompt policy or switch to another credential source.\n\n- Preferences → Providers → Claude → Keychain prompt policy.\n- Options:\n  - `Never prompt`: never attempts interactive Claude OAuth Keychain prompts.\n  - `Only on user action` (default): interactive prompts are reserved for user-initiated repair flows.\n  - `Always allow prompts`: allows interactive prompts in both user and background flows.\n- Explicit Refresh can repair Claude OAuth Keychain access when direct-read consent is enabled and the policy\n  allows user prompts. Ordinary OAuth fetches remain noninteractive, including with `Always allow prompts`;\n  that policy still governs the existing delegated refresh and experimental reader paths.\n- After a missing-credentials failure, a newer Claude Code credential timestamp replaces the stale error with\n  **Claude Code credentials changed** and a **Refresh** instruction in the menu and OAuth CLI output. Click the\n  existing menu **Refresh** action to retry; direct-read consent and the prompt policy still control access.\n  The freshness probe reads only Keychain attributes with UI disabled and the credentials file's modification\n  time. It never requests the Keychain credential payload, launches Claude, or grants access in the background.\n  A changed timestamp can mean a login or token rotation; it does not prove the credential is valid. Custom\n  credential profiles use their own file timestamp, never the global Claude Code Keychain item. CodexBar saves\n  only the last failure time per profile so the guidance survives restarts and is shared with the bundled CLI;\n  a readable credential clears it. Auto continues to configured CLI/Web sources under their existing access gates.\n- This setting only affects Claude OAuth Keychain prompting behavior; it does not switch your Claude usage source.\n- The policy also applies to the experimental `/usr/bin/security` reader and delegated OAuth refresh through\n  `claude`: background operations that can prompt require `Always allow prompts`.\n- CodexBar's `Always allow prompts` permits future prompts; macOS's **Always Allow** grants access to the current\n  Keychain item. Claude Code can recreate `Claude Code-credentials` and reset that grant. An ACL entry still named\n  CodexBar does not prove that its stored code-signing requirement matches the running binary. `Only on user action`\n  reduces background interruptions but may require a manual Refresh to recover OAuth access. In #3798, a\n  before/after trace shows Claude Code preserving the decrypt ACL's CodexBar entry but removing CodexBar's Team ID\n  from the separate partition ACL. Decrypt-ACL preflight alone cannot establish partition authorization; repeated\n  manual grants therefore need not survive the next Claude Code refresh.\n- If Preferences → Advanced → Disable Keychain access is enabled, this policy remains visible but inactive until\n  Keychain access is re-enabled.\n\n### Debug selection (debug menu enabled)\n- The Debug pane can force OAuth / Web / CLI.\n- Web extras are internal-only (not exposed in the Providers pane).\n- CLI Web enrichment requires matching nonempty account emails; an organization display name alone cannot authorize\n  merging another session's optional usage or spend. OAuth enrichment retains verified organization-UUID matching.\n\n## OAuth API (preferred)\n- OAuth refresh form-encodes credential values, preserving literal plus signs and other reserved characters.\n- Expiry values outside the diagnostic integer range are reported as `out_of_range` without changing credential expiry or refresh decisions.\n- Credentials:\n  - Explicit OAuth environment override, when configured.\n  - CodexBar OAuth cache when available.\n  - File fallback: `~/.claude/.credentials.json`.\n  - Claude CLI Keychain bootstrap/repair fallback: `Claude Code-credentials`.\n- When a CodexBar-owned OAuth cache item's ACL rejects the current build, fresh credentials from an allowed source\n  can replace that cache item using no-UI deletion and creation. A locked or inconclusive Keychain is preserved;\n  failed ACL repairs back off for five minutes. This never deletes or recreates Claude Code's credential item.\n- If CodexBar's cache is temporarily unavailable, automatic refreshes can reuse an unexpired credential already in\n  memory beyond the normal 30-minute cache window, ahead of a stale credentials file. Each refresh retries the\n  persistent cache. Token expiry, profile changes, cache invalidation, and Never prompt still prevent reuse;\n  after a rejected cache write, the next refresh first clears the stale persistent entry, then reuses and persists\n  an unexpired in-memory credential even after 30 minutes once that cleanup succeeds. Extended reuse requires\n  evidence of that exact failed write and its original consent; an unrelated invalidation cannot authorize it.\n  Rejected writes during CodexBar-owned token refresh retain the same recovery, bound to the refreshed credential;\n  a delayed older write cannot replace a newer credential's recovery.\n  This does not discover an external login or enable additional background reads of Claude Code's Keychain item.\n- For the default CLI profile, expired cached or file credentials can adopt a fresh CLI Keychain token after file fallback, even when its fingerprint was already observed during an earlier repair. Existing direct-read consent, prompt policy, cooldown, one-minute freshness-check throttle, and noninteractive-read checks still apply. Custom profiles are not recovered from the unscoped global item, and CLI credentials are never rewritten by this synchronization. Background recovery still requires the Always allow prompts policy; the default Only on user action policy requires an explicit Refresh.\n- Credential selection does not rank unrelated sources by the largest `expiresAt`: expiry establishes validity,\n  not account identity or issuance order. A valid profile file remains ahead of Keychain bootstrap. Keychain candidates\n  are ordered by modification date (creation date as fallback); freshness sync reads only that newest item and never\n  rewrites Claude Code's credentials file. An expired default-profile record can be replaced even when the stored\n  Keychain fingerprint already matches, subject to the access gates above.\n- On Claude Code 2.1.x, `Claude Code-credentials` may contain only MCP server OAuth state (`mcpOAuth`) with no `claudeAiOauth`. CodexBar treats that as an OAuth configuration error, does not run background delegated `claude /status` refresh, and surfaces re-auth guidance. Use Web or CLI usage source, or restore a valid Claude OAuth keychain entry. See #1844.\n- Requires `user:profile` scope (CLI tokens with only `user:inference` cannot call usage).\n- Missing-scope errors require a Claude Code sign-in token with usage access. `claude setup-token` produces a token for model requests and is not a usage-scope recovery step ([Claude Code authentication](https://code.claude.com/docs/en/authentication#generate-a-long-lived-token)). Remove any configured OAuth token override before switching Claude Source to Web/CLI.\n- Endpoints:\n  - `GET https://api.anthropic.com/api/oauth/usage?cedar_ember=1` → usage and saved reset inventory.\n    HTTP 400/403 retries once without the optional query and with the legacy request identity; spending is never skipped.\n    Missing-profile-scope errors, 401, and 429 keep their normal handling without that retry.\n  - `GET https://api.anthropic.com/api/oauth/profile` → account identity used to verify that optional Web enrichment\n    belongs to the same Claude account.\n- Headers:\n  - `Authorization: Bearer <access_token>`\n  - `anthropic-beta: oauth-2025-04-20`\n  - Reset inventory uses `claude-cli/<detected-version> (external, cli)`; eligibility remains server-controlled.\n- Mapping:\n  - `five_hour` → session window.\n  - `seven_day` → weekly window; also becomes the primary fallback when `five_hour` is absent or has no utilization.\n  - `seven_day_sonnet` / `seven_day_opus` → model-specific weekly window.\n  - `limits[].weekly_scoped` → model-specific weekly windows; generic `All models` scopes stay in the main weekly row.\n    OAuth and Web share per-entry parsing: malformed siblings or optional fields do not hide valid model rows. Boolean and string percentages are ignored.\n  - The menu localizes scoped titles as a model name plus weekly duration; canonical snapshot and CLI titles remain unchanged.\n  - Automatic and Session + Weekly menu bar metrics fall back to the most constrained known scoped weekly window when the regular quota windows are missing. Unknown scoped measurements remain unavailable; Extra usage stays a spend-only fallback.\n  - `seven_day_routines` / `seven_day_cowork` → Daily Routines extra window.\n  - Claude Design/Omelette keys are ignored because Claude Design shares the main Claude usage limit.\n  - `extra_usage` → Extra usage cost (monthly spend/limit).\n- Preferences → Providers → Claude → Visible usage items lets you hide the Daily Routines row in menus, the Settings\n  preview, and Overview. The global optional credits and extra usage setting remains its master switch. Hiding this\n  row does not change fetching, history, notifications, widgets, model-scoped weekly limits, hooks, or CLI output.\n- Preferences → Providers → Claude → Show model-specific weekly usage in widgets controls model-scoped weekly quota\n  rows in desktop widgets. It is off by default; turning it on displays every known Claude window with a\n  `claude-weekly-scoped-` identifier (for example, Fable). Turning it back off also drops scoped rows that a previous\n  snapshot persisted. It does not change fetching, the menu, history, notifications, hooks, or CLI output.\n- Refreshing credentials for the same identified account preserves quota-threshold warning history. Verified\n  credential-owner/account bindings also preserve threshold history when active-account metadata temporarily\n  disappears or OAuth falls back to CLI. Threshold warnings re-arm after quota recovers above a threshold and fire\n  on a later downward crossing. Predictive warnings and quota-low hooks keep their existing source-scoped histories;\n  their baselines are not merged with threshold notification state. OAuth/CLI samples without a warning owner use\n  one stable unresolved-account scope, so credential rewrites preserve threshold crossings and predictive warnings\n  remain available. When a stable account identity or verified owner binding becomes available, its threshold scope\n  adopts the newest unresolved history. Later identity gaps reuse the last known account when the reset timestamp\n  is unchanged and remaining quota has not increased. Without that continuity, samples use the stable unresolved\n  scope; once its history has joined an account, subsequent gaps preserve already-fired thresholds rather than\n  starting a new warning episode on every refresh. The first independent unresolved sample can still issue an\n  initial warning. Unverified credential owners remain independent; changing such an owner can still\n  produce an initial warning because account continuity cannot be established.\n- Successful OAuth login enables Claude and preserves the selected usage source. With the default Auto source, OAuth\n  remains preferred when readable, while CLI/Web fallback stays available when OAuth credentials are not usable.\n- Claude Code periodically rotates its `Claude Code-credentials` Keychain item and can replace the ACL grant that\n  allowed CodexBar to read it. Auto treats that as a failed OAuth source, reuses a recent successful CLI result or\n  continues to CLI/Web, and does not misreport the existing credentials as missing. A manual Refresh can re-grant\n  Keychain access; selecting CLI or Web avoids the foreign-Keychain dependency.\n- When every live Auto source fails, CodexBar keeps the last captured session/weekly percentages from\n  `history/claude.json` visible as stale data and shows their capture age instead of blanking the quota bars.\n  Restored history and CLI-scraped percentages both show “Limited usage detail”: the warning describes reduced\n  fidelity, not the source of a historical capture. It does not change sign-in or refresh recovery actions.\n- Plan inference: `subscriptionType` is preferred when present; `rate_limit_tier` falls back to\n  Max/Pro/Team/Enterprise. When a Max `rate_limit_tier` carries a usage multiplier\n  (`default_claude_max_5x` / `default_claude_max_20x`), it is surfaced in the label as \"Max 5x\" / \"Max 20x\".\n\n## Web API (cookies)\n\n### Claude desktop app cookies (opt-in, macOS)\n\nChoose **Usage source → Web** and **Claude cookies → Automatic**, then enable **Use Claude desktop app cookies**.\nSign in to Claude.app first. This option is off by default. The equivalent provider config field is `\"claudeDesktopCookiesEnabled\": true`,\nalso honored by CLI usage. The desktop app source reads `~/Library/Application Support/Claude/Cookies` (or\n`Network/Cookies`) through SweetCookieKit, using `Claude Safe Storage` / `Claude Key` for encrypted values.\n\nDesktop cookie imports are exclusive: a missing or unreadable desktop session never falls back to a browser account.\nExplicitly selected token accounts keep their own credentials; this option applies to automatic-cookie fetches.\nUsage source **Auto** keeps its existing runtime-specific source order; choose **Web** to follow only the desktop session.\nCookies are reimported on each refresh so account switches made in Claude.app are observed; desktop sessions\ndo not read or write the browser cookie cache. The normal shared access gate still forbids prompts from background/settings\nwork, checks matching cookie hosts before Safe Storage access, permits at most one interactive read per manual\naction, and honors denial cooldowns. OAuth and Claude Code login sources are unchanged.\n\n- Optional subscription dates come from `GET /api/organizations/{org_id}/subscription_details` on `claude.ai`,\n  using an existing manual or cached session cookie. They do not come from the quota response or OAuth expiry.\n  `next_charge_at` / `next_charge_date` supply renewal; `plan_ending_at` / `plan_ending_before` supply paid-access\n  expiration and take precedence over renewal. Calendar-only dates stay calendar-only when displayed.\n- The menu and Settings preview reuse the shared subscription row. Missing, unavailable, or unrecognized billing\n  data does not invent a date or fail usage. CLI JSON exposes `subscriptionRenewsAt` / `subscriptionExpiresAt`\n  and a corresponding `...IsDateOnly: true` when the server only supplies a calendar date.\n- OAuth enrichment additionally verifies the OAuth profile's account and organization against the cookie session\n  before and after billing. OAuth credentials alone cannot fetch these dates. No cookie discovery, credential\n  repair, sign-in, or new Keychain access is performed for billing; cookie source Off disables it.\n- App and CLI use the same provider fetch and normal account-scoped publication. Billing has a separate two-second\n  total budget after quota succeeds; expiry, errors, or a changed verified owner leave successful quota intact.\n  No delayed billing task writes back into an already published snapshot, and dates are not carried across refreshes.\n- Availability is determined by the authenticated billing response, not the Pro/Max/Team/Enterprise plan label.\n  The fixtures cover the reported subscription schema, including cancellation and absent dates; they do not\n  establish that every plan or organization role can access this endpoint. Team/Enterprise billing access and\n  live cancelled subscriptions remain unverified. Quota resets and Extra usage balances are independent.\n- Session quota warnings ignore a weekly quota promoted into the primary field when the five-hour payload is missing. Existing session warning history stays tied to its account, and weekly warnings continue independently.\n- Preferences → Providers → Claude → Cookie source (Automatic or Manual).\n- Manual mode accepts a `Cookie:` header from a claude.ai request.\nEach Claude token account can optionally select a **Claude config directory** in Settings → Providers → Claude.\nWith Track costs enabled, the active account’s local token and API-price cost estimates scan only its `projects` history,\nwithout default, claude-swap, or Pi history. Selecting a directory declares that its history belongs to the account;\nCodexBar does not infer ownership or detect mixed/copied history beyond existing scanner deduplication. A missing or\nunreadable history directory leaves its estimate unavailable. Clear the mapping to restore the existing combined\nview; unmapped accounts and claude-swap retain their current behavior. The optional `claudeConfigDirectory` field\nis stored on the token account in the config file and does not change quota fetching or credential resolution.\n\n- Multi-account manual tokens: add entries to `~/.codexbar/config.json` (`tokenAccounts`) and set Claude cookies to\n  Manual. The menu can show all accounts stacked or a switcher bar (Preferences → Advanced → Display).\n- In Settings → Providers → Claude, **Edit** changes a saved account’s label and credential in place.\n  The credential field is masked and starts empty; leave it blank to keep the saved credential, or enter a\n  replacement and choose **Save**. **Cancel** discards the draft. Up/down controls persist account order in\n  the existing config file and menu switcher. Edits and moves preserve the selected account and account ID,\n  including its Claude config directory. Other providers with manual token accounts share the editor; editing\n  a dormant account does not select it.\n- Claude token accounts accept either `sessionKey` cookies or OAuth access tokens (`sk-ant-oat...`). OAuth-token\n  accounts route to the OAuth path and disable cookie mode; session-key or cookie-header accounts stay in manual\n  cookie mode. The exact edge-routing rules are documented in\n  [docs/refactor/claude-current-baseline.md](refactor/claude-current-baseline.md).\n- Cookie source order:\n  1) Safari: `~/Library/Cookies/Cookies.binarycookies`\n  2) Chrome/Chromium forks: `~/Library/Application Support/Google/Chrome/*/Cookies`\n  3) Firefox: `~/Library/Application Support/Firefox/Profiles/*/cookies.sqlite`\n- Domain: `claude.ai`.\n- Linux: the web source supports an explicitly configured manual `sessionKey` cookie. Automatic browser import,\n  including Firefox import, remains unavailable. This enables the same API request path as macOS; it does not\n  bypass Cloudflare challenges, refresh Claude Code OAuth credentials, or guarantee that a browser session will\n  work outside the browser. Use the OAuth source when the web request is challenged.\n- Linux CLI Auto mode: an existing valid manual cookie makes Web eligible ahead of CLI after upgrading.\n  Web success returns without launching Claude Code; authentication rejection or a Cloudflare challenge falls\n  back to an available CLI using the existing Auto policy. Cancellation stops without launching the CLI.\n  Explicit Web mode does not fall back. Use explicit OAuth mode for passive polling that must not launch the CLI.\n- Linux CLI Auto mode: the plan is Web → OAuth → CLI PTY. OAuth runs when Claude Code OAuth credentials\n  (`~/.claude/.credentials.json`) are usable without a prompt, so a logged-in user does not launch an interactive\n  `claude` session. Without usable OAuth credentials, the OAuth step is skipped and CLI remains the fallback.\n  OAuth request failures such as 401 or 429 also fall back to the CLI; cancellation stops without launching it.\n- Cookie name required:\n  - `sessionKey` (value prefix `sk-ant-...`).\n- Cached cookies: Keychain cache `com.steipete.codexbar.cache` (account `cookie.claude`, source + timestamp).\n  Reused before re-importing from browsers.\n- After replacing an expired cached cookie, failures from the recovered session retain their actual error type,\n  including temporary network failures, server outages, Cloudflare challenges, and cancellation. The original\n  sign-in error is retained only when browser recovery itself fails to find a usable session.\n- API calls (all include `Cookie: sessionKey=<value>`):\n  - `GET https://claude.ai/api/organizations` → org UUID.\n  - `GET https://claude.ai/api/organizations/{orgId}/usage?cedar_ember=1` → session/weekly/opus, plus limit-reset\n    grants in the `cedar_ember` block. Rejected requests, including ordinary 403 responses, retry once without\n    `cedar_ember=1`, so unsupported reset queries keep the usage windows.\n    Success, 401, 429, and recognized Cloudflare challenges retain their normal handling without a retry.\n  - `GET https://claude.ai/api/organizations/{orgId}/overage_spend_limit` → Extra usage spend/limit.\n  - `GET https://claude.ai/api/organizations/{orgId}/prepaid/credits` → remaining Usage credits balance.\n  - `GET https://claude.ai/api/account` → email + plan hints.\n- Outputs:\n  - Session + weekly + model-specific percent used.\n  - A missing Web session stays absent instead of becoming a synthetic 0% session; measured weekly and extra windows stay visible. Older cached placeholders remain readable.\n  - Daily Routines extra window when returned by the usage API.\n  - Extra usage spend/limit (if enabled). Compact Overview keeps this section when no measured quota bars exist, including Enterprise accounts with unavailable limits.\n  - Remaining Usage credits balance (if enabled). Custom menu bar layouts can show it with the **Balance** element\n    beside the quota windows; the default menu bar text keeps showing quota. Turning off **Show credits & extra\n    usage** hides it from the menu card and the Balance element immediately. Otherwise a fresh snapshot without a\n    successful prepaid credits response has no balance, while a failed refresh keeps the last snapshot and its balance.\n  - Account email + inferred plan.\n  - Limit Reset Credits (see below).\n- A Cloudflare challenge on `claude.ai` is a network-path restriction, not a stale-cookie signal. CodexBar keeps the\n  cached cookie and prior quota snapshot, identifies the challenge, and links to Settings. Select OAuth for live\n  quota windows on that network (the web-only Usage credits balance is unavailable), or try a different network.\n  Explicit Web mode remains terminal and never reads OAuth credentials as a fallback.\n- Limit Reset Credits (\"Reset for free\" in Claude Settings > Usage), Web and OAuth sources:\n  - These are saved resets a user can redeem, separate from the session and weekly reset timestamps already\n    supplied by Web, OAuth, and CLI. Existing cookie settings and source selection govern all Web access; this\n    feature does not enable cookies, broaden browser discovery, or initiate Web enrichment.\n  - Read from `cedar_ember` in the same usage response as the session and weekly windows, so they share its session\n    and organization. Observed on a personal Pro/Max account; Team and Enterprise organizations are not verified.\n  - The count sums `resets_left` over grants that, at refresh time, are not paused, have started, and have not\n    expired. An expiry that passes before the next refresh drops that reset from the display. `usable_now` is not\n    consulted, so a saved reset still counts while Claude gates its use.\n  - Requires `eligible: true`. A grant with an unreadable `resets_left`, `resets_total`, `paused`, `starts_at`, or\n    `ends_at` is dropped. More than 50 available resets or more than 200 grant records show nothing. The usage\n    windows are unaffected either way.\n  - Menu: a live `Limit Reset Credits` section with its own visibility choice. An upgrade preserves a previously\n    disabled global optional-usage preference by initially hiding this section; it can then be enabled independently.\n    CLI and `codexbar serve`: a `Limit Reset Credits` row in `usage.details` (`N available`, next expiry).\n  - Live-only: grant IDs are never decoded, the usage request skips the URL cache, and cached or synced snapshots do\n    not restore the inventory. A reset used on claude.ai disappears at the next successful refresh.\n  - Source precedence stays unchanged: credits appear only from the source supplying the primary usage snapshot. CLI\n    does not report saved reset credits, and optional Web enrichment never adds Web credits to another source,\n    even when the account matches. The menu replaces the generic details row with one shared reset-credit section.\n    CodexBar never redeems a reset; use Claude on the web or Claude Desktop.\n\nEnterprise spend details depend on the selected source. Auto stops at the first successful OAuth/CLI/Web result;\nit does not import a missing monthly spend cap from a different session. Select **Web API (cookies)** for\nbrowser-only billing details, and enable **Show credits and extra usage** to display them.\n\n## Extra usage rollover\n\nFor Claude spend-only accounts, positive monthly caps use the shared usage-window presentation: one Meter bar,\nwith Auto, Extra usage, and the card using the same cap percentage and reset. The card retains its spend/limit and\nprepaid-balance caption. Accounts with quota windows keep their separate Extra usage section, including in the\nmenu-card-sections layout. Uncapped balances keep their compact Credits row; Admin API spend is unchanged.\nExtra usage still follows its existing visibility choice and the global optional-usage switch. Pace follows the\nexisting pace setting, independently of redeemable Limit Reset Credits.\n\nClaude does not report the cap reset. CodexBar infers midnight UTC on the first of the next month from the cost\nobservation, applying [Anthropic's documented spend-limit period](https://platform.claude.com/docs/en/manage-claude/spend-limits-api)\nto all Claude monthly caps. Reset text uses local time (November 1 UTC can appear as October 31 in the Americas);\nthe metric tooltip identifies the inference. Shared calendar-month pace uses the actual 28–31-day month.\nChanging a cap changes the percentage, not its rollover. A stale observation does not advance into a new month.\n\n## Cloud-session credits\n\n- OAuth and Web usage responses can supply promotional cloud-session credit in `iguana_necktie`.\n  CodexBar shows a separate **Cloud credits** balance row in the menu and a detail section in the CLI when\n  optional credits/extra usage is enabled. The menu row matches the prepaid **Credits** row and shows only the\n  remaining balance; the allowance, progress, and expiry are available in CLI output and the widget snapshot's\n  optional `details` field. CLI JSON exposes the section through `usage.details`, including numeric progress and\n  remaining dollars. No additional request, login, or browser\n  discovery is needed for these credits.\n- `limit_dollars`, `used_dollars`, and `remaining_dollars` are already USD amounts. They are never divided by 100,\n  added to prepaid Extra usage, counted as local spending, or used for quota pacing. The reported remaining amount\n  wins when present; otherwise it is derived from the allowance and reported used dollars.\n- `resets_at` denotes expiration, not a recurring quota reset. The section shows an absolute UTC timestamp\n  (`Z`); cached details retain the last observation and its expiry. Exhausted credits retain a zero balance;\n  credits already expired at observation or locked by the provider are labeled expired/unavailable. The menu\n  also labels a cached balance expired once its stored expiry has passed.\n- Missing or malformed credit blocks omit this section without failing ordinary usage. The response determines\n  availability, without a Pro/Max plan-name gate. CLI-probe-only results do not include cloud credits, and optional\n  Web enrichment preserves the primary source's credits rather than importing Web credits into OAuth/CLI results.\n- Settings → Providers → Claude → Visible usage items can hide **Cloud credits** independently. The optional\n  credits/extra usage setting remains its master switch; the individual visibility choice does not change CLI output.\n- The response shape is based on [Pane's implementation and live-shape test](https://github.com/ItsJazii/pane/blob/beb4bbfd4e7c776d970a56d254e8cce4d61154d9/src-tauri/src/providers/claude.rs#L443).\n  CodexBar's fixtures validate parsing and presentation; they are not independent live Pro/Max verification.\n\n## claude-swap accounts (opt-in)\n\nThe accepted multi-account design in\n[claude-multi-account-and-status-items.md](claude-multi-account-and-status-items.md).\n\n- Setup: Preferences → Providers → Claude → \"Read accounts from claude-swap\", then set the path to the\n  [`cswap`](https://github.com/realiti4/claude-swap) executable (for example `~/.local/bin/cswap`) in the field\n  directly beneath the enabled toggle. The path field and its help are hidden while the integration is off.\n- tcr source: [tcr](https://github.com/dhkts1/teamclaude-rs) prints the same list schema v1 from `tcr list --json`.\n  Pick \"tcr\" in the Source picker shown while the integration is on (config key `claudeSwapSource: \"tcr\"`) and set\n  the path to the `tcr` executable. CodexBar then runs `tcr list --json` and `tcr --version` with the same bounds,\n  parser and isolation rules; cards are labelled `tcr` and identity is `tcr:<slot>`. tcr pools its accounts and\n  routes each request itself, so its list reports no active account and `supportsAccountSwitching: false`: no row\n  offers Switch or Re-authenticate, and CodexBar never sends tcr a switch command. Late list results from a\n  previously selected source are discarded before publishing cards, refresh times, or forecast history, including\n  when the initial history load is still pending.\n- tcr forecast history requires both a nonempty email and `organizationUuid` in the list row. When both are\n  supplied, it uses the same normalized email/organization hash as claude-swap; display names and slot numbers\n  never substitute for an organization UUID. The current tcr contract fixture omits `organizationUuid`, so those\n  accounts show usage without a history-based forecast.\n- Version detection retries after a failed or cancelled startup probe; replaced refreshes cannot overwrite a newer\n  result, and disabling the adapter or changing its executable clears the previous detected version.\n- Behavior: on each Claude refresh, CodexBar runs `cswap --list --json` independently of the ambient Claude fetch (no\n  shell, fixed arguments, bounded runtime and output), requires `schemaVersion == 1`, and parses only slot number,\n  active state, usage status, email, `organizationUuid`, display-only `organizationName` (always present, may be empty),\n  optional display-only `alias` when non-empty, the 5-hour/7-day windows, and optional display-only model-scoped\n  weekly windows from `usage.scoped`, optional `usage.spend`, and source measurement times. Identity stays\n  `claude-swap:<slot>`; organization name and alias are never\n  used as identity. When two or more slots share an email, cards append ` · organizationName` or ` · Account N`;\n  a user-chosen cswap alias replaces that label. Unique emails stay email-only.\n- Menu and terminal cards can show the source's `lastGoodUsage` when live usage is unavailable. Its required\n  `lastGoodFetchedAt` remains the measurement time, and the card shows a last-known marker and capture age alongside\n  the diagnostic. Terminal cards also show the capture timestamp. Malformed additive spend or last-good fields do not\n  discard valid live quota windows. The free-form row `message` is not parsed.\n  Brief terminal output keeps the diagnostic and excludes historical metrics from its warnings and next-reset summary.\n- Last-known usage keeps its provenance through quota retention and cache reload. It never drives the menu-bar icon,\n  whose compact display cannot show its age. Compact account rows show the capture age and do not recommend or fold\n  last-known rows into the ready-account group. Explicit source measurements take precedence over locally retained\n  quota windows.\n- Slots excluded from claude-swap's automatic rotation are marked `(disabled)` but remain valid explicit switch\n  targets. The active account label is emphasized.\n- Display: when claude-swap reports more than one account, its accounts replace ambient/token-account Claude cards.\n  The app honors **Menu → Multi-account layout**: Segmented shows account buttons and one active account card;\n  pending or failed switches show the requested account's details while the active marker stays source-owned.\n  Expired or otherwise unavailable accounts remain inspectable without activation; selecting the active account\n  returns to its card. If the adapter reports no active account, the menu says so instead of selecting the first row.\n  Buttons wrap into two rows above three accounts. Hide Personal Info uses stable `Account N` slot labels across\n  segmented buttons, native account cards, compact menu rows, and accessibility text, including unavailable accounts.\n  These numbers come from validated claude-swap slots and remain stable when accounts are reordered; aliases,\n  organization names, and email addresses stay hidden.\n  Stacked shows one card per account (active account first, then numeric slot). With four or more\n  accounts the stacked menu switches to a compact layout (`AccountMenuLayoutPlanner`): the active account keeps its full\n  card, inactive accounts become one-line rows sorted by remaining headroom (most constrained first, red/amber below\n  50%/10% left, a star on the healthiest activatable account), and healthy rows fold behind a \"N more accounts ready\"\n  summary row. Clicking a compact row expands that account's full card; clicking an inactive card collapses it again.\n  This choice is remembered locally across menu opens and app restarts. The summary row reveals the hidden rows for\n  the current menu session. `codexbar cards` keeps the full per-account output. The same compact layout applies to\n  every stacked multi-account list (token accounts on any provider, and flat Codex account lists; workspace-grouped\n  Codex lists keep their sectioned stacked layout). To use this\n  presentation with one account, enable “Show account card when only one account is available” or set\n  `claudeSwapShowSingleAccount: true` on the Claude provider in the resolved config file (normally\n  `~/.config/codexbar/config.json`; legacy installs may use `~/.codexbar/config.json`). The option defaults off,\n  zero accounts still use the ambient presentation, and account identity is `claude-swap:<slot>`, never the display\n  email.\n- Session forecasts use each account's own recorded usage. History keys hash the normalized email and organization\n  UUID from the existing list response, so moving slots preserves history and the same email in different workspaces\n  stays separate. Empty organization UUIDs identify personal accounts; an email change starts a new history.\n  Raw emails and organization UUIDs are not saved in history. Fresh samples retain their source timestamps and the\n  shared 17,520-sample limit per series; last-known or failed rows do not add samples. Ambient Claude history is separate.\n- Provider widgets follow the active claude-swap account under the same multiple-account/single-account presentation\n  rule. Successful list refreshes and clearing the adapter publish a widget snapshot even with account widgets off.\n  Retained quota keeps its original measurement time and is bound to the slot's opaque owner fingerprint; unavailable\n  or replaced accounts never borrow another account's quota. Local cost history remains combined across Claude homes.\n- Terminal scope: cards expands the account list, while Auto `codexbar usage` follows its active slot on every\n  supported CLI platform (including single-slot lists). An explicit\n  Claude provider or `--source auto` remains eligible, while `--account`, `--account-index`, `--all-accounts`, and\n  explicit non-auto source flags bypass the adapter. A configured non-auto source also bypasses it for `usage`.\n  Unavailable active-slot usage falls back to ordinary source selection with an explicit adapter fallback source label;\n  the returned identity belongs to that fallback source. Serve `/usage`/`/cost` remain unchanged,\n  while `codexbar dashboard` and `GET /dashboard/v1/snapshot` additionally nest one entry per swap account in the\n  Claude provider row, with full identity by default or redacted email local parts when `--identity redacted` is set.\n- Isolation: CodexBar never reads claude-swap or Claude Code credential storage for this feature; the\n  subprocess handles its own credential access. In the app, adapter failures keep the last successful accounts as\n  stale data, surface the error in provider settings, and never affect the ambient Claude usage card. In terminal\n  cards, a list failure retains the current ambient output, adds a distinct `Claude (claude-swap)` footer entry, and\n  exits non-zero.\n- Sentinel statuses (`token_expired`, `relogin_required`, `api_key`, `keychain_unavailable`, `no_credentials`,\n  `foreign_credential`, and unknown future values) retain per-account diagnostics. Full cards can show explicitly\n  reported last-known usage alongside its age; without it they remain notes-only. Brief terminal cards keep the\n  diagnostic. When no explicit last-good measurement is supplied and a window is still at 100%, CodexBar keeps that slot's last\n  projected usage bars and names the exhausted window (5-hour session, 7-day weekly, and/or a scoped model such as\n  Fable) plus its reset time — not \"Usage fetch failed.\" A first refresh that is already `unavailable` with no\n  retained windows says usage is unavailable, without assuming why the source could not fetch it. Active rows are marked `[active]`; no claude-swap row infers\n  a plan badge.\n- Read-only adapters may set top-level `supportsAccountSwitching: false` in their schema-v1 list response. Usage,\n  account details, and active markers remain visible, while switching and re-authentication actions are suppressed.\n  Omitting the capability preserves existing switching behavior; a present value must be a JSON boolean.\n- Switching: an inactive account with usable source credentials shows “Switch Account…”. Clicking it runs exactly\n  `cswap --switch-to <slot> --json`, validates the versioned result and requested slot, then refreshes both ambient\n  Claude usage and every claude-swap account card. Switches are serialized; no automatic switching occurs. While\n  reconciling, the ambient Claude refresh is given five seconds to finish; a stalled refresh continues in the\n  background while switching waits for the adapter's active-account list, so it cannot leave the account chips inert.\n  A later switch can refresh the adapter list even when its ambient refresh is queued behind an earlier probe. While\n  claude-swap owns account presentation, the separate ambient OAuth action reads “Sign in with Claude Code…” and does\n  not add or switch a claude-swap account.\n- Expired, missing, unknown, or Keychain-inaccessible credentials stay non-actionable. A failed switch remains visible\n  on that account without discarding its last successful usage. A running Claude Code process can take up to the\n  claude-swap Keychain cache interval to observe the new account.\n- A `foreign_credential` row explains that the live credential belongs to another account. An inactive row can use\n  the existing explicit slot switch. An active row offers **Re-authenticate**, which runs the same\n  `cswap --switch-to <slot> --json` command to let claude-swap reconcile its own credential state, without `--force`. Clicking the active segment\n  still only inspects it; repair requires its explicit button.\n- Multiple claude-swap accounts—and a single account when explicitly enabled—take precedence over Claude\n  token-account presentation (stacked cards and the segmented switcher).\n\nPackaged synthetic proof (fake `cswap` executable, no real accounts or credentials):\n\n![Stacked claude-swap account cards](screenshots/claude-swap-accounts-synthetic-proof.png)\n\nModel-scoped weekly-window proof (synthetic data, no real accounts or credentials):\n\n| Before | After |\n| --- | --- |\n| ![claude-swap card before scoped windows](screenshots/claude-swap-scoped-before.png) | ![claude-swap card with a Fable scoped weekly window](screenshots/claude-swap-scoped-after.png) |\n\n## CLI PTY (fallback)\n- Runs `claude` in a PTY session (`ClaudeCLISession`).\n- The PTY and replay screen share a 160-column,\n\n<!-- File: docs/copilot.md (Tokens: ~1684 | Category: copilot-instructions) -->\n---\nsummary: \"Copilot provider data sources: GitHub device flow, Copilot internal usage API, and optional GitHub web budgets.\"\nread_when:\n  - Debugging Copilot login or usage parsing\n  - Updating GitHub OAuth device flow behavior\n---\n\n# Copilot provider\n\nCopilot uses GitHub OAuth device flow and the Copilot internal usage API for primary usage. Optional budget extras use GitHub web cookies only when enabled.\n\nCLI-only setups can run `codexbar config login --provider copilot` (or add `--json` for a stream of login\nevents). It uses the same device flow, issuer-aware account matching, and config-backed token accounts as\nthe app, without opening a browser or prompting for Keychain access. See [CLI configuration](cli-configuration.md#copilot-device-login).\n\n## Data sources + fallback order\n\n1) **GitHub OAuth device flow** (user initiated)\n   - Device code request:\n     - `POST https://github.com/login/device/code`\n   - Token polling:\n     - `POST https://github.com/login/oauth/access_token`\n   - Optional enterprise host:\n     - set Copilot `enterpriseHost` in `~/.codexbar/config.json` or the provider settings UI\n     - CodexBar normalizes values such as `https://octocorp.ghe.com/login` to `octocorp.ghe.com`\n     - device flow uses `https://<enterpriseHost>/login/...`\n     - identity lookup uses `https://api.<enterpriseHost>/user`, matching the usage API host\n     - default HTTPS ports and trailing host dots normalize to the same issuer; nondefault ports remain distinct\n   - Scope: `read:user`.\n   - Token stored in config:\n     - `~/.codexbar/config.json` → `providers[].apiKey` for `copilot`\n     - token accounts use `providers[].tokenAccounts`\n     - Legacy token matching honors a resolved stable GitHub user ID; display-label fallback is used only when token identity is unavailable and no verified match exists.\n     - Enterprise accounts match by API host and numeric user ID. They never adopt a public or unidentified legacy account merely because its login or label matches. The configured host still controls requests; stored account identifiers do not select an endpoint.\n     - Enterprise sign-in requires a resolved identity. A cancelled sign-in or a host change while it is pending leaves saved accounts unchanged.\n\n2) **Usage fetch**\n   - `GET https://api.github.com/copilot_internal/user`\n   - With an enterprise host, the API host is `api.<enterpriseHost>`.\n   - Headers:\n     - `Authorization: token <github_oauth_token>`\n     - `Accept: application/json`\n     - `Editor-Version: vscode/1.96.2`\n     - `Editor-Plugin-Version: copilot-chat/0.26.7`\n     - `User-Agent: GitHubCopilotChat/0.26.7`\n     - `X-Github-Api-Version: 2025-04-01`\n\n3) **Budget fetch** (optional GitHub web endpoint, best-effort)\n   - Available only for the public GitHub host. Enterprise quota fetches skip public identity and browser-cookie budget enrichment.\n   - Disabled by default. The Copilot provider's \"Budget extras\" setting must be enabled before CodexBar imports\n     github.com cookies or renders budget bars.\n   - Automatic cookie imports follow the shared supported-browser order and access gate; background imports never request Keychain permission.\n   - CodexBar asks the logged-in GitHub web endpoint for customer-scope budgets:\n     - `GET https://github.com/settings/billing/budgets?page=<page>&page_size=10&scope=customer`\n   - Headers:\n     - `Cookie: <github.com browser cookies>`\n     - `Accept: application/json`\n     - `X-Requested-With: XMLHttpRequest`\n     - `GitHub-Verified-Fetch: true`\n     - `X-Fetch-Nonce: <fresh nonce when available>`\n   - CodexBar first tries to read a fresh nonce from `https://github.com/settings/billing/budgets`, then calls the JSON\n     endpoint. If GitHub rejects the web request, CodexBar keeps the normal Copilot quota bars and omits budget bars.\n   - This is intentionally not the public GitHub REST billing API. The REST API did not expose the personal budget list\n     for the tested individual account.\n\n## Snapshot mapping\n- Primary: `quotaSnapshots.premiumInteractions` percent remaining → used percent.\n- Secondary: `quotaSnapshots.chat` percent remaining → used percent.\n- Extra: positive Copilot billing budgets from the GitHub web endpoint → `extraRateWindows`, only when \"Budget extras\"\n  is enabled.\n  - Product budget: `copilot`\n  - SKU budgets: `copilot_premium_request`, `copilot_agent_premium_request`, `spark_premium_request`\n- Seat AI credits: `quota_snapshots.premium_interactions.credits_used` → the shared \"Credits used\" provider-detail\n  row, shown only when it carries real signal (token-based billing, unlimited quota, nonzero credits, or a\n  configured seat entitlement) so a metered Pro/Individual seat never grows a permanent \"0 credits used\" row.\n  Deliberately not summed with `chat`/`completions` credits — GitHub can report the same pool under multiple\n  snapshot keys, and summing would double-count.\n- Reset dates are not provided by the API.\n- Plan label from `copilotPlan`.\n\n## AI credit entitlements\nGitHub does not publish an included-credit entitlement on any documented endpoint — all 8 billing endpoints plus\n`budgets`, `cost-centers`, and `usage/summary` were probed, and none returns a ceiling for the seat. The denominator\nis therefore user-entered:\n- Preferences → Providers → Copilot → \"Included AI credits (per seat)\"\n\nA configured entitlement turns the row into a progress bar (\"31 / 3000\") via the shared provider-detail row's\noptional progress ratio. Without one, the row stays plain text, because a bar would imply a limit CodexBar cannot\nactually know. Either way the row also carries the numeric credits used (`usageValue`), so editing or clearing\nthe entitlement rewrites the cached row (text ↔ bar) immediately, even when the follow-up refresh never lands\n(offline, token lost, 401).\n\nIn Automatic mode, the provider tab uses this configured seat-credit ratio when no metered quota window is\navailable. It follows the used/remaining preference. Existing quota windows retain priority, explicit metric\nselections retain their meaning, and a missing allowance leaves the tab's progress bar absent.\n\n## Manual seat credit allowance\n\nFor token-billed seats, **Seat AI credit allowance** optionally turns the menu card's Credits used text into a progress\nbar. Enter a positive monthly credit allowance; no ceiling is inferred from the plan. This is a local display preference,\nseparate from Budget extras, and does not set a GitHub billing limit. Icon, widget, and CLI quota windows are unchanged.\n\nEach saved account can override the legacy global allowance. Clearing an override restores that fallback, or returns to\ntext if no fallback exists. **Clear default allowance** removes the legacy fallback while retaining explicit account\noverrides; accounts inheriting that default return to text immediately, including while offline. Edits update the matching cached account without\nchanging its usage timestamp. Switching accounts preserves each account's usage; changing a credential or enterprise\nhost still invalidates its cached data. A refresh already in flight uses the current allowance when it publishes, so a\nlate successful response cannot restore a cleared default.\n\n## Key files\n- `Sources/CodexBarCore/Providers/Copilot/CopilotUsageFetcher.swift`\n- `Sources/CodexBarCore/Providers/Copilot/CopilotDeviceFlow.swift`\n- `Sources/CodexBarCore/Providers/Copilot/CopilotCreditEntitlementParser.swift`\n- `Sources/CodexBar/Providers/Copilot/CopilotLoginFlow.swift`\n- `Sources/CodexBar/CopilotTokenStore.swift` (legacy migration helper)\n</yakaai_skills>"}