videodraft 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -12
- package/dist/client.d.ts +7 -0
- package/dist/client.js +37 -0
- package/dist/index.js +246 -10
- package/package.json +1 -1
- package/skills/videodraft/SKILL.md +12 -5
package/README.md
CHANGED
|
@@ -30,18 +30,9 @@ 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
|
-
##
|
|
33
|
+
## Asset generation first
|
|
34
34
|
|
|
35
|
-
|
|
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}"
|
|
@@ -56,6 +47,32 @@ videodraft upscale image ./photo.png --scale 4x --download ./photo-4x.png
|
|
|
56
47
|
videodraft avatar create ./founder.jpg --script "$(videodraft avatar script 'our launch' --json | jq -r .script)"
|
|
57
48
|
```
|
|
58
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
|
+
|
|
59
76
|
## Commands
|
|
60
77
|
|
|
61
78
|
| Group | Commands |
|
|
@@ -67,7 +84,7 @@ videodraft avatar create ./founder.jpg --script "$(videodraft avatar script 'our
|
|
|
67
84
|
| Generate | `generate image/video/voiceover/music/sound-effect/dialogue/voice-changer/dub` `upscale image/video` `avatar script/create/render/get/list` |
|
|
68
85
|
| Jobs | `status <job>` `wait <job>` `generations` |
|
|
69
86
|
| Media | `upload <file>` `media list` `describe <url\|file>` `download <url>` |
|
|
70
|
-
| 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>'` |
|
|
71
88
|
| Agents | `skills install [--agent claude\|codex\|cursor]` `skills path` |
|
|
72
89
|
| Utility | `config get/set/path` `completion bash\|zsh` `docs` `--version` |
|
|
73
90
|
|
package/dist/client.d.ts
CHANGED
|
@@ -38,6 +38,13 @@ declare class VideoDraftClient {
|
|
|
38
38
|
get endpoint(): string;
|
|
39
39
|
rpc<T = any>(method: string, params?: unknown): Promise<T>;
|
|
40
40
|
private post;
|
|
41
|
+
/**
|
|
42
|
+
* Make an authenticated REST request to a non-MCP API route (e.g.
|
|
43
|
+
* /api/elevenlabs-key, which manages the user's BYOK ElevenLabs key and has no
|
|
44
|
+
* MCP tool by design). Uses the same bearer token + 401 refresh as rpc().
|
|
45
|
+
* Returns the parsed JSON body; throws on non-2xx with the server's error.
|
|
46
|
+
*/
|
|
47
|
+
restRequest<T = any>(method: string, path: string, body?: unknown): Promise<T>;
|
|
41
48
|
/**
|
|
42
49
|
* Batch several JSON-RPC requests into ONE HTTP round trip (the server
|
|
43
50
|
* implements JSON-RPC 2.0 batching). With N concurrent jobs this turns N
|
package/dist/client.js
CHANGED
|
@@ -264,6 +264,43 @@ var VideoDraftClient = class {
|
|
|
264
264
|
signal: AbortSignal.timeout(this.requestTimeoutMs)
|
|
265
265
|
});
|
|
266
266
|
}
|
|
267
|
+
/**
|
|
268
|
+
* Make an authenticated REST request to a non-MCP API route (e.g.
|
|
269
|
+
* /api/elevenlabs-key, which manages the user's BYOK ElevenLabs key and has no
|
|
270
|
+
* MCP tool by design). Uses the same bearer token + 401 refresh as rpc().
|
|
271
|
+
* Returns the parsed JSON body; throws on non-2xx with the server's error.
|
|
272
|
+
*/
|
|
273
|
+
async restRequest(method, path4, body) {
|
|
274
|
+
const doFetch = (token2) => this.fetchImpl(`${this.baseUrl}${path4}`, {
|
|
275
|
+
method,
|
|
276
|
+
headers: {
|
|
277
|
+
"content-type": "application/json",
|
|
278
|
+
authorization: `Bearer ${token2}`,
|
|
279
|
+
"user-agent": this.userAgent
|
|
280
|
+
},
|
|
281
|
+
...body === void 0 ? {} : { body: JSON.stringify(body) },
|
|
282
|
+
signal: AbortSignal.timeout(this.requestTimeoutMs)
|
|
283
|
+
});
|
|
284
|
+
const token = await this.tokenProvider.getAccessToken();
|
|
285
|
+
let response = await doFetch(token);
|
|
286
|
+
if (response.status === 401 && this.tokenProvider.onUnauthorized) {
|
|
287
|
+
const fresh = await this.tokenProvider.onUnauthorized();
|
|
288
|
+
if (fresh) response = await doFetch(fresh);
|
|
289
|
+
}
|
|
290
|
+
if (response.status === 401) {
|
|
291
|
+
throw new AuthError("Token is invalid, expired, or revoked.");
|
|
292
|
+
}
|
|
293
|
+
let parsed = null;
|
|
294
|
+
try {
|
|
295
|
+
parsed = await response.json();
|
|
296
|
+
} catch {
|
|
297
|
+
}
|
|
298
|
+
if (!response.ok) {
|
|
299
|
+
const detail = parsed?.error?.message ?? parsed?.error ?? `HTTP ${response.status}`;
|
|
300
|
+
throw new RpcError(response.status, detail);
|
|
301
|
+
}
|
|
302
|
+
return parsed;
|
|
303
|
+
}
|
|
267
304
|
/**
|
|
268
305
|
* Batch several JSON-RPC requests into ONE HTTP round trip (the server
|
|
269
306
|
* implements JSON-RPC 2.0 batching). With N concurrent jobs this turns N
|
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.3.0") {
|
|
28
|
+
return "0.3.0";
|
|
29
29
|
}
|
|
30
30
|
return readVersionFromDisk();
|
|
31
31
|
}
|
|
@@ -691,6 +691,43 @@ var VideoDraftClient = class {
|
|
|
691
691
|
signal: AbortSignal.timeout(this.requestTimeoutMs)
|
|
692
692
|
});
|
|
693
693
|
}
|
|
694
|
+
/**
|
|
695
|
+
* Make an authenticated REST request to a non-MCP API route (e.g.
|
|
696
|
+
* /api/elevenlabs-key, which manages the user's BYOK ElevenLabs key and has no
|
|
697
|
+
* MCP tool by design). Uses the same bearer token + 401 refresh as rpc().
|
|
698
|
+
* Returns the parsed JSON body; throws on non-2xx with the server's error.
|
|
699
|
+
*/
|
|
700
|
+
async restRequest(method, path5, body) {
|
|
701
|
+
const doFetch = (token2) => this.fetchImpl(`${this.baseUrl}${path5}`, {
|
|
702
|
+
method,
|
|
703
|
+
headers: {
|
|
704
|
+
"content-type": "application/json",
|
|
705
|
+
authorization: `Bearer ${token2}`,
|
|
706
|
+
"user-agent": this.userAgent
|
|
707
|
+
},
|
|
708
|
+
...body === void 0 ? {} : { body: JSON.stringify(body) },
|
|
709
|
+
signal: AbortSignal.timeout(this.requestTimeoutMs)
|
|
710
|
+
});
|
|
711
|
+
const token = await this.tokenProvider.getAccessToken();
|
|
712
|
+
let response = await doFetch(token);
|
|
713
|
+
if (response.status === 401 && this.tokenProvider.onUnauthorized) {
|
|
714
|
+
const fresh = await this.tokenProvider.onUnauthorized();
|
|
715
|
+
if (fresh) response = await doFetch(fresh);
|
|
716
|
+
}
|
|
717
|
+
if (response.status === 401) {
|
|
718
|
+
throw new AuthError("Token is invalid, expired, or revoked.");
|
|
719
|
+
}
|
|
720
|
+
let parsed = null;
|
|
721
|
+
try {
|
|
722
|
+
parsed = await response.json();
|
|
723
|
+
} catch {
|
|
724
|
+
}
|
|
725
|
+
if (!response.ok) {
|
|
726
|
+
const detail = parsed?.error?.message ?? parsed?.error ?? `HTTP ${response.status}`;
|
|
727
|
+
throw new RpcError(response.status, detail);
|
|
728
|
+
}
|
|
729
|
+
return parsed;
|
|
730
|
+
}
|
|
694
731
|
/**
|
|
695
732
|
* Batch several JSON-RPC requests into ONE HTTP round trip (the server
|
|
696
733
|
* implements JSON-RPC 2.0 batching). With N concurrent jobs this turns N
|
|
@@ -1057,6 +1094,127 @@ ${fmt.bold(ctxOut, "Open this URL to log in:")}
|
|
|
1057
1094
|
]);
|
|
1058
1095
|
});
|
|
1059
1096
|
});
|
|
1097
|
+
const el = program.command("elevenlabs").description("Manage your ElevenLabs API key (BYOK) \u2014 use your cloned voices");
|
|
1098
|
+
el.command("status").description("Show whether your ElevenLabs key is connected and active").action(async function() {
|
|
1099
|
+
const ctx = buildContext(this);
|
|
1100
|
+
const st = await ctx.client.restRequest("GET", "/api/elevenlabs-key");
|
|
1101
|
+
emit(ctx.out, st, (o) => {
|
|
1102
|
+
kv(o, [
|
|
1103
|
+
["Connected", st?.hasKey ? `yes (\u2022\u2022\u2022\u2022${st.hint ?? ""})` : "no"],
|
|
1104
|
+
["Active", st?.enabled ? "yes" : "no"],
|
|
1105
|
+
["Server", ctx.baseUrl]
|
|
1106
|
+
]);
|
|
1107
|
+
if (!st?.hasKey) {
|
|
1108
|
+
note(
|
|
1109
|
+
o,
|
|
1110
|
+
fmt.dim(o, "Connect one: videodraft elevenlabs set --key <xi-...>")
|
|
1111
|
+
);
|
|
1112
|
+
}
|
|
1113
|
+
});
|
|
1114
|
+
});
|
|
1115
|
+
el.command("set").description("Connect (or replace) your ElevenLabs API key and activate it").option("--key <key>", "your ElevenLabs API key (xi-...)").option("--no-enable", "store the key without turning it on").action(async function() {
|
|
1116
|
+
const opts = this.opts();
|
|
1117
|
+
const ctx = buildContext(this);
|
|
1118
|
+
let key = opts.key?.trim();
|
|
1119
|
+
if (!key && !process.stdin.isTTY) key = (await readStdin()).trim();
|
|
1120
|
+
if (!key) {
|
|
1121
|
+
throw new CliError(
|
|
1122
|
+
"Provide your key with --key <xi-...> (or pipe it via stdin).",
|
|
1123
|
+
EXIT.USAGE
|
|
1124
|
+
);
|
|
1125
|
+
}
|
|
1126
|
+
const spin = spinner(ctx.out, "Validating key with ElevenLabs\u2026");
|
|
1127
|
+
try {
|
|
1128
|
+
const res = await ctx.client.restRequest(
|
|
1129
|
+
"POST",
|
|
1130
|
+
"/api/elevenlabs-key",
|
|
1131
|
+
{ key, enabled: opts.enable !== false }
|
|
1132
|
+
);
|
|
1133
|
+
spin.stop();
|
|
1134
|
+
capture("cli_elevenlabs_set");
|
|
1135
|
+
emit(
|
|
1136
|
+
ctx.out,
|
|
1137
|
+
res,
|
|
1138
|
+
(o) => note(
|
|
1139
|
+
o,
|
|
1140
|
+
fmt.green(
|
|
1141
|
+
o,
|
|
1142
|
+
`ElevenLabs key saved (\u2022\u2022\u2022\u2022${res.hint ?? ""})${res.enabled ? " and active \u2014 your cloned voices are ready." : " \u2014 run `videodraft elevenlabs enable` to turn it on."}`
|
|
1143
|
+
)
|
|
1144
|
+
)
|
|
1145
|
+
);
|
|
1146
|
+
} catch (e) {
|
|
1147
|
+
spin.stop();
|
|
1148
|
+
throw e;
|
|
1149
|
+
}
|
|
1150
|
+
});
|
|
1151
|
+
el.command("enable").description("Turn your ElevenLabs key on").action(async function() {
|
|
1152
|
+
const ctx = buildContext(this);
|
|
1153
|
+
const res = await ctx.client.restRequest(
|
|
1154
|
+
"PATCH",
|
|
1155
|
+
"/api/elevenlabs-key",
|
|
1156
|
+
{ enabled: true }
|
|
1157
|
+
);
|
|
1158
|
+
emit(
|
|
1159
|
+
ctx.out,
|
|
1160
|
+
res,
|
|
1161
|
+
(o) => note(o, fmt.green(o, "ElevenLabs key is now active."))
|
|
1162
|
+
);
|
|
1163
|
+
});
|
|
1164
|
+
el.command("disable").description("Turn your ElevenLabs key off (use VideoDraft credits again)").action(async function() {
|
|
1165
|
+
const ctx = buildContext(this);
|
|
1166
|
+
const res = await ctx.client.restRequest(
|
|
1167
|
+
"PATCH",
|
|
1168
|
+
"/api/elevenlabs-key",
|
|
1169
|
+
{ enabled: false }
|
|
1170
|
+
);
|
|
1171
|
+
emit(
|
|
1172
|
+
ctx.out,
|
|
1173
|
+
res,
|
|
1174
|
+
(o) => note(
|
|
1175
|
+
o,
|
|
1176
|
+
"ElevenLabs key disabled. ElevenLabs generations use the VideoDraft path."
|
|
1177
|
+
)
|
|
1178
|
+
);
|
|
1179
|
+
});
|
|
1180
|
+
el.command("remove").description("Delete your stored ElevenLabs API key").action(async function() {
|
|
1181
|
+
const ctx = buildContext(this);
|
|
1182
|
+
const res = await ctx.client.restRequest(
|
|
1183
|
+
"DELETE",
|
|
1184
|
+
"/api/elevenlabs-key"
|
|
1185
|
+
);
|
|
1186
|
+
capture("cli_elevenlabs_remove");
|
|
1187
|
+
emit(
|
|
1188
|
+
ctx.out,
|
|
1189
|
+
res ?? { ok: true },
|
|
1190
|
+
(o) => note(o, "ElevenLabs key removed.")
|
|
1191
|
+
);
|
|
1192
|
+
});
|
|
1193
|
+
el.command("voices").description("List your ElevenLabs cloned/professional voices").action(async function() {
|
|
1194
|
+
const ctx = buildContext(this);
|
|
1195
|
+
const res = await ctx.client.callTool("list_cloned_voices");
|
|
1196
|
+
emit(ctx.out, res, (o) => {
|
|
1197
|
+
if (!res?.byok_active) {
|
|
1198
|
+
note(
|
|
1199
|
+
o,
|
|
1200
|
+
fmt.dim(
|
|
1201
|
+
o,
|
|
1202
|
+
res?.hint ?? "Connect your ElevenLabs key first: videodraft elevenlabs set --key <xi-...>"
|
|
1203
|
+
)
|
|
1204
|
+
);
|
|
1205
|
+
return;
|
|
1206
|
+
}
|
|
1207
|
+
const voices = res?.voices ?? [];
|
|
1208
|
+
if (!voices.length) {
|
|
1209
|
+
note(o, "No cloned voices in your ElevenLabs account yet.");
|
|
1210
|
+
return;
|
|
1211
|
+
}
|
|
1212
|
+
kv(
|
|
1213
|
+
o,
|
|
1214
|
+
voices.map((v) => [v.name, v.id])
|
|
1215
|
+
);
|
|
1216
|
+
});
|
|
1217
|
+
});
|
|
1060
1218
|
}
|
|
1061
1219
|
|
|
1062
1220
|
// src/commands/account.ts
|
|
@@ -2576,19 +2734,97 @@ function parseKeyValueArgs(pairs) {
|
|
|
2576
2734
|
}
|
|
2577
2735
|
return args;
|
|
2578
2736
|
}
|
|
2737
|
+
var CATEGORY_ORDER = [
|
|
2738
|
+
"asset_generation",
|
|
2739
|
+
"asset_io",
|
|
2740
|
+
"asset_library",
|
|
2741
|
+
"project_creation",
|
|
2742
|
+
"project_data",
|
|
2743
|
+
"production",
|
|
2744
|
+
"account_models_costs",
|
|
2745
|
+
"jobs",
|
|
2746
|
+
"danger_zone",
|
|
2747
|
+
"raw"
|
|
2748
|
+
];
|
|
2749
|
+
var LANE_ORDER = [
|
|
2750
|
+
"assets",
|
|
2751
|
+
"asset_io",
|
|
2752
|
+
"projects",
|
|
2753
|
+
"project_data",
|
|
2754
|
+
"production",
|
|
2755
|
+
"library",
|
|
2756
|
+
"account",
|
|
2757
|
+
"danger",
|
|
2758
|
+
"raw"
|
|
2759
|
+
];
|
|
2760
|
+
function firstSentence(description) {
|
|
2761
|
+
return description.split(/[.!]\s/)[0].slice(0, 90);
|
|
2762
|
+
}
|
|
2579
2763
|
function registerToolCommands(program) {
|
|
2580
2764
|
const tools = program.command("tools").description("Inspect the full MCP tool catalog");
|
|
2581
|
-
tools.command("list", { isDefault: true }).description("List
|
|
2765
|
+
tools.command("list", { isDefault: true }).description("List the grouped VideoDraft tool catalog").option(
|
|
2766
|
+
"--lane <lane>",
|
|
2767
|
+
"filter by lane: assets | asset_io | projects | project_data | production | library | account | danger | raw"
|
|
2768
|
+
).option(
|
|
2769
|
+
"--category <category>",
|
|
2770
|
+
"filter by category, e.g. asset_generation, asset_io, project_data, or production"
|
|
2771
|
+
).action(async function() {
|
|
2582
2772
|
const ctx = buildContext(this);
|
|
2583
|
-
const
|
|
2584
|
-
|
|
2585
|
-
|
|
2773
|
+
const opts = this.opts();
|
|
2774
|
+
if (opts.lane && !LANE_ORDER.includes(opts.lane)) {
|
|
2775
|
+
throw new CliError(
|
|
2776
|
+
`Unknown lane "${opts.lane}". Expected one of: ${LANE_ORDER.join(", ")}`,
|
|
2777
|
+
EXIT.USAGE
|
|
2778
|
+
);
|
|
2779
|
+
}
|
|
2780
|
+
if (opts.category && !CATEGORY_ORDER.includes(opts.category)) {
|
|
2781
|
+
throw new CliError(
|
|
2782
|
+
`Unknown category "${opts.category}". Expected one of: ${CATEGORY_ORDER.join(", ")}`,
|
|
2783
|
+
EXIT.USAGE
|
|
2784
|
+
);
|
|
2785
|
+
}
|
|
2786
|
+
let summary;
|
|
2787
|
+
const catalog = await ctx.client.callTool(
|
|
2788
|
+
"get_tool_catalog",
|
|
2789
|
+
compact({ lane: opts.lane, category: opts.category })
|
|
2790
|
+
);
|
|
2791
|
+
summary = catalog?.tools ?? [];
|
|
2792
|
+
emit(ctx.out, summary, (o) => {
|
|
2793
|
+
const categories = Array.from(
|
|
2794
|
+
new Set(summary.map((tool) => tool.category))
|
|
2795
|
+
).sort((a, b) => {
|
|
2796
|
+
const ai = CATEGORY_ORDER.indexOf(a);
|
|
2797
|
+
const bi = CATEGORY_ORDER.indexOf(b);
|
|
2798
|
+
return (ai === -1 ? 999 : ai) - (bi === -1 ? 999 : bi) || a.localeCompare(b);
|
|
2799
|
+
});
|
|
2800
|
+
if (categories.length === 0) {
|
|
2801
|
+
table(o, ["name", "type", "risk", "description"], []);
|
|
2802
|
+
}
|
|
2803
|
+
for (const category of categories) {
|
|
2804
|
+
const rows = summary.filter((tool) => tool.category === category);
|
|
2805
|
+
process.stdout.write(`
|
|
2806
|
+
${fmt.bold(o, category)}
|
|
2807
|
+
`);
|
|
2808
|
+
table(
|
|
2809
|
+
o,
|
|
2810
|
+
["name", "type", "risk", "description"],
|
|
2811
|
+
rows.map((tool) => [
|
|
2812
|
+
tool.name,
|
|
2813
|
+
tool.subcategory ?? "",
|
|
2814
|
+
tool.risks.join(","),
|
|
2815
|
+
firstSentence(tool.description)
|
|
2816
|
+
])
|
|
2817
|
+
);
|
|
2818
|
+
}
|
|
2819
|
+
const suffix = opts.lane ? ` in lane "${opts.lane}"` : opts.category ? ` in category "${opts.category}"` : "";
|
|
2820
|
+
note(
|
|
2586
2821
|
o,
|
|
2587
|
-
|
|
2588
|
-
|
|
2822
|
+
fmt.dim(
|
|
2823
|
+
o,
|
|
2824
|
+
`
|
|
2825
|
+
${summary.length} tools${suffix}. Inspect one: videodraft tools schema <name>`
|
|
2826
|
+
)
|
|
2589
2827
|
);
|
|
2590
|
-
note(o, fmt.dim(o, `
|
|
2591
|
-
${list.length} tools. Inspect one: videodraft tools schema <name>`));
|
|
2592
2828
|
});
|
|
2593
2829
|
});
|
|
2594
2830
|
tools.command("schema <name>").description("Show a tool's description and JSON input schema").action(async function(name) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "videodraft",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
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",
|
|
@@ -5,7 +5,11 @@ description: Create AI videos, images, voiceovers, music, sound effects, dialogu
|
|
|
5
5
|
|
|
6
6
|
# VideoDraft
|
|
7
7
|
|
|
8
|
-
VideoDraft is an AI video creation platform
|
|
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.
|
|
9
13
|
|
|
10
14
|
## How to connect
|
|
11
15
|
|
|
@@ -17,15 +21,18 @@ Two equivalent surfaces (same backend, same credits, same projects):
|
|
|
17
21
|
• HEADLESS / CI (no browser): set `VIDEODRAFT_API_KEY=vd_mcp_...` (a token the user mints at https://app.videodraft.ai/mcp-keys).
|
|
18
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.
|
|
19
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).
|
|
20
|
-
-
|
|
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>'`.
|
|
21
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.
|
|
22
28
|
|
|
23
|
-
## First decision: asset or
|
|
29
|
+
## First decision: asset or project?
|
|
24
30
|
|
|
25
|
-
- **One standalone asset** (
|
|
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.
|
|
26
32
|
- `videodraft generate image "a red fox in snow, cinematic" --ar 16:9 --download ./out/`
|
|
27
33
|
- `videodraft generate video "slow dolly over a misty lake" --model google-veo3.1 --duration 6 --download ./out/`
|
|
28
|
-
- **A
|
|
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.
|
|
29
36
|
- `videodraft create "30s launch video for our espresso machine" --ar 9:16`
|
|
30
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.
|
|
31
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.
|