@decantr/cli 1.5.6 → 1.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Decantr AI
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/dist/bin.js CHANGED
@@ -1,3 +1,3 @@
1
1
  #!/usr/bin/env node
2
- import "./chunk-G77ADKWE.js";
3
- import "./chunk-VKWRNLHR.js";
2
+ import "./chunk-DESYSWLL.js";
3
+ import "./chunk-PMBLHVBX.js";
@@ -9,7 +9,7 @@ import {
9
9
  scaffoldMinimal,
10
10
  scaffoldProject,
11
11
  syncRegistry
12
- } from "./chunk-VKWRNLHR.js";
12
+ } from "./chunk-PMBLHVBX.js";
13
13
 
14
14
  // src/index.ts
15
15
  import { readFileSync as readFileSync15, existsSync as existsSync23, readdirSync as readdirSync6 } from "fs";
@@ -2189,6 +2189,7 @@ ${YELLOW5}Next step:${RESET8} Ask your AI assistant to read ${BOLD3}.decantr/ana
2189
2189
  // src/commands/magic.ts
2190
2190
  import { join as join19 } from "path";
2191
2191
  import { existsSync as existsSync19 } from "fs";
2192
+ import * as fs from "fs/promises";
2192
2193
  var BOLD4 = "\x1B[1m";
2193
2194
  var DIM9 = "\x1B[2m";
2194
2195
  var RESET9 = "\x1B[0m";
@@ -2336,7 +2337,32 @@ function parseMagicPrompt(prompt) {
2336
2337
  archetype
2337
2338
  };
2338
2339
  }
2339
- function resolveTheme(hints) {
2340
+ async function resolveTheme(intent, registryClient) {
2341
+ const hints = intent.themeHints;
2342
+ if (registryClient) {
2343
+ try {
2344
+ const themes = await registryClient.fetchThemes();
2345
+ if (themes && themes.length > 0) {
2346
+ const scored = themes.map((t) => {
2347
+ let score = 0;
2348
+ const personality = (t.personality || "").toLowerCase();
2349
+ const tags = (t.tags || []).map((tag) => tag.toLowerCase());
2350
+ for (const hint of intent.themeHints) {
2351
+ if (personality.includes(hint)) score += 3;
2352
+ if (tags.some((tag) => tag.includes(hint))) score += 2;
2353
+ }
2354
+ if (intent.themeHints.includes("dark") && t.modes?.includes("dark")) score += 1;
2355
+ if (intent.themeHints.includes("light") && t.modes?.includes("light")) score += 1;
2356
+ return { id: t.id || t.slug, score, modes: t.modes };
2357
+ }).sort((a, b) => b.score - a.score);
2358
+ if (scored[0] && scored[0].score > 0) {
2359
+ const mode2 = intent.themeHints.includes("light") ? "light" : intent.themeHints.includes("dark") ? "dark" : scored[0].modes?.includes("dark") ? "dark" : "light";
2360
+ return { style: scored[0].id, mode: mode2 };
2361
+ }
2362
+ }
2363
+ } catch {
2364
+ }
2365
+ }
2340
2366
  let mode = "dark";
2341
2367
  if (hints.includes("light")) mode = "light";
2342
2368
  if (hints.includes("neon") || hints.includes("glass")) return { style: "obsidianite", mode };
@@ -2358,6 +2384,18 @@ function buildPersonality(intent) {
2358
2384
  if (intent.themeHints.includes("playful")) return ["friendly", "energetic"];
2359
2385
  return ["professional"];
2360
2386
  }
2387
+ function buildRichPersonality(intent, blueprintData, themeData) {
2388
+ const parts = [];
2389
+ if (blueprintData?.personality && typeof blueprintData.personality === "string")
2390
+ parts.push(blueprintData.personality);
2391
+ if (intent.personalityHints.length > 0 && !parts.some((p) => intent.personalityHints.every((h) => p.toLowerCase().includes(h))))
2392
+ parts.push(`Visual character: ${intent.personalityHints.join(", ")}.`);
2393
+ if (themeData?.personality && !parts.some((p) => p.toLowerCase().includes(themeData.personality.toLowerCase())))
2394
+ parts.push(`Theme influence: ${themeData.personality}.`);
2395
+ if (parts.length === 0)
2396
+ parts.push(`Modern, production-ready ${intent.description}. Clean typography, intentional spacing, polished interactions.`);
2397
+ return parts.join(" ");
2398
+ }
2361
2399
  async function cmdMagic(prompt, projectRoot, options) {
2362
2400
  console.log("");
2363
2401
  console.log(`${MAGENTA}${BOLD4} Decantr Magic${RESET9}`);
@@ -2435,7 +2473,7 @@ async function cmdMagic(prompt, projectRoot, options) {
2435
2473
  } else {
2436
2474
  console.log(dim(" Offline mode \u2014 using defaults."));
2437
2475
  }
2438
- const themeResolved = resolveTheme(intent.themeHints);
2476
+ const themeResolved = await resolveTheme(intent, apiAvailable ? registryClient : void 0);
2439
2477
  const personality = buildPersonality(intent);
2440
2478
  const initOptions = {
2441
2479
  blueprint: matchedBlueprint,
@@ -2463,17 +2501,28 @@ async function cmdMagic(prompt, projectRoot, options) {
2463
2501
  initOptions.personality = [...merged];
2464
2502
  }
2465
2503
  }
2504
+ if (intent.constraints.includes("accessible")) {
2505
+ initOptions.accessibility = { wcag_level: "AA" };
2506
+ }
2507
+ if (intent.constraints.includes("mobile-first")) {
2508
+ initOptions.density = "comfortable";
2509
+ }
2510
+ if (intent.constraints.includes("real-time")) {
2511
+ initOptions.features = [...initOptions.features || [], "websockets", "live-updates"];
2512
+ }
2466
2513
  if (options.dryRun) {
2514
+ const personalityStr = initOptions.personality.join(". ");
2467
2515
  console.log("");
2468
- console.log(`${YELLOW6}${BOLD4} Dry run \u2014 no files written${RESET9}`);
2469
- console.log("");
2470
- console.log(` Blueprint: ${matchedBlueprint || dim("(none \u2014 archetype-based)")}`);
2471
- console.log(` Theme: ${initOptions.theme} (${initOptions.mode})`);
2472
- console.log(` Personality: ${initOptions.personality.join(", ")}`);
2473
- console.log(` Archetype: ${initOptions.archetype}`);
2474
- console.log(` Guard: ${initOptions.guard}`);
2475
- console.log(` Density: ${initOptions.density}`);
2476
- console.log("");
2516
+ console.log(`${BOLD4}decantr magic preview${RESET9}`);
2517
+ console.log("\u2500".repeat(60));
2518
+ console.log(` Blueprint: ${matchedBlueprint || "none (archetype-direct)"}`);
2519
+ console.log(` Theme: ${initOptions.theme} (${initOptions.mode}${initOptions.shape ? ", " + initOptions.shape : ""})`);
2520
+ console.log(` Archetype: ${initOptions.archetype || "auto-detected"}`);
2521
+ console.log(` Personality: ${personalityStr.slice(0, 80)}${personalityStr.length > 80 ? "..." : ""}`);
2522
+ console.log(` WCAG: ${initOptions.accessibility?.wcag_level || "AA"} | Density: ${initOptions.density} | Guard: ${initOptions.guard}`);
2523
+ if (intent.constraints.length > 0)
2524
+ console.log(` Constraints: ${intent.constraints.join(", ")}`);
2525
+ console.log("\u2500".repeat(60));
2477
2526
  return;
2478
2527
  }
2479
2528
  console.log("");
@@ -2625,6 +2674,10 @@ async function cmdMagic(prompt, projectRoot, options) {
2625
2674
  };
2626
2675
  }
2627
2676
  }
2677
+ const richPersonality = buildRichPersonality(intent, blueprintData, themeData);
2678
+ if (richPersonality) {
2679
+ initOptions.personality = [richPersonality];
2680
+ }
2628
2681
  console.log(`${BOLD4} Scaffolding...${RESET9}`);
2629
2682
  const result = await scaffoldProject(
2630
2683
  projectRoot,
@@ -2664,6 +2717,25 @@ async function cmdMagic(prompt, projectRoot, options) {
2664
2717
  if (result.gitignoreUpdated) {
2665
2718
  console.log(` ${dim(".gitignore updated")}`);
2666
2719
  }
2720
+ const contextDir = join19(projectRoot, ".decantr", "context");
2721
+ let sectionCount = 0;
2722
+ try {
2723
+ const files = await fs.readdir(contextDir);
2724
+ sectionCount = files.filter((f) => f.startsWith("section-")).length;
2725
+ } catch {
2726
+ }
2727
+ const treatmentsPath = join19(projectRoot, "src", "styles", "treatments.css");
2728
+ let hasLayers = false;
2729
+ try {
2730
+ const css = await fs.readFile(treatmentsPath, "utf-8");
2731
+ hasLayers = css.includes("@layer");
2732
+ } catch {
2733
+ }
2734
+ console.log(`
2735
+ ${GREEN9}${BOLD4}Quality summary:${RESET9}`);
2736
+ console.log(` Context files: ${sectionCount} sections + scaffold.md + DECANTR.md`);
2737
+ console.log(` CSS: tokens.css + treatments.css + global.css`);
2738
+ console.log(` @layer cascade: ${hasLayers ? GREEN9 + "yes" + RESET9 : YELLOW6 + "missing" + RESET9}`);
2667
2739
  console.log("");
2668
2740
  console.log(`${BOLD4} Ready!${RESET9} Next steps:`);
2669
2741
  console.log(` 1. Read ${cyan("DECANTR.md")} to understand the design system`);
@@ -3576,12 +3648,22 @@ ${YELLOW9}You're offline. Scaffolding Decantr default.${RESET13}`);
3576
3648
  const blueprints = blueprintsResult.data.items;
3577
3649
  const themes = themesResult.data.items;
3578
3650
  let options;
3651
+ const userExplicit = {
3652
+ theme: Boolean(args.theme),
3653
+ mode: Boolean(args.mode),
3654
+ shape: Boolean(args.shape),
3655
+ personality: Boolean(args.personality)
3656
+ };
3579
3657
  if (args.yes || selectedBlueprint !== "default") {
3580
3658
  const flags = parseFlags(args, detected);
3581
3659
  flags.blueprint = selectedBlueprint !== "default" ? selectedBlueprint : flags.blueprint;
3582
3660
  options = mergeWithDefaults(flags, detected);
3583
3661
  } else {
3584
3662
  options = await runInteractivePrompts(detected, archetypes, blueprints, themes);
3663
+ userExplicit.theme = true;
3664
+ userExplicit.mode = true;
3665
+ userExplicit.shape = true;
3666
+ userExplicit.personality = true;
3585
3667
  }
3586
3668
  let topologyMarkdown = "";
3587
3669
  let archetypeData;
@@ -3592,20 +3674,19 @@ ${YELLOW9}You're offline. Scaffolding Decantr default.${RESET13}`);
3592
3674
  if (options.blueprint) {
3593
3675
  const blueprintResult = await registryClient.fetchBlueprint(options.blueprint);
3594
3676
  if (blueprintResult) {
3595
- const rawBlueprint = blueprintResult.data;
3596
- const blueprint = rawBlueprint.data ?? rawBlueprint;
3677
+ const blueprint = blueprintResult.data;
3597
3678
  if (blueprint.theme) {
3598
- if ((blueprint.theme.id || blueprint.theme.style) && options.theme === "luminarum") {
3679
+ if (!userExplicit.theme && (blueprint.theme.id || blueprint.theme.style)) {
3599
3680
  options.theme = blueprint.theme.id || blueprint.theme.style;
3600
3681
  }
3601
- if (blueprint.theme.mode && options.mode === "dark") {
3682
+ if (!userExplicit.mode && blueprint.theme.mode) {
3602
3683
  options.mode = blueprint.theme.mode;
3603
3684
  }
3604
- if (blueprint.theme.shape && options.shape === "rounded") {
3685
+ if (!userExplicit.shape && blueprint.theme.shape) {
3605
3686
  options.shape = blueprint.theme.shape;
3606
3687
  }
3607
3688
  }
3608
- if (blueprint.personality && (!options.personality || options.personality.length === 0 || options.personality.length === 1 && options.personality[0] === "professional")) {
3689
+ if (!userExplicit.personality && blueprint.personality) {
3609
3690
  options.personality = typeof blueprint.personality === "string" ? [blueprint.personality] : blueprint.personality;
3610
3691
  }
3611
3692
  if (blueprint.compose && blueprint.compose.length > 0) {
@@ -3614,9 +3695,7 @@ ${YELLOW9}You're offline. Scaffolding Decantr default.${RESET13}`);
3614
3695
  for (const entry of entries) {
3615
3696
  const id = typeof entry === "string" ? entry : entry.archetype;
3616
3697
  const r = await registryClient.fetchArchetype(id);
3617
- const raw = r?.data;
3618
- const inner = raw?.data ?? raw;
3619
- results.push([id, inner]);
3698
+ results.push([id, r?.data ?? null]);
3620
3699
  }
3621
3700
  const archetypeMap = new Map(results.map(([id, data]) => [id, data || null]));
3622
3701
  const composed = composeArchetypes(entries, archetypeMap);
@@ -3664,8 +3743,7 @@ ${YELLOW9}You're offline. Scaffolding Decantr default.${RESET13}`);
3664
3743
  try {
3665
3744
  const result2 = await registryClient.fetchPattern(pid);
3666
3745
  if (result2) {
3667
- const raw = result2.data;
3668
- const inner = raw.data ?? raw;
3746
+ const inner = result2.data;
3669
3747
  const defaultPreset = inner.default_preset || "standard";
3670
3748
  const preset = inner.presets?.[defaultPreset];
3671
3749
  patternSpecs[pid] = {
@@ -3717,8 +3795,7 @@ ${YELLOW9}You're offline. Scaffolding Decantr default.${RESET13}`);
3717
3795
  } else if (options.archetype) {
3718
3796
  const archetypeResult = await registryClient.fetchArchetype(options.archetype);
3719
3797
  if (archetypeResult) {
3720
- const rawArch = archetypeResult.data;
3721
- archetypeData = rawArch.data ?? rawArch;
3798
+ archetypeData = archetypeResult.data;
3722
3799
  } else {
3723
3800
  console.log(`${YELLOW9} Warning: Could not fetch archetype "${options.archetype}". Using defaults.${RESET13}`);
3724
3801
  }
@@ -3727,8 +3804,7 @@ ${YELLOW9}You're offline. Scaffolding Decantr default.${RESET13}`);
3727
3804
  if (options.theme) {
3728
3805
  const themeResult = await registryClient.fetchTheme(options.theme);
3729
3806
  if (themeResult) {
3730
- const rawTheme = themeResult.data;
3731
- const theme = rawTheme.data ?? rawTheme;
3807
+ const theme = themeResult.data;
3732
3808
  themeData = {
3733
3809
  seed: theme.seed,
3734
3810
  palette: theme.palette,
@@ -4256,7 +4332,7 @@ async function main() {
4256
4332
  break;
4257
4333
  }
4258
4334
  case "upgrade": {
4259
- const { cmdUpgrade } = await import("./upgrade-6MKC6EDQ.js");
4335
+ const { cmdUpgrade } = await import("./upgrade-2OG7UKTR.js");
4260
4336
  const applyFlag = args.includes("--apply");
4261
4337
  await cmdUpgrade(process.cwd(), { apply: applyFlag });
4262
4338
  break;
@@ -9,6 +9,8 @@ function generateTreatmentCSS(spatialTokens, treatmentOverrides, themeDecorators
9
9
  const lines = [];
10
10
  lines.push("/* Generated by @decantr/cli \u2014 Visual Treatment System */");
11
11
  lines.push("");
12
+ lines.push("@layer treatments {");
13
+ lines.push("");
12
14
  lines.push("/* \u2500\u2500 Layer 1: Base Treatments \u2500\u2500 */");
13
15
  lines.push("");
14
16
  function emitRule(selector, props) {
@@ -202,16 +204,6 @@ function generateTreatmentCSS(spatialTokens, treatmentOverrides, themeDecorators
202
204
  ["color", "var(--d-text-muted)"],
203
205
  ["font-family", "var(--d-font-mono, ui-monospace, monospace)"]
204
206
  ]);
205
- if (themeDecorators && Object.keys(themeDecorators).length > 0) {
206
- const label = themeName ? ` (${themeName})` : "";
207
- lines.push(`/* \u2500\u2500 Layer 3: Theme Decorators${label} \u2500\u2500 */`);
208
- lines.push("");
209
- for (const [name, description] of Object.entries(themeDecorators)) {
210
- const rule = generateDecoratorRule(name, description);
211
- lines.push(rule);
212
- lines.push("");
213
- }
214
- }
215
207
  lines.push("/* \u2500\u2500 Keyframes \u2500\u2500 */");
216
208
  lines.push("");
217
209
  lines.push("@keyframes decantr-fade-in {");
@@ -223,6 +215,22 @@ function generateTreatmentCSS(spatialTokens, treatmentOverrides, themeDecorators
223
215
  lines.push(" 0%, 100% { opacity: 1; }");
224
216
  lines.push(" 50% { opacity: 0.5; }");
225
217
  lines.push("}");
218
+ lines.push("");
219
+ lines.push("} /* end @layer treatments */");
220
+ if (themeDecorators && Object.keys(themeDecorators).length > 0) {
221
+ const label = themeName ? ` (${themeName})` : "";
222
+ lines.push("");
223
+ lines.push("@layer decorators {");
224
+ lines.push("");
225
+ lines.push(`/* \u2500\u2500 Layer 3: Theme Decorators${label} \u2500\u2500 */`);
226
+ lines.push("");
227
+ for (const [name, description] of Object.entries(themeDecorators)) {
228
+ const rule = generateDecoratorRule(name, description);
229
+ lines.push(rule);
230
+ lines.push("");
231
+ }
232
+ lines.push("} /* end @layer decorators */");
233
+ }
226
234
  return lines.join("\n");
227
235
  }
228
236
  function generatePersonalityCSS(personality, themeData) {
@@ -252,7 +260,7 @@ function generatePersonalityCSS(personality, themeData) {
252
260
  rules.push(`@keyframes decantr-entrance { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: translateY(0); } }`);
253
261
  }
254
262
  if (rules.length === 0) return "";
255
- return "\n/* \u2500\u2500 Personality-Derived Utilities \u2500\u2500 */\n\n" + rules.join("\n\n") + "\n";
263
+ return "\n@layer utilities {\n\n/* \u2500\u2500 Personality-Derived Utilities \u2500\u2500 */\n\n" + rules.join("\n\n") + "\n\n} /* end @layer utilities */\n";
256
264
  }
257
265
 
258
266
  // src/scaffold.ts
@@ -509,6 +517,7 @@ function generateTokensCSS(themeData, mode, spatialTokens) {
509
517
  if (!themeData) {
510
518
  const spatialLines2 = spatialTokens ? "\n" + Object.entries(spatialTokens).map(([k, v]) => ` ${k}: ${v};`).join("\n") : "";
511
519
  return `/* No theme data available */
520
+ @layer tokens {
512
521
  :root {
513
522
  --d-primary: #6366f1;
514
523
  --d-secondary: #a1a1aa;
@@ -520,6 +529,7 @@ function generateTokensCSS(themeData, mode, spatialTokens) {
520
529
  --d-text: #fafafa;
521
530
  --d-text-muted: #a1a1aa;${spatialLines2}
522
531
  }
532
+ }
523
533
  `;
524
534
  }
525
535
  const seed = themeData.seed || {};
@@ -584,6 +594,7 @@ function generateTokensCSS(themeData, mode, spatialTokens) {
584
594
  const lines = Object.entries(tokens).map(([key, value]) => ` ${key}: ${value};`).join("\n");
585
595
  const spatialLines = spatialTokens ? "\n" + Object.entries(spatialTokens).map(([k, v]) => ` ${k}: ${v};`).join("\n") : "";
586
596
  let css = `/* Generated by @decantr/cli */
597
+ @layer tokens {
587
598
  :root {
588
599
  ${lines}${spatialLines}
589
600
  }
@@ -612,9 +623,11 @@ ${lightLines}
612
623
  }
613
624
  `;
614
625
  }
626
+ css += `}
627
+ `;
615
628
  return css;
616
629
  }
617
- function generateGlobalCSS(personality) {
630
+ function generateGlobalCSS(personality, essence) {
618
631
  const personalityText = personality.join(" ").toLowerCase();
619
632
  let fontBody = "system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif";
620
633
  if (personalityText.includes("inter")) {
@@ -622,7 +635,12 @@ function generateGlobalCSS(personality) {
622
635
  } else if (personalityText.includes("geist")) {
623
636
  fontBody = `'Geist', ${fontBody}`;
624
637
  }
638
+ const mode = essence?.dna?.theme?.mode || "dark";
639
+ const colorScheme = mode === "auto" ? "light dark" : mode;
625
640
  return `/* Generated by @decantr/cli \u2014 global reset + body styles */
641
+ @layer reset, tokens, treatments, decorators, utilities, app;
642
+
643
+ @layer reset {
626
644
 
627
645
  *, *::before, *::after {
628
646
  box-sizing: border-box;
@@ -631,7 +649,7 @@ function generateGlobalCSS(personality) {
631
649
  }
632
650
 
633
651
  html {
634
- color-scheme: dark;
652
+ color-scheme: ${colorScheme};
635
653
  -webkit-font-smoothing: antialiased;
636
654
  -moz-osx-font-smoothing: grayscale;
637
655
  text-rendering: optimizeLegibility;
@@ -671,6 +689,8 @@ input, button, textarea, select {
671
689
  white-space: nowrap;
672
690
  border-width: 0;
673
691
  }
692
+
693
+ }
674
694
  `;
675
695
  }
676
696
  function generateDecoratorRule(name, description) {
@@ -1291,12 +1311,44 @@ Check \`decantr.essence.json\` \u2192 \`meta.platform.routing\` for the routing
1291
1311
  - \`"history"\` \u2192 use \`BrowserRouter\` (e.g., for server-rendered apps)
1292
1312
 
1293
1313
  Routes are defined in \`decantr.essence.json\` \u2192 \`blueprint.routes\` and listed in \`.decantr/context/scaffold.md\`.`;
1294
- function generateDecantrMdV31(guardMode, cssApproach) {
1314
+ function generateDecantrMdV31(params) {
1295
1315
  const template = loadTemplate("DECANTR.md.template");
1296
- return renderTemplate(template, {
1297
- GUARD_MODE: guardMode,
1298
- CSS_APPROACH: cssApproach
1316
+ const body = renderTemplate(template, {
1317
+ GUARD_MODE: params.guardMode,
1318
+ CSS_APPROACH: params.cssApproach
1299
1319
  });
1320
+ const briefLines = [];
1321
+ briefLines.push("## Project Brief");
1322
+ briefLines.push("");
1323
+ briefLines.push(`- **Blueprint:** ${params.blueprintId || "custom"}`);
1324
+ const themeParts = [params.themeName || "default"];
1325
+ if (params.themeMode) themeParts.push(`${params.themeMode} mode`);
1326
+ if (params.themeShape) themeParts.push(params.themeShape);
1327
+ briefLines.push(`- **Theme:** ${themeParts.join(" (").replace(/ \($/, "") + (themeParts.length > 1 ? ")" : "")}`);
1328
+ if (params.personality && params.personality.length > 0) {
1329
+ briefLines.push(`- **Personality:** ${params.personality.join(". ")}`);
1330
+ }
1331
+ if (params.sections && params.sections.length > 0) {
1332
+ const sectionList = params.sections.map((s) => `${s.id} [${s.role}]`).join(", ");
1333
+ briefLines.push(`- **Sections:** ${params.sections.length} (${sectionList})`);
1334
+ }
1335
+ if (params.features && params.features.length > 0) {
1336
+ briefLines.push(`- **Features:** ${params.features.join(", ")}`);
1337
+ }
1338
+ briefLines.push(`- **Guard mode:** ${params.guardMode}`);
1339
+ briefLines.push("");
1340
+ if (params.decorators && params.decorators.length > 0) {
1341
+ briefLines.push("### Decorator Quick Reference");
1342
+ briefLines.push("| Class | Purpose |");
1343
+ briefLines.push("|-------|---------|");
1344
+ for (const d of params.decorators) {
1345
+ briefLines.push(`| \`.${d.name}\` | ${d.description} |`);
1346
+ }
1347
+ briefLines.push("");
1348
+ }
1349
+ briefLines.push("---");
1350
+ briefLines.push("");
1351
+ return briefLines.join("\n") + body;
1300
1352
  }
1301
1353
  function generateProjectJson(detected, options, registrySource) {
1302
1354
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -1706,8 +1758,7 @@ async function refreshDerivedFiles(projectRoot, essence, registry, prefetchedThe
1706
1758
  if (!themeData) try {
1707
1759
  const themeResult = await registry.fetchTheme(themeName);
1708
1760
  if (themeResult?.data) {
1709
- const raw = themeResult.data;
1710
- const t = raw.data ?? raw;
1761
+ const t = themeResult.data;
1711
1762
  themeData = {
1712
1763
  seed: t.seed,
1713
1764
  palette: t.palette,
@@ -1758,7 +1809,7 @@ async function refreshDerivedFiles(projectRoot, essence, registry, prefetchedThe
1758
1809
  }
1759
1810
  const stylesDir = join(projectRoot, "src", "styles");
1760
1811
  mkdirSync(stylesDir, { recursive: true });
1761
- const densityLevel = options.density || "comfortable";
1812
+ const densityLevel = essence.dna?.spacing?.density || "comfortable";
1762
1813
  const spatialTokens = computeSpatialTokens(densityLevel, themeData?.spatial ? {
1763
1814
  section_padding: themeData.spatial.section_padding ?? void 0,
1764
1815
  density_bias: typeof themeData.spatial.density_bias === "number" ? themeData.spatial.density_bias : void 0,
@@ -1781,11 +1832,45 @@ async function refreshDerivedFiles(projectRoot, essence, registry, prefetchedThe
1781
1832
  writeFileSync(treatmentsPath, treatmentCSS);
1782
1833
  const globalPath = join(stylesDir, "global.css");
1783
1834
  if (!existsSync(globalPath)) {
1784
- writeFileSync(globalPath, generateGlobalCSS(personality));
1835
+ writeFileSync(globalPath, generateGlobalCSS(personality, essence));
1785
1836
  }
1786
1837
  const cssFiles = [tokensPath, treatmentsPath, globalPath];
1838
+ const earlyDecoratorList = [];
1839
+ if (themeData?.decorators) {
1840
+ for (const [name, desc] of Object.entries(themeData.decorators)) {
1841
+ earlyDecoratorList.push({ name, description: desc });
1842
+ }
1843
+ }
1844
+ const allFeatures = [];
1845
+ const sectionSummaries = [];
1846
+ if (essence.blueprint.sections && essence.blueprint.sections.length > 0) {
1847
+ for (const s of essence.blueprint.sections) {
1848
+ sectionSummaries.push({ id: s.id, role: s.role });
1849
+ if (s.features) {
1850
+ for (const f of s.features) {
1851
+ if (!allFeatures.includes(f)) allFeatures.push(f);
1852
+ }
1853
+ }
1854
+ }
1855
+ }
1856
+ if (essence.blueprint.features) {
1857
+ for (const f of essence.blueprint.features) {
1858
+ if (!allFeatures.includes(f)) allFeatures.push(f);
1859
+ }
1860
+ }
1787
1861
  const decantrMdPath = join(projectRoot, "DECANTR.md");
1788
- writeFileSync(decantrMdPath, generateDecantrMdV31(guardMode, CSS_APPROACH_CONTENT));
1862
+ writeFileSync(decantrMdPath, generateDecantrMdV31({
1863
+ guardMode,
1864
+ cssApproach: CSS_APPROACH_CONTENT,
1865
+ blueprintId: essence.meta.blueprint || void 0,
1866
+ themeName,
1867
+ themeMode: mode,
1868
+ themeShape: essence.dna.theme.shape || void 0,
1869
+ personality,
1870
+ sections: sectionSummaries.length > 0 ? sectionSummaries : void 0,
1871
+ features: allFeatures.length > 0 ? allFeatures : void 0,
1872
+ decorators: earlyDecoratorList.length > 0 ? earlyDecoratorList : void 0
1873
+ }));
1789
1874
  const hasSections = essence.blueprint.sections && essence.blueprint.sections.length > 0;
1790
1875
  const contextFiles = [];
1791
1876
  if (!hasSections) {
@@ -1822,8 +1907,7 @@ async function refreshDerivedFiles(projectRoot, essence, registry, prefetchedThe
1822
1907
  try {
1823
1908
  const patResult = await registry.fetchPattern(name);
1824
1909
  if (patResult?.data) {
1825
- const raw = patResult.data;
1826
- const inner = raw.data ?? raw;
1910
+ const inner = patResult.data;
1827
1911
  const defaultPreset = inner.default_preset || "standard";
1828
1912
  const preset = inner.presets?.[defaultPreset];
1829
1913
  let slots = preset?.layout?.slots || {};
@@ -1837,7 +1921,12 @@ async function refreshDerivedFiles(projectRoot, essence, registry, prefetchedThe
1837
1921
  description: inner.description || "",
1838
1922
  components: inner.components || [],
1839
1923
  slots,
1840
- layout_hints: inner.layout_hints
1924
+ layout_hints: inner.layout_hints,
1925
+ visual_brief: inner.visual_brief,
1926
+ composition: inner.composition,
1927
+ motion: inner.motion,
1928
+ responsive: inner.responsive,
1929
+ accessibility: inner.accessibility
1841
1930
  };
1842
1931
  if (!spec.components || spec.components.length === 0) {
1843
1932
  const syntheticComps = generateSyntheticComponents(name, spec.description);
@@ -1890,19 +1979,6 @@ async function refreshDerivedFiles(projectRoot, essence, registry, prefetchedThe
1890
1979
  for (const [name, desc] of Object.entries(themeData.decorators)) {
1891
1980
  decoratorList.push({ name, description: desc });
1892
1981
  }
1893
- } else if (existsSync(decoratorsPath)) {
1894
- const decoratorsCss = readFileSync(decoratorsPath, "utf-8");
1895
- const classRegex = /\/\*\s*(.+?)\s*\*\/\s*\n\s*\.([\w-]+)\s*\{/g;
1896
- let match;
1897
- while ((match = classRegex.exec(decoratorsCss)) !== null) {
1898
- decoratorList.push({ name: match[2], description: match[1] });
1899
- }
1900
- if (decoratorList.length === 0) {
1901
- const simpleClassRegex = /^\.([\w-]+)\s*\{/gm;
1902
- while ((match = simpleClassRegex.exec(decoratorsCss)) !== null) {
1903
- decoratorList.push({ name: match[1], description: "" });
1904
- }
1905
- }
1906
1982
  }
1907
1983
  const shellInfoCache = {};
1908
1984
  const seenShells = /* @__PURE__ */ new Set();
@@ -1913,8 +1989,7 @@ async function refreshDerivedFiles(projectRoot, essence, registry, prefetchedThe
1913
1989
  try {
1914
1990
  const shellResult = await registry.fetchShell(shellId);
1915
1991
  if (shellResult?.data) {
1916
- const raw = shellResult.data;
1917
- const inner = raw.data ?? raw;
1992
+ const inner = shellResult.data;
1918
1993
  shellInfoCache[shellId] = {
1919
1994
  description: inner.description || "",
1920
1995
  regions: inner.config?.regions || [],
@@ -1958,8 +2033,15 @@ async function refreshDerivedFiles(projectRoot, essence, registry, prefetchedThe
1958
2033
  themeName,
1959
2034
  zoneContext,
1960
2035
  patternSpecs: sectionPatterns,
2036
+ themeHints: themeData ? {
2037
+ preferred: themeData.pattern_preferences?.prefer,
2038
+ compositions: themeData.compositions ? Object.entries(themeData.compositions).map(([k, v]) => `**${k}:** ${v.description || v}`).join("\n") : void 0,
2039
+ spatialHints: themeData.spatial ? `Density bias: ${themeData.spatial.density_bias || "none"}. Section padding: ${themeData.spatial.section_padding || "default"}. Card wrapping: ${themeData.spatial.card_wrapping || "default"}.` : void 0
2040
+ } : void 0,
1961
2041
  constraints: essence.dna.constraints,
1962
- shellInfo: shellInfoCache[section.shell]
2042
+ shellInfo: shellInfoCache[section.shell],
2043
+ themeData,
2044
+ themeMode: mode
1963
2045
  });
1964
2046
  const sectionContextPath = join(contextDir, `section-${section.id}.md`);
1965
2047
  writeFileSync(sectionContextPath, contextContent);
@@ -2003,8 +2085,7 @@ async function refreshDerivedFiles(projectRoot, essence, registry, prefetchedThe
2003
2085
  try {
2004
2086
  const patResult = await registry.fetchPattern(name);
2005
2087
  if (patResult?.data) {
2006
- const raw = patResult.data;
2007
- const inner = raw.data ?? raw;
2088
+ const inner = patResult.data;
2008
2089
  const defaultPreset = inner.default_preset || "standard";
2009
2090
  const preset = inner.presets?.[defaultPreset];
2010
2091
  let slots = preset?.layout?.slots || {};
@@ -2049,26 +2130,12 @@ async function refreshDerivedFiles(projectRoot, essence, registry, prefetchedThe
2049
2130
  for (const [name, desc] of Object.entries(themeData.decorators)) {
2050
2131
  decoratorList.push({ name, description: desc });
2051
2132
  }
2052
- } else if (existsSync(decoratorsPath)) {
2053
- const decoratorsCss = readFileSync(decoratorsPath, "utf-8");
2054
- const classRegex = /\/\*\s*(.+?)\s*\*\/\s*\n\s*\.([\w-]+)\s*\{/g;
2055
- let match;
2056
- while ((match = classRegex.exec(decoratorsCss)) !== null) {
2057
- decoratorList.push({ name: match[2], description: match[1] });
2058
- }
2059
- if (decoratorList.length === 0) {
2060
- const simpleClassRegex = /^\.([\w-]+)\s*\{/gm;
2061
- while ((match = simpleClassRegex.exec(decoratorsCss)) !== null) {
2062
- decoratorList.push({ name: match[1], description: "" });
2063
- }
2064
- }
2065
2133
  }
2066
2134
  let v30ShellInfo;
2067
2135
  try {
2068
2136
  const shellResult = await registry.fetchShell(shell);
2069
2137
  if (shellResult?.data) {
2070
- const raw = shellResult.data;
2071
- const inner = raw.data ?? raw;
2138
+ const inner = shellResult.data;
2072
2139
  v30ShellInfo = {
2073
2140
  description: inner.description || "",
2074
2141
  regions: inner.config?.regions || [],
@@ -2087,8 +2154,15 @@ async function refreshDerivedFiles(projectRoot, essence, registry, prefetchedThe
2087
2154
  themeName,
2088
2155
  zoneContext: `This is the primary section (${shell} shell).`,
2089
2156
  patternSpecs,
2157
+ themeHints: themeData ? {
2158
+ preferred: themeData.pattern_preferences?.prefer,
2159
+ compositions: themeData.compositions ? Object.entries(themeData.compositions).map(([k, v]) => `**${k}:** ${v.description || v}`).join("\n") : void 0,
2160
+ spatialHints: themeData.spatial ? `Density bias: ${themeData.spatial.density_bias || "none"}. Section padding: ${themeData.spatial.section_padding || "default"}. Card wrapping: ${themeData.spatial.card_wrapping || "default"}.` : void 0
2161
+ } : void 0,
2090
2162
  constraints: essence.dna.constraints,
2091
- shellInfo: v30ShellInfo
2163
+ shellInfo: v30ShellInfo,
2164
+ themeData,
2165
+ themeMode: mode
2092
2166
  });
2093
2167
  const sectionContextPath = join(contextDir, `section-${syntheticSection.id}.md`);
2094
2168
  writeFileSync(sectionContextPath, contextContent);
@@ -2220,14 +2294,47 @@ function generateSectionContext(input) {
2220
2294
  lines.push("");
2221
2295
  lines.push(`**Guard:** ${guardConfig.mode} mode | DNA violations = ${guardConfig.dna_enforcement} | Blueprint violations = ${guardConfig.blueprint_enforcement}`);
2222
2296
  lines.push("");
2223
- lines.push(`**Theme tokens:** see \`src/styles/tokens.css\` \u2014 use \`var(--d-primary)\`, \`var(--d-bg)\`, etc.`);
2297
+ lines.push("**Key palette tokens:**");
2298
+ lines.push("");
2299
+ lines.push("| Token | Value | Role |");
2300
+ lines.push("|-------|-------|------|");
2301
+ const semanticRoles = {
2302
+ background: "Page canvas / base layer",
2303
+ surface: "Cards, panels, containers",
2304
+ "surface-raised": "Elevated containers, modals, popovers",
2305
+ border: "Dividers, card borders, separators",
2306
+ text: "Body text, headings, primary content",
2307
+ "text-muted": "Secondary text, placeholders, labels",
2308
+ primary: "Brand color, key interactive, selected states",
2309
+ "primary-hover": "Hover state for primary elements"
2310
+ };
2311
+ if (input.themeData?.palette) {
2312
+ const modeKey = input.themeMode || "dark";
2313
+ for (const [name, values] of Object.entries(input.themeData.palette)) {
2314
+ const val = values[modeKey] || values.dark || values.light || Object.values(values)[0];
2315
+ lines.push(`| \`--d-${name}\` | \`${val}\` | ${semanticRoles[name] || ""} |`);
2316
+ }
2317
+ }
2318
+ if (input.themeData?.seed?.accent) {
2319
+ lines.push(`| \`--d-accent\` | \`${input.themeData.seed.accent}\` | CTAs, links, active states, glow effects |`);
2320
+ }
2321
+ lines.push("");
2322
+ lines.push("Full token set: `src/styles/tokens.css`");
2224
2323
  lines.push("");
2225
2324
  lines.push("**Visual Treatments:** All 6 base treatments available (see DECANTR.md for usage).");
2226
2325
  if (decorators.length > 0) {
2227
- const names = decorators.map((d) => d.name).join(", ");
2228
- lines.push(`**Theme decorators:** ${names}`);
2326
+ lines.push("**Theme decorators:**");
2327
+ lines.push("");
2328
+ lines.push("| Class | Usage |");
2329
+ lines.push("|-------|-------|");
2330
+ for (const d of decorators) {
2331
+ lines.push(`| \`.${d.name}\` | ${d.description} |`);
2332
+ }
2333
+ lines.push("");
2334
+ } else {
2335
+ lines.push("**Theme decorators:** None defined for this theme.");
2336
+ lines.push("");
2229
2337
  }
2230
- lines.push("");
2231
2338
  if (themeHints) {
2232
2339
  if (themeHints.preferred && themeHints.preferred.length > 0) {
2233
2340
  lines.push(`**Preferred:** ${themeHints.preferred.join(", ")}`);
@@ -2260,8 +2367,24 @@ function generateSectionContext(input) {
2260
2367
  lines.push("");
2261
2368
  }
2262
2369
  if (personality.length > 0) {
2263
- lines.push("**Personality:** See scaffold.md for personality and visual direction.");
2370
+ const personalityText = personality.join(". ");
2371
+ lines.push("## Visual Direction");
2372
+ lines.push("");
2373
+ lines.push(`**Personality:** ${personalityText}`);
2264
2374
  lines.push("");
2375
+ const pLower = personalityText.toLowerCase();
2376
+ const utils = [];
2377
+ if (pLower.includes("neon") || pLower.includes("glow"))
2378
+ utils.push("`neon-glow`, `neon-glow-hover`, `neon-text-glow`, `neon-border-glow` \u2014 Apply to elements needing accent emphasis");
2379
+ if (pLower.includes("mono") || pLower.includes("monospace"))
2380
+ utils.push("`mono-data` \u2014 Monospace + tabular-nums for metrics, IDs, timestamps");
2381
+ if (pLower.includes("pulse") || pLower.includes("ring") || pLower.includes("status"))
2382
+ utils.push('`status-ring` with `data-status="active|idle|error|processing"` \u2014 Color-coded status with pulse animation');
2383
+ if (utils.length > 0) {
2384
+ lines.push("**Personality utilities available in treatments.css:**");
2385
+ for (const u of utils) lines.push(`- ${u}`);
2386
+ lines.push("");
2387
+ }
2265
2388
  }
2266
2389
  if (constraints && Object.keys(constraints).length > 0) {
2267
2390
  lines.push("## Constraints");
@@ -2290,8 +2413,21 @@ function generateSectionContext(input) {
2290
2413
  lines.push("");
2291
2414
  lines.push(spec.description);
2292
2415
  lines.push("");
2416
+ if (spec.visual_brief) {
2417
+ lines.push(`**Visual brief:** ${spec.visual_brief}`);
2418
+ lines.push("");
2419
+ }
2293
2420
  lines.push(`**Components:** ${spec.components.join(", ")}`);
2294
2421
  lines.push("");
2422
+ if (spec.composition && Object.keys(spec.composition).length > 0) {
2423
+ lines.push("**Composition:**");
2424
+ lines.push("```");
2425
+ for (const [name, expr] of Object.entries(spec.composition)) {
2426
+ lines.push(`${name} = ${expr}`);
2427
+ }
2428
+ lines.push("```");
2429
+ lines.push("");
2430
+ }
2295
2431
  lines.push("**Layout slots:**");
2296
2432
  for (const [slot, desc] of Object.entries(spec.slots)) {
2297
2433
  lines.push(`- \`${slot}\`: ${desc}`);
@@ -2302,6 +2438,38 @@ function generateSectionContext(input) {
2302
2438
  lines.push(` - ${key}: ${value}`);
2303
2439
  }
2304
2440
  }
2441
+ if (spec.motion) {
2442
+ const entries = [];
2443
+ const isObj = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
2444
+ if (isObj(spec.motion.micro)) for (const [k, v] of Object.entries(spec.motion.micro)) entries.push([k, v]);
2445
+ else if (typeof spec.motion.micro === "string") entries.push(["micro", spec.motion.micro]);
2446
+ if (isObj(spec.motion.transitions)) for (const [k, v] of Object.entries(spec.motion.transitions)) entries.push([k, v]);
2447
+ else if (typeof spec.motion.transitions === "string") entries.push(["transitions", spec.motion.transitions]);
2448
+ if (isObj(spec.motion.ambient)) for (const [k, v] of Object.entries(spec.motion.ambient)) entries.push([k, v]);
2449
+ else if (typeof spec.motion.ambient === "string") entries.push(["ambient", spec.motion.ambient]);
2450
+ if (entries.length > 0) {
2451
+ lines.push("**Motion:**");
2452
+ lines.push("| Interaction | Animation |");
2453
+ lines.push("|-------------|-----------|");
2454
+ for (const [k, v] of entries) lines.push(`| ${k} | ${v} |`);
2455
+ lines.push("");
2456
+ }
2457
+ }
2458
+ if (spec.responsive) {
2459
+ lines.push("**Responsive:**");
2460
+ if (spec.responsive.mobile) lines.push(`- **Mobile (<640px):** ${spec.responsive.mobile}`);
2461
+ if (spec.responsive.tablet) lines.push(`- **Tablet (640-1024px):** ${spec.responsive.tablet}`);
2462
+ if (spec.responsive.desktop) lines.push(`- **Desktop (>1024px):** ${spec.responsive.desktop}`);
2463
+ lines.push("");
2464
+ }
2465
+ if (spec.accessibility) {
2466
+ lines.push("**Accessibility:**");
2467
+ if (spec.accessibility.role) lines.push(`- Role: \`${spec.accessibility.role}\``);
2468
+ if (Array.isArray(spec.accessibility.keyboard) && spec.accessibility.keyboard.length) lines.push(`- Keyboard: ${spec.accessibility.keyboard.join("; ")}`);
2469
+ if (Array.isArray(spec.accessibility.announcements) && spec.accessibility.announcements.length) lines.push(`- Announcements: ${spec.accessibility.announcements.join("; ")}`);
2470
+ if (spec.accessibility.focus_management) lines.push(`- Focus: ${spec.accessibility.focus_management}`);
2471
+ lines.push("");
2472
+ }
2305
2473
  lines.push("");
2306
2474
  }
2307
2475
  lines.push("---");
@@ -2340,6 +2508,18 @@ function generateScaffoldContext(input) {
2340
2508
  lines.push(`**Personality:** ${personality.join(", ")}`);
2341
2509
  lines.push("**Guard mode:** creative (no enforcement during initial scaffolding)");
2342
2510
  lines.push("");
2511
+ if (input.voice) {
2512
+ lines.push("## Voice & Copy");
2513
+ lines.push("");
2514
+ if (input.voice.tone) lines.push(`**Tone:** ${input.voice.tone}`);
2515
+ if (input.voice.cta_verbs?.length) lines.push(`**CTA verbs:** ${input.voice.cta_verbs.join(", ")}`);
2516
+ if (input.voice.avoid?.length) lines.push(`**Avoid:** ${input.voice.avoid.join(", ")}`);
2517
+ if (input.voice.empty_states) lines.push(`**Empty states:** ${input.voice.empty_states}`);
2518
+ if (input.voice.errors) lines.push(`**Errors:** ${input.voice.errors}`);
2519
+ if (input.voice.loading) lines.push(`**Loading states:** ${input.voice.loading}`);
2520
+ if (input.voice.metrics_format) lines.push(`**Metrics format:** ${input.voice.metrics_format}`);
2521
+ lines.push("");
2522
+ }
2343
2523
  lines.push("## App Topology");
2344
2524
  lines.push("");
2345
2525
  lines.push(topologyMarkdown);
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- import "./chunk-G77ADKWE.js";
2
- import "./chunk-VKWRNLHR.js";
1
+ import "./chunk-DESYSWLL.js";
2
+ import "./chunk-PMBLHVBX.js";
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  RegistryClient,
3
3
  refreshDerivedFiles
4
- } from "./chunk-VKWRNLHR.js";
4
+ } from "./chunk-PMBLHVBX.js";
5
5
 
6
6
  // src/commands/upgrade.ts
7
7
  import { readFileSync, writeFileSync, existsSync } from "fs";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@decantr/cli",
3
- "version": "1.5.6",
3
+ "version": "1.6.1",
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": {
@@ -22,13 +22,13 @@
22
22
  "publishConfig": {
23
23
  "access": "public"
24
24
  },
25
+ "dependencies": {
26
+ "@decantr/essence-spec": "1.0.0-beta.11",
27
+ "@decantr/registry": "1.0.0-beta.11"
28
+ },
25
29
  "scripts": {
26
30
  "build": "tsup",
27
31
  "test": "vitest run",
28
32
  "test:watch": "vitest"
29
- },
30
- "dependencies": {
31
- "@decantr/essence-spec": "workspace:*",
32
- "@decantr/registry": "workspace:*"
33
33
  }
34
- }
34
+ }