junco
contents

CLI reference

Every command the installed junco answers to, generated from its own help surface at the version stamped below and annotated by hand. Synopsis conventions: <required>, [optional], a|b alternatives. Global flags work on every command; per-command flags are covered with their command.

global flags

--once
(start) Process one task then exit
--platform <name>
(service) Target platform: launchd | systemd default launchd on macOS, systemd elsewhere
--plan
(unwatch) Print what would be deleted as JSON; delete nothing
--help, -h
Show this help message
--version
Print junco's version and exit

junco

junco

The bare command is the daily driver: on an interactive terminal it makes sure the supervised daemon is running, then opens the dashboard. With no config yet, it opens the guided setup walkthrough instead. It never starts a daemon on a non-interactive terminal — services and scripts use junco start.

junco

see also: start · dashboard · config init

junco analyze

junco analyze <owner/repo#N|url>

Resolves one GitHub issue — owner/repo#N or a full issue URL; there is no local-path form — and queues a read-only investigation ticket: root cause, file:line evidence, reproduction steps, a suggested fix direction. The daemon parks the result as a comment draft, sanitized before it lands (HTML comments and control characters stripped, capped at 60,000 characters); nothing posts until the separate junco analyze post confirm. Works on owned and unowned repos alike — an unwatched repo is auto-forked, cloned into a managed directory, and added to the watchlist. One pending draft per issue: re-analyzing overwrites the old draft, and queuing while the same issue's ticket still waits in the inbox fails loud.

junco analyze acme/reef-api#52
junco analyze https://github.com/upstream/reef-api/issues/52

see also: analyze review · analyze post · analyze guide

junco analyze edit

junco analyze edit <id>

Opens the pending draft in $VISUAL (falling back to $EDITOR) as a temporary Markdown file, git-commit-message style. On save the text is re-sanitized — the same HTML-comment and control-character stripping and length cap the original draft got — and written back to the store. The disclosure footer is never part of the file; it's composed at post/preview time, not stored in the draft text. An editor that exits nonzero, or a draft that's empty after sanitizing, leaves the draft unchanged. With no editor configured, the command prints the draft's JSON file path instead of guessing.

junco analyze edit analyze-acme-reef-api-52
draft updated — junco analyze review analyze-acme-reef-api-52 to preview

see also: analyze review · analyze post · analyze guide

junco analyze post

junco analyze post <id> [--no-footer]

The human confirm gate, and the only outward write in the analyze flow: composes the final comment — draft text plus the disclosure footer unless the draft has it off or --no-footer is passed — posts it under your own gh auth via gh issue comment (or queues it to the durable outbox when GitHub is unreachable), and archives the draft to posted/ on either outcome. One draft, one deliberate action — there is no separate --yes flag. A non-network failure (locked issue, permission error) leaves the draft pending, so the same id can be retried after the fix.

junco analyze post analyze-acme-reef-api-52
junco analyze post analyze-acme-reef-api-52 --no-footer
note — the default footer reads Analysis drafted with junco and human-reviewed before posting. It's a flag on the parked draft, not baked-in text — the preview shows it exactly as it would post, and editing never has to string-match it out.

see also: analyze review · outbox · analyze guide

junco analyze review

junco analyze review [<id>]

With no id: one line per pending draft — id, nwo#issue, (owned)/(external), the parked timestamp, and the draft's first non-empty line truncated at 60 characters. With an id: the draft's nwo#issue, scope, and issue title, then exactly what junco analyze post would post — draft text plus the disclosure footer when the draft carries one — followed by a post: hint. Read-only; nothing posts from here. The same review is available in the dashboard under v: enter previews, f/enter posts, x discards.

junco analyze review
junco analyze review analyze-acme-reef-api-52

see also: analyze edit · analyze post · analyze guide

junco assess

junco assess <path|owner/repo|owner/repo#N> [--auto-plan]

Queues a read-only audit ticket: npm audit over the dependency tree plus a read-only agent pass over the code, with findings validated, severity-filtered, deduped against your own most recent 500 issues on the repo, and parked for review — nothing is filed until you confirm with junco assess file. The target is a local path, an already-watched owner/repo (an unwatched one errors out rather than guessing a clone path), or owner/repo#N / an issue URL, which scopes the audit to the code that issue implicates and auto-provisions an unwatched repo. --auto-plan stamps the configured trigger label on every issue the batch later files — effective only on owned, bridge-watched repos; an external batch forces it off.

junco assess acme/reef-api --auto-plan
junco assess acme/reef-api#52
note — a batch that's never reviewed doesn't expire, and re-running assess on the same ticket id overwrites the batch instead of piling up duplicates.

see also: assess review · assess file · assess discard · assess guide

junco assess discard

junco assess discard <id>

Archives a pending review batch without filing any of its findings. Filing (junco assess file) no longer auto-archives the batch it draws from — a batch you reviewed and decided not to act on needs this explicit step, or it keeps showing up in junco assess review. The same action is bound to x in the dashboard's review view.

junco assess discard 20260718-093042

see also: assess · assess review · assess file · assess guide

junco assess file

junco assess file <id> --all | --only <fp,...>

The confirmation half of an audit: files reviewed findings from a parked batch as GitHub issues. There is no bare default — you choose --all or an explicit --only list of fingerprints from junco assess review <id>. Filed issues arrive fingerprinted, severity-labeled, and deduped against your recent findings. Filing stamps per-finding accounting (created/queued/deduped, plus a timestamp and URL) but no longer auto-archives the batch — it stays in junco assess review, filed findings and all, until you run junco assess discard on it.

junco assess file 20260718-093042 --all
junco assess file 20260718-093042 --only 9f2c01ab54ce77d1,4a11beef00c2d9e3
warn — closing a filed finding issue — even as wontfix — suppresses its fingerprint from every future audit, including a genuine regression that hashes the same. To make a finding filable again, delete the issue or edit out its <!-- junco:finding --> marker line.

see also: assess · assess review · assess discard · assess guide

junco assess review

junco assess review [<id>]

Read-only inspection of the parked-findings store. With no id: one line per pending batch — id, owner/repo, (owned)/(external), finding count, and the audit's timestamp. With an id: the batch's repo and scope, then each finding's fingerprint, severity, and title, closing with two ready-to-run junco assess file hints (--all, and a --only example built from the first two fingerprints). Nothing is written; filing is the separate junco assess file confirm step. The same checklist review is available in the dashboard under v.

junco assess review
junco assess review 20260718-093042
note — a finding you don't select isn't suppressed — no issue and no marker exist yet, so the next audit re-parks it. Only a filed issue's marker suppresses a fingerprint.

see also: assess · assess file · assess discard · assess guide

junco auth grant

junco auth grant <owner/repo>

Grants the bot push access to a repo by driving both identities junco holds: invites the bot as a collaborator with permission=push under your own ambient login (you need admin on the repo — anything less fails with a message saying so), switches to the bot's isolated GH_CONFIG_DIR to accept the matching pending invitation (bounded retry, since invitation propagation can lag), then verifies under the bot's identity that it now has push before reporting success. Idempotent — re-running on an already-granted repo skips straight to verification. The dashboard runs the same grant in the background after you watch a repo with the bot enabled; a failure there shows a toast naming this command as the fix.

junco auth grant acme/reef-api
✓ junco-agent has write on acme/reef-api
note — in a SAML-enforced org the grant fails with a SAML error regardless of identity. That's a one-time authorization, not a permission problem: sign in as the bot on github.com and authorize its gh OAuth token for the org — junco recognizes the error and prints this guidance.

see also: auth login · doctor · bot account guide

junco auth login

junco auth login

Logs the daemon's dedicated bot account in: runs gh auth login's own device-flow into an isolated GH_CONFIG_DIR (default ~/.junco/gh, fully separate from your personal gh config; a legacy login at ~/.config/junco/gh keeps working until junco data migrate moves it), verifies the resulting identity, and flips botAccount.enabled to true in config.json on success. It prints who junco now acts as and reminds you to run junco restart to apply it. gh owns token refresh — nothing secret lands in config.json or the daemon's environment; child processes only ever receive the config-dir path.

junco auth login
warn — fails closed: with botAccount.enabled set but the bot's login missing or expired, junco start and junco run-once refuse to run at all — there is no silent fallback to your personal identity. Re-run junco auth login.

see also: auth grant · restart · bot account guide

junco config get

junco config get <path>

Reads one lever by dotted path from the resolved config.json and prints its value. Read-only; nothing on disk changes.

junco config get worker.maxConcurrent

see also: config set · config list · configuration reference

junco config init

junco config init

Headless default-config scaffold for scripting and CI: writes ~/.junco/config.json with safe defaults and creates the queue directories, no prompts. With a config already present it only ensures the directories exist — it never overwrites an existing file. A fresh scaffold writes no legacy path keys, and writes dataDir only when it differs from the default. The interactive equivalent is the setup walkthrough in the dashboard.

junco config init

see also: config path · doctor · configuration reference

junco config list

junco config list

Prints the full annotated reference: every config lever with its default, type, and one-line explanation. This is the always-current source of truth for the lever set. Levers marked editable: false (e.g. tools) are structural and require a direct edit of config.json rather than config set. Read-only.

junco config list

see also: config get · config set · configuration reference

junco config path

junco config path

Prints the resolved config file location and exits: ~/.junco/config.json, or the legacy ~/.config/junco/config.json (respects XDG_CONFIG_HOME) while it exists and no canonical file does. Resolution is environment-only — the working directory never matters, and the deprecated --config flag is ignored. Reads nothing else, writes nothing — the target for direct edits of structural fields.

junco config path

see also: config list · config init · configuration reference

junco config set

junco config set <path> <value>

Writes one lever by dotted path through a validated read/mutate/write of config.json. The daemon re-reads the file on change, so live-safe levers apply at the next poll with no restart.

junco config set model.id myprovider/my-model
junco config set sandbox.enabled false
note — restart-kind levers (e.g. dataDir, worker.maxConcurrent) take effect only after restart: with the daemon up, set prints a restart hint, and the pending change is reported in pendingRestartFields (junco status, /health) rather than silently ignored.

see also: config get · restart · configuration reference

junco dashboard

junco dashboard

The fullscreen terminal UI, drawn in the terminal's alternate buffer and restored exactly on exit. Two modes, swapped with m: GITHUB — watched repos, their trigger-labeled issues, and junco-authored PRs — and LOCAL — the queue, the GitHub outbox, watched repos on disk, ticket worktrees, and the daemon itself. It observes without starting anything — the daemon runs separately — and the command palette (:) runs junco CLI subcommands without leaving it. With no config yet, the first run opens the guided setup walkthrough (re-run anytime from the palette's "setup").

junco dashboard
note — needs a real TTY — run it from a terminal, not piped or backgrounded. Bare junco also lands here after ensuring the supervised daemon is running.

see also: junco · status · dashboard guide

junco data

junco data [--json]

Prints the resolved data tree under dataDir: every path with live counts, legacy-override provenance, and any pending migration. A pure read-only view — it never creates or moves anything. Absent nodes are still listed ((absent) is normal before the first daemon start); a legacy-overridden root prints its config key with a deprecation tag, and an unmigrated old-name directory prints the pending rename. --json emits the same information as { root, layout, paths, counts, legacy, pendingMigrations, deprecations } for scripts.

junco data
junco data --json
junco data — root: /Users/you/.junco

queue      /Users/you/.junco/queue
  inbox 2 · processing 1 · done 148 · failed 9

see also: data migrate · doctor · configuration reference

junco data migrate

junco data migrate [--dry-run] [--force]

The opt-in full unification of everything junco owns under the single ~/.junco root — the ticket queue never moves automatically. Moves the queue into <dataDir>/queue (rename, falling back to copy+verify+delete across filesystems), restructures a flat legacy tree into the v2 layout (data/, cache/, logs/), relocates the bot's gh credentials to ~/.junco/gh and the config file itself to ~/.junco/config.json, rewrites the config to drop vaultRoot / juncoSubdir / observability.stateDir, and removes the emptied legacy root. Every step is journaled to migrated.json. Holds migrate.lock at every root it may touch so two migrations can't race; nothing already moved is ever rolled back, so fixing a conflict by hand and re-running picks up where it left off. --dry-run prints the full plan and changes nothing.

junco data migrate --dry-run
junco data migrate
warn — refuses to run while the daemon appears up: any /health response (even non-200) counts, as does a live-held worker.lock next to config.json. --force skips both checks. A state-tree name collision where the destination holds real files is left untouched and exits non-zero — file-holding data is never deleted.

see also: data · restart · configuration reference

--dry-run
Print every move without touching anything
--force
Skip the daemon-up refusal checks

junco dispatch

junco dispatch <ref>

Fetches a GitHub issue — owner/repo#N or a full issue URL — and queues a ticket for it; the daemon works the issue in an isolated worktree and opens a draft PR. Access decides the mode: with push access the clone is fork-less, branches land on the repo itself, and the repo is auto-onboarded to the watchlist as a first-class entry with no confirmation step; a public repo without push access is auto-forked, cloned into a managed directory, and watched as an external entry, with the draft PR opened against upstream — no labels or comments ever touch the upstream issue. A private repo without push access fails loud before anything is cloned. Ticket frontmatter is machine-built, never model output; the issue text rides in the ticket body as untrusted data, not instructions.

junco dispatch acme/reef-api#52
junco dispatch https://github.com/upstream/reef-api/issues/52
note — in fork-PR mode the draft PR is the single artifact that reaches upstream. Review its diff before marking it ready — that gate replaces the plan-review hop an owned repo gets. Re-dispatching the same issue collides on the existing branch by design; push follow-up commits with an amends_pr ticket instead.

see also: prs · submit · github loop guide

junco doctor

junco doctor

Preflight for the whole install: config parses, node/git/gh are present, gh is authenticated, the inference endpoint is reachable, the configured model is advertised, and the queue/worktree/data directories are writable. Also preflights the execution sandbox's backend binary.

junco doctor
warn — the sandbox fails closed when its backend binary is missing (bubblewrap on Linux): install bwrap, or set sandbox.backend: "none" / sandbox.enabled: false.

see also: status · config list · operations guide

junco inbox-path

junco inbox-path

Prints the resolved inbox directory path and exits — the directory the daemon watches, resolved from the active config.json. Useful for tools that write ticket files directly instead of going through submit.

junco inbox-path
warn — the inbox is a code-execution boundary: anyone who can write to it can act as you. Keep it on a local disk you own, never a synced or shared folder.

see also: submit · list · tickets guide

junco list

junco list [box]

Lists tickets newest-first per queue box — inbox | processing | done | failed — with terminal statuses; pass a box name to show just that one. Read-only: nothing moves on disk.

junco list
junco list failed

see also: retry · rm · status

junco logs

junco logs [-f] [-n N] [--json|--human]

Tails the worker log at ~/.junco/logs/worker.log (<dataDir>/worker.log on a not-yet-migrated flat tree; rotated at 10 MB). -f follows, -n N bounds the tail. Output is human-readable on a TTY and raw JSON when piped or with --json; --human forces the readable format even when piped.

junco logs -f
junco logs -n 200 --json
09:31:05 INFO  claimed {"src":"inbox/gh-acme-reef-api-52.md","dst":"processing/gh-acme-reef-api-52.md"}
09:43:56 INFO  [gh-acme-reef-api-52] opened PR https://github.com/acme/reef-api/pull/57
09:43:58 INFO  idle

see also: status · dashboard · operations guide

junco outbox

junco outbox [flush]

Lists the offline GitHub backlog: when GitHub is unreachable, lifecycle label flips, issue comments, filed assess findings, analyze comment posts, and the whole PR push+create sequence queue as durable per-op JSON files under <dataDir>/outbox/. The listing shows each op's type, target issue/branch, age, and attempt count, plus any dead-letter count; junco outbox flush pushes the backlog now instead of waiting for the next bridge sweep. Both forms work with the daemon down. Replay is FIFO and idempotent — a crash mid-flush won't double-post comments or duplicate labels — and a non-network failure dead-letters an op after 3 attempts to <dataDir>/outbox/dead/.

junco outbox
junco outbox flush
note — junco doctor warns while dead-lettered ops exist. To retry one: edit it by hand, move it from outbox/dead/ back into outbox/, then run junco outbox flush.

see also: status · doctor · github loop guide

junco prs

junco prs

Lists junco-authored pull requests across watched repos, sorted attention-first — checks-failing and changes-requested surface at the top, the same order as the dashboard's PRs view. External fork-PR entries count: a repo watched as external: true is excluded from the bridge's label sweep but stays watched for PR listing, so upstream draft PRs opened in fork-PR mode appear alongside PRs on repos you own. Read-only — it lists, nothing is posted or flipped.

junco prs

see also: dispatch · outbox · github loop guide

junco replay

junco replay <ticket-id|path.jsonl> [--budget-per-kind

Re-runs a recorded per-ticket event transcript through a fresh guard manager and reports what the guards would decide under a chosen policy — a what-if report, never a live rerun: no endpoint traffic, no tool execution, no queue changes. Each of the four supervisor knobs resolves independently by precedence: an explicit flag, then the policy recorded in the transcript's run frames, then the loaded config, then the built-in defaults. The report compares recorded decisions against replayed ones per run and ends in a verdict with caveats; --json prints the raw report instead.

junco replay gh-acme-reef-api-52
junco replay ~/.junco/data/transcripts/gh-acme-reef-api-52.jsonl --budget-per-kind 2 --json
note — replay reads the v2 run frames (junco_run_start / junco_guard_decision) that transcripts carry from 0.11.0 on; a bare ticket id resolves against <dataDir>/data/transcripts/.

see also: logs · operations guide

junco restart

junco restart

Restarts the supervised daemon so it picks up config and code changes. It validates the config first — refusing to bounce the daemon onto a config it can't parse — then finds the launchd plist / systemd user unit referencing the config path, kicks it with the platform-correct verb (launchctl kickstart -k / systemctl --user restart, the verbs that relaunch unconditionally), and confirms the pid changed before reporting success.

junco restart
note — a plain SIGTERM is not a restart: with launchd's SuccessfulExit=false keep-alive, a graceful exit stays down.

see also: service · update · operations guide

junco retry

junco retry <name…|--all>

Moves failed tickets back to the inbox for a fresh run — the claim stamp, appended result blocks, and retry bookkeeping are stripped, so the ticket re-enters the queue as if newly submitted. Takes one or more ticket names, or --all to requeue everything in failed/.

junco retry gh-acme-reef-api-52.md
junco retry --all
note — a retried ticket is cut at the first appended <!-- junco-result separator, so a ticket body containing that literal line would lose its tail on retry.

see also: list · rm · tickets guide

junco rm

junco rm <name>

Best-effort delete of a queued ticket from inbox/ only — it never touches processing/ (the daemon owns it) and refuses any name that resolves outside the inbox. The name is fuzzy-matched against inbox/*.md, the same way retry matches.

junco rm add-util-2026-05-31.md
note — a miss is not an error: the daemon can atomically claim a ticket into processing/ between your listing and the delete, so "not present in inbox" exits 0. This is a best-effort inbox delete, not an authoritative kill.

see also: list · retry

junco run-once

junco run-once

Processes a single available task and exits — the one-shot form of the worker loop. Takes no lock, so it is safe for cron or by-hand runs alongside a running daemon.

junco run-once

see also: start · submit · operations guide

junco schema

junco schema

Prints the ticket-frontmatter JSON Schema — the typed contract for every frontmatter field — and exits. Read-only; nothing on disk changes. The reference for writing tickets by hand or validating them from other tools.

junco schema

see also: submit · ticket schema reference

junco service

junco service

Renders a platform-native service file to stdout — a launchd plist or a systemd unit, defaulting to launchd on macOS and systemd elsewhere; --platform launchd|systemd overrides. Pipe it to the right location and load it; once supervised, restart knows how to bounce the daemon.

junco service --platform launchd > ~/Library/LaunchAgents/com.junco.worker.plist
junco service --platform systemd > ~/.config/systemd/user/junco.service
note — after writing the file: launchctl load then launchctl start com.junco.worker on macOS; systemctl --user daemon-reload then systemctl --user enable --now junco on Linux.

see also: start · restart · operations guide

junco skill install

junco skill install [--harness <name|path>]...

Links the junco-dispatch skill into agent-harness skills directories through the <dataDir>/skills mount. A harness passed by name or by path is written to skills.harnessDirs — standing consent junco honors from then on: the daemon re-creates and repairs the links at every start and after junco update, and junco never writes into a directory not on that list. With no arguments the command re-ensures the configured links and prints what it created, repaired, or skipped.

junco skill install --harness codex
junco skill install --harness ~/.config/opencode/skills
junco skill install
note — a valid symlink is never repointed and a real file or directory occupying a link's place is never touched — the run warns and moves on; junco doctor names the blocked path.

see also: doctor · update · tickets guide

junco start

junco start

Runs the daemon in the foreground: it polls the inbox forever, claiming and processing tickets, until Ctrl-C. On startup it acquires a single-instance lock (worker.lock, next to config.json); if another instance already holds the lock it exits 0 instead of erroring, so a supervisor (launchd, systemd) never enters a restart loop after a double start. --once processes one task and exits.

junco start
junco start --once
note — by default (worker.startupWait = true) startup blocks and retries every 30 s until the inference endpoint responds; set it to false to start immediately and fail individual tickets while the endpoint is down.

see also: run-once · service · restart · operations guide

junco status

junco status

One-glance view of the runtime: daemon pid and uptime, inference-endpoint readiness, in-flight tickets, processed counts, and queue sizes — the full record behind the dashboard header's condensed pulse.

junco status

see also: logs · doctor · dashboard · operations guide

junco submit

junco submit <file|->

Places a ticket into the inbox with an atomic write — the daemon can never claim a half-written file. The filename comes from the ticket's id (falling back to the source filename); - reads the ticket from stdin, which is how other tools pipe work in.

junco submit fix-tide-rounding.md
cat ticket.md | junco submit -
queued inbox/fix-tide-rounding.md
warn — the inbox is a code-execution boundary: a PR-flow ticket runs an agent against the repo it names, as you. Keep the inbox on a disk only you can write, and confine targets with git.allowedRepoRoots.

see also: list · retry · schema · tickets guide

junco unwatch

junco unwatch <owner/repo> [--plan]

Stops watching a repository and deletes its junco-owned operational state: queued inbox tickets, the worktree namespace, and outbox, review, history, mirror, and cache traces. --plan prints the itemized deletion as JSON and deletes nothing. Re-running after a partial failure sweeps the leftover residue idempotently. The dashboard's unwatch runs the same core behind an itemized confirmation.

junco unwatch acme/reef-api --plan
junco unwatch acme/reef-api
warn — deletion is permanent — parked review batches, per-repo history, and queued tickets for the repo are gone; run --plan first. A ticket for the repo currently in processing/ blocks the run until it finishes.

see also: data · github loop guide

junco update

junco update

Installs the latest npm release of junco, then drains and restarts the supervised daemon — a graceful stop drains in-flight work before the new version takes over.

junco update

see also: restart · service · status · operations guide

junco worktree prune

junco worktree prune <path>

Deletes a stale or backup ticket worktree by path (a failed ticket's preserved worktree lives at <worktreeRoot>/<id>). The prune runs under the daemon's own shared worktree lock with an in-lock liveness gate, so it can never race a ticket that just started using the tree — and it refuses a live worktree outright: the daemon owns those.

junco worktree prune <worktreeRoot>/gh-acme-reef-api-52
note — the dashboard's LOCAL → Worktrees section (x) runs this same command under the hood — no separate implementation.

see also: dashboard · status · operations guide