@decantr/cli 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -2,16 +2,17 @@
2
2
  import {
3
3
  RegistryClient,
4
4
  syncRegistry
5
- } from "./chunk-PWTUBGGJ.js";
5
+ } from "./chunk-K6MIDPQH.js";
6
6
  import {
7
7
  __require
8
8
  } from "./chunk-PDX44BCA.js";
9
9
 
10
10
  // src/index.ts
11
- import { readFileSync as readFileSync4, existsSync as existsSync4 } from "fs";
12
- import { join as join4 } from "path";
11
+ import { readFileSync as readFileSync6, existsSync as existsSync7 } from "fs";
12
+ import { join as join7, dirname as dirname2 } from "path";
13
+ import { fileURLToPath as fileURLToPath2 } from "url";
13
14
  import { validateEssence, evaluateGuard } from "@decantr/essence-spec";
14
- import { createResolver, createRegistryClient } from "@decantr/registry";
15
+ import { RegistryAPIClient as RegistryAPIClient2 } from "@decantr/registry";
15
16
 
16
17
  // src/detect.ts
17
18
  import { existsSync, readFileSync } from "fs";
@@ -370,6 +371,148 @@ import { join as join2, dirname } from "path";
370
371
  import { fileURLToPath } from "url";
371
372
  var __dirname = dirname(fileURLToPath(import.meta.url));
372
373
  var CLI_VERSION = "1.0.0";
374
+ function generateTokensCSS(themeData, mode) {
375
+ if (!themeData) {
376
+ return `/* No theme data available */
377
+ :root {
378
+ --d-primary: #6366f1;
379
+ --d-secondary: #a1a1aa;
380
+ --d-accent: #f59e0b;
381
+ --d-bg: #18181b;
382
+ --d-surface: #1f1f23;
383
+ --d-surface-raised: #27272a;
384
+ --d-border: #3f3f46;
385
+ --d-text: #fafafa;
386
+ --d-text-muted: #a1a1aa;
387
+ }
388
+ `;
389
+ }
390
+ const seed = themeData.seed || {};
391
+ const palette = themeData.palette || {};
392
+ const tokens = {
393
+ // Seed colors
394
+ "--d-primary": seed.primary || "#6366f1",
395
+ "--d-secondary": seed.secondary || "#a1a1aa",
396
+ "--d-accent": seed.accent || "#f59e0b",
397
+ // Palette colors (mode-aware)
398
+ "--d-bg": palette.background?.[mode] || "#18181b",
399
+ "--d-surface": palette.surface?.[mode] || "#1f1f23",
400
+ "--d-surface-raised": palette["surface-raised"]?.[mode] || "#27272a",
401
+ "--d-border": palette.border?.[mode] || "#3f3f46",
402
+ "--d-text": palette.text?.[mode] || "#fafafa",
403
+ "--d-text-muted": palette["text-muted"]?.[mode] || "#a1a1aa",
404
+ "--d-primary-hover": palette["primary-hover"]?.[mode] || seed.primary || "#6366f1",
405
+ // Spacing scale
406
+ "--d-gap-1": "0.25rem",
407
+ "--d-gap-2": "0.5rem",
408
+ "--d-gap-3": "0.75rem",
409
+ "--d-gap-4": "1rem",
410
+ "--d-gap-6": "1.5rem",
411
+ "--d-gap-8": "2rem",
412
+ "--d-gap-12": "3rem",
413
+ // Radii
414
+ "--d-radius": "0.5rem",
415
+ "--d-radius-sm": "0.25rem",
416
+ "--d-radius-lg": "0.75rem",
417
+ "--d-radius-xl": "1rem",
418
+ "--d-radius-full": "9999px",
419
+ // Shadows
420
+ "--d-shadow-sm": "0 1px 2px rgba(0,0,0,0.05)",
421
+ "--d-shadow": "0 1px 3px rgba(0,0,0,0.1)",
422
+ "--d-shadow-md": "0 4px 6px rgba(0,0,0,0.1)",
423
+ "--d-shadow-lg": "0 10px 15px rgba(0,0,0,0.1)",
424
+ // Status colors
425
+ "--d-success": themeData.tokens?.base?.success || "#22c55e",
426
+ "--d-error": themeData.tokens?.base?.danger || "#ef4444",
427
+ "--d-warning": themeData.tokens?.base?.warning || "#f59e0b",
428
+ "--d-info": "#3b82f6"
429
+ };
430
+ const lines = Object.entries(tokens).map(([key, value]) => ` ${key}: ${value};`).join("\n");
431
+ return `/* Generated by @decantr/cli */
432
+ :root {
433
+ ${lines}
434
+ }
435
+ `;
436
+ }
437
+ function generateDecoratorsCSS(recipeData, themeName) {
438
+ if (!recipeData?.decorators) {
439
+ return `/* No recipe decorators available */`;
440
+ }
441
+ const decorators = recipeData.decorators;
442
+ const css = [
443
+ `/* Generated by @decantr/cli from recipe: ${themeName} */`,
444
+ ""
445
+ ];
446
+ for (const [name, description] of Object.entries(decorators)) {
447
+ css.push(generateDecoratorRule(name, description));
448
+ css.push("");
449
+ }
450
+ css.push(`/* Animation keyframes */
451
+ @keyframes decantr-fade-in {
452
+ from { opacity: 0; transform: translateY(8px); }
453
+ to { opacity: 1; transform: translateY(0); }
454
+ }
455
+
456
+ @keyframes decantr-pulse {
457
+ 0%, 100% { opacity: 1; }
458
+ 50% { opacity: 0.5; }
459
+ }
460
+ `);
461
+ return css.join("\n");
462
+ }
463
+ function generateDecoratorRule(name, description) {
464
+ const rules = [];
465
+ const descLower = description.toLowerCase();
466
+ if (descLower.includes("surface background") || descLower.includes("surface elevation")) {
467
+ rules.push("background: var(--d-surface)");
468
+ } else if (descLower.includes("background") && descLower.includes("theme")) {
469
+ rules.push("background: var(--d-bg)");
470
+ } else if (descLower.includes("primary-tinted") || descLower.includes("primary background")) {
471
+ rules.push("background: color-mix(in srgb, var(--d-primary) 15%, var(--d-surface))");
472
+ }
473
+ if (descLower.includes("1px border") || descLower.includes("subtle border")) {
474
+ rules.push("border: 1px solid var(--d-border)");
475
+ } else if (descLower.includes("border") && !descLower.includes("radius")) {
476
+ rules.push("border: 1px solid var(--d-border)");
477
+ }
478
+ const radiusMatch = descLower.match(/(\d+)px radius/);
479
+ if (radiusMatch) {
480
+ rules.push(`border-radius: ${radiusMatch[1]}px`);
481
+ } else if (descLower.includes("radius") || descLower.includes("rounded")) {
482
+ rules.push("border-radius: var(--d-radius)");
483
+ }
484
+ if (descLower.includes("hover shadow") || descLower.includes("shadow transition")) {
485
+ rules.push("transition: box-shadow 0.15s ease");
486
+ }
487
+ if (descLower.includes("elevation") || descLower.includes("shadow")) {
488
+ rules.push("box-shadow: var(--d-shadow)");
489
+ }
490
+ if (descLower.includes("entrance animation") || descLower.includes("fade")) {
491
+ rules.push("animation: decantr-fade-in 0.2s ease-out");
492
+ }
493
+ if (descLower.includes("pulse animation") || descLower.includes("skeleton")) {
494
+ rules.push("animation: decantr-pulse 1.5s ease-in-out infinite");
495
+ }
496
+ if (descLower.includes("blur") || descLower.includes("glass")) {
497
+ rules.push("backdrop-filter: blur(8px)");
498
+ }
499
+ if (descLower.includes("right-aligned")) {
500
+ rules.push("margin-left: auto");
501
+ } else if (descLower.includes("left-aligned")) {
502
+ rules.push("margin-right: auto");
503
+ }
504
+ if (descLower.includes("message bubble") || descLower.includes("bubble")) {
505
+ rules.push("padding: var(--d-gap-3) var(--d-gap-4)");
506
+ rules.push("border-radius: var(--d-radius-lg)");
507
+ rules.push("max-width: 80%");
508
+ }
509
+ if (rules.length === 0) {
510
+ return `/* .${name}: ${description} */`;
511
+ }
512
+ return `.${name} {
513
+ ${rules.join(";\n ")};
514
+ }`;
515
+ }
373
516
  function serializeLayoutItem(item) {
374
517
  if (typeof item === "string") {
375
518
  return item;
@@ -771,7 +914,7 @@ ${cacheEntry}
771
914
  return true;
772
915
  }
773
916
  }
774
- function scaffoldProject(projectRoot, options, detected, archetypeData, registrySource = "bundled", themeData, recipeData) {
917
+ function scaffoldProject(projectRoot, options, detected, archetypeData, registrySource = "cache", themeData, recipeData) {
775
918
  const essence = buildEssence(options, archetypeData);
776
919
  const decantrDir = join2(projectRoot, ".decantr");
777
920
  const contextDir = join2(decantrDir, "context");
@@ -797,12 +940,145 @@ function scaffoldProject(projectRoot, options, detected, archetypeData, registry
797
940
  const summaryPath = join2(contextDir, "essence-summary.md");
798
941
  writeFileSync(summaryPath, generateEssenceSummary(essence));
799
942
  contextFiles.push(summaryPath);
943
+ const stylesDir = join2(projectRoot, "src", "styles");
944
+ mkdirSync(stylesDir, { recursive: true });
945
+ const tokensPath = join2(stylesDir, "tokens.css");
946
+ writeFileSync(tokensPath, generateTokensCSS(themeData, essence.theme.mode));
947
+ const decoratorsPath = join2(stylesDir, "decorators.css");
948
+ writeFileSync(decoratorsPath, generateDecoratorsCSS(recipeData, essence.theme.style));
800
949
  const gitignoreUpdated = updateGitignore(projectRoot);
801
950
  return {
802
951
  essencePath,
803
952
  decantrMdPath,
804
953
  projectJsonPath,
805
954
  contextFiles,
955
+ cssFiles: [tokensPath, decoratorsPath],
956
+ gitignoreUpdated
957
+ };
958
+ }
959
+ function scaffoldMinimal(projectRoot) {
960
+ const decantrDir = join2(projectRoot, ".decantr");
961
+ const customDir = join2(decantrDir, "custom");
962
+ const contentTypes = ["patterns", "recipes", "themes", "blueprints", "archetypes", "shells"];
963
+ for (const type of contentTypes) {
964
+ mkdirSync(join2(customDir, type), { recursive: true });
965
+ }
966
+ const essence = {
967
+ version: "2.0.0",
968
+ archetype: "custom",
969
+ theme: {
970
+ style: "default",
971
+ mode: "dark",
972
+ recipe: "default",
973
+ shape: "rounded"
974
+ },
975
+ personality: ["clean", "modern"],
976
+ platform: {
977
+ type: "spa",
978
+ routing: "hash"
979
+ },
980
+ structure: [
981
+ { id: "home", shell: "sidebar-main", layout: ["hero"] }
982
+ ],
983
+ features: [],
984
+ guard: {
985
+ enforce_style: true,
986
+ enforce_recipe: true,
987
+ mode: "guided"
988
+ },
989
+ density: {
990
+ level: "comfortable",
991
+ content_gap: "_gap4"
992
+ },
993
+ target: "react"
994
+ };
995
+ const essencePath = join2(projectRoot, "decantr.essence.json");
996
+ writeFileSync(essencePath, JSON.stringify(essence, null, 2) + "\n");
997
+ const now = (/* @__PURE__ */ new Date()).toISOString();
998
+ const projectJson = {
999
+ detected: {
1000
+ framework: "unknown",
1001
+ version: null,
1002
+ packageManager: "npm",
1003
+ hasTypeScript: false,
1004
+ hasTailwind: false,
1005
+ existingRuleFiles: []
1006
+ },
1007
+ overrides: {
1008
+ framework: null
1009
+ },
1010
+ sync: {
1011
+ status: "needs-sync",
1012
+ lastSync: now,
1013
+ registrySource: "cache",
1014
+ cachedContent: {
1015
+ archetypes: [],
1016
+ patterns: [],
1017
+ themes: [],
1018
+ recipes: []
1019
+ }
1020
+ },
1021
+ initialized: {
1022
+ at: now,
1023
+ via: "cli",
1024
+ version: CLI_VERSION,
1025
+ flags: "--offline --minimal"
1026
+ }
1027
+ };
1028
+ const projectJsonPath = join2(decantrDir, "project.json");
1029
+ writeFileSync(projectJsonPath, JSON.stringify(projectJson, null, 2));
1030
+ const decantrMdPath = join2(projectRoot, "DECANTR.md");
1031
+ const decantrMdContent = `# DECANTR.md
1032
+
1033
+ > This file was generated by \`decantr init\` in offline/minimal mode.
1034
+ > Run \`decantr upgrade\` when online to pull full registry content.
1035
+
1036
+ ## Guard Mode: guided
1037
+
1038
+ ## Project Summary
1039
+
1040
+ **Archetype:** custom
1041
+ **Target:** react
1042
+ **Theme:** default (dark mode)
1043
+ **Guard Mode:** guided
1044
+ **Pages:** home
1045
+
1046
+ ## Pages
1047
+
1048
+ | Page | Shell | Layout |
1049
+ |------|-------|--------|
1050
+ | home | sidebar-main | hero |
1051
+
1052
+ ## Quick Start
1053
+
1054
+ 1. Edit \`decantr.essence.json\` to define your project structure.
1055
+ 2. Run \`decantr sync\` when online to fetch registry content.
1056
+ 3. Use \`decantr create <type> <name>\` to create custom content.
1057
+ 4. Use \`decantr search <query>\` to find patterns and themes.
1058
+ 5. Use \`decantr validate\` to check your essence file.
1059
+
1060
+ ## Commands
1061
+
1062
+ - \`decantr status\` \u2014 Project health
1063
+ - \`decantr search\` \u2014 Search the registry
1064
+ - \`decantr get <type> <id>\` \u2014 Fetch content details
1065
+ - \`decantr validate\` \u2014 Validate essence file
1066
+ - \`decantr sync\` \u2014 Sync registry content
1067
+ - \`decantr create <type> <name>\` \u2014 Create custom content
1068
+ - \`decantr publish <type> <name>\` \u2014 Publish to community registry
1069
+
1070
+ ---
1071
+
1072
+ *Generated by @decantr/cli v${CLI_VERSION}*
1073
+ `;
1074
+ writeFileSync(decantrMdPath, decantrMdContent);
1075
+ const gitignoreUpdated = updateGitignore(projectRoot);
1076
+ return {
1077
+ essencePath,
1078
+ decantrMdPath,
1079
+ projectJsonPath,
1080
+ contextFiles: [],
1081
+ cssFiles: [],
806
1082
  gitignoreUpdated
807
1083
  };
808
1084
  }
@@ -1029,6 +1305,155 @@ function importTheme(projectRoot, sourcePath) {
1029
1305
  };
1030
1306
  }
1031
1307
 
1308
+ // src/auth.ts
1309
+ import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync4, writeFileSync as writeFileSync3, rmSync as rmSync2 } from "fs";
1310
+ import { join as join4 } from "path";
1311
+ import { homedir } from "os";
1312
+ var CONFIG_DIR = join4(homedir(), ".config", "decantr");
1313
+ var AUTH_FILE = join4(CONFIG_DIR, "auth.json");
1314
+ function getCredentials() {
1315
+ if (!existsSync4(AUTH_FILE)) return null;
1316
+ try {
1317
+ return JSON.parse(readFileSync4(AUTH_FILE, "utf-8"));
1318
+ } catch {
1319
+ return null;
1320
+ }
1321
+ }
1322
+ function saveCredentials(creds) {
1323
+ mkdirSync3(CONFIG_DIR, { recursive: true });
1324
+ writeFileSync3(AUTH_FILE, JSON.stringify(creds, null, 2));
1325
+ }
1326
+ function clearCredentials() {
1327
+ if (existsSync4(AUTH_FILE)) {
1328
+ rmSync2(AUTH_FILE);
1329
+ }
1330
+ }
1331
+ function getApiKeyOrToken() {
1332
+ const creds = getCredentials();
1333
+ if (!creds) return null;
1334
+ return creds.api_key || creds.access_token || null;
1335
+ }
1336
+
1337
+ // src/commands/publish.ts
1338
+ import { existsSync as existsSync5, readFileSync as readFileSync5 } from "fs";
1339
+ import { join as join5 } from "path";
1340
+ import { RegistryAPIClient } from "@decantr/registry";
1341
+ var PLURAL_TO_SINGULAR = {
1342
+ patterns: "pattern",
1343
+ recipes: "recipe",
1344
+ themes: "theme",
1345
+ blueprints: "blueprint",
1346
+ archetypes: "archetype",
1347
+ shells: "shell"
1348
+ };
1349
+ var SINGULAR_TO_PLURAL = {
1350
+ pattern: "patterns",
1351
+ recipe: "recipes",
1352
+ theme: "themes",
1353
+ blueprint: "blueprints",
1354
+ archetype: "archetypes",
1355
+ shell: "shells"
1356
+ };
1357
+ async function cmdPublish(type, name, projectRoot = process.cwd()) {
1358
+ const token = getApiKeyOrToken();
1359
+ if (!token) {
1360
+ console.error("Not authenticated. Run `decantr login` first.");
1361
+ process.exitCode = 1;
1362
+ return;
1363
+ }
1364
+ const singularType = PLURAL_TO_SINGULAR[type] || type;
1365
+ const pluralType = SINGULAR_TO_PLURAL[type] || SINGULAR_TO_PLURAL[singularType] || `${type}s`;
1366
+ const customPath = join5(projectRoot, ".decantr", "custom", pluralType, `${name}.json`);
1367
+ if (!existsSync5(customPath)) {
1368
+ console.error(`Custom ${singularType} "${name}" not found at ${customPath}`);
1369
+ console.error(`Create one first: decantr create ${singularType} ${name}`);
1370
+ process.exitCode = 1;
1371
+ return;
1372
+ }
1373
+ let data;
1374
+ try {
1375
+ data = JSON.parse(readFileSync5(customPath, "utf-8"));
1376
+ } catch {
1377
+ console.error(`Failed to parse ${customPath}`);
1378
+ process.exitCode = 1;
1379
+ return;
1380
+ }
1381
+ const client = new RegistryAPIClient({
1382
+ apiKey: token
1383
+ });
1384
+ try {
1385
+ const result = await client.publishContent({
1386
+ type: pluralType,
1387
+ slug: name,
1388
+ version: data.version || "1.0.0",
1389
+ data,
1390
+ namespace: "@community",
1391
+ visibility: "public"
1392
+ });
1393
+ console.log(`Published ${singularType}/${name} to @community`);
1394
+ console.log(`Status: ${result.status}`);
1395
+ } catch (err) {
1396
+ console.error(`Failed to publish: ${err.message}`);
1397
+ process.exitCode = 1;
1398
+ }
1399
+ }
1400
+
1401
+ // src/commands/create.ts
1402
+ import { existsSync as existsSync6, mkdirSync as mkdirSync4, writeFileSync as writeFileSync4 } from "fs";
1403
+ import { join as join6 } from "path";
1404
+ var CONTENT_TYPES = ["pattern", "recipe", "theme", "blueprint", "archetype", "shell"];
1405
+ var PLURAL = {
1406
+ pattern: "patterns",
1407
+ recipe: "recipes",
1408
+ theme: "themes",
1409
+ blueprint: "blueprints",
1410
+ archetype: "archetypes",
1411
+ shell: "shells"
1412
+ };
1413
+ function getSkeleton(type, id, name) {
1414
+ const base = {
1415
+ id,
1416
+ name,
1417
+ description: "",
1418
+ version: "1.0.0",
1419
+ source: "custom"
1420
+ };
1421
+ switch (type) {
1422
+ case "pattern":
1423
+ return { ...base, components: [], presets: {}, layout: {} };
1424
+ case "recipe":
1425
+ return { ...base, shell: {}, spatial: {}, effects: {} };
1426
+ case "theme":
1427
+ return { ...base, seed: { primary: "#6500C6", secondary: "#0AF3EB", accent: "#F58882", background: "#0D0D1A" }, modes: ["dark"], shapes: ["rounded"] };
1428
+ case "blueprint":
1429
+ return { ...base, compose: [], theme: {}, personality: [] };
1430
+ case "archetype":
1431
+ return { ...base, pages: [], features: [], suggested_theme: "" };
1432
+ case "shell":
1433
+ return { ...base, regions: [], layout: "sidebar-main" };
1434
+ }
1435
+ }
1436
+ function cmdCreate(type, name, projectRoot = process.cwd()) {
1437
+ if (!CONTENT_TYPES.includes(type)) {
1438
+ console.error(`Invalid type "${type}". Must be one of: ${CONTENT_TYPES.join(", ")}`);
1439
+ process.exitCode = 1;
1440
+ return;
1441
+ }
1442
+ const plural = PLURAL[type];
1443
+ const customDir = join6(projectRoot, ".decantr", "custom", plural);
1444
+ const filePath = join6(customDir, `${name}.json`);
1445
+ if (existsSync6(filePath)) {
1446
+ console.error(`${type} "${name}" already exists at ${filePath}`);
1447
+ process.exitCode = 1;
1448
+ return;
1449
+ }
1450
+ mkdirSync4(customDir, { recursive: true });
1451
+ const skeleton = getSkeleton(type, name, name.replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase()));
1452
+ writeFileSync4(filePath, JSON.stringify(skeleton, null, 2));
1453
+ console.log(`Created ${type} "${name}" at ${filePath}`);
1454
+ console.log(`Edit it, then publish with: decantr publish ${type} ${name}`);
1455
+ }
1456
+
1032
1457
  // src/index.ts
1033
1458
  var BOLD2 = "\x1B[1m";
1034
1459
  var DIM2 = "\x1B[2m";
@@ -1109,58 +1534,67 @@ ${sep}
1109
1534
  ${body}
1110
1535
  ${bottom}`;
1111
1536
  }
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() });
1537
+ function getAPIClient() {
1538
+ return new RegistryAPIClient2({
1539
+ baseUrl: process.env.DECANTR_API_URL || void 0,
1540
+ apiKey: process.env.DECANTR_API_KEY || void 0
1541
+ });
1118
1542
  }
1119
1543
  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("");
1544
+ const apiClient = getAPIClient();
1545
+ try {
1546
+ const response = await apiClient.search({ q: query, type });
1547
+ const results = response.results;
1548
+ if (results.length === 0) {
1549
+ console.log(dim(`No results for "${query}"`));
1550
+ return;
1551
+ }
1552
+ console.log(heading(`${results.length} result(s) for "${query}"`));
1553
+ for (const r of results) {
1554
+ console.log(` ${cyan(r.type.padEnd(12))} ${BOLD2}${r.slug}${RESET2}`);
1555
+ console.log(` ${dim(r.description || "")}`);
1556
+ console.log("");
1557
+ }
1558
+ } catch {
1559
+ console.log(dim(`Search failed. API may be unavailable.`));
1131
1560
  }
1132
1561
  }
1133
1562
  async function cmdSuggest(query, type) {
1134
- const client = createRegistryClient();
1563
+ const apiClient = getAPIClient();
1135
1564
  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 || ""}`);
1565
+ try {
1566
+ const response = await apiClient.search({ q: query, type: searchType });
1567
+ const results = response.results;
1568
+ if (results.length === 0) {
1569
+ console.log(dim(`No suggestions for "${query}"`));
1570
+ console.log("");
1571
+ console.log("Try:");
1572
+ console.log(` ${cyan("decantr list patterns")} - see all patterns`);
1573
+ console.log(` ${cyan("decantr search <broader-term>")} - broaden your search`);
1574
+ return;
1153
1575
  }
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 || ""}`);
1576
+ console.log(heading(`Suggestions for "${query}"`));
1577
+ const queryLower = query.toLowerCase();
1578
+ const exact = results.filter((r) => r.slug.toLowerCase().includes(queryLower));
1579
+ const related = results.filter((r) => !r.slug.toLowerCase().includes(queryLower));
1580
+ if (exact.length > 0) {
1581
+ console.log(`${BOLD2}Direct matches:${RESET2}`);
1582
+ for (const r of exact.slice(0, 3)) {
1583
+ console.log(` ${cyan(r.slug)} - ${r.description || ""}`);
1584
+ }
1585
+ console.log("");
1160
1586
  }
1161
- console.log("");
1587
+ if (related.length > 0) {
1588
+ console.log(`${BOLD2}Related:${RESET2}`);
1589
+ for (const r of related.slice(0, 5)) {
1590
+ console.log(` ${cyan(r.slug)} - ${r.description || ""}`);
1591
+ }
1592
+ console.log("");
1593
+ }
1594
+ console.log(dim(`Use "decantr get pattern <id>" for full details`));
1595
+ } catch {
1596
+ console.log(dim(`Suggestion search failed. API may be unavailable.`));
1162
1597
  }
1163
- console.log(dim(`Use "decantr get pattern <id>" for full details`));
1164
1598
  }
1165
1599
  async function cmdGet(type, id) {
1166
1600
  const validTypes = ["pattern", "archetype", "recipe", "theme", "blueprint", "shell"];
@@ -1169,64 +1603,60 @@ async function cmdGet(type, id) {
1169
1603
  process.exitCode = 1;
1170
1604
  return;
1171
1605
  }
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;
1606
+ const typeMap = {
1607
+ pattern: "patterns",
1608
+ archetype: "archetypes",
1609
+ recipe: "recipes",
1610
+ theme: "themes",
1611
+ blueprint: "blueprints",
1612
+ shell: "shells"
1613
+ };
1614
+ const apiType = typeMap[type];
1615
+ const registryClient = new RegistryClient({
1616
+ cacheDir: join7(process.cwd(), ".decantr", "cache")
1617
+ });
1618
+ const result = await registryClient.fetchContentItem(apiType, id);
1619
+ if (result) {
1620
+ console.log(JSON.stringify(result.data, null, 2));
1183
1621
  return;
1184
1622
  }
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;
1623
+ const currentDir = dirname2(fileURLToPath2(import.meta.url));
1624
+ const bundledFromDist = join7(currentDir, "..", "src", "bundled", apiType, `${id}.json`);
1625
+ const bundledFromSrc = join7(currentDir, "bundled", apiType, `${id}.json`);
1626
+ const bundledPath = existsSync7(bundledFromDist) ? bundledFromDist : existsSync7(bundledFromSrc) ? bundledFromSrc : null;
1627
+ if (bundledPath) {
1628
+ const data = JSON.parse(readFileSync6(bundledPath, "utf-8"));
1629
+ console.log(JSON.stringify(data, null, 2));
1202
1630
  return;
1203
1631
  }
1204
- console.log(JSON.stringify(result.item, null, 2));
1632
+ console.error(error(`${type} "${id}" not found.`));
1633
+ process.exitCode = 1;
1634
+ return;
1205
1635
  }
1206
1636
  function buildRegistryContext() {
1207
1637
  const { readdirSync: readdirSync2 } = __require("fs");
1208
1638
  const themeRegistry = /* @__PURE__ */ new Map();
1209
1639
  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
- }
1640
+ const projectRoot = process.cwd();
1641
+ const cacheDir = join7(projectRoot, ".decantr", "cache");
1642
+ const customDir = join7(projectRoot, ".decantr", "custom");
1643
+ const cachedThemesDir = join7(cacheDir, "@official", "themes");
1644
+ try {
1645
+ if (existsSync7(cachedThemesDir)) {
1646
+ for (const f of readdirSync2(cachedThemesDir).filter((f2) => f2.endsWith(".json") && f2 !== "index.json")) {
1647
+ const data = JSON.parse(readFileSync6(join7(cachedThemesDir, f), "utf-8"));
1648
+ if (data.id && !themeRegistry.has(data.id)) {
1649
+ themeRegistry.set(data.id, { modes: data.modes || ["light", "dark"] });
1220
1650
  }
1221
1651
  }
1222
- } catch {
1223
1652
  }
1653
+ } catch {
1224
1654
  }
1225
- const customThemesDir = join4(process.cwd(), ".decantr", "custom", "themes");
1655
+ const customThemesDir = join7(customDir, "themes");
1226
1656
  try {
1227
- if (existsSync4(customThemesDir)) {
1657
+ if (existsSync7(customThemesDir)) {
1228
1658
  for (const f of readdirSync2(customThemesDir).filter((f2) => f2.endsWith(".json"))) {
1229
- const data = JSON.parse(readFileSync4(join4(customThemesDir, f), "utf-8"));
1659
+ const data = JSON.parse(readFileSync6(join7(customThemesDir, f), "utf-8"));
1230
1660
  if (data.id) {
1231
1661
  themeRegistry.set(`custom:${data.id}`, { modes: data.modes || ["light", "dark"] });
1232
1662
  }
@@ -1234,27 +1664,25 @@ function buildRegistryContext() {
1234
1664
  }
1235
1665
  } catch {
1236
1666
  }
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
- }
1667
+ const cachedPatternsDir = join7(cacheDir, "@official", "patterns");
1668
+ try {
1669
+ if (existsSync7(cachedPatternsDir)) {
1670
+ for (const f of readdirSync2(cachedPatternsDir).filter((f2) => f2.endsWith(".json") && f2 !== "index.json")) {
1671
+ const data = JSON.parse(readFileSync6(join7(cachedPatternsDir, f), "utf-8"));
1672
+ if (data.id && !patternRegistry.has(data.id)) {
1673
+ patternRegistry.set(data.id, data);
1246
1674
  }
1247
1675
  }
1248
- } catch {
1249
1676
  }
1677
+ } catch {
1250
1678
  }
1251
1679
  return { themeRegistry, patternRegistry };
1252
1680
  }
1253
1681
  async function cmdValidate(path) {
1254
- const essencePath = path || join4(process.cwd(), "decantr.essence.json");
1682
+ const essencePath = path || join7(process.cwd(), "decantr.essence.json");
1255
1683
  let raw;
1256
1684
  try {
1257
- raw = readFileSync4(essencePath, "utf-8");
1685
+ raw = readFileSync6(essencePath, "utf-8");
1258
1686
  } catch {
1259
1687
  console.error(error(`Could not read ${essencePath}`));
1260
1688
  process.exitCode = 1;
@@ -1303,110 +1731,40 @@ async function cmdList(type) {
1303
1731
  process.exitCode = 1;
1304
1732
  return;
1305
1733
  }
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`);
1734
+ const registryClient = new RegistryClient({
1735
+ cacheDir: join7(process.cwd(), ".decantr", "cache")
1736
+ });
1737
+ const result = await registryClient.fetchContentList(type);
1738
+ const items = result.data.items;
1739
+ if (items.length === 0) {
1740
+ console.log(dim(`No ${type} found.`));
1316
1741
  return;
1317
1742
  }
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
1743
  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 {
1744
+ const customItems = registryClient.listCustomContent("themes");
1745
+ const customIds = new Set(customItems.map((c) => c.id));
1746
+ const registryItems = items.filter((i) => !customIds.has(i.id));
1747
+ console.log(heading(`Registry themes (${registryItems.length}):`));
1748
+ for (const item of registryItems) {
1749
+ console.log(` ${cyan(item.id)} ${dim(item.description || item.name || "")}`);
1360
1750
  }
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.'));
1751
+ if (customItems.length > 0) {
1752
+ console.log("");
1753
+ console.log(heading(`Custom themes (${customItems.length}):`));
1754
+ for (const item of customItems) {
1755
+ console.log(` ${cyan(`custom:${item.id}`)} ${dim(item.description || item.name || "")}`);
1378
1756
  }
1379
1757
  } 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
- }
1758
+ console.log("");
1759
+ console.log(dim("Custom themes (0):"));
1760
+ console.log(dim(' Run "decantr theme create <name>" to create a custom theme.'));
1384
1761
  }
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;
1762
+ } else {
1763
+ console.log(heading(`${items.length} ${type}`));
1764
+ for (const item of items) {
1765
+ console.log(` ${cyan(item.id)} ${dim(item.description || item.name || "")}`);
1406
1766
  }
1407
- } catch {
1408
1767
  }
1409
- console.log(dim(`No ${type} found.`));
1410
1768
  }
1411
1769
  async function cmdInit(args) {
1412
1770
  const projectRoot = process.cwd();
@@ -1421,16 +1779,36 @@ async function cmdInit(args) {
1421
1779
  }
1422
1780
  }
1423
1781
  const registryClient = new RegistryClient({
1424
- cacheDir: join4(projectRoot, ".decantr", "cache"),
1782
+ cacheDir: join7(projectRoot, ".decantr", "cache"),
1425
1783
  apiUrl: args.registry,
1426
1784
  offline: args.offline
1427
1785
  });
1428
1786
  const apiAvailable = await registryClient.checkApiAvailability();
1429
1787
  let selectedBlueprint = "default";
1430
- let registrySource = "bundled";
1788
+ let registrySource = "cache";
1431
1789
  if (args.yes) {
1432
1790
  selectedBlueprint = args.blueprint || "default";
1433
1791
  } else if (!apiAvailable) {
1792
+ if (!args.blueprint) {
1793
+ console.log(`
1794
+ ${YELLOW2}You're offline. Scaffolding minimal Decantr project.${RESET2}`);
1795
+ console.log(dim("Run `decantr sync` or `decantr upgrade` when online to pull full registry content.\n"));
1796
+ const result2 = scaffoldMinimal(projectRoot);
1797
+ console.log(success("\nProject scaffolded (minimal/offline)!\n"));
1798
+ console.log(" Files created:");
1799
+ console.log(` ${cyan("decantr.essence.json")} Design specification`);
1800
+ console.log(` ${cyan("DECANTR.md")} LLM instructions`);
1801
+ console.log(` ${cyan(".decantr/")} Project state & custom content dirs`);
1802
+ if (result2.gitignoreUpdated) {
1803
+ console.log(` ${dim(".gitignore updated")}`);
1804
+ }
1805
+ console.log("");
1806
+ console.log(" Next steps:");
1807
+ console.log(` 1. Run ${cyan("decantr sync")} when online`);
1808
+ console.log(` 2. Use ${cyan("decantr create <type> <name>")} to create custom content`);
1809
+ console.log(` 3. Review DECANTR.md for methodology`);
1810
+ return;
1811
+ }
1434
1812
  console.log(`
1435
1813
  ${YELLOW2}You're offline. Scaffolding Decantr default.${RESET2}`);
1436
1814
  console.log(dim("Run `decantr upgrade` when online, or visit decantr.ai/registry\n"));
@@ -1438,7 +1816,7 @@ ${YELLOW2}You're offline. Scaffolding Decantr default.${RESET2}`);
1438
1816
  } else {
1439
1817
  console.log(dim("Fetching registry content..."));
1440
1818
  const blueprintsResult2 = await registryClient.fetchBlueprints();
1441
- registrySource = blueprintsResult2.source.type === "api" ? "api" : "bundled";
1819
+ registrySource = blueprintsResult2.source.type === "api" ? "api" : "cache";
1442
1820
  const { selectedBlueprint: selected } = await runSimplifiedInit(
1443
1821
  blueprintsResult2.data.items
1444
1822
  );
@@ -1468,6 +1846,17 @@ ${YELLOW2}You're offline. Scaffolding Decantr default.${RESET2}`);
1468
1846
  const blueprintResult = await registryClient.fetchBlueprint(options.blueprint);
1469
1847
  if (blueprintResult) {
1470
1848
  const blueprint = blueprintResult.data;
1849
+ if (blueprint.theme) {
1850
+ if (blueprint.theme.style && options.theme === "luminarum") {
1851
+ options.theme = blueprint.theme.style;
1852
+ }
1853
+ if (blueprint.theme.mode && options.mode === "dark") {
1854
+ options.mode = blueprint.theme.mode;
1855
+ }
1856
+ if (blueprint.theme.shape && options.shape === "rounded") {
1857
+ options.shape = blueprint.theme.shape;
1858
+ }
1859
+ }
1471
1860
  const primaryArchetype = blueprint.compose?.[0];
1472
1861
  if (primaryArchetype) {
1473
1862
  const archetypeResult = await registryClient.fetchArchetype(primaryArchetype);
@@ -1489,8 +1878,13 @@ ${YELLOW2}You're offline. Scaffolding Decantr default.${RESET2}`);
1489
1878
  const themeResult = await registryClient.fetchTheme(options.theme);
1490
1879
  if (themeResult) {
1491
1880
  const theme = themeResult.data;
1492
- if (theme.seed) {
1493
- themeData = { seed: theme.seed };
1881
+ themeData = {
1882
+ seed: theme.seed,
1883
+ palette: theme.palette,
1884
+ tokens: theme.tokens
1885
+ };
1886
+ if (theme.decorators) {
1887
+ recipeData = { decorators: theme.decorators };
1494
1888
  }
1495
1889
  }
1496
1890
  }
@@ -1524,7 +1918,7 @@ ${YELLOW2}You're offline. Scaffolding Decantr default.${RESET2}`);
1524
1918
  console.log(` ${cyan("decantr validate")} Check essence file`);
1525
1919
  console.log(` ${cyan("decantr upgrade")} Update to latest patterns`);
1526
1920
  console.log(` ${cyan("decantr heal")} Fix drift issues`);
1527
- const essenceContent = readFileSync4(result.essencePath, "utf-8");
1921
+ const essenceContent = readFileSync6(result.essencePath, "utf-8");
1528
1922
  const essence = JSON.parse(essenceContent);
1529
1923
  const validation = validateEssence(essence);
1530
1924
  if (!validation.valid) {
@@ -1546,22 +1940,22 @@ Validation warnings: ${validation.errors.join(", ")}`));
1546
1940
  const curatedPrompt = generateCuratedPrompt(promptCtx);
1547
1941
  console.log(boxedPrompt(curatedPrompt, "Copy this prompt for your AI assistant"));
1548
1942
  console.log("");
1549
- if (registrySource === "bundled") {
1943
+ if (registrySource === "cache") {
1550
1944
  console.log(dim('Run "decantr sync" when online to get the latest registry content.'));
1551
1945
  }
1552
1946
  }
1553
1947
  async function cmdStatus() {
1554
1948
  const projectRoot = process.cwd();
1555
- const essencePath = join4(projectRoot, "decantr.essence.json");
1556
- const projectJsonPath = join4(projectRoot, ".decantr", "project.json");
1949
+ const essencePath = join7(projectRoot, "decantr.essence.json");
1950
+ const projectJsonPath = join7(projectRoot, ".decantr", "project.json");
1557
1951
  console.log(heading("Decantr Project Status"));
1558
- if (!existsSync4(essencePath)) {
1952
+ if (!existsSync7(essencePath)) {
1559
1953
  console.log(`${RED}No decantr.essence.json found.${RESET2}`);
1560
1954
  console.log(dim('Run "decantr init" to create one.'));
1561
1955
  return;
1562
1956
  }
1563
1957
  try {
1564
- const essence = JSON.parse(readFileSync4(essencePath, "utf-8"));
1958
+ const essence = JSON.parse(readFileSync6(essencePath, "utf-8"));
1565
1959
  const validation = validateEssence(essence);
1566
1960
  console.log(`${BOLD2}Essence:${RESET2}`);
1567
1961
  if (validation.valid) {
@@ -1577,9 +1971,9 @@ async function cmdStatus() {
1577
1971
  }
1578
1972
  console.log("");
1579
1973
  console.log(`${BOLD2}Sync Status:${RESET2}`);
1580
- if (existsSync4(projectJsonPath)) {
1974
+ if (existsSync7(projectJsonPath)) {
1581
1975
  try {
1582
- const projectJson = JSON.parse(readFileSync4(projectJsonPath, "utf-8"));
1976
+ const projectJson = JSON.parse(readFileSync6(projectJsonPath, "utf-8"));
1583
1977
  const syncStatus = projectJson.sync?.status || "unknown";
1584
1978
  const lastSync = projectJson.sync?.lastSync || "never";
1585
1979
  const source = projectJson.sync?.registrySource || "unknown";
@@ -1597,33 +1991,33 @@ async function cmdStatus() {
1597
1991
  }
1598
1992
  async function cmdSync() {
1599
1993
  const projectRoot = process.cwd();
1600
- const cacheDir = join4(projectRoot, ".decantr", "cache");
1994
+ const cacheDir = join7(projectRoot, ".decantr", "cache");
1601
1995
  console.log(heading("Syncing registry content..."));
1602
1996
  const result = await syncRegistry(cacheDir);
1603
- if (result.source === "api") {
1997
+ if (result.synced.length > 0) {
1604
1998
  console.log(success("Sync completed successfully."));
1605
- if (result.synced.length > 0) {
1606
- console.log(` Synced: ${result.synced.join(", ")}`);
1607
- }
1999
+ console.log(` Synced: ${result.synced.join(", ")}`);
1608
2000
  if (result.failed.length > 0) {
1609
2001
  console.log(` ${YELLOW2}Failed: ${result.failed.join(", ")}${RESET2}`);
1610
2002
  }
1611
2003
  } else {
1612
2004
  console.log(`${YELLOW2}Could not sync: API unavailable${RESET2}`);
1613
- console.log(dim("Using bundled content."));
2005
+ if (result.failed.length > 0) {
2006
+ console.log(` ${YELLOW2}Failed: ${result.failed.join(", ")}${RESET2}`);
2007
+ }
1614
2008
  }
1615
2009
  }
1616
2010
  async function cmdAudit() {
1617
2011
  const projectRoot = process.cwd();
1618
- const essencePath = join4(projectRoot, "decantr.essence.json");
2012
+ const essencePath = join7(projectRoot, "decantr.essence.json");
1619
2013
  console.log(heading("Auditing project..."));
1620
- if (!existsSync4(essencePath)) {
2014
+ if (!existsSync7(essencePath)) {
1621
2015
  console.log(`${RED}No decantr.essence.json found.${RESET2}`);
1622
2016
  process.exitCode = 1;
1623
2017
  return;
1624
2018
  }
1625
2019
  try {
1626
- const essence = JSON.parse(readFileSync4(essencePath, "utf-8"));
2020
+ const essence = JSON.parse(readFileSync6(essencePath, "utf-8"));
1627
2021
  const validation = validateEssence(essence);
1628
2022
  if (!validation.valid) {
1629
2023
  console.log(`${RED}Essence validation failed:${RESET2}`);
@@ -1723,14 +2117,14 @@ ${BOLD2}Examples:${RESET2}
1723
2117
  process.exitCode = 1;
1724
2118
  return;
1725
2119
  }
1726
- const themePath = join4(projectRoot, ".decantr", "custom", "themes", `${name}.json`);
1727
- if (!existsSync4(themePath)) {
2120
+ const themePath = join7(projectRoot, ".decantr", "custom", "themes", `${name}.json`);
2121
+ if (!existsSync7(themePath)) {
1728
2122
  console.error(error(`Theme "${name}" not found at ${themePath}`));
1729
2123
  process.exitCode = 1;
1730
2124
  return;
1731
2125
  }
1732
2126
  try {
1733
- const theme = JSON.parse(readFileSync4(themePath, "utf-8"));
2127
+ const theme = JSON.parse(readFileSync6(themePath, "utf-8"));
1734
2128
  const result = validateCustomTheme(theme);
1735
2129
  if (result.valid) {
1736
2130
  console.log(success(`Custom theme "${name}" is valid`));
@@ -1803,6 +2197,10 @@ ${BOLD2}Usage:${RESET2}
1803
2197
  decantr list <type>
1804
2198
  decantr validate [path]
1805
2199
  decantr theme <subcommand>
2200
+ decantr create <type> <name>
2201
+ decantr publish <type> <name>
2202
+ decantr login
2203
+ decantr logout
1806
2204
  decantr help
1807
2205
 
1808
2206
  ${BOLD2}Init Options:${RESET2}
@@ -1830,6 +2228,10 @@ ${BOLD2}Commands:${RESET2}
1830
2228
  ${cyan("list")} List items by type
1831
2229
  ${cyan("validate")} Validate essence file
1832
2230
  ${cyan("theme")} Manage custom themes (create, list, validate, delete, import)
2231
+ ${cyan("create")} Create a custom content item (pattern, recipe, theme, etc.)
2232
+ ${cyan("publish")} Publish a custom content item to the community registry
2233
+ ${cyan("login")} Authenticate with the Decantr registry
2234
+ ${cyan("logout")} Remove stored credentials
1833
2235
  ${cyan("help")} Show this help
1834
2236
 
1835
2237
  ${BOLD2}Examples:${RESET2}
@@ -1842,6 +2244,8 @@ ${BOLD2}Examples:${RESET2}
1842
2244
  decantr suggest leaderboard
1843
2245
  decantr suggest ranking --type pattern
1844
2246
  decantr list patterns
2247
+ decantr create pattern my-card
2248
+ decantr publish pattern my-card
1845
2249
  `);
1846
2250
  }
1847
2251
  async function main() {
@@ -1887,7 +2291,7 @@ async function main() {
1887
2291
  break;
1888
2292
  }
1889
2293
  case "upgrade": {
1890
- const { cmdUpgrade } = await import("./upgrade-FWICWIQW.js");
2294
+ const { cmdUpgrade } = await import("./upgrade-KRFCKUMR.js");
1891
2295
  await cmdUpgrade(process.cwd());
1892
2296
  break;
1893
2297
  }
@@ -1953,6 +2357,60 @@ async function main() {
1953
2357
  await cmdTheme(args.slice(1));
1954
2358
  break;
1955
2359
  }
2360
+ case "login": {
2361
+ const apiKeyArg = args[1];
2362
+ if (apiKeyArg && apiKeyArg.startsWith("--api-key=")) {
2363
+ const key = apiKeyArg.split("=")[1];
2364
+ saveCredentials({ access_token: key, api_key: key });
2365
+ console.log(success("API key saved."));
2366
+ } else {
2367
+ console.log(heading("Decantr Login"));
2368
+ console.log(" To authenticate, get your API key from the Decantr dashboard:");
2369
+ console.log("");
2370
+ console.log(` ${cyan("https://decantr.ai/dashboard/api-keys")}`);
2371
+ console.log("");
2372
+ console.log(" Then run:");
2373
+ console.log(` ${cyan("decantr login --api-key=<your-key>")}`);
2374
+ console.log("");
2375
+ console.log(" Or set the environment variable:");
2376
+ console.log(` ${cyan("export DECANTR_API_KEY=<your-key>")}`);
2377
+ const existingCreds = getCredentials();
2378
+ if (existingCreds) {
2379
+ console.log("");
2380
+ console.log(dim("You are currently authenticated."));
2381
+ }
2382
+ }
2383
+ break;
2384
+ }
2385
+ case "logout": {
2386
+ clearCredentials();
2387
+ console.log(success("Logged out. Credentials removed."));
2388
+ break;
2389
+ }
2390
+ case "create": {
2391
+ const type = args[1];
2392
+ const name = args[2];
2393
+ if (!type || !name) {
2394
+ console.error(error("Usage: decantr create <type> <name>"));
2395
+ console.error(dim("Types: pattern, recipe, theme, blueprint, archetype, shell"));
2396
+ process.exitCode = 1;
2397
+ break;
2398
+ }
2399
+ cmdCreate(type, name);
2400
+ break;
2401
+ }
2402
+ case "publish": {
2403
+ const type = args[1];
2404
+ const name = args[2];
2405
+ if (!type || !name) {
2406
+ console.error(error("Usage: decantr publish <type> <name>"));
2407
+ console.error(dim("Types: pattern, recipe, theme, blueprint, archetype, shell"));
2408
+ process.exitCode = 1;
2409
+ break;
2410
+ }
2411
+ await cmdPublish(type, name);
2412
+ break;
2413
+ }
1956
2414
  default:
1957
2415
  console.error(error(`Unknown command: ${command}`));
1958
2416
  cmdHelp();