Metadata-Version: 2.4
Name: ccstory
Version: 0.5.0
Summary: Claude Code usage recap with narrative. ccusage tells you the bill, ccstory tells the story.
Project-URL: Homepage, https://github.com/atomchung/ccstory
Project-URL: Issues, https://github.com/atomchung/ccstory/issues
Author: atomchung
License: MIT
License-File: LICENSE
Keywords: analytics,ccusage,claude,claude-code,cli,developer-tools,narrative,productivity,recap,tracking,usage,weekly-report
Classifier: Development Status :: 3 - Alpha
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Software Development :: Quality Assurance
Classifier: Topic :: Utilities
Requires-Python: >=3.11
Requires-Dist: rich>=13.0
Provides-Extra: test
Requires-Dist: pytest>=8.0; extra == 'test'
Description-Content-Type: text/markdown

# 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](https://github.com/ryoppippi/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

```bash
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`:

```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:

```markdown
| 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:

```toml
[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:

```bash
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:

```bash
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]]`:

```yaml
---
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 |

```bash
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`:

```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](https://github.com/ryoppippi/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:

```bash
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](ccstory/session_summarizer.py).

## Requirements

- **Python 3.11+** and **pipx**
  (`brew install pipx` on macOS, [other platforms](https://pipx.pypa.io/stable/installation/)).
- **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

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

See the [issue tracker](https://github.com/atomchung/ccstory/issues) for the
full backlog.

## License

MIT — see [LICENSE](LICENSE).
