Live terminal viewer (ratatui) for Claude Code Workflow runs: the current workflow, its phases, and every agent in a tiled layout — each tile rendering the agent's chat transcript in the familiar Claude Code style (⏺ tool calls, ⎿ results, ✻ Thinking…, > prompts).
✳ review-changes wf_demo1234 RUNNING ⠇ ⏱ 29m18s ⚒ 3 tools ⬡ 62.4k out
⠇ Review ── ⠇ Verify ── ○ Report
╭ workflow ──────────────────────╮╭ ⠇ review:perf · opus · 29m08s · 27.6k tok ───────────────────────────────╮
│Review the working diff, then ││ ⎿ change: +38.4% (p = 0.00) │
│verify every finding. ││ │
│ ││ ✻ Thinking… │
│▸ Review one per dimension ││ The miss path now hashes twice — once for the shard, once for the │
│ ✓ review:bugs opus 3m50s ││ entry. │
│ ❯ ⠇ review:perf opus 29m08s │╰──────────────────────────────────────────────────────────────────────────╯
│▸ Verify refute each finding │╭ ⠇ verify:cache-key · sonnet · 12m08s · 7.3k tok ─────────────────────────╮
│ ⠇ verify:cache-ke… sonnet 12m││ + let key = format!("{}:{}:{}", tenant, scope, name); │
│○ Report ││ ⎿ Applied 1 edit to src/cache.rs │
│ ││ │
│ ││ ⏺ Confirmed: the collision reproduces without the tenant prefix. │
╰────────────────────────────────╯╰──────────────────────────────────────────────────────────────────────────╯
tab agent ↑↓ scroll g/G top/follow enter zoom w runs q quit
nix run github:toowoxx/workflow-viewer # without cloning
nix run . # most recently active workflow run
nix run . -- --help
nix run . -- <wf-transcript-dir> # a specific run
nix run . -- --claude-dir /path/to/.claude # non-default Claude homeOr with cargo: cargo run --release. Nothing but a terminal is required — the viewer
only reads files, it never talks to Claude Code or the network.
Everything is derived read-only from Claude Code's on-disk session data:
| Source | Contents |
|---|---|
~/.claude/projects/<proj>/<session>/subagents/workflows/wf_*/journal.jsonl |
agent started/result events |
…/wf_*/agent-<id>.jsonl |
full per-agent transcript (chat history) |
…/wf_*/agent-<id>.meta.json |
agent type |
<session>/workflows/wf_*.json |
final state of completed runs (phases, labels, models) |
| the workflow script | meta.phases + agent({label, phase, model}) for runs still in flight |
For a running workflow there is no state file yet, so the viewer resolves the script (from workflows/scripts/, from the Script file: path recorded in the parent session transcript, or — if that file is gone — from the Write tool call that created it) and matches each live transcript back to its agent() call by the distinctive static text of the call's prompt template. Completed runs use the authoritative wf_*.json.
Tool calls render richly: Bash shows the full command wrapped over several lines, and Edit/Write show a real line diff (computed from the tool input) with red/green backgrounds and collapsed unchanged context — more of everything in zoom mode.
Live, not polled: updates are driven by inotify (notify crate) on the run directory; appended JSONL lines are read incrementally from remembered byte offsets. The only timer is a 250 ms UI tick for spinners/elapsed times.
| Key | Action |
|---|---|
tab / shift-tab / ← → |
select agent |
↑ ↓ / j k, pgup pgdn |
scroll the selected pane (scrollback) |
g / G or End |
jump to top / re-follow the tail |
enter / esc |
zoom the selected agent to full width / back |
w |
run switcher: projects first, then that project's runs (enter descends/opens, esc goes back) |
r |
force refresh |
q / ctrl-c |
quit |
Auto-advance follows the work at both levels, always after a 3 s grace period so the thing that just ended stays readable.
When the viewed run finishes, the viewer hops to the newest still-live run in the same project. If nothing is live yet it stays put and jumps the moment a new run appears. Runs with no state file that haven't been written to for 5 minutes count as abandoned, not live, so a killed workflow never traps the viewer. Two cases opt out: a run named on the command line that is already finished, and a finished run you picked by hand in the switcher — both are history you asked to read.
The selection auto-follows the longest-running agent — the one that started earliest and is still working. When it finishes, the selection moves to the next longest-running agent after the same 3 s grace period; if nothing else is running it waits and jumps as soon as an agent starts. tab (pin) and scrollback suspend this; esc / G resume it without waiting. Tiles show only the agents that are currently working (up to 6, newest first) — finished agents stay reachable through the sidebar selection (tab), which pulls them back into view; when nothing is running the newest agent is shown.
MIT — see LICENSE.
An independent project, not affiliated with or endorsed by Anthropic. "Claude" and "Claude Code" are trademarks of Anthropic, PBC.