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