docs: surface the Windows menubar

README gains a Windows section next to the macOS and GNOME ones, a download
badge on the menubar card, and an honest note that the Tauri tray builds on
Linux but is unreleased there. docs/architecture.md picks up windows/ in the
surfaces diagram and gets a section covering the crate layout, the PATH and
System32 spawn rules, and the Claude quota parity.

`codeburn menubar` on Windows now points at the windows-v release page instead
of failing with "macOS only". The generalized installer from the source branch
is not brought over: it is 1182 commits behind this file and would drop the
proxy support, retry/backoff, checksum and bundle verification, and persistent
CLI path handling that landed since.
This commit is contained in:
iamtoruk 2026-08-18 04:14:05 -07:00
parent 120747d2fa
commit 562bab65ba
4 changed files with 58 additions and 21 deletions

View file

@ -7,6 +7,9 @@
- **Applied fixes get re-measured on every `optimize` run, and told plainly whether they worked.** After `codeburn optimize --apply`, every still-applied fix comes back in an `Applied fixes` section on subsequent `codeburn optimize` runs, carrying the verdict `act report` already computes from the same reconciliation: `worked` (at least 70% of its window-scaled estimate realized), `partial` (something, but under that), `no-effect` (no measured reduction, printed with the exact `codeburn act undo <id>` that puts it back), or `measuring` for anything younger than the 3-day measurement window. The numbers are measured — provider-counted usage over the post-apply window — not re-estimated. `--apply` now says when the re-measure will happen, `--format json` gains `appliedFixes[]` (add-only), and the same section appears in the dashboard TUI and the desktop app. New `codeburn optimize --auto-revert` undoes the fixes that measured no reduction at all through the same code path as `codeburn act undo`; it never touches `partial` or still-measuring fixes, and never auto-reverts a `CLAUDE.md` rule (it prints the undo command instead), matching the `--yes` guardrail.
- **Optimize findings say what to do with them and where their number came from.** Every finding now carries a class and a basis, and every surface groups by it: `Fix now (apply-able)` for findings `codeburn optimize --apply` can write itself, `Habits` for the behavioural ones, `FYI` for informational ones whose cost may be justified. A finding only counts as apply-able when a plan can actually be built for that instance, so an `mcp-deferral-off` caused by Vertex policy or a shell-profile override is grouped as a habit rather than promising a fix that does not exist. Alongside it, each finding is marked `measured` (summed from provider-counted usage) or `estimated` (a schema-size or recovery-fraction model), with the split reported in the header as `N measured · M estimated` in place of the blanket "Estimates only." footer. Sessions whose cost the provider never reported are kept out of the `cost-outliers` peer comparison, and a provider that only ever estimates gets the finding marked `estimated` rather than dropped. `--format json` gains `class` and `basis` per finding plus `summary.measuredSavingsUSD` (existing fields unchanged), and the new `docs/optimize.md` covers what is scanned, exactly what `--apply` may write, and how to read the health grade.
### Added (Windows)
- **A menubar app for Windows.** `windows/` is a Tauri 2 tray app — Rust binary, React popover — that puts today's spend in the notification area and mirrors the macOS menubar screen for screen: agent tabs, period switcher, Trend, Forecast, Pulse, Stats and Plan insights, activity and model breakdowns, optimize findings, CSV/JSON export, launch at login, currency, and theme. Windows has no menubar title, so the number lives in a second tray icon rendered from the system font at the panel's native icon size (Settings can turn it off; the tooltip always carries it). It reads everything through the CLI like the macOS and GNOME clients do, and gates on **codeburn 0.9.9 or newer** — the first release accepting `status --format menubar-json --no-optimize` — showing a setup screen with the install command until it finds one. Refresh follows popover visibility the way the macOS app does: 60 s with optimize findings while open, 2 minutes for today's total while closed, and immediately on open when what you are looking at has gone stale. The Claude quota view never spends Claude's single-use refresh token; on a 401 it re-reads Claude Code's own credential file for a token it has already rotated, matching the macOS client. Ships as an unsigned `.msi` from the `windows-v*` tag; `codeburn menubar` on Windows points at that release page. The same crate still builds and runs a tray on Linux, but that stays experimental and unreleased — `gnome/` is the supported Linux surface.
### Added (CLI)
- **DeepSeek Harness (`dsh`) is now a supported provider.** Reads DeepSeek's open-source agent harness from `~/.dsh/sessions` (`DSH_HOME` relocates the root), both the default zstd logs and the uncompressed `session.jsonl` variant. A `.zstd` log is a concatenation of independent zstd frames, one per write batch, so it is decoded frame by frame behind a structural frame scan and a torn trailing frame from a crashed writer is ignored rather than failing the file (needs Node 22.15+ for `zlib` zstd; below that dsh is skipped with a notice instead of counted as $0). One call per `(turn, step)`, with the step's final `assistant/message` usage superseding the streamed `assistant/chunk` sample of the same call rather than adding to it, the model taken from the message that served the step, and reasoning tokens billed at the output rate. DSH records tokens but no cost, so calls are priced from the shared tables. The events a forked session replays from its parent are skipped, since codeburn already counts the parent's own log. The session format is pinned at version 0 upstream with no compatibility implied, so a log stamped with any other version is skipped with a notice instead of read under today's assumptions.

View file

@ -52,9 +52,10 @@
<code>npx codeburn</code>
</td>
<td align="center" width="50%">
<strong>macOS Menubar</strong><br/>
<strong>Menubar</strong><br/>
<img src="https://raw.githubusercontent.com/getagentseal/codeburn/main/assets/menubar-app.jpg" alt="CodeBurn macOS menubar" /><br/>
<code>codeburn menubar</code>
<code>codeburn menubar</code><br/>
<a href="https://github.com/getagentseal/codeburn/releases/tag/windows-v0.9.20"><img src="https://img.shields.io/badge/Windows-Tray_app_(.msi)-F97316?logo=windows&logoColor=white" alt="Download the CodeBurn Windows menubar" /></a>
</td>
</tr>
</table>
@ -107,7 +108,7 @@ Also runs via `bunx codeburn` or `pnpm dlx codeburn`, or `brew install codeburn`
codeburn menubar
```
On Linux, a GNOME Shell extension gives the same panel view; see [Linux (GNOME)](#linux-gnome).
On Windows the same view is a tray app; see [Windows](#windows). On Linux, a GNOME Shell extension gives it in the top panel; see [Linux (GNOME)](#linux-gnome).
Requires **Node.js 22.13+** and at least one supported tool with session data on disk. For Cursor and OpenCode, `better-sqlite3` installs automatically.
@ -288,6 +289,8 @@ Pairing is PIN-authorized and stays on your local network. You can also discover
## Menu bar
### macOS
```bash
codeburn menubar
```
@ -328,6 +331,14 @@ defaults write org.agentseal.codeburn-menubar CodeBurnPreferredTerminal -string
Allowed values are `terminal` (macOS Terminal.app, the default) and `iterm2`. Anything else falls back to `terminal`. Only terminals that can script a command into a live window are offered; if the chosen app is missing or fails to accept the command, CodeBurn tries Terminal.app and then runs the command in the background, logging each step to Console.app. Takes effect on the next launch of a command, no relaunch needed.
### Windows
Windows gets the same ambient view from the system tray. Download the `.msi` from the [latest Windows Menubar release](https://github.com/getagentseal/codeburn/releases/tag/windows-v0.9.20) and run it; `codeburn menubar` on Windows just points you at that page.
Today's spend sits in the tray as a number beside the flame icon (turn it off in Settings, and the tooltip always carries it). Click for the same popover the macOS app shows: agent tabs, period switcher, Trend, Forecast, Pulse, Stats and Plan insights, activity and model breakdowns, optimize findings, and CSV/JSON export. Settings covers launch at login, the tray number, theme, and currency. It refreshes every 60 seconds while the popover is open and every 2 minutes while it is closed.
The tray app reads everything through the CLI, so install that first (`npm install -g codeburn`) — it needs **codeburn 0.9.9 or newer**, and shows a setup screen with the install command until it finds one. Source and build instructions are in [`windows/`](windows/) ([windows/DEVELOPMENT.md](windows/DEVELOPMENT.md)). The `.msi` is unsigned for now, so SmartScreen prompts on first run.
### Linux (GNOME)
Linux gets the same ambient view through a GNOME Shell extension (GNOME 45+): spend in the top panel, period switcher, compact mode, and daily budget alerts. It lives in [`gnome/`](gnome/):
@ -338,7 +349,7 @@ git clone https://github.com/getagentseal/codeburn && cd codeburn/gnome
gnome-extensions enable codeburn@codeburn.dev
```
See [gnome/README.md](gnome/README.md) for settings and development notes. On Windows, `codeburn web` is the always-on view for now.
See [gnome/README.md](gnome/README.md) for settings and development notes. The Tauri tray app in `windows/` also builds and runs on Linux, but it is experimental and unreleased there — the GNOME extension is the supported Linux surface.
## CodeBurn in your agent (MCP)

View file

@ -4,27 +4,27 @@ A map of the codebase. Read this once before opening a non-trivial PR.
## Three Surfaces
CodeBurn is one Node.js CLI plus two GUI clients that shell out to it.
CodeBurn is one Node.js CLI plus three ambient GUI clients that shell out to it.
```
+----------------------+ +-----------------+
| mac/ (Swift) | ---> | |
+----------------------+ | src/cli.ts |
| gnome/ (JavaScript) | ---> | (the CLI) |
+----------------------+ | |
| status |
| --format |
| menubar-json |
+-----------------+
|
v
+----------------------------+
| session files on disk |
| (JSONL, SQLite, protobuf) |
+----------------------------+
+---------------------------+ +-----------------+
| mac/ (Swift) | ---> | |
+---------------------------+ | src/cli.ts |
| windows/ (Rust + React) | ---> | (the CLI) |
+---------------------------+ | |
| gnome/ (JavaScript) | ---> | status |
+---------------------------+ | --format |
| menubar-json |
+-----------------+
|
v
+----------------------------+
| session files on disk |
| (JSONL, SQLite, protobuf) |
+----------------------------+
```
The macOS menubar (`mac/`) and the GNOME extension (`gnome/`) both invoke `codeburn status --format menubar-json --period <p>` and parse the JSON. They do not share code with the CLI; they only depend on its output contract.
The macOS menubar (`mac/`), the Windows tray app (`windows/`), and the GNOME extension (`gnome/`) all invoke `codeburn status --format menubar-json --period <p>` and parse the JSON. They do not share code with the CLI; they only depend on its output contract.
## CLI (`src/`)
@ -217,6 +217,20 @@ Tests live in `mac/Tests/CodeBurnMenubarTests/` (currently `CapacityEstimatorTes
The build artifact is a zipped `.app` bundle produced by `mac/Scripts/package-app.sh`. See `RELEASING.md` for how the GitHub Actions workflow uses it.
## Windows Menubar (`windows/`)
Tauri 2 app: a Rust binary (`windows/src-tauri/`) owning the tray and the process spawning, plus a React + TypeScript popover (`windows/src/`) rendered in a WebView2 window. Design tokens come from `windows/tokens.json`, the same file `mac/` reads at build time, so both products render as one.
- `src-tauri/src/lib.rs` builds the tray, positions the popover against the taskbar edge, and registers the `#[tauri::command]` surface the frontend calls.
- `src-tauri/src/cli.rs` resolves and spawns the CLI. Only absolute `PATH` directories are searched (an empty entry from `;;` would otherwise resolve against the current directory), `CODEBURN_BIN` is allowlisted, and Windows system tools are spawned by absolute `%SystemRoot%\System32` path because `CreateProcess` searches the current directory first. `MIN_CLI_VERSION` gates the whole app; below it the popover shows a setup screen.
- `src-tauri/src/plan.rs` ports the Claude quota view. Like the macOS `ClaudeCredentialStore`, it never spends Claude's single-use refresh token; on a 401 it re-reads Claude's own credential file for a token Claude Code has already rotated.
- `src-tauri/src/tray_badge.rs` renders today's spend into a second tray icon, since Windows has no menubar title.
- `src/App.tsx` owns the payload cache, the CLI gate, and the refresh cadence, which follows popover visibility the way `mac/`'s `RefreshCadence.swift` does.
`cargo test` covers the PATH filter and the version gate. `windows/DEVELOPMENT.md` has the build, security, and release details; CI is `.github/workflows/windows-menubar-ci.yml` and releases go out on `windows-v*` tags.
The Linux (ksni) paths in the same crate are kept compiling but are experimental and unreleased; `gnome/` is the shipping Linux surface.
## GNOME Extension (`gnome/`)
Plain JavaScript, no bundler. Targets GNOME Shell 45-50 (`metadata.json`).

View file

@ -22,6 +22,9 @@ const EXPECTED_BUNDLE_ID = 'org.agentseal.codeburn-menubar'
const VERSIONED_ASSET_PATTERN = /^CodeBurnMenubar-v.+\.zip$/
const APP_PROCESS_NAME = 'CodeBurnMenubar'
const SUPPORTED_OS = 'darwin'
/// The Windows tray app (windows/) is released as an .msi under its own tag, so this command
/// points at it rather than pretending to install anything.
const WINDOWS_RELEASE_PAGE = 'https://github.com/getagentseal/codeburn/releases?q=windows-v'
const MIN_MACOS_MAJOR = 14
const PERSISTED_CLI_PATH = join(homedir(), 'Library', 'Application Support', 'CodeBurn', 'codeburn-cli-path.v1')
const PERSISTENT_CLI_REQUIRED_MESSAGE =
@ -184,6 +187,12 @@ async function exists(path: string): Promise<boolean> {
}
async function ensureSupportedPlatform(): Promise<void> {
if (platform() === 'win32') {
throw new Error(
'The Windows menubar ships as an installer, not through this command. ' +
`Download the .msi from ${WINDOWS_RELEASE_PAGE} and run it.`,
)
}
if (platform() !== SUPPORTED_OS) {
throw new Error(`The menubar app is macOS only (detected: ${platform()}).`)
}