{"_id":"@ab0t/acp","name":"@ab0t/acp","dist-tags":{"latest":"0.1.0"},"versions":{"0.1.0":{"name":"@ab0t/acp","version":"0.1.0","description":"TypeScript/browser SDK for ACP (Agent Coordination Protocol): shared filesystem, ordered event log, mailbox, fencing leases, CRDT co-editing, presence — over the acp/1 wire.","license":"MIT","type":"module","main":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./browser":"./dist/browser/acp.esm.js"},"engines":{"node":">=20"},"_id":"@ab0t/acp@0.1.0","_integrity":"sha512-LzOdu4JwrMt9i+6NCeKgJtmRSnv5+avcHHRW0tPrYe8hGOWH8w92sGnNK8b+yPzBEeRbGc1XjpQA7Oy7NjxCTA==","_resolved":"/home/ubuntu/tools/shared_filesystem/acp/dist/sdk-ts/ab0t-acp-0.1.0.tgz","_from":"file:/home/ubuntu/tools/shared_filesystem/acp/dist/sdk-ts/ab0t-acp-0.1.0.tgz","_nodeVersion":"22.19.0","_npmVersion":"10.9.3","dist":{"integrity":"sha512-LzOdu4JwrMt9i+6NCeKgJtmRSnv5+avcHHRW0tPrYe8hGOWH8w92sGnNK8b+yPzBEeRbGc1XjpQA7Oy7NjxCTA==","shasum":"02c9fd32c87be40ce931cc0ac7034e7ca793c1a2","tarball":"https://registry.npmjs.org/@ab0t/acp/-/acp-0.1.0.tgz","fileCount":24,"unpackedSize":411954,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQC1rUFpoWG6nzfwHu1XAZlwcPE16D0/P+uYyHGTHb/KQAIhAKsVu/GHWsn2IxXsWSw96fNfn8+27GhsIryr1YXFSIai"}]},"_npmUser":{"name":"ab0t","email":"mike@ab0t.com"},"directories":{},"maintainers":[{"name":"ab0t","email":"mike@ab0t.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/acp_0.1.0_1786583164998_0.9646578717410457"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-13T01:06:04.824Z","0.1.0":"2026-08-13T01:06:05.204Z","modified":"2026-08-13T01:06:05.478Z"},"maintainers":[{"name":"ab0t","email":"mike@ab0t.com"}],"description":"TypeScript/browser SDK for ACP (Agent Coordination Protocol): shared filesystem, ordered event log, mailbox, fencing leases, CRDT co-editing, presence — over the acp/1 wire.","license":"MIT","readme":"# @ab0t/acp — the ACP TypeScript SDK\n\nThe TypeScript/browser SDK for **ACP (Agent Coordination Protocol)**: a shared\ncontent-addressed filesystem, a totally-ordered event log, a directed mailbox,\nfencing-token leases, CRDT co-editing (text + JSON), and live presence — as\nordinary async calls against a self-hosted `coordd` daemon, over the frozen\n`acp/1` wire.\n\n- **Zero runtime dependencies.** Built-in `fetch` + web streams. Node ≥ 20 and\n  evergreen browsers.\n- **Same API shape as the Go SDK.** Same verbs, same error taxonomy, same\n  semantics (the SDK suite interface contract). Learn once.\n- **No Go toolchain, no source access needed** — the SDK speaks the public wire.\n\n> **Availability:** published on npm — `npm install @ab0t/acp`. It needs a\n> running `coordd` daemon (self-hosted; 60 seconds to start, see below). The\n> `acp` CLI and the `acp-mcp` bridge are the other public client surfaces; this\n> SDK is the programmatic path (Node + browser).\n\n## Quickstart (≤ 5 minutes)\n\n**1. Run a daemon** (60 seconds — one static binary or Docker):\n\n```bash\ncoordd -data ./acp-data -token dev-token\n# or: docker run -p 8443:8443 ab0tcom/acp\n# prints: clients: --server https://<host>:8443  --cert ./acp-data/cert.pem\n```\n\n**2. Install the SDK** into your project:\n\n```bash\nnpm install @ab0t/acp\n```\n\n**3. Connect and coordinate** (`demo.mjs`):\n\n```js\nimport { Client, APIError } from \"@ab0t/acp\";\n\nconst c = new Client({\n  baseUrl: \"https://localhost:8443\",\n  token: \"dev-token\",\n  agent: \"demo-1\",\n});\n\nawait c.health();                                   // reachable?\n\n// The event log: append a fact, read it back.\nconst ev = await c.append({ action: \"demo.start\", entity: \"run/1\" });\nconsole.log(\"appended seq\", ev.seq);\n\n// The shared filesystem: content-addressed blob + CAS commit.\nconst { hash, size } = await c.putBlob(\"# hello from TypeScript\\n\");\nconst m = await c.manifest();\ntry {\n  await c.commit({\n    base_version: m.version,\n    changes: [{ path: \"docs/hello.md\", hash, size }],\n    note: \"first commit\",\n  });\n} catch (err) {\n  if (err instanceof APIError && err.conflict()) {\n    // someone committed first — re-read the manifest, rebase, retry\n  } else throw err;\n}\nconsole.log(\"committed docs/hello.md\");\n\n// The mailbox: a directed message to another agent.\nawait c.send({ to: \"demo-2\", type: \"inform\", subject: \"hello\", body: \"file is up\" });\n\n// Follow the log live (Ctrl-C to stop).\nawait c.follow(ev.seq + 1, (e) => console.log(`#${e.seq} ${e.actor} ${e.action}`));\n```\n\n**4. Run it** (the daemon's cert is self-signed, so hand it to Node):\n\n```bash\nNODE_EXTRA_CA_CERTS=./acp-data/cert.pem node demo.mjs\n```\n\nThat's the whole loop: facts on an ordered log, files under CAS, messages,\nand a live stream — one daemon, no other services.\n\n## The primitives (when to use what)\n\n| You want to record… | Use |\n|---|---|\n| a **fact** / audit trail (correctness) | the **event log** — `append`, `follow` |\n| **mutual exclusion** across machines | a **lease** — `acquireLease` (the returned `token` is your fencing token; lease `file:<path>` to gate commits to that path) |\n| a **directed handoff** to one agent | the **mailbox** — `send` / `inbox` / `ack` (threads are keyed by the `thread_id` you set) |\n| an **artifact** (a file) | **blobs + a commit** — `putBlob` → `commit` (CAS; 409 ⇒ rebase on `err.current`, retry) |\n| a **live co-edited** document | a **CRDT doc** — `RGA` + `pushCRDTOps`/`pullCRDTOps` (text), `pushCRDTJSONOps`/`crdtJSONDoc` (JSON) |\n| an ephemeral **hint** (\"what I'm doing now\") | **awareness** — `setAwareness` / `followAwareness` (lossy; never correctness) |\n\n## Errors\n\nServer-side failures reject with `APIError`:\n\n```js\ntry {\n  await c.acquireLease(\"build:main\", 30);\n} catch (err) {\n  if (err instanceof APIError) {\n    if (err.conflict())  { /* 409 — contended: err.current is the holding lease */ }\n    if (err.locked())    { /* 423 — a write gated by another holder's lease */ }\n    if (err.overQuota()) { /* 507 — persistent quota; do NOT blind-retry */ }\n    // err.status has the raw code (429 = transient, back off and retry)\n  }\n}\n```\n\n## Co-editing text (CRDT)\n\n```js\nimport { RGA } from \"@ab0t/acp\";\n\nconst doc = new RGA(\"replica-A\");                 // stable, unique per client\nconst pulled = await c.pullCRDTOps(\"notes.txt\", 0);\nfor (const op of pulled.ops) doc.apply(op);       // fold in peers' ops\n\nconst ops = doc.generateOps(doc.text() + \"\\nmy new line\");\nawait c.pushCRDTOps(\"notes.txt\", ops, pulled.epoch);\n// a 409 with a new epoch means the doc was compacted: rebuild from pullCRDTOps(doc, 0)\n```\n\nTwo replicas (in any language) and the daemon converge on identical text —\nthat cross-language convergence is pinned by the SDK gate.\n\n## Browser bundles (plain HTML pages, no build step)\n\n`npm run build` also emits one-file browser bundles under `dist/browser/`:\n\n```html\n<!-- ES module -->\n<script type=\"module\">\n  import { Client, RGA } from \"/dist/browser/acp.esm.js\";\n</script>\n\n<!-- or a plain script tag: the SDK as a global -->\n<script src=\"/dist/browser/acp.global.js\"></script>\n<script> const c = new ACP.Client({ baseUrl, token, agent: \"page-1\" }); </script>\n```\n\nBoth are dependency-free, ES2022, sourcemapped. (Remember the browser rules\nbelow: tokenless pages behind a credential broker.)\n\n## Agent helpers (the two loops every agent hand-rolls)\n\n```js\n// Follow the log FOREVER: auto-reconnect, resume from lastSeq+1, backoff.\nconst stop = new AbortController();\nawait c.followForever(0, (e) => handle(e), {\n  filter: new EventFilter({ actions: [\"task.*\"] }),\n  signal: stop.signal,\n});\n\n// Hold a lease SAFELY: waits out contention, renews at ~TTL/3, releases;\n// `lost` fires if a renewal fails — stop side-effecting immediately.\nawait c.withLease(\"build:main\", 30, async (lease, lost) => {\n  // lease.token is your fencing token — present it on protected writes\n  await doTheWork({ signal: lost });\n});\n```\n\n## The examples: collab-docs + collab-notebook\n\n- `examples/collab-docs/` — a collaborative document: two browser tabs typing\n  into one doc, conflict-free, live presence carets — then `node agent.mjs`\n  and an **agent co-author types alongside you**. Uses the ESM bundle.\n- `examples/collab-notebook/` — a Colab-style shared notebook: JSON-CRDT cell\n  structure + a text-CRDT per cell, per-cell presence, and a local-only \"Run\"\n  (code executes in YOUR tab — the daemon never runs code; outputs are shared\n  as data). Its agent adds and fills cells next to yours. Uses the\n  `<script>`-tag global bundle.\n- `examples/serve.mjs` — the shared static server + credential broker both\n  demos run behind (tokenless pages). `examples/lib/` — the reusable bindings\n  (`TextDocSync`, `Presence`) that show the recommended sync-loop shapes.\n\n## Browsers, tokens, and TLS (read this before shipping a web page)\n\n- **Do not put a space-wide writer token in an untrusted page.** Today's\n  tokens scope by role/space/path-prefix — per-end-user read-scoping is a\n  daemon roadmap item. Until then, browser deployments should be trusted\n  surfaces (internal tools, kiosks) or fronted by a **gateway** that holds the\n  credential and proxies (the page stays tokenless).\n- **TLS:** browsers cannot pin a self-signed daemon cert. Use a real\n  certificate on `coordd`, or terminate TLS at your gateway/reverse proxy.\n- **WebSocket auth:** `coordd` authenticates the awareness WebSocket upgrade\n  via the `Authorization` header, which browsers cannot set on a WebSocket —\n  browser pages use the HTTP awareness follow, or a credential-injecting\n  gateway. In Node, pass a header-capable factory:\n\n```js\nimport WebSocket from \"ws\";\nconst c = new Client({\n  baseUrl, token, agent: \"dash-1\",\n  webSocket: (url, headers) => new WebSocket(url, { headers }),\n});\nconst sock = await c.awarenessSocket({ onDelta: (d) => render(d) }); // needs coordd -awareness-ws\nsock.set({ cursor: [12, 40] }, 30, \"tab-1\");\n```\n\n- **Clusters:** a follower answers live awareness follows with a redirect to\n  the leader; Node hops it automatically, browsers cannot (the SDK throws a\n  clear error) — point browser clients at the leader or a gateway.\n\n## Testing\n\n```bash\nnpm test                      # build + unit tests (CRDT convergence, matcher parity, transport)\n../../hack/run-ts-sdk-gate.sh # the full acceptance gate against a real local coordd\n```\n\n## Relationship to the other ACP surfaces\n\n- **`acp` CLI** — humans and scripts; the same primitives as commands.\n- **`acp-mcp`** — agents in MCP harnesses (Claude Code, Codex, …).\n- **Go SDK** (`pkg/client`) — Go services; same interface contract as this SDK.\n\nThe SDK suite contract (verbs, naming rules, error taxonomy) lives in the\n`INTERFACE.md` of the SDK-suite ticket; this package conforms to it.\n","readmeFilename":"README.md","_rev":"1-66b9e69a0bf4a4c191591f3438f4689c"}