Skip to main content

Claude Code usage recap with narrative. ccusage tells you the bill, ccstory tells the story.

Project description

ccstory

Your Claude Code week, in plain English. Reads ~/.claude/projects/**/*.jsonl locally and writes a categorized recap with active hours, costs, and a per-bucket narrative.

Sibling to ccusage: ccusage tells you how much you spent · ccstory tells you what on.

Who this is for

  • People who want to write a weekly status without scrolling scrollback.
  • People who saw a ccusage number and want to know what kind of work those tokens went to.
  • People who do a Sunday-night reflection on what they actually shipped.

Quick start

pipx install ccstory
ccstory init
ccstory week

That's it. init is a one-time auto-categorize step that scans your recent sessions; ccstory week produces the recap. Full report saves to ~/.ccstory/reports/recap-*.md.

Demo


╭──────────────── Claude Code Recap · May 5 – 12, 2026 ────────────────╮
│                                                                      │
│  ★ Top focus  coding  10.9h  (53% of active time)                    │
│    ↳ Built /show-routine slash command using bash+python to fetch…   │
│                                                                      │
│  Active  20.6h  Sessions  74   Output  2.92M                         │
│  Turns   3,692  Cache     96%  Cost    $1,608                        │
│                                                                      │
│  Time by category                                                    │
│  coding          ███████████████░░░░░░░░░░░░░   10.9h    53%         │
│  writing         █████████░░░░░░░░░░░░░░░░░░░    6.2h    30%         │
│  research        █████░░░░░░░░░░░░░░░░░░░░░░░    3.5h    17%         │
│                                                                      │
│  Full report → ~/.ccstory/reports/recap-2026-05-10_2026-05-17.md     │
│                                                                      │
╰────────────────────────────── ccstory ───────────────────────────────╯

The markdown report adds a 2–3 sentence synthesis per bucket plus per-session one-liners. Run with --llm-narrative to upgrade per-session lines from the instant first-user-msg fallback to claude-polished prose:

Re-running upgrades retroactively. If you viewed a window in the default (instant) mode first, re-running it with --llm-narrative upgrades those cached fallbacks to polished summaries — so ccstory month --llm-narrative polishes weeks you already skimmed. Already-polished sessions are reused (no re-burn) unless their prompt version is stale; add --refresh to force every in-window summary to regenerate (e.g. after a claude model upgrade you want reflected).

### coding

Shipped the /show-routine slash command end-to-end this week — bash+python
wrapper to surface scheduled-task output, plus a routine-detail bookmark
flow after the live debug session on Wednesday.

- 2026-05-10 03:24 · 123m · 212 msg — Built /show-routine slash command using
  bash+python to fetch scheduled-task output and surface it inline.
- 2026-05-08 12:30 · 67m · 294 msg — Debugged hook race condition in
  background-task notification dispatch; landed fix in main.

Usage

Basic

Command What it does
ccstory init One-time auto-categorize from recent sessions
ccstory Current month so far (default window)
ccstory week Past 7 days
ccstory month Current month
ccstory 2026-04 A specific month
ccstory trend Last 8 weeks of sparklines
ccstory category list Show your custom bucket rules
ccstory category set <bucket> <keyword>… Pin a project to a bucket
ccstory category unset <bucket> <keyword>… Remove a keyword from a bucket

Advanced

Window

Command What it does
ccstory all Entire history
ccstory trend --weeks 12 Custom trend range
ccstory trend --months 6 By calendar months

Narrative depth

Flag What it does
--minimal Numbers only, no per-session lines
--llm-narrative claude -p per-session prose (slow, opt-in)
--no-aggregate Skip the per-bucket synthesis

Comparison block (vs-previous, auto-attached to week/month)

Flag What it does
--no-compare Skip the entire block
--no-compare-narrative Keep numeric deltas, drop the prose

Session classification mode

Flag What it does
--classify folder Folder-name rules only
--classify content claude -p reads each session
--classify hybrid User rule wins, else content (default)

Export

Flag What it does
--for=obsidian YAML frontmatter + [[wikilinks]]

Output format

Flag What it does
--format=card Force the Rich terminal card (default in a real tty)
--format=markdown Force the full Markdown report to stdout
--format=auto (default) Markdown when CLAUDECODE=1 or stdout is not a tty (piped / redirected), else card

The auto-detect means asking Claude Code "show me my week with ccstory" renders an actual Markdown report in the chat instead of ANSI escape codes. The Markdown body is the same content saved to ~/.ccstory/reports/ (recap-*.md for the default window, trend-*.md for ccstory trend), just printed to stdout so the chat can render it inline. In markdown mode all progress / status lines route to stderr, so stdout is a clean Markdown stream you can pipe.

Refresh (apply rule changes retroactively)

Flag What it does
--refresh Re-do this window's cached work: re-classify after a rule edit, and (with --llm-narrative) force-regenerate every per-session summary
--refresh-all Wipe the entire content-classification cache, not just this window

Trend output

Hours by bucket
total          ▁▄▆▇▃█    16.5h   avg 9.0h   ▲ +183%
coding         ▁▂▃▄▁█    10.2h   avg 3.3h   ▲ +1148%
writing        ▁▇█▆▁▁     6.2h   avg 4.1h   ▲ +51%
research       ▁▃▅█▆█     3.5h   avg 2.0h   ▲ +75%

Overall
output         ▁▁▁▄▁█     3.0M   avg 0.8M   ▲ +260%
cost           ▁▁▂▃▁█   $1,643   avg $463   ▲ +255%
burn %         ▁▁▂▃▁█     201%   avg 57%    ▲ +255%

The burn % row is API-equivalent cost as a percentage of your prorated monthly quota. Set monthly_quota_usd in ~/.ccstory/config.toml (default $3,500 ≈ Max 20x plan); set to 0 to hide the row.

Categories

Four default buckets, matched against the project folder name:

Bucket Keywords (sample)
investment investment, stock, portfolio, trading, ticker, etf, finance
writing blog, newsletter, post, docs, content, article
coding app, sdk, cli, plugin, mcp, server, frontend, backend, lib, …
other playground, scratch, sandbox, experiment

Unmatched projects fall back to coding. Customize in ~/.ccstory/config.toml:

default_bucket = "coding"

[categories]
"work"    = ["company-repo", "internal-tool"]
"writing" = ["blog", "newsletter", "essay"]

Folder rules can be overridden per-session by content (--classify content / hybrid), where one batched claude -p call re-buckets sessions by what they were actually about. Results cache in ~/.ccstory/cache.db so reruns are free.

What shipped

Time tells half the story; the other half is what the time produced. Each report includes a What shipped section — per-repo output metrics for the repos you actually worked in during the window:

| Repo     | Commits | PRs merged | Releases | Stars   |
|----------|--------:|-----------:|----------|--------:|
| ccstory  | 5       | 3          | v0.5.0   | 42 (+6) |
| myapp    | 21      | 1          | –        | 12      |

- PyPI **ccstory**: 107 downloads (last week)
  • Repos are inferred from session working directories — no config needed. Worktrees collapse into their main repository.
  • Commits come from local git (works offline, counts all branches). PRs merged / releases / stars need the gh CLI; without it those columns degrade to . PyPI downloads come from pypistats.org for packages auto-detected in active repos' pyproject.toml.
  • Stars delta compares against the last snapshot taken before the window, so it becomes meaningful from your second run onward.

Skip per run with --no-artifacts, or persistently via config:

[artifacts]
enabled = true
exclude = ["playground"]   # substring match on repo path
pypi = ["my-package"]      # extra packages beyond auto-detection

Narrative depth

## What you did is one 3-sentence synthesis by default. For real retrospectives, --narrative goes deeper:

ccstory week --narrative per-category   # 2-3 lines per bucket instead
ccstory week --narrative both           # overall first, then per-bucket

Each bucket costs one claude -p call, cached until that bucket's session set changes — rerunning the same window is free. A bucket whose synthesis fails (or that has no real summaries) is simply omitted; the report never blocks on it. In --json mode the same text lands in buckets[].narrative.

JSON output

For dashboards, bots, and sync scripts — one machine-readable object instead of parsing markdown:

ccstory week --json          # shorthand for --format=json
ccstory month --format json
ccstory trend --weeks 8 --json

stdout is pure JSON (progress goes to stderr, same as markdown mode), so ccstory week --json | jq .totals.active_hours just works. The envelope carries schema_version (currently 1): renames/removals bump it, additive fields don't — consumers should tolerate unknown keys. Covers window, totals (hours/tokens/cost/cache), buckets, per-session lines, model breakdown, narrative, comparison, artifacts, and the pricing snapshot date. The markdown report file is still written either way; JSON is a view, not a replacement.

Obsidian export

ccstory --for=obsidian swaps the plain markdown for a PKM-vault-ready variant with YAML frontmatter and [[wikilinks]]:

---
date_start: 2026-05-10
date_end: 2026-05-17
active_hours: 20.6
top_focus: coding
buckets: [coding, writing, research]
cost_usd: 1608.42
output_tokens: 2920000
---

Queryable in Obsidian's Dataview / Bases (WHERE top_focus = "coding"). Bucket names with special characters are JSON-quoted so the frontmatter stays valid even for client: acme, inc.

Narrative language

ccstory delegates narrative writing to your local claude -p. By default it inherits whatever language Claude Code itself responds in; override it per run, per shell, or persist a per-tool choice.

Precedence (high → low):

Source Notes
--lang "Traditional Chinese" One-off, this invocation only
CCSTORY_LANG=日本語 env var Shell-scoped
language = "Spanish" in ~/.ccstory/config.toml Persistent, ccstory-only
~/.claude/CLAUDE.md Pasted verbatim, so it can carry richer directives
~/.claude/settings.json language Set by Claude Code's /config UI
System locale ($LANG) Auto-detected — zh_TW → Traditional Chinese, etc.
English Final fallback
ccstory week --lang "Traditional Chinese"   # one-off
export CCSTORY_LANG="日本語"                  # shell-scoped
# or in ~/.ccstory/config.toml:
# language = "Spanish"

The value is dropped straight into the prompt as Respond in <value>., so any name Claude can parse ("Traditional Chinese", "日本語", "pt-BR") works.

Custom pricing

Default API list prices snapshot to 2026-07. The report footer always shows the snapshot date so a stale price table can't silently distort cost over time. Override per-model in ~/.ccstory/config.toml:

[prices]
snapshot_date = "2026-08"

[prices.opus]
input       = 6.0
output      = 30.0
cache_write = 7.5
cache_read  = 0.6

Partial overrides are fine — unspecified keys keep their default. Defining a brand-new model ([prices.custom]) with only some keys defaults the rest to $0 with a warning so misconfig is loud.

How ccstory differs from ccusage

ccusage ccstory
Role The bill The story
Active hours (5-min gap heuristic)
Activity categories ✅ folder rules + content-aware
Per-session narrative ✅ via local claude -p
Per-bucket synthesis
Cross-period narrative
Local-only / no telemetry

Pair them — ccusage monthly for the spend, ccstory month for the breakdown:

ccusage monthly
ccstory month

Privacy

Everything runs locally. ccstory never sends your conversation data anywhere.

  • Data source: ~/.claude/projects/**/*.jsonl — Claude Code's own logs.
  • Narratives: subprocess-call your local claude -p (uses your own session / quota, no API key needed, no cost to ccstory).
  • Cache: ~/.ccstory/cache.db (sqlite, per-session summaries).
  • Reports: ~/.ccstory/reports/recap-*.md.

No telemetry, no network calls, no upload buttons. Verify in ccstory/session_summarizer.py.

Requirements

  • Python 3.11+ and pipx (brew install pipx on macOS, other platforms).
  • Claude Code CLI on PATH — required for --llm-narrative, content classification, and the cross-period synthesis. Without it, narratives fall back to the first user message and --classify falls back to folder rules.

Implementation notes

  • Time math: 5-minute gap heuristic — consecutive messages within 5 minutes count as active, longer gaps are "stepped away". Wall-clock dedup prevents parallel sessions from double-counting. The 5-min cap is a practical floor for "still at the keyboard"; comparable across periods even though not precise.
  • Timezone: session timestamps are parsed UTC-aware. Window boundaries (week, month) are local-midnight aligned, so "this week" matches the calendar week you actually lived in. --weeks N for trend mode does the same.
  • Cost comparison: cross-period diffs use output tokens, not total_tokens. In typical use ~96% of total_tokens is cache_read, which inflates with turn count and system prompt size and isn't a stable signal of work done. Output tokens stay comparable month over month.
  • Pricing: prices are list prices snapshotted by date (default 2026-07); the snapshot date renders in every report footer so stale numbers can't sneak past unnoticed.

Roadmap

  • --json structured output — one general primitive over per-destination export flavors
  • Optional PNG card export
  • ccstory year — annual recap (Spotify-Wrapped style)
  • Git commit / PR correlation — period-level What shipped section (per-session attribution still open, #11)

See the issue tracker for the full backlog.

License

MIT — see LICENSE.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

ccstory-0.5.0.tar.gz (464.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

ccstory-0.5.0-py3-none-any.whl (79.7 kB view details)

Uploaded Python 3

File details

Details for the file ccstory-0.5.0.tar.gz.

File metadata

  • Download URL: ccstory-0.5.0.tar.gz
  • Upload date:
  • Size: 464.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.5

File hashes

Hashes for ccstory-0.5.0.tar.gz
Algorithm Hash digest
SHA256 f6f1139859a7cde32fe3eb312565bdbd75587f9045a10f6db319561abd867c54
MD5 53eb3c658883134b0c5eabdbf181cf21
BLAKE2b-256 b11929cd2d6d12f4c61acff9aab0a740d13cf3b7a1451b06f323b437722913f3

See more details on using hashes here.

File details

Details for the file ccstory-0.5.0-py3-none-any.whl.

File metadata

  • Download URL: ccstory-0.5.0-py3-none-any.whl
  • Upload date:
  • Size: 79.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.5

File hashes

Hashes for ccstory-0.5.0-py3-none-any.whl
Algorithm Hash digest
SHA256 7250b526a1ea2ac2542dda9bd9f5e8677e4619d0a77a0cab849877c94f02f309
MD5 27fcf5693024469afc359fc26f8849f0
BLAKE2b-256 9e54698ba5f06504d64af9057512ca233274a71fab3bf00d0dfb3b457cdf4233

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page