# Duvo CLI

> Drive the Duvo public API from the terminal via the `duvo` CLI (`@duvoai/cli`). Use when the user wants to script Duvo — managing agents, runs, cases, queues, files, skills, connections, Clarity processes, Pulse dashboards, or hitting an arbitrary endpoint via `duvo api` — instead of clicking through the Duvo web UI or hand-crafting `curl` calls.

- Skill: `duvoai/duvo-cli` (Agent Skill, multi-file: 4 files)
- Install (CLI): `npx skillmds@latest add duvoai/duvo-cli`
- Raw SKILL.md: https://api.skillmd.com/api/skills/duvoai/duvo-cli/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Integrations & APIs
- License: MIT
- Author: duvoai (https://skillmd.com/u/duvoai)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/duvoai/duvo-cli

---


# Duvo CLI

`duvo` is the official command-line interface for the [Duvo](https://duvo.ai)
public API. It is published to npm as [`@duvoai/cli`](https://www.npmjs.com/package/@duvoai/cli):
a single binary with resource-grouped subcommands (`duvo agents …`,
`duvo runs …`, `duvo cases …`, …) and a low-level `duvo api <method> <path>`
escape hatch for endpoints that don't yet have a dedicated command.

Use the CLI when the user wants to **automate** or **script** Duvo. The
web UI at <https://app.duvo.ai> is the right answer when they want to
click around interactively; `duvo` is the right answer the moment they
ask for a shell snippet, a cron job, a CI step, or "how do I do X
without opening the browser?". Both surfaces talk to the same public
API — there is no functional gap to bridge by reaching for `curl`.

## Managed sessions and manual setup

In a managed session, the host runtime instructions govern transport, setup,
reference lookup, and scope. Follow them before this standalone guide. When
the host says the CLI is installed and configured, do not probe installation,
log in, inspect credentials, change profiles, or switch transport. Read the
references it names before commands. The installation, authentication, and
profile-management steps below are for manual standalone setup only.

## Install (manual setup)

```bash
npm install -g @duvoai/cli
duvo --version
```

Requires Node.js ≥ 22.22.0. `npx @duvoai/cli <cmd>` also works without
a global install.

## Authentication (manual setup)

`duvo` keeps credentials as **named profiles** on disk. One profile per
(team, environment). The first profile added becomes the default; from
then on, every command uses the default unless overridden.

```bash
duvo login                       # OAuth flow — opens your browser
duvo login --name acme           # name the profile up front
duvo login --api-key <key>       # skip OAuth and store an API key directly
duvo whoami                      # confirm who's signed in
```

`duvo login` defaults to **browser-based OAuth** against the Duvo
production tenant. Pass `--api-key <key>` to store a long-lived API key
instead (grab one from <https://app.duvo.ai/settings/api-keys>).

Switching between profiles:

```bash
duvo profiles list               # show all profiles; `▶` marks the default
duvo profiles use                # interactively pick the default profile
duvo profiles use acme           # set a named profile as the default
duvo --profile acme whoami       # one-off override for a single command
```

**Resolution precedence** (the first match wins):

- Profile: `--profile <name>` flag → `DUVO_PROFILE` env → `defaultProfile` in config.
- Credential: `DUVO_API_KEY` env (bypasses the profile's stored credential lookup) → the profile's stored credential.
- Base URL: `DUVO_API_BASE_URL` env → the profile's `apiBaseUrl` → `https://api.duvo.ai`.
- Team: `--team <id>` flag → `DUVO_TEAM_ID` env → the profile's `defaultTeamId` → the team derived from the active credential. Team-scoped API keys reject a different `--team`; user-scoped keys can select any team the user can access.

## Reading the help

Read `references/commands.md` for command syntax. Every command also supports
`--help`; in managed sessions, use it only when the host runtime permits it.
For standalone use, these help commands describe the installed binary:

```bash
duvo --help                      # global help, lists every top-level group
duvo agents --help               # subcommand group
duvo agents create --help        # exact flags for one command
```

If the binary rejects a documented flag, its help describes the installed
version. Do not guess flags or treat standalone help examples as instructions
to probe a managed session.

## Output modes

Each command picks a default output shape based on what it returns:

- **Single resource** (`duvo agents get <id>`, `duvo runs get <id>`) →
  human-readable key/value text.
- **Collection** (`duvo agents list`, `duvo runs list`) → table with a
  fixed column set per resource.
- **`--json`** on either of the above → raw JSON to stdout. This is the
  **stable contract** for scripts — table and text formats can change
  between versions, JSON is safe to pipe into `jq` or `yq`.

```bash
duvo agents list --team <target-team-id> --json | jq '.agents[].id'
```

For scripting, pass `--json` wherever the command reference lists it and parse the response. Don't
parse the table output — column widths and truncation thresholds are
not part of the public contract. Envelope keys differ across
endpoints — `agents list` returns `.agents[]`, `queues list` returns
`.queues[]`, `runs list` returns `.data[]`, `agents create` returns
`.agent.id`. Inspect the shape with a one-off `--json` call before
hard-coding a jq path.

## The `duvo api` escape hatch

Any endpoint in the public API is reachable via `duvo api <METHOD>
<path>`, modelled directly on `gh api`. Reach for it when:

- A new public endpoint shipped but doesn't have a dedicated `duvo`
  command yet.
- You need a very narrow query-string or body shape that the
  high-level command doesn't expose.
- You're debugging — `duvo api` shows exactly what the API returned,
  with no formatting in the way.

```bash
duvo api GET /v1/agents -F limit=20 -F offset=0
duvo api POST /v1/agents -f name="Ops bot" -F build[name]="v1"
duvo api POST /v1/agents --input body.json
cat body.json | duvo api POST /v1/agents --input -
```

`-f` sends the value as a **string**; `-F` parses it as a typed value
(boolean, number, JSON `null`, or `@file` to load JSON from disk). See
`references/api-escape-hatch.md` for the full flag table.

High-level commands like `duvo agents create` are sugar over `duvo
api` — they share the same HTTP client, output pipeline, and exit
codes, so a script can mix the two freely.

## Resource groups

See `references/commands.md` for the full command tree with flags.
The top-level groups are:

- **Auth & profiles** — `login`, `logout`, `whoami`, `profiles …`
- **Agents** — `agents …`, `agents delete`, `agents move`, `agents duplicate`/`clone`, `agents models`, `agents set-model`, `agents case-triggers …`, `agents schedules …`, `agents triggers …`, `agents slack-triggers …`, `agents memory …`, `agents eval-scores`, `agents eval-rubrics …`
- **Suggestions** — `suggestions …` (Connection suggestions: list, consume, reject)
- **Agent folders** — `agent-folders …` (organize agents in a tree)
- **Revisions** — `revisions …`, `revision-integrations …` (versioned configs)
- **Runs** — `runs …` (start, get, message, stop, respond to HITL, evaluation)
- **Queues & cases** — `queues …`, `queues eval-rubrics …`, `queue-labels …`, `cases …`
- **Notifications** — `notifications …` (list, get, feed, get-batch, counts, unread-count, mark-read, mark-batch-read, mark-all-read, dismiss, delete-read, delete-all)
- **Files & sandboxes** — `files …`, `sandboxes …`
- **Connections & integrations** — `integrations …`, `connections …`, `oauth …`
- **Secrets & credentials** — `secrets …` (env-var secrets), `credentials …` (browser logins), `revision-secrets …`, `revision-logins …`
- **Clarity** — `clarity …` (process search, versions, captures, gaps, evidence, facets, export, generation, promotion, artifact imports, invite links, doctor, process landscape, process links, process tags, process summaries)
- **Pulse** — `pulse …` (create, get, list, send message, attach files, refresh, stop, rename, share, duplicate, move to another team, pdf/snapshot export, version history, restore, messages, answer HITL, connections, comment threads, delete Pulse dashboards)
- **Skills & plugins** — `skills …`, `plugins …`
- **Team** — `team current`, `team get`, `team members`, `team set-role`, `team remove-member`, `team leave`, `team use`, `teams list`, `teams org`, `teams orgs`, `teams create-org-team`, `teams org-insights`, `teams org-metrics`, `teams org-usage`
- **Invitations** — `invite list`, `invite create`, `invite bulk`, `invite update`, `invite resend`, `invite delete`, `invite org-member`, `invite link get|create|delete`
- **Bundled guides** — `guide …` (version-matched CLI guides for AI agents)
- **Self-update** — `update` (update the installed CLI to the latest version)
- **Low-level** — `api <method> <path>`

For end-to-end recipes (create an agent → start a run → respond to
HITL; manage a queue and its cases; upload files for a sandboxed run;
authorize a custom MCP server) see `references/workflows.md`.

## Conventions you can rely on across commands

These hold for every command group, so call them out when explaining
the CLI rather than restating per-command:

- `--profile <name>` is a **global** flag that overrides the default
  profile for that single invocation.
- `--team <id>` is a **global** flag that overrides the resolved team
  for commands that use request-team scope. In managed sessions, always
  pass the target team explicitly for those commands, even when it matches
  the configured default. Follow the host instructions to choose the target.
  Resource-ID commands may ignore this flag; raw API paths are not rewritten.
  Command-local ownership flags (including landscape `propose-process --team`)
  assign an owner and must not receive a default team mechanically. Organization
  commands use `--org` or a positional organization ID as documented. OAuth
  and user-scoped API-key profiles can select an accessible team; team-scoped
  API-key profiles reject a different team.
- `--json` is available on nearly every command that hits the API and is
  the shape to use in scripts. Use it only when the command's help or command
  reference lists it; never invent an unsupported flag or parse table output.
- Destructive operations (`duvo logout`, `duvo profiles remove`,
  `duvo runs stop`, `duvo agents delete`, `duvo agents move`,
  `duvo agent-folders delete`,
  `duvo cases delete`, `duvo cases clear`, `duvo cases bulk-delete`,
  `duvo cases bulk-reprocess`, `duvo cases bulk-update-status`,
  `duvo cases bulk-update-priority`,
  `duvo skills delete`, `duvo skills revisions delete`,
  `duvo queues delete`, `duvo secrets delete`,
  `duvo credentials delete`, `duvo revision-secrets detach`,
  `duvo revision-logins detach`, `duvo revision-integrations remove`,
  `duvo revision-integrations connections unpin`,
  `duvo agents schedules delete`, `duvo agents case-triggers delete`,
  `duvo agents triggers delete`, `duvo agents slack-triggers delete`,
  `duvo agents eval-rubrics remove`, `duvo agents eval-rubrics replace`,
  `duvo queues eval-rubrics remove`, `duvo queues eval-rubrics replace`,
  `duvo connections delete`, `duvo queue-labels delete`,
  `duvo integrations custom delete`, `duvo clarity process-labels delete`,
  `duvo suggestions reject`, `duvo files delete`,
  `duvo pulse delete`, `duvo pulse move`,
  `duvo notifications delete-read`, `duvo notifications delete-all`,
  `duvo team remove-member`, `duvo team leave`, …)
  prompt for confirmation in a TTY and refuse on a non-TTY stdin. Pass
  `-y` / `--yes` to skip the prompt — never pipe `yes` into the CLI to
  bypass the prompt; it explicitly refuses inferred consent from piped
  input.
- Exit codes: `0` success, `1` generic error, `2` auth error (missing
  or invalid key, unknown profile), `3` not found. Useful for CI
  scripts that want to branch on the failure mode.
- IDs are printed bare (no quotes) so they copy cleanly into shell
  pipelines and `$()` substitutions:
  ```bash
  agent=$(duvo agents create --team <target-team-id> --name "Ops bot" --input "What to do" --json | jq -r .agent.id)
  duvo runs start --agent "$agent"
  ```
- Deprecation warnings print to **stderr** (so they don't corrupt
  `--json` stdout) and can't be silenced. If a user reports an
  unexpected warning, check whether their `@duvoai/cli` version is
  behind.

## Pitfalls and gotchas

- **Profile vs credential.** `--profile foo` changes which profile is
  read; `DUVO_API_KEY=…` overrides the **credential** but still uses
  the selected profile's `apiBaseUrl`. To target a totally different
  environment, set both `DUVO_API_KEY` and `DUVO_API_BASE_URL`.
- **`profiles use` with no argument** opens an interactive picker that
  only works in a TTY. In CI, always pass a name explicitly.
- **`agents create` is interactive by default.** When `--name` and
  `--input` are omitted and stdin is a TTY, the CLI prompts. In CI,
  pass both flags (and `--no-build` if you don't want an initial
  build) so it never blocks waiting for input.
- **`cases create --from-file -`** reads JSON from stdin. The file (or
  stdin) is a single case object or an array of up to 100 cases. Each
  case is `{ "title": "…", "data": "…", "labels": [{ "key": "…", "value": "…" }] }`.
  Labels with no `key` are tag-only labels.
- **Sandbox files vs team files.** `duvo files …` manages persistent
  team files (visible in the Files surface in the UI); `duvo sandboxes …`
  stages files for a single run (`duvo runs start --sandbox-id <id>`). Don't mix them.
- **Connections vs integrations.** `duvo integrations list` shows the
  team's catalog of integration types; `duvo connections list` shows
  the user's actual connected accounts (one per OAuth/credential
  flow). OAuth-based connections (Gmail, Slack, …) are started by
  `duvo oauth native start <provider>`, not by `duvo connections create`
  — the latter is only for user-provided MCP servers.
- **Attached ≠ connected.** `revision-integrations attach` only creates
  the integration **slot** on the revision. For OAuth and user-provided
  integrations (HubSpot, Gong, Slack, custom MCP, …) the run can only
  use the slot once one of the user's connections is **pinned** to it —
  an attached slot with no pinned connection fails at runtime with "not
  connected", and nothing warns you at attach time. Find the connection
  with `duvo connections list --team <target-team-id> --type <slug>`, pin it with
  `duvo revision-integrations connections pin`, and verify with
  `duvo revision-integrations connections list` (expect ≥ 1 entry per
  slot). Default integrations (browser, Exa, human-in-the-loop,
  `case-queue-producer`/`case-queue-consumer`, …) need no pin — but
  case-queue slots need a **queue mapped** instead, verified with
  `duvo revision-integrations case-queue-setup`. See workflow 7 and the
  setup checklist in workflow 4 of `references/workflows.md`.
- **Secrets vs connections vs credentials.** Three separate stores:
  `duvo secrets` holds env-var key/value pairs injected into runs at
  runtime; `duvo credentials` holds browser logins (domain + password
  - optional TOTP) used by the browsing agent; `duvo connections`
    holds OAuth/API-key connections to external services (Gmail, Slack,
    custom MCP, …). They don’t overlap — attach secrets with
    `revision-secrets`, logins with `revision-logins`, and connection
    slots with `revision-integrations connections pin`.
- **Multi-team OAuth and user-scoped API-key profiles.** An OAuth login,
  or an API key not pinned to a single team by the server (a _user-scoped_
  key), can act on several teams. Use `duvo teams list` to see all teams,
  then pass `--team <id>` on request-scoped commands. In manual setup,
  `duvo team use <id>` can set the profile default
  — both work for these profiles. A _team-scoped_ API key is the one
  exception: the server already pins it to one team, so it cannot target a
  different team via `--team`.
- **Reconnecting an expired OAuth connection.** Don't create a new
  connection for a stale one — pass `--reconnect-instance-id <id>` to
  `duvo oauth native start` or `duvo oauth mcp authorize` with the
  existing connection's ID (from `duvo connections list`) to
  re-authorize it in place instead of creating a duplicate.
- **Clarity has both read and explicit write commands.** Start with
  `duvo clarity overview <process-id>`, then use `versions`, `current`,
  `proposal`, `compare`, `gaps`, `evidence`, `readiness`, or `facets` to
  understand the process. Use the write commands only when the user
  actually wants to mutate Clarity state: generation, promotion, revert,
  postprocessing, automation build, extra-capture agent, invite
  links, or Miro artifact imports. Those v2-only commands require a
  Clarity v2 process; legacy v1 processes
  support only `overview`, `status`, `captures`, `capture`, and `export`.
  Default output is compact; transcripts and media URLs are included only
  when a JSON command explicitly passes `--include-transcripts`.
- **Notifications: batches vs individuals.** `duvo notifications feed`
  returns a mix of individual notifications and notification batches
  grouped by Agent or Queue;
  `get-batch` and `mark-batch-read` only accept a batch ID (from `feed`
  or a notification's `batchId`), not an individual notification ID. On
  `notifications list`, `--sort importance` requires `--batch` — the CLI
  rejects the combination locally if `--batch` is omitted.
- **Clarity artifact imports use a two-phase API under the hood.** Use
  `duvo clarity import-artifact <process-id> <file>` for local Miro SVG,
  XML, PNG, or JPEG exports. It creates the signed URL, uploads bytes, and
  completes the import. For custom upload clients or artifact-chat
  workflows, use `duvo api` against the public route directly.
- **Slack channel triggers need a connected workspace first.**
  `agents slack-triggers create` fails unless the Slack workspace is
  already bound to the agent — check with `agents slack-triggers
workspaces <agent-id>` before creating, and pass `--workspace` when
  the team has more than one installed workspace.
- **Team member management has per-command role floors.** `team
set-role` needs Manager and above (only an Owner can grant or remove
  the Owner role); `team remove-member` needs Superadmin and above;
  `team leave` works for any member. All three refuse to act on the
  last remaining Owner.
- **Two ways to attach a file to Pulse.** `duvo pulse send-message <id>
--message <text> --attach-file <path>` uploads and references a file
  in one step — use it when you already know the instruction. `duvo
pulse attach <id> <file>` only stages the file and prints its
  attachment ID, for when the instruction is composed or sent
  separately (e.g. from a script building the message body). Both
  share the same 5-file, 25MB-each limit.

## When the CLI is the wrong tool

Steer users to a different surface in these cases:

- **They want to read a stream of run events as they happen.** The
  CLI's `runs get` / `runs messages` are point-in-time reads. Use
  `--webhook-url` on `runs start` to receive events asynchronously,
  or open the run in the web UI.
- **They want to edit an AOP or agent configuration interactively.**
  `revisions create` and `revisions update` accept a config file, but
  composing the config by hand is painful. Direct them to the
  Agent editor in the web UI for anything beyond a small targeted
  patch.
- **They want to run an agent in their own process rather than on
  Duvo.** That's not what the public API exposes; the CLI is a client
  to the hosted Duvo platform.

## In-skill references

- `references/commands.md` — full command tree with flags, grouped by
  resource. Use this when you need the exact flag a command expects.
- `references/workflows.md` — end-to-end recipes (create an agent and
  start a run; queue + cases lifecycle; sandboxed runs with uploaded
  files; authorize a custom MCP server).
- `references/api-escape-hatch.md` — `duvo api` flag reference, with
  worked examples of `-f` vs `-F`, `@file` typed fields, and
  `--input` for raw JSON bodies.

Read the relevant command reference before acting; managed sessions follow
the host runtime instructions for any help fallback.

## See also

- `aop-writer` — author or rewrite the AOP that ships in a Build (`duvo revisions create` / `duvo revisions update`).
- `run-debugger` — diagnose a failed Run; pairs with `duvo runs get`, `duvo runs messages`, and `duvo revisions get` for API-mode reads.
- `workflow-debugger` — audit an Agent or workflow across many Runs; pairs with `duvo runs list`, `duvo queues agents`, and `duvo revisions get`.

## Resources

- [Duvo](https://duvo.ai) — product website
- [Duvo documentation](https://docs.duvo.ai) — concepts, building Agents, Connections
- [Duvo API reference](https://docs.duvo.ai/api-reference) — every endpoint the CLI wraps
- [`@duvoai/cli` on npm](https://www.npmjs.com/package/@duvoai/cli) — versions, install instructions, changelog
- [Web app](https://app.duvo.ai) — interactive surface for everything the CLI scripts
- [API keys](https://app.duvo.ai/settings/api-keys) — issue and revoke API keys for `duvo login --api-key`
- [Public skill repository](https://github.com/duvoai/skills) — the MIT-licensed community release of this skill, packaged for installation in third-party Claude Code setups

