Bump the version, document the Zed provider (logo, provider doc, data
table row), add the audit and context commands to the README, and
refresh the stale Cursor data-source description to the composer-meter
accounting.
MiniMax moved M3 to tiered pricing: the official standard tier (inputs
up to 512K) is $0.30/$1.20 per M with $0.06 cache reads, doubling above
512K. Upstream LiteLLM now carries the standard tier, so the pinned
expectation follows it; the old $0.60/$2.40 figure is the long-context
tier, not the base price anymore.
Validated against a real thread: request_token_usage is keyed by user
message and covered only part of the requests (cumulative was ~3x the
map sum), so per-request calls are now supplemented by one remainder
entry that brings each thread exactly to cumulative_token_usage. Live
run matches the store token-for-token on all four fields.
Zed's DataType enum carries both zstd (the current save path) and json;
verified against crates/agent/src/db.rs, along with the exact TokenUsage
field names, the SerializedLanguageModel {provider, model} shape, and
RFC3339 updated_at serialization.
Reads Zed's agent threads from the single threads.db SQLite store: each
row's zstd-compressed JSON carries the thread model and per-request
Anthropic-shaped token usage, so calls are emitted per request with
exact input/output/cache fields and priced through the existing engine.
In-progress threads with an empty per-request map fall back to the
cumulative counter. zstd comes from node:zlib, which ships it from
22.15; on older Nodes the provider skips with a notice instead of
failing. On-disk format documented by @chatzinikolakisk in #480 and the
schema verified against a real Zed install.
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.
* feat(context): add experimental context command with per-session token tree
Reconstructs context breakdown for a Claude Code session
from its transcript: messages and tokens by role, block type, and tool,
split into full-session history vs the live window since the last
compaction (following preservedSegment.headUuid).
Block tokens are chars/4 estimates, images estimated from sniffed PNG or
JPEG dimensions, reasoning derived per message from output_tokens minus
visible output since transcripts strip thinking text. The exact context
size comes from the last assistant message's API usage, and the gap to
the estimate is shown as derived system prompt and tool overhead.
* feat(context): show session titles in context --list
Reads the latest ai-title entry (summary entry as fallback for older
sessions) from one tail and one head chunk of each transcript, so the
list stays fast on 100MB files.
* feat(dash): context explorer page with Claude Code and Codex session trees
Adds a Context page to the web dashboard: a navbar toggle, a provider
picker (Claude Code / Codex), the 15 most recent sessions with titles,
and per-session expandable details showing the context token tree, the
exact live context vs window, and a live/full-history scope toggle.
Server side adds /api/context/sessions and /api/context/tree with an
mtime-keyed tree cache, plus a Codex rollout builder: response items
feed the tree, compacted entries (with replacement_history) split live
window from full history, reasoning comes exact from cumulative
token_count totals, and model_context_window gives the real window.
* feat(context): interactive TUI for the context command
codeburn context with no arguments now opens an ink TUI: arrow keys to
move, enter to expand a session's token tree inline (with a spinner
while the transcript reads), tab to switch between Claude Code and
Codex, f to toggle live window vs full history, q to quit. Flag and id
forms keep the plain output for scripts, and non-TTY runs fall back to
the static list. Tree rows are shared with the static renderer via
snapshotRows.
* fix(context): review fixes across window detection, codex parsing, and surfaces
Window sizes are no longer guessed from token counts alone: opus-4-8 and
[1m] model ids map to the 1M window (a 190K opus-4-8 session used to
render as 95% of a 200K window), and Codex omits the percentage instead
of borrowing Anthropic constants when the rollout lacks
model_context_window.
Codex compaction accounting now reads the encrypted compaction item and
developer messages from replacement_history, so post-compaction windows
stop undercounting. Both builders stream with largeLineAsBuffer so one
oversized line cannot silently truncate the walk.
The tree API stops sending session.filePath, serves the flattened rows
so the dash renders the same tree as the CLI and TUI, resolves ids
directly instead of re-scanning every session file per request, and the
tree cache evicts LRU. Session discovery honors CLAUDE_CONFIG_DIRS,
CLAUDE_CONFIG_DIR, and CODEX_HOME, stats files in parallel, and id
lookups stat only matching files.
CLI gains --provider codex and machine-readable --list --json; --full
now opens the TUI in full scope instead of bypassing it. The TUI shows
build errors instead of a frozen spinner and drops the ref-plus-counter
repaint for plain state. The dashboard scopes the usage error banner and
device sidebar to the Usage page.
* fix(cursor): use Cursor's real context tokens for input
Current Cursor builds leave the per-bubble tokenCount at {0,0}, so the provider
fell back to estimating input from visible text plus a second agentKv
content-char pass that double-counted the same conversation. Cursor records its
own tokenizer-accurate context size per conversation in
composerData.promptTokenBreakdown (the number behind the in-app context-window
bar); read that and credit it once per conversation for input instead.
Measured on a real local DB: today's Cursor input went 44,873 -> 168,486 tokens,
matching the sum of per-conversation context. The admin portal still counts
cumulative-per-turn plus cache, which are server-side only, so an opt-in Cursor
API stays the path to exact parity.
Output is a reply-text estimate; agentKv is retained for a tools/bash breakdown
in a follow-up.
* feat(cursor): add tools and bash-command breakdown from agentKv
Cursor logs the agent's tool calls (Read, Grep, Glob, Shell, ...) in agentKv
blobs. Join them to conversations via the turn requestId (carried on the bubble's
$.requestId and inherited positionally by the turn's agentKv rows) and attach each
conversation's tool list and Shell commands to the call that carries its input.
Measured on a real DB: Cursor now reports tools {Read, Grep, Glob, Shell,
SemanticSearch} and the executed shell commands, which were previously empty.
Removes the now-superseded parseAgentKv content-char estimate.
* fix(cursor): price composer-2.5 as Sonnet 4.6
composer-2.5 was missing from the built-in Cursor model aliases, so its usage
showed $0. Map it to claude-sonnet-4-6 like composer-2 (per cursor.com/blog).
* fix(cursor): cache version, model attribution, user message join, tool classification
- Bump CURSOR_CACHE_VERSION to 5: parser semantics changed (parseAgentKv
removed, real context tokens from composerData.promptTokenBreakdown),
stale v4 caches would show double-counted agentKv calls.
- Fix model attribution: real input tokens are credited on user bubbles
(type=1) which carry no modelInfo. Add a pre-pass building composerId ->
model from assistant bubbles so pricing/display uses the conversation's
actual model instead of the default cursor-auto/sonnet-4.5.
- Fix buildUserMessageMap: was keying by JSON conversationId (empty in
current Cursor builds). Now extracts composerId from the bubble key,
matching parseBubbles.
- Add 'Shell' to BASH_TOOLS in classifier: Cursor's agent uses 'Shell'
as the tool name, but it was missing from the bash tool set so Cursor
agent turns with shell commands wouldn't classify as bash/build/test.
- Fix null coalescing in loadComposerInputTokens: r.used ?? r.ctx would
fall through on a valid totalUsedTokens of 0. Use explicit null check.
- Decouple agentTools attachment from input credit: tools/bash were only
attached on the first credited turn (creditedHere), silently dropping
tool usage from subsequent turns in multi-turn conversations.
- Update stale comment about parseAgentKv being kept for a follow-up.
- Add tests for real token crediting, once-per-conversation, fallback,
contextTokensUsed, tool/bash attribution, and model attribution.
* fix(cursor): avoid duplicating aggregated agent tools
* fix(cursor): price house composer models from Cursor's published rates
composer-1/1.5/2/2.5 were proxied to Claude Sonnet, overcounting cost
(~6x for composer-2/2.5). Use Cursor's published per-model rates instead,
and note in the parser why local reads undercount the admin console.
Co-authored-by: AgentSeal <hello@agentseal.org>
* fix(cursor): estimate non-Composer turn input from the agent stream
Non-Composer sessions (e.g. GPT) record no context-window meter and keep
the prompt in the agent stream, so the user bubble's own text is empty.
Those turns hit the 0/0-token fallback with text_length 0 and were dropped
entirely, so that model's traffic never appeared in the report.
loadAgentToolsByComposer now also sums the user-role stream text length per
conversation, and the meterless fallback estimates input from it (chars/4),
credited once per conversation, when the bubble text is empty. Turns with no
stream text are left untouched, so no phantom tokens are invented.
* fix(cursor): stable conversation crediting, restored stream coverage, and cache invalidation
Review fixes for the real-token accounting:
Conversation input now lands on one composer-anchored record
(cursor:composer-input:<id>) timestamped at composerData.createdAt, so
the credited day no longer depends on the parse window or cache floors,
daily-cache gap fills dedupe instead of multiplying, and each
conversation picks exactly one input source (real bubble tokenCounts,
the context meter, the agent stream, or visible text) so sources can
never stack or double count. A zero totalUsedTokens no longer shadows
contextTokensUsed.
The agent stream regained what the parseAgentKv removal dropped: tool
and system rows count as context, stream-only replies count as output,
and sessions with no bubble join are emitted again (DB mtime timestamp,
as before). Block-array content is measured by its text, not its JSON
envelope. Rows written before their requestId appears buffer forward
instead of inheriting the previous conversation, and a system row closes
the boundary. Tool names canonicalize to Bash and commands go through
extractBashCommands so cross-provider breakdowns merge; the classifier
no longer special-cases Shell (which also reclassified Copilot turns).
User bubbles consume their own queue entry so assistant replies pair
with the right question, and every cursor call is flagged
costIsEstimated.
The requestId and model joins ride the existing budgeted bubble scan
instead of two new unbounded full-table decodes, and the composerData
read seeks the key range. SQLITE_BUSY now propagates to the parser's
retry path instead of caching a silently degraded parse.
Upgrades actually take effect: the session cache gets a cursor parse
version, DAILY_CACHE_VERSION bumps to 10 so finalized days re-hydrate
under the new accounting, the cursor results cache bumps to v6, and the
builtin composer rates participate in the price config hash (rates now
cite cursor.com/docs/models).
Verified against a real Cursor store: all metered conversations match
the on-disk meter exactly, narrow and wide parse windows anchor
identically, repeat runs are byte-identical, and agentKv-only sessions
reappear.
---------
Co-authored-by: ozymandiashh <234437643+ozymandiashh@users.noreply.github.com>
* fix(devin): report friendly GPT model names with effort tier (#487)
Devin transcripts label each step with a generation_model in dash form with
an effort suffix (gpt-5-3-codex-xhigh). getShortModelName is keyed in dot form
(gpt-5.3-codex), and its version-boundary check matches the dash id against the
base gpt-5 entry, collapsing every Devin GPT variant to GPT-5.
Normalize the dash form to canonical dot form, map through the short-name table,
and surface the effort tier, e.g. GPT-5.3 Codex (xhigh). Fall back to the
friendly model_name when generation_model is an opaque MODEL_* id, and read
extra.generation_model so ATIF v1.7 transcripts behave like v1.4. Devin rows
in the JSON report and model-efficiency now key on the friendly name.
* fix(devin): stop dated GPT snapshots being mislabeled as versions
The friendly-name path treated any two-number dash id (gpt-4-1106-preview) as a
dotted minor version, producing corrupt labels like 'GPT-4.1106 Preview'.
- Restrict the dash-to-dot rewrite to a single-digit minor at a token boundary,
matching Devin's real ids (gpt-5-3-codex) while leaving dated snapshots alone.
- In getFriendlyGptName, defer to getShortModelName when any suffix token is
purely numeric, so unknown snapshot ids pass through raw instead of being
fabricated into a fake friendly name.
Adds a regression case (gpt-4-1106-preview) to the Devin variant matrix.
* chore: remove accidental node_modules symlink
An absolute-path node_modules symlink was committed by mistake. It leaked a
local username, was a dangling symlink for everyone else, and broke git/npm on
checkout (git won't replace a real node_modules/ with the symlink). It slipped
past .gitignore because the ignore rule is 'node_modules/' with a trailing
slash, which matches a directory but not a symlink.
Pi and OMP have no dedicated skill tool: a native skill load is emitted as
an ordinary `read` tool call whose path points at the skill's SKILL.md (or a
`skill://<name>` URI in newer OMP builds). The parser mapped every read to the
Read tool and never populated `skills`, so Pi/OMP sessions over-counted Reads
AND always showed an empty "Skills & Agents" breakdown.
Detect these reads (basename === 'SKILL.md', or a skill:// URI), extract the
skill name (parent directory, or the URI segment), and surface the call as the
`Skill` tool with the name recorded in `skills` -- exactly how the Claude
parser represents a skill invocation. That both removes the Read over-count and
lets the shared classifier tag the turn `general` so the Skills & Agents
breakdown picks it up (populating a field the dashboard never received before).
The path is read from arguments.path with a defensive arguments.file_path
fallback.
Tests cover SKILL.md / skill:// / file_path detection, a non-skill read staying
a Read, and an end-to-end check that a parsed skill load reaches the classifier
subCategory that feeds skillBreakdown.
The dashboard Cache read/write cards and the `codeburn devices` summarizer
summed the full 365-day history.daily backfill instead of the selected period,
so shorter windows over-counted (today ~197x on real data). Every other metric
already reads the period-scoped `current` block.
buildMenubarPayload now emits period-scoped cacheReadTokens/cacheWriteTokens on
`current` (from PeriodData, alongside inputTokens/outputTokens); the web cards
(single + combined views) and summarizeOneDevice read those instead of reducing
history.daily. The trend chart still uses history.daily. Older peers that omit
the fields fall back to 0 (web) or the windowed daily sum (CLI).
Adds regression tests for both surfaces.
Adopt the more complete responsive implementation from #587 over the
initial one in #582: header controls become a hidden-scrollbar strip
instead of overflowing the page on phones, the sidebar drawer uses
visibility:hidden when closed so its links leave the tab order, aria
attributes on the toggle, 100dvh for mobile browser chrome, and 36px
touch targets.
Add mobile responsive support for the web dashboard:
- Hamburger button in header (hidden on md+) to toggle sidebar
- Sidebar becomes off-canvas drawer on mobile with slide-in transition
- Backdrop overlay with blur when sidebar is open
- Header stays sticky on mobile scroll
- Content area stacks vertically on mobile
- Main content takes full width on mobile
- Sidebar auto-closes when a device is selected
All changes use Tailwind responsive classes (md: breakpoint).
Desktop layout is completely unchanged.
Co-authored-by: yufo <yufo@MacBook-Pro.local>
Adds a per-(provider, model) audit table showing the raw token fields each
provider records (input, output, reasoning, cache write/read) alongside the
totals codeburn displays and prices. It makes the normalizations explicit:
reasoning folds into output, the Anthropic cacheReadInput / OpenAI cached
vocabularies collapse to a per-call max, and cache write/read use the 1.25x /
0.1x input multipliers when a model omits explicit cache rates.
--format json adds per-component cost, the applied rates, both raw cache-read
fields, and recomputed vs attributed cost, so any token-to-cost drift is
visible in one command. Same options as `codeburn models`.
The web server is long-lived, so cache what the per-invocation CLI cannot:
- Cache the parsed local payload in-memory (single-flight, 180s TTL matched to
the parser session cache, expired entries pruned on write). /api/usage and the
local half of /api/devices now return from a Map hit after the first parse.
- Prewarm today at startup and inline that payload into index.html as a
bootstrap, so the SPA paints today's numbers with no round-trip. Only the
local device is embedded; '<' is escaped so a name cannot break the script
tag; served no-store; the seeded view refetches at once so live peers appear.
- Default the web command and dashboard to today.
- Cap the peer connect phase at 3s. req.setTimeout does not abort a stalled TCP
connect, so an offline paired device hung ~75s on the OS timeout; it now
degrades to an unreachable row in ~3s. The cap clears on TCP connect, so the
TLS handshake and the 65s pairing-approval wait are unaffected.
Measured: /api/usage 0.0007s warm (was ~0.22s), /api/devices ~3s with an offline
peer (was ~75s), first paint instant.
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.
* feat(menubar): expose combined multi-device usage in menubar-json (#566)
The macOS menubar reads `codeburn status --format menubar-json`, which only
ever reflected the local machine. Multi-device "Combined" usage existed only in
the terminal `devices` command. This exposes combined + per-device usage through
the menubar-json contract so the menubar can mirror the combined dashboard.
- Extract the per-device summary + combined reduce that was inline in
renderDevices into a reusable pure `summarizeDeviceUsage(results, window?)`.
Error/unreachable devices are excluded from the combined sums (kept in
perDevice); deviceCount counts all, reachableCount counts the reachable ones.
renderDevices now formats from it with byte-identical output.
- Add an optional `combined?: CombinedUsage` block to MenubarPayload (perDevice
list + combined totals incl. calls/sessions). Absent by default.
- Add `--scope local|combined` to `status` (default local). `combined` builds the
local payload, pulls paired devices (pullDevices isolates per-peer failures),
and attaches the summary.
- Correctness guards: reject `--scope combined` with `--days` (non-contiguous,
not representable over the sharing query) and with `--provider`/`--project`/
`--exclude` (the sharing query carries no filters, so peers would report
unfiltered usage). Window-scope the cache-token sum to the selected period
(cache lives in 365-day daily history; current carries no cache counts).
TS/CLI only. The menubar Local/Combined toggle + render is a follow-up.
* fix(menubar): never let combined enrichment break the base local payload
The status --format menubar-json --scope combined path pulls paired devices.
Wrap that best-effort enrichment in a guard so an unexpected failure (corrupt
remotes store, peer I/O) can never take down the base local menubar payload —
on error the local payload is still emitted with combined omitted. Add a test
that a corrupt remote-devices.json still yields a valid combined (local-only) payload.
---------
Co-authored-by: AgentSeal <hello@agentseal.org>
VS Code GitHub Copilot Chat users with no OTel agent-traces.db and no
~/.copilot/session-state/ saw $0.00 cost: codeburn never read VS Code's
core chat persistence, the only on-disk source carrying their token counts.
Add a fourth Copilot source that reads the VS Code chat delta-journals at
workspaceStorage/<hash>/chatSessions/*.jsonl and
globalStorage/emptyWindowChatSessions/*.jsonl. The files are a kind:0
snapshot / kind:1 path-set / kind:2 array-append journal; we replay it
(prototype-pollution-safe: __proto__/prototype/constructor segments are
rejected and containers use Object.create(null)) and read each request's
result.metadata.promptTokens / outputTokens (falling back to
completionTokens) and resolvedModel for pricing.
Dedup so users with multiple sources are not double-counted: prefer OTel
(skip chatSessions discovery when an OTel source is present), and skip a
workspace's legacy GitHub.copilot-chat/transcripts when that workspace has
chatSessions. New env overrides CODEBURN_COPILOT_GLOBAL_STORAGE_DIR and the
existing CODEBURN_COPILOT_WS_STORAGE_DIR keep discovery testable.
Tests cover the reporter's real request shape (promptTokens 32543 /
outputTokens 60 -> non-zero cost), empty sessions, emptyWindow discovery,
append-then-edit replay, requestId dedup, prototype-pollution paths, the
transcript skip, and the OTel-prefer skip.
The Skills & Agents panel was always empty for OpenCode sessions even
when skills and subagents were used. buildAssistantCall (shared by the
OpenCode SQLite and file-based parsers, and Kilo Code) counted the
skill/task tool invocations but never read the skill name or subagent
type from the tool-call input, so the dedicated breakdowns had no names
to aggregate.
In OpenCode's part files the identifier lives in state.input: the skill
tool carries input.name and the task tool carries input.subagent_type.
Extract both in buildAssistantCall and surface them on
ParsedProviderCall.skills / .subagentTypes, then stop hard-coding
skills: [] in providerCallToTurn and providerCallToCachedCall so the
classifier's subCategory and the subagent breakdown receive the data.
Verified against real OpenCode data: the previously empty skills[] and
subagents[] now populate (pipeline-investigation, plan-spec, splunk, ...
and explore/general subagents).
Fixes#556
Co-authored-by: Kevin Addison <kevin.addison3@tesco.com>
* fix(web): reject invalid dashboard periods without exiting
* test(web): assert invalid periods return 400 without exiting; drop redundant /api/devices re-parse
- Add tests/web-dashboard.test.ts: boots the dashboard on an ephemeral port and asserts
/api/usage and /api/devices answer 400 (not process.exit) for a bad period, and that
the server keeps serving afterward. runWebDashboard now returns the http.Server so it
can be exercised in-process; callers that ignore the return value are unaffected.
- /api/devices: resolve periodInfo once instead of validating then re-parsing it inside
localGetUsage (pullDevices invokes localGetUsage with the same already-validated query).
---------
Co-authored-by: AgentSeal <hello@agentseal.org>
* fix: add mssing support for ATIF v1.7
Co-Authored-By: bmcdonough <18721778+bmcdonough@users.noreply.github.com>
* fix(devin): drop unused MCP-coupled types, dead guard, harden metrics fallback
- Remove the @modelcontextprotocol/sdk JsonSchemaType import and the unused
ToolDefinition/FunctionDefinition types it served; type Agent.tool_definitions
as unknown since the parser never reads it (no dependency warranted).
- Remove isImageContentPart: unused, and its `"image" in part` check could never
be true (image parts carry `source`/`type`, not `image`).
- Use the `type` discriminant in isTextContentPart instead of property presence.
- getMetricsFromStep: fall back to legacy metadata.metrics when step.metrics is
present but empty, so a partial metrics object cannot silently zero usage.
- Tests: cover the empty step.metrics fallback and image-only message normalization.
---------
Co-authored-by: bmcdonough <18721778+bmcdonough@users.noreply.github.com>
Co-authored-by: AgentSeal <hello@agentseal.org>
Leads the README with the canonical one-sentence definition (matching codeburn.app, llms.txt, and the JSON-LD) so search and AI answer engines lift a consistent description. The existing hook paragraph stays below.
The 2GB stream cap silently dropped whole Codex session files over the limit, so a live 2.4GB session (about 308M tokens of real gpt-5.5 spend) was excluded and reported as zero. Raise MAX_STREAM_SESSION_FILE_BYTES to 4GB (the line-by-line reader is bounded-memory and handles multi-GB files), and surface any remaining oversize skip via an always-on notice instead of a verbose-gated warning so a dropped session is never silent. Adds a maxBytes option for testability and tests covering the cap boundary.
Browser dashboard (codeburn web), device sharing (share/devices), new providers (Grok Build, ZCode, Hermes Agent, Kiro CLI, zerostack), Codex credit usage, plus CLI and menubar fixes. See CHANGELOG for the full list and contributor credits.
Document codeburn web and device sharing in the README and bump the package version.
Two post-0.9.12 cleanups.
Report model names: getShortModelName resolves a model's pricing alias before looking up its display name, so models priced via a sibling alias (ZCode/Hermes GLM-5.2 via glm-5p1, Grok Build via grok-build-0.1) leaked the internal pricing key as the model name in report --format json and the menubar model breakdown. Grok Composer was unmapped and showed raw. Add SHORT_NAMES entries so each resolves to its real name (GLM-5.2, Grok Build, Grok Composer 2.5 Fast). The models command was already correct because it uses each provider's own modelDisplayName.
Daily cache: providers added since the v8 rollup (Grok, Hermes, ZCode) parse usage that older binaries skipped, so days cached at v8 omit them and report 0 across history. Bump DAILY_CACHE_VERSION and MIN_SUPPORTED_VERSION to 9 to force a one-time full re-hydration so new providers backfill without a manual cache clear.
The agent tab strip never showed Grok, Hermes, or ZCode: ProviderFilter had no cases for them, so they could not become tabs. It also derived the visible tabs from today's providers rather than the range the user actually has selected, so an agent used in the selected window but not today never appeared.
Add the three missing cases (filter keys, CLI arg, accent color) and rebuild visibleFilters to source from the selected period via cost(for:). Every agent with usage in the range now appears, ordered by spend descending, and agents with no usage in the range are omitted.
The pi provider's modelDisplayName matches with a startsWith prefix. With no
gpt-5.5 entry, the model fell into the greedy 'gpt-5' -> 'GPT-5' prefix and
rendered as "GPT-5", while codex/hermes (via the global short-name table) show
"GPT-5.5". Add an explicit gpt-5.5 entry so the longer, exact key wins.
* 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.
* fix(antigravity): read current agy antigravity-cli on-disk layout
* fix(antigravity): propagate SQLITE_BUSY from the .db read so the run retries
---------
Co-authored-by: AgentSeal <hello@agentseal.org>
* fix(cursor-agent): ingest workspace-less CLI transcript layout
* fix(cursor-agent): bump parse version so cached sessions pick up the new ingestion
---------
Co-authored-by: AgentSeal <hello@agentseal.org>
gpt-5.3-codex-spark is a distinct model variant, but the longest-first
startsWith(key+'-') matcher (intended for reasoning suffixes -high/-low)
swept it into the base 'GPT-5.3 Codex' label, making a distinct model look
like the retired base in today/models/status output. Add an explicit
display entry in SHORT_NAMES and the codex provider so the longer key wins;
-high/-low still fall through to the base. Pricing is unaffected (LiteLLM
has no spark entry; cost falls back to the base rate as before).
Fixes#461
- Read the populated sessions.cwd column for project grouping; fall back to
scraping the transcript, then the profile name. The current Hermes build no
longer writes "Current working directory:" lines into messages.
- Set costIsEstimated when the figure is the LiteLLM fallback (no recorded cost).
- Re-throw SQLITE_BUSY from the discovery and read paths so a transient lock on
the live state.db is retried instead of being cached as an empty result.
- Tests: add the cwd column to fixtures; cover cwd grouping, the estimated flag,
and tool-result tool_name extraction.
Reads session-level usage from Hermes SQLite state databases
(~/.hermes/state.db and per-profile state.dbs). Tracks input,
output, cache read/write, reasoning tokens, stored costs, tools,
shell commands, and inferred projects.
- Provider reads both root and profile state databases
- Cost fallback: actual_cost_usd > estimated_cost_usd > LiteLLM pricing
- inferProject filters to user/system messages only
- Discovery query capped at LIMIT 10000
- sanitizeProject handles repeated leading separators
- All five review items from PR #386 addressed
- Tested against real Hermes data (557MB state.db with 198 sessions)
Closes#368. Supersedes #386.
The 'By Project' panel collapsed sibling Claude Code projects to a parent
folder in two cases:
1) resolveCanonicalProjectPath walked up to ANY ancestor .git and used it as
the canonical project. A stray .git in a parent of several projects (a
dotfiles bare repo, an accidental git init) made every sibling resolve to
that parent ('Projects'/'home'). Now only a real linked worktree (.git is a
FILE pointing at <main>/.git/worktrees/<name>) canonicalizes to its main
repo; an ordinary repo or a stray ancestor .git keeps the session's own cwd
as the project. Genuine worktree grouping is preserved.
2) When a Claude session has no cwd metadata, the dir slug was unsanitized by
replacing every '-' with '/', inventing path segments that overview then
split on, so 'Projects-Content-OS' rendered as 'os'. The lossy slug is now
kept intact, and overview only basenames ABSOLUTE paths.
Fixes#493
Reads ZCode CLI usage from ~/.zcode/cli/db/db.sqlite. The model_usage
table has exact per-request tokens; cost is computed from the pricing
table since ZCode stores none (GLM-5.2 runs on the z.ai start-plan
subscription).
- Split cached tokens out of input_tokens (OpenAI-style) so fresh input
and cache reads price correctly
- Attach each turn's tool calls to one request to avoid double-counting
- Map GLM-5.2 to glm-5p1 (GLM-5.1 rate) until LiteLLM lists it
- Register as a lazy SQLite provider; add test and provider doc