AGENTS.md
Guidance for coding agents working on claude-usage-cost. Humans can start with README.md; this guide captures the build, validation, architecture, and safety rules agents need before changing the VS Code extension.
Project Overview
claude-usage-cost is a VS Code extension that estimates Claude Code token usage and API-equivalent USD cost from local Claude Code transcript JSONL files. It is local-only: it parses existing transcript logs, computes per-model and per-token-category aggregates, and renders a status bar plus dashboard. It should not call external APIs or require API keys.
The extension contributes two commands:
Claude Usage: Show Cost DashboardviaclaudeUsageCost.showDashboardClaude Usage: Refresh (rescan transcripts)viaclaudeUsageCost.refresh
The extension activates on onStartupFinished, scans the configured transcript directory, caches aggregate state under VS Code global storage, and updates through file watching plus a polling fallback.
Repository Layout
| Path | Purpose |
|---|---|
src/extension.ts | VS Code host integration: activation, status bar, settings, webview, scanning lifecycle, state save/restore. |
src/parser.ts | Plain Node transcript scanner and aggregator. No vscode import; keep it independently testable. |
src/pricing.ts | Plain Node pricing resolver and cost calculator. Built-in rates plus user overrides. |
src/types.ts | Shared TypeScript interfaces for prices, token counts, buckets, dashboard payloads, and session/cap views. |
src/selftest.ts | Standalone verification entry that runs the same parser/pricing path against a transcript directory and prints JSON. |
media/dashboard.js | Browser-side webview rendering for cards, charts, cap gauge, sessions, model table, and warnings. |
media/dashboard.css | Webview styling. |
esbuild.js | Bundles src/extension.ts to out/extension.js; keeps vscode external. |
out/ | Generated build output. Do not edit by hand. |
Setup Commands
Install dependencies:
bashnpm install
Build the extension bundle:
bashnpm run compile
Run the TypeScript typecheck:
bashnpm run check-types
Create a VSIX package:
bashnpm run package
Start an esbuild watch loop while editing:
bashnpm run watch
Verification
There is no dedicated npm test script in package.json. Before publishing or claiming a change is ready, run at least:
bashnpm run check-types
npm run compile
For parser/pricing changes, also bundle and run the standalone selftest:
bashnpx esbuild src/selftest.ts --bundle --platform=node --format=cjs --outfile=out/selftest.js
node out/selftest.js <transcript-dir>
Use a redacted or disposable transcript fixture when sharing output. Never paste real transcript content, local usernames, private project names, or exact local transcript paths into public issues, docs, screenshots, or archive items.
Architecture Rules
Keep the host/webview/parser boundaries intact:
src/extension.tsmay importvscode, manage the status bar, create the webview, read VS Code settings, and schedule scans.src/parser.ts,src/pricing.ts, andsrc/types.tsmust stay free ofvscodeimports so they can run under plain Node and be exercised bysrc/selftest.ts.media/dashboard.jsreceives already-aggregated data from the extension webview bridge. Do not move transcript parsing or filesystem access into the webview.esbuild.jsmust keepvscodeinexternal; the VS Code host provides it at runtime.
The scanning path is designed for large transcript trees. UsageStore reads JSONL files in chunks, tracks offsets for incremental scans, stores partial-line leftovers, and yields during full scans so the extension host stays responsive. Preserve this streaming design when changing parsing behavior.
Usage Accounting Rules
The core billing invariant is: count each billable Anthropic message.id once when dedupe is enabled.
Important parser behavior:
- Only lines containing
"usage"are parsed as JSON. - Zero-token stream-start stubs are ignored and must not claim the message id.
- If the same message id appears more than once, keep the larger token snapshot and remove the smaller contribution from its bucket.
- Buckets are keyed by model and hour.
rawTokenscounts every copy before dedupe; billed totals come from deduped buckets.STATE_VERSIONcontrols restore compatibility for serialized parser state. Bump it when the serialized shape changes.
Do not replace this with a naive file-total sum. The README documents that duplicate transcript copies can materially overcount usage.
Pricing Rules
Pricing lives in src/pricing.ts and is expressed as USD per one million tokens. Keep category splits explicit:
- input
- output
- 5-minute cache write
- 1-hour cache write
- cache read
User overrides come from claudeUsageCost.pricingOverride; longest model-id substring matches should keep winning. If built-in rates change, update:
PRICING_AS_OFBUILTIN_PRICES- the README pricing table and accuracy notes
For unrecognized model ids, preserve the current estimated-family behavior and warning surface. Unknown pricing should be visible to the dashboard rather than silently treated as authoritative.
Dashboard And Webview Rules
extension.ts creates a webview with a strict Content Security Policy. Keep the nonce-based script policy and avoid external scripts, fonts, images, or network resources.
Dashboard rendering should continue to be data-driven from DashboardData:
- summary cards
- monthly soft-cap gauge
- cost and token category splits
- daily trend
- reconstructed five-hour sessions
- per-model table
- warnings for missing transcripts, estimated pricing, or unpriced models
When adding fields, update src/types.ts, UsageStore.aggregate(), and media/dashboard.js together. Keep browser-side string escaping for dynamic text.
Configuration Rules
Settings are declared in package.json under contributes.configuration. If a setting changes, update README and ensure extension.ts reacts to configuration changes when needed.
Current settings include:
claudeUsageCost.transcriptDirclaudeUsageCost.autoRefreshclaudeUsageCost.dedupeclaudeUsageCost.statusBarMetricclaudeUsageCost.pricingOverrideclaudeUsageCost.includedMonthlyAllowanceUsdclaudeUsageCost.softCapLinesUsd
The empty transcript directory setting resolves to the default Claude Code transcript location through os.homedir() at runtime. In public docs or reports, describe that as the default Claude transcript directory or <claude-projects-dir> instead of publishing any real local path.
Security And Privacy Rules
This extension's privacy promise is local computation. Preserve it.
- Do not add telemetry, analytics, API calls, external pricing fetches, or network-backed dashboards.
- Do not log raw transcript lines or prompt/response content.
- Do not show raw transcript content in the webview.
- Do not expose real transcript paths in public documentation beyond repo-relative code references and neutral placeholders.
- Treat transcript JSONL as sensitive local data even though this extension reads only usage fields.
- Keep webview content self-contained and CSP-restricted.
When debugging, prefer aggregate counts and synthetic transcript fixtures. If a real user transcript is needed, keep it local and redact every path, username, project name, and message body before sharing.
Code Style
The project uses TypeScript with strict: true, CommonJS modules, ES2022 target, and Node-style imports. Follow the existing direct style:
- Keep parser/pricing functions small and explicit.
- Prefer plain objects and typed interfaces from
src/types.ts. - Avoid unnecessary dependencies; the extension already bundles with esbuild.
- Keep generated output in
out/out of manual edits. - Keep comments short and reserved for non-obvious parser or host-boundary behavior.
Common Change Patterns
When adding a new pricing tier:
- Add or adjust entries in
BUILTIN_PRICES. - Update
PRICING_AS_OF. - Update README pricing documentation.
- Run
npm run check-typesandnpm run compile. - Run
selftestagainst a small fixture or local redacted transcript directory.
When changing parser logic:
- Preserve chunked reading and incremental offsets.
- Preserve dedupe upgrade behavior for repeated message ids.
- Validate both dedupe-on and dedupe-off paths if relevant.
- Check dashboard warnings still make sense.
- Run
npm run check-types,npm run compile, andselftest.
When changing dashboard UI:
- Keep
DashboardDataas the single webview input. - Escape dynamic strings in
media/dashboard.js. - Keep the CSP compatible with local extension resources only.
- Run
npm run compileand manually inspect the webview in VS Code when possible.
Packaging Notes
npm run package invokes @vscode/vsce with --allow-missing-repository --skip-license. The README says the project is MIT licensed, but packaging currently skips a license check. If distribution requirements change, add or update the license file and remove the skip flag only after confirming the package still builds.
The extension version is currently 0.1.4 in package.json. Keep README install examples, package output names, and package metadata aligned when bumping versions.
Commit And PR Rules
Before opening a PR or publishing a VSIX:
bashnpm run check-types
npm run compile
Also run the selftest for parser or pricing changes. In the PR summary, call out:
- whether pricing tables changed
- whether parser/dedupe behavior changed
- whether dashboard payload shape changed
- whether any privacy boundary changed
Do not include real transcript snippets, local paths, screenshots with private project names, or exact usage totals from a private workspace in PR descriptions.