Commit graph

54 commits

Author SHA1 Message Date
ozymandiashh
a95a2c5bf8 fix(desktop): close cache and lifecycle review gaps 2026-08-12 20:31:17 +03:00
ozymandiashh
d8d343e83a perf(desktop): share cache state and eliminate duplicate cold hydration 2026-08-12 17:16:41 +03:00
iamtoruk
6cd11ab436 fix(app,dash): punchcard tooltip never crops; remove the menubar Workflow strip
- Punchcard tooltips flipped below the cursor on the top rows (the
  overflow container clips anything above its own edge) and clamp
  horizontally near the strip edges. Applied to both the app and dash
  copies of the component.
- The menubar Workflow strip is removed (view, strip model, tests):
  the popover is the compact surface and the workflow metrics live in
  the desktop app, dash and TUI. The payload keeps emitting the block
  (add-only contract; the other surfaces read it).
2026-08-10 14:35:40 -07:00
ozymandiashh
1959550218 feat(menubar): add preferred terminal setting with Terminal.app fallback
Full Report and Optimize always opened Terminal.app. Add a closed
PreferredTerminal enum (Terminal.app, iTerm2), a General settings picker,
and graceful fallback: chosen terminal -> Terminal.app -> headless spawn.

Defaults to Terminal.app so existing users see no change.

The terminal is selected from a closed enum, never a user string, so the
`tell application "..."` target stays a compile-time literal. Commands are
still whitespace-joined argv validated token-by-token by CodeburnCLI.isSafe
before any interpolation, preserving the shell-injection invariant.

Only terminals with a real "run in a live window" scripting verb are listed:
Terminal.app has `do script`, iTerm2 has `write text` on a session. Ghostty,
WezTerm, Warp, Alacritty and kitty expose no equivalent, so they keep the
existing headless fallback rather than shipping a window that closes on exit.

The iTerm2 script targets `application "iTerm"`, not `"iTerm2"`. AppleScript
resolves the name of a not-yet-running app through LaunchServices by bundle
file name, and the bundle is iTerm.app. Measured on iTerm2 3.6.11: with the
app quit, `tell application "iTerm2"` fails to compile (-2741) while
`tell application "iTerm"` compiles, cold-launches iTerm2 and runs the
command. The `"iTerm2"` spelling only works while the app already happens to
be running.

Fallback is a chain that checks results rather than a single fire-and-forget
pick, because "installed" does not imply "scriptable": osascript can still
fail on a missing Automation approval or a broken bundle. Each candidate is
run, waited on and its exit status checked, off the main thread so the
popover stays responsive; only once every candidate has failed do we spawn
headless. Every step logs via NSLog, so a user who sees no window has a trail
in Console.app instead of an app that looks dead. The decision logic is
extracted into terminalChain/runFirstWorking so tests exercise
"primary failed -> fell back" without launching anything.

Document the setting in the README next to the other menubar defaults keys.

Closes #877
2026-08-04 00:19:00 +03:00
AgentSeal
067174b885 Merge branch 'main' into feat/menubar-degraded-device-indicator 2026-08-03 21:34:54 +02:00
AgentSeal
ac8ff954cd Merge branch 'main' into feat/desktop-combined-scope 2026-08-03 21:33:03 +02:00
AgentSeal
75a02854fd fix(codex): align credit/dollar footer formatting across desktop and menubar
The desktop dollar-balance footer used toFixed, which drops thousands
separators, while the new menubar footer groups via a currency
NumberFormatter, so the two clients showed $12500.00 vs $12,500.00 for
the same balance. The menubar credit footer also rounded half-even while
the desktop uses Math.round (half-up), disagreeing on exact-half values.

Render the desktop dollar path through the en_US currency locale, and pin
the menubar footer formatter to half-up. Adds a grouping assertion (TS)
and an exact-half rounding test (Swift).
2026-08-01 22:32:53 +02:00
Marc Reynolds
f19d9d8d4b
feat(menubar): mark badge when a paired device is unreachable in combined scope
Under combined scope the badge falls back to the local figure whenever a
paired device doesn't report (asleep / off-network), which read as a glitch.
Append a dimmed "reachable/total" marker (e.g. "$95.94 /mo · 1/2") and a
matching tooltip so the reduced total is legibly "peer unreachable" instead.
The marker clears the instant every paired device reports again.

Adds AppStore.menubarBadgeDeviceShortfall plus tests.
2026-07-31 15:13:38 -06:00
Marc Reynolds
61581edb18
feat(dashboard): add combined-device scope to desktop app and menubar badge
Fixes #795: the desktop Dashboard showed only local-device cost while the
menubar and web GUI aggregated across paired devices.

Desktop app:
- Add a Local/Combined Scope setting (Settings > General), mirroring the
  macOS menubar. Combined passes `--scope combined` to the CLI, forces the
  provider filter to all, and persists to localStorage.
- Overview hero shows the paired-device aggregate (cost/calls/sessions) with
  a "Combined · N devices" caption and per-device breakdown when Combined is
  selected; detailed panels remain local (the combined payload carries totals
  only).

Menubar:
- The badge figure now reflects Combined scope instead of always showing the
  local total: refreshMenubarBadge fetches the combined payload for the badge
  period and the badge renders the cross-device aggregate, falling back to
  local when no combined payload is available.
2026-07-31 15:12:29 -06:00
Richard Boisvert
3e400bffa7
feat(codex): show the credit limit on credit-metered ChatGPT workspaces
Signed-off-by: Richard Boisvert <rboisvert@devolutions.net>
2026-07-27 08:19:59 -04:00
Resham Joshi
85781999a5
Merge pull request #807 from ZacharyHu0/fix/update-failure-feedback
Some checks failed
CI / semgrep (push) Has been cancelled
fix(menubar): clarify update failure status
2026-07-26 07:12:57 -07:00
Resham Joshi
2e2082061e
Merge pull request #803 from hyknerf/fix/802-menubar-right-click-menu
fix(mac): keep right-click status-item menu open and stable
2026-07-26 06:58:51 -07:00
iamtoruk
494136a3e8 fix(menubar): keep showing Kimi quota when the login is idle, stamp stale data
Kimi Code tokens live ~15 min and only the CLI renews them, so the load
state sits in .terminalFailure as its dominant steady state between CLI
uses. The Plan tab and tab-strip chip flapped to a reconnect screen every
cycle even with a good last snapshot on hand.

Extract the display decision into a pure KimiQuotaPresentation helper:
terminal-with-data shows the usage bars (flagged idle) and only the
no-data case falls through to reconnect. loadedBody stamps an 'as of
<time>' caption once a snapshot is older than 10 min, in every state that
renders it. The chip's kimiQuotaSummary downgrades terminal-with-data to
.stale so the bar and popover keep the last-known rows instead of the
reconnect card. Settings' connect/disconnect pane still explains the
terminal reason.

Adds KimiQuotaPresentationTests.
2026-07-26 06:35:13 -07:00
hao
4e9c3a1dfe fix(menubar): clarify update failure status 2026-07-26 15:20:13 +08:00
Frenky Harry S. Sinaga
8e8b58626a
test(mac): lock status-item context menu policy (#802)
Extract StatusItemContextMenuPolicy (event mask, debounce, presentation
mode) and unit-test it so rightMouseUp + statusItemMenu cannot regress
to the flash/scroll-jump paths without a failing test.
2026-07-24 19:27:25 +07:00
reviewer
576f47d854 menubar: add Kimi Code subscription quota tracking
Read ~/.kimi-code credentials directly and poll api.kimi.com
/coding/v1/usages for the weekly quota and rate-limit windows,
mirroring the existing Codex/Claude quota services.

- New KimiSubscriptionService with lenient decoding (string/number
  values, enum-style time units, derived used from remaining) and
  429 backoff
- Quota chip on the Kimi Code tab, Plan insight, and a Kimi tab in
  Settings with connect/disconnect
- Independent refresh cadence anchor so Kimi-only setups don't poll
  on every payload tick
- Expired tokens surface as terminal with automatic recovery once
  the CLI refreshes the credential file
- Keep insights visible for quota-capable providers on empty days
- Widen Settings so six tabs don't collapse into the overflow menu
2026-07-23 21:31:55 +02:00
Resham Joshi
8097052f67
menubar: add Workflow strip after the Models section (#785)
Compact one-row strip: correction rate with count, median time to first
edit, and the top reworked file, plus one coaching note derived locally
with the same thresholds and copy as the CLI's workflow-insights
buildCoachingNotes. Decodes two new optional payload blocks
(current.workflow and current.topReworkedFiles), so payloads from older
CLIs still parse. The section hides entirely when there is no signal and
individual stats never render as zero placeholders. It reads the store's
current payload, so it follows the selected agent tab automatically.

Adds unit tests for the duration formatter, note selection, model
derivation, and payload decoding.

Co-authored-by: reviewer <review@local>
2026-07-20 13:55:55 -07:00
Resham Joshi
00160275cd
menubar: one-click update for the CLI and the app together (#778)
The Update badge previously replaced only the menubar (via the CLI's
menubar --force installer), and the CLI banner only copied the update
command to the clipboard. One click now runs the whole sequence: update
the CLI in place through the package manager it was installed with
(brew upgrade, or npm install -g codeburn@latest --force; the npm next
to the codeburn launcher wins so nvm/volta/asdf installs update inside
their own toolchain), re-read the installed version, then run the app
replacement from the freshly updated CLI. No recognizable package
manager surfaces the manual command instead of guessing at a mutation;
a homebrew CLI without a findable brew never falls through to npm,
which would create a second conflicting install. The badge now also
appears for CLI-only updates, and the banner's primary action is Update
now with the copyable command kept as secondary. Six resolution tests,
mutation-verified.

Co-authored-by: reviewer <review@local>
2026-07-20 10:17:31 -07:00
iamtoruk
6eb8797efa fix(clients): display CLI-computed values verbatim, align period windows across app/dash/menubar 2026-07-20 06:26:15 -07:00
Aditya Vikram Singh
bcef34f081
menubar: linear pace on Codex quota windows — deficit/reserve, projection, run-out ETA (#726) (#728)
Co-authored-by: Aditya Vikram Singh <247195684+avs-io@users.noreply.github.com>
2026-07-20 05:21:43 -07:00
Aditya Vikram Singh
688467e86f
menubar: surface Codex limit-reset credits (count and next expiry) in the Plan tab (#723) (#724)
Some checks are pending
CI / semgrep (push) Waiting to run
Co-authored-by: Aditya Vikram Singh <247195684+avs-io@users.noreply.github.com>
2026-07-17 05:58:21 -07:00
Aditya Vikram Singh
71a8e8e250
menubar: skip unchanged background refreshes, 120s AC idle cadence, utility QoS for tick spawns (#703) (#704)
Co-authored-by: Aditya Vikram Singh <247195684+avs-io@users.noreply.github.com>
2026-07-16 15:11:25 -07:00
Aditya Vikram Singh
4689b5afa9
menubar: honor Retry-After on Claude quota 429s, back off failed refreshes (#702)
* menubar: honor Retry-After on Claude quota 429s, back off failed refreshes (#701)

* Fix operator precedence in claudeDue fallback

---------

Co-authored-by: Aditya Vikram Singh <247195684+avs-io@users.noreply.github.com>
2026-07-16 14:52:47 -07:00
AgentSeal
6b6b6b8df6 feat(menubar): Claude config selector, hardened (supersedes #635)
Scopes the menubar to one Claude config for multi-config (CLAUDE_CONFIG_DIRS)
setups, with All as the default. Rebased onto main and fixed the review
findings from the original #635:

- Fix a TS2206 build break (a 'type' modifier inside an import type block).
- Reject --claude-config-source with a non-Claude --provider, and scan Claude
  only in the scoped branch (a config is Claude-only): fixes provider data
  leaking into a scoped query and avoids parsing every provider's corpus.
- Scope the macOS menu-bar figure to the selected config (badge matched the
  popover), clear the selection when switching to a non-Claude provider tab,
  and stop the on-disk badge fallback from showing an unscoped number while
  scoped.
- Tag Claude Desktop / Cowork sessions as their own 'claude-desktop' source so
  they are a selectable bucket instead of silently vanishing from per-config
  views (sum of options now equals All).
- Skip the redundant Claude discovery walk for plain single-config users while
  keeping idle configs and Claude Desktop selectable.

Reviewed by Codex 5.6; all findings addressed. Full suite: 1581 TS tests, 76
Swift tests, tsc clean.
2026-07-10 01:44:51 +02:00
Resham Joshi
23f118879c
Merge pull request #649 from getagentseal/merge/lingtai-tui-hardened
Add LingTai TUI provider (hardened) — supersedes #636
2026-07-09 23:51:02 +02:00
ZacharyHu0
a485087b5b Add LingTai TUI provider support 2026-07-09 23:47:59 +02:00
AgentSeal
b4c04e7beb feat(menubar): user-selectable usage refresh cadence (Auto / 1m / 5m / 15m / Manual)
Settings gains a Usage Refresh picker mirroring the quota cadence
pattern. Auto keeps the adaptive default (30s active, battery and Low
Power backoff); fixed cadences ignore power state but an open popover
always gets the active 30s; Manual never auto-spawns, refreshing only
on popover open, Refresh Now, and first launch (wake included: manual
wakes run stuck-state cleanup without spawning). Stored in UserDefaults
as CodeBurnMenubarRefreshSeconds (-1 auto, 0 manual, else seconds) and
documented in the README with the defaults-write escape hatch.

Refs #647
2026-07-09 22:48:07 +02:00
AgentSeal
6a08a8f31c fix(menubar): cut idle energy by gating spawns on popover, power, and display state
Every 30s tick spawned 2-3 concurrent Node CLI processes at 100%+ CPU,
and an app-lifetime activity assertion disabled App Nap forever, putting
the menubar's energy use in the video-call class (#647).

- Closed popover now fetches only what the status item renders (menubar
  period plus today); the popover's selected key refreshes on open via
  the existing recovery path, which now also catches up the status
  figure and clears a latched display-sleep flag.
- Spawn cadence stretches to 150s on battery and 300s in Low Power Mode
  while the popover is closed; the 30s timer itself keeps running.
- The app-lifetime beginActivity is gone; each in-flight refresh
  (including manual Refresh Now) holds its own scoped activity, and an
  NSBackgroundActivityScheduler backstop tick every ~3min bounds status
  staleness once App Nap starts coalescing the timer.
- Display sleep skips ticks entirely; wake and popover open clear it.
- The status-payload fallback honors the same spawn interval so the 30s
  cache TTL cannot defeat the backoff.

Child process QoS stays .userInitiated on purpose: lowering it is
documented to slow parses 5-10x and starve the cadence; the win here is
spawn frequency, not priority. Measured on an idle machine: two
concurrent 100%+ CPU spawns per 30s window before, one spawn in 72s
after, with App Nap coalescing the rest.

Fixes #647
2026-07-09 22:47:30 +02:00
AgentSeal
61209bd8b5 fix(menubar): provider-correct plan tab copy; surface model-scoped weekly limits
The Codex plan tab reused the shared connect, no-credentials and reconnect
views whose copy was hardcoded for Claude, so the Codex tab showed Connect
Claude subscription. Worse, the no-credentials Try Again button always
re-ran the Claude bootstrap. The three views now take title, message and
action from the call site.

The oauth usage endpoint gained a limits array carrying model-scoped
weekly buckets (currently Fable). Parse weekly_scoped entries generically
by their display name and show them in the Claude hover popover and the
plan tab, with the usual pace projection. Parser now has test coverage
using the captured live response shape.
2026-07-02 19:37:51 +02:00
AgentSeal
256511618d fix(menubar): refuse menubar updates from CLIs older than 0.9.9
CLIs before 0.9.9 resolve /releases/latest for menubar --force, which can
point at a CLI-only release with no menubar asset, so the update they run
installs nothing. The update dialog and performUpdate now detect an
installed CLI older than the installer fix (909efcf, first shipped in
0.9.9) and direct the user to upgrade the CLI first, with the exact
command, instead of suggesting a command that cannot work. Unknown CLI
versions are not flagged.
2026-07-02 05:30:19 +02:00
ozymandiashh
805d2ffa49
feat(menubar): add Local/Combined usage toggle (#566) (#568)
Some checks are pending
CI / semgrep (push) Waiting to run
Mirror the combined multi-device dashboard in the macOS menubar via a
Local/Combined scope toggle (Settings -> Display). Builds on the menubar-json
`--scope combined` contract.

- MenubarScope enum (.local default) persisted in UserDefaults; PayloadCacheKey
  gains a scope dimension so local and combined payloads cache separately.
- DataClient.fetch(scope:): a pure statusSubcommand builder appends
  `--scope combined` only for combined, forces `--provider all` (the CLI rejects
  combined with a provider/project/exclude filter), and coerces multi-day
  selections to local (the CLI rejects combined + --days).
- MenubarPayload decodes the optional `combined` block (per-device + totals);
  nil when absent (back-compat).
- The badge always stays on the local payload; the combined network pull lives
  on a separate cache key and falls back to local with a "Combined unavailable"
  indicator, so a slow/offline peer never blocks the badge.
- Hero shows combined totals + a per-device breakdown in Combined scope; the
  token metric stays input+output (cache excluded) to match local; the
  per-device daily-budget warning is suppressed for a multi-device total;
  selecting Combined resets the provider tab to All.

Tests run via `swift test` (CommandLineTools Testing.framework on the rpath, no
Xcode): scope persistence, argv (local/combined/provider-forcing/multi-day),
cache-key partition, badge-survives-combined-failure, combined decode, token
consistency, provider reset, budget suppression.
2026-06-28 22:01:01 +02:00
Resham Joshi
454d836f6e
fix(menubar): surface CLI stdout/stderr on decode failure (#515) (#547)
* fix(menubar): surface CLI stdout/stderr on decode failure (#515)

A failed decode of the CLI menubar-json output threw an opaque
DataClientError.decode(error) that surfaced only "not valid JSON", hiding
whether stdout was empty or carried a non-JSON prefix (e.g. a stray Node
banner on stdout, the root cause in #515). Wrap it in a CLIDecodeFailure that
carries a bounded stdout snippet, the stdout byte count, and stderr, so
String(describing:) is self-diagnosing in logs and the UI.

* test(menubar): add missing codexCredits arg so the Swift test target compiles

Two tests built CurrentBlock without the codexCredits parameter added in #510,
so the Swift test target failed to compile (CI does not run the Swift tests, so
it went unnoticed). Add codexCredits: nil to both.
2026-06-22 01:33:41 +02:00
Tiago Santos
cf35854b09
feat: add devin provider (#444)
* feat: add devin provider

* feat: add devin to gnome extension and improve dev scripts

* fix: fix local installer

* chore: cleanup unecessary changes and address pr comments
2026-06-09 21:58:31 +02:00
Resham Joshi
02b16351be
fix(menubar-tests): add missing localModelSavings/savingsUSD args so swift tests compile (#465) 2026-06-09 21:56:13 +02:00
Resham Joshi
f5d1a8513d
fix(menubar): await terminationHandler instead of blocking a queue thread; cap CLI spawns (#462)
The #426 fix moved waitUntilExit and its timeout onto the same global(qos:.utility)
queue. Under sustained load every utility worker blocked in waitUntilExit, so the
timeout could never be scheduled to kill them and the menubar wedged on Loading
forever (confirmed via sample after ~a week of soak). Await process.terminationHandler
(fires on a Foundation queue, blocks no worker) so the timeout always has a free
thread. Add an actor-based async semaphore capping concurrent CLI spawns at 6.
2026-06-09 21:27:32 +02:00
Porun
42ef1ff97e
Add contribution heatmap insight (#437)
* Add contribution heatmap insight

* fix(menubar): heatmap layout polish — labels, spacing, pill order

---------

Co-authored-by: AgentSeal <hello@agentseal.org>
2026-06-06 02:49:08 +02:00
iamtoruk
4ffec37861 fix(menubar): run CLI exit-wait and timeout off the cooperative pool
The menubar wedged on "Loading Today…" for hours after an idle period.
Root cause: DataClient.runCLI called the blocking process.waitUntilExit()
from an async function on Swift's cooperative thread pool. On a 16-core
machine, 16 concurrent slow `codeburn` subprocesses pinned all 16
cooperative threads inside waitUntilExit; the 45s timeout — itself a Task
on that same pool — could then never be scheduled to kill them, so the
deadlock was permanent. Confirmed via sample: 16/16 cooperative threads
parked in waitUntilExit. PR #412 (AppStore inFlightKeys bookkeeping) was a
layer above the OS-thread deadlock and could not fix it.

Move both blocking points off the cooperative pool: bridge waitUntilExit
through a global (overcommit) queue via a continuation, and drive the
timeout from a DispatchSource on a global queue so it fires even when the
pool is saturated. Extract runProcess for testability; add a concurrency +
timeout smoke test and an output/exit-code test.
2026-06-01 02:09:05 -07:00
iamtoruk
c350dd2a55 feat(menubar): refresh via headless app binary instead of node script
Run CodeBurn's own signed binary in --refresh-once mode under the LaunchAgent
so the spawned CLI inherits CodeBurn's TCC grant. The bare-node shell script
prompted "node would like to access data from other apps" because launchd-
spawned children lose the signed app's TCC attribution. Drops the generated
shell script, scriptEnvironment, and per-period regeneration; the plist is now
static and period is read from UserDefaults at refresh time.
2026-05-30 13:05:48 -07:00
iamtoruk
8884e212e3 test(menubar): lock in Codex terminal-failure classification 2026-05-30 12:32:19 -07:00
iamtoruk
341f6e959d test(menubar): integration test for refresh-script round trip 2026-05-30 00:45:04 -07:00
iamtoruk
1341b626a2 feat(menubar): generate self-contained menubar-refresh.sh
The script runs the CLI for the badge period and atomically writes
menubar-status.json, so a static LaunchAgent can refresh the badge
out of process.
2026-05-30 00:40:03 -07:00
iamtoruk
be7b8278c5 feat(menubar): add CodeburnCLI.scriptEnvironment for shell-script embedding
Exposes the validated argv and augmented PATH so the LaunchAgent runner
script can be generated without re-resolving the binary or PATH.
2026-05-30 00:10:29 -07:00
iamtoruk
bdbd7b19f6 feat(menubar): add MenubarStatusCache read side for badge backstop 2026-05-30 00:01:07 -07:00
iamtoruk
75edff64ec fix(menubar): popover-open always force-recovers current key
Opening the popover now unconditionally restarts a dead timer and
clears stuck loading bookkeeping before force-fetching, so a user
looking at a stuck tab always recovers within one CLI round-trip.
2026-05-29 23:51:48 -07:00
iamtoruk
372681cae2 fix(menubar): recover from stuck loading when in-flight entry is orphaned
A quiet refresh torn down across sleep/wake (or a generation reset) can
leave an orphaned inFlightKeys entry for the current key. The stuck-loading
recovery guard bailed whenever any in-flight entry existed, so the popover's
retry loop no-oped forever and the spinner ("Loading Today...") never
cleared — observed on a long-lived instance that crossed the midnight day
rollover.

Clear stale loading/in-flight state via the existing watchdog before the
in-flight guard, so an orphaned entry can no longer trap recovery. A healthy
in-flight fetch (younger than the watchdog) is still respected.
2026-05-28 14:06:13 -07:00
iamtoruk
3751b3381c fix(menubar): add watchdog backoff, remove dead RefreshBackoff code
PR #393 accidentally deleted the RefreshBackoff integration from PR #388,
leaving RefreshBackoff.swift as dead code and tests calling nonexistent
methods. Delete the dead code and fix the broken test target.

Add exponential backoff to the loading watchdog (8s, 16s, 32s... up to
60s, max 6 attempts) so it stops hammering the CLI when it's unavailable.
After exhausting retries, show an error overlay with a Retry button.

Fix recoverFromStuckLoading to skip recovery when a fetch is already
in-flight (avoids killing healthy fetches via generation bump).

Fix selectedDay to return nil for multi-day selections, and pass days
through startInteractiveSelectionRefresh so the cache key matches
currentKey.
2026-05-27 04:38:42 -07:00
iamtoruk
aa69857e30 Add configurable menubar status period (#302)
Some checks are pending
CI / semgrep (push) Waiting to run
The menubar status item can now track Today, Week, Month, or 6 Months
independently from the popover's active period. Setting is persisted
in UserDefaults and changeable from Settings or via `defaults write`.

Thanks @ozymandiashh. Closes #291.
2026-05-24 03:15:39 -07:00
森曜
da641eeb52
Pause menubar refresh after repeated stalls (#388) 2026-05-24 01:04:52 -07:00
iamtoruk
909efcf989 Harden menubar refresh and installer 2026-05-14 18:32:15 -07:00
iamtoruk
478131d5b7 Harden menubar status refresh timer 2026-05-14 17:57:36 -07:00