Skip to content

Repository files navigation

TokenCast

Predict what an agentic coding task will cost and how long it takes — so you can build software estimates and plan timelines in the metered era. One file, no dependencies, Python 3.8+ (or open the no-install browser version).

Estimation used to be a human sizing a ticket. That no longer predicts the bill: a Microsoft/Stanford study found human-rated difficulty only weakly tracks actual token cost, and the same task can vary up to 30x. So TokenCast doesn't ask you to guess. It reads the session logs your agent already writes to disk, learns what tasks like the one you're planning have actually cost and taken, and returns a range (p50 / p90 / p95) for a single task or a whole sprint.

Use it where you scope work. Drop a forecast into a plan or a ticket and you have a cost-and-time number to defend before the work is committed, plus a budget and timeline for the whole sprint before you size it. Setup is pointing it at the logs your agent already writes; there's nothing to instrument, and nothing leaves your machine.

Install

pipx install tokencast        # light tier as a CLI (no dependencies)
uvx tokencast forecast --files 8 --tools 30   # or run it ephemerally
pip install tokencast         # or into your current environment

That gives you the stdlib-only light tier (forecast / report / estimate / budget / demo). The heavy optimizer tier (the tokencast-optimize command, which needs the Claude Agent SDK) installs via the extra:

pip install 'tokencast[optimize]'

No install at all? Just double-click tokencast.html (see below), or run python tokencast.py … directly from a clone.

Forecasts, not just reports

Most cost tools are rear-view: they tell you what you already spent. That's the right answer for a finance review and the wrong one when you're scoping next sprint. TokenCast estimates the cost and time of a task before you run it, using your own history as the calibration set. Pair it with a rear-view tool — ccusage or Anthropic's Usage and Cost API — when you want to reconcile a forecast against what actually happened.

Read the forecast as a calibrated bet, not a quote. The same agent on the same task can vary up to 30x (Microsoft Research / Stanford), which is why a single point estimate misleads and the p90 is the number to budget.

Easiest: no install (browser)

Open tokencast.html by double-clicking it. No Python, no terminal, no setup. Click Load demo data to try it instantly, or Choose your ~/.claude/projects folder to run it on your real history. Everything is parsed in the browser — nothing is uploaded. Works on any OS.

Power user: the CLI

# 1. Generate synthetic logs so you can try it with no setup
python tokencast.py demo --out ./sample_logs

# 2. Estimate one task (cost + time) from your history
python tokencast.py forecast ./sample_logs --files 8 --tools 30

# 3. Roll a sprint of 12 similar tasks into a budget + timeline
python tokencast.py forecast ./sample_logs --files 8 --tools 30 --count 12

# 4. Annotate a plan/ticket file with a p90 cost+time per line + a sprint total
python tokencast.py estimate plan.md ./sample_logs --files 8 --tools 30

# 5. (secondary) Attribute past spend, and see what a cap would clip
python tokencast.py report ./sample_logs --cap 5

Install (so tokencast is a command)

The light tier is pure stdlib, so you can just run python tokencast.py …. To get a tokencast command on your PATH (no dependencies installed):

pipx install tokencast      # or, ephemerally:
uvx tokencast forecast --files 8 --tools 30

Both expose the light tier (forecast/report/estimate/demo/budget). The heavy optimizer tier ships as the separate tokencast-optimize command (pip install 'tokencast[optimize]').

Point it at your real Claude Code logs by passing ~/.claude/projects (the default):

python tokencast.py report
python tokencast.py forecast --files 12 --tools 40

What it does

  • forecast (the primary command) — finds the k most similar past tasks by feature vector (files touched, tool calls, output tokens, turns, and cache-read tokens — the real cost driver) and returns a cost and time estimate as a range. It's a distance-weighted k-nearest-neighbors: closer past tasks count more, skewed features are compared on a log scale, and the output reports a one-word "neighbor fit" (tight / moderate / loose) so you can tell a calibrated estimate from a thin-history guess. Pass --count N to roll up a sprint/project total via Monte Carlo. This is the number you put in your estimate — calibrated on your history, not a human guess.
  • estimate — the "cost line in the plan." Takes a plan/ticket markdown file and annotates each ticket (any -/*/+ bullet, GitHub - [ ] checkbox, or 1./2) ordered-list item; headings, blanks, and prose are ignored) with its p90 cost + p90 time, then prints a sprint total (sum of per-ticket p90s plus a Monte-Carlo p50/p90). Uses the same kNN forecaster and the same accurate-vs-floor labeling as forecast. A ticket can hint its own size inline, e.g. - Add OAuth (files=8 tools=30 output=4000) — those override the global --files/--tools/--output for that ticket; otherwise each falls back to your history mean. Tickets with no hint all land on that same mean, so they get the same number — hint the ones whose size actually differs (the command prints a note when un-sized tickets share one estimate).
  • report (secondary) — total spend, per-task distribution (p50/p90/p95/max), breakdown by project and model, your most expensive tasks, and an optional --cap overlay showing how many tasks a hard ceiling would have cut off mid-work. Tools like ccusage already do this well.
  • demo — writes synthetic JSONL matching Claude Code's schema so you can try everything without real data.

Time estimates use wall-clock session duration (first-to-last event), so they include human think/idle time — treat them as a rough timeline proxy, not billed compute.

Segmenting sessions into tasks (optional)

A single Claude Code session often holds several distinct tasks — you start something, walk away, then come back hours later on something new. By default TokenCast treats one log file as one task (one session = one task), which can lump those together. Pass --segment to split each session into task-sized units wherever there's a long idle gap, so "a task" matches the unit a planner actually estimates (a ticket / a feature):

# Split sessions wherever there's a >30-min idle gap (the default threshold)
python tokencast.py forecast ./sample_logs --files 8 --tools 30 --segment

# Tune the gap, or also cut at fresh user turns between tasks
python tokencast.py forecast ./sample_logs --segment --gap-min 20 --split-on-user
python tokencast.py report   ./sample_logs --segment
  • --segment is opt-in and backward-compatible: with no flag, behavior is exactly as before.
  • --gap-min N sets the idle-gap threshold in minutes (default 30; 0 disables the gap rule).
  • --split-on-user additionally cuts at a fresh user turn once the current segment already has work in it (off by default — pure user-turn splitting over-segments).
  • Costs and token/call counts of the segments sum to the whole-session totals (a file touched in two segments counts in each, so files_touched may not); segmentation changes the unit of estimation, not the underlying token accuracy (the floor caveat below still applies).

The browser version mirrors this: tick Segment long sessions into tasks in the forecast panel and set the idle-gap minutes.

Read the absolute numbers as a floor

Claude Code's JSONL logs undercount input tokensinput_tokens is a streaming placeholder that's 0 or 1 in ~75% of entries, while the real volume lives in the cache fields (which are reliable). So TokenCast's absolute numbers are a floor, and the tool flags this in its output.

Once you run tasks through the optimizer tier (tokencast-optimize run/auto), those runs are measured accurately and written to ./runs with an accuracy marker. forecast then prefers them: with ≥5 accurate runs that carry a usable (>0) cost signal it calibrates on real token counts and drops the floor caveat (point it at a different dir with --runs DIR; the default ./runs is relative to your current directory). Until then it's still a floor — and says so.

Until then, use the ranges for what they're good at: relative planning. Which tasks are expensive, where a budget cap starts cutting off work mid-task, how one sprint compares to the next — all of that holds up even when the absolute floor runs low.

Pricing (and staying current)

Anthropic publishes no machine-readable price feed, so TokenCast ships with built-in defaults (Opus $5/$25, Sonnet $3/$15, Haiku $1/$5 per 1M tokens; cache write 1.25×, read 0.10×) and can pull live rates from the community LiteLLM cost map, which is day-0 updated.

  • Browser: prices auto-refresh from the feed on load (silent fallback to defaults if offline); there's also a manual ↻ Refresh from live pricing button. Any field is editable.
  • CLI: add --refresh-prices to fetch and cache the latest (~/.tokencast_prices.json); it falls back to the cache, then to built-in defaults, if the network is unavailable.
python tokencast.py report --refresh-prices

Verify high-stakes numbers against Anthropic's pricing page. The feed is community-maintained, not official, so double-check anything you're committing a budget to.

Extending it (multi-agent)

The forecast/report layer is agent-agnostic — it consumes a list of per-session summaries and does the percentile/kNN math on them. Only the reader (the parser) differs per tool. Readers live in a small registry (READERS in tokencast.py); a reader maps one log file to zero-or-more of the same summary dicts the Claude Code parser produces. Add a tool by registering a reader; nothing else changes.

Pick a reader with --format on forecast/report (default auto, which detects Claude Code and falls back gracefully — a directory can even mix tools):

python tokencast.py forecast ./logs --format auto         # detect per file (default)
python tokencast.py forecast ./logs --format claude-code  # today's Claude Code JSONL
python tokencast.py forecast ./logs --format generic      # the portable schema below

The generic schema is the documented minimal shape any agent that logs per-message token usage can emit — one JSON object per line (JSONL), one per assistant message:

{"timestamp": "2026-01-01T00:00:00Z",
 "model": "claude-sonnet-4-6",
 "usage": {"input_tokens": 1234, "output_tokens": 567,
           "cache_creation_input_tokens": 0, "cache_read_input_tokens": 0},
 "tools": ["Edit", "Bash"],
 "files": ["src/a.py"]}

Only model + usage.{input_tokens,output_tokens} are required; cache fields default to 0, tools/files are optional feature signals. Cost uses the same model-family pricing as Claude Code (a claude-* id prices correctly; an unknown id falls back to Sonnet, flagged). The input-token undercount honesty carries over: placeholder input_tokens<=1 with real output still counts as a suspect entry, so a generic-log forecast is a floor unless the run was harness-measured (set "tokencast_accurate": true on a line to mark it accurate).

Native readers for Cursor, Copilot/gh, Codex, and Aider are future work that slots into the same registry — write the tool's read + sniff and register them; or pre-process its logs into the generic JSONL above. The browser (tokencast.html) stays Claude-Code-focused for now; multi- format in the browser is future work. Contributions for new readers are welcome. See docs/superpowers/specs/2026-06-06-multi-agent-readers-design.md.

Budgets (optional)

In the metered era teams set hard spend caps (e.g. a per-engineer quarterly budget). TokenCast can track spend against a cap and tell you your runway — entirely optional, stdlib-only, offline. Create a tokencast_budget.json:

{
  "period": "quarterly",
  "period_start": "2026-04-01",
  "budgets": [
    { "scope": "global", "amount": 15000 },
    { "scope": "project:tokencast", "amount": 2000 }
  ]
}
python tokencast.py budget                       # consumed / remaining / burn rate / runway
python tokencast.py budget --scope project:tokencast --per-task 12.50
python tokencast.py budget --forecast 800        # would an $800 sprint fit what's left?

It counts two spend streams, labeled separately: your real Claude Code usage (~/.claude/projects, a floor due to the input-token undercount) and accurate TokenCast runs (./runs). With no tokencast_budget.json, nothing changes and budget just prints a hint.

Optimizer tier (preview)

The forecast/report/demo commands above are the stdlib-only, offline core. A separate optimizer tier wraps a locally-running Claude via the Claude Agent SDK to measure runs accurately — the SDK returns real token counts, so this path is not subject to the JSONL undercount floor that the core warns about.

The whole loop, end to end

The recommended entry is the /tokencast-optimize skill — describe your task, project, and budget, and it drives everything below: it does the judgment (drafting the eval set, choosing what to optimize, reading the numbers) and shells out to these CLIs for the deterministic math. Under the hood it's a handful of composable steps, each usable on its own and each gating real spend behind a confirmation:

Step Command What it does
Forecast python tokencast.py forecast pre-flight cost/time from your history
Draft an eval set tokencast-optimize eval init exit criteria → scorable dimensions
Score a config tokencast-optimize eval run composite quality + accurate cost/time
Optimize (+ decompose) tokencast-optimize auto … --decompose rank configs across every axis, promote the winner, compare task splits
Track budget python tokencast.py budget spend vs cap → runway

The axes auto optimizes: model, instructions, tools, skills, MCP servers, and task decomposition — cost-first under a quality floor, budget-aware, all optional.

Install the extra and run one task under one config:

pip install -e ".[optimize]"        # pulls claude-agent-sdk + pyyaml
tokencast-optimize run examples/task.md --config configs/baseline/ --budget 2.00 --out runs/
python tokencast.py report runs/    # the core reads the accurate logs back

A config dir mirrors Agent Optimizer's layout: metadata.yaml (model, budget, max_turns), instructions.md (system-prompt append), tools.json (allowed/disallowed tools, MCP servers), and an optional skills/ dir. Before any real spend the CLI prints a pre-flight cost estimate and asks you to confirm.

Security note. An eval set's command rule runs its cmd through the shell with your user privileges (that's how it runs your real test suite). eval init can draft an eval set with an LLM — treat that draft as untrusted and read it before running. File paths (task ids, check paths) are confined to the sandbox; the shell command itself is not.

run is the foundation; the full closed-loop optimizer is built on top of it — eval harness, cost-first ranking, failure-driven candidate generation, skills/MCP axes, and task decomposition, tied together by the auto command and the /tokencast-optimize skill (below). See docs/superpowers/specs/ for the design of each piece.

Evaluating configs (eval harness)

Score a config against an eval set (one or many tasks). Each task runs in an isolated sandbox (fresh temp dir, optionally seeded from a dir or a git worktree), then is scored by deterministic rule checks (tests pass, file exists) plus an LLM judge for qualitative dimensions:

# draft an eval set from your repo (LLM; review before running)
tokencast-optimize eval init --root . --out evals/generated/

# run an eval set under a config, scored
tokencast-optimize eval run evals/generated/evalset.yaml --config configs/baseline/ --out runs/

eval run prints a composite quality score, pass rate, and total cost/time — the numbers the optimize loop (next sub-project) ranks candidate configs on. Generated eval sets are drafts: their rule checks are LLM-authored shell commands, so review them before running.

Optimizing (cost-first, budget-aware)

Rank candidate configs against an eval set and promote the cheapest that holds quality:

# try opus/sonnet/haiku variants of the baseline, ranked cost-first under its quality floor
tokencast-optimize optimize evals/generated/evalset.yaml --config configs/baseline/ --model-sweep

# frame the win against a budget: how much more work fits the same cap?
tokencast-optimize optimize evalset.yaml --config configs/baseline/ --model-sweep \
    --budget-remaining 15000 --need-tasks 1000

It prints a ranked table (* winner, + Pareto frontier), the winner-vs-baseline cost delta, and — when a budget is supplied (--budget-remaining, or --budget-config/--budget-scope to read your tokencast_budget.json ledger) — the runway gained ("winner affords ~1,428 tasks vs 830, same budget") plus a --need-tasks N fit verdict. The winner config is written to runs/promoted/ (and to --promote DEST if given); your live CLAUDE.md is never touched.

Add --generate N to have an LLM read the baseline's eval failures and propose N candidate configs (rewritten instructions / adjusted tool lists) — they're evaluated and ranked alongside the rest:

tokencast-optimize optimize evalset.yaml --config configs/baseline/ --generate 3

The generator only ever changes instructions and tool allow/deny lists (the skills & MCP axes come later); its proposals run through the same sandbox + ranking as any candidate.

A config can also declare which skills it uses (a skills: name list in metadata.yaml, selecting from your ~/.claude/skills + plugins) and bring its own skills via a skills/ subdir (staged into each run's sandbox). MCP servers are set via tools.json's mcp_servers. These become optimization axes the generator can tune in a later step.

With --generate N, the generator now tunes those axes too: it's shown each failing dimension's definition (rubric / rule) and the skills + MCP servers you have available, and may propose adding some to a candidate. Point it at a specific catalog with --skills-dir DIR (default ~/.claude/skills) and --mcp-catalog FILE (default ~/.claude.json); proposed names are validated against those catalogs and added on top of the baseline's.

Decomposing a task

Sometimes the cheapest win isn't a different config — it's splitting the task into smaller sub-tasks (and routing the easy ones to a cheaper model). decompose compares the whole-task run against LLM-proposed decompositions and reports which is actually cheaper at an acceptable quality:

tokencast-optimize decompose evalset.yaml --config configs/baseline/ --generate 3

Each decomposition's sub-tasks run in sequence in one sandbox (later steps see earlier file changes) and are scored on the same dimensions; per-task it reports the monolithic vs the winning decomposition's cost/time. Use --by time to optimize wall-clock and --min-quality to set the floor (default: the monolithic run's quality).

One-shot front door

auto chains the common path — forecast, optimize (model + instructions + tools + skills + MCP), promote the winner — behind a single confirmation, and optionally compares task decompositions too:

tokencast-optimize auto evalset.yaml --config configs/baseline/ --generate 3 --decompose

--generate N is the optimize axis (candidate configs); --decompose proposes its own decompositions on the winner, --decompose-generate N per task (default 2), so --decompose always tries real splits even without --generate. It writes a consolidated auto.json. For a conversational driver that drafts the eval set, forecasts, and interprets the results for you, use the /tokencast-optimize skill — it calls these CLIs for the deterministic work (forecast, scoring, ranking) and handles the judgment (what to optimize, how to read the numbers) itself.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages