@clemrig/flow-ui
A local, read-only dashboard of the runs of @clemrig/flow workflows.
Use
Requires macOS or Linux and Node.js 24 or later.
npm install -g @clemrig/flow-ui
flow-ui [RUNS_DIR] [--port PORT]
flow-ui serves the runs kept in RUNS_DIR (default: .flow/runs, where @clemrig/flow keeps them; a RUNS_DIR given must be an existing directory) on 127.0.0.1, on PORT or a port the system chooses, prints its URL and stops on Ctrl-C. The page shows the runs in a rail you can fold, grouped by day, named by their input, filtered by status or workflow and searched (/). A run shows its name, status, links its result gives, input and figures (start, end, active time, cost against the initial budget with each budget allotted and where it comes from on hover, then launches, rounds and stage executions), above three tabs. Timeline: its launches (the first one, then each resumption) on one bar, each sized by its active time with its day and start time, the pause between two written out, and why each failed one stopped, a launch opening what it ran with (versions, prompts, runtimes, budget and its origin, machine, directory, the runtimes' variables set, values given), what changed since the launch before marked; its stage executions over its active time, one after the other as the run executes them, the shortest wider than their time, a lane per agent stage then one for the code stages, each gate a badge with its verdict on the end of the execution it judges, with the resumptions, the rounds its corrections started and each correction taken, zoomed on demand (Fit, 2×, 4×, or a pinch or Ctrl/⌘ with the wheel on the lanes); its correction counters; below, the selected execution's panel with a tab per question, a code stage having only Summary, Output and Activity: Summary (its outcome, why it failed or was sent back, the correction it received, its warnings; for an agent stage, what its main agent read from its first step to its latest, in tokens, its steps (the main agent's responses), its tool calls, sub-agents' included, its cost, partial when a call's own cost is unknown (a call's own cost: what it announced, or for Claude Code, whose figures are a session's total, that total less the total as the session's previous call in the run ended); its agent: runtime, model and effort, permissions and where they come from, the tools the profile lists and how many the session loaded, the conversation, the timeout; each call when it made several; and the tools used), Configuration (what each of its sessions ran with, as the session's first call in the stage announced it: what the profile sets beside what the runtime announced, and the differences it reported; for Claude Code, the built-in tools, MCP tools by server, sub-agents, skills, commands (the operator's) and plugins, each with what the profile sets and what was loaded, opening on each item marked by what the profile allows of it: allowed, refused (not in its tools under its permission mode, unless the machine's rules, which are not recorded, allow it), undecided (it lists no tools, or its mode decides), in its tools but not loaded, or, for sub-agents and skills, without the Agent or Skill tool they need; for Pi, its tools, skills and extensions; Codex says it does not announce its configuration; then the system prompt as the call sent it: the runtime's own and the workflow's, sized at a conversation's first call only, the parts estimated and marked ≈), Input (what each call sent, one at a time: its message part by part, what the request named instead of sending because the conversation already had it, then the request and the command exactly), Output (what the stage returned, the correction it sent back or could not send, each call's final text when the stage returned nothing, and what a call that succeeded wrote on its error output) and Activity (its calls, tool calls, journal, the hooks that ran, a failed one marked, what the runtime reported, such as refused tools, limits, retries and compactions, and warnings, in one timeline you can filter). Cost: its cost by session and over its active time with each launch's budget when known. Result: its result, its data as a tree you unfold, and its runtimes. The address keeps the run and its tab and, on the timeline, the execution chosen and the execution's tab (#/<id>/stages/<n>/<tab>, #/<id>/cost, #/<id>/result); while another tab is open, the timeline keeps the execution chosen, the launch opened, the activity's filter and the lanes' zoom and scroll, and a run chosen in the rail opens on the same tab. On the timeline, j, k and f move between executions, and 1 to 5 open the execution's tabs (three for a code stage). It follows a running run as it goes, and follows the selected run again when it is resumed. A result shows the run's validated data as JSON.
To watch a run on another machine, forward the same port through SSH: ssh -L 4321:127.0.0.1:4321 machine, run flow-ui --port 4321 there, and open http://localhost:4321/.
While it runs, any local process that can reach 127.0.0.1 can read the runs it serves.
From code
import { startDashboard } from "@clemrig/flow-ui";
const dashboard = await startDashboard({
runsDir: ".flow/runs",
title: "flow",
label: "my-project",
presentResult: (state) => [JSON.stringify(state.data)], // optional
});
console.log(dashboard.url);
// later: await dashboard.close();
With presentResult, a run's result is its lines, and the page shows the run's validated data folded below them.
dashboardHandler(options) gives the same dashboard as a request handler, for a server that serves it beside other pages: it answers (request, response, url), where url is the request's address relative to where the dashboard is mounted (/api/runs for /runs/api/runs), and refuses, like startDashboard, every method but GET and any Host other than its own address. The page's own addresses are relative, so it works under any path that ends with /.
License
MIT