# Quickstart

> Five-minute laptop + second-Mac pairing: drop configs, run `agentcookie pair`, start the sink LaunchAgent, push from source, and verify with `agentcookie status`.

- 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

- `docs/quickstart.md`
- `examples/source.yaml`
- `examples/sink.yaml`
- `examples/allowlist.yaml`
- `examples/launchd-sink.plist`

---

---
title: "Quickstart"
description: "Five-minute laptop + second-Mac pairing: drop configs, run `agentcookie pair`, start the sink LaunchAgent, push from source, and verify with `agentcookie status`."
---

`agentcookie` ships one binary that runs in two roles. On the laptop (the **source**), `agentcookie source` reads Chrome's `Cookies` SQLite, filters against `blocklist.yaml`, and AES-256-GCM-seals each batch under a per-peer key. On the second Mac (the **sink**), `agentcookie sink` listens on a tailnet `100.x` address, validates the envelope, and fans cookies out to the three delivery surfaces. This page walks the seven steps from "two Macs and a tailnet" to a verified push, end-to-end in roughly five minutes.

## Prerequisites

<ParamField body="Tailscale" type="both Macs" required>
Installed and signed in. `tailscale status` on each side should list the other. The sink's listen address must be a tailnet `100.x.y.z`; `agentcookie sink` refuses `0.0.0.0`, `::`, and any non-tailnet routable host at startup.
</ParamField>

<ParamField body="Chrome (stable)" type="both Macs" required>
The source reads `~/Library/Application Support/Google/Chrome/Default/Cookies`; the sink writes back through Chrome DevTools Protocol when `cdp.enabled: true`.
</ParamField>

<ParamField body="Go toolchain" type="both Macs">
The published `go.mod` declares `go 1.26.2`; pre-built release tarballs are the alternative when a recent Go is not available. The wider installation page references the lower published floor for downstream callers.
</ParamField>

<ParamField body="Auto-login on the sink" type="recommended">
A LaunchAgent runs inside the user's GUI session. The Mac mini should auto-log-in to a user session at boot so the sink is reachable after a power cycle.
</ParamField>

## Step 1 — install the binary on both machines

<CodeGroup>
```bash both Macs
go install github.com/mvanhorn/agentcookie/cmd/agentcookie@latest
mkdir -p ~/.config/agentcookie
```
</CodeGroup>

The unified `agentcookie` binary exposes `source`, `sink`, `pair`, `wizard`, `doctor`, `status`, `secret`, `discover`, `cookies`, and `version` subcommands; all are wired from `internal/cli`.

## Step 2 — drop the example configs

The repo's `examples/` directory carries ready-to-edit starters. Copy them into `~/.config/agentcookie/` and then edit the small handful of host-specific fields.

<Tabs>
<Tab title="Source (laptop)">

```bash
cd $(mktemp -d) && git clone https://github.com/mvanhorn/agentcookie.git
cp agentcookie/examples/source.yaml    ~/.config/agentcookie/source.yaml
cp agentcookie/examples/blocklist.yaml ~/.config/agentcookie/blocklist.yaml
```

Edit `~/.config/agentcookie/source.yaml`:

- `sink.url` — the sink's tailnet `/sync` URL, e.g. `http://my-mac-mini.tailnet.ts.net:9999/sync`.
- `peer.hostname` — the sink's tailnet hostname. After pairing, this is the filename under `~/.config/agentcookie/keys/<peer>.json`.
- `chrome.db_path` — only set if you read from a non-default Chrome profile.

```yaml ~/.config/agentcookie/source.yaml
sink:
  url: http://my-mac-mini.tailnet.ts.net:9999/sync
chrome:
  db_path: ~/Library/Application Support/Google/Chrome/Default/Cookies
peer:
  hostname: my-mac-mini.tailnet.ts.net
```

</Tab>
<Tab title="Sink (second Mac)">

```bash
cp agentcookie/examples/sink.yaml      ~/.config/agentcookie/sink.yaml
cp agentcookie/examples/blocklist.yaml ~/.config/agentcookie/blocklist.yaml
```

Edit `~/.config/agentcookie/sink.yaml`:

- `listen.addr` — the sink's tailnet `100.x.y.z:9999`. `agentcookie sink` calls `validateListenAddr`, which rejects `0.0.0.0`, `::`, and non-tailnet hosts; loopback is permitted only for local-dev.
- `peer.hostname` — the source's tailnet hostname.
- `cdp.enabled: true` and `cdp.managed: true` (default) — the sink launches its own Chrome subprocess against `~/.agentcookie/chrome-profile` and writes cookies through CDP `Storage.setCookies`; no macOS Keychain prompt fires for this path.

```yaml ~/.config/agentcookie/sink.yaml
listen:
  addr: 100.x.y.z:9999
cdp:
  enabled: true
  managed: true
peer:
  hostname: my-laptop.tailnet.ts.net
```

</Tab>
</Tabs>

<Info>
`blocklist.yaml` is the v0.3 opt-out replacement for the legacy `allowlist.yaml`. The loader still recognises an existing `allowlist.yaml` and renames it to `allowlist.yaml.v2.bak` on first run; sync-all is now the default.
</Info>

## Step 3 — pair source and sink

`agentcookie pair` performs an X25519 + HKDF-SHA256 handshake salted with a one-time base32 code. Both sides save the derived 32-byte key to `~/.config/agentcookie/keys/<peer>.json` at mode 0600.

<Steps>
<Step title="Start the source listener">

```bash my-laptop
agentcookie pair --as source
```

The source resolves its own tailnet `100.x` address and binds `:9998`. Expected output:

```
agentcookie pair (source side)
  pairing code: YILU-OIVK
  source hostname: my-laptop.tailnet.ts.net
  listening on: 100.x.y.z:9998

  Run this on the sink machine within 10m0s
    agentcookie pair --as sink --peer my-laptop.tailnet.ts.net \
      --pair-url http://my-laptop.tailnet.ts.net:9998/pair --code YILU-OIVK

  Waiting for sink...
```

</Step>
<Step title="Complete the handshake from the sink">

```bash my-mac-mini
agentcookie pair --as sink --peer my-laptop.tailnet.ts.net \
  --pair-url http://my-laptop.tailnet.ts.net:9998/pair \
  --code YILU-OIVK
```

Both sides print a confirmation with a matching fingerprint and `key saved to ~/.config/agentcookie/keys/<peer>.json (mode 0600)`. Re-running `pair` clobbers the file; the legacy `security.shared_secret` fallback in `source.yaml`/`sink.yaml` is only consulted when no peer key exists.

</Step>
</Steps>

## Step 4 — install the sink LaunchAgent

Run the sink unattended as a user-level LaunchAgent so it survives logouts and crashes (`KeepAlive` + 10-second `ThrottleInterval`).

<Steps>
<Step title="Copy and edit the plist">

```bash my-mac-mini
cp agentcookie/examples/launchd-sink.plist ~/Library/LaunchAgents/dev.agentcookie.sink.plist
```

Edit the new plist:

- Replace `REPLACE_WITH_FULL_PATH_TO_AGENTCOOKIE` with the absolute path printed by `which agentcookie` (commonly `/Users/<you>/go/bin/agentcookie`).
- Replace `REPLACE_WITH_USERNAME` in the `HOME` environment variable.

</Step>
<Step title="Bootstrap into launchd">

```bash my-mac-mini
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/dev.agentcookie.sink.plist
```

Logs land at `/tmp/agentcookie-sink.out.log` and `/tmp/agentcookie-sink.err.log`.

</Step>
<Step title="Optional: bring Chrome up against the managed profile">

```bash my-mac-mini
open -na "Google Chrome" --args \
  --user-data-dir=$HOME/.agentcookie/chrome-profile \
  --remote-debugging-port=9222
```

With `cdp.managed: true` (the default), the sink launches its own Chrome against this isolated profile and there is nothing to do here. Run the command above only when you want a visible Chrome window pointed at the synced profile.

</Step>
</Steps>

<Tip>
For a smoke test, skip the plist and run `agentcookie sink` in a terminal. The startup line reports the resolved listen address; `^C` to stop.
</Tip>

## Step 5 — push from the source

Trigger a one-shot push and watch it land:

```bash my-laptop
agentcookie source --once --verbose
```

Expected output (one line per matched domain plus the post summary):

```
agentcookie source: %instacart.com -> 12 cookies
agentcookie source: %granola.so   -> 3 cookies
agentcookie source: posted 15 cookies, sink replied: ok: wrote 15 cookies via cdp; dropped 0 non-allowlisted
```

`--once` runs one read+push cycle and exits — the right choice for cron, CI, and this verification step. `--watch` is the long-running fsnotify watcher that LaunchAgents call in steady state.

## Step 6 — verify with `agentcookie status`

`agentcookie status` reads `source.yaml`, `sink.yaml`, the blocklist, and the persisted source/sink state files, and prints a single roll-up.

```bash either Mac
agentcookie status
```

Expected human output (fields it surfaces on each side):

```
agentcookie 0.x.y
config dir: /Users/you/.config/agentcookie
  source -> http://my-mac-mini.tailnet.ts.net:9999/sync
    chrome db: ~/Library/Application Support/Google/Chrome/Default/Cookies
  sink listening on 100.x.y.z:9999
  allowlist v1: N domains
  source daemon: 1 pushes, 0 failures, last push 4s ago
  sink daemon:   1 writes via cdp, 0 rejected, last write 4s ago
    adapters (last run): 5 ok, 0 skipped, 0 failed (of 5)
```

Pass `--json` for an agent-friendly envelope (`source_config`, `sink_config`, `allowlist`, `source_state`, `sink_state`, `errors`). Any load error — for example a missing `source.yaml` or an unparseable `blocklist.yaml` — appears in the `errors` array and is logged to stderr as `warning:` in non-JSON mode.

<Check>
A healthy first verify shows `source daemon: 1 pushes, 0 failures` and `sink daemon: 1 writes via cdp, 0 rejected`, with the most recent push timestamp within a few seconds of when you ran `source --once`.
</Check>

## Step 7 — make it continuous

`agentcookie source --once` is a single shot. Wire it to your trigger of choice while `--watch` mode is tuned. A reasonable cron entry:

```cron crontab -e
*/5 * * * * /Users/you/go/bin/agentcookie source --once >> ~/.agentcookie/source-cron.log 2>&1
```

Five-minute resolution is fine for most sites; session tokens generally rotate on the order of hours.

## Device-bound sessions (DBSC)

A small set of sites — today, mostly Google accounts — bind a login to the source Mac's secure hardware through Chrome's Device Bound Session Credentials. A copied cookie works on the sink for a few minutes and then Chrome there cannot refresh it. `agentcookie` flags these cookies in `agentcookie doctor` and, by default, ships them with a warning.

<ParamField body="--skip-dbsc-suspect" type="flag on `agentcookie source`">
Drop DBSC-suspect cookies instead of shipping them.
</ParamField>

<ParamField body="AGENTCOOKIE_SKIP_DBSC_SUSPECT" type="env var, `1` enables">
Same effect, useful inside LaunchAgents and cron lines.
</ParamField>

For Google specifically, sign the sink's Chrome into the same account once; it establishes its own device-bound session locally. Non-DBSC sites and the secrets bus are unaffected.

## Troubleshooting

<AccordionGroup>
<Accordion title="`refuses to bind on \"0.0.0.0\"` on sink startup">
The v0.12 hardening rejects any-interface and non-tailnet binds. Re-run `tailscale status`, pin a `100.x.y.z` IP into `sink.yaml`'s `listen.addr`, and restart the LaunchAgent. Loopback (`127.0.0.1`) is permitted only when an operator types it explicitly.
</Accordion>

<Accordion title="`connection refused` while pairing">
The sink-side `pair --as sink` is reaching a source that is no longer listening (the source exits 10 minutes after printing its code). Re-run `agentcookie pair --as source` on the laptop and try again with the freshly printed code.
</Accordion>

<Accordion title="Sink replied `ok: dropped N non-allowlisted`">
The dropped cookies are matching a pattern in the sink's `blocklist.yaml`. The sink owner has final say on what lands; review and trim the sink-side blocklist.
</Accordion>

<Accordion title="`agentcookie status` shows `source: not configured`">
`source.yaml` is missing or unparseable at `~/.config/agentcookie/source.yaml`. The exact parse error is on stderr as `warning: source.yaml: ...` (or in the `errors` field of `--json` output).
</Accordion>
</AccordionGroup>

## Next

<CardGroup>
<Card title="Installation" href="/installation">
Prerequisites, `go install`, and the one-command `agentcookie wizard install` alternative that bundles pairing and LaunchAgent setup.
</Card>
<Card title="Headless second-Mac install" href="/headless-install">
SSH-only sink install: degraded mode, the one-password Safe Storage open, and the `wizard set-keychain-access` upgrade.
</Card>
<Card title="Configure source and sink" href="/configure-source-sink">
Every field in `source.yaml` and `sink.yaml`, plus listen-address validation and `skip_chrome_sqlite` for headless sinks.
</Card>
<Card title="doctor and adapter verification" href="/doctor-health-checks">
The fifteen `agentcookie doctor` checks, the `DoctorReport` JSON envelope, and `wizard verify-adapters`.
</Card>
<Card title="LaunchAgent management" href="/launchagent-management">
How `wizard install` writes the source and sink plists, bootstrap with `launchctl`, and log paths.
</Card>
<Card title="Device-bound cookies (DBSC)" href="/dbsc-handling">
Detection heuristic, the default warn-and-ship behavior, and the `--skip-dbsc-suspect` drop path.
</Card>
<Card title="Troubleshooting" href="/troubleshooting">
The full failure catalogue: pairing errors, Keychain prompts, stale `SingletonLock`, missing `~/go/bin`, and DBSC drops.
</Card>
</CardGroup>
