Commit graph

112 commits

Author SHA1 Message Date
AgentSeal
7aefd674fc fix: drop better-sqlite3 to remove deprecated prebuild-install (#75)
npm was warning on every install that prebuild-install@7.1.3 is no
longer maintained. prebuild-install ships as a transitive dependency
of better-sqlite3 and upstream PR #1446 to replace it is still open,
so we switch to Node's built-in node:sqlite module (stable in Node 24,
experimental in Node 22/23) and remove the better-sqlite3 dep entirely.

- src/sqlite.ts: uses DatabaseSync from node:sqlite. The one-shot
  ExperimentalWarning about SQLite on Node 22/23 is silenced for that
  specific warning; other warnings pass through unchanged.
- package.json: engines.node bumped to >=22 (Node 20 EOL 2026-04-30),
  better-sqlite3 and @types/better-sqlite3 removed, @types/node added
  (it was coming in transitively via @types/better-sqlite3).
- tests/providers/opencode.test.ts: fixture DB creation switched to
  node:sqlite (API parity for the CREATE TABLE + INSERT + prepare
  path we use).

End-user install footprint shrinks from 167 to 40 packages and prints
zero deprecation warnings.

Credit: @primeminister for the report.
2026-04-18 01:26:23 -07:00
Resham Joshi
03f12ce81f fix(status): bucket Today/Month by local date, not UTC
renderStatusBar computed `today` via `new Date().toISOString().slice(0,10)`,
which is the UTC date. Session timestamps are also UTC ISO strings, but
the user's expectation of "today" is their wall-clock day. During the
window between local midnight and UTC midnight (e.g. 17:00 PDT on
2026-04-17, which is already 00:00 UTC on 2026-04-18), every session
bucketed under local April 17 missed the UTC-April-18 filter and the
status bar read `Today $0.00  0 calls` even while `--format json`
and the menubar app correctly showed the spend.

Both sides of the comparison now use the local date of each session
timestamp, so the terminal status and the JSON / menubar paths agree.
Verified at UTC midnight (the regression moment that surfaced the bug):

  Before: Today $0.0000  0 calls
  After:  Today $339.87  1839 calls

Caught during the fresh-clone review of the menubar PR.
2026-04-17 17:05:08 -07:00
Resham Joshi
495a254338 feat(mac): native Swift menubar app + one-command install
Introduces mac/ with a native SwiftUI menubar app that replaces the
previous SwiftBar plugin entirely. Install via `npx codeburn menubar`,
which downloads the .app from GitHub Releases, strips Gatekeeper
quarantine, and drops it into ~/Applications.

Highlights

- mac/ SwiftUI app: agent tabs, Today/7/30/Month/All period switcher,
  Trend/Forecast/Pulse/Stats/Plan insights, activity + model
  breakdowns, optimize findings, CSV/JSON export, Star-on-GitHub
  banner, live 60s refresh, instant currency switching with offline FX
  cache.
- Security: CodeburnCLI argv-based spawn (no shell interpretation),
  SafeFile symlink guards + O_NOFOLLOW writes, FX rate clamping to
  [0.0001, 1_000_000], keychain filtered to account == "default",
  removed byte-window credential log, in-flight refresh guard, POSIX
  flock on config.json writes, TerminalLauncher validates argv before
  AppleScript interpolation.
- Performance: shared static NumberFormatter (thousands of allocations
  per popover redraw eliminated), concurrent pipe drain with 20 MB cap
  + 60s timeout in DataClient, Observation-tracked reactive UI, 5-min
  payload cache keyed on (period, provider).
- CLI: new `codeburn menubar` subcommand that downloads + installs +
  launches the .app (no clone, no build). New `status --format
  menubar-json` payload builder. `export` rewritten to produce a
  folder of one-table-per-file CSVs with a `.codeburn-export` marker
  so arbitrary -o paths cannot be silently deleted.
- Removed: src/menubar.ts (SwiftBar plugin generator),
  install-menubar / uninstall-menubar subcommands, `status --format
  menubar` directive output, tests/menubar.test.ts,
  tests/security/menubar-injection.test.ts.
- Release: .github/workflows/release-menubar.yml builds universal
  binary, assembles .app, ad-hoc signs, zips, uploads on mac-v* tag
  push. Runs on the free macos-latest runner.

Tests

- 230 TypeScript tests pass
- 10 Swift CapacityEstimator tests pass
- TypeScript typecheck clean
- Swift release build clean
2026-04-17 16:55:56 -07:00
AgentSeal
41c84b1e51
Merge pull request #66 from jeisaacs/fix/menubar-node-version-path
fix: prepend install-time node bin dir to menubar plugin PATH
2026-04-17 14:21:58 +02:00
AgentSeal
77257bcb89
Merge pull request #68 from lfl1337/fix/remove-claudeignore-references
docs(optimize): remove references to .claudeignore (#61)
2026-04-17 14:20:50 +02:00
Ninym
8f5927153e feat(cli): add --verbose flag for stderr warnings
Sets CODEBURN_VERBOSE=1 via commander preAction, which the fs-utils
helpers check before emitting stderr lines on skipped or failed reads.
Closes LOW-1 from the 2026-04-16 audit.
2026-04-17 08:32:20 +02:00
Ninym
646635c262 fix(menubar): sanitize SwiftBar labels via allowlist
Replaces any character outside [A-Za-z0-9 ._/-] with ? in model and
category labels and truncates to 14 chars before padEnd. Closes the
MEDIUM-2 finding from the 2026-04-16 audit: an attacker-controlled
JSONL with a crafted model name no longer injects SwiftBar directives
or ANSI escapes.
2026-04-17 08:32:20 +02:00
Ninym
216782391a fix(optimize): use bounded read helpers
All four read paths in the optimizer (async session scan + three sync
config/import/profile scans) now pass through the 128 MB-capped
helpers. JSON.parse in readJsonFile stays wrapped in try/catch.
MEDIUM-1 coverage for the optimize module.
2026-04-17 08:32:20 +02:00
Ninym
1bdbac4927 fix(context-budget): use bounded readSessionFile helper
Config JSON, CLAUDE.md scans, and session-discovery reads now pass
through the 128 MB-capped helper. JSON.parse remains wrapped in
try/catch to preserve the previous 'null on malformed JSON' contract.
MEDIUM-1 coverage for the context-budget module.
2026-04-17 08:32:19 +02:00
Ninym
716e080cb3 fix(pi): use bounded readSessionFile helper
Both Pi session read paths (first-entry meta and full-session parse)
now pass through the 128 MB-capped helper. MEDIUM-1 coverage for the
Pi provider.
2026-04-17 08:32:19 +02:00
Ninym
9f6827d528 fix(copilot): use bounded readSessionFile helper
Events JSONL and workspace.yaml reads now pass through the 128 MB-capped
helper. The workspace.yaml path stays non-fatal: a null read skips cwd
derivation but still pushes the session with sessionId as the fallback
project label. MEDIUM-1 coverage for the Copilot provider.
2026-04-17 08:32:19 +02:00
Ninym
1de0baf329 fix(codex): use bounded readSessionFile helper
Both Codex session read paths (first-line meta and full-session parse)
now pass through the 128 MB-capped helper. MEDIUM-1 coverage for the
Codex provider.
2026-04-17 08:32:19 +02:00
Ninym
ee738a1b26 fix(parser): use bounded readSessionFile helper
Replaces the unbounded readFile in parseSessionFile with the 128 MB-capped
helper from src/fs-utils. Addresses MEDIUM-1 for the Claude provider
hot path.

Verbose-mode stderr output replaces the previous silent catch,
closing LOW-1 as a side effect.
2026-04-17 08:32:19 +02:00
Ninym
82c3125638 feat(fs-utils): bounded session-file read helper
Adds readSessionFile / readSessionFileSync / readSessionLines with a
128 MB hard cap and 8 MB streaming threshold. Verbose mode
(CODEBURN_VERBOSE=1) logs skipped and failed reads to stderr.

Prepares the MEDIUM-1 migration of all provider read paths.
2026-04-17 08:32:19 +02:00
Ninym
5b810161e7 fix(parser): block prototype pollution via Object.create(null)
Initialize the four breakdown maps (model, tool, mcp, bash) with null
prototype so attacker-controlled keys named __proto__ create own
properties on the map instead of mutating Object.prototype.

Closes the HIGH-1 finding from the 2026-04-16 external security audit.
2026-04-17 08:32:18 +02:00
Ninym
bd71377fdd docs(optimize): remove references to non-existent .claudeignore
Claude Code does not document or implement a .claudeignore feature.
The junk-reads detector's fix is now a CLAUDE.md instruction asking
Claude to avoid generated/dependency directories. The separate
detectMissingClaudeignore finding and its tests are removed; checking
for the presence of a non-existent file has no signal.

Closes #61.
2026-04-17 08:32:07 +02:00
Jonathan
276b14adea fix: prepend install-time node bin dir to menubar plugin PATH
SwiftBar/xbar launch plugins with a minimal environment. If an older
system Node (e.g. v18.x from Homebrew) appears earlier on PATH than the
NVM-managed Node used to install codeburn, the plugin silently fails
because string-width uses the /v regex flag (requires Node >= 20.11).

Resolve the node binary directory at install time via process.execPath
and prepend it to PATH in the generated plugin script. Also explicitly
set HOME, which SwiftBar does not always inherit.

Fixes #63
2026-04-17 07:29:14 +01:00
AgentSeal
f958756861 chore(optimize): name preview-count literals and drop punctuation dashes
Rename slice(0, N) and length - N literals used in waste-finding
preview strings to GHOST_NAMES_PREVIEW, GHOST_CLEANUP_COMMANDS_LIMIT,
TOP_ITEMS_PREVIEW, MISSING_IGNORE_PATHS_PREVIEW, JUNK_DIRS_IGNORE_PREVIEW.
Drop the punctuation double-hyphen from the config-health header.
2026-04-16 16:20:53 -07:00
AgentSeal
3bd3c0d7b4 chore(optimize): extract trend period magic number to named constant 2026-04-16 16:09:19 -07:00
AgentSeal
1cd96ea19f Merge origin/main into feat/optimize
Resolve conflicts in src/dashboard.tsx: keep optimize view plumbing
(setOptimizeResult, OptimizeResult state, o/b keys) while integrating
project/exclude filters on reloadData and renderDashboard entry.
2026-04-16 16:08:20 -07:00
AgentSeal
87210da598 Merge pull request #52 from mallek/feature/project-filter
feat: add --project and --exclude filters for project-level filtering
2026-04-16 15:52:57 -07:00
AgentSeal
d15532af0b fix: apply --project/--exclude to menubar per-provider today totals
The menubar status output computes per-provider today costs by iterating
all providers after the main period blocks. That loop bypassed the
project filter, so --project/--exclude affected the main totals but not
the provider breakdown shown below them.
2026-04-16 15:50:00 -07:00
AgentSeal
d90042ec3c fix: stop Top Sessions panel from truncating the calls column
The TopSessions row layout summed to the full panel width without
leaving room for the Box border (round) + paddingX, so Ink truncated
the last 4 characters -- landing exactly on the calls column and
producing rows like "$182.58 ..." with no calls value.

Introduce PANEL_CHROME = 4 and subtract it from the name column so the
row fits inside the panel's inner area.
2026-04-16 15:44:39 -07:00
Travis Haley
67c504a60a feat: add --project and --exclude filters for project-level filtering
Adds two new repeatable flags to all commands (report, today, month, status, export):
- --project <name>: include only projects matching name (substring, case-insensitive)
- --exclude <name>: exclude projects matching name (substring, case-insensitive)

Both flags can be specified multiple times to match multiple projects.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 15:54:37 -06:00
AgentSeal
4154413e59 chore: DRY json report branches and drop unused import
- extract Period type alias
- hoist repeated json action body into runJsonReport helper
- remove unused getCostColumnHeader import
2026-04-16 14:43:03 -07:00
AgentSeal
f3482de33d
Merge pull request #53 from mallek/feature/json-output
feat: add --format json to report, today, and month commands
2026-04-16 23:39:25 +02:00
AgentSeal
b0d7a8b678
Merge pull request #57 from Galeas/main
fix: respect SwiftBar settings when installing the menu bar
2026-04-16 22:10:21 +02:00
AgentSeal
21444df2bf merge main into feat/optimize
- resolve dashboard.tsx conflicts: keep optimize view + context budget column alongside main's all-time period and TopSessions panel
- ProjectBreakdown: add avg/s column from main plus overhead column from optimize, widths 30/40
- StatusBar: 1-5 periods including all-time, plus o-optimize when findings exist
- DashboardContent: all-time period handling and TopSessions panel preserved

Copilot provider and its 253 tests from main merged cleanly as additions.
2026-04-16 12:56:53 -07:00
Travis Haley
634a8f11ab fix: add claude-opus-4-7 model mapping and pricing 2026-04-16 13:48:58 -06:00
akki
9b6a9e8fc3 fix: respect SwiftBar settings when installing the menu bar 2026-04-16 22:39:51 +03:00
Travis Haley
7a061ea679 feat: add periodKey and topSessions to JSON output 2026-04-16 13:13:09 -06:00
Travis Haley
60712785d7 style: remove section-label comments per repo convention 2026-04-16 13:10:05 -06:00
Travis Haley
fad21f3097 fix: destructure cost before spread so convertCost isn't overwritten in models JSON output 2026-04-16 13:08:46 -06:00
Travis Haley
09139b1d92 feat: add --format json to report, today, and month commands
Outputs full dashboard data as structured JSON to stdout, including:
overview, daily breakdown, projects, models with token counts,
activities with one-shot rates, core tools, MCP servers, and
shell commands.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 12:21:51 -06:00
AgentSeal
1e01020ee9
Merge pull request #44 from theodorosD/feat/copilot-provider
feat: add GitHub Copilot provider
2026-04-16 19:02:57 +02:00
AgentSeal
b9b2c7a900 chore: remove dead ContextBudgetPanel and unused dateRange parameter
- ContextBudgetPanel in dashboard.tsx was defined but never rendered
  after the per-project overhead column replaced it in Phase 1.
- detectLowReadEditRatio accepted a dateRange parameter that was
  never used inside the function (trend is computed via recent-call
  flags on ToolCall).

Verified with tsc --noUnusedLocals --noUnusedParameters: 0 errors.
160 tests pass.
2026-04-16 09:35:46 -07:00
Teo Delis
e7633d932b fix: address PR review feedback on Copilot provider
- init currentModel to '' and skip assistant messages before first
  session.model_change to avoid silent misattribution
- add comment documenting why inputTokens is always 0
- fix delete_file tool mapping ('Edit' -> 'Delete')
- add schema doc comment to ToolRequest optional fields
- remove catch-all from CopilotEvent union for proper TS narrowing
- add tests: pre-model-change skip, workspace.yaml quote/comment strip,
  longest-prefix model display name match
2026-04-16 19:30:08 +03:00
AgentSeal
b9dffc16ec chore(optimize): remove dead versions plumbing + name remaining magic numbers
- ScanData.versions and ScanFileResult.versions were collected but never
  read in scanAndDetect. Per-call version lives on ApiCallMeta.version
  which is what detectCacheBloat actually uses. Dropped the unused
  aggregation path end-to-end.
- Extract DEFAULT_CACHE_BASELINE_TOKENS, CACHE_BASELINE_QUANTILE,
  CACHE_VERSION_MIN_SAMPLES, CACHE_VERSION_DIFF_THRESHOLD as named
  module-scope constants. Honors the "no magic numbers" project rule
  across the full optimize engine.
2026-04-16 07:09:20 -07:00
AgentSeal
707d2faff1 feat(optimize): UX polish
- Rename "ctx" column to "overhead" with wider padding for legibility
  in the By Project panel.
- Name magic numbers for the project column widths.
- Empty-state now includes a one-line description of what optimize
  does, so first-time users with no findings still understand the
  feature.
2026-04-16 06:59:07 -07:00
AgentSeal
a9ca2a1134 feat(optimize): fix tracking via recent vs baseline split
Solves the problem where users who fixed an issue continued to see
the finding for the remainder of the period. Findings now show
visible progress or disappear entirely.

Mechanism (no state file, no new I/O):
- ToolCall and ApiCallMeta gain a `recent` boolean, set when the
  entry's timestamp falls inside a rolling 48-hour window.
- Each session-based detector counts recent vs total occurrences.
- computeTrend classifies each finding:
    active    -- recent rate matches baseline
    improving -- recent rate under half of baseline (green arrow)
    resolved  -- zero recent waste AND confirmed recent activity
- Resolved findings are suppressed. Improving findings render with
  a green "improving down-arrow" badge next to the impact label.
- When no recent activity exists, findings default to active so a
  user who simply paused is not told everything is fixed.

Applies to the four session-based detectors: junk reads, duplicate
reads, low read:edit ratio, cache bloat. The filesystem detectors
(missing .claudeignore, bloated CLAUDE.md, unused MCP, ghost agents
/ skills / commands, bash limit) already self-heal on next run.

5 new tests cover computeTrend edge cases. 126 tests pass.
2026-04-16 06:53:08 -07:00
Ninym
0461193819 fix: handle empty firstTimestamp in TopSessions, add dashboard tests
- TopSessions: show '----------' placeholder when session.firstTimestamp
  is empty (Copilot provider yields '' when timestamp is missing)
- DashboardContent: add comment explaining undefined days tri-state
- tests/dashboard.test.ts: cover top-5 selection (fewer than 5 sessions,
  tied costs, descending sort) and avg/s returning '-' for zero sessions

Authored-by: AgentSeal <hello@agentseal.org>
2026-04-16 15:48:24 +02:00
AgentSeal
7e4edf66b6 perf(optimize): mtime pre-filter, parallel reads, result cache
- runWithConcurrency helper runs file reads with configurable parallelism
  (default 16) instead of sequential await.
- isFileStaleForRange skips files whose mtime is older than the date range
  start, avoiding unnecessary reads for narrow periods.
- Result-level cache keyed on (dateRange, project fingerprint) with
  60s TTL. Warm dashboard 'o' press now hits cache instead of rescanning.
- Early-return when projects array is empty so empty-state path does not
  trigger filesystem walk.

Measured CLI cold scan on 12K files, 1833 sessions week:
  before: 12-17s
  after: 6-7s
Dashboard warm cache hit: <50ms.
2026-04-16 06:35:39 -07:00
AgentSeal
be45045fd8 refactor(optimize): correctness, constants, and real tests
Phase 1 hardening pass.

Bug fixes:
- Move cwd/version collection inside date-range filter. 7d and 30d
  now produce different findings for filesystem detectors.
- detectGhostSkills threshold aligned with peer detectors.
- detectUnusedMcp gets 24-hour grace period via config file mtime so
  newly added servers are not flagged as unused.
- detectCacheBloat replaces hardcoded 50K baseline with user-derived
  p25 of cache writes. Flags only when median exceeds 1.4x baseline.
- detectBashBloat scans user shell profiles instead of the auditor's
  process.env.
- @-import pattern requires ./ ../ or / to avoid matching email
  addresses or npm scopes.
- Command usage pattern requires leading whitespace/start-of-line
  before /cmd so path references like /tmp are not counted as usage.
- AVG_TOKENS_PER_READ lowered from 1500 to 600 and
  CLAUDEMD_TOKENS_PER_LINE lowered from 25 to 13 for realistic
  prose/config sizing.

Code quality:
- Every magic number extracted to named module-scope constants.
- Dead code removed (IMPACT_ORDER, unused stat import).
- Shared loadMcpConfigs helper deduplicates config walking.
- Shared shortHomePath, isReadTool, inRange helpers.
- All detectors and computeHealth exported for real tests.
- Ghost detectors run in parallel via Promise.all.
- Cost rate defaults to 0 when unknown so UI can suppress instead of
  showing fabricated numbers.

Tests:
- Replaced 17 fake tests that re-implemented detector logic with
  26 tests importing and exercising the real exports.
- Cover threshold boundaries, impact scaling, edge cases.
- 121 tests pass.

UX header: "Setup" renamed to "Health", issue count shown inline.
CLAUDE.md: adds rule against "steal/copy/rip-off" wording in
public-facing text.
2026-04-16 06:30:15 -07:00
Teo Delis
a8517d3235 feat: add GitHub Copilot provider
- Parse ~/.copilot/session-state/*/events.jsonl
- Track model via session.model_change events
- Extract tools from assistant.message toolRequests
- Add fallback pricing for gpt-4.1, gpt-4.1-mini, gpt-4.1-nano, gpt-5-mini, o3, o4-mini
- Factory function createCopilotProvider(sessionStateDir?) for testability
- Typed event variants (ModelChangeData, UserMessageData, AssistantMessageData)
- bashCommands: [] in yield (Copilot does not log bash commands)
- 13 tests covering parsing, model tracking, tool extraction, dedup, discoverSessions
- Note: only outputTokens available (Copilot does not log input tokens)
2026-04-16 15:40:22 +03:00
AgentSeal
b88f2cd730 feat: ghost detectors, health grade, @-import expansion
Expanded the optimize engine with new detectors and scoring:

1. Health score + letter grade (A-F) in optimize header. Weighted
   per-impact with caps. Gives users an instant "is my setup healthy"
   read that doubles as a shareable number.

2. Urgency score replaces impact-enum sort. Weighted 0.7 * impact
   + 0.3 * normalized tokens. Produces better-ranked findings.

3. Three new ghost detectors:
   - Ghost agents: files in ~/.claude/agents/ never invoked via
     Agent/Task tool
   - Ghost skills: SKILL.md directories never triggered
   - Ghost slash-commands: ~/.claude/commands/ files never referenced
     in user messages

4. @-import chain expansion for CLAUDE.md. Recursively follows
   @path/to/file imports (max depth 5) so bloat detection counts
   transitive load, not just the base file. Fixes undercounting for
   users with modular CLAUDE.md setups.

9 new tests covering health scoring and import expansion.
2026-04-16 05:09:01 -07:00
Ninym
cfd046a95c feat: add All Time period, avg/s per project, and Top Sessions panel
- Add 'all' period (key 5) to dashboard showing data from all time
- Daily Activity panel shows all available days when All Time is active
- Add avg cost per session column to Project breakdown
- Add Top Sessions panel highlighting the 5 most expensive sessions
2026-04-16 13:14:34 +02:00
AgentSeal
710316053e feat: add optimize command, in-TUI optimize view, and per-project context budget
Optimize engine detects 8 waste patterns from Claude Code session data:
- Junk directory reads (node_modules, .git, dist, etc.)
- Duplicate file reads per session
- Unused MCP servers (configured but never called)
- Missing .claudeignore in projects with junk dirs
- Bloated CLAUDE.md files (>200 lines)
- Uncapped BASH_MAX_OUTPUT_LENGTH
- Low Read:Edit ratio (edit-without-reading, per #42796)
- High cache_creation overhead (per #46917)

Each finding includes impact rating, token/cost savings estimate, and
exact fix (paste, command, or file content).

Dashboard integration:
- o key switches to in-TUI optimize view, b key goes back
- Background scan on load, o button only when findings exist
- Per-project Context Budget column in By Project panel showing
  estimated per-call overhead (system + MCP tools + skills + CLAUDE.md)

CLI: codeburn optimize [-p period] [--provider]
2026-04-16 04:04:37 -07:00
AgentSeal
5e095bf86e chore: polish Pi provider integration
- Add Pi entry to PROVIDER_COLORS (pink) and PROVIDER_DISPLAY_NAMES.
- Update README with Pi in supported providers, requirements,
  command examples, and the data-format section.
2026-04-16 02:11:48 -07:00
AgentSeal
1a101b586c chore: map Pi dispatch_agent to Agent for activity classification
dispatch_agent is Pi's delegation tool. Mapping it to Agent makes
the classifier route those turns into the Delegation category
instead of Conversation, matching the existing semantics for the
task tool.
2026-04-16 02:08:07 -07:00
AgentSeal
97c0869763 chore: hoist Pi model sort + cover bash-utils edge cases
- Move modelDisplayEntries sort to module scope so it runs once
  instead of on every modelDisplayName call.
- Add bash-utils tests for the env var prefix and true/false
  skip behaviors that came in with the Pi commit.

Verified Pi token semantics against real session data:
input + cacheRead + cacheWrite + output equals totalTokens
exactly, confirming Anthropic-style accounting (cached tokens
disjoint from input). No double-counting.
2026-04-16 02:02:32 -07:00