@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/bin.js +4 -0
- package/dist/{chunk-PDX44BCA.js → chunk-3RG5ZIWI.js} +0 -1
- package/dist/chunk-CT5XHG6I.js +203 -0
- package/dist/chunk-LXOY447U.js +2422 -0
- package/dist/{heal-2OPN63OT.js → heal-4DWU7BJS.js} +1 -2
- package/dist/index.js +3 -1965
- package/dist/{upgrade-FWICWIQW.js → upgrade-3YL3NFOG.js} +2 -3
- package/package.json +7 -7
- package/src/templates/DECANTR.md.template +53 -53
- package/LICENSE +0 -21
- package/dist/chunk-PWTUBGGJ.js +0 -359
package/dist/index.js
CHANGED
|
@@ -1,1965 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
import
|
|
3
|
-
|
|
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";
|