junco
contents

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:

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

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.

The derived keys, per context (every key works wherever its context is active, whether or not its chip fits):

A held key auto-repeats, and every repeat counts: a burst the terminal delivers at once is replayed press by press.

note — every GitHub-side action is an ordinary label mutation made through your own 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.

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.