@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.
- package/README.de.md +148 -11
- package/README.md +174 -16
- package/cli/__fixtures__/atelier-library/showcases/01-login/app.css +1 -0
- package/cli/__fixtures__/atelier-library/showcases/01-login/app.js +2 -0
- package/cli/__fixtures__/atelier-library/showcases/01-login/index.html +5 -0
- package/cli/__fixtures__/atelier-library/showcases/04-pricing/app.css +1 -0
- package/cli/__fixtures__/atelier-library/showcases/04-pricing/app.js +2 -0
- package/cli/__fixtures__/atelier-library/showcases/04-pricing/index.html +5 -0
- package/cli/__fixtures__/atelier-library/showcases/36-calendar/app.css +1 -0
- package/cli/__fixtures__/atelier-library/showcases/36-calendar/app.js +2 -0
- package/cli/__fixtures__/atelier-library/showcases/36-calendar/index.html +5 -0
- package/cli/atelier-catalog.json +1267 -0
- package/cli/atelier-formats.js +159 -0
- package/cli/atelier.js +382 -0
- package/cli/blueprints/empty-state.html +3 -5
- package/cli/cli-manifest.json +441 -161
- 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 +1384 -361
- package/cli/motion.js +738 -0
- package/cli/production/component-graph.json +166 -0
- package/cli/production/explain.js +52 -0
- package/cli/production/extract.js +278 -0
- package/cli/production/graph.js +102 -0
- package/cli/production/report.js +168 -0
- package/cli/production/run.js +355 -0
- package/cli/production/trim-css.js +177 -0
- package/cli/production/trim-fonts.js +23 -0
- package/cli/production/trim-icons.js +38 -0
- package/cli/production/trim-js.js +48 -0
- package/cli/production/trim-motion.js +22 -0
- package/cli/production/trim-themes.js +50 -0
- package/cli/production/watch.js +38 -0
- package/cli/review.js +48 -1
- package/cli/scripts/write-atelier-fixtures.mjs +33 -0
- 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/index.js +3 -0
- package/components/runtime/component-loaders.js +3 -0
- package/components/velin-drawer.js +43 -4
- package/components/velin-empty-state.js +83 -0
- package/components/velin-modal.js +76 -15
- package/components/velin-otp-input.js +220 -0
- package/components/velin-password-strength.js +147 -0
- package/components/velin-sheet.js +49 -4
- package/components/velin-theme-toggle.js +15 -1
- package/core/a11y/component-contracts.json +391 -255
- package/core/attributes/registry.js +1 -1
- package/core/meta/knowledge/components.json +75 -3
- 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-2ORR27KA.js +404 -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/chunk-Y6HN7HWX.js +116 -0
- package/dist/chunks/runtime-entry.js +1 -1
- package/dist/chunks/velin-drawer-A7Q7EBOS.js +162 -0
- package/dist/chunks/velin-empty-state-3NTUH2RF.js +81 -0
- package/dist/chunks/velin-modal-3MV4FYBK.js +231 -0
- package/dist/chunks/velin-otp-input-PSK42MM6.js +207 -0
- package/dist/chunks/velin-password-strength-TAXMLSED.js +136 -0
- package/dist/chunks/velin-sheet-N2VVYXFP.js +157 -0
- package/dist/chunks/velin-theme-toggle-J2NHC7IM.js +304 -0
- package/dist/llms.txt +4 -4
- package/dist/search-index.json +232 -5
- package/dist/velin-agent.json +433 -46
- package/dist/velinstyle-components.iife.js +3695 -2272
- package/dist/velinstyle-components.js +3698 -2271
- package/dist/velinstyle-components.min.js +262 -120
- package/dist/velinstyle.css +96 -13
- package/dist/velinstyle.d.ts +3 -0
- package/dist/velinstyle.min.css +1 -1
- package/package.json +19 -4
- 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/components/data-table.css +19 -0
- package/src/components/empty-auth.css +33 -0
- package/src/components/table.css +16 -6
- package/src/tokens/motion.css +7 -0
- package/src/utilities/scroll-animation.css +66 -0
- package/src/velinstyle.css +1 -0
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Motion engines / AI providers catalog for `velinstyle motion providers`.
|
|
3
|
+
*/
|
|
4
|
+
import {
|
|
5
|
+
PIXVERSE_AFFILIATE_URL,
|
|
6
|
+
formatPixVerseSignupHelp,
|
|
7
|
+
} from './ai/providers/pixverse-setup.js';
|
|
8
|
+
|
|
9
|
+
export function listMotionProviders() {
|
|
10
|
+
return [
|
|
11
|
+
{
|
|
12
|
+
id: 'local',
|
|
13
|
+
kind: 'local',
|
|
14
|
+
title: 'LOCAL',
|
|
15
|
+
description: 'Kostenlos, lokal',
|
|
16
|
+
requiresApiKey: false,
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
id: 'pixverse',
|
|
20
|
+
kind: 'ai',
|
|
21
|
+
title: 'PIXVERSE',
|
|
22
|
+
description: 'AI Image-to-Video',
|
|
23
|
+
requiresApiKey: true,
|
|
24
|
+
setupCommand: 'velinstyle motion setup pixverse',
|
|
25
|
+
signupUrl: PIXVERSE_AFFILIATE_URL,
|
|
26
|
+
note: 'API erforderlich',
|
|
27
|
+
},
|
|
28
|
+
];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Human-readable providers listing (German, matches plan copy).
|
|
33
|
+
*/
|
|
34
|
+
export function formatMotionProvidersText() {
|
|
35
|
+
return [
|
|
36
|
+
'VelinStyle Motion Providers',
|
|
37
|
+
'',
|
|
38
|
+
'LOCAL',
|
|
39
|
+
'Kostenlos, lokal',
|
|
40
|
+
'',
|
|
41
|
+
formatPixVerseSignupHelp(),
|
|
42
|
+
'',
|
|
43
|
+
].join('\n');
|
|
44
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { readFileSync } from 'fs';
|
|
2
|
+
import { fileURLToPath } from 'url';
|
|
3
|
+
import { dirname, join } from 'path';
|
|
4
|
+
|
|
5
|
+
const ROOT = dirname(fileURLToPath(import.meta.url));
|
|
6
|
+
const DATA = JSON.parse(readFileSync(join(ROOT, 'presets.json'), 'utf8'));
|
|
7
|
+
|
|
8
|
+
export function listClipPresets() {
|
|
9
|
+
return DATA.presets.slice();
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function getClipPreset(id) {
|
|
13
|
+
if (!id) return null;
|
|
14
|
+
const key = String(id).toLowerCase();
|
|
15
|
+
return DATA.presets.find((p) => p.id === key || p.title.toLowerCase() === key) || null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function listWizardChoices() {
|
|
19
|
+
return listClipPresets().map((p, i) => ({
|
|
20
|
+
index: i + 1,
|
|
21
|
+
id: p.id,
|
|
22
|
+
title: p.title,
|
|
23
|
+
engine: p.engine,
|
|
24
|
+
}));
|
|
25
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema": "velinstyle.motion.clip.presets",
|
|
3
|
+
"version": 1,
|
|
4
|
+
"presets": [
|
|
5
|
+
{
|
|
6
|
+
"id": "soft-zoom",
|
|
7
|
+
"title": "Soft Zoom",
|
|
8
|
+
"engine": "local",
|
|
9
|
+
"effects": ["zoom"],
|
|
10
|
+
"durationMs": 4000,
|
|
11
|
+
"easing": "ease-in-out",
|
|
12
|
+
"status": "stable",
|
|
13
|
+
"description": "Gentle Ken Burns zoom-in on the image."
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"id": "cinematic",
|
|
17
|
+
"title": "Cinematic",
|
|
18
|
+
"engine": "local",
|
|
19
|
+
"effects": ["pan", "zoom"],
|
|
20
|
+
"durationMs": 5000,
|
|
21
|
+
"easing": "ease-in-out",
|
|
22
|
+
"status": "stable",
|
|
23
|
+
"description": "Slow pan with subtle zoom for hero/showcase frames."
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"id": "atelier",
|
|
27
|
+
"title": "Atelier Reveal",
|
|
28
|
+
"engine": "local",
|
|
29
|
+
"effects": ["intro", "soft-zoom"],
|
|
30
|
+
"durationMs": 4500,
|
|
31
|
+
"easing": "cubic-bezier(0.22, 1, 0.36, 1)",
|
|
32
|
+
"status": "stable",
|
|
33
|
+
"description": "Atelier-style fade-in then soft zoom — good for batch reels."
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"id": "rotate",
|
|
37
|
+
"title": "Rotate",
|
|
38
|
+
"engine": "local",
|
|
39
|
+
"effects": ["rotate"],
|
|
40
|
+
"durationMs": 4000,
|
|
41
|
+
"easing": "ease-in-out",
|
|
42
|
+
"status": "stable",
|
|
43
|
+
"description": "Subtle rotation with settle."
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"id": "particle-reveal",
|
|
47
|
+
"title": "Particle Reveal",
|
|
48
|
+
"engine": "local",
|
|
49
|
+
"effects": ["particles", "fade"],
|
|
50
|
+
"durationMs": 5000,
|
|
51
|
+
"easing": "ease-out",
|
|
52
|
+
"status": "stable",
|
|
53
|
+
"description": "Particle overlay fade revealing the image."
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"id": "rotate-explode",
|
|
57
|
+
"title": "Rotate + Explosion",
|
|
58
|
+
"engine": "local",
|
|
59
|
+
"effects": ["rotate", "particles"],
|
|
60
|
+
"durationMs": 4500,
|
|
61
|
+
"easing": "cubic-bezier(0.34, 1.56, 0.64, 1)",
|
|
62
|
+
"status": "stable",
|
|
63
|
+
"description": "Spin then particle burst."
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
"id": "ai-reveal",
|
|
67
|
+
"title": "AI Animation",
|
|
68
|
+
"engine": "ai",
|
|
69
|
+
"effects": ["ai"],
|
|
70
|
+
"durationMs": 5000,
|
|
71
|
+
"easing": null,
|
|
72
|
+
"status": "beta",
|
|
73
|
+
"description": "Provider-driven image-to-video (PixVerse by default). Requires credits."
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
"id": "custom-prompt",
|
|
77
|
+
"title": "Eigener Prompt / Custom Prompt",
|
|
78
|
+
"engine": "hybrid",
|
|
79
|
+
"effects": ["mini-prompt"],
|
|
80
|
+
"durationMs": 5000,
|
|
81
|
+
"status": "beta",
|
|
82
|
+
"description": "Mini-prompt → structured motion profile (local or AI)."
|
|
83
|
+
}
|
|
84
|
+
]
|
|
85
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cost confirmation gate before AI jobs.
|
|
3
|
+
*/
|
|
4
|
+
import { createInterface } from 'readline';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @param {object} opts
|
|
8
|
+
* @param {string} opts.provider
|
|
9
|
+
* @param {string} opts.model
|
|
10
|
+
* @param {number} opts.duration
|
|
11
|
+
* @param {string} opts.quality
|
|
12
|
+
* @param {boolean} [opts.yes]
|
|
13
|
+
* @param {boolean} [opts.quiet]
|
|
14
|
+
*/
|
|
15
|
+
export async function confirmAiCost(opts) {
|
|
16
|
+
if (opts.yes || opts.quiet || !process.stdin.isTTY) return true;
|
|
17
|
+
|
|
18
|
+
const lines = [
|
|
19
|
+
'',
|
|
20
|
+
`${opts.provider || 'AI'}`,
|
|
21
|
+
`Modell: ${opts.model}`,
|
|
22
|
+
`Dauer: ${opts.duration} Sekunden`,
|
|
23
|
+
`Qualität: ${opts.quality}`,
|
|
24
|
+
'',
|
|
25
|
+
`Dieser Auftrag verwendet ${opts.provider || 'Provider'}-Credits.`,
|
|
26
|
+
'',
|
|
27
|
+
];
|
|
28
|
+
process.stdout.write(lines.join('\n'));
|
|
29
|
+
|
|
30
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
31
|
+
const answer = await new Promise((resolve) => {
|
|
32
|
+
rl.question('Starten? [J/n] ', resolve);
|
|
33
|
+
});
|
|
34
|
+
rl.close();
|
|
35
|
+
const a = (answer || 'j').trim().toLowerCase();
|
|
36
|
+
return a === '' || a === 'j' || a === 'y' || a === 'ja' || a === 'yes';
|
|
37
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ensure PixVerse API key before AI jobs.
|
|
3
|
+
* Hard rules: --yes → no browser/prompt; setup_completed → no auto affiliate reopen.
|
|
4
|
+
* Affiliate-first: open partner link / copy link / enter key — never bare platform bypass.
|
|
5
|
+
*/
|
|
6
|
+
import { createInterface } from 'readline';
|
|
7
|
+
import {
|
|
8
|
+
hasPixVerseApiKey,
|
|
9
|
+
isPixVerseSetupCompleted,
|
|
10
|
+
loadMotionConfig,
|
|
11
|
+
loadMotionDotEnv,
|
|
12
|
+
markPixVerseSetupCompleted,
|
|
13
|
+
} from '../config.js';
|
|
14
|
+
import {
|
|
15
|
+
PIXVERSE_DOCS_URL,
|
|
16
|
+
PIXVERSE_KEY_MISSING_HINT,
|
|
17
|
+
copyPixVersePartnerLink,
|
|
18
|
+
formatPixVerseSignupHelp,
|
|
19
|
+
formatPixVerseTrackerHint,
|
|
20
|
+
runPixVersePartnerOpenFlow,
|
|
21
|
+
promptAndSaveApiKey,
|
|
22
|
+
} from '../ai/providers/pixverse-setup.js';
|
|
23
|
+
|
|
24
|
+
function ask(rl, q) {
|
|
25
|
+
return new Promise((resolve) => {
|
|
26
|
+
rl.question(q, resolve);
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function writeCopied(result) {
|
|
31
|
+
if (result.ok) {
|
|
32
|
+
process.stdout.write(`\nPartnerlink kopiert:\n${result.url}\n`);
|
|
33
|
+
} else {
|
|
34
|
+
process.stdout.write(`\nPartnerlink (manuell kopieren):\n${result.url}\n`);
|
|
35
|
+
}
|
|
36
|
+
process.stdout.write(`\n${formatPixVerseTrackerHint()}\n`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* @param {object} opts
|
|
41
|
+
* @param {string} [opts.cwd]
|
|
42
|
+
* @param {boolean} [opts.yes]
|
|
43
|
+
* @param {boolean} [opts.quiet]
|
|
44
|
+
* @param {boolean} [opts.json]
|
|
45
|
+
* @param {boolean} [opts.dryRun]
|
|
46
|
+
* @param {(url: string, o?: object) => object|string} [opts.openImpl]
|
|
47
|
+
* @returns {Promise<{ ok: boolean, dryRun?: boolean }>}
|
|
48
|
+
*/
|
|
49
|
+
export async function ensurePixVerseReady(opts = {}) {
|
|
50
|
+
const cwd = opts.cwd || process.cwd();
|
|
51
|
+
loadMotionDotEnv(cwd);
|
|
52
|
+
loadMotionConfig(cwd);
|
|
53
|
+
|
|
54
|
+
if (hasPixVerseApiKey(undefined, cwd)) {
|
|
55
|
+
return { ok: true };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const nonInteractive = Boolean(opts.yes || opts.quiet || opts.json || !process.stdin.isTTY);
|
|
59
|
+
|
|
60
|
+
if (opts.dryRun || process.env.VELIN_MOTION_DRY_RUN === '1') {
|
|
61
|
+
return { ok: true, dryRun: true };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (nonInteractive) {
|
|
65
|
+
throw new Error(PIXVERSE_KEY_MISSING_HINT);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const completed = isPixVerseSetupCompleted(cwd);
|
|
69
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
if (!completed) {
|
|
73
|
+
process.stdout.write(`\nVelinStyle Motion\n\n${formatPixVerseSignupHelp()}\n\n`);
|
|
74
|
+
const choice = (await ask(rl, 'Auswahl [1/2/3/4]: ')).trim();
|
|
75
|
+
if (choice === '4' || choice.toLowerCase() === 'q' || choice.toLowerCase() === 'c') {
|
|
76
|
+
throw new Error('Abgebrochen');
|
|
77
|
+
}
|
|
78
|
+
if (choice === '1' || choice === '') {
|
|
79
|
+
const outcome = await runPixVersePartnerOpenFlow({
|
|
80
|
+
rl,
|
|
81
|
+
openImpl: opts.openImpl,
|
|
82
|
+
spawnImpl: opts.spawnImpl,
|
|
83
|
+
copyImpl: opts.copyImpl,
|
|
84
|
+
forceFail: opts.forceFail,
|
|
85
|
+
});
|
|
86
|
+
if (outcome === 'aborted') throw new Error('Abgebrochen');
|
|
87
|
+
process.stdout.write(
|
|
88
|
+
'\nRichte den API-Zugang im PixVerse-Dashboard ein, dann füge den Key hier ein.\n',
|
|
89
|
+
);
|
|
90
|
+
await promptAndSaveApiKey({ cwd, rl });
|
|
91
|
+
markPixVerseSetupCompleted(cwd);
|
|
92
|
+
return { ok: true };
|
|
93
|
+
}
|
|
94
|
+
if (choice === '2') {
|
|
95
|
+
writeCopied(copyPixVersePartnerLink({ copyImpl: opts.copyImpl }));
|
|
96
|
+
process.stdout.write(
|
|
97
|
+
'\nÖffne den kopierten Partnerlink, richte den API-Zugang ein, dann Key hier einfügen.\n',
|
|
98
|
+
);
|
|
99
|
+
await promptAndSaveApiKey({ cwd, rl });
|
|
100
|
+
markPixVerseSetupCompleted(cwd);
|
|
101
|
+
return { ok: true };
|
|
102
|
+
}
|
|
103
|
+
if (choice === '3') {
|
|
104
|
+
process.stdout.write(
|
|
105
|
+
'\nAPI-Key aus dem PixVerse-Dashboard einfügen (Env / .velin/motion/.env).\n',
|
|
106
|
+
);
|
|
107
|
+
await promptAndSaveApiKey({ cwd, rl });
|
|
108
|
+
markPixVerseSetupCompleted(cwd);
|
|
109
|
+
return { ok: true };
|
|
110
|
+
}
|
|
111
|
+
throw new Error('Abgebrochen');
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// setup_completed but key missing — same affiliate-first menu, no auto browser
|
|
115
|
+
process.stdout.write(
|
|
116
|
+
[
|
|
117
|
+
'',
|
|
118
|
+
'VelinStyle Motion',
|
|
119
|
+
'',
|
|
120
|
+
'PixVerse Setup',
|
|
121
|
+
'',
|
|
122
|
+
'PixVerse API-Key fehlt (Env / .velin/motion/.env).',
|
|
123
|
+
'Oder erneut: velinstyle motion setup pixverse',
|
|
124
|
+
'',
|
|
125
|
+
formatPixVerseSignupHelp().split('\n').slice(6).join('\n'),
|
|
126
|
+
'',
|
|
127
|
+
].join('\n'),
|
|
128
|
+
);
|
|
129
|
+
const choice = (await ask(rl, 'Auswahl [1/2/3/4]: ')).trim();
|
|
130
|
+
if (choice === '4' || choice.toLowerCase() === 'q' || choice.toLowerCase() === 'c') {
|
|
131
|
+
throw new Error('Abgebrochen');
|
|
132
|
+
}
|
|
133
|
+
if (choice === '1' || choice === '') {
|
|
134
|
+
const outcome = await runPixVersePartnerOpenFlow({
|
|
135
|
+
rl,
|
|
136
|
+
openImpl: opts.openImpl,
|
|
137
|
+
spawnImpl: opts.spawnImpl,
|
|
138
|
+
copyImpl: opts.copyImpl,
|
|
139
|
+
forceFail: opts.forceFail,
|
|
140
|
+
});
|
|
141
|
+
if (outcome === 'aborted') throw new Error('Abgebrochen');
|
|
142
|
+
} else if (choice === '2') {
|
|
143
|
+
writeCopied(copyPixVersePartnerLink({ copyImpl: opts.copyImpl }));
|
|
144
|
+
} else if (choice !== '3') {
|
|
145
|
+
throw new Error('Abgebrochen');
|
|
146
|
+
}
|
|
147
|
+
process.stdout.write(`Docs: ${PIXVERSE_DOCS_URL}\n`);
|
|
148
|
+
await promptAndSaveApiKey({ cwd, rl });
|
|
149
|
+
return { ok: true };
|
|
150
|
+
} finally {
|
|
151
|
+
rl.close();
|
|
152
|
+
}
|
|
153
|
+
}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interactive TTY wizard for motion create / atelier.
|
|
3
|
+
*/
|
|
4
|
+
import { createInterface } from 'readline';
|
|
5
|
+
import { listWizardChoices, getClipPreset } from '../registry/presets.js';
|
|
6
|
+
import { listAiProviders, loadBuiltinAiProviders } from '../ai/provider.js';
|
|
7
|
+
import { suggestFromMiniPrompt } from '../ai/mini-prompt.js';
|
|
8
|
+
|
|
9
|
+
function rl() {
|
|
10
|
+
return createInterface({ input: process.stdin, output: process.stdout });
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function ask(r, q) {
|
|
14
|
+
return new Promise((resolve) => r.question(q, resolve));
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @param {object} ctx
|
|
19
|
+
* @param {string} ctx.image
|
|
20
|
+
* @param {boolean} [ctx.ai]
|
|
21
|
+
* @param {boolean} [ctx.yes]
|
|
22
|
+
* @param {object} [ctx.flags] — prefilled non-interactive
|
|
23
|
+
*/
|
|
24
|
+
export async function runCreateWizard(ctx = {}) {
|
|
25
|
+
if (ctx.yes || ctx.flags?.preset || !process.stdin.isTTY) {
|
|
26
|
+
return resolveNonInteractive(ctx);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const r = rl();
|
|
30
|
+
try {
|
|
31
|
+
process.stdout.write(`\nVelinStyle Motion\n\nBild: ${ctx.image}\n\nAnimation auswählen:\n\n`);
|
|
32
|
+
const choices = listWizardChoices();
|
|
33
|
+
for (const c of choices) {
|
|
34
|
+
process.stdout.write(`${c.index}. ${c.title}${c.engine === 'ai' ? ' (AI)' : ''}\n`);
|
|
35
|
+
}
|
|
36
|
+
process.stdout.write(`${choices.length + 1}. Eigener Prompt / Mini-Prompt\n`);
|
|
37
|
+
const raw = await ask(r, '\nAuswahl: ');
|
|
38
|
+
const n = Number(raw);
|
|
39
|
+
let preset = null;
|
|
40
|
+
let profile = null;
|
|
41
|
+
let engine = 'local';
|
|
42
|
+
let provider = null;
|
|
43
|
+
let promptText = '';
|
|
44
|
+
|
|
45
|
+
if (n === choices.length + 1 || ctx.ai) {
|
|
46
|
+
const phrase = await ask(r, '\nWas soll passieren?\n> ');
|
|
47
|
+
const suggestions = suggestFromMiniPrompt(phrase);
|
|
48
|
+
if (suggestions.length) {
|
|
49
|
+
process.stdout.write('\nVelinStyle schlägt vor:\n\n');
|
|
50
|
+
suggestions.forEach((s, i) => process.stdout.write(`[${i + 1}] ${s.label}\n`));
|
|
51
|
+
const pick = Number(await ask(r, '\nAuswahl: ')) || 1;
|
|
52
|
+
profile = suggestions[Math.max(0, Math.min(suggestions.length, pick) - 1)];
|
|
53
|
+
} else {
|
|
54
|
+
profile = suggestFromMiniPrompt(phrase)[0];
|
|
55
|
+
}
|
|
56
|
+
engine = ctx.ai || (await ask(r, 'Engine [local/ai] (local): ')) === 'ai' ? 'ai' : (ctx.ai ? 'ai' : 'local');
|
|
57
|
+
if (ctx.ai) engine = 'ai';
|
|
58
|
+
preset = getClipPreset(engine === 'ai' ? 'ai-reveal' : 'custom-prompt');
|
|
59
|
+
} else {
|
|
60
|
+
const choice = choices[n - 1] || choices[0];
|
|
61
|
+
preset = getClipPreset(choice.id);
|
|
62
|
+
engine = preset.engine === 'ai' || preset.engine === 'hybrid' ? (choice.engine === 'ai' ? 'ai' : 'local') : 'local';
|
|
63
|
+
if (preset.engine === 'ai') engine = 'ai';
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (engine === 'ai') {
|
|
67
|
+
await loadBuiltinAiProviders();
|
|
68
|
+
const providers = listAiProviders();
|
|
69
|
+
process.stdout.write('\nAI Provider\n\n');
|
|
70
|
+
providers.forEach((p, i) => process.stdout.write(`${i + 1}. ${p.title}\n`));
|
|
71
|
+
process.stdout.write(`${providers.length + 1}. Weitere Provider (bald)\n`);
|
|
72
|
+
const pi = Number(await ask(r, '\nAuswahl: ')) || 1;
|
|
73
|
+
if (pi > providers.length) {
|
|
74
|
+
throw new Error('Weitere Provider sind in 1.3.0 noch nicht verfügbar — bitte PixVerse wählen.');
|
|
75
|
+
}
|
|
76
|
+
provider = providers[pi - 1];
|
|
77
|
+
const model = (await ask(r, `Modell [${provider.models[0]}]: `)) || provider.models[0];
|
|
78
|
+
const quality = (await ask(r, 'Qualität [720p]: ')) || '720p';
|
|
79
|
+
const duration = Number(await ask(r, 'Dauer Sekunden [5]: ')) || 5;
|
|
80
|
+
promptText = profile?.aiPrompt || (await ask(r, 'Prompt (Enter = Profil): ')) || profile?.aiPrompt || '';
|
|
81
|
+
|
|
82
|
+
process.stdout.write(`\n${provider.title} Animation\n\n`);
|
|
83
|
+
process.stdout.write(`Bild: ${ctx.image}\n`);
|
|
84
|
+
process.stdout.write(`Preset: ${preset?.title || 'AI'}\n`);
|
|
85
|
+
process.stdout.write(`Dauer: ${duration}s\n`);
|
|
86
|
+
process.stdout.write(`Modell: ${model}\n`);
|
|
87
|
+
process.stdout.write(`Qualität: ${quality}\n`);
|
|
88
|
+
process.stdout.write(`Prompt: ${promptText}\n`);
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
image: ctx.image,
|
|
92
|
+
preset: preset?.id || 'ai-reveal',
|
|
93
|
+
engine: 'ai',
|
|
94
|
+
provider: provider.id,
|
|
95
|
+
model,
|
|
96
|
+
quality,
|
|
97
|
+
duration,
|
|
98
|
+
prompt: promptText,
|
|
99
|
+
profile,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const duration = Number(await ask(r, 'Dauer Sekunden [4]: ')) || (preset?.durationMs || 4000) / 1000;
|
|
104
|
+
process.stdout.write(`\nLocal Animation\nBild: ${ctx.image}\nPreset: ${preset?.title}\nDauer: ${duration}s\n`);
|
|
105
|
+
const ok = await ask(r, '\nStarten? [J/n] ');
|
|
106
|
+
if (ok && !/^(j|y|ja|yes)?$/i.test(ok.trim() || 'j')) {
|
|
107
|
+
throw new Error('Abgebrochen');
|
|
108
|
+
}
|
|
109
|
+
return {
|
|
110
|
+
image: ctx.image,
|
|
111
|
+
preset: preset?.id || 'soft-zoom',
|
|
112
|
+
engine: 'local',
|
|
113
|
+
duration,
|
|
114
|
+
profile,
|
|
115
|
+
};
|
|
116
|
+
} finally {
|
|
117
|
+
r.close();
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function resolveNonInteractive(ctx) {
|
|
122
|
+
const flags = ctx.flags || {};
|
|
123
|
+
const presetId = flags.preset || (ctx.ai ? 'ai-reveal' : 'soft-zoom');
|
|
124
|
+
const preset = getClipPreset(presetId);
|
|
125
|
+
return {
|
|
126
|
+
image: ctx.image,
|
|
127
|
+
preset: preset?.id || presetId,
|
|
128
|
+
engine: flags.style || (ctx.ai || preset?.engine === 'ai' ? 'ai' : 'local'),
|
|
129
|
+
provider: flags.provider || 'pixverse',
|
|
130
|
+
model: flags.model,
|
|
131
|
+
quality: flags.quality || '720p',
|
|
132
|
+
duration: flags.duration ? Number(flags.duration) : undefined,
|
|
133
|
+
prompt: flags.prompt,
|
|
134
|
+
profile: flags.profile,
|
|
135
|
+
outDir: flags.out,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Atelier batch wizard (simplified interactive).
|
|
141
|
+
*/
|
|
142
|
+
export async function runAtelierWizard(ctx = {}) {
|
|
143
|
+
if (ctx.yes || !process.stdin.isTTY) {
|
|
144
|
+
return {
|
|
145
|
+
count: ctx.count || 10,
|
|
146
|
+
preset: ctx.preset || 'atelier',
|
|
147
|
+
engine: ctx.ai ? 'ai' : 'local',
|
|
148
|
+
provider: 'pixverse',
|
|
149
|
+
duration: ctx.duration || 5,
|
|
150
|
+
quality: '720p',
|
|
151
|
+
outDir: ctx.outDir || './motion-output',
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
const r = rl();
|
|
155
|
+
try {
|
|
156
|
+
process.stdout.write(`\nVelinStyle Atelier Motion\n\n${ctx.assetCount ?? '?'} Assets gefunden.\n\n`);
|
|
157
|
+
const count = Number(await ask(r, 'Wie viele Animationen?\n> ')) || 10;
|
|
158
|
+
const preset = (await ask(r, 'Preset [atelier]: ')) || 'atelier';
|
|
159
|
+
const engine = (await ask(r, 'Engine [local/ai]: ')) || 'local';
|
|
160
|
+
let provider = 'pixverse';
|
|
161
|
+
let quality = '720p';
|
|
162
|
+
if (engine === 'ai') {
|
|
163
|
+
provider = (await ask(r, 'Provider [pixverse]: ')) || 'pixverse';
|
|
164
|
+
quality = (await ask(r, 'Qualität [720p]: ')) || '720p';
|
|
165
|
+
}
|
|
166
|
+
const duration = Number(await ask(r, 'Dauer [5]: ')) || 5;
|
|
167
|
+
return { count, preset, engine, provider, duration, quality, outDir: './motion-output' };
|
|
168
|
+
} finally {
|
|
169
|
+
r.close();
|
|
170
|
+
}
|
|
171
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validate still-image inputs for Motion Clip CLI (local + AI).
|
|
3
|
+
* Rejects missing, empty, wrong-extension, and non-image payloads.
|
|
4
|
+
*/
|
|
5
|
+
import { existsSync, statSync, openSync, readSync, closeSync } from 'fs';
|
|
6
|
+
import { extname, basename } from 'path';
|
|
7
|
+
|
|
8
|
+
const ALLOWED_EXT = new Set(['.webp', '.png', '.jpg', '.jpeg']);
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @param {string} filePath absolute path
|
|
12
|
+
* @param {string} [label]
|
|
13
|
+
*/
|
|
14
|
+
export function assertMotionImageFile(filePath, label = 'Image') {
|
|
15
|
+
if (!filePath || typeof filePath !== 'string') {
|
|
16
|
+
throw new Error(`${label} path is required`);
|
|
17
|
+
}
|
|
18
|
+
if (!existsSync(filePath)) {
|
|
19
|
+
throw new Error(`${label} not found: ${filePath}`);
|
|
20
|
+
}
|
|
21
|
+
let st;
|
|
22
|
+
try {
|
|
23
|
+
st = statSync(filePath);
|
|
24
|
+
} catch {
|
|
25
|
+
throw new Error(`${label} not readable: ${filePath}`);
|
|
26
|
+
}
|
|
27
|
+
if (!st.isFile()) {
|
|
28
|
+
throw new Error(`${label} must be a file: ${filePath}`);
|
|
29
|
+
}
|
|
30
|
+
if (st.size <= 0) {
|
|
31
|
+
throw new Error(`${label} is empty (0 bytes): ${basename(filePath)}`);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const ext = extname(filePath).toLowerCase();
|
|
35
|
+
if (!ALLOWED_EXT.has(ext)) {
|
|
36
|
+
throw new Error(
|
|
37
|
+
`${label} has unsupported extension "${ext || '(none)'}" — use .webp, .png, .jpg, or .jpeg`,
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const head = readHead(filePath, 16);
|
|
42
|
+
if (!looksLikeRasterImage(head)) {
|
|
43
|
+
throw new Error(
|
|
44
|
+
`${label} is not a valid PNG/JPEG/WebP file: ${basename(filePath)}`,
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return { path: filePath, size: st.size, ext };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* @param {string} filePath
|
|
53
|
+
* @param {number} n
|
|
54
|
+
*/
|
|
55
|
+
function readHead(filePath, n) {
|
|
56
|
+
const buf = Buffer.alloc(n);
|
|
57
|
+
const fd = openSync(filePath, 'r');
|
|
58
|
+
try {
|
|
59
|
+
readSync(fd, buf, 0, n, 0);
|
|
60
|
+
} finally {
|
|
61
|
+
closeSync(fd);
|
|
62
|
+
}
|
|
63
|
+
return buf;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Accept PNG / JPEG / WebP magic regardless of which allowed extension was used
|
|
68
|
+
* (fixtures often store PNG bytes under a .webp name).
|
|
69
|
+
* @param {Buffer} buf
|
|
70
|
+
*/
|
|
71
|
+
function looksLikeRasterImage(buf) {
|
|
72
|
+
if (!buf || buf.length < 3) return false;
|
|
73
|
+
if (buf[0] === 0x89 && buf[1] === 0x50 && buf[2] === 0x4e && buf[3] === 0x47) return true;
|
|
74
|
+
if (buf[0] === 0xff && buf[1] === 0xd8 && buf[2] === 0xff) return true;
|
|
75
|
+
if (
|
|
76
|
+
buf.length >= 12 &&
|
|
77
|
+
buf[0] === 0x52 &&
|
|
78
|
+
buf[1] === 0x49 &&
|
|
79
|
+
buf[2] === 0x46 &&
|
|
80
|
+
buf[3] === 0x46 &&
|
|
81
|
+
buf[8] === 0x57 &&
|
|
82
|
+
buf[9] === 0x45 &&
|
|
83
|
+
buf[10] === 0x42 &&
|
|
84
|
+
buf[11] === 0x50
|
|
85
|
+
) {
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export { ALLOWED_EXT as MOTION_IMAGE_EXTENSIONS };
|