Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.duvo.ai/llms.txt

Use this file to discover all available pages before exploring further.

The Duvo CLI is designed to run in scripts and automation pipelines, not just interactively. This page covers how to authenticate in non-interactive environments, common automation patterns, and three complete worked examples.

Authentication in CI/CD and scripts

Use an API key, not OAuth

OAuth sessions require a browser login and are bound to a user’s session. For CI/CD pipelines and server-side scripts, use an API key instead. Generate a key at Team Settings → API Keys and store it as a secret environment variable in your CI system (GitHub Actions secrets, GitLab CI variables, AWS Secrets Manager, etc.).

Pass the key as an environment variable

The CLI reads DUVO_API_KEY automatically, so you never need to touch a config file or run duvo login in a pipeline:
DUVO_API_KEY="dv_..." duvo agents list --json
Or export it once at the top of your script:
export DUVO_API_KEY="${DUVO_API_KEY}"  # sourced from environment
duvo agents list --json
duvo runs start --agent "$AGENT_ID" --json

Rotating API keys

API keys do not expire by default. Rotate them by:
  1. Creating a new key at Team Settings → API Keys.
  2. Updating the key in your CI secret store.
  3. Deleting the old key from the dashboard.
There is a brief window between steps where both keys are valid — this ensures zero-downtime rotation. If a key is compromised, delete it immediately and treat any Jobs that ran under it as potentially untrusted.

GitHub Actions example

jobs:
  duvo-sync:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm install -g @duvoai/cli
      - run: duvo agents list --json
        env:
          DUVO_API_KEY: ${{ secrets.DUVO_API_KEY }}

Common scripting patterns

Start a Job and wait for it to finish

duvo runs start returns immediately. Poll duvo runs get until the Job reaches a terminal status:
RUN_ID=$(duvo runs start \
  --agent "$AGENT_ID" \
  --message "Process the daily batch." \
  --json | jq -r '.run.id')

while true; do
  STATUS=$(duvo runs get "$RUN_ID" --json | jq -r '.run.status')
  case "$STATUS" in
    completed|failed|stopped) break ;;
  esac
  sleep 10
done

if [ "$STATUS" != "completed" ]; then
  echo "Job failed with status: $STATUS" >&2
  exit 1
fi
echo "Job $RUN_ID completed."

Upload files before starting a Job

When your Assignment needs to process files, create a sandbox, upload the files, and pass the sandbox ID to the run:
SANDBOX_ID=$(duvo sandboxes create --json | jq -r '.sandbox.id')

duvo sandboxes upload "$SANDBOX_ID" ./input-data.csv

RUN_ID=$(duvo runs start \
  --agent "$AGENT_ID" \
  --sandbox-id "$SANDBOX_ID" \
  --json | jq -r '.run.id')
For files larger than 10 MB, get a presigned URL and upload directly:
UPLOAD=$(duvo sandboxes prepare-upload-url "$SANDBOX_ID" \
  --path /workspace/data.csv --json)
URL=$(echo "$UPLOAD" | jq -r '.upload_url')
curl -X PUT --data-binary "@./data.csv" "$URL"

Bulk-delegate Cases to an Assignment

When you need to route a set of Cases to a specific Assignment — for example, assigning a backlog of items after a new Assignment is deployed:
duvo cases bulk-delegate \
  --queue "$QUEUE_ID" \
  --agent "$AGENT_ID" \
  --ids "case-1,case-2,case-3,case-4,case-5" \
  --yes
--ids accepts a comma-separated list of up to 100 Case IDs. The --yes flag skips the confirmation prompt, which is required in non-interactive scripts.

Collect Job output from a completed run

After a Job finishes, pull the assistant’s messages to feed the output into downstream systems:
duvo runs messages "$RUN_ID" --json \
  | jq -r '[.messages[] | select(.role=="assistant") | .content] | join("\n")'

Roll out an Assignment config change to multiple Assignments

When a shared config file is updated (for example, a common SOP or tool set), push it as a new Revision across every affected Assignment:
REVISION_NAME="config-update-$(date +%Y%m%d)"

for AGENT_ID in agent-id-1 agent-id-2 agent-id-3; do
  duvo revisions create \
    --agent "$AGENT_ID" \
    --name "$REVISION_NAME" \
    --config-file ./shared-config.json
  echo "Updated $AGENT_ID"
done

Worked examples

Example 1: One-shot Job trigger

Trigger a Job from any shell or pipeline and print the final output. Exit non-zero if the Job fails.
#!/usr/bin/env bash
set -euo pipefail

AGENT_ID="$1"          # pass as argument: ./trigger-job.sh <agent-id>
MESSAGE="${2:-}"       # optional message

RUN_ID=$(duvo runs start \
  --agent "$AGENT_ID" \
  ${MESSAGE:+--message "$MESSAGE"} \
  --json | jq -r '.run.id')

echo "Started Job: $RUN_ID"

while true; do
  STATUS=$(duvo runs get "$RUN_ID" --json | jq -r '.run.status')
  case "$STATUS" in
    completed|failed|stopped) break ;;
  esac
  sleep 10
done

echo "Job finished: $STATUS"

# Print the last assistant message
duvo runs messages "$RUN_ID" --json \
  | jq -r '.messages[] | select(.role=="assistant") | .content' \
  | tail -1

[ "$STATUS" = "completed" ] || exit 1

Example 2: Nightly Assignment config sync from Git

Store Assignment configs as JSON files in a Git repository and push any changed configs to Duvo on every merge to main. This lets you version-control your Assignment Setups alongside your application code. Repository layout:
assignments/
  invoice-processor.json
  order-tracker.json
  supplier-follow-up.json
agent-ids.env          # INVOICE_PROCESSOR_ID=abc123 ...
Sync script (.github/workflows/sync-assignments.yml):
name: Sync Assignments

on:
  push:
    branches: [main]
    paths:
      - "assignments/**"

jobs:
  sync:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 2 # need HEAD and HEAD^ to diff

      - run: npm install -g @duvoai/cli

      - name: Push changed configs
        env:
          DUVO_API_KEY: ${{ secrets.DUVO_API_KEY }}
        run: |
          source agent-ids.env
          CHANGED=$(git diff --name-only HEAD^ HEAD -- assignments/)

          for FILE in $CHANGED; do
            NAME=$(basename "$FILE" .json)
            VAR_NAME=$(echo "$NAME" | tr '[:lower:]-' '[:upper:]_')_ID
            AGENT_ID="${!VAR_NAME:-}"

            if [ -z "$AGENT_ID" ]; then
              echo "No Assignment ID for $NAME — skipping."
              continue
            fi

            duvo revisions create \
              --agent "$AGENT_ID" \
              --name "git-$(git rev-parse --short HEAD)" \
              --config-file "$FILE"

            echo "Synced $NAME ($AGENT_ID)"
          done
Only files changed in the push are synced, so the workflow is fast even with many Assignment files in the repo.

Example 3: Weekly Job-status report

Run weekly in CI to summarize how many Jobs completed, failed, or are still running across your key Assignments. Post the summary wherever your team receives reports.
#!/usr/bin/env bash
set -euo pipefail

# Space-separated list of Assignment IDs to include in the report
AGENTS="agent-id-1 agent-id-2 agent-id-3"

completed=0
failed=0
stopped=0

for AGENT_ID in $AGENTS; do
  RUNS=$(duvo runs list --agent "$AGENT_ID" --limit 100 --json 2>/dev/null \
    || echo '{"runs":[]}')

  completed=$((completed + $(echo "$RUNS" | jq '[.runs[] | select(.status=="completed")] | length')))
  failed=$((failed     + $(echo "$RUNS" | jq '[.runs[] | select(.status=="failed")] | length')))
  stopped=$((stopped   + $(echo "$RUNS" | jq '[.runs[] | select(.status=="stopped")] | length')))
done

total=$((completed + failed + stopped))

echo "Weekly Job Summary"
echo "=================="
echo "Completed : $completed / $total"
echo "Failed    : $failed / $total"
echo "Stopped   : $stopped / $total"
Pipe the output to slack-cli, mail, or any notification tool your team uses.

Tips

  • Always add --yes to bulk operations (bulk-delegate, bulk-update-status, cases delete) in scripts so they don’t block waiting for confirmation.
  • Combine --json with jq for all scripting — human-readable output can change between CLI versions, but JSON is stable.
  • Set DUVO_PROFILE to target a non-default profile (e.g., staging) without changing your shell’s default: DUVO_PROFILE=staging duvo runs start --agent "$AGENT_ID" --json.
  • Run duvo <command> --help to see the full flag set for any command.