@soulcraft/sdk 1.0.0

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 (149) hide show
  1. package/dist/client/index.d.ts +62 -0
  2. package/dist/client/index.d.ts.map +1 -0
  3. package/dist/client/index.js +60 -0
  4. package/dist/client/index.js.map +1 -0
  5. package/dist/index.d.ts +33 -0
  6. package/dist/index.d.ts.map +1 -0
  7. package/dist/index.js +17 -0
  8. package/dist/index.js.map +1 -0
  9. package/dist/modules/ai/index.d.ts +55 -0
  10. package/dist/modules/ai/index.d.ts.map +1 -0
  11. package/dist/modules/ai/index.js +263 -0
  12. package/dist/modules/ai/index.js.map +1 -0
  13. package/dist/modules/ai/types.d.ts +216 -0
  14. package/dist/modules/ai/types.d.ts.map +1 -0
  15. package/dist/modules/ai/types.js +30 -0
  16. package/dist/modules/ai/types.js.map +1 -0
  17. package/dist/modules/auth/backchannel.d.ts +85 -0
  18. package/dist/modules/auth/backchannel.d.ts.map +1 -0
  19. package/dist/modules/auth/backchannel.js +168 -0
  20. package/dist/modules/auth/backchannel.js.map +1 -0
  21. package/dist/modules/auth/config.d.ts +122 -0
  22. package/dist/modules/auth/config.d.ts.map +1 -0
  23. package/dist/modules/auth/config.js +158 -0
  24. package/dist/modules/auth/config.js.map +1 -0
  25. package/dist/modules/auth/middleware.d.ts +146 -0
  26. package/dist/modules/auth/middleware.d.ts.map +1 -0
  27. package/dist/modules/auth/middleware.js +204 -0
  28. package/dist/modules/auth/middleware.js.map +1 -0
  29. package/dist/modules/auth/types.d.ts +162 -0
  30. package/dist/modules/auth/types.d.ts.map +1 -0
  31. package/dist/modules/auth/types.js +14 -0
  32. package/dist/modules/auth/types.js.map +1 -0
  33. package/dist/modules/billing/types.d.ts +7 -0
  34. package/dist/modules/billing/types.d.ts.map +1 -0
  35. package/dist/modules/billing/types.js +7 -0
  36. package/dist/modules/billing/types.js.map +1 -0
  37. package/dist/modules/brainy/auth.d.ts +104 -0
  38. package/dist/modules/brainy/auth.d.ts.map +1 -0
  39. package/dist/modules/brainy/auth.js +144 -0
  40. package/dist/modules/brainy/auth.js.map +1 -0
  41. package/dist/modules/brainy/errors.d.ts +118 -0
  42. package/dist/modules/brainy/errors.d.ts.map +1 -0
  43. package/dist/modules/brainy/errors.js +142 -0
  44. package/dist/modules/brainy/errors.js.map +1 -0
  45. package/dist/modules/brainy/events.d.ts +63 -0
  46. package/dist/modules/brainy/events.d.ts.map +1 -0
  47. package/dist/modules/brainy/events.js +14 -0
  48. package/dist/modules/brainy/events.js.map +1 -0
  49. package/dist/modules/brainy/proxy.d.ts +48 -0
  50. package/dist/modules/brainy/proxy.d.ts.map +1 -0
  51. package/dist/modules/brainy/proxy.js +95 -0
  52. package/dist/modules/brainy/proxy.js.map +1 -0
  53. package/dist/modules/brainy/types.d.ts +83 -0
  54. package/dist/modules/brainy/types.d.ts.map +1 -0
  55. package/dist/modules/brainy/types.js +21 -0
  56. package/dist/modules/brainy/types.js.map +1 -0
  57. package/dist/modules/events/index.d.ts +41 -0
  58. package/dist/modules/events/index.d.ts.map +1 -0
  59. package/dist/modules/events/index.js +53 -0
  60. package/dist/modules/events/index.js.map +1 -0
  61. package/dist/modules/events/types.d.ts +129 -0
  62. package/dist/modules/events/types.d.ts.map +1 -0
  63. package/dist/modules/events/types.js +32 -0
  64. package/dist/modules/events/types.js.map +1 -0
  65. package/dist/modules/formats/types.d.ts +7 -0
  66. package/dist/modules/formats/types.d.ts.map +1 -0
  67. package/dist/modules/formats/types.js +7 -0
  68. package/dist/modules/formats/types.js.map +1 -0
  69. package/dist/modules/hall/types.d.ts +56 -0
  70. package/dist/modules/hall/types.d.ts.map +1 -0
  71. package/dist/modules/hall/types.js +16 -0
  72. package/dist/modules/hall/types.js.map +1 -0
  73. package/dist/modules/kits/types.d.ts +7 -0
  74. package/dist/modules/kits/types.d.ts.map +1 -0
  75. package/dist/modules/kits/types.js +7 -0
  76. package/dist/modules/kits/types.js.map +1 -0
  77. package/dist/modules/license/types.d.ts +7 -0
  78. package/dist/modules/license/types.d.ts.map +1 -0
  79. package/dist/modules/license/types.js +7 -0
  80. package/dist/modules/license/types.js.map +1 -0
  81. package/dist/modules/notifications/types.d.ts +7 -0
  82. package/dist/modules/notifications/types.d.ts.map +1 -0
  83. package/dist/modules/notifications/types.js +7 -0
  84. package/dist/modules/notifications/types.js.map +1 -0
  85. package/dist/modules/skills/index.d.ts +60 -0
  86. package/dist/modules/skills/index.d.ts.map +1 -0
  87. package/dist/modules/skills/index.js +253 -0
  88. package/dist/modules/skills/index.js.map +1 -0
  89. package/dist/modules/skills/types.d.ts +127 -0
  90. package/dist/modules/skills/types.d.ts.map +1 -0
  91. package/dist/modules/skills/types.js +23 -0
  92. package/dist/modules/skills/types.js.map +1 -0
  93. package/dist/modules/versions/types.d.ts +31 -0
  94. package/dist/modules/versions/types.d.ts.map +1 -0
  95. package/dist/modules/versions/types.js +9 -0
  96. package/dist/modules/versions/types.js.map +1 -0
  97. package/dist/modules/vfs/types.d.ts +26 -0
  98. package/dist/modules/vfs/types.d.ts.map +1 -0
  99. package/dist/modules/vfs/types.js +11 -0
  100. package/dist/modules/vfs/types.js.map +1 -0
  101. package/dist/server/create-sdk.d.ts +70 -0
  102. package/dist/server/create-sdk.d.ts.map +1 -0
  103. package/dist/server/create-sdk.js +125 -0
  104. package/dist/server/create-sdk.js.map +1 -0
  105. package/dist/server/hall-handlers.d.ts +195 -0
  106. package/dist/server/hall-handlers.d.ts.map +1 -0
  107. package/dist/server/hall-handlers.js +239 -0
  108. package/dist/server/hall-handlers.js.map +1 -0
  109. package/dist/server/handlers.d.ts +216 -0
  110. package/dist/server/handlers.d.ts.map +1 -0
  111. package/dist/server/handlers.js +214 -0
  112. package/dist/server/handlers.js.map +1 -0
  113. package/dist/server/index.d.ts +52 -0
  114. package/dist/server/index.d.ts.map +1 -0
  115. package/dist/server/index.js +50 -0
  116. package/dist/server/index.js.map +1 -0
  117. package/dist/server/instance-pool.d.ts +299 -0
  118. package/dist/server/instance-pool.d.ts.map +1 -0
  119. package/dist/server/instance-pool.js +359 -0
  120. package/dist/server/instance-pool.js.map +1 -0
  121. package/dist/transports/http.d.ts +86 -0
  122. package/dist/transports/http.d.ts.map +1 -0
  123. package/dist/transports/http.js +134 -0
  124. package/dist/transports/http.js.map +1 -0
  125. package/dist/transports/local.d.ts +76 -0
  126. package/dist/transports/local.d.ts.map +1 -0
  127. package/dist/transports/local.js +101 -0
  128. package/dist/transports/local.js.map +1 -0
  129. package/dist/transports/sse.d.ts +99 -0
  130. package/dist/transports/sse.d.ts.map +1 -0
  131. package/dist/transports/sse.js +192 -0
  132. package/dist/transports/sse.js.map +1 -0
  133. package/dist/transports/transport.d.ts +68 -0
  134. package/dist/transports/transport.d.ts.map +1 -0
  135. package/dist/transports/transport.js +14 -0
  136. package/dist/transports/transport.js.map +1 -0
  137. package/dist/transports/ws.d.ts +135 -0
  138. package/dist/transports/ws.d.ts.map +1 -0
  139. package/dist/transports/ws.js +331 -0
  140. package/dist/transports/ws.js.map +1 -0
  141. package/dist/types.d.ts +152 -0
  142. package/dist/types.d.ts.map +1 -0
  143. package/dist/types.js +8 -0
  144. package/dist/types.js.map +1 -0
  145. package/docs/ADR-001-sdk-design.md +282 -0
  146. package/docs/IMPLEMENTATION-PLAN.md +708 -0
  147. package/docs/USAGE.md +646 -0
  148. package/docs/kit-sdk-guide.md +474 -0
  149. package/package.json +61 -0
@@ -0,0 +1,708 @@
1
+ # @soulcraft/sdk — Detailed Implementation Plan
2
+
3
+ **Status:** Ready to implement
4
+ **Decisions:** See `ADR-001-sdk-design.md` for all architecture decisions and rationale.
5
+ **Research basis:** Full cross-project exploration of Workshop, Venue, Academy, brainy-client,
6
+ and @soulcraft/auth conducted 2026-02-27.
7
+
8
+ ---
9
+
10
+ ## Source Material — Where Code Comes From
11
+
12
+ Every module in this SDK migrates or absorbs code from an existing location.
13
+ A new session MUST read the source file(s) before implementing each module.
14
+
15
+ | SDK Module | Migrates from | Key source files |
16
+ |------------|--------------|-----------------|
17
+ | `transports/local` | `@soulcraft/brainy-client` | `/media/dpsifr/storage/home/Projects/brainy-client/src/transports/local.ts` |
18
+ | `transports/http` | `@soulcraft/brainy-client` | `/media/dpsifr/storage/home/Projects/brainy-client/src/transports/http.ts` |
19
+ | `transports/ws` | `@soulcraft/brainy-client` | `/media/dpsifr/storage/home/Projects/brainy-client/src/transports/ws.ts` |
20
+ | `transports/sse` | Workshop events | `/media/dpsifr/storage/home/Projects/workshop/src/lib/services/vfsEventService.ts` |
21
+ | `modules/brainy` | `@soulcraft/brainy-client` | `/media/dpsifr/storage/home/Projects/brainy-client/src/index.ts` + Brainy types |
22
+ | `modules/vfs` | Brainy VFS sub-API | `node_modules/@soulcraft/brainy/dist/brainy.d.ts` (brain.vfs.*) |
23
+ | `modules/versions` | Brainy versions sub-API | same (brain.versions.*) |
24
+ | `server/instance-pool` | Workshop singleton + Venue cache | `/media/dpsifr/storage/home/Projects/workshop/brainy/brainy-singleton.ts` + `/home/dpsifr/Projects/venue/apps/web/server.ts` |
25
+ | `modules/auth` | `@soulcraft/auth` + Workshop auth | `/media/dpsifr/storage/home/Projects/auth/packages/auth/src/` + `/media/dpsifr/storage/home/Projects/workshop/auth/better-auth.ts` |
26
+ | `modules/license` | Workshop usage + billing | `/media/dpsifr/storage/home/Projects/workshop/brainy/usage-tracking.ts` + `brainy/config-service.ts` |
27
+ | `modules/ai` | Workshop chat | `/media/dpsifr/storage/home/Projects/workshop/chat/` (models.ts, delegator.ts, executor.ts, planner.ts, tools.ts, ui-actions.ts) + `routes/briggy.ts` |
28
+ | `modules/events` | Workshop data-change-emitter | `/media/dpsifr/storage/home/Projects/workshop/events/data-change-emitter.ts` |
29
+ | `modules/skills` | Workshop skill service | `/media/dpsifr/storage/home/Projects/workshop/brainy/skill-service.ts` + `brainy/bundled-skills.ts` |
30
+ | `modules/kits` | Workshop kit loader | `/media/dpsifr/storage/home/Projects/workshop/src/lib/templates/loader.ts` |
31
+ | `modules/formats` | @soulcraft/views + Workshop types | `/media/dpsifr/storage/home/Projects/workshop/packages/views/src/shared/types.ts` + `src/lib/types/richDocument.ts` |
32
+ | `modules/billing` | Workshop billing | `/media/dpsifr/storage/home/Projects/workshop/brainy/usage-tracking.ts` + `brainy/billing-provider.ts` + `brainy/config-service.ts` + `brainy/config-defaults.ts` |
33
+ | `modules/notifications` | Venue notification service | `/home/dpsifr/Projects/venue/apps/web/src/lib/server/notifications/` |
34
+
35
+ ---
36
+
37
+ ## Phase 1 — Foundation ✅ COMPLETE
38
+
39
+ All items done. Repo created, ADR written, scaffold committed.
40
+
41
+ Remaining scaffolding items (minor, do at start of Phase 2):
42
+ - [ ] `vitest.config.ts` — test runner config
43
+ - [ ] `.env.example` — document any env vars the SDK needs
44
+ - [ ] Update `src/modules/*/types.ts` placeholder comments as modules are implemented
45
+
46
+ ---
47
+
48
+ ## Phase 2 — Core Data Layer
49
+
50
+ **Goal:** The SDK can connect to Brainy via all four transports and expose the full
51
+ `sdk.brainy.*`, `sdk.vfs.*`, `sdk.versions.*` API surface. Server mode can create and
52
+ pool Brainy instances with all three strategies.
53
+
54
+ ### 2a. Transport Layer (`src/transports/`)
55
+
56
+ **Source:** `/media/dpsifr/storage/home/Projects/brainy-client/src/transports/`
57
+
58
+ Four files to create:
59
+
60
+ #### `src/transports/transport.ts` — Shared interface
61
+ ```typescript
62
+ interface Transport {
63
+ call(method: string, args: unknown[]): Promise<unknown>
64
+ onEvent(handler: (event: BrainyChangeEvent) => void): void
65
+ offEvent(handler: (event: BrainyChangeEvent) => void): void
66
+ isAlive(): boolean
67
+ close(): Promise<void>
68
+ }
69
+ ```
70
+
71
+ #### `src/transports/local.ts` — In-process, zero overhead
72
+ - Wraps a native Brainy instance
73
+ - Resolves dot-separated method names (e.g. `'vfs.readdir'`) by property traversal
74
+ - `call()` directly invokes `target[method].apply(target, args)`
75
+ - `onEvent`/`offEvent` are no-ops (in-process changes are synchronous)
76
+ - `isAlive()` always true, `close()` is a no-op
77
+ - **Migrate directly from brainy-client local.ts — minimal changes**
78
+
79
+ #### `src/transports/http.ts` — JSON RPC over HTTP
80
+ - POSTs to `{baseUrl}/api/brainy/rpc`
81
+ - Request: `{ method: string, args: unknown[] }` — plain JSON
82
+ - Response: `{ result: unknown }` or `{ error: { code: string, message: string } }`
83
+ - Auth: `Authorization: Bearer <token>` or `credentials: 'include'` (session cookies)
84
+ - 30s timeout via AbortController
85
+ - Error mapping: 401 → `SDKAuthError`, 403 → `SDKForbiddenError`, timeout → `SDKTimeoutError`
86
+ - No push events — stateless
87
+ - **Migrate directly from brainy-client http.ts — rename error classes**
88
+
89
+ #### `src/transports/ws.ts` — MessagePack binary RPC + change push
90
+ - Binary MessagePack via `@msgpack/msgpack`
91
+ - Client → Server: `{ id: string, method: string, args: unknown[] }` (encoded)
92
+ - Server → Client:
93
+ - Ready: `{ type: 'ready', scope: string }`
94
+ - RPC response: `{ id: string, result?: unknown, error?: { code, message } }`
95
+ - Change push: `{ type: 'change', event: 'add'|'update'|'delete'|'relate'|'unrelate', entity?, relation? }`
96
+ - Auth: `Authorization: Bearer <token>` on upgrade request
97
+ - Scope: `?scope=<tenantSlug>` query param
98
+ - Reconnection: exponential backoff 1s → 30s, max 10 attempts; never retry on 4001/4003
99
+ - Connection states: `'disconnected' | 'connecting' | 'connected' | 'closed'`
100
+ - `connect()` resolves on `ready` message (15s timeout)
101
+ - **Migrate directly from brainy-client ws.ts — minimal changes**
102
+
103
+ #### `src/transports/sse.ts` — Server-push event stream (NEW — not in brainy-client)
104
+ - Connects to `{baseUrl}/api/workspace/events` via `EventSource`
105
+ - Listens for VFS and entity change events
106
+ - Reconnects automatically (EventSource handles this natively)
107
+ - Emits typed `DataChangeEvent` objects to registered handlers
108
+ - Does NOT support RPC (send-only from server) — complement to http transport
109
+ - Based on: Workshop's `src/lib/services/vfsEventService.ts`
110
+
111
+ ### 2b. Brainy API Proxy (`src/modules/brainy/`)
112
+
113
+ **Source:** `/media/dpsifr/storage/home/Projects/brainy-client/src/index.ts` + Brainy types
114
+
115
+ The core of the SDK. A JavaScript `Proxy` that intercepts any property access and routes
116
+ it to the active transport via `transport.call(method, args)`.
117
+
118
+ #### `src/modules/brainy/proxy.ts`
119
+ ```typescript
120
+ // Creates a recursive Proxy that maps any property chain to transport.call()
121
+ // client.find(...) → transport.call('find', [...])
122
+ // client.versions.save(...) → transport.call('versions.save', [...])
123
+ // Special root properties handled locally:
124
+ // close(), isAlive(), onDataChange(), offDataChange(), _transport, then
125
+ ```
126
+
127
+ #### `src/modules/brainy/types.ts` — Full Brainy type surface
128
+ All types imported or re-exported from `@soulcraft/brainy`:
129
+ - `Entity<T>`, `Relation<T>`, `Result<T>`, `Vector`
130
+ - `NounType`, `VerbType` (enums)
131
+ - `AddParams`, `UpdateParams`, `FindParams`, `SimilarParams`, `RelateParams`
132
+ - `GetOptions`, `GetRelationsParams`, `FindDuplicatesOptions`
133
+ - `BatchResult`, `IndexStats`, `DiagnosticsResult`, `MigrationResult`
134
+ - `BrainyChangeEvent`, `BrainyChangeEntity`, `BrainyChangeRelation` (from brainy-client)
135
+ - `SoulcraftBrainy` — the full SDK interface (extends `Pick<Brainy, all methods>` + change handlers)
136
+
137
+ Full method list (all must be proxied):
138
+ `add`, `get`, `batchGet`, `update`, `delete`, `find`, `similar`, `embed`, `embedBatch`,
139
+ `similarity`, `relate`, `unrelate`, `getRelations`, `neighbors`, `addMany`, `deleteMany`,
140
+ `updateMany`, `relateMany`, `indexStats`, `findDuplicates`, `cluster`, `defineAggregate`,
141
+ `removeAggregate`, `flush`, `migrate`, `diagnostics`, `clear`
142
+
143
+ Sub-APIs (also proxied via dot-notation):
144
+ `vfs.*`, `versions.*`, `neural()`, `counts.*`
145
+
146
+ #### `src/modules/brainy/auth.ts` — Capability tokens
147
+ Migrated from `brainy-client/src/auth.ts`:
148
+ - `createCapabilityToken(options)` — HMAC-SHA256, `<base64url(payload)>.<base64url(sig)>`
149
+ - `verifyCapabilityToken(token, secret)` — constant-time comparison, expiry check
150
+ - `CreateCapabilityTokenOptions`, `CapabilityTokenClaims` types
151
+ - Default TTL: 1 hour
152
+
153
+ #### `src/modules/brainy/errors.ts`
154
+ - `SDKError` (base)
155
+ - `SDKAuthError` (401)
156
+ - `SDKForbiddenError` (403)
157
+ - `SDKTimeoutError`
158
+ - `SDKDisconnectedError`
159
+ - `SDKRpcError` (server returned error object)
160
+
161
+ ### 2c. VFS Namespace (`src/modules/vfs/`)
162
+
163
+ **Source:** `node_modules/@soulcraft/brainy` — `brain.vfs.*` sub-API
164
+
165
+ Thin namespace that delegates to `sdk.brainy.vfs.*` (which routes through the proxy).
166
+ Exists as a first-class namespace so callers write `sdk.vfs.readFile()` not
167
+ `sdk.brainy.vfs.readFile()`.
168
+
169
+ Full method surface (from Brainy's `VirtualFileSystem`):
170
+ `readFile`, `writeFile`, `appendFile`, `unlink`, `mkdir`, `rmdir`, `readdir`, `stat`,
171
+ `exists`, `search`, `rename`, `copy`, `move`, `getDirectChildren`, `getTreeStructure`,
172
+ `getDescendants`, `getEntity`, `resolvePath`, `watch`, `getTodos`
173
+
174
+ Types to re-export: `VFSStats`, `VFSDirent`, `VFSEntity`, `VFSMetadata`
175
+
176
+ ### 2d. Versions Namespace (`src/modules/versions/`)
177
+
178
+ Same pattern as vfs — thin namespace delegating to `sdk.brainy.versions.*`.
179
+
180
+ Full method surface (from Brainy's `VersioningAPI`):
181
+ `save`, `list`, `getVersion`, `restore`, `compare`, `prune`
182
+
183
+ Types: `EntityVersion`, `VersionDiff`
184
+
185
+ ### 2e. Server Mode Instance Pool (`src/server/instance-pool.ts`)
186
+
187
+ **Source:** Workshop's `brainy/brainy-singleton.ts` + Venue's `server.ts` tenantCache
188
+
189
+ This is the most complex piece in Phase 2. Manages a pool of Brainy instances with
190
+ three strategies.
191
+
192
+ #### Configuration
193
+ ```typescript
194
+ interface InstancePoolConfig {
195
+ storage: 'filesystem' | 'mmap-filesystem'
196
+ dataPath: string
197
+ strategy: 'per-user' | 'per-tenant' | 'per-scope'
198
+ scopeKey?: (userId: string, workspaceId: string) => string
199
+ maxInstances: number // default: 200 (Workshop), 50 (Venue)
200
+ flushOnEvict: boolean // default: true
201
+ cortexActivationFile?: string // path to .soulcraft.json
202
+ }
203
+ ```
204
+
205
+ #### API
206
+ ```typescript
207
+ class BrainyInstancePool {
208
+ async forUser(userId: string, workspaceId: string): Promise<Brainy>
209
+ async forTenant(tenantSlug: string): Promise<Brainy>
210
+ async forScope(key: string, factory: () => Promise<Brainy>): Promise<Brainy>
211
+ async flush(key: string): Promise<void>
212
+ async flushAll(): Promise<void>
213
+ async shutdown(): Promise<void> // flush all + release
214
+ getStats(): { size: number, maxSize: number, keys: string[] }
215
+ }
216
+ ```
217
+
218
+ #### Implementation notes
219
+ - LRU cache with `lru-cache` package (already in dependencies)
220
+ - On eviction: call `brain.flush()` if `flushOnEvict: true`
221
+ - On `forUser` in `per-user` mode: key = `${emailHash}:${workspaceId}`
222
+ - Storage path: `{dataPath}/{emailHash}/{workspaceId}/`
223
+ - On `forTenant`: key = `tenantSlug`, path: `{dataPath}/{tenantSlug}/`
224
+ - Cortex activation: load `.soulcraft.json`, pass plugin to Brainy constructor
225
+ - First `forUser/forTenant` call initializes Brainy (25-30s cold start — normal)
226
+
227
+ ### 2f. Server RPC + WS Handler Factories (`src/server/handlers.ts`)
228
+
229
+ Migrated from `brainy-client/src/server/`:
230
+
231
+ ```typescript
232
+ // Hono-compatible HTTP RPC handler:
233
+ createBrainyHandler(config: BrainyHandlerConfig): (c: Context) => Promise<Response>
234
+
235
+ // Bun WebSocket handler:
236
+ createBrainyWsHandler(config: BrainyWsHandlerConfig): BrainyWsHandler
237
+
238
+ // SvelteKit adapter:
239
+ sveltekitBrainyHandler(config): RequestHandler
240
+ ```
241
+
242
+ Config shapes:
243
+ ```typescript
244
+ interface BrainyHandlerConfig {
245
+ getBrainy: (scope: string) => Promise<Brainy> // pool.forTenant(scope)
246
+ verifyToken: (token: string) => Promise<CapabilityTokenClaims | null>
247
+ blockedMethods?: string[] // e.g. ['clear'] — default []
248
+ }
249
+ ```
250
+
251
+ ### 2g. Tests (Phase 2)
252
+
253
+ - `tests/transports/local.test.ts` — call(), onEvent(), close()
254
+ - `tests/transports/http.test.ts` — mock fetch, JSON RPC, error mapping, auth header
255
+ - `tests/transports/ws.test.ts` — mock WebSocket, MessagePack encode/decode, change push, reconnection
256
+ - `tests/transports/sse.test.ts` — mock EventSource, event parsing, reconnect
257
+ - `tests/brainy/proxy.test.ts` — method proxying, dot-notation sub-APIs, special properties
258
+ - `tests/brainy/auth.test.ts` — createCapabilityToken, verifyCapabilityToken, expiry, timing-safe
259
+ - `tests/server/instance-pool.test.ts` — per-user, per-tenant, per-scope, LRU eviction, flush
260
+
261
+ ---
262
+
263
+ ## Phase 3 — Auth + License
264
+
265
+ **Goal:** `sdk.auth.*` and `sdk.license.*` are fully functional. Products can drop
266
+ their own auth config and use the SDK middleware directly.
267
+
268
+ ### 3a. Auth Module (`src/modules/auth/`)
269
+
270
+ **Source:** `/media/dpsifr/storage/home/Projects/auth/packages/auth/src/` +
271
+ `/media/dpsifr/storage/home/Projects/workshop/auth/better-auth.ts`
272
+
273
+ #### `src/modules/auth/types.ts`
274
+ All types from `@soulcraft/auth`:
275
+ - `PlatformRole` — `'creator' | 'viewer' | 'customer' | 'staff' | 'manager' | 'owner' | 'learner' | 'instructor'`
276
+ - `SoulcraftProduct` — `'workshop' | 'venue' | 'academy' | 'portal'`
277
+ - `SoulcraftUserFields` — `{ platformRole, emailHash }`
278
+ - `SoulcraftSessionUser` — resolved session user with all platform fields
279
+ - `SoulcraftSession` — `{ user, sessionId, expiresAt }`
280
+ - `OIDCClientConfig` — `{ idpUrl, clientId, clientSecret, redirectUri? }`
281
+ - `AuthMode` — `'oidc-client'` (always — we dropped standalone per decision 8)
282
+
283
+ #### `src/modules/auth/config.ts`
284
+ From `@soulcraft/auth/config`:
285
+ - `SOULCRAFT_USER_FIELDS` — better-auth `additionalFields` definition
286
+ - `SOULCRAFT_SESSION_CONFIG` — 30-day sessions, 24-hour rolling refresh
287
+ - `computeEmailHash(email: string): string` — SHA-256 hex of lowercased+trimmed email
288
+ - `getOIDCClientConfig(): OIDCClientConfig` — reads from env
289
+
290
+ #### `src/modules/auth/better-auth.ts`
291
+ Creates the better-auth instance for a product:
292
+ ```typescript
293
+ createProductAuth(options: {
294
+ product: SoulcraftProduct
295
+ clientId: string
296
+ clientSecret: string
297
+ dbPath?: string
298
+ defaultRole?: PlatformRole
299
+ }): BetterAuth
300
+ ```
301
+
302
+ Includes: OIDC client plugin (`genericOAuth`), PKCE, `crossSubDomainCookies`,
303
+ `additionalFields` (platformRole + emailHash), `databaseHooks.user.create.before`
304
+ (computes emailHash, sets defaultRole).
305
+
306
+ #### `src/modules/auth/middleware.ts`
307
+ ```typescript
308
+ // Hono middleware:
309
+ createAuthMiddleware(auth: BetterAuth): MiddlewareHandler
310
+ requireAuth(auth: BetterAuth): MiddlewareHandler
311
+ optionalAuth(auth: BetterAuth): MiddlewareHandler
312
+
313
+ // SvelteKit handle:
314
+ createAuthHandle(auth: BetterAuth): Handle
315
+
316
+ // Session proxy (for products verifying sessions against auth.soulcraft.com):
317
+ createRemoteSessionVerifier(options: {
318
+ idpUrl: string
319
+ cacheTtlMs?: number // default 30 000
320
+ cacheMax?: number // default 500
321
+ }): (cookieHeader: string) => Promise<SoulcraftSession | null>
322
+ ```
323
+
324
+ #### `src/modules/auth/backchannel.ts`
325
+ ```typescript
326
+ // Hono route handler for POST /api/auth/backchannel-logout:
327
+ createBackchannelLogoutHandler(options: {
328
+ auth: BetterAuth
329
+ clientSecret: string
330
+ idpUrl: string
331
+ clientId: string
332
+ }): (c: Context) => Promise<Response>
333
+ ```
334
+
335
+ Implements the full verification flow: parse logout_token, verify HS256 JWT,
336
+ validate iss/aud/events claims, delete all sessions for the subject user.
337
+
338
+ ### 3b. License Module (`src/modules/license/`)
339
+
340
+ **Source:** `/media/dpsifr/storage/home/Projects/workshop/brainy/usage-tracking.ts` +
341
+ `brainy/config-service.ts` + `brainy/billing-provider.ts` + `brainy/config-defaults.ts`
342
+
343
+ #### `src/modules/license/cortex.ts`
344
+ ```typescript
345
+ // Activate Cortex from .soulcraft.json activation code:
346
+ activateCortex(options: {
347
+ activationFile?: string // default './.soulcraft.json'
348
+ portalUrl?: string // default 'https://soulcraft.com'
349
+ }): Promise<CortexPlugin>
350
+
351
+ // Background heartbeat:
352
+ startHeartbeat(plugin: CortexPlugin, intervalMs?: number): () => void // returns stop fn
353
+ ```
354
+
355
+ #### `src/modules/license/plan.ts`
356
+ ```typescript
357
+ // Plan verification via Portal:
358
+ getLicensePlan(userId: string): Promise<LicensePlan>
359
+ // LicensePlan: { plan: 'free'|'standard'|'pro', status: 'active'|'expired'|'cancelled',
360
+ // features: { cortex: boolean, ai: { credits: number } } }
361
+
362
+ isLicenseActive(userId: string): Promise<boolean>
363
+ getLicenseFeatures(userId: string): Promise<LicenseFeatures>
364
+ ```
365
+
366
+ #### `src/modules/license/credits.ts`
367
+ ```typescript
368
+ // AI usage metering (migrated from usage-tracking.ts):
369
+ checkAICredits(userId: string): Promise<number> // remaining credits
370
+ consumeAICredit(userId: string, cost: { inputTokens: number, outputTokens: number }): Promise<void>
371
+ canAccessAI(userId: string): Promise<boolean>
372
+ getModelForUser(userId: string): Promise<string> // platform model or BYOK
373
+ recordMessageUsage(userId: string, usage: UsageRecord): Promise<void>
374
+ getUsageStatus(userId: string): Promise<UsageStatus>
375
+ ```
376
+
377
+ #### `src/modules/license/byok.ts`
378
+ ```typescript
379
+ // BYOK (Bring Your Own Key) API key management:
380
+ validateBYOK(apiKey: string): Promise<boolean>
381
+ getAIProvider(userId: string): Promise<AIProviderConfig>
382
+ // AIProviderConfig: { apiKey: string, source: 'platform' | 'byok' }
383
+ ```
384
+
385
+ ### 3c. Tests (Phase 3)
386
+
387
+ - `tests/auth/config.test.ts` — computeEmailHash, SOULCRAFT_SESSION_CONFIG
388
+ - `tests/auth/middleware.test.ts` — requireAuth 401, optionalAuth null, session extraction
389
+ - `tests/auth/backchannel.test.ts` — JWT verification, claims validation, session deletion
390
+ - `tests/auth/remote-session.test.ts` — LRU cache, TTL, proxy to IdP
391
+ - `tests/license/cortex.test.ts` — activation file parsing, mock Portal exchange
392
+ - `tests/license/credits.test.ts` — quota check, credit consumption, usage recording
393
+ - `tests/license/byok.test.ts` — key validation, provider selection
394
+
395
+ ---
396
+
397
+ ## Phase 4 — AI + Events + Skills + Kits + Formats
398
+
399
+ **Goal:** The full AI integration, event bus, skill system, kit loader, and Soulcraft
400
+ format types are available in the SDK.
401
+
402
+ ### 4a. AI Module (`src/modules/ai/`)
403
+
404
+ **Source:** `/media/dpsifr/storage/home/Projects/workshop/chat/`
405
+
406
+ #### `src/modules/ai/models.ts`
407
+ From `chat/models.ts`:
408
+ - `MODEL_TIERS` — `{ planner: 'claude-opus-4-6', triage: 'claude-sonnet-4-6', executor: 'claude-haiku-4-5-20251001', fallback: 'claude-haiku-4-5-20251001' }`
409
+ - `estimateCost(inputTokens, outputTokens, model)` → `number`
410
+ - `formatCost(cost: number)` → `string`
411
+ - `ModelRole` type
412
+
413
+ #### `src/modules/ai/delegator.ts`
414
+ From `chat/delegator.ts` + `chat/executor.ts` + `chat/planner.ts`:
415
+ - `createDelegator(options)` — tiered model routing (Sonnet triage → Haiku execute → Sonnet synthesize)
416
+ - `TriageDecision` — `'direct' | 'delegate_tasks' | 'recommend_plan'`
417
+ - Tool execution with auto-escalation
418
+
419
+ #### `src/modules/ai/tools.ts`
420
+ From `chat/tools.ts`:
421
+ - `ChatContext` type — `{ mode: 'file' | 'entity' | 'multi' | 'none', ... }`
422
+ - `KitAIContext` type — AI persona + domain knowledge + workflows + graph guidance
423
+ - `generateToolDefinitions(context: ChatContext)` — creates Anthropic tool definitions
424
+ - All tool definition schemas (Workshop-specific tools stay in Workshop)
425
+
426
+ #### `src/modules/ai/ui-actions.ts`
427
+ From `chat/ui-actions.ts`:
428
+ - `UI_ACTIONS` registry — all UI-controllable actions
429
+ - `generateUIToolDefinitions()` — auto-generates Claude tool definitions from registry
430
+ - `isUIAction(name)`, `getUIAction(name)` helpers
431
+ - `UIAction`, `UIActionParam` types
432
+
433
+ #### `src/modules/ai/briggy.ts`
434
+ From `routes/briggy.ts`:
435
+ - `BriggyPersona` type — name, description, systemPrompt, voice, etc.
436
+ - `createBriggyHandler(options)` — Hono route handler factory for `/api/briggy/*`
437
+ - `BriggyGenerateRequest`, `BriggyChatRequest`, `BriggyResponse` types
438
+ - Max tokens: 1024 default, 2048 ceiling
439
+ - Stateless generate + session-based chat endpoints
440
+
441
+ ### 4b. Events Module (`src/modules/events/`)
442
+
443
+ **Source:** `/media/dpsifr/storage/home/Projects/workshop/events/data-change-emitter.ts`
444
+
445
+ ```typescript
446
+ // All 30+ event types:
447
+ type DataChangeType =
448
+ | 'vfs_write' | 'vfs_edit' | 'vfs_delete' | 'vfs_mkdir' | 'vfs_rename'
449
+ | 'vfs_copy' | 'vfs_upload' | 'vfs_bulk_write'
450
+ | 'entity_create' | 'entity_created' | 'entity_update' | 'entity_updated'
451
+ | 'entity_delete' | 'relationship_created' | 'relation_create' | 'relation_delete'
452
+ | 'activity_message_new' | 'activity_tool_new' | 'activity_task_new'
453
+ | 'activity_note_new' | 'activity_conversation_new'
454
+ | 'file_deleted' | 'directory_deleted' | 'file_renamed' | 'file_copied'
455
+ | 'directory_created' | 'data_imported'
456
+
457
+ interface DataChangeEvent {
458
+ type: DataChangeType
459
+ timestamp: number
460
+ userId: string
461
+ workspaceId?: string
462
+ data: { path?: string; id?: string; nounType?: string; [key: string]: unknown }
463
+ }
464
+
465
+ // Singleton emitter (server-side):
466
+ class PlatformEventBus extends EventEmitter {
467
+ emitChange(event: DataChangeEvent): void
468
+ onDataChange(handler: (event: DataChangeEvent) => void): void
469
+ offDataChange(handler: (event: DataChangeEvent) => void): void
470
+ }
471
+
472
+ createEventBus(): PlatformEventBus
473
+ ```
474
+
475
+ SSE stream factory (for Hono endpoints):
476
+ ```typescript
477
+ createSSEStream(options: {
478
+ bus: PlatformEventBus
479
+ filter: (event: DataChangeEvent) => boolean
480
+ heartbeatMs?: number // default 30 000
481
+ }): (c: Context) => Response
482
+ ```
483
+
484
+ ### 4c. Skills Module (`src/modules/skills/`)
485
+
486
+ **Source:** `/media/dpsifr/storage/home/Projects/workshop/brainy/skill-service.ts` +
487
+ `brainy/bundled-skills.ts`
488
+
489
+ ```typescript
490
+ // Skill metadata (from VFS frontmatter):
491
+ interface SkillMetadata {
492
+ id: string
493
+ name: string
494
+ scope: 'universal' | string // string = kitId
495
+ userInvocable: boolean
496
+ disableModelInvocation?: boolean
497
+ allowedTools?: string[]
498
+ argumentHint?: string
499
+ }
500
+
501
+ class SkillService {
502
+ constructor(private vfs: VfsModule) {}
503
+ listSkills(): Promise<SkillMetadata[]>
504
+ getActiveSkills(): Promise<SkillMetadata[]>
505
+ getUserInvocableSkills(): Promise<SkillMetadata[]>
506
+ getSkillContent(id: string): Promise<string> // the markdown content
507
+ createSkill(meta: SkillMetadata, content: string): Promise<void>
508
+ updateSkill(id: string, updates: Partial<SkillMetadata>, content?: string): Promise<void>
509
+ deleteSkill(id: string): Promise<void>
510
+ toggleSkill(id: string, enabled: boolean): Promise<void>
511
+ }
512
+
513
+ // Bundled skills (pre-packaged with SDK):
514
+ listBundledSkills(): BundledSkill[]
515
+ installBundledSkill(skillId: string, vfs: VfsModule): Promise<void>
516
+ installAllBundledSkills(vfs: VfsModule): Promise<void>
517
+ getMissingBundledSkills(vfs: VfsModule): Promise<BundledSkill[]>
518
+ ```
519
+
520
+ ### 4d. Kits Module (`src/modules/kits/`)
521
+
522
+ **Source:** `/media/dpsifr/storage/home/Projects/workshop/src/lib/templates/loader.ts` +
523
+ `@soulcraft/kits` package
524
+
525
+ ```typescript
526
+ // Kit loading and initialization:
527
+ loadKit(kitId: string): Promise<SoulcraftKitConfig>
528
+ listKits(): Promise<SoulcraftKitConfig[]>
529
+ applyKit(kitId: string, brainy: SoulcraftBrainy, vfs: VfsModule): Promise<void>
530
+
531
+ // Types (re-exported from @soulcraft/kit-schema):
532
+ // SoulcraftKitConfig, WorkshopConfig, WorkshopWorkbench, WorkshopStarterFile,
533
+ // SharedDataModel, AcademyKitBlock, etc.
534
+ ```
535
+
536
+ ### 4e. Formats Module (`src/modules/formats/`)
537
+
538
+ **Source:** `/media/dpsifr/storage/home/Projects/workshop/packages/views/src/shared/types.ts` +
539
+ `src/lib/types/richDocument.ts`
540
+
541
+ Centralizes all Soulcraft portable content format types:
542
+
543
+ ```typescript
544
+ // Already exists in @soulcraft/views — migrate here as canonical home:
545
+ export type { WvizDocument, WvizEntity, WvizEdge, WvizViewType } from './wviz.js'
546
+
547
+ // WDOC (Workshop Document — TipTap/ProseMirror JSON):
548
+ export type { WdocDocument, WdocNode, WdocMark } from './wdoc.js'
549
+
550
+ // WSLIDE (Workshop Slide):
551
+ export type { WslideDocument, WslideSlide, WslideBackground, WslideTransition } from './wslide.js'
552
+
553
+ // WQUIZ (Workshop Quiz):
554
+ export type { WquizDocument, WquizQuestion, WquizAnswer } from './wquiz.js'
555
+
556
+ // Common:
557
+ export type { SoulcraftFormat } from './common.js'
558
+ // SoulcraftFormat = 'wviz' | 'wdoc' | 'wslide' | 'wquiz' | 'wform'
559
+ ```
560
+
561
+ ---
562
+
563
+ ## Phase 5 — Billing + Notifications
564
+
565
+ ### 5a. Billing Module (`src/modules/billing/`)
566
+
567
+ **Source:** `/media/dpsifr/storage/home/Projects/workshop/brainy/usage-tracking.ts` +
568
+ `brainy/billing-provider.ts` + `brainy/config-service.ts` + `brainy/config-defaults.ts`
569
+
570
+ Note: billing overlaps with license/credits (Phase 3). Phase 3 covers AI credit
571
+ metering. Phase 5 covers the full Stripe subscription + top-up flow.
572
+
573
+ ```typescript
574
+ // Usage tracking (complement to license/credits):
575
+ getCurrentPeriod(): string // 'YYYY-MM'
576
+ getPeriodEnd(userId: string): Promise<Date>
577
+ checkUsageLimits(usage: UsageData, limit: number): LimitCheckResult
578
+
579
+ // Stripe subscription management:
580
+ getSubscriptionStatus(userId: string): Promise<SubscriptionData>
581
+ subscribeToPlan(userId: string, priceId: string): Promise<{ url: string }>
582
+ cancelSubscription(userId: string): Promise<void>
583
+ getPortalUrl(userId: string): Promise<string>
584
+
585
+ // Top-up credits:
586
+ purchaseTopUp(userId: string, amount: number): Promise<void>
587
+ getTopUpBalance(userId: string): Promise<number>
588
+ addTopUp(userId: string, amount: number): Promise<void>
589
+
590
+ // Config (plans, pricing — from Firestore or defaults):
591
+ getPlatformConfig(): Promise<PlatformConfig>
592
+ getModelPricing(model: string): Promise<ModelPricing>
593
+
594
+ // Billing provider (Firestore vs local dev mode):
595
+ createBillingProvider(mode: 'firestore' | 'local'): BillingProvider
596
+ ```
597
+
598
+ ### 5b. Notifications Module (`src/modules/notifications/`)
599
+
600
+ **Source:** `/home/dpsifr/Projects/venue/apps/web/src/lib/server/notifications/`
601
+
602
+ ```typescript
603
+ // Provider-agnostic interface:
604
+ interface NotificationSender {
605
+ send(notification: Notification): Promise<NotificationResult>
606
+ }
607
+
608
+ type Notification =
609
+ | { channel: 'email'; to: string; subject: string; html: string; text: string }
610
+ | { channel: 'sms'; to: string; body: string }
611
+
612
+ interface NotificationResult {
613
+ success: boolean
614
+ messageId?: string
615
+ error?: string
616
+ }
617
+
618
+ // Factory — auto-selects provider from env:
619
+ getNotificationSender(): NotificationSender
620
+ // - POSTMARK_API_KEY set → PostmarkSender
621
+ // - TWILIO_ACCOUNT_SID set → TwilioSender
622
+ // - neither → DevConsoleSender (logs to console)
623
+
624
+ // Individual providers (for explicit use):
625
+ createPostmarkSender(apiKey: string): NotificationSender
626
+ createTwilioSender(config: TwilioConfig): NotificationSender
627
+ createDevSender(): NotificationSender
628
+
629
+ // Template helpers (Venue's booking templates become generic):
630
+ renderEmailTemplate(template: string, data: Record<string, unknown>): string
631
+ ```
632
+
633
+ ---
634
+
635
+ ## Phase 6 — npm Publish + Migrate All Products
636
+
637
+ ### 6a. Pre-publish checklist
638
+ - [ ] All tests passing (`bun test`)
639
+ - [ ] TypeScript builds cleanly (`bun run check`)
640
+ - [ ] No stubs, no TODOs, no `as any`
641
+ - [ ] JSDoc on every exported symbol
642
+ - [ ] Module-level `@module` block on every file
643
+ - [ ] `package.json` version set to `1.0.0`
644
+ - [ ] `README.md` written with usage examples for all three entry points
645
+
646
+ ### 6b. Publish
647
+ ```bash
648
+ npm publish --access restricted
649
+ ```
650
+
651
+ ### 6c. Migrate Workshop
652
+ Files to update in `/media/dpsifr/storage/home/Projects/workshop/`:
653
+ 1. `package.json` — add `@soulcraft/sdk`, remove `@soulcraft/brainy-client`, `@soulcraft/auth`
654
+ 2. `build.sh` — add `sdk` to bundle loop, remove `brainy-client` and `auth`
655
+ 3. `auth/better-auth.ts` — replace with `createProductAuth('workshop', ...)` from SDK
656
+ 4. `server-hono.ts` — replace brainy-client handler factories with SDK handlers
657
+ 5. `brainy/brainy-singleton.ts` — replace with `sdk.server.instancePool`
658
+ 6. `brainy/remote-brainy.ts` — replace with SDK `WsTransport`
659
+ 7. `brainy/usage-tracking.ts` — replace with `sdk.license.credits.*` + `sdk.billing.*`
660
+ 8. All `import { ... } from '@soulcraft/auth'` → `from '@soulcraft/sdk'`
661
+ 9. All `import { createBrainyClient } from '@soulcraft/brainy-client'` → `from '@soulcraft/sdk/client'`
662
+
663
+ ### 6d. Migrate Venue
664
+ Files to update in `/home/dpsifr/Projects/venue/`:
665
+ 1. `apps/web/package.json` — same package swap
666
+ 2. `apps/web/server.ts` — replace brainy-client WS handler with SDK
667
+ 3. `apps/web/src/lib/server/auth.ts` — replace with `createProductAuth('venue', ...)`
668
+ 4. `apps/web/src/routes/api/brainy/rpc/+server.ts` — replace with SDK HTTP handler
669
+ 5. All notification code → `sdk.notifications.*`
670
+
671
+ ### 6e. Migrate Academy
672
+ Files to update in `/media/dpsifr/storage/home/Projects/academy/`:
673
+ 1. `package.json`, `auth/better-auth.ts`, `server.ts` — same patterns as Workshop
674
+
675
+ ### 6f. Post-migration
676
+ - Deprecate `@soulcraft/brainy-client` on npmjs.com (`npm deprecate`)
677
+ - Deprecate `@soulcraft/auth` on npmjs.com
678
+ - Update `PLATFORM-HANDOFF.md` thread to RESOLVED
679
+
680
+ ---
681
+
682
+ ## Key Reference Files (Read Before Starting Each Phase)
683
+
684
+ A new session implementing any phase should read these files FIRST:
685
+
686
+ **Phase 2:**
687
+ - `/media/dpsifr/storage/home/Projects/brainy-client/src/` — all transport implementations
688
+ - `/media/dpsifr/storage/home/Projects/brainy-client/src/server/` — handler factories
689
+ - `/media/dpsifr/storage/home/Projects/workshop/brainy/brainy-singleton.ts` — instance pool pattern
690
+ - `node_modules/@soulcraft/brainy/dist/brainy.d.ts` — full Brainy type surface
691
+
692
+ **Phase 3:**
693
+ - `/media/dpsifr/storage/home/Projects/auth/packages/auth/src/` — shared auth types + config
694
+ - `/media/dpsifr/storage/home/Projects/workshop/auth/better-auth.ts` — product auth pattern
695
+ - `/media/dpsifr/storage/home/Projects/workshop/brainy/usage-tracking.ts` — credit metering
696
+
697
+ **Phase 4:**
698
+ - `/media/dpsifr/storage/home/Projects/workshop/chat/` — entire directory
699
+ - `/media/dpsifr/storage/home/Projects/workshop/events/data-change-emitter.ts`
700
+ - `/media/dpsifr/storage/home/Projects/workshop/brainy/skill-service.ts`
701
+
702
+ **Phase 5:**
703
+ - `/media/dpsifr/storage/home/Projects/workshop/brainy/billing-provider.ts`
704
+ - `/home/dpsifr/Projects/venue/apps/web/src/lib/server/notifications/`
705
+
706
+ **Phase 6:**
707
+ - Workshop's `build.sh` — bundle loop pattern
708
+ - All `import` statements in Workshop/Venue/Academy server files