# Install Double Agent: runbook for coding agents

Works with Claude Code, Cursor, Codex, Lovable, Emergent, Bolt, v0 and Replit. Follow the steps in order, and don't skip step 5.

No account, key or backend is needed. The install is **keyless**. Don't create an account unless your human asks for one; step 6 explains what to tell them.

## 0. Fastest: the Agent Skill

If you support [Agent Skills](https://github.com/vercel-labs/skills), install ours and follow it instead of this page:

```sh
npx skills add doubleagent-so/skills
```

It carries this runbook plus `verify`, `snippet` and `create-account` scripts. No skills support? Use the CLI below, or edit by hand from step 1.

## 0b. Fast path: the CLI

If the project has a repo and Node 18+:

```sh
npx @doubleagent-so/cli init --yes --json
```

- Exit `0` with `status` `install` or `installed`: go to step 3. The JSON has `keyless: true` and a `claim_url`; keep it for step 6.
- Exit `2`, or `status: "advice"` (for example a Shopify theme): follow `next_steps` in the JSON, then continue at step 2.
- Exit `1`: an error. Read `{"error": …}`.
- If the human already gave you a public key (`pk_…`), add `--key pk_…`.

`--dry-run` previews the diff without writing. To install by hand, carry on below.

## 1. Detect the stack

Run these checks from the repo root. The first match wins.

| Check | Stack | Go to |
|---|---|---|
| `package.json` has `"next"` | Next.js | 2a |
| `package.json` has `"vite"` or `"react-scripts"`, or there's an `index.html` at the root or in `public/` | Vite / React / SPA | 2b |
| `layout/theme.liquid` exists, or `shopify.theme.toml` | Shopify theme | 2d |
| `wp-content/` or `functions.php` exists | WordPress | 2h |
| `*.html` files and no bundler | Plain HTML | 2c |
| No repo; it's a hosted builder (Wix, Squarespace, Webflow) | Hosted | 2e / 2f / 2g |

Also note which tools are already on the page, for step 3:

```sh
grep -rEl "gtag\(|googletagmanager|fbq\(|klaviyo|mixpanel|analytics\.load|posthog|amplitude|_hsq|ttq\." --include=*.{html,tsx,jsx,ts,js,liquid,php} . | head
```

## 2. Add the snippet (exact edits)

The keyless snippet, used in 2b, 2c, 2e, 2f, 2g and 2h:

```html
<script>window.doubleagent=window.doubleagent||{q:[],push(){this.q.push(arguments)}};</script>
<script async src="https://cdn.doubleagent.so/v1/doubleagent.js" data-profile="auto"></script>
```

If the human gave you a public key, add `data-key="pk_…"` to the first tag. Never put a secret key (`sk_…`) in client code.

### 2a. Next.js (App Router)

Edit `app/layout.tsx`:

```tsx
import Script from 'next/script';

// inside <html>…<head>, or directly in <body> (next/script hoists it)
<Script src="https://cdn.doubleagent.so/v1/doubleagent.js" strategy="afterInteractive" />
<Script id="doubleagent-stub" strategy="beforeInteractive">{`window.doubleagent=window.doubleagent||{q:[],push(){this.q.push(arguments)}};`}</Script>
```

With the Pages Router, put the same two tags in `pages/_document.tsx` `<Head>`.

### 2b. Vite / React / Lovable / Bolt / v0

Paste the snippet into `index.html` inside `<head>`, before any other script.

### 2c. Plain HTML

Paste the snippet into `<head>` on every page. If there's a shared partial or include, edit only that.

### 2d. Shopify

Don't edit theme code. Tell the human to install the **Double Agent** app, then go to Online Store › Themes › Customize › App embeds, turn on **Double Agent** and save. For a headless or Hydrogen storefront, use 2a or 2b instead.

### 2e. Wix

Go to Settings › Custom Code › Add Custom Code. Paste the snippet, choose **All pages**, **Load code once**, **Head**, then publish. This needs a Premium plan and a connected domain.

### 2f. Squarespace

Go to Settings › Developer tools › Code injection › **Header**, paste the snippet and save. This needs a Core plan or higher.

### 2g. Webflow

Go to Site settings › Custom code › **Head code**, paste the snippet, then save and publish. This needs a paid site plan.

### 2h. WordPress

Put this in the child theme's `functions.php`, or in a code-snippets plugin:

```php
add_action('wp_head', function () { ?>
<script>window.doubleagent=window.doubleagent||{q:[],push(){this.q.push(arguments)}};</script>
<script async src="https://cdn.doubleagent.so/v1/doubleagent.js" data-profile="auto"></script>
<?php }, 1);
```

## 3. Wire the integrations you found

Nothing is needed for GA4, GTM, Meta, TikTok, Google Ads, Mixpanel, Segment, PostHog, Amplitude, Klaviyo, HubSpot, Intercom, Clarity or Hotjar: they're detected and tagged automatically, keyless included.

| Found | Add |
|---|---|
| GA4 | Tell the user to register `da_class` and `da_agent` as user-scoped custom dimensions (Admin › Custom definitions). |
| Mailchimp embed form | Tell the user to create a hidden text merge field `DACLASS`. The SDK fills it. |
| HubSpot | Tell the user to create contact properties `da_class`, `da_agent`, `da_score`, `da_bucket`, `da_rec` and `da_verified`. |

To link a known user, pass a hash, never a raw email:

```js
window.doubleagent.push('identify', sha256Hex(userId));
```

## 4. Server checks (only with a key)

Signed tokens (`check`, `getToken`, `protect`, `stripeToken`) need a public key; keyless they reject with `key_required`. Skip this step unless the human gave you a key. With one:

```js
window.doubleagent.push('protect', formElementOrSelector, 'signup'); // adds a hidden da_token on submit
```

```ts
import { createDoubleAgent } from '@doubleagent-so/node';
const da = createDoubleAgent();
const claims = await da.verifyToken(body.da_token, { site: 'st_…' }).catch(() => null);
// claims?.cls is 'human' | 'bot' | 'agent'. null means unknown, not bot.
```

Without Node, POST `{"token":"…"}` to `https://api.doubleagent.so/v1/verify`. It returns `{ "valid": true, "claims": { … } }`. Don't block on it unless asked.

## 5. Check the install

Local first (optional): with the test key (`pk_test_…`), the snippet works on `localhost`. The human sees those sessions in HQ under *Test data (localhost)*. Use the live key in production.

After deploying, check the public URL:

```sh
npx @doubleagent-so/cli verify https://SITE --json
# or
curl -s "https://api.doubleagent.so/v1/install-check?url=https://SITE"
```

This is what OK looks like (`GET /v1/install-check`):

```json
{ "ok": true, "url": "https://site.example/", "final_url": "https://site.example/", "http_status": 200,
  "script_found": true, "script_src": "https://cdn.doubleagent.so/v1/doubleagent.js", "script_count": 1,
  "key": null, "key_valid": false, "keyless": true, "claim_url": "https://app.doubleagent.so/claim?domain=site.example",
  "profile_attr": null, "stub_found": true, "stub_before_script": true,
  "integrations_detected": ["ga4", "meta"], "last_beacon_at": null,
  "checked_at": 1790150000, "cached": false,
  "problems": [ { "code": "no_beacons_yet", "severity": "info", "message": "…", "fix": "…" } ] }
```

- `ok` is true when the script is found, the key (if any) is valid, and no problem has severity `error`. Keyless installs report `keyless: true`.
- If `ok` is `false`, apply each problem's `fix` and run the check again.
- Problem codes (all include a `fix`):

  | Severity | Codes |
  |---|---|
  | error | `script_missing`, `insecure_src`, `key_malformed`, `key_unknown`, `secret_key_exposed` (rotate it now), `stub_missing`, `csp_script_blocked`, `csp_connect_blocked`, `fetch_failed`, `page_http_error`, `not_html` |
  | warning | `loader_not_found_bundled` (SDK bundled from npm?), `duplicate_loader`, `not_async`, `profile_invalid` |
  | info | `stub_recommended`, `no_beacons_yet` (normal on a new site), `page_truncated` |

- Limits: public http(s) URLs on ports 80 and 443 only, 3 redirects, a 5 s timeout, and 30 requests a minute per IP (`429 rate_limited` with `Retry-After`). The check fetches with `User-Agent: DoubleAgent-InstallCheck/1`.
- Locally, open the page and run `await doubleagent.ready()` in the browser console. It should return a verdict object.

Report the final JSON to the user.

## 6. Unlock more (optional; tell the human, don't do it)

Say this, in one sentence: *"Double Agent is running keyless; claim your domain at the claim URL to see the data already collected and unlock the dashboard."*

| Tier | How | Adds |
|---|---|---|
| Keyless | This runbook | Verdicts in the browser, every integration tagged, bot ad conversions held back |
| Public key + verified domain | Claim at `claim_url`, or `npx @doubleagent-so/cli init --email you@company.com` | HQ dashboard with all data since install, live view, signed tokens (`check`, `protect`), the Handler |
| Secret key | Created in HQ, server only | `/v1/stats`, `/v1/sessions`, Debrief webhooks, server relabel |

Details: [capabilities](https://doubleagent.so/docs/capabilities.md), [claiming](https://doubleagent.so/docs/claim.md). Only create an account if the human asks you to.

## Practise first

Want to see verdicts before touching a real site? The public test sites at [https://lab.doubleagent.dev](https://lab.doubleagent.dev) run the same script on one page per site type (SaaS, shop, content, social, payments, fintech, ticketing, lead gen, government, keyless). Point a browser or an agent at them and watch the Dossier panel.
