{"_id":"@ademu/adc-control","_rev":"2-24309d7c7584733e9d2b4cebc40e905a","name":"@ademu/adc-control","dist-tags":{"latest":"0.1.1"},"versions":{"0.1.0":{"name":"@ademu/adc-control","version":"0.1.0","author":{"url":"https://ademu.com","name":"Ademú","email":"npm@ademu.com"},"license":"(MIT OR Apache-2.0)","_id":"@ademu/adc-control@0.1.0","maintainers":[{"name":"marios_ademu","email":"npm@ademu.com"}],"homepage":"https://ademu.com","dist":{"shasum":"37bc44070ab5c3659ad8d3a58ac083b1e2a81e82","tarball":"https://registry.npmjs.org/@ademu/adc-control/-/adc-control-0.1.0.tgz","fileCount":34,"integrity":"sha512-AfF/qOIHGvNmsoTfW/lCA/wPYvO0o/i65M6fQx7J9ftmNmmlw3Tf0msPlmABb7CZ2Yt6BB7AhaeA+hsvDpaZMg==","signatures":[{"sig":"MEUCIEhYEG2oJspv91cpM3CuKgCuQThR1dIvye+3UQAJO3HkAiEApepvlj1mkxZ/Daa/rEaE32MGXl6UTHwhM0/mzfFu+4Y=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":149529},"type":"module","engines":{"node":">=20"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"gitHead":"8e0c1ca4510920122e2abbd2360f9ae25677b7fc","scripts":{"test":"tsc && tsc -p tsconfig.typetests.json && node --test test/*.test.mjs","build":"tsc"},"_npmUser":{"name":"marios_ademu","email":"npm@ademu.com"},"_npmVersion":"11.19.0","description":"TypeScript operator client for the ADC daemon's control socket (PROTOCOL.md Part II, shipped in @ademu/adc-client) — pairing ceremony + token mint + ensureDaemon().","directories":{},"_nodeVersion":"24.20.0","dependencies":{"@ademu/adc-client":"^0.1.0"},"publishConfig":{"access":"public","registry":"https://registry.npmjs.org/"},"_hasShrinkwrap":false,"devDependencies":{"typescript":"^5.9.2","@types/node":"^20.19.0"},"_npmOperationalInternal":{"tmp":"tmp/adc-control_0.1.0_1788444804809_0.49905463659722593","host":"s3://npm-registry-packages-npm-production"}},"0.1.1":{"name":"@ademu/adc-control","version":"0.1.1","description":"TypeScript operator client for the ADC daemon's control socket (PROTOCOL.md Part II, shipped in @ademu/adc-client) — pairing ceremony + token mint + ensureDaemon().","type":"module","license":"(MIT OR Apache-2.0)","author":{"name":"Ademú","email":"npm@ademu.com","url":"https://ademu.com"},"homepage":"https://ademu.com","engines":{"node":">=20"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"publishConfig":{"access":"public","registry":"https://registry.npmjs.org/"},"scripts":{"build":"tsc","test":"tsc && tsc -p tsconfig.typetests.json && node --test test/*.test.mjs"},"dependencies":{"@ademu/adc-client":"^0.1.1"},"devDependencies":{"@types/node":"^20.19.0","typescript":"^5.9.2"},"_id":"@ademu/adc-control@0.1.1","_integrity":"sha512-HhUjjbxXsHh40uQhBYZWZU8cmocNKPErfjheZ+E+ELFPhOtGwLwkHhimBc09BJ734P+GbxwUNXjTkvqQ7sDLZw==","_resolved":"/home/runner/work/_temp/tarballs/ademu-adc-control-0.1.1.tgz","_from":"file:/home/runner/work/_temp/tarballs/ademu-adc-control-0.1.1.tgz","_nodeVersion":"24.20.0","_npmVersion":"11.19.0","dist":{"integrity":"sha512-HhUjjbxXsHh40uQhBYZWZU8cmocNKPErfjheZ+E+ELFPhOtGwLwkHhimBc09BJ734P+GbxwUNXjTkvqQ7sDLZw==","shasum":"7533c14694e263faba4db3e9edf823f63a5ff709","tarball":"https://registry.npmjs.org/@ademu/adc-control/-/adc-control-0.1.1.tgz","fileCount":34,"unpackedSize":149529,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQD8iMymhcE4qBaYaLt3gY0xMtLXSKmmIHvJCTTGLfWjWgIgCV9fEnz1m2L+dJeg2/FU7aCopwaDKk/mdSaXDInWXCs="}]},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:cb220e55-ce29-48ff-9aa0-6774e70ac375"}},"directories":{},"maintainers":[{"name":"marios_ademu","email":"npm@ademu.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/adc-control_0.1.1_1788446999698_0.900436759770467"},"_hasShrinkwrap":false}},"time":{"created":"2026-09-03T14:13:24.684Z","modified":"2026-09-03T14:50:00.076Z","0.1.0":"2026-09-03T14:13:25.017Z","0.1.1":"2026-09-03T14:49:59.848Z"},"author":{"name":"Ademú","email":"npm@ademu.com","url":"https://ademu.com"},"license":"(MIT OR Apache-2.0)","homepage":"https://ademu.com","description":"TypeScript operator client for the ADC daemon's control socket (PROTOCOL.md Part II, shipped in @ademu/adc-client) — pairing ceremony + token mint + ensureDaemon().","maintainers":[{"name":"marios_ademu","email":"npm@ademu.com"}],"readme":"# @ademu/adc-control\n\nThe operator-side onboarding client for the ADC daemon's **control socket**. **`PROTOCOL.md`\n(Part II; shipped in `@ademu/adc-client`'s tarball, `adc/PROTOCOL.md` in the repository) is the\ncontract; this library is one consumer of it and cites it, never substitutes for it.** The session\nprotocol (Part I) is [`@ademu/adc-client`](https://www.npmjs.com/package/@ademu/adc-client)'s territory — this package\nnever handles a session or an fd. Together they are the two halves of a programmatic agent\nconnector: this package gets a device **onto** Ademú (pairing ceremony + token mint); the session\nclient is how the agent **lives** there.\n\nZero runtime dependencies beyond `@ademu/adc-client` (whose `/internal` subpath supplies the\nshared NDJSON framing), ESM, strict TypeScript, `engines.node >= 20`.\n\n## Install\n\n```sh\nnpm i @ademu/adc-control\n```\n\nPulls `@ademu/adc-client` at the same lockstep version (the range is `^<this package's own\nversion>`, re-pinned every release). Published on npm from a private monorepo, so the tarball is\nself-sufficient: `dist/`, `src/`, `README.md`, `LICENSE-MIT` + `LICENSE-APACHE`. Releases are cut\nby tag (`clients-v<semver>`, both libraries on one version) and published by CI via npm Trusted\nPublishing — no stored or long-lived npm token exists anywhere. Pair it with `@ademu/adc-bin` when the host must not rely\non `adc` being on PATH: `ensureDaemon({ binaryPath: resolveAdcBinaryPath() })`. Release procedure\nfor maintainers: `docs/RELEASING_NPM.md`.\n\n## Authority: ambient, same-UID, operator-side ONLY\n\n**This package wields ambient operator authority — the same authority as the `adc` CLI: any\nprocess running as the daemon's UID can drive it. It is for operator-side tooling (plugins\nrunning on the owner's machine) and must NEVER be reachable by remote or untrusted input.**\nAuthority never moves off the owner: creating a device is inert until the owner's phone scans the\nQR, and the four-word comparison stays a human attestation — this package can render the words,\nnever confirm them on its own authority. The CLI is for humans; this package is for programs.\n\n**Hardened hosts (forward-compat).** On a hardened deployment (dedicated service user,\nmint-authority separation — the \"3b\" posture), same-UID self-provisioning WILL fail. The typed\n`PrivilegeError` is reserved now for exactly that refusal: it is exported (write your degradation\nbranch today — print operator instructions and stop) but constructed nowhere in v1; it maps to\nthe structured code when 3b mints one.\n\n## Quickstart — the ceremony, as the plugin drives it\n\n```ts\nimport { ensureDaemon, connect } from '@ademu/adc-control';\nimport { connect as attachSession } from '@ademu/adc-client';\n\nawait ensureDaemon();                       // probe; spawn `adc daemon run` if absent\nconst control = await connect();            // daemon greets first; proto gated\n\nconst { device_id } = await control.createDevice({ agent_name: 'my-agent' });\n\n// Render each snapshot in YOUR UI: show the QR (qrPayload), and the four\n// safety words when they appear. The owner scans with their phone and\n// confirms out-of-band; the poll resolves when the daemon reaches a\n// terminal state.\nconst terminal = await control.pollPairing(device_id, (s) => render(s));\nif (terminal.state !== 'enrolled') throw new Error(`ceremony ended ${terminal.state}`);\n\nconst { token } = await control.tokenMint({ device_id, label: 'my-plugin' });\nconst { session_socket_path } = await control.daemonInfo();\nawait control.close();\n\n// The handoff: token + the daemon's OWN session socket path (the authority —\n// never re-derive it while the daemon is reachable). The field is optional\n// on the wire (pre-tier-1 daemons omit it) and the session client would\n// silently fall back to ITS ladder if handed undefined — so fail loudly\n// instead: a daemon that old cannot do token attach anyway.\nif (session_socket_path === undefined) {\n  throw new Error('this daemon predates token attach — upgrade it');\n}\nconst session = await attachSession({ token, socketPath: session_socket_path });\n```\n\n`pollPairing` is deliberately thin: the CLI's own 1 s cadence, a snapshot per poll (terminal\nincluded — dedupe in your renderer if you want to), terminal at `enrolled`/`revoked`/`retired`,\ncancellation via `AbortSignal`. There is no `\"words\"` state — words availability is the `words`\n**field**, orthogonal to `state`.\n\n## `ensureDaemon()` — spawn-if-needed, with two recorded divergences\n\nPorts the CLI's semantics (`adc agent add`'s probe → spawn → backoff-wait, the exact\n`[50,100,200,400,800,1600,3200]` ms ladder), spawning `adc` from PATH detached with stdout/err\nappended to `<data_dir>/daemon.log` (0600). The pre-spawn probe is a bare connect; every\npost-spawn probe reads and **validates the hello** (1 s per rung), so a wedged or incompatible\nlistener fails loudly instead of reporting ready. Two divergences from the CLI, both deliberate:\n\n1. **No config pre-flight.** This library parses no TOML; a daemon that refuses to boot (missing\n   `ADC_REST_BASE_URL`/`ADC_WS_URL` or `[server]` config) surfaces as `DaemonUnreachableError`\n   naming the `daemon.log` to read.\n2. **No explicit spawn flags.** The daemon is spawned as plain `adc daemon run` — a config-blind\n   client must never override the daemon's config-aware path resolution. If your config relocates\n   the socket, set `ADC_SOCKET_PATH`.\n\n`ensureDaemon({ binaryPath })` spawns that file instead of looking `adc` up on PATH — pair it with\n`@ademu/adc-bin`'s `resolveAdcBinaryPath()` when the host must not rely on a PATH install (the\nOpenClaw plugin does). Only the command changes; the argv stays `daemon run`. ENOENT on the given\npath is still `NotInstalledError`, now carrying `.binaryPath` and a message that names the path\ninstead of the install one-liner.\n\nThe auto-spawn inherits the CLI's accepted connect-probe race (two clients may both spawn; the\nloser fails cleanly — same-user blast radius only).\n\n## Socket path resolution\n\nThe daemon's hardened ladder, ported exactly: `$ADC_SOCKET_PATH` → gated `$XDG_RUNTIME_DIR`\n(never on macOS) → `$ADC_DATA_DIR` verbatim → the XDG data chain, filename `adc.sock`. Never\nprobes candidates; joins verbatim. **Deliberately omitted:** the config-file rungs — this library\nis zero-dependency and parses no TOML; pass `socketPath` or set `ADC_SOCKET_PATH` if your\ndaemon's socket moved via config.\n\n## Requests, timeouts, and the excluded verbs\n\n`request(op, params, { timeoutMs })` is the generic escape hatch; the eight typed wrappers\n(`createDevice`, `listDevices`, `deviceStatus`, `confirmWords`, `getPairingDisplay`,\n`cancelPairing`, `tokenMint`, `daemonInfo`) only name-type-delegate onto it (enforced by test).\nThe 30 s default timeout is hang insurance, not a protocol signal — the control socket answers\nevery well-formed request line (`bad_request` and `unknown_op` included). One connection per\nclient; no reconnect machinery — reconnect by calling `connect()` again.\n\n**Excluded on purpose:** `attach`/`detach` (session territory — an attach hands an fd over\n`SCM_RIGHTS`, which this package never touches), `shutdown`, and `token_list`/`token_revoke`.\nForward note: the plugin's future \"forget Ademú\" flow will need `token_revoke`; it is additive\nwhen that flow ships, not before. `token_mint` rotation is `replace: true` — the type is the\nliteral `true`, so a plain mint omits the key entirely (wire parity with the daemon's serde).\n\n## Error taxonomy\n\n| Error | When | Remedy |\n| --- | --- | --- |\n| `NotInstalledError` | `adc` not on PATH | the message carries the install one-liner |\n| `DaemonUnreachableError` | spawn-wait exhausted, or the child exited | read `.logPath`; if config relocates the socket, set `ADC_SOCKET_PATH` |\n| `ProtoSkewError` | the hello's `proto` ≠ 1 | upgrade one side; never guess at the wire |\n| `HelloTimeoutError` | no hello within 10 s of connect | the peer is wedged or not the daemon |\n| `ControlError` | an `ok:false` reply; branch on `.code`, never parse `.message` | reachable v1 codes: `bad_request`, `unknown_op`, `unknown_device`, `device_not_ready`, `words_mismatch`, `not_cancellable`, `invalid_state`, `invalid_agent_name`, `label_exists`, `token_not_found`, `internal_error` |\n| `ControlTimeoutError` | no reply within the deadline (default 30 s) | a wedged/incompatible daemon, or an oversized line the daemon dropped |\n| `ConnectionClosedError` | EOF/close; carries `{op, id}` when one was in flight | reconcile via queries, reconnect |\n| `LineTooLongError` | outbound frame over the daemon's 1 MiB line cap (refused pre-write) | from `@ademu/adc-client` — shared `instanceof` |\n| `ProtocolViolationError` | the peer sent what a correct daemon never sends | it may not be the daemon at all |\n| `PrivilegeError` | **reserved** (hardened hosts, 3b) | degrade to printing operator instructions |\n\nNo error message ever carries token plaintext, agent names, QR payloads, or safety words — codes,\nops, ids, paths, and byte lengths only (CI-enforced by `dev/privacy-audit.sh`'s TS scan).\n\n## Testing\n\n`npm test` = strict compile + compile-time type fixtures (`test/types/`) + `node:test` suites,\nincluding golden-fixture conformance (`test/fixture.test.mjs` — the control half's v1 lines,\ndecode and reproduce classes, unknown-tolerance; the Rust suite owns the total line count) and\nthe privacy-detector self-test (`test/privacy-pattern.test.mjs`, driving the real audit script\nover a bait tree). CI: `.github/workflows/js.yml` (the `adc/clients` workspace, node 20 + current\nLTS). The manual live lane is `integration/live-control.sh` (deliberately outside `test/`): a\nfully scratch daemon lifecycle — spawn, ceremony-to-cancel, pre-enrollment mint — with no phone\nceremony (that belongs to the plugin slice's E2E).\n","readmeFilename":"README.md"}