@soulcraft/sdk 1.4.6 → 1.4.7

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.
@@ -2,9 +2,12 @@
2
2
  * @module formats/types
3
3
  * @description Soulcraft portable content format type definitions.
4
4
  *
5
- * This module exports the four core document formats used across the Soulcraft
6
- * platform. All formats are pure data JSON documents stored in the VFS and
7
- * passed between products, AI tools, and rendering components.
5
+ * These types mirror `@soulcraft/formats` exactly. Once `@soulcraft/formats`
6
+ * ships compiled `.d.ts` output (rather than raw `.ts` source), this module
7
+ * can be replaced with simple re-exports. Until then, the types live here
8
+ * to avoid pulling `@soulcraft/formats`' Zod schemas into the SDK's type-check.
9
+ *
10
+ * See: handoff action "Remove duplicate format types from SDK src/modules/formats/"
8
11
  *
9
12
  * | Format | Extension | Purpose |
10
13
  * |---------|------------------|----------------------------------------------|
@@ -12,24 +15,6 @@
12
15
  * | WDOC | `.wdoc` | Rich text documents (TipTap/ProseMirror) |
13
16
  * | WSLIDE | `.wslide.json` | Slide deck presentations |
14
17
  * | WQUIZ | `.wquiz.json` | Assessments and quizzes |
15
- *
16
- * Formats contain no runtime behavior — they are schema contracts. Products
17
- * read them from the VFS, parse with `JSON.parse()`, and cast to the appropriate
18
- * interface. The `SoulcraftFormat` discriminant on each document's `format` field
19
- * enables safe narrowing at runtime.
20
- *
21
- * @example
22
- * ```typescript
23
- * import type { WdocDocument, WvizDocument, SoulcraftFormatDocument } from '@soulcraft/sdk'
24
- *
25
- * // Read from VFS and narrow to the correct type
26
- * const buf = await sdk.vfs.readFile('/projects/notes.wdoc')
27
- * const doc = JSON.parse(buf.toString('utf-8')) as SoulcraftFormatDocument
28
- * if (doc.format === 'wdoc') {
29
- * const wdoc = doc as WdocDocument
30
- * console.log(wdoc.meta.title)
31
- * }
32
- * ```
33
18
  */
34
19
  export type { WvizDocument, WvizEntity, WvizEdge, WvizSnapshot, WvizViewType, } from './wviz.js';
35
20
  export type { WdocDocument, WdocMeta, WdocBlock, WdocInlineContent, WdocTextNode, WdocMark, WdocParagraphBlock, WdocHeadingBlock, WdocBlockquoteBlock, WdocCodeBlock, WdocBulletListBlock, WdocOrderedListBlock, WdocListItemBlock, WdocTaskListBlock, WdocTaskItemBlock, WdocImageBlock, WdocVideoBlock, WdocIconBlock, WdocSvgEmbedBlock, WdocEmbedBlock, WdocTableBlock, WdocTableRowBlock, WdocTableCellBlock, WdocTableHeaderBlock, WdocHorizontalRuleBlock, WdocCalloutBlock, } from './wdoc.js';
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/modules/formats/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAEH,YAAY,EACV,YAAY,EACZ,UAAU,EACV,QAAQ,EACR,YAAY,EACZ,YAAY,GACb,MAAM,WAAW,CAAA;AAElB,YAAY,EACV,YAAY,EACZ,QAAQ,EACR,SAAS,EACT,iBAAiB,EACjB,YAAY,EACZ,QAAQ,EACR,kBAAkB,EAClB,gBAAgB,EAChB,mBAAmB,EACnB,aAAa,EACb,mBAAmB,EACnB,oBAAoB,EACpB,iBAAiB,EACjB,iBAAiB,EACjB,iBAAiB,EACjB,cAAc,EACd,cAAc,EACd,aAAa,EACb,iBAAiB,EACjB,cAAc,EACd,cAAc,EACd,iBAAiB,EACjB,kBAAkB,EAClB,oBAAoB,EACpB,uBAAuB,EACvB,gBAAgB,GACjB,MAAM,WAAW,CAAA;AAElB,YAAY,EACV,cAAc,EACd,WAAW,EACX,gBAAgB,EAChB,YAAY,EACZ,gBAAgB,EAChB,YAAY,GACb,MAAM,aAAa,CAAA;AAEpB,YAAY,EACV,aAAa,EACb,aAAa,EACb,iBAAiB,EACjB,WAAW,EACX,iBAAiB,GAClB,MAAM,YAAY,CAAA;AAMnB;;;;;;;GAOG;AACH,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,MAAM,GAAG,QAAQ,GAAG,OAAO,CAAA;AAElE;;;;;;;;GAQG;AACH,eAAO,MAAM,iBAAiB,8CAAoF,CAAA;AAElH;;;;;GAKG;AACH,MAAM,WAAW,uBAAuB;IACtC,wEAAwE;IACxE,MAAM,EAAE,eAAe,CAAA;IACvB,4CAA4C;IAC5C,OAAO,EAAE,MAAM,CAAA;CAChB"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/modules/formats/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,YAAY,EACV,YAAY,EACZ,UAAU,EACV,QAAQ,EACR,YAAY,EACZ,YAAY,GACb,MAAM,WAAW,CAAA;AAElB,YAAY,EACV,YAAY,EACZ,QAAQ,EACR,SAAS,EACT,iBAAiB,EACjB,YAAY,EACZ,QAAQ,EACR,kBAAkB,EAClB,gBAAgB,EAChB,mBAAmB,EACnB,aAAa,EACb,mBAAmB,EACnB,oBAAoB,EACpB,iBAAiB,EACjB,iBAAiB,EACjB,iBAAiB,EACjB,cAAc,EACd,cAAc,EACd,aAAa,EACb,iBAAiB,EACjB,cAAc,EACd,cAAc,EACd,iBAAiB,EACjB,kBAAkB,EAClB,oBAAoB,EACpB,uBAAuB,EACvB,gBAAgB,GACjB,MAAM,WAAW,CAAA;AAElB,YAAY,EACV,cAAc,EACd,WAAW,EACX,gBAAgB,EAChB,YAAY,EACZ,gBAAgB,EAChB,YAAY,GACb,MAAM,aAAa,CAAA;AAEpB,YAAY,EACV,aAAa,EACb,aAAa,EACb,iBAAiB,EACjB,WAAW,EACX,iBAAiB,GAClB,MAAM,YAAY,CAAA;AAMnB;;;;;;;GAOG;AACH,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,MAAM,GAAG,QAAQ,GAAG,OAAO,CAAA;AAElE;;;;;;;;GAQG;AACH,eAAO,MAAM,iBAAiB,8CAAoF,CAAA;AAElH;;;;;GAKG;AACH,MAAM,WAAW,uBAAuB;IACtC,wEAAwE;IACxE,MAAM,EAAE,eAAe,CAAA;IACvB,4CAA4C;IAC5C,OAAO,EAAE,MAAM,CAAA;CAChB"}
@@ -2,9 +2,12 @@
2
2
  * @module formats/types
3
3
  * @description Soulcraft portable content format type definitions.
4
4
  *
5
- * This module exports the four core document formats used across the Soulcraft
6
- * platform. All formats are pure data JSON documents stored in the VFS and
7
- * passed between products, AI tools, and rendering components.
5
+ * These types mirror `@soulcraft/formats` exactly. Once `@soulcraft/formats`
6
+ * ships compiled `.d.ts` output (rather than raw `.ts` source), this module
7
+ * can be replaced with simple re-exports. Until then, the types live here
8
+ * to avoid pulling `@soulcraft/formats`' Zod schemas into the SDK's type-check.
9
+ *
10
+ * See: handoff action "Remove duplicate format types from SDK src/modules/formats/"
8
11
  *
9
12
  * | Format | Extension | Purpose |
10
13
  * |---------|------------------|----------------------------------------------|
@@ -12,24 +15,6 @@
12
15
  * | WDOC | `.wdoc` | Rich text documents (TipTap/ProseMirror) |
13
16
  * | WSLIDE | `.wslide.json` | Slide deck presentations |
14
17
  * | WQUIZ | `.wquiz.json` | Assessments and quizzes |
15
- *
16
- * Formats contain no runtime behavior — they are schema contracts. Products
17
- * read them from the VFS, parse with `JSON.parse()`, and cast to the appropriate
18
- * interface. The `SoulcraftFormat` discriminant on each document's `format` field
19
- * enables safe narrowing at runtime.
20
- *
21
- * @example
22
- * ```typescript
23
- * import type { WdocDocument, WvizDocument, SoulcraftFormatDocument } from '@soulcraft/sdk'
24
- *
25
- * // Read from VFS and narrow to the correct type
26
- * const buf = await sdk.vfs.readFile('/projects/notes.wdoc')
27
- * const doc = JSON.parse(buf.toString('utf-8')) as SoulcraftFormatDocument
28
- * if (doc.format === 'wdoc') {
29
- * const wdoc = doc as WdocDocument
30
- * console.log(wdoc.meta.title)
31
- * }
32
- * ```
33
18
  */
34
19
  /**
35
20
  * All Soulcraft format strings as a const array — useful for validation.
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/modules/formats/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAsEH;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,CAA+C,CAAA"}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/modules/formats/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAsEH;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,CAA+C,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/modules/kits/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAGH,OAAO,KAAK,EAAsB,UAAU,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAA;AA8BzF;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,GAAE,uBAA4B,GAAG,UAAU,CAkBlF"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/modules/kits/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAGH,OAAO,KAAK,EAAsB,UAAU,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAA;AAgDzF;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,GAAE,uBAA4B,GAAG,UAAU,CAoClF"}
@@ -23,25 +23,28 @@ import { createRequire } from 'node:module';
23
23
  // createRequire enables CJS-style require() inside an ESM module — the only
24
24
  // reliable way to conditionally load optional peer dependencies in Node/Bun ESM.
25
25
  const _require = createRequire(import.meta.url);
26
- // Module-level cache — loaded once per process, not per SDK instance.
27
- let _registryCache = undefined;
26
+ let _kitsPackage = undefined;
28
27
  /**
29
- * Load the kit registry from the installed `@soulcraft/kits` package.
30
- * Returns `null` when the package is not installed, allowing callers to
31
- * degrade gracefully rather than throwing.
28
+ * Lazily load the `@soulcraft/kits` optional peer dependency.
29
+ * Returns `null` when the package is not installed; callers degrade gracefully.
32
30
  */
33
- function _getBundledRegistry() {
34
- if (_registryCache !== undefined)
35
- return _registryCache;
31
+ function _loadKitsPackage() {
32
+ if (_kitsPackage !== undefined)
33
+ return _kitsPackage;
36
34
  try {
37
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
38
- const kits = _require('@soulcraft/kits');
39
- _registryCache = kits.kitRegistry ?? null;
35
+ _kitsPackage = _require('@soulcraft/kits');
40
36
  }
41
37
  catch {
42
- _registryCache = null;
38
+ _kitsPackage = null;
43
39
  }
44
- return _registryCache;
40
+ return _kitsPackage;
41
+ }
42
+ /**
43
+ * Load the kit registry from the installed `@soulcraft/kits` package.
44
+ * Returns `null` when the package is not installed.
45
+ */
46
+ function _getBundledRegistry() {
47
+ return _loadKitsPackage()?.kitRegistry ?? null;
45
48
  }
46
49
  // ─────────────────────────────────────────────────────────────────────────────
47
50
  // Factory
@@ -80,6 +83,24 @@ export function createKitsModule(options = {}) {
80
83
  return [];
81
84
  return Object.values(registry);
82
85
  },
86
+ async resolveFilesPath(kitId, product) {
87
+ const pkg = _loadKitsPackage();
88
+ if (!pkg?.resolveKitFilesPath)
89
+ return null;
90
+ return pkg.resolveKitFilesPath(kitId, product);
91
+ },
92
+ async resolveSkillsPath(kitId) {
93
+ const pkg = _loadKitsPackage();
94
+ if (!pkg?.resolveKitSkillsPath)
95
+ return null;
96
+ return pkg.resolveKitSkillsPath(kitId);
97
+ },
98
+ async resolveKitsRoot() {
99
+ const pkg = _loadKitsPackage();
100
+ if (!pkg?.resolveKitsRoot)
101
+ return null;
102
+ return pkg.resolveKitsRoot();
103
+ },
83
104
  };
84
105
  }
85
106
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/modules/kits/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAG3C,4EAA4E;AAC5E,iFAAiF;AACjF,MAAM,QAAQ,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;AAE/C,sEAAsE;AACtE,IAAI,cAAc,GAA0D,SAAS,CAAA;AAErF;;;;GAIG;AACH,SAAS,mBAAmB;IAC1B,IAAI,cAAc,KAAK,SAAS;QAAE,OAAO,cAAc,CAAA;IACvD,IAAI,CAAC;QACH,8DAA8D;QAC9D,MAAM,IAAI,GAAG,QAAQ,CAAC,iBAAiB,CAAQ,CAAA;QAC/C,cAAc,GAAI,IAAI,CAAC,WAAkD,IAAI,IAAI,CAAA;IACnF,CAAC;IAAC,MAAM,CAAC;QACP,cAAc,GAAG,IAAI,CAAA;IACvB,CAAC;IACD,OAAO,cAAc,CAAA;AACvB,CAAC;AAED,gFAAgF;AAChF,UAAU;AACV,gFAAgF;AAEhF;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,gBAAgB,CAAC,UAAmC,EAAE;IACpE,MAAM,WAAW,GAAG,OAAO,CAAC,eAAe,KAAK,SAAS;QACvD,CAAC,CAAC,GAA8C,EAAE,CAAC,OAAO,CAAC,eAAe,IAAI,IAAI;QAClF,CAAC,CAAC,mBAAmB,CAAA;IAEvB,OAAO;QACL,KAAK,CAAC,IAAI,CAAC,KAAa;YACtB,MAAM,QAAQ,GAAG,WAAW,EAAE,CAAA;YAC9B,IAAI,CAAC,QAAQ;gBAAE,OAAO,IAAI,CAAA;YAC1B,OAAO,QAAQ,CAAC,KAAK,CAAC,IAAI,IAAI,CAAA;QAChC,CAAC;QAED,KAAK,CAAC,IAAI;YACR,MAAM,QAAQ,GAAG,WAAW,EAAE,CAAA;YAC9B,IAAI,CAAC,QAAQ;gBAAE,OAAO,EAAE,CAAA;YACxB,OAAO,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAA;QAChC,CAAC;KACF,CAAA;AACH,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/modules/kits/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAG3C,4EAA4E;AAC5E,iFAAiF;AACjF,MAAM,QAAQ,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;AAgB/C,IAAI,YAAY,GAAmC,SAAS,CAAA;AAE5D;;;GAGG;AACH,SAAS,gBAAgB;IACvB,IAAI,YAAY,KAAK,SAAS;QAAE,OAAO,YAAY,CAAA;IACnD,IAAI,CAAC;QACH,YAAY,GAAG,QAAQ,CAAC,iBAAiB,CAAgB,CAAA;IAC3D,CAAC;IAAC,MAAM,CAAC;QACP,YAAY,GAAG,IAAI,CAAA;IACrB,CAAC;IACD,OAAO,YAAY,CAAA;AACrB,CAAC;AAED;;;GAGG;AACH,SAAS,mBAAmB;IAC1B,OAAO,gBAAgB,EAAE,EAAE,WAAW,IAAI,IAAI,CAAA;AAChD,CAAC;AAED,gFAAgF;AAChF,UAAU;AACV,gFAAgF;AAEhF;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,gBAAgB,CAAC,UAAmC,EAAE;IACpE,MAAM,WAAW,GAAG,OAAO,CAAC,eAAe,KAAK,SAAS;QACvD,CAAC,CAAC,GAA8C,EAAE,CAAC,OAAO,CAAC,eAAe,IAAI,IAAI;QAClF,CAAC,CAAC,mBAAmB,CAAA;IAEvB,OAAO;QACL,KAAK,CAAC,IAAI,CAAC,KAAa;YACtB,MAAM,QAAQ,GAAG,WAAW,EAAE,CAAA;YAC9B,IAAI,CAAC,QAAQ;gBAAE,OAAO,IAAI,CAAA;YAC1B,OAAO,QAAQ,CAAC,KAAK,CAAC,IAAI,IAAI,CAAA;QAChC,CAAC;QAED,KAAK,CAAC,IAAI;YACR,MAAM,QAAQ,GAAG,WAAW,EAAE,CAAA;YAC9B,IAAI,CAAC,QAAQ;gBAAE,OAAO,EAAE,CAAA;YACxB,OAAO,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAA;QAChC,CAAC;QAED,KAAK,CAAC,gBAAgB,CAAC,KAAa,EAAE,OAA6B;YACjE,MAAM,GAAG,GAAG,gBAAgB,EAAE,CAAA;YAC9B,IAAI,CAAC,GAAG,EAAE,mBAAmB;gBAAE,OAAO,IAAI,CAAA;YAC1C,OAAO,GAAG,CAAC,mBAAmB,CAAC,KAAK,EAAE,OAAO,CAAC,CAAA;QAChD,CAAC;QAED,KAAK,CAAC,iBAAiB,CAAC,KAAa;YACnC,MAAM,GAAG,GAAG,gBAAgB,EAAE,CAAA;YAC9B,IAAI,CAAC,GAAG,EAAE,oBAAoB;gBAAE,OAAO,IAAI,CAAA;YAC3C,OAAO,GAAG,CAAC,oBAAoB,CAAC,KAAK,CAAC,CAAA;QACxC,CAAC;QAED,KAAK,CAAC,eAAe;YACnB,MAAM,GAAG,GAAG,gBAAgB,EAAE,CAAA;YAC9B,IAAI,CAAC,GAAG,EAAE,eAAe;gBAAE,OAAO,IAAI,CAAA;YACtC,OAAO,GAAG,CAAC,eAAe,EAAE,CAAA;QAC9B,CAAC;KACF,CAAA;AACH,CAAC"}
@@ -61,12 +61,15 @@ export interface CreateKitsModuleOptions {
61
61
  bundledRegistry?: Record<string, SoulcraftKitConfig> | null;
62
62
  }
63
63
  /**
64
- * The `sdk.kits.*` namespace — read-only access to the Soulcraft kit registry.
64
+ * The `sdk.kits.*` namespace — read-only access to the Soulcraft kit registry
65
+ * and template file paths.
65
66
  *
66
67
  * Backed by the `@soulcraft/kits` package (a peer dependency). If the package
67
68
  * is not installed, `load()` returns `null` and `list()` returns an empty array.
69
+ * Path resolver methods return `null` when the package is absent or the directory
70
+ * does not exist.
68
71
  *
69
- * @example
72
+ * @example Load a kit and run AI with its persona
70
73
  * ```typescript
71
74
  * const kit = await sdk.kits.load('wicks-and-whiskers')
72
75
  * if (kit) {
@@ -76,6 +79,12 @@ export interface CreateKitsModuleOptions {
76
79
  * })
77
80
  * }
78
81
  * ```
82
+ *
83
+ * @example Locate kit template files on disk
84
+ * ```typescript
85
+ * const dir = await sdk.kits.resolveFilesPath('blog-series', 'workshop')
86
+ * // → '/path/to/node_modules/@soulcraft/kits/kits/blog-series/workshop/files'
87
+ * ```
79
88
  */
80
89
  export interface KitsModule {
81
90
  /**
@@ -107,5 +116,60 @@ export interface KitsModule {
107
116
  * ```
108
117
  */
109
118
  list(): Promise<SoulcraftKitConfig[]>;
119
+ /**
120
+ * Resolve the absolute path to a kit's product-specific template files directory.
121
+ *
122
+ * Returns the path to `kits/{kitId}/{product}/files/` inside the installed
123
+ * `@soulcraft/kits` npm package, or `null` if the directory does not exist
124
+ * (the kit has no template files for that product, or the package is absent).
125
+ *
126
+ * This is the single source of truth for kit file paths across all products.
127
+ * Use it instead of hard-coding `node_modules` paths or maintaining per-product
128
+ * filesystem layouts.
129
+ *
130
+ * @param kitId - Kit identifier (e.g. `'blog-series'`, `'recipe-manager'`).
131
+ * @param product - Product consuming the files: `'workshop'` or `'venue'`.
132
+ * @returns Absolute path to the files directory, or `null` if it does not exist.
133
+ *
134
+ * @example
135
+ * ```typescript
136
+ * const dir = await sdk.kits.resolveFilesPath('blog-series', 'workshop')
137
+ * // → '/path/to/node_modules/@soulcraft/kits/kits/blog-series/workshop/files'
138
+ * ```
139
+ */
140
+ resolveFilesPath(kitId: string, product: 'workshop' | 'venue'): Promise<string | null>;
141
+ /**
142
+ * Resolve the absolute path to a kit's skills directory.
143
+ *
144
+ * Returns the path to `kits/{kitId}/skills/` inside the installed `@soulcraft/kits`
145
+ * npm package, or `null` if the directory does not exist.
146
+ *
147
+ * @param kitId - Kit identifier.
148
+ * @returns Absolute path to the skills directory, or `null` if it does not exist.
149
+ *
150
+ * @example
151
+ * ```typescript
152
+ * const dir = await sdk.kits.resolveSkillsPath('blog-series')
153
+ * // → '/path/to/node_modules/@soulcraft/kits/kits/blog-series/skills'
154
+ * ```
155
+ */
156
+ resolveSkillsPath(kitId: string): Promise<string | null>;
157
+ /**
158
+ * Resolve the absolute path to the root `kits/` directory inside the npm package.
159
+ *
160
+ * Useful when enumerating all available kits on the filesystem rather than relying
161
+ * on the `kitRegistry` in-memory list (e.g. build tooling, validators).
162
+ *
163
+ * Returns `null` if `@soulcraft/kits` is not installed.
164
+ *
165
+ * @returns Absolute path to the `kits/` directory, or `null`.
166
+ *
167
+ * @example
168
+ * ```typescript
169
+ * const root = await sdk.kits.resolveKitsRoot()
170
+ * // → '/path/to/node_modules/@soulcraft/kits/kits'
171
+ * ```
172
+ */
173
+ resolveKitsRoot(): Promise<string | null>;
110
174
  }
111
175
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/modules/kits/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAMH;;;;;;;GAOG;AACH,MAAM,WAAW,kBAAkB;IACjC,qFAAqF;IACrF,EAAE,EAAE,MAAM,CAAA;IACV,+BAA+B;IAC/B,IAAI,EAAE,MAAM,CAAA;IACZ,wDAAwD;IACxD,WAAW,EAAE,MAAM,CAAA;IACnB,gDAAgD;IAChD,OAAO,EAAE,MAAM,CAAA;IACf,6BAA6B;IAC7B,IAAI,EAAE,OAAO,GAAG,SAAS,GAAG,KAAK,GAAG,SAAS,GAAG,WAAW,CAAA;IAC3D,0CAA0C;IAC1C,MAAM,CAAC,EAAE;QACP,gCAAgC;QAChC,SAAS,CAAC,EAAE,MAAM,CAAA;QAClB,+CAA+C;QAC/C,WAAW,CAAC,EAAE,KAAK,CAAC;YAAE,KAAK,EAAE,MAAM,CAAC;YAAC,MAAM,EAAE,MAAM,CAAA;SAAE,CAAC,CAAA;KACvD,CAAA;IACD,wDAAwD;IACxD,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CACvB;AAMD;;;GAGG;AACH,MAAM,WAAW,uBAAuB;IACtC;;;;OAIG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,GAAG,IAAI,CAAA;CAC5D;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,WAAW,UAAU;IACzB;;;;;;;;;;;;;OAaG;IACH,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,CAAA;IAEvD;;;;;;;;;;;;OAYG;IACH,IAAI,IAAI,OAAO,CAAC,kBAAkB,EAAE,CAAC,CAAA;CACtC"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/modules/kits/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAMH;;;;;;;GAOG;AACH,MAAM,WAAW,kBAAkB;IACjC,qFAAqF;IACrF,EAAE,EAAE,MAAM,CAAA;IACV,+BAA+B;IAC/B,IAAI,EAAE,MAAM,CAAA;IACZ,wDAAwD;IACxD,WAAW,EAAE,MAAM,CAAA;IACnB,gDAAgD;IAChD,OAAO,EAAE,MAAM,CAAA;IACf,6BAA6B;IAC7B,IAAI,EAAE,OAAO,GAAG,SAAS,GAAG,KAAK,GAAG,SAAS,GAAG,WAAW,CAAA;IAC3D,0CAA0C;IAC1C,MAAM,CAAC,EAAE;QACP,gCAAgC;QAChC,SAAS,CAAC,EAAE,MAAM,CAAA;QAClB,+CAA+C;QAC/C,WAAW,CAAC,EAAE,KAAK,CAAC;YAAE,KAAK,EAAE,MAAM,CAAC;YAAC,MAAM,EAAE,MAAM,CAAA;SAAE,CAAC,CAAA;KACvD,CAAA;IACD,wDAAwD;IACxD,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CACvB;AAMD;;;GAGG;AACH,MAAM,WAAW,uBAAuB;IACtC;;;;OAIG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,GAAG,IAAI,CAAA;CAC5D;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,WAAW,UAAU;IACzB;;;;;;;;;;;;;OAaG;IACH,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,CAAA;IAEvD;;;;;;;;;;;;OAYG;IACH,IAAI,IAAI,OAAO,CAAC,kBAAkB,EAAE,CAAC,CAAA;IAErC;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,GAAG,OAAO,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAA;IAEtF;;;;;;;;;;;;;;OAcG;IACH,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAA;IAExD;;;;;;;;;;;;;;;OAeG;IACH,eAAe,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAA;CAC1C"}
@@ -219,47 +219,47 @@ consistent everywhere.
219
219
  - [x] Create `soulcraftlabs/sdk` GitHub repo
220
220
  - [x] Initialize TypeScript + Bun package with conditional exports
221
221
  - [x] Write this ADR and CLAUDE.md
222
- - [ ] Scaffold `src/` directory structure (empty modules with JSDoc stubs)
223
- - [ ] Write `tsconfig.json`, `vitest.config.ts`, `.env.example`
222
+ - [x]Scaffold `src/` directory structure (empty modules with JSDoc stubs)
223
+ - [x]Write `tsconfig.json`, `vitest.config.ts`, `.env.example`
224
224
 
225
225
  ### Phase 2 — Core Data Layer
226
- - [ ] `src/transports/` — migrate Local, HTTP, WS from brainy-client; add SSE
227
- - [ ] `src/modules/brainy/` — full Brainy API proxy (all methods, all sub-APIs)
228
- - [ ] `src/modules/vfs/` — VFS namespace (delegates to `brain.vfs.*`)
229
- - [ ] `src/modules/versions/` — versions namespace (delegates to `brain.versions.*`)
230
- - [ ] `src/types.ts` — core shared types (SoulcraftSDK, SDKOptions, etc.)
231
- - [ ] Server mode instance pool (`per-user`, `per-tenant`, `per-scope`)
232
- - [ ] Tests: all transport modes, all brainy methods via proxy
226
+ - [x]`src/transports/` — migrate Local, HTTP, WS from brainy-client; add SSE
227
+ - [x]`src/modules/brainy/` — full Brainy API proxy (all methods, all sub-APIs)
228
+ - [x]`src/modules/vfs/` — VFS namespace (delegates to `brain.vfs.*`)
229
+ - [x]`src/modules/versions/` — versions namespace (delegates to `brain.versions.*`)
230
+ - [x]`src/types.ts` — core shared types (SoulcraftSDK, SDKOptions, etc.)
231
+ - [x]Server mode instance pool (`per-user`, `per-tenant`, `per-scope`)
232
+ - [x]Tests: all transport modes, all brainy methods via proxy
233
233
 
234
234
  ### Phase 3 — Auth + License
235
- - [ ] `src/modules/auth/` — OIDC client config, session cache, capability tokens,
235
+ - [x]`src/modules/auth/` — OIDC client config, session cache, capability tokens,
236
236
  Hono middleware, SvelteKit handle, backchannel logout
237
- - [ ] `src/modules/license/` — Cortex activation, plan verification, credit metering,
237
+ - [x]`src/modules/license/` — Cortex activation, plan verification, credit metering,
238
238
  BYOK validation, heartbeat background task
239
- - [ ] Absorb `@soulcraft/auth` package entirely
240
- - [ ] Tests: auth middleware, capability token create/verify, license exchange
239
+ - [x]Absorb `@soulcraft/auth` package entirely
240
+ - [x]Tests: auth middleware, capability token create/verify, license exchange
241
241
 
242
242
  ### Phase 4 — AI + Events + Skills + Kits + Formats
243
- - [ ] `src/modules/ai/` — model tiers, delegator, Briggy API, tool definition types,
243
+ - [x]`src/modules/ai/` — model tiers, delegator, Briggy API, tool definition types,
244
244
  KitAIContext, UI action registry
245
- - [ ] `src/modules/events/` — platform event bus (DataChangeEmitter, all 30+ event types)
246
- - [ ] `src/modules/skills/` — skill loading, bundled skills, user-invocable registry
247
- - [ ] `src/modules/kits/` — kit loader, initialization, template injection
248
- - [ ] `src/modules/formats/` — WVIZ, WDOC, WSLIDE, WQUIZ types (migrate from @soulcraft/views + richDocument.ts)
245
+ - [x]`src/modules/events/` — platform event bus (DataChangeEmitter, all 30+ event types)
246
+ - [x]`src/modules/skills/` — skill loading, bundled skills, user-invocable registry
247
+ - [x]`src/modules/kits/` — kit loader, initialization, template injection
248
+ - [x]`src/modules/formats/` — WVIZ, WDOC, WSLIDE, WQUIZ types (migrate from @soulcraft/views + richDocument.ts)
249
249
 
250
250
  ### Phase 5 — Billing + Notifications
251
- - [ ] `src/modules/billing/` — usage tracking, quota checks, top-up credits,
251
+ - [x]`src/modules/billing/` — usage tracking, quota checks, top-up credits,
252
252
  Stripe subscription management, billing provider (Firestore vs local)
253
- - [ ] `src/modules/notifications/` — NotificationSender interface, Postmark, Twilio,
253
+ - [x]`src/modules/notifications/` — NotificationSender interface, Postmark, Twilio,
254
254
  dev-mode console sender, notification templates
255
255
 
256
256
  ### Phase 6 — Publish + First npm Release
257
- - [ ] Publish `@soulcraft/sdk@1.0.0` to npmjs.com (private/restricted)
258
- - [ ] Update Workshop: replace all `@soulcraft/brainy-client` + `@soulcraft/auth` imports
259
- - [ ] Update Venue: same
260
- - [ ] Update Academy: same
261
- - [ ] Deprecate (do not unpublish) `@soulcraft/brainy-client` and `@soulcraft/auth`
262
- - [ ] Add `@soulcraft/sdk` to bundle loop in all three `build.sh` files
257
+ - [x]Publish `@soulcraft/sdk@1.0.0` to npmjs.com (private/restricted)
258
+ - [x]Update Workshop: replace all `@soulcraft/brainy-client` + `@soulcraft/auth` imports
259
+ - [x]Update Venue: same
260
+ - [x]Update Academy: same
261
+ - [x]Deprecate (do not unpublish) `@soulcraft/brainy-client` and `@soulcraft/auth`
262
+ - [x]Add `@soulcraft/sdk` to bundle loop in all three `build.sh` files
263
263
 
264
264
  ---
265
265
 
@@ -277,6 +277,6 @@ notice in their README and `package.json` `"deprecated"` field pointing to `@sou
277
277
 
278
278
  ## Related Documents
279
279
 
280
- - `docs/ADR-002-transport-protocol.md` — Wire protocol specification (to be written in Phase 2)
281
- - `docs/ADR-003-instance-strategies.md` — Brainy instance pooling spec (to be written in Phase 2)
280
+ - `docs/ADR-002-transport-protocol.md` — Wire protocol specification
281
+ - `docs/ADR-003-instance-strategies.md` — Brainy instance pooling and Cortex activation
282
282
  - `/home/dpsifr/.strategy/PLATFORM-HANDOFF.md` — Cross-project coordination thread
@@ -0,0 +1,248 @@
1
+ # ADR-002: Transport Protocol — Wire Format, Auth, and Reconnection
2
+
3
+ **Status:** Accepted
4
+ **Date:** 2026-03-02
5
+ **Supersedes:** None
6
+ **See also:** `ADR-001-sdk-design.md` (Decision 5)
7
+
8
+ ---
9
+
10
+ ## Context
11
+
12
+ The SDK must communicate between kit apps (browser) or product backends and a Brainy
13
+ server over a network. Four distinct communication patterns exist across the platform:
14
+
15
+ 1. **Stateless RPC** — kit apps call Brainy methods on demand (no persistent connection needed)
16
+ 2. **Real-time bidirectional RPC + push** — Venue kit apps need live change events alongside RPC
17
+ 3. **Server-push only** — Workshop workspace event stream (VFS/entity mutations)
18
+ 4. **In-process** — server-mode SDK calls Brainy directly with zero serialization overhead
19
+
20
+ These patterns have meaningfully different requirements for serialization, auth, and
21
+ error handling. This ADR records the decisions made for each.
22
+
23
+ ---
24
+
25
+ ## Decision 1: Four Transports, Fixed Serialization
26
+
27
+ Four transport implementations are provided. Serialization format is fixed per transport
28
+ and is never configurable:
29
+
30
+ | Transport | Class | Serialization | Direction | Use case |
31
+ |-----------|-------|--------------|-----------|---------|
32
+ | `local` | `LocalTransport` | None (in-process) | — | Server mode, zero overhead |
33
+ | `http` | `HttpTransport` | JSON | Request/response | Stateless RPC — kit apps, simple clients |
34
+ | `ws` | `WsTransport` | MessagePack binary | Bidirectional | Real-time — RPC + change push events |
35
+ | `sse` | `SseTransport` | text/event-stream | Server → Client | Live updates only — VFS/entity events |
36
+
37
+ **Rationale for fixed serialization:**
38
+ - HTTP=JSON: universally debuggable with `curl`, browser DevTools, and server logs.
39
+ The request volume at typical HTTP RPC frequencies makes binary encoding a
40
+ premature optimization with no measurable benefit.
41
+ - WebSocket=MessagePack: bidirectional real-time traffic has high message frequency
42
+ and persistent connections where binary encoding is measurably more efficient.
43
+ MessagePack was already the wire format in `@soulcraft/brainy-client`.
44
+ - Making serialization configurable adds complexity with no practical benefit —
45
+ consumers never need to mix formats within a single transport.
46
+
47
+ **Rejected alternatives:**
48
+ - MessagePack over HTTP: non-standard, incompatible with standard proxies and logging tools.
49
+ - gRPC/Protobuf: over-engineered for the current scale and requires a schema compilation step.
50
+ - Configurable serializer per transport: extra complexity for a use case that has never arisen.
51
+
52
+ ---
53
+
54
+ ## Decision 2: HTTP Transport Wire Format
55
+
56
+ **Endpoint:** `POST {baseUrl}/api/brainy/rpc`
57
+
58
+ **Request body** (JSON):
59
+ ```json
60
+ { "method": "find", "args": [{ "query": "candle kits", "limit": 10 }] }
61
+ ```
62
+
63
+ **Success response** (JSON):
64
+ ```json
65
+ { "result": [ ... ] }
66
+ ```
67
+
68
+ **Error response** (JSON):
69
+ ```json
70
+ { "error": { "code": "BRAINY_NOT_FOUND", "message": "Entity not found" } }
71
+ ```
72
+
73
+ **Auth:** `Authorization: Bearer <capability-token>` for server-to-server calls;
74
+ `credentials: 'include'` (session cookies) for browser kit apps.
75
+
76
+ **Timeout:** 30 seconds via `AbortController`. Throws `SDKTimeoutError` on expiry.
77
+
78
+ **HTTP status mapping:**
79
+ | Status | Error class |
80
+ |--------|------------|
81
+ | 401 | `SDKAuthError` |
82
+ | 403 | `SDKForbiddenError` |
83
+ | network failure | `SDKDisconnectedError` |
84
+ | 200 + `error` body | `SDKRpcError` |
85
+
86
+ The HTTP transport is stateless — `isAlive()` always returns `true`. Errors surface
87
+ as rejected promises from individual `call()` invocations.
88
+
89
+ ---
90
+
91
+ ## Decision 3: WebSocket Transport Wire Format
92
+
93
+ **Endpoint:** `wss://{host}/api/brainy/ws`
94
+
95
+ **Auth:** Capability token sent as `Authorization: Bearer <token>` on the WebSocket
96
+ upgrade request. This is a Bun-specific extension (`@ts-expect-error` in source).
97
+ Standard browser `WebSocket` does not support custom headers on the upgrade request —
98
+ browser kit apps use the HTTP transport or pass the token as a `?token=` query param.
99
+
100
+ **Scope param:** `?scope=<tenantSlug>` on the connection URL. Venue uses this to
101
+ select the correct per-tenant Brainy instance.
102
+
103
+ ### Connection handshake
104
+
105
+ After a successful upgrade + auth check the server sends a `ready` message:
106
+ ```
107
+ Server → Client: { "type": "ready", "scope": "wicks-and-whiskers" }
108
+ ```
109
+
110
+ `WsTransport.connect()` resolves only after this message. A 15-second timeout
111
+ applies — if no `ready` arrives the connection is aborted with an error.
112
+
113
+ ### RPC messages (binary MessagePack)
114
+
115
+ **Client → Server:**
116
+ ```
117
+ { id: "42", method: "vfs.readdir", args: ["/workspace/docs"] }
118
+ ```
119
+
120
+ The `id` is a monotonically incrementing integer cast to string, unique per connection.
121
+
122
+ **Server → Client (RPC response):**
123
+ ```
124
+ { id: "42", result: [ ... ] }
125
+ { id: "42", error: { code: "VFS_NOT_FOUND", message: "Path not found" } }
126
+ ```
127
+
128
+ Pending RPCs are matched to their response by `id`. A per-call 30-second timeout
129
+ rejects calls that receive no response.
130
+
131
+ ### Change push events (binary MessagePack)
132
+
133
+ ```
134
+ { type: "change", event: "add"|"update"|"delete"|"relate"|"unrelate",
135
+ entity?: { ... }, relation?: { ... } }
136
+ ```
137
+
138
+ Push events have no `id` field and are dispatched to all registered `onEvent` handlers.
139
+
140
+ ### Reconnection
141
+
142
+ On unexpected disconnection (any close code except 4001, 4003, or explicit `close()`):
143
+
144
+ - All pending RPC calls are rejected with `SDKDisconnectedError`
145
+ - Reconnect is attempted with **exponential backoff**: `min(1000 × 2^attempt, 30_000)` ms
146
+ - Maximum 10 attempts (configurable via constructor)
147
+ - After 10 failed attempts the transport enters `closed` state permanently
148
+
149
+ **Auth failure codes — no retry:**
150
+ | Close code | Meaning | Behaviour |
151
+ |-----------|---------|----------|
152
+ | 4001 | Unauthorized (bad/expired token) | Reject pending calls with `SDKAuthError`, set `closed` |
153
+ | 4003 | Forbidden (insufficient scope) | Reject pending calls with `SDKForbiddenError`, set `closed` |
154
+
155
+ ### Connection states
156
+
157
+ ```
158
+ disconnected → connecting → connected → disconnected (unexpected) → connecting (reconnect) ...
159
+ → closed (explicit close() or auth failure)
160
+ ```
161
+
162
+ ---
163
+
164
+ ## Decision 4: SSE Transport Wire Format
165
+
166
+ **Endpoint:** `GET {baseUrl}/api/workspace/events[?workspaceId=<id>]`
167
+
168
+ Uses the browser's native `EventSource` API. `EventSource` handles reconnection
169
+ automatically using the server-sent `retry:` interval. Additionally, the SDK applies
170
+ manual exponential backoff (identical schedule to WsTransport) on `onerror` events
171
+ to prevent thundering-herd reconnects on server restart.
172
+
173
+ **Event format** (JSON-encoded in the SSE `data:` field):
174
+ ```
175
+ data: {"type":"change","event":"update","entity":{"id":"abc","nounType":"Booking",...}}
176
+ ```
177
+
178
+ **Connection confirmation** (internal, not dispatched to listeners):
179
+ ```
180
+ data: {"type":"connected","connectionId":"conn-abc123"}
181
+ ```
182
+
183
+ **Receive-only:** `call()` throws `SDKError` with code `SSE_NO_RPC`. For outbound
184
+ operations pair this transport with an `HttpTransport`.
185
+
186
+ ---
187
+
188
+ ## Decision 5: Local Transport (In-Process)
189
+
190
+ Used exclusively in server mode. Wraps a native `Brainy` instance. No serialization,
191
+ no network, no error class mapping.
192
+
193
+ **Method resolution:** Dot-separated method paths are resolved by recursive property
194
+ traversal on the Brainy instance. `'vfs.readdir'` → `brain.vfs.readdir(args)`.
195
+
196
+ **Change events:** `onEvent`/`offEvent` delegate to Brainy's built-in change listeners
197
+ directly on the instance. No intermediary.
198
+
199
+ **isAlive():** Always `true`. **close():** No-op.
200
+
201
+ ---
202
+
203
+ ## Decision 6: Shared Error Classes
204
+
205
+ All transports share a common error hierarchy:
206
+
207
+ ```
208
+ SDKError (base, has `code: string`)
209
+ ├── SDKAuthError (401 / close code 4001)
210
+ ├── SDKForbiddenError (403 / close code 4003)
211
+ ├── SDKTimeoutError (30s per-call timeout)
212
+ ├── SDKDisconnectedError (transport not connected, or unexpected close)
213
+ └── SDKRpcError (server returned { error: { code, message } })
214
+ ```
215
+
216
+ Consumer code catches `SDKAuthError` to redirect to login, `SDKDisconnectedError`
217
+ to show a reconnecting state, etc.
218
+
219
+ ---
220
+
221
+ ## Decision 7: Capability Tokens for Server-to-Server Auth
222
+
223
+ Transport-level auth uses HMAC-SHA256 capability tokens:
224
+
225
+ ```
226
+ <base64url(payload)>.<base64url(signature)>
227
+ ```
228
+
229
+ `payload` is `{ sub, scope, iat, exp }` JSON. Default TTL: 1 hour.
230
+
231
+ **Timing-safe verification** uses `crypto.timingSafeEqual` to prevent timing attacks.
232
+
233
+ These tokens are separate from OIDC session tokens. They are used for:
234
+ - Kit apps making RPC calls to the Venue or Workshop backend
235
+ - Server-to-server calls (e.g. Academy → Workshop)
236
+
237
+ Session cookie auth (browser kit apps in same-origin context) uses `credentials: 'include'`
238
+ on the HTTP transport and does not require a capability token.
239
+
240
+ ---
241
+
242
+ ## Related Documents
243
+
244
+ - `ADR-001-sdk-design.md` — Overall SDK architecture (Decision 5: transport rationale)
245
+ - `ADR-003-instance-strategies.md` — How server-side Brainy instances are pooled and selected
246
+ - `src/transports/` — Implementation of all four transports
247
+ - `src/modules/brainy/errors.ts` — Shared error class hierarchy
248
+ - `src/modules/brainy/auth.ts` — Capability token creation and verification