Zeno is experimental. Personal project, no SLA, breaking changes expected.

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
FlagTypeDefaultDescription
<profile>positionalprofile identifier (lowercase kebab-case, e.g. "personal
--portstring
--quietboolean

zeno profile list

Inventory of every profile in state.db, with live container status and dashboard port.

zeno profile list
FlagTypeDefaultDescription
--jsonboolean
--quietboolean

zeno profile show

Full detail block for a single profile.

zeno profile show personal
FlagTypeDefaultDescription
<profile>positionalprofile identifier
--jsonboolean
--quietboolean

zeno profile edit

Move a profile's host port. Useful when a port is already taken.

zeno profile edit personal --port 6105
FlagTypeDefaultDescription
<profile>positionalprofile identifier
--portstring
--quietboolean

zeno profile use

Set the sticky profile that subcommands act on when no profile argument is given.

zeno profile use personal

Omitting 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>.

FlagTypeDefaultDescription
<name>positionalprofile identifier
--clearboolean
--quietboolean

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)
FlagTypeDefaultDescription
<profile>positionalprofile identifier
--yesboolean
--quietboolean

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
FlagTypeDefaultDescription
<profile>positionalprofile identifier (omit for sticky)
--allboolean
--buildbooleanforce rebuild of zeno-agent:dev image before start
--quietboolean

zeno stop

Stop a profile container without removing it.

zeno stop personal
zeno stop --all
FlagTypeDefaultDescription
<profile>positionalprofile identifier (omit for sticky)
--allboolean
--quietboolean

zeno restart

Stop + start. Cheapest way to pick up an env change.

zeno restart personal
FlagTypeDefaultDescription
<profile>positionalprofile identifier (omit for sticky)
--allboolean
--buildboolean
--quietboolean

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 --quiet

The 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 in state.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; 0 when not running.
  • connectorCount — count from GET /api/connectors. null when the profile is stopped or the call timed out (rendered as ? in the table).
  • lastCron — most recent entry from GET /api/crons/runs?limit=1, or null.
  • lastError — most recent entry from GET /api/logs?level=error&limit=1, or null.
FlagTypeDefaultDescription
--jsonboolean
--quietboolean

zeno logs

Tail container logs. SIGINT (Ctrl-C) to abort.

zeno logs personal --tail 100
FlagTypeDefaultDescription
<profile>positionalprofile identifier (omit for sticky)
--tailstring
--quietboolean

zeno open

Open the profile's dashboard in the system browser.

zeno open personal
FlagTypeDefaultDescription
<profile>positionalprofile identifier (omit for sticky)
--quietboolean

zeno doctor

Preflight diagnostics: docker reachable, drift between state.db and the running Docker world, etc. Reports findings; does not heal.

zeno doctor
FlagTypeDefaultDescription
--quietboolean

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

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

The 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).

FlagTypeDefaultDescription
--tostring
--unstableboolean
--branchstring
--prstring
--prereleaseboolean
--latestbooleanjump straight to latest stable (skips the picker)
--listboolean
--notesstring
--forceboolean
--dryRunboolean
--yesboolean
--limitstringpagination limit for --list / picker (default 30)
--quietboolean

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
FlagTypeDefaultDescription
--profilestringprofile name
--jsonbooleanfalseemit raw JSON
--quietboolean

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-81b09f3a4255

Both slug and UUID are accepted on every dynamic :id route.

FlagTypeDefaultDescription
<target>positionalslug or id
--profilestringprofile name
--jsonbooleanfalseemit raw JSON
--quietboolean

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 --json

This 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_xxx

A second install of a multiInstance: false catalog returns 409 single_instance_catalog_already_installed.

FlagTypeDefaultDescription
<catalogId>positionalcatalog entry id (e.g. "linear
--labelstringinstance label (operator-supplied; required when installing a second instance of the same catalog)
--profilestringprofile name
--secretstringsecret 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
FlagTypeDefaultDescription
<target>positionalslug or id
--profilestringprofile name
--quietboolean
FlagTypeDefaultDescription
<target>positionalslug or id
--profilestringprofile name
--quietboolean

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)
FlagTypeDefaultDescription
<target>positionalslug or id
--profilestringprofile name
--yesbooleanfalseconfirm destructive uninstall
--quietboolean

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
FlagTypeDefaultDescription
<target>positionalslug or id
--profilestringprofile name
--quietboolean

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
FlagTypeDefaultDescription
<target>positionalslug or id
--profilestringprofile name
--quietboolean

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
FlagTypeDefaultDescription
<target>positionalslug or id
--profilestringprofile name
--jsonboolean
--quietboolean

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 never

Permissions: always_allow, ask, never. The connector-permission guardrail in the worker reads these on every tool call.

FlagTypeDefaultDescription
<target>positionalslug or id
<tool>positionaltool name
<permission>positionalalways_allow
--profilestringprofile name
--quietboolean

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 ask

This 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
FlagTypeDefaultDescription
<target>positionalslug or id
--profilestringprofile name
--jsonboolean
--quietboolean

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__
FlagTypeDefaultDescription
<target>positionalslug or id
<key>positionalsecret key
--profilestringprofile name
--quietboolean

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
FlagTypeDefaultDescription
<target>positionalslug or id
--profilestringprofile name
--quietboolean

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__
FlagTypeDefaultDescription
<target>positionalslug or id
<key>positionalsecret key
--profilestringprofile name
--quietboolean

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.pem

A 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
FlagTypeDefaultDescription
--profilestringprofile name
--jsonboolean
--quietboolean
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)
FlagTypeDefaultDescription
--profilestringprofile name
--yesboolean
--quietboolean

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
FlagTypeDefaultDescription
--profilestringprofile name
--jsonbooleanfalseemit JSON
--quietboolean

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.

FlagTypeDefaultDescription
<slug>positionalchannel slug or id
--profilestringprofile name
--jsonbooleanfalseemit JSON
--quietboolean

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 prompts

A second install of an already-installed channel exits 0 with <type> already installed on stderr.

FlagTypeDefaultDescription
<type>positionalchannel type (slack, ...)
--profilestringprofile name
--secretstringKEY=VALUE — can repeat for multiple
--quietboolean

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 U01H1A2B3

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

FlagTypeDefaultDescription
<slug>positionalchannel slug
--profilestringprofile 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 slack

Returns 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.

FlagTypeDefaultDescription
<slug>positionalchannel slug
--profilestringprofile name
--jsonbooleanfalseemit JSON
--quietboolean

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.

FlagTypeDefaultDescription
<slug>positionalchannel slug
--profilestringprofile name
--secretstringKEY=VALUE — can repeat for non-interactive
--quietboolean

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    # scripted

In non-TTY without --yes, exits 1 with error: destructive operation requires --yes in non-interactive mode.

FlagTypeDefaultDescription
<slug>positionalchannel slug
--profilestringprofile name
--yesbooleanfalseskip confirmation
--quietboolean

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)"
FlagTypeDefaultDescription
--quietboolean

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
FlagTypeDefaultDescription
--profilestringprofile name
--jsonbooleanfalseemit JSON
--quietboolean

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
FlagTypeDefaultDescription
<slug>positionalcron slug
--profilestringprofile name
--jsonbooleanfalseemit JSON
--quietboolean

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"
FlagTypeDefaultDescription
<slug>positionalcron slug (lowercase, kebab-case)
--schedulestringcron expression, e.g. '0 9 * * 1-5'
--namestring
--descriptionstring
--profilestringprofile name
--quietboolean

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/
FlagTypeDefaultDescription
<slug>positionalcron slug (optional)
--profilestringprofile name
--quietboolean

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
FlagTypeDefaultDescription
<slug>positionalcron slug
--profilestringprofile name
--quietboolean
FlagTypeDefaultDescription
<slug>positionalcron slug
--profilestringprofile name
--quietboolean

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
FlagTypeDefaultDescription
<slug>positionalcron slug
--yesboolean
--profilestringprofile name
--quietboolean

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
FlagTypeDefaultDescription
<slug>positionalcron slug
--profilestringprofile name
--jsonbooleanfalseemit JSON
--timeoutstringwait timeout in ms (default 300000)
--quietboolean

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.tsProfileListItem, 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 --yes

Risky 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.

On this page