Commit graph

235 commits

Author SHA1 Message Date
ozymandiashh
312605c921 fix(opencode): support custom data dir and db prefix 2026-07-04 22:56:07 +03:00
ozymandiashh
1678cbd883
Merge pull request #599 from Enclavet/fix-cursor-projects
Some checks failed
CI / semgrep (push) Has been cancelled
Fix cursor project parsing
2026-07-03 19:44:48 +03:00
AgentSeal
fea66a80d3 fix(act): report scale, corrupt-journal safety, and under-claim gaps (#606)
Bug-hunt follow-ups on the realized-savings report:

- Scale the displayed estimate to the measured window so the Estimated and
  Realized columns are comparable: mcp/archive use the per-session baseline
  times the post-window session count, read-edit uses deficitThen times the
  same edits denominator as realized (so realized never exceeds it). The
  at-apply estimate stays in --json as estimatedAtApply next to
  estimatedForWindow; the footer states the scaling and that mcp/archive
  realized figures are derived from session counts, not independently
  measured.
- Never crash on a corrupt journal: records without a string status or a
  parseable string `at` are skipped and surfaced ("N malformed records
  skipped"); the optimize header computation is additionally wrapped so any
  error just drops the header.
- Zero post-window sessions now reads "not measurable: no sessions in the
  window yet" instead of measured-zero.
- The optimize header sums only normal-confidence measured rows (under-claim);
  low-confidence rows stay visible in act report only.
- Tests: floor discipline on non-integer mcp and read-edit products (a ceil
  mutation fails), the project-scope keeper subtraction, the archive
  estimate==realized tautology, malformed-journal robustness, and the
  low-confidence header exclusion.
2026-07-03 13:36:36 +02:00
AgentSeal
ad471f3d8c feat(act): realized savings measurement (codeburn act report) (#606)
Capture a trailing-14-day before-baseline when a fix is applied and
re-measure it against the post-apply window so optimize can show realized
numbers next to estimates.

- ActionBaseline (windowDays, capturedAt, estimatedTokens, sessions, metrics)
  persisted by runAction; captured in the optimize --apply flow and at guard
  install time.
- codeburn act report [--json]: applied, not-undone actions older than 3 days,
  re-running the detectors over apply-date-to-now (capped 30 days). Per-kind
  realized deltas: MCP/archive tokens-per-session times saved sessions with
  reverted-by-user detection; read-edit deficit reduction; guard yield split
  labeled correlation. Bash cap is marked not measurable (result sizes are not
  retained). Low confidence under 20 post-window sessions or past a 2x volume
  shift. Realized numbers rounded down, estimate kept visible.
- optimize gains one header line only when a measured action exists, and
  appends "(previously applied <date>, re-flagged)" to re-triggered findings.
  No change for users with no applied actions.

Reuses scanAndDetect helpers over a date-bounded range; exports the token
constants and read/edit tool sets rather than duplicating the math.
2026-07-03 13:04:37 +02:00
AgentSeal
be2470d0e5 fix(guard): dedupe streaming message copies in the incremental cost fold
Claude Code rewrites each assistant message several times as it streams,
every copy carrying the full final usage; the shipped parser dedupes these
last-wins (dedupeStreamingMessageIds) but the guard fold summed every line,
measuring real sessions at 2.5-2.8x their true cost and false-blocking the
hard cap at roughly 40% of the configured spend.

- The session cache now maps message id -> that id's cost contribution and
  each id-carrying line replaces its previous contribution; id-less lines
  keep plain adds. Validated against two real transcripts (90MB and 116MB):
  guard totals now equal the shipped deduped totals exactly.
- Replace semantics also self-heal the trailing-line case: a complete final
  line without its newline is folded but byteOffset stops before it, so the
  next invocation re-reads it as a replace, not a double add.
- editCount becomes a set-once sawEdit boolean so duplicate copies of an
  edit tool_use cannot inflate it; cache schema bumped to v2 (old caches
  cold-reparse once).
- Per-session state moves to guard/sessions/ so a session id can never
  collide with the shared flags.json, dropping the doAllow special case.
- The git-commit detector now requires commit as the git subcommand at a
  command boundary (start of string or line, or ; & |), with intra-command
  gaps that never cross newlines: 'git log --grep commit' and
  'git diff && echo commit' no longer match, while newline-separated
  'git add ...\ngit commit' in multi-line Bash calls now does (verified as
  a real false negative on a live transcript).
- Corrected the statusline protocol note: each stdout line renders as its
  own row; we emit exactly one.
- New tests: streaming-duplicate fixtures (3x identical, growing last-wins,
  incremental replace) asserted equal to a cold shipped-parser computation,
  the trailing-partial-line scenario, the commit-detector matrix, and a
  stale-plan test proving guard-install plans carry expectedHash (a
  concurrent settings edit aborts the apply and survives). The act list CLI
  spawn test now anchors to the repo root from the test file location.
2026-07-03 12:32:46 +02:00
AgentSeal
dc182dc275 feat(guard): opt-in session-time hook pack for Claude Code (#605)
codeburn guard install|uninstall|status|refresh|allow plus the internal
hook/statusline handlers. Off by default, fully local, cleanly removable.

- Settings edits go through the action journal (guard-install / guard-uninstall)
  with expectedHash, appending our entries and removing exactly ours by command
  prefix; a byte-identical uninstall is asserted by test.
- PreToolUse budget cap (soft warn once, hard block with a per-session allow
  override), Stop yield checkpoint (expensive with no edits and no commit, once),
  and a flagged-project SessionStart opener built from the optimize detectors.
- Incremental per-session cache keyed by session id: resumes the transcript
  parse from the last complete-line byte offset via readSessionLines, folding
  only the tail into running totals. Warm invocation ~0.28s against a 90MB
  transcript, dominated by CLI startup; the tail parse itself is negligible.
- All handlers fail open: any error, malformed stdin, or missing transcript
  exits 0 with no output so a broken guard can never block a session.
- Hook protocol verified against the live docs (dated block at the top of
  hooks.ts); zero new dependencies.
2026-07-03 11:54:04 +02:00
AgentSeal
dcd47e65f5 fix(act): refuse to apply a plan whose target changed since it was built
Plans serialize the full post-edit file content at build time, so a
target edited between the preview and the interactive confirm would be
silently overwritten with stale content (recoverable via backup, but a
silent violation of the dry-run-shows-exactly-what-changes principle).

- PlannedChange edit/create variants gain optional expectedHash: sha256
  of the raw on-disk bytes the plan was built from (hashed before the
  BOM strip), null when the plan expects the file to be absent,
  undefined to skip validation (framework back-compat).
- plans.ts sets it everywhere a target is read: ConfigDocs hashes the
  raw buffer it parses, and the marker builders hash the file behind a
  shared markerChange helper.
- runAction validates all expected hashes after snapshotting and before
  the first mutation; a mismatch throws "<path> changed since the plan
  was built; re-run codeburn optimize --apply", removes the backup dir,
  and journals nothing. No rollback is involved since nothing mutated.

Tests: stale edit target rejected with no journal record and no backup
dir left, expects-absent plan rejected when the file appeared, matching
hash still applies, and a hash-less change still applies unchecked.
2026-07-03 11:20:59 +02:00
AgentSeal
92d970196e fix(act): scope project-scope removals, --yes safety, --only validation (#604)
Review fixes plus one coordinator amendment on top of the initial
optimize --apply implementation.

- mcp-project-scope no longer strips a server from every projects[*]
  container in ~/.claude.json: only the top-level entry and the
  finding's cold projects lose it, and the cwd's own config files count
  as cold only when the cwd is in the cold list. The plan preview
  annotates ~/.claude.json with the project entries that lose the
  server.
- unused-mcp findings are now appliable (remove-everywhere mcp-remove
  plans, same as low-coverage).
- Notes are rendered under manual findings too, so an all-unparseable
  config surfaces its parse error instead of a bare "manual".
- ConfigDocs strips a leading UTF-8 BOM before JSON.parse.
- claude-md plans are excluded from --apply --yes (they write to
  cwd/CLAUDE.md); they apply via the interactive picker or an explicit
  --only selection, and --yes prints them as skipped with the reason.
- --only with an unknown or not-appliable id errors to stderr with the
  run's valid appliable ids and exit code 2.
- EOF at the interactive prompt prints "Nothing applied." and exits 0;
  an answer that arrives together with EOF is still honored.

Adds end-to-end tests driving runOptimizeApply over injected stdio and
a fixture home: --yes output with journal ids and undo hints, picker
parsing, --only filtering and validation, EOF, claude-md skip, the
projects[*] over-deletion regression, manual-note rendering, and BOM
configs.
2026-07-03 11:14:06 +02:00
AgentSeal
0dd54366b7 feat(act): optimize --apply for config-class fixes (#604)
Add stable kebab-case finding ids to every optimize detector and to the
JSON report, then route the config-class findings through the action
journal so they can be applied and undone.

- optimize.ts: id on every WasteFinding and OptimizeJsonReport entry;
  appliable findings also carry a machine-readable apply payload (mcp
  server list, project-scope keepers, archive names).
- act/plans.ts: planFor(finding) builds concrete, journaled file
  mutations for mcp-remove, mcp-project-scope, skill/agent/command
  archives, CLAUDE.md rule blocks, and the bash output cap. JSON edits
  preserve the rest of the document (2-space indent, trailing newline);
  unparseable config files are reported and skipped, not fatal.
- act/optimize-apply.ts: codeburn optimize --apply with a plain-readline
  confirm, plus --yes, --dry-run, and --only; prints each journal id and
  the undo hint. --apply with --json exits 2.

Tests cover mcp remove/undo, project-scope global-to-project move,
unparseable-file skip, archive collision suffixing, CLAUDE.md marker
idempotency, a byte-identical dry-run tree hash, and a finding-id guard.
2026-07-03 10:41:29 +02:00
AgentSeal
2eec2fdc31 fix(act): support directory moves for archive actions
The archive actions in the acting epic move whole skill/agent directories,
which the framework could not handle: snapshotFile used copyFile (EISDIR on
a directory) and the afterHash pass used readFile. Snapshots now branch on
lstat, copying directory trees with fs.cp recursive. Directories get an
empty afterHash ('' means no content hash) and drift detection skips the
hash comparison for them; the occupied-original-path check still applies and
a missing movedTo still falls back to the backup. Backup restore likewise
branches: a directory snapshot replaces the target (rm then cp recursive).

Apply-side rename cannot replace a directory destination (ENOTEMPTY), so a
move retries once after clearing the already-snapshotted destination,
rethrowing other codes before any destination damage.

Tests: archive a directory tree and restore it byte-identical, move a
directory onto an existing destination directory (destBackup taken, both
trees restored), and dir-move undo with an occupied original path refusing
without --force and overwriting with it.
2026-07-03 10:14:48 +02:00
AgentSeal
d756eacae5 fix(act): harden undo, apply, and locking against review findings
Undo no longer clobbers files it did not create: an occupied original path
counts as drift for moves (--force removes then renames back), a move
destination that already exists is snapshotted (destBackup) and restored
after the file moves back, and a missing moved file falls back to the source
snapshot so forced undo cannot die mid-loop. Non-move reverts now key on
backup presence instead of the op label, which also restores files that a
create overwrote.

Apply snapshots once per unique path, hashes after all mutations so
overlapping changes carry the final state, and journals inside the rollback
region so a failed append reverts the mutations. The lock is taken with a
single wx write and goes stale by mtime only, so a fresh lock can never be
stolen while empty. Drift reads treat any unreadable target as drift with
its error code, ambiguous id prefixes report the match count, undo --last
skips already-undone records ("Nothing to undo."), and readRecords only
swallows ENOENT.

Tests cover each new behavior plus two mutation probes (forward-order revert
and removed locking both fail the suite), and a CLI-level check of
`act list --json` output shape and ordering.
2026-07-03 10:07:22 +02:00
AgentSeal
883580e6b9 feat(act): action journal, backups, and undo core
Add src/act, a dependency-free framework for journaling and reverting any
file CodeBurn modifies. runAction is the single mutation path: it snapshots
every target, applies the changes, then appends a JSONL record, rolling back
completed steps and journaling nothing if a mutation throws midway. Undo
checks each file against its post-apply sha256 and refuses on drift unless
forced, restoring edits, deletes (created files), and moves. A pid plus
timestamp lockfile (stale after 60s) guards apply and undo, and the journal
reader tolerates corrupt lines with last-line-wins status updates.

Wire up `codeburn act list` (table or --json) and
`codeburn act undo <id|--last> [--force]`. Storage lives under the existing
config home via the config.ts resolver. Tests cover apply with backups and
afterHash, byte-identical undo per op type, drift refusal and --force,
mid-apply rollback, and corrupt-journal tolerance.
2026-07-03 09:39:33 +02:00
Andrew Lee
400ca083a2 fix(cursor): map composers to workspace via composer.composerHeaders
Recent Cursor builds renamed the per-workspace composer list from
ItemTable['composer.composerData'] to 'composer.composerHeaders' (identical
{ allComposers: [{ composerId }] } shape). loadWorkspaceMap only read the old
key, so on these builds the composer->workspace map came back empty and every
composer fell through to the 'cursor' orphan bucket, losing per-project
attribution.

Read both keys and merge their allComposers lists (backward compatible with
older installs). Add a regression test covering the new composer.composerHeaders
key and the legacy composer.composerData key.

Verified against a real workspace state.vscdb: composer.composerData absent,
composer.composerHeaders present with composerIds matching the bubbleId/
composerData rows; 21/22 calls now attribute to the real workspace instead of
the 'cursor' orphan bucket.

AI-Origin: human
2026-07-02 17:51:23 +00:00
AgentSeal
de57bccda3 chore(pricing): refresh LiteLLM snapshot; MiniMax-M3 standard tier
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.
2026-07-02 06:17:01 +02:00
AgentSeal
ab89765afc fix(zed): top threads up to the cumulative counter
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.
2026-07-02 05:53:40 +02:00
AgentSeal
a936e28595 fix(zed): read legacy uncompressed json thread rows
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.
2026-07-02 05:46:59 +02:00
AgentSeal
1f45c1541b feat(providers): add Zed agent provider (#480)
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.
2026-07-02 05:41:16 +02:00
Resham Joshi
f1a4e8cc4f
fix(cursor): use Cursor's real context tokens for input (#574) (#575)
* 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>
2026-07-02 04:53:07 +02:00
ozymandiashh
2fc4d32e66
fix(devin): report friendly GPT model names with effort tier (#585)
Some checks are pending
CI / semgrep (push) Waiting to run
* 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.
2026-07-01 16:01:49 +02:00
ozymandiashh
d869ad86e2
fix(pi): classify native skill loads as Skill, not Read (#588) (#590)
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.
2026-07-01 15:57:31 +02:00
ozymandiashh
61eb5fbca5
fix: scope cache read/write to the selected period (web + devices CLI) (#583) (#586)
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.
2026-07-01 15:53:44 +02:00
Resham Joshi
2b0f781977
feat: add codeburn audit token-source view (#578)
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`.
2026-07-01 13:02:29 +02:00
Resham Joshi
22d5fc1743
perf(web): instant dashboard load, default to today, fast-fail offline peers (#573)
Some checks failed
CI / semgrep (push) Has been cancelled
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.
2026-06-29 04:21:36 +02:00
ozymandiashh
88c1cee467
feat(menubar): expose combined multi-device usage in menubar-json (#566) (#567)
* 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>
2026-06-28 20:59:23 +02:00
ozymandiashh
b24dd31ede
fix(copilot): read VS Code chatSessions for token cost (#555) (#563)
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.
2026-06-28 20:15:02 +02:00
Mr Kevin D Addison
d5002f1deb
Populate OpenCode skills and subagents breakdowns (#557)
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>
2026-06-28 19:46:09 +02:00
ozymandiashh
7cd1f90631
fix(web): reject invalid dashboard periods without exiting (#554)
* 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>
2026-06-28 19:35:46 +02:00
ozymandiashh
fd44d995c7
feat(providers): add open-design provider for per-model usage tracking (#559) 2026-06-28 19:17:23 +02:00
ozymandiashh
2a0edd0d68
feat(pricing): add user price overrides for models (#390) (#560) 2026-06-28 19:07:29 +02:00
Tiago Santos
b424bf1d3f
fix(devin): add missing support for ATIF v1.7 (#570)
* 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>
2026-06-28 19:00:51 +02:00
Resham Joshi
1aa05bdbbe
Raise streaming session cap to 4GB so large Codex sessions are counted (#569)
Some checks are pending
CI / semgrep (push) Waiting to run
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.
2026-06-26 01:16:56 +02:00
Resham Joshi
71b1a9ebce
fix: clean model names in reports and re-hydrate daily cache for new providers (#550)
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.
2026-06-22 03:38:50 +02:00
Jon Jozwiak
99a90cb480
fix(copilot): Correct shell commands and skills/agents display (#527) 2026-06-22 01:14:49 +02:00
ozymandiashh
d54f21d08c
fix(antigravity): read current agy antigravity-cli on-disk layout (#541)
* 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>
2026-06-22 01:08:44 +02:00
ozymandiashh
4dcb7e6c3d
fix(models): price Hermes lowercase glm-5.2 the same as GLM-5.2 (#545) 2026-06-22 01:02:59 +02:00
ozymandiashh
10d911d962
fix(cursor-agent): ingest workspace-less CLI transcript layout (#542)
* 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>
2026-06-22 00:55:01 +02:00
ozymandiashh
7c2d36f1f0
Distinguish gpt-5.3-codex-spark from base GPT-5.3 Codex label (#539)
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
2026-06-22 00:44:56 +02:00
AgentSeal
e80da3139a fix(hermes): use sessions.cwd, flag estimated cost, propagate SQLITE_BUSY
- 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.
2026-06-21 23:50:10 +02:00
Anthony Armijo
645b3c2bdd feat: add Hermes Agent provider
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.
2026-06-21 23:29:22 +02:00
ozymandiashh
a12db6e549
Show real Claude project leaf names; stop stray-.git over-grouping (#540)
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
2026-06-21 22:42:19 +02:00
Resham Joshi
9ce649809a
feat(providers): add ZCode (z.ai GLM-5.2) usage provider (#537)
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
2026-06-20 21:37:32 +02:00
Resham Joshi
98befc1c8b
feat(overview): cache in/out tokens table, roomier tables (#535)
* feat(overview): cache in/out tokens table, roomier tables

Add a Tokens breakdown table (Input / Output / Cache in / Cache out /
Total with share %) so cache write/read are shown clearly instead of
crammed into the Totals line, and widen table cell padding to 2 spaces so
columns aren't congested.

* feat(overview): full comma-grouped numbers for a precise, spacious look

Render token counts as full thousands-grouped integers (e.g.
3,926,923,819) instead of abbreviated M/B, so the roomy tables read like
a precise statement. Update the overview test to match.
2026-06-20 19:22:59 +02:00
Resham Joshi
1dba4e0aa4
feat(web): in-dashboard device discovery, share-from-browser, redesign + hardening (#534)
* feat(web): device discovery + pairing endpoints for the dashboard

Add /api/identity, /api/devices/scan (mDNS browse with confirm code and
paired status), and /api/devices/pair (approve-style pairing via
linkRemote). Backs the Search local devices button so pairing can happen
from the browser instead of the CLI.

* feat(web): two-panel dashboard with eywa-inspired warm theme

Redesign the dashboard into a left sidebar (device switcher + Search
local devices) and a right content panel, restyled to a warm-paper,
forest-green archival theme (serif display numbers, ink text, hairline
borders) in place of the dark midnight theme. The Search local devices
modal discovers nearby devices over mDNS and pairs in one click (approve
on the other device), backed by /api/devices/scan and /api/devices/pair.

* feat(web): use the CodeBurn flame logo in the dashboard header

Replace the placeholder triangle with the flame mark (the repo's
codeburn-symbolic vector), tinted the theme's forest green, and set the
same flame as the favicon.

* feat(web): color the All-devices chart by device, add task-level combined views

In the All devices view the daily chart now stacks by device (one color
per device) instead of by model, and the combined panels add a By task
breakdown plus in/out token detail. Devices that cannot be reached are
hidden entirely rather than shown as error rows.

* feat(web): use the flame image as the dashboard logo and favicon

Replace the inline vector flame with the brand flame PNG
(public/codeburn-flame.png) in the header and as the favicon.

* fix(web): trim flame logo padding and tighten brand spacing

Crop the transparent border off the flame PNG (it was 184px of padding
each side) and reduce the header gap so the mark sits close to the
wordmark.

* feat(web): cost/tokens toggle, cache read/write, full feature panels

Add a Cost/Tokens unit toggle that switches the hero number and the chart
between dollars and tokens. Surface cache write/read token totals as
metric cards. Add per-device panels for subagents, skills, MCP servers,
and a savings / retry-tax / routing-waste summary. The combined view
gains cache totals and the unit toggle too.

* feat(web): use CodeBurn website logos and add community links

Use the website's navbar flame (logo.png) with the Code+Burn wordmark in
the header, and the three-flame app icon (icon.png) as the favicon.
Add Website, Discord, and X links to the sidebar footer.

* fix(web): use the single-flame logo for the favicon too

The three-flame icon was wrong for the tab; use the same single flame as
the navbar everywhere and drop the unused three-flame asset.

* chore(web): set dashboard title to CodeBurn - Local Dashboard

* harden sharing + dashboard for public launch

Security:
- pairing: cap PIN attempts (close window after 5 wrong guesses) so a
  6-digit PIN cannot be brute-forced within the TTL on a 0.0.0.0 listener.
- web dashboard: reject non-loopback Host (defeats DNS rebinding that
  could read unsanitized local usage) and cross-origin requests (CSRF);
  require application/json on the pair endpoint.
- store tokens with 0600 perms (was world-readable), 0700 dir.

Robustness:
- client: per-request timeout so a hung/asleep peer cannot hang a pull;
  pullDevices fetches remotes concurrently and isolates failures.
- dashboard: normalize peer payloads at the boundary and add an error
  boundary so a peer on a different version cannot white-screen the SPA;
  finite-guard fmtNum/compactUsd.

Tests: PIN attempt-cap test added (1269 pass).

* feat(web): share this device from the dashboard, with browser approval

Add a Share this device toggle to the dashboard sidebar. It runs the
secure share server in-process (mTLS + mDNS advertise) so no terminal is
needed, with a Keep sharing always option (persisted; otherwise it stops
after idle). Incoming approve-style pairings are queued and surfaced in
the browser as an Approve/Deny prompt with the matching code, instead of
a terminal prompt. The shared payload is sanitized; start degrades
gracefully if the port is already held by a CLI share.

* fix(sharing): keep a paired device from dropping on a transient pull

- client: 8s -> 15s timeout and a fresh socket per request (agent:false) so
  the pinned-fingerprint check always reads this connection's cert.
- share server: wrap request handling so a getUsage error returns a fast
  500 instead of hanging the caller (which times out and drops the device).
- dashboard: re-pull paired devices every 20s so a brief drop self-heals
  instead of staying gone until you change tabs.

* fix(sharing): re-sanitize remote payloads on receipt

A peer might run an older build that does not strip its own project
names/sessions. Sanitize every remote payload when we receive it too, so
project names never cross into our dashboard regardless of the sender's
version. Aggregate numbers (cost, tokens, models, tools, daily) are kept.

* harden dashboard sharing: version-skew, lifecycle, server errors

- normalize remote daily-history entries so a peer on an older build (daily
  rows missing topModels) can no longer crash the chart / brick the page.
- ShareController: commit always/sharing state only after listen() binds, so
  a port-in-use start no longer reports sharing when it is not.
- attach durable error/tlsClientError handlers to both HTTPS servers so a
  malformed peer connection cannot crash the process.
- reset view/provider selection when the viewed device disappears or lacks
  the selected provider (no more empty/no-selection state on sleep-wake).
- by-device chart: stable per-device color/key so bars do not reshuffle when
  a device drops or returns between polls.

* polish: device identity, approval guard, queue cap, formatting

- give each device a stable unique id (cert fingerprint for remotes,
  'local' for this device); key the sidebar, selection, and by-device
  chart by id so two devices sharing a hostname no longer collide.
- approval prompt: drop a request from the UI the moment it is answered
  so it cannot be double-clicked.
- share controller: cap concurrent pending approvals and allow only one
  per device, so a LAN peer cannot flood the prompt.
- usd(): render negatives as -$5.00, consistent with compactUsd.
2026-06-20 19:02:03 +02:00
Resham Joshi
887374de2c
feat(sharing): securely combine usage across your own devices (#532)
* feat(sharing): pairing, token, and device-identity core

First piece of local device sharing: self-cert fingerprint identity
(trust-on-first-use), a one-time expiring pairing PIN, fingerprint-bound
tokens, and a peer store that authorizes a pull only when both the token
and the TLS peer fingerprint match the same paired device. Pure logic,
fully unit-tested; the TLS share server and host pull build on this.

* feat(sharing): secure mutual-TLS transport + pairing handshake

Add device identity (self-signed cert, persisted; fingerprint = sha256 of
the cert DER), an HTTPS share server (mutual TLS: presents its cert, reads
the client's, and serves /api/usage only when the bearer token AND the
client fingerprint match the same paired peer), a one-time-PIN pairing
endpoint, and a fingerprint-pinning client. Verified end to end on
loopback: PIN pairing, pinned authed pull, and rejection of a wrong PIN,
a token replayed from another device, and a mismatched server
fingerprint. Adds the selfsigned dep (Node cannot generate certs natively).

* feat(sharing): share + devices CLI (pair, pull, combine)

Phase 3 terminal flow: codeburn share runs the secure server on-demand
(stops after 10 min idle; --always to persist, --pair to add a device),
and codeburn devices add <host> --pin <pin> pairs and pins a remote.
codeburn devices pulls this machine plus every paired device, keeps each
separate, and prints a per-device table with a simple summed Combined
row (no server-side merge). Persists identity and peers under the config
dir. Host pair-and-pull flow covered by a loopback integration test.

* feat(sharing): mDNS discovery + approve-style (no-PIN) pairing

Add bonjour-service discovery (advertise/browse over the LAN), a short
confirmation code derived from both cert fingerprints (Bluetooth-style
'do these match?' check), and an approve-style pairing endpoint that
prompts the owner instead of requiring a typed PIN. Loopback-tested:
approved device gets a working token with a matching code on both sides,
declined device is rejected.

* feat(sharing): AirDrop-style discover + approve UX

codeburn share now advertises on the LAN and approves incoming devices
interactively (confirm the matching code, no typed PIN). codeburn devices
add (no args) discovers nearby devices, lets you pick one, shows the
confirmation code, and waits for the owner to approve. Manual
add <host> --pin stays as a fallback for networks that block mDNS.

* feat(sharing): share only aggregates, never project names or paths

Sanitize each device's payload before it leaves the machine: drop
topProjects and topSessions (project names + session detail) and send
only aggregate numbers (cost, tokens, models, tools, activities, daily).
What you are working on stays local; only the totals travel.
2026-06-20 16:24:53 +02:00
Tiago Santos
75c32e6d65
fix: fix and improve test isolation and collision with environment (#530)
* fix: fix and improve test isolation and collision with environment

* docs: remove unnecessary comment

* test(env-isolation): clear CODEBURN_FORCE_MACOS_MAJOR and pin TZ

Two env vars read in src/ were not isolated: CODEBURN_FORCE_MACOS_MAJOR
(now cleared so it cannot leak between tests) and TZ (now pinned to UTC,
since clearing it falls back to the OS zone and would shift date buckets
versus a clean CI runner).

---------

Co-authored-by: AgentSeal <hello@agentseal.org>
2026-06-20 13:42:10 +02:00
Resham Joshi
c55dba2dd2
feat(overview): plain-text monthly usage overview command (#528)
* feat(overview): plain-text monthly usage overview command

Add 'codeburn overview', a copy-pasteable text report. Defaults to the
current month, with --from/--to to filter and --no-color for plain
output. Renders Totals, By tool, Top models, Highest-value days, Top
projects, a daily table, By activity, and Tools, with colored section
headings (stripped under --no-color or when piped).

Reuses the existing session aggregation. Cost gains thousands
separators and tokens roll up to a B unit for readability via
display-only wrappers, without changing the shared formatters. Project
names use the path basename instead of the sanitized full path.

* fix(cli): exit cleanly on EPIPE when the stdout pipe closes early

Piping output to a reader that closes early (| head, quitting less, a
missing command) made stdout writes throw EPIPE and crash with an
unhandled error event. Handle EPIPE on process.stdout and exit 0 so
piping the overview and other commands behaves normally.

* docs(readme): document and highlight the overview command

Add a 'Your month at a glance' section featuring codeburn overview with
examples and sample output, a Commands-table entry, and the provider
flag note.
2026-06-20 11:46:45 +02:00
Resham Joshi
6a26ee8284
feat(opencode): read file-based JSON sessions (OpenCode 1.1+) (#523)
OpenCode 1.1+ stores sessions as file-based JSON under
storage/{session,message,part}/ instead of a SQLite DB, so the
SQLite-only provider discovered zero sessions on current installs and
reported no usage at all.

Add a file-based discovery and parser path, preferred when present and
falling back to the SQLite DB for pre-migration installs. Extract the
shared message-to-call logic (token extraction, tool and bash parsing,
cost) into session-message.ts so the file path, the SQLite path, and
Kilo Code stay identical.
2026-06-19 19:04:51 +02:00
Resham Joshi
ebfb1de0cb
feat(providers): add Grok Build provider (#521)
Adds Grok Build (xAI coding CLI) as an eager provider with caching- and compaction-aware token estimation, real tool/shell extraction, Skills & Agents from spawn_subagent, grok-build pricing, and SuperGrok plan presets. Tested against real sessions; full suite green.
2026-06-19 17:21:41 +02:00
kevinpauer
7c42534910
Add zerostack provider (#519)
* Add zerostack provider scaffold

Register zerostack (gi-dellav/zerostack) as a core provider reading
plain-text sessions from $XDG_DATA_HOME/zerostack/sessions/. Parser is
modeled on pi.ts; on-disk schema is unverified and must be confirmed
against real sessions before a PR (see docs/providers/zerostack.md).

* Rework zerostack provider against real session schema

Replace the initial scaffold (modeled on pi.ts JSONL) with the verified
format from a real local run and the zerostack source:

- Sessions are one JSON file per session under the platform data dir
  (~/Library/Application Support/zerostack/sessions on macOS,
  $XDG_DATA_HOME/zerostack/sessions on Linux; ZS_DATA_DIR overrides).
- Tokens are cumulative session totals, not per-call, so emit one
  ParsedProviderCall per session from total_input/output_tokens.
- Recompute cost via LiteLLM (calculateCost); OpenRouter ids arrive
  route-prefixed and resolve through canonical names.
- zerostack persists only final assistant text, so tools/bash are empty.

Verified against a real session (DeepSeek v4 Pro via OpenRouter):
34.1K in / 961 out / $0.016, matching the recorded total_cost. Adds a
fixture-based test and rewrites the provider doc to match.
2026-06-19 14:44:45 +02:00
Resham Joshi
808a149bd6
feat(export): surface MCP server usage in JSON and CSV exports (#514)
Some checks are pending
CI / semgrep (push) Waiting to run
The export schema emitted tools and shell-commands but no MCP section, and
the tools list is built from extractCoreTools which strips mcp__ names, so
MCP usage never appeared in codeburn export output even when sessions had
MCP activity (issue #496).

Add an mcp section to the JSON export and an mcp.csv to the CSV export,
sourced from session.mcpBreakdown (server -> calls, with share). Mirrors the
existing tools section.

The underlying parse fix (Codex mcp_tool_call_end attribution) landed in
#513; this makes that data visible in exports. Validated on real local data:
the JSON export now reports mcp: [{Server: node_repl, Calls: 5, ...}].

Fixes #496
2026-06-18 23:13:48 +02:00