@poppykit/client 0.0.0-stage → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/API.md +25 -0
  2. package/AUTH.md +51 -0
  3. package/CHANGELOG.md +18 -0
  4. package/CONTRIBUTING.md +38 -0
  5. package/DELIVERY.md +94 -0
  6. package/INTEGRATIONS.md +87 -0
  7. package/LICENSE +21 -0
  8. package/PROVIDER.md +99 -0
  9. package/README.md +49 -2
  10. package/RELEASING.md +91 -0
  11. package/SECURITY.md +13 -0
  12. package/STORAGE.md +96 -0
  13. package/dist/auth/challenge.d.ts +19 -0
  14. package/dist/auth/challenge.js +68 -0
  15. package/dist/auth/client-auth.d.ts +297 -0
  16. package/dist/auth/client-auth.js +1583 -0
  17. package/dist/auth/ownership.d.ts +9 -0
  18. package/dist/auth/ownership.js +23 -0
  19. package/dist/auth/sqlite-store.d.ts +14 -0
  20. package/dist/auth/sqlite-store.js +123 -0
  21. package/dist/auth/store.d.ts +19 -0
  22. package/dist/auth/store.js +1 -0
  23. package/dist/conversation-store.d.ts +15 -0
  24. package/dist/conversation-store.js +1 -0
  25. package/dist/conversation.d.ts +118 -0
  26. package/dist/conversation.js +404 -0
  27. package/dist/index.d.ts +18 -0
  28. package/dist/index.js +7 -0
  29. package/dist/integrations.d.ts +30 -0
  30. package/dist/integrations.js +58 -0
  31. package/dist/memory.d.ts +15 -0
  32. package/dist/memory.js +32 -0
  33. package/dist/network/discovery.d.ts +101 -0
  34. package/dist/network/discovery.js +276 -0
  35. package/dist/network/transport.d.ts +31 -0
  36. package/dist/network/transport.js +305 -0
  37. package/dist/protocol/sse.d.ts +15 -0
  38. package/dist/protocol/sse.js +90 -0
  39. package/dist/protocol/wire.d.ts +128 -0
  40. package/dist/protocol/wire.js +278 -0
  41. package/dist/provider-sqlite.d.ts +8 -0
  42. package/dist/provider-sqlite.js +30 -0
  43. package/dist/provider.d.ts +121 -0
  44. package/dist/provider.js +342 -0
  45. package/dist/sqlite-init.d.ts +2 -0
  46. package/dist/sqlite-init.js +17 -0
  47. package/dist/sqlite.d.ts +20 -0
  48. package/dist/sqlite.js +80 -0
  49. package/examples/quickstart.mjs +19 -0
  50. package/package.json +72 -4
package/API.md ADDED
@@ -0,0 +1,25 @@
1
+ # API reference
2
+
3
+ Every export has an accompanying `dist/*.d.ts` declaration; use editor completion
4
+ for complete option/result types. This map describes the supported integration seams.
5
+
6
+ | Entry point | Main exports | Guide |
7
+ | --- | --- | --- |
8
+ | `@poppykit/client` | `createMetadataResolver`, `createNodeTransport` | [README](README.md), [delivery](DELIVERY.md) |
9
+ | `@poppykit/client` | `createClientAuth`, `AuthFailure`, authentication/identity/input types | [authentication](AUTH.md) |
10
+ | `@poppykit/client` | `createConversationClient`, `ConversationError`, `parseEventStream` | [delivery](DELIVERY.md) |
11
+ | `@poppykit/client` | `ApiTarget`, `AgentBrowserController`, `FeedbackReport` types | [integrations](INTEGRATIONS.md) |
12
+ | `@poppykit/client/sqlite` | `SqliteAuthStore`, `SqliteConversationStore` | [storage](STORAGE.md) |
13
+ | `@poppykit/client/memory` | `MemoryConversationStore` (non-durable) | [storage](STORAGE.md) |
14
+ | `@poppykit/client/provider` | `createProvider`, `createProviderClient`, `createResultCipher` | [provider](PROVIDER.md) |
15
+ | `@poppykit/client/provider/sqlite` | `SqliteProviderStore` | [provider](PROVIDER.md) |
16
+
17
+ ClientAuth methods include `start/get/list/target`, `authorization`,
18
+ `beginSignIn/continueSignIn/completeSignIn/cancelSignIn/operation`, `renew`,
19
+ `signOut/retryCleanup/recover`, `authorizedFetch`, `apiTarget/apiFetch`,
20
+ `joinBrowser` and `feedback`. Conversation operations bind one connection, issuer
21
+ and endpoint; never substitute profile names or model-provided URLs for that binding.
22
+
23
+ The [quickstart](examples/quickstart.mjs) is executed by CI. Integration examples
24
+ describe adapters your application supplies; they are not built-in browser drivers
25
+ or general API executors.
package/AUTH.md ADDED
@@ -0,0 +1,51 @@
1
+ # Authentication lifecycle
2
+
3
+ `createClientAuth` owns Direct, Device and Mediated sign-in, token renewal,
4
+ replacement, revocation and abandoned-operation recovery. Call `recover()` on
5
+ startup. Another live process is never interrupted; an ambiguous dispatched
6
+ exchange is uncertain and must not be replayed.
7
+
8
+ Ask `authorization(connectionId, scopes)` for the current grant, missing scopes
9
+ and advertised methods. Choose a method explicitly in `beginSignIn`. Direct
10
+ requires an HTTPS registered callback and an authenticated browser binding.
11
+ Display its URL in the user's browser. Device returns a checked link/code and
12
+ company identity; call `continueSignIn(operationId)` when its deadline is due.
13
+ Do not automate user approval. Mediated calls the configured `TrustedInput`
14
+ adapter for declared fields and correlated one-time codes. Only trusted UI code
15
+ implements this adapter. Values are cleared after each submission and never
16
+ persisted by the SDK or exposed in public results. Input adapters must not log
17
+ values, keep copies, or return them through model tools.
18
+
19
+ First-time partial grants complete with `grantedScopes` and `missingScopes`.
20
+ Replacement preserves every old grant scope by default. An explicit
21
+ `requiredExistingScopes` subset permits dropping other old scopes. A rejected
22
+ replacement retains the previous grant and queues the new token for revocation;
23
+ accepted replacements queue the previous token instead. Grants are never unioned.
24
+ `force: true` represents an explicit user request to sign in again. Otherwise a
25
+ sufficient current grant is reused. Run `retryCleanup` to retry durable retirement.
26
+
27
+ Metadata is refreshed through the supplied resolver. A changed exact issuer or
28
+ saved destination fails with `metadata_changed`; start a new connection after
29
+ reviewing the new metadata. Saved work is never rerouted. Server Retry-After
30
+ deadlines are persisted as absolute decimal milliseconds, including enormous
31
+ values, and survive restarts without a local delay cap.
32
+
33
+ `start(domain, { anonymous: true })` uses a fresh identity and DPoP key. Ending
34
+ it deletes that private key and local identity; sign-in starts a new regular
35
+ connection/session. Consume the returned connection ID and do not transfer old
36
+ conversations. Regular issuer identity remains stable and separate.
37
+
38
+ Provider adapters implement J1's `AssertionProvider` and `ProviderIdentity`;
39
+ their authenticated identity supplies the issuer-specific UserID. Only purpose,
40
+ issuer and operation binding are passed for assertions, never arbitrary bytes
41
+ or claims. The operator must publish the signing key. For anonymous sessions a
42
+ separate local `IdentitySigner` is required and its key must also be published in
43
+ the client's JWKS. A provider-only configuration reports `anonymous_unavailable`;
44
+ it never substitutes the provider's stable identity for an anonymous one.
45
+
46
+ Account Tokens, device codes, PKCE and DPoP keys belong in the configured
47
+ SecretStore; Session Tokens stay in memory. SQLite and secret storage are separate
48
+ commit boundaries. A host must sweep orphan staged secrets only after checking
49
+ ownership and a grace period; crashes between stage and durable reference can
50
+ leave such orphans. Cancellation/revocation generation checks defeat late results.
51
+ Revocation stops future protected work and never automatically reconnects.
package/CHANGELOG.md ADDED
@@ -0,0 +1,18 @@
1
+ # Changelog
2
+
3
+ ## 0.1.1
4
+
5
+ Documentation refresh: registry installation, runnable quickstarts, focused guides
6
+ and the tagged GitHub/OIDC release runbook. No runtime API, storage format or
7
+ third-party dependency change.
8
+
9
+ ## 0.1.0
10
+
11
+ Initial MIT development release, published manually. Discovery, safe HTTPS, all
12
+ three sign-in methods, targeted durable storage and recovery, conversations and
13
+ API/browser/feedback hooks. The CLI uses the client SDK with trusted secure-backend
14
+ adapters. J8 established strict typed tests and exact installed-artifact checks.
15
+
16
+ Version 0.x may still change APIs/storage; future changes must explain compatibility,
17
+ migration/reset and security implications. No stable 1.0 or external-host certification
18
+ is promised. Older private 0.0.0 candidates retain their own historical evidence.
@@ -0,0 +1,38 @@
1
+ # Contributing
2
+
3
+ Follow the live draft 0.2 [specification](https://personalagentprotocol.org/docs/0.2/spec)
4
+ and [guides](https://personalagentprotocol.org/docs/0.2/guides). Tests/snapshots are
5
+ supporting evidence. Demo/business workflows and individual extensions are excluded.
6
+ Code is [MIT](LICENSE); confirm rights for third-party contributions.
7
+
8
+ Use Node 24.12.0, TypeScript 5.9.3 and @types/node 24.10.0.
9
+ Install the packageManager-pinned pnpm and run pnpm install --frozen-lockfile.
10
+
11
+ ```sh
12
+ npm run typecheck
13
+ npm run build
14
+ npm run typecheck:tests
15
+ npm test
16
+ npm run check:docs
17
+ ```
18
+
19
+ First-party tests/workers/verification helpers are canonical `.mts`. The existing
20
+ compiler checks the whole strict program before emitting ignored adjacent `.mjs`.
21
+ Failed builds remove stale outputs; no loader/type stripping substitutes for checking.
22
+ Run `npm run build:tests` before `node scripts/run-checks.mjs GROUP` (unit, contracts,
23
+ integration, validation). Groups are exhaustive/disjoint. LF source bytes are enforced
24
+ by Git attributes. Generated test code is outside the npm allowlist; production tests
25
+ import built/installed JavaScript. Release/build tools and examples may remain JS.
26
+
27
+ `npm run coverage` builds production and checked tests, then writes native Node
28
+ line/branch/function reports under `coverage/` (summary.txt, lcov.info, report.json,
29
+ raw/). Metrics cover loaded first-party dist JS: no production source maps or TS
30
+ remapping; node_modules excluded, unloaded modules listed. Raw subprocess reports
31
+ are retained, but arbitrary children may not merge and killed workers may not flush.
32
+ Installed/OS/host checks are separate evidence; no percentage proves conformance.
33
+
34
+ Include callers, meaningful regression evidence and docs with behavior changes.
35
+ Preserve credential/destination validation, private errors and persisted data.
36
+ Report checks actually run and limits. Use Issues for non-sensitive defects and
37
+ [SECURITY.md](SECURITY.md) for vulnerabilities. Follow [RELEASING.md](RELEASING.md)
38
+ before tagging/publishing; obtain explicit owner authorization.
package/DELIVERY.md ADDED
@@ -0,0 +1,94 @@
1
+ # Conversation delivery and transport
2
+
3
+ The SDK targets Personal Agent Protocol draft 0.2. Company content, messages,
4
+ unknown events and partial text remain untrusted data for the host application.
5
+
6
+ ## Client
7
+
8
+ Bind `createConversationClient` to the existing auth lifecycle:
9
+
10
+ ```ts
11
+ const target = await auth.target(connectionId);
12
+ const conversations = createConversationClient({
13
+ ...target, connectionId, store, clock: { now: Date.now },
14
+ authorizedRequest: request => auth.authorizedFetch(connectionId, request, target),
15
+ authorization: (id, scopes) => auth.authorization(id, scopes),
16
+ });
17
+ for await (const item of conversations.watch(conversationId, { consumer: 'ui', signal })) {
18
+ await processEvent(item.event);
19
+ await item.ack();
20
+ }
21
+ ```
22
+
23
+ `authorization(eventOrError)` returns the single auth lifecycle's verified
24
+ required/granted/missing scopes and eligible methods. The UI requests consent and
25
+ chooses an advertised method; Device or Mediated does not require Direct support.
26
+
27
+ `read` returns one page; `drain` follows `has_more` immediately. Named consumers
28
+ advance only on explicit acknowledgement. `watch` accepts SSE or JSON fallback,
29
+ skips unknown event types, deduplicates up to 10,000 event IDs per invocation, and
30
+ never rewinds its cursor on a duplicate. JSON pagination has its separate
31
+ `maxPages` bound (default 1000), without consuming reconnect attempts. Reconnects
32
+ are bounded (default 4); exhaustion is explicit `reconnect_exhausted`.
33
+
34
+ `lastEventId` takes precedence over the starting cursor. On reconnect the watcher
35
+ sends `Last-Event-ID`. `onPartial` is display-only: a null value clears unfinished
36
+ text on completion/disconnect; never execute or reason from an incomplete delta.
37
+ Messages are actionable only after a complete `message` event. SSE frames and
38
+ JSON fallback are limited to 1 MiB, streams to 16 MiB, and partial text per stream
39
+ to 1 MiB. UTF-8 fragmentation and CR/LF/CRLF are supported.
40
+
41
+ `cursor_expired` is an explicit history gap. Inform the user, then reset a named
42
+ consumer and reread retained history, or supply `onHistoryGap` returning true.
43
+ Automatic recovery is limited to one reset per watch; it does not recover missing
44
+ events. Checkpoints and pending sends survive process restart with durable stores.
45
+ Send retries keep their original connection, issuer, endpoint, ID, body and read
46
+ parameters. Abort signals are invocation-local and are never persisted. Canceling
47
+ delivery does not cancel an accepted company action or prove a lost send failed.
48
+
49
+ ## Node transport
50
+
51
+ `createNodeTransport` uses Node HTTPS agents, with separate discovery, JSON and
52
+ stream pools. Defaults: 8 pools total, 2 connections per pool (including pending
53
+ TLS), 32 active operations, 15-second JSON timeout, and 5-second discovery timeout.
54
+ There is no hidden request queue: excess pool capacity fails `overloaded`, so a
55
+ later attempt performs fresh DNS and policy checks. Hosts decide scheduling.
56
+ Streams use `transport.stream` with a 5-minute/16-MiB lifetime budget. Call
57
+ `transport.close()` to cancel outstanding reads, DNS waiters, connects and streams.
58
+
59
+ Every request rechecks DNS and current address policy before pool assignment.
60
+ Changed approved addresses retire incompatible pools; actual TLS peers and reused
61
+ sockets are checked before HTTP bytes. TLS certificates remain verified. Only
62
+ explicit hostname/address exceptions permit private peers. Discovery never carries
63
+ credentials and follows only HTTPS redirects; credential-bearing requests never
64
+ follow redirects. Pool retirement may interrupt an active stream; reconnect uses
65
+ the durable cursor and fresh authorization.
66
+
67
+ Metadata caching remains bounded and honors freshness, Age, validators, no-cache
68
+ and no-store, with a 300-second upper bound. Expired entries revalidate; 304s retain
69
+ prior caching directives. `resolver.invalidate(target?)` fences old pending cache
70
+ writes without bypassing fetch concurrency/rate limits. The auth lifecycle rejects changed saved
71
+ issuer/endpoint/resource bindings rather than rerouting persisted work.
72
+
73
+ ## Peer retention
74
+
75
+ The [server delivery guide](https://github.com/LMol-4/poppykit-server/blob/main/DELIVERY.md)
76
+ describes service configuration, byte budgets, notifications and bounded streams.
77
+ Keep the following distinct lifetimes when choosing an operator retention policy:
78
+
79
+ | Record | Lifetime / deletion rule |
80
+ | --- | --- |
81
+ | Conversation and message receipts | Entire conversation lifetime, including after closure and payload pruning; no automatic deletion |
82
+ | Event payloads | Retained indefinitely by default; host may call SQLite `pruneEvents(id, before, limit)` in bounded batches; recommend at least 30 days |
83
+ | Event cursor identities | Conversation lifetime; distinguish expired payloads (410) from foreign cursors (400) |
84
+ | Active work | Never pruned while held/pending/claimed; event pruning protects these conversations |
85
+ | Completed work | Bounded inspection retention; stale fences cannot publish |
86
+ | Latest user message, context and account binding | Operational conversation record, retained for its lifetime, including handoff after payload pruning |
87
+ | Client outbound/checkpoints | Retained until explicit local store deletion/reset; preserve pending/uncertain sends until reconciled |
88
+ | Pending auth and replay records | Separate auth validity/owner rules in AUTH.md and STORAGE.md; not the event TTL |
89
+ | CLI transcripts/retries | CLI ownership; local deletion never deletes company history or revokes authorization |
90
+
91
+ Pruning is explicit operator policy, never automatic data loss. It preserves old
92
+ message receipts and cursor identity. Concurrent reads see a coherent snapshot or
93
+ `cursor_expired`. Invalid/expired waited-POST cursors are rejected before accepting
94
+ the new write; an existing message ID still returns its original receipt.
@@ -0,0 +1,87 @@
1
+ # API, browser and feedback hooks
2
+
3
+ Use the same `ClientAuth` instance and trusted adapters as sign-in and conversations.
4
+ Do not expose credential-bearing hooks, request headers, browser form fields or
5
+ private responses as printable model tools. Company descriptions and responses are
6
+ untrusted data. These hooks do not execute OpenAPI operations, MCP tools or browser
7
+ automation for you.
8
+
9
+ ## Existing API clients
10
+
11
+ ```ts
12
+ // Read the API description without credentials using the configured safe transport.
13
+ const description = await transport.fetch(new Request(descriptionUrl), { mode: 'discovery' });
14
+ // An existing OpenAPI client selects its server and constructs its final Request.
15
+ const target = await auth.apiTarget(connectionId, descriptionUrl, 'https://company.example/api');
16
+ const fetchForClient = (request: Request) => auth.apiFetch(connectionId, request, target, ['records:read']);
17
+ const result = await fetchForClient(new Request('https://company.example/api/records'));
18
+ ```
19
+
20
+ OpenAPI `apiUrl` is the description URL from verified company discovery. The third
21
+ argument is the actual destination base; its host must independently serve verified
22
+ company discovery naming the same issuer. This conservative policy requires API
23
+ hosts to publish discovery; a document's `servers` field alone grants no authority.
24
+ Final requests must stay within the verified origin and slash-delimited base path.
25
+ The proof binds the actual final method and URL. Destination changes require a new
26
+ explicit connection; saved work never moves silently.
27
+
28
+ For MCP, use `auth.apiTarget(connectionId, mcpUrl)` and the same fetch closure in an
29
+ existing Streamable HTTP MCP client (version 2025-06-18 or later). The SDK does not
30
+ encode JSON-RPC or manage MCP sessions. The exact discovery URL is the resource and
31
+ endpoint; extra paths or queries are refused. Query-bearing discovery endpoints are
32
+ not supported. Protected-resource metadata must name that exact resource and the
33
+ verified company issuer. ES256 DPoP metadata selects DPoP; absence selects Bearer.
34
+ A Bearer-only MCP resource cannot also authorize a conversation. Resource-specific
35
+ credential slots use the shared renewal, rotation, nonce, cooldown and revocation
36
+ lifecycle. The wire `token_type` must match verified metadata; there is no fallback
37
+ from DPoP to Bearer. Redirects never forward tokens or proofs. Auth 401 rejection may
38
+ cause one fresh-token retry; unknown results are not automatically retried.
39
+
40
+ ## Agent-controlled browser
41
+
42
+ ```ts
43
+ await auth.joinBrowser(connectionId, 'https://company.example/account', {
44
+ kind: 'agent-controlled',
45
+ async postForm({ url, fields, validateRedirect }) {
46
+ // Bind your existing browser controller here. It must enforce this contract:
47
+ // isolated per-session storage; verified TLS/actual peer addresses; no logging;
48
+ // top-level form POST; intercept redirects BEFORE following or replaying a body.
49
+ const response = await controlledBrowser.postFormWithoutFollowing(url, fields);
50
+ const destination = validateRedirect(response.status, response.location);
51
+ await controlledBrowser.navigateWithoutPostBody(destination);
52
+ },
53
+ });
54
+ ```
55
+
56
+ `controlledBrowser` is the host's existing controller, not an SDK driver. This hook
57
+ is trusted code. Its network layer must reject loopback/private/link-local actual
58
+ connected addresses unless narrowly configured, validate TLS and enforce redirect
59
+ interception. Never replay the assertion on 307/308 or send it in a URL. The supplied
60
+ validator permits only 303 to the exact validated `returnTo`. HttpTransport cannot
61
+ enforce network policy inside an unrelated browser engine. Keep this capability
62
+ separate from the human consent-browser opener. Never join the user's browser.
63
+ Use separate cookies/local storage for each session/user, join just before browsing,
64
+ rejoin on lost recognition and close the context when the session ends. Custom
65
+ controllers own those guarantees; the SDK validates the assertion destination and
66
+ navigation contract but cannot inspect their internal browser implementation.
67
+
68
+ ## Feedback
69
+
70
+ ```ts
71
+ await auth.feedback(connectionId, {
72
+ kind: 'missing', task: 'Export records',
73
+ description: 'There is no export operation in the API, conversation or website.',
74
+ });
75
+ ```
76
+
77
+ Signed-out sessions are allowed and no account scope is required. Task and
78
+ description are nonempty strings of at most 200 and 2000 Unicode codepoints. Only
79
+ `missing` is supported client-side. Omit identifiers, personal data and secrets;
80
+ report task limitations rather than individual errors. Success is an empty 202.
81
+ There are no automatic POST retries (including nonce/auth rejection). A lost
82
+ response can mean accepted; callers decide whether to repeat because the protocol
83
+ defines no deduplication key. Prefer one report per unfulfilled task.
84
+
85
+ The executable generic caller/controller fixtures are `tests/integrations.test.mts`.
86
+ They exercise the same hooks used by the CLI; installed CLI integration is
87
+ verified separately against immutable candidate tarballs.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 PoppyKit contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/PROVIDER.md ADDED
@@ -0,0 +1,99 @@
1
+ # Optional identity provider
2
+
3
+ Import `createProvider`, `createProviderClient`, and `createResultCipher` from
4
+ `@poppykit/client/provider`; durable `SqliteProviderStore` is separately exported
5
+ from `@poppykit/client/provider/sqlite`. Neither is loaded by the core import.
6
+ This is PoppyKit provider contract **v1**, not a Personal Agent Protocol extension.
7
+ Node 24.12+ is required for the SQLite reference adapter.
8
+
9
+ An operator supplies HTTPS hosting, a provider client metadata URL, JWKS URL,
10
+ HTTPS callback URL on that same domain, an ES256 signer/KMS, a verified company
11
+ metadata resolver, authenticated enrollment and encrypted callback-result storage.
12
+ The provider has no company account-token vault, token-exchange proxy, login UI,
13
+ deployment dependency or cloud dependency.
14
+
15
+ ```js
16
+ import { createProvider, createResultCipher } from '@poppykit/client/provider';
17
+ import { SqliteProviderStore } from '@poppykit/client/provider/sqlite';
18
+ const provider = createProvider({
19
+ clientId: 'https://identity.example/client.json',
20
+ jwksUri: 'https://identity.example/jwks',
21
+ callbackUrl: 'https://identity.example/callback', clientName: 'Operator agent',
22
+ store: new SqliteProviderStore('/private/provider.sqlite'),
23
+ cipher: createResultCipher(await operatorKeys.readResultEncryptionKey()),
24
+ signer: operatorKeys.signer, metadata: verifiedCompanyResolver,
25
+ authenticateEnrollment: async request => {
26
+ // Authenticate the human using the operator's existing login; validate CSRF.
27
+ const human = await operatorLogin.authenticateEnrollment(request);
28
+ return human ? { subject: human.stableSubject, allowedCompanies: human.companyDomains } : null;
29
+ },
30
+ verifySession: binding => operatorSessionVerifier.owns(binding),
31
+ });
32
+ // Route Fetch Requests under /v1/{operation}, plus the exact public URLs above:
33
+ // await provider.handle(request)
34
+ ```
35
+
36
+ `operatorKeys`, `operatorLogin`, `operatorSessionVerifier` and
37
+ `verifiedCompanyResolver` are operator-owned adapters, not globals supplied by
38
+ the package. A session verifier must independently verify both the provider client
39
+ and derived UserID; an opaque session ID alone does not prove ownership. Browser
40
+ signing fails closed without that verifier. Starting a session signs a derived
41
+ UserID assertion; the company checks session/client/UserID association on renewals.
42
+
43
+ POST JSON operations use `version: 1` and `installationId`. Enroll additionally
44
+ takes a fresh `idempotencyKey`; private operations carry `Authorization: Bearer
45
+ <enrollmentId.secret>`. The secret is generated once and delivered only in that
46
+ authenticated enrollment response. A repeated enrollment key returns `consumed`;
47
+ lost credential delivery requires fresh human authentication and enrollment.
48
+ Installations have independent credentials and callback operations. Re-enrollment
49
+ can recover a visitor identity through the same authenticated operator subject,
50
+ but cannot recover another installation's result. Enrollment never accepts caller
51
+ subjects, arbitrary audiences, lifetimes, key IDs, raw bytes or signing claims.
52
+
53
+ `identity` takes domain and exact issuer; `begin` registers immutable operation,
54
+ issuer/domain, purpose (`session`, `client-authentication`, `browser`, `direct`),
55
+ optional session and browser return destination. Company metadata is independently
56
+ resolved and enrollment company policy checked. `assert` takes operation/purpose
57
+ only and constructs a 60-second assertion with fresh 256-bit jti. Browser assertions
58
+ are audience-bound to the verified browser endpoint. `createProviderClient` binds
59
+ enrollment credentials and automatically registers SDK assertion operations.
60
+
61
+ Direct sign-in uses SDK-generated 256-bit state. Trusted CLI code privately
62
+ registers it with `beginDirect` after SDK preparation and before displaying the
63
+ authorization URL. Only its hash is indexed. The callback accepts state, exact
64
+ issuer, and exactly one code/error; result ciphertext is sealed atomically with
65
+ the immutable binding. Identical pending callbacks are harmless; conflicting,
66
+ canceled, expired and consumed callbacks fail. `takeResult` requires enrollment,
67
+ installation, operation, issuer and exact session; it consumes within the same
68
+ SQLite transaction before returning private callback data. Lost retrieval delivery
69
+ is uncertain, never a reason to exchange or retrieve again. SDK code owns PKCE and
70
+ the exchange. Never log request URLs or responses on this route.
71
+
72
+ SQLite serializes writers across processes. Provider transactions must be
73
+ synchronous, targeted and atomic; custom stores implement `ProviderStore` with
74
+ the same guarantees. The AES-GCM result key comes from an independent secure
75
+ backend and must never be stored beside the database. Cipher failures roll back
76
+ consumption. KMS failures fail closed; a revoke/cancel racing signing is rechecked
77
+ before delivery. External stores/KMS are separate failure boundaries.
78
+
79
+ Defaults bound enrollments globally to 10,000, pending operations to ten per
80
+ enrollment, assertions to 60/minute, operation lifetime to ten minutes and request
81
+ bodies to 16 KiB. A hard cap of 1,000 retained operations per enrollment keeps
82
+ quota/cleanup/revocation scans complete even under tombstone flooding. Expired
83
+ operations are cleaned in bounded batches on begin;
84
+ tombstones survive expiry plus 30 seconds. Hosts must additionally enforce
85
+ per-human enrollment rate limits, ingress rate/connection/time limits, total
86
+ database retention, and authenticated account recovery. Rate-limit responses
87
+ include a retry timestamp. Errors contain fixed codes only.
88
+
89
+ Rotate signer configuration only after publishing the new key. Supply
90
+ `publishedKeys` during rotation and retain old public keys until every assertion
91
+ expires plus 30-second skew; never publish private JWK fields. The operator owns
92
+ the publication grace period and KMS key lifecycle.
93
+
94
+ Enrollment revocation stops new signing/retrieval and cancels pending results.
95
+ It cannot invalidate delivered assertions, existing company Session Tokens,
96
+ Account Tokens or DPoP keys. Revoke company authorization **before** disabling
97
+ provider signing, or use authenticated company account controls afterward.
98
+ This is scoped authority, redaction and provider key custody; it does not isolate
99
+ authority from unrestricted programs running as the same OS user.
package/README.md CHANGED
@@ -1,3 +1,50 @@
1
- # Temporary Holding Version
1
+ # PoppyKit client
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ TypeScript SDK for [Personal Agent Protocol draft 0.2](https://personalagentprotocol.org/docs/0.2/spec):
4
+ discovery, authentication, conversations and API/browser/feedback hooks. Your
5
+ application supplies the agent and trusted infrastructure. [MIT](LICENSE).
6
+
7
+ ## Install
8
+
9
+ Requires Node **24.12.0+**, ESM and HTTPS. CI tests Node 24.12.0 on Linux, macOS and
10
+ Windows; later majors are not separately certified.
11
+
12
+ ```sh
13
+ npm install @poppykit/client@0.1.1
14
+ ```
15
+
16
+ ## Discover a company
17
+
18
+ No credentials or database are needed. Replace `company.example` with a company
19
+ publishing draft 0.2 discovery:
20
+
21
+ ```js
22
+ import { createMetadataResolver, createNodeTransport } from '@poppykit/client';
23
+
24
+ const transport = createNodeTransport();
25
+ try {
26
+ const company = await createMetadataResolver(transport).resolveCompany('company.example');
27
+ console.log(company.document.organization);
28
+ } finally {
29
+ transport.close?.();
30
+ }
31
+ ```
32
+
33
+ The packaged [quickstart](examples/quickstart.mjs) also supports `--self-check`
34
+ without a network service.
35
+
36
+ ## Guides
37
+
38
+ - [Authentication](AUTH.md): sessions, Direct/Device/Mediated, consent, renewal and revocation.
39
+ - [Storage](STORAGE.md): targeted adapters, secret references, recovery and explicit resets.
40
+ - [Delivery](DELIVERY.md): messages, streaming, cancellation, retries and retention.
41
+ - [Integrations](INTEGRATIONS.md): verified API targets, agent browser and feedback.
42
+ - [Optional provider](PROVIDER.md): operator-hosted signing and callback custody.
43
+ - [API exports](API.md), [contributing](CONTRIBUTING.md), [security](SECURITY.md),
44
+ [changelog](CHANGELOG.md) and [releasing](RELEASING.md).
45
+
46
+ Keep Account Tokens, DPoP keys and PKCE in a secure SecretStore; never expose them
47
+ to model tools. An uncertain remote result must not be silently replayed. SQLite
48
+ is optional/experimental and is not vault encryption. Production issuers, vaults,
49
+ browsers and hosting require operator validation; individual extensions and
50
+ application business workflows are outside this SDK.
package/RELEASING.md ADDED
@@ -0,0 +1,91 @@
1
+ # Releasing
2
+
3
+ Use the tagged GitHub Actions workflow, not a local rebuild. Packages are MIT;
4
+ 0.1.0 was manually bootstrapped. Version 0.1.1 refreshes documentation without
5
+ changing runtime APIs. Each new version needs its own source/artifact evidence.
6
+
7
+ ## Before tagging
8
+
9
+ - Confirm npm ownership, unused versions, supported Node 24.12.0 and npm>=11.5.1.
10
+ Keep the pinned compiler/dependencies; do not upgrade tooling during a release.
11
+ - Update version, changelog and shipped docs. CLI's client dependency and both
12
+ candidate JSON files must match exact final SDK versions/full source SHAs.
13
+ - Review the full diff and run typecheck, build, tests and documentation checks.
14
+ Preserve the existing API/storage/security contracts and historical evidence.
15
+ - Protect source, workflow files, pins and release tags with repository review/rules.
16
+ All actions are commit-pinned; release verification never restores a PR cache.
17
+ - Verify GitHub environment **npm**: variable **NPM_PUBLISH_ENABLED=true**,
18
+ intended required reviewer, no admin bypass, and allowed deployment tags **v***.
19
+ Reviewers approve the real pending deployment; automation must not approve for them.
20
+ - Configure the npm package's GitHub trusted publisher: owner **LMol-4**, this
21
+ package repository, workflow **release.yml**, environment **npm**. Match case.
22
+ Enable **direct npm publish** permission; this workflow does not stage a publish.
23
+ Configure near the release: unused configurations expire after two days. Public
24
+ source/package visibility and supported GitHub-hosted runners are required for
25
+ this provenance path. Do not supply NODE_AUTH_TOKEN or fall back to a long-lived
26
+ token if OIDC fails. Confirm current [npm requirements](https://docs.npmjs.com/trusted-publishers/).
27
+
28
+ ## Publish exact tagged source
29
+
30
+ Client and server are independent. Publish client before CLI: the CLI registry
31
+ guard requires its tested client tarball integrity to equal the published one.
32
+
33
+ 1. Merge reviewed source to main. Record the full SHA and create/push an approved
34
+ immutable **vVERSION** tag at that exact commit. Never move or replace a tag.
35
+
36
+ ```sh
37
+ git tag vVERSION FULL_SHA
38
+ git push origin refs/tags/vVERSION
39
+ ```
40
+ 2. Dispatch **Release** from that tag, with matching version, tag and full commit.
41
+ Optional `publish=false` rehearses verification only. A later publishing dispatch
42
+ is a new run with newly identified artifacts; it does not reuse a rehearsal's bytes.
43
+ 3. Dispatch with `publish=true` when authorized. Example (replace all placeholders):
44
+
45
+ ```sh
46
+ gh workflow run release.yml --repo LMol-4/PACKAGE --ref vVERSION -f version=VERSION -f tag=vVERSION -f commit=FULL_SHA -f publish=true
47
+ ```
48
+
49
+ 4. Wait for candidate, fast, contracts, integration, packaged-linux/windows/macos,
50
+ validation and gate to succeed. Missing, failed, canceled or skipped prerequisites
51
+ fail closed. Then follow the Actions environment-approval link. Do not bypass it.
52
+ 5. The publisher rechecks source/tag/ref/version, trusted manifest digest, unchanged
53
+ tarball metadata/bytes, owner enablement, registry absence and dependency integrity.
54
+ It publishes the same-run tested tarball with `--access public --provenance
55
+ --ignore-scripts`; it never installs, builds or packs again.
56
+ 6. Save run URL, full source SHAs, manifest SHA256, filenames, versions, byte counts,
57
+ tarball SHA256/npm SHA512 and registry integrity/provenance. Download before the
58
+ 14-day candidate retention expires. Verify registry bytes, a clean exact-version
59
+ registry install, exported modules and the real npm CLI shim.
60
+
61
+ Ordinary main/tag CI is useful evidence but is not a tagged Release/OIDC publishing
62
+ run. A dry run or green tests alone do not prove npm authentication or provenance.
63
+ The separate checked test-harness artifact is source/output-hash bound, used only
64
+ by unprivileged verification jobs, and never published in an npm package.
65
+
66
+ ## Failure and partial release
67
+
68
+ Stop on tag/source/version/digest mismatch, unknown registry responses or changed
69
+ client integrity. Do not weaken gates, repack approved bytes, change pins silently,
70
+ force an overwrite or use a token fallback. Inspect the failed job and preserve logs.
71
+ For OIDC/auth failure check publisher spelling/case, workflow/environment identity,
72
+ expiry, reviewer approval, permissions and npm/toolchain support before a new run.
73
+
74
+ If a version already exists, compare its registry integrity and provenance with the
75
+ saved receipt. Matching successful publication is recorded, never republished.
76
+ For partial publication, finish only absent packages; verify the published client's
77
+ exact bytes before CLI. If bytes differ or a fix is needed, use a reviewed new
78
+ version and complete verification. A source checkout rollback cannot undo an npm
79
+ publication or external side effect. Deprecation, dist-tag changes, unpublish and
80
+ replacement versions need separate owner authorization; never delete evidence.
81
+
82
+ ## Verification limits
83
+
84
+ Deterministic source suites run once per verification graph on Linux; exhaustive
85
+ file groups reject omissions/duplicates. Packaged jobs test actual installed JS,
86
+ exports/declarations/content/SQLite and representative platform behavior; CLI also
87
+ tests real shims, auth/retry processes and API/browser/feedback wiring. They do not
88
+ certify a production keyring, physical terminal, browser engine, KMS or TLS deployment.
89
+ Windows GNU tar is given archive basename plus directory cwd to avoid drive-colon
90
+ remote parsing. Failure logs retain seven days. Expired/missing artifacts require a
91
+ new full run, not relabeling old results. See [CONTRIBUTING.md](CONTRIBUTING.md).
package/SECURITY.md ADDED
@@ -0,0 +1,13 @@
1
+ # Security reporting
2
+
3
+ Do not put credentials, tokens, private keys, personal records or exploit details
4
+ in a public issue. Use this repository's GitHub **Security → Report a vulnerability**
5
+ when private reporting is enabled. If it is unavailable, request a private contact
6
+ from the repository owner without disclosing the vulnerability publicly. The owner
7
+ must enable/confirm that route; no response SLA is promised.
8
+
9
+ Include affected commit/version, a minimal reproduction using synthetic data,
10
+ impact and the trust boundary involved. These draft packages have no promised
11
+ support window or response SLA. Custom vaults, issuers, browser controllers,
12
+ proxy routing and deployment policy remain operator responsibilities. Redaction
13
+ and scoped authority do not isolate an unrestricted same-user program.