CLI reference
Every zeno subcommand with its flag table.
The zeno CLI is the operator's entry point for daily ops. It talks directly to the Docker socket via dockerode and stores profile metadata in ~/.zeno/state.db.
The flag tables below are generated at docs build time from the citty args schemas in apps/cli/src/commands/<cmd>.ts — when a flag is added or renamed, the next zeno-docs deploy carries it without anyone touching this page.
Profile management
zeno profile create
Creates a new profile, allocates a host port, generates a master key, and scaffolds ~/.zeno/profiles/<name>/AGENTS.md and .env from the templates under templates/profile/.
zeno profile create personal| Flag | Type | Default | Description |
|---|---|---|---|
<profile> | positional | — | profile identifier (lowercase kebab-case, e.g. "personal |
--port | string | — | |
--quiet | boolean | — |
zeno profile list
Inventory of every profile in state.db, with live container status and dashboard port.
zeno profile list| Flag | Type | Default | Description |
|---|---|---|---|
--json | boolean | — | |
--quiet | boolean | — |
zeno profile show
Full detail block for a single profile.
zeno profile show personal| Flag | Type | Default | Description |
|---|---|---|---|
<profile> | positional | — | profile identifier |
--json | boolean | — | |
--quiet | boolean | — |
zeno profile edit
Move a profile's host port. Useful when a port is already taken.
zeno profile edit personal --port 6105| Flag | Type | Default | Description |
|---|---|---|---|
<profile> | positional | — | profile identifier |
--port | string | — | |
--quiet | boolean | — |
zeno profile use
Set the sticky profile that subcommands act on when no profile argument is given.
zeno profile use personalOmitting the positional in TTY opens a picker over every profile in state.db (the current sticky is marked with *). In non-TTY without an argument it exits 1 with usage: zeno profile use <name>.
| Flag | Type | Default | Description |
|---|---|---|---|
<name> | positional | — | profile identifier |
--clear | boolean | — | |
--quiet | boolean | — |
zeno profile delete
Remove a profile: container, both volumes, the ~/.zeno/profiles/<name>/ directory, and the state.db row. In TTY, prompts delete profile '<name>'? this destroys volumes and data. (y/N); pass --yes to skip. In non-TTY without --yes, exits 1 with error: destructive operation requires --yes in non-interactive mode.
zeno profile delete personal # interactive prompt
zeno profile delete personal --yes # skip prompt (scripted)| Flag | Type | Default | Description |
|---|---|---|---|
<profile> | positional | — | profile identifier |
--yes | boolean | — | |
--quiet | boolean | — |
zeno profile
The umbrella subcommand. Running it with no further arg prints the available verbs.
This subcommand takes no flags.
Lifecycle
zeno start, stop, restart, logs, and open resolve their target profile in this order: explicit positional → sticky (zeno profile use) → picker over running containers (TTY only) → exit 1 with no profile specified. use --profile <name> (non-TTY). Passing --all skips the picker and acts on every profile.
zeno start
Start a profile container. Auto-builds the zeno-agent:dev image if it's missing.
zeno start personal
zeno start --all
zeno start personal --build| Flag | Type | Default | Description |
|---|---|---|---|
<profile> | positional | — | profile identifier (omit for sticky) |
--all | boolean | — | |
--build | boolean | — | force rebuild of zeno-agent:dev image before start |
--quiet | boolean | — |
zeno stop
Stop a profile container without removing it.
zeno stop personal
zeno stop --all| Flag | Type | Default | Description |
|---|---|---|---|
<profile> | positional | — | profile identifier (omit for sticky) |
--all | boolean | — | |
--quiet | boolean | — |
zeno restart
Stop + start. Cheapest way to pick up an env change.
zeno restart personal| Flag | Type | Default | Description |
|---|---|---|---|
<profile> | positional | — | profile identifier (omit for sticky) |
--all | boolean | — | |
--build | boolean | — | |
--quiet | boolean | — |
Inspect
zeno status
One-screen overview of every profile: container state (running / stopped / error), connector count, last cron run, and last error. Resolves the inventory from state.db and then fans out one HTTP call per running profile to its worker API in parallel; stopped profiles skip the HTTP calls and render stopped.
zeno status
zeno status --json
zeno status --quietThe per-profile HTTP timeout is 1 second. When a call times out, the affected field renders ? rather than blocking the table. zeno status is a humanly-driven status check, not a real-time monitor — for production observability hook the worker's /api/health directly.
--json emits an array of objects, one per profile, with the schema documented in apps/cli/src/types/json-output.ts (see StatusJson):
[
{
"name": "personal",
"port": 6101,
"state": "running",
"uptimeMs": 132040,
"connectorCount": 4,
"lastCron": null,
"lastError": null
}
]Fields:
name— profile name as registered instate.db.port— host-side dashboard port (127.0.0.1:<port>).state— container state.running,stopped,restarting,error, etc.uptimeMs— milliseconds since the container last started;0when not running.connectorCount— count fromGET /api/connectors.nullwhen the profile is stopped or the call timed out (rendered as?in the table).lastCron— most recent entry fromGET /api/crons/runs?limit=1, ornull.lastError— most recent entry fromGET /api/logs?level=error&limit=1, ornull.
| Flag | Type | Default | Description |
|---|---|---|---|
--json | boolean | — | |
--quiet | boolean | — |
zeno logs
Tail container logs. SIGINT (Ctrl-C) to abort.
zeno logs personal --tail 100| Flag | Type | Default | Description |
|---|---|---|---|
<profile> | positional | — | profile identifier (omit for sticky) |
--tail | string | — | |
--quiet | boolean | — |
zeno open
Open the profile's dashboard in the system browser.
zeno open personal| Flag | Type | Default | Description |
|---|---|---|---|
<profile> | positional | — | profile identifier (omit for sticky) |
--quiet | boolean | — |
zeno doctor
Preflight diagnostics: docker reachable, drift between state.db and the running Docker world, etc. Reports findings; does not heal.
zeno doctor| Flag | Type | Default | Description |
|---|---|---|---|
--quiet | boolean | — |
Updates
zeno upgrade
Switch the local clone to a different release tag, branch, or PR. With no flags in TTY, drops into an arrow-key picker over the available releases plus an unstable row (highlighted, separated by a horizontal rule) at the bottom. The picker cursor lands on the latest stable release; the row matching the currently-installed ref is marked with *. In non-TTY without flags, resolves the same fallback chain as install.sh: latest stable → most recent prerelease → main HEAD.
zeno upgrade
zeno upgrade --list
zeno upgrade --to v2026.5.7-7Target flags
--to, --unstable, --branch, --pr, --latest, --prerelease are mutually exclusive — passing more than one prints an error and exits 1.
zeno upgrade --unstable # main HEAD; explicit confirmation in TTY, --yes in non-TTY
zeno upgrade --branch feat/foo --yes # arbitrary branch (testing)
zeno upgrade --pr 123 --yes # pull request branch via `gh pr checkout`--unstable, --branch, and --pr are flagged as risky targets: in TTY they prompt <kind> target may break. continue? (y/N); in non-TTY they exit 1 unless --yes is passed.
Pipeline & safety flags
zeno upgrade --branch feat/foo --dry-run # print resolved target + 7 pipeline steps; exit 0 without touching anything
zeno upgrade --to v2026.5.7-7 --yes # skip the (y/N) prompt for risky targets
zeno upgrade --list --limit 10 # paginate the release listing (default 30)
zeno upgrade --notes v2026.5.7-7 # print release notes via `gh release view` and exitThe pipeline runs seven enumerated steps (fetchTags, checkoutRef, setVersion, writeMeta, installDeps, buildCli, buildImage). On failure of any post-checkout step the upgrade auto-reverts: re-checks out the previous ref, restores state.db + .installed-from, prints ✓ reverted to <prev>, and exits 1.
zeno --version reads .installed-from and renders the active ref accordingly: v2026.5.7, branch:feat/foo (a1b2c3d), pr:#123 (a1b2c3d), or unstable (a1b2c3d).
| Flag | Type | Default | Description |
|---|---|---|---|
--to | string | — | |
--unstable | boolean | — | |
--branch | string | — | |
--pr | string | — | |
--prerelease | boolean | — | |
--latest | boolean | — | jump straight to latest stable (skips the picker) |
--list | boolean | — | |
--notes | string | — | |
--force | boolean | — | |
--dryRun | boolean | — | |
--yes | boolean | — | |
--limit | string | — | pagination limit for --list / picker (default 30) |
--quiet | boolean | — |
Connector management
zeno connector is the only path to mutate connector configuration. The dashboard at /connectors is read-only by default — every action button there opens a modal showing the equivalent zeno connector command, which you copy and run here. The dashboard reads GET /api/mode once at load to know which UI variant to render.
The behaviour is controlled by the ZENO_API_WRITES env var on the worker container (default cli). To re-enable dashboard mutations temporarily, set ZENO_API_WRITES=dashboard in ~/.zeno/profiles/<name>/.env and restart the profile. Even when set to cli, the CLI itself bypasses the gate by sending a X-Zeno-Origin: cli header — trust-based, since the API binds 127.0.0.1 on a single-user host.
The CLI talks to the profile's API over HTTP (http://127.0.0.1:<port>). All mutations are queued through a commands table; the CLI polls /api/commands/:correlationId until the worker reports terminal status (success or failed). Synchronous endpoints (/test, /secrets/:key/reveal) skip the queue.
Picker fallback
Every zeno connector * command resolves its profile through the same chain as the lifecycle commands: explicit --profile flag → sticky → picker (TTY) → exit 1 (non-TTY). When the profile picker opens (no sticky, ≥2 profiles, no --quiet), the command also emits tip: zeno profile use <name> → skip picker next time after the selection.
The same pattern applies to other positional arguments — omitting the connector slug, catalog id, secret key, tool name, or permission level in TTY opens a picker over the relevant API result; in non-TTY it exits 1 with a clear error. Every command still accepts the flag/positional directly, in which case the picker is skipped.
zeno connector list
Lists every installed connector. Multi-instance plain catalogs (e.g. Linear with several workspaces) collapse into a connector_group heading with drill rows; single-instance rows render flat. App-pattern catalogs (github-app) appear as app:<catalog-id> with their installations nested.
zeno connector list
zeno connector list --profile work
zeno connector list --json| Flag | Type | Default | Description |
|---|---|---|---|
--profile | string | — | profile name |
--json | boolean | false | emit raw JSON |
--quiet | boolean | — |
zeno connector show
Read a single connector's full record (status, secrets masked, tools with permissions, recent activity).
zeno connector show linear-acme
zeno connector show 9a5ee0d1-5a5d-4353-b2d2-81b09f3a4255Both slug and UUID are accepted on every dynamic :id route.
| Flag | Type | Default | Description |
|---|---|---|---|
<target> | positional | — | slug or id |
--profile | string | — | profile name |
--json | boolean | false | emit raw JSON |
--quiet | boolean | — |
zeno connector catalog
Lists the curated catalog. Each entry shows whether it's currently installed and (via --json) whether it's multiInstance: false.
zeno connector catalog
zeno connector catalog --jsonThis subcommand takes no flags.
zeno connector install
Install a catalog connector. Pass --label to give the installation a human-readable name (used as instance_label in the DB; the slug is derived from the catalog id plus a kebab-cased label, e.g. linear-acme). Required secrets can come from --secret KEY=VALUE flags or be prompted interactively.
zeno connector install linear --label "Acme workspace" --secret '__MCP_AUTHORIZATION__=lin_api_xxx'
zeno connector install sentry --secret SENTRY_ACCESS_TOKEN=sntryu_xxxA second install of a multiInstance: false catalog returns 409 single_instance_catalog_already_installed.
| Flag | Type | Default | Description |
|---|---|---|---|
<catalogId> | positional | — | catalog entry id (e.g. "linear |
--label | string | — | instance label (operator-supplied; required when installing a second instance of the same catalog) |
--profile | string | — | profile name |
--secret | string | — | secret to set (repeatable, e.g. --secret LINEAR_API_KEY=xyz) |
zeno connector enable / zeno connector disable
Toggle a connector's status between enabled and disabled. No-op when the connector is already in the requested state.
zeno connector enable linear-acme
zeno connector disable linear-acme| Flag | Type | Default | Description |
|---|---|---|---|
<target> | positional | — | slug or id |
--profile | string | — | profile name |
--quiet | boolean | — |
| Flag | Type | Default | Description |
|---|---|---|---|
<target> | positional | — | slug or id |
--profile | string | — | profile name |
--quiet | boolean | — |
zeno connector uninstall
Permanently remove a connector. In TTY, prompts uninstall connector '<slug>'? (y/N); pass --yes to skip. In non-TTY without --yes, exits 1 with error: destructive operation requires --yes in non-interactive mode. The dashboard's destructive <CommandModal> always pre-fills --yes.
zeno connector uninstall linear-acme # interactive prompt
zeno connector uninstall linear-acme --yes # skip prompt (scripted)| Flag | Type | Default | Description |
|---|---|---|---|
<target> | positional | — | slug or id |
--profile | string | — | profile name |
--yes | boolean | false | confirm destructive uninstall |
--quiet | boolean | — |
zeno connector test
Probe the MCP server with the configured secrets. Returns passed · N tools · Xms or surfaces the upstream error (e.g. auth_failed, timeout).
zeno connector test linear-acme| Flag | Type | Default | Description |
|---|---|---|---|
<target> | positional | — | slug or id |
--profile | string | — | profile name |
--quiet | boolean | — |
zeno connector refresh-tools
Re-discover the connector's tool list. Updates connector_tool_permissions rows; never alters existing per-tool permission overrides (only adds new tools as defaultPermission).
zeno connector refresh-tools linear-acme| Flag | Type | Default | Description |
|---|---|---|---|
<target> | positional | — | slug or id |
--profile | string | — | profile name |
--quiet | boolean | — |
zeno connector tool
Inspect or change per-tool permissions. Three subcommands.
This subcommand takes no flags.
zeno connector tool list
zeno connector tool list linear-acme| Flag | Type | Default | Description |
|---|---|---|---|
<target> | positional | — | slug or id |
--profile | string | — | profile name |
--json | boolean | — | |
--quiet | boolean | — |
zeno connector tool set
Set the permission for one tool.
zeno connector tool set linear-acme create_issue ask
zeno connector tool set linear-acme delete_issue neverPermissions: always_allow, ask, never. The connector-permission guardrail in the worker reads these on every tool call.
| Flag | Type | Default | Description |
|---|---|---|---|
<target> | positional | — | slug or id |
<tool> | positional | — | tool name |
<permission> | positional | — | always_allow |
--profile | string | — | profile name |
--quiet | boolean | — |
zeno connector tool bulk
Set the permission for every tool in a category. Categories: read, write, interactive.
zeno connector tool bulk linear-acme --category read --permission always_allow
zeno connector tool bulk linear-acme --category write --permission askThis subcommand takes no flags.
zeno connector secret
Inspect or update a connector's encrypted secrets. Values are encrypted with the profile's master key (AES-256-GCM) at rest; reveals are rate-limited (5 reveals per key per minute) and audit-logged.
This subcommand takes no flags.
zeno connector secret list
Masked listing.
zeno connector secret list linear-acme| Flag | Type | Default | Description |
|---|---|---|---|
<target> | positional | — | slug or id |
--profile | string | — | profile name |
--json | boolean | — | |
--quiet | boolean | — |
zeno connector secret set
Update a single secret. The CLI prompts for the value with no echo (TTY only); pipe-friendly fallback echoes the prompt.
zeno connector secret set linear-acme __MCP_AUTHORIZATION__| Flag | Type | Default | Description |
|---|---|---|---|
<target> | positional | — | slug or id |
<key> | positional | — | secret key |
--profile | string | — | profile name |
--quiet | boolean | — |
zeno connector secret rotate
Walk every required secret declared in the catalog entry, prompt for each value, PATCH them all atomically. Catalog connectors only — custom rows error with rotate not supported for custom connectors; use 'secret set' per key.
zeno connector secret rotate linear-acme| Flag | Type | Default | Description |
|---|---|---|---|
<target> | positional | — | slug or id |
--profile | string | — | profile name |
--quiet | boolean | — |
zeno connector secret reveal
Print the plaintext value of one secret. Returns 429 retryAfter when the rate limit fires (5 per key per minute).
zeno connector secret reveal linear-acme __MCP_AUTHORIZATION__| Flag | Type | Default | Description |
|---|---|---|---|
<target> | positional | — | slug or id |
<key> | positional | — | secret key |
--profile | string | — | profile name |
--quiet | boolean | — |
zeno connector app
Manage app-pattern catalogs (github-app today; future apps share the same shape — one App entity with N instances underneath). Three top-level verbs.
This subcommand takes no flags.
zeno connector app install
Install the App entity. Accepts the App ID and a path to the PEM private key. Validates by signing a JWT and calling GitHub's GET /app before persisting.
zeno connector app install --catalog github-app --app-id 123456 --pem-file ~/keys/zeno-agent-app.pemA second install of the same App catalog returns 409 app_already_installed — uninstall before reinstalling.
This subcommand takes no flags.
zeno connector app instances
Manage the instances under the App. Two subcommands. The catalog declares display vocabulary via terminology.instance (e.g. github-app calls them "Installation"); the CLI uses that label when rendering.
This subcommand takes no flags.
zeno connector app instances discover
Calls GitHub's GET /app/installations and prints a table: instance id, account name, account type (Organization/User), repo count, whether the instance is already wired into Zeno.
zeno connector app instances discover| Flag | Type | Default | Description |
|---|---|---|---|
--profile | string | — | profile name |
--json | boolean | — | |
--quiet | boolean | — |
zeno connector app instances add
Wire one instance into Zeno. Creates a connector row with slug github-app-<kebab-label> that inherits the App's PEM at runtime.
zeno connector app instances add --instance-id 125887887 --label "FNLivros"This subcommand takes no flags.
zeno connector app uninstall
Tear down the App entity and cascade-delete every installation row underneath. In TTY, prompts uninstall app '<App Name>'? this cascades to N installations. (y/N); pass --yes to skip. In non-TTY without --yes it exits 1 with error: destructive operation requires --yes in non-interactive mode.
zeno connector app uninstall # interactive prompt
zeno connector app uninstall --yes # skip prompt (scripted)| Flag | Type | Default | Description |
|---|---|---|---|
--profile | string | — | profile name |
--yes | boolean | — | |
--quiet | boolean | — |
zeno connector (parent)
The umbrella verb. Running zeno connector --help lists every subcommand above.
This subcommand takes no flags.
Channel management
zeno channel is the only path to mutate channel configuration. Like connectors, the dashboard /channels page is read-only by default — every action chip there opens a modal showing the equivalent zeno channel command, which you copy and run here. The dashboard reads GET /api/mode once at load to know which UI variant to render.
The behaviour is controlled by the same ZENO_API_WRITES env var on the worker container (default cli) that gates connectors. The CLI bypasses the gate by sending an X-Zeno-Origin: cli header on every request. Channels share storage with connectors (connectors table with kind='channel'); the install wire is POST /api/connectors with kind: 'channel' and channel-shaped routes (PATCH /api/channels/:slug/secrets, DELETE /api/channels/:slug, POST /api/channels/:slug/test) live under /api/channels.
Channels are single-instance — at most one Slack workspace per profile, one Discord guild, etc. Multi-instance is reserved for connectors. The catalog file at agent/channels-catalog.json declares each channel's fields[] with required and public flags; required private fields prompt for hidden input, public fields render unmasked and configure via typed CLI flags.
Hot-reload is owned by the worker's ChannelManager, which polls the runtime DB every 2 s. After rotate or configure, the running adapter stops and restarts with the new tokens within one poll tick — no zeno restart needed.
zeno channel list
List every channel installed in this profile. Status comes from the runtime: connected (last test passed, no error) or disconnected (last event surfaced an error).
zeno channel list
zeno channel list --profile work
zeno channel list --json| Flag | Type | Default | Description |
|---|---|---|---|
--profile | string | — | profile name |
--json | boolean | false | emit JSON |
--quiet | boolean | — |
zeno channel show
Read a single channel's full record. Private fields render with …<last4>; public fields (e.g. dm_owner_user_id) render unmasked.
zeno channel show slack
zeno channel show <uuid>Both slug and UUID are accepted.
| Flag | Type | Default | Description |
|---|---|---|---|
<slug> | positional | — | channel slug or id |
--profile | string | — | profile name |
--json | boolean | false | emit JSON |
--quiet | boolean | — |
zeno channel install
Install a channel from the catalog. In TTY without a positional, a picker opens over the catalog (today: slack only — the picker still appears for forward-compatibility with future channels). In non-TTY without a positional, exits 1 with usage: zeno channel install <type>.
zeno channel install # TTY: picker
zeno channel install slack # positional: skip picker, prompt secrets
zeno channel install slack \
--secret 'SLACK_APP_TOKEN=xapp-…' \
--secret 'SLACK_BOT_TOKEN=xoxb-…' # scripted: no promptsA second install of an already-installed channel exits 0 with <type> already installed on stderr.
| Flag | Type | Default | Description |
|---|---|---|---|
<type> | positional | — | channel type (slack, ...) |
--profile | string | — | profile name |
--secret | string | — | KEY=VALUE — can repeat for multiple |
--quiet | boolean | — |
zeno channel configure
Update public (non-secret) config fields. For Slack today: --dm-owner-user-id <Uxxx> restricts DMs to a single Slack user.
zeno channel configure slack --dm-owner-user-id U01H1A2B3The handler reads the catalog at request time and stores the value as connector_secrets with is_public=1. Calling configure for a channel that has no public fields exits 1 with no public config fields supplied.
| Flag | Type | Default | Description |
|---|---|---|---|
<slug> | positional | — | channel slug |
--profile | string | — | profile name |
zeno channel test
Run the catalog-declared probe strategy synchronously. For Slack, the strategy is slack_auth_test — a direct call to https://slack.com/api/auth.test with the stored bot token.
zeno channel test slackReturns passed · Xms and exits 0 on success; prints failed · <error> to stderr and exits 1 on failure. Possible error codes: auth_failed, timeout (5 s wall clock), network, not_implemented. The handler writes lastVerifiedAt on pass and lastError + lastErrorAt on fail, which the dashboard surfaces in the CH3 disconnected banner.
| Flag | Type | Default | Description |
|---|---|---|---|
<slug> | positional | — | channel slug |
--profile | string | — | profile name |
--json | boolean | false | emit JSON |
--quiet | boolean | — |
zeno channel rotate
Walk every required private field in the catalog, prompt each via hidden input, submit an atomic merge PATCH, then immediately call test so the operator sees the result in the same command.
zeno channel rotate slack
# prompt: App Token (SLACK_APP_TOKEN)
# prompt: Bot Token (SLACK_BOT_TOKEN)In non-TTY, every required key must come via --secret KEY=VALUE. Missing required key → exits 1.
| Flag | Type | Default | Description |
|---|---|---|---|
<slug> | positional | — | channel slug |
--profile | string | — | profile name |
--secret | string | — | KEY=VALUE — can repeat for non-interactive |
--quiet | boolean | — |
zeno channel uninstall
Permanently remove a channel. FK CASCADE drops every secret row in the same transaction. The worker's ChannelManager picks up the row deletion on the next poll tick and stops the adapter automatically.
zeno channel uninstall slack # TTY: prompts to confirm
zeno channel uninstall slack --yes # scriptedIn non-TTY without --yes, exits 1 with error: destructive operation requires --yes in non-interactive mode.
| Flag | Type | Default | Description |
|---|---|---|---|
<slug> | positional | — | channel slug |
--profile | string | — | profile name |
--yes | boolean | false | skip confirmation |
--quiet | boolean | — |
zeno channel (parent)
The umbrella verb. Running zeno channel --help lists every subcommand above.
This subcommand takes no flags.
Misc
zeno repo
Print the canonical repo path. Useful in shell scripts that need to cd into the source tree.
cd "$(zeno repo)"| Flag | Type | Default | Description |
|---|---|---|---|
--quiet | boolean | — |
Cron management
zeno cron is the only path to manage crons. Each cron is a folder under ~/.zeno/profiles/<name>/crons/<slug>/ with a CRON.md file (YAML frontmatter + markdown body). The body is the prompt that fires on the schedule declared in the frontmatter. The worker's CronManager polls the folder every 2 s and reconciles a slim DB cache; only the agent session id and run history live in SQLite. The dashboard /crons page reads that cache and surfaces every action through a <CommandModal> showing the corresponding zeno cron … command.
Mutation verbs (create, enable, disable, delete) operate on the filesystem directly — no HTTP call, no ZENO_API_WRITES gate. The single HTTP verb is test, which fires the cron synchronously and is gated like every other CLI mutation (the dashboard never calls it).
Slugs match ^[a-z][a-z0-9-]*$ (lowercase, kebab-case, ≤ 63 chars). Reserved names: anything starting with _ or . (e.g. _template, _README.md, .disabled).
zeno cron
Parent command — mounts the eight verbs.
This subcommand takes no flags.
zeno cron list
Walk the filesystem and join with the API's slim DB cache. Both sources are surfaced; an entry shown only on disk means the reconciler hasn't picked it up yet (within 2 s).
zeno cron list
zeno cron list --json| Flag | Type | Default | Description |
|---|---|---|---|
--profile | string | — | profile name |
--json | boolean | false | emit JSON |
--quiet | boolean | — |
zeno cron show
Print the parsed frontmatter and the raw markdown body of a single cron.
zeno cron show send-hello
zeno cron show send-hello --json| Flag | Type | Default | Description |
|---|---|---|---|
<slug> | positional | — | cron slug |
--profile | string | — | profile name |
--json | boolean | false | emit JSON |
--quiet | boolean | — |
zeno cron create
Scaffold a cron folder + CRON.md from the profile's _template/CRON.md. Validates the slug and the schedule (via cron-parser) before writing anything. The new cron lands with enabled: true; the body is the placeholder from the template, ready for the operator to replace.
zeno cron create send-hello --schedule '0 9 * * 1-5'
zeno cron create daily-digest --schedule '0 8 * * *' --name "Daily digest"| Flag | Type | Default | Description |
|---|---|---|---|
<slug> | positional | — | cron slug (lowercase, kebab-case) |
--schedule | string | — | cron expression, e.g. '0 9 * * 1-5' |
--name | string | — | |
--description | string | — | |
--profile | string | — | profile name |
--quiet | boolean | — |
zeno cron open
Open the profile's crons/ folder — or a specific cron's subfolder — in the OS file browser. Mirrors zeno knowledge open.
zeno cron open # opens crons/
zeno cron open send-hello # opens crons/send-hello/| Flag | Type | Default | Description |
|---|---|---|---|
<slug> | positional | — | cron slug (optional) |
--profile | string | — | profile name |
--quiet | boolean | — |
zeno cron enable / zeno cron disable
Flip the frontmatter enabled flag in place. The body bytes are untouched (atomic tmp-file + rename). The reconciler picks up the change on the next poll tick.
zeno cron enable send-hello
zeno cron disable send-hello| Flag | Type | Default | Description |
|---|---|---|---|
<slug> | positional | — | cron slug |
--profile | string | — | profile name |
--quiet | boolean | — |
| Flag | Type | Default | Description |
|---|---|---|---|
<slug> | positional | — | cron slug |
--profile | string | — | profile name |
--quiet | boolean | — |
zeno cron delete
Remove the cron folder. TTY prompts delete cron '<slug>'? (y/N); non-TTY requires --yes. The reconciler clears the DB row + run history (CASCADE) within 4 s.
zeno cron delete send-hello
zeno cron delete send-hello --yes| Flag | Type | Default | Description |
|---|---|---|---|
<slug> | positional | — | cron slug |
--yes | boolean | — | |
--profile | string | — | profile name |
--quiet | boolean | — |
zeno cron test
Fire the cron once on demand. Synchronous (≤ 5 s). Prints the agent session id of the run. The route the CLI hits (POST /api/crons/:slug/test) is the only mutation endpoint left in /api/crons/*.
zeno cron test send-hello
zeno cron test send-hello --json| Flag | Type | Default | Description |
|---|---|---|---|
<slug> | positional | — | cron slug |
--profile | string | — | profile name |
--json | boolean | false | emit JSON |
--timeout | string | — | wait timeout in ms (default 300000) |
--quiet | boolean | — |
Scripting & automation
The CLI is designed to be drivable by AI agents and shell scripts without a TTY. Three guarantees hold across every subcommand.
Flags work without TTY
Every flag, positional, and --profile argument behaves the same way in TTY and in pipes. When all required arguments are passed, no command needs interaction. When a required argument is missing in non-TTY, the command exits 1 with a clear error (no profile specified. use --profile <name>, usage: zeno profile use <name>, etc.) instead of hanging on a prompt.
zeno connector install --profile work --secret 'SENTRY_ACCESS_TOKEN=val' sentry # no prompts, exits 0
zeno connector list # non-TTY + no sticky → exit 1 with hint--json for read commands
Every read command accepts --json and emits a single JSON value to stdout: profile list / show, connector list / show / catalog, connector secret list, connector tool list, status. The schemas are exported types in apps/cli/src/types/json-output.ts — ProfileListItem, ProfileShowJson, ConnectorListItem, CatalogListItem, SecretListItem, ToolListItem, StatusJson, DiscoveredInstallationJson.
The contract is per-command (no envelope, no version field). Breaking changes land in release notes. Add --quiet to strip every other byte of output:
zeno status --json --quiet | jq '.[] | select(.state == "running")'--quiet everywhere
Every command accepts --quiet. Effects: spinners stay silent, headers and dividers are omitted, ANSI colors are stripped (same semantics as NO_COLOR), and info(...) lines are suppressed. Errors still print to stderr. --json --quiet produces stdout that is parseable JSON with no leading or trailing decoration.
Destructive ops require --yes in non-TTY
zeno profile delete, zeno connector uninstall, and zeno connector app uninstall prompt (y/N) in TTY. In non-TTY without --yes they exit 1 with error: destructive operation requires --yes in non-interactive mode. Pre-approving the destruction keeps automation explicit.
zeno connector uninstall linear-acme --yes
zeno profile delete personal --yesRisky upgrade targets require --yes in non-TTY
zeno upgrade --unstable, --branch <name>, and --pr <number> are flagged as risky. In TTY they prompt <kind> target may break. continue? (y/N); in non-TTY they exit 1 unless --yes is passed. Tagged versions (--to <tag>, --latest) skip the confirmation.