Skip to content

CLI reference ​

Run repoos with no arguments to see this list in your terminal.

Setup ​

repoos init [name] ​

Scaffolds RepoOS files in the current repo, plus an inbox starter task so the board isn't empty — a suggestion for you, not work to auto-run, promoted with repoos mv <id> ready. The starter body follows the repo's content: an effectively empty repo (no meaningful source yet) gets the product-vision task, otherwise the read-the-codebase one; override with repoos init --starter vision|codebase. By default files go under a repoos/ subdirectory; interactive prompts let you choose a different location or / for the repo root. repoos.toml and AGENTS.md always stay at the root. Run outside a git repo, it starts a guided new-project flow instead, which can launch the web console for you. On an existing repo, an interactive run also offers to seed the task-area vocabulary (and commented preview-target stubs for it) — skippable, and skipped automatically once [[areas]] is declared. See Configuration.

--docs-from copies an existing doc set into docsDir (a directory, a single file, or a .zip); --force overwrites collisions. The guided flow also prompts for a path, then offers to scaffold starter docs. Nothing is imported or scaffolded unless you pass the flag or answer yes. See repoos docs below.

Non-interactive new-project mode (for agents and scripts) ​

Outside a git repo with no TTY, repoos init <name> can't prompt, so it prints the exact command to run and exits non-zero. Pass --new (or --yes) to run the same guided flow with answers from flags and defaults — no pty tricks:

bash
repoos init myproject --new \
  --description "A tiny social app for book clubs" \
  --areas web,api \
  --no-launch
FlagMeaning
--new, --yesRun the new-project flow non-interactively. Required without a TTY.
--description "<text>"Project description seeded into the starter task.
--description-file <path>Read the description from a file; - reads stdin (multi-line markdown).
--areas web,apiSeed the area vocabulary (same comma-separated rule as the area picker).
--layout <ns>repoos (default), / for the repo root, or a namespace path.
--commit / --no-commitMake the initial commit of the scaffold (default: commit).
--launch / --no-launchStart the web console when done (default: no launch without a TTY).
--preview-stub / --no-preview-stubScaffold commented [[preview.targets]] stubs for the areas.
--dir <path>Where to create the project; alternative to the positional name.
--forceScaffold into an existing non-empty directory.
--jsonPrint { root, tasks: [...], created: [...] } instead of the human summary.
--helpDocumented flags, and which apply to new vs existing repos.

--new seeds the new-project starter ("Flesh out the product vision and initial architecture"). Do not work around a missing TTY by running git init first: inside a git repo, repoos init seeds the existing-codebase starter ("Read this codebase…"), which is the wrong route for a brand-new project.

When the repo has no check plan yet, init inspects its stack and writes a starter plan to an uncommitted repoos.check-plan.proposed.toml for review; interactive init offers to move it into repoos.toml. See Checks before merge.

repoos upgrade [--channel beta|canary|rc] ​

Self-updates a standalone (curl-installed) RepoOS to the latest release. Tracks stable by default; --channel follows a prerelease line. Package-manager installs are not modified: RepoOS detects the usual npm, Bun, pnpm, Homebrew, and legacy mise paths and prints the matching update command instead. --channel applies only to the standalone curl install.

See Updating RepoOS for every install method.

The board ​

repoos new "<title>" ​

Creates a task. When the repo is a git checkout, RepoOS writes the task file and attempts an initial commit of that file only (docs(<id>): add task …). A failed commit leaves the file on disk uncommitted and prints a warning — set git identity in config or via GIT_AUTHOR_* / GIT_COMMITTER_* env vars.

bash
repoos new "Fix the login redirect loop" --type bug --area web --priority p1
repoos new "Redesign the onboarding flow" --area "web, onboarding"
repoos new "Build the settings screen" --depends-on 0542,0538
repoos new "Wire up the export endpoint" --paths src/server/export.ts,src/ui-app/src/views/ExportView.vue --hold true

repoos new --help lists every accepted flag with a one-line description. Both that help and the usage text printed on a bad flag are generated from the command's flag table, so a flag cannot be added without appearing in --help.

FlagValues
--typefeature, bug, chore, spec, refactor
--priorityp0, p1, p2, p3
--areaFree text, comma-separated for several (--area web, core). Areas a repo declares ([[areas]] in repoos.toml) appear in the task drawer's area picker; anything outside it stays allowed.
--storyTag the task as part of a story (a delivery slice).
--depends-onComma-separated task ids that must be completed and merged into main first.
--holdtrue or false — hold the task out of the auto-engineering picker.
--pathsComma-separated files the task touches, e.g. src/a.ts,src/b.ts.
--needs-inputtrue or false — flag the task as needing human input.
--questionsOpen questions for the human, newline-separated or a JSON list.
--assigned-toai or human — who owns the task. Any other value is refused.
--aiShorthand for --assigned-to ai
--humanShorthand for --assigned-to human
--bodyTask body; pass - to read from stdin
--human-step-overrideA reason that clears a needs-human-step keyword match on an AI task (see below).
--shotsRaw JSON list declaring the ## Shots section; pass - to read from stdin.
--screenshotAttach a local image file to the task's ## Screenshots section. Repeatable (--screenshot a.png --screenshot b.png).
--captionAlt text for a --screenshot image. Repeatable; the Nth caption labels the Nth screenshot.

--priority and --type are validated on every write: a value outside the sets above is rejected with the field, the bad value and the full list of valid values (e.g. priority 'medium' is not valid; use one of p0, p1, p2, p3), and is never silently defaulted. The same check applies to repoos update and the HTTP API. Task files that already carry a legacy value (priority: high, type: ux) still load and display as-is — only new writes are checked.

A task assigned to a human is created with a ## Human task note saying so, and skips the agent task-body checks: ## Notes for AI is optional and the task is not flagged underspecified or needs-human-step. This is the one-command way to put human work on the board:

bash
repoos new "Flush the Divot DNS cache" --assigned-to human

Human-assigned tasks are inert board work: move them between Inbox, Ready, Active, Review and Done by dragging, using the drawer status control, or with repoos mv <id> <status>. These moves only change status; they never start an agent, run checks, create a worktree or invoke review/close-out. hold is not needed for human assignment. Mark done in the drawer or run repoos mv <id> done --note "what was completed"; RepoOS records who closed it and the optional note. An AI agent may move a human task only when the human explicitly asked it to update that task's board status.

Pass at most one of --assigned-to, --ai and --human; combining them is an error.

On an AI task, acceptance criteria that mention a device, account, or credentials raise needs-human-step. When that is a keyword false positive, clear it deliberately with a reason — on creation or with repoos update. The reason is stored on the task and written to its activity log, so the clearance is on the record rather than a silent rewording:

bash
repoos new "Reduce the retry backoff" --human-step-override "the API-key mention is a fixture"
repoos update 0815 --human-step-override "no real credentials are involved"

The override only clears the needs-human-step flag; the matched keywords are still listed in needs_input_detail until the override is set. Pass an empty value (--human-step-override "") to remove it.

repoos list [status] ​

Shows the board, or one column: inbox, ready, active, review, done. Archived tasks are hidden by default (a count is printed at the bottom); repoos list archived shows them with their reason.

repoos show <id> ​

Prints a task's full spec — metadata, body, and activity log. An archived task shows an archived row with its reason.

repoos mv <id> <status> ​

Moves a task to a new status. Takes --note "..." to record why.

--note only adds an activity log entry; it does not send feedback to the engineer. Use repoos send-back when returning work with instructions.

bash
repoos mv 0001 ready --note "spec is settled, ready to hand over"

review is the one status with special behaviour, because reaching it means running the checks, not just editing a field. From your terminal the move goes through the same handoff finalization the UI uses — commit the branch, run repoos check, pass the commit guard, then land in review. The task stays active while that runs and stays active with the reason if the check fails; the command itself still exits 0. See Review and close-out.

Run from inside a RepoOS-managed agent session for that same task, repoos mv <id> review records a handoff request instead of moving anything: the runner picks it up when the turn ends and finalizes then. That is deliberate — an agent moving its own task out of active would have its turn killed mid-flight.

done is refused when the task's branch still exists and is not merged into main; repoos mv done only flips the flag, it never merges. Use repoos done <id> (the real close-out pipeline), Move to done in the UI, or merge the branch yourself first.

For server-backed actions (start, pause, sync, handoff, close-out, previews, config, runners, running agents, board stats), use the Control plane commands below instead of hand-rolled curl — they authenticate once (loopback token or stored session, with transparent dev-login re-auth on 401).

An archived task is refused too: archiving is orthogonal to status, and the only way back is to unarchive it (from the Archived list in the UI, or POST /api/tasks/:id/unarchive).

Control plane (running server) ​

These commands talk to the live RepoOS server (repoos serve). They reuse the loopback CLI token when auth is on, or a stored session after dev-login, and support --json on every command. Long operations accept --wait (review and done wait by default in human mode).

CommandAPI
repoos start <id> [--fresh] [--force]POST /api/tasks/:id/start — --force starts a held task (see hold below)
repoos pause <id>POST /api/tasks/:id/pause — stop the current run and keep the task paused (a hold does more; see below)
repoos hold <id> --reason "…" [--until <date>]POST /api/tasks/:id/hold — stop any running engineer and make the task inert for ALL automation
repoos unhold <id> [--start]POST /api/tasks/:id/unhold — release the hold; --start re-launches the engineer on the now-released task
repoos sync <id>POST /api/tasks/:id/sync — merge main into the task branch (lists conflicting paths and exits non-zero on conflict)
repoos message <id> "…" [--now]POST /api/tasks/:id/message (now: true interrupts a busy agent)
repoos send-back <id> --message "…" or `--message-file <file->`
repoos review <id> [--wait]PATCH /api/tasks/:id (status: review)
repoos review cancel <id>PATCH /api/tasks/:id (status: active, cancels an in-flight handoff/review)
repoos review again <id>POST /api/tasks/:id/review/again
repoos done <id> [--commit-dirty] [--wait]POST /api/tasks/:id/done
repoos done <id> --cancel (repoos abort <id>)POST /api/tasks/:id/done/cancel
repoos api <METHOD> /api/<path> [--json body] [--port N]Any /api/ route
repoos landable <id>GET /api/tasks/:id/landable — read-only merge readiness checks; exits non-zero unless the task is ready to merge (--json for a stable checklist shape)
repoos handoff-status <id>GET /api/tasks/:id/handoff-status — non-blocking latest handoff state (not_requested, running, passed, failed, deferred), step and reason; never starts or waits for a handoff
repoos override <id> --cli X --model YPATCH /api/tasks/:id
repoos preview <id> [--stop]POST /api/tasks/:id/preview (+ /stop)
repoos dev [list] · repoos dev <name> start|stop|restart|status|logsGET/POST /api/dev/targets
repoos config get|set <key> [value]GET / PATCH /api/config
repoos runners [--probe]remote-validation status + test
repoos runners check [--json]engineer CLI install/auth matrix per declared host (#0915); exits non-zero when a cell is red
repoos runners setup <host> [--login <cli>]install missing declared CLIs; --login opens interactive SSH into the CLI login prompt
repoos integration list|pipeline|get <id>|retry <id>|refresh-install <id>Integration job inspection and retry routes
repoos attention list|dismiss <id>GET /api/attention and needs-input dismissal
repoos agentsGET /api/agents/running
repoos agents pause|resume <agent> [--model M] [--until <date>] [--reason "…"]PATCH /api/config — pause/resume an allowlist agent or model (#0850)
repoos statsGET /api/stats/board + GET /api/system — board spend, pressure, and top CPU/memory processes labeled RepoOS vs other (--json includes both under system)
repoos decisionsGET /api/decisions — CTO escalation digest with cause, evidence, and actions
repoos driver briefGET /api/driver/brief — the CTO board brief from live state (#0731)

Pass --port N when the server is not on the default port. If the server is down, the CLI exits with a clear message instead of a generic fetch error. repoos api is an authenticated escape hatch for a route that has no dedicated CLI command yet. Prefer the named command when one exists. API parity is enforced by src/cli/api-route-parity.ts; routes intentionally unavailable to drivers must have a documented exemption there.

Hold / unhold a task out of ALL automation (#0960) ​

Existing hold: true tasks and tasks tagged hold gain the same CTO/watchdog protection automatically; no task-format migration or new status is required.

A hold is a single, durable, documented state that makes a task inert for every automation RepoOS runs — auto-dispatch, the CTO monitor (idle nudges and stalled-agent restarts), the watchdog's auto-resume, crash recovery, and queued-message delivery that would otherwise start an engineer. A held task keeps its status (a held active task stops its engineer but stays active; a held ready one simply never auto-dispatches); it is never started by anything but an explicit human or driver command that clears the hold. This is broader than pause, which only stops the current run — the CTO monitor could still restart a paused task. Use hold when you want a task to sit completely still.

bash
repoos hold 0960 --reason "waiting on design sign-off" --until 2026-12-01T00:00:00Z
repoos unhold 0960            # release it
repoos unhold 0960 --start     # release it and re-launch the engineer

--reason is optional free text on the Hold dialog and on POST /api/tasks/:id/hold (a hold with no reason omits the reason row). The repoos hold command still requires --reason. --until is an optional ISO-8601 UTC auto-release time — past that moment the hold is inert and the task behaves as if never held (so a calendar date works as a "resume after…"). The hold is written into the task's frontmatter (hold, hold_reason, hold_until, hold_by, hold_at) alongside its status, so it survives a server restart and is visible on the board card and in the driver brief (repoos driver brief gains a Held section). A held task also does not count toward maxActiveTasks.

repoos start <id> --force starts a held task by overriding the hold; this is the explicit escape hatch for a human or driver who has decided to proceed. A normal repoos start on a held task is refused with a clear message naming the reason, and tells you to repoos unhold (or --force) first. Held tasks do not appear in the auto-engineering picker, and a held ready task is never selected. A hold tag on a task is treated the same as the hold flag, so you can park work without editing frontmatter directly.

repoos bundle (easter-eggs bundles) ​

Surgical edits to an open Easter eggs bundle: task without replacing the whole body. Writes go through POST /api/tasks/<id>/bundle; bundle task status is untouched.

bash
repoos bundle add 0822 --title "Hub find bar" --area web --fix "keep find below title" --source "#0795"
repoos bundle edit 0822 E2 --section "Desired UX" --section-body "New UX text."
repoos bundle done 0822 E1
repoos bundle undo 0822 E1

add appends one ## Items checklist line and a matching ## E<n> · Title section (next free id). edit changes one ### subsection inside an item. done / undo tick or clear that item's - [x] checkbox in ## Items only.

Refuses tasks whose title does not start with Easter eggs bundle: unless --force. Pass --port N when the control plane is not on the default port.

repoos update <id> ​

Edits a task's metadata or body: --title, --area, --story, --depends-on, --priority, --type, --body, --branch, --assigned-to, --human-step-override, --needs-input, --needs-merge, --hold, --paths, --questions, --clear-questions, --shots, --section / --section-body, --force, and per-role agent pins (--agent, --cli, --model, plus --pm-* and --review-* variants). Run repoos update --help for the full, generated list. These write the same fields as PATCH /api/tasks/<id> and are what engineer/reviewer runs read. POST /api/tasks/<id>/start and /message reject override fields in the request body (HTTP 400); PM chat routes still accept cliOverride / modelOverride for a single turn. See Running with agents.

bash
repoos update 0615 --depends-on 0542,0538
repoos update 0615 --depends-on ""
repoos update 0316 --needs-input false
repoos update 0397 --needs-merge false

The --needs-input / --needs-merge flags take true or false, and clearing the flag also clears the reason/detail recorded alongside it.

Dependencies are satisfied only when the upstream task is done and Git confirms its merged commit is an ancestor of main. A blocked task cannot be started manually unless you explicitly confirm the override; missing or unverifiable upstream tasks are marked as needing a human. Unknown ids, self-dependencies, and cycles are rejected when written.

To declare evidence screenshots, pass raw JSON to --shots. The CLI validates every entry and writes the fenced ## Shots section itself, leaving the rest of the body (including the Activity log) untouched. repoos new accepts it too; - reads the JSON from stdin:

repoos update 0612 --shots '[{"target":"default","route":"/agents?tab=detected","label":"Detected tab","highlight":".detect-row"}]'

Attaching screenshots (--screenshot) ​

When you have the actual image files — an agent handed a human's screenshots in chat, for example — pass each one to --screenshot (repeatable) on repoos new or repoos update. The command validates the file against the same allowlist and size limit the UI upload uses (.png, .jpg/.jpeg, .gif, .webp, .avif, .bmp; max 10 MB), stores it under the gitignored work/.attachments/<id>/, assigns an opaque att_<uuid>.<ext> storage name (stable in committed Markdown), and writes or extends the task's ## Screenshots section (kept before ## Activity). It prints the resulting attachment URLs:

bash
repoos new "Fix the login redirect loop" --screenshot before.png --screenshot after.png
repoos update 0763 --screenshot repro.png --caption "crash on save"

--caption sets alt text for --screenshot images by position: repeat both flags (--screenshot a.png --caption "before" --screenshot b.png --caption "after"). Fewer captions than screenshots is fine (the rest keep default alt text). More captions than screenshots is an error that names both counts. A missing, unsupported, empty, or oversize file is rejected with a clear error before any change is written. Nothing under work/ is staged — the images are gitignored and served by the running server from disk; only the ## Screenshots markdown lands in the task file.

To replace any other single ## Section (create it if absent), use --section "<heading>" --section-body "...". A Shots section written this way must be a fenced JSON list, and malformed content is rejected rather than silently falling back to a / capture. --shots '[]' (an empty JSON list) writes an empty shot list (## Shots\n```json\n[]), which clears all declared shots and lets the task fall back to automatic captures. To remove the entire ## Shots section instead, use repoos update <id> --section "Shots" --section-body "". --shots cannot be combined with --section or --body.

A full --body replace that would drop Problem / Desired UX / Acceptance criteria / Notes for AI is refused unless --force is passed; the error points at the --section form.

Editing the body of an active task — --body, --section, the API PATCH, or the drawer's Update requirements action — records one activity entry naming the changed sections and sends the engineer one message carrying the same summary (with the new text of added items). The engineer's worktree copy of the task file is deliberately not written: the notice tells it what moved, and merging main (or the next handoff) brings the file in line. A body edit on a task that is not active sends no engineer notice.

repoos note <id> "<text>" ​

Appends a free-form note to the task's activity log. Useful for leaving context an agent should read before picking the task up.

repoos new-doc "<description>" ​

Creates a document from a description, via the Product Manager agent.

repoos docs import <dir|file|.zip> [--force] [--dry-run] ​

Copies an existing doc set into docsDir, preserving folder structure. The source may be a directory, a single file, or a .zip archive (common for a browser download): an archive is extracted to a temp directory first, a lone top-level folder is unwrapped so you don't get an extra level, and macOS junk (__MACOSX/, .DS_Store, ._*) is ignored. Archives that try to escape the extraction directory (zip-slip), contain symlinks, or are absurdly large or numerous are refused. Existing files are left untouched and reported unless --force is passed; --dry-run prints the plan and writes nothing.

repoos docs scaffold ​

Writes a minimal, opt-in starter skeleton into docsDir: an index README.md with a reading order and an "if you learn something durable, write it here" line, plus short product.md, architecture.md, conventions.md and glossary.md stubs with prompts. It never overwrites an existing file, and nothing is created unless you run the command (or answer yes to the scaffold prompt in the guided repoos init flow).

Stories ​

repoos story new|list|show|update ​

Stories are an optional grouping over tasks (see Stories). The New story flow in the UI always hands the description to the PM agent, which writes up the scope and tags any existing untagged tasks that belong. When an agent or script already has the finished story, repoos story writes it verbatim instead: no PM, no tagging.

bash
repoos story new "Project updates email" --body "Why this slice exists, scope, non-goals."
repoos story new "Checkout rework" --body -            # body from stdin
repoos story list                                       # numbers + paths
repoos story list --json
repoos story show 0007                                  # definition + member tasks with status
repoos story show "Project updates email" --json
repoos story update 0007 --body "Rewritten scope."      # keeps the stable number
repoos story update 0007 --name "Project update emails" # rename keeps the number

### `repoos schedule list|run|enable|disable|new|update|preview|delete`

Manage **scheduled tasks** — markdown definitions under `schedules/` that create
ordinary `work/*.md` tasks on a cadence (the same files the Agents → Scheduled
Tasks UI edits). Writes land on the board root and commit when git tracks the
file.

```bash
repoos schedule list
repoos schedule list --json
repoos schedule preview --cadence weekly --timezone UTC --time-of-day 09:00 --day-of-week 1 --title "Doc audit" --task-title "Audit docs"
repoos schedule new --title "Doc audit" --task-title "Audit docs" --cadence weekly --timezone UTC --time-of-day 09:00 --day-of-week 1 --body -
repoos schedule update 0001 --enabled false
repoos schedule enable 0001
repoos schedule disable 0001
repoos schedule run 0001
repoos schedule delete 0001 --yes
SubcommandAPI equivalent
listGET /api/schedules
newPOST /api/schedules
update, enable, disablePATCH /api/schedules/:id
deleteDELETE /api/schedules/:id
runPOST /api/schedules/:id/run
previewPOST /api/schedules/preview

`new` and `update` write and commit the definition under `storiesDir`, exactly
like `repoos new` commits a task file, and print the story's number and path.
`--body -` reads the body from stdin. `show` resolves the story by number,
name, or key, prints the definition, and lists every member task with its
status; a tag-only story (tasks tagged but no file under `stories/`) shows with
`(tag-only, no definition)`. `list` and `show` accept `--json`.

These commands are board-rooted like `repoos new`: run from inside a task
worktree, they still write the MAIN checkout's `stories/`, which is the only
copy the live board reads. `repoos new --story "…"` and
`repoos update <id> --story "…"` tag tasks with a story and are unchanged.

The same write is on the HTTP API for a script that prefers it:
`POST /api/stories { name?, body }` and `PATCH /api/stories/:key { name?, body? }`.

## Project secrets

### `repoos secrets`

Manage explicitly registered project environments without printing saved
values. Use `list` to view key names and safe metadata; `add`, `replace`, and
`delete` read new values from stdin or a hidden terminal prompt. The API and CLI
use a content version for each write, so a concurrent edit returns a conflict
and must be retried after reloading. Keys inherited only from an earlier source
are read-only through the API, CLI, and Secrets page; neither replace nor delete
silently creates an override.

```bash
repoos secrets list dev
printf '%s' 'fake-value' | repoos secrets add dev SERVICE_TOKEN
repoos secrets replace dev SERVICE_TOKEN   # hidden prompt in a terminal
repoos secrets delete dev SERVICE_TOKEN
repoos secrets bind dev:web dev-nick DATABASE_URL STRIPE_SECRET_KEY
repoos secrets bindings

Bindings select one environment and an explicit key allowlist for a local consumer. They never fall back to another environment. See Environment and secrets for storage, permissions, and exposure limits.

Running it ​

repoos serve [--port N] [--host <address>] ​

Starts the local server — web UI, API, and live event stream. Without --port it uses servePort from repoos.toml, or a stable port derived from the repo's path so separate repos never collide.

By default the listener binds to 127.0.0.1 (localhost only). When RepoOS detects Tailscale on this machine, it widens the bind to 0.0.0.0 so other tailnet devices can reach the server while localhost still works. The startup banner names the bound interface. Pass --host 127.0.0.1 to force localhost-only even when Tailscale is running.

repoos service / repoos service <subcommand> ​

Manages a background service for this repo (install, start, stop, restart, enable/disable login auto-start, list all services). With no subcommand, shows this repo's service status — the usual way to run an unattended control plane instead of keeping repoos serve in a terminal.

repoos uninstall ​

Removes a standalone install created by the curl installer (install.sh). Does not remove source checkouts or package-manager installs; those are uninstalled through npm, Homebrew, or your package manager. Project data and user configuration are left in place.

repoos --version / repoos version ​

Prints the installed RepoOS version and build metadata.

repoos stop [--port N] ​

Stops this repo's server, identified by its own lockfile. It will not touch a server belonging to a different repo.

repoos servers [--json] [--prune] ​

Lists every control-plane repoos serve that registered itself under ~/.repoos/servers/ (one <port>.json per listener). Shows port, project name, repo root, PID, uptime, live engineer count, and whether the entry still looks healthy (PID alive and /api/health reports the same root).

--prune removes stale registry files (dead PID, or a live PID whose /api/health is unreachable or reports a different root), then lists the rest. --json prints the same data for scripts. Preview servers and task worktree serves never register. The Hub uses the same registry to discover local servers.

repoos status [--json] ​

One-screen health snapshot: server, build freshness, board counts, worktrees, tunnel, and git state.

The server line names the running server's own RepoOS version and build (v0.5.66 (build abc1234)), and when that build differs from the installed CLI's it prints a warning — server is older than the installed CLI … restart to pick up fixes — with the command that restarts it (repoos service restart for a managed service, repoos serve for a hand-run process). The build line describes this checkout, so in a project repo it reads no RepoOS build rather than implying the repo is a RepoOS source checkout. --json exposes cli and server.buildState (same/stale/unknown) so scripts can detect a stale server without parsing text.

repoos doctor [--json] [--verbose] [--probe <cli> [--model <id>]] [--binary <path>] ​

A read-only readiness preflight for a real project. It checks the repository identity (root, git, linked worktree), parses and validates repoos.toml, verifies the configured layout and existing task frontmatter, detects the required runtimes and enabled agent CLIs, explains whether a meaningful repoos check plan is configured, and reports server, auth and credential readiness — each with a stable finding id, a severity (pass / warn / fail) and a concrete next step. It also reports each enabled harness's compatibility contract status (verified, upgrade recommended, newer than verified, unsupported, or not yet probed).

It also runs an advisory docs-wiring check and warns when AGENTS.md never points at the docs index (docsDir/README.md), when docs exist that the index doesn't link to, or when the docs directory is empty while tasks exist. These are always warnings with a one-line fix hint — they never change the exit code. See Give the project docs.

bash
repoos doctor            # warnings/failures, summary and next steps
repoos doctor --verbose  # every check, including the passing ones
repoos doctor --json     # the same findings, machine-readable

By default the report collapses passing checks so a single failure isn't buried under ~25 green lines: only sections with warnings or failures are listed, followed by the summary and the next steps (failures first), and a closing hint that --verbose shows the whole checklist. --json is unaffected — it always emits every finding. Output is laid out for the terminal width and wraps cleanly when piped, so the default run never breaks its own indentation. The default run never initializes, rewrites config, installs, logs in, contacts a model provider, kills a process or mutates git, and it works offline. It exits non-zero when any finding is a failure, so it is usable in a script. Paste repoos doctor output into an issue to report a setup problem.

bash
repoos doctor --probe opencode --yes   # run the live adapter-contract probe

--probe <cli> is a separate, explicitly opt-in mode: it runs the named harness's adapter-contract suite against the installed binary inside an isolated temporary directory, then cleans up. Unlike the default report it starts real harness runs, so it may use provider credentials and spend tokens (the one-shot, resume and cancellation seams each start a run). It refuses to run headless without --yes, and it never reads task files, prompts or project content. --model <id> optionally pins the model used for the probe's runs (e.g. --probe pi --model openrouter/deepseek/deepseek-v4.1-flash), for harnesses whose default model depends on your environment; it is ignored by harnesses that have no model flag. See Coding harness compatibility.

repoos support bundle / repoos support inspect ​

Creates a small, inspectable, redacted diagnostic archive to attach to an issue when RepoOS fails on a real-world project. It is built locally and never uploaded; opening a browser, copying to the clipboard or filing an issue are all separate, explicit actions you take yourself.

bash
repoos support bundle                   # write .repoos/support/repoos-support-<ts>.tar.gz
repoos support bundle --dry-run         # show exactly what would be included; write nothing
repoos support bundle --out /tmp/x.tar.gz
repoos support inspect <bundle.tar.gz>  # list every file inside an existing bundle

The bundle contains a versioned structured report: RepoOS version/build metadata, platform/runtime versions, the effective configuration shape (flags, counts, schema versions — never secret values or free-form fields), agent/tool detection and versions, the resolved check plan, the sanitized repoos doctor findings, the classified result of the latest doctor run, server/health/lifecycle diagnostics, bounded recent error messages, and a machine-readable manifest.json listing every file, its size and hash, the collection time and the redaction rules/version.

It never includes .env, environment values, API tokens, passwords, cookies, SSH keys, session material, prompts, transcripts, task bodies, source code, diffs, attachments or raw logs. Home directories and the repo root are minimized to ~ and <repo>. A final scan verifies no known secret shape or private path survived; a miss aborts the write rather than packaging it. A down server, a missing agent CLI or an unparseable repoos.toml degrade that one section into an explicit omission with the reason, and the rest of the bundle is still written. The default output lands under the cache directory (gitignored in most repos); if that path is inside the repo and not gitignored, both the CLI and the UI warn that the archive could be committed and suggest --out or a .gitignore entry. In the web UI it is on Settings → Support, and a Create a redacted support bundle action also sits on the failed Move-to-done / check panel — the moment you're most likely to need it.

Quality and maintenance ​

repoos check ​

The definition-of-done gate. It runs the check plan your repo declares in repoos.toml — for any stack (see Checks before merge). Exits non-zero on any failure, so it works in CI as well as locally.

bash
repoos check --profile full      # every declared step, including slow ones
repoos check --changed main      # fast pre-review pass over changed paths
repoos check --print-plan        # print the resolved plan as [[check.steps]]
repoos check --fix               # run each format step's fixer before its check
repoos check --step tests        # run only the named step(s), to re-check one

Every run ends with a short failed-steps summary naming each failed step and the repoos check --step <name> command that reruns it. --fix never applies at close-out, so an unformatted committed tree still fails the merge gate.

repoos outline <file> ​

Prints a compact map of a source file — top-level and exported functions, classes, interfaces, types, enums and constants with their start-end line numbers; for .vue, the template/script/style block ranges plus script symbols; for .css, its top-level selectors and at-rules. Run it once before reading a large file, then read only the range you need (offset/limit) instead of the whole file.

Supported: .ts, .tsx, .js, .jsx, .mjs, .cjs, .vue, .css. Anything else prints one clear line saying so. --json emits the same data as JSON.

bash
repoos outline src/server/server.ts
repoos outline src/ui-app/src/components/TaskDrawer.vue
repoos outline src/ui-app/src/style.css --json

repoos shot ​

Captures screenshots of a task's managed preview so a UI change leaves visual evidence a reviewer or human can actually look at, instead of "it looked fine". Run it from the task's worktree; it asks the running repoos serve to start the task's preview (never starting a server itself), drives the same optional Playwright/WebKit path the UI check uses, and stores PNGs under work/.attachments/<taskId>/shots/ — gitignored, never referenced from the task body, and shown as the UI changes sub-tab of the drawer's Changes tab. This is the default local attachment storage; an opt-in cloud provider is configured under storage.provider.

repoos shot follows the remoteValidation.browserCaptureMode setting. With local (the default) it drives Playwright/WebKit on this machine; runner-available captures on a capable runner and falls back locally with a visible note; runner-only returns the runner's structured failure instead of capturing locally. An arbitrary absolute URL, or an explicit --viewport, --wait, or --full-page, stays local with a note in runner-available mode (and fails with the reason in runner-only, because the remote single-shot path cannot reproduce it). Command capture targets always run locally on this machine.

bash
repoos shot                          # capture the preview root for this task
repoos shot /repo/commits/abc123     # capture one route
repoos shot --target "Docs site"     # force a target (skip path/area resolution)
repoos shot --selector ".sidebar"    # capture one element instead of the page
repoos shot --wait 2000              # wait longer before capturing (default 900ms)
repoos shot https://example.test/x   # capture an arbitrary URL
repoos shot --target "macOS app"     # run a command capture target (non-web app)

repoos handoff-status ​

Prints the latest handoff attempt for a task and returns immediately: not requested, running, passed, failed or deferred, with the current step and any failure/deferral reason. It never starts, waits for, or changes a handoff, so it is safe to call while a finalization is running — including from an engineer that is unsure whether its request reached the server. After a pass, fail or deferral, RepoOS also queues one message with the outcome for the engineer's next turn, so request-then-end-turn needs no polling.

bash
repoos handoff-status 1028
repoos handoff-status 1028 --json

repoos review again ​

Start a fresh reviewer run on a task that is already in review — the same action as Review again in the task drawer. Use this after repoos verify <id> refreshes stale evidence, or whenever you want another review pass without moving the task back to active. Refuses when an engineer turn, close-out, or another review is already in progress.

bash
repoos review again 0832
repoos review again 0832 --json

repoos verify ​

Refresh the current review evidence without starting an engineer turn. This re-runs the declared browser and command captures and UI verification against the task worktree's current HEAD. The command waits for the capture to finish.

repoos verify refreshes through the server, so the remoteValidation.browserCaptureMode policy applies exactly as it does to the automatic handoff capture: runner-available prefers a capable runner and falls back locally with a visible note, while runner-only reports the runner's failure instead of capturing on this machine.

bash
repoos verify 0766
repoos verify 0766 --json

The refresh is available only for a clean task in review with no agent run or close-out in progress. Captures and the verification record are stored in the gitignored evidence directories. A failed or unavailable capture is reported in the response and verification record.

A capture command that writes a tracked file (ignoring REPOOS_SHOT_DIR) does not leave edits behind: the refresh restores tracked files and removes untracked files the capture created, reports what was restored (restored in the JSON response, and per-path lines in the human output), and the worktree is porcelain-clean again afterwards. A HEAD that moved under the capture still fails the refresh — the record can only be tied to the tree it was captured on — but the tracked damage is restored even then.

A repo whose product is a native macOS app, a mobile app or a desktop app declares a command capture target instead of (or as well as) a dev-server [[preview.targets]] row — a [[shot.targets]] command that renders image files offscreen. repoos shot --target "<name>" runs that command and stores the images it writes into REPOOS_SHOT_DIR, and the automatic handoff capture runs it too. See Previews and shots for the config shape and platform recipes.

After the page loads, repoos shot waits for network quiet (best-effort) and a short settle before capturing, so a dev server's client-mount spinner is not what gets recorded. Raise --wait <ms> for a page that renders slower than that.

Which target(s) to capture is decided from the files the task changed, matched against [[preview.targets]].paths globs in repoos.toml — not the task's up-front area:. The default (main-app) preview can declare its own globs as paths under [preview] (this repo: src/ui-app/**), so an app diff resolves it too and a mixed app+docs diff captures both. If nothing matches, it falls back to area resolution; --target overrides. Each shot records its resolved target name. The drawer warns whenever the changed paths touch a target the task's area does not resolve to, so a mislabeled area becomes visible rather than silently screenshotting the wrong app. Only one preview runs per task, so a multi-target capture restarts the preview between targets (stopping one you may be watching) — the command prints a note when it will do that.

The task's ## Shots list ​

A change inside a drawer, modal or filled form is not visible from /, and you are the one who knows which page/state shows it. Declare it in the task body — a ## Shots section holding a fenced JSON list, one entry per shot:

json
[
  {
    "target": "default",
    "route": "/",
    "label": "New-task drawer open",
    "highlight": "form.new-task .title-input",
    "steps": [
      { "click": "button[data-test-id='new-task']" },
      { "waitMs": 300 }
    ]
  }
]

Each entry takes target (a resolved preview target name; omitted means the default target), route (default /), an optional selector (capture one element), a human label shown as the shot's caption in the Changes tab, an optional highlight CSS selector — every element it matches is outlined in the capture, pointing the reviewer at what changed — and optional ordered steps — { "click": "<selector>" }, { "fill": "<selector>", "text": "..." }, { "waitFor": "<selector>" }, { "hover": "<selector>" } or { "waitMs": 300 } — using plain CSS selectors or test ids, no framework knowledge. Optional viewportWidth and viewportHeight (px, same bounds as the CLI --viewport flag) pin the capture to a specific window size — use two heights on the same route when layout must be checked at tall and short viewports (#0951). Say what changed: a label naming the change and a highlight on the changed element are the difference between a reviewer reading "New-task drawer open" and squinting at a full page.

repoos shot captures this list, and the SERVER does too: declared shots are captured automatically when a task moves to review, with targets resolved the same way the CLI resolves them — the diff's changed paths first, then the task's area, then the default [preview] command. The automatic capture is deliberately conservative when nothing is declared (#0603): a fallback shot's caption records the glob that matched (auto: matched src/ui-app/**), a declared shot's caption records its label (declared: New-task drawer open); a diff touching only tests or task notes touches a glob but is not a UI change, so nothing is captured without a declaration (test files are not UI evidence); and a docs target matched only by content files is skipped unless a declared shot names a route — the docs home page does not show a wording edit. (Repos whose UI is itself markdown-driven: a genuine UI change in a .md file counts as content, so declare the shot with its route.) Skips and failures are recorded in the task log and activity as shots: skipped — <reason> / shots: failed — <reason>, never as a failed handoff; skips that ride alongside a captured shot appear in the same note. An engineer-made capture pre-empts the automatic one.

Typing a route (or passing --selector) on the command line replaces the declared list for that run, and applies to every resolved target — a typed route is captured, not deferred to the / fallback (#0610). The declared entries are not re-pointed at it: each one carries steps, a highlight and a label written for its own route, so moving entry #2 to a route you just typed would click selectors that may not exist there. A route you name also counts as the justification the docs-content skip asks for, so repoos shot /configuration --target "Docs site" captures the page you asked for instead of standing down. The command prints the plan it will shoot (plan: default /settings – …) before it starts a browser.

Playwright/WebKit is optional (a dev dependency). When it is missing, repoos shot prints install advice and exits non-zero without touching anything: bun add -d @playwright/test && bunx playwright install webkit. A screenshot is the one sanctioned use of the managed preview by an engineer agent, and only for UI-visible changes — see Review and close-out.

repoos index [--json] ​

Rebuilds the derived index cache. The cache is disposable — the markdown files are the source of truth — so this is safe to run any time.

repoos gc [--yes|--dry-run] ​

Reclaims leaked task worktrees and branches, using the same automatic and fail-closed policy as the server's boot/close-out sweep. It only removes throwaway close-out candidates and worktrees for resolved tasks whose content is provably already in main; active, human-assigned, unmerged, and uniquely dirty work is kept and reported with a reason. Worktrees outside RepoOS's managed directory are listed as external and never touched. If the task index or the durable close-out-job records cannot be read, cleanup is skipped and the error is reported rather than guessing that work is inactive.

Ignored files are inspected too: a worktree holding a local .env, an attachment tree, or other non-generated ignored data is retained even though git status would call it clean. Regenerable output (dist/, node_modules/, coverage, .repoos/ cache) does not block cleanup, and an inherited .env symlink into the main checkout is safe to drop because the canonical file stays. Retained entries report commits ahead, changed source paths, dirty paths, and ignored paths so the reason is actionable.

--dry-run shows the candidate / retained / external breakdown without changing anything. --yes is accepted as a compatibility alias; the default run already applies the safe candidates.

repoos tunnel <subcommand> ​

Publishes local apps through Cloudflare Tunnel + Zero Trust: setup, create, allow, deny, start, install, stop, list, status.

FSL-1.1-MIT — free to use, self-host and modify; converts to MIT two years after release.