Commit graph

42 commits

Author SHA1 Message Date
AgentSeal
888f592bd2 Make daily cache durable: hydrate from all commands, migrate instead of nuke
- Extract ensureCacheHydrated() from menubar-json path into daily-cache.ts
- Call it from every command that parses sessions (report, status, today,
  month, export, optimize, compare, yield) so CLI-only users also persist
  historical data that survives source file deletion
- Replace strict version equality check with fill-defaults migration for
  cache versions 2-4, preserving history across schema changes
- Back up old cache to .bak before discarding on unmigrateable versions
- Fix Copilot auto bucket display names in menubar (Copilot (Anthropic),
  Copilot (OpenAI))
- Fix Roo Code / KiloCode provider key matching in menubar tab strip
2026-04-28 22:41:01 +02:00
Dunccan de Weerdt
26ebe75aa1 Add Droid CLI provider
Discovers and parses sessions from ~/.factory/sessions/, reading JSONL
message logs and companion settings.json files for token usage tracking.

- Discovers sessions by scanning per-cwd subdirectories
- Skips internal .factory housekeeping sessions
- Extracts tools, bash commands, and user messages from JSONL
- Distributes session-level cumulative token counts across calls
- Normalizes Droid model wrappers before existing pricing lookup
- Derives clean project names from cwd paths
- Adds menubar provider filtering for Droid
2026-04-28 20:16:45 +02:00
AgentSeal
d043795855 Add Qwen provider and replace hardcoded pricing with LiteLLM snapshot
- Add Qwen CLI provider (discovers sessions from ~/.qwen/projects/)
- Replace FALLBACK_PRICING (40 hand-maintained entries) with auto-generated
  LiteLLM snapshot (3595 models including Azure, OpenRouter pricing)
- Build script fetches and bundles LiteLLM data before tsup
- Provider-prefixed lookups (azure/, openrouter/) resolve to correct pricing
- Add display names for all GPT-5.x model variants
- Add Qwen to menubar provider filter and tab strip
2026-04-28 19:49:14 +02:00
Resham Joshi
ec2de6a642
Add OpenClaw, Roo Code, and KiloCode providers (#175)
- OpenClaw: JSONL parser with multi-path discovery, tool extraction
  (toolCall + tool_use block types), model tracking via model_change
  and custom model-snapshot events
- Roo Code + KiloCode: shared Cline-family parser extracts model from
  <model> tags in api_conversation_history.json, strips provider
  prefixes from model names
- Add cline-auto and openclaw-auto aliases and display names
- Add menubar provider filters and tab colors for all three
- Show cached data instantly instead of blocking on CLI refresh
2026-04-28 09:24:14 -07:00
AgentSeal
220d3193db Fix menubar not dimming on inactive screens
Drop explicit foregroundColor from the status item attributed string
so NSStatusBarButton can apply native inactive-screen dimming.

Fixes #170
2026-04-28 14:54:15 +02:00
Resham Joshi
6d15ea43a5
Add Gemini CLI provider for session tracking (#168)
Parse ~/.gemini/tmp/<project>/chats/session-*.json files from Gemini
CLI 0.38+. Uses real token counts (input, output, cached, thoughts)
embedded in each message instead of character estimation. Correctly
separates cached tokens from fresh input to avoid double-charging.

- Pricing for gemini-3.1-pro-preview, gemini-3-flash-preview,
  gemini-2.5-pro, gemini-2.5-flash from official Google API rates
- Tool name normalization (ReadFile->Read, SearchText->Grep, etc.)
- Menubar tab with Google Blue color (#4485F4)

Closes #166
2026-04-27 19:48:25 -07:00
Resham Joshi
f7f64a01ab
Add new providers, fix menubar tabs, accent color picker (#167)
* Add Kiro provider and transparent auto-model naming

- Add Kiro IDE provider: parses .chat JSON files, estimates tokens,
  normalizes dot-versioned model IDs for cost lookup
- Show "Cursor (auto)", "Copilot (auto)", "Kiro (auto)" in CLI
  dashboard instead of pretending to know which model was used
- Route auto model names through BUILTIN_ALIASES for cost estimation

* Fix menubar tabs: add missing providers, show period-scoped costs

- Add Kiro, OMP to ProviderFilter enum so installed providers appear as tabs
- Merge Cursor + Cursor Agent into single Cursor tab
- Tab costs now reflect the selected period (7d/30d/month/all) instead
  of always showing today
- Tab visibility still uses today's provider list so tabs don't
  disappear when switching to periods with no data

* Add accent color picker to menubar with Apple system presets

- 9 presets using Apple's exact macOS dark-mode accent colors
  (Ember, Blue, Purple, Pink, Red, Orange, Yellow, Green, Graphite)
- Color picker in header, persisted via UserDefaults
- "Burn" text stays fixed ember regardless of accent
- ThemeState is MainActor-isolated for thread safety
- Picker state lifted to AppStore so it survives .id() tree rebuild
- Accessibility labels on all color swatches
- Renamed brandAccentDark/brandEmberDeep/brandEmberGlow to match
  their actual light/deep/glow semantics

* Fix review findings: case-sensitive cost lookup, Kiro timestamp guard, cache versioning

- Normalize provider dictionary keys to lowercase in tab cost lookup
  so "Cursor Agent" (title-case from CLI) matches providerKeys
- Guard against missing/invalid/epoch startTime in Kiro parser to
  prevent RangeError crash or 1970-01-01 ghost entries
- Bump DAILY_CACHE_VERSION to 4 so upgraded users get a clean
  recompute with the new auto-model naming (cursor-auto vs default)
- Add version field to cursor-results.json cache to invalidate stale
  entries that still use the old 'default' model name
2026-04-27 19:46:30 -07:00
iamtoruk
e2254e9237 Revert AgentTabStrip to original todayPayload behavior
Some checks are pending
CI / semgrep (push) Waiting to run
2026-04-25 01:59:51 +02:00
AgentSeal
d7c92225e5 Revert "Fix trend chart to show days matching selected period"
This reverts commit c10484fe2b.
2026-04-25 01:56:51 +02:00
iamtoruk
c10484fe2b Fix trend chart to show days matching selected period 2026-04-25 01:47:30 +02:00
iamtoruk
bfcae0f84e Fix provider tabs showing wrong costs when period changes
The AgentTabStrip was using allProvidersToday for cost display, which
meant tabs always showed today's per-provider costs regardless of
which period was selected. This caused the hero to show e.g. $209 for
30 Days but the Claude tab to show $59 (today's Claude cost).

Fix: cost(for:) now reads from store.payload (selected period) instead
of allProvidersToday. Tab VISIBILITY still uses todayPayload so tabs
don't disappear when switching periods.

Bug existed since the original menubar app commit (495a254, Apr 17).
2026-04-25 01:34:10 +02:00
Resham Joshi
4aafe89f62
Merge pull request #147 from getagentseal/fix/tahoe-statusitem-rendering
Fix NSStatusItem not rendering on macOS Tahoe (26.x)
2026-04-24 11:05:17 -07:00
AgentSeal
2268a8a47d Fix NSStatusItem not rendering on macOS Tahoe (26.x)
On macOS 26.4+, accessory apps may fail to render their status item if
the activation policy is set to .accessory before the status item is
created and populated. The status item draw code executes but nothing
appears in the menu bar.

Workaround:
1. Start as .regular app and call activate() to ensure window server
   registration
2. Set a simple SF Symbol image immediately on the status button
3. Defer the attributed title setup to ensure initial render completes
4. Switch to .accessory policy after setup to hide from Dock

Fixes #146
2026-04-24 20:03:26 +02:00
AgentSeal
88f7e5329d Reduce menubar CPU usage from 14% to near zero
Remove duplicate refresh timer, increase interval to 30s, add debounce
2026-04-24 15:30:27 +02:00
AgentSeal
f893c964cc Add auto-start on login via osascript subprocess
Some checks are pending
CI / semgrep (push) Waiting to run
2026-04-24 13:50:32 +02:00
AgentSeal
b3b0dfbf60 Auto-register as login item on first launch 2026-04-24 13:27:39 +02:00
AgentSeal
d3c4de0375 Reduce CLI timeout from 60s to 20s for faster recovery
Some checks are pending
CI / semgrep (push) Waiting to run
2026-04-24 05:53:52 +02:00
AgentSeal
053ca25253 Show loading spinner on every refresh, not just first load 2026-04-24 02:59:38 +02:00
AgentSeal
68daad5dfa Fix menubar crashes and add reliable auto-refresh
Fixes crash when switching timeframes or providers by handling
duplicate dates in history data gracefully.

Adds LaunchAgent that posts a distributed notification every 15
seconds to keep prices fresh even after long idle periods.
2026-04-23 21:09:46 +02:00
iamtoruk
17b7e6ae6c fix(menubar): Swift 6 strict concurrency for wake observers
Some checks are pending
CI / semgrep (push) Waiting to run
2026-04-23 05:39:03 -07:00
AgentSeal
e970f64cbb fix: menubar auto refresh stops working after app is idle
The menubar price would freeze after the app was idle for a while because macOS App Nap suspends Task.sleep even with beginActivity.

Replace Task.sleep with DispatchSourceTimer which is more robust for background execution. Also add observers for system wake and screen wake events to force a refresh when the Mac resumes from sleep.
2026-04-23 02:25:20 +02:00
iamtoruk
3355e3699e fix(menubar): variable width for both compact and default mode 2026-04-22 05:45:51 -07:00
iamtoruk
3d063c9100 fix(menubar): keychain account filter + App Nap hardening + single query
Remove hardcoded "default" account allowlist from keychain credential
lookup. Claude Code 2.1.x writes the macOS login username, not
"default", so the filter silently dropped valid credentials on every
install.

Collapse the two-phase keychain enumeration into a single
SecItemCopyMatching call (one keychain prompt instead of four on
debug builds).

Harden App Nap opt-out: disable automaticTerminationSupport and
suddenTermination at the process level so AppKit cannot override
the beginActivity token.

Closes #115
2026-04-22 05:27:07 -07:00
iamtoruk
e8f8ccc94a feat(menubar): opt-in compact mode with variable-width status item
UserDefaults key CodeBurnMenubarCompact toggles a tighter menubar
display: no decimals, no leading space, variable width that hugs
the rendered text instead of the fixed 130pt slot.

Closes #129
2026-04-22 04:33:35 -07:00
iamtoruk
6ed0799b36 fix(menubar): opt out of App Nap so the refresh loop keeps ticking
Confirmed in the system log: while the menubar icon sits idle in the
background, macOS flips _kLSApplicationWouldBeTerminatedByTALKey to 1,
which is the Automatic Termination and App Nap subsystem declaring the
app a suspend candidate. Once that happens the 15s refresh Task's sleep
stretches arbitrarily, so the status bar label freezes until the user
clicks the icon (which triggers NSApp.activate and wakes everything up).

Hold a ProcessInfo.beginActivity handle for the life of the app with
.userInitiated + .automaticTerminationDisabled + .suddenTerminationDisabled
so macOS leaves the app alone. Released implicitly when the app exits.
2026-04-21 13:05:56 -07:00
iamtoruk
556f2bf78c fix(menubar): force status bar redraw and lift subprocess QoS
Two independent causes for the stuck-label / only-refreshes-on-click
behaviour, both fixed here.

1. NSStatusItem button defers the status bar paint for accessory apps
   that are not foreground, so after refreshStatusButton sets the new
   attributed title the menu bar visually froze until the user opened
   the popover (which triggers NSApp.activate and a forced redraw
   cycle). Explicit needsDisplay + display() forces the paint every
   cycle.

2. The codeburn subprocess inherited the accessory app's default QoS,
   which macOS background-throttles. That could stretch a sub-1-second
   parse into tens of seconds on large corpora and overrun the 15s
   refresh cadence. Set .userInitiated so the CLI runs at the same
   priority it does from a user-interactive terminal.
2026-04-21 11:50:28 -07:00
iamtoruk
eb0d096c62 fix(menubar): drop prefetchAll to keep Today label fresh
The detached prefetch spawned four codeburn subprocesses (week, 30days,
month, all) that competed with the main refresh loop's Today fetch for
disk and parser time. On large session corpora that starved the Today
refresh for minutes at a time, and the status label drifted stale until
the prefetch queue finally drained. Users perceived this as the menubar
"stopping" until they clicked a provider tab, which coincided with the
queue clearing.

Drop prefetchAll. Period tabs in the popover now fetch lazily on first
click (3-10 seconds once per app lifetime per period), same as they did
in 0.8.1. Today's label stays fresh because nothing else is hammering
the parser in the background.
2026-04-21 07:36:00 -07:00
iamtoruk
2467578a52 fix(menubar): run prefetchAll in a detached task so the refresh loop starts immediately
prefetchAll was awaited inside the refresh loop after the first cycle.
On large corpora the All Time parse takes 60+ seconds and kept the Today
refresh blocked for that whole window, so the status label got stuck for
minutes at a time while the CLI was returning fresh numbers.

Detach prefetchAll into its own @MainActor task. The popover still gets
the benefit of pre-warmed period caches, the refresh loop ticks every 15
seconds regardless.
2026-04-21 05:22:34 -07:00
iamtoruk
41839edd96 fix(menubar): prefetch periods and align dashboard dates with local timezone
Loading overlay no longer flashes on every 15s poll. isLoading now only
toggles when the cache is cold, and all periods prefetch once on launch
so tab switching is instant.

Heatmap tooltip, trend bars, forecast, and all-time stats were computing
on UTC dates while the CLI reports on local dates, so the two disagreed
at day boundaries. Switched every date formatter and calendar in these
paths to .current so the menubar matches codeburn today output.
2026-04-21 04:22:10 -07:00
iamtoruk
31367f2c01 fix(menubar): use numeric version comparison for update check
Compare versions with .orderedDescending instead of != to prevent
showing update button when installed version is newer than cached.
2026-04-21 04:20:42 -07:00
iamtoruk
dba33428ca fix(mac): update badge always visible due to version prefix mismatch
GitHub asset name includes a v prefix (v0.8.0) while
CFBundleShortVersionString does not (0.8.0). Strip the prefix
before comparing. Also capture stderr on update failure so the
button doesn't hang on "Updating..." forever.
2026-04-19 13:00:20 -07:00
iamtoruk
fc576f44ba fix: real-time refresh for menubar and TUI dashboard
Menubar: reduce cache TTL from 300s to 30s, background refresh from
60s to 15s, always fetch fresh data on tab switch instead of serving
stale cache. TUI: default auto-refresh to 30s (--refresh 0 to disable).

Closes #107
2026-04-19 08:55:48 -07:00
iamtoruk
bc92b49c1b feat(mac): auto-update checker and Plan pane button cleanup
Remove the broken "Connect Claude" / "Reconnect Claude" buttons from
the Plan pane -- they opened a terminal session that did nothing useful
for already-logged-in users. Keep only the "Retry" button.

Add an auto-update checker that queries GitHub releases every 2 days in
the background. When a newer menubar build is available, an "Update"
pill appears in the header. Clicking it runs the existing installer
flow (download, replace, relaunch) with no manual steps.
2026-04-19 03:33:37 -07:00
AgentSeal
a031c8d32d
chore: point repo URLs at getagentseal org (#97)
Add package.json repository/bugs/homepage fields. Swap hardcoded
AgentSeal/codeburn URLs to getagentseal/codeburn across README,
mac README, macOS menubar star banner, and the menubar installer's
release-API endpoint. 301 redirects keep old URLs working, but
canonical links now point at the current org.

Co-authored-by: AgentSeal <hello@agentseal.org>
2026-04-18 14:55:44 -07:00
AgentSeal
94240f5341 fix(mac): show correct cost in trend tooltip for per-provider views
The trend chart tooltip always displayed `bar.tokens` in its header,
which is zero for provider-filtered history (the CLI only carries
per-provider cost+calls in the daily cache, not tokens). Result: when
you selected Claude/Codex/Cursor/Pi, hovering a bar showed $0.00 even
on days with real spend.

The trend chart's main metric already falls back to cost when tokens
are zero. Pass that same metric value through to the tooltip so both
stay consistent.

Also removed the misleading "No model breakdown available" fallback
line. For provider-filtered views the per-model breakdown legitimately
doesn't exist in the payload, so the tooltip now just shows date +
cost without the error-sounding message.
2026-04-18 13:18:11 -07:00
AgentSeal
7ee8b679f9 fix(mac): keep (today, all) cache fresh for menubar title and tab labels
The refresh loop previously skipped `refreshQuietly(.today)` when the
user was already viewing the Today period. That guard meant while the
user was on (today, claude) or any other non-.all provider, the
(today, all) cache went stale. The menubar title and the agent tab
strip both read from that cache, so they displayed stale costs while
the hero section (which reads the currently-viewed payload) showed
the correct fresh value.

Remove the guard so the (today, all) cache refreshes every cycle
regardless of the currently selected period/provider.

Shipped as mac-v0.7.4.
2026-04-18 12:58:50 -07:00
AgentSeal
43a938ff9e feat(mac): add Connect Claude button to Plan pane
The Plan pane previously told users to "run claude login in your
terminal, then retry" with no way to start the flow from the app.
Added a primary Connect Claude button on both the no-credentials and
failed states that launches Terminal.app with `claude login`, so the
OAuth flow is one click away.

TerminalLauncher.openClaudeLogin() uses a hardcoded literal, so no
user input reaches AppleScript. Refactored the common path into
runInTerminal(command:preValidated:) which re-validates any non-
literal input against CodeburnCLI.isSafe as defense-in-depth.

On machines without Terminal.app (iTerm/Ghostty/Warp), the button
surfaces an inline instruction to run `claude login` manually instead
of failing silently.
2026-04-18 06:54:57 -07:00
AgentSeal
9483d66e65 fix(mac): restore agent tab strip to show all detected providers
Tabs were filtering on `value > 0` (today's spend), which hid the row
whenever only one provider had activity today. The CLI's providers map
already contains only providers detected on the system, so showing the
map as-is matches user intent: a tab for each installed tool,
regardless of today's spend. Tab strip only hides when nothing is
detected.

This also makes the Plan pill reachable again: it gates on
`selectedProvider == .claude`, which required clicking the Claude tab
to select.
2026-04-18 06:54:06 -07:00
AgentSeal
85d7bea7ea feat(mac): hide agent tabs when fewer than two providers have spend
The tab strip was visible for everyone regardless of which tools they
actually run, which produced a row of All + one provider for Claude-only
users and a row of All + zeros for users on exotic stacks. Hide the
whole row until a second provider has real spend, matching the behavior
the GNOME extension ships with.

Also expand ProviderFilter to include every provider the CLI supports
(OpenCode and Pi were missing) so their tabs appear when those tools
produce sessions. The CLI already emits pi and opencode in the payload's
providers map; the Mac app just wasn't offering a tab for them.

visibleFilters now filters on value > 0 instead of key presence, because
the CLI includes zero-cost entries for discovered-but-unused providers
and we don't want those rendering as blank tabs.
2026-04-18 05:07:36 -07:00
Resham Joshi
0b96ff182e docs: scrub private strategy notes from public text
Removes references to future signing decisions, dollar amounts, and
star thresholds from the menubar README, the CHANGELOG, the release
workflow (its YAML comments and the auto-generated release body), and
the packaging script. The technical description stays; the 'we are
not paying for X right now' framing is out.
2026-04-17 17:12:48 -07:00
Resham Joshi
3482478a49 fix(mac): drop empty Resources/ reference from Package.swift
Caught in a fresh-clone smoke test: SwiftPM refused to build because
.process("../../Resources") pointed at an empty directory that git
does not track. The bundle has no assets to ship yet (icon will land
with signing work), so the reference is gone. Build passes on a clean
checkout and the packaging script produces the universal .app zip as
expected.
2026-04-17 17:01:00 -07:00
Resham Joshi
495a254338 feat(mac): native Swift menubar app + one-command install
Introduces mac/ with a native SwiftUI menubar app that replaces the
previous SwiftBar plugin entirely. Install via `npx codeburn menubar`,
which downloads the .app from GitHub Releases, strips Gatekeeper
quarantine, and drops it into ~/Applications.

Highlights

- mac/ SwiftUI app: agent tabs, Today/7/30/Month/All period switcher,
  Trend/Forecast/Pulse/Stats/Plan insights, activity + model
  breakdowns, optimize findings, CSV/JSON export, Star-on-GitHub
  banner, live 60s refresh, instant currency switching with offline FX
  cache.
- Security: CodeburnCLI argv-based spawn (no shell interpretation),
  SafeFile symlink guards + O_NOFOLLOW writes, FX rate clamping to
  [0.0001, 1_000_000], keychain filtered to account == "default",
  removed byte-window credential log, in-flight refresh guard, POSIX
  flock on config.json writes, TerminalLauncher validates argv before
  AppleScript interpolation.
- Performance: shared static NumberFormatter (thousands of allocations
  per popover redraw eliminated), concurrent pipe drain with 20 MB cap
  + 60s timeout in DataClient, Observation-tracked reactive UI, 5-min
  payload cache keyed on (period, provider).
- CLI: new `codeburn menubar` subcommand that downloads + installs +
  launches the .app (no clone, no build). New `status --format
  menubar-json` payload builder. `export` rewritten to produce a
  folder of one-table-per-file CSVs with a `.codeburn-export` marker
  so arbitrary -o paths cannot be silently deleted.
- Removed: src/menubar.ts (SwiftBar plugin generator),
  install-menubar / uninstall-menubar subcommands, `status --format
  menubar` directive output, tests/menubar.test.ts,
  tests/security/menubar-injection.test.ts.
- Release: .github/workflows/release-menubar.yml builds universal
  binary, assembles .app, ad-hoc signs, zips, uploads on mac-v* tag
  push. Runs on the free macos-latest runner.

Tests

- 230 TypeScript tests pass
- 10 Swift CapacityEstimator tests pass
- TypeScript typecheck clean
- Swift release build clean
2026-04-17 16:55:56 -07:00