@decantr/cli 1.0.0-beta.5 → 1.0.0-beta.7
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 +1111 -157
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,37 +1,888 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
-
import { readFileSync
|
|
5
|
-
import { join } from "path";
|
|
6
|
-
import { createInterface } from "readline";
|
|
4
|
+
import { readFileSync as readFileSync4, existsSync as existsSync4 } from "fs";
|
|
5
|
+
import { join as join4 } from "path";
|
|
7
6
|
import { validateEssence, evaluateGuard } from "@decantr/essence-spec";
|
|
8
7
|
import { createResolver, createRegistryClient } from "@decantr/registry";
|
|
8
|
+
|
|
9
|
+
// src/detect.ts
|
|
10
|
+
import { existsSync, readFileSync } from "fs";
|
|
11
|
+
import { join } from "path";
|
|
12
|
+
var RULE_FILES = [
|
|
13
|
+
"CLAUDE.md",
|
|
14
|
+
".cursorrules",
|
|
15
|
+
".cursor/rules",
|
|
16
|
+
"AGENTS.md",
|
|
17
|
+
"GEMINI.md",
|
|
18
|
+
"copilot-instructions.md"
|
|
19
|
+
];
|
|
20
|
+
function detectProject(projectRoot = process.cwd()) {
|
|
21
|
+
const result = {
|
|
22
|
+
framework: "unknown",
|
|
23
|
+
packageManager: "unknown",
|
|
24
|
+
hasTypeScript: false,
|
|
25
|
+
hasTailwind: false,
|
|
26
|
+
existingRuleFiles: [],
|
|
27
|
+
existingEssence: false,
|
|
28
|
+
projectRoot
|
|
29
|
+
};
|
|
30
|
+
result.existingEssence = existsSync(join(projectRoot, "decantr.essence.json"));
|
|
31
|
+
for (const ruleFile of RULE_FILES) {
|
|
32
|
+
if (existsSync(join(projectRoot, ruleFile))) {
|
|
33
|
+
result.existingRuleFiles.push(ruleFile);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
if (existsSync(join(projectRoot, "pnpm-lock.yaml"))) {
|
|
37
|
+
result.packageManager = "pnpm";
|
|
38
|
+
} else if (existsSync(join(projectRoot, "yarn.lock"))) {
|
|
39
|
+
result.packageManager = "yarn";
|
|
40
|
+
} else if (existsSync(join(projectRoot, "bun.lockb"))) {
|
|
41
|
+
result.packageManager = "bun";
|
|
42
|
+
} else if (existsSync(join(projectRoot, "package-lock.json"))) {
|
|
43
|
+
result.packageManager = "npm";
|
|
44
|
+
}
|
|
45
|
+
result.hasTypeScript = existsSync(join(projectRoot, "tsconfig.json"));
|
|
46
|
+
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"));
|
|
47
|
+
if (existsSync(join(projectRoot, "next.config.js")) || existsSync(join(projectRoot, "next.config.ts")) || existsSync(join(projectRoot, "next.config.mjs"))) {
|
|
48
|
+
result.framework = "nextjs";
|
|
49
|
+
result.version = getPackageVersion(projectRoot, "next");
|
|
50
|
+
return result;
|
|
51
|
+
}
|
|
52
|
+
if (existsSync(join(projectRoot, "nuxt.config.js")) || existsSync(join(projectRoot, "nuxt.config.ts"))) {
|
|
53
|
+
result.framework = "nuxt";
|
|
54
|
+
result.version = getPackageVersion(projectRoot, "nuxt");
|
|
55
|
+
return result;
|
|
56
|
+
}
|
|
57
|
+
if (existsSync(join(projectRoot, "astro.config.mjs")) || existsSync(join(projectRoot, "astro.config.ts"))) {
|
|
58
|
+
result.framework = "astro";
|
|
59
|
+
result.version = getPackageVersion(projectRoot, "astro");
|
|
60
|
+
return result;
|
|
61
|
+
}
|
|
62
|
+
if (existsSync(join(projectRoot, "svelte.config.js")) || existsSync(join(projectRoot, "svelte.config.ts"))) {
|
|
63
|
+
result.framework = "svelte";
|
|
64
|
+
result.version = getPackageVersion(projectRoot, "svelte");
|
|
65
|
+
return result;
|
|
66
|
+
}
|
|
67
|
+
if (existsSync(join(projectRoot, "angular.json"))) {
|
|
68
|
+
result.framework = "angular";
|
|
69
|
+
result.version = getPackageVersion(projectRoot, "@angular/core");
|
|
70
|
+
return result;
|
|
71
|
+
}
|
|
72
|
+
const packageJsonPath = join(projectRoot, "package.json");
|
|
73
|
+
if (existsSync(packageJsonPath)) {
|
|
74
|
+
try {
|
|
75
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
|
|
76
|
+
const deps = { ...packageJson.dependencies, ...packageJson.devDependencies };
|
|
77
|
+
if (deps.next) {
|
|
78
|
+
result.framework = "nextjs";
|
|
79
|
+
result.version = deps.next.replace(/^\^|~/, "");
|
|
80
|
+
} else if (deps.nuxt) {
|
|
81
|
+
result.framework = "nuxt";
|
|
82
|
+
result.version = deps.nuxt.replace(/^\^|~/, "");
|
|
83
|
+
} else if (deps.astro) {
|
|
84
|
+
result.framework = "astro";
|
|
85
|
+
result.version = deps.astro.replace(/^\^|~/, "");
|
|
86
|
+
} else if (deps.svelte) {
|
|
87
|
+
result.framework = "svelte";
|
|
88
|
+
result.version = deps.svelte.replace(/^\^|~/, "");
|
|
89
|
+
} else if (deps["@angular/core"]) {
|
|
90
|
+
result.framework = "angular";
|
|
91
|
+
result.version = deps["@angular/core"].replace(/^\^|~/, "");
|
|
92
|
+
} else if (deps.vue) {
|
|
93
|
+
result.framework = "vue";
|
|
94
|
+
result.version = deps.vue.replace(/^\^|~/, "");
|
|
95
|
+
} else if (deps.react) {
|
|
96
|
+
result.framework = "react";
|
|
97
|
+
result.version = deps.react.replace(/^\^|~/, "");
|
|
98
|
+
}
|
|
99
|
+
} catch {
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
if (result.framework === "unknown" && !existsSync(packageJsonPath)) {
|
|
103
|
+
if (existsSync(join(projectRoot, "index.html"))) {
|
|
104
|
+
result.framework = "html";
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return result;
|
|
108
|
+
}
|
|
109
|
+
function getPackageVersion(projectRoot, packageName) {
|
|
110
|
+
const packageJsonPath = join(projectRoot, "package.json");
|
|
111
|
+
if (!existsSync(packageJsonPath)) return void 0;
|
|
112
|
+
try {
|
|
113
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
|
|
114
|
+
const deps = { ...packageJson.dependencies, ...packageJson.devDependencies };
|
|
115
|
+
const version = deps[packageName];
|
|
116
|
+
return version?.replace(/^\^|~/, "");
|
|
117
|
+
} catch {
|
|
118
|
+
return void 0;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// src/prompts.ts
|
|
123
|
+
import { createInterface } from "readline";
|
|
9
124
|
var BOLD = "\x1B[1m";
|
|
10
125
|
var DIM = "\x1B[2m";
|
|
11
126
|
var RESET = "\x1B[0m";
|
|
12
|
-
var RED = "\x1B[31m";
|
|
13
127
|
var GREEN = "\x1B[32m";
|
|
14
|
-
var CYAN = "\x1B[36m";
|
|
15
128
|
var YELLOW = "\x1B[33m";
|
|
129
|
+
var CYAN = "\x1B[36m";
|
|
130
|
+
function ask(question, defaultValue) {
|
|
131
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
132
|
+
const prompt = defaultValue ? `${question} ${DIM}(${defaultValue})${RESET}: ` : `${question}: `;
|
|
133
|
+
return new Promise((resolve) => {
|
|
134
|
+
rl.question(prompt, (answer) => {
|
|
135
|
+
rl.close();
|
|
136
|
+
resolve(answer.trim() || defaultValue || "");
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
async function select(question, options, defaultIdx = 0, allowOther = false) {
|
|
141
|
+
console.log(`
|
|
142
|
+
${BOLD}${question}${RESET}`);
|
|
143
|
+
for (let i = 0; i < options.length; i++) {
|
|
144
|
+
const marker = i === defaultIdx ? `${GREEN}>${RESET}` : " ";
|
|
145
|
+
const desc = options[i].description ? ` ${DIM}\u2014 ${options[i].description}${RESET}` : "";
|
|
146
|
+
console.log(` ${marker} ${i + 1}. ${options[i].label}${desc}`);
|
|
147
|
+
}
|
|
148
|
+
if (allowOther) {
|
|
149
|
+
console.log(` ${options.length + 1}. ${DIM}other (enter custom value)${RESET}`);
|
|
150
|
+
}
|
|
151
|
+
const maxIdx = allowOther ? options.length + 1 : options.length;
|
|
152
|
+
const answer = await ask(`Choose (1-${maxIdx})`, String(defaultIdx + 1));
|
|
153
|
+
const idx = parseInt(answer, 10) - 1;
|
|
154
|
+
if (allowOther && idx === options.length) {
|
|
155
|
+
const custom = await ask("Enter custom value");
|
|
156
|
+
return custom;
|
|
157
|
+
}
|
|
158
|
+
const validIdx = Math.max(0, Math.min(idx, options.length - 1));
|
|
159
|
+
return options[validIdx].value;
|
|
160
|
+
}
|
|
161
|
+
async function confirm(question, defaultYes = true) {
|
|
162
|
+
const hint = defaultYes ? "Y/n" : "y/N";
|
|
163
|
+
const answer = await ask(`${question} [${hint}]`, defaultYes ? "y" : "n");
|
|
164
|
+
return answer.toLowerCase() === "y" || answer.toLowerCase() === "yes";
|
|
165
|
+
}
|
|
166
|
+
function warn(message) {
|
|
167
|
+
console.log(`
|
|
168
|
+
${YELLOW} Warning: ${message}${RESET}`);
|
|
169
|
+
}
|
|
170
|
+
function showDetection(detected) {
|
|
171
|
+
console.log(`
|
|
172
|
+
${CYAN}Detected project configuration:${RESET}`);
|
|
173
|
+
if (detected.framework !== "unknown") {
|
|
174
|
+
const version = detected.version ? ` ${detected.version}` : "";
|
|
175
|
+
console.log(` Framework: ${detected.framework}${version}`);
|
|
176
|
+
}
|
|
177
|
+
if (detected.packageManager !== "unknown") {
|
|
178
|
+
console.log(` Package manager: ${detected.packageManager}`);
|
|
179
|
+
}
|
|
180
|
+
if (detected.hasTypeScript) {
|
|
181
|
+
console.log(` TypeScript: ${GREEN}yes${RESET}`);
|
|
182
|
+
}
|
|
183
|
+
if (detected.hasTailwind) {
|
|
184
|
+
console.log(` Tailwind CSS: ${GREEN}yes${RESET}`);
|
|
185
|
+
}
|
|
186
|
+
if (detected.existingEssence) {
|
|
187
|
+
console.log(` Existing essence: ${YELLOW}yes${RESET}`);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
async function runInteractivePrompts(detected, archetypes, blueprints, themes) {
|
|
191
|
+
showDetection(detected);
|
|
192
|
+
const blueprintOptions = [
|
|
193
|
+
{ value: "none", label: "none", description: "Start from scratch (blank canvas)" },
|
|
194
|
+
...blueprints.map((b) => ({
|
|
195
|
+
value: b.id,
|
|
196
|
+
label: b.id,
|
|
197
|
+
description: b.description
|
|
198
|
+
}))
|
|
199
|
+
];
|
|
200
|
+
const blueprint = await select("What are you building?", blueprintOptions, 0, true);
|
|
201
|
+
const isBlank = blueprint === "none";
|
|
202
|
+
const themeOptions = themes.map((t) => ({
|
|
203
|
+
value: t.id,
|
|
204
|
+
label: t.id,
|
|
205
|
+
description: t.description
|
|
206
|
+
}));
|
|
207
|
+
const defaultThemeIdx = themeOptions.findIndex((t) => t.value === "luminarum") || 0;
|
|
208
|
+
const theme = await select("Choose a theme", themeOptions, Math.max(0, defaultThemeIdx), true);
|
|
209
|
+
const mode = await select(
|
|
210
|
+
"Color mode",
|
|
211
|
+
[
|
|
212
|
+
{ value: "dark", label: "dark", description: "Dark background" },
|
|
213
|
+
{ value: "light", label: "light", description: "Light background" },
|
|
214
|
+
{ value: "auto", label: "auto", description: "Follow system preference" }
|
|
215
|
+
],
|
|
216
|
+
0
|
|
217
|
+
);
|
|
218
|
+
const shape = await select(
|
|
219
|
+
"Border shape",
|
|
220
|
+
[
|
|
221
|
+
{ value: "pill", label: "pill", description: "Fully rounded corners" },
|
|
222
|
+
{ value: "rounded", label: "rounded", description: "Moderately rounded" },
|
|
223
|
+
{ value: "sharp", label: "sharp", description: "No border radius" }
|
|
224
|
+
],
|
|
225
|
+
0,
|
|
226
|
+
true
|
|
227
|
+
);
|
|
228
|
+
const frameworkOptions = [
|
|
229
|
+
{ value: "react", label: "react", description: "React / Create React App" },
|
|
230
|
+
{ value: "nextjs", label: "nextjs", description: "Next.js" },
|
|
231
|
+
{ value: "vue", label: "vue", description: "Vue.js" },
|
|
232
|
+
{ value: "nuxt", label: "nuxt", description: "Nuxt" },
|
|
233
|
+
{ value: "svelte", label: "svelte", description: "Svelte / SvelteKit" },
|
|
234
|
+
{ value: "astro", label: "astro", description: "Astro" },
|
|
235
|
+
{ value: "angular", label: "angular", description: "Angular" },
|
|
236
|
+
{ value: "html", label: "html", description: "Plain HTML/CSS/JS" }
|
|
237
|
+
];
|
|
238
|
+
let defaultFrameworkIdx = frameworkOptions.findIndex((f) => f.value === detected.framework);
|
|
239
|
+
if (defaultFrameworkIdx < 0) defaultFrameworkIdx = 0;
|
|
240
|
+
const target = await select("Target framework", frameworkOptions, defaultFrameworkIdx, true);
|
|
241
|
+
if (detected.framework !== "unknown" && target !== detected.framework) {
|
|
242
|
+
warn(`This project appears to be ${detected.framework} but you selected ${target}.`);
|
|
243
|
+
const proceed = await confirm("Continue anyway?", false);
|
|
244
|
+
if (!proceed) {
|
|
245
|
+
console.log(`${DIM}Using detected framework: ${detected.framework}${RESET}`);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
const guardMode = await select(
|
|
249
|
+
"Guard enforcement level",
|
|
250
|
+
[
|
|
251
|
+
{ value: "creative", label: "creative", description: "Advisory only (new projects)" },
|
|
252
|
+
{ value: "guided", label: "guided", description: "Style, structure, recipe enforced" },
|
|
253
|
+
{ value: "strict", label: "strict", description: "All 5 rules enforced exactly" }
|
|
254
|
+
],
|
|
255
|
+
detected.existingEssence ? 1 : 2
|
|
256
|
+
// Default to guided for existing, strict for new
|
|
257
|
+
);
|
|
258
|
+
const density = await select(
|
|
259
|
+
"Spacing density",
|
|
260
|
+
[
|
|
261
|
+
{ value: "compact", label: "compact", description: "Dense UI, minimal spacing" },
|
|
262
|
+
{ value: "comfortable", label: "comfortable", description: "Balanced spacing" },
|
|
263
|
+
{ value: "spacious", label: "spacious", description: "Generous whitespace" }
|
|
264
|
+
],
|
|
265
|
+
1
|
|
266
|
+
);
|
|
267
|
+
const shellOptions = [
|
|
268
|
+
{ value: "sidebar-main", label: "sidebar-main", description: "Collapsible sidebar with main content" },
|
|
269
|
+
{ value: "top-nav-main", label: "top-nav-main", description: "Horizontal nav with full-width content" },
|
|
270
|
+
{ value: "centered", label: "centered", description: "Centered card (auth flows)" },
|
|
271
|
+
{ value: "full-bleed", label: "full-bleed", description: "No persistent nav (landing pages)" },
|
|
272
|
+
{ value: "minimal-header", label: "minimal-header", description: "Slim header with centered content" }
|
|
273
|
+
];
|
|
274
|
+
let defaultShellIdx = 0;
|
|
275
|
+
if (["nextjs", "nuxt", "astro"].includes(target)) {
|
|
276
|
+
defaultShellIdx = shellOptions.findIndex((s) => s.value === "top-nav-main");
|
|
277
|
+
}
|
|
278
|
+
const shell = await select("Default page shell (layout)", shellOptions, Math.max(0, defaultShellIdx), true);
|
|
279
|
+
return {
|
|
280
|
+
blueprint: isBlank ? void 0 : blueprint,
|
|
281
|
+
archetype: void 0,
|
|
282
|
+
// Will be derived from blueprint
|
|
283
|
+
theme,
|
|
284
|
+
mode,
|
|
285
|
+
shape,
|
|
286
|
+
target,
|
|
287
|
+
guard: guardMode,
|
|
288
|
+
density,
|
|
289
|
+
shell,
|
|
290
|
+
personality: ["professional"],
|
|
291
|
+
features: [],
|
|
292
|
+
existing: detected.existingEssence
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
function parseFlags(args, detected) {
|
|
296
|
+
const options = {};
|
|
297
|
+
if (typeof args.blueprint === "string") options.blueprint = args.blueprint;
|
|
298
|
+
if (typeof args.archetype === "string") options.archetype = args.archetype;
|
|
299
|
+
if (typeof args.theme === "string") options.theme = args.theme;
|
|
300
|
+
if (args.mode === "dark" || args.mode === "light" || args.mode === "auto") options.mode = args.mode;
|
|
301
|
+
if (typeof args.shape === "string") options.shape = args.shape;
|
|
302
|
+
if (typeof args.target === "string") options.target = args.target;
|
|
303
|
+
if (args.guard === "creative" || args.guard === "guided" || args.guard === "strict") options.guard = args.guard;
|
|
304
|
+
if (args.density === "compact" || args.density === "comfortable" || args.density === "spacious") options.density = args.density;
|
|
305
|
+
if (typeof args.shell === "string") options.shell = args.shell;
|
|
306
|
+
if (typeof args.personality === "string") options.personality = args.personality.split(",").map((s) => s.trim());
|
|
307
|
+
if (typeof args.features === "string") options.features = args.features.split(",").map((s) => s.trim());
|
|
308
|
+
if (args.existing === true) options.existing = true;
|
|
309
|
+
return options;
|
|
310
|
+
}
|
|
311
|
+
function mergeWithDefaults(flags, detected) {
|
|
312
|
+
return {
|
|
313
|
+
blueprint: flags.blueprint,
|
|
314
|
+
archetype: flags.archetype,
|
|
315
|
+
theme: flags.theme || "luminarum",
|
|
316
|
+
mode: flags.mode || "dark",
|
|
317
|
+
shape: flags.shape || "rounded",
|
|
318
|
+
target: flags.target || (detected.framework !== "unknown" ? detected.framework : "react"),
|
|
319
|
+
guard: flags.guard || (detected.existingEssence ? "guided" : "strict"),
|
|
320
|
+
density: flags.density || "comfortable",
|
|
321
|
+
shell: flags.shell || "sidebar-main",
|
|
322
|
+
personality: flags.personality || ["professional"],
|
|
323
|
+
features: flags.features || [],
|
|
324
|
+
existing: flags.existing || detected.existingEssence
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// src/scaffold.ts
|
|
329
|
+
import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, writeFileSync, appendFileSync } from "fs";
|
|
330
|
+
import { join as join2, dirname } from "path";
|
|
331
|
+
import { fileURLToPath } from "url";
|
|
332
|
+
var __dirname = dirname(fileURLToPath(import.meta.url));
|
|
333
|
+
var CLI_VERSION = "1.0.0";
|
|
334
|
+
function loadTemplate(name) {
|
|
335
|
+
const templatePath = join2(__dirname, "templates", name);
|
|
336
|
+
if (existsSync2(templatePath)) {
|
|
337
|
+
return readFileSync2(templatePath, "utf-8");
|
|
338
|
+
}
|
|
339
|
+
const srcPath = join2(__dirname, "..", "src", "templates", name);
|
|
340
|
+
if (existsSync2(srcPath)) {
|
|
341
|
+
return readFileSync2(srcPath, "utf-8");
|
|
342
|
+
}
|
|
343
|
+
throw new Error(`Template not found: ${name}`);
|
|
344
|
+
}
|
|
345
|
+
function renderTemplate(template, vars) {
|
|
346
|
+
let result = template;
|
|
347
|
+
for (const [key, value] of Object.entries(vars)) {
|
|
348
|
+
result = result.replace(new RegExp(`\\{\\{${key}\\}\\}`, "g"), value);
|
|
349
|
+
}
|
|
350
|
+
return result;
|
|
351
|
+
}
|
|
352
|
+
function buildEssence(options, blueprint) {
|
|
353
|
+
let structure = [
|
|
354
|
+
{ id: "home", shell: options.shell, layout: [] }
|
|
355
|
+
];
|
|
356
|
+
let features = options.features;
|
|
357
|
+
if (blueprint?.pages) {
|
|
358
|
+
structure = blueprint.pages.map((p) => ({
|
|
359
|
+
id: p.id,
|
|
360
|
+
shell: p.shell || options.shell,
|
|
361
|
+
layout: p.default_layout || []
|
|
362
|
+
}));
|
|
363
|
+
}
|
|
364
|
+
if (blueprint?.features) {
|
|
365
|
+
features = [.../* @__PURE__ */ new Set([...features, ...blueprint.features])];
|
|
366
|
+
}
|
|
367
|
+
const contentGapMap = {
|
|
368
|
+
compact: "_gap2",
|
|
369
|
+
comfortable: "_gap4",
|
|
370
|
+
spacious: "_gap6"
|
|
371
|
+
};
|
|
372
|
+
return {
|
|
373
|
+
version: "2.0.0",
|
|
374
|
+
archetype: options.archetype,
|
|
375
|
+
blueprint: options.blueprint,
|
|
376
|
+
theme: {
|
|
377
|
+
style: options.theme,
|
|
378
|
+
mode: options.mode,
|
|
379
|
+
recipe: options.theme,
|
|
380
|
+
// Recipe defaults to theme
|
|
381
|
+
shape: options.shape
|
|
382
|
+
},
|
|
383
|
+
personality: options.personality,
|
|
384
|
+
platform: {
|
|
385
|
+
type: "spa",
|
|
386
|
+
routing: "hash"
|
|
387
|
+
},
|
|
388
|
+
structure,
|
|
389
|
+
features,
|
|
390
|
+
guard: {
|
|
391
|
+
enforce_style: true,
|
|
392
|
+
enforce_recipe: true,
|
|
393
|
+
mode: options.guard
|
|
394
|
+
},
|
|
395
|
+
density: {
|
|
396
|
+
level: options.density,
|
|
397
|
+
content_gap: contentGapMap[options.density] || "_gap4"
|
|
398
|
+
},
|
|
399
|
+
target: options.target
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
function generateDecantrMd(essence, detected) {
|
|
403
|
+
const template = loadTemplate("DECANTR.md.template");
|
|
404
|
+
const pagesTable = essence.structure.map(
|
|
405
|
+
(p) => `| ${p.id} | ${p.shell} | ${p.layout.join(", ") || "none"} |`
|
|
406
|
+
).join("\n");
|
|
407
|
+
const allPatterns = [...new Set(essence.structure.flatMap((p) => p.layout))];
|
|
408
|
+
const patternsList = allPatterns.length > 0 ? allPatterns.map((p) => `- \`${p}\``).join("\n") : "- No patterns specified yet";
|
|
409
|
+
const projectSummary = [
|
|
410
|
+
`**Archetype:** ${essence.archetype || "custom"}`,
|
|
411
|
+
`**Target:** ${essence.target}`,
|
|
412
|
+
`**Theme:** ${essence.theme.style} (${essence.theme.mode} mode)`,
|
|
413
|
+
`**Guard Mode:** ${essence.guard.mode}`,
|
|
414
|
+
`**Pages:** ${essence.structure.map((s) => s.id).join(", ")}`
|
|
415
|
+
].join("\n");
|
|
416
|
+
const shellStructures = {
|
|
417
|
+
"sidebar-main": "nav (left) | header (top) | body (scrollable)",
|
|
418
|
+
"top-nav-main": "header (full width) | body (scrollable)",
|
|
419
|
+
"centered": "body (centered card)",
|
|
420
|
+
"full-bleed": "header (floating) | body (full page sections)",
|
|
421
|
+
"minimal-header": "header (slim) | body (centered)"
|
|
422
|
+
};
|
|
423
|
+
const defaultShell = essence.structure[0]?.shell || "sidebar-main";
|
|
424
|
+
const shellStructure = shellStructures[defaultShell] || "Custom shell layout";
|
|
425
|
+
const vars = {
|
|
426
|
+
GUARD_MODE: essence.guard.mode,
|
|
427
|
+
PROJECT_SUMMARY: projectSummary,
|
|
428
|
+
THEME_STYLE: essence.theme.style,
|
|
429
|
+
THEME_MODE: essence.theme.mode,
|
|
430
|
+
THEME_RECIPE: essence.theme.recipe,
|
|
431
|
+
TARGET: essence.target,
|
|
432
|
+
PAGES_TABLE: `| Page | Shell | Layout |
|
|
433
|
+
|------|-------|--------|
|
|
434
|
+
${pagesTable}`,
|
|
435
|
+
PATTERNS_LIST: patternsList,
|
|
436
|
+
DEFAULT_SHELL: defaultShell,
|
|
437
|
+
SHELL_STRUCTURE: shellStructure,
|
|
438
|
+
PERSONALITY: essence.personality.join(", "),
|
|
439
|
+
DENSITY: essence.density.level,
|
|
440
|
+
AVAILABLE_PATTERNS: "(See registry or .decantr/cache/patterns/)",
|
|
441
|
+
AVAILABLE_THEMES: "(See registry or .decantr/cache/themes/)",
|
|
442
|
+
AVAILABLE_SHELLS: "sidebar-main, top-nav-main, centered, full-bleed, minimal-header",
|
|
443
|
+
VERSION: CLI_VERSION
|
|
444
|
+
};
|
|
445
|
+
return renderTemplate(template, vars);
|
|
446
|
+
}
|
|
447
|
+
function generateProjectJson(detected, options, registrySource) {
|
|
448
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
449
|
+
const data = {
|
|
450
|
+
detected: {
|
|
451
|
+
framework: detected.framework,
|
|
452
|
+
version: detected.version || null,
|
|
453
|
+
packageManager: detected.packageManager,
|
|
454
|
+
hasTypeScript: detected.hasTypeScript,
|
|
455
|
+
hasTailwind: detected.hasTailwind,
|
|
456
|
+
existingRuleFiles: detected.existingRuleFiles
|
|
457
|
+
},
|
|
458
|
+
overrides: {
|
|
459
|
+
framework: options.target !== detected.framework ? options.target : null
|
|
460
|
+
},
|
|
461
|
+
sync: {
|
|
462
|
+
status: registrySource === "api" ? "synced" : "needs-sync",
|
|
463
|
+
lastSync: now,
|
|
464
|
+
registrySource,
|
|
465
|
+
cachedContent: {
|
|
466
|
+
archetypes: [],
|
|
467
|
+
patterns: [],
|
|
468
|
+
themes: [],
|
|
469
|
+
recipes: []
|
|
470
|
+
}
|
|
471
|
+
},
|
|
472
|
+
initialized: {
|
|
473
|
+
at: now,
|
|
474
|
+
via: "cli",
|
|
475
|
+
version: CLI_VERSION,
|
|
476
|
+
flags: buildFlagsString(options)
|
|
477
|
+
}
|
|
478
|
+
};
|
|
479
|
+
return JSON.stringify(data, null, 2);
|
|
480
|
+
}
|
|
481
|
+
function buildFlagsString(options) {
|
|
482
|
+
const flags = [];
|
|
483
|
+
if (options.blueprint) flags.push(`--blueprint=${options.blueprint}`);
|
|
484
|
+
if (options.theme) flags.push(`--theme=${options.theme}`);
|
|
485
|
+
if (options.mode) flags.push(`--mode=${options.mode}`);
|
|
486
|
+
if (options.guard) flags.push(`--guard=${options.guard}`);
|
|
487
|
+
return flags.join(" ");
|
|
488
|
+
}
|
|
489
|
+
function generateTaskContext(templateName, essence) {
|
|
490
|
+
const template = loadTemplate(templateName);
|
|
491
|
+
const defaultShell = essence.structure[0]?.shell || "sidebar-main";
|
|
492
|
+
const layout = essence.structure[0]?.layout.join(", ") || "none";
|
|
493
|
+
const scaffoldStructure = essence.structure.map((p) => {
|
|
494
|
+
const patterns = p.layout.length > 0 ? `
|
|
495
|
+
- Patterns: ${p.layout.join(", ")}` : "";
|
|
496
|
+
return `- **${p.id}** (${p.shell})${patterns}`;
|
|
497
|
+
}).join("\n");
|
|
498
|
+
const vars = {
|
|
499
|
+
TARGET: essence.target,
|
|
500
|
+
THEME_STYLE: essence.theme.style,
|
|
501
|
+
THEME_MODE: essence.theme.mode,
|
|
502
|
+
THEME_RECIPE: essence.theme.recipe,
|
|
503
|
+
DEFAULT_SHELL: defaultShell,
|
|
504
|
+
GUARD_MODE: essence.guard.mode,
|
|
505
|
+
LAYOUT: layout,
|
|
506
|
+
DENSITY: essence.density.level,
|
|
507
|
+
CONTENT_GAP: essence.density.content_gap,
|
|
508
|
+
SCAFFOLD_STRUCTURE: scaffoldStructure
|
|
509
|
+
};
|
|
510
|
+
return renderTemplate(template, vars);
|
|
511
|
+
}
|
|
512
|
+
function generateEssenceSummary(essence) {
|
|
513
|
+
const template = loadTemplate("essence-summary.md.template");
|
|
514
|
+
const pagesTable = `| Page | Shell | Layout |
|
|
515
|
+
|------|-------|--------|
|
|
516
|
+
${essence.structure.map((p) => `| ${p.id} | ${p.shell} | ${p.layout.join(", ") || "none"} |`).join("\n")}`;
|
|
517
|
+
const featuresList = essence.features.length > 0 ? essence.features.map((f) => `- ${f}`).join("\n") : "- No features specified";
|
|
518
|
+
const vars = {
|
|
519
|
+
ARCHETYPE: essence.archetype || "custom",
|
|
520
|
+
BLUEPRINT: essence.blueprint || "none",
|
|
521
|
+
PERSONALITY: essence.personality.join(", "),
|
|
522
|
+
TARGET: essence.target,
|
|
523
|
+
THEME_STYLE: essence.theme.style,
|
|
524
|
+
THEME_MODE: essence.theme.mode,
|
|
525
|
+
THEME_RECIPE: essence.theme.recipe,
|
|
526
|
+
SHAPE: essence.theme.shape,
|
|
527
|
+
PAGES_TABLE: pagesTable,
|
|
528
|
+
FEATURES_LIST: featuresList,
|
|
529
|
+
GUARD_MODE: essence.guard.mode,
|
|
530
|
+
ENFORCE_STYLE: String(essence.guard.enforce_style),
|
|
531
|
+
ENFORCE_RECIPE: String(essence.guard.enforce_recipe),
|
|
532
|
+
DENSITY: essence.density.level,
|
|
533
|
+
CONTENT_GAP: essence.density.content_gap,
|
|
534
|
+
LAST_UPDATED: (/* @__PURE__ */ new Date()).toISOString()
|
|
535
|
+
};
|
|
536
|
+
return renderTemplate(template, vars);
|
|
537
|
+
}
|
|
538
|
+
function updateGitignore(projectRoot) {
|
|
539
|
+
const gitignorePath = join2(projectRoot, ".gitignore");
|
|
540
|
+
const cacheEntry = ".decantr/cache/";
|
|
541
|
+
if (existsSync2(gitignorePath)) {
|
|
542
|
+
const content = readFileSync2(gitignorePath, "utf-8");
|
|
543
|
+
if (!content.includes(cacheEntry)) {
|
|
544
|
+
appendFileSync(gitignorePath, `
|
|
545
|
+
# Decantr cache
|
|
546
|
+
${cacheEntry}
|
|
547
|
+
`);
|
|
548
|
+
return true;
|
|
549
|
+
}
|
|
550
|
+
return false;
|
|
551
|
+
} else {
|
|
552
|
+
writeFileSync(gitignorePath, `# Decantr cache
|
|
553
|
+
${cacheEntry}
|
|
554
|
+
`);
|
|
555
|
+
return true;
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
function scaffoldProject(projectRoot, options, detected, blueprint, registrySource = "bundled") {
|
|
559
|
+
const essence = buildEssence(options, blueprint);
|
|
560
|
+
const decantrDir = join2(projectRoot, ".decantr");
|
|
561
|
+
const contextDir = join2(decantrDir, "context");
|
|
562
|
+
const cacheDir = join2(decantrDir, "cache");
|
|
563
|
+
mkdirSync(contextDir, { recursive: true });
|
|
564
|
+
mkdirSync(cacheDir, { recursive: true });
|
|
565
|
+
const essencePath = join2(projectRoot, "decantr.essence.json");
|
|
566
|
+
writeFileSync(essencePath, JSON.stringify(essence, null, 2) + "\n");
|
|
567
|
+
const decantrMdPath = join2(projectRoot, "DECANTR.md");
|
|
568
|
+
writeFileSync(decantrMdPath, generateDecantrMd(essence, detected));
|
|
569
|
+
const projectJsonPath = join2(decantrDir, "project.json");
|
|
570
|
+
writeFileSync(projectJsonPath, generateProjectJson(detected, options, registrySource));
|
|
571
|
+
const contextFiles = [];
|
|
572
|
+
const scaffoldPath = join2(contextDir, "task-scaffold.md");
|
|
573
|
+
writeFileSync(scaffoldPath, generateTaskContext("task-scaffold.md.template", essence));
|
|
574
|
+
contextFiles.push(scaffoldPath);
|
|
575
|
+
const addPagePath = join2(contextDir, "task-add-page.md");
|
|
576
|
+
writeFileSync(addPagePath, generateTaskContext("task-add-page.md.template", essence));
|
|
577
|
+
contextFiles.push(addPagePath);
|
|
578
|
+
const modifyPath = join2(contextDir, "task-modify.md");
|
|
579
|
+
writeFileSync(modifyPath, generateTaskContext("task-modify.md.template", essence));
|
|
580
|
+
contextFiles.push(modifyPath);
|
|
581
|
+
const summaryPath = join2(contextDir, "essence-summary.md");
|
|
582
|
+
writeFileSync(summaryPath, generateEssenceSummary(essence));
|
|
583
|
+
contextFiles.push(summaryPath);
|
|
584
|
+
const gitignoreUpdated = updateGitignore(projectRoot);
|
|
585
|
+
return {
|
|
586
|
+
essencePath,
|
|
587
|
+
decantrMdPath,
|
|
588
|
+
projectJsonPath,
|
|
589
|
+
contextFiles,
|
|
590
|
+
gitignoreUpdated
|
|
591
|
+
};
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
// src/registry.ts
|
|
595
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2, readdirSync } from "fs";
|
|
596
|
+
import { join as join3, dirname as dirname2 } from "path";
|
|
597
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
598
|
+
var __dirname2 = dirname2(fileURLToPath2(import.meta.url));
|
|
599
|
+
var DEFAULT_API_URL = "https://decantr-registry.fly.dev/v1";
|
|
600
|
+
function getBundledContentRoot() {
|
|
601
|
+
const bundled = join3(__dirname2, "..", "..", "..", "content");
|
|
602
|
+
if (existsSync3(bundled)) return bundled;
|
|
603
|
+
const distBundled = join3(__dirname2, "..", "..", "..", "..", "content");
|
|
604
|
+
if (existsSync3(distBundled)) return distBundled;
|
|
605
|
+
return bundled;
|
|
606
|
+
}
|
|
607
|
+
async function fetchWithTimeout(url, timeoutMs = 5e3) {
|
|
608
|
+
const controller = new AbortController();
|
|
609
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
610
|
+
try {
|
|
611
|
+
const response = await fetch(url, { signal: controller.signal });
|
|
612
|
+
return response;
|
|
613
|
+
} finally {
|
|
614
|
+
clearTimeout(timeout);
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
async function tryApi(endpoint, apiUrl = DEFAULT_API_URL) {
|
|
618
|
+
try {
|
|
619
|
+
const url = `${apiUrl}/${endpoint}`;
|
|
620
|
+
const response = await fetchWithTimeout(url);
|
|
621
|
+
if (!response.ok) return null;
|
|
622
|
+
const data = await response.json();
|
|
623
|
+
return {
|
|
624
|
+
data,
|
|
625
|
+
source: { type: "api", url: apiUrl }
|
|
626
|
+
};
|
|
627
|
+
} catch {
|
|
628
|
+
return null;
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
function loadFromCache(cacheDir, contentType, id) {
|
|
632
|
+
const cachePath = id ? join3(cacheDir, contentType, `${id}.json`) : join3(cacheDir, contentType, "index.json");
|
|
633
|
+
if (!existsSync3(cachePath)) return null;
|
|
634
|
+
try {
|
|
635
|
+
const data = JSON.parse(readFileSync3(cachePath, "utf-8"));
|
|
636
|
+
return {
|
|
637
|
+
data,
|
|
638
|
+
source: { type: "cache" }
|
|
639
|
+
};
|
|
640
|
+
} catch {
|
|
641
|
+
return null;
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
function loadFromBundled(contentType, id) {
|
|
645
|
+
const contentRoot = getBundledContentRoot();
|
|
646
|
+
if (id) {
|
|
647
|
+
const itemPath = join3(contentRoot, contentType, `${id}.json`);
|
|
648
|
+
if (!existsSync3(itemPath)) return null;
|
|
649
|
+
try {
|
|
650
|
+
const data = JSON.parse(readFileSync3(itemPath, "utf-8"));
|
|
651
|
+
return {
|
|
652
|
+
data,
|
|
653
|
+
source: { type: "bundled" }
|
|
654
|
+
};
|
|
655
|
+
} catch {
|
|
656
|
+
return null;
|
|
657
|
+
}
|
|
658
|
+
} else {
|
|
659
|
+
const dir = join3(contentRoot, contentType);
|
|
660
|
+
if (!existsSync3(dir)) return null;
|
|
661
|
+
try {
|
|
662
|
+
const files = readdirSync(dir).filter((f) => f.endsWith(".json"));
|
|
663
|
+
const items = files.map((f) => {
|
|
664
|
+
const content = JSON.parse(readFileSync3(join3(dir, f), "utf-8"));
|
|
665
|
+
return { id: content.id || f.replace(".json", ""), ...content };
|
|
666
|
+
});
|
|
667
|
+
return {
|
|
668
|
+
data: { items, total: items.length },
|
|
669
|
+
source: { type: "bundled" }
|
|
670
|
+
};
|
|
671
|
+
} catch {
|
|
672
|
+
return null;
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
function saveToCache(cacheDir, contentType, id, data) {
|
|
677
|
+
const dir = join3(cacheDir, contentType);
|
|
678
|
+
mkdirSync2(dir, { recursive: true });
|
|
679
|
+
const cachePath = id ? join3(dir, `${id}.json`) : join3(dir, "index.json");
|
|
680
|
+
writeFileSync2(cachePath, JSON.stringify(data, null, 2));
|
|
681
|
+
}
|
|
682
|
+
var RegistryClient = class {
|
|
683
|
+
cacheDir;
|
|
684
|
+
apiUrl;
|
|
685
|
+
offline;
|
|
686
|
+
constructor(options = {}) {
|
|
687
|
+
this.cacheDir = options.cacheDir || join3(process.cwd(), ".decantr", "cache");
|
|
688
|
+
this.apiUrl = options.apiUrl || DEFAULT_API_URL;
|
|
689
|
+
this.offline = options.offline || false;
|
|
690
|
+
}
|
|
691
|
+
/**
|
|
692
|
+
* Fetch archetypes list.
|
|
693
|
+
*/
|
|
694
|
+
async fetchArchetypes() {
|
|
695
|
+
if (!this.offline) {
|
|
696
|
+
const apiResult = await tryApi("archetypes", this.apiUrl);
|
|
697
|
+
if (apiResult) {
|
|
698
|
+
saveToCache(this.cacheDir, "archetypes", null, apiResult.data);
|
|
699
|
+
return apiResult;
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
const cacheResult = loadFromCache(
|
|
703
|
+
this.cacheDir,
|
|
704
|
+
"archetypes"
|
|
705
|
+
);
|
|
706
|
+
if (cacheResult) return cacheResult;
|
|
707
|
+
const bundledResult = loadFromBundled("archetypes");
|
|
708
|
+
if (bundledResult) return bundledResult;
|
|
709
|
+
return {
|
|
710
|
+
data: { items: [], total: 0 },
|
|
711
|
+
source: { type: "bundled" }
|
|
712
|
+
};
|
|
713
|
+
}
|
|
714
|
+
/**
|
|
715
|
+
* Fetch a single archetype.
|
|
716
|
+
*/
|
|
717
|
+
async fetchArchetype(id) {
|
|
718
|
+
if (!this.offline) {
|
|
719
|
+
const apiResult = await tryApi(`archetypes/${id}`, this.apiUrl);
|
|
720
|
+
if (apiResult) {
|
|
721
|
+
saveToCache(this.cacheDir, "archetypes", id, apiResult.data);
|
|
722
|
+
return apiResult;
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
const cacheResult = loadFromCache(this.cacheDir, "archetypes", id);
|
|
726
|
+
if (cacheResult) return cacheResult;
|
|
727
|
+
return loadFromBundled("archetypes", id);
|
|
728
|
+
}
|
|
729
|
+
/**
|
|
730
|
+
* Fetch blueprints list.
|
|
731
|
+
*/
|
|
732
|
+
async fetchBlueprints() {
|
|
733
|
+
if (!this.offline) {
|
|
734
|
+
const apiResult = await tryApi("blueprints", this.apiUrl);
|
|
735
|
+
if (apiResult) {
|
|
736
|
+
saveToCache(this.cacheDir, "blueprints", null, apiResult.data);
|
|
737
|
+
return apiResult;
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
const cacheResult = loadFromCache(
|
|
741
|
+
this.cacheDir,
|
|
742
|
+
"blueprints"
|
|
743
|
+
);
|
|
744
|
+
if (cacheResult) return cacheResult;
|
|
745
|
+
const bundledResult = loadFromBundled("blueprints");
|
|
746
|
+
if (bundledResult) return bundledResult;
|
|
747
|
+
return {
|
|
748
|
+
data: { items: [], total: 0 },
|
|
749
|
+
source: { type: "bundled" }
|
|
750
|
+
};
|
|
751
|
+
}
|
|
752
|
+
/**
|
|
753
|
+
* Fetch a single blueprint.
|
|
754
|
+
*/
|
|
755
|
+
async fetchBlueprint(id) {
|
|
756
|
+
if (!this.offline) {
|
|
757
|
+
const apiResult = await tryApi(`blueprints/${id}`, this.apiUrl);
|
|
758
|
+
if (apiResult) {
|
|
759
|
+
saveToCache(this.cacheDir, "blueprints", id, apiResult.data);
|
|
760
|
+
return apiResult;
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
const cacheResult = loadFromCache(this.cacheDir, "blueprints", id);
|
|
764
|
+
if (cacheResult) return cacheResult;
|
|
765
|
+
return loadFromBundled("blueprints", id);
|
|
766
|
+
}
|
|
767
|
+
/**
|
|
768
|
+
* Fetch themes list.
|
|
769
|
+
*/
|
|
770
|
+
async fetchThemes() {
|
|
771
|
+
if (!this.offline) {
|
|
772
|
+
const apiResult = await tryApi("themes", this.apiUrl);
|
|
773
|
+
if (apiResult) {
|
|
774
|
+
saveToCache(this.cacheDir, "themes", null, apiResult.data);
|
|
775
|
+
return apiResult;
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
const cacheResult = loadFromCache(
|
|
779
|
+
this.cacheDir,
|
|
780
|
+
"themes"
|
|
781
|
+
);
|
|
782
|
+
if (cacheResult) return cacheResult;
|
|
783
|
+
const bundledResult = loadFromBundled("themes");
|
|
784
|
+
if (bundledResult) return bundledResult;
|
|
785
|
+
return {
|
|
786
|
+
data: { items: [], total: 0 },
|
|
787
|
+
source: { type: "bundled" }
|
|
788
|
+
};
|
|
789
|
+
}
|
|
790
|
+
/**
|
|
791
|
+
* Fetch patterns list.
|
|
792
|
+
*/
|
|
793
|
+
async fetchPatterns() {
|
|
794
|
+
if (!this.offline) {
|
|
795
|
+
const apiResult = await tryApi("patterns", this.apiUrl);
|
|
796
|
+
if (apiResult) {
|
|
797
|
+
saveToCache(this.cacheDir, "patterns", null, apiResult.data);
|
|
798
|
+
return apiResult;
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
const cacheResult = loadFromCache(
|
|
802
|
+
this.cacheDir,
|
|
803
|
+
"patterns"
|
|
804
|
+
);
|
|
805
|
+
if (cacheResult) return cacheResult;
|
|
806
|
+
const bundledResult = loadFromBundled("patterns");
|
|
807
|
+
if (bundledResult) return bundledResult;
|
|
808
|
+
return {
|
|
809
|
+
data: { items: [], total: 0 },
|
|
810
|
+
source: { type: "bundled" }
|
|
811
|
+
};
|
|
812
|
+
}
|
|
813
|
+
/**
|
|
814
|
+
* Check if API is available.
|
|
815
|
+
*/
|
|
816
|
+
async checkApiAvailability() {
|
|
817
|
+
if (this.offline) return false;
|
|
818
|
+
try {
|
|
819
|
+
const response = await fetchWithTimeout(`${this.apiUrl.replace("/v1", "")}/health`, 3e3);
|
|
820
|
+
return response.ok;
|
|
821
|
+
} catch {
|
|
822
|
+
return false;
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
/**
|
|
826
|
+
* Get the source used for the last fetch.
|
|
827
|
+
*/
|
|
828
|
+
getSourceType() {
|
|
829
|
+
return this.offline ? "bundled" : "api";
|
|
830
|
+
}
|
|
831
|
+
};
|
|
832
|
+
async function syncRegistry(cacheDir, apiUrl = DEFAULT_API_URL) {
|
|
833
|
+
const client = new RegistryClient({ cacheDir, apiUrl, offline: false });
|
|
834
|
+
const synced = [];
|
|
835
|
+
const failed = [];
|
|
836
|
+
const apiAvailable = await client.checkApiAvailability();
|
|
837
|
+
if (!apiAvailable) {
|
|
838
|
+
return { synced: [], failed: ["API unavailable"], source: "bundled" };
|
|
839
|
+
}
|
|
840
|
+
const types = ["archetypes", "blueprints", "themes", "patterns"];
|
|
841
|
+
for (const type of types) {
|
|
842
|
+
try {
|
|
843
|
+
const fetchMethod = `fetch${type.charAt(0).toUpperCase()}${type.slice(1)}`;
|
|
844
|
+
const result = await client[fetchMethod]();
|
|
845
|
+
if (result.source.type === "api") {
|
|
846
|
+
synced.push(type);
|
|
847
|
+
}
|
|
848
|
+
} catch {
|
|
849
|
+
failed.push(type);
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
return {
|
|
853
|
+
synced,
|
|
854
|
+
failed,
|
|
855
|
+
source: synced.length > 0 ? "api" : "bundled"
|
|
856
|
+
};
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
// src/index.ts
|
|
860
|
+
var BOLD2 = "\x1B[1m";
|
|
861
|
+
var DIM2 = "\x1B[2m";
|
|
862
|
+
var RESET2 = "\x1B[0m";
|
|
863
|
+
var RED = "\x1B[31m";
|
|
864
|
+
var GREEN2 = "\x1B[32m";
|
|
865
|
+
var CYAN2 = "\x1B[36m";
|
|
866
|
+
var YELLOW2 = "\x1B[33m";
|
|
16
867
|
function heading(text) {
|
|
17
868
|
return `
|
|
18
|
-
${
|
|
869
|
+
${BOLD2}${text}${RESET2}
|
|
19
870
|
`;
|
|
20
871
|
}
|
|
21
872
|
function success(text) {
|
|
22
|
-
return `${
|
|
873
|
+
return `${GREEN2}${text}${RESET2}`;
|
|
23
874
|
}
|
|
24
875
|
function error(text) {
|
|
25
|
-
return `${RED}${text}${
|
|
876
|
+
return `${RED}${text}${RESET2}`;
|
|
26
877
|
}
|
|
27
878
|
function dim(text) {
|
|
28
|
-
return `${
|
|
879
|
+
return `${DIM2}${text}${RESET2}`;
|
|
29
880
|
}
|
|
30
881
|
function cyan(text) {
|
|
31
|
-
return `${
|
|
882
|
+
return `${CYAN2}${text}${RESET2}`;
|
|
32
883
|
}
|
|
33
884
|
function getContentRoot() {
|
|
34
|
-
const bundled =
|
|
885
|
+
const bundled = join4(import.meta.dirname, "..", "..", "..", "content");
|
|
35
886
|
return process.env.DECANTR_CONTENT_ROOT || bundled;
|
|
36
887
|
}
|
|
37
888
|
function getResolver() {
|
|
@@ -46,7 +897,7 @@ async function cmdSearch(query, type) {
|
|
|
46
897
|
}
|
|
47
898
|
console.log(heading(`${results.length} result(s) for "${query}"`));
|
|
48
899
|
for (const r of results) {
|
|
49
|
-
console.log(` ${cyan(r.type.padEnd(12))} ${
|
|
900
|
+
console.log(` ${cyan(r.type.padEnd(12))} ${BOLD2}${r.id}${RESET2}`);
|
|
50
901
|
console.log(` ${dim(r.description || "")}`);
|
|
51
902
|
console.log("");
|
|
52
903
|
}
|
|
@@ -80,10 +931,10 @@ async function cmdGet(type, id) {
|
|
|
80
931
|
console.log(JSON.stringify(result.item, null, 2));
|
|
81
932
|
}
|
|
82
933
|
async function cmdValidate(path) {
|
|
83
|
-
const essencePath = path ||
|
|
934
|
+
const essencePath = path || join4(process.cwd(), "decantr.essence.json");
|
|
84
935
|
let raw;
|
|
85
936
|
try {
|
|
86
|
-
raw =
|
|
937
|
+
raw = readFileSync4(essencePath, "utf-8");
|
|
87
938
|
} catch {
|
|
88
939
|
console.error(error(`Could not read ${essencePath}`));
|
|
89
940
|
process.exitCode = 1;
|
|
@@ -103,7 +954,7 @@ async function cmdValidate(path) {
|
|
|
103
954
|
} else {
|
|
104
955
|
console.error(error("Validation failed:"));
|
|
105
956
|
for (const err of result.errors) {
|
|
106
|
-
console.error(` ${RED}${err}${
|
|
957
|
+
console.error(` ${RED}${err}${RESET2}`);
|
|
107
958
|
}
|
|
108
959
|
process.exitCode = 1;
|
|
109
960
|
}
|
|
@@ -113,7 +964,7 @@ async function cmdValidate(path) {
|
|
|
113
964
|
console.log(heading("Guard violations:"));
|
|
114
965
|
for (const v of violations) {
|
|
115
966
|
const vr = v;
|
|
116
|
-
console.log(` ${
|
|
967
|
+
console.log(` ${YELLOW2}[${vr.rule}]${RESET2} ${vr.message}`);
|
|
117
968
|
}
|
|
118
969
|
} else if (result.valid) {
|
|
119
970
|
console.log(success("No guard violations."));
|
|
@@ -128,16 +979,16 @@ async function cmdList(type) {
|
|
|
128
979
|
process.exitCode = 1;
|
|
129
980
|
return;
|
|
130
981
|
}
|
|
131
|
-
const { readdirSync } = await import("fs");
|
|
132
|
-
const dir =
|
|
982
|
+
const { readdirSync: readdirSync2 } = await import("fs");
|
|
983
|
+
const dir = join4(getContentRoot(), type);
|
|
133
984
|
let found = false;
|
|
134
985
|
try {
|
|
135
|
-
const files =
|
|
986
|
+
const files = readdirSync2(dir).filter((f) => f.endsWith(".json"));
|
|
136
987
|
if (files.length > 0) {
|
|
137
988
|
found = true;
|
|
138
989
|
console.log(heading(`${files.length} ${type}`));
|
|
139
990
|
for (const f of files) {
|
|
140
|
-
const data = JSON.parse(
|
|
991
|
+
const data = JSON.parse(readFileSync4(join4(dir, f), "utf-8"));
|
|
141
992
|
console.log(` ${cyan(data.id || f.replace(".json", ""))} ${dim(data.description || data.name || "")}`);
|
|
142
993
|
}
|
|
143
994
|
}
|
|
@@ -159,169 +1010,238 @@ async function cmdList(type) {
|
|
|
159
1010
|
console.log(dim(`No ${type} found.`));
|
|
160
1011
|
}
|
|
161
1012
|
}
|
|
162
|
-
function
|
|
163
|
-
const
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
});
|
|
171
|
-
}
|
|
172
|
-
async function select(question, options, defaultIdx = 0) {
|
|
173
|
-
console.log(`
|
|
174
|
-
${BOLD}${question}${RESET}`);
|
|
175
|
-
for (let i = 0; i < options.length; i++) {
|
|
176
|
-
const marker = i === defaultIdx ? `${GREEN}>${RESET}` : " ";
|
|
177
|
-
console.log(` ${marker} ${i + 1}. ${options[i]}`);
|
|
178
|
-
}
|
|
179
|
-
const answer = await ask(`Choose (1-${options.length})`, String(defaultIdx + 1));
|
|
180
|
-
const idx = parseInt(answer, 10) - 1;
|
|
181
|
-
return options[Math.max(0, Math.min(idx, options.length - 1))];
|
|
182
|
-
}
|
|
183
|
-
async function cmdInit() {
|
|
184
|
-
console.log(heading("Create a new Decantr project"));
|
|
185
|
-
const essencePath = join(process.cwd(), "decantr.essence.json");
|
|
186
|
-
if (existsSync(essencePath)) {
|
|
187
|
-
const overwrite = await ask("decantr.essence.json already exists. Overwrite?", "n");
|
|
188
|
-
if (overwrite.toLowerCase() !== "y") {
|
|
1013
|
+
async function cmdInit(args) {
|
|
1014
|
+
const projectRoot = process.cwd();
|
|
1015
|
+
console.log(heading("Decantr Project Setup"));
|
|
1016
|
+
const detected = detectProject(projectRoot);
|
|
1017
|
+
if (detected.existingEssence && !args.existing) {
|
|
1018
|
+
console.log(`${YELLOW2}Warning: decantr.essence.json already exists.${RESET2}`);
|
|
1019
|
+
const overwrite = await confirm("Overwrite existing configuration?", false);
|
|
1020
|
+
if (!overwrite) {
|
|
189
1021
|
console.log(dim("Cancelled."));
|
|
190
1022
|
return;
|
|
191
1023
|
}
|
|
192
1024
|
}
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
1025
|
+
const registryClient = new RegistryClient({
|
|
1026
|
+
cacheDir: join4(projectRoot, ".decantr", "cache"),
|
|
1027
|
+
apiUrl: args.registry,
|
|
1028
|
+
offline: args.offline
|
|
1029
|
+
});
|
|
1030
|
+
console.log(dim("Fetching registry content..."));
|
|
1031
|
+
const [archetypesResult, blueprintsResult, themesResult] = await Promise.all([
|
|
1032
|
+
registryClient.fetchArchetypes(),
|
|
1033
|
+
registryClient.fetchBlueprints(),
|
|
1034
|
+
registryClient.fetchThemes()
|
|
1035
|
+
]);
|
|
1036
|
+
const registrySource = archetypesResult.source.type;
|
|
1037
|
+
if (registrySource === "bundled") {
|
|
1038
|
+
console.log(dim("Using bundled content (API unavailable)"));
|
|
201
1039
|
}
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
}
|
|
212
|
-
const archetypeOptions = archetypes.map((a) => `${a.id} ${dim(`\u2014 ${a.description || ""}`)}`);
|
|
213
|
-
const selectedArchetype = await select("What are you building?", archetypeOptions);
|
|
214
|
-
const archetypeId = selectedArchetype.split(" ")[0];
|
|
215
|
-
let themes = [];
|
|
216
|
-
try {
|
|
217
|
-
const res = await fetch("https://decantr-registry.fly.dev/v1/themes");
|
|
218
|
-
if (res.ok) {
|
|
219
|
-
const data = await res.json();
|
|
220
|
-
themes = data.items;
|
|
221
|
-
}
|
|
222
|
-
} catch {
|
|
1040
|
+
const archetypes = archetypesResult.data.items;
|
|
1041
|
+
const blueprints = blueprintsResult.data.items;
|
|
1042
|
+
const themes = themesResult.data.items;
|
|
1043
|
+
let options;
|
|
1044
|
+
if (args.yes) {
|
|
1045
|
+
const flags = parseFlags(args, detected);
|
|
1046
|
+
options = mergeWithDefaults(flags, detected);
|
|
1047
|
+
} else {
|
|
1048
|
+
options = await runInteractivePrompts(detected, archetypes, blueprints, themes);
|
|
223
1049
|
}
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
];
|
|
230
|
-
}
|
|
231
|
-
const themeOptions = themes.map((t) => `${t.id} ${dim(`\u2014 ${t.description || ""}`)}`);
|
|
232
|
-
const selectedTheme = await select("Choose a theme", themeOptions);
|
|
233
|
-
const themeId = selectedTheme.split(" ")[0];
|
|
234
|
-
const mode = await select("Mode", ["dark", "light"], 0);
|
|
235
|
-
const shape = await select("Shape", ["pill", "rounded", "sharp"], 0);
|
|
236
|
-
const target = await select("Target framework", ["react", "vue", "svelte", "html"], 0);
|
|
237
|
-
let pages = [];
|
|
238
|
-
try {
|
|
239
|
-
const res = await fetch(`https://decantr-registry.fly.dev/v1/archetypes/${archetypeId}`);
|
|
240
|
-
if (res.ok) {
|
|
241
|
-
const data = await res.json();
|
|
242
|
-
if (data.pages) pages = data.pages;
|
|
1050
|
+
let blueprintData;
|
|
1051
|
+
if (options.blueprint) {
|
|
1052
|
+
const result2 = await registryClient.fetchBlueprint(options.blueprint);
|
|
1053
|
+
if (result2) {
|
|
1054
|
+
blueprintData = result2.data;
|
|
243
1055
|
}
|
|
244
|
-
} catch {
|
|
245
1056
|
}
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
}
|
|
1057
|
+
console.log(heading("Scaffolding project..."));
|
|
1058
|
+
const result = scaffoldProject(
|
|
1059
|
+
projectRoot,
|
|
1060
|
+
options,
|
|
1061
|
+
detected,
|
|
1062
|
+
blueprintData,
|
|
1063
|
+
registrySource
|
|
1064
|
+
);
|
|
1065
|
+
console.log(success("\nProject scaffolded successfully!"));
|
|
1066
|
+
console.log("");
|
|
1067
|
+
console.log(` ${cyan("decantr.essence.json")} Design specification`);
|
|
1068
|
+
console.log(` ${cyan("DECANTR.md")} LLM instructions`);
|
|
1069
|
+
console.log(` ${cyan(".decantr/project.json")} Project state`);
|
|
1070
|
+
console.log(` ${cyan(".decantr/context/")} Task-specific guides`);
|
|
1071
|
+
if (result.gitignoreUpdated) {
|
|
1072
|
+
console.log(` ${dim(".gitignore updated to exclude .decantr/cache/")}`);
|
|
259
1073
|
}
|
|
260
|
-
const
|
|
261
|
-
|
|
262
|
-
archetype: archetypeId,
|
|
263
|
-
theme: {
|
|
264
|
-
style: themeId,
|
|
265
|
-
mode,
|
|
266
|
-
recipe: themeId,
|
|
267
|
-
shape
|
|
268
|
-
},
|
|
269
|
-
personality: ["professional"],
|
|
270
|
-
platform: { type: "spa", routing: "hash" },
|
|
271
|
-
structure,
|
|
272
|
-
features,
|
|
273
|
-
guard: { enforce_style: true, enforce_recipe: true, mode: "strict" },
|
|
274
|
-
density: { level: "comfortable", content_gap: "_gap4" },
|
|
275
|
-
target
|
|
276
|
-
};
|
|
277
|
-
writeFileSync(essencePath, JSON.stringify(essence, null, 2) + "\n");
|
|
278
|
-
console.log(success(`
|
|
279
|
-
Created decantr.essence.json`));
|
|
280
|
-
console.log(dim(` Archetype: ${archetypeId}`));
|
|
281
|
-
console.log(dim(` Theme: ${themeId} (${mode})`));
|
|
282
|
-
console.log(dim(` Pages: ${structure.map((s) => s.id).join(", ")}`));
|
|
283
|
-
console.log(dim(` Target: ${target}`));
|
|
1074
|
+
const essenceContent = readFileSync4(result.essencePath, "utf-8");
|
|
1075
|
+
const essence = JSON.parse(essenceContent);
|
|
284
1076
|
const validation = validateEssence(essence);
|
|
285
1077
|
if (validation.valid) {
|
|
286
|
-
console.log(success("
|
|
1078
|
+
console.log(success("\nValidation passed."));
|
|
287
1079
|
} else {
|
|
288
|
-
console.log(error(`
|
|
1080
|
+
console.log(error(`
|
|
1081
|
+
Validation warnings: ${validation.errors.join(", ")}`));
|
|
289
1082
|
}
|
|
290
1083
|
console.log(heading("Next steps"));
|
|
291
|
-
console.log(
|
|
292
|
-
console.log(
|
|
293
|
-
console.log(
|
|
294
|
-
console.log(
|
|
295
|
-
|
|
1084
|
+
console.log("1. Review DECANTR.md to understand the methodology");
|
|
1085
|
+
console.log("2. Share DECANTR.md with your AI assistant");
|
|
1086
|
+
console.log("3. Start building! The AI will follow the essence spec.");
|
|
1087
|
+
console.log("");
|
|
1088
|
+
if (registrySource === "bundled") {
|
|
1089
|
+
console.log(dim('Run "decantr sync" when online to get the latest registry content.'));
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
async function cmdStatus() {
|
|
1093
|
+
const projectRoot = process.cwd();
|
|
1094
|
+
const essencePath = join4(projectRoot, "decantr.essence.json");
|
|
1095
|
+
const projectJsonPath = join4(projectRoot, ".decantr", "project.json");
|
|
1096
|
+
console.log(heading("Decantr Project Status"));
|
|
1097
|
+
if (!existsSync4(essencePath)) {
|
|
1098
|
+
console.log(`${RED}No decantr.essence.json found.${RESET2}`);
|
|
1099
|
+
console.log(dim('Run "decantr init" to create one.'));
|
|
1100
|
+
return;
|
|
1101
|
+
}
|
|
1102
|
+
try {
|
|
1103
|
+
const essence = JSON.parse(readFileSync4(essencePath, "utf-8"));
|
|
1104
|
+
const validation = validateEssence(essence);
|
|
1105
|
+
console.log(`${BOLD2}Essence:${RESET2}`);
|
|
1106
|
+
if (validation.valid) {
|
|
1107
|
+
console.log(` ${GREEN2}Valid${RESET2}`);
|
|
1108
|
+
} else {
|
|
1109
|
+
console.log(` ${RED}Invalid: ${validation.errors.join(", ")}${RESET2}`);
|
|
1110
|
+
}
|
|
1111
|
+
console.log(` Theme: ${essence.theme?.style || "unknown"} (${essence.theme?.mode || "unknown"})`);
|
|
1112
|
+
console.log(` Guard: ${essence.guard?.mode || "unknown"}`);
|
|
1113
|
+
console.log(` Pages: ${(essence.structure || []).length}`);
|
|
1114
|
+
} catch (e) {
|
|
1115
|
+
console.log(` ${RED}Error reading essence: ${e.message}${RESET2}`);
|
|
1116
|
+
}
|
|
1117
|
+
console.log("");
|
|
1118
|
+
console.log(`${BOLD2}Sync Status:${RESET2}`);
|
|
1119
|
+
if (existsSync4(projectJsonPath)) {
|
|
1120
|
+
try {
|
|
1121
|
+
const projectJson = JSON.parse(readFileSync4(projectJsonPath, "utf-8"));
|
|
1122
|
+
const syncStatus = projectJson.sync?.status || "unknown";
|
|
1123
|
+
const lastSync = projectJson.sync?.lastSync || "never";
|
|
1124
|
+
const source = projectJson.sync?.registrySource || "unknown";
|
|
1125
|
+
const statusColor = syncStatus === "synced" ? GREEN2 : YELLOW2;
|
|
1126
|
+
console.log(` Status: ${statusColor}${syncStatus}${RESET2}`);
|
|
1127
|
+
console.log(` Last sync: ${dim(lastSync)}`);
|
|
1128
|
+
console.log(` Source: ${dim(source)}`);
|
|
1129
|
+
} catch {
|
|
1130
|
+
console.log(` ${YELLOW2}Could not read project.json${RESET2}`);
|
|
1131
|
+
}
|
|
1132
|
+
} else {
|
|
1133
|
+
console.log(` ${YELLOW2}No .decantr/project.json found${RESET2}`);
|
|
1134
|
+
console.log(dim(' Run "decantr init" to create project files.'));
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
async function cmdSync() {
|
|
1138
|
+
const projectRoot = process.cwd();
|
|
1139
|
+
const cacheDir = join4(projectRoot, ".decantr", "cache");
|
|
1140
|
+
console.log(heading("Syncing registry content..."));
|
|
1141
|
+
const result = await syncRegistry(cacheDir);
|
|
1142
|
+
if (result.source === "api") {
|
|
1143
|
+
console.log(success("Sync completed successfully."));
|
|
1144
|
+
if (result.synced.length > 0) {
|
|
1145
|
+
console.log(` Synced: ${result.synced.join(", ")}`);
|
|
1146
|
+
}
|
|
1147
|
+
if (result.failed.length > 0) {
|
|
1148
|
+
console.log(` ${YELLOW2}Failed: ${result.failed.join(", ")}${RESET2}`);
|
|
1149
|
+
}
|
|
1150
|
+
} else {
|
|
1151
|
+
console.log(`${YELLOW2}Could not sync: API unavailable${RESET2}`);
|
|
1152
|
+
console.log(dim("Using bundled content."));
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
async function cmdAudit() {
|
|
1156
|
+
const projectRoot = process.cwd();
|
|
1157
|
+
const essencePath = join4(projectRoot, "decantr.essence.json");
|
|
1158
|
+
console.log(heading("Auditing project..."));
|
|
1159
|
+
if (!existsSync4(essencePath)) {
|
|
1160
|
+
console.log(`${RED}No decantr.essence.json found.${RESET2}`);
|
|
1161
|
+
process.exitCode = 1;
|
|
1162
|
+
return;
|
|
1163
|
+
}
|
|
1164
|
+
try {
|
|
1165
|
+
const essence = JSON.parse(readFileSync4(essencePath, "utf-8"));
|
|
1166
|
+
const validation = validateEssence(essence);
|
|
1167
|
+
if (!validation.valid) {
|
|
1168
|
+
console.log(`${RED}Essence validation failed:${RESET2}`);
|
|
1169
|
+
for (const err of validation.errors) {
|
|
1170
|
+
console.log(` ${RED}${err}${RESET2}`);
|
|
1171
|
+
}
|
|
1172
|
+
process.exitCode = 1;
|
|
1173
|
+
return;
|
|
1174
|
+
}
|
|
1175
|
+
console.log(success("Essence is valid."));
|
|
1176
|
+
const violations = evaluateGuard(essence, {});
|
|
1177
|
+
if (violations.length > 0) {
|
|
1178
|
+
console.log("");
|
|
1179
|
+
console.log(`${YELLOW2}Guard violations:${RESET2}`);
|
|
1180
|
+
for (const v of violations) {
|
|
1181
|
+
const vr = v;
|
|
1182
|
+
console.log(` ${YELLOW2}[${vr.rule}]${RESET2} ${vr.message}`);
|
|
1183
|
+
}
|
|
1184
|
+
} else {
|
|
1185
|
+
console.log(success("No guard violations."));
|
|
1186
|
+
}
|
|
1187
|
+
console.log("");
|
|
1188
|
+
console.log(`${BOLD2}Summary:${RESET2}`);
|
|
1189
|
+
console.log(` Pages defined: ${essence.structure.length}`);
|
|
1190
|
+
console.log(` Guard mode: ${essence.guard.mode}`);
|
|
1191
|
+
console.log(` Theme: ${essence.theme.style}`);
|
|
1192
|
+
} catch (e) {
|
|
1193
|
+
console.log(`${RED}Error: ${e.message}${RESET2}`);
|
|
1194
|
+
process.exitCode = 1;
|
|
1195
|
+
}
|
|
296
1196
|
}
|
|
297
1197
|
function cmdHelp() {
|
|
298
1198
|
console.log(`
|
|
299
|
-
${
|
|
1199
|
+
${BOLD2}decantr${RESET2} \u2014 Design intelligence for AI-generated UI
|
|
300
1200
|
|
|
301
|
-
${
|
|
302
|
-
decantr init
|
|
303
|
-
decantr
|
|
1201
|
+
${BOLD2}Usage:${RESET2}
|
|
1202
|
+
decantr init [options]
|
|
1203
|
+
decantr status
|
|
1204
|
+
decantr sync
|
|
1205
|
+
decantr audit
|
|
1206
|
+
decantr search <query> [--type <type>]
|
|
304
1207
|
decantr get <type> <id>
|
|
305
1208
|
decantr list <type>
|
|
306
1209
|
decantr validate [path]
|
|
307
1210
|
decantr help
|
|
308
1211
|
|
|
309
|
-
${
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
1212
|
+
${BOLD2}Init Options:${RESET2}
|
|
1213
|
+
--blueprint, -b Blueprint ID
|
|
1214
|
+
--theme Theme ID
|
|
1215
|
+
--mode Color mode: dark | light | auto
|
|
1216
|
+
--shape Border shape: pill | rounded | sharp
|
|
1217
|
+
--target Framework: react | vue | svelte | nextjs | html
|
|
1218
|
+
--guard Guard mode: creative | guided | strict
|
|
1219
|
+
--density Spacing: compact | comfortable | spacious
|
|
1220
|
+
--shell Default shell layout
|
|
1221
|
+
--existing Initialize in existing project
|
|
1222
|
+
--offline Force offline mode
|
|
1223
|
+
--yes, -y Accept defaults, skip confirmations
|
|
1224
|
+
--registry Custom registry URL
|
|
316
1225
|
|
|
317
|
-
${
|
|
1226
|
+
${BOLD2}Commands:${RESET2}
|
|
1227
|
+
${cyan("init")} Initialize a new Decantr project with full scaffolding
|
|
1228
|
+
${cyan("status")} Show project status and sync state
|
|
1229
|
+
${cyan("sync")} Sync registry content from API
|
|
1230
|
+
${cyan("audit")} Validate essence and check for drift
|
|
1231
|
+
${cyan("search")} Search the registry
|
|
1232
|
+
${cyan("get")} Get full details of a registry item
|
|
1233
|
+
${cyan("list")} List items by type
|
|
1234
|
+
${cyan("validate")} Validate essence file
|
|
1235
|
+
${cyan("help")} Show this help
|
|
1236
|
+
|
|
1237
|
+
${BOLD2}Examples:${RESET2}
|
|
318
1238
|
decantr init
|
|
1239
|
+
decantr init --blueprint=saas-dashboard --theme=luminarum --yes
|
|
1240
|
+
decantr status
|
|
1241
|
+
decantr sync
|
|
1242
|
+
decantr audit
|
|
319
1243
|
decantr search dashboard
|
|
320
|
-
decantr search kpi --type pattern
|
|
321
|
-
decantr get pattern kpi-grid
|
|
322
|
-
decantr get recipe luminarum
|
|
323
1244
|
decantr list patterns
|
|
324
|
-
decantr validate
|
|
325
1245
|
`);
|
|
326
1246
|
}
|
|
327
1247
|
async function main() {
|
|
@@ -333,7 +1253,41 @@ async function main() {
|
|
|
333
1253
|
}
|
|
334
1254
|
switch (command) {
|
|
335
1255
|
case "init": {
|
|
336
|
-
|
|
1256
|
+
const initArgs = {};
|
|
1257
|
+
for (let i = 1; i < args.length; i++) {
|
|
1258
|
+
const arg = args[i];
|
|
1259
|
+
if (arg === "--yes" || arg === "-y") {
|
|
1260
|
+
initArgs.yes = true;
|
|
1261
|
+
} else if (arg === "--offline") {
|
|
1262
|
+
initArgs.offline = true;
|
|
1263
|
+
} else if (arg === "--existing") {
|
|
1264
|
+
initArgs.existing = true;
|
|
1265
|
+
} else if (arg.startsWith("--")) {
|
|
1266
|
+
const [key, value] = arg.slice(2).split("=");
|
|
1267
|
+
if (value) {
|
|
1268
|
+
initArgs[key] = value;
|
|
1269
|
+
} else if (args[i + 1] && !args[i + 1].startsWith("-")) {
|
|
1270
|
+
initArgs[key] = args[++i];
|
|
1271
|
+
}
|
|
1272
|
+
} else if (arg.startsWith("-")) {
|
|
1273
|
+
const key = arg.slice(1);
|
|
1274
|
+
if (key === "b" && args[i + 1]) initArgs.blueprint = args[++i];
|
|
1275
|
+
if (key === "y") initArgs.yes = true;
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1278
|
+
await cmdInit(initArgs);
|
|
1279
|
+
break;
|
|
1280
|
+
}
|
|
1281
|
+
case "status": {
|
|
1282
|
+
await cmdStatus();
|
|
1283
|
+
break;
|
|
1284
|
+
}
|
|
1285
|
+
case "sync": {
|
|
1286
|
+
await cmdSync();
|
|
1287
|
+
break;
|
|
1288
|
+
}
|
|
1289
|
+
case "audit": {
|
|
1290
|
+
await cmdAudit();
|
|
337
1291
|
break;
|
|
338
1292
|
}
|
|
339
1293
|
case "search": {
|
package/package.json
CHANGED