@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.
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  RegistryClient
4
- } from "./chunk-PWTUBGGJ.js";
4
+ } from "./chunk-K6MIDPQH.js";
5
5
  import "./chunk-PDX44BCA.js";
6
6
 
7
7
  // src/commands/upgrade.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@decantr/cli",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "Decantr CLI — search the registry, validate essence files, and access design intelligence from the terminal",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -22,13 +22,13 @@
22
22
  "publishConfig": {
23
23
  "access": "public"
24
24
  },
25
- "dependencies": {
26
- "@decantr/essence-spec": "1.0.0-beta.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
- ### CSS Layer Structure
272
+ ### Using Atoms
260
273
 
261
- Use `@layer` for proper cascade control:
274
+ The `css()` function processes atom strings and injects CSS at runtime:
262
275
 
263
- ```css
264
- @layer decantr.reset, decantr.tokens, decantr.decorators, decantr.patterns, decantr.utilities, app;
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
- @layer decantr.reset {
267
- *, *::before, *::after { box-sizing: border-box; }
268
- body { margin: 0; }
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
- @layer decantr.tokens {
272
- /* Base spacing (theme-agnostic) */
273
- :root {
274
- --gap1: 4px;
275
- --gap2: 8px;
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
- @layer decantr.decorators {
306
- /* Recipe-specific classes from {{THEME_RECIPE}} */
307
- }
294
+ ### Common Atoms
308
295
 
309
- @layer decantr.patterns {
310
- .pattern-hero { }
311
- .pattern-kpi-grid { }
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
- @layer app {
315
- /* Your application overrides */
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
- | `--{recipe}-` | Recipe-specific | `--lum-orb-opacity` |
325
- | `--gap{N}` | Spacing tokens | `--gap4`, `--gap8` |
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.
@@ -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
- };