@decantr/cli 1.0.0 → 1.1.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/dist/bin.js +4 -0
- package/dist/{chunk-PDX44BCA.js → chunk-3RG5ZIWI.js} +0 -1
- package/dist/chunk-CT5XHG6I.js +203 -0
- package/dist/chunk-LXOY447U.js +2422 -0
- package/dist/{heal-2OPN63OT.js → heal-4DWU7BJS.js} +1 -2
- package/dist/index.js +3 -1965
- package/dist/{upgrade-FWICWIQW.js → upgrade-3YL3NFOG.js} +2 -3
- package/package.json +7 -7
- package/src/templates/DECANTR.md.template +53 -53
- package/LICENSE +0 -21
- package/dist/chunk-PWTUBGGJ.js +0 -359
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decantr/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.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": {
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"homepage": "https://decantr.ai",
|
|
12
12
|
"type": "module",
|
|
13
13
|
"bin": {
|
|
14
|
-
"decantr": "./dist/
|
|
14
|
+
"decantr": "./dist/bin.js"
|
|
15
15
|
},
|
|
16
16
|
"main": "dist/index.js",
|
|
17
17
|
"files": [
|
|
@@ -22,13 +22,13 @@
|
|
|
22
22
|
"publishConfig": {
|
|
23
23
|
"access": "public"
|
|
24
24
|
},
|
|
25
|
-
"dependencies": {
|
|
26
|
-
"@decantr/essence-spec": "1.0.0-beta.3",
|
|
27
|
-
"@decantr/registry": "1.0.0-beta.3"
|
|
28
|
-
},
|
|
29
25
|
"scripts": {
|
|
30
26
|
"build": "tsup",
|
|
31
27
|
"test": "vitest run",
|
|
32
28
|
"test:watch": "vitest"
|
|
29
|
+
},
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"@decantr/essence-spec": "workspace:*",
|
|
32
|
+
"@decantr/registry": "workspace:*"
|
|
33
33
|
}
|
|
34
|
-
}
|
|
34
|
+
}
|
|
@@ -237,6 +237,19 @@ Use the same component variants throughout a pattern. If buttons are `primary` s
|
|
|
237
237
|
|
|
238
238
|
## CSS Implementation
|
|
239
239
|
|
|
240
|
+
This project uses **@decantr/css** for layout atoms and the generated CSS files for theme tokens and recipe decorators.
|
|
241
|
+
|
|
242
|
+
### Setup
|
|
243
|
+
|
|
244
|
+
```javascript
|
|
245
|
+
// 1. Import the atoms runtime
|
|
246
|
+
import { css } from '@decantr/css';
|
|
247
|
+
|
|
248
|
+
// 2. Import generated CSS files (created by decantr init)
|
|
249
|
+
import './styles/tokens.css'; // Theme tokens (--d-primary, --d-surface, etc.)
|
|
250
|
+
import './styles/decorators.css'; // Recipe decorators ({{THEME_RECIPE}}-card, etc.)
|
|
251
|
+
```
|
|
252
|
+
|
|
240
253
|
### HTML Setup
|
|
241
254
|
|
|
242
255
|
Your `index.html` MUST have theme and mode attributes:
|
|
@@ -256,64 +269,51 @@ Your `index.html` MUST have theme and mode attributes:
|
|
|
256
269
|
- `data-mode` enables light/dark toggle
|
|
257
270
|
- `color-scheme` tells the browser to style native controls (scrollbars, inputs) correctly
|
|
258
271
|
|
|
259
|
-
###
|
|
272
|
+
### Using Atoms
|
|
260
273
|
|
|
261
|
-
|
|
274
|
+
The `css()` function processes atom strings and injects CSS at runtime:
|
|
262
275
|
|
|
263
|
-
```
|
|
264
|
-
|
|
276
|
+
```jsx
|
|
277
|
+
// Layout atoms
|
|
278
|
+
<div className={css('_flex _col _gap4 _p4')}>
|
|
279
|
+
<h1 className={css('_heading1')}>Title</h1>
|
|
280
|
+
<p className={css('_textsm _fgmuted')}>Description</p>
|
|
281
|
+
</div>
|
|
265
282
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
283
|
+
// Responsive prefixes (mobile-first)
|
|
284
|
+
<div className={css('_gc1 _sm:gc2 _lg:gc4')}>
|
|
285
|
+
{/* 1 col -> 2 cols at 640px -> 4 cols at 1024px */}
|
|
286
|
+
</div>
|
|
270
287
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
--gap3: 12px;
|
|
277
|
-
--gap4: 16px;
|
|
278
|
-
--gap6: 24px;
|
|
279
|
-
--gap8: 32px;
|
|
280
|
-
--gap12: 48px;
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
/* Theme-specific tokens */
|
|
284
|
-
html[data-theme="{{THEME_STYLE}}"] {
|
|
285
|
-
color-scheme: {{THEME_MODE}};
|
|
286
|
-
|
|
287
|
-
/* Seed colors from theme */
|
|
288
|
-
--d-primary: /* from theme.seed.primary */;
|
|
289
|
-
--d-secondary: /* from theme.seed.secondary */;
|
|
290
|
-
--d-accent: /* from theme.seed.accent */;
|
|
291
|
-
--d-bg: /* from theme.seed.background */;
|
|
292
|
-
|
|
293
|
-
/* Surface stack */
|
|
294
|
-
--d-surface: rgba(255, 255, 255, 0.03);
|
|
295
|
-
--d-surface-raised: rgba(255, 255, 255, 0.06);
|
|
296
|
-
--d-border: rgba(255, 255, 255, 0.1);
|
|
297
|
-
|
|
298
|
-
/* Text hierarchy */
|
|
299
|
-
--d-text: #FFFFFF;
|
|
300
|
-
--d-text-secondary: rgba(255, 255, 255, 0.7);
|
|
301
|
-
--d-text-muted: rgba(255, 255, 255, 0.4);
|
|
302
|
-
}
|
|
303
|
-
}
|
|
288
|
+
// Pseudo-class prefixes
|
|
289
|
+
<button className={css('_bgprimary _h:bgprimary/80')}>
|
|
290
|
+
Hover me
|
|
291
|
+
</button>
|
|
292
|
+
```
|
|
304
293
|
|
|
305
|
-
|
|
306
|
-
/* Recipe-specific classes from {{THEME_RECIPE}} */
|
|
307
|
-
}
|
|
294
|
+
### Common Atoms
|
|
308
295
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
296
|
+
| Category | Atoms | Description |
|
|
297
|
+
|----------|-------|-------------|
|
|
298
|
+
| Display | `_flex`, `_grid`, `_block`, `_none` | Display types |
|
|
299
|
+
| Flexbox | `_col`, `_row`, `_wrap`, `_flex1` | Flex direction/behavior |
|
|
300
|
+
| Alignment | `_aic`, `_jcc`, `_jcsb` | Align/justify content |
|
|
301
|
+
| Spacing | `_gap{n}`, `_p{n}`, `_m{n}`, `_px{n}` | Gap, padding, margin |
|
|
302
|
+
| Sizing | `_wfull`, `_hfull`, `_w{n}`, `_h{n}` | Width, height |
|
|
303
|
+
| Typography | `_textsm`, `_textlg`, `_heading1`-`_heading6` | Font sizes |
|
|
304
|
+
| Colors | `_bgprimary`, `_bgsurface`, `_fgmuted` | Background, foreground |
|
|
313
305
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
306
|
+
### CSS Architecture
|
|
307
|
+
|
|
308
|
+
The CSS is organized into two parts:
|
|
309
|
+
|
|
310
|
+
1. **Atoms (@decantr/css)** - Layout utilities injected at runtime into `@layer d.atoms`
|
|
311
|
+
2. **Generated CSS files** - Theme tokens and recipe decorators created during scaffold
|
|
312
|
+
|
|
313
|
+
```
|
|
314
|
+
src/styles/
|
|
315
|
+
tokens.css # :root { --d-primary: #...; --d-surface: #...; }
|
|
316
|
+
decorators.css # .{{THEME_RECIPE}}-card { ... }
|
|
317
317
|
```
|
|
318
318
|
|
|
319
319
|
### Variable Naming Convention
|
|
@@ -321,8 +321,8 @@ Use `@layer` for proper cascade control:
|
|
|
321
321
|
| Prefix | Purpose | Example |
|
|
322
322
|
|--------|---------|---------|
|
|
323
323
|
| `--d-` | Core Decantr tokens | `--d-primary`, `--d-bg` |
|
|
324
|
-
| `--{
|
|
325
|
-
| `--
|
|
324
|
+
| `--d-gap-{n}` | Spacing tokens | `--d-gap-4`, `--d-gap-8` |
|
|
325
|
+
| `--d-radius` | Border radius | `--d-radius`, `--d-radius-lg` |
|
|
326
326
|
|
|
327
327
|
### Theme Switching (JavaScript)
|
|
328
328
|
|
package/LICENSE
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
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/chunk-PWTUBGGJ.js
DELETED
|
@@ -1,359 +0,0 @@
|
|
|
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
|
-
};
|