CLI reference
The ctrlrelay console script is a Typer app
defined at
src/ctrlrelay/cli.py.
This page is generated by hand from that source — when in doubt, read the file.
Run any command with no arguments (or --help) to see Typer’s auto-generated
help.
ctrlrelay [--version] <command> ...
| Top-level | Purpose |
|---|---|
ctrlrelay --version / -v |
Print the package version and exit. |
ctrlrelay version |
Same, as a subcommand. |
ctrlrelay status |
Show active locks and the 5 most-recent sessions. |
ctrlrelay config ... |
Configuration helpers. |
ctrlrelay skills ... |
Skill discovery / audit. |
ctrlrelay bridge ... |
Manage the Telegram bridge daemon. |
ctrlrelay run ... |
Run a pipeline interactively. |
ctrlrelay ci ... |
CI helpers used by the dev pipeline prompt. |
ctrlrelay poller ... |
Manage the issue-poller daemon. |
Note: the
repos,export,import,team-export,team-import,setup,manifest,codex-export,codex-import, andcodex-installcommands are shell-script subcommands of the legacy./syncwrapper, not the PythonctrlrelayCLI. See the README for those.
Every command that reads config accepts --config / -c to point at a
non-default orchestrator.yaml (default: config/orchestrator.yaml).
ctrlrelay setup
ctrlrelay setup [OPTIONS]
First-run onboarding: detect GitHub orgs, enumerate their non-fork
non-archived repos, write a fresh ~/.config/ctrlrelay/orchestrator.yaml,
clone every repo to ~/Projects/<owner.lower()>/<repo>, and (optionally)
configure the personalization sync block plus install launchd/systemd unit
files. Composes the building blocks operators previously had to wire by hand.
The interactive flow asks one question at a time. --yes skips prompts and
accepts every default. Repeat --owner to lock the owner list non-interactively.
Key flags:
| Flag | Default | Notes |
|---|---|---|
--owner OWNER |
(interactive prompt) | Repeatable. When omitted, prompts to pick from accessible owners. |
--repo-root PATH |
~/Projects |
Where repos land — ~/Projects/<owner.lower()>/<repo>. |
--config-out PATH |
~/.config/ctrlrelay/orchestrator.yaml |
Where to write the generated config. |
--timezone TZ |
UTC |
IANA zone for cron schedules. |
--transport TRANSPORT |
file_mock |
telegram or file_mock. |
--telegram-chat-id ID |
none | Required for --transport=telegram. |
--personalization-repo OWNER/REPO |
none | Adds a personalization block. |
--no-personalization |
off | Skip the personalization prompt entirely. |
--install-daemons |
(prompted) | Render and write launchd/systemd unit files. Reads $CTRLRELAY_TELEGRAM_TOKEN so the rendered plist isn’t a placeholder. |
--skip-archived/--include-archived |
skip | gh repo list filter. |
--skip-forks/--include-forks |
skip | gh repo list filter. |
--yes / -y |
off | Accept every default; never prompt. |
--force |
off | Overwrite an existing orchestrator.yaml (and existing daemon plists when --install-daemons). |
Refuses to overwrite an existing config or daemon plist without --force.
Refuses to proceed if gh auth status fails — run gh auth login first.
After setup completes, the next manual steps are:
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.ctrlrelay.ctrlrelay-poller.plist
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.ctrlrelay.ctrlrelay-bridge.plist
(Or the systemd equivalents on Linux.)
ctrlrelay config
config validate
ctrlrelay config validate [-c PATH]
Loads config/orchestrator.yaml, validates the pydantic schema, prints the
parsed node_id, timezone, transport type, and repo count. Exits non-zero on
validation errors.
config repos
ctrlrelay config repos [-c PATH]
Lists configured repositories in a table (name, local path, deploy provider).
ctrlrelay skills
skills audit
ctrlrelay skills audit [-p SKILLS_DIR] [-c PATH]
Audits each skill under paths.skills (or --path) for orchestrator
readiness — checks shape, required metadata, common pitfalls. Exits non-zero if
any skill fails.
skills list
ctrlrelay skills list [-p SKILLS_DIR] [-c PATH]
Lists discovered skills as a table (name, path).
ctrlrelay bridge
See Telegram bridge for the full setup walkthrough.
bridge start
ctrlrelay bridge start [-c PATH] [-F|--foreground]
Starts the bridge listening on transport.telegram.socket_path. Reads the
bot token from the env var named in transport.telegram.bot_token_env
(default CTRLRELAY_TELEGRAM_TOKEN). Daemonizes by default — forks a
detached process, writes a PID file alongside the socket, and returns to
the shell. Pass --foreground / -F under launchd/systemd or when
debugging interactively; the process runs in the foreground and still
writes its PID file so ctrlrelay bridge status works.
Fails if transport.type is not telegram, the token env var is unset, or a
PID file already exists for a live process.
bridge stop
ctrlrelay bridge stop [-c PATH]
Reads the PID file and sends SIGTERM.
bridge status
ctrlrelay bridge status [-c PATH]
Reports running / not-running by consulting the PID file. If the PID file is
missing but the socket exists (e.g. the bridge was started by an older
launchd plist that pre-dates the PID-file change), exits non-zero with a
hint to restart the bridge — a restart on the new version will regenerate
the PID file and let status work.
bridge test
ctrlrelay bridge test [-m "MESSAGE"] [-c PATH]
Connects to the bridge socket as a client and sends a one-off message. Useful for confirming end-to-end delivery to your Telegram chat without waiting for a real pipeline run.
ctrlrelay run
run dev
ctrlrelay run dev -i ISSUE [-r REPO] [-c PATH]
| Flag | Required | Default | Description |
|---|---|---|---|
--issue / -i |
yes | — | GitHub issue number. |
--repo / -r |
when more than one repo is configured | — | Limit to a single owner/repo. |
--config / -c |
no | config/orchestrator.yaml |
Config path. |
Acquires the per-repo lock, creates the worktree, spawns Claude with the
issue’s title/body in the prompt, and reports the result. If Claude blocks and
the Telegram transport is configured, posts the question to your chat and
waits for a reply (up to DEFAULT_MAX_BLOCKED_ROUNDS rounds).
run secops
ctrlrelay run secops [-r REPO] [-c PATH]
Runs the secops pipeline across configured repos (or one repo with --repo).
Each repo’s run is serialised by the per-repo lock; the overall sweep runs in
parallel across repos. Reports per-repo success/failure and exits non-zero if
any repo failed.
ctrlrelay ci
ci wait
ctrlrelay ci wait --pr N --repo OWNER/REPO [--timeout 600] [--interval 15]
| Flag | Default | Description |
|---|---|---|
--pr / -p |
— (required) | PR number to wait on. |
--repo / -r |
— (required) | Repository as owner/name. |
--timeout / -t |
600 |
Hard timeout in seconds. |
--interval / -i |
15 |
Seconds between polls. |
Polls gh pr checks until every check has left the pending bucket (or the
hard timeout is hit), then exits with:
- 0 — all checks passed (or the repo has no CI configured)
- 1 — at least one check failed / cancelled / timed_out
- 2 — hard timeout while checks were still pending
Exists specifically so the dev pipeline’s prompt can point Claude at one
correct command instead of asking it to improvise a bash until / while
loop — every attempt at improvising those has been inverted-semantics or
pipe-swallowed the exit code (see issue #85).
ctrlrelay poller
poller start
ctrlrelay poller start [-c PATH] [-F|--foreground] [-i SECONDS]
| Flag | Default | Description |
|---|---|---|
--interval / -i |
300 |
Seconds between polls. |
--foreground / -F |
off | Run in the foreground. Default is to daemonize (fork + return to shell). Pass this under launchd/systemd. |
--config / -c |
config/orchestrator.yaml |
Config path. |
Polls each configured repo for issues assigned to your GitHub user (resolved
via gh api user --jq .login). On the first run, seeds the seen-issue set
with current assignments so it does not replay your existing backlog.
For each newly assigned issue, runs run_dev_issue(...) (i.e. the dev pipeline)
in-process. If transport.type: telegram is configured and the bridge socket
exists, sends notifications: 🔔 New issue on detection, ⏸️ Blocked on
question, ✅ PR ready on success, ❌ Failed otherwise.
State is persisted to <state_db_dir>/poller_state.json.
poller stop
ctrlrelay poller stop [-c PATH]
Sends SIGTERM to the daemon PID.
poller status
ctrlrelay poller status [-c PATH]
Reports running / not-running.
ctrlrelay personalization
See Personalization sync
for the conceptual walkthrough. All subcommands require a
personalization: block in orchestrator.yaml and operate on the
checkout at personalization.checkout_path (default
~/.ctrlrelay/personalization).
personalization init
ctrlrelay personalization init [-c PATH] [--no-adopt]
Clones the personalization repo into checkout_path, creates the
per-machine working branch (personalization/<node_id>), and lays
down the symlinks declared in personalization.paths.
By default, adopt-flow is on: a target that exists as a real file
or directory while its corresponding source slot in the repo is empty
is moved into the repo and replaced with a symlink. Your existing
content is preserved and immediately under sync. Run personalization
push after init to send the adopted content to GitHub.
Pass --no-adopt to opt out of adoption — pre-existing real targets
surface as skipped-real-file-at-target instead, and you back them up
- remove them manually before re-running.
If both the repo source and the on-disk target have real content,
init refuses with skipped-conflict-both-exist regardless of
--no-adopt. Reconcile manually before re-running.
personalization status
ctrlrelay personalization status [-c PATH]
Prints the working branch, repo URL, ahead/behind counts vs. origin,
and per-symlink state — correct, wrong-target, missing,
source-missing, etc. Read-only; doesn’t touch the filesystem.
personalization push
ctrlrelay personalization push [-c PATH] -m "MESSAGE"
Stages the entries declared in paths (allowlist — random files in
the checkout are not committed), commits with MESSAGE, rebases the
per-machine branch onto origin/<main_branch>, and pushes. Retries
the fast-forward of origin/main up to three times if another
machine’s push lands between fetch and push. Uses
--force-with-lease on the per-machine branch only when the local
working branch has diverged from the remote (after a rebase) — never
on main.
Conflicts during the rebase abort and list the unmerged files for you to resolve.
personalization pull
ctrlrelay personalization pull [-c PATH]
Fetches, rebases the per-machine branch onto origin/<main_branch>,
fast-forwards local main if it’s an ancestor of the remote, and
re-wires symlinks (the config-as-code shipped in the repo may have
changed). Uses adopt=True like init so newly-declared paths can
adopt local content during a pull. Conflicts abort cleanly and list
the unmerged files.
The daemon-driven auto-pull (under
schedules.personalization_cron)
calls the same code with two extra rails: skip when the working tree
is dirty, and re-wire with adopt=False so a background sync never
silently moves files.
ctrlrelay status
ctrlrelay status [-c PATH]
Opens the SQLite state DB at paths.state_db and prints:
- Active locks —
(repo → session_id)for each held repo lock. - Recent sessions — the 5 most-recent rows from the
sessionstable (id,pipeline,repo,status).
status values: running, done, blocked, failed.
If the DB doesn’t exist yet, prints a hint to run a pipeline first.
ctrlrelay version
ctrlrelay version
Prints the package version (same as ctrlrelay --version).