@birdapi/velinstyle 1.2.1 → 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.
Files changed (156) hide show
  1. package/README.de.md +148 -11
  2. package/README.md +174 -16
  3. package/cli/__fixtures__/atelier-library/showcases/01-login/app.css +1 -0
  4. package/cli/__fixtures__/atelier-library/showcases/01-login/app.js +2 -0
  5. package/cli/__fixtures__/atelier-library/showcases/01-login/index.html +5 -0
  6. package/cli/__fixtures__/atelier-library/showcases/04-pricing/app.css +1 -0
  7. package/cli/__fixtures__/atelier-library/showcases/04-pricing/app.js +2 -0
  8. package/cli/__fixtures__/atelier-library/showcases/04-pricing/index.html +5 -0
  9. package/cli/__fixtures__/atelier-library/showcases/36-calendar/app.css +1 -0
  10. package/cli/__fixtures__/atelier-library/showcases/36-calendar/app.js +2 -0
  11. package/cli/__fixtures__/atelier-library/showcases/36-calendar/index.html +5 -0
  12. package/cli/atelier-catalog.json +1267 -0
  13. package/cli/atelier-formats.js +159 -0
  14. package/cli/atelier.js +382 -0
  15. package/cli/blueprints/empty-state.html +3 -5
  16. package/cli/cli-manifest.json +441 -161
  17. package/cli/docgen/extract-cli.js +2 -1
  18. package/cli/docs-generate.js +9 -0
  19. package/cli/experience.js +173 -0
  20. package/cli/index.js +1384 -361
  21. package/cli/motion.js +738 -0
  22. package/cli/production/component-graph.json +166 -0
  23. package/cli/production/explain.js +52 -0
  24. package/cli/production/extract.js +278 -0
  25. package/cli/production/graph.js +102 -0
  26. package/cli/production/report.js +168 -0
  27. package/cli/production/run.js +355 -0
  28. package/cli/production/trim-css.js +177 -0
  29. package/cli/production/trim-fonts.js +23 -0
  30. package/cli/production/trim-icons.js +38 -0
  31. package/cli/production/trim-js.js +48 -0
  32. package/cli/production/trim-motion.js +22 -0
  33. package/cli/production/trim-themes.js +50 -0
  34. package/cli/production/watch.js +38 -0
  35. package/cli/review.js +48 -1
  36. package/cli/scripts/write-atelier-fixtures.mjs +33 -0
  37. package/cli/security.js +190 -0
  38. package/cli/skills.js +53 -10
  39. package/cli/transparency.js +140 -41
  40. package/cli/workflow.js +32 -17
  41. package/components/index.js +3 -0
  42. package/components/runtime/component-loaders.js +3 -0
  43. package/components/velin-drawer.js +43 -4
  44. package/components/velin-empty-state.js +83 -0
  45. package/components/velin-modal.js +76 -15
  46. package/components/velin-otp-input.js +220 -0
  47. package/components/velin-password-strength.js +147 -0
  48. package/components/velin-sheet.js +49 -4
  49. package/components/velin-theme-toggle.js +15 -1
  50. package/core/a11y/component-contracts.json +391 -255
  51. package/core/attributes/registry.js +1 -1
  52. package/core/meta/knowledge/components.json +75 -3
  53. package/core/motion/analyze.js +38 -0
  54. package/core/motion/blueprint.js +51 -0
  55. package/core/motion/composer.js +114 -0
  56. package/core/motion/data/registry.json +352 -0
  57. package/core/motion/effects.js +17 -1
  58. package/core/motion/index.js +112 -9
  59. package/core/motion/optimize.js +44 -0
  60. package/core/motion/policy.js +77 -0
  61. package/core/motion/registry.js +131 -0
  62. package/core/motion/review.js +59 -0
  63. package/core/motion/scan.js +150 -0
  64. package/core/motion/timeline.js +68 -0
  65. package/core/motion/transitions.js +49 -0
  66. package/core/motion/triggers.js +168 -0
  67. package/core/security/checks/fs.js +92 -0
  68. package/core/security/detect.js +35 -0
  69. package/core/security/engine.js +116 -0
  70. package/core/security/index.js +18 -0
  71. package/core/security/registry.js +85 -0
  72. package/core/security/report/report.js +113 -0
  73. package/core/security/rules/ci/index.js +146 -0
  74. package/core/security/rules/consumer/index.js +174 -0
  75. package/core/security/rules/deps/index.js +182 -0
  76. package/core/security/rules/index.js +24 -0
  77. package/core/security/rules/publish/index.js +230 -0
  78. package/core/security/rules/repo/index.js +143 -0
  79. package/core/security/rules/secrets/index.js +133 -0
  80. package/core/security/score/dependency-health.js +53 -0
  81. package/core/security/score/release-health.js +66 -0
  82. package/core/security/score/score.js +102 -0
  83. package/core/security/timeline/timeline.js +76 -0
  84. package/dist/chunks/attributes-2ORR27KA.js +404 -0
  85. package/dist/chunks/attributes-HK7VIOLA.js +1080 -0
  86. package/dist/chunks/attributes-VRHHVNDO.js +1080 -0
  87. package/dist/chunks/chunk-MYKUI364.js +116 -0
  88. package/dist/chunks/chunk-NEVBPD5T.js +116 -0
  89. package/dist/chunks/chunk-Y6HN7HWX.js +116 -0
  90. package/dist/chunks/runtime-entry.js +1 -1
  91. package/dist/chunks/velin-drawer-A7Q7EBOS.js +162 -0
  92. package/dist/chunks/velin-empty-state-3NTUH2RF.js +81 -0
  93. package/dist/chunks/velin-modal-3MV4FYBK.js +231 -0
  94. package/dist/chunks/velin-otp-input-PSK42MM6.js +207 -0
  95. package/dist/chunks/velin-password-strength-TAXMLSED.js +136 -0
  96. package/dist/chunks/velin-sheet-N2VVYXFP.js +157 -0
  97. package/dist/chunks/velin-theme-toggle-J2NHC7IM.js +304 -0
  98. package/dist/llms.txt +4 -4
  99. package/dist/search-index.json +232 -5
  100. package/dist/velin-agent.json +433 -46
  101. package/dist/velinstyle-components.iife.js +3695 -2272
  102. package/dist/velinstyle-components.js +3698 -2271
  103. package/dist/velinstyle-components.min.js +262 -120
  104. package/dist/velinstyle.css +96 -13
  105. package/dist/velinstyle.d.ts +3 -0
  106. package/dist/velinstyle.min.css +1 -1
  107. package/package.json +19 -4
  108. package/packages/velinstyle-cli-core/package.json +11 -0
  109. package/packages/velinstyle-cli-core/src/config.js +84 -0
  110. package/packages/velinstyle-cli-core/src/contract.js +56 -0
  111. package/packages/velinstyle-cli-core/src/event-bus.js +50 -0
  112. package/packages/velinstyle-cli-core/src/index.js +18 -0
  113. package/packages/velinstyle-cli-core/src/lifecycle.js +33 -0
  114. package/packages/velinstyle-cli-core/src/runner.js +129 -0
  115. package/packages/velinstyle-cli-registry/data/commands.json +1216 -0
  116. package/packages/velinstyle-cli-registry/data/layouts.json +33 -0
  117. package/packages/velinstyle-cli-registry/data/widgets.json +35 -0
  118. package/packages/velinstyle-cli-registry/package.json +13 -0
  119. package/packages/velinstyle-cli-registry/src/index.js +88 -0
  120. package/packages/velinstyle-cli-renderer/package.json +8 -0
  121. package/packages/velinstyle-cli-renderer/src/index.js +286 -0
  122. package/packages/velinstyle-cli-theme/package.json +8 -0
  123. package/packages/velinstyle-cli-theme/src/index.js +84 -0
  124. package/packages/velinstyle-cli-ui/package.json +8 -0
  125. package/packages/velinstyle-cli-ui/src/index.js +34 -0
  126. package/packages/velinstyle-motion/README.md +81 -0
  127. package/packages/velinstyle-motion/package.json +37 -0
  128. package/packages/velinstyle-motion/src/ai/mini-prompt.js +119 -0
  129. package/packages/velinstyle-motion/src/ai/provider.js +59 -0
  130. package/packages/velinstyle-motion/src/ai/providers/pixverse-setup.js +402 -0
  131. package/packages/velinstyle-motion/src/ai/providers/pixverse.js +245 -0
  132. package/packages/velinstyle-motion/src/config.js +257 -0
  133. package/packages/velinstyle-motion/src/export/html-clip.js +141 -0
  134. package/packages/velinstyle-motion/src/index.js +304 -0
  135. package/packages/velinstyle-motion/src/jobs/cache.js +30 -0
  136. package/packages/velinstyle-motion/src/jobs/queue.js +69 -0
  137. package/packages/velinstyle-motion/src/jobs/usage.js +53 -0
  138. package/packages/velinstyle-motion/src/local/effects.js +139 -0
  139. package/packages/velinstyle-motion/src/local/engine.js +96 -0
  140. package/packages/velinstyle-motion/src/providers-catalog.js +44 -0
  141. package/packages/velinstyle-motion/src/registry/presets.js +25 -0
  142. package/packages/velinstyle-motion/src/registry/presets.json +85 -0
  143. package/packages/velinstyle-motion/src/ux/cost-gate.js +37 -0
  144. package/packages/velinstyle-motion/src/ux/pixverse-gate.js +153 -0
  145. package/packages/velinstyle-motion/src/ux/wizard.js +171 -0
  146. package/packages/velinstyle-motion/src/validate-image.js +91 -0
  147. package/packages/velinstyle-skills/catalog.json +1 -1
  148. package/packages/velinstyle-skills/registry.json +1 -142
  149. package/packages/velinstyle-skills/skills/velin-motion-reduced-safe/SKILL.md +1 -1
  150. package/packages/velinstyle-skills/skills/velin-motion-reveal-feedback/SKILL.md +1 -1
  151. package/src/components/data-table.css +19 -0
  152. package/src/components/empty-auth.css +33 -0
  153. package/src/components/table.css +16 -6
  154. package/src/tokens/motion.css +7 -0
  155. package/src/utilities/scroll-animation.css +66 -0
  156. package/src/velinstyle.css +1 -0
package/cli/motion.js ADDED
@@ -0,0 +1,738 @@
1
+ /**
2
+ * CLI: velinstyle motion list|presets|doctor|…|create|atelier|transition|sequence|usage|…
3
+ */
4
+ import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync, statSync, copyFileSync } from 'fs';
5
+ import { join, resolve, basename, relative } from 'path';
6
+ import { fileURLToPath } from 'url';
7
+ import {
8
+ catalogSummary,
9
+ listPresets,
10
+ listPolicies,
11
+ listTriggers,
12
+ listBlueprints,
13
+ listTimelines,
14
+ listMotion,
15
+ getPolicy,
16
+ validatePolicy,
17
+ doctorMotionHtml,
18
+ scanMotionHtml,
19
+ analyzeMotionHtml,
20
+ reviewMotionHtml,
21
+ optimizeMotionHtml,
22
+ emitMotionBlueprint,
23
+ NAMESPACES,
24
+ } from '../core/motion/index.js';
25
+ import {
26
+ createMotionClip,
27
+ createMotionTransition,
28
+ createMotionSequence,
29
+ createAtelierBatch,
30
+ createEffectClip,
31
+ listClipPresets,
32
+ getUsageSummary,
33
+ runCreateWizard,
34
+ runAtelierWizard,
35
+ listImageFiles,
36
+ ensureMotionDirs,
37
+ loadMotionConfig,
38
+ runPixVerseSetup,
39
+ listMotionProviders,
40
+ formatMotionProvidersText,
41
+ redactSecrets,
42
+ } from '../packages/velinstyle-motion/src/index.js';
43
+
44
+ const PKG_ROOT = join(fileURLToPath(new URL('.', import.meta.url)), '..');
45
+
46
+ const CLIP_SUBS = new Set([
47
+ 'create', 'atelier', 'transition', 'sequence', 'export', 'usage',
48
+ 'zoom', 'pan', 'rotate', 'particles', 'intro', 'outro',
49
+ 'setup', 'providers',
50
+ ]);
51
+
52
+ function collectHtmlFiles(target) {
53
+ const abs = resolve(target || '.');
54
+ if (!existsSync(abs)) throw new Error(`Path not found: ${abs}`);
55
+ const st = statSync(abs);
56
+ if (st.isFile()) return [abs];
57
+ const out = [];
58
+ const walk = (dir) => {
59
+ for (const name of readdirSync(dir)) {
60
+ if (name === 'node_modules' || name === '.git' || name === 'dist' || name === 'vendor') continue;
61
+ const p = join(dir, name);
62
+ const s = statSync(p);
63
+ if (s.isDirectory()) walk(p);
64
+ else if (/\.html?$/i.test(name) || /\.blade\.php$/i.test(name)) out.push(p);
65
+ }
66
+ };
67
+ walk(abs);
68
+ return out;
69
+ }
70
+
71
+ function readAllHtml(target) {
72
+ const files = collectHtmlFiles(target);
73
+ return {
74
+ files,
75
+ html: files.map((f) => readFileSync(f, 'utf-8')).join('\n'),
76
+ };
77
+ }
78
+
79
+ /**
80
+ * @param {string[]} args
81
+ * @param {{ C: object, getArg: Function, hasFlag: Function, emitExperience: Function }} util
82
+ */
83
+ export async function motionCmd(args, util) {
84
+ const { getArg, hasFlag, emitExperience } = util;
85
+ const sub = args[0] || 'list';
86
+ const rest = args.slice(1);
87
+ const target = rest.find((a) => !a.startsWith('-')) || '.';
88
+ const asJson = hasFlag('--json');
89
+ const present = (payload) => emitExperience('motion', payload);
90
+
91
+ try {
92
+ if (CLIP_SUBS.has(sub)) {
93
+ await runClipCommand(sub, rest, { getArg, hasFlag, asJson, present });
94
+ return;
95
+ }
96
+
97
+ if (sub === 'list' || sub === '--help' || sub === '-h') {
98
+ if (sub !== 'list' && (sub === '--help' || sub === '-h' || !args[0])) {
99
+ await present({
100
+ message: 'Usage: velinstyle motion <list|presets|create|atelier|doctor|scan|…>',
101
+ suggestions: [
102
+ { text: 'velinstyle motion create image.webp' },
103
+ { text: 'velinstyle motion presets --clips' },
104
+ { text: 'velinstyle motion doctor .' },
105
+ { text: 'velinstyle motion analyze .' },
106
+ ],
107
+ });
108
+ return;
109
+ }
110
+ const summary = catalogSummary();
111
+ if (asJson) {
112
+ console.log(JSON.stringify(summary, null, 2));
113
+ return;
114
+ }
115
+ await present({
116
+ subtitle: 'list',
117
+ message: `Motion Registry v${summary.version}`,
118
+ items: Object.entries(summary.counts).map(([k, v]) => ({ label: k, value: String(v) })),
119
+ table: {
120
+ columns: ['Namespace', 'Count'],
121
+ rows: Object.entries(summary.counts).map(([k, v]) => [k, String(v)]),
122
+ },
123
+ notifications: [
124
+ { tone: 'info', message: 'Page Motion: presets · timelines · triggers · policies · blueprints' },
125
+ { tone: 'info', message: 'Clip Motion (1.3): motion create · atelier · providers · setup pixverse · usage' },
126
+ ],
127
+ });
128
+ return;
129
+ }
130
+
131
+ if (sub === 'presets') {
132
+ if (hasFlag('--clips')) {
133
+ const clips = listClipPresets();
134
+ if (asJson) {
135
+ console.log(JSON.stringify(clips, null, 2));
136
+ return;
137
+ }
138
+ await present({
139
+ subtitle: 'clip-presets',
140
+ message: `${clips.length} clip presets`,
141
+ table: {
142
+ columns: ['Id', 'Engine', 'Status', 'Title'],
143
+ rows: clips.map((p) => [p.id, p.engine || '', p.status || '', p.title || '']),
144
+ },
145
+ });
146
+ return;
147
+ }
148
+ const items = listPresets();
149
+ if (asJson) {
150
+ console.log(JSON.stringify(items, null, 2));
151
+ return;
152
+ }
153
+ await present({
154
+ subtitle: 'presets',
155
+ message: `${items.length} presets`,
156
+ table: {
157
+ columns: ['Id', 'Category', 'Status', 'Title'],
158
+ rows: items.map((p) => [p.id, p.category || '', p.status || '', p.title || '']),
159
+ },
160
+ });
161
+ return;
162
+ }
163
+
164
+ if (sub === 'policy') {
165
+ const action = rest[0] && !rest[0].startsWith('-') ? rest[0] : 'list';
166
+ if (action === 'list' || action === 'list'.toString()) {
167
+ const policies = listPolicies();
168
+ if (asJson) {
169
+ console.log(JSON.stringify(policies, null, 2));
170
+ return;
171
+ }
172
+ await present({
173
+ subtitle: 'policy',
174
+ message: `${policies.length} policies`,
175
+ table: {
176
+ columns: ['Id', 'Title', 'Max ms'],
177
+ rows: policies.map((p) => [p.id, p.title || '', String(p.maxDurationMs ?? '—')]),
178
+ },
179
+ });
180
+ return;
181
+ }
182
+ if (action === 'show' || action === 'validate') {
183
+ const id = rest[1] || rest[0];
184
+ const policyId = action === 'show' || action === 'validate' ? (rest[1] || getArg('--id')) : action;
185
+ const resolved = validatePolicy(policyId === 'show' || policyId === 'validate' ? rest[1] : policyId);
186
+ // simplify: velinstyle motion policy safe
187
+ const pid = ['list', 'show', 'validate'].includes(action) ? rest[1] : action;
188
+ const result = validatePolicy(pid);
189
+ if (!result.ok) {
190
+ await present({ status: 'ERROR', message: result.error, exitCode: 1 });
191
+ return;
192
+ }
193
+ if (asJson) {
194
+ console.log(JSON.stringify(result.policy, null, 2));
195
+ return;
196
+ }
197
+ await present({
198
+ subtitle: 'policy',
199
+ message: result.policy.title || result.policy.id,
200
+ items: [
201
+ { label: 'Id', value: result.policy.id },
202
+ { label: 'Max duration', value: String(result.policy.maxDurationMs ?? '—') },
203
+ { label: 'Status', value: result.policy.status || '—' },
204
+ ],
205
+ notifications: [{ tone: 'info', message: result.policy.description || '' }],
206
+ });
207
+ return;
208
+ }
209
+ const result = validatePolicy(action);
210
+ if (!result.ok) {
211
+ await present({ status: 'ERROR', message: result.error, exitCode: 1 });
212
+ return;
213
+ }
214
+ await present({
215
+ subtitle: 'policy',
216
+ message: result.policy.title,
217
+ notifications: [{ tone: 'ok', message: result.policy.description }],
218
+ });
219
+ return;
220
+ }
221
+
222
+ if (sub === 'demo') {
223
+ const gallery = join(PKG_ROOT, 'samples', 'motion-gallery.html');
224
+ await present({
225
+ subtitle: 'demo',
226
+ message: 'Motion Playground / Gallery',
227
+ items: [
228
+ { label: 'Gallery', value: existsSync(gallery) ? gallery : 'samples/motion-gallery.html' },
229
+ { label: 'Showcase', value: 'Gallery → Code → Preview → A11y → Performance → CLI → Studio' },
230
+ ],
231
+ suggestions: [
232
+ { text: `velinstyle serve ${existsSync(gallery) ? 'samples' : '.'}` },
233
+ { text: 'Open samples/motion-gallery.html' },
234
+ ],
235
+ });
236
+ return;
237
+ }
238
+
239
+ if (sub === 'doctor') {
240
+ const { files, html } = readAllHtml(target);
241
+ if (!files.length) {
242
+ await present({ status: 'WARNING', message: 'No HTML files found', exitCode: 1 });
243
+ return;
244
+ }
245
+ const report = doctorMotionHtml(html);
246
+ if (asJson) {
247
+ console.log(JSON.stringify(report, null, 2));
248
+ process.exitCode = report.ok ? 0 : 1;
249
+ return;
250
+ }
251
+ await present({
252
+ subtitle: 'doctor',
253
+ status: report.ok ? (report.summary.warnings ? 'WARNING' : 'PASS') : 'ERROR',
254
+ message: report.ok
255
+ ? `motion doctor: ok (${report.summary.elements} elements)`
256
+ : `motion doctor: ${report.summary.errors} error(s)`,
257
+ items: [
258
+ { label: 'Files', value: String(files.length) },
259
+ { label: 'Elements', value: String(report.summary.elements) },
260
+ { label: 'Errors', value: String(report.summary.errors) },
261
+ { label: 'Warnings', value: String(report.summary.warnings) },
262
+ ],
263
+ table: {
264
+ columns: ['Sev', 'Code', 'Message'],
265
+ rows: report.findings.slice(0, 25).map((f) => [f.severity, f.code, String(f.message).slice(0, 48)]),
266
+ },
267
+ exitCode: report.ok ? undefined : 1,
268
+ });
269
+ return;
270
+ }
271
+
272
+ if (sub === 'scan') {
273
+ const { files, html } = readAllHtml(target);
274
+ const report = scanMotionHtml(html);
275
+ if (asJson) {
276
+ console.log(JSON.stringify(report, null, 2));
277
+ return;
278
+ }
279
+ await present({
280
+ subtitle: 'scan',
281
+ message: `${report.summary.elements} motion elements · ${report.summary.presets} presets · ${report.summary.triggers} triggers`,
282
+ items: [
283
+ { label: 'Files', value: String(files.length) },
284
+ { label: 'Elements', value: String(report.summary.elements) },
285
+ { label: 'Presets', value: String(report.summary.presets) },
286
+ { label: 'Infinite', value: String(report.summary.infinite) },
287
+ ],
288
+ table: {
289
+ columns: ['Preset', 'Count'],
290
+ rows: Object.entries(report.presets || {}).map(([k, v]) => [k, String(v)]),
291
+ },
292
+ });
293
+ return;
294
+ }
295
+
296
+ if (sub === 'analyze') {
297
+ const { files, html } = readAllHtml(target);
298
+ const report = analyzeMotionHtml(html);
299
+ if (asJson) {
300
+ console.log(JSON.stringify(report, null, 2));
301
+ return;
302
+ }
303
+ const m = report.metrics;
304
+ await present({
305
+ subtitle: 'analyze',
306
+ status: report.ok ? 'PASS' : 'WARNING',
307
+ message: `Animations ${m.animations} · Duration ${m.durationLabel}`,
308
+ items: [
309
+ { label: 'Animations', value: String(m.animations) },
310
+ { label: 'Duration', value: m.durationLabel },
311
+ { label: 'Average', value: `${m.averageMs} ms` },
312
+ { label: 'Longest', value: `${m.longestMs} ms` },
313
+ { label: 'Reduced Motion', value: m.reducedMotion },
314
+ { label: 'Infinite', value: String(m.infinite) },
315
+ { label: 'Layout Shift Risk', value: m.layoutShiftRisk },
316
+ ],
317
+ project: { files: files.length },
318
+ });
319
+ return;
320
+ }
321
+
322
+ if (sub === 'review') {
323
+ const { html } = readAllHtml(target);
324
+ const report = reviewMotionHtml(html);
325
+ if (asJson) {
326
+ console.log(JSON.stringify(report, null, 2));
327
+ process.exitCode = report.gate === 'fail' ? 1 : 0;
328
+ return;
329
+ }
330
+ await present({
331
+ layout: 'review',
332
+ subtitle: 'review',
333
+ status: report.gate === 'fail' ? 'ERROR' : report.gate === 'warn' ? 'WARNING' : 'PASS',
334
+ message: `gate ${report.gate} · motion score ${report.scores.motion}`,
335
+ stats: report.scores,
336
+ items: Object.entries(report.metrics).map(([k, v]) => ({ label: k, value: String(v) })),
337
+ table: {
338
+ columns: ['Sev', 'Code', 'Message'],
339
+ rows: report.findings.slice(0, 20).map((f) => [f.severity, f.code, String(f.message).slice(0, 48)]),
340
+ },
341
+ exitCode: report.gate === 'fail' ? 1 : undefined,
342
+ });
343
+ return;
344
+ }
345
+
346
+ if (sub === 'report') {
347
+ const { files, html } = readAllHtml(target);
348
+ const analysis = analyzeMotionHtml(html);
349
+ const outDir = resolve(getArg('--out') || 'motion-report');
350
+ mkdirSync(outDir, { recursive: true });
351
+ const payload = {
352
+ schema: 'velinstyle.motion.report',
353
+ version: 1,
354
+ files: files.map((f) => relative(process.cwd(), f)),
355
+ ...analysis,
356
+ catalog: catalogSummary(),
357
+ };
358
+ writeFileSync(join(outDir, 'index.json'), JSON.stringify(payload, null, 2));
359
+ await present({
360
+ subtitle: 'report',
361
+ message: `Wrote motion report → ${outDir}`,
362
+ items: [
363
+ { label: 'Files', value: String(files.length) },
364
+ { label: 'Animations', value: String(analysis.metrics.animations) },
365
+ ],
366
+ });
367
+ return;
368
+ }
369
+
370
+ if (sub === 'optimize') {
371
+ const write = hasFlag('--write');
372
+ const files = collectHtmlFiles(target);
373
+ let changedFiles = 0;
374
+ const rows = [];
375
+ for (const file of files) {
376
+ const html = readFileSync(file, 'utf-8');
377
+ const result = optimizeMotionHtml(html);
378
+ if (result.changed) {
379
+ changedFiles += 1;
380
+ rows.push([basename(file), result.changes.slice(0, 2).join('; ')]);
381
+ if (write) writeFileSync(file, result.html, 'utf-8');
382
+ }
383
+ }
384
+ if (asJson) {
385
+ console.log(JSON.stringify({ changedFiles, dryRun: !write, rows }, null, 2));
386
+ return;
387
+ }
388
+ await present({
389
+ subtitle: 'optimize',
390
+ message: changedFiles
391
+ ? `${write ? 'Updated' : 'Would update'} ${changedFiles} file(s)`
392
+ : 'No safe optimizations',
393
+ table: rows.length ? { columns: ['File', 'Changes'], rows: rows.slice(0, 30) } : null,
394
+ suggestions: write ? [] : [{ text: 'Re-run with --write to apply' }],
395
+ });
396
+ return;
397
+ }
398
+
399
+ if (sub === 'blueprint') {
400
+ const kind = rest[0];
401
+ if (!kind || kind === 'list') {
402
+ const bps = listBlueprints();
403
+ await present({
404
+ subtitle: 'blueprint',
405
+ message: `${bps.length} blueprints`,
406
+ table: {
407
+ columns: ['Id', 'Status', 'Title'],
408
+ rows: bps.map((b) => [b.id, b.status || '', b.title || '']),
409
+ },
410
+ });
411
+ return;
412
+ }
413
+ const result = emitMotionBlueprint(kind);
414
+ if (!result.ok) {
415
+ await present({ status: 'ERROR', message: result.error, exitCode: 1 });
416
+ return;
417
+ }
418
+ const out = getArg('--output', '-o');
419
+ if (out) {
420
+ writeFileSync(resolve(out), result.html, 'utf-8');
421
+ await present({
422
+ subtitle: 'blueprint',
423
+ message: `Wrote ${resolve(out)}`,
424
+ items: [
425
+ { label: 'Kind', value: kind },
426
+ { label: 'Policy', value: result.blueprint.policy || 'safe' },
427
+ ],
428
+ });
429
+ return;
430
+ }
431
+ console.log(result.html);
432
+ return;
433
+ }
434
+
435
+ // namespace dump for studio: velinstyle motion timelines
436
+ if (NAMESPACES.includes(sub) || NAMESPACES.includes(`${sub}s`)) {
437
+ const ns = NAMESPACES.includes(sub) ? sub : `${sub}s`;
438
+ const items = listMotion(ns);
439
+ if (asJson) {
440
+ console.log(JSON.stringify(items, null, 2));
441
+ return;
442
+ }
443
+ await present({
444
+ subtitle: ns,
445
+ message: `${items.length} ${ns}`,
446
+ table: {
447
+ columns: ['Id', 'Status', 'Title'],
448
+ rows: items.map((i) => [i.id, i.status || '', i.title || '']),
449
+ },
450
+ });
451
+ return;
452
+ }
453
+
454
+ await present({
455
+ status: 'ERROR',
456
+ message: `Unknown motion subcommand: ${sub}`,
457
+ suggestions: [{ text: 'velinstyle motion list' }],
458
+ exitCode: 1,
459
+ });
460
+ } catch (e) {
461
+ await present({
462
+ status: 'ERROR',
463
+ message: /** @type {Error} */ (e).message || String(e),
464
+ exitCode: 1,
465
+ });
466
+ }
467
+ }
468
+
469
+ /**
470
+ * Clip pipeline commands (1.3.0 — @birdapi/velinstyle-motion).
471
+ */
472
+ async function runClipCommand(sub, rest, { getArg, hasFlag, asJson, present }) {
473
+ const yes = hasFlag('--yes') || hasFlag('-y');
474
+ const ai = hasFlag('--ai');
475
+ const dryRun = hasFlag('--dry-run') || process.env.VELIN_MOTION_DRY_RUN === '1';
476
+ const out = getArg('--out') || getArg('--output') || getArg('-o');
477
+ const preset = getArg('--preset');
478
+ const provider = getArg('--provider');
479
+ const model = getArg('--model');
480
+ const quality = getArg('--quality');
481
+ const duration = getArg('--duration');
482
+ const style = getArg('--style');
483
+ const prompt = getArg('--prompt');
484
+ const positional = rest.filter((a) => !a.startsWith('-'));
485
+
486
+ if (sub === 'providers') {
487
+ const list = listMotionProviders();
488
+ if (asJson) {
489
+ console.log(JSON.stringify(redactSecrets(list), null, 2));
490
+ return;
491
+ }
492
+ process.stdout.write(formatMotionProvidersText());
493
+ return;
494
+ }
495
+
496
+ if (sub === 'setup') {
497
+ const target = (positional[0] || 'pixverse').toLowerCase();
498
+ if (target !== 'pixverse') {
499
+ throw new Error(`Unknown provider setup: ${target}. Supported: pixverse`);
500
+ }
501
+ const result = await runPixVerseSetup({
502
+ yes,
503
+ skipNetwork: dryRun || yes,
504
+ });
505
+ if (asJson) {
506
+ console.log(JSON.stringify(redactSecrets(result), null, 2));
507
+ return;
508
+ }
509
+ await present({
510
+ subtitle: 'setup pixverse',
511
+ status: 'PASS',
512
+ message: 'PixVerse Setup abgeschlossen',
513
+ items: [
514
+ { label: 'Provider', value: 'pixverse' },
515
+ { label: 'setup_completed', value: 'true' },
516
+ { label: 'API key', value: result.apiKeyPresent ? 'present' : 'missing' },
517
+ ],
518
+ });
519
+ return;
520
+ }
521
+
522
+ if (sub === 'usage') {
523
+ const summary = getUsageSummary(process.cwd());
524
+ if (asJson) {
525
+ console.log(JSON.stringify(summary, null, 2));
526
+ return;
527
+ }
528
+ await present({
529
+ subtitle: 'usage',
530
+ message: 'Motion Usage',
531
+ items: [
532
+ { label: 'Heute Clips', value: String(summary.today.clips) },
533
+ { label: 'Heute AI', value: String(summary.today.ai) },
534
+ { label: 'Woche Clips', value: String(summary.week.clips) },
535
+ { label: 'Woche AI', value: String(summary.week.ai) },
536
+ { label: 'PixVerse Credits (Hint)', value: String(summary.pixverse.creditsUsedHint) },
537
+ ],
538
+ });
539
+ return;
540
+ }
541
+
542
+ if (sub === 'export') {
543
+ const cfg = loadMotionConfig();
544
+ const src = positional[0] || cfg.output_dir;
545
+ await present({
546
+ subtitle: 'export',
547
+ message: `Export source: ${resolve(src)} — local clips are already HTML/CSS under motion-output; AI jobs write MP4 when provider returns a URL.`,
548
+ suggestions: [{ text: 'velinstyle motion create image.webp --preset soft-zoom --yes' }],
549
+ });
550
+ return;
551
+ }
552
+
553
+ if (['zoom', 'pan', 'rotate', 'particles', 'intro', 'outro'].includes(sub)) {
554
+ const image = positional[0];
555
+ if (!image) throw new Error(`Usage: velinstyle motion ${sub} <image>`);
556
+ const result = await createEffectClip(sub, image, {
557
+ duration: duration ? Number(duration) : undefined,
558
+ outDir: out,
559
+ yes: true,
560
+ });
561
+ if (asJson) {
562
+ console.log(JSON.stringify(result, null, 2));
563
+ return;
564
+ }
565
+ await present({
566
+ subtitle: sub,
567
+ message: `Local clip → ${result.htmlPath}`,
568
+ items: [
569
+ { label: 'Engine', value: 'local' },
570
+ { label: 'Duration', value: `${(result.durationMs || 0) / 1000}s` },
571
+ ],
572
+ });
573
+ return;
574
+ }
575
+
576
+ if (sub === 'create') {
577
+ const image = positional[0];
578
+ if (!image) throw new Error('Usage: velinstyle motion create <image> [--preset …] [--ai] [--yes]');
579
+ ensureMotionDirs();
580
+ const plan = await runCreateWizard({
581
+ image,
582
+ ai: ai || style === 'ai',
583
+ yes: yes || asJson || Boolean(preset),
584
+ flags: {
585
+ preset,
586
+ provider,
587
+ model,
588
+ quality,
589
+ duration,
590
+ style: style || (ai ? 'ai' : undefined),
591
+ prompt,
592
+ out,
593
+ },
594
+ });
595
+ const result = await createMotionClip({
596
+ ...plan,
597
+ yes: yes || asJson,
598
+ quiet: asJson,
599
+ json: asJson,
600
+ dryRun,
601
+ outDir: plan.outDir || out,
602
+ });
603
+ if (asJson) {
604
+ console.log(JSON.stringify(redactSecrets(result), null, 2));
605
+ return;
606
+ }
607
+ await present({
608
+ subtitle: 'create',
609
+ status: 'PASS',
610
+ message: result.htmlPath
611
+ ? `Clip fertig → ${result.htmlPath}`
612
+ : result.videoPath
613
+ ? `Video fertig → ${result.videoPath}`
614
+ : result.manifestPath
615
+ ? `AI dry-run → ${result.manifestPath}`
616
+ : `Job ${result.job?.id || ''} fertig`,
617
+ items: [
618
+ { label: 'Engine', value: result.engine || plan.engine },
619
+ { label: 'Preset', value: plan.preset || '—' },
620
+ { label: 'Job', value: result.job?.id || '—' },
621
+ ],
622
+ });
623
+ return;
624
+ }
625
+
626
+ if (sub === 'atelier') {
627
+ const dir = positional[0] || '.';
628
+ const images = listImageFiles(resolve(dir));
629
+ const plan = await runAtelierWizard({
630
+ assetCount: images.length,
631
+ yes: yes || asJson,
632
+ ai: ai || style === 'ai',
633
+ count: getArg('--count') ? Number(getArg('--count')) : undefined,
634
+ preset: preset || 'atelier',
635
+ duration: duration ? Number(duration) : undefined,
636
+ outDir: out,
637
+ });
638
+ const batch = await createAtelierBatch({
639
+ dir,
640
+ count: plan.count,
641
+ preset: plan.preset,
642
+ engine: plan.engine,
643
+ provider: plan.provider,
644
+ duration: plan.duration,
645
+ quality: plan.quality,
646
+ outDir: plan.outDir || out,
647
+ yes: yes || asJson,
648
+ json: asJson,
649
+ dryRun,
650
+ });
651
+ if (asJson) {
652
+ console.log(JSON.stringify(redactSecrets(batch), null, 2));
653
+ return;
654
+ }
655
+ await present({
656
+ subtitle: 'atelier',
657
+ message: `Motion Batch fertig — ${batch.count} Clips · ${batch.totalSec}s Gesamtmaterial`,
658
+ items: [
659
+ { label: 'Bilder', value: String(batch.images) },
660
+ { label: 'Clips', value: String(batch.count) },
661
+ { label: 'Output', value: batch.outDir },
662
+ ],
663
+ });
664
+ return;
665
+ }
666
+
667
+ if (sub === 'transition') {
668
+ const [a, b] = positional;
669
+ if (!a || !b) throw new Error('Usage: velinstyle motion transition <imageA> <imageB>');
670
+ const result = await createMotionTransition({
671
+ imageA: a,
672
+ imageB: b,
673
+ engine: ai || style === 'ai' ? 'ai' : 'local',
674
+ provider,
675
+ model,
676
+ quality,
677
+ duration: duration ? Number(duration) : undefined,
678
+ prompt,
679
+ outDir: out,
680
+ yes: yes || asJson,
681
+ json: asJson,
682
+ dryRun,
683
+ });
684
+ if (asJson) {
685
+ console.log(JSON.stringify(redactSecrets(result), null, 2));
686
+ return;
687
+ }
688
+ await present({
689
+ subtitle: 'transition',
690
+ message: result.htmlPath || result.videoPath || result.manifestPath || 'Transition done',
691
+ });
692
+ return;
693
+ }
694
+
695
+ if (sub === 'sequence') {
696
+ if (positional.length < 2) throw new Error('Usage: velinstyle motion sequence <f1> <f2> […fn]');
697
+ const result = await createMotionSequence({
698
+ frames: positional,
699
+ provider: provider || 'pixverse',
700
+ model,
701
+ quality,
702
+ duration: duration ? Number(duration) : undefined,
703
+ prompt,
704
+ outDir: out,
705
+ yes: yes || asJson,
706
+ json: asJson,
707
+ dryRun,
708
+ });
709
+ if (asJson) {
710
+ console.log(JSON.stringify(redactSecrets(result), null, 2));
711
+ return;
712
+ }
713
+ await present({
714
+ subtitle: 'sequence',
715
+ message: result.videoPath || result.manifestPath || 'Sequence done',
716
+ });
717
+ return;
718
+ }
719
+
720
+ throw new Error(`Unknown clip subcommand: ${sub}`);
721
+ }
722
+
723
+ /**
724
+ * Lightweight step for velinstyle check.
725
+ */
726
+ export async function motionCheckStep(path, { quiet, C } = {}) {
727
+ try {
728
+ const { html, files } = readAllHtml(path);
729
+ if (!files.length) return { ok: true, skipped: true };
730
+ const report = doctorMotionHtml(html);
731
+ if (!quiet && C) {
732
+ console.log(` motion: ${report.summary.elements} elements · ${report.ok ? C.green('ok') : C.red('issues')}`);
733
+ }
734
+ return { ok: report.ok, ...report.summary };
735
+ } catch {
736
+ return { ok: true, skipped: true };
737
+ }
738
+ }