mcpField
D
duvo-cli

duvo-cli

github.com/duvoaiAgent workflows890 installsUpdated 6d ago
Repository

About duvo-cli

duvo-cli is the command-line interface for Duvo's public API, aimed at automating and scripting Duvo workflows. It combines resource-specific commands with a low-level API command and JSON output for scripts.

Key features

  1. 01Manage agents, runs, cases, and automations
  2. 02Use JSON output for scriptable responses
  3. 03Call endpoints through the low-level API command
  4. 04Manage authentication profiles and teams
  5. 05Work with queues, files, integrations, and skills

Use cases

  1. 01Automate Duvo tasks in CI or scheduled scripts
  2. 02Create agents and start or manage runs
  3. 03Query API endpoints without a dedicated command
  4. 04Process cases and queues from the command line

MCP support

The CLI documentation mentions authorizing a custom MCP server as part of an end-to-end workflow. It does not describe the CLI itself as an MCP client or server.

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/duvoai/skills --skill duvo-cli

After installing, type/duvo-cliin Claude Code to confirm it loaded.

Bundled files

References and scripts installed along with SKILL.md.

references/
api-escape-hatch.md
commands.md
workflows.md

Details

From the project README

Duvo CLI

duvo is the official command-line interface for the Duvo public API. It is published to npm as @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)

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.

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:

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:

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

duvo api passes the path through verbatim — it does not resolve --team/DUVO_TEAM_ID/the profile default the way the high-level commands do, so spell out whatever the endpoint's own path is. Route shapes vary per resource: some collections are team-scoped (/v2/teams/<team_id>/agents), others are flat (/v2/artifacts), and some by-ID routes stay team-scoped (/v2/teams/<team_id>/invites/<id>). Take the exact path from GET /v2/documentation/json (the raw OpenAPI document; /v2/documentation is the Swagger UI HTML), don't infer it.

duvo api GET "/v2/teams/$TEAM_ID/agents" -F limit=20 -F offset=0
duvo api POST "/v2/teams/$TEAM_ID/agents" -f name="Ops bot" -F [email protected]
duvo api POST "/v2/teams/$TEAM_ID/agents" --input body.json
cat body.json | duvo api POST "/v2/teams/$TEAM_ID/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 …
  • Automations — automations … (list, create, get, current, flow, rename, duplicate, delete, add/remove agents and queues, tabs) and their Policy: automations config-fields, add-config-field, edit-config-field, set-config-field, hide-config-field, restore-config-field, config-sections …
  • 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 …, queues aggregations … (Cube-style aggregations over typed Case data), queue-labels …, cases …, cases search
  • 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, pulse data … to connect/disconnect queue data sources)
  • 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 create-workspace, teams org-agents, teams org-insights, teams org-metrics, teams org-runs, 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.

Automations

On a team that has moved to Automations, every agent and queue belongs to an automation: the container the web app shows for one process.

  • duvo automations create --name <name> creates the automation and its first agent. The --json response carries automation.id and first_agent_id. Configure that first agent instead of creating another.
  • duvo queues create requires --automation-id <id>. Without it the API returns 409: "This team has moved to Automations, so a new agent or queue must be created inside one."
  • duvo agents create accepts the same --automation-id. Without it the backend creates a new automation named after the agent, so pass the flag to keep every agent of one process in one automation.
  • duvo automations list gives the IDs; duvo automations current <id> shows the agents, queues, triggers and schedules the automation runs now.

On a team that hasn't moved, automations commands return 403 ("This team hasn't moved to Automations, so it has no automations to work with.") and agents and queues are created without the flag.

Recommended build order

When building a process from the CLI, work in this order. Each step depends on the one before it.

  1. Connections. For every service the agents use, check duvo connections list --team <target-team-id> --type <slug>. Connect what is missing first (see "Native OAuth provider keys" below). A connection that doesn't exist can't be pinned later.
  2. Automation. duvo automations create --name <name> --json. Keep automation.id and first_agent_id.
  3. Queues, schema, labels. duvo queues create --name <name> --automation-id <id>, then duvo queues schema create <queue-id> --file schema.json --name <name> for typed cases, then duvo queue-labels create. Schema rules and an example are in workflow 4 of references/workflows.md.
  4. Agents. Create the remaining agents with --automation-id, attach integrations, pin a connection to every OAuth or user-provided slot, and wire queues with duvo queues link-agent (--enable-trigger for a consumer, or its case trigger starts off). Answer every Policy question the agents read (see "Policy (config fields)" below). Run the setup checklist in workflow 4 of references/workflows.md before going on.
  5. Case triggers on. duvo agents case-triggers list <consumer-agent-id> --json must show enabled: true. If it shows false with pending_change set, the team saves automation changes to a draft: the trigger switches on only when someone clicks Make active in the automation's header in the Duvo app, and the CLI has no command for it. Ask the user to do that; until then a test case stays Pending.
  6. Test run. duvo runs start --agent <id>, or add one test case to the queue, and read the outcome with duvo runs get <run-id>.
  7. Schedule, created paused. Pass the user's IANA timezone explicitly:
    duvo agents schedules create <agent-id> --frequency workday --time 07:30 \
      --timezone Europe/Prague --disabled
    duvo agents schedules update <agent-id> <schedule-id> --enable   # after the test run is right
    
    Whoever creates the schedule receives that agent's approval requests (see "Approvals go to the Run's author" below).

Policy (config fields)

An automation's Policy holds typed answers such as thresholds, mailboxes and sender lists. Put values a person will tune there, not in AOP text. An agent reads a field when its AOP contains <DUVO:CONFIG key="<key>" name="<label>">, and each run is handed the current answers, so changing an answer needs no new revision. A run is refused while a field it reads has no answer.

Agents can't write Policy. Values change on the Policy screen, with duvo automations set-config-field, or through Ask Duvo on a user's instruction. Rules an agent learns while running belong in a file or in agent memory. Commands, answer types, value shapes, --fill-mode and --binding are in references/commands.md under "Policy (config fields)".

Invite people to a Clarity interview

Prefer duvo clarity interview-invites send <process-id> with repeated --invitee flags for multiple interview recipients: at most 25 unique recipients per batch. invite bulk is team-only; use invite create for --no-interview or --process-role. Read the interview invitation workflow for recipient selection and delivery handling; avoid one-by-one loops.

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 automations delete, duvo automations remove-agent, duvo automations remove-queue, 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 queues aggregations 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:
    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.
  • An empty connections list is per user and per team. It lists the calling user's connections in the selected team, plus connections shared with that team. integrations list rows always show has_oauth_tokens: false: they are catalog slots, not accounts. So integrations listed with 0 connections means this user has authorized nothing in this team yet. Check duvo whoami and the --team you passed before concluding the account is missing.
  • Native OAuth provider keys are not integration types. oauth native start takes the provider key, which integrations list --json reports as oauth_provider_key (Gmail is google_gmail, Google Calendar is google_calendar, Outlook is microsoft_outlook). Passing the type (gmail) returns "Unknown OAuth provider". A null key means the integration isn't a native OAuth one. HubSpot, Asana, Attio, Amplitude, Supabase, Notion, Granola and Apify connect through MCP OAuth instead; the server URL and --integration-type for each are in references/commands.md under "OAuth flows": duvo oauth mcp authorize --url https://mcp.hubspot.com --name HubSpot --integration-type hubspot. Connect anything else with a null key in the web app.
  • 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.
  • A queue with no consumer keeps its cases in Pending. Nothing claims them, so they stay there until a person closes them by hand (duvo cases bulk-update-status). Don't create an "audit" or "log" queue that only a producer writes to. For a record of what happened, have the agent write a file (duvo files …) or add one digest case per run to a queue that does have a consumer.
  • Approvals go to the Run's author. A human-in-the-loop request lands with whoever the run belongs to: a scheduled run belongs to the person who created the schedule, and a case-triggered run belongs to the owner of the previous run on that case, falling back to whoever created the case. A schedule created with an API key belongs to the key's user. The request lands in that person's Requests inbox, so create schedules as the person who should answer the approvals.
  • 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. To change only the AOP, pass a markdown file with --aop-file instead. 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; decisions through HITL; queue + cases lifecycle, case schemas and dedupe; 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

Comments

Markdown supported