# How the `/caveman-stats` Command Calculates Token Savings and Costs

> Learn how the /caveman-stats command calculates token savings and costs by analyzing logs, applying compression benchmarks, and using pricing tables for accurate USD estimates.

- Repository: [Julius Brussee/caveman](https://github.com/JuliusBrussee/caveman)
- Tags: how-to-guide
- Published: 2026-07-07

---

**The `/caveman-stats` command analyzes local Claude session logs to attribute output tokens to specific Caveman modes, applies compression benchmarks to estimate saved tokens, and converts those savings into USD using model-specific pricing tables.**

The `/caveman-stats` command is a slash command for Claude Code that provides detailed analytics on token usage and cost savings when using the Caveman mode. According to the JuliusBrussee/caveman source code, the calculation involves parsing session logs, attributing tokens to specific modes based on timestamps, and applying compression ratios to estimate financial savings. The implementation lives primarily in [`src/hooks/caveman-stats.js`](https://github.com/JuliusBrussee/caveman/blob/main/src/hooks/caveman-stats.js).

## Parsing the Session Log

### Locating the Session File

The hook first identifies the Claude session file to analyze. It receives the path via the `--session-file` flag (injected by the mode tracker) or discovers the most recent session automatically using `findRecentSession()`.

According to lines 55-75 of [`src/hooks/caveman-stats.js`](https://github.com/JuliusBrussee/caveman/blob/main/src/hooks/caveman-stats.js), the discovery logic scans the `.claude/projects/` directory for recent `session.jsonl` files.

### Extracting Token Counts

Once located, `parseSession()` walks each line of the JSONL file to aggregate raw usage statistics. Based on lines 95-100, the parser extracts:

- `outputTokens` from `usage.output_tokens`
- `cacheReadTokens` from `usage.cache_read_input_tokens`
- `turns` incremented per assistant message
- `model` from the first `message.model` encountered
- `messages` array with timestamps and token counts

```js
// Excerpt from src/hooks/caveman-stats.js#L95-L100
outputTokens    += usage.output_tokens           || 0;
cacheReadTokens += usage.cache_read_input_tokens || 0;

```

## Mode Attribution and Token Mapping

Caveman can run in different modes (`full`, `lite`, etc.). The command must attribute token usage to the correct mode based on when messages were generated, not just the current mode flag.

### Tracking Mode Transitions

The system tracks mode changes via the `.caveman-active` flag file and a transition log (`.caveman-mode-log.jsonl`). The hook reads these files via `readModeLog()` to build a timeline of mode switches.

### Attribution Logic

The `attributeByMode()` function (lines 84-90) matches each message's timestamp to the most recent mode transition. It returns an attribution map keyed by mode, plus `unknownTokens` for messages that cannot be matched.

```js
// From src/hooks/caveman-stats.js#L84-L90
const attribution = attributeByMode({
  messages: parsed.messages,
  modeLog,
  mode,
  flagMtimeMs,
  outputTokens: parsed.outputTokens,
});

```

This ensures that if you switched from `lite` to `full` mid-session, tokens are correctly attributed to each respective mode.

## Calculating Token Savings

### Compression Benchmarks

The savings calculation relies on hard-coded compression ratios stored in the `COMPRESSION` map at lines 19-20. Currently, only the `full` mode has a defined benchmark of **65%** savings (a ratio of 0.65).

### The Savings Formula

The `deriveSavings()` function (lines 44-47) applies these ratios to estimate how many tokens would have been generated without Caveman:

```js
// From src/hooks/caveman-stats.js#L44-L47
estSavedTokens += Math.round(tokens / (1 - ratio)) - tokens;

```

This formula calculates the total tokens that would have been required (dividing current tokens by the remaining 35%) and subtracts the actual tokens used.

## Converting Tokens to USD Costs

### Model Pricing Lookup

To convert savings into dollars, the script uses the `MODEL_OUTPUT_PRICE_PER_M` table (lines 26-39), which maps model prefixes to USD per million output tokens. The `priceForModel()` function selects the first matching prefix, prioritizing the most specific match.

### Currency Calculation and Formatting

Lines 48-50 multiply the estimated saved tokens by the model's price:

```js
// From src/hooks/caveman-stats.js#L48-L50
const price = priceForModel(model);
const estSavedUsd = price !== null ? (estSavedTokens / 1_000_000) * price : 0;

```

The `formatUsd()` function (lines 49-53) then formats this value for display.

## Passive Memory Compression Detection

Beyond active session savings, the command detects passive memory savings from compressed markdown files.

### Finding Compressed File Pairs

`findCompressedPairs()` (lines 113-144) scans project folders for `*.original.md` / `*.md` pairs created by the `caveman-compress` skill. It calculates the byte difference between original and compressed versions.

### Token Estimation from Bytes

The byte savings are converted to approximate token counts using a divisor of 4 (bytes per token), displayed under "Memory compressed" in the final statistics.

## Lifetime Aggregation and Status Line Updates

### Historical Data Storage

Every successful run appends session totals to `.caveman-history.jsonl`. The `aggregateHistory()` function (lines 63-84) reads this file, keeps only the latest entry per session, and sums `output_tokens`, `est_saved_tokens`, and `est_saved_usd`.

### Generating the Status Line Suffix

The aggregated savings are converted to a human-readable suffix (e.g., `⛏ 12.4k`) via `humanizeTokens()` (lines 302-307) and written to `.caveman-statusline-suffix`. This allows Claude's UI to display lifetime savings without shell parsing.

## Rendering the Output

### Detailed Statistics Report

The `formatStats()` function (lines 447-462) generates the multi-line report shown when running `/caveman-stats`, including the attribution breakdown, compression estimates, and USD savings.

### Shareable Summary

When invoked with `--share`, `formatShare()` (lines 330-340) creates a concise, tweet-ready line suitable for social media sharing.

```bash

# Generate detailed report

/caveman-stats

# Create shareable summary

/caveman-stats --share

# Show lifetime totals for last 7 days

/caveman-stats --since 7d

```

## Programmatic Usage

You can also use the calculation functions directly in Node.js:

```js
const { formatStats, parseSession, attributeByMode } = require('./src/hooks/caveman-stats');

const sessionPath = '/home/you/.claude/projects/foo/session.jsonl';
const parsed = parseSession(sessionPath);
const attribution = attributeByMode({
  messages: parsed.messages,
  modeLog: [],
  mode: 'full',
  flagMtimeMs: null,
  outputTokens: parsed.outputTokens,
});

const stats = formatStats({
  ...parsed,
  mode: 'full',
  sessionPath,
  compressed: null,
  attribution,
});
console.log(stats);

```

## Summary

- **Session Parsing**: The command reads Claude's JSONL session logs using `parseSession()` to extract raw token counts and model information from [`src/hooks/caveman-stats.js`](https://github.com/JuliusBrussee/caveman/blob/main/src/hooks/caveman-stats.js).
- **Mode Attribution**: `attributeByMode()` maps each message to the active Caveman mode at the time of generation using timestamp matching against `.caveman-mode-log.jsonl`.
- **Savings Estimation**: `deriveSavings()` applies benchmark compression ratios (65% for `full` mode) to calculate estimated tokens saved.
- **Cost Conversion**: `priceForModel()` looks up model-specific pricing in `MODEL_OUTPUT_PRICE_PER_M` to convert saved tokens to USD.
- **Memory Compression**: `findCompressedPairs()` detects compressed markdown files and estimates passive savings.
- **Lifetime Tracking**: `aggregateHistory()` maintains running totals in `.caveman-history.jsonl` and updates `.caveman-statusline-suffix` for UI display.

## Frequently Asked Questions

### How does caveman-stats handle sessions where the mode changed mid-conversation?

The `attributeByMode()` function matches each message's timestamp against the mode transition log (`.caveman-mode-log.jsonl`). Tokens generated before a switch are attributed to the previous mode, while those after belong to the new mode. Unmatched tokens are categorized as `unknownTokens`.

### Why does the savings calculation only consider output tokens?

According to the source code and repository documentation ([`docs/HONEST-NUMBERS.md`](https://github.com/JuliusBrussee/caveman/blob/main/docs/HONEST-NUMBERS.md)), Caveman specifically targets output token reduction through compression and context management. Input tokens remain largely unaffected by the mode's optimizations, so the calculation focuses solely on `output_tokens` to provide an honest metric of the tool's impact.

### Where does the 65% compression benchmark come from?

The 65% savings ratio for `full` mode is hard-coded in the `COMPRESSION` map at lines 19-20 of [`src/hooks/caveman-stats.js`](https://github.com/JuliusBrussee/caveman/blob/main/src/hooks/caveman-stats.js). This benchmark represents the estimated reduction in output tokens achieved when Caveman's compression and context optimization features are fully active.

### Can I use caveman-stats without Claude Code?

While designed as a Claude Code slash command, the core functions are accessible programmatically. You can import `parseSession`, `attributeByMode`, and `deriveSavings` from [`src/hooks/caveman-stats.js`](https://github.com/JuliusBrussee/caveman/blob/main/src/hooks/caveman-stats.js) and pass a session file path directly. However, the mode attribution logic expects the `.caveman-mode-log.jsonl` and flag files created by the Caveman mode tracker.