@decantr/cli 1.0.0-beta.7 → 1.0.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.
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env node
2
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
3
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
4
+ }) : x)(function(x) {
5
+ if (typeof require !== "undefined") return require.apply(this, arguments);
6
+ throw Error('Dynamic require of "' + x + '" is not supported');
7
+ });
8
+
9
+ export {
10
+ __require
11
+ };
@@ -0,0 +1,359 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/registry.ts
4
+ import { existsSync, mkdirSync, readFileSync, writeFileSync, readdirSync } from "fs";
5
+ import { join, dirname } from "path";
6
+ import { fileURLToPath } from "url";
7
+ var __dirname = dirname(fileURLToPath(import.meta.url));
8
+ var DEFAULT_API_URL = "https://decantr-registry.fly.dev/v1";
9
+ function getLocalBundledRoot() {
10
+ return join(__dirname, "bundled");
11
+ }
12
+ function loadFromBundledLocal(contentType, id) {
13
+ const bundledRoot = getLocalBundledRoot();
14
+ if (id) {
15
+ const filePath = join(bundledRoot, contentType, `${id}.json`);
16
+ if (existsSync(filePath)) {
17
+ try {
18
+ const data = JSON.parse(readFileSync(filePath, "utf-8"));
19
+ return { data, source: { type: "bundled" } };
20
+ } catch {
21
+ return null;
22
+ }
23
+ }
24
+ return null;
25
+ }
26
+ const dir = join(bundledRoot, contentType);
27
+ if (!existsSync(dir)) return null;
28
+ try {
29
+ const files = readdirSync(dir).filter((f) => f.endsWith(".json"));
30
+ const items = files.map((f) => {
31
+ const content = JSON.parse(readFileSync(join(dir, f), "utf-8"));
32
+ return { id: content.id || f.replace(".json", ""), ...content };
33
+ });
34
+ return {
35
+ data: { items, total: items.length },
36
+ source: { type: "bundled" }
37
+ };
38
+ } catch {
39
+ return null;
40
+ }
41
+ }
42
+ async function fetchWithTimeout(url, timeoutMs = 5e3) {
43
+ const controller = new AbortController();
44
+ const timeout = setTimeout(() => controller.abort(), timeoutMs);
45
+ try {
46
+ const response = await fetch(url, { signal: controller.signal });
47
+ return response;
48
+ } finally {
49
+ clearTimeout(timeout);
50
+ }
51
+ }
52
+ async function tryApi(endpoint, apiUrl = DEFAULT_API_URL) {
53
+ try {
54
+ const url = `${apiUrl}/${endpoint}`;
55
+ const response = await fetchWithTimeout(url);
56
+ if (!response.ok) return null;
57
+ const data = await response.json();
58
+ return {
59
+ data,
60
+ source: { type: "api", url: apiUrl }
61
+ };
62
+ } catch {
63
+ return null;
64
+ }
65
+ }
66
+ function loadFromCache(cacheDir, contentType, id) {
67
+ const cachePath = id ? join(cacheDir, contentType, `${id}.json`) : join(cacheDir, contentType, "index.json");
68
+ if (!existsSync(cachePath)) return null;
69
+ try {
70
+ const data = JSON.parse(readFileSync(cachePath, "utf-8"));
71
+ return {
72
+ data,
73
+ source: { type: "cache" }
74
+ };
75
+ } catch {
76
+ return null;
77
+ }
78
+ }
79
+ function saveToCache(cacheDir, contentType, id, data) {
80
+ const dir = join(cacheDir, contentType);
81
+ mkdirSync(dir, { recursive: true });
82
+ const cachePath = id ? join(dir, `${id}.json`) : join(dir, "index.json");
83
+ writeFileSync(cachePath, JSON.stringify(data, null, 2));
84
+ }
85
+ var RegistryClient = class {
86
+ cacheDir;
87
+ apiUrl;
88
+ offline;
89
+ projectRoot;
90
+ constructor(options = {}) {
91
+ this.projectRoot = options.projectRoot || process.cwd();
92
+ this.cacheDir = options.cacheDir || join(this.projectRoot, ".decantr", "cache");
93
+ this.apiUrl = options.apiUrl || DEFAULT_API_URL;
94
+ this.offline = options.offline || false;
95
+ }
96
+ /**
97
+ * Load content from .decantr/custom/{contentType}/{id}.json
98
+ */
99
+ loadCustomContent(contentType, id) {
100
+ const customPath = join(
101
+ this.projectRoot,
102
+ ".decantr",
103
+ "custom",
104
+ contentType,
105
+ `${id}.json`
106
+ );
107
+ if (!existsSync(customPath)) {
108
+ return null;
109
+ }
110
+ try {
111
+ const data = JSON.parse(readFileSync(customPath, "utf-8"));
112
+ return {
113
+ data,
114
+ source: { type: "custom", path: customPath }
115
+ };
116
+ } catch {
117
+ return null;
118
+ }
119
+ }
120
+ /**
121
+ * Fetch archetypes list.
122
+ */
123
+ async fetchArchetypes() {
124
+ if (!this.offline) {
125
+ const apiResult = await tryApi("archetypes", this.apiUrl);
126
+ if (apiResult) {
127
+ saveToCache(this.cacheDir, "archetypes", null, apiResult.data);
128
+ return apiResult;
129
+ }
130
+ }
131
+ const cacheResult = loadFromCache(
132
+ this.cacheDir,
133
+ "archetypes"
134
+ );
135
+ if (cacheResult) return cacheResult;
136
+ const bundledResult = loadFromBundledLocal("archetypes");
137
+ if (bundledResult) return bundledResult;
138
+ return {
139
+ data: { items: [], total: 0 },
140
+ source: { type: "bundled" }
141
+ };
142
+ }
143
+ /**
144
+ * Fetch a single archetype.
145
+ */
146
+ async fetchArchetype(id) {
147
+ if (!this.offline) {
148
+ const apiResult = await tryApi(`archetypes/${id}`, this.apiUrl);
149
+ if (apiResult) {
150
+ saveToCache(this.cacheDir, "archetypes", id, apiResult.data);
151
+ return apiResult;
152
+ }
153
+ }
154
+ const cacheResult = loadFromCache(this.cacheDir, "archetypes", id);
155
+ if (cacheResult) return cacheResult;
156
+ return loadFromBundledLocal("archetypes", id);
157
+ }
158
+ /**
159
+ * Fetch blueprints list.
160
+ */
161
+ async fetchBlueprints() {
162
+ if (!this.offline) {
163
+ const apiResult = await tryApi("blueprints", this.apiUrl);
164
+ if (apiResult) {
165
+ saveToCache(this.cacheDir, "blueprints", null, apiResult.data);
166
+ return apiResult;
167
+ }
168
+ }
169
+ const cacheResult = loadFromCache(
170
+ this.cacheDir,
171
+ "blueprints"
172
+ );
173
+ if (cacheResult) return cacheResult;
174
+ const bundledResult = loadFromBundledLocal("blueprints");
175
+ if (bundledResult) return bundledResult;
176
+ return {
177
+ data: { items: [], total: 0 },
178
+ source: { type: "bundled" }
179
+ };
180
+ }
181
+ /**
182
+ * Fetch a single blueprint.
183
+ */
184
+ async fetchBlueprint(id) {
185
+ if (!this.offline) {
186
+ const apiResult = await tryApi(`blueprints/${id}`, this.apiUrl);
187
+ if (apiResult) {
188
+ saveToCache(this.cacheDir, "blueprints", id, apiResult.data);
189
+ return apiResult;
190
+ }
191
+ }
192
+ const cacheResult = loadFromCache(this.cacheDir, "blueprints", id);
193
+ if (cacheResult) return cacheResult;
194
+ const bundledResult = loadFromBundledLocal("blueprints", id);
195
+ if (bundledResult) return bundledResult;
196
+ return loadFromBundledLocal("blueprints", id);
197
+ }
198
+ /**
199
+ * Fetch themes list.
200
+ */
201
+ async fetchThemes() {
202
+ if (!this.offline) {
203
+ const apiResult = await tryApi("themes", this.apiUrl);
204
+ if (apiResult) {
205
+ saveToCache(this.cacheDir, "themes", null, apiResult.data);
206
+ return apiResult;
207
+ }
208
+ }
209
+ const cacheResult = loadFromCache(
210
+ this.cacheDir,
211
+ "themes"
212
+ );
213
+ if (cacheResult) return cacheResult;
214
+ const bundledResult = loadFromBundledLocal("themes");
215
+ if (bundledResult) return bundledResult;
216
+ return {
217
+ data: { items: [], total: 0 },
218
+ source: { type: "bundled" }
219
+ };
220
+ }
221
+ /**
222
+ * Fetch a single theme.
223
+ */
224
+ async fetchTheme(id) {
225
+ if (id.startsWith("custom:")) {
226
+ return this.loadCustomContent("themes", id.slice(7));
227
+ }
228
+ if (!this.offline) {
229
+ const apiResult = await tryApi(`themes/${id}`, this.apiUrl);
230
+ if (apiResult) {
231
+ saveToCache(this.cacheDir, "themes", id, apiResult.data);
232
+ return apiResult;
233
+ }
234
+ }
235
+ const cacheResult = loadFromCache(this.cacheDir, "themes", id);
236
+ if (cacheResult) return cacheResult;
237
+ const bundledResult = loadFromBundledLocal("themes", id);
238
+ if (bundledResult) return bundledResult;
239
+ return loadFromBundledLocal("themes", id);
240
+ }
241
+ /**
242
+ * Fetch patterns list.
243
+ */
244
+ async fetchPatterns() {
245
+ if (!this.offline) {
246
+ const apiResult = await tryApi("patterns", this.apiUrl);
247
+ if (apiResult) {
248
+ saveToCache(this.cacheDir, "patterns", null, apiResult.data);
249
+ return apiResult;
250
+ }
251
+ }
252
+ const cacheResult = loadFromCache(
253
+ this.cacheDir,
254
+ "patterns"
255
+ );
256
+ if (cacheResult) return cacheResult;
257
+ const bundledResult = loadFromBundledLocal("patterns");
258
+ if (bundledResult) return bundledResult;
259
+ return {
260
+ data: { items: [], total: 0 },
261
+ source: { type: "bundled" }
262
+ };
263
+ }
264
+ /**
265
+ * Fetch shells list.
266
+ */
267
+ async fetchShells() {
268
+ if (!this.offline) {
269
+ const apiResult = await tryApi("shells", this.apiUrl);
270
+ if (apiResult) {
271
+ saveToCache(this.cacheDir, "shells", null, apiResult.data);
272
+ return apiResult;
273
+ }
274
+ }
275
+ const cacheResult = loadFromCache(
276
+ this.cacheDir,
277
+ "shells"
278
+ );
279
+ if (cacheResult) return cacheResult;
280
+ const bundledResult = loadFromBundledLocal("shells");
281
+ if (bundledResult) return bundledResult;
282
+ const localBundled = loadFromBundledLocal("shells");
283
+ if (localBundled) return localBundled;
284
+ return {
285
+ data: { items: [], total: 0 },
286
+ source: { type: "bundled" }
287
+ };
288
+ }
289
+ /**
290
+ * Fetch a single shell.
291
+ * Note: API only has /shells list endpoint, not /shells/{id}, so we fetch all and filter.
292
+ */
293
+ async fetchShell(id) {
294
+ if (!this.offline) {
295
+ const apiResult = await tryApi("shells", this.apiUrl);
296
+ if (apiResult) {
297
+ const shell = apiResult.data.items.find((s) => s.id === id);
298
+ if (shell) {
299
+ saveToCache(this.cacheDir, "shells", id, shell);
300
+ return { data: shell, source: apiResult.source };
301
+ }
302
+ }
303
+ }
304
+ const cacheResult = loadFromCache(this.cacheDir, "shells", id);
305
+ if (cacheResult) return cacheResult;
306
+ const bundledResult = loadFromBundledLocal("shells", id);
307
+ if (bundledResult) return bundledResult;
308
+ return null;
309
+ }
310
+ /**
311
+ * Check if API is available.
312
+ */
313
+ async checkApiAvailability() {
314
+ if (this.offline) return false;
315
+ try {
316
+ const response = await fetchWithTimeout(`${this.apiUrl.replace("/v1", "")}/health`, 3e3);
317
+ return response.ok;
318
+ } catch {
319
+ return false;
320
+ }
321
+ }
322
+ /**
323
+ * Get the source used for the last fetch.
324
+ */
325
+ getSourceType() {
326
+ return this.offline ? "bundled" : "api";
327
+ }
328
+ };
329
+ async function syncRegistry(cacheDir, apiUrl = DEFAULT_API_URL) {
330
+ const client = new RegistryClient({ cacheDir, apiUrl, offline: false });
331
+ const synced = [];
332
+ const failed = [];
333
+ const apiAvailable = await client.checkApiAvailability();
334
+ if (!apiAvailable) {
335
+ return { synced: [], failed: ["API unavailable"], source: "bundled" };
336
+ }
337
+ const types = ["archetypes", "blueprints", "themes", "patterns", "shells"];
338
+ for (const type of types) {
339
+ try {
340
+ const fetchMethod = `fetch${type.charAt(0).toUpperCase()}${type.slice(1)}`;
341
+ const result = await client[fetchMethod]();
342
+ if (result.source.type === "api") {
343
+ synced.push(type);
344
+ }
345
+ } catch {
346
+ failed.push(type);
347
+ }
348
+ }
349
+ return {
350
+ synced,
351
+ failed,
352
+ source: synced.length > 0 ? "api" : "bundled"
353
+ };
354
+ }
355
+
356
+ export {
357
+ RegistryClient,
358
+ syncRegistry
359
+ };
@@ -0,0 +1,63 @@
1
+ #!/usr/bin/env node
2
+ import "./chunk-PDX44BCA.js";
3
+
4
+ // src/commands/heal.ts
5
+ import { readFileSync, existsSync } from "fs";
6
+ import { join } from "path";
7
+ import { validateEssence, evaluateGuard } from "@decantr/essence-spec";
8
+ var GREEN = "\x1B[32m";
9
+ var RED = "\x1B[31m";
10
+ var YELLOW = "\x1B[33m";
11
+ var RESET = "\x1B[0m";
12
+ var DIM = "\x1B[2m";
13
+ async function cmdHeal(projectRoot = process.cwd()) {
14
+ const essencePath = join(projectRoot, "decantr.essence.json");
15
+ if (!existsSync(essencePath)) {
16
+ console.error("No decantr.essence.json found. Run `decantr init` first.");
17
+ process.exitCode = 1;
18
+ return;
19
+ }
20
+ const essence = JSON.parse(readFileSync(essencePath, "utf-8"));
21
+ console.log("Scanning for issues...\n");
22
+ const issues = [];
23
+ const validation = validateEssence(essence);
24
+ if (!validation.valid) {
25
+ for (const err of validation.errors) {
26
+ issues.push({
27
+ type: "error",
28
+ rule: "schema",
29
+ message: err
30
+ });
31
+ }
32
+ }
33
+ try {
34
+ const violations = evaluateGuard(essence, { themeRegistry: /* @__PURE__ */ new Map(), patternRegistry: /* @__PURE__ */ new Map() });
35
+ for (const v of violations) {
36
+ issues.push({
37
+ type: v.severity === "error" ? "error" : "warning",
38
+ rule: v.rule,
39
+ message: v.message,
40
+ suggestion: v.suggestion
41
+ });
42
+ }
43
+ } catch {
44
+ }
45
+ if (issues.length === 0) {
46
+ console.log(`${GREEN}No issues found. Project is healthy.${RESET}`);
47
+ return;
48
+ }
49
+ console.log(`Found ${issues.length} issue(s):
50
+ `);
51
+ for (const issue of issues) {
52
+ const icon = issue.type === "error" ? `${RED}x${RESET}` : `${YELLOW}!${RESET}`;
53
+ console.log(`${icon} [${issue.rule}] ${issue.message}`);
54
+ if (issue.suggestion) {
55
+ console.log(` ${DIM}Suggestion: ${issue.suggestion}${RESET}`);
56
+ }
57
+ }
58
+ console.log(`
59
+ ${YELLOW}Manual fixes required. Review the issues above.${RESET}`);
60
+ }
61
+ export {
62
+ cmdHeal
63
+ };