# CLI

```sh
npx @doubleagent-so/cli init                               # keyless install, prints the claim URL
npx @doubleagent-so/cli init --email you@company.com       # create account + site, install its pk
npx @doubleagent-so/cli init --key pk_live_xxx             # install with an existing public key
npx @doubleagent-so/cli init --yes --json                  # for coding agents
npx @doubleagent-so/cli verify https://SITE [--api <origin>] [--json]
npx @doubleagent-so/cli login | logout [--all]
npx @doubleagent-so/cli sites
npx @doubleagent-so/cli keys [list|create|rotate|revoke] [key_id] [--site st_…] [--kind pk|sk] [--env live|test]
npx @doubleagent-so/cli verify-domain shop.example.com --method dns|meta|file|script [--site st_…]
```

Node 18+, no runtime dependencies. npm package: `doubleagent`.

## init

| Flag | |
|---|---|
| none | Keyless. Prints `Installed without a key. Claim <domain> at https://app.doubleagent.so/claim?domain=<domain> to see the data (everything collected until then is kept).` Domain from `--domain`, `package.json` `homepage` or `CNAME`. `--portal` or `$DOUBLEAGENT_PORTAL` changes the portal. |
| `--key pk_…` | Adds `data-key`; re-running on a keyless install adds it in place. `sk_…` is refused. |
| `--email you@x.com [--domain] [--name] [--test]` | `POST /v1/accounts`, solves the proof-of-work, installs the pk, prints `sk_test` once |
| `--profile`, `--cwd`, `--dry-run`, `--yes`/`-y`, `--json` | As named. `--dry-run` never creates an account. |

| Stack | Where the snippet goes |
|---|---|
| Next.js App Router | `app/layout.*` (next/script) |
| Next.js Pages Router | `pages/_document.*` `<Head>`, created if missing |
| Vite, React, Vue (Lovable, Bolt, v0) | `index.html` `<head>` |
| Static HTML | Every root `*.html`, or `public/index.html` |
| Astro | `src/layouts/*.astro`, `is:inline` |
| Nuxt | `nuxt.config` `app.head.script`, or `plugins/doubleagent.client.*` |
| SvelteKit | `src/app.html`, before `%sveltekit.head%` |
| Remix / React Router | `app/root.*` `<head>` |
| WordPress theme | `header.php`, before `wp_head()` |
| Shopify theme | No edits. Use the [app embed](/docs/shopify). |

```json
{ "stack": "next-app", "status": "install", "dry_run": false, "keyless": true, "key": null,
  "domain": "shop.example.com", "claim_url": "https://app.doubleagent.so/claim?domain=shop.example.com",
  "account": null, "files_changed": ["app/layout.tsx"], "integrations": ["ga4"], "next_steps": ["…"], "diff": "…" }
```

Exit codes: `0` ok, `1` error or aborted, `2` unsupported stack.

## login, sites, keys, verify-domain

- `login` uses the device flow: approve the code at `app.doubleagent.so/device`. Credentials go to `~/.config/doubleagent/credentials.json` (mode 0600).
- `sites`, `keys` and `verify-domain` take `--json`. `keys` defaults to your only site and prints a new or rotated `sk` once.
- `verify-domain` adds the domain if needed, verifies it, prints the claimed session count; otherwise prints what to publish and exits `1`.

## verify

Checks the server HTML for the script, the stub and (optional) key, then calls [`GET /v1/install-check`](/docs/rest). A keyless script passes. Exit `0` only if all pass.
