@decantr/cli 1.0.0 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,1965 +1,3 @@
1
- #!/usr/bin/env node
2
- import {
3
- RegistryClient,
4
- syncRegistry
5
- } from "./chunk-PWTUBGGJ.js";
6
- import {
7
- __require
8
- } from "./chunk-PDX44BCA.js";
9
-
10
- // src/index.ts
11
- import { readFileSync as readFileSync4, existsSync as existsSync4 } from "fs";
12
- import { join as join4 } from "path";
13
- import { validateEssence, evaluateGuard } from "@decantr/essence-spec";
14
- import { createResolver, createRegistryClient } from "@decantr/registry";
15
-
16
- // src/detect.ts
17
- import { existsSync, readFileSync } from "fs";
18
- import { join } from "path";
19
- var RULE_FILES = [
20
- "CLAUDE.md",
21
- ".cursorrules",
22
- ".cursor/rules",
23
- "AGENTS.md",
24
- "GEMINI.md",
25
- "copilot-instructions.md"
26
- ];
27
- function detectProject(projectRoot = process.cwd()) {
28
- const result = {
29
- framework: "unknown",
30
- packageManager: "unknown",
31
- hasTypeScript: false,
32
- hasTailwind: false,
33
- existingRuleFiles: [],
34
- existingEssence: false,
35
- projectRoot
36
- };
37
- result.existingEssence = existsSync(join(projectRoot, "decantr.essence.json"));
38
- for (const ruleFile of RULE_FILES) {
39
- if (existsSync(join(projectRoot, ruleFile))) {
40
- result.existingRuleFiles.push(ruleFile);
41
- }
42
- }
43
- if (existsSync(join(projectRoot, "pnpm-lock.yaml"))) {
44
- result.packageManager = "pnpm";
45
- } else if (existsSync(join(projectRoot, "yarn.lock"))) {
46
- result.packageManager = "yarn";
47
- } else if (existsSync(join(projectRoot, "bun.lockb"))) {
48
- result.packageManager = "bun";
49
- } else if (existsSync(join(projectRoot, "package-lock.json"))) {
50
- result.packageManager = "npm";
51
- }
52
- result.hasTypeScript = existsSync(join(projectRoot, "tsconfig.json"));
53
- result.hasTailwind = existsSync(join(projectRoot, "tailwind.config.js")) || existsSync(join(projectRoot, "tailwind.config.ts")) || existsSync(join(projectRoot, "tailwind.config.mjs")) || existsSync(join(projectRoot, "tailwind.config.cjs"));
54
- if (existsSync(join(projectRoot, "next.config.js")) || existsSync(join(projectRoot, "next.config.ts")) || existsSync(join(projectRoot, "next.config.mjs"))) {
55
- result.framework = "nextjs";
56
- result.version = getPackageVersion(projectRoot, "next");
57
- return result;
58
- }
59
- if (existsSync(join(projectRoot, "nuxt.config.js")) || existsSync(join(projectRoot, "nuxt.config.ts"))) {
60
- result.framework = "nuxt";
61
- result.version = getPackageVersion(projectRoot, "nuxt");
62
- return result;
63
- }
64
- if (existsSync(join(projectRoot, "astro.config.mjs")) || existsSync(join(projectRoot, "astro.config.ts"))) {
65
- result.framework = "astro";
66
- result.version = getPackageVersion(projectRoot, "astro");
67
- return result;
68
- }
69
- if (existsSync(join(projectRoot, "svelte.config.js")) || existsSync(join(projectRoot, "svelte.config.ts"))) {
70
- result.framework = "svelte";
71
- result.version = getPackageVersion(projectRoot, "svelte");
72
- return result;
73
- }
74
- if (existsSync(join(projectRoot, "angular.json"))) {
75
- result.framework = "angular";
76
- result.version = getPackageVersion(projectRoot, "@angular/core");
77
- return result;
78
- }
79
- const packageJsonPath = join(projectRoot, "package.json");
80
- if (existsSync(packageJsonPath)) {
81
- try {
82
- const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
83
- const deps = { ...packageJson.dependencies, ...packageJson.devDependencies };
84
- if (deps.next) {
85
- result.framework = "nextjs";
86
- result.version = deps.next.replace(/^\^|~/, "");
87
- } else if (deps.nuxt) {
88
- result.framework = "nuxt";
89
- result.version = deps.nuxt.replace(/^\^|~/, "");
90
- } else if (deps.astro) {
91
- result.framework = "astro";
92
- result.version = deps.astro.replace(/^\^|~/, "");
93
- } else if (deps.svelte) {
94
- result.framework = "svelte";
95
- result.version = deps.svelte.replace(/^\^|~/, "");
96
- } else if (deps["@angular/core"]) {
97
- result.framework = "angular";
98
- result.version = deps["@angular/core"].replace(/^\^|~/, "");
99
- } else if (deps.vue) {
100
- result.framework = "vue";
101
- result.version = deps.vue.replace(/^\^|~/, "");
102
- } else if (deps.react) {
103
- result.framework = "react";
104
- result.version = deps.react.replace(/^\^|~/, "");
105
- }
106
- } catch {
107
- }
108
- }
109
- if (result.framework === "unknown" && !existsSync(packageJsonPath)) {
110
- if (existsSync(join(projectRoot, "index.html"))) {
111
- result.framework = "html";
112
- }
113
- }
114
- return result;
115
- }
116
- function getPackageVersion(projectRoot, packageName) {
117
- const packageJsonPath = join(projectRoot, "package.json");
118
- if (!existsSync(packageJsonPath)) return void 0;
119
- try {
120
- const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
121
- const deps = { ...packageJson.dependencies, ...packageJson.devDependencies };
122
- const version = deps[packageName];
123
- return version?.replace(/^\^|~/, "");
124
- } catch {
125
- return void 0;
126
- }
127
- }
128
-
129
- // src/prompts.ts
130
- import { createInterface } from "readline";
131
- var BOLD = "\x1B[1m";
132
- var DIM = "\x1B[2m";
133
- var RESET = "\x1B[0m";
134
- var GREEN = "\x1B[32m";
135
- var YELLOW = "\x1B[33m";
136
- var CYAN = "\x1B[36m";
137
- function ask(question, defaultValue) {
138
- const rl = createInterface({ input: process.stdin, output: process.stdout });
139
- const prompt = defaultValue ? `${question} ${DIM}(${defaultValue})${RESET}: ` : `${question}: `;
140
- return new Promise((resolve) => {
141
- rl.question(prompt, (answer) => {
142
- rl.close();
143
- resolve(answer.trim() || defaultValue || "");
144
- });
145
- });
146
- }
147
- async function select(question, options, defaultIdx = 0, allowOther = false) {
148
- console.log(`
149
- ${BOLD}${question}${RESET}`);
150
- for (let i = 0; i < options.length; i++) {
151
- const marker = i === defaultIdx ? `${GREEN}>${RESET}` : " ";
152
- const desc = options[i].description ? ` ${DIM}\u2014 ${options[i].description}${RESET}` : "";
153
- console.log(` ${marker} ${i + 1}. ${options[i].label}${desc}`);
154
- }
155
- if (allowOther) {
156
- console.log(` ${options.length + 1}. ${DIM}other (enter custom value)${RESET}`);
157
- }
158
- const maxIdx = allowOther ? options.length + 1 : options.length;
159
- const answer = await ask(`Choose (1-${maxIdx})`, String(defaultIdx + 1));
160
- const idx = parseInt(answer, 10) - 1;
161
- if (allowOther && idx === options.length) {
162
- const custom = await ask("Enter custom value");
163
- return custom;
164
- }
165
- const validIdx = Math.max(0, Math.min(idx, options.length - 1));
166
- return options[validIdx].value;
167
- }
168
- async function confirm(question, defaultYes = true) {
169
- const hint = defaultYes ? "Y/n" : "y/N";
170
- const answer = await ask(`${question} [${hint}]`, defaultYes ? "y" : "n");
171
- return answer.toLowerCase() === "y" || answer.toLowerCase() === "yes";
172
- }
173
- function warn(message) {
174
- console.log(`
175
- ${YELLOW} Warning: ${message}${RESET}`);
176
- }
177
- function showDetection(detected) {
178
- console.log(`
179
- ${CYAN}Detected project configuration:${RESET}`);
180
- if (detected.framework !== "unknown") {
181
- const version = detected.version ? ` ${detected.version}` : "";
182
- console.log(` Framework: ${detected.framework}${version}`);
183
- }
184
- if (detected.packageManager !== "unknown") {
185
- console.log(` Package manager: ${detected.packageManager}`);
186
- }
187
- if (detected.hasTypeScript) {
188
- console.log(` TypeScript: ${GREEN}yes${RESET}`);
189
- }
190
- if (detected.hasTailwind) {
191
- console.log(` Tailwind CSS: ${GREEN}yes${RESET}`);
192
- }
193
- if (detected.existingEssence) {
194
- console.log(` Existing essence: ${YELLOW}yes${RESET}`);
195
- }
196
- }
197
- async function runInteractivePrompts(detected, archetypes, blueprints, themes) {
198
- showDetection(detected);
199
- const blueprintOptions = [
200
- { value: "none", label: "none", description: "Start from scratch (blank canvas)" },
201
- ...blueprints.map((b) => ({
202
- value: b.id,
203
- label: b.id,
204
- description: b.description
205
- }))
206
- ];
207
- const blueprint = await select("What are you building?", blueprintOptions, 0, true);
208
- const isBlank = blueprint === "none";
209
- const themeOptions = themes.map((t) => ({
210
- value: t.id,
211
- label: t.id,
212
- description: t.description
213
- }));
214
- const defaultThemeIdx = themeOptions.findIndex((t) => t.value === "luminarum") || 0;
215
- const theme = await select("Choose a theme", themeOptions, Math.max(0, defaultThemeIdx), true);
216
- const mode = await select(
217
- "Color mode",
218
- [
219
- { value: "dark", label: "dark", description: "Dark background" },
220
- { value: "light", label: "light", description: "Light background" },
221
- { value: "auto", label: "auto", description: "Follow system preference" }
222
- ],
223
- 0
224
- );
225
- const shape = await select(
226
- "Border shape",
227
- [
228
- { value: "pill", label: "pill", description: "Fully rounded corners" },
229
- { value: "rounded", label: "rounded", description: "Moderately rounded" },
230
- { value: "sharp", label: "sharp", description: "No border radius" }
231
- ],
232
- 0,
233
- true
234
- );
235
- const frameworkOptions = [
236
- { value: "react", label: "react", description: "React / Create React App" },
237
- { value: "nextjs", label: "nextjs", description: "Next.js" },
238
- { value: "vue", label: "vue", description: "Vue.js" },
239
- { value: "nuxt", label: "nuxt", description: "Nuxt" },
240
- { value: "svelte", label: "svelte", description: "Svelte / SvelteKit" },
241
- { value: "astro", label: "astro", description: "Astro" },
242
- { value: "angular", label: "angular", description: "Angular" },
243
- { value: "html", label: "html", description: "Plain HTML/CSS/JS" }
244
- ];
245
- let defaultFrameworkIdx = frameworkOptions.findIndex((f) => f.value === detected.framework);
246
- if (defaultFrameworkIdx < 0) defaultFrameworkIdx = 0;
247
- const target = await select("Target framework", frameworkOptions, defaultFrameworkIdx, true);
248
- if (detected.framework !== "unknown" && target !== detected.framework) {
249
- warn(`This project appears to be ${detected.framework} but you selected ${target}.`);
250
- const proceed = await confirm("Continue anyway?", false);
251
- if (!proceed) {
252
- console.log(`${DIM}Using detected framework: ${detected.framework}${RESET}`);
253
- }
254
- }
255
- const guardMode = await select(
256
- "Guard enforcement level",
257
- [
258
- { value: "creative", label: "creative", description: "Advisory only (new projects)" },
259
- { value: "guided", label: "guided", description: "Style, structure, recipe enforced" },
260
- { value: "strict", label: "strict", description: "All 5 rules enforced exactly" }
261
- ],
262
- detected.existingEssence ? 1 : 2
263
- // Default to guided for existing, strict for new
264
- );
265
- const density = await select(
266
- "Spacing density",
267
- [
268
- { value: "compact", label: "compact", description: "Dense UI, minimal spacing" },
269
- { value: "comfortable", label: "comfortable", description: "Balanced spacing" },
270
- { value: "spacious", label: "spacious", description: "Generous whitespace" }
271
- ],
272
- 1
273
- );
274
- const shellOptions = [
275
- { value: "sidebar-main", label: "sidebar-main", description: "Collapsible sidebar with main content" },
276
- { value: "top-nav-main", label: "top-nav-main", description: "Horizontal nav with full-width content" },
277
- { value: "centered", label: "centered", description: "Centered card (auth flows)" },
278
- { value: "full-bleed", label: "full-bleed", description: "No persistent nav (landing pages)" },
279
- { value: "minimal-header", label: "minimal-header", description: "Slim header with centered content" }
280
- ];
281
- let defaultShellIdx = 0;
282
- if (["nextjs", "nuxt", "astro"].includes(target)) {
283
- defaultShellIdx = shellOptions.findIndex((s) => s.value === "top-nav-main");
284
- }
285
- const shell = await select("Default page shell (layout)", shellOptions, Math.max(0, defaultShellIdx), true);
286
- return {
287
- blueprint: isBlank ? void 0 : blueprint,
288
- archetype: void 0,
289
- // Will be derived from blueprint
290
- theme,
291
- mode,
292
- shape,
293
- target,
294
- guard: guardMode,
295
- density,
296
- shell,
297
- personality: ["professional"],
298
- features: [],
299
- existing: detected.existingEssence
300
- };
301
- }
302
- function parseFlags(args, detected) {
303
- const options = {};
304
- if (typeof args.blueprint === "string") options.blueprint = args.blueprint;
305
- if (typeof args.archetype === "string") options.archetype = args.archetype;
306
- if (typeof args.theme === "string") options.theme = args.theme;
307
- if (args.mode === "dark" || args.mode === "light" || args.mode === "auto") options.mode = args.mode;
308
- if (typeof args.shape === "string") options.shape = args.shape;
309
- if (typeof args.target === "string") options.target = args.target;
310
- if (args.guard === "creative" || args.guard === "guided" || args.guard === "strict") options.guard = args.guard;
311
- if (args.density === "compact" || args.density === "comfortable" || args.density === "spacious") options.density = args.density;
312
- if (typeof args.shell === "string") options.shell = args.shell;
313
- if (typeof args.personality === "string") options.personality = args.personality.split(",").map((s) => s.trim());
314
- if (typeof args.features === "string") options.features = args.features.split(",").map((s) => s.trim());
315
- if (args.existing === true) options.existing = true;
316
- return options;
317
- }
318
- function mergeWithDefaults(flags, detected) {
319
- return {
320
- blueprint: flags.blueprint,
321
- archetype: flags.archetype,
322
- theme: flags.theme || "luminarum",
323
- mode: flags.mode || "dark",
324
- shape: flags.shape || "rounded",
325
- target: flags.target || (detected.framework !== "unknown" ? detected.framework : "react"),
326
- guard: flags.guard || (detected.existingEssence ? "guided" : "strict"),
327
- density: flags.density || "comfortable",
328
- shell: flags.shell || "sidebar-main",
329
- personality: flags.personality || ["professional"],
330
- features: flags.features || [],
331
- existing: flags.existing || detected.existingEssence
332
- };
333
- }
334
- async function runSimplifiedInit(blueprints) {
335
- const rl = createInterface({ input: process.stdin, output: process.stdout });
336
- const question = (q) => new Promise((resolve) => rl.question(q, resolve));
337
- console.log("\n? What blueprint would you like to scaffold?\n");
338
- console.log(" 1. Decantr default (recommended)");
339
- console.log(" 2. Search registry...\n");
340
- const choice = await question("Enter choice (1 or 2): ");
341
- if (choice === "1" || choice === "") {
342
- rl.close();
343
- return { choice: "default" };
344
- }
345
- const searchQuery = await question("Search: ");
346
- const matches = blueprints.filter(
347
- (b) => b.id.toLowerCase().includes(searchQuery.toLowerCase()) || b.name?.toLowerCase().includes(searchQuery.toLowerCase()) || b.description?.toLowerCase().includes(searchQuery.toLowerCase())
348
- ).slice(0, 10);
349
- if (matches.length === 0) {
350
- console.log("\nNo matches found. Using Decantr default.");
351
- rl.close();
352
- return { choice: "default" };
353
- }
354
- console.log("\nResults:");
355
- matches.forEach((b, i) => {
356
- console.log(` ${i + 1}. ${b.id} \u2014 ${b.description || b.name || ""}`);
357
- });
358
- const selection = await question("\nSelect (number): ");
359
- const idx = parseInt(selection, 10) - 1;
360
- rl.close();
361
- if (idx >= 0 && idx < matches.length) {
362
- return { choice: "search", selectedBlueprint: matches[idx].id };
363
- }
364
- return { choice: "default" };
365
- }
366
-
367
- // src/scaffold.ts
368
- import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, writeFileSync, appendFileSync } from "fs";
369
- import { join as join2, dirname } from "path";
370
- import { fileURLToPath } from "url";
371
- var __dirname = dirname(fileURLToPath(import.meta.url));
372
- var CLI_VERSION = "1.0.0";
373
- function serializeLayoutItem(item) {
374
- if (typeof item === "string") {
375
- return item;
376
- }
377
- if (typeof item === "object" && item !== null) {
378
- const obj = item;
379
- if (typeof obj.pattern === "string") {
380
- const preset = obj.preset ? ` (${obj.preset})` : "";
381
- const alias = obj.as ? ` as ${obj.as}` : "";
382
- return `${obj.pattern}${preset}${alias}`;
383
- }
384
- if (Array.isArray(obj.cols)) {
385
- const cols = obj.cols.map(serializeLayoutItem).join(" | ");
386
- const breakpoint = obj.at ? ` @${obj.at}` : "";
387
- return `[${cols}]${breakpoint}`;
388
- }
389
- }
390
- return "custom";
391
- }
392
- function extractPatternNames(item) {
393
- if (typeof item === "string") {
394
- return [item];
395
- }
396
- if (typeof item === "object" && item !== null) {
397
- const obj = item;
398
- if (typeof obj.pattern === "string") {
399
- return [obj.pattern];
400
- }
401
- if (Array.isArray(obj.cols)) {
402
- return obj.cols.flatMap(extractPatternNames);
403
- }
404
- }
405
- return [];
406
- }
407
- function loadTemplate(name) {
408
- const fromDist = join2(__dirname, "..", "src", "templates", name);
409
- if (existsSync2(fromDist)) {
410
- return readFileSync2(fromDist, "utf-8");
411
- }
412
- const fromSrc = join2(__dirname, "templates", name);
413
- if (existsSync2(fromSrc)) {
414
- return readFileSync2(fromSrc, "utf-8");
415
- }
416
- throw new Error(`Template not found: ${name}`);
417
- }
418
- function renderTemplate(template, vars) {
419
- let result = template;
420
- for (const [key, value] of Object.entries(vars)) {
421
- result = result.replace(new RegExp(`\\{\\{${key}\\}\\}`, "g"), value);
422
- }
423
- return result;
424
- }
425
- function resolvePatternAlias(item, patterns) {
426
- if (!patterns) return item;
427
- if (typeof item === "string") {
428
- const patternDef = patterns.find((p) => p.as === item);
429
- if (patternDef) {
430
- if (patternDef.preset) {
431
- return { pattern: patternDef.pattern, preset: patternDef.preset };
432
- }
433
- return patternDef.pattern;
434
- }
435
- return item;
436
- }
437
- if (typeof item === "object" && item !== null) {
438
- const obj = item;
439
- if (Array.isArray(obj.cols)) {
440
- return {
441
- ...obj,
442
- cols: obj.cols.map((col) => resolvePatternAlias(col, patterns))
443
- };
444
- }
445
- }
446
- return item;
447
- }
448
- function buildEssence(options, archetypeData) {
449
- let structure = [
450
- { id: "home", shell: options.shell, layout: ["hero"] }
451
- ];
452
- let features = options.features;
453
- if (archetypeData?.pages) {
454
- structure = archetypeData.pages.map((p) => {
455
- const resolvedLayout = (p.default_layout?.length ? p.default_layout : ["hero"]).map((item) => resolvePatternAlias(item, p.patterns));
456
- return {
457
- id: p.id,
458
- shell: p.shell || options.shell,
459
- layout: resolvedLayout
460
- };
461
- });
462
- }
463
- if (archetypeData?.features) {
464
- features = [.../* @__PURE__ */ new Set([...features, ...archetypeData.features])];
465
- }
466
- const contentGapMap = {
467
- compact: "_gap2",
468
- comfortable: "_gap4",
469
- spacious: "_gap6"
470
- };
471
- const archetype = options.archetype || "custom";
472
- const essence = {
473
- version: "2.0.0",
474
- archetype,
475
- theme: {
476
- style: options.theme,
477
- mode: options.mode,
478
- recipe: options.theme,
479
- // Recipe defaults to theme
480
- shape: options.shape
481
- },
482
- personality: options.personality,
483
- platform: {
484
- type: "spa",
485
- routing: "hash"
486
- },
487
- structure,
488
- features,
489
- guard: {
490
- enforce_style: true,
491
- enforce_recipe: true,
492
- mode: options.guard
493
- },
494
- density: {
495
- level: options.density,
496
- content_gap: contentGapMap[options.density] || "_gap4"
497
- },
498
- target: options.target
499
- };
500
- if (options.accessibility) {
501
- essence.accessibility = options.accessibility;
502
- }
503
- return essence;
504
- }
505
- function generateAccessibilitySection(essence, themeData) {
506
- const accessibility = essence.accessibility;
507
- if (!accessibility?.wcag_level || accessibility.wcag_level === "none") {
508
- return "";
509
- }
510
- const wcagLevel = accessibility.wcag_level;
511
- const cvdPreference = accessibility.cvd_preference || "none";
512
- const cvdSupport = themeData?.cvd_support || [];
513
- let section = `---
514
-
515
- ## Accessibility
516
-
517
- **WCAG Level:** ${wcagLevel}
518
- `;
519
- if (cvdSupport.length > 0) {
520
- section += `**CVD Support:** Theme supports ${cvdSupport.join(", ")}
521
- **CVD Preference:** ${cvdPreference}
522
- `;
523
- }
524
- section += `
525
- ### What This Means
526
-
527
- This project requires WCAG 2.1 Level ${wcagLevel} compliance. You already know these rules \u2014 apply them:
528
-
529
- - Semantic HTML structure
530
- - Sufficient color contrast (4.5:1 for normal text, 3:1 for large text)
531
- - Keyboard navigability for all interactive elements
532
- - Visible focus indicators
533
- - Meaningful alt text for images
534
- - Proper heading hierarchy
535
- `;
536
- if (cvdSupport.length > 0) {
537
- section += `
538
- ### CVD Implementation
539
-
540
- The theme provides these data attributes:
541
-
542
- \`\`\`html
543
- <html data-theme="${essence.theme.style}" data-mode="${essence.theme.mode}" data-cvd="none">
544
- \`\`\`
545
-
546
- Valid \`data-cvd\` values for this theme: \`none\`, ${cvdSupport.map((m) => `\`${m}\``).join(", ")}
547
- `;
548
- if (cvdPreference === "auto") {
549
- section += `
550
- Detect user preference via \`prefers-contrast\` or user settings and apply accordingly.
551
- `;
552
- }
553
- }
554
- section += `
555
- ---
556
- `;
557
- return section;
558
- }
559
- function generateSeoSection(essence, archetypeData) {
560
- const seoHints = archetypeData?.seo_hints;
561
- if (!seoHints) {
562
- return "";
563
- }
564
- const schemaOrg = seoHints.schema_org || [];
565
- const metaPriorities = seoHints.meta_priorities || [];
566
- if (schemaOrg.length === 0 && metaPriorities.length === 0) {
567
- return "";
568
- }
569
- let section = `---
570
-
571
- ## SEO Guidance
572
-
573
- This archetype (\`${essence.archetype}\`) typically benefits from:
574
-
575
- `;
576
- if (schemaOrg.length > 0) {
577
- section += `- **Schema.org:** ${schemaOrg.join(", ")}
578
- `;
579
- }
580
- if (metaPriorities.length > 0) {
581
- section += `- **Meta priorities:** ${metaPriorities.join(", ")}
582
- `;
583
- }
584
- section += `
585
- These are suggestions, not requirements. Apply where appropriate for the page content.
586
-
587
- ---
588
- `;
589
- return section;
590
- }
591
- function generateDecantrMd(essence, detected, themeData, recipeData, archetypeData) {
592
- const template = loadTemplate("DECANTR.md.template");
593
- const pagesTable = essence.structure.map((p) => {
594
- const layoutStr = p.layout.map(serializeLayoutItem).join(", ") || "none";
595
- return `| ${p.id} | ${p.shell} | ${layoutStr} |`;
596
- }).join("\n");
597
- const allPatternNames = [...new Set(essence.structure.flatMap((p) => p.layout.flatMap(extractPatternNames)))];
598
- const patternsList = allPatternNames.length > 0 ? allPatternNames.map((p) => `- \`${p}\``).join("\n") : "- No patterns specified yet";
599
- const projectSummary = [
600
- `**Archetype:** ${essence.archetype || "custom"}`,
601
- `**Target:** ${essence.target}`,
602
- `**Theme:** ${essence.theme.style} (${essence.theme.mode} mode)`,
603
- `**Guard Mode:** ${essence.guard.mode}`,
604
- `**Pages:** ${essence.structure.map((s) => s.id).join(", ")}`
605
- ].join("\n");
606
- const shellStructures = {
607
- "sidebar-main": "nav (left) | header (top) | body (scrollable)",
608
- "top-nav-main": "header (full width) | body (scrollable)",
609
- "centered": "body (centered card)",
610
- "full-bleed": "header (floating) | body (full page sections)",
611
- "minimal-header": "header (slim) | body (centered)"
612
- };
613
- const defaultShell = essence.structure[0]?.shell || "sidebar-main";
614
- const shellStructure = shellStructures[defaultShell] || "Custom shell layout";
615
- let themeQuickRef = "";
616
- if (themeData?.seed) {
617
- const colors = Object.entries(themeData.seed).map(([name, hex]) => `- **${name}:** \`${hex}\``).join("\n");
618
- themeQuickRef = `**Seed Colors:**
619
- ${colors}`;
620
- }
621
- if (recipeData?.decorators) {
622
- const decorators = Object.entries(recipeData.decorators).slice(0, 5).map(([name, desc]) => `- \`${name}\` \u2014 ${desc}`).join("\n");
623
- if (themeQuickRef) {
624
- themeQuickRef += `
625
-
626
- **Key Decorators:**
627
- ${decorators}`;
628
- } else {
629
- themeQuickRef = `**Key Decorators:**
630
- ${decorators}`;
631
- }
632
- }
633
- if (!themeQuickRef) {
634
- themeQuickRef = `See \`decantr get theme ${essence.theme.style}\` for details.`;
635
- }
636
- const accessibilitySection = generateAccessibilitySection(essence, themeData);
637
- const seoSection = generateSeoSection(essence, archetypeData);
638
- const vars = {
639
- GUARD_MODE: essence.guard.mode,
640
- PROJECT_SUMMARY: projectSummary,
641
- THEME_STYLE: essence.theme.style,
642
- THEME_MODE: essence.theme.mode,
643
- THEME_RECIPE: essence.theme.recipe,
644
- TARGET: essence.target,
645
- PAGES_TABLE: `| Page | Shell | Layout |
646
- |------|-------|--------|
647
- ${pagesTable}`,
648
- PATTERNS_LIST: patternsList,
649
- DEFAULT_SHELL: defaultShell,
650
- SHELL_STRUCTURE: shellStructure,
651
- PERSONALITY: essence.personality.join(", "),
652
- DENSITY: essence.density.level,
653
- AVAILABLE_PATTERNS: "(See registry or .decantr/cache/patterns/)",
654
- AVAILABLE_THEMES: "(See registry or .decantr/cache/themes/)",
655
- AVAILABLE_SHELLS: "sidebar-main, top-nav-main, centered, full-bleed, minimal-header",
656
- VERSION: CLI_VERSION,
657
- THEME_QUICK_REFERENCE: themeQuickRef,
658
- ACCESSIBILITY_SECTION: accessibilitySection,
659
- SEO_SECTION: seoSection
660
- };
661
- return renderTemplate(template, vars);
662
- }
663
- function generateProjectJson(detected, options, registrySource) {
664
- const now = (/* @__PURE__ */ new Date()).toISOString();
665
- const data = {
666
- detected: {
667
- framework: detected.framework,
668
- version: detected.version || null,
669
- packageManager: detected.packageManager,
670
- hasTypeScript: detected.hasTypeScript,
671
- hasTailwind: detected.hasTailwind,
672
- existingRuleFiles: detected.existingRuleFiles
673
- },
674
- overrides: {
675
- framework: options.target !== detected.framework ? options.target : null
676
- },
677
- sync: {
678
- status: registrySource === "api" ? "synced" : "needs-sync",
679
- lastSync: now,
680
- registrySource,
681
- cachedContent: {
682
- archetypes: [],
683
- patterns: [],
684
- themes: [],
685
- recipes: []
686
- }
687
- },
688
- initialized: {
689
- at: now,
690
- via: "cli",
691
- version: CLI_VERSION,
692
- flags: buildFlagsString(options)
693
- }
694
- };
695
- return JSON.stringify(data, null, 2);
696
- }
697
- function buildFlagsString(options) {
698
- const flags = [];
699
- if (options.blueprint) flags.push(`--blueprint=${options.blueprint}`);
700
- if (options.theme) flags.push(`--theme=${options.theme}`);
701
- if (options.mode) flags.push(`--mode=${options.mode}`);
702
- if (options.guard) flags.push(`--guard=${options.guard}`);
703
- return flags.join(" ");
704
- }
705
- function generateTaskContext(templateName, essence) {
706
- const template = loadTemplate(templateName);
707
- const defaultShell = essence.structure[0]?.shell || "sidebar-main";
708
- const layout = essence.structure[0]?.layout.map(serializeLayoutItem).join(", ") || "none";
709
- const scaffoldStructure = essence.structure.map((p) => {
710
- const patterns = p.layout.length > 0 ? `
711
- - Patterns: ${p.layout.map(serializeLayoutItem).join(", ")}` : "";
712
- return `- **${p.id}** (${p.shell})${patterns}`;
713
- }).join("\n");
714
- const vars = {
715
- TARGET: essence.target,
716
- THEME_STYLE: essence.theme.style,
717
- THEME_MODE: essence.theme.mode,
718
- THEME_RECIPE: essence.theme.recipe,
719
- DEFAULT_SHELL: defaultShell,
720
- GUARD_MODE: essence.guard.mode,
721
- LAYOUT: layout,
722
- DENSITY: essence.density.level,
723
- CONTENT_GAP: essence.density.content_gap,
724
- SCAFFOLD_STRUCTURE: scaffoldStructure
725
- };
726
- return renderTemplate(template, vars);
727
- }
728
- function generateEssenceSummary(essence) {
729
- const template = loadTemplate("essence-summary.md.template");
730
- const pagesTable = `| Page | Shell | Layout |
731
- |------|-------|--------|
732
- ${essence.structure.map((p) => `| ${p.id} | ${p.shell} | ${p.layout.map(serializeLayoutItem).join(", ") || "none"} |`).join("\n")}`;
733
- const featuresList = essence.features.length > 0 ? essence.features.map((f) => `- ${f}`).join("\n") : "- No features specified";
734
- const vars = {
735
- ARCHETYPE: essence.archetype || "custom",
736
- BLUEPRINT: essence.blueprint || "none",
737
- PERSONALITY: essence.personality.join(", "),
738
- TARGET: essence.target,
739
- THEME_STYLE: essence.theme.style,
740
- THEME_MODE: essence.theme.mode,
741
- THEME_RECIPE: essence.theme.recipe,
742
- SHAPE: essence.theme.shape,
743
- PAGES_TABLE: pagesTable,
744
- FEATURES_LIST: featuresList,
745
- GUARD_MODE: essence.guard.mode,
746
- ENFORCE_STYLE: String(essence.guard.enforce_style),
747
- ENFORCE_RECIPE: String(essence.guard.enforce_recipe),
748
- DENSITY: essence.density.level,
749
- CONTENT_GAP: essence.density.content_gap,
750
- LAST_UPDATED: (/* @__PURE__ */ new Date()).toISOString()
751
- };
752
- return renderTemplate(template, vars);
753
- }
754
- function updateGitignore(projectRoot) {
755
- const gitignorePath = join2(projectRoot, ".gitignore");
756
- const cacheEntry = ".decantr/cache/";
757
- if (existsSync2(gitignorePath)) {
758
- const content = readFileSync2(gitignorePath, "utf-8");
759
- if (!content.includes(cacheEntry)) {
760
- appendFileSync(gitignorePath, `
761
- # Decantr cache
762
- ${cacheEntry}
763
- `);
764
- return true;
765
- }
766
- return false;
767
- } else {
768
- writeFileSync(gitignorePath, `# Decantr cache
769
- ${cacheEntry}
770
- `);
771
- return true;
772
- }
773
- }
774
- function scaffoldProject(projectRoot, options, detected, archetypeData, registrySource = "bundled", themeData, recipeData) {
775
- const essence = buildEssence(options, archetypeData);
776
- const decantrDir = join2(projectRoot, ".decantr");
777
- const contextDir = join2(decantrDir, "context");
778
- const cacheDir = join2(decantrDir, "cache");
779
- mkdirSync(contextDir, { recursive: true });
780
- mkdirSync(cacheDir, { recursive: true });
781
- const essencePath = join2(projectRoot, "decantr.essence.json");
782
- writeFileSync(essencePath, JSON.stringify(essence, null, 2) + "\n");
783
- const decantrMdPath = join2(projectRoot, "DECANTR.md");
784
- writeFileSync(decantrMdPath, generateDecantrMd(essence, detected, themeData, recipeData, archetypeData));
785
- const projectJsonPath = join2(decantrDir, "project.json");
786
- writeFileSync(projectJsonPath, generateProjectJson(detected, options, registrySource));
787
- const contextFiles = [];
788
- const scaffoldPath = join2(contextDir, "task-scaffold.md");
789
- writeFileSync(scaffoldPath, generateTaskContext("task-scaffold.md.template", essence));
790
- contextFiles.push(scaffoldPath);
791
- const addPagePath = join2(contextDir, "task-add-page.md");
792
- writeFileSync(addPagePath, generateTaskContext("task-add-page.md.template", essence));
793
- contextFiles.push(addPagePath);
794
- const modifyPath = join2(contextDir, "task-modify.md");
795
- writeFileSync(modifyPath, generateTaskContext("task-modify.md.template", essence));
796
- contextFiles.push(modifyPath);
797
- const summaryPath = join2(contextDir, "essence-summary.md");
798
- writeFileSync(summaryPath, generateEssenceSummary(essence));
799
- contextFiles.push(summaryPath);
800
- const gitignoreUpdated = updateGitignore(projectRoot);
801
- return {
802
- essencePath,
803
- decantrMdPath,
804
- projectJsonPath,
805
- contextFiles,
806
- gitignoreUpdated
807
- };
808
- }
809
-
810
- // src/theme-commands.ts
811
- import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2, readdirSync, rmSync } from "fs";
812
- import { join as join3 } from "path";
813
-
814
- // src/theme-templates.ts
815
- function getThemeSkeleton(id, name) {
816
- return {
817
- $schema: "https://decantr.ai/schemas/style-metadata.v1.json",
818
- id,
819
- name,
820
- description: "",
821
- tags: [],
822
- seed: {
823
- primary: "#6366F1",
824
- secondary: "#8B5CF6",
825
- accent: "#EC4899",
826
- background: "#0F172A"
827
- },
828
- palette: {},
829
- modes: ["dark"],
830
- shapes: ["rounded"],
831
- decantr_compat: ">=1.0.0",
832
- source: "custom"
833
- };
834
- }
835
- function getHowToThemeDoc() {
836
- return `# Custom Themes
837
-
838
- Create custom themes for your Decantr project.
839
-
840
- ## Quick Start
841
-
842
- \`\`\`bash
843
- decantr theme create mytheme
844
- \`\`\`
845
-
846
- ## Theme Structure
847
-
848
- | Field | Required | Description |
849
- |-------|----------|-------------|
850
- | id | Yes | Unique identifier (matches filename) |
851
- | name | Yes | Display name |
852
- | description | No | Brief description |
853
- | tags | No | Searchable tags |
854
- | seed | Yes | Core colors: primary, secondary, accent, background |
855
- | palette | No | Extended color palette |
856
- | modes | Yes | Supported modes: ["light"], ["dark"], or both |
857
- | shapes | Yes | Supported shapes: sharp, rounded, pill |
858
- | decantr_compat | Yes | Version compatibility (e.g., ">=1.0.0") |
859
- | source | Yes | Must be "custom" |
860
-
861
- ## Using Your Theme
862
-
863
- In \`decantr.essence.json\`:
864
-
865
- \`\`\`json
866
- {
867
- "theme": {
868
- "style": "custom:mytheme",
869
- "mode": "dark"
870
- }
871
- }
872
- \`\`\`
873
-
874
- ## Validation
875
-
876
- \`\`\`bash
877
- decantr theme validate mytheme
878
- \`\`\`
879
-
880
- ## Reference
881
-
882
- See registry themes for examples:
883
-
884
- \`\`\`bash
885
- decantr get theme auradecantism
886
- \`\`\`
887
- `;
888
- }
889
-
890
- // src/theme-commands.ts
891
- var REQUIRED_FIELDS = ["id", "name", "seed", "modes", "shapes", "decantr_compat", "source"];
892
- var REQUIRED_SEED = ["primary", "secondary", "accent", "background"];
893
- var VALID_MODES = ["light", "dark"];
894
- var VALID_SHAPES = ["sharp", "rounded", "pill"];
895
- function validateCustomTheme(theme) {
896
- const errors = [];
897
- for (const field of REQUIRED_FIELDS) {
898
- if (!(field in theme)) {
899
- errors.push(`Missing required field: ${field}`);
900
- }
901
- }
902
- if (theme.seed && typeof theme.seed === "object") {
903
- const seed = theme.seed;
904
- for (const color of REQUIRED_SEED) {
905
- if (!(color in seed)) {
906
- errors.push(`Missing seed color: ${color}`);
907
- }
908
- }
909
- }
910
- if (Array.isArray(theme.modes)) {
911
- for (const mode of theme.modes) {
912
- if (!VALID_MODES.includes(mode)) {
913
- errors.push(`Invalid mode "${mode}" - must be "light" or "dark"`);
914
- }
915
- }
916
- }
917
- if (Array.isArray(theme.shapes)) {
918
- for (const shape of theme.shapes) {
919
- if (!VALID_SHAPES.includes(shape)) {
920
- errors.push(`Invalid shape "${shape}" - use: sharp, rounded, pill`);
921
- }
922
- }
923
- }
924
- return {
925
- valid: errors.length === 0,
926
- errors
927
- };
928
- }
929
- function createTheme(projectRoot, id, name) {
930
- const customThemesDir = join3(projectRoot, ".decantr", "custom", "themes");
931
- const themePath = join3(customThemesDir, `${id}.json`);
932
- const howToPath = join3(customThemesDir, "how-to-theme.md");
933
- mkdirSync2(customThemesDir, { recursive: true });
934
- if (existsSync3(themePath)) {
935
- return {
936
- success: false,
937
- error: `Theme "${id}" already exists at ${themePath}`
938
- };
939
- }
940
- const skeleton = getThemeSkeleton(id, name);
941
- writeFileSync2(themePath, JSON.stringify(skeleton, null, 2));
942
- if (!existsSync3(howToPath)) {
943
- writeFileSync2(howToPath, getHowToThemeDoc());
944
- }
945
- return {
946
- success: true,
947
- path: themePath
948
- };
949
- }
950
- function listCustomThemes(projectRoot) {
951
- const customThemesDir = join3(projectRoot, ".decantr", "custom", "themes");
952
- if (!existsSync3(customThemesDir)) {
953
- return [];
954
- }
955
- const themes = [];
956
- try {
957
- const files = readdirSync(customThemesDir).filter((f) => f.endsWith(".json"));
958
- for (const file of files) {
959
- const filePath = join3(customThemesDir, file);
960
- try {
961
- const data = JSON.parse(readFileSync3(filePath, "utf-8"));
962
- themes.push({
963
- id: data.id || file.replace(".json", ""),
964
- name: data.name || data.id,
965
- description: data.description,
966
- path: filePath
967
- });
968
- } catch {
969
- }
970
- }
971
- } catch {
972
- }
973
- return themes;
974
- }
975
- function deleteTheme(projectRoot, id) {
976
- const themePath = join3(projectRoot, ".decantr", "custom", "themes", `${id}.json`);
977
- if (!existsSync3(themePath)) {
978
- return {
979
- success: false,
980
- error: `Theme "${id}" not found at ${themePath}`
981
- };
982
- }
983
- try {
984
- rmSync(themePath);
985
- return { success: true };
986
- } catch (e) {
987
- return {
988
- success: false,
989
- error: `Failed to delete: ${e.message}`
990
- };
991
- }
992
- }
993
- function importTheme(projectRoot, sourcePath) {
994
- if (!existsSync3(sourcePath)) {
995
- return {
996
- success: false,
997
- errors: [`Source file not found: ${sourcePath}`]
998
- };
999
- }
1000
- let theme;
1001
- try {
1002
- theme = JSON.parse(readFileSync3(sourcePath, "utf-8"));
1003
- } catch (e) {
1004
- return {
1005
- success: false,
1006
- errors: [`Invalid JSON: ${e.message}`]
1007
- };
1008
- }
1009
- const validation = validateCustomTheme(theme);
1010
- if (!validation.valid) {
1011
- return {
1012
- success: false,
1013
- errors: validation.errors
1014
- };
1015
- }
1016
- theme.source = "custom";
1017
- const id = theme.id;
1018
- const customThemesDir = join3(projectRoot, ".decantr", "custom", "themes");
1019
- const destPath = join3(customThemesDir, `${id}.json`);
1020
- mkdirSync2(customThemesDir, { recursive: true });
1021
- const howToPath = join3(customThemesDir, "how-to-theme.md");
1022
- if (!existsSync3(howToPath)) {
1023
- writeFileSync2(howToPath, getHowToThemeDoc());
1024
- }
1025
- writeFileSync2(destPath, JSON.stringify(theme, null, 2));
1026
- return {
1027
- success: true,
1028
- path: destPath
1029
- };
1030
- }
1031
-
1032
- // src/index.ts
1033
- var BOLD2 = "\x1B[1m";
1034
- var DIM2 = "\x1B[2m";
1035
- var RESET2 = "\x1B[0m";
1036
- var RED = "\x1B[31m";
1037
- var GREEN2 = "\x1B[32m";
1038
- var CYAN2 = "\x1B[36m";
1039
- var YELLOW2 = "\x1B[33m";
1040
- function heading(text) {
1041
- return `
1042
- ${BOLD2}${text}${RESET2}
1043
- `;
1044
- }
1045
- function success(text) {
1046
- return `${GREEN2}${text}${RESET2}`;
1047
- }
1048
- function error(text) {
1049
- return `${RED}${text}${RESET2}`;
1050
- }
1051
- function dim(text) {
1052
- return `${DIM2}${text}${RESET2}`;
1053
- }
1054
- function cyan(text) {
1055
- return `${CYAN2}${text}${RESET2}`;
1056
- }
1057
- function extractPatternName(item) {
1058
- if (typeof item === "string") return item;
1059
- if (typeof item === "object" && item !== null) {
1060
- const obj = item;
1061
- if (typeof obj.pattern === "string") return obj.pattern;
1062
- if (Array.isArray(obj.cols)) {
1063
- return obj.cols.map(extractPatternName).join(" | ");
1064
- }
1065
- }
1066
- return "custom";
1067
- }
1068
- function generateCuratedPrompt(ctx) {
1069
- const lines = [];
1070
- lines.push(`I'm building a ${ctx.archetype} application using ${ctx.target}.`);
1071
- lines.push("");
1072
- if (ctx.blueprint) {
1073
- lines.push(`Blueprint: ${ctx.blueprint}`);
1074
- }
1075
- lines.push(`Theme: ${ctx.theme} (${ctx.mode} mode)`);
1076
- lines.push(`Personality: ${ctx.personality.join(", ")}`);
1077
- lines.push(`Guard mode: ${ctx.guard}`);
1078
- lines.push("");
1079
- lines.push("Pages to build:");
1080
- for (const page of ctx.pages) {
1081
- const patternNames = page.layout.map(extractPatternName);
1082
- const patterns = patternNames.length > 0 ? patternNames.join(", ") : "custom";
1083
- lines.push(` - ${page.id}: ${page.shell} shell with ${patterns}`);
1084
- }
1085
- if (ctx.features.length > 0) {
1086
- lines.push("");
1087
- lines.push(`Features: ${ctx.features.join(", ")}`);
1088
- }
1089
- lines.push("");
1090
- lines.push("Please read DECANTR.md for the full design spec and methodology.");
1091
- lines.push("Follow the guard rules and use the patterns from decantr.essence.json.");
1092
- return lines.join("\n");
1093
- }
1094
- function boxedPrompt(content, title) {
1095
- const lines = content.split("\n");
1096
- const maxLen = Math.max(...lines.map((l) => l.length), title.length + 4);
1097
- const width = maxLen + 4;
1098
- const top = `\u250C${"\u2500".repeat(width - 2)}\u2510`;
1099
- const titleLine = `\u2502 ${BOLD2}${title}${RESET2}${" ".repeat(width - title.length - 4)} \u2502`;
1100
- const sep = `\u251C${"\u2500".repeat(width - 2)}\u2524`;
1101
- const bottom = `\u2514${"\u2500".repeat(width - 2)}\u2518`;
1102
- const body = lines.map((line) => {
1103
- const padding = " ".repeat(width - line.length - 4);
1104
- return `\u2502 ${line}${padding} \u2502`;
1105
- }).join("\n");
1106
- return `${top}
1107
- ${titleLine}
1108
- ${sep}
1109
- ${body}
1110
- ${bottom}`;
1111
- }
1112
- function getContentRoot() {
1113
- const bundled = join4(import.meta.dirname, "..", "..", "..", "content");
1114
- return process.env.DECANTR_CONTENT_ROOT || bundled;
1115
- }
1116
- function getResolver() {
1117
- return createResolver({ contentRoot: getContentRoot() });
1118
- }
1119
- async function cmdSearch(query, type) {
1120
- const client = createRegistryClient();
1121
- const results = await client.search(query, type);
1122
- if (results.length === 0) {
1123
- console.log(dim(`No results for "${query}"`));
1124
- return;
1125
- }
1126
- console.log(heading(`${results.length} result(s) for "${query}"`));
1127
- for (const r of results) {
1128
- console.log(` ${cyan(r.type.padEnd(12))} ${BOLD2}${r.id}${RESET2}`);
1129
- console.log(` ${dim(r.description || "")}`);
1130
- console.log("");
1131
- }
1132
- }
1133
- async function cmdSuggest(query, type) {
1134
- const client = createRegistryClient();
1135
- const searchType = type || "pattern";
1136
- const results = await client.search(query, searchType);
1137
- if (results.length === 0) {
1138
- console.log(dim(`No suggestions for "${query}"`));
1139
- console.log("");
1140
- console.log("Try:");
1141
- console.log(` ${cyan("decantr list patterns")} - see all patterns`);
1142
- console.log(` ${cyan("decantr search <broader-term>")} - broaden your search`);
1143
- return;
1144
- }
1145
- console.log(heading(`Suggestions for "${query}"`));
1146
- const queryLower = query.toLowerCase();
1147
- const exact = results.filter((r) => r.id.toLowerCase().includes(queryLower));
1148
- const related = results.filter((r) => !r.id.toLowerCase().includes(queryLower));
1149
- if (exact.length > 0) {
1150
- console.log(`${BOLD2}Direct matches:${RESET2}`);
1151
- for (const r of exact.slice(0, 3)) {
1152
- console.log(` ${cyan(r.id)} - ${r.description || ""}`);
1153
- }
1154
- console.log("");
1155
- }
1156
- if (related.length > 0) {
1157
- console.log(`${BOLD2}Related:${RESET2}`);
1158
- for (const r of related.slice(0, 5)) {
1159
- console.log(` ${cyan(r.id)} - ${r.description || ""}`);
1160
- }
1161
- console.log("");
1162
- }
1163
- console.log(dim(`Use "decantr get pattern <id>" for full details`));
1164
- }
1165
- async function cmdGet(type, id) {
1166
- const validTypes = ["pattern", "archetype", "recipe", "theme", "blueprint", "shell"];
1167
- if (!validTypes.includes(type)) {
1168
- console.error(error(`Invalid type "${type}". Must be one of: ${validTypes.join(", ")}`));
1169
- process.exitCode = 1;
1170
- return;
1171
- }
1172
- if (type === "shell") {
1173
- const registryClient = new RegistryClient({
1174
- cacheDir: join4(process.cwd(), ".decantr", "cache")
1175
- });
1176
- const shellResult = await registryClient.fetchShell(id);
1177
- if (shellResult) {
1178
- console.log(JSON.stringify(shellResult.data, null, 2));
1179
- return;
1180
- }
1181
- console.error(error(`shell "${id}" not found.`));
1182
- process.exitCode = 1;
1183
- return;
1184
- }
1185
- const resolver = getResolver();
1186
- let result = await resolver.resolve(type, id);
1187
- if (!result) {
1188
- const apiType = type === "blueprint" ? "blueprints" : `${type}s`;
1189
- try {
1190
- const res = await fetch(`https://decantr-registry.fly.dev/v1/${apiType}/${id}`);
1191
- if (res.ok) {
1192
- const item = await res.json();
1193
- if (!item.error) {
1194
- console.log(JSON.stringify(item, null, 2));
1195
- return;
1196
- }
1197
- }
1198
- } catch {
1199
- }
1200
- console.error(error(`${type} "${id}" not found.`));
1201
- process.exitCode = 1;
1202
- return;
1203
- }
1204
- console.log(JSON.stringify(result.item, null, 2));
1205
- }
1206
- function buildRegistryContext() {
1207
- const { readdirSync: readdirSync2 } = __require("fs");
1208
- const themeRegistry = /* @__PURE__ */ new Map();
1209
- const patternRegistry = /* @__PURE__ */ new Map();
1210
- const contentRoot = getContentRoot();
1211
- const themeDirs = [join4(contentRoot, "themes"), join4(contentRoot, "core", "themes")];
1212
- for (const dir of themeDirs) {
1213
- try {
1214
- if (existsSync4(dir)) {
1215
- for (const f of readdirSync2(dir).filter((f2) => f2.endsWith(".json"))) {
1216
- const data = JSON.parse(readFileSync4(join4(dir, f), "utf-8"));
1217
- if (data.id && !themeRegistry.has(data.id)) {
1218
- themeRegistry.set(data.id, { modes: data.modes || ["light", "dark"] });
1219
- }
1220
- }
1221
- }
1222
- } catch {
1223
- }
1224
- }
1225
- const customThemesDir = join4(process.cwd(), ".decantr", "custom", "themes");
1226
- try {
1227
- if (existsSync4(customThemesDir)) {
1228
- for (const f of readdirSync2(customThemesDir).filter((f2) => f2.endsWith(".json"))) {
1229
- const data = JSON.parse(readFileSync4(join4(customThemesDir, f), "utf-8"));
1230
- if (data.id) {
1231
- themeRegistry.set(`custom:${data.id}`, { modes: data.modes || ["light", "dark"] });
1232
- }
1233
- }
1234
- }
1235
- } catch {
1236
- }
1237
- const patternDirs = [join4(contentRoot, "patterns"), join4(contentRoot, "core", "patterns")];
1238
- for (const dir of patternDirs) {
1239
- try {
1240
- if (existsSync4(dir)) {
1241
- for (const f of readdirSync2(dir).filter((f2) => f2.endsWith(".json"))) {
1242
- const data = JSON.parse(readFileSync4(join4(dir, f), "utf-8"));
1243
- if (data.id && !patternRegistry.has(data.id)) {
1244
- patternRegistry.set(data.id, data);
1245
- }
1246
- }
1247
- }
1248
- } catch {
1249
- }
1250
- }
1251
- return { themeRegistry, patternRegistry };
1252
- }
1253
- async function cmdValidate(path) {
1254
- const essencePath = path || join4(process.cwd(), "decantr.essence.json");
1255
- let raw;
1256
- try {
1257
- raw = readFileSync4(essencePath, "utf-8");
1258
- } catch {
1259
- console.error(error(`Could not read ${essencePath}`));
1260
- process.exitCode = 1;
1261
- return;
1262
- }
1263
- let essence;
1264
- try {
1265
- essence = JSON.parse(raw);
1266
- } catch (e) {
1267
- console.error(error(`Invalid JSON: ${e.message}`));
1268
- process.exitCode = 1;
1269
- return;
1270
- }
1271
- const result = validateEssence(essence);
1272
- if (result.valid) {
1273
- console.log(success("Essence is valid."));
1274
- } else {
1275
- console.error(error("Validation failed:"));
1276
- for (const err of result.errors) {
1277
- console.error(` ${RED}${err}${RESET2}`);
1278
- }
1279
- process.exitCode = 1;
1280
- }
1281
- try {
1282
- const { themeRegistry, patternRegistry } = buildRegistryContext();
1283
- const violations = evaluateGuard(essence, { themeRegistry, patternRegistry });
1284
- if (violations.length > 0) {
1285
- console.log(heading("Guard violations:"));
1286
- for (const v of violations) {
1287
- const vr = v;
1288
- console.log(` ${YELLOW2}[${vr.rule}]${RESET2} ${vr.message}`);
1289
- if (vr.suggestion) {
1290
- console.log(` ${DIM2}Suggestion: ${vr.suggestion}${RESET2}`);
1291
- }
1292
- }
1293
- } else if (result.valid) {
1294
- console.log(success("No guard violations."));
1295
- }
1296
- } catch {
1297
- }
1298
- }
1299
- async function cmdList(type) {
1300
- const validTypes = ["patterns", "archetypes", "recipes", "themes", "blueprints", "shells"];
1301
- if (!validTypes.includes(type)) {
1302
- console.error(error(`Invalid type "${type}". Must be one of: ${validTypes.join(", ")}`));
1303
- process.exitCode = 1;
1304
- return;
1305
- }
1306
- if (type === "shells") {
1307
- const registryClient = new RegistryClient({
1308
- cacheDir: join4(process.cwd(), ".decantr", "cache")
1309
- });
1310
- const shellsResult = await registryClient.fetchShells();
1311
- for (const item of shellsResult.data.items) {
1312
- console.log(` ${item.id}${item.description ? ` \u2014 ${item.description}` : ""}`);
1313
- }
1314
- console.log(`
1315
- ${shellsResult.data.total} shells found`);
1316
- return;
1317
- }
1318
- const { readdirSync: readdirSync2, existsSync: existsSync5 } = await import("fs");
1319
- const contentRoot = getContentRoot();
1320
- const mainDir = join4(contentRoot, type);
1321
- const coreDir = join4(contentRoot, "core", type);
1322
- const items = [];
1323
- try {
1324
- if (existsSync5(mainDir)) {
1325
- const files = readdirSync2(mainDir).filter((f) => f.endsWith(".json"));
1326
- for (const f of files) {
1327
- const data = JSON.parse(readFileSync4(join4(mainDir, f), "utf-8"));
1328
- items.push({ id: data.id || f.replace(".json", ""), description: data.description, name: data.name });
1329
- }
1330
- }
1331
- } catch {
1332
- }
1333
- try {
1334
- if (existsSync5(coreDir)) {
1335
- const files = readdirSync2(coreDir).filter((f) => f.endsWith(".json"));
1336
- const existingIds = new Set(items.map((i) => i.id));
1337
- for (const f of files) {
1338
- const data = JSON.parse(readFileSync4(join4(coreDir, f), "utf-8"));
1339
- const itemId = data.id || f.replace(".json", "");
1340
- if (!existingIds.has(itemId)) {
1341
- items.push({ id: itemId, description: data.description, name: data.name });
1342
- }
1343
- }
1344
- }
1345
- } catch {
1346
- }
1347
- const customItems = [];
1348
- if (type === "themes") {
1349
- try {
1350
- const custom = listCustomThemes(process.cwd());
1351
- for (const theme of custom) {
1352
- customItems.push({
1353
- id: `custom:${theme.id}`,
1354
- description: theme.description,
1355
- name: theme.name,
1356
- source: "custom"
1357
- });
1358
- }
1359
- } catch {
1360
- }
1361
- }
1362
- if (items.length > 0 || customItems.length > 0) {
1363
- if (type === "themes") {
1364
- console.log(heading(`Registry themes (${items.length}):`));
1365
- for (const item of items) {
1366
- console.log(` ${cyan(item.id)} ${dim(item.description || item.name || "")}`);
1367
- }
1368
- if (customItems.length > 0) {
1369
- console.log("");
1370
- console.log(heading(`Custom themes (${customItems.length}):`));
1371
- for (const item of customItems) {
1372
- console.log(` ${cyan(item.id)} ${dim(item.description || item.name || "")}`);
1373
- }
1374
- } else {
1375
- console.log("");
1376
- console.log(dim("Custom themes (0):"));
1377
- console.log(dim(' Run "decantr theme create <name>" to create a custom theme.'));
1378
- }
1379
- } else {
1380
- console.log(heading(`${items.length} ${type}`));
1381
- for (const item of items) {
1382
- console.log(` ${cyan(item.id)} ${dim(item.description || item.name || "")}`);
1383
- }
1384
- }
1385
- return;
1386
- }
1387
- try {
1388
- const res = await fetch(`https://decantr-registry.fly.dev/v1/${type}`);
1389
- if (res.ok) {
1390
- const data = await res.json();
1391
- if (type === "themes") {
1392
- console.log(heading(`Registry themes (${data.total}):`));
1393
- for (const item of data.items) {
1394
- console.log(` ${cyan(item.id)} ${dim(item.description || item.name || "")}`);
1395
- }
1396
- console.log("");
1397
- console.log(dim("Custom themes (0):"));
1398
- console.log(dim(' Run "decantr theme create <name>" to create a custom theme.'));
1399
- } else {
1400
- console.log(heading(`${data.total} ${type}`));
1401
- for (const item of data.items) {
1402
- console.log(` ${cyan(item.id)} ${dim(item.description || item.name || "")}`);
1403
- }
1404
- }
1405
- return;
1406
- }
1407
- } catch {
1408
- }
1409
- console.log(dim(`No ${type} found.`));
1410
- }
1411
- async function cmdInit(args) {
1412
- const projectRoot = process.cwd();
1413
- console.log(heading("Decantr Project Setup"));
1414
- const detected = detectProject(projectRoot);
1415
- if (detected.existingEssence && !args.existing) {
1416
- console.log(`${YELLOW2}Warning: decantr.essence.json already exists.${RESET2}`);
1417
- const overwrite = await confirm("Overwrite existing configuration?", false);
1418
- if (!overwrite) {
1419
- console.log(dim("Cancelled."));
1420
- return;
1421
- }
1422
- }
1423
- const registryClient = new RegistryClient({
1424
- cacheDir: join4(projectRoot, ".decantr", "cache"),
1425
- apiUrl: args.registry,
1426
- offline: args.offline
1427
- });
1428
- const apiAvailable = await registryClient.checkApiAvailability();
1429
- let selectedBlueprint = "default";
1430
- let registrySource = "bundled";
1431
- if (args.yes) {
1432
- selectedBlueprint = args.blueprint || "default";
1433
- } else if (!apiAvailable) {
1434
- console.log(`
1435
- ${YELLOW2}You're offline. Scaffolding Decantr default.${RESET2}`);
1436
- console.log(dim("Run `decantr upgrade` when online, or visit decantr.ai/registry\n"));
1437
- selectedBlueprint = "default";
1438
- } else {
1439
- console.log(dim("Fetching registry content..."));
1440
- const blueprintsResult2 = await registryClient.fetchBlueprints();
1441
- registrySource = blueprintsResult2.source.type === "api" ? "api" : "bundled";
1442
- const { selectedBlueprint: selected } = await runSimplifiedInit(
1443
- blueprintsResult2.data.items
1444
- );
1445
- selectedBlueprint = selected || "default";
1446
- }
1447
- const [archetypesResult, blueprintsResult, themesResult] = await Promise.all([
1448
- registryClient.fetchArchetypes(),
1449
- registryClient.fetchBlueprints(),
1450
- registryClient.fetchThemes()
1451
- ]);
1452
- if (archetypesResult.source.type === "api") {
1453
- registrySource = "api";
1454
- }
1455
- const archetypes = archetypesResult.data.items;
1456
- const blueprints = blueprintsResult.data.items;
1457
- const themes = themesResult.data.items;
1458
- let options;
1459
- if (args.yes || selectedBlueprint !== "default") {
1460
- const flags = parseFlags(args, detected);
1461
- flags.blueprint = selectedBlueprint !== "default" ? selectedBlueprint : flags.blueprint;
1462
- options = mergeWithDefaults(flags, detected);
1463
- } else {
1464
- options = await runInteractivePrompts(detected, archetypes, blueprints, themes);
1465
- }
1466
- let archetypeData;
1467
- if (options.blueprint) {
1468
- const blueprintResult = await registryClient.fetchBlueprint(options.blueprint);
1469
- if (blueprintResult) {
1470
- const blueprint = blueprintResult.data;
1471
- const primaryArchetype = blueprint.compose?.[0];
1472
- if (primaryArchetype) {
1473
- const archetypeResult = await registryClient.fetchArchetype(primaryArchetype);
1474
- if (archetypeResult) {
1475
- archetypeData = archetypeResult.data;
1476
- options.archetype = primaryArchetype;
1477
- }
1478
- }
1479
- }
1480
- } else if (options.archetype) {
1481
- const archetypeResult = await registryClient.fetchArchetype(options.archetype);
1482
- if (archetypeResult) {
1483
- archetypeData = archetypeResult.data;
1484
- }
1485
- }
1486
- let themeData;
1487
- let recipeData;
1488
- if (options.theme) {
1489
- const themeResult = await registryClient.fetchTheme(options.theme);
1490
- if (themeResult) {
1491
- const theme = themeResult.data;
1492
- if (theme.seed) {
1493
- themeData = { seed: theme.seed };
1494
- }
1495
- }
1496
- }
1497
- console.log(heading("Scaffolding project..."));
1498
- const result = scaffoldProject(
1499
- projectRoot,
1500
- options,
1501
- detected,
1502
- archetypeData,
1503
- registrySource,
1504
- themeData,
1505
- recipeData
1506
- );
1507
- console.log(success("\nProject scaffolded!\n"));
1508
- console.log(" Files created:");
1509
- console.log(` ${cyan("decantr.essence.json")} Design specification`);
1510
- console.log(` ${cyan("DECANTR.md")} LLM instructions`);
1511
- console.log(` ${cyan(".decantr/")} Project state & cache`);
1512
- if (result.gitignoreUpdated) {
1513
- console.log(` ${dim(".gitignore updated")}`);
1514
- }
1515
- console.log("");
1516
- console.log(" Next steps:");
1517
- console.log(" 1. Review DECANTR.md for methodology");
1518
- console.log(" 2. Explore more at decantr.ai/registry");
1519
- console.log("");
1520
- console.log(" Commands:");
1521
- console.log(` ${cyan("decantr status")} Project health`);
1522
- console.log(` ${cyan("decantr search")} Search registry`);
1523
- console.log(` ${cyan("decantr get")} Fetch content details`);
1524
- console.log(` ${cyan("decantr validate")} Check essence file`);
1525
- console.log(` ${cyan("decantr upgrade")} Update to latest patterns`);
1526
- console.log(` ${cyan("decantr heal")} Fix drift issues`);
1527
- const essenceContent = readFileSync4(result.essencePath, "utf-8");
1528
- const essence = JSON.parse(essenceContent);
1529
- const validation = validateEssence(essence);
1530
- if (!validation.valid) {
1531
- console.log(error(`
1532
- Validation warnings: ${validation.errors.join(", ")}`));
1533
- }
1534
- console.log("");
1535
- const promptCtx = {
1536
- archetype: options.archetype || "custom",
1537
- blueprint: options.blueprint,
1538
- theme: options.theme,
1539
- mode: options.mode,
1540
- target: options.target,
1541
- pages: essence.structure || [{ id: "home", shell: options.shell, layout: ["hero"] }],
1542
- personality: options.personality,
1543
- features: options.features,
1544
- guard: options.guard
1545
- };
1546
- const curatedPrompt = generateCuratedPrompt(promptCtx);
1547
- console.log(boxedPrompt(curatedPrompt, "Copy this prompt for your AI assistant"));
1548
- console.log("");
1549
- if (registrySource === "bundled") {
1550
- console.log(dim('Run "decantr sync" when online to get the latest registry content.'));
1551
- }
1552
- }
1553
- async function cmdStatus() {
1554
- const projectRoot = process.cwd();
1555
- const essencePath = join4(projectRoot, "decantr.essence.json");
1556
- const projectJsonPath = join4(projectRoot, ".decantr", "project.json");
1557
- console.log(heading("Decantr Project Status"));
1558
- if (!existsSync4(essencePath)) {
1559
- console.log(`${RED}No decantr.essence.json found.${RESET2}`);
1560
- console.log(dim('Run "decantr init" to create one.'));
1561
- return;
1562
- }
1563
- try {
1564
- const essence = JSON.parse(readFileSync4(essencePath, "utf-8"));
1565
- const validation = validateEssence(essence);
1566
- console.log(`${BOLD2}Essence:${RESET2}`);
1567
- if (validation.valid) {
1568
- console.log(` ${GREEN2}Valid${RESET2}`);
1569
- } else {
1570
- console.log(` ${RED}Invalid: ${validation.errors.join(", ")}${RESET2}`);
1571
- }
1572
- console.log(` Theme: ${essence.theme?.style || "unknown"} (${essence.theme?.mode || "unknown"})`);
1573
- console.log(` Guard: ${essence.guard?.mode || "unknown"}`);
1574
- console.log(` Pages: ${(essence.structure || []).length}`);
1575
- } catch (e) {
1576
- console.log(` ${RED}Error reading essence: ${e.message}${RESET2}`);
1577
- }
1578
- console.log("");
1579
- console.log(`${BOLD2}Sync Status:${RESET2}`);
1580
- if (existsSync4(projectJsonPath)) {
1581
- try {
1582
- const projectJson = JSON.parse(readFileSync4(projectJsonPath, "utf-8"));
1583
- const syncStatus = projectJson.sync?.status || "unknown";
1584
- const lastSync = projectJson.sync?.lastSync || "never";
1585
- const source = projectJson.sync?.registrySource || "unknown";
1586
- const statusColor = syncStatus === "synced" ? GREEN2 : YELLOW2;
1587
- console.log(` Status: ${statusColor}${syncStatus}${RESET2}`);
1588
- console.log(` Last sync: ${dim(lastSync)}`);
1589
- console.log(` Source: ${dim(source)}`);
1590
- } catch {
1591
- console.log(` ${YELLOW2}Could not read project.json${RESET2}`);
1592
- }
1593
- } else {
1594
- console.log(` ${YELLOW2}No .decantr/project.json found${RESET2}`);
1595
- console.log(dim(' Run "decantr init" to create project files.'));
1596
- }
1597
- }
1598
- async function cmdSync() {
1599
- const projectRoot = process.cwd();
1600
- const cacheDir = join4(projectRoot, ".decantr", "cache");
1601
- console.log(heading("Syncing registry content..."));
1602
- const result = await syncRegistry(cacheDir);
1603
- if (result.source === "api") {
1604
- console.log(success("Sync completed successfully."));
1605
- if (result.synced.length > 0) {
1606
- console.log(` Synced: ${result.synced.join(", ")}`);
1607
- }
1608
- if (result.failed.length > 0) {
1609
- console.log(` ${YELLOW2}Failed: ${result.failed.join(", ")}${RESET2}`);
1610
- }
1611
- } else {
1612
- console.log(`${YELLOW2}Could not sync: API unavailable${RESET2}`);
1613
- console.log(dim("Using bundled content."));
1614
- }
1615
- }
1616
- async function cmdAudit() {
1617
- const projectRoot = process.cwd();
1618
- const essencePath = join4(projectRoot, "decantr.essence.json");
1619
- console.log(heading("Auditing project..."));
1620
- if (!existsSync4(essencePath)) {
1621
- console.log(`${RED}No decantr.essence.json found.${RESET2}`);
1622
- process.exitCode = 1;
1623
- return;
1624
- }
1625
- try {
1626
- const essence = JSON.parse(readFileSync4(essencePath, "utf-8"));
1627
- const validation = validateEssence(essence);
1628
- if (!validation.valid) {
1629
- console.log(`${RED}Essence validation failed:${RESET2}`);
1630
- for (const err of validation.errors) {
1631
- console.log(` ${RED}${err}${RESET2}`);
1632
- }
1633
- process.exitCode = 1;
1634
- return;
1635
- }
1636
- console.log(success("Essence is valid."));
1637
- const { themeRegistry, patternRegistry } = buildRegistryContext();
1638
- const violations = evaluateGuard(essence, { themeRegistry, patternRegistry });
1639
- if (violations.length > 0) {
1640
- console.log("");
1641
- console.log(`${YELLOW2}Guard violations:${RESET2}`);
1642
- for (const v of violations) {
1643
- const vr = v;
1644
- console.log(` ${YELLOW2}[${vr.rule}]${RESET2} ${vr.message}`);
1645
- if (vr.suggestion) {
1646
- console.log(` ${DIM2}Suggestion: ${vr.suggestion}${RESET2}`);
1647
- }
1648
- }
1649
- } else {
1650
- console.log(success("No guard violations."));
1651
- }
1652
- console.log("");
1653
- console.log(`${BOLD2}Summary:${RESET2}`);
1654
- console.log(` Pages defined: ${essence.structure.length}`);
1655
- console.log(` Guard mode: ${essence.guard.mode}`);
1656
- console.log(` Theme: ${essence.theme.style}`);
1657
- } catch (e) {
1658
- console.log(`${RED}Error: ${e.message}${RESET2}`);
1659
- process.exitCode = 1;
1660
- }
1661
- }
1662
- async function cmdTheme(args) {
1663
- const subcommand = args[0];
1664
- const projectRoot = process.cwd();
1665
- if (!subcommand || subcommand === "help") {
1666
- console.log(`
1667
- ${BOLD2}decantr theme${RESET2} \u2014 Manage custom themes
1668
-
1669
- ${BOLD2}Commands:${RESET2}
1670
- ${cyan("create")} <name> Create a new custom theme
1671
- ${cyan("create")} <name> --guided Interactive theme creation
1672
- ${cyan("list")} List custom themes
1673
- ${cyan("validate")} <name> Validate a custom theme
1674
- ${cyan("delete")} <name> Delete a custom theme
1675
- ${cyan("import")} <path> Import theme from JSON file
1676
-
1677
- ${BOLD2}Examples:${RESET2}
1678
- decantr theme create mytheme
1679
- decantr theme list
1680
- decantr theme validate mytheme
1681
- decantr theme import ./external-theme.json
1682
- `);
1683
- return;
1684
- }
1685
- switch (subcommand) {
1686
- case "create": {
1687
- const name = args[1];
1688
- if (!name) {
1689
- console.error(error("Usage: decantr theme create <name>"));
1690
- process.exitCode = 1;
1691
- return;
1692
- }
1693
- const displayName = name.charAt(0).toUpperCase() + name.slice(1).replace(/-/g, " ");
1694
- const result = createTheme(projectRoot, name, displayName);
1695
- if (result.success) {
1696
- console.log(success(`Created custom theme "${name}"`));
1697
- console.log(dim(` Path: ${result.path}`));
1698
- console.log("");
1699
- console.log(`Use in essence: ${cyan(`"style": "custom:${name}"`)}`);
1700
- } else {
1701
- console.error(error(result.error || "Failed to create theme"));
1702
- process.exitCode = 1;
1703
- }
1704
- break;
1705
- }
1706
- case "list": {
1707
- const themes = listCustomThemes(projectRoot);
1708
- if (themes.length === 0) {
1709
- console.log(dim("No custom themes found."));
1710
- console.log(dim('Run "decantr theme create <name>" to create one.'));
1711
- } else {
1712
- console.log(heading(`${themes.length} custom theme(s)`));
1713
- for (const theme of themes) {
1714
- console.log(` ${cyan(`custom:${theme.id}`)} ${dim(theme.description || theme.name)}`);
1715
- }
1716
- }
1717
- break;
1718
- }
1719
- case "validate": {
1720
- const name = args[1];
1721
- if (!name) {
1722
- console.error(error("Usage: decantr theme validate <name>"));
1723
- process.exitCode = 1;
1724
- return;
1725
- }
1726
- const themePath = join4(projectRoot, ".decantr", "custom", "themes", `${name}.json`);
1727
- if (!existsSync4(themePath)) {
1728
- console.error(error(`Theme "${name}" not found at ${themePath}`));
1729
- process.exitCode = 1;
1730
- return;
1731
- }
1732
- try {
1733
- const theme = JSON.parse(readFileSync4(themePath, "utf-8"));
1734
- const result = validateCustomTheme(theme);
1735
- if (result.valid) {
1736
- console.log(success(`Custom theme "${name}" is valid`));
1737
- } else {
1738
- console.error(error("Validation failed:"));
1739
- for (const err of result.errors) {
1740
- console.error(` ${RED}${err}${RESET2}`);
1741
- }
1742
- process.exitCode = 1;
1743
- }
1744
- } catch (e) {
1745
- console.error(error(`Invalid JSON: ${e.message}`));
1746
- process.exitCode = 1;
1747
- }
1748
- break;
1749
- }
1750
- case "delete": {
1751
- const name = args[1];
1752
- if (!name) {
1753
- console.error(error("Usage: decantr theme delete <name>"));
1754
- process.exitCode = 1;
1755
- return;
1756
- }
1757
- const result = deleteTheme(projectRoot, name);
1758
- if (result.success) {
1759
- console.log(success(`Deleted custom theme "${name}"`));
1760
- } else {
1761
- console.error(error(result.error || "Failed to delete theme"));
1762
- process.exitCode = 1;
1763
- }
1764
- break;
1765
- }
1766
- case "import": {
1767
- const sourcePath = args[1];
1768
- if (!sourcePath) {
1769
- console.error(error("Usage: decantr theme import <path>"));
1770
- process.exitCode = 1;
1771
- return;
1772
- }
1773
- const result = importTheme(projectRoot, sourcePath);
1774
- if (result.success) {
1775
- console.log(success("Theme imported successfully"));
1776
- console.log(dim(` Path: ${result.path}`));
1777
- } else {
1778
- console.error(error("Import failed:"));
1779
- for (const err of result.errors || []) {
1780
- console.error(` ${RED}${err}${RESET2}`);
1781
- }
1782
- process.exitCode = 1;
1783
- }
1784
- break;
1785
- }
1786
- default:
1787
- console.error(error(`Unknown theme command: ${subcommand}`));
1788
- process.exitCode = 1;
1789
- }
1790
- }
1791
- function cmdHelp() {
1792
- console.log(`
1793
- ${BOLD2}decantr${RESET2} \u2014 Design intelligence for AI-generated UI
1794
-
1795
- ${BOLD2}Usage:${RESET2}
1796
- decantr init [options]
1797
- decantr status
1798
- decantr sync
1799
- decantr audit
1800
- decantr search <query> [--type <type>]
1801
- decantr suggest <query> [--type <type>]
1802
- decantr get <type> <id>
1803
- decantr list <type>
1804
- decantr validate [path]
1805
- decantr theme <subcommand>
1806
- decantr help
1807
-
1808
- ${BOLD2}Init Options:${RESET2}
1809
- --blueprint, -b Blueprint ID
1810
- --theme Theme ID
1811
- --mode Color mode: dark | light | auto
1812
- --shape Border shape: pill | rounded | sharp
1813
- --target Framework: react | vue | svelte | nextjs | html
1814
- --guard Guard mode: creative | guided | strict
1815
- --density Spacing: compact | comfortable | spacious
1816
- --shell Default shell layout
1817
- --existing Initialize in existing project
1818
- --offline Force offline mode
1819
- --yes, -y Accept defaults, skip confirmations
1820
- --registry Custom registry URL
1821
-
1822
- ${BOLD2}Commands:${RESET2}
1823
- ${cyan("init")} Initialize a new Decantr project with full scaffolding
1824
- ${cyan("status")} Show project status and sync state
1825
- ${cyan("sync")} Sync registry content from API
1826
- ${cyan("audit")} Validate essence and check for drift
1827
- ${cyan("search")} Search the registry
1828
- ${cyan("suggest")} Suggest patterns or alternatives for a query
1829
- ${cyan("get")} Get full details of a registry item
1830
- ${cyan("list")} List items by type
1831
- ${cyan("validate")} Validate essence file
1832
- ${cyan("theme")} Manage custom themes (create, list, validate, delete, import)
1833
- ${cyan("help")} Show this help
1834
-
1835
- ${BOLD2}Examples:${RESET2}
1836
- decantr init
1837
- decantr init --blueprint=saas-dashboard --theme=luminarum --yes
1838
- decantr status
1839
- decantr sync
1840
- decantr audit
1841
- decantr search dashboard
1842
- decantr suggest leaderboard
1843
- decantr suggest ranking --type pattern
1844
- decantr list patterns
1845
- `);
1846
- }
1847
- async function main() {
1848
- const args = process.argv.slice(2);
1849
- const command = args[0];
1850
- if (!command || command === "help" || command === "--help" || command === "-h") {
1851
- cmdHelp();
1852
- return;
1853
- }
1854
- switch (command) {
1855
- case "init": {
1856
- const initArgs = {};
1857
- for (let i = 1; i < args.length; i++) {
1858
- const arg = args[i];
1859
- if (arg === "--yes" || arg === "-y") {
1860
- initArgs.yes = true;
1861
- } else if (arg === "--offline") {
1862
- initArgs.offline = true;
1863
- } else if (arg === "--existing") {
1864
- initArgs.existing = true;
1865
- } else if (arg.startsWith("--")) {
1866
- const [key, value] = arg.slice(2).split("=");
1867
- if (value) {
1868
- initArgs[key] = value;
1869
- } else if (args[i + 1] && !args[i + 1].startsWith("-")) {
1870
- initArgs[key] = args[++i];
1871
- }
1872
- } else if (arg.startsWith("-")) {
1873
- const key = arg.slice(1);
1874
- if (key === "b" && args[i + 1]) initArgs.blueprint = args[++i];
1875
- if (key === "y") initArgs.yes = true;
1876
- }
1877
- }
1878
- await cmdInit(initArgs);
1879
- break;
1880
- }
1881
- case "status": {
1882
- await cmdStatus();
1883
- break;
1884
- }
1885
- case "sync": {
1886
- await cmdSync();
1887
- break;
1888
- }
1889
- case "upgrade": {
1890
- const { cmdUpgrade } = await import("./upgrade-FWICWIQW.js");
1891
- await cmdUpgrade(process.cwd());
1892
- break;
1893
- }
1894
- case "heal": {
1895
- const { cmdHeal } = await import("./heal-2OPN63OT.js");
1896
- await cmdHeal(process.cwd());
1897
- break;
1898
- }
1899
- case "audit": {
1900
- await cmdAudit();
1901
- break;
1902
- }
1903
- case "search": {
1904
- const query = args[1];
1905
- if (!query) {
1906
- console.error(error("Usage: decantr search <query> [--type <type>]"));
1907
- process.exitCode = 1;
1908
- return;
1909
- }
1910
- const typeIdx = args.indexOf("--type");
1911
- const type = typeIdx !== -1 ? args[typeIdx + 1] : void 0;
1912
- await cmdSearch(query, type);
1913
- break;
1914
- }
1915
- case "suggest": {
1916
- const query = args[1];
1917
- if (!query) {
1918
- console.error(error("Usage: decantr suggest <query> [--type <type>]"));
1919
- process.exitCode = 1;
1920
- return;
1921
- }
1922
- const typeIdx = args.indexOf("--type");
1923
- const type = typeIdx !== -1 ? args[typeIdx + 1] : void 0;
1924
- await cmdSuggest(query, type);
1925
- break;
1926
- }
1927
- case "get": {
1928
- const type = args[1];
1929
- const id = args[2];
1930
- if (!type || !id) {
1931
- console.error(error("Usage: decantr get <type> <id>"));
1932
- process.exitCode = 1;
1933
- return;
1934
- }
1935
- await cmdGet(type, id);
1936
- break;
1937
- }
1938
- case "list": {
1939
- const type = args[1];
1940
- if (!type) {
1941
- console.error(error("Usage: decantr list <type>"));
1942
- process.exitCode = 1;
1943
- return;
1944
- }
1945
- await cmdList(type);
1946
- break;
1947
- }
1948
- case "validate": {
1949
- await cmdValidate(args[1]);
1950
- break;
1951
- }
1952
- case "theme": {
1953
- await cmdTheme(args.slice(1));
1954
- break;
1955
- }
1956
- default:
1957
- console.error(error(`Unknown command: ${command}`));
1958
- cmdHelp();
1959
- process.exitCode = 1;
1960
- }
1961
- }
1962
- main().catch((e) => {
1963
- console.error(error(e.message));
1964
- process.exitCode = 1;
1965
- });
1
+ import "./chunk-LXOY447U.js";
2
+ import "./chunk-CT5XHG6I.js";
3
+ import "./chunk-3RG5ZIWI.js";