videodraft 0.1.2 → 0.2.1

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # videodraft
2
2
 
3
- The official [VideoDraft](https://videodraft.ai) CLI — create AI videos, images, voiceovers and music from your terminal. Built for humans **and** coding agents: every command supports `--json`, exit codes are stable, async jobs are first-class.
3
+ The official [VideoDraft](https://videodraft.ai) CLI — create AI videos, images and audio from your terminal. Built for humans **and** coding agents: every command supports `--json`, exit codes are stable, async jobs are first-class.
4
4
 
5
5
  ```bash
6
6
  npx videodraft login
@@ -30,40 +30,61 @@ export VIDEODRAFT_API_KEY=vd_mcp_… # headless / CI — no login command neede
30
30
 
31
31
  Credentials are stored in `~/.config/videodraft/config.json` (0600). `videodraft logout` revokes and clears them.
32
32
 
33
- ## The pipeline
33
+ ## Asset generation first
34
34
 
35
- ```bash
36
- videodraft credits # know your budget
37
- videodraft create "<idea>" --ar 9:16 # idea → script → visual assets → storyboard
38
- videodraft shots <project> --grid --estimate # preview the cost…
39
- videodraft shots <project> --grid # …then batch-generate every shot image
40
- videodraft produce <project> # voiceovers + captions + production timeline
41
- videodraft export <project> --download final.mp4
42
- ```
43
-
44
- Single assets don't need a project:
35
+ Standalone images, clips and audio are complete deliverables. They do not need a VideoDraft project unless you want to attach them to an existing project or turn them into a multi-scene production.
45
36
 
46
37
  ```bash
47
38
  videodraft generate image "isometric workspace, warm light" --num 4 --download "./out/{job_id}_{index}.{ext}"
48
39
  videodraft generate video "slow dolly over a misty lake" --model google-veo3.1 --duration 6 --estimate
49
40
  videodraft generate voiceover "Welcome to VideoDraft" --download welcome.mp3
50
41
  videodraft generate music "minimal ambient, 60 BPM" --download bgm.mp3
42
+ videodraft generate sound-effect "cinematic whoosh, sub hit" --duration 3 --download sfx.mp3
43
+ videodraft generate dialogue --line "elevenlabs-kPzsL2i3teMYv0FxEYQ6:Ready?" --line "elevenlabs-s3TPKV1kjDlVtZbl4Ksh:Let's go." --download dialogue.mp3
44
+ videodraft generate voice-changer ./speech.wav --voice elevenlabs-kPzsL2i3teMYv0FxEYQ6 --duration 12 --download changed.mp3
45
+ videodraft generate dub ./clip.mp4 --to es --duration 30 --download dubbed.mp4
51
46
  videodraft upscale image ./photo.png --scale 4x --download ./photo-4x.png
52
47
  videodraft avatar create ./founder.jpg --script "$(videodraft avatar script 'our launch' --json | jq -r .script)"
53
48
  ```
54
49
 
50
+ Discover the full asset lane:
51
+
52
+ ```bash
53
+ videodraft tools list
54
+ videodraft tools list --lane assets
55
+ videodraft tools list --lane asset_io
56
+ videodraft models image
57
+ videodraft models video
58
+ videodraft models audio
59
+ ```
60
+
61
+ Asset I/O is part of the asset workflow: `videodraft upload`, `videodraft download`, generation `--download`, and local refs like `--ref ./image.png` make files usable by agents and visible in local workspaces.
62
+
63
+ ## The project pipeline
64
+
65
+ Use projects when the user asks for a story, storyboard, editable web project, timeline, production flow, or exported MP4.
66
+
67
+ ```bash
68
+ videodraft credits # know your budget
69
+ videodraft create "<idea>" --ar 9:16 # idea → script → visual assets → storyboard
70
+ videodraft shots <project> --grid --estimate # preview the cost…
71
+ videodraft shots <project> --grid # …then batch-generate every shot image
72
+ videodraft produce <project> # voiceovers + captions + production timeline
73
+ videodraft export <project> --download final.mp4
74
+ ```
75
+
55
76
  ## Commands
56
77
 
57
78
  | Group | Commands |
58
79
  |---|---|
59
80
  | Auth | `login` `logout` `whoami` |
60
- | Account | `credits` `costs [model]` `models [image\|video\|voices\|styles]` `workspaces` `sessions list/create` |
81
+ | Account | `credits` `costs [model]` `models [image\|video\|audio\|voices\|styles]` `workspaces` `sessions list/create` |
61
82
  | Projects | `projects list/get/delete/favorite/open` `checkpoint create/list/restore` |
62
83
  | Pipeline | `create` `shots` `produce` (`--mode full_video`) `attach` `finalize` `export` `export-status` `video-prompts` |
63
- | Generate | `generate image/video/voiceover/music` `upscale image/video` `avatar script/create/render/get/list` |
84
+ | Generate | `generate image/video/voiceover/music/sound-effect/dialogue/voice-changer/dub` `upscale image/video` `avatar script/create/render/get/list` |
64
85
  | Jobs | `status <job>` `wait <job>` `generations` |
65
86
  | Media | `upload <file>` `media list` `describe <url\|file>` `download <url>` |
66
- | Everything else | `tools list` `tools schema <name>` `call <tool> --args '<json>'` |
87
+ | Everything else | `tools list [--lane assets\|asset_io\|project_data\|production]` `tools schema <name>` `call <tool> --args '<json>'` |
67
88
  | Agents | `skills install [--agent claude\|codex\|cursor]` `skills path` |
68
89
  | Utility | `config get/set/path` `completion bash\|zsh` `docs` `--version` |
69
90
 
package/dist/index.js CHANGED
@@ -15,7 +15,7 @@ __export(version_exports, {
15
15
  VERSION: () => VERSION
16
16
  });
17
17
  import fs from "fs";
18
- function readVersion() {
18
+ function readVersionFromDisk() {
19
19
  try {
20
20
  const pkg = JSON.parse(fs.readFileSync(new URL("../package.json", import.meta.url), "utf8"));
21
21
  return typeof pkg.version === "string" ? pkg.version : "0.0.0";
@@ -23,11 +23,17 @@ function readVersion() {
23
23
  return "0.0.0";
24
24
  }
25
25
  }
26
+ function resolveVersion() {
27
+ if ("0.2.1") {
28
+ return "0.2.1";
29
+ }
30
+ return readVersionFromDisk();
31
+ }
26
32
  var VERSION;
27
33
  var init_version = __esm({
28
34
  "src/version.ts"() {
29
35
  "use strict";
30
- VERSION = readVersion();
36
+ VERSION = resolveVersion();
31
37
  }
32
38
  });
33
39
 
@@ -1068,7 +1074,12 @@ function registerAccountCommands(program) {
1068
1074
  ]);
1069
1075
  });
1070
1076
  });
1071
- program.command("costs [model]").description("Show credit costs \u2014 pass a model id plus video settings for an exact estimate").option("--type <type>", "image | video").option("--duration <seconds>", "video duration in seconds").option("--resolution <res>", 'e.g. "720p", "1080p", "1K", "2K"').option("--quality <tier>", 'e.g. "standard", "pro", "fast"').option("--rendering-speed <tier>", 'image speed/cost tier, e.g. Ideogram V4 "Turbo"/"Balanced"/"Quality"').option("--audio", "include native model audio in the estimate").option("--no-audio", "exclude native model audio").option("--num <n>", "image batch size").action(async function(model) {
1077
+ program.command("costs [model]").description(
1078
+ "Show credit costs \u2014 pass a model id plus settings for an exact estimate"
1079
+ ).option("--type <type>", "image | video | audio").option("--duration <seconds>", "video/audio duration in seconds").option("--length <seconds>", "ElevenLabs Music output length in seconds").option("--chars <n>", "ElevenLabs Dialogue character count").option("--resolution <res>", 'e.g. "720p", "1080p", "1K", "2K"').option("--quality <tier>", 'e.g. "standard", "pro", "fast"').option(
1080
+ "--rendering-speed <tier>",
1081
+ 'image speed/cost tier, e.g. Ideogram V4 "Turbo"/"Balanced"/"Quality"'
1082
+ ).option("--audio", "include native model audio in the estimate").option("--no-audio", "exclude native model audio").option("--num <n>", "image batch size").action(async function(model) {
1072
1083
  const ctx = buildContext(this);
1073
1084
  const opts = this.opts();
1074
1085
  const result = await ctx.client.callTool(
@@ -1077,6 +1088,8 @@ function registerAccountCommands(program) {
1077
1088
  model_id: model,
1078
1089
  type: opts.type,
1079
1090
  duration_seconds: opts.duration ? Number(opts.duration) : void 0,
1091
+ length_seconds: opts.length ? Number(opts.length) : void 0,
1092
+ characters: opts.chars ? Number(opts.chars) : void 0,
1080
1093
  resolution: opts.resolution,
1081
1094
  quality: opts.quality,
1082
1095
  rendering_speed: opts.renderingSpeed,
@@ -1086,7 +1099,9 @@ function registerAccountCommands(program) {
1086
1099
  );
1087
1100
  emit(ctx.out, result);
1088
1101
  });
1089
- program.command("models [kind]").description("List available models: image | video | voices | styles (default: image + video)").action(async function(kind) {
1102
+ program.command("models [kind]").description(
1103
+ "List available models: image | video | audio | voices | styles (default: image + video + audio)"
1104
+ ).action(async function(kind) {
1090
1105
  const ctx = buildContext(this);
1091
1106
  const wanted = kind ?? "all";
1092
1107
  const result = {};
@@ -1096,8 +1111,15 @@ function registerAccountCommands(program) {
1096
1111
  if (wanted === "video" || wanted === "all") {
1097
1112
  result.video = await ctx.client.callTool("list_available_video_models");
1098
1113
  }
1099
- if (wanted === "voices") result.voices = await ctx.client.callTool("list_available_voices");
1100
- if (wanted === "styles") result.styles = await ctx.client.callTool("list_available_styles");
1114
+ if (wanted === "audio" || wanted === "all") {
1115
+ result.audio = await ctx.client.callTool("list_available_audio_models");
1116
+ }
1117
+ if (wanted === "voices") {
1118
+ result.voices = await ctx.client.callTool("list_available_voices");
1119
+ }
1120
+ if (wanted === "styles") {
1121
+ result.styles = await ctx.client.callTool("list_available_styles");
1122
+ }
1101
1123
  emit(ctx.out, result, (o) => {
1102
1124
  for (const [section, payload] of Object.entries(result)) {
1103
1125
  const models = Array.isArray(payload) ? payload : payload?.models ?? payload?.voices ?? payload?.styles ?? [];
@@ -1577,6 +1599,14 @@ async function uploadFile(client, localPath, options = {}) {
1577
1599
 
1578
1600
  // src/commands/generate.ts
1579
1601
  var URI_SCHEME = /^[a-z][a-z0-9+.-]*:\/\//i;
1602
+ var VIDEO_SOURCE_RE = /\.(mp4|mov|webm|m4v|gif)(?:[?#].*)?$/i;
1603
+ function inferDubMediaType(source, explicit) {
1604
+ if (explicit) {
1605
+ if (explicit === "audio" || explicit === "video") return explicit;
1606
+ throw new Error('--type must be "audio" or "video"');
1607
+ }
1608
+ return VIDEO_SOURCE_RE.test(source) ? "video" : "audio";
1609
+ }
1580
1610
  async function resolveRefs(ctx, refs) {
1581
1611
  const resolved = [];
1582
1612
  for (const ref of refs) {
@@ -1669,7 +1699,7 @@ async function handleAsyncJob(ctx, submitted, options) {
1669
1699
  }
1670
1700
  }
1671
1701
  function registerGenerateCommands(program) {
1672
- const generate = program.command("generate").description("Generate images, video, voiceovers and music");
1702
+ const generate = program.command("generate").description("Generate images, video and audio");
1673
1703
  generate.command("image <prompt...>").description("Generate an image (async; waits by default)").option("--model <id>", "image model id (default nano-banana-2)").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("--rendering-speed <tier>", 'Ideogram speed/cost tier, e.g. V4 "Turbo"/"Balanced"/"Quality"').option("--num <n>", "variations of this prompt in one call (1-4)").option("--seed <n>", "seed (supported models only, e.g. Flux, Ideogram V4)").option("--ref <url|file>", "reference image (repeatable; local files are uploaded)", collect, []).option("--video-ref <url|file>", "video reference \u2014 nano-banana-2 only (http(s)/gs:///YouTube, or local file)").option("--style <id>", "style preset id").option("--project <id>", "attach to a project").option("--session <id>", "AI Studio session id").option("--scene <n>", "0-based scene index (with --project: writes onto that shot)").option("--shot <n>", "0-based shot index").option("--download <path>", "download outputs (template: {job_id} {index} {ext})").option("--no-wait", "submit and return the job id immediately").option("--estimate", "print the cost estimate and exit (spends nothing)").action(async function(promptWords) {
1674
1704
  const ctx = buildContext(this);
1675
1705
  const opts = this.opts();
@@ -1716,7 +1746,10 @@ function registerGenerateCommands(program) {
1716
1746
  label: "Generating image"
1717
1747
  });
1718
1748
  });
1719
- generate.command("video [prompt...]").description("Generate a video clip (async; per-second pricing \u2014 see --estimate)").option("--model <id>", "video model id (default google-veo3.1 fast)").option("--ar <ratio>", 'aspect ratio, e.g. "16:9", "9:16"').option("--duration <seconds>", "clip duration in seconds").option("--resolution <res>", 'e.g. "720p", "1080p"').option("--quality <tier>", 'e.g. "fast", "quality", "standard", "pro"').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("--ref-video <url|file>", "reference video (repeatable; Seedance 2, Wan 2.7; local files uploaded)", collect, []).option("--ref-audio <url|file>", "reference audio (repeatable; Seedance 2; local files uploaded)", collect, []).option("--segment <prompt:seconds>", "multi-prompt segment (repeatable; Kling 3.0 / 3.0 Turbo / O3)", collect, []).option("--negative <text>", "negative prompt (Kling/Wan/Luma)").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("--download <path>", "download outputs (template: {job_id} {index} {ext})").option("--no-wait", "submit and return the job id immediately").option("--estimate", "print the cost estimate and exit (spends nothing)").action(async function(promptWords = []) {
1749
+ generate.command("video [prompt...]").description("Generate a video clip (async; per-second pricing \u2014 see --estimate)").option("--model <id>", "video model id (default google-veo3.1 fast)").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(
1750
+ "--quality <tier>",
1751
+ 'e.g. "mini", "fast", "standard", "quality", "pro"'
1752
+ ).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("--ref-video <url|file>", "reference video (repeatable; Seedance 2, Wan 2.7; local files uploaded)", collect, []).option("--ref-audio <url|file>", "reference audio (repeatable; Seedance 2; local files uploaded)", collect, []).option("--segment <prompt:seconds>", "multi-prompt segment (repeatable; Kling 3.0 / 3.0 Turbo / O3)", collect, []).option("--negative <text>", "negative prompt (Kling/Wan/Luma)").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("--download <path>", "download outputs (template: {job_id} {index} {ext})").option("--no-wait", "submit and return the job id immediately").option("--estimate", "print the cost estimate and exit (spends nothing)").action(async function(promptWords = []) {
1720
1753
  const ctx = buildContext(this);
1721
1754
  const opts = this.opts();
1722
1755
  const prompt = promptWords.join(" ").trim();
@@ -1804,16 +1837,19 @@ function registerGenerateCommands(program) {
1804
1837
  for (const f of downloaded ?? []) note(o, fmt.dim(o, `saved ${f.path}`));
1805
1838
  });
1806
1839
  });
1807
- generate.command("music <prompt...>").description("Generate background music (Lyria 3)").option("--model <id>", "lyria-3-clip-preview (30s, default) | lyria-3-pro-preview (180s)").option("--ref <url|file>", "reference image to inspire the music (repeatable)", collect, []).option("--project <id>", "link the generation to a project's AI Studio session").option("--attach <project_id>", "also set the track as that project's background music").option("--volume <n>", "0-100 BGM volume when attaching (default 30)").option("--bgm-disabled", "when attaching, store the BGM as disabled (enabled:false)").option("--session <id>", "AI Studio session id").option("--download <path>", "download the audio file").action(async function(promptWords) {
1840
+ generate.command("music <prompt...>").description("Generate background music").option("--model <id>", "lyria-3-clip-preview (default) | lyria-3-pro-preview | elevenlabs-music").option("--length <seconds>", "for --model elevenlabs-music: length 10\u2013120s (default 30)").option("--instrumental", "for --model elevenlabs-music: force instrumental (no vocals)").option("--ref <url|file>", "reference image to inspire the music (Lyria only, repeatable)", collect, []).option("--project <id>", "link the generation to a project's AI Studio session").option("--attach <project_id>", "also set the track as that project's background music").option("--volume <n>", "0-100 BGM volume when attaching (default 30)").option("--bgm-disabled", "when attaching, store the BGM as disabled (enabled:false)").option("--session <id>", "AI Studio session id").option("--download <path>", "download the audio file").action(async function(promptWords) {
1808
1841
  const ctx = buildContext(this);
1809
1842
  const opts = this.opts();
1810
- const refs = await resolveRefs(ctx, opts.ref ?? []);
1811
- capture("cli_generate", { kind: "music", model: opts.model ?? "default" });
1843
+ const musicModel = opts.model ?? "lyria-3-clip-preview";
1844
+ const refs = musicModel === "elevenlabs-music" ? [] : await resolveRefs(ctx, opts.ref ?? []);
1845
+ capture("cli_generate", { kind: "music", model: musicModel });
1812
1846
  const result = await ctx.client.callTool(
1813
1847
  "generate_music",
1814
1848
  compact({
1815
1849
  prompt: promptWords.join(" "),
1816
- model: opts.model,
1850
+ model: musicModel,
1851
+ length_seconds: opts.length ? Number(opts.length) : void 0,
1852
+ force_instrumental: opts.instrumental ? true : void 0,
1817
1853
  image_urls: refs.length > 0 ? refs : void 0,
1818
1854
  project_id: opts.project,
1819
1855
  attach_to_project_id: opts.attach,
@@ -1834,6 +1870,173 @@ function registerGenerateCommands(program) {
1834
1870
  for (const f of downloaded ?? []) note(o, fmt.dim(o, `saved ${f.path}`));
1835
1871
  });
1836
1872
  });
1873
+ 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) {
1874
+ const ctx = buildContext(this);
1875
+ const opts = this.opts();
1876
+ capture("cli_generate", { kind: "sound_effect" });
1877
+ const result = await ctx.client.callTool(
1878
+ "generate_sound_effect",
1879
+ compact({
1880
+ prompt: promptWords.join(" "),
1881
+ duration_seconds: opts.duration ? Number(opts.duration) : void 0,
1882
+ prompt_influence: opts.influence ? Number(opts.influence) : void 0,
1883
+ project_id: opts.project,
1884
+ session_id: opts.session
1885
+ })
1886
+ );
1887
+ const urls = extractOutputUrls(result);
1888
+ let downloaded;
1889
+ if (opts.download && urls.length > 0) {
1890
+ downloaded = await downloadOutputs(urls, opts.download, {
1891
+ name: "sound-effect"
1892
+ });
1893
+ }
1894
+ const media = buildMediaDescriptors(urls, "audio");
1895
+ emit(
1896
+ ctx.out,
1897
+ { ...result, downloaded_files: downloaded, output_media: media },
1898
+ (o) => {
1899
+ for (const url of urls) process.stdout.write(`${url}
1900
+ `);
1901
+ for (const f of downloaded ?? []) note(o, fmt.dim(o, `saved ${f.path}`));
1902
+ }
1903
+ );
1904
+ });
1905
+ generate.command("dialogue").description(
1906
+ "Generate multi-speaker dialogue (ElevenLabs Text-to-Dialogue). Repeat --line."
1907
+ ).option(
1908
+ "--line <voiceId:text>",
1909
+ 'a dialogue line as "voiceId:text" (repeatable)',
1910
+ collect,
1911
+ []
1912
+ ).option("--stability <0|0.5|1>", "voice stability").option("--language <iso>", "ISO 639-1 language code").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() {
1913
+ const ctx = buildContext(this);
1914
+ const opts = this.opts();
1915
+ const lines = opts.line.map((raw) => {
1916
+ const i = raw.indexOf(":");
1917
+ if (i < 0) {
1918
+ throw new Error(`--line must be "voiceId:text" (got "${raw}")`);
1919
+ }
1920
+ return { voice_id: raw.slice(0, i).trim(), text: raw.slice(i + 1).trim() };
1921
+ });
1922
+ if (lines.length === 0) throw new Error("at least one --line is required");
1923
+ capture("cli_generate", { kind: "dialogue" });
1924
+ const result = await ctx.client.callTool(
1925
+ "generate_dialogue",
1926
+ compact({
1927
+ lines,
1928
+ stability: opts.stability !== void 0 ? Number(opts.stability) : void 0,
1929
+ language_code: opts.language,
1930
+ project_id: opts.project,
1931
+ session_id: opts.session
1932
+ })
1933
+ );
1934
+ const urls = extractOutputUrls(result);
1935
+ let downloaded;
1936
+ if (opts.download && urls.length > 0) {
1937
+ downloaded = await downloadOutputs(urls, opts.download, {
1938
+ name: "dialogue"
1939
+ });
1940
+ }
1941
+ const media = buildMediaDescriptors(urls, "audio");
1942
+ emit(
1943
+ ctx.out,
1944
+ { ...result, downloaded_files: downloaded, output_media: media },
1945
+ (o) => {
1946
+ for (const url of urls) process.stdout.write(`${url}
1947
+ `);
1948
+ for (const f of downloaded ?? []) note(o, fmt.dim(o, `saved ${f.path}`));
1949
+ }
1950
+ );
1951
+ });
1952
+ generate.command("voice-changer <audio>").description("Restyle speech into another ElevenLabs voice (Voice Changer)").option("--voice <id>", "target ElevenLabs voice id (default Brittney)").option(
1953
+ "--duration <seconds>",
1954
+ "length of the source audio in seconds (required, max 300)"
1955
+ ).option("--remove-noise", "remove background noise from the input").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(source) {
1956
+ const ctx = buildContext(this);
1957
+ const opts = this.opts();
1958
+ if (!opts.duration) {
1959
+ throw new Error(
1960
+ "--duration <seconds> is required (length of the source audio)"
1961
+ );
1962
+ }
1963
+ const [audioUrl] = await resolveRefs(ctx, [source]);
1964
+ capture("cli_generate", { kind: "voice_changer" });
1965
+ const result = await ctx.client.callTool(
1966
+ "change_voice",
1967
+ compact({
1968
+ audio_url: audioUrl,
1969
+ voice_id: opts.voice,
1970
+ duration_seconds: Number(opts.duration),
1971
+ remove_background_noise: opts.removeNoise ? true : void 0,
1972
+ project_id: opts.project,
1973
+ session_id: opts.session
1974
+ })
1975
+ );
1976
+ const urls = extractOutputUrls(result);
1977
+ let downloaded;
1978
+ if (opts.download && urls.length > 0) {
1979
+ downloaded = await downloadOutputs(urls, opts.download, {
1980
+ name: "voice-changed"
1981
+ });
1982
+ }
1983
+ const media = buildMediaDescriptors(urls, "audio");
1984
+ emit(
1985
+ ctx.out,
1986
+ { ...result, downloaded_files: downloaded, output_media: media },
1987
+ (o) => {
1988
+ for (const url of urls) process.stdout.write(`${url}
1989
+ `);
1990
+ for (const f of downloaded ?? []) note(o, fmt.dim(o, `saved ${f.path}`));
1991
+ }
1992
+ );
1993
+ });
1994
+ generate.command("dub <media>").description("Dub a video/audio file into another language (ElevenLabs Dubbing)").option("--to <iso>", "target language ISO 639-1 code, e.g. es or te (required)").option("--from <iso>", "source language ISO 639-1 code (auto-detected if omitted)").option("--type <audio|video>", "source media type override").option(
1995
+ "--duration <seconds>",
1996
+ "length of the source media in seconds (required, max 300)"
1997
+ ).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) {
1998
+ const ctx = buildContext(this);
1999
+ const opts = this.opts();
2000
+ if (!opts.to) throw new Error("--to <iso> (target language) is required");
2001
+ if (!opts.duration) {
2002
+ throw new Error(
2003
+ "--duration <seconds> is required (length of the source media)"
2004
+ );
2005
+ }
2006
+ const mediaType = inferDubMediaType(source, opts.type);
2007
+ const [mediaUrl] = await resolveRefs(ctx, [source]);
2008
+ capture("cli_generate", { kind: "dub" });
2009
+ const result = await ctx.client.callTool(
2010
+ "dub_media",
2011
+ compact({
2012
+ video_url: mediaType === "video" ? mediaUrl : void 0,
2013
+ audio_url: mediaType === "audio" ? mediaUrl : void 0,
2014
+ target_lang: opts.to,
2015
+ source_lang: opts.from,
2016
+ num_speakers: opts.speakers ? Number(opts.speakers) : void 0,
2017
+ duration_seconds: Number(opts.duration),
2018
+ project_id: opts.project,
2019
+ session_id: opts.session
2020
+ })
2021
+ );
2022
+ const urls = extractOutputUrls(result);
2023
+ let downloaded;
2024
+ if (opts.download && urls.length > 0) {
2025
+ downloaded = await downloadOutputs(urls, opts.download, {
2026
+ name: "dubbed"
2027
+ });
2028
+ }
2029
+ const media = buildMediaDescriptors(urls, mediaType);
2030
+ emit(
2031
+ ctx.out,
2032
+ { ...result, downloaded_files: downloaded, output_media: media },
2033
+ (o) => {
2034
+ for (const url of urls) process.stdout.write(`${url}
2035
+ `);
2036
+ for (const f of downloaded ?? []) note(o, fmt.dim(o, `saved ${f.path}`));
2037
+ }
2038
+ );
2039
+ });
1837
2040
  const upscale = program.command("upscale").description("Upscale images and videos (Topaz)");
1838
2041
  upscale.command("image <url|file>").description("Upscale an image (synchronous)").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) {
1839
2042
  const ctx = buildContext(this);
@@ -2257,7 +2460,7 @@ function registerJobCommands(program) {
2257
2460
  throw err;
2258
2461
  }
2259
2462
  });
2260
- program.command("generations").description("List recent + in-flight generations").option("--type <type>", "image | video").option("--status <status>", "pending | processing | completed | failed").option("--limit <n>", "max rows (default 30)").action(async function() {
2463
+ program.command("generations").description("List recent + in-flight generations").option("--type <type>", "image | video | sounds").option("--status <status>", "pending | processing | completed | failed").option("--limit <n>", "max rows (default 30)").action(async function() {
2261
2464
  const ctx = buildContext(this);
2262
2465
  const opts = this.opts();
2263
2466
  const result = await ctx.client.callTool(
@@ -2373,19 +2576,97 @@ function parseKeyValueArgs(pairs) {
2373
2576
  }
2374
2577
  return args;
2375
2578
  }
2579
+ var CATEGORY_ORDER = [
2580
+ "asset_generation",
2581
+ "asset_io",
2582
+ "asset_library",
2583
+ "project_creation",
2584
+ "project_data",
2585
+ "production",
2586
+ "account_models_costs",
2587
+ "jobs",
2588
+ "danger_zone",
2589
+ "raw"
2590
+ ];
2591
+ var LANE_ORDER = [
2592
+ "assets",
2593
+ "asset_io",
2594
+ "projects",
2595
+ "project_data",
2596
+ "production",
2597
+ "library",
2598
+ "account",
2599
+ "danger",
2600
+ "raw"
2601
+ ];
2602
+ function firstSentence(description) {
2603
+ return description.split(/[.!]\s/)[0].slice(0, 90);
2604
+ }
2376
2605
  function registerToolCommands(program) {
2377
2606
  const tools = program.command("tools").description("Inspect the full MCP tool catalog");
2378
- tools.command("list", { isDefault: true }).description("List every available tool").action(async function() {
2607
+ tools.command("list", { isDefault: true }).description("List the grouped VideoDraft tool catalog").option(
2608
+ "--lane <lane>",
2609
+ "filter by lane: assets | asset_io | projects | project_data | production | library | account | danger | raw"
2610
+ ).option(
2611
+ "--category <category>",
2612
+ "filter by category, e.g. asset_generation, asset_io, project_data, or production"
2613
+ ).action(async function() {
2379
2614
  const ctx = buildContext(this);
2380
- const list = await ctx.client.listTools();
2381
- emit(ctx.out, list.map((t) => ({ name: t.name, description: t.description })), (o) => {
2382
- table(
2615
+ const opts = this.opts();
2616
+ if (opts.lane && !LANE_ORDER.includes(opts.lane)) {
2617
+ throw new CliError(
2618
+ `Unknown lane "${opts.lane}". Expected one of: ${LANE_ORDER.join(", ")}`,
2619
+ EXIT.USAGE
2620
+ );
2621
+ }
2622
+ if (opts.category && !CATEGORY_ORDER.includes(opts.category)) {
2623
+ throw new CliError(
2624
+ `Unknown category "${opts.category}". Expected one of: ${CATEGORY_ORDER.join(", ")}`,
2625
+ EXIT.USAGE
2626
+ );
2627
+ }
2628
+ let summary;
2629
+ const catalog = await ctx.client.callTool(
2630
+ "get_tool_catalog",
2631
+ compact({ lane: opts.lane, category: opts.category })
2632
+ );
2633
+ summary = catalog?.tools ?? [];
2634
+ emit(ctx.out, summary, (o) => {
2635
+ const categories = Array.from(
2636
+ new Set(summary.map((tool) => tool.category))
2637
+ ).sort((a, b) => {
2638
+ const ai = CATEGORY_ORDER.indexOf(a);
2639
+ const bi = CATEGORY_ORDER.indexOf(b);
2640
+ return (ai === -1 ? 999 : ai) - (bi === -1 ? 999 : bi) || a.localeCompare(b);
2641
+ });
2642
+ if (categories.length === 0) {
2643
+ table(o, ["name", "type", "risk", "description"], []);
2644
+ }
2645
+ for (const category of categories) {
2646
+ const rows = summary.filter((tool) => tool.category === category);
2647
+ process.stdout.write(`
2648
+ ${fmt.bold(o, category)}
2649
+ `);
2650
+ table(
2651
+ o,
2652
+ ["name", "type", "risk", "description"],
2653
+ rows.map((tool) => [
2654
+ tool.name,
2655
+ tool.subcategory ?? "",
2656
+ tool.risks.join(","),
2657
+ firstSentence(tool.description)
2658
+ ])
2659
+ );
2660
+ }
2661
+ const suffix = opts.lane ? ` in lane "${opts.lane}"` : opts.category ? ` in category "${opts.category}"` : "";
2662
+ note(
2383
2663
  o,
2384
- ["name", "description"],
2385
- list.map((t) => [t.name, t.description.split(/[.!]\s/)[0].slice(0, 90)])
2664
+ fmt.dim(
2665
+ o,
2666
+ `
2667
+ ${summary.length} tools${suffix}. Inspect one: videodraft tools schema <name>`
2668
+ )
2386
2669
  );
2387
- note(o, fmt.dim(o, `
2388
- ${list.length} tools. Inspect one: videodraft tools schema <name>`));
2389
2670
  });
2390
2671
  });
2391
2672
  tools.command("schema <name>").description("Show a tool's description and JSON input schema").action(async function(name) {
@@ -2787,7 +3068,7 @@ async function configureProxy() {
2787
3068
  function buildProgram() {
2788
3069
  const program = new Command();
2789
3070
  program.name("videodraft").description(
2790
- "Create AI videos, images, voiceovers and music 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)."
3071
+ "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)."
2791
3072
  ).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("--base-url <url>", "VideoDraft server (default https://app.videodraft.ai; env VIDEODRAFT_BASE_URL)").option("--token <vd_mcp_token>", "bearer token for this invocation (env VIDEODRAFT_API_KEY)").option("--profile <name>", "config profile (default: default)").option("--wait-interval <duration>", "poll interval for --wait, e.g. 3s (default)").option("--wait-timeout <duration>", "max wait for --wait, e.g. 10m (default)").showSuggestionAfterError(true).exitOverride();
2792
3073
  registerAuthCommands(program);
2793
3074
  registerAccountCommands(program);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "videodraft",
3
- "version": "0.1.2",
4
- "description": "Official VideoDraft CLI — create AI videos, images, voiceovers and music from your terminal. Agent-friendly: --json everywhere, stable exit codes, async job polling.",
3
+ "version": "0.2.1",
4
+ "description": "Official VideoDraft CLI — create AI videos, images and audio from your terminal. Agent-friendly: --json everywhere, stable exit codes, async job polling.",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "homepage": "https://videodraft.ai/cli",
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  name: videodraft
3
- description: Create AI videos, images, voiceovers, music, 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, or background music — including batch/programmatic video generation in scripts or CI. Works via the `videodraft` CLI (preferred in terminals) or the VideoDraft MCP connector.
4
- license: MIT
5
- metadata:
6
- author: VideoDraft (videodraft.ai)
7
- homepage: https://videodraft.ai/cli
3
+ description: 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 — including batch/programmatic video generation in scripts or CI. Works via the `videodraft` CLI (preferred in terminals) or the VideoDraft MCP connector.
8
4
  ---
9
5
 
10
6
  # VideoDraft
11
7
 
12
- VideoDraft is an AI video creation platform: idea script storyboard (scenes + shot images) → production (voiceover, captions, motion clips, music) → exported MP4. You can drive all of it from this environment.
8
+ VideoDraft is an AI video creation platform where asset generation is the priority lane:
9
+
10
+ - **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.
11
+ - **Asset I/O**: upload local files, download outputs, auto-upload local references, and save generated media where the user can see it.
12
+ - **Project production**: idea → script → storyboard (scenes + shot images) → project data → production timeline → exported MP4. Use this only when the user asks for a story, storyboard, editable project, timeline, or final video.
13
13
 
14
14
  ## How to connect
15
15
 
@@ -21,15 +21,18 @@ Two equivalent surfaces (same backend, same credits, same projects):
21
21
  • HEADLESS / CI (no browser): set `VIDEODRAFT_API_KEY=vd_mcp_...` (a token the user mints at https://app.videodraft.ai/mcp-keys).
22
22
  • SECURITY: never ask the user to paste a `vd_mcp_...` token into the chat — use browser `login` or the env var so the token never lands in the transcript.
23
23
  - 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 (→ tell the user, don't retry).
24
- - Full API access: `videodraft tools list`, `videodraft tools schema <name>`, `videodraft call <tool> --args '<json>'`.
24
+ - 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`.
25
+ - Asset lane: `videodraft generate image|video|voiceover|music|sound-effect|dialogue|voice-changer|dub`, `videodraft upload`, and `videodraft download`.
26
+ - Full API access: `videodraft tools schema <name>`, `videodraft call <tool> --args '<json>'`.
25
27
  2. **MCP connector**: if VideoDraft MCP tools (e.g. `generate_storyboard_from_idea`) are available, call them directly — the CLI's curated commands map 1:1 onto these tools.
26
28
 
27
- ## First decision: asset or video?
29
+ ## First decision: asset or project?
28
30
 
29
- - **One standalone asset** (a single image, clip, voiceover, or music track, no story): generate it directly. Do NOT create a project.
31
+ - **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.
30
32
  - `videodraft generate image "a red fox in snow, cinematic" --ar 16:9 --download ./out/`
31
33
  - `videodraft generate video "slow dolly over a misty lake" --model google-veo3.1 --duration 6 --download ./out/`
32
- - **A video / ad / explainer / anything multi-scene**: create a project so the work stays organized, editable in the web app, and exportable.
34
+ - **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.
35
+ - **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.
33
36
  - `videodraft create "30s launch video for our espresso machine" --ar 9:16`
34
37
  - **Just a script** (no video asked for): `videodraft create "..." --script-only`. Stop at the script — do not build a storyboard the user didn't ask for.
35
38
  - **Iterating on existing work**: find it first (`videodraft projects list`) and reuse that project. Never create a new project to change an existing one.
@@ -39,10 +42,10 @@ Two equivalent surfaces (same backend, same credits, same projects):
39
42
  Generation costs credits (video is per-second; shot-image batches are the largest single spend). Before anything expensive:
40
43
 
41
44
  1. `videodraft credits` — check the balance.
42
- 2. `videodraft generate video "..." --estimate` or `videodraft costs <model> --duration 8 --resolution 1080p` — get the quote.
45
+ 2. `videodraft generate video "..." --estimate` or `videodraft costs <model> --duration 8 --resolution 1080p` — get the quote. For ElevenLabs audio, use `--type audio` plus `--duration`, `--length`, or `--chars`.
43
46
  3. 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.
44
47
 
45
- `videodraft models image|video` lists every model with its supported inputs (aspect ratios, resolutions, reference limits) — consult it instead of guessing capabilities.
48
+ `videodraft models image|video|audio` lists every model with its supported inputs (aspect ratios, resolutions, reference limits, audio billing inputs) — consult it instead of guessing capabilities.
46
49
 
47
50
  ## Async jobs
48
51
 
@@ -80,7 +83,7 @@ videodraft produce <project_id> # voiceovers + captions + produc
80
83
  videodraft export <project_id> --download final.mp4
81
84
  ```
82
85
 
83
- Optional 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>`), and music (`videodraft generate music "..." --attach <project_id>`). Details, per-step tools and editing rules: [references/pipeline.md](references/pipeline.md).
86
+ Optional 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).
84
87
 
85
88
  Avatar/talking-head videos are their own short flow: `videodraft avatar script` → `avatar create` → `avatar render` (paid step).
86
89
 
@@ -5,6 +5,7 @@ Always consult the live catalog instead of memorizing this page — models chang
5
5
  ```bash
6
6
  videodraft models image --json # every image model + inputs (aspect ratios, resolutions, max refs)
7
7
  videodraft models video --json # every video model + inputs + per-second pricing metadata
8
+ videodraft models audio --json # standalone audio/media models + pricing inputs
8
9
  videodraft models voices --json # TTS voices
9
10
  videodraft models styles --json # visual style presets
10
11
  ```
@@ -14,7 +15,8 @@ videodraft models styles --json # visual style presets
14
15
  - **Image**: `nano-banana-2` (the platform default, 1K). Use `--num 1..4` for variations of one prompt in a single call — never loop for variations.
15
16
  - **Video**: `google-veo3.1` at fast quality (6s / 720p) — the platform default.
16
17
  - **Voiceover**: ElevenLabs Brittney (default voice).
17
- - **Music**: `lyria-3-clip-preview` (30s, cheap); `lyria-3-pro-preview` for 180s/quality.
18
+ - **Music**: `lyria-3-clip-preview` (30s, cheap); `lyria-3-pro-preview` for 180s/quality; `elevenlabs-music` for music that can include vocals/lyrics.
19
+ - **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.
18
20
 
19
21
  ## Capability gotchas
20
22
 
@@ -32,12 +34,15 @@ videodraft models styles --json # visual style presets
32
34
  - Video: usually credits/second × duration; rate depends on model + resolution + quality + native audio on/off.
33
35
  - Shot-image batches: one image per shot (+1 grid image per scene in `--grid` mode) — the largest single spend in the pipeline.
34
36
  - Avatar renders: ~10 credits/sec at 480p, ~20/sec at 720p.
37
+ - 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.
35
38
  - Upscales: priced by scale and source size.
36
39
 
37
40
  Quote before spending:
38
41
 
39
42
  ```bash
40
43
  videodraft costs google-veo3.1 --type video --duration 8 --resolution 1080p --audio
44
+ videodraft costs elevenlabs-dubbing --type audio --duration 60
45
+ videodraft costs elevenlabs-dialogue --type audio --chars 350
41
46
  videodraft generate video "..." --estimate # same quote, inline
42
47
  videodraft credits # current balance
43
48
  ```
@@ -16,6 +16,10 @@ Everything here works through the CLI (`videodraft <command>` / `videodraft call
16
16
  | Motion clip for a shot | `videodraft generate video --project <id>` | `generate_video` |
17
17
  | Attach a finished clip to the timeline | `videodraft attach <project> --scene N --shot M --media <url> --type video` | `attach_media_to_shot` |
18
18
  | Background music | `videodraft generate music --attach <project>` | `generate_music` / `set_background_music` |
19
+ | Sound effect | `videodraft generate sound-effect "..."` | `generate_sound_effect` |
20
+ | Dialogue audio | `videodraft generate dialogue --line "voice:text"` | `generate_dialogue` |
21
+ | Voice changer | `videodraft generate voice-changer <audio>` | `change_voice` |
22
+ | Dubbing | `videodraft generate dub <audio_or_video>` | `dub_media` |
19
23
  | Scene voiceover | `videodraft generate voiceover --project <id> --scene N` | `generate_voiceover` |
20
24
  | Final MP4 | `videodraft export <project>` | `export_video` + `check_export_status` |
21
25