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-client

v0.1.1

Published

TypeScript session client for the ADC daemon's device-session protocol (PROTOCOL.md, Part I — shipped in this tarball) — the mind-side library.

Readme

@ademu/adc-client

The TypeScript session client for the ADC daemon's device-session protocol — the mind-side library. PROTOCOL.md (Part I; shipped alongside this README in the tarball, adc/PROTOCOL.md in the repository) is the contract; this library is one consumer of it and cites it, never substitutes for it. It is conformance-tested against the same golden fixture that pins the daemon (adc/adc-proto/tests/fixtures/ipc_v1.ndjson), so it structurally cannot drift.

Zero runtime dependencies (node stdlib only), ESM, strict TypeScript, engines.node >= 20.

Install

npm i @ademu/adc-client

Published on npm from a private monorepo, so the tarball is self-sufficient: dist/ (ESM + .d.ts + source maps), src/ (the TypeScript the maps point at), PROTOCOL.md (the contract this README cites — Part I is the session protocol), README.md, LICENSE-MIT + LICENSE-APACHE. Releases are cut by tag (clients-v<semver>) and published by CI via npm Trusted Publishing — no stored or long-lived npm token exists anywhere. Verify a tarball's contents with npm pack @ademu/adc-client && tar tzf ademu-adc-client-*.tgz. Release procedure for maintainers: docs/RELEASING_NPM.md.

Quickstart

import { connect } from '@ademu/adc-client';

const client = await connect({ token: process.env.ADC_TOKEN! });
console.log(client.hello.device_id, client.hello.last_acked_seq);

for await (const ev of client.events()) {
  if (ev.known && ev.event === 'message_received') {
    await client.sendText({ group_id: ev.group_id, body: `echo: ${ev.body}` });
    client.ackThrough(ev); // ONLY after your handling durably succeeded
  } else {
    client.ackThrough(ev);
  }
}

examples/echo.ts is the runnable version (npm run build:examples, then ADC_TOKEN='adc1_…' node examples-dist/examples/echo.js).

Acks carry read-receipt weight — there is no auto-ack

An ack {seq} cumulatively marks every durable event ≤ seq as handed off, and handed-off fires read receipts to the humans in the conversation (PROTOCOL.md §The seq/ack contract, rider R3 — agents have no read-receipt opt-out). That is why this library never acks on its own and never will: call ack(seq) / ackThrough(event) yourself, after YOUR handling durably succeeded — not on receipt. The cursor lives daemon-side (hello.last_acked_seq); the client persists nothing.

Requests, and the ambiguous-outcome discipline

request(op, params?, {timeoutMs}) correlates replies strictly by id (pipelining is legal); the twelve typed wrappers (sendText … searchMessages) are TRANSPARENT — each only names the op, types the params/reply, and delegates, enforced by test. request() stays public as the forward-compatibility escape hatch: the surface is additive-forever and this client is a snapshot — a newer daemon's ops are reachable via request + a client.capabilities.has(op) check without waiting for a package update. Wrappers do NOT pre-check capabilities (no hidden logic).

A rejected command promise does not mean the command failed. A command can become durable before its reply is written, so DetachedError and RequestTimeoutError are OUTCOME-UNKNOWN: reconcile via queries (getMessages, getMessageStatus) before retrying side-effecting ops, and never blind-auto-retry commands. The daemon silently drops malformed/unknown requests (no error reply — PROTOCOL.md §Commands and queries), so a timeout's likeliest causes are an op this daemon doesn't serve (check capabilities) or a malformed request shape.

Reconnect semantics

reconnect: 'auto' (the default) survives daemon restarts: on post-seat EOF the client backs off (full jitter, 200 ms base, 5 s cap), re-resolves the socket path (an explicit socketPath stays pinned verbatim), re-authenticates, and the daemon replays un-acked durable events into the SAME events() stream. ECONNREFUSED and ENOENT are both transient (the restart window — PROTOCOL.md §Reconnect & liveness). Terminal: invalid_token, device_not_ready, and already_attached (the last after a bounded grace of 3 fixed-delay retries, because a fast re-attach can race the daemon's EOF processing of your old channel).

Takeover is one-shot. takeover: true applies to the initial connect() only; reconnects always re-auth with takeover: false — a displaced client must not evict the newer legitimate holder (and two auto-reconnecting takeover clients would ping-pong forever). The wedged-holder recipe (RUNBOOK §Token lifecycle): start a NEW client with takeover: true; the wedged holder sees plain EOF.

No heartbeat exists and none should be added — liveness is "read until EOF". The client reads eagerly and buffers events in memory without bound: the daemon auto-detaches a mind whose socket stays blocked for 5 s, so pacing socket reads by consumer pull would be strictly worse; pace your HANDLING (and thus your acks), not the reads.

Socket path resolution

The daemon's hardened ladder, ported exactly (PROTOCOL.md §Resolving the session socket path): $ADC_SESSION_SOCKET_PATH → gated $XDG_RUNTIME_DIR (never on macOS) → $ADC_DATA_DIR verbatim → the XDG data chain. Never probes candidates; joins verbatim. Deliberately omitted: the two config-file rungs ([daemon] session_socket_path and [daemon] data_dir) — this library is zero-dependency and parses no TOML. If your daemon's socket moved via config, pass socketPath or set ADC_SESSION_SOCKET_PATH; a reachable daemon's daemon_info.session_socket_path is always the authority (adc doctor prints it).

@ademu/adc-client/internal — for sibling packages only

The ./internal subpath export carries the NDJSON framing layer (LineDecoder, encodeFrame, the two byte caps) for sibling @ademu packages — today @ademu/adc-control. It is internal and semver-exempt: anything there may change or vanish in any release without a major bump. Applications import from the root export only. The framing errors (LineTooLongError, ProtocolViolationError) stay on the root export so instanceof identity is shared everywhere.

Error taxonomy

| Error | When | Terminal for auto-reconnect? | | --- | --- | --- | | InvalidTokenError | reject invalid_token (deliberately undifferentiated — no oracle) | yes | | DeviceNotReadyError | reject device_not_ready: the device is not enrolled. Tokens can exist for a not-yet-enrolled device — if enrollment is mid-flight, retry with a fresh connect() after adc agent add completes. | yes (fail-fast by design) | | AlreadyAttachedError | reject already_attached with takeover:false | initial: immediately; reconnect: after the bounded grace | | HandshakeClosedError | EOF before the hello, no reject line (timeout/oversize/internal — ambiguous BY DESIGN) | transient | | HandshakeTimeoutError | no hello within 10 s. Attachment outcome UNKNOWN: the daemon may still process the queued attach late; a fresh connect may briefly see already_attached. | transient | | DetachedError | the session ended (reason: 'eof' | 'closed' | 'reconnecting'); carries {op, id} for in-flight requests — outcome unknown | n/a | | RequestError | an ok:false reply; branch on .code. The daemon's debug text is a deliberate read via non-enumerable .detail — it never reaches err.message, JSON.stringify, or util.inspect | n/a | | RequestTimeoutError | no reply within the deadline (default 30 s) — outcome unknown | n/a | | LineTooLongError | outbound: refused pre-write (the daemon reads ≤ 1 MiB/line); inbound: over the 64 MiB library safety guard | inbound: yes | | ProtocolViolationError | the peer sent what a correct daemon never sends (malformed JSON, illegal root, invalid UTF-8) | yes — it may not be the daemon at all |

Testing

npm test = strict compile + compile-time type fixtures (test/types/) + example type-check + node:test suites, including the golden-fixture conformance suite (test/fixture.test.mjs) — every mind-facing fixture line, decode and reproduce classes, unknown-tolerance legs. CI: .github/workflows/js.yml (node 20 + current LTS). The manual live-daemon lane is integration/live-daemon.sh (deliberately outside test/).