@sourcescape/ds-cli 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/cli.js +692 -461
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -5,17 +5,30 @@ import { parseArgs } from "util";
5
5
 
6
6
  // src/systems.ts
7
7
  import { readFileSync, readdirSync, existsSync, statSync } from "fs";
8
- import { join, resolve, dirname } from "path";
9
- var REPO_ROOT = resolve(dirname(new URL(import.meta.url).pathname), "..", "..");
10
- var SYSTEMS_ROOT = join(REPO_ROOT, "systems");
11
- function setSystemsRoot(dir) {
12
- SYSTEMS_ROOT = resolve(dir);
8
+ import { join, resolve } from "path";
9
+ function getSystemsRoot() {
10
+ const dir = process.env.DESIGN_SYSTEMS_DIR;
11
+ if (!dir) {
12
+ console.error("Error: DESIGN_SYSTEMS_DIR environment variable is required.");
13
+ console.error("Set it to the path containing your design system definitions.");
14
+ process.exit(1);
15
+ }
16
+ return resolve(dir);
17
+ }
18
+ function readComponentJson(componentDir) {
19
+ const jsonPath = join(componentDir, "description.json");
20
+ if (!existsSync(jsonPath)) return null;
21
+ try {
22
+ return JSON.parse(readFileSync(jsonPath, "utf-8"));
23
+ } catch {
24
+ return null;
25
+ }
13
26
  }
14
27
  function listSystems() {
15
- return readdirSync(SYSTEMS_ROOT, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name).sort();
28
+ return readdirSync(getSystemsRoot(), { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name).sort();
16
29
  }
17
30
  function systemDir(system) {
18
- return join(SYSTEMS_ROOT, system);
31
+ return join(getSystemsRoot(), system);
19
32
  }
20
33
  function systemExists(system) {
21
34
  return existsSync(systemDir(system));
@@ -64,12 +77,24 @@ function listComponents(system) {
64
77
  if (!existsSync(kindDir)) continue;
65
78
  const dirs = readdirSync(kindDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name).sort();
66
79
  for (const name of dirs) {
67
- const descPath = join(kindDir, name, "description.md");
68
- results.push({
69
- name,
70
- kind,
71
- description: readFirstParagraph(descPath)
72
- });
80
+ const compDir = join(kindDir, name);
81
+ const json = readComponentJson(compDir);
82
+ if (json) {
83
+ results.push({
84
+ name,
85
+ kind,
86
+ description: json.description,
87
+ tag: json.tag,
88
+ example: json.example
89
+ });
90
+ } else {
91
+ const descPath = join(compDir, "description.md");
92
+ results.push({
93
+ name,
94
+ kind,
95
+ description: readFirstParagraph(descPath)
96
+ });
97
+ }
73
98
  }
74
99
  }
75
100
  return results;
@@ -95,77 +120,67 @@ function allComponentNames(system) {
95
120
  }
96
121
  return results;
97
122
  }
98
- function listTokenFiles(system) {
99
- const tokensDir = join(systemDir(system), "tokens");
100
- if (!existsSync(tokensDir)) return [];
101
- return readdirSync(tokensDir).filter((f) => f.endsWith(".json")).map((f) => f.replace(".json", "")).sort();
102
- }
103
- function readTokenFile(system, name) {
104
- const filePath = join(systemDir(system), "tokens", `${name}.json`);
105
- if (!existsSync(filePath)) return null;
106
- return JSON.parse(readFile(filePath));
107
- }
108
- function listPrinciples(system) {
109
- const dir = join(systemDir(system), "principles");
110
- if (!existsSync(dir)) return [];
111
- return readdirSync(dir).filter((f) => f.endsWith(".md") && f !== "index.md").map((f) => {
112
- const name = f.replace(".md", "");
113
- const title = readFirstHeading(join(dir, f));
114
- return { name, title: title || name };
115
- }).sort((a, b) => a.name.localeCompare(b.name));
116
- }
117
- function readPrinciple(system, name) {
118
- const filePath = join(systemDir(system), "principles", `${name}.md`);
119
- if (!existsSync(filePath)) return null;
120
- return readFile(filePath);
121
- }
122
- function hasArchitecture(system) {
123
- return existsSync(join(systemDir(system), "architecture"));
124
- }
125
- function listArchitecture(system, subpath) {
126
- const baseDir = join(systemDir(system), "architecture");
127
- const dir = subpath ? join(baseDir, subpath) : baseDir;
128
- if (!existsSync(dir)) return { entries: [], indexContent: null };
129
- const indexPath = join(dir, "index.md");
130
- const indexContent = existsSync(indexPath) ? readFile(indexPath) : null;
131
- const entries = readdirSync(dir, { withFileTypes: true }).filter((d) => d.name !== "index.md").map((d) => ({ name: d.name, isDir: d.isDirectory() })).sort((a, b) => {
132
- if (a.isDir !== b.isDir) return a.isDir ? -1 : 1;
133
- return a.name.localeCompare(b.name);
134
- });
135
- return { entries, indexContent };
136
- }
137
- function readArchitectureFile(system, subpath) {
138
- const filePath = join(systemDir(system), "architecture", subpath);
139
- if (!existsSync(filePath)) return null;
140
- if (statSync(filePath).isDirectory()) return null;
141
- return readFile(filePath);
142
- }
143
- function isArchitectureDir(system, subpath) {
144
- const filePath = join(systemDir(system), "architecture", subpath);
145
- return existsSync(filePath) && statSync(filePath).isDirectory();
146
- }
147
- function listExamples(system) {
148
- const dir = join(systemDir(system), "examples");
149
- if (!existsSync(dir)) return [];
150
- return readdirSync(dir).filter((f) => f.endsWith(".html")).sort();
151
- }
152
- function listReferences(system) {
153
- const dir = join(systemDir(system), "references");
154
- if (!existsSync(dir)) return [];
155
- return readdirSync(dir, { withFileTypes: true }).filter((d) => !d.isDirectory()).map((d) => d.name).sort();
156
- }
157
123
  function suggestComponent(system, query) {
158
124
  const all = allComponentNames(system);
159
125
  const q = query.toLowerCase();
160
126
  return all.filter((c) => c.name.includes(q) || q.includes(c.name)).map((c) => `${c.name} (${c.kind})`);
161
127
  }
128
+ function readComponentMeta(system, componentName) {
129
+ const found = findComponent(system, componentName);
130
+ if (!found) return { name: componentName, tag: "", description: "" };
131
+ const descPath = join(found.dir, "description.md");
132
+ if (!existsSync(descPath)) return { name: componentName, tag: "", description: "" };
133
+ const lines = readFileSync(descPath, "utf-8").split("\n");
134
+ let name = componentName;
135
+ let tag = "";
136
+ let description = "";
137
+ for (let i = 0; i < lines.length; i++) {
138
+ const line = lines[i];
139
+ if (line.startsWith("# ")) {
140
+ name = line.slice(2).trim();
141
+ continue;
142
+ }
143
+ if (!tag && /^`<.+>`$/.test(line.trim())) {
144
+ tag = line.trim();
145
+ continue;
146
+ }
147
+ if (name && line.trim().length > 0 && !line.startsWith("#") && !line.startsWith("`")) {
148
+ description = line.trim();
149
+ break;
150
+ }
151
+ }
152
+ return { name, tag: tag || "*(auto)*", description };
153
+ }
154
+ function readFullDescription(system, name, kind) {
155
+ const compDir = join(systemDir(system), "components", kind, name);
156
+ if (!existsSync(compDir)) return "";
157
+ const mdPath = join(compDir, "description.md");
158
+ if (existsSync(mdPath)) {
159
+ return readFileSync(mdPath, "utf-8");
160
+ }
161
+ const json = readComponentJson(compDir);
162
+ return json?.description ?? "";
163
+ }
164
+ function descriptionMtime(system, name, kind) {
165
+ const compDir = join(systemDir(system), "components", kind, name);
166
+ if (!existsSync(compDir)) return 0;
167
+ const mdPath = join(compDir, "description.md");
168
+ if (existsSync(mdPath)) {
169
+ return statSync(mdPath).mtimeMs;
170
+ }
171
+ const jsonPath = join(compDir, "description.json");
172
+ if (existsSync(jsonPath)) {
173
+ return statSync(jsonPath).mtimeMs;
174
+ }
175
+ return 0;
176
+ }
162
177
  function findComponentSources(system, name) {
163
178
  const found = findComponent(system, name);
164
179
  if (!found) return { css: [], js: [] };
165
180
  const css = [];
166
181
  const js = [];
167
182
  const stylePath = join(found.dir, "style.css");
168
- const scriptPath = join(found.dir, "script.js");
183
+ const scriptPath = join(found.dir, "component.js");
169
184
  if (existsSync(stylePath)) css.push(stylePath);
170
185
  if (existsSync(scriptPath)) js.push(scriptPath);
171
186
  return { css, js };
@@ -181,12 +196,6 @@ function subheading(text) {
181
196
  console.log(`
182
197
  ${text}`);
183
198
  }
184
- function list(items, indent = 2) {
185
- const pad = " ".repeat(indent);
186
- for (const item of items) {
187
- console.log(`${pad}${item}`);
188
- }
189
- }
190
199
  function descriptionList(items, indent = 2) {
191
200
  if (items.length === 0) return;
192
201
  const maxName = Math.max(...items.map((i) => i.name.length));
@@ -196,9 +205,6 @@ function descriptionList(items, indent = 2) {
196
205
  console.log(`${pad}${item.name.padEnd(maxName)}${desc}`);
197
206
  }
198
207
  }
199
- function countLine(label, count) {
200
- console.log(` ${label}: ${count}`);
201
- }
202
208
  function error(message, suggestions) {
203
209
  console.error(`Error: ${message}`);
204
210
  if (suggestions && suggestions.length > 0) {
@@ -229,82 +235,15 @@ function commandList() {
229
235
  return 0;
230
236
  }
231
237
 
232
- // src/tokens.ts
233
- function isToken(obj) {
234
- return typeof obj === "object" && obj !== null && "$value" in obj;
235
- }
236
- function flattenTokens(obj, prefix = "") {
237
- const tokens = [];
238
- for (const [key, value] of Object.entries(obj)) {
239
- if (key.startsWith("$")) continue;
240
- const path = prefix ? `${prefix}.${key}` : key;
241
- if (isToken(value)) {
242
- tokens.push({
243
- path,
244
- $value: value.$value,
245
- $type: value.$type ?? "unknown"
246
- });
247
- } else if (typeof value === "object" && value !== null) {
248
- tokens.push(...flattenTokens(value, path));
249
- }
250
- }
251
- return tokens;
252
- }
253
- function groupTokens(tokens) {
254
- const groups = /* @__PURE__ */ new Map();
255
- for (const token of tokens) {
256
- const category = token.path.split(".")[0];
257
- if (!groups.has(category)) {
258
- groups.set(category, []);
259
- }
260
- groups.get(category).push(token);
261
- }
262
- return Array.from(groups.entries()).map(([name, tokens2]) => ({
263
- name,
264
- tokens: tokens2
265
- }));
266
- }
267
- function formatValue(value) {
268
- if (typeof value === "string") return value;
269
- if (typeof value === "number") return String(value);
270
- if (typeof value === "object" && value !== null) {
271
- return JSON.stringify(value);
272
- }
273
- return String(value);
274
- }
275
- function renderTokens(data) {
276
- const tokens = flattenTokens(data);
277
- const groups = groupTokens(tokens);
278
- for (const group of groups) {
279
- console.log(`
280
- ${group.name}`);
281
- console.log("\u2500".repeat(Math.min(group.name.length, 40)));
282
- const maxPath = Math.max(...group.tokens.map((t) => t.path.length));
283
- for (const token of group.tokens) {
284
- const typeTag = token.$type !== "unknown" ? ` [${token.$type}]` : "";
285
- console.log(` ${token.path.padEnd(maxPath)} ${formatValue(token.$value)}${typeTag}`);
286
- }
287
- }
288
- console.log();
289
- }
290
- function renderFlat(data) {
291
- const tokens = flattenTokens(data);
292
- for (const token of tokens) {
293
- console.log(`${token.path} = ${formatValue(token.$value)}`);
294
- }
295
- }
296
- function renderJson(data) {
297
- console.log(JSON.stringify(data, null, 2));
298
- }
299
- function tokenSummary(data) {
300
- const tokens = flattenTokens(data);
301
- const types = new Set(tokens.map((t) => t.$type));
302
- const typeList = Array.from(types).filter((t) => t !== "unknown").sort();
303
- const typeSuffix = typeList.length > 0 ? ` (${typeList.join(", ")})` : "";
304
- return `${tokens.length} tokens${typeSuffix}`;
305
- }
306
-
307
238
  // src/commands/show.ts
239
+ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
240
+ import { join as join2 } from "path";
241
+ function loadManifest(system) {
242
+ const path = join2(systemDir(system), "references", "manifest.json");
243
+ if (!existsSync2(path)) return [];
244
+ const data = JSON.parse(readFileSync2(path, "utf-8"));
245
+ return data.references || [];
246
+ }
308
247
  function commandShow(system) {
309
248
  if (!systemExists(system)) {
310
249
  const available = listSystems();
@@ -321,48 +260,312 @@ function commandShow(system) {
321
260
  if (desc) {
322
261
  markdown(desc);
323
262
  }
324
- heading("Sections");
325
263
  const components = listComponents(system);
326
264
  const molecules = components.filter((c) => c.kind === "molecules");
327
265
  const cells = components.filter((c) => c.kind === "cells");
328
- countLine("Components", components.length);
329
- if (molecules.length > 0) countLine(" Molecules", molecules.length);
330
- if (cells.length > 0) countLine(" Cells", cells.length);
331
- const tokenFiles = listTokenFiles(system);
332
- if (tokenFiles.length > 0) {
333
- countLine("Token files", tokenFiles.length);
334
- for (const name of tokenFiles) {
335
- const data = readTokenFile(system, name);
336
- const summary = data ? tokenSummary(data) : "";
337
- console.log(` ${name}: ${summary}`);
338
- }
339
- }
340
- const principles = listPrinciples(system);
341
- if (principles.length > 0) countLine("Principles", principles.length);
342
- if (hasArchitecture(system)) {
343
- console.log(" Architecture: yes");
344
- }
345
- const examples = listExamples(system);
346
- if (examples.length > 0) countLine("Examples", examples.length);
347
- const references = listReferences(system);
348
- if (references.length > 0) countLine("References", references.length);
266
+ console.log("\n## Component Inventory");
267
+ function printTable(label, items) {
268
+ const metas = items.map((c) => readComponentMeta(system, c.name));
269
+ console.log(`
270
+ ### ${label} (${items.length} active)
271
+ `);
272
+ console.log("| Component | Tag | Description |");
273
+ console.log("|---|---|---|");
274
+ for (const m of metas) {
275
+ console.log(`| ${m.name} | ${m.tag} | ${m.description} |`);
276
+ }
277
+ }
278
+ if (molecules.length > 0) printTable("Molecules", molecules);
279
+ if (cells.length > 0) printTable("Cells", cells);
280
+ const refs = loadManifest(system);
281
+ if (refs.length > 0) {
282
+ console.log("\n## References\n");
283
+ for (const ref of refs) {
284
+ console.log(`- \`${ref.file}\` \u2014 ${ref.description}`);
285
+ }
286
+ }
349
287
  blank();
350
288
  return 0;
351
289
  }
352
290
 
353
291
  // src/commands/browse.ts
354
- import { join as join5 } from "path";
355
- import { existsSync as existsSync6 } from "fs";
356
292
  import { exec } from "child_process";
357
293
 
358
294
  // src/commands/components.ts
359
- import { join as join2, basename as basename2, relative } from "path";
360
- import { existsSync as existsSync2, readFileSync as readFileSync2, readdirSync as readdirSync2 } from "fs";
295
+ import { join as join4, basename, relative } from "path";
296
+ import { existsSync as existsSync4, readFileSync as readFileSync4, readdirSync as readdirSync2 } from "fs";
297
+
298
+ // src/search/tfidf.ts
299
+ var STOP_WORDS = /* @__PURE__ */ new Set([
300
+ "a",
301
+ "an",
302
+ "the",
303
+ "and",
304
+ "or",
305
+ "but",
306
+ "in",
307
+ "on",
308
+ "at",
309
+ "to",
310
+ "for",
311
+ "of",
312
+ "with",
313
+ "by",
314
+ "from",
315
+ "is",
316
+ "are",
317
+ "was",
318
+ "were",
319
+ "be",
320
+ "been",
321
+ "has",
322
+ "have",
323
+ "had",
324
+ "do",
325
+ "does",
326
+ "did",
327
+ "will",
328
+ "would",
329
+ "could",
330
+ "should",
331
+ "may",
332
+ "might",
333
+ "can",
334
+ "this",
335
+ "that",
336
+ "these",
337
+ "those",
338
+ "it",
339
+ "its",
340
+ "not",
341
+ "no",
342
+ "as",
343
+ "if",
344
+ "so",
345
+ "than",
346
+ "up",
347
+ "out",
348
+ "about",
349
+ "into",
350
+ "over",
351
+ "after",
352
+ "before",
353
+ "between",
354
+ "under",
355
+ "such",
356
+ "each",
357
+ "all",
358
+ "any",
359
+ "both",
360
+ "more",
361
+ "other",
362
+ "some"
363
+ ]);
364
+ function stripMarkdown(text) {
365
+ return text.replace(/^#{1,6}\s+/gm, "").replace(/```[\s\S]*?```/g, "").replace(/`[^`]+`/g, "").replace(/\[([^\]]+)\]\([^)]+\)/g, "$1").replace(/[|─┌┐└┘├┤┬┴┼]/g, " ").replace(/<[^>]+>/g, "").replace(/\*\*([^*]+)\*\*/g, "$1").replace(/\*([^*]+)\*/g, "$1").replace(/!\[[^\]]*\]\([^)]+\)/g, "").replace(/^[-*]\s+/gm, "").replace(/^\d+\.\s+/gm, "").replace(/^>\s+/gm, "");
366
+ }
367
+ function tokenize(text) {
368
+ return text.toLowerCase().replace(/[^a-z0-9-]/g, " ").split(/\s+/).filter((t) => t.length > 1 && !STOP_WORDS.has(t));
369
+ }
370
+ function computeTF(tokens) {
371
+ const counts = {};
372
+ for (const t of tokens) {
373
+ counts[t] = (counts[t] || 0) + 1;
374
+ }
375
+ const len = tokens.length || 1;
376
+ const tf = {};
377
+ for (const [term, count] of Object.entries(counts)) {
378
+ tf[term] = count / len;
379
+ }
380
+ return tf;
381
+ }
382
+ function computeIDF(documents) {
383
+ const N = documents.length || 1;
384
+ const docFreq = {};
385
+ for (const tokens of documents) {
386
+ const seen = new Set(tokens);
387
+ for (const t of seen) {
388
+ docFreq[t] = (docFreq[t] || 0) + 1;
389
+ }
390
+ }
391
+ const idf = {};
392
+ for (const [term, df] of Object.entries(docFreq)) {
393
+ idf[term] = Math.log(1 + N / df);
394
+ }
395
+ return idf;
396
+ }
397
+ function computeTFIDF(tf, idf) {
398
+ const tfidf = {};
399
+ for (const [term, tfVal] of Object.entries(tf)) {
400
+ const idfVal = idf[term] ?? Math.log(1 + 1);
401
+ tfidf[term] = tfVal * idfVal;
402
+ }
403
+ return tfidf;
404
+ }
405
+ function cosineSimilarity(a, b) {
406
+ let dot = 0;
407
+ let magA = 0;
408
+ let magB = 0;
409
+ for (const [term, val] of Object.entries(a)) {
410
+ magA += val * val;
411
+ if (term in b) {
412
+ dot += val * b[term];
413
+ }
414
+ }
415
+ for (const val of Object.values(b)) {
416
+ magB += val * val;
417
+ }
418
+ const denom = Math.sqrt(magA) * Math.sqrt(magB);
419
+ return denom === 0 ? 0 : dot / denom;
420
+ }
421
+
422
+ // src/search/indexer.ts
423
+ import { readFileSync as readFileSync3, writeFileSync, existsSync as existsSync3 } from "fs";
424
+ import { join as join3 } from "path";
425
+ var INDEX_FILE = ".search-index.json";
426
+ function indexPath(system) {
427
+ return join3(systemDir(system), INDEX_FILE);
428
+ }
429
+ function buildSearchIndex(system) {
430
+ const components = listComponents(system);
431
+ const docTokens = [];
432
+ const rawEntries = [];
433
+ for (const comp of components) {
434
+ const fullDesc = readFullDescription(system, comp.name, comp.kind);
435
+ const plain = stripMarkdown(fullDesc || comp.description);
436
+ const tokens = tokenize(`${comp.name} ${plain}`);
437
+ const unique = [...new Set(tokens)];
438
+ docTokens.push(unique);
439
+ rawEntries.push({
440
+ name: comp.name,
441
+ kind: comp.kind,
442
+ summary: comp.description.slice(0, 200),
443
+ tokens: unique,
444
+ tf: computeTF(tokens),
445
+ mtime: descriptionMtime(system, comp.name, comp.kind)
446
+ });
447
+ }
448
+ const idf = computeIDF(docTokens);
449
+ const entries = rawEntries.map((raw) => ({
450
+ name: raw.name,
451
+ kind: raw.kind,
452
+ summary: raw.summary,
453
+ tfidf: computeTFIDF(raw.tf, idf),
454
+ tokens: raw.tokens,
455
+ descriptionMtime: raw.mtime
456
+ }));
457
+ return {
458
+ version: 1,
459
+ buildTime: (/* @__PURE__ */ new Date()).toISOString(),
460
+ systemName: system,
461
+ documentCount: entries.length,
462
+ idf,
463
+ entries
464
+ };
465
+ }
466
+ function writeSearchIndex(system, index) {
467
+ try {
468
+ writeFileSync(indexPath(system), JSON.stringify(index, null, 2), "utf-8");
469
+ } catch {
470
+ }
471
+ }
472
+ function loadSearchIndex(system) {
473
+ const path = indexPath(system);
474
+ if (!existsSync3(path)) return null;
475
+ try {
476
+ const data = JSON.parse(readFileSync3(path, "utf-8"));
477
+ if (data.version !== 1) return null;
478
+ return data;
479
+ } catch {
480
+ return null;
481
+ }
482
+ }
483
+ function isStale(system, index) {
484
+ for (const entry of index.entries) {
485
+ const currentMtime = descriptionMtime(system, entry.name, entry.kind);
486
+ if (currentMtime !== entry.descriptionMtime) return true;
487
+ }
488
+ const currentCount = listComponents(system).length;
489
+ if (currentCount !== index.documentCount) return true;
490
+ return false;
491
+ }
492
+ function ensureSearchIndex(system) {
493
+ const existing = loadSearchIndex(system);
494
+ if (existing && !isStale(system, existing)) {
495
+ return existing;
496
+ }
497
+ const index = buildSearchIndex(system);
498
+ writeSearchIndex(system, index);
499
+ return index;
500
+ }
501
+
502
+ // src/search/search.ts
503
+ var NAME_WEIGHT = 0.4;
504
+ var KEYWORD_WEIGHT = 0.3;
505
+ var TFIDF_WEIGHT = 0.3;
506
+ function nameScore(queryTokens, componentName) {
507
+ const name = componentName.toLowerCase();
508
+ const parts = name.split("-");
509
+ let matched = 0;
510
+ for (const qt of queryTokens) {
511
+ if (name.includes(qt) || parts.some((p) => p.includes(qt) || qt.includes(p))) {
512
+ matched++;
513
+ }
514
+ }
515
+ return queryTokens.length === 0 ? 0 : matched / queryTokens.length;
516
+ }
517
+ function keywordScore(queryTokens, docTokens) {
518
+ if (queryTokens.length === 0) return 0;
519
+ const docSet = new Set(docTokens);
520
+ let matched = 0;
521
+ for (const qt of queryTokens) {
522
+ if (docSet.has(qt)) {
523
+ matched++;
524
+ } else {
525
+ for (const dt of docTokens) {
526
+ if (dt.includes(qt) || qt.includes(dt)) {
527
+ matched += 0.5;
528
+ break;
529
+ }
530
+ }
531
+ }
532
+ }
533
+ return matched / queryTokens.length;
534
+ }
535
+ function searchComponents(system, query) {
536
+ const index = ensureSearchIndex(system);
537
+ const queryTokens = tokenize(query);
538
+ if (queryTokens.length === 0) return [];
539
+ const queryTF = computeTF(queryTokens);
540
+ const queryVec = computeTFIDF(queryTF, index.idf);
541
+ const results = [];
542
+ for (const entry of index.entries) {
543
+ const ns = nameScore(queryTokens, entry.name);
544
+ const ks = keywordScore(queryTokens, entry.tokens);
545
+ const ts = cosineSimilarity(queryVec, entry.tfidf);
546
+ const score = NAME_WEIGHT * ns + KEYWORD_WEIGHT * ks + TFIDF_WEIGHT * ts;
547
+ if (score > 0.01) {
548
+ results.push({
549
+ name: entry.name,
550
+ kind: entry.kind,
551
+ summary: entry.summary,
552
+ score: Math.round(score * 100) / 100
553
+ });
554
+ }
555
+ }
556
+ results.sort((a, b) => b.score - a.score);
557
+ return results;
558
+ }
559
+
560
+ // src/commands/components.ts
361
561
  async function commandComponents(system, args) {
362
562
  const [subcommand, ...rest] = args;
363
563
  if (!subcommand || subcommand === "list") {
364
564
  return listAllComponents(system);
365
565
  }
566
+ if (subcommand === "inventory") {
567
+ return showInventory(system);
568
+ }
366
569
  if (subcommand === "details") {
367
570
  const name = rest[0];
368
571
  if (!name) {
@@ -371,6 +574,17 @@ async function commandComponents(system, args) {
371
574
  }
372
575
  return showDetails(system, name);
373
576
  }
577
+ if (subcommand === "search") {
578
+ const query = rest.join(" ");
579
+ if (!query) {
580
+ error("Usage: ds <system> components search <query>");
581
+ return 1;
582
+ }
583
+ return showSearch(system, query);
584
+ }
585
+ if (subcommand === "index") {
586
+ return rebuildIndex(system);
587
+ }
374
588
  return showDetails(system, subcommand);
375
589
  }
376
590
  function listAllComponents(system) {
@@ -385,14 +599,20 @@ function listAllComponents(system) {
385
599
  if (molecules.length > 0) {
386
600
  subheading(`Molecules (${molecules.length})`);
387
601
  descriptionList(
388
- molecules.map((c) => ({ name: c.name, description: c.description })),
602
+ molecules.map((c) => ({
603
+ name: c.tag ? `${c.name} ${c.tag}` : c.name,
604
+ description: c.description
605
+ })),
389
606
  4
390
607
  );
391
608
  }
392
609
  if (cells.length > 0) {
393
610
  subheading(`Cells (${cells.length})`);
394
611
  descriptionList(
395
- cells.map((c) => ({ name: c.name, description: c.description })),
612
+ cells.map((c) => ({
613
+ name: c.tag ? `${c.name} ${c.tag}` : c.name,
614
+ description: c.description
615
+ })),
396
616
  4
397
617
  );
398
618
  }
@@ -408,49 +628,272 @@ function showDetails(system, name) {
408
628
  }
409
629
  const sysDir = systemDir(system);
410
630
  const relPath = relative(sysDir, found.dir);
631
+ const json = readComponentJson(found.dir);
411
632
  heading(`${name}`);
412
633
  console.log(` Kind: ${found.kind}`);
634
+ if (json?.tag) console.log(` Tag: ${json.tag}`);
413
635
  console.log(` Path: ${relPath}`);
414
636
  const files = readdirSync2(found.dir);
415
637
  console.log(` Files: ${files.join(", ")}`);
416
638
  blank();
417
- const descPath = join2(found.dir, "description.md");
418
- if (existsSync2(descPath)) {
639
+ if (json?.description) {
419
640
  subheading("Description");
420
641
  blank();
642
+ console.log(` ${json.description}`);
643
+ blank();
644
+ }
645
+ if (json?.example) {
646
+ subheading("Example");
647
+ blank();
648
+ console.log("```xml");
649
+ console.log(json.example);
650
+ console.log("```");
651
+ blank();
652
+ }
653
+ const descPath = join4(found.dir, "description.md");
654
+ if (existsSync4(descPath)) {
655
+ subheading("Description (full)");
656
+ blank();
421
657
  console.log(readFile(descPath));
422
658
  }
423
659
  const sources = findComponentSources(system, name);
424
660
  if (sources.css.length > 0) {
425
661
  for (const cssPath of sources.css) {
426
- subheading(`Source CSS: ${basename2(cssPath)}`);
662
+ subheading(`Source CSS: ${basename(cssPath)}`);
427
663
  blank();
428
664
  console.log("```css");
429
- console.log(readFileSync2(cssPath, "utf-8").trimEnd());
665
+ console.log(readFileSync4(cssPath, "utf-8").trimEnd());
430
666
  console.log("```");
431
667
  blank();
432
668
  }
433
669
  }
434
670
  if (sources.js.length > 0) {
435
671
  for (const jsPath of sources.js) {
436
- subheading(`Source JS: ${basename2(jsPath)}`);
672
+ subheading(`Source JS: ${basename(jsPath)}`);
437
673
  blank();
438
674
  console.log("```js");
439
- console.log(readFileSync2(jsPath, "utf-8").trimEnd());
675
+ console.log(readFileSync4(jsPath, "utf-8").trimEnd());
440
676
  console.log("```");
441
677
  blank();
442
678
  }
443
679
  }
444
680
  return 0;
445
681
  }
682
+ function showInventory(system) {
683
+ const components = listComponents(system);
684
+ if (components.length === 0) {
685
+ console.log(`No components found in ${system}.`);
686
+ return 0;
687
+ }
688
+ const molecules = components.filter((c) => c.kind === "molecules");
689
+ const cells = components.filter((c) => c.kind === "cells");
690
+ const printTable = (items) => {
691
+ console.log("| Name | Tag | Description |");
692
+ console.log("|------|-----|-------------|");
693
+ for (const c of items) {
694
+ const tag = c.tag ? `\`${c.tag}\`` : "";
695
+ console.log(`| ${c.name} | ${tag} | ${c.description} |`);
696
+ }
697
+ };
698
+ console.log(`# ${system} \u2014 Component Inventory
699
+ `);
700
+ if (molecules.length > 0) {
701
+ console.log(`## Molecules (${molecules.length})
702
+ `);
703
+ printTable(molecules);
704
+ console.log();
705
+ }
706
+ if (cells.length > 0) {
707
+ console.log(`## Cells (${cells.length})
708
+ `);
709
+ printTable(cells);
710
+ console.log();
711
+ }
712
+ return 0;
713
+ }
714
+ function showSearch(system, query) {
715
+ const results = searchComponents(system, query);
716
+ const total = listComponents(system).length;
717
+ console.log(`
718
+ Search: "${query}" (${total} components indexed)`);
719
+ console.log("\u2500".repeat(50));
720
+ if (results.length === 0) {
721
+ console.log("\n No matches found.\n");
722
+ return 0;
723
+ }
724
+ console.log(`
725
+ Results (${results.length} match${results.length === 1 ? "" : "es"})
726
+ `);
727
+ const maxName = Math.max(...results.map((r) => r.name.length));
728
+ const maxKind = Math.max(...results.map((r) => r.kind.length + 2));
729
+ for (const r of results) {
730
+ const name = r.name.padEnd(maxName);
731
+ const kind = `(${r.kind})`.padEnd(maxKind);
732
+ const score = r.score.toFixed(2);
733
+ console.log(` ${name} ${kind} ${score} ${r.summary}`);
734
+ }
735
+ blank();
736
+ return 0;
737
+ }
738
+ function rebuildIndex(system) {
739
+ console.log(`Building search index for ${system}...`);
740
+ const index = buildSearchIndex(system);
741
+ writeSearchIndex(system, index);
742
+ console.log(` Documents: ${index.documentCount}`);
743
+ console.log(` Terms: ${Object.keys(index.idf).length}`);
744
+ console.log(` Written: ${index.buildTime}`);
745
+ return 0;
746
+ }
747
+
748
+ // src/commands/references.ts
749
+ import { existsSync as existsSync5, readFileSync as readFileSync5, readdirSync as readdirSync3 } from "fs";
750
+ import { join as join5, extname } from "path";
751
+ function referencesDir(system) {
752
+ return join5(systemDir(system), "references");
753
+ }
754
+ function loadManifest2(system) {
755
+ const path = join5(referencesDir(system), "manifest.json");
756
+ if (!existsSync5(path)) return null;
757
+ return JSON.parse(readFileSync5(path, "utf-8"));
758
+ }
759
+ function listReferenceFiles(system) {
760
+ const dir = referencesDir(system);
761
+ if (!existsSync5(dir)) return [];
762
+ return readdirSync3(dir).filter((f) => f !== "manifest.json").sort();
763
+ }
764
+ function commandReferences(system, segments) {
765
+ const dir = referencesDir(system);
766
+ if (!existsSync5(dir)) {
767
+ error(`No references directory for "${system}"`);
768
+ return 1;
769
+ }
770
+ if (segments.length === 0) {
771
+ const files = listReferenceFiles(system);
772
+ if (files.length === 0) {
773
+ console.log("No reference files.");
774
+ return 0;
775
+ }
776
+ const manifest = loadManifest2(system);
777
+ const lookup = /* @__PURE__ */ new Map();
778
+ if (manifest) {
779
+ for (const entry of manifest.references) {
780
+ lookup.set(entry.file, entry);
781
+ }
782
+ }
783
+ heading(`References (${files.length})`);
784
+ descriptionList(
785
+ files.map((f) => {
786
+ const entry = lookup.get(f);
787
+ return {
788
+ name: f,
789
+ description: entry ? entry.description : ""
790
+ };
791
+ })
792
+ );
793
+ return 0;
794
+ }
795
+ const fileName = segments.join("/");
796
+ const filePath = join5(dir, fileName);
797
+ if (!existsSync5(filePath)) {
798
+ const files = listReferenceFiles(system);
799
+ const suggestions = files.filter(
800
+ (f) => f.includes(fileName) || fileName.includes(f.replace(extname(f), ""))
801
+ );
802
+ error(`Reference "${fileName}" not found`, suggestions);
803
+ return 1;
804
+ }
805
+ const content = readFileSync5(filePath, "utf-8");
806
+ process.stdout.write(content);
807
+ return 0;
808
+ }
446
809
 
447
810
  // src/commands/render.ts
448
- import { readFileSync as readFileSync4, existsSync as existsSync5 } from "fs";
811
+ import { readFileSync as readFileSync8, existsSync as existsSync8 } from "fs";
449
812
  import { resolve as resolve2 } from "path";
450
813
 
451
814
  // src/resolve-deps.ts
452
- import { readFileSync as readFileSync3, existsSync as existsSync3 } from "fs";
453
- import { join as join3, relative as relative2 } from "path";
815
+ import { readFileSync as readFileSync7, existsSync as existsSync6 } from "fs";
816
+ import { join as join7, relative as relative2 } from "path";
817
+
818
+ // src/tokens.ts
819
+ import { readFileSync as readFileSync6, readdirSync as readdirSync4 } from "fs";
820
+ import { join as join6, basename as basename2 } from "path";
821
+ function loadTokens(system) {
822
+ const dir = join6(systemDir(system), "tokens");
823
+ const files = readdirSync4(dir).filter((f) => f.endsWith(".json")).sort();
824
+ return files.map((f) => ({
825
+ name: basename2(f, ".json"),
826
+ data: JSON.parse(readFileSync6(join6(dir, f), "utf-8"))
827
+ }));
828
+ }
829
+ function walkTokens(obj, path = []) {
830
+ const leaves = [];
831
+ for (const [key, val] of Object.entries(obj)) {
832
+ if (key.startsWith("$")) continue;
833
+ const child = val;
834
+ if (child.$type !== void 0 && child.$value !== void 0) {
835
+ leaves.push({ path: [...path, key], type: child.$type, value: child.$value });
836
+ } else if (typeof child === "object" && child !== null) {
837
+ leaves.push(...walkTokens(child, [...path, key]));
838
+ }
839
+ }
840
+ return leaves;
841
+ }
842
+ function toCssVarName(path) {
843
+ return `--${path.join("-")}`;
844
+ }
845
+ function formatValue(type, value) {
846
+ switch (type) {
847
+ case "color":
848
+ case "dimension":
849
+ case "gradient":
850
+ case "duration":
851
+ return String(value);
852
+ case "fontFamily": {
853
+ const families = value;
854
+ const generics = ["serif", "sans-serif", "monospace", "cursive", "fantasy", "system-ui"];
855
+ return families.map((f) => {
856
+ if (generics.includes(f) || !/\s/.test(f)) return f;
857
+ return f.includes("'") ? `"${f}"` : `'${f}'`;
858
+ }).join(", ");
859
+ }
860
+ // Composite types — skip
861
+ case "typography":
862
+ case "object":
863
+ case "cubicBezier":
864
+ return null;
865
+ default:
866
+ return null;
867
+ }
868
+ }
869
+ function generateTokensCss(system) {
870
+ const tokenFiles = loadTokens(system);
871
+ const groups = [];
872
+ for (const { name, data } of tokenFiles) {
873
+ const leaves = walkTokens(data);
874
+ const vars = [];
875
+ for (const leaf of leaves) {
876
+ const formatted = formatValue(leaf.type, leaf.value);
877
+ if (formatted !== null) {
878
+ vars.push(` ${toCssVarName(leaf.path)}: ${formatted};`);
879
+ }
880
+ }
881
+ if (vars.length > 0) {
882
+ groups.push({ name, vars });
883
+ }
884
+ }
885
+ const lines = ["@layer tokens {", " :root {"];
886
+ for (let i = 0; i < groups.length; i++) {
887
+ const { name, vars } = groups[i];
888
+ if (i > 0) lines.push("");
889
+ lines.push(` /* ${name}.json */`);
890
+ lines.push(...vars);
891
+ }
892
+ lines.push(" }", "}");
893
+ return lines.join("\n") + "\n";
894
+ }
895
+
896
+ // src/resolve-deps.ts
454
897
  function buildRegistry(system) {
455
898
  const components = listComponents(system);
456
899
  const entries = [];
@@ -458,14 +901,14 @@ function buildRegistry(system) {
458
901
  for (const comp of components) {
459
902
  const found = findComponent(system, comp.name);
460
903
  if (!found) continue;
461
- const scriptPath = join3(found.dir, "script.js");
462
- const stylePath = join3(found.dir, "style.css");
463
- const hasJs = existsSync3(scriptPath);
464
- const hasCss = existsSync3(stylePath);
904
+ const scriptPath = join7(found.dir, "component.js");
905
+ const stylePath = join7(found.dir, "style.css");
906
+ const hasJs = existsSync6(scriptPath);
907
+ const hasCss = existsSync6(stylePath);
465
908
  const tags = [];
466
909
  const dynamicTags = [];
467
910
  if (hasJs) {
468
- const js = readFileSync3(scriptPath, "utf-8");
911
+ const js = readFileSync7(scriptPath, "utf-8");
469
912
  for (const m of js.matchAll(/customElements\.define\(\s*['"]([^'"]+)['"]/g)) {
470
913
  tags.push(m[1]);
471
914
  }
@@ -480,7 +923,7 @@ function buildRegistry(system) {
480
923
  }
481
924
  }
482
925
  if (hasCss) {
483
- const css = readFileSync3(stylePath, "utf-8");
926
+ const css = readFileSync7(stylePath, "utf-8");
484
927
  for (const m of css.matchAll(/(?:^|[\s,}>+~])([a-z][a-z0-9]*-[a-z0-9-]*)(?=[\s,{[:.>+~]|$)/gm)) {
485
928
  const tag = m[1];
486
929
  if (!tags.includes(tag)) tags.push(tag);
@@ -538,35 +981,43 @@ function resolveMarkup(registry, markup) {
538
981
  const cssFiles = [];
539
982
  const jsFiles = [];
540
983
  for (const entry of matched) {
541
- if (entry.hasCss) cssFiles.push(join3(entry.dir, "style.css"));
542
- if (entry.hasJs) jsFiles.push(join3(entry.dir, "script.js"));
984
+ if (entry.hasCss) cssFiles.push(join7(entry.dir, "style.css"));
985
+ if (entry.hasJs) jsFiles.push(join7(entry.dir, "component.js"));
543
986
  }
544
987
  return { cssFiles, jsFiles, unmatchedTags };
545
988
  }
546
- function buildPreviewHtml(system, markup, deps) {
989
+ async function bundleJs(system) {
990
+ const entrypoint = join7(systemDir(system), "src", "index.js");
991
+ if (!existsSync6(entrypoint)) return null;
992
+ const result = await Bun.build({
993
+ entrypoints: [entrypoint],
994
+ bundle: true
995
+ });
996
+ if (!result.success) {
997
+ console.warn("Bundle failed:", result.logs);
998
+ return null;
999
+ }
1000
+ return await result.outputs[0].text();
1001
+ }
1002
+ function buildPreviewHtml(system, markup, deps, bundledJs) {
547
1003
  const sysDir = systemDir(system);
548
- const sharedCss = [
549
- join3(sysDir, "src", "_shared", "_tokens.css"),
550
- join3(sysDir, "src", "_shared", "_base.css")
551
- ];
552
- const utilitiesCss = join3(sysDir, "src", "_shared", "_utilities.css");
553
- const blocks = ["@layer tokens, base, components, utilities;"];
554
- for (const p of sharedCss) {
555
- if (existsSync3(p)) {
556
- blocks.push(`/* ${relative2(sysDir, p)} */`);
557
- blocks.push(readFileSync3(p, "utf-8"));
558
- }
1004
+ const blocks = ["@layer tokens, base, components;"];
1005
+ const tokensCss = generateTokensCss(system);
1006
+ blocks.push("/* tokens (generated) */");
1007
+ blocks.push(tokensCss);
1008
+ const baseCss = join7(sysDir, "src", "_shared", "_base.css");
1009
+ if (existsSync6(baseCss)) {
1010
+ blocks.push(`/* ${relative2(sysDir, baseCss)} */`);
1011
+ blocks.push(readFileSync7(baseCss, "utf-8"));
559
1012
  }
560
1013
  for (const p of deps.cssFiles) {
561
1014
  blocks.push(`/* ${relative2(sysDir, p)} */`);
562
- blocks.push(readFileSync3(p, "utf-8"));
563
- }
564
- if (existsSync3(utilitiesCss)) {
565
- blocks.push(`/* ${relative2(sysDir, utilitiesCss)} */`);
566
- blocks.push(readFileSync3(utilitiesCss, "utf-8"));
1015
+ blocks.push(readFileSync7(p, "utf-8"));
567
1016
  }
568
1017
  const inlinedCss = blocks.join("\n");
569
- const scriptTags = deps.jsFiles.map((p) => ` <script type="module" src="/${relative2(sysDir, p)}"></script>`).join("\n");
1018
+ const scriptTags = bundledJs ? ` <script>
1019
+ ${bundledJs}
1020
+ </script>` : deps.jsFiles.map((p) => ` <script type="module" src="/${relative2(sysDir, p)}"></script>`).join("\n");
570
1021
  return `<!DOCTYPE html>
571
1022
  <html lang="en">
572
1023
  <head>
@@ -585,9 +1036,9 @@ ${scriptTags}
585
1036
 
586
1037
  // src/preview-server.ts
587
1038
  import { createServer } from "http";
588
- import { join as join4, extname } from "path";
1039
+ import { join as join8, extname as extname2 } from "path";
589
1040
  import { readFile as readFile2 } from "fs/promises";
590
- import { existsSync as existsSync4 } from "fs";
1041
+ import { existsSync as existsSync7 } from "fs";
591
1042
  var MIME_TYPES = {
592
1043
  ".html": "text/html; charset=utf-8",
593
1044
  ".css": "text/css; charset=utf-8",
@@ -613,15 +1064,15 @@ function startPreviewServer(systemDir2, opts) {
613
1064
  res.end(opts.html);
614
1065
  return;
615
1066
  }
616
- const filePath = join4(systemDir2, decodeURIComponent(url.pathname));
617
- if (!existsSync4(filePath)) {
1067
+ const filePath = join8(systemDir2, decodeURIComponent(url.pathname));
1068
+ if (!existsSync7(filePath)) {
618
1069
  res.writeHead(404);
619
1070
  res.end("Not found");
620
1071
  return;
621
1072
  }
622
1073
  try {
623
1074
  const data = await readFile2(filePath);
624
- const ext = extname(filePath).toLowerCase();
1075
+ const ext = extname2(filePath).toLowerCase();
625
1076
  const contentType = MIME_TYPES[ext] ?? "application/octet-stream";
626
1077
  res.writeHead(200, { "Content-Type": contentType });
627
1078
  res.end(data);
@@ -672,18 +1123,19 @@ async function commandRender(system, fileArg, flags) {
672
1123
  markup = await readStdin();
673
1124
  } else {
674
1125
  const filePath = resolve2(fileArg);
675
- if (!existsSync5(filePath)) {
1126
+ if (!existsSync8(filePath)) {
676
1127
  error(`File not found: ${filePath}`);
677
1128
  return 1;
678
1129
  }
679
- markup = readFileSync4(filePath, "utf-8");
1130
+ markup = readFileSync8(filePath, "utf-8");
680
1131
  }
681
1132
  const registry = buildRegistry(system);
682
1133
  const deps = resolveMarkup(registry, markup);
683
1134
  for (const tag of deps.unmatchedTags) {
684
1135
  console.warn(` warn: unmatched custom element <${tag}>`);
685
1136
  }
686
- const html = buildPreviewHtml(system, markup, deps);
1137
+ const js = await bundleJs(system);
1138
+ const html = buildPreviewHtml(system, markup, deps, js);
687
1139
  if (flags.html) {
688
1140
  process.stdout.write(html);
689
1141
  return 0;
@@ -720,27 +1172,12 @@ async function commandBrowse(system, path, flags) {
720
1172
  switch (section) {
721
1173
  case "components":
722
1174
  return commandComponents(system, segments.slice(1));
1175
+ case "references":
1176
+ return commandReferences(system, segments.slice(1));
723
1177
  case "render":
724
1178
  return commandRender(system, segments.slice(1).join("/"), flags);
725
- case "tokens":
726
- return handleTokens(system, segments.slice(1), flags);
727
- case "principles":
728
- return handlePrinciples(system, segments.slice(1));
729
- case "architecture":
730
- return handleArchitecture(system, segments.slice(1));
731
- case "examples":
732
- return handleExamples(system, segments.slice(1), flags);
733
- case "references":
734
- return handleReferences(system, segments.slice(1));
735
1179
  default:
736
- error(`Unknown section "${section}"`, [
737
- "components",
738
- "tokens",
739
- "principles",
740
- "architecture",
741
- "examples",
742
- "references"
743
- ]);
1180
+ error(`Unknown section "${section}"`, ["components", "references"]);
744
1181
  return 1;
745
1182
  }
746
1183
  }
@@ -748,198 +1185,6 @@ function openInBrowser(target) {
748
1185
  const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
749
1186
  exec(`${cmd} "${target}"`);
750
1187
  }
751
- function handleTokens(system, segments, flags) {
752
- if (segments.length === 0) {
753
- return listAllTokens(system);
754
- }
755
- return showTokenFile(system, segments[0], flags);
756
- }
757
- function listAllTokens(system) {
758
- const files = listTokenFiles(system);
759
- if (files.length === 0) {
760
- console.log(`No token files found in ${system}.`);
761
- return 0;
762
- }
763
- heading(`${system} \u2014 Tokens`);
764
- for (const name of files) {
765
- const data = readTokenFile(system, name);
766
- if (!data) continue;
767
- console.log(` ${name}: ${tokenSummary(data)}`);
768
- }
769
- blank();
770
- return 0;
771
- }
772
- function showTokenFile(system, name, flags) {
773
- const data = readTokenFile(system, name);
774
- if (!data) {
775
- const available = listTokenFiles(system);
776
- const suggestions = available.filter(
777
- (f) => f.includes(name) || name.includes(f)
778
- );
779
- error(
780
- `Token file "${name}" not found in ${system}`,
781
- suggestions.length > 0 ? suggestions : available
782
- );
783
- return 1;
784
- }
785
- if (flags.json) {
786
- renderJson(data);
787
- return 0;
788
- }
789
- if (flags.flat) {
790
- renderFlat(data);
791
- return 0;
792
- }
793
- renderTokens(data);
794
- return 0;
795
- }
796
- function handlePrinciples(system, segments) {
797
- if (segments.length === 0) {
798
- return listAllPrinciples(system);
799
- }
800
- return showPrinciple(system, segments[0]);
801
- }
802
- function listAllPrinciples(system) {
803
- const principles = listPrinciples(system);
804
- if (principles.length === 0) {
805
- console.log(`No principles found in ${system}.`);
806
- return 0;
807
- }
808
- heading(`${system} \u2014 Principles`);
809
- descriptionList(
810
- principles.map((p) => ({ name: p.name, description: p.title }))
811
- );
812
- blank();
813
- return 0;
814
- }
815
- function showPrinciple(system, name) {
816
- const content = readPrinciple(system, name);
817
- if (!content) {
818
- const available = listPrinciples(system);
819
- const suggestions = available.filter((p) => p.name.includes(name) || name.includes(p.name)).map((p) => p.name);
820
- error(
821
- `Principle "${name}" not found in ${system}`,
822
- suggestions.length > 0 ? suggestions : available.map((p) => p.name)
823
- );
824
- return 1;
825
- }
826
- markdown(content);
827
- return 0;
828
- }
829
- function handleArchitecture(system, segments) {
830
- if (!hasArchitecture(system)) {
831
- console.log(`No architecture section in ${system}.`);
832
- return 0;
833
- }
834
- const subpath = segments.length > 0 ? segments.join("/") : void 0;
835
- if (subpath) {
836
- const content = readArchitectureFile(system, subpath);
837
- if (content !== null) {
838
- markdown(content);
839
- return 0;
840
- }
841
- const contentMd = readArchitectureFile(system, subpath + ".md");
842
- if (contentMd !== null) {
843
- markdown(contentMd);
844
- return 0;
845
- }
846
- if (isArchitectureDir(system, subpath)) {
847
- return listArchitectureDir(system, subpath);
848
- }
849
- error(`Architecture path "${subpath}" not found in ${system}`);
850
- return 1;
851
- }
852
- return listArchitectureDir(system, void 0);
853
- }
854
- function listArchitectureDir(system, subpath) {
855
- const { entries, indexContent } = listArchitecture(system, subpath);
856
- const label = subpath ? `${system} \u2014 Architecture / ${subpath}` : `${system} \u2014 Architecture`;
857
- heading(label);
858
- if (indexContent) {
859
- markdown(indexContent);
860
- blank();
861
- }
862
- if (entries.length > 0) {
863
- for (const entry of entries) {
864
- const suffix = entry.isDir ? "/" : "";
865
- console.log(` ${entry.name}${suffix}`);
866
- }
867
- blank();
868
- }
869
- return 0;
870
- }
871
- function handleExamples(system, segments, flags) {
872
- if (segments.length === 0) {
873
- return listAllExamples(system);
874
- }
875
- return showExample(system, segments[0], flags);
876
- }
877
- function listAllExamples(system) {
878
- const examples = listExamples(system);
879
- if (examples.length === 0) {
880
- console.log(`No examples found in ${system}.`);
881
- return 0;
882
- }
883
- heading(`${system} \u2014 Examples`);
884
- list(examples);
885
- blank();
886
- return 0;
887
- }
888
- function showExample(system, name, flags) {
889
- let filename = name;
890
- if (!filename.endsWith(".html")) filename += ".html";
891
- const filePath = join5(systemDir(system), "examples", filename);
892
- if (!existsSync6(filePath)) {
893
- const available = listExamples(system);
894
- const suggestions = available.filter(
895
- (e) => e.includes(name) || name.includes(e.replace(".html", ""))
896
- );
897
- error(
898
- `Example "${name}" not found in ${system}`,
899
- suggestions.length > 0 ? suggestions.map((s) => s.replace(".html", "")) : available.map((s) => s.replace(".html", ""))
900
- );
901
- return 1;
902
- }
903
- if (flags.open) {
904
- openInBrowser(filePath);
905
- console.log(`Opening ${filename} in browser...`);
906
- return 0;
907
- }
908
- console.log(`Example: ${filename}`);
909
- console.log(`Path: ${filePath}`);
910
- console.log(`Use --open to view in browser.`);
911
- return 0;
912
- }
913
- function handleReferences(system, segments) {
914
- if (segments.length === 0) {
915
- return listAllReferences(system);
916
- }
917
- const name = segments.join("/");
918
- const filePath = join5(systemDir(system), "references", name);
919
- if (!existsSync6(filePath)) {
920
- const available = listReferences(system);
921
- error(`Reference "${name}" not found in ${system}`, available);
922
- return 1;
923
- }
924
- if (name.endsWith(".md")) {
925
- markdown(readFile(filePath));
926
- return 0;
927
- }
928
- console.log(`Reference: ${name}`);
929
- console.log(`Path: ${filePath}`);
930
- return 0;
931
- }
932
- function listAllReferences(system) {
933
- const refs = listReferences(system);
934
- if (refs.length === 0) {
935
- console.log(`No references found in ${system}.`);
936
- return 0;
937
- }
938
- heading(`${system} \u2014 References`);
939
- list(refs);
940
- blank();
941
- return 0;
942
- }
943
1188
 
944
1189
  // src/cli.ts
945
1190
  function showHelp() {
@@ -950,25 +1195,21 @@ Usage:
950
1195
  design-system list List all design systems
951
1196
  design-system <system> Show system overview
952
1197
  design-system <system> components list List all components
1198
+ design-system <system> components inventory Generate markdown inventory table
953
1199
  design-system <system> components details <n> Component description + source CSS/JS
954
- design-system <system> tokens List token files
955
- design-system <system> tokens/<name> Pretty-print token values
956
- design-system <system> principles List principles
957
- design-system <system> principles/<name> Show principle content
958
- design-system <system> architecture List architecture areas
959
- design-system <system> architecture/<path> Navigate architecture tree
1200
+ design-system <system> components search <q> Search components by query
1201
+ design-system <system> components index Rebuild search index
1202
+ design-system <system> references List reference files
1203
+ design-system <system> references <file> Display reference file
960
1204
  design-system <system> render <file> Render markup with resolved deps
961
1205
  design-system <system> render - Render from stdin
962
- design-system <system> examples List example files
963
- design-system <system> references List reference files
964
1206
 
965
1207
  Flags:
966
- --open Open HTML file in browser (examples)
967
- --json Output raw JSON (tokens)
968
- --flat Output flat path = value lines (tokens)
969
1208
  --html Dump raw HTML to stdout
970
- --systems-dir <path> Override default systems directory
971
1209
  --help, -h Show this help message
1210
+
1211
+ Environment:
1212
+ DESIGN_SYSTEMS_DIR Path to directory containing design system definitions (required)
972
1213
  `);
973
1214
  }
974
1215
  async function main() {
@@ -976,18 +1217,11 @@ async function main() {
976
1217
  args: process.argv.slice(2),
977
1218
  options: {
978
1219
  help: { type: "boolean", short: "h" },
979
- open: { type: "boolean" },
980
- json: { type: "boolean" },
981
- flat: { type: "boolean" },
982
- html: { type: "boolean" },
983
- "systems-dir": { type: "string" }
1220
+ html: { type: "boolean" }
984
1221
  },
985
1222
  allowPositionals: true,
986
1223
  strict: true
987
1224
  });
988
- if (values["systems-dir"]) {
989
- setSystemsRoot(values["systems-dir"]);
990
- }
991
1225
  if (values.help && positionals.length === 0) {
992
1226
  showHelp();
993
1227
  process.exit(0);
@@ -1012,9 +1246,6 @@ async function main() {
1012
1246
  process.exit(await commandRender(first, rest.slice(1).join(" "), { html: values.html }));
1013
1247
  }
1014
1248
  const flags = {
1015
- open: values.open,
1016
- json: values.json,
1017
- flat: values.flat,
1018
1249
  html: values.html
1019
1250
  };
1020
1251
  process.exit(await commandBrowse(first, second, flags));