diff --git a/AGENTS.md b/AGENTS.md index 9095c30645..ce4be4ca00 100755 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,46 +1,79 @@ # Repository Guidelines -## Project Overview - -- Windows desktop tray app for AI-provider usage and limits (Win-CodexBar port of CodexBar). -- Default product surface: Tauri 2 desktop shell in `apps/desktop-tauri`, not the CLI. -- Shared domain/backend and CLI live in the `rust/` crate `codexbar`. -- Material under `docs/` that describes the upstream macOS/Swift project is historical unless the task is explicitly about upstream parity. -- When repo docs conflict, trust active sources: `apps/desktop-tauri` plus `rust/src`. - -## Architecture & Data Flow - -- Cargo workspace (root `Cargo.toml`): members `rust`, `apps/desktop-tauri/src-tauri`; **default-member** is the Tauri crate. -- Path dependency: `codexbar-desktop-tauri` → `codexbar = { path = "../../../rust" }`. -- Frontend: React 18 + Vite in `apps/desktop-tauri/src/`. Typed invoke bridge in `src/lib/tauri.ts`; DTOs in `src/types/bridge.ts`. -- Surfaces: the hidden `main` webview routes by window label / surface mode — TrayPanel, Settings, FloatBar. Settings, float bar, and the tray-panel flyout use detached windows. The flyout's TrayPanel is the only dashboard layout; the legacy PopOut layout is retired (`SurfaceMode::PopOut` remains only as a data key). -- **Provider refresh**: `codexbar::core::instantiate_provider` (`rust/src/core/provider_factory.rs`) → `Provider::fetch_usage` → shell `commands/providers.rs` (semaphore + timeout) → `AppState.provider_cache` → events → React `useProviders`. -- **Settings**: `%config%/CodexBar/settings.json` via `Settings::load` / `save` and `secure_file` (DPAPI-capable on Windows). Frontend `updateSettings` patch → save → `codexbar:settings-updated` / float-bar config events. -- **Tray**: `tray_bridge` + `tray_menu`. Icon pixels from shared `codexbar::tray::{render_bar_icon_rgba, render_percent_icon_rgba}`. -- **Float bar**: `floatbar/` owns the auxiliary always-on-top window. The builder must pin `.theme(Some(tauri::Theme::Dark))` — WebView2 resolves `prefers-color-scheme` on a shared process profile; an unpinned window flips other webviews under theme `auto`. -- **Proof harness**: env `CODEXBAR_PROOF_MODE` (e.g. `settings:menu`) opens a target surface and suppresses blur-dismiss for automation / CUA capture. `CODEXBAR_SEED_USAGE_JSON=` seeds one synthetic bridge-shaped Codex `ProviderUsageSnapshot` into the provider cache at launch (pinned against refresh eviction; malformed files are warned about and skipped). - -## Key Directories - -- `apps/desktop-tauri/src/` — React UI (surfaces, hooks, i18n, bridge types) -- `apps/desktop-tauri/src-tauri/src/` — Tauri shell (`main`, tray, floatbar, shell windows, commands, proof_harness) -- `rust/src/core/` — `ProviderId`, `Provider` trait, `instantiate_provider`, fetch context -- `rust/src/providers/` — one module per provider (fetch/parse/auth) -- `rust/src/settings/` — settings model and load/save -- `rust/src/browser/` — Windows browser detection + cookie extraction -- `rust/src/tray/` — shared tray-icon renderer -- `rust/src/cli/` — CLI subcommands (`codexbar` binary) -- `scripts/` — `dev.ps1`, `local-check.ps1`, release and smoke scripts -- `docs/` — Windows port docs (`ARCHITECTURE`, `CLI`, `CONFIGURATION`, `PROVIDERS`, `BUILDING`, `COOKIES`, `WINDOWS_PROOF`, ADRs). Upstream macOS docs are read-only reference only. -- `.circleci/config.yml` — primary hosted Windows PR/push gate; `.github/workflows/pr-check.yml` — manual Blacksmith Windows reserve; `.github/workflows/interaction-guard.yml` — lightweight GitHub-hosted policy guard. - -## Development Commands - -```text -# Local CI slice (mirrors hosted PR check) +Win-CodexBar is the Windows port of CodexBar: a tray app that shows AI-provider usage and limits. This file holds the rules for working in this repo. Long reference material lives in `docs/`; the [docs index](#docs-index) says where. + +## How to use this file + +- Read the code before you rely on memory or on a doc. The sources of truth are `apps/desktop-tauri` and `rust/src`. When a doc and the code disagree, the code wins; fix the doc. +- Precedence: the user's instructions in the current task come first, then this file, then other docs. Upstream macOS docs under `docs/` are historical unless the task is about upstream parity. +- Do the reversible work first (reading, editing, local builds and tests, local commits). Ask the user before anything in [Ask first](#ask-first). + +## Project overview + +- The product is the Tauri 2 desktop app in `apps/desktop-tauri`, not the CLI. +- The shared backend and the CLI are the `rust/` crate `codexbar`. +- The root `Cargo.toml` is a workspace with members `rust` and `apps/desktop-tauri/src-tauri`. The Tauri crate is the default member, so plain `cargo` commands from the repo root target the desktop app. Use `cd rust` only for CLI or backend-only work. +- The Tauri crate depends on `codexbar = { path = "../../../rust" }`. +- The frontend is React 18 and Vite in `apps/desktop-tauri/src/`. The typed invoke bridge is `src/lib/tauri.ts`; its DTOs are in `src/types/bridge.ts`. + +## Architecture and data flow + +[docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) has the full picture. The rules that matter on every change: + +- **Windows.** The hidden `main` webview routes by window label. Settings, the float bar and the tray-panel flyout are separate windows. The flyout's TrayPanel is the only dashboard layout. The old PopOut layout is retired; `SurfaceMode::PopOut` remains only as a data key. +- **Provider refresh.** `codexbar::core::instantiate_provider` (`rust/src/core/provider_factory.rs`) builds the provider, then `Provider::fetch_usage` runs from the shell's `commands/providers.rs` under a semaphore and a timeout. Results go to `AppState.provider_cache`, then to events, then to the React hook `useProviders`. +- **Settings.** `%APPDATA%\CodexBar\settings.json` is read and written by `Settings::load` / `save` through `secure_file`, which wraps it with DPAPI on Windows. The frontend sends an `updateSettings` patch; the shell saves it and the UI reloads on `codexbar:settings-updated` and the float-bar config events. +- **Tray.** `tray_bridge` and `tray_menu` own the icon and the menu. The icon pixels come from the shared `codexbar::tray::{render_bar_icon_rgba, render_percent_icon_rgba}`. +- **Float bar.** `floatbar/` owns the always-on-top window. Its builder must pin `.theme(Some(tauri::Theme::Dark))`. WebView2 resolves `prefers-color-scheme` on a process profile shared by all windows, so an unpinned float bar flips the other windows when the theme is `auto`. Call this failure the **theme flip**. +- **Proof harness.** `CODEXBAR_PROOF_MODE` (for example `settings:menu`) opens one surface and turns off blur-dismiss for automation. `CODEXBAR_SEED_USAGE_JSON=` seeds synthetic bridge-shaped `ProviderUsageSnapshot` data into the provider cache at launch, pinned against refresh eviction. A malformed seed file logs a warning and is skipped. See [docs/WINDOWS_PROOF.md](docs/WINDOWS_PROOF.md#proof-mode). + +## Key directories + +| Path | Contents | +| --- | --- | +| `apps/desktop-tauri/src/` | React UI: surfaces, hooks, i18n, bridge types | +| `apps/desktop-tauri/src-tauri/src/` | Tauri shell: `main`, tray, floatbar, shell windows, commands, proof_harness | +| `rust/src/core/` | `ProviderId`, the `Provider` trait, `instantiate_provider`, fetch context | +| `rust/src/providers/` | One module per provider: fetch, parse, auth | +| `rust/src/settings/` | Settings model, load and save | +| `rust/src/browser/` | Windows browser detection and cookie extraction | +| `rust/src/tray/` | Shared tray-icon renderer | +| `rust/src/cli/` | CLI subcommands (`codexbar` binary) | +| `scripts/` | `dev.ps1`, `local-check.ps1`, release, smoke and safety scripts. There is no root `Scripts/` (capital S) pipeline. | +| `docs/` | Windows port docs and ADRs. Upstream macOS docs there are read-only reference. | +| `.circleci/config.yml` | Primary hosted Windows PR and push gate | +| `.github/workflows/pr-check.yml` | Manual Blacksmith Windows reserve | +| `.github/workflows/interaction-guard.yml` | Lightweight GitHub-hosted policy guard | + +## Important files + +| File | Role | +| --- | --- | +| `apps/desktop-tauri/src-tauri/src/main.rs` | Shell entry, command registration, setup | +| `apps/desktop-tauri/src/App.tsx` | Surface routing by window label | +| `apps/desktop-tauri/src/lib/tauri.ts` | Frontend invoke bridge | +| `apps/desktop-tauri/src/types/bridge.ts` | DTOs and `SettingsTabId` | +| `rust/src/core/provider_factory.rs` | The only provider factory, and `pace_capability` | +| `rust/src/core/provider.rs` | `ProviderId` and the `Provider` trait | +| `apps/desktop-tauri/src-tauri/src/commands/providers.rs` | Refresh engine | +| `apps/desktop-tauri/src-tauri/src/tray_bridge.rs` | Tray icon and menu | +| `apps/desktop-tauri/src-tauri/src/floatbar/window.rs` | Float bar window builder | +| `apps/desktop-tauri/src-tauri/src/surface_target.rs` | Proof and settings tab whitelist | +| `apps/desktop-tauri/src-tauri/tauri.conf.json` | Active Tauri config | +| `scripts/local-check.ps1` | Local CI slice | +| `.circleci/config.yml` | Primary hosted PR and push gate (`.github/workflows/pr-check.yml` is the manual Blacksmith reserve) | +| `CONTEXT.md` | CI roles and budget glossary (Blacksmith pool, `CI_BUDGET_MODE`) | + +## Commands + +Run from the repo root unless a line says otherwise. + +```powershell +# Local CI slice; -Slice ci mirrors the hosted PR check .\scripts\local-check.ps1 +.\scripts\local-check.ps1 -Slice ci -# Rust backend / CLI +# Rust backend and CLI cargo test --manifest-path rust/Cargo.toml cargo clippy --manifest-path rust/Cargo.toml --all-targets -- -D warnings cargo build -p codexbar @@ -50,7 +83,7 @@ cargo run -p codexbar -- --help cargo test --manifest-path apps/desktop-tauri/src-tauri/Cargo.toml cargo clippy --manifest-path apps/desktop-tauri/src-tauri/Cargo.toml --all-targets -- -D warnings -# Frontend (cwd apps/desktop-tauri) — use pnpm, not npm +# Frontend (from apps/desktop-tauri); use pnpm, not npm pnpm install pnpm test pnpm run build @@ -58,138 +91,115 @@ pnpm run tauri:dev pnpm run tauri:build:debug pnpm run tauri:build -# Dev launch helpers (repo root) +# Dev launch helpers .\scripts\dev.ps1 .\scripts\dev.ps1 -SkipBuild -./dev.sh +./scripts/dev.sh ``` -- Raw `cargo build --release` on the Tauri crate can still embed the **dev URL**. Prefer `pnpm run tauri:build` / `tauri:build:debug` or `scripts/dev.ps1`. -- Binaries: `codexbar.exe` (CLI), `codexbar-desktop-tauri.exe` (desktop). -- Default desktop work runs from the repo root (default-member). Use `cd rust` only for CLI/backend-only focus. -- There is no active root `Scripts/` (capital S) pipeline — use `scripts/`. -- Format before handoff when Rust changed: `cargo fmt --all`. Clippy both manifests with `-D warnings` (or explain skips). - -## Code Conventions & Common Patterns - -- Prefer small, typed structs/enums and focused modules; keep changes local. -- Provider-specific logic stays inside `rust/src/providers//` (or that module). Do not add cross-provider branching in shared paths. -- **New provider**: (1) `ProviderId` variant + metadata methods (`cli_name`, `display_name`, …), (2) provider module implementing `Provider`, (3) match arm in `core/provider_factory.rs::instantiate`. The factory is exhaustive — missing arms fail to compile. Never duplicate factories in the shell or CLI. -- Errors: `thiserror` (`ProviderError`) and `anyhow` where already used; keep user-facing messages friendly. -- Logging: `tracing` only. Never log secrets, cookies, tokens, or raw API keys. -- Frontend tests are co-located `*.test.ts` / `*.test.tsx`. Bridge types in `types/bridge.ts` must stay aligned with Rust command payloads (including settings tab ids). -- **Settings tab ids** (case-sensitive; backend whitelist in `surface_target.rs` must mirror frontend `SettingsTabId` / `TAB_META`): `general`, `providers`, `notifications`, `menuBar`, `menu`, `usageSpend`, `advanced`, `hooks`, `about`. Unknown ids fall back to General in the UI. Old ids `display` / `apiKeys` / `cookies` are not valid settings tabs. -- Cookie import UX uses **explicit browser selection** in Preferences — do not assume Chrome-only. -- Claude CLI output is user-configurable; do not treat a customizable status line as the usage source of truth. -- Keep provider data siloed: never show identity / plan / email from provider A in provider B UI. -- Secrets (manual cookies, API keys, token accounts): use existing redaction, `secure_file`, and keyring helpers. -- Do not add dependencies or tooling without confirmation. -- Do not open issues or PRs against upstream `steipete/CodexBar` unless the user explicitly asks. This repo is Win-CodexBar only. -- **GitHub write safety:** for any mutating `gh` command (comment, review, merge, close, edit, create, label, release, etc.), always pass the explicit `--repo owner/repo`; never rely on the current remote or a PR/issue number alone. -- Before any GitHub mutation, perform a read-back against that same explicit repo and verify the returned owner/repo or URL exactly matches the intended target. Abort on any mismatch. -- Treat `steipete/CodexBar` as **read-only by default**. A GitHub write to upstream requires explicit user authorization in the current turn; prior permission never carries forward. - -## Important Files - -- `apps/desktop-tauri/src-tauri/src/main.rs` — shell entry, command registration, setup -- `apps/desktop-tauri/src/App.tsx` — surface routing by window label -- `apps/desktop-tauri/src/lib/tauri.ts` — frontend invoke bridge -- `apps/desktop-tauri/src/types/bridge.ts` — DTOs + `SettingsTabId` -- `rust/src/core/provider_factory.rs` — sole provider factory -- `rust/src/core/provider.rs` — `ProviderId` + `Provider` trait -- `apps/desktop-tauri/src-tauri/src/commands/providers.rs` — refresh engine -- `apps/desktop-tauri/src-tauri/src/tray_bridge.rs` — tray icon and menu -- `apps/desktop-tauri/src-tauri/src/floatbar/window.rs` — float bar window builder -- `apps/desktop-tauri/src-tauri/src/surface_target.rs` — proof / settings tab whitelist -- `apps/desktop-tauri/src-tauri/tauri.conf.json` — active Tauri config -- `scripts/local-check.ps1` — local CI slice -- `.circleci/config.yml` — primary hosted PR/push gate; `.github/workflows/pr-check.yml` — manual Blacksmith reserve -- `CONTEXT.md` — CI budget glossary (Blacksmith pool / `CI_BUDGET_MODE`) - -## Runtime/Tooling Preferences - -- Package manager: **pnpm**, with the exact version pinned only by `packageManager` in `apps/desktop-tauri/package.json` (and reflected by the lockfile). Do not introduce npm or yarn lockfiles. -- Node: CircleCI pins **Node 24.18.0**; no `.nvmrc` in repo. Prefer Node 24.18.0 locally for hosted parity. -- Rust: edition **2024**, stable toolchain; CI target `x86_64-pc-windows-msvc`. No committed `rust-toolchain.toml` / `rustfmt.toml` / `clippy.toml` — defaults plus CI flags (`clippy -- -D warnings`). -- Tray / DPAPI / browser-cookie behavior: validate on **Windows-native** hosts. WSL/Linux is insufficient for those paths. -- **CUA (computer-use) for UI proof** — see [Testing & QA](#testing--qa). Project: [trycua/cua](https://github.com/trycua/cua). On this machine the Windows driver is typically `%LOCALAPPDATA%\Programs\Cua\cua-driver\bin\cua-driver.exe`. - -### Worktree storage policy - -- Keep source isolation in Git worktrees when branches are edited concurrently. Read-only issue review can use an existing checkout, `git show`, or `git diff` without creating another worktree. -- Local Cargo builds should load `scripts/worktree-env.ps1`. It sets a process-local `CARGO_TARGET_DIR` outside every registered worktree. Use `WCB_CARGO_TARGET_ROOT` for a temporary machine-local cache root or `WCB_CARGO_TARGET_DIR` for an explicit per-process override. -- Do not commit a shared writable `target-dir` in `.cargo/config.toml`, set a global `CARGO_TARGET_DIR`, or share one exact target directory between concurrent builds. The source worktree remains isolated; only reproducible build output is redirected. -- `scripts/worktree-storage.ps1` is read-only. It reports free disk, registered worktrees, worktree-local `target`, `node_modules`, and the configured external Cargo target. Storage warnings are advisory and never delete files, clean Cargo output, switch branches, or prune Git metadata. -- Treat `target`, Cargo incremental artifacts, frontend build output, and reinstallable `node_modules` as disposable. Preserve source edits, commits, branches, PR history, and review evidence. Before retiring a worktree, verify it is clean and its commit is preserved; removing a worktree does not delete its branch. -- Use these local warning guides: below 60 GiB free, above 1 GiB per worktree target, or above 5 GiB aggregate worktree targets. Treat below 35 GiB free or above 5 GiB for one target / 10 GiB aggregate as an immediate cleanup review. CircleCI keeps its normal runner-local cache and skips this workstation audit. - - -## Testing & QA - -- Rust: prefer focused `#[cfg(test)]` unit tests near the changed module. Run both manifests after Rust changes. -- Frontend: Vitest 3 + jsdom + Testing Library. From `apps/desktop-tauri`: `pnpm test` (`src/**/*.{test,spec}.{ts,tsx}`). -- **Hosted PR check**: CircleCI Windows is primary and runs `scripts/local-check.ps1 -Slice ci` for same-repository ordinary/canonical PRs and `main` (never for fork PRs); micro PRs targeting `port/upstream-*` intentionally skip hosted Windows compute; a micro-named PR targeting `main` does not. `.github/workflows/pr-check.yml` is manual Blacksmith Windows reserve only. Budget details: `CONTEXT.md`, `.github/CI.md`, and ADRs under `docs/adr/`. -- **Fork PRs and reviews**: CircleCI never runs on fork PRs, and this project adds no CircleCI API token. Read the full diff, then run the `-Slice ci` steps from main's copy of `scripts/local-check.ps1` against the fork head (`pull//head`) in a disposable, credential-free Windows environment (Windows Sandbox or a throwaway VM; a worktree is not isolation), and post the result (see `.github/CI.md`). CodeRabbit (`.coderabbit.yaml`) reviews every PR, drafts included, against these rules; fix or answer its findings before merging. -- **Hosted mirror**: `.\scripts\local-check.ps1 -Slice ci`. The default no-parameter developer slice remains available and does not run full installer/smoke unless requested. -- Parser / fetcher changes: add deterministic samples or fixtures where practical. -- No coverage thresholds are configured — do not invent any. - -### UI validation with CUA ([trycua/cua](https://github.com/trycua/cua)) +- A raw `cargo build --release` of the Tauri crate can embed the **dev URL** instead of the built frontend. Build with `pnpm run tauri:build`, `tauri:build:debug` or `scripts/dev.ps1`. +- Cargo builds `codexbar.exe` (CLI) and `codexbar-desktop-tauri.exe` (desktop). Release builds rename them: the installer ships `codexbar.exe` (desktop), `codexbar-cli.exe` (CLI) and `codexbar-desktop.exe` (compatibility alias). See [docs/BUILDING.md](docs/BUILDING.md#where-the-binaries-land). +- When Rust changed, run `cargo fmt --all` before handoff and clippy on both manifests with `-D warnings`. If you skip one, say why. -Unit tests and `local-check` do **not** prove tray, settings, float bar, theme, or WebView2 behavior. For UI / tray / settings / float-bar / visual changes, agents **must** retest on a real Windows desktop build using **Cua Drivers** (background computer-use: click, type, screenshot, UIA) from the open-source [trycua/cua](https://github.com/trycua/cua) project. Docs: [cua.ai/docs](https://cua.ai/docs), driver install: [install guide](https://cua.ai/docs/how-to-guides/driver/install), CLI reference: [cua-driver CLI](https://cua.ai/docs/reference/cua-driver/cli-reference). +## Code conventions -**Install (Windows PowerShell, from upstream README):** +- Prefer small typed structs and enums in focused modules. Keep changes local. +- Keep provider-specific logic inside `rust/src/providers//`. Shared paths have no cross-provider branching; when shared code needs a provider-specific answer, add a typed field or a trait method with a default. +- **New provider:** (1) a `ProviderId` variant with its metadata methods (`cli_name`, `display_name`, ...), (2) a provider module that implements `Provider`, (3) a match arm in `instantiate` and in `pace_capability` in `core/provider_factory.rs`. Both matches are exhaustive, so a missing arm doesn't compile. Never add a second factory in the shell or the CLI. See [docs/PROVIDERS.md](docs/PROVIDERS.md#add-a-provider). +- Errors: `thiserror` (`ProviderError`), and `anyhow` where it is already used. Keep user-facing messages friendly. +- Logging: `tracing` only. Never log secrets, cookies, tokens or raw API keys. +- Frontend tests sit next to the code as `*.test.ts` / `*.test.tsx`. `types/bridge.ts` must match the Rust command payloads, including the settings tab ids. +- **Settings tab ids** are case-sensitive: `general`, `providers`, `notifications`, `menuBar`, `menu`, `usageSpend`, `advanced`, `hooks`, `about`. The backend whitelist in `surface_target.rs` must match the frontend `SettingsTabId` and `TAB_META`. Unknown ids fall back to General in the UI. The old ids `display`, `apiKeys` and `cookies` are not settings tabs. +- Cookie import uses **explicit browser selection** in Settings. Don't assume Chrome. +- Claude CLI output is user-configurable. Don't treat a customizable status line as the usage source of truth. +- Keep provider data siloed: never show identity, plan or email from provider A in provider B's UI. +- Secrets (manual cookies, API keys, token accounts) go through the existing redaction, `secure_file` and keyring helpers. -```powershell -irm https://cua.ai/driver/install.ps1 | iex -``` +## Ask first -Then follow post-install instructions (permissions / accessibility as prompted). +These need the user's confirmation, because they are hard to undo or affect shared systems: -**Typical layout after install:** +- Adding a dependency or new tooling. +- Any GitHub write to upstream `steipete/CodexBar`. It is read-only by default. A write needs explicit authorization from the user in the current turn; earlier permission never carries over. Don't open issues or PRs there unless the user asks. +- Opening issues or PRs against trycua/cua. Use it as tooling only. -- Driver binary: `%LOCALAPPDATA%\Programs\Cua\cua-driver\bin\cua-driver.exe` -- Long-lived daemon: `cua-driver serve` (often over a named pipe). One-shot tools: `cua-driver call ''` (e.g. `list_windows`, `get_window_state`, `click`, screenshots via `--screenshot-out-file`). +## GitHub write safety -**Required retest loop after UI-affecting code changes:** +- Route every GitHub mutation (comment, review, merge, close, edit, create, label, release and so on) through `scripts/gh-safe.sh`. It is the only supported mutation wrapper. It binds the repo, reads the target back, blocks repo overrides and fails closed on a mismatch. +- Always pass the explicit `--repo owner/repo`. Never rely on the current remote or on a PR or issue number alone. +- Before any mutation, read the target back from that same repo and check that the returned owner/repo or URL matches exactly. Abort on any mismatch. +- For object mutations (PR, issue, release), bind both the exact `owner/repo` and the exact number or tag. Use repo-only verification only for creates, where the object doesn't exist yet. +- `gh-safe.sh` allows three repos: `nesszer/Win-CodexBar`, `Finesssee/winget-pkgs` (the fork that receives the manifest branch) and `microsoft/winget-pkgs` (where the Winget PR opens). -1. **Rebuild** a local desktop binary that includes the change (`pnpm --dir apps/desktop-tauri run tauri:build:debug` or `.\scripts\dev.ps1`). Do not validate against a stale pre-change exe. -2. **Close** any already-running CodexBar instance (single-instance plugin may hand off to the old process). -3. **Launch** the new binary. For stable automation (no blur-dismiss), set proof mode, e.g. - `$env:CODEXBAR_PROOF_MODE = 'settings:menu'` - (settings tab ids: `general`, `providers`, `notifications`, `menuBar`, `menu`, `usageSpend`, `advanced`, `hooks`, `about` — float bar section is on **`menu`**). - To use CDP, also set `$env:WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS = '--remote-debugging-port='` (one port per parallel lane). -4. **Drive in the background only** — never `bring_to_front`, foreground mode, `hotkey`, or `type_text`; they take the user's focus or keyboard. - - **CDP (preferred for webview controls):** read `http://127.0.0.1:/json/list` and attach to the window's own page target by URL — main / tray panel / Settings: `http://tauri.localhost/`; float bar: `…?window=floatbar…`. Never the first target: tools that attach to it or open `about:blank` (e.g. browser-use) see a blank page. Click with `Input.dispatchMouseEvent` (does not move the OS cursor or take focus); assert DOM state and the persisted value via the app bridge (`get_settings_snapshot`). - - **cua-driver 0.31+:** actions take `element_token` from `get_window_state` (`element_index` / `snapshot_id` are rejected); pixel clicks need a screenshot from the same session first. Pass the same `session` label on the state read and every action that uses its tokens (or keep one `serve` connection); separate one-shot `call`s without it retire the snapshot, so the token goes stale. -5. **Prove with CUA every time:** `verify_state` (`timeout_ms`, `stable_samples`) for window existence/bounds, real-pixel screenshots via `get_window_state` + `--screenshot-out-file`, and native facts CDP can't see (DWM dark under `auto`, float bar topmost/layered, window frame). CDP screenshots are not proof. -6. **Attach proof** to the PR: CDP assertions + CUA screenshots; list anything not covered (e.g. tray icon / tray menu pixels) explicitly. If CUA cannot run, say why and attach equivalent manual proof (PR template). +## Runtime and tooling -**Do not** treat Vitest/jsdom or `cargo test` alone as sufficient for tray icon, DWM, WebView2 theme, float bar z-order, or settings chrome. **Do not** open issues/PRs against trycua/cua unless the user explicitly asks; use it as tooling. +- **pnpm** only. The exact version is pinned only by `packageManager` in `apps/desktop-tauri/package.json`, and the lockfile reflects it. Don't add npm or yarn lockfiles. +- **Node:** CircleCI pins Node 24.18.0. There is no `.nvmrc`. Use 24.18.0 locally to match CI. +- **Rust:** edition 2024, stable, target `x86_64-pc-windows-msvc`. CI pins the version in `scripts/circleci-pinned-rust.txt`. There is no committed `rust-toolchain.toml`, `rustfmt.toml` or `clippy.toml`; use the defaults plus the CI flags (`clippy -- -D warnings`). +- Test tray, DPAPI and browser-cookie behavior on a native Windows host. WSL and Linux can't exercise those paths. +### Worktree storage policy -## Commit & PR Guidelines +- Use Git worktrees to isolate branches that are edited at the same time. Read-only review can use an existing checkout, `git show` or `git diff` without a new worktree. +- Load `scripts/worktree-env.ps1` before local Cargo builds. It sets a process-local `CARGO_TARGET_DIR` outside every registered worktree. Set `WCB_CARGO_TARGET_ROOT` for a temporary machine-local cache root, or `WCB_CARGO_TARGET_DIR` for an explicit per-process target. +- Don't commit a shared writable `target-dir` in `.cargo/config.toml`, set a global `CARGO_TARGET_DIR`, or share one exact target directory between concurrent builds. The source worktree stays isolated; only reproducible build output moves. +- `scripts/worktree-storage.ps1` is read-only. It reports free disk, registered worktrees, worktree-local `target` and `node_modules`, and the configured external Cargo target. Its warnings are advisory. It never deletes files, cleans Cargo output, switches branches or prunes Git metadata. +- `target`, Cargo incremental output, frontend build output and reinstallable `node_modules` are disposable. Source edits, commits, branches, PR history and review evidence are not. Before you retire a worktree, check that it is clean and its commit is preserved. Removing a worktree doesn't delete its branch. +- Warning levels: under 60 GiB free, over 1 GiB for one worktree target, or over 5 GiB for all worktree targets together. Review cleanup at once under 35 GiB free, or over 5 GiB for one target or 10 GiB in total. CircleCI keeps its normal runner-local cache and skips this audit. -- Short imperative commit messages (e.g. `Fix Claude CLI parser`, `Improve cookie import errors`). -- Keep commits scoped to one change. -- In PRs / patches include: - - Summary of behavior changes - - Commands run (`cargo test`, `pnpm test`, `.\scripts\local-check.ps1`, etc.) - - Screenshots / GIFs for UI changes (Windows) - - Linked issue / reference when relevant -- Hosted PR check exists: `ci/circleci: pr-check` is the primary Windows gate; `.github/workflows/pr-check.yml` is manual Blacksmith backup. Porting micro PRs targeting `port/upstream-*` use focused local evidence and intentionally skip automatic hosted Windows CI; same-repository `main`-bound PRs always get the hosted gate; fork PRs never do (see Fork PRs and reviews). -- UI / tray / settings / float-bar / visual PRs: **hybrid proof is the default** — CDP for actions and state assertions, CUA Driver ([trycua/cua](https://github.com/trycua/cua)) for real-pixel screenshots and native checks — after a **fresh local rebuild** — see [UI validation with CUA](#ui-validation-with-cua-trycuacua). If CUA cannot be used, explain why and attach equivalent manual proof (PR template checkboxes). -- Before non-trivial merge: thermo-nuclear structure review when the project process requires it. +## Testing & QA +- Rust: prefer focused `#[cfg(test)]` unit tests next to the changed module. After a Rust change, run the tests for both manifests. +- Frontend: Vitest 3 with jsdom and Testing Library. From `apps/desktop-tauri`: `pnpm test` (`src/**/*.{test,spec}.{ts,tsx}`). +- Parser and fetcher changes: add deterministic samples or fixtures where practical. +- Run the checks that fit the change. There are no coverage thresholds; don't invent any. +- **Hosted PR check.** CircleCI Windows is the primary gate. It runs `scripts/local-check.ps1 -Slice ci` for same-repository ordinary and canonical PRs and for `main`, and never for fork PRs. Micro PRs that target `port/upstream-*` skip hosted Windows compute on purpose; a micro-named PR that targets `main` does not. `.github/workflows/pr-check.yml` is a manual Blacksmith Windows reserve only. Budget details are in `CONTEXT.md`, `.github/CI.md` and `docs/adr/`. +- **Local mirror.** `.\scripts\local-check.ps1 -Slice ci` mirrors the hosted check. The default developer slice, with no parameters, doesn't run the full installer or smoke test unless asked. +- **Fork PRs and reviews.** CircleCI never runs on fork PRs, and this project adds no CircleCI API token. Read the full diff. Then run the `-Slice ci` steps from main's copy of `scripts/local-check.ps1` against the fork head (`pull//head`) in a disposable, credential-free Windows environment (Windows Sandbox or a throwaway VM; a worktree is not isolation), and post the result (see `.github/CI.md`). +- **CodeRabbit** (`.coderabbit.yaml`) reviews every PR, drafts included, against these rules. Fix or answer its findings before merging. -## Release & Winget Notes +### UI validation with CUA ([trycua/cua](https://github.com/trycua/cua)) -- Treat Winget updates as a normal release step after GitHub release artifacts are stable. -- Winget does not track "latest" GitHub releases; every version needs its own immutable manifest folder in `microsoft/winget-pkgs`, for example `manifests/f/Finesssee/Win-CodexBar/0.23.6/`. -- For routine version bumps, copy the previous approved manifest folder and change only version-specific fields: `PackageVersion`, `InstallerUrl`, `InstallerSha256`, `DisplayName`, `DisplayVersion`, `ReleaseNotes`, and `ReleaseNotesUrl`. -- Keep stable package identity and installer behavior unchanged unless there is a real packaging reason: `PackageIdentifier`, `InstallerType`, `Scope`, `ProductCode`, `Publisher`, package URLs, and silent install behavior. -- Before opening a Winget PR, verify the release installer URL resolves and recompute the SHA-256 from the downloaded asset. On Windows, run `winget validate` when available. -- The first Winget package submission was approved in `microsoft/winget-pkgs#366653`; the v0.23.5 update was approved in `microsoft/winget-pkgs#366794`. Future updates should be faster, but still expect Microsoft validation/review. -- Agents must route GitHub mutations through `scripts/gh-safe.sh` instead of calling mutating `gh` subcommands directly. This is the sole supported mutation wrapper. It owns repo binding, performs read-back verification, blocks repo overrides, and fails closed on target mismatch. -- `gh-safe.sh` allowlists three repos: `nesszer/Win-CodexBar`, plus `Finesssee/winget-pkgs` (the fork that receives the manifest branch) and `microsoft/winget-pkgs` (where the winget PR opens). -- For object mutations (PR/issue/release), bind both the exact `owner/repo` and exact object number/tag. Use repo-only verification only for creates where the target object does not exist yet. +Unit tests and `local-check` don't prove tray, Settings, float bar, theme or WebView2 behavior. After any UI, tray, Settings, float-bar or visual change, retest on a real Windows desktop build. Vitest, jsdom and `cargo test` alone are never enough for the tray icon, DWM, the WebView2 theme, float bar z-order or Settings chrome. + +1. **Rebuild** a desktop binary that includes the change: `pnpm --dir apps/desktop-tauri run tauri:build:debug` or `.\scripts\dev.ps1`. Proof from an exe built before the change (a **stale exe**) proves nothing. +2. **Close** any running CodexBar. The single-instance plugin can hand a new launch to the old process. +3. **Launch** the new binary in proof mode, for example `$env:CODEXBAR_PROOF_MODE = 'settings:menu'`. The float bar section is on the `menu` pane. For CDP, also set `$env:WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS = '--remote-debugging-port='`, one port per parallel lane. +4. **Drive in the background only.** Never use `bring_to_front`, foreground mode, `hotkey` or `type_text`; they take the user's focus or keyboard. Use CDP for webview controls: attach to the window's own page target by URL, never the first target, click with `Input.dispatchMouseEvent`, and assert the DOM state and the persisted value through the bridge (`get_settings_snapshot`). Use cua-driver 0.31+ with `element_token` from `get_window_state`, and pass one `session` label on the state read and every action that uses its tokens. +5. **Prove with CUA every time:** `verify_state` for window existence and bounds, real-pixel screenshots through `get_window_state` with `--screenshot-out-file`, and native facts CDP can't see (DWM dark under `auto`, float bar topmost and layered, window frame). CDP screenshots are not proof. +6. **Attach the proof** to the PR: CDP assertions and CUA screenshots. List what isn't covered, such as tray icon or tray menu pixels. If CUA can't run, say why and attach equivalent manual proof (PR template). + +**Reviewing snapshots.** When you ask Astra or Fable to review snapshots of the app, ask it to collage them into one contact sheet first. It then reads one image instead of one per snapshot, which saves a lot of context. + +[docs/WINDOWS_PROOF.md](docs/WINDOWS_PROOF.md) has the cua-driver install command (driver usually at `%LOCALAPPDATA%\Programs\Cua\cua-driver\bin\cua-driver.exe`), the CDP target URLs, the cua-driver session rules and the post-install checklist. + +## Commits and PRs + +- Short imperative commit messages, for example `Fix Claude CLI parser` or `Improve cookie import errors`. One change per commit. +- A PR includes: a summary of behavior changes, the commands you ran (`cargo test`, `pnpm test`, `.\scripts\local-check.ps1` ...), Windows screenshots or GIFs for UI changes, and the linked issue or reference. +- `ci/circleci: pr-check` is the primary Windows gate; `.github/workflows/pr-check.yml` is the manual Blacksmith backup. Porting micro PRs that target `port/upstream-*` use focused local evidence and skip automatic hosted Windows CI on purpose. Same-repository PRs to `main` always get the hosted gate; fork PRs never do (see Fork PRs and reviews). +- UI, tray, Settings, float-bar and visual PRs need hybrid proof by default: CDP for actions and state, CUA for real pixels and native checks, after a fresh local rebuild. See [UI validation with CUA](#ui-validation-with-cua-trycuacua). +- Before a non-trivial merge, run the thermo-nuclear structure review when the project process requires it. + +## Release and Winget + +- Update Winget as a normal release step, after the GitHub release artifacts are stable. +- Every version needs its own immutable manifest folder in `microsoft/winget-pkgs`, for example `manifests/f/Finesssee/Win-CodexBar/0.23.6/`. Winget doesn't track "latest". +- For a routine bump, copy the previous approved folder and change only `PackageVersion`, `InstallerUrl`, `InstallerSha256`, `DisplayName`, `DisplayVersion`, `ReleaseNotes` and `ReleaseNotesUrl`. +- Keep `PackageIdentifier`, `InstallerType`, `Scope`, `ProductCode`, `Publisher`, the package URLs and the silent install behavior unchanged unless there is a real packaging reason. +- Before you open a Winget PR, check that the installer URL resolves and recompute the SHA-256 from the downloaded file. On Windows, run `winget validate` when it is available. +- The first submission was approved in `microsoft/winget-pkgs#366653` and the v0.23.5 update in `microsoft/winget-pkgs#366794`. Later updates still go through Microsoft validation and review. + +## Docs index + +| Need | File | +| --- | --- | +| Windows, data flow, events, pace | `docs/ARCHITECTURE.md` | +| Settings panes, keys, files, portable preferences, hooks | `docs/CONFIGURATION.md` | +| Adding a provider, windows, pace rules, provider notes | `docs/PROVIDERS.md` | +| CLI commands and flags | `docs/CLI.md` | +| Build, checks, release builds, binary names | `docs/BUILDING.md` | +| Browser cookie import | `docs/COOKIES.md` | +| Proof mode, seeding, CDP, cua-driver | `docs/WINDOWS_PROOF.md` | +| CI roles and budget terms | `CONTEXT.md`, `.github/CI.md`, `docs/adr/` | +| Porting from upstream | `docs/PORTING.md` | diff --git a/CONTEXT.md b/CONTEXT.md index 4efb6d2fc4..6c57b18609 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -1,50 +1,67 @@ # Shared CI budget glossary -This glossary is shared with the sister repo (`linear-cli`) so operators can reason about CI spend across both projects with one vocabulary. +This glossary defines the CI budget terms that Win-CodexBar shares with its sister repo, `linear-cli`. Use it to reason about CI spend across both projects. -Detailed Win-CodexBar CI topology, trigger configuration, cache policy, release flow, and operator setup belong in [`.github/CI.md`](.github/CI.md). This file intentionally stays small so shared budget language does not become a second CI manual. +Trigger rules, caches, fork PRs and the release workflow are in [`.github/CI.md`](.github/CI.md). The budget modes are defined here and in [ADR 0002](docs/adr/0002-ci-budget-modes.md), because `.github/CI.md` does not cover them. ## Win-CodexBar compute roles -- **CircleCI Windows** — primary hosted PR/`main` integration gate and release builder. -- **Blacksmith Windows** — manual reserve/second-opinion CI only. -- **GitHub-hosted Ubuntu** — lightweight interaction guard for untrusted authors. +- **CircleCI Windows** runs the hosted PR and `main` check (`pr-check`). It builds no releases. +- **GitHub Actions Windows** (`windows-latest`) builds, signs and drafts tagged releases in `.github/workflows/release.yml`. +- **Blacksmith Windows** runs `.github/workflows/pr-check.yml` by manual dispatch only, as a reserve or second opinion. +- **GitHub-hosted Ubuntu** (`ubuntu-latest`) runs the interaction guard for authors who are not owners, members or collaborators. -The primary and reserve Windows jobs use the same canonical `scripts/local-check.ps1 -Slice ci` contract. See `.github/CI.md` for the exact trigger and runner rules. +CircleCI and the Blacksmith reserve run the same command, `scripts/local-check.ps1 -Slice ci`. -## Blacksmith Pool +## Blacksmith pool -The Blacksmith allowance is shared with `linear-cli`. For Win-CodexBar it is reserve capacity rather than recurring PR compute. Do not automatically run Blacksmith beside a healthy CircleCI run. +Win-CodexBar shares its Blacksmith allowance with `linear-cli`. Win-CodexBar uses Blacksmith only as reserve capacity. Don't run Blacksmith beside a healthy CircleCI run. -The historical cross-repo intent split was roughly **60% Win-CodexBar / 30% linear-cli / 10% buffer**. Treat that as budget history, not a reason to schedule duplicate validation. +The original split between the repos was about **60% Win-CodexBar, 30% linear-cli and 10% buffer**. That split is budget history. It is not a reason to run duplicate checks. ## CI budget mode -`CI_BUDGET_MODE` is the coarse emergency control. Supported values are `normal`, `thin`, and `off`; unset/empty means `normal`. +`CI_BUDGET_MODE` is the emergency stop for hosted checks. It takes `normal`, `thin` or `off`. Unset or empty means `normal`. | Mode | CircleCI PR check | Interaction guard | Blacksmith reserve | Release | | --- | --- | --- | --- | --- | -| `normal` | runs when in scope | runs when needed | manual only | tag-triggered CircleCI | -| `thin` | runs when in scope | runs when needed | manual only | tag-triggered CircleCI | -| `off` | skip | skip | skip | tag-triggered CircleCI | +| `normal` | runs when in scope | runs when needed | manual only | GitHub Actions on a `v*.*.*` tag | +| `thin` | runs when in scope | runs when needed | manual only | GitHub Actions on a `v*.*.*` tag | +| `off` | skips | skips | skips | GitHub Actions on a `v*.*.*` tag | -`thin` currently has the same single hosted Windows integration job as `normal`; there is no matrix left to trim. Porting micro PRs save compute through their semantic integration-branch topology instead. See `.github/CI.md` and `docs/PORTING.md`. +`thin` behaves like `normal`. The code checks only for `off`, and the PR check is a single job with nothing to trim. The value exists for parity with `linear-cli`. -Set the variable in provider settings, not source code: +Porting micro PRs that target a `port/upstream-*` branch save compute in a different way: CircleCI skips them. See `docs/PORTING.md`. -- CircleCI: Project Settings -> Environment Variables -> `CI_BUDGET_MODE` -- GitHub Actions: Settings -> Secrets and variables -> Actions -> Variables +Set the variable in each CI service's settings, not in source code: -Budget mode does not disable the protected release-tag pipeline. +- CircleCI: Project Settings > Environment Variables > `CI_BUDGET_MODE`. The `pr-check` gate reads it. +- GitHub Actions: Settings > Secrets and variables > Actions > Variables. `pr-check.yml` and `interaction-guard.yml` read it. + +`release.yml` doesn't read `CI_BUDGET_MODE`, so budget mode never stops a tagged release. ## Local check slice -The hosted Windows contract is: +The hosted Windows check runs this command from the repo root: ```powershell .\scripts\local-check.ps1 -Slice ci ``` -It covers formatting, Clippy, Rust tests, frozen frontend install, frontend tests/build, and interaction-guard tests. Installer/release packaging and UI proof remain separate responsibilities. +It runs these steps in order: + +1. `scripts/circleci-pr.tests.ps1` (CI gate tests) +2. `cargo fmt --all --check` +3. `cargo clippy --workspace --all-targets -- -D warnings` +4. `cargo test --workspace` +5. `pnpm install --frozen-lockfile` +6. `pnpm run lint` (oxlint) +7. `pnpm run check:anti-slop` +8. `pnpm run test:anti-slop` +9. `pnpm test` +10. `pnpm run build` +11. `node --test .github/scripts/interaction-guard.test.mjs` + +The slice doesn't build the installer or release assets, and it doesn't prove UI behavior. -For UI, tray, settings, and float-bar changes, CI is not sufficient: use a fresh Windows build plus CUA (or documented equivalent manual proof) as required by `AGENTS.md`. +For tray, settings, float bar and other UI changes, CI is not enough. Rebuild on Windows and prove the change in the real window with CUA, or attach equivalent manual proof. `AGENTS.md` and `docs/WINDOWS_PROOF.md` describe the proof. diff --git a/README.md b/README.md index 187393ab0d..6cbe617694 100755 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [English](./README.md) | [简体中文](./README.zh-CN.md) | [繁體中文(臺灣)](./README.zh-TW.md) | [日本語](./README.ja-JP.md) | [한국어](./README.ko-KR.md) | [Español mexicano](./README.es-MX.md) | [Português (Brasil)](./README.pt-BR.md) | [Türkçe](./README.tr-TR.md) -Win-CodexBar is a Windows system-tray app for keeping AI coding-tool usage visible without opening a dozen dashboards. It ports the spirit of [CodexBar](https://github.com/steipete/CodexBar) to a Tauri + React desktop shell backed by shared Rust provider logic. +Win-CodexBar is a Windows tray app that shows how much of your AI coding-tool quota you have used, without opening each provider's dashboard. It is a Windows port of [CodexBar](https://github.com/steipete/CodexBar) for macOS. The app is a Tauri 2 and React shell on top of shared Rust provider code. @@ -15,57 +15,61 @@ Win-CodexBar is a Windows system-tray app for keeping AI coding-tool usage visib
-## Highlights - -- **56 providers** including Codex, Claude, Copilot, OpenRouter, Cursor, Gemini, DeepSeek, MiniMax, Kiro, Antigravity, Groq, Qoder, Sakana AI, CrossModel, and more. -- **Tray-first workflow** with a compact provider grid, usage cards, refresh action, settings shortcut, and quit control. -- **Provider settings** for source selection, credentials, cookie import, token accounts, API keys, regions, and tray-display preferences. -- **Windows credential protection** for app-managed API keys, manual cookies, and token accounts, using user-scoped DPAPI where available. -- **Browser cookie import** for Chrome Stable, Beta, Dev, Canary, Chrome for Testing, Chromium, Edge, Brave, and Firefox, kept opt-in per provider. -- **Installed local CLI** for scripting usage, cost, config, diagnostics, and loopback integrations. -- **Installer + portable builds** with WebView2 runtime bootstrap, VC++ runtime bootstrap, and SHA-256 checksum files. - ## Install -Install with Windows Package Manager: +Install with the Windows Package Manager: ```powershell winget install Finesssee.Win-CodexBar ``` -Or download the latest installer/portable build from [GitHub Releases](https://github.com/nesszer/Win-CodexBar/releases). +Or download a build from [GitHub Releases](https://github.com/nesszer/Win-CodexBar/releases): - Installer: `CodexBar--Setup.exe` - Portable: `CodexBar--portable.exe` -- Checksums: each release includes `.sha256` files +- CLI only: `CodexBarCLI-v-windows-x64.zip` +- Each file has a `.sha256` checksum next to it. -Winget distribution is approved through [microsoft/winget-pkgs](https://github.com/microsoft/winget-pkgs/tree/master/manifests/f/Finesssee/Win-CodexBar). New versions can take a little time to appear because every Winget update is pinned to a specific release URL and installer hash. +The installer installs the WebView2 runtime if it is missing. Release builds link the C runtime statically, so they don't need the VC++ redistributable. -## Code signing +Winget packages come from [microsoft/winget-pkgs](https://github.com/microsoft/winget-pkgs/tree/master/manifests/f/Finesssee/Win-CodexBar). Each Winget version pins one release URL and installer hash, so a new version can take a while to appear there. -> **Code signing:** SignPath.io is wired into the GitHub Actions release path, but production signing remains fail-closed until the Release certificate 2026 is issued and the `release-signing` policy becomes valid. See [docs/CODE_SIGNING.md](docs/CODE_SIGNING.md) for the signing policy. -> The v0.60.3 release is immutable and unsigned; verify its SHA-256 files. The first signed release will be the next normal version after SignPath onboarding. +### Code signing -## First Run +Releases are not signed yet. SignPath.io is set up in the GitHub Actions release workflow, but signing stays off until SignPath issues the Release certificate 2026 and the `release-signing` policy is valid. Until then, check each download against its `.sha256` file. See [docs/CODE_SIGNING.md](docs/CODE_SIGNING.md). -1. Launch **CodexBar** from the Start Menu or portable executable. -2. Click the tray icon to open the usage panel. -3. Open **Settings -> Providers**. -4. Enable the providers you use. -5. Add the matching credential type: OAuth/device login, API key, browser cookies, local CLI login, or token account. +## First run -For Claude, browser cookies/sessionKey are preferred because they match Claude's settings-page usage. OAuth and CLI stay available as fallbacks. For CLI-based providers such as Codex and Gemini, sign in with the provider CLI first. +1. Start **CodexBar** from the Start menu, or run the portable executable. +2. Click the tray icon to open the tray panel. +3. Open **Settings > Providers** from the tray menu. +4. Turn on the providers you use. A new install turns on Claude and Codex. +5. Add the credential each provider needs: an OAuth or device login, an API key, browser cookies, a local CLI login or a token account. -## Latest Release +For Claude, browser cookies (the `sessionKey`) are preferred, because they match the usage on Claude's settings page. OAuth and the CLI are fallbacks. For CLI-based providers such as Codex and Gemini, sign in with that provider's CLI first. -**v0.33.2** fixes tray-panel dismissal so the popover closes on focus loss or Escape, without immediately reopening from the same tray click. +## What the app does -See the full history in [CHANGELOG.md](CHANGELOG.md). +- **Tray panel.** Left-click the tray icon, or press `Ctrl+Shift+U`, to open a light panel in the Mac style. It shows one card per provider. Each card has a metric row per usage window, with a bar, the reset time and, when the provider supports it, a pace line such as "10% in deficit · Runs out in 9d 8h". +- **Tray icon.** The icon shows bars or a percentage for one provider, or two providers stacked. With **Color Pace Indicator** on, it turns green when you are behind pace and red when you are ahead. +- **Float bar.** An optional always-on-top strip with the same usage. +- **Providers.** More than 90 providers, including Codex, Claude, Copilot, OpenRouter, Cursor, Gemini, DeepSeek, MiniMax, Kiro, Antigravity, Groq, Qoder and Sakana AI. Each provider has its own source, credentials, cookie import, token accounts, API keys and region settings. +- **Settings.** A window with a sidebar: General, Usage & Spend, Notifications, Menu Bar, Menu, Advanced, Hooks and About, then one page per provider. Each provider row has a status dot. +- **Provider status pages.** With **Check provider status** on (the default), the app polls the status pages of providers such as OpenAI, Claude, Copilot, Cursor and Gemini. An incident colors that provider's dot in Settings and adds a line to the tray panel and the tray tooltip. +- **Per-provider quota warnings.** Each provider can use the global warning thresholds, its own thresholds, or none. +- **Usage & Spend.** Local cost estimates from Codex and Claude logs and from provider APIs. Turn off **Track costs** to hide them. +- **Hooks.** Run a local program when a quota runs low, runs out or resets, or when a provider fails or recovers. +- **Hide personal information.** Removes email addresses and replaces account names with "Account 1", "Account 2" and so on. +- **Portable preferences.** Export your display and behavior choices to a JSON file and import them on another machine. Secrets are never exported. +- **CLI.** A console tool for scripts: usage, cost, config, diagnostics and a loopback dashboard server. +- **Languages.** The app follows the Windows display language by default. -## Supported Providers +[docs/CONFIGURATION.md](docs/CONFIGURATION.md) describes every setting. + +## Supported providers
-Provider matrix +Provider matrix (a selection; the app supports more) | Provider | Auth | Tracks | |---|---|---| @@ -85,7 +89,7 @@ See the full history in [CHANGELOG.md](CHANGELOG.md). | Augment | Cookies | Credits | | OpenCode | Local Config | Usage | | Kimi | Cookies | 5h Rate, Weekly | -| Kimi K2 | API Key | Credits | +| Kimi K2 (deprecated) | API Key | Credits | | Amp | Cookies | Usage | | Warp | Local Config | Usage | | Ollama | Cookies / API Key | Usage, Cloud Models, Pace windows | @@ -129,15 +133,17 @@ See the full history in [CHANGELOG.md](CHANGELOG.md). | xKiro | API Key | Daily free tokens | | Synthetic | API Key | Five-hour quota, Weekly tokens, Hourly search, Plan | | Langdock | Browser cookies / manual Cookie header | Session (5h), Weekly | -| LithosAI | Browser cookies / manual Cookie header | Prepaid balance, UTC spend today and this month | +| LithosAI | Browser cookies / manual Cookie header | Prepaid balance (Credits), UTC spend today and this month | | Muse (muse.ai) | Browser cookies / manual Cookie header | Weekly usage, tokens left, top-up tokens | | WorkBuddy | Browser cookies / manual Cookie header | Monthly credits left, plan, cycle reset |
-## Supported Languages +[docs/PROVIDERS.md](docs/PROVIDERS.md) explains how providers fetch data, which windows get pace, and the details of individual providers. + +## Supported languages -The UI and contributor reporting currently support: +The app ships these 10 languages: - English - 简体中文 @@ -146,28 +152,39 @@ The UI and contributor reporting currently support: - 한국어 - Español mexicano - Português (Brasil) +- Русский - Türkçe - Українська -## Build From Source +A new install follows the Windows display language. Pick **System** or a language in **Settings > General**. + +## Build from source + +You need Rust (stable, `x86_64-pc-windows-msvc`), the Visual Studio C++ build tools, Node.js and pnpm. `scripts/dev.ps1` checks for them but installs nothing. Run these from the repo root: ```powershell -# Prerequisites: Node.js + pnpm. Rust and MinGW are installed by the script when needed. git clone https://github.com/nesszer/Win-CodexBar.git cd Win-CodexBar +pnpm --dir apps/desktop-tauri install --frozen-lockfile .\scripts\dev.ps1 ``` -Useful dev flags: +Other `dev.ps1` options: ```powershell -.\scripts\dev.ps1 -Release # optimized build -.\scripts\dev.ps1 -SkipBuild # relaunch the last build +.\scripts\dev.ps1 -Release +.\scripts\dev.ps1 -SkipBuild ``` -CLI examples: +`-Release` builds an optimized binary. `-SkipBuild` starts the last build again. + +[docs/BUILDING.md](docs/BUILDING.md) covers the checks, the CLI build and release builds. -```bash +## Use the CLI + +The installer puts the console CLI in `codexbar-cli.exe`. `codexbar.exe` is the tray app, and `codexbar-desktop.exe` is a copy kept for older shortcuts and autostart entries. Start menu shortcuts start the tray app. + +```powershell codexbar-cli --help codexbar-cli diagnose --pretty codexbar-cli usage -p claude @@ -175,45 +192,53 @@ codexbar-cli usage -p all codexbar-cli cost -p codex ``` -Installer builds include `codexbar.exe` as the tray app and `codexbar-cli.exe` as the console CLI. Start Menu shortcuts launch the desktop app; terminal commands use `codexbar-cli.exe`. `codexbar-desktop.exe` is still installed as a compatibility alias for older shortcuts and autostart entries. +A source build names the CLI `codexbar.exe`. See [docs/CLI.md](docs/CLI.md). -## Release Builds +## Release builds -For local Windows release builds, use the cached release builder: +To build a release locally on Windows, use the cached release builder: ```powershell -.\scripts\windows-release-build.ps1 -Ref v0.33.2 -SmokeInstall +.\scripts\windows-release-build.ps1 -Ref v0.70.0 -SmokeInstall ``` -The script builds the real Tauri release binary plus the console CLI, verifies signed installer dependencies, packages with Inno Setup, writes installer/portable assets, writes SHA-256 sidecars, and can run a silent install/uninstall smoke test. - -More release automation notes live in [docs/release/ci-cd.md](docs/release/ci-cd.md). +It builds the Tauri release binary and the console CLI, checks the installer dependencies, packages the installer with Inno Setup, writes the installer and portable files with SHA-256 checksums, and with `-SmokeInstall` runs a silent install and uninstall test. See [docs/BUILDING.md](docs/BUILDING.md#build-a-release-on-windows) and [docs/release/ci-cd.md](docs/release/ci-cd.md). ## Privacy -- **On-device by default**: provider data is read from known local paths or provider APIs you configure. -- **Opt-in cookies**: browser-cookie extraction only runs for providers you enable. -- **Protected secrets**: API keys, manual cookies, and token accounts use the secure-file layer; Windows uses user-scoped DPAPI where available. -- **Safe diagnostics**: diagnostics expose provider/source/status metadata only, never raw cookies, API keys, bearer tokens, or OAuth values. -- **Verified updates**: installer downloads require a GitHub SHA-256 digest and are re-verified immediately before apply. +- **Local by default.** The app reads provider data from known local paths or from provider APIs you set up. +- **Cookies are opt-in.** The app reads browser cookies only for providers you turn on. +- **Secrets are protected.** API keys, pasted cookies, token accounts and `settings.json` are stored through the secure-file layer, which uses DPAPI for the current Windows user. +- **Diagnostics are safe to share.** They contain provider, source and status metadata only. They never contain cookies, API keys, bearer tokens or OAuth values. +- **Updates are verified.** An update download needs a SHA-256 digest from GitHub and is checked again right before it is applied. + +See [docs/PRIVACY.md](docs/PRIVACY.md). ## Docs | Topic | Link | |---|---| | Building from source | [docs/BUILDING.md](docs/BUILDING.md) | -| WSL setup and auth tips | [docs/WSL.md](docs/WSL.md) | -| Browser cookie details | [docs/COOKIES.md](docs/COOKIES.md) | +| Settings and files | [docs/CONFIGURATION.md](docs/CONFIGURATION.md) | +| Providers, windows and pace | [docs/PROVIDERS.md](docs/PROVIDERS.md) | +| The CLI | [docs/CLI.md](docs/CLI.md) | +| Browser cookies | [docs/COOKIES.md](docs/COOKIES.md) | +| How the app is built | [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) | +| Proving UI changes | [docs/WINDOWS_PROOF.md](docs/WINDOWS_PROOF.md) | +| WSL setup and sign-in tips | [docs/WSL.md](docs/WSL.md) | +| Contributing | [CONTRIBUTING.md](CONTRIBUTING.md) | + +See [CHANGELOG.md](CHANGELOG.md) for the release history. The current release is 0.70.0. ## Local integrations -- [AI Usage Limits](https://github.com/lenadweb/stream-deck-ai-limits) — Elgato Stream Deck integration that can consume the local `codexbar serve` dashboard/API to show provider, account, quota, or payload metrics. -- [AI Monitor](https://github.com/tobymarks/esp32-ai-monitor) — ESP32 desk display (Cheap Yellow Display) with a Windows companion app (beta) that calls the installed CLI (`codexbar-cli usage -p --json`) and streams Claude, Codex, Copilot, Cursor, Gemini, or Antigravity limits to the display over USB serial. +- [AI Usage Limits](https://github.com/lenadweb/stream-deck-ai-limits): an Elgato Stream Deck plugin that reads the local `codexbar serve` dashboard and API to show provider, account, quota or payload metrics. +- [AI Monitor](https://github.com/tobymarks/esp32-ai-monitor): an ESP32 desk display (Cheap Yellow Display) with a Windows companion app (beta). The app calls the installed CLI (`codexbar-cli usage -p --json`) and sends Claude, Codex, Copilot, Cursor, Gemini or Antigravity limits to the display over USB serial. ## Credits - Original macOS app: [steipete/CodexBar](https://github.com/steipete/CodexBar) by Peter Steinberger -- Inspired by [ccusage](https://github.com/ryoppippi/ccusage) for cost tracking +- Cost tracking inspired by [ccusage](https://github.com/ryoppippi/ccusage) ## License diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 1687816457..bc21d87f5c 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -1,64 +1,116 @@ -# Architecture overview (Windows) +# Architecture -Windows rewrite of upstream CodexBar architecture concepts for **Win-CodexBar**. -Upstream `docs/architecture.md` describes Swift modules (`CodexBarCore`, menu bar app, WidgetKit, Sparkle). Those do **not** apply here. +This page shows how Win-CodexBar is put together: the crates, the windows, and how usage data and settings move between them. It covers the Windows port only. The upstream `docs/architecture.md` describes Swift modules, WidgetKit and Sparkle, which this port doesn't use. -## Modules +## Crates and folders | Area | Path | Role | -|------|------|------| -| Shared backend + CLI | `rust/` (`codexbar` crate) | Providers, settings, browser cookies, tray icon pixels, CLI | -| Desktop shell | `apps/desktop-tauri/src-tauri/` (`codexbar-desktop-tauri`) | Tauri 2 host: tray, windows, IPC commands, float bar, proof harness | -| Frontend | `apps/desktop-tauri/src/` | React 18 + Vite surfaces (tray panel, settings, float bar) | +| --- | --- | --- | +| Shared backend and CLI | `rust/` (crate `codexbar`) | Providers, settings, browser cookies, tray icon pixels, the CLI | +| Desktop shell | `apps/desktop-tauri/src-tauri/` (crate `codexbar-desktop-tauri`) | Tauri 2 host: tray, windows, commands, float bar, proof harness | +| Frontend | `apps/desktop-tauri/src/` | React 18 and Vite: tray panel, Settings, float bar | -Cargo workspace (root `Cargo.toml`): members `rust`, `apps/desktop-tauri/src-tauri`; **default-member** is the Tauri crate. Shell depends on `codexbar = { path = "../../../rust" }`. +The root `Cargo.toml` is a workspace with the members `rust` and `apps/desktop-tauri/src-tauri`. The Tauri crate is the default member. It depends on `codexbar = { path = "../../../rust" }`. ## Entry points -- **Desktop**: `apps/desktop-tauri/src-tauri/src/main.rs` — plugins, tray setup, float bar install, auto-refresh, command registration. Binary: `codexbar-desktop-tauri.exe`. -- **CLI**: `rust/src/main.rs` — subcommands only (no GUI on bare invoke). Binary: `codexbar.exe`. -- **Frontend bootstrap**: `apps/desktop-tauri/src/App.tsx` routes by window label (`main`, settings, floatbar, flyout). +- **Desktop app:** `apps/desktop-tauri/src-tauri/src/main.rs`. It loads settings, reads the proof harness variables, sets up the tray and float bar, starts auto refresh and registers the commands. Cargo builds it as `codexbar-desktop-tauri.exe`. +- **CLI:** `rust/src/main.rs`. It has no GUI. Run with no subcommand, it prints a hint and exits with code 64. Cargo builds it as `codexbar.exe`. +- **Frontend:** `apps/desktop-tauri/src/App.tsx` picks the surface from the window label. + +Release builds rename both binaries. See [BUILDING.md](./BUILDING.md#build-a-release-on-windows). + +## Windows + +The app runs one hidden window and opens the others on demand. + +| Label | Page | What it shows | +| --- | --- | --- | +| `main` | `index.html` | Nothing. It stays hidden and hosts app state. Proof mode `trayPanel` draws the tray panel here. | +| `flyout` | `index.html?window=flyout` | The tray panel | +| `settings` | `index.html?window=settings&tab=` | Settings | +| `floatbar` | `index.html?window=floatbar&orientation=` | The float bar | + +### Tray panel + +The tray panel is the only dashboard layout. Tray left-click, **Pop Out Dashboard** in the tray menu, the global shortcut, app launch and a second launch of the app all open the `flyout` window. `shell/flyout_window.rs` owns it. + +- It is a light panel in the Mac style. It is 310 px wide, times the **Panel scale** setting, and its height fits its content. +- It paints `#DEDEE2` before the page loads, so it never flashes white. +- It opens next to the tray and can't be moved or resized. +- It closes when it loses focus. Proof mode, `CODEXBAR_START_VISIBLE` and dragging a provider tab to reorder keep it open. + +The panel shows one card per provider. A card has metric rows for each usage window, with a bar, the reset time and, when the provider supports it, a pace line. [PROVIDERS.md](./PROVIDERS.md) lists the windows and pace support per provider. + +The older PopOut layout on `main` is retired. `SurfaceMode::PopOut` remains only as a key in stored data. + +### Settings + +Settings is a separate window with a sidebar. `shell/settings_window.rs` owns it. The pane ids are `general`, `providers`, `notifications`, `menuBar`, `menu`, `usageSpend`, `advanced`, `hooks` and `about`. `surface_target.rs` in the shell and `TAB_META` in `apps/desktop-tauri/src/surfaces/settings/settingsTabs.ts` must list the same ids. An unknown id opens General. [CONFIGURATION.md](./CONFIGURATION.md) describes each pane. + +### Float bar + +The float bar is an optional always-on-top strip. `floatbar/` owns it. Its window builder must pin `.theme(Some(tauri::Theme::Dark))`. WebView2 resolves `prefers-color-scheme` on a process profile that all windows share, so an unpinned float bar flips the other windows when the theme is `auto`. ## Data flow -1. **Provider refresh** - `instantiate_provider` (`rust/src/core/provider_factory.rs`) → `Provider::fetch_usage` → shell `commands/providers.rs` (semaphore + timeout) → `AppState.provider_cache` → events → React `useProviders`. +### Provider refresh -2. **Settings** - `%AppData%\Roaming\CodexBar\settings.json` (and sibling stores) via `Settings::load` / `save` + `secure_file` (DPAPI-capable). UI patches go through `updateSettings` → save → settings / float-bar events. +1. `codexbar::core::instantiate_provider` in `rust/src/core/provider_factory.rs` builds each provider. It is the only provider factory. +2. `commands/providers.rs` in the shell calls `Provider::fetch_usage`. It runs at most 8 fetches at once. +3. Each fetch has a timeout: 75 seconds for Claude, Codex and Copilot, and 35 seconds for the others. LiteLLM gets 40 seconds when optional details are on. A longer web timeout in settings raises these, up to 65 seconds. +4. Results go into `AppState.provider_cache`. +5. The shell sends `provider-updated` for each provider and `refresh-complete` at the end. The React hook `useProviders` listens and redraws. -3. **Tray** - `tray_bridge` + `tray_menu`. Icon RGBA from shared `codexbar::tray::{render_bar_icon_rgba, render_percent_icon_rgba}`. +A full provider refresh also starts a status poll (`status_poller.rs`) while `status_checks_enabled` is on (the auto-resume probe does not). The poller fetches the status page of each enabled provider that has one, keeps the results in `AppState.provider_statuses`, and sends `provider-statuses-updated` with the full list when a status changes. The React hook `useProviderStatuses` reads it, and the tray tooltip adds a line per incident. See [PROVIDERS.md](./PROVIDERS.md#status-pages). -4. **Float bar** - Detached always-on-top window owned by `floatbar/`. Builder must pin `.theme(Some(tauri::Theme::Dark))` so WebView2’s shared profile does not flip other windows under theme `auto`. +`auto_refresh.rs` starts refreshes on a timer. The adaptive mode also checks for running coding agents (`coding_activity.rs`). -5. **CLI** - Same provider factory and settings stores as the app. Useful for scripts without UI (`usage`, `cost`, `guard`, `serve`, `config`, …). +### Settings -## Surfaces (desktop) +1. `Settings::load` reads `%APPDATA%\CodexBar\settings.json`. Other stores, such as `api_keys.json`, `manual_cookies.json` and `hooks.json`, sit in the same folder. `settings.json` and the secret stores are written through `secure_file`, which wraps them with DPAPI on Windows. `hooks.json` is plain JSON. See [CONFIGURATION.md](./CONFIGURATION.md#files-and-where-they-live). +2. The frontend calls `updateSettings` with a patch. +3. The shell saves the file and sends `settings-changed`. In the page, the DOM event `codexbar:settings-updated` tells other parts of the UI to reload. The float bar has its own config events. -- **Tray panel** — left-click tray; blur-dismiss (suppressed in proof mode and while a provider tab is dragged to reorder). A light, Mac-style panel 310 px wide (times the **Panel scale** setting in Settings > Menu) whose height fits its content. It always opens next to the tray and can't be moved or resized. -- **Tray panel flyout** — larger dashboard window opened from the tray - menu ("Pop Out Dashboard"); the tray panel itself is the only - dashboard layout. -- **Settings** — detached window; tabs: `general`, `providers`, `notifications`, `menuBar`, `menu`, `usageSpend`, `advanced`, `hooks`, `about`. -- **Float bar** — optional capacity strip. +The Hooks pane reads and writes `hooks.json` through the commands `get_hooks_config` and `save_hooks_config` (`commands/hooks.rs`). Hooks run only when both `hooks_enabled` in settings and `enabled` in `hooks.json` are on. -## Concurrency & platform +### Events -- Rust edition 2024; async via Tokio in CLI and shell. -- Provider fetches are concurrent with a semaphore cap in the shell. -- Windows-specific: DPAPI cookie decrypt, DWM dark caption, tray promotion, start-at-login (`HKCU\...\Run`), WebView2. -- Prefer validating tray/DPAPI/cookies on **native Windows**; WSL is insufficient for DPAPI. +The shell sends these Tauri events (`events.rs`): `surface-mode-changed`, `provider-updated`, `refresh-started`, `refresh-complete`, `update-state-changed`, `locale-changed`, `settings-changed`, `stay-awake-changed`, `codex-accounts-updated`, `login-phase` and `provider-statuses-updated`. -## Related docs +### Tray icon + +- `tray_bridge.rs` owns the icon and `tray_menu.rs` builds the right-click menu. +- `tray_presentation.rs` decides what the icon shows. The pixels come from `codexbar::tray` in `rust/src/tray/render.rs`. +- With **Color Pace Indicator** on (setting `menu_bar_color_pace`, in Settings > Menu Bar), a single-provider bar or percent icon turns green when usage is behind pace and red when it is ahead. On track or unknown pace keeps the normal usage color. Stacked icons keep the usage color. +- The pace for the tint is scored on one window. A provider can name that window with `Provider::menu_bar_pace_lane()`. When it returns `None`, the app uses the window the icon draws for the selected metric. + +### Pace + +Pace compares how much of a window you have used with how much time of it has passed. `commands/bridge/pace.rs` scores it into seven stages, from `far_behind` to `far_ahead`. + +Session and weekly windows get pace from their length and reset time. Providers that refill on a billing date opt in to reset-window pace: each declares a `PACE_CAPABILITY` in its own module, and `pace_capability()` in `provider_factory.rs` maps every `ProviderId` to it or to `ProviderPaceCapability::UNSUPPORTED`. The match is exhaustive, so a new provider must state its pace support to compile. [PROVIDERS.md](./PROVIDERS.md#pace) lists the rules. + +### CLI -- [BUILDING.md](./BUILDING.md) — build / test / release -- [CLI.md](./CLI.md) — command-line surface -- [CONFIGURATION.md](./CONFIGURATION.md) — settings paths and stores -- [PROVIDERS.md](./PROVIDERS.md) — provider factory and sources -- [COOKIES.md](./COOKIES.md) — browser cookie import -- Root [AGENTS.md](../AGENTS.md) — agent-oriented guidelines +The CLI uses the same provider factory and the same settings stores as the app. Use it for scripts without a UI, for example `usage`, `cost`, `guard` and `serve`. See [CLI.md](./CLI.md). + +## Proof harness + +`proof_harness.rs` reads `CODEXBAR_PROOF_MODE` and `CODEXBAR_SEED_USAGE_JSON` at startup. Proof mode opens one surface and keeps it open for automation. The seed fills the provider cache with synthetic data. See [WINDOWS_PROOF.md](./WINDOWS_PROOF.md). + +## Platform notes + +- Rust edition 2024. The CLI and the shell use Tokio. +- Windows-only parts: DPAPI cookie decryption, the dark DWM caption, tray icon promotion, start at login under `HKCU\...\Run`, and WebView2. +- Test tray, DPAPI and cookie behavior on Windows. WSL can't decrypt DPAPI data. See [WSL.md](./WSL.md). + +## Related docs -Upstream-only (macOS): WidgetKit, Sparkle, Keychain Safe Storage prompts, `Scripts/package_app.sh` — not used in this port. +- [BUILDING.md](./BUILDING.md): build, test and release. +- [CLI.md](./CLI.md): the command-line tool. +- [CONFIGURATION.md](./CONFIGURATION.md): settings files and the Settings window. +- [PROVIDERS.md](./PROVIDERS.md): providers, sign-in sources, windows and pace. +- [COOKIES.md](./COOKIES.md): browser cookie import. +- [WINDOWS_PROOF.md](./WINDOWS_PROOF.md): proving UI changes. +- [AGENTS.md](../AGENTS.md): rules for agents and contributors. diff --git a/docs/BUILDING.md b/docs/BUILDING.md index 04c4fd4f39..496241f61c 100644 --- a/docs/BUILDING.md +++ b/docs/BUILDING.md @@ -1,175 +1,228 @@ -# Building from Source +# Building from source + +This page shows how to build and test Win-CodexBar on Windows. The desktop app is the Tauri shell in `apps/desktop-tauri`. The CLI and the shared backend are the `codexbar` crate in `rust/`. ## Prerequisites -- **Rust** stable with the `x86_64-pc-windows-msvc` target -- **Microsoft Visual Studio Build Tools** with the **Desktop development with C++** workload -- **Node.js** 22.13+ (24 recommended) and pnpm 11 +- Rust stable with the `x86_64-pc-windows-msvc` target. CI pins the version in `scripts/circleci-pinned-rust.txt` (1.98.0). +- Visual Studio Build Tools with the **Desktop development with C++** workload. +- Node.js. CI uses Node 24.18.0, so use the same version locally. +- pnpm, at the exact version in the `packageManager` field of `apps/desktop-tauri/package.json` (`pnpm@11.25.0`). Corepack can activate it. Don't use npm or yarn. + +Install the tools yourself with rustup, winget or corepack, or with a tool manager such as mise. The repo has no setup script that installs them. -Install the tools manually with rustup/winget/corepack, or use a tool manager -such as mise. There is no automatic Windows bootstrap script in this port. +## Build the desktop app -## Build the Desktop App +Run these from the repo root: ```powershell -cd apps/desktop-tauri -pnpm install --frozen-lockfile -cd ../.. +pnpm --dir apps/desktop-tauri install --frozen-lockfile pnpm --dir apps/desktop-tauri run tauri:build ``` -The release binary lands at `target/release/codexbar-desktop-tauri.exe`. +`tauri:build` runs `tauri build --no-bundle`. It builds the frontend and the release desktop binary, `codexbar-desktop-tauri.exe`, and makes no installer. + +For a faster debug build: -For a debug build (faster compile, no optimisations): ```powershell -cd apps/desktop-tauri -pnpm run tauri:build:debug +pnpm --dir apps/desktop-tauri run tauri:build:debug ``` -## Build the CLI Only +A raw `cargo build --release` of the Tauri crate can embed the dev server URL instead of the built frontend. Use the pnpm scripts or `scripts/dev.ps1` instead. + +## Build the CLI only ```powershell cargo build -p codexbar --release -# Binary at: target/release/codexbar.exe ``` -## Dev Mode (Hot Reload) +The binary is `codexbar.exe`. + +## Where the binaries land + +Without extra setup, Cargo writes to `target\` in the repo root, for example `target\release\codexbar-desktop-tauri.exe` and `target\release\codexbar.exe`. + +In a Git worktree, load `scripts/worktree-env.ps1` first. It sets `CARGO_TARGET_DIR` for the current PowerShell process only, to a folder outside every registered worktree: + +```powershell +. .\scripts\worktree-env.ps1 +``` + +The default folder is `%LOCALAPPDATA%\Win-CodexBar\cargo-target\repo-\worktree-`. The script prints the path it chose. Set `WCB_CARGO_TARGET_ROOT` to change the root folder, or `WCB_CARGO_TARGET_DIR` to set the exact folder. `scripts/local-check.ps1` loads this script itself. + +## Run the app while you develop + +`scripts/dev.ps1` builds the desktop app and starts it: + +```powershell +.\scripts\dev.ps1 +.\scripts\dev.ps1 -Release +.\scripts\dev.ps1 -SkipBuild +.\scripts\dev.ps1 -Verbose +``` + +- With no switch, it makes a debug build with `tauri:build:debug` and starts it. +- `-Release` builds with `tauri:build` instead. +- `-SkipBuild` starts the last build without building again. +- `-Verbose` sets `RUST_LOG=debug`. + +The script installs nothing and doesn't run `pnpm install`. It checks that `cargo` and `pnpm.cmd` are on `PATH`, and it checks for `dlltool` on GNU toolchain hosts only. Run `pnpm install` yourself first. + +For hot reload of the frontend, run Tauri dev mode: ```powershell -.\scripts\dev.ps1 # default debug build + launch -.\scripts\dev.ps1 -Release # optimised build -.\scripts\dev.ps1 -Verbose # debug logging -.\scripts\dev.ps1 -SkipBuild # run last build without rebuilding +pnpm --dir apps/desktop-tauri run tauri:dev ``` -Or directly: +On macOS, Linux or WSL, `scripts/dev.sh` takes `--release`, `--skip-build`, `--verbose` and `--cli`. In WSL with no display server, it switches to CLI mode. + +## Run the checks + +Run the same check as the hosted PR check: + +```powershell +.\scripts\local-check.ps1 -Slice ci +``` + +`CONTEXT.md` lists its 11 steps. With no parameters, `local-check.ps1` runs a shorter developer check: the worktree storage report, the `gh-safe.sh` tests, both Rust test suites, and the frontend tests and build. Add `-Format`, `-Clippy`, `-ReleaseDoctor` or `-All` for more. + +To run single steps: + ```powershell -cd apps/desktop-tauri && pnpm run tauri:dev +cargo fmt --all --check +cargo clippy --workspace --all-targets -- -D warnings +cargo test --workspace +pnpm --dir apps/desktop-tauri test +pnpm --dir apps/desktop-tauri run lint +pnpm --dir apps/desktop-tauri run build ``` -## Fast Windows Release Build +`pnpm run build` runs `check-locale` first, then `tsc --noEmit` and `vite build`. `check-locale` fails when the Rust and TypeScript locale keys differ. + +Tests and builds don't prove tray, settings, float bar or WebView2 behavior. See [WINDOWS_PROOF.md](./WINDOWS_PROOF.md). + +## Build a release on Windows -For repeat release builds on a Windows server, prefer the cached release script: +GitHub Actions builds, signs and drafts the published releases when a `vX.Y.Z` tag is pushed (`.github/workflows/release.yml`). [release/ci-cd.md](./release/ci-cd.md) describes that flow. + +To build release assets on your own Windows PC, use the cached release builder: ```powershell -.\scripts\windows-release-build.ps1 -Ref v0.27.4 +.\scripts\windows-release-build.ps1 -Ref vX.Y.Z ``` -It builds from a clean managed checkout but keeps Cargo output, the pnpm store, -and signed installer bootstrapper downloads in `C:\code\Win-CodexBar-release\cache`. -Release assets land in `C:\code\Win-CodexBar-release\assets`. Keep the -`.sha256` sidecars; they are the copy/paste source for Winget's -`InstallerSha256`. +It builds from a clean checkout under `-WorkRoot` (default `C:\code\Win-CodexBar-release`): + +- `source\` holds the checkout. +- `cache\` keeps the pnpm store, the Cargo output and the installer downloads between runs. +- `assets\` receives the release files. + +The script renames the binaries for release. The desktop app becomes `codexbar.exe`, with `codexbar-desktop.exe` as a second name for older shortcuts. The CLI becomes `codexbar-cli.exe`. It writes these assets, each with a `.sha256` file: -Useful release flags: +| Asset | Contents | +| --- | --- | +| `CodexBar--Setup.exe` | Inno Setup installer for `codexbar.exe`, `codexbar-cli.exe` and `codexbar-desktop.exe` | +| `CodexBar--portable.exe` | The desktop app as one file | +| `CodexBarCLI-v-windows-x64.zip` | `codexbar-cli.exe` | + +Keep the `.sha256` files. They are the source for Winget's `InstallerSha256`. + +Other switches: + +| Switch | What it does | +| --- | --- | +| `-WarmCacheOnly` | Builds the desktop binary to fill the caches, then stops before installer packaging | +| `-SmokeInstall` | Installs and uninstalls the new installer to test it | +| `-RefreshInstallerDependencies` | Downloads the installer bootstrappers again instead of reusing the cached copies | +| `-RepoUrl` | The repo to clone when the managed checkout doesn't exist yet (default `https://github.com/nesszer/Win-CodexBar.git`) | + +The script only builds and tests. It has no upload switch. + +To check release assets: ```powershell -.\scripts\windows-release-build.ps1 -Ref vX.Y.Z -WarmCacheOnly -.\scripts\windows-release-build.ps1 -Ref vX.Y.Z -SmokeInstall .\scripts\release-doctor.ps1 -Version X.Y.Z ``` -`windows-release-build.ps1` only builds and smoke-tests. It has no upload -switch. The hosted release path is the approval-gated CircleCI workflow -documented in `docs/release/ci-cd.md`; publication uses its no-clobber, -SHA-256-checked draft publisher. +`release-doctor.ps1` also takes `-AssetsDir` and `-SkipGitHub`. + +To test an installer on its own: + +```powershell +.\scripts\windows-smoke-install.ps1 -InstallerPath -ExpectedVersion X.Y.Z +``` + +The smoke test checks the silent install switches (`/VERYSILENT /SUPPRESSMSGBOXES /NORESTART`), the installed files, the CLI `--help` and `--version` output, the Start Menu shortcut, the uninstall registry entry and the silent uninstall. Add `-LeaveInstalled` to keep the app installed for manual checks, or `-InstallDir` to choose the install folder. -## macOS Windows Cross Build +## Cross-build from macOS -For a fast compile check from macOS, use the cross-build wrapper: +For a compile check from a Mac, use the cross-build wrapper: ```bash ./scripts/macos-windows-cross-build.sh ``` -Or call the desktop package script directly: +Or run the package script: ```bash pnpm --dir apps/desktop-tauri run tauri:build:windows-cross ``` -This uses `cargo-xwin` plus Homebrew `llvm`/`lld` to build the Windows MSVC -Tauri executable at `target/x86_64-pc-windows-msvc/release/codexbar-desktop-tauri.exe`. -It is useful for catching frontend, Tauri, and Windows-target Rust compile -failures from a Mac. It does not replace the Windows server release path: -installer packaging, tray behavior, WebView2, DPAPI, startup integration, and -smoke install validation still need a real Windows machine. +This uses `cargo-xwin` with Homebrew `llvm` and `lld`, and builds `target/x86_64-pc-windows-msvc/release/codexbar-desktop-tauri.exe`. It catches frontend, Tauri and Windows-target Rust compile errors. It doesn't replace a Windows build: installer packaging, tray behavior, WebView2, DPAPI, startup and smoke install still need a real Windows PC. -## Project Structure +## Project layout ``` Win-CodexBar/ -├── apps/desktop-tauri/ # Tauri desktop shell -│ ├── src/ # React frontend (TypeScript) -│ └── src-tauri/ # Tauri/Rust backend -│ └── src/ -│ ├── commands/ # Tauri IPC commands -│ ├── shell/ # Window management, DWM, tray bridge -│ └── main.rs # App entry point -├── rust/ # Shared backend crate + CLI +├── apps/desktop-tauri/ Tauri desktop shell +│ ├── src/ React frontend (TypeScript) +│ └── src-tauri/src/ +│ ├── commands/ Tauri commands called by the frontend +│ ├── shell/ Windows: activation, DWM, flyout and Settings windows +│ ├── floatbar/ Float bar window +│ ├── tray_bridge.rs Tray icon and menu +│ └── main.rs Desktop entry point +├── rust/ Shared backend crate and CLI │ └── src/ -│ ├── providers/ # Per-provider fetch/parse/auth -│ ├── core/ # Provider IDs, cost pricing -│ ├── browser/ # Browser cookie extraction (DPAPI) -│ ├── tray/ # Tray icon rendering -│ └── main.rs # CLI entry point -├── docs/ # Documentation -└── scripts/ # Dev/release helper scripts +│ ├── core/ Provider ids, the Provider trait, the provider factory +│ ├── providers/ One module per provider +│ ├── settings/ Settings model, load and save +│ ├── browser/ Browser detection and cookie import +│ ├── tray/ Tray icon renderer +│ ├── cli/ CLI subcommands +│ └── main.rs CLI entry point +├── docs/ Documentation +└── scripts/ Dev, check and release scripts ``` ## Documentation map | Doc | Contents | -|-----|----------| -| [ARCHITECTURE.md](./ARCHITECTURE.md) | Modules, entry points, data flow (Windows/Tauri) | -| [CLI.md](./CLI.md) | `codexbar.exe` commands | -| [CONFIGURATION.md](./CONFIGURATION.md) | Config paths, `codexbar config`, settings tabs | -| [PROVIDERS.md](./PROVIDERS.md) | Provider factory and sources | -| [COOKIES.md](./COOKIES.md) | Browser cookie import (DPAPI) | -| [WSL.md](./WSL.md) | WSL limitations | -| [WINDOWS_PROOF.md](./WINDOWS_PROOF.md) | Manual/runtime proof checklist | -| [release/ci-cd.md](./release/ci-cd.md) | Hosted PR check + local release | -| [../AGENTS.md](../AGENTS.md) | Agent/contributor guidelines | - -Upstream macOS docs (`steipete/CodexBar`) are a **read-only** concept source. Do not copy Swift/Keychain/Sparkle instructions here without a Windows rewrite. - - -## Running Tests +| --- | --- | +| [ARCHITECTURE.md](./ARCHITECTURE.md) | Modules, entry points and data flow | +| [CLI.md](./CLI.md) | `codexbar` CLI commands | +| [CONFIGURATION.md](./CONFIGURATION.md) | Settings, config files and the Settings window | +| [PROVIDERS.md](./PROVIDERS.md) | Providers, sign-in sources and usage windows | +| [COOKIES.md](./COOKIES.md) | Browser cookie import | +| [WSL.md](./WSL.md) | WSL limits | +| [WINDOWS_PROOF.md](./WINDOWS_PROOF.md) | Proving UI changes on Windows | +| [release/ci-cd.md](./release/ci-cd.md) | Hosted PR check and the release workflow | +| [../AGENTS.md](../AGENTS.md) | Rules for agents and contributors | + +The upstream macOS docs (`steipete/CodexBar`) are read-only reference. Don't copy Swift, Keychain or Sparkle steps here without rewriting them for Windows. -```bash -# Shared crate tests -cargo test --manifest-path rust/Cargo.toml - -# Tauri crate tests -cargo test --manifest-path apps/desktop-tauri/src-tauri/Cargo.toml +## Stored process environments -# TypeScript type check -cd apps/desktop-tauri && pnpm exec tsc --noEmit +A process environment carries API keys, tokens and cookies. Every struct, union or enum-variant field that stores one uses `codexbar::process_environment::ProcessEnvironment`. This includes optional maps and `(OsString, OsString)` pair lists. -# Lint -cargo clippy --all-targets -- -D warnings -cargo fmt --all --check -``` +- Its `Debug` output is `ProcessEnvironment(N entries; redacted)`. So `{:?}`, `dbg!`, `tracing` field captures and `assert_eq!` failures never print variable names or values. +- Wrapping an `Option` keeps `None` apart from an empty map. Equality still compares the original contents. +- Deref, iteration, `get` and `insert` still return the original values for the child process. Never log what they return. +- Function parameters and locals are short-lived and stay unwrapped. -## Stored process environments +The `process_environment::storage_guard` test scans `rust/src` and `apps/desktop-tauri/src-tauri/src` for fields with an environment-like name whose type is a string map or a string pair list. It looks through `Option`, `Box`, `Arc`, references, slices and local `type` aliases, and fails on any field that is not wrapped. Each entry in its `REVIEWED_EXCEPTIONS` list must match exactly one declaration, so stale or duplicate exceptions fail too. -A process environment carries API keys, tokens and cookies. Every struct, union or enum-variant -field that stores one uses `codexbar::process_environment::ProcessEnvironment`, including -optional maps and `(OsString, OsString)` pair lists. Its `Debug` output is -`ProcessEnvironment(N entries; redacted)`, so `{:?}`, `dbg!`, `tracing` field captures and -`assert_eq!` failures never print variable names or values. Wrapping an `Option` keeps `None` -distinct from an empty map, and equality still compares the original contents. - -Deref, iteration, `get` and `insert` still return the original values for the child process, -so never log what they return. Function parameters and locals are transient and stay unwrapped. - -The `process_environment::storage_guard` test scans `rust/src` and -`apps/desktop-tauri/src-tauri/src` for environment-named fields whose type is a string map or a -string pair list, through `Option`, `Box`, `Arc`, references, slices and local `type` aliases, -and fails on any that is not wrapped. Each entry in its `REVIEWED_EXCEPTIONS` list must match -exactly one declaration, so stale or duplicate exceptions fail too. The guard is a lexical -tripwire, not a type checker: generic parameters, differently named fields and explicit logging -of an unwrapped map still need code review. +The guard matches text. It doesn't check types. Generic parameters, fields with other names and explicit logging of an unwrapped map still need code review. ```bash cargo test -p codexbar process_environment diff --git a/docs/CLI.md b/docs/CLI.md index 5a5903cd4d..c480e0dcc4 100644 --- a/docs/CLI.md +++ b/docs/CLI.md @@ -1,26 +1,27 @@ -# CodexBar CLI (Windows) +# CodexBar CLI on Windows -Windows rewrite of upstream `docs/cli.md` for the **`codexbar`** binary built from `rust/`. -Upstream install paths (`/Applications`, Homebrew, Sparkle-bundled Helpers) do **not** apply. +The `codexbar` CLI prints provider usage and local cost, gates scripts on remaining quota, and serves a local dashboard. It is built from the `rust/` crate and shares its settings with the desktop app. -## Install / build +This page covers the Windows port. The upstream macOS install paths (`/Applications`, Homebrew, Sparkle helpers) don't apply. + +## Build and run the CLI + +Run these from the repo root: ```powershell -# From repo root cargo build -p codexbar --release -# Binary: target\release\codexbar.exe (or target\\release\ under some setups) - cargo run -p codexbar -- --help ``` -Release installers may place CLI next to the desktop app; for development, run the cargo-built `codexbar.exe` or put it on `PATH` yourself. There is no “Preferences → Install CLI” symlink flow like macOS. +The build writes `target\release\codexbar.exe`. If `CARGO_TARGET_DIR` is set, the exe is under that folder instead. -## Configuration +The installer and release ZIP name the CLI `codexbar-cli.exe`. The installed `codexbar.exe` is the desktop app. See [BUILDING.md](./BUILDING.md) for the release names. There is no "Install CLI" button like on macOS, so put the exe on `PATH` yourself. -CLI and desktop app share the same Windows config directory (see [CONFIGURATION.md](./CONFIGURATION.md)): +`codexbar` with no subcommand prints a hint and exits with code 64. Always pass a subcommand. -- Settings: `%AppData%\Roaming\CodexBar\settings.json` -- Manual cookies / API keys / token accounts: sibling files under that folder +## Point the CLI at your settings + +The CLI and the desktop app read the same settings folder, `%APPDATA%\CodexBar\`. See [CONFIGURATION.md](./CONFIGURATION.md) for the files in it. ```powershell codexbar config path @@ -28,53 +29,63 @@ codexbar config validate codexbar config dump ``` -Set `CODEXBAR_CONFIG` to run the CLI against another settings file; see [Separate config file](#separate-config-file-codexbar_config). - -## Commands (current) +`config dump` redacts secrets unless you pass `--show-secrets`. It writes JSON by default, or TOML with `-f toml`. -Top-level (from `codexbar --help`): +To use a different settings file, set `CODEXBAR_CONFIG`. See [Separate config file](#separate-config-file-codexbar_config). -| Command | Purpose | -|---------|---------| -| `usage` | Print usage from the enabled providers, or the ones passed with `-p` | -| `cost` | Local token cost usage (Claude + Codex session scans; no web required for those) | -| `guard` | Gate automation on remaining quota for one provider | -| `diagnose` | Export safe provider diagnostics as JSON | -| `sessions` | List or focus local / SSH agent sessions | -| `serve` | HTTP JSON/dashboard server with optional Prometheus metrics | -| `dashboard` | Emit a one-shot dashboard snapshot (JSON to stdout or `--output` file) | -| `autostart` | Manage Windows boot auto-start | -| `account` | Token accounts for providers | -| `config` | validate / dump / providers / enable / disable / set-api-key / path / claude-code-credentials | -| `hooks` | List, enable, disable, test, or watch external hooks | -| `workspaces` | List local Codex project/workspace usage | - -### Usage +## Check usage ```powershell codexbar usage codexbar usage -p claude -f json --pretty codexbar usage -p all --status -codexbar usage --source auto # auto | web | cli | oauth +codexbar usage --source oauth codexbar usage --brief ``` -These flags belong to `usage`: `-p/--provider`, `-f/--format` (`text`, `json` or `toon`), `--json`, `--pretty`, `--status`, `--all-accounts`, `--account`, `--no-credits`, `--no-color`, `--source`, `--web-timeout`, `--brief`. The root command takes only `-v/--verbose`, `--json-output` (JSON logs on stderr) and `--no-color`; `codexbar` without a subcommand prints a hint and exits with a usage error. +`usage` options: + +| Option | What it does | +| --- | --- | +| `-p`, `--provider` | A provider CLI name, `both` (Codex and Claude) or `all`. Default: the providers enabled in settings. | +| `-f`, `--format` | `text` (default), `json` or `toon`. | +| `--json` | Same as `--format json`. | +| `--pretty` | Pretty-prints JSON. | +| `--status` | Also fetches each provider's status page. | +| `--all-accounts` | Fetches every token account, where the provider supports it. | +| `--account` | A token-account label or 1-based index. Needs exactly one provider. | +| `--no-credits` | Leaves out the credits line. | +| `--source` | `auto` (default), `oauth`, `web` or `cli`. | +| `--web-timeout` | Web fetch timeout in seconds. Default 60. | +| `--brief` | One short line per provider. | + +The global options are `-v`/`--verbose`, `--json-output` (JSON logs on stderr) and `--no-color`. + +Which providers `usage` fetches without `-p`: + +- If exactly Codex and Claude are enabled, it fetches them as `both`. +- If one provider is enabled, it fetches that one. +- Any other set is fetched in display order. +- If nothing is enabled, JSON and TOON print an empty list. Text output explains how to enable a provider (`codexbar config enable `) or pass `--provider`. -Without `-p`, `usage` follows the providers enabled in settings, like upstream: exactly Codex and Claude are fetched as `both`, one enabled provider is a single fetch, and any other set keeps the display order. With nothing enabled, JSON and TOON print an empty list, and text explains how to enable a provider (`codexbar config enable `) or pass `--provider`. An explicit `-p` (a provider, `both` or `all`) never reads the enabled list. `--account` needs exactly one provider, including when the enabled list resolves to several. `cost` keeps its Claude default. +An explicit `-p` never reads the enabled list. `--account` needs exactly one provider, even when the enabled list has several. -**Error rows.** In JSON and TOON output a provider that fails becomes a row with `provider`, the human-readable `error`, and `errorKind`, so scripts can react without parsing the text: +### Error rows + +In JSON and TOON output, a provider that fails becomes a row with `provider`, the readable `error` and an `errorKind`. Scripts can branch on `errorKind` without parsing text. | `errorKind` | Meaning | -|---|---| -| `needsAuthentication` | Credentials are missing or were rejected; sign in again or add a key. | -| `expiredSession` | The credentials worked but the session or token has expired. | -| `localRuntimeOffline` | A local runtime the provider reads is not running, for example the Claude CLI is not installed. | -| `browserSignInRequired` | Only a browser sign-in can bring usage back. The row adds `signInUrl`, the page to open. | +| --- | --- | +| `needsAuthentication` | Credentials are missing or were rejected. Sign in again or add a key. | +| `expiredSession` | The credentials worked, but the session or token expired. | +| `localRuntimeOffline` | A local runtime the provider reads is not running. For example, the Claude CLI is not installed. | +| `browserSignInRequired` | Only a browser sign-in can restore usage. The row adds `signInUrl`, the page to open. | | `timeout` | The fetch ran out of time. | | `unknown` | Anything else, including failures before the provider runs (account selection, stored keys). | -The first three use the same names as the desktop's provider states. Claude reports `browserSignInRequired` (with `signInUrl` `https://claude.ai/login`) when its OAuth usage endpoint is rate limited, no claude.ai browser session was found, and the CLI probe failed too. Until the rate limit lifts, signing in to claude.ai in a browser is the only way back, and the `error` text says so. +The first three use the same names as the desktop's provider states. + +Claude reports `browserSignInRequired` with `signInUrl` `https://claude.ai/login` when all three of these happen: its OAuth usage endpoint is rate limited, no claude.ai browser session is found, and the CLI probe fails. Until the rate limit lifts, signing in to claude.ai in a browser is the only fix. The `error` text says so. ```json { @@ -85,7 +96,7 @@ The first three use the same names as the desktop's provider states. Claude repo } ``` -### Cost +## Check local cost ```powershell codexbar cost @@ -97,51 +108,134 @@ codexbar cost --period all --json codexbar cost --days 7 --json ``` -Claude/Codex costs come from local session logs. Antigravity reads supported local token history; known models receive API list-price estimates from the bundled price table or the models.dev pricing catalog, and unknown models stay unpriced rather than becoming a false `$0`. Antigravity's Gemini 3.1 Pro aliases (`gemini-pro-default`, `gemini-pro-agent`, and the `gemini-3.1-pro` effort tiers) price as `gemini-3.1-pro-preview`, the only catalogued Gemini 3.1 Pro row; the alias is provider-local and recorded model names stay unchanged. These estimates are not Antigravity charges or credit deductions. Other providers may differ; do not assume upstream Cursor dashboard cost behavior unless implemented in this tree. +`cost` reads local logs and makes no web or CLI calls for Claude and Codex. `-p` takes `codex`, `claude`, `pi`, `muse`, `antigravity`, `cursor`, `gemini`, `copilot`, `all` or `both`. The default is Claude. `-f` takes `text` or `json`. + +Claude and Codex costs come from local session logs. Other providers may work differently. Don't assume upstream Cursor dashboard cost behavior unless this tree implements it. + +### Antigravity cost + +Antigravity reads supported local token history. Known models get API list-price estimates from the bundled price table or the models.dev pricing catalog. Unknown models stay unpriced, never a false `$0`. These estimates are not Antigravity charges or credit deductions. + +Antigravity's Gemini 3.1 Pro aliases (`gemini-pro-default`, `gemini-pro-agent` and the `gemini-3.1-pro` effort tiers) price as `gemini-3.1-pro-preview`, the only Gemini 3.1 Pro row in the catalog. The alias applies only inside this provider. Recorded model names don't change. + +Incomplete Antigravity history is never shown as exact: + +- A scan with trustworthy decoded rows reports `tokensAreLowerBound` and `costIsLowerBound`. Text output says "at least N". +- A scan cut off by a hard limit, or one whose sources contradict each other, is withheld and publishes no total. +- Within one process, a later partial read doesn't replace an earlier complete read of the same window and roots. + +Reading the history never waits on the network. When a recorded model has no known price, the desktop Usage & Spend view and `serve /cost` start one bounded models.dev pricing refresh in the background, and a later read uses the new prices. `codexbar cost --provider antigravity --refresh` waits for that refresh and rescans. A plain `codexbar cost` starts no download. The request carries no account identity or usage data. If it fails, the affected models stay unpriced. Empty or missing history never starts a pricing download. -Antigravity history that stopped short is never shown as exact. A scan whose decoded rows are trustworthy reports `tokensAreLowerBound` / `costIsLowerBound` (text: "at least N"); a scan cut off by a hard limit, or one whose sources contradict each other, is withheld and publishes no total. A later partial read does not replace an earlier complete read of the same window and roots within one process. Reading the history never waits on the network. When a recorded model has no known public price, the desktop Usage & Spend view and `serve /cost` start one bounded models.dev pricing refresh in the background and a later read picks up the new prices; `codexbar cost --provider antigravity --refresh` waits for that refresh and rescans, while a plain `codexbar cost` starts no download. The request carries no account identity or usage data, and a failure leaves the affected models unpriced. Empty or absent history never starts a pricing download. -**Reporting period.** `--period month-to-date|all` selects the calendar month so far (midnight on the first of the month, in the machine's local zone, through today) or all available local history. `--days N` (1..=365) always selects a rolling window and wins over `--period`. With neither flag the saved `cost_reporting_period` in `settings.json` applies (`rolling:30`, `month-to-date` or `all`; 30 days when unset). Text headings show the period label (`Last 30 days`, `Month to date`, `All`, `Today`). JSON payloads add `reportingPeriod` (the raw value), `historyLabel`, and a `totals` object (`inputTokens`, `outputTokens`, `cachedTokens`, `reasoningTokens`, `totalTokens`, `totalCost`) for the selected window, and `days_scanned` is the resolved day count; the existing `cost`, `tokens`, and `sessions_count` fields keep their meaning. `totalTokens` follows each provider's token rule: Claude and Pi add cache reads and writes to input + output, while Codex input already includes cached input, so its total is input + output. `totals` is `null` when a scan found nothing without establishing a known zero. +### Reporting period -`--remote` and `--summary-only` keep the 1..=365 day protocol: month to date is sent as its current day count, and All is rejected unless you pass an explicit `--days N`. `--group-by session` lists the most recent 365 days at most when All is selected. All reads every available local log, including logs older than a year; missing or deleted logs cannot be recovered and incomplete scans stay marked incomplete. +| Flag | Window | +| --- | --- | +| `--days N` (1 to 365) | A rolling window of N days. Wins over `--period`. | +| `--period month-to-date` | From midnight on the first of the month, local time, through today. | +| `--period all` | All available local history. | +| Neither flag | The saved `cost_reporting_period` in `settings.json`: `rolling:30`, `month-to-date` or `all`. 30 days when unset. | -`serve` `/cost` reads the saved period on every request, so `days_scanned`, `totals`, the scanner windows and the `daily` chart rows follow it (`daily` covers at most the latest 365 days for All). The dashboard cost line, the dashboard snapshot, and the Prometheus gauge `codexbar_cost_last_30_days_usd` keep their 30-day meaning. +Text headings show the period label: `Last 30 days`, `Month to date`, `All` or `Today`. -Claude/Codex costs come from local session logs. Antigravity exposes local **token history only** through `cost`; dollar cost remains unknown rather than becoming a false `$0`. Other providers may differ; do not assume upstream Cursor dashboard cost behavior unless implemented in this tree. +JSON output adds: -`--remote` adds one separate native Codex report fetched through non-interactive SSH; overlapping local and remote histories are never combined. `--summary-only` emits the versioned, path-free JSON contract used by the remote comparison and accepts only `--provider codex --format json`. Both modes reject session grouping and other provider selections. +- `reportingPeriod`, the raw value; +- `historyLabel`; +- a `totals` object for the selected window, with `inputTokens`, `outputTokens`, `cachedTokens`, `reasoningTokens`, `totalTokens` and `totalCost`. -Codex local-history scans use a 60-second scanner-side debounce for ordinary disk-cache reads. This is separate from the desktop provider refresh setting. With Adaptive refresh off, **Manual** (`refresh_interval_secs = 0`) disables the recurring desktop refresh timer, but it does not forbid startup/stale-aware reads, explicit refreshes, or pending Codex catch-up scans. Low Power Mode floors recurring automatic refreshes to 30 minutes; explicit/manual work remains immediate. +`days_scanned` is the resolved day count. The `cost`, `tokens` and `sessions_count` fields keep their meaning. `totals` is `null` when a scan found nothing and couldn't establish a known zero. -### Guard +`totalTokens` follows each provider's token rule. Claude and Pi add cache reads and writes to input plus output. Codex input already includes cached input, so its total is input plus output. + +`--remote` and `--summary-only` keep the 1 to 365 day protocol. They send month to date as its current day count, and they reject All unless you also pass `--days N`. `--group-by session` lists at most the latest 365 days when All is selected. All reads every available local log, including logs older than a year. Missing or deleted logs can't be recovered, and incomplete scans stay marked incomplete. + +`serve` `/cost` reads the saved period on every request. `days_scanned`, `totals`, the scanner windows and the `daily` chart rows follow it. `daily` covers at most the latest 365 days for All. The dashboard cost line, the dashboard snapshot and the Prometheus gauge `codexbar_cost_last_30_days_usd` always mean 30 days. + +### Remote and summary reports + +`--remote ` adds one separate native Codex report, fetched over non-interactive SSH. Local and remote histories are never merged. `--summary-only` prints the versioned, path-free JSON contract that the remote comparison uses. It accepts only `--provider codex --format json`. Both modes reject session grouping and other providers. + +`--provider-native-only` leaves out pi/OMP agent session mirrors. This Windows build has no such mirrors, so the flag is accepted but changes nothing here. + +### Scan timing + +Codex local-history scans debounce ordinary disk-cache reads for 60 seconds. This is separate from the desktop refresh setting. + +With Adaptive refresh off, **Manual** (`refresh_interval_secs = 0`) turns off the recurring desktop refresh timer. Startup reads, stale-aware reads, explicit refreshes and pending Codex catch-up scans still run. Low Power Mode makes recurring automatic refreshes wait at least 30 minutes. Explicit refreshes still run at once. + +## Gate a script on quota ```powershell codexbar guard -p claude --min-remaining 10 --window session codexbar guard -p codex --json --pretty --fail-open ``` -Exit codes (stable intent): `0` ok, `1` below threshold, usage errors for bad args, unavailable when quota cannot be checked (`--fail-open` turns unavailable into `0`). +`guard` checks one provider. `--window` is `session` (the primary window, default) or `weekly` (the secondary window). `--min-remaining` is a percent, default 10. `--timeout` is the overall fetch timeout in seconds, default 60, where 0 turns it off. + +| Exit code | Meaning | +| --- | --- | +| `0` | Remaining quota is at or above the threshold. | +| `1` | Remaining quota is below the threshold. | +| `64` | Invalid arguments. | +| `69` | Quota is unavailable. `--fail-open` turns this into `0`. | -### Serve +## Export diagnostics + +```powershell +codexbar diagnose --pretty +codexbar diagnose -p codex --pretty +``` + +`diagnose` writes safe provider diagnostics as JSON. `-p` defaults to `all`, which means the enabled providers. + +## Serve the local dashboard ```powershell codexbar serve --port 8080 -# Non-loopback binds need a dashboard token and --allow-plain-http (cleartext bearer). -# Prefer: $env:CODEXBAR_DASHBOARD_TOKEN = '...' ``` -Typical endpoints: `/health`, `/usage`, `/cost`, and `/dashboard/v1/snapshot`. Loopback default keeps local use simple; treat non-loopback as a threat-model choice because the token for protected requests crosses the network over HTTP. +`serve` binds to `127.0.0.1:8080` by default. A non-loopback `--host` needs a dashboard token and `--allow-plain-http`, because the token then crosses the network over cleartext HTTP. Set the token in `CODEXBAR_DASHBOARD_TOKEN` rather than on the command line. + +| Route | What it returns | +| --- | --- | +| `/` | The dashboard page. | +| `/health` | Health check. | +| `/usage` | Usage JSON. Without a `provider` query, it follows the enabled providers like a plain `codexbar usage`. | +| `/cost` | Cost JSON. Defaults to Claude. | +| `/dashboard/v1/snapshot` | The stable dashboard-v1 JSON snapshot. | +| `/metrics` | Prometheus text, only with `--metrics`. Otherwise `404`. | +| `/icons/.svg` | Embedded provider icons. No auth. | + +`/usage` fetches the selected providers at the same time. Its error rows carry the same `errorKind` and `signInUrl` as `usage --json`. + +`--refresh-interval` sets how many seconds the dashboard snapshot is cached. Default 60. + +`--identity redacted|full` sets how much account identity the snapshot shows. Without it, each request follows the app's Hide Personal Info setting. -The dashboard page at `/` has a "Usage display" control (Follow server, Used, Remaining). Follow server uses `host.usageBarsShowUsed` from the snapshot; the other two override it for this browser only, stored in `localStorage` under `codexbar.dashboard.usageDisplay`. Changing it re-renders the cached snapshot without refetching. Bar colour always follows consumption (70% used warns, 90% used is full), whichever value is shown. +### Provider icons -`/usage` without a `provider` query (or with an empty one) follows the enabled providers, like a plain `codexbar usage`; `/cost` still defaults to Claude. `/usage` fetches the selected providers concurrently, and its error rows carry the same `errorKind` (and `signInUrl`) as `usage --json`. +The dashboard shows each provider's brand icon from the embedded `/icons/` route. The icons are copies of the upstream `ProviderIcon-*.svg` files. #828 added icons for ClawRouter, IBM Bob, Langdock, LithosAI, MuseAI, Synthetic and WorkBuddy. A provider with no embedded icon shows a neutral dot. Icons that use `currentColor` render dark inside the page's `` tags, as upstream does. + +### Usage display control + +The dashboard page has a "Usage display" control with Follow server, Used and Remaining. Follow server uses `host.usageBarsShowUsed` from the snapshot. The other two override it for this browser only, stored in `localStorage` under `codexbar.dashboard.usageDisplay`. Changing it re-renders the cached snapshot without fetching again. Bar colour always follows consumption, whichever value is shown: 70% used warns and 90% used is full. + +### Request timeout ```powershell codexbar serve --request-timeout 30 ``` -`--request-timeout ` bounds `/usage` and `/cost`. A provider still running after 0.8 of the timeout becomes a row such as `{"provider":"claude","error":"claude usage timed out","errorKind":"timeout"}` (`/cost`: `codex cost refresh timed out`), and a request that outlives the whole timeout answers `504` with `{"error":"request timed out"}`. The fetch behind a timed-out row keeps running: the next request for that provider joins it instead of starting another, and at most eight fetches or scans run at once. Finished results are not cached, so the first request after a fetch completes starts a new one. The default `0` waits for every provider. Upstream defaults to 30 seconds and serves the late result from a response cache; this port has no such cache, so the timeout is opt-in. Values are capped at 86400, and negative values are rejected with `--request-timeout must be zero or greater.` +`--request-timeout ` limits `/usage` and `/cost`. + +- A provider still running at 0.8 of the timeout becomes a row such as `{"provider":"claude","error":"claude usage timed out","errorKind":"timeout"}`. For `/cost` the error is `codex cost refresh timed out`. +- A request that runs past the whole timeout gets `504` with `{"error":"request timed out"}`. +- The fetch behind a timed-out row keeps running. The next request for that provider joins it instead of starting another. At most eight fetches or scans run at once. +- Finished results are not cached, so the first request after a fetch completes starts a new one. + +The default, `0`, waits for every provider. Values are capped at 86400. A negative value fails with `--request-timeout must be zero or greater.` Upstream defaults to 30 seconds and serves late results from a response cache. This port has no such cache, so the timeout is opt-in. -Pass `--metrics` to enable the Prometheus text endpoint at `/metrics`; it returns `404` when the flag is absent. The endpoint uses the same Host allowlist, Bearer token, snapshot cache, and single-flight collection as the dashboard snapshot. A scrape never waits for provider I/O: it returns the last successful snapshot while an expired value refreshes in the background, or `codexbar_up 0` until the first collection succeeds. +### Prometheus metrics ```powershell $env:CODEXBAR_DASHBOARD_TOKEN = 'replace-with-a-long-random-token' @@ -149,13 +243,29 @@ codexbar serve --metrics --port 8080 curl.exe -H "Authorization: Bearer $env:CODEXBAR_DASHBOARD_TOKEN" http://127.0.0.1:8080/metrics ``` -The metrics contract exports collection health for every enabled, known provider. The only provider label is its bounded canonical CLI slug, for example `codexbar_provider_up{provider="claude"}`; disabled providers are absent, and an ordinary provider fetch failure does not suppress healthy provider series. Quota semantics are currently exported only for Codex through fixed `session`, `weekly`, `monthly`, and `code_review` metric families. Used and remaining values are ratios from `0` to `1`, with no dynamic window label. Available local Codex cost estimates are also exported. +`/metrics` uses the same Host allowlist, Bearer token, snapshot cache and single-flight collection as the dashboard snapshot. A scrape never waits for provider I/O. It returns the last good snapshot while an expired one refreshes in the background, or `codexbar_up 0` until the first collection succeeds. -Codex OAuth reset credits are exposed as `codexbar_reset_credits_available{provider="codex"}` (`0` exhausted, `-1` unavailable or PAT) and, when present, `codexbar_reset_credits_next_expiry_timestamp_seconds{provider="codex"}`. Successful and unavailable reset-credit observations are cached for ten minutes. See [Prometheus examples](prometheus/README.zh-CN.md) for a Chinese Grafana dashboard, scrape settings, and alert rules. +What the metrics export: -Unknown, informational, non-finite, and dynamic additional-limit values are omitted instead of being inferred or replaced with sentinels. Account identity, display labels, source names, free-form provider errors, and version strings are not exposed. Consumers should alert on provider health, snapshot staleness, and quota values together. +- Collection health for every enabled, known provider. The only provider label is its canonical CLI name, for example `codexbar_provider_up{provider="claude"}`. Disabled providers are absent. One provider's fetch failure doesn't hide the other providers' series. +- Quota values, for Codex only, in fixed `session`, `weekly`, `monthly` and `code_review` metric families. Used and remaining values are ratios from `0` to `1`, with no dynamic window label. +- Local Codex cost estimates, when available. +- Codex OAuth reset credits as `codexbar_reset_credits_available{provider="codex"}` (`0` means used up, `-1` means unavailable or a PAT), and `codexbar_reset_credits_next_expiry_timestamp_seconds{provider="codex"}` when present. Reset-credit results, good or unavailable, are cached for ten minutes. -### Config +The metrics leave out unknown, informational, non-finite and dynamic additional-limit values. They never guess a value or use a sentinel. They don't expose account identity, display labels, source names, free-form provider errors or version strings. Alert on provider health, snapshot age and quota values together. + +[Prometheus examples](prometheus/README.zh-CN.md) has a Grafana dashboard, scrape settings and alert rules (in Chinese). + +## Write a dashboard snapshot once + +```powershell +codexbar dashboard --pretty +codexbar dashboard --output C:\temp\codexbar-snapshot.json +``` + +`dashboard` prints one dashboard-v1 snapshot. `--output` writes it atomically to a file whose folder must already exist. `--timeout` defaults to 30 seconds, and 0 turns it off. `--identity` defaults to `redacted`. `full` shows real account emails, so use it only where the output stays private. + +## Manage settings from the CLI ```powershell codexbar config providers @@ -164,20 +274,37 @@ codexbar config enable -p cursor codexbar config disable cursor --json printf '%s' $env:OPENROUTER_API_KEY | codexbar config set-api-key -p openrouter --stdin codexbar config claude-code-credentials status --json +codexbar config preferences export --file C:\temp\codexbar-preferences.json +codexbar config preferences import --file C:\temp\codexbar-preferences.json codexbar config validate ``` -`enable` / `disable` persist settings. `usage -p ` is a one-shot override and does not by itself toggle enabled state the same way. `enable`, `disable` and `set-api-key` take the provider either as a positional name or as `-p/--provider`. +`config` subcommands: + +| Subcommand | What it does | +| --- | --- | +| `validate` | Checks the configuration files. | +| `dump` | Prints the configuration. Secrets are redacted. | +| `providers` | Lists providers and whether each is enabled. | +| `enable`, `disable` | Turns a provider on or off and saves the setting. | +| `set-api-key` | Stores a provider API key. `--stdin` reads it from stdin. `--no-enable` stores it without enabling the provider. | +| `path` | Shows the configuration file paths. | +| `preferences export`, `preferences import` | Exports or imports portable preferences. See [CONFIGURATION.md](./CONFIGURATION.md#portable-preferences). | +| `claude-code-credentials` | `allow`, `deny` or `status`. | + +`enable`, `disable` and `set-api-key` take the provider as a positional name or with `-p`/`--provider`. `usage -p ` is a one-off choice and doesn't change the enabled list. + +`config providers`, `enable`, `disable` and `claude-code-credentials` accept `--format text|json`, `--json` and `--pretty`. Their JSON matches upstream: -`config providers`, `enable`, `disable` and `claude-code-credentials` accept `--format text|json`, `--json` and `--pretty`. Their JSON follows upstream: +- `config providers` prints one `{provider, displayName, enabled, defaultEnabled}` object per provider. Retired providers (`kimik2`, `crossmodel`) are listed only while enabled, like in Settings. They still resolve by name, for example `--provider kimik2`. +- `config enable` and `config disable` print `{provider, displayName, enabled, configPath}`. +- `config claude-code-credentials allow|deny|status` prints `{allowed, configPath}`. -- `config providers`: one `{provider, displayName, enabled, defaultEnabled}` object per provider. The text lines are unchanged. Retired providers (`kimik2`, `crossmodel`) are listed only while enabled, like the Settings UI; they still resolve by name (for example `--provider kimik2`). -- `config enable` / `config disable`: `{provider, displayName, enabled, configPath}`. -- `config claude-code-credentials allow|deny|status`: `{allowed, configPath}`. +`claude-code-credentials` controls whether CodexBar may read and refresh Claude Code's own OAuth credentials (`~/.claude/.credentials.json` or Windows Credential Manager). It is the same choice as the desktop setting, and it is off by default. While it is off, Claude Auto falls back to CLI usage, which has less detail. `allow` and `deny` save the choice. `status` only reports it. -`claude-code-credentials` controls whether CodexBar may read, and refresh, Claude Code's own OAuth credentials (`~/.claude/.credentials.json` or Windows Credential Manager). It is the same choice as the desktop setting and is off by default. While it is off, Claude Auto falls back to reduced-fidelity CLI usage. `allow` and `deny` save the choice, and `status` only reports it. +`config preferences import` changes the saved settings. Restart a running CodexBar afterwards. -#### Separate config file (`CODEXBAR_CONFIG`) +### Separate config file (`CODEXBAR_CONFIG`) ```powershell $env:CODEXBAR_CONFIG = 'D:\isolated\codexbar\settings.json' @@ -185,47 +312,91 @@ codexbar config path codexbar usage --json ``` -`CODEXBAR_CONFIG` names the settings file this CLI process uses instead of `%AppData%\Roaming\CodexBar\settings.json`. Surrounding whitespace is trimmed, an empty value is ignored, a leading `~` becomes your home directory, and a relative path is resolved from the current directory. Upstream keeps provider keys, cookies and token accounts in that one file; this port keeps them in separate stores, so `api_keys.json`, `manual_cookies.json` and `token-accounts.json` are read from and written to the same folder as the chosen file. Logs, caches and other app state stay in their default locations. +`CODEXBAR_CONFIG` names the settings file this CLI process uses instead of `%APPDATA%\CodexBar\settings.json`. + +- Surrounding whitespace is trimmed, and an empty value is ignored. +- A leading `~` becomes your home folder. +- A relative path is resolved from the current folder. + +Upstream keeps provider keys, cookies and token accounts in that one file. This port keeps them in separate stores, so `api_keys.json`, `manual_cookies.json` and `token-accounts.json` are read from and written to the folder of the chosen file. Logs, caches and other app state stay in their default places. + +Only the `codexbar` CLI reads the variable. The desktop app ignores it. With the variable set, the CLI also skips two desktop steps that loading settings normally runs: it doesn't sync the start-at-login registry entry, and it doesn't run the tray-default migration. `config path` lists the resulting paths and says when they come from `CODEXBAR_CONFIG`. + +## Manage token accounts -Only the `codexbar` CLI reads the variable; the desktop app ignores it. A CLI run with the variable set also skips the desktop integrations that loading settings normally performs: it does not sync the start-at-login registry entry or run the tray-default migration. `config path` lists the resulting paths and notes when they come from `CODEXBAR_CONFIG`. +```powershell +codexbar account list claude +codexbar account add zai --label Work --token +codexbar account switch zai Work +codexbar account remove zai Work +``` + +`account switch` and `account remove` take an account label or ID. `--token` takes a token or cookie value, and the value ends up in your shell history. Settings > Providers can add token accounts without that. -### Hooks +## Run hooks ```powershell codexbar hooks list --json +codexbar hooks enable codexbar hooks test usage_updated --provider codex --json codexbar hooks watch --provider codex --json ``` -The opt-in `usage_updated` event is emitted after a successful refresh and -contains the primary and secondary quota usage, window durations, and reset -timestamps when available. Failed or superseded refreshes do not emit it. -The desktop refresh path emits it after publishing a current provider -snapshot; `hooks watch` emits it directly after `provider.fetch_usage` -succeeds, without publishing a snapshot. Repeated events for the same -provider account are limited to one per ten minutes; the private account -discriminator used for that limit is never sent to the hook payload or -environment. +Hook rules live in `hooks.json` next to `settings.json`. `hooks enable` and `hooks disable` flip its master switch. The desktop Settings > Hooks pane edits the same file. See [CONFIGURATION.md](./CONFIGURATION.md#hooks). + +`hooks test` runs the matching rules for a sample event. The events are `quota_low`, `quota_reached`, `quota_reset`, `usage_updated`, `provider_unavailable`, `provider_recovered` and `refresh_failed`. + +`hooks watch` polls providers and fires hooks on real changes. `--interval` defaults to 300 seconds, with a minimum of 60. `--provider` takes one or more CLI names, comma-separated or repeated. Without it, the enabled providers are polled. -### Sessions +`usage_updated` is opt-in. It fires after a successful refresh and carries the primary and secondary quota usage, the window lengths and the reset times when known. Failed or superseded refreshes don't fire it. + +- The desktop app fires it after it publishes a current provider snapshot. +- `hooks watch` fires it right after `provider.fetch_usage` succeeds, without publishing a snapshot. +- Each provider account fires it at most once per ten minutes. The private account key used for that limit is never sent in the hook payload or environment. + +## List sessions ```powershell codexbar sessions codexbar sessions --json --pretty +codexbar sessions --json-v2 --pretty codexbar sessions --focus codexbar sessions --ssh-host user@host ``` -### Cache / cookies +`--json` prints the legacy v1 format, Codex and Claude only. `--json-v2` adds Pi-family sessions. `--ssh-host` reads a configured SSH host, and you can repeat it or pass a comma-separated list. `--brief` prints one line per session. + +## List Codex workspaces + +```powershell +codexbar workspaces +codexbar workspaces --json --pretty --days 7 +``` + +`workspaces` lists local Codex project usage. `--days` defaults to 30. `--force` rebuilds the index even when a cached snapshot exists. + +## Start CodexBar at login + +```powershell +codexbar autostart --status +codexbar autostart --enable +codexbar autostart --disable +``` + +`--enable` and `--disable` write the Windows start-at-login registry entry. + +## Browser cookies + +[COOKIES.md](./COOKIES.md) covers browser cookie import. In the desktop app, pick the browser in Settings > Providers. Paste a cookie by hand when DPAPI import fails or under WSL. -Browser cookie import for the app is documented in [COOKIES.md](./COOKIES.md). Prefer Settings → Providers → browser picker on Windows. Manual cookie paste is supported when DPAPI import fails or under WSL. +## Differences from upstream -## Upstream differences (do not copy blindly) +Don't copy upstream CLI docs without checking: -- No Commander/Swift CLI product name `CodexBarCLI` -- No macOS Keychain cookie cache flags as primary docs -- No Homebrew Linux tarball install story as the default Windows path -- Cards / claude-swap–specific CLI behavior from upstream docs may be absent or different — trust `codexbar --help` on this binary +- There is no Swift CLI product called `CodexBarCLI`. The release ZIP is named `CodexBarCLI-v-windows-x64.zip`, but it holds `codexbar-cli.exe`. +- There are no macOS Keychain cookie cache flags. +- There is no Homebrew or Linux tarball install path for Windows. +- Card and claude-swap behavior from upstream may be missing or different. Trust `codexbar --help` on this binary. ## Related diff --git a/docs/CONFIGURATION.md b/docs/CONFIGURATION.md index 2df051c055..758f4777cf 100644 --- a/docs/CONFIGURATION.md +++ b/docs/CONFIGURATION.md @@ -1,227 +1,492 @@ -# Configuration (Windows) +# Configuration -Windows rewrite of upstream `docs/configuration.md` and `docs/cli-configuration.md`. -Upstream default paths (`~/.config/codexbar/config.json`, `~/.codexbar/config.json`, macOS Keychain layout) are **not** the primary story here. +This page tells you where Win-CodexBar keeps its settings, how to change them from the Settings window or the CLI, and what each Settings pane controls. It covers the Windows port. The upstream paths `~/.config/codexbar/config.json` and `~/.codexbar/config.json` and the macOS Keychain don't apply here. -## Location +## Change a setting -On Windows, config lives under the roaming app data directory: +Use the Settings window for day-to-day changes. Open it from the tray menu with **Settings...**. Changes save at once. -| Store | Typical path | -|-------|----------------| -| Settings | `%AppData%\Roaming\CodexBar\settings.json` | -| Manual cookies | `%AppData%\Roaming\CodexBar\manual_cookies.json` | -| API keys | `%AppData%\Roaming\CodexBar\api_keys.json` | -| Token accounts | `%AppData%\Roaming\CodexBar\token-accounts.json` | - -Resolve at runtime: +Use the CLI for scripts. It reads and writes the same files as the app: ```powershell codexbar config path +codexbar config providers +codexbar config providers --json --pretty +codexbar config enable -p grok +codexbar config disable -p cursor +codexbar config validate +codexbar config dump +``` + +To store an API key, pipe it on stdin: + +```powershell +$env:OPENROUTER_API_KEY | codexbar config set-api-key -p openrouter --stdin ``` -Implementation: `dirs::config_dir()/CodexBar/...` via `Settings::settings_path()` and related helpers in `rust/src/settings/`. Reads/writes go through `secure_file` (can use Windows DPAPI protection for sensitive material). +- `enable` and `disable` are saved. +- `codexbar usage -p ` queries one provider for that run only. It doesn't change what is enabled. +- If every provider is disabled, `usage` has nothing to show. Pass `-p` to query a provider anyway. + +[CLI.md](./CLI.md#manage-settings-from-the-cli) lists every `config` subcommand. + +## Files and where they live -Desktop UI and CLI share these stores. Prefer the Settings window for day-to-day toggles; use `codexbar config` for scripts/CI. +The app keeps its files in `%APPDATA%\CodexBar` (`dirs::config_dir()` plus `CodexBar`). Print the settings path with `codexbar config path`. -A CLI process can use another settings file through `CODEXBAR_CONFIG`; the API key, manual cookie and token account stores then sit beside that file. The desktop app ignores the variable. See [CLI.md](./CLI.md#separate-config-file-codexbar_config). +| File | Folder | DPAPI | Holds | +| --- | --- | --- | --- | +| `settings.json` | `%APPDATA%\CodexBar` | Yes | Everything in the Settings window | +| `api_keys.json` | `%APPDATA%\CodexBar` | Yes | API keys entered in the app | +| `manual_cookies.json` | `%APPDATA%\CodexBar` | Yes | Cookie headers pasted by hand | +| `token-accounts.json` | `%APPDATA%\CodexBar` | Yes | Token accounts | +| `claude-accounts\accounts.json` | `%APPDATA%\CodexBar` | Yes | Saved Claude Code logins | +| `grok-accounts\accounts.json` | `%APPDATA%\CodexBar` | Yes | Saved Grok logins | +| `codex-accounts\accounts.json` | `%APPDATA%\CodexBar` | Yes | Codex accounts | +| `codex-accounts\snapshots.json` | `%APPDATA%\CodexBar` | No | Codex account usage snapshots | +| `history\quota-burndown-v1.json` | `%APPDATA%\CodexBar` | Yes | Quota history | +| `claude\quota-reset-observations-v1.json`, `codex\quota-reset-observations-v1.json` | `%APPDATA%\CodexBar` | Yes | Observed reset times | +| `hooks.json` | `%APPDATA%\CodexBar` | No | Hook rules | +| `-cookie.json` | `%LOCALAPPDATA%\CodexBar` | Yes | Cached browser cookies | +| `codex-weekly-reset-v1.json` | `%LOCALAPPDATA%\CodexBar` | Yes | Codex weekly reset cache | -## What lives where (conceptual) +- Files marked DPAPI are written through `secure_file` in `rust/src/settings/`. On Windows it wraps them with DPAPI for the current user every time it saves. +- `settings.json` is always wrapped. The app still reads a plain JSON file you edited by hand, but the next save wraps it again. If the file can't be parsed, the app loads the defaults. +- Browser cookies are read from the browser profiles when a refresh needs them. See [COOKIES.md](./COOKIES.md). +- `token-accounts.json` falls back to `~/.codexbar/` only when Windows reports no config folder. +- Don't commit a real `settings.json` or key file to the repo. -Aligned with upstream *ideas*, mapped to this port: +A CLI process can use another settings file through `CODEXBAR_CONFIG`. The API key, manual cookie and token account files then sit beside that file. The desktop app ignores the variable. See [CLI.md](./CLI.md#separate-config-file-codexbar_config). -- **Enabled providers, theme, refresh, float bar, UI language, metrics, …** → `settings.json` -- **Manual cookie headers** → `manual_cookies.json` (and/or settings fields depending on provider path) -- **API keys** → `api_keys.json` / keyring helpers where used -- **Token accounts** → `token-accounts.json` -- **Browser auto cookies** → extracted at runtime from Chrome (any channel)/Chromium/Edge/Brave/Firefox profiles (see [COOKIES.md](./COOKIES.md)); not a substitute for committing secrets into git +## Settings window -Do not commit real `settings.json` / key files into the repo. +The Settings window has a sidebar. The top part lists the panes in this order: General, Usage & Spend, Notifications, Menu Bar, Menu, Advanced, Hooks and About. Below them is a **Providers** section with one row per provider and a count of how many are on. + +### Settings tab ids + +Code, links and proof mode open a pane by its id. The ids are case-sensitive: + +`general`, `providers`, `notifications`, `menuBar`, `menu`, `usageSpend`, `advanced`, `hooks`, `about` -## CLI configuration commands +- `surface_target.rs` in the shell and `TAB_META` in `apps/desktop-tauri/src/surfaces/settings/settingsTabs.ts` must list the same ids. +- An unknown id opens General. +- The old ids `display`, `apiKeys` and `cookies` aren't tabs any more. Their controls moved to other panes and to the provider pages. + +To open a pane for automation, set proof mode before you launch the app: ```powershell -codexbar config providers # list enablement -codexbar config providers --json --pretty -codexbar config enable -p grok -codexbar config disable -p cursor -codexbar config validate -codexbar config dump -codexbar config path +$env:CODEXBAR_PROOF_MODE = 'settings:menu' +``` -# API key via stdin (example) -printf '%s' $env:OPENROUTER_API_KEY | codexbar config set-api-key -p openrouter --stdin +See [WINDOWS_PROOF.md](./WINDOWS_PROOF.md#proof-mode). -# Portable preferences (also under Settings > Advanced) -codexbar config preferences export --file prefs.json # omit --file to print to stdout +### Provider list in the sidebar + +- **Search providers** filters by name or id. Esc clears it. +- The sort button turns on alphabetical order (`providers_sorted_alphabetically`). Enabled providers stay first. It doesn't change your saved order. +- Reorder providers by dragging, with Alt+Up and Alt+Down, or from the right-click menu (**Move Up**, **Move Down**). Reordering only works while the list isn't sorted and the search box is empty. The order is saved in `provider_order`. +- The right-click menu can also enable or disable a provider. +- Each enabled provider has a status dot: + +| Dot | Meaning | +| --- | --- | +| Green | Up to date | +| Amber | Stale: the last update is more than 10 minutes old | +| Red | The last refresh failed | +| Blue, pulsing | Loading: no data yet | + +Disabled providers are dimmed and have no dot. + +While **Check provider status** (General) is on, a provider that has a status page shows a service dot instead: green, yellow, orange or red by incident level, gray for maintenance or unknown. See [PROVIDERS.md](./PROVIDERS.md#status-pages). + +## What each pane controls + +Each row gives the label, the `settings.json` key and the default for a new install. + +### General + +| Control | Key | Default | +| --- | --- | --- | +| Language | `ui_language`, `ui_language_follows_system` | System | +| Preferred currency | `preferred_currency_code` | `AUTO` | +| Appearance | `theme` (`auto`, `light`, `dark`) | `auto` | +| Start at login | `start_at_login` | off | +| Start minimized | `start_minimized` | off | +| Refresh interval | `refresh_interval_secs`, `adaptive_refresh` | 300 seconds, adaptive on | +| Refresh when the menu opens | `refresh_all_providers_on_menu_open` | off | +| Low power mode | `low_power_mode_preference` (`off`, `on`, `automatic`) | `off` | +| Check provider status | `status_checks_enabled` | on | +| Open menu (global shortcut) | `global_shortcut` | `Ctrl+Shift+U` | + +General also has the **Portable preferences** group with **Export Preferences...**, **Import Preferences...** and **Provider Switcher Shortcuts...**. See [Portable preferences](#portable-preferences) and [Provider switcher keys](#provider-switcher-keys). + +**Language.** The first option, **System**, follows the Windows display language. New installs use it. A settings file saved before this option existed keeps the language it had. At load, the app reads these HKCU values in order and uses the first language it supports: + +1. `Control Panel\Desktop` `PreferredUILanguages` +2. `Control Panel\International\User Profile` `Languages` +3. `Control Panel\Desktop\MuiCached` `MachinePreferredUILanguages` + +If none match, it uses English. The app ships 10 languages: English, Simplified Chinese, Traditional Chinese, Japanese, Korean, Spanish, Brazilian Portuguese, Russian, Turkish and Ukrainian. + +**Refresh interval.** The options are Manual (0), 1, 2, 5, 15, 30 and 60 minutes, and Adaptive. Adaptive sets `adaptive_refresh` to true. Any other choice sets it to false. A settings file without `adaptive_refresh` gets false. + +**Start at login** writes the value `CodexBar` under `HKCU\Software\Microsoft\Windows\CurrentVersion\Run`, pointing at the desktop app. The CLI has `codexbar autostart` for the same job. + +### Usage & Spend + +| Control | Key | Default | +| --- | --- | --- | +| Track costs | `cost_usage_enabled` | on | +| Period | `spend_dashboard_period` (`rolling:7`, `rolling:30`, `rolling:90`, `month-to-date`, `all`) | copies `cost_reporting_period` | +| Hidden sources | `spend_dashboard_hidden_source_ids` | none | +| OpenCodex usage logs | `open_codex_usage_logs_enabled` | off | +| Hide native Codex cost when OpenCodex is present | `hide_native_codex_cost_when_open_codex_present` | off | + +When **Track costs** is off, the pane hides the period picker, the dashboard, OpenCodex and the source list, and shows "Cost tracking is off". **Refresh**, **Copy JSON**, **Save JSON** and **Share PNG** stay visible but are disabled. + +Costs are list-price estimates, not billing receipts. + +### Notifications + +| Control | Key | Default | +| --- | --- | --- | +| Show notifications | `show_notifications` | on | +| Session quota | `session_quota_notifications_enabled` | on | +| Credential expiry | `credential_expiry_notifications_enabled` | off | +| Predictive pace | `predictive_pace_warning_enabled` | off | +| Warning at % used | `high_usage_threshold` | 70 | +| Critical at % used | `critical_usage_threshold` | 90 | +| Sound | `sound_enabled` | on | +| Sound theme | `notification_sound_theme` (`windows`, `codexBar`) | `windows` | +| Sound files per event | `notification_sound_paths` | none | + +- Credential expiry and Predictive pace are disabled while **Show notifications** is off. +- The two thresholds stay editable when notifications are off, because they also color the bars and the float bar. They range from 0 to 100 in steps of 5. +- The sound controls show only while sound is on. Each of these events can have its own WAV file: `predictiveWarning`, `highUsage`, `criticalUsage`, `exhausted`, `statusIssue`, `sessionDepleted` and `sessionRestored`. +- A provider can override the thresholds. See [Quota warnings per provider](#quota-warnings-per-provider). + +### Menu Bar + +These controls set the tray icon. + +| Control | Key | Default | +| --- | --- | --- | +| Icon style | `menu_bar_icon_style` (`bars`, `percent`) | `bars` | +| Color Pace Indicator | `menu_bar_color_pace` | off | +| Pin to Taskbar (Windows 11) | `promote_tray_icon` | on | +| Combined icon style | `tray_icon_mode` (`single`, `stacked`) | `single` | +| Stacked top and bottom provider | `stacked_tray_top_provider`, `stacked_tray_bottom_provider` | Automatic | +| Show every provider in the tray menu | `tray_icon_mode` = `perprovider` | off | +| Switcher rows | `switcher_shows_icons` | icons | +| Show most-used provider | `menu_bar_shows_highest_usage` | off | +| Overview providers | `merged_overview_selected_providers` | first 6 enabled | + +- **Color Pace Indicator** tints a single-provider icon green when usage is behind pace and red when it is ahead. Stacked icons keep the usage color. [ARCHITECTURE.md](./ARCHITECTURE.md#tray-icon) explains how the window is picked. +- Icon style, **Show every provider** and **Show most-used provider** are disabled while the icon is stacked. +- The Overview can hold up to 6 providers. +- In `settings.json`, `tray_icon_mode` is lowercase: `single`, `perprovider` or `stacked`. + +### Menu + +These controls set the tray panel and the float bar. + +| Control | Key | Default | +| --- | --- | --- | +| Bars show used instead of remaining | `show_as_used` | off | +| Show quota warning markers | `quota_warning_markers_visible` | on | +| Show pace | `show_pace` | on | +| Work days | `weekly_progress_work_days` (Automatic, 4, 5 or 7) | Automatic | +| Workday ticks | `workday_tick_style` (`hidden`, `subtle`, `high_contrast`) | `subtle` | +| Reset times as countdown | `reset_time_relative` | on | +| Keep the tray panel on top | `tray_panel_always_on_top` | off | +| Show reset time when exhausted | `show_reset_when_exhausted` | off | +| Panel scale | `tray_scale_percent` (100 to 200, step 5) | 100 | +| Overview layout | `overview_layout` (`detailed`, `compact`) | `detailed` | +| Show all token accounts | `show_all_token_accounts_in_menu` | off | +| Stay awake | `stay_awake_enabled` | off | +| Agent sessions | `agent_sessions_enabled` | off | +| SSH hosts for agent sessions | `agent_session_ssh_hosts` (comma-separated) | none | +| Cost summary | `cost_summary_display_style` (`compact`, `detailed`, `hidden`) | `compact` | +| Cost reporting period | `cost_reporting_period` | `rolling:30` | + +Workday ticks are disabled while Work days is Automatic. SSH hosts are disabled while agent sessions are off. + +The float bar section is on this pane too. Every control except the first is disabled while the float bar is off. + +| Key | Values | Default | +| --- | --- | --- | +| `float_bar_enabled` | on, off | off | +| `float_bar_orientation` | `horizontal`, `vertical` | `horizontal` | +| `float_bar_style` | `floating`, `taskbar` | `floating` | +| `float_bar_opacity` | 30 to 100 | 80 | +| `float_bar_scale` | 75 to 200 | 100 | +| `float_bar_show_cost` | on, off | off | +| `float_bar_show_reset_inline` | on, off | off | +| `float_bar_dark_text` (Invert Colors) | on, off | off | +| `float_bar_click_through` | on, off | off | + +### Advanced + +| Control | Key | Default | +| --- | --- | --- | +| Hide personal information | `hide_personal_info` | off | +| Disable Credential Manager access | `disable_keychain_access` | off | +| Codex local log folders | `codex_custom_sessions_dirs` (`;`-separated) | none | +| PowerToys status pipe | `powertoys_status_pipe_enabled` | off | +| Network proxy | `http_proxy_enabled`, `http_proxy_url`, `http_proxy_username`, `http_proxy_password` | off | + +The proxy password is stored in `settings.json`, which is DPAPI-wrapped. Advanced also has a button that copies diagnostics. + +**Hide personal information** is in the Privacy group. When it is on: + +- Email addresses are removed from the tray panel, the tray tooltip and provider error text. No placeholder is shown. +- The account line on each tray panel card is hidden. +- Account lists in the tray menu and in Settings show "Account 1", "Account 2" and so on. For Codex this covers every account, ambient and managed. The number comes from the sorted account id, not the list position. +- Settings > Providers hides the email and organization, and shows an OpenAI API project id as "Project: ••••". +- Usage & Spend and cost history show Codex project names as "Workspace", drop session titles and clear paths. +- Hook payloads never get the account identity. + +### Hooks + +The Hooks pane edits `hooks.json`. See [Hooks](#hooks). + +### About + +| Control | Key | Default | +| --- | --- | --- | +| Download updates automatically | `auto_download_updates` | off | +| Update channel | `update_channel` (`stable`, `beta`) | `stable` | + +About also has **Check for Updates** and links. + +### Providers + +Each provider page has, from top to bottom: + +1. Identity, with a notice when the last fetch failed. +2. Usage: usage, cost, details, charts and quick actions. +3. Which usage rows to show. +4. The metric the tray icon uses (`provider_metrics`). +5. Connection: usage source, cookie source, region, API key, cookie and token accounts, as the provider supports them. +6. Accounts, for Codex, Claude and Grok only. +7. Accent color. +8. Quota warnings. +9. Options. For Claude this includes **Allow reading Claude Code's credentials** (`claude_allow_reading_claude_code_credentials`, off). + +## Quota warnings per provider + +Each provider page has a **Quota warnings** group with a **Session** row and a **Weekly** row. Each row has three modes: + +| Mode | Effect | Stored as | +| --- | --- | --- | +| Global | Uses the Notifications thresholds | nothing | +| Custom | Uses its own **Warning** and **Critical** values. A blank value uses the global one. | `high`, `critical` | +| Off | No threshold notifications and no bar markers for that window | `enabled: false` | + +- The values live under `provider_configs..quota_warnings` as `{session, weekly}`. Each is `{enabled, high, critical}` in percent used, from 0 to 100. +- Off keeps the saved values, so switching back to Custom restores them. +- Threshold hooks still fire when a window is Off. +- The group is locked when both **Show notifications** and **Show quota warning markers** are off. +- The older `provider_usage_thresholds` key is folded into `quota_warnings` once at startup. A window that already has a non-global setting is left alone. If that save fails, the app retries on the next launch. + +## Portable preferences + +Portable preferences copy your display and behavior choices to another machine. They never include secrets or machine-specific paths. + +From the app, use **Settings > General > Portable preferences**. **Import Preferences...** applies the file at once. From the CLI: + +```powershell +codexbar config preferences export --file prefs.json codexbar config preferences import --file prefs.json ``` -Portable preferences are a versioned JSON document (`{"version": 1, "preferences": {...}}`) -limited to display, refresh, notification, provider-order, float-bar, cost reporting period, -preferred currency and switcher-shortcut choices. API keys, cookies, token accounts, folders, SSH -hosts, proxy settings, update settings, Stay Awake and the pinned cost time zone are never -exported, and an import that contains any other key, or any invalid value, is rejected without -changing settings. -The CLI import writes `settings.json` only; restart a running CodexBar to pick it up. The desktop -Import button applies the file live. +Leave out `--file` on export to print to stdout. The CLI import only writes `settings.json`, so restart a running CodexBar afterwards. -Notes: +The file is plain JSON, at most 256 KiB, in this shape: -- `enable` / `disable` are **persistent** (same idea as upstream). -- `codexbar usage -p ` is a **one-shot** query override; it is not a full substitute for enable/disable. -- If every provider is disabled, bare `usage` may print nothing useful; pass `-p` explicitly to force a provider for that run. +```json +{ "version": 1, "preferences": { "theme": "dark", "show_pace": true } } +``` -## Settings UI tabs (desktop) +These keys are exported (`ALLOWED_PREFERENCES` in `rust/src/settings/preferences_document.rs`): -Canonical tab ids (frontend + proof harness whitelist must match): +| Group | Keys | +| --- | --- | +| Refresh | `refresh_interval_secs`, `adaptive_refresh`, `refresh_all_providers_on_menu_open`, `status_checks_enabled`, `low_power_mode_preference` | +| Notifications | `show_notifications`, `session_quota_notifications_enabled`, `sound_enabled`, `high_usage_threshold`, `critical_usage_threshold`, `predictive_pace_warning_enabled` | +| Display | `show_pace`, `show_as_used`, `reset_time_relative`, `show_reset_when_exhausted`, `hide_personal_info`, `menu_bar_shows_highest_usage`, `menu_bar_shows_percent`, `menu_bar_icon_style`, `merged_overview_selected_providers`, `quota_warning_markers_visible`, `workday_tick_style`, `tray_icon_mode`, `switcher_shows_icons`, `providers_sorted_alphabetically`, `overview_layout`, `menu_bar_display_mode` | +| Providers | `provider_order`, `enabled_providers`, `provider_metrics`, `provider_quota_warnings` | +| Look | `theme`, `ui_language`, `ui_language_follows_system`, `window_scale_percent`, `tray_scale_percent` | +| Float bar | `float_bar_opacity`, `float_bar_scale`, `float_bar_orientation`, `float_bar_style`, `float_bar_dark_text`, `float_bar_show_reset_inline`, `float_bar_show_cost` | +| Cost | `cost_reporting_period`, `cost_usage_enabled`, `spend_dashboard_period`, `spend_dashboard_hidden_source_ids`, `preferred_currency_code` | +| Shortcuts | `switcher_shortcuts` | -`general`, `providers`, `notifications`, `menuBar`, `menu`, `usageSpend`, `advanced`, `hooks`, `about` +`provider_quota_warnings` holds the per-provider quota warnings, for providers that don't use Global only. It exists only in the export file; in `settings.json` the values live under `provider_configs`. -Unknown ids fall back to General. Legacy ids `display` / `apiKeys` / `cookies` are **not** settings tabs (content lives under other tabs / provider detail). +These are never exported: -Proof / automation example: +- Secrets: `provider_configs` (API keys, cookies, token accounts) and every `http_proxy_*` key. +- Machine paths and hosts: sound file paths, Codex log folders, SSH hosts and the pinned cost time zone. +- Choices that need consent or change the system: start at login, start minimized, hooks, the global shortcut, agent sessions, Credential Manager access, reading Claude Code's credentials, Codex external OAuth sources, OpenCodex logs, the PowerToys pipe, float bar on and click-through, Pin to Taskbar, always on top and Stay Awake. +- Update settings. +- Keys outside the version 1 list, such as `menu_bar_color_pace`, `weekly_progress_work_days` and `cost_summary_display_style`. -```powershell -$env:CODEXBAR_PROOF_MODE = "settings:menu" -# then launch the desktop binary -``` +Import rules: + +- An unknown key, a wrong type, a value out of range or another version rejects the whole file. Nothing changes. +- A missing key leaves that setting as it is. `null` restores the default. +- `provider_quota_warnings` replaces the warnings of every provider. Providers not in the file go back to Global. +- An older file with `ui_language` but no `ui_language_follows_system` turns System off, so it keeps the chosen language. +- The older `provider_usage_thresholds` key is accepted on import. ## Provider switcher keys -The tray flyout switches providers from the keyboard (ported from upstream 0.67.0; the shortcuts are menu-local, not global hotkeys): +The tray panel switches providers from the keyboard. The keys work only inside the panel; they aren't global hotkeys. | Action | Default key | -|--------|-------------| -| Previous / next | `Left` / `Right` (wraps through Overview) | -| Overview, then providers in display order | `Ctrl+1` ... `Ctrl+9` | +| --- | --- | +| Previous, next | `Left`, `Right` (wraps around through Overview) | +| Overview, then providers in display order | `Ctrl+1` to `Ctrl+9` | -Keys are ignored while a text field, select or slider (the zoom slider) has focus, or while a grid drag is active. +The keys are ignored while focus is in a text field, a select, an editable area or a slider, and while a grid drag is in progress. ### Customizing the keys -**Settings → Menu → Provider switcher shortcuts** lists all 11 actions. Choose **Record**, then press the key or combination; **Backspace** while recording (or **Clear**) disables the action, **Reset to defaults** restores every default. The editor rejects duplicates and reserved keys with an inline message and saves nothing in that case. +1. Open **Settings > General**. +2. Under **Portable preferences**, choose **Provider Switcher Shortcuts...**. A sheet lists all 11 actions. +3. Choose **Record** for an action, then press the key or combination. Esc cancels. +4. Press Backspace while recording, or choose **Clear**, to disable the action. It then shows "Disabled". +5. **Restore Defaults** resets every action. -Only the actions you changed are stored, in `settings.json` under `switcher_shortcuts` (the key is omitted when everything is default): +The sheet rejects a duplicate or reserved key with a message and saves nothing. + +Only the actions you changed are stored, under `switcher_shortcuts` in `settings.json`. The key is left out when everything is default: ```json { "switcher_shortcuts": { "previous": "shift+left", "select2": "ctrl+alt+2", "next": "none" } } ``` -Rules (upstream grammar, validated by `rust/src/switcher_shortcuts.rs` and mirrored in `apps/desktop-tauri/src/lib/switcherShortcuts.ts`): +`rust/src/switcher_shortcuts.rs` checks the grammar and `apps/desktop-tauri/src/lib/switcherShortcuts.ts` mirrors it: + +- The actions are `previous`, `next` and `select1` to `select9`. Unknown actions are rejected. +- A shortcut is any of `ctrl`, `alt` and `shift` plus one key: `left`, `right`, `,`, a letter or a digit. `cmd` means `ctrl`, so shortcuts copied from macOS still work. +- Letters, digits and `,` need `ctrl` or `alt`. `ctrl+r`, `ctrl+q`, `ctrl+,` and `ctrl+w` are reserved for Refresh, Quit, Settings and closing the window. +- Two actions can't share a shortcut. `none` disables an action and frees its key. +- If the stored map is invalid, the app logs a warning and uses the defaults. The rest of the settings still load. -- Actions: `previous`, `next`, `select1` ... `select9`. Unknown actions are rejected. -- A shortcut is optional `ctrl`, `alt`, `shift` modifiers plus one key: `left`, `right`, `,`, a letter or a digit. `cmd` is accepted as an alias for `ctrl`, so shortcuts copied from macOS stay valid. -- Letters, digits and `,` need `ctrl` or `alt`. `ctrl+r`, `ctrl+q`, `ctrl+,` and `ctrl+w` are reserved (Refresh, Quit, Settings and window close). -- Two actions cannot share a shortcut. `none` disables an action and frees its key. -- An invalid stored map is ignored on load (defaults apply) and logged as a warning; it never blocks the rest of the settings. +The global shortcut that opens the tray panel is separate. It is `global_shortcut`, set under **Settings > General > Keyboard shortcut**. ## Claude Code accounts -In **Settings → Providers → Claude → Claude Code accounts**, use **Save current -account** to retain an existing CLI login, or **Add account** to sign in to another -Claude subscription in your browser. Adding an account leaves the current CLI -login active. The native Claude Code executable must be installed. - -Close running Claude Code CLI sessions, select **Switch**, then reopen the CLI. -The tray's **Claude Code accounts** submenu provides the same actions. Win-CodexBar -saves the outgoing login before switching, including its latest refresh token. -**Remove** forgets the saved copy; it does not log out an active CLI session. - -Saved logins are protected with the existing Windows DPAPI storage helper under -`%APPDATA%\CodexBar\claude-accounts\accounts.json`. Sign-in uses a temporary -`CLAUDE_CONFIG_DIR`; successful, failed, cancelled, and timed-out attempts clean up -that directory. Switching updates `claudeAiOauth` in the CLI credentials file and -`oauthAccount` in the CLI configuration, preserving other settings and MCP secrets. -An absolute `CLAUDE_CONFIG_DIR` inherited by Win-CodexBar selects a custom CLI home. -This account feature follows the [documented Windows Claude Code credential -file](https://code.claude.com/docs/en/authentication), `.claude\.credentials.json`. -It does not manage macOS Keychain logins or custom keyring integrations. -On Windows, the isolated sign-in process belongs to a job that terminates it if -Win-CodexBar exits. Startup also removes abandoned UUID sign-in directories; -cleanup skips links and reparse points. - -These controls switch **Claude Code CLI**, not Claude Desktop or browser sessions. -Usage monitoring still follows the provider's source settings and the -**Allow reading Claude Code's credentials** toggle. API-key or OAuth-token -environment overrides must be removed before using saved subscription logins. -When credential reading is disabled, active-account status is unknown and every -saved account remains switchable. The list does not open ambient credential or -identity files. Explicitly selecting the already-current account is a no-op. +Open **Settings > Providers > Claude > Claude Code accounts**. + +- **Save current account** keeps the login the CLI uses now. +- **Add account** signs in to another Claude subscription in your browser. The current CLI login stays active. The native Claude Code executable must be installed. +- **Switch** makes a saved account the CLI login. Close running Claude Code CLI sessions first and reopen them after. The app saves the outgoing login, with its latest refresh token, before it switches. +- **Remove** forgets the saved copy. It doesn't log out an active CLI session. +- **Refresh login** and **Cancel sign-in** are also on this page. + +The tray menu's **Claude Code accounts** submenu can switch accounts, add an account, save the current one and cancel a sign-in. Remove and Refresh login are only in Settings. + +Saved logins are in `%APPDATA%\CodexBar\claude-accounts\accounts.json`, protected with DPAPI. + +- Sign-in runs with a temporary `CLAUDE_CONFIG_DIR`. The app removes that folder whether the sign-in succeeds, fails, is cancelled or times out. +- On Windows the sign-in process belongs to a job object, so it ends if Win-CodexBar exits. At startup the app also removes abandoned sign-in folders. It skips links and reparse points. +- Switching updates `claudeAiOauth` in the CLI credentials file and `oauthAccount` in the CLI configuration. Other settings and MCP secrets stay as they are. +- An absolute `CLAUDE_CONFIG_DIR` that Win-CodexBar inherits selects a custom CLI home. +- The feature uses the [Windows Claude Code credentials file](https://code.claude.com/docs/en/authentication), `.claude\.credentials.json`. It doesn't manage macOS Keychain logins or custom keyring setups. + +These controls switch the **Claude Code CLI** only, not Claude Desktop or a browser session. + +- Usage monitoring still follows the provider's source settings and **Allow reading Claude Code's credentials**. +- Remove API-key or OAuth-token environment overrides before you use saved subscription logins. +- With credential reading off, the active account is unknown and every saved account can be switched to. +- The list doesn't open ambient credential or identity files. +- Choosing the account that is already active does nothing. ## Grok accounts -In **Settings → Providers → Grok → Grok accounts**, use **Save current -account** to retain the existing `~/.grok/auth.json` login, or **Add account** -to run `grok login --oauth` in an isolated `GROK_HOME`. Finish that sign-in in -the Firefox profile (or browser) for the second SuperGrok account. Adding an -account leaves the current CLI login active until you **Switch**. - -The tray **Grok accounts** submenu provides the same actions. Win-CodexBar -saves the outgoing login before switching. **Remove** forgets the saved copy; -it does not log out the active CLI session. Restart running Grok CLI sessions -after a switch. - -Saved logins are protected with the existing Windows DPAPI storage helper under -`%APPDATA%\CodexBar\grok-accounts\accounts.json`. Sign-in uses a temporary -`GROK_HOME`; successful, failed, cancelled, and timed-out attempts clean up -that directory. Switching replaces only `~/.grok/auth.json` (or -`$GROK_HOME/auth.json` when that environment variable is set). Sessions, -skills, and other Grok home files stay in place. `XAI_API_KEY` and -`GROK_OAUTH_TOKEN` are unset for the isolated sign-in so the browser OAuth -flow is used. +Open **Settings > Providers > Grok > Grok accounts**. + +- **Save current account** keeps the login in `~/.grok/auth.json`. +- **Add account** runs `grok login --oauth` in a separate `GROK_HOME`. Finish the sign-in in the browser profile of the second SuperGrok account. The current CLI login stays active until you choose **Switch**. +- **Switch** saves the outgoing login, then replaces it. Restart running Grok CLI sessions afterwards. +- **Remove** forgets the saved copy. It doesn't log out the active CLI session. +- **Sign in again**, **Refresh usage** and **Cancel sign-in** are also on this page. + +The tray menu's **Grok accounts** submenu can switch accounts, add an account, save the current one and cancel a sign-in. Remove is only in Settings. + +Saved logins are in `%APPDATA%\CodexBar\grok-accounts\accounts.json`, protected with DPAPI. + +- Sign-in uses a temporary `GROK_HOME`, which the app removes however the sign-in ends. +- Switching replaces only `~/.grok/auth.json`, or `$GROK_HOME/auth.json` when that variable is set. Sessions, skills and other Grok files stay in place. +- The app unsets `XAI_API_KEY` and `GROK_OAUTH_TOKEN` for the sign-in, so the browser OAuth flow runs. + +## Codex accounts + +Open **Settings > Providers > Codex > Codex Accounts**. It has **Add account**, **Refresh login**, **Refresh usage**, **Switch**, **Remove** and **Restart Codex Desktop**. Each account carries an **Ambient** or **Managed** badge. The accounts are in `%APPDATA%\CodexBar\codex-accounts\accounts.json`, protected with DPAPI. The tray menu's **Codex Accounts** submenu can switch accounts and add one. ## Antigravity CLI path -When neither the Antigravity desktop app nor a signed-in `agy` session is -running, the Antigravity provider can use the `agy` CLI. Win-CodexBar looks -for it on `PATH`, then at `%LOCALAPPDATA%\agy\bin\agy.exe`, then at -`%USERPROFILE%\.local\bin\agy.exe`. Set `ANTIGRAVITY_CLI_PATH` to the full -path of `agy.exe` when it is installed somewhere else. - -A set `ANTIGRAVITY_CLI_PATH` is authoritative. If it is empty or does not -point to a file, Win-CodexBar skips the CLI source and names the variable in -the provider error instead of discovering another `agy` through `PATH` or the -install directories, so a background refresh never starts a different CLI -that could ask for an interactive sign-in. A running desktop app still -answers first, and offline conversation history is still shown when it -exists. Unset the variable to restore automatic discovery. Other providers -keep their own override behavior. +When neither the Antigravity desktop app nor a signed-in `agy` session is running, the Antigravity provider can use the `agy` CLI. Win-CodexBar looks for it in this order: -## Source mode +1. on `PATH` +2. `%LOCALAPPDATA%\agy\bin\agy.exe` +3. `%USERPROFILE%\.local\bin\agy.exe` + +If `agy.exe` is somewhere else, set `ANTIGRAVITY_CLI_PATH` to its full path. + +When `ANTIGRAVITY_CLI_PATH` is set, the app uses only that path. -CLI `--source` values on this port (see `codexbar usage --help`): `auto`, `web`, `cli`, `oauth`. +- If the variable is empty or doesn't point to a file, the app skips the CLI source and names the variable in the provider error. It doesn't search `PATH` or the install folders. This keeps a background refresh from starting another `agy` that might ask you to sign in. +- A running desktop app still answers first, and offline conversation history still shows when it exists. +- Unset the variable to go back to automatic discovery. -Upstream also documents `api` extensively; treat per-provider support as defined by **this** codebase’s provider modules and help text, not by copying upstream tables blindly. +Other providers keep their own override rules. + +## Source mode + +The CLI `--source` values are `auto`, `web`, `cli` and `oauth` (see `codexbar usage --help`). Which ones a provider supports is defined by its module in `rust/src/providers/`. Upstream also documents `api`; don't copy upstream tables. ## Hooks -The shared `hooks.json` rules can match `usage_updated` in addition to the -quota and provider-status events. The desktop refresh path emits it after a -successful, current provider publication; `codexbar hooks watch` emits it after -`provider.fetch_usage` succeeds without publishing a provider snapshot. Its -payload can include primary and secondary usage fractions, window durations, -and reset timestamps. A failed or superseded refresh does not produce a -successful-update event. +Hooks run a local program when something happens to a provider's usage. Edit them in **Settings > Hooks** or with `codexbar hooks` (see [CLI.md](./CLI.md#run-hooks)). + +- **Enable hooks** is on only when both `hooks_enabled` in `settings.json` and `enabled` in `hooks.json` are true. The pane shows a note when only one is on. The switch writes both. +- Each rule has an on switch, an event, a provider (or **Any provider**), an executable, arguments and a timeout. `quota_low` rules also have a threshold in percent. +- **+ Add rule** adds an enabled `quota_reached` rule with a 10-second timeout. Edits save at once. Text fields save when you leave them. +- If `hooks.json` can't be read, the editor locks and the app never overwrites the file. + +`hooks.json` is plain JSON in `%APPDATA%\CodexBar`, written atomically: + +| Field | Meaning | +| --- | --- | +| `enabled` | Master switch, default false | +| `events` | The list of rules | +| rule `enabled`, `event` or `events`, `provider`, `threshold` | When the rule fires. `threshold` is a fraction from 0 to 1. | +| rule `executable` | Absolute path to the program | +| rule `arguments` | Argument list | +| rule `timeoutSecs` | 1 to 300, default 10 | + +Limits: 32 rules, 32 arguments per rule and a 4096-byte payload. -Repeated `usage_updated` events are limited to one per provider account per ten -minutes in memory. The account discriminator used for that private limit is -never serialized or passed to the hook process. Configure trusted local -executables only; never point hooks at untrusted paths. +The events are `quota_low`, `quota_reached`, `quota_reset`, `usage_updated`, `provider_unavailable`, `provider_recovered` and `refresh_failed`. -## Start at login (Windows) +- `usage_updated`, `provider_unavailable` and `refresh_failed` fire at most once per provider account every 600 seconds. The account key used for that limit is kept in memory and never passed to the hook. +- The desktop app sends `usage_updated` after a successful, current refresh. `codexbar hooks watch` sends it after `fetch_usage` succeeds. A failed or replaced refresh sends nothing. +- The `usage_updated` payload can include the primary and secondary usage fractions, the window lengths and the reset times. -Desktop start-at-login uses `HKCU\Software\Microsoft\Windows\CurrentVersion\Run` value `CodexBar` pointing at the desktop executable (managed via settings). CLI also has `codexbar autostart` for boot integration helpers. +Point hooks only at programs you trust. ## Security -- Do not log cookies, tokens, or API keys (`tracing` only, redacted helpers). -- Manual cookie paste and API keys are secrets — handle like passwords. -- `codexbar serve` on non-loopback without TLS sends bearer tokens in cleartext; require intentional flags/env (see [CLI.md](./CLI.md)). +- Don't log cookies, tokens or API keys. Use `tracing` and the redaction helpers. +- Treat pasted cookies and API keys like passwords. +- `codexbar serve` on a non-loopback address without TLS sends bearer tokens in clear text. It needs a dashboard token and `--allow-plain-http`. See [CLI.md](./CLI.md#serve-the-local-dashboard). -## Related +## Related docs - [CLI.md](./CLI.md) - [COOKIES.md](./COOKIES.md) - [PROVIDERS.md](./PROVIDERS.md) -- Root [AGENTS.md](../AGENTS.md) +- [AGENTS.md](../AGENTS.md) diff --git a/docs/COOKIES.md b/docs/COOKIES.md index 1a33031228..3d2048ffc0 100644 --- a/docs/COOKIES.md +++ b/docs/COOKIES.md @@ -1,56 +1,79 @@ -# Browser Cookie Extraction (Windows) +# Browser cookies on Windows -Win-CodexBar can extract browser cookies for providers that use web authentication (Claude, Cursor, Kimi, and others). This is the Windows rewrite of upstream cookie/Keychain concepts: **DPAPI + browser profiles**, not macOS Keychain prompts. +Some providers sign in through their website, for example Claude, Cursor and Kimi. For these providers CodexBar can read the session cookie from a browser on the same PC, or you can paste the cookie yourself. On Windows, CodexBar uses DPAPI and browser profile folders. It doesn't use the macOS Keychain. -## Supported Browsers +## Import cookies for a provider -| Browser | Encryption | Status | -|---------|-----------|--------| -| Chrome | DPAPI + AES-256-GCM; modern profiles may use Chromium ABE (`v20`) | ⚠️ Automatic only when the needed cookies are not App-Bound | -| Edge | DPAPI + AES-256-GCM; modern profiles may use Chromium ABE (`v20`) | ⚠️ Automatic only when the needed cookies are not App-Bound | -| Brave | DPAPI + AES-256-GCM; modern profiles may use Chromium ABE (`v20`) | ⚠️ Automatic only when the needed cookies are not App-Bound | -| Chrome Beta, Dev, Canary, Chrome for Testing, Chromium | DPAPI + AES-256-GCM; modern profiles may use Chromium ABE (`v20`) | ⚠️ Automatic only when the needed cookies are not App-Bound | -| Firefox | Unencrypted SQLite | ✅ Automatic | +1. Open **Settings > Providers** and select the provider. +2. Find the **Browser Cookies** section in the provider pane. It appears only for providers that sign in with web cookies. +3. Pick a browser and profile set from the list. The list shows only browsers that CodexBar found on this PC, with their profile count. +4. Click **Import from Browser**. -Chromium App-Bound Encryption (ABE) binds protected cookie keys to the browser installation. Win-CodexBar does not bypass that protection. If the selected Chromium profile stores the provider cookies as App-Bound `v20` values, automatic import cannot decrypt them with the normal user DPAPI key. Use a manual Cookie header or Firefox instead. Chromium browser choices remain available because older or unmigrated profiles can still contain readable DPAPI/AES-GCM cookies. Each Chrome channel (Stable, Beta, Dev, Canary, Chrome for Testing) and Chromium is a separate import choice that reads only its own `User Data` profiles. +CodexBar reads the cookies for that provider's domain and saves them as the provider's manual cookie. The section then shows a **Saved** badge and a **Remove** button. -## How It Works +The provider's **Cookie source** setting decides how CodexBar uses cookies during a refresh: -1. CodexBar reads the browser's cookie database from its standard location -2. For Chromium-based browsers, CodexBar can decrypt legacy DPAPI/AES-GCM cookies using the current user's credentials; App-Bound `v20` cookies are intentionally not bypassed -3. Only cookies for enabled providers are extracted (e.g., `claude.ai`, `cursor.com`) -4. Cookies are stored in-memory and refreshed on each provider poll +- **Automatic** reads the browser cookies on each refresh. +- **Manual** uses only the cookie you pasted or imported. +- **Off** turns cookie sign-in off for that provider. -## Setting Up Cookie Import +Some providers add their own steps to Automatic. The help text under the setting says what each one reads. -1. Open **Settings** → **Providers** tab -2. Select the provider you want to configure -3. In the provider detail pane, find the **Browser Cookies** section -4. Choose your browser from the dropdown and click **Import** +## Paste a cookie by hand -## Manual Cookies +Use this when automatic import fails. For example, the browser uses App-Bound Encryption, the cookie database can't be read, or CodexBar runs in WSL. -If automatic extraction fails (for example, Chromium App-Bound Encryption is active, the browser database cannot be read, or CodexBar is running in WSL): +1. Open the provider's website in your browser and sign in, for example `claude.ai`. +2. Open DevTools (F12) and select the **Network** tab. +3. Reload the page and select any request to the provider. +4. Under **Request Headers**, copy the value of the `Cookie` header. +5. In **Settings > Providers > (provider) > Browser Cookies**, paste the value and click **Save Cookie**. -1. Open your browser and navigate to the provider's website (e.g., `claude.ai`) -2. Open DevTools (F12) → **Network** tab -3. Refresh the page and click any request to the provider -4. Copy the `Cookie` header value from **Request Headers** -5. In CodexBar Settings → provider detail → **Browser Cookies**, paste the value +Manual cookies are stored in `manual_cookies.json` in the CodexBar config folder. The file is written through `secure_file`, which uses DPAPI on Windows. See [CONFIGURATION.md](./CONFIGURATION.md). -## Kimi local-storage tokens +## Supported browsers -With the Kimi cookie source set to automatic, CodexBar also reads `access_token` from Chromium browsers' `Local Storage` for the selected Kimi region (`www.kimi.com` or `www.kimi.ai`), after the Kimi Desktop session and browser cookies. Local storage is not App-Bound encrypted, so this can work when cookie decryption is blocked. Only unexpired three-segment JWTs are used; refresh tokens are never read. A manual Cookie header always wins, and Cookie source Off or Manual skips this step. Open Kimi in the browser to renew an expired session. Firefox and Safari local storage are not read, and only the `Default` and `Profile N` profiles are scanned. +| Browser | Cookie storage | Automatic import | +| --- | --- | --- | +| Chrome | DPAPI and AES-256-GCM. Newer profiles may use App-Bound Encryption (`v20`). | Works only when the needed cookies are not App-Bound. | +| Chrome Beta, Dev, Canary and Chrome for Testing | Same as Chrome | Same as Chrome | +| Chromium | Same as Chrome | Same as Chrome | +| Edge | Same as Chrome | Same as Chrome | +| Brave | Same as Chrome | Same as Chrome | +| Arc | Same as Chrome | Same as Chrome | +| Firefox | Unencrypted SQLite | Works | + +Each Chrome channel, Chromium, Edge, Brave and Arc is a separate choice. Each one reads only its own `User Data` profiles. Firefox reads the profiles under `%APPDATA%\Mozilla\Firefox\Profiles`. + +## App-Bound Encryption + +Chromium App-Bound Encryption (ABE) ties the cookie key to the browser install. CodexBar doesn't bypass it. If the selected profile stores the provider's cookies as App-Bound `v20` values, CodexBar can't decrypt them with the user's DPAPI key. + +Closing the browser doesn't remove ABE. Paste the cookie by hand, or sign in to the provider in Firefox and import from there. The Chromium choices stay in the list because older profiles can still hold readable DPAPI and AES-GCM cookies. + +## Kimi tokens from local storage + +When the Kimi cookie source is Automatic, CodexBar also reads `access_token` from the `Local Storage` of Chromium browsers, for the selected Kimi region (`www.kimi.com` or `www.kimi.ai`). It tries this after the Kimi Desktop session and the browser cookies. + +- Local storage is not App-Bound encrypted, so this can work when cookie decryption is blocked. +- CodexBar uses only unexpired JWTs with three segments. It never reads refresh tokens. +- A manual cookie always wins. Cookie source **Manual** or **Off** skips this step. +- CodexBar scans only the `Default` and `Profile N` profiles. It doesn't read Firefox local storage. + +If the token has expired, open Kimi in the browser to renew the session. ## Troubleshooting -- **"Chromium App-Bound Encryption"**: Modern Chrome, Edge, Brave, and other Chromium-based profiles can protect cookies with ABE. Closing the browser does not remove ABE; use a manual Cookie header or Firefox for the same login -- **"Cookie decryption failed"**: Close the browser and retry if the cookie database itself is locked -- **Empty cookies**: Make sure you're logged into the provider's web interface in that browser -- **WSL**: Chromium DPAPI cookies cannot be decrypted from WSL. Use manual cookies or CLI-based auth instead +| Message or symptom | What to do | +| --- | --- | +| "... Chromium App-Bound Encryption ... blocking automatic cookie import" | Paste the cookie by hand, or import from Firefox. | +| "Database error" or "Decryption error" | Close the browser and try again, in case the cookie database is locked. | +| "No cookies found for (domain)" | Sign in to the provider's website in that browser, then import again. | +| The browser list is missing | CodexBar found no supported browser. Paste the cookie by hand. | +| CodexBar runs in WSL | WSL can't decrypt Chromium DPAPI cookies. Paste the cookie by hand or use CLI sign-in. See [WSL.md](./WSL.md). | ## Related -- [CONFIGURATION.md](./CONFIGURATION.md) — where manual cookies and settings live on disk -- [PROVIDERS.md](./PROVIDERS.md) — web vs cli vs oauth sources -- [WSL.md](./WSL.md) — why automatic Chromium decrypt fails in WSL +- [CONFIGURATION.md](./CONFIGURATION.md): where manual cookies and settings are stored. +- [PROVIDERS.md](./PROVIDERS.md): which providers use web, CLI or OAuth sign-in. +- [WSL.md](./WSL.md): why Chromium cookie decryption fails in WSL. diff --git a/docs/PROVIDERS.md b/docs/PROVIDERS.md index ee6e503247..d798b0f645 100644 --- a/docs/PROVIDERS.md +++ b/docs/PROVIDERS.md @@ -1,44 +1,82 @@ -# Providers (Windows) +# Providers -Windows rewrite of the *role* of upstream `docs/providers.md`: how providers are registered and fetched in **this** repo. -Do **not** treat upstream’s full strategy table as authoritative for Win-CodexBar without checking code — IDs and auto-order drift. +This page explains how a provider is added and fetched in Win-CodexBar, how its usage windows and pace work, and what is special about individual providers. The upstream `docs/providers.md` describes the macOS app. Its ids and fallback order differ from this port, so check the code before you copy anything from it. -## Single factory +## Add a provider -All shells and the CLI construct providers through: +1. Add a `ProviderId` variant in `rust/src/core/provider.rs`, with its metadata: `cli_name`, `display_name`, `from_cli_name` and, if it uses cookies, the cookie domain. +2. Implement the `Provider` trait in `rust/src/providers//`. +3. Add a match arm in `instantiate` in `rust/src/core/provider_factory.rs`. +4. Add a match arm in `pace_capability` in the same file. Map the provider to its own `PACE_CAPABILITY`, or to `ProviderPaceCapability::UNSUPPORTED`. See [Pace](#pace). -```text -codexbar::core::instantiate_provider → rust/src/core/provider_factory.rs -``` +Both matches are exhaustive, so a missing arm doesn't compile. A test checks that every id can be built. + +Rules: + +- `instantiate_provider` is the only provider factory. The desktop shell and the CLI both use it. Don't write another one. +- Keep parsing, auth and display choices inside the provider module. Shared code has no `if provider == X` branches. When shared code needs a provider-specific answer, add a typed field or a trait method with a default, as `menu_bar_pace_lane` and `reset_regenerates` below do. +- Keep identity, plan and email per provider. Never show one provider's account data on another provider's card or page. + +`ProviderId` has 96 variants. A fresh install enables only Claude and Codex. Kimi K2 and CrossModel are deprecated and stay hidden unless they are already enabled. + +## Fetch sources + +| Source | Meaning | +| --- | --- | +| `auto` | The provider's own fallback order | +| `web` | Browser cookies or a pasted cookie against the provider's web dashboard | +| `cli` | A local CLI, PTY or RPC helper | +| `oauth` | An OAuth flow, where the provider supports it | + +Pick one for a single CLI run with `codexbar usage --source `. Most providers look for credentials in this order: the value saved in Settings, then the credential store, then environment variables. Each module sets its own order. + +## Usage windows -`ProviderId` lives in `rust/src/core/provider.rs`. The factory match is **exhaustive** (missing arm = compile error). Tests ensure every id instantiates. +A provider returns its usage as rate windows (`RateWindow` in `rust/src/core/rate_window.rs`). The tray panel draws one metric row per window, with a bar, the reset time and, where it applies, a pace line. -**Never** duplicate provider factories in the Tauri shell or ad-hoc commands. +- `window_minutes` is the window length: 300 for a five-hour session, 10080 for a week, 43200 as the 30-day sentinel for a monthly window. A window with no length gets no tray pace tint and, for some rows, no pace line. +- `reset_regenerates` makes the card say "Regenerates in ..." instead of "Resets in ...", and hides the pace line for that window. Synthetic sets it on its regenerating lanes. +- `hidden_on_card` keeps the window for the tray icon and float bar, but the card draws no row for it. LithosAI uses it for its balance. +- `card_title_key` on `CostSnapshot` names the cost block. ClawRouter uses "Monthly budget" when the policy has a limit and "ClawRouter spend" when it doesn't. LithosAI uses "Credits". -## Adding a provider +These three fields cross to the frontend only when a provider sets them. The CLI JSON doesn't change. -1. Add a `ProviderId` variant + `cli_name` / `display_name` / cookie domain / `from_cli_name` metadata as required. -2. Implement `Provider` in `rust/src/providers//` (or module). -3. Add the match arm in `provider_factory.rs::instantiate`. -4. Keep provider-specific parsing and auth **inside** that module — no cross-provider branching in shared UI paths. -5. Keep identity / plan / email **siloed** per provider in the UI. +## Pace -## Fetch strategies (concept) +Pace compares how much of a window you have used with how much of its time has passed. The card shows it as a line such as "10% in deficit · Runs out in 9d 8h". The shell scores it into seven stages, from `far_behind` to `far_ahead` (`commands/bridge/pace.rs`). -Same vocabulary as upstream, implemented in Rust: +- Session and weekly windows get pace from their length and their reset time. +- **Work days** in Settings > Menu limits weekly pace to working days. When it is set from 2 to 6, a 10080-minute window counts only Monday up to that weekday as elapsed time. Day boundaries are local midnight. Automatic, 1 and 7 use plain elapsed time. +- The card hides the pace line at 100% used. It also hides it unless at least 3% use is expected or the quota runs out now. -| Source label | Meaning (typical) | -|--------------|-------------------| -| `auto` | Provider-specific fallback order | -| `web` | Cookie / dashboard HTTP | -| `cli` | Local CLI / PTY / RPC helpers | -| `oauth` | OAuth-backed flows where supported | +### Reset-window pace for monthly and billing windows -CLI: `codexbar usage --source auto|web|cli|oauth`. +Some providers refill on a billing date instead of on a fixed session or week. Those providers opt in to reset-window pace. Each one declares a `PACE_CAPABILITY` constant in its own module (`rust/src/core/pace_capability.rs` defines the type). The rule says which windows get pace and whether a 30-day sentinel becomes the real calendar month that ends at the reset, so a February cycle scores as 28 days. -Auth resolution helpers in `rust/src/providers/` commonly try: explicit settings → keyring/entry → environment variables (exact order is provider-specific). +| Provider | Windows that get pace | Uses the calendar month | +| --- | --- | --- | +| Copilot | any window with a reset date | when the window has no length | +| Cursor | any window with a length | no | +| Kimi | exactly 10080 minutes | no | +| Grok | a window labelled weekly whose reset falls inside it | no | +| Z.ai | the 43200-minute MCP window | yes | +| Amp | a window with a length whose reset text starts with "renews in " | no | +| Alibaba, Alibaba Token Plan, Command Code, Doubao, MiMo, StepFun, Notion, Ollama, OpenCode Go | exactly 43200 minutes | yes | -### Devin manual authentication +Every other provider is `UNSUPPORTED` and keeps only the session and weekly pace. + +### Tray pace tint + +With **Color Pace Indicator** on (`menu_bar_color_pace`, Settings > Menu Bar), a single-provider tray icon turns green when usage is behind pace and red when it is ahead. Stacked icons keep the usage color. + +The tint is scored on one window: + +- `Provider::menu_bar_pace_lane()` can name it. Codex returns its weekly lane and Abacus its primary lane. +- For every other provider (`None`), it is the window the icon draws for the selected metric. + +The window must have a length, quota left and at least 3% expected use. Copilot's windows have no length, so Copilot never gets a tint. + +## Devin manual authentication On Windows, Devin uses a manually pasted Bearer token; Chrome-session import is not available. In Devin, open Developer Tools → Network, reload **Usage & @@ -56,10 +94,8 @@ organization. ## Cookie-backed providers -Windows browser import: Chrome Stable, Beta, Dev, Canary, Chrome for Testing, Chromium, Edge, Brave (DPAPI + AES-GCM), and Firefox (SQLite). Chrome channels are detected separately and keep distinct import choices. -Settings → **Providers** → provider detail → choose browser → Import. -Manual cookie header paste is the fallback (required under WSL for Chromium DPAPI). -Details: [COOKIES.md](./COOKIES.md). +Providers that read a web dashboard use cookies from a browser profile, or a cookie header you paste. Choose the browser for each provider in **Settings > Providers > (provider)**, then import. A pasted header is the fallback, and the only option under WSL. [COOKIES.md](./COOKIES.md#supported-browsers) lists the supported browsers. + ### Venice web session @@ -149,11 +185,15 @@ signed-in console at `https://console.lithosai.cloud` with both session cookies, - Requests, in order: `GET /api/me` (active organization, email), then `GET /api/billing` and `GET /api/billing/spend?start=YYYY-MM-01&end=YYYY-MM-DD` (UTC month to date), both with `X-Organization-Id`. -- The card shows the prepaid USD balance (1 USD = 1,000,000,000 nanos) as a - "Prepaid credits" cost block and a **Billing** section: Balance, Payment card, - Account status, Today (UTC) and This month (UTC). There is no quota bar. A - negative balance has no cost block and stays visible as, for example, - "Balance: -$1.00" in the primary line and the Balance row. +- The prepaid USD balance (1 USD = 1,000,000,000 nanos) shows twice on the + detailed card, as on the Mac. First comes a **Billing** section: Balance, + Payment card, Account status, Today (UTC) and This month (UTC). Then comes a + cost block titled **Credits** that reads, for example, "Balance: $42.37". + There is no quota bar. The balance window is `hidden_on_card`, so it feeds + the tray and float bar without drawing a card row. +- The compact Overview shows the Billing rows once and no Credits block. +- A negative balance has no cost block. It shows in the Balance row, for + example "-$1.00". - A spend report that fails or does not parse shows "Spend: Unavailable" and keeps the balance; a 401 anywhere rejects that session, and an expired session is reported once no candidate is left. @@ -301,6 +341,11 @@ Synthetic (or `SYNTHETIC_API_KEY`); the CLI also accepts `synthetic.new`. "76% after next regen · Full in ~3 regens". The weekly lane does the same when it carries `maxCredits` and `nextRegenCredits`. Like the Mac card, no weekly USD cost is shown (on the card or in CLI JSON). +- Regenerating lanes keep their length (300 and 10080 minutes) and set + `reset_regenerates`. The card counts them down as "Regenerates in 45m" + instead of "Resets in", always as a relative time, and shows no pace line + for them. Plain lanes keep "Resets in". The tray pace tint still scores a + regenerating lane. - The plan name (`plan`, `planName`, `tier`, ...) is shown as the plan. - 401 and 403 report "Invalid Synthetic API credentials."; response bodies are never echoed in errors. @@ -317,8 +362,10 @@ credentials, a query or a fragment are rejected before the key is read. Redirects are never followed and response bodies are never echoed in errors. - The primary lane is the monthly budget (spent / limit), resetting on the - first day of the next month (UTC). The cost line shows this month's spend - against the limit. + first day of the next month (UTC). The cost block is titled "Monthly budget" + and shows this month's spend against the limit. Without a positive limit it + is titled "ClawRouter spend". Both titles stay in English in every language, + as on the Mac. - Details show requests, tokens, actual cost, the budget ledger and the monthly budget, then up to 20 routed providers ordered by spend and requests. @@ -344,7 +391,21 @@ portal in Settings → Providers → IBM Bob, as token accounts, or with - 401 and 403 report that the key was rejected; response bodies are never echoed in errors. -## Listing what is enabled +## Window lengths that match the Mac + +These providers set window lengths the same way the Mac app does, so their rows get pace and the tray tint can score them: + +| Provider | Window | Length | +| --- | --- | --- | +| Codebuff | Weekly lane | 10080 minutes. The Credits lane has no length. | +| MiniMax | Services rows from the coding-plan page | From the row's window type, for example 300 for 5 hours and 10080 for Weekly | +| MiniMax | Remains weekly rows | Always 10080 minutes, even without a start and end | +| Synthetic | Regenerating five-hour and weekly lanes | 300 and 10080 minutes, from the payload | +| Grok | Credits proxy period | Kept after the period ends. An ended period has no pace. | +| Z.ai | `TIME_LIMIT` windows | From the unit: 1 = 1440, 3 = 60, 5 = 1, 6 = 10080 minutes per unit. The monthly MCP marker keeps the 30-day sentinel. | +| Notion | Billing lane | Labelled "Monthly". Other monthly lanes keep the "30-Day" label. | + +## See what is enabled ```powershell codexbar config providers @@ -352,61 +413,129 @@ codexbar config enable -p cursor codexbar config disable -p cursor ``` -Desktop: Settings → Providers (sidebar reorder, per-provider credential UI). +In the app, use **Settings > Providers**. The sidebar can reorder and toggle providers, and each provider page holds its credentials. See [CONFIGURATION.md](./CONFIGURATION.md#provider-list-in-the-sidebar). ## Status pages -Optional status polling (provider status pages) is available via CLI `--status` and Settings advanced toggles where wired. Mapping of Statuspage vs Google incidents is provider metadata in code — see provider modules rather than upstream-only URLs if they disagree. +The app polls provider status pages while **Settings > General > Check provider status** (`status_checks_enabled`, on by default) is on. A full provider refresh starts a status poll for the enabled providers that have a status source. The auto-resume probe (Windows-only, 60-second exact-session watcher) does not poll status. Unforced refreshes skip a provider whose last poll started less than 30 seconds ago; forced refreshes (manual, menu open, settings toggle) always poll. One fetch times out after 10 seconds and the whole poll after 15 seconds. A failed fetch keeps the last good status. + +Where the status shows: + +- **Settings sidebar.** For a provider with a status source, a service dot replaces the fetch-status dot: green for operational, yellow for a partial outage, orange for a major outage, red for a critical issue, gray for maintenance or unknown (also before the first poll). The tooltip reads "Provider service status: ..." with the label. +- **Tray panel.** While the selected provider has an incident, the menu shows one extra line under the provider actions: the feed's description (or the status label) and when the feed was updated. +- **Tray icon tooltip.** Each enabled provider with an incident adds a `Provider: label` line under the tooltip title. + +The status source per provider is set by `status_source` in `rust/src/status/mod.rs`: + +| Source | Providers | +| --- | --- | +| Statuspage (`/api/v2/status.json`) | Claude, Codex and OpenAI API (status.openai.com), Copilot (githubstatus.com), Cursor, Factory, Augment, Hugging Face, ZoomMate (zoomstatus.com) | +| Google Workspace incident feed | Gemini, Antigravity | + +Other providers have no status source, so they keep the fetch-status dot and never add an incident line. + +`codexbar usage --status` fetches the same sources from the CLI and adds them to the output. In JSON, `status.level` holds: `operational`, `degraded` (raw `minor`), `major` (raw `major` or `critical`), or `unknown` (raw `maintenance` or `unknown`). When this table and the code disagree, the code wins. ## Usage & Spend -Desktop tab id: `usageSpend`. The desktop and Overview consume one shared spend catalog. Codex and Claude local logs are first-class; routed OpenCodex usage enriches the matching Codex, OpenCode Go, Kimi, or DeepSeek subscription instead of appearing as a second fake provider. xAI and OpenRouter can publish exact provider-metered daily USD spend when their management credentials are configured, while Grok local sessions contribute tokens only (the scan covers exactly the requested number of local calendar days, including today). Missing spend sources remain unknown rather than becoming a false `$0`. Do not invent cross-currency totals. +The Settings pane id is `usageSpend`. The pane and the Overview read one shared spend catalog. + +- Codex and Claude local logs are first-class sources. +- Routed OpenCodex usage is added to the matching Codex, OpenCode Go, Kimi or DeepSeek subscription. It doesn't appear as a separate provider. +- xAI and OpenRouter can report exact daily USD spend from the provider when their management credentials are set. +- Grok local sessions add tokens only. The scan covers exactly the requested number of local calendar days, including today. +- A missing spend source stays unknown. It never shows as `$0`. +- Totals are never converted across currencies. ### AWS Bedrock monitoring -AWS Bedrock is a Windows provider backed by signed Cost Explorer requests and optional CloudWatch activity. It is disabled by default, and monitoring requests can add charges to your AWS bill. AWS currently charges $0.01 per Cost Explorer API request; paginated monthly-spend reads can therefore use more than one billed request, while optional CloudWatch activity is billed under CloudWatch pricing. +AWS Bedrock uses signed Cost Explorer requests and, optionally, CloudWatch activity. It is disabled by default. + +- Monitoring can add charges to your AWS bill. AWS charges $0.01 per Cost Explorer API request, and a paginated monthly-spend read can take more than one request. CloudWatch activity is billed at CloudWatch prices. +- The shared refresh interval controls automatic polling. Manual (`0`) stops the timer, but an explicit refresh and **Refresh when the menu opens** still fetch Bedrock data. Disable Bedrock to stop its refreshes. +- `CODEXBAR_BEDROCK_BUDGET` only changes the monthly progress shown. It doesn't cap AWS charges, stop polling or enforce a limit. + +Custom pricing overlays override prices only where the local spend data has matching provider and model token evidence. + +- An explicit zero rate means free. A rate you leave out stays unknown. +- Usage & Spend keeps provenance and coverage visible, and keeps cost-only model rows when token coverage is partial. +- **Copy JSON** and **Save JSON** export the same JSON contract. -The shared refresh interval controls automatic provider polling. `0` / Manual disables the recurring timer, but explicit refreshes and **Refresh when the menu opens** can still fetch Bedrock data. Disable Bedrock itself to stop its app refreshes. +### OpenCode, Codex quota and local cost -`CODEXBAR_BEDROCK_BUDGET` changes only the displayed monthly progress. It does **not** cap AWS charges, stop polling, or enforce a billing limit. +OpenCode can hold an OpenAI or Codex OAuth login. Win-CodexBar reuses it for **remote Codex account quota** only when the Codex provider's **External OAuth sources** setting is on. -Custom pricing overlays are exact-match overrides used only where the local spend contract has matching provider/model token evidence. Explicit zero rates mean free; omitted rate fields stay unknown. The Usage & Spend surface keeps provenance/coverage visible, preserves cost-only model rows when token coverage is partial, and can Copy JSON or save the same JSON contract through the native file picker. +- Native Codex credentials still come first. +- An explicit `CODEX_HOME` stays isolated. +- External credentials are read-only. +- Ordinary OpenCode sessions are never added to Codex token or spend totals. -### OpenCode, Codex quota, and local cost boundaries +OpenCode Go's local SQLite reader reads only its own `opencode-go` assistant records. OpenAI API-platform usage is a separate provider. -OpenCode-held OpenAI/Codex OAuth can be reused for **remote Codex account quota** only when the Codex provider's `External OAuth sources` setting is explicitly enabled. Native Codex credentials still take precedence, an explicit `CODEX_HOME` stays isolated, and external credentials remain read-only. This does **not** import ordinary OpenCode sessions into Codex token or spend totals. OpenCode Go's local SQLite reader remains scoped to its own `opencode-go` assistant records; OpenAI API-platform usage is a separate provider. Its recorded local token counts (`tokens` on assistant messages and step-finish parts; step-finish parts win over their parent message) fill Usage & Spend token columns and per-model history; costs stay the recorded `cost` field, and rows with absent, malformed, negative, or overflowing tokens leave that window's totals incomplete instead of counting as zero. +- Token counts come from `tokens` on assistant messages and step-finish parts. A step-finish part wins over its parent message. +- They fill the Usage & Spend token columns and per-model history. Costs are the recorded `cost` field. +- A row with missing, malformed, negative or overflowing tokens marks that window's totals incomplete. It isn't counted as zero. -Codex local cost prices **Priority (Fast) turns** at the Fast rate. A turn counts as Priority when `/logs_2.sqlite` (Codex's trace database) holds a `response.create` websocket request with `service_tier == "priority"` for that turn's id. The database is opened read-only, scanned incrementally with a persisted cursor in the cost cache, and only turn ids, model names, and timestamps are kept; row bodies contain prompts and are never stored or logged. A missing or unreadable database keeps Standard pricing, and turn evidence never crosses `CODEX_HOME` scopes. Models without a Fast lane stay Standard. Older cost caches rebuild once (Codex cache schema v4 records each row's turn id). +Codex local cost prices **Priority (Fast)** turns at the Fast rate. + +- A turn is Priority when `/logs_2.sqlite`, Codex's trace database, has a `response.create` websocket request with `service_tier == "priority"` for that turn id. +- The database is opened read-only and scanned incrementally, with a cursor saved in the cost cache. +- Only turn ids, model names and timestamps are kept. Row bodies contain prompts and are never stored or logged. +- A missing or unreadable database keeps Standard pricing. Turn evidence never crosses `CODEX_HOME` scopes. +- Models without a Fast rate stay Standard. +- Older cost caches rebuild once. Codex cache schema v4 records each row's turn id. + +## API key notes ### OpenRouter keys -The **API key** field (or `OPENROUTER_API_KEY`) is required and accepts either a regular API key or a Management API key. A Management key entered there also enables account Activity on the official OpenRouter API. The separate **Management API key** field (or `OPENROUTER_MANAGEMENT_API_KEY`) is an optional additional key for account Activity, only needed to use a Management key different from the one in the required field. Filling only the Management field does not select an account for quota or balance; with no primary key, the provider reports the missing-key message instead. +- The **API key** field (or `OPENROUTER_API_KEY`) is required. It takes a regular API key or a Management API key. A Management key there also turns on account Activity from the official OpenRouter API. +- The **Management API key** field (or `OPENROUTER_MANAGEMENT_API_KEY`) is optional. You need it only to use a different Management key for account Activity. +- A Management key alone doesn't select an account for quota or balance. Without the main key, the provider reports the missing-key message. ### z.ai Coding Plan quotas -z.ai Coding Plans accept both `TOKENS_LIMIT` and `CREDIT_LIMIT` rows. The shortest known Coding Plan window becomes primary and the longest becomes secondary; `TIME_LIMIT` is the separate MCP lane. When absolute usage/remaining counts are available they determine the used percentage, otherwise the provider percentage is used, always clamped to 0–100%. This behavior is shared by the tray, provider detail, CLI, and other Windows surfaces. Empty or wholly unrecognized limits never fabricate a 0% window; they show a "Coding Plan usage: Unavailable" detail row that points to the Usage Dashboard, and mixed responses keep the recognized windows and add "Additional quota: Unavailable". Unknown string limit types are skipped, while malformed entries and unsupported envelopes fail with Usage Dashboard guidance. +- z.ai Coding Plans accept both `TOKENS_LIMIT` and `CREDIT_LIMIT` rows. The shortest known Coding Plan window is the primary lane and the longest is the secondary lane. `TIME_LIMIT` is the separate MCP lane. +- When absolute used and remaining counts exist, they set the used percentage. Otherwise the provider's percentage is used. Both are clamped to 0-100%. The tray, the provider page and the CLI all use this rule. +- Empty or unrecognized limits never invent a 0% window. They show a "Coding Plan usage: Unavailable" row that points to the Usage Dashboard. A mixed response keeps the windows it recognizes and adds "Additional quota: Unavailable". +- Unknown string limit types are skipped. Malformed entries and unsupported envelopes fail with Usage Dashboard guidance. ### xKiro daily free tokens -xKiro reads `GET https://api.xkiro.com/v1/usage` with `XKIRO_API_KEY` (or the key saved in Preferences) as a bearer token. Only the `free_tokens` counters are used: the primary lane is `used_today / limit_per_day`, resetting at the next 00:00 UTC, and a limit of `0` reads as fully used. The paid `wallet` balance is never treated as headroom and is not shown. Counters that are missing stay missing rather than becoming zero, a payload with no usable counter is reported as unrecognized, and an explicit `plan: null` is labelled "Pay as you go". There are no token accounts. The wire shape follows upstream's documented example; live account behavior has not been verified. +xKiro reads `GET https://api.xkiro.com/v1/usage` with `XKIRO_API_KEY`, or the key saved in Settings, as a bearer token. -Upstream's independent **WidgetKit** provider-widget configuration has no Windows analogue in this repository. Win-CodexBar has no WidgetKit extension; provider cards and tray entries are already independent Windows/Tauri surfaces. +- Only the `free_tokens` counters are used. The primary lane is `used_today / limit_per_day` and resets at the next 00:00 UTC. A limit of `0` reads as fully used. +- The paid `wallet` balance is never treated as headroom and isn't shown. +- Missing counters stay missing; they don't become zero. A payload with no usable counter is reported as unrecognized. +- An explicit `plan: null` is labelled "Pay as you go". +- There are no token accounts. +- The response shape follows upstream's documented example. It hasn't been checked against a live account. ### LiteLLM budgets -LiteLLM reads a virtual key's own budgets from the proxy's management routes. Set the base URL and key in Settings → Providers → LiteLLM, or use `LITELLM_BASE_URL` and `LITELLM_API_KEY`. A trailing `/v1` is dropped. The base URL must use HTTPS unless it names `localhost`, a `.local` host, or a loopback, RFC 1918, link-local or IPv6 unique-local address, and it must not embed credentials, because the key is sent as a bearer token. +LiteLLM reads a virtual key's own budgets from the proxy's management routes. Set the base URL and key in **Settings > Providers > LiteLLM**, or with `LITELLM_BASE_URL` and `LITELLM_API_KEY`. + +- A trailing `/v1` is dropped. +- The base URL must use HTTPS unless it names `localhost`, a `.local` host, or a loopback, RFC 1918, link-local or IPv6 unique-local address. It must not embed credentials, because the key is sent as a bearer token. +- The provider calls `GET /key/info`. Then it calls `GET /user/info?user_id=…` for a key bound to a user, or `GET /team/info?team_id=…` for a team-only key. A response whose user or team id differs from the key's is rejected. +- The personal budget is the primary lane and the key's team budget is the secondary lane. With only one budget, it takes the primary lane under its own label. A key with no budget shows "No budget set". +- Amounts such as `$25.00 / $100.00` are detail lines, apart from a real reset date. +- The Automatic tray and float bar metric shows the team budget unless a budget is exhausted. +- Spend without a budget stays visible as API spend. Budget resets give no pace. + +## Not ported -The provider calls `GET /key/info`, then `GET /user/info?user_id=…` for a user-bound key or `GET /team/info?team_id=…` for a team-only key, and rejects a response whose user or team ID differs from the key's. The personal budget fills the primary lane and the key's matching team budget fills the secondary lane. When only one budget exists, it takes the primary lane under its own label, and a key without any budget shows "No budget set". Amounts such as `$25.00 / $100.00` are detail lines, shown apart from a real reset date. The Automatic tray and float bar metric shows the team budget unless a budget is exhausted. Spend without a budget stays visible as API spend, and no pace is derived from budget resets. +Upstream's WidgetKit provider-widget settings have no Windows version. Win-CodexBar has no WidgetKit extension; provider cards and tray entries already work on their own. -## Upstream doc warning +## Upstream provider docs -Upstream `docs/providers.md` is a large auto-strategy matrix (60+ providers) for the macOS app. Use it as **inspiration** when porting a provider. For runtime truth on Windows: +Upstream `docs/providers.md` is a large table of fallback orders for the macOS app. Use it for ideas when you port a provider. For what Windows actually does, read: 1. `rust/src/core/provider.rs` (`ProviderId`) 2. `rust/src/providers//` -3. `codexbar usage -p -v` / desktop provider detail errors +3. the output of `codexbar usage -p -v`, or the error on the provider's Settings page -## Related +## Related docs - [ARCHITECTURE.md](./ARCHITECTURE.md) - [CLI.md](./CLI.md) diff --git a/docs/WINDOWS_PROOF.md b/docs/WINDOWS_PROOF.md index 9de894acd8..7b680aa4b5 100644 --- a/docs/WINDOWS_PROOF.md +++ b/docs/WINDOWS_PROOF.md @@ -1,178 +1,173 @@ -# Windows build proof — `feature/port-phase-13-e2e` +# Proving UI changes on Windows -This document describes how to produce the Windows installer from the -Phase-13 desktop-shell branch and what to manually verify on the -resulting machine. It replaces the stale macOS/Swift workflow docs for -the purposes of the Phase-13 sign-off. +Unit tests and `scripts/local-check.ps1` don't prove tray, Settings, float bar, theme or WebView2 behavior. For a change to any of these, rebuild the app on Windows, drive the real window, and attach the proof to the PR. This page shows how. The rule itself is in [AGENTS.md](../AGENTS.md#ui-validation-with-cua-trycuacua). -**Branch:** `feature/port-phase-13-e2e` -**Default app:** Tauri shell (`apps/desktop-tauri/src-tauri`) +## The proof loop ---- +1. **Rebuild.** Build a desktop binary that includes the change: -## 1. Build commands (PowerShell, from repo root) + ```powershell + pnpm --dir apps/desktop-tauri run tauri:build:debug + ``` -Run in a clean PowerShell 7 session on Windows 10 or 11 (x64). `cd` into -the worktree root first. + Or run `.\scripts\dev.ps1`. A binary built before the change proves nothing. +2. **Close CodexBar.** The app uses the single-instance plugin, so a new launch can hand off to an old process that is still running. +3. **Launch in proof mode.** Set `CODEXBAR_PROOF_MODE` before you start the new binary. See [Proof mode](#proof-mode). +4. **Drive the window in the background.** Use CDP for webview controls and cua-driver for native checks. See [Drive the app](#drive-the-app). +5. **Check what you can see**, not only exit codes. For example: pixels, the window list, a toggle's checked state, the persisted setting, the theme under `auto`, or whether the float bar window exists. +6. **Attach the proof** to the PR: CDP assertions and CUA screenshots. List what the proof doesn't cover, such as tray icon or tray menu pixels. If CUA can't run, say why and attach equivalent manual proof, as the PR template asks. + +## Proof mode + +`CODEXBAR_PROOF_MODE` opens one surface at startup and keeps it open for automation. In proof mode the app: + +- doesn't close the surface when it loses focus; +- shows each surface without activating it, so it never takes focus from the app you are using. + +| Value | What opens | +| --- | --- | +| `trayPanel` | The tray panel on the `main` window, at a fixed position above the taskbar | +| `popOut` | The tray panel flyout window, the same window a tray left-click opens | +| `hidden` | Nothing | +| `settings` | Settings on the General pane | +| `settings:` | Settings on one pane, for example `settings:menuBar` | +| `settings:providers:` | Settings on one provider, for example `settings:providers:codex` | + +The tab ids are `general`, `providers`, `notifications`, `menuBar`, `menu`, `usageSpend`, `advanced`, `hooks` and `about`. They are case-sensitive. The provider part must be the exact `cli_name`. + +The app logs a warning and starts normally when the value is wrong. That includes an unknown surface, an unknown tab, a payload on `trayPanel`, `hidden` or `popOut` (for example `popOut:codex`), and `floatbar`, which is not a proof surface. + +```powershell +$env:CODEXBAR_PROOF_MODE = 'settings:menu' +``` + +### Seed usage data + +`CODEXBAR_SEED_USAGE_JSON` points at a JSON file with synthetic usage. The app loads it into the provider cache before the first window reads it, so a proof run doesn't need real accounts. + +```powershell +$env:CODEXBAR_SEED_USAGE_JSON = 'C:\proof\seed.json' +``` + +The file holds one of two shapes: + +- **One Codex snapshot.** A single JSON object in the bridge `ProviderUsageSnapshot` shape. Its `providerId` must be `codex`. This works with or without proof mode. +- **A list of snapshots.** A non-empty JSON array of snapshots. This works only when `CODEXBAR_PROOF_MODE` holds a valid value. Each `providerId` must be an exact bridge id (the `cli_name`), and each provider may appear once. The array is all or nothing: one bad entry skips the whole file. + +The app fills fields that the file leaves out: + +- `remainingPercent` from `usedPercent`; +- `formattedUsed` from `used`; +- `updatedAt` from the current time; +- the pace for any window that has `resetsAt` but no `pace`, with the same math and work-days setting as a live refresh. + +A file the app can't read or parse logs a warning, and the app starts without the seed. A normal refresh doesn't replace seeded data while the cache holds every provider the refresh asks for. A forced refresh, such as **Refresh All**, fetches live data. + +`CODEXBAR_SEED_CLAUDE_ACCOUNTS_JSON` works the same way for the saved Claude account list. It takes effect only in proof mode. A file the app can't parse logs a warning, and the app uses the real account list. + +## Drive the app + +Drive the app without taking the user's focus or keyboard. Don't use cua-driver's `bring_to_front`, foreground mode, `hotkey` or `type_text`. + +### CDP for webview controls + +Set a remote debugging port before you launch the app. Use one port for each app you run in parallel. ```powershell -# Prerequisites (once per machine) -rustup target add x86_64-pc-windows-msvc -# WiX Toolset v3 is required for the .msi artefact and is installed by the -# Tauri bundler on first run (https://tauri.app/v2/reference/config/#bundle). - -# Shared backend + Rust tests -cargo fmt --all -cargo clippy --all-targets -- -D warnings -cargo test - -# Desktop Tauri shell — focused -cargo test -p codexbar-desktop-tauri -cargo clippy -p codexbar-desktop-tauri --all-targets -- -D warnings - -# Proof/CUA captures: set `CODEXBAR_PROOF_MODE` (e.g. `trayPanel`) to open a -# surface with blur-dismiss suppressed; optionally set -# `CODEXBAR_SEED_USAGE_JSON=` to plant one synthetic bridge-shaped -# Codex ProviderUsageSnapshot into the provider cache at launch (malformed -# files are warned about and ignored) — the seeded cache is pinned against -# refresh eviction for the duration of the run. - -# Frontend unit tests + bundle -cd apps\desktop-tauri -pnpm install --frozen-lockfile -pnpm run check-locale # exits non-zero on Rust/TS locale-key drift -pnpm run test # vitest smoke tests -pnpm run build # prebuild runs check-locale, then tsc + vite - -# Produce the Windows installer (.msi via WiX + .exe via NSIS) -pnpm run tauri:build # add -- --bundles msi or nsis to narrow +$env:WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS = '--remote-debugging-port=' ``` -Artefacts land in -`apps\desktop-tauri\src-tauri\target\release\bundle\msi\*.msi` and -`\nsis\*.exe`. Invocations that pass `--no-bundle` (our default `pnpm run -tauri:build`) skip installer packaging; drop `--no-bundle` for the real -release flow via `cargo tauri build` directly. +Read `http://127.0.0.1:/json/list` and attach to the window's own page target. Pick the target by URL: + +| Window | Label | Page URL | +| --- | --- | --- | +| Hidden main window (and `trayPanel` proof) | `main` | `http://tauri.localhost/` | +| Tray panel flyout | `flyout` | `http://tauri.localhost/index.html?window=flyout` | +| Settings | `settings` | `http://tauri.localhost/index.html?window=settings&tab=` | +| Float bar | `floatbar` | `http://tauri.localhost/index.html?window=floatbar&orientation=` | -For the Inno Setup release artifact, prefer the cached Windows release builder: +Don't attach to the first target in the list by default. Tools that do that, or that open `about:blank` (for example browser-use), see a blank page. + +Click with `Input.dispatchMouseEvent`. It doesn't move the cursor or take focus. Check the DOM state, and check the saved value through the app bridge command `get_settings_snapshot`. + +### cua-driver for native checks + +[cua-driver](https://github.com/trycua/cua) from trycua/cua does background computer use on Windows: window lists, UI Automation, clicks and real screenshots. See the [docs](https://cua.ai/docs), the [install guide](https://cua.ai/docs/how-to-guides/driver/install) and the [CLI reference](https://cua.ai/docs/reference/cua-driver/cli-reference). Install it in PowerShell: ```powershell -.\scripts\windows-release-build.ps1 -Ref v0.27.4 +irm https://cua.ai/driver/install.ps1 | iex ``` -It keeps a clean managed checkout while reusing Cargo, pnpm, and signed -installer dependency caches. Use `-WarmCacheOnly` after large ports to prepare -the desktop cache, and `-SmokeInstall` to install/uninstall the generated -installer. The builder has no upload switch. Publication is performed only by -the approval-gated CircleCI draft publisher in `docs/release/ci-cd.md`, which -checks hashes and never overwrites an existing asset. -Run the standalone smoke installer test on a Windows machine before approval: +Then follow the steps the installer prints. The binary is usually `%LOCALAPPDATA%\Programs\Cua\cua-driver\bin\cua-driver.exe`. + +- `cua-driver serve` runs a long-lived daemon. `cua-driver call ''` runs one tool, for example `list_windows`, `get_window_state` or `click`. +- In cua-driver 0.31 and later, actions take an `element_token` from `get_window_state`. `element_index` and `snapshot_id` are rejected. +- A pixel click needs a screenshot from the same session first. +- Pass the same `session` label on the state read and on every action that uses its tokens, or keep one `serve` connection. Separate one-shot `call`s without a session retire the snapshot, and its tokens go stale. + +Use CUA in every proof for what CDP can't see: + +- `verify_state` (with `timeout_ms` and `stable_samples`) for window existence and bounds; +- real-pixel screenshots through `get_window_state` with `--screenshot-out-file`; +- native facts, such as a dark DWM frame under theme `auto`, the float bar being topmost and layered, and the window frame. + +CDP screenshots are not proof. + +## What to check after an install + +Install the build, start **CodexBar**, and walk through this list. + +1. **Tray icon.** The icon appears in the notification area. On first run you may need to turn it on under taskbar settings. +2. **Left-click the tray icon.** The tray panel flyout opens near the tray, on the monitor that holds the taskbar. +3. **Right-click the tray icon.** The native menu opens. From top to bottom it has the account submenus (Codex, Claude Code and Grok), the provider status rows, **Refresh All**, **Pop Out Dashboard**, **Show Float Bar**, **Providers**, **Settings...**, **Check for Updates**, **About CodexBar** and **Quit**. +4. **Settings.** Settings opens from the tray menu and from the gear in the tray panel. Every sidebar pane opens. +5. **Theme.** In General, switch the theme between Automatic, Light and Dark. The open windows repaint. Automatic follows the Windows color mode. +6. **Global shortcut.** In General > Keyboard, record a shortcut, for example `Ctrl+Shift+K`. The shortcut opens and closes the tray panel from any app. +7. **Provider order.** Reorder providers in Settings, then restart the app. The tray panel and Settings keep the new order. +8. **Reset countdown.** A card shows "Resets in" with a time. The text updates within a minute; the timer ticks every 30 seconds. +9. **Updates.** In About, click **Check for Updates**. When an update exists, a banner appears at the top of the tray panel with buttons to download, view the release and dismiss. + +## Build and release checks + +Run these from the repo root. [BUILDING.md](./BUILDING.md) explains each one. ```powershell -powershell -ExecutionPolicy Bypass ` - -File .\scripts\windows-smoke-install.ps1 ` - -InstallerPath .\rust\target\installer\CodexBar--Setup.exe ` - -ExpectedVersion +.\scripts\local-check.ps1 -Slice ci +pnpm --dir apps/desktop-tauri run tauri:build +.\scripts\windows-release-build.ps1 -Ref vX.Y.Z -SmokeInstall +.\scripts\release-doctor.ps1 -Version X.Y.Z ``` -The script verifies the silent install switches -`/VERYSILENT /SUPPRESSMSGBOXES /NORESTART`, installed files, CLI `--help` and -`--version` output, Start Menu shortcut, uninstall registry entry, optional -version match, and silent uninstall cleanup. Add `-LeaveInstalled` when the VM -should keep the installed app for manual UI validation. - ---- - -## 2. Observable behaviours to verify on Windows - -After installing the built `.msi`, launch **CodexBar** and walk through -the list below. Check each item off in §4. - -1. **Tray icon visible** in the Windows notification area (may require - "Show all icons" in the taskbar settings on first run). -2. **Left-click the tray icon** → borderless tray panel anchored - near the tray. On multi-monitor setups it should appear on the - display hosting the tray. -3. **Right-click the tray icon** → native Windows context menu with - Pop Out Dashboard / Refresh / Settings / Check for updates / Quit entries. -4. **Preferences window** opens from the tray menu or from the tray panel - settings gear; all tabs render (General, Providers, Display, API - keys, Cookies, Token accounts, Advanced, About). -5. **Theme toggle** — Appearance → Theme cycles `Auto ↔ Light ↔ Dark` - and the active window repaints instantly. Auto should track the OS - setting (Windows Settings → Personalisation → Colors). -6. **Shortcut capture** — General → Global shortcut → Record, press - e.g. `Ctrl+Shift+K`. Chip shows the combo, `Saved` appears, and the - shortcut toggles the tray panel globally. -7. **Provider rows drag-reorder** in Preferences → Providers: grab the - handle, drop above/below another provider, refresh the tray panel, and - confirm the new order is persisted across app restarts. -8. **Chart tooltip** — open a provider detail → Cost/Credits chart. - Hovering a bar/point shows a tooltip with value + date. -9. **Reset countdown** — tray panel card shows `Resets in Xh Ym` and - re-renders at least once during a 1-minute stare (internal tick is - 30 s). -10. **Update banner** — Advanced → Check for updates; when an update is - offered the banner appears at the top of the tray panel and dismiss / - download / install-and-restart buttons all respond. - ---- - -## 3. Known platform limitations - -- **Work-area rect** on Linux hosts (X11/Wayland) returns the full - monitor size because the Tauri `monitor.work_area` helper does not - yet exclude panels. The production target is Windows, where - `SystemParametersInfoW(SPI_GETWORKAREA)` is correct. -- The following CLIs are **Windows-only** in this port and cannot be - exercised on macOS/Linux: DPAPI-protected browser cookie import - (Chrome/Edge), the single-instance lock via named mutex, and the - MSI-based auto-update channel. -- **`fSingleSessionPerUser`** (terminal-services policy): if the - post-install tray icon does not appear on a multi-user Windows - server, toggle - `HKLM\SYSTEM\CurrentControlSet\Control\Terminal Server\fSingleSessionPerUser=0` - on the host. This is **not** set by the installer — it is a host - policy decision, documented here only so operators can unblock - themselves. This repository must not ship a registry tweak for it. -- E2E browser automation (Playwright / WebDriver) is intentionally out - of scope for Phase 13; the `pnpm run test` suite covers unit-level - behaviour only. - ---- - -## 4. Machine-readable check-list - -Tick each entry as it is verified on the Windows target. +`tauri:build` makes no installer. The release builder makes the Inno Setup installer, the portable binary and the CLI zip. + +## Known limits + +- On Linux hosts, the Tauri work-area helper returns the whole monitor, because it doesn't exclude panels. On Windows, `SystemParametersInfoW(SPI_GETWORKAREA)` gives the correct work area. +- DPAPI cookie import, the single-instance lock and the installer-based update work only on Windows. +- On a multi-user Windows server, the tray icon may not appear after install. The host policy `HKLM\SYSTEM\CurrentControlSet\Control\Terminal Server\fSingleSessionPerUser` can cause this. Setting it to `0` on the host fixes it. The installer doesn't change it, and this repo must not ship a registry change for it. +- `pnpm test` runs unit tests in jsdom. It doesn't drive a real window. + +## Checklist ``` -[ ] build.cargo-fmt cargo fmt --all -[ ] build.cargo-clippy cargo clippy --all-targets -- -D warnings -[ ] build.cargo-test cargo test -[ ] build.tauri-clippy cargo clippy -p codexbar-desktop-tauri --all-targets -- -D warnings -[ ] build.tauri-test cargo test -p codexbar-desktop-tauri -[ ] build.pnpm-install cd apps/desktop-tauri && pnpm install --frozen-lockfile -[ ] build.pnpm-check-locale pnpm run check-locale -[ ] build.pnpm-test pnpm run test -[ ] build.pnpm-build pnpm run build -[ ] build.tauri-bundle pnpm run tauri:build (msi + nsis) -[ ] release.cached-build .\scripts\windows-release-build.ps1 -Ref +[ ] build.local-check-ci .\scripts\local-check.ps1 -Slice ci +[ ] build.tauri-build pnpm --dir apps/desktop-tauri run tauri:build +[ ] release.cached-build .\scripts\windows-release-build.ps1 -Ref -SmokeInstall [ ] release.doctor .\scripts\release-doctor.ps1 -Version [ ] runtime.tray-icon-visible [ ] runtime.tray-left-click-opens-panel [ ] runtime.tray-context-menu -[ ] runtime.preferences-opens -[ ] runtime.preferences-all-tabs +[ ] runtime.settings-opens +[ ] runtime.settings-all-panes [ ] runtime.theme-light [ ] runtime.theme-dark [ ] runtime.theme-auto-tracks-os [ ] runtime.shortcut-capture-records -[ ] runtime.shortcut-capture-toggles-panel -[ ] runtime.provider-drag-reorder +[ ] runtime.shortcut-toggles-panel +[ ] runtime.provider-reorder [ ] runtime.provider-order-persists -[ ] runtime.chart-tooltip-visible [ ] runtime.reset-countdown-ticks [ ] runtime.update-banner-flow -[ ] runtime.single-instance-mutex +[ ] runtime.single-instance ``` diff --git a/docs/adr/0001-pr-check-blacksmith-local-release.md b/docs/adr/0001-pr-check-blacksmith-local-release.md index 875cb7f9ae..8307a00926 100644 --- a/docs/adr/0001-pr-check-blacksmith-local-release.md +++ b/docs/adr/0001-pr-check-blacksmith-local-release.md @@ -1,19 +1,19 @@ # ADR 0001: PR check on Blacksmith Windows, release stays local Date: 2026-07-24 -Status: Accepted +Status: Superseded. ADR 0005 moved the hosted PR check to CircleCI Windows. ADR 0004 moved releases to GitHub Actions. ## Context Win-CodexBar had no hosted CI. Contributors ran `scripts/local-check.ps1` -before PRs, and the canonical release path was `scripts/windows-release-build.ps1` -on a local Windows server. We want hosted feedback on PRs without paying for +before opening a PR. Releases were built with `scripts/windows-release-build.ps1` +on a local Windows server. We wanted hosted feedback on PRs without paying for release-grade CI. ## Decision -Add a single PR check workflow (`.github/workflows/pr-check.yml`) that runs on -**Blacksmith Windows** (`blacksmith-4vcpu-windows-2025`). It mirrors only the +Add one PR check workflow, `.github/workflows/pr-check.yml`, that runs on +**Blacksmith Windows** (`blacksmith-4vcpu-windows-2025`). It runs only the local-check slice: - `cargo fmt --all --check` (workspace) @@ -23,14 +23,28 @@ local-check slice: - `pnpm test` and `pnpm run build` (which runs `tsc --noEmit`) in `apps/desktop-tauri` -Release stays **LOCAL**: no release workflow on Blacksmith. `tauri:build` -release, installer packaging, smoke install, and upload to GitHub Releases -continue to use `scripts/windows-release-build.ps1` on the operator's Windows -server, exactly as documented in `docs/BUILDING.md`. +Releases stay **local**. There is no release workflow on Blacksmith. The +operator builds the release with `tauri:build`, packages the installer, runs +the smoke install and uploads to GitHub Releases with +`scripts/windows-release-build.ps1` on their own Windows server, as +`docs/BUILDING.md` describes. ## Consequences -- PRs get hosted fmt/clippy/test feedback on the real Windows target. -- No release CI spend. Release remains a deliberate, operator-driven step. -- The Blacksmith Windows runner has VS Build Tools available, so the Rust + - Tauri toolchain works without extra provisioning. +- PRs get hosted fmt, clippy and test results on the real Windows target. +- Releases cost no CI time. A release is a deliberate step that the operator runs. +- The Blacksmith Windows runner has VS Build Tools, so the Rust and Tauri + toolchain works without extra setup. + +## Later changes + +Added 2026-10-12. The decision above is kept as history. + +- ADR 0005 (2026-08-30) moved the automatic hosted PR check to CircleCI + Windows. `pr-check.yml` still exists, but it now runs only by manual + dispatch, as a Blacksmith reserve. +- The local release path no longer applies. ADR 0004 records that + `.github/workflows/release.yml` builds, signs and drafts releases on + GitHub-hosted Windows when a `vX.Y.Z` tag is pushed. +- The check slice has grown. The current list of steps is in `CONTEXT.md` + under "Local check slice". diff --git a/docs/adr/0002-ci-budget-modes.md b/docs/adr/0002-ci-budget-modes.md index e1d7b0ae55..7d605f82f8 100644 --- a/docs/adr/0002-ci-budget-modes.md +++ b/docs/adr/0002-ci-budget-modes.md @@ -1,33 +1,47 @@ # ADR 0002: CI budget modes Date: 2026-07-24 -Status: Accepted +Status: Accepted. ADR 0005 replaced the PR check gate description, and the runner and release facts below are out of date. See "Later changes". ## Context -Win-CodexBar and its sister repo (`linear-cli`) share Blacksmith CI minute -budget. We need a single, coarse control to keep spend within the 60% -intent share and to stop runaway spend fast. Per-workflow toggles are too -fine-grained for an emergency stop. +Win-CodexBar and its sister repo, `linear-cli`, share a Blacksmith CI minute +budget. We need one coarse control to keep Win-CodexBar within its 60% share +and to stop runaway spend quickly. Per-workflow switches are too fine-grained +for an emergency stop. ## Decision -Introduce a repository variable `CI_BUDGET_MODE` with three modes: -`normal`, `thin`, and `off`. Unset is treated as `normal`. +Add a repository variable, `CI_BUDGET_MODE`, with three modes: `normal`, +`thin` and `off`. Unset means `normal`. -- **PR check** (`pr-check.yml`): runs when mode is unset, `normal`, or `thin`; - skips only when `off`. +- **PR check** (`pr-check.yml`) runs when the mode is unset, `normal` or + `thin`, and skips only when it is `off`. Gate: `if: vars.CI_BUDGET_MODE != 'off'` -- **Interaction guard** (`interaction-guard.yml`): same gate. Keeps its - existing `blacksmith-2vcpu-ubuntu-2404` runner. -- **Release**: unaffected — release stays local regardless of mode. +- **Interaction guard** (`interaction-guard.yml`) uses the same gate. It keeps + its existing `blacksmith-2vcpu-ubuntu-2404` runner. +- **Release** is not affected. Releases stay local in every mode. -`thin` is a no-op for Win-CodexBar's PR check because it is already a single -job (no cross-OS matrix to trim). The mode exists for parity with the sister -repo and as a future dial if a second job is added. +`thin` does nothing for Win-CodexBar's PR check, because the check is already +a single job with no cross-OS matrix to trim. The mode exists for parity with +the sister repo, and as a setting to use if a second job is added. ## Consequences -- One variable controls both workflows; `off` is an immediate, full stop. -- Operators set `CI_BUDGET_MODE` in repo Settings → Variables, not in code. -- The glossary in `CONTEXT.md` is the shared definition across both repos. +- One variable controls both workflows. `off` stops both at once. +- Operators set `CI_BUDGET_MODE` in repo Settings > Variables, not in code. +- The glossary in `CONTEXT.md` is the shared definition for both repos. + +## Later changes + +Added 2026-10-12. The decision above is kept as history. + +- ADR 0005 (2026-08-30) moved the automatic PR check to CircleCI. The CircleCI + `pr-check` job reads `CI_BUDGET_MODE` from CircleCI project environment + variables and halts when it is `off`. `pr-check.yml` is now manual dispatch + only and keeps the `vars.CI_BUDGET_MODE != 'off'` gate. +- The interaction guard moved to the GitHub-hosted `ubuntu-latest` runner in + #416 (`Optimize hosted CI usage`). It still uses the same budget gate. +- Releases are no longer local. ADR 0004 records that + `.github/workflows/release.yml` builds them on GitHub Actions. That workflow + does not read `CI_BUDGET_MODE`. diff --git a/docs/adr/0003-multi-account-codex.md b/docs/adr/0003-multi-account-codex.md index 990035dcb3..108d99c7da 100644 --- a/docs/adr/0003-multi-account-codex.md +++ b/docs/adr/0003-multi-account-codex.md @@ -1,34 +1,34 @@ # ADR 0003: Multi-account Codex coexistence with ambient provider Date: 2026-08-06 -Status: Accepted +Status: Accepted. The tray-flyout parts of PR 5/5 were superseded by #813. See "Later changes". ## Context -CodexBar today surfaces exactly one `codex` provider snapshot. That snapshot is -produced by the ambient single-account Codex provider in -`rust/src/providers/codex/`: it reads the identity that happens to be active in -`~/.codex` (`auth.json`), calls `fetch_usage`, and publishes the result through -the standard provider pipeline to the tray, flyout, and settings surfaces. +CodexBar shows exactly one `codex` provider snapshot. The ambient +single-account Codex provider in `rust/src/providers/codex/` produces it. The +provider reads whichever identity is active in `~/.codex` (`auth.json`), calls +`fetch_usage`, and publishes the result through the standard provider pipeline +to the tray, the flyout and Settings. -A stacked 5-PR series ports the MIT-licensed Windows core of +A stacked series of 5 PRs ports the MIT-licensed Windows core of [`ademisler/codexcontrol`](https://github.com/ademisler/codexcontrol) (Windows -Python modules) into Rust as `rust/src/codex_accounts/`: +Python modules) to Rust as `rust/src/codex_accounts/`: -- **PR 1/5 — domain core**: account model (`CodexAccount`, - `CodexAccountSource::Ambient | Managed`), ambient vs app-managed +- **PR 1/5, domain core.** The account model (`CodexAccount`, + `CodexAccountSource::Ambient | ManagedByApp`), ambient vs app-managed `CODEX_HOME` discovery (`file_locations`), per-account quota snapshots, account stores, login runner, and Codex Desktop (MSIX) session switching. -- **PR 2/5 — shell**: Tauri commands exposing discovery/login/switch/remove, +- **PR 2/5, shell.** Tauri commands exposing discovery/login/switch/remove, plus managed-account refresh lanes that fetch and cache a snapshot per managed account in parallel. -- **PR 3/5 — frontend bridge**: TS types, `tauri.ts` wrappers, i18n keys. -- **PR 4/5 — settings panel**: `CodexAccountsSection` in provider settings. -- **PR 5/5 — tray menu**: `CodexAccountsMenu` flyout. +- **PR 3/5, frontend bridge.** TS types, `tauri.ts` wrappers, i18n keys. +- **PR 4/5, settings panel.** `CodexAccountsSection` in provider settings. +- **PR 5/5, tray menu.** The `CodexAccountsMenu` flyout. -The new module therefore manages *many* accounts while the existing provider -pipeline still models *one* `codex` provider. Both must work while the port -lands in reviewable slices. +The new module manages *many* accounts, while the provider pipeline still +models *one* `codex` provider. Both must work while the port lands in +reviewable slices. ## Decision @@ -46,12 +46,12 @@ lands in reviewable slices. - No existing provider, surface, or persistence path is rewired in this series; the ambient provider keeps publishing exactly as before. -**Replacement intent** (tracked as part of this ADR decision, not executed in -this series): the ambient provider path retires in a follow-up major release -once `codex_accounts` reaches feature parity against it — i.e. the managed -lanes match ambient `fetch_usage` behavior *and* local cost scans are migrated -onto the managed-home model. Retirement is a separate change with its own -review; this series only prepares the ground. +**Replacement intent.** This ADR records the intent, but this series does not +carry it out. The ambient provider path retires in a later major release, once +`codex_accounts` reaches feature parity with it. Parity means the managed lanes +match ambient `fetch_usage` behavior *and* local cost scans move to the +managed-home model. Retirement is a separate change with its own review. This +series only prepares for it. ## Consequences @@ -83,3 +83,18 @@ review; this series only prepares the ground. clobbering them: copying on unknown freshness is exactly how a stale ambient token turned working accounts into dead ones, which is the duplicate-home failure mode this policy exists to prevent. + +## Later changes + +Added 2026-10-12. The decision above is kept as history. + +- The enum variant is `CodexAccountSource::ManagedByApp` + (`rust/src/codex_accounts/models.rs`). Earlier text called it `Managed`. +- The ambient provider has not retired. `rust/src/providers/codex/` still + publishes the single `codex` snapshot. +- #813 (`feat/mac-card-anatomy`) removed the React `CodexAccountsMenu` and + `ClaudeAccountsMenu` flyouts from the tray panel. A Codex card now ends with + a "Switch Account..." or "Add Account..." row that opens Settings > + Providers, where account switching lives. The tray icon's right-click menu + still has native "Codex Accounts", "Claude Code accounts" and "Grok + accounts" submenus (`apps/desktop-tauri/src-tauri/src/tray_accounts.rs`). diff --git a/docs/adr/0004-circleci-release-trust-boundary.md b/docs/adr/0004-circleci-release-trust-boundary.md index 7350147417..b48b172e5b 100644 --- a/docs/adr/0004-circleci-release-trust-boundary.md +++ b/docs/adr/0004-circleci-release-trust-boundary.md @@ -1,20 +1,22 @@ # ADR 0004: GitHub Actions release and CircleCI validation boundary Date: 2026-09-16 -Status: Accepted; supersedes the release portion of the previous ADR 0004 +Status: Accepted. This version replaces the release part of the first ADR 0004 +(dated 2026-08-14, #285), which put tag releases on CircleCI. That first version +superseded the local-release decision in ADR 0001. ## Context -Win-CodexBar needs reproducible hosted Windows validation and a release path -whose build provenance can be verified by SignPath. SignPath's GitHub trusted -build integration requires the build, the uploaded signing artifact, and the -jobs leading to signing to be observed as GitHub Actions work on GitHub-hosted -runners. CircleCI is retained for ordinary PR and protected-branch validation, -but it cannot be the producer of the SignPath release artifact. +Win-CodexBar needs reproducible hosted Windows checks and a release path whose +build origin SignPath can verify. SignPath's GitHub trusted-build integration +must see the build, the uploaded signing artifact and the jobs before signing +run as GitHub Actions work on GitHub-hosted runners. CircleCI stays for +ordinary PR and protected-branch checks, but it cannot produce the artifact +that SignPath signs. -Release credentials must remain unavailable to validation and build steps that -do not need GitHub release write access. v0.60.3 was released before this -change and is immutable. +Validation and build steps that don't need GitHub release write access must +not get release credentials. v0.60.3 was released before this change and is +immutable. ## Decision @@ -72,3 +74,14 @@ own GitHub-hosted cache strategy, but the final release assets are always built fresh from the frozen tag SHA and then signed. A cached output is never accepted as the final signed artifact without the full post-sign verification and manifest generation steps. + +## Later changes + +Added 2026-10-12. No change to the decision. + +- The build step in `release.yml` runs `scripts/circleci-release-build.ps1`. + The script name comes from the CircleCI era. GitHub Actions runs it on + `windows-latest`. +- The six release assets are `CodexBar--Setup.exe`, + `CodexBar--portable.exe` and `CodexBarCLI-v-windows-x64.zip`, + each with a `.sha256` file. diff --git a/docs/adr/0005-pr-check-moves-to-circleci-windows.md b/docs/adr/0005-pr-check-moves-to-circleci-windows.md index 92283e2bcc..dc63723b25 100644 --- a/docs/adr/0005-pr-check-moves-to-circleci-windows.md +++ b/docs/adr/0005-pr-check-moves-to-circleci-windows.md @@ -1,104 +1,133 @@ # ADR 0005: Hosted PR check moves to CircleCI Windows Date: 2026-08-30 -Status: Accepted; supersedes the hosted-runner decision of ADR 0001 and the -Blacksmith gate description in ADR 0002 +Status: Accepted. Supersedes the hosted-runner decision of ADR 0001 and the +Blacksmith gate description in ADR 0002. ## Context -The shared Blacksmith free-tier minute pool is exhausted, so the Blacksmith -GitHub Actions runners behind ADR 0001's PR check -(`.github/workflows/pr-check.yml`) can no longer be relied on for scheduled -PR validation. CircleCI is already this repo's hosted Windows platform for -releases (ADR 0004), with a working `circleci/windows@5.0` configuration, and -the repository is public, so its PR builds draw on CircleCI's Free Plan -open-source allowance rather than the personal credit block. +The shared Blacksmith free-tier minute pool is used up. The Blacksmith GitHub +Actions runners behind ADR 0001's PR check (`.github/workflows/pr-check.yml`) +can no longer run scheduled PR checks reliably. + +CircleCI is already this repo's hosted Windows platform for releases (ADR 0004 +as it stood on this date), with a working `circleci/windows@5.0` +configuration. The repository is public, so its PR builds use CircleCI's Free +Plan open-source allowance, not the personal credit block. ## Decision -Move the hosted PR/push gate to CircleCI as a new `pr-check` job in -`.circleci/config.yml` (workflow `pr-check`): - -- `win/default` executor, `size: medium`. The check steps are not re-declared - in the config: the provisioning and gate logic is extracted into scripts - that the config calls, and the whole check delegates to - `scripts/local-check.ps1 -Slice ci`, an opt-in slice that mirrors - `.github/workflows/pr-check.yml` step for step (`cargo fmt --all --check`, - `cargo clippy --workspace --all-targets -- -D warnings`, `cargo test - --workspace`, `pnpm install --frozen-lockfile`, `pnpm test`, `pnpm run - build`, plus the interaction-guard script tests). The script's default - (no-parameter) local behavior is unchanged. - - `scripts/circleci-pr-gates.ps1` owns the three skip decisions that used - to live inline in the config (budget `off`, non-PR branch pushes other - than main/master and `gh-readonly-queue/` merge-queue pushes, docs-only PR - diffs); each true skip calls `circleci-agent step - halt`. - - `scripts/run-circleci-pr-check.ps1` owns toolchain provisioning with - official checksum verification: `rustup-init.exe` is downloaded from - static.rust-lang.org and verified against the official adjacent - `.sha256` file; the Node 24.18.0 x64 MSI is verified against the - official `SHASUMS256.txt` entry before `msiexec` runs (into a dedicated - per-version `INSTALLDIR`, because a same-product MSI upgrade silently - no-ops); pnpm is activated by corepack from the exact `packageManager` - pin in `apps/desktop-tauri/package.json`. - - Pure checksum/gate logic lives in `scripts/circleci-pr-common.ps1` and - is exercised offline by `scripts/circleci-pr.tests.ps1`. - - The pinned Rust version is read from - `scripts/circleci-pinned-rust.txt`; the Cargo target cache keys embed - `{{ checksum "scripts/circleci-pinned-rust.txt" }}` so a Rust pin bump - invalidates the target cache (no pipeline parameter, no config edit). - - The config depends on compile-time GitHub App PR pipeline values: - `pipeline.event.context.github.pr_url` and - `pipeline.event.github.pull_request.base.sha` (populated only on pull - request events; empty on push pipelines). They come from CircleCI's - GitHub App integration, so fork-PR pipelines are never built and a - manual same-repo branch fallback is needed for external contributors' - changes. -- Trigger: the workflow filter stays wide (`branches: only: /.*/`) because - CircleCI delivers same-repo PR builds as branch pipelines; parity with the - GitHub workflow (PRs plus pushes to `main`/`master`, `paths-ignore` for - `docs/**`, `**/*.md`, `CONTEXT.md`, `.github/CI.md`) is enforced inside the - job by early gates. Each true skip condition logs its reason and then calls - `circleci-agent step halt`, which stops the entire job — before the cache - restore steps and the fused provision/run step — so a skip spends no cache - or toolchain minutes (the throw on a non-zero `circleci-agent` exit code - prevents a silent no-op). Docs-only detection diffs the base commit - against the head with a two-dot tree diff (robust when the base is fetched - at depth 1): on PRs the base SHA comes primarily from the compile-time - GitHub App pipeline value `pipeline.event.github.pull_request.base.sha` - (populated on pull request events); if that is empty while the pipeline is - still PR-associated, the base SHA is resolved from the public GitHub pulls - API using the documented `CIRCLE_PROJECT_USERNAME`/`CIRCLE_PROJECT_REPONAME` - variables and fetched with `--depth=1`. Docs-only evaluation applies only - to PR-associated pipelines; every `main`/`master` push and every - `gh-readonly-queue/` merge-queue push runs the full checks. Any resolution, fetch, or diff failure on a PR pipeline fails - open: it exits only the gate step and therefore continues the job - into the checks; the gate never silently skips on an unknown base. -- Budget gating stays honest and coarse: the job reads `CI_BUDGET_MODE` as a - CircleCI project environment variable (unset/empty = `normal`) and halts - the job via `circleci-agent step halt` when it equals `off`. - -`.github/workflows/pr-check.yml` is retained as a manual-dispatch-only -fallback: its `on:` block now holds `workflow_dispatch` only (push and -`pull_request` triggers removed), so it no longer schedules automatically and -can be run by hand for Blacksmith diagnostics with the job body intact. The -interaction guard (`.github/workflows/interaction-guard.yml`) remains a -GitHub Actions workflow and is unaffected. +Move the hosted PR and push check to CircleCI, as a new `pr-check` job in +`.circleci/config.yml` (workflow `pr-check`). + +### What the job runs + +- The job uses the `win/default` executor with `size: medium`. +- The config doesn't repeat the check steps. It calls scripts, and the check + itself is `scripts/local-check.ps1 -Slice ci`. This opt-in slice runs the + same steps as `.github/workflows/pr-check.yml`: `cargo fmt --all --check`, + `cargo clippy --workspace --all-targets -- -D warnings`, + `cargo test --workspace`, `pnpm install --frozen-lockfile`, `pnpm test`, + `pnpm run build`, and the interaction-guard script tests. Running the script + with no parameters still does what it did before. +- `scripts/circleci-pr-gates.ps1` owns the three skip decisions that used to + be inline in the config: + 1. the budget mode is `off`; + 2. the pipeline is a branch push other than `main`, `master` or a + `gh-readonly-queue/` merge-queue push; + 3. the PR changes only docs. + + Each real skip calls `circleci-agent step halt`. +- `scripts/run-circleci-pr-check.ps1` installs the toolchain and verifies each + download against its official checksum: + - It downloads `rustup-init.exe` from static.rust-lang.org and checks it + against the official `.sha256` file next to it. + - It checks the Node 24.18.0 x64 MSI against the official `SHASUMS256.txt` + entry before `msiexec` runs. The MSI installs into its own per-version + `INSTALLDIR`, because an upgrade of the same product silently does nothing. + - corepack activates pnpm from the exact `packageManager` pin in + `apps/desktop-tauri/package.json`. +- `scripts/circleci-pr-common.ps1` holds the pure checksum and gate logic. + `scripts/circleci-pr.tests.ps1` tests it offline. +- `scripts/circleci-pinned-rust.txt` holds the pinned Rust version. The Cargo + target cache keys include `{{ checksum "scripts/circleci-pinned-rust.txt" }}`, + so bumping the Rust pin invalidates the target cache. No pipeline parameter + or config edit is needed. +- The config uses two compile-time PR values from CircleCI's GitHub App + integration: `pipeline.event.context.github.pr_url` and + `pipeline.event.github.pull_request.base.sha`. They are set only on pull + request events and are empty on push pipelines. Because they come from the + GitHub App, CircleCI never builds fork PRs. External contributors' changes + need a manual fallback on a same-repo branch. + +### Triggers and skips + +- The workflow filter stays wide (`branches: only: /.*/`), because CircleCI + delivers same-repo PR builds as branch pipelines. Early gates inside the job + give the same result as the GitHub workflow: PRs plus pushes to `main` and + `master`, skipping `docs/**`, `**/*.md`, `CONTEXT.md` and `.github/CI.md`. +- Each real skip logs its reason and calls `circleci-agent step halt`. This + stops the whole job before the cache restore and the provision-and-run step, + so a skip spends no cache or toolchain minutes. A non-zero exit from + `circleci-agent` throws, so a failed halt can't pass silently. +- The docs-only check diffs the base commit against the head with a two-dot + tree diff. This works when the base is fetched at depth 1. + - The base SHA comes from `pipeline.event.github.pull_request.base.sha`. + - If that value is empty but the pipeline is still tied to a PR, the gate + looks up the base SHA in the public GitHub pulls API, using + `CIRCLE_PROJECT_USERNAME` and `CIRCLE_PROJECT_REPONAME`, and fetches it + with `--depth=1`. + - Only PR pipelines get the docs-only check. Every `main` or `master` push + and every `gh-readonly-queue/` merge-queue push runs the full check. + - If resolving, fetching or diffing fails on a PR pipeline, the gate fails + open. It ends only the gate step, and the job runs the checks. The gate + never skips on an unknown base. +- The job reads `CI_BUDGET_MODE` as a CircleCI project environment variable + (unset or empty means `normal`) and halts with `circleci-agent step halt` + when it is `off`. + +### The GitHub workflows + +`.github/workflows/pr-check.yml` stays as a manual fallback. Its `on:` block +now holds only `workflow_dispatch`, with the push and `pull_request` triggers +removed. It no longer runs on its own, and a maintainer can run it by hand for +Blacksmith diagnostics. The job body is unchanged. The interaction guard +(`.github/workflows/interaction-guard.yml`) stays a GitHub Actions workflow +and doesn't change. ## Consequences - Auto-cancel of superseded pushes is a CircleCI **project setting** - ("Auto-cancel redundant workflows", Project Settings → Advanced), keyed by - branch the same way the GitHub workflow's `concurrency` group was; the - `pr-check` job carries no `concurrency` YAML. -- Hosted PR feedback continues on the real Windows target with no Blacksmith - dependency. -- `CI_BUDGET_MODE` must now be set in two places to control both surfaces: - CircleCI project environment variables (read by the `pr-check` guard) and + ("Auto-cancel redundant workflows", Project Settings > Advanced). It is keyed + by branch, like the GitHub workflow's `concurrency` group was. The `pr-check` + job has no `concurrency` YAML. +- Hosted PR results still come from the real Windows target, with no + dependency on Blacksmith. +- `CI_BUDGET_MODE` must now be set in two places to control both services: + CircleCI project environment variables (read by the `pr-check` gate) and GitHub Actions repository variables (read by `vars.CI_BUDGET_MODE`). The glossary in `CONTEXT.md` documents both. -- CircleCI Windows credits become the recurring PR cost, spent against the - open-source allowance; organization credit alerts should cover the PR check +- CircleCI Windows credits become the recurring PR cost, charged to the + open-source allowance. Organization credit alerts should cover the PR check as well as releases. -- ADRs 0001 and 0002 remain as immutable history describing the Blacksmith - era; this ADR supersedes only where the hosted PR check runs. +- ADRs 0001 and 0002 stay as unchanged history of the Blacksmith period. This + ADR supersedes them only on where the hosted PR check runs. + +## Later changes + +Added 2026-10-12. The decision above is kept as history. + +- Releases left CircleCI. ADR 0004 was rewritten on 2026-09-16: GitHub Actions + (`.github/workflows/release.yml`) now builds, signs and drafts releases. + CircleCI ignores release tags, so its credits cover only the PR check. +- The CircleCI workflow no longer uses a `branches: only: /.*/` filter. It has + a `when:` condition that runs on pushes and on PRs, except PRs whose base + branch starts with `port/upstream-`. The job ignores `vX.Y.Z` tags. +- The docs-only check matches each changed path against + `^(docs/.*|.*\.md)$`, which also covers `CONTEXT.md` and `.github/CI.md`. +- There is no same-repo branch fallback for fork PRs. Maintainers validate a + fork PR locally in a disposable, credential-free environment and merge it + with an admin override. `.github/CI.md` under "Fork pull requests" has the + steps. +- The `-Slice ci` check now has 11 steps, including oxlint and the anti-slop + checks. `CONTEXT.md` under "Local check slice" lists them. diff --git a/docs/adr/0006-dependency-consolidation-candidates.md b/docs/adr/0006-dependency-consolidation-candidates.md index df45f9f388..17c538ba5a 100644 --- a/docs/adr/0006-dependency-consolidation-candidates.md +++ b/docs/adr/0006-dependency-consolidation-candidates.md @@ -5,15 +5,14 @@ Status: Proposed ## Context -Win-CodexBar has grown across roughly 70 providers, a Rust backend, a Tauri 2 -shell, and a React frontend. The codebase now contains several places where the -same domain information or infrastructure policy is expressed repeatedly. -Adding dependencies is only worthwhile when a crate removes measurable -boilerplate, reduces synchronization points, or materially improves -correctness. This ADR records candidates for review before implementation. It -does not approve adding every crate listed below. +Win-CodexBar has about 70 providers, a Rust backend, a Tauri 2 shell and a +React frontend. In several places the code repeats the same domain information +or infrastructure policy. A new dependency is worth adding only when it removes +measurable boilerplate, removes places that must be kept in sync, or fixes a +real correctness problem. This ADR lists candidates to review before any +implementation. It does not approve adding every crate below. -The September 2026 audit found these concrete pressure points: +The September 2026 audit found these problems: - `rust/src/core/provider.rs` is 1,131 lines. `ProviderId` has 70 variants, with repeated handwritten tables for iteration, CLI names, display names, cookie @@ -243,3 +242,14 @@ Reviewers should challenge each candidate with the same questions: - This ADR is intentionally `Proposed`; accepting it does not automatically authorize every Tier 1 or Tier 2 dependency. Each implementation PR still needs its own measured justification and tests. + +## Later changes + +Added 2026-10-12. The status is still `Proposed`. + +- None of the 15 candidate crates is a direct dependency yet. None is listed in + `rust/Cargo.toml`, `apps/desktop-tauri/src-tauri/Cargo.toml` or the root + `Cargo.toml`. `reqwest` is still on 0.12. +- The audit numbers above are from September 2026. On 2026-10-12, + `rust/src/core/provider.rs` is 1,997 lines and `ProviderId` has 96 variants + (the `ProviderId::all()` test asserts 96).