@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
package/docs/USAGE.md ADDED
@@ -0,0 +1,646 @@
1
+ # @soulcraft/sdk — Usage Guide
2
+
3
+ This guide is for engineers, AI assistants, kit developers, and anyone building on
4
+ the Soulcraft platform. It covers every implemented module with working examples.
5
+
6
+ **Three rules to remember:**
7
+ 1. Server code (Hono, SvelteKit routes, Bun backends) imports from `@soulcraft/sdk/server`
8
+ 2. Browser code (kit apps, Svelte components) imports from `@soulcraft/sdk/client`
9
+ 3. Shared types import from `@soulcraft/sdk`
10
+
11
+ ---
12
+
13
+ ## Table of Contents
14
+
15
+ 1. [Installation](#installation)
16
+ 2. [Server Setup — createSDK](#server-setup--createsdk)
17
+ 3. [Client Setup — Transports](#client-setup--transports)
18
+ 4. [sdk.brainy — Graph Data](#sdkbrainy--graph-data)
19
+ 5. [sdk.vfs — Virtual Filesystem](#sdkvfs--virtual-filesystem)
20
+ 6. [sdk.auth — Authentication & Tokens](#sdkauth--authentication--tokens)
21
+ 7. [sdk.ai — Claude AI Integration](#sdkai--claude-ai-integration)
22
+ 8. [sdk.events — Platform Event Bus](#sdkevents--platform-event-bus)
23
+ 9. [sdk.skills — Skill System](#sdkskills--skill-system)
24
+ 10. [BrainyInstancePool — Instance Lifecycle](#brainyinstancepool--instance-lifecycle)
25
+ 11. [Capability Tokens — Cross-Product Access](#capability-tokens--cross-product-access)
26
+ 12. [Auth Middleware](#auth-middleware)
27
+ 13. [For AI Assistants](#for-ai-assistants)
28
+
29
+ ---
30
+
31
+ ## Installation
32
+
33
+ ```bash
34
+ # Requires npm login to the @soulcraft org:
35
+ npm login
36
+ bun add @soulcraft/sdk
37
+ ```
38
+
39
+ Peer dependencies — must already be in your project:
40
+ ```bash
41
+ bun add @soulcraft/brainy @soulcraft/cortex
42
+ ```
43
+
44
+ **GCE VM deploys:** The SDK cannot be installed on the VM without an npm auth token.
45
+ Bundle it from local `node_modules` in your `build.sh`:
46
+ ```bash
47
+ cp -r node_modules/@soulcraft/sdk build/node_modules/@soulcraft/sdk
48
+ ```
49
+
50
+ ---
51
+
52
+ ## Server Setup — createSDK
53
+
54
+ Use `createSDK` in any request handler that has already resolved a Brainy instance.
55
+ `createSDK` is a thin wrapper — create one per request, not one per process.
56
+
57
+ ```typescript
58
+ import { BrainyInstancePool, createSDK } from '@soulcraft/sdk/server'
59
+
60
+ // One pool per process — module singleton
61
+ const pool = new BrainyInstancePool({
62
+ storage: process.env.STORAGE_TYPE === 'mmap-filesystem' ? 'mmap-filesystem' : 'filesystem',
63
+ dataPath: process.env.BRAINY_DATA_PATH ?? './brainy-data',
64
+ strategy: 'per-user',
65
+ maxInstances: 200,
66
+ flushOnEvict: true,
67
+ })
68
+
69
+ // In a request handler:
70
+ app.get('/api/data', requireAuth, async (c) => {
71
+ const user = c.get('user')!
72
+ const brain = await pool.forUser(user.emailHash, 'main')
73
+ const sdk = createSDK({ brain })
74
+
75
+ // All namespaces now available:
76
+ const items = await sdk.brainy.find({ query: 'inventory items' })
77
+ const readme = await sdk.vfs.readFile('/projects/README.md')
78
+ const skill = await sdk.skills.load('inventory-health', 'wicks-and-whiskers')
79
+
80
+ return c.json({ items })
81
+ })
82
+ ```
83
+
84
+ ### Instance Strategies
85
+
86
+ | Strategy | When to use | Pool method |
87
+ |----------|-------------|-------------|
88
+ | `per-user` | One Brainy per user (Workshop) | `pool.forUser(emailHash, workspaceId)` |
89
+ | `per-tenant` | One Brainy per org/location (Venue) | `pool.forTenant(slug)` |
90
+ | `per-scope` | Custom key — Academy or bespoke | `pool.forScope(key, factory)` |
91
+
92
+ ```typescript
93
+ // per-scope example (Academy — content vs learner brains):
94
+ const contentPool = new BrainyInstancePool({ strategy: 'per-scope', ... })
95
+ const brain = await contentPool.forScope(
96
+ `content:${workspaceId}`,
97
+ () => initBrainy(`content/${workspaceId}`, storagePath)
98
+ )
99
+ ```
100
+
101
+ ---
102
+
103
+ ## Client Setup — Transports
104
+
105
+ Used in browser kit apps and remote connections.
106
+
107
+ ```typescript
108
+ import { createBrainyProxy, HttpTransport, WsTransport } from '@soulcraft/sdk/client'
109
+
110
+ // HTTP transport — stateless, for simple kit apps:
111
+ const transport = new HttpTransport('https://workshop.soulcraft.com', undefined, true)
112
+ const brainy = createBrainyProxy(transport)
113
+
114
+ const results = await brainy.find({ query: 'inventory' })
115
+
116
+ // WebSocket transport — real-time, change push events:
117
+ const wsTransport = new WsTransport(
118
+ 'wss://venue.soulcraft.com/api/brainy/ws',
119
+ capabilityToken,
120
+ 'wicks-and-whiskers'
121
+ )
122
+ await wsTransport.connect()
123
+ const brainy = createBrainyProxy(wsTransport)
124
+
125
+ brainy.onDataChange((event) => {
126
+ console.log('Live change:', event)
127
+ })
128
+ ```
129
+
130
+ ### Which transport to use?
131
+
132
+ | Scenario | Transport | Why |
133
+ |----------|-----------|-----|
134
+ | Kit app reading/writing data | `http` | Stateless, simple, no connection overhead |
135
+ | Real-time sync + change events | `ws` | Bidirectional, MessagePack, change push |
136
+ | Live update notifications only | `sse` | Lightweight server-push, auto-reconnects |
137
+ | Server handler (same process) | `local` (via `createSDK`) | Zero overhead |
138
+
139
+ ---
140
+
141
+ ## sdk.brainy — Graph Data
142
+
143
+ The full Brainy graph API. Works identically in server mode (via `createSDK`) and client mode (via transport proxy).
144
+
145
+ ```typescript
146
+ import { NounType, VerbType } from '@soulcraft/brainy'
147
+
148
+ // Add entities
149
+ await sdk.brainy.add({
150
+ id: 'product-lavender',
151
+ type: 'Product',
152
+ metadata: { name: 'Lavender Soy Candle', price: 28, stock: 45 },
153
+ })
154
+
155
+ // Find with semantic search
156
+ const results = await sdk.brainy.find({
157
+ query: 'soy candle lavender scent',
158
+ type: 'Product',
159
+ limit: 10,
160
+ })
161
+
162
+ // Find with filter
163
+ const inStock = await sdk.brainy.find({
164
+ query: 'candles',
165
+ filter: { stock: { $gt: 0 } },
166
+ })
167
+
168
+ // Get by ID
169
+ const product = await sdk.brainy.get('product-lavender')
170
+
171
+ // Update
172
+ await sdk.brainy.update({
173
+ id: 'product-lavender',
174
+ metadata: { stock: 40 },
175
+ })
176
+
177
+ // Delete
178
+ await sdk.brainy.delete('product-lavender')
179
+
180
+ // Relationships
181
+ await sdk.brainy.relate({
182
+ from: 'product-lavender',
183
+ to: 'category-candles',
184
+ type: 'BelongsTo',
185
+ })
186
+
187
+ const relations = await sdk.brainy.getRelations({ from: 'product-lavender' })
188
+ await sdk.brainy.unrelate({ from: 'product-lavender', to: 'category-candles', type: 'BelongsTo' })
189
+
190
+ // Real-time change events (local transport: use sdk.events; WS transport: use onDataChange)
191
+ sdk.brainy.onDataChange((event) => {
192
+ // event: { type: 'change', event: 'add'|'update'|'delete'|'relate'|'unrelate', entity?, relation? }
193
+ console.log('Brainy change:', event.event, event.entity?.id)
194
+ })
195
+ ```
196
+
197
+ ---
198
+
199
+ ## sdk.vfs — Virtual Filesystem
200
+
201
+ VFS stores documents, code, slides, and any file content alongside Brainy graph data.
202
+ Accessed via `sdk.vfs.*` — the same API as `brain.vfs.*` but through the SDK proxy.
203
+
204
+ ```typescript
205
+ // Write files
206
+ await sdk.vfs.writeFile('/projects/my-project/README.md', '# My Project\n...')
207
+ await sdk.vfs.writeFile('/notes/todo.txt', 'Buy more candles')
208
+
209
+ // Read files — returns Buffer; convert to string for text:
210
+ const buffer = await sdk.vfs.readFile('/projects/my-project/README.md')
211
+ const content = buffer.toString('utf-8')
212
+
213
+ // List directory
214
+ const entries = await sdk.vfs.readdir('/projects/my-project')
215
+ // → ['README.md', 'src', 'package.json']
216
+
217
+ // File info
218
+ const stat = await sdk.vfs.stat('/projects/my-project/README.md')
219
+ // → { size, mtime, isDirectory, ... }
220
+
221
+ // Rename / move
222
+ await sdk.vfs.rename('/projects/old-name', '/projects/new-name')
223
+
224
+ // Delete
225
+ await sdk.vfs.unlink('/notes/todo.txt')
226
+ await sdk.vfs.rmdir('/projects/old-project', { recursive: true })
227
+
228
+ // Make directories
229
+ await sdk.vfs.mkdir('/projects/new-project', { recursive: true })
230
+ ```
231
+
232
+ ---
233
+
234
+ ## sdk.auth — Authentication & Tokens
235
+
236
+ The auth namespace handles capability tokens for cross-product server-to-server calls.
237
+ Session auth (OIDC, middleware) is configured at the product level — see [Auth Middleware](#auth-middleware).
238
+
239
+ ```typescript
240
+ import { createCapabilityToken, verifyCapabilityToken } from '@soulcraft/sdk/server'
241
+
242
+ // Or via sdk.auth (same functions):
243
+ const token = await sdk.auth.createToken({
244
+ email: user.email,
245
+ scope: 'wicks-and-whiskers',
246
+ secret: process.env.VENUE_RPC_SECRET!,
247
+ // ttlMs: 3_600_000 // default: 1 hour
248
+ })
249
+
250
+ // On the receiving server:
251
+ const claims = await sdk.auth.verifyToken(token, process.env.VENUE_RPC_SECRET!)
252
+ if (!claims) return c.json({ error: 'Unauthorized' }, 401)
253
+ console.log(claims.email, claims.scope, claims.exp)
254
+ ```
255
+
256
+ Token format: `<base64url(JSON payload)>.<base64url(HMAC-SHA256 signature)>`
257
+
258
+ ---
259
+
260
+ ## sdk.ai — Claude AI Integration
261
+
262
+ Wraps the Anthropic Claude API with Soulcraft defaults. Requires `ANTHROPIC_API_KEY`.
263
+
264
+ ```typescript
265
+ import { AI_MODELS } from '@soulcraft/sdk'
266
+
267
+ // Basic completion
268
+ const response = await sdk.ai.complete({
269
+ messages: [{ role: 'user', content: 'What candles are low in stock?' }],
270
+ systemPrompt: kit.shared.aiPersona, // from kit.json → shared.aiPersona
271
+ model: AI_MODELS.haiku, // fast + cheap for simple lookups
272
+ tools: [
273
+ {
274
+ name: 'search_inventory',
275
+ description: 'Search Brainy for inventory data',
276
+ inputSchema: {
277
+ type: 'object',
278
+ properties: { query: { type: 'string' } },
279
+ required: ['query'],
280
+ },
281
+ }
282
+ ],
283
+ })
284
+
285
+ // response.text — Claude's text reply (null if only tool calls)
286
+ // response.toolCalls — tool invocations Claude wants to make (null if none)
287
+ // response.stopReason — 'end_turn' | 'tool_use' | 'max_tokens'
288
+ // response.usage — { inputTokens, outputTokens }
289
+
290
+ // Handle tool calls:
291
+ if (response.toolCalls) {
292
+ for (const call of response.toolCalls) {
293
+ if (call.name === 'search_inventory') {
294
+ const results = await sdk.brainy.find({ query: call.input.query as string })
295
+ // Continue the conversation by appending a tool_result message
296
+ }
297
+ }
298
+ }
299
+
300
+ // Streaming — yields text, tool_use, and done events:
301
+ for await (const event of sdk.ai.stream({
302
+ messages: [{ role: 'user', content: 'Write a product description for lavender candles' }],
303
+ systemPrompt: 'You are a Wicks & Whiskers product writer.',
304
+ model: AI_MODELS.sonnet,
305
+ })) {
306
+ if (event.type === 'text') process.stdout.write(event.text)
307
+ if (event.type === 'tool_use') console.log('Tool call:', event.toolCall.name)
308
+ if (event.type === 'done') console.log('Total tokens:', event.result.usage)
309
+ }
310
+ ```
311
+
312
+ ### Model tiers
313
+
314
+ | Constant | Model ID | When to use |
315
+ |----------|----------|-------------|
316
+ | `AI_MODELS.haiku` | `claude-haiku-4-5-20251001` | Fast lookups, data extraction, simple Q&A |
317
+ | `AI_MODELS.sonnet` | `claude-sonnet-4-6` | Most kit operations, content generation, analysis |
318
+ | `AI_MODELS.opus` | `claude-opus-4-6` | Complex reasoning, long-form synthesis |
319
+
320
+ ---
321
+
322
+ ## sdk.events — Platform Event Bus
323
+
324
+ A typed EventEmitter for coordinating platform events across modules and kit code.
325
+ Events are local to the SDK instance — they do not cross process boundaries.
326
+
327
+ ```typescript
328
+ // Subscribe to built-in platform events:
329
+ sdk.events.on('brainy:change', (event) => {
330
+ if (event.event === 'add' && event.entity?.nounType === 'Product') {
331
+ updateInventoryCache(event.entity)
332
+ }
333
+ })
334
+
335
+ sdk.events.on('vfs:write', ({ path, content }) => {
336
+ if (path.endsWith('.md')) {
337
+ broadcastDocumentUpdate(path, content)
338
+ }
339
+ })
340
+
341
+ sdk.events.on('vfs:delete', ({ path }) => {
342
+ invalidateCache(path)
343
+ })
344
+
345
+ sdk.events.on('vfs:rename', ({ from, to }) => {
346
+ updateFileTree(from, to)
347
+ })
348
+
349
+ // Emit custom application events:
350
+ sdk.events.emit('kit:session-completed', {
351
+ userId: user.id,
352
+ sessionId,
353
+ kitId: 'wicks-and-whiskers',
354
+ })
355
+
356
+ // Remove a listener:
357
+ const handler = (event) => { ... }
358
+ sdk.events.on('brainy:change', handler)
359
+ sdk.events.off('brainy:change', handler)
360
+ ```
361
+
362
+ ### Adding custom event types
363
+
364
+ Extend `SoulcraftEventMap` with declaration merging for type-safe custom events:
365
+
366
+ ```typescript
367
+ // In your project (e.g. types.d.ts):
368
+ declare module '@soulcraft/sdk' {
369
+ interface SoulcraftEventMap {
370
+ 'kit:session-completed': { userId: string; sessionId: string; kitId: string }
371
+ 'venue:booking-created': { bookingId: string; tenantId: string }
372
+ 'academy:enrollment': { userId: string; courseId: string; workspaceId: string }
373
+ }
374
+ }
375
+ ```
376
+
377
+ After this declaration, `sdk.events.on('kit:session-completed', ...)` is fully typed.
378
+
379
+ ---
380
+
381
+ ## sdk.skills — Skill System
382
+
383
+ Skills are SKILL.md files that define an AI persona, capabilities, and workflow for
384
+ a kit domain. The skills module loads from VFS first, then falls back to the bundled
385
+ `@soulcraft/kits` registry.
386
+
387
+ ```typescript
388
+ // Load a specific skill (VFS first, then bundled):
389
+ const skill = await sdk.skills.load('inventory-health', 'wicks-and-whiskers')
390
+ if (skill) {
391
+ console.log(skill.id) // 'inventory-health'
392
+ console.log(skill.kitId) // 'wicks-and-whiskers'
393
+ console.log(skill.source) // 'vfs' or 'bundled'
394
+ console.log(skill.content) // full SKILL.md content string
395
+ }
396
+
397
+ // List all skills for a kit:
398
+ const skills = await sdk.skills.list({ kitId: 'wicks-and-whiskers' })
399
+ const systemPrompt = buildSystemPrompt(kit, skills.map(s => s.content))
400
+
401
+ // List all skills across all kits:
402
+ const allSkills = await sdk.skills.list()
403
+
404
+ // List only VFS skills (user-installed), no bundled:
405
+ const userSkills = await sdk.skills.list({
406
+ kitId: 'wicks-and-whiskers',
407
+ includeBundled: false,
408
+ })
409
+
410
+ // Install a skill into the VFS (makes it available to future load() calls):
411
+ await sdk.skills.install({
412
+ id: 'custom-flow',
413
+ kitId: 'wicks-and-whiskers',
414
+ content: `---
415
+ id: custom-flow
416
+ title: Custom Checkout Flow
417
+ ---
418
+ You are a candle shop checkout specialist...`,
419
+ })
420
+ ```
421
+
422
+ ### VFS path convention for skills
423
+
424
+ ```
425
+ /skills/{kitId}/{skillId}.md
426
+ ```
427
+
428
+ Example: `/skills/wicks-and-whiskers/inventory-health.md`
429
+
430
+ ---
431
+
432
+ ## BrainyInstancePool — Instance Lifecycle
433
+
434
+ The pool manages Brainy instances: creation, caching, LRU eviction, and graceful shutdown.
435
+
436
+ ```typescript
437
+ import { BrainyInstancePool } from '@soulcraft/sdk/server'
438
+
439
+ const pool = new BrainyInstancePool({
440
+ storage: 'mmap-filesystem', // or 'filesystem' for dev
441
+ dataPath: '/mnt/brainy-data',
442
+ strategy: 'per-user',
443
+ maxInstances: 200,
444
+ flushOnEvict: true,
445
+ onInit: async (brain, storagePath) => {
446
+ // Optional: post-init hook for migrations, integrity checks, etc.
447
+ await runMigrations(brain)
448
+ },
449
+ })
450
+
451
+ // per-user (Workshop):
452
+ const brain = await pool.forUser(user.emailHash, workspaceId)
453
+
454
+ // per-tenant (Venue):
455
+ const brain = await pool.forTenant('wicks-and-whiskers')
456
+
457
+ // per-scope (custom key — Academy, multi-branch, etc.):
458
+ const brain = await pool.forScope(
459
+ `content:${workspaceId}`,
460
+ () => initBrainy('content', storagePath)
461
+ )
462
+
463
+ // Pool stats:
464
+ const stats = pool.getStats()
465
+ // { size: 12, maxSize: 200, keys: ['abc123:ws-1', ...] }
466
+
467
+ // Graceful shutdown (flush all, clear cache):
468
+ await pool.shutdown()
469
+
470
+ // Flush and evict one instance:
471
+ await pool.flush('abc123:workspace-1')
472
+ ```
473
+
474
+ ### Concurrent init deduplication
475
+
476
+ If two requests arrive simultaneously for the same scope key while Brainy is still
477
+ initializing, the pool deduplicates — only one Brainy instance is ever created per key,
478
+ and both requests await the same initialization promise.
479
+
480
+ ---
481
+
482
+ ## Capability Tokens — Cross-Product Access
483
+
484
+ Used when a product backend (e.g. Workshop) needs to call another product's Brainy RPC
485
+ endpoint. Short-lived HMAC-SHA256 tokens replace session cookies for server-to-server calls.
486
+
487
+ ```typescript
488
+ import { createCapabilityToken, verifyCapabilityToken } from '@soulcraft/sdk/server'
489
+
490
+ // Issuing server — create a scoped token:
491
+ const token = await createCapabilityToken({
492
+ email: user.email,
493
+ scope: 'wicks-and-whiskers', // optional — restricts to one tenant
494
+ secret: process.env.VENUE_RPC_SECRET!,
495
+ ttlMs: 3_600_000, // 1 hour (default)
496
+ })
497
+
498
+ // Send token in Authorization header:
499
+ // Authorization: Bearer <token>
500
+
501
+ // Receiving server — verify in the RPC handler:
502
+ const claims = await verifyCapabilityToken(token, process.env.VENUE_RPC_SECRET!)
503
+ if (!claims) return new Response('Unauthorized', { status: 401 })
504
+
505
+ console.log(claims.email) // original user's email
506
+ console.log(claims.scope) // 'wicks-and-whiskers'
507
+ console.log(claims.exp) // expiry timestamp (ms)
508
+ ```
509
+
510
+ ---
511
+
512
+ ## Auth Middleware
513
+
514
+ The SDK provides Hono middleware that resolves better-auth sessions and injects the
515
+ typed user into request context.
516
+
517
+ ```typescript
518
+ import { betterAuth } from 'better-auth'
519
+ import {
520
+ SOULCRAFT_USER_FIELDS,
521
+ SOULCRAFT_SESSION_CONFIG,
522
+ computeEmailHash,
523
+ createAuthMiddleware,
524
+ } from '@soulcraft/sdk/server'
525
+ import type { AuthContext } from '@soulcraft/sdk/server'
526
+
527
+ // 1. Configure better-auth with Soulcraft fields:
528
+ const auth = betterAuth({
529
+ database: new Database('./auth.db'),
530
+ secret: process.env.BETTER_AUTH_SECRET!,
531
+ session: SOULCRAFT_SESSION_CONFIG, // 30-day sessions, 24h refresh
532
+ user: { additionalFields: SOULCRAFT_USER_FIELDS },
533
+ databaseHooks: {
534
+ user: {
535
+ create: {
536
+ before: async (user) => ({
537
+ data: { ...user, emailHash: computeEmailHash(user.email), platformRole: 'creator' }
538
+ })
539
+ }
540
+ }
541
+ },
542
+ })
543
+
544
+ // 2. Create middleware:
545
+ const { requireAuth, optionalAuth } = createAuthMiddleware(auth)
546
+
547
+ // 3. Use in routes:
548
+ app.all('/api/auth/*', (c) => auth.handler(c.req.raw))
549
+
550
+ app.get('/api/data', requireAuth, async (c) => {
551
+ const user = c.get('user')! // SoulcraftSessionUser — always non-null after requireAuth
552
+ // user.id, user.email, user.emailHash, user.platformRole
553
+ })
554
+
555
+ app.get('/api/public', optionalAuth, async (c) => {
556
+ const user = c.get('user') // SoulcraftSessionUser | null
557
+ })
558
+ ```
559
+
560
+ ### Auth modes
561
+
562
+ | Mode | When | How |
563
+ |------|------|-----|
564
+ | Standalone | `SOULCRAFT_IDP_URL` not set | better-auth runs locally with SQLite — local dev default |
565
+ | OIDC client | `SOULCRAFT_IDP_URL=https://auth.soulcraft.com` | Delegates all auth to the central IdP — all production deployments |
566
+
567
+ Production always uses OIDC. Standalone is the dev default — no internet connection,
568
+ no credentials, no setup required.
569
+
570
+ ### Backchannel logout (OIDC)
571
+
572
+ For products running in OIDC client mode:
573
+
574
+ ```typescript
575
+ import { createBackchannelLogoutHandler } from '@soulcraft/sdk/server'
576
+
577
+ const backchannelHandler = createBackchannelLogoutHandler({
578
+ auth,
579
+ clientSecret: process.env.SOULCRAFT_OIDC_CLIENT_SECRET!,
580
+ idpUrl: process.env.SOULCRAFT_IDP_URL!,
581
+ clientId: 'workshop',
582
+ })
583
+
584
+ app.post('/api/auth/backchannel-logout', backchannelHandler)
585
+ ```
586
+
587
+ ---
588
+
589
+ ## For AI Assistants
590
+
591
+ When helping implement Soulcraft server code, follow this checklist:
592
+
593
+ **1. Import correctly:**
594
+ ```typescript
595
+ // Server code (Hono, SvelteKit server routes, Bun scripts):
596
+ import { BrainyInstancePool, createSDK, createAuthMiddleware, computeEmailHash } from '@soulcraft/sdk/server'
597
+ import type { SoulcraftSessionUser, AuthContext } from '@soulcraft/sdk'
598
+
599
+ // Shared types only:
600
+ import { AI_MODELS } from '@soulcraft/sdk'
601
+ ```
602
+
603
+ **2. Resolve Brainy from the pool, then create SDK:**
604
+ ```typescript
605
+ const brain = await pool.forUser(user.emailHash, workspaceId) // or forTenant, forScope
606
+ const sdk = createSDK({ brain })
607
+ ```
608
+
609
+ **3. Call modules on the sdk object:**
610
+ ```typescript
611
+ await sdk.brainy.find({ query: '...' })
612
+ await sdk.vfs.readFile('/path/to/file')
613
+ await sdk.ai.complete({ messages, systemPrompt, model: AI_MODELS.haiku })
614
+ await sdk.skills.load('skill-id', 'kit-id')
615
+ sdk.events.on('brainy:change', handler)
616
+ sdk.events.emit('custom:event', payload)
617
+ ```
618
+
619
+ **4. Module implementation status:**
620
+
621
+ | Module | Status | Notes |
622
+ |--------|--------|-------|
623
+ | `sdk.brainy.*` | ✅ Implemented | Full Brainy API via proxy |
624
+ | `sdk.vfs.*` | ✅ Implemented | Brainy VFS sub-API via proxy |
625
+ | `sdk.auth.*` | ✅ Implemented | Capability tokens only; middleware is product-level |
626
+ | `sdk.ai.*` | ✅ Implemented | Claude complete() + stream() |
627
+ | `sdk.events.*` | ✅ Implemented | EventEmitter, typed, local to SDK instance |
628
+ | `sdk.skills.*` | ✅ Implemented | VFS + bundled registry fallback |
629
+ | `sdk.versions.*` | ✅ Types only | Runtime proxy works; Brainy versions API |
630
+ | `sdk.license.*` | ❌ Not yet | Will throw if accessed |
631
+ | `sdk.kits.*` | ❌ Not yet | Will throw if accessed |
632
+ | `sdk.formats.*` | ❌ Not yet | Will throw if accessed |
633
+ | `sdk.billing.*` | ❌ Not yet | Will throw if accessed |
634
+ | `sdk.notifications.*` | ❌ Not yet | Will throw if accessed |
635
+
636
+ **5. Environment variables required:**
637
+
638
+ | Variable | Module | Required in prod |
639
+ |----------|--------|-----------------|
640
+ | `ANTHROPIC_API_KEY` | `sdk.ai` | Yes |
641
+ | `SOULCRAFT_IDP_URL` | Auth (OIDC mode) | Production only |
642
+ | `SOULCRAFT_OIDC_CLIENT_ID` | Auth (OIDC mode) | If SOULCRAFT_IDP_URL is set |
643
+ | `SOULCRAFT_OIDC_CLIENT_SECRET` | Auth (OIDC mode) | If SOULCRAFT_IDP_URL is set |
644
+ | `BETTER_AUTH_SECRET` | better-auth | Yes |
645
+ | `BRAINY_DATA_PATH` | BrainyInstancePool | Yes (defaults to `./brainy-data`) |
646
+ | `STORAGE_TYPE` | BrainyInstancePool | Yes (`filesystem` or `mmap-filesystem`) |