# CLI

{% hint style="success" %}
**New to toani?** Start with the [Quickstart](localfile://path?./quickstart) for the 2-minute happy path. This page is the full CLI reference with every command, flag, credential type, and troubleshooting entry.
{% endhint %}

**In 10 minutes**, you'll have an AI agent that can log into services, fetch data, and run tasks for you, using credentials you never hand over in plaintext. Everything sensitive lives inside a TEE sandbox; your AI tools, like Claude Code, only sees results.

***

## How the pieces fit together

<figure><img src="/files/GZp6UahDbMhvDe1qfJso" alt=""><figcaption></figcaption></figure>

**The short version**: credentials and tokens are **created in the** [**Dashboard**](https://dashboard.toani.ai) (not in the CLI). The CLI is the local gateway that stores your token and drives TEE-sandboxed execution. AI tools talk to the CLI through a Skill.

### Mental model (read this first)

Four truths that prevent most confusion:

1. **The CLI does not create credentials or tokens.** Both live in the Dashboard. The CLI consumes a `credential_id` and a bearer token; it never mints either.
2. **`sandbox` is a remote TEE browser**, not your local Chrome. Drive it through `create-session` → `execute` → `terminate`. Sessions don't auto-close.
3. **Global flags must appear before the command group.** `toani --output json credentials list`, not `toani credentials list --output json`. Affected: `--output`, `--base-url`, `--token`.
4. **Credential references in `fill` use object syntax**: `{"$credential":"password"}`, not the string `"$credential.password"`. The string form silently fills as literal text.

Trust `toani --help` over any older doc. Currently exposed groups: `login`, `doctor`, `config`, `credentials` (read-only `list`/`get`), `sandbox`. Anything else (`auth`, `tokens`, `audit`, mutating `credentials`) is not in `@toani/vault-cli@0.0.20`.

***

## Default endpoints

| URL                                              | Purpose                  | Override                               |
| ------------------------------------------------ | ------------------------ | -------------------------------------- |
| [dashboard.toani.ai](https://dashboard.toani.ai) | Browser-facing Dashboard | `TOANI_VAULT_DASHBOARD_BASE_URL`       |
| [api.toani.ai](https://api.toani.ai)             | CLI ↔ backend API        | `TOANI_BASE_URL` (or `--base-url`flag) |

**If you're a cloud user**, these defaults work out of the box. **If you're on a private deployment**, your operator will give you different URLs, set them via environment variables or `toani config init --url <api-url>` before running `toani login`.

***

## Prerequisites

| Requirement         | Version    | Notes                                                                                                              |
| ------------------- | ---------- | ------------------------------------------------------------------------------------------------------------------ |
| Node.js             | >= 22      | Required to run `toani` CLI                                                                                        |
| npm                 | >= 10      | Bundled with Node.js 22                                                                                            |
| jq                  | >= 1.6     | Optional. Used for pretty-printing JSON output; `login` and `doctor`don't need it                                  |
| toani Vault account | -          | Free sign-up via Dashboard email OTP                                                                               |
| Browser             | any modern | For `toani login`. On headless / SSH, use the [Headless variant](/toani-vault/getting-started/cli.md#headless-ssh) |

***

## Workflow at a Glance

### Interactive path (recommended)

<table><thead><tr><th width="69.099609375">#</th><th width="293.51953125">Action</th><th>Where</th></tr></thead><tbody><tr><td>1</td><td>Install Node.js 22+, npm, jq (optional)</td><td>Local shell</td></tr><tr><td>2</td><td>Install the CLI</td><td>Local shell</td></tr><tr><td>3</td><td>(Private deployments only) Point the CLI at your Dashboard</td><td>Local shell</td></tr><tr><td>4</td><td>Run <code>toani login</code>: sign in, create credential, issue token, install Skill</td><td>Guided browser + CLI</td></tr><tr><td>5</td><td>Verify with <code>toani doctor</code></td><td>Local shell</td></tr><tr><td>6</td><td>Invoke from your AI tool</td><td>AI chat interface</td></tr></tbody></table>

### Headless / SSH (no browser)

See the [Headless ](/toani-vault/getting-started/cli.md#headless-ssh)[variant](/toani-vault/getting-started/cli.md#headless-ssh)[ ](/toani-vault/getting-started/cli.md#headless-ssh)[session](/toani-vault/getting-started/cli.md#headless-ssh) for the full flow.

## Step 1: Install Runtime Prerequisites

{% tabs %}
{% tab title="macOS" %}
Install **Node.js 22** and **jq**.

```bash
# macOS (Homebrew)
brew install node@22 jq
```

> **macOS note**: `node@22` is keg-only. If `node --version` doesn't show v22 after install, link it:
>
> ```bash
> brew link node@22 --force --overwrite
> ```
>
> or add `/opt/homebrew/opt/node@22/bin` to your `PATH`.
> {% endtab %}

{% tab title="Windows" %}
{% code expandable="true" %}

```shellscript
# Recommended: install WSL2, then follow the Linux tab                                                  
  wsl --install                                                                                           
                                                                                                          
# Native Windows: download Node.js v22 LTS from https://nodejs.org                                      
# then install jq:                                                                                      
winget install jqlang.jq
```

{% endcode %}
{% endtab %}

{% tab title="Linux" %}

<pre class="language-bash"><code class="lang-bash"># Ubuntu / Debian
curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -
sudo apt install -y nodejs jq

# Cross-platform via nvm (recommended on any Unix)
<strong>nvm install 22 &#x26;&#x26; nvm use 22
</strong></code></pre>

{% endtab %}
{% endtabs %}

Verify:

```bash
node --version   # v22.x.x
npm --version    # 10.x.x
jq --version     # jq-1.6 or later (optional)
```

***

## Step 2: Install the CLI

```bash
npm install -g @toani/vault-cli
```

Verify:

```bash
toani --version
# {"name": "@toani/vault-cli", "version": "0.0.20"}  (or later)
```

> **`toani: command not found`?** See the [Troubleshooting](/toani-vault/getting-started/cli.md#troubleshooting) section for npm global bin / PATH fixes.

***

## Step 3: Point the CLI at your Dashboard

The CLI currently defaults to `http://localhost:8080` (for local development). **Cloud users must set the URL explicitly**, or the CLI will fail with "Connection refused":

```bash
export TOANI_VAULT_DASHBOARD_BASE_URL="https://dashboard.toani.ai"
export TOANI_BASE_URL="https://api.toani.ai"
```

> This default will change in a future version, so cloud users won't need this step. For now, it's required.

If your operator gave you a private deployment URL instead, use those values:

```bash
export TOANI_VAULT_DASHBOARD_BASE_URL="https://dashboard.your-company.com"
export TOANI_BASE_URL="https://api.your-company.com"
```

Add these lines to `~/.zshrc` / `~/.bashrc` so they persist.

Or store the API URL in config:

```bash
toani config init --url https://api.your-company.com
```

> `--url` and `--base-url` both work here; they're aliases. The value is saved as `baseUrl` in `~/.toani/config.json`.

***

## Step 4: Run `toani login`

One command that signs you in, creates a credential, issues a token, and (optionally) installs the Skill into your AI tool.

```bash
toani login
```

<details>

<summary><strong>4.1 Sign in to the Dashboard</strong></summary>

The CLI opens your browser to the Dashboard sign-in page.

1. Enter your email and submit the 6-digit OTP from your inbox.
2. On first login, set a **Display name** and click **Continue**.
3. **When the browser lands on the Dashboard home page (Credentials / API Tokens / Audit in the sidebar)**, return to the terminal and press Enter.

</details>

<details>

<summary><strong>4.2 Create a credential</strong></summary>

**Pick the type that matches what you have, not what the service is called.**

| You want the AI to...                   | Credential type       | Example services                                                    |
| --------------------------------------- | --------------------- | ------------------------------------------------------------------- |
| Call an API with a single secret string | `api_key`             | Anthropic, OpenAI, OpenRouter, Stripe, SendGrid                     |
| Log into a website (form-based)         | `username_password`   | Gmail, LinkedIn, internal dashboards, `test-web.zk.me`              |
| Use a token someone already issued you  | `oauth_token`         | GitHub PAT, Slack bot token, Notion integration (refresh-only flow) |
| Connect to a database                   | `database_connection` | Postgres, MySQL, MongoDB Atlas                                      |
| SSH into a server                       | `ssh_key`             | EC2, bare-metal, internal VMs                                       |
| Authenticate via mTLS                   | `client_certificate`  | Internal corporate APIs, banking sandboxes                          |

> If a service offers both an API key and OAuth, prefer `api_key`, fewer moving parts and easier to rotate.

> **Recommended first test**: store an API key from an LLM provider, whichever one you already have. Claude (Anthropic) and OpenAI both work. The example below uses Claude; swap in your own values as needed.

In the browser (CLI opens the **Credentials** page for you):

1. Click **+ New credential**.
2. Fill in the fields. A complete Claude / Anthropic example:

| Field          | Value                               | Notes                                               |
| -------------- | ----------------------------------- | --------------------------------------------------- |
| **Service ID** | `claude-personal`                   | lowercase, digits, hyphens. Cannot be renamed later |
| **Type**       | **API Key** (identifier: `api_key`) | pick from dropdown                                  |
| **key**        | `sk-ant-...your-anthropic-key...`   | the secret value                                    |
| **secret**     | *(leave blank)*                     | only used for HMAC-style APIs                       |
| **Expires At** | *(leave blank)*                     | no expiry for the credential itself                 |

> Using OpenAI instead? Use `openai-personal` as Service ID and your `sk-...` key. Other providers (OpenRouter, DeepSeek, etc.) follow the same pattern.

The dropdown displays human-readable labels; the identifier shown in parentheses is used in credential references:

| Dropdown label      | Identifier            |
| ------------------- | --------------------- |
| Username & Password | `username_password`   |
| API Key             | `api_key`             |
| OAuth Token         | `oauth_token`         |
| Client Certificate  | `client_certificate`  |
| SSH Key             | `ssh_key`             |
| Database Connection | `database_connection` |

3. Click **Create securely**.

Return to the terminal. You'll see `Done? Ready to generate a token?`. Press Enter.

> Plaintext secret values are never displayed in the Dashboard. After creation, only the credential's `service_id` and field names are shown.

</details>

<details>

<summary><strong>4.3 Issue a token</strong></summary>

The CLI opens the **API Tokens** page.

1. Select the credential you just created (`claude-personal`). You can select multiple if you want one token to access several credentials.
2. The **scope** is pre-filled with `credential:read`. Leave it. This scope lets the CLI create sandbox sessions, look up the credential's `service_id` and fields, and inject field values into sandbox calls inside TEE. It **does not** let the CLI dump plaintext secrets, it **does not** let the CLI issue new tokens, and it **does not** let it edit or delete credentials.
3. Set **expires in** seconds. Suggested values:
   * `3600` (1 hour) for first-time setup / debugging
   * `86400` (1 day) for regular development
   * `604800` (7 days, the max) for production agents. Values above 604800 are **clamped** to 604800, not rejected
4. Click **Generate token**.
5. Click the **📋 Copy** button. **Shown only once.** If you lose it, revoke it in the Dashboard and issue a new one.

Back in the terminal, you'll see a watcher:

```plain_text
  ● Watching clipboard...  (P=paste · Q=quit · 4:59 left)
```

Three ways to hand the token to the CLI:

* **Auto-detect (default)** - as soon as you clicked Copy in the browser, the watcher picks it up and continues. No action needed.
* **Press `P`** to open a masked paste prompt (token is not echoed to terminal or shell history).
* **Clipboard blocked** (common on Linux / some SSH forwarding): press `Q`, then:

  ```bash
  export TOANI_VAULT_TOKEN="v4.local.xxx..."
  # or
  toani config init --token "v4.local.xxx..."
  ```
* **Timer runs out (5 minutes)**: no token is lost. The watcher drops you back to the 3-choice menu. Re-click **Copy** in the browser and pick the appropriate option.

Once provided, the token is:

1. **Validated** against the backend (reachability + auth check).
2. **Stored in the OS Keychain** (macOS Keychain / libsecret on Linux / Windows Credential Manager). Not written to `~/.toani/config.json`.

</details>

<details>

<summary><strong>4.4 Install the Skill (opt-in)</strong></summary>

After the token is validated, `toani login` asks:

```plain_text
? Install the Skill into your AI tool?
  ● Claude Code (~/.claude/skills/toani-vault-cli/SKILL.md)
  ● Codex       (~/.codex/skills/toani-vault-cli/SKILL.md)
  ● Both
  ● Skip
```

Pick the tool(s) you use. The CLI copies the bundled `SKILL.md` into the corresponding directory. Skipped now? Re-run `toani login` later and the prompt reappears.

> **Important**: `npm install -g @toani/vault-cli` alone **does not** register the Skill. The `toani login` prompt above is how it gets installed.

You'll see `🎉 Connected!` and a summary of where the token lives.

</details>

***

## Step 5: Verify with `toani doctor`

```bash
toani doctor
```

Checks 7 items: CLI version, Node.js version, token storage, token format, base URL, server reachable, token valid. All ✓ means you're ready.

| Failed check               | Meaning                              | Fix                                                                            |
| -------------------------- | ------------------------------------ | ------------------------------------------------------------------------------ |
| `✗ Token valid: HTTP 401`  | Token expired or revoked             | `toani login`                                                                  |
| `✗ Server reachable: DNS`  | Wrong base URL or no network         | `toani config show`; `curl $TOANI_BASE_URL`                                    |
| `✗ Node.js version: v20.x` | Node too old                         | Upgrade (see [Step 1](localfile://path?#step-1-install-runtime-prerequisites)) |
| `✗ Token storage: none`    | No token in keychain, env, or config | `toani login` or `toani config init --token ...`                               |

***

## Step 6: Invoke from Your AI Tool

The Skill is named **`toani-vault-cli`**. Invocation syntax depends on the tool.

| AI tool     | Prefix                    | Skill location                              |
| ----------- | ------------------------- | ------------------------------------------- |
| Claude Code | `/toani-vault-cli <task>` | `~/.claude/skills/toani-vault-cli/SKILL.md` |
| Cursor      | `/toani-vault-cli <task>` | `~/.claude/skills/toani-vault-cli/SKILL.md` |
| Codex       | `$toani-vault-cli <task>` | `~/.codex/skills/toani-vault-cli/SKILL.md`  |

You don't write CLI flags or JSON in your prompt. **Describe the task and reference the `service_id`.**&#x54;he AI expands it into the right `toani sandbox ...` chain.

### Two prompt shapes

Almost every task is one of these two patterns. Pick the one that matches your goal.

{% tabs %}
{% tab title="Shape A: Direct API call (no browser)" %}
For services where the credential **is** the auth (Stripe, Linear, Resend, REST APIs, webhooks). The AI uses `sandbox execute --operation-type http_request` end-to-end, no browser involved.

```plain_text
/toani-vault-cli Using my stripe-personal credential, list my 5
most recent customers.
```

What the AI runs under the hood:

1. `toani sandbox create-session --service-id <service> --credential-id <id> --original-intent "..."`
2. `toani sandbox execute <sessionId> --operation-type http_request --params '{"method":"GET","url":"https://api.stripe.com/v1/customers?limit=5","headers":{"Authorization":{"$credential":"api_key","prefix":"Bearer "}}}'`
3. `toani sandbox terminate <sessionId>`

Your API key never appears in the chat transcript, in `--params`, or on disk outside the OS Keychain. The credential reference `{"$credential":"api_key","prefix":"Bearer "}` is resolved inside the TEE.
{% endtab %}

{% tab title="Shape B: Browser login + scraping" %}
For services that require form-based login (Gmail, LinkedIn, internal apps). The AI drives a remote Lightpanda browser inside the TEE.

```plain_text
/toani-vault-cli Using the credential with service_id "gmail-main", sign in
to mail.google.com, and summarize unread emails from today. Terminate the
sandbox session when done.
```

Standard chain the AI follows:

1. `create-session --service-id <s> --credential-id <id>`
2. `execute --operation-type navigate --params '{"url":"..."}'`
3. `bootstrap-page --mode rocket_loader --replay-lifecycle-events true --wait-selector '<form-input>'` (only for Cloudflare Rocket Loader pages)
4. `execute --operation-type wait` for the form
5. `execute --operation-type fill --params '{"selector":"...","value":{"$credential":"username"}}'`
6. `execute --operation-type fill --params '{"selector":"...","value":{"$credential":"password"}}'`
7. `execute --operation-type click` to submit
8. `get-session` or `export-dom --format text` to read the post-login state
9. `terminate`

The plaintext password never leaves the TEE. The DOM export is redacted on sensitive selectors.
{% endtab %}
{% endtabs %}

### Iterating on a task

Once a task works once, refine with micro-prompts in the same conversation. The AI keeps the `sessionId` in context and chains `execute` calls:

* "Now click the **Sent** tab and list the most recent 5 messages."
* "Export that table as CSV."
* "Terminate the session."

### If the Skill doesn't respond

1. Confirm the Skill file exists:

   ```bash
   ls ~/.claude/skills/toani-vault-cli/SKILL.md   # Claude Code / Cursor
   ls ~/.codex/skills/toani-vault-cli/SKILL.md    # Codex
   ```

   If missing, re-run `toani login` and pick the right tool at the Skill prompt.
2. Reload your AI tool:
   * **Claude Code**: `/clear` or restart the terminal session
   * **Cursor**: `Cmd/Ctrl+Shift+P` > "Reload Window"
   * **Codex**: restart the CLI
3. Confirm your AI tool has shell execution enabled (Cursor: Agent mode; Claude Code: bash tool enabled; Codex: default).

***

## Headless / SSH (no browser)

`toani login` needs a browser. On a headless server (CI runner, SSH-only box), skip it and configure manually.

### Step H1: Issue a token from another machine

From any machine with a browser, sign in to the Dashboard and create a credential + token (Steps 4.1-4.3 above, only the browser parts). Copy the token string (starts with `v4.local.`).

### Step H2: Configure on the headless machine

**Option A**: environment variables.

```bash
export TOANI_BASE_URL="https://api.toani.ai"
export TOANI_VAULT_TOKEN="v4.local.xxxxxxxxxxxxxxxxxxx..."
toani doctor
```

**Option B**: persist via `toani config init`.

```bash
toani config init \
  --url https://api.toani.ai \
  --token "v4.local.xxxxxxxxxxxxxxxxxxx..."
toani doctor
```

`config init --token` stores the token in OS Keychain on systems that support it (macOS / Windows / Linux with libsecret). Otherwise, it falls back to `~/.toani/config.json` with `0600` permissions.

### Step H3: (Optional) Install the Skill manually

```bash
# Find where npm installed @toani/vault-cli
npm root -g
# Typically /usr/local/lib/node_modules (or ~/.npm-global/lib/node_modules)

# Copy SKILL.md into your AI tool's skills directory
mkdir -p ~/.claude/skills/toani-vault-cli
cp "$(npm root -g)/@toani/vault-cli/SKILL.md" ~/.claude/skills/toani-vault-cli/
```

Same pattern for `~/.codex/skills/toani-vault-cli/` if you use Codex.

***

## CI / Automation

For CI runners and deployment platforms (GitHub Actions, Vercel, Railway), configure the CLI without the browser flow.

<details>

<summary><strong>Token resolution priority</strong></summary>

Highest to lowest:

1. `--token` flag on the command
2. `TOANI_VAULT_TOKEN` environment variable (either exported or read from `.env` in cwd)
3. `CREDBRIDGE_TOKEN` environment variable (legacy compat)
4. OS Keychain entry `toani-vault-cli:default`

</details>

<details>

<summary><strong><code>.env</code> handling (important nuance)</strong></summary>

The CLI auto-reads **only `TOANI_VAULT_TOKEN`** from a `.env` file in the current working directory. **Other variables** (`TOANI_BASE_URL`, `TOANI_VAULT_DASHBOARD_BASE_URL`, etc.) in `.env` are **not** auto-loaded, you must `export` them yourself or use a dotenv loader.

If you need all variables in `.env` available to the shell:

```bash
set -a; source .env; set +a
```

</details>

<details>

<summary><strong>GitHub Actions / Vercel / Railway</strong></summary>

```yaml
env:
  TOANI_BASE_URL: https://api.toani.ai
  TOANI_VAULT_TOKEN: ${{ secrets.TOANI_VAULT_TOKEN }}
```

</details>

<details>

<summary><strong>Local <code>.env</code></strong></summary>

```plain_text
TOANI_BASE_URL="https://api.toani.ai"
TOANI_VAULT_TOKEN="v4.local.xxxxxxxxxxxxxxxxxxx..."
```

Remember: `TOANI_BASE_URL` in `.env` will **not** be auto-loaded by `toani`. You must also run `set -a; source .env; set +a` or add it to your shell profile.

</details>

***

## Credential Types

Field names below are **case-sensitive** and must match exactly in credential references.

<details>

<summary><code>username_password</code></summary>

| Field      | Meaning                               | Example             | Required |
| ---------- | ------------------------------------- | ------------------- | -------- |
| `username` | Login name (email / phone / username) | `alice@example.com` | Yes      |
| `password` | Login password                        | `Hunter2!`          | Yes      |

</details>

<details>

<summary><code>api_key</code></summary>

| Field    | Meaning                                | Example               | Required |
| -------- | -------------------------------------- | --------------------- | -------- |
| `key`    | The API key value                      | `sk-abc123...`        | Yes      |
| `secret` | Signing secret associated with the key | `shhh-this-is-secret` | No       |

> The field name is **`key`**, not `api_key`.

</details>

<details>

<summary><code>oauth_token</code></summary>

| Field          | Meaning             | Example                    | Required |
| -------------- | ------------------- | -------------------------- | -------- |
| `refreshToken` | OAuth refresh token | `1//0g-long-refresh-token` | Yes      |

> The backend normalizes `refresh_token` (snake\_case) to **`refreshToken`** (camelCase). Always reference with `refreshToken`.
>
> **Current schema only covers refresh-only flows.** If your provider needs `clientId` / `clientSecret` / `accessToken`, contact your deployment operator.

</details>

<details>

<summary><code>client_certificate</code></summary>

| Field         | Meaning                                                   | Example        | Required |
| ------------- | --------------------------------------------------------- | -------------- | -------- |
| `certificate` | PEM certificate (including `-----BEGIN CERTIFICATE-----`) | Multi-line PEM | Yes      |
| `private_key` | PEM private key (including `-----BEGIN PRIVATE KEY-----`) | Multi-line PEM | No       |

> Field names are **`certificate`** (not `cert_pem`) and **`private_key`** (not `key_pem`).

</details>

<details>

<summary><code>ssh_key</code></summary>

| Field     | Meaning                      | Example                                    | Required |
| --------- | ---------------------------- | ------------------------------------------ | -------- |
| `ssh_key` | Full SSH private key content | `-----BEGIN OPENSSH PRIVATE KEY-----\n...` | Yes      |

</details>

<details>

<summary><code>database_connection</code></summary>

| Field      | Meaning                           | Example            | Required |
| ---------- | --------------------------------- | ------------------ | -------- |
| `host`     | Hostname or IP address            | `db.prod.internal` | Yes      |
| `port`     | Port number, **stored as string** | `"5432"`           | Yes      |
| `database` | Database name                     | `analytics`        | Yes      |
| `username` | Database user                     | `readonly_bot`     | Yes      |
| `password` | Database password                 | `xxxxx`            | Yes      |

> `port` is a string to preserve leading zeros and accommodate drivers with non-integer port semantics. Always quote.

</details>

***

## Current CLI Scope

<table><thead><tr><th width="135.169921875">Command group</th><th width="325.388671875">Subcommands</th><th>Purpose</th></tr></thead><tbody><tr><td><code>login</code></td><td>(interactive)</td><td>Browser-assisted onboarding + Skill install</td></tr><tr><td><code>doctor</code></td><td>(interactive)</td><td>Health check (CLI version, Node, token storage, URL reachability, token validity)</td></tr><tr><td><code>config</code></td><td><code>init</code>, <code>show</code></td><td>Non-interactive configuration</td></tr><tr><td><code>credentials</code></td><td><code>list</code>, <code>get</code></td><td>Read-only credential inspection</td></tr><tr><td><code>sandbox</code></td><td><code>create-session</code>, <code>list-sessions</code>, <code>get-session</code>, <code>terminate</code>, <code>pause</code>, <code>resume</code>, <code>bootstrap-page</code>, <code>execute</code>, <code>export-dom</code>, <code>export-data</code>, <code>get-operation</code>, <code>stats</code></td><td>TEE sandbox lifecycle and execution</td></tr></tbody></table>

**Dashboard-only** (not in CLI):

* Creating, updating, or deleting credentials
* Issuing or revoking tokens
* Viewing audit logs

If a command behaves differently from this doc, run `toani <subcommand> --help` and consult the OpenAPI reference as the authoritative source.

***

## Anti-patterns

These look reasonable but break security, reproducibility, or both.

<details>

<summary><strong>Secrets handling</strong></summary>

| Don't                                                 | Why                                                                 | Do instead                                                                                                                                      |
| ----------------------------------------------------- | ------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| Paste your plaintext API key into the AI chat         | The key bypasses TEE; it's now in chat history and AI provider logs | Create a credential in the Dashboard, reference by `service_id`                                                                                 |
| `git add ~/.toani/` or commit a token-bearing config  | A leaked token grants full sandbox access; rotation is manual       | Tokens default to OS Keychain. If a legacy `~/.toani/config.json` token leaks, run `toani login` to issue a new one and revoke in the Dashboard |
| Run `toani login`inside CI                            | Login needs a browser plus interactive OTP                          | Issue a token from a workstation, set `TOANI_VAULT_TOKEN` in CI secrets                                                                         |
| Hardcode `service_id`strings into prompts you publish | Service IDs are per-credential; sharing leaks your inventory        | Let the AI run `toani credentials list` first                                                                                                   |
| Reuse one credential across `dev` and `prod`          | One leak compromises both environments                              | One credential per env; name them `service-dev` / `service-prod`                                                                                |

</details>

<details>

<summary><strong>Sandbox usage</strong></summary>

| Don't                                                          | Why                                                                   | Do instead                                                                                                                                              |
| -------------------------------------------------------------- | --------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Write `"$credential.password"`as `fill.value`                  | It's filled as a literal string; no resolution happens                | Use the object form `{"$credential":"password"}`; verify `"sensitive": true` in the response                                                            |
| Hunt for `--reveal` / `--decrypt` to read a stored secret back | By design, no such switch exists                                      | Verify by running the flow (does login succeed? does the API call return 200?), not by inspecting the secret                                            |
| Try to read a password back via `execute_script`after a `fill` | `execute_script`is disabled after credential-backed fills (by design) | Do DOM inspection **before** the fill; verify state via `export-dom` / `get_text` / `get-session`afterwards                                             |
| Pass `--params` to `bootstrap-page`                            | The subcommand rejects it; common copy-paste mistake                  | Use the dedicated flags: `--mode`, `--script-selectors`, `--include-plain-scripts`, `--replay-lifecycle-events`, `--wait-selector`, `--wait-timeout-ms` |
| Put `--output json` after the command group                    | It's parsed as a subcommand arg, not a global flag                    | `toani --output json <group> <subcommand>`                                                                                                              |
| Leave sandbox sessions open between tasks                      | TEE compute stays billed; you'll hit quota                            | `terminate` at the end of every flow                                                                                                                    |

</details>

***

## Troubleshooting

<details>

<summary><strong>Install / version</strong></summary>

| Symptom                                                              | Likely cause                                        | Fix                                                                                                                       |
| -------------------------------------------------------------------- | --------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
| `toani: command not found`                                           | npm global `bin`not on `PATH`                       | Run `npm bin -g`, add that path to your shell profile, or reinstall Node via `nvm`                                        |
| `npm install -g` gives `EACCES`                                      | npm writing to `/usr/local`without permission       | Use `nvm` instead of `sudo npm install -g`; or `npm config set prefix ~/.npm-global` then add `~/.npm-global/bin` to PATH |
| macOS: `node --version`doesn't show v22 after `brew install node@22` | `node@22` is keg-only                               | `brew link node@22 --force --overwrite`                                                                                   |
| `toani --version` shows outdated version                             | Install is stale                                    | `npm install -g @toani/vault-cli@latest`                                                                                  |
| `jq: command not found`                                              | jq not installed; doesn't affect `login` / `doctor` | Install jq (Step 1) or ignore if you don't need JSON parsing                                                              |

</details>

<details>

<summary><strong>Login flow</strong></summary>

| Symptom                                        | Likely cause                                   | Fix                                                                      |
| ---------------------------------------------- | ---------------------------------------------- | ------------------------------------------------------------------------ |
| `toani login` opens the wrong Dashboard        | `TOANI_VAULT_DASHBOARD_BASE_URL`unset or wrong | Re-do [Step 3](localfile://path?#step-3-point-the-cli-at-your-dashboard) |
| `toani login` hangs at "Watching clipboard..." | Clipboard unavailable (common on Linux, SSH)   | Press `P` to paste manually, or `Q` then set `TOANI_VAULT_TOKEN`         |
| Closed browser before clicking Copy in 4.3     | Token never issued                             | `Q` in terminal, re-run `toani login`, redo Step 4.3                     |
| Closed browser after Copy but before pasting   | Token in clipboard; watcher should auto-detect | Return to terminal; if not detected, press `P` and paste                 |
| Missed the "shown only once" Copy moment       | Token cannot be recovered                      | Revoke in Dashboard, issue a new one, re-run `toani login`               |

</details>

<details>

<summary><strong><code>toani doctor</code> failures</strong></summary>

| Symptom                         | Likely cause                                           | Fix                                                                                 |
| ------------------------------- | ------------------------------------------------------ | ----------------------------------------------------------------------------------- |
| `✗ Token valid: HTTP 401`       | Token expired or revoked                               | `toani login`                                                                       |
| `✗ Server reachable: DNS`       | Wrong base URL or network issue                        | `toani config show`; `curl $TOANI_BASE_URL`                                         |
| Keychain-related error on Linux | `libsecret` not installed / no keyring service running | `sudo apt install libsecret-1-0 gnome-keyring` (or use `TOANI_VAULT_TOKEN` env var) |

</details>

<details>

<summary><strong>Skill / AI tool</strong></summary>

| Symptom                                                    | Likely cause                                      | Fix                                                                                       |
| ---------------------------------------------------------- | ------------------------------------------------- | ----------------------------------------------------------------------------------------- |
| `/toani-vault-cli` (or `$toani-vault-cli`) doesn't respond | Skill file missing, or AI tool cache not reloaded | See [Step 6: If the Skill doesn't respond](localfile://path?#if-the-skill-doesnt-respond) |
| AI tool says "I can't run shell commands"                  | Shell permission not enabled                      | Cursor: switch to Agent mode. Claude Code: enable bash tool. Codex: verify config         |
| On headless server / SSH, `toani login` fails              | No browser for OAuth step                         | Use [Headless / SSH](localfile://path?./headless)                                         |

</details>

***

## Quick reference

<details>

<summary><strong>Daily commands</strong></summary>

```bash
toani --version
toani doctor
toani --output json config show
toani --output json credentials list [--service-id <id>] [--credential-type <type>] [--only-valid true]
toani --output json credentials get <credentialId>

toani sandbox stats
toani sandbox list-sessions
toani sandbox create-session --service-id <s> --credential-id <id> --original-intent "<intent>"
toani sandbox execute <sessionId> --operation-type <type> --params '<json>'
toani sandbox bootstrap-page <sessionId> --mode rocket_loader --wait-selector '<sel>' --wait-timeout-ms 15000
toani sandbox export-dom <sessionId> --format text --root-selector body
toani sandbox terminate <sessionId>
```

</details>

<details>

<summary><strong>Operation types for sandbox execute</strong></summary>

`navigate` · `click` · `fill` · `wait` · `get_text` · `execute_script` · `http_request` · `export` · `dom_export`

(`bootstrap_page` has its own subcommand. Legacy `screenshot` and `get_attribute` are removed.)

</details>

<details>

<summary><strong>Credential reference syntax</strong></summary>

```jsonc
// In fill.value (top-level)
{"$credential":"password"}

// In http_request headers/body, with optional prefix/suffix
{"$credential":"api_key","prefix":"Bearer "}
```

Never use the string form `"$credential.x"`, never put credential references in `execute_script.bindings`.

</details>

<details>

<summary><strong>Token resolution priority</strong></summary>

`--token` > `TOANI_VAULT_TOKEN` > `CREDBRIDGE_TOKEN` > OS Keychain (`toani-vault-cli:default`) > legacy `~/.toani/config.json`

</details>

<details>

<summary><strong>Base URL resolution priority</strong></summary>

`--base-url` > `TOANI_BASE_URL` > `CREDBRIDGE_BASE_URL` > `~/.toani/config.json` `baseUrl` > `TOANI_VAULT_DASHBOARD_BASE_URL` or `https://dashboard.toani.ai`

</details>

***

## Feedback

If you find a discrepancy between this documentation and the service's actual behavior, open an issue in the repository and include:

* Output of `toani --version`
* Output of `toani doctor`
* The exact command that was run


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.toani.ai/toani-vault/getting-started/cli.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
