@decantr/cli 1.0.0-beta.2
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 +225 -0
- package/package.json +30 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
import { readFileSync } from "fs";
|
|
5
|
+
import { join } from "path";
|
|
6
|
+
import { validateEssence, evaluateGuard } from "@decantr/essence-spec";
|
|
7
|
+
import { createResolver, createRegistryClient } from "@decantr/registry";
|
|
8
|
+
var BOLD = "\x1B[1m";
|
|
9
|
+
var DIM = "\x1B[2m";
|
|
10
|
+
var RESET = "\x1B[0m";
|
|
11
|
+
var RED = "\x1B[31m";
|
|
12
|
+
var GREEN = "\x1B[32m";
|
|
13
|
+
var CYAN = "\x1B[36m";
|
|
14
|
+
var YELLOW = "\x1B[33m";
|
|
15
|
+
function heading(text) {
|
|
16
|
+
return `
|
|
17
|
+
${BOLD}${text}${RESET}
|
|
18
|
+
`;
|
|
19
|
+
}
|
|
20
|
+
function success(text) {
|
|
21
|
+
return `${GREEN}${text}${RESET}`;
|
|
22
|
+
}
|
|
23
|
+
function error(text) {
|
|
24
|
+
return `${RED}${text}${RESET}`;
|
|
25
|
+
}
|
|
26
|
+
function dim(text) {
|
|
27
|
+
return `${DIM}${text}${RESET}`;
|
|
28
|
+
}
|
|
29
|
+
function cyan(text) {
|
|
30
|
+
return `${CYAN}${text}${RESET}`;
|
|
31
|
+
}
|
|
32
|
+
function getContentRoot() {
|
|
33
|
+
const bundled = join(import.meta.dirname, "..", "..", "..", "content");
|
|
34
|
+
return process.env.DECANTR_CONTENT_ROOT || bundled;
|
|
35
|
+
}
|
|
36
|
+
function getResolver() {
|
|
37
|
+
return createResolver({ contentRoot: getContentRoot() });
|
|
38
|
+
}
|
|
39
|
+
async function cmdSearch(query, type) {
|
|
40
|
+
const client = createRegistryClient();
|
|
41
|
+
const results = await client.search(query, type);
|
|
42
|
+
if (results.length === 0) {
|
|
43
|
+
console.log(dim(`No results for "${query}"`));
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
console.log(heading(`${results.length} result(s) for "${query}"`));
|
|
47
|
+
for (const r of results) {
|
|
48
|
+
console.log(` ${cyan(r.type.padEnd(12))} ${BOLD}${r.id}${RESET}`);
|
|
49
|
+
console.log(` ${dim(r.description || "")}`);
|
|
50
|
+
console.log("");
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
async function cmdGet(type, id) {
|
|
54
|
+
const validTypes = ["pattern", "archetype", "recipe", "theme", "blueprint"];
|
|
55
|
+
if (!validTypes.includes(type)) {
|
|
56
|
+
console.error(error(`Invalid type "${type}". Must be one of: ${validTypes.join(", ")}`));
|
|
57
|
+
process.exitCode = 1;
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
const resolver = getResolver();
|
|
61
|
+
const result = await resolver.resolve(type, id);
|
|
62
|
+
if (!result) {
|
|
63
|
+
console.error(error(`${type} "${id}" not found.`));
|
|
64
|
+
process.exitCode = 1;
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
console.log(JSON.stringify(result.item, null, 2));
|
|
68
|
+
}
|
|
69
|
+
async function cmdValidate(path) {
|
|
70
|
+
const essencePath = path || join(process.cwd(), "decantr.essence.json");
|
|
71
|
+
let raw;
|
|
72
|
+
try {
|
|
73
|
+
raw = readFileSync(essencePath, "utf-8");
|
|
74
|
+
} catch {
|
|
75
|
+
console.error(error(`Could not read ${essencePath}`));
|
|
76
|
+
process.exitCode = 1;
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
let essence;
|
|
80
|
+
try {
|
|
81
|
+
essence = JSON.parse(raw);
|
|
82
|
+
} catch (e) {
|
|
83
|
+
console.error(error(`Invalid JSON: ${e.message}`));
|
|
84
|
+
process.exitCode = 1;
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
const result = validateEssence(essence);
|
|
88
|
+
if (result.valid) {
|
|
89
|
+
console.log(success("Essence is valid."));
|
|
90
|
+
} else {
|
|
91
|
+
console.error(error("Validation failed:"));
|
|
92
|
+
for (const err of result.errors) {
|
|
93
|
+
console.error(` ${RED}${err}${RESET}`);
|
|
94
|
+
}
|
|
95
|
+
process.exitCode = 1;
|
|
96
|
+
}
|
|
97
|
+
try {
|
|
98
|
+
const violations = evaluateGuard(essence, {});
|
|
99
|
+
if (violations.length > 0) {
|
|
100
|
+
console.log(heading("Guard violations:"));
|
|
101
|
+
for (const v of violations) {
|
|
102
|
+
const vr = v;
|
|
103
|
+
console.log(` ${YELLOW}[${vr.rule}]${RESET} ${vr.message}`);
|
|
104
|
+
}
|
|
105
|
+
} else if (result.valid) {
|
|
106
|
+
console.log(success("No guard violations."));
|
|
107
|
+
}
|
|
108
|
+
} catch {
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
async function cmdList(type) {
|
|
112
|
+
const validTypes = ["patterns", "archetypes", "recipes", "themes", "blueprints"];
|
|
113
|
+
if (!validTypes.includes(type)) {
|
|
114
|
+
console.error(error(`Invalid type "${type}". Must be one of: ${validTypes.join(", ")}`));
|
|
115
|
+
process.exitCode = 1;
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
const { readdirSync } = await import("fs");
|
|
119
|
+
const dir = join(getContentRoot(), type);
|
|
120
|
+
try {
|
|
121
|
+
const files = readdirSync(dir).filter((f) => f.endsWith(".json"));
|
|
122
|
+
console.log(heading(`${files.length} ${type}`));
|
|
123
|
+
for (const f of files) {
|
|
124
|
+
const data = JSON.parse(readFileSync(join(dir, f), "utf-8"));
|
|
125
|
+
console.log(` ${cyan(data.id || f.replace(".json", ""))} ${dim(data.description || data.name || "")}`);
|
|
126
|
+
}
|
|
127
|
+
} catch {
|
|
128
|
+
const coreDir = join(getContentRoot(), "core", type);
|
|
129
|
+
try {
|
|
130
|
+
const files = readdirSync(coreDir).filter((f) => f.endsWith(".json"));
|
|
131
|
+
console.log(heading(`${files.length} ${type} (core)`));
|
|
132
|
+
for (const f of files) {
|
|
133
|
+
const data = JSON.parse(readFileSync(join(coreDir, f), "utf-8"));
|
|
134
|
+
console.log(` ${cyan(data.id || f.replace(".json", ""))} ${dim(data.description || data.name || "")}`);
|
|
135
|
+
}
|
|
136
|
+
} catch {
|
|
137
|
+
console.log(dim(`No ${type} found.`));
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
function cmdHelp() {
|
|
142
|
+
console.log(`
|
|
143
|
+
${BOLD}decantr${RESET} \u2014 Design intelligence for AI-generated UI
|
|
144
|
+
|
|
145
|
+
${BOLD}Usage:${RESET}
|
|
146
|
+
decantr search <query> [--type pattern|archetype|recipe|theme]
|
|
147
|
+
decantr get <type> <id>
|
|
148
|
+
decantr list <type>
|
|
149
|
+
decantr validate [path]
|
|
150
|
+
decantr help
|
|
151
|
+
|
|
152
|
+
${BOLD}Commands:${RESET}
|
|
153
|
+
${cyan("search")} Search the registry for patterns, archetypes, recipes, themes
|
|
154
|
+
${cyan("get")} Get full details of a registry item as JSON
|
|
155
|
+
${cyan("list")} List all items of a type (patterns, archetypes, recipes, themes, blueprints)
|
|
156
|
+
${cyan("validate")} Validate a decantr.essence.json file against the schema and guard rules
|
|
157
|
+
${cyan("help")} Show this help message
|
|
158
|
+
|
|
159
|
+
${BOLD}Examples:${RESET}
|
|
160
|
+
decantr search dashboard
|
|
161
|
+
decantr search kpi --type pattern
|
|
162
|
+
decantr get pattern kpi-grid
|
|
163
|
+
decantr get recipe luminarum
|
|
164
|
+
decantr get theme luminarum
|
|
165
|
+
decantr list patterns
|
|
166
|
+
decantr list themes
|
|
167
|
+
decantr validate
|
|
168
|
+
decantr validate ./my-project/decantr.essence.json
|
|
169
|
+
`);
|
|
170
|
+
}
|
|
171
|
+
async function main() {
|
|
172
|
+
const args = process.argv.slice(2);
|
|
173
|
+
const command = args[0];
|
|
174
|
+
if (!command || command === "help" || command === "--help" || command === "-h") {
|
|
175
|
+
cmdHelp();
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
switch (command) {
|
|
179
|
+
case "search": {
|
|
180
|
+
const query = args[1];
|
|
181
|
+
if (!query) {
|
|
182
|
+
console.error(error("Usage: decantr search <query> [--type <type>]"));
|
|
183
|
+
process.exitCode = 1;
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
const typeIdx = args.indexOf("--type");
|
|
187
|
+
const type = typeIdx !== -1 ? args[typeIdx + 1] : void 0;
|
|
188
|
+
await cmdSearch(query, type);
|
|
189
|
+
break;
|
|
190
|
+
}
|
|
191
|
+
case "get": {
|
|
192
|
+
const type = args[1];
|
|
193
|
+
const id = args[2];
|
|
194
|
+
if (!type || !id) {
|
|
195
|
+
console.error(error("Usage: decantr get <type> <id>"));
|
|
196
|
+
process.exitCode = 1;
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
await cmdGet(type, id);
|
|
200
|
+
break;
|
|
201
|
+
}
|
|
202
|
+
case "list": {
|
|
203
|
+
const type = args[1];
|
|
204
|
+
if (!type) {
|
|
205
|
+
console.error(error("Usage: decantr list <type>"));
|
|
206
|
+
process.exitCode = 1;
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
await cmdList(type);
|
|
210
|
+
break;
|
|
211
|
+
}
|
|
212
|
+
case "validate": {
|
|
213
|
+
await cmdValidate(args[1]);
|
|
214
|
+
break;
|
|
215
|
+
}
|
|
216
|
+
default:
|
|
217
|
+
console.error(error(`Unknown command: ${command}`));
|
|
218
|
+
cmdHelp();
|
|
219
|
+
process.exitCode = 1;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
main().catch((e) => {
|
|
223
|
+
console.error(error(e.message));
|
|
224
|
+
process.exitCode = 1;
|
|
225
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@decantr/cli",
|
|
3
|
+
"version": "1.0.0-beta.2",
|
|
4
|
+
"description": "Decantr CLI — search the registry, validate essence files, and access design intelligence from the terminal",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/decantr-ai/decantr.git",
|
|
9
|
+
"directory": "packages/cli"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://decantr.ai",
|
|
12
|
+
"type": "module",
|
|
13
|
+
"bin": {
|
|
14
|
+
"decantr": "./dist/index.js"
|
|
15
|
+
},
|
|
16
|
+
"main": "dist/index.js",
|
|
17
|
+
"files": ["dist"],
|
|
18
|
+
"publishConfig": {
|
|
19
|
+
"access": "public"
|
|
20
|
+
},
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "tsup",
|
|
23
|
+
"test": "vitest run",
|
|
24
|
+
"test:watch": "vitest"
|
|
25
|
+
},
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"@decantr/essence-spec": "workspace:*",
|
|
28
|
+
"@decantr/registry": "workspace:*"
|
|
29
|
+
}
|
|
30
|
+
}
|