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:
- Creating a new key at Team Settings → API Keys.
- Updating the key in your CI secret store.
- 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.