@vibeframe/cli 0.70.0 → 0.72.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/dist/index.js +1745 -755
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -6557,6 +6557,7 @@ __export(output_exports, {
|
|
|
6557
6557
|
notFoundError: () => notFoundError,
|
|
6558
6558
|
outputError: () => outputError,
|
|
6559
6559
|
outputResult: () => outputResult,
|
|
6560
|
+
outputSuccess: () => outputSuccess,
|
|
6560
6561
|
spinner: () => spinner,
|
|
6561
6562
|
suggestNext: () => suggestNext,
|
|
6562
6563
|
usageError: () => usageError
|
|
@@ -6615,6 +6616,40 @@ function formatCost(min, max, unit) {
|
|
|
6615
6616
|
if (min === max) return `~$${min.toFixed(2)} ${unit}`;
|
|
6616
6617
|
return `~$${min.toFixed(2)}-$${max.toFixed(2)} ${unit}`;
|
|
6617
6618
|
}
|
|
6619
|
+
function lookupCostEstimateUpperBound(command) {
|
|
6620
|
+
return COST_ESTIMATES[command]?.max ?? 0;
|
|
6621
|
+
}
|
|
6622
|
+
function outputSuccess(opts) {
|
|
6623
|
+
const elapsedMs = Math.max(0, Date.now() - opts.startedAt);
|
|
6624
|
+
const costUsd = opts.costUsd ?? (opts.dryRun ? lookupCostEstimateUpperBound(opts.command) : 0);
|
|
6625
|
+
const envelope = {
|
|
6626
|
+
command: opts.command,
|
|
6627
|
+
...opts.dryRun ? { dryRun: true } : {},
|
|
6628
|
+
elapsedMs,
|
|
6629
|
+
costUsd,
|
|
6630
|
+
warnings: opts.warnings ?? [],
|
|
6631
|
+
data: opts.data
|
|
6632
|
+
};
|
|
6633
|
+
if (isJsonMode()) {
|
|
6634
|
+
const fields = process.env.VIBE_OUTPUT_FIELDS;
|
|
6635
|
+
if (fields) {
|
|
6636
|
+
const keys = fields.split(",").map((k) => k.trim());
|
|
6637
|
+
const data = opts.data;
|
|
6638
|
+
const filteredData = {};
|
|
6639
|
+
for (const key of keys) {
|
|
6640
|
+
if (key in data) filteredData[key] = data[key];
|
|
6641
|
+
}
|
|
6642
|
+
envelope.data = filteredData;
|
|
6643
|
+
}
|
|
6644
|
+
console.log(JSON.stringify(envelope, null, 2));
|
|
6645
|
+
return;
|
|
6646
|
+
}
|
|
6647
|
+
if (isQuietMode()) {
|
|
6648
|
+
const data = opts.data;
|
|
6649
|
+
const primary = data.outputPath ?? data.output ?? data.path ?? data.url ?? data.id;
|
|
6650
|
+
if (primary !== void 0) console.log(String(primary));
|
|
6651
|
+
}
|
|
6652
|
+
}
|
|
6618
6653
|
function outputResult(result) {
|
|
6619
6654
|
if (result.dryRun && result.command && typeof result.command === "string") {
|
|
6620
6655
|
const cost = COST_ESTIMATES[result.command];
|
|
@@ -29108,6 +29143,26 @@ function detectAgentHosts(env2 = process.env) {
|
|
|
29108
29143
|
detected: false,
|
|
29109
29144
|
signals: [],
|
|
29110
29145
|
projectFiles: ["AGENTS.md", ".aider.conf.yml"]
|
|
29146
|
+
},
|
|
29147
|
+
{
|
|
29148
|
+
id: "gemini-cli",
|
|
29149
|
+
label: "Gemini CLI",
|
|
29150
|
+
detected: false,
|
|
29151
|
+
signals: [],
|
|
29152
|
+
// Per https://geminicli.com/docs/cli/gemini-md/ Gemini CLI's primary
|
|
29153
|
+
// context file is GEMINI.md; AGENTS.md is the cross-tool fallback
|
|
29154
|
+
// VibeFrame writes by default. Both are honoured.
|
|
29155
|
+
projectFiles: ["GEMINI.md", "AGENTS.md", ".gemini/"]
|
|
29156
|
+
},
|
|
29157
|
+
{
|
|
29158
|
+
id: "opencode",
|
|
29159
|
+
label: "OpenCode",
|
|
29160
|
+
detected: false,
|
|
29161
|
+
signals: [],
|
|
29162
|
+
// sst/opencode officially supports the agents.md spec — AGENTS.md at
|
|
29163
|
+
// project root is the standard place. Local config also under
|
|
29164
|
+
// `.opencode/` per https://opencode.ai/docs/config/.
|
|
29165
|
+
projectFiles: ["AGENTS.md", ".opencode/"]
|
|
29111
29166
|
}
|
|
29112
29167
|
].map((host) => {
|
|
29113
29168
|
const signals2 = [];
|
|
@@ -29165,14 +29220,21 @@ var init_agent_host_detect = __esm({
|
|
|
29165
29220
|
"claude-code": "claude",
|
|
29166
29221
|
codex: "codex",
|
|
29167
29222
|
cursor: "cursor",
|
|
29168
|
-
aider: "aider"
|
|
29223
|
+
aider: "aider",
|
|
29224
|
+
"gemini-cli": "gemini",
|
|
29225
|
+
opencode: "opencode"
|
|
29169
29226
|
};
|
|
29170
29227
|
HOST_CONFIG_DIRS = {
|
|
29171
29228
|
"claude-code": ".claude",
|
|
29172
29229
|
codex: ".codex",
|
|
29173
29230
|
cursor: ".cursor",
|
|
29174
29231
|
// some installs; macOS app stores prefs elsewhere
|
|
29175
|
-
aider: null
|
|
29232
|
+
aider: null,
|
|
29233
|
+
"gemini-cli": ".gemini",
|
|
29234
|
+
// sst/opencode uses XDG-style `~/.config/opencode/` per
|
|
29235
|
+
// https://opencode.ai/docs/config/. The path is relative to $HOME so
|
|
29236
|
+
// the join in detectAgentHosts() resolves correctly.
|
|
29237
|
+
opencode: ".config/opencode"
|
|
29176
29238
|
};
|
|
29177
29239
|
}
|
|
29178
29240
|
});
|
|
@@ -30020,20 +30082,24 @@ Examples:
|
|
|
30020
30082
|
A scene project is bilingual: it works with both \`vibe\` and \`npx hyperframes\`.
|
|
30021
30083
|
Run 'vibe schema scene.<command>' for structured parameter info.`);
|
|
30022
30084
|
sceneCommand.command("init").description("Scaffold a new scene project (or safely augment an existing Hyperframes project)").argument("<dir>", "Project directory (created if it doesn't exist)").option("-n, --name <name>", "Project name (defaults to directory basename)").option("-r, --ratio <ratio>", "Aspect ratio: 16:9, 9:16, 1:1, 4:5", "16:9").option("-d, --duration <sec>", "Default root composition duration (seconds)", "10").option("--visual-style <name>", `Seed DESIGN.md from a named style (browse via \`vibe scene styles\`). E.g. "Swiss Pulse"`).option("--dry-run", "Preview parameters without writing files").action(async (dir, options) => {
|
|
30085
|
+
const startedAt = Date.now();
|
|
30023
30086
|
const aspect = validateAspect(options.ratio);
|
|
30024
30087
|
const duration = validateDuration(options.duration);
|
|
30025
30088
|
const name = options.name ?? basename4(dir.replace(/\/+$/, ""));
|
|
30026
30089
|
const visualStyle = options.visualStyle ? validateVisualStyle(options.visualStyle) : void 0;
|
|
30027
30090
|
if (options.dryRun) {
|
|
30028
|
-
|
|
30029
|
-
dryRun: true,
|
|
30091
|
+
outputSuccess({
|
|
30030
30092
|
command: "scene init",
|
|
30031
|
-
|
|
30032
|
-
|
|
30033
|
-
|
|
30034
|
-
|
|
30035
|
-
|
|
30036
|
-
|
|
30093
|
+
startedAt,
|
|
30094
|
+
dryRun: true,
|
|
30095
|
+
data: {
|
|
30096
|
+
params: {
|
|
30097
|
+
dir,
|
|
30098
|
+
name,
|
|
30099
|
+
aspect,
|
|
30100
|
+
duration,
|
|
30101
|
+
visualStyle: visualStyle?.name ?? null
|
|
30102
|
+
}
|
|
30037
30103
|
}
|
|
30038
30104
|
});
|
|
30039
30105
|
return;
|
|
@@ -30049,19 +30115,21 @@ Run 'vibe schema scene.<command>' for structured parameter info.`);
|
|
|
30049
30115
|
hosts: skillHosts
|
|
30050
30116
|
});
|
|
30051
30117
|
if (isJsonMode()) {
|
|
30052
|
-
|
|
30053
|
-
success: true,
|
|
30118
|
+
outputSuccess({
|
|
30054
30119
|
command: "scene init",
|
|
30055
|
-
|
|
30056
|
-
|
|
30057
|
-
|
|
30058
|
-
|
|
30059
|
-
|
|
30060
|
-
|
|
30061
|
-
|
|
30062
|
-
|
|
30063
|
-
|
|
30064
|
-
|
|
30120
|
+
startedAt,
|
|
30121
|
+
data: {
|
|
30122
|
+
dir,
|
|
30123
|
+
name,
|
|
30124
|
+
aspect,
|
|
30125
|
+
duration,
|
|
30126
|
+
visualStyle: visualStyle?.name ?? null,
|
|
30127
|
+
created: result.created,
|
|
30128
|
+
merged: result.merged,
|
|
30129
|
+
skipped: result.skipped,
|
|
30130
|
+
skillFiles: skillResult.files,
|
|
30131
|
+
skillBundleVersion: skillResult.bundleVersion
|
|
30132
|
+
}
|
|
30065
30133
|
});
|
|
30066
30134
|
return;
|
|
30067
30135
|
}
|
|
@@ -30102,6 +30170,7 @@ Run 'vibe schema scene.<command>' for structured parameter info.`);
|
|
|
30102
30170
|
});
|
|
30103
30171
|
VALID_INSTALL_SKILL_HOSTS = ["claude-code", "cursor", "auto", "all"];
|
|
30104
30172
|
sceneCommand.command("install-skill").description("Install the Hyperframes skill into a scene project so the host agent can read it (Phase H1)").argument("[project-dir]", "Project directory containing STORYBOARD.md / DESIGN.md", ".").option("--host <id>", `Host layout target: ${VALID_INSTALL_SKILL_HOSTS.join(" | ")}`, "auto").option("--force", "Overwrite existing skill files (default: skip-on-exist)").option("--dry-run", "Preview which files would be written without changing anything").action(async (projectDirArg, options) => {
|
|
30173
|
+
const startedAt = Date.now();
|
|
30105
30174
|
const hostFlag = options.host ?? "auto";
|
|
30106
30175
|
if (!VALID_INSTALL_SKILL_HOSTS.includes(hostFlag)) {
|
|
30107
30176
|
exitWithError(usageError(`Invalid --host: ${hostFlag}`, `Valid: ${VALID_INSTALL_SKILL_HOSTS.join(", ")}`));
|
|
@@ -30121,15 +30190,17 @@ Run 'vibe schema scene.<command>' for structured parameter info.`);
|
|
|
30121
30190
|
dryRun: options.dryRun ?? false
|
|
30122
30191
|
});
|
|
30123
30192
|
if (isJsonMode()) {
|
|
30124
|
-
|
|
30125
|
-
success: true,
|
|
30193
|
+
outputSuccess({
|
|
30126
30194
|
command: "scene install-skill",
|
|
30127
|
-
|
|
30128
|
-
|
|
30129
|
-
|
|
30130
|
-
|
|
30131
|
-
|
|
30132
|
-
|
|
30195
|
+
startedAt,
|
|
30196
|
+
dryRun: options.dryRun ?? false,
|
|
30197
|
+
data: {
|
|
30198
|
+
projectDir,
|
|
30199
|
+
host: hostFlag,
|
|
30200
|
+
resolvedHosts: hosts,
|
|
30201
|
+
bundleVersion: result.bundleVersion,
|
|
30202
|
+
files: result.files
|
|
30203
|
+
}
|
|
30133
30204
|
});
|
|
30134
30205
|
return;
|
|
30135
30206
|
}
|
|
@@ -30151,6 +30222,7 @@ Run 'vibe schema scene.<command>' for structured parameter info.`);
|
|
|
30151
30222
|
}
|
|
30152
30223
|
});
|
|
30153
30224
|
sceneCommand.command("compose-prompts").description("Emit the per-beat compose plan for the host agent to author HTML itself (Phase H2 \u2014 no LLM call)").argument("[project-dir]", "Project directory containing STORYBOARD.md / DESIGN.md", ".").option("--beat <id>", "Restrict the plan to a single beat by id (e.g. 'hook', '1')").action(async (projectDirArg, options) => {
|
|
30225
|
+
const startedAt = Date.now();
|
|
30154
30226
|
const projectDir = resolve14(projectDirArg);
|
|
30155
30227
|
const result = await getComposePrompts({
|
|
30156
30228
|
projectDir,
|
|
@@ -30158,18 +30230,21 @@ Run 'vibe schema scene.<command>' for structured parameter info.`);
|
|
|
30158
30230
|
});
|
|
30159
30231
|
if (!result.success) {
|
|
30160
30232
|
if (isJsonMode()) {
|
|
30161
|
-
|
|
30233
|
+
outputSuccess({
|
|
30162
30234
|
command: "scene compose-prompts",
|
|
30163
|
-
|
|
30235
|
+
startedAt,
|
|
30236
|
+
data: { ...result }
|
|
30164
30237
|
});
|
|
30165
|
-
process.
|
|
30238
|
+
process.exitCode = 1;
|
|
30239
|
+
return;
|
|
30166
30240
|
}
|
|
30167
30241
|
exitWithError(generalError(result.error ?? "compose-prompts failed"));
|
|
30168
30242
|
}
|
|
30169
30243
|
if (isJsonMode()) {
|
|
30170
|
-
|
|
30244
|
+
outputSuccess({
|
|
30171
30245
|
command: "scene compose-prompts",
|
|
30172
|
-
|
|
30246
|
+
startedAt,
|
|
30247
|
+
data: { ...result }
|
|
30173
30248
|
});
|
|
30174
30249
|
return;
|
|
30175
30250
|
}
|
|
@@ -30203,20 +30278,23 @@ Run 'vibe schema scene.<command>' for structured parameter info.`);
|
|
|
30203
30278
|
console.log(source_default.dim("Re-run with --json to get the full per-beat userPrompt + cues for direct consumption."));
|
|
30204
30279
|
});
|
|
30205
30280
|
sceneCommand.command("styles").description("List vendored visual styles (or show one) for DESIGN.md seeding").argument("[name]", "Style name to inspect (omit to list all)").action((name) => {
|
|
30281
|
+
const startedAt = Date.now();
|
|
30206
30282
|
if (!name) {
|
|
30207
30283
|
const all = listVisualStyles();
|
|
30208
30284
|
if (isJsonMode()) {
|
|
30209
|
-
|
|
30210
|
-
success: true,
|
|
30285
|
+
outputSuccess({
|
|
30211
30286
|
command: "scene styles",
|
|
30212
|
-
|
|
30213
|
-
|
|
30214
|
-
|
|
30215
|
-
|
|
30216
|
-
|
|
30217
|
-
|
|
30218
|
-
|
|
30219
|
-
|
|
30287
|
+
startedAt,
|
|
30288
|
+
data: {
|
|
30289
|
+
count: all.length,
|
|
30290
|
+
styles: all.map((s) => ({
|
|
30291
|
+
name: s.name,
|
|
30292
|
+
slug: s.slug,
|
|
30293
|
+
designer: s.designer,
|
|
30294
|
+
mood: s.mood,
|
|
30295
|
+
bestFor: s.bestFor
|
|
30296
|
+
}))
|
|
30297
|
+
}
|
|
30220
30298
|
});
|
|
30221
30299
|
return;
|
|
30222
30300
|
}
|
|
@@ -30244,7 +30322,11 @@ Run 'vibe schema scene.<command>' for structured parameter info.`);
|
|
|
30244
30322
|
return;
|
|
30245
30323
|
}
|
|
30246
30324
|
if (isJsonMode()) {
|
|
30247
|
-
|
|
30325
|
+
outputSuccess({
|
|
30326
|
+
command: "scene styles",
|
|
30327
|
+
startedAt,
|
|
30328
|
+
data: { style }
|
|
30329
|
+
});
|
|
30248
30330
|
return;
|
|
30249
30331
|
}
|
|
30250
30332
|
console.log();
|
|
@@ -30266,6 +30348,7 @@ Run 'vibe schema scene.<command>' for structured parameter info.`);
|
|
|
30266
30348
|
console.log(source_default.dim("Seed DESIGN.md:"), source_default.cyan(`vibe scene init <dir> --visual-style "${style.name}"`));
|
|
30267
30349
|
});
|
|
30268
30350
|
sceneCommand.command("add").description("Add a new scene to a project: AI narration + image + per-scene HTML").argument("<name>", "Scene name (slugified into the composition id)").option("--style <preset>", `Style preset: ${SCENE_PRESETS.join(", ")}`, "simple").option("--narration <text>", "Narration text (or path to a .txt file). Drives TTS + scene duration.").option("--narration-file <path>", "Existing narration audio file (.wav/.mp3). Skips TTS \u2014 useful with hyperframes tts, Mac say, or other external tools.").option("-d, --duration <sec>", "Explicit scene duration in seconds (overrides narration audio)").option("--visuals <prompt>", "Image prompt \u2014 generates assets/scene-<id>.png via the configured image provider").option("--headline <text>", "Visible headline (defaults to the humanised scene name)").option("--kicker <text>", "Small label above the headline (explainer / product-shot)").option("--insert-into <path>", "Root composition file to update", "index.html").option("--project <dir>", "Project directory", ".").option("--image-provider <name>", "Image provider: gemini, openai", "gemini").option("--tts <provider>", "TTS provider: auto, elevenlabs, kokoro (default auto \u2014 picks ElevenLabs when key set, else Kokoro local)", "auto").option("--voice <id>", "Voice id (ElevenLabs name/id, or Kokoro id like af_heart, am_michael)").option("--no-audio", "Skip TTS even when --narration is provided (useful for tests/agent dry runs)").option("--no-image", "Skip image generation even when --visuals is provided").option("--no-transcribe", "Skip Whisper word-level transcribe step (no transcript-<id>.json emitted)").option("--transcribe-language <code>", "BCP-47 language code passed to Whisper (e.g. en, ko)").option("--force", "Overwrite an existing compositions/scene-<id>.html").option("--dry-run", "Preview parameters without writing files or calling APIs").action(async (name, options) => {
|
|
30351
|
+
const startedAt = Date.now();
|
|
30269
30352
|
if (options.style) options.style = validatePreset(options.style);
|
|
30270
30353
|
if (options.duration !== void 0) options.duration = validateDuration(options.duration);
|
|
30271
30354
|
let tts;
|
|
@@ -30276,25 +30359,28 @@ Run 'vibe schema scene.<command>' for structured parameter info.`);
|
|
|
30276
30359
|
}
|
|
30277
30360
|
if (options.dryRun) {
|
|
30278
30361
|
const id = slugifySceneName(name);
|
|
30279
|
-
|
|
30280
|
-
dryRun: true,
|
|
30362
|
+
outputSuccess({
|
|
30281
30363
|
command: "scene add",
|
|
30282
|
-
|
|
30283
|
-
|
|
30284
|
-
|
|
30285
|
-
|
|
30286
|
-
|
|
30287
|
-
|
|
30288
|
-
|
|
30289
|
-
|
|
30290
|
-
|
|
30291
|
-
|
|
30292
|
-
|
|
30293
|
-
|
|
30294
|
-
|
|
30295
|
-
|
|
30296
|
-
|
|
30297
|
-
|
|
30364
|
+
startedAt,
|
|
30365
|
+
dryRun: true,
|
|
30366
|
+
data: {
|
|
30367
|
+
params: {
|
|
30368
|
+
name,
|
|
30369
|
+
id,
|
|
30370
|
+
preset: options.style,
|
|
30371
|
+
narration: !!options.narration,
|
|
30372
|
+
visuals: !!options.visuals,
|
|
30373
|
+
duration: options.duration,
|
|
30374
|
+
headline: options.headline,
|
|
30375
|
+
kicker: options.kicker,
|
|
30376
|
+
project: options.project,
|
|
30377
|
+
insertInto: options.insertInto,
|
|
30378
|
+
imageProvider: options.imageProvider,
|
|
30379
|
+
tts,
|
|
30380
|
+
audio: options.audio,
|
|
30381
|
+
// commander sets `audio: false` when --no-audio is passed
|
|
30382
|
+
image: options.image
|
|
30383
|
+
}
|
|
30298
30384
|
}
|
|
30299
30385
|
});
|
|
30300
30386
|
return;
|
|
@@ -30329,9 +30415,10 @@ Run 'vibe schema scene.<command>' for structured parameter info.`);
|
|
|
30329
30415
|
exitWithError(generalError(result.error ?? "Scene add failed"));
|
|
30330
30416
|
}
|
|
30331
30417
|
if (isJsonMode()) {
|
|
30332
|
-
|
|
30418
|
+
outputSuccess({
|
|
30333
30419
|
command: "scene add",
|
|
30334
|
-
|
|
30420
|
+
startedAt,
|
|
30421
|
+
data: { ...result }
|
|
30335
30422
|
});
|
|
30336
30423
|
return;
|
|
30337
30424
|
}
|
|
@@ -30363,6 +30450,7 @@ Run 'vibe schema scene.<command>' for structured parameter info.`);
|
|
|
30363
30450
|
}
|
|
30364
30451
|
});
|
|
30365
30452
|
sceneCommand.command("lint").description("Validate scene HTML against Hyperframes rules (in-process, no Chrome required)").argument("[root]", "Root composition file relative to --project", "index.html").option("--project <dir>", "Project directory", ".").option("--fix", 'Apply mechanical auto-fixes (currently: missing class="clip")').action(async (root, options) => {
|
|
30453
|
+
const startedAt = Date.now();
|
|
30366
30454
|
const projectDir = resolve14(options.project);
|
|
30367
30455
|
if (!await rootExists(projectDir, root)) {
|
|
30368
30456
|
exitWithError(generalError(
|
|
@@ -30380,11 +30468,12 @@ Run 'vibe schema scene.<command>' for structured parameter info.`);
|
|
|
30380
30468
|
exitWithError(generalError(`Lint failed: ${msg}`));
|
|
30381
30469
|
}
|
|
30382
30470
|
if (isJsonMode()) {
|
|
30383
|
-
|
|
30471
|
+
outputSuccess({
|
|
30384
30472
|
command: "scene lint",
|
|
30385
|
-
|
|
30473
|
+
startedAt,
|
|
30474
|
+
data: { ...result }
|
|
30386
30475
|
});
|
|
30387
|
-
if (!result.ok) process.
|
|
30476
|
+
if (!result.ok) process.exitCode = 1;
|
|
30388
30477
|
return;
|
|
30389
30478
|
}
|
|
30390
30479
|
if (result.ok && result.warningCount === 0 && result.infoCount === 0) {
|
|
@@ -30418,29 +30507,33 @@ Run 'vibe schema scene.<command>' for structured parameter info.`);
|
|
|
30418
30507
|
console.log(` ${source_default.green("\u2714")} ${fx.file} ${source_default.dim(fx.codes.join(", "))}`);
|
|
30419
30508
|
}
|
|
30420
30509
|
}
|
|
30421
|
-
if (!result.ok) process.
|
|
30510
|
+
if (!result.ok) process.exitCode = 1;
|
|
30422
30511
|
});
|
|
30423
30512
|
VALID_FPS = [24, 30, 60];
|
|
30424
30513
|
VALID_QUALITIES = ["draft", "standard", "high"];
|
|
30425
30514
|
VALID_FORMATS = ["mp4", "webm", "mov"];
|
|
30426
30515
|
sceneCommand.command("render").description("Render a scene project to MP4/WebM/MOV via the Hyperframes producer (requires Chrome)").argument("[root]", "Root composition file relative to --project", "index.html").option("--project <dir>", "Project directory", ".").option("-o, --out <path>", "Output file (default: renders/<name>-<timestamp>.<format>)").option("--fps <n>", `Frames per second: ${VALID_FPS.join("|")}`, "30").option("--quality <q>", `Quality preset: ${VALID_QUALITIES.join("|")}`, "standard").option("--format <f>", `Output container: ${VALID_FORMATS.join("|")}`, "mp4").option("--workers <n>", "Capture workers (1-16, default 1)", "1").option("--dry-run", "Preview parameters without rendering").action(async (root, options) => {
|
|
30516
|
+
const startedAt = Date.now();
|
|
30427
30517
|
const fps = validateFps(options.fps);
|
|
30428
30518
|
const quality = validateQuality(options.quality);
|
|
30429
30519
|
const format = validateFormat(options.format);
|
|
30430
30520
|
const workers = validateWorkers(options.workers);
|
|
30431
30521
|
const projectDir = resolve14(options.project);
|
|
30432
30522
|
if (options.dryRun) {
|
|
30433
|
-
|
|
30434
|
-
dryRun: true,
|
|
30523
|
+
outputSuccess({
|
|
30435
30524
|
command: "scene render",
|
|
30436
|
-
|
|
30437
|
-
|
|
30438
|
-
|
|
30439
|
-
|
|
30440
|
-
|
|
30441
|
-
|
|
30442
|
-
|
|
30443
|
-
|
|
30525
|
+
startedAt,
|
|
30526
|
+
dryRun: true,
|
|
30527
|
+
data: {
|
|
30528
|
+
params: {
|
|
30529
|
+
projectDir,
|
|
30530
|
+
root,
|
|
30531
|
+
output: options.out,
|
|
30532
|
+
fps,
|
|
30533
|
+
quality,
|
|
30534
|
+
format,
|
|
30535
|
+
workers
|
|
30536
|
+
}
|
|
30444
30537
|
}
|
|
30445
30538
|
});
|
|
30446
30539
|
return;
|
|
@@ -30461,13 +30554,22 @@ Run 'vibe schema scene.<command>' for structured parameter info.`);
|
|
|
30461
30554
|
if (!result.success) {
|
|
30462
30555
|
spinner2?.fail("Render failed");
|
|
30463
30556
|
if (isJsonMode()) {
|
|
30464
|
-
|
|
30465
|
-
|
|
30557
|
+
outputSuccess({
|
|
30558
|
+
command: "scene render",
|
|
30559
|
+
startedAt,
|
|
30560
|
+
data: { ...result }
|
|
30561
|
+
});
|
|
30562
|
+
process.exitCode = 1;
|
|
30563
|
+
return;
|
|
30466
30564
|
}
|
|
30467
30565
|
exitWithError(generalError(result.error ?? "Render failed"));
|
|
30468
30566
|
}
|
|
30469
30567
|
if (isJsonMode()) {
|
|
30470
|
-
|
|
30568
|
+
outputSuccess({
|
|
30569
|
+
command: "scene render",
|
|
30570
|
+
startedAt,
|
|
30571
|
+
data: { ...result }
|
|
30572
|
+
});
|
|
30471
30573
|
return;
|
|
30472
30574
|
}
|
|
30473
30575
|
spinner2?.succeed(source_default.green(`Render complete: ${result.outputPath}`));
|
|
@@ -30487,25 +30589,29 @@ Run 'vibe schema scene.<command>' for structured parameter info.`);
|
|
|
30487
30589
|
}
|
|
30488
30590
|
});
|
|
30489
30591
|
sceneCommand.command("build").description("One-shot: read STORYBOARD.md cues, dispatch TTS + image-gen per beat, compose, render to MP4 (v0.60)").argument("[project-dir]", "Project directory containing STORYBOARD.md", ".").option("--mode <mode>", "Build mode: agent (host-agent authors HTML) | batch (CLI's internal LLM authors HTML) | auto (agent if any host detected) [Plan H \u2014 Phase 3]", "auto").option("--effort <level>", "Compose effort tier (batch mode only): low|medium|high", "medium").option("--composer <provider>", "LLM that composes scene HTML in batch mode: claude|openai|gemini (default: auto-resolve from available API keys, claude > gemini > openai)").option("--skip-narration", "Don't dispatch TTS even when beats declare narration cues").option("--skip-backdrop", "Don't dispatch image-gen even when beats declare backdrop cues").option("--skip-render", "Compose only \u2014 don't render to MP4").option("--tts <provider>", "TTS provider: auto|elevenlabs|kokoro (overrides frontmatter)").option("--voice <id>", "Voice id (provider-specific \u2014 overrides frontmatter)").option("--image-provider <name>", "Image provider: openai (only one supported in v0.60)").option("--quality <q>", "Image quality: standard|hd", "hd").option("--image-size <s>", "Image size: 1024x1024|1536x1024|1024x1536", "1536x1024").option("--force", "Re-dispatch primitives even when assets already exist").option("--dry-run", "Preview parameters without dispatching").action(async (projectDirArg, options) => {
|
|
30592
|
+
const startedAt = Date.now();
|
|
30490
30593
|
const projectDir = resolve14(projectDirArg);
|
|
30491
30594
|
if (options.dryRun) {
|
|
30492
|
-
|
|
30493
|
-
dryRun: true,
|
|
30595
|
+
outputSuccess({
|
|
30494
30596
|
command: "scene build",
|
|
30495
|
-
|
|
30496
|
-
|
|
30497
|
-
|
|
30498
|
-
|
|
30499
|
-
|
|
30500
|
-
|
|
30501
|
-
|
|
30502
|
-
|
|
30503
|
-
|
|
30504
|
-
|
|
30505
|
-
|
|
30506
|
-
|
|
30507
|
-
|
|
30508
|
-
|
|
30597
|
+
startedAt,
|
|
30598
|
+
dryRun: true,
|
|
30599
|
+
data: {
|
|
30600
|
+
params: {
|
|
30601
|
+
projectDir,
|
|
30602
|
+
mode: options.mode,
|
|
30603
|
+
effort: options.effort,
|
|
30604
|
+
composer: options.composer,
|
|
30605
|
+
skipNarration: options.skipNarration ?? false,
|
|
30606
|
+
skipBackdrop: options.skipBackdrop ?? false,
|
|
30607
|
+
skipRender: options.skipRender ?? false,
|
|
30608
|
+
ttsProvider: options.tts,
|
|
30609
|
+
voice: options.voice,
|
|
30610
|
+
imageProvider: options.imageProvider,
|
|
30611
|
+
imageQuality: options.quality,
|
|
30612
|
+
imageSize: options.imageSize,
|
|
30613
|
+
force: options.force ?? false
|
|
30614
|
+
}
|
|
30509
30615
|
}
|
|
30510
30616
|
});
|
|
30511
30617
|
return;
|
|
@@ -30559,13 +30665,22 @@ Run 'vibe schema scene.<command>' for structured parameter info.`);
|
|
|
30559
30665
|
if (!result.success) {
|
|
30560
30666
|
spinner2?.fail(`Build failed: ${result.error}`);
|
|
30561
30667
|
if (isJsonMode()) {
|
|
30562
|
-
|
|
30563
|
-
|
|
30668
|
+
outputSuccess({
|
|
30669
|
+
command: "scene build",
|
|
30670
|
+
startedAt,
|
|
30671
|
+
data: { ...result }
|
|
30672
|
+
});
|
|
30673
|
+
process.exitCode = 1;
|
|
30674
|
+
return;
|
|
30564
30675
|
}
|
|
30565
30676
|
exitWithError(generalError(result.error ?? "Build failed"));
|
|
30566
30677
|
}
|
|
30567
30678
|
if (isJsonMode()) {
|
|
30568
|
-
|
|
30679
|
+
outputSuccess({
|
|
30680
|
+
command: "scene build",
|
|
30681
|
+
startedAt,
|
|
30682
|
+
data: { ...result }
|
|
30683
|
+
});
|
|
30569
30684
|
return;
|
|
30570
30685
|
}
|
|
30571
30686
|
if (result.phase === "needs-author") {
|
|
@@ -31579,27 +31694,31 @@ Use this image analysis to inform the color palette, typography placement, and o
|
|
|
31579
31694
|
}
|
|
31580
31695
|
function registerMotionCommand(aiCommand) {
|
|
31581
31696
|
aiCommand.command("motion").description("Generate motion graphics using Claude + Remotion (render & composite)").argument("<description>", "Natural language description of the motion graphic").option("-k, --api-key <key>", "Anthropic API key (or set ANTHROPIC_API_KEY env)").option("-o, --output <path>", "Output file path", "motion.tsx").option("-d, --duration <sec>", "Duration in seconds", "5").option("-w, --width <px>", "Width in pixels", "1920").option("-h, --height <px>", "Height in pixels", "1080").option("--fps <fps>", "Frame rate", "30").option("-s, --style <style>", "Style preset: minimal, corporate, playful, cinematic").option("--render", "Render the generated code with Remotion (output .webm)").option("--video <path>", "Base video to composite the motion graphic onto").option("--image <path>", "Image to analyze with Gemini \u2014 color/mood fed into Claude prompt").option("--from-tsx <path>", "Refine an existing TSX file instead of generating from scratch").option("-m, --model <alias>", "LLM model: sonnet (default), opus, gemini, gemini-3.1-pro", "sonnet").option("--dry-run", "Preview parameters without executing").action(async (description, options) => {
|
|
31697
|
+
const startedAt = Date.now();
|
|
31582
31698
|
try {
|
|
31583
31699
|
if (options.output) {
|
|
31584
31700
|
validateOutputPath(options.output);
|
|
31585
31701
|
}
|
|
31586
31702
|
if (options.dryRun) {
|
|
31587
|
-
|
|
31703
|
+
outputSuccess({
|
|
31704
|
+
command: "generate motion",
|
|
31705
|
+
startedAt,
|
|
31588
31706
|
dryRun: true,
|
|
31589
|
-
|
|
31590
|
-
|
|
31591
|
-
|
|
31592
|
-
|
|
31593
|
-
|
|
31594
|
-
|
|
31595
|
-
|
|
31596
|
-
|
|
31597
|
-
|
|
31598
|
-
|
|
31599
|
-
|
|
31600
|
-
|
|
31601
|
-
|
|
31602
|
-
|
|
31707
|
+
data: {
|
|
31708
|
+
params: {
|
|
31709
|
+
description: description.slice(0, 200),
|
|
31710
|
+
duration: options.duration,
|
|
31711
|
+
width: options.width,
|
|
31712
|
+
height: options.height,
|
|
31713
|
+
fps: options.fps,
|
|
31714
|
+
style: options.style,
|
|
31715
|
+
render: options.render ?? false,
|
|
31716
|
+
video: options.video,
|
|
31717
|
+
image: options.image,
|
|
31718
|
+
fromTsx: options.fromTsx,
|
|
31719
|
+
model: options.model,
|
|
31720
|
+
output: options.output
|
|
31721
|
+
}
|
|
31603
31722
|
}
|
|
31604
31723
|
});
|
|
31605
31724
|
return;
|
|
@@ -31713,20 +31832,24 @@ async function executeSoundEffect(options) {
|
|
|
31713
31832
|
}
|
|
31714
31833
|
function registerSoundEffectCommand(parent) {
|
|
31715
31834
|
parent.command("sound-effect").description("Generate sound effect using ElevenLabs").argument("<prompt>", "Description of the sound effect").option("-k, --api-key <key>", "ElevenLabs API key (or set ELEVENLABS_API_KEY env)").option("-o, --output <path>", "Output audio file path", "sound-effect.mp3").option("-d, --duration <seconds>", "Duration in seconds (0.5-22, default: auto)").option("--prompt-influence <value>", "Prompt influence (0-1, default: 0.3)").option("--dry-run", "Preview parameters without executing").action(async (prompt3, options) => {
|
|
31835
|
+
const startedAt = Date.now();
|
|
31716
31836
|
try {
|
|
31717
31837
|
rejectControlChars(prompt3);
|
|
31718
31838
|
if (options.output) {
|
|
31719
31839
|
validateOutputPath(options.output);
|
|
31720
31840
|
}
|
|
31721
31841
|
if (options.dryRun) {
|
|
31722
|
-
|
|
31723
|
-
dryRun: true,
|
|
31842
|
+
outputSuccess({
|
|
31724
31843
|
command: "generate sound-effect",
|
|
31725
|
-
|
|
31726
|
-
|
|
31727
|
-
|
|
31728
|
-
|
|
31729
|
-
|
|
31844
|
+
startedAt,
|
|
31845
|
+
dryRun: true,
|
|
31846
|
+
data: {
|
|
31847
|
+
params: {
|
|
31848
|
+
prompt: prompt3,
|
|
31849
|
+
duration: options.duration,
|
|
31850
|
+
promptInfluence: options.promptInfluence,
|
|
31851
|
+
output: options.output
|
|
31852
|
+
}
|
|
31730
31853
|
}
|
|
31731
31854
|
});
|
|
31732
31855
|
return;
|
|
@@ -31753,7 +31876,11 @@ function registerSoundEffectCommand(parent) {
|
|
|
31753
31876
|
await writeFile13(outputPath, result.audioBuffer);
|
|
31754
31877
|
spinner2.succeed(source_default.green("Sound effect generated"));
|
|
31755
31878
|
if (isJsonMode()) {
|
|
31756
|
-
|
|
31879
|
+
outputSuccess({
|
|
31880
|
+
command: "generate sound-effect",
|
|
31881
|
+
startedAt,
|
|
31882
|
+
data: { outputPath }
|
|
31883
|
+
});
|
|
31757
31884
|
return;
|
|
31758
31885
|
}
|
|
31759
31886
|
console.log(source_default.green(`Saved to: ${outputPath}`));
|
|
@@ -31803,6 +31930,7 @@ async function executeMusicStatus(options) {
|
|
|
31803
31930
|
}
|
|
31804
31931
|
function registerMusicStatusCommand(parent) {
|
|
31805
31932
|
parent.command("music-status", { hidden: true }).description("Check music generation status").argument("<task-id>", "Task ID from music generation").option("-k, --api-key <key>", "Replicate API token (or set REPLICATE_API_TOKEN env)").action(async (taskId, options) => {
|
|
31933
|
+
const startedAt = Date.now();
|
|
31806
31934
|
try {
|
|
31807
31935
|
const apiKey = await requireApiKey(
|
|
31808
31936
|
"REPLICATE_API_TOKEN",
|
|
@@ -31814,12 +31942,15 @@ function registerMusicStatusCommand(parent) {
|
|
|
31814
31942
|
const result = await replicate.getMusicStatus(taskId);
|
|
31815
31943
|
if (isJsonMode()) {
|
|
31816
31944
|
const status = result.audioUrl ? "completed" : result.error ? "failed" : "processing";
|
|
31817
|
-
|
|
31818
|
-
|
|
31819
|
-
|
|
31820
|
-
|
|
31821
|
-
|
|
31822
|
-
|
|
31945
|
+
outputSuccess({
|
|
31946
|
+
command: "generate music-status",
|
|
31947
|
+
startedAt,
|
|
31948
|
+
data: {
|
|
31949
|
+
taskId,
|
|
31950
|
+
status,
|
|
31951
|
+
audioUrl: result.audioUrl,
|
|
31952
|
+
error: result.error
|
|
31953
|
+
}
|
|
31823
31954
|
});
|
|
31824
31955
|
return;
|
|
31825
31956
|
}
|
|
@@ -31857,6 +31988,7 @@ var init_music_status = __esm({
|
|
|
31857
31988
|
// src/commands/generate/video-cancel.ts
|
|
31858
31989
|
function registerVideoCancelCommand(parent) {
|
|
31859
31990
|
parent.command("video-cancel", { hidden: true }).description("Cancel video generation (Grok or Runway)").argument("<task-id>", "Task ID to cancel").option("-p, --provider <provider>", "Provider: grok, runway", "grok").option("-k, --api-key <key>", "API key (or set XAI_API_KEY / RUNWAY_API_SECRET env)").action(async (taskId, options) => {
|
|
31991
|
+
const startedAt = Date.now();
|
|
31860
31992
|
try {
|
|
31861
31993
|
const provider = (options.provider || "grok").toLowerCase();
|
|
31862
31994
|
let success = false;
|
|
@@ -31869,7 +32001,11 @@ function registerVideoCancelCommand(parent) {
|
|
|
31869
32001
|
if (success) {
|
|
31870
32002
|
spinner2.succeed(source_default.green("Generation cancelled"));
|
|
31871
32003
|
if (isJsonMode()) {
|
|
31872
|
-
|
|
32004
|
+
outputSuccess({
|
|
32005
|
+
command: "generate video-cancel",
|
|
32006
|
+
startedAt,
|
|
32007
|
+
data: { taskId, provider: "grok", cancelled: true }
|
|
32008
|
+
});
|
|
31873
32009
|
return;
|
|
31874
32010
|
}
|
|
31875
32011
|
} else {
|
|
@@ -31889,7 +32025,11 @@ function registerVideoCancelCommand(parent) {
|
|
|
31889
32025
|
if (success) {
|
|
31890
32026
|
spinner2.succeed(source_default.green("Generation cancelled"));
|
|
31891
32027
|
if (isJsonMode()) {
|
|
31892
|
-
|
|
32028
|
+
outputSuccess({
|
|
32029
|
+
command: "generate video-cancel",
|
|
32030
|
+
startedAt,
|
|
32031
|
+
data: { taskId, provider: "runway", cancelled: true }
|
|
32032
|
+
});
|
|
31893
32033
|
return;
|
|
31894
32034
|
}
|
|
31895
32035
|
} else {
|
|
@@ -31962,16 +32102,18 @@ async function executeBackground(options) {
|
|
|
31962
32102
|
}
|
|
31963
32103
|
function registerBackgroundCommand(parent) {
|
|
31964
32104
|
parent.command("background").description("Generate video background using DALL-E").argument("<description>", "Background description").option("-k, --api-key <key>", "OpenAI API key (or set OPENAI_API_KEY env)").option("-o, --output <path>", "Output file path (downloads image)").option("-a, --aspect <ratio>", "Aspect ratio: 16:9, 9:16, 1:1", "16:9").option("--dry-run", "Preview parameters without executing").action(async (description, options) => {
|
|
32105
|
+
const startedAt = Date.now();
|
|
31965
32106
|
try {
|
|
31966
32107
|
rejectControlChars(description);
|
|
31967
32108
|
if (options.output) {
|
|
31968
32109
|
validateOutputPath(options.output);
|
|
31969
32110
|
}
|
|
31970
32111
|
if (options.dryRun) {
|
|
31971
|
-
|
|
31972
|
-
dryRun: true,
|
|
32112
|
+
outputSuccess({
|
|
31973
32113
|
command: "generate background",
|
|
31974
|
-
|
|
32114
|
+
startedAt,
|
|
32115
|
+
dryRun: true,
|
|
32116
|
+
data: { params: { description, aspect: options.aspect, output: options.output } }
|
|
31975
32117
|
});
|
|
31976
32118
|
return;
|
|
31977
32119
|
}
|
|
@@ -32006,7 +32148,11 @@ function registerBackgroundCommand(parent) {
|
|
|
32006
32148
|
await mkdir9(dirname7(outputPath), { recursive: true });
|
|
32007
32149
|
await writeFile14(outputPath, buffer);
|
|
32008
32150
|
}
|
|
32009
|
-
|
|
32151
|
+
outputSuccess({
|
|
32152
|
+
command: "generate background",
|
|
32153
|
+
startedAt,
|
|
32154
|
+
data: { imageUrl: img.url, outputPath }
|
|
32155
|
+
});
|
|
32010
32156
|
return;
|
|
32011
32157
|
}
|
|
32012
32158
|
console.log();
|
|
@@ -32152,6 +32298,7 @@ async function executeStoryboard(options) {
|
|
|
32152
32298
|
}
|
|
32153
32299
|
function registerStoryboardCommand(parent) {
|
|
32154
32300
|
parent.command("storyboard").description("Generate video storyboard from content using Claude").argument("<content>", "Content to analyze (text or file path)").option("-k, --api-key <key>", "Anthropic API key (or set ANTHROPIC_API_KEY env)").option("-o, --output <path>", "Output JSON file path").option("-d, --duration <sec>", "Target total duration in seconds").option("-f, --file", "Treat content argument as file path").option("-c, --creativity <level>", "Creativity level: low (default, consistent) or high (varied, unexpected)", "low").option("--dry-run", "Preview parameters without executing").action(async (content, options) => {
|
|
32301
|
+
const startedAt = Date.now();
|
|
32155
32302
|
try {
|
|
32156
32303
|
rejectControlChars(content);
|
|
32157
32304
|
if (options.output) {
|
|
@@ -32167,13 +32314,16 @@ function registerStoryboardCommand(parent) {
|
|
|
32167
32314
|
textContent = await readFile14(filePath, "utf-8");
|
|
32168
32315
|
}
|
|
32169
32316
|
if (options.dryRun) {
|
|
32170
|
-
|
|
32171
|
-
dryRun: true,
|
|
32317
|
+
outputSuccess({
|
|
32172
32318
|
command: "generate storyboard",
|
|
32173
|
-
|
|
32174
|
-
|
|
32175
|
-
|
|
32176
|
-
|
|
32319
|
+
startedAt,
|
|
32320
|
+
dryRun: true,
|
|
32321
|
+
data: {
|
|
32322
|
+
params: {
|
|
32323
|
+
content: textContent.substring(0, 200),
|
|
32324
|
+
duration: options.duration,
|
|
32325
|
+
creativity
|
|
32326
|
+
}
|
|
32177
32327
|
}
|
|
32178
32328
|
});
|
|
32179
32329
|
return;
|
|
@@ -32205,16 +32355,23 @@ function registerStoryboardCommand(parent) {
|
|
|
32205
32355
|
const outputPath = resolve19(process.cwd(), options.output);
|
|
32206
32356
|
await writeFile15(outputPath, JSON.stringify(segments, null, 2), "utf-8");
|
|
32207
32357
|
if (isJsonMode()) {
|
|
32208
|
-
|
|
32209
|
-
|
|
32210
|
-
|
|
32211
|
-
|
|
32212
|
-
|
|
32358
|
+
outputSuccess({
|
|
32359
|
+
command: "generate storyboard",
|
|
32360
|
+
startedAt,
|
|
32361
|
+
data: {
|
|
32362
|
+
segmentCount: segments.length,
|
|
32363
|
+
segments,
|
|
32364
|
+
outputPath
|
|
32365
|
+
}
|
|
32213
32366
|
});
|
|
32214
32367
|
return;
|
|
32215
32368
|
}
|
|
32216
32369
|
} else if (isJsonMode()) {
|
|
32217
|
-
|
|
32370
|
+
outputSuccess({
|
|
32371
|
+
command: "generate storyboard",
|
|
32372
|
+
startedAt,
|
|
32373
|
+
data: { segmentCount: segments.length, segments }
|
|
32374
|
+
});
|
|
32218
32375
|
return;
|
|
32219
32376
|
}
|
|
32220
32377
|
console.log();
|
|
@@ -32459,6 +32616,7 @@ async function executeSpeech(options) {
|
|
|
32459
32616
|
}
|
|
32460
32617
|
function registerSpeechCommand(parent) {
|
|
32461
32618
|
parent.command("speech").alias("tts").description("Generate speech from text using ElevenLabs").argument("[text]", "Text to convert to speech (interactive if omitted)").option("-k, --api-key <key>", "ElevenLabs API key (or set ELEVENLABS_API_KEY env)").option("-o, --output <path>", "Output audio file path", "output.mp3").option("-v, --voice <id>", "Voice ID (default: Rachel)", "21m00Tcm4TlvDq8ikWAM").option("--list-voices", "List available voices").option("--fit-duration <seconds>", "Speed up audio to fit target duration (via FFmpeg atempo)", parseFloat).option("--dry-run", "Preview parameters without executing").action(async (text, options) => {
|
|
32619
|
+
const startedAt = Date.now();
|
|
32462
32620
|
try {
|
|
32463
32621
|
if (!text) {
|
|
32464
32622
|
if (hasTTY()) {
|
|
@@ -32480,10 +32638,11 @@ function registerSpeechCommand(parent) {
|
|
|
32480
32638
|
validateOutputPath(options.output);
|
|
32481
32639
|
}
|
|
32482
32640
|
if (options.dryRun) {
|
|
32483
|
-
|
|
32484
|
-
dryRun: true,
|
|
32641
|
+
outputSuccess({
|
|
32485
32642
|
command: "generate speech",
|
|
32486
|
-
|
|
32643
|
+
startedAt,
|
|
32644
|
+
dryRun: true,
|
|
32645
|
+
data: { params: { text, voice: options.voice, output: options.output } }
|
|
32487
32646
|
});
|
|
32488
32647
|
return;
|
|
32489
32648
|
}
|
|
@@ -32572,10 +32731,13 @@ function registerSpeechCommand(parent) {
|
|
|
32572
32731
|
}
|
|
32573
32732
|
}
|
|
32574
32733
|
if (isJsonMode()) {
|
|
32575
|
-
|
|
32576
|
-
|
|
32577
|
-
|
|
32578
|
-
|
|
32734
|
+
outputSuccess({
|
|
32735
|
+
command: "generate speech",
|
|
32736
|
+
startedAt,
|
|
32737
|
+
data: {
|
|
32738
|
+
characterCount: result.characterCount,
|
|
32739
|
+
outputPath
|
|
32740
|
+
}
|
|
32579
32741
|
});
|
|
32580
32742
|
return;
|
|
32581
32743
|
}
|
|
@@ -32664,6 +32826,7 @@ async function executeMusic(options) {
|
|
|
32664
32826
|
}
|
|
32665
32827
|
function registerMusicCommand(parent) {
|
|
32666
32828
|
parent.command("music").description("Generate background music from a text prompt (ElevenLabs or Replicate MusicGen)").argument("<prompt>", "Description of the music to generate").option("-p, --provider <provider>", "Provider: elevenlabs (default, up to 10min), replicate (MusicGen, max 30s)", "elevenlabs").option("-k, --api-key <key>", "API key (or set ELEVENLABS_API_KEY / REPLICATE_API_TOKEN env)").option("-d, --duration <seconds>", "Duration in seconds (elevenlabs: 3-600, replicate: 1-30)", "8").option("--instrumental", "Force instrumental music, no vocals (ElevenLabs only)").option("-m, --melody <file>", "Reference melody audio file for conditioning (Replicate only)").option("--model <model>", "Model variant (Replicate only): large, stereo-large, melody-large, stereo-melody-large", "stereo-large").option("-o, --output <path>", "Output audio file path", "music.mp3").option("--no-wait", "Don't wait for generation to complete (Replicate async mode)").option("--dry-run", "Preview parameters without executing").action(async (prompt3, options) => {
|
|
32829
|
+
const startedAt = Date.now();
|
|
32667
32830
|
try {
|
|
32668
32831
|
rejectControlChars(prompt3);
|
|
32669
32832
|
if (options.output) {
|
|
@@ -32671,16 +32834,19 @@ function registerMusicCommand(parent) {
|
|
|
32671
32834
|
}
|
|
32672
32835
|
const provider = (options.provider || "elevenlabs").toLowerCase();
|
|
32673
32836
|
if (options.dryRun) {
|
|
32674
|
-
|
|
32675
|
-
dryRun: true,
|
|
32837
|
+
outputSuccess({
|
|
32676
32838
|
command: "generate music",
|
|
32677
|
-
|
|
32678
|
-
|
|
32679
|
-
|
|
32680
|
-
|
|
32681
|
-
|
|
32682
|
-
|
|
32683
|
-
|
|
32839
|
+
startedAt,
|
|
32840
|
+
dryRun: true,
|
|
32841
|
+
data: {
|
|
32842
|
+
params: {
|
|
32843
|
+
prompt: prompt3,
|
|
32844
|
+
provider,
|
|
32845
|
+
duration: options.duration,
|
|
32846
|
+
model: options.model,
|
|
32847
|
+
output: options.output,
|
|
32848
|
+
instrumental: options.instrumental
|
|
32849
|
+
}
|
|
32684
32850
|
}
|
|
32685
32851
|
});
|
|
32686
32852
|
return;
|
|
@@ -32707,11 +32873,14 @@ function registerMusicCommand(parent) {
|
|
|
32707
32873
|
await writeFile17(outputPath, result.audioBuffer);
|
|
32708
32874
|
spinner2.succeed(source_default.green("Music generated successfully"));
|
|
32709
32875
|
if (isJsonMode()) {
|
|
32710
|
-
|
|
32711
|
-
|
|
32712
|
-
|
|
32713
|
-
|
|
32714
|
-
|
|
32876
|
+
outputSuccess({
|
|
32877
|
+
command: "generate music",
|
|
32878
|
+
startedAt,
|
|
32879
|
+
data: {
|
|
32880
|
+
provider: "elevenlabs",
|
|
32881
|
+
outputPath,
|
|
32882
|
+
duration
|
|
32883
|
+
}
|
|
32715
32884
|
});
|
|
32716
32885
|
return;
|
|
32717
32886
|
}
|
|
@@ -32779,12 +32948,15 @@ function registerMusicCommand(parent) {
|
|
|
32779
32948
|
await writeFile17(outputPath, audioBuffer);
|
|
32780
32949
|
spinner2.succeed(source_default.green("Music generated successfully"));
|
|
32781
32950
|
if (isJsonMode()) {
|
|
32782
|
-
|
|
32783
|
-
|
|
32784
|
-
|
|
32785
|
-
|
|
32786
|
-
|
|
32787
|
-
|
|
32951
|
+
outputSuccess({
|
|
32952
|
+
command: "generate music",
|
|
32953
|
+
startedAt,
|
|
32954
|
+
data: {
|
|
32955
|
+
provider: "replicate",
|
|
32956
|
+
taskId: result.taskId,
|
|
32957
|
+
audioUrl: finalResult.audioUrl,
|
|
32958
|
+
outputPath
|
|
32959
|
+
}
|
|
32788
32960
|
});
|
|
32789
32961
|
return;
|
|
32790
32962
|
}
|
|
@@ -33084,6 +33256,7 @@ import { existsSync as existsSync15 } from "node:fs";
|
|
|
33084
33256
|
import { writeFile as writeFile19, mkdir as mkdir11 } from "node:fs/promises";
|
|
33085
33257
|
function registerThumbnailCommand(parent) {
|
|
33086
33258
|
parent.command("thumbnail").description("Generate video thumbnail (DALL-E) or extract best frame from video (Gemini)").argument("[description]", "Thumbnail description (for DALL-E generation)").option("-k, --api-key <key>", "API key (OpenAI for generation, Google for best-frame)").option("-o, --output <path>", "Output file path").option("-s, --style <style>", "Platform style: youtube, instagram, tiktok, twitter").option("--best-frame <video>", "Extract best thumbnail frame from video using Gemini AI").option("--prompt <prompt>", "Custom prompt for best-frame analysis").option("--model <model>", "Gemini model: flash, latest, pro (default: flash)", "flash").action(async (description, options) => {
|
|
33259
|
+
const startedAt = Date.now();
|
|
33087
33260
|
try {
|
|
33088
33261
|
if (description) rejectControlChars(description);
|
|
33089
33262
|
if (options.output) {
|
|
@@ -33119,11 +33292,14 @@ function registerThumbnailCommand(parent) {
|
|
|
33119
33292
|
}
|
|
33120
33293
|
spinner3.succeed(source_default.green("Best frame extracted"));
|
|
33121
33294
|
if (isJsonMode()) {
|
|
33122
|
-
|
|
33123
|
-
|
|
33124
|
-
|
|
33125
|
-
|
|
33126
|
-
|
|
33295
|
+
outputSuccess({
|
|
33296
|
+
command: "generate thumbnail",
|
|
33297
|
+
startedAt,
|
|
33298
|
+
data: {
|
|
33299
|
+
timestamp: result2.timestamp,
|
|
33300
|
+
reason: result2.reason,
|
|
33301
|
+
outputPath: result2.outputPath
|
|
33302
|
+
}
|
|
33127
33303
|
});
|
|
33128
33304
|
return;
|
|
33129
33305
|
}
|
|
@@ -33171,7 +33347,11 @@ function registerThumbnailCommand(parent) {
|
|
|
33171
33347
|
await mkdir11(dirname9(outputPath), { recursive: true });
|
|
33172
33348
|
await writeFile19(outputPath, buffer);
|
|
33173
33349
|
}
|
|
33174
|
-
|
|
33350
|
+
outputSuccess({
|
|
33351
|
+
command: "generate thumbnail",
|
|
33352
|
+
startedAt,
|
|
33353
|
+
data: { imageUrl: img.url, outputPath }
|
|
33354
|
+
});
|
|
33175
33355
|
return;
|
|
33176
33356
|
}
|
|
33177
33357
|
console.log();
|
|
@@ -33242,6 +33422,7 @@ function getStatusColor(status) {
|
|
|
33242
33422
|
}
|
|
33243
33423
|
function registerVideoStatusCommand(parent) {
|
|
33244
33424
|
parent.command("video-status", { hidden: true }).description("Check video generation status (Grok, Runway, or Kling)").argument("<task-id>", "Task ID from video generation").option("-p, --provider <provider>", "Provider: grok, runway, kling", "grok").option("-k, --api-key <key>", "API key (or set XAI_API_KEY / RUNWAY_API_SECRET / KLING_API_KEY env)").option("-t, --type <type>", "Task type: text2video or image2video (Kling only)", "text2video").option("-w, --wait", "Wait for completion").option("-o, --output <path>", "Download video when complete").action(async (taskId, options) => {
|
|
33425
|
+
const startedAt = Date.now();
|
|
33245
33426
|
try {
|
|
33246
33427
|
const provider = (options.provider || "grok").toLowerCase();
|
|
33247
33428
|
if (provider === "grok") {
|
|
@@ -33264,14 +33445,17 @@ function registerVideoStatusCommand(parent) {
|
|
|
33264
33445
|
outputPath = resolve24(process.cwd(), options.output);
|
|
33265
33446
|
await writeFile20(outputPath, buffer);
|
|
33266
33447
|
}
|
|
33267
|
-
|
|
33268
|
-
|
|
33269
|
-
|
|
33270
|
-
|
|
33271
|
-
|
|
33272
|
-
|
|
33273
|
-
|
|
33274
|
-
|
|
33448
|
+
outputSuccess({
|
|
33449
|
+
command: "generate video-status",
|
|
33450
|
+
startedAt,
|
|
33451
|
+
data: {
|
|
33452
|
+
taskId,
|
|
33453
|
+
provider: "grok",
|
|
33454
|
+
status: result.status,
|
|
33455
|
+
videoUrl: result.videoUrl,
|
|
33456
|
+
error: result.error,
|
|
33457
|
+
outputPath
|
|
33458
|
+
}
|
|
33275
33459
|
});
|
|
33276
33460
|
return;
|
|
33277
33461
|
}
|
|
@@ -33329,15 +33513,18 @@ function registerVideoStatusCommand(parent) {
|
|
|
33329
33513
|
outputPath = resolve24(process.cwd(), options.output);
|
|
33330
33514
|
await writeFile20(outputPath, buffer);
|
|
33331
33515
|
}
|
|
33332
|
-
|
|
33333
|
-
|
|
33334
|
-
|
|
33335
|
-
|
|
33336
|
-
|
|
33337
|
-
|
|
33338
|
-
|
|
33339
|
-
|
|
33340
|
-
|
|
33516
|
+
outputSuccess({
|
|
33517
|
+
command: "generate video-status",
|
|
33518
|
+
startedAt,
|
|
33519
|
+
data: {
|
|
33520
|
+
taskId,
|
|
33521
|
+
provider: "runway",
|
|
33522
|
+
status: result.status,
|
|
33523
|
+
videoUrl: result.videoUrl,
|
|
33524
|
+
progress: result.progress,
|
|
33525
|
+
error: result.error,
|
|
33526
|
+
outputPath
|
|
33527
|
+
}
|
|
33341
33528
|
});
|
|
33342
33529
|
return;
|
|
33343
33530
|
}
|
|
@@ -33393,15 +33580,18 @@ function registerVideoStatusCommand(parent) {
|
|
|
33393
33580
|
outputPath = resolve24(process.cwd(), options.output);
|
|
33394
33581
|
await writeFile20(outputPath, buffer);
|
|
33395
33582
|
}
|
|
33396
|
-
|
|
33397
|
-
|
|
33398
|
-
|
|
33399
|
-
|
|
33400
|
-
|
|
33401
|
-
|
|
33402
|
-
|
|
33403
|
-
|
|
33404
|
-
|
|
33583
|
+
outputSuccess({
|
|
33584
|
+
command: "generate video-status",
|
|
33585
|
+
startedAt,
|
|
33586
|
+
data: {
|
|
33587
|
+
taskId,
|
|
33588
|
+
provider: "kling",
|
|
33589
|
+
status: result.status,
|
|
33590
|
+
videoUrl: result.videoUrl,
|
|
33591
|
+
duration: result.duration,
|
|
33592
|
+
error: result.error,
|
|
33593
|
+
outputPath
|
|
33594
|
+
}
|
|
33405
33595
|
});
|
|
33406
33596
|
return;
|
|
33407
33597
|
}
|
|
@@ -33465,22 +33655,26 @@ import { resolve as resolve25 } from "node:path";
|
|
|
33465
33655
|
import { writeFile as writeFile21 } from "node:fs/promises";
|
|
33466
33656
|
function registerVideoExtendCommand(parent) {
|
|
33467
33657
|
parent.command("video-extend", { hidden: true }).description("Extend video duration (Kling by video ID, Veo by operation name)").argument("<id>", "Kling video ID or Veo operation name").option("-p, --provider <provider>", "Provider: kling, veo", "kling").option("-k, --api-key <key>", "API key (KLING_API_KEY or GOOGLE_API_KEY)").option("-o, --output <path>", "Output file path").option("--prompt <text>", "Continuation prompt").option("-d, --duration <sec>", "Duration: 5 or 10 (Kling), 4/6/8 (Veo)", "5").option("-n, --negative <prompt>", "Negative prompt (what to avoid, Kling only)").option("--veo-model <model>", "Veo model: 3.0, 3.1, 3.1-fast", "3.1").option("--no-wait", "Start extension and return task ID without waiting").option("--dry-run", "Preview parameters without executing").action(async (id, options) => {
|
|
33658
|
+
const startedAt = Date.now();
|
|
33468
33659
|
try {
|
|
33469
33660
|
const provider = (options.provider || "kling").toLowerCase();
|
|
33470
33661
|
if (options.output) {
|
|
33471
33662
|
validateOutputPath(options.output);
|
|
33472
33663
|
}
|
|
33473
33664
|
if (options.dryRun) {
|
|
33474
|
-
|
|
33475
|
-
dryRun: true,
|
|
33665
|
+
outputSuccess({
|
|
33476
33666
|
command: "generate video-extend",
|
|
33477
|
-
|
|
33478
|
-
|
|
33479
|
-
|
|
33480
|
-
|
|
33481
|
-
|
|
33482
|
-
|
|
33483
|
-
|
|
33667
|
+
startedAt,
|
|
33668
|
+
dryRun: true,
|
|
33669
|
+
data: {
|
|
33670
|
+
params: {
|
|
33671
|
+
id,
|
|
33672
|
+
provider,
|
|
33673
|
+
prompt: options.prompt,
|
|
33674
|
+
duration: options.duration,
|
|
33675
|
+
negative: options.negative,
|
|
33676
|
+
veoModel: options.veoModel
|
|
33677
|
+
}
|
|
33484
33678
|
}
|
|
33485
33679
|
});
|
|
33486
33680
|
return;
|
|
@@ -33537,13 +33731,16 @@ function registerVideoExtendCommand(parent) {
|
|
|
33537
33731
|
outputPath = resolve25(process.cwd(), options.output);
|
|
33538
33732
|
await writeFile21(outputPath, buffer);
|
|
33539
33733
|
}
|
|
33540
|
-
|
|
33541
|
-
|
|
33542
|
-
|
|
33543
|
-
|
|
33544
|
-
|
|
33545
|
-
|
|
33546
|
-
|
|
33734
|
+
outputSuccess({
|
|
33735
|
+
command: "generate video-extend",
|
|
33736
|
+
startedAt,
|
|
33737
|
+
data: {
|
|
33738
|
+
provider: "kling",
|
|
33739
|
+
taskId: result.id,
|
|
33740
|
+
videoUrl: finalResult.videoUrl,
|
|
33741
|
+
duration: finalResult.duration,
|
|
33742
|
+
outputPath
|
|
33743
|
+
}
|
|
33547
33744
|
});
|
|
33548
33745
|
return;
|
|
33549
33746
|
}
|
|
@@ -33623,13 +33820,16 @@ function registerVideoExtendCommand(parent) {
|
|
|
33623
33820
|
outputPath = resolve25(process.cwd(), options.output);
|
|
33624
33821
|
await writeFile21(outputPath, buffer);
|
|
33625
33822
|
}
|
|
33626
|
-
|
|
33627
|
-
|
|
33628
|
-
|
|
33629
|
-
|
|
33630
|
-
|
|
33631
|
-
|
|
33632
|
-
|
|
33823
|
+
outputSuccess({
|
|
33824
|
+
command: "generate video-extend",
|
|
33825
|
+
startedAt,
|
|
33826
|
+
data: {
|
|
33827
|
+
provider: "veo",
|
|
33828
|
+
taskId: result.id,
|
|
33829
|
+
videoUrl: finalResult.videoUrl,
|
|
33830
|
+
duration: finalResult.duration,
|
|
33831
|
+
outputPath
|
|
33832
|
+
}
|
|
33633
33833
|
});
|
|
33634
33834
|
return;
|
|
33635
33835
|
}
|
|
@@ -33744,6 +33944,7 @@ Examples:
|
|
|
33744
33944
|
$ vibe gen img "landscape photo" -o wide.png -r 16:9
|
|
33745
33945
|
$ vibe gen img "portrait" -o portrait.png -p gemini -m pro
|
|
33746
33946
|
$ vibe gen img "product shot" --dry-run --json`).action(async (prompt3, options) => {
|
|
33947
|
+
const startedAt = Date.now();
|
|
33747
33948
|
try {
|
|
33748
33949
|
if (!prompt3) {
|
|
33749
33950
|
if (hasTTY()) {
|
|
@@ -33802,18 +34003,21 @@ Examples:
|
|
|
33802
34003
|
provider = resolved?.name ?? "gemini";
|
|
33803
34004
|
}
|
|
33804
34005
|
if (options.dryRun) {
|
|
33805
|
-
|
|
33806
|
-
dryRun: true,
|
|
34006
|
+
outputSuccess({
|
|
33807
34007
|
command: "generate image",
|
|
33808
|
-
|
|
33809
|
-
|
|
33810
|
-
|
|
33811
|
-
|
|
33812
|
-
|
|
33813
|
-
|
|
33814
|
-
|
|
33815
|
-
|
|
33816
|
-
|
|
34008
|
+
startedAt,
|
|
34009
|
+
dryRun: true,
|
|
34010
|
+
data: {
|
|
34011
|
+
params: {
|
|
34012
|
+
prompt: prompt3,
|
|
34013
|
+
provider,
|
|
34014
|
+
model: options.model,
|
|
34015
|
+
ratio: options.ratio,
|
|
34016
|
+
size: options.size,
|
|
34017
|
+
quality: options.quality,
|
|
34018
|
+
count: options.count,
|
|
34019
|
+
output: options.output
|
|
34020
|
+
}
|
|
33817
34021
|
}
|
|
33818
34022
|
});
|
|
33819
34023
|
return;
|
|
@@ -33851,14 +34055,17 @@ Examples:
|
|
|
33851
34055
|
await mkdir12(dirname10(outputPath), { recursive: true });
|
|
33852
34056
|
await writeFile22(outputPath, buffer);
|
|
33853
34057
|
}
|
|
33854
|
-
|
|
33855
|
-
|
|
33856
|
-
|
|
33857
|
-
|
|
33858
|
-
|
|
33859
|
-
|
|
33860
|
-
|
|
33861
|
-
|
|
34058
|
+
outputSuccess({
|
|
34059
|
+
command: "generate image",
|
|
34060
|
+
startedAt,
|
|
34061
|
+
data: {
|
|
34062
|
+
provider: "openai",
|
|
34063
|
+
images: result.images.map((img) => ({
|
|
34064
|
+
url: img.url,
|
|
34065
|
+
revisedPrompt: img.revisedPrompt
|
|
34066
|
+
})),
|
|
34067
|
+
outputPath
|
|
34068
|
+
}
|
|
33862
34069
|
});
|
|
33863
34070
|
return;
|
|
33864
34071
|
}
|
|
@@ -33970,13 +34177,18 @@ Examples:
|
|
|
33970
34177
|
await mkdir12(dirname10(outputPath), { recursive: true });
|
|
33971
34178
|
await writeFile22(outputPath, buffer);
|
|
33972
34179
|
}
|
|
33973
|
-
|
|
33974
|
-
|
|
33975
|
-
|
|
33976
|
-
|
|
33977
|
-
|
|
33978
|
-
|
|
33979
|
-
|
|
34180
|
+
outputSuccess({
|
|
34181
|
+
command: "generate image",
|
|
34182
|
+
startedAt,
|
|
34183
|
+
warnings: usedLabel.includes("fallback") ? [`Model "${options.model}" failed; fell back to flash`] : [],
|
|
34184
|
+
data: {
|
|
34185
|
+
provider: "gemini",
|
|
34186
|
+
model: usedLabel,
|
|
34187
|
+
images: result.images.map((img) => ({
|
|
34188
|
+
mimeType: img.mimeType
|
|
34189
|
+
})),
|
|
34190
|
+
outputPath
|
|
34191
|
+
}
|
|
33980
34192
|
});
|
|
33981
34193
|
return;
|
|
33982
34194
|
}
|
|
@@ -34060,11 +34272,14 @@ Examples:
|
|
|
34060
34272
|
await mkdir12(dirname10(outputPath), { recursive: true });
|
|
34061
34273
|
await writeFile22(outputPath, buffer);
|
|
34062
34274
|
}
|
|
34063
|
-
|
|
34064
|
-
|
|
34065
|
-
|
|
34066
|
-
|
|
34067
|
-
|
|
34275
|
+
outputSuccess({
|
|
34276
|
+
command: "generate image",
|
|
34277
|
+
startedAt,
|
|
34278
|
+
data: {
|
|
34279
|
+
provider: "grok",
|
|
34280
|
+
images: result.images.map((img) => ({ url: img.url })),
|
|
34281
|
+
outputPath
|
|
34282
|
+
}
|
|
34068
34283
|
});
|
|
34069
34284
|
return;
|
|
34070
34285
|
}
|
|
@@ -34135,11 +34350,14 @@ Examples:
|
|
|
34135
34350
|
proc.on("close", (code) => {
|
|
34136
34351
|
if (code === 0) {
|
|
34137
34352
|
if (isJsonMode()) {
|
|
34138
|
-
|
|
34139
|
-
|
|
34140
|
-
|
|
34141
|
-
|
|
34142
|
-
|
|
34353
|
+
outputSuccess({
|
|
34354
|
+
command: "generate image",
|
|
34355
|
+
startedAt,
|
|
34356
|
+
data: {
|
|
34357
|
+
provider: "runway",
|
|
34358
|
+
images: [{ format: "file" }],
|
|
34359
|
+
outputPath
|
|
34360
|
+
}
|
|
34143
34361
|
});
|
|
34144
34362
|
} else {
|
|
34145
34363
|
spinner2.succeed(source_default.green("Generated image with Runway"));
|
|
@@ -35865,6 +36083,7 @@ Examples:
|
|
|
35865
36083
|
$ vibe gen vid "epic scene" -i frame.png -o out.mp4 -p runway # Image-to-video
|
|
35866
36084
|
$ vibe gen vid "ocean waves" -o waves.mp4 -p veo --resolution 1080p # Veo
|
|
35867
36085
|
$ vibe gen vid "sunset" -o sun.mp4 -d 10 --dry-run --json`).action(async (prompt3, options) => {
|
|
36086
|
+
const startedAt = Date.now();
|
|
35868
36087
|
try {
|
|
35869
36088
|
if (!prompt3) {
|
|
35870
36089
|
if (hasTTY()) {
|
|
@@ -35956,19 +36175,22 @@ Examples:
|
|
|
35956
36175
|
options.ratio = "16:9";
|
|
35957
36176
|
}
|
|
35958
36177
|
if (options.dryRun) {
|
|
35959
|
-
|
|
35960
|
-
dryRun: true,
|
|
36178
|
+
outputSuccess({
|
|
35961
36179
|
command: "generate video",
|
|
35962
|
-
|
|
35963
|
-
|
|
35964
|
-
|
|
35965
|
-
|
|
35966
|
-
|
|
35967
|
-
|
|
35968
|
-
|
|
35969
|
-
|
|
35970
|
-
|
|
35971
|
-
|
|
36180
|
+
startedAt,
|
|
36181
|
+
dryRun: true,
|
|
36182
|
+
data: {
|
|
36183
|
+
params: {
|
|
36184
|
+
prompt: prompt3,
|
|
36185
|
+
provider,
|
|
36186
|
+
duration: options.duration,
|
|
36187
|
+
ratio: options.ratio,
|
|
36188
|
+
image: options.image,
|
|
36189
|
+
mode: options.mode,
|
|
36190
|
+
negative: options.negative,
|
|
36191
|
+
resolution: options.resolution,
|
|
36192
|
+
veoModel: options.veoModel
|
|
36193
|
+
}
|
|
35972
36194
|
}
|
|
35973
36195
|
});
|
|
35974
36196
|
return;
|
|
@@ -36248,13 +36470,16 @@ Examples:
|
|
|
36248
36470
|
outputPath = resolve29(process.cwd(), options.output);
|
|
36249
36471
|
await writeFile25(outputPath, buffer);
|
|
36250
36472
|
}
|
|
36251
|
-
|
|
36252
|
-
|
|
36253
|
-
|
|
36254
|
-
|
|
36255
|
-
|
|
36256
|
-
|
|
36257
|
-
|
|
36473
|
+
outputSuccess({
|
|
36474
|
+
command: "generate video",
|
|
36475
|
+
startedAt,
|
|
36476
|
+
data: {
|
|
36477
|
+
provider,
|
|
36478
|
+
taskId: result?.id,
|
|
36479
|
+
videoUrl: finalResult.videoUrl,
|
|
36480
|
+
duration: finalResult.duration,
|
|
36481
|
+
outputPath
|
|
36482
|
+
}
|
|
36258
36483
|
});
|
|
36259
36484
|
return;
|
|
36260
36485
|
}
|
|
@@ -37687,6 +37912,7 @@ Examples:
|
|
|
37687
37912
|
$ vibe ed sc video.mp4 --dry-run --json
|
|
37688
37913
|
|
|
37689
37914
|
No API key needed (FFmpeg only). Use --use-gemini for smart detection (requires GOOGLE_API_KEY).`).action(async (videoPath, options) => {
|
|
37915
|
+
const startedAt = Date.now();
|
|
37690
37916
|
try {
|
|
37691
37917
|
if (options.output) {
|
|
37692
37918
|
validateOutputPath(options.output);
|
|
@@ -37703,16 +37929,19 @@ No API key needed (FFmpeg only). Use --use-gemini for smart detection (requires
|
|
|
37703
37929
|
const outputPath = options.output || `${name}-cut${ext}`;
|
|
37704
37930
|
const useGemini = options.useGemini || false;
|
|
37705
37931
|
if (options.dryRun) {
|
|
37706
|
-
|
|
37707
|
-
dryRun: true,
|
|
37932
|
+
outputSuccess({
|
|
37708
37933
|
command: "edit silence-cut",
|
|
37709
|
-
|
|
37710
|
-
|
|
37711
|
-
|
|
37712
|
-
|
|
37713
|
-
|
|
37714
|
-
|
|
37715
|
-
|
|
37934
|
+
startedAt,
|
|
37935
|
+
dryRun: true,
|
|
37936
|
+
data: {
|
|
37937
|
+
params: {
|
|
37938
|
+
videoPath: absVideoPath,
|
|
37939
|
+
noiseThreshold: parseFloat(options.noise),
|
|
37940
|
+
minDuration: parseFloat(options.minDuration),
|
|
37941
|
+
padding: parseFloat(options.padding),
|
|
37942
|
+
useGemini,
|
|
37943
|
+
analyzeOnly: options.analyzeOnly || false
|
|
37944
|
+
}
|
|
37716
37945
|
}
|
|
37717
37946
|
});
|
|
37718
37947
|
return;
|
|
@@ -37737,13 +37966,16 @@ No API key needed (FFmpeg only). Use --use-gemini for smart detection (requires
|
|
|
37737
37966
|
}
|
|
37738
37967
|
spinner2.succeed(source_default.green("Silence detection complete"));
|
|
37739
37968
|
if (isJsonMode()) {
|
|
37740
|
-
|
|
37741
|
-
|
|
37742
|
-
|
|
37743
|
-
|
|
37744
|
-
|
|
37745
|
-
|
|
37746
|
-
|
|
37969
|
+
outputSuccess({
|
|
37970
|
+
command: "edit silence-cut",
|
|
37971
|
+
startedAt,
|
|
37972
|
+
data: {
|
|
37973
|
+
method: result.method,
|
|
37974
|
+
totalDuration: result.totalDuration,
|
|
37975
|
+
silentPeriods: result.silentPeriods,
|
|
37976
|
+
silentDuration: result.silentDuration,
|
|
37977
|
+
outputPath: result.outputPath
|
|
37978
|
+
}
|
|
37747
37979
|
});
|
|
37748
37980
|
return;
|
|
37749
37981
|
}
|
|
@@ -37781,6 +38013,7 @@ Examples:
|
|
|
37781
38013
|
$ vibe ed cap video.mp4 --dry-run --json
|
|
37782
38014
|
|
|
37783
38015
|
Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoPath, options) => {
|
|
38016
|
+
const startedAt = Date.now();
|
|
37784
38017
|
try {
|
|
37785
38018
|
if (options.output) {
|
|
37786
38019
|
validateOutputPath(options.output);
|
|
@@ -37793,16 +38026,19 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
|
|
|
37793
38026
|
exitWithError(generalError("FFmpeg not found. Install with: brew install ffmpeg (macOS) or apt install ffmpeg (Linux). Run `vibe doctor` for details."));
|
|
37794
38027
|
}
|
|
37795
38028
|
if (options.dryRun) {
|
|
37796
|
-
|
|
37797
|
-
dryRun: true,
|
|
38029
|
+
outputSuccess({
|
|
37798
38030
|
command: "edit caption",
|
|
37799
|
-
|
|
37800
|
-
|
|
37801
|
-
|
|
37802
|
-
|
|
37803
|
-
|
|
37804
|
-
|
|
37805
|
-
|
|
38031
|
+
startedAt,
|
|
38032
|
+
dryRun: true,
|
|
38033
|
+
data: {
|
|
38034
|
+
params: {
|
|
38035
|
+
videoPath: absVideoPath,
|
|
38036
|
+
style: options.style,
|
|
38037
|
+
fontSize: options.fontSize ? parseInt(options.fontSize) : void 0,
|
|
38038
|
+
fontColor: options.color,
|
|
38039
|
+
language: options.language,
|
|
38040
|
+
position: options.position
|
|
38041
|
+
}
|
|
37806
38042
|
}
|
|
37807
38043
|
});
|
|
37808
38044
|
return;
|
|
@@ -37831,12 +38067,15 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
|
|
|
37831
38067
|
}
|
|
37832
38068
|
spinner2.succeed(source_default.green("Captions applied"));
|
|
37833
38069
|
if (isJsonMode()) {
|
|
37834
|
-
|
|
37835
|
-
|
|
37836
|
-
|
|
37837
|
-
|
|
37838
|
-
|
|
37839
|
-
|
|
38070
|
+
outputSuccess({
|
|
38071
|
+
command: "edit caption",
|
|
38072
|
+
startedAt,
|
|
38073
|
+
data: {
|
|
38074
|
+
segmentCount: result.segmentCount,
|
|
38075
|
+
style: options.style || "bold",
|
|
38076
|
+
outputPath: result.outputPath,
|
|
38077
|
+
srtPath: result.srtPath
|
|
38078
|
+
}
|
|
37840
38079
|
});
|
|
37841
38080
|
return;
|
|
37842
38081
|
}
|
|
@@ -37855,6 +38094,7 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
|
|
|
37855
38094
|
}
|
|
37856
38095
|
});
|
|
37857
38096
|
aiCommand.command("noise-reduce").description("Remove background noise from audio/video using FFmpeg (no API key needed)").argument("<input>", "Audio or video file path").option("-o, --output <path>", "Output file path (default: <name>-denoised.<ext>)").option("-s, --strength <level>", "Noise reduction strength: low, medium, high (default: medium)", "medium").option("-n, --noise-floor <dB>", "Custom noise floor in dB (overrides strength preset)").option("--dry-run", "Preview parameters without executing").action(async (inputPath, options) => {
|
|
38097
|
+
const startedAt = Date.now();
|
|
37858
38098
|
try {
|
|
37859
38099
|
if (options.output) {
|
|
37860
38100
|
validateOutputPath(options.output);
|
|
@@ -37867,13 +38107,16 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
|
|
|
37867
38107
|
exitWithError(generalError("FFmpeg not found. Install with: brew install ffmpeg (macOS) or apt install ffmpeg (Linux). Run `vibe doctor` for details."));
|
|
37868
38108
|
}
|
|
37869
38109
|
if (options.dryRun) {
|
|
37870
|
-
|
|
37871
|
-
dryRun: true,
|
|
38110
|
+
outputSuccess({
|
|
37872
38111
|
command: "edit noise-reduce",
|
|
37873
|
-
|
|
37874
|
-
|
|
37875
|
-
|
|
37876
|
-
|
|
38112
|
+
startedAt,
|
|
38113
|
+
dryRun: true,
|
|
38114
|
+
data: {
|
|
38115
|
+
params: {
|
|
38116
|
+
inputPath: absInputPath,
|
|
38117
|
+
strength: options.strength,
|
|
38118
|
+
noiseFloor: options.noiseFloor ? parseFloat(options.noiseFloor) : void 0
|
|
38119
|
+
}
|
|
37877
38120
|
}
|
|
37878
38121
|
});
|
|
37879
38122
|
return;
|
|
@@ -37894,11 +38137,14 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
|
|
|
37894
38137
|
}
|
|
37895
38138
|
spinner2.succeed(source_default.green("Noise reduction complete"));
|
|
37896
38139
|
if (isJsonMode()) {
|
|
37897
|
-
|
|
37898
|
-
|
|
37899
|
-
|
|
37900
|
-
|
|
37901
|
-
|
|
38140
|
+
outputSuccess({
|
|
38141
|
+
command: "edit noise-reduce",
|
|
38142
|
+
startedAt,
|
|
38143
|
+
data: {
|
|
38144
|
+
inputDuration: result.inputDuration,
|
|
38145
|
+
strength: options.strength || "medium",
|
|
38146
|
+
outputPath: result.outputPath
|
|
38147
|
+
}
|
|
37902
38148
|
});
|
|
37903
38149
|
return;
|
|
37904
38150
|
}
|
|
@@ -37914,6 +38160,7 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
|
|
|
37914
38160
|
}
|
|
37915
38161
|
});
|
|
37916
38162
|
aiCommand.command("fade").description("Apply fade in/out effects to video (FFmpeg only, no API key needed)").argument("<video>", "Video file path").option("-o, --output <path>", "Output file path (default: <name>-faded.<ext>)").option("--fade-in <seconds>", "Fade-in duration in seconds (default: 1)", "1").option("--fade-out <seconds>", "Fade-out duration in seconds (default: 1)", "1").option("--audio-only", "Apply fade to audio only (video stream copied)").option("--video-only", "Apply fade to video only (audio stream copied)").option("--dry-run", "Preview parameters without executing").action(async (videoPath, options) => {
|
|
38163
|
+
const startedAt = Date.now();
|
|
37917
38164
|
try {
|
|
37918
38165
|
if (options.output) {
|
|
37919
38166
|
validateOutputPath(options.output);
|
|
@@ -37926,15 +38173,18 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
|
|
|
37926
38173
|
exitWithError(generalError("FFmpeg not found. Install with: brew install ffmpeg (macOS) or apt install ffmpeg (Linux). Run `vibe doctor` for details."));
|
|
37927
38174
|
}
|
|
37928
38175
|
if (options.dryRun) {
|
|
37929
|
-
|
|
37930
|
-
dryRun: true,
|
|
38176
|
+
outputSuccess({
|
|
37931
38177
|
command: "edit fade",
|
|
37932
|
-
|
|
37933
|
-
|
|
37934
|
-
|
|
37935
|
-
|
|
37936
|
-
|
|
37937
|
-
|
|
38178
|
+
startedAt,
|
|
38179
|
+
dryRun: true,
|
|
38180
|
+
data: {
|
|
38181
|
+
params: {
|
|
38182
|
+
videoPath: absVideoPath,
|
|
38183
|
+
fadeIn: parseFloat(options.fadeIn),
|
|
38184
|
+
fadeOut: parseFloat(options.fadeOut),
|
|
38185
|
+
audioOnly: options.audioOnly || false,
|
|
38186
|
+
videoOnly: options.videoOnly || false
|
|
38187
|
+
}
|
|
37938
38188
|
}
|
|
37939
38189
|
});
|
|
37940
38190
|
return;
|
|
@@ -37957,12 +38207,15 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
|
|
|
37957
38207
|
}
|
|
37958
38208
|
spinner2.succeed(source_default.green("Fade effects applied"));
|
|
37959
38209
|
if (isJsonMode()) {
|
|
37960
|
-
|
|
37961
|
-
|
|
37962
|
-
|
|
37963
|
-
|
|
37964
|
-
|
|
37965
|
-
|
|
38210
|
+
outputSuccess({
|
|
38211
|
+
command: "edit fade",
|
|
38212
|
+
startedAt,
|
|
38213
|
+
data: {
|
|
38214
|
+
totalDuration: result.totalDuration,
|
|
38215
|
+
fadeInApplied: result.fadeInApplied,
|
|
38216
|
+
fadeOutApplied: result.fadeOutApplied,
|
|
38217
|
+
outputPath: result.outputPath
|
|
38218
|
+
}
|
|
37966
38219
|
});
|
|
37967
38220
|
return;
|
|
37968
38221
|
}
|
|
@@ -37979,6 +38232,7 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
|
|
|
37979
38232
|
}
|
|
37980
38233
|
});
|
|
37981
38234
|
aiCommand.command("translate-srt").description("Translate SRT subtitle file to another language (Claude/OpenAI)").argument("<srt>", "SRT file path").option("-t, --target <language>", "Target language (e.g., ko, es, fr, ja, zh)").option("-o, --output <path>", "Output file path (default: <name>-<target>.srt)").option("-p, --provider <provider>", "Translation provider: claude, openai (default: claude)", "claude").option("--source <language>", "Source language (auto-detected if omitted)").option("-k, --api-key <key>", "API key (or set ANTHROPIC_API_KEY / OPENAI_API_KEY env)").option("--dry-run", "Preview parameters without executing").action(async (srtPath, options) => {
|
|
38235
|
+
const startedAt = Date.now();
|
|
37982
38236
|
try {
|
|
37983
38237
|
if (options.output) {
|
|
37984
38238
|
validateOutputPath(options.output);
|
|
@@ -37991,14 +38245,17 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
|
|
|
37991
38245
|
exitWithError(notFoundError(absSrtPath));
|
|
37992
38246
|
}
|
|
37993
38247
|
if (options.dryRun) {
|
|
37994
|
-
|
|
37995
|
-
dryRun: true,
|
|
38248
|
+
outputSuccess({
|
|
37996
38249
|
command: "edit translate-srt",
|
|
37997
|
-
|
|
37998
|
-
|
|
37999
|
-
|
|
38000
|
-
|
|
38001
|
-
|
|
38250
|
+
startedAt,
|
|
38251
|
+
dryRun: true,
|
|
38252
|
+
data: {
|
|
38253
|
+
params: {
|
|
38254
|
+
srtPath: absSrtPath,
|
|
38255
|
+
targetLanguage: options.target,
|
|
38256
|
+
provider: options.provider || "claude",
|
|
38257
|
+
sourceLanguage: options.source
|
|
38258
|
+
}
|
|
38002
38259
|
}
|
|
38003
38260
|
});
|
|
38004
38261
|
return;
|
|
@@ -38028,12 +38285,15 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
|
|
|
38028
38285
|
}
|
|
38029
38286
|
spinner2.succeed(source_default.green("Translation complete"));
|
|
38030
38287
|
if (isJsonMode()) {
|
|
38031
|
-
|
|
38032
|
-
|
|
38033
|
-
|
|
38034
|
-
|
|
38035
|
-
|
|
38036
|
-
|
|
38288
|
+
outputSuccess({
|
|
38289
|
+
command: "edit translate-srt",
|
|
38290
|
+
startedAt,
|
|
38291
|
+
data: {
|
|
38292
|
+
segmentCount: result.segmentCount,
|
|
38293
|
+
sourceLanguage: result.sourceLanguage,
|
|
38294
|
+
targetLanguage: result.targetLanguage,
|
|
38295
|
+
outputPath: result.outputPath
|
|
38296
|
+
}
|
|
38037
38297
|
});
|
|
38038
38298
|
return;
|
|
38039
38299
|
}
|
|
@@ -38050,6 +38310,7 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
|
|
|
38050
38310
|
}
|
|
38051
38311
|
});
|
|
38052
38312
|
aiCommand.command("jump-cut").description("Remove filler words (um, uh, like, etc.) from video using Whisper word-level timestamps").argument("<video>", "Video file path").option("-o, --output <path>", "Output file path (default: <name>-jumpcut.<ext>)").option("--fillers <words>", "Comma-separated filler words to detect").option("--padding <seconds>", "Padding around cuts in seconds (default: 0.05)", "0.05").option("-l, --language <lang>", "Language code for transcription (e.g., en, ko)").option("--analyze-only", "Only detect fillers, don't cut").option("-k, --api-key <key>", "OpenAI API key (or set OPENAI_API_KEY env)").option("--dry-run", "Preview parameters without executing").action(async (videoPath, options) => {
|
|
38313
|
+
const startedAt = Date.now();
|
|
38053
38314
|
try {
|
|
38054
38315
|
if (options.output) {
|
|
38055
38316
|
validateOutputPath(options.output);
|
|
@@ -38064,15 +38325,18 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
|
|
|
38064
38325
|
}
|
|
38065
38326
|
if (options.dryRun) {
|
|
38066
38327
|
const fillers2 = options.fillers ? options.fillers.split(",").map((f) => f.trim()) : void 0;
|
|
38067
|
-
|
|
38068
|
-
dryRun: true,
|
|
38328
|
+
outputSuccess({
|
|
38069
38329
|
command: "edit jump-cut",
|
|
38070
|
-
|
|
38071
|
-
|
|
38072
|
-
|
|
38073
|
-
|
|
38074
|
-
|
|
38075
|
-
|
|
38330
|
+
startedAt,
|
|
38331
|
+
dryRun: true,
|
|
38332
|
+
data: {
|
|
38333
|
+
params: {
|
|
38334
|
+
videoPath: absVideoPath,
|
|
38335
|
+
fillers: fillers2,
|
|
38336
|
+
padding: parseFloat(options.padding),
|
|
38337
|
+
language: options.language,
|
|
38338
|
+
analyzeOnly: options.analyzeOnly || false
|
|
38339
|
+
}
|
|
38076
38340
|
}
|
|
38077
38341
|
});
|
|
38078
38342
|
return;
|
|
@@ -38101,13 +38365,16 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
|
|
|
38101
38365
|
}
|
|
38102
38366
|
spinner2.succeed(source_default.green("Filler detection complete"));
|
|
38103
38367
|
if (isJsonMode()) {
|
|
38104
|
-
|
|
38105
|
-
|
|
38106
|
-
|
|
38107
|
-
|
|
38108
|
-
|
|
38109
|
-
|
|
38110
|
-
|
|
38368
|
+
outputSuccess({
|
|
38369
|
+
command: "edit jump-cut",
|
|
38370
|
+
startedAt,
|
|
38371
|
+
data: {
|
|
38372
|
+
totalDuration: result.totalDuration,
|
|
38373
|
+
fillerCount: result.fillerCount,
|
|
38374
|
+
fillerDuration: result.fillerDuration,
|
|
38375
|
+
fillers: result.fillers,
|
|
38376
|
+
outputPath: result.outputPath
|
|
38377
|
+
}
|
|
38111
38378
|
});
|
|
38112
38379
|
return;
|
|
38113
38380
|
}
|
|
@@ -38909,6 +39176,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
38909
39176
|
registerEditCommands(editCommand);
|
|
38910
39177
|
registerFillGapsCommand(editCommand);
|
|
38911
39178
|
editCommand.command("grade").description("Apply AI-generated color grading (Claude + FFmpeg)").argument("<video>", "Video file path").option("-s, --style <prompt>", "Style description (e.g., 'cinematic warm')").option("--preset <name>", "Built-in preset: film-noir, vintage, cinematic-warm, cool-tones, high-contrast, pastel, cyberpunk, horror").option("-o, --output <path>", "Output video file path").option("--analyze-only", "Show filter without applying").option("-k, --api-key <key>", "Anthropic API key (or set ANTHROPIC_API_KEY env)").option("--dry-run", "Preview parameters without executing").action(async (videoPath, options) => {
|
|
39179
|
+
const startedAt = Date.now();
|
|
38912
39180
|
try {
|
|
38913
39181
|
if (options.style) rejectControlChars(options.style);
|
|
38914
39182
|
if (options.output) {
|
|
@@ -38924,13 +39192,16 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
38924
39192
|
exitWithError(notFoundError("FFmpeg not found. Install with: brew install ffmpeg"));
|
|
38925
39193
|
}
|
|
38926
39194
|
if (options.dryRun) {
|
|
38927
|
-
|
|
38928
|
-
dryRun: true,
|
|
39195
|
+
outputSuccess({
|
|
38929
39196
|
command: "edit grade",
|
|
38930
|
-
|
|
38931
|
-
|
|
38932
|
-
|
|
38933
|
-
|
|
39197
|
+
startedAt,
|
|
39198
|
+
dryRun: true,
|
|
39199
|
+
data: {
|
|
39200
|
+
params: {
|
|
39201
|
+
videoPath: resolve33(process.cwd(), videoPath),
|
|
39202
|
+
style: options.style || options.preset,
|
|
39203
|
+
analyzeOnly: options.analyzeOnly || false
|
|
39204
|
+
}
|
|
38934
39205
|
}
|
|
38935
39206
|
});
|
|
38936
39207
|
return;
|
|
@@ -38956,12 +39227,15 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
38956
39227
|
if (isJsonMode()) {
|
|
38957
39228
|
const absPath2 = resolve33(process.cwd(), videoPath);
|
|
38958
39229
|
const gradeOutputPath = options.output ? resolve33(process.cwd(), options.output) : absPath2.replace(/(\.[^.]+)$/, "-graded$1");
|
|
38959
|
-
|
|
38960
|
-
|
|
38961
|
-
|
|
38962
|
-
|
|
38963
|
-
|
|
38964
|
-
|
|
39230
|
+
outputSuccess({
|
|
39231
|
+
command: "edit grade",
|
|
39232
|
+
startedAt,
|
|
39233
|
+
data: {
|
|
39234
|
+
style: options.preset || options.style,
|
|
39235
|
+
description: gradeResult.description,
|
|
39236
|
+
ffmpegFilter: gradeResult.ffmpegFilter,
|
|
39237
|
+
outputPath: options.analyzeOnly ? void 0 : gradeOutputPath
|
|
39238
|
+
}
|
|
38965
39239
|
});
|
|
38966
39240
|
return;
|
|
38967
39241
|
}
|
|
@@ -38990,6 +39264,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
38990
39264
|
}
|
|
38991
39265
|
});
|
|
38992
39266
|
editCommand.command("text-overlay").description("Apply text overlays to video (FFmpeg drawtext)").argument("<video>", "Video file path").option("-t, --text <texts...>", "Text lines to overlay (repeat for multiple)").option("-s, --style <style>", "Overlay style: lower-third, center-bold, subtitle, minimal", "lower-third").option("--font-size <size>", "Font size in pixels (auto-calculated if omitted)").option("--font-color <color>", "Font color (default: white)", "white").option("--fade <seconds>", "Fade in/out duration in seconds", "0.3").option("--start <seconds>", "Start time in seconds", "0").option("--end <seconds>", "End time in seconds (default: video duration)").option("-o, --output <path>", "Output video file path").option("--dry-run", "Preview parameters without executing").action(async (videoPath, options) => {
|
|
39267
|
+
const startedAt = Date.now();
|
|
38993
39268
|
try {
|
|
38994
39269
|
if (!options.text || options.text.length === 0) {
|
|
38995
39270
|
exitWithError(usageError("At least one --text option is required", 'Example: vibe edit text-overlay video.mp4 -t "NEXUS AI" --style center-bold'));
|
|
@@ -39002,18 +39277,21 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
39002
39277
|
exitWithError(generalError("FFmpeg not found", "Install with: brew install ffmpeg (macOS) or apt install ffmpeg (Linux)"));
|
|
39003
39278
|
}
|
|
39004
39279
|
if (options.dryRun) {
|
|
39005
|
-
|
|
39006
|
-
dryRun: true,
|
|
39280
|
+
outputSuccess({
|
|
39007
39281
|
command: "edit text-overlay",
|
|
39008
|
-
|
|
39009
|
-
|
|
39010
|
-
|
|
39011
|
-
|
|
39012
|
-
|
|
39013
|
-
|
|
39014
|
-
|
|
39015
|
-
|
|
39016
|
-
|
|
39282
|
+
startedAt,
|
|
39283
|
+
dryRun: true,
|
|
39284
|
+
data: {
|
|
39285
|
+
params: {
|
|
39286
|
+
videoPath: resolve33(process.cwd(), videoPath),
|
|
39287
|
+
texts: options.text,
|
|
39288
|
+
style: options.style,
|
|
39289
|
+
fontSize: options.fontSize ? parseInt(options.fontSize) : void 0,
|
|
39290
|
+
fontColor: options.fontColor,
|
|
39291
|
+
fade: parseFloat(options.fade),
|
|
39292
|
+
start: parseFloat(options.start),
|
|
39293
|
+
end: options.end ? parseFloat(options.end) : void 0
|
|
39294
|
+
}
|
|
39017
39295
|
}
|
|
39018
39296
|
});
|
|
39019
39297
|
return;
|
|
@@ -39038,11 +39316,14 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
39038
39316
|
}
|
|
39039
39317
|
spinner2.succeed(source_default.green("Text overlays applied"));
|
|
39040
39318
|
if (isJsonMode()) {
|
|
39041
|
-
|
|
39042
|
-
|
|
39043
|
-
|
|
39044
|
-
|
|
39045
|
-
|
|
39319
|
+
outputSuccess({
|
|
39320
|
+
command: "edit text-overlay",
|
|
39321
|
+
startedAt,
|
|
39322
|
+
data: {
|
|
39323
|
+
style: options.style,
|
|
39324
|
+
texts: options.text,
|
|
39325
|
+
outputPath: result.outputPath
|
|
39326
|
+
}
|
|
39046
39327
|
});
|
|
39047
39328
|
return;
|
|
39048
39329
|
}
|
|
@@ -39059,6 +39340,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
39059
39340
|
}
|
|
39060
39341
|
});
|
|
39061
39342
|
editCommand.command("speed-ramp").description("Apply content-aware speed ramping (Whisper + Claude + FFmpeg)").argument("<video>", "Video file path").option("-o, --output <path>", "Output video file path").option("-s, --style <style>", "Style: dramatic, smooth, action", "dramatic").option("--min-speed <factor>", "Minimum speed factor", "0.25").option("--max-speed <factor>", "Maximum speed factor", "4.0").option("--analyze-only", "Show keyframes without applying").option("-l, --language <lang>", "Language code for transcription").option("-k, --api-key <key>", "Anthropic API key (or set ANTHROPIC_API_KEY env)").option("--dry-run", "Preview parameters without executing").action(async (videoPath, options) => {
|
|
39343
|
+
const startedAt = Date.now();
|
|
39062
39344
|
try {
|
|
39063
39345
|
if (options.output) {
|
|
39064
39346
|
validateOutputPath(options.output);
|
|
@@ -39067,15 +39349,18 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
39067
39349
|
exitWithError(generalError("FFmpeg not found", "Install with: brew install ffmpeg (macOS) or apt install ffmpeg (Linux)"));
|
|
39068
39350
|
}
|
|
39069
39351
|
if (options.dryRun) {
|
|
39070
|
-
|
|
39071
|
-
dryRun: true,
|
|
39352
|
+
outputSuccess({
|
|
39072
39353
|
command: "edit speed-ramp",
|
|
39073
|
-
|
|
39074
|
-
|
|
39075
|
-
|
|
39076
|
-
|
|
39077
|
-
|
|
39078
|
-
|
|
39354
|
+
startedAt,
|
|
39355
|
+
dryRun: true,
|
|
39356
|
+
data: {
|
|
39357
|
+
params: {
|
|
39358
|
+
videoPath: resolve33(process.cwd(), videoPath),
|
|
39359
|
+
style: options.style,
|
|
39360
|
+
minSpeed: parseFloat(options.minSpeed),
|
|
39361
|
+
maxSpeed: parseFloat(options.maxSpeed),
|
|
39362
|
+
analyzeOnly: options.analyzeOnly || false
|
|
39363
|
+
}
|
|
39079
39364
|
}
|
|
39080
39365
|
});
|
|
39081
39366
|
return;
|
|
@@ -39128,11 +39413,14 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
39128
39413
|
if (isJsonMode()) {
|
|
39129
39414
|
const avgSpeed2 = speedResult.keyframes.reduce((sum, kf) => sum + kf.speed, 0) / speedResult.keyframes.length;
|
|
39130
39415
|
const speedRampOutputPath = options.output ? resolve33(process.cwd(), options.output) : absPath.replace(/(\.[^.]+)$/, "-ramped$1");
|
|
39131
|
-
|
|
39132
|
-
|
|
39133
|
-
|
|
39134
|
-
|
|
39135
|
-
|
|
39416
|
+
outputSuccess({
|
|
39417
|
+
command: "edit speed-ramp",
|
|
39418
|
+
startedAt,
|
|
39419
|
+
data: {
|
|
39420
|
+
keyframes: speedResult.keyframes,
|
|
39421
|
+
avgSpeed: avgSpeed2,
|
|
39422
|
+
outputPath: options.analyzeOnly ? void 0 : speedRampOutputPath
|
|
39423
|
+
}
|
|
39136
39424
|
});
|
|
39137
39425
|
return;
|
|
39138
39426
|
}
|
|
@@ -39172,6 +39460,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
39172
39460
|
}
|
|
39173
39461
|
});
|
|
39174
39462
|
editCommand.command("reframe").description("Auto-reframe video to different aspect ratio (Claude Vision + FFmpeg)").argument("<video>", "Video file path").option("-a, --aspect <ratio>", "Target aspect ratio: 9:16, 1:1, 4:5", "9:16").option("-f, --focus <mode>", "Focus mode: auto, face, center, action", "auto").option("-o, --output <path>", "Output video file path").option("--analyze-only", "Show crop regions without applying").option("--keyframes <path>", "Export keyframes to JSON file").option("-k, --api-key <key>", "Anthropic API key (or set ANTHROPIC_API_KEY env)").option("--dry-run", "Preview parameters without executing").action(async (videoPath, options) => {
|
|
39463
|
+
const startedAt = Date.now();
|
|
39175
39464
|
try {
|
|
39176
39465
|
if (options.output) {
|
|
39177
39466
|
validateOutputPath(options.output);
|
|
@@ -39180,14 +39469,17 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
39180
39469
|
exitWithError(generalError("FFmpeg not found", "Install with: brew install ffmpeg (macOS) or apt install ffmpeg (Linux)"));
|
|
39181
39470
|
}
|
|
39182
39471
|
if (options.dryRun) {
|
|
39183
|
-
|
|
39184
|
-
dryRun: true,
|
|
39472
|
+
outputSuccess({
|
|
39185
39473
|
command: "edit reframe",
|
|
39186
|
-
|
|
39187
|
-
|
|
39188
|
-
|
|
39189
|
-
|
|
39190
|
-
|
|
39474
|
+
startedAt,
|
|
39475
|
+
dryRun: true,
|
|
39476
|
+
data: {
|
|
39477
|
+
params: {
|
|
39478
|
+
videoPath: resolve33(process.cwd(), videoPath),
|
|
39479
|
+
aspect: options.aspect,
|
|
39480
|
+
focus: options.focus,
|
|
39481
|
+
analyzeOnly: options.analyzeOnly || false
|
|
39482
|
+
}
|
|
39191
39483
|
}
|
|
39192
39484
|
});
|
|
39193
39485
|
return;
|
|
@@ -39255,13 +39547,16 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
39255
39547
|
spinner2.succeed(source_default.green(`Analyzed ${cropKeyframes.length} keyframes`));
|
|
39256
39548
|
if (isJsonMode()) {
|
|
39257
39549
|
const reframeOutputPath = options.output ? resolve33(process.cwd(), options.output) : absPath.replace(/(\.[^.]+)$/, `-${options.aspect.replace(":", "x")}$1`);
|
|
39258
|
-
|
|
39259
|
-
|
|
39260
|
-
|
|
39261
|
-
|
|
39262
|
-
|
|
39263
|
-
|
|
39264
|
-
|
|
39550
|
+
outputSuccess({
|
|
39551
|
+
command: "edit reframe",
|
|
39552
|
+
startedAt,
|
|
39553
|
+
data: {
|
|
39554
|
+
sourceWidth,
|
|
39555
|
+
sourceHeight,
|
|
39556
|
+
aspect: options.aspect,
|
|
39557
|
+
cropKeyframes,
|
|
39558
|
+
outputPath: options.analyzeOnly ? void 0 : reframeOutputPath
|
|
39559
|
+
}
|
|
39265
39560
|
});
|
|
39266
39561
|
return;
|
|
39267
39562
|
}
|
|
@@ -39312,6 +39607,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
39312
39607
|
}
|
|
39313
39608
|
});
|
|
39314
39609
|
editCommand.command("image").description("Edit image(s) using AI (Gemini/OpenAI/Grok)").argument("<images...>", "Input image file(s) followed by edit prompt").option("-p, --provider <provider>", "Provider: gemini (default), openai, grok", "gemini").option("-k, --api-key <key>", "API key (or set env variable)").option("-o, --output <path>", "Output file path", "edited.png").option("-m, --model <model>", "Model: flash/3.1-flash/latest/pro (Gemini only)", "flash").option("-r, --ratio <ratio>", "Output aspect ratio").option("-s, --size <resolution>", "Resolution: 1K, 2K, 4K (Gemini Pro only)").option("--dry-run", "Preview parameters without executing").action(async (args, options) => {
|
|
39610
|
+
const startedAt = Date.now();
|
|
39315
39611
|
try {
|
|
39316
39612
|
if (args.length < 2) {
|
|
39317
39613
|
exitWithError(usageError("Need at least one image and a prompt"));
|
|
@@ -39327,16 +39623,19 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
39327
39623
|
exitWithError(usageError("Grok supports only 1 input image for editing.", "Use -p gemini (up to 14 images) or -p openai (up to 16 images) for multi-image editing."));
|
|
39328
39624
|
}
|
|
39329
39625
|
if (options.dryRun) {
|
|
39330
|
-
|
|
39331
|
-
dryRun: true,
|
|
39626
|
+
outputSuccess({
|
|
39332
39627
|
command: "edit image",
|
|
39333
|
-
|
|
39334
|
-
|
|
39335
|
-
|
|
39336
|
-
|
|
39337
|
-
|
|
39338
|
-
|
|
39339
|
-
|
|
39628
|
+
startedAt,
|
|
39629
|
+
dryRun: true,
|
|
39630
|
+
data: {
|
|
39631
|
+
params: {
|
|
39632
|
+
imagePaths: imagePaths.map((p) => resolve33(process.cwd(), p)),
|
|
39633
|
+
prompt: prompt3,
|
|
39634
|
+
provider,
|
|
39635
|
+
model: options.model,
|
|
39636
|
+
ratio: options.ratio,
|
|
39637
|
+
size: options.size
|
|
39638
|
+
}
|
|
39340
39639
|
}
|
|
39341
39640
|
});
|
|
39342
39641
|
return;
|
|
@@ -39414,11 +39713,14 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
39414
39713
|
};
|
|
39415
39714
|
const resultModel = result.model;
|
|
39416
39715
|
if (isJsonMode()) {
|
|
39417
|
-
|
|
39418
|
-
|
|
39419
|
-
|
|
39420
|
-
|
|
39421
|
-
|
|
39716
|
+
outputSuccess({
|
|
39717
|
+
command: "edit image",
|
|
39718
|
+
startedAt,
|
|
39719
|
+
data: {
|
|
39720
|
+
provider,
|
|
39721
|
+
model: resultModel || options.model,
|
|
39722
|
+
outputPath
|
|
39723
|
+
}
|
|
39422
39724
|
});
|
|
39423
39725
|
await saveImage();
|
|
39424
39726
|
return;
|
|
@@ -39434,6 +39736,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
39434
39736
|
}
|
|
39435
39737
|
});
|
|
39436
39738
|
editCommand.command("interpolate").description("Create slow motion with frame interpolation (FFmpeg)").argument("<video>", "Video file path").option("-o, --output <path>", "Output file path").option("-f, --factor <number>", "Slow motion factor: 2, 4, or 8", "2").option("--fps <number>", "Target output FPS").option("--quality <mode>", "Quality: fast or quality", "quality").option("--dry-run", "Preview parameters without executing").action(async (videoPath, options) => {
|
|
39739
|
+
const startedAt = Date.now();
|
|
39437
39740
|
try {
|
|
39438
39741
|
if (options.output) {
|
|
39439
39742
|
validateOutputPath(options.output);
|
|
@@ -39444,14 +39747,17 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
39444
39747
|
exitWithError(usageError("Factor must be 2, 4, or 8"));
|
|
39445
39748
|
}
|
|
39446
39749
|
if (options.dryRun) {
|
|
39447
|
-
|
|
39448
|
-
dryRun: true,
|
|
39750
|
+
outputSuccess({
|
|
39449
39751
|
command: "edit interpolate",
|
|
39450
|
-
|
|
39451
|
-
|
|
39452
|
-
|
|
39453
|
-
|
|
39454
|
-
|
|
39752
|
+
startedAt,
|
|
39753
|
+
dryRun: true,
|
|
39754
|
+
data: {
|
|
39755
|
+
params: {
|
|
39756
|
+
videoPath: absPath,
|
|
39757
|
+
factor,
|
|
39758
|
+
fps: options.fps ? parseInt(options.fps) : void 0,
|
|
39759
|
+
quality: options.quality
|
|
39760
|
+
}
|
|
39455
39761
|
}
|
|
39456
39762
|
});
|
|
39457
39763
|
return;
|
|
@@ -39478,12 +39784,15 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
39478
39784
|
await execSafe("ffmpeg", ["-i", absPath, "-filter:v", `minterpolate='${mi}:fps=${targetFps}',setpts=${factor}*PTS`, "-an", outputPath, "-y"], { timeout: 6e5 });
|
|
39479
39785
|
spinner2.succeed(source_default.green(`Created ${factor}x slow motion`));
|
|
39480
39786
|
if (isJsonMode()) {
|
|
39481
|
-
|
|
39482
|
-
|
|
39483
|
-
|
|
39484
|
-
|
|
39485
|
-
|
|
39486
|
-
|
|
39787
|
+
outputSuccess({
|
|
39788
|
+
command: "edit interpolate",
|
|
39789
|
+
startedAt,
|
|
39790
|
+
data: {
|
|
39791
|
+
originalFps,
|
|
39792
|
+
targetFps,
|
|
39793
|
+
factor,
|
|
39794
|
+
outputPath
|
|
39795
|
+
}
|
|
39487
39796
|
});
|
|
39488
39797
|
return;
|
|
39489
39798
|
}
|
|
@@ -39509,6 +39818,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
39509
39818
|
}
|
|
39510
39819
|
});
|
|
39511
39820
|
editCommand.command("upscale-video").description("Upscale video resolution using AI or FFmpeg").argument("<video>", "Video file path").option("-o, --output <path>", "Output file path").option("-s, --scale <factor>", "Scale factor: 2 or 4", "2").option("-m, --model <model>", "Model: real-esrgan, topaz", "real-esrgan").option("--ffmpeg", "Use FFmpeg lanczos (free, no API)").option("-k, --api-key <key>", "Replicate API token (or set REPLICATE_API_TOKEN env)").option("--no-wait", "Start processing and return task ID without waiting").option("--dry-run", "Preview parameters without executing").action(async (videoPath, options) => {
|
|
39821
|
+
const startedAt = Date.now();
|
|
39512
39822
|
try {
|
|
39513
39823
|
if (options.output) {
|
|
39514
39824
|
validateOutputPath(options.output);
|
|
@@ -39519,14 +39829,17 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
39519
39829
|
exitWithError(usageError("Scale must be 2 or 4"));
|
|
39520
39830
|
}
|
|
39521
39831
|
if (options.dryRun) {
|
|
39522
|
-
|
|
39523
|
-
dryRun: true,
|
|
39832
|
+
outputSuccess({
|
|
39524
39833
|
command: "edit upscale-video",
|
|
39525
|
-
|
|
39526
|
-
|
|
39527
|
-
|
|
39528
|
-
|
|
39529
|
-
|
|
39834
|
+
startedAt,
|
|
39835
|
+
dryRun: true,
|
|
39836
|
+
data: {
|
|
39837
|
+
params: {
|
|
39838
|
+
videoPath: absPath,
|
|
39839
|
+
scale,
|
|
39840
|
+
model: options.model,
|
|
39841
|
+
ffmpeg: options.ffmpeg || false
|
|
39842
|
+
}
|
|
39530
39843
|
}
|
|
39531
39844
|
});
|
|
39532
39845
|
return;
|
|
@@ -39552,10 +39865,13 @@ Run 'vibe schema edit.<command>' for structured parameter info.
|
|
|
39552
39865
|
await execSafe("ffmpeg", ["-i", absPath, "-vf", `scale=${newWidth}:${newHeight}:flags=lanczos`, "-c:a", "copy", outputPath, "-y"]);
|
|
39553
39866
|
spinner3.succeed(source_default.green(`Upscaled to ${newWidth}x${newHeight}`));
|
|
39554
39867
|
if (isJsonMode()) {
|
|
39555
|
-
|
|
39556
|
-
|
|
39557
|
-
|
|
39558
|
-
|
|
39868
|
+
outputSuccess({
|
|
39869
|
+
command: "edit upscale-video",
|
|
39870
|
+
startedAt,
|
|
39871
|
+
data: {
|
|
39872
|
+
dimensions: `${newWidth}x${newHeight}`,
|
|
39873
|
+
outputPath
|
|
39874
|
+
}
|
|
39559
39875
|
});
|
|
39560
39876
|
return;
|
|
39561
39877
|
}
|
|
@@ -39995,21 +40311,25 @@ Score each category 1-10. For fixable issues, provide an FFmpeg filter in autoFi
|
|
|
39995
40311
|
}
|
|
39996
40312
|
function registerReviewCommand(aiCommand) {
|
|
39997
40313
|
aiCommand.command("review").description("Review video quality using Gemini AI and optionally auto-fix issues").argument("<video>", "Video file path").option("-s, --storyboard <path>", "Storyboard JSON file for context").option("--auto-apply", "Automatically apply fixable corrections").option("--verify", "Run verification pass after applying fixes").option("-m, --model <model>", "Gemini model: flash (default), flash-2.5, pro", "flash").option("-o, --output <path>", "Output video file path (for auto-apply)").option("--dry-run", "Preview parameters without executing").action(async (videoPath, options) => {
|
|
40314
|
+
const startedAt = Date.now();
|
|
39998
40315
|
try {
|
|
39999
40316
|
if (options.output) {
|
|
40000
40317
|
validateOutputPath(options.output);
|
|
40001
40318
|
}
|
|
40002
40319
|
if (options.dryRun) {
|
|
40003
|
-
|
|
40004
|
-
dryRun: true,
|
|
40320
|
+
outputSuccess({
|
|
40005
40321
|
command: "ai review",
|
|
40006
|
-
|
|
40007
|
-
|
|
40008
|
-
|
|
40009
|
-
|
|
40010
|
-
|
|
40011
|
-
|
|
40012
|
-
|
|
40322
|
+
startedAt,
|
|
40323
|
+
dryRun: true,
|
|
40324
|
+
data: {
|
|
40325
|
+
params: {
|
|
40326
|
+
videoPath,
|
|
40327
|
+
storyboard: options.storyboard,
|
|
40328
|
+
autoApply: options.autoApply ?? false,
|
|
40329
|
+
verify: options.verify ?? false,
|
|
40330
|
+
model: options.model,
|
|
40331
|
+
output: options.output
|
|
40332
|
+
}
|
|
40013
40333
|
}
|
|
40014
40334
|
});
|
|
40015
40335
|
return;
|
|
@@ -40543,6 +40863,7 @@ Analyze both VISUALS (expressions, actions, scene changes) and AUDIO (speech, re
|
|
|
40543
40863
|
}
|
|
40544
40864
|
function registerHighlightsCommands(aiCommand) {
|
|
40545
40865
|
aiCommand.command("highlights").description("Extract highlights from long-form video/audio content").argument("<media>", "Video or audio file path").option("-o, --output <path>", "Output JSON file with highlights").option("--project <path>", "Create project with highlight clips").option("-d, --duration <seconds>", "Target highlight reel duration", "60").option("-n, --count <number>", "Maximum number of highlights").option("-t, --threshold <value>", "Confidence threshold (0-1)", "0.7").option("--criteria <type>", "Selection criteria: emotional | informative | funny | all", "all").option("-l, --language <lang>", "Language code for transcription (e.g., en, ko)").option("--use-gemini", "Use Gemini Video Understanding for enhanced visual+audio analysis").option("--low-res", "Use low resolution mode for longer videos (Gemini only)").option("--dry-run", "Preview parameters without executing").action(async (mediaPath, options) => {
|
|
40866
|
+
const startedAt = Date.now();
|
|
40546
40867
|
try {
|
|
40547
40868
|
if (options.output) {
|
|
40548
40869
|
validateOutputPath(options.output);
|
|
@@ -40552,20 +40873,23 @@ function registerHighlightsCommands(aiCommand) {
|
|
|
40552
40873
|
exitWithError(notFoundError(absPath));
|
|
40553
40874
|
}
|
|
40554
40875
|
if (options.dryRun) {
|
|
40555
|
-
|
|
40556
|
-
dryRun: true,
|
|
40876
|
+
outputSuccess({
|
|
40557
40877
|
command: "ai highlights",
|
|
40558
|
-
|
|
40559
|
-
|
|
40560
|
-
|
|
40561
|
-
|
|
40562
|
-
|
|
40563
|
-
|
|
40564
|
-
|
|
40565
|
-
|
|
40566
|
-
|
|
40567
|
-
|
|
40568
|
-
|
|
40878
|
+
startedAt,
|
|
40879
|
+
dryRun: true,
|
|
40880
|
+
data: {
|
|
40881
|
+
params: {
|
|
40882
|
+
mediaPath,
|
|
40883
|
+
output: options.output,
|
|
40884
|
+
project: options.project,
|
|
40885
|
+
duration: options.duration,
|
|
40886
|
+
count: options.count,
|
|
40887
|
+
threshold: options.threshold,
|
|
40888
|
+
criteria: options.criteria,
|
|
40889
|
+
language: options.language,
|
|
40890
|
+
useGemini: options.useGemini ?? false,
|
|
40891
|
+
lowRes: options.lowRes ?? false
|
|
40892
|
+
}
|
|
40569
40893
|
}
|
|
40570
40894
|
});
|
|
40571
40895
|
return;
|
|
@@ -40758,13 +41082,13 @@ Analyze both what is SHOWN (visual cues, actions, expressions) and what is SAID
|
|
|
40758
41082
|
});
|
|
40759
41083
|
if (allHighlights.length === 0) {
|
|
40760
41084
|
analyzeSpinner.warn(source_default.yellow("No highlights detected in the content"));
|
|
40761
|
-
|
|
41085
|
+
return;
|
|
40762
41086
|
}
|
|
40763
41087
|
analyzeSpinner.succeed(source_default.green(`Found ${allHighlights.length} potential highlights`));
|
|
40764
41088
|
}
|
|
40765
41089
|
if (allHighlights.length === 0) {
|
|
40766
41090
|
console.log(source_default.yellow("No highlights detected in the content"));
|
|
40767
|
-
|
|
41091
|
+
return;
|
|
40768
41092
|
}
|
|
40769
41093
|
const filterSpinner = ora("\u{1F4CA} Filtering and ranking...").start();
|
|
40770
41094
|
const threshold = parseFloat(options.threshold);
|
|
@@ -40840,6 +41164,7 @@ Analyze both what is SHOWN (visual cues, actions, expressions) and what is SAID
|
|
|
40840
41164
|
}
|
|
40841
41165
|
});
|
|
40842
41166
|
aiCommand.command("auto-shorts").alias("shorts").description("Auto-generate shorts from long-form video").argument("<video>", "Video file path").option("-o, --output <path>", "Output file (single) or directory (multiple)").option("-d, --duration <seconds>", "Target duration in seconds (15-60)", "60").option("-n, --count <number>", "Number of shorts to generate", "1").option("-a, --aspect <ratio>", "Aspect ratio: 9:16, 1:1", "9:16").option("--output-dir <dir>", "Output directory for multiple shorts").option("--add-captions", "Add auto-generated captions").option("--caption-style <style>", "Caption style: minimal, bold, animated", "bold").option("--analyze-only", "Show segments without generating").option("-l, --language <lang>", "Language code for transcription").option("--use-gemini", "Use Gemini Video Understanding for enhanced visual+audio analysis").option("--low-res", "Use low resolution mode for longer videos (Gemini only)").option("--dry-run", "Preview parameters without executing").action(async (videoPath, options) => {
|
|
41167
|
+
const startedAt = Date.now();
|
|
40843
41168
|
try {
|
|
40844
41169
|
if (options.output) {
|
|
40845
41170
|
validateOutputPath(options.output);
|
|
@@ -40849,22 +41174,25 @@ Analyze both what is SHOWN (visual cues, actions, expressions) and what is SAID
|
|
|
40849
41174
|
exitWithError(notFoundError(absPath));
|
|
40850
41175
|
}
|
|
40851
41176
|
if (options.dryRun) {
|
|
40852
|
-
|
|
40853
|
-
dryRun: true,
|
|
41177
|
+
outputSuccess({
|
|
40854
41178
|
command: "ai auto-shorts",
|
|
40855
|
-
|
|
40856
|
-
|
|
40857
|
-
|
|
40858
|
-
|
|
40859
|
-
|
|
40860
|
-
|
|
40861
|
-
|
|
40862
|
-
|
|
40863
|
-
|
|
40864
|
-
|
|
40865
|
-
|
|
40866
|
-
|
|
40867
|
-
|
|
41179
|
+
startedAt,
|
|
41180
|
+
dryRun: true,
|
|
41181
|
+
data: {
|
|
41182
|
+
params: {
|
|
41183
|
+
videoPath,
|
|
41184
|
+
output: options.output,
|
|
41185
|
+
duration: options.duration,
|
|
41186
|
+
count: options.count,
|
|
41187
|
+
aspect: options.aspect,
|
|
41188
|
+
outputDir: options.outputDir,
|
|
41189
|
+
addCaptions: options.addCaptions ?? false,
|
|
41190
|
+
captionStyle: options.captionStyle,
|
|
41191
|
+
analyzeOnly: options.analyzeOnly ?? false,
|
|
41192
|
+
language: options.language,
|
|
41193
|
+
useGemini: options.useGemini ?? false,
|
|
41194
|
+
lowRes: options.lowRes ?? false
|
|
41195
|
+
}
|
|
40868
41196
|
}
|
|
40869
41197
|
});
|
|
40870
41198
|
return;
|
|
@@ -51752,13 +52080,13 @@ function getPresetSettings(preset, aspectRatio) {
|
|
|
51752
52080
|
}
|
|
51753
52081
|
return settings;
|
|
51754
52082
|
}
|
|
51755
|
-
async function runHyperframesExport(projectPath, options, spinner2) {
|
|
52083
|
+
async function runHyperframesExport(projectPath, options, spinner2, startedAt) {
|
|
51756
52084
|
spinner2.text = "Loading project...";
|
|
51757
52085
|
const { readFile: readFile42 } = await import("node:fs/promises");
|
|
51758
52086
|
const { resolve: resolve62, basename: basename22 } = await import("node:path");
|
|
51759
52087
|
const { Project: Project2 } = await Promise.resolve().then(() => (init_engine(), engine_exports));
|
|
51760
52088
|
const { createHyperframesBackend: createHyperframesBackend2 } = await Promise.resolve().then(() => (init_hyperframes(), hyperframes_exports));
|
|
51761
|
-
const { exitWithError: exitWithError2, generalError: generalError2,
|
|
52089
|
+
const { exitWithError: exitWithError2, generalError: generalError2, outputSuccess: outputSuccess2 } = await Promise.resolve().then(() => (init_output(), output_exports));
|
|
51762
52090
|
const chalk2 = (await Promise.resolve().then(() => (init_source(), source_exports))).default;
|
|
51763
52091
|
const filePath = resolve62(process.cwd(), projectPath);
|
|
51764
52092
|
const content = await readFile42(filePath, "utf-8");
|
|
@@ -51784,10 +52112,10 @@ async function runHyperframesExport(projectPath, options, spinner2) {
|
|
|
51784
52112
|
return;
|
|
51785
52113
|
}
|
|
51786
52114
|
spinner2.succeed(chalk2.green(`Exported: ${result.outputPath}`));
|
|
51787
|
-
|
|
51788
|
-
success: true,
|
|
52115
|
+
outputSuccess2({
|
|
51789
52116
|
command: "export",
|
|
51790
|
-
|
|
52117
|
+
startedAt,
|
|
52118
|
+
data: {
|
|
51791
52119
|
outputPath: result.outputPath,
|
|
51792
52120
|
backend: "hyperframes",
|
|
51793
52121
|
durationMs: result.durationMs,
|
|
@@ -51831,6 +52159,7 @@ Cost: Free (no API keys needed). Requires FFmpeg.
|
|
|
51831
52159
|
GIF format: 15fps, no audio, looping. Good for previews and sharing.
|
|
51832
52160
|
Custom flags (--bitrate, --fps, --resolution, --codec) override preset values.
|
|
51833
52161
|
Run 'vibe schema export' for structured parameter info.`).action(async (projectPath, options) => {
|
|
52162
|
+
const startedAt = Date.now();
|
|
51834
52163
|
const spinner2 = ora("Checking FFmpeg...").start();
|
|
51835
52164
|
try {
|
|
51836
52165
|
if (options.output) {
|
|
@@ -51848,27 +52177,30 @@ Run 'vibe schema export' for structured parameter info.`).action(async (projectP
|
|
|
51848
52177
|
exitWithError(usageError(overrideError));
|
|
51849
52178
|
}
|
|
51850
52179
|
if (options.dryRun) {
|
|
51851
|
-
|
|
51852
|
-
dryRun: true,
|
|
52180
|
+
outputSuccess({
|
|
51853
52181
|
command: "export",
|
|
51854
|
-
|
|
51855
|
-
|
|
51856
|
-
|
|
51857
|
-
|
|
51858
|
-
|
|
51859
|
-
|
|
51860
|
-
|
|
51861
|
-
|
|
51862
|
-
|
|
51863
|
-
|
|
51864
|
-
|
|
51865
|
-
|
|
52182
|
+
startedAt,
|
|
52183
|
+
dryRun: true,
|
|
52184
|
+
data: {
|
|
52185
|
+
params: {
|
|
52186
|
+
project: projectPath,
|
|
52187
|
+
output: options.output || null,
|
|
52188
|
+
format: options.format,
|
|
52189
|
+
preset: options.preset,
|
|
52190
|
+
overwrite: options.overwrite,
|
|
52191
|
+
gapFill: options.gapFill,
|
|
52192
|
+
backend: options.backend,
|
|
52193
|
+
bitrate: options.bitrate ?? null,
|
|
52194
|
+
fps: customOverrides.fps ?? null,
|
|
52195
|
+
resolution: options.resolution ?? null,
|
|
52196
|
+
codec: options.codec ?? null
|
|
52197
|
+
}
|
|
51866
52198
|
}
|
|
51867
52199
|
});
|
|
51868
52200
|
return;
|
|
51869
52201
|
}
|
|
51870
52202
|
if (options.backend === "hyperframes") {
|
|
51871
|
-
await runHyperframesExport(projectPath, options, spinner2);
|
|
52203
|
+
await runHyperframesExport(projectPath, options, spinner2, startedAt);
|
|
51872
52204
|
return;
|
|
51873
52205
|
}
|
|
51874
52206
|
const ffmpegPath = await findFFmpeg();
|
|
@@ -52118,20 +52450,24 @@ var init_detect = __esm({
|
|
|
52118
52450
|
init_validate();
|
|
52119
52451
|
detectCommand = new Command("detect").description("Auto-detect scenes, beats, and silences in media");
|
|
52120
52452
|
detectCommand.command("scenes").description("Detect scene changes in video").argument("<video>", "Video file path").option("-t, --threshold <value>", "Scene change threshold (0-1)", "0.3").option("-o, --output <path>", "Output JSON file with timestamps").option("-p, --project <path>", "Add scenes as clips to project").option("--dry-run", "Preview parameters without executing").action(async (videoPath, options) => {
|
|
52453
|
+
const startedAt = Date.now();
|
|
52121
52454
|
const spinner2 = ora("Detecting scenes...").start();
|
|
52122
52455
|
try {
|
|
52123
52456
|
if (options.output) {
|
|
52124
52457
|
validateOutputPath(options.output);
|
|
52125
52458
|
}
|
|
52126
52459
|
if (options.dryRun) {
|
|
52127
|
-
|
|
52128
|
-
dryRun: true,
|
|
52460
|
+
outputSuccess({
|
|
52129
52461
|
command: "detect scenes",
|
|
52130
|
-
|
|
52131
|
-
|
|
52132
|
-
|
|
52133
|
-
|
|
52134
|
-
|
|
52462
|
+
startedAt,
|
|
52463
|
+
dryRun: true,
|
|
52464
|
+
data: {
|
|
52465
|
+
params: {
|
|
52466
|
+
video: videoPath,
|
|
52467
|
+
threshold: options.threshold,
|
|
52468
|
+
output: options.output || null,
|
|
52469
|
+
project: options.project || null
|
|
52470
|
+
}
|
|
52135
52471
|
}
|
|
52136
52472
|
});
|
|
52137
52473
|
return;
|
|
@@ -52233,20 +52569,24 @@ var init_detect = __esm({
|
|
|
52233
52569
|
}
|
|
52234
52570
|
});
|
|
52235
52571
|
detectCommand.command("silence").description("Detect silence in audio/video").argument("<media>", "Media file path").option("-n, --noise <dB>", "Noise threshold in dB", "-30").option("-d, --duration <sec>", "Minimum silence duration", "0.5").option("-o, --output <path>", "Output JSON file with timestamps").option("--dry-run", "Preview parameters without executing").action(async (mediaPath, options) => {
|
|
52572
|
+
const startedAt = Date.now();
|
|
52236
52573
|
const spinner2 = ora("Detecting silence...").start();
|
|
52237
52574
|
try {
|
|
52238
52575
|
if (options.output) {
|
|
52239
52576
|
validateOutputPath(options.output);
|
|
52240
52577
|
}
|
|
52241
52578
|
if (options.dryRun) {
|
|
52242
|
-
|
|
52243
|
-
dryRun: true,
|
|
52579
|
+
outputSuccess({
|
|
52244
52580
|
command: "detect silence",
|
|
52245
|
-
|
|
52246
|
-
|
|
52247
|
-
|
|
52248
|
-
|
|
52249
|
-
|
|
52581
|
+
startedAt,
|
|
52582
|
+
dryRun: true,
|
|
52583
|
+
data: {
|
|
52584
|
+
params: {
|
|
52585
|
+
media: mediaPath,
|
|
52586
|
+
noise: options.noise,
|
|
52587
|
+
duration: options.duration,
|
|
52588
|
+
output: options.output || null
|
|
52589
|
+
}
|
|
52250
52590
|
}
|
|
52251
52591
|
});
|
|
52252
52592
|
return;
|
|
@@ -52315,18 +52655,22 @@ var init_detect = __esm({
|
|
|
52315
52655
|
}
|
|
52316
52656
|
});
|
|
52317
52657
|
detectCommand.command("beats").description("Detect beats in audio (for music sync)").argument("<audio>", "Audio file path").option("-o, --output <path>", "Output JSON file with timestamps").option("--dry-run", "Preview parameters without executing").action(async (audioPath, options) => {
|
|
52658
|
+
const startedAt = Date.now();
|
|
52318
52659
|
const spinner2 = ora("Detecting beats...").start();
|
|
52319
52660
|
try {
|
|
52320
52661
|
if (options.output) {
|
|
52321
52662
|
validateOutputPath(options.output);
|
|
52322
52663
|
}
|
|
52323
52664
|
if (options.dryRun) {
|
|
52324
|
-
|
|
52325
|
-
dryRun: true,
|
|
52665
|
+
outputSuccess({
|
|
52326
52666
|
command: "detect beats",
|
|
52327
|
-
|
|
52328
|
-
|
|
52329
|
-
|
|
52667
|
+
startedAt,
|
|
52668
|
+
dryRun: true,
|
|
52669
|
+
data: {
|
|
52670
|
+
params: {
|
|
52671
|
+
audio: audioPath,
|
|
52672
|
+
output: options.output || null
|
|
52673
|
+
}
|
|
52330
52674
|
}
|
|
52331
52675
|
});
|
|
52332
52676
|
return;
|
|
@@ -61356,6 +61700,363 @@ Audio:`);
|
|
|
61356
61700
|
}
|
|
61357
61701
|
});
|
|
61358
61702
|
|
|
61703
|
+
// src/commands/_shared/walkthroughs/walkthroughs.ts
|
|
61704
|
+
function loadWalkthrough(topic) {
|
|
61705
|
+
const meta = META[topic];
|
|
61706
|
+
const content = CONTENT[topic];
|
|
61707
|
+
if (!meta || !content) {
|
|
61708
|
+
throw new Error(`Unknown walkthrough topic: ${topic}`);
|
|
61709
|
+
}
|
|
61710
|
+
return {
|
|
61711
|
+
topic,
|
|
61712
|
+
title: meta.title,
|
|
61713
|
+
summary: meta.summary,
|
|
61714
|
+
steps: meta.steps,
|
|
61715
|
+
relatedCommands: meta.relatedCommands,
|
|
61716
|
+
content
|
|
61717
|
+
};
|
|
61718
|
+
}
|
|
61719
|
+
function listWalkthroughs() {
|
|
61720
|
+
return WALKTHROUGH_TOPICS.map((topic) => ({
|
|
61721
|
+
topic,
|
|
61722
|
+
title: META[topic].title,
|
|
61723
|
+
summary: META[topic].summary
|
|
61724
|
+
}));
|
|
61725
|
+
}
|
|
61726
|
+
function isWalkthroughTopic(value) {
|
|
61727
|
+
return typeof value === "string" && WALKTHROUGH_TOPICS.includes(value);
|
|
61728
|
+
}
|
|
61729
|
+
var SCENE_WALKTHROUGH, PIPELINE_WALKTHROUGH, META, CONTENT, WALKTHROUGH_TOPICS;
|
|
61730
|
+
var init_walkthroughs = __esm({
|
|
61731
|
+
"src/commands/_shared/walkthroughs/walkthroughs.ts"() {
|
|
61732
|
+
"use strict";
|
|
61733
|
+
SCENE_WALKTHROUGH = `# Scene authoring with vibe
|
|
61734
|
+
|
|
61735
|
+
A scene project is a directory that is **bilingual**: it works with both
|
|
61736
|
+
\`vibe\` and \`npx hyperframes\`. Each scene is one HTML file with scoped CSS
|
|
61737
|
+
and a paused GSAP timeline. Cheap to edit, cheap to lint, expensive only
|
|
61738
|
+
at render.
|
|
61739
|
+
|
|
61740
|
+
\`vibe scene build\` (v0.60+) is the supported one-shot driver from a
|
|
61741
|
+
written storyboard to an MP4. Plan H (v0.70) added \`--mode agent\` so the
|
|
61742
|
+
host agent itself authors the per-beat HTML \u2014 no internal LLM call.
|
|
61743
|
+
|
|
61744
|
+
## Three authoring paths
|
|
61745
|
+
|
|
61746
|
+
| Path | Command | When to use |
|
|
61747
|
+
|---|---|---|
|
|
61748
|
+
| **One-shot (default, v0.60+)** | \`vibe scene build [project-dir]\` | STORYBOARD.md has YAML frontmatter + per-beat cues |
|
|
61749
|
+
| **High-craft (manual)** | \`DESIGN.md\` + Hyperframes skill in your agent | Maximum control: hand-author each scene |
|
|
61750
|
+
| **Quick draft** | \`vibe scene add --style <preset>\` | No agent or no API keys; fast iteration |
|
|
61751
|
+
|
|
61752
|
+
Recommend \`vibe scene build\` whenever the user has a STORYBOARD with
|
|
61753
|
+
narration / backdrop intent.
|
|
61754
|
+
|
|
61755
|
+
## High-craft path
|
|
61756
|
+
|
|
61757
|
+
1. \`vibe scene init my-promo --visual-style "Swiss Pulse"\` \u2014 seeds
|
|
61758
|
+
\`DESIGN.md\` (palette, typography, motion, transitions) plus the
|
|
61759
|
+
\`vibe.project.yaml\` / \`hyperframes.json\` / \`index.html\` scaffold.
|
|
61760
|
+
In Plan H this **also installs the Hyperframes skill** at the
|
|
61761
|
+
right place for your host (\`.claude/skills/hyperframes/\` for Claude
|
|
61762
|
+
Code, \`.cursor/rules/hyperframes.mdc\` for Cursor, universal
|
|
61763
|
+
\`SKILL.md\` for everyone else).
|
|
61764
|
+
2. Read \`SKILL.md\` (or the host-specific copy) \u2014 Hyperframes
|
|
61765
|
+
framework rules, motion principles, type system, transition recipes.
|
|
61766
|
+
3. Read \`DESIGN.md\` \u2014 project-specific palette / typography / motion
|
|
61767
|
+
signature (visual identity hard-gate).
|
|
61768
|
+
4. Author each scene HTML directly under \`compositions/scene-<id>.html\`
|
|
61769
|
+
using the rules from steps 2 and 3. The skill enforces the visual
|
|
61770
|
+
identity contract \u2014 scenes that contradict DESIGN.md fail lint.
|
|
61771
|
+
5. \`vibe scene lint --fix\` for mechanical issues, \`vibe scene render\`
|
|
61772
|
+
to MP4.
|
|
61773
|
+
|
|
61774
|
+
## Quick-draft path
|
|
61775
|
+
|
|
61776
|
+
\`\`\`bash
|
|
61777
|
+
vibe scene init my-promo -r 16:9 -d 30
|
|
61778
|
+
vibe scene add intro --style announcement \\
|
|
61779
|
+
--headline "Ship videos, not clicks"
|
|
61780
|
+
vibe scene lint
|
|
61781
|
+
vibe scene render
|
|
61782
|
+
\`\`\`
|
|
61783
|
+
|
|
61784
|
+
\`vibe scene init\` is **idempotent** \u2014 running it on an existing
|
|
61785
|
+
Hyperframes directory merges \`hyperframes.json\` instead of clobbering it.
|
|
61786
|
+
Safe to invoke on user-provided projects.
|
|
61787
|
+
|
|
61788
|
+
## Subcommands
|
|
61789
|
+
|
|
61790
|
+
\`\`\`bash
|
|
61791
|
+
vibe scene init <dir> [-r 16:9|9:16|1:1|4:5] [-d <sec>] [--visual-style "<name>"]
|
|
61792
|
+
vibe scene styles [<name>] # list / show vendored visual identities
|
|
61793
|
+
vibe scene install-skill [<dir>] [--host all] # retroactive Hyperframes-skill install
|
|
61794
|
+
vibe scene add <name> --style <preset> [...]
|
|
61795
|
+
vibe scene compose-prompts [<dir>] [--beat <id>] # H2: emit plan, no LLM call
|
|
61796
|
+
vibe scene lint [<root>] [--json] [--fix]
|
|
61797
|
+
vibe scene render [<root>] [--fps 30] [--quality standard] [--format mp4]
|
|
61798
|
+
vibe scene build [<dir>] [--mode agent|batch|auto] # H3 dispatch
|
|
61799
|
+
\`\`\`
|
|
61800
|
+
|
|
61801
|
+
## Style presets (for \`vibe scene add --style\`)
|
|
61802
|
+
|
|
61803
|
+
- **simple** \u2014 backdrop + bottom caption (default)
|
|
61804
|
+
- **announcement** \u2014 single huge headline, gradient text
|
|
61805
|
+
- **explainer** \u2014 kicker + title + subtitle stack
|
|
61806
|
+
- **kinetic-type** \u2014 words animate in word-by-word
|
|
61807
|
+
- **product-shot** \u2014 corner label + bottom headline + slow zoom
|
|
61808
|
+
|
|
61809
|
+
All presets accept \`--narration <text|file>\`, \`--visuals <prompt>\`,
|
|
61810
|
+
\`--headline\`, \`--kicker\`. With \`--narration\`, scene duration auto-derives
|
|
61811
|
+
from the generated TTS audio.
|
|
61812
|
+
|
|
61813
|
+
## STORYBOARD-to-MP4 (one command, v0.60+)
|
|
61814
|
+
|
|
61815
|
+
\`\`\`bash
|
|
61816
|
+
vibe scene init my-promo --visual-style "Swiss Pulse" -d 12
|
|
61817
|
+
# (edit STORYBOARD.md with per-beat YAML cues \u2014 narration, backdrop, duration)
|
|
61818
|
+
vibe scene build my-promo
|
|
61819
|
+
\`\`\`
|
|
61820
|
+
|
|
61821
|
+
\`vibe scene build\` reads the STORYBOARD frontmatter + per-beat cues,
|
|
61822
|
+
dispatches TTS + image-gen per beat, then either:
|
|
61823
|
+
|
|
61824
|
+
- **\`--mode agent\`** (default when an agent host is detected) \u2014 emits a
|
|
61825
|
+
\`needs-author\` plan via \`vibe scene compose-prompts\`. The host agent
|
|
61826
|
+
authors each \`compositions/scene-<id>.html\` itself, then re-invoking
|
|
61827
|
+
\`vibe scene build\` proceeds to lint + render.
|
|
61828
|
+
- **\`--mode batch\`** \u2014 VibeFrame runs an internal LLM (Claude / OpenAI /
|
|
61829
|
+
Gemini) to compose the HTML, then renders.
|
|
61830
|
+
|
|
61831
|
+
\`VIBE_BUILD_MODE\` env var overrides the auto-resolve.
|
|
61832
|
+
|
|
61833
|
+
## Lint feedback loop
|
|
61834
|
+
|
|
61835
|
+
\`\`\`bash
|
|
61836
|
+
vibe scene lint --json --fix
|
|
61837
|
+
\`\`\`
|
|
61838
|
+
|
|
61839
|
+
Returns structured findings. The recommended loop: 1) run lint with
|
|
61840
|
+
\`--fix\` (mechanical fixes applied), 2) if \`errorCount > 0\`, edit the
|
|
61841
|
+
scene HTML, 3) re-lint. Cap retries at 3 \u2014 if errors persist, fall back
|
|
61842
|
+
to a template preset (\`vibe scene add <id> --style simple --force\`)
|
|
61843
|
+
and surface the error to the user.
|
|
61844
|
+
|
|
61845
|
+
## When to use VibeFrame vs raw Hyperframes
|
|
61846
|
+
|
|
61847
|
+
| Task | Tool |
|
|
61848
|
+
|------|------|
|
|
61849
|
+
| Generate narration + image, then author scene | \`vibe scene add\` |
|
|
61850
|
+
| Generate a full scenes project from a STORYBOARD | \`vibe scene build\` |
|
|
61851
|
+
| Hand-tweak a single scene's animation | edit \`compositions/<file>.html\` directly |
|
|
61852
|
+
| Render the project | \`vibe scene render\` *or* \`npx hyperframes render\` (equivalent) |
|
|
61853
|
+
| Lint | \`vibe scene lint\` *or* \`npx hyperframes lint\` (equivalent) |
|
|
61854
|
+
|
|
61855
|
+
The \`vibe\` CLI adds asset generation, AI orchestration, and pipeline
|
|
61856
|
+
integration on top of Hyperframes' rendering primitives.
|
|
61857
|
+
|
|
61858
|
+
## Quality checklist before render
|
|
61859
|
+
|
|
61860
|
+
- [ ] \`vibe scene lint\` exits 0 (or only warnings)
|
|
61861
|
+
- [ ] \`vibe doctor\` confirms a usable Chrome (required for render)
|
|
61862
|
+
- [ ] Root \`data-duration\` matches the sum of clip durations
|
|
61863
|
+
- [ ] Aspect ratio in \`vibe.project.yaml\` matches the destination platform
|
|
61864
|
+
`;
|
|
61865
|
+
PIPELINE_WALKTHROUGH = `# YAML pipelines (Video as Code)
|
|
61866
|
+
|
|
61867
|
+
A pipeline is a YAML manifest with steps that reference each other's
|
|
61868
|
+
outputs. \`vibe run pipeline.yaml\` executes them with checkpointing and
|
|
61869
|
+
cost estimation.
|
|
61870
|
+
|
|
61871
|
+
## Minimal skeleton
|
|
61872
|
+
|
|
61873
|
+
\`\`\`yaml
|
|
61874
|
+
name: promo-video
|
|
61875
|
+
description: 15s product teaser
|
|
61876
|
+
steps:
|
|
61877
|
+
- id: backdrop
|
|
61878
|
+
action: generate-image
|
|
61879
|
+
prompt: "sleek product shot on white background"
|
|
61880
|
+
output: backdrop.png
|
|
61881
|
+
- id: scene
|
|
61882
|
+
action: generate-video
|
|
61883
|
+
image: $backdrop.output # reference previous step output
|
|
61884
|
+
prompt: "slow camera pan"
|
|
61885
|
+
duration: 5
|
|
61886
|
+
output: scene.mp4
|
|
61887
|
+
- id: voice
|
|
61888
|
+
action: generate-tts
|
|
61889
|
+
text: "Meet the new standard."
|
|
61890
|
+
output: voice.mp3
|
|
61891
|
+
- id: final
|
|
61892
|
+
action: compose
|
|
61893
|
+
video: $scene.output
|
|
61894
|
+
audio: $voice.output
|
|
61895
|
+
output: final.mp4
|
|
61896
|
+
\`\`\`
|
|
61897
|
+
|
|
61898
|
+
## Supported actions
|
|
61899
|
+
|
|
61900
|
+
- \`generate-image\`, \`generate-video\`, \`generate-tts\`, \`generate-music\`,
|
|
61901
|
+
\`generate-sound-effect\`, \`generate-storyboard\`, \`generate-motion\`
|
|
61902
|
+
- \`edit-silence-cut\`, \`edit-jump-cut\`, \`edit-caption\`, \`edit-grade\`,
|
|
61903
|
+
\`edit-reframe\`, \`edit-speed-ramp\`, \`edit-fade\`, \`edit-noise-reduce\`,
|
|
61904
|
+
\`edit-text-overlay\`, \`edit-fill-gaps\`
|
|
61905
|
+
- \`analyze-media\`, \`analyze-video\`, \`analyze-review\`, \`analyze-suggest\`
|
|
61906
|
+
- \`audio-transcribe\`, \`audio-isolate\`, \`audio-voice-clone\`, \`audio-dub\`,
|
|
61907
|
+
\`audio-duck\`
|
|
61908
|
+
- \`detect-scenes\`, \`detect-silence\`, \`detect-beats\`
|
|
61909
|
+
- \`compose\`, \`export\`
|
|
61910
|
+
- \`scene-build\` (Plan H one-shot driver) and \`scene-render\`
|
|
61911
|
+
- \`compose-scenes-with-skills\` (internal-LLM compose pass)
|
|
61912
|
+
|
|
61913
|
+
The full set lives in \`packages/cli/src/pipeline/executor.ts\`.
|
|
61914
|
+
|
|
61915
|
+
## Variable references
|
|
61916
|
+
|
|
61917
|
+
- \`$<step-id>.output\` \u2014 previous step's output path
|
|
61918
|
+
- \`$<step-id>.result.<field>\` \u2014 structured field from JSON result
|
|
61919
|
+
- \`\${ENV_VAR}\` \u2014 environment variable
|
|
61920
|
+
- Values can be templated: \`"\${SCRIPT_TITLE} - Episode \${EPISODE}"\`
|
|
61921
|
+
|
|
61922
|
+
## Running
|
|
61923
|
+
|
|
61924
|
+
\`\`\`bash
|
|
61925
|
+
vibe run pipeline.yaml --dry-run # plan + cost estimate, no execution
|
|
61926
|
+
vibe run pipeline.yaml # execute
|
|
61927
|
+
vibe run pipeline.yaml --resume # retry from last successful step
|
|
61928
|
+
vibe run pipeline.yaml --from scene # start at specific step
|
|
61929
|
+
vibe run pipeline.yaml --provider-video kling # override provider
|
|
61930
|
+
\`\`\`
|
|
61931
|
+
|
|
61932
|
+
Checkpoints land next to the YAML: \`pipeline.yaml.checkpoint.json\`.
|
|
61933
|
+
|
|
61934
|
+
## Authoring tips
|
|
61935
|
+
|
|
61936
|
+
1. **Start from examples** \u2014 \`examples/demo-pipeline.yaml\` (FFmpeg-only,
|
|
61937
|
+
no keys), \`examples/promo-video.yaml\` (AI providers).
|
|
61938
|
+
2. **Dry-run first** \u2014 you see estimated cost and resolved variable
|
|
61939
|
+
graph before spending API credits.
|
|
61940
|
+
3. **Keep step ids short and descriptive** (\`intro\`, \`scene1\`, \`voice\`,
|
|
61941
|
+
\`bgm\`) \u2014 they appear in logs and variable refs.
|
|
61942
|
+
4. **Name outputs** with extensions matching the action (\`.mp4\`, \`.mp3\`,
|
|
61943
|
+
\`.png\`, \`.json\`).
|
|
61944
|
+
5. **Declare \`budget:\`** on expensive pipelines:
|
|
61945
|
+
\`\`\`yaml
|
|
61946
|
+
budget:
|
|
61947
|
+
tokens: 500_000
|
|
61948
|
+
max_tool_errors: 3
|
|
61949
|
+
cost_usd: 5.00
|
|
61950
|
+
\`\`\`
|
|
61951
|
+
6. **Split large pipelines** into smaller YAML files and compose via
|
|
61952
|
+
\`action: run-pipeline\` (nested).
|
|
61953
|
+
|
|
61954
|
+
## Converting ad-hoc shell sessions to pipelines
|
|
61955
|
+
|
|
61956
|
+
When the user has a working shell sequence, extract steps:
|
|
61957
|
+
|
|
61958
|
+
- Each \`vibe ...\` command becomes one step
|
|
61959
|
+
- File outputs become step outputs; downstream \`-i <file>\` references
|
|
61960
|
+
become \`$<id>.output\`
|
|
61961
|
+
- Shared parameters move to a top-level \`defaults:\` section
|
|
61962
|
+
- Wrap the entire chain in a \`name:\` + \`steps:\` skeleton
|
|
61963
|
+
|
|
61964
|
+
The \`compose\` action is the catch-all assembly step (audio mux, video
|
|
61965
|
+
overlay, etc.) \u2014 useful at the tail of a pipeline.
|
|
61966
|
+
`;
|
|
61967
|
+
META = {
|
|
61968
|
+
scene: {
|
|
61969
|
+
title: "Scene authoring with vibe",
|
|
61970
|
+
summary: "Author per-scene HTML compositions and render to MP4 (BUILD flow)",
|
|
61971
|
+
steps: [
|
|
61972
|
+
'Run `vibe scene init <dir> --visual-style "<style name>"` to scaffold the project + install the Hyperframes skill (Plan H).',
|
|
61973
|
+
"Edit `STORYBOARD.md` with per-beat YAML cues (narration / backdrop / duration).",
|
|
61974
|
+
"Read `SKILL.md` for the framework rules and `DESIGN.md` for the visual-identity hard-gate.",
|
|
61975
|
+
"Run `vibe scene build <dir>`. With an agent host detected, the CLI emits a `needs-author` plan; the host agent authors each `compositions/scene-<id>.html` and re-invokes to render.",
|
|
61976
|
+
"Run `vibe scene lint --fix` to validate, then `vibe scene render` to produce the MP4."
|
|
61977
|
+
],
|
|
61978
|
+
relatedCommands: [
|
|
61979
|
+
"vibe scene init",
|
|
61980
|
+
"vibe scene styles",
|
|
61981
|
+
"vibe scene install-skill",
|
|
61982
|
+
"vibe scene compose-prompts",
|
|
61983
|
+
"vibe scene build",
|
|
61984
|
+
"vibe scene lint",
|
|
61985
|
+
"vibe scene render",
|
|
61986
|
+
"vibe scene add"
|
|
61987
|
+
]
|
|
61988
|
+
},
|
|
61989
|
+
pipeline: {
|
|
61990
|
+
title: "YAML pipelines (Video as Code)",
|
|
61991
|
+
summary: "Author and run reproducible multi-step video workflows",
|
|
61992
|
+
steps: [
|
|
61993
|
+
"Sketch the workflow as YAML \u2014 `name`, `description`, then `steps:` with `id` + `action` + inputs/outputs.",
|
|
61994
|
+
"Reference previous step outputs via `$<step-id>.output` (or `$<step-id>.result.<field>` for structured returns).",
|
|
61995
|
+
"Run `vibe run pipeline.yaml --dry-run` to see the resolved graph + cost estimate before spending API budget.",
|
|
61996
|
+
"Add a `budget:` block (tokens / cost_usd / max_tool_errors) to cap expensive runs.",
|
|
61997
|
+
"Run `vibe run pipeline.yaml` to execute. Failed steps checkpoint to `pipeline.yaml.checkpoint.json`; resume with `--resume`."
|
|
61998
|
+
],
|
|
61999
|
+
relatedCommands: [
|
|
62000
|
+
"vibe run",
|
|
62001
|
+
"vibe schema --list",
|
|
62002
|
+
"vibe doctor"
|
|
62003
|
+
]
|
|
62004
|
+
}
|
|
62005
|
+
};
|
|
62006
|
+
CONTENT = {
|
|
62007
|
+
scene: SCENE_WALKTHROUGH,
|
|
62008
|
+
pipeline: PIPELINE_WALKTHROUGH
|
|
62009
|
+
};
|
|
62010
|
+
WALKTHROUGH_TOPICS = ["scene", "pipeline"];
|
|
62011
|
+
}
|
|
62012
|
+
});
|
|
62013
|
+
|
|
62014
|
+
// src/tools/manifest/walkthrough.ts
|
|
62015
|
+
var walkthroughSchema, walkthroughTool, walkthroughTools;
|
|
62016
|
+
var init_walkthrough = __esm({
|
|
62017
|
+
"src/tools/manifest/walkthrough.ts"() {
|
|
62018
|
+
"use strict";
|
|
62019
|
+
init_zod();
|
|
62020
|
+
init_define_tool();
|
|
62021
|
+
init_walkthroughs();
|
|
62022
|
+
walkthroughSchema = external_exports.object({
|
|
62023
|
+
topic: external_exports.enum(WALKTHROUGH_TOPICS).optional().describe(
|
|
62024
|
+
"Walkthrough topic to load. Omit to list every available walkthrough \u2014 useful for discovery on first contact."
|
|
62025
|
+
)
|
|
62026
|
+
});
|
|
62027
|
+
walkthroughTool = defineTool({
|
|
62028
|
+
name: "walkthrough",
|
|
62029
|
+
category: "agent",
|
|
62030
|
+
cost: "free",
|
|
62031
|
+
description: "Load the step-by-step authoring guide for a vibe workflow (BUILD scene authoring, YAML pipeline authoring). Universal CLI-equivalent of Claude Code's /vibe-* slash commands \u2014 any host agent that calls this tool gets the same content the slash menu delivers in Claude Code, with no Claude Code dependency. Without a topic, returns the catalog of walkthroughs for discovery.",
|
|
62032
|
+
schema: walkthroughSchema,
|
|
62033
|
+
async execute(args) {
|
|
62034
|
+
if (!args.topic) {
|
|
62035
|
+
const topics = listWalkthroughs();
|
|
62036
|
+
return {
|
|
62037
|
+
success: true,
|
|
62038
|
+
data: { action: "list", topics },
|
|
62039
|
+
humanLines: [
|
|
62040
|
+
`Available walkthroughs: ${topics.map((t) => t.topic).join(", ")}.`,
|
|
62041
|
+
`Call again with topic to load full content.`
|
|
62042
|
+
]
|
|
62043
|
+
};
|
|
62044
|
+
}
|
|
62045
|
+
const result = loadWalkthrough(args.topic);
|
|
62046
|
+
return {
|
|
62047
|
+
success: true,
|
|
62048
|
+
data: { action: "show", ...result },
|
|
62049
|
+
humanLines: [
|
|
62050
|
+
`Loaded walkthrough: ${result.title}.`,
|
|
62051
|
+
`${result.steps.length} steps, ${result.relatedCommands.length} related commands, ${result.content.length} chars of guide content.`
|
|
62052
|
+
]
|
|
62053
|
+
};
|
|
62054
|
+
}
|
|
62055
|
+
});
|
|
62056
|
+
walkthroughTools = [walkthroughTool];
|
|
62057
|
+
}
|
|
62058
|
+
});
|
|
62059
|
+
|
|
61359
62060
|
// src/tools/manifest/index.ts
|
|
61360
62061
|
var manifest_exports = {};
|
|
61361
62062
|
__export(manifest_exports, {
|
|
@@ -61370,7 +62071,8 @@ __export(manifest_exports, {
|
|
|
61370
62071
|
pipelineTools: () => pipelineTools,
|
|
61371
62072
|
projectTools: () => projectTools,
|
|
61372
62073
|
sceneTools: () => sceneTools,
|
|
61373
|
-
timelineTools: () => timelineTools
|
|
62074
|
+
timelineTools: () => timelineTools,
|
|
62075
|
+
walkthroughTools: () => walkthroughTools
|
|
61374
62076
|
});
|
|
61375
62077
|
var manifest;
|
|
61376
62078
|
var init_manifest = __esm({
|
|
@@ -61387,6 +62089,7 @@ var init_manifest = __esm({
|
|
|
61387
62089
|
init_project2();
|
|
61388
62090
|
init_export2();
|
|
61389
62091
|
init_agent_only();
|
|
62092
|
+
init_walkthrough();
|
|
61390
62093
|
manifest = [
|
|
61391
62094
|
...sceneTools,
|
|
61392
62095
|
...audioTools,
|
|
@@ -61398,7 +62101,8 @@ var init_manifest = __esm({
|
|
|
61398
62101
|
...timelineTools,
|
|
61399
62102
|
...projectTools,
|
|
61400
62103
|
...exportTools,
|
|
61401
|
-
...agentOnlyTools
|
|
62104
|
+
...agentOnlyTools,
|
|
62105
|
+
...walkthroughTools
|
|
61402
62106
|
];
|
|
61403
62107
|
}
|
|
61404
62108
|
});
|
|
@@ -61602,7 +62306,7 @@ init_source();
|
|
|
61602
62306
|
// package.json
|
|
61603
62307
|
var package_default = {
|
|
61604
62308
|
name: "@vibeframe/cli",
|
|
61605
|
-
version: "0.
|
|
62309
|
+
version: "0.72.0",
|
|
61606
62310
|
description: "VibeFrame CLI - natural language video editing from the terminal",
|
|
61607
62311
|
private: false,
|
|
61608
62312
|
license: "MIT",
|
|
@@ -61834,20 +62538,24 @@ Examples:
|
|
|
61834
62538
|
Cost: Free (no API keys needed). Projects are saved as .vibe.json files.
|
|
61835
62539
|
Run 'vibe schema project.<command>' for structured parameter info.`);
|
|
61836
62540
|
projectCommand.command("create").description("Create a new project").argument("<name>", "Project name or path (e.g., 'my-project' or 'output/my-project')").option("-o, --output <path>", "Output file path (overrides name-based path)").option("-r, --ratio <ratio>", "Aspect ratio (16:9, 9:16, 1:1, 4:5)", "16:9").option("-f, --fps <fps>", "Frame rate", "30").option("--dry-run", "Preview parameters without executing").action(async (name, options) => {
|
|
62541
|
+
const startedAt = Date.now();
|
|
61837
62542
|
const spinner2 = ora("Creating project...").start();
|
|
61838
62543
|
try {
|
|
61839
62544
|
if (options.output) {
|
|
61840
62545
|
validateOutputPath(options.output);
|
|
61841
62546
|
}
|
|
61842
62547
|
if (options.dryRun) {
|
|
61843
|
-
|
|
61844
|
-
dryRun: true,
|
|
62548
|
+
outputSuccess({
|
|
61845
62549
|
command: "project create",
|
|
61846
|
-
|
|
61847
|
-
|
|
61848
|
-
|
|
61849
|
-
|
|
61850
|
-
|
|
62550
|
+
startedAt,
|
|
62551
|
+
dryRun: true,
|
|
62552
|
+
data: {
|
|
62553
|
+
params: {
|
|
62554
|
+
name,
|
|
62555
|
+
output: options.output || null,
|
|
62556
|
+
aspectRatio: options.ratio,
|
|
62557
|
+
frameRate: options.fps
|
|
62558
|
+
}
|
|
61851
62559
|
}
|
|
61852
62560
|
});
|
|
61853
62561
|
return;
|
|
@@ -61912,17 +62620,21 @@ projectCommand.command("info").description("Show project information").argument(
|
|
|
61912
62620
|
}
|
|
61913
62621
|
});
|
|
61914
62622
|
projectCommand.command("set").description("Update project settings").argument("<file>", "Project file path").option("-n, --name <name>", "Project name").option("-r, --ratio <ratio>", "Aspect ratio (16:9, 9:16, 1:1, 4:5)").option("-f, --fps <fps>", "Frame rate").option("--dry-run", "Preview parameters without executing").action(async (file, options) => {
|
|
62623
|
+
const startedAt = Date.now();
|
|
61915
62624
|
const spinner2 = ora("Updating project...").start();
|
|
61916
62625
|
try {
|
|
61917
62626
|
if (options.dryRun) {
|
|
61918
|
-
|
|
61919
|
-
dryRun: true,
|
|
62627
|
+
outputSuccess({
|
|
61920
62628
|
command: "project set",
|
|
61921
|
-
|
|
61922
|
-
|
|
61923
|
-
|
|
61924
|
-
|
|
61925
|
-
|
|
62629
|
+
startedAt,
|
|
62630
|
+
dryRun: true,
|
|
62631
|
+
data: {
|
|
62632
|
+
params: {
|
|
62633
|
+
file,
|
|
62634
|
+
name: options.name || null,
|
|
62635
|
+
ratio: options.ratio || null,
|
|
62636
|
+
fps: options.fps || null
|
|
62637
|
+
}
|
|
61926
62638
|
}
|
|
61927
62639
|
});
|
|
61928
62640
|
return;
|
|
@@ -61973,18 +62685,22 @@ Typical workflow: create project \u2192 add-source \u2192 add-clip \u2192 trim/s
|
|
|
61973
62685
|
Cost: Free (no API keys needed).
|
|
61974
62686
|
Run 'vibe schema timeline.<command>' for structured parameter info.`);
|
|
61975
62687
|
timelineCommand.command("add-source").description("Add a media source to the project").argument("<project>", "Project file path").argument("<media>", "Media file path").option("-n, --name <name>", "Source name (defaults to filename)").option("-t, --type <type>", "Media type (video, audio, image, lottie)").option("-d, --duration <seconds>", "Duration in seconds (required for images)").option("--dry-run", "Preview parameters without executing").action(async (projectPath, mediaPath, options) => {
|
|
62688
|
+
const startedAt = Date.now();
|
|
61976
62689
|
const spinner2 = ora("Adding source...").start();
|
|
61977
62690
|
try {
|
|
61978
62691
|
if (options.dryRun) {
|
|
61979
|
-
|
|
61980
|
-
dryRun: true,
|
|
62692
|
+
outputSuccess({
|
|
61981
62693
|
command: "timeline add-source",
|
|
61982
|
-
|
|
61983
|
-
|
|
61984
|
-
|
|
61985
|
-
|
|
61986
|
-
|
|
61987
|
-
|
|
62694
|
+
startedAt,
|
|
62695
|
+
dryRun: true,
|
|
62696
|
+
data: {
|
|
62697
|
+
params: {
|
|
62698
|
+
project: projectPath,
|
|
62699
|
+
media: mediaPath,
|
|
62700
|
+
name: options.name || null,
|
|
62701
|
+
type: options.type || null,
|
|
62702
|
+
duration: options.duration || null
|
|
62703
|
+
}
|
|
61988
62704
|
}
|
|
61989
62705
|
});
|
|
61990
62706
|
return;
|
|
@@ -62017,21 +62733,25 @@ timelineCommand.command("add-source").description("Add a media source to the pro
|
|
|
62017
62733
|
}
|
|
62018
62734
|
});
|
|
62019
62735
|
timelineCommand.command("add-clip").description("Add a clip to the timeline").argument("<project>", "Project file path").argument("<source-id>", "Source ID to use").option("-t, --track <id>", "Track ID (defaults to first matching track)").option("-s, --start <seconds>", "Start time in timeline", "0").option("-d, --duration <seconds>", "Clip duration (defaults to source duration)").option("--offset <seconds>", "Source start offset", "0").option("--dry-run", "Preview parameters without executing").action(async (projectPath, sourceId, options) => {
|
|
62736
|
+
const startedAt = Date.now();
|
|
62020
62737
|
const spinner2 = ora("Adding clip...").start();
|
|
62021
62738
|
try {
|
|
62022
62739
|
validateResourceId(sourceId);
|
|
62023
62740
|
if (options.track) validateResourceId(options.track);
|
|
62024
62741
|
if (options.dryRun) {
|
|
62025
|
-
|
|
62026
|
-
dryRun: true,
|
|
62742
|
+
outputSuccess({
|
|
62027
62743
|
command: "timeline add-clip",
|
|
62028
|
-
|
|
62029
|
-
|
|
62030
|
-
|
|
62031
|
-
|
|
62032
|
-
|
|
62033
|
-
|
|
62034
|
-
|
|
62744
|
+
startedAt,
|
|
62745
|
+
dryRun: true,
|
|
62746
|
+
data: {
|
|
62747
|
+
params: {
|
|
62748
|
+
project: projectPath,
|
|
62749
|
+
sourceId,
|
|
62750
|
+
track: options.track || null,
|
|
62751
|
+
start: options.start,
|
|
62752
|
+
duration: options.duration || null,
|
|
62753
|
+
offset: options.offset
|
|
62754
|
+
}
|
|
62035
62755
|
}
|
|
62036
62756
|
});
|
|
62037
62757
|
return;
|
|
@@ -62080,16 +62800,20 @@ timelineCommand.command("add-clip").description("Add a clip to the timeline").ar
|
|
|
62080
62800
|
}
|
|
62081
62801
|
});
|
|
62082
62802
|
timelineCommand.command("add-track").description("Add a new track").argument("<project>", "Project file path").argument("<type>", "Track type (video, audio)").option("-n, --name <name>", "Track name").option("--dry-run", "Preview parameters without executing").action(async (projectPath, type, options) => {
|
|
62803
|
+
const startedAt = Date.now();
|
|
62083
62804
|
const spinner2 = ora("Adding track...").start();
|
|
62084
62805
|
try {
|
|
62085
62806
|
if (options.dryRun) {
|
|
62086
|
-
|
|
62087
|
-
dryRun: true,
|
|
62807
|
+
outputSuccess({
|
|
62088
62808
|
command: "timeline add-track",
|
|
62089
|
-
|
|
62090
|
-
|
|
62091
|
-
|
|
62092
|
-
|
|
62809
|
+
startedAt,
|
|
62810
|
+
dryRun: true,
|
|
62811
|
+
data: {
|
|
62812
|
+
params: {
|
|
62813
|
+
project: projectPath,
|
|
62814
|
+
type,
|
|
62815
|
+
name: options.name || null
|
|
62816
|
+
}
|
|
62093
62817
|
}
|
|
62094
62818
|
});
|
|
62095
62819
|
return;
|
|
@@ -62121,20 +62845,24 @@ timelineCommand.command("add-track").description("Add a new track").argument("<p
|
|
|
62121
62845
|
}
|
|
62122
62846
|
});
|
|
62123
62847
|
timelineCommand.command("add-effect").description("Add an effect to a clip").argument("<project>", "Project file path").argument("<clip-id>", "Clip ID").argument("<effect-type>", "Effect type (fadeIn, fadeOut, blur, brightness, contrast, saturation, speed, volume)").option("-s, --start <seconds>", "Effect start time (relative to clip)", "0").option("-d, --duration <seconds>", "Effect duration (defaults to clip duration)").option("-p, --params <json>", "Effect parameters as JSON", "{}").option("--dry-run", "Preview parameters without executing").action(async (projectPath, clipId, effectType, options) => {
|
|
62848
|
+
const startedAt = Date.now();
|
|
62124
62849
|
const spinner2 = ora("Adding effect...").start();
|
|
62125
62850
|
try {
|
|
62126
62851
|
validateResourceId(clipId);
|
|
62127
62852
|
if (options.dryRun) {
|
|
62128
|
-
|
|
62129
|
-
dryRun: true,
|
|
62853
|
+
outputSuccess({
|
|
62130
62854
|
command: "timeline add-effect",
|
|
62131
|
-
|
|
62132
|
-
|
|
62133
|
-
|
|
62134
|
-
|
|
62135
|
-
|
|
62136
|
-
|
|
62137
|
-
|
|
62855
|
+
startedAt,
|
|
62856
|
+
dryRun: true,
|
|
62857
|
+
data: {
|
|
62858
|
+
params: {
|
|
62859
|
+
project: projectPath,
|
|
62860
|
+
clipId,
|
|
62861
|
+
effectType,
|
|
62862
|
+
start: options.start,
|
|
62863
|
+
duration: options.duration || null,
|
|
62864
|
+
params: options.params
|
|
62865
|
+
}
|
|
62138
62866
|
}
|
|
62139
62867
|
});
|
|
62140
62868
|
return;
|
|
@@ -62174,18 +62902,22 @@ timelineCommand.command("add-effect").description("Add an effect to a clip").arg
|
|
|
62174
62902
|
}
|
|
62175
62903
|
});
|
|
62176
62904
|
timelineCommand.command("trim").description("Trim a clip").argument("<project>", "Project file path").argument("<clip-id>", "Clip ID").option("--start <seconds>", "New start time").option("--duration <seconds>", "New duration").option("--dry-run", "Preview parameters without executing").action(async (projectPath, clipId, options) => {
|
|
62905
|
+
const startedAt = Date.now();
|
|
62177
62906
|
const spinner2 = ora("Trimming clip...").start();
|
|
62178
62907
|
try {
|
|
62179
62908
|
validateResourceId(clipId);
|
|
62180
62909
|
if (options.dryRun) {
|
|
62181
|
-
|
|
62182
|
-
dryRun: true,
|
|
62910
|
+
outputSuccess({
|
|
62183
62911
|
command: "timeline trim",
|
|
62184
|
-
|
|
62185
|
-
|
|
62186
|
-
|
|
62187
|
-
|
|
62188
|
-
|
|
62912
|
+
startedAt,
|
|
62913
|
+
dryRun: true,
|
|
62914
|
+
data: {
|
|
62915
|
+
params: {
|
|
62916
|
+
project: projectPath,
|
|
62917
|
+
clipId,
|
|
62918
|
+
start: options.start || null,
|
|
62919
|
+
duration: options.duration || null
|
|
62920
|
+
}
|
|
62189
62921
|
}
|
|
62190
62922
|
});
|
|
62191
62923
|
return;
|
|
@@ -62278,17 +63010,21 @@ timelineCommand.command("list").description("List timeline contents").argument("
|
|
|
62278
63010
|
}
|
|
62279
63011
|
});
|
|
62280
63012
|
timelineCommand.command("split").description("Split a clip at a specific time").argument("<project>", "Project file path").argument("<clip-id>", "Clip ID to split").option("-t, --time <seconds>", "Split time relative to clip start", "0").option("--dry-run", "Preview parameters without executing").action(async (projectPath, clipId, options) => {
|
|
63013
|
+
const startedAt = Date.now();
|
|
62281
63014
|
const spinner2 = ora("Splitting clip...").start();
|
|
62282
63015
|
try {
|
|
62283
63016
|
validateResourceId(clipId);
|
|
62284
63017
|
if (options.dryRun) {
|
|
62285
|
-
|
|
62286
|
-
dryRun: true,
|
|
63018
|
+
outputSuccess({
|
|
62287
63019
|
command: "timeline split",
|
|
62288
|
-
|
|
62289
|
-
|
|
62290
|
-
|
|
62291
|
-
|
|
63020
|
+
startedAt,
|
|
63021
|
+
dryRun: true,
|
|
63022
|
+
data: {
|
|
63023
|
+
params: {
|
|
63024
|
+
project: projectPath,
|
|
63025
|
+
clipId,
|
|
63026
|
+
time: options.time
|
|
63027
|
+
}
|
|
62292
63028
|
}
|
|
62293
63029
|
});
|
|
62294
63030
|
return;
|
|
@@ -62325,17 +63061,21 @@ timelineCommand.command("split").description("Split a clip at a specific time").
|
|
|
62325
63061
|
}
|
|
62326
63062
|
});
|
|
62327
63063
|
timelineCommand.command("duplicate").description("Duplicate a clip").argument("<project>", "Project file path").argument("<clip-id>", "Clip ID to duplicate").option("-t, --time <seconds>", "Start time for duplicate (default: after original)").option("--dry-run", "Preview parameters without executing").action(async (projectPath, clipId, options) => {
|
|
63064
|
+
const startedAt = Date.now();
|
|
62328
63065
|
const spinner2 = ora("Duplicating clip...").start();
|
|
62329
63066
|
try {
|
|
62330
63067
|
validateResourceId(clipId);
|
|
62331
63068
|
if (options.dryRun) {
|
|
62332
|
-
|
|
62333
|
-
dryRun: true,
|
|
63069
|
+
outputSuccess({
|
|
62334
63070
|
command: "timeline duplicate",
|
|
62335
|
-
|
|
62336
|
-
|
|
62337
|
-
|
|
62338
|
-
|
|
63071
|
+
startedAt,
|
|
63072
|
+
dryRun: true,
|
|
63073
|
+
data: {
|
|
63074
|
+
params: {
|
|
63075
|
+
project: projectPath,
|
|
63076
|
+
clipId,
|
|
63077
|
+
time: options.time || null
|
|
63078
|
+
}
|
|
62339
63079
|
}
|
|
62340
63080
|
});
|
|
62341
63081
|
return;
|
|
@@ -62367,16 +63107,20 @@ timelineCommand.command("duplicate").description("Duplicate a clip").argument("<
|
|
|
62367
63107
|
}
|
|
62368
63108
|
});
|
|
62369
63109
|
timelineCommand.command("delete").description("Delete a clip from the timeline").argument("<project>", "Project file path").argument("<clip-id>", "Clip ID to delete").option("--dry-run", "Preview parameters without executing").action(async (projectPath, clipId, options) => {
|
|
63110
|
+
const startedAt = Date.now();
|
|
62370
63111
|
const spinner2 = ora("Deleting clip...").start();
|
|
62371
63112
|
try {
|
|
62372
63113
|
validateResourceId(clipId);
|
|
62373
63114
|
if (options.dryRun) {
|
|
62374
|
-
|
|
62375
|
-
dryRun: true,
|
|
63115
|
+
outputSuccess({
|
|
62376
63116
|
command: "timeline delete",
|
|
62377
|
-
|
|
62378
|
-
|
|
62379
|
-
|
|
63117
|
+
startedAt,
|
|
63118
|
+
dryRun: true,
|
|
63119
|
+
data: {
|
|
63120
|
+
params: {
|
|
63121
|
+
project: projectPath,
|
|
63122
|
+
clipId
|
|
63123
|
+
}
|
|
62380
63124
|
}
|
|
62381
63125
|
});
|
|
62382
63126
|
return;
|
|
@@ -62404,19 +63148,23 @@ timelineCommand.command("delete").description("Delete a clip from the timeline")
|
|
|
62404
63148
|
}
|
|
62405
63149
|
});
|
|
62406
63150
|
timelineCommand.command("move").description("Move a clip to a new position").argument("<project>", "Project file path").argument("<clip-id>", "Clip ID to move").option("-t, --time <seconds>", "New start time").option("--track <track-id>", "Move to different track").option("--dry-run", "Preview parameters without executing").action(async (projectPath, clipId, options) => {
|
|
63151
|
+
const startedAt = Date.now();
|
|
62407
63152
|
const spinner2 = ora("Moving clip...").start();
|
|
62408
63153
|
try {
|
|
62409
63154
|
validateResourceId(clipId);
|
|
62410
63155
|
if (options.track) validateResourceId(options.track);
|
|
62411
63156
|
if (options.dryRun) {
|
|
62412
|
-
|
|
62413
|
-
dryRun: true,
|
|
63157
|
+
outputSuccess({
|
|
62414
63158
|
command: "timeline move",
|
|
62415
|
-
|
|
62416
|
-
|
|
62417
|
-
|
|
62418
|
-
|
|
62419
|
-
|
|
63159
|
+
startedAt,
|
|
63160
|
+
dryRun: true,
|
|
63161
|
+
data: {
|
|
63162
|
+
params: {
|
|
63163
|
+
project: projectPath,
|
|
63164
|
+
clipId,
|
|
63165
|
+
time: options.time || null,
|
|
63166
|
+
track: options.track || null
|
|
63167
|
+
}
|
|
62420
63168
|
}
|
|
62421
63169
|
});
|
|
62422
63170
|
return;
|
|
@@ -62499,6 +63247,7 @@ Run 'vibe schema analyze.<command>' for structured parameter info.
|
|
|
62499
63247
|
`
|
|
62500
63248
|
);
|
|
62501
63249
|
analyzeCommand.command("media").description("Analyze any media: images, videos, or YouTube URLs using Gemini").argument("<source>", "Image/video file path, image URL, or YouTube URL").argument("<prompt>", "Analysis prompt (e.g., 'Describe this image', 'Summarize this video')").option("-k, --api-key <key>", "Google API key (or set GOOGLE_API_KEY env)").option("-m, --model <model>", "Model: flash (default), flash-2.5, pro", "flash").option("--fps <number>", "Frames per second for video (default: 1)").option("--start <seconds>", "Start offset in seconds (video only)").option("--end <seconds>", "End offset in seconds (video only)").option("--low-res", "Use low resolution mode (fewer tokens)").option("-v, --verbose", "Show token usage").option("--fields <fields>", "Comma-separated fields to include in output (e.g., response,model)").action(async (source, prompt3, options) => {
|
|
63250
|
+
const startedAt = Date.now();
|
|
62502
63251
|
try {
|
|
62503
63252
|
rejectControlChars(prompt3);
|
|
62504
63253
|
if (options.apiKey) {
|
|
@@ -62523,15 +63272,17 @@ analyzeCommand.command("media").description("Analyze any media: images, videos,
|
|
|
62523
63272
|
spinner2.succeed(source_default.green("Analysis complete"));
|
|
62524
63273
|
const response = sanitizeLLMResponse(result.response || "");
|
|
62525
63274
|
if (isJsonMode()) {
|
|
62526
|
-
|
|
63275
|
+
const data = { response, sourceType: result.sourceType, model: result.model };
|
|
62527
63276
|
if (result.totalTokens) {
|
|
62528
|
-
|
|
62529
|
-
|
|
62530
|
-
|
|
62531
|
-
|
|
62532
|
-
|
|
62533
|
-
|
|
62534
|
-
|
|
63277
|
+
data.promptTokens = result.promptTokens;
|
|
63278
|
+
data.responseTokens = result.responseTokens;
|
|
63279
|
+
data.totalTokens = result.totalTokens;
|
|
63280
|
+
}
|
|
63281
|
+
outputSuccess({
|
|
63282
|
+
command: "analyze media",
|
|
63283
|
+
startedAt,
|
|
63284
|
+
data
|
|
63285
|
+
});
|
|
62535
63286
|
return;
|
|
62536
63287
|
}
|
|
62537
63288
|
console.log();
|
|
@@ -62554,6 +63305,7 @@ analyzeCommand.command("media").description("Analyze any media: images, videos,
|
|
|
62554
63305
|
}
|
|
62555
63306
|
});
|
|
62556
63307
|
analyzeCommand.command("video").description("Analyze video using Gemini (summarize, Q&A, extract info)").argument("<source>", "Video file path or YouTube URL").argument("<prompt>", "Analysis prompt (e.g., 'Summarize this video')").option("-k, --api-key <key>", "Google API key (or set GOOGLE_API_KEY env)").option("-m, --model <model>", "Model: flash (default), flash-2.5, pro", "flash").option("--fps <number>", "Frames per second (default: 1, higher for action)").option("--start <seconds>", "Start offset in seconds (for clipping)").option("--end <seconds>", "End offset in seconds (for clipping)").option("--low-res", "Use low resolution mode (fewer tokens, longer videos)").option("-v, --verbose", "Show token usage").option("--fields <fields>", "Comma-separated fields to include in output (e.g., response,model)").action(async (source, prompt3, options) => {
|
|
63308
|
+
const startedAt = Date.now();
|
|
62557
63309
|
try {
|
|
62558
63310
|
rejectControlChars(prompt3);
|
|
62559
63311
|
if (options.apiKey) {
|
|
@@ -62578,15 +63330,17 @@ analyzeCommand.command("video").description("Analyze video using Gemini (summari
|
|
|
62578
63330
|
spinner2.succeed(source_default.green("Video analyzed"));
|
|
62579
63331
|
const response = sanitizeLLMResponse(result.response || "");
|
|
62580
63332
|
if (isJsonMode()) {
|
|
62581
|
-
|
|
63333
|
+
const data = { response, model: result.model };
|
|
62582
63334
|
if (result.totalTokens) {
|
|
62583
|
-
|
|
62584
|
-
|
|
62585
|
-
|
|
62586
|
-
|
|
62587
|
-
|
|
62588
|
-
|
|
62589
|
-
|
|
63335
|
+
data.promptTokens = result.promptTokens;
|
|
63336
|
+
data.responseTokens = result.responseTokens;
|
|
63337
|
+
data.totalTokens = result.totalTokens;
|
|
63338
|
+
}
|
|
63339
|
+
outputSuccess({
|
|
63340
|
+
command: "analyze video",
|
|
63341
|
+
startedAt,
|
|
63342
|
+
data
|
|
63343
|
+
});
|
|
62590
63344
|
return;
|
|
62591
63345
|
}
|
|
62592
63346
|
console.log();
|
|
@@ -62609,6 +63363,7 @@ analyzeCommand.command("video").description("Analyze video using Gemini (summari
|
|
|
62609
63363
|
});
|
|
62610
63364
|
registerReviewCommand(analyzeCommand);
|
|
62611
63365
|
analyzeCommand.command("suggest").description("Get AI edit suggestions using Gemini").argument("<project>", "Project file path").argument("<instruction>", "Natural language instruction").option("-k, --api-key <key>", "Google API key (or set GOOGLE_API_KEY env)").option("--apply", "Apply the first suggestion automatically").action(async (projectPath, instruction, options) => {
|
|
63366
|
+
const startedAt = Date.now();
|
|
62612
63367
|
try {
|
|
62613
63368
|
rejectControlChars(instruction);
|
|
62614
63369
|
const apiKey = await requireApiKey("GOOGLE_API_KEY", "Google", options.apiKey);
|
|
@@ -62624,7 +63379,13 @@ analyzeCommand.command("suggest").description("Get AI edit suggestions using Gem
|
|
|
62624
63379
|
const suggestions = await gemini.autoEdit(clips, instruction);
|
|
62625
63380
|
spinner2.succeed(source_default.green(`Found ${suggestions.length} suggestion(s)`));
|
|
62626
63381
|
if (isJsonMode()) {
|
|
62627
|
-
|
|
63382
|
+
outputSuccess({
|
|
63383
|
+
command: "analyze suggest",
|
|
63384
|
+
startedAt,
|
|
63385
|
+
data: {
|
|
63386
|
+
suggestions: suggestions.map((s) => ({ type: s.type, description: s.description, confidence: s.confidence, clipIds: s.clipIds, params: s.params }))
|
|
63387
|
+
}
|
|
63388
|
+
});
|
|
62628
63389
|
return;
|
|
62629
63390
|
}
|
|
62630
63391
|
console.log();
|
|
@@ -62693,6 +63454,7 @@ Run 'vibe schema audio.<command>' for structured parameter info.
|
|
|
62693
63454
|
`
|
|
62694
63455
|
);
|
|
62695
63456
|
audioCommand.command("transcribe").description("Transcribe audio using Whisper").argument("<audio>", "Audio file path").option("-k, --api-key <key>", "OpenAI API key (or set OPENAI_API_KEY env)").option("-l, --language <lang>", "Language code (e.g., en, ko)").option("-o, --output <path>", "Output file path").option("-f, --format <format>", "Output format: json, srt, vtt (auto-detected from extension)").action(async (audioPath, options) => {
|
|
63457
|
+
const startedAt = Date.now();
|
|
62696
63458
|
try {
|
|
62697
63459
|
if (options.output) {
|
|
62698
63460
|
validateOutputPath(options.output);
|
|
@@ -62713,7 +63475,16 @@ audioCommand.command("transcribe").description("Transcribe audio using Whisper")
|
|
|
62713
63475
|
}
|
|
62714
63476
|
spinner2.succeed(source_default.green("Transcription complete"));
|
|
62715
63477
|
if (isJsonMode()) {
|
|
62716
|
-
|
|
63478
|
+
outputSuccess({
|
|
63479
|
+
command: "audio transcribe",
|
|
63480
|
+
startedAt,
|
|
63481
|
+
data: {
|
|
63482
|
+
fullText: result.fullText,
|
|
63483
|
+
segments: result.segments,
|
|
63484
|
+
language: result.detectedLanguage,
|
|
63485
|
+
outputPath: options.output ? resolve37(process.cwd(), options.output) : void 0
|
|
63486
|
+
}
|
|
63487
|
+
});
|
|
62717
63488
|
return;
|
|
62718
63489
|
}
|
|
62719
63490
|
console.log();
|
|
@@ -62743,6 +63514,7 @@ audioCommand.command("transcribe").description("Transcribe audio using Whisper")
|
|
|
62743
63514
|
}
|
|
62744
63515
|
});
|
|
62745
63516
|
audioCommand.command("voices").description("List available ElevenLabs voices").option("-k, --api-key <key>", "ElevenLabs API key (or set ELEVENLABS_API_KEY env)").action(async (options) => {
|
|
63517
|
+
const startedAt = Date.now();
|
|
62746
63518
|
try {
|
|
62747
63519
|
const apiKey = await requireApiKey("ELEVENLABS_API_KEY", "ElevenLabs", options.apiKey);
|
|
62748
63520
|
const spinner2 = ora("Fetching voices...").start();
|
|
@@ -62751,7 +63523,13 @@ audioCommand.command("voices").description("List available ElevenLabs voices").o
|
|
|
62751
63523
|
const voices = await elevenlabs.getVoices();
|
|
62752
63524
|
spinner2.succeed(source_default.green(`Found ${voices.length} voices`));
|
|
62753
63525
|
if (isJsonMode()) {
|
|
62754
|
-
|
|
63526
|
+
outputSuccess({
|
|
63527
|
+
command: "audio voices",
|
|
63528
|
+
startedAt,
|
|
63529
|
+
data: {
|
|
63530
|
+
voices: voices.map((v) => ({ name: v.name, voiceId: v.voice_id, category: v.category, labels: v.labels }))
|
|
63531
|
+
}
|
|
63532
|
+
});
|
|
62755
63533
|
return;
|
|
62756
63534
|
}
|
|
62757
63535
|
console.log();
|
|
@@ -62769,9 +63547,15 @@ audioCommand.command("voices").description("List available ElevenLabs voices").o
|
|
|
62769
63547
|
}
|
|
62770
63548
|
});
|
|
62771
63549
|
audioCommand.command("isolate").description("Isolate vocals from audio using ElevenLabs").argument("<audio>", "Input audio file path").option("-k, --api-key <key>", "ElevenLabs API key (or set ELEVENLABS_API_KEY env)").option("-o, --output <path>", "Output audio file path", "vocals.mp3").option("--dry-run", "Preview parameters without executing").action(async (audioPath, options) => {
|
|
63550
|
+
const startedAt = Date.now();
|
|
62772
63551
|
try {
|
|
62773
63552
|
if (options.dryRun) {
|
|
62774
|
-
|
|
63553
|
+
outputSuccess({
|
|
63554
|
+
command: "audio isolate",
|
|
63555
|
+
startedAt,
|
|
63556
|
+
dryRun: true,
|
|
63557
|
+
data: { params: { audioPath } }
|
|
63558
|
+
});
|
|
62775
63559
|
return;
|
|
62776
63560
|
}
|
|
62777
63561
|
if (options.output) {
|
|
@@ -62793,7 +63577,11 @@ audioCommand.command("isolate").description("Isolate vocals from audio using Ele
|
|
|
62793
63577
|
await writeFile32(outputPath, result.audioBuffer);
|
|
62794
63578
|
spinner2.succeed(source_default.green("Vocals isolated"));
|
|
62795
63579
|
if (isJsonMode()) {
|
|
62796
|
-
|
|
63580
|
+
outputSuccess({
|
|
63581
|
+
command: "audio isolate",
|
|
63582
|
+
startedAt,
|
|
63583
|
+
data: { outputPath }
|
|
63584
|
+
});
|
|
62797
63585
|
return;
|
|
62798
63586
|
}
|
|
62799
63587
|
console.log(source_default.green(`Saved to: ${outputPath}`));
|
|
@@ -62804,9 +63592,15 @@ audioCommand.command("isolate").description("Isolate vocals from audio using Ele
|
|
|
62804
63592
|
}
|
|
62805
63593
|
});
|
|
62806
63594
|
audioCommand.command("voice-clone").description("Clone a voice from audio samples using ElevenLabs").argument("[samples...]", "Audio sample files (1-25 files)").option("-k, --api-key <key>", "ElevenLabs API key (or set ELEVENLABS_API_KEY env)").option("-n, --name <name>", "Voice name (required)").option("-d, --description <desc>", "Voice description").option("--labels <json>", `Labels as JSON (e.g., '{"accent": "american"}')`).option("--remove-noise", "Remove background noise from samples").option("--list", "List all available voices").option("--dry-run", "Preview parameters without executing").action(async (samples, options) => {
|
|
63595
|
+
const startedAt = Date.now();
|
|
62807
63596
|
try {
|
|
62808
63597
|
if (options.dryRun) {
|
|
62809
|
-
|
|
63598
|
+
outputSuccess({
|
|
63599
|
+
command: "audio voice-clone",
|
|
63600
|
+
startedAt,
|
|
63601
|
+
dryRun: true,
|
|
63602
|
+
data: { params: { samples: samples?.length, name: options.name } }
|
|
63603
|
+
});
|
|
62810
63604
|
return;
|
|
62811
63605
|
}
|
|
62812
63606
|
const apiKey = await requireApiKey("ELEVENLABS_API_KEY", "ElevenLabs", options.apiKey);
|
|
@@ -62862,7 +63656,11 @@ audioCommand.command("voice-clone").description("Clone a voice from audio sample
|
|
|
62862
63656
|
}
|
|
62863
63657
|
spinner2.succeed(source_default.green("Voice cloned successfully"));
|
|
62864
63658
|
if (isJsonMode()) {
|
|
62865
|
-
|
|
63659
|
+
outputSuccess({
|
|
63660
|
+
command: "audio voice-clone",
|
|
63661
|
+
startedAt,
|
|
63662
|
+
data: { name: options.name, voiceId: result.voiceId }
|
|
63663
|
+
});
|
|
62866
63664
|
return;
|
|
62867
63665
|
}
|
|
62868
63666
|
console.log();
|
|
@@ -62880,9 +63678,15 @@ audioCommand.command("voice-clone").description("Clone a voice from audio sample
|
|
|
62880
63678
|
}
|
|
62881
63679
|
});
|
|
62882
63680
|
audioCommand.command("dub").description("Dub audio/video to another language (transcribe, translate, TTS)").argument("<media>", "Input media file (video or audio)").option("-l, --language <lang>", "Target language code (e.g., es, ko, ja) (required)").option("--source <lang>", "Source language code (default: auto-detect)").option("-v, --voice <id>", "ElevenLabs voice ID for output").option("--analyze-only", "Only analyze and show timing, don't generate audio").option("-o, --output <path>", "Output file path").option("--dry-run", "Preview parameters without executing").action(async (mediaPath, options) => {
|
|
63681
|
+
const startedAt = Date.now();
|
|
62883
63682
|
try {
|
|
62884
63683
|
if (options.dryRun) {
|
|
62885
|
-
|
|
63684
|
+
outputSuccess({
|
|
63685
|
+
command: "audio dub",
|
|
63686
|
+
startedAt,
|
|
63687
|
+
dryRun: true,
|
|
63688
|
+
data: { params: { mediaPath, targetLanguage: options.language, sourceLanguage: options.source, voice: options.voice } }
|
|
63689
|
+
});
|
|
62886
63690
|
return;
|
|
62887
63691
|
}
|
|
62888
63692
|
if (options.output) {
|
|
@@ -63042,7 +63846,16 @@ ${segmentTexts}`,
|
|
|
63042
63846
|
await writeFile32(finalOutputPath, combinedBuffer);
|
|
63043
63847
|
spinner2.succeed(source_default.green("Dubbing complete"));
|
|
63044
63848
|
if (isJsonMode()) {
|
|
63045
|
-
|
|
63849
|
+
outputSuccess({
|
|
63850
|
+
command: "audio dub",
|
|
63851
|
+
startedAt,
|
|
63852
|
+
data: {
|
|
63853
|
+
sourceLanguage: transcriptResult.detectedLanguage || options.source || "auto",
|
|
63854
|
+
targetLanguage: options.language,
|
|
63855
|
+
segmentCount: translatedSegments.length,
|
|
63856
|
+
outputPath: finalOutputPath
|
|
63857
|
+
}
|
|
63858
|
+
});
|
|
63046
63859
|
return;
|
|
63047
63860
|
}
|
|
63048
63861
|
console.log();
|
|
@@ -63061,13 +63874,19 @@ ${segmentTexts}`,
|
|
|
63061
63874
|
}
|
|
63062
63875
|
});
|
|
63063
63876
|
audioCommand.command("duck").description("Auto-duck background music when voice is present (FFmpeg)").argument("<music>", "Background music file path").option("-v, --voice <path>", "Voice/narration track (required)").option("-o, --output <path>", "Output audio file path").option("-t, --threshold <dB>", "Sidechain threshold in dB", "-30").option("-r, --ratio <ratio>", "Compression ratio", "3").option("-a, --attack <ms>", "Attack time in ms", "20").option("-l, --release <ms>", "Release time in ms", "200").option("--dry-run", "Preview parameters without executing").action(async (musicPath, options) => {
|
|
63877
|
+
const startedAt = Date.now();
|
|
63064
63878
|
try {
|
|
63065
63879
|
if (options.dryRun) {
|
|
63066
63880
|
const threshold = parseFloat(options.threshold);
|
|
63067
63881
|
const ratio2 = parseFloat(options.ratio);
|
|
63068
63882
|
const attack2 = parseFloat(options.attack);
|
|
63069
63883
|
const release2 = parseFloat(options.release);
|
|
63070
|
-
|
|
63884
|
+
outputSuccess({
|
|
63885
|
+
command: "audio duck",
|
|
63886
|
+
startedAt,
|
|
63887
|
+
dryRun: true,
|
|
63888
|
+
data: { params: { musicPath, voicePath: options.voice, threshold, ratio: ratio2, attack: attack2, release: release2 } }
|
|
63889
|
+
});
|
|
63071
63890
|
return;
|
|
63072
63891
|
}
|
|
63073
63892
|
if (options.output) {
|
|
@@ -63092,7 +63911,11 @@ audioCommand.command("duck").description("Auto-duck background music when voice
|
|
|
63092
63911
|
await execSafe("ffmpeg", ["-i", absMusic, "-i", absVoice, "-filter_complex", filterComplex, "-map", "[out]", outputPath, "-y"]);
|
|
63093
63912
|
spinner2.succeed(source_default.green("Audio ducking complete"));
|
|
63094
63913
|
if (isJsonMode()) {
|
|
63095
|
-
|
|
63914
|
+
outputSuccess({
|
|
63915
|
+
command: "audio duck",
|
|
63916
|
+
startedAt,
|
|
63917
|
+
data: { musicPath: absMusic, voicePath: options.voice, threshold: thresholdDb, ratio, outputPath }
|
|
63918
|
+
});
|
|
63096
63919
|
return;
|
|
63097
63920
|
}
|
|
63098
63921
|
console.log();
|
|
@@ -63130,6 +63953,7 @@ import { resolve as resolve38 } from "node:path";
|
|
|
63130
63953
|
import { existsSync as existsSync29 } from "node:fs";
|
|
63131
63954
|
function registerScriptPipelineCommands(aiCommand) {
|
|
63132
63955
|
aiCommand.command("regenerate-scene").description("Regenerate a specific scene in a script-to-video output directory").argument("<project-dir>", "Path to the script-to-video output directory").requiredOption("--scene <numbers>", "Scene number(s) to regenerate (1-based), e.g., 3 or 3,4,5").option("--video-only", "Only regenerate video").option("--narration-only", "Only regenerate narration").option("--image-only", "Only regenerate image").option("-g, --generator <engine>", "Video generator: grok | kling | runway | veo", "grok").option("-i, --image-provider <provider>", "Image provider: gemini | openai | grok", "gemini").option("-v, --voice <id>", "ElevenLabs voice ID for narration").option("-a, --aspect-ratio <ratio>", "Aspect ratio: 16:9 | 9:16 | 1:1", "16:9").option("--retries <count>", "Number of retries for video generation failures", String(DEFAULT_VIDEO_RETRIES)).option("--reference-scene <num>", "Use another scene's image as reference for character consistency").option("--dry-run", "Preview parameters without executing").action(async (projectDir, options) => {
|
|
63956
|
+
const startedAt = Date.now();
|
|
63133
63957
|
try {
|
|
63134
63958
|
const outputDir = resolve38(process.cwd(), projectDir);
|
|
63135
63959
|
const projectPath = resolve38(outputDir, "project.vibe.json");
|
|
@@ -63148,20 +63972,23 @@ function registerScriptPipelineCommands(aiCommand) {
|
|
|
63148
63972
|
exitWithError(usageError("Scene number must be a positive integer (1-based)", "e.g., --scene 3 or --scene 3,4,5"));
|
|
63149
63973
|
}
|
|
63150
63974
|
if (options.dryRun) {
|
|
63151
|
-
|
|
63152
|
-
dryRun: true,
|
|
63975
|
+
outputSuccess({
|
|
63153
63976
|
command: "pipeline regenerate-scene",
|
|
63154
|
-
|
|
63155
|
-
|
|
63156
|
-
|
|
63157
|
-
|
|
63158
|
-
|
|
63159
|
-
|
|
63160
|
-
|
|
63161
|
-
|
|
63162
|
-
|
|
63163
|
-
|
|
63164
|
-
|
|
63977
|
+
startedAt,
|
|
63978
|
+
dryRun: true,
|
|
63979
|
+
data: {
|
|
63980
|
+
params: {
|
|
63981
|
+
projectDir,
|
|
63982
|
+
scene: sceneNums,
|
|
63983
|
+
videoOnly: options.videoOnly ?? false,
|
|
63984
|
+
narrationOnly: options.narrationOnly ?? false,
|
|
63985
|
+
imageOnly: options.imageOnly ?? false,
|
|
63986
|
+
generator: options.generator,
|
|
63987
|
+
imageProvider: options.imageProvider,
|
|
63988
|
+
aspectRatio: options.aspectRatio,
|
|
63989
|
+
retries: options.retries,
|
|
63990
|
+
referenceScene: options.referenceScene
|
|
63991
|
+
}
|
|
63165
63992
|
}
|
|
63166
63993
|
});
|
|
63167
63994
|
return;
|
|
@@ -63356,6 +64183,7 @@ Styles:
|
|
|
63356
64183
|
Required API Key: OPENAI_API_KEY (Whisper transcription)
|
|
63357
64184
|
`
|
|
63358
64185
|
).action(async (videoPath, options) => {
|
|
64186
|
+
const startedAt = Date.now();
|
|
63359
64187
|
try {
|
|
63360
64188
|
const absVideoPath = resolve41(process.cwd(), videoPath);
|
|
63361
64189
|
if (!existsSync32(absVideoPath)) {
|
|
@@ -63366,20 +64194,23 @@ Required API Key: OPENAI_API_KEY (Whisper transcription)
|
|
|
63366
64194
|
}
|
|
63367
64195
|
const outputFile = options.output || videoPath.replace(/(\.\w+)$/, "-captioned$1");
|
|
63368
64196
|
if (options.dryRun) {
|
|
63369
|
-
|
|
63370
|
-
dryRun: true,
|
|
64197
|
+
outputSuccess({
|
|
63371
64198
|
command: "pipeline animated-caption",
|
|
63372
|
-
|
|
63373
|
-
|
|
63374
|
-
|
|
63375
|
-
|
|
63376
|
-
|
|
63377
|
-
|
|
63378
|
-
|
|
63379
|
-
|
|
63380
|
-
|
|
63381
|
-
|
|
63382
|
-
|
|
64199
|
+
startedAt,
|
|
64200
|
+
dryRun: true,
|
|
64201
|
+
data: {
|
|
64202
|
+
params: {
|
|
64203
|
+
videoPath: absVideoPath,
|
|
64204
|
+
outputPath: outputFile,
|
|
64205
|
+
style: options.style,
|
|
64206
|
+
highlightColor: options.highlightColor,
|
|
64207
|
+
fontSize: options.fontSize ? parseInt(options.fontSize) : "auto",
|
|
64208
|
+
position: options.position,
|
|
64209
|
+
wordsPerGroup: options.wordsPerGroup ? parseInt(options.wordsPerGroup) : "auto",
|
|
64210
|
+
maxChars: options.maxChars ? parseInt(options.maxChars) : "auto",
|
|
64211
|
+
language: options.language || "auto",
|
|
64212
|
+
fast: !!options.fast
|
|
64213
|
+
}
|
|
63383
64214
|
}
|
|
63384
64215
|
});
|
|
63385
64216
|
return;
|
|
@@ -63410,13 +64241,16 @@ Required API Key: OPENAI_API_KEY (Whisper transcription)
|
|
|
63410
64241
|
}
|
|
63411
64242
|
spinner2.succeed(source_default.green("Animated captions applied successfully"));
|
|
63412
64243
|
if (isJsonMode()) {
|
|
63413
|
-
|
|
63414
|
-
|
|
63415
|
-
|
|
63416
|
-
|
|
63417
|
-
|
|
63418
|
-
|
|
63419
|
-
|
|
64244
|
+
outputSuccess({
|
|
64245
|
+
command: "pipeline animated-caption",
|
|
64246
|
+
startedAt,
|
|
64247
|
+
data: {
|
|
64248
|
+
outputPath: result.outputPath,
|
|
64249
|
+
wordCount: result.wordCount,
|
|
64250
|
+
groupCount: result.groupCount,
|
|
64251
|
+
style: result.style,
|
|
64252
|
+
tier: result.tier
|
|
64253
|
+
}
|
|
63420
64254
|
});
|
|
63421
64255
|
return;
|
|
63422
64256
|
}
|
|
@@ -67684,18 +68518,22 @@ function isMediaFile(filePath) {
|
|
|
67684
68518
|
return mediaExts.includes(ext);
|
|
67685
68519
|
}
|
|
67686
68520
|
batchCommand.command("import").description("Import multiple media files from a directory").argument("<project>", "Project file path").argument("<directory>", "Directory containing media files").option("-r, --recursive", "Search subdirectories", false).option("-d, --duration <seconds>", "Default duration for images", "5").option("--filter <pattern>", "Filter files by extension (e.g., '.mp4,.mov')").option("--dry-run", "Preview parameters without executing").action(async (projectPath, directory, options) => {
|
|
68521
|
+
const startedAt = Date.now();
|
|
67687
68522
|
const spinner2 = ora("Scanning directory...").start();
|
|
67688
68523
|
try {
|
|
67689
68524
|
if (options.dryRun) {
|
|
67690
|
-
|
|
67691
|
-
dryRun: true,
|
|
68525
|
+
outputSuccess({
|
|
67692
68526
|
command: "batch import",
|
|
67693
|
-
|
|
67694
|
-
|
|
67695
|
-
|
|
67696
|
-
|
|
67697
|
-
|
|
67698
|
-
|
|
68527
|
+
startedAt,
|
|
68528
|
+
dryRun: true,
|
|
68529
|
+
data: {
|
|
68530
|
+
params: {
|
|
68531
|
+
project: projectPath,
|
|
68532
|
+
directory,
|
|
68533
|
+
recursive: options.recursive,
|
|
68534
|
+
duration: options.duration,
|
|
68535
|
+
filter: options.filter || null
|
|
68536
|
+
}
|
|
67699
68537
|
}
|
|
67700
68538
|
});
|
|
67701
68539
|
return;
|
|
@@ -67757,19 +68595,23 @@ batchCommand.command("import").description("Import multiple media files from a d
|
|
|
67757
68595
|
}
|
|
67758
68596
|
});
|
|
67759
68597
|
batchCommand.command("concat").description("Concatenate multiple sources into sequential clips").argument("<project>", "Project file path").argument("[source-ids...]", "Source IDs to concatenate (or --all)").option("--all", "Concatenate all sources in order", false).option("--track <track-id>", "Track to place clips on").option("--start <seconds>", "Starting time", "0").option("--gap <seconds>", "Gap between clips", "0").option("--dry-run", "Preview parameters without executing").action(async (projectPath, sourceIds, options) => {
|
|
68598
|
+
const startedAt = Date.now();
|
|
67760
68599
|
const spinner2 = ora("Creating clips...").start();
|
|
67761
68600
|
try {
|
|
67762
68601
|
if (options.dryRun) {
|
|
67763
|
-
|
|
67764
|
-
dryRun: true,
|
|
68602
|
+
outputSuccess({
|
|
67765
68603
|
command: "batch concat",
|
|
67766
|
-
|
|
67767
|
-
|
|
67768
|
-
|
|
67769
|
-
|
|
67770
|
-
|
|
67771
|
-
|
|
67772
|
-
|
|
68604
|
+
startedAt,
|
|
68605
|
+
dryRun: true,
|
|
68606
|
+
data: {
|
|
68607
|
+
params: {
|
|
68608
|
+
project: projectPath,
|
|
68609
|
+
sourceIds,
|
|
68610
|
+
all: options.all,
|
|
68611
|
+
track: options.track || null,
|
|
68612
|
+
start: options.start,
|
|
68613
|
+
gap: options.gap
|
|
68614
|
+
}
|
|
67773
68615
|
}
|
|
67774
68616
|
});
|
|
67775
68617
|
return;
|
|
@@ -67831,20 +68673,24 @@ batchCommand.command("concat").description("Concatenate multiple sources into se
|
|
|
67831
68673
|
});
|
|
67832
68674
|
batchCommand.command("apply-effect").description("Apply an effect to multiple clips").argument("<project>", "Project file path").argument("<effect-type>", "Effect type (fadeIn, fadeOut, blur, etc.)").argument("[clip-ids...]", "Clip IDs to apply effect to (or --all)").option("--all", "Apply to all clips", false).option("-d, --duration <seconds>", "Effect duration", "1").option("-s, --start <seconds>", "Effect start time (relative to clip)", "0").option("--intensity <value>", "Effect intensity (0-1)", "1").option("--dry-run", "Preview parameters without executing").action(
|
|
67833
68675
|
async (projectPath, effectType, clipIds, options) => {
|
|
68676
|
+
const startedAt = Date.now();
|
|
67834
68677
|
const spinner2 = ora("Applying effects...").start();
|
|
67835
68678
|
try {
|
|
67836
68679
|
if (options.dryRun) {
|
|
67837
|
-
|
|
67838
|
-
dryRun: true,
|
|
68680
|
+
outputSuccess({
|
|
67839
68681
|
command: "batch apply-effect",
|
|
67840
|
-
|
|
67841
|
-
|
|
67842
|
-
|
|
67843
|
-
|
|
67844
|
-
|
|
67845
|
-
|
|
67846
|
-
|
|
67847
|
-
|
|
68682
|
+
startedAt,
|
|
68683
|
+
dryRun: true,
|
|
68684
|
+
data: {
|
|
68685
|
+
params: {
|
|
68686
|
+
project: projectPath,
|
|
68687
|
+
effectType,
|
|
68688
|
+
clipIds,
|
|
68689
|
+
all: options.all,
|
|
68690
|
+
duration: options.duration,
|
|
68691
|
+
start: options.start,
|
|
68692
|
+
intensity: options.intensity
|
|
68693
|
+
}
|
|
67848
68694
|
}
|
|
67849
68695
|
});
|
|
67850
68696
|
return;
|
|
@@ -67898,17 +68744,21 @@ batchCommand.command("apply-effect").description("Apply an effect to multiple cl
|
|
|
67898
68744
|
}
|
|
67899
68745
|
);
|
|
67900
68746
|
batchCommand.command("remove-clips").description("Remove multiple clips from the timeline").argument("<project>", "Project file path").argument("[clip-ids...]", "Clip IDs to remove").option("--all", "Remove all clips", false).option("--track <track-id>", "Remove clips from specific track only").option("--dry-run", "Preview parameters without executing").action(async (projectPath, clipIds, options) => {
|
|
68747
|
+
const startedAt = Date.now();
|
|
67901
68748
|
const spinner2 = ora("Removing clips...").start();
|
|
67902
68749
|
try {
|
|
67903
68750
|
if (options.dryRun) {
|
|
67904
|
-
|
|
67905
|
-
dryRun: true,
|
|
68751
|
+
outputSuccess({
|
|
67906
68752
|
command: "batch remove-clips",
|
|
67907
|
-
|
|
67908
|
-
|
|
67909
|
-
|
|
67910
|
-
|
|
67911
|
-
|
|
68753
|
+
startedAt,
|
|
68754
|
+
dryRun: true,
|
|
68755
|
+
data: {
|
|
68756
|
+
params: {
|
|
68757
|
+
project: projectPath,
|
|
68758
|
+
clipIds,
|
|
68759
|
+
all: options.all,
|
|
68760
|
+
track: options.track || null
|
|
68761
|
+
}
|
|
67912
68762
|
}
|
|
67913
68763
|
});
|
|
67914
68764
|
return;
|
|
@@ -68432,6 +69282,27 @@ subcommands for video generation, editing, analysis, and pipeline
|
|
|
68432
69282
|
orchestration. Cost tiers below \u2014 **always confirm with the user before
|
|
68433
69283
|
running High / Very High tier commands**.
|
|
68434
69284
|
|
|
69285
|
+
## Agent host coverage
|
|
69286
|
+
|
|
69287
|
+
VibeFrame's CLI works with any bash-capable AI coding agent. Six host
|
|
69288
|
+
families have first-class auto-detection and scaffolds today:
|
|
69289
|
+
|
|
69290
|
+
- **Claude Code** \u2014 \`CLAUDE.md\` (this file's import wrapper) +
|
|
69291
|
+
\`AGENTS.md\` + \`/vibe-*\` slash commands.
|
|
69292
|
+
- **OpenAI Codex** \u2014 reads \`AGENTS.md\` natively (agents.md spec).
|
|
69293
|
+
- **Cursor** \u2014 reads \`AGENTS.md\` plus \`.cursor/rules/\` for project
|
|
69294
|
+
rules; supports MCP via \`.cursor/mcp.json\`.
|
|
69295
|
+
- **Aider** \u2014 reads \`AGENTS.md\` (binary-only detection by VibeFrame).
|
|
69296
|
+
- **Gemini CLI** \u2014 primary context file is \`GEMINI.md\`; also reads
|
|
69297
|
+
\`AGENTS.md\` if present.
|
|
69298
|
+
- **OpenCode** \u2014 reads \`AGENTS.md\` (officially supported per
|
|
69299
|
+
\`opencode.ai/docs/rules\`); supports MCP via \`.opencode/mcp.json\`.
|
|
69300
|
+
|
|
69301
|
+
Run \`vibe doctor\` to see which host(s) the CLI detects in your
|
|
69302
|
+
environment. Anyone running another bash-capable agent (Continue,
|
|
69303
|
+
Sourcegraph Cody, etc.) still gets this \`AGENTS.md\` as the universal
|
|
69304
|
+
fallback.
|
|
69305
|
+
|
|
68435
69306
|
## Self-discovery
|
|
68436
69307
|
|
|
68437
69308
|
Don't memorise the command surface \u2014 read it from the CLI:
|
|
@@ -68441,6 +69312,8 @@ vibe --help # command groups overview
|
|
|
68441
69312
|
vibe schema --list # full machine-readable catalog (80+)
|
|
68442
69313
|
vibe schema generate.video # JSON Schema for any single command
|
|
68443
69314
|
vibe doctor # available providers + system health
|
|
69315
|
+
vibe walkthrough scene # step-by-step authoring guide (scene)
|
|
69316
|
+
vibe walkthrough pipeline # step-by-step authoring guide (pipeline)
|
|
68444
69317
|
\`\`\`
|
|
68445
69318
|
|
|
68446
69319
|
## Two flows \u2014 pick by intent
|
|
@@ -68570,7 +69443,7 @@ var CLAUDE_MD = `@AGENTS.md
|
|
|
68570
69443
|
|
|
68571
69444
|
This file imports \`AGENTS.md\` (above) \u2014 that's the canonical source.
|
|
68572
69445
|
Add anything Claude-Code-specific *below*; everything generic should
|
|
68573
|
-
stay in AGENTS.md so other agent hosts (Codex, Cursor, Aider) see it
|
|
69446
|
+
stay in AGENTS.md so other agent hosts (Codex, Cursor, Aider, Gemini CLI, OpenCode) see it
|
|
68574
69447
|
too.
|
|
68575
69448
|
|
|
68576
69449
|
## Skills
|
|
@@ -68593,6 +69466,20 @@ curl -fsSL https://raw.githubusercontent.com/vericontext/vibeframe/main/scripts/
|
|
|
68593
69466
|
<!-- Add Claude-specific notes here: which sub-agents to prefer, custom
|
|
68594
69467
|
slash commands you've added, conversational style preferences, etc. -->
|
|
68595
69468
|
`;
|
|
69469
|
+
var GEMINI_MD = `@AGENTS.md
|
|
69470
|
+
|
|
69471
|
+
# Gemini CLI overrides
|
|
69472
|
+
|
|
69473
|
+
This project's canonical agent guidance lives in \`AGENTS.md\` \u2014 read
|
|
69474
|
+
it first. The \`@AGENTS.md\` line above is the import marker; if
|
|
69475
|
+
Gemini CLI doesn't inline imported files in your version, open
|
|
69476
|
+
\`AGENTS.md\` directly. Both files are kept in sync by \`vibe init\`.
|
|
69477
|
+
|
|
69478
|
+
## Project-specific guidance
|
|
69479
|
+
|
|
69480
|
+
<!-- Add Gemini-CLI-specific notes here: any conventions specific to
|
|
69481
|
+
how you drive vibe from Gemini CLI (preferred models, tone, etc.). -->
|
|
69482
|
+
`;
|
|
68596
69483
|
function renderEnvExample(opts = {}) {
|
|
68597
69484
|
const fallback2 = opts.withLocalFallbackHeader === false ? "" : `# \u2500\u2500 Local fallbacks (no key needed) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
68598
69485
|
# Kokoro TTS (local, ~330MB on first call) \u2192 works without ELEVENLABS_API_KEY
|
|
@@ -68651,8 +69538,9 @@ defaults:
|
|
|
68651
69538
|
|
|
68652
69539
|
// src/commands/init.ts
|
|
68653
69540
|
init_output();
|
|
68654
|
-
var VALID_AGENTS = ["claude-code", "codex", "cursor", "aider", "all", "auto"];
|
|
69541
|
+
var VALID_AGENTS = ["claude-code", "codex", "cursor", "aider", "gemini-cli", "opencode", "all", "auto"];
|
|
68655
69542
|
var initCommand = new Command("init").description("Scaffold project-scope agent files (AGENTS.md / CLAUDE.md / .env.example / .gitignore)").argument("[project-dir]", "Project directory (defaults to cwd)", ".").option("--agent <id>", `Agent target: ${VALID_AGENTS.join(" | ")}`, "auto").option("--force", "Overwrite existing files instead of skipping").option("--dry-run", "Print the file list without writing anything").action(async (projectDirArg, options) => {
|
|
69543
|
+
const startedAt = Date.now();
|
|
68656
69544
|
const agent = options.agent;
|
|
68657
69545
|
if (!VALID_AGENTS.includes(agent)) {
|
|
68658
69546
|
exitWithError(usageError(`Invalid --agent: ${agent}`, `Must be one of: ${VALID_AGENTS.join(", ")}`));
|
|
@@ -68661,6 +69549,7 @@ var initCommand = new Command("init").description("Scaffold project-scope agent
|
|
|
68661
69549
|
const projectName = basename19(projectDir);
|
|
68662
69550
|
const targetHosts = resolveTargets(agent);
|
|
68663
69551
|
const wantsClaude = targetHosts.includes("claude-code");
|
|
69552
|
+
const wantsGemini = targetHosts.includes("gemini-cli");
|
|
68664
69553
|
const wantsCrossTool = targetHosts.some((h) => h !== "claude-code");
|
|
68665
69554
|
const actions = [];
|
|
68666
69555
|
if (!existsSync34(projectDir)) {
|
|
@@ -68686,6 +69575,14 @@ var initCommand = new Command("init").description("Scaffold project-scope agent
|
|
|
68686
69575
|
options.dryRun
|
|
68687
69576
|
));
|
|
68688
69577
|
}
|
|
69578
|
+
if (wantsGemini) {
|
|
69579
|
+
actions.push(await writeIfMissing(
|
|
69580
|
+
resolve48(projectDir, "GEMINI.md"),
|
|
69581
|
+
GEMINI_MD,
|
|
69582
|
+
options.force,
|
|
69583
|
+
options.dryRun
|
|
69584
|
+
));
|
|
69585
|
+
}
|
|
68689
69586
|
actions.push(await writeIfMissing(
|
|
68690
69587
|
resolve48(projectDir, ".env.example"),
|
|
68691
69588
|
renderEnvExample(),
|
|
@@ -68704,13 +69601,16 @@ var initCommand = new Command("init").description("Scaffold project-scope agent
|
|
|
68704
69601
|
options.dryRun
|
|
68705
69602
|
));
|
|
68706
69603
|
if (isJsonMode()) {
|
|
68707
|
-
|
|
69604
|
+
outputSuccess({
|
|
68708
69605
|
command: "init",
|
|
68709
|
-
|
|
68710
|
-
|
|
68711
|
-
|
|
68712
|
-
|
|
68713
|
-
|
|
69606
|
+
startedAt,
|
|
69607
|
+
...options.dryRun ? { dryRun: true } : {},
|
|
69608
|
+
data: {
|
|
69609
|
+
projectDir,
|
|
69610
|
+
agent,
|
|
69611
|
+
targetHosts,
|
|
69612
|
+
actions
|
|
69613
|
+
}
|
|
68714
69614
|
});
|
|
68715
69615
|
return;
|
|
68716
69616
|
}
|
|
@@ -68744,7 +69644,7 @@ var initCommand = new Command("init").description("Scaffold project-scope agent
|
|
|
68744
69644
|
});
|
|
68745
69645
|
function resolveTargets(agent) {
|
|
68746
69646
|
if (agent === "all") {
|
|
68747
|
-
return ["claude-code", "codex", "cursor", "aider"];
|
|
69647
|
+
return ["claude-code", "codex", "cursor", "aider", "gemini-cli", "opencode"];
|
|
68748
69648
|
}
|
|
68749
69649
|
if (agent === "auto") {
|
|
68750
69650
|
const detected = detectedAgentHosts().map((h) => h.id);
|
|
@@ -68834,14 +69734,15 @@ Examples:
|
|
|
68834
69734
|
$ vibe doctor --json Machine-readable output
|
|
68835
69735
|
`
|
|
68836
69736
|
).action(async (options) => {
|
|
69737
|
+
const startedAt = Date.now();
|
|
68837
69738
|
if (options.json) process.env.VIBE_JSON_OUTPUT = "1";
|
|
68838
69739
|
const isJson = process.env.VIBE_JSON_OUTPUT === "1";
|
|
68839
69740
|
const results = await runDiagnostics();
|
|
68840
69741
|
if (isJson) {
|
|
68841
|
-
|
|
68842
|
-
success: true,
|
|
69742
|
+
outputSuccess({
|
|
68843
69743
|
command: "doctor",
|
|
68844
|
-
|
|
69744
|
+
startedAt,
|
|
69745
|
+
data: { ...results }
|
|
68845
69746
|
});
|
|
68846
69747
|
return;
|
|
68847
69748
|
}
|
|
@@ -69213,6 +70114,7 @@ async function generateTestVideo(outputPath) {
|
|
|
69213
70114
|
}
|
|
69214
70115
|
}
|
|
69215
70116
|
var demoCommand = new Command("demo").description("Run sample edits on a test video (no API keys needed)").option("--keep", "Keep demo output files after completion").option("--json", "Output results as JSON").action(async (options) => {
|
|
70117
|
+
const startedAt = Date.now();
|
|
69216
70118
|
if (options.json) process.env.VIBE_JSON_OUTPUT = "1";
|
|
69217
70119
|
const isJson = isJsonMode() || !process.stdin.isTTY;
|
|
69218
70120
|
if (!commandExists("ffmpeg")) {
|
|
@@ -69312,11 +70214,16 @@ var demoCommand = new Command("demo").description("Run sample edits on a test vi
|
|
|
69312
70214
|
const passed = results.filter((r) => r.success).length;
|
|
69313
70215
|
const total = results.length;
|
|
69314
70216
|
if (isJson) {
|
|
69315
|
-
|
|
69316
|
-
success: passed === total,
|
|
70217
|
+
outputSuccess({
|
|
69317
70218
|
command: "demo",
|
|
69318
|
-
|
|
69319
|
-
|
|
70219
|
+
startedAt,
|
|
70220
|
+
data: {
|
|
70221
|
+
allPassed: passed === total,
|
|
70222
|
+
passed,
|
|
70223
|
+
total,
|
|
70224
|
+
steps: results,
|
|
70225
|
+
demoDir: DEMO_DIR
|
|
70226
|
+
}
|
|
69320
70227
|
});
|
|
69321
70228
|
} else {
|
|
69322
70229
|
console.log();
|
|
@@ -69429,6 +70336,7 @@ Variable references: $step_id.output, $step_id.data.field, \${ENV_VAR}
|
|
|
69429
70336
|
Cost: Depends on steps. Use --dry-run to preview before executing.
|
|
69430
70337
|
Run 'vibe schema run' for structured parameter info.
|
|
69431
70338
|
`).action(async (pipelinePath, options) => {
|
|
70339
|
+
const startedAt = Date.now();
|
|
69432
70340
|
loadEnv();
|
|
69433
70341
|
const isJson = options.json || process.env.VIBE_JSON_OUTPUT === "1";
|
|
69434
70342
|
const cliBudget = {};
|
|
@@ -69461,13 +70369,16 @@ Run 'vibe schema run' for structured parameter info.
|
|
|
69461
70369
|
budget: hasBudgetFlag ? cliBudget : void 0
|
|
69462
70370
|
});
|
|
69463
70371
|
if (isJson) {
|
|
69464
|
-
|
|
69465
|
-
dryRun: true,
|
|
70372
|
+
outputSuccess({
|
|
69466
70373
|
command: "run",
|
|
69467
|
-
|
|
69468
|
-
|
|
69469
|
-
|
|
69470
|
-
|
|
70374
|
+
startedAt,
|
|
70375
|
+
dryRun: true,
|
|
70376
|
+
data: {
|
|
70377
|
+
name: manifest2.name,
|
|
70378
|
+
steps: result2.steps.map((s) => ({ id: s.id, action: s.action, estimatedCost: s.data?.estimatedCost })),
|
|
70379
|
+
totalSteps: result2.totalSteps,
|
|
70380
|
+
budget: result2.budget
|
|
70381
|
+
}
|
|
69471
70382
|
});
|
|
69472
70383
|
} else {
|
|
69473
70384
|
console.log(source_default.bold(" Execution Plan:"));
|
|
@@ -69499,24 +70410,27 @@ Run 'vibe schema run' for structured parameter info.
|
|
|
69499
70410
|
});
|
|
69500
70411
|
spinner2?.stop();
|
|
69501
70412
|
if (isJson) {
|
|
69502
|
-
|
|
69503
|
-
|
|
69504
|
-
|
|
69505
|
-
|
|
69506
|
-
|
|
69507
|
-
|
|
69508
|
-
|
|
69509
|
-
|
|
69510
|
-
|
|
69511
|
-
|
|
69512
|
-
|
|
69513
|
-
|
|
69514
|
-
|
|
69515
|
-
|
|
69516
|
-
|
|
69517
|
-
|
|
69518
|
-
|
|
69519
|
-
|
|
70413
|
+
outputSuccess({
|
|
70414
|
+
command: "run",
|
|
70415
|
+
startedAt,
|
|
70416
|
+
data: {
|
|
70417
|
+
name: result.name,
|
|
70418
|
+
error: result.error,
|
|
70419
|
+
completedSteps: result.completedSteps,
|
|
70420
|
+
totalSteps: result.totalSteps,
|
|
70421
|
+
totalDuration: result.totalDuration,
|
|
70422
|
+
outputDir: result.outputDir,
|
|
70423
|
+
budget: result.budget,
|
|
70424
|
+
steps: result.steps.map((s) => ({
|
|
70425
|
+
id: s.id,
|
|
70426
|
+
action: s.action,
|
|
70427
|
+
success: s.success,
|
|
70428
|
+
output: s.output,
|
|
70429
|
+
duration: s.duration,
|
|
70430
|
+
error: s.error,
|
|
70431
|
+
data: s.data
|
|
70432
|
+
}))
|
|
70433
|
+
}
|
|
69520
70434
|
});
|
|
69521
70435
|
} else {
|
|
69522
70436
|
console.log();
|
|
@@ -70301,6 +71215,81 @@ function getEnvVar(provider) {
|
|
|
70301
71215
|
return envVars[provider] || "API_KEY";
|
|
70302
71216
|
}
|
|
70303
71217
|
|
|
71218
|
+
// src/commands/walkthrough.ts
|
|
71219
|
+
init_esm();
|
|
71220
|
+
init_source();
|
|
71221
|
+
init_walkthroughs();
|
|
71222
|
+
init_output();
|
|
71223
|
+
var walkthroughCommand = new Command("walkthrough").description("Step-by-step authoring guide for a vibe workflow (universal /vibe-* slash-command equivalent)").argument("[topic]", `Walkthrough topic: ${WALKTHROUGH_TOPICS.join(" | ")}. Omit to list all.`).option("--list", "List available walkthroughs and exit").action(async (topicArg, options) => {
|
|
71224
|
+
const startedAt = Date.now();
|
|
71225
|
+
if (!topicArg || options.list) {
|
|
71226
|
+
const topics = listWalkthroughs();
|
|
71227
|
+
if (isJsonMode()) {
|
|
71228
|
+
outputSuccess({
|
|
71229
|
+
command: "walkthrough",
|
|
71230
|
+
startedAt,
|
|
71231
|
+
data: {
|
|
71232
|
+
action: "list",
|
|
71233
|
+
topics
|
|
71234
|
+
}
|
|
71235
|
+
});
|
|
71236
|
+
return;
|
|
71237
|
+
}
|
|
71238
|
+
console.log();
|
|
71239
|
+
console.log(source_default.bold.cyan("Available walkthroughs"));
|
|
71240
|
+
console.log(source_default.dim("\u2500".repeat(60)));
|
|
71241
|
+
for (const t of topics) {
|
|
71242
|
+
console.log(` ${source_default.bold(t.topic.padEnd(10))} ${source_default.dim(t.summary)}`);
|
|
71243
|
+
}
|
|
71244
|
+
console.log();
|
|
71245
|
+
console.log(source_default.dim("Run `vibe walkthrough <topic>` for the full guide."));
|
|
71246
|
+
console.log(source_default.dim("Add `--json` to get structured output for an agent host."));
|
|
71247
|
+
console.log();
|
|
71248
|
+
return;
|
|
71249
|
+
}
|
|
71250
|
+
if (!isWalkthroughTopic(topicArg)) {
|
|
71251
|
+
exitWithError(usageError(
|
|
71252
|
+
`Unknown walkthrough topic: ${topicArg}`,
|
|
71253
|
+
`Valid topics: ${WALKTHROUGH_TOPICS.join(", ")}`
|
|
71254
|
+
));
|
|
71255
|
+
}
|
|
71256
|
+
const topic = topicArg;
|
|
71257
|
+
const result = loadWalkthrough(topic);
|
|
71258
|
+
if (isJsonMode()) {
|
|
71259
|
+
outputSuccess({
|
|
71260
|
+
command: "walkthrough",
|
|
71261
|
+
startedAt,
|
|
71262
|
+
data: {
|
|
71263
|
+
action: "show",
|
|
71264
|
+
...result
|
|
71265
|
+
}
|
|
71266
|
+
});
|
|
71267
|
+
return;
|
|
71268
|
+
}
|
|
71269
|
+
console.log();
|
|
71270
|
+
console.log(source_default.bold.cyan(result.title));
|
|
71271
|
+
console.log(source_default.dim("\u2500".repeat(60)));
|
|
71272
|
+
console.log(source_default.dim(result.summary));
|
|
71273
|
+
console.log();
|
|
71274
|
+
console.log(source_default.bold("Quick steps"));
|
|
71275
|
+
console.log(source_default.dim("\u2500".repeat(60)));
|
|
71276
|
+
for (let i = 0; i < result.steps.length; i++) {
|
|
71277
|
+
console.log(` ${source_default.cyan(`${i + 1}.`)} ${result.steps[i]}`);
|
|
71278
|
+
}
|
|
71279
|
+
console.log();
|
|
71280
|
+
console.log(source_default.bold("Related commands"));
|
|
71281
|
+
console.log(source_default.dim("\u2500".repeat(60)));
|
|
71282
|
+
for (const cmd of result.relatedCommands) {
|
|
71283
|
+
console.log(` ${source_default.cyan(cmd)}`);
|
|
71284
|
+
}
|
|
71285
|
+
console.log();
|
|
71286
|
+
console.log(source_default.bold("Full guide"));
|
|
71287
|
+
console.log(source_default.dim("\u2500".repeat(60)));
|
|
71288
|
+
console.log();
|
|
71289
|
+
console.log(result.content);
|
|
71290
|
+
console.log();
|
|
71291
|
+
});
|
|
71292
|
+
|
|
70304
71293
|
// src/index.ts
|
|
70305
71294
|
init_api_key();
|
|
70306
71295
|
|
|
@@ -70501,6 +71490,7 @@ program2.addCommand(detectCommand);
|
|
|
70501
71490
|
program2.addCommand(batchCommand);
|
|
70502
71491
|
program2.addCommand(schemaCommand);
|
|
70503
71492
|
program2.addCommand(contextCommand);
|
|
71493
|
+
program2.addCommand(walkthroughCommand);
|
|
70504
71494
|
program2.addCommand(mediaCommand, { hidden: true });
|
|
70505
71495
|
function propagateErrorHandling(cmd) {
|
|
70506
71496
|
for (const sub of cmd.commands) {
|