@devicai/cli
CLI for the Devic AI Platform API. Agent-first — JSON output by default when piped, human-readable in terminal. Single runtime dependency (commander).
Installation
npm install -g @devicai/cli
# or
npx @devicai/cli <command>
Authentication
# Login with API key (validates with a test request, stores in ~/.config/devic/config.json)
devic auth login --api-key devic-xxx
# Check auth status
devic auth status
# Logout (removes stored credentials)
devic auth logout
Environment variables override stored config:
| Variable | Description |
|----------|-------------|
| DEVIC_API_KEY | API key |
| DEVIC_BASE_URL | API base URL (default: https://api.devic.ai) |
Global Options
| Option | Description |
|--------|-------------|
| -o, --output <format> | Output format: json or human. Auto-detected: JSON when piped, human in TTY |
| --base-url <url> | API base URL. Overrides config and env var. Priority: --base-url flag > DEVIC_BASE_URL env > config file > https://api.devic.ai |
| -V, --version | Show version |
| -h, --help | Show help |
Exit Codes
| Code | Meaning | |------|---------| | 0 | Success | | 1 | Error | | 2 | Authentication required | | 3 | Poll timeout |
Errors are written to stderr. In JSON mode: {"error":"...","code":"..."}. In human mode: markdown-formatted error with bold label and inline code.
Human Output Format
When using -o human (or in a TTY), all output is markdown-formatted:
- Lists: Rendered as markdown tables with
| Header | ... |format - Details: Properties with
**Bold Label:** value, IDs in`inline code` - Conversations: Messages labeled
**USER:**/**ASSISTANT:**/**TOOL:** - Status indicators:
[OK]success,[..]in-progress,[!!]needs attention,[XX]failed - Actions: Success messages prefixed with
[OK] - Errors: Written to stderr with
**Error:** messageformat - Pagination: Footer with
**Total:** N | **Offset:** N | ... - Nested objects: Rendered as fenced JSON code blocks
- Thread tasks: Displayed as
[x]/[ ]checklists
This format is designed for both human reading and LLM/agent consumption.
JSON Input
All create/update commands support --from-json <file> (or - for stdin) for complex payloads:
# From file
devic agents create --from-json agent-config.json
# From stdin
echo '{"name":"My Agent"}' | devic agents create --from-json -
Pagination
All list commands support:
| Option | Default | Description |
|--------|---------|-------------|
| --offset <n> | 0 | Items to skip |
| --limit <n> | 10 | Max items (max 100) |
Commands Reference
devic assistants
Manage assistants and chat conversations.
devic assistants list
List all assistant specializations.
devic assistants list [--external] [--offset <n>] [--limit <n>]
| Option | Description |
|--------|-------------|
| --external | Only show externally accessible assistants |
devic assistants get
Get details of a specific assistant.
devic assistants get <identifier>
devic assistants create
Create a new assistant.
devic assistants create [--name <name>] [--description <desc>] [--from-json <file>]
| Option | Description |
|--------|-------------|
| --name <name> | Assistant name |
| --description <desc> | Assistant description |
| --from-json <file> | Read full assistant config from JSON file (- for stdin) |
The --from-json payload supports all assistant specialization fields: name, description, presets, model, provider, imgUrl, state, availableToolsGroupsUids, enabledTools, accessConfiguration, widgetConfiguration, memoryDocuments, structuredOutput, guardrailsConfiguration, codeSnippetIds, availableSkillIds, subagentsIds, maxChatMessages, maxToolResponseInputTokens, contextManagement ({ fullContextTurnDepth?: number, alwaysIncludeUserMessages?: boolean } — send only the most recent N turns in full and summarize older ones).
devic assistants update
Update an existing assistant (partial updates supported).
devic assistants update <identifier> [--name <name>] [--description <desc>] [--enabled-tools <a,b,c>] [--all-tools] [--from-json <file>]
Same options as create, plus the tool selection flags below. Only provided fields will be updated.
| Option | Description |
|--------|-------------|
| --enabled-tools <a,b,c> | Replace the enabled tool names. Use "" to enable none |
| --all-tools | Enable every tool of the assigned tool groups |
--enabled-tools and --all-tools are mutually exclusive. Pass neither and the
current tool selection is left untouched — read it with devic assistants get,
which reports it as all, none, or the explicit list.
Requires CLI 0.14.0 or newer. Earlier versions could only change the selection
through --from-json.
devic assistants delete
Delete an assistant.
devic assistants delete <identifier>
devic assistants chat
Send a message to an assistant. Uses async mode with polling by default.
devic assistants chat <identifier> -m "message" [options]
| Option | Description |
|--------|-------------|
| -m, --message <text> | Required. Message to send |
| --chat-uid <uid> | Continue an existing conversation |
| --provider <provider> | LLM provider override (openai, anthropic, azure, google) |
| --model <model> | Model override |
| --tags <tags> | Comma-separated tags |
| --wait | Async mode + poll for result (default: true) |
| --no-wait | Synchronous mode — blocks until response |
| --from-json <file> | Read full ProcessMessageDto from file (- for stdin) |
When --wait is active, status updates are emitted during polling:
- JSON mode (
-o json): NDJSON lines on stdout:{"type":"chat_status","chatUid":"...","status":"processing","timestamp":1234567890} {"type":"chat_status","chatUid":"...","status":"completed","timestamp":1234567891} - Human mode (
-o human): Readable status lines on stderr:[..] Chat `550e8400...` — **processing** [OK] Chat `550e8400...` — **completed**
Chat polling: 1s initial interval, 1.5x backoff, 10s max, 5min timeout.
Status values: processing, completed, error, waiting_for_tool_response, handed_off.
Status indicators: [OK] completed/active, [..] processing/queued, [!!] paused/waiting, [XX] failed/error.
devic assistants stop
Stop an in-progress async chat.
devic assistants stop <identifier> <chatUid>
devic assistants chats list
List chat histories for an assistant.
devic assistants chats list <identifier> [--omit-content] [--offset <n>] [--limit <n>]
devic assistants chats get
Get a specific chat history.
devic assistants chats get <identifier> <chatUid>
devic assistants chats search
Search chat histories across all assistants with filters.
devic assistants chats search [options]
| Option | Description |
|--------|-------------|
| --assistant <identifier> | Filter by assistant |
| --tags <tags> | Comma-separated tags |
| --start-date <date> | Start date (ISO string) |
| --end-date <date> | End date (ISO string) |
| --omit-content | Exclude chat content |
| --from-json <file> | Read filters from file |
devic agents
Manage agents, execution threads, and costs.
devic agents list
devic agents list [--archived] [--offset <n>] [--limit <n>]
devic agents get
devic agents get <agentId>
devic agents create
devic agents create [--name <name>] [--description <desc>] [--from-json <file>]
[--cron <expr> | --schedule-at <HH:MM> | --every-days <n>]
[--days <list>] [--at <HH:MM>] [--start-date <YYYY-MM-DD>]
[--timezone <tz>] [--no-schedule]
[--environment <environment>]
The --from-json payload supports all agent fields: name, description, assistantSpecialization (with presets, availableToolsGroupsUids, enabledTools, codeSnippetIds, model, provider, subagentsIds, contextManagement), provider, llm, maxExecutionInputTokens, maxExecutionToolCalls, evaluationConfig, subAgentConfig, periodicExecution, sandboxPreprovision, environmentId.
devic agents update
devic agents update <agentId> [--name <name>] [--description <desc>] [--from-json <file>]
[scheduling flags] [--environment <environment>]
Scheduling: the agent runs itself
Devic has a scheduler. An agent can wake itself up; you do not need an
external cron poking threads create every morning. Three ways to say when,
mutually exclusive:
# A 5-field cron expression
devic agents create --name "Daily report" --cron "0 7 * * 1-5" --timezone Europe/Madrid
# A time of day, optionally narrowed to weekdays
devic agents update <agentId> --schedule-at 09:00 --days monday,friday --timezone Europe/Madrid
# Every N days from an anchor date. Cron cannot express this: its day-of-month
# restarts every month, so "every 14 days" is not a cron expression.
devic agents update <agentId> --every-days 14 --at 08:30 --start-date 2026-01-06
# Off
devic agents update <agentId> --no-schedule
--timezone takes an IANA name and defaults to UTC, which is rarely what a
business report wants. Bad input is refused before the request: an invalid time
of day, an unknown weekday, or two scheduling modes at once.
Environment: the machine, secrets and tools
devic agents create --name "Reporter" --environment "Reporting box"
devic agents update <agentId> --environment null # disconnect
Accepts an environment _id or its name. See devic environments below.
devic agents delete
devic agents delete <agentId>
devic agents threads
Manage agent execution threads.
devic agents threads create
Create and optionally poll a new thread.
devic agents threads create <agentId> -m "task" [options]
| Option | Description |
|--------|-------------|
| -m, --message <text> | Required. Initial message/task |
| --tags <tags> | Comma-separated tags |
| --wait | Poll until terminal state |
| --from-json <file> | Read thread config from file |
When --wait is active, status updates are emitted during polling:
- JSON mode (
-o json): NDJSON lines on stdout:{"type":"thread_status","threadId":"...","state":"processing","tasks":[...],"timestamp":1234567890} {"type":"thread_status","threadId":"...","state":"completed","tasks":[...],"timestamp":1234567891} - Human mode (
-o human): Readable status lines on stderr:[..] Thread `thread-456` — **processing** (tasks: 1/3) [OK] Thread `thread-456` — **completed** (tasks: 3/3)
Thread polling: 2s initial interval, 1.5x backoff, 15s max, 10min timeout.
Terminal states: completed, failed, terminated.
Actionable states (returned to caller): paused_for_approval.
devic agents threads list
devic agents threads list <agentId> [options]
| Option | Description |
|--------|-------------|
| --state <state> | Filter by state |
| --start-date <date> | Start date filter |
| --end-date <date> | End date filter |
| --date-order <order> | Sort: asc or desc |
| --tags <tags> | Comma-separated tags |
| --omit-content | Exclude thread content from response (returns metadata and state only). Significantly reduces payload size for large thread lists |
devic agents threads get
devic agents threads get <threadId> [--with-tasks] [--grep <pattern>]
| Option | Description |
|--------|-------------|
| --with-tasks | Include task details |
| --grep <pattern> | Filter thread content to only show messages matching the pattern (case-insensitive). Useful for finding specific data within large threads without scanning the full content manually |
devic agents threads approve
devic agents threads approve <threadId> [-m "message"]
devic agents threads reject
devic agents threads reject <threadId> [-m "message"]
devic agents threads pause
devic agents threads pause <threadId>
devic agents threads resume
devic agents threads resume <threadId>
devic agents threads complete
Manually set a thread's final state.
devic agents threads complete <threadId> --state <COMPLETED|FAILED|CANCELLED|TERMINATED>
devic agents threads evaluate
Trigger AI evaluation of a completed thread.
devic agents threads evaluate <threadId>
devic agents costs
Track agent execution costs.
devic agents costs daily
devic agents costs daily <agentId> [--start-date YYYY-MM-DD] [--end-date YYYY-MM-DD]
devic agents costs monthly
devic agents costs monthly <agentId> [--start-month YYYY-MM] [--end-month YYYY-MM]
devic agents costs summary
Get today's and current month's cost summary.
devic agents costs summary <agentId>
devic tool-servers
Manage tool servers, their definitions, and individual tools.
Three kinds of tool server, told apart by the type column: http (tool
definitions you wrote, called against a base URL), mcp (a remote MCP server),
and integration (an app connected in Devic — Gmail, Drive, HubSpot). An
integration has no URL and no stored definition; its tools live in the connected
app, and the target column names that app, with (not connected) when no
account is linked.
Credentials come back masked (••••••••). Sending a masked value back in an
update keeps the stored secret, so get → edit → update is safe; send a real
value to change it.
devic tool-servers list
devic tool-servers list [--offset <n>] [--limit <n>] [--project <project>]
--project accepts an _id, an identifier, or a name.
devic tool-servers get
devic tool-servers get <toolServerId>
devic tool-servers create
devic tool-servers create [--name <name>] [--url <url>] [--description <desc>] [--from-json <file>]
The --from-json payload supports: name, description, url, identifier, enabled, mcpType, toolDefinitions, authenticationConfig, imageUrl.
url, each tool's endpoint and the advanced body template accept {{...}}
template references resolved at call time, so one tool server can target a
different upstream per environment instead of being cloned:
{{metadata.<field>}}— thread or chat metadata (bare{{field}}is the same thing){{env.<VAR>}}— env var of the Environment connected to the agent or assistant{{fields.<apiName>}}— published-MCP connection field, resolved by the wrapper only
Unknown references resolve to an empty string. Do not confuse these with the
${arg} / {arg} placeholders in endpoint, which are tool arguments the model
supplies.
devic tool-servers create --name "Billing API" --url 'https://{{env.BILLING_HOST}}'
devic tool-servers update
devic tool-servers update <toolServerId> [--name <name>] [--url <url>] [--description <desc>] [--enabled <bool>] [--from-json <file>]
devic tool-servers delete
devic tool-servers delete <toolServerId>
devic tool-servers clone
devic tool-servers clone <toolServerId>
devic tool-servers definition
Get the full tool server definition.
devic tool-servers definition <toolServerId>
devic tool-servers update-definition
devic tool-servers update-definition <toolServerId> --from-json <file>
devic tool-servers tools
Manage individual tools within a tool server.
devic tool-servers tools list
devic tool-servers tools list <toolServerId> [--available] [--limit <n>] [--cursor <cursor>]
Lists the tools the server exposes. On an integration, --available browses
everything the connected app offers instead, with an enabled column marking
which of them this server uses. That catalogue is paged — follow the --cursor
the output suggests rather than counting against the total, which includes tools
the app has deprecated and are not listed.
devic tool-servers tools get
devic tool-servers tools get <toolServerId> <toolName>
devic tool-servers tools add
devic tool-servers tools add <toolServerId> --from-json <file>
JSON structure:
{
"type": "function",
"function": {
"name": "tool_name",
"description": "What it does",
"parameters": {
"type": "object",
"properties": { "param": { "type": "string" } },
"required": ["param"]
}
},
"endpoint": "/api/path/${param}",
"method": "GET",
"pathParametersKeys": ["param"]
}
devic tool-servers tools update
devic tool-servers tools update <toolServerId> <toolName> --from-json <file>
devic tool-servers tools delete
devic tool-servers tools delete <toolServerId> <toolName>
devic tool-servers tools test
Test a tool call with parameters.
devic tool-servers tools test <toolServerId> <toolName> --from-json <file>
The JSON file should contain the parameters object: {"city": "London"}.
devic integrations / devic triggers
Browse the catalogue of connectable apps (Gmail, Drive, HubSpot, …) and the events each emits, connect an account, and manage the triggers that start an agent or assistant from those events.
devic integrations list --search gmail
devic integrations triggers gmail # an app's event types
devic integrations connect gmail --wait # connect + build the tool server
devic triggers create --tool-server <id> --agent <id> --trigger GMAIL_NEW_GMAIL_MESSAGE
devic triggers events <id>
For detailed documentation, see integrations-and-triggers.md.
devic documents
Manage knowledge documents (markdown). List, read, create, update and manage versions.
devic documents list [--project <p>] [--folder <id>] [--file-type md]
devic documents get <documentId>
devic documents create --name <name> [content source] [--folder <id>] [--tags <t...>] [--as-skill]
devic documents update <documentId> [fields...] [--tags <t...>] [--as-skill|--no-skill]
devic documents versions list <documentId>
devic documents versions revert <documentId> <version>
devic documents attach <documentId> --target-type agent|assistant|environment --target-id <id>
devic documents usage <documentId>
devic documents folders create --name <name> [--as-skill] [--tags <t...>]
--folder on create, --tags and --as-skill/--no-skill require CLI ≥ 0.18.0.
--as-skill publishes the document in the skills catalog; its name and
description then come from the YAML frontmatter at the top of the markdown.
Providing document content (create / update)
The markdown body is never read from a shell redirect by itself. You must pass it through one of these content sources:
| Source | Example |
|--------|---------|
| Inline | devic documents update <id> --content "# Title\n..." |
| From a file | devic documents update <id> --from-file SKILL.md |
| From stdin (pipe) | cat SKILL.md \| devic documents update <id> --from-stdin |
⚠️ Do NOT use
devic documents update <id> < file.mdon its own. A bare shell redirect is only picked up when the process detects a piped stdin; the reliable forms are--from-file <path>or a real pipe with--from-stdin. To avoid silent mistakes,updatenow refuses an empty payload (it errors out instead of returning a misleading success), and the response includesversionCreated: true|falseso you can confirm a new version was actually written. Always check that flag after updating.
update only creates a new version when the content actually changes; updating
with identical content (or only metadata like --name/--folder) reports
versionCreated: false.
devic skills
Browse and install Devic skills (documents or folders flagged as skills, in the
SKILL.md format) into your local coding agents. Mirrors the skills.sh model:
each skill is written as a folder named after the skill into the agent's skills
directory, and an install registry (lockfile) tracks what is installed so it can
be refreshed with update.
devic skills list (alias: ls)
List the skills catalog.
devic skills list [--tag <tag...>] [--search <text>] [--project <projectId>] [--limit <n>] [--page <n>]
| Option | Description |
|--------|-------------|
| --tag <tag...> | Filter by tag (repeatable, e.g. --tag cli --tag qa) |
| --search <text> | Free-text search over name/description |
| --project <projectId> | Filter by project id |
| --limit <n> | Page size (max 200, default 100) — CLI ≥ 0.18.0 |
| --page <n> | 1-based page number — CLI ≥ 0.18.0 |
Human output shows a table with id, name, type (document/folder), tags, and
usage stats: linked agents, assistants, and reads (how often the skill
was consulted by agents via the knowledge tools).
devic skills create
Create a folder-skill: the folder plus its SKILL.md manifest, with the
name/description frontmatter already written. Requires CLI ≥ 0.18.0.
devic skills create <name> [-d <description>] [--tags <t...>] [--project <projectId>] [--parent <folderId>] [--from-file <path>]
| Option | Description |
|--------|-------------|
| -d, --description <text> | One-line description. It is what the model reads before deciding to load the skill, so phrase it as a trigger ("How to … when …") |
| --tags <tags...> | Category tags |
| --project <projectId> | Scope the skill to a project |
| --parent <folderId> | Create it inside an existing folder |
| --from-file <path> | Replace the generated stub manifest with your own markdown |
The output prints the folder id — that is what you attach as
knowledgeSkills: [{ id, type: "folder" }] — and the manifest document id.
devic skills create "Incident triage" -d "How to triage a production incident." --tags ops
devic skills create "Release drill" --from-file ./SKILL.md
devic skills get
Show one catalog entry (by id or name), including its tags and usage counts. Requires CLI ≥ 0.18.0.
devic skills get <id|name>
devic skills tree
Show the files a skill contains — what an install would download — without recording an install. Requires CLI ≥ 0.18.0.
devic skills tree <id|name> [--out <dir>]
--out <dir> also writes the files to disk, preserving their relative paths.
Useful to inspect or diff a skill before installing it.
devic skills tags
List the distinct tags across skills (for --tag filtering).
devic skills tags [--project <projectId>]
devic skills install (alias: add)
Download a skill's whole tree into your coding agents. The skill is resolved by id or name. Records the install locally (lockfile) and in Devic (install counter + per-user install date).
devic skills install <id|name> [-a <agents...>] [-g]
| Option | Description |
|--------|-------------|
| -a, --agent <agents...> | Target agents: claude-code, codex, cursor, opencode, cline, or * for all. Auto-detected from installed agents when omitted (falls back to claude-code) |
| -g, --global | Install to the user-level (global) agent directories instead of the project ones |
Install locations (a folder named after the skill is created inside):
| Agent | Project path | Global path |
|-------|--------------|-------------|
| claude-code | .claude/skills/ | ~/.claude/skills/ |
| codex | .agents/skills/ | ~/.codex/skills/ |
| cursor | .agents/skills/ | ~/.cursor/skills/ |
| opencode | .agents/skills/ | ~/.config/opencode/skills/ |
| cline | .agents/skills/ | ~/.agents/skills/ |
A folder-skill is written with its full tree (SKILL.md + referenced files, at
their relative paths). A document-skill is written as a single SKILL.md.
devic skills update
Refresh installed skills to their latest version. Compares the installed version (from the lockfile) against Devic and only rewrites what changed.
devic skills update [id|name] [-g]
Omit the argument to update every installed skill; pass an id/name to update just
one. -g targets the global install registry.
devic skills installed
List what is installed locally (from the lockfile), with the installed version and date.
devic skills installed [-g]
The lockfile lives at .devic/skills.json (project) or ~/.devic/skills.json
(global).
devic environments
The reusable package an agent runs on: the sandbox and its snapshot, the
knowledge and tools connected entities inherit, and the encrypted variables they
can read. Aliased as devic envs. Every command takes the environment by
_id or by name.
devic environments list [--project <project>]
devic environments get <environment>
devic environments create --name <name> [--runtime node24|node22|python3.13]
[--init-script-file <file>] [--env KEY=VALUE ...]
[--snapshots] [--evolving-snapshot | --fixed-snapshot]
[--per-tenant-snapshots] [--auto-extend] [--persist]
devic environments update <environment> [same flags, each with a --no- form] [--unset-env KEY]
devic environments delete <environment>
devic environments connections <environment>
devic environments connect <environment> agent|assistant <entityId>
devic environments disconnect <environment> agent|assistant <entityId>
devic environments snapshot init <environment> # bake or re-bake the base
devic environments snapshot tenants <environment>
devic environments sessions list <environment>
devic environments clis <environment>
Two things that are easy to get wrong:
--envmerges over the stored map, it does not replace it. Values are encrypted at rest and read back masked; the merge is what stops adding one variable from deleting the others. This is where a database password belongs — not in a thread message, which is stored and readable back indefinitely.--snapshotsalone gives a fixed snapshot. Sessions start from the saved state and cannot write back.--evolving-snapshotis what lets every session replace it. Fixed is the default because it stops a stray agent run from destroying a machine you spent time provisioning.
devic sandbox
A real Linux machine started on an environment. The lifecycle is explicit because none of it is free: starting provisions and bills a machine, and stopping is what saves the snapshot.
devic sandbox start <environment> [--timeout <minutes>] [--tenant <id>] [--force]
devic sandbox status <environment>
devic sandbox exec <environment> <command> [--cwd <path>] [--sudo]
devic sandbox stop <environment> [--save | --no-save] [--force]
devic sandbox ls <environment> [path]
devic sandbox cat <environment> <path>
devic sandbox write <environment> <path> (--content <text> | --file <local> | --url <src>)
After start, the other commands find the live session on their own — no
sandbox id to carry between calls.
exec takes a line of shell, not argv. Quote it: pipes, && and cd belong
inside the command.
devic sandbox exec "Reporting box" 'cd /workspace && ./report.sh | tail -20'
Whether stop saves depends on the environment, matching the dashboard
terminal: an evolving snapshot saves by default, a fixed one does not unless you
pass --save. Saying nothing never overwrites a snapshot someone froze on
purpose — and --save on a fixed environment is how you provision one.
For the provisioning workflow (install once, bake, then leave it alone), the
snapshot modes, per-tenant snapshots, the SESSION_ACTIVE and
SNAPSHOT_SAVE_IN_PROGRESS conflicts and cleanup, see
environments-and-sandboxes.md.
devic snippets
Code snippets: functions written once and given to agents and assistants as
tools. Alias: devic code-snippets. Each defines main(input); parameters
is the JSON Schema of input, and the model calls it by its toolName. The
full contract (how the code runs, versions, validation) is in the devic-api
skill, code-snippets.md.
devic snippets list [--search <text>] [--language <lang>] [--enabled | --disabled]
[--tag <tag>] [--project <id>] [--limit <n>] [--offset <n>]
devic snippets get <id> [--code-only]
devic snippets create --name <snake_case> --description <text>
(--code-file <file> | --code <source>) --parameters <json|file>
[--language <lang>] [--tags <a,b>] [--project <id>] [--disabled]
[--from-json <file>]
devic snippets update <id> [--name ...] [--description ...] [--code-file ... | --code ...]
[--parameters <json|file>] [--language <lang>] [--tags <a,b>]
[--enabled | --disabled] [--test-cases <json|file>]
[--expected-version <n>] [--from-json <file>]
devic snippets delete <id>
devic snippets test <id> [--input <json|file>]... [--inputs-file <file>] [--timeout <ms>]
| Option | Description |
|--------|-------------|
| --code-file <file> | Source defining main(input); - reads stdin. The language is inferred from .js, .mjs, .cjs, .ts or .py unless --language is given |
| --parameters <json\|file> | JSON Schema of input (type: "object"), inline or a file |
| --from-json <file> | Full payload: name, description, language, code, parameters, tags, testCases, enabled, projectId. Flags override it |
| --test-cases <json\|file> | Replaces the saved test cases: [{ "name": "...", "input": { ... } }] |
| --expected-version <n> | Refuse with 409 when the snippet is no longer at version n |
| --code-only | get prints only the source, to redirect into a file |
| --input <json\|file> | One input object; repeat it for several runs. Without inputs, the saved test cases run |
update changes only what you pass. A change to the name, description,
language, code or parameters saves a new version; --enabled/--disabled,
tags and test cases do not.
test exits 1 when any run fails, so it works as a check in a script. A
failing run is still printed with its error: an input that does not match
parameters fails before the code runs, with the schema error.
# Round trip: edit the code locally and check it before agents get it
devic snippets get 69cd0438... --code-only > distance.js
$EDITOR distance.js
devic snippets update 69cd0438... --code-file distance.js --expected-version 2
devic snippets test 69cd0438... && echo "ready"
To give a snippet to an agent, add its id to
assistantSpecialization.codeSnippetIds (assistants: top-level
codeSnippetIds) with --from-json. If the entity has an enabledTools
list, add the snippet's toolName to it too (for example with
--enabled-tools on assistants), or runs leave the snippet out.
devic feedback
Submit and view feedback on chat messages and thread messages.
devic feedback submit-chat
devic feedback submit-chat <identifier> <chatUid> --message-id <id> [options]
| Option | Description |
|--------|-------------|
| --message-id <id> | Required. Message UID to give feedback on |
| --positive | Positive feedback (thumbs up) |
| --negative | Negative feedback (thumbs down) |
| --comment <text> | Feedback comment |
| --from-json <file> | Read full feedback payload from file |
devic feedback list-chat
devic feedback list-chat <identifier> <chatUid>
devic feedback submit-thread
devic feedback submit-thread <threadId> --message-id <id> [options]
Same options as submit-chat.
devic feedback list-thread
devic feedback list-thread <threadId>
Usage Examples
Send a message and get the result
# Simple chat
devic assistants chat default -m "What is the capital of France?"
# Continue a conversation
CHAT_UID=$(devic assistants chat default -m "Hello" -o json | jq -r '.chatUID')
devic assistants chat default -m "Tell me more" --chat-uid "$CHAT_UID"
Create an agent and run a thread
# Create agent from JSON
cat <<'EOF' | devic agents create --from-json -
{
"name": "Data Analyst",
"description": "Analyzes data and creates reports",
"assistantSpecialization": {
"presets": "You are a data analyst. Analyze data and provide insights.",
"model": "gpt-4o"
}
}
EOF
# Run a thread and wait for completion
devic agents threads create <agentId> -m "Analyze Q4 sales data" --wait
Handle thread approvals in a script
# Create thread
RESULT=$(devic agents threads create <agentId> -m "Delete old records" --wait -o json)
STATE=$(echo "$RESULT" | jq -r '.state')
if [ "$STATE" = "paused_for_approval" ]; then
THREAD_ID=$(echo "$RESULT" | jq -r '._id')
devic agents threads approve "$THREAD_ID" -m "Approved"
fi
Set up a tool server with tools
# Create tool server
cat <<'EOF' | devic tool-servers create --from-json -
{
"name": "Weather API",
"url": "https://api.weather.example.com",
"toolDefinitions": [{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get weather for a city",
"parameters": {
"type": "object",
"properties": { "city": { "type": "string" } },
"required": ["city"]
}
},
"endpoint": "/weather/${city}",
"method": "GET",
"pathParametersKeys": ["city"]
}]
}
EOF
# Test the tool
echo '{"city":"London"}' | devic tool-servers tools test <serverId> get_weather --from-json -
Search for specific data in agent threads
# List threads without content (fast, metadata only)
devic agents threads list <agentId> --omit-content --limit 50
# Find a specific email within a thread's content
devic agents threads get <threadId> --grep "user@example.com"
# Combine: list threads, then search each for a keyword
devic agents threads list <agentId> --omit-content -o json | \
jq -r '.[].threadId' | \
while read tid; do
RESULT=$(devic agents threads get "$tid" --grep "target@email.com" -o json)
COUNT=$(echo "$RESULT" | jq '.threadContent | length')
if [ "$COUNT" -gt "0" ]; then echo "Found in thread: $tid"; fi
done
A nightly report against a private database
The shape all of this exists for: no external scheduler, and no credential in a message.
# 1. An environment holding the credentials and the tooling.
devic environments create --name "Reporting box" \
--runtime python3.13 \
--env "DATABASE_URL=postgres://reporter:...@db.internal/analytics" \
--env "SSH_PRIVATE_KEY=$(cat ~/.ssh/reporter_ed25519)" \
--init-script "pip install psycopg2-binary && mkdir -p /workspace" \
--snapshots --auto-extend
# 2. Bake the snapshot once, so no run pays for the install.
devic environments snapshot init "Reporting box"
# 3. Try it by hand before trusting it to a schedule.
devic sandbox start "Reporting box" --timeout 10
devic sandbox write "Reporting box" /workspace/report.py --file ./report.py
devic sandbox exec "Reporting box" 'cd /workspace && python report.py'
devic sandbox stop "Reporting box" # saves into the snapshot
# 4. An agent that wakes itself at 07:00 on weekdays, on that machine.
devic agents create --name "Nightly analytics report" \
--environment "Reporting box" \
--cron "0 7 * * 1-5" --timezone Europe/Madrid
Step 3 is the one worth keeping: a sandbox you drive by hand is the same machine the agent will get, so a script verified there is verified for real.
Pipe JSON between commands
# Get all completed threads and their evaluations
devic agents threads list <agentId> --state COMPLETED -o json | \
jq -r '.[].threadId' | \
while read tid; do devic agents threads evaluate "$tid"; done
Scan to join WeChat group