@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.
- package/dist/client/index.d.ts +62 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +60 -0
- package/dist/client/index.js.map +1 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +17 -0
- package/dist/index.js.map +1 -0
- package/dist/modules/ai/index.d.ts +55 -0
- package/dist/modules/ai/index.d.ts.map +1 -0
- package/dist/modules/ai/index.js +263 -0
- package/dist/modules/ai/index.js.map +1 -0
- package/dist/modules/ai/types.d.ts +216 -0
- package/dist/modules/ai/types.d.ts.map +1 -0
- package/dist/modules/ai/types.js +30 -0
- package/dist/modules/ai/types.js.map +1 -0
- package/dist/modules/auth/backchannel.d.ts +85 -0
- package/dist/modules/auth/backchannel.d.ts.map +1 -0
- package/dist/modules/auth/backchannel.js +168 -0
- package/dist/modules/auth/backchannel.js.map +1 -0
- package/dist/modules/auth/config.d.ts +122 -0
- package/dist/modules/auth/config.d.ts.map +1 -0
- package/dist/modules/auth/config.js +158 -0
- package/dist/modules/auth/config.js.map +1 -0
- package/dist/modules/auth/middleware.d.ts +146 -0
- package/dist/modules/auth/middleware.d.ts.map +1 -0
- package/dist/modules/auth/middleware.js +204 -0
- package/dist/modules/auth/middleware.js.map +1 -0
- package/dist/modules/auth/types.d.ts +162 -0
- package/dist/modules/auth/types.d.ts.map +1 -0
- package/dist/modules/auth/types.js +14 -0
- package/dist/modules/auth/types.js.map +1 -0
- package/dist/modules/billing/types.d.ts +7 -0
- package/dist/modules/billing/types.d.ts.map +1 -0
- package/dist/modules/billing/types.js +7 -0
- package/dist/modules/billing/types.js.map +1 -0
- package/dist/modules/brainy/auth.d.ts +104 -0
- package/dist/modules/brainy/auth.d.ts.map +1 -0
- package/dist/modules/brainy/auth.js +144 -0
- package/dist/modules/brainy/auth.js.map +1 -0
- package/dist/modules/brainy/errors.d.ts +118 -0
- package/dist/modules/brainy/errors.d.ts.map +1 -0
- package/dist/modules/brainy/errors.js +142 -0
- package/dist/modules/brainy/errors.js.map +1 -0
- package/dist/modules/brainy/events.d.ts +63 -0
- package/dist/modules/brainy/events.d.ts.map +1 -0
- package/dist/modules/brainy/events.js +14 -0
- package/dist/modules/brainy/events.js.map +1 -0
- package/dist/modules/brainy/proxy.d.ts +48 -0
- package/dist/modules/brainy/proxy.d.ts.map +1 -0
- package/dist/modules/brainy/proxy.js +95 -0
- package/dist/modules/brainy/proxy.js.map +1 -0
- package/dist/modules/brainy/types.d.ts +83 -0
- package/dist/modules/brainy/types.d.ts.map +1 -0
- package/dist/modules/brainy/types.js +21 -0
- package/dist/modules/brainy/types.js.map +1 -0
- package/dist/modules/events/index.d.ts +41 -0
- package/dist/modules/events/index.d.ts.map +1 -0
- package/dist/modules/events/index.js +53 -0
- package/dist/modules/events/index.js.map +1 -0
- package/dist/modules/events/types.d.ts +129 -0
- package/dist/modules/events/types.d.ts.map +1 -0
- package/dist/modules/events/types.js +32 -0
- package/dist/modules/events/types.js.map +1 -0
- package/dist/modules/formats/types.d.ts +7 -0
- package/dist/modules/formats/types.d.ts.map +1 -0
- package/dist/modules/formats/types.js +7 -0
- package/dist/modules/formats/types.js.map +1 -0
- package/dist/modules/hall/types.d.ts +56 -0
- package/dist/modules/hall/types.d.ts.map +1 -0
- package/dist/modules/hall/types.js +16 -0
- package/dist/modules/hall/types.js.map +1 -0
- package/dist/modules/kits/types.d.ts +7 -0
- package/dist/modules/kits/types.d.ts.map +1 -0
- package/dist/modules/kits/types.js +7 -0
- package/dist/modules/kits/types.js.map +1 -0
- package/dist/modules/license/types.d.ts +7 -0
- package/dist/modules/license/types.d.ts.map +1 -0
- package/dist/modules/license/types.js +7 -0
- package/dist/modules/license/types.js.map +1 -0
- package/dist/modules/notifications/types.d.ts +7 -0
- package/dist/modules/notifications/types.d.ts.map +1 -0
- package/dist/modules/notifications/types.js +7 -0
- package/dist/modules/notifications/types.js.map +1 -0
- package/dist/modules/skills/index.d.ts +60 -0
- package/dist/modules/skills/index.d.ts.map +1 -0
- package/dist/modules/skills/index.js +253 -0
- package/dist/modules/skills/index.js.map +1 -0
- package/dist/modules/skills/types.d.ts +127 -0
- package/dist/modules/skills/types.d.ts.map +1 -0
- package/dist/modules/skills/types.js +23 -0
- package/dist/modules/skills/types.js.map +1 -0
- package/dist/modules/versions/types.d.ts +31 -0
- package/dist/modules/versions/types.d.ts.map +1 -0
- package/dist/modules/versions/types.js +9 -0
- package/dist/modules/versions/types.js.map +1 -0
- package/dist/modules/vfs/types.d.ts +26 -0
- package/dist/modules/vfs/types.d.ts.map +1 -0
- package/dist/modules/vfs/types.js +11 -0
- package/dist/modules/vfs/types.js.map +1 -0
- package/dist/server/create-sdk.d.ts +70 -0
- package/dist/server/create-sdk.d.ts.map +1 -0
- package/dist/server/create-sdk.js +125 -0
- package/dist/server/create-sdk.js.map +1 -0
- package/dist/server/hall-handlers.d.ts +195 -0
- package/dist/server/hall-handlers.d.ts.map +1 -0
- package/dist/server/hall-handlers.js +239 -0
- package/dist/server/hall-handlers.js.map +1 -0
- package/dist/server/handlers.d.ts +216 -0
- package/dist/server/handlers.d.ts.map +1 -0
- package/dist/server/handlers.js +214 -0
- package/dist/server/handlers.js.map +1 -0
- package/dist/server/index.d.ts +52 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +50 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/instance-pool.d.ts +299 -0
- package/dist/server/instance-pool.d.ts.map +1 -0
- package/dist/server/instance-pool.js +359 -0
- package/dist/server/instance-pool.js.map +1 -0
- package/dist/transports/http.d.ts +86 -0
- package/dist/transports/http.d.ts.map +1 -0
- package/dist/transports/http.js +134 -0
- package/dist/transports/http.js.map +1 -0
- package/dist/transports/local.d.ts +76 -0
- package/dist/transports/local.d.ts.map +1 -0
- package/dist/transports/local.js +101 -0
- package/dist/transports/local.js.map +1 -0
- package/dist/transports/sse.d.ts +99 -0
- package/dist/transports/sse.d.ts.map +1 -0
- package/dist/transports/sse.js +192 -0
- package/dist/transports/sse.js.map +1 -0
- package/dist/transports/transport.d.ts +68 -0
- package/dist/transports/transport.d.ts.map +1 -0
- package/dist/transports/transport.js +14 -0
- package/dist/transports/transport.js.map +1 -0
- package/dist/transports/ws.d.ts +135 -0
- package/dist/transports/ws.d.ts.map +1 -0
- package/dist/transports/ws.js +331 -0
- package/dist/transports/ws.js.map +1 -0
- package/dist/types.d.ts +152 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +8 -0
- package/dist/types.js.map +1 -0
- package/docs/ADR-001-sdk-design.md +282 -0
- package/docs/IMPLEMENTATION-PLAN.md +708 -0
- package/docs/USAGE.md +646 -0
- package/docs/kit-sdk-guide.md +474 -0
- package/package.json +61 -0
|
@@ -0,0 +1,474 @@
|
|
|
1
|
+
# Kit SDK Integration Guide
|
|
2
|
+
|
|
3
|
+
This guide is for developers building applications with Soulcraft application kits —
|
|
4
|
+
whether from scratch, extending an existing kit, or integrating with AI assistance.
|
|
5
|
+
It covers how to use `@soulcraft/sdk` to give kit-based applications access to Brainy,
|
|
6
|
+
VFS, AI (Claude), skills, auth, and events.
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## What Are Kits?
|
|
11
|
+
|
|
12
|
+
Kits (`@soulcraft/kits`) are pure **configuration** — `kit.json` manifests and
|
|
13
|
+
`SKILL.md` prompt files. They describe a domain, persona, glossary, and workflow.
|
|
14
|
+
They contain no TypeScript logic, no server code, and no SDK imports.
|
|
15
|
+
|
|
16
|
+
The **application code** that loads and runs a kit is what uses the SDK. That code
|
|
17
|
+
lives in Workshop, Venue, Academy, or your own custom product.
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
@soulcraft/kits → Domain config (kit.json, SKILL.md)
|
|
21
|
+
@soulcraft/sdk → Runtime: Brainy, AI, auth, events, skills
|
|
22
|
+
Your application code → Wires the two together
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Getting Started
|
|
28
|
+
|
|
29
|
+
Install the SDK in your server application:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
bun add @soulcraft/sdk @soulcraft/brainy
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
For server-side use (Node.js, Bun, Hono, SvelteKit server routes):
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
import { BrainyInstancePool, createSDK, createAuthMiddleware } from '@soulcraft/sdk/server'
|
|
39
|
+
import type { SoulcraftSessionUser } from '@soulcraft/sdk'
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
For browser/client use (kit app components, SvelteKit frontend):
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
import { createBrainyProxy, HttpTransport, WsTransport } from '@soulcraft/sdk/client'
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## Loading a Kit and Accessing Brainy
|
|
51
|
+
|
|
52
|
+
Every kit application needs a Brainy instance to store and retrieve its graph data.
|
|
53
|
+
Use `BrainyInstancePool` on the server to manage instance lifecycle, then `createSDK`
|
|
54
|
+
to get the full SDK namespace:
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
import { BrainyInstancePool, createSDK } from '@soulcraft/sdk/server'
|
|
58
|
+
import { kitRegistry } from '@soulcraft/kits'
|
|
59
|
+
|
|
60
|
+
// One pool per deployment — typically a 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-tenant', // per-user for Workshop, per-tenant for Venue, per-scope for custom
|
|
65
|
+
maxInstances: 50,
|
|
66
|
+
flushOnEvict: true,
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
// Load the kit config
|
|
70
|
+
const kit = kitRegistry['wicks-and-whiskers']
|
|
71
|
+
console.log(kit.name) // → "Wicks & Whiskers"
|
|
72
|
+
|
|
73
|
+
// In a request handler: get the Brainy instance for this tenant
|
|
74
|
+
const brain = await pool.forTenant('wicks-charlotte')
|
|
75
|
+
|
|
76
|
+
// Create the SDK — wraps the brain instance with all namespaces
|
|
77
|
+
const sdk = createSDK({ brain })
|
|
78
|
+
|
|
79
|
+
// Use Brainy directly through the SDK:
|
|
80
|
+
const candles = await sdk.brainy.find({ query: 'candle inventory' })
|
|
81
|
+
|
|
82
|
+
// Or use the raw brain instance directly (same thing, different style):
|
|
83
|
+
const candles2 = await brain.find({ query: 'candle inventory' })
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## Storing Kit Data in Brainy
|
|
89
|
+
|
|
90
|
+
Kit entities are Brainy graph nodes. Shape your entity types using the noun types
|
|
91
|
+
your kit defines in its `glossary` and `aiExpertise`:
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
// Add a product entity from a Venue kit
|
|
95
|
+
await sdk.brainy.add({
|
|
96
|
+
id: 'product-lavender-soy',
|
|
97
|
+
type: 'Product',
|
|
98
|
+
metadata: {
|
|
99
|
+
name: 'Lavender Soy Candle',
|
|
100
|
+
price: 28.00,
|
|
101
|
+
category: 'candles',
|
|
102
|
+
stock: 45,
|
|
103
|
+
kitId: 'wicks-and-whiskers',
|
|
104
|
+
}
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
// Relate it to a category
|
|
108
|
+
await sdk.brainy.relate({
|
|
109
|
+
from: 'product-lavender-soy',
|
|
110
|
+
to: 'category-candles',
|
|
111
|
+
type: 'BelongsTo',
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
// Find similar products using semantic search
|
|
115
|
+
const similar = await sdk.brainy.find({
|
|
116
|
+
query: 'soy candle lavender scent',
|
|
117
|
+
type: 'Product',
|
|
118
|
+
limit: 10,
|
|
119
|
+
})
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## Using the VFS for Kit Files
|
|
125
|
+
|
|
126
|
+
Kits that work with documents, code, or media use the Virtual Filesystem (VFS).
|
|
127
|
+
Access it through `sdk.vfs.*`:
|
|
128
|
+
|
|
129
|
+
```typescript
|
|
130
|
+
// Write a generated document to the VFS
|
|
131
|
+
await sdk.vfs.writeFile('/projects/my-project/README.md', readmeContent)
|
|
132
|
+
|
|
133
|
+
// Read it back — returns a Buffer; convert to string for text:
|
|
134
|
+
const buffer = await sdk.vfs.readFile('/projects/my-project/README.md')
|
|
135
|
+
const content = buffer.toString('utf-8')
|
|
136
|
+
|
|
137
|
+
// List the project directory
|
|
138
|
+
const entries = await sdk.vfs.readdir('/projects/my-project')
|
|
139
|
+
// → ['README.md', 'package.json', 'src']
|
|
140
|
+
|
|
141
|
+
// File info
|
|
142
|
+
const stat = await sdk.vfs.stat('/projects/my-project/README.md')
|
|
143
|
+
|
|
144
|
+
// Rename / delete
|
|
145
|
+
await sdk.vfs.rename('/projects/old-name', '/projects/new-name')
|
|
146
|
+
await sdk.vfs.unlink('/projects/my-project/old-file.md')
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## Loading Skills
|
|
152
|
+
|
|
153
|
+
Skills (`SKILL.md`) define the AI persona, capabilities, and workflow steps for
|
|
154
|
+
a kit. Load them from the VFS or the bundled `@soulcraft/kits` registry:
|
|
155
|
+
|
|
156
|
+
```typescript
|
|
157
|
+
// Load a single skill (VFS first, bundled registry fallback):
|
|
158
|
+
const skill = await sdk.skills.load('inventory-health', 'wicks-and-whiskers')
|
|
159
|
+
if (skill) {
|
|
160
|
+
// Use skill.content (the full SKILL.md string) to build a system prompt
|
|
161
|
+
console.log(skill.source) // 'vfs' or 'bundled'
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Load all skills for a kit:
|
|
165
|
+
const skills = await sdk.skills.list({ kitId: 'wicks-and-whiskers' })
|
|
166
|
+
const systemPrompt = buildSystemPrompt(kit, skills.map(s => s.content))
|
|
167
|
+
|
|
168
|
+
// Install a custom skill into the VFS:
|
|
169
|
+
await sdk.skills.install({
|
|
170
|
+
id: 'custom-upsell-flow',
|
|
171
|
+
kitId: 'wicks-and-whiskers',
|
|
172
|
+
content: `---
|
|
173
|
+
id: custom-upsell-flow
|
|
174
|
+
title: Candle Upsell Flow
|
|
175
|
+
---
|
|
176
|
+
You are an expert at recommending complementary candle products...`,
|
|
177
|
+
})
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
For the lower-level approach (loading directly from the bundled kits package):
|
|
181
|
+
|
|
182
|
+
```typescript
|
|
183
|
+
import { skillRegistry } from '@soulcraft/kits'
|
|
184
|
+
|
|
185
|
+
// skillRegistry['wicks-and-whiskers'] → Record<skillId, content>
|
|
186
|
+
const kitSkills = skillRegistry['wicks-and-whiskers']
|
|
187
|
+
const systemPrompt = buildSystemPrompt(kit, Object.values(kitSkills ?? {}))
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
## AI Integration (Claude)
|
|
193
|
+
|
|
194
|
+
Use the AI module to interact with Claude in the context of a kit's domain:
|
|
195
|
+
|
|
196
|
+
```typescript
|
|
197
|
+
import { AI_MODELS } from '@soulcraft/sdk'
|
|
198
|
+
|
|
199
|
+
// Invoke the AI with the kit's domain context
|
|
200
|
+
const response = await sdk.ai.complete({
|
|
201
|
+
messages: [{ role: 'user', content: 'What candles are low in stock?' }],
|
|
202
|
+
systemPrompt: kit.shared.aiPersona, // kit.json → shared.aiPersona
|
|
203
|
+
model: AI_MODELS.haiku, // Haiku for fast queries
|
|
204
|
+
tools: [
|
|
205
|
+
{
|
|
206
|
+
name: 'search_inventory',
|
|
207
|
+
description: 'Search Brainy for inventory data',
|
|
208
|
+
inputSchema: {
|
|
209
|
+
type: 'object',
|
|
210
|
+
properties: { query: { type: 'string' } },
|
|
211
|
+
required: ['query'],
|
|
212
|
+
},
|
|
213
|
+
}
|
|
214
|
+
],
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
// Handle tool calls — execute the tool and continue the conversation
|
|
218
|
+
if (response.toolCalls) {
|
|
219
|
+
for (const call of response.toolCalls) {
|
|
220
|
+
if (call.name === 'search_inventory') {
|
|
221
|
+
const results = await sdk.brainy.find({
|
|
222
|
+
query: call.input.query as string,
|
|
223
|
+
type: 'Product',
|
|
224
|
+
})
|
|
225
|
+
// Append a tool_result message and call sdk.ai.complete() again
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Streaming — useful for long responses:
|
|
231
|
+
for await (const event of sdk.ai.stream({
|
|
232
|
+
messages: [{ role: 'user', content: 'Write a product description for lavender candles' }],
|
|
233
|
+
systemPrompt: kit.shared.aiPersona,
|
|
234
|
+
model: AI_MODELS.sonnet,
|
|
235
|
+
})) {
|
|
236
|
+
if (event.type === 'text') process.stdout.write(event.text)
|
|
237
|
+
if (event.type === 'done') console.log('Tokens used:', event.result.usage)
|
|
238
|
+
}
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
---
|
|
242
|
+
|
|
243
|
+
## Events
|
|
244
|
+
|
|
245
|
+
Subscribe to and emit platform events across the kit application:
|
|
246
|
+
|
|
247
|
+
```typescript
|
|
248
|
+
// Subscribe to Brainy change events
|
|
249
|
+
sdk.events.on('brainy:change', (event) => {
|
|
250
|
+
if (event.event === 'add' && event.entity?.nounType === 'Product') {
|
|
251
|
+
updateInventoryCache(event.entity)
|
|
252
|
+
}
|
|
253
|
+
})
|
|
254
|
+
|
|
255
|
+
// Subscribe to VFS events for live document awareness
|
|
256
|
+
sdk.events.on('vfs:write', ({ path, content }) => {
|
|
257
|
+
if (path.startsWith('/projects/')) {
|
|
258
|
+
notifyCollaborators(path)
|
|
259
|
+
}
|
|
260
|
+
})
|
|
261
|
+
|
|
262
|
+
// Emit custom application events (declare the type first via declaration merging)
|
|
263
|
+
sdk.events.emit('kit:session-completed', {
|
|
264
|
+
userId: user.id,
|
|
265
|
+
sessionId,
|
|
266
|
+
kitId: 'wicks-and-whiskers',
|
|
267
|
+
})
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
**Note:** The event bus is local to the SDK instance. Events do not cross process
|
|
271
|
+
boundaries. For real-time cross-client events, use the WebSocket transport's `onDataChange`
|
|
272
|
+
or broadcast via your own WebSocket/SSE channel.
|
|
273
|
+
|
|
274
|
+
---
|
|
275
|
+
|
|
276
|
+
## Authentication
|
|
277
|
+
|
|
278
|
+
Kit applications that need user authentication use `createAuthMiddleware` from the
|
|
279
|
+
SDK, wired to a better-auth instance:
|
|
280
|
+
|
|
281
|
+
```typescript
|
|
282
|
+
import { betterAuth } from 'better-auth'
|
|
283
|
+
import {
|
|
284
|
+
SOULCRAFT_USER_FIELDS,
|
|
285
|
+
SOULCRAFT_SESSION_CONFIG,
|
|
286
|
+
computeEmailHash,
|
|
287
|
+
createAuthMiddleware,
|
|
288
|
+
} from '@soulcraft/sdk/server'
|
|
289
|
+
|
|
290
|
+
const auth = betterAuth({
|
|
291
|
+
database: new Database('./auth.db'),
|
|
292
|
+
secret: process.env.BETTER_AUTH_SECRET!,
|
|
293
|
+
session: SOULCRAFT_SESSION_CONFIG,
|
|
294
|
+
user: { additionalFields: SOULCRAFT_USER_FIELDS },
|
|
295
|
+
databaseHooks: {
|
|
296
|
+
user: {
|
|
297
|
+
create: {
|
|
298
|
+
before: async (user) => ({
|
|
299
|
+
data: { ...user, emailHash: computeEmailHash(user.email), platformRole: 'creator' }
|
|
300
|
+
})
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
},
|
|
304
|
+
})
|
|
305
|
+
|
|
306
|
+
const { requireAuth, optionalAuth } = createAuthMiddleware(auth)
|
|
307
|
+
|
|
308
|
+
// Protect kit routes
|
|
309
|
+
app.get('/api/kit-data', requireAuth, async (c) => {
|
|
310
|
+
const user = c.get('user')! // SoulcraftSessionUser — id, email, emailHash, platformRole
|
|
311
|
+
const brain = await pool.forUser(user.emailHash, 'my-workspace')
|
|
312
|
+
const sdk = createSDK({ brain })
|
|
313
|
+
// ...
|
|
314
|
+
})
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
**Auth modes:**
|
|
318
|
+
- **Standalone** (dev default, `SOULCRAFT_IDP_URL` not set): better-auth handles auth locally
|
|
319
|
+
- **OIDC** (production, `SOULCRAFT_IDP_URL=https://auth.soulcraft.com`): central IdP handles all auth
|
|
320
|
+
|
|
321
|
+
---
|
|
322
|
+
|
|
323
|
+
## Capability Tokens (Cross-Product Access)
|
|
324
|
+
|
|
325
|
+
When a kit application needs to read from another product's Brainy (e.g. Workshop
|
|
326
|
+
reads from Venue), use capability tokens:
|
|
327
|
+
|
|
328
|
+
```typescript
|
|
329
|
+
import { createCapabilityToken, verifyCapabilityToken } from '@soulcraft/sdk/server'
|
|
330
|
+
|
|
331
|
+
// Server A: Issue a scoped token
|
|
332
|
+
const token = await createCapabilityToken({
|
|
333
|
+
email: user.email,
|
|
334
|
+
scope: 'wicks-charlotte',
|
|
335
|
+
ttlMs: 3600 * 1000,
|
|
336
|
+
secret: process.env.CAPABILITY_TOKEN_SECRET!,
|
|
337
|
+
})
|
|
338
|
+
|
|
339
|
+
// Server B: Verify it in the RPC handler
|
|
340
|
+
const claims = await verifyCapabilityToken(token, process.env.CAPABILITY_TOKEN_SECRET!)
|
|
341
|
+
if (!claims) return c.json({ error: 'Unauthorized' }, 401)
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
---
|
|
345
|
+
|
|
346
|
+
## Full Application Example
|
|
347
|
+
|
|
348
|
+
A minimal kit application combining auth, Brainy, and AI:
|
|
349
|
+
|
|
350
|
+
```typescript
|
|
351
|
+
import { Hono } from 'hono'
|
|
352
|
+
import { betterAuth } from 'better-auth'
|
|
353
|
+
import { Database } from 'bun:sqlite'
|
|
354
|
+
import {
|
|
355
|
+
BrainyInstancePool,
|
|
356
|
+
createSDK,
|
|
357
|
+
createAuthMiddleware,
|
|
358
|
+
SOULCRAFT_USER_FIELDS,
|
|
359
|
+
SOULCRAFT_SESSION_CONFIG,
|
|
360
|
+
computeEmailHash,
|
|
361
|
+
} from '@soulcraft/sdk/server'
|
|
362
|
+
import { AI_MODELS } from '@soulcraft/sdk'
|
|
363
|
+
import { kitRegistry } from '@soulcraft/kits'
|
|
364
|
+
|
|
365
|
+
// ── Kit config ────────────────────────────────────────────────────────────────
|
|
366
|
+
const kit = kitRegistry['wicks-and-whiskers']
|
|
367
|
+
|
|
368
|
+
// ── Auth ──────────────────────────────────────────────────────────────────────
|
|
369
|
+
const auth = betterAuth({
|
|
370
|
+
database: new Database('./auth.db'),
|
|
371
|
+
secret: process.env.BETTER_AUTH_SECRET!,
|
|
372
|
+
session: SOULCRAFT_SESSION_CONFIG,
|
|
373
|
+
user: { additionalFields: SOULCRAFT_USER_FIELDS },
|
|
374
|
+
databaseHooks: {
|
|
375
|
+
user: {
|
|
376
|
+
create: {
|
|
377
|
+
before: async (u) => ({
|
|
378
|
+
data: { ...u, emailHash: computeEmailHash(u.email), platformRole: 'creator' }
|
|
379
|
+
})
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
},
|
|
383
|
+
})
|
|
384
|
+
const { requireAuth } = createAuthMiddleware(auth)
|
|
385
|
+
|
|
386
|
+
// ── Brainy pool ───────────────────────────────────────────────────────────────
|
|
387
|
+
const pool = new BrainyInstancePool({
|
|
388
|
+
storage: 'filesystem',
|
|
389
|
+
dataPath: './brainy-data',
|
|
390
|
+
strategy: 'per-user',
|
|
391
|
+
maxInstances: 100,
|
|
392
|
+
})
|
|
393
|
+
|
|
394
|
+
// ── Hono app ──────────────────────────────────────────────────────────────────
|
|
395
|
+
const app = new Hono()
|
|
396
|
+
|
|
397
|
+
app.all('/api/auth/*', (c) => auth.handler(c.req.raw))
|
|
398
|
+
|
|
399
|
+
app.get('/api/inventory', requireAuth, async (c) => {
|
|
400
|
+
const user = c.get('user')!
|
|
401
|
+
const brain = await pool.forUser(user.emailHash, 'main')
|
|
402
|
+
const sdk = createSDK({ brain })
|
|
403
|
+
|
|
404
|
+
const items = await sdk.brainy.find({
|
|
405
|
+
query: 'inventory items in stock',
|
|
406
|
+
type: 'Product',
|
|
407
|
+
limit: 50,
|
|
408
|
+
})
|
|
409
|
+
|
|
410
|
+
return c.json({ kit: kit.name, items })
|
|
411
|
+
})
|
|
412
|
+
|
|
413
|
+
app.post('/api/ask', requireAuth, async (c) => {
|
|
414
|
+
const user = c.get('user')!
|
|
415
|
+
const { question } = await c.req.json<{ question: string }>()
|
|
416
|
+
|
|
417
|
+
const brain = await pool.forUser(user.emailHash, 'main')
|
|
418
|
+
const sdk = createSDK({ brain })
|
|
419
|
+
|
|
420
|
+
const skills = await sdk.skills.list({ kitId: 'wicks-and-whiskers' })
|
|
421
|
+
const systemPrompt = [
|
|
422
|
+
kit.shared.aiPersona,
|
|
423
|
+
...skills.map(s => s.content),
|
|
424
|
+
].join('\n\n')
|
|
425
|
+
|
|
426
|
+
const response = await sdk.ai.complete({
|
|
427
|
+
messages: [{ role: 'user', content: question }],
|
|
428
|
+
systemPrompt,
|
|
429
|
+
model: AI_MODELS.haiku,
|
|
430
|
+
})
|
|
431
|
+
|
|
432
|
+
return c.json({ answer: response.text })
|
|
433
|
+
})
|
|
434
|
+
|
|
435
|
+
export default app
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
---
|
|
439
|
+
|
|
440
|
+
## Instance Strategies
|
|
441
|
+
|
|
442
|
+
Choose the pooling strategy that fits your kit application:
|
|
443
|
+
|
|
444
|
+
| Strategy | When to use | Pool method |
|
|
445
|
+
|----------|-------------|-------------|
|
|
446
|
+
| `per-user` | One Brainy per user (Workshop pattern) | `pool.forUser(emailHash, workspaceId)` |
|
|
447
|
+
| `per-tenant` | One Brainy per organization/location (Venue pattern) | `pool.forTenant(slug)` |
|
|
448
|
+
| `per-scope` | Custom key — Academy's content/learner split, multi-branch, etc. | `pool.forScope(key, factory)` |
|
|
449
|
+
|
|
450
|
+
Use `per-scope` when:
|
|
451
|
+
- Your kit has multiple instance types (e.g. Academy's content brain vs learner brain)
|
|
452
|
+
- You need multi-branch support (e.g. `userId:workspaceId:branch` key)
|
|
453
|
+
- You need post-init logic in the factory (migrations, integrity checks)
|
|
454
|
+
|
|
455
|
+
---
|
|
456
|
+
|
|
457
|
+
## Kit API Reference
|
|
458
|
+
|
|
459
|
+
| Package | What it provides |
|
|
460
|
+
|---------|-----------------|
|
|
461
|
+
| `@soulcraft/kits` | `kitRegistry`, `skillRegistry`, per-kit TypeScript types |
|
|
462
|
+
| `@soulcraft/sdk` | Shared types, `AI_MODELS`, error classes |
|
|
463
|
+
| `@soulcraft/sdk/server` | `BrainyInstancePool`, `createSDK`, `createAuthMiddleware`, `createBrainyHandler`, `createBrainyWsHandler`, `createBackchannelLogoutHandler`, `computeEmailHash`, `createCapabilityToken`, `verifyCapabilityToken`, `SOULCRAFT_USER_FIELDS`, `SOULCRAFT_SESSION_CONFIG` |
|
|
464
|
+
| `@soulcraft/sdk/client` | `createBrainyProxy`, `HttpTransport`, `WsTransport`, `SseTransport` |
|
|
465
|
+
| `@soulcraft/brainy` | `Brainy` class (peer dep — version must match server) |
|
|
466
|
+
|
|
467
|
+
---
|
|
468
|
+
|
|
469
|
+
## See Also
|
|
470
|
+
|
|
471
|
+
- `docs/USAGE.md` — Full SDK usage reference for all namespaces
|
|
472
|
+
- `docs/ADR-001-sdk-design.md` — Architecture decision record
|
|
473
|
+
- `@soulcraft/kit-schema` — Kit manifest JSON schema and TypeScript types
|
|
474
|
+
- `@soulcraft/kits` — Kit registry with 57+ domain kits
|
package/package.json
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@soulcraft/sdk",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "The unified Soulcraft platform SDK — data, auth, AI, billing, and notifications",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"publishConfig": {
|
|
7
|
+
"access": "restricted",
|
|
8
|
+
"registry": "https://registry.npmjs.org"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist",
|
|
12
|
+
"docs",
|
|
13
|
+
"README.md"
|
|
14
|
+
],
|
|
15
|
+
"exports": {
|
|
16
|
+
".": {
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
18
|
+
"import": "./dist/index.js"
|
|
19
|
+
},
|
|
20
|
+
"./server": {
|
|
21
|
+
"types": "./dist/server/index.d.ts",
|
|
22
|
+
"import": "./dist/server/index.js"
|
|
23
|
+
},
|
|
24
|
+
"./client": {
|
|
25
|
+
"types": "./dist/client/index.d.ts",
|
|
26
|
+
"import": "./dist/client/index.js"
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"scripts": {
|
|
30
|
+
"build": "tsc",
|
|
31
|
+
"dev": "tsc --watch",
|
|
32
|
+
"test": "vitest run",
|
|
33
|
+
"test:watch": "vitest",
|
|
34
|
+
"check": "tsc --noEmit",
|
|
35
|
+
"lint": "eslint src/",
|
|
36
|
+
"publish:npm": "npm publish --access restricted"
|
|
37
|
+
},
|
|
38
|
+
"peerDependencies": {
|
|
39
|
+
"@soulcraft/brainy": ">=7.17.0",
|
|
40
|
+
"@soulcraft/cortex": ">=2.1.5",
|
|
41
|
+
"@soulcraft/hall": ">=0.1.0"
|
|
42
|
+
},
|
|
43
|
+
"peerDependenciesMeta": {
|
|
44
|
+
"@soulcraft/hall": {
|
|
45
|
+
"optional": true
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
"dependencies": {
|
|
49
|
+
"@anthropic-ai/sdk": "^0.39.0",
|
|
50
|
+
"@msgpack/msgpack": "^3.0.0",
|
|
51
|
+
"better-auth": "^1.0.0",
|
|
52
|
+
"lru-cache": "^11.0.0"
|
|
53
|
+
},
|
|
54
|
+
"devDependencies": {
|
|
55
|
+
"@soulcraft/hall": "link:@soulcraft/hall",
|
|
56
|
+
"@types/node": "^22.0.0",
|
|
57
|
+
"hono": "^4.12.3",
|
|
58
|
+
"typescript": "^5.7.0",
|
|
59
|
+
"vitest": "^3.0.0"
|
|
60
|
+
}
|
|
61
|
+
}
|