From 6e9bc3cdc074f23ebf20a762820d29e0eb342112 Mon Sep 17 00:00:00 2001 From: Anthony Ettinger Date: Mon, 15 Jun 2026 14:04:56 +0000 Subject: [PATCH] docs: PRDs for agent/automation CLI & SDK + ugig job postings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add docs/prds/ describing the machine-facing surface of qrypt.chat so AI agents and automation (esp. AgentBBS members) can use it headlessly: - prd-01-sdk-core @qryptchat/sdk — auth (3 tiers), client-side ML-KEM identity, conversations, E2EE send/receive, event stream - prd-02-agent-cli @qryptchat/cli (qrypt) — headless, NDJSON `listen --json` - prd-03-invite-automation @qryptchat/invite — mint/verify/redeem/quota/revoke, Go⇄JS compatible, AgentBBS issuer bridge - prd-04-mcp-skill @qryptchat/mcp — MCP server + skill.md for LLM agents Plus one ugig.net job posting per PRD under docs/prds/gigs/ (mapped to the ugig gig schema: title/skills_required/budget). Additive only — does not touch the interactive TUI, the phone/SMS path, or the web app. Co-Authored-By: Claude Opus 4.8 --- docs/prds/README.md | 67 +++++++++ docs/prds/gigs/qrypt-agent-cli-ugig.md | 44 ++++++ .../prds/gigs/qrypt-invite-automation-ugig.md | 50 +++++++ docs/prds/gigs/qrypt-mcp-skill-ugig.md | 48 +++++++ docs/prds/gigs/qrypt-sdk-core-ugig.md | 48 +++++++ docs/prds/prd-01-sdk-core.md | 131 ++++++++++++++++++ docs/prds/prd-02-agent-cli.md | 95 +++++++++++++ docs/prds/prd-03-invite-automation.md | 124 +++++++++++++++++ docs/prds/prd-04-mcp-skill.md | 102 ++++++++++++++ 9 files changed, 709 insertions(+) create mode 100644 docs/prds/README.md create mode 100644 docs/prds/gigs/qrypt-agent-cli-ugig.md create mode 100644 docs/prds/gigs/qrypt-invite-automation-ugig.md create mode 100644 docs/prds/gigs/qrypt-mcp-skill-ugig.md create mode 100644 docs/prds/gigs/qrypt-sdk-core-ugig.md create mode 100644 docs/prds/prd-01-sdk-core.md create mode 100644 docs/prds/prd-02-agent-cli.md create mode 100644 docs/prds/prd-03-invite-automation.md create mode 100644 docs/prds/prd-04-mcp-skill.md diff --git a/docs/prds/README.md b/docs/prds/README.md new file mode 100644 index 00000000..01824b2d --- /dev/null +++ b/docs/prds/README.md @@ -0,0 +1,67 @@ +# qrypt.chat — Agent & Automation Platform PRDs + +> Make qrypt.chat a **first-class citizen of the agent economy**: any AI agent, +> bot, or automation script can register, hold a quantum-resistant identity, and +> send/receive end-to-end-encrypted messages without ever touching a browser or +> a TUI. + +These PRDs describe the **machine-facing surface** of qrypt.chat. The web app, +the PWA, and the `blessed`-based interactive TUI (`bin/qryptchat-cli.js`) already +exist and stay untouched — this work is **additive** and sits *beside* them. + +The primary consumer is [AgentBBS](https://github.com/profullstack/agentbbs): +SSH-pubkey-authenticated members (humans and agents) get issued anonymous, +invite-only qrypt.chat identities, then talk to each other (and to humans) +E2EE — chat-in-the-BBS that's quantum-safe by default. + +## Background (reality, not greenfield) + +- **Stack:** Next.js + Supabase. **Already post-quantum** — ML-KEM-1024 + (FIPS 203) for message + call encryption; per-user keypair in + `user_public_keys`; **the on-device private key never leaves the client** and + IS the identity. Email/phone are discovery + abuse-control only, never a + server-side key-swap/recovery path. +- **Three login tiers (all live in prod):** + 1. **Phone + PIN over SMS** (Telnyx/Twilio OTP) — `/api/auth/send-sms`, + `/api/auth/verify-sms`. `account_type = 'verified'`. + 2. **CoinPay OIDC** ("Log in with CoinPay", open to anyone) — + `/api/auth/coinpay/login` + `/callback`. `account_type = 'verified'`. + 3. **Anonymous, invite-only** — Supabase native anonymous sign-in gated behind + an Ed25519-signed invite token (`qci1..`), + redeemed at `POST /api/auth/register-anon`. `account_type = 'anonymous'`, + `phone_number` NULL. Issuers in `invite_issuers`; one-time `jti` burned in + `registration_invites`. AgentBBS is a registered issuer (`id='agentbbs'`). +- **Realtime chat:** WebSocket server authenticates each connection via + `supabase.auth.getUser(token)` and speaks a typed JSON protocol + (`auth`, `load_conversations`, `join_conversation`, `send_message`, + `message_received`, `load_messages`, `typing_*`, `ping/pong` — see + `WEBSOCKET_CHAT_API.md`). A WS→POST/SSE migration is in flight + (`WEBSOCKET_TO_SSE_IMPLEMENTATION_GUIDE.md`); the SDK must abstract transport. + +## The suite (build order) + +| # | PRD | Component | npm name | Lang | Depends on | +|---|-----|-----------|----------|------|-----------| +| 1 | [prd-01-sdk-core.md](./prd-01-sdk-core.md) | **Core SDK** — auth, keys, conversations, E2EE send/receive, event stream | `@qryptchat/sdk` | TS/ESM | — | +| 2 | [prd-02-agent-cli.md](./prd-02-agent-cli.md) | **Agent CLI** — headless, scriptable `qrypt` with NDJSON I/O | `@qryptchat/cli` (`qrypt`) | TS/ESM | PRD-01 | +| 3 | [prd-03-invite-automation.md](./prd-03-invite-automation.md) | **Invite automation** — issue/redeem/quota/revoke, AgentBBS bridge | `@qryptchat/invite` | TS/ESM | PRD-01 | +| 4 | [prd-04-mcp-skill.md](./prd-04-mcp-skill.md) | **MCP server + `skill.md`** — drop-in tool surface for LLM agents | `@qryptchat/mcp` | TS/ESM | PRD-01, PRD-02 | + +Each PRD is independently shippable behind the SDK boundary and has a matching +ugig.net job posting under [`./gigs/`](./gigs/). + +## Cross-cutting principles + +1. **No private key ever leaves the device.** The SDK does all ML-KEM + encap/decap and message sealing locally. The server stores only public keys + and ciphertext. Recovery for anon accounts = seed phrase, never email reset. +2. **Headless-first.** Every capability works with zero TTY: credentials from + `--key-file`, env vars, or stdin; output is JSON/NDJSON on stdout; logs and + prompts go to stderr; exit codes are meaningful. +3. **Transport-agnostic.** Consumers call `sdk.messages.subscribe(...)`; whether + that's WebSocket today or POST/SSE tomorrow is an internal detail. +4. **Additive & non-breaking.** Reuse existing endpoints and the existing WS + auth (`supabase.auth.getUser(token)`). Do not modify the interactive TUI, the + phone/SMS path, or the web app. +5. **Spec-tested.** Every public method/command ships with vitest coverage and a + documented contract; cross-language invite compatibility (Go ⇄ JS) must hold. diff --git a/docs/prds/gigs/qrypt-agent-cli-ugig.md b/docs/prds/gigs/qrypt-agent-cli-ugig.md new file mode 100644 index 00000000..589532a5 --- /dev/null +++ b/docs/prds/gigs/qrypt-agent-cli-ugig.md @@ -0,0 +1,44 @@ +# Build the qrypt.chat Headless Agent CLI (`qrypt`) + +> ugig.net gig posting — implements [PRD-02](../prd-02-agent-cli.md) + +- **Title:** Build the qrypt.chat Headless Agent CLI (`qrypt`) +- **Skills required:** `typescript`, `nodejs`, `cli-design`, `ndjson`, `unix-pipelines`, `vitest` +- **Budget type:** fixed +- **Budget (USD):** 1,200 – 2,200 +- **Repo:** https://github.com/profullstack/qryptchat-web · branch `feat/agent-cli` +- **Spec:** `docs/prds/prd-02-agent-cli.md` +- **Depends on:** `@qryptchat/sdk` (PRD-01) — build against its public API + +## What we need + +A non-interactive `qrypt` CLI built on `@qryptchat/sdk`, for bots, cron jobs, +shell pipelines, and AgentBBS plugins. The marquee feature is +`qrypt listen --json`: a clean **NDJSON event stream** (one JSON object per line +on stdout) of incoming E2EE messages, pipeable into `jq` or any language. This is +the interface that unblocks agents talking on the BBS. + +There is an interactive `blessed` TUI today (`bin/qryptchat-cli.js`) — it stays. +This is a separate, headless, scriptable client. + +## Scope + +- Commands: `register --invite`, `login --phone|--coinpay`, `whoami`, `keygen`, + `seed --export/--import`, `conversations`, `send` (argv or stdin via `-`), + `read`, `listen --json`, and `invite …` (delegates to PRD-03). +- Headless credential resolution: `--key-file` → env (`QRYPT_KEY_FILE`, + `QRYPT_SESSION`) → stdin → error; **never prompt** unless TTY and not `--json`. +- Strict stream discipline: **data on stdout, logs/prompts on stderr**, machine + output never decorated under `--json`/non-TTY, immediate flush + transparent + reconnect for `listen`. +- Meaningful exit codes (0 ok, 2 usage, 3 auth, 4 invite, 5 network). +- Published as `@qryptchat/cli`, bin `qrypt`, `npx`-runnable. + +## Acceptance criteria + +- A shell one-liner registers via invite, sends a message, and + `qrypt listen --json | jq` shows incoming messages live — no TTY, no browser. +- CLI integration tests spawn the binary and assert stdout/stderr/exit-code + separation; the NDJSON event schema is snapshot-tested. +- Scripted e2e: two identities exchange an E2EE message, a third process observes + it as one NDJSON line. Green CI + README. diff --git a/docs/prds/gigs/qrypt-invite-automation-ugig.md b/docs/prds/gigs/qrypt-invite-automation-ugig.md new file mode 100644 index 00000000..2ecda45c --- /dev/null +++ b/docs/prds/gigs/qrypt-invite-automation-ugig.md @@ -0,0 +1,50 @@ +# Build qrypt.chat Invite Automation (`@qryptchat/invite`) + +> ugig.net gig posting — implements [PRD-03](../prd-03-invite-automation.md) + +- **Title:** Build qrypt.chat Invite Issuance & Redemption Automation (`@qryptchat/invite`) +- **Skills required:** `typescript`, `nodejs`, `cryptography`, `ed25519`, `go-interop`, `supabase`, `vitest` +- **Budget type:** fixed +- **Budget (USD):** 1,400 – 2,400 +- **Repo:** https://github.com/profullstack/qryptchat-web · branch `feat/invite-automation` +- **Spec:** `docs/prds/prd-03-invite-automation.md` +- **Depends on:** `@qryptchat/sdk` (PRD-01) + +## What we need + +A library + CLI surface for the **anonymous, invite-only tier**: mint, verify, +redeem, quota, and revoke Ed25519-signed invite tokens so trusted issuers +(chiefly AgentBBS) can provision anonymous, PII-free qrypt.chat identities +programmatically. + +The server side is already live (`invite_issuers`, `registration_invites`, +`POST /api/auth/register-anon`, a registered `agentbbs` issuer) and AgentBBS +already mints tokens in **Go** that verify against the deployed Node +`src/lib/invites/verify.js`. **Cross-language byte-compatibility is a hard +requirement** — a JS-minted token must pass the Go verifier and vice-versa. + +## Scope + +- Canonical JS minter/verifier for `qci1..` + (payload `{jti,iss,tier:"anonymous",iat,exp,uses}`, Ed25519, single fixed alg — + no JWT, no alg negotiation). Standard-b64 seed; url-safe-no-pad segments. +- `mint`, `verify` (distinct reasons: bad_signature / expired / used / unknown + issuer), `redeem` (via SDK `auth.withInvite`), per-issuer quotas (default 5), + `revoke`, and `IssuerKey` keygen that prints the pubkey to register. +- `issueForMember()` bridge helper (link/QR) mirroring `agentbbs qrypt-invite`. +- CLI surface exposed through `qrypt invite …` and standalone. + +## Acceptance criteria + +- One command hands out a working **one-time** invite that provisions an + anonymous identity; **double-spend is impossible** (second redeem → `invite_used`). +- Committed cross-language test vectors: a Go-minted token verifies in JS, and a + JS-minted token passes the deployed `verify.js`. Vitest covers + mint↔verify, tamper, expiry, reuse, over-quota. +- Server stores only hashed token + redeemed flag (no invite graph). Issuer seeds + never logged; rotation documented. Green CI + README. + +## Notes + +- Coordinate with the SECURITY DEFINER RPC hardening for the redeem race + (insert-then-compensate today). Define one canonical quota store (Go vs JS). diff --git a/docs/prds/gigs/qrypt-mcp-skill-ugig.md b/docs/prds/gigs/qrypt-mcp-skill-ugig.md new file mode 100644 index 00000000..e8c08ff1 --- /dev/null +++ b/docs/prds/gigs/qrypt-mcp-skill-ugig.md @@ -0,0 +1,48 @@ +# Build the qrypt.chat MCP Server + `skill.md` (`@qryptchat/mcp`) + +> ugig.net gig posting — implements [PRD-04](../prd-04-mcp-skill.md) + +- **Title:** Build the qrypt.chat MCP Server + Agent `skill.md` (`@qryptchat/mcp`) +- **Skills required:** `typescript`, `nodejs`, `mcp`, `model-context-protocol`, `llm-agents`, `technical-writing`, `vitest` +- **Budget type:** fixed +- **Budget (USD):** 1,300 – 2,300 +- **Repo:** https://github.com/profullstack/qryptchat-web · branch `feat/mcp-skill` +- **Spec:** `docs/prds/prd-04-mcp-skill.md` +- **Depends on:** `@qryptchat/sdk` (PRD-01), `@qryptchat/cli` (PRD-02) + +## What we need + +An MCP (Model Context Protocol) server plus a published `skill.md` that make +qrypt.chat a drop-in tool any LLM agent (Claude, etc.) can use to hold +quantum-resistant E2EE conversations — register, send, read, and be *notified* of +new messages — without the agent author writing qrypt-specific code. This is what +makes qrypt.chat a first-class agent-economy platform (cf. Moltbook / The Colony +`skill.md` convention in `awesome-agent-platforms.md`). + +The MCP server is strictly a thin wrapper over `@qryptchat/sdk` — no new +auth/crypto/transport. Each server instance is bound to one identity/session; the +agent never sees raw keys. + +## Scope + +- MCP stdio server (optional HTTP/SSE) built on the official MCP SDK, bound to a + key file / env session via the SDK storage adapter; documented Claude Code / + Desktop config snippet; `npx @qryptchat/mcp` entrypoint. +- Tools: `qrypt_register`, `qrypt_whoami`, `qrypt_list_conversations`, + `qrypt_create_conversation`, `qrypt_send`, `qrypt_read`, `qrypt_safety_number` + — each input-validated, returning structured JSON with mapped error types. +- New-message **notifications / resource subscription** backed by SDK + `messages.subscribe()` (fall back to a `qrypt_poll` tool where a client lacks + subscriptions). Never expose seeds/keys in tool output (except a one-time + `seed_out` file write on register). +- `skill.md` served at a stable URL + committed: what qrypt.chat is, the identity + model, how to onboard via an AgentBBS invite, full tool/command reference with + copy-paste examples, safety etiquette, and links. CI doc-lint keeps it in sync. + +## Acceptance criteria + +- An LLM agent configured with `@qryptchat/mcp`, guided **only** by `skill.md`, + registers via an AgentBBS invite and exchanges E2EE messages. +- Vitest + MCP client harness asserts tool schemas and error mapping; e2e covers + register → send → receive-via-subscription; a cold-start doc test follows + `skill.md` verbatim. Green CI + README. diff --git a/docs/prds/gigs/qrypt-sdk-core-ugig.md b/docs/prds/gigs/qrypt-sdk-core-ugig.md new file mode 100644 index 00000000..c65f9bbc --- /dev/null +++ b/docs/prds/gigs/qrypt-sdk-core-ugig.md @@ -0,0 +1,48 @@ +# Build the qrypt.chat Core Agent SDK (`@qryptchat/sdk`) + +> ugig.net gig posting — implements [PRD-01](../prd-01-sdk-core.md) + +- **Title:** Build the qrypt.chat Core Agent SDK (`@qryptchat/sdk`) +- **Skills required:** `typescript`, `nodejs`, `cryptography`, `ml-kem`, `supabase`, `websockets`, `sdk-design`, `vitest` +- **Budget type:** fixed +- **Budget (USD):** 1,800 – 3,000 +- **Repo:** https://github.com/profullstack/qryptchat-web · branch `feat/sdk-core` +- **Spec:** `docs/prds/prd-01-sdk-core.md` + +## What we need + +A TypeScript-first, headless SDK that lets any Node program be a full qrypt.chat +client — authenticate, hold a **client-side ML-KEM-1024** identity, and send / +receive **end-to-end-encrypted** messages with no browser and no TUI. This is the +foundation the CLI, invite, and MCP packages all build on, so API quality and +test coverage matter more than feature breadth. + +qrypt.chat is a real, deployed Next.js + Supabase app that is already +post-quantum (ML-KEM-1024). **You are extracting and packaging existing +auth/crypto/transport logic into one tested library — not reimplementing +primitives.** The on-device private key must never leave the process. + +## Scope + +1. `QryptClient` with an `auth` namespace covering all three tiers: phone+PIN + SMS, CoinPay OIDC (headless device/redirect capture), and anonymous invite + (`signInAnonymously` → `POST /api/auth/register-anon`). +2. Client-side ML-KEM-1024 keypair generation/storage, public-key registration, + fingerprint + safety-number, seed-phrase export/import. Reuse the web client's + message format byte-for-byte (`ENCRYPTION.md`) — verify interop both ways. +3. Conversations (list/create/join) + paginated history. +4. Transport-agnostic `messages.subscribe()` over the current WebSocket protocol + (`auth`→`join_conversation`→`message_received`), with auto-reconnect and token + refresh, structured so the planned WS→POST/SSE migration is a drop-in swap. +5. Pluggable storage adapters (`file`, `env`, `memory`); typed errors with stable + codes; ESM-only, Node ≥ 20, tree-shakeable. + +## Acceptance criteria + +- `npm i @qryptchat/sdk`, authenticate on all three tiers, send + receive an + E2EE message, and stream live messages — headless, key never leaving the + process. +- Vitest coverage on every public method; crypto round-trip + cross-client format + test; a Supabase-test-project integration test for the anon-invite e2e. +- No changes to server endpoints, DB schema, the existing WS auth, or the + interactive TUI. PR with green CI, README, and typed public API. diff --git a/docs/prds/prd-01-sdk-core.md b/docs/prds/prd-01-sdk-core.md new file mode 100644 index 00000000..2fb2487a --- /dev/null +++ b/docs/prds/prd-01-sdk-core.md @@ -0,0 +1,131 @@ +# PRD-01: `@qryptchat/sdk` — Core Agent SDK + +**Status:** Draft · **Owner:** TBD · **Depends on:** none · **Consumers:** +PRD-02 (CLI), PRD-03 (invite), PRD-04 (MCP) + +## 1. Overview + +A programmatic, TypeScript-first SDK that lets any Node.js (and, where feasible, +browser/Deno) program be a full qrypt.chat client: authenticate on any of the +three tiers, manage a post-quantum keypair entirely client-side, list and create +conversations, send and receive **end-to-end-encrypted** messages, and subscribe +to a live message stream — with **no browser, no TUI, no DOM**. + +This is the foundation every other PRD builds on. Today there is an interactive +`blessed`+`ws` CLI (`bin/qryptchat-cli.js`) and a half-built headless agent CLI; +both reimplement auth/crypto/transport ad hoc. This SDK extracts that into one +tested, reusable library. + +## 2. Goals + +- One import to go from credentials → authenticated, encrypted session. +- **Client-side ML-KEM-1024** key generation, storage, and message sealing; the + private key never touches the network. +- Support all three auth tiers behind a single `auth` namespace. +- Transport-agnostic realtime: a `subscribe()` API that works over the current + WebSocket protocol and survives the planned WS→POST/SSE migration unchanged. +- Runs headless: pluggable key/session storage (file, env, in-memory, custom). +- Fully typed, ESM, tree-shakeable, vitest-covered. + +## 3. Non-Goals + +- No UI of any kind (CLI is PRD-02, MCP is PRD-04). +- No changes to server endpoints, DB schema, or the existing WS auth. +- No voice/video calls (covered by the separate call PRD / ML-KEM call work). +- No server-side key escrow or email-based recovery — out of scope by design. + +## 4. Public API (target shape) + +```ts +import { QryptClient } from '@qryptchat/sdk'; + +const client = new QryptClient({ + baseUrl: 'https://qrypt.chat', + storage: fileStorage('~/.config/qrypt/agent.json'), // pluggable +}); + +// --- Auth (pick one tier) --- +await client.auth.withPhone({ phone, pin }); // SMS-verified tier +await client.auth.withCoinPay({ /* OIDC device/redirect flow */ }); +await client.auth.withInvite({ token, seedPhrase? }); // anonymous tier +await client.auth.restore(); // resume saved session +const me = await client.whoami(); // { userId, username, accountType, fingerprint } + +// --- Identity / keys (client-side ML-KEM-1024) --- +const ident = await client.identity.ensure(); // generate-or-load keypair +ident.publicKey; ident.fingerprint; ident.safetyNumber(peerPubKey); +await client.identity.exportSeed(); await client.identity.importSeed(seed); + +// --- Conversations --- +const convos = await client.conversations.list(); +const convo = await client.conversations.create({ type: 'direct'|'group', participants }); +await client.conversations.join(convoId); + +// --- Messages (E2EE) --- +await client.messages.send(convoId, { text }); // seals locally before send +const page = await client.messages.history(convoId, { before, limit }); + +// --- Realtime (transport-agnostic) --- +const sub = client.messages.subscribe({ conversationId? }, (evt) => { + // evt: { type: 'message', conversationId, from, plaintext, ts } | 'typing' | 'presence' +}); +sub.close(); +``` + +## 5. Functional requirements + +1. **Auth namespace** + - `withPhone` → `/api/auth/send-sms` + `/api/auth/verify-sms`, yields a + Supabase session. + - `withCoinPay` → drive the OIDC flow; headless variant must support a + device/redirect-capture mode usable without a browser tab (return the + authorize URL + poll/callback capture). `account_type='verified'`. + - `withInvite` → Supabase `signInAnonymously` then + `POST /api/auth/register-anon` with the `qci1.…` token (Bearer the anon + session). Handles `invite_used` / expiry / bad-signature errors distinctly. + - All tiers converge on a stored session usable by the WS auth + (`supabase.auth.getUser(token)`), with token refresh on long-lived sessions. +2. **Identity / keys** — generate ML-KEM-1024 keypair locally, persist via the + storage adapter, register **public** key with `user_public_keys`, expose + fingerprint + safety-number comparison. Seed-phrase export/import for anon + recovery. +3. **E2EE messaging** — encapsulate to each recipient's public key, derive + per-message keys via KDF, seal plaintext; on receive, decapsulate + open. Must + interoperate byte-for-byte with the existing web client's message format + (verify against `ENCRYPTION.md`). Never log plaintext or keys. +4. **Conversations & history** — list/create/join; paginated history + (`load_messages` / `load_more_messages`). +5. **Realtime** — `subscribe()` wraps the WS protocol (`auth` → + `join_conversation` → `message_received`) behind one callback; auto-reconnect + with backoff; re-auth on token refresh; pluggable transport so the POST/SSE + migration is a drop-in swap. + +## 6. Non-functional + +- TS, ESM-only, Node ≥ 20, zero hard browser deps; crypto via the same lib the + web app uses (no new primitive). Tree-shakeable, no top-level side effects. +- Storage adapter interface: `{ get(k), set(k,v), delete(k) }` — ships + `fileStorage`, `envStorage`, `memoryStorage`. +- Errors are typed (`QryptAuthError`, `QryptInviteError`, `QryptCryptoError`, + `QryptTransportError`) with stable `.code`s. +- Secrets are zeroized where possible; never serialized to logs. + +## 7. Testing & acceptance + +- Vitest unit coverage for every public method; crypto round-trip tests; a + cross-client test that a message sealed by the SDK decrypts in the web client + format and vice-versa. +- Integration test against a Supabase test project: full anon-invite e2e + (signInAnonymously → register-anon → send → receive), and a phone-tier path. +- **Done when:** an external script can `npm i @qryptchat/sdk`, authenticate on + all three tiers, send and receive an E2EE message, and stream live messages — + headless, with the private key never leaving the process. + +## 8. Risks / open questions + +- Exact reuse of the web client's ML-KEM message format — must extract, not + reimplement, to avoid format drift (`ENCRYPTION.md`, + `CROSS_DEVICE_KEY_SYNC.md`). +- CoinPay OIDC without a browser: confirm device-code or loopback-capture is + acceptable to the provider. +- WS→POST/SSE migration timing — ship transport interface now so neither break. diff --git a/docs/prds/prd-02-agent-cli.md b/docs/prds/prd-02-agent-cli.md new file mode 100644 index 00000000..95ac630f --- /dev/null +++ b/docs/prds/prd-02-agent-cli.md @@ -0,0 +1,95 @@ +# PRD-02: `@qryptchat/cli` — Headless Agent CLI (`qrypt`) + +**Status:** Draft · **Owner:** TBD · **Depends on:** PRD-01 (`@qryptchat/sdk`) + +## 1. Overview + +A non-interactive command-line client for qrypt.chat, built on `@qryptchat/sdk`, +designed for bots, cron jobs, shell pipelines, and AgentBBS plugins. Everything +the interactive TUI (`bin/qryptchat-cli.js`) does for a human, `qrypt` does for a +program: register, identify, list conversations, send, read, and **stream live +messages as NDJSON** — fully scriptable, no TTY required. + +This is the interface that unblocks agents on the BBS: `qrypt listen --json` +emits one JSON object per line to stdout, `qrypt send` accepts a message on the +command line or stdin, and credentials come from a key file or env. Pipeable, +language-agnostic, no polling. + +## 2. Goals + +- A clean, composable command surface that maps 1:1 onto SDK capabilities. +- **NDJSON streaming** (`listen --json`): one event per line on stdout, parseable + by any language, suitable for `| jq`, `| while read`, or a long-running bot. +- Zero interactivity by default: creds from `--key-file` / env / stdin; prompts + and logs to **stderr**; data to **stdout**; meaningful exit codes. +- Drop-in for AgentBBS: a single static-ish binary/entrypoint a pod can shell out + to. + +## 3. Non-Goals + +- No replacement of the interactive `blessed` TUI (it stays). +- No business logic outside the SDK — the CLI is a thin, well-tested shell. +- No bundled MCP server (that's PRD-04). + +## 4. Command surface + +``` +qrypt register --invite [--seed-out ] [--key-file ] # anon tier +qrypt login --phone --pin [--key-file ] # SMS tier +qrypt login --coinpay [--print-url | --callback-port

] # OIDC tier +qrypt whoami # { userId, username, accountType, fingerprint } +qrypt keygen [--seed-out ] # create/rotate local ML-KEM keypair +qrypt seed --export | --import + +qrypt conversations [list|create] # create: --type direct|group --to +qrypt send "text" # or: echo "text" | qrypt send - +qrypt read [--limit N] [--before ] [--json] +qrypt listen [--conversation ] --json # NDJSON event stream on stdout +qrypt invite # delegated to @qryptchat/invite (PRD-03) +``` + +Global flags: `--base-url`, `--key-file`, `--json` (force machine output), +`--quiet`, `--verbose` (→ stderr), `--version`, `--help`. + +## 5. Functional requirements + +1. **Credentials, headless.** Resolution order: `--key-file` → `QRYPT_KEY_FILE` + / `QRYPT_SESSION` env → stdin → error. Never prompt unless attached to a TTY + *and* not `--json`. Sessions persist to the SDK storage adapter. +2. **`register`** drives the anon-invite flow (PRD-01 `auth.withInvite`); writes + the recovery seed to `--seed-out` (or stdout once, with a stderr warning) and + the session to the key file. +3. **`send`** seals via the SDK and sends; text from argv or, when the last arg + is `-`, from stdin (so `cat msg | qrypt send -` works). +4. **`read`** prints history; `--json` emits a JSON array of decrypted messages. +5. **`listen --json`** is the marquee feature: subscribe via the SDK and emit one + compact JSON object per line per event + (`{"type":"message","conversationId":...,"from":...,"text":...,"ts":...}`), + flushing immediately, reconnecting transparently, surviving token refresh. + Without `--json`, render a human-readable tail. +6. **Exit codes:** `0` ok · `1` generic · `2` usage · `3` auth/credential · + `4` invite (used/expired/bad-sig) · `5` network/transport. + +## 6. Non-functional + +- TS/ESM, published as `@qryptchat/cli`, bin name `qrypt`; runnable via `npx`. +- Stdout is **data only** — never decorate it when `--json`/non-TTY so pipelines + stay clean. All human chatter to stderr. +- Secrets never echoed; `--key-file` perms checked (warn if world-readable). +- Startup < 300 ms to first output for simple commands. + +## 7. Testing & acceptance + +- Vitest + CLI integration tests (spawn the binary, assert stdout/stderr/exit + code separation). Snapshot the NDJSON event schema. +- A scripted e2e: two `qrypt` identities (one anon-invite, one phone) exchange an + E2EE message; a third process `qrypt listen --json` observes it as one NDJSON + line. +- **Done when:** a shell one-liner can register via invite, send a message, and + `qrypt listen --json | jq` shows incoming messages live — no TTY, no browser. + +## 8. Risks / open questions + +- CoinPay login from a headless box → `--print-url` (operator opens it) vs + `--callback-port` loopback capture; pick what the SDK supports. +- Buffering: ensure line-buffered, unbuffered-friendly stdout for `listen`. diff --git a/docs/prds/prd-03-invite-automation.md b/docs/prds/prd-03-invite-automation.md new file mode 100644 index 00000000..7357a66e --- /dev/null +++ b/docs/prds/prd-03-invite-automation.md @@ -0,0 +1,124 @@ +# PRD-03: `@qryptchat/invite` — Invite Issuance & Redemption Automation + +**Status:** Draft · **Owner:** TBD · **Depends on:** PRD-01 (`@qryptchat/sdk`) + +## 1. Overview + +A library + CLI surface for the **anonymous, invite-only tier**: minting +Ed25519-signed invite tokens, redeeming them, enforcing per-issuer quotas, and +revoking them — so trusted systems (chiefly [AgentBBS](https://github.com/profullstack/agentbbs)) +can provision anonymous qrypt.chat identities for their members +programmatically. + +The server side already exists and is live: `invite_issuers` (trusted issuer +pubkeys), `registration_invites` (burns one-time `jti` to stop double-spend), +`POST /api/auth/register-anon`, and a registered `agentbbs` issuer. AgentBBS +already mints tokens in Go (`internal/qryptinvite`) that verify against the Node +`verify.js` lib. **This PRD makes that capability a clean, reusable npm package** +so any authorized issuer — not just the Go BBS — can participate, and so the JS +ecosystem (CLI, MCP, web admin) shares one tested implementation. + +## 2. Goals + +- A canonical **JS minter/verifier** for the `qci1..` + token format, byte-compatible with the existing Go implementation and + `src/lib/invites/verify.js`. +- Programmatic **issue → deliver → redeem → revoke** lifecycle with per-issuer + quotas and expiry/use-count enforcement. +- An AgentBBS-style bridge so any trusted issuer can hand a member a capped, + expiring invite (and optionally a deep link / QR). +- Privacy preserved: server stores only hashed token + redeemed flag, never a + social/invite graph. + +## 3. Non-Goals + +- No new token format or crypto agility — single fixed alg (Ed25519), no JWT, no + alg negotiation (that's the whole anti-alg-confusion point). +- No change to the anonymity model: invites gate signup, they do **not** link the + resulting account to the inviter by default. +- No phone/CoinPay tier logic (those are open and need no invite). + +## 4. Token & API contract (existing, to be honored) + +``` +token = "qci1." + b64url(payload) + "." + b64url(sig) +payload = { jti, iss, tier: "anonymous", iat, exp, uses } +sig = Ed25519(issuerPrivKey, payloadBytes) +verify : look up iss in invite_issuers → check sig, exp, uses; burn jti in + registration_invites at redeem (insert-then-compensate today; a + SECURITY DEFINER RPC is the hardening follow-up). +redeem : POST /api/auth/register-anon (Bearer = anon Supabase session) +``` + +> Compatibility is a hard requirement: a token minted by this lib must verify +> with the deployed `verify.js`, and a Go-minted token must verify with this lib. +> The seed uses **standard** base64; token segments use **url-safe, no-pad**. + +## 5. Public API (target shape) + +```ts +import { IssuerKey, mint, verify, redeem } from '@qryptchat/invite'; + +const issuer = IssuerKey.fromSeed(process.env.QRYPT_ISSUER_SEED); // standard-b64 seed +const token = mint(issuer, { iss: 'agentbbs', uses: 1, ttl: '7d' }); + +const result = verify(token, { issuers }); // { ok, payload } | { ok:false, reason } +await redeem({ token, baseUrl, session }); // → anon account (delegates to SDK) + +// quota helpers (server-backed) +await quota.remaining({ issuer: 'agentbbs', subject }); // default 5 +await revoke({ jti, issuer }); +``` + +CLI (exposed through `qrypt invite …` from PRD-02, and standalone): + +``` +qrypt invite issue --issuer [--uses N] [--ttl 7d] [--qr] [--link] +qrypt invite verify +qrypt invite redeem # provisions an anon account here +qrypt invite revoke --jti +qrypt issuer keygen [--seed-out ] # prints pubkey to register in invite_issuers +``` + +## 6. Functional requirements + +1. **Mint** — produce spec-exact tokens; refuse to mint past quota; embed + `jti` (random, unique), `iat`, `exp`, `uses`. +2. **Verify** — resolve `iss` against trusted issuer pubkeys; reject bad + signature, expired, exhausted-uses, or unknown issuer with distinct reasons. +3. **Redeem** — call the SDK's `auth.withInvite` path; surface `invite_used`, + expiry, and bad-signature as typed errors; idempotent on retry. +4. **Quotas** — per-issuer (and optionally per-subject) caps; default 5, + configurable; backed by the existing quota table semantics. +5. **Revoke** — burn a `jti` before redemption; reflected in verify. +6. **Issuer key mgmt** — `keygen` produces a seed + the pubkey string to insert + into `invite_issuers`; never logs the seed except to the requested file/once. +7. **Bridge helper** — a thin `issueForMember({ issuer, memberRef })` that mints + a capped/expiring token + returns a redeem link/QR, mirroring AgentBBS's + `agentbbs qrypt-invite `. + +## 7. Non-functional + +- TS/ESM. **Cross-language test vectors** committed (a Go-minted token + its + expected verify result) so drift is caught in CI. +- Issuer seeds treated as top secret: env/file only, never in logs, rotation + documented (re-keygen → update secret + the `invite_issuers` row). +- Server stores hashed token + redeemed flag only — no invite graph. + +## 8. Testing & acceptance + +- Vitest: mint↔verify round-trip; tampered payload → `bad_signature`; expired → + `expired`; reused `jti` → `invite_used`; over-quota mint refused. +- Cross-language: load a committed Go-minted token, assert `verify` passes; + assert a JS-minted token passes the deployed `verify.js`. +- e2e: `qrypt invite issue` → `qrypt register --invite ` → new anon + account; second redeem of same token → `invite_used`. +- **Done when:** an authorized issuer can, in one command, hand out a working + one-time invite that provisions an anonymous, PII-free qrypt.chat identity, and + double-spend is impossible. + +## 9. Risks / open questions + +- Insert-then-compensate redeem has a race window; the SECURITY DEFINER RPC + hardening should land with or before this. +- Quota source of truth (Go BBS table vs JS) — define one canonical store. diff --git a/docs/prds/prd-04-mcp-skill.md b/docs/prds/prd-04-mcp-skill.md new file mode 100644 index 00000000..b0a77981 --- /dev/null +++ b/docs/prds/prd-04-mcp-skill.md @@ -0,0 +1,102 @@ +# PRD-04: `@qryptchat/mcp` — MCP Server + `skill.md` for LLM Agents + +**Status:** Draft · **Owner:** TBD · **Depends on:** PRD-01 (`@qryptchat/sdk`), +PRD-02 (`@qryptchat/cli`) + +## 1. Overview + +A **Model Context Protocol (MCP) server** plus a published **`skill.md`** that +turn qrypt.chat into a drop-in tool any LLM agent (Claude, etc.) can use to hold +quantum-resistant E2EE conversations — register, send, read, and receive +messages — without the agent author writing any qrypt-specific code. + +The MCP server wraps `@qryptchat/sdk`; `skill.md` is the human/agent-readable +onboarding doc (the same convention used by Moltbook, The Colony, argue.fun, +etc. — see `awesome-agent-platforms.md`). Together they make qrypt.chat a +**first-class agent-economy platform**: zero-friction onboarding, a documented +tool surface, and a path for BBS agents to chat securely. + +## 2. Goals + +- An MCP server exposing qrypt.chat as a small set of well-described **tools**. +- A `skill.md` an agent can fetch and follow to self-onboard + (register-via-invite → send/read), served at a stable URL. +- A long-lived **event/notification surface** so an agent can be *told* about new + messages (MCP notifications / resource subscription) rather than polling. +- Safe-by-default: the agent never sees raw keys; the server holds the session, + the SDK does the crypto. + +## 3. Non-Goals + +- No new protocol or transport — MCP over stdio (and optionally HTTP/SSE) using + the standard MCP SDK. +- No re-implementation of auth/crypto — strictly an SDK wrapper. +- Not a public, unauthenticated relay: each MCP server instance is bound to one + identity/session. + +## 4. Tool surface (MCP tools) + +| Tool | Input | Output | +|------|-------|--------| +| `qrypt_register` | `{ invite_token, seed_out? }` | `{ user_id, fingerprint, account_type }` | +| `qrypt_whoami` | `{}` | identity summary | +| `qrypt_list_conversations` | `{}` | conversations[] | +| `qrypt_create_conversation` | `{ type, participants[] }` | `{ conversation_id }` | +| `qrypt_send` | `{ conversation_id, text }` | `{ message_id, ts }` | +| `qrypt_read` | `{ conversation_id, limit?, before? }` | messages[] (decrypted) | +| `qrypt_safety_number` | `{ peer }` | `{ safety_number }` | + +**Notifications / resources:** expose incoming messages as an MCP resource +subscription (or server-initiated notifications) backed by the SDK's +`messages.subscribe()` — so the agent is woken on a new message, not polling. + +## 5. `skill.md` requirements + +Served at a stable URL (e.g. `https://qrypt.chat/skill.md`) and committed in the +repo. Must contain, in agent-friendly prose: + +1. What qrypt.chat is (quantum-resistant E2EE chat) and the **identity model** + (your on-device keypair IS your identity; no PII for anon tier). +2. How to onboard: obtaining an invite (from AgentBBS or another issuer), then + `qrypt register --invite ` **or** the `qrypt_register` MCP tool. +3. The full command/tool reference with copy-pasteable examples. +4. Etiquette + safety: verify safety numbers, never paste your seed, rate norms. +5. Links: repo, `@qryptchat/cli`, `@qryptchat/mcp`, this PRD set. + +## 6. Functional requirements + +1. **Server lifecycle** — `npx @qryptchat/mcp` starts an MCP stdio server bound + to a key file / env session (reuses SDK storage). Optional HTTP/SSE mode. +2. **Tools** — each tool validates input, calls the SDK, returns structured + JSON, and maps SDK typed errors to clear MCP errors (auth, invite, transport). +3. **Subscriptions** — register an MCP resource or emit notifications on new + messages; clean teardown on disconnect; reconnect with the SDK. +4. **No key exposure** — tools never return private key material or seeds except + `qrypt_register`'s one-time `seed_out` write (to a file path, not the chat). +5. **Discovery** — `skill.md` + a short README so an agent can go from "never + heard of qrypt.chat" to "sent an E2EE message" by reading one doc. + +## 7. Non-functional + +- TS/ESM, built on the official MCP SDK; works as a Claude Code / Claude Desktop + MCP server out of the box (document the config snippet). +- Stateless beyond the bound session; no secrets in tool outputs or logs. +- `skill.md` kept in sync with the CLI/SDK in CI (a doc-lint that the documented + commands exist). + +## 8. Testing & acceptance + +- Vitest + an MCP client harness: list tools, call `qrypt_whoami`, `qrypt_send`, + `qrypt_read`; assert schemas and error mapping. +- e2e: an MCP client registers via invite, sends a message, and receives a new + inbound message via subscription/notification. +- A "cold-start" doc test: following `skill.md` verbatim onboards a fresh agent. +- **Done when:** an LLM agent configured with `@qryptchat/mcp` can, guided only + by `skill.md`, register via an AgentBBS invite and exchange E2EE messages. + +## 9. Risks / open questions + +- MCP subscription/notification ergonomics across clients — fall back to a + `qrypt_poll` tool if a client lacks subscriptions. +- Where to host `skill.md` (Next.js public route vs static) and how to keep it + authoritative vs the npm READMEs.