Token Forge
A pixel-art blacksmith forge in your VS Code sidebar that burns hotter and
smokes more the more tokens your AI agent burns in the current turn.
Quick question → a few embers. Big refactor → a white-hot roaring blaze.
Status: working prototype
Verified end-to-end against live session data:
claude-code lastActivity=2s ago tokensThisTurn=20757 -> intensity 0.558
copilot-chat lastActivity=934s ago tokens=13 credits=11.16675
MUX picks: claude-code
How it works
Heat providers (src/providers.ts) read whichever agent you're using and
normalize it to one number. Both sources were confirmed to persist real
per-turn usage on disk:
| Tool |
Source |
Fields |
Status |
| Claude Code |
~/.claude/projects/<workspace>/<session>.jsonl |
message.usage.output_tokens summed since the last real user prompt |
verified live |
| GitHub Copilot Chat |
<VSCode>/User/workspaceStorage/<ws>/chatSessions/*.jsonl |
outputTokens / completionTokens / copilotCredits |
verified live |
| GitHub Copilot CLI |
~/.copilot/session-state/<id>/events.jsonl |
assistant.message.outputTokens, reset on user.message |
verified live |
| OpenAI Codex |
~/.codex/logs_2.sqlite and its -wal |
total_usage_tokens per turn; turns ordered by the UUIDv7 timestamp embedded in turn.id |
verified live |
Copilot CLI also mirrors totals into session-store.db, but the JSONL event log
is used instead so the extension takes on no SQLite dependency.
HeatMultiplexer picks whichever tool wrote most recently, so it follows the
agent you're actually using. toIntensity() maps usage → 0..1 on a
log-ish curve (ordinary turns still show real fire; huge turns go white-hot).
Five themes ship with it — forge, arcane, rainbow, necrotic and ash. Each is a
different furnace with its own flame ramp, glow and ember tints, switchable live
from settings.
The fire is 100% procedural (media/forge.js) — no animation assets:
- Doom-fire cellular simulation seeded from the coal bed; higher intensity =
less decay = taller, brighter flames
- 64-step palette: black → red → orange → amber → warm gold. Orange is held
high up the ramp so a full blaze still reads as fire instead of washing out
- Smoke whose volume, rise speed and lifetime all scale with heat, so a
cold forge wisps and a hot one throws a tall billowing column. The ramp is
linear-dominant — an earlier quadratic left mid-range turns (~10k tokens)
reaching only a fifth of the available height. The canvas carries 118px of
headroom, and the smoke is mid-gray so it reads on both dark and light
editor themes. Puffs are blitted from one pre-rendered sprite rather than
built per particle, which keeps hundreds of them cheap. Rise speed is tuned
so the column burns out before the top of the canvas — note that velocity
damping means travel is
vy·(1−0.996^N)/0.004, not vy·N, and getting that
wrong guillotines the plume at high heat. An alpha ramp over the last 34px
catches any outliers.
- Embers from the fire, plus sparks carried up the chimney once it's really
hot; spawn rate scales with the square of intensity
- Heat haze in the flue column — from just above the flames up through the
hood and out the top of the chimney, i.e. the path the hot air actually
takes, and horizontally confined to the channel above the fire opening so
the outer brick piers stay still. The wobble is feathered across that channel
(full mid-column, zero at the sides) so there's no seam against the
stationary brickwork. Amplitude is deliberately small (~2px): the sprite has
hard pixel edges, so a large per-row shift breaks the hood's silhouette and
reads as a glitch. The smoke above the chimney has already cooled, so it is
left crisp.
Worst-case cost measured at 0.55 ms/frame (intensity 1.0, ~350 smoke particles,
220 embers, haze on) — roughly 3% of a 60 fps budget.
- A radial glow that lights the surrounding brick
- Everything is clipped to the detected firebox cavity mask
The only art assets are the five forge bodies (media/forge*.png), cleaned from
AI references with unfake.js — the same
pipeline used for the Jimothy pet. Each furnace's firebox cavity is detected
automatically: the cavity is a large flat field of a single colour, so it is
found by sampling the middle of the arch and then eroding the matching mask to
break the thin outlines that otherwise connect it to the sprite's edge.
Layout
src/providers.ts heat interface + Claude Code / Copilot adapters + multiplexer
src/extension.ts webview view provider, polls providers, posts heat messages
media/forge.js procedural fire / smoke / ember / glow engine
media/main.js webview entry — eases intensity, drives the render loop
media/forge.png the forge sprite (from Furnace.png via unfake)
media/forge_regions.json detected firebox + chimney coordinates
Run it
npm install
npm run compile
# then F5 in VS Code to launch the Extension Development Host
Open the Token Forge view in the activity bar and ask your agent to do
something big.
Changing theme
Run Token Forge: Change Theme from the command palette, or use the palette
button on the view's title bar. Themes preview as you arrow through them and
revert if you cancel. The tokenForge.flameTheme setting can also be edited
directly; either way the change applies without reloading.
Settings
| Setting |
Default |
Meaning |
tokenForge.flameTheme |
forge |
Flame colours: forge, arcane, rainbow, necrotic, ash |
tokenForge.fullBlazeTokens |
60000 |
Tokens in one turn = white-hot (lower = ramps up sooner) |
tokenForge.pollMs |
1000 |
How often to re-read session data |
tokenForge.coldAfterSeconds |
300 |
Idle time after which the forge goes cold |
Known gaps / next steps
- No agent streams partial usage to disk, so counts arrive per completed model
message. The forge animates between readings rather than inventing numbers;
a genuinely live count would need an API-proxy approach.
- Codex's log format is confirmed, but only
thread_title generations have
been observed so far. Turn grouping (currently a 25s activity gap) should be
re-checked against a real agent turn.
- Ideas: coal pile that depletes, anvil + Jimothy working the bellows,
status-bar mini-flame, cumulative session totals.
Agent support
| Agent |
Status |
Source |
| Claude Code |
✅ Verified |
~/.claude/projects/**/**.jsonl — message.usage.output_tokens |
| GitHub Copilot Chat |
✅ Verified |
chatSessions/*.jsonl in workspace storage |
| Copilot CLI |
✅ Verified |
~/.copilot/session-state/*/events.jsonl — outputTokens |
Codex (openai.chatgpt) |
✅ Verified |
~/.codex/logs_2.sqlite (+WAL) — total_usage_tokens per turn.id |
Whichever tool wrote most recently wins, so switching agents switches the fire.
If nothing is active, the forge sits cold with banked embers.
On Codex: real agent turns are recorded in ~/.codex/logs_2.sqlite, not in
the extension-host log (which only carries internal generations such as thread
titles). To avoid a SQLite dependency the adapter scans the tail of the database
and its WAL; because WAL frames are not in write order, turns are sequenced by
the Unix-millisecond timestamp embedded in each turn.id (a UUIDv7) rather than
by file position.
A note on live updates: every one of these tools writes usage only when a
model message completes — nothing streams partial counts to disk. The forge
therefore receives usage in steps, and animates the counter up to each new
reading rather than snapping. The number shown is always one the agent actually
reported; nothing is extrapolated.
Where it lives
The forge appears as a section in the Explorer sidebar, so it stays visible
while you work with your files. Drag its header to move it anywhere you like —
the secondary sidebar, the bottom panel, or its own activity-bar container.
Drag the section divider to resize it; the forge scales to fit.
For a big view, run Token Forge: Open in Editor Tab from the Command
Palette — it opens as a normal editor tab you can split, move, and resize
freely.
Scoping
Every provider is restricted to the window's workspace, so multiple VS Code
windows each show their own project's activity rather than mirroring one
another. Claude Code reads only its encoded project directory, Copilot Chat only
this window's workspaceStorage entry, and Codex — which keeps one shared log —
filters turns by the working directory recorded against each thread.
Privacy
Token Forge reads only token/usage counters from agent session data that
already exists on your machine:
| Agent |
File read |
What is read |
| Claude Code |
~/.claude/projects/<workspace>/*.jsonl |
usage.output_tokens per message |
| GitHub Copilot Chat |
VS Code workspaceStorage/**/chatSessions/*.json |
per-response token/credit counters |
It does not read, store, or transmit your prompts, your code, or model
responses. Nothing leaves your machine — it makes no network calls, and has no
telemetry, and no analytics of any kind. Everything is rendered locally in a
webview.