{"_id":"@edngibson/reporter","name":"@edngibson/reporter","dist-tags":{"latest":"0.1.0"},"versions":{"0.1.0":{"name":"@edngibson/reporter","version":"0.1.0","type":"module","description":"Stateless reporter for the agent-dock Layer 1 rendezvous: Claude Code hooks + deadman heartbeat + client-side encryption. Zero runtime dependencies (Node >= 18 stdlib only).","engines":{"node":">=18"},"bin":{"reporter":"reporter.mjs"},"publishConfig":{"access":"public"},"repository":{"type":"git","url":"git+https://github.com/EdenGibson/agent-dock.git","directory":"reporter"},"scripts":{"test":"node --test"},"_id":"@edngibson/reporter@0.1.0","bugs":{"url":"https://github.com/EdenGibson/agent-dock/issues"},"homepage":"https://github.com/EdenGibson/agent-dock#readme","_nodeVersion":"22.20.0","_npmVersion":"10.9.3","dist":{"integrity":"sha512-n9JrwRBUwvyJo6FeSmMXPNAu+S0OUe5aMGJDwZSHg+peBNl31X5N7xMtnfOIq2V/SwUBaLyoeUyrlq9ZvMxvbg==","shasum":"9a3a38f93ce9233ee28a078e5fc19b4915ff3a57","tarball":"https://registry.npmjs.org/@edngibson/reporter/-/reporter-0.1.0.tgz","fileCount":7,"unpackedSize":61124,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQCokJM6xuAkNznFrRz5kIRQlTft3c8AlgF0JQCtsZQAIgIhAO183qhcHc7dl5lzlOusoiiYJpPkbkD8uRxfJS/zG0rD"}]},"_npmUser":{"name":"edngibson","email":"edengibson355@gmail.com"},"directories":{},"maintainers":[{"name":"edngibson","email":"edengibson355@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/reporter_0.1.0_1782027094547_0.538317633860079"},"_hasShrinkwrap":false}},"time":{"created":"2026-06-21T07:31:34.287Z","0.1.0":"2026-06-21T07:31:34.745Z","modified":"2026-06-21T07:31:35.017Z"},"maintainers":[{"name":"edngibson","email":"edengibson355@gmail.com"}],"description":"Stateless reporter for the agent-dock Layer 1 rendezvous: Claude Code hooks + deadman heartbeat + client-side encryption. Zero runtime dependencies (Node >= 18 stdlib only).","homepage":"https://github.com/EdenGibson/agent-dock#readme","repository":{"type":"git","url":"git+https://github.com/EdenGibson/agent-dock.git","directory":"reporter"},"bugs":{"url":"https://github.com/EdenGibson/agent-dock/issues"},"readme":"# agent-dock reporter\n\nThe Layer 1 **write path**: a stateless reporter that tells the [agent-dock\nrendezvous](../docs/superpowers/specs/2026-06-17-agent-management-design.md) which\nagents are running on **this** host, so the dock — on any machine — sees the merged\nglobal fleet.\n\nOne small Node script (`reporter.mjs`), **zero runtime dependencies** — Node ≥ 18\nstdlib only. It's a script, not a compiled binary, on purpose: every host that runs\nagents already runs Claude Code, which already requires Node, so the reporter adds\nno new runtime and no cross-compile. Copy `reporter/` to the host and you're done.\n\n## What it does\n\nBoth triggers run the **same** path — enumerate this host's agents via\n`claude agents --json`, encrypt the sensitive fields, and POST a **full snapshot**\nto the rendezvous `ingest` function:\n\n- **hook** (one-shot) — wired to Claude Code lifecycle hooks. Low-latency: a\n  transition is reflected almost immediately. Full snapshots mean a finished agent\n  *disappears* the instant its `Stop` hook fires, rather than waiting out the TTL.\n- **heartbeat** (long-running) — re-snapshots every 90 s (`ADK_HEARTBEAT_SECONDS`).\n  The deadman: it proves the host is alive so the server can TTL-expire a\n  silently-dead host (crash / `kill -9` / OOM / power loss), and it self-heals any\n  hook push that got dropped. This is the **only** periodic traffic.\n\n**Zero-knowledge (decision I):** `label`, `cwd`, and `current_action` are encrypted\nclient-side with **AES-256-GCM** using a key the rendezvous **never sees**. Status,\nkind, and timestamps travel in clear so the server can route and expire without\ndecrypting. A leaked server row is unreadable without the key.\n\n## Onboard a machine — `pair` (recommended)\n\n> **Status:** the `pair` / `unpair` CLI and the dock's bundle-mint command\n> (`mint_pairing_bundle`) are implemented; still pending are publishing\n> `@edngibson/reporter` to npm and wiring the dock's **\"+ Add a machine\"** button\n> into the UI. Until then, use the manual setup below — this section describes the\n> target one-paste experience.\n\nOne command does everything the manual sections below do by hand — enroll, write the\nconfig, install the Claude Code hooks, and start the heartbeat service:\n\n```bash\nnpx -y @edngibson/reporter pair <bundle>\n```\n\nGet `<bundle>` from the dock's **\"+ Add a machine\"** button: it mints a single-use,\n15-minute pairing code and shows the `adk1_…` string to paste plus a four-word\n**fingerprint**. `pair` prints the same fingerprint on this host — eyeball that the\ntwo match (this catches a bundle swapped in transit through Slack/email/SSH), and\nyou're on the dock within seconds. The bundle carries the rendezvous URL, the\npairing code, and the encryption key **out-of-band**; the server never sees the key.\n\n`pair` is idempotent — re-running refreshes in place (one hook per event, one\nservice). It installs the right per-user service for the OS, no sudo: systemd\n`--user` (+ `loginctl enable-linger`), a launchd LaunchAgent, or a Task Scheduler\n`ONLOGON` task. The hooks it installs are `async` (a slow or down rendezvous never\nblocks the agent) and cover the lifecycle events — `SessionStart`, `Stop`,\n`SubagentStop`, `Notification`; `current_action` is refreshed by the heartbeat.\n\n```bash\nnpx -y @edngibson/reporter unpair   # reverse it: stop the service, strip the hooks, delete the config\n```\n\nThe sections below document the **manual** equivalents — `pair` automates all of\nthem. Reach for them to customize beyond what `pair` does, or where `npx` isn't\navailable.\n\n## Configuration\n\nConfig comes from **env vars** (which override an optional file at\n`~/.agent-dock/reporter.json`, or `$ADK_CONFIG`). The token and key arrive\n**out-of-band** from the dock's pairing bundle — they are never fetched from the\nserver.\n\n| Env var | File key | Required | Meaning |\n|---|---|---|---|\n| `ADK_SUPABASE_URL` | `supabaseUrl` | one of these | Project URL, e.g. `https://<ref>.supabase.co` (ingest URL is derived) |\n| `ADK_INGEST_URL` | `ingestUrl` | one of these | Full ingest URL (overrides the derived one) |\n| `ADK_REPORTER_TOKEN` | `token` | ✅ | Opaque per-host token (`adk_…`) from pairing |\n| `ADK_ENC_KEY` | `encKey` | ✅ | Shared key, **base64 of 32 bytes** |\n| `ADK_HEARTBEAT_SECONDS` | `heartbeatSeconds` | — | Snapshot interval, default `90` |\n| `ADK_CLAUDE_BIN` | `claudeBin` | — | Claude CLI command, default `claude` |\n| `ADK_APIKEY` | `apikey` | — | Optional gateway `apikey` header (not needed for the current `verify_jwt=false` ingest) |\n| `ADK_CONFIG` | — | — | Path to the config file |\n\nGenerate a fresh encryption key (put the **same** value in the dock and every host\nyou pair):\n\n```bash\nnode reporter.mjs gen-key      # prints base64 of 32 random bytes\n```\n\nExample `~/.agent-dock/reporter.json`:\n\n```json\n{\n  \"supabaseUrl\": \"https://txwhijfclpozpsukmypm.supabase.co\",\n  \"token\": \"adk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\",\n  \"encKey\": \"base64-of-32-bytes-same-as-the-dock\"\n}\n```\n\n## Install — Claude Code hooks (every host)\n\nAdd to `~/.claude/settings.json` (user-level → covers all projects). The reporter\nruns as a **`type:\"command\"` hook with `async:true`** so a slow or down rendezvous\n**never** blocks or fails the agent (`type:\"http\"` hooks block — that's why this is\na command hook). Use an absolute path to `reporter.mjs`.\n\n```jsonc\n{\n  \"hooks\": {\n    \"SessionStart\":  [{ \"hooks\": [{ \"type\": \"command\", \"command\": \"node /opt/agent-dock/reporter/reporter.mjs hook\", \"async\": true }] }],\n    \"Stop\":          [{ \"hooks\": [{ \"type\": \"command\", \"command\": \"node /opt/agent-dock/reporter/reporter.mjs hook\", \"async\": true }] }],\n    \"SubagentStop\":  [{ \"hooks\": [{ \"type\": \"command\", \"command\": \"node /opt/agent-dock/reporter/reporter.mjs hook\", \"async\": true }] }],\n    \"Notification\":  [{ \"hooks\": [{ \"type\": \"command\", \"command\": \"node /opt/agent-dock/reporter/reporter.mjs hook\", \"async\": true }] }],\n    \"PreToolUse\":    [{ \"matcher\": \"*\", \"hooks\": [{ \"type\": \"command\", \"command\": \"node /opt/agent-dock/reporter/reporter.mjs hook\", \"async\": true }] }],\n    \"PostToolUse\":   [{ \"matcher\": \"*\", \"hooks\": [{ \"type\": \"command\", \"command\": \"node /opt/agent-dock/reporter/reporter.mjs hook\", \"async\": true }] }]\n  }\n}\n```\n\nOn Windows use the absolute path, e.g.\n`node C:\\\\opt\\\\agent-dock\\\\reporter\\\\reporter.mjs hook`.\n\nCoverage notes: `SessionStart`/`Stop`/`SubagentStop`/`Pre|PostToolUse` fire in\n**both** interactive and background sessions. `Notification` (blocked-waiting) is\n**interactive-only**; in headless sessions the blocked state is caught by the next\nsnapshot, since `claude agents --json` reports `state:\"blocked\"`. `current_action`\nis best-effort — `PreToolUse` names the running tool; it reverts to nothing on the\nnext heartbeat.\n\n## Install — heartbeat service (every host)\n\n### Linux / VPS (systemd)\n\n`/etc/systemd/system/agent-dock-reporter.service`:\n\n```ini\n[Unit]\nDescription=agent-dock reporter (deadman heartbeat)\nAfter=network-online.target\nWants=network-online.target\n\n[Service]\nExecStart=/usr/bin/node /opt/agent-dock/reporter/reporter.mjs heartbeat\nEnvironment=ADK_SUPABASE_URL=https://txwhijfclpozpsukmypm.supabase.co\nEnvironment=ADK_REPORTER_TOKEN=adk_xxxxxxxx\nEnvironment=ADK_ENC_KEY=base64-of-32-bytes\nRestart=always\nRestartSec=10\nUser=youruser\n\n[Install]\nWantedBy=multi-user.target\n```\n\n```bash\nsudo systemctl daemon-reload\nsudo systemctl enable --now agent-dock-reporter\njournalctl -u agent-dock-reporter -f      # watch snapshots\n```\n\n### Windows (laptop)\n\nRun the heartbeat at logon via Task Scheduler:\n\n```powershell\n$action  = New-ScheduledTaskAction -Execute \"node\" `\n  -Argument \"C:\\opt\\agent-dock\\reporter\\reporter.mjs heartbeat\"\n$trigger = New-ScheduledTaskTrigger -AtLogOn\nRegister-ScheduledTask -TaskName \"agent-dock-reporter\" `\n  -Action $action -Trigger $trigger -RunLevel Limited\n```\n\nPut the config in `%USERPROFILE%\\.agent-dock\\reporter.json` (Task Scheduler doesn't\ninherit a shell's env vars).\n\n## Develop\n\n```bash\nnode --test          # unit tests (status/label mapping, snapshot, crypto round-trip + KAT)\nnode reporter.mjs gen-key\nADK_CLAUDE_BIN=./test-fixtures/fake-claude node reporter.mjs heartbeat   # point at a stub\n```\n\nThe AES-256-GCM wire envelope is `base64( nonce(12) ‖ ciphertext ‖ tag(16) )`. The\ndock decrypts the inverse with the Rust `aes-gcm` crate (Task 3); `reporter.test.mjs`\npins a known-answer vector so the two stay byte-compatible.\n","readmeFilename":"README.md","_rev":"1-084806ead051d9c3f0f80d089f53cc6e"}