Commit graph

714 commits

Author SHA1 Message Date
Resham Joshi
dbe4e94322
fix(menubar): keep cost budget in USD and flag empty custom budget (#508)
Two follow-ups from the budget review:

- Currency consistency: the daily cost budget is defined in USD (the presets and
  the custom field are labeled "$"), but the "exceeded" banner ran the value
  through the display-currency rate, so a non-USD user saw the field and banner
  disagree (e.g. field "$100", banner "EUR 92"). Render the budget label in USD
  via a new asUSD() helper so the picker, field, and banner all agree. The
  over-budget comparison was already USD vs USD and is unchanged.

- Empty custom cue: selecting "Custom..." and leaving the field blank stores 0,
  which silently disables the alert while the picker still shows "Custom...".
  The help text now says "Enter an amount above, or the alert stays off." in
  that state so it does not look armed when it isn't.
2026-06-18 13:30:58 +02:00
Resham Joshi
8794ddeb83
fix(menubar): track isOverDailyBudget so the flame re-tints on budget crossings (#507)
The menubar refresh observation block read displayMetric and the two budget
values, but not the derived isOverDailyBudget (which also depends on
todayPayload). It worked only because payload/menubarPayload happened to touch
the same cache storage. Read isOverDailyBudget explicitly so the flame re-tints
when today's usage crosses the budget, and so the dependency survives any future
cache refactor.
2026-06-18 13:23:57 +02:00
Resham Joshi
8467b104cc
feat(menubar): allow a custom daily budget amount (#497) (#506)
The daily budget could only be one of a few fixed presets. Add a "Custom…"
option to the budget picker that reveals a text field for an exact amount:
dollars for the cost metric, millions of tokens for the token metrics. The
typed value is stored in the same dailyBudget / dailyTokenBudget fields, so
the flame tint and banner pick it up unchanged. On open, a previously saved
non-preset value reselects "Custom…" and pre-fills the field.
2026-06-18 13:17:19 +02:00
Resham Joshi
dab1297001
feat(menubar): make the daily budget alert respect the display metric (#497) (#505)
The daily budget was always measured in dollars, so users tracking the
Tokens or Total Tokens metric had no token-based budget alert. Add a
separate token-denominated daily budget and route the flame tint and the
hero "budget exceeded" banner through one metric-aware check.

- New dailyTokenBudget (stored separately from the cost budget so switching
  metric never reinterprets a threshold).
- Centralize the logic on AppStore: isTokenMetric, activeDailyBudget,
  todayMetricTotal, isOverDailyBudget, dailyBudgetLabel.
- Settings shows a token-threshold picker (1M..100M) for token metrics and
  the existing dollar picker for the cost metric, with metric-aware help text.
- Flame tint and the banner now use isOverDailyBudget / dailyBudgetLabel.
2026-06-18 13:07:28 +02:00
Resham Joshi
b481f81fc4
test(cli-proxy-path): raise timeout for spawn-based tests to fix load flake (#504)
Each test spawns 'tsx src/cli.ts' several times, re-transpiling the CLI on
every spawn. Under full-suite parallel load these spawns contend for CPU and
the slowest test could exceed the 5s default timeout, even though it runs in
~1.7s in isolation and the spawnSync calls already allow 30s each. Set the
file testTimeout to 30s so the wrapper matches the per-spawn cap. Full suite
is green across repeated runs.
2026-06-18 12:57:44 +02:00
Resham Joshi
a9c0273a42
test(usage-aggregator): build payload against an empty HOME to fix timeout flake (#503)
buildMenubarPayloadForRange('today') parsed the developer's real on-disk
session data under a fixed 5s timeout, so on a heavy-usage day the test
timed out locally (it stayed green on CI, which has no real data). Point
HOME and the config dirs at an empty temp dir so the payload is built from
an empty dataset: deterministic and fast (~0.3s) while still asserting the
payload shape and the optimize:false short-circuit.
2026-06-18 12:50:39 +02:00
Resham Joshi
59efa7c824
Merge pull request #502 from getagentseal/kiro-cli-sessions-rebased
feat(kiro): add Kiro CLI session discovery and parsing (#476)
2026-06-18 12:44:29 +02:00
Wurzer, Wilhelm
89b365c269 feat(kiro): add Kiro CLI session discovery and parsing
The Kiro provider previously only detected sessions from the Kiro IDE
(VS Code-based) stored in globalStorage. This adds support for Kiro CLI
(`kiro-cli chat`) sessions stored in ~/.kiro/sessions/cli/.

CLI sessions use a different format:
- .jsonl files with Prompt/AssistantMessage/ToolResults entries
- Companion .json files with session metadata, model info, and
  per-turn metering usage (credits)

Changes:
- Add CLI session discovery (scans ~/.kiro/sessions/cli/ for .jsonl)
- Add CLI JSONL parser that extracts turns, tools, and cost from
  metering_usage credits
- Add CLI tool name mappings (read, write, shell, grep, glob)
- Make CLI sessions dir configurable via third param to
  createKiroProvider for testability
- Add unit tests for CLI session discovery and parsing
2026-06-18 12:42:58 +02:00
Resham Joshi
c5bf09921b
fix(cli): validate --provider and make non-TTY report deterministic (#501)
Two CLI polish fixes found during a full command sweep:

- Validate --provider against the known provider set (report, status, today,
  month, export, optimize, compare, models). An unknown provider previously
  produced a silently empty report; it now exits 1 with a clear message
  listing valid values, matching how --period and --format already behave.
  Provider names are exposed via a lazy allProviderNames() helper so importing
  the providers module never depends on every provider object being defined.

- Make the non-interactive report/today/month render deterministic: yield a
  tick so ink flushes the one-shot frame to stdout before unmounting, instead
  of unmounting synchronously which could race the flush and drop the output.

Adds CLI provider-validation tests (rejection, valid name, all sentinel, and
a drift guard that allProviderNames covers every loadable provider).
2026-06-18 12:39:22 +02:00
Resham Joshi
15536ed71b
Merge pull request #500 from getagentseal/json-optimize-yield-rebased
feat(cli): JSON output for optimize and yield (#492)
2026-06-18 12:14:55 +02:00
barry
a2947a48be Add JSON output for optimize and yield
Dashboard integrations need machine-readable optimize findings and yield ratios without parsing terminal output. This threads the existing analysis results into conservative JSON serializers while preserving text output as the default.

Constraint: Issue #419 asks for dashboard-friendly JSON for optimize and yield

Rejected: Build a separate analysis path | would risk drift from terminal output

Confidence: high

Scope-risk: narrow

Tested: npm test

Tested: npm run build

Tested: npm run dev -- optimize -p today --format json

Tested: npm run dev -- yield -p today --format json

Not-tested: Real downstream dashboard integration
2026-06-18 12:13:17 +02:00
Resham Joshi
49d1ddc376
Merge pull request #499 from getagentseal/reduce-status-parsing-patched
fix(menubar): reduce repeated status parsing (#486) + clock-skew guard
2026-06-18 12:08:51 +02:00
AgentSeal
7f44eeb1ba fix(daily-cache): purge cached today/future entries on hydration
Keeps the perf win from PR #486 (yesterday stays cached, no re-parse every
run) but restores a narrow safety guard: drop any cached entry dated today or
later before computing the gap range. The cache only ever stores complete past
days, so a >= today entry can only come from the clock moving backward or a
stale older cache; without this it would be served frozen instead of being
recomputed live. Yesterday and earlier are untouched, so this does not
re-parse already-cached days. Adds a regression test.
2026-06-18 12:07:35 +02:00
Resham Joshi
7b484c1644
Merge pull request #498 from getagentseal/copilot-otel-patched
feat(copilot): OTel cache-token parsing (#477) + maintainer review fixes
2026-06-18 11:57:27 +02:00
AgentSeal
9917a8a181 refactor(copilot): apply review fixes to OTel parsing PR #477
Maintainer review fixes on top of the OTel cache-token work:

- Remove all DEBUG_OTEL console.warn scaffolding from parser.ts and
  copilot.ts (gated but unlike the rest of the codebase).
- Parameterize the spans IN (...) query instead of string-interpolating
  trace IDs.
- Fold the per-chat-span metadata query into the trace-span query to drop
  the N+1 (one query per chat span -> one per conversation).
- Guard epochToISO against null/NaN/0 so a malformed start_time_ms row no
  longer throws on new Date(NaN).toISOString().
- Remove dead code: parseSpanAttributes, OTelSpanRow, and the unreachable
  `if (!db) return`; drop unused catch bindings.
- Note in parseProviderSources that the non-durable append path assumes
  unique source paths.
- Document the OTel source in docs/providers/copilot.md: Node 22+
  requirement, durable-cache monotonic totals, and the one-time
  parse-version cache reset on upgrade.
2026-06-18 11:56:04 +02:00
steelp02
d68a214c76
fix(copilot): Implement durable sources for OTel DB parsing.
Signed-off-by: steelp02 <pieter.steel@pfizer.com>
2026-06-17 21:42:40 +02:00
AgentSeal
a5e443ed38 docs: move Claude for Open Source badge to top of README
Some checks failed
CI / semgrep (push) Has been cancelled
2026-06-16 00:56:01 +02:00
AgentSeal
653f2e1271 docs: make Claude for Open Source recipient badge visible 2026-06-16 00:54:47 +02:00
AgentSeal
42222b4ab5 docs: note Claude for Open Source program support in README 2026-06-16 00:52:32 +02:00
Resham Joshi
450f6575b6
Update README.md 2026-06-16 00:40:23 +02:00
Resham Joshi
421eea3bef
Update README.md 2026-06-16 00:39:48 +02:00
Resham Joshi
694e8ce79b
fix(menubar): read Claude keychain via security CLI on silent refresh (#490) (#491)
Some checks failed
CI / semgrep (push) Has been cancelled
Background token refreshes re-read the "Claude Code-credentials" keychain
item via the Security framework. On macOS Sierra+, access is governed by the
item's partition list, not the legacy "Always Allow" ACL. Claude Code resets
that partition list every time it rotates the credential, dropping our app
from the allowed set, so the next read raises a fresh keychain password
prompt. On a heavy usage day this fires dozens of times. The LAContext
interactionNotAllowed flag we relied on does not suppress that prompt for a
plain generic-password item.

Route the silent path (proactive refresh and post-401 re-read) through
/usr/bin/security instead. The Apple-signed security binary sits in the
item's apple-tool: partition, so it reads the secret without prompting and
without depending on the user's ACL grant. It is read-only and never spends
the shared refresh token, preserving the existing invariant that the Claude
CLI owns the grant.

The user-initiated bootstrap keeps the framework read, where a single
consent prompt is expected. Drops the now-unused LocalAuthentication import.
2026-06-14 10:31:56 +02:00
Vaibhav Arora
0ac09d8909 fix(menubar): reduce repeated status parsing
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-13 00:07:04 +05:30
Stephan Leicht Vogt
04f3fc1060
fix(menubar): support installer HTTP proxies (#475)
Some checks failed
CI / semgrep (push) Has been cancelled
Route the menubar installer's GitHub downloads through an undici ProxyAgent when HTTP(S)_PROXY is set, honoring NO_PROXY for bypass. Falls back to direct fetch when no proxy is configured. Closes #473.
2026-06-11 14:13:10 +02:00
theparlor
81fdeee5d6
fix(mac): restore right-click status-item menu on macOS 27 (#472)
Present the right-click menu from a global right-mouse-down monitor (hit-tested against the status-item window) via NSMenu.popUp, since macOS 27 no longer routes right-mouse events to the status-item action. Legacy action path retained for macOS <= 26, guarded by a debounce. Remove the global monitor on terminate.
2026-06-11 09:56:56 +02:00
steelp02
7d067e82b2
feat(providers): [copilot.ts]Add OTel cache token parsing including tests and regression checks.
Signed-off-by: steelp02 <pieter.steel@pfizer.com>
2026-06-10 22:58:12 +02:00
iamtoruk
f1bf7a197b feat(report): Claude-scoped agent-type breakdown
Some checks are pending
CI / semgrep (push) Waiting to run
Surfaces real subagent-transcript spend grouped by agentType
(workflow-subagent / Explore / general-purpose / …), read from each agent's
sibling .meta.json (cached on CachedFile; session cache bumped 3→4). This makes
ultra/workflow usage visible — the existing Task-input-based "subagents" section
never sees workflow agents. Shown in `report --format json` (claudeAgentTypes)
and a "Claude Agent Types" dashboard panel that renders only when Claude agent
transcripts exist, so it never appears for the other providers.
2026-06-10 11:45:28 +02:00
iamtoruk
aa07d5c939 fix(parser): count workflow/ultracode subagent usage (#470)
collectJsonlFiles only read agent transcripts directly under `subagents/`, but
the workflow feature nests them at `subagents/workflows/<wf>/agent-*.jsonl`, so
their tokens were dropped — undercounting whenever workflow/ultracode was on.
Walk the `subagents/` subtree recursively. Verified on real data: a workflow-
using project recovered ~16% of its cost, with no change to other projects.
2026-06-10 11:45:28 +02:00
iamtoruk
edc07c6149 chore(ci): bump GitHub Actions to Node 24 versions
Some checks are pending
CI / semgrep (push) Waiting to run
GitHub forces Node 20 actions to Node 24 on 2026-06-16. Bump to current
node24 runtimes (inputs verified unchanged):
- actions/checkout v4 -> v6
- actions/upload-artifact v4 -> v7
- softprops/action-gh-release v2 -> v3
2026-06-10 01:11:08 +02:00
iamtoruk
1e96a27df7 chore(release): 0.9.12
Changelog for 0.9.12, README node badge aligned to the real >=22 requirement
(node:sqlite for Cursor/OpenCode), and ws lockfile bumped to patched 8.21.0
(npm audit clean). No source changes.
2026-06-10 00:54:26 +02:00
iamtoruk
f0768d4c32 fix(parser): isolate Claude per-file parse failures
The per-file failure isolation #441 added to the provider path was missing on
the Claude path: a throw in groupIntoTurns/canonicalization propagated up and
emptied the whole daily-history backfill. Wrap the per-file parse in try/catch
+ a failed marker, mirroring the provider path; one bad file no longer wipes
the trend.
2026-06-10 00:09:31 +02:00
iamtoruk
e7c0e21846 fix(vercel-gateway): route network sources through aggregation
The synthetic source path has no on-disk file, so fingerprintFile returned
null and parseProviderSources dropped the source before parsing — the provider
always reported $0 even with a key set. Add a `network` provider flag; such
sources skip the fingerprint gate, re-fetch each run with a synthetic
fingerprint, and keep the gateway's reported cost (instead of recomputing from
tokens, which would be $0 for any model codeburn can't price). Adds an
end-to-end test through parseAllSessions.
2026-06-10 00:09:31 +02:00
iamtoruk
f4fd6a1640 fix(pricing): restore Mythos 5 pricing dropped by gap-fill cleanup
Mythos 5 ($10/$50) is not in LiteLLM or the models.dev/OpenRouter gap-fill
yet (Fable is), so dropping the manual patch left it priced at $0. Add it to
MANUAL_ENTRIES + the snapshot and map the "Mythos 5" display name.
2026-06-10 00:09:31 +02:00
Gaurav Sisodia
a2e18d79e7
fix(cursor): period-aligned lookback; add Vercel AI Gateway provider (#432)
Opt-in Vercel AI Gateway provider (AI_GATEWAY_API_KEY/VERCEL_OIDC_TOKEN), inert without a key. Cursor lookback now period-aligned with a 6-month floor. Gateway fetch hardened on merge with an 8s timeout and try/catch fallback.
2026-06-09 23:01:43 +02:00
KirDE
35a8518518
feat(antigravity-ide): added support for antigravity IDE (#418) 2026-06-09 22:12:01 +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
e83160f415
feat(pricing): proxy-aware cost attribution for subscription-backed Claude (#417) (#459)
Claude Code routed through a GitHub-Copilot-backed proxy (ANTHROPIC_BASE_URL
-> claude-code-over-github-copilot / claudegate) costs ~$0 marginal but was
priced at full Anthropic API rates, producing a misleading cost figure. The
JSONL records only the model name and no endpoint, so proxying cannot be
auto-detected; the user declares it.

Add a `proxyPaths` config (managed via a new `codeburn proxy-path` subcommand)
listing project directories served through a subscription proxy. Sessions whose
canonical path is under one keep their full API-rate totalCostUSD (the billable
would-be figure is never destroyed) and additionally report totalProxiedCostUSD,
so the JSON report overview exposes cost / proxiedCost / netCost
(netCost = cost - proxiedCost). With no proxyPaths configured the new fields are
0 and every existing consumer is unchanged.

Design: "full cost, flagged" was chosen over zeroing cost so a misconfigured or
since-changed path can never silently erase real Anthropic spend. Attribution is
project-level (one canonical path per project), computed in a single helper
(summarizeProject) that all ProjectSummary builders route through, including the
cross-provider merge in parseAllSessions (re-derived from the merged total so a
repo used with both Claude and Codex stays correct). The in-memory session-cache
key folds in a proxy-config hash so toggling proxyPaths in a long-lived process
cannot serve stale attribution. Path matching is segment-boundary anchored
(/foo does not match /foobar), trailing-slash and backslash tolerant, leading-
slash agnostic (so a non-Claude provider's slash-stripped path matches the same
way Claude's absolute path does), and case-folded only on case-insensitive
filesystems (macOS/Windows, not Linux). The proxy-path CLI sanitizes a
hand-edited config.json (non-array / non-string entries) rather than crashing.

Tested: isProxiedPath matching matrix (boundary, case, Windows, empty, root,
multi-path, leading-slash form); config-hash distinctness/order-independence;
end-to-end attribution through parseAllSessions incl. the critical negative
cases (real spend must not be zeroed); cross-provider Claude+Codex merge;
Codex-only project under a proxy path; date-range-filtered attribution;
cache-staleness after a config change; and the proxy-path CLI add/list/remove,
malformed-config robustness, and the report --format json overview.

Scope note: proxiedCost/netCost currently surface in `report --format json`
only; wiring them into the TUI dashboard and menubar payload is a follow-up.
2026-06-09 21:30:57 +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
Resham Joshi
ad251cfa3d
chore(pricing): drop manual Fable/Mythos patch; fable now gap-filled from models.dev/OpenRouter; keep Fable 5 name (#464) 2026-06-09 21:22:38 +02:00
Resham Joshi
a385f65dee
feat(pricing): automatic gap-fill from models.dev and OpenRouter (#457)
Keep model pricing automatic instead of hand-coding new models. The bundler
now layers three sources in priority order: LiteLLM (broad list prices),
hand-curated MANUAL_ENTRIES overrides, then a separate last-resort fallback
file gap-filled from models.dev first-party makers (official direct prices)
and OpenRouter (resale backstop). New models such as MiniMax-M3 ($0.6/$2.4)
now price correctly with no per-model code.

The fallback is written to its own pricing-fallback.json and consulted only
case-insensitively as the final step in getModelCosts, so a reseller variant
name can never shadow a canonical or aliased match.

Fixes surfaced while building and verifying this:
- Alias precedence: LiteLLM ships snowflake/claude-4-opus ($5), which the
  bundler strips to a bare claude-4-opus key that shadowed the curated alias
  to claude-opus-4 ($15 official). An explicit alias for a bare name now wins
  over a coincidental stripped reseller key; the prefixed gateway price is
  still returned for the fully-qualified id.
- Zero-stub guard: LiteLLM [0,0] price stubs (e.g. GigaChat-2-Max) are
  excluded from the case-insensitive index so a case-mismatched query stays
  null and keeps firing the unknown-model warning instead of silently
  reporting $0.
- Negative-sentinel guard: OpenRouter returns -1 for variable/BYOK-priced
  models. The bundler now rejects any non-positive rate pair (and strips the
  sentinel from cache fields) so a negative per-token cost can never ship and
  subtract from spend totals.

Bundler hardening: bareKey strips @pin and date suffixes to match the runtime
canonical form, seen-set dedupes on both full and bare key shapes, and it logs
MANUAL_ENTRIES now covered upstream plus models.dev allowlist drift. Extracted
buildCosts so the cache-cost heuristics live in one place. Added a data-hygiene
test that fails CI if a rebundle reintroduces negative, free, or unreachable
fallback entries.
2026-06-09 21:17:23 +02:00
Resham Joshi
c36f3afa76
chore(pricing): temp Fable 5 + Mythos 5 launch pricing ($10/$50 per M) + names until LiteLLM indexes them (#463) 2026-06-09 20:51:51 +02:00
Resham Joshi
8e460096e3
fix(codex): content-address fork dedupe key to stop undercounting divergent events (#458)
Some checks failed
CI / semgrep (push) Has been cancelled
Forked Codex sessions copy the parent's entire token_count history
(re-timestamped), so those replays are keyed into the parent's namespace
(forkedFromId) and deduped to avoid double-counting the parent's spend. But the
key used cumulativeTotal alone, which is too coarse: a genuine post-divergence
fork event whose running total coincidentally equals some parent total collided
with it and was dropped, undercounting real spend.

Add the cumulative token breakdown (input, cached, output, reasoning) to the
dedupe key. A fork replays those cumulative figures verbatim, so a true replay
still collides and parent and fork are not double-counted, while genuinely
different work that happens to reach the same cumulative total stays distinct.

The breakdown must come from the CUMULATIVE totals, not the per-event deltas:
the deltas are computed against a running `prev` that the fork advances
differently once the 5s replay cutoff skips some events, so a delta-based key
would spuriously diverge on a replay and double-count it (in the total-only
fallback branch).

This is the correct fix for issue #413. The reported "undercount" is mostly
replayed parent history being correctly credited to the parent, not lost; the
issue's suggested change (key on the fork's own session id) would instead
re-double-count the entire replayed history. Three regression tests pin the
invariants: a replay past the 5s cutoff counts once, a divergent fork event
sharing a parent's cumulative total is kept, and a total-only fork whose replay
straddles the cutoff does not overcount.
2026-06-07 07:29:42 +02:00
Resham Joshi
0edc8644aa
test(copilot): add JetBrains coverage; fix Windows path inference (#433 follow-up) (#456)
Some checks are pending
CI / semgrep (push) Waiting to run
Follow-up to #433. Adds tests for the JetBrains format: discovery of a jb
chat.jsonl, parsing into a call with inferred model/tools/user message, the
isJetBrainsFormat routing guard (legacy user.message files must not route to
the JB parser), and the id-less dedup fallback.

Also fixes inferJBProjectFromContent: it split homedir() on the platform
separator but the recorded tool path on a fixed '/', so project inference
always fell back to the raw session id on Windows. Split both on either
separator.
2026-06-06 23:32:56 +02:00
Zuber Khan
e7920fc6aa
feat: add JetBrains (IntelliJ/DataGrip) session discovery and parsing (#433)
* feat(copilot): add JetBrains (IntelliJ/DataGrip) session discovery and parsing
The Copilot provider previously only discovered sessions from:
- ~/.copilot/session-state/ (legacy VS Code format)
- VS Code workspace storage directories
IntelliJ and other JetBrains IDEs store Copilot chat sessions in
~/.copilot/jb/<session-id>/partition-*.jsonl using a slightly different
event format. This commit adds:
- discoverJetBrainsSessions(): finds all .jsonl files under ~/.copilot/jb/
- isJetBrainsFormat(): detects JB event format (no session.start header)
- parseJetBrainsEvents(): parses JB events (assistant.message with text/
  thinking, tool.execution_start for tool names, tool call ID prefixes
  for model inference)
- inferJBProjectName(): extracts project name from tool execution paths
Closes #N/A

* fix: address PR review feedback for JetBrains Copilot support
- Add 'sep' to path import (fixes ReferenceError in project inference)
- Use incrementing index as dedup key fallback when messageId is absent
  to prevent undercounting tokens/cost
- Move project inference to parse time using already-loaded content
  (avoids bypassing readSessionFile's 128MB safety cap)
- Tighten isJetBrainsFormat to only match user.message_rendered and
  partition.created (avoids false-positive on legacy files starting
  with user.message)
- Add dedicated inferModelFromEvents for JB format that checks
  data.model (100x weight) and tool call ID prefixes on
  tool.execution_start/complete events
- Remove dead first-pass loop that was never read
- Add jbDirOverride param to createCopilotProvider for test fixtures
2026-06-06 23:28:42 +02:00
Resham Joshi
4a384cd31d
feat(menubar): add macOS app icon (#455)
Adds assets/menubar-logo.png and generates AppIcon.icns (all standard sizes
via sips + iconutil) in package-app.sh, so the menubar app ships with a real
icon instead of the generic one. Info.plist already referenced AppIcon.

Icon and bundler change from @tvcsantos's #439; the unrelated tsconfig.json
change in that PR was left out.
2026-06-06 23:16:03 +02:00
Resham Joshi
fda117face
Merge pull request #454 from getagentseal/integrate/local-model-savings
feat(cli): local-model cost savings, re-homed onto current main (#421)
2026-06-06 22:38:23 +02:00
AgentSeal
a318375c6e Only show the Saved column when local-model savings exist
Without a model-savings mapping the Saved column was an unlabeled column of
dashes between Cost and Calls (menubar) and a dim '-' column (CLI models
report). Show it only when at least one model has savingsUSD > 0, so users
with no mapping keep the plain Cost/Calls layout. The menubar Hero caption was
already conditional.
2026-06-06 22:26:29 +02:00
AgentSeal
5a41f39944 Merge PR #423 (local-model savings) onto main, re-homing payload savings into usage-aggregator
Resolves conflicts from the post-PR refactor that moved buildPeriodData,
hydrateCache, and the menubar payload builder out of main.ts into
usage-aggregator.ts. The PR's savings additions to those functions are
re-homed there; config.ts keeps both new fields; parser.ts keeps both imports;
redact.ts session details carry savingsUSD.
2026-06-06 22:14:13 +02:00
Resham Joshi
efa8593cc5
fix(parser): cache parse failures so broken files aren't re-read every run (#441 follow-up) (#453)
Some checks are pending
CI / semgrep (push) Waiting to run
Follow-up to #450. When a session file throws during parse it was excluded but
left uncached, so every refresh (~4x/min in the menubar) re-read and re-parsed
it, and only the first failing file per provider was ever surfaced.

- Add a negative-result marker: a failed file is cached as { fingerprint,
  turns: [], failed: true }. reconcileFile treats it as 'unchanged' at the same
  fingerprint, so it's skipped (no re-read) until the file changes. Empty turns
  => contributes no usage.
- Warn per offending file (with its path), capped at 5 per provider per run,
  instead of once-per-provider — so a systemic break surfaces more than one file
  without flooding. Cached markers keep it quiet across refreshes.

Tests: marker round-trips through save/load; reconcile stays 'unchanged' at the
same fingerprint and re-parses when the file changes.
2026-06-06 21:10:10 +02:00