@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/chunk-K6MIDPQH.js +205 -0
- package/dist/chunk-PDX44BCA.js +0 -0
- package/dist/heal-2OPN63OT.js +0 -0
- package/dist/index.js +682 -224
- package/dist/{upgrade-FWICWIQW.js → upgrade-KRFCKUMR.js} +1 -1
- package/package.json +6 -6
- package/src/templates/DECANTR.md.template +53 -53
- package/LICENSE +0 -21
- package/dist/chunk-PWTUBGGJ.js +0 -359
package/dist/index.js
CHANGED
|
@@ -2,16 +2,17 @@
|
|
|
2
2
|
import {
|
|
3
3
|
RegistryClient,
|
|
4
4
|
syncRegistry
|
|
5
|
-
} from "./chunk-
|
|
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
|
|
12
|
-
import { join as
|
|
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 {
|
|
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 = "
|
|
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
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
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
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
console.log(
|
|
1129
|
-
|
|
1130
|
-
|
|
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
|
|
1563
|
+
const apiClient = getAPIClient();
|
|
1135
1564
|
const searchType = type || "pattern";
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
}
|
|
1145
|
-
|
|
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
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
console.log(
|
|
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
|
-
|
|
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
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
process.
|
|
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
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
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.
|
|
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
|
|
1211
|
-
const
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
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 =
|
|
1655
|
+
const customThemesDir = join7(customDir, "themes");
|
|
1226
1656
|
try {
|
|
1227
|
-
if (
|
|
1657
|
+
if (existsSync7(customThemesDir)) {
|
|
1228
1658
|
for (const f of readdirSync2(customThemesDir).filter((f2) => f2.endsWith(".json"))) {
|
|
1229
|
-
const data = JSON.parse(
|
|
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
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
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 ||
|
|
1682
|
+
const essencePath = path || join7(process.cwd(), "decantr.essence.json");
|
|
1255
1683
|
let raw;
|
|
1256
1684
|
try {
|
|
1257
|
-
raw =
|
|
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
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
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
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
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
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
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(
|
|
1381
|
-
|
|
1382
|
-
|
|
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
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
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:
|
|
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 = "
|
|
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" : "
|
|
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
|
-
|
|
1493
|
-
|
|
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 =
|
|
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 === "
|
|
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 =
|
|
1556
|
-
const projectJsonPath =
|
|
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 (!
|
|
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(
|
|
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 (
|
|
1974
|
+
if (existsSync7(projectJsonPath)) {
|
|
1581
1975
|
try {
|
|
1582
|
-
const projectJson = JSON.parse(
|
|
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 =
|
|
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.
|
|
1997
|
+
if (result.synced.length > 0) {
|
|
1604
1998
|
console.log(success("Sync completed successfully."));
|
|
1605
|
-
|
|
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
|
-
|
|
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 =
|
|
2012
|
+
const essencePath = join7(projectRoot, "decantr.essence.json");
|
|
1619
2013
|
console.log(heading("Auditing project..."));
|
|
1620
|
-
if (!
|
|
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(
|
|
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 =
|
|
1727
|
-
if (!
|
|
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(
|
|
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-
|
|
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();
|