videodraft 0.3.7 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -21
- package/dist/index.js +800 -96
- package/package.json +1 -1
- package/skills/index.json +9 -9
- package/skills/videodraft/SKILL.md +46 -14
- package/skills/videodraft/references/examples.md +68 -5
- package/skills/videodraft/references/models.md +107 -12
- package/skills/videodraft/references/pipeline.md +34 -19
package/dist/index.js
CHANGED
|
@@ -24,8 +24,8 @@ function readVersionFromDisk() {
|
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
26
|
function resolveVersion() {
|
|
27
|
-
if ("0.
|
|
28
|
-
return "0.
|
|
27
|
+
if ("0.4.0") {
|
|
28
|
+
return "0.4.0";
|
|
29
29
|
}
|
|
30
30
|
return readVersionFromDisk();
|
|
31
31
|
}
|
|
@@ -59,6 +59,12 @@ var CliError = class extends Error {
|
|
|
59
59
|
this.hint = hint;
|
|
60
60
|
}
|
|
61
61
|
};
|
|
62
|
+
var UsageError = class extends CliError {
|
|
63
|
+
constructor(message, hint) {
|
|
64
|
+
super(message, EXIT.USAGE, hint);
|
|
65
|
+
this.name = "UsageError";
|
|
66
|
+
}
|
|
67
|
+
};
|
|
62
68
|
var AuthError = class extends CliError {
|
|
63
69
|
constructor(message = "Not authenticated.") {
|
|
64
70
|
super(
|
|
@@ -1237,11 +1243,14 @@ function registerAccountCommands(program) {
|
|
|
1237
1243
|
const balance = await ctx.client.callTool("get_credits_balance");
|
|
1238
1244
|
emit(ctx.out, balance, (o) => {
|
|
1239
1245
|
kv(o, [
|
|
1246
|
+
["Plan", balance?.planId],
|
|
1240
1247
|
["Available credits", balance?.availableCredits],
|
|
1241
1248
|
["Monthly allowance", balance?.totalCreditsMonthly],
|
|
1242
1249
|
["Used this month", balance?.monthlyCreditsUsed],
|
|
1243
1250
|
["Bonus credits", balance?.bonusCredits],
|
|
1244
|
-
["Bonus expiry", balance?.bonusCreditsExpiry]
|
|
1251
|
+
["Bonus expiry", balance?.bonusCreditsExpiry],
|
|
1252
|
+
["Last monthly reset", balance?.lastMonthlyReset],
|
|
1253
|
+
["Next monthly reset", balance?.nextMonthlyReset]
|
|
1245
1254
|
]);
|
|
1246
1255
|
});
|
|
1247
1256
|
});
|
|
@@ -1275,15 +1284,45 @@ function registerAccountCommands(program) {
|
|
|
1275
1284
|
});
|
|
1276
1285
|
program.command("models [kind]").description(
|
|
1277
1286
|
"List available models: image | video | audio | voices | styles (default: image + video + audio)"
|
|
1287
|
+
).option(
|
|
1288
|
+
"--category <name>",
|
|
1289
|
+
"video only: generation | video_edit | motion_control | avatar_lipsync | upscale"
|
|
1278
1290
|
).action(async function(kind) {
|
|
1279
1291
|
const ctx = buildContext(this);
|
|
1292
|
+
const opts = this.opts();
|
|
1280
1293
|
const wanted = kind ?? "all";
|
|
1294
|
+
const videoCategories = /* @__PURE__ */ new Set([
|
|
1295
|
+
"generation",
|
|
1296
|
+
"video_edit",
|
|
1297
|
+
"motion_control",
|
|
1298
|
+
"avatar_lipsync",
|
|
1299
|
+
"upscale"
|
|
1300
|
+
]);
|
|
1301
|
+
if (opts.category && !videoCategories.has(opts.category)) {
|
|
1302
|
+
throw new UsageError(
|
|
1303
|
+
`Unknown video category "${opts.category}". Use generation, video_edit, motion_control, avatar_lipsync, or upscale.`
|
|
1304
|
+
);
|
|
1305
|
+
}
|
|
1306
|
+
if (opts.category && wanted !== "video" && wanted !== "all") {
|
|
1307
|
+
throw new UsageError(
|
|
1308
|
+
"--category is only valid for the video model catalog."
|
|
1309
|
+
);
|
|
1310
|
+
}
|
|
1281
1311
|
const result = {};
|
|
1282
1312
|
if (wanted === "image" || wanted === "all") {
|
|
1283
1313
|
result.image = await ctx.client.callTool("list_available_image_models");
|
|
1284
1314
|
}
|
|
1285
1315
|
if (wanted === "video" || wanted === "all") {
|
|
1286
|
-
|
|
1316
|
+
const video = await ctx.client.callTool(
|
|
1317
|
+
"list_available_video_models"
|
|
1318
|
+
);
|
|
1319
|
+
result.video = opts.category ? {
|
|
1320
|
+
...video,
|
|
1321
|
+
models: (video?.models ?? []).filter(
|
|
1322
|
+
(model) => model.category === opts.category
|
|
1323
|
+
),
|
|
1324
|
+
selected_category: opts.category
|
|
1325
|
+
} : video;
|
|
1287
1326
|
}
|
|
1288
1327
|
if (wanted === "audio" || wanted === "all") {
|
|
1289
1328
|
result.audio = await ctx.client.callTool("list_available_audio_models");
|
|
@@ -1304,14 +1343,17 @@ ${section.toUpperCase()}
|
|
|
1304
1343
|
process.stdout.write(JSON.stringify(payload, null, 2) + "\n");
|
|
1305
1344
|
continue;
|
|
1306
1345
|
}
|
|
1346
|
+
const rows = models.map((m) => [
|
|
1347
|
+
String(m.id ?? m.model_id ?? m.voice_id ?? ""),
|
|
1348
|
+
String(m.name ?? "").slice(0, 40),
|
|
1349
|
+
String(m.category ?? ""),
|
|
1350
|
+
String(m.tool ?? ""),
|
|
1351
|
+
String(m.credit_cost ?? m.cost ?? m.pricing?.summary ?? "")
|
|
1352
|
+
]);
|
|
1307
1353
|
table(
|
|
1308
1354
|
o,
|
|
1309
|
-
["id", "name", "cost"],
|
|
1310
|
-
|
|
1311
|
-
String(m.id ?? m.model_id ?? m.voice_id ?? ""),
|
|
1312
|
-
String(m.name ?? "").slice(0, 40),
|
|
1313
|
-
String(m.credit_cost ?? m.cost ?? m.pricing?.summary ?? "")
|
|
1314
|
-
])
|
|
1355
|
+
section === "video" ? ["id", "name", "category", "tool", "cost"] : ["id", "name", "cost"],
|
|
1356
|
+
section === "video" ? rows : rows.map((row) => [row[0] ?? "", row[1] ?? "", row[4] ?? ""])
|
|
1315
1357
|
);
|
|
1316
1358
|
}
|
|
1317
1359
|
});
|
|
@@ -1354,7 +1396,9 @@ ${section.toUpperCase()}
|
|
|
1354
1396
|
);
|
|
1355
1397
|
});
|
|
1356
1398
|
});
|
|
1357
|
-
sessions.command("create <name>").description(
|
|
1399
|
+
sessions.command("create <name>").description(
|
|
1400
|
+
"Create an AI Studio session (reuse its id across standalone generations)"
|
|
1401
|
+
).option("--project <id>", "attach the session to a project").action(async function(name) {
|
|
1358
1402
|
const ctx = buildContext(this);
|
|
1359
1403
|
const result = await ctx.client.callTool(
|
|
1360
1404
|
"create_ai_studio_session",
|
|
@@ -1801,16 +1845,57 @@ function parseSegments(values) {
|
|
|
1801
1845
|
return values.map((v) => {
|
|
1802
1846
|
const i = v.lastIndexOf(":");
|
|
1803
1847
|
if (i <= 0 || i === v.length - 1) {
|
|
1804
|
-
throw new CliError(
|
|
1848
|
+
throw new CliError(
|
|
1849
|
+
`--segment expects "prompt:seconds", got: ${v}`,
|
|
1850
|
+
EXIT.USAGE
|
|
1851
|
+
);
|
|
1805
1852
|
}
|
|
1806
1853
|
const prompt = v.slice(0, i).trim();
|
|
1807
1854
|
const duration = Number(v.slice(i + 1));
|
|
1808
1855
|
if (!prompt || !Number.isFinite(duration) || duration <= 0) {
|
|
1809
|
-
throw new CliError(
|
|
1856
|
+
throw new CliError(
|
|
1857
|
+
`--segment "${v}" must be "<prompt>:<positive seconds>".`,
|
|
1858
|
+
EXIT.USAGE
|
|
1859
|
+
);
|
|
1810
1860
|
}
|
|
1811
1861
|
return { prompt, duration };
|
|
1812
1862
|
});
|
|
1813
1863
|
}
|
|
1864
|
+
function optionalPositiveNumber(value, label, integer = false) {
|
|
1865
|
+
if (value === void 0) return void 0;
|
|
1866
|
+
const parsed = Number(value);
|
|
1867
|
+
if (!Number.isFinite(parsed) || parsed <= 0 || integer && !Number.isInteger(parsed)) {
|
|
1868
|
+
throw new CliError(
|
|
1869
|
+
`${label} must be a positive${integer ? " whole" : ""} number.`,
|
|
1870
|
+
EXIT.USAGE
|
|
1871
|
+
);
|
|
1872
|
+
}
|
|
1873
|
+
return parsed;
|
|
1874
|
+
}
|
|
1875
|
+
function optionalSeed(value) {
|
|
1876
|
+
if (value === void 0) return void 0;
|
|
1877
|
+
const parsed = Number(value);
|
|
1878
|
+
if (!Number.isSafeInteger(parsed) || parsed < 0) {
|
|
1879
|
+
throw new CliError(
|
|
1880
|
+
"--seed must be a non-negative safe integer.",
|
|
1881
|
+
EXIT.USAGE
|
|
1882
|
+
);
|
|
1883
|
+
}
|
|
1884
|
+
return parsed;
|
|
1885
|
+
}
|
|
1886
|
+
function estimateVideoModel(opts, duration) {
|
|
1887
|
+
if (opts.model) return opts.model;
|
|
1888
|
+
const referenceImageCount = Array.isArray(opts.ref) ? opts.ref.length : 0;
|
|
1889
|
+
const referenceVideoCount = Array.isArray(opts.refVideo) ? opts.refVideo.length : 0;
|
|
1890
|
+
const referenceAudioCount = Array.isArray(opts.refAudio) ? opts.refAudio.length : 0;
|
|
1891
|
+
const seedanceTask = duration !== void 0 && duration > 10 || referenceVideoCount > 1 || referenceAudioCount > 0 || referenceVideoCount > 0 && referenceImageCount > 0 || opts.quality === "mini" || opts.quality === "standard";
|
|
1892
|
+
if (seedanceTask) return "seedance-2";
|
|
1893
|
+
const veoTask = Boolean(opts.endImage) || opts.audio === false || opts.quality === "fast" || opts.quality === "quality" || typeof opts.resolution === "string" && opts.resolution !== "720p";
|
|
1894
|
+
if (veoTask) {
|
|
1895
|
+
return referenceVideoCount > 0 ? "seedance-2" : "google-veo3.1";
|
|
1896
|
+
}
|
|
1897
|
+
return "gemini-omni-flash";
|
|
1898
|
+
}
|
|
1814
1899
|
async function printEstimate(ctx, params) {
|
|
1815
1900
|
const estimate = await ctx.client.callTool(
|
|
1816
1901
|
"get_model_costs",
|
|
@@ -1842,29 +1927,47 @@ async function handleAsyncJob(ctx, submitted, options) {
|
|
|
1842
1927
|
intervalMs: ctx.intervalMs,
|
|
1843
1928
|
timeoutMs: ctx.timeoutMs,
|
|
1844
1929
|
adaptive: ctx.adaptive,
|
|
1845
|
-
onTick: (status) => spin.update(`${options.label}
|
|
1930
|
+
onTick: (status) => spin.update(`${options.label}: ${status} (job ${jobId})`)
|
|
1846
1931
|
});
|
|
1847
1932
|
spin.stop();
|
|
1848
1933
|
if (result.status === "failed") {
|
|
1849
1934
|
emit(ctx.out, { job_id: jobId, ...result.payload }, (o) => {
|
|
1850
|
-
note(
|
|
1935
|
+
note(
|
|
1936
|
+
o,
|
|
1937
|
+
fmt.red(
|
|
1938
|
+
o,
|
|
1939
|
+
`Job ${jobId} failed: ${result.payload?.error ?? "unknown error"}`
|
|
1940
|
+
)
|
|
1941
|
+
);
|
|
1851
1942
|
});
|
|
1852
1943
|
process.exitCode = 1;
|
|
1853
1944
|
return;
|
|
1854
1945
|
}
|
|
1855
1946
|
let downloaded;
|
|
1856
1947
|
if (options.download && result.outputUrls.length > 0) {
|
|
1857
|
-
downloaded = await downloadOutputs(result.outputUrls, options.download, {
|
|
1948
|
+
downloaded = await downloadOutputs(result.outputUrls, options.download, {
|
|
1949
|
+
job_id: jobId
|
|
1950
|
+
});
|
|
1858
1951
|
}
|
|
1859
|
-
const media = buildMediaDescriptors(
|
|
1952
|
+
const media = buildMediaDescriptors(
|
|
1953
|
+
result.outputUrls,
|
|
1954
|
+
result.payload?.type
|
|
1955
|
+
);
|
|
1860
1956
|
emit(
|
|
1861
1957
|
ctx.out,
|
|
1862
|
-
{
|
|
1958
|
+
{
|
|
1959
|
+
job_id: jobId,
|
|
1960
|
+
status: result.status,
|
|
1961
|
+
outputs: result.outputUrls,
|
|
1962
|
+
downloaded_files: downloaded,
|
|
1963
|
+
output_media: media
|
|
1964
|
+
},
|
|
1863
1965
|
(o) => {
|
|
1864
1966
|
note(o, fmt.green(o, `Completed \u2014 job ${jobId}`));
|
|
1865
1967
|
for (const url of result.outputUrls) process.stdout.write(`${url}
|
|
1866
1968
|
`);
|
|
1867
|
-
for (const file of downloaded ?? [])
|
|
1969
|
+
for (const file of downloaded ?? [])
|
|
1970
|
+
note(o, fmt.dim(o, `saved ${file.path}`));
|
|
1868
1971
|
}
|
|
1869
1972
|
);
|
|
1870
1973
|
} catch (err) {
|
|
@@ -1877,13 +1980,35 @@ function registerGenerateCommands(program) {
|
|
|
1877
1980
|
generate.command("image <prompt...>").description("Generate an image (async; waits by default)").option(
|
|
1878
1981
|
"--model <id|name>",
|
|
1879
1982
|
"image model id or display name (default nano-banana-2); run `videodraft models image`"
|
|
1880
|
-
).option("--ar <ratio>", 'aspect ratio, e.g. "16:9"').option("--resolution <res>", 'e.g. "1K", "2K", "4K"').option("--quality <tier>", "model-specific quality tier").option(
|
|
1983
|
+
).option("--ar <ratio>", 'aspect ratio, e.g. "16:9"').option("--resolution <res>", 'e.g. "1K", "2K", "4K"').option("--quality <tier>", "model-specific quality tier").option(
|
|
1984
|
+
"--rendering-speed <tier>",
|
|
1985
|
+
'Ideogram speed/cost tier, e.g. V4 "Turbo"/"Balanced"/"Quality"'
|
|
1986
|
+
).option("--num <n>", "variations of this prompt in one call (1-4)").option(
|
|
1987
|
+
"--seed <n>",
|
|
1988
|
+
"seed (supported models only, e.g. Flux, Ideogram V4)"
|
|
1989
|
+
).option(
|
|
1990
|
+
"--ref <url|file>",
|
|
1991
|
+
"reference image (repeatable; local files are uploaded)",
|
|
1992
|
+
collect,
|
|
1993
|
+
[]
|
|
1994
|
+
).option(
|
|
1995
|
+
"--video-ref <url|file>",
|
|
1996
|
+
"video reference, nano-banana-2 only (http(s)/gs:///YouTube, or local file)"
|
|
1997
|
+
).option("--style <id>", "style preset id").option("--project <id>", "attach to a project").option("--session <id>", "AI Studio session id").option(
|
|
1998
|
+
"--scene <n>",
|
|
1999
|
+
"0-based scene index (with --project: writes onto that shot)"
|
|
2000
|
+
).option("--shot <n>", "0-based shot index").option(
|
|
2001
|
+
"--download <path>",
|
|
2002
|
+
"download outputs (template: {job_id} {index} {ext})"
|
|
2003
|
+
).option("--no-wait", "submit and return the job id immediately").option("--estimate", "print the cost estimate and exit (spends nothing)").action(async function(promptWords) {
|
|
1881
2004
|
const ctx = buildContext(this);
|
|
1882
2005
|
const opts = this.opts();
|
|
1883
2006
|
const prompt = promptWords.join(" ");
|
|
1884
2007
|
if (opts.estimate) {
|
|
1885
2008
|
await printEstimate(ctx, {
|
|
1886
|
-
model
|
|
2009
|
+
// Cost lookup needs a concrete model. Keep runtime generation
|
|
2010
|
+
// model-less so the server can still make its task-aware choice.
|
|
2011
|
+
model: opts.model ?? "nano-banana-2",
|
|
1887
2012
|
type: "image",
|
|
1888
2013
|
resolution: opts.resolution,
|
|
1889
2014
|
quality: opts.quality,
|
|
@@ -1896,7 +2021,11 @@ function registerGenerateCommands(program) {
|
|
|
1896
2021
|
resolveRefs(ctx, opts.ref ?? []),
|
|
1897
2022
|
opts.videoRef ? resolveRefs(ctx, [opts.videoRef]).then((r) => r[0]) : void 0
|
|
1898
2023
|
]);
|
|
1899
|
-
capture("cli_generate", {
|
|
2024
|
+
capture("cli_generate", {
|
|
2025
|
+
kind: "image",
|
|
2026
|
+
model: opts.model ?? "default",
|
|
2027
|
+
wait: opts.wait !== false
|
|
2028
|
+
});
|
|
1900
2029
|
const submitted = await ctx.client.callTool(
|
|
1901
2030
|
"generate_image",
|
|
1902
2031
|
compact({
|
|
@@ -1923,19 +2052,45 @@ function registerGenerateCommands(program) {
|
|
|
1923
2052
|
label: "Generating image"
|
|
1924
2053
|
});
|
|
1925
2054
|
});
|
|
1926
|
-
generate.command("video [prompt...]").description(
|
|
2055
|
+
generate.command("video [prompt...]").description(
|
|
2056
|
+
"Generate a video clip (async; per-second pricing, see --estimate)"
|
|
2057
|
+
).option(
|
|
2058
|
+
"--model <id>",
|
|
2059
|
+
"video model id (task-aware when omitted; usually Gemini Omni Flash, Seedance 2 for longer/mixed-reference work)"
|
|
2060
|
+
).option("--ar <ratio>", 'aspect ratio, e.g. "16:9", "9:16"').option("--duration <seconds>", "clip duration in seconds").option("--resolution <res>", 'e.g. "480p", "720p", "1080p", "4k"').option(
|
|
1927
2061
|
"--quality <tier>",
|
|
1928
2062
|
'e.g. "mini", "fast", "standard", "quality", "pro"'
|
|
1929
|
-
).option("--audio", "generate native model audio").option("--no-audio", "disable native model audio").option("--start-image <url|file>", "start frame (image-to-video)").option("--end-image <url|file>", "end frame (supported models only)").option("--ref <url|file>", "reference image (repeatable)", collect, []).option(
|
|
2063
|
+
).option("--audio", "generate native model audio").option("--no-audio", "disable native model audio").option("--start-image <url|file>", "start frame (image-to-video)").option("--end-image <url|file>", "end frame (supported models only)").option("--ref <url|file>", "reference image (repeatable)", collect, []).option(
|
|
2064
|
+
"--ref-video <url|file>",
|
|
2065
|
+
"reference video (repeatable; Gemini Omni Flash, Seedance 2, Wan 2.7, Kling/Wan Ref-Edit reference mode; local files uploaded)",
|
|
2066
|
+
collect,
|
|
2067
|
+
[]
|
|
2068
|
+
).option(
|
|
2069
|
+
"--ref-audio <url|file>",
|
|
2070
|
+
"reference audio (repeatable; Seedance 2; local files uploaded)",
|
|
2071
|
+
collect,
|
|
2072
|
+
[]
|
|
2073
|
+
).option(
|
|
2074
|
+
"--segment <prompt:seconds>",
|
|
2075
|
+
"multi-prompt segment (repeatable; Kling 3.0 / 3.0 Turbo / O3)",
|
|
2076
|
+
collect,
|
|
2077
|
+
[]
|
|
2078
|
+
).option("--negative <text>", "negative prompt (Kling/Wan/Luma)").option("--camera-fixed", "Seedance 1.5 Pro: lock camera motion").option("--seed <n>", "seed").option("--project <id>", "attach to a project").option("--session <id>", "AI Studio session id").option("--scene <n>", "0-based scene index").option("--shot <n>", "0-based shot index").option(
|
|
2079
|
+
"--download <path>",
|
|
2080
|
+
"download outputs (template: {job_id} {index} {ext})"
|
|
2081
|
+
).option("--no-wait", "submit and return the job id immediately").option("--estimate", "print the cost estimate and exit (spends nothing)").action(async function(promptWords = []) {
|
|
1930
2082
|
const ctx = buildContext(this);
|
|
1931
2083
|
const opts = this.opts();
|
|
1932
2084
|
const prompt = promptWords.join(" ").trim();
|
|
1933
|
-
const duration = opts.duration
|
|
2085
|
+
const duration = optionalPositiveNumber(opts.duration, "--duration");
|
|
2086
|
+
const seed = optionalSeed(opts.seed);
|
|
1934
2087
|
if (opts.estimate) {
|
|
2088
|
+
const estimateModel = estimateVideoModel(opts, duration);
|
|
2089
|
+
const estimateDuration = duration ?? (!opts.model && estimateModel === "google-veo3.1" ? Array.isArray(opts.ref) && opts.ref.length > 0 ? 8 : 6 : void 0);
|
|
1935
2090
|
await printEstimate(ctx, {
|
|
1936
|
-
model:
|
|
2091
|
+
model: estimateModel,
|
|
1937
2092
|
type: "video",
|
|
1938
|
-
duration,
|
|
2093
|
+
duration: estimateDuration,
|
|
1939
2094
|
resolution: opts.resolution,
|
|
1940
2095
|
quality: opts.quality,
|
|
1941
2096
|
audio: opts.audio
|
|
@@ -1956,10 +2111,84 @@ function registerGenerateCommands(program) {
|
|
|
1956
2111
|
EXIT.USAGE
|
|
1957
2112
|
);
|
|
1958
2113
|
}
|
|
1959
|
-
capture("cli_generate", {
|
|
1960
|
-
|
|
1961
|
-
"
|
|
1962
|
-
|
|
2114
|
+
capture("cli_generate", {
|
|
2115
|
+
kind: "video",
|
|
2116
|
+
model: opts.model ?? "default",
|
|
2117
|
+
wait: opts.wait !== false
|
|
2118
|
+
});
|
|
2119
|
+
const videoEditModels = /* @__PURE__ */ new Set([
|
|
2120
|
+
"happy-horse-video-edit",
|
|
2121
|
+
"grok-imagine-video-edit"
|
|
2122
|
+
]);
|
|
2123
|
+
const motionControlModels = /* @__PURE__ */ new Set([
|
|
2124
|
+
"kling-v3-motion-control",
|
|
2125
|
+
"kling-2.6-motion-control"
|
|
2126
|
+
]);
|
|
2127
|
+
let toolName = "generate_video";
|
|
2128
|
+
let toolArgs;
|
|
2129
|
+
if (videoEditModels.has(opts.model)) {
|
|
2130
|
+
if (refVideos.length !== 1) {
|
|
2131
|
+
throw new CliError(
|
|
2132
|
+
`${opts.model} requires exactly one --ref-video source. You can also use videodraft edit video.`,
|
|
2133
|
+
EXIT.USAGE
|
|
2134
|
+
);
|
|
2135
|
+
}
|
|
2136
|
+
if (startImage || endImage || refAudios.length > 0 || segments.length > 0 || opts.ar || opts.negative || opts.cameraFixed || opts.seed) {
|
|
2137
|
+
throw new CliError(
|
|
2138
|
+
`${opts.model} does not support --start-image, --end-image, --ref-audio, --segment, --ar, --negative, --camera-fixed, or --seed in video-edit mode. Use --ref for supported reference images.`,
|
|
2139
|
+
EXIT.USAGE
|
|
2140
|
+
);
|
|
2141
|
+
}
|
|
2142
|
+
toolName = "edit_video";
|
|
2143
|
+
toolArgs = {
|
|
2144
|
+
model: opts.model,
|
|
2145
|
+
prompt: prompt || void 0,
|
|
2146
|
+
video_url: refVideos[0],
|
|
2147
|
+
reference_images: refs.length > 0 ? refs : void 0,
|
|
2148
|
+
resolution: opts.resolution,
|
|
2149
|
+
quality: opts.quality,
|
|
2150
|
+
duration_seconds: duration,
|
|
2151
|
+
preserve_audio: opts.audio,
|
|
2152
|
+
project_id: opts.project,
|
|
2153
|
+
session_id: opts.session,
|
|
2154
|
+
scene_index: opts.scene !== void 0 ? Number(opts.scene) : void 0,
|
|
2155
|
+
shot_index: opts.shot !== void 0 ? Number(opts.shot) : void 0
|
|
2156
|
+
};
|
|
2157
|
+
} else if (motionControlModels.has(opts.model)) {
|
|
2158
|
+
if (!startImage || refVideos.length !== 1) {
|
|
2159
|
+
throw new CliError(
|
|
2160
|
+
`${opts.model} requires --start-image plus exactly one --ref-video motion source. You can also use videodraft edit motion.`,
|
|
2161
|
+
EXIT.USAGE
|
|
2162
|
+
);
|
|
2163
|
+
}
|
|
2164
|
+
if (endImage || refs.length > 0 || refAudios.length > 0 || segments.length > 0 || opts.ar || opts.negative || opts.cameraFixed || opts.seed || opts.resolution) {
|
|
2165
|
+
throw new CliError(
|
|
2166
|
+
`${opts.model} does not support --end-image, --ref, --ref-audio, --segment, --ar, --negative, --camera-fixed, --seed, or --resolution in motion-control mode.`,
|
|
2167
|
+
EXIT.USAGE
|
|
2168
|
+
);
|
|
2169
|
+
}
|
|
2170
|
+
if (duration !== void 0) {
|
|
2171
|
+
throw new CliError(
|
|
2172
|
+
`${opts.model} follows the motion video duration and orientation cap; use videodraft edit motion --estimate for a duration-based estimate.`,
|
|
2173
|
+
EXIT.USAGE
|
|
2174
|
+
);
|
|
2175
|
+
}
|
|
2176
|
+
toolName = "generate_motion_control_video";
|
|
2177
|
+
toolArgs = {
|
|
2178
|
+
model: opts.model,
|
|
2179
|
+
prompt: prompt || void 0,
|
|
2180
|
+
image_url: startImage,
|
|
2181
|
+
motion_video_url: refVideos[0],
|
|
2182
|
+
quality: opts.quality,
|
|
2183
|
+
keep_original_sound: opts.audio,
|
|
2184
|
+
duration_seconds: duration,
|
|
2185
|
+
project_id: opts.project,
|
|
2186
|
+
session_id: opts.session,
|
|
2187
|
+
scene_index: opts.scene !== void 0 ? Number(opts.scene) : void 0,
|
|
2188
|
+
shot_index: opts.shot !== void 0 ? Number(opts.shot) : void 0
|
|
2189
|
+
};
|
|
2190
|
+
} else {
|
|
2191
|
+
toolArgs = {
|
|
1963
2192
|
prompt: prompt || void 0,
|
|
1964
2193
|
model: opts.model,
|
|
1965
2194
|
aspect_ratio: opts.ar,
|
|
@@ -1974,20 +2203,25 @@ function registerGenerateCommands(program) {
|
|
|
1974
2203
|
reference_audio: refAudios.length > 0 ? refAudios : void 0,
|
|
1975
2204
|
multi_prompt: segments.length > 0 ? segments : void 0,
|
|
1976
2205
|
negative_prompt: opts.negative,
|
|
1977
|
-
|
|
2206
|
+
camera_fixed: opts.cameraFixed ? true : void 0,
|
|
2207
|
+
seed,
|
|
1978
2208
|
project_id: opts.project,
|
|
1979
2209
|
session_id: opts.session,
|
|
1980
2210
|
scene_index: opts.scene !== void 0 ? Number(opts.scene) : void 0,
|
|
1981
2211
|
shot_index: opts.shot !== void 0 ? Number(opts.shot) : void 0
|
|
1982
|
-
}
|
|
1983
|
-
|
|
2212
|
+
};
|
|
2213
|
+
}
|
|
2214
|
+
const submitted = await ctx.client.callTool(toolName, compact(toolArgs));
|
|
1984
2215
|
await handleAsyncJob(ctx, submitted, {
|
|
1985
2216
|
wait: opts.wait !== false,
|
|
1986
2217
|
download: opts.download,
|
|
1987
2218
|
label: "Generating video"
|
|
1988
2219
|
});
|
|
1989
2220
|
});
|
|
1990
|
-
generate.command("voiceover <text...>").description("Generate TTS audio (synchronous \u2014 returns an audio URL)").option("--voice <id>", "voice id (see `videodraft models voices`)").option("--language <bcp47>", 'target language, default "en"').option("--project <id>", "attach to a project").option(
|
|
2221
|
+
generate.command("voiceover <text...>").description("Generate TTS audio (synchronous \u2014 returns an audio URL)").option("--voice <id>", "voice id (see `videodraft models voices`)").option("--language <bcp47>", 'target language, default "en"').option("--project <id>", "attach to a project").option(
|
|
2222
|
+
"--scene <n>",
|
|
2223
|
+
"0-based scene index; wires the audio onto that scene"
|
|
2224
|
+
).option("--session <id>", "AI Studio session id").option("--download <path>", "download the audio file").action(async function(textWords) {
|
|
1991
2225
|
const ctx = buildContext(this);
|
|
1992
2226
|
const opts = this.opts();
|
|
1993
2227
|
capture("cli_generate", { kind: "voiceover" });
|
|
@@ -2005,16 +2239,46 @@ function registerGenerateCommands(program) {
|
|
|
2005
2239
|
const urls = extractOutputUrls(result);
|
|
2006
2240
|
let downloaded;
|
|
2007
2241
|
if (opts.download && urls.length > 0) {
|
|
2008
|
-
downloaded = await downloadOutputs(urls, opts.download, {
|
|
2242
|
+
downloaded = await downloadOutputs(urls, opts.download, {
|
|
2243
|
+
name: "voiceover"
|
|
2244
|
+
});
|
|
2009
2245
|
}
|
|
2010
2246
|
const media = buildMediaDescriptors(urls, "audio");
|
|
2011
|
-
emit(
|
|
2012
|
-
|
|
2247
|
+
emit(
|
|
2248
|
+
ctx.out,
|
|
2249
|
+
{ ...result, downloaded_files: downloaded, output_media: media },
|
|
2250
|
+
(o) => {
|
|
2251
|
+
for (const url of urls) process.stdout.write(`${url}
|
|
2013
2252
|
`);
|
|
2014
|
-
|
|
2015
|
-
|
|
2253
|
+
for (const f of downloaded ?? [])
|
|
2254
|
+
note(o, fmt.dim(o, `saved ${f.path}`));
|
|
2255
|
+
}
|
|
2256
|
+
);
|
|
2016
2257
|
});
|
|
2017
|
-
generate.command("music <prompt...>").description("Generate background music").option(
|
|
2258
|
+
generate.command("music <prompt...>").description("Generate background music").option(
|
|
2259
|
+
"--model <id>",
|
|
2260
|
+
"lyria-3-clip-preview (default) | lyria-3-pro-preview | elevenlabs-music"
|
|
2261
|
+
).option(
|
|
2262
|
+
"--length <seconds>",
|
|
2263
|
+
"for --model elevenlabs-music: length 10\u2013120s (default 30)"
|
|
2264
|
+
).option(
|
|
2265
|
+
"--instrumental",
|
|
2266
|
+
"for --model elevenlabs-music: force instrumental (no vocals)"
|
|
2267
|
+
).option(
|
|
2268
|
+
"--ref <url|file>",
|
|
2269
|
+
"reference image to inspire the music (Lyria only, repeatable)",
|
|
2270
|
+
collect,
|
|
2271
|
+
[]
|
|
2272
|
+
).option(
|
|
2273
|
+
"--project <id>",
|
|
2274
|
+
"link the generation to a project's AI Studio session"
|
|
2275
|
+
).option(
|
|
2276
|
+
"--attach <project_id>",
|
|
2277
|
+
"also set the track as that project's background music"
|
|
2278
|
+
).option("--volume <n>", "0-100 BGM volume when attaching (default 30)").option(
|
|
2279
|
+
"--bgm-disabled",
|
|
2280
|
+
"when attaching, store the BGM as disabled (enabled:false)"
|
|
2281
|
+
).option("--session <id>", "AI Studio session id").option("--download <path>", "download the audio file").action(async function(promptWords) {
|
|
2018
2282
|
const ctx = buildContext(this);
|
|
2019
2283
|
const opts = this.opts();
|
|
2020
2284
|
const musicModel = opts.model ?? "lyria-3-clip-preview";
|
|
@@ -2038,14 +2302,21 @@ function registerGenerateCommands(program) {
|
|
|
2038
2302
|
const urls = extractOutputUrls(result);
|
|
2039
2303
|
let downloaded;
|
|
2040
2304
|
if (opts.download && urls.length > 0) {
|
|
2041
|
-
downloaded = await downloadOutputs(urls, opts.download, {
|
|
2305
|
+
downloaded = await downloadOutputs(urls, opts.download, {
|
|
2306
|
+
name: "music"
|
|
2307
|
+
});
|
|
2042
2308
|
}
|
|
2043
2309
|
const media = buildMediaDescriptors(urls, "music");
|
|
2044
|
-
emit(
|
|
2045
|
-
|
|
2310
|
+
emit(
|
|
2311
|
+
ctx.out,
|
|
2312
|
+
{ ...result, downloaded_files: downloaded, output_media: media },
|
|
2313
|
+
(o) => {
|
|
2314
|
+
for (const url of urls) process.stdout.write(`${url}
|
|
2046
2315
|
`);
|
|
2047
|
-
|
|
2048
|
-
|
|
2316
|
+
for (const f of downloaded ?? [])
|
|
2317
|
+
note(o, fmt.dim(o, `saved ${f.path}`));
|
|
2318
|
+
}
|
|
2319
|
+
);
|
|
2049
2320
|
});
|
|
2050
2321
|
generate.command("sound-effect <prompt...>").description("Generate a sound effect (ElevenLabs Sound Effects)").option("--duration <seconds>", "length 0.5\u201322s (default 5)").option("--influence <0-1>", "prompt influence (default 0.3)").option("--project <id>", "link to a project's AI Studio session").option("--session <id>", "AI Studio session id").option("--download <path>", "download the audio file").action(async function(promptWords) {
|
|
2051
2322
|
const ctx = buildContext(this);
|
|
@@ -2075,7 +2346,8 @@ function registerGenerateCommands(program) {
|
|
|
2075
2346
|
(o) => {
|
|
2076
2347
|
for (const url of urls) process.stdout.write(`${url}
|
|
2077
2348
|
`);
|
|
2078
|
-
for (const f of downloaded ?? [])
|
|
2349
|
+
for (const f of downloaded ?? [])
|
|
2350
|
+
note(o, fmt.dim(o, `saved ${f.path}`));
|
|
2079
2351
|
}
|
|
2080
2352
|
);
|
|
2081
2353
|
});
|
|
@@ -2094,9 +2366,13 @@ function registerGenerateCommands(program) {
|
|
|
2094
2366
|
if (i < 0) {
|
|
2095
2367
|
throw new Error(`--line must be "voiceId:text" (got "${raw}")`);
|
|
2096
2368
|
}
|
|
2097
|
-
return {
|
|
2369
|
+
return {
|
|
2370
|
+
voice_id: raw.slice(0, i).trim(),
|
|
2371
|
+
text: raw.slice(i + 1).trim()
|
|
2372
|
+
};
|
|
2098
2373
|
});
|
|
2099
|
-
if (lines.length === 0)
|
|
2374
|
+
if (lines.length === 0)
|
|
2375
|
+
throw new Error("at least one --line is required");
|
|
2100
2376
|
capture("cli_generate", { kind: "dialogue" });
|
|
2101
2377
|
const result = await ctx.client.callTool(
|
|
2102
2378
|
"generate_dialogue",
|
|
@@ -2122,7 +2398,8 @@ function registerGenerateCommands(program) {
|
|
|
2122
2398
|
(o) => {
|
|
2123
2399
|
for (const url of urls) process.stdout.write(`${url}
|
|
2124
2400
|
`);
|
|
2125
|
-
for (const f of downloaded ?? [])
|
|
2401
|
+
for (const f of downloaded ?? [])
|
|
2402
|
+
note(o, fmt.dim(o, `saved ${f.path}`));
|
|
2126
2403
|
}
|
|
2127
2404
|
);
|
|
2128
2405
|
});
|
|
@@ -2164,11 +2441,20 @@ function registerGenerateCommands(program) {
|
|
|
2164
2441
|
(o) => {
|
|
2165
2442
|
for (const url of urls) process.stdout.write(`${url}
|
|
2166
2443
|
`);
|
|
2167
|
-
for (const f of downloaded ?? [])
|
|
2444
|
+
for (const f of downloaded ?? [])
|
|
2445
|
+
note(o, fmt.dim(o, `saved ${f.path}`));
|
|
2168
2446
|
}
|
|
2169
2447
|
);
|
|
2170
2448
|
});
|
|
2171
|
-
generate.command("dub <media>").description(
|
|
2449
|
+
generate.command("dub <media>").description(
|
|
2450
|
+
"Dub a video/audio file into another language (ElevenLabs Dubbing)"
|
|
2451
|
+
).option(
|
|
2452
|
+
"--to <iso>",
|
|
2453
|
+
"target language ISO 639-1 code, e.g. es or te (required)"
|
|
2454
|
+
).option(
|
|
2455
|
+
"--from <iso>",
|
|
2456
|
+
"source language ISO 639-1 code (auto-detected if omitted)"
|
|
2457
|
+
).option("--type <audio|video>", "source media type override").option(
|
|
2172
2458
|
"--duration <seconds>",
|
|
2173
2459
|
"length of the source media in seconds (required, max 300)"
|
|
2174
2460
|
).option("--speakers <n>", "number of speakers (auto-detected if omitted)").option("--project <id>", "link to a project's AI Studio session").option("--session <id>", "AI Studio session id").option("--download <path>", "download the dubbed file").action(async function(source) {
|
|
@@ -2210,33 +2496,55 @@ function registerGenerateCommands(program) {
|
|
|
2210
2496
|
(o) => {
|
|
2211
2497
|
for (const url of urls) process.stdout.write(`${url}
|
|
2212
2498
|
`);
|
|
2213
|
-
for (const f of downloaded ?? [])
|
|
2499
|
+
for (const f of downloaded ?? [])
|
|
2500
|
+
note(o, fmt.dim(o, `saved ${f.path}`));
|
|
2214
2501
|
}
|
|
2215
2502
|
);
|
|
2216
2503
|
});
|
|
2217
2504
|
const upscale = program.command("upscale").description("Upscale images and videos (Topaz)");
|
|
2218
|
-
upscale.command("image <url|file>").description(
|
|
2505
|
+
upscale.command("image <url|file>").description(
|
|
2506
|
+
"Enhance or upscale an existing image with Topaz (synchronous)"
|
|
2507
|
+
).option("--scale <factor>", '"1x" | "2x" | "4x" (default 2x)').option("--session <id>", "AI Studio session id").option("--download <path>", "download the result").action(async function(source) {
|
|
2219
2508
|
const ctx = buildContext(this);
|
|
2220
2509
|
const opts = this.opts();
|
|
2221
2510
|
const [url] = await resolveRefs(ctx, [source]);
|
|
2222
2511
|
capture("cli_upscale", { kind: "image" });
|
|
2223
2512
|
const result = await ctx.client.callTool(
|
|
2224
2513
|
"upscale_image",
|
|
2225
|
-
compact({
|
|
2514
|
+
compact({
|
|
2515
|
+
image_url: url,
|
|
2516
|
+
scale: opts.scale,
|
|
2517
|
+
session_id: opts.session
|
|
2518
|
+
})
|
|
2226
2519
|
);
|
|
2227
2520
|
const urls = extractOutputUrls(result);
|
|
2228
2521
|
let downloaded;
|
|
2229
2522
|
if (opts.download && urls.length > 0) {
|
|
2230
|
-
downloaded = await downloadOutputs(urls, opts.download, {
|
|
2523
|
+
downloaded = await downloadOutputs(urls, opts.download, {
|
|
2524
|
+
name: "upscaled"
|
|
2525
|
+
});
|
|
2231
2526
|
}
|
|
2232
2527
|
const media = buildMediaDescriptors(urls, "image");
|
|
2233
|
-
emit(
|
|
2234
|
-
|
|
2528
|
+
emit(
|
|
2529
|
+
ctx.out,
|
|
2530
|
+
{ ...result, downloaded_files: downloaded, output_media: media },
|
|
2531
|
+
(o) => {
|
|
2532
|
+
for (const u of urls) process.stdout.write(`${u}
|
|
2235
2533
|
`);
|
|
2236
|
-
|
|
2237
|
-
|
|
2534
|
+
for (const f of downloaded ?? [])
|
|
2535
|
+
note(o, fmt.dim(o, `saved ${f.path}`));
|
|
2536
|
+
}
|
|
2537
|
+
);
|
|
2238
2538
|
});
|
|
2239
|
-
upscale.command("video <url|file>").description(
|
|
2539
|
+
upscale.command("video <url|file>").description(
|
|
2540
|
+
"Enhance or upscale an existing video with Topaz (async; waits by default)"
|
|
2541
|
+
).option("--scale <factor>", 'e.g. "2x" (default)').option("--session <id>", "AI Studio session id").option(
|
|
2542
|
+
"--duration <seconds>",
|
|
2543
|
+
"source duration override (only if auto-probe fails, e.g. >100MB)"
|
|
2544
|
+
).option("--width <px>", "source width override (only if auto-probe fails)").option(
|
|
2545
|
+
"--height <px>",
|
|
2546
|
+
"source height override (only if auto-probe fails)"
|
|
2547
|
+
).option("--download <path>", "download the result").option("--no-wait", "submit and return the job id immediately").action(async function(source) {
|
|
2240
2548
|
const ctx = buildContext(this);
|
|
2241
2549
|
const opts = this.opts();
|
|
2242
2550
|
const [url] = await resolveRefs(ctx, [source]);
|
|
@@ -2893,10 +3201,20 @@ ${summary.length} tools${suffix}. Inspect one: videodraft tools schema <name>`
|
|
|
2893
3201
|
}
|
|
2894
3202
|
|
|
2895
3203
|
// src/commands/avatar.ts
|
|
2896
|
-
|
|
3204
|
+
function positiveNumber(value, label) {
|
|
3205
|
+
if (value === void 0) return void 0;
|
|
3206
|
+
const parsed = Number(value);
|
|
3207
|
+
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
3208
|
+
throw new UsageError(`${label} must be a positive number.`);
|
|
3209
|
+
}
|
|
3210
|
+
return parsed;
|
|
3211
|
+
}
|
|
2897
3212
|
function registerAvatarCommands(program) {
|
|
2898
|
-
const avatar = program.command("avatar").description("Avatar / talking-head videos");
|
|
2899
|
-
avatar.command("script <idea...>").description("Generate a ~30s spoken script for an avatar video (free)").option(
|
|
3213
|
+
const avatar = program.command("avatar").description("Avatar / talking-head videos with VEED Fabric");
|
|
3214
|
+
avatar.command("script <idea...>").description("Generate a ~30s spoken script for an avatar video (free)").option(
|
|
3215
|
+
"--style <style>",
|
|
3216
|
+
"narrative | ad-style | casual-talk | promotional | educational"
|
|
3217
|
+
).action(async function(ideaWords) {
|
|
2900
3218
|
const ctx = buildContext(this);
|
|
2901
3219
|
const result = await ctx.client.callTool(
|
|
2902
3220
|
"generate_avatar_script",
|
|
@@ -2904,14 +3222,12 @@ function registerAvatarCommands(program) {
|
|
|
2904
3222
|
);
|
|
2905
3223
|
emit(ctx.out, result);
|
|
2906
3224
|
});
|
|
2907
|
-
avatar.command("create <image_url_or_file>").description(
|
|
3225
|
+
avatar.command("create <image_url_or_file>").description(
|
|
3226
|
+
"Create VEED Fabric avatar speech from a portrait + script (free; render separately)"
|
|
3227
|
+
).requiredOption("--script <text>", "the spoken script (~30s)").option("--voice <id>", "TTS voice id (default ElevenLabs Brittney)").option("--name <name>", "avatar display name").option("--ar <ratio>", 'aspect ratio (default "9:16")').option("--language <bcp47>", "target language").action(async function(source) {
|
|
2908
3228
|
const ctx = buildContext(this);
|
|
2909
3229
|
const opts = this.opts();
|
|
2910
|
-
|
|
2911
|
-
if (!/^https?:\/\//.test(source) && fs6.existsSync(source)) {
|
|
2912
|
-
const uploaded = await uploadFile(ctx.client, source);
|
|
2913
|
-
imageUrl = uploaded.url;
|
|
2914
|
-
}
|
|
3230
|
+
const [imageUrl] = await resolveRefs(ctx, [source]);
|
|
2915
3231
|
capture("cli_avatar", { step: "create" });
|
|
2916
3232
|
const result = await ctx.client.callTool(
|
|
2917
3233
|
"create_avatar_video",
|
|
@@ -2926,23 +3242,188 @@ function registerAvatarCommands(program) {
|
|
|
2926
3242
|
);
|
|
2927
3243
|
const media = buildMediaDescriptors(extractOutputUrls(result), "audio");
|
|
2928
3244
|
emit(ctx.out, { ...result, output_media: media }, (o) => {
|
|
2929
|
-
note(
|
|
2930
|
-
|
|
3245
|
+
note(
|
|
3246
|
+
o,
|
|
3247
|
+
fmt.green(o, `Avatar video ${result?.avatar_video_id ?? "created"}.`)
|
|
3248
|
+
);
|
|
3249
|
+
note(
|
|
3250
|
+
o,
|
|
3251
|
+
fmt.dim(
|
|
3252
|
+
o,
|
|
3253
|
+
`Render (paid): videodraft avatar render ${result?.avatar_video_id}`
|
|
3254
|
+
)
|
|
3255
|
+
);
|
|
2931
3256
|
});
|
|
2932
3257
|
});
|
|
2933
|
-
avatar.command("
|
|
3258
|
+
avatar.command("fabric <image_url_or_file>").description(
|
|
3259
|
+
"Generate a direct VEED Fabric video from a portrait plus text or audio"
|
|
3260
|
+
).option("--text <text>", "text for Fabric to speak").option("--audio <url|file>", "existing audio to lip-sync").option(
|
|
3261
|
+
"--voice-description <text>",
|
|
3262
|
+
"text-mode voice direction, for example a warm British narrator"
|
|
3263
|
+
).option("--speed <mode>", 'audio mode: "normal" or "fast" (default normal)').option("--resolution <res>", '"480p" or "720p" (default 720p)').option(
|
|
3264
|
+
"--audio-duration <seconds>",
|
|
3265
|
+
"optional estimate hint; server verifies MCP billing duration"
|
|
3266
|
+
).option("--project <id>", "group in a project's AI Studio session").option("--session <id>", "AI Studio session id").option("--download <path>", "download the finished video").option("--no-wait", "submit and return the job id immediately").option("--estimate", "print the cost estimate and exit").action(async function(imageSource) {
|
|
3267
|
+
const ctx = buildContext(this);
|
|
3268
|
+
const opts = this.opts();
|
|
3269
|
+
const hasText = typeof opts.text === "string" && opts.text.trim();
|
|
3270
|
+
const hasAudio = typeof opts.audio === "string" && opts.audio.trim();
|
|
3271
|
+
if (Boolean(hasText) === Boolean(hasAudio)) {
|
|
3272
|
+
throw new UsageError("Provide exactly one of --text or --audio.");
|
|
3273
|
+
}
|
|
3274
|
+
const mode = hasAudio ? "audio" : "text";
|
|
3275
|
+
const speed = opts.speed ?? "normal";
|
|
3276
|
+
if (!["normal", "fast"].includes(speed)) {
|
|
3277
|
+
throw new UsageError('--speed must be "normal" or "fast".');
|
|
3278
|
+
}
|
|
3279
|
+
if (mode === "text" && speed !== "normal") {
|
|
3280
|
+
throw new UsageError("--speed applies only with --audio.");
|
|
3281
|
+
}
|
|
3282
|
+
const resolution = opts.resolution ?? "720p";
|
|
3283
|
+
if (!["480p", "720p"].includes(resolution)) {
|
|
3284
|
+
throw new UsageError('--resolution must be "480p" or "720p".');
|
|
3285
|
+
}
|
|
3286
|
+
if (mode === "audio" && opts.voiceDescription) {
|
|
3287
|
+
throw new UsageError("--voice-description applies only with --text.");
|
|
3288
|
+
}
|
|
3289
|
+
const audioDuration = positiveNumber(
|
|
3290
|
+
opts.audioDuration,
|
|
3291
|
+
"--audio-duration"
|
|
3292
|
+
);
|
|
3293
|
+
const model = mode === "text" ? "veed-fabric-text" : speed === "fast" ? "veed-fabric-fast" : "veed-fabric";
|
|
3294
|
+
const estimatedDuration = mode === "text" ? Math.max(
|
|
3295
|
+
1,
|
|
3296
|
+
Math.min(120, Math.ceil(String(opts.text).trim().length / 15))
|
|
3297
|
+
) : audioDuration;
|
|
3298
|
+
if (opts.estimate) {
|
|
3299
|
+
const estimate = await ctx.client.callTool(
|
|
3300
|
+
"get_model_costs",
|
|
3301
|
+
compact({
|
|
3302
|
+
model_id: model,
|
|
3303
|
+
type: "video",
|
|
3304
|
+
duration_seconds: estimatedDuration,
|
|
3305
|
+
resolution
|
|
3306
|
+
})
|
|
3307
|
+
);
|
|
3308
|
+
emit(ctx.out, {
|
|
3309
|
+
estimate,
|
|
3310
|
+
note: "No credits were spent (--estimate)."
|
|
3311
|
+
});
|
|
3312
|
+
return;
|
|
3313
|
+
}
|
|
3314
|
+
const [imageUrl] = await resolveRefs(ctx, [imageSource]);
|
|
3315
|
+
const audioUrl = hasAudio ? (await resolveRefs(ctx, [opts.audio]))[0] : void 0;
|
|
3316
|
+
capture("cli_avatar", { step: "fabric", mode, speed });
|
|
3317
|
+
const submitted = await ctx.client.callTool(
|
|
3318
|
+
"generate_veed_fabric_video",
|
|
3319
|
+
compact({
|
|
3320
|
+
image_url: imageUrl,
|
|
3321
|
+
mode,
|
|
3322
|
+
text: mode === "text" ? opts.text : void 0,
|
|
3323
|
+
voice_description: opts.voiceDescription,
|
|
3324
|
+
audio_url: audioUrl,
|
|
3325
|
+
audio_duration_seconds: audioDuration,
|
|
3326
|
+
speed,
|
|
3327
|
+
resolution,
|
|
3328
|
+
project_id: opts.project,
|
|
3329
|
+
session_id: opts.session
|
|
3330
|
+
})
|
|
3331
|
+
);
|
|
3332
|
+
await handleAsyncJob(ctx, submitted, {
|
|
3333
|
+
wait: opts.wait !== false,
|
|
3334
|
+
download: opts.download,
|
|
3335
|
+
label: "Generating VEED Fabric video"
|
|
3336
|
+
});
|
|
3337
|
+
});
|
|
3338
|
+
avatar.command("lipsync <video_url_or_file>").description("Lip-sync an existing video to audio with Sync Labs").requiredOption("--audio <url|file>", "replacement speech/audio track").option(
|
|
3339
|
+
"--sync-mode <mode>",
|
|
3340
|
+
"loop | bounce | cut_off | silence | remap (default loop)"
|
|
3341
|
+
).option("--temperature <0-1>", "expression intensity (default 0.5)").option(
|
|
3342
|
+
"--active-speaker",
|
|
3343
|
+
"detect the active speaker in multi-person video"
|
|
3344
|
+
).option(
|
|
3345
|
+
"--audio-duration <seconds>",
|
|
3346
|
+
"optional estimate hint; server verifies MCP billing duration"
|
|
3347
|
+
).option("--project <id>", "group in a project's AI Studio session").option("--session <id>", "AI Studio session id").option("--download <path>", "download the finished video").option("--no-wait", "submit and return the job id immediately").option("--estimate", "print the cost estimate and exit").action(async function(videoSource) {
|
|
3348
|
+
const ctx = buildContext(this);
|
|
3349
|
+
const opts = this.opts();
|
|
3350
|
+
const audioDuration = positiveNumber(
|
|
3351
|
+
opts.audioDuration,
|
|
3352
|
+
"--audio-duration"
|
|
3353
|
+
);
|
|
3354
|
+
if (opts.syncMode && !["loop", "bounce", "cut_off", "silence", "remap"].includes(
|
|
3355
|
+
opts.syncMode
|
|
3356
|
+
)) {
|
|
3357
|
+
throw new UsageError(
|
|
3358
|
+
"--sync-mode must be loop, bounce, cut_off, silence, or remap."
|
|
3359
|
+
);
|
|
3360
|
+
}
|
|
3361
|
+
if (opts.temperature !== void 0) {
|
|
3362
|
+
const temperature = Number(opts.temperature);
|
|
3363
|
+
if (!Number.isFinite(temperature) || temperature < 0 || temperature > 1) {
|
|
3364
|
+
throw new UsageError("--temperature must be between 0 and 1.");
|
|
3365
|
+
}
|
|
3366
|
+
}
|
|
3367
|
+
if (opts.estimate) {
|
|
3368
|
+
const estimate = await ctx.client.callTool(
|
|
3369
|
+
"get_model_costs",
|
|
3370
|
+
compact({
|
|
3371
|
+
model_id: "sync-lipsync-2",
|
|
3372
|
+
type: "video",
|
|
3373
|
+
duration_seconds: audioDuration
|
|
3374
|
+
})
|
|
3375
|
+
);
|
|
3376
|
+
emit(ctx.out, {
|
|
3377
|
+
estimate,
|
|
3378
|
+
note: "No credits were spent (--estimate)."
|
|
3379
|
+
});
|
|
3380
|
+
return;
|
|
3381
|
+
}
|
|
3382
|
+
const [[videoUrl], [audioUrl]] = await Promise.all([
|
|
3383
|
+
resolveRefs(ctx, [videoSource]),
|
|
3384
|
+
resolveRefs(ctx, [opts.audio])
|
|
3385
|
+
]);
|
|
3386
|
+
capture("cli_avatar", { step: "sync_lipsync" });
|
|
3387
|
+
const submitted = await ctx.client.callTool(
|
|
3388
|
+
"generate_sync_lipsync_video",
|
|
3389
|
+
compact({
|
|
3390
|
+
video_url: videoUrl,
|
|
3391
|
+
audio_url: audioUrl,
|
|
3392
|
+
audio_duration_seconds: audioDuration,
|
|
3393
|
+
sync_mode: opts.syncMode,
|
|
3394
|
+
temperature: opts.temperature !== void 0 ? Number(opts.temperature) : void 0,
|
|
3395
|
+
active_speaker: opts.activeSpeaker ? true : void 0,
|
|
3396
|
+
project_id: opts.project,
|
|
3397
|
+
session_id: opts.session
|
|
3398
|
+
})
|
|
3399
|
+
);
|
|
3400
|
+
await handleAsyncJob(ctx, submitted, {
|
|
3401
|
+
wait: opts.wait !== false,
|
|
3402
|
+
download: opts.download,
|
|
3403
|
+
label: "Lip-syncing video"
|
|
3404
|
+
});
|
|
3405
|
+
});
|
|
3406
|
+
avatar.command("render <avatar_video_id>").description(
|
|
3407
|
+
"Render an avatar video with VEED Fabric (spends credits; waits by default)"
|
|
3408
|
+
).option("--resolution <res>", '"480p" | "720p" (default 720p)').option("--no-wait", "queue the render and return immediately").action(async function(avatarVideoId) {
|
|
2934
3409
|
const ctx = buildContext(this);
|
|
2935
3410
|
const opts = this.opts();
|
|
2936
3411
|
capture("cli_avatar", { step: "render" });
|
|
2937
3412
|
const started = await ctx.client.callTool(
|
|
2938
3413
|
"render_avatar_video",
|
|
2939
|
-
compact({
|
|
3414
|
+
compact({
|
|
3415
|
+
avatar_video_id: avatarVideoId,
|
|
3416
|
+
resolution: opts.resolution
|
|
3417
|
+
})
|
|
2940
3418
|
);
|
|
2941
3419
|
if (opts.wait === false) {
|
|
2942
3420
|
emit(
|
|
2943
3421
|
ctx.out,
|
|
2944
3422
|
started,
|
|
2945
|
-
(o) => note(
|
|
3423
|
+
(o) => note(
|
|
3424
|
+
o,
|
|
3425
|
+
`Render queued. Check with: videodraft avatar get ${avatarVideoId}`
|
|
3426
|
+
)
|
|
2946
3427
|
);
|
|
2947
3428
|
return;
|
|
2948
3429
|
}
|
|
@@ -2953,21 +3434,31 @@ function registerAvatarCommands(program) {
|
|
|
2953
3434
|
const status = await ctx.client.callTool("get_avatar_video", {
|
|
2954
3435
|
avatar_video_id: avatarVideoId
|
|
2955
3436
|
});
|
|
2956
|
-
const exportStatus = String(
|
|
3437
|
+
const exportStatus = String(
|
|
3438
|
+
status?.status ?? status?.export_status ?? status?.data?.export_status ?? "unknown"
|
|
3439
|
+
);
|
|
2957
3440
|
spin.update(`Rendering avatar video \u2014 ${exportStatus}`);
|
|
2958
3441
|
if (exportStatus === "completed") {
|
|
2959
3442
|
spin.stop();
|
|
2960
|
-
const media = buildMediaDescriptors(
|
|
3443
|
+
const media = buildMediaDescriptors(
|
|
3444
|
+
extractOutputUrls(status),
|
|
3445
|
+
"video"
|
|
3446
|
+
);
|
|
2961
3447
|
emit(ctx.out, { ...status, output_media: media }, (o) => {
|
|
2962
3448
|
note(o, fmt.green(o, "Avatar render completed."));
|
|
2963
|
-
if (status?.video_url)
|
|
3449
|
+
if (status?.video_url)
|
|
3450
|
+
process.stdout.write(`${status.video_url}
|
|
2964
3451
|
`);
|
|
2965
3452
|
});
|
|
2966
3453
|
return;
|
|
2967
3454
|
}
|
|
2968
3455
|
if (exportStatus === "failed") {
|
|
2969
3456
|
spin.stop();
|
|
2970
|
-
emit(
|
|
3457
|
+
emit(
|
|
3458
|
+
ctx.out,
|
|
3459
|
+
status,
|
|
3460
|
+
(o) => note(o, fmt.red(o, "Avatar render failed."))
|
|
3461
|
+
);
|
|
2971
3462
|
process.exitCode = 1;
|
|
2972
3463
|
return;
|
|
2973
3464
|
}
|
|
@@ -2976,7 +3467,9 @@ function registerAvatarCommands(program) {
|
|
|
2976
3467
|
`Timed out waiting for avatar render ${avatarVideoId} (last: ${exportStatus}).`
|
|
2977
3468
|
);
|
|
2978
3469
|
}
|
|
2979
|
-
await new Promise(
|
|
3470
|
+
await new Promise(
|
|
3471
|
+
(r) => setTimeout(r, Math.max(ctx.intervalMs, 5e3))
|
|
3472
|
+
);
|
|
2980
3473
|
}
|
|
2981
3474
|
} catch (err) {
|
|
2982
3475
|
spin.stop();
|
|
@@ -2985,7 +3478,9 @@ function registerAvatarCommands(program) {
|
|
|
2985
3478
|
});
|
|
2986
3479
|
avatar.command("get <avatar_video_id>").description("Fetch one avatar video (status + video_url when rendered)").action(async function(avatarVideoId) {
|
|
2987
3480
|
const ctx = buildContext(this);
|
|
2988
|
-
const result = await ctx.client.callTool("get_avatar_video", {
|
|
3481
|
+
const result = await ctx.client.callTool("get_avatar_video", {
|
|
3482
|
+
avatar_video_id: avatarVideoId
|
|
3483
|
+
});
|
|
2989
3484
|
const media = buildMediaDescriptors(extractOutputUrls(result), "video");
|
|
2990
3485
|
emit(ctx.out, { ...result, output_media: media });
|
|
2991
3486
|
});
|
|
@@ -3009,7 +3504,7 @@ function registerAvatarCommands(program) {
|
|
|
3009
3504
|
}
|
|
3010
3505
|
|
|
3011
3506
|
// src/commands/skills.ts
|
|
3012
|
-
import
|
|
3507
|
+
import fs6 from "fs";
|
|
3013
3508
|
import path4 from "path";
|
|
3014
3509
|
import os2 from "os";
|
|
3015
3510
|
import { fileURLToPath } from "url";
|
|
@@ -3022,22 +3517,22 @@ function bundledSkillDir() {
|
|
|
3022
3517
|
// src/commands in dev (tsx)
|
|
3023
3518
|
];
|
|
3024
3519
|
for (const candidate of candidates) {
|
|
3025
|
-
if (
|
|
3520
|
+
if (fs6.existsSync(path4.join(candidate, "SKILL.md"))) return candidate;
|
|
3026
3521
|
}
|
|
3027
3522
|
throw new CliError("Bundled skill not found (package is missing skills/videodraft).");
|
|
3028
3523
|
}
|
|
3029
3524
|
function bundledSkillFiles() {
|
|
3030
|
-
if ('{"SKILL.md":"---\\nname: videodraft\\ndescription: Create AI videos, images, voiceovers, music, sound effects, dialogue, dubbing, storyboards, avatar videos and product/ad videos with VideoDraft. Use when the user mentions VideoDraft, or asks to generate/make a video, video ad, explainer, storyboard, talking-head/avatar video, AI image, voiceover/TTS, background music, sound effects, dialogue audio, voice changing, or dubbing \u2014 including batch/programmatic video generation in scripts or CI. Works via the `videodraft` CLI (preferred in terminals) or the VideoDraft MCP connector.\\n---\\n\\n# VideoDraft\\n\\nVideoDraft is an AI video creation platform where asset generation is the priority lane:\\n\\n- **Asset generation**: standalone images, video clips, voiceovers, music, sound effects, dialogue, voice-changed audio, dubbed media, upscales, and image descriptions. This is the fastest and most important lane. Treat these as complete deliverables when the user asks for assets.\\n- **Asset I/O**: upload local files, download outputs, auto-upload local references, and save generated media where the user can see it.\\n- **Project production**: idea \u2192 script \u2192 storyboard (scenes + shot images) \u2192 project data \u2192 production timeline \u2192 exported MP4. Use this only when the user asks for a story, storyboard, editable project, timeline, or final video.\\n\\n## How to connect\\n\\nTwo equivalent surfaces (same backend, same credits, same projects):\\n\\n1. **CLI** (preferred when you have a shell): run `videodraft` if it\'s on PATH; otherwise `npx -y videodraft@latest` runs it with no install (needs Node \u226520; the `-y` skips npx\'s install prompt so it runs non-interactively; the package is fetched on first use and cached). For heavy use, `npm install -g videodraft`. If there\'s no Node/shell here but the MCP connector below is available, use that instead; if neither works, tell the user how to install (https://videodraft.ai/cli).\\n - Auth \u2014 pick by context, don\'t guess:\\n \u2022 INTERACTIVE (a human is in the session, e.g. Claude Code / Codex): on exit code 3 (\\"not authenticated\\"), tell the user to run `videodraft login` in their terminal \u2014 it opens their browser for a one-click VideoDraft sign-in (OAuth), no key to copy. Wait for them to confirm it succeeded, then retry the command. This is the preferred path when the user is present.\\n \u2022 HEADLESS / CI (no browser): set `VIDEODRAFT_API_KEY=vd_mcp_...` (a token the user mints at https://app.videodraft.ai/mcp-keys).\\n \u2022 SECURITY: never ask the user to paste a `vd_mcp_...` token into the chat \u2014 use browser `login` or the env var so the token never lands in the transcript.\\n - Every command accepts `--json` (parse this, don\'t scrape text). Exit codes: 0 ok, 1 error, 2 usage, 3 auth (see Auth above), 4 insufficient credits (\u2192 tell the user, don\'t retry).\\n - Tool discovery: start with `videodraft tools list` for the grouped catalog, then narrow with `videodraft tools list --lane assets`, `--lane asset_io`, `--lane project_data`, or `--lane production`.\\n - Asset lane: `videodraft generate image|video|voiceover|music|sound-effect|dialogue|voice-changer|dub`, `videodraft upload`, and `videodraft download`.\\n - Full API access: `videodraft tools schema <name>`, `videodraft call <tool> --args \'<json>\'`.\\n2. **MCP connector**: if VideoDraft MCP tools (e.g. `generate_storyboard_from_idea`) are available, call them directly \u2014 the CLI\'s curated commands map 1:1 onto these tools.\\n\\n## First decision: asset or project?\\n\\n- **One standalone asset** (image, clip, voiceover, music track, sound effect, dialogue track, voice-changed file, dubbed media file, upscale, or description): generate it directly. Do NOT create a project.\\n - `videodraft generate image \\"a red fox in snow, cinematic\\" --ar 16:9 --download ./out/`\\n - `videodraft generate video \\"slow dolly over a misty lake\\" --model google-veo3.1 --duration 6 --download ./out/`\\n- **A small set of related assets**: still stay in the asset lane. Use an AI Studio session if you need to group related generations, but do not make a storyboard/project unless the user asks for one.\\n- **A multi-scene video / ad / explainer, storyboard, timeline, or final exported video**: create a project so the work stays organized, editable in the web app, and exportable.\\n - `videodraft create \\"30s launch video for our espresso machine\\" --ar 9:16`\\n- **Just a script** (no video asked for): `videodraft create \\"...\\" --script-only`. Stop at the script \u2014 do not build a storyboard the user didn\'t ask for.\\n- **Iterating on existing work**: find it first (`videodraft projects list`) and reuse that project. Never create a new project to change an existing one.\\n\\n## Credits: confirm before spending\\n\\nGeneration costs credits (video is per-second; shot-image batches are the largest single spend). Before anything expensive:\\n\\n1. `videodraft credits` \u2014 check the balance.\\n2. `videodraft generate video \\"...\\" --estimate` or `videodraft costs <model> --duration 8 --resolution 1080p` \u2014 get the quote. For ElevenLabs audio, use `--type audio` plus `--duration`, `--length`, or `--chars`.\\n3. Tell the user the model + settings + rough cost and get a go-ahead. Ask rather than assume aspect ratio, duration, and model when they matter.\\n4. Honor the user\'s stated preference on confirmation. If they tell you to stop asking (e.g. \\"don\'t ask me\\", \\"just go\\", \\"stop confirming cost\\"), proceed without a go-ahead for the rest of the session. If they say to always confirm, confirm every spend. Default when unspecified: confirm the first spend of a session and any unusually large one (a shot-image batch, a long or 1080p video).\\n\\n`videodraft models image|video|audio` lists every model with its supported inputs (aspect ratios, resolutions, reference limits, audio billing inputs) \u2014 consult it instead of guessing capabilities.\\n\\n## Async jobs\\n\\nImage/video generation is asynchronous: commands submit a job and **wait by default**, printing output URLs (and saving files with `--download`). In scripts/CI prefer explicit control:\\n\\n```bash\\nJOB=$(videodraft generate image \\"...\\" --no-wait --json | jq -r .job_id)\\nvideodraft wait \\"$JOB\\" --download \\"./outputs/{job_id}_{index}.{ext}\\" --json\\n```\\n\\nFor MANY jobs: submit each with `--no-wait`, collect ALL with one command \u2014 `videodraft wait <id1> <id2> ...` polls every job from one process with one batched request per tick. Do NOT spawn parallel `wait`/`generate --wait` processes for a batch.\\n\\nIf a wait times out, the job is still running server-side \u2014 `videodraft status <job_id>` later. Never re-submit just because a wait timed out (that double-spends credits).\\n\\n## Local files and reference images\\n\\nReference inputs must be public URLs. The CLI uploads local files automatically wherever a URL is expected (`--ref photo.jpg`, `--start-image frame.png`), or explicitly:\\n\\n```bash\\nURL=$(videodraft upload ./product.png --json | jq -r .url)\\n```\\n\\nNever silently drop a reference you couldn\'t upload \u2014 stop and tell the user. Never upload a user\'s file to a third-party host.\\n\\nWhen the user attaches media, classify each item before acting: a recurring **visual asset** (character/product/location/style), actual **footage to place as shots**, or **inspiration only**. See [references/pipeline.md](references/pipeline.md) for how each role flows into a project.\\n\\n## Showing media to the user\\n\\nGenerated media is **not** displayed in the chat automatically \u2014 you decide what to show. To preview an asset inline, save it locally (use `--download` so it lands under `media/`) and reference its **local path** as a Markdown link with a **leading `./`**:\\n\\n```\\n[ferrari shot](./media/ferrari_01.png) \u2190 image card\\n[the clip](./media/clip.mp4) \u2190 video player\\n[voiceover](./media/vo.mp3) \u2190 audio player\\n```\\n\\nPut the Markdown link **in your message text** \u2014 video and audio embed exactly like images. Do **not** use `SendUserFile` (or other file-send tools) to display media: that renders inside a collapsible tool card and gets buried in the tool list. The Markdown link in your prose is what produces the inline card.\\n\\nUse the path you saved to: a **workspace-relative** path (`./media/clip.mp4`, or `./<any-folder>/clip.mp4` \u2014 any folder in the workspace works), or the **absolute** path for a file outside the workspace (e.g. `/Users/you/Desktop/clip.mp4` or another workspace\'s path). Both render. Show the finished results worth showing (and only those \u2014 not every intermediate job). A bare CDN URL or a JSON dump of output URLs does **not** render; the local-path Markdown link is what produces an inline card.\\n\\n## The full pipeline (idea \u2192 MP4)\\n\\n```bash\\nvideodraft credits\\nvideodraft create \\"<idea>\\" --ar 9:16 # project: script \u2192 visual assets \u2192 storyboard\\nvideodraft shots <project_id> --grid --estimate # cost preview, confirm with user\\nvideodraft shots <project_id> --grid # batch shot images (waits, writes onto shot cards)\\nvideodraft produce <project_id> # voiceovers + captions + production timeline\\nvideodraft export <project_id> --download final.mp4\\n```\\n\\nOptional between produce and export: per-shot motion clips (`videodraft generate video ... --project <id>` then place it with `videodraft attach <project> --scene N --shot M --media <url|file> --type video --duration <s>`), music (`videodraft generate music \\"...\\" --attach <project_id>`), and standalone audio assets (`generate sound-effect`, `generate dialogue`, `generate voice-changer`, `generate dub`). Details, per-step tools and editing rules: [references/pipeline.md](references/pipeline.md).\\n\\nAvatar/talking-head videos are their own short flow: `videodraft avatar script` \u2192 `avatar create` \u2192 `avatar render` (paid step).\\n\\n## Working with project data\\n\\nA project is one JSON blob (script, storyboard scenes, shot cards, visual assets, production timeline). To inspect: `videodraft projects get <id>`. To edit: fetch `--raw`, modify, then `videodraft call update_project` \u2014 objects deep-merge, **arrays replace wholesale** (send the complete `storyboard.scenes` array to change one scene). Snapshot first with `videodraft checkpoint create <id>` before risky edits. Schema reference: `videodraft call get_project_schema`.\\n\\n## More\\n\\n- [references/pipeline.md](references/pipeline.md) \u2014 project data model, step-by-step tools, attaching media, editing safely\\n- [references/models.md](references/models.md) \u2014 choosing image/video models, pricing patterns, voices and styles\\n- [references/examples.md](references/examples.md) \u2014 recipes: batch product videos from a CSV, talking-head from a script, changelog video in CI\\n","references/examples.md":"# Recipes\\n\\nWorking patterns for common asks. All assume auth (`videodraft login` once, or `VIDEODRAFT_API_KEY` in the environment) and use `--json` for parsing.\\n\\n## 1. Batch product videos from a CSV\\n\\nOne 9:16 product clip per row of `products.csv` (`name,image_url,tagline`):\\n\\n```bash\\n#!/usr/bin/env bash\\nset -euo pipefail\\nmkdir -p outputs\\n\\nwhile IFS=, read -r name image tagline; do\\n job=$(videodraft generate video \\\\\\n \\"Premium product shot of ${name}: ${tagline}. Slow orbit, studio lighting.\\" \\\\\\n --model google-veo3.1 --ar 9:16 --duration 6 \\\\\\n --start-image \\"$image\\" \\\\\\n --no-wait --json | jq -r .job_id)\\n echo \\"$name,$job\\" >> outputs/jobs.csv\\ndone < <(tail -n +2 products.csv)\\n\\n# Collect ALL results with ONE process (batched polling \u2014 one request per tick)\\nvideodraft wait $(cut -d, -f2 outputs/jobs.csv) \\\\\\n --download \\"outputs/{job_id}_{index}.{ext}\\" --json > outputs/results.json\\n# map job ids back to product names via outputs/jobs.csv\\n```\\n\\nSubmit-then-collect parallelizes server-side generation; the single multi-id `wait` keeps it to one local process and one batched poll request per tick no matter how many jobs. Estimate first: `videodraft costs google-veo3.1 --type video --duration 6` \xD7 rows, and confirm with the user.\\n\\n## 2. Full marketing video from one idea\\n\\n```bash\\nvideodraft credits --json\\nvideodraft create \\"30-second launch video for Solace, a sleep-tracking ring. Calm, premium, dark palette.\\" \\\\\\n --ar 9:16 --style cinematic --json > project.json\\nPROJECT=$(jq -r .project_id project.json)\\n\\nvideodraft shots \\"$PROJECT\\" --grid --estimate # show the user the cost; get a go-ahead\\nvideodraft shots \\"$PROJECT\\" --grid\\nvideodraft produce \\"$PROJECT\\"\\nvideodraft generate music \\"minimal ambient, warm pads, 60 BPM\\" --attach \\"$PROJECT\\"\\nvideodraft export \\"$PROJECT\\" --download solace-launch.mp4\\n```\\n\\nThe project stays editable at the URL in `project.json` (`.urls`) \u2014 hand it to the user for tweaks.\\n\\n## 3. Talking-head (avatar) video\\n\\n```bash\\nSCRIPT=$(videodraft avatar script \\"why our espresso subscription saves you money\\" --style ad-style --json | jq -r .script)\\nAVATAR=$(videodraft avatar create ./founder.jpg --script \\"$SCRIPT\\" --ar 9:16 --json | jq -r .avatar_video_id)\\nvideodraft avatar render \\"$AVATAR\\" --resolution 720p # paid step \u2014 confirm cost first (~20 credits/sec)\\n```\\n\\n## 4. Changelog video in CI\\n\\nIn a GitHub Action with `VIDEODRAFT_API_KEY` set as a secret:\\n\\n```bash\\nNOTES=$(git log --oneline v1.2.0..HEAD | head -20)\\nvideodraft create \\"Weekly product update video. Energetic, 20 seconds. Changes: ${NOTES}\\" --ar 16:9 --json > p.json\\nPROJECT=$(jq -r .project_id p.json)\\nvideodraft shots \\"$PROJECT\\" && videodraft produce \\"$PROJECT\\"\\nvideodraft export \\"$PROJECT\\" --download changelog.mp4 --wait-timeout 30m\\n```\\n\\n## 5. Variations and picking a winner\\n\\n```bash\\nvideodraft generate image \\"logo concept: minimalist fox, geometric\\" --num 4 --download \\"./concepts/{job_id}_{index}.{ext}\\" --json\\n# Show all 4 to the user; regenerate the chosen one at higher res:\\nvideodraft generate image \\"<same prompt>\\" --model nano-banana-pro --resolution 4K\\n```\\n\\n## 6. Reaching tools without a curated command\\n\\n```bash\\nvideodraft tools list --json | jq -r \'.[].name\'\\nvideodraft tools schema attach_media_to_shot --json\\nvideodraft call attach_media_to_shot --args \'{\\"project_id\\":\\"...\\",\\"scene_index\\":0,\\"shot_index\\":1,\\"media_url\\":\\"https://...\\",\\"media_type\\":\\"video\\",\\"duration_seconds\\":6}\'\\n```\\n\\nAnything the VideoDraft MCP exposes \u2014 character studio, product studio, timeline editing \u2014 is reachable this way even before it gets a curated command.\\n","references/models.md":"# Choosing models (and predicting cost)\\n\\nAlways consult the live catalog instead of memorizing this page \u2014 models change weekly:\\n\\n```bash\\nvideodraft models image --json # every image model + inputs (aspect ratios, resolutions, max refs)\\nvideodraft models video --json # every video model + inputs + per-second pricing metadata\\nvideodraft models audio --json # standalone audio/media models + pricing inputs\\nvideodraft models voices --json # TTS voices\\nvideodraft models styles --json # visual style presets\\n```\\n\\n## Defaults (safe starting points)\\n\\n- **Image**: `nano-banana-2` (the platform default, 1K, up to 14 reference images). Use `--num 1..4` for variations of one prompt in a single call \u2014 never loop for variations. `nano-banana-2-lite` is the fastest/cheapest Google direct image model (1K only, up to 14 reference images).\\n- **Video**: `google-veo3.1` at fast quality (6s / 720p) \u2014 the platform default. `gemini-omni-flash` is Google\'s any-to-any multimodal video model (text/image/video \u2192 video, auto or 3-10s, 720p, audio always on).\\n- **Voiceover**: ElevenLabs Brittney (default voice). 10 credits per 1000 characters (30 per 1000 for cloned `custom-*` voices; min 1, pro-rated); free on your own key under BYOK.\\n- **Music**: `lyria-3-clip-preview` (30s, 10 credits); `lyria-3-pro-preview` for 180s/quality (15 credits); `elevenlabs-music` for music that can include vocals/lyrics (per started minute).\\n- **ElevenLabs audio**: `generate sound-effect`, `generate dialogue`, `generate voice-changer`, and `generate dub` are synchronous audio/media calls. Voice changer and dubbing require the source media duration in seconds for billing and currently accept source media up to 300s.\\n\\n## Capability gotchas\\n\\n- Each model\'s `inputs` block is authoritative: supported `aspect_ratios`, `resolutions`, `quality_options`, `start_frame`/`end_frame`, `max_reference_images/videos/audio`, `multi_prompt`, `audio_toggle`. Passing an unsupported input fails with a clear error \u2014 check first, don\'t trial-and-error paid calls.\\n- Most video models support only 16:9 / 9:16 / 1:1. A 3:4 request hard-fails on most.\\n- `--seed` reproduces a specific output on models that support it (e.g. Flux, Ideogram V4); everything else ignores it. You do not need a seed for variation \u2014 `--num` already varies.\\n- `--rendering-speed` applies to Ideogram (V3: `Default`/`Turbo`/`Quality`; V4: `Turbo`/`Balanced`/`Quality`) and affects image cost \u2014 pass it to `videodraft costs ... --rendering-speed <tier>` for an accurate estimate. Always trust `videodraft models image --json` over this list; new models and tiers appear there the moment the platform ships them, with no CLI update.\\n- `seedream-v5-pro` supports unified text-to-image and reference-image editing with up to 10 image references. Use `--resolution 1K` for 7 credits/image or `--resolution 2K` for 14 credits/image.\\n- Reference inputs: `--ref <img>` (images), `--ref-video <v>` (Gemini Omni Flash, Seedance 2, Kling O3, Wan 2.7), `--ref-audio <a>` (Seedance 2). The CLI uploads local files for all of these, so you can pass a path or a URL. `--segment \\"<prompt>:<seconds>\\"` (repeatable) drives multi-prompt models (Kling 3.0 / 3.0 Turbo / O3); total 3-15s. `generate image --video-ref` is the nano-banana-2 video reference.\\n- The top-level prompt is OPTIONAL for `generate video` with multi-prompt models and for Kling 3.0 Turbo (`--model kling-v3-turbo`) image-to-video \u2014 a `--segment`-only or `--start-image`-only call is valid. Every other model still needs a prompt; the server enforces per-model rules.\\n- AI Production: `videodraft produce <project> --mode full_video` generates one Seedance 2 video per scene; poll with `videodraft generations`, then `videodraft finalize <project>` swaps them into the timeline before `export`.\\n\\n## Cost model\\n\\n- Images: per image (\xD7 `--num`). Matrix-priced models (GPT-Image, Nano Banana Pro, Seedream v5 Pro) vary by resolution/quality.\\n- Video: usually credits/second \xD7 duration; rate depends on model + resolution + quality + native audio on/off.\\n- Shot-image batches: one image per shot (+1 grid image per scene in `--grid` mode) \u2014 the largest single spend in the pipeline.\\n- Avatar renders: ~10 credits/sec at 480p, ~20/sec at 720p.\\n- Voiceover TTS: 10 credits per 1000 characters for standard voices, 30 per 1000 for cloned `custom-*` voices (min 1, pro-rated); applies to standalone voiceovers AND per-scene narration during `produce`. Silent tracks are free. Voice cloning itself is a flat 150 credits per clone.\\n- Lyria music: flat per track, 10 credits (clip) / 15 credits (pro).\\n- ElevenLabs audio: sound effects are per second, dialogue is per character, music/voice-changer/dubbing are per started minute. Voice changer and dubbing reject source media above 300s in the current synchronous flow.\\n- Upscales: priced by scale and source size.\\n\\nQuote before spending:\\n\\n```bash\\nvideodraft costs google-veo3.1 --type video --duration 8 --resolution 1080p --audio\\nvideodraft costs elevenlabs-dubbing --type audio --duration 60\\nvideodraft costs elevenlabs-dialogue --type audio --chars 350\\nvideodraft costs voiceover --type audio --chars 800 # TTS: 10 cr / 1000 chars\\nvideodraft generate video \\"...\\" --estimate # same quote, inline\\nvideodraft credits # current balance\\n```\\n","references/pipeline.md":"# VideoDraft pipeline reference\\n\\nEverything here works through the CLI (`videodraft <command>` / `videodraft call <tool>`) or the MCP connector (tool names in backticks). One backend; pick the surface you have.\\n\\n## Stages and their tools\\n\\n| Stage | CLI | Underlying tool |\\n|---|---|---|\\n| Idea \u2192 full storyboard project | `videodraft create \\"<idea>\\"` | `generate_storyboard_from_idea` |\\n| Idea \u2192 script only (stop there) | `videodraft create \\"<idea>\\" --script-only` | `generate_script_from_idea` |\\n| Footage IS the video | `videodraft call generate_storyboard_from_media` | `generate_storyboard_from_media` |\\n| Batch shot images | `videodraft shots <project>` | `generate_shot_images` |\\n| One shot image | `videodraft generate image --project <id> --scene N --shot M` | `generate_image` |\\n| Produce (voiceover, captions, timeline) | `videodraft produce <project>` | `produce_project` |\\n| Per-shot motion prompts | `videodraft video-prompts <project>` | `generate_video_prompts` |\\n| Motion clip for a shot | `videodraft generate video --project <id>` | `generate_video` |\\n| Attach a finished clip to the timeline | `videodraft attach <project> --scene N --shot M --media <url> --type video` | `attach_media_to_shot` |\\n| Background music | `videodraft generate music --attach <project>` | `generate_music` / `set_background_music` |\\n| Sound effect | `videodraft generate sound-effect \\"...\\"` | `generate_sound_effect` |\\n| Dialogue audio | `videodraft generate dialogue --line \\"voice:text\\"` | `generate_dialogue` |\\n| Voice changer | `videodraft generate voice-changer <audio>` | `change_voice` |\\n| Dubbing | `videodraft generate dub <audio_or_video>` | `dub_media` |\\n| Scene voiceover | `videodraft generate voiceover --project <id> --scene N` | `generate_voiceover` |\\n| Final MP4 | `videodraft export <project>` | `export_video` + `check_export_status` |\\n\\n## Rules that prevent broken results\\n\\n- **The storyboard is generated FROM the script**, never from the raw idea. `videodraft create` runs the whole chain correctly. Don\'t call `generate_storyboard_scenes` with a raw idea as the \\"script\\".\\n- **Visual consistency**: never generate a storyboard shot in isolation. Shot prompts carry `[[asset:Name]]` / `[[shot:X-Y]]` tags that `generate_shot_images` resolves against the project\'s visual assets and prior shots. When generating a single shot whose prompt has no tags, pass `--ref` images yourself (the project\'s visual assets and/or the previous shot\'s image \u2014 `projects get` exposes both). Grid mode (`--grid`) gives the strongest cross-shot consistency.\\n- **Hold off generating shot images while the user is still iterating** on storyboard structure.\\n- **produce \u2192 export ordering**: `export` requires a produced project where every production scene has timeline media. If `produce` returns `generating_shot_images`, poll the job ids it returns, then re-run produce.\\n- **Generated motion clips do not auto-attach**: after `generate video` completes, attach the clip with `attach_media_to_shot` (`media_type:\\"video\\"`, include `duration_seconds`) \u2014 it replaces the production timeline clip while keeping the storyboard still.\\n- **Timeouts on the one-shot create**: if `create` times out at the transport layer, the project was still created server-side \u2014 `videodraft projects list`, take the most recent, and resume with its id. Don\'t start a duplicate.\\n\\n## User-attached media: classify roles first\\n\\nFor EACH attached file decide:\\n\\n- **visual_asset** \u2014 recurring reference (character / product / location / style). Upload, then pass in `visual_assets` of `generate_storyboard_from_idea` (via `videodraft call`), or add to an existing project with `add_visual_assets`. Type must be one of `character | object | location | style | custom` with a short name + concrete description.\\n- **shot** \u2014 the media IS footage for the video. Whole video = footage \u2192 `generate_storyboard_from_media`. Idea + footage \u2192 `generate_storyboard_from_idea` with `shot_media`. Existing storyboard \u2192 `attach_media_to_shots`.\\n- **reference** \u2014 inspiration only \u2192 fold a description into the idea/instructions; don\'t place it as a shot or asset.\\n\\nAmbiguous (e.g. a person holding a product)? Ask the user.\\n\\nUploads persist in the media library \u2014 recall later with `videodraft media list`.\\n\\n## Editing project data safely\\n\\n1. `videodraft call get_project_schema` \u2014 read the structure once per session.\\n2. `videodraft projects get <id> --raw` \u2014 the exact editable blob.\\n3. Modify; then `videodraft call update_project --stdin` with `{\\"project_id\\": \\"...\\", \\"data\\": {...}}`.\\n - Objects deep-merge key-by-key; **arrays replace wholesale** \u2014 send the complete array you\'re changing (e.g. all of `storyboard.scenes`).\\n - Scene shot arrays (`image_prompt` / `shot_types` / `shot_actions` / `search_prompt` / `preview_media`) are auto-aligned; fix-ups come back as warnings.\\n4. Snapshot before risky edits: `videodraft checkpoint create <id> --name \\"before re-script\\"`. Restore with `videodraft checkpoint restore <id> <version>`.\\n\\n## AI Studio sessions (standalone generations)\\n\\nProject generations group automatically. For standalone work in a long conversation, create one session up front and reuse it:\\n\\n```bash\\nSESSION=$(videodraft call create_ai_studio_session --arg name=\\"Fox brand explorations\\" --json | jq -r .session_id)\\nvideodraft generate image \\"...\\" --session \\"$SESSION\\"\\n```\\n"}') {
|
|
3031
|
-
return JSON.parse('{"SKILL.md":"---\\nname: videodraft\\ndescription: Create AI videos, images, voiceovers, music, sound effects, dialogue, dubbing, storyboards, avatar videos and product/ad videos with VideoDraft. Use when the user mentions VideoDraft, or asks to generate/make a video, video ad, explainer, storyboard, talking-head/avatar video, AI image, voiceover/TTS, background music, sound effects, dialogue audio, voice changing, or dubbing \u2014 including batch/programmatic video generation in scripts or CI. Works via the `videodraft` CLI (preferred in terminals) or the VideoDraft MCP connector.\\n---\\n\\n# VideoDraft\\n\\nVideoDraft is an AI video creation platform where asset generation is the priority lane:\\n\\n- **Asset generation**: standalone images, video clips, voiceovers, music, sound effects, dialogue, voice-changed audio, dubbed media, upscales, and image descriptions. This is the fastest and most important lane. Treat these as complete deliverables when the user asks for assets.\\n- **Asset I/O**: upload local files, download outputs, auto-upload local references, and save generated media where the user can see it.\\n- **Project production**: idea \u2192 script \u2192 storyboard (scenes + shot images) \u2192 project data \u2192 production timeline \u2192 exported MP4. Use this only when the user asks for a story, storyboard, editable project, timeline, or final video.\\n\\n## How to connect\\n\\nTwo equivalent surfaces (same backend, same credits, same projects):\\n\\n1. **CLI** (preferred when you have a shell): run `videodraft` if it\'s on PATH; otherwise `npx -y videodraft@latest` runs it with no install (needs Node \u226520; the `-y` skips npx\'s install prompt so it runs non-interactively; the package is fetched on first use and cached). For heavy use, `npm install -g videodraft`. If there\'s no Node/shell here but the MCP connector below is available, use that instead; if neither works, tell the user how to install (https://videodraft.ai/cli).\\n - Auth \u2014 pick by context, don\'t guess:\\n \u2022 INTERACTIVE (a human is in the session, e.g. Claude Code / Codex): on exit code 3 (\\"not authenticated\\"), tell the user to run `videodraft login` in their terminal \u2014 it opens their browser for a one-click VideoDraft sign-in (OAuth), no key to copy. Wait for them to confirm it succeeded, then retry the command. This is the preferred path when the user is present.\\n \u2022 HEADLESS / CI (no browser): set `VIDEODRAFT_API_KEY=vd_mcp_...` (a token the user mints at https://app.videodraft.ai/mcp-keys).\\n \u2022 SECURITY: never ask the user to paste a `vd_mcp_...` token into the chat \u2014 use browser `login` or the env var so the token never lands in the transcript.\\n - Every command accepts `--json` (parse this, don\'t scrape text). Exit codes: 0 ok, 1 error, 2 usage, 3 auth (see Auth above), 4 insufficient credits (\u2192 tell the user, don\'t retry).\\n - Tool discovery: start with `videodraft tools list` for the grouped catalog, then narrow with `videodraft tools list --lane assets`, `--lane asset_io`, `--lane project_data`, or `--lane production`.\\n - Asset lane: `videodraft generate image|video|voiceover|music|sound-effect|dialogue|voice-changer|dub`, `videodraft upload`, and `videodraft download`.\\n - Full API access: `videodraft tools schema <name>`, `videodraft call <tool> --args \'<json>\'`.\\n2. **MCP connector**: if VideoDraft MCP tools (e.g. `generate_storyboard_from_idea`) are available, call them directly \u2014 the CLI\'s curated commands map 1:1 onto these tools.\\n\\n## First decision: asset or project?\\n\\n- **One standalone asset** (image, clip, voiceover, music track, sound effect, dialogue track, voice-changed file, dubbed media file, upscale, or description): generate it directly. Do NOT create a project.\\n - `videodraft generate image \\"a red fox in snow, cinematic\\" --ar 16:9 --download ./out/`\\n - `videodraft generate video \\"slow dolly over a misty lake\\" --model google-veo3.1 --duration 6 --download ./out/`\\n- **A small set of related assets**: still stay in the asset lane. Use an AI Studio session if you need to group related generations, but do not make a storyboard/project unless the user asks for one.\\n- **A multi-scene video / ad / explainer, storyboard, timeline, or final exported video**: create a project so the work stays organized, editable in the web app, and exportable.\\n - `videodraft create \\"30s launch video for our espresso machine\\" --ar 9:16`\\n- **Just a script** (no video asked for): `videodraft create \\"...\\" --script-only`. Stop at the script \u2014 do not build a storyboard the user didn\'t ask for.\\n- **Iterating on existing work**: find it first (`videodraft projects list`) and reuse that project. Never create a new project to change an existing one.\\n\\n## Credits: confirm before spending\\n\\nGeneration costs credits (video is per-second; shot-image batches are the largest single spend). Before anything expensive:\\n\\n1. `videodraft credits` \u2014 check the balance.\\n2. `videodraft generate video \\"...\\" --estimate` or `videodraft costs <model> --duration 8 --resolution 1080p` \u2014 get the quote. For ElevenLabs audio, use `--type audio` plus `--duration`, `--length`, or `--chars`.\\n3. Tell the user the model + settings + rough cost and get a go-ahead. Ask rather than assume aspect ratio, duration, and model when they matter.\\n4. Honor the user\'s stated preference on confirmation. If they tell you to stop asking (e.g. \\"don\'t ask me\\", \\"just go\\", \\"stop confirming cost\\"), proceed without a go-ahead for the rest of the session. If they say to always confirm, confirm every spend. Default when unspecified: confirm the first spend of a session and any unusually large one (a shot-image batch, a long or 1080p video).\\n\\n`videodraft models image|video|audio` lists every model with its supported inputs (aspect ratios, resolutions, reference limits, audio billing inputs) \u2014 consult it instead of guessing capabilities.\\n\\n## Async jobs\\n\\nImage/video generation is asynchronous: commands submit a job and **wait by default**, printing output URLs (and saving files with `--download`). In scripts/CI prefer explicit control:\\n\\n```bash\\nJOB=$(videodraft generate image \\"...\\" --no-wait --json | jq -r .job_id)\\nvideodraft wait \\"$JOB\\" --download \\"./outputs/{job_id}_{index}.{ext}\\" --json\\n```\\n\\nFor MANY jobs: submit each with `--no-wait`, collect ALL with one command \u2014 `videodraft wait <id1> <id2> ...` polls every job from one process with one batched request per tick. Do NOT spawn parallel `wait`/`generate --wait` processes for a batch.\\n\\nIf a wait times out, the job is still running server-side \u2014 `videodraft status <job_id>` later. Never re-submit just because a wait timed out (that double-spends credits).\\n\\n## Local files and reference images\\n\\nReference inputs must be public URLs. The CLI uploads local files automatically wherever a URL is expected (`--ref photo.jpg`, `--start-image frame.png`), or explicitly:\\n\\n```bash\\nURL=$(videodraft upload ./product.png --json | jq -r .url)\\n```\\n\\nNever silently drop a reference you couldn\'t upload \u2014 stop and tell the user. Never upload a user\'s file to a third-party host.\\n\\nWhen the user attaches media, classify each item before acting: a recurring **visual asset** (character/product/location/style), actual **footage to place as shots**, or **inspiration only**. See [references/pipeline.md](references/pipeline.md) for how each role flows into a project.\\n\\n## Showing media to the user\\n\\nGenerated media is **not** displayed in the chat automatically \u2014 you decide what to show. To preview an asset inline, save it locally (use `--download` so it lands under `media/`) and reference its **local path** as a Markdown link with a **leading `./`**:\\n\\n```\\n[ferrari shot](./media/ferrari_01.png) \u2190 image card\\n[the clip](./media/clip.mp4) \u2190 video player\\n[voiceover](./media/vo.mp3) \u2190 audio player\\n```\\n\\nPut the Markdown link **in your message text** \u2014 video and audio embed exactly like images. Do **not** use `SendUserFile` (or other file-send tools) to display media: that renders inside a collapsible tool card and gets buried in the tool list. The Markdown link in your prose is what produces the inline card.\\n\\nUse the path you saved to: a **workspace-relative** path (`./media/clip.mp4`, or `./<any-folder>/clip.mp4` \u2014 any folder in the workspace works), or the **absolute** path for a file outside the workspace (e.g. `/Users/you/Desktop/clip.mp4` or another workspace\'s path). Both render. Show the finished results worth showing (and only those \u2014 not every intermediate job). A bare CDN URL or a JSON dump of output URLs does **not** render; the local-path Markdown link is what produces an inline card.\\n\\n## The full pipeline (idea \u2192 MP4)\\n\\n```bash\\nvideodraft credits\\nvideodraft create \\"<idea>\\" --ar 9:16 # project: script \u2192 visual assets \u2192 storyboard\\nvideodraft shots <project_id> --grid --estimate # cost preview, confirm with user\\nvideodraft shots <project_id> --grid # batch shot images (waits, writes onto shot cards)\\nvideodraft produce <project_id> # voiceovers + captions + production timeline\\nvideodraft export <project_id> --download final.mp4\\n```\\n\\nOptional between produce and export: per-shot motion clips (`videodraft generate video ... --project <id>` then place it with `videodraft attach <project> --scene N --shot M --media <url|file> --type video --duration <s>`), music (`videodraft generate music \\"...\\" --attach <project_id>`), and standalone audio assets (`generate sound-effect`, `generate dialogue`, `generate voice-changer`, `generate dub`). Details, per-step tools and editing rules: [references/pipeline.md](references/pipeline.md).\\n\\nAvatar/talking-head videos are their own short flow: `videodraft avatar script` \u2192 `avatar create` \u2192 `avatar render` (paid step).\\n\\n## Working with project data\\n\\nA project is one JSON blob (script, storyboard scenes, shot cards, visual assets, production timeline). To inspect: `videodraft projects get <id>`. To edit: fetch `--raw`, modify, then `videodraft call update_project` \u2014 objects deep-merge, **arrays replace wholesale** (send the complete `storyboard.scenes` array to change one scene). Snapshot first with `videodraft checkpoint create <id>` before risky edits. Schema reference: `videodraft call get_project_schema`.\\n\\n## More\\n\\n- [references/pipeline.md](references/pipeline.md) \u2014 project data model, step-by-step tools, attaching media, editing safely\\n- [references/models.md](references/models.md) \u2014 choosing image/video models, pricing patterns, voices and styles\\n- [references/examples.md](references/examples.md) \u2014 recipes: batch product videos from a CSV, talking-head from a script, changelog video in CI\\n","references/examples.md":"# Recipes\\n\\nWorking patterns for common asks. All assume auth (`videodraft login` once, or `VIDEODRAFT_API_KEY` in the environment) and use `--json` for parsing.\\n\\n## 1. Batch product videos from a CSV\\n\\nOne 9:16 product clip per row of `products.csv` (`name,image_url,tagline`):\\n\\n```bash\\n#!/usr/bin/env bash\\nset -euo pipefail\\nmkdir -p outputs\\n\\nwhile IFS=, read -r name image tagline; do\\n job=$(videodraft generate video \\\\\\n \\"Premium product shot of ${name}: ${tagline}. Slow orbit, studio lighting.\\" \\\\\\n --model google-veo3.1 --ar 9:16 --duration 6 \\\\\\n --start-image \\"$image\\" \\\\\\n --no-wait --json | jq -r .job_id)\\n echo \\"$name,$job\\" >> outputs/jobs.csv\\ndone < <(tail -n +2 products.csv)\\n\\n# Collect ALL results with ONE process (batched polling \u2014 one request per tick)\\nvideodraft wait $(cut -d, -f2 outputs/jobs.csv) \\\\\\n --download \\"outputs/{job_id}_{index}.{ext}\\" --json > outputs/results.json\\n# map job ids back to product names via outputs/jobs.csv\\n```\\n\\nSubmit-then-collect parallelizes server-side generation; the single multi-id `wait` keeps it to one local process and one batched poll request per tick no matter how many jobs. Estimate first: `videodraft costs google-veo3.1 --type video --duration 6` \xD7 rows, and confirm with the user.\\n\\n## 2. Full marketing video from one idea\\n\\n```bash\\nvideodraft credits --json\\nvideodraft create \\"30-second launch video for Solace, a sleep-tracking ring. Calm, premium, dark palette.\\" \\\\\\n --ar 9:16 --style cinematic --json > project.json\\nPROJECT=$(jq -r .project_id project.json)\\n\\nvideodraft shots \\"$PROJECT\\" --grid --estimate # show the user the cost; get a go-ahead\\nvideodraft shots \\"$PROJECT\\" --grid\\nvideodraft produce \\"$PROJECT\\"\\nvideodraft generate music \\"minimal ambient, warm pads, 60 BPM\\" --attach \\"$PROJECT\\"\\nvideodraft export \\"$PROJECT\\" --download solace-launch.mp4\\n```\\n\\nThe project stays editable at the URL in `project.json` (`.urls`) \u2014 hand it to the user for tweaks.\\n\\n## 3. Talking-head (avatar) video\\n\\n```bash\\nSCRIPT=$(videodraft avatar script \\"why our espresso subscription saves you money\\" --style ad-style --json | jq -r .script)\\nAVATAR=$(videodraft avatar create ./founder.jpg --script \\"$SCRIPT\\" --ar 9:16 --json | jq -r .avatar_video_id)\\nvideodraft avatar render \\"$AVATAR\\" --resolution 720p # paid step \u2014 confirm cost first (~20 credits/sec)\\n```\\n\\n## 4. Changelog video in CI\\n\\nIn a GitHub Action with `VIDEODRAFT_API_KEY` set as a secret:\\n\\n```bash\\nNOTES=$(git log --oneline v1.2.0..HEAD | head -20)\\nvideodraft create \\"Weekly product update video. Energetic, 20 seconds. Changes: ${NOTES}\\" --ar 16:9 --json > p.json\\nPROJECT=$(jq -r .project_id p.json)\\nvideodraft shots \\"$PROJECT\\" && videodraft produce \\"$PROJECT\\"\\nvideodraft export \\"$PROJECT\\" --download changelog.mp4 --wait-timeout 30m\\n```\\n\\n## 5. Variations and picking a winner\\n\\n```bash\\nvideodraft generate image \\"logo concept: minimalist fox, geometric\\" --num 4 --download \\"./concepts/{job_id}_{index}.{ext}\\" --json\\n# Show all 4 to the user; regenerate the chosen one at higher res:\\nvideodraft generate image \\"<same prompt>\\" --model nano-banana-pro --resolution 4K\\n```\\n\\n## 6. Reaching tools without a curated command\\n\\n```bash\\nvideodraft tools list --json | jq -r \'.[].name\'\\nvideodraft tools schema attach_media_to_shot --json\\nvideodraft call attach_media_to_shot --args \'{\\"project_id\\":\\"...\\",\\"scene_index\\":0,\\"shot_index\\":1,\\"media_url\\":\\"https://...\\",\\"media_type\\":\\"video\\",\\"duration_seconds\\":6}\'\\n```\\n\\nAnything the VideoDraft MCP exposes \u2014 character studio, product studio, timeline editing \u2014 is reachable this way even before it gets a curated command.\\n","references/models.md":"# Choosing models (and predicting cost)\\n\\nAlways consult the live catalog instead of memorizing this page \u2014 models change weekly:\\n\\n```bash\\nvideodraft models image --json # every image model + inputs (aspect ratios, resolutions, max refs)\\nvideodraft models video --json # every video model + inputs + per-second pricing metadata\\nvideodraft models audio --json # standalone audio/media models + pricing inputs\\nvideodraft models voices --json # TTS voices\\nvideodraft models styles --json # visual style presets\\n```\\n\\n## Defaults (safe starting points)\\n\\n- **Image**: `nano-banana-2` (the platform default, 1K, up to 14 reference images). Use `--num 1..4` for variations of one prompt in a single call \u2014 never loop for variations. `nano-banana-2-lite` is the fastest/cheapest Google direct image model (1K only, up to 14 reference images).\\n- **Video**: `google-veo3.1` at fast quality (6s / 720p) \u2014 the platform default. `gemini-omni-flash` is Google\'s any-to-any multimodal video model (text/image/video \u2192 video, auto or 3-10s, 720p, audio always on).\\n- **Voiceover**: ElevenLabs Brittney (default voice). 10 credits per 1000 characters (30 per 1000 for cloned `custom-*` voices; min 1, pro-rated); free on your own key under BYOK.\\n- **Music**: `lyria-3-clip-preview` (30s, 10 credits); `lyria-3-pro-preview` for 180s/quality (15 credits); `elevenlabs-music` for music that can include vocals/lyrics (per started minute).\\n- **ElevenLabs audio**: `generate sound-effect`, `generate dialogue`, `generate voice-changer`, and `generate dub` are synchronous audio/media calls. Voice changer and dubbing require the source media duration in seconds for billing and currently accept source media up to 300s.\\n\\n## Capability gotchas\\n\\n- Each model\'s `inputs` block is authoritative: supported `aspect_ratios`, `resolutions`, `quality_options`, `start_frame`/`end_frame`, `max_reference_images/videos/audio`, `multi_prompt`, `audio_toggle`. Passing an unsupported input fails with a clear error \u2014 check first, don\'t trial-and-error paid calls.\\n- Most video models support only 16:9 / 9:16 / 1:1. A 3:4 request hard-fails on most.\\n- `--seed` reproduces a specific output on models that support it (e.g. Flux, Ideogram V4); everything else ignores it. You do not need a seed for variation \u2014 `--num` already varies.\\n- `--rendering-speed` applies to Ideogram (V3: `Default`/`Turbo`/`Quality`; V4: `Turbo`/`Balanced`/`Quality`) and affects image cost \u2014 pass it to `videodraft costs ... --rendering-speed <tier>` for an accurate estimate. Always trust `videodraft models image --json` over this list; new models and tiers appear there the moment the platform ships them, with no CLI update.\\n- `seedream-v5-pro` supports unified text-to-image and reference-image editing with up to 10 image references. Use `--resolution 1K` for 7 credits/image or `--resolution 2K` for 14 credits/image.\\n- Reference inputs: `--ref <img>` (images), `--ref-video <v>` (Gemini Omni Flash, Seedance 2, Kling O3, Wan 2.7), `--ref-audio <a>` (Seedance 2). The CLI uploads local files for all of these, so you can pass a path or a URL. `--segment \\"<prompt>:<seconds>\\"` (repeatable) drives multi-prompt models (Kling 3.0 / 3.0 Turbo / O3); total 3-15s. `generate image --video-ref` is the nano-banana-2 video reference.\\n- The top-level prompt is OPTIONAL for `generate video` with multi-prompt models and for Kling 3.0 Turbo (`--model kling-v3-turbo`) image-to-video \u2014 a `--segment`-only or `--start-image`-only call is valid. Every other model still needs a prompt; the server enforces per-model rules.\\n- AI Production: `videodraft produce <project> --mode full_video` generates one Seedance 2 video per scene; poll with `videodraft generations`, then `videodraft finalize <project>` swaps them into the timeline before `export`.\\n\\n## Cost model\\n\\n- Images: per image (\xD7 `--num`). Matrix-priced models (GPT-Image, Nano Banana Pro, Seedream v5 Pro) vary by resolution/quality.\\n- Video: usually credits/second \xD7 duration; rate depends on model + resolution + quality + native audio on/off.\\n- Shot-image batches: one image per shot (+1 grid image per scene in `--grid` mode) \u2014 the largest single spend in the pipeline.\\n- Avatar renders: ~10 credits/sec at 480p, ~20/sec at 720p.\\n- Voiceover TTS: 10 credits per 1000 characters for standard voices, 30 per 1000 for cloned `custom-*` voices (min 1, pro-rated); applies to standalone voiceovers AND per-scene narration during `produce`. Silent tracks are free. Voice cloning itself is a flat 150 credits per clone.\\n- Lyria music: flat per track, 10 credits (clip) / 15 credits (pro).\\n- ElevenLabs audio: sound effects are per second, dialogue is per character, music/voice-changer/dubbing are per started minute. Voice changer and dubbing reject source media above 300s in the current synchronous flow.\\n- Upscales: priced by scale and source size.\\n\\nQuote before spending:\\n\\n```bash\\nvideodraft costs google-veo3.1 --type video --duration 8 --resolution 1080p --audio\\nvideodraft costs elevenlabs-dubbing --type audio --duration 60\\nvideodraft costs elevenlabs-dialogue --type audio --chars 350\\nvideodraft costs voiceover --type audio --chars 800 # TTS: 10 cr / 1000 chars\\nvideodraft generate video \\"...\\" --estimate # same quote, inline\\nvideodraft credits # current balance\\n```\\n","references/pipeline.md":"# VideoDraft pipeline reference\\n\\nEverything here works through the CLI (`videodraft <command>` / `videodraft call <tool>`) or the MCP connector (tool names in backticks). One backend; pick the surface you have.\\n\\n## Stages and their tools\\n\\n| Stage | CLI | Underlying tool |\\n|---|---|---|\\n| Idea \u2192 full storyboard project | `videodraft create \\"<idea>\\"` | `generate_storyboard_from_idea` |\\n| Idea \u2192 script only (stop there) | `videodraft create \\"<idea>\\" --script-only` | `generate_script_from_idea` |\\n| Footage IS the video | `videodraft call generate_storyboard_from_media` | `generate_storyboard_from_media` |\\n| Batch shot images | `videodraft shots <project>` | `generate_shot_images` |\\n| One shot image | `videodraft generate image --project <id> --scene N --shot M` | `generate_image` |\\n| Produce (voiceover, captions, timeline) | `videodraft produce <project>` | `produce_project` |\\n| Per-shot motion prompts | `videodraft video-prompts <project>` | `generate_video_prompts` |\\n| Motion clip for a shot | `videodraft generate video --project <id>` | `generate_video` |\\n| Attach a finished clip to the timeline | `videodraft attach <project> --scene N --shot M --media <url> --type video` | `attach_media_to_shot` |\\n| Background music | `videodraft generate music --attach <project>` | `generate_music` / `set_background_music` |\\n| Sound effect | `videodraft generate sound-effect \\"...\\"` | `generate_sound_effect` |\\n| Dialogue audio | `videodraft generate dialogue --line \\"voice:text\\"` | `generate_dialogue` |\\n| Voice changer | `videodraft generate voice-changer <audio>` | `change_voice` |\\n| Dubbing | `videodraft generate dub <audio_or_video>` | `dub_media` |\\n| Scene voiceover | `videodraft generate voiceover --project <id> --scene N` | `generate_voiceover` |\\n| Final MP4 | `videodraft export <project>` | `export_video` + `check_export_status` |\\n\\n## Rules that prevent broken results\\n\\n- **The storyboard is generated FROM the script**, never from the raw idea. `videodraft create` runs the whole chain correctly. Don\'t call `generate_storyboard_scenes` with a raw idea as the \\"script\\".\\n- **Visual consistency**: never generate a storyboard shot in isolation. Shot prompts carry `[[asset:Name]]` / `[[shot:X-Y]]` tags that `generate_shot_images` resolves against the project\'s visual assets and prior shots. When generating a single shot whose prompt has no tags, pass `--ref` images yourself (the project\'s visual assets and/or the previous shot\'s image \u2014 `projects get` exposes both). Grid mode (`--grid`) gives the strongest cross-shot consistency.\\n- **Hold off generating shot images while the user is still iterating** on storyboard structure.\\n- **produce \u2192 export ordering**: `export` requires a produced project where every production scene has timeline media. If `produce` returns `generating_shot_images`, poll the job ids it returns, then re-run produce.\\n- **Generated motion clips do not auto-attach**: after `generate video` completes, attach the clip with `attach_media_to_shot` (`media_type:\\"video\\"`, include `duration_seconds`) \u2014 it replaces the production timeline clip while keeping the storyboard still.\\n- **Timeouts on the one-shot create**: if `create` times out at the transport layer, the project was still created server-side \u2014 `videodraft projects list`, take the most recent, and resume with its id. Don\'t start a duplicate.\\n\\n## User-attached media: classify roles first\\n\\nFor EACH attached file decide:\\n\\n- **visual_asset** \u2014 recurring reference (character / product / location / style). Upload, then pass in `visual_assets` of `generate_storyboard_from_idea` (via `videodraft call`), or add to an existing project with `add_visual_assets`. Type must be one of `character | object | location | style | custom` with a short name + concrete description.\\n- **shot** \u2014 the media IS footage for the video. Whole video = footage \u2192 `generate_storyboard_from_media`. Idea + footage \u2192 `generate_storyboard_from_idea` with `shot_media`. Existing storyboard \u2192 `attach_media_to_shots`.\\n- **reference** \u2014 inspiration only \u2192 fold a description into the idea/instructions; don\'t place it as a shot or asset.\\n\\nAmbiguous (e.g. a person holding a product)? Ask the user.\\n\\nUploads persist in the media library \u2014 recall later with `videodraft media list`.\\n\\n## Editing project data safely\\n\\n1. `videodraft call get_project_schema` \u2014 read the structure once per session.\\n2. `videodraft projects get <id> --raw` \u2014 the exact editable blob.\\n3. Modify; then `videodraft call update_project --stdin` with `{\\"project_id\\": \\"...\\", \\"data\\": {...}}`.\\n - Objects deep-merge key-by-key; **arrays replace wholesale** \u2014 send the complete array you\'re changing (e.g. all of `storyboard.scenes`).\\n - Scene shot arrays (`image_prompt` / `shot_types` / `shot_actions` / `search_prompt` / `preview_media`) are auto-aligned; fix-ups come back as warnings.\\n4. Snapshot before risky edits: `videodraft checkpoint create <id> --name \\"before re-script\\"`. Restore with `videodraft checkpoint restore <id> <version>`.\\n\\n## AI Studio sessions (standalone generations)\\n\\nProject generations group automatically. For standalone work in a long conversation, create one session up front and reuse it:\\n\\n```bash\\nSESSION=$(videodraft call create_ai_studio_session --arg name=\\"Fox brand explorations\\" --json | jq -r .session_id)\\nvideodraft generate image \\"...\\" --session \\"$SESSION\\"\\n```\\n"}');
|
|
3525
|
+
if ('{"SKILL.md":"---\\nname: videodraft\\ndescription: Create AI videos, images, voiceovers, music, sound effects, dialogue, dubbing, storyboards, avatar videos, media upscales and product/ad videos with VideoDraft. Use when the user mentions VideoDraft, or asks to generate/make a video, video ad, explainer, storyboard, talking-head/avatar video, AI image, voiceover/TTS, background music, sound effects, dialogue audio, voice changing, dubbing, or image/video enhancement and upscaling, including batch/programmatic video generation in scripts or CI. Works via the `videodraft` CLI (preferred in terminals) or the VideoDraft MCP connector.\\n---\\n\\n# VideoDraft\\n\\nVideoDraft is an AI video creation platform where asset generation is the priority lane:\\n\\n- **Asset generation**: standalone images, video clips, voiceovers, music, sound effects, dialogue, voice-changed audio, dubbed media, upscales, and image descriptions. This is the fastest and most important lane. Treat these as complete deliverables when the user asks for assets.\\n- **Asset I/O**: upload local files, download outputs, auto-upload local references, and save generated media where the user can see it.\\n- **Project production**: idea \u2192 script \u2192 storyboard (scenes + shot images) \u2192 project data \u2192 production timeline \u2192 exported MP4. Use it for a multi-scene video, story, ad, explainer, storyboard, editable timeline, or final export, even when the user does not say \\"project.\\" A script-only request also creates a script-stage project but stops at the script.\\n\\n## How to connect\\n\\nTwo equivalent surfaces (same backend, same credits, same projects):\\n\\n1. **CLI** (preferred when you have a shell): run `videodraft` if it\'s on PATH; otherwise `npx -y videodraft@latest` runs it with no install (needs Node \u226520; the `-y` skips npx\'s install prompt so it runs non-interactively; the package is fetched on first use and cached). For heavy use, `npm install -g videodraft`. If there\'s no Node/shell here but the MCP connector below is available, use that instead; if neither works, tell the user how to install (https://videodraft.ai/cli).\\n - Auth \u2014 pick by context, don\'t guess:\\n \u2022 INTERACTIVE (a human is in the session, e.g. Claude Code / Codex): on exit code 3 (\\"not authenticated\\"), tell the user to run `videodraft login` in their terminal \u2014 it opens their browser for a one-click VideoDraft sign-in (OAuth), no key to copy. Wait for them to confirm it succeeded, then retry the command. This is the preferred path when the user is present.\\n \u2022 HEADLESS / CI (no browser): set `VIDEODRAFT_API_KEY=vd_mcp_...` (a token the user mints at https://app.videodraft.ai/mcp-keys).\\n \u2022 SECURITY: never ask the user to paste a `vd_mcp_...` token into the chat \u2014 use browser `login` or the env var so the token never lands in the transcript.\\n - Every command accepts `--json` (parse this, don\'t scrape text). Exit codes: 0 ok, 1 error, 2 usage, 3 auth (see Auth above), 4 insufficient credits (\u2192 tell the user, don\'t retry).\\n - Tool discovery: start with `videodraft tools list` for the grouped catalog, then narrow with `videodraft tools list --lane assets`, `--lane asset_io`, `--lane project_data`, or `--lane production`.\\n - Asset lane: `videodraft generate ...`, `videodraft edit video|motion`, `videodraft avatar ...`, `videodraft upscale ...`, `videodraft upload`, and `videodraft download`.\\n - Full API access: `videodraft tools schema <name>`, `videodraft call <tool> --args \'<json>\'`.\\n2. **MCP connector**: if VideoDraft MCP tools (e.g. `generate_storyboard_from_idea`) are available, call them directly \u2014 the CLI\'s curated commands map 1:1 onto these tools.\\n\\n## First decision: asset or project?\\n\\n- **One standalone asset** (image, clip, voiceover, music track, sound effect, dialogue track, voice-changed file, dubbed media file, upscale, or description): generate it directly. Do NOT create a project.\\n - `videodraft generate image \\"a red fox in snow, cinematic\\" --ar 16:9 --download ./out/`\\n - `videodraft generate video \\"slow dolly over a misty lake\\" --model gemini-omni-flash --duration 6 --download ./out/`\\n- **A small set of related assets**: still stay in the asset lane. Use an AI Studio session if you need to group related generations. Switch to a project only when the deliverable matches the project criteria below or the user asks to attach the assets to one.\\n- **A multi-scene video / ad / explainer, storyboard, timeline, or final exported video**: create a project so the work stays organized, editable in the web app, and exportable.\\n - `videodraft create \\"30s launch video for our espresso machine\\" --ar 9:16`\\n- **Just a script** (no video asked for): `videodraft create \\"...\\" --script-only`. Stop at the script \u2014 do not build a storyboard the user didn\'t ask for.\\n- **Iterating on existing work**: find it first (`videodraft projects list`) and reuse that project. Never create a new project to change an existing one.\\n\\n## Choose the model from the task\\n\\nIf the user names a model, use it when compatible. If it cannot handle the request, explain why and recommend alternatives instead of silently switching. Otherwise inspect the inputs, duration, audio, quality, speed, and cost, check the live catalog, and pass an explicit model.\\n\\n**Images:**\\n\\n- `nano-banana-2`: general default, editing, consistency, and references.\\n- `nano-banana-pro`: maximum quality. `nano-banana-2-lite`: fast, inexpensive drafts.\\n- `gpt-image-2`: posters, logos, signs, title cards, readable text, or precise composition/editing.\\n\\n**Videos:**\\n\\n- `gemini-omni-flash`: general default up to 10s, first frame/image references, or editing one source video without extra media references. Fixed 720p with audio.\\n- `seedance-2`: 11-15s, video/audio/mixed references, wider ratios, selectable audio, or first/last frames. Use `mini` for cost, `fast` for speed, `standard` for quality or 1080p/4K.\\n- `kling-v3-turbo`: fast polished 3-15s with first frame, multi-prompt, and audio. `kling-o3`: image references, first/last frames, multi-prompt, audio control, or 4K. `kling-3.0`: similar without reference-image mode.\\n- Existing-video edits use `videodraft edit video`, not generic generation. Choose from the `video_edit` catalog category: Grok for simple prompt edits, Wan 2.7 for one style reference or source-matching duration, Happy Horse for up to 5 references, and Kling O3 for controlled reference-image edits.\\n- Kling O3 and Wan 2.7 Ref/Edit also have reference-generation modes. Use `videodraft generate video --model <ref-edit-id>` with `--ref-video`/`--ref` to generate a new guided clip; use `videodraft edit video` when changing the source itself.\\n- Motion transfer uses `videodraft edit motion` with Kling V3 by default, or Kling 2.6 when explicitly requested or lower cost matters. It requires a subject image and a motion-reference video.\\n- Use Veo 3.1 when explicitly requested or as a fallback.\\n\\n**Audio and utilities:**\\n\\n- Prefer ElevenLabs for voiceover, dialogue, voice changing, dubbing, and sound effects. Honor an explicitly selected supported TTS voice/provider. Use Lyria for instrumental music and ElevenLabs Music for vocals, lyrics, or exact timing.\\n- Talking head/presenter: choose by source. Use managed `avatar create` then `avatar render` when the user wants a reusable avatar record and bundled speech. Use `avatar fabric` for a one-off portrait plus text or existing audio. Use `avatar lipsync` when both the source video and replacement audio already exist.\\n- Enhancement: use Topaz image/video upscaling only when the content is already correct. Use image 1x for cleanup, 2x by default, 4x when justified; use video 2x by default. Edit or regenerate creative errors.\\n\\nSee [references/models.md](references/models.md) for the detailed routing table and exact capability limits.\\n\\n## Prefer references when continuity matters\\n\\nPure text-to-image or text-to-video is fine for a generic one-off asset. When a specific character, product, location, style, composition, or brand identity must survive generation, use references instead of hoping the prompt recreates it.\\n\\n- If the user supplies reference media, preserve and pass it. Never reduce the request to text alone.\\n- When continuity matters, generate/select a strong still first with the selected image model (`nano-banana-2` by default), wait for its URL, then animate it as a start frame/reference. Confirm the combined image and video cost.\\n- For multiple shots, use `videodraft shots <project_id> --model <selected-image-model> --grid`, then animate the decoded shots. Preserve explicit models. A requested non-Seedance video model must use manual per-shot generation instead of Seedance full-video mode.\\n\\n## Cost and credits\\n\\nDo not call `videodraft credits` before routine generations. Paid endpoints validate and deduct atomically; if the balance is insufficient, the request is rejected before the provider job starts (CLI exit code 4). Check the balance only when the user asks, gives a credit budget, or a large workflow needs budget planning.\\n\\nFor expensive work, estimate with `--estimate` or `videodraft costs`, state the selected model/settings/cost, and get a go-ahead. This matters most for shot-image batches, long or high-resolution video, AI Production, and paid audio batches. Honor the user\'s confirmation preference for the session.\\n\\n`videodraft models image|video` lists the live image and video catalogs with supported inputs. Video entries are grouped as `generation`, `video_edit`, `motion_control`, `avatar_lipsync`, and `upscale`, and each reports the exact tool. Use `videodraft models video --category video_edit` to narrow the list. `videodraft models audio` lists Google Lyria and ElevenLabs audio/media tools, while `videodraft models voices` lists TTS voices. Consult them instead of guessing capabilities.\\n\\n## Async jobs\\n\\nImage/video generation is asynchronous: commands submit a job and **wait by default**, printing output URLs (and saving files with `--download`). In scripts/CI prefer explicit control:\\n\\n```bash\\nJOB=$(videodraft generate image \\"...\\" --no-wait --json | jq -r .job_id)\\nvideodraft wait \\"$JOB\\" --download \\"./outputs/{job_id}_{index}.{ext}\\" --json\\n```\\n\\nFor MANY jobs: submit each with `--no-wait`, collect ALL with one command \u2014 `videodraft wait <id1> <id2> ...` polls every job from one process with one batched request per tick. Do NOT spawn parallel `wait`/`generate --wait` processes for a batch.\\n\\nIf a wait times out, the job is still running server-side \u2014 `videodraft status <job_id>` later. Never re-submit just because a wait timed out (that double-spends credits).\\n\\n## Local files and reference images\\n\\nReference inputs must be public URLs. The CLI uploads local files automatically wherever a URL is expected (`--ref photo.jpg`, `--start-image frame.png`), or explicitly:\\n\\n```bash\\nURL=$(videodraft upload ./product.png --json | jq -r .url)\\n```\\n\\nNever silently drop a reference you couldn\'t upload \u2014 stop and tell the user. Never upload a user\'s file to a third-party host.\\n\\nWhen the user attaches media, classify each item before acting: a recurring **visual asset** (character/product/location/style), actual **footage to place as shots**, or **inspiration only**. See [references/pipeline.md](references/pipeline.md) for how each role flows into a project.\\n\\n## Showing media to the user\\n\\nGenerated media is **not** displayed in the chat automatically \u2014 you decide what to show. To preview an asset inline, save it locally (use `--download` so it lands under `media/`) and reference its **local path** as a Markdown link with a **leading `./`**:\\n\\n```\\n[ferrari shot](./media/ferrari_01.png) \u2190 image card\\n[the clip](./media/clip.mp4) \u2190 video player\\n[voiceover](./media/vo.mp3) \u2190 audio player\\n```\\n\\nPut the Markdown link **in your message text** \u2014 video and audio embed exactly like images. Do **not** use `SendUserFile` (or other file-send tools) to display media: that renders inside a collapsible tool card and gets buried in the tool list. The Markdown link in your prose is what produces the inline card.\\n\\nUse the path you saved to: a **workspace-relative** path (`./media/clip.mp4`, or `./<any-folder>/clip.mp4` \u2014 any folder in the workspace works), or the **absolute** path for a file outside the workspace (e.g. `/Users/you/Desktop/clip.mp4` or another workspace\'s path). Both render. Show the finished results worth showing (and only those \u2014 not every intermediate job). A bare CDN URL or a JSON dump of output URLs does **not** render; the local-path Markdown link is what produces an inline card.\\n\\n## The full pipeline (idea \u2192 MP4)\\n\\n```bash\\nvideodraft create \\"<idea>\\" --ar 9:16 # project: script \u2192 visual assets \u2192 storyboard\\nvideodraft shots <project_id> --grid --estimate # cost preview, confirm with user\\nvideodraft shots <project_id> --grid # batch shot images (waits, writes onto shot cards)\\nvideodraft produce <project_id> # voiceovers + captions + production timeline\\nvideodraft export <project_id> --download final.mp4\\n```\\n\\nOptional between produce and export: per-shot motion clips (`videodraft generate video ... --project <id>` then place it with `videodraft attach <project> --scene N --shot M --media <url|file> --type video --duration <s>`), music (`videodraft generate music \\"...\\" --attach <project_id>`), and standalone audio assets (`generate sound-effect`, `generate dialogue`, `generate voice-changer`, `generate dub`). Details, per-step tools and editing rules: [references/pipeline.md](references/pipeline.md).\\n\\nAvatar/talking-head videos use dedicated commands. For a reusable managed avatar, obtain or generate a clear portrait \u2192 `videodraft avatar script` when needed \u2192 `videodraft avatar create` \u2192 `videodraft avatar render --resolution 720p`. For a one-off portrait, use `videodraft avatar fabric <portrait> --text \\"...\\"` or `--audio <file>`. For an existing video plus replacement audio, use `videodraft avatar lipsync <video> --audio <file>`. Managed script/creation is bundled/free; direct Fabric, Sync, the managed Fabric render, and optional portrait generation/upscaling are paid. Confirm expensive steps first.\\n\\n## Working with project data\\n\\nA project is one JSON blob (script, storyboard scenes, shot cards, visual assets, production timeline). To inspect: `videodraft projects get <id>`. To edit: fetch `--raw`, modify, then `videodraft call update_project` \u2014 objects deep-merge, **arrays replace wholesale** (send the complete `storyboard.scenes` array to change one scene). Snapshot first with `videodraft checkpoint create <id>` before risky edits. Schema reference: `videodraft call get_project_schema`.\\n\\n## More\\n\\n- [references/pipeline.md](references/pipeline.md) \u2014 project data model, step-by-step tools, attaching media, editing safely\\n- [references/models.md](references/models.md) \u2014 choosing image/video models, pricing patterns, voices and styles\\n- [references/examples.md](references/examples.md) \u2014 recipes: batch product videos from a CSV, talking-head from a script, changelog video in CI\\n","references/examples.md":"# Recipes\\n\\nWorking patterns for common asks. All assume auth (`videodraft login` once, or `VIDEODRAFT_API_KEY` in the environment) and use `--json` for parsing.\\n\\n## 1. Batch product videos from a CSV\\n\\nOne 9:16 product clip per row of `products.csv` (`name,image_url,tagline`):\\n\\n```bash\\n#!/usr/bin/env bash\\nset -euo pipefail\\nmkdir -p outputs\\n\\nwhile IFS=, read -r name image tagline; do\\n job=$(videodraft generate video \\\\\\n \\"Premium product shot of ${name}: ${tagline}. Slow orbit, studio lighting.\\" \\\\\\n --model gemini-omni-flash --ar 9:16 --duration 6 \\\\\\n --start-image \\"$image\\" \\\\\\n --no-wait --json | jq -r .job_id)\\n echo \\"$name,$job\\" >> outputs/jobs.csv\\ndone < <(tail -n +2 products.csv)\\n\\n# Collect ALL results with ONE process (batched polling \u2014 one request per tick)\\nvideodraft wait $(cut -d, -f2 outputs/jobs.csv) \\\\\\n --download \\"outputs/{job_id}_{index}.{ext}\\" --json > outputs/results.json\\n# map job ids back to product names via outputs/jobs.csv\\n```\\n\\nSubmit-then-collect parallelizes server-side generation; the single multi-id `wait` keeps it to one local process and one batched poll request per tick no matter how many jobs. Gemini Omni Flash is selected because these are six-second first-frame product clips. Estimate first: `videodraft costs gemini-omni-flash --type video --duration 6 --resolution 720p --audio` \xD7 rows, and confirm with the user.\\n\\n## 2. Full marketing video from one idea\\n\\n```bash\\nvideodraft create \\"30-second launch video for Solace, a sleep-tracking ring. Calm, premium, dark palette.\\" \\\\\\n --ar 9:16 --style cinematic --json > project.json\\nPROJECT=$(jq -r .project_id project.json)\\n\\nvideodraft shots \\"$PROJECT\\" --grid --estimate # show the user the cost; get a go-ahead\\nvideodraft shots \\"$PROJECT\\" --grid\\nvideodraft produce \\"$PROJECT\\"\\nvideodraft generate music \\"minimal ambient, warm pads, 60 BPM\\" --attach \\"$PROJECT\\"\\nvideodraft export \\"$PROJECT\\" --download solace-launch.mp4\\n```\\n\\nThe project stays editable at the URL in `project.json` (`.urls`) \u2014 hand it to the user for tweaks.\\n\\n## 3. Talking-head (avatar) video\\n\\nWhen the user has no portrait, generate a clear front-facing avatar image first. Skip this step when they supplied one or an existing character should be reused.\\n\\n```bash\\nvideodraft generate image \\\\\\n \\"Front-facing head-and-shoulders portrait of a friendly coffee expert, direct eye contact, natural expression, clean studio background\\" \\\\\\n --model nano-banana-2 --ar 9:16 --download ./media/avatar.png\\n\\nSCRIPT=$(videodraft avatar script \\"why our espresso subscription saves you money\\" --style ad-style --json | jq -r .script)\\nAVATAR=$(videodraft avatar create ./media/avatar.png --script \\"$SCRIPT\\" --voice elevenlabs-kPzsL2i3teMYv0FxEYQ6 --ar 9:16 --json | jq -r .avatar_video_id)\\nvideodraft avatar render \\"$AVATAR\\" --resolution 720p # VEED Fabric paid step; confirm cost first (~20 credits/sec)\\n```\\n\\n`avatar script` and `avatar create` (including speech) are bundled/free. In this example only the optional portrait generation and Fabric render spend credits.\\n\\nIf the portrait is low resolution, enhance it before `avatar create`:\\n\\n```bash\\nvideodraft upscale image ./founder-small.jpg --scale 2x --download ./media/founder-upscaled.png\\n```\\n\\nFor a one-off portrait animation without creating a managed avatar record:\\n\\n```bash\\nvideodraft avatar fabric ./founder.jpg \\\\\\n --text \\"Welcome to the weekly product update.\\" \\\\\\n --voice-description \\"warm, confident American presenter\\" \\\\\\n --resolution 720p --download ./media/presenter.mp4\\n```\\n\\nWhen the user already has both the video and replacement speech:\\n\\n```bash\\nvideodraft avatar lipsync ./presenter.mp4 \\\\\\n --audio ./localized-voiceover.mp3 \\\\\\n --sync-mode loop --download ./media/presenter-localized.mp4\\n```\\n\\nEdit an existing video with a dedicated edit model:\\n\\n```bash\\nvideodraft models video --category video_edit\\nvideodraft edit video ./product-demo.mp4 \\\\\\n \\"Turn the room into a warm evening scene while preserving the product and camera motion\\" \\\\\\n --model wan-2.7-ref-edit --ref ./evening-style.jpg \\\\\\n --preserve-audio --download ./media/product-demo-evening.mp4\\n```\\n\\nTransfer motion from a reference clip onto a character image:\\n\\n```bash\\nvideodraft edit motion ./character.png \\\\\\n \\"Apply the dancer\'s movement to this character while preserving identity\\" \\\\\\n --motion-video ./dance-reference.mp4 \\\\\\n --model kling-v3-motion-control --quality pro \\\\\\n --download ./media/character-dance.mp4\\n```\\n\\n## 4. Changelog video in CI\\n\\nIn a GitHub Action with `VIDEODRAFT_API_KEY` set as a secret:\\n\\n```bash\\nNOTES=$(git log --oneline v1.2.0..HEAD | head -20)\\nvideodraft create \\"Weekly product update video. Energetic, 20 seconds. Changes: ${NOTES}\\" --ar 16:9 --json > p.json\\nPROJECT=$(jq -r .project_id p.json)\\nvideodraft shots \\"$PROJECT\\" && videodraft produce \\"$PROJECT\\"\\nvideodraft export \\"$PROJECT\\" --download changelog.mp4 --wait-timeout 30m\\n```\\n\\n## 5. Variations and picking a winner\\n\\n```bash\\nvideodraft generate image \\"logo concept: minimalist fox, geometric\\" --num 4 --download \\"./concepts/{job_id}_{index}.{ext}\\" --json\\n# Show all 4 to the user; regenerate the chosen one at higher res:\\nvideodraft generate image \\"<same prompt>\\" --model nano-banana-pro --resolution 4K\\n```\\n\\n## 6. Reaching tools without a curated command\\n\\n```bash\\nvideodraft tools list --json | jq -r \'.[].name\'\\nvideodraft tools schema attach_media_to_shot --json\\nvideodraft call attach_media_to_shot --args \'{\\"project_id\\":\\"...\\",\\"scene_index\\":0,\\"shot_index\\":1,\\"media_url\\":\\"https://...\\",\\"media_type\\":\\"video\\",\\"duration_seconds\\":6}\'\\n```\\n\\nAnything the VideoDraft MCP exposes \u2014 character studio, product studio, timeline editing \u2014 is reachable this way even before it gets a curated command.\\n\\n## 7. Enhance an existing asset without changing it\\n\\n```bash\\n# Light image cleanup, no enlargement\\nvideodraft upscale image ./poster.png --scale 1x --download ./media/poster-enhanced.png\\n\\n# General image and video enlargement\\nvideodraft upscale image ./frame.png --scale 2x --download ./media/frame-2x.png\\nvideodraft upscale video ./clip.mp4 --scale 2x --download ./media/clip-2x.mp4\\n```\\n\\nUse these when the content is correct and only quality or resolution needs improvement. If the poster text, composition, subject, or motion is wrong, edit or regenerate instead.\\n","references/models.md":"# Choosing models (and predicting cost)\\n\\nAlways consult the live catalog instead of memorizing this page \u2014 models change weekly:\\n\\n```bash\\nvideodraft models image --json # every image model + inputs (aspect ratios, resolutions, max refs)\\nvideodraft models video --json # every video model + inputs + per-second pricing metadata\\nvideodraft models audio --json # standalone audio/media models + pricing inputs\\nvideodraft models voices --json # TTS voices\\nvideodraft models styles --json # visual style presets\\n```\\n\\n## Task-based model selection\\n\\nHonor an explicitly named model when it supports the request. Otherwise choose from the task\'s inputs, duration, audio, quality, speed, and cost. Pass the chosen model explicitly instead of relying on a blind platform fallback.\\n\\n### Images\\n\\n| Need | Choose | Why |\\n| -------------------------------------------------------------------------------------- | -------------------- | ----------------------------------------------------------------- |\\n| Most generation, editing, character consistency, or reference work | `nano-banana-2` | Best general default; 1K/2K/4K and up to 14 reference images |\\n| Highest-quality complex generation or reasoning | `nano-banana-pro` | Premium Nano Banana quality and reasoning |\\n| Fast, inexpensive drafts and iteration | `nano-banana-2-lite` | Fastest/cheapest Nano Banana option; 1K only, up to 14 references |\\n| Posters, title cards, signs, logos, or any image with important readable text | `gpt-image-2` | Strong text rendering; up to 16 image inputs and 1K/2K/4K output |\\n| Complex multi-image composition, precise editing, or a strong alternate interpretation | `gpt-image-2` | Strong non-Nano alternative with multi-image input |\\n\\nUse `--num 1..4` for variations of one prompt in a single call. Never loop separate paid calls for variations that fit in one request.\\n\\n### Videos\\n\\n| Need | Choose | Important limits |\\n| ---------------------------------------------------------------------------------------------------------- | ------------------- | --------------------------------------------------------------------------------------------------- |\\n| Most text, first-frame, image-reference, or source-video-edit requests up to 10s | `gemini-omni-flash` | 720p, 3-10s or auto, audio always on, up to 10 total image inputs, one source video |\\n| Video/audio references, mixed reference media, broad aspect ratios, frame-mode first+last frame, or 11-15s | `seedance-2` | 4-15s or auto; up to 9 image, 3 video, and 3 audio refs; audio toggle; Mini/Fast are 480p/720p only |\\n| Fast polished 3-15s video with first frame, multi-prompt, and native audio | `kling-v3-turbo` | Audio always on; Pro default; no end frame or reference-media mode |\\n| Cinematic 3-15s with image references, first+last frame, multi-prompt, audio control, or 4K | `kling-o3` | Up to 7 image refs; Standard/Pro/4K; audio toggle |\\n| Kling 3-15s with first+last frame, multi-prompt, optional audio, or 4K, without reference-image mode | `kling-3.0` | Standard/Pro/4K; audio toggle |\\n| User explicitly requests Veo, or the selected workflow specifically needs Veo | `google-veo3.1` | Good fallback, but not the preferred general model |\\n\\nRouting rules:\\n\\n- Around 11-15 seconds with native audio: use Kling or Seedance, not Gemini.\\n- One existing source video that should be edited, with an output up to 10 seconds and no additional media references to preserve: use Gemini Omni Flash.\\n- Video or audio supplied as creative reference: use Seedance 2.0.\\n- A video plus any image/audio references that must all be preserved: use Seedance 2.0. Do not promise that Gemini will preserve mixed source media; its Fal BYOK edit mode accepts only the source video and prompt.\\n- First and last frame control: use Seedance, Kling O3, or Kling 3.0. Gemini supports a first frame but not a last frame.\\n- Seedance reference mode and first-plus-last-frame mode are separate. Do not promise reference video/audio plus a last frame in one generation.\\n- Multi-prompt sequencing: use Kling 3.0 Turbo, Kling O3, or Kling 3.0.\\n- Seedance quality: `mini` for the lowest cost, `fast` for speed, `standard` for maximum quality and for 1080p/4K.\\n\\n### Video edit and motion-control categories\\n\\nUse `videodraft models video --category video_edit` for existing-video transforms and `--category motion_control` for motion transfer.\\n\\n| Need | Command/model | Important limits |\\n| ------------------------------------- | --------------------------------------------------------------------- | -------------------------------------------------------------------------------- |\\n| Simple prompt edit of one video | `videodraft edit video <video> \\"...\\" --model grok-imagine-video-edit` | No image refs; source truncated to 8s; auto/480p/720p |\\n| Edit with one style/reference image | `--model wan-2.7-ref-edit --ref <image>` | One image ref; 2-10s or match source |\\n| Edit with several image references | `--model happy-horse-video-edit --ref ...` | Up to 5 refs; 720p/1080p; source capped at 15s |\\n| Controlled Kling edit | `--model kling-o3-video-ref-edit --ref ...` | Up to 4 refs; Standard/Pro; source clamped to 3-10s |\\n| Transfer reference motion to an image | `videodraft edit motion <image> \\"...\\" --motion-video <video>` | Kling V3 default; image orientation caps motion at 10s, video orientation at 30s |\\n\\nIf the user explicitly names one of these models, preserve it. The CLI uploads local source videos and reference images automatically. Editing returns an async job and waits by default.\\n\\nKling O3 and Wan 2.7 Ref/Edit are dual-mode cards. `videodraft edit video` uses edit mode. `videodraft generate video --model kling-o3-video-ref-edit` requires exactly one `--ref-video` and generates a new reference-guided clip. `--model wan-2.7-ref-edit` generates a new clip from one or more `--ref`/`--ref-video` inputs.\\n\\n### Reference-first video workflow\\n\\n- Prefer a start frame or reference image whenever a specific character, product, location, style, composition, or brand identity must stay recognizable.\\n- If the user gives a reference, pass it. Never silently replace it with a text description.\\n- If no reference exists and continuity matters, generate a still first with the user\'s explicitly requested compatible image model, otherwise use Nano Banana 2. Wait for the image URL, then animate it with the selected video model. Confirm the combined image plus video cost before starting.\\n- For multi-shot scenes, generate shot images with `videodraft shots <project_id> --model <selected-image-model> --grid`. Preserve an explicitly requested compatible image model; otherwise use `nano-banana-2`. The grid establishes the scene and characters together, then decodes into individual shot images.\\n- Animate the decoded shot images as per-shot start frames or references. Do not independently text-generate each video clip when the shots need to match.\\n- Pure text-to-video remains appropriate for generic one-off footage where no subject, composition, or continuity needs to be preserved.\\n\\n### Audio\\n\\n- **Voiceover/TTS**: prefer ElevenLabs. Brittney is the platform default voice; under ElevenLabs BYOK, use a compatible voice from the user\'s account. Honor another supported voice/provider when the user explicitly selects it.\\n- **Dialogue, voice changing, and dubbing**: ElevenLabs only.\\n- **Sound effects**: ElevenLabs Sound Effects only.\\n- **Music**: use `lyria-3-clip-preview` for a short instrumental/background score, `lyria-3-pro-preview` for a longer or higher-quality instrumental score, and `elevenlabs-music` when vocals/lyrics or a specified 10-120 second length matter.\\n- Voice Changer and Dubbing require the source media duration and currently accept source media up to 300 seconds.\\n\\n### Avatar / talking head\\n\\nChoose the dedicated path from the media the user already has:\\n\\n| Starting media | Command | Use |\\n| ----------------------------------------- | ------------------------------------------------------------------------- | --------------------------------------------------- |\\n| Portrait + script, reusable avatar record | `videodraft avatar create <portrait> --script \\"...\\"` then `avatar render` | Managed avatar flow with bundled speech preparation |\\n| Portrait + text | `videodraft avatar fabric <portrait> --text \\"...\\"` | One-off direct VEED Fabric text mode |\\n| Portrait + existing audio | `videodraft avatar fabric <portrait> --audio <audio>` | One-off direct VEED Fabric audio lip sync |\\n| Existing video + existing audio | `videodraft avatar lipsync <video> --audio <audio>` | Sync Labs Lipsync 2 |\\n\\nThe managed renderer is VEED Fabric Fast (`veed/fabric-1.0/fast`). Direct Fabric and Sync Labs are paid AI Studio generations and return async job IDs.\\n\\n1. Obtain the avatar image. Prefer the user\'s supplied portrait or an existing character. If none exists, use the user\'s explicitly requested compatible image model, otherwise generate a front-facing head-and-shoulders portrait with `nano-banana-2`, direct eye contact, a natural expression, and a clean background. Match the intended video aspect ratio when practical.\\n2. If the portrait is visibly soft or too small, run Topaz image enhancement/upscaling before animation.\\n3. Generate a script only if needed: `videodraft avatar script \\"<idea>\\"`.\\n4. Create the avatar record and speech: `videodraft avatar create <portrait-url-or-file> --script \\"...\\" --voice <id> --ar 9:16`. Prefer ElevenLabs when unspecified, but honor another explicitly selected supported voice/provider.\\n5. Render with VEED Fabric: `videodraft avatar render <avatar_video_id> --resolution 720p`.\\n\\nThe portrait is passed as the avatar\'s character image, not as a generic video\'s start frame. Prefer rendering directly at 720p. Use 480p only when the user prioritizes lower cost. Avatar script generation and `avatar create` (including speech) are bundled/free. Confirm the Fabric render cost, plus portrait generation or upscaling when needed.\\n\\nDirect Fabric text/audio and Sync Labs do not use the managed avatar record. The CLI uploads local portrait, video, and audio files automatically. `avatar fabric --speed fast` applies only to audio mode. Sync costs 5 credits per verified audio second; under Fal BYOK, `sync_mode` remains available but `temperature` and `active_speaker` are ignored by the provider.\\n\\n### Upscaling / enhancement\\n\\n- **Images**: Topaz via `videodraft upscale image <url-or-file> --scale 1x|2x|4x`. Use 1x for light enhancement without enlargement, 2x as the general default, and 4x only when the source quality and target size justify it. The result is synchronous.\\n- **Videos**: Topaz via `videodraft upscale video <url-or-file> --scale 2x`. Use 2x by default. The job is asynchronous; the CLI waits by default, while MCP callers poll `check_generation_status`. MCP video input must be VideoDraft-hosted, so upload local or external sources first.\\n- Use upscaling to preserve the image/video while improving detail, resolution, or cleanup. It cannot fix the wrong subject, misspelled text, bad framing, unwanted objects, broken continuity, or incorrect motion. Use an edit or regeneration for those problems.\\n- For a new Fabric avatar, render directly at 720p instead of rendering at 480p and then upscaling. Upscale the source portrait first only when the portrait itself is low quality.\\n\\n## Capability gotchas\\n\\n- Each model\'s `inputs` block is authoritative: supported `aspect_ratios`, `resolutions`, `quality_options`, `start_frame`/`end_frame`, `max_reference_images/videos/audio`, `multi_prompt`, `audio_toggle`. Passing an unsupported input fails with a clear error \u2014 check first, don\'t trial-and-error paid calls.\\n- Most video models support only 16:9 / 9:16 / 1:1. A 3:4 request hard-fails on most.\\n- `--seed` reproduces a specific output on models that support it (e.g. Flux, Ideogram V4); everything else ignores it. You do not need a seed for variation \u2014 `--num` already varies.\\n- `--rendering-speed` applies to Ideogram (V3: `Default`/`Turbo`/`Quality`; V4: `Turbo`/`Balanced`/`Quality`) and affects image cost \u2014 pass it to `videodraft costs ... --rendering-speed <tier>` for an accurate estimate. Always trust `videodraft models image --json` over this list; new models and tiers appear there the moment the platform ships them, with no CLI update.\\n- `seedream-v5-pro` supports unified text-to-image and reference-image editing with up to 10 image references. Use `--resolution 1K` for 7 credits/image or `--resolution 2K` for 14 credits/image.\\n- Reference inputs: `--ref <img>` (images), `--ref-video <v>` (Gemini Omni Flash, Seedance 2, Wan 2.7), `--ref-audio <a>` (Seedance 2). The CLI uploads local files for all of these, so you can pass a path or a URL. `--segment \\"<prompt>:<seconds>\\"` (repeatable) drives multi-prompt models (Kling 3.0 / 3.0 Turbo / O3); total 3-15s. `generate image --video-ref` is the nano-banana-2 video reference.\\n- The top-level prompt is OPTIONAL for `generate video` with multi-prompt models and for Kling 3.0 Turbo (`--model kling-v3-turbo`) image-to-video \u2014 a `--segment`-only or `--start-image`-only call is valid. Every other model still needs a prompt; the server enforces per-model rules.\\n- AI Production: `videodraft produce <project> --mode full_video` generates one Seedance 2 video per scene; poll with `videodraft generations`, then `videodraft finalize <project>` swaps them into the timeline before `export`. If the user explicitly requests another compatible video model, do not use this fixed Seedance path. Generate the project shots manually with the requested model and attach them to the timeline.\\n\\n## Cost model\\n\\n- Images: per image (\xD7 `--num`). Matrix-priced models (GPT-Image, Nano Banana Pro, Seedream v5 Pro) vary by resolution/quality.\\n- Video: usually credits/second \xD7 duration; rate depends on model + resolution + quality + native audio on/off.\\n- Shot-image batches: one image per shot (+1 grid image per scene in `--grid` mode) \u2014 the largest single spend in the pipeline.\\n- VEED Fabric avatar renders: ~10 credits/sec at 480p, ~20/sec at 720p. Avatar creation and its speech are bundled/free; only optional portrait generation/upscaling adds cost before the render.\\n- Direct VEED Fabric: text or normal audio is 8 credits/sec at 480p and 15/sec at 720p; fast audio is 10/sec at 480p and 20/sec at 720p.\\n- Sync Labs Lipsync 2: 5 credits per verified audio second.\\n- Voiceover TTS: 10 credits per 1000 characters for standard voices, 30 per 1000 for cloned `custom-*` voices (min 1, pro-rated); applies to standalone voiceovers AND per-scene narration during `produce`. Silent tracks are free. Voice cloning itself is a flat 150 credits per clone.\\n- Lyria music: flat per track, 10 credits (clip) / 15 credits (pro).\\n- ElevenLabs audio: sound effects are per second, dialogue is per character, music/voice-changer/dubbing are per started minute. Voice changer and dubbing reject source media above 300s in the current synchronous flow.\\n- Upscales: priced by scale and source size.\\n\\nQuote before spending:\\n\\n```bash\\nvideodraft costs gemini-omni-flash --type video --duration 8 --resolution 720p --audio\\nvideodraft costs seedance-2 --type video --duration 15 --resolution 720p --quality standard --audio\\nvideodraft costs elevenlabs-dubbing --type audio --duration 60\\nvideodraft costs elevenlabs-dialogue --type audio --chars 350\\nvideodraft costs voiceover --type audio --chars 800 # TTS: 10 cr / 1000 chars\\nvideodraft generate video \\"...\\" --model gemini-omni-flash --estimate # same quote, inline\\n```\\n","references/pipeline.md":"# VideoDraft pipeline reference\\n\\nEverything here works through the CLI (`videodraft <command>` / `videodraft call <tool>`) or the MCP connector (tool names in backticks). One backend; pick the surface you have.\\n\\nUse direct asset tools for standalone images, clips, audio, upscales, and descriptions. Use a project for any multi-scene video, story, ad, explainer, storyboard, editable timeline, or final export even when the request does not use the word \\"project.\\" Script-only uses a script-stage project and stops at the script.\\n\\n## Stages and their tools\\n\\n| Stage | CLI | Underlying tool |\\n| --------------------------------------- | --------------------------------------------------------------------------- | ------------------------------------------ |\\n| Idea \u2192 full storyboard project | `videodraft create \\"<idea>\\"` | `generate_storyboard_from_idea` |\\n| Idea \u2192 script only (stop there) | `videodraft create \\"<idea>\\" --script-only` | `generate_script_from_idea` |\\n| Footage IS the video | `videodraft call generate_storyboard_from_media` | `generate_storyboard_from_media` |\\n| Batch shot images | `videodraft shots <project>` | `generate_shot_images` |\\n| One shot image | `videodraft generate image --project <id> --scene N --shot M` | `generate_image` |\\n| Produce (voiceover, captions, timeline) | `videodraft produce <project>` | `produce_project` |\\n| Per-shot motion prompts | `videodraft video-prompts <project>` | `generate_video_prompts` |\\n| Motion clip for a shot | `videodraft generate video --project <id>` | `generate_video` |\\n| Attach a finished clip to the timeline | `videodraft attach <project> --scene N --shot M --media <url> --type video` | `attach_media_to_shot` |\\n| Background music | `videodraft generate music --attach <project>` | `generate_music` / `set_background_music` |\\n| Sound effect | `videodraft generate sound-effect \\"...\\"` | `generate_sound_effect` |\\n| Dialogue audio | `videodraft generate dialogue --line \\"voice:text\\"` | `generate_dialogue` |\\n| Voice changer | `videodraft generate voice-changer <audio>` | `change_voice` |\\n| Dubbing | `videodraft generate dub <audio_or_video>` | `dub_media` |\\n| Scene voiceover | `videodraft generate voiceover --project <id> --scene N` | `generate_voiceover` |\\n| Avatar script | `videodraft avatar script \\"<idea>\\"` | `generate_avatar_script` |\\n| Avatar + speech | `videodraft avatar create <portrait> --script \\"...\\"` | `create_avatar_video` |\\n| Talking-head render | `videodraft avatar render <avatar_video_id>` | `render_avatar_video` + `get_avatar_video` |\\n| Direct portrait + text/audio | `videodraft avatar fabric <portrait> --text \\"...\\"` or `--audio <file>` | `generate_veed_fabric_video` |\\n| Existing video + replacement audio | `videodraft avatar lipsync <video> --audio <file>` | `generate_sync_lipsync_video` |\\n| Existing-video AI edit | `videodraft edit video <video> \\"<change>\\" --model <video-edit-model>` | `edit_video` |\\n| Motion transfer | `videodraft edit motion <image> \\"<direction>\\" --motion-video <video>` | `generate_motion_control_video` |\\n| Image enhancement/upscale | `videodraft upscale image <image>` | `upscale_image` |\\n| Video enhancement/upscale | `videodraft upscale video <video>` | `upscale_video` |\\n| Final MP4 | `videodraft export <project>` | `export_video` + `check_export_status` |\\n\\n## Rules that prevent broken results\\n\\n- **The storyboard is generated FROM the script**, never from the raw idea. `videodraft create` runs the whole chain correctly. Don\'t call `generate_storyboard_scenes` with a raw idea as the \\"script\\".\\n- **Visual consistency**: never generate a storyboard shot in isolation. Shot prompts carry `[[asset:Name]]` / `[[shot:X-Y]]` tags that `generate_shot_images` resolves against the project\'s visual assets and prior shots. When generating a single shot whose prompt has no tags, pass `--ref` images yourself (the project\'s visual assets and/or the previous shot\'s image; `projects get` exposes both). For scenes with multiple shots or recurring characters, prefer `videodraft shots <project> --model <selected-image-model> --grid`: preserve an explicitly requested compatible image model, otherwise use `nano-banana-2`. It creates one coherent scene grid, then decodes it into individual shot images.\\n- **Reference-first video**: when identity, styling, or composition matters, do not generate each motion clip from text alone. Generate or select the shot still first, then pass the decoded shot image as `--start-image` or `--ref` to the selected video model. AI Production already composes scene grids and sends them to Seedance as references. If the user explicitly requests another compatible video model, bypass fixed Seedance full-video mode and generate the per-shot clips with the requested model, using the individual decoded shot images as anchors.\\n- **Hold off generating shot images while the user is still iterating** on storyboard structure.\\n- **produce \u2192 export ordering**: `export` requires a produced project where every production scene has timeline media. If `produce` returns `generating_shot_images`, poll the job ids it returns, then re-run produce.\\n- **Generated motion clips do not auto-attach**: after `generate video` completes, attach the clip with `attach_media_to_shot` (`media_type:\\"video\\"`, include `duration_seconds`) \u2014 it replaces the production timeline clip while keeping the storyboard still.\\n- **Talking heads use dedicated avatar tools**: do not use `generate video`. Use managed `avatar create` and `avatar render` for reusable avatars, direct `avatar fabric` for a portrait plus text/audio, and `avatar lipsync` for an existing video plus replacement audio. Reuse a supplied person image or generate a clear front-facing portrait with the explicitly requested compatible image model, otherwise Nano Banana 2. Managed avatar creation and speech are bundled/free; direct Fabric, Sync, and render are paid.\\n- **Existing-video edits use their own category**: call `edit_video` or `videodraft edit video` with a `video_edit` model when transforming the source itself. Kling O3 and Wan 2.7 Ref/Edit are dual-mode: their reference-generation modes may use generic `generate_video` to create a new guided clip. Motion transfer similarly uses `generate_motion_control_video` or `videodraft edit motion` with a `motion_control` model.\\n- **Upscaling preserves rather than redesigns**: use Topaz when resolution, detail, or cleanup is the problem. Regenerate or edit when the subject, text, framing, continuity, or motion is wrong. Upscale a low-quality avatar portrait before Fabric; do not render a new avatar at 480p just to upscale the result.\\n- **Timeouts on the one-shot create**: if `create` times out at the transport layer, the project was still created server-side \u2014 `videodraft projects list`, take the most recent, and resume with its id. Don\'t start a duplicate.\\n\\n## User-attached media: classify roles first\\n\\nFor EACH attached file decide:\\n\\n- **visual_asset** \u2014 recurring reference (character / product / location / style). Upload, then pass in `visual_assets` of `generate_storyboard_from_idea` (via `videodraft call`), or add to an existing project with `add_visual_assets`. Type must be one of `character | object | location | style | custom` with a short name + concrete description.\\n- **shot** \u2014 the media IS footage for the video. Whole video = footage \u2192 `generate_storyboard_from_media`. Idea + footage \u2192 `generate_storyboard_from_idea` with `shot_media`. Existing storyboard \u2192 `attach_media_to_shots`.\\n- **reference** \u2014 inspiration only \u2192 fold a description into the idea/instructions; don\'t place it as a shot or asset.\\n\\nAmbiguous (e.g. a person holding a product)? Ask the user.\\n\\nUploads persist in the media library \u2014 recall later with `videodraft media list`.\\n\\n## Editing project data safely\\n\\n1. `videodraft call get_project_schema` \u2014 read the structure once per session.\\n2. `videodraft projects get <id> --raw` \u2014 the exact editable blob.\\n3. Modify; then `videodraft call update_project --stdin` with `{\\"project_id\\": \\"...\\", \\"data\\": {...}}`.\\n - Objects deep-merge key-by-key; **arrays replace wholesale** \u2014 send the complete array you\'re changing (e.g. all of `storyboard.scenes`).\\n - Scene shot arrays (`image_prompt` / `shot_types` / `shot_actions` / `search_prompt` / `preview_media`) are auto-aligned; fix-ups come back as warnings.\\n4. Snapshot before risky edits: `videodraft checkpoint create <id> --name \\"before re-script\\"`. Restore with `videodraft checkpoint restore <id> <version>`.\\n\\n## AI Studio sessions (standalone generations)\\n\\nProject generations group automatically. For standalone work in a long conversation, create one session up front and reuse it:\\n\\n```bash\\nSESSION=$(videodraft call create_ai_studio_session --arg name=\\"Fox brand explorations\\" --json | jq -r .session_id)\\nvideodraft generate image \\"...\\" --session \\"$SESSION\\"\\n```\\n"}') {
|
|
3526
|
+
return JSON.parse('{"SKILL.md":"---\\nname: videodraft\\ndescription: Create AI videos, images, voiceovers, music, sound effects, dialogue, dubbing, storyboards, avatar videos, media upscales and product/ad videos with VideoDraft. Use when the user mentions VideoDraft, or asks to generate/make a video, video ad, explainer, storyboard, talking-head/avatar video, AI image, voiceover/TTS, background music, sound effects, dialogue audio, voice changing, dubbing, or image/video enhancement and upscaling, including batch/programmatic video generation in scripts or CI. Works via the `videodraft` CLI (preferred in terminals) or the VideoDraft MCP connector.\\n---\\n\\n# VideoDraft\\n\\nVideoDraft is an AI video creation platform where asset generation is the priority lane:\\n\\n- **Asset generation**: standalone images, video clips, voiceovers, music, sound effects, dialogue, voice-changed audio, dubbed media, upscales, and image descriptions. This is the fastest and most important lane. Treat these as complete deliverables when the user asks for assets.\\n- **Asset I/O**: upload local files, download outputs, auto-upload local references, and save generated media where the user can see it.\\n- **Project production**: idea \u2192 script \u2192 storyboard (scenes + shot images) \u2192 project data \u2192 production timeline \u2192 exported MP4. Use it for a multi-scene video, story, ad, explainer, storyboard, editable timeline, or final export, even when the user does not say \\"project.\\" A script-only request also creates a script-stage project but stops at the script.\\n\\n## How to connect\\n\\nTwo equivalent surfaces (same backend, same credits, same projects):\\n\\n1. **CLI** (preferred when you have a shell): run `videodraft` if it\'s on PATH; otherwise `npx -y videodraft@latest` runs it with no install (needs Node \u226520; the `-y` skips npx\'s install prompt so it runs non-interactively; the package is fetched on first use and cached). For heavy use, `npm install -g videodraft`. If there\'s no Node/shell here but the MCP connector below is available, use that instead; if neither works, tell the user how to install (https://videodraft.ai/cli).\\n - Auth \u2014 pick by context, don\'t guess:\\n \u2022 INTERACTIVE (a human is in the session, e.g. Claude Code / Codex): on exit code 3 (\\"not authenticated\\"), tell the user to run `videodraft login` in their terminal \u2014 it opens their browser for a one-click VideoDraft sign-in (OAuth), no key to copy. Wait for them to confirm it succeeded, then retry the command. This is the preferred path when the user is present.\\n \u2022 HEADLESS / CI (no browser): set `VIDEODRAFT_API_KEY=vd_mcp_...` (a token the user mints at https://app.videodraft.ai/mcp-keys).\\n \u2022 SECURITY: never ask the user to paste a `vd_mcp_...` token into the chat \u2014 use browser `login` or the env var so the token never lands in the transcript.\\n - Every command accepts `--json` (parse this, don\'t scrape text). Exit codes: 0 ok, 1 error, 2 usage, 3 auth (see Auth above), 4 insufficient credits (\u2192 tell the user, don\'t retry).\\n - Tool discovery: start with `videodraft tools list` for the grouped catalog, then narrow with `videodraft tools list --lane assets`, `--lane asset_io`, `--lane project_data`, or `--lane production`.\\n - Asset lane: `videodraft generate ...`, `videodraft edit video|motion`, `videodraft avatar ...`, `videodraft upscale ...`, `videodraft upload`, and `videodraft download`.\\n - Full API access: `videodraft tools schema <name>`, `videodraft call <tool> --args \'<json>\'`.\\n2. **MCP connector**: if VideoDraft MCP tools (e.g. `generate_storyboard_from_idea`) are available, call them directly \u2014 the CLI\'s curated commands map 1:1 onto these tools.\\n\\n## First decision: asset or project?\\n\\n- **One standalone asset** (image, clip, voiceover, music track, sound effect, dialogue track, voice-changed file, dubbed media file, upscale, or description): generate it directly. Do NOT create a project.\\n - `videodraft generate image \\"a red fox in snow, cinematic\\" --ar 16:9 --download ./out/`\\n - `videodraft generate video \\"slow dolly over a misty lake\\" --model gemini-omni-flash --duration 6 --download ./out/`\\n- **A small set of related assets**: still stay in the asset lane. Use an AI Studio session if you need to group related generations. Switch to a project only when the deliverable matches the project criteria below or the user asks to attach the assets to one.\\n- **A multi-scene video / ad / explainer, storyboard, timeline, or final exported video**: create a project so the work stays organized, editable in the web app, and exportable.\\n - `videodraft create \\"30s launch video for our espresso machine\\" --ar 9:16`\\n- **Just a script** (no video asked for): `videodraft create \\"...\\" --script-only`. Stop at the script \u2014 do not build a storyboard the user didn\'t ask for.\\n- **Iterating on existing work**: find it first (`videodraft projects list`) and reuse that project. Never create a new project to change an existing one.\\n\\n## Choose the model from the task\\n\\nIf the user names a model, use it when compatible. If it cannot handle the request, explain why and recommend alternatives instead of silently switching. Otherwise inspect the inputs, duration, audio, quality, speed, and cost, check the live catalog, and pass an explicit model.\\n\\n**Images:**\\n\\n- `nano-banana-2`: general default, editing, consistency, and references.\\n- `nano-banana-pro`: maximum quality. `nano-banana-2-lite`: fast, inexpensive drafts.\\n- `gpt-image-2`: posters, logos, signs, title cards, readable text, or precise composition/editing.\\n\\n**Videos:**\\n\\n- `gemini-omni-flash`: general default up to 10s, first frame/image references, or editing one source video without extra media references. Fixed 720p with audio.\\n- `seedance-2`: 11-15s, video/audio/mixed references, wider ratios, selectable audio, or first/last frames. Use `mini` for cost, `fast` for speed, `standard` for quality or 1080p/4K.\\n- `kling-v3-turbo`: fast polished 3-15s with first frame, multi-prompt, and audio. `kling-o3`: image references, first/last frames, multi-prompt, audio control, or 4K. `kling-3.0`: similar without reference-image mode.\\n- Existing-video edits use `videodraft edit video`, not generic generation. Choose from the `video_edit` catalog category: Grok for simple prompt edits, Wan 2.7 for one style reference or source-matching duration, Happy Horse for up to 5 references, and Kling O3 for controlled reference-image edits.\\n- Kling O3 and Wan 2.7 Ref/Edit also have reference-generation modes. Use `videodraft generate video --model <ref-edit-id>` with `--ref-video`/`--ref` to generate a new guided clip; use `videodraft edit video` when changing the source itself.\\n- Motion transfer uses `videodraft edit motion` with Kling V3 by default, or Kling 2.6 when explicitly requested or lower cost matters. It requires a subject image and a motion-reference video.\\n- Use Veo 3.1 when explicitly requested or as a fallback.\\n\\n**Audio and utilities:**\\n\\n- Prefer ElevenLabs for voiceover, dialogue, voice changing, dubbing, and sound effects. Honor an explicitly selected supported TTS voice/provider. Use Lyria for instrumental music and ElevenLabs Music for vocals, lyrics, or exact timing.\\n- Talking head/presenter: choose by source. Use managed `avatar create` then `avatar render` when the user wants a reusable avatar record and bundled speech. Use `avatar fabric` for a one-off portrait plus text or existing audio. Use `avatar lipsync` when both the source video and replacement audio already exist.\\n- Enhancement: use Topaz image/video upscaling only when the content is already correct. Use image 1x for cleanup, 2x by default, 4x when justified; use video 2x by default. Edit or regenerate creative errors.\\n\\nSee [references/models.md](references/models.md) for the detailed routing table and exact capability limits.\\n\\n## Prefer references when continuity matters\\n\\nPure text-to-image or text-to-video is fine for a generic one-off asset. When a specific character, product, location, style, composition, or brand identity must survive generation, use references instead of hoping the prompt recreates it.\\n\\n- If the user supplies reference media, preserve and pass it. Never reduce the request to text alone.\\n- When continuity matters, generate/select a strong still first with the selected image model (`nano-banana-2` by default), wait for its URL, then animate it as a start frame/reference. Confirm the combined image and video cost.\\n- For multiple shots, use `videodraft shots <project_id> --model <selected-image-model> --grid`, then animate the decoded shots. Preserve explicit models. A requested non-Seedance video model must use manual per-shot generation instead of Seedance full-video mode.\\n\\n## Cost and credits\\n\\nDo not call `videodraft credits` before routine generations. Paid endpoints validate and deduct atomically; if the balance is insufficient, the request is rejected before the provider job starts (CLI exit code 4). Check the balance only when the user asks, gives a credit budget, or a large workflow needs budget planning.\\n\\nFor expensive work, estimate with `--estimate` or `videodraft costs`, state the selected model/settings/cost, and get a go-ahead. This matters most for shot-image batches, long or high-resolution video, AI Production, and paid audio batches. Honor the user\'s confirmation preference for the session.\\n\\n`videodraft models image|video` lists the live image and video catalogs with supported inputs. Video entries are grouped as `generation`, `video_edit`, `motion_control`, `avatar_lipsync`, and `upscale`, and each reports the exact tool. Use `videodraft models video --category video_edit` to narrow the list. `videodraft models audio` lists Google Lyria and ElevenLabs audio/media tools, while `videodraft models voices` lists TTS voices. Consult them instead of guessing capabilities.\\n\\n## Async jobs\\n\\nImage/video generation is asynchronous: commands submit a job and **wait by default**, printing output URLs (and saving files with `--download`). In scripts/CI prefer explicit control:\\n\\n```bash\\nJOB=$(videodraft generate image \\"...\\" --no-wait --json | jq -r .job_id)\\nvideodraft wait \\"$JOB\\" --download \\"./outputs/{job_id}_{index}.{ext}\\" --json\\n```\\n\\nFor MANY jobs: submit each with `--no-wait`, collect ALL with one command \u2014 `videodraft wait <id1> <id2> ...` polls every job from one process with one batched request per tick. Do NOT spawn parallel `wait`/`generate --wait` processes for a batch.\\n\\nIf a wait times out, the job is still running server-side \u2014 `videodraft status <job_id>` later. Never re-submit just because a wait timed out (that double-spends credits).\\n\\n## Local files and reference images\\n\\nReference inputs must be public URLs. The CLI uploads local files automatically wherever a URL is expected (`--ref photo.jpg`, `--start-image frame.png`), or explicitly:\\n\\n```bash\\nURL=$(videodraft upload ./product.png --json | jq -r .url)\\n```\\n\\nNever silently drop a reference you couldn\'t upload \u2014 stop and tell the user. Never upload a user\'s file to a third-party host.\\n\\nWhen the user attaches media, classify each item before acting: a recurring **visual asset** (character/product/location/style), actual **footage to place as shots**, or **inspiration only**. See [references/pipeline.md](references/pipeline.md) for how each role flows into a project.\\n\\n## Showing media to the user\\n\\nGenerated media is **not** displayed in the chat automatically \u2014 you decide what to show. To preview an asset inline, save it locally (use `--download` so it lands under `media/`) and reference its **local path** as a Markdown link with a **leading `./`**:\\n\\n```\\n[ferrari shot](./media/ferrari_01.png) \u2190 image card\\n[the clip](./media/clip.mp4) \u2190 video player\\n[voiceover](./media/vo.mp3) \u2190 audio player\\n```\\n\\nPut the Markdown link **in your message text** \u2014 video and audio embed exactly like images. Do **not** use `SendUserFile` (or other file-send tools) to display media: that renders inside a collapsible tool card and gets buried in the tool list. The Markdown link in your prose is what produces the inline card.\\n\\nUse the path you saved to: a **workspace-relative** path (`./media/clip.mp4`, or `./<any-folder>/clip.mp4` \u2014 any folder in the workspace works), or the **absolute** path for a file outside the workspace (e.g. `/Users/you/Desktop/clip.mp4` or another workspace\'s path). Both render. Show the finished results worth showing (and only those \u2014 not every intermediate job). A bare CDN URL or a JSON dump of output URLs does **not** render; the local-path Markdown link is what produces an inline card.\\n\\n## The full pipeline (idea \u2192 MP4)\\n\\n```bash\\nvideodraft create \\"<idea>\\" --ar 9:16 # project: script \u2192 visual assets \u2192 storyboard\\nvideodraft shots <project_id> --grid --estimate # cost preview, confirm with user\\nvideodraft shots <project_id> --grid # batch shot images (waits, writes onto shot cards)\\nvideodraft produce <project_id> # voiceovers + captions + production timeline\\nvideodraft export <project_id> --download final.mp4\\n```\\n\\nOptional between produce and export: per-shot motion clips (`videodraft generate video ... --project <id>` then place it with `videodraft attach <project> --scene N --shot M --media <url|file> --type video --duration <s>`), music (`videodraft generate music \\"...\\" --attach <project_id>`), and standalone audio assets (`generate sound-effect`, `generate dialogue`, `generate voice-changer`, `generate dub`). Details, per-step tools and editing rules: [references/pipeline.md](references/pipeline.md).\\n\\nAvatar/talking-head videos use dedicated commands. For a reusable managed avatar, obtain or generate a clear portrait \u2192 `videodraft avatar script` when needed \u2192 `videodraft avatar create` \u2192 `videodraft avatar render --resolution 720p`. For a one-off portrait, use `videodraft avatar fabric <portrait> --text \\"...\\"` or `--audio <file>`. For an existing video plus replacement audio, use `videodraft avatar lipsync <video> --audio <file>`. Managed script/creation is bundled/free; direct Fabric, Sync, the managed Fabric render, and optional portrait generation/upscaling are paid. Confirm expensive steps first.\\n\\n## Working with project data\\n\\nA project is one JSON blob (script, storyboard scenes, shot cards, visual assets, production timeline). To inspect: `videodraft projects get <id>`. To edit: fetch `--raw`, modify, then `videodraft call update_project` \u2014 objects deep-merge, **arrays replace wholesale** (send the complete `storyboard.scenes` array to change one scene). Snapshot first with `videodraft checkpoint create <id>` before risky edits. Schema reference: `videodraft call get_project_schema`.\\n\\n## More\\n\\n- [references/pipeline.md](references/pipeline.md) \u2014 project data model, step-by-step tools, attaching media, editing safely\\n- [references/models.md](references/models.md) \u2014 choosing image/video models, pricing patterns, voices and styles\\n- [references/examples.md](references/examples.md) \u2014 recipes: batch product videos from a CSV, talking-head from a script, changelog video in CI\\n","references/examples.md":"# Recipes\\n\\nWorking patterns for common asks. All assume auth (`videodraft login` once, or `VIDEODRAFT_API_KEY` in the environment) and use `--json` for parsing.\\n\\n## 1. Batch product videos from a CSV\\n\\nOne 9:16 product clip per row of `products.csv` (`name,image_url,tagline`):\\n\\n```bash\\n#!/usr/bin/env bash\\nset -euo pipefail\\nmkdir -p outputs\\n\\nwhile IFS=, read -r name image tagline; do\\n job=$(videodraft generate video \\\\\\n \\"Premium product shot of ${name}: ${tagline}. Slow orbit, studio lighting.\\" \\\\\\n --model gemini-omni-flash --ar 9:16 --duration 6 \\\\\\n --start-image \\"$image\\" \\\\\\n --no-wait --json | jq -r .job_id)\\n echo \\"$name,$job\\" >> outputs/jobs.csv\\ndone < <(tail -n +2 products.csv)\\n\\n# Collect ALL results with ONE process (batched polling \u2014 one request per tick)\\nvideodraft wait $(cut -d, -f2 outputs/jobs.csv) \\\\\\n --download \\"outputs/{job_id}_{index}.{ext}\\" --json > outputs/results.json\\n# map job ids back to product names via outputs/jobs.csv\\n```\\n\\nSubmit-then-collect parallelizes server-side generation; the single multi-id `wait` keeps it to one local process and one batched poll request per tick no matter how many jobs. Gemini Omni Flash is selected because these are six-second first-frame product clips. Estimate first: `videodraft costs gemini-omni-flash --type video --duration 6 --resolution 720p --audio` \xD7 rows, and confirm with the user.\\n\\n## 2. Full marketing video from one idea\\n\\n```bash\\nvideodraft create \\"30-second launch video for Solace, a sleep-tracking ring. Calm, premium, dark palette.\\" \\\\\\n --ar 9:16 --style cinematic --json > project.json\\nPROJECT=$(jq -r .project_id project.json)\\n\\nvideodraft shots \\"$PROJECT\\" --grid --estimate # show the user the cost; get a go-ahead\\nvideodraft shots \\"$PROJECT\\" --grid\\nvideodraft produce \\"$PROJECT\\"\\nvideodraft generate music \\"minimal ambient, warm pads, 60 BPM\\" --attach \\"$PROJECT\\"\\nvideodraft export \\"$PROJECT\\" --download solace-launch.mp4\\n```\\n\\nThe project stays editable at the URL in `project.json` (`.urls`) \u2014 hand it to the user for tweaks.\\n\\n## 3. Talking-head (avatar) video\\n\\nWhen the user has no portrait, generate a clear front-facing avatar image first. Skip this step when they supplied one or an existing character should be reused.\\n\\n```bash\\nvideodraft generate image \\\\\\n \\"Front-facing head-and-shoulders portrait of a friendly coffee expert, direct eye contact, natural expression, clean studio background\\" \\\\\\n --model nano-banana-2 --ar 9:16 --download ./media/avatar.png\\n\\nSCRIPT=$(videodraft avatar script \\"why our espresso subscription saves you money\\" --style ad-style --json | jq -r .script)\\nAVATAR=$(videodraft avatar create ./media/avatar.png --script \\"$SCRIPT\\" --voice elevenlabs-kPzsL2i3teMYv0FxEYQ6 --ar 9:16 --json | jq -r .avatar_video_id)\\nvideodraft avatar render \\"$AVATAR\\" --resolution 720p # VEED Fabric paid step; confirm cost first (~20 credits/sec)\\n```\\n\\n`avatar script` and `avatar create` (including speech) are bundled/free. In this example only the optional portrait generation and Fabric render spend credits.\\n\\nIf the portrait is low resolution, enhance it before `avatar create`:\\n\\n```bash\\nvideodraft upscale image ./founder-small.jpg --scale 2x --download ./media/founder-upscaled.png\\n```\\n\\nFor a one-off portrait animation without creating a managed avatar record:\\n\\n```bash\\nvideodraft avatar fabric ./founder.jpg \\\\\\n --text \\"Welcome to the weekly product update.\\" \\\\\\n --voice-description \\"warm, confident American presenter\\" \\\\\\n --resolution 720p --download ./media/presenter.mp4\\n```\\n\\nWhen the user already has both the video and replacement speech:\\n\\n```bash\\nvideodraft avatar lipsync ./presenter.mp4 \\\\\\n --audio ./localized-voiceover.mp3 \\\\\\n --sync-mode loop --download ./media/presenter-localized.mp4\\n```\\n\\nEdit an existing video with a dedicated edit model:\\n\\n```bash\\nvideodraft models video --category video_edit\\nvideodraft edit video ./product-demo.mp4 \\\\\\n \\"Turn the room into a warm evening scene while preserving the product and camera motion\\" \\\\\\n --model wan-2.7-ref-edit --ref ./evening-style.jpg \\\\\\n --preserve-audio --download ./media/product-demo-evening.mp4\\n```\\n\\nTransfer motion from a reference clip onto a character image:\\n\\n```bash\\nvideodraft edit motion ./character.png \\\\\\n \\"Apply the dancer\'s movement to this character while preserving identity\\" \\\\\\n --motion-video ./dance-reference.mp4 \\\\\\n --model kling-v3-motion-control --quality pro \\\\\\n --download ./media/character-dance.mp4\\n```\\n\\n## 4. Changelog video in CI\\n\\nIn a GitHub Action with `VIDEODRAFT_API_KEY` set as a secret:\\n\\n```bash\\nNOTES=$(git log --oneline v1.2.0..HEAD | head -20)\\nvideodraft create \\"Weekly product update video. Energetic, 20 seconds. Changes: ${NOTES}\\" --ar 16:9 --json > p.json\\nPROJECT=$(jq -r .project_id p.json)\\nvideodraft shots \\"$PROJECT\\" && videodraft produce \\"$PROJECT\\"\\nvideodraft export \\"$PROJECT\\" --download changelog.mp4 --wait-timeout 30m\\n```\\n\\n## 5. Variations and picking a winner\\n\\n```bash\\nvideodraft generate image \\"logo concept: minimalist fox, geometric\\" --num 4 --download \\"./concepts/{job_id}_{index}.{ext}\\" --json\\n# Show all 4 to the user; regenerate the chosen one at higher res:\\nvideodraft generate image \\"<same prompt>\\" --model nano-banana-pro --resolution 4K\\n```\\n\\n## 6. Reaching tools without a curated command\\n\\n```bash\\nvideodraft tools list --json | jq -r \'.[].name\'\\nvideodraft tools schema attach_media_to_shot --json\\nvideodraft call attach_media_to_shot --args \'{\\"project_id\\":\\"...\\",\\"scene_index\\":0,\\"shot_index\\":1,\\"media_url\\":\\"https://...\\",\\"media_type\\":\\"video\\",\\"duration_seconds\\":6}\'\\n```\\n\\nAnything the VideoDraft MCP exposes \u2014 character studio, product studio, timeline editing \u2014 is reachable this way even before it gets a curated command.\\n\\n## 7. Enhance an existing asset without changing it\\n\\n```bash\\n# Light image cleanup, no enlargement\\nvideodraft upscale image ./poster.png --scale 1x --download ./media/poster-enhanced.png\\n\\n# General image and video enlargement\\nvideodraft upscale image ./frame.png --scale 2x --download ./media/frame-2x.png\\nvideodraft upscale video ./clip.mp4 --scale 2x --download ./media/clip-2x.mp4\\n```\\n\\nUse these when the content is correct and only quality or resolution needs improvement. If the poster text, composition, subject, or motion is wrong, edit or regenerate instead.\\n","references/models.md":"# Choosing models (and predicting cost)\\n\\nAlways consult the live catalog instead of memorizing this page \u2014 models change weekly:\\n\\n```bash\\nvideodraft models image --json # every image model + inputs (aspect ratios, resolutions, max refs)\\nvideodraft models video --json # every video model + inputs + per-second pricing metadata\\nvideodraft models audio --json # standalone audio/media models + pricing inputs\\nvideodraft models voices --json # TTS voices\\nvideodraft models styles --json # visual style presets\\n```\\n\\n## Task-based model selection\\n\\nHonor an explicitly named model when it supports the request. Otherwise choose from the task\'s inputs, duration, audio, quality, speed, and cost. Pass the chosen model explicitly instead of relying on a blind platform fallback.\\n\\n### Images\\n\\n| Need | Choose | Why |\\n| -------------------------------------------------------------------------------------- | -------------------- | ----------------------------------------------------------------- |\\n| Most generation, editing, character consistency, or reference work | `nano-banana-2` | Best general default; 1K/2K/4K and up to 14 reference images |\\n| Highest-quality complex generation or reasoning | `nano-banana-pro` | Premium Nano Banana quality and reasoning |\\n| Fast, inexpensive drafts and iteration | `nano-banana-2-lite` | Fastest/cheapest Nano Banana option; 1K only, up to 14 references |\\n| Posters, title cards, signs, logos, or any image with important readable text | `gpt-image-2` | Strong text rendering; up to 16 image inputs and 1K/2K/4K output |\\n| Complex multi-image composition, precise editing, or a strong alternate interpretation | `gpt-image-2` | Strong non-Nano alternative with multi-image input |\\n\\nUse `--num 1..4` for variations of one prompt in a single call. Never loop separate paid calls for variations that fit in one request.\\n\\n### Videos\\n\\n| Need | Choose | Important limits |\\n| ---------------------------------------------------------------------------------------------------------- | ------------------- | --------------------------------------------------------------------------------------------------- |\\n| Most text, first-frame, image-reference, or source-video-edit requests up to 10s | `gemini-omni-flash` | 720p, 3-10s or auto, audio always on, up to 10 total image inputs, one source video |\\n| Video/audio references, mixed reference media, broad aspect ratios, frame-mode first+last frame, or 11-15s | `seedance-2` | 4-15s or auto; up to 9 image, 3 video, and 3 audio refs; audio toggle; Mini/Fast are 480p/720p only |\\n| Fast polished 3-15s video with first frame, multi-prompt, and native audio | `kling-v3-turbo` | Audio always on; Pro default; no end frame or reference-media mode |\\n| Cinematic 3-15s with image references, first+last frame, multi-prompt, audio control, or 4K | `kling-o3` | Up to 7 image refs; Standard/Pro/4K; audio toggle |\\n| Kling 3-15s with first+last frame, multi-prompt, optional audio, or 4K, without reference-image mode | `kling-3.0` | Standard/Pro/4K; audio toggle |\\n| User explicitly requests Veo, or the selected workflow specifically needs Veo | `google-veo3.1` | Good fallback, but not the preferred general model |\\n\\nRouting rules:\\n\\n- Around 11-15 seconds with native audio: use Kling or Seedance, not Gemini.\\n- One existing source video that should be edited, with an output up to 10 seconds and no additional media references to preserve: use Gemini Omni Flash.\\n- Video or audio supplied as creative reference: use Seedance 2.0.\\n- A video plus any image/audio references that must all be preserved: use Seedance 2.0. Do not promise that Gemini will preserve mixed source media; its Fal BYOK edit mode accepts only the source video and prompt.\\n- First and last frame control: use Seedance, Kling O3, or Kling 3.0. Gemini supports a first frame but not a last frame.\\n- Seedance reference mode and first-plus-last-frame mode are separate. Do not promise reference video/audio plus a last frame in one generation.\\n- Multi-prompt sequencing: use Kling 3.0 Turbo, Kling O3, or Kling 3.0.\\n- Seedance quality: `mini` for the lowest cost, `fast` for speed, `standard` for maximum quality and for 1080p/4K.\\n\\n### Video edit and motion-control categories\\n\\nUse `videodraft models video --category video_edit` for existing-video transforms and `--category motion_control` for motion transfer.\\n\\n| Need | Command/model | Important limits |\\n| ------------------------------------- | --------------------------------------------------------------------- | -------------------------------------------------------------------------------- |\\n| Simple prompt edit of one video | `videodraft edit video <video> \\"...\\" --model grok-imagine-video-edit` | No image refs; source truncated to 8s; auto/480p/720p |\\n| Edit with one style/reference image | `--model wan-2.7-ref-edit --ref <image>` | One image ref; 2-10s or match source |\\n| Edit with several image references | `--model happy-horse-video-edit --ref ...` | Up to 5 refs; 720p/1080p; source capped at 15s |\\n| Controlled Kling edit | `--model kling-o3-video-ref-edit --ref ...` | Up to 4 refs; Standard/Pro; source clamped to 3-10s |\\n| Transfer reference motion to an image | `videodraft edit motion <image> \\"...\\" --motion-video <video>` | Kling V3 default; image orientation caps motion at 10s, video orientation at 30s |\\n\\nIf the user explicitly names one of these models, preserve it. The CLI uploads local source videos and reference images automatically. Editing returns an async job and waits by default.\\n\\nKling O3 and Wan 2.7 Ref/Edit are dual-mode cards. `videodraft edit video` uses edit mode. `videodraft generate video --model kling-o3-video-ref-edit` requires exactly one `--ref-video` and generates a new reference-guided clip. `--model wan-2.7-ref-edit` generates a new clip from one or more `--ref`/`--ref-video` inputs.\\n\\n### Reference-first video workflow\\n\\n- Prefer a start frame or reference image whenever a specific character, product, location, style, composition, or brand identity must stay recognizable.\\n- If the user gives a reference, pass it. Never silently replace it with a text description.\\n- If no reference exists and continuity matters, generate a still first with the user\'s explicitly requested compatible image model, otherwise use Nano Banana 2. Wait for the image URL, then animate it with the selected video model. Confirm the combined image plus video cost before starting.\\n- For multi-shot scenes, generate shot images with `videodraft shots <project_id> --model <selected-image-model> --grid`. Preserve an explicitly requested compatible image model; otherwise use `nano-banana-2`. The grid establishes the scene and characters together, then decodes into individual shot images.\\n- Animate the decoded shot images as per-shot start frames or references. Do not independently text-generate each video clip when the shots need to match.\\n- Pure text-to-video remains appropriate for generic one-off footage where no subject, composition, or continuity needs to be preserved.\\n\\n### Audio\\n\\n- **Voiceover/TTS**: prefer ElevenLabs. Brittney is the platform default voice; under ElevenLabs BYOK, use a compatible voice from the user\'s account. Honor another supported voice/provider when the user explicitly selects it.\\n- **Dialogue, voice changing, and dubbing**: ElevenLabs only.\\n- **Sound effects**: ElevenLabs Sound Effects only.\\n- **Music**: use `lyria-3-clip-preview` for a short instrumental/background score, `lyria-3-pro-preview` for a longer or higher-quality instrumental score, and `elevenlabs-music` when vocals/lyrics or a specified 10-120 second length matter.\\n- Voice Changer and Dubbing require the source media duration and currently accept source media up to 300 seconds.\\n\\n### Avatar / talking head\\n\\nChoose the dedicated path from the media the user already has:\\n\\n| Starting media | Command | Use |\\n| ----------------------------------------- | ------------------------------------------------------------------------- | --------------------------------------------------- |\\n| Portrait + script, reusable avatar record | `videodraft avatar create <portrait> --script \\"...\\"` then `avatar render` | Managed avatar flow with bundled speech preparation |\\n| Portrait + text | `videodraft avatar fabric <portrait> --text \\"...\\"` | One-off direct VEED Fabric text mode |\\n| Portrait + existing audio | `videodraft avatar fabric <portrait> --audio <audio>` | One-off direct VEED Fabric audio lip sync |\\n| Existing video + existing audio | `videodraft avatar lipsync <video> --audio <audio>` | Sync Labs Lipsync 2 |\\n\\nThe managed renderer is VEED Fabric Fast (`veed/fabric-1.0/fast`). Direct Fabric and Sync Labs are paid AI Studio generations and return async job IDs.\\n\\n1. Obtain the avatar image. Prefer the user\'s supplied portrait or an existing character. If none exists, use the user\'s explicitly requested compatible image model, otherwise generate a front-facing head-and-shoulders portrait with `nano-banana-2`, direct eye contact, a natural expression, and a clean background. Match the intended video aspect ratio when practical.\\n2. If the portrait is visibly soft or too small, run Topaz image enhancement/upscaling before animation.\\n3. Generate a script only if needed: `videodraft avatar script \\"<idea>\\"`.\\n4. Create the avatar record and speech: `videodraft avatar create <portrait-url-or-file> --script \\"...\\" --voice <id> --ar 9:16`. Prefer ElevenLabs when unspecified, but honor another explicitly selected supported voice/provider.\\n5. Render with VEED Fabric: `videodraft avatar render <avatar_video_id> --resolution 720p`.\\n\\nThe portrait is passed as the avatar\'s character image, not as a generic video\'s start frame. Prefer rendering directly at 720p. Use 480p only when the user prioritizes lower cost. Avatar script generation and `avatar create` (including speech) are bundled/free. Confirm the Fabric render cost, plus portrait generation or upscaling when needed.\\n\\nDirect Fabric text/audio and Sync Labs do not use the managed avatar record. The CLI uploads local portrait, video, and audio files automatically. `avatar fabric --speed fast` applies only to audio mode. Sync costs 5 credits per verified audio second; under Fal BYOK, `sync_mode` remains available but `temperature` and `active_speaker` are ignored by the provider.\\n\\n### Upscaling / enhancement\\n\\n- **Images**: Topaz via `videodraft upscale image <url-or-file> --scale 1x|2x|4x`. Use 1x for light enhancement without enlargement, 2x as the general default, and 4x only when the source quality and target size justify it. The result is synchronous.\\n- **Videos**: Topaz via `videodraft upscale video <url-or-file> --scale 2x`. Use 2x by default. The job is asynchronous; the CLI waits by default, while MCP callers poll `check_generation_status`. MCP video input must be VideoDraft-hosted, so upload local or external sources first.\\n- Use upscaling to preserve the image/video while improving detail, resolution, or cleanup. It cannot fix the wrong subject, misspelled text, bad framing, unwanted objects, broken continuity, or incorrect motion. Use an edit or regeneration for those problems.\\n- For a new Fabric avatar, render directly at 720p instead of rendering at 480p and then upscaling. Upscale the source portrait first only when the portrait itself is low quality.\\n\\n## Capability gotchas\\n\\n- Each model\'s `inputs` block is authoritative: supported `aspect_ratios`, `resolutions`, `quality_options`, `start_frame`/`end_frame`, `max_reference_images/videos/audio`, `multi_prompt`, `audio_toggle`. Passing an unsupported input fails with a clear error \u2014 check first, don\'t trial-and-error paid calls.\\n- Most video models support only 16:9 / 9:16 / 1:1. A 3:4 request hard-fails on most.\\n- `--seed` reproduces a specific output on models that support it (e.g. Flux, Ideogram V4); everything else ignores it. You do not need a seed for variation \u2014 `--num` already varies.\\n- `--rendering-speed` applies to Ideogram (V3: `Default`/`Turbo`/`Quality`; V4: `Turbo`/`Balanced`/`Quality`) and affects image cost \u2014 pass it to `videodraft costs ... --rendering-speed <tier>` for an accurate estimate. Always trust `videodraft models image --json` over this list; new models and tiers appear there the moment the platform ships them, with no CLI update.\\n- `seedream-v5-pro` supports unified text-to-image and reference-image editing with up to 10 image references. Use `--resolution 1K` for 7 credits/image or `--resolution 2K` for 14 credits/image.\\n- Reference inputs: `--ref <img>` (images), `--ref-video <v>` (Gemini Omni Flash, Seedance 2, Wan 2.7), `--ref-audio <a>` (Seedance 2). The CLI uploads local files for all of these, so you can pass a path or a URL. `--segment \\"<prompt>:<seconds>\\"` (repeatable) drives multi-prompt models (Kling 3.0 / 3.0 Turbo / O3); total 3-15s. `generate image --video-ref` is the nano-banana-2 video reference.\\n- The top-level prompt is OPTIONAL for `generate video` with multi-prompt models and for Kling 3.0 Turbo (`--model kling-v3-turbo`) image-to-video \u2014 a `--segment`-only or `--start-image`-only call is valid. Every other model still needs a prompt; the server enforces per-model rules.\\n- AI Production: `videodraft produce <project> --mode full_video` generates one Seedance 2 video per scene; poll with `videodraft generations`, then `videodraft finalize <project>` swaps them into the timeline before `export`. If the user explicitly requests another compatible video model, do not use this fixed Seedance path. Generate the project shots manually with the requested model and attach them to the timeline.\\n\\n## Cost model\\n\\n- Images: per image (\xD7 `--num`). Matrix-priced models (GPT-Image, Nano Banana Pro, Seedream v5 Pro) vary by resolution/quality.\\n- Video: usually credits/second \xD7 duration; rate depends on model + resolution + quality + native audio on/off.\\n- Shot-image batches: one image per shot (+1 grid image per scene in `--grid` mode) \u2014 the largest single spend in the pipeline.\\n- VEED Fabric avatar renders: ~10 credits/sec at 480p, ~20/sec at 720p. Avatar creation and its speech are bundled/free; only optional portrait generation/upscaling adds cost before the render.\\n- Direct VEED Fabric: text or normal audio is 8 credits/sec at 480p and 15/sec at 720p; fast audio is 10/sec at 480p and 20/sec at 720p.\\n- Sync Labs Lipsync 2: 5 credits per verified audio second.\\n- Voiceover TTS: 10 credits per 1000 characters for standard voices, 30 per 1000 for cloned `custom-*` voices (min 1, pro-rated); applies to standalone voiceovers AND per-scene narration during `produce`. Silent tracks are free. Voice cloning itself is a flat 150 credits per clone.\\n- Lyria music: flat per track, 10 credits (clip) / 15 credits (pro).\\n- ElevenLabs audio: sound effects are per second, dialogue is per character, music/voice-changer/dubbing are per started minute. Voice changer and dubbing reject source media above 300s in the current synchronous flow.\\n- Upscales: priced by scale and source size.\\n\\nQuote before spending:\\n\\n```bash\\nvideodraft costs gemini-omni-flash --type video --duration 8 --resolution 720p --audio\\nvideodraft costs seedance-2 --type video --duration 15 --resolution 720p --quality standard --audio\\nvideodraft costs elevenlabs-dubbing --type audio --duration 60\\nvideodraft costs elevenlabs-dialogue --type audio --chars 350\\nvideodraft costs voiceover --type audio --chars 800 # TTS: 10 cr / 1000 chars\\nvideodraft generate video \\"...\\" --model gemini-omni-flash --estimate # same quote, inline\\n```\\n","references/pipeline.md":"# VideoDraft pipeline reference\\n\\nEverything here works through the CLI (`videodraft <command>` / `videodraft call <tool>`) or the MCP connector (tool names in backticks). One backend; pick the surface you have.\\n\\nUse direct asset tools for standalone images, clips, audio, upscales, and descriptions. Use a project for any multi-scene video, story, ad, explainer, storyboard, editable timeline, or final export even when the request does not use the word \\"project.\\" Script-only uses a script-stage project and stops at the script.\\n\\n## Stages and their tools\\n\\n| Stage | CLI | Underlying tool |\\n| --------------------------------------- | --------------------------------------------------------------------------- | ------------------------------------------ |\\n| Idea \u2192 full storyboard project | `videodraft create \\"<idea>\\"` | `generate_storyboard_from_idea` |\\n| Idea \u2192 script only (stop there) | `videodraft create \\"<idea>\\" --script-only` | `generate_script_from_idea` |\\n| Footage IS the video | `videodraft call generate_storyboard_from_media` | `generate_storyboard_from_media` |\\n| Batch shot images | `videodraft shots <project>` | `generate_shot_images` |\\n| One shot image | `videodraft generate image --project <id> --scene N --shot M` | `generate_image` |\\n| Produce (voiceover, captions, timeline) | `videodraft produce <project>` | `produce_project` |\\n| Per-shot motion prompts | `videodraft video-prompts <project>` | `generate_video_prompts` |\\n| Motion clip for a shot | `videodraft generate video --project <id>` | `generate_video` |\\n| Attach a finished clip to the timeline | `videodraft attach <project> --scene N --shot M --media <url> --type video` | `attach_media_to_shot` |\\n| Background music | `videodraft generate music --attach <project>` | `generate_music` / `set_background_music` |\\n| Sound effect | `videodraft generate sound-effect \\"...\\"` | `generate_sound_effect` |\\n| Dialogue audio | `videodraft generate dialogue --line \\"voice:text\\"` | `generate_dialogue` |\\n| Voice changer | `videodraft generate voice-changer <audio>` | `change_voice` |\\n| Dubbing | `videodraft generate dub <audio_or_video>` | `dub_media` |\\n| Scene voiceover | `videodraft generate voiceover --project <id> --scene N` | `generate_voiceover` |\\n| Avatar script | `videodraft avatar script \\"<idea>\\"` | `generate_avatar_script` |\\n| Avatar + speech | `videodraft avatar create <portrait> --script \\"...\\"` | `create_avatar_video` |\\n| Talking-head render | `videodraft avatar render <avatar_video_id>` | `render_avatar_video` + `get_avatar_video` |\\n| Direct portrait + text/audio | `videodraft avatar fabric <portrait> --text \\"...\\"` or `--audio <file>` | `generate_veed_fabric_video` |\\n| Existing video + replacement audio | `videodraft avatar lipsync <video> --audio <file>` | `generate_sync_lipsync_video` |\\n| Existing-video AI edit | `videodraft edit video <video> \\"<change>\\" --model <video-edit-model>` | `edit_video` |\\n| Motion transfer | `videodraft edit motion <image> \\"<direction>\\" --motion-video <video>` | `generate_motion_control_video` |\\n| Image enhancement/upscale | `videodraft upscale image <image>` | `upscale_image` |\\n| Video enhancement/upscale | `videodraft upscale video <video>` | `upscale_video` |\\n| Final MP4 | `videodraft export <project>` | `export_video` + `check_export_status` |\\n\\n## Rules that prevent broken results\\n\\n- **The storyboard is generated FROM the script**, never from the raw idea. `videodraft create` runs the whole chain correctly. Don\'t call `generate_storyboard_scenes` with a raw idea as the \\"script\\".\\n- **Visual consistency**: never generate a storyboard shot in isolation. Shot prompts carry `[[asset:Name]]` / `[[shot:X-Y]]` tags that `generate_shot_images` resolves against the project\'s visual assets and prior shots. When generating a single shot whose prompt has no tags, pass `--ref` images yourself (the project\'s visual assets and/or the previous shot\'s image; `projects get` exposes both). For scenes with multiple shots or recurring characters, prefer `videodraft shots <project> --model <selected-image-model> --grid`: preserve an explicitly requested compatible image model, otherwise use `nano-banana-2`. It creates one coherent scene grid, then decodes it into individual shot images.\\n- **Reference-first video**: when identity, styling, or composition matters, do not generate each motion clip from text alone. Generate or select the shot still first, then pass the decoded shot image as `--start-image` or `--ref` to the selected video model. AI Production already composes scene grids and sends them to Seedance as references. If the user explicitly requests another compatible video model, bypass fixed Seedance full-video mode and generate the per-shot clips with the requested model, using the individual decoded shot images as anchors.\\n- **Hold off generating shot images while the user is still iterating** on storyboard structure.\\n- **produce \u2192 export ordering**: `export` requires a produced project where every production scene has timeline media. If `produce` returns `generating_shot_images`, poll the job ids it returns, then re-run produce.\\n- **Generated motion clips do not auto-attach**: after `generate video` completes, attach the clip with `attach_media_to_shot` (`media_type:\\"video\\"`, include `duration_seconds`) \u2014 it replaces the production timeline clip while keeping the storyboard still.\\n- **Talking heads use dedicated avatar tools**: do not use `generate video`. Use managed `avatar create` and `avatar render` for reusable avatars, direct `avatar fabric` for a portrait plus text/audio, and `avatar lipsync` for an existing video plus replacement audio. Reuse a supplied person image or generate a clear front-facing portrait with the explicitly requested compatible image model, otherwise Nano Banana 2. Managed avatar creation and speech are bundled/free; direct Fabric, Sync, and render are paid.\\n- **Existing-video edits use their own category**: call `edit_video` or `videodraft edit video` with a `video_edit` model when transforming the source itself. Kling O3 and Wan 2.7 Ref/Edit are dual-mode: their reference-generation modes may use generic `generate_video` to create a new guided clip. Motion transfer similarly uses `generate_motion_control_video` or `videodraft edit motion` with a `motion_control` model.\\n- **Upscaling preserves rather than redesigns**: use Topaz when resolution, detail, or cleanup is the problem. Regenerate or edit when the subject, text, framing, continuity, or motion is wrong. Upscale a low-quality avatar portrait before Fabric; do not render a new avatar at 480p just to upscale the result.\\n- **Timeouts on the one-shot create**: if `create` times out at the transport layer, the project was still created server-side \u2014 `videodraft projects list`, take the most recent, and resume with its id. Don\'t start a duplicate.\\n\\n## User-attached media: classify roles first\\n\\nFor EACH attached file decide:\\n\\n- **visual_asset** \u2014 recurring reference (character / product / location / style). Upload, then pass in `visual_assets` of `generate_storyboard_from_idea` (via `videodraft call`), or add to an existing project with `add_visual_assets`. Type must be one of `character | object | location | style | custom` with a short name + concrete description.\\n- **shot** \u2014 the media IS footage for the video. Whole video = footage \u2192 `generate_storyboard_from_media`. Idea + footage \u2192 `generate_storyboard_from_idea` with `shot_media`. Existing storyboard \u2192 `attach_media_to_shots`.\\n- **reference** \u2014 inspiration only \u2192 fold a description into the idea/instructions; don\'t place it as a shot or asset.\\n\\nAmbiguous (e.g. a person holding a product)? Ask the user.\\n\\nUploads persist in the media library \u2014 recall later with `videodraft media list`.\\n\\n## Editing project data safely\\n\\n1. `videodraft call get_project_schema` \u2014 read the structure once per session.\\n2. `videodraft projects get <id> --raw` \u2014 the exact editable blob.\\n3. Modify; then `videodraft call update_project --stdin` with `{\\"project_id\\": \\"...\\", \\"data\\": {...}}`.\\n - Objects deep-merge key-by-key; **arrays replace wholesale** \u2014 send the complete array you\'re changing (e.g. all of `storyboard.scenes`).\\n - Scene shot arrays (`image_prompt` / `shot_types` / `shot_actions` / `search_prompt` / `preview_media`) are auto-aligned; fix-ups come back as warnings.\\n4. Snapshot before risky edits: `videodraft checkpoint create <id> --name \\"before re-script\\"`. Restore with `videodraft checkpoint restore <id> <version>`.\\n\\n## AI Studio sessions (standalone generations)\\n\\nProject generations group automatically. For standalone work in a long conversation, create one session up front and reuse it:\\n\\n```bash\\nSESSION=$(videodraft call create_ai_studio_session --arg name=\\"Fox brand explorations\\" --json | jq -r .session_id)\\nvideodraft generate image \\"...\\" --session \\"$SESSION\\"\\n```\\n"}');
|
|
3032
3527
|
}
|
|
3033
3528
|
const root = bundledSkillDir();
|
|
3034
3529
|
const files = {};
|
|
3035
3530
|
const walk = (dir) => {
|
|
3036
|
-
for (const entry of
|
|
3531
|
+
for (const entry of fs6.readdirSync(dir, { withFileTypes: true })) {
|
|
3037
3532
|
if (entry.name.startsWith(".")) continue;
|
|
3038
3533
|
const full = path4.join(dir, entry.name);
|
|
3039
3534
|
if (entry.isDirectory()) walk(full);
|
|
3040
|
-
else files[path4.relative(root, full).split(path4.sep).join("/")] =
|
|
3535
|
+
else files[path4.relative(root, full).split(path4.sep).join("/")] = fs6.readFileSync(full, "utf8");
|
|
3041
3536
|
}
|
|
3042
3537
|
};
|
|
3043
3538
|
walk(root);
|
|
@@ -3046,8 +3541,8 @@ function bundledSkillFiles() {
|
|
|
3046
3541
|
function writeSkillFiles(files, destRoot) {
|
|
3047
3542
|
for (const [rel, content] of Object.entries(files)) {
|
|
3048
3543
|
const dest = path4.join(destRoot, rel);
|
|
3049
|
-
|
|
3050
|
-
|
|
3544
|
+
fs6.mkdirSync(path4.dirname(dest), { recursive: true });
|
|
3545
|
+
fs6.writeFileSync(dest, content);
|
|
3051
3546
|
}
|
|
3052
3547
|
}
|
|
3053
3548
|
var KNOWN_AGENTS = ["claude", "codex", "cursor"];
|
|
@@ -3068,7 +3563,7 @@ function targetDir(agent, project) {
|
|
|
3068
3563
|
function detectInstalledAgents() {
|
|
3069
3564
|
return KNOWN_AGENTS.filter((a) => {
|
|
3070
3565
|
try {
|
|
3071
|
-
return
|
|
3566
|
+
return fs6.existsSync(path4.join(os2.homedir(), agentDir(a)));
|
|
3072
3567
|
} catch {
|
|
3073
3568
|
return false;
|
|
3074
3569
|
}
|
|
@@ -3120,7 +3615,7 @@ function registerSkillCommands(program) {
|
|
|
3120
3615
|
}
|
|
3121
3616
|
const results = agents.map((agent) => {
|
|
3122
3617
|
const dest = targetDir(agent, Boolean(opts.project));
|
|
3123
|
-
const existed =
|
|
3618
|
+
const existed = fs6.existsSync(dest);
|
|
3124
3619
|
if (existed && !opts.force) {
|
|
3125
3620
|
return { agent, installed_to: dest, status: "skipped", reason: "already installed (use --force)" };
|
|
3126
3621
|
}
|
|
@@ -3286,6 +3781,194 @@ _videodraft
|
|
|
3286
3781
|
});
|
|
3287
3782
|
}
|
|
3288
3783
|
|
|
3784
|
+
// src/commands/edit.ts
|
|
3785
|
+
var VIDEO_EDIT_MODELS = /* @__PURE__ */ new Set([
|
|
3786
|
+
"happy-horse-video-edit",
|
|
3787
|
+
"kling-o3-video-ref-edit",
|
|
3788
|
+
"grok-imagine-video-edit",
|
|
3789
|
+
"wan-2.7-ref-edit"
|
|
3790
|
+
]);
|
|
3791
|
+
function collect2(value, previous) {
|
|
3792
|
+
return [...previous, value];
|
|
3793
|
+
}
|
|
3794
|
+
function positiveNumber2(value, label) {
|
|
3795
|
+
if (value === void 0) return void 0;
|
|
3796
|
+
const parsed = Number(value);
|
|
3797
|
+
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
3798
|
+
throw new UsageError(`${label} must be a positive number.`);
|
|
3799
|
+
}
|
|
3800
|
+
return parsed;
|
|
3801
|
+
}
|
|
3802
|
+
function chooseEditModel(explicit, refCount) {
|
|
3803
|
+
if (explicit) {
|
|
3804
|
+
if (!VIDEO_EDIT_MODELS.has(explicit)) {
|
|
3805
|
+
throw new UsageError(
|
|
3806
|
+
`Unsupported video edit model "${explicit}". Run videodraft models video and use a video_edit entry.`
|
|
3807
|
+
);
|
|
3808
|
+
}
|
|
3809
|
+
return explicit;
|
|
3810
|
+
}
|
|
3811
|
+
if (refCount > 1) return "happy-horse-video-edit";
|
|
3812
|
+
if (refCount === 1) return "wan-2.7-ref-edit";
|
|
3813
|
+
return "grok-imagine-video-edit";
|
|
3814
|
+
}
|
|
3815
|
+
function registerEditCommands(program) {
|
|
3816
|
+
const edit = program.command("edit").description("Edit or motion-transfer existing video media");
|
|
3817
|
+
edit.command("video <video_url_or_file> <prompt...>").description("Edit an existing video with a dedicated video-edit model").option(
|
|
3818
|
+
"--model <id>",
|
|
3819
|
+
"happy-horse-video-edit | kling-o3-video-ref-edit | grok-imagine-video-edit | wan-2.7-ref-edit"
|
|
3820
|
+
).option("--ref <url|file>", "reference image (repeatable)", collect2, []).option("--resolution <res>", "model-specific output resolution").option("--quality <tier>", "Kling O3 only: standard or pro").option(
|
|
3821
|
+
"--duration <seconds>",
|
|
3822
|
+
"Wan 2.7 only: 2-10s; other edit models follow the source/model duration"
|
|
3823
|
+
).option("--preserve-audio", "preserve original source audio when supported").option("--project <id>", "group in a project's AI Studio session").option("--session <id>", "AI Studio session id").option("--download <path>", "download the finished video").option("--no-wait", "submit and return the job id immediately").option("--estimate", "print the cost estimate and exit").action(async function(videoSource, promptWords) {
|
|
3824
|
+
const ctx = buildContext(this);
|
|
3825
|
+
const opts = this.opts();
|
|
3826
|
+
const model = chooseEditModel(opts.model, (opts.ref ?? []).length);
|
|
3827
|
+
const duration = positiveNumber2(opts.duration, "--duration");
|
|
3828
|
+
const refCount = (opts.ref ?? []).length;
|
|
3829
|
+
const maxRefs = model === "happy-horse-video-edit" ? 5 : model === "kling-o3-video-ref-edit" ? 4 : model === "wan-2.7-ref-edit" ? 1 : 0;
|
|
3830
|
+
if (refCount > maxRefs) {
|
|
3831
|
+
throw new UsageError(
|
|
3832
|
+
`${model} accepts at most ${maxRefs} reference images.`
|
|
3833
|
+
);
|
|
3834
|
+
}
|
|
3835
|
+
if (opts.quality && model !== "kling-o3-video-ref-edit") {
|
|
3836
|
+
throw new UsageError(
|
|
3837
|
+
"--quality applies only to kling-o3-video-ref-edit."
|
|
3838
|
+
);
|
|
3839
|
+
}
|
|
3840
|
+
if (opts.quality && !["standard", "pro"].includes(opts.quality)) {
|
|
3841
|
+
throw new UsageError('--quality must be "standard" or "pro".');
|
|
3842
|
+
}
|
|
3843
|
+
if (opts.preserveAudio && model === "grok-imagine-video-edit") {
|
|
3844
|
+
throw new UsageError(
|
|
3845
|
+
"grok-imagine-video-edit does not expose source-audio preservation."
|
|
3846
|
+
);
|
|
3847
|
+
}
|
|
3848
|
+
if (opts.estimate) {
|
|
3849
|
+
const estimate = await ctx.client.callTool(
|
|
3850
|
+
"get_model_costs",
|
|
3851
|
+
compact({
|
|
3852
|
+
model_id: model,
|
|
3853
|
+
type: "video",
|
|
3854
|
+
duration_seconds: duration,
|
|
3855
|
+
resolution: opts.resolution,
|
|
3856
|
+
quality: opts.quality
|
|
3857
|
+
})
|
|
3858
|
+
);
|
|
3859
|
+
emit(ctx.out, {
|
|
3860
|
+
estimate,
|
|
3861
|
+
model,
|
|
3862
|
+
note: "No credits were spent (--estimate)."
|
|
3863
|
+
});
|
|
3864
|
+
return;
|
|
3865
|
+
}
|
|
3866
|
+
if (duration !== void 0 && model !== "wan-2.7-ref-edit") {
|
|
3867
|
+
throw new UsageError(
|
|
3868
|
+
`--duration is only controllable for wan-2.7-ref-edit. ${model} follows its source/model duration.`
|
|
3869
|
+
);
|
|
3870
|
+
}
|
|
3871
|
+
const [[videoUrl], referenceImages] = await Promise.all([
|
|
3872
|
+
resolveRefs(ctx, [videoSource]),
|
|
3873
|
+
resolveRefs(ctx, opts.ref ?? [])
|
|
3874
|
+
]);
|
|
3875
|
+
capture("cli_edit", { kind: "video", model, wait: opts.wait !== false });
|
|
3876
|
+
const submitted = await ctx.client.callTool(
|
|
3877
|
+
"edit_video",
|
|
3878
|
+
compact({
|
|
3879
|
+
model,
|
|
3880
|
+
prompt: promptWords.join(" ").trim(),
|
|
3881
|
+
video_url: videoUrl,
|
|
3882
|
+
reference_images: referenceImages.length > 0 ? referenceImages : void 0,
|
|
3883
|
+
resolution: opts.resolution,
|
|
3884
|
+
quality: opts.quality,
|
|
3885
|
+
duration_seconds: duration,
|
|
3886
|
+
preserve_audio: opts.preserveAudio ? true : void 0,
|
|
3887
|
+
project_id: opts.project,
|
|
3888
|
+
session_id: opts.session
|
|
3889
|
+
})
|
|
3890
|
+
);
|
|
3891
|
+
await handleAsyncJob(ctx, submitted, {
|
|
3892
|
+
wait: opts.wait !== false,
|
|
3893
|
+
download: opts.download,
|
|
3894
|
+
label: `Editing video with ${model}`
|
|
3895
|
+
});
|
|
3896
|
+
});
|
|
3897
|
+
edit.command("motion <image_url_or_file> <prompt...>").description("Transfer motion from a reference video onto a subject image").requiredOption("--motion-video <url|file>", "motion reference video").option(
|
|
3898
|
+
"--model <id>",
|
|
3899
|
+
"kling-v3-motion-control (default) | kling-2.6-motion-control"
|
|
3900
|
+
).option("--quality <tier>", "standard or pro (default pro)").option(
|
|
3901
|
+
"--orientation <mode>",
|
|
3902
|
+
"video (30s cap) or image (10s cap); defaults to the model setting"
|
|
3903
|
+
).option("--no-original-sound", "remove sound from the motion reference").option("--duration <seconds>", "optional estimate hint").option("--project <id>", "group in a project's AI Studio session").option("--session <id>", "AI Studio session id").option("--download <path>", "download the finished video").option("--no-wait", "submit and return the job id immediately").option("--estimate", "print the cost estimate and exit").action(async function(imageSource, promptWords) {
|
|
3904
|
+
const ctx = buildContext(this);
|
|
3905
|
+
const opts = this.opts();
|
|
3906
|
+
const model = opts.model ?? "kling-v3-motion-control";
|
|
3907
|
+
if (model !== "kling-v3-motion-control" && model !== "kling-2.6-motion-control") {
|
|
3908
|
+
throw new UsageError(
|
|
3909
|
+
`Unsupported motion-control model "${model}". Run videodraft models video and use a motion_control entry.`
|
|
3910
|
+
);
|
|
3911
|
+
}
|
|
3912
|
+
const duration = positiveNumber2(opts.duration, "--duration");
|
|
3913
|
+
if (opts.quality && !["standard", "pro"].includes(opts.quality)) {
|
|
3914
|
+
throw new UsageError('--quality must be "standard" or "pro".');
|
|
3915
|
+
}
|
|
3916
|
+
if (opts.orientation && !["image", "video"].includes(opts.orientation)) {
|
|
3917
|
+
throw new UsageError('--orientation must be "image" or "video".');
|
|
3918
|
+
}
|
|
3919
|
+
if (opts.estimate) {
|
|
3920
|
+
const estimate = await ctx.client.callTool(
|
|
3921
|
+
"get_model_costs",
|
|
3922
|
+
compact({
|
|
3923
|
+
model_id: model,
|
|
3924
|
+
type: "video",
|
|
3925
|
+
duration_seconds: duration,
|
|
3926
|
+
quality: opts.quality
|
|
3927
|
+
})
|
|
3928
|
+
);
|
|
3929
|
+
emit(ctx.out, {
|
|
3930
|
+
estimate,
|
|
3931
|
+
model,
|
|
3932
|
+
note: "No credits were spent (--estimate)."
|
|
3933
|
+
});
|
|
3934
|
+
return;
|
|
3935
|
+
}
|
|
3936
|
+
if (duration !== void 0) {
|
|
3937
|
+
throw new UsageError(
|
|
3938
|
+
"--duration is estimate-only for motion control. Output duration follows the motion video and orientation cap."
|
|
3939
|
+
);
|
|
3940
|
+
}
|
|
3941
|
+
const [[imageUrl], [motionVideoUrl]] = await Promise.all([
|
|
3942
|
+
resolveRefs(ctx, [imageSource]),
|
|
3943
|
+
resolveRefs(ctx, [opts.motionVideo])
|
|
3944
|
+
]);
|
|
3945
|
+
capture("cli_edit", {
|
|
3946
|
+
kind: "motion_control",
|
|
3947
|
+
model,
|
|
3948
|
+
wait: opts.wait !== false
|
|
3949
|
+
});
|
|
3950
|
+
const submitted = await ctx.client.callTool(
|
|
3951
|
+
"generate_motion_control_video",
|
|
3952
|
+
compact({
|
|
3953
|
+
model,
|
|
3954
|
+
prompt: promptWords.join(" ").trim(),
|
|
3955
|
+
image_url: imageUrl,
|
|
3956
|
+
motion_video_url: motionVideoUrl,
|
|
3957
|
+
quality: opts.quality,
|
|
3958
|
+
character_orientation: opts.orientation,
|
|
3959
|
+
keep_original_sound: opts.originalSound !== false,
|
|
3960
|
+
project_id: opts.project,
|
|
3961
|
+
session_id: opts.session
|
|
3962
|
+
})
|
|
3963
|
+
);
|
|
3964
|
+
await handleAsyncJob(ctx, submitted, {
|
|
3965
|
+
wait: opts.wait !== false,
|
|
3966
|
+
download: opts.download,
|
|
3967
|
+
label: `Generating motion control with ${model}`
|
|
3968
|
+
});
|
|
3969
|
+
});
|
|
3970
|
+
}
|
|
3971
|
+
|
|
3289
3972
|
// src/index.ts
|
|
3290
3973
|
async function configureProxy() {
|
|
3291
3974
|
if (!process.env.HTTPS_PROXY && !process.env.HTTP_PROXY && !process.env.https_proxy && !process.env.http_proxy) {
|
|
@@ -3301,7 +3984,19 @@ function buildProgram() {
|
|
|
3301
3984
|
const program = new Command();
|
|
3302
3985
|
program.name("videodraft").description(
|
|
3303
3986
|
"Create AI videos, images and audio from your terminal.\nAgent-friendly: every command supports --json; exit codes are stable\n(0 ok, 1 error, 2 usage, 3 auth, 4 insufficient credits)."
|
|
3304
|
-
).version(VERSION, "-v, --version", "print the CLI version").option("--json", "machine-readable JSON output").option("--no-color", "disable colored output (NO_COLOR is also respected)").option(
|
|
3987
|
+
).version(VERSION, "-v, --version", "print the CLI version").option("--json", "machine-readable JSON output").option("--no-color", "disable colored output (NO_COLOR is also respected)").option(
|
|
3988
|
+
"--base-url <url>",
|
|
3989
|
+
"VideoDraft server (default https://app.videodraft.ai; env VIDEODRAFT_BASE_URL)"
|
|
3990
|
+
).option(
|
|
3991
|
+
"--token <vd_mcp_token>",
|
|
3992
|
+
"bearer token for this invocation (env VIDEODRAFT_API_KEY)"
|
|
3993
|
+
).option("--profile <name>", "config profile (default: default)").option(
|
|
3994
|
+
"--wait-interval <duration>",
|
|
3995
|
+
"poll interval for --wait, e.g. 3s (default)"
|
|
3996
|
+
).option(
|
|
3997
|
+
"--wait-timeout <duration>",
|
|
3998
|
+
"max wait for --wait, e.g. 10m (default)"
|
|
3999
|
+
).showSuggestionAfterError(true).exitOverride();
|
|
3305
4000
|
registerAuthCommands(program);
|
|
3306
4001
|
registerAccountCommands(program);
|
|
3307
4002
|
registerProjectCommands(program);
|
|
@@ -3310,6 +4005,7 @@ function buildProgram() {
|
|
|
3310
4005
|
registerJobCommands(program);
|
|
3311
4006
|
registerMediaCommands(program);
|
|
3312
4007
|
registerAvatarCommands(program);
|
|
4008
|
+
registerEditCommands(program);
|
|
3313
4009
|
registerToolCommands(program);
|
|
3314
4010
|
registerSkillCommands(program);
|
|
3315
4011
|
registerMiscCommands(program);
|
|
@@ -3322,11 +4018,17 @@ async function main() {
|
|
|
3322
4018
|
const startedAt = Date.now();
|
|
3323
4019
|
let commandPath = "help";
|
|
3324
4020
|
program.hook("preAction", (_thisCommand, actionCommand) => {
|
|
3325
|
-
commandPath = buildCommandPath(
|
|
4021
|
+
commandPath = buildCommandPath(
|
|
4022
|
+
actionCommand
|
|
4023
|
+
);
|
|
3326
4024
|
});
|
|
3327
4025
|
try {
|
|
3328
4026
|
await program.parseAsync(process.argv);
|
|
3329
|
-
capture("cli_command", {
|
|
4027
|
+
capture("cli_command", {
|
|
4028
|
+
command: commandPath,
|
|
4029
|
+
ok: true,
|
|
4030
|
+
duration_ms: Date.now() - startedAt
|
|
4031
|
+
});
|
|
3330
4032
|
await maybeCheckForUpdate();
|
|
3331
4033
|
} catch (err) {
|
|
3332
4034
|
if (err instanceof CommanderError) {
|
|
@@ -3371,8 +4073,10 @@ async function main() {
|
|
|
3371
4073
|
const out = makeOutput({ json: process.argv.includes("--json") });
|
|
3372
4074
|
const message = err?.message ?? String(err);
|
|
3373
4075
|
if (out.json) {
|
|
3374
|
-
process.stdout.write(
|
|
3375
|
-
|
|
4076
|
+
process.stdout.write(
|
|
4077
|
+
`${JSON.stringify({ error: message, exit_code: EXIT.ERROR }, null, 2)}
|
|
4078
|
+
`
|
|
4079
|
+
);
|
|
3376
4080
|
} else {
|
|
3377
4081
|
process.stderr.write(`${fmt.red(out, "Error:")} ${message}
|
|
3378
4082
|
`);
|