A full-screen dashboard
The reference dashboard: tabs, a checklist with progress, a log tail and a status line that collapses, in the alternate screen — and clean lines in a pipe and NDJSON under --json, from the same program.
examples/dashboard is a
boilerplate in the shape of a setup wizard: two tabs, a checklist with progress, a log tail and a
status line that collapses, in the alternate screen. Copy it to start your own:
npx degit ofri-peretz/burgee/examples/dashboard my-dashboard
cd my-dashboard && npm install && node dashboard.mjs| Key | What it does |
|---|---|
← → | switch tab |
s | collapse or expand the status line |
q, Ctrl+C | quit, and hand the terminal back as it was |
How it is built
One layout per tab. layout is a function of the screen's state, so the status tab shows
the learn panel beside the checklist and the logs tab shows the tail:
function layout(state) {
const body =
state.active === 0
? { direction: 'row', parts: [{ size: { fr: 1, min: 20 }, content: 'learn' }, { size: { fr: 2 }, content: 'tasks' }] }
: 'log';
return {
direction: 'column',
parts: [
{ size: 1, content: 'tabs' },
{ size: 'fit', content: 'status' },
{ content: body },
{ size: 1, content: 'hints' },
],
};
}Keys are data. The keymap binds the arrows to the reducer's tab.prev and tab.next, s to
toggle:status, which collapses the status pane, and q to the program's own quit. The hint
line at the bottom is hints(KEYMAP, LABELS), so it lists exactly those keys:
const KEYMAP = { left: 'tab.prev', right: 'tab.next', s: 'toggle:status', q: 'quit', 'ctrl+c': 'quit' };
const LABELS = { 'tab.prev': 'switch tab', 'tab.next': 'switch tab', 'toggle:status': 'toggle status', quit: 'quit' };Panes are flagstaff components. tabBar(), tasks() and logTail() are flagstaff's; the
status and learn panes are a one-line text component. The tab bar and the hint line are
liveOnly, because tabs and keys mean nothing off a terminal.
The work drives the screen. Each step marks itself running, pushes its log lines, and updates three panes; the last one commits a summary and closes. In the alternate screen the commit is held and printed to the main screen as the program exits, so the summary is what is left in the terminal.
Every caller
On a terminal it is a full screen. Piped, each pane prints under its label, and then each change is one line:
Status
Progress: 0/4 completed
Learn
The wizard installs the SDK
and checks the first event.
Tasks
Log
◆ Detect the framework
┊ found package.json
┊ framework: next
✔ Detect the framework
◆ Install the SDK
┊ npm install posthog-js
┊ added 1 package
Progress: 1/4 completed
✔ Install the SDK
◆ Write the config
┊ wrote posthog.config.js
Progress: 2/4 completed
✔ Write the config
◆ Check the setup
┊ sent a test event
┊ event received
Progress: 3/4 completed
✔ Check the setup
Progress: 4/4 completed
Done: Progress: 4/4 completednode dashboard.mjs --json writes one NDJSON event per update to stderr, {"event":"tasks", "state":{…}} and so on, ending with the commit and each pane's last state. The example's own
npm test checks the pipe, --json, and the keys on a simulated terminal: → to the log tab,
s to collapse the status line, q to leave the alternate screen.
What to change first
STEPS, the work: atitleand theloglines each step produces. In your program, run the real work whereadvance()marks a step running, and push its output intolines.layout(), the arrangement per tab (Layout).KEYMAPandLABELS, the keys (Tabs, focus and keys).
Coming from ink
Move an ink program to controlroom/ink by changing its import and installing react-reconciler; graded by ink 8.0.0's own suite at 1304 of 1304. Packages written for ink run unchanged with one package.json line.
A chat CLI
A chat CLI in the shape of Claude Code on the native API: an inline screen whose transcript flows into scrollback, a streamed markdown reply, slash commands, a permission prompt, Esc to interrupt — and one prompt per stdin line in a pipe.