Commit graph

660 commits

Author SHA1 Message Date
Resham Joshi
cef1a6b669
refactor(mux): drop dead openai reasoning fallback (#446)
usage.reasoningTokens is the AI SDK v6 normalized field across all
provider families, so the providerMetadata.openai fallback never fired.
Removed it and the now-unused openai extraction.
2026-06-06 03:05:07 +02:00
Thomas Kosiewski
e0051fc403
feat(providers): add coder/mux as a datasource (#438)
* feat(providers): add coder/mux as a datasource

Reads coder/mux session data from ~/.mux/sessions/<workspaceId>/chat.jsonl and normalizes per-assistant-message token usage into ParsedProviderCall. Discovery resolves project names from config.json; the token decomposition matches mux's own inclusive input/output accounting, and cost is recomputed via LiteLLM. Includes unit tests and a provider doc.

Change-Id: Ie6ce9d41254d8bf05ff0965b443328dfa7b598de
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Thomas Kosiewski <tk@coder.com>

* fix(providers): discover mux sub-agent transcripts

discoverSessions only globbed sessions/<id>/chat.jsonl and skipped each
spawned sub-agent's transcript at
sessions/<id>/subagent-transcripts/<childTaskId>/chat.jsonl. Against a real
~/.mux (629 workspaces) those nested files are 5,889 sessions and ~51% of all
assistant messages, so sub-agent spend was silently dropped: total mux usage
went from $17,113 to $21,564 (+26%) once counted.

Walk the subagent-transcripts dir per workspace and attribute each child
session to the parent's project. Dedup is unaffected: the parser keys off the
<childTaskId> dir name, which is disjoint from every workspace id, so each call
is still counted once. Cross-checked parsed per-model token totals against
mux's sibling session-usage.json.

Also corrects the provider doc, which wrongly claimed sub-agents get their own
top-level sessions/<id>/chat.jsonl, and documents the Google reasoning>output
decomposition edge case.

Change-Id: I1d43f26eec7c9c6c523e5ea541e2ff8d0c3aa07e
Signed-off-by: Thomas Kosiewski <tk@coder.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Signed-off-by: Thomas Kosiewski <tk@coder.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 03:04:24 +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
Resham Joshi
3cdd13881f
feat(menubar): configure CLAUDE_CONFIG_DIRS via Settings (#434) (#436)
Adds a "Config Directories" section under the Claude settings tab so users
can aggregate usage across multiple Claude config directories (work /
personal accounts) from the GUI. The menubar is an accessory app that
doesn't inherit the user's shell environment, so CLAUDE_CONFIG_DIRS was
previously unreachable from the app.

Rather than injecting the value as an env var into every spawned subprocess
(which would force arbitrary user paths through the shell/AppleScript
allowlist that deliberately excludes shell metacharacters), the list is
persisted to the shared ~/.config/codeburn/config.json. The CLI reads it
regardless of how it's launched, so both the menubar's data refresh and
terminal-launched `report`/`optimize` honor it consistently.

CLI: getClaudeConfigDirs() now reads a claudeConfigDirs array from config
as a fallback below the CLAUDE_CONFIG_DIRS / CLAUDE_CONFIG_DIR env vars
(env still wins for per-shell overrides), above the ~/.claude default.

Menubar: CLIClaudeConfig mirrors CLICurrencyConfig's flock-guarded write;
the Settings UI offers an add/remove list with a folder picker and forces
a refresh on edit.
2026-06-06 02:30:46 +02:00
Resham Joshi
14fbd4d6d1
fix(menubar): use SupportedCurrency enum in settings picker (#435)
Some checks failed
CI / semgrep (push) Has been cancelled
The currency picker hardcoded 8 currencies while the SupportedCurrency
enum defines 18. New currencies added to the enum (like CNY in #430)
required a separate SettingsView edit to appear in the picker.
2026-06-03 23:27:55 +02:00
Resham Joshi
a6601efc60
Merge pull request #430 from PorunC/feat/add-cny-currency
Add CNY currency support
2026-06-03 23:25:32 +02:00
AgentSeal
692bc09729 Merge feat/codeburn-mcp: MCP server for AI agent usage queries
Adds stdio-based MCP server (codeburn mcp) exposing get_usage and
get_savings tools. Includes pseudonymized project names with per-install
salt, session detail redaction, structured output schemas, and inflight
request coalescing.
2026-06-03 22:48:36 +02:00
Misaka
906c5338e4 Add CNY currency support 2026-06-03 10:45:39 +08:00
AgentSeal
f748d3463b fix(test): kilo-code tests fail on machines with KiloCode installed
Some checks are pending
CI / semgrep (push) Waiting to run
discoverSessions always checks the SQLite path at ~/.local/share/kilo
regardless of overrideDir, so the test found real sessions on the host.
Filter to override-dir sessions and avoid hardcoded /tmp paths.
2026-06-03 01:03:39 +02:00
AgentSeal
679f7ba5a6 fix(mcp): harden redaction, error responses, and pre-warm race
- Add per-install random salt to pseudonym hash to prevent rainbow table
  reversal of common project names
- Redact session details (dates, models) when project names are hashed
  to prevent re-identification via cost fingerprinting
- Return structuredContent in error responses to satisfy strict MCP
  clients that validate against declared outputSchema
- Remove pre-warm fire-and-forget that raced with the inflight map
- Fix empty markdown table producing malformed cells
- Add tests for session detail redaction and cross-field consistency
2026-06-03 00:56:30 +02:00
Resham Joshi
bec0491667
Merge pull request #426 from getagentseal/fix/menubar-dataclient-deadlock
fix(menubar): run CLI exit-wait and timeout off the cooperative pool
2026-06-02 15:56:11 -07:00
iamtoruk
94e6b77672 feat(mcp): add 'codeburn mcp' stdio command with stdout guard 2026-06-02 02:18:00 -07:00
iamtoruk
1e54967d97 feat(mcp): get_usage + get_savings tools with annotations, schemas, coalescing 2026-06-02 02:16:10 -07:00
iamtoruk
296085dff1 feat(mcp): markdown table renderers for usage and savings 2026-06-02 02:13:51 -07:00
iamtoruk
1e82a9cf21 feat(mcp): hash project names by default with opt-in reveal 2026-06-02 02:13:08 -07:00
iamtoruk
d5c8ad0bfd refactor: extract buildMenubarPayloadForRange for reuse by MCP 2026-06-02 02:12:27 -07:00
iamtoruk
2c46488f0f refactor: move buildPeriodData into usage-aggregator module 2026-06-02 02:01:45 -07:00
iamtoruk
4e61fd1369 build(mcp): add @modelcontextprotocol/sdk + zod, externalize in tsup 2026-06-02 01:38:45 -07:00
iamtoruk
0843efdecb docs(mcp): add implementation plan for codeburn MCP server 2026-06-02 01:33:08 -07:00
iamtoruk
dc7a1a7b0e docs(mcp): add design spec for codeburn MCP server 2026-06-02 01:20:58 -07:00
Resham Joshi
0431105052
Update README.md
Some checks are pending
CI / semgrep (push) Waiting to run
2026-06-01 23:46:00 -07: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
69b1736365 refactor(cli): share persistent-codeburn resolver; tighten Antigravity hook ownership
Some checks are pending
CI / semgrep (push) Waiting to run
Extract the duplicated codeburn-binary lookup (PATH building, npx-shim skipping,
default lookup dirs, resolver) from menubar-installer and the Antigravity
statusline installer into src/persistent-codeburn.ts. Both now import it and
keep their own user-facing "install globally" message.

Match the Antigravity statusLine hook on the trailing agy-statusline-hook token
instead of a bare substring, so a custom command that merely mentions the token
is treated as custom (protected, backed up) rather than silently overwritten.
2026-05-31 20:07:57 -07:00
iamtoruk
b246e822b7 Merge pull request #410 from ozymandiashh/codex/antigravity-hook-stale-path
Fix Antigravity hook stale CLI paths: install the statusLine hook through a
persistent codeburn binary resolved from PATH and repair stale CodeBurn-owned
hooks on re-install.
2026-05-31 20:04:11 -07:00
iamtoruk
3fa255fefc fix(models): match model names on version boundary, not bare prefix
A bare startsWith let an unlisted future minor collapse into the base entry
(gpt-5.6 -> "GPT-5"), the same mis-bucketing class as #420 but for non-Claude
families. Require an exact match or a key-plus-dash boundary in both
getShortModelName and the Codex provider so new versions fall through to their
raw id instead of a wrong name and pricing tier.
2026-05-31 05:37:12 -07:00
iamtoruk
0520ecbde8 fix(claude): derive model display names via getShortModelName
The Claude provider kept its own shortNames map with no claude-opus-4-8
entry, so the models table and menubar bucketed it into "Opus 4" — the
actual #420 path (the earlier models.ts map is unused here). Delegating to
getShortModelName removes the duplicate and picks up new versions for free.
2026-05-31 05:31:56 -07:00
iamtoruk
aa9bd9f0f1 feat(models): derive Claude names and fast multipliers automatically
New Claude releases no longer need a hand-maintained SHORT_NAMES entry or
FAST_MULTIPLIERS row. Display names are derived from the claude-<family>-<major>-<minor>
id, and the fast-mode multiplier rides along as a 5th element in the LiteLLM
snapshot tuple (provider_specific_entry.fast). Fixes #420: claude-opus-4-8 gets
its own line and correct pricing instead of falling into the Opus 4 bucket.
2026-05-31 05:17:14 -07:00
iamtoruk
ca41021a51 fix(menubar): treat the CLI credential store as the source of truth
The menubar kept its own copy of each provider's OAuth grant and refreshed
it on a timer, racing the CLI. Both Claude and Codex use single-use refresh
tokens that rotate on every refresh, so the menubar's self-rotation could
invalidate the user's own CLI login and surfaced as "disconnected" after a
long idle period.

Codex: read ~/.codex/auth.json fresh each cycle; only self-refresh when
last_refresh is older than 8 days; on 401 re-read the source before spending
our token; write rotated tokens back to auth.json (atomic, preserving other
keys); recover from reuse/invalid_grant by re-reading instead of going
terminal.

Claude: never HTTP-refresh the CLI-owned token. On expiry/401 re-read the
keychain with a no-UI query (LAContext.interactionNotAllowed) to adopt a
token the CLI already rotated; when none is available yet, report a transient
sourceTokenStale rather than a terminal disconnect.
2026-05-31 05:01:19 -07:00
iamtoruk
92dbad9503 refactor(menubar): remove dead distributed-notification listener
Nothing posts com.codeburn.refresh since the launchd fetcher was dropped,
so the listener and its heartbeat handler were vestigial.
2026-05-31 05:01:10 -07:00
iamtoruk
515a7a1467 feat(menubar): drop launchd fetcher; GUI writes its own badge backstop
A launchd-spawned process gets separate TCC attribution from the LaunchServices
app, so the out-of-process refresher prompted for "access data from other apps"
on every run regardless of the GUI's grant. Remove it entirely: the in-app loop
(timer survives sleep, popover-open recovery) is the source of truth and now
writes menubar-status.json on each successful refresh. On upgrade, any leftover
com.codeburn.refresh LaunchAgent is unloaded and deleted.
2026-05-30 13:35:07 -07:00
iamtoruk
a2ab52aafd build(menubar): allow signing with a stable identity via CODESIGN_IDENTITY 2026-05-30 13:18:46 -07:00
iamtoruk
b158af09e7 fix(menubar): read period from standard defaults in headless refresh 2026-05-30 13:06:54 -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
d9a2e829ae docs(menubar): note deliberate unquoted argv interpolation in refresh script 2026-05-30 12:40:28 -07:00
iamtoruk
8884e212e3 test(menubar): lock in Codex terminal-failure classification 2026-05-30 12:32:19 -07:00
iamtoruk
51b90aa1a5 feat(menubar): badge falls back to launchd status file
refreshStatusButton now uses the fresher of the in-memory payload and
the file written by the LaunchAgent, so the menubar number advances
within ~30s even if the in-app loop is dead.
2026-05-30 00:48:12 -07:00
iamtoruk
3f4298103b feat(menubar): write refresh script on launch and period change 2026-05-30 00:46:54 -07:00
iamtoruk
1269406c7a feat(menubar): static LaunchAgent runs menubar-refresh.sh
Migrate off the osascript distributed-notification form (dropped by
napped apps) to a static /bin/sh runner. The plist no longer encodes
period, so it migrates once; period changes rewrite only the script.
2026-05-30 00:45:45 -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
ea64dca52e fix(menubar): keep refresh timer alive across sleep
The sleep handler tore down the DispatchSource timer; a missed wake
notification then left it nil forever, stranding the refresh loop.
Cancel only in-flight tasks and let the kernel-paused timer resume.
2026-05-29 23:46:16 -07:00
Resham Joshi
d7a4048b9f
Merge pull request #412 from getagentseal/fix/menubar-stuck-loading-orphaned-inflight
Some checks failed
CI / semgrep (push) Has been cancelled
fix(menubar): recover from stuck loading when in-flight entry is orphaned
2026-05-28 14:07:22 -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
ozymandiashh
6ae80c651c Fix Antigravity hook stale path repair 2026-05-28 18:50:41 +03:00
iamtoruk
8d3d773cc2 docs: update menubar screenshot to 0.9.11
Some checks failed
CI / semgrep (push) Has been cancelled
2026-05-27 06:37:19 -07:00
iamtoruk
11b3e1be58 fix(menubar): re-check CLI version on every update cycle 2026-05-27 06:35:00 -07:00
iamtoruk
af99516633 feat(menubar): show CLI update banner when a newer version is available 2026-05-27 06:27:49 -07:00