Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .claude-plugin/plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
"$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
"name": "claude-code-cost",
"displayName": "Claude Code Cost",
"version": "1.0.0",
"description": "See your Claude Code spend in USD per project and per git branch, computed locally by reading the session logs Claude Code already writes to ~/.claude/projects. Never touches API traffic. Zero key, zero prompts, zero latency.",
"version": "1.1.0",
"description": "See your Claude Code spend in USD per project, per git branch, and over time (by day, week, month), computed locally by reading the session logs Claude Code already writes to ~/.claude/projects. Never touches API traffic. Zero key, zero prompts, zero latency.",
"author": {
"name": "RoninForge",
"url": "https://roninforge.org"
Expand Down
56 changes: 41 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# claude-code-cost

See your Claude Code spend in USD, per project and per git branch, without leaving Claude Code.
See your Claude Code spend in USD, per project, per git branch, and over time, without leaving Claude Code.

`/cost` reads the session logs Claude Code already writes to `~/.claude/projects/**/*.jsonl`, prices every assistant response with a dated, point-in-time price index, and prints a per-project, per-branch table. It never touches API traffic. Zero key, zero prompts, zero latency.
`/claude-code-cost:report` reads the session logs Claude Code already writes to `~/.claude/projects/**/*.jsonl`, prices every assistant response with a dated, point-in-time price index, and prints a per-project, per-branch table. It never touches API traffic. Zero key, zero prompts, zero latency.

```
PROJECT BRANCH TODAY WEEK MONTH
Expand All @@ -12,6 +12,18 @@ other-repo main $23.41 $23.41 $85.85
TOTAL $31.20 $31.20 $292.18
```

Four sibling commands answer "when did I spend?": `/claude-code-cost:by-day`, `/claude-code-cost:by-week`, `/claude-code-cost:by-month` (each a dated, most-recent-first breakdown), and `/claude-code-cost:by-project` (a per-project rollup with each project's last-active date).

```
> /claude-code-cost:by-day

DATE SPEND
2026-06-23 $9.98
2026-06-22 $4.20
2026-06-19 $7.49
TOTAL $21.67
```

## Why

Claude Code shows you a running session cost, but not how much a given project or branch has cost you today, this week, or this month. This plugin answers that question from data already on your disk. It is a usage guard, not a proxy: it parses the local JSONL transcripts, it does not sit in front of the API, store keys, or add latency.
Expand All @@ -27,20 +39,29 @@ In Claude Code:
/plugin install claude-code-cost@roninforge
```

Then run `/cost`.
Then run `/claude-code-cost:report`.

Requires `node` (>= 18) on your PATH, which Claude Code users almost always have.

## Usage
## Commands

```
/cost spend per project and branch (today / week / month)
/cost <filter> only rows whose project or branch contains <filter>
/cost --self force the built-in reader (skip BudgetClaw, see below)
/cost --json machine-readable JSON instead of the table
/claude-code-cost:report spend per project + branch (today / week / month)
/claude-code-cost:by-day spend per day, dated, most recent first (~30 days)
/claude-code-cost:by-week spend per Monday-based week, dated (~12 weeks)
/claude-code-cost:by-month spend per calendar month, dated (~12 months)
/claude-code-cost:by-project per project (branches merged) + last active date
```

`/cost my-app` filters to one project or branch. The filter is a case-insensitive substring match on either column.
Every command takes an optional case-insensitive substring `[filter]` on the project or branch name, so the dated views double as a per-project history:

```
/claude-code-cost:report my-app only rows whose project or branch contains "my-app"
/claude-code-cost:by-day my-app my-app's spend per day, dated
/claude-code-cost:by-month my-app my-app's spend per month, dated
```

Flags (all commands): `--json` for machine-readable output, `--self` to force the built-in reader instead of BudgetClaw (report only; the by-* views always read the raw logs directly).

## How it works

Expand All @@ -52,7 +73,7 @@ Requires `node` (>= 18) on your PATH, which Claude Code users almost always have

## Optional: BudgetClaw integration

If [BudgetClaw](https://roninforge.org/budgetclaw) is installed, `/cost` uses it automatically for a richer read backed by BudgetClaw's persistent database and budget caps. If it is not installed, the bundled self-contained reader is used instead. Either way the numbers come from your local logs. Pass `--self` to always use the built-in reader.
If [BudgetClaw](https://roninforge.org/budgetclaw) is installed, `/claude-code-cost:report` uses it automatically for a richer read backed by BudgetClaw's persistent database and budget caps. If it is not installed, the bundled self-contained reader is used instead. Either way the numbers come from your local logs. Pass `--self` to always use the built-in reader. The dated views (`by-day`, `by-week`, `by-month`, `by-project`) always read the raw logs directly, because `budgetclaw status` only emits the pre-rolled today/week/month snapshot with no per-date data.

## Privacy and trust

Expand All @@ -63,13 +84,18 @@ If [BudgetClaw](https://roninforge.org/budgetclaw) is installed, `/cost` uses it
## Development

```sh
node scripts/cost.mjs --self # run against your real logs
node scripts/cost.mjs --self --json # JSON output
node scripts/test.mjs # run the test suite
node scripts/check-manifests.mjs # manifest sanity check
claude plugin validate . --strict # validate the plugin manifest
node scripts/cost.mjs --self # report, against your real logs
node scripts/cost.mjs --mode by-day --self # spend per day
node scripts/cost.mjs --mode by-project --self # per-project rollup
node scripts/cost.mjs --self --json # JSON output
node scripts/cost.mjs --help # all modes and flags
node scripts/test.mjs # run the test suite
node scripts/check-manifests.mjs # manifest sanity check
claude plugin validate . --strict # validate the plugin manifest
```

Each command in `commands/` is a thin wrapper that runs `scripts/cost.mjs` with a `--mode`. The reader makes a single pass over the JSONL (dedup + point-in-time pricing) and every mode aggregates from that one priced-event list.

The pricing data under `scripts/vendor/ai-price-index/` is a bundled snapshot of the published `ai-price-index` package. To refresh it, re-copy `lib/{data.json,engine.js,index.js}` from a newer release and bump the plugin version. See `scripts/vendor/ai-price-index/VENDORED.md`.

## License
Expand Down
24 changes: 24 additions & 0 deletions commands/by-day.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
---
description: Claude Code spend grouped by calendar day, dated, most recent first. Computed locally from the session logs Claude Code already writes. Never touches API traffic; zero key, zero prompts, zero latency.
argument-hint: "[filter] [--json]"
allowed-tools: Bash(node:*)
---

Show the user their Claude Code spend broken down by day.

Run exactly this command with the Bash tool, passing the user's arguments through verbatim:

```
node "${CLAUDE_PLUGIN_ROOT}/scripts/cost.mjs" --mode by-day $ARGUMENTS
```

Then present the command's stdout to the user VERBATIM inside a fenced code block. Do not reformat the table, do not add columns, do not re-sort rows, and do not convert it to Markdown. Do not editorialize or add commentary about the numbers unless the user explicitly asks for analysis.

Notes for you (the model), not for the user:

- Output is a `DATE SPEND` table covering the last ~30 days, newest day first, with a TOTAL footer. Only days with spend appear.
- The script reads only the local JSONL session logs under `~/.claude/projects`. It never touches API traffic: zero key, zero prompts, zero latency. This view always reads the raw logs directly (it does not use budgetclaw, which has no per-date data).
- A first non-flag argument is a case-insensitive substring filter on project or branch name, scoping the daily totals to that project/branch (for example `/claude-code-cost:by-day roninforge`).
- `--json` makes it emit a JSON array of `{period, spend}` instead of the table.
- If the output ends with a "Note:" line about unpriced models, keep it.
- If the script prints "No Claude Code spend tracked yet." or "No Claude Code session logs found ...", relay that message as-is.
24 changes: 24 additions & 0 deletions commands/by-month.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
---
description: Claude Code spend grouped by calendar month, dated, most recent first. Computed locally from the session logs Claude Code already writes. Never touches API traffic; zero key, zero prompts, zero latency.
argument-hint: "[filter] [--json]"
allowed-tools: Bash(node:*)
---

Show the user their Claude Code spend broken down by month.

Run exactly this command with the Bash tool, passing the user's arguments through verbatim:

```
node "${CLAUDE_PLUGIN_ROOT}/scripts/cost.mjs" --mode by-month $ARGUMENTS
```

Then present the command's stdout to the user VERBATIM inside a fenced code block. Do not reformat the table, do not add columns, do not re-sort rows, and do not convert it to Markdown. Do not editorialize or add commentary about the numbers unless the user explicitly asks for analysis.

Notes for you (the model), not for the user:

- Output is a `MONTH SPEND` table covering the last ~12 months, newest month first, with a TOTAL footer. Each row is labelled `YYYY-MM` (UTC). Only months with spend appear.
- The script reads only the local JSONL session logs under `~/.claude/projects`. It never touches API traffic: zero key, zero prompts, zero latency. This view always reads the raw logs directly (it does not use budgetclaw, which has no per-date data).
- A first non-flag argument is a case-insensitive substring filter on project or branch name, scoping the monthly totals to that project/branch (for example `/claude-code-cost:by-month roninforge`).
- `--json` makes it emit a JSON array of `{period, spend}` instead of the table.
- If the output ends with a "Note:" line about unpriced models, keep it.
- If the script prints "No Claude Code spend tracked yet." or "No Claude Code session logs found ...", relay that message as-is.
24 changes: 24 additions & 0 deletions commands/by-project.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
---
description: Claude Code spend per project (branches merged) with today / this week / this month and the last active date, ranked by month spend. Computed locally from the session logs Claude Code already writes. Never touches API traffic; zero key, zero prompts, zero latency.
argument-hint: "[filter] [--json]"
allowed-tools: Bash(node:*)
---

Show the user their Claude Code spend rolled up per project, with each project's last active date.

Run exactly this command with the Bash tool, passing the user's arguments through verbatim:

```
node "${CLAUDE_PLUGIN_ROOT}/scripts/cost.mjs" --mode by-project $ARGUMENTS
```

Then present the command's stdout to the user VERBATIM inside a fenced code block. Do not reformat the table, do not add columns, do not re-sort rows, and do not convert it to Markdown. Do not editorialize or add commentary about the numbers unless the user explicitly asks for analysis.

Notes for you (the model), not for the user:

- Output is a `PROJECT TODAY WEEK MONTH LAST` table, one row per project with its git branches merged together, ranked by month spend, with a TOTAL footer. `LAST` is the most recent day (UTC) that project had any spend.
- The script reads only the local JSONL session logs under `~/.claude/projects`. It never touches API traffic: zero key, zero prompts, zero latency. This view always reads the raw logs directly (it does not use budgetclaw, which has no last-active data).
- A first non-flag argument is a case-insensitive substring filter on the project name, keeping only matching projects (for example `/claude-code-cost:by-project roninforge`).
- `--json` makes it emit a JSON array of `{project, today, week, month, lastActive}` instead of the table.
- If the output ends with a "Note:" line about unpriced models, keep it.
- If the script prints "No Claude Code spend tracked yet." or "No Claude Code session logs found ...", relay that message as-is.
24 changes: 24 additions & 0 deletions commands/by-week.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
---
description: Claude Code spend grouped by Monday-based week, dated, most recent first. Computed locally from the session logs Claude Code already writes. Never touches API traffic; zero key, zero prompts, zero latency.
argument-hint: "[filter] [--json]"
allowed-tools: Bash(node:*)
---

Show the user their Claude Code spend broken down by week.

Run exactly this command with the Bash tool, passing the user's arguments through verbatim:

```
node "${CLAUDE_PLUGIN_ROOT}/scripts/cost.mjs" --mode by-week $ARGUMENTS
```

Then present the command's stdout to the user VERBATIM inside a fenced code block. Do not reformat the table, do not add columns, do not re-sort rows, and do not convert it to Markdown. Do not editorialize or add commentary about the numbers unless the user explicitly asks for analysis.

Notes for you (the model), not for the user:

- Output is a `WEEK OF SPEND` table covering the last ~12 weeks, newest week first, with a TOTAL footer. Each row is labelled with that week's Monday (UTC). Only weeks with spend appear.
- The script reads only the local JSONL session logs under `~/.claude/projects`. It never touches API traffic: zero key, zero prompts, zero latency. This view always reads the raw logs directly (it does not use budgetclaw, which has no per-date data).
- A first non-flag argument is a case-insensitive substring filter on project or branch name, scoping the weekly totals to that project/branch (for example `/claude-code-cost:by-week roninforge`).
- `--json` makes it emit a JSON array of `{period, spend}` instead of the table.
- If the output ends with a "Note:" line about unpriced models, keep it.
- If the script prints "No Claude Code spend tracked yet." or "No Claude Code session logs found ...", relay that message as-is.
9 changes: 5 additions & 4 deletions commands/cost.md → commands/report.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
---
description: Show your Claude Code spend in USD per project and per git branch, computed locally from the session logs Claude Code already writes. Never touches API traffic; zero key, zero prompts, zero latency.
description: Claude Code spend in USD per project and per git branch (today / this week / this month), computed locally from the session logs Claude Code already writes. Never touches API traffic; zero key, zero prompts, zero latency.
argument-hint: "[filter] [--self] [--json]"
allowed-tools: Bash(node:*), Bash(budgetclaw:*)
---

Run the bundled cost reader and show the user their Claude Code spend.
Run the bundled cost reader and show the user their Claude Code spend, grouped by project and git branch.

Run exactly this command with the Bash tool, passing the user's arguments through verbatim:

```
node "${CLAUDE_PLUGIN_ROOT}/scripts/cost.mjs" $ARGUMENTS
node "${CLAUDE_PLUGIN_ROOT}/scripts/cost.mjs" --mode report $ARGUMENTS
```

Then present the command's stdout to the user VERBATIM inside a fenced code block. Do not reformat the table, do not add columns, do not re-sort rows, and do not convert it to Markdown. Do not rank projects, editorialize, or add commentary about the numbers unless the user explicitly asks for analysis.
Expand All @@ -18,7 +18,8 @@ Notes for you (the model), not for the user:

- The script reads only the local JSONL session logs under `~/.claude/projects`. It never touches API traffic. If asked, you can state plainly: zero key, zero prompts, zero latency.
- If `budgetclaw` is installed, the script uses it automatically (richer: persistent database plus budget caps). Otherwise it falls back to a self-contained reader. Pass `--self` to force the built-in reader.
- A first non-flag argument is a case-insensitive substring filter on project or branch name (for example `/cost roninforge`).
- A first non-flag argument is a case-insensitive substring filter on project or branch name (for example `/claude-code-cost:report roninforge`).
- For spend over time use the sibling commands `/claude-code-cost:by-day`, `/claude-code-cost:by-week`, `/claude-code-cost:by-month`. For a per-project rollup with last-active dates, `/claude-code-cost:by-project`.
- `--json` makes it emit a JSON array instead of the table.
- If the output ends with a "Note:" line about unpriced models, keep it; it surfaces spend the price index could not value yet.
- If the script prints "No Claude Code spend tracked yet." or "No Claude Code session logs found ...", relay that message as-is.
Loading
Loading