# Drive install from an agent

> Using the bundled Claude Code skill to install agentcookie on source + sink unattended: required inputs, the SSH-driven flow, success signals, and error recovery.

- Repository: mvanhorn/agentcookie
- GitHub: https://github.com/mvanhorn/agentcookie
- Human docs: https://grok-wiki.com/public/docs/mvanhorn-agentcookie-137da38edfae
- Complete Markdown: https://grok-wiki.com/public/docs/mvanhorn-agentcookie-137da38edfae/llms-full.txt

## Source Files

- `skill/SKILL.md`
- `skill/prompts/install-on-both-machines.md`
- `internal/cli/wizard.go`
- `internal/cli/wizard_verify_adapters.go`

---

---
title: "Drive install from an agent"
description: "Using the bundled Claude Code skill to install agentcookie on source + sink unattended: required inputs, the SSH-driven flow, success signals, and error recovery."
---

The repo ships a Claude Code skill at `skill/SKILL.md` (name `agentcookie-install`, version `0.2.0`) that an AI coding agent — Claude Code, OpenClaw, Hermes, Codex, Cursor, or any agent that runs local shell + SSH — uses to install agentcookie on both the source (laptop) and sink (Mac mini / cloud VM / second Mac) in a single user prompt. The skill orchestrates two `agentcookie wizard install` invocations, one local and one over SSH, and hands the pairing handshake between them through `~/.agentcookie/pairing.json`. End-to-end run takes about 30 seconds; the user does not need to touch the sink's screen or answer Keychain prompts on it.

## When the skill triggers

The skill's frontmatter `description` is what tells the agent harness when to invoke it:

> Install agentcookie on the user's source (laptop) and sink (Mac mini / cloud VM / second Mac) machines and pair them so Chrome cookies sync continuously over their Tailscale tailnet. Use when the user says "install agentcookie", "set up cookie sync", "share my Chrome sessions with my Mac mini", or "make my agent log in as me".

The companion file `skill/prompts/install-on-both-machines.md` is the prompt template the user pastes into the agent:

> Install agentcookie on this laptop and my Mac mini so my Chrome sessions sync continuously. Use Tailscale to find the Mac mini. Confirm with me which machine is the source and which is the sink, then run the full install end to end. After install, verify both daemons are running and tell me what you see.

## Required inputs

The agent must have all three before it starts. The skill instructs the agent to stop and ask if any are missing.

| Input | Purpose | How the agent learns it |
| --- | --- | --- |
| `source` hostname | Machine the user logs into Chrome on (usually the laptop running the agent). | Current host, confirmed against `tailscale status` output (the "active" / top entry). |
| `sink` hostname | Machine where AI agents act and need synced cookies. | Other macOS entries in `tailscale status`, confirmed back with the user. |
| Passwordless SSH from source to sink | The skill SSHes from the laptop to the sink to run the sink-side wizard. | `ssh -o ConnectTimeout=5 -o BatchMode=yes <sink> whoami`. |

Tailscale must be up on both sides (the wizard refuses to bind on `0.0.0.0` and requires a `100.x` address; see `validateListenAddr` in `internal/cli/wizard.go`).

## SSH-driven flow

<Steps>

<Step title="Detect the lay of the land">

The skill instructs the agent to probe the current host:

```bash
which agentcookie 2>/dev/null || echo "missing"
/Applications/Tailscale.app/Contents/MacOS/Tailscale status 2>&1 | head -20
ssh -o ConnectTimeout=5 -o BatchMode=yes <suspected-sink> 'whoami' 2>&1
```

The current host is whichever entry is marked "active" or appears first in `tailscale status`. Every other macOS entry is a candidate sink.

</Step>

<Step title="Confirm source vs sink with the user">

The skill requires a blocking confirmation before any install runs:

> I see you're on `<current-hostname>`. Looks like `<other-hostname>` (Tailscale IP `100.x.y.z`) is your other Mac. Should I install agentcookie with `<current-hostname>` as the source (your logged-in Chrome) and `<other-hostname>` as the sink (where your agents run)?

</Step>

<Step title="Install on the source in the background">

```bash
go install github.com/mvanhorn/agentcookie/cmd/agentcookie@latest
agentcookie wizard install --as source \
  --peer <sink-hostname> \
  --local-name <source-hostname> &
WIZARD_PID=$!
```

The source wizard blocks until the sink completes the pairing handshake, so the agent runs it in the background and polls.

</Step>

<Step title="Read pairing info from ~/.agentcookie/pairing.json">

The wizard's `pairingInfoWriter` intercepts the pairing announcement and writes a JSON sibling file at `~/.agentcookie/pairing.json` (mode `0600`) the moment the code is generated:

```json
{
  "code": "BASE32CODE",
  "peer": "<source-hostname>",
  "pair_url": "http://100.x.y.z:9998/pair",
  "sink_run": "agentcookie wizard install --as sink --peer <source-hostname> --code <code> --pair-url http://100.x.y.z:9998/pair"
}
```

The agent polls for the file (up to 10 seconds, every 250 ms) and parses out `code` and `pair_url`. The wizard removes the file again on successful pairing.

</Step>

<Step title="Install on the sink over SSH">

```bash
ssh <sink-hostname> "go install github.com/mvanhorn/agentcookie/cmd/agentcookie@latest && \
  agentcookie wizard install --as sink \
    --peer <source-hostname> \
    --code <code-from-pairing.json> \
    --pair-url <pair_url-from-pairing.json> \
    --local-name <sink-hostname>"
```

The sink-side `wizard install`:

1. Writes `~/.config/agentcookie/sink.yaml` with a tailnet listen address resolved via `tsclient.RequireTailnetIP`.
2. Attempts the one-password universal Chrome Safe Storage open (set `AGENTCOOKIE_LOGIN_PASSWORD=…` to keep it fully non-interactive over SSH). On a non-interactive box with no password available, the install **non-fatally downgrades to degraded mode** (`skip_chrome_sqlite: true` + CDP-managed Chrome), so the daemon still starts.
3. Runs the X25519 + HKDF-SHA256 pairing handshake against the source's `pair_url` and saves the per-peer key to `~/.config/agentcookie/keys/<peer>.json`.
4. Installs `dev.agentcookie.sink` via `launchctl` and starts the daemon.

</Step>

<Step title="Verify both daemons">

```bash
launchctl list | grep dev.agentcookie
ssh <sink-hostname> 'launchctl list | grep dev.agentcookie'
```

Each side reports `dev.agentcookie.source` (laptop) or `dev.agentcookie.sink` (sink) with a numeric PID.

</Step>

<Step title="Verify a real sync round-trip">

```bash
agentcookie status --json
ssh <sink-hostname> 'agentcookie status --json'
```

Source state should show `source_state.last_push` within seconds of a Chrome write. Sink state should show `sink_state.last_write` and `total_writes > 0`. If both are zero, opening a tab on the source's Chrome (any allowlisted domain) forces a write within ~2 seconds.

</Step>

</Steps>

## Success signals an agent can parse

The agent does not screen-scrape human prose. It checks structured signals.

<ResponseField name="~/.agentcookie/pairing.json" type="file (transient)">
Exists for a few seconds during source-side pairing. Contains `code`, `peer`, `pair_url`, `sink_run`. Removed by the wizard on successful handshake.
</ResponseField>

<ResponseField name="~/.config/agentcookie/keys/<peer>.json" type="file (persistent)">
Per-peer key written on successful pairing. Presence means the handshake completed; the file mode is `0600`.
</ResponseField>

<ResponseField name="launchctl list | grep dev.agentcookie" type="string">
A populated line with a non-`-` PID means the LaunchAgent (`dev.agentcookie.source` or `dev.agentcookie.sink`) is bootstrapped and the daemon process is alive.
</ResponseField>

<ResponseField name="agentcookie status --json" type="JSON envelope">
Top-level fields: `version`, `config_dir`, `source_config`, `sink_config`, `allowlist`, `source_state`, `sink_state`, `errors`. The agent watches `source_state.last_push` (recent timestamp) and `sink_state.last_write` / `sink_state.total_writes` (non-zero) to declare a real round-trip.
</ResponseField>

<ResponseField name="agentcookie --json wizard verify-adapters" type="JSON envelope">
Reads `~/.agentcookie/sink-state.json` and emits `{status, results: [...]}`. `status` is one of `ok`, `no_state`, `no_runs`. Each result names a registered v0.11 adapter and reports `Err`, `Skipped`, `SkippedReason`, `Pushed`, `RanAt`. Exit code `1` if any adapter failed (informational; sink does not abort on adapter failures).
</ResponseField>

## Reporting back to the user

After verification the skill instructs the agent to give a plain-language summary like:

> Done. agentcookie is running on both `<source>` and `<sink>`. The source pushes cookies as soon as they change in Chrome on `<source>`; the sink writes them into a dedicated Chrome instance at `~/.agentcookie/chrome-profile` on `<sink>`. Your agents on `<sink>` connect to that Chrome via CDP at `~/.agentcookie/chrome-profile`. After this install, the user does not run agentcookie commands by hand again.

## Error recovery

The skill enumerates the failure modes the agent must recognize and remediate without escalating to the user when possible.

<AccordionGroup>

<Accordion title="agentcookie: command not found on the sink">
After `go install` on the sink, `~/go/bin` is not on the non-interactive SSH `$PATH`. Either source the shell rc (`ssh <sink> 'source ~/.zshrc && agentcookie ...'`) or invoke by absolute path (`~/go/bin/agentcookie ...`). If Go is not installed at all, fix with `brew install go` on the sink, or `scp` a prebuilt binary from the laptop.
</Accordion>

<Accordion title="Sink pairing returns connection refused">
Tailscale ACLs are blocking tailnet-internal traffic. Default Tailscale ACLs allow everything between a user's own devices; custom ACLs must permit ports **9998 (pairing)** and **9999 (sync)**. Confirm with `tailscale status` that the source is reachable.
</Accordion>

<Accordion title="Sink wizard hangs at 'Chrome did not publish DevToolsActivePort'">
The managed Chrome subprocess failed to start. Most likely: Google Chrome is not installed at `/Applications/Google Chrome.app`. Install Chrome, or set `cdp.chrome_binary` in `sink.yaml`.
</Accordion>

<Accordion title="agentcookie status reports zero pushes after install">
The source watcher has not seen a Chrome write yet. Open a tab on the source's Chrome (any allowlisted domain) and refresh — push should appear in `source_state.last_push` within 2 seconds.
</Accordion>

<Accordion title="Sink Chrome subprocess crashes repeatedly">
Inspect `~/.agentcookie/logs/sink.err.log`. Most common cause: stale `SingletonLock` from a prior Chrome session sharing the user-data-dir. Remediation: `rm ~/.agentcookie/chrome-profile/SingletonLock` and let the supervisor restart.
</Accordion>

<Accordion title="Universal keychain open did not complete (headless SSH)">
On a sink with no GUI session and no `AGENTCOOKIE_LOGIN_PASSWORD` env var, the inline partition-list open fails. The wizard **does not abort** — it downgrades to degraded delivery (sidecar + adapter push + CDP-managed Chrome) and prints a one-line upgrade instruction:

```bash
AGENTCOOKIE_LOGIN_PASSWORD=… agentcookie wizard set-keychain-access
```

After that, the sink reads the real Default Chrome profile and any unmodified cookie tool on the sink sees the synced cookies.
</Accordion>

</AccordionGroup>

The wizard is idempotent: existing configs, keys, and LaunchAgents are detected and reused unless `--repair` or `--force` is passed. After fixing a failure, the agent can re-paste the prompt and the install will pick up where it left off.

## What the skill does not do

Stated out-of-scope in `skill/SKILL.md`:

- Code-signing the binary so Keychain access is granted without any prompt.
- Web Store extension install (planned for v0.3).
- Linux sink support (planned for v0.3).
- Bidirectional sync (planned for v0.3).
- Adding new allowlist domains after install — the user edits `~/.config/agentcookie/allowlist.yaml` on each side; LaunchAgents pick up the change on next restart (every 10 seconds after a config save).

## Flow at a glance

```text
laptop (source)                                        Mac mini (sink)
──────────────                                         ────────────────
agent prompt                                                 │
    │                                                        │
    ├─ which agentcookie / tailscale status / ssh probe ─────│
    │  AskUser: "source=<a>, sink=<b>?"                      │
    │                                                        │
    ├─ go install ...@latest                                 │
    ├─ agentcookie wizard install --as source &              │
    │       │                                                │
    │       └─ writes ~/.agentcookie/pairing.json            │
    │           { code, pair_url, sink_run, peer }           │
    │                                                        │
    ├─ poll pairing.json (up to 10s, 250 ms)                 │
    │                                                        │
    └─ ssh <sink> "go install ...@latest &&                  │
                   agentcookie wizard install --as sink      │
                     --peer <source> --code … --pair-url …" ─►
                                                             ├─ drop sink.yaml (+ allowlist)
                                                             ├─ universal keychain open
                                                             │     (or non-fatal downgrade)
                                                             ├─ X25519 + HKDF handshake ◄─── http://<source>:9998/pair
                                                             ├─ keys/<source>.json
                                                             └─ launchctl bootstrap dev.agentcookie.sink

verify on both sides:
  launchctl list | grep dev.agentcookie
  agentcookie status --json        →  source_state.last_push   sink_state.last_write
  agentcookie --json wizard verify-adapters  →  {status:"ok", results:[…]}
```

## Related pages

<CardGroup>
  <Card title="Headless second-Mac install" href="/headless-install">
    Manual SSH-only install path the skill is built on, with the one-password Safe Storage open and the `wizard set-keychain-access` upgrade.
  </Card>
  <Card title="Quickstart" href="/quickstart">
    Five-minute laptop + second-Mac pairing done by hand, useful when the agent flow needs debugging.
  </Card>
  <Card title="Pairing and per-peer keys" href="/pairing-and-keys">
    The X25519 + HKDF-SHA256 handshake, the base32 pairing code, and what gets written to `keys/<peer>.json`.
  </Card>
  <Card title="doctor and adapter verification" href="/doctor-health-checks">
    Deeper post-install checks and the `DoctorReport` JSON envelope for agent consumption.
  </Card>
  <Card title="Troubleshooting" href="/troubleshooting">
    Full failure-mode catalog the skill's error-recovery section is a subset of.
  </Card>
  <Card title="LaunchAgent management" href="/launchagent-management">
    What `dev.agentcookie.source` / `dev.agentcookie.sink` look like on disk, log paths, and lifecycle.
  </Card>
</CardGroup>
