feat(statusline): add current-month total cost placeholder#55
Open
Brusdeylins wants to merge 3 commits into
Open
feat(statusline): add current-month total cost placeholder#55Brusdeylins wants to merge 3 commits into
Brusdeylins wants to merge 3 commits into
Conversation
rse
reviewed
Jun 6, 2026
| directory and recomputed at most once per *--month-cost-ttl* window | ||
| by a detached background process, so a render never blocks on the | ||
| transcript scan; missing or empty logs simply suppress the | ||
| placeholder. When run inside a *tmux* pane, the resolved task id is also |
Owner
There was a problem hiding this comment.
Here need a blank line, I think. It is very confusing that the tmux stuff is at the end of the %Y stuff.
rse
reviewed
Jun 6, 2026
| "claude-opus-4-6": { input: 5.00, output: 25.00 }, | ||
| "claude-sonnet-4-6": { input: 3.00, output: 15.00 }, | ||
| "claude-haiku-4-5": { input: 1.00, output: 5.00 } | ||
| } |
Owner
There was a problem hiding this comment.
This both can become a maintenance issue (when new models occur or prices change) and does not support any models in Github Copilot.
rse
reviewed
Jun 6, 2026
| catch (_e) { | ||
| user = process.env.USER ?? "default" | ||
| } | ||
| return path.join(os.tmpdir(), `ase-statusline-month-cost-${user}.json`) |
Owner
There was a problem hiding this comment.
Really under the OS temp directory like /tmp or $TMPDIR? I would have expected ~/.ase/xxx or something like this but not the temp directory.
rse
reviewed
Jun 6, 2026
| emit(`${prefix("$", "cost")}${c.bold(formatCostUsd(sessCost))}`) | ||
| }, | ||
| Y: () => { | ||
| const monthCost = monthCostForRender(new Date(), opts.monthCostTtl) |
Owner
There was a problem hiding this comment.
At least here we would have to check the "agent harness": "claude" vs. "copilot". For copilot this at least should expand to the empty string or perhaps even "N.A." or something like this.
94364e5 to
fc0f453
Compare
b55504d to
5d92e26
Compare
Add a new %Y statusline placeholder that shows the cumulative cost (USD) across ALL Claude Code sessions of the current calendar month, complementing %X which only reflects the current session. - new tool/src/ase-statusline-cost.ts: scans ~/.claude/projects/**/*.jsonl (honoring CLAUDE_CONFIG_DIR), sums each entry's message.usage tokens times the per-model price, filtered to the current month (local time). Pricing is cross-checked against the LiteLLM price table (the same source ccusage uses): input/output plus cache-read (0.1x), 5-minute cache-write (1.25x) and 1-hour cache-write (2x) derived from each model's input price. Duplicate messages (same id+requestId across session logs) are de-duplicated. - performance: the transcript scan never runs in the render path. The result is cached in the temp directory and, when stale/missing, refreshed by a detached background process (ase statusline --refresh-month-cost); the render serves the last cached value immediately. TTL is configurable via --month-cost-ttl (default 300s). - robustness: missing/empty logs or unknown models simply suppress the placeholder; no errors. - docs: document %Y, its data source, caching, and --month-cost-ttl in docs/usage-tool.md and extend the statusline argument hint. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add Claude Fable 5 (model id `claude-fable-5`, $10/MTok input, $50/MTok output) to the `%Y` cumulative-month-cost price table. Cache-token costs derive from the standard prompt-caching multipliers as for the other models (read 0.1x, 5-min write 1.25x, 1-hour write 2x), which match the published Fable 5 cache rates. Prices verified against the official Anthropic pricing and models-overview documentation. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…onth cost Claude Code rewrites a message's log entry with progressively growing usage counts while the response streams. The previous first-wins de-duplication kept whichever snapshot was encountered first, often a partial one, systematically undercounting the month total by ~0.5%. Now the most expensive snapshot per message-id/request-id key is kept, which reflects the final billed state. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
5d92e26 to
cd440d6
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds a new
%Ystatusline placeholder showing the cumulative cost (USD) across all Claude Code sessions of the current calendar month — complementing%X, which only reflects the current session (cost.total_cost_usd).Example:
∑ month: $1223.47Data source & pricing
Claude Code's local session transcripts (
~/.claude/projects/**/*.jsonl, honoringCLAUDE_CONFIG_DIR) — the same sourceccusageuses. These logs carry no precomputedcostUSDfield, so cost is computed from each entry'smessage.usagetoken counts × per-model price:model_prices_and_context_window.jsontable (Opus 4.x $5/$25, Sonnet 4.6 $3/$15, Haiku 4.5 $1/$5 per 1M).id+requestIdacross session logs, e.g. after resume/fork) are de-duplicated.Performance (non-blocking)
The statusline renders very frequently, and a full transcript scan takes ~1.5s. So the scan never runs in the render path:
MonthCostCache: month/cost/timestamp).ase statusline --refresh-month-cost) that recomputes and rewrites the cache. The next render picks up the fresh value.--month-cost-ttl <seconds>(default300).stat-skipped to keep the scan bounded.Robustness
Missing/empty logs, unreadable files, malformed JSON lines, and unknown models all degrade gracefully — the placeholder simply renders nothing, never an error.
Verification
npm --prefix tool start build— lint + tsc green.CLAUDE_CONFIG_DIR: hand-computed total$0.17725matchedcomputeMonthCost()to 1e-9, exercising the 5m/1h cache split, month filtering (previous-month entry excluded), dated model-id suffix resolution, unknown/<synthetic>model skipped, no-usage line skipped, and cross-file de-duplication.--refresh-month-costwrites the cache;%Ythen renders∑ month: $0.18;%X %Yrender side by side; empty logs suppress%Y.$1223.47for the current month against the local transcript archive; confirmed the render reads the cache (fast) rather than scanning.Docs
%Y, its data source, caching behavior, and--month-cost-ttlare documented indocs/usage-tool.md; the statusline argument hint lists%Y.🤖 Generated with Claude Code