CLI reference
Everything the rove binary does. The scriptable surface for agents and
scripts has its own page: rove api.
Two things stay authoritative if this page and the binary ever disagree:
rove --help for the command list, and rove api schema for the rove api
surface.
Install and update
Needs git and at least one engine CLI on PATH. The CLI runs on the Bun
runtime (≥ 1.3.11); each route below installs Bun for you when it is missing.
curl -fsSL https://rove.run/install.sh | sh # installs Bun, then Rove
npm install -g @sma1lboy/rove # npm (asks about Bun on first run)
bun install -g @sma1lboy/rove # bun
npx @sma1lboy/rove # try without installingThe rove bin is a small launcher: it runs the CLI directly when
started by Bun, and find (or offer to install) a Bun when started by node,
which is what npm install -g and npx do. Two environment variables steer
that: ROVE_BUN names the Bun binary to use, ROVE_NO_BUN_BOOTSTRAP=1 turns a
missing Bun into a plain error instead of an install offer. A Bun older than the
package's engines.bun floor (1.3.11) is refused with the upgrade command for
it, since Rove's terminals need Bun's PTY API and produce nothing at all
without it; ROVE_SKIP_BUN_CHECK=1 overrides that at your own risk.
The installed package exposes only rove. Product data lives under ~/.rove
and ~/.config/rove/state.json.
rove update # newest build on your channel
rove update 0.7.90 # pin a version
rove update nightly # switch to the nightly channel (also: --channel nightly)
rove update latest # switch back to stable
rove update list # browse recent versions (also: --list)
rove update dry-run # print the command without running it (also: --dry-run)There are two channels. latest is the stable line — batched, reviewed
releases. nightly is an automated daily cut from main: it passes the same
test gates, but its contents haven't been reviewed as a set, so expect rough
edges in exchange for changes landing days earlier.
You don't configure a channel — the build you're running is the channel, so
update checks follow whichever one you installed from, and switching is just
installing from the other. rove update with no arguments never moves you
between channels.
rove updates using whichever package manager owns the rove on your PATH,
so the new version can't land in a shadowed prefix. Manual fallback:
npm install -g @sma1lboy/rove@latest (or @nightly).
The update script is POSIX shell. On Windows, Rove runs it through Git for
Windows' bash — the same shell every engine and terminal tab launches through
— so rove update needs Git for Windows installed, exactly like the rest of
the app. Without it the command says so and points at the manual fallback.
Some versions are marked breaking. Installing across one prints a heads-up
(dry-run included, so the rehearsal shows it too), and the next launch asks
you to run rove reset first. Worktrees are never touched.
Installing new files does not replace running processes. Rove keeps two of
them, and a finished rove update says so:
- The daemon holds the fast-moving code.
rove daemon restartreplaces it and never touches a live session. - The PTY host owns every running engine and terminal. It survives a
daemon restart by design, so only
rove resetreplaces it — at the cost of every live session. A long-lived host can end up serving code from several releases ago.
rove doctor reports both versions and tells you which one is actually stale.
Launching
rove # the TUI (first run: onboarding wizard)
rove . # open a directory as a task, the `code .` gestureA typo never silently opens the TUI: an unknown subcommand prints usage and exits 2.
All commands
rove <version>
Usage: rove [command] [options]
Run with no command to launch PureTUI.
Run `rove .` (or `rove <path>`) to open a directory as a standalone task.
Commands:
completions <shell> Generate shell completion script (bash/zsh/fish)
add [path] Save a repo path for the new-task picker
remove [path] Forget a saved project (inverse of add; non-destructive)
adopt [glob] Import existing git worktrees as tasks
export [--csv|--json] Print the task list (json/csv/table; daemon-free)
repo <verb> Per-repo init script + first prompt (show|set|unset)
api <verb> Scriptable RPC surface for agents (see `rove api --help`)
daemon <verb> Manage the daemon (start|stop|status|restart)
machine <verb> Other computers running Rove (add|remove|list)
weixin <verb> Talk to Rove from WeChat (login|logout|status|allow|deny)
doctor [--report|--fix] Diagnose daemon/PTY/engines/git; --fix walks the remedies
config [--path] Open rove's config file (state.json) in your editor
reset [--hard] Stop runtimes; optionally wipe task/UI state
theme <verb> Manage user themes (list|add|remove)
skill <verb> Install the rove agent skill (install|status|command|print)
plugin <verb> Install and run plugins (install|link|list|action|…)
feedback Send feedback to GitHub Discussions
update [version|channel|list] Self-update rove, switch channel, or list versions
Options:
-v, --version Print version
-h, --help Print this help
--skill Print the agent skill file and exitBare rove version and rove help work too, as spelled-out forms of the
flags.
Managing projects
rove add [path] # save a repo for the new-task picker (defaults to .)
rove remove [path] # forget it; files, worktrees, and tasks all stay
rove remove <ssh://…> --purge-credentials
# also delete that remote project's SSH password from the
# OS keychain (macOS). Off by default — forgetting a
# project never destroys a stored secret on its own.
rove adopt [glob] [--repo <path>] [--vendor <engine>] [--yes]
# list/import existing git worktrees as tasksrove remove accepts --purge-credentials=true, --purge-credentials=1,
and --purge-credentials=yes to purge the credential, just like the bare flag.
--purge-credentials=false, --purge-credentials=0, and
--purge-credentials=no preserve the credential while forgetting the project.
Any other inline value exits with status 2 and displays usage before forgetting
the project or deleting its credential.
rove add needs a real git repo. It creates the project's sidebar row and
folds in any existing unlinked worktrees as tasks.
Point it at a linked worktree and it saves the repository, not the worktree: one repo is one project, whichever of its checkouts you name. The folded-in worktrees never include the repository's own primary checkout — that is the project, not a disposable task.
rove adopt scans the current repo by default; --repo <path> selects another
one and --vendor <engine> chooses the engine recorded on imported tasks. With
no glob it is a dry run that lists what it would import; pass a glob to filter
(rove adopt 'feature-*') and --yes / -y to actually do it.
Remote projects (experimental; enable Settings → Dev → Experimental first) can register an SSH host and create task worktrees there:
rove add --remote --host <host> --user <user> --path <basePath> \
[--port N] [--key [path] | --password]A remote project is identified by host, user, port and --path, so two
repositories on one host are two projects. A project registered before the
base path was part of that identity keeps its original ssh://user@host key.
Auth is either --key (ssh-agent when you omit the path) or --password.
Password auth is macOS-only today: Rove prompts for it and stores only a
reference in state.json; the secret lives in the macOS keychain. Linux and
Windows reject --password, so use a key or ssh-agent there.
This is not remote-execution parity yet. Remote worktree creation is wired, but the current Hosted PTY engine launcher does not wrap the engine command in SSH. A remote-only worktree path therefore cannot be treated like a supported local engine cwd, and engine launch may fail. Files/diffs and repo init also lack full remote parity. Do not use this experiment as a security boundary or assume prompts, engine execution, or repository reads are confined to the SSH host.
completions
source <(rove completions zsh) # generate now (a process per shell)
rove completions zsh --install # hook the pre-generated file into ~/.zshrc
rove completions bash --install # …into ~/.bashrc
rove completions fish --install # ~/.config/fish/completions/rove.fish
rove completions zsh --path # print the shipped script's pathThe script is a build-time constant, so the package ships it:
dist/completions/rove.<shell>. --path prints that file's path and
--install writes source "<path>" into your shell config, which a new shell
reads with no process at all — unlike source <(rove completions zsh), which
starts the CLI (node launcher → bun, ≈0.3s) on every new shell just to print
1.8KB of static text. The first-run wizard writes the file form; the
source <(…) form keeps working, and is the only one available in a source
checkout, where no dist/completions exists (--path then fails instead of
printing a path that is not there).
Completes two levels: the subcommand, then its verb.
rove <TAB> completions add remove adopt export repo api daemon …
rove daemon <TAB> status start stop restart
rove machine <TAB> add remove list
rove theme <TAB> list add remove
rove api routine-<TAB> routine-list routine-create routine-update …api, daemon, machine, plugin, repo, skill and theme carry verbs;
the rest take flags only, and get no second level rather than an invented one.
Flags are never completed — each subcommand owns its own.
Both levels are derived, not transcribed. The api verbs come from the same
registry rove api schema enumerates, and the other six from a table each
command validates its own argv against — so a verb the CLI accepts but the
completion script omits is not a state the two can reach. Regenerate the
script after upgrading Rove to pick up new verbs.
export
rove export [--json | --csv | --format <json|csv|table>] # --format=<fmt> works tooPrints your task list. Read-only and works with the daemon down, which is
what makes it different from rove api list. Columns: id, title, status, vendor, branch, repo, worktreePath. Default is JSON; --format table aligns it for humans.
config
rove config [--path] # `rove config path` works tooOpens ~/.config/rove/state.json in your editor. See
Configuration.
theme
rove theme list # alias: ls
rove theme add <url|path> [--name|-n <name>] [--force|-f]
rove theme remove <name> # alias: rmUser themes land in ~/.rove/themes/ and can shadow a bundled name. Bundled
themes can't be removed. See Themes.
repo
rove repo show [path]
rove repo set [path] [--init-script <text> | --init-script-file <path>]
[--init-prompt <text> | --init-prompt-file <path>]
# at least one of the four
rove repo unset [path] [--init-script] [--init-prompt]Sets a per-user init override for a repo. If the repo commits its own
.rove/init.sh / .rove/init-prompt.md, those win. Path defaults to the current directory. unset with
no flag clears both.
skill
rove skill install [--global|-g | --project|-p] [--agent NAME]…
rove skill status
rove skill command [--global|-g | --project|-p] [--agent NAME]…
# print, don't run
rove skill print # print the SKILL.md itselfInstalls the Rove agent skill, which teaches a coding agent to drive
rove api. Installs are global (user-level) by default: the skill
drives a machine-wide daemon, so one copy per machine keeps one staleness
lifecycle; --project / -p installs into the current project instead.
--global / -g restates the default explicitly. With no --agent it
detects your installed agents and asks. To name them yourself, repeat the flag
(--agent claude-code --agent codex; --agent=codex also works); a
comma-joined list is rejected rather than silently using only the first.
The SKILL.md ships inside the npm package, so no repo clone is needed; the
install itself still runs npx skills add (which falls back to a repo clone
only if the bundled copy is missing).
rove --skill (top-level flag) is shorthand for rove skill print: it dumps
the bundled SKILL.md to stdout so an agent can learn the rove api surface in
one command, e.g. prompt your agent with read `rove --skill` then fan out tasks, no pre-installed skill required.
plugin
rove plugin install <owner/repo[/subdir]> [--yes] [--ref <rev>]
rove plugin link <dir> register a local directory (dev)
rove plugin list installed + linked plugins
rove plugin search [query] browse the marketplace
rove plugin outdated check installs against upstream
rove plugin update <id…> | --all [--yes] reinstall stale plugins
rove plugin enable <id> | disable <id> toggle without unregistering
rove plugin unlink <id> unregister a linked plugin
rove plugin uninstall <id-or-spec> unregister + remove the checkout
rove plugin config-dir <id> print its config directory
rove plugin log <id> [-n <count>] tail its command log (default 20)
rove plugin action list [--plugin <id>]
rove plugin action invoke <plugin-id.action-id> [args…]
rove plugin pane open <plugin-id.pane-id> [--task <task-id>]
rove plugin pane open --plugin <id> --entrypoint <pane-id> # equivalent formChanges apply to a running daemon without a restart. Writing one: Plugin authoring. Marketplace: https://github.com/topics/rove-plugin.
doctor
rove doctor [--report] [--fix] [--kill-orphans]Read-only check of your build (including install integrity and a stale bundled
Bun), terminal, git, engine CLIs and logins, the engine hook channel, daemon,
running sessions, processes left behind by a PTY session that died without
Rove seeing it, leftover pre-v0.8 tmux sessions, node-pty's macOS
spawn-helper exec bit, agent skill, and state files. The terminal section names
TERM/TERM_PROGRAM/COLORTERM, whether you are inside a multiplexer (tmux,
zellij, screen — all three rewrite keys on the way in), and asks the terminal
live whether it speaks the kitty keyboard protocol. That last answer settles
keyboard reports on its own: without the protocol, ctrl+h/ctrl+j arrive as
plain C0 bytes and the two split chords cannot be encoded at all (see
Keybindings). Piped output skips the live probe rather
than writing escape bytes into the pipe. Every registered engine gets a
row — the built-ins plus any engine you added — so a login Rove can't read is
reported as such rather than as a missing account. The plain run never changes
anything. --report also writes a bug bundle (diagnosis + recent logs + env)
and prints its path; attach that to bug reports.
--fix walks the remedies for whatever the diagnosis found, one at a time:
- Safe fixes run after a per-fix
y/N— each shows the exact command before asking (e.g.rove daemon restartfor a stale/dead daemon or a dead hook channel,rove skill installfor a missing/stale agent skill,chmod 755on a node-ptyspawn-helperthat lost its exec bit). Nothing is batched; declining one fix never skips the next prompt. - Risky remedies are printed, never executed — anything that kills live
sessions (
rove reset, closing engine tabs) or needs a human (installing git/Node.js, engine logins) is shown with the step and why doctor won't run it. Without a TTY (--fixin a script), nothing at all is executed.
--kill-orphans ends the process groups listed under orphans: — SIGTERM,
then SIGKILL on whatever survives. It is a separate flag rather than a --fix
entry because the report cannot tell a leak from a process you backgrounded on
purpose from a Rove terminal and then closed the tab on: both are reparented to
init with a dead group leader. Run the plain report, read the list, then pass
the flag. See Troubleshooting.
The remedies mirror Troubleshooting — --fix is that
page's executable half.
reset
rove reset [--hard] [--yes]Recovers a wedged install: stops the daemon and the PTY host (ending all background sessions), and also stops any pre-v0.8 tmux sessions the retired runtime left behind. It also clears the frozen-session store, so the next host comes up empty instead of restoring the scene you just ended. Never touches git worktrees.
--hard additionally deletes two files outright:
~/.rove/tasks.json— the task index.~/.config/rove/state.json— the whole settings filerove configopens, not a UI-only slice of it. That means your saved projects, every custom engine you registered (customEngineIdsand theengineCommand.*/engineName.*entries that define them — this file is the only place they exist), your theme, default engine, language, and the onboarding flag, so the wizard runs again. None of it is recoverable, and the saved-project backfill cannot help because--harddeletes the task index in the same run.
The confirmation prints the real list with counts before anything happens.
Reset asks for it unless --yes (-y). Without a terminal to prompt on,
--yes is required: a non-interactive rove reset without it prints the
plan, changes nothing, and exits 2 rather than reporting success for a run
that did nothing.
daemon
rove daemon status # status JSON; exit 1 when nothing is running
rove daemon start # run in the FOREGROUND (this process becomes it)
rove daemon stop
rove daemon restart # stop, then respawn in the backgroundBare rove daemon defaults to status.
The daemon auto-starts when the TUI or rove api needs it, so start is
mainly for debugging. Logs are at ~/.rove/daemon.log; read them first when
something's wrong.
Working on Rove itself? Restart after editing daemon/orchestrator/engine code — Bun doesn't hot-reload.
rove daemon restartreloads the daemon; an attached TUI is told the code is being swapped and offersctrl+arto reload itself too. Settings → Dev → Restart backend does both in one step. Hosted engine sessions live in the PTY host and survive all of it.
weixin
rove weixin login # show a QR code; scan it in WeChat to bind
rove weixin status # binding, allowed senders, reply windows, undelivered pushes
rove weixin allow <user-id> # let another WeChat user send commands
rove weixin deny <user-id>
rove weixin logout # remove the binding and its credentialsLets you ask for task status, message a task's agent and start tasks from WeChat, and get a message when a task needs you. The running daemon does the messaging. See WeChat.
feedback
rove feedback --title <text> (--body <text> | --body-file <path>) [--category <slug>]Opens a GitHub Discussion via the gh CLI (needs gh auth login).
--body-file - reads from stdin. --category defaults to feedback.
Internal subcommands
Not in --help, listed so they aren't a mystery if you see them:
rove pty-host. The process that owns embedded terminals so they survive TUI exits and daemon restarts. Spawned automatically.rove hook <verb>. Fired by an engine's own hooks to report activity. It always exits 0 and never starts the daemon, so it can't fail your engine. Two verbs are user-facing:rove hook cleanupremoves Rove's settings-managed hooks from~/.claude/settings.jsonafter the Claude Code plugin takes over (see Configuration → Claude Code plugin), androve hook setupis deprecated and now performs cleanup only: it removes the legacyWorktreeCreatesync hook, setsexternalWorktreeSynctooff, prints what it did, and exits 0.
Exit codes
- 0. Success, including "already in that state" (
daemon stopwith no daemon). - 1. Runtime failure:
rove addon a non-repo, no editor found, no daemon fordaemon status, plugin errors. - 2. Bad invocation: unknown command, verb, or flag; missing value. Always comes with usage text.
rove api is the JSON-first surface (JSON on stdout, a JSON error envelope on
stderr). Everything else prints human text. For machine-readable task data
without a daemon, use rove export --json.
Environment variables
Only the ROVE_* environment namespace is supported.
| Variable | What it does |
|---|---|
ROVE_HOME_DIR | Move Rove's home-rooted task/runtime data; platform settings and engine-owned history keep their own locations |
ROVE_OPEN_EDITOR | Command that opens a worktree in a GUI editor (code, cursor, …) |
ROVE_DEV=1 | Mark a developer checkout; hides the update chip |
ROVE_DEBUG=1 | Print full startup errors instead of one line |
ROVE_WEIXIN_BASE_URL | iLink endpoint rove weixin login uses (default https://ilinkai.weixin.qq.com); for testing against a fake server |
ROVE_TASK_ID / ROVE_TAB_ID | Set inside tabs Rove opens; how rove api verbs resolve the calling task |
ROVE_FILETREE_WATCH=0 | Turn off the Files pane's worktree watcher; r becomes the only refresh |
ROVE_RPC_TIMEOUT_MS | Deadline for one daemon RPC (default 20000; 0 or negative waits forever) |
ROVE_CONNECT_TIMEOUT_MS | Deadline for opening a connection to the daemon or PTY host (default 5000; 0 or negative waits forever) |
ROVE_DAEMON_IDLE_GRACE_MS | Grace before a daemon with no attached GUI stops itself (default 3000ms) |
ROVE_HOOK_DEBUG=1 | Print engine-hook failures to stderr instead of swallowing them |
ROVE_OPEN_EDITOR wins over Rove's auto-detection, and it's separate from the
editor.* settings, which pick your TTY editor.
Where state lives
Product data lives under ~/.rove/, or beneath the home selected by ROVE_HOME_DIR:
tasks.json: the task indexworktrees/<repo-key>/<task-slug>/: managed worktreesweixin/: the WeChat binding (credentials, owner-only); see WeChatthemes/,settings/keybindings.yaml, issues, notes, and automations- daemon and PTY sockets, pid files, logs, and plugin data
Settings live in ~/.config/rove/state.json. New hosts create only canonical
runtime names. Until the next minor release, clients can reattach to a live
pre-rename host after the canonical connection fails. The old host retains its
boot-time code and endpoint until it exits or you run rove reset.
State migration never overwrites canonical data or creates legacy links.
After import, non-live source files move to canonical paths or, when those
paths already exist, to .rove/migration-conflicts/. Live runtime files and
existing Git worktrees retain their addresses; cleanup retries at daemon startup.