@decantr/cli 1.0.0-beta.3 → 1.0.0-beta.5
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 +176 -16
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
-
import { readFileSync } from "fs";
|
|
4
|
+
import { readFileSync, writeFileSync, existsSync } from "fs";
|
|
5
5
|
import { join } from "path";
|
|
6
|
+
import { createInterface } from "readline";
|
|
6
7
|
import { validateEssence, evaluateGuard } from "@decantr/essence-spec";
|
|
7
8
|
import { createResolver, createRegistryClient } from "@decantr/registry";
|
|
8
9
|
var BOLD = "\x1B[1m";
|
|
@@ -58,8 +59,20 @@ async function cmdGet(type, id) {
|
|
|
58
59
|
return;
|
|
59
60
|
}
|
|
60
61
|
const resolver = getResolver();
|
|
61
|
-
|
|
62
|
+
let result = await resolver.resolve(type, id);
|
|
62
63
|
if (!result) {
|
|
64
|
+
const apiType = type === "blueprint" ? "blueprints" : `${type}s`;
|
|
65
|
+
try {
|
|
66
|
+
const res = await fetch(`https://decantr-registry.fly.dev/v1/${apiType}/${id}`);
|
|
67
|
+
if (res.ok) {
|
|
68
|
+
const item = await res.json();
|
|
69
|
+
if (!item.error) {
|
|
70
|
+
console.log(JSON.stringify(item, null, 2));
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
} catch {
|
|
75
|
+
}
|
|
63
76
|
console.error(error(`${type} "${id}" not found.`));
|
|
64
77
|
process.exitCode = 1;
|
|
65
78
|
return;
|
|
@@ -117,32 +130,176 @@ async function cmdList(type) {
|
|
|
117
130
|
}
|
|
118
131
|
const { readdirSync } = await import("fs");
|
|
119
132
|
const dir = join(getContentRoot(), type);
|
|
133
|
+
let found = false;
|
|
120
134
|
try {
|
|
121
135
|
const files = readdirSync(dir).filter((f) => f.endsWith(".json"));
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
136
|
+
if (files.length > 0) {
|
|
137
|
+
found = true;
|
|
138
|
+
console.log(heading(`${files.length} ${type}`));
|
|
139
|
+
for (const f of files) {
|
|
140
|
+
const data = JSON.parse(readFileSync(join(dir, f), "utf-8"));
|
|
141
|
+
console.log(` ${cyan(data.id || f.replace(".json", ""))} ${dim(data.description || data.name || "")}`);
|
|
142
|
+
}
|
|
126
143
|
}
|
|
127
144
|
} catch {
|
|
128
|
-
|
|
145
|
+
}
|
|
146
|
+
if (!found) {
|
|
129
147
|
try {
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
148
|
+
const res = await fetch(`https://decantr-registry.fly.dev/v1/${type}`);
|
|
149
|
+
if (res.ok) {
|
|
150
|
+
const data = await res.json();
|
|
151
|
+
console.log(heading(`${data.total} ${type}`));
|
|
152
|
+
for (const item of data.items) {
|
|
153
|
+
console.log(` ${cyan(item.id)} ${dim(item.description || item.name || "")}`);
|
|
154
|
+
}
|
|
155
|
+
return;
|
|
135
156
|
}
|
|
136
157
|
} catch {
|
|
137
|
-
console.log(dim(`No ${type} found.`));
|
|
138
158
|
}
|
|
159
|
+
console.log(dim(`No ${type} found.`));
|
|
139
160
|
}
|
|
140
161
|
}
|
|
162
|
+
function ask(question, defaultValue) {
|
|
163
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
164
|
+
const prompt = defaultValue ? `${question} ${dim(`(${defaultValue})`)}: ` : `${question}: `;
|
|
165
|
+
return new Promise((resolve) => {
|
|
166
|
+
rl.question(prompt, (answer) => {
|
|
167
|
+
rl.close();
|
|
168
|
+
resolve(answer.trim() || defaultValue || "");
|
|
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") {
|
|
189
|
+
console.log(dim("Cancelled."));
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
let archetypes = [];
|
|
194
|
+
try {
|
|
195
|
+
const res = await fetch("https://decantr-registry.fly.dev/v1/archetypes");
|
|
196
|
+
if (res.ok) {
|
|
197
|
+
const data = await res.json();
|
|
198
|
+
archetypes = data.items;
|
|
199
|
+
}
|
|
200
|
+
} catch {
|
|
201
|
+
}
|
|
202
|
+
if (archetypes.length === 0) {
|
|
203
|
+
archetypes = [
|
|
204
|
+
{ id: "saas-dashboard", description: "Analytics dashboard with KPIs and data tables" },
|
|
205
|
+
{ id: "ecommerce", description: "Online store with product catalog" },
|
|
206
|
+
{ id: "portfolio", description: "Personal or agency portfolio site" },
|
|
207
|
+
{ id: "marketing-landing", description: "Product marketing landing page" },
|
|
208
|
+
{ id: "gaming-platform", description: "Gaming community hub" },
|
|
209
|
+
{ id: "content-site", description: "Blog or content site" }
|
|
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 {
|
|
223
|
+
}
|
|
224
|
+
if (themes.length === 0) {
|
|
225
|
+
themes = [
|
|
226
|
+
{ id: "luminarum", description: "Dark geometric canvas with vibrant accents" },
|
|
227
|
+
{ id: "clean", description: "Professional, minimal, universal" },
|
|
228
|
+
{ id: "glassmorphism", description: "Frosted glass aesthetic" }
|
|
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;
|
|
243
|
+
}
|
|
244
|
+
} catch {
|
|
245
|
+
}
|
|
246
|
+
const structure = pages.length > 0 ? pages.map((p) => ({
|
|
247
|
+
id: p.id,
|
|
248
|
+
shell: p.shell || "sidebar-main",
|
|
249
|
+
layout: p.default_layout || []
|
|
250
|
+
})) : [{ id: "home", shell: "full-bleed", layout: ["hero-split"] }];
|
|
251
|
+
let features = [];
|
|
252
|
+
try {
|
|
253
|
+
const res = await fetch(`https://decantr-registry.fly.dev/v1/archetypes/${archetypeId}`);
|
|
254
|
+
if (res.ok) {
|
|
255
|
+
const data = await res.json();
|
|
256
|
+
if (data.features) features = data.features;
|
|
257
|
+
}
|
|
258
|
+
} catch {
|
|
259
|
+
}
|
|
260
|
+
const essence = {
|
|
261
|
+
version: "2.0.0",
|
|
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}`));
|
|
284
|
+
const validation = validateEssence(essence);
|
|
285
|
+
if (validation.valid) {
|
|
286
|
+
console.log(success(" Validation: passed"));
|
|
287
|
+
} else {
|
|
288
|
+
console.log(error(` Validation: ${validation.errors.join(", ")}`));
|
|
289
|
+
}
|
|
290
|
+
console.log(heading("Next steps"));
|
|
291
|
+
console.log(` 1. Open your AI assistant (Claude, Cursor, etc.)`);
|
|
292
|
+
console.log(` 2. Tell it to read ${cyan("decantr.essence.json")} before generating code`);
|
|
293
|
+
console.log(` 3. The essence file defines your theme, pages, and patterns`);
|
|
294
|
+
console.log(` 4. Run ${cyan("decantr validate")} after changes to check for drift
|
|
295
|
+
`);
|
|
296
|
+
}
|
|
141
297
|
function cmdHelp() {
|
|
142
298
|
console.log(`
|
|
143
299
|
${BOLD}decantr${RESET} \u2014 Design intelligence for AI-generated UI
|
|
144
300
|
|
|
145
301
|
${BOLD}Usage:${RESET}
|
|
302
|
+
decantr init
|
|
146
303
|
decantr search <query> [--type pattern|archetype|recipe|theme]
|
|
147
304
|
decantr get <type> <id>
|
|
148
305
|
decantr list <type>
|
|
@@ -150,6 +307,7 @@ ${BOLD}Usage:${RESET}
|
|
|
150
307
|
decantr help
|
|
151
308
|
|
|
152
309
|
${BOLD}Commands:${RESET}
|
|
310
|
+
${cyan("init")} Create a new decantr.essence.json \u2014 pick an archetype, theme, and target
|
|
153
311
|
${cyan("search")} Search the registry for patterns, archetypes, recipes, themes
|
|
154
312
|
${cyan("get")} Get full details of a registry item as JSON
|
|
155
313
|
${cyan("list")} List all items of a type (patterns, archetypes, recipes, themes, blueprints)
|
|
@@ -157,15 +315,13 @@ ${BOLD}Commands:${RESET}
|
|
|
157
315
|
${cyan("help")} Show this help message
|
|
158
316
|
|
|
159
317
|
${BOLD}Examples:${RESET}
|
|
318
|
+
decantr init
|
|
160
319
|
decantr search dashboard
|
|
161
320
|
decantr search kpi --type pattern
|
|
162
321
|
decantr get pattern kpi-grid
|
|
163
322
|
decantr get recipe luminarum
|
|
164
|
-
decantr get theme luminarum
|
|
165
323
|
decantr list patterns
|
|
166
|
-
decantr list themes
|
|
167
324
|
decantr validate
|
|
168
|
-
decantr validate ./my-project/decantr.essence.json
|
|
169
325
|
`);
|
|
170
326
|
}
|
|
171
327
|
async function main() {
|
|
@@ -176,6 +332,10 @@ async function main() {
|
|
|
176
332
|
return;
|
|
177
333
|
}
|
|
178
334
|
switch (command) {
|
|
335
|
+
case "init": {
|
|
336
|
+
await cmdInit();
|
|
337
|
+
break;
|
|
338
|
+
}
|
|
179
339
|
case "search": {
|
|
180
340
|
const query = args[1];
|
|
181
341
|
if (!query) {
|
package/package.json
CHANGED