@dfosco/storyboard 0.6.0-beta.2 → 0.6.0-beta.21

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 (49) hide show
  1. package/dist/storyboard-ui.js +3112 -3098
  2. package/dist/storyboard-ui.js.map +1 -1
  3. package/mascot/frame-01-peek-left.txt +4 -0
  4. package/mascot/frame-02-eyes-open.txt +4 -0
  5. package/mascot/frame-03-peek-right.txt +4 -0
  6. package/mascot/frame-04-eyes-open.txt +4 -0
  7. package/mascot/frame-05-eyes-closed.txt +4 -0
  8. package/mascot/frame-06-eyes-open.txt +4 -0
  9. package/mascot.config.json +13 -0
  10. package/package.json +5 -2
  11. package/scaffold/AGENTS.md +1 -0
  12. package/scaffold/gitignore +12 -2
  13. package/scaffold/skills/design-system-catalog/SKILL.md +98 -0
  14. package/scaffold/skills/design-system-catalog/extract-components.mjs +441 -0
  15. package/scaffold/skills/design-system-catalog/generate-catalog.sh +255 -0
  16. package/scaffold/skills/migrate/SKILL.md +72 -50
  17. package/scaffold/terminal-agent.agent.md +8 -1
  18. package/src/core/canvas/agent-session.js +103 -17
  19. package/src/core/canvas/agent-session.test.js +29 -1
  20. package/src/core/canvas/collision.js +54 -45
  21. package/src/core/canvas/collision.test.js +39 -0
  22. package/src/core/canvas/configReader.js +110 -0
  23. package/src/core/canvas/hot-pool.js +5 -3
  24. package/src/core/canvas/server.js +32 -13
  25. package/src/core/canvas/terminal-server.js +156 -91
  26. package/src/core/cli/agent.js +86 -33
  27. package/src/core/cli/dev.js +303 -17
  28. package/src/core/cli/server.js +1 -1
  29. package/src/core/cli/setup.js +203 -60
  30. package/src/core/cli/terminal-welcome.js +5 -6
  31. package/src/core/cli/userState.js +63 -0
  32. package/src/core/stores/configSchema.js +1 -0
  33. package/src/core/stores/themeStore.ts +24 -0
  34. package/src/core/tools/handlers/devtools.test.js +1 -1
  35. package/src/core/vite/server-plugin.js +107 -10
  36. package/src/internals/CommandPalette/CommandPalette.jsx +1 -1
  37. package/src/internals/Viewfinder.jsx +10 -2
  38. package/src/internals/canvas/CanvasPage.jsx +30 -9
  39. package/src/internals/canvas/WebGLContextPool.jsx +6 -7
  40. package/src/internals/canvas/componentIsolate.jsx +7 -8
  41. package/src/internals/canvas/componentSetIsolate.jsx +7 -8
  42. package/src/internals/canvas/widgets/PrototypeEmbed.jsx +3 -1
  43. package/src/internals/canvas/widgets/StorySetWidget.jsx +19 -7
  44. package/src/internals/canvas/widgets/StoryWidget.jsx +9 -3
  45. package/src/internals/canvas/widgets/TerminalWidget.jsx +74 -13
  46. package/src/internals/canvas/widgets/expandUtils.js +4 -2
  47. package/src/internals/hooks/usePrototypeReloadGuard.js +9 -5
  48. package/src/internals/vite/data-plugin.js +126 -3
  49. package/terminal.config.json +66 -0
@@ -0,0 +1,4 @@
1
+ ╭─────────────────╮
2
+ │ ● ◡ ● · · │
3
+ │ · · · · · │
4
+ ╰─────────────────╯
@@ -0,0 +1,4 @@
1
+ ╭─────────────────╮
2
+ │ · ● ◡ ● · │
3
+ │ · · · · · │
4
+ ╰─────────────────╯
@@ -0,0 +1,4 @@
1
+ ╭─────────────────╮
2
+ │ · · ● ◡ ● │
3
+ │ · · · · · │
4
+ ╰─────────────────╯
@@ -0,0 +1,4 @@
1
+ ╭─────────────────╮
2
+ │ · ● ◡ ● · │
3
+ │ · · · · · │
4
+ ╰─────────────────╯
@@ -0,0 +1,4 @@
1
+ ╭─────────────────╮
2
+ │ · ◠ ◡ ◠ · │
3
+ │ · · · · · │
4
+ ╰─────────────────╯
@@ -0,0 +1,4 @@
1
+ ╭─────────────────╮
2
+ │ · ● ◡ ● · │
3
+ │ · · · · · │
4
+ ╰─────────────────╯
@@ -0,0 +1,13 @@
1
+ {
2
+ "$comment": "Mascot animation rendered above the Vite dev banner. Each entry in `frames` is either a string (uses frameDurationMs as the default delay) or a [filename, delayMs] tuple for per-frame timing. After loops finish, settleFrame is left on screen with the dev URL beside it.",
3
+ "frames": [
4
+ ["frame-01-peek-left.txt", 600],
5
+ ["frame-03-peek-right.txt", 600],
6
+ ["frame-04-eyes-open.txt", 400],
7
+ ["frame-05-eyes-closed.txt", 200],
8
+ ["frame-06-eyes-open.txt", 400]
9
+ ],
10
+ "loops": 1,
11
+ "settleFrame": "frame-06-eyes-open.txt",
12
+ "enabled": true
13
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dfosco/storyboard",
3
- "version": "0.6.0-beta.2",
3
+ "version": "0.6.0-beta.21",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "description": "Storyboard prototyping framework — core engine, React integration, and canvas",
@@ -18,10 +18,13 @@
18
18
  "src",
19
19
  "dist",
20
20
  "scaffold",
21
+ "mascot",
21
22
  "toolbar.config.json",
22
23
  "widgets.config.json",
23
24
  "paste.config.json",
24
- "commandpalette.config.json"
25
+ "commandpalette.config.json",
26
+ "terminal.config.json",
27
+ "mascot.config.json"
25
28
  ],
26
29
  "scripts": {
27
30
  "build:css": "tailwindcss -i src/core/styles/tailwind.css -o dist/tailwind.css --minify",
@@ -233,6 +233,7 @@ The fix was a one-line compaction (4MB → 9KB). Everything the user reported
233
233
  - Use **Primer Octicons** from `@primer/octicons-react` for icons
234
234
  - **Before importing any icon, verify it exists in `.agents/data/primer-octicons.json`** (the authoritative export list, generated from the installed package). Agents frequently invent icon names like `ScissorsIcon` that do not exist and break the page at runtime. If the icon you want is not in that list, pick the closest available name from the list — never guess.
235
235
  - Regenerate the list after bumping `@primer/octicons-react`: `node scripts/gen-octicons-list.mjs`
236
+ - Use the **canvas widget type registry** at `.agents/data/widget-types.json` as the authoritative list of valid canvas widget `type` strings, their props, default sizes, and when-to-use guidance. Never invent widget types. In particular: when showing multiple variants of a single story file, use ONE `component-set` widget — not N `story` widgets. Regenerate after changing the widget registry: `node scripts/gen-widget-types.mjs`.
236
237
  - Use **CSS Modules** (`*.module.css`) for component-specific styles
237
238
  - If you find any `sx` styled-components styling, migrate them to CSS Modules
238
239
  - **Components must live in their own directory:** `src/components/Name/Name.jsx`, `Name.module.css`, `name.story.jsx`. Never place component files flat in `src/components/`.
@@ -52,8 +52,18 @@ packages/core/dist/storyboard-ui.*
52
52
  # Agent Browser
53
53
  agent-browser.json
54
54
 
55
- # Selected widgets bridge (real-time canvas selection for Copilot)
56
- .storyboard
55
+ # Auto-generated scaffold dir (copies of library config defaults — overwritten on every dev-server boot)
56
+ .storyboard/scaffold/
57
+
58
+ # Real-time canvas selection bridge for Copilot
59
+ .storyboard/.selectedwidgets.json
60
+
61
+ # Runtime/transient state (per-machine, per-session)
62
+ .storyboard/agent-sessions/
63
+ .storyboard/hot-pool/
64
+ .storyboard/logs/
65
+ .storyboard/terminal-buffers/
66
+ .storyboard/messages/
57
67
 
58
68
  # Private canvas images (tilde prefix = not committed)
59
69
  src/canvas/images/~*
@@ -0,0 +1,98 @@
1
+ # Design System Catalog Generator
2
+
3
+ > Triggered by: "generate component catalog", "create component catalog", "catalog components for [package]", "design system catalog", "create catalog for [package]", when the user wants to catalog an installed design system's components
4
+
5
+ ## What This Does
6
+
7
+ Generates a **new skill** that catalogs all components from any React design system package installed in `node_modules`. The output skill provides the agent with a complete reference of available components, sub-components, and props.
8
+
9
+ ## When This Applies
10
+
11
+ - User installs a new design system and wants an agent-readable component reference
12
+ - User wants to create a component catalog for an existing dependency
13
+ - A design system package is upgraded and the catalog needs regenerating
14
+
15
+ ## How It Works
16
+
17
+ The extractor uses a **TypeScript-first** strategy:
18
+
19
+ 1. **Find declarations** — Locates the package's `.d.ts` entry point via `package.json` fields (`types`, `typings`, `exports["."].types`, `typesVersions`) or common paths (`dist/index.d.ts`, etc.)
20
+ 2. **Walk exports** — Recursively follows `export *` and `export { ... } from` to discover all PascalCase exports (components)
21
+ 3. **Extract details** — For each component, parses `.d.ts` files for `*Props` types and sub-component declarations
22
+ 4. **Auto-categorize** — Infers categories from the package's directory structure (e.g., `dist/layout/` → Layout). Falls back to alphabetical grouping
23
+ 5. **Runtime enrichment** — Optionally tries `import()` to detect compound component sub-components (best-effort, skipped if the package can't be imported in Node)
24
+
25
+ ## Prerequisites
26
+
27
+ - The design system package **must be installed** in `node_modules`
28
+ - The package should ship TypeScript declarations (`.d.ts`) or have `@types/*` available
29
+ - Node.js must be available
30
+
31
+ ## Steps
32
+
33
+ ### Step 1 — Ask for the package
34
+
35
+ Ask the user which design system package to catalog. They should provide:
36
+ - **Package name** (required): e.g., `@primer/react`, `@chakra-ui/react`, `antd`, `@mui/material`
37
+ - **Display name** (optional): e.g., "Primer React", "Chakra UI". Auto-derived if not provided.
38
+
39
+ ### Step 2 — Verify installation
40
+
41
+ Check that the package exists in `node_modules`:
42
+
43
+ ```bash
44
+ ls node_modules/<package-name>/package.json
45
+ ```
46
+
47
+ If not found, tell the user to run `npm install <package-name>` first.
48
+
49
+ ### Step 3 — Run the generator
50
+
51
+ ```bash
52
+ REPO_ROOT="$(git rev-parse --show-toplevel)"
53
+ .agents/skills/design-system-catalog/generate-catalog.sh "<package-name>" "<display-name>"
54
+ ```
55
+
56
+ The script will:
57
+ 1. Extract all components from the package's TypeScript declarations
58
+ 2. Create a new skill directory: `.agents/skills/<name>-components-catalog/`
59
+ 3. Generate `SKILL.md` with the full component catalog
60
+ 4. Generate `extract-components.sh` for future regeneration
61
+
62
+ ### Step 4 — Report results
63
+
64
+ Tell the user:
65
+ - How many components were found
66
+ - Where the generated skill lives
67
+ - How to regenerate when the package is upgraded
68
+ - Suggest they review the catalog and customize categories if needed
69
+
70
+ ## Output Structure
71
+
72
+ The generator creates:
73
+
74
+ ```
75
+ .agents/skills/<name>-components-catalog/
76
+ SKILL.md # Skill file with triggers, usage, and full catalog
77
+ extract-components.sh # Thin wrapper to regenerate the catalog
78
+ ```
79
+
80
+ Where `<name>` is derived from the package name (e.g., `@primer/react` → `primer-react`).
81
+
82
+ ## Customization
83
+
84
+ After generation, the user can:
85
+ - Edit category groupings in the generated `SKILL.md`
86
+ - Add codebase-specific conventions (e.g., "never use Box", "prefer CSS Modules over sx")
87
+ - Add or remove components from the catalog
88
+ - Change trigger conditions
89
+
90
+ ## Regeneration
91
+
92
+ Each generated skill includes an `extract-components.sh` that calls back to this skill's shared extractor. To regenerate:
93
+
94
+ ```bash
95
+ .agents/skills/<name>-components-catalog/extract-components.sh
96
+ ```
97
+
98
+ This should be run whenever the design system package is upgraded.
@@ -0,0 +1,441 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Universal design system component extractor.
5
+ *
6
+ * Extracts React components from any installed design system package.
7
+ * Primary strategy: parse TypeScript declarations (.d.ts).
8
+ * Secondary: optional runtime import for sub-component enrichment.
9
+ *
10
+ * Usage: node extract-components.mjs <package-name>
11
+ * Output: JSON to stdout
12
+ */
13
+
14
+ import { readFileSync, existsSync } from "fs";
15
+ import { join, dirname, relative, sep } from "path";
16
+ import { createRequire } from "module";
17
+
18
+ // ── Args ────────────────────────────────────────────────────
19
+ const packageName = process.argv[2];
20
+ if (!packageName) {
21
+ process.stderr.write("Usage: node extract-components.mjs <package-name>\n");
22
+ process.exit(1);
23
+ }
24
+
25
+ // ── Resolve package ─────────────────────────────────────────
26
+ const require_ = createRequire(
27
+ join(process.env.REPO_ROOT || process.cwd(), "package.json")
28
+ );
29
+
30
+ let pkgRoot;
31
+ try {
32
+ const p = require_.resolve(`${packageName}/package.json`);
33
+ pkgRoot = dirname(p);
34
+ } catch {
35
+ const fallback = join(
36
+ process.env.REPO_ROOT || process.cwd(),
37
+ "node_modules",
38
+ ...packageName.split("/")
39
+ );
40
+ if (existsSync(join(fallback, "package.json"))) {
41
+ pkgRoot = fallback;
42
+ } else {
43
+ process.stderr.write(`Error: package "${packageName}" not found.\n`);
44
+ process.exit(1);
45
+ }
46
+ }
47
+
48
+ const pkgJson = JSON.parse(
49
+ readFileSync(join(pkgRoot, "package.json"), "utf-8")
50
+ );
51
+ const version = pkgJson.version;
52
+
53
+ // ── Skip lists ──────────────────────────────────────────────
54
+
55
+ const SKIP_NAMES = new Set([
56
+ "Omit", "Pick", "Partial", "Record", "React", "HTMLElement",
57
+ "PropsWithChildren", "ForwardRefExoticComponent", "FCWithSlotMarker",
58
+ "Provider", "Consumer", "Fragment", "Suspense", "StrictMode",
59
+ "Component", "PureComponent", "Element",
60
+ ]);
61
+
62
+ const SKIP_PROPS = new Set([
63
+ "children", "className", "style", "ref", "key", "as",
64
+ "dangerouslySetInnerHTML", "suppressHydrationWarning",
65
+ ]);
66
+
67
+ // ── TypeScript declaration parsing (primary) ────────────────
68
+
69
+ function findTypesEntryPoint() {
70
+ const candidates = [
71
+ pkgJson.types,
72
+ pkgJson.typings,
73
+ ];
74
+
75
+ // Crawl the exports map for type entries
76
+ if (pkgJson.exports) {
77
+ const root = pkgJson.exports["."];
78
+ if (typeof root === "string" && root.endsWith(".d.ts")) {
79
+ candidates.push(root);
80
+ } else if (typeof root === "object" && root !== null) {
81
+ candidates.push(
82
+ root.types,
83
+ root.import?.types,
84
+ root.require?.types,
85
+ root.node?.types,
86
+ root.default?.types
87
+ );
88
+ }
89
+ }
90
+
91
+ // typesVersions (e.g. {"*": {"*": ["dist/types/*"]}})
92
+ if (pkgJson.typesVersions) {
93
+ for (const range of Object.values(pkgJson.typesVersions)) {
94
+ if (range["*"]) {
95
+ for (const pattern of range["*"]) {
96
+ const resolved = pattern.replace("*", "index");
97
+ candidates.push(resolved);
98
+ }
99
+ }
100
+ if (range["."]) {
101
+ for (const pattern of range["."]) {
102
+ candidates.push(pattern.replace("*", "index"));
103
+ }
104
+ }
105
+ }
106
+ }
107
+
108
+ // Common fallback locations
109
+ candidates.push(
110
+ "dist/index.d.ts", "dist/index.d.mts", "dist/index.d.cts",
111
+ "lib/index.d.ts", "index.d.ts", "types/index.d.ts",
112
+ "dist/cjs/index.d.ts", "dist/esm/index.d.ts"
113
+ );
114
+
115
+ for (const c of candidates.filter(Boolean)) {
116
+ const resolved = join(pkgRoot, c);
117
+ if (existsSync(resolved)) return resolved;
118
+ }
119
+
120
+ // Try DefinitelyTyped (@types/*)
121
+ const typesPackage = packageName.startsWith("@")
122
+ ? `@types/${packageName.slice(1).replace("/", "__")}`
123
+ : `@types/${packageName}`;
124
+ try {
125
+ return require_.resolve(`${typesPackage}/index.d.ts`);
126
+ } catch {
127
+ /* not found */
128
+ }
129
+
130
+ return null;
131
+ }
132
+
133
+ function resolveImportPath(fromFile, specifier) {
134
+ const dir = dirname(fromFile);
135
+ for (const ext of [
136
+ ".d.ts", ".d.mts", ".d.cts",
137
+ "/index.d.ts", "/index.d.mts", "/index.d.cts",
138
+ ]) {
139
+ const candidate = join(dir, specifier + ext);
140
+ if (existsSync(candidate)) return candidate;
141
+ }
142
+ return null;
143
+ }
144
+
145
+ /**
146
+ * Walk the export tree starting from entryFile.
147
+ * Returns Map<name, { name, sourceFile }>.
148
+ */
149
+ function parseExports(entryFile, depth = 0, visited = new Set()) {
150
+ if (depth > 8 || visited.has(entryFile) || !existsSync(entryFile))
151
+ return new Map();
152
+ visited.add(entryFile);
153
+
154
+ const content = readFileSync(entryFile, "utf-8");
155
+ const results = new Map();
156
+
157
+ for (const line of content.split("\n")) {
158
+ if (!line.startsWith("export ")) continue;
159
+ if (line.startsWith("export type ")) continue;
160
+
161
+ // export * from './path'
162
+ const starMatch = line.match(/export\s*\*\s*from\s*['"]([^'"]+)['"]/);
163
+ if (starMatch) {
164
+ const resolved = resolveImportPath(entryFile, starMatch[1]);
165
+ if (resolved) {
166
+ for (const [k, v] of parseExports(resolved, depth + 1, visited)) {
167
+ if (!results.has(k)) results.set(k, v);
168
+ }
169
+ }
170
+ continue;
171
+ }
172
+
173
+ // export { Foo, Bar, default as Baz } from './path'
174
+ const namedFrom = line.match(
175
+ /export\s*\{([^}]+)\}\s*from\s*['"]([^'"]+)['"]/
176
+ );
177
+ if (namedFrom) {
178
+ const resolved = resolveImportPath(entryFile, namedFrom[2]);
179
+ for (const part of namedFrom[1].split(",").map((s) => s.trim())) {
180
+ if (part.startsWith("type ")) continue;
181
+ let name;
182
+ const asDefault = part.match(/default\s+as\s+(\w+)/);
183
+ const asRename = part.match(/(\w+)\s+as\s+(\w+)/);
184
+ const plain = part.match(/^(\w+)$/);
185
+ if (asDefault) name = asDefault[1];
186
+ else if (asRename) name = asRename[2];
187
+ else if (plain) name = plain[1];
188
+ if (name && /^[A-Z]/.test(name) && !SKIP_NAMES.has(name)) {
189
+ results.set(name, { name, sourceFile: resolved || entryFile });
190
+ }
191
+ }
192
+ continue;
193
+ }
194
+
195
+ // export declare const/function/class Foo
196
+ const decl = line.match(
197
+ /export\s+declare\s+(?:const|function|class)\s+([A-Z]\w+)/
198
+ );
199
+ if (decl && !SKIP_NAMES.has(decl[1])) {
200
+ results.set(decl[1], { name: decl[1], sourceFile: entryFile });
201
+ }
202
+ }
203
+
204
+ return results;
205
+ }
206
+
207
+ /**
208
+ * Extract sub-components from .d.ts content for a compound component.
209
+ * Handles both property declarations and intersection types.
210
+ */
211
+ function extractSubComponentsDts(dtsContent, compName) {
212
+ const subs = [];
213
+ const seen = new Set();
214
+
215
+ // Pattern 1: indented property — ` SubName: ...`
216
+ for (const line of dtsContent.split("\n")) {
217
+ const m = line.match(/^\s{2,8}([A-Z][a-zA-Z]+)\s*:/);
218
+ if (m && !SKIP_NAMES.has(m[1]) && !seen.has(m[1])) {
219
+ seen.add(m[1]);
220
+ subs.push(m[1]);
221
+ }
222
+ }
223
+
224
+ // Pattern 2: intersection — `typeof X & { Cell: ...; Header: ... }`
225
+ const intersectionRe = /&\s*\{([^}]+)\}/g;
226
+ let im;
227
+ while ((im = intersectionRe.exec(dtsContent)) !== null) {
228
+ const block = im[1];
229
+ const propRe = /([A-Z][a-zA-Z]+)\s*:/g;
230
+ let pm;
231
+ while ((pm = propRe.exec(block)) !== null) {
232
+ if (!SKIP_NAMES.has(pm[1]) && !seen.has(pm[1])) {
233
+ seen.add(pm[1]);
234
+ subs.push(pm[1]);
235
+ }
236
+ }
237
+ }
238
+
239
+ return subs;
240
+ }
241
+
242
+ /**
243
+ * Extract props from *Props type/interface definitions.
244
+ */
245
+ function extractPropsDts(dtsContent, compName) {
246
+ const props = [];
247
+ const seen = new Set();
248
+
249
+ // Match type/interface blocks named {CompName}Props or {CompName}BaseProps
250
+ const patterns = [
251
+ new RegExp(
252
+ `(?:export\\s+)?(?:type|interface)\\s+${compName}(?:Base)?Props[^{]*\\{([^}]+(?:\\{[^}]*\\}[^}]*)*)\\}`,
253
+ "gs"
254
+ ),
255
+ ];
256
+
257
+ for (const pattern of patterns) {
258
+ let match;
259
+ while ((match = pattern.exec(dtsContent)) !== null) {
260
+ const block = match[1];
261
+ const propRe =
262
+ /(?:\/\*\*[\s\S]*?\*\/\s*)?['"]?(\w+)['"]?\s*\??\s*:\s*([^;\n]+)/g;
263
+ let pm;
264
+ while ((pm = propRe.exec(block)) !== null) {
265
+ const propName = pm[1];
266
+ let propType = pm[2].trim();
267
+ if (SKIP_PROPS.has(propName)) continue;
268
+ if (propName.startsWith("_")) continue;
269
+ if (seen.has(propName)) continue;
270
+ if (
271
+ /^[A-Z]/.test(propName) &&
272
+ /(?:ForwardRef|FCWith|ExoticComponent|typeof)/.test(propType)
273
+ )
274
+ continue;
275
+
276
+ propType = propType.replace(/import\([^)]+\)\./g, "");
277
+ if (propType.length > 80) propType = propType.substring(0, 77) + "...";
278
+
279
+ seen.add(propName);
280
+ props.push({ name: propName, type: propType });
281
+ }
282
+ }
283
+ }
284
+
285
+ return props;
286
+ }
287
+
288
+ /**
289
+ * Read all relevant .d.ts content for a component.
290
+ */
291
+ function readComponentDts(sourceFile, compName) {
292
+ if (!sourceFile || !existsSync(sourceFile)) return "";
293
+
294
+ const sources = [];
295
+ sources.push(readFileSync(sourceFile, "utf-8"));
296
+
297
+ const dir = dirname(sourceFile);
298
+
299
+ // Also check for types.d.ts in same directory
300
+ for (const extra of ["types.d.ts", `${compName}.d.ts`]) {
301
+ const f = join(dir, extra);
302
+ if (existsSync(f) && f !== sourceFile) {
303
+ sources.push(readFileSync(f, "utf-8"));
304
+ }
305
+ }
306
+
307
+ return sources.join("\n");
308
+ }
309
+
310
+ // ── Auto-categorization ─────────────────────────────────────
311
+
312
+ const CATEGORY_MAP = {
313
+ layout: "Layout", layouts: "Layout", grid: "Layout",
314
+ navigation: "Navigation", nav: "Navigation",
315
+ action: "Actions", actions: "Actions", menu: "Actions",
316
+ menus: "Actions", button: "Actions", buttons: "Actions",
317
+ form: "Forms", forms: "Forms", input: "Forms", inputs: "Forms",
318
+ data: "Data Display", display: "Data Display", "data-display": "Data Display",
319
+ feedback: "Feedback",
320
+ overlay: "Overlays", overlays: "Overlays", modal: "Overlays", dialog: "Overlays",
321
+ content: "Content", typography: "Typography",
322
+ utility: "Utilities", utilities: "Utilities",
323
+ general: "General", common: "General",
324
+ };
325
+
326
+ function categorizeFromPath(sourceFile) {
327
+ if (!sourceFile) return "Components";
328
+
329
+ const rel = relative(pkgRoot, sourceFile);
330
+ const parts = rel
331
+ .split(sep)
332
+ .filter((p) => !["dist", "lib", "src", "cjs", "esm", "types"].includes(p));
333
+
334
+ for (const part of parts) {
335
+ const lower = part.toLowerCase();
336
+ if (CATEGORY_MAP[lower]) return CATEGORY_MAP[lower];
337
+ }
338
+
339
+ if (parts.length >= 2) {
340
+ const parentDir = parts[parts.length - 2].toLowerCase();
341
+ if (CATEGORY_MAP[parentDir]) return CATEGORY_MAP[parentDir];
342
+ }
343
+
344
+ return "Components";
345
+ }
346
+
347
+ // ── Runtime enrichment (optional, best-effort) ──────────────
348
+
349
+ async function tryRuntimeEnrichment(components) {
350
+ try {
351
+ const mod = await import(packageName);
352
+
353
+ for (const [name, comp] of components) {
354
+ const value = mod[name];
355
+ if (!value) continue;
356
+
357
+ // Enrich sub-components from runtime (most reliable for compound components)
358
+ try {
359
+ const runtimeSubs = [];
360
+ for (const [subName, subValue] of Object.entries(value)) {
361
+ if (!/^[A-Z]/.test(subName)) continue;
362
+ if (SKIP_NAMES.has(subName)) continue;
363
+ const isSub =
364
+ typeof subValue === "function" ||
365
+ (typeof subValue === "object" && subValue?.$$typeof != null) ||
366
+ (typeof subValue === "object" &&
367
+ typeof subValue?.render === "function");
368
+ if (isSub) runtimeSubs.push(subName);
369
+ }
370
+
371
+ if (runtimeSubs.length > 0) {
372
+ const existing = new Set(comp.subComponents);
373
+ for (const sub of runtimeSubs) {
374
+ if (!existing.has(sub)) comp.subComponents.push(sub);
375
+ }
376
+ }
377
+ } catch {
378
+ /* non-iterable export */
379
+ }
380
+ }
381
+
382
+ process.stderr.write("Runtime enrichment: success\n");
383
+ } catch {
384
+ process.stderr.write(
385
+ "Runtime enrichment: skipped (import failed, using .d.ts data only)\n"
386
+ );
387
+ }
388
+ }
389
+
390
+ // ── Build component list ────────────────────────────────────
391
+
392
+ const typesEntry = findTypesEntryPoint();
393
+
394
+ if (!typesEntry) {
395
+ process.stderr.write(
396
+ `Error: no TypeScript declarations found for "${packageName}".\n` +
397
+ "Ensure the package ships .d.ts files or install @types/* for it.\n"
398
+ );
399
+ process.exit(1);
400
+ }
401
+
402
+ process.stderr.write(`Types entry: ${relative(pkgRoot, typesEntry)}\n`);
403
+
404
+ const tsExports = parseExports(typesEntry);
405
+
406
+ process.stderr.write(
407
+ `Found ${tsExports.size} PascalCase exports in declarations\n`
408
+ );
409
+
410
+ // Build component map from TypeScript exports
411
+ const components = new Map();
412
+
413
+ for (const [name, info] of tsExports) {
414
+ if (name.startsWith("use") || !/^[A-Z]/.test(name)) continue;
415
+
416
+ const dtsContent = readComponentDts(info.sourceFile, name);
417
+
418
+ components.set(name, {
419
+ name,
420
+ importPath: packageName,
421
+ subComponents: extractSubComponentsDts(dtsContent, name),
422
+ props: extractPropsDts(dtsContent, name),
423
+ category: categorizeFromPath(info.sourceFile),
424
+ });
425
+ }
426
+
427
+ // Try runtime enrichment for sub-components (best-effort)
428
+ await tryRuntimeEnrichment(components);
429
+
430
+ // ── Output ──────────────────────────────────────────────────
431
+
432
+ const output = {
433
+ packageName,
434
+ version,
435
+ totalComponents: components.size,
436
+ components: [...components.values()].sort((a, b) =>
437
+ a.name.localeCompare(b.name)
438
+ ),
439
+ };
440
+
441
+ process.stdout.write(JSON.stringify(output, null, 2) + "\n");