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
19 changes: 18 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,11 @@ tokenleak focus --provider codex --days 30
tokenleak nutrition
tokenleak nutrition --days 30 --format json

# Optimization intelligence
tokenleak simulate-routing --days 30
tokenleak waste --severity high
tokenleak behavior-diff --provider claude-code,codex --days 30 --format json

# Authenticate Cursor and sync its local cache
tokenleak cursor login --name work

Expand All @@ -151,7 +156,7 @@ tokenleak --list-providers

### Analysis commands

Tokenleak ships three dedicated investigation commands in addition to the main dashboard flow:
Tokenleak ships dedicated investigation and optimization commands in addition to the main dashboard flow:

```bash
# Explain what drove a specific day
Expand Down Expand Up @@ -181,12 +186,24 @@ tokenleak nutrition --days 30

# Emit the AI ROI report as JSON
tokenleak nutrition --format json --output ai-roi.json

# Estimate savings from model routing
tokenleak simulate-routing --days 30

# Detect agent waste signals with evidence and recipes
tokenleak waste --days 30

# Compare two agent/provider/model cohorts
tokenleak behavior-diff --provider claude-code,codex --days 30
```

- `tokenleak explain <date>` builds a narrative day report with top providers, sessions, projects, models, and anomaly flags.
- `tokenleak focus` ranks sessions by a deep-work score derived from duration, token density, and project streak.
- `tokenleak replay [date]` shows a chronological timeline of all sessions for a day, clustering events into flow blocks with a pulse chart and flow/think ratio. Defaults to today. Pass `--interactive` (or `-i`) to open a browser scrub UI on `http://localhost:3567` — drag the timeline, press space to play the day at 60–600× speed, watch the cumulative cost odometer tick up. Combine with `--open` to launch the browser automatically.
- `tokenleak nutrition` powers the TUI **AI ROI** view. It resolves local Git repo roots from provider project paths, runs read-only `git log --numstat`, and reports tokens/cost per commit and changed line. `No Git signal` means Tokenleak saw AI usage for a repo path but found no commits in the selected date window; switch to a wider window or ensure the project path exists locally as a Git worktree.
- `tokenleak simulate-routing` re-prices historical events under conservative downgrade rules so pro users can estimate savings before changing model habits or team guidance.
- `tokenleak waste` detects deterministic waste signals such as context drag, repeated prompt clusters, model churn, cache misses, and premium models used for small tasks.
- `tokenleak behavior-diff` compares cohorts such as provider-vs-provider or model-vs-model and emits deterministic takeaways for engineering teams.

### Cursor commands

Expand Down
70 changes: 65 additions & 5 deletions packages/cli/src/cli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -797,7 +797,9 @@ describe('CLI invocation', () => {
expect(stdout).toContain('--more');
expect(stdout).toContain('tokenleak explain <date>');
expect(stdout).toContain('focus');
expect(stdout).not.toContain('tokenleak waste');
expect(stdout).toContain('tokenleak simulate-routing');
expect(stdout).toContain('tokenleak waste');
expect(stdout).toContain('tokenleak behavior-diff');
expect(stdout).toContain('interactive launcher');
expect(stdout).toContain('Examples:');
});
Expand Down Expand Up @@ -874,16 +876,74 @@ describe('CLI invocation', () => {
expect(stderr).toContain('--days must be a positive number');
});

test('waste is not exposed as a standalone command', async () => {
test('waste --help exits with code 0 and prints waste usage', async () => {
const proc = Bun.spawn(['bun', cliPath, 'waste', '--help'], {
stdout: 'pipe',
stderr: 'pipe',
});
const exitCode = await proc.exited;
const stderr = await new Response(proc.stderr).text();
const stdout = await new Response(proc.stdout).text();

expect(exitCode).toBe(1);
expect(stderr).toContain('Advisor view for Waste Patterns');
expect(exitCode).toBe(0);
expect(stdout).toContain('tokenleak waste');
expect(stdout).toContain('agent waste signals');
});

test('waste emits a JSON agent waste report', async () => {
const { env, cleanup } = createProviderFixtureEnv();

try {
const waste = Bun.spawn(['bun', cliPath, 'waste', '--format', 'json', '--provider', 'pi'], {
stdout: 'pipe',
stderr: 'pipe',
env,
});
const wasteExit = await waste.exited;
const wasteStdout = await new Response(waste.stdout).text();
expect(wasteExit).toBe(0);
expect(JSON.parse(wasteStdout).summary).toBeDefined();
} finally {
cleanup();
}
});

test('simulate-routing emits a JSON routing report', async () => {
const { env, cleanup } = createProviderFixtureEnv();

try {
const simulate = Bun.spawn(['bun', cliPath, 'simulate-routing', '--format', 'json', '--provider', 'pi'], {
stdout: 'pipe',
stderr: 'pipe',
env,
});
const simulateExit = await simulate.exited;
const simulateStdout = await new Response(simulate.stdout).text();
expect(simulateExit).toBe(0);
expect(JSON.parse(simulateStdout).strategy).toBe('conservative');
} finally {
cleanup();
}
});

test('behavior-diff emits a JSON behavior diff report', async () => {
const { env, cleanup } = createProviderFixtureEnv();

try {
const diff = Bun.spawn(
['bun', cliPath, 'behavior-diff', '--format', 'json', '--provider', 'pi,claude-code'],
{
stdout: 'pipe',
stderr: 'pipe',
env,
},
);
const diffExit = await diff.exited;
const diffStdout = await new Response(diff.stdout).text();
expect(diffExit).toBe(0);
expect(JSON.parse(diffStdout).takeaways).toBeArray();
} finally {
cleanup();
}
});

test('--version prints version', async () => {
Expand Down
Loading
Loading