---
title: CLI reference
description: Every zeno subcommand with its flag table.
---

import ConnectorFlags from '@/generated/cli-flags/connector.mdx';
import ConnectorListFlags from '@/generated/cli-flags/connector-list.mdx';
import ConnectorShowFlags from '@/generated/cli-flags/connector-show.mdx';
import ConnectorCatalogFlags from '@/generated/cli-flags/connector-catalog.mdx';
import ConnectorInstallFlags from '@/generated/cli-flags/connector-install.mdx';
import ConnectorEnableFlags from '@/generated/cli-flags/connector-enable.mdx';
import ConnectorDisableFlags from '@/generated/cli-flags/connector-disable.mdx';
import ConnectorUninstallFlags from '@/generated/cli-flags/connector-uninstall.mdx';
import ConnectorTestFlags from '@/generated/cli-flags/connector-test.mdx';
import ConnectorRefreshToolsFlags from '@/generated/cli-flags/connector-refresh-tools.mdx';
import ConnectorToolFlags from '@/generated/cli-flags/connector-tool.mdx';
import ConnectorToolListFlags from '@/generated/cli-flags/connector-tool-list.mdx';
import ConnectorToolSetFlags from '@/generated/cli-flags/connector-tool-set.mdx';
import ConnectorToolBulkFlags from '@/generated/cli-flags/connector-tool-bulk.mdx';
import ConnectorSecretFlags from '@/generated/cli-flags/connector-secret.mdx';
import ConnectorSecretListFlags from '@/generated/cli-flags/connector-secret-list.mdx';
import ConnectorSecretSetFlags from '@/generated/cli-flags/connector-secret-set.mdx';
import ConnectorSecretRotateFlags from '@/generated/cli-flags/connector-secret-rotate.mdx';
import ConnectorSecretRevealFlags from '@/generated/cli-flags/connector-secret-reveal.mdx';
import ConnectorAppFlags from '@/generated/cli-flags/connector-app.mdx';
import ConnectorAppInstallFlags from '@/generated/cli-flags/connector-app-install.mdx';
import ConnectorAppInstancesFlags from '@/generated/cli-flags/connector-app-instances.mdx';
import ConnectorAppInstancesDiscoverFlags from '@/generated/cli-flags/connector-app-instances-discover.mdx';
import ConnectorAppInstancesAddFlags from '@/generated/cli-flags/connector-app-instances-add.mdx';
import ConnectorAppUninstallFlags from '@/generated/cli-flags/connector-app-uninstall.mdx';
import ChannelFlags from '@/generated/cli-flags/channel.mdx';
import ChannelListFlags from '@/generated/cli-flags/channel-list.mdx';
import ChannelShowFlags from '@/generated/cli-flags/channel-show.mdx';
import ChannelInstallFlags from '@/generated/cli-flags/channel-install.mdx';
import ChannelConfigureFlags from '@/generated/cli-flags/channel-configure.mdx';
import ChannelTestFlags from '@/generated/cli-flags/channel-test.mdx';
import ChannelRotateFlags from '@/generated/cli-flags/channel-rotate.mdx';
import ChannelUninstallFlags from '@/generated/cli-flags/channel-uninstall.mdx';
import DoctorFlags from '@/generated/cli-flags/doctor.mdx';
import LogsFlags from '@/generated/cli-flags/logs.mdx';
import OpenFlags from '@/generated/cli-flags/open.mdx';
import ProfileFlags from '@/generated/cli-flags/profile.mdx';
import ProfileCreateFlags from '@/generated/cli-flags/profile-create.mdx';
import ProfileDeleteFlags from '@/generated/cli-flags/profile-delete.mdx';
import ProfileEditFlags from '@/generated/cli-flags/profile-edit.mdx';
import ProfileListFlags from '@/generated/cli-flags/profile-list.mdx';
import ProfileShowFlags from '@/generated/cli-flags/profile-show.mdx';
import ProfileUseFlags from '@/generated/cli-flags/profile-use.mdx';
import RepoFlags from '@/generated/cli-flags/repo.mdx';
import RestartFlags from '@/generated/cli-flags/restart.mdx';
import StartFlags from '@/generated/cli-flags/start.mdx';
import StatusFlags from '@/generated/cli-flags/status.mdx';
import StopFlags from '@/generated/cli-flags/stop.mdx';
import UpgradeFlags from '@/generated/cli-flags/upgrade.mdx';

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/`.

```bash
zeno profile create personal
```

<ProfileCreateFlags />

### `zeno profile list`

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

```bash
zeno profile list
```

<ProfileListFlags />

### `zeno profile show`

Full detail block for a single profile.

```bash
zeno profile show personal
```

<ProfileShowFlags />

### `zeno profile edit`

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

```bash
zeno profile edit personal --port 6105
```

<ProfileEditFlags />

### `zeno profile use`

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

```bash
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>`.

<ProfileUseFlags />

### `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`.

```bash
zeno profile delete personal           # interactive prompt
zeno profile delete personal --yes     # skip prompt (scripted)
```

<ProfileDeleteFlags />

### `zeno profile`

The umbrella subcommand. Running it with no further arg prints the available verbs.

<ProfileFlags />

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

```bash
zeno start personal
zeno start --all
zeno start personal --build
```

<StartFlags />

### `zeno stop`

Stop a profile container without removing it.

```bash
zeno stop personal
zeno stop --all
```

<StopFlags />

### `zeno restart`

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

```bash
zeno restart personal
```

<RestartFlags />

## 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`.

```bash
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`](https://github.com/ribeirogab/zeno-agent/blob/main/apps/cli/src/types/json-output.ts) (see `StatusJson`):

```json
[
  {
    "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`.

<StatusFlags />

### `zeno logs`

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

```bash
zeno logs personal --tail 100
```

<LogsFlags />

### `zeno open`

Open the profile's dashboard in the system browser.

```bash
zeno open personal
```

<OpenFlags />

### `zeno doctor`

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

```bash
zeno doctor
```

<DoctorFlags />

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

```bash
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.

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

```bash
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)`.

<UpgradeFlags />

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

```bash
zeno connector list
zeno connector list --profile work
zeno connector list --json
```

<ConnectorListFlags />

### `zeno connector show`

Read a single connector's full record (status, secrets masked, tools with permissions, recent activity).

```bash
zeno connector show linear-acme
zeno connector show 9a5ee0d1-5a5d-4353-b2d2-81b09f3a4255
```

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

<ConnectorShowFlags />

### `zeno connector catalog`

Lists the curated catalog. Each entry shows whether it's currently installed and (via `--json`) whether it's `multiInstance: false`.

```bash
zeno connector catalog
zeno connector catalog --json
```

<ConnectorCatalogFlags />

### `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.

```bash
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`.

<ConnectorInstallFlags />

### `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.

```bash
zeno connector enable linear-acme
zeno connector disable linear-acme
```

<ConnectorEnableFlags />

<ConnectorDisableFlags />

### `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`.

```bash
zeno connector uninstall linear-acme            # interactive prompt
zeno connector uninstall linear-acme --yes      # skip prompt (scripted)
```

<ConnectorUninstallFlags />

### `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`).

```bash
zeno connector test linear-acme
```

<ConnectorTestFlags />

### `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`).

```bash
zeno connector refresh-tools linear-acme
```

<ConnectorRefreshToolsFlags />

### `zeno connector tool`

Inspect or change per-tool permissions. Three subcommands.

<ConnectorToolFlags />

#### `zeno connector tool list`

```bash
zeno connector tool list linear-acme
```

<ConnectorToolListFlags />

#### `zeno connector tool set`

Set the permission for one tool.

```bash
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.

<ConnectorToolSetFlags />

#### `zeno connector tool bulk`

Set the permission for every tool in a category. Categories: `read`, `write`, `interactive`.

```bash
zeno connector tool bulk linear-acme --category read --permission always_allow
zeno connector tool bulk linear-acme --category write --permission ask
```

<ConnectorToolBulkFlags />

### `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.

<ConnectorSecretFlags />

#### `zeno connector secret list`

Masked listing.

```bash
zeno connector secret list linear-acme
```

<ConnectorSecretListFlags />

#### `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.

```bash
zeno connector secret set linear-acme __MCP_AUTHORIZATION__
```

<ConnectorSecretSetFlags />

#### `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`.

```bash
zeno connector secret rotate linear-acme
```

<ConnectorSecretRotateFlags />

#### `zeno connector secret reveal`

Print the plaintext value of one secret. Returns `429 retryAfter` when the rate limit fires (5 per key per minute).

```bash
zeno connector secret reveal linear-acme __MCP_AUTHORIZATION__
```

<ConnectorSecretRevealFlags />

### `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.

<ConnectorAppFlags />

#### `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.

```bash
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.

<ConnectorAppInstallFlags />

#### `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.

<ConnectorAppInstancesFlags />

##### `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.

```bash
zeno connector app instances discover
```

<ConnectorAppInstancesDiscoverFlags />

##### `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.

```bash
zeno connector app instances add --instance-id 125887887 --label "FNLivros"
```

<ConnectorAppInstancesAddFlags />

#### `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`.

```bash
zeno connector app uninstall            # interactive prompt
zeno connector app uninstall --yes      # skip prompt (scripted)
```

<ConnectorAppUninstallFlags />

### `zeno connector` (parent)

The umbrella verb. Running `zeno connector --help` lists every subcommand above.

<ConnectorFlags />

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

```bash
zeno channel list
zeno channel list --profile work
zeno channel list --json
```

<ChannelListFlags />

### `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.

```bash
zeno channel show slack
zeno channel show <uuid>
```

Both slug and UUID are accepted.

<ChannelShowFlags />

### `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>`.

```bash
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.

<ChannelInstallFlags />

### `zeno channel configure`

Update public (non-secret) config fields. For Slack today: `--dm-owner-user-id <Uxxx>` restricts DMs to a single Slack user.

```bash
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`.

<ChannelConfigureFlags />

### `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.

```bash
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.

<ChannelTestFlags />

### `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.

```bash
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.

<ChannelRotateFlags />

### `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.

```bash
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`.

<ChannelUninstallFlags />

### `zeno channel` (parent)

The umbrella verb. Running `zeno channel --help` lists every subcommand above.

<ChannelFlags />

## Misc

### `zeno repo`

Print the canonical repo path. Useful in shell scripts that need to `cd` into the source tree.

```bash
cd "$(zeno repo)"
```

<RepoFlags />

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

```bash
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`](https://github.com/ribeirogab/zeno-agent/blob/main/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:

```bash
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.

```bash
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.
