* feat(context): add experimental context command with per-session token tree
Reconstructs context breakdown for a Claude Code session
from its transcript: messages and tokens by role, block type, and tool,
split into full-session history vs the live window since the last
compaction (following preservedSegment.headUuid).
Block tokens are chars/4 estimates, images estimated from sniffed PNG or
JPEG dimensions, reasoning derived per message from output_tokens minus
visible output since transcripts strip thinking text. The exact context
size comes from the last assistant message's API usage, and the gap to
the estimate is shown as derived system prompt and tool overhead.
* feat(context): show session titles in context --list
Reads the latest ai-title entry (summary entry as fallback for older
sessions) from one tail and one head chunk of each transcript, so the
list stays fast on 100MB files.
* feat(dash): context explorer page with Claude Code and Codex session trees
Adds a Context page to the web dashboard: a navbar toggle, a provider
picker (Claude Code / Codex), the 15 most recent sessions with titles,
and per-session expandable details showing the context token tree, the
exact live context vs window, and a live/full-history scope toggle.
Server side adds /api/context/sessions and /api/context/tree with an
mtime-keyed tree cache, plus a Codex rollout builder: response items
feed the tree, compacted entries (with replacement_history) split live
window from full history, reasoning comes exact from cumulative
token_count totals, and model_context_window gives the real window.
* feat(context): interactive TUI for the context command
codeburn context with no arguments now opens an ink TUI: arrow keys to
move, enter to expand a session's token tree inline (with a spinner
while the transcript reads), tab to switch between Claude Code and
Codex, f to toggle live window vs full history, q to quit. Flag and id
forms keep the plain output for scripts, and non-TTY runs fall back to
the static list. Tree rows are shared with the static renderer via
snapshotRows.
* fix(context): review fixes across window detection, codex parsing, and surfaces
Window sizes are no longer guessed from token counts alone: opus-4-8 and
[1m] model ids map to the 1M window (a 190K opus-4-8 session used to
render as 95% of a 200K window), and Codex omits the percentage instead
of borrowing Anthropic constants when the rollout lacks
model_context_window.
Codex compaction accounting now reads the encrypted compaction item and
developer messages from replacement_history, so post-compaction windows
stop undercounting. Both builders stream with largeLineAsBuffer so one
oversized line cannot silently truncate the walk.
The tree API stops sending session.filePath, serves the flattened rows
so the dash renders the same tree as the CLI and TUI, resolves ids
directly instead of re-scanning every session file per request, and the
tree cache evicts LRU. Session discovery honors CLAUDE_CONFIG_DIRS,
CLAUDE_CONFIG_DIR, and CODEX_HOME, stats files in parallel, and id
lookups stat only matching files.
CLI gains --provider codex and machine-readable --list --json; --full
now opens the TUI in full scope instead of bypassing it. The TUI shows
build errors instead of a frozen spinner and drops the ref-plus-counter
repaint for plain state. The dashboard scopes the usage error banner and
device sidebar to the Usage page.
The dashboard Cache read/write cards and the `codeburn devices` summarizer
summed the full 365-day history.daily backfill instead of the selected period,
so shorter windows over-counted (today ~197x on real data). Every other metric
already reads the period-scoped `current` block.
buildMenubarPayload now emits period-scoped cacheReadTokens/cacheWriteTokens on
`current` (from PeriodData, alongside inputTokens/outputTokens); the web cards
(single + combined views) and summarizeOneDevice read those instead of reducing
history.daily. The trend chart still uses history.daily. Older peers that omit
the fields fall back to 0 (web) or the windowed daily sum (CLI).
Adds regression tests for both surfaces.
Adopt the more complete responsive implementation from #587 over the
initial one in #582: header controls become a hidden-scrollbar strip
instead of overflowing the page on phones, the sidebar drawer uses
visibility:hidden when closed so its links leave the tab order, aria
attributes on the toggle, 100dvh for mobile browser chrome, and 36px
touch targets.
Add mobile responsive support for the web dashboard:
- Hamburger button in header (hidden on md+) to toggle sidebar
- Sidebar becomes off-canvas drawer on mobile with slide-in transition
- Backdrop overlay with blur when sidebar is open
- Header stays sticky on mobile scroll
- Content area stacks vertically on mobile
- Main content takes full width on mobile
- Sidebar auto-closes when a device is selected
All changes use Tailwind responsive classes (md: breakpoint).
Desktop layout is completely unchanged.
Co-authored-by: yufo <yufo@MacBook-Pro.local>
The web server is long-lived, so cache what the per-invocation CLI cannot:
- Cache the parsed local payload in-memory (single-flight, 180s TTL matched to
the parser session cache, expired entries pruned on write). /api/usage and the
local half of /api/devices now return from a Map hit after the first parse.
- Prewarm today at startup and inline that payload into index.html as a
bootstrap, so the SPA paints today's numbers with no round-trip. Only the
local device is embedded; '<' is escaped so a name cannot break the script
tag; served no-store; the seeded view refetches at once so live peers appear.
- Default the web command and dashboard to today.
- Cap the peer connect phase at 3s. req.setTimeout does not abort a stalled TCP
connect, so an offline paired device hung ~75s on the OS timeout; it now
degrades to an unreachable row in ~3s. The cap clears on TCP connect, so the
TLS handshake and the 65s pairing-approval wait are unaffected.
Measured: /api/usage 0.0007s warm (was ~0.22s), /api/devices ~3s with an offline
peer (was ~75s), first paint instant.
* fix(web): reject invalid dashboard periods without exiting
* test(web): assert invalid periods return 400 without exiting; drop redundant /api/devices re-parse
- Add tests/web-dashboard.test.ts: boots the dashboard on an ephemeral port and asserts
/api/usage and /api/devices answer 400 (not process.exit) for a bad period, and that
the server keeps serving afterward. runWebDashboard now returns the http.Server so it
can be exercised in-process; callers that ignore the return value are unaffected.
- /api/devices: resolve periodInfo once instead of validating then re-parsing it inside
localGetUsage (pullDevices invokes localGetUsage with the same already-validated query).
---------
Co-authored-by: AgentSeal <hello@agentseal.org>
* feat(web): device discovery + pairing endpoints for the dashboard
Add /api/identity, /api/devices/scan (mDNS browse with confirm code and
paired status), and /api/devices/pair (approve-style pairing via
linkRemote). Backs the Search local devices button so pairing can happen
from the browser instead of the CLI.
* feat(web): two-panel dashboard with eywa-inspired warm theme
Redesign the dashboard into a left sidebar (device switcher + Search
local devices) and a right content panel, restyled to a warm-paper,
forest-green archival theme (serif display numbers, ink text, hairline
borders) in place of the dark midnight theme. The Search local devices
modal discovers nearby devices over mDNS and pairs in one click (approve
on the other device), backed by /api/devices/scan and /api/devices/pair.
* feat(web): use the CodeBurn flame logo in the dashboard header
Replace the placeholder triangle with the flame mark (the repo's
codeburn-symbolic vector), tinted the theme's forest green, and set the
same flame as the favicon.
* feat(web): color the All-devices chart by device, add task-level combined views
In the All devices view the daily chart now stacks by device (one color
per device) instead of by model, and the combined panels add a By task
breakdown plus in/out token detail. Devices that cannot be reached are
hidden entirely rather than shown as error rows.
* feat(web): use the flame image as the dashboard logo and favicon
Replace the inline vector flame with the brand flame PNG
(public/codeburn-flame.png) in the header and as the favicon.
* fix(web): trim flame logo padding and tighten brand spacing
Crop the transparent border off the flame PNG (it was 184px of padding
each side) and reduce the header gap so the mark sits close to the
wordmark.
* feat(web): cost/tokens toggle, cache read/write, full feature panels
Add a Cost/Tokens unit toggle that switches the hero number and the chart
between dollars and tokens. Surface cache write/read token totals as
metric cards. Add per-device panels for subagents, skills, MCP servers,
and a savings / retry-tax / routing-waste summary. The combined view
gains cache totals and the unit toggle too.
* feat(web): use CodeBurn website logos and add community links
Use the website's navbar flame (logo.png) with the Code+Burn wordmark in
the header, and the three-flame app icon (icon.png) as the favicon.
Add Website, Discord, and X links to the sidebar footer.
* fix(web): use the single-flame logo for the favicon too
The three-flame icon was wrong for the tab; use the same single flame as
the navbar everywhere and drop the unused three-flame asset.
* chore(web): set dashboard title to CodeBurn - Local Dashboard
* harden sharing + dashboard for public launch
Security:
- pairing: cap PIN attempts (close window after 5 wrong guesses) so a
6-digit PIN cannot be brute-forced within the TTL on a 0.0.0.0 listener.
- web dashboard: reject non-loopback Host (defeats DNS rebinding that
could read unsanitized local usage) and cross-origin requests (CSRF);
require application/json on the pair endpoint.
- store tokens with 0600 perms (was world-readable), 0700 dir.
Robustness:
- client: per-request timeout so a hung/asleep peer cannot hang a pull;
pullDevices fetches remotes concurrently and isolates failures.
- dashboard: normalize peer payloads at the boundary and add an error
boundary so a peer on a different version cannot white-screen the SPA;
finite-guard fmtNum/compactUsd.
Tests: PIN attempt-cap test added (1269 pass).
* feat(web): share this device from the dashboard, with browser approval
Add a Share this device toggle to the dashboard sidebar. It runs the
secure share server in-process (mTLS + mDNS advertise) so no terminal is
needed, with a Keep sharing always option (persisted; otherwise it stops
after idle). Incoming approve-style pairings are queued and surfaced in
the browser as an Approve/Deny prompt with the matching code, instead of
a terminal prompt. The shared payload is sanitized; start degrades
gracefully if the port is already held by a CLI share.
* fix(sharing): keep a paired device from dropping on a transient pull
- client: 8s -> 15s timeout and a fresh socket per request (agent:false) so
the pinned-fingerprint check always reads this connection's cert.
- share server: wrap request handling so a getUsage error returns a fast
500 instead of hanging the caller (which times out and drops the device).
- dashboard: re-pull paired devices every 20s so a brief drop self-heals
instead of staying gone until you change tabs.
* fix(sharing): re-sanitize remote payloads on receipt
A peer might run an older build that does not strip its own project
names/sessions. Sanitize every remote payload when we receive it too, so
project names never cross into our dashboard regardless of the sender's
version. Aggregate numbers (cost, tokens, models, tools, daily) are kept.
* harden dashboard sharing: version-skew, lifecycle, server errors
- normalize remote daily-history entries so a peer on an older build (daily
rows missing topModels) can no longer crash the chart / brick the page.
- ShareController: commit always/sharing state only after listen() binds, so
a port-in-use start no longer reports sharing when it is not.
- attach durable error/tlsClientError handlers to both HTTPS servers so a
malformed peer connection cannot crash the process.
- reset view/provider selection when the viewed device disappears or lacks
the selected provider (no more empty/no-selection state on sleep-wake).
- by-device chart: stable per-device color/key so bars do not reshuffle when
a device drops or returns between polls.
* polish: device identity, approval guard, queue cap, formatting
- give each device a stable unique id (cert fingerprint for remotes,
'local' for this device); key the sidebar, selection, and by-device
chart by id so two devices sharing a hostname no longer collide.
- approval prompt: drop a request from the UI the moment it is answered
so it cannot be double-clicked.
- share controller: cap concurrent pending approvals and allow only one
per device, so a LAN peer cannot flood the prompt.
- usd(): render negatives as -$5.00, consistent with compactUsd.
Add /api/devices to the web server: it returns this machine plus every
paired device (via pullDevices), each kept separate. The dashboard gains
a device switcher with an All devices combined view (totals, a per-device
breakdown table, and a merged chart) plus a per-device tab. Remote
payloads arrive sanitized, so their project and session detail is shown
as private rather than listed. With no devices paired, it renders exactly
as before.
Add a Vite + React 19 + Tailwind v4 + Recharts SPA (dash/) and a 'web'
command that serves the built UI plus a local /api/usage endpoint backed
by the existing menubar aggregation. Midnight theme with CodeBurn's
orange chart ramp: hero cost, a stacked-by-model daily bar chart with a
custom tooltip and hover-dim, metric cards, by-tool and by-activity bar
lists, and top-projects and tools tables. Period and provider filters,
react-query with skeleton loading. Stays 100% local.
build:dash builds the SPA into dist/dash (shipped via package files,
served at runtime); a missing build falls back to a helpful message.