The dashboard
The dashboard runs fullscreen in the terminal's alternate buffer, adapts its layout to the window (a third pane appears at 110 columns and up), and restores the terminal exactly on exit. It needs a real TTY — not piped, not backgrounded. Flags: CLI reference.
junco dashboard
One view
The left rail is the navigation spine: every repo junco knows about — watched GitHub repos and local checkouts found on disk — with a pinned system group below it (Queue, Outbox, Worktrees, Daemon, Logs). The body follows the rail cursor:
- a watched GitHub repo shows its trigger-labeled issues and, on wide terminals, a PR monitor pane — an interactive view over the GitHub loop;
- a local-only or unwatched repo shows a repo detail panel: path, origin, branch@sha, dirty flag, its ticket worktrees, its recent queue activity;
- a system row shows that section's body — the machine-local runtime underneath it all.
With github.enabled = false there is no GitHub side: every repo renders the detail panel and nothing polls GitHub. The header's left side is a breadcrumb (acme/reef ▸ #46, system ▸ queue, transcript ▸ <ticket-id>); its right side is a live pulse of chips — warnings first, then the 24h record, what is running now, and daemon / queue / outbox — each hidden when it has nothing to say. Below 110 columns only the any-width chips stay; the full record always lives in junco status.
The panes
- Rail — every repo with a per-state issue-count badge and a
(cfg)marker on entries that came fromconfig.json; the system rows carry live badges (running count, outbox depth, stale worktrees, daemon up/down).enteropens the repo detail panel;uaudits the selected repo (parked for review, never filed on its own);vopens the review view — a per-finding checklist you confirm to file, and comment drafts you preview and post or discard. - Issues pane — the selected repo's trigger-labeled issues, each with a lifecycle glyph and its state badge (planning / plan-ready / approved / queued / working / done / failed / denied).
enteropens the full issue plus its posted plan as a scrollable overlay;ndrafts an investigation comment on it, parked, not posted;topens the transcript of the ticket junco built for it, live while it runs. - PR monitor — wide terminals only: the selected repo's junco-authored pull requests, attention-first, with checks (
✗fail ✓pass ◍pending), the review badge, and age.enteropens a fullscreen PR overlay;bopens the PR in the browser.
p opens the PRs view: one flat, cross-repo list of junco-authored PRs sorted attention-first — checks-failing and changes-requested at the top, then pending, approved, drafts, merged. On wide terminals a side card tracks the selection with the checks breakdown, review decision, mergeable status, branch ← base, the originating ticket slug, and ± diff stats — all from data already in the row, no extra fetch.
The shortcut bar
The bottom two rows. The actions row names what is under the cursor — a repo, issue #46, PR #12, a system section — and lists what you can do to it: the filled chat chip first wherever a repo is in context, then the row's own verbs, then │ and the places you can go (queue, review, PRs). The underlined letter in a chip is its key; an UPPERCASE letter means shift, reserved for the guarded verbs (Unwatch, Delete, Prune, Restart, Discard), which keep their confirm modals. A system row lists only its section's verbs, an empty or filtered-to-nothing issue list drops the per-issue verbs, and a repo that is not watched never offers Unwatch. A toast paints over this row for four seconds or until your next key.
The navigate row is the same in every view — the structural keys as small keycaps, ? help and q pinned right — and only its labels change to say what ⏎, → or ← do here. It fits any width: it drops ,, then :, then g G, then [ ] (they still work; the help modal lists them), then the shared label slot, then / filter, so help and quit stay visible down to the 60-column minimum.
↑/↓(j/k) — move;←/→(h/l,tab) — switch panes;g/G— first / lastenter— open the selection: repo detail on a rail repo row, issue detail, the PR overlay, the log overlay on the logs row, a queue row's transcript/— filter issues (enterapplies,escclears);[/]— scroll overlays, panels, command output, transcripts,— config editor;:— command palette;esc— back / close;?— the full per-view key list
The derived keys, per context (every key works wherever its context is active, whether or not its chip fits):
- everywhere in the main view —
cchat ·aadd repo ·Uunwatch ·bbrowser ·rrefresh ·uaudit ·equeue ·vreview ·pPRs ·qquit - issues body —
mimport (Iimport as ask: read-only Q&A, no plan, no PR) ·oapprove the posted plan ·ninvestigate ·Rre-plan / re-cycle ·Aaudit with--auto-plan·ttranscript - system rows — queue:
entertranscript,tretry a failed ticket,DDelete a queued one; outbox:fflush; worktrees:PPrune a stale worktree; daemon:RRestart (in-flight tickets soft-abort, committed work salvaged),fflush - overlays — issue detail:
bbrowser ·cchat ·ttranscript; transcript:tthinking ·ffollow ·cchat; review:aall ·nnone ·ffile/post ·DDiscard ·ssubmit ·eedit ·rroute; logs:ffollow ·llevel ·tticket ·/search ·Gbottom;qcloses any of them
A held key auto-repeats, and every repeat counts: a burst the terminal delivers at once is replayed press by press.
gh auth. Import, approve, and re-plan run nothing themselves; they move labels the daemon's sweep acts on — see the GitHub loop.Chat with the agent
c on any repo opens an ongoing, read-only conversation with the coding agent about that repo — file-backed and daemon-owned, so it survives a dashboard quit or a daemon restart. From an issue or PR the composer is prefilled with /issue N or /pr N; enter pulls the thread in, nothing is sent until you send it. Every dispatch shape is reachable from the chat — a plain ticket, a ticket set, a plan set, an amend on an open PR, an apply-ticket patch, an audit or an investigation — and each one parks as a draft card under the answer: s submit, e edit, r cycle the route (inbox or a parked issue), D discard; /submit [id] in the composer submits a parked draft directly.
Ask it to submit and the agent calls its one action tool, junco_submit: a card appears — ▸ submit add-readme → inbox — awaiting you · y submit · n keep parked — the header reads ◐ awaiting your confirmation, and the agent's turn waits, its own clock paused, until you answer. y runs the same junco submit the card's s would and the card shows ▸ submitting… then ✓ submitted; n keeps the draft parked and tells the agent so; no answer within chat.confirmTimeoutMinutes (10) expires the proposal. The agent's file tools stay read-only, it cannot run a shell, and it never moves your view.
The keys read as a chat: ↑/↓ scroll a row, PgUp/PgDn a page (both still work while composing), tab/shift+tab step between draft cards, ⏎ expands one, G/End resumes following the tail, g/Home jumps to the top; i focuses the composer, esc blurs it (the text stays), esc again leaves — back to the pane you came from. Your messages read you:, the agent's junco:. The header shows today's chat spend. The same transcript prints outside the TUI with junco transcript --chat owner/repo.
Mouse support
Clicking a row focuses its pane and selects it; clicking the already-selected row opens it, exactly like enter. Blank pane space focuses the pane without moving the selection; the wheel acts on whatever is under the cursor; the shortcut-bar chips are clickable. On the chat and ticket transcripts the right-edge scrollbar is live — press a row of its track to jump there, hold and drag to keep scrolling. Detail views carry an ↗ owner/repo#n line (click it, or cmd+click in OSC 8 terminals, or b) to open the item on GitHub. A guarded action opens a confirm modal with two clickable buttons — [ esc cancel ] and a solid y confirm pill — and nothing else reaches the dashboard while it is open. Hold shift while dragging to select terminal text.
The command palette
: opens the palette: type to filter, enter to run. Commands that take arguments — list, retry, outbox, submit, logs, service, transcript — get an args field first. Each run spawns the actual junco CLI as a subprocess against the dashboard's own config — no reimplementation, no drift, no shell in the middle. The command's real output appears in a scrollable pane with its exit code; r re-runs, esc returns. logs runs bounded (-n 200); init, start, and dashboard are greyed out with the reason they can't run here (use restart for the daemon). Every mutating action on a system row — requeue, delete, flush, restart, prune — uses the same subprocess model.
The system rows
Selecting a system row swaps the body to that section; →/l/enter moves focus into it, ←/h/esc returns to the rail.
- Queue — running, waiting, and recent tickets: a time-budget gauge on each running row (
▰▰▰▱▱ 23m / 45m budget), a no-activity warning once a running ticket has gone five minutes without progress, waiting rows in claim order with their retry backoff, and a read-only STATS roll-up of the task-history ledger — 24h and 7-day records, spend and tokens, guard nudges and kills. - Outbox — the offline GitHub backlog, live and dead ops;
fflushes it. - Worktrees — every ticket worktree: repo, slug, live/stale/backup, HEAD, age.
Pprunes a stale or backup one; a live worktree is never prunable — the daemon owns it. - Daemon — a scrollable stat grid: state and uptime, the GitHub refresh stamp, endpoint reachability (or the provider gate's badge and reason), guard counts, token totals, today's spend against the daily budget, and live per-ticket turn progress.
- Logs — a live tail of
worker.log, working whether the daemon is up or down.enterexpands it to a full-screen overlay with scrollback:ftoggles follow,lcycles the level threshold,tfocuses one ticket at a time,/searches.
Transcripts. enter on a running or recent queue row — or t on an issue — opens the ticket's recorded event transcript: one header per run (model, duration, tokens, how it stopped), then each turn's text and tool calls (▸ read game.js → 214 lines). j/k move over the tool rows and enter expands a call's full result; t shows the model's thinking; a running ticket's transcript follows the tail as the agent works and flips to the outcome when the run ends. Rows the daemon itself owns are never actionable: a running queue row opens its transcript and nothing else, and a live worktree turns a prune into a safe toast. junco transcript <ticket-id> renders the same view from the CLI.
The first-run walkthrough
With no config yet, bare junco or junco dashboard opens a guided setup in chapters: workspace, model setup (an inference endpoint with live discovery, or a hosted provider from the built-in catalog), repo containment, the GitHub bridge, an Account chapter for the dedicated bot identity, and the recommended extras. It then creates the queue, verifies the result with a flight check, and lands in the dashboard. Re-run it anytime from the palette ("setup") — it only writes what changes. The headless equivalent is junco config init; the full key reference is Configuration.
Watching repos
a prompts for owner/repo or a github.com URL; an empty clone path clones into <dataDir>/clones/watched/<owner>/<repo>. It validates the clone's origin, confirms reachability via gh, and creates the trigger label if missing. Entries live in <dataDir>/watchlist.json, separate from github.repos in config.json; the daemon's bridge sweep re-reads the file every sweep, so watchlist changes apply without a restart. Where a repo appears in both, the config entry wins — config-sourced repos carry a (cfg) marker, are read-only in the dashboard, and U reports where they're defined instead of removing them.