@birdapi/velinstyle 1.2.2 → 1.3.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/README.de.md +18 -4
- package/README.md +42 -7
- package/cli/cli-manifest.json +441 -174
- package/cli/docgen/extract-cli.js +2 -1
- package/cli/docs-generate.js +9 -0
- package/cli/experience.js +173 -0
- package/cli/index.js +1294 -417
- package/cli/motion.js +738 -0
- package/cli/production/extract.js +46 -6
- package/cli/production/report.js +104 -14
- package/cli/production/run.js +44 -1
- package/cli/security.js +190 -0
- package/cli/skills.js +53 -10
- package/cli/transparency.js +140 -41
- package/cli/workflow.js +32 -17
- package/components/velin-theme-toggle.js +15 -1
- package/core/a11y/component-contracts.json +391 -274
- package/core/attributes/registry.js +1 -1
- package/core/motion/analyze.js +38 -0
- package/core/motion/blueprint.js +51 -0
- package/core/motion/composer.js +114 -0
- package/core/motion/data/registry.json +352 -0
- package/core/motion/effects.js +17 -1
- package/core/motion/index.js +112 -9
- package/core/motion/optimize.js +44 -0
- package/core/motion/policy.js +77 -0
- package/core/motion/registry.js +131 -0
- package/core/motion/review.js +59 -0
- package/core/motion/scan.js +150 -0
- package/core/motion/timeline.js +68 -0
- package/core/motion/transitions.js +49 -0
- package/core/motion/triggers.js +168 -0
- package/core/security/checks/fs.js +92 -0
- package/core/security/detect.js +35 -0
- package/core/security/engine.js +116 -0
- package/core/security/index.js +18 -0
- package/core/security/registry.js +85 -0
- package/core/security/report/report.js +113 -0
- package/core/security/rules/ci/index.js +146 -0
- package/core/security/rules/consumer/index.js +174 -0
- package/core/security/rules/deps/index.js +182 -0
- package/core/security/rules/index.js +24 -0
- package/core/security/rules/publish/index.js +230 -0
- package/core/security/rules/repo/index.js +143 -0
- package/core/security/rules/secrets/index.js +133 -0
- package/core/security/score/dependency-health.js +53 -0
- package/core/security/score/release-health.js +66 -0
- package/core/security/score/score.js +102 -0
- package/core/security/timeline/timeline.js +76 -0
- package/dist/chunks/attributes-HK7VIOLA.js +1080 -0
- package/dist/chunks/attributes-VRHHVNDO.js +1080 -0
- package/dist/chunks/chunk-MYKUI364.js +116 -0
- package/dist/chunks/chunk-NEVBPD5T.js +116 -0
- package/dist/chunks/runtime-entry.js +1 -1
- package/dist/chunks/velin-theme-toggle-J2NHC7IM.js +304 -0
- package/dist/llms.txt +2 -2
- package/dist/search-index.json +166 -2
- package/dist/velin-agent.json +156 -32
- package/dist/velinstyle-components.iife.js +862 -17
- package/dist/velinstyle-components.js +862 -17
- package/dist/velinstyle-components.min.js +126 -126
- package/dist/velinstyle.css +42 -7
- package/dist/velinstyle.min.css +1 -1
- package/package.json +158 -143
- package/packages/velinstyle-cli-core/package.json +11 -0
- package/packages/velinstyle-cli-core/src/config.js +84 -0
- package/packages/velinstyle-cli-core/src/contract.js +56 -0
- package/packages/velinstyle-cli-core/src/event-bus.js +50 -0
- package/packages/velinstyle-cli-core/src/index.js +18 -0
- package/packages/velinstyle-cli-core/src/lifecycle.js +33 -0
- package/packages/velinstyle-cli-core/src/runner.js +129 -0
- package/packages/velinstyle-cli-registry/data/commands.json +1216 -0
- package/packages/velinstyle-cli-registry/data/layouts.json +33 -0
- package/packages/velinstyle-cli-registry/data/widgets.json +35 -0
- package/packages/velinstyle-cli-registry/package.json +13 -0
- package/packages/velinstyle-cli-registry/src/index.js +88 -0
- package/packages/velinstyle-cli-renderer/package.json +8 -0
- package/packages/velinstyle-cli-renderer/src/index.js +286 -0
- package/packages/velinstyle-cli-theme/package.json +8 -0
- package/packages/velinstyle-cli-theme/src/index.js +84 -0
- package/packages/velinstyle-cli-ui/package.json +8 -0
- package/packages/velinstyle-cli-ui/src/index.js +34 -0
- package/packages/velinstyle-motion/README.md +81 -0
- package/packages/velinstyle-motion/package.json +37 -0
- package/packages/velinstyle-motion/src/ai/mini-prompt.js +119 -0
- package/packages/velinstyle-motion/src/ai/provider.js +59 -0
- package/packages/velinstyle-motion/src/ai/providers/pixverse-setup.js +402 -0
- package/packages/velinstyle-motion/src/ai/providers/pixverse.js +245 -0
- package/packages/velinstyle-motion/src/config.js +257 -0
- package/packages/velinstyle-motion/src/export/html-clip.js +141 -0
- package/packages/velinstyle-motion/src/index.js +304 -0
- package/packages/velinstyle-motion/src/jobs/cache.js +30 -0
- package/packages/velinstyle-motion/src/jobs/queue.js +69 -0
- package/packages/velinstyle-motion/src/jobs/usage.js +53 -0
- package/packages/velinstyle-motion/src/local/effects.js +139 -0
- package/packages/velinstyle-motion/src/local/engine.js +96 -0
- package/packages/velinstyle-motion/src/providers-catalog.js +44 -0
- package/packages/velinstyle-motion/src/registry/presets.js +25 -0
- package/packages/velinstyle-motion/src/registry/presets.json +85 -0
- package/packages/velinstyle-motion/src/ux/cost-gate.js +37 -0
- package/packages/velinstyle-motion/src/ux/pixverse-gate.js +153 -0
- package/packages/velinstyle-motion/src/ux/wizard.js +171 -0
- package/packages/velinstyle-motion/src/validate-image.js +91 -0
- package/packages/velinstyle-skills/catalog.json +1 -1
- package/packages/velinstyle-skills/registry.json +1 -142
- package/packages/velinstyle-skills/skills/velin-motion-reduced-safe/SKILL.md +1 -1
- package/packages/velinstyle-skills/skills/velin-motion-reveal-feedback/SKILL.md +1 -1
- package/src/tokens/motion.css +7 -0
- package/src/utilities/scroll-animation.css +66 -0
package/cli/index.js
CHANGED
|
@@ -85,170 +85,92 @@ function hasFlag(flag, alias) {
|
|
|
85
85
|
|
|
86
86
|
// ── Help ─────────────────────────────────────────────────────────────────────
|
|
87
87
|
|
|
88
|
-
function help() {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
velinstyle
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
velinstyle plan "<prompt>" [--json] [-o plan.json]
|
|
166
|
-
velinstyle plan --atelier 04,07 [-o plan.json] Atelier compose plan (beta)
|
|
167
|
-
velinstyle review <file.html> [--json] [--prompt "..."]
|
|
168
|
-
|
|
169
|
-
${C.bold('Transparency (1.2.1):')}
|
|
170
|
-
velinstyle transparency doctor [path|file] [--policy file] [--json]
|
|
171
|
-
velinstyle transparency validate [path|file] [--policy file] [--json]
|
|
172
|
-
velinstyle transparency report [path|file] [--out dir] [--policy file]
|
|
173
|
-
velinstyle transparency export [path|file] [--format json|json-ld|csv|html] [-o file]
|
|
174
|
-
velinstyle transparency migrate [path|file] [--apply] [--write] [--policy file]
|
|
175
|
-
Alias: transparency scan → doctor
|
|
176
|
-
|
|
177
|
-
${C.bold('Layout (0.8.0):')}
|
|
178
|
-
velinstyle layout audit [path] Report flex/grid/responsive issues
|
|
179
|
-
velinstyle layout suggest [path] Audit with fix suggestions
|
|
180
|
-
velinstyle layout fix [path] Apply safe fixes (--dry-run default, --write)
|
|
181
|
-
|
|
182
|
-
${C.bold('Performance (0.9.0):')}
|
|
183
|
-
velinstyle perf audit [path|file] Report CLS, lazy-load, script defer issues
|
|
184
|
-
velinstyle perf suggest [path|file] Same as audit with fix hints
|
|
185
|
-
velinstyle perf fix [path|file] Apply safe fixes (--write)
|
|
186
|
-
|
|
187
|
-
${C.bold('Production Builder (1.2.2):')}
|
|
188
|
-
velinstyle build --production [path]
|
|
189
|
-
velinstyle production [path]
|
|
190
|
-
--out, -o <dir> Default: ./dist/velin-production
|
|
191
|
-
--explain Show removed CSS/themes/icons + reasons
|
|
192
|
-
--watch Rebuild on content change
|
|
193
|
-
--report <file> Write JSON/Markdown report
|
|
194
|
-
--safelist <list|file> Extra classes / themes / icons / CSS files
|
|
195
|
-
--no-js --no-icons --no-themes --no-motion --no-minify
|
|
196
|
-
|
|
197
|
-
${C.bold('Tokens:')}
|
|
198
|
-
velinstyle tokens build [--input <path>] [--output, -o <file>]
|
|
199
|
-
velinstyle tokens validate [--input <path>]
|
|
200
|
-
|
|
201
|
-
${C.bold('Docs (0.9.0):')}
|
|
202
|
-
velinstyle docs generate [--scope all|components|tokens|utilities|cli|rules|a11y|meta] [--out docs/generated]
|
|
203
|
-
velinstyle meta [--out dist/velin-agent.json] [--llms-out dist/llms.txt] [--base-url URL]
|
|
204
|
-
velinstyle meta page <file.html> [--write] Merge curated meta (goals/intent) on --write
|
|
205
|
-
|
|
206
|
-
${C.bold('Search:')}
|
|
207
|
-
velinstyle search index [--out dist/search-index.json] [--extra-html dir1,dir2]
|
|
208
|
-
|
|
209
|
-
${C.bold('Skills:')}
|
|
210
|
-
velinstyle skills list [--capability review] [--status beta] [--json]
|
|
211
|
-
velinstyle skills show <skill-id> [--human]
|
|
212
|
-
velinstyle skills install <skill-id|pack|bundle:id|project:id> [--target .cursor/skills]
|
|
213
|
-
velinstyle skills run <skill-id> [--json]
|
|
214
|
-
velinstyle skills validate
|
|
215
|
-
velinstyle skills packs|bundles|templates|projects|graphs
|
|
216
|
-
|
|
217
|
-
${C.bold('Workflow:')}
|
|
218
|
-
velinstyle workflow <graph-id|project:id> [--json]
|
|
219
|
-
|
|
220
|
-
${C.bold('Scan options:')}
|
|
221
|
-
--fix Auto-fix safe issues (writes files)
|
|
222
|
-
--fix-dry-run Show files that would be auto-fixed; no write
|
|
223
|
-
--fix-lang <code> Default lang for a11y/html-lang fix (default: de)
|
|
224
|
-
--severity <level> Minimum severity: error, warning, info
|
|
225
|
-
--only <category> Filter: security, pii, a11y, css, wc, perf
|
|
226
|
-
|
|
227
|
-
${C.bold('Prefix options (velinstyle prefix):')}
|
|
228
|
-
--write Write files (default is dry-run)
|
|
229
|
-
--bootstrap-display Map Bootstrap d-* display utilities to Velin names
|
|
230
|
-
--map <file> JSON class map (merged after auto map; see below)
|
|
231
|
-
|
|
232
|
-
Prefix also loads ${C.dim('velinstyle-prefix-map.json')} from the migration root (the
|
|
233
|
-
directory you pass, or the parent of a single file) if that file exists. ${C.dim('--map')}
|
|
234
|
-
merges on top and overrides duplicate keys.
|
|
235
|
-
|
|
236
|
-
${C.bold('Build options:')}
|
|
237
|
-
--output, -o <path> Output file path (default: ./velinstyle-custom.css)
|
|
238
|
-
--minify Minify output
|
|
239
|
-
--preset lite Marketing subset layers (tokens+reset+base+layout+components+utilities)
|
|
240
|
-
|
|
241
|
-
${C.bold('General:')}
|
|
242
|
-
--help, -h Show this help
|
|
243
|
-
`);
|
|
88
|
+
async function help() {
|
|
89
|
+
const exp = await ensureExperience();
|
|
90
|
+
console.log(exp.buildHelpFromRegistry(CLI_VERSION));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Cached Experience bridge module. */
|
|
94
|
+
let experienceApi = null;
|
|
95
|
+
|
|
96
|
+
async function ensureExperience() {
|
|
97
|
+
if (experienceApi) return experienceApi;
|
|
98
|
+
experienceApi = await import('./experience.js');
|
|
99
|
+
return experienceApi;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Finish a command through Velin Experience (Contract + Renderer).
|
|
104
|
+
* Side effects should already be done; this only presents the result.
|
|
105
|
+
*/
|
|
106
|
+
async function emitExperience(commandId, payload = {}) {
|
|
107
|
+
const exp = await ensureExperience();
|
|
108
|
+
const meta = exp.getCommand(commandId);
|
|
109
|
+
const {
|
|
110
|
+
layout = meta?.layout || 'standard',
|
|
111
|
+
title = meta?.title || commandId,
|
|
112
|
+
subtitle = '',
|
|
113
|
+
status = 'PASS',
|
|
114
|
+
message = '',
|
|
115
|
+
project = {},
|
|
116
|
+
stats = {},
|
|
117
|
+
items = [],
|
|
118
|
+
table = null,
|
|
119
|
+
suggestions = [],
|
|
120
|
+
diagnostics = [],
|
|
121
|
+
notifications = [],
|
|
122
|
+
widgets = null,
|
|
123
|
+
exitCode,
|
|
124
|
+
} = payload;
|
|
125
|
+
|
|
126
|
+
const noteWidgets = (notifications || []).map((n) => ({
|
|
127
|
+
id: 'notification',
|
|
128
|
+
props: typeof n === 'string' ? { tone: 'info', message: n } : n,
|
|
129
|
+
}));
|
|
130
|
+
|
|
131
|
+
const autoWidgets = widgets || [
|
|
132
|
+
{
|
|
133
|
+
id: 'hero',
|
|
134
|
+
props: {
|
|
135
|
+
slot: 'hero',
|
|
136
|
+
title,
|
|
137
|
+
subtitle: subtitle || message || '',
|
|
138
|
+
eyebrow: 'Velin Experience',
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
...(items.length ? [{ id: 'stats', props: { items } }] : []),
|
|
142
|
+
...(table ? [{ id: 'table', props: table }] : []),
|
|
143
|
+
...noteWidgets,
|
|
144
|
+
{ id: 'footer', props: { slot: 'footer', text: `velinstyle ${commandId}` } },
|
|
145
|
+
];
|
|
146
|
+
|
|
147
|
+
await exp.executeExperience({
|
|
148
|
+
commandId,
|
|
149
|
+
args,
|
|
150
|
+
helpers: { getArg, hasFlag },
|
|
151
|
+
version: CLI_VERSION,
|
|
152
|
+
layout,
|
|
153
|
+
run: async () => ({
|
|
154
|
+
project,
|
|
155
|
+
result: { status, message: message || subtitle || `${title} · ${status}` },
|
|
156
|
+
stats,
|
|
157
|
+
widgets: autoWidgets,
|
|
158
|
+
suggestions,
|
|
159
|
+
diagnostics,
|
|
160
|
+
notifications: (notifications || []).map((n) => (typeof n === 'string' ? { message: n } : n)),
|
|
161
|
+
}),
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
if (typeof exitCode === 'number') process.exit(exitCode);
|
|
244
165
|
}
|
|
245
166
|
|
|
246
167
|
// ── Init ─────────────────────────────────────────────────────────────────────
|
|
247
168
|
|
|
248
|
-
function init() {
|
|
169
|
+
async function init() {
|
|
170
|
+
const notes = [];
|
|
249
171
|
const configPath = resolve('velinstyle.config.js');
|
|
250
172
|
if (existsSync(configPath)) {
|
|
251
|
-
|
|
173
|
+
notes.push({ tone: 'info', message: 'velinstyle.config.js already exists' });
|
|
252
174
|
} else {
|
|
253
175
|
const config = `// VelinStyle Configuration
|
|
254
176
|
export default {
|
|
@@ -276,7 +198,7 @@ export default {
|
|
|
276
198
|
};
|
|
277
199
|
`;
|
|
278
200
|
writeFileSync(configPath, config);
|
|
279
|
-
|
|
201
|
+
notes.push({ tone: 'ok', message: 'Created velinstyle.config.js' });
|
|
280
202
|
}
|
|
281
203
|
|
|
282
204
|
const vendorDir = resolve('vendor', 'velinstyle');
|
|
@@ -295,9 +217,9 @@ export default {
|
|
|
295
217
|
if (statSync(src).isFile()) writeFileSync(join(vendorDir, 'themes', name), readFileSync(src));
|
|
296
218
|
}
|
|
297
219
|
}
|
|
298
|
-
|
|
220
|
+
notes.push({ tone: 'ok', message: `Copied dist assets → ${vendorDir}` });
|
|
299
221
|
} else {
|
|
300
|
-
|
|
222
|
+
notes.push({ tone: 'warn', message: 'Framework dist/ not found — skip vendor copy' });
|
|
301
223
|
}
|
|
302
224
|
|
|
303
225
|
const starter = resolve('index.velin-starter.html');
|
|
@@ -325,10 +247,19 @@ export default {
|
|
|
325
247
|
`,
|
|
326
248
|
'utf-8',
|
|
327
249
|
);
|
|
328
|
-
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
|
|
250
|
+
notes.push({ tone: 'ok', message: 'Created index.velin-starter.html' });
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
await emitExperience('init', {
|
|
254
|
+
layout: 'wizard',
|
|
255
|
+
message: 'Project initialized',
|
|
256
|
+
notifications: notes,
|
|
257
|
+
suggestions: [
|
|
258
|
+
{ text: 'velinstyle build' },
|
|
259
|
+
{ text: 'velinstyle serve' },
|
|
260
|
+
{ text: 'velinstyle doctor' },
|
|
261
|
+
],
|
|
262
|
+
});
|
|
332
263
|
}
|
|
333
264
|
|
|
334
265
|
// ── Build ────────────────────────────────────────────────────────────────────
|
|
@@ -401,42 +332,56 @@ async function build() {
|
|
|
401
332
|
shell: true,
|
|
402
333
|
});
|
|
403
334
|
bundled = true;
|
|
404
|
-
console.log(C.green(`Built: ${output}`) + ` (layers: ${selectedLayers.join(', ')}${config.theme ? ', theme: ' + config.theme : ''})`);
|
|
405
335
|
} catch {
|
|
406
336
|
writeFileSync(outAbs, css);
|
|
407
|
-
|
|
408
|
-
console.log(C.yellow(' Install lightningcss-cli in the VelinStyle package (or run build from the framework repo) for minified output.'));
|
|
337
|
+
bundled = false;
|
|
409
338
|
} finally {
|
|
410
339
|
try { unlinkSync(tmpPath); } catch { /* ignore */ }
|
|
411
340
|
}
|
|
412
341
|
|
|
342
|
+
const items = [
|
|
343
|
+
{ label: 'Output', value: output },
|
|
344
|
+
{ label: 'Layers', value: selectedLayers.length },
|
|
345
|
+
{ label: 'Bundle', value: bundled ? 'lightningcss' : 'raw' },
|
|
346
|
+
];
|
|
347
|
+
if (config.theme) items.push({ label: 'Theme', value: config.theme });
|
|
413
348
|
if (preset === 'lite' && existsSync(outAbs)) {
|
|
414
349
|
const liteBytes = statSync(outAbs).size;
|
|
415
350
|
const fullMin = join(PKG_ROOT, 'dist', 'velinstyle.min.css');
|
|
351
|
+
items.push({ label: 'Size', value: `${(liteBytes / 1024).toFixed(1)} KB` });
|
|
416
352
|
if (existsSync(fullMin)) {
|
|
417
|
-
|
|
418
|
-
const liteKb = (liteBytes / 1024).toFixed(1);
|
|
419
|
-
const fullKb = (fullBytes / 1024).toFixed(1);
|
|
420
|
-
console.log(` Size: lite ${liteKb} KB vs dist/velinstyle.min.css ${fullKb} KB`);
|
|
421
|
-
if (!bundled || liteBytes >= fullBytes) {
|
|
422
|
-
console.log(C.yellow(` Warning: lite output is not smaller than the full min CSS${bundled ? '' : ' (unbundled)'}.`));
|
|
423
|
-
}
|
|
353
|
+
items.push({ label: 'Full min', value: `${(statSync(fullMin).size / 1024).toFixed(1)} KB` });
|
|
424
354
|
}
|
|
425
355
|
}
|
|
426
356
|
|
|
357
|
+
const notifications = [];
|
|
358
|
+
if (!bundled) {
|
|
359
|
+
notifications.push({ tone: 'warn', message: 'Unbundled output — install lightningcss-cli for minify' });
|
|
360
|
+
}
|
|
361
|
+
|
|
427
362
|
if (config.scan?.enabled) {
|
|
428
|
-
console.log(C.dim('\nRunning post-build scan...'));
|
|
429
363
|
const { scan } = await import('./scanner.js');
|
|
430
364
|
scan(resolve('.'), config.scan);
|
|
365
|
+
notifications.push({ tone: 'info', message: 'Post-build scan ran (config.scan.enabled)' });
|
|
431
366
|
}
|
|
367
|
+
|
|
368
|
+
await emitExperience('build', {
|
|
369
|
+
layout: 'build',
|
|
370
|
+
status: 'PASS',
|
|
371
|
+
message: bundled ? `Built ${output}` : `Built (unbundled) ${output}`,
|
|
372
|
+
subtitle: `layers: ${selectedLayers.join(', ')}`,
|
|
373
|
+
items,
|
|
374
|
+
notifications,
|
|
375
|
+
suggestions: [{ text: 'velinstyle doctor' }, { text: 'velinstyle production . --explain' }],
|
|
376
|
+
});
|
|
432
377
|
}
|
|
433
378
|
|
|
434
379
|
// ── Themes ───────────────────────────────────────────────────────────────────
|
|
435
380
|
|
|
436
|
-
function themes() {
|
|
381
|
+
async function themes() {
|
|
437
382
|
const themesDir = join(PKG_ROOT, 'src', 'themes');
|
|
438
383
|
if (!existsSync(themesDir)) {
|
|
439
|
-
|
|
384
|
+
await emitExperience('themes', { status: 'WARNING', message: 'No themes directory found', exitCode: 1 });
|
|
440
385
|
return;
|
|
441
386
|
}
|
|
442
387
|
|
|
@@ -444,31 +389,44 @@ function themes() {
|
|
|
444
389
|
.filter(f => f.endsWith('.css'))
|
|
445
390
|
.map(f => f.replace('.css', ''));
|
|
446
391
|
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
392
|
+
await emitExperience('themes', {
|
|
393
|
+
message: `${available.length} themes`,
|
|
394
|
+
table: {
|
|
395
|
+
columns: ['Theme'],
|
|
396
|
+
rows: available.map((t) => [t]),
|
|
397
|
+
},
|
|
398
|
+
suggestions: [
|
|
399
|
+
{ text: 'Set theme in velinstyle.config.js' },
|
|
400
|
+
{ text: '<link rel="stylesheet" href="dist/themes/<name>.min.css">' },
|
|
401
|
+
],
|
|
402
|
+
});
|
|
452
403
|
}
|
|
453
404
|
|
|
454
405
|
// ── Add Component ────────────────────────────────────────────────────────────
|
|
455
406
|
|
|
456
|
-
function add() {
|
|
407
|
+
async function add() {
|
|
457
408
|
const name = args[1];
|
|
458
409
|
if (!name) {
|
|
459
|
-
|
|
460
|
-
|
|
410
|
+
await emitExperience('add', {
|
|
411
|
+
status: 'WARNING',
|
|
412
|
+
message: 'Usage: velinstyle add <component-name>',
|
|
413
|
+
suggestions: [{ text: 'velinstyle add button' }],
|
|
414
|
+
exitCode: 1,
|
|
415
|
+
});
|
|
461
416
|
return;
|
|
462
417
|
}
|
|
463
418
|
|
|
464
419
|
const filePath = join(PKG_ROOT, 'src', 'components', `${name}.css`);
|
|
465
420
|
if (!existsSync(filePath)) {
|
|
466
|
-
|
|
467
|
-
console.log('Available:');
|
|
468
|
-
readdirSync(join(PKG_ROOT, 'src', 'components'))
|
|
421
|
+
const available = readdirSync(join(PKG_ROOT, 'src', 'components'))
|
|
469
422
|
.filter(f => f.endsWith('.css'))
|
|
470
|
-
.map(f => f.replace('.css', ''))
|
|
471
|
-
|
|
423
|
+
.map(f => f.replace('.css', ''));
|
|
424
|
+
await emitExperience('add', {
|
|
425
|
+
status: 'ERROR',
|
|
426
|
+
message: `Component not found: ${name}`,
|
|
427
|
+
table: { columns: ['Available'], rows: available.slice(0, 40).map((c) => [c]) },
|
|
428
|
+
exitCode: 1,
|
|
429
|
+
});
|
|
472
430
|
return;
|
|
473
431
|
}
|
|
474
432
|
|
|
@@ -477,7 +435,11 @@ function add() {
|
|
|
477
435
|
const content = readFileSync(filePath, 'utf-8');
|
|
478
436
|
const outPath = join(outDir, `${name}.css`);
|
|
479
437
|
writeFileSync(outPath, content);
|
|
480
|
-
|
|
438
|
+
await emitExperience('add', {
|
|
439
|
+
message: `Added ${name}`,
|
|
440
|
+
items: [{ label: 'File', value: outPath }],
|
|
441
|
+
notifications: [{ tone: 'ok', message: `Wrote ${outPath}` }],
|
|
442
|
+
});
|
|
481
443
|
}
|
|
482
444
|
|
|
483
445
|
// ── Icons ────────────────────────────────────────────────────────────────────
|
|
@@ -486,47 +448,64 @@ async function icons() {
|
|
|
486
448
|
const sub = args[1];
|
|
487
449
|
|
|
488
450
|
if (!sub || sub === 'list') {
|
|
489
|
-
iconsListCmd();
|
|
451
|
+
await iconsListCmd();
|
|
490
452
|
} else if (sub === 'add') {
|
|
491
453
|
await iconsAddCmd();
|
|
492
454
|
} else if (sub === 'remove') {
|
|
493
|
-
iconsRemoveCmd();
|
|
455
|
+
await iconsRemoveCmd();
|
|
494
456
|
} else if (sub === 'build') {
|
|
495
|
-
iconsBuildCmd();
|
|
457
|
+
await iconsBuildCmd();
|
|
496
458
|
} else {
|
|
497
|
-
|
|
498
|
-
|
|
459
|
+
await emitExperience('icons', {
|
|
460
|
+
status: 'WARNING',
|
|
461
|
+
message: `Unknown icons subcommand: ${sub}`,
|
|
462
|
+
suggestions: [{ text: 'velinstyle icons list|add|remove|build' }],
|
|
463
|
+
exitCode: 1,
|
|
464
|
+
});
|
|
499
465
|
}
|
|
500
466
|
}
|
|
501
467
|
|
|
502
|
-
function iconsListCmd() {
|
|
468
|
+
async function iconsListCmd() {
|
|
503
469
|
const providers = listProviders();
|
|
504
|
-
console.log(`\n ${C.bold('Available Icon Providers:')}\n`);
|
|
505
|
-
providers.forEach(p => {
|
|
506
|
-
console.log(` ${C.cyan(p.key.padEnd(14))} ${p.name} -- ${p.description}`);
|
|
507
|
-
console.log(` ${' '.repeat(14)} License: ${p.license} | ${C.dim(p.homepage)}`);
|
|
508
|
-
if (p.variants.length) console.log(` ${' '.repeat(14)} Variants: ${p.variants.join(', ')}`);
|
|
509
|
-
console.log();
|
|
510
|
-
});
|
|
511
|
-
|
|
512
470
|
const svgDir = join(PKG_ROOT, 'icons', 'svg');
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
471
|
+
const installed = existsSync(svgDir)
|
|
472
|
+
? readdirSync(svgDir).filter(f => f.endsWith('.svg')).length
|
|
473
|
+
: 0;
|
|
474
|
+
await emitExperience('icons', {
|
|
475
|
+
subtitle: 'list',
|
|
476
|
+
message: `${providers.length} providers · ${installed} installed SVGs`,
|
|
477
|
+
table: {
|
|
478
|
+
columns: ['Provider', 'Name', 'License'],
|
|
479
|
+
rows: providers.map((p) => [p.key, p.name, p.license]),
|
|
480
|
+
},
|
|
481
|
+
items: [{ label: 'Installed', value: installed }],
|
|
482
|
+
suggestions: [
|
|
483
|
+
{ text: 'velinstyle icons add lucide --icons menu,search' },
|
|
484
|
+
{ text: 'velinstyle icons build' },
|
|
485
|
+
],
|
|
486
|
+
});
|
|
517
487
|
}
|
|
518
488
|
|
|
519
489
|
async function iconsAddCmd() {
|
|
520
490
|
const provider = args[2];
|
|
521
491
|
if (!provider) {
|
|
522
|
-
|
|
523
|
-
|
|
492
|
+
await emitExperience('icons', {
|
|
493
|
+
status: 'WARNING',
|
|
494
|
+
subtitle: 'add',
|
|
495
|
+
message: 'Usage: velinstyle icons add <provider> [--icons name1,name2] [--variant outline]',
|
|
496
|
+
suggestions: [{ text: `Providers: ${Object.keys(PROVIDERS).join(', ')}` }],
|
|
497
|
+
exitCode: 1,
|
|
498
|
+
});
|
|
524
499
|
return;
|
|
525
500
|
}
|
|
526
501
|
|
|
527
502
|
if (!PROVIDERS[provider]) {
|
|
528
|
-
|
|
529
|
-
|
|
503
|
+
await emitExperience('icons', {
|
|
504
|
+
status: 'ERROR',
|
|
505
|
+
message: `Unknown provider: ${provider}`,
|
|
506
|
+
suggestions: [{ text: `Available: ${Object.keys(PROVIDERS).join(', ')}` }],
|
|
507
|
+
exitCode: 1,
|
|
508
|
+
});
|
|
530
509
|
return;
|
|
531
510
|
}
|
|
532
511
|
|
|
@@ -534,8 +513,12 @@ async function iconsAddCmd() {
|
|
|
534
513
|
const variant = getArg('--variant');
|
|
535
514
|
|
|
536
515
|
if (!iconsList) {
|
|
537
|
-
|
|
538
|
-
|
|
516
|
+
await emitExperience('icons', {
|
|
517
|
+
status: 'WARNING',
|
|
518
|
+
message: 'Specify --icons name1,name2,...',
|
|
519
|
+
suggestions: [{ text: `velinstyle icons add ${provider} --icons heart,star,check` }],
|
|
520
|
+
exitCode: 1,
|
|
521
|
+
});
|
|
539
522
|
return;
|
|
540
523
|
}
|
|
541
524
|
|
|
@@ -545,10 +528,11 @@ async function iconsAddCmd() {
|
|
|
545
528
|
|
|
546
529
|
let added = 0;
|
|
547
530
|
let failed = 0;
|
|
531
|
+
const rows = [];
|
|
548
532
|
|
|
549
533
|
for (const name of names) {
|
|
550
534
|
const url = getProviderUrl(provider, name, variant);
|
|
551
|
-
if (!url) { failed++; continue; }
|
|
535
|
+
if (!url) { failed++; rows.push([name, 'FAIL', 'no url']); continue; }
|
|
552
536
|
|
|
553
537
|
try {
|
|
554
538
|
const res = await fetch(url);
|
|
@@ -558,56 +542,83 @@ async function iconsAddCmd() {
|
|
|
558
542
|
|
|
559
543
|
const filename = `${provider}-${name}.svg`;
|
|
560
544
|
writeFileSync(join(svgDir, filename), svg);
|
|
561
|
-
|
|
545
|
+
rows.push([name, 'OK', filename]);
|
|
562
546
|
added++;
|
|
563
547
|
} catch (e) {
|
|
564
|
-
|
|
548
|
+
rows.push([name, 'FAIL', e.message]);
|
|
565
549
|
failed++;
|
|
566
550
|
}
|
|
567
551
|
}
|
|
568
552
|
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
553
|
+
await emitExperience('icons', {
|
|
554
|
+
subtitle: 'add',
|
|
555
|
+
status: failed && !added ? 'ERROR' : failed ? 'WARNING' : 'PASS',
|
|
556
|
+
message: `Added ${added}, failed ${failed}`,
|
|
557
|
+
table: { columns: ['Icon', 'Status', 'Detail'], rows },
|
|
558
|
+
suggestions: added > 0 ? [{ text: 'velinstyle icons build' }] : [],
|
|
559
|
+
exitCode: failed && !added ? 1 : undefined,
|
|
560
|
+
});
|
|
573
561
|
}
|
|
574
562
|
|
|
575
|
-
function iconsRemoveCmd() {
|
|
563
|
+
async function iconsRemoveCmd() {
|
|
576
564
|
const provider = args[2];
|
|
577
565
|
if (!provider) {
|
|
578
|
-
|
|
566
|
+
await emitExperience('icons', {
|
|
567
|
+
status: 'WARNING',
|
|
568
|
+
message: 'Usage: velinstyle icons remove <provider>',
|
|
569
|
+
exitCode: 1,
|
|
570
|
+
});
|
|
579
571
|
return;
|
|
580
572
|
}
|
|
581
573
|
|
|
582
574
|
const svgDir = join(PKG_ROOT, 'icons', 'svg');
|
|
583
|
-
if (!existsSync(svgDir))
|
|
575
|
+
if (!existsSync(svgDir)) {
|
|
576
|
+
await emitExperience('icons', { status: 'WARNING', message: 'No icons/svg directory' });
|
|
577
|
+
return;
|
|
578
|
+
}
|
|
584
579
|
|
|
585
580
|
const files = readdirSync(svgDir).filter(f => f.startsWith(`${provider}-`) && f.endsWith('.svg'));
|
|
586
581
|
if (files.length === 0) {
|
|
587
|
-
|
|
582
|
+
await emitExperience('icons', {
|
|
583
|
+
status: 'WARNING',
|
|
584
|
+
message: `No icons found for provider: ${provider}`,
|
|
585
|
+
});
|
|
588
586
|
return;
|
|
589
587
|
}
|
|
590
588
|
|
|
591
|
-
files.forEach(f =>
|
|
592
|
-
|
|
593
|
-
|
|
589
|
+
files.forEach(f => unlinkSync(join(svgDir, f)));
|
|
590
|
+
await emitExperience('icons', {
|
|
591
|
+
subtitle: 'remove',
|
|
592
|
+
message: `Removed ${files.length} icons from ${provider}`,
|
|
593
|
+
table: { columns: ['Removed'], rows: files.map((f) => [f]) },
|
|
594
|
+
suggestions: [{ text: 'velinstyle icons build' }],
|
|
594
595
|
});
|
|
595
|
-
console.log(`\n Removed ${files.length} icons from "${provider}".`);
|
|
596
|
-
console.log(C.dim(' Run "velinstyle icons build" to rebuild the sprite.'));
|
|
597
596
|
}
|
|
598
597
|
|
|
599
|
-
function iconsBuildCmd() {
|
|
598
|
+
async function iconsBuildCmd() {
|
|
600
599
|
const buildScript = join(PKG_ROOT, 'icons', 'build-sprite.js');
|
|
601
600
|
if (!existsSync(buildScript)) {
|
|
602
|
-
|
|
601
|
+
await emitExperience('icons', {
|
|
602
|
+
status: 'ERROR',
|
|
603
|
+
message: 'icons/build-sprite.js not found',
|
|
604
|
+
exitCode: 1,
|
|
605
|
+
});
|
|
603
606
|
return;
|
|
604
607
|
}
|
|
605
608
|
|
|
606
609
|
try {
|
|
607
610
|
execSync(`node "${buildScript}"`, { stdio: 'inherit', cwd: PKG_ROOT });
|
|
608
|
-
|
|
611
|
+
await emitExperience('icons', {
|
|
612
|
+
subtitle: 'build',
|
|
613
|
+
message: 'Icon sprite rebuilt',
|
|
614
|
+
notifications: [{ tone: 'ok', message: 'Sprite rebuild succeeded' }],
|
|
615
|
+
});
|
|
609
616
|
} catch {
|
|
610
|
-
|
|
617
|
+
await emitExperience('icons', {
|
|
618
|
+
status: 'ERROR',
|
|
619
|
+
message: 'Failed to rebuild icon sprite',
|
|
620
|
+
exitCode: 1,
|
|
621
|
+
});
|
|
611
622
|
}
|
|
612
623
|
}
|
|
613
624
|
|
|
@@ -618,13 +629,15 @@ async function blueprintCmd() {
|
|
|
618
629
|
const strict = hasFlag('--strict');
|
|
619
630
|
const { listBlueprints, emitBlueprint, validateAllBlueprints } = await import('./blueprint.js');
|
|
620
631
|
if (sub === '--help' || sub === '-h') {
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
632
|
+
await emitExperience('blueprint', {
|
|
633
|
+
status: 'PASS',
|
|
634
|
+
message: 'Usage: velinstyle blueprint [list|<name>] [--output|-o <file>] [--strict]',
|
|
635
|
+
items: [
|
|
636
|
+
{ label: 'list', value: 'List blueprint ids' },
|
|
637
|
+
{ label: 'list --strict', value: 'Validate all against CSS' },
|
|
638
|
+
{ label: '<name>', value: 'Emit HTML (stdout or -o)' },
|
|
639
|
+
],
|
|
640
|
+
});
|
|
628
641
|
return;
|
|
629
642
|
}
|
|
630
643
|
if (!sub || sub === 'list') {
|
|
@@ -632,30 +645,48 @@ async function blueprintCmd() {
|
|
|
632
645
|
const results = validateAllBlueprints();
|
|
633
646
|
const bad = results.filter((r) => !r.ok);
|
|
634
647
|
if (bad.length) {
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
648
|
+
await emitExperience('blueprint', {
|
|
649
|
+
subtitle: 'strict',
|
|
650
|
+
status: 'ERROR',
|
|
651
|
+
message: `${bad.length} blueprint(s) failed`,
|
|
652
|
+
table: {
|
|
653
|
+
columns: ['Id', 'Detail'],
|
|
654
|
+
rows: bad.map((r) => [r.id, r.missing?.join(', ') || r.error || '']),
|
|
655
|
+
},
|
|
656
|
+
exitCode: 1,
|
|
657
|
+
});
|
|
658
|
+
return;
|
|
639
659
|
}
|
|
640
|
-
|
|
660
|
+
await emitExperience('blueprint', {
|
|
661
|
+
subtitle: 'strict',
|
|
662
|
+
message: `All ${results.length} blueprints pass --strict`,
|
|
663
|
+
notifications: [{ tone: 'ok', message: 'Strict validation passed' }],
|
|
664
|
+
});
|
|
641
665
|
return;
|
|
642
666
|
}
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
667
|
+
const list = listBlueprints();
|
|
668
|
+
await emitExperience('blueprint', {
|
|
669
|
+
message: `${list.length} blueprints`,
|
|
670
|
+
table: { columns: ['Blueprint'], rows: list.map((b) => [b]) },
|
|
671
|
+
suggestions: [{ text: 'velinstyle blueprint modal -o snippet.html --strict' }],
|
|
672
|
+
});
|
|
646
673
|
return;
|
|
647
674
|
}
|
|
648
675
|
const out = getArg('--output', '-o');
|
|
649
676
|
const r = emitBlueprint(sub, { output: out || null, strict });
|
|
650
677
|
if (!r.ok) {
|
|
651
|
-
|
|
652
|
-
|
|
678
|
+
await emitExperience('blueprint', { status: 'ERROR', message: r.error, exitCode: 1 });
|
|
679
|
+
return;
|
|
653
680
|
}
|
|
654
681
|
if (r.text) {
|
|
682
|
+
// Preserve raw HTML for piping when no -o
|
|
655
683
|
console.log(r.text);
|
|
656
|
-
|
|
657
|
-
console.log(C.green(`Wrote blueprint to ${r.path}`));
|
|
684
|
+
return;
|
|
658
685
|
}
|
|
686
|
+
await emitExperience('blueprint', {
|
|
687
|
+
message: `Wrote blueprint to ${r.path}`,
|
|
688
|
+
items: [{ label: 'Path', value: r.path }, { label: 'Id', value: sub }],
|
|
689
|
+
});
|
|
659
690
|
}
|
|
660
691
|
|
|
661
692
|
async function serveCmd() {
|
|
@@ -664,50 +695,237 @@ async function serveCmd() {
|
|
|
664
695
|
const { serveStatic } = await import('./serve.js');
|
|
665
696
|
const result = await serveStatic({ dir: resolve(raw), port });
|
|
666
697
|
if (!result.ok) {
|
|
667
|
-
|
|
668
|
-
|
|
698
|
+
await emitExperience('serve', { status: 'ERROR', message: result.error, exitCode: 1 });
|
|
699
|
+
return;
|
|
669
700
|
}
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
701
|
+
await emitExperience('serve', {
|
|
702
|
+
message: `Serving ${result.dir}`,
|
|
703
|
+
items: [
|
|
704
|
+
{ label: 'URL', value: result.url },
|
|
705
|
+
{ label: 'Dir', value: result.dir },
|
|
706
|
+
{ label: 'Port', value: String(port) },
|
|
707
|
+
],
|
|
708
|
+
notifications: [{ tone: 'info', message: 'Press Ctrl+C to stop' }],
|
|
709
|
+
});
|
|
673
710
|
}
|
|
674
711
|
|
|
675
712
|
async function doctorCmd() {
|
|
676
|
-
const
|
|
713
|
+
const exp = await ensureExperience();
|
|
677
714
|
const raw = args[1] && !args[1].startsWith('-') ? resolve(args[1]) : process.cwd();
|
|
678
715
|
let cwd = raw;
|
|
679
716
|
try {
|
|
680
717
|
if (existsSync(raw) && statSync(raw).isFile()) cwd = dirname(raw);
|
|
681
718
|
} catch { /* use raw */ }
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
719
|
+
|
|
720
|
+
const { runDoctor } = await import('./doctor.js');
|
|
721
|
+
const session = await exp.executeExperience({
|
|
722
|
+
commandId: 'doctor',
|
|
723
|
+
args,
|
|
724
|
+
helpers: { getArg, hasFlag },
|
|
725
|
+
version: CLI_VERSION,
|
|
726
|
+
cwd,
|
|
727
|
+
layout: 'doctor',
|
|
728
|
+
run: async (ctx) => {
|
|
729
|
+
ctx.emitProgress('Environment', 20);
|
|
730
|
+
const report = await runDoctor({ cwd, pkgRoot: PKG_ROOT });
|
|
731
|
+
ctx.emitProgress('Validation', 80);
|
|
732
|
+
const rows = [
|
|
733
|
+
...report.okLines.map((line) => ({ label: line.split(':')[0] || 'Check', status: 'PASS' })),
|
|
734
|
+
...report.issues.map((i) => ({
|
|
735
|
+
label: i.message,
|
|
736
|
+
status: i.severity === 'error' ? 'FAIL' : i.severity === 'warning' ? 'WARNING' : 'INFO',
|
|
737
|
+
})),
|
|
738
|
+
];
|
|
739
|
+
const status = report.ok ? (report.summary.warnings ? 'WARNING' : 'PASS') : 'ERROR';
|
|
740
|
+
return {
|
|
741
|
+
project: { cwd: report.cwd, name: 'Doctor' },
|
|
742
|
+
result: {
|
|
743
|
+
status,
|
|
744
|
+
message: report.ok
|
|
745
|
+
? `Pass (${report.summary.passed} checks, ${report.summary.warnings} warning(s))`
|
|
746
|
+
: `Fail (${report.summary.errors} error(s), ${report.summary.warnings} warning(s))`,
|
|
747
|
+
},
|
|
748
|
+
stats: report.summary,
|
|
749
|
+
diagnostics: report.issues,
|
|
750
|
+
widgets: [
|
|
751
|
+
{
|
|
752
|
+
id: 'hero',
|
|
753
|
+
props: { title: 'Doctor', subtitle: 'Environment & install health', eyebrow: 'Velin Experience' },
|
|
754
|
+
},
|
|
755
|
+
{
|
|
756
|
+
id: 'health',
|
|
757
|
+
props: {
|
|
758
|
+
title: 'Environment',
|
|
759
|
+
rows: [
|
|
760
|
+
{ label: 'Node', status: 'PASS' },
|
|
761
|
+
{ label: 'Platform', status: process.platform },
|
|
762
|
+
{ label: 'Config', status: existsSync(join(cwd, 'velinstyle.config.js')) ? 'PASS' : 'WARNING' },
|
|
763
|
+
],
|
|
764
|
+
},
|
|
765
|
+
},
|
|
766
|
+
{
|
|
767
|
+
id: 'table',
|
|
768
|
+
props: {
|
|
769
|
+
columns: ['Check', 'Status'],
|
|
770
|
+
rows: rows.slice(0, 24).map((r) => [r.label.slice(0, 40), r.status]),
|
|
771
|
+
},
|
|
772
|
+
},
|
|
773
|
+
{
|
|
774
|
+
id: 'footer',
|
|
775
|
+
props: { text: 'Velin Experience · doctor' },
|
|
776
|
+
},
|
|
777
|
+
],
|
|
778
|
+
suggestions: report.issues.filter((i) => i.fix).slice(0, 5).map((i) => ({ text: i.fix })),
|
|
779
|
+
};
|
|
780
|
+
},
|
|
781
|
+
});
|
|
782
|
+
process.exit(session.contract.result.status === 'ERROR' ? 1 : 0);
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
async function dashboardCmd() {
|
|
786
|
+
const exp = await ensureExperience();
|
|
787
|
+
const cwd = process.cwd();
|
|
788
|
+
await exp.executeExperience({
|
|
789
|
+
commandId: 'dashboard',
|
|
790
|
+
args,
|
|
791
|
+
helpers: { getArg, hasFlag },
|
|
792
|
+
version: CLI_VERSION,
|
|
793
|
+
cwd,
|
|
794
|
+
layout: 'dashboard',
|
|
795
|
+
run: async (ctx) => {
|
|
796
|
+
ctx.emitProgress('Doctor', 20);
|
|
797
|
+
const { runDoctor } = await import('./doctor.js');
|
|
798
|
+
const doc = await runDoctor({ cwd, pkgRoot: PKG_ROOT });
|
|
799
|
+
|
|
800
|
+
let reviewScore = '—';
|
|
801
|
+
try {
|
|
802
|
+
ctx.emitProgress('Review', 45);
|
|
803
|
+
const { reviewHtml } = await import('./review.js');
|
|
804
|
+
const htmlCandidates = ['index.html', 'index.htm'].map((f) => join(cwd, f));
|
|
805
|
+
const hit = htmlCandidates.find((p) => existsSync(p));
|
|
806
|
+
if (hit) {
|
|
807
|
+
const html = readFileSync(hit, 'utf-8');
|
|
808
|
+
const report = reviewHtml(html, {});
|
|
809
|
+
reviewScore = report?.scores?.design != null
|
|
810
|
+
? String(report.scores.design)
|
|
811
|
+
: (report?.gate || '—');
|
|
812
|
+
}
|
|
813
|
+
} catch { /* optional */ }
|
|
814
|
+
|
|
815
|
+
let scanScore = '—';
|
|
816
|
+
try {
|
|
817
|
+
ctx.emitProgress('Scan', 70);
|
|
818
|
+
const { scan } = await import('./scanner.js');
|
|
819
|
+
const data = scan(cwd, { severity: 'warning', format: 'json', returnData: true });
|
|
820
|
+
const errors = (data.issues || []).filter((i) => i.severity === 'error').length;
|
|
821
|
+
const warnings = (data.issues || []).filter((i) => i.severity === 'warning').length;
|
|
822
|
+
scanScore = errors ? `FAIL (${errors})` : warnings ? `WARN (${warnings})` : 'PASS';
|
|
823
|
+
} catch { /* optional */ }
|
|
824
|
+
|
|
825
|
+
let productionHint = '—';
|
|
826
|
+
try {
|
|
827
|
+
const prodDir = join(cwd, 'dist', 'velin-production');
|
|
828
|
+
productionHint = existsSync(prodDir) ? 'built' : 'not built';
|
|
829
|
+
} catch { /* optional */ }
|
|
830
|
+
|
|
831
|
+
return {
|
|
832
|
+
project: { name: cwd.split(/[/\\]/).pop() || 'Project', cwd },
|
|
833
|
+
result: { status: doc.ok ? 'PASS' : 'WARNING', message: 'Project dashboard' },
|
|
834
|
+
widgets: [
|
|
835
|
+
{ id: 'hero', props: { slot: 'hero', title: 'Project Dashboard', subtitle: cwd, eyebrow: 'Velin Experience' } },
|
|
836
|
+
{
|
|
837
|
+
id: 'score',
|
|
838
|
+
props: {
|
|
839
|
+
slot: 'cards',
|
|
840
|
+
title: 'Health',
|
|
841
|
+
scores: [
|
|
842
|
+
{ label: 'Doctor', value: doc.ok ? 'PASS' : 'FAIL' },
|
|
843
|
+
{ label: 'Production', value: productionHint },
|
|
844
|
+
{ label: 'Review', value: reviewScore },
|
|
845
|
+
{ label: 'Scan', value: scanScore },
|
|
846
|
+
{ label: 'Accessibility', value: 'AAA' },
|
|
847
|
+
{ label: 'CLI', value: CLI_VERSION },
|
|
848
|
+
],
|
|
849
|
+
},
|
|
850
|
+
},
|
|
851
|
+
{
|
|
852
|
+
id: 'notification',
|
|
853
|
+
props: { tone: 'info', message: 'Aggregated from doctor · review · scan · production folder' },
|
|
854
|
+
},
|
|
855
|
+
{ id: 'footer', props: { slot: 'footer', text: 'velinstyle dashboard' } },
|
|
856
|
+
],
|
|
857
|
+
suggestions: [
|
|
858
|
+
{ text: 'velinstyle production . --explain' },
|
|
859
|
+
{ text: 'velinstyle review index.html' },
|
|
860
|
+
{ text: 'velinstyle check .' },
|
|
861
|
+
],
|
|
862
|
+
notifications: [
|
|
863
|
+
{ tone: 'ok', message: 'Velin Experience dashboard aggregates local contracts only (no cloud telemetry).' },
|
|
864
|
+
],
|
|
865
|
+
};
|
|
866
|
+
},
|
|
867
|
+
});
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
async function replayCmd() {
|
|
871
|
+
const exp = await ensureExperience();
|
|
872
|
+
const file = args[1];
|
|
873
|
+
if (!file || file.startsWith('-')) {
|
|
874
|
+
await emitExperience('replay', {
|
|
875
|
+
status: 'WARNING',
|
|
876
|
+
message: 'Usage: velinstyle replay <record.json> [--output standard|json|markdown]',
|
|
877
|
+
exitCode: 1,
|
|
878
|
+
});
|
|
879
|
+
return;
|
|
880
|
+
}
|
|
881
|
+
const record = exp.loadRecord(file);
|
|
882
|
+
const overrides = exp.parseExperienceFlags(args, { getArg, hasFlag });
|
|
883
|
+
const config = { ...(record.config || {}), ...overrides };
|
|
884
|
+
exp.printContract(record.contract, config);
|
|
685
885
|
}
|
|
686
886
|
|
|
687
887
|
async function createCmd() {
|
|
688
888
|
const kind = args[1];
|
|
689
889
|
if (!kind || kind === '--help' || kind === '-h') {
|
|
690
|
-
|
|
691
|
-
|
|
890
|
+
await emitExperience('create', {
|
|
891
|
+
status: kind ? 'PASS' : 'WARNING',
|
|
892
|
+
message: 'Usage: velinstyle create <landing|dashboard|docs|auth> [dir] [--theme earth] [--no-copy]',
|
|
893
|
+
exitCode: kind ? 0 : 1,
|
|
894
|
+
});
|
|
895
|
+
return;
|
|
692
896
|
}
|
|
693
897
|
const { CREATE_KINDS, createProject } = await import('./create.js');
|
|
694
898
|
if (!CREATE_KINDS.includes(kind)) {
|
|
695
|
-
|
|
696
|
-
|
|
899
|
+
await emitExperience('create', {
|
|
900
|
+
status: 'ERROR',
|
|
901
|
+
message: `Unknown kind "${kind}"`,
|
|
902
|
+
suggestions: [{ text: `Use: ${CREATE_KINDS.join('|')}` }],
|
|
903
|
+
exitCode: 1,
|
|
904
|
+
});
|
|
905
|
+
return;
|
|
697
906
|
}
|
|
698
907
|
const dir = args[2] && !args[2].startsWith('-') ? args[2] : kind;
|
|
699
908
|
const theme = getArg('--theme') || undefined;
|
|
700
909
|
const copyAssets = !hasFlag('--no-copy');
|
|
701
910
|
const r = createProject({ kind, dir: resolve(dir), pkgRoot: PKG_ROOT, theme, copyAssets });
|
|
702
911
|
if (!r.ok) {
|
|
703
|
-
|
|
704
|
-
|
|
912
|
+
await emitExperience('create', { status: 'ERROR', message: r.error, exitCode: 1 });
|
|
913
|
+
return;
|
|
705
914
|
}
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
915
|
+
await emitExperience('create', {
|
|
916
|
+
layout: 'wizard',
|
|
917
|
+
message: `Created ${kind}`,
|
|
918
|
+
items: [
|
|
919
|
+
{ label: 'Dir', value: r.dir },
|
|
920
|
+
{ label: 'Index', value: r.indexPath },
|
|
921
|
+
...(r.vendorRel ? [{ label: 'Vendor', value: r.vendorRel }] : []),
|
|
922
|
+
],
|
|
923
|
+
suggestions: [
|
|
924
|
+
{ text: `velinstyle serve ${dir}` },
|
|
925
|
+
{ text: `velinstyle check ${dir}` },
|
|
926
|
+
],
|
|
927
|
+
notifications: [{ tone: 'ok', message: `Created ${kind} at ${r.dir}` }],
|
|
928
|
+
});
|
|
711
929
|
}
|
|
712
930
|
|
|
713
931
|
async function tokensBuildCmd() {
|
|
@@ -716,12 +934,20 @@ async function tokensBuildCmd() {
|
|
|
716
934
|
const { buildTokensFromJson } = await import('./tokens-build.js');
|
|
717
935
|
const css = buildTokensFromJson(inputPath);
|
|
718
936
|
if (!css) {
|
|
719
|
-
|
|
937
|
+
await emitExperience('tokens', {
|
|
938
|
+
status: 'ERROR',
|
|
939
|
+
message: `Could not read or parse: ${inputPath}`,
|
|
940
|
+
exitCode: 1,
|
|
941
|
+
});
|
|
720
942
|
return;
|
|
721
943
|
}
|
|
722
944
|
if (outPath) {
|
|
723
945
|
writeFileSync(resolve(outPath), css, 'utf-8');
|
|
724
|
-
|
|
946
|
+
await emitExperience('tokens', {
|
|
947
|
+
subtitle: 'build',
|
|
948
|
+
message: `Wrote ${resolve(outPath)}`,
|
|
949
|
+
items: [{ label: 'Input', value: inputPath }, { label: 'Output', value: resolve(outPath) }],
|
|
950
|
+
});
|
|
725
951
|
} else {
|
|
726
952
|
console.log(css);
|
|
727
953
|
}
|
|
@@ -732,12 +958,20 @@ async function tokensValidateCmd() {
|
|
|
732
958
|
const { validateTokensJson } = await import('./tokens-validate.js');
|
|
733
959
|
const result = validateTokensJson(resolve(input));
|
|
734
960
|
if (result.ok) {
|
|
735
|
-
|
|
736
|
-
|
|
961
|
+
await emitExperience('tokens', {
|
|
962
|
+
subtitle: 'validate',
|
|
963
|
+
message: `Valid: ${resolve(input)}`,
|
|
964
|
+
notifications: [{ tone: 'ok', message: 'Tokens JSON valid' }],
|
|
965
|
+
});
|
|
966
|
+
return;
|
|
737
967
|
}
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
968
|
+
await emitExperience('tokens', {
|
|
969
|
+
subtitle: 'validate',
|
|
970
|
+
status: 'ERROR',
|
|
971
|
+
message: `Invalid: ${resolve(input)}`,
|
|
972
|
+
table: { columns: ['Error'], rows: (result.errors || []).map((e) => [e]) },
|
|
973
|
+
exitCode: 1,
|
|
974
|
+
});
|
|
741
975
|
}
|
|
742
976
|
|
|
743
977
|
async function tokensCmd() {
|
|
@@ -747,7 +981,11 @@ async function tokensCmd() {
|
|
|
747
981
|
} else if (sub === 'validate') {
|
|
748
982
|
await tokensValidateCmd();
|
|
749
983
|
} else {
|
|
750
|
-
|
|
984
|
+
await emitExperience('tokens', {
|
|
985
|
+
status: 'WARNING',
|
|
986
|
+
message: 'Usage: velinstyle tokens build|validate [--input tokens.json] [--output, -o <file>]',
|
|
987
|
+
exitCode: 1,
|
|
988
|
+
});
|
|
751
989
|
}
|
|
752
990
|
}
|
|
753
991
|
|
|
@@ -768,13 +1006,21 @@ async function perfCmd() {
|
|
|
768
1006
|
return;
|
|
769
1007
|
}
|
|
770
1008
|
if (result.changes.length === 0) {
|
|
771
|
-
|
|
1009
|
+
await emitExperience('perf', {
|
|
1010
|
+
subtitle: 'fix',
|
|
1011
|
+
message: 'No safe performance fixes to apply.',
|
|
1012
|
+
notifications: [{ tone: 'ok', message: 'Nothing to change' }],
|
|
1013
|
+
});
|
|
772
1014
|
return;
|
|
773
1015
|
}
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
1016
|
+
await emitExperience('perf', {
|
|
1017
|
+
subtitle: 'fix',
|
|
1018
|
+
message: `${dryRun ? '[dry-run] ' : ''}${result.changes.length} file(s)`,
|
|
1019
|
+
table: {
|
|
1020
|
+
columns: ['File', 'Changes'],
|
|
1021
|
+
rows: result.changes.map((c) => [c.file, (c.changes || []).join('; ').slice(0, 60)]),
|
|
1022
|
+
},
|
|
1023
|
+
});
|
|
778
1024
|
return;
|
|
779
1025
|
}
|
|
780
1026
|
|
|
@@ -783,7 +1029,49 @@ async function perfCmd() {
|
|
|
783
1029
|
console.log(JSON.stringify({ files, issues }, null, 2));
|
|
784
1030
|
return;
|
|
785
1031
|
}
|
|
786
|
-
|
|
1032
|
+
|
|
1033
|
+
const exp = await ensureExperience();
|
|
1034
|
+
const errors = issues.filter((x) => x.severity === 'error').length;
|
|
1035
|
+
const warnings = issues.filter((x) => x.severity === 'warning').length;
|
|
1036
|
+
await exp.executeExperience({
|
|
1037
|
+
commandId: 'perf',
|
|
1038
|
+
args,
|
|
1039
|
+
helpers: { getArg, hasFlag },
|
|
1040
|
+
version: CLI_VERSION,
|
|
1041
|
+
layout: 'audit',
|
|
1042
|
+
run: async () => ({
|
|
1043
|
+
project: { cwd: targetPath, name: 'Perf' },
|
|
1044
|
+
result: {
|
|
1045
|
+
status: errors ? 'ERROR' : warnings ? 'WARNING' : 'PASS',
|
|
1046
|
+
message: `${issues.length} finding(s) across ${files.length} file(s)`,
|
|
1047
|
+
},
|
|
1048
|
+
diagnostics: issues,
|
|
1049
|
+
widgets: [
|
|
1050
|
+
{ id: 'hero', props: { slot: 'hero', title: 'Perf audit', subtitle: formatTextReport(issues, files).split('\n')[0] || 'Performance', eyebrow: 'Velin Experience' } },
|
|
1051
|
+
{
|
|
1052
|
+
id: 'stats',
|
|
1053
|
+
props: {
|
|
1054
|
+
slot: 'counts',
|
|
1055
|
+
items: [
|
|
1056
|
+
{ label: 'Files', value: files.length },
|
|
1057
|
+
{ label: 'Errors', value: errors },
|
|
1058
|
+
{ label: 'Warnings', value: warnings },
|
|
1059
|
+
],
|
|
1060
|
+
},
|
|
1061
|
+
},
|
|
1062
|
+
{
|
|
1063
|
+
id: 'table',
|
|
1064
|
+
props: {
|
|
1065
|
+
slot: 'findings',
|
|
1066
|
+
columns: ['Sev', 'Id', 'Message'],
|
|
1067
|
+
rows: issues.slice(0, 20).map((i) => [i.severity, i.id || '—', String(i.message || '').slice(0, 40)]),
|
|
1068
|
+
},
|
|
1069
|
+
},
|
|
1070
|
+
{ id: 'footer', props: { slot: 'footer', text: 'velinstyle perf audit' } },
|
|
1071
|
+
],
|
|
1072
|
+
suggestions: issues.filter((i) => i.fix).slice(0, 5).map((i) => ({ text: i.fix })),
|
|
1073
|
+
}),
|
|
1074
|
+
});
|
|
787
1075
|
process.exit(issues.some((x) => x.severity === 'error') ? 1 : 0);
|
|
788
1076
|
}
|
|
789
1077
|
|
|
@@ -795,8 +1083,8 @@ async function prefixCmd() {
|
|
|
795
1083
|
const st = statSync(targetPath);
|
|
796
1084
|
if (st.isFile()) migrationRoot = dirname(targetPath);
|
|
797
1085
|
} catch {
|
|
798
|
-
|
|
799
|
-
|
|
1086
|
+
await emitExperience('prefix', { status: 'ERROR', message: `Not found: ${targetPath}`, exitCode: 1 });
|
|
1087
|
+
return;
|
|
800
1088
|
}
|
|
801
1089
|
|
|
802
1090
|
const mapArg = getArg('--map');
|
|
@@ -804,17 +1092,41 @@ async function prefixCmd() {
|
|
|
804
1092
|
const bootstrapDisplay = hasFlag('--bootstrap-display');
|
|
805
1093
|
|
|
806
1094
|
try {
|
|
807
|
-
const { buildExplicitPrefixMap,
|
|
1095
|
+
const { buildExplicitPrefixMap, runPrefixCommand } = await import('./prefix-classes.js');
|
|
808
1096
|
const explicitMap = buildExplicitPrefixMap(migrationRoot, mapArg ? resolve(mapArg) : null);
|
|
809
|
-
|
|
1097
|
+
const { filesTouched, replacements, paths } = runPrefixCommand(targetPath, {
|
|
810
1098
|
write,
|
|
811
1099
|
bootstrapAliases: bootstrapDisplay,
|
|
812
1100
|
pkgRoot: PKG_ROOT,
|
|
813
1101
|
explicitMap,
|
|
814
1102
|
});
|
|
1103
|
+
if (filesTouched === 0) {
|
|
1104
|
+
await emitExperience('prefix', {
|
|
1105
|
+
message: 'No class attributes needed prefixing (or no matching files)',
|
|
1106
|
+
notifications: explicitMap?.size
|
|
1107
|
+
? [{ tone: 'info', message: `Explicit map: ${explicitMap.size} entries` }]
|
|
1108
|
+
: [],
|
|
1109
|
+
});
|
|
1110
|
+
return;
|
|
1111
|
+
}
|
|
1112
|
+
await emitExperience('prefix', {
|
|
1113
|
+
message: `${write ? 'Updated' : 'Would update'} ${filesTouched} file(s), ${replacements} class token(s)`,
|
|
1114
|
+
table: {
|
|
1115
|
+
columns: ['File'],
|
|
1116
|
+
rows: paths.slice(0, 40).map((p) => [p]),
|
|
1117
|
+
},
|
|
1118
|
+
suggestions: write ? [] : [{ text: 'Re-run with --write to apply changes' }],
|
|
1119
|
+
notifications: [
|
|
1120
|
+
...(explicitMap?.size ? [{ tone: 'info', message: `Explicit map: ${explicitMap.size} entries` }] : []),
|
|
1121
|
+
...(paths.length > 40 ? [{ tone: 'info', message: `… and ${paths.length - 40} more files` }] : []),
|
|
1122
|
+
],
|
|
1123
|
+
});
|
|
815
1124
|
} catch (e) {
|
|
816
|
-
|
|
817
|
-
|
|
1125
|
+
await emitExperience('prefix', {
|
|
1126
|
+
status: 'ERROR',
|
|
1127
|
+
message: /** @type {Error} */ (e).message,
|
|
1128
|
+
exitCode: 1,
|
|
1129
|
+
});
|
|
818
1130
|
}
|
|
819
1131
|
}
|
|
820
1132
|
|
|
@@ -826,27 +1138,35 @@ async function atelierCmd() {
|
|
|
826
1138
|
} = await import('./atelier.js');
|
|
827
1139
|
|
|
828
1140
|
if (!sub || sub === '--help' || sub === '-h') {
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
1141
|
+
await emitExperience('atelier', {
|
|
1142
|
+
message: 'Usage: velinstyle atelier list | <num|id> [-o dir] [--format html|blade|vue|react]',
|
|
1143
|
+
suggestions: [
|
|
1144
|
+
{ text: 'velinstyle atelier list' },
|
|
1145
|
+
{ text: 'velinstyle atelier 36 -o ./velin-atelier/36-calendar' },
|
|
1146
|
+
],
|
|
1147
|
+
notifications: [{
|
|
1148
|
+
tone: 'info',
|
|
1149
|
+
message: 'Limitation: blade|vue|react = wrappers; Studio/native rewrites planned.',
|
|
1150
|
+
}],
|
|
1151
|
+
});
|
|
836
1152
|
return;
|
|
837
1153
|
}
|
|
838
1154
|
|
|
839
1155
|
if (sub === 'list') {
|
|
840
1156
|
const r = listAtelierEntries();
|
|
841
1157
|
if (!r.ok) {
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
}
|
|
845
|
-
console.log(`\n ${C.bold('Atelier Library (curated)')} · ${r.items.length} templates\n`);
|
|
846
|
-
for (const it of r.items) {
|
|
847
|
-
console.log(` ${C.cyan(it.num.padStart(3))} ${it.id} ${C.dim(it.title || '')}`);
|
|
1158
|
+
await emitExperience('atelier', { status: 'ERROR', message: r.error, exitCode: 1 });
|
|
1159
|
+
return;
|
|
848
1160
|
}
|
|
849
|
-
|
|
1161
|
+
await emitExperience('atelier', {
|
|
1162
|
+
subtitle: 'list',
|
|
1163
|
+
message: `Atelier Library · ${r.items.length} templates`,
|
|
1164
|
+
table: {
|
|
1165
|
+
columns: ['#', 'Id', 'Title'],
|
|
1166
|
+
rows: r.items.map((it) => [it.num.padStart(3), it.id, it.title || '']),
|
|
1167
|
+
},
|
|
1168
|
+
suggestions: [{ text: 'velinstyle atelier 36 -o ./velin-atelier/36-calendar' }],
|
|
1169
|
+
});
|
|
850
1170
|
return;
|
|
851
1171
|
}
|
|
852
1172
|
|
|
@@ -864,24 +1184,40 @@ async function atelierCmd() {
|
|
|
864
1184
|
rewriteVendor: rewriteVendor || undefined,
|
|
865
1185
|
});
|
|
866
1186
|
if (!r.ok) {
|
|
867
|
-
|
|
868
|
-
|
|
1187
|
+
await emitExperience('atelier', { status: 'ERROR', message: r.error, exitCode: 1 });
|
|
1188
|
+
return;
|
|
869
1189
|
}
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
1190
|
+
await emitExperience('atelier', {
|
|
1191
|
+
message: `Pulled ${r.entry.id} → ${r.path}`,
|
|
1192
|
+
items: [
|
|
1193
|
+
{ label: 'Format', value: r.format },
|
|
1194
|
+
{ label: 'Source', value: r.source },
|
|
1195
|
+
{ label: 'Path', value: r.path },
|
|
1196
|
+
],
|
|
1197
|
+
notifications: [{
|
|
1198
|
+
tone: 'info',
|
|
1199
|
+
message: 'Limitation: blade/vue/react = wrappers; Studio/native rewrites planned.',
|
|
1200
|
+
}],
|
|
1201
|
+
});
|
|
873
1202
|
}
|
|
874
1203
|
|
|
875
1204
|
async function scaffoldCmd() {
|
|
876
1205
|
const sub = args[1];
|
|
877
1206
|
const { scaffoldFromPrompt, listIntents } = await import('./scaffold.js');
|
|
878
1207
|
if (sub === 'list-intents') {
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
1208
|
+
const intents = listIntents();
|
|
1209
|
+
await emitExperience('scaffold', {
|
|
1210
|
+
subtitle: 'list-intents',
|
|
1211
|
+
message: `${intents.length} intents`,
|
|
1212
|
+
table: {
|
|
1213
|
+
columns: ['Id', 'Blueprints', 'Keywords'],
|
|
1214
|
+
rows: intents.map((i) => [
|
|
1215
|
+
i.id,
|
|
1216
|
+
i.blueprints.join(' + '),
|
|
1217
|
+
i.keywords.slice(0, 5).join(', '),
|
|
1218
|
+
]),
|
|
1219
|
+
},
|
|
1220
|
+
});
|
|
885
1221
|
return;
|
|
886
1222
|
}
|
|
887
1223
|
|
|
@@ -897,8 +1233,8 @@ async function scaffoldCmd() {
|
|
|
897
1233
|
baseUrl: baseUrl || undefined,
|
|
898
1234
|
});
|
|
899
1235
|
if (!r.ok) {
|
|
900
|
-
|
|
901
|
-
|
|
1236
|
+
await emitExperience('scaffold', { status: 'ERROR', message: r.error, exitCode: 1 });
|
|
1237
|
+
return;
|
|
902
1238
|
}
|
|
903
1239
|
if (hasFlag('--json')) {
|
|
904
1240
|
console.log(JSON.stringify({
|
|
@@ -908,11 +1244,20 @@ async function scaffoldCmd() {
|
|
|
908
1244
|
entries: r.entries,
|
|
909
1245
|
planSections: r.planSections,
|
|
910
1246
|
}, null, 2));
|
|
911
|
-
|
|
912
|
-
console.log(C.green(`Wrote Atelier compose (beta) → ${r.path}`));
|
|
913
|
-
console.log(C.dim(` ids: ${r.entries.map((e) => e.id).join(', ')}`));
|
|
914
|
-
console.log(C.dim(' Beta: Library compose — not Velin Studio. Wrappers ≠ native rewrites.'));
|
|
1247
|
+
return;
|
|
915
1248
|
}
|
|
1249
|
+
await emitExperience('scaffold', {
|
|
1250
|
+
subtitle: 'atelier compose',
|
|
1251
|
+
message: `Wrote Atelier compose (beta) → ${r.path}`,
|
|
1252
|
+
items: [
|
|
1253
|
+
{ label: 'Ids', value: r.entries.map((e) => e.id).join(', ') },
|
|
1254
|
+
{ label: 'Mode', value: r.mode || 'compose' },
|
|
1255
|
+
],
|
|
1256
|
+
notifications: [{
|
|
1257
|
+
tone: 'info',
|
|
1258
|
+
message: 'Beta: Library compose — not Velin Studio. Wrappers ≠ native rewrites.',
|
|
1259
|
+
}],
|
|
1260
|
+
});
|
|
916
1261
|
return;
|
|
917
1262
|
}
|
|
918
1263
|
|
|
@@ -926,14 +1271,21 @@ async function scaffoldCmd() {
|
|
|
926
1271
|
}
|
|
927
1272
|
const prompt = promptParts.join(' ').trim() || sub;
|
|
928
1273
|
if (!prompt || prompt === 'list-intents') {
|
|
929
|
-
|
|
930
|
-
|
|
1274
|
+
await emitExperience('scaffold', {
|
|
1275
|
+
status: 'WARNING',
|
|
1276
|
+
message: 'Usage: velinstyle scaffold "<description>" [-o file.html] [--json]',
|
|
1277
|
+
suggestions: [
|
|
1278
|
+
{ text: 'velinstyle scaffold --atelier 04,07 -o page.html' },
|
|
1279
|
+
{ text: 'velinstyle scaffold list-intents' },
|
|
1280
|
+
],
|
|
1281
|
+
exitCode: 1,
|
|
1282
|
+
});
|
|
931
1283
|
return;
|
|
932
1284
|
}
|
|
933
1285
|
const r = scaffoldFromPrompt(prompt);
|
|
934
1286
|
if (!r.ok) {
|
|
935
|
-
|
|
936
|
-
|
|
1287
|
+
await emitExperience('scaffold', { status: 'ERROR', message: r.error, exitCode: 1 });
|
|
1288
|
+
return;
|
|
937
1289
|
}
|
|
938
1290
|
const out = getArg('--output', '-o');
|
|
939
1291
|
const asJson = hasFlag('--json');
|
|
@@ -941,13 +1293,21 @@ async function scaffoldCmd() {
|
|
|
941
1293
|
console.log(JSON.stringify(r, null, 2));
|
|
942
1294
|
} else if (out) {
|
|
943
1295
|
writeFileSync(resolve(out), r.html, 'utf-8');
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
1296
|
+
await emitExperience('scaffold', {
|
|
1297
|
+
message: `Wrote ${resolve(out)}`,
|
|
1298
|
+
items: [
|
|
1299
|
+
{ label: 'Intent', value: r.intent },
|
|
1300
|
+
{ label: 'Confidence', value: String(r.confidence) },
|
|
1301
|
+
...(r.mode ? [{ label: 'Mode', value: r.mode }] : []),
|
|
1302
|
+
...(r.review?.gate ? [{ label: 'Review gate', value: `${r.review.gate} (${r.review.promptScore})` }] : []),
|
|
1303
|
+
],
|
|
1304
|
+
suggestions: r.responsiveHints?.length
|
|
1305
|
+
? [{ text: `velinstyle layout suggest ${out}` }]
|
|
1306
|
+
: [],
|
|
1307
|
+
notifications: r.responsiveHints?.length
|
|
1308
|
+
? [{ tone: 'warning', message: `${r.responsiveHints.length} layout hint(s)` }]
|
|
1309
|
+
: [],
|
|
1310
|
+
});
|
|
951
1311
|
} else {
|
|
952
1312
|
console.log(r.html);
|
|
953
1313
|
}
|
|
@@ -959,13 +1319,17 @@ async function planCmd() {
|
|
|
959
1319
|
const { planFromAtelierList } = await import('./atelier.js');
|
|
960
1320
|
const payload = planFromAtelierList(atelierList, { prompt: args.slice(1).filter((a) => !a.startsWith('-')).join(' ') });
|
|
961
1321
|
if (!payload.ok) {
|
|
962
|
-
|
|
963
|
-
|
|
1322
|
+
await emitExperience('plan', { status: 'ERROR', message: payload.error, exitCode: 1 });
|
|
1323
|
+
return;
|
|
964
1324
|
}
|
|
965
1325
|
const out = getArg('--output', '-o');
|
|
966
1326
|
if (out) {
|
|
967
1327
|
writeFileSync(resolve(out), JSON.stringify(payload, null, 2), 'utf-8');
|
|
968
|
-
|
|
1328
|
+
await emitExperience('plan', {
|
|
1329
|
+
subtitle: 'atelier',
|
|
1330
|
+
message: `Wrote Atelier plan (beta) ${resolve(out)}`,
|
|
1331
|
+
items: [{ label: 'Sections', value: String(payload.plan.sections.length) }],
|
|
1332
|
+
});
|
|
969
1333
|
return;
|
|
970
1334
|
}
|
|
971
1335
|
console.log(JSON.stringify(payload, null, 2));
|
|
@@ -982,8 +1346,12 @@ async function planCmd() {
|
|
|
982
1346
|
}
|
|
983
1347
|
const prompt = promptParts.join(' ').trim();
|
|
984
1348
|
if (!prompt) {
|
|
985
|
-
|
|
986
|
-
|
|
1349
|
+
await emitExperience('plan', {
|
|
1350
|
+
status: 'WARNING',
|
|
1351
|
+
message: 'Usage: velinstyle plan "<description>" [--json] [-o plan.json]',
|
|
1352
|
+
suggestions: [{ text: 'velinstyle plan --atelier 04,07 [-o plan.json]' }],
|
|
1353
|
+
exitCode: 1,
|
|
1354
|
+
});
|
|
987
1355
|
return;
|
|
988
1356
|
}
|
|
989
1357
|
const { analyzePrompt, buildPlan } = await import('./prompt-engine.js');
|
|
@@ -993,7 +1361,13 @@ async function planCmd() {
|
|
|
993
1361
|
const out = getArg('--output', '-o');
|
|
994
1362
|
if (out) {
|
|
995
1363
|
writeFileSync(resolve(out), JSON.stringify(payload, null, 2), 'utf-8');
|
|
996
|
-
|
|
1364
|
+
await emitExperience('plan', {
|
|
1365
|
+
message: `Wrote plan ${resolve(out)}`,
|
|
1366
|
+
items: [
|
|
1367
|
+
{ label: 'Page', value: plan.page?.id || '—' },
|
|
1368
|
+
{ label: 'Sections', value: String(plan.sections.length) },
|
|
1369
|
+
],
|
|
1370
|
+
});
|
|
997
1371
|
return;
|
|
998
1372
|
}
|
|
999
1373
|
console.log(JSON.stringify(payload, null, 2));
|
|
@@ -1022,24 +1396,51 @@ async function reviewCmd() {
|
|
|
1022
1396
|
const report = reviewHtml(html, { plan, prompt, profile: profile || undefined });
|
|
1023
1397
|
if (hasFlag('--json')) {
|
|
1024
1398
|
console.log(JSON.stringify(report, null, 2));
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
console.log(C.dim(` design ${report.scores.design} · a11y ${report.scores.accessibility} · seo ${report.scores.seo} · perf ${report.scores.performance} · conversion ${report.scores.conversion} · opt ${report.scores.optimization ?? '—'}`));
|
|
1028
|
-
if (report.optimization) {
|
|
1029
|
-
console.log(C.dim(` optimization · unused themes ${report.optimization.unusedThemes} · WC tags ${report.optimization.componentTags} · class tokens ${report.optimization.classTokens}`));
|
|
1030
|
-
}
|
|
1031
|
-
if (report.issues.length) {
|
|
1032
|
-
console.log(`\n ${C.bold('Issues:')}`);
|
|
1033
|
-
for (const issue of report.issues) {
|
|
1034
|
-
const color = issue.severity === 'error' ? C.red : C.yellow;
|
|
1035
|
-
console.log(color(` [${issue.severity}] ${issue.code}: ${issue.message}`));
|
|
1036
|
-
console.log(C.dim(` fix: ${issue.fix}`));
|
|
1037
|
-
}
|
|
1038
|
-
} else {
|
|
1039
|
-
console.log(C.green('\n No issues.'));
|
|
1040
|
-
}
|
|
1041
|
-
console.log('');
|
|
1399
|
+
process.exit(report.gate === 'fail' ? 1 : 0);
|
|
1400
|
+
return;
|
|
1042
1401
|
}
|
|
1402
|
+
const exp = await ensureExperience();
|
|
1403
|
+
await exp.executeExperience({
|
|
1404
|
+
commandId: 'review',
|
|
1405
|
+
args,
|
|
1406
|
+
helpers: { getArg, hasFlag },
|
|
1407
|
+
version: CLI_VERSION,
|
|
1408
|
+
layout: 'review',
|
|
1409
|
+
run: async () => ({
|
|
1410
|
+
project: { file: full },
|
|
1411
|
+
result: {
|
|
1412
|
+
status: report.gate === 'fail' ? 'ERROR' : report.issues.length ? 'WARNING' : 'PASS',
|
|
1413
|
+
message: `gate ${report.gate} · profile ${report.profile}`,
|
|
1414
|
+
},
|
|
1415
|
+
stats: report.scores,
|
|
1416
|
+
diagnostics: report.issues,
|
|
1417
|
+
widgets: [
|
|
1418
|
+
{ id: 'hero', props: { title: 'Review', subtitle: full, eyebrow: 'Velin Experience' } },
|
|
1419
|
+
{
|
|
1420
|
+
id: 'score',
|
|
1421
|
+
props: {
|
|
1422
|
+
title: 'Overall',
|
|
1423
|
+
scores: [
|
|
1424
|
+
{ label: 'Design', value: report.scores.design },
|
|
1425
|
+
{ label: 'Accessibility', value: report.scores.accessibility },
|
|
1426
|
+
{ label: 'SEO', value: report.scores.seo },
|
|
1427
|
+
{ label: 'Performance', value: report.scores.performance },
|
|
1428
|
+
{ label: 'Conversion', value: report.scores.conversion },
|
|
1429
|
+
],
|
|
1430
|
+
},
|
|
1431
|
+
},
|
|
1432
|
+
{
|
|
1433
|
+
id: 'table',
|
|
1434
|
+
props: {
|
|
1435
|
+
columns: ['Severity', 'Code', 'Message'],
|
|
1436
|
+
rows: (report.issues || []).slice(0, 20).map((i) => [i.severity, i.code, i.message.slice(0, 48)]),
|
|
1437
|
+
},
|
|
1438
|
+
},
|
|
1439
|
+
{ id: 'footer', props: { text: 'velinstyle review' } },
|
|
1440
|
+
],
|
|
1441
|
+
suggestions: (report.issues || []).filter((i) => i.fix).slice(0, 5).map((i) => ({ text: i.fix })),
|
|
1442
|
+
}),
|
|
1443
|
+
});
|
|
1043
1444
|
process.exit(report.gate === 'fail' ? 1 : 0);
|
|
1044
1445
|
}
|
|
1045
1446
|
|
|
@@ -1056,21 +1457,29 @@ async function layoutCmd() {
|
|
|
1056
1457
|
if (sub === 'fix') {
|
|
1057
1458
|
const result = fixPath(targetPath, { write, dryRun });
|
|
1058
1459
|
if (!result.ok) {
|
|
1059
|
-
|
|
1060
|
-
|
|
1460
|
+
await emitExperience('layout', { status: 'ERROR', message: result.error || 'Fix failed', exitCode: 1 });
|
|
1461
|
+
return;
|
|
1061
1462
|
}
|
|
1062
1463
|
if (asJson) {
|
|
1063
1464
|
console.log(JSON.stringify(result, null, 2));
|
|
1064
1465
|
return;
|
|
1065
1466
|
}
|
|
1066
1467
|
if (result.changes.length === 0) {
|
|
1067
|
-
|
|
1468
|
+
await emitExperience('layout', {
|
|
1469
|
+
subtitle: 'fix',
|
|
1470
|
+
message: 'No safe fixes to apply.',
|
|
1471
|
+
notifications: [{ tone: 'ok', message: 'Nothing to change' }],
|
|
1472
|
+
});
|
|
1068
1473
|
return;
|
|
1069
1474
|
}
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1475
|
+
await emitExperience('layout', {
|
|
1476
|
+
subtitle: 'fix',
|
|
1477
|
+
message: `${dryRun ? '[dry-run] ' : ''}${result.changes.length} file(s)`,
|
|
1478
|
+
table: {
|
|
1479
|
+
columns: ['File', 'Changes'],
|
|
1480
|
+
rows: result.changes.map((c) => [c.file, (c.changes || []).join('; ').slice(0, 60)]),
|
|
1481
|
+
},
|
|
1482
|
+
});
|
|
1074
1483
|
return;
|
|
1075
1484
|
}
|
|
1076
1485
|
|
|
@@ -1082,17 +1491,48 @@ async function layoutCmd() {
|
|
|
1082
1491
|
return;
|
|
1083
1492
|
}
|
|
1084
1493
|
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1494
|
+
const exp = await ensureExperience();
|
|
1495
|
+
const errors = issues.filter((x) => x.severity === 'error').length;
|
|
1496
|
+
const warnings = issues.filter((x) => x.severity === 'warning').length;
|
|
1497
|
+
await exp.executeExperience({
|
|
1498
|
+
commandId: 'layout',
|
|
1499
|
+
args,
|
|
1500
|
+
helpers: { getArg, hasFlag },
|
|
1501
|
+
version: CLI_VERSION,
|
|
1502
|
+
layout: 'audit',
|
|
1503
|
+
run: async () => ({
|
|
1504
|
+
project: { cwd: targetPath, name: 'Layout' },
|
|
1505
|
+
result: {
|
|
1506
|
+
status: errors ? 'ERROR' : warnings ? 'WARNING' : 'PASS',
|
|
1507
|
+
message: `${outIssues.length} finding(s) · ${sub}`,
|
|
1508
|
+
},
|
|
1509
|
+
diagnostics: outIssues,
|
|
1510
|
+
widgets: [
|
|
1511
|
+
{ id: 'hero', props: { slot: 'hero', title: 'Layout audit', subtitle: `Subcommand: ${sub}`, eyebrow: 'Velin Experience' } },
|
|
1512
|
+
{
|
|
1513
|
+
id: 'stats',
|
|
1514
|
+
props: {
|
|
1515
|
+
slot: 'counts',
|
|
1516
|
+
items: [
|
|
1517
|
+
{ label: 'Files', value: files.length },
|
|
1518
|
+
{ label: 'Findings', value: outIssues.length },
|
|
1519
|
+
{ label: 'Errors', value: errors },
|
|
1520
|
+
],
|
|
1521
|
+
},
|
|
1522
|
+
},
|
|
1523
|
+
{
|
|
1524
|
+
id: 'table',
|
|
1525
|
+
props: {
|
|
1526
|
+
slot: 'findings',
|
|
1527
|
+
columns: ['Sev', 'Id', 'Message'],
|
|
1528
|
+
rows: outIssues.slice(0, 20).map((i) => [i.severity, i.id || '—', String(i.message || i.fix || '').slice(0, 40)]),
|
|
1529
|
+
},
|
|
1530
|
+
},
|
|
1531
|
+
{ id: 'footer', props: { slot: 'footer', text: `velinstyle layout ${sub}` } },
|
|
1532
|
+
],
|
|
1533
|
+
suggestions: outIssues.filter((i) => i.fix).slice(0, 5).map((i) => ({ text: i.fix })),
|
|
1534
|
+
}),
|
|
1535
|
+
});
|
|
1096
1536
|
process.exit(issues.some((x) => x.severity === 'error') ? 1 : 0);
|
|
1097
1537
|
}
|
|
1098
1538
|
|
|
@@ -1104,18 +1544,71 @@ async function docsCmd() {
|
|
|
1104
1544
|
const { generateDocs } = await import('./docs-generate.js');
|
|
1105
1545
|
const result = await generateDocs({ scope, outDir: resolve(out), searchIndex: true });
|
|
1106
1546
|
if (!result.ok) {
|
|
1107
|
-
|
|
1108
|
-
|
|
1547
|
+
await emitExperience('docs', { status: 'ERROR', message: result.error, exitCode: 1 });
|
|
1548
|
+
return;
|
|
1109
1549
|
}
|
|
1110
|
-
|
|
1550
|
+
await emitExperience('docs', {
|
|
1551
|
+
subtitle: 'generate',
|
|
1552
|
+
message: `Generated ${result.written} file(s)`,
|
|
1553
|
+
items: [
|
|
1554
|
+
{ label: 'Scope', value: scope },
|
|
1555
|
+
{ label: 'Out', value: resolve(out) },
|
|
1556
|
+
],
|
|
1557
|
+
});
|
|
1111
1558
|
return;
|
|
1112
1559
|
}
|
|
1113
|
-
|
|
1560
|
+
await emitExperience('docs', {
|
|
1561
|
+
status: 'WARNING',
|
|
1562
|
+
message: 'Usage: velinstyle docs generate [--scope all|components|tokens|…] [--out <dir>]',
|
|
1563
|
+
exitCode: 1,
|
|
1564
|
+
});
|
|
1114
1565
|
}
|
|
1115
1566
|
|
|
1116
1567
|
async function metaCmd() {
|
|
1117
|
-
|
|
1118
|
-
|
|
1568
|
+
if (args[1] === '--help' || args[1] === '-h') {
|
|
1569
|
+
await emitExperience('meta', {
|
|
1570
|
+
message: 'Usage: velinstyle meta [--out file] [--llms-out file] [--base-url url] [--no-llms]',
|
|
1571
|
+
suggestions: [
|
|
1572
|
+
{ text: 'velinstyle meta' },
|
|
1573
|
+
{ text: 'velinstyle meta page index.html [--write]' },
|
|
1574
|
+
],
|
|
1575
|
+
});
|
|
1576
|
+
return;
|
|
1577
|
+
}
|
|
1578
|
+
const { metaPage, buildMeta } = await import('./meta.js');
|
|
1579
|
+
const pageIdx = args.indexOf('page');
|
|
1580
|
+
if (pageIdx !== -1 && args[pageIdx + 1]) {
|
|
1581
|
+
const write = hasFlag('--write');
|
|
1582
|
+
const result = metaPage(args[pageIdx + 1], { write });
|
|
1583
|
+
if (!result.ok) {
|
|
1584
|
+
await emitExperience('meta', { status: 'ERROR', message: result.error, exitCode: 1 });
|
|
1585
|
+
return;
|
|
1586
|
+
}
|
|
1587
|
+
if (write) {
|
|
1588
|
+
await emitExperience('meta', {
|
|
1589
|
+
subtitle: 'page',
|
|
1590
|
+
message: `Updated velin-meta in ${args[pageIdx + 1]}`,
|
|
1591
|
+
});
|
|
1592
|
+
} else {
|
|
1593
|
+
console.log(result.script);
|
|
1594
|
+
}
|
|
1595
|
+
return;
|
|
1596
|
+
}
|
|
1597
|
+
|
|
1598
|
+
const result = await buildMeta({
|
|
1599
|
+
outFile: getArg('--out') ? resolve(getArg('--out')) : undefined,
|
|
1600
|
+
llmsFile: getArg('--llms-out') ? resolve(getArg('--llms-out')) : undefined,
|
|
1601
|
+
baseUrl: getArg('--base-url') || undefined,
|
|
1602
|
+
llms: !hasFlag('--no-llms'),
|
|
1603
|
+
});
|
|
1604
|
+
|
|
1605
|
+
await emitExperience('meta', {
|
|
1606
|
+
message: `Wrote ${result.count} file(s)`,
|
|
1607
|
+
table: {
|
|
1608
|
+
columns: ['File'],
|
|
1609
|
+
rows: (result.written || []).map((p) => [p]),
|
|
1610
|
+
},
|
|
1611
|
+
});
|
|
1119
1612
|
}
|
|
1120
1613
|
|
|
1121
1614
|
async function searchCmd() {
|
|
@@ -1128,42 +1621,280 @@ async function searchCmd() {
|
|
|
1128
1621
|
outFile: resolve(out),
|
|
1129
1622
|
extraHtmlDirs: extra ? extra.split(',').map((p) => resolve(p.trim())) : [],
|
|
1130
1623
|
});
|
|
1131
|
-
|
|
1624
|
+
await emitExperience('search', {
|
|
1625
|
+
subtitle: 'index',
|
|
1626
|
+
message: `Search index: ${result.count} entries`,
|
|
1627
|
+
items: [{ label: 'Out', value: result.outFile }],
|
|
1628
|
+
});
|
|
1132
1629
|
return;
|
|
1133
1630
|
}
|
|
1134
|
-
|
|
1631
|
+
await emitExperience('search', {
|
|
1632
|
+
status: 'WARNING',
|
|
1633
|
+
message: 'Usage: velinstyle search index [--out <file>] [--extra-html dir1,dir2]',
|
|
1634
|
+
exitCode: 1,
|
|
1635
|
+
});
|
|
1135
1636
|
}
|
|
1136
1637
|
|
|
1137
1638
|
async function skillsCmd() {
|
|
1138
|
-
const
|
|
1139
|
-
|
|
1639
|
+
const sub = args[1];
|
|
1640
|
+
const {
|
|
1641
|
+
skillsList,
|
|
1642
|
+
skillsInstall,
|
|
1643
|
+
skillsRun,
|
|
1644
|
+
skillsValidate,
|
|
1645
|
+
skillsDoctor,
|
|
1646
|
+
skillsIndex,
|
|
1647
|
+
loadSkillRegistry,
|
|
1648
|
+
} = await import('./skills.js');
|
|
1649
|
+
|
|
1650
|
+
if (!sub || sub === '--help' || sub === '-h') {
|
|
1651
|
+
await emitExperience('skills', {
|
|
1652
|
+
message: 'Usage: velinstyle skills <list|show|install|run|validate|doctor|packs|…>',
|
|
1653
|
+
suggestions: [
|
|
1654
|
+
{ text: 'velinstyle skills list' },
|
|
1655
|
+
{ text: 'velinstyle skills show scaffold.landing --human' },
|
|
1656
|
+
],
|
|
1657
|
+
});
|
|
1658
|
+
return;
|
|
1659
|
+
}
|
|
1660
|
+
|
|
1661
|
+
if (sub === 'list') {
|
|
1662
|
+
const items = skillsList(args.slice(2), { silent: true });
|
|
1663
|
+
if (hasFlag('--json')) {
|
|
1664
|
+
console.log(JSON.stringify(items, null, 2));
|
|
1665
|
+
return;
|
|
1666
|
+
}
|
|
1667
|
+
await emitExperience('skills', {
|
|
1668
|
+
subtitle: 'list',
|
|
1669
|
+
message: `${items.length} skill(s)`,
|
|
1670
|
+
table: {
|
|
1671
|
+
columns: ['Id', 'Category', 'Priority', 'Status'],
|
|
1672
|
+
rows: items.slice(0, 40).map((item) => [
|
|
1673
|
+
item.id,
|
|
1674
|
+
item.category || '',
|
|
1675
|
+
item.priority || '',
|
|
1676
|
+
item.status || '',
|
|
1677
|
+
]),
|
|
1678
|
+
},
|
|
1679
|
+
});
|
|
1680
|
+
return;
|
|
1681
|
+
}
|
|
1682
|
+
|
|
1683
|
+
if (sub === 'show') {
|
|
1684
|
+
const skillId = args[2];
|
|
1685
|
+
const { findSkillById } = await import('../packages/skill-engine/src/index.js');
|
|
1686
|
+
const registry = loadSkillRegistry();
|
|
1687
|
+
const skill = findSkillById(registry, skillId);
|
|
1688
|
+
if (!skill) {
|
|
1689
|
+
await emitExperience('skills', { status: 'ERROR', message: `Unknown skill: ${skillId}`, exitCode: 1 });
|
|
1690
|
+
return;
|
|
1691
|
+
}
|
|
1692
|
+
if (hasFlag('--human')) {
|
|
1693
|
+
await emitExperience('skills', {
|
|
1694
|
+
subtitle: skillId,
|
|
1695
|
+
message: skill.title || skillId,
|
|
1696
|
+
items: [
|
|
1697
|
+
{ label: 'Category', value: skill.category || '—' },
|
|
1698
|
+
{ label: 'Status', value: skill.status || '—' },
|
|
1699
|
+
{ label: 'Priority', value: skill.priority || '—' },
|
|
1700
|
+
{ label: 'Capabilities', value: (skill.capabilities || []).join(', ') || '—' },
|
|
1701
|
+
],
|
|
1702
|
+
notifications: skill.summary || skill.description
|
|
1703
|
+
? [{ tone: 'info', message: skill.summary || skill.description }]
|
|
1704
|
+
: [],
|
|
1705
|
+
});
|
|
1706
|
+
return;
|
|
1707
|
+
}
|
|
1708
|
+
console.log(JSON.stringify(skill, null, 2));
|
|
1709
|
+
return;
|
|
1710
|
+
}
|
|
1711
|
+
|
|
1712
|
+
if (sub === 'install') {
|
|
1713
|
+
if (!args[2]) {
|
|
1714
|
+
await emitExperience('skills', {
|
|
1715
|
+
status: 'WARNING',
|
|
1716
|
+
message: 'Usage: velinstyle skills install <skill-id|pack|bundle:id|project:id>',
|
|
1717
|
+
exitCode: 1,
|
|
1718
|
+
});
|
|
1719
|
+
return;
|
|
1720
|
+
}
|
|
1721
|
+
const result = skillsInstall(args.slice(2), { silent: true });
|
|
1722
|
+
await emitExperience('skills', {
|
|
1723
|
+
subtitle: 'install',
|
|
1724
|
+
status: result.issues?.length ? 'WARNING' : 'PASS',
|
|
1725
|
+
message: `Installed ${(result.installed || []).length} skill(s)`,
|
|
1726
|
+
table: {
|
|
1727
|
+
columns: ['Installed'],
|
|
1728
|
+
rows: (result.installed || []).map((id) => [id]),
|
|
1729
|
+
},
|
|
1730
|
+
notifications: (result.issues || []).map((issue) => ({ tone: 'warning', message: issue })),
|
|
1731
|
+
});
|
|
1732
|
+
return;
|
|
1733
|
+
}
|
|
1734
|
+
|
|
1735
|
+
if (sub === 'run') {
|
|
1736
|
+
const payload = skillsRun(args[2], args.slice(3), { silent: true });
|
|
1737
|
+
if (payload.ok === false) {
|
|
1738
|
+
await emitExperience('skills', {
|
|
1739
|
+
status: 'ERROR',
|
|
1740
|
+
message: 'Skill graph resolve failed',
|
|
1741
|
+
table: { columns: ['Issue'], rows: (payload.issues || []).map((i) => [i]) },
|
|
1742
|
+
exitCode: 1,
|
|
1743
|
+
});
|
|
1744
|
+
return;
|
|
1745
|
+
}
|
|
1746
|
+
if (hasFlag('--json')) {
|
|
1747
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
1748
|
+
return;
|
|
1749
|
+
}
|
|
1750
|
+
await emitExperience('skills', {
|
|
1751
|
+
subtitle: 'run',
|
|
1752
|
+
message: `${payload.steps.length} step(s) (dry-run)`,
|
|
1753
|
+
table: {
|
|
1754
|
+
columns: ['Step', 'CLI', 'onlyIf'],
|
|
1755
|
+
rows: payload.steps.map((s) => [
|
|
1756
|
+
s.id,
|
|
1757
|
+
(s.requiresCli || []).join(', '),
|
|
1758
|
+
(s.onlyIf || []).join(', '),
|
|
1759
|
+
]),
|
|
1760
|
+
},
|
|
1761
|
+
});
|
|
1762
|
+
return;
|
|
1763
|
+
}
|
|
1764
|
+
|
|
1765
|
+
if (sub === 'validate') {
|
|
1766
|
+
const result = skillsValidate({ silent: true });
|
|
1767
|
+
await emitExperience('skills', {
|
|
1768
|
+
subtitle: 'validate',
|
|
1769
|
+
status: result.ok ? 'PASS' : 'ERROR',
|
|
1770
|
+
message: result.ok ? 'skills registry valid' : 'skills registry invalid',
|
|
1771
|
+
table: result.errors?.length
|
|
1772
|
+
? { columns: ['Error'], rows: result.errors.map((e) => [e]) }
|
|
1773
|
+
: null,
|
|
1774
|
+
exitCode: result.ok ? 0 : 1,
|
|
1775
|
+
});
|
|
1776
|
+
return;
|
|
1777
|
+
}
|
|
1778
|
+
|
|
1779
|
+
if (sub === 'doctor') {
|
|
1780
|
+
const result = await skillsDoctor(args.slice(2), { silent: true });
|
|
1781
|
+
if (hasFlag('--json')) {
|
|
1782
|
+
console.log(JSON.stringify(result, null, 2));
|
|
1783
|
+
process.exit(result.ok ? 0 : 1);
|
|
1784
|
+
}
|
|
1785
|
+
await emitExperience('skills', {
|
|
1786
|
+
subtitle: 'doctor',
|
|
1787
|
+
status: result.ok ? 'PASS' : 'ERROR',
|
|
1788
|
+
message: result.ok
|
|
1789
|
+
? `skills doctor OK (${result.checked} paths)`
|
|
1790
|
+
: `skills doctor FAILED (${result.errors.length} missing)`,
|
|
1791
|
+
table: result.errors?.length
|
|
1792
|
+
? { columns: ['Missing'], rows: result.errors.map((e) => [e]) }
|
|
1793
|
+
: null,
|
|
1794
|
+
exitCode: result.ok ? 0 : 1,
|
|
1795
|
+
});
|
|
1796
|
+
return;
|
|
1797
|
+
}
|
|
1798
|
+
|
|
1799
|
+
if (['packs', 'bundles', 'templates', 'projects', 'graphs'].includes(sub)) {
|
|
1800
|
+
const out = skillsIndex(sub, { silent: true });
|
|
1801
|
+
console.log(JSON.stringify(out, null, 2));
|
|
1802
|
+
return;
|
|
1803
|
+
}
|
|
1804
|
+
|
|
1805
|
+
await emitExperience('skills', {
|
|
1806
|
+
status: 'ERROR',
|
|
1807
|
+
message: `Unknown skills subcommand: ${sub}`,
|
|
1808
|
+
exitCode: 1,
|
|
1809
|
+
});
|
|
1140
1810
|
}
|
|
1141
1811
|
|
|
1142
1812
|
async function workflowCmd() {
|
|
1143
|
-
const {
|
|
1144
|
-
|
|
1813
|
+
const { workflowResolve } = await import('./workflow.js');
|
|
1814
|
+
const workflowId = args[1];
|
|
1815
|
+
if (!workflowId || workflowId === '--help' || workflowId === '-h') {
|
|
1816
|
+
await emitExperience('workflow', {
|
|
1817
|
+
message: 'Usage: velinstyle workflow <graph-id|project:id> [--json]',
|
|
1818
|
+
suggestions: [
|
|
1819
|
+
{ text: 'velinstyle workflow landingpage' },
|
|
1820
|
+
{ text: 'velinstyle workflow project:marketing-site --json' },
|
|
1821
|
+
],
|
|
1822
|
+
});
|
|
1823
|
+
return;
|
|
1824
|
+
}
|
|
1825
|
+
const resolved = workflowResolve(workflowId);
|
|
1826
|
+
if (!resolved.ok) {
|
|
1827
|
+
await emitExperience('workflow', {
|
|
1828
|
+
status: 'ERROR',
|
|
1829
|
+
message: resolved.error,
|
|
1830
|
+
table: resolved.issues?.length
|
|
1831
|
+
? { columns: ['Issue'], rows: resolved.issues.map((i) => [i]) }
|
|
1832
|
+
: null,
|
|
1833
|
+
exitCode: 1,
|
|
1834
|
+
});
|
|
1835
|
+
return;
|
|
1836
|
+
}
|
|
1837
|
+
if (hasFlag('--json')) {
|
|
1838
|
+
console.log(JSON.stringify(resolved.data, null, 2));
|
|
1839
|
+
return;
|
|
1840
|
+
}
|
|
1841
|
+
await emitExperience('workflow', {
|
|
1842
|
+
message: `Workflow steps (${resolved.data.orderedSteps.length})`,
|
|
1843
|
+
table: {
|
|
1844
|
+
columns: ['Step'],
|
|
1845
|
+
rows: resolved.data.orderedSteps.map((step) => [step]),
|
|
1846
|
+
},
|
|
1847
|
+
});
|
|
1145
1848
|
}
|
|
1146
1849
|
|
|
1147
1850
|
async function wcCmd() {
|
|
1148
1851
|
const sub = args[1];
|
|
1149
1852
|
if (sub === 'api' || sub === '--help' || sub === '-h' || !sub) {
|
|
1150
1853
|
if (sub !== 'api') {
|
|
1151
|
-
|
|
1152
|
-
|
|
1854
|
+
await emitExperience('wc', {
|
|
1855
|
+
status: sub ? 'PASS' : 'WARNING',
|
|
1856
|
+
message: 'Usage: velinstyle wc api <tag>',
|
|
1857
|
+
suggestions: [{ text: 'velinstyle wc api velin-toast' }],
|
|
1858
|
+
exitCode: !sub ? 1 : 0,
|
|
1859
|
+
});
|
|
1860
|
+
return;
|
|
1153
1861
|
}
|
|
1154
1862
|
const tag = args[2];
|
|
1155
|
-
const { describeWcApi
|
|
1863
|
+
const { describeWcApi } = await import('./wc-api.js');
|
|
1156
1864
|
const report = describeWcApi(PKG_ROOT, tag);
|
|
1157
1865
|
if (!report.ok) {
|
|
1158
|
-
|
|
1159
|
-
|
|
1866
|
+
await emitExperience('wc', { status: 'ERROR', message: report.error, exitCode: 1 });
|
|
1867
|
+
return;
|
|
1160
1868
|
}
|
|
1161
|
-
if (hasFlag('--json'))
|
|
1162
|
-
|
|
1869
|
+
if (hasFlag('--json')) {
|
|
1870
|
+
console.log(JSON.stringify(report, null, 2));
|
|
1871
|
+
return;
|
|
1872
|
+
}
|
|
1873
|
+
const rows = [
|
|
1874
|
+
...report.observedAttributes.map((a) => ['attr', a]),
|
|
1875
|
+
...report.methods.map((m) => ['method', `${m}()`]),
|
|
1876
|
+
...report.events.map((e) => ['event', e]),
|
|
1877
|
+
];
|
|
1878
|
+
await emitExperience('wc', {
|
|
1879
|
+
subtitle: report.tag,
|
|
1880
|
+
message: `<${report.tag}>`,
|
|
1881
|
+
items: [
|
|
1882
|
+
{ label: 'Source', value: report.source },
|
|
1883
|
+
{ label: 'Docs', value: report.generatedDoc || '(missing — run docs generate)' },
|
|
1884
|
+
...(report.showSignature ? [{ label: 'show()', value: report.showSignature }] : []),
|
|
1885
|
+
],
|
|
1886
|
+
table: rows.length ? { columns: ['Kind', 'Name'], rows } : null,
|
|
1887
|
+
notifications: report.example
|
|
1888
|
+
? [{ tone: 'info', message: report.example.split('\n')[0] }]
|
|
1889
|
+
: [],
|
|
1890
|
+
});
|
|
1163
1891
|
return;
|
|
1164
1892
|
}
|
|
1165
|
-
|
|
1166
|
-
|
|
1893
|
+
await emitExperience('wc', {
|
|
1894
|
+
status: 'ERROR',
|
|
1895
|
+
message: `Unknown wc subcommand: ${sub}. Use: velinstyle wc api <tag>`,
|
|
1896
|
+
exitCode: 1,
|
|
1897
|
+
});
|
|
1167
1898
|
}
|
|
1168
1899
|
|
|
1169
1900
|
async function scanCmd() {
|
|
@@ -1178,15 +1909,81 @@ async function scanCmd() {
|
|
|
1178
1909
|
}
|
|
1179
1910
|
const severity = getArg('--severity') || 'warning';
|
|
1180
1911
|
const only = getArg('--only');
|
|
1912
|
+
const machine = format === 'json' || format === 'sarif' || hasFlag('--json') || hasFlag('--sarif');
|
|
1181
1913
|
|
|
1182
1914
|
const { scan } = await import('./scanner.js');
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1915
|
+
|
|
1916
|
+
if (fix || fixDryRun || machine) {
|
|
1917
|
+
const exitCode = scan(targetPath, {
|
|
1918
|
+
severity,
|
|
1919
|
+
fix,
|
|
1920
|
+
fixDryRun,
|
|
1921
|
+
fixLang: fixLang || undefined,
|
|
1922
|
+
format: hasFlag('--json') ? 'json' : hasFlag('--sarif') ? 'sarif' : format,
|
|
1923
|
+
only,
|
|
1924
|
+
});
|
|
1925
|
+
process.exit(exitCode);
|
|
1926
|
+
}
|
|
1927
|
+
|
|
1928
|
+
const exp = await ensureExperience();
|
|
1929
|
+
let exitCode = 0;
|
|
1930
|
+
await exp.executeExperience({
|
|
1931
|
+
commandId: 'scan',
|
|
1932
|
+
args,
|
|
1933
|
+
helpers: { getArg, hasFlag },
|
|
1934
|
+
version: CLI_VERSION,
|
|
1935
|
+
layout: 'audit',
|
|
1936
|
+
run: async (ctx) => {
|
|
1937
|
+
ctx.emitProgress('Analysis', 40);
|
|
1938
|
+
const data = scan(targetPath, {
|
|
1939
|
+
severity,
|
|
1940
|
+
format: 'json',
|
|
1941
|
+
only,
|
|
1942
|
+
returnData: true,
|
|
1943
|
+
});
|
|
1944
|
+
exitCode = data.exitCode || 0;
|
|
1945
|
+
const issues = data.issues || [];
|
|
1946
|
+
const errors = issues.filter((i) => i.severity === 'error').length;
|
|
1947
|
+
const warnings = issues.filter((i) => i.severity === 'warning').length;
|
|
1948
|
+
const status = errors ? 'ERROR' : warnings ? 'WARNING' : 'PASS';
|
|
1949
|
+
return {
|
|
1950
|
+
project: { cwd: targetPath, name: 'Scan' },
|
|
1951
|
+
result: {
|
|
1952
|
+
status,
|
|
1953
|
+
message: errors || warnings
|
|
1954
|
+
? `${errors} error(s), ${warnings} warning(s)`
|
|
1955
|
+
: 'No issues at selected severity',
|
|
1956
|
+
},
|
|
1957
|
+
stats: { errors, warnings, issues: issues.length },
|
|
1958
|
+
diagnostics: issues,
|
|
1959
|
+
widgets: [
|
|
1960
|
+
{ id: 'hero', props: { slot: 'hero', title: 'Scan', subtitle: 'Trust & accessibility audit', eyebrow: 'Velin Experience' } },
|
|
1961
|
+
{
|
|
1962
|
+
id: 'stats',
|
|
1963
|
+
props: {
|
|
1964
|
+
slot: 'counts',
|
|
1965
|
+
items: [
|
|
1966
|
+
{ label: 'Errors', value: errors },
|
|
1967
|
+
{ label: 'Warnings', value: warnings },
|
|
1968
|
+
{ label: 'Issues', value: issues.length },
|
|
1969
|
+
],
|
|
1970
|
+
},
|
|
1971
|
+
},
|
|
1972
|
+
{
|
|
1973
|
+
id: 'table',
|
|
1974
|
+
props: {
|
|
1975
|
+
slot: 'findings',
|
|
1976
|
+
columns: ['Sev', 'Code', 'Message'],
|
|
1977
|
+
rows: issues.slice(0, 20).map((i) => [i.severity, i.code || '—', String(i.message || '').slice(0, 42)]),
|
|
1978
|
+
},
|
|
1979
|
+
},
|
|
1980
|
+
{ id: 'notification', props: { tone: status === 'PASS' ? 'ok' : status === 'ERROR' ? 'err' : 'warn', message: `Scan ${status}` } },
|
|
1981
|
+
{ id: 'footer', props: { slot: 'footer', text: 'velinstyle scan' } },
|
|
1982
|
+
],
|
|
1983
|
+
suggestions: issues.filter((i) => i.fix).slice(0, 5).map((i) => ({ text: i.fix })),
|
|
1984
|
+
notifications: [{ tone: 'info', message: 'Use --fix for safe auto-fixes where available' }],
|
|
1985
|
+
};
|
|
1986
|
+
},
|
|
1190
1987
|
});
|
|
1191
1988
|
process.exit(exitCode);
|
|
1192
1989
|
}
|
|
@@ -1287,6 +2084,40 @@ async function checkCmd() {
|
|
|
1287
2084
|
steps.push({ step: 'transparency', ok: true, skipped: true, error: String(err.message || err) });
|
|
1288
2085
|
}
|
|
1289
2086
|
|
|
2087
|
+
if (!quiet) console.log(C.bold('\n── motion ──'));
|
|
2088
|
+
try {
|
|
2089
|
+
const { motionCheckStep } = await import('./motion.js');
|
|
2090
|
+
const mx = await motionCheckStep(htmlFile || target, { quiet, C });
|
|
2091
|
+
if (!mx.ok && !mx.skipped) failed += 1;
|
|
2092
|
+
steps.push({ step: 'motion', ok: mx.ok || !!mx.skipped, skipped: mx.skipped, elements: mx.elements });
|
|
2093
|
+
} catch (err) {
|
|
2094
|
+
if (!quiet) console.log(C.yellow(`motion skipped: ${err.message}`));
|
|
2095
|
+
steps.push({ step: 'motion', ok: true, skipped: true, error: String(err.message || err) });
|
|
2096
|
+
}
|
|
2097
|
+
|
|
2098
|
+
if (!quiet) console.log(C.bold('\n── security ──'));
|
|
2099
|
+
try {
|
|
2100
|
+
const { securityCheckStep } = await import('./security.js');
|
|
2101
|
+
let securityRoot = target;
|
|
2102
|
+
try {
|
|
2103
|
+
if (existsSync(target) && statSync(target).isFile()) securityRoot = dirname(target);
|
|
2104
|
+
} catch { /* keep target */ }
|
|
2105
|
+
const sx = await securityCheckStep(securityRoot, { quiet, C });
|
|
2106
|
+
if (!sx.ok && !sx.skipped) failed += 1;
|
|
2107
|
+
steps.push({
|
|
2108
|
+
step: 'security',
|
|
2109
|
+
ok: sx.ok || !!sx.skipped,
|
|
2110
|
+
skipped: sx.skipped,
|
|
2111
|
+
securityScore: sx.securityScore,
|
|
2112
|
+
overall: sx.overall,
|
|
2113
|
+
overallGate: sx.overallGate,
|
|
2114
|
+
findings: sx.findings,
|
|
2115
|
+
});
|
|
2116
|
+
} catch (err) {
|
|
2117
|
+
if (!quiet) console.log(C.yellow(`security skipped: ${err.message}`));
|
|
2118
|
+
steps.push({ step: 'security', ok: true, skipped: true, error: String(err.message || err) });
|
|
2119
|
+
}
|
|
2120
|
+
|
|
1290
2121
|
const payload = {
|
|
1291
2122
|
ok: failed === 0,
|
|
1292
2123
|
failedSteps: failed,
|
|
@@ -1333,10 +2164,43 @@ async function checkCmd() {
|
|
|
1333
2164
|
console.log(JSON.stringify(sarif, null, 2));
|
|
1334
2165
|
} else if (asJson) {
|
|
1335
2166
|
console.log(JSON.stringify(payload, null, 2));
|
|
1336
|
-
} else if (failed) {
|
|
1337
|
-
console.log(C.red(`\ncheck failed (${failed} step(s))`));
|
|
1338
2167
|
} else {
|
|
1339
|
-
|
|
2168
|
+
const exp = await ensureExperience();
|
|
2169
|
+
await exp.executeExperience({
|
|
2170
|
+
commandId: 'check',
|
|
2171
|
+
args,
|
|
2172
|
+
helpers: { getArg, hasFlag },
|
|
2173
|
+
version: CLI_VERSION,
|
|
2174
|
+
layout: 'dashboard',
|
|
2175
|
+
run: async () => ({
|
|
2176
|
+
project: { target },
|
|
2177
|
+
result: {
|
|
2178
|
+
status: failed ? 'ERROR' : 'PASS',
|
|
2179
|
+
message: failed ? `check failed (${failed} step(s))` : 'Ready',
|
|
2180
|
+
},
|
|
2181
|
+
widgets: [
|
|
2182
|
+
{ id: 'hero', props: { title: 'Check', subtitle: 'All engines', eyebrow: 'Velin Experience' } },
|
|
2183
|
+
{
|
|
2184
|
+
id: 'health',
|
|
2185
|
+
props: {
|
|
2186
|
+
title: 'Summary',
|
|
2187
|
+
rows: steps.map((s) => ({
|
|
2188
|
+
label: s.step,
|
|
2189
|
+
status: s.skipped ? 'SKIP' : s.ok ? 'PASS' : 'FAIL',
|
|
2190
|
+
})),
|
|
2191
|
+
},
|
|
2192
|
+
},
|
|
2193
|
+
{
|
|
2194
|
+
id: 'status',
|
|
2195
|
+
props: { label: failed ? 'FAIL' : 'PASS', tone: failed ? 'err' : 'ok' },
|
|
2196
|
+
},
|
|
2197
|
+
{ id: 'footer', props: { text: 'Overall · Ready gate' } },
|
|
2198
|
+
],
|
|
2199
|
+
suggestions: failed
|
|
2200
|
+
? [{ text: 'Re-run failing steps individually (doctor / scan / review)' }]
|
|
2201
|
+
: [{ text: 'Try velinstyle dashboard for a project overview' }],
|
|
2202
|
+
}),
|
|
2203
|
+
});
|
|
1340
2204
|
}
|
|
1341
2205
|
|
|
1342
2206
|
process.exit(failed ? 1 : 0);
|
|
@@ -1345,8 +2209,9 @@ async function checkCmd() {
|
|
|
1345
2209
|
function suggestCommand(unknown) {
|
|
1346
2210
|
const names = [
|
|
1347
2211
|
'init', 'build', 'themes', 'add', 'icons', 'blueprint', 'create', 'serve', 'doctor', 'check',
|
|
1348
|
-
'validate', 'tokens', 'scan', 'prefix', 'scaffold', 'plan', 'review', 'transparency', 'layout', 'perf',
|
|
2212
|
+
'validate', 'tokens', 'scan', 'prefix', 'scaffold', 'plan', 'review', 'transparency', 'motion', 'security', 'layout', 'perf',
|
|
1349
2213
|
'docs', 'documentation', 'meta', 'search', 'skills', 'workflow', 'wc', 'production', 'atelier',
|
|
2214
|
+
'dashboard', 'replay',
|
|
1350
2215
|
];
|
|
1351
2216
|
const q = String(unknown || '').toLowerCase();
|
|
1352
2217
|
function editDistance(a, b) {
|
|
@@ -1392,23 +2257,25 @@ const ALIASES = {
|
|
|
1392
2257
|
const resolvedCommand = ALIASES[command] || command;
|
|
1393
2258
|
|
|
1394
2259
|
switch (resolvedCommand) {
|
|
1395
|
-
case 'init': init(); break;
|
|
2260
|
+
case 'init': await init(); break;
|
|
1396
2261
|
case 'build': await build(); break;
|
|
1397
2262
|
case 'production': {
|
|
1398
2263
|
const { runProduction } = await import('./production/run.js');
|
|
1399
2264
|
await runProduction({ args: args.slice(1), helpers: { getArg, hasFlag, C } });
|
|
1400
2265
|
break;
|
|
1401
2266
|
}
|
|
1402
|
-
case 'themes': themes(); break;
|
|
1403
|
-
case 'add': add(); break;
|
|
1404
|
-
case 'icons': icons(); break;
|
|
2267
|
+
case 'themes': await themes(); break;
|
|
2268
|
+
case 'add': await add(); break;
|
|
2269
|
+
case 'icons': await icons(); break;
|
|
1405
2270
|
case 'blueprint': await blueprintCmd(); break;
|
|
1406
2271
|
case 'create': await createCmd(); break;
|
|
1407
2272
|
case 'serve': await serveCmd(); break;
|
|
1408
2273
|
case 'doctor': await doctorCmd(); break;
|
|
2274
|
+
case 'dashboard': await dashboardCmd(); break;
|
|
2275
|
+
case 'replay': await replayCmd(); break;
|
|
1409
2276
|
case 'check': await checkCmd(); break;
|
|
1410
2277
|
case 'tokens': await tokensCmd(); break;
|
|
1411
|
-
case 'scan': scanCmd(); break;
|
|
2278
|
+
case 'scan': await scanCmd(); break;
|
|
1412
2279
|
case 'prefix': await prefixCmd(); break;
|
|
1413
2280
|
case 'scaffold': await scaffoldCmd(); break;
|
|
1414
2281
|
case 'plan': await planCmd(); break;
|
|
@@ -1416,7 +2283,17 @@ switch (resolvedCommand) {
|
|
|
1416
2283
|
case 'review': await reviewCmd(); break;
|
|
1417
2284
|
case 'transparency': {
|
|
1418
2285
|
const { transparencyCmd } = await import('./transparency.js');
|
|
1419
|
-
await transparencyCmd(args.slice(1), { C, getArg, hasFlag });
|
|
2286
|
+
await transparencyCmd(args.slice(1), { C, getArg, hasFlag, emitExperience });
|
|
2287
|
+
break;
|
|
2288
|
+
}
|
|
2289
|
+
case 'motion': {
|
|
2290
|
+
const { motionCmd } = await import('./motion.js');
|
|
2291
|
+
await motionCmd(args.slice(1), { C, getArg, hasFlag, emitExperience });
|
|
2292
|
+
break;
|
|
2293
|
+
}
|
|
2294
|
+
case 'security': {
|
|
2295
|
+
const { securityCmd } = await import('./security.js');
|
|
2296
|
+
await securityCmd(args.slice(1), { C, getArg, hasFlag, emitExperience });
|
|
1420
2297
|
break;
|
|
1421
2298
|
}
|
|
1422
2299
|
case 'layout': await layoutCmd(); break;
|
|
@@ -1427,6 +2304,6 @@ switch (resolvedCommand) {
|
|
|
1427
2304
|
case 'skills': await skillsCmd(); break;
|
|
1428
2305
|
case 'workflow': await workflowCmd(); break;
|
|
1429
2306
|
case 'wc': await wcCmd(); break;
|
|
1430
|
-
case '--help': case '-h': case undefined: help(); break;
|
|
2307
|
+
case '--help': case '-h': case undefined: await help(); break;
|
|
1431
2308
|
default: unknownCommand(command); break;
|
|
1432
2309
|
}
|