npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@ademu/adc-control

v0.1.1

Published

TypeScript operator client for the ADC daemon's control socket (PROTOCOL.md Part II, shipped in @ademu/adc-client) — pairing ceremony + token mint + ensureDaemon().

Readme

@ademu/adc-control

The operator-side onboarding client for the ADC daemon's control socket. PROTOCOL.md (Part II; shipped in @ademu/adc-client's tarball, adc/PROTOCOL.md in the repository) is the contract; this library is one consumer of it and cites it, never substitutes for it. The session protocol (Part I) is @ademu/adc-client's territory — this package never handles a session or an fd. Together they are the two halves of a programmatic agent connector: this package gets a device onto Ademú (pairing ceremony + token mint); the session client is how the agent lives there.

Zero runtime dependencies beyond @ademu/adc-client (whose /internal subpath supplies the shared NDJSON framing), ESM, strict TypeScript, engines.node >= 20.

Install

npm i @ademu/adc-control

Pulls @ademu/adc-client at the same lockstep version (the range is ^<this package's own version>, re-pinned every release). Published on npm from a private monorepo, so the tarball is self-sufficient: dist/, src/, README.md, LICENSE-MIT + LICENSE-APACHE. Releases are cut by tag (clients-v<semver>, both libraries on one version) and published by CI via npm Trusted Publishing — no stored or long-lived npm token exists anywhere. Pair it with @ademu/adc-bin when the host must not rely on adc being on PATH: ensureDaemon({ binaryPath: resolveAdcBinaryPath() }). Release procedure for maintainers: docs/RELEASING_NPM.md.

Authority: ambient, same-UID, operator-side ONLY

This package wields ambient operator authority — the same authority as the adc CLI: any process running as the daemon's UID can drive it. It is for operator-side tooling (plugins running on the owner's machine) and must NEVER be reachable by remote or untrusted input. Authority never moves off the owner: creating a device is inert until the owner's phone scans the QR, and the four-word comparison stays a human attestation — this package can render the words, never confirm them on its own authority. The CLI is for humans; this package is for programs.

Hardened hosts (forward-compat). On a hardened deployment (dedicated service user, mint-authority separation — the "3b" posture), same-UID self-provisioning WILL fail. The typed PrivilegeError is reserved now for exactly that refusal: it is exported (write your degradation branch today — print operator instructions and stop) but constructed nowhere in v1; it maps to the structured code when 3b mints one.

Quickstart — the ceremony, as the plugin drives it

import { ensureDaemon, connect } from '@ademu/adc-control';
import { connect as attachSession } from '@ademu/adc-client';

await ensureDaemon();                       // probe; spawn `adc daemon run` if absent
const control = await connect();            // daemon greets first; proto gated

const { device_id } = await control.createDevice({ agent_name: 'my-agent' });

// Render each snapshot in YOUR UI: show the QR (qrPayload), and the four
// safety words when they appear. The owner scans with their phone and
// confirms out-of-band; the poll resolves when the daemon reaches a
// terminal state.
const terminal = await control.pollPairing(device_id, (s) => render(s));
if (terminal.state !== 'enrolled') throw new Error(`ceremony ended ${terminal.state}`);

const { token } = await control.tokenMint({ device_id, label: 'my-plugin' });
const { session_socket_path } = await control.daemonInfo();
await control.close();

// The handoff: token + the daemon's OWN session socket path (the authority —
// never re-derive it while the daemon is reachable). The field is optional
// on the wire (pre-tier-1 daemons omit it) and the session client would
// silently fall back to ITS ladder if handed undefined — so fail loudly
// instead: a daemon that old cannot do token attach anyway.
if (session_socket_path === undefined) {
  throw new Error('this daemon predates token attach — upgrade it');
}
const session = await attachSession({ token, socketPath: session_socket_path });

pollPairing is deliberately thin: the CLI's own 1 s cadence, a snapshot per poll (terminal included — dedupe in your renderer if you want to), terminal at enrolled/revoked/retired, cancellation via AbortSignal. There is no "words" state — words availability is the words field, orthogonal to state.

ensureDaemon() — spawn-if-needed, with two recorded divergences

Ports the CLI's semantics (adc agent add's probe → spawn → backoff-wait, the exact [50,100,200,400,800,1600,3200] ms ladder), spawning adc from PATH detached with stdout/err appended to <data_dir>/daemon.log (0600). The pre-spawn probe is a bare connect; every post-spawn probe reads and validates the hello (1 s per rung), so a wedged or incompatible listener fails loudly instead of reporting ready. Two divergences from the CLI, both deliberate:

  1. No config pre-flight. This library parses no TOML; a daemon that refuses to boot (missing ADC_REST_BASE_URL/ADC_WS_URL or [server] config) surfaces as DaemonUnreachableError naming the daemon.log to read.
  2. No explicit spawn flags. The daemon is spawned as plain adc daemon run — a config-blind client must never override the daemon's config-aware path resolution. If your config relocates the socket, set ADC_SOCKET_PATH.

ensureDaemon({ binaryPath }) spawns that file instead of looking adc up on PATH — pair it with @ademu/adc-bin's resolveAdcBinaryPath() when the host must not rely on a PATH install (the OpenClaw plugin does). Only the command changes; the argv stays daemon run. ENOENT on the given path is still NotInstalledError, now carrying .binaryPath and a message that names the path instead of the install one-liner.

The auto-spawn inherits the CLI's accepted connect-probe race (two clients may both spawn; the loser fails cleanly — same-user blast radius only).

Socket path resolution

The daemon's hardened ladder, ported exactly: $ADC_SOCKET_PATH → gated $XDG_RUNTIME_DIR (never on macOS) → $ADC_DATA_DIR verbatim → the XDG data chain, filename adc.sock. Never probes candidates; joins verbatim. Deliberately omitted: the config-file rungs — this library is zero-dependency and parses no TOML; pass socketPath or set ADC_SOCKET_PATH if your daemon's socket moved via config.

Requests, timeouts, and the excluded verbs

request(op, params, { timeoutMs }) is the generic escape hatch; the eight typed wrappers (createDevice, listDevices, deviceStatus, confirmWords, getPairingDisplay, cancelPairing, tokenMint, daemonInfo) only name-type-delegate onto it (enforced by test). The 30 s default timeout is hang insurance, not a protocol signal — the control socket answers every well-formed request line (bad_request and unknown_op included). One connection per client; no reconnect machinery — reconnect by calling connect() again.

Excluded on purpose: attach/detach (session territory — an attach hands an fd over SCM_RIGHTS, which this package never touches), shutdown, and token_list/token_revoke. Forward note: the plugin's future "forget Ademú" flow will need token_revoke; it is additive when that flow ships, not before. token_mint rotation is replace: true — the type is the literal true, so a plain mint omits the key entirely (wire parity with the daemon's serde).

Error taxonomy

| Error | When | Remedy | | --- | --- | --- | | NotInstalledError | adc not on PATH | the message carries the install one-liner | | DaemonUnreachableError | spawn-wait exhausted, or the child exited | read .logPath; if config relocates the socket, set ADC_SOCKET_PATH | | ProtoSkewError | the hello's proto ≠ 1 | upgrade one side; never guess at the wire | | HelloTimeoutError | no hello within 10 s of connect | the peer is wedged or not the daemon | | 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 | | ControlTimeoutError | no reply within the deadline (default 30 s) | a wedged/incompatible daemon, or an oversized line the daemon dropped | | ConnectionClosedError | EOF/close; carries {op, id} when one was in flight | reconcile via queries, reconnect | | LineTooLongError | outbound frame over the daemon's 1 MiB line cap (refused pre-write) | from @ademu/adc-client — shared instanceof | | ProtocolViolationError | the peer sent what a correct daemon never sends | it may not be the daemon at all | | PrivilegeError | reserved (hardened hosts, 3b) | degrade to printing operator instructions |

No error message ever carries token plaintext, agent names, QR payloads, or safety words — codes, ops, ids, paths, and byte lengths only (CI-enforced by dev/privacy-audit.sh's TS scan).

Testing

npm test = strict compile + compile-time type fixtures (test/types/) + node:test suites, including golden-fixture conformance (test/fixture.test.mjs — the control half's v1 lines, decode and reproduce classes, unknown-tolerance; the Rust suite owns the total line count) and the privacy-detector self-test (test/privacy-pattern.test.mjs, driving the real audit script over a bait tree). CI: .github/workflows/js.yml (the adc/clients workspace, node 20 + current LTS). The manual live lane is integration/live-control.sh (deliberately outside test/): a fully scratch daemon lifecycle — spawn, ceremony-to-cancel, pre-enrollment mint — with no phone ceremony (that belongs to the plugin slice's E2E).