> ## Documentation Index
> Fetch the complete documentation index at: https://docs.erdo.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# CLI

> Drive Erdo from the terminal or CI — agents, pages, evals, datasets, and more

# CLI

`@erdoai/cli` wraps the same `/v1` API the [MCP server](/mcp/overview) exposes, so
anything an AI assistant can do over MCP you can do (and script) from a shell.

## Install

Requires Node.js 18 or later.

```bash theme={null}
npm install -g @erdoai/cli   # then run `erdo`
```

Or run it without installing:

```bash theme={null}
npx @erdoai/cli --help
```

Verify and sign in:

```bash theme={null}
erdo --version
erdo login                 # browser sign-in (multi-account, like gh)
erdo whoami
```

## Update

```bash theme={null}
erdo update                # checks npm and installs the latest if newer
```

Equivalent to `npm install -g @erdoai/cli@latest`.

## Auth & orgs

Accounts are stored at `~/.config/erdo/config.json`, one active at a time.

```bash theme={null}
erdo login                      # browser OAuth; mints + stores a token
erdo login --key erdo_api_...   # headless/CI: paste an API key instead
                                # over ssh: press c to copy the login URL to
                                # your local clipboard, authorize in your local
                                # browser, paste the localhost callback URL back
erdo auth status                # list accounts (* = active)
erdo auth switch [email]        # switch active account (auto-toggles with two)
erdo logout [email]

erdo org list                   # your orgs (* = active)
erdo org use [id|slug]          # set the active org for this account
erdo --org acme <command>       # one-off override for a single command
```

Env overrides for CI: `ERDO_API_KEY`, `ERDO_ORG`, `ERDO_API_URL`, `ERDO_ACCOUNT`.

### Pin the org in automation

The active org set by `erdo org use` lives in machine-global config, so it is
shared across every concurrent session — another shell (or another job) running
`org use` switches the org for all of them. A command that only reads is
harmless, but a mutation that lands in the wrong org is not, so any command that
writes prints the org it is about to act in on stderr before it fires:

```
org: acme (active org — pass --org to pin)
org: acme (pinned)                          # when --org / ERDO_ORG was set
```

For scripts and CI, don't rely on the shared active org — pin it explicitly with
`erdo --org <id|slug> <command>` or by setting `ERDO_ORG`, so the command acts in
the org you intended regardless of what any concurrent session did. Runs that
build real artifacts enforce this: `erdo eval run` refuses an artifact-building
suite unless the org is pinned (see [Evals](/evals#cli)).

## API tokens

An API token is an **account-level credential** — it acts as you in any org you
belong to, not just one. The org stored on a token is only its **default** (used
when a request names no org); `erdo --org <x> <command>` or `erdo org use <x>`
steers any command to another of your orgs, and the backend re-checks your
membership on every request.

```bash theme={null}
erdo token create --name ci                 # mint a token; the secret prints ONCE
erdo token create --name ci --expires-days 90
erdo token create --name ci --org acme      # set the token's default org
erdo token list                             # your tokens (never shows the secret)
erdo token revoke <id>                       # revoke a token by id
```

Store the token the moment it's printed — it can't be retrieved again. Use it in
CI via `ERDO_API_KEY`, or authenticate an interactive session with
`erdo login --key <token>`. There is deliberately no way for an AI assistant
(MCP) to mint tokens — creation lives only on the CLI/REST surface a human drives.

## Manager accounts

A **manager account** operates many client ("managed") orgs with **one** credential —
the pattern a portal uses to provision and run an org per customer without a pasted
key per tenant. Your active org is the manager (you must be an admin/owner of it);
`erdo org managed create` provisions a client org, and `erdo org managed key` mints
a single **manager key** that acts inside any managed org via `--org <slug>`.

```bash theme={null}
erdo org managed create --name "Acme Corp"   # provision a client org
erdo org managed list                          # slug  name  role  id
erdo org managed key                           # mint/rotate the manager key; shown ONCE
erdo org managed revoke acme-corp-a1b2c3d4     # stop managing (keeps an audit record)

# operate a managed org with the manager key:
erdo --org acme-corp-a1b2c3d4 datasets list
```

The manager key is one non-expiring credential for every org you manage — target a
specific one with `erdo --org <slug>` (or the `X-Organization-ID` header). Running
`erdo org managed key` again rotates it. See [Manager accounts](/manager-accounts)
for the full portal flow and the REST/MCP surface.

## Project context

Use the global `--project <uuid>` flag when a command should run inside an Erdo
project. Pair it with `--org` whenever the token can operate more than one
organization:

```bash theme={null}
erdo --org acme project list
erdo --org acme --project 01234567-89ab-cdef-0123-456789abcdef datasets list
erdo --org acme --project 01234567-89ab-cdef-0123-456789abcdef agent ask "Build the launch page"
```

The project must belong to the selected organization. Project-aware reads are
narrowed to its attached resources and, with contributor access, newly-created
work is attached to it; organization-wide endpoints stay organization-wide.
`ERDO_PROJECT` is the environment-variable equivalent for CI.

## Agents

Running an agent is sending it a message; the artifact-builder produces pages this way.

```bash theme={null}
erdo agent ask "what was revenue last week?" --datasets sales
erdo agent thread --name "landing build"        # -> thread id
erdo agent send <thread> "Build a landing page for ACME ..." --agent erdo.artifact-builder
erdo agent send <thread> "What should I do next?" --context "Current screen: checkout experiment"
erdo agent threads
```

Use `--context` when an application or script knows state that should guide one
turn but should not masquerade as the operator's words in the transcript. The
agent receives it as application context; the thread still shows the exact
message argument as the user message.

`ask` and `send` start the run, print its thread id, and poll until it finishes.
Agent runs routinely take minutes — building a landing page, screening variants —
and a single HTTP request held open that long is cut off by the edge proxy, so
polling is the default. The thread id is printed before the wait begins: if you
interrupt the CLI, or the run pauses for approval, the work carries on server-side
and you pick it back up by thread.

```bash theme={null}
erdo agent ask "Build and publish a landing page for ACME with a lead form ..."
erdo agent wait <thread>              # re-attach and wait for the run to finish
erdo agent messages <thread>          # read the full conversation later
erdo approvals list --status pending  # a run that paused waiting on you
```

A run that paused for approval resumes once you `erdo approvals decide <id> --approve`;
`erdo agent wait <thread>` then picks the wait back up and prints the answer when
the run completes.

Pass `--sync` to hold one request open instead. It returns faster on quick
questions, and it will time out on anything long.

## Pages & artifacts

```bash theme={null}
erdo pages deploy --title "ACME" --html @page.html --js @page.js --public
erdo pages update <artifact-id> --js @page.js
erdo pages update <artifact-id> --title "ACME v2" --html @page.html --css @page.css --public
erdo pages validate --html @page.html
erdo pages list --query "pricing" --created-after 2026-07-01T00:00:00Z
erdo pages get <artifact-id>
erdo pages delete <artifact-id>
erdo pages restore <artifact-id>
```

`list` shows your pages newest first (id, created-at, visibility, title) and filters by title substring (`--query`) or a created-at window (`--created-after` / `--created-before`, RFC3339). Pass `--type <type>` to list any artifact type (charts, tables) instead of just pages. `delete` is a **soft delete** — the page's public link stops working and it drops out of `list`, but `restore` brings it back (private, since deleting revoked its public grant).

`--html/--js/--css` accept `@path` to read a file.

`update` edits an existing page in place (the URL and id stay the same) and **merges** the fields you pass, so you only send what's changing. It takes the same content flags as `deploy` — `--title`, `--html`, `--js`, `--css` (plus the `--datasets` / `--writable-datasets` / `--kv` / `--writable-kv` grants below) — but all are **optional**: pass just `--js` to swap the script while keeping the existing HTML, CSS, and title. `--public` / `--private` change visibility. (Runtime is fixed at create time, so there's no `--runtime` on `update`.)

Datasets and KV stores are wired with read/write grants. `--datasets` / `--kv` grant **read** (for `window.erdo.queryDataset` / `erdo.kv.get`); `--writable-datasets` / `--writable-kv` grant **write** (for `erdo.insertRows` / `erdo.kv.set`):

```bash theme={null}
erdo pages deploy --title "Lead capture" --html @form.html --js @form.js \
  --datasets acme.products \
  --writable-datasets acme.leads \
  --writable-kv campaign-state
```

Without the writable grant those write calls return a permission error. Public pages capturing data from logged-out visitors should write through an event pipeline (`erdo.submitEvent`) instead — see [Build Apps](/apps/build-apps#writing-data).

## Agent runs

Inspect what agents have done (the runs behind ask/send/evals).

```bash theme={null}
erdo runs list --agent erdo.artifact-builder --status failed
erdo runs get <run-id>
```

## Approvals

Some agent actions pause for a human decision. List them and approve/reject so the paused run can continue. See [Approvals](/approvals).

```bash theme={null}
erdo approvals list --status pending
erdo approvals decide <id> --approve                 # --reject to reject
erdo approvals decide <id> --approve --scope always_user
```

## Review queue

The knowledge agents propose, the investigations they open, and the failure signals they keep counting — the queue of things awaiting a human decision. See [the review queue](/review-queue).

```bash theme={null}
erdo reviews list                          # the open queue (decision items first)
erdo reviews list --type knowledge_patch   # only proposed knowledge
erdo reviews show <id>                      # full payload (the proposed patch body)
erdo reviews decide <id> --apply           # apply a knowledge patch, then resolve
erdo reviews decide <id> --resolve --note "handled in PR #123"
erdo reviews decide <id> --reject
erdo reviews decide <id> --snooze 1440     # snooze for 24 hours (default 7 days)
```

## Datasets

```bash theme={null}
erdo datasets list                                 # slug, type, status, name
erdo datasets query sales "top 10 customers by revenue"   # natural-language query
erdo datasets upload leads.csv --name "Leads"      # file -> queryable dataset (max 20 MB)
```

`upload` accepts CSV, TSV, Excel, JSON, JSONL, PDF, DOCX, TXT, Markdown, and
more — the extension drives type detection. The schema is extracted before the
command returns, so the printed slug is immediately usable with `query`. Larger
files (over 20 MB) go through the web app's resumable upload.

## Integrations

Connect third-party apps and data sources from the terminal. Credential-based
apps (databases, API keys, service accounts) connect in one command; OAuth
apps print a connect URL to open in a browser, and `status` completes the
connection once you've authorized.

```bash theme={null}
erdo integrations list                             # app, status, auth type, name
erdo integrations apps slack                       # search connectable apps
erdo integrations connect postgres \
  -c host=db.example.com -c port=5432 -c database=analytics \
  -c username=readonly -c password=secret
erdo integrations connect slack                    # prints a connect URL to authorize
erdo integrations status slack                     # confirms the connection afterwards
erdo integrations tables postgres                  # list schemas on a connected database
erdo integrations tables postgres public           # list tables + columns in a schema
```

Once a database is connected, make it queryable as a dataset — queries run
live against the source, nothing is copied. Pass the schemas to include (some
integrations allow only one); `tables` lists the selectable ones. Per-table
column listing is available for SQL databases (Postgres, MySQL, and
compatible); warehouses list schemas only.

```bash theme={null}
erdo datasets from-integration postgres --name "Production DB" --schemas public
erdo datasets configure-integration DATASET_ID --segments public --enable-sync
erdo datasets query my-org.production-db "how many orders this week?"
```

## Knowledge

[Knowledge](/knowledge) is your agents' shared brain — definitions, skills, and learnings.

```bash theme={null}
erdo knowledge list
erdo knowledge search "how do we define active users" --limit 5
erdo knowledge visibility <id> public    # publish to external surfaces; 'workspace' to un-publish
```

Entries are `workspace` (organization-internal) by default; `public` opts an
entry into anonymous external surfaces such as the
[website voice widget](/voice-widget) — a draft is approved in the same step
and goes live immediately.
See [Knowledge visibility](/knowledge#visibility--workspace-vs-public).

## KV (collections)

Named [KV stores](/apps/build-apps#per-page-state-kv) (collections) are Erdo's shared key/value store — the canonical config and values (pricing, targets, brand tokens) that pages read, Knowledge bodies reference as `{{slug.key}}`, and agents resolve. One store, consistent everywhere.

```bash theme={null}
erdo kv list
erdo kv create pricing                   # slug: lowercase letters, digits, hyphens
erdo kv get pricing monthly
erdo kv set pricing monthly '"$29"'      # value parsed as JSON, else stored as a string
erdo kv set targets q3 '{"arr": 1200000, "logos": 40}'
erdo kv delete pricing monthly
```

## Automations

Automations run on a schedule — either an agent that reasons each tick, or a zero-LLM script that runs a deterministic check. See [Automations](/automations).

```bash theme={null}
erdo automations list                              # id, state, name
erdo automations run <id>                          # trigger now
erdo automations disable <id>                      # pause a misbehaving automation
erdo automations enable <id>                       # re-enable a disabled automation

# edit in place — every flag is optional and merges over the stored automation
erdo automations update <id> --name "Lead alert (v2)"
erdo automations update <id> --interval 30 --timezone America/New_York
erdo automations update <id> --instructions "Summarise yesterday's leads"   # agent automation
erdo automations update <id> --script-file alert.js                          # scripted automation
```

`update` changes only the fields you pass. Edit an agent automation's prompt with `--instructions`, or a scripted automation's body with `--script-js` / `--script-file` — sending the wrong one for the automation's kind is rejected rather than silently ignored. `list` shows each automation's `kind` (`agent` or `script`).

## Evals

```bash theme={null}
erdo eval suites
erdo eval create landing-variations --agent erdo.artifact-builder --evaluate-artifact --no-cron \
  --case '{"name":"voice","input":"{\"artifact_kind\":\"landing_page\",\"description\":\"...\"}","rubric":[{"criterion":"voice widget loads","weight":2}]}'
erdo eval update landing-variations --agent erdo.data-question-answerer  # only passed flags change
erdo eval run landing-variations --watch        # CI gate: non-zero if a case fails
erdo eval results <run-id>
erdo eval case add <suite> --name x --input "..." --rubric '[{"criterion":"...","weight":1}]'
erdo eval case add <suite> --name y --input "..." --evaluator '{"type":"script","script":"function evaluate(ctx){return {score:5,passed:true,reasoning:\"ok\"}}"}'
# multi-step flow: --setup turns run first (same thread/agent), then --input is the evaluated turn
erdo eval case add <suite> --name voice --setup "Create a voice concierge widget for Lumen Yoga" \
  --input "Build the landing page wired to that concierge" --rubric '[{"criterion":"voice widget loads","weight":2}]'
```

See [Evals](/evals) for the evaluator model (LLM rubric vs deterministic script).

## Workstreams

[Workstreams](/workstreams) track multi-step business work — campaigns, lead engines, comms loops — with phases, an event log, and overall state.

```bash theme={null}
# list / inspect
erdo workstream list                       # all workstreams in the active org
erdo workstream list --status active blocked
erdo workstream get acme-lead-engine-2026-06
erdo workstream events acme-lead-engine-2026-06 --limit 50

# create
erdo workstream create \
  --project acme \
  --slug acme-lead-engine-2026-06 \
  --title "Acme lead engine" \
  --description "Stand up the Acme outbound lead engine"

# drive
erdo workstream phase-add acme-lead-engine-2026-06 --phase-slug brand-brief --title "Brand brief"
erdo workstream log acme-lead-engine-2026-06 "Landing pages live, monitoring conversions"
erdo workstream set-state acme-lead-engine-2026-06 --status awaiting_user --description "Awaiting brand sign-off"
```

## Experiments

[Experiments](/experiments) are structured tests — hypothesis, variants, decision rule — with an append-only observation log. They live on their own or inside a workstream.

```bash theme={null}
# inspect
erdo experiment list --status running
erdo experiment get acme-cpl-ab
erdo experiment observations acme-cpl-ab --type metric_read   # the loop's evidence
erdo experiment calibration --project acme   # each judge's track record vs measured reality
erdo experiment policy acme-cpl-ab                            # decision-policy state (page experiments)

# create + drive
erdo experiment create \
  --project acme \
  --slug acme-cpl-ab \
  --title "Landing page CPL A/B" \
  --workstream acme-lead-engine-2026-06 \
  --primary-metric cost_per_lead \
  --hypothesis "Variant B's shorter form lifts conversion"
erdo experiment set-status acme-cpl-ab running
erdo experiment decide acme-cpl-ab --decision ship --outcome "B cut CPL 22% over 2 weeks, shipping"
```
