design-brain-memory 0.9.2 → 0.9.4

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 (124) hide show
  1. package/README.md +273 -0
  2. package/dist/agentBrowser.js +1 -1
  3. package/dist/agentBrowser.js.map +1 -1
  4. package/dist/aggregate.d.ts +9 -0
  5. package/dist/aggregate.js +53 -0
  6. package/dist/aggregate.js.map +1 -0
  7. package/dist/batch.d.ts +16 -0
  8. package/dist/batch.js +44 -0
  9. package/dist/batch.js.map +1 -0
  10. package/dist/cli.js +250 -216
  11. package/dist/cli.js.map +1 -1
  12. package/dist/commands.d.ts +60 -1
  13. package/dist/commands.js +323 -10
  14. package/dist/commands.js.map +1 -1
  15. package/dist/compare.d.ts +33 -0
  16. package/dist/compare.js +83 -0
  17. package/dist/compare.js.map +1 -0
  18. package/dist/componentGraph.d.ts +22 -0
  19. package/dist/componentGraph.js +106 -0
  20. package/dist/componentGraph.js.map +1 -0
  21. package/dist/contextLayer.d.ts +12 -0
  22. package/dist/contextLayer.js +263 -0
  23. package/dist/contextLayer.js.map +1 -0
  24. package/dist/cssInJs.d.ts +9 -0
  25. package/dist/cssInJs.js +124 -0
  26. package/dist/cssInJs.js.map +1 -0
  27. package/dist/extractFromUrl.d.ts +8 -0
  28. package/dist/extractFromUrl.js +104 -355
  29. package/dist/extractFromUrl.js.map +1 -1
  30. package/dist/graphView.d.ts +21 -0
  31. package/dist/graphView.js +492 -0
  32. package/dist/graphView.js.map +1 -0
  33. package/dist/index.d.ts +26 -11
  34. package/dist/index.js +17 -10
  35. package/dist/index.js.map +1 -1
  36. package/dist/knowledge.d.ts +20 -0
  37. package/dist/knowledge.js +208 -0
  38. package/dist/knowledge.js.map +1 -0
  39. package/dist/liveView.d.ts +15 -0
  40. package/dist/liveView.js +475 -0
  41. package/dist/liveView.js.map +1 -0
  42. package/dist/llm.js +1 -9
  43. package/dist/llm.js.map +1 -1
  44. package/dist/moodboard.d.ts +3 -0
  45. package/dist/moodboard.js +152 -0
  46. package/dist/moodboard.js.map +1 -0
  47. package/dist/persona.d.ts +2 -2
  48. package/dist/persona.js +82 -210
  49. package/dist/persona.js.map +1 -1
  50. package/dist/query.js +1 -6
  51. package/dist/query.js.map +1 -1
  52. package/dist/render.d.ts +2 -10
  53. package/dist/render.js +80 -175
  54. package/dist/render.js.map +1 -1
  55. package/dist/reviewChecklist.d.ts +17 -0
  56. package/dist/reviewChecklist.js +126 -0
  57. package/dist/reviewChecklist.js.map +1 -0
  58. package/dist/scan.d.ts +19 -7
  59. package/dist/scan.js +132 -374
  60. package/dist/scan.js.map +1 -1
  61. package/dist/scorecard.d.ts +53 -0
  62. package/dist/scorecard.js +325 -0
  63. package/dist/scorecard.js.map +1 -0
  64. package/dist/skillPrompt.d.ts +1 -3
  65. package/dist/skillPrompt.js +22 -148
  66. package/dist/skillPrompt.js.map +1 -1
  67. package/dist/store.d.ts +2 -2
  68. package/dist/store.js +7 -9
  69. package/dist/store.js.map +1 -1
  70. package/dist/styleDictionary.d.ts +16 -0
  71. package/dist/styleDictionary.js +89 -0
  72. package/dist/styleDictionary.js.map +1 -0
  73. package/dist/svg.d.ts +5 -0
  74. package/dist/svg.js +162 -0
  75. package/dist/svg.js.map +1 -0
  76. package/dist/systemDiff.d.ts +28 -0
  77. package/dist/systemDiff.js +107 -0
  78. package/dist/systemDiff.js.map +1 -0
  79. package/dist/tailwind.d.ts +2 -0
  80. package/dist/tailwind.js +122 -0
  81. package/dist/tailwind.js.map +1 -0
  82. package/dist/taste.d.ts +3 -2
  83. package/dist/taste.js +349 -536
  84. package/dist/taste.js.map +1 -1
  85. package/dist/tasteRenderer.d.ts +2 -3
  86. package/dist/tasteRenderer.js +123 -119
  87. package/dist/tasteRenderer.js.map +1 -1
  88. package/dist/tokenNaming.d.ts +5 -0
  89. package/dist/tokenNaming.js +229 -0
  90. package/dist/tokenNaming.js.map +1 -0
  91. package/dist/tokens.d.ts +17 -0
  92. package/dist/tokens.js +44 -0
  93. package/dist/tokens.js.map +1 -0
  94. package/dist/trends.d.ts +12 -0
  95. package/dist/trends.js +178 -0
  96. package/dist/trends.js.map +1 -0
  97. package/dist/types.d.ts +47 -101
  98. package/dist/wiki.d.ts +10 -0
  99. package/dist/wiki.js +346 -0
  100. package/dist/wiki.js.map +1 -0
  101. package/dist/writingStyle.d.ts +38 -0
  102. package/dist/writingStyle.js +224 -0
  103. package/dist/writingStyle.js.map +1 -0
  104. package/package.json +5 -4
  105. package/dist/classify.d.ts +0 -21
  106. package/dist/classify.js +0 -205
  107. package/dist/classify.js.map +0 -1
  108. package/dist/scanRenderer.d.ts +0 -2
  109. package/dist/scanRenderer.js +0 -155
  110. package/dist/scanRenderer.js.map +0 -1
  111. package/dist/tasteDiff.d.ts +0 -19
  112. package/dist/tasteDiff.js +0 -340
  113. package/dist/tasteDiff.js.map +0 -1
  114. package/dist/tasteGenerate.d.ts +0 -12
  115. package/dist/tasteGenerate.js +0 -140
  116. package/dist/tasteGenerate.js.map +0 -1
  117. package/dist/tasteRefine.d.ts +0 -13
  118. package/dist/tasteRefine.js +0 -351
  119. package/dist/tasteRefine.js.map +0 -1
  120. package/dist/theatrical.d.ts +0 -5
  121. package/dist/theatrical.js +0 -258
  122. package/dist/theatrical.js.map +0 -1
  123. package/skills/SKILL.md +0 -36
  124. package/skills/design-brain/SKILL.md +0 -77
package/dist/cli.js CHANGED
@@ -1,18 +1,16 @@
1
1
  #!/usr/bin/env node
2
2
  import path from 'node:path';
3
+ import { readFileSync } from 'node:fs';
4
+ import { fileURLToPath } from 'node:url';
3
5
  import { Command } from 'commander';
4
- import { loadTasteProfile } from './store.js';
5
- import { askBrain, initBrain, ingestInspiration, recordOutcome, reindexBrain, searchBrain, } from './commands.js';
6
- import { applyDecision, nextClarifyingQuestion } from './tasteRefine.js';
7
- import { buildTasteProfile } from './taste.js';
8
- import { cherryPickComponent, scoreTaste } from './tasteDiff.js';
9
- import { generateFromTaste } from './tasteGenerate.js';
10
- import { renderTasteDiff, renderTasteProfile } from './tasteRenderer.js';
11
- import { confirmPrompt, shouldSkipPrompts } from './interactive.js';
6
+ import { batchCapture } from './batch.js';
7
+ import { askBrain, compareCaptures, detectAndWriteTrends, exportDesignSystem, generateComponentGraphCmd, generateMoodboard, generateReviewChecklist, initBrain, ingestInspiration, nameTokensCmd, recordOutcome, reindexBrain, runSystemDiff, searchBrain, analyzeWritingStyleCmd, buildGraphView, buildWiki, generateContext, } from './commands.js';
8
+ import { runScorecard } from './scorecard.js';
9
+ import { shouldSkipPrompts } from './interactive.js';
12
10
  import { resolveLlmConfig } from './llm.js';
13
- import { maybePromptSkillInstall } from './skillPrompt.js';
14
- import { scanDesignSystem, scanFromUrl, looksLikeUrl, normalizeToUrl } from './scan.js';
15
- import { renderScanResults } from './scanRenderer.js';
11
+ import { getDefaultSkillRepo, installSkill, maybePromptSkillInstall } from './skillPrompt.js';
12
+ import { buildTasteProfile } from './taste.js';
13
+ import { renderTasteProfile } from './tasteRenderer.js';
16
14
  function toStringList(value) {
17
15
  if (!value) {
18
16
  return [];
@@ -46,42 +44,44 @@ function parseViewports(values) {
46
44
  });
47
45
  return parsed.length > 0 ? parsed : undefined;
48
46
  }
49
- function resolveTasteAnswer(answer, options) {
50
- const trimmed = answer.trim();
51
- if (/^\d+$/.test(trimmed)) {
52
- const index = Number.parseInt(trimmed, 10) - 1;
53
- if (index >= 0 && index < options.length) {
54
- return options[index];
55
- }
47
+ function getVersion() {
48
+ try {
49
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
50
+ const pkg = JSON.parse(readFileSync(path.join(__dirname, '..', 'package.json'), 'utf-8'));
51
+ return pkg.version ?? '0.0.0';
56
52
  }
57
- return trimmed;
53
+ catch {
54
+ return '0.0.0';
55
+ }
56
+ }
57
+ function printBanner(version) {
58
+ const DIM = '\x1b[2m';
59
+ const BOLD = '\x1b[1m';
60
+ const CYAN = '\x1b[36m';
61
+ const RESET = '\x1b[0m';
62
+ const banner = [
63
+ '',
64
+ `${BOLD}${CYAN} ┌─────────────────────────────────────────┐${RESET}`,
65
+ `${BOLD}${CYAN} │${RESET} ${BOLD}${CYAN}│${RESET}`,
66
+ `${BOLD}${CYAN} │${RESET} ${BOLD}design-brain${RESET} ${DIM}v${version}${RESET} ${BOLD}${CYAN}│${RESET}`,
67
+ `${BOLD}${CYAN} │${RESET} ${DIM}Relational design memory${RESET} ${BOLD}${CYAN}│${RESET}`,
68
+ `${BOLD}${CYAN} │${RESET} ${BOLD}${CYAN}│${RESET}`,
69
+ `${BOLD}${CYAN} └─────────────────────────────────────────┘${RESET}`,
70
+ '',
71
+ ];
72
+ console.log(banner.join('\n'));
58
73
  }
59
74
  async function main() {
75
+ const version = getVersion();
60
76
  const program = new Command();
61
77
  program
62
78
  .name('design-brain-memory')
63
79
  .description('Relational markdown design memory powered by Agent Browser CLI')
64
- .version('0.9.2')
65
- .option('-y, --yes', 'Skip interactive prompts');
66
- /* ─── scan (default command) ─── */
67
- program
68
- .command('scan [path]')
69
- .description('Scan codebase for design tokens and compute health score')
70
- .action(async (scanPath) => {
71
- const input = scanPath ?? process.cwd();
72
- let result;
73
- if (looksLikeUrl(input)) {
74
- const url = normalizeToUrl(input);
75
- result = await scanFromUrl(url);
76
- }
77
- else {
78
- result = await scanDesignSystem(path.resolve(input));
79
- }
80
- renderScanResults(result);
81
- const globalOptions = program.opts();
82
- await maybePromptSkillInstall(shouldSkipPrompts(Boolean(globalOptions.yes)));
80
+ .version(version)
81
+ .option('-y, --yes', 'Skip interactive prompts')
82
+ .hook('preAction', () => {
83
+ printBanner(version);
83
84
  });
84
- /* ─── init ─── */
85
85
  program
86
86
  .command('init')
87
87
  .description('Initialize .design-brain in the given workspace')
@@ -90,7 +90,6 @@ async function main() {
90
90
  await initBrain(path.resolve(options.root));
91
91
  console.log(`Initialized design brain at ${path.resolve(options.root, '.design-brain')}`);
92
92
  });
93
- /* ─── ingest ─── */
94
93
  program
95
94
  .command('ingest')
96
95
  .description('Ingest a URL or screenshot into a project design wiki')
@@ -110,6 +109,8 @@ async function main() {
110
109
  .option('--llm-model <model>', 'LLM model id')
111
110
  .option('--llm-timeout-ms <ms>', 'LLM timeout in milliseconds', '30000')
112
111
  .option('--root <dir>', 'Workspace root', process.cwd())
112
+ .option('--no-visuals', 'Skip SVG visual generation')
113
+ .option('--live', 'Show live extraction visualization in terminal')
113
114
  .action(async (options) => {
114
115
  const llm = resolveLlmConfig({
115
116
  baseUrl: options.llmBaseUrl,
@@ -131,12 +132,13 @@ async function main() {
131
132
  llm,
132
133
  journeySteps: parseInteger(options.journeySteps, 3),
133
134
  responsiveViewports: parseViewports(options.viewport),
135
+ skipVisuals: !options.visuals,
136
+ live: options.live ?? false,
134
137
  });
135
138
  console.log(`Captured inspiration ${result.inspirationId} in project ${result.projectId}`);
136
139
  const globalOptions = program.opts();
137
140
  await maybePromptSkillInstall(shouldSkipPrompts(Boolean(globalOptions.yes)));
138
141
  });
139
- /* ─── outcome ─── */
140
142
  program
141
143
  .command('outcome')
142
144
  .description('Record what the team built and link it to inspirations')
@@ -159,7 +161,6 @@ async function main() {
159
161
  });
160
162
  console.log(`Recorded outcome ${result.outcomeId} in project ${result.projectId}`);
161
163
  });
162
- /* ─── search ─── */
163
164
  program
164
165
  .command('search')
165
166
  .description('Keyword search over relational design memory')
@@ -188,7 +189,6 @@ async function main() {
188
189
  console.log(` ${result.snippet}`);
189
190
  }
190
191
  });
191
- /* ─── ask ─── */
192
192
  program
193
193
  .command('ask')
194
194
  .description('Ask a design question against the design brain')
@@ -222,212 +222,246 @@ async function main() {
222
222
  }
223
223
  }
224
224
  });
225
- /* ─── taste ─── */
226
- const taste = program
227
- .command('taste')
228
- .description('Build, inspect, and apply taste profiles');
229
- taste
230
- .command('build <urls...>')
231
- .description('Build taste profile from one or more inspiration URLs')
232
- .option('--project <project>', 'Project ID/slug', 'default')
233
- .option('--name <name>', 'Readable project name')
234
- .option('--headed', 'Use visible browser scan for taste analysis', false)
235
- .option('--llm-base-url <url>', 'OpenAI-compatible LLM base URL')
236
- .option('--llm-api-key <key>', 'LLM API key')
237
- .option('--llm-model <model>', 'LLM model id')
238
- .option('--llm-timeout-ms <ms>', 'LLM timeout in milliseconds', '30000')
225
+ program
226
+ .command('install-skill')
227
+ .description('Install Design Brain skill into your local skills registry')
228
+ .option('--repo <repo>', 'Skill repository override', getDefaultSkillRepo())
229
+ .action(async (options) => {
230
+ const installed = installSkill(options.repo);
231
+ if (!installed) {
232
+ throw new Error(`Skill install failed. Try manually: npx -y skills add ${options.repo}`);
233
+ }
234
+ console.log(`Installed skill from ${options.repo}`);
235
+ });
236
+ program
237
+ .command('reindex')
238
+ .description('Regenerate markdown wiki and relation graph from database.json')
239
239
  .option('--root <dir>', 'Workspace root', process.cwd())
240
- .action(async (urls, options) => {
241
- const llm = resolveLlmConfig({
242
- baseUrl: options.llmBaseUrl,
243
- apiKey: options.llmApiKey,
244
- model: options.llmModel,
245
- timeoutMs: parseInteger(options.llmTimeoutMs, 30000),
240
+ .option('--no-visuals', 'Skip SVG visual generation')
241
+ .action(async (options) => {
242
+ await reindexBrain(path.resolve(options.root), !options.visuals);
243
+ console.log(`Reindexed ${path.resolve(options.root, '.design-brain')}`);
244
+ });
245
+ program
246
+ .command('export')
247
+ .description('Export design system from captured data')
248
+ .requiredOption('--project <project>', 'Project ID/slug')
249
+ .option('--format <format>', 'Export format (tailwind, style-dictionary, css-in-js)', 'tailwind')
250
+ .option('--root <dir>', 'Workspace root', process.cwd())
251
+ .action(async (options) => {
252
+ const outPath = await exportDesignSystem({
253
+ rootDir: path.resolve(options.root),
254
+ project: options.project,
255
+ format: options.format,
246
256
  });
247
- const profile = await buildTasteProfile({
257
+ console.log(`Exported ${options.format} config to ${outPath}`);
258
+ });
259
+ program
260
+ .command('compare')
261
+ .description('Compare two captures or two versions of the same URL')
262
+ .option('--a <id>', 'First inspiration ID')
263
+ .option('--b <id>', 'Second inspiration ID')
264
+ .option('--inspo <id>', 'Inspiration ID (compares against previous version)')
265
+ .option('--root <dir>', 'Workspace root', process.cwd())
266
+ .action(async (options) => {
267
+ const outPath = await compareCaptures({
248
268
  rootDir: path.resolve(options.root),
249
- projectId: options.project,
250
- projectName: options.name,
251
- urls,
252
- headed: options.headed,
253
- llm,
269
+ inspoA: options.a,
270
+ inspoB: options.b,
271
+ inspo: options.inspo,
254
272
  });
255
- renderTasteProfile(profile);
273
+ console.log(`Comparison written to ${outPath}`);
256
274
  });
257
- taste
258
- .command('show')
259
- .description('Display saved taste profile for a project')
260
- .option('--project <project>', 'Project ID/slug', 'default')
261
- .option('--json', 'Output as JSON', false)
275
+ program
276
+ .command('batch')
277
+ .description('Batch capture from a file of URLs')
278
+ .requiredOption('--project <project>', 'Project ID/slug')
279
+ .requiredOption('--file <path>', 'Path to URL list file (tab-separated: URL, name, tags)')
262
280
  .option('--root <dir>', 'Workspace root', process.cwd())
281
+ .option('--no-visuals', 'Skip SVG visual generation')
263
282
  .action(async (options) => {
264
- const profile = await loadTasteProfile(path.resolve(options.root), options.project);
265
- if (!profile) {
266
- throw new Error(`Taste profile not found for project: ${options.project}`);
267
- }
268
- if (options.json) {
269
- console.log(JSON.stringify(profile, null, 2));
270
- return;
283
+ const result = await batchCapture({
284
+ rootDir: path.resolve(options.root),
285
+ project: options.project,
286
+ filePath: path.resolve(options.file),
287
+ skipVisuals: !options.visuals,
288
+ });
289
+ console.log(`Batch complete: ${result.succeeded}/${result.total} captured`);
290
+ if (result.failed.length > 0) {
291
+ console.log(`Failed: ${result.failed.join(', ')}`);
271
292
  }
272
- renderTasteProfile(profile);
273
293
  });
274
- taste
275
- .command('pick')
276
- .description('Cherry-pick a component from an inspiration source')
277
- .requiredOption('--component <kind>', 'Component kind to pick')
278
- .requiredOption('--from <source>', 'Source inspiration ID, URL, or hostname')
279
- .option('--project <project>', 'Project ID/slug', 'default')
280
- .option('--index <n>', 'Pick Nth matched component', '0')
281
- .option('--note <text>', 'Optional note for why this was picked')
294
+ program
295
+ .command('moodboard')
296
+ .description('Generate visual moodboard from project captures')
297
+ .requiredOption('--project <project>', 'Project ID/slug')
282
298
  .option('--root <dir>', 'Workspace root', process.cwd())
283
299
  .action(async (options) => {
284
- const pick = await cherryPickComponent({
300
+ const outPath = await generateMoodboard({
285
301
  rootDir: path.resolve(options.root),
286
- projectId: options.project,
287
- componentKind: options.component,
288
- sourceUrlOrId: options.from,
289
- index: parseInteger(options.index, 0),
290
- note: options.note,
302
+ project: options.project,
291
303
  });
292
- console.log(`Cherry-picked ${pick.componentKind} from ${pick.sourceUrl}`);
304
+ console.log(`Moodboard generated at ${outPath}`);
293
305
  });
294
- taste
295
- .command('score [scanPath]')
296
- .description('Score codebase alignment against a taste profile')
297
- .option('--project <project>', 'Project ID/slug', 'default')
298
- .option('--json', 'Output as JSON', false)
306
+ program
307
+ .command('trends')
308
+ .description('Detect design trends across captures')
309
+ .option('--project <project>', 'Filter to project')
299
310
  .option('--root <dir>', 'Workspace root', process.cwd())
300
- .action(async (scanPath, options) => {
301
- const result = await scoreTaste({
311
+ .action(async (options) => {
312
+ const result = await detectAndWriteTrends({
302
313
  rootDir: path.resolve(options.root),
303
- projectId: options.project,
304
- scanPath: scanPath ?? process.cwd(),
314
+ project: options.project,
305
315
  });
306
- if (options.json) {
307
- console.log(JSON.stringify(result, null, 2));
308
- return;
309
- }
310
- renderTasteDiff(result.diff);
316
+ console.log(`Detected ${result.trends} trends, wrote ${result.written} notes`);
311
317
  });
312
- taste
313
- .command('ask')
314
- .description('Resolve unresolved taste conflicts')
315
- .option('--project <project>', 'Project ID/slug', 'default')
316
- .option('--answer <text>', 'Answer text or option number')
318
+ program
319
+ .command('scorecard')
320
+ .description('Audit local codebase against captured design tokens')
321
+ .requiredOption('--project <project>', 'Project ID/slug')
322
+ .requiredOption('--scan <path>', 'Directory to scan')
317
323
  .option('--root <dir>', 'Workspace root', process.cwd())
318
324
  .action(async (options) => {
319
- const rootDir = path.resolve(options.root);
320
- const profile = await loadTasteProfile(rootDir, options.project);
321
- if (!profile) {
322
- throw new Error(`Taste profile not found for project: ${options.project}`);
323
- }
324
- const next = nextClarifyingQuestion(profile);
325
- if (!next) {
326
- console.log('No unresolved taste conflicts.');
327
- return;
328
- }
329
- const unresolvedIndex = profile.conflicts.findIndex((conflict) => !conflict.resolved);
330
- if (unresolvedIndex < 0) {
331
- console.log('No unresolved taste conflicts.');
332
- return;
333
- }
334
- let answer = options.answer ? resolveTasteAnswer(options.answer, next.options) : '';
335
- if (!answer) {
336
- console.log(next.question);
337
- for (let i = 0; i < next.options.length; i += 1) {
338
- const shouldUse = await confirmPrompt(`Use option ${i + 1}: ${next.options[i]}?`, false);
339
- if (shouldUse) {
340
- answer = next.options[i];
341
- break;
342
- }
343
- }
344
- }
345
- if (!answer) {
346
- console.log('No decision applied.');
347
- return;
348
- }
349
- const decision = await applyDecision({
350
- rootDir,
351
- projectId: options.project,
352
- conflictIndex: unresolvedIndex,
353
- answer,
325
+ const outPath = await runScorecard({
326
+ rootDir: path.resolve(options.root),
327
+ project: options.project,
328
+ scanPath: path.resolve(options.scan),
354
329
  });
355
- console.log(`Saved decision ${decision.id}: ${decision.answer}`);
330
+ console.log(`Scorecard written to ${outPath}`);
356
331
  });
357
- taste
358
- .command('generate')
359
- .description('Generate code from a taste profile')
360
- .option('--project <project>', 'Project ID/slug', 'default')
361
- .option('--target <target>', 'Generation target: component, page, or tokens', 'component')
362
- .option('--component <kind>', 'Component kind when target=component')
363
- .option('--framework <name>', 'Framework hint (react, next, vue, etc.)')
364
- .option('--llm-base-url <url>', 'OpenAI-compatible LLM base URL')
365
- .option('--llm-api-key <key>', 'LLM API key')
366
- .option('--llm-model <model>', 'LLM model id')
367
- .option('--llm-timeout-ms <ms>', 'LLM timeout in milliseconds', '30000')
332
+ program
333
+ .command('component-graph')
334
+ .description('Generate component relationship graph for a project')
335
+ .requiredOption('--project <project>', 'Project ID/slug')
368
336
  .option('--root <dir>', 'Workspace root', process.cwd())
369
337
  .action(async (options) => {
370
- const profile = await loadTasteProfile(path.resolve(options.root), options.project);
371
- if (!profile) {
372
- throw new Error(`Taste profile not found for project: ${options.project}`);
373
- }
374
- const target = options.target;
375
- if (!['component', 'page', 'tokens'].includes(target)) {
376
- throw new Error(`Invalid target: ${options.target}`);
377
- }
378
- const llm = target === 'tokens'
379
- ? undefined
380
- : resolveLlmConfig({
381
- baseUrl: options.llmBaseUrl,
382
- apiKey: options.llmApiKey,
383
- model: options.llmModel,
384
- timeoutMs: parseInteger(options.llmTimeoutMs, 30000),
385
- });
386
- const generated = await generateFromTaste({
387
- taste: profile,
388
- target,
389
- componentKind: options.component,
390
- framework: options.framework,
391
- llm,
338
+ const outPath = await generateComponentGraphCmd({
339
+ rootDir: path.resolve(options.root),
340
+ project: options.project,
341
+ });
342
+ console.log(`Component graph written to ${outPath}`);
343
+ });
344
+ program
345
+ .command('review')
346
+ .description('Generate design review checklist')
347
+ .requiredOption('--project <project>', 'Project ID/slug')
348
+ .option('--scan <path>', 'Directory to scan for codebase audit')
349
+ .option('--root <dir>', 'Workspace root', process.cwd())
350
+ .action(async (options) => {
351
+ const outPath = await generateReviewChecklist({
352
+ rootDir: path.resolve(options.root),
353
+ project: options.project,
354
+ scanPath: options.scan ? path.resolve(options.scan) : undefined,
392
355
  });
393
- console.log(generated.code);
394
- if (generated.explanation) {
395
- console.log(`\n${generated.explanation}`);
356
+ console.log(`Review checklist written to ${outPath}`);
357
+ });
358
+ program
359
+ .command('name-tokens')
360
+ .description('Preview human-readable token names for a project')
361
+ .requiredOption('--project <project>', 'Project ID/slug')
362
+ .option('--root <dir>', 'Workspace root', process.cwd())
363
+ .action(async (options) => {
364
+ const map = await nameTokensCmd({
365
+ rootDir: path.resolve(options.root),
366
+ project: options.project,
367
+ });
368
+ for (const [raw, name] of map) {
369
+ console.log(`${raw} → ${name}`);
396
370
  }
371
+ console.log(`\n${map.size} tokens named`);
397
372
  });
398
- /* ─── install-skill ─── */
399
373
  program
400
- .command('install-skill')
401
- .description('Install Design Brain skill into your AI agents')
402
- .action(async () => {
403
- await maybePromptSkillInstall(false);
374
+ .command('wiki')
375
+ .description('Generate design wiki with per-project pages and shared context space')
376
+ .option('--root <dir>', 'Workspace root', process.cwd())
377
+ .action(async (options) => {
378
+ const result = await buildWiki({
379
+ rootDir: path.resolve(options.root),
380
+ });
381
+ console.log(`Wiki generated: ${result.projectPages} project pages, ${result.sharedTokens} shared tokens`);
382
+ console.log(`Browse at ${result.wikiDir}`);
404
383
  });
405
- /* ─── reindex ─── */
406
384
  program
407
- .command('reindex')
408
- .description('Regenerate markdown wiki and relation graph from database.json')
385
+ .command('graph')
386
+ .description('Generate interactive knowledge graph visualization')
409
387
  .option('--root <dir>', 'Workspace root', process.cwd())
410
388
  .action(async (options) => {
411
- await reindexBrain(path.resolve(options.root));
412
- console.log(`Reindexed ${path.resolve(options.root, '.design-brain')}`);
389
+ const outPath = await buildGraphView({
390
+ rootDir: path.resolve(options.root),
391
+ });
392
+ console.log(`Knowledge graph generated at ${outPath}`);
393
+ console.log('Open in your browser to explore.');
394
+ });
395
+ program
396
+ .command('context')
397
+ .description('Generate design context file for AI tools (Claude Code, Cursor)')
398
+ .option('--project <project>', 'Project ID (omit for unified cross-project context)')
399
+ .option('--root <dir>', 'Workspace root', process.cwd())
400
+ .action(async (options) => {
401
+ const outPath = await generateContext({
402
+ rootDir: path.resolve(options.root),
403
+ project: options.project,
404
+ });
405
+ console.log(`Design context written to ${outPath}`);
406
+ console.log('Drop this file into .claude/ or .cursorrules for AI-assisted development.');
407
+ });
408
+ program
409
+ .command('writing-style')
410
+ .description('Analyze writing style patterns across captured content')
411
+ .requiredOption('--project <project>', 'Project ID/slug')
412
+ .option('--root <dir>', 'Workspace root', process.cwd())
413
+ .action(async (options) => {
414
+ const outPath = await analyzeWritingStyleCmd({
415
+ rootDir: path.resolve(options.root),
416
+ project: options.project,
417
+ });
418
+ console.log(`Writing style analysis written to ${outPath}`);
419
+ });
420
+ program
421
+ .command('system-diff')
422
+ .description('Diff design systems between projects or over time')
423
+ .option('--project <project>', 'Project ID for temporal diff')
424
+ .option('--project-a <project>', 'First project for cross-project diff')
425
+ .option('--project-b <project>', 'Second project for cross-project diff')
426
+ .option('--root <dir>', 'Workspace root', process.cwd())
427
+ .action(async (options) => {
428
+ const outPath = await runSystemDiff({
429
+ rootDir: path.resolve(options.root),
430
+ project: options.project,
431
+ projectA: options.projectA,
432
+ projectB: options.projectB,
433
+ });
434
+ console.log(`System diff written to ${outPath}`);
435
+ });
436
+ program
437
+ .command('taste')
438
+ .description('Build a taste profile from multiple design inspirations')
439
+ .argument('<urls...>', 'URLs or domains to analyze')
440
+ .requiredOption('--project <project>', 'Project ID/slug')
441
+ .option('--project-name <name>', 'Readable project name')
442
+ .option('--headed', 'Use visible browser for capture', false)
443
+ .option('--llm-base-url <url>', 'OpenAI-compatible LLM base URL')
444
+ .option('--llm-api-key <key>', 'LLM API key')
445
+ .option('--llm-model <model>', 'LLM model id')
446
+ .option('--llm-timeout-ms <ms>', 'LLM timeout in milliseconds', '30000')
447
+ .option('--root <dir>', 'Workspace root', process.cwd())
448
+ .action(async (urls, options) => {
449
+ const llm = resolveLlmConfig({
450
+ baseUrl: options.llmBaseUrl,
451
+ apiKey: options.llmApiKey,
452
+ model: options.llmModel,
453
+ timeoutMs: parseInteger(options.llmTimeoutMs, 30000),
454
+ });
455
+ const profile = await buildTasteProfile({
456
+ rootDir: path.resolve(options.root),
457
+ projectId: options.project,
458
+ projectName: options.projectName,
459
+ urls,
460
+ headed: options.headed,
461
+ llm,
462
+ });
463
+ console.log(renderTasteProfile(profile));
413
464
  });
414
- /* ─── Default command routing ─── */
415
- // Bare URLs/domains route to `taste build`.
416
- // Bare local paths route to `scan`.
417
- // No args defaults to `scan`.
418
- const commandNames = program.commands.map((command) => command.name());
419
- const firstArg = process.argv[2];
420
- if (!firstArg) {
421
- process.argv.splice(2, 0, 'scan');
422
- }
423
- else if (!firstArg.startsWith('-') && !commandNames.includes(firstArg)) {
424
- if (looksLikeUrl(firstArg)) {
425
- process.argv.splice(2, 0, 'taste', 'build');
426
- }
427
- else {
428
- process.argv.splice(2, 0, 'scan');
429
- }
430
- }
431
465
  await program.parseAsync(process.argv);
432
466
  }
433
467
  main().catch((error) => {