@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.
Files changed (2) hide show
  1. package/dist/index.js +176 -16
  2. 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
- const result = await resolver.resolve(type, id);
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
- console.log(heading(`${files.length} ${type}`));
123
- for (const f of files) {
124
- const data = JSON.parse(readFileSync(join(dir, f), "utf-8"));
125
- console.log(` ${cyan(data.id || f.replace(".json", ""))} ${dim(data.description || data.name || "")}`);
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
- const coreDir = join(getContentRoot(), "core", type);
145
+ }
146
+ if (!found) {
129
147
  try {
130
- const files = readdirSync(coreDir).filter((f) => f.endsWith(".json"));
131
- console.log(heading(`${files.length} ${type} (core)`));
132
- for (const f of files) {
133
- const data = JSON.parse(readFileSync(join(coreDir, f), "utf-8"));
134
- console.log(` ${cyan(data.id || f.replace(".json", ""))} ${dim(data.description || data.name || "")}`);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@decantr/cli",
3
- "version": "1.0.0-beta.3",
3
+ "version": "1.0.0-beta.5",
4
4
  "description": "Decantr CLI — search the registry, validate essence files, and access design intelligence from the terminal",
5
5
  "license": "MIT",
6
6
  "repository": {