Rove

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 installing

The 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 restart replaces 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 reset replaces 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 .` gesture

A 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 exit

Bare 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 tasks

rove 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 path

The 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 too

Prints 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 too

Opens ~/.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: rm

User 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 itself

Installs 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 form

Changes 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 restart for a stale/dead daemon or a dead hook channel, rove skill install for a missing/stale agent skill, chmod 755 on a node-pty spawn-helper that 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 (--fix in 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 file rove config opens, not a UI-only slice of it. That means your saved projects, every custom engine you registered (customEngineIds and the engineCommand.* / 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 --hard deletes 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 background

Bare 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 restart reloads the daemon; an attached TUI is told the code is being swapped and offers ctrl+a r to 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 credentials

Lets 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 cleanup removes Rove's settings-managed hooks from ~/.claude/settings.json after the Claude Code plugin takes over (see Configuration → Claude Code plugin), and rove hook setup is deprecated and now performs cleanup only: it removes the legacy WorktreeCreate sync hook, sets externalWorktreeSync to off, prints what it did, and exits 0.

Exit codes

  • 0. Success, including "already in that state" (daemon stop with no daemon).
  • 1. Runtime failure: rove add on a non-repo, no editor found, no daemon for daemon 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.

VariableWhat it does
ROVE_HOME_DIRMove Rove's home-rooted task/runtime data; platform settings and engine-owned history keep their own locations
ROVE_OPEN_EDITORCommand that opens a worktree in a GUI editor (code, cursor, …)
ROVE_DEV=1Mark a developer checkout; hides the update chip
ROVE_DEBUG=1Print full startup errors instead of one line
ROVE_WEIXIN_BASE_URLiLink endpoint rove weixin login uses (default https://ilinkai.weixin.qq.com); for testing against a fake server
ROVE_TASK_ID / ROVE_TAB_IDSet inside tabs Rove opens; how rove api verbs resolve the calling task
ROVE_FILETREE_WATCH=0Turn off the Files pane's worktree watcher; r becomes the only refresh
ROVE_RPC_TIMEOUT_MSDeadline for one daemon RPC (default 20000; 0 or negative waits forever)
ROVE_CONNECT_TIMEOUT_MSDeadline for opening a connection to the daemon or PTY host (default 5000; 0 or negative waits forever)
ROVE_DAEMON_IDLE_GRACE_MSGrace before a daemon with no attached GUI stops itself (default 3000ms)
ROVE_HOOK_DEBUG=1Print 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 index
  • worktrees/<repo-key>/<task-slug>/: managed worktrees
  • weixin/: the WeChat binding (credentials, owner-only); see WeChat
  • themes/, 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.

On this page