blitzreels 0.1.3 → 0.1.4
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 +61 -0
- package/dist/cli-runtime.d.ts +25 -0
- package/dist/cli-runtime.d.ts.map +1 -0
- package/dist/cli-runtime.js +125 -0
- package/dist/cli-runtime.js.map +1 -0
- package/dist/cli-types.d.ts +20 -0
- package/dist/cli-types.d.ts.map +1 -0
- package/dist/cli-types.js +2 -0
- package/dist/cli-types.js.map +1 -0
- package/dist/command-metadata.d.ts +54 -0
- package/dist/command-metadata.d.ts.map +1 -0
- package/dist/command-metadata.js +457 -0
- package/dist/command-metadata.js.map +1 -0
- package/dist/command-registry.d.ts +3 -0
- package/dist/command-registry.d.ts.map +1 -0
- package/dist/command-registry.js +856 -0
- package/dist/command-registry.js.map +1 -0
- package/dist/commands/exports.d.ts +33 -0
- package/dist/commands/exports.d.ts.map +1 -0
- package/dist/commands/exports.js +71 -0
- package/dist/commands/exports.js.map +1 -0
- package/dist/commands/projects.d.ts +52 -0
- package/dist/commands/projects.d.ts.map +1 -0
- package/dist/commands/projects.js +157 -0
- package/dist/commands/projects.js.map +1 -0
- package/dist/index.js +3280 -1116
- package/dist/index.js.map +1 -1
- package/dist/preview-first-edit.d.ts +37 -0
- package/dist/preview-first-edit.d.ts.map +1 -0
- package/dist/preview-first-edit.js +25 -0
- package/dist/preview-first-edit.js.map +1 -0
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -3,448 +3,18 @@ import { spawn } from "node:child_process";
|
|
|
3
3
|
import { mkdir, readFile, stat, writeFile } from "node:fs/promises";
|
|
4
4
|
import { homedir } from "node:os";
|
|
5
5
|
import { basename, dirname, extname, join, resolve } from "node:path";
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
import { BlitzReelsError } from "@blitzreels/sdk";
|
|
7
|
+
import { CLI_VERSION, createCliClient, DEFAULT_BASE_URL, loadConfig, persistLoggedInApiKey, resolveRuntimeConfig, storeApiKey, } from "./cli-runtime.js";
|
|
8
|
+
import { commandLabel, commandsEqual, flagsForCommand, GLOBAL_FLAGS, suggestCommandSpecs, suggestValues, } from "./command-metadata.js";
|
|
9
|
+
import { COMMAND_SPECS } from "./command-registry.js";
|
|
10
|
+
import { handleExportsCommand } from "./commands/exports.js";
|
|
11
|
+
import { handleProjectsCommand } from "./commands/projects.js";
|
|
12
|
+
import { buildDryRunRequest, requireDeleteConfirmation, requirePreviewFirstConfirmation, } from "./preview-first-edit.js";
|
|
13
|
+
const UPDATE_CACHE_PATH = join(process.env.XDG_CONFIG_HOME ?? join(homedir(), ".config"), "blitzreels", "update-check.json");
|
|
14
|
+
const UPDATE_CHECK_TTL_MS = 24 * 60 * 60 * 1000;
|
|
15
|
+
const UPDATE_CHECK_TIMEOUT_MS = 800;
|
|
10
16
|
const DEFAULT_WAIT_INTERVAL_MS = 3000;
|
|
11
17
|
const DEFAULT_WAIT_TIMEOUT_MS = 20 * 60 * 1000;
|
|
12
|
-
const GLOBAL_FLAGS = ["api-key", "base-url", "json", "help"];
|
|
13
|
-
const TEXT_STYLE_FLAGS = [
|
|
14
|
-
"spec-json",
|
|
15
|
-
"font-family",
|
|
16
|
-
"font-size",
|
|
17
|
-
"font-weight",
|
|
18
|
-
"color",
|
|
19
|
-
"background-color",
|
|
20
|
-
"padding-x",
|
|
21
|
-
"padding-y",
|
|
22
|
-
"border-radius",
|
|
23
|
-
"top",
|
|
24
|
-
"left",
|
|
25
|
-
"text-align",
|
|
26
|
-
"text-shadow",
|
|
27
|
-
"no-background",
|
|
28
|
-
];
|
|
29
|
-
const CAPTION_WORD_SELECTOR_FLAGS = [
|
|
30
|
-
"word-id",
|
|
31
|
-
"word-ids",
|
|
32
|
-
"timeline-item-id",
|
|
33
|
-
"match-text",
|
|
34
|
-
"match-pattern",
|
|
35
|
-
];
|
|
36
|
-
const CAPTION_WORD_STYLE_FLAGS = [
|
|
37
|
-
"style-json",
|
|
38
|
-
"font-family",
|
|
39
|
-
"font-weight",
|
|
40
|
-
"font-style",
|
|
41
|
-
"color",
|
|
42
|
-
"active-color",
|
|
43
|
-
"background-color",
|
|
44
|
-
"active-animation",
|
|
45
|
-
"text-transform",
|
|
46
|
-
"text-shadow",
|
|
47
|
-
"glow-color",
|
|
48
|
-
"glow-blur-px",
|
|
49
|
-
"scale",
|
|
50
|
-
"offset-x",
|
|
51
|
-
"offset-y",
|
|
52
|
-
"rotation-deg",
|
|
53
|
-
"letter-spacing",
|
|
54
|
-
"opacity",
|
|
55
|
-
"clear-existing",
|
|
56
|
-
];
|
|
57
|
-
function commandSpec({ command, usage, flags = [], }) {
|
|
58
|
-
return { command, usage, flags };
|
|
59
|
-
}
|
|
60
|
-
const COMMAND_SPECS = [
|
|
61
|
-
commandSpec({
|
|
62
|
-
command: ["auth", "login"],
|
|
63
|
-
usage: "blitzreels auth login [--no-browser] [--timeout-ms 120000]",
|
|
64
|
-
flags: ["no-browser", "timeout-ms"],
|
|
65
|
-
}),
|
|
66
|
-
commandSpec({
|
|
67
|
-
command: ["auth", "set-key"],
|
|
68
|
-
usage: "blitzreels auth set-key --api-key KEY [--base-url URL]",
|
|
69
|
-
}),
|
|
70
|
-
commandSpec({
|
|
71
|
-
command: ["auth", "whoami"],
|
|
72
|
-
usage: "blitzreels auth whoami [--json]",
|
|
73
|
-
}),
|
|
74
|
-
commandSpec({
|
|
75
|
-
command: ["workspace", "settings", "get"],
|
|
76
|
-
usage: "blitzreels workspace settings get [--json]",
|
|
77
|
-
}),
|
|
78
|
-
commandSpec({
|
|
79
|
-
command: ["workspace", "settings", "update"],
|
|
80
|
-
usage: "blitzreels workspace settings update [--name NAME] [--description TEXT|null] [--protected-words foo,bar] [--icon-storage-key KEY|null] [--aspect-ratio 9:16] [--orientation portrait] [--resolution 1080p] [--frame-rate 30] [--auto-manage-broll true] [--caption-theme-id ID|null]",
|
|
81
|
-
flags: [
|
|
82
|
-
"name",
|
|
83
|
-
"description",
|
|
84
|
-
"protected-words",
|
|
85
|
-
"icon-storage-key",
|
|
86
|
-
"aspect-ratio",
|
|
87
|
-
"orientation",
|
|
88
|
-
"resolution",
|
|
89
|
-
"frame-rate",
|
|
90
|
-
"auto-manage-broll",
|
|
91
|
-
"caption-theme-id",
|
|
92
|
-
],
|
|
93
|
-
}),
|
|
94
|
-
commandSpec({
|
|
95
|
-
command: ["projects", "list"],
|
|
96
|
-
usage: "blitzreels projects list [--search QUERY] [--status active|archived|all] [--limit 50] [--offset 0] [--cursor CURSOR] [--json]",
|
|
97
|
-
flags: ["search", "status", "limit", "offset", "cursor"],
|
|
98
|
-
}),
|
|
99
|
-
commandSpec({
|
|
100
|
-
command: ["projects", "create"],
|
|
101
|
-
usage: "blitzreels projects create --name NAME [--description TEXT] [--aspect-ratio 9:16] [--project-type video|carousel] [--carousel-settings-json JSON]",
|
|
102
|
-
flags: [
|
|
103
|
-
"name",
|
|
104
|
-
"description",
|
|
105
|
-
"aspect-ratio",
|
|
106
|
-
"project-type",
|
|
107
|
-
"carousel-settings-json",
|
|
108
|
-
],
|
|
109
|
-
}),
|
|
110
|
-
commandSpec({
|
|
111
|
-
command: ["projects", "get"],
|
|
112
|
-
usage: "blitzreels projects get --project-id ID [--json]",
|
|
113
|
-
flags: ["project-id"],
|
|
114
|
-
}),
|
|
115
|
-
commandSpec({
|
|
116
|
-
command: ["projects", "rename"],
|
|
117
|
-
usage: "blitzreels projects rename --project-id ID --name NAME",
|
|
118
|
-
flags: ["project-id", "name"],
|
|
119
|
-
}),
|
|
120
|
-
commandSpec({
|
|
121
|
-
command: ["projects", "update"],
|
|
122
|
-
usage: "blitzreels projects update --project-id ID [--name NAME] [--description TEXT|null]",
|
|
123
|
-
flags: ["project-id", "name", "description"],
|
|
124
|
-
}),
|
|
125
|
-
commandSpec({
|
|
126
|
-
command: ["projects", "delete"],
|
|
127
|
-
usage: "blitzreels projects delete --project-id ID --confirm-delete",
|
|
128
|
-
flags: ["project-id", "confirm-delete"],
|
|
129
|
-
}),
|
|
130
|
-
commandSpec({
|
|
131
|
-
command: ["media", "list"],
|
|
132
|
-
usage: "blitzreels media list [--folder-id ID] [--asset-type video|audio|image|document] [--search QUERY] [--limit 100] [--offset 0] [--json]",
|
|
133
|
-
flags: ["folder-id", "asset-type", "search", "limit", "offset"],
|
|
134
|
-
}),
|
|
135
|
-
commandSpec({
|
|
136
|
-
command: ["media", "get"],
|
|
137
|
-
usage: "blitzreels media get --asset-id ID [--json]",
|
|
138
|
-
flags: ["asset-id"],
|
|
139
|
-
}),
|
|
140
|
-
commandSpec({
|
|
141
|
-
command: ["media", "delete"],
|
|
142
|
-
usage: "blitzreels media delete --asset-id ID --confirm-delete",
|
|
143
|
-
flags: ["asset-id", "confirm-delete"],
|
|
144
|
-
}),
|
|
145
|
-
commandSpec({
|
|
146
|
-
command: ["media", "upload"],
|
|
147
|
-
usage: "blitzreels media upload --file ./video.mp4 [--name NAME] [--content-type video/mp4] [--auto-process true] [--auto-transcribe true] [--auto-describe true] [--auto-suggest-shorts true]",
|
|
148
|
-
flags: [
|
|
149
|
-
"file",
|
|
150
|
-
"name",
|
|
151
|
-
"content-type",
|
|
152
|
-
"auto-process",
|
|
153
|
-
"auto-transcribe",
|
|
154
|
-
"auto-describe",
|
|
155
|
-
"auto-suggest-shorts",
|
|
156
|
-
],
|
|
157
|
-
}),
|
|
158
|
-
commandSpec({
|
|
159
|
-
command: ["media", "import-youtube"],
|
|
160
|
-
usage: "blitzreels media import-youtube --url URL [--folder-id ID|root] [--quality 1080p] [--fps 30] [--audio-language fr|null]",
|
|
161
|
-
flags: ["url", "folder-id", "quality", "fps", "audio-language"],
|
|
162
|
-
}),
|
|
163
|
-
commandSpec({
|
|
164
|
-
command: ["media", "rename"],
|
|
165
|
-
usage: "blitzreels media rename --asset-id ID --name NAME",
|
|
166
|
-
flags: ["asset-id", "name"],
|
|
167
|
-
}),
|
|
168
|
-
commandSpec({
|
|
169
|
-
command: ["media", "update"],
|
|
170
|
-
usage: "blitzreels media update --asset-id ID [--name NAME] [--description TEXT|null] [--folder-id ID|root] [--allow-broll true]",
|
|
171
|
-
flags: ["asset-id", "name", "description", "folder-id", "allow-broll"],
|
|
172
|
-
}),
|
|
173
|
-
commandSpec({
|
|
174
|
-
command: ["media", "move"],
|
|
175
|
-
usage: "blitzreels media move --asset-id ID|--asset-ids ID1,ID2 --target-folder-id ID|root",
|
|
176
|
-
flags: ["asset-id", "asset-ids", "target-folder-id"],
|
|
177
|
-
}),
|
|
178
|
-
commandSpec({
|
|
179
|
-
command: ["media", "attach"],
|
|
180
|
-
usage: "blitzreels media attach --project-id ID --asset-id ID [--at 0] [--duration 4] [--layer-index 2] [--allow-duplicate true] [--position-preset center] [--position-x 0] [--position-y 0] [--width-px 320] [--height-px 180] [--scale 1.2] [--opacity 0.9] [--animation-preset popIn]",
|
|
181
|
-
flags: [
|
|
182
|
-
"project-id",
|
|
183
|
-
"asset-id",
|
|
184
|
-
"at",
|
|
185
|
-
"duration",
|
|
186
|
-
"layer-index",
|
|
187
|
-
"allow-duplicate",
|
|
188
|
-
"position-preset",
|
|
189
|
-
"position-x",
|
|
190
|
-
"position-y",
|
|
191
|
-
"width-px",
|
|
192
|
-
"height-px",
|
|
193
|
-
"scale",
|
|
194
|
-
"opacity",
|
|
195
|
-
"animation-preset",
|
|
196
|
-
],
|
|
197
|
-
}),
|
|
198
|
-
commandSpec({
|
|
199
|
-
command: ["media", "folders", "list"],
|
|
200
|
-
usage: "blitzreels media folders list [--parent-folder-id ID] [--json]",
|
|
201
|
-
flags: ["parent-folder-id"],
|
|
202
|
-
}),
|
|
203
|
-
commandSpec({
|
|
204
|
-
command: ["media", "folders", "create"],
|
|
205
|
-
usage: "blitzreels media folders create --name NAME [--parent-folder-id ID] [--description TEXT|null] [--icon-type video]",
|
|
206
|
-
flags: ["name", "parent-folder-id", "description", "icon-type"],
|
|
207
|
-
}),
|
|
208
|
-
commandSpec({
|
|
209
|
-
command: ["media", "folders", "rename"],
|
|
210
|
-
usage: "blitzreels media folders rename --folder-id ID --name NAME",
|
|
211
|
-
flags: ["folder-id", "name"],
|
|
212
|
-
}),
|
|
213
|
-
commandSpec({
|
|
214
|
-
command: ["media", "folders", "update"],
|
|
215
|
-
usage: "blitzreels media folders update --folder-id ID [--name NAME] [--parent-folder-id ID|root] [--description TEXT|null] [--icon-type video]",
|
|
216
|
-
flags: [
|
|
217
|
-
"folder-id",
|
|
218
|
-
"name",
|
|
219
|
-
"parent-folder-id",
|
|
220
|
-
"description",
|
|
221
|
-
"icon-type",
|
|
222
|
-
],
|
|
223
|
-
}),
|
|
224
|
-
commandSpec({
|
|
225
|
-
command: ["media", "folders", "delete"],
|
|
226
|
-
usage: "blitzreels media folders delete --folder-id ID --confirm-delete [--cascade true]",
|
|
227
|
-
flags: ["folder-id", "confirm-delete", "cascade"],
|
|
228
|
-
}),
|
|
229
|
-
commandSpec({
|
|
230
|
-
command: ["project", "inspect"],
|
|
231
|
-
usage: "blitzreels project inspect --project-id ID [--mode full] [--json]",
|
|
232
|
-
flags: ["project-id", "mode"],
|
|
233
|
-
}),
|
|
234
|
-
commandSpec({
|
|
235
|
-
command: ["project", "timeline"],
|
|
236
|
-
usage: "blitzreels project timeline --project-id ID [--json]",
|
|
237
|
-
flags: ["project-id"],
|
|
238
|
-
}),
|
|
239
|
-
commandSpec({
|
|
240
|
-
command: ["project", "snapshot"],
|
|
241
|
-
usage: "blitzreels project snapshot --project-id ID --at 00:03 [--target-width 608] [--image-format jpeg|png|webp] [--json]",
|
|
242
|
-
flags: ["project-id", "at", "target-width", "image-format"],
|
|
243
|
-
}),
|
|
244
|
-
commandSpec({
|
|
245
|
-
command: ["project", "snapshots"],
|
|
246
|
-
usage: "blitzreels project snapshots --project-id ID --at 00:01,00:03 [--target-width 608] [--image-format jpeg|png|webp] [--json]",
|
|
247
|
-
flags: ["project-id", "at", "target-width", "image-format"],
|
|
248
|
-
}),
|
|
249
|
-
commandSpec({
|
|
250
|
-
command: ["timeline", "transform"],
|
|
251
|
-
usage: "blitzreels timeline transform --project-id ID --timeline-item-id ID [--position-x 0] [--position-y 0] [--width-px 320|null] [--height-px 180|null] [--keep-aspect-ratio true]",
|
|
252
|
-
flags: [
|
|
253
|
-
"project-id",
|
|
254
|
-
"timeline-item-id",
|
|
255
|
-
"position-x",
|
|
256
|
-
"position-y",
|
|
257
|
-
"width-px",
|
|
258
|
-
"height-px",
|
|
259
|
-
"keep-aspect-ratio",
|
|
260
|
-
],
|
|
261
|
-
}),
|
|
262
|
-
commandSpec({
|
|
263
|
-
command: ["timeline", "move"],
|
|
264
|
-
usage: "blitzreels timeline move --project-id ID --timeline-item-id ID --at 00:03 [--layer-index 2]",
|
|
265
|
-
flags: ["project-id", "timeline-item-id", "at", "layer-index"],
|
|
266
|
-
}),
|
|
267
|
-
commandSpec({
|
|
268
|
-
command: ["timeline", "delete"],
|
|
269
|
-
usage: "blitzreels timeline delete --project-id ID --timeline-item-id ID|--timeline-item-ids ID1,ID2 [--auto-pack true]",
|
|
270
|
-
flags: ["project-id", "timeline-item-id", "timeline-item-ids", "auto-pack"],
|
|
271
|
-
}),
|
|
272
|
-
commandSpec({
|
|
273
|
-
command: ["text", "add"],
|
|
274
|
-
usage: "blitzreels text add --project-id ID --text \"Hook\" [--name NAME] [--at 0] [--duration 4] [--layer-index 2] [--spec-json JSON] [--font-family Inter] [--font-size 64] [--font-weight 700] [--color '#fff'] [--background-color '#000'] [--padding-x 32] [--padding-y 16] [--border-radius 24] [--top '18%'] [--left '50%'] [--text-align center] [--text-shadow '0 6px 18px #000'] [--no-background]",
|
|
275
|
-
flags: [
|
|
276
|
-
"project-id",
|
|
277
|
-
"text",
|
|
278
|
-
"name",
|
|
279
|
-
"at",
|
|
280
|
-
"duration",
|
|
281
|
-
"layer-index",
|
|
282
|
-
...TEXT_STYLE_FLAGS,
|
|
283
|
-
],
|
|
284
|
-
}),
|
|
285
|
-
commandSpec({
|
|
286
|
-
command: ["text", "list"],
|
|
287
|
-
usage: "blitzreels text list --project-id ID [--json]",
|
|
288
|
-
flags: ["project-id"],
|
|
289
|
-
}),
|
|
290
|
-
commandSpec({
|
|
291
|
-
command: ["text", "update"],
|
|
292
|
-
usage: "blitzreels text update --project-id ID --content-item-id ID [--text \"New hook\"] [--name NAME] [--spec-json JSON] [--font-family Pacifico] [--font-size 82] [--font-weight 700] [--color '#17FFA6'] [--background-color '#051914'] [--padding-x 32] [--padding-y 16] [--border-radius 24] [--top '18%'] [--left '50%'] [--text-align center] [--text-shadow '0 6px 18px #000'] [--no-background]",
|
|
293
|
-
flags: [
|
|
294
|
-
"project-id",
|
|
295
|
-
"content-item-id",
|
|
296
|
-
"text",
|
|
297
|
-
"name",
|
|
298
|
-
...TEXT_STYLE_FLAGS,
|
|
299
|
-
],
|
|
300
|
-
}),
|
|
301
|
-
commandSpec({
|
|
302
|
-
command: ["text", "remove"],
|
|
303
|
-
usage: "blitzreels text remove --project-id ID --content-item-id ID",
|
|
304
|
-
flags: ["project-id", "content-item-id"],
|
|
305
|
-
}),
|
|
306
|
-
commandSpec({
|
|
307
|
-
command: ["audio", "add"],
|
|
308
|
-
usage: "blitzreels audio add --project-id ID --asset-id ID [--at 0] [--duration 30] [--volume 0.6] [--fade-in 0.5] [--fade-out 0.5] [--loop true]",
|
|
309
|
-
flags: [
|
|
310
|
-
"project-id",
|
|
311
|
-
"asset-id",
|
|
312
|
-
"at",
|
|
313
|
-
"duration",
|
|
314
|
-
"volume",
|
|
315
|
-
"fade-in",
|
|
316
|
-
"fade-out",
|
|
317
|
-
"loop",
|
|
318
|
-
],
|
|
319
|
-
}),
|
|
320
|
-
commandSpec({
|
|
321
|
-
command: ["silence", "plan"],
|
|
322
|
-
usage: "blitzreels silence plan --project-id ID --timeline-item-id ID [--padding-ms 120] [--silence-threshold-db -35] [--min-silence-duration 0.25] [--remove-blips-max-duration 0.12] [--use-caption-validation true] [--json]",
|
|
323
|
-
flags: [
|
|
324
|
-
"project-id",
|
|
325
|
-
"timeline-item-id",
|
|
326
|
-
"padding-ms",
|
|
327
|
-
"silence-threshold-db",
|
|
328
|
-
"min-silence-duration",
|
|
329
|
-
"remove-blips-max-duration",
|
|
330
|
-
"use-caption-validation",
|
|
331
|
-
],
|
|
332
|
-
}),
|
|
333
|
-
commandSpec({
|
|
334
|
-
command: ["silence", "apply"],
|
|
335
|
-
usage: "blitzreels silence apply --project-id ID --timeline-item-id ID --confirm-preview [--padding-ms 120] [--silence-threshold-db -35] [--min-silence-duration 0.25] [--remove-blips-max-duration 0.12] [--use-caption-validation true] [--delete-silent-clips true] [--manual-segment-overrides-json JSON]",
|
|
336
|
-
flags: [
|
|
337
|
-
"project-id",
|
|
338
|
-
"timeline-item-id",
|
|
339
|
-
"confirm-preview",
|
|
340
|
-
"padding-ms",
|
|
341
|
-
"silence-threshold-db",
|
|
342
|
-
"min-silence-duration",
|
|
343
|
-
"remove-blips-max-duration",
|
|
344
|
-
"use-caption-validation",
|
|
345
|
-
"delete-silent-clips",
|
|
346
|
-
"manual-segment-overrides-json",
|
|
347
|
-
],
|
|
348
|
-
}),
|
|
349
|
-
commandSpec({
|
|
350
|
-
command: ["mistakes", "plan"],
|
|
351
|
-
usage: "blitzreels mistakes plan --project-id ID --timeline-item-id ID [--json]",
|
|
352
|
-
flags: ["project-id", "timeline-item-id"],
|
|
353
|
-
}),
|
|
354
|
-
commandSpec({
|
|
355
|
-
command: ["mistakes", "apply"],
|
|
356
|
-
usage: "blitzreels mistakes apply --project-id ID --timeline-item-id ID --confirm-preview [--plan-json JSON] [--manual-verdicts-json JSON] [--delete-mistakes true]",
|
|
357
|
-
flags: [
|
|
358
|
-
"project-id",
|
|
359
|
-
"timeline-item-id",
|
|
360
|
-
"confirm-preview",
|
|
361
|
-
"plan-json",
|
|
362
|
-
"manual-verdicts-json",
|
|
363
|
-
"delete-mistakes",
|
|
364
|
-
],
|
|
365
|
-
}),
|
|
366
|
-
commandSpec({
|
|
367
|
-
command: ["captions", "words", "list"],
|
|
368
|
-
usage: "blitzreels captions words list --project-id ID [--timeline-item-id ID] [--match-text TEXT] [--limit 100] [--offset 0]",
|
|
369
|
-
flags: ["project-id", "timeline-item-id", "match-text", "limit", "offset"],
|
|
370
|
-
}),
|
|
371
|
-
commandSpec({
|
|
372
|
-
command: ["captions", "words", "text"],
|
|
373
|
-
usage: 'blitzreels captions words text --project-id ID --word-id ID --text "Fix"|--new-text "Fix"',
|
|
374
|
-
flags: ["project-id", "word-id", "text", "new-text"],
|
|
375
|
-
}),
|
|
376
|
-
commandSpec({
|
|
377
|
-
command: ["captions", "words", "batch-text"],
|
|
378
|
-
usage: 'blitzreels captions words batch-text --project-id ID --updates-json \'[{"word_id":"...","new_text":"..."}]\'',
|
|
379
|
-
flags: ["project-id", "updates-json"],
|
|
380
|
-
}),
|
|
381
|
-
commandSpec({
|
|
382
|
-
command: ["captions", "words", "style"],
|
|
383
|
-
usage: "blitzreels captions words style --project-id ID [--word-id ID|--word-ids IDs|--timeline-item-id ID|--match-text TEXT|--match-pattern numbers|caps|exclamations] [--style-json JSON|null] [--font-family Pacifico] [--font-weight 700] [--font-style italic] [--color '#17FFA6'] [--active-color '#fff'] [--background-color '#051914'] [--active-animation pop] [--text-transform uppercase] [--text-shadow '0 6px 18px #000'] [--glow-color '#17FFA6'] [--glow-blur-px 18] [--scale 1.45] [--offset-x 0] [--offset-y -8] [--rotation-deg -2] [--letter-spacing 1] [--opacity 1] [--clear-existing true]",
|
|
384
|
-
flags: [
|
|
385
|
-
"project-id",
|
|
386
|
-
...CAPTION_WORD_SELECTOR_FLAGS,
|
|
387
|
-
...CAPTION_WORD_STYLE_FLAGS,
|
|
388
|
-
],
|
|
389
|
-
}),
|
|
390
|
-
commandSpec({
|
|
391
|
-
command: ["captions", "words", "emphasis"],
|
|
392
|
-
usage: "blitzreels captions words emphasis --project-id ID [--word-id ID|--word-ids IDs|--timeline-item-id ID|--match-text TEXT|--match-pattern numbers|caps|exclamations] [--emphasis true]",
|
|
393
|
-
flags: ["project-id", ...CAPTION_WORD_SELECTOR_FLAGS, "emphasis"],
|
|
394
|
-
}),
|
|
395
|
-
commandSpec({
|
|
396
|
-
command: ["captions", "words", "delete"],
|
|
397
|
-
usage: "blitzreels captions words delete --project-id ID --word-id ID|--word-ids ID1,ID2",
|
|
398
|
-
flags: ["project-id", "word-id", "word-ids"],
|
|
399
|
-
}),
|
|
400
|
-
commandSpec({
|
|
401
|
-
command: ["captions", "words", "merge"],
|
|
402
|
-
usage: 'blitzreels captions words merge --project-id ID --word-ids ID1,ID2 [--text "Merged"]',
|
|
403
|
-
flags: ["project-id", "word-ids", "text"],
|
|
404
|
-
}),
|
|
405
|
-
commandSpec({
|
|
406
|
-
command: ["captions", "words", "split"],
|
|
407
|
-
usage: 'blitzreels captions words split --project-id ID --word-id ID --words "foo,bar"',
|
|
408
|
-
flags: ["project-id", "word-id", "words"],
|
|
409
|
-
}),
|
|
410
|
-
commandSpec({
|
|
411
|
-
command: ["captions", "themes", "list"],
|
|
412
|
-
usage: "blitzreels captions themes list [--json]",
|
|
413
|
-
}),
|
|
414
|
-
commandSpec({
|
|
415
|
-
command: ["captions", "theme", "set"],
|
|
416
|
-
usage: "blitzreels captions theme set --project-id ID --theme-id ID",
|
|
417
|
-
flags: ["project-id", "theme-id"],
|
|
418
|
-
}),
|
|
419
|
-
commandSpec({
|
|
420
|
-
command: ["captions", "theme", "set-default"],
|
|
421
|
-
usage: "blitzreels captions theme set-default --theme-id ID",
|
|
422
|
-
flags: ["theme-id"],
|
|
423
|
-
}),
|
|
424
|
-
commandSpec({
|
|
425
|
-
command: ["exports", "start"],
|
|
426
|
-
usage: "blitzreels exports start --project-id ID [--resolution 1080p] [--format mp4] [--include-captions true]",
|
|
427
|
-
flags: ["project-id", "resolution", "format", "include-captions"],
|
|
428
|
-
}),
|
|
429
|
-
commandSpec({
|
|
430
|
-
command: ["exports", "wait"],
|
|
431
|
-
usage: "blitzreels exports wait --export-id ID [--interval-ms 3000] [--timeout-ms 1200000] [--json]",
|
|
432
|
-
flags: ["export-id", "interval-ms", "timeout-ms"],
|
|
433
|
-
}),
|
|
434
|
-
commandSpec({
|
|
435
|
-
command: ["exports", "download"],
|
|
436
|
-
usage: "blitzreels exports download --export-id ID --output ./short.mp4",
|
|
437
|
-
flags: ["export-id", "output"],
|
|
438
|
-
}),
|
|
439
|
-
commandSpec({
|
|
440
|
-
command: ["api", "docs"],
|
|
441
|
-
usage: "blitzreels api docs",
|
|
442
|
-
}),
|
|
443
|
-
commandSpec({
|
|
444
|
-
command: ["api", "spec"],
|
|
445
|
-
usage: "blitzreels api spec",
|
|
446
|
-
}),
|
|
447
|
-
];
|
|
448
18
|
class CliUsageError extends Error {
|
|
449
19
|
code;
|
|
450
20
|
suggestions;
|
|
@@ -464,33 +34,118 @@ async function main() {
|
|
|
464
34
|
return;
|
|
465
35
|
}
|
|
466
36
|
if (getBooleanFlag({ flags: parsed.flags, name: "help" })) {
|
|
467
|
-
|
|
37
|
+
if (parsed.command.length === 0) {
|
|
38
|
+
printHelp();
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
printCommandOrGroupHelpOrThrow({ command: parsed.command });
|
|
468
42
|
return;
|
|
469
43
|
}
|
|
44
|
+
if (!findCommandSpec({ command: parsed.command })) {
|
|
45
|
+
const groupSpecs = findGroupCommandSpecs({ command: parsed.command });
|
|
46
|
+
if (groupSpecs.length > 0) {
|
|
47
|
+
printGroupHelp({ command: parsed.command, specs: groupSpecs });
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
470
51
|
validateParsedArgs({ parsed });
|
|
471
|
-
|
|
472
|
-
const
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
config.baseUrl;
|
|
478
|
-
const clientConfig = {
|
|
479
|
-
apiKey,
|
|
480
|
-
baseUrl,
|
|
481
|
-
fetchFn: undefined,
|
|
482
|
-
userAgent: "blitzreels-cli/0.0.0",
|
|
483
|
-
};
|
|
52
|
+
validateSafeInputs({ parsed });
|
|
53
|
+
const storedConfig = await loadConfig();
|
|
54
|
+
const config = resolveRuntimeConfig({
|
|
55
|
+
flags: parsed.flags,
|
|
56
|
+
storedConfig,
|
|
57
|
+
});
|
|
484
58
|
const context = {
|
|
485
59
|
args: parsed,
|
|
486
60
|
json: getBooleanFlag({ flags: parsed.flags, name: "json" }),
|
|
487
|
-
|
|
488
|
-
|
|
61
|
+
dryRun: getBooleanFlag({ flags: parsed.flags, name: "dry-run" }),
|
|
62
|
+
fields: parseCsvFlag({ flags: parsed.flags, name: "fields" }),
|
|
63
|
+
client: createCliClient({ config }),
|
|
64
|
+
config,
|
|
489
65
|
};
|
|
490
66
|
await runCommand({ context });
|
|
67
|
+
await maybePrintUpdateNotice({
|
|
68
|
+
parsed,
|
|
69
|
+
baseUrl: config.baseUrl,
|
|
70
|
+
json: context.json,
|
|
71
|
+
});
|
|
491
72
|
}
|
|
492
73
|
async function runCommand({ context }) {
|
|
493
74
|
const [group, action, subaction] = context.args.command;
|
|
75
|
+
if (group === "version") {
|
|
76
|
+
const update = await getUpdateStatus({
|
|
77
|
+
baseUrl: context.config.baseUrl,
|
|
78
|
+
cachePolicy: "refresh",
|
|
79
|
+
});
|
|
80
|
+
printResult({
|
|
81
|
+
json: context.json,
|
|
82
|
+
value: {
|
|
83
|
+
current: CLI_VERSION,
|
|
84
|
+
latest: update.manifest.latest,
|
|
85
|
+
update_available: update.updateAvailable,
|
|
86
|
+
upgrade: update.manifest.upgrade,
|
|
87
|
+
changelog_url: update.manifest.url,
|
|
88
|
+
},
|
|
89
|
+
human: buildVersionLines({ update }),
|
|
90
|
+
});
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
if (group === "doctor") {
|
|
94
|
+
const update = await getUpdateStatus({
|
|
95
|
+
baseUrl: context.config.baseUrl,
|
|
96
|
+
cachePolicy: "refresh",
|
|
97
|
+
});
|
|
98
|
+
printResult({
|
|
99
|
+
json: context.json,
|
|
100
|
+
value: {
|
|
101
|
+
cli: {
|
|
102
|
+
current: CLI_VERSION,
|
|
103
|
+
latest: update.manifest.latest,
|
|
104
|
+
update_available: update.updateAvailable,
|
|
105
|
+
supported: !isVersionLessThan({
|
|
106
|
+
left: CLI_VERSION,
|
|
107
|
+
right: update.manifest.minSupported,
|
|
108
|
+
}),
|
|
109
|
+
},
|
|
110
|
+
config: {
|
|
111
|
+
api_key: context.config.apiKey ? "configured" : "missing",
|
|
112
|
+
base_url: context.config.baseUrl ?? "https://www.blitzreels.com/api/v1",
|
|
113
|
+
key_storage: context.config.keyStorage ?? "none",
|
|
114
|
+
},
|
|
115
|
+
upgrade: update.manifest.upgrade,
|
|
116
|
+
},
|
|
117
|
+
human: buildDoctorLines({ context, update }),
|
|
118
|
+
});
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
if (group === "agent-context" || group === "schema") {
|
|
122
|
+
const result = buildAgentContext({
|
|
123
|
+
commandFilter: getStringFlag({
|
|
124
|
+
flags: context.args.flags,
|
|
125
|
+
name: "command",
|
|
126
|
+
}),
|
|
127
|
+
});
|
|
128
|
+
printResult({
|
|
129
|
+
json: context.json,
|
|
130
|
+
value: result,
|
|
131
|
+
human: buildAgentContextLines({ value: result }),
|
|
132
|
+
});
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
if (group === "prime" || group === "llms") {
|
|
136
|
+
const result = buildAgentPrime({
|
|
137
|
+
commandFilter: getStringFlag({
|
|
138
|
+
flags: context.args.flags,
|
|
139
|
+
name: "command",
|
|
140
|
+
}),
|
|
141
|
+
});
|
|
142
|
+
printResult({
|
|
143
|
+
json: context.json,
|
|
144
|
+
value: result,
|
|
145
|
+
human: buildAgentPrimeLines({ value: result }),
|
|
146
|
+
});
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
494
149
|
if (group === "auth" && action === "set-key") {
|
|
495
150
|
await setApiKey({ context });
|
|
496
151
|
return;
|
|
@@ -501,14 +156,22 @@ async function runCommand({ context }) {
|
|
|
501
156
|
}
|
|
502
157
|
if (group === "auth" && action === "whoami") {
|
|
503
158
|
const result = await context.client.auth.validate();
|
|
159
|
+
const displayEnvironment = resolveDisplayEnvironment({
|
|
160
|
+
baseUrl: context.config.baseUrl,
|
|
161
|
+
environment: result.environment,
|
|
162
|
+
});
|
|
163
|
+
const value = {
|
|
164
|
+
...result,
|
|
165
|
+
display_environment: displayEnvironment,
|
|
166
|
+
};
|
|
504
167
|
printResult({
|
|
505
168
|
json: context.json,
|
|
506
|
-
value
|
|
169
|
+
value,
|
|
507
170
|
human: [
|
|
508
171
|
`Workspace: ${result.organization_id}`,
|
|
509
172
|
`User: ${result.user_id}`,
|
|
510
173
|
`Plan: ${result.plan}`,
|
|
511
|
-
`Environment: ${
|
|
174
|
+
`Environment: ${displayEnvironment}`,
|
|
512
175
|
`Docs: ${result.docs_url}`,
|
|
513
176
|
],
|
|
514
177
|
});
|
|
@@ -517,10 +180,18 @@ async function runCommand({ context }) {
|
|
|
517
180
|
if (group === "api" && action === "spec") {
|
|
518
181
|
const url = new URL(context.config.baseUrl ?? "https://www.blitzreels.com/api/v1");
|
|
519
182
|
url.pathname = "/api/openapi.json";
|
|
183
|
+
const response = await fetch(url, {
|
|
184
|
+
headers: { Accept: "application/json" },
|
|
185
|
+
});
|
|
186
|
+
const spec = await readJsonResponse({
|
|
187
|
+
response,
|
|
188
|
+
url: url.toString(),
|
|
189
|
+
action: "fetch OpenAPI spec",
|
|
190
|
+
});
|
|
520
191
|
printResult({
|
|
521
|
-
json:
|
|
522
|
-
value:
|
|
523
|
-
human: [
|
|
192
|
+
json: true,
|
|
193
|
+
value: spec,
|
|
194
|
+
human: [],
|
|
524
195
|
});
|
|
525
196
|
return;
|
|
526
197
|
}
|
|
@@ -534,6 +205,14 @@ async function runCommand({ context }) {
|
|
|
534
205
|
});
|
|
535
206
|
return;
|
|
536
207
|
}
|
|
208
|
+
if (context.dryRun) {
|
|
209
|
+
printResult({
|
|
210
|
+
json: true,
|
|
211
|
+
value: buildDryRunPreview({ context }),
|
|
212
|
+
human: [],
|
|
213
|
+
});
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
537
216
|
requireApiKey({ apiKey: context.config.apiKey });
|
|
538
217
|
if (group === "workspace" && action === "settings" && subaction === "get") {
|
|
539
218
|
const result = await context.client.workspace.getSettings();
|
|
@@ -575,120 +254,27 @@ async function runCommand({ context }) {
|
|
|
575
254
|
});
|
|
576
255
|
return;
|
|
577
256
|
}
|
|
578
|
-
if (group === "projects"
|
|
579
|
-
const
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
if (group === "projects" && action === "create") {
|
|
596
|
-
const result = await context.client.projects.create({
|
|
597
|
-
name: requireStringFlag({ flags: context.args.flags, name: "name" }),
|
|
598
|
-
description: getStringFlag({
|
|
599
|
-
flags: context.args.flags,
|
|
600
|
-
name: "description",
|
|
601
|
-
}),
|
|
602
|
-
aspectRatio: parseAspectRatio({
|
|
603
|
-
value: getStringFlag({
|
|
604
|
-
flags: context.args.flags,
|
|
605
|
-
name: "aspect-ratio",
|
|
606
|
-
}),
|
|
607
|
-
}),
|
|
608
|
-
projectType: parseProjectType({
|
|
609
|
-
value: getStringFlag({
|
|
610
|
-
flags: context.args.flags,
|
|
611
|
-
name: "project-type",
|
|
612
|
-
}),
|
|
613
|
-
}),
|
|
614
|
-
carouselSettings: parseJsonObjectFlag({
|
|
615
|
-
flags: context.args.flags,
|
|
616
|
-
name: "carousel-settings-json",
|
|
617
|
-
}),
|
|
618
|
-
});
|
|
619
|
-
printResult({
|
|
620
|
-
json: context.json,
|
|
621
|
-
value: result,
|
|
622
|
-
human: [`Project created: ${result.id} ${result.name}`],
|
|
623
|
-
});
|
|
624
|
-
return;
|
|
625
|
-
}
|
|
626
|
-
if (group === "projects" && action === "get") {
|
|
627
|
-
const result = await context.client.projects.get({
|
|
628
|
-
projectId: requireStringFlag({
|
|
629
|
-
flags: context.args.flags,
|
|
630
|
-
name: "project-id",
|
|
631
|
-
}),
|
|
632
|
-
});
|
|
633
|
-
printResult({
|
|
634
|
-
json: context.json,
|
|
635
|
-
value: result,
|
|
636
|
-
human: [
|
|
637
|
-
`${result.id} ${result.status} ${result.name}`,
|
|
638
|
-
`Description: ${result.description ?? ""}`,
|
|
639
|
-
],
|
|
640
|
-
});
|
|
641
|
-
return;
|
|
642
|
-
}
|
|
643
|
-
if (group === "projects" && action === "rename") {
|
|
644
|
-
const result = await context.client.projects.update({
|
|
645
|
-
projectId: requireStringFlag({
|
|
646
|
-
flags: context.args.flags,
|
|
647
|
-
name: "project-id",
|
|
648
|
-
}),
|
|
649
|
-
name: requireStringFlag({ flags: context.args.flags, name: "name" }),
|
|
650
|
-
description: undefined,
|
|
651
|
-
});
|
|
652
|
-
printResult({
|
|
653
|
-
json: context.json,
|
|
654
|
-
value: result,
|
|
655
|
-
human: [`Project renamed: ${result.id} ${result.name}`],
|
|
656
|
-
});
|
|
657
|
-
return;
|
|
658
|
-
}
|
|
659
|
-
if (group === "projects" && action === "update") {
|
|
660
|
-
const result = await context.client.projects.update({
|
|
661
|
-
projectId: requireStringFlag({
|
|
662
|
-
flags: context.args.flags,
|
|
663
|
-
name: "project-id",
|
|
664
|
-
}),
|
|
665
|
-
name: getStringFlag({ flags: context.args.flags, name: "name" }),
|
|
666
|
-
description: parseNullableStringFlag({
|
|
667
|
-
flags: context.args.flags,
|
|
668
|
-
name: "description",
|
|
669
|
-
}),
|
|
670
|
-
});
|
|
671
|
-
printResult({
|
|
672
|
-
json: context.json,
|
|
673
|
-
value: result,
|
|
674
|
-
human: [`Project updated: ${result.id} ${result.name}`],
|
|
675
|
-
});
|
|
676
|
-
return;
|
|
677
|
-
}
|
|
678
|
-
if (group === "projects" && action === "delete") {
|
|
679
|
-
requireConfirmDelete({ flags: context.args.flags });
|
|
680
|
-
const result = await context.client.projects.delete({
|
|
681
|
-
projectId: requireStringFlag({
|
|
682
|
-
flags: context.args.flags,
|
|
683
|
-
name: "project-id",
|
|
684
|
-
}),
|
|
685
|
-
});
|
|
686
|
-
printResult({
|
|
687
|
-
json: context.json,
|
|
688
|
-
value: result,
|
|
689
|
-
human: ["Project deleted."],
|
|
257
|
+
if (group === "projects") {
|
|
258
|
+
const handled = await handleProjectsCommand({
|
|
259
|
+
context,
|
|
260
|
+
action,
|
|
261
|
+
helpers: {
|
|
262
|
+
getBooleanFlag,
|
|
263
|
+
getNumberFlag,
|
|
264
|
+
getStringFlag,
|
|
265
|
+
requireStringFlag,
|
|
266
|
+
parseNullableStringFlag,
|
|
267
|
+
parseProjectStatus,
|
|
268
|
+
parseAspectRatio,
|
|
269
|
+
parseProjectType,
|
|
270
|
+
parseJsonObjectFlag,
|
|
271
|
+
buildProjectDashboardUrl,
|
|
272
|
+
printResult,
|
|
273
|
+
},
|
|
690
274
|
});
|
|
691
|
-
|
|
275
|
+
if (handled) {
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
692
278
|
}
|
|
693
279
|
if (group === "media" && action === "list") {
|
|
694
280
|
const result = await context.client.media.list({
|
|
@@ -719,13 +305,31 @@ async function runCommand({ context }) {
|
|
|
719
305
|
value: result,
|
|
720
306
|
human: [
|
|
721
307
|
`${result.asset.id} ${result.asset.assetType} ${result.asset.name}`,
|
|
308
|
+
`Processing: ${result.asset.processingStatus ?? "unknown"}${result.asset.processingSubstage ? `/${result.asset.processingSubstage}` : ""}`,
|
|
309
|
+
...(result.asset.processingError
|
|
310
|
+
? [`Processing error: ${result.asset.processingError}`]
|
|
311
|
+
: []),
|
|
312
|
+
`Retryable: ${result.asset.retryable ? "yes" : "no"}`,
|
|
313
|
+
...(result.asset.providerRequestIds.processJobRunId
|
|
314
|
+
? [`Process job: ${result.asset.providerRequestIds.processJobRunId}`]
|
|
315
|
+
: []),
|
|
316
|
+
...(result.asset.providerRequestIds.transcribeJobRunId
|
|
317
|
+
? [
|
|
318
|
+
`Transcribe job: ${result.asset.providerRequestIds.transcribeJobRunId}`,
|
|
319
|
+
]
|
|
320
|
+
: []),
|
|
722
321
|
`URL: ${result.asset.fileUrl ?? ""}`,
|
|
723
322
|
],
|
|
724
323
|
});
|
|
725
324
|
return;
|
|
726
325
|
}
|
|
727
326
|
if (group === "media" && action === "delete") {
|
|
728
|
-
|
|
327
|
+
requireDeleteConfirmation({
|
|
328
|
+
confirmed: getBooleanFlag({
|
|
329
|
+
flags: context.args.flags,
|
|
330
|
+
name: "confirm-delete",
|
|
331
|
+
}),
|
|
332
|
+
});
|
|
729
333
|
const result = await context.client.media.delete({
|
|
730
334
|
assetId: requireStringFlag({
|
|
731
335
|
flags: context.args.flags,
|
|
@@ -833,7 +437,8 @@ async function runCommand({ context }) {
|
|
|
833
437
|
});
|
|
834
438
|
return;
|
|
835
439
|
}
|
|
836
|
-
if (group === "media" && action === "attach")
|
|
440
|
+
if ((group === "media" && action === "attach") ||
|
|
441
|
+
(group === "broll" && action === "add")) {
|
|
837
442
|
const result = await context.client.media.attachToProject({
|
|
838
443
|
projectId: requireStringFlag({
|
|
839
444
|
flags: context.args.flags,
|
|
@@ -891,24 +496,6 @@ async function runCommand({ context }) {
|
|
|
891
496
|
}),
|
|
892
497
|
}),
|
|
893
498
|
});
|
|
894
|
-
const requestedLayer = getNumberFlag({
|
|
895
|
-
flags: context.args.flags,
|
|
896
|
-
name: "layer-index",
|
|
897
|
-
});
|
|
898
|
-
if (requestedLayer !== undefined) {
|
|
899
|
-
await Promise.all(result.inserted
|
|
900
|
-
.map((item) => item.timeline_item_id)
|
|
901
|
-
.filter((item) => typeof item === "string")
|
|
902
|
-
.map((timelineItemId) => context.client.timeline.move({
|
|
903
|
-
projectId: requireStringFlag({
|
|
904
|
-
flags: context.args.flags,
|
|
905
|
-
name: "project-id",
|
|
906
|
-
}),
|
|
907
|
-
timelineItemId,
|
|
908
|
-
newStartSeconds: result.inserted.find((item) => item.timeline_item_id === timelineItemId)?.start_seconds ?? 0,
|
|
909
|
-
newLayerIndex: requestedLayer,
|
|
910
|
-
})));
|
|
911
|
-
}
|
|
912
499
|
printResult({
|
|
913
500
|
json: context.json,
|
|
914
501
|
value: result,
|
|
@@ -918,45 +505,268 @@ async function runCommand({ context }) {
|
|
|
918
505
|
});
|
|
919
506
|
return;
|
|
920
507
|
}
|
|
921
|
-
if (group === "media" && action === "
|
|
922
|
-
const result = await context.client.media.
|
|
923
|
-
|
|
508
|
+
if (group === "media" && action === "overlay" && subaction === "update") {
|
|
509
|
+
const result = await context.client.media.updateOverlay({
|
|
510
|
+
projectId: requireStringFlag({
|
|
924
511
|
flags: context.args.flags,
|
|
925
|
-
name: "
|
|
512
|
+
name: "project-id",
|
|
513
|
+
}),
|
|
514
|
+
timelineItemId: requireStringFlag({
|
|
515
|
+
flags: context.args.flags,
|
|
516
|
+
name: "timeline-item-id",
|
|
517
|
+
}),
|
|
518
|
+
positionPreset: parseMediaPositionPreset({
|
|
519
|
+
value: getStringFlag({
|
|
520
|
+
flags: context.args.flags,
|
|
521
|
+
name: "position-preset",
|
|
522
|
+
}),
|
|
523
|
+
}),
|
|
524
|
+
positionX: getNumberFlag({
|
|
525
|
+
flags: context.args.flags,
|
|
526
|
+
name: "position-x",
|
|
527
|
+
}),
|
|
528
|
+
positionY: getNumberFlag({
|
|
529
|
+
flags: context.args.flags,
|
|
530
|
+
name: "position-y",
|
|
531
|
+
}),
|
|
532
|
+
widthPx: getNumberFlag({
|
|
533
|
+
flags: context.args.flags,
|
|
534
|
+
name: "width-px",
|
|
535
|
+
}),
|
|
536
|
+
heightPx: getNumberFlag({
|
|
537
|
+
flags: context.args.flags,
|
|
538
|
+
name: "height-px",
|
|
539
|
+
}),
|
|
540
|
+
scale: getNumberFlag({ flags: context.args.flags, name: "scale" }),
|
|
541
|
+
animationPreset: parseMediaAnimationPreset({
|
|
542
|
+
value: getStringFlag({
|
|
543
|
+
flags: context.args.flags,
|
|
544
|
+
name: "animation-preset",
|
|
545
|
+
}),
|
|
546
|
+
}),
|
|
547
|
+
removeAnimations: getOptionalBooleanFlag({
|
|
548
|
+
flags: context.args.flags,
|
|
549
|
+
name: "remove-animations",
|
|
550
|
+
}),
|
|
551
|
+
blurredBackground: getOptionalBooleanFlag({
|
|
552
|
+
flags: context.args.flags,
|
|
553
|
+
name: "blurred-background",
|
|
926
554
|
}),
|
|
927
555
|
});
|
|
928
556
|
printResult({
|
|
929
557
|
json: context.json,
|
|
930
558
|
value: result,
|
|
931
|
-
human: result.
|
|
559
|
+
human: [result.message],
|
|
932
560
|
});
|
|
933
561
|
return;
|
|
934
562
|
}
|
|
935
|
-
if (group === "media" && action === "
|
|
936
|
-
const result = await
|
|
937
|
-
name: requireStringFlag({ flags: context.args.flags, name: "name" }),
|
|
938
|
-
parentFolderId: parseFolderIdFlag({
|
|
939
|
-
flags: context.args.flags,
|
|
940
|
-
name: "parent-folder-id",
|
|
941
|
-
}),
|
|
942
|
-
description: parseNullableStringFlag({
|
|
943
|
-
flags: context.args.flags,
|
|
944
|
-
name: "description",
|
|
945
|
-
}),
|
|
946
|
-
iconType: getStringFlag({ flags: context.args.flags, name: "icon-type" }),
|
|
947
|
-
});
|
|
563
|
+
if (group === "media" && action === "wait") {
|
|
564
|
+
const result = await waitForMediaAsset({ context });
|
|
948
565
|
printResult({
|
|
949
566
|
json: context.json,
|
|
950
567
|
value: result,
|
|
951
|
-
human: [
|
|
568
|
+
human: [
|
|
569
|
+
`Media ready: ${result.asset.id} ${result.asset.name} (${result.ready_for.join(", ")})`,
|
|
570
|
+
`Processing: ${result.asset.processingStatus ?? "unknown"}${result.asset.processingSubstage ? `/${result.asset.processingSubstage}` : ""}`,
|
|
571
|
+
...(result.asset.processingError
|
|
572
|
+
? [`Processing error: ${result.asset.processingError}`]
|
|
573
|
+
: []),
|
|
574
|
+
],
|
|
952
575
|
});
|
|
953
576
|
return;
|
|
954
577
|
}
|
|
955
|
-
if (group === "media" && action === "
|
|
956
|
-
const result = await context.client.media.
|
|
957
|
-
|
|
578
|
+
if (group === "media" && action === "shorts" && subaction === "list") {
|
|
579
|
+
const result = await context.client.media.shorts.list({
|
|
580
|
+
assetId: requireStringFlag({
|
|
958
581
|
flags: context.args.flags,
|
|
959
|
-
name: "
|
|
582
|
+
name: "asset-id",
|
|
583
|
+
}),
|
|
584
|
+
});
|
|
585
|
+
printResult({
|
|
586
|
+
json: context.json,
|
|
587
|
+
value: result,
|
|
588
|
+
human: [
|
|
589
|
+
`Saved: ${result.saved.length}`,
|
|
590
|
+
`Drafts: ${result.drafts.length}`,
|
|
591
|
+
...[...result.saved, ...result.drafts].map((suggestion) => JSON.stringify(suggestion)),
|
|
592
|
+
],
|
|
593
|
+
});
|
|
594
|
+
return;
|
|
595
|
+
}
|
|
596
|
+
if (group === "media" && action === "shorts" && subaction === "generate") {
|
|
597
|
+
const result = await context.client.media.shorts.generate({
|
|
598
|
+
assetId: requireStringFlag({
|
|
599
|
+
flags: context.args.flags,
|
|
600
|
+
name: "asset-id",
|
|
601
|
+
}),
|
|
602
|
+
contentProfile: parseShortContentProfile({
|
|
603
|
+
value: getStringFlag({
|
|
604
|
+
flags: context.args.flags,
|
|
605
|
+
name: "content-profile",
|
|
606
|
+
}),
|
|
607
|
+
}),
|
|
608
|
+
autoTrimSilence: getOptionalBooleanFlag({
|
|
609
|
+
flags: context.args.flags,
|
|
610
|
+
name: "auto-trim-silence",
|
|
611
|
+
}),
|
|
612
|
+
});
|
|
613
|
+
printResult({
|
|
614
|
+
json: context.json,
|
|
615
|
+
value: result,
|
|
616
|
+
human: [`Short suggestion generation queued: ${result.run_id}`],
|
|
617
|
+
});
|
|
618
|
+
return;
|
|
619
|
+
}
|
|
620
|
+
if (group === "media" && action === "shorts" && subaction === "apply") {
|
|
621
|
+
const result = await context.client.media.shorts.apply({
|
|
622
|
+
assetId: requireStringFlag({
|
|
623
|
+
flags: context.args.flags,
|
|
624
|
+
name: "asset-id",
|
|
625
|
+
}),
|
|
626
|
+
suggestionId: requireStringFlag({
|
|
627
|
+
flags: context.args.flags,
|
|
628
|
+
name: "suggestion-id",
|
|
629
|
+
}),
|
|
630
|
+
projectId: requireStringFlag({
|
|
631
|
+
flags: context.args.flags,
|
|
632
|
+
name: "project-id",
|
|
633
|
+
}),
|
|
634
|
+
captionStyleId: parseNullableStringFlag({
|
|
635
|
+
flags: context.args.flags,
|
|
636
|
+
name: "caption-style-id",
|
|
637
|
+
}),
|
|
638
|
+
markSaved: getOptionalBooleanFlag({
|
|
639
|
+
flags: context.args.flags,
|
|
640
|
+
name: "mark-saved",
|
|
641
|
+
}),
|
|
642
|
+
allowUnverifiedExport: getOptionalBooleanFlag({
|
|
643
|
+
flags: context.args.flags,
|
|
644
|
+
name: "allow-unverified-export",
|
|
645
|
+
}),
|
|
646
|
+
automaticLayout: parseJsonObjectFlag({
|
|
647
|
+
flags: context.args.flags,
|
|
648
|
+
name: "automatic-layout-json",
|
|
649
|
+
}),
|
|
650
|
+
});
|
|
651
|
+
printResult({
|
|
652
|
+
json: context.json,
|
|
653
|
+
value: result,
|
|
654
|
+
human: [
|
|
655
|
+
`Short applied: ${result.timeline_item_id} in project ${result.project_id}`,
|
|
656
|
+
],
|
|
657
|
+
});
|
|
658
|
+
return;
|
|
659
|
+
}
|
|
660
|
+
if (group === "clips" && action === "create") {
|
|
661
|
+
const result = await context.client.clips.create(toCreateClipOptions({ flags: context.args.flags }));
|
|
662
|
+
printClipResult({ context, result });
|
|
663
|
+
return;
|
|
664
|
+
}
|
|
665
|
+
if (group === "clips" && action === "get") {
|
|
666
|
+
const result = await context.client.clips.get({
|
|
667
|
+
clipId: requireStringFlag({
|
|
668
|
+
flags: context.args.flags,
|
|
669
|
+
name: "clip-id",
|
|
670
|
+
}),
|
|
671
|
+
});
|
|
672
|
+
printClipResult({ context, result });
|
|
673
|
+
return;
|
|
674
|
+
}
|
|
675
|
+
if (group === "clips" && action === "wait") {
|
|
676
|
+
const result = await waitForClip({ context });
|
|
677
|
+
printClipResult({ context, result });
|
|
678
|
+
return;
|
|
679
|
+
}
|
|
680
|
+
if (group === "clips" && action === "reselect") {
|
|
681
|
+
const result = await context.client.clips.reselect({
|
|
682
|
+
clipId: requireStringFlag({
|
|
683
|
+
flags: context.args.flags,
|
|
684
|
+
name: "clip-id",
|
|
685
|
+
}),
|
|
686
|
+
selection: buildClipSelection({ flags: context.args.flags }),
|
|
687
|
+
target: buildClipTarget({
|
|
688
|
+
flags: context.args.flags,
|
|
689
|
+
requireMaxDuration: false,
|
|
690
|
+
}),
|
|
691
|
+
});
|
|
692
|
+
printClipResult({ context, result });
|
|
693
|
+
return;
|
|
694
|
+
}
|
|
695
|
+
if (group === "clips" && action === "repair") {
|
|
696
|
+
const result = await context.client.clips.repair({
|
|
697
|
+
clipId: requireStringFlag({
|
|
698
|
+
flags: context.args.flags,
|
|
699
|
+
name: "clip-id",
|
|
700
|
+
}),
|
|
701
|
+
repairMode: parseClipRepairMode({
|
|
702
|
+
value: getStringFlag({
|
|
703
|
+
flags: context.args.flags,
|
|
704
|
+
name: "repair-mode",
|
|
705
|
+
}),
|
|
706
|
+
}),
|
|
707
|
+
});
|
|
708
|
+
printClipResult({ context, result });
|
|
709
|
+
return;
|
|
710
|
+
}
|
|
711
|
+
if (group === "clips" && action === "export") {
|
|
712
|
+
const result = await context.client.clips.export({
|
|
713
|
+
clipId: requireStringFlag({
|
|
714
|
+
flags: context.args.flags,
|
|
715
|
+
name: "clip-id",
|
|
716
|
+
}),
|
|
717
|
+
resolution: parseClipExportResolution({
|
|
718
|
+
value: getStringFlag({ flags: context.args.flags, name: "resolution" }),
|
|
719
|
+
}),
|
|
720
|
+
format: parseClipExportFormat({
|
|
721
|
+
value: getStringFlag({ flags: context.args.flags, name: "format" }),
|
|
722
|
+
}),
|
|
723
|
+
allowBlockingQaBypass: getOptionalBooleanFlag({
|
|
724
|
+
flags: context.args.flags,
|
|
725
|
+
name: "allow-blocking-qa-bypass",
|
|
726
|
+
}) ?? false,
|
|
727
|
+
});
|
|
728
|
+
printClipResult({ context, result });
|
|
729
|
+
return;
|
|
730
|
+
}
|
|
731
|
+
if (group === "media" && action === "folders" && subaction === "list") {
|
|
732
|
+
const result = await context.client.media.folders.list({
|
|
733
|
+
parentFolderId: getStringFlag({
|
|
734
|
+
flags: context.args.flags,
|
|
735
|
+
name: "parent-folder-id",
|
|
736
|
+
}),
|
|
737
|
+
});
|
|
738
|
+
printResult({
|
|
739
|
+
json: context.json,
|
|
740
|
+
value: result,
|
|
741
|
+
human: result.folders.map((folder) => `${folder.id} ${folder.name} (${folder.assetCount ?? 0} assets)`),
|
|
742
|
+
});
|
|
743
|
+
return;
|
|
744
|
+
}
|
|
745
|
+
if (group === "media" && action === "folders" && subaction === "create") {
|
|
746
|
+
const result = await context.client.media.folders.create({
|
|
747
|
+
name: requireStringFlag({ flags: context.args.flags, name: "name" }),
|
|
748
|
+
parentFolderId: parseFolderIdFlag({
|
|
749
|
+
flags: context.args.flags,
|
|
750
|
+
name: "parent-folder-id",
|
|
751
|
+
}),
|
|
752
|
+
description: parseNullableStringFlag({
|
|
753
|
+
flags: context.args.flags,
|
|
754
|
+
name: "description",
|
|
755
|
+
}),
|
|
756
|
+
iconType: getStringFlag({ flags: context.args.flags, name: "icon-type" }),
|
|
757
|
+
});
|
|
758
|
+
printResult({
|
|
759
|
+
json: context.json,
|
|
760
|
+
value: result,
|
|
761
|
+
human: [`Folder created: ${result.folder.id} ${result.folder.name}`],
|
|
762
|
+
});
|
|
763
|
+
return;
|
|
764
|
+
}
|
|
765
|
+
if (group === "media" && action === "folders" && subaction === "rename") {
|
|
766
|
+
const result = await context.client.media.folders.update({
|
|
767
|
+
folderId: requireStringFlag({
|
|
768
|
+
flags: context.args.flags,
|
|
769
|
+
name: "folder-id",
|
|
960
770
|
}),
|
|
961
771
|
name: requireStringFlag({ flags: context.args.flags, name: "name" }),
|
|
962
772
|
parentFolderId: undefined,
|
|
@@ -995,7 +805,12 @@ async function runCommand({ context }) {
|
|
|
995
805
|
return;
|
|
996
806
|
}
|
|
997
807
|
if (group === "media" && action === "folders" && subaction === "delete") {
|
|
998
|
-
|
|
808
|
+
requireDeleteConfirmation({
|
|
809
|
+
confirmed: getBooleanFlag({
|
|
810
|
+
flags: context.args.flags,
|
|
811
|
+
name: "confirm-delete",
|
|
812
|
+
}),
|
|
813
|
+
});
|
|
999
814
|
const result = await context.client.media.folders.delete({
|
|
1000
815
|
folderId: requireStringFlag({
|
|
1001
816
|
flags: context.args.flags,
|
|
@@ -1042,7 +857,10 @@ async function runCommand({ context }) {
|
|
|
1042
857
|
printResult({
|
|
1043
858
|
json: context.json,
|
|
1044
859
|
value: result,
|
|
1045
|
-
human: [
|
|
860
|
+
human: [
|
|
861
|
+
...timelineLayerModelHelp(),
|
|
862
|
+
JSON.stringify(result.data, null, 2),
|
|
863
|
+
],
|
|
1046
864
|
});
|
|
1047
865
|
return;
|
|
1048
866
|
}
|
|
@@ -1203,6 +1021,10 @@ async function runCommand({ context }) {
|
|
|
1203
1021
|
text: getStringFlag({ flags: context.args.flags, name: "text" }),
|
|
1204
1022
|
name: getStringFlag({ flags: context.args.flags, name: "name" }),
|
|
1205
1023
|
spec,
|
|
1024
|
+
durationSeconds: getNumberFlag({
|
|
1025
|
+
flags: context.args.flags,
|
|
1026
|
+
name: "duration",
|
|
1027
|
+
}),
|
|
1206
1028
|
});
|
|
1207
1029
|
printResult({
|
|
1208
1030
|
json: context.json,
|
|
@@ -1293,45 +1115,60 @@ async function runCommand({ context }) {
|
|
|
1293
1115
|
});
|
|
1294
1116
|
return;
|
|
1295
1117
|
}
|
|
1296
|
-
if (group === "
|
|
1297
|
-
const
|
|
1118
|
+
if (group === "effects" && action === "zoom" && subaction === "add") {
|
|
1119
|
+
const durationSeconds = getNumberFlag({
|
|
1120
|
+
flags: context.args.flags,
|
|
1121
|
+
name: "duration",
|
|
1122
|
+
});
|
|
1123
|
+
const result = await context.client.effects.zoom.add({
|
|
1298
1124
|
projectId: requireStringFlag({
|
|
1299
1125
|
flags: context.args.flags,
|
|
1300
1126
|
name: "project-id",
|
|
1301
1127
|
}),
|
|
1302
|
-
|
|
1128
|
+
timelineItemId: getStringFlag({
|
|
1303
1129
|
flags: context.args.flags,
|
|
1304
|
-
name: "
|
|
1130
|
+
name: "timeline-item-id",
|
|
1305
1131
|
}),
|
|
1306
|
-
|
|
1307
|
-
|
|
1132
|
+
contentItemId: getStringFlag({
|
|
1133
|
+
flags: context.args.flags,
|
|
1134
|
+
name: "content-item-id",
|
|
1308
1135
|
}),
|
|
1309
|
-
|
|
1136
|
+
effectId: getStringFlag({
|
|
1310
1137
|
flags: context.args.flags,
|
|
1311
|
-
name: "
|
|
1138
|
+
name: "effect-id",
|
|
1312
1139
|
}),
|
|
1313
|
-
|
|
1314
|
-
|
|
1140
|
+
name: getStringFlag({ flags: context.args.flags, name: "name" }),
|
|
1141
|
+
startTimeSeconds: getTimelineStartFlag({ flags: context.args.flags }),
|
|
1142
|
+
durationSeconds,
|
|
1143
|
+
easing: parseEffectEasing({
|
|
1144
|
+
value: getStringFlag({ flags: context.args.flags, name: "easing" }),
|
|
1145
|
+
}),
|
|
1146
|
+
layerIndex: getNumberFlag({
|
|
1315
1147
|
flags: context.args.flags,
|
|
1316
|
-
name: "
|
|
1148
|
+
name: "layer-index",
|
|
1317
1149
|
}),
|
|
1318
|
-
|
|
1150
|
+
orderIndex: getNumberFlag({
|
|
1319
1151
|
flags: context.args.flags,
|
|
1320
|
-
name: "
|
|
1152
|
+
name: "order-index",
|
|
1153
|
+
}),
|
|
1154
|
+
settings: buildZoomEffectSettings({
|
|
1155
|
+
flags: context.args.flags,
|
|
1156
|
+
durationSeconds,
|
|
1321
1157
|
}),
|
|
1322
|
-
loop: getOptionalBooleanFlag({ flags: context.args.flags, name: "loop" }),
|
|
1323
1158
|
});
|
|
1324
1159
|
printResult({
|
|
1325
1160
|
json: context.json,
|
|
1326
1161
|
value: result,
|
|
1327
|
-
human: [
|
|
1328
|
-
`Inserted ${result.summary.inserted}/${result.summary.total} audio item(s).`,
|
|
1329
|
-
],
|
|
1162
|
+
human: [`Zoom effect added: ${result.timeline_item_id}`],
|
|
1330
1163
|
});
|
|
1331
1164
|
return;
|
|
1332
1165
|
}
|
|
1333
|
-
if (group === "
|
|
1334
|
-
const
|
|
1166
|
+
if (group === "effects" && action === "zoom" && subaction === "update") {
|
|
1167
|
+
const durationSeconds = getNumberFlag({
|
|
1168
|
+
flags: context.args.flags,
|
|
1169
|
+
name: "duration",
|
|
1170
|
+
});
|
|
1171
|
+
const result = await context.client.effects.zoom.update({
|
|
1335
1172
|
projectId: requireStringFlag({
|
|
1336
1173
|
flags: context.args.flags,
|
|
1337
1174
|
name: "project-id",
|
|
@@ -1340,83 +1177,71 @@ async function runCommand({ context }) {
|
|
|
1340
1177
|
flags: context.args.flags,
|
|
1341
1178
|
name: "timeline-item-id",
|
|
1342
1179
|
}),
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
name: "
|
|
1346
|
-
}),
|
|
1347
|
-
silenceThresholdDb: getNumberFlag({
|
|
1348
|
-
flags: context.args.flags,
|
|
1349
|
-
name: "silence-threshold-db",
|
|
1350
|
-
}),
|
|
1351
|
-
minSilenceDurationSeconds: getNumberFlag({
|
|
1352
|
-
flags: context.args.flags,
|
|
1353
|
-
name: "min-silence-duration",
|
|
1354
|
-
}),
|
|
1355
|
-
removeBlipsMaxDurationSeconds: getNumberFlag({
|
|
1356
|
-
flags: context.args.flags,
|
|
1357
|
-
name: "remove-blips-max-duration",
|
|
1180
|
+
durationSeconds,
|
|
1181
|
+
easing: parseEffectEasing({
|
|
1182
|
+
value: getStringFlag({ flags: context.args.flags, name: "easing" }),
|
|
1358
1183
|
}),
|
|
1359
|
-
|
|
1184
|
+
settings: buildZoomEffectSettings({
|
|
1360
1185
|
flags: context.args.flags,
|
|
1361
|
-
|
|
1186
|
+
durationSeconds,
|
|
1362
1187
|
}),
|
|
1363
1188
|
});
|
|
1364
1189
|
printResult({
|
|
1365
1190
|
json: context.json,
|
|
1366
1191
|
value: result,
|
|
1367
|
-
human: [
|
|
1192
|
+
human: [`Zoom effect updated: ${result.timeline_item_id}`],
|
|
1368
1193
|
});
|
|
1369
1194
|
return;
|
|
1370
1195
|
}
|
|
1371
|
-
if (group === "
|
|
1372
|
-
const result = await context.client.
|
|
1196
|
+
if (group === "effects" && action === "mask" && subaction === "add") {
|
|
1197
|
+
const result = await context.client.effects.mask.add({
|
|
1373
1198
|
projectId: requireStringFlag({
|
|
1374
1199
|
flags: context.args.flags,
|
|
1375
1200
|
name: "project-id",
|
|
1376
1201
|
}),
|
|
1377
|
-
timelineItemId:
|
|
1202
|
+
timelineItemId: getStringFlag({
|
|
1378
1203
|
flags: context.args.flags,
|
|
1379
1204
|
name: "timeline-item-id",
|
|
1380
1205
|
}),
|
|
1381
|
-
|
|
1206
|
+
contentItemId: getStringFlag({
|
|
1382
1207
|
flags: context.args.flags,
|
|
1383
|
-
name: "
|
|
1208
|
+
name: "content-item-id",
|
|
1384
1209
|
}),
|
|
1385
|
-
|
|
1210
|
+
effectId: getStringFlag({
|
|
1386
1211
|
flags: context.args.flags,
|
|
1387
|
-
name: "
|
|
1212
|
+
name: "effect-id",
|
|
1388
1213
|
}),
|
|
1389
|
-
|
|
1214
|
+
name: getStringFlag({ flags: context.args.flags, name: "name" }),
|
|
1215
|
+
startTimeSeconds: getTimelineStartFlag({ flags: context.args.flags }),
|
|
1216
|
+
durationSeconds: getNumberFlag({
|
|
1390
1217
|
flags: context.args.flags,
|
|
1391
|
-
name: "
|
|
1218
|
+
name: "duration",
|
|
1392
1219
|
}),
|
|
1393
|
-
|
|
1394
|
-
flags: context.args.flags,
|
|
1395
|
-
name: "remove-blips-max-duration",
|
|
1220
|
+
easing: parseEffectEasing({
|
|
1221
|
+
value: getStringFlag({ flags: context.args.flags, name: "easing" }),
|
|
1396
1222
|
}),
|
|
1397
|
-
|
|
1223
|
+
layerIndex: getNumberFlag({
|
|
1398
1224
|
flags: context.args.flags,
|
|
1399
|
-
name: "
|
|
1225
|
+
name: "layer-index",
|
|
1400
1226
|
}),
|
|
1401
|
-
|
|
1227
|
+
orderIndex: getNumberFlag({
|
|
1402
1228
|
flags: context.args.flags,
|
|
1403
|
-
name: "
|
|
1229
|
+
name: "order-index",
|
|
1404
1230
|
}),
|
|
1405
|
-
|
|
1231
|
+
settings: parseJsonObjectFlag({
|
|
1406
1232
|
flags: context.args.flags,
|
|
1407
|
-
name: "
|
|
1233
|
+
name: "settings-json",
|
|
1408
1234
|
}),
|
|
1409
|
-
previewConfirmed: requireConfirmPreview({ flags: context.args.flags }),
|
|
1410
1235
|
});
|
|
1411
1236
|
printResult({
|
|
1412
1237
|
json: context.json,
|
|
1413
1238
|
value: result,
|
|
1414
|
-
human: [
|
|
1239
|
+
human: [`Mask effect added: ${result.timeline_item_id}`],
|
|
1415
1240
|
});
|
|
1416
1241
|
return;
|
|
1417
1242
|
}
|
|
1418
|
-
if (group === "
|
|
1419
|
-
const result = await context.client.
|
|
1243
|
+
if (group === "effects" && action === "mask" && subaction === "update") {
|
|
1244
|
+
const result = await context.client.effects.mask.update({
|
|
1420
1245
|
projectId: requireStringFlag({
|
|
1421
1246
|
flags: context.args.flags,
|
|
1422
1247
|
name: "project-id",
|
|
@@ -1425,450 +1250,2566 @@ async function runCommand({ context }) {
|
|
|
1425
1250
|
flags: context.args.flags,
|
|
1426
1251
|
name: "timeline-item-id",
|
|
1427
1252
|
}),
|
|
1253
|
+
durationSeconds: getNumberFlag({
|
|
1254
|
+
flags: context.args.flags,
|
|
1255
|
+
name: "duration",
|
|
1256
|
+
}),
|
|
1257
|
+
easing: parseEffectEasing({
|
|
1258
|
+
value: getStringFlag({ flags: context.args.flags, name: "easing" }),
|
|
1259
|
+
}),
|
|
1260
|
+
settings: parseJsonObjectFlag({
|
|
1261
|
+
flags: context.args.flags,
|
|
1262
|
+
name: "settings-json",
|
|
1263
|
+
}),
|
|
1428
1264
|
});
|
|
1429
1265
|
printResult({
|
|
1430
1266
|
json: context.json,
|
|
1431
1267
|
value: result,
|
|
1432
|
-
human: [
|
|
1268
|
+
human: [`Mask effect updated: ${result.timeline_item_id}`],
|
|
1433
1269
|
});
|
|
1434
1270
|
return;
|
|
1435
1271
|
}
|
|
1436
|
-
if (group === "
|
|
1437
|
-
const result = await context.client.
|
|
1272
|
+
if (group === "effects" && action === "colorgrade" && subaction === "add") {
|
|
1273
|
+
const result = await context.client.effects.colorgrade.add({
|
|
1438
1274
|
projectId: requireStringFlag({
|
|
1439
1275
|
flags: context.args.flags,
|
|
1440
1276
|
name: "project-id",
|
|
1441
1277
|
}),
|
|
1442
|
-
timelineItemId:
|
|
1278
|
+
timelineItemId: getStringFlag({
|
|
1443
1279
|
flags: context.args.flags,
|
|
1444
1280
|
name: "timeline-item-id",
|
|
1445
1281
|
}),
|
|
1446
|
-
|
|
1282
|
+
contentItemId: getStringFlag({
|
|
1447
1283
|
flags: context.args.flags,
|
|
1448
|
-
name: "
|
|
1284
|
+
name: "content-item-id",
|
|
1449
1285
|
}),
|
|
1450
|
-
|
|
1286
|
+
effectId: getStringFlag({
|
|
1451
1287
|
flags: context.args.flags,
|
|
1452
|
-
name: "
|
|
1288
|
+
name: "effect-id",
|
|
1453
1289
|
}),
|
|
1454
|
-
|
|
1290
|
+
name: getStringFlag({ flags: context.args.flags, name: "name" }),
|
|
1291
|
+
startTimeSeconds: getTimelineStartFlag({ flags: context.args.flags }),
|
|
1292
|
+
durationSeconds: getNumberFlag({
|
|
1455
1293
|
flags: context.args.flags,
|
|
1456
|
-
name: "
|
|
1294
|
+
name: "duration",
|
|
1295
|
+
}),
|
|
1296
|
+
easing: parseEffectEasing({
|
|
1297
|
+
value: getStringFlag({ flags: context.args.flags, name: "easing" }),
|
|
1298
|
+
}),
|
|
1299
|
+
layerIndex: getNumberFlag({
|
|
1300
|
+
flags: context.args.flags,
|
|
1301
|
+
name: "layer-index",
|
|
1302
|
+
}),
|
|
1303
|
+
orderIndex: getNumberFlag({
|
|
1304
|
+
flags: context.args.flags,
|
|
1305
|
+
name: "order-index",
|
|
1306
|
+
}),
|
|
1307
|
+
settings: parseJsonObjectFlag({
|
|
1308
|
+
flags: context.args.flags,
|
|
1309
|
+
name: "settings-json",
|
|
1457
1310
|
}),
|
|
1458
|
-
previewConfirmed: requireConfirmPreview({ flags: context.args.flags }),
|
|
1459
1311
|
});
|
|
1460
1312
|
printResult({
|
|
1461
1313
|
json: context.json,
|
|
1462
1314
|
value: result,
|
|
1463
|
-
human: [
|
|
1315
|
+
human: [`Color grade effect added: ${result.timeline_item_id}`],
|
|
1464
1316
|
});
|
|
1465
1317
|
return;
|
|
1466
1318
|
}
|
|
1467
|
-
if (group === "
|
|
1468
|
-
|
|
1319
|
+
if (group === "effects" &&
|
|
1320
|
+
action === "colorgrade" &&
|
|
1321
|
+
subaction === "update") {
|
|
1322
|
+
const result = await context.client.effects.colorgrade.update({
|
|
1469
1323
|
projectId: requireStringFlag({
|
|
1470
1324
|
flags: context.args.flags,
|
|
1471
1325
|
name: "project-id",
|
|
1472
1326
|
}),
|
|
1473
|
-
timelineItemId:
|
|
1327
|
+
timelineItemId: requireStringFlag({
|
|
1474
1328
|
flags: context.args.flags,
|
|
1475
1329
|
name: "timeline-item-id",
|
|
1476
1330
|
}),
|
|
1477
|
-
|
|
1331
|
+
durationSeconds: getNumberFlag({
|
|
1478
1332
|
flags: context.args.flags,
|
|
1479
|
-
name: "
|
|
1333
|
+
name: "duration",
|
|
1334
|
+
}),
|
|
1335
|
+
easing: parseEffectEasing({
|
|
1336
|
+
value: getStringFlag({ flags: context.args.flags, name: "easing" }),
|
|
1337
|
+
}),
|
|
1338
|
+
settings: parseJsonObjectFlag({
|
|
1339
|
+
flags: context.args.flags,
|
|
1340
|
+
name: "settings-json",
|
|
1480
1341
|
}),
|
|
1481
|
-
limit: getNumberFlag({ flags: context.args.flags, name: "limit" }),
|
|
1482
|
-
offset: getNumberFlag({ flags: context.args.flags, name: "offset" }),
|
|
1483
1342
|
});
|
|
1484
1343
|
printResult({
|
|
1485
1344
|
json: context.json,
|
|
1486
1345
|
value: result,
|
|
1487
|
-
human:
|
|
1346
|
+
human: [`Color grade effect updated: ${result.timeline_item_id}`],
|
|
1488
1347
|
});
|
|
1489
1348
|
return;
|
|
1490
1349
|
}
|
|
1491
|
-
if (group === "
|
|
1492
|
-
const result = await context.client.
|
|
1350
|
+
if (group === "audio" && action === "add") {
|
|
1351
|
+
const result = await context.client.audio.add({
|
|
1493
1352
|
projectId: requireStringFlag({
|
|
1494
1353
|
flags: context.args.flags,
|
|
1495
1354
|
name: "project-id",
|
|
1496
1355
|
}),
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1356
|
+
assetId: requireStringFlag({
|
|
1357
|
+
flags: context.args.flags,
|
|
1358
|
+
name: "asset-id",
|
|
1359
|
+
}),
|
|
1360
|
+
startSeconds: parseTimecode({
|
|
1361
|
+
value: getStringFlag({ flags: context.args.flags, name: "at" }) ?? "0",
|
|
1362
|
+
}),
|
|
1363
|
+
durationSeconds: getNumberFlag({
|
|
1364
|
+
flags: context.args.flags,
|
|
1365
|
+
name: "duration",
|
|
1366
|
+
}),
|
|
1367
|
+
volume: getNumberFlag({ flags: context.args.flags, name: "volume" }),
|
|
1368
|
+
fadeInSeconds: getNumberFlag({
|
|
1369
|
+
flags: context.args.flags,
|
|
1370
|
+
name: "fade-in",
|
|
1371
|
+
}),
|
|
1372
|
+
fadeOutSeconds: getNumberFlag({
|
|
1373
|
+
flags: context.args.flags,
|
|
1374
|
+
name: "fade-out",
|
|
1375
|
+
}),
|
|
1376
|
+
loop: getOptionalBooleanFlag({ flags: context.args.flags, name: "loop" }),
|
|
1500
1377
|
});
|
|
1501
1378
|
printResult({
|
|
1502
1379
|
json: context.json,
|
|
1503
1380
|
value: result,
|
|
1504
1381
|
human: [
|
|
1505
|
-
`
|
|
1382
|
+
`Inserted ${result.summary.inserted}/${result.summary.total} audio item(s).`,
|
|
1506
1383
|
],
|
|
1507
1384
|
});
|
|
1508
1385
|
return;
|
|
1509
1386
|
}
|
|
1510
|
-
if (group === "
|
|
1511
|
-
|
|
1512
|
-
subaction === "batch-text") {
|
|
1513
|
-
const result = await context.client.captions.batchUpdateWordText({
|
|
1387
|
+
if (group === "audio" && action === "update") {
|
|
1388
|
+
const result = await context.client.audio.update({
|
|
1514
1389
|
projectId: requireStringFlag({
|
|
1515
1390
|
flags: context.args.flags,
|
|
1516
1391
|
name: "project-id",
|
|
1517
1392
|
}),
|
|
1518
|
-
|
|
1393
|
+
timelineItemId: requireStringFlag({
|
|
1519
1394
|
flags: context.args.flags,
|
|
1520
|
-
name: "
|
|
1395
|
+
name: "timeline-item-id",
|
|
1521
1396
|
}),
|
|
1522
|
-
|
|
1523
|
-
printResult({
|
|
1524
|
-
json: context.json,
|
|
1525
|
-
value: result,
|
|
1526
|
-
human: [result.message],
|
|
1527
|
-
});
|
|
1528
|
-
return;
|
|
1529
|
-
}
|
|
1530
|
-
if (group === "captions" && action === "words" && subaction === "style") {
|
|
1531
|
-
const styleJson = parseNullableJsonObjectFlag({
|
|
1532
|
-
flags: context.args.flags,
|
|
1533
|
-
name: "style-json",
|
|
1534
|
-
});
|
|
1535
|
-
const style = styleJson === undefined
|
|
1536
|
-
? buildCaptionWordStyleFromFlags({ flags: context.args.flags })
|
|
1537
|
-
: styleJson;
|
|
1538
|
-
const result = await context.client.captions.setWordStyle({
|
|
1539
|
-
...buildCaptionWordSelector({ flags: context.args.flags }),
|
|
1540
|
-
style,
|
|
1541
|
-
clearExisting: getOptionalBooleanFlag({
|
|
1397
|
+
durationSeconds: getNumberFlag({
|
|
1542
1398
|
flags: context.args.flags,
|
|
1543
|
-
name: "
|
|
1399
|
+
name: "duration",
|
|
1400
|
+
}),
|
|
1401
|
+
volume: getNumberFlag({ flags: context.args.flags, name: "volume" }),
|
|
1402
|
+
fadeInSeconds: getNumberFlag({
|
|
1403
|
+
flags: context.args.flags,
|
|
1404
|
+
name: "fade-in",
|
|
1405
|
+
}),
|
|
1406
|
+
fadeOutSeconds: getNumberFlag({
|
|
1407
|
+
flags: context.args.flags,
|
|
1408
|
+
name: "fade-out",
|
|
1544
1409
|
}),
|
|
1545
1410
|
});
|
|
1546
1411
|
printResult({
|
|
1547
1412
|
json: context.json,
|
|
1548
1413
|
value: result,
|
|
1549
|
-
human: [`
|
|
1414
|
+
human: [`Audio updated: ${result.timeline_item_id}`],
|
|
1550
1415
|
});
|
|
1551
1416
|
return;
|
|
1552
1417
|
}
|
|
1553
|
-
if (group === "
|
|
1554
|
-
const result = await context.client.
|
|
1555
|
-
|
|
1556
|
-
|
|
1418
|
+
if (group === "audio" && action === "search") {
|
|
1419
|
+
const result = await context.client.audioLibrary.search({
|
|
1420
|
+
query: requireStringFlag({ flags: context.args.flags, name: "query" }),
|
|
1421
|
+
provider: parseAudioLibraryProviderFilter({
|
|
1422
|
+
value: getStringFlag({ flags: context.args.flags, name: "provider" }),
|
|
1423
|
+
}),
|
|
1424
|
+
license: parseAudioLibraryLicense({
|
|
1425
|
+
value: getStringFlag({ flags: context.args.flags, name: "license" }),
|
|
1426
|
+
}),
|
|
1427
|
+
audioType: parseAudioLibraryType({
|
|
1428
|
+
value: getStringFlag({ flags: context.args.flags, name: "audio-type" }),
|
|
1429
|
+
}),
|
|
1430
|
+
minDurationSeconds: getNumberFlag({
|
|
1557
1431
|
flags: context.args.flags,
|
|
1558
|
-
name: "
|
|
1559
|
-
})
|
|
1432
|
+
name: "min-duration",
|
|
1433
|
+
}),
|
|
1434
|
+
maxDurationSeconds: getNumberFlag({
|
|
1435
|
+
flags: context.args.flags,
|
|
1436
|
+
name: "max-duration",
|
|
1437
|
+
}),
|
|
1438
|
+
limit: getNumberFlag({ flags: context.args.flags, name: "limit" }),
|
|
1439
|
+
page: getNumberFlag({ flags: context.args.flags, name: "page" }),
|
|
1560
1440
|
});
|
|
1561
1441
|
printResult({
|
|
1562
1442
|
json: context.json,
|
|
1563
1443
|
value: result,
|
|
1564
|
-
human: [
|
|
1444
|
+
human: [
|
|
1445
|
+
...result.warnings.map((warning) => `Warning: ${warning}`),
|
|
1446
|
+
...result.items.map((item) => `${item.provider}:${item.external_id} ${item.title} ${item.duration_seconds ?? "-"}s ${item.license_code}`),
|
|
1447
|
+
],
|
|
1565
1448
|
});
|
|
1566
1449
|
return;
|
|
1567
1450
|
}
|
|
1568
|
-
if (group === "
|
|
1569
|
-
const result = await context.client.
|
|
1570
|
-
|
|
1451
|
+
if (group === "audio" && action === "import") {
|
|
1452
|
+
const result = await context.client.audioLibrary.importAsset({
|
|
1453
|
+
provider: parseAudioLibraryProvider({
|
|
1454
|
+
value: requireStringFlag({
|
|
1455
|
+
flags: context.args.flags,
|
|
1456
|
+
name: "provider",
|
|
1457
|
+
}),
|
|
1458
|
+
}),
|
|
1459
|
+
externalId: requireStringFlag({
|
|
1460
|
+
flags: context.args.flags,
|
|
1461
|
+
name: "external-id",
|
|
1462
|
+
}),
|
|
1463
|
+
folderId: parseFolderIdFlag({
|
|
1464
|
+
flags: context.args.flags,
|
|
1465
|
+
name: "folder-id",
|
|
1466
|
+
}),
|
|
1467
|
+
projectId: getStringFlag({
|
|
1571
1468
|
flags: context.args.flags,
|
|
1572
1469
|
name: "project-id",
|
|
1573
1470
|
}),
|
|
1574
|
-
|
|
1471
|
+
startSeconds: parseTimecode({
|
|
1472
|
+
value: getStringFlag({ flags: context.args.flags, name: "at" }) ?? "0",
|
|
1473
|
+
}),
|
|
1474
|
+
durationSeconds: parseNullableNumberFlag({
|
|
1475
|
+
flags: context.args.flags,
|
|
1476
|
+
name: "duration",
|
|
1477
|
+
}),
|
|
1478
|
+
volume: getNumberFlag({ flags: context.args.flags, name: "volume" }),
|
|
1479
|
+
fadeInSeconds: getNumberFlag({
|
|
1480
|
+
flags: context.args.flags,
|
|
1481
|
+
name: "fade-in",
|
|
1482
|
+
}),
|
|
1483
|
+
fadeOutSeconds: getNumberFlag({
|
|
1484
|
+
flags: context.args.flags,
|
|
1485
|
+
name: "fade-out",
|
|
1486
|
+
}),
|
|
1487
|
+
loop: getOptionalBooleanFlag({ flags: context.args.flags, name: "loop" }),
|
|
1488
|
+
audioType: parseAudioLibraryType({
|
|
1489
|
+
value: getStringFlag({ flags: context.args.flags, name: "audio-type" }),
|
|
1490
|
+
}),
|
|
1575
1491
|
});
|
|
1576
1492
|
printResult({
|
|
1577
1493
|
json: context.json,
|
|
1578
1494
|
value: result,
|
|
1579
|
-
human: [
|
|
1495
|
+
human: [
|
|
1496
|
+
`${result.imported ? "Imported" : "Reused"} ${result.asset.name} (${result.asset.id}).`,
|
|
1497
|
+
result.timeline_item_id
|
|
1498
|
+
? `Inserted on timeline as ${result.timeline_item_id}.`
|
|
1499
|
+
: "No timeline insert requested.",
|
|
1500
|
+
],
|
|
1580
1501
|
});
|
|
1581
1502
|
return;
|
|
1582
1503
|
}
|
|
1583
|
-
if (group === "
|
|
1584
|
-
const result = await context.client.
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1504
|
+
if (group === "stock" && action === "search") {
|
|
1505
|
+
const result = await context.client.stockMedia.search({
|
|
1506
|
+
query: requireStringFlag({ flags: context.args.flags, name: "query" }),
|
|
1507
|
+
assetType: parseStockAssetType({
|
|
1508
|
+
value: requireStringFlag({
|
|
1509
|
+
flags: context.args.flags,
|
|
1510
|
+
name: "asset-type",
|
|
1511
|
+
}),
|
|
1588
1512
|
}),
|
|
1589
|
-
|
|
1590
|
-
|
|
1513
|
+
provider: parseStockProvider({
|
|
1514
|
+
value: getStringFlag({ flags: context.args.flags, name: "provider" }),
|
|
1515
|
+
}),
|
|
1516
|
+
orientation: parseStockOrientation({
|
|
1517
|
+
value: getStringFlag({
|
|
1518
|
+
flags: context.args.flags,
|
|
1519
|
+
name: "orientation",
|
|
1520
|
+
}),
|
|
1521
|
+
}),
|
|
1522
|
+
limit: getNumberFlag({ flags: context.args.flags, name: "limit" }),
|
|
1523
|
+
page: getNumberFlag({ flags: context.args.flags, name: "page" }),
|
|
1591
1524
|
});
|
|
1592
1525
|
printResult({
|
|
1593
1526
|
json: context.json,
|
|
1594
1527
|
value: result,
|
|
1595
|
-
human: [
|
|
1528
|
+
human: [
|
|
1529
|
+
...result.warnings.map((warning) => `Warning: ${warning}`),
|
|
1530
|
+
...result.items.map((item) => `${item.provider}:${item.provider_asset_id} ${item.asset_type} ${item.title} ${item.duration_seconds ?? "-"}s ${item.license_code}`),
|
|
1531
|
+
],
|
|
1596
1532
|
});
|
|
1597
1533
|
return;
|
|
1598
1534
|
}
|
|
1599
|
-
if (group === "
|
|
1600
|
-
const result = await context.client.
|
|
1601
|
-
|
|
1535
|
+
if (group === "stock" && action === "import") {
|
|
1536
|
+
const result = await context.client.stockMedia.importAsset({
|
|
1537
|
+
provider: parseRequiredStockProvider({
|
|
1538
|
+
value: requireStringFlag({
|
|
1539
|
+
flags: context.args.flags,
|
|
1540
|
+
name: "provider",
|
|
1541
|
+
}),
|
|
1542
|
+
}),
|
|
1543
|
+
providerAssetId: requireStringFlag({
|
|
1602
1544
|
flags: context.args.flags,
|
|
1603
|
-
name: "
|
|
1545
|
+
name: "provider-asset-id",
|
|
1546
|
+
}),
|
|
1547
|
+
assetType: parseStockAssetType({
|
|
1548
|
+
value: requireStringFlag({
|
|
1549
|
+
flags: context.args.flags,
|
|
1550
|
+
name: "asset-type",
|
|
1551
|
+
}),
|
|
1552
|
+
}),
|
|
1553
|
+
variantId: parseNullableStringFlag({
|
|
1554
|
+
flags: context.args.flags,
|
|
1555
|
+
name: "variant-id",
|
|
1556
|
+
}),
|
|
1557
|
+
folderId: parseFolderIdFlag({
|
|
1558
|
+
flags: context.args.flags,
|
|
1559
|
+
name: "folder-id",
|
|
1560
|
+
}),
|
|
1561
|
+
audioType: parseStockAudioType({
|
|
1562
|
+
value: getStringFlag({ flags: context.args.flags, name: "audio-type" }),
|
|
1604
1563
|
}),
|
|
1605
|
-
wordId: requireStringFlag({ flags: context.args.flags, name: "word-id" }),
|
|
1606
|
-
words: parseCsvRequiredFlag({ flags: context.args.flags, name: "words" }),
|
|
1607
|
-
});
|
|
1608
|
-
printResult({
|
|
1609
|
-
json: context.json,
|
|
1610
|
-
value: result,
|
|
1611
|
-
human: [result.message],
|
|
1612
1564
|
});
|
|
1613
|
-
return;
|
|
1614
|
-
}
|
|
1615
|
-
if (group === "captions" && action === "themes" && subaction === "list") {
|
|
1616
|
-
const result = await context.client.captions.listThemes();
|
|
1617
1565
|
printResult({
|
|
1618
1566
|
json: context.json,
|
|
1619
1567
|
value: result,
|
|
1620
|
-
human:
|
|
1568
|
+
human: [
|
|
1569
|
+
`${result.imported ? "Imported" : "Reused"} ${result.asset.name} (${result.asset.id}).`,
|
|
1570
|
+
],
|
|
1621
1571
|
});
|
|
1622
1572
|
return;
|
|
1623
1573
|
}
|
|
1624
|
-
if (group === "
|
|
1625
|
-
const result = await context.client.
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1574
|
+
if (group === "library" && action === "search") {
|
|
1575
|
+
const result = await context.client.library.search({
|
|
1576
|
+
query: getStringFlag({ flags: context.args.flags, name: "query" }),
|
|
1577
|
+
assetType: parseNullableStockAssetType({
|
|
1578
|
+
value: getStringFlag({
|
|
1579
|
+
flags: context.args.flags,
|
|
1580
|
+
name: "asset-type",
|
|
1581
|
+
}),
|
|
1629
1582
|
}),
|
|
1630
|
-
|
|
1631
|
-
flags: context.args.flags,
|
|
1632
|
-
name: "theme-id",
|
|
1583
|
+
audioType: parseGlobalBrollAudioType({
|
|
1584
|
+
value: getStringFlag({ flags: context.args.flags, name: "audio-type" }),
|
|
1633
1585
|
}),
|
|
1586
|
+
limit: getNumberFlag({ flags: context.args.flags, name: "limit" }),
|
|
1587
|
+
offset: getNumberFlag({ flags: context.args.flags, name: "offset" }),
|
|
1634
1588
|
});
|
|
1635
1589
|
printResult({
|
|
1636
1590
|
json: context.json,
|
|
1637
1591
|
value: result,
|
|
1638
|
-
human:
|
|
1592
|
+
human: result.items.map((item) => `${item.id} ${item.asset_type} ${item.title} ${item.duration_seconds ?? "-"}s ${item.license_code}`),
|
|
1639
1593
|
});
|
|
1640
1594
|
return;
|
|
1641
1595
|
}
|
|
1642
|
-
if (group === "
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
const result = await context.client.captions.setDefaultTheme({
|
|
1646
|
-
themeId: requireStringFlag({
|
|
1596
|
+
if (group === "library" && action === "import") {
|
|
1597
|
+
const result = await context.client.library.importAsset({
|
|
1598
|
+
libraryAssetId: requireStringFlag({
|
|
1647
1599
|
flags: context.args.flags,
|
|
1648
|
-
name: "
|
|
1600
|
+
name: "library-asset-id",
|
|
1601
|
+
}),
|
|
1602
|
+
folderId: parseFolderIdFlag({
|
|
1603
|
+
flags: context.args.flags,
|
|
1604
|
+
name: "folder-id",
|
|
1649
1605
|
}),
|
|
1650
1606
|
});
|
|
1651
1607
|
printResult({
|
|
1652
1608
|
json: context.json,
|
|
1653
1609
|
value: result,
|
|
1654
|
-
human: [
|
|
1610
|
+
human: [
|
|
1611
|
+
`${result.imported ? "Imported" : "Reused"} ${result.asset.name} (${result.asset.id}).`,
|
|
1612
|
+
],
|
|
1655
1613
|
});
|
|
1656
1614
|
return;
|
|
1657
1615
|
}
|
|
1658
|
-
if (group === "
|
|
1659
|
-
const result = await context.client.
|
|
1616
|
+
if (group === "silence" && action === "plan") {
|
|
1617
|
+
const result = await context.client.silence.plan({
|
|
1660
1618
|
projectId: requireStringFlag({
|
|
1661
1619
|
flags: context.args.flags,
|
|
1662
1620
|
name: "project-id",
|
|
1663
1621
|
}),
|
|
1664
|
-
|
|
1622
|
+
timelineItemId: requireStringFlag({
|
|
1623
|
+
flags: context.args.flags,
|
|
1624
|
+
name: "timeline-item-id",
|
|
1625
|
+
}),
|
|
1626
|
+
allMediaItems: undefined,
|
|
1627
|
+
paddingMs: getNumberFlag({
|
|
1665
1628
|
flags: context.args.flags,
|
|
1666
|
-
name: "
|
|
1629
|
+
name: "padding-ms",
|
|
1667
1630
|
}),
|
|
1668
|
-
|
|
1669
|
-
includeCaptions: getOptionalBooleanFlag({
|
|
1631
|
+
silenceThresholdDb: getNumberFlag({
|
|
1670
1632
|
flags: context.args.flags,
|
|
1671
|
-
name: "
|
|
1633
|
+
name: "silence-threshold-db",
|
|
1672
1634
|
}),
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
});
|
|
1679
|
-
return;
|
|
1680
|
-
}
|
|
1681
|
-
if (group === "exports" && action === "wait") {
|
|
1682
|
-
const result = await context.client.exports.wait({
|
|
1683
|
-
exportId: requireStringFlag({
|
|
1635
|
+
minSilenceDurationSeconds: getNumberFlag({
|
|
1636
|
+
flags: context.args.flags,
|
|
1637
|
+
name: "min-silence-duration",
|
|
1638
|
+
}),
|
|
1639
|
+
removeBlipsMaxDurationSeconds: getNumberFlag({
|
|
1684
1640
|
flags: context.args.flags,
|
|
1685
|
-
name: "
|
|
1641
|
+
name: "remove-blips-max-duration",
|
|
1642
|
+
}),
|
|
1643
|
+
useCaptionValidation: getOptionalBooleanFlag({
|
|
1644
|
+
flags: context.args.flags,
|
|
1645
|
+
name: "use-caption-validation",
|
|
1686
1646
|
}),
|
|
1687
|
-
intervalMs: getNumberFlag({ flags: context.args.flags, name: "interval-ms" }) ??
|
|
1688
|
-
DEFAULT_WAIT_INTERVAL_MS,
|
|
1689
|
-
timeoutMs: getNumberFlag({ flags: context.args.flags, name: "timeout-ms" }) ??
|
|
1690
|
-
DEFAULT_WAIT_TIMEOUT_MS,
|
|
1691
1647
|
});
|
|
1692
1648
|
printResult({
|
|
1693
1649
|
json: context.json,
|
|
1694
1650
|
value: result,
|
|
1695
|
-
human: [
|
|
1696
|
-
`Export ready: ${result.download_url ?? result.short_download_url ?? result.export_id}`,
|
|
1697
|
-
],
|
|
1651
|
+
human: [JSON.stringify(result, null, 2)],
|
|
1698
1652
|
});
|
|
1699
1653
|
return;
|
|
1700
1654
|
}
|
|
1701
|
-
if (group === "
|
|
1702
|
-
const
|
|
1655
|
+
if (group === "silence" && action === "apply") {
|
|
1656
|
+
const allMediaItems = getOptionalBooleanFlag({
|
|
1703
1657
|
flags: context.args.flags,
|
|
1704
|
-
name: "
|
|
1705
|
-
});
|
|
1706
|
-
const
|
|
1707
|
-
|
|
1658
|
+
name: "all-media-items",
|
|
1659
|
+
}) ?? false;
|
|
1660
|
+
const result = await context.client.silence.apply({
|
|
1661
|
+
projectId: requireStringFlag({
|
|
1708
1662
|
flags: context.args.flags,
|
|
1709
|
-
name: "
|
|
1663
|
+
name: "project-id",
|
|
1710
1664
|
}),
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
}
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1665
|
+
timelineItemId: allMediaItems
|
|
1666
|
+
? undefined
|
|
1667
|
+
: requireStringFlag({
|
|
1668
|
+
flags: context.args.flags,
|
|
1669
|
+
name: "timeline-item-id",
|
|
1670
|
+
}),
|
|
1671
|
+
allMediaItems,
|
|
1672
|
+
paddingMs: getNumberFlag({
|
|
1673
|
+
flags: context.args.flags,
|
|
1674
|
+
name: "padding-ms",
|
|
1675
|
+
}),
|
|
1676
|
+
silenceThresholdDb: getNumberFlag({
|
|
1677
|
+
flags: context.args.flags,
|
|
1678
|
+
name: "silence-threshold-db",
|
|
1679
|
+
}),
|
|
1680
|
+
minSilenceDurationSeconds: getNumberFlag({
|
|
1681
|
+
flags: context.args.flags,
|
|
1682
|
+
name: "min-silence-duration",
|
|
1683
|
+
}),
|
|
1684
|
+
removeBlipsMaxDurationSeconds: getNumberFlag({
|
|
1685
|
+
flags: context.args.flags,
|
|
1686
|
+
name: "remove-blips-max-duration",
|
|
1687
|
+
}),
|
|
1688
|
+
useCaptionValidation: getOptionalBooleanFlag({
|
|
1689
|
+
flags: context.args.flags,
|
|
1690
|
+
name: "use-caption-validation",
|
|
1691
|
+
}),
|
|
1692
|
+
deleteSilentClips: getOptionalBooleanFlag({
|
|
1693
|
+
flags: context.args.flags,
|
|
1694
|
+
name: "delete-silent-clips",
|
|
1695
|
+
}),
|
|
1696
|
+
manualSegmentOverrides: parseStringRecordFlag({
|
|
1697
|
+
flags: context.args.flags,
|
|
1698
|
+
name: "manual-segment-overrides-json",
|
|
1699
|
+
}),
|
|
1700
|
+
previewConfirmed: requirePreviewFirstConfirmation({
|
|
1701
|
+
confirmed: getBooleanFlag({
|
|
1702
|
+
flags: context.args.flags,
|
|
1703
|
+
name: "confirm-preview",
|
|
1704
|
+
}),
|
|
1705
|
+
}),
|
|
1706
|
+
});
|
|
1707
|
+
printResult({
|
|
1708
|
+
json: context.json,
|
|
1709
|
+
value: result,
|
|
1710
|
+
human: [JSON.stringify(result, null, 2)],
|
|
1711
|
+
});
|
|
1712
|
+
return;
|
|
1713
|
+
}
|
|
1714
|
+
if (group === "mistakes" && action === "plan") {
|
|
1715
|
+
const result = await context.client.mistakes.plan({
|
|
1716
|
+
projectId: requireStringFlag({
|
|
1717
|
+
flags: context.args.flags,
|
|
1718
|
+
name: "project-id",
|
|
1719
|
+
}),
|
|
1720
|
+
timelineItemId: requireStringFlag({
|
|
1721
|
+
flags: context.args.flags,
|
|
1722
|
+
name: "timeline-item-id",
|
|
1723
|
+
}),
|
|
1724
|
+
});
|
|
1725
|
+
const value = getBooleanFlag({
|
|
1726
|
+
flags: context.args.flags,
|
|
1727
|
+
name: "summary",
|
|
1728
|
+
})
|
|
1729
|
+
? summarizeMistakePlan({ plan: result })
|
|
1730
|
+
: result;
|
|
1731
|
+
printResult({
|
|
1732
|
+
json: context.json,
|
|
1733
|
+
value,
|
|
1734
|
+
human: [JSON.stringify(value, null, 2)],
|
|
1735
|
+
});
|
|
1736
|
+
return;
|
|
1737
|
+
}
|
|
1738
|
+
if (group === "mistakes" && action === "apply") {
|
|
1739
|
+
const result = await context.client.mistakes.apply({
|
|
1740
|
+
projectId: requireStringFlag({
|
|
1741
|
+
flags: context.args.flags,
|
|
1742
|
+
name: "project-id",
|
|
1743
|
+
}),
|
|
1744
|
+
timelineItemId: requireStringFlag({
|
|
1745
|
+
flags: context.args.flags,
|
|
1746
|
+
name: "timeline-item-id",
|
|
1747
|
+
}),
|
|
1748
|
+
plan: parseJsonObjectFlag({
|
|
1749
|
+
flags: context.args.flags,
|
|
1750
|
+
name: "plan-json",
|
|
1751
|
+
}),
|
|
1752
|
+
manualVerdicts: parseStringRecordFlag({
|
|
1753
|
+
flags: context.args.flags,
|
|
1754
|
+
name: "manual-verdicts-json",
|
|
1755
|
+
}),
|
|
1756
|
+
deleteMistakes: getOptionalBooleanFlag({
|
|
1757
|
+
flags: context.args.flags,
|
|
1758
|
+
name: "delete-mistakes",
|
|
1759
|
+
}),
|
|
1760
|
+
previewConfirmed: requirePreviewFirstConfirmation({
|
|
1761
|
+
confirmed: getBooleanFlag({
|
|
1762
|
+
flags: context.args.flags,
|
|
1763
|
+
name: "confirm-preview",
|
|
1764
|
+
}),
|
|
1765
|
+
}),
|
|
1766
|
+
});
|
|
1767
|
+
printResult({
|
|
1768
|
+
json: context.json,
|
|
1769
|
+
value: result,
|
|
1770
|
+
human: [JSON.stringify(result, null, 2)],
|
|
1771
|
+
});
|
|
1772
|
+
return;
|
|
1773
|
+
}
|
|
1774
|
+
if (group === "captions" &&
|
|
1775
|
+
((action === "words" && subaction === "list") || action === "list-words")) {
|
|
1776
|
+
const result = await context.client.captions.listWords({
|
|
1777
|
+
projectId: requireStringFlag({
|
|
1778
|
+
flags: context.args.flags,
|
|
1779
|
+
name: "project-id",
|
|
1780
|
+
}),
|
|
1781
|
+
timelineItemId: getStringFlag({
|
|
1782
|
+
flags: context.args.flags,
|
|
1783
|
+
name: "timeline-item-id",
|
|
1784
|
+
}),
|
|
1785
|
+
matchText: getStringFlag({
|
|
1786
|
+
flags: context.args.flags,
|
|
1787
|
+
name: "match-text",
|
|
1788
|
+
}),
|
|
1789
|
+
limit: getNumberFlag({ flags: context.args.flags, name: "limit" }),
|
|
1790
|
+
offset: getNumberFlag({ flags: context.args.flags, name: "offset" }),
|
|
1791
|
+
});
|
|
1792
|
+
printResult({
|
|
1793
|
+
json: context.json,
|
|
1794
|
+
value: result,
|
|
1795
|
+
human: result.words.map((word) => JSON.stringify(word)),
|
|
1796
|
+
});
|
|
1797
|
+
return;
|
|
1798
|
+
}
|
|
1799
|
+
if (group === "captions" &&
|
|
1800
|
+
(action === "delete" || action === "hide" || action === "show")) {
|
|
1801
|
+
const selector = buildCaptionTrackSelector({ flags: context.args.flags });
|
|
1802
|
+
const result = action === "delete"
|
|
1803
|
+
? await context.client.captions.delete(selector)
|
|
1804
|
+
: action === "hide"
|
|
1805
|
+
? await context.client.captions.hide(selector)
|
|
1806
|
+
: await context.client.captions.show(selector);
|
|
1807
|
+
printResult({
|
|
1808
|
+
json: context.json,
|
|
1809
|
+
value: result,
|
|
1810
|
+
human: [result.message],
|
|
1811
|
+
});
|
|
1812
|
+
return;
|
|
1813
|
+
}
|
|
1814
|
+
if (group === "captions" && action === "words" && subaction === "text") {
|
|
1815
|
+
const result = await context.client.captions.updateWordText({
|
|
1816
|
+
projectId: requireStringFlag({
|
|
1817
|
+
flags: context.args.flags,
|
|
1818
|
+
name: "project-id",
|
|
1819
|
+
}),
|
|
1820
|
+
wordId: requireStringFlag({ flags: context.args.flags, name: "word-id" }),
|
|
1821
|
+
newText: getStringFlag({ flags: context.args.flags, name: "new-text" }) ??
|
|
1822
|
+
requireStringFlag({ flags: context.args.flags, name: "text" }),
|
|
1823
|
+
});
|
|
1824
|
+
printResult({
|
|
1825
|
+
json: context.json,
|
|
1826
|
+
value: result,
|
|
1827
|
+
human: [
|
|
1828
|
+
`Updated ${result.word_id}: ${result.old_text} -> ${result.new_text}`,
|
|
1829
|
+
],
|
|
1830
|
+
});
|
|
1831
|
+
return;
|
|
1832
|
+
}
|
|
1833
|
+
if (group === "captions" &&
|
|
1834
|
+
action === "words" &&
|
|
1835
|
+
subaction === "batch-text") {
|
|
1836
|
+
const result = await context.client.captions.batchUpdateWordText({
|
|
1837
|
+
projectId: requireStringFlag({
|
|
1838
|
+
flags: context.args.flags,
|
|
1839
|
+
name: "project-id",
|
|
1840
|
+
}),
|
|
1841
|
+
updates: parseWordTextUpdatesFlag({
|
|
1842
|
+
flags: context.args.flags,
|
|
1843
|
+
name: "updates-json",
|
|
1844
|
+
}),
|
|
1845
|
+
});
|
|
1846
|
+
printResult({
|
|
1847
|
+
json: context.json,
|
|
1848
|
+
value: result,
|
|
1849
|
+
human: [result.message],
|
|
1850
|
+
});
|
|
1851
|
+
return;
|
|
1852
|
+
}
|
|
1853
|
+
if (group === "captions" &&
|
|
1854
|
+
((action === "words" && subaction === "style") || action === "style-words")) {
|
|
1855
|
+
const styleJson = parseNullableJsonObjectFlag({
|
|
1856
|
+
flags: context.args.flags,
|
|
1857
|
+
name: "style-json",
|
|
1858
|
+
});
|
|
1859
|
+
const style = styleJson === undefined
|
|
1860
|
+
? buildCaptionWordStyleFromFlags({ flags: context.args.flags })
|
|
1861
|
+
: styleJson;
|
|
1862
|
+
const result = await context.client.captions.setWordStyle({
|
|
1863
|
+
...buildCaptionWordSelector({ flags: context.args.flags }),
|
|
1864
|
+
style,
|
|
1865
|
+
clearExisting: getOptionalBooleanFlag({
|
|
1866
|
+
flags: context.args.flags,
|
|
1867
|
+
name: "clear-existing",
|
|
1868
|
+
}),
|
|
1869
|
+
});
|
|
1870
|
+
printResult({
|
|
1871
|
+
json: context.json,
|
|
1872
|
+
value: result,
|
|
1873
|
+
human: [`Updated ${result.updated_count} caption word(s).`],
|
|
1874
|
+
});
|
|
1875
|
+
return;
|
|
1876
|
+
}
|
|
1877
|
+
if (group === "captions" &&
|
|
1878
|
+
((action === "words" && subaction === "emphasis") || action === "emphasize")) {
|
|
1879
|
+
const result = await context.client.captions.setWordEmphasis({
|
|
1880
|
+
...buildCaptionWordSelector({ flags: context.args.flags }),
|
|
1881
|
+
emphasis: getOptionalBooleanFlag({
|
|
1882
|
+
flags: context.args.flags,
|
|
1883
|
+
name: "emphasis",
|
|
1884
|
+
}) ?? true,
|
|
1885
|
+
});
|
|
1886
|
+
printResult({
|
|
1887
|
+
json: context.json,
|
|
1888
|
+
value: result,
|
|
1889
|
+
human: [`Updated ${result.updated_count} caption word(s).`],
|
|
1890
|
+
});
|
|
1891
|
+
return;
|
|
1892
|
+
}
|
|
1893
|
+
if (group === "captions" && action === "words" && subaction === "delete") {
|
|
1894
|
+
const result = await context.client.captions.deleteWords({
|
|
1895
|
+
projectId: requireStringFlag({
|
|
1896
|
+
flags: context.args.flags,
|
|
1897
|
+
name: "project-id",
|
|
1898
|
+
}),
|
|
1899
|
+
wordIds: parseWordIds({ flags: context.args.flags }),
|
|
1900
|
+
});
|
|
1901
|
+
printResult({
|
|
1902
|
+
json: context.json,
|
|
1903
|
+
value: result,
|
|
1904
|
+
human: [result.message],
|
|
1905
|
+
});
|
|
1906
|
+
return;
|
|
1907
|
+
}
|
|
1908
|
+
if (group === "captions" && action === "words" && subaction === "merge") {
|
|
1909
|
+
const result = await context.client.captions.mergeWords({
|
|
1910
|
+
projectId: requireStringFlag({
|
|
1911
|
+
flags: context.args.flags,
|
|
1912
|
+
name: "project-id",
|
|
1913
|
+
}),
|
|
1914
|
+
wordIds: parseWordIds({ flags: context.args.flags }),
|
|
1915
|
+
text: getStringFlag({ flags: context.args.flags, name: "text" }),
|
|
1916
|
+
});
|
|
1917
|
+
printResult({
|
|
1918
|
+
json: context.json,
|
|
1919
|
+
value: result,
|
|
1920
|
+
human: [result.message],
|
|
1921
|
+
});
|
|
1922
|
+
return;
|
|
1923
|
+
}
|
|
1924
|
+
if (group === "captions" && action === "words" && subaction === "split") {
|
|
1925
|
+
const result = await context.client.captions.splitWord({
|
|
1926
|
+
projectId: requireStringFlag({
|
|
1927
|
+
flags: context.args.flags,
|
|
1928
|
+
name: "project-id",
|
|
1929
|
+
}),
|
|
1930
|
+
wordId: requireStringFlag({ flags: context.args.flags, name: "word-id" }),
|
|
1931
|
+
words: parseCsvRequiredFlag({ flags: context.args.flags, name: "words" }),
|
|
1932
|
+
});
|
|
1933
|
+
printResult({
|
|
1934
|
+
json: context.json,
|
|
1935
|
+
value: result,
|
|
1936
|
+
human: [result.message],
|
|
1937
|
+
});
|
|
1938
|
+
return;
|
|
1939
|
+
}
|
|
1940
|
+
if (group === "captions" && action === "themes" && subaction === "list") {
|
|
1941
|
+
const result = await context.client.captions.listThemes();
|
|
1942
|
+
printResult({
|
|
1943
|
+
json: context.json,
|
|
1944
|
+
value: result,
|
|
1945
|
+
human: result.themes.length > 0
|
|
1946
|
+
? result.themes.map((theme) => JSON.stringify(theme))
|
|
1947
|
+
: [
|
|
1948
|
+
"No custom caption themes yet.",
|
|
1949
|
+
"Create one in the dashboard, then use `blitzreels captions theme set --project-id ID --theme-id ID` or `blitzreels captions theme set-default --theme-id ID`.",
|
|
1950
|
+
],
|
|
1951
|
+
});
|
|
1952
|
+
return;
|
|
1953
|
+
}
|
|
1954
|
+
if (group === "captions" && action === "theme" && subaction === "set") {
|
|
1955
|
+
const result = await context.client.captions.setProjectTheme({
|
|
1956
|
+
projectId: requireStringFlag({
|
|
1957
|
+
flags: context.args.flags,
|
|
1958
|
+
name: "project-id",
|
|
1959
|
+
}),
|
|
1960
|
+
themeId: requireStringFlag({
|
|
1961
|
+
flags: context.args.flags,
|
|
1962
|
+
name: "theme-id",
|
|
1963
|
+
}),
|
|
1964
|
+
});
|
|
1965
|
+
printResult({
|
|
1966
|
+
json: context.json,
|
|
1967
|
+
value: result,
|
|
1968
|
+
human: ["Project caption theme updated."],
|
|
1969
|
+
});
|
|
1970
|
+
return;
|
|
1971
|
+
}
|
|
1972
|
+
if (group === "captions" && action === "theme" && subaction === "update") {
|
|
1973
|
+
const result = await context.client.captions.updateTheme({
|
|
1974
|
+
themeId: requireStringFlag({
|
|
1975
|
+
flags: context.args.flags,
|
|
1976
|
+
name: "theme-id",
|
|
1977
|
+
}),
|
|
1978
|
+
name: getStringFlag({ flags: context.args.flags, name: "name" }),
|
|
1979
|
+
description: getStringFlag({
|
|
1980
|
+
flags: context.args.flags,
|
|
1981
|
+
name: "description",
|
|
1982
|
+
}),
|
|
1983
|
+
settings: buildCaptionThemeSettingsPatch({ flags: context.args.flags }),
|
|
1984
|
+
});
|
|
1985
|
+
printResult({
|
|
1986
|
+
json: context.json,
|
|
1987
|
+
value: result,
|
|
1988
|
+
human: ["Caption theme updated."],
|
|
1989
|
+
});
|
|
1990
|
+
return;
|
|
1991
|
+
}
|
|
1992
|
+
if (group === "captions" &&
|
|
1993
|
+
action === "theme" &&
|
|
1994
|
+
subaction === "set-default") {
|
|
1995
|
+
const result = await context.client.captions.setDefaultTheme({
|
|
1996
|
+
themeId: requireStringFlag({
|
|
1997
|
+
flags: context.args.flags,
|
|
1998
|
+
name: "theme-id",
|
|
1999
|
+
}),
|
|
2000
|
+
});
|
|
2001
|
+
printResult({
|
|
2002
|
+
json: context.json,
|
|
2003
|
+
value: result,
|
|
2004
|
+
human: ["Default caption theme updated."],
|
|
2005
|
+
});
|
|
2006
|
+
return;
|
|
2007
|
+
}
|
|
2008
|
+
if (group === "exports") {
|
|
2009
|
+
const handled = await handleExportsCommand({
|
|
2010
|
+
context,
|
|
2011
|
+
action,
|
|
2012
|
+
helpers: {
|
|
2013
|
+
getNumberFlag,
|
|
2014
|
+
getOptionalBooleanFlag,
|
|
2015
|
+
getStringFlag,
|
|
2016
|
+
requireStringFlag,
|
|
2017
|
+
printResult,
|
|
2018
|
+
},
|
|
2019
|
+
defaultWaitIntervalMs: DEFAULT_WAIT_INTERVAL_MS,
|
|
2020
|
+
defaultWaitTimeoutMs: DEFAULT_WAIT_TIMEOUT_MS,
|
|
2021
|
+
});
|
|
2022
|
+
if (handled) {
|
|
2023
|
+
return;
|
|
2024
|
+
}
|
|
2025
|
+
}
|
|
2026
|
+
throw buildUnknownCommandError({ command: context.args.command });
|
|
2027
|
+
}
|
|
2028
|
+
async function setApiKey({ context }) {
|
|
2029
|
+
const apiKey = getStringFlag({ flags: context.args.flags, name: "api-key" }) ??
|
|
2030
|
+
process.env.BLITZREELS_API_KEY;
|
|
2031
|
+
if (!apiKey) {
|
|
2032
|
+
throw new Error("Pass --api-key or set BLITZREELS_API_KEY.");
|
|
2033
|
+
}
|
|
2034
|
+
const stored = await storeApiKey({
|
|
2035
|
+
apiKey,
|
|
2036
|
+
baseUrl: getStringFlag({ flags: context.args.flags, name: "base-url" }),
|
|
2037
|
+
});
|
|
2038
|
+
printResult({
|
|
2039
|
+
json: context.json,
|
|
2040
|
+
value: { configPath: stored.configPath, keyStorage: stored.keyStorage },
|
|
2041
|
+
human: [`API key saved to ${stored.configPath}`],
|
|
2042
|
+
});
|
|
2043
|
+
}
|
|
2044
|
+
async function loginWithBrowser({ context, }) {
|
|
2045
|
+
const appUrl = resolveAppUrl({ baseUrl: context.config.baseUrl });
|
|
2046
|
+
const deviceUrl = `${appUrl}/api/cli/auth/device`;
|
|
2047
|
+
const startResponse = await fetch(deviceUrl, {
|
|
2048
|
+
method: "POST",
|
|
2049
|
+
headers: { Accept: "application/json" },
|
|
2050
|
+
});
|
|
2051
|
+
const login = parseDeviceLoginResponse({
|
|
2052
|
+
value: await readJsonResponse({
|
|
2053
|
+
response: startResponse,
|
|
2054
|
+
url: deviceUrl,
|
|
2055
|
+
action: "start CLI login",
|
|
2056
|
+
}),
|
|
2057
|
+
});
|
|
2058
|
+
const shouldOpenBrowser = !getBooleanFlag({
|
|
2059
|
+
flags: context.args.flags,
|
|
2060
|
+
name: "no-browser",
|
|
2061
|
+
});
|
|
2062
|
+
if (shouldOpenBrowser) {
|
|
2063
|
+
openBrowser({ url: login.verification_uri_complete });
|
|
2064
|
+
}
|
|
2065
|
+
const loginInstructions = [
|
|
2066
|
+
`First copy your one-time code: ${login.user_code}`,
|
|
2067
|
+
`Then open: ${login.verification_uri_complete}`,
|
|
2068
|
+
"Waiting for browser approval...",
|
|
2069
|
+
];
|
|
2070
|
+
if (context.json) {
|
|
2071
|
+
console.error(loginInstructions.join("\n"));
|
|
2072
|
+
}
|
|
2073
|
+
else {
|
|
2074
|
+
printResult({
|
|
2075
|
+
json: context.json,
|
|
2076
|
+
value: {
|
|
2077
|
+
user_code: login.user_code,
|
|
2078
|
+
verification_uri: login.verification_uri_complete,
|
|
2079
|
+
expires_in: login.expires_in,
|
|
2080
|
+
},
|
|
2081
|
+
human: loginInstructions,
|
|
2082
|
+
});
|
|
2083
|
+
}
|
|
2084
|
+
const startedAt = Date.now();
|
|
2085
|
+
const timeoutMs = Math.min(login.expires_in * 1000, getNumberFlag({ flags: context.args.flags, name: "timeout-ms" }) ??
|
|
2086
|
+
login.expires_in * 1000);
|
|
2087
|
+
const intervalMs = Math.max(1000, login.interval * 1000);
|
|
2088
|
+
while (Date.now() - startedAt <= timeoutMs) {
|
|
2089
|
+
await sleep({ ms: intervalMs });
|
|
2090
|
+
const tokenUrl = `${appUrl}/api/cli/auth/token`;
|
|
2091
|
+
const pollResponse = await fetch(tokenUrl, {
|
|
2092
|
+
method: "POST",
|
|
2093
|
+
headers: {
|
|
2094
|
+
Accept: "application/json",
|
|
2095
|
+
"Content-Type": "application/json",
|
|
2096
|
+
},
|
|
2097
|
+
body: JSON.stringify({ device_code: login.device_code }),
|
|
2098
|
+
});
|
|
2099
|
+
const result = parseDeviceTokenResponse({
|
|
2100
|
+
value: await readJsonResponse({
|
|
2101
|
+
response: pollResponse,
|
|
2102
|
+
url: tokenUrl,
|
|
2103
|
+
action: "poll CLI login",
|
|
2104
|
+
}),
|
|
2105
|
+
});
|
|
2106
|
+
if (result.status === "approved" && result.api_key) {
|
|
2107
|
+
await persistLoggedInApiKey({
|
|
2108
|
+
apiKey: result.api_key,
|
|
2109
|
+
baseUrl: context.config.baseUrl,
|
|
2110
|
+
});
|
|
2111
|
+
printResult({
|
|
2112
|
+
json: context.json,
|
|
2113
|
+
value: { success: true, key_prefix: result.key_prefix ?? "" },
|
|
2114
|
+
human: [`Logged in. API key prefix: ${result.key_prefix ?? "saved"}`],
|
|
2115
|
+
});
|
|
2116
|
+
return;
|
|
2117
|
+
}
|
|
2118
|
+
if (result.status === "expired" ||
|
|
2119
|
+
result.status === "denied" ||
|
|
2120
|
+
result.status === "invalid" ||
|
|
2121
|
+
result.status === "consumed") {
|
|
2122
|
+
throw new Error(`Login failed: ${result.status}.`);
|
|
2123
|
+
}
|
|
2124
|
+
}
|
|
2125
|
+
throw new Error("Timed out waiting for browser approval.");
|
|
2126
|
+
}
|
|
2127
|
+
async function uploadMediaFile({ context }) {
|
|
2128
|
+
const filePath = resolve(requireStringFlag({ flags: context.args.flags, name: "file" }));
|
|
2129
|
+
const fileStats = await stat(filePath);
|
|
2130
|
+
if (!fileStats.isFile()) {
|
|
2131
|
+
throw new Error(`Not a file: ${filePath}`);
|
|
2132
|
+
}
|
|
2133
|
+
const fileName = getStringFlag({ flags: context.args.flags, name: "name" }) ??
|
|
2134
|
+
basename(filePath);
|
|
2135
|
+
const contentType = getStringFlag({ flags: context.args.flags, name: "content-type" }) ??
|
|
2136
|
+
inferContentType({ filePath });
|
|
2137
|
+
const autoJobs = buildUploadAutoJobNotice({ flags: context.args.flags });
|
|
2138
|
+
if (autoJobs.length > 0) {
|
|
2139
|
+
console.error(`BlitzReels upload will queue background jobs: ${autoJobs.join(", ")}. Use \`blitzreels media wait --asset-id ID --for all\` after upload when you need processed metadata.`);
|
|
2140
|
+
}
|
|
2141
|
+
const init = await context.client.media.initUpload({
|
|
2142
|
+
fileName,
|
|
2143
|
+
contentType,
|
|
2144
|
+
});
|
|
2145
|
+
const bytes = await readFile(filePath);
|
|
2146
|
+
const uploadResponse = await fetch(init.upload_url, {
|
|
2147
|
+
method: "PUT",
|
|
2148
|
+
headers: { "Content-Type": contentType },
|
|
2149
|
+
body: new Blob([new Uint8Array(bytes)], { type: contentType }),
|
|
2150
|
+
});
|
|
2151
|
+
if (!uploadResponse.ok) {
|
|
2152
|
+
throw new Error(`Upload failed with ${uploadResponse.status}: ${await uploadResponse.text()}`);
|
|
2153
|
+
}
|
|
2154
|
+
return context.client.media.finalizeUpload({
|
|
2155
|
+
storageKey: init.storage_key,
|
|
2156
|
+
fileName,
|
|
2157
|
+
contentType,
|
|
2158
|
+
fileSizeBytes: fileStats.size,
|
|
2159
|
+
autoProcessEnabled: getOptionalBooleanFlag({
|
|
2160
|
+
flags: context.args.flags,
|
|
2161
|
+
name: "auto-process",
|
|
2162
|
+
}),
|
|
2163
|
+
autoTranscribe: getOptionalBooleanFlag({
|
|
2164
|
+
flags: context.args.flags,
|
|
2165
|
+
name: "auto-transcribe",
|
|
2166
|
+
}),
|
|
2167
|
+
autoDescribeAfterTranscribe: getOptionalBooleanFlag({
|
|
2168
|
+
flags: context.args.flags,
|
|
2169
|
+
name: "auto-describe",
|
|
2170
|
+
}),
|
|
2171
|
+
autoSuggestShorts: getOptionalBooleanFlag({
|
|
2172
|
+
flags: context.args.flags,
|
|
2173
|
+
name: "auto-suggest-shorts",
|
|
2174
|
+
}),
|
|
2175
|
+
});
|
|
2176
|
+
}
|
|
2177
|
+
async function waitForMediaAsset({ context, }) {
|
|
2178
|
+
const assetId = requireStringFlag({
|
|
2179
|
+
flags: context.args.flags,
|
|
2180
|
+
name: "asset-id",
|
|
2181
|
+
});
|
|
2182
|
+
const projectId = getStringFlag({
|
|
2183
|
+
flags: context.args.flags,
|
|
2184
|
+
name: "project-id",
|
|
2185
|
+
});
|
|
2186
|
+
const waitFor = parseMediaWaitFor({
|
|
2187
|
+
value: getStringFlag({ flags: context.args.flags, name: "for" }),
|
|
2188
|
+
});
|
|
2189
|
+
const intervalMs = getNumberFlag({ flags: context.args.flags, name: "interval-ms" }) ??
|
|
2190
|
+
DEFAULT_WAIT_INTERVAL_MS;
|
|
2191
|
+
const timeoutMs = getNumberFlag({ flags: context.args.flags, name: "timeout-ms" }) ??
|
|
2192
|
+
10 * 60 * 1000;
|
|
2193
|
+
const startedAt = Date.now();
|
|
2194
|
+
let lastAsset;
|
|
2195
|
+
while (true) {
|
|
2196
|
+
const result = await context.client.media.get({ assetId });
|
|
2197
|
+
lastAsset = result.asset;
|
|
2198
|
+
const clippingStatus = await context.client.media.clippingStatus({
|
|
2199
|
+
assetId,
|
|
2200
|
+
projectId,
|
|
2201
|
+
});
|
|
2202
|
+
const readyFor = getReadyMediaStates({
|
|
2203
|
+
asset: lastAsset,
|
|
2204
|
+
clippingStatus,
|
|
2205
|
+
});
|
|
2206
|
+
if (mediaWaitSatisfied({ readyFor, waitFor })) {
|
|
2207
|
+
return {
|
|
2208
|
+
asset: {
|
|
2209
|
+
id: lastAsset.id,
|
|
2210
|
+
name: lastAsset.name,
|
|
2211
|
+
processingStatus: lastAsset.processingStatus,
|
|
2212
|
+
processingSubstage: lastAsset.processingSubstage,
|
|
2213
|
+
processingError: lastAsset.processingError,
|
|
2214
|
+
processingErrors: lastAsset.processingErrors,
|
|
2215
|
+
retryable: lastAsset.retryable,
|
|
2216
|
+
providerRequestIds: lastAsset.providerRequestIds,
|
|
2217
|
+
durationSeconds: lastAsset.durationSeconds,
|
|
2218
|
+
aiDescription: lastAsset.aiDescription,
|
|
2219
|
+
},
|
|
2220
|
+
ready_for: readyFor,
|
|
2221
|
+
waited_ms: Date.now() - startedAt,
|
|
2222
|
+
};
|
|
2223
|
+
}
|
|
2224
|
+
if (Date.now() - startedAt >= timeoutMs) {
|
|
2225
|
+
throw new Error(`Timed out waiting for media asset ${assetId}. Last status: ${lastAsset.processingStatus ?? "unknown"}${lastAsset.processingSubstage ? `/${lastAsset.processingSubstage}` : ""}${lastAsset.processingError ? `: ${lastAsset.processingError}` : ""}.`);
|
|
2226
|
+
}
|
|
2227
|
+
await sleep({ ms: intervalMs });
|
|
2228
|
+
}
|
|
2229
|
+
}
|
|
2230
|
+
function getClipRecord({ result, }) {
|
|
2231
|
+
return isRecord(result.clip) ? result.clip : {};
|
|
2232
|
+
}
|
|
2233
|
+
function printClipResult({ context, result, }) {
|
|
2234
|
+
const clip = getClipRecord({ result });
|
|
2235
|
+
const clipId = typeof clip.clip_id === "string" ? clip.clip_id : "unknown";
|
|
2236
|
+
const status = typeof clip.status === "string" ? clip.status : "unknown";
|
|
2237
|
+
const nextAction = typeof clip.next_action === "string" ? clip.next_action : "unknown";
|
|
2238
|
+
const projectId = typeof clip.project_id === "string" ? clip.project_id : null;
|
|
2239
|
+
printResult({
|
|
2240
|
+
json: context.json,
|
|
2241
|
+
value: result,
|
|
2242
|
+
human: [
|
|
2243
|
+
`Clip ${clipId}: ${status}`,
|
|
2244
|
+
`Next action: ${nextAction}`,
|
|
2245
|
+
...(projectId === null ? [] : [`Project: ${projectId}`]),
|
|
2246
|
+
],
|
|
2247
|
+
});
|
|
2248
|
+
}
|
|
2249
|
+
function parseClipSourceType({ value, }) {
|
|
2250
|
+
if (value === "asset" || value === "youtube") {
|
|
2251
|
+
return value;
|
|
2252
|
+
}
|
|
2253
|
+
throw new Error("--source-type must be asset or youtube.");
|
|
2254
|
+
}
|
|
2255
|
+
function parseClipSelectionMode({ value, }) {
|
|
2256
|
+
if (value === undefined) {
|
|
2257
|
+
return "auto_best";
|
|
2258
|
+
}
|
|
2259
|
+
if (value === "auto_best" ||
|
|
2260
|
+
value === "suggestion" ||
|
|
2261
|
+
value === "time_range") {
|
|
2262
|
+
return value;
|
|
2263
|
+
}
|
|
2264
|
+
throw new Error("--selection-mode must be auto_best, suggestion, or time_range.");
|
|
2265
|
+
}
|
|
2266
|
+
function parseClipLayoutMode({ value, }) {
|
|
2267
|
+
if (value === undefined) {
|
|
2268
|
+
return "auto";
|
|
2269
|
+
}
|
|
2270
|
+
if (value === "auto" ||
|
|
2271
|
+
value === "prefer_split" ||
|
|
2272
|
+
value === "prefer_focus") {
|
|
2273
|
+
return value;
|
|
2274
|
+
}
|
|
2275
|
+
throw new Error("--layout-mode must be auto, prefer_split, or prefer_focus.");
|
|
2276
|
+
}
|
|
2277
|
+
function parseClipContentTypeHint({ value, }) {
|
|
2278
|
+
if (value === undefined) {
|
|
2279
|
+
return undefined;
|
|
2280
|
+
}
|
|
2281
|
+
if (value === "auto" ||
|
|
2282
|
+
value === "podcast" ||
|
|
2283
|
+
value === "tutorial" ||
|
|
2284
|
+
value === "generic") {
|
|
2285
|
+
return value;
|
|
2286
|
+
}
|
|
2287
|
+
throw new Error("--content-type-hint must be auto, podcast, tutorial, or generic.");
|
|
2288
|
+
}
|
|
2289
|
+
function parseClipQaMode({ value, }) {
|
|
2290
|
+
if (value === undefined) {
|
|
2291
|
+
return "required";
|
|
2292
|
+
}
|
|
2293
|
+
if (value === "required" || value === "permissive") {
|
|
2294
|
+
return value;
|
|
2295
|
+
}
|
|
2296
|
+
throw new Error("--qa-mode must be required or permissive.");
|
|
2297
|
+
}
|
|
2298
|
+
function parseClipRepairMode({ value, }) {
|
|
2299
|
+
if (value === undefined) {
|
|
2300
|
+
return "auto";
|
|
2301
|
+
}
|
|
2302
|
+
if (value === "auto" ||
|
|
2303
|
+
value === "prefer_split" ||
|
|
2304
|
+
value === "prefer_focus" ||
|
|
2305
|
+
value === "prefer_tutorial" ||
|
|
2306
|
+
value === "move_captions_off_faces") {
|
|
2307
|
+
return value;
|
|
2308
|
+
}
|
|
2309
|
+
throw new Error("--repair-mode must be auto, prefer_split, prefer_focus, prefer_tutorial, or move_captions_off_faces.");
|
|
2310
|
+
}
|
|
2311
|
+
function parseClipExportResolution({ value, }) {
|
|
2312
|
+
if (value === undefined) {
|
|
2313
|
+
return "1080p";
|
|
2314
|
+
}
|
|
2315
|
+
if (value === "720p" || value === "1080p") {
|
|
2316
|
+
return value;
|
|
2317
|
+
}
|
|
2318
|
+
throw new Error("--resolution must be 720p or 1080p.");
|
|
2319
|
+
}
|
|
2320
|
+
function parseClipExportFormat({ value, }) {
|
|
2321
|
+
if (value === undefined) {
|
|
2322
|
+
return "mp4";
|
|
2323
|
+
}
|
|
2324
|
+
if (value === "mp4" || value === "mov") {
|
|
2325
|
+
return value;
|
|
2326
|
+
}
|
|
2327
|
+
throw new Error("--format must be mp4 or mov.");
|
|
2328
|
+
}
|
|
2329
|
+
function buildClipSource({ flags }) {
|
|
2330
|
+
const sourceType = parseClipSourceType({
|
|
2331
|
+
value: requireStringFlag({ flags, name: "source-type" }),
|
|
2332
|
+
});
|
|
2333
|
+
if (sourceType === "asset") {
|
|
2334
|
+
return {
|
|
2335
|
+
sourceType,
|
|
2336
|
+
assetId: requireStringFlag({ flags, name: "asset-id" }),
|
|
2337
|
+
youtubeUrl: null,
|
|
2338
|
+
};
|
|
2339
|
+
}
|
|
2340
|
+
return {
|
|
2341
|
+
sourceType,
|
|
2342
|
+
assetId: null,
|
|
2343
|
+
youtubeUrl: requireStringFlag({ flags, name: "youtube-url" }),
|
|
2344
|
+
};
|
|
2345
|
+
}
|
|
2346
|
+
function requireNumberFlag({ flags, name, }) {
|
|
2347
|
+
const value = getNumberFlag({ flags, name });
|
|
2348
|
+
if (value === undefined) {
|
|
2349
|
+
throw new Error(`Missing required --${name}.`);
|
|
2350
|
+
}
|
|
2351
|
+
return value;
|
|
2352
|
+
}
|
|
2353
|
+
function buildClipSelection({ flags }) {
|
|
2354
|
+
const mode = parseClipSelectionMode({
|
|
2355
|
+
value: getStringFlag({ flags, name: "selection-mode" }),
|
|
2356
|
+
});
|
|
2357
|
+
return {
|
|
2358
|
+
mode,
|
|
2359
|
+
suggestionId: mode === "suggestion"
|
|
2360
|
+
? requireStringFlag({ flags, name: "suggestion-id" })
|
|
2361
|
+
: (parseNullableStringFlag({ flags, name: "suggestion-id" }) ?? null),
|
|
2362
|
+
startSeconds: mode === "time_range"
|
|
2363
|
+
? requireNumberFlag({ flags, name: "start-seconds" })
|
|
2364
|
+
: (parseNullableNumberFlag({ flags, name: "start-seconds" }) ?? null),
|
|
2365
|
+
endSeconds: mode === "time_range"
|
|
2366
|
+
? requireNumberFlag({ flags, name: "end-seconds" })
|
|
2367
|
+
: (parseNullableNumberFlag({ flags, name: "end-seconds" }) ?? null),
|
|
2368
|
+
};
|
|
2369
|
+
}
|
|
2370
|
+
function buildClipTarget({ flags, requireMaxDuration, }) {
|
|
2371
|
+
const maxDurationSeconds = getNumberFlag({ flags, name: "max-duration" });
|
|
2372
|
+
const minDurationInput = parseNullableNumberFlag({
|
|
2373
|
+
flags,
|
|
2374
|
+
name: "min-duration",
|
|
2375
|
+
});
|
|
2376
|
+
if (maxDurationSeconds === undefined) {
|
|
2377
|
+
if (requireMaxDuration) {
|
|
2378
|
+
throw new Error("Missing required --max-duration.");
|
|
2379
|
+
}
|
|
2380
|
+
if (minDurationInput !== undefined && minDurationInput !== null) {
|
|
2381
|
+
throw new Error("--max-duration is required when --min-duration is set.");
|
|
2382
|
+
}
|
|
2383
|
+
return null;
|
|
2384
|
+
}
|
|
2385
|
+
const minDurationSeconds = minDurationInput ?? null;
|
|
2386
|
+
if (minDurationSeconds !== null && minDurationSeconds > maxDurationSeconds) {
|
|
2387
|
+
throw new Error("--min-duration must be less than or equal to --max-duration.");
|
|
2388
|
+
}
|
|
2389
|
+
return {
|
|
2390
|
+
minDurationSeconds,
|
|
2391
|
+
maxDurationSeconds,
|
|
2392
|
+
};
|
|
2393
|
+
}
|
|
2394
|
+
function toCreateClipOptions({ flags, }) {
|
|
2395
|
+
const target = buildClipTarget({ flags, requireMaxDuration: true });
|
|
2396
|
+
if (target === null) {
|
|
2397
|
+
throw new Error("Missing required --max-duration.");
|
|
2398
|
+
}
|
|
2399
|
+
return {
|
|
2400
|
+
projectName: parseNullableStringFlag({ flags, name: "name" }) ?? null,
|
|
2401
|
+
source: buildClipSource({ flags }),
|
|
2402
|
+
selection: buildClipSelection({ flags }),
|
|
2403
|
+
target: {
|
|
2404
|
+
aspectRatio: "9:16",
|
|
2405
|
+
...target,
|
|
2406
|
+
},
|
|
2407
|
+
layout: {
|
|
2408
|
+
mode: parseClipLayoutMode({
|
|
2409
|
+
value: getStringFlag({ flags, name: "layout-mode" }),
|
|
2410
|
+
}),
|
|
2411
|
+
contentTypeHint: parseClipContentTypeHint({
|
|
2412
|
+
value: getStringFlag({ flags, name: "content-type-hint" }),
|
|
2413
|
+
}),
|
|
2414
|
+
},
|
|
2415
|
+
captions: {
|
|
2416
|
+
enabled: getOptionalBooleanFlag({ flags, name: "captions" }) ?? true,
|
|
2417
|
+
styleId: parseNullableStringFlag({ flags, name: "caption-style-id" }) ?? null,
|
|
2418
|
+
},
|
|
2419
|
+
qa: {
|
|
2420
|
+
mode: parseClipQaMode({
|
|
2421
|
+
value: getStringFlag({ flags, name: "qa-mode" }),
|
|
2422
|
+
}),
|
|
2423
|
+
},
|
|
2424
|
+
export: {
|
|
2425
|
+
autoExport: getOptionalBooleanFlag({ flags, name: "auto-export" }) ?? false,
|
|
2426
|
+
},
|
|
2427
|
+
};
|
|
2428
|
+
}
|
|
2429
|
+
async function waitForClip({ context, }) {
|
|
2430
|
+
const clipId = requireStringFlag({
|
|
2431
|
+
flags: context.args.flags,
|
|
2432
|
+
name: "clip-id",
|
|
2433
|
+
});
|
|
2434
|
+
const intervalMs = getNumberFlag({ flags: context.args.flags, name: "interval-ms" }) ??
|
|
2435
|
+
DEFAULT_WAIT_INTERVAL_MS;
|
|
2436
|
+
const timeoutMs = getNumberFlag({ flags: context.args.flags, name: "timeout-ms" }) ??
|
|
2437
|
+
10 * 60 * 1000;
|
|
2438
|
+
const startedAt = Date.now();
|
|
2439
|
+
while (true) {
|
|
2440
|
+
const result = await context.client.clips.get({ clipId });
|
|
2441
|
+
const clip = getClipRecord({ result });
|
|
2442
|
+
const nextAction = typeof clip.next_action === "string" ? clip.next_action : null;
|
|
2443
|
+
const status = typeof clip.status === "string" ? clip.status : null;
|
|
2444
|
+
if (nextAction !== "poll" || status === "failed" || status === "exported") {
|
|
2445
|
+
return result;
|
|
2446
|
+
}
|
|
2447
|
+
if (Date.now() - startedAt >= timeoutMs) {
|
|
2448
|
+
throw new Error(`Timed out waiting for clip ${clipId}. Last status: ${status ?? "unknown"}.`);
|
|
2449
|
+
}
|
|
2450
|
+
await sleep({ ms: intervalMs });
|
|
2451
|
+
}
|
|
2452
|
+
}
|
|
2453
|
+
function buildUploadAutoJobNotice({ flags }) {
|
|
2454
|
+
const jobs = [];
|
|
2455
|
+
if (getOptionalBooleanFlag({ flags, name: "auto-process" }) !== false) {
|
|
2456
|
+
jobs.push("process");
|
|
2457
|
+
}
|
|
2458
|
+
if (getOptionalBooleanFlag({ flags, name: "auto-transcribe" }) !== false) {
|
|
2459
|
+
jobs.push("transcribe");
|
|
2460
|
+
}
|
|
2461
|
+
if (getOptionalBooleanFlag({ flags, name: "auto-describe" }) !== false) {
|
|
2462
|
+
jobs.push("describe");
|
|
2463
|
+
}
|
|
2464
|
+
if (getOptionalBooleanFlag({ flags, name: "auto-suggest-shorts" }) !== false) {
|
|
2465
|
+
jobs.push("suggest-shorts");
|
|
2466
|
+
}
|
|
2467
|
+
return jobs;
|
|
2468
|
+
}
|
|
2469
|
+
function parseMediaWaitFor({ value, }) {
|
|
2470
|
+
if (value === undefined) {
|
|
2471
|
+
return "processing";
|
|
2472
|
+
}
|
|
2473
|
+
if (value === "processing" ||
|
|
2474
|
+
value === "transcription" ||
|
|
2475
|
+
value === "description" ||
|
|
2476
|
+
value === "shorts" ||
|
|
2477
|
+
value === "all") {
|
|
2478
|
+
return value;
|
|
2479
|
+
}
|
|
2480
|
+
throw new Error("--for must be processing, transcription, description, shorts, or all.");
|
|
2481
|
+
}
|
|
2482
|
+
function getReadyMediaStates({ asset, clippingStatus, }) {
|
|
2483
|
+
const ready = new Set();
|
|
2484
|
+
if (asset.processingStatus === "completed" ||
|
|
2485
|
+
asset.durationSeconds !== null) {
|
|
2486
|
+
ready.add("processing");
|
|
2487
|
+
}
|
|
2488
|
+
const readiness = isRecord(clippingStatus.readiness)
|
|
2489
|
+
? clippingStatus.readiness
|
|
2490
|
+
: {};
|
|
2491
|
+
if (readiness.transcript_ready === true) {
|
|
2492
|
+
ready.add("transcription");
|
|
2493
|
+
}
|
|
2494
|
+
if (asset.aiDescription && asset.aiDescription.trim().length > 0) {
|
|
2495
|
+
ready.add("description");
|
|
2496
|
+
}
|
|
2497
|
+
if (readiness.suggestions_ready === true) {
|
|
2498
|
+
ready.add("shorts");
|
|
2499
|
+
}
|
|
2500
|
+
return [...ready];
|
|
2501
|
+
}
|
|
2502
|
+
function mediaWaitSatisfied({ readyFor, waitFor, }) {
|
|
2503
|
+
if (waitFor === "all") {
|
|
2504
|
+
return (readyFor.includes("processing") &&
|
|
2505
|
+
readyFor.includes("transcription") &&
|
|
2506
|
+
readyFor.includes("description") &&
|
|
2507
|
+
readyFor.includes("shorts"));
|
|
2508
|
+
}
|
|
2509
|
+
return readyFor.includes(waitFor);
|
|
2510
|
+
}
|
|
2511
|
+
function parseShortContentProfile({ value, }) {
|
|
2512
|
+
if (value === undefined) {
|
|
2513
|
+
return undefined;
|
|
2514
|
+
}
|
|
2515
|
+
if (value === "auto" ||
|
|
2516
|
+
value === "podcast" ||
|
|
2517
|
+
value === "tutorial" ||
|
|
2518
|
+
value === "generic") {
|
|
2519
|
+
return value;
|
|
2520
|
+
}
|
|
2521
|
+
throw new Error("--content-profile must be auto, podcast, tutorial, or generic.");
|
|
2522
|
+
}
|
|
2523
|
+
function summarizeMistakePlan({ plan, }) {
|
|
2524
|
+
const record = isRecord(plan) ? plan : {};
|
|
2525
|
+
const segments = Array.isArray(record.segments)
|
|
2526
|
+
? record.segments
|
|
2527
|
+
: Array.isArray(record.analyzedSegments)
|
|
2528
|
+
? record.analyzedSegments
|
|
2529
|
+
: [];
|
|
2530
|
+
const verdictCounts = {};
|
|
2531
|
+
const topSegments = segments.slice(0, 20).map((segment) => {
|
|
2532
|
+
const item = isRecord(segment) ? segment : {};
|
|
2533
|
+
const verdict = typeof item.verdict === "string" ? item.verdict : "unknown";
|
|
2534
|
+
verdictCounts[verdict] = (verdictCounts[verdict] ?? 0) + 1;
|
|
2535
|
+
return {
|
|
2536
|
+
label: item.label,
|
|
2537
|
+
verdict,
|
|
2538
|
+
confidence: item.confidence,
|
|
2539
|
+
trim_start_seconds: item.trimStartSeconds,
|
|
2540
|
+
trim_end_seconds: item.trimEndSeconds,
|
|
2541
|
+
duration_seconds: item.durationSeconds,
|
|
2542
|
+
reason: item.reason,
|
|
2543
|
+
transcript_preview: item.transcriptPreview,
|
|
2544
|
+
};
|
|
2545
|
+
});
|
|
2546
|
+
return {
|
|
2547
|
+
timeline_item_id: record.timelineItemId,
|
|
2548
|
+
media_asset_id: record.mediaAssetId,
|
|
2549
|
+
summary: record.summary,
|
|
2550
|
+
verdict_counts: verdictCounts,
|
|
2551
|
+
segment_count: segments.length,
|
|
2552
|
+
top_segments: topSegments,
|
|
2553
|
+
truncated: segments.length > topSegments.length,
|
|
2554
|
+
};
|
|
2555
|
+
}
|
|
2556
|
+
function isRecord(value) {
|
|
2557
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2558
|
+
}
|
|
2559
|
+
function buildAgentContext({ commandFilter, surfaceName = "agent-context", }) {
|
|
2560
|
+
const commands = commandFilter
|
|
2561
|
+
? COMMAND_SPECS.filter((spec) => commandsEqual(spec.command, splitCommandFilter({ value: commandFilter })))
|
|
2562
|
+
: COMMAND_SPECS;
|
|
2563
|
+
if (commandFilter && commands.length === 0) {
|
|
2564
|
+
throw new CliUsageError({
|
|
2565
|
+
code: "unknown_command",
|
|
2566
|
+
message: `Unknown command for ${surfaceName}: ${commandFilter}`,
|
|
2567
|
+
suggestions: suggestCommands({
|
|
2568
|
+
command: splitCommandFilter({ value: commandFilter }),
|
|
2569
|
+
}).map((spec) => commandLabel({ command: spec.command })),
|
|
2570
|
+
});
|
|
2571
|
+
}
|
|
2572
|
+
return {
|
|
2573
|
+
schema_version: "1",
|
|
2574
|
+
cli: {
|
|
2575
|
+
name: "blitzreels",
|
|
2576
|
+
version: CLI_VERSION,
|
|
2577
|
+
default_base_url: DEFAULT_BASE_URL,
|
|
2578
|
+
},
|
|
2579
|
+
commands: commands.map((spec) => ({
|
|
2580
|
+
command: commandLabel({ command: spec.command }),
|
|
2581
|
+
description: spec.description,
|
|
2582
|
+
usage: spec.usage,
|
|
2583
|
+
mutates: spec.mutates,
|
|
2584
|
+
destructive: spec.destructive,
|
|
2585
|
+
requires_auth: spec.requiresAuth,
|
|
2586
|
+
flags: flagsForCommand({ spec }),
|
|
2587
|
+
examples: spec.examples,
|
|
2588
|
+
errors: buildCommandErrors({ spec }),
|
|
2589
|
+
})),
|
|
2590
|
+
};
|
|
2591
|
+
}
|
|
2592
|
+
function buildAgentPrime({ commandFilter, }) {
|
|
2593
|
+
const context = buildAgentContext({
|
|
2594
|
+
commandFilter,
|
|
2595
|
+
surfaceName: "prime",
|
|
2596
|
+
});
|
|
2597
|
+
return {
|
|
2598
|
+
schema_version: "1",
|
|
2599
|
+
cli: context.cli,
|
|
2600
|
+
instructions: [
|
|
2601
|
+
"Use the BlitzReels CLI for BlitzReels video project inspection, editing, captions, media placement, snapshots, and exports.",
|
|
2602
|
+
"Prefer CLI/SDK/API calls over database access. Do not diagnose normal media pipeline issues by reading Postgres first.",
|
|
2603
|
+
"Use --json for any command whose output you will parse.",
|
|
2604
|
+
"Inspect before editing: project inspect, project timeline, media get, captions list/words list, or clips get.",
|
|
2605
|
+
"Use --dry-run on supported mutating commands before risky edits. Destructive commands still require explicit confirmation flags.",
|
|
2606
|
+
"After every write, verify with project timeline, project inspect, project snapshot, project snapshots, media get, or clips get/wait.",
|
|
2607
|
+
...timelineLayerModelHelp(),
|
|
2608
|
+
"When a CLI command fails, read the structured error, suggestions, and usage. Do not retry random flag names.",
|
|
2609
|
+
],
|
|
2610
|
+
workflow: [
|
|
2611
|
+
"1. Run blitzreels auth whoami --json if auth state is uncertain.",
|
|
2612
|
+
"2. Orient with blitzreels project inspect --project-id PROJECT_ID --mode full --json or blitzreels project timeline --project-id PROJECT_ID --json.",
|
|
2613
|
+
"3. Select exact project, media, timeline, caption, or clip IDs from read commands.",
|
|
2614
|
+
"4. Apply the narrowest edit command. Use --dry-run first when available.",
|
|
2615
|
+
"5. Verify the resulting timeline, media state, clip state, snapshot, or export.",
|
|
2616
|
+
],
|
|
2617
|
+
command_schema_command: commandFilter === undefined
|
|
2618
|
+
? "blitzreels agent-context --json"
|
|
2619
|
+
: `blitzreels agent-context --command ${JSON.stringify(commandFilter)} --json`,
|
|
2620
|
+
command_filter: commandFilter ?? null,
|
|
2621
|
+
commands: context.commands,
|
|
2622
|
+
};
|
|
2623
|
+
}
|
|
2624
|
+
function timelineLayerModelHelp() {
|
|
2625
|
+
return [
|
|
2626
|
+
"Timeline layer rule: lower layer_index renders above higher layer_index.",
|
|
2627
|
+
"Default visual order: captions 0, text/effects 1, image B-roll 2, main video 3, audio 4, background/fill 5.",
|
|
2628
|
+
"Use layer_index 2 for normal visible image/video B-roll above the main clip; use 1 only when it must sit above other overlays, and avoid 0 unless replacing captions.",
|
|
2629
|
+
];
|
|
2630
|
+
}
|
|
2631
|
+
function splitCommandFilter({ value }) {
|
|
2632
|
+
return value
|
|
2633
|
+
.trim()
|
|
2634
|
+
.split(/\s+/)
|
|
2635
|
+
.filter((part) => part.length > 0);
|
|
2636
|
+
}
|
|
2637
|
+
function buildCommandErrors({ spec, }) {
|
|
2638
|
+
const errors = [
|
|
2639
|
+
{ code: "unknown_command", description: "Command is not registered." },
|
|
2640
|
+
{ code: "unknown_flag", description: "Flag is not accepted by command." },
|
|
2641
|
+
{ code: "cli_error", description: "Input or local execution failed." },
|
|
2642
|
+
];
|
|
2643
|
+
if (spec.requiresAuth) {
|
|
2644
|
+
errors.push({
|
|
2645
|
+
code: "api_error",
|
|
2646
|
+
description: "BlitzReels API returned a non-2xx response.",
|
|
2647
|
+
});
|
|
2648
|
+
}
|
|
2649
|
+
if (spec.mutates && !spec.dryRunSupported) {
|
|
2650
|
+
errors.push({
|
|
2651
|
+
code: "unsupported_dry_run",
|
|
2652
|
+
description: "--dry-run was used on a command that cannot preview.",
|
|
2653
|
+
});
|
|
2654
|
+
}
|
|
2655
|
+
return errors;
|
|
2656
|
+
}
|
|
2657
|
+
function buildAgentContextLines({ value }) {
|
|
2658
|
+
return value.commands.map((command) => `${command.command} ${command.mutates ? "write" : "read"}${command.destructive ? " destructive" : ""} - ${command.description}`);
|
|
2659
|
+
}
|
|
2660
|
+
function buildAgentPrimeLines({ value }) {
|
|
2661
|
+
const commandLines = value.commands.map((command) => `- ${command.command}: ${command.usage}${command.mutates ? " [writes]" : " [reads]"}${command.destructive ? " [destructive]" : ""}`);
|
|
2662
|
+
return [
|
|
2663
|
+
"<EXTREMELY_IMPORTANT>",
|
|
2664
|
+
"# BlitzReels CLI Usage Guide for AI Agents",
|
|
2665
|
+
"",
|
|
2666
|
+
...value.instructions.map((line) => `- ${line}`),
|
|
2667
|
+
"",
|
|
2668
|
+
"## Recommended Workflow",
|
|
2669
|
+
"",
|
|
2670
|
+
...value.workflow.map((line) => `- ${line}`),
|
|
2671
|
+
"",
|
|
2672
|
+
"## Machine-Readable Command Contract",
|
|
2673
|
+
"",
|
|
2674
|
+
`Run: \`${value.command_schema_command}\``,
|
|
2675
|
+
"",
|
|
2676
|
+
"## Relevant Commands",
|
|
2677
|
+
"",
|
|
2678
|
+
...commandLines,
|
|
2679
|
+
"",
|
|
2680
|
+
"</EXTREMELY_IMPORTANT>",
|
|
2681
|
+
];
|
|
2682
|
+
}
|
|
2683
|
+
function resolveDisplayEnvironment({ baseUrl, environment, }) {
|
|
2684
|
+
if (environment === "test" &&
|
|
2685
|
+
(baseUrl?.includes("localhost") || baseUrl?.includes("127.0.0.1"))) {
|
|
2686
|
+
return "local";
|
|
2687
|
+
}
|
|
2688
|
+
return environment;
|
|
2689
|
+
}
|
|
2690
|
+
function buildProjectDashboardUrl({ baseUrl, projectId, }) {
|
|
2691
|
+
const url = new URL(baseUrl ?? DEFAULT_BASE_URL);
|
|
2692
|
+
if (url.pathname.startsWith("/api/")) {
|
|
2693
|
+
url.pathname = `/dashboard/projects/${encodeURIComponent(projectId)}`;
|
|
2694
|
+
}
|
|
2695
|
+
else {
|
|
2696
|
+
url.pathname = `/dashboard/projects/${encodeURIComponent(projectId)}`;
|
|
2697
|
+
}
|
|
2698
|
+
url.search = "";
|
|
2699
|
+
url.hash = "";
|
|
2700
|
+
return url.toString();
|
|
2701
|
+
}
|
|
2702
|
+
function buildDryRunPreview({ context, }) {
|
|
2703
|
+
const spec = findCommandSpec({ command: context.args.command });
|
|
2704
|
+
if (!spec?.dryRunSupported) {
|
|
2705
|
+
throw new CliUsageError({
|
|
2706
|
+
code: "unsupported_dry_run",
|
|
2707
|
+
message: "--dry-run is not supported for this command.",
|
|
2708
|
+
usage: spec?.usage,
|
|
2709
|
+
});
|
|
2710
|
+
}
|
|
2711
|
+
const [group, action, subaction] = context.args.command;
|
|
2712
|
+
const flags = context.args.flags;
|
|
2713
|
+
const projectId = () => requireStringFlag({ flags, name: "project-id" });
|
|
2714
|
+
const assetId = () => requireStringFlag({ flags, name: "asset-id" });
|
|
2715
|
+
const folderId = () => requireStringFlag({ flags, name: "folder-id" });
|
|
2716
|
+
const timelineItemId = () => requireStringFlag({ flags, name: "timeline-item-id" });
|
|
2717
|
+
if (group === "workspace" &&
|
|
2718
|
+
action === "settings" &&
|
|
2719
|
+
subaction === "update") {
|
|
2720
|
+
return dryRunRequest({
|
|
2721
|
+
spec,
|
|
2722
|
+
method: "PATCH",
|
|
2723
|
+
path: "/workspace/settings",
|
|
2724
|
+
body: {
|
|
2725
|
+
name: getStringFlag({ flags, name: "name" }),
|
|
2726
|
+
description: parseNullableStringFlag({ flags, name: "description" }),
|
|
2727
|
+
protected_words: parseCsvFlag({ flags, name: "protected-words" }),
|
|
2728
|
+
icon_storage_key: parseNullableStringFlag({
|
|
2729
|
+
flags,
|
|
2730
|
+
name: "icon-storage-key",
|
|
2731
|
+
}),
|
|
2732
|
+
defaults: toApiWorkspaceDefaults({ flags }),
|
|
2733
|
+
},
|
|
2734
|
+
});
|
|
2735
|
+
}
|
|
2736
|
+
if (group === "projects" && action === "create") {
|
|
2737
|
+
return dryRunRequest({
|
|
2738
|
+
spec,
|
|
2739
|
+
method: "POST",
|
|
2740
|
+
path: "/projects",
|
|
2741
|
+
body: {
|
|
2742
|
+
name: requireStringFlag({ flags, name: "name" }),
|
|
2743
|
+
description: getStringFlag({ flags, name: "description" }),
|
|
2744
|
+
aspect_ratio: parseAspectRatio({
|
|
2745
|
+
value: getStringFlag({ flags, name: "aspect-ratio" }),
|
|
2746
|
+
}),
|
|
2747
|
+
project_type: parseProjectType({
|
|
2748
|
+
value: getStringFlag({ flags, name: "project-type" }),
|
|
2749
|
+
}),
|
|
2750
|
+
carousel_settings: parseJsonObjectFlag({
|
|
2751
|
+
flags,
|
|
2752
|
+
name: "carousel-settings-json",
|
|
2753
|
+
}),
|
|
2754
|
+
},
|
|
2755
|
+
});
|
|
2756
|
+
}
|
|
2757
|
+
if (group === "projects" && (action === "rename" || action === "update")) {
|
|
2758
|
+
return dryRunRequest({
|
|
2759
|
+
spec,
|
|
2760
|
+
method: "PATCH",
|
|
2761
|
+
path: `/projects/${encodeURIComponent(projectId())}`,
|
|
2762
|
+
body: {
|
|
2763
|
+
name: action === "rename"
|
|
2764
|
+
? requireStringFlag({ flags, name: "name" })
|
|
2765
|
+
: getStringFlag({ flags, name: "name" }),
|
|
2766
|
+
description: action === "rename"
|
|
2767
|
+
? undefined
|
|
2768
|
+
: parseNullableStringFlag({ flags, name: "description" }),
|
|
2769
|
+
},
|
|
2770
|
+
});
|
|
2771
|
+
}
|
|
2772
|
+
if (group === "projects" && action === "delete") {
|
|
2773
|
+
requireDeleteConfirmation({
|
|
2774
|
+
confirmed: getBooleanFlag({ flags, name: "confirm-delete" }),
|
|
2775
|
+
});
|
|
2776
|
+
return dryRunRequest({
|
|
2777
|
+
spec,
|
|
2778
|
+
method: "DELETE",
|
|
2779
|
+
path: `/projects/${encodeURIComponent(projectId())}`,
|
|
2780
|
+
body: undefined,
|
|
2781
|
+
});
|
|
2782
|
+
}
|
|
2783
|
+
if (group === "media" && action === "delete") {
|
|
2784
|
+
requireDeleteConfirmation({
|
|
2785
|
+
confirmed: getBooleanFlag({ flags, name: "confirm-delete" }),
|
|
2786
|
+
});
|
|
2787
|
+
return dryRunRequest({
|
|
2788
|
+
spec,
|
|
2789
|
+
method: "POST",
|
|
2790
|
+
path: `/workspace/media/assets/${encodeURIComponent(assetId())}/delete`,
|
|
2791
|
+
body: {},
|
|
2792
|
+
});
|
|
2793
|
+
}
|
|
2794
|
+
if (group === "media" && (action === "update" || action === "rename")) {
|
|
2795
|
+
return dryRunRequest({
|
|
2796
|
+
spec,
|
|
2797
|
+
method: "PATCH",
|
|
2798
|
+
path: `/workspace/media/assets/${encodeURIComponent(assetId())}`,
|
|
2799
|
+
body: {
|
|
2800
|
+
name: action === "rename"
|
|
2801
|
+
? requireStringFlag({ flags, name: "name" })
|
|
2802
|
+
: getStringFlag({ flags, name: "name" }),
|
|
2803
|
+
description: action === "rename"
|
|
2804
|
+
? undefined
|
|
2805
|
+
: parseNullableStringFlag({ flags, name: "description" }),
|
|
2806
|
+
folder_id: action === "rename"
|
|
2807
|
+
? undefined
|
|
2808
|
+
: parseFolderIdFlag({ flags, name: "folder-id" }),
|
|
2809
|
+
allow_using_as_broll: action === "rename"
|
|
2810
|
+
? undefined
|
|
2811
|
+
: getOptionalBooleanFlag({ flags, name: "allow-broll" }),
|
|
2812
|
+
},
|
|
2813
|
+
});
|
|
2814
|
+
}
|
|
2815
|
+
if (group === "media" && action === "move") {
|
|
2816
|
+
return dryRunRequest({
|
|
2817
|
+
spec,
|
|
2818
|
+
method: "POST",
|
|
2819
|
+
path: "/workspace/media/assets/move",
|
|
2820
|
+
body: {
|
|
2821
|
+
asset_ids: parseAssetIds({ flags }),
|
|
2822
|
+
target_folder_id: requireFolderIdFlag({
|
|
2823
|
+
flags,
|
|
2824
|
+
name: "target-folder-id",
|
|
2825
|
+
}),
|
|
2826
|
+
},
|
|
2827
|
+
});
|
|
2828
|
+
}
|
|
2829
|
+
if ((group === "media" && action === "attach") ||
|
|
2830
|
+
(group === "broll" && action === "add")) {
|
|
2831
|
+
return dryRunRequest({
|
|
2832
|
+
spec,
|
|
2833
|
+
method: "POST",
|
|
2834
|
+
path: `/projects/${encodeURIComponent(projectId())}/timeline/media`,
|
|
2835
|
+
body: {
|
|
2836
|
+
items: [
|
|
2837
|
+
{
|
|
2838
|
+
asset_id: assetId(),
|
|
2839
|
+
start_seconds: getStringFlag({ flags, name: "at" }) === undefined
|
|
2840
|
+
? 0
|
|
2841
|
+
: parseTimecode({
|
|
2842
|
+
value: requireStringFlag({ flags, name: "at" }),
|
|
2843
|
+
}),
|
|
2844
|
+
duration_seconds: getNumberFlag({ flags, name: "duration" }) ?? null,
|
|
2845
|
+
position_preset: toApiMediaPositionPreset({
|
|
2846
|
+
value: parseMediaPositionPreset({
|
|
2847
|
+
value: getStringFlag({ flags, name: "position-preset" }),
|
|
2848
|
+
}),
|
|
2849
|
+
}),
|
|
2850
|
+
position_x: getNumberFlag({ flags, name: "position-x" }),
|
|
2851
|
+
position_y: getNumberFlag({ flags, name: "position-y" }),
|
|
2852
|
+
width_px: getNumberFlag({ flags, name: "width-px" }),
|
|
2853
|
+
height_px: getNumberFlag({ flags, name: "height-px" }),
|
|
2854
|
+
scale: getNumberFlag({ flags, name: "scale" }),
|
|
2855
|
+
opacity: getNumberFlag({ flags, name: "opacity" }),
|
|
2856
|
+
animation_preset: toApiMediaAnimationPreset({
|
|
2857
|
+
value: parseMediaAnimationPreset({
|
|
2858
|
+
value: getStringFlag({ flags, name: "animation-preset" }),
|
|
2859
|
+
}),
|
|
2860
|
+
}),
|
|
2861
|
+
layer_index: getNumberFlag({ flags, name: "layer-index" }),
|
|
2862
|
+
},
|
|
2863
|
+
],
|
|
2864
|
+
allow_duplicate: getOptionalBooleanFlag({
|
|
2865
|
+
flags,
|
|
2866
|
+
name: "allow-duplicate",
|
|
2867
|
+
}),
|
|
2868
|
+
},
|
|
2869
|
+
});
|
|
2870
|
+
}
|
|
2871
|
+
if (group === "media" && action === "overlay" && subaction === "update") {
|
|
2872
|
+
return dryRunRequest({
|
|
2873
|
+
spec,
|
|
2874
|
+
method: "PATCH",
|
|
2875
|
+
path: `/projects/${encodeURIComponent(projectId())}/timeline/media/${encodeURIComponent(requireStringFlag({ flags, name: "timeline-item-id" }))}`,
|
|
2876
|
+
body: {
|
|
2877
|
+
position_preset: toApiMediaPositionPreset({
|
|
2878
|
+
value: parseMediaPositionPreset({
|
|
2879
|
+
value: getStringFlag({ flags, name: "position-preset" }),
|
|
2880
|
+
}),
|
|
2881
|
+
}),
|
|
2882
|
+
position_x: getNumberFlag({ flags, name: "position-x" }),
|
|
2883
|
+
position_y: getNumberFlag({ flags, name: "position-y" }),
|
|
2884
|
+
width_px: getNumberFlag({ flags, name: "width-px" }),
|
|
2885
|
+
height_px: getNumberFlag({ flags, name: "height-px" }),
|
|
2886
|
+
scale: getNumberFlag({ flags, name: "scale" }),
|
|
2887
|
+
animation_preset: toApiMediaAnimationPreset({
|
|
2888
|
+
value: parseMediaAnimationPreset({
|
|
2889
|
+
value: getStringFlag({ flags, name: "animation-preset" }),
|
|
2890
|
+
}),
|
|
2891
|
+
}),
|
|
2892
|
+
remove_animations: getOptionalBooleanFlag({
|
|
2893
|
+
flags,
|
|
2894
|
+
name: "remove-animations",
|
|
2895
|
+
}),
|
|
2896
|
+
blurred_background: getOptionalBooleanFlag({
|
|
2897
|
+
flags,
|
|
2898
|
+
name: "blurred-background",
|
|
2899
|
+
}),
|
|
2900
|
+
},
|
|
2901
|
+
});
|
|
2902
|
+
}
|
|
2903
|
+
if (group === "clips" && action === "create") {
|
|
2904
|
+
const options = toCreateClipOptions({ flags });
|
|
2905
|
+
return dryRunRequest({
|
|
2906
|
+
spec,
|
|
2907
|
+
method: "POST",
|
|
2908
|
+
path: "/clips",
|
|
2909
|
+
body: {
|
|
2910
|
+
project_name: options.projectName,
|
|
2911
|
+
source: {
|
|
2912
|
+
source_type: options.source.sourceType,
|
|
2913
|
+
asset_id: options.source.assetId,
|
|
2914
|
+
youtube_url: options.source.youtubeUrl,
|
|
2915
|
+
},
|
|
2916
|
+
selection: {
|
|
2917
|
+
selection_mode: options.selection.mode,
|
|
2918
|
+
suggestion_id: options.selection.suggestionId,
|
|
2919
|
+
start_seconds: options.selection.startSeconds,
|
|
2920
|
+
end_seconds: options.selection.endSeconds,
|
|
2921
|
+
},
|
|
2922
|
+
target: {
|
|
2923
|
+
aspect_ratio: options.target.aspectRatio,
|
|
2924
|
+
min_duration_seconds: options.target.minDurationSeconds,
|
|
2925
|
+
max_duration_seconds: options.target.maxDurationSeconds,
|
|
2926
|
+
},
|
|
2927
|
+
layout: {
|
|
2928
|
+
layout_mode: options.layout.mode,
|
|
2929
|
+
content_type_hint: options.layout.contentTypeHint,
|
|
2930
|
+
},
|
|
2931
|
+
captions: {
|
|
2932
|
+
enabled: options.captions.enabled,
|
|
2933
|
+
style_id: options.captions.styleId,
|
|
2934
|
+
},
|
|
2935
|
+
qa: {
|
|
2936
|
+
qa_mode: options.qa.mode,
|
|
2937
|
+
},
|
|
2938
|
+
export: {
|
|
2939
|
+
auto_export: options.export.autoExport,
|
|
2940
|
+
},
|
|
2941
|
+
},
|
|
2942
|
+
});
|
|
2943
|
+
}
|
|
2944
|
+
if (group === "clips" && action === "reselect") {
|
|
2945
|
+
const target = buildClipTarget({ flags, requireMaxDuration: false });
|
|
2946
|
+
const selection = buildClipSelection({ flags });
|
|
2947
|
+
return dryRunRequest({
|
|
2948
|
+
spec,
|
|
2949
|
+
method: "POST",
|
|
2950
|
+
path: `/clips/${encodeURIComponent(requireStringFlag({ flags, name: "clip-id" }))}/reselect`,
|
|
2951
|
+
body: {
|
|
2952
|
+
selection: {
|
|
2953
|
+
selection_mode: selection.mode,
|
|
2954
|
+
suggestion_id: selection.suggestionId,
|
|
2955
|
+
start_seconds: selection.startSeconds,
|
|
2956
|
+
end_seconds: selection.endSeconds,
|
|
2957
|
+
},
|
|
2958
|
+
target: target === null
|
|
2959
|
+
? null
|
|
2960
|
+
: {
|
|
2961
|
+
min_duration_seconds: target.minDurationSeconds,
|
|
2962
|
+
max_duration_seconds: target.maxDurationSeconds,
|
|
2963
|
+
},
|
|
2964
|
+
},
|
|
2965
|
+
});
|
|
2966
|
+
}
|
|
2967
|
+
if (group === "clips" && action === "repair") {
|
|
2968
|
+
return dryRunRequest({
|
|
2969
|
+
spec,
|
|
2970
|
+
method: "POST",
|
|
2971
|
+
path: `/clips/${encodeURIComponent(requireStringFlag({ flags, name: "clip-id" }))}/repair`,
|
|
2972
|
+
body: {
|
|
2973
|
+
repair_mode: parseClipRepairMode({
|
|
2974
|
+
value: getStringFlag({ flags, name: "repair-mode" }),
|
|
2975
|
+
}),
|
|
2976
|
+
},
|
|
2977
|
+
});
|
|
2978
|
+
}
|
|
2979
|
+
if (group === "clips" && action === "export") {
|
|
2980
|
+
return dryRunRequest({
|
|
2981
|
+
spec,
|
|
2982
|
+
method: "POST",
|
|
2983
|
+
path: `/clips/${encodeURIComponent(requireStringFlag({ flags, name: "clip-id" }))}/export`,
|
|
2984
|
+
body: {
|
|
2985
|
+
resolution: parseClipExportResolution({
|
|
2986
|
+
value: getStringFlag({ flags, name: "resolution" }),
|
|
2987
|
+
}),
|
|
2988
|
+
format: parseClipExportFormat({
|
|
2989
|
+
value: getStringFlag({ flags, name: "format" }),
|
|
2990
|
+
}),
|
|
2991
|
+
allow_blocking_qa_bypass: getOptionalBooleanFlag({
|
|
2992
|
+
flags,
|
|
2993
|
+
name: "allow-blocking-qa-bypass",
|
|
2994
|
+
}) ?? false,
|
|
2995
|
+
},
|
|
2996
|
+
});
|
|
2997
|
+
}
|
|
2998
|
+
if (group === "media" && action === "folders" && subaction === "create") {
|
|
2999
|
+
return dryRunRequest({
|
|
3000
|
+
spec,
|
|
3001
|
+
method: "POST",
|
|
3002
|
+
path: "/workspace/media/folders",
|
|
3003
|
+
body: {
|
|
3004
|
+
name: requireStringFlag({ flags, name: "name" }),
|
|
3005
|
+
parent_folder_id: parseFolderIdFlag({
|
|
3006
|
+
flags,
|
|
3007
|
+
name: "parent-folder-id",
|
|
3008
|
+
}),
|
|
3009
|
+
description: parseNullableStringFlag({ flags, name: "description" }),
|
|
3010
|
+
icon_type: getStringFlag({ flags, name: "icon-type" }),
|
|
3011
|
+
},
|
|
3012
|
+
});
|
|
3013
|
+
}
|
|
3014
|
+
if (group === "media" &&
|
|
3015
|
+
action === "folders" &&
|
|
3016
|
+
(subaction === "rename" || subaction === "update")) {
|
|
3017
|
+
return dryRunRequest({
|
|
3018
|
+
spec,
|
|
3019
|
+
method: "PATCH",
|
|
3020
|
+
path: `/workspace/media/folders/${encodeURIComponent(folderId())}`,
|
|
3021
|
+
body: {
|
|
3022
|
+
name: subaction === "rename"
|
|
3023
|
+
? requireStringFlag({ flags, name: "name" })
|
|
3024
|
+
: getStringFlag({ flags, name: "name" }),
|
|
3025
|
+
parent_folder_id: subaction === "rename"
|
|
3026
|
+
? undefined
|
|
3027
|
+
: parseFolderIdFlag({ flags, name: "parent-folder-id" }),
|
|
3028
|
+
description: subaction === "rename"
|
|
3029
|
+
? undefined
|
|
3030
|
+
: parseNullableStringFlag({ flags, name: "description" }),
|
|
3031
|
+
icon_type: subaction === "rename"
|
|
3032
|
+
? undefined
|
|
3033
|
+
: getStringFlag({ flags, name: "icon-type" }),
|
|
3034
|
+
},
|
|
3035
|
+
});
|
|
3036
|
+
}
|
|
3037
|
+
if (group === "media" && action === "folders" && subaction === "delete") {
|
|
3038
|
+
requireDeleteConfirmation({
|
|
3039
|
+
confirmed: getBooleanFlag({ flags, name: "confirm-delete" }),
|
|
3040
|
+
});
|
|
3041
|
+
return dryRunRequest({
|
|
3042
|
+
spec,
|
|
3043
|
+
method: "DELETE",
|
|
3044
|
+
path: `/workspace/media/folders/${encodeURIComponent(folderId())}`,
|
|
3045
|
+
query: { cascade: getOptionalBooleanFlag({ flags, name: "cascade" }) },
|
|
3046
|
+
body: undefined,
|
|
3047
|
+
});
|
|
3048
|
+
}
|
|
3049
|
+
if (group === "text" && action === "add") {
|
|
3050
|
+
const text = requireStringFlag({ flags, name: "text" });
|
|
3051
|
+
const durationSeconds = getNumberFlag({ flags, name: "duration" }) ?? 4;
|
|
3052
|
+
return dryRunRequest({
|
|
3053
|
+
spec,
|
|
3054
|
+
method: "POST",
|
|
3055
|
+
path: `/projects/${encodeURIComponent(projectId())}/content-items`,
|
|
3056
|
+
body: {
|
|
3057
|
+
kind: "overlay",
|
|
3058
|
+
text,
|
|
3059
|
+
name: getStringFlag({ flags, name: "name" }),
|
|
3060
|
+
spec: parseJsonObjectFlag({ flags, name: "spec-json" }) ??
|
|
3061
|
+
(hasTextStyleFlags({ flags })
|
|
3062
|
+
? buildTextOverlaySpec({
|
|
3063
|
+
text,
|
|
3064
|
+
name: getStringFlag({ flags, name: "name" }),
|
|
3065
|
+
durationSeconds,
|
|
3066
|
+
flags,
|
|
3067
|
+
})
|
|
3068
|
+
: undefined),
|
|
3069
|
+
start_seconds: parseTimecode({
|
|
3070
|
+
value: getStringFlag({ flags, name: "at" }) ?? "0",
|
|
3071
|
+
}),
|
|
3072
|
+
duration_seconds: durationSeconds,
|
|
3073
|
+
layer_index: getNumberFlag({ flags, name: "layer-index" }),
|
|
3074
|
+
},
|
|
3075
|
+
});
|
|
3076
|
+
}
|
|
3077
|
+
if (group === "text" && action === "update") {
|
|
3078
|
+
return dryRunRequest({
|
|
3079
|
+
spec,
|
|
3080
|
+
method: "PATCH",
|
|
3081
|
+
path: `/projects/${encodeURIComponent(projectId())}/content-items/${encodeURIComponent(requireStringFlag({ flags, name: "content-item-id" }))}`,
|
|
3082
|
+
body: {
|
|
3083
|
+
name: getStringFlag({ flags, name: "name" }),
|
|
3084
|
+
text: getStringFlag({ flags, name: "text" }),
|
|
3085
|
+
spec: parseJsonObjectFlag({ flags, name: "spec-json" }),
|
|
3086
|
+
duration_seconds: getNumberFlag({ flags, name: "duration" }),
|
|
3087
|
+
},
|
|
3088
|
+
notes: hasTextStyleFlags({ flags })
|
|
3089
|
+
? [
|
|
3090
|
+
"Style flags require reading the existing content item before building the final spec during real execution.",
|
|
3091
|
+
]
|
|
3092
|
+
: undefined,
|
|
3093
|
+
});
|
|
3094
|
+
}
|
|
3095
|
+
if (group === "text" && action === "remove") {
|
|
3096
|
+
return dryRunRequest({
|
|
3097
|
+
spec,
|
|
3098
|
+
method: "DELETE",
|
|
3099
|
+
path: `/projects/${encodeURIComponent(projectId())}/content-items/${encodeURIComponent(requireStringFlag({ flags, name: "content-item-id" }))}`,
|
|
3100
|
+
body: undefined,
|
|
3101
|
+
});
|
|
3102
|
+
}
|
|
3103
|
+
if (group === "timeline" && action === "transform") {
|
|
3104
|
+
return dryRunRequest({
|
|
3105
|
+
spec,
|
|
3106
|
+
method: "POST",
|
|
3107
|
+
path: `/projects/${encodeURIComponent(projectId())}/timeline/edits`,
|
|
3108
|
+
body: {
|
|
3109
|
+
action: "transform",
|
|
3110
|
+
timeline_item_id: timelineItemId(),
|
|
3111
|
+
position_x: getNumberFlag({ flags, name: "position-x" }),
|
|
3112
|
+
position_y: getNumberFlag({ flags, name: "position-y" }),
|
|
3113
|
+
width_px: parseNullableNumberFlag({ flags, name: "width-px" }),
|
|
3114
|
+
height_px: parseNullableNumberFlag({ flags, name: "height-px" }),
|
|
3115
|
+
keep_aspect_ratio: getOptionalBooleanFlag({
|
|
3116
|
+
flags,
|
|
3117
|
+
name: "keep-aspect-ratio",
|
|
3118
|
+
}),
|
|
3119
|
+
},
|
|
3120
|
+
});
|
|
3121
|
+
}
|
|
3122
|
+
if (group === "timeline" && action === "move") {
|
|
3123
|
+
return dryRunRequest({
|
|
3124
|
+
spec,
|
|
3125
|
+
method: "POST",
|
|
3126
|
+
path: `/projects/${encodeURIComponent(projectId())}/timeline/edits`,
|
|
3127
|
+
body: {
|
|
3128
|
+
action: "move",
|
|
3129
|
+
timeline_item_id: timelineItemId(),
|
|
3130
|
+
new_start_seconds: parseTimecode({
|
|
3131
|
+
value: requireStringFlag({ flags, name: "at" }),
|
|
3132
|
+
}),
|
|
3133
|
+
new_layer_index: getNumberFlag({ flags, name: "layer-index" }),
|
|
3134
|
+
},
|
|
3135
|
+
});
|
|
3136
|
+
}
|
|
3137
|
+
if (group === "timeline" && action === "delete") {
|
|
3138
|
+
return dryRunRequest({
|
|
3139
|
+
spec,
|
|
3140
|
+
method: "POST",
|
|
3141
|
+
path: `/projects/${encodeURIComponent(projectId())}/timeline/edits`,
|
|
3142
|
+
body: {
|
|
3143
|
+
action: "delete",
|
|
3144
|
+
timeline_item_ids: parseTimelineItemIds({ flags }),
|
|
3145
|
+
auto_pack: getOptionalBooleanFlag({ flags, name: "auto-pack" }),
|
|
3146
|
+
},
|
|
3147
|
+
});
|
|
3148
|
+
}
|
|
3149
|
+
if (group === "audio" && action === "add") {
|
|
3150
|
+
return dryRunRequest({
|
|
3151
|
+
spec,
|
|
3152
|
+
method: "POST",
|
|
3153
|
+
path: `/projects/${encodeURIComponent(projectId())}/timeline/audio`,
|
|
3154
|
+
body: {
|
|
3155
|
+
items: [
|
|
3156
|
+
{
|
|
3157
|
+
asset_id: assetId(),
|
|
3158
|
+
start_seconds: parseTimecode({
|
|
3159
|
+
value: getStringFlag({ flags, name: "at" }) ?? "0",
|
|
3160
|
+
}),
|
|
3161
|
+
duration_seconds: getNumberFlag({ flags, name: "duration" }),
|
|
3162
|
+
volume: getNumberFlag({ flags, name: "volume" }),
|
|
3163
|
+
fade_in_seconds: getNumberFlag({ flags, name: "fade-in" }),
|
|
3164
|
+
fade_out_seconds: getNumberFlag({ flags, name: "fade-out" }),
|
|
3165
|
+
loop: getOptionalBooleanFlag({ flags, name: "loop" }),
|
|
3166
|
+
},
|
|
3167
|
+
],
|
|
3168
|
+
},
|
|
3169
|
+
});
|
|
3170
|
+
}
|
|
3171
|
+
if (group === "audio" && action === "import") {
|
|
3172
|
+
return dryRunRequest({
|
|
3173
|
+
spec,
|
|
3174
|
+
method: "POST",
|
|
3175
|
+
path: "/audio-library/import",
|
|
3176
|
+
body: {
|
|
3177
|
+
provider: parseAudioLibraryProvider({
|
|
3178
|
+
value: requireStringFlag({ flags, name: "provider" }),
|
|
3179
|
+
}),
|
|
3180
|
+
external_id: requireStringFlag({ flags, name: "external-id" }),
|
|
3181
|
+
folder_id: parseFolderIdFlag({ flags, name: "folder-id" }),
|
|
3182
|
+
project_id: getStringFlag({ flags, name: "project-id" }),
|
|
3183
|
+
start_seconds: parseTimecode({
|
|
3184
|
+
value: getStringFlag({ flags, name: "at" }) ?? "0",
|
|
3185
|
+
}),
|
|
3186
|
+
duration_seconds: parseNullableNumberFlag({ flags, name: "duration" }),
|
|
3187
|
+
volume: getNumberFlag({ flags, name: "volume" }),
|
|
3188
|
+
fade_in_seconds: getNumberFlag({ flags, name: "fade-in" }),
|
|
3189
|
+
fade_out_seconds: getNumberFlag({ flags, name: "fade-out" }),
|
|
3190
|
+
loop: getOptionalBooleanFlag({ flags, name: "loop" }),
|
|
3191
|
+
audio_type: parseAudioLibraryType({
|
|
3192
|
+
value: getStringFlag({ flags, name: "audio-type" }),
|
|
3193
|
+
}),
|
|
3194
|
+
},
|
|
3195
|
+
});
|
|
3196
|
+
}
|
|
3197
|
+
if (group === "audio" && action === "update") {
|
|
3198
|
+
return dryRunRequest({
|
|
3199
|
+
spec,
|
|
3200
|
+
method: "PATCH",
|
|
3201
|
+
path: `/projects/${encodeURIComponent(projectId())}/timeline/audio/${encodeURIComponent(requireStringFlag({ flags, name: "timeline-item-id" }))}`,
|
|
3202
|
+
body: {
|
|
3203
|
+
duration_seconds: getNumberFlag({ flags, name: "duration" }),
|
|
3204
|
+
volume: getNumberFlag({ flags, name: "volume" }),
|
|
3205
|
+
fade_in_seconds: getNumberFlag({ flags, name: "fade-in" }),
|
|
3206
|
+
fade_out_seconds: getNumberFlag({ flags, name: "fade-out" }),
|
|
3207
|
+
},
|
|
3208
|
+
});
|
|
3209
|
+
}
|
|
3210
|
+
if (group === "library" && action === "import") {
|
|
3211
|
+
const libraryAssetId = requireStringFlag({
|
|
3212
|
+
flags,
|
|
3213
|
+
name: "library-asset-id",
|
|
3214
|
+
});
|
|
3215
|
+
return dryRunRequest({
|
|
3216
|
+
spec,
|
|
3217
|
+
method: "POST",
|
|
3218
|
+
path: `/library/broll/${encodeURIComponent(libraryAssetId)}/import`,
|
|
3219
|
+
body: {
|
|
3220
|
+
folder_id: parseFolderIdFlag({ flags, name: "folder-id" }),
|
|
3221
|
+
},
|
|
3222
|
+
});
|
|
3223
|
+
}
|
|
3224
|
+
if (group === "captions" && action === "words") {
|
|
3225
|
+
return buildCaptionWordsDryRun({ spec, flags, subaction });
|
|
3226
|
+
}
|
|
3227
|
+
if (group === "captions" &&
|
|
3228
|
+
(action === "list-words" ||
|
|
3229
|
+
action === "style-words" ||
|
|
3230
|
+
action === "emphasize")) {
|
|
3231
|
+
const aliasSubaction = action === "list-words"
|
|
3232
|
+
? "list"
|
|
3233
|
+
: action === "style-words"
|
|
3234
|
+
? "style"
|
|
3235
|
+
: "emphasis";
|
|
3236
|
+
return buildCaptionWordsDryRun({ spec, flags, subaction: aliasSubaction });
|
|
3237
|
+
}
|
|
3238
|
+
if (group === "captions" &&
|
|
3239
|
+
(action === "delete" || action === "hide" || action === "show")) {
|
|
3240
|
+
return buildCaptionTrackDryRun({ spec, flags, action });
|
|
3241
|
+
}
|
|
3242
|
+
if (group === "captions" && action === "theme" && subaction === "set") {
|
|
3243
|
+
return dryRunRequest({
|
|
3244
|
+
spec,
|
|
3245
|
+
method: "POST",
|
|
3246
|
+
path: `/projects/${encodeURIComponent(projectId())}/caption-theme`,
|
|
3247
|
+
body: {
|
|
3248
|
+
theme_id: requireStringFlag({ flags, name: "theme-id" }),
|
|
3249
|
+
},
|
|
3250
|
+
});
|
|
3251
|
+
}
|
|
3252
|
+
if (group === "captions" && action === "theme" && subaction === "update") {
|
|
3253
|
+
return dryRunRequest({
|
|
3254
|
+
spec,
|
|
3255
|
+
method: "PATCH",
|
|
3256
|
+
path: `/caption-themes/${encodeURIComponent(requireStringFlag({ flags, name: "theme-id" }))}`,
|
|
3257
|
+
body: {
|
|
3258
|
+
name: getStringFlag({ flags, name: "name" }),
|
|
3259
|
+
description: getStringFlag({ flags, name: "description" }),
|
|
3260
|
+
settings: buildCaptionThemeSettingsPatch({ flags }),
|
|
3261
|
+
},
|
|
3262
|
+
});
|
|
3263
|
+
}
|
|
3264
|
+
if (group === "captions" &&
|
|
3265
|
+
action === "theme" &&
|
|
3266
|
+
subaction === "set-default") {
|
|
3267
|
+
return dryRunRequest({
|
|
3268
|
+
spec,
|
|
3269
|
+
method: "POST",
|
|
3270
|
+
path: `/caption-themes/${encodeURIComponent(requireStringFlag({ flags, name: "theme-id" }))}/set-default`,
|
|
3271
|
+
body: {},
|
|
3272
|
+
});
|
|
3273
|
+
}
|
|
3274
|
+
if (group === "exports" && action === "start") {
|
|
3275
|
+
return dryRunRequest({
|
|
3276
|
+
spec,
|
|
3277
|
+
method: "POST",
|
|
3278
|
+
path: `/projects/${encodeURIComponent(projectId())}/export`,
|
|
3279
|
+
body: {
|
|
3280
|
+
resolution: getStringFlag({ flags, name: "resolution" }),
|
|
3281
|
+
format: getStringFlag({ flags, name: "format" }),
|
|
3282
|
+
},
|
|
3283
|
+
});
|
|
3284
|
+
}
|
|
3285
|
+
throw new CliUsageError({
|
|
3286
|
+
code: "unsupported_dry_run",
|
|
3287
|
+
message: `--dry-run is not implemented for ${commandLabel({ command: spec.command })}.`,
|
|
3288
|
+
usage: spec.usage,
|
|
1742
3289
|
});
|
|
1743
3290
|
}
|
|
1744
|
-
|
|
1745
|
-
const
|
|
1746
|
-
const
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
const shouldOpenBrowser = !getBooleanFlag({
|
|
1759
|
-
flags: context.args.flags,
|
|
1760
|
-
name: "no-browser",
|
|
1761
|
-
});
|
|
1762
|
-
if (shouldOpenBrowser) {
|
|
1763
|
-
openBrowser({ url: login.verification_uri_complete });
|
|
3291
|
+
function buildCaptionWordsDryRun({ spec, flags, subaction, }) {
|
|
3292
|
+
const projectId = requireStringFlag({ flags, name: "project-id" });
|
|
3293
|
+
const basePath = `/projects/${encodeURIComponent(projectId)}/captions/words`;
|
|
3294
|
+
if (subaction === "text") {
|
|
3295
|
+
return dryRunRequest({
|
|
3296
|
+
spec,
|
|
3297
|
+
method: "POST",
|
|
3298
|
+
path: `${basePath}/text`,
|
|
3299
|
+
body: {
|
|
3300
|
+
word_id: requireStringFlag({ flags, name: "word-id" }),
|
|
3301
|
+
new_text: getStringFlag({ flags, name: "new-text" }) ??
|
|
3302
|
+
requireStringFlag({ flags, name: "text" }),
|
|
3303
|
+
},
|
|
3304
|
+
});
|
|
1764
3305
|
}
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
3306
|
+
if (subaction === "batch-text") {
|
|
3307
|
+
return dryRunRequest({
|
|
3308
|
+
spec,
|
|
3309
|
+
method: "POST",
|
|
3310
|
+
path: `${basePath}/batch-text`,
|
|
3311
|
+
body: {
|
|
3312
|
+
updates: parseWordTextUpdatesFlag({ flags, name: "updates-json" }),
|
|
3313
|
+
},
|
|
3314
|
+
});
|
|
1772
3315
|
}
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
3316
|
+
if (subaction === "style") {
|
|
3317
|
+
const styleJson = parseNullableJsonObjectFlag({
|
|
3318
|
+
flags,
|
|
3319
|
+
name: "style-json",
|
|
3320
|
+
});
|
|
3321
|
+
return dryRunRequest({
|
|
3322
|
+
spec,
|
|
3323
|
+
method: "POST",
|
|
3324
|
+
path: `${basePath}/style`,
|
|
3325
|
+
body: {
|
|
3326
|
+
...buildCaptionWordSelectorBody({ flags }),
|
|
3327
|
+
style: styleJson === undefined
|
|
3328
|
+
? buildCaptionWordStyleFromFlags({ flags })
|
|
3329
|
+
: styleJson,
|
|
3330
|
+
clear_existing: getOptionalBooleanFlag({
|
|
3331
|
+
flags,
|
|
3332
|
+
name: "clear-existing",
|
|
3333
|
+
}),
|
|
1780
3334
|
},
|
|
1781
|
-
human: loginInstructions,
|
|
1782
3335
|
});
|
|
1783
3336
|
}
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
const intervalMs = Math.max(1000, login.interval * 1000);
|
|
1788
|
-
while (Date.now() - startedAt <= timeoutMs) {
|
|
1789
|
-
await sleep({ ms: intervalMs });
|
|
1790
|
-
const tokenUrl = `${appUrl}/api/cli/auth/token`;
|
|
1791
|
-
const pollResponse = await fetch(tokenUrl, {
|
|
3337
|
+
if (subaction === "emphasis") {
|
|
3338
|
+
return dryRunRequest({
|
|
3339
|
+
spec,
|
|
1792
3340
|
method: "POST",
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
3341
|
+
path: `${basePath}/emphasis`,
|
|
3342
|
+
body: {
|
|
3343
|
+
...buildCaptionWordSelectorBody({ flags }),
|
|
3344
|
+
emphasis: getOptionalBooleanFlag({ flags, name: "emphasis" }) ?? true,
|
|
1796
3345
|
},
|
|
1797
|
-
body: JSON.stringify({ device_code: login.device_code }),
|
|
1798
3346
|
});
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
3347
|
+
}
|
|
3348
|
+
if (subaction === "delete") {
|
|
3349
|
+
return dryRunRequest({
|
|
3350
|
+
spec,
|
|
3351
|
+
method: "POST",
|
|
3352
|
+
path: `${basePath}/delete`,
|
|
3353
|
+
body: { word_ids: parseWordIds({ flags }) },
|
|
3354
|
+
});
|
|
3355
|
+
}
|
|
3356
|
+
if (subaction === "merge") {
|
|
3357
|
+
return dryRunRequest({
|
|
3358
|
+
spec,
|
|
3359
|
+
method: "POST",
|
|
3360
|
+
path: `${basePath}/merge`,
|
|
3361
|
+
body: {
|
|
3362
|
+
word_ids: parseWordIds({ flags }),
|
|
3363
|
+
text: getStringFlag({ flags, name: "text" }),
|
|
3364
|
+
},
|
|
3365
|
+
});
|
|
3366
|
+
}
|
|
3367
|
+
if (subaction === "split") {
|
|
3368
|
+
return dryRunRequest({
|
|
3369
|
+
spec,
|
|
3370
|
+
method: "POST",
|
|
3371
|
+
path: `${basePath}/split`,
|
|
3372
|
+
body: {
|
|
3373
|
+
word_id: requireStringFlag({ flags, name: "word-id" }),
|
|
3374
|
+
words: parseCsvRequiredFlag({ flags, name: "words" }),
|
|
3375
|
+
},
|
|
3376
|
+
});
|
|
3377
|
+
}
|
|
3378
|
+
throw new CliUsageError({
|
|
3379
|
+
code: "unsupported_dry_run",
|
|
3380
|
+
message: `--dry-run is not implemented for captions words ${subaction ?? ""}.`,
|
|
3381
|
+
usage: spec.usage,
|
|
3382
|
+
});
|
|
3383
|
+
}
|
|
3384
|
+
function buildCaptionTrackDryRun({ spec, flags, action, }) {
|
|
3385
|
+
const selector = buildCaptionTrackSelectorBody({ flags });
|
|
3386
|
+
return dryRunRequest({
|
|
3387
|
+
spec,
|
|
3388
|
+
method: "POST",
|
|
3389
|
+
path: `/projects/${encodeURIComponent(requireStringFlag({ flags, name: "project-id" }))}/captions/${action}`,
|
|
3390
|
+
body: selector,
|
|
3391
|
+
});
|
|
3392
|
+
}
|
|
3393
|
+
function buildCaptionWordSelectorBody({ flags, }) {
|
|
3394
|
+
return {
|
|
3395
|
+
word_ids: parseCsvFlag({ flags, name: "word-ids" }) ??
|
|
3396
|
+
parseCsvFlag({ flags, name: "word-id" }),
|
|
3397
|
+
match_text: getStringFlag({ flags, name: "match-text" }),
|
|
3398
|
+
match_pattern: parseCaptionMatchPattern({
|
|
3399
|
+
value: getStringFlag({ flags, name: "match-pattern" }),
|
|
3400
|
+
}),
|
|
3401
|
+
timeline_item_id: getStringFlag({ flags, name: "timeline-item-id" }),
|
|
3402
|
+
all: getOptionalBooleanFlag({ flags, name: "all" }),
|
|
3403
|
+
from_seconds: getStringFlag({ flags, name: "from" }) === undefined
|
|
3404
|
+
? undefined
|
|
3405
|
+
: parseTimecode({
|
|
3406
|
+
value: requireStringFlag({ flags, name: "from" }),
|
|
3407
|
+
}),
|
|
3408
|
+
to_seconds: getStringFlag({ flags, name: "to" }) === undefined
|
|
3409
|
+
? undefined
|
|
3410
|
+
: parseTimecode({
|
|
3411
|
+
value: requireStringFlag({ flags, name: "to" }),
|
|
1804
3412
|
}),
|
|
3413
|
+
};
|
|
3414
|
+
}
|
|
3415
|
+
function buildCaptionTrackSelectorBody({ flags, }) {
|
|
3416
|
+
return {
|
|
3417
|
+
all: getOptionalBooleanFlag({ flags, name: "all" }),
|
|
3418
|
+
caption_id: getStringFlag({ flags, name: "caption-id" }),
|
|
3419
|
+
timeline_item_id: getStringFlag({ flags, name: "timeline-item-id" }),
|
|
3420
|
+
from_seconds: getStringFlag({ flags, name: "from" }) === undefined
|
|
3421
|
+
? undefined
|
|
3422
|
+
: parseTimecode({
|
|
3423
|
+
value: requireStringFlag({ flags, name: "from" }),
|
|
3424
|
+
}),
|
|
3425
|
+
to_seconds: getStringFlag({ flags, name: "to" }) === undefined
|
|
3426
|
+
? undefined
|
|
3427
|
+
: parseTimecode({
|
|
3428
|
+
value: requireStringFlag({ flags, name: "to" }),
|
|
3429
|
+
}),
|
|
3430
|
+
};
|
|
3431
|
+
}
|
|
3432
|
+
function buildCaptionTrackSelector({ flags }) {
|
|
3433
|
+
const body = buildCaptionTrackSelectorBody({ flags });
|
|
3434
|
+
return {
|
|
3435
|
+
projectId: requireStringFlag({ flags, name: "project-id" }),
|
|
3436
|
+
all: body.all,
|
|
3437
|
+
captionId: body.caption_id,
|
|
3438
|
+
timelineItemId: body.timeline_item_id,
|
|
3439
|
+
fromSeconds: body.from_seconds,
|
|
3440
|
+
toSeconds: body.to_seconds,
|
|
3441
|
+
};
|
|
3442
|
+
}
|
|
3443
|
+
function dryRunRequest({ spec, method, path, query, body, followUpRequests, notes, }) {
|
|
3444
|
+
return buildDryRunRequest({
|
|
3445
|
+
spec,
|
|
3446
|
+
method,
|
|
3447
|
+
path,
|
|
3448
|
+
query,
|
|
3449
|
+
body,
|
|
3450
|
+
followUpRequests,
|
|
3451
|
+
notes,
|
|
3452
|
+
});
|
|
3453
|
+
}
|
|
3454
|
+
function toApiWorkspaceDefaults({ flags, }) {
|
|
3455
|
+
const defaults = buildWorkspaceDefaults({ flags });
|
|
3456
|
+
if (defaults === undefined) {
|
|
3457
|
+
return undefined;
|
|
3458
|
+
}
|
|
3459
|
+
return {
|
|
3460
|
+
aspect_ratio: defaults.aspectRatio,
|
|
3461
|
+
orientation: defaults.orientation,
|
|
3462
|
+
resolution: defaults.resolution,
|
|
3463
|
+
frame_rate: defaults.frameRate,
|
|
3464
|
+
auto_manage_broll: defaults.autoManageBroll,
|
|
3465
|
+
caption_theme_id: defaults.captionThemeId,
|
|
3466
|
+
};
|
|
3467
|
+
}
|
|
3468
|
+
function applyFieldSelection({ value, fields, }) {
|
|
3469
|
+
if (Array.isArray(value)) {
|
|
3470
|
+
return value.map((item) => applyFieldSelection({ value: item, fields }));
|
|
3471
|
+
}
|
|
3472
|
+
if (!value || typeof value !== "object") {
|
|
3473
|
+
return value;
|
|
3474
|
+
}
|
|
3475
|
+
const record = value;
|
|
3476
|
+
const arrayEntries = Object.entries(record).filter(([, item]) => Array.isArray(item));
|
|
3477
|
+
if (arrayEntries.length === 1) {
|
|
3478
|
+
const [key, items] = arrayEntries[0];
|
|
3479
|
+
return {
|
|
3480
|
+
...record,
|
|
3481
|
+
[key]: items.map((item) => applyFieldSelection({ value: item, fields })),
|
|
3482
|
+
};
|
|
3483
|
+
}
|
|
3484
|
+
return Object.fromEntries(fields
|
|
3485
|
+
.filter((field) => field in record)
|
|
3486
|
+
.map((field) => [field, record[field]]));
|
|
3487
|
+
}
|
|
3488
|
+
function validateSafeInputs({ parsed }) {
|
|
3489
|
+
for (const [name, value] of Object.entries(parsed.flags)) {
|
|
3490
|
+
if (typeof value !== "string") {
|
|
3491
|
+
continue;
|
|
3492
|
+
}
|
|
3493
|
+
rejectControlChars({ name, value });
|
|
3494
|
+
if (isIdLikeFlag({ name })) {
|
|
3495
|
+
rejectUnsafeId({ name, value });
|
|
3496
|
+
}
|
|
3497
|
+
if (name === "url") {
|
|
3498
|
+
rejectUnsafeUrl({ name, value });
|
|
3499
|
+
}
|
|
3500
|
+
if (name === "output") {
|
|
3501
|
+
rejectUnsafeOutputPath({ value });
|
|
3502
|
+
}
|
|
3503
|
+
}
|
|
3504
|
+
}
|
|
3505
|
+
function rejectControlChars({ name, value, }) {
|
|
3506
|
+
if (Array.from(value).some((character) => {
|
|
3507
|
+
const code = character.charCodeAt(0);
|
|
3508
|
+
return code < 32 || code === 127;
|
|
3509
|
+
})) {
|
|
3510
|
+
throw new CliUsageError({
|
|
3511
|
+
code: "unsafe_input",
|
|
3512
|
+
message: `--${name} contains control characters.`,
|
|
1805
3513
|
});
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
}
|
|
3514
|
+
}
|
|
3515
|
+
}
|
|
3516
|
+
function isIdLikeFlag({ name }) {
|
|
3517
|
+
return name === "id" || name.endsWith("-id") || name.endsWith("-ids");
|
|
3518
|
+
}
|
|
3519
|
+
function rejectUnsafeId({ name, value, }) {
|
|
3520
|
+
if (/[?#%]/.test(value)) {
|
|
3521
|
+
throw new CliUsageError({
|
|
3522
|
+
code: "unsafe_input",
|
|
3523
|
+
message: `--${name} must be an ID, not a URL or encoded path.`,
|
|
3524
|
+
});
|
|
3525
|
+
}
|
|
3526
|
+
}
|
|
3527
|
+
function rejectUnsafeUrl({ name, value, }) {
|
|
3528
|
+
let url;
|
|
3529
|
+
try {
|
|
3530
|
+
url = new URL(value);
|
|
3531
|
+
}
|
|
3532
|
+
catch {
|
|
3533
|
+
throw new CliUsageError({
|
|
3534
|
+
code: "unsafe_input",
|
|
3535
|
+
message: `--${name} must be a valid URL.`,
|
|
3536
|
+
});
|
|
3537
|
+
}
|
|
3538
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
3539
|
+
throw new CliUsageError({
|
|
3540
|
+
code: "unsafe_input",
|
|
3541
|
+
message: `--${name} must use http or https.`,
|
|
3542
|
+
});
|
|
3543
|
+
}
|
|
3544
|
+
if (url.username || url.password) {
|
|
3545
|
+
throw new CliUsageError({
|
|
3546
|
+
code: "unsafe_input",
|
|
3547
|
+
message: `--${name} must not include embedded credentials.`,
|
|
3548
|
+
});
|
|
3549
|
+
}
|
|
3550
|
+
}
|
|
3551
|
+
function rejectUnsafeOutputPath({ value }) {
|
|
3552
|
+
const outputPath = resolve(value);
|
|
3553
|
+
const sensitiveDirs = [join(homedir(), ".ssh"), join(homedir(), ".gnupg")];
|
|
3554
|
+
if (sensitiveDirs.some((dir) => outputPath.startsWith(`${dir}/`) || outputPath === dir)) {
|
|
3555
|
+
throw new CliUsageError({
|
|
3556
|
+
code: "unsafe_input",
|
|
3557
|
+
message: "--output cannot write into sensitive home directories.",
|
|
3558
|
+
});
|
|
3559
|
+
}
|
|
3560
|
+
}
|
|
3561
|
+
async function maybePrintUpdateNotice({ parsed, baseUrl, json, }) {
|
|
3562
|
+
if (!shouldRunPassiveUpdateCheck({ parsed, json })) {
|
|
3563
|
+
return;
|
|
3564
|
+
}
|
|
3565
|
+
try {
|
|
3566
|
+
const update = await getUpdateStatus({ baseUrl, cachePolicy: "cache" });
|
|
3567
|
+
if (!update.updateAvailable) {
|
|
1816
3568
|
return;
|
|
1817
3569
|
}
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
3570
|
+
console.error(buildUpdateNotice({ update }).join("\n"));
|
|
3571
|
+
}
|
|
3572
|
+
catch {
|
|
3573
|
+
// Update checks must never affect the requested CLI command.
|
|
3574
|
+
}
|
|
3575
|
+
}
|
|
3576
|
+
function shouldRunPassiveUpdateCheck({ parsed, json, }) {
|
|
3577
|
+
if (json) {
|
|
3578
|
+
return false;
|
|
3579
|
+
}
|
|
3580
|
+
if (process.env.BLITZREELS_NO_UPDATE_NOTIFIER === "1") {
|
|
3581
|
+
return false;
|
|
3582
|
+
}
|
|
3583
|
+
if (process.env.CI === "true") {
|
|
3584
|
+
return false;
|
|
3585
|
+
}
|
|
3586
|
+
if (!process.stderr.isTTY &&
|
|
3587
|
+
process.env.BLITZREELS_FORCE_UPDATE_NOTIFIER !== "1") {
|
|
3588
|
+
return false;
|
|
3589
|
+
}
|
|
3590
|
+
if (parsed.command.length === 0 ||
|
|
3591
|
+
parsed.command[0] === "version" ||
|
|
3592
|
+
parsed.command[0] === "doctor" ||
|
|
3593
|
+
parsed.command[0] === "agent-context" ||
|
|
3594
|
+
parsed.command[0] === "schema" ||
|
|
3595
|
+
parsed.command[0] === "prime" ||
|
|
3596
|
+
parsed.command[0] === "llms" ||
|
|
3597
|
+
getBooleanFlag({ flags: parsed.flags, name: "help" })) {
|
|
3598
|
+
return false;
|
|
3599
|
+
}
|
|
3600
|
+
return true;
|
|
3601
|
+
}
|
|
3602
|
+
async function getUpdateStatus({ baseUrl, cachePolicy, }) {
|
|
3603
|
+
const cached = await readUpdateCache();
|
|
3604
|
+
const now = Date.now();
|
|
3605
|
+
if (cachePolicy === "cache" &&
|
|
3606
|
+
cached &&
|
|
3607
|
+
now - cached.fetchedAt < UPDATE_CHECK_TTL_MS) {
|
|
3608
|
+
return buildUpdateStatus({ manifest: cached.manifest, source: "cache" });
|
|
3609
|
+
}
|
|
3610
|
+
const manifest = await fetchUpdateManifest({ baseUrl }).catch(() => undefined);
|
|
3611
|
+
if (manifest) {
|
|
3612
|
+
await writeUpdateCache({ manifest });
|
|
3613
|
+
return buildUpdateStatus({ manifest, source: "network" });
|
|
3614
|
+
}
|
|
3615
|
+
if (cached) {
|
|
3616
|
+
return buildUpdateStatus({ manifest: cached.manifest, source: "cache" });
|
|
3617
|
+
}
|
|
3618
|
+
return buildUpdateStatus({
|
|
3619
|
+
manifest: buildFallbackUpdateManifest(),
|
|
3620
|
+
source: "fallback",
|
|
3621
|
+
});
|
|
3622
|
+
}
|
|
3623
|
+
function buildUpdateStatus({ manifest, source, }) {
|
|
3624
|
+
return {
|
|
3625
|
+
current: CLI_VERSION,
|
|
3626
|
+
manifest,
|
|
3627
|
+
updateAvailable: isVersionLessThan({
|
|
3628
|
+
left: CLI_VERSION,
|
|
3629
|
+
right: manifest.latest,
|
|
3630
|
+
}),
|
|
3631
|
+
source,
|
|
3632
|
+
};
|
|
3633
|
+
}
|
|
3634
|
+
async function fetchUpdateManifest({ baseUrl, }) {
|
|
3635
|
+
const controller = new AbortController();
|
|
3636
|
+
const timeout = setTimeout(() => {
|
|
3637
|
+
controller.abort();
|
|
3638
|
+
}, UPDATE_CHECK_TIMEOUT_MS);
|
|
3639
|
+
try {
|
|
3640
|
+
const response = await fetch(resolveUpdateManifestUrl({ baseUrl }), {
|
|
3641
|
+
headers: {
|
|
3642
|
+
Accept: "application/json",
|
|
3643
|
+
"User-Agent": `blitzreels-cli/${CLI_VERSION}`,
|
|
3644
|
+
},
|
|
3645
|
+
signal: controller.signal,
|
|
3646
|
+
});
|
|
3647
|
+
const value = await readJsonResponse({
|
|
3648
|
+
response,
|
|
3649
|
+
url: resolveUpdateManifestUrl({ baseUrl }),
|
|
3650
|
+
action: "check CLI updates",
|
|
3651
|
+
});
|
|
3652
|
+
return parseUpdateManifest({ value });
|
|
3653
|
+
}
|
|
3654
|
+
finally {
|
|
3655
|
+
clearTimeout(timeout);
|
|
1824
3656
|
}
|
|
1825
|
-
throw new Error("Timed out waiting for browser approval.");
|
|
1826
3657
|
}
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
if (!fileStats.isFile()) {
|
|
1831
|
-
throw new Error(`Not a file: ${filePath}`);
|
|
3658
|
+
function resolveUpdateManifestUrl({ baseUrl, }) {
|
|
3659
|
+
if (process.env.BLITZREELS_UPDATE_MANIFEST_URL) {
|
|
3660
|
+
return process.env.BLITZREELS_UPDATE_MANIFEST_URL;
|
|
1832
3661
|
}
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
});
|
|
1841
|
-
const bytes = await readFile(filePath);
|
|
1842
|
-
const uploadResponse = await fetch(init.upload_url, {
|
|
1843
|
-
method: "PUT",
|
|
1844
|
-
headers: { "Content-Type": contentType },
|
|
1845
|
-
body: new Blob([new Uint8Array(bytes)], { type: contentType }),
|
|
1846
|
-
});
|
|
1847
|
-
if (!uploadResponse.ok) {
|
|
1848
|
-
throw new Error(`Upload failed with ${uploadResponse.status}: ${await uploadResponse.text()}`);
|
|
3662
|
+
return `${resolveAppUrl({ baseUrl })}/api/cli/releases`;
|
|
3663
|
+
}
|
|
3664
|
+
async function readUpdateCache() {
|
|
3665
|
+
try {
|
|
3666
|
+
const raw = await readFile(UPDATE_CACHE_PATH, "utf8");
|
|
3667
|
+
const parsed = JSON.parse(raw);
|
|
3668
|
+
return parseUpdateCache({ value: parsed });
|
|
1849
3669
|
}
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
3670
|
+
catch {
|
|
3671
|
+
return undefined;
|
|
3672
|
+
}
|
|
3673
|
+
}
|
|
3674
|
+
async function writeUpdateCache({ manifest, }) {
|
|
3675
|
+
await mkdir(dirname(UPDATE_CACHE_PATH), { recursive: true });
|
|
3676
|
+
await writeFile(UPDATE_CACHE_PATH, JSON.stringify({ fetchedAt: Date.now(), manifest }, null, 2), { mode: 0o600 });
|
|
3677
|
+
}
|
|
3678
|
+
function parseUpdateCache({ value, }) {
|
|
3679
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
3680
|
+
return undefined;
|
|
3681
|
+
}
|
|
3682
|
+
const record = value;
|
|
3683
|
+
if (typeof record.fetchedAt !== "number") {
|
|
3684
|
+
return undefined;
|
|
3685
|
+
}
|
|
3686
|
+
return {
|
|
3687
|
+
fetchedAt: record.fetchedAt,
|
|
3688
|
+
manifest: parseUpdateManifest({ value: record.manifest }),
|
|
3689
|
+
};
|
|
3690
|
+
}
|
|
3691
|
+
function parseUpdateManifest({ value }) {
|
|
3692
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
3693
|
+
throw new Error("CLI release manifest returned an invalid response.");
|
|
3694
|
+
}
|
|
3695
|
+
const record = value;
|
|
3696
|
+
const upgrade = record.upgrade;
|
|
3697
|
+
if (!upgrade || typeof upgrade !== "object" || Array.isArray(upgrade)) {
|
|
3698
|
+
throw new Error("CLI release manifest is missing upgrade commands.");
|
|
3699
|
+
}
|
|
3700
|
+
const upgradeRecord = upgrade;
|
|
3701
|
+
const severity = record.severity === "warning" || record.severity === "critical"
|
|
3702
|
+
? record.severity
|
|
3703
|
+
: "notice";
|
|
3704
|
+
if (typeof record.latest !== "string" ||
|
|
3705
|
+
typeof record.minRecommended !== "string" ||
|
|
3706
|
+
typeof record.minSupported !== "string" ||
|
|
3707
|
+
typeof record.releasedAt !== "string" ||
|
|
3708
|
+
!Array.isArray(record.changes) ||
|
|
3709
|
+
typeof upgradeRecord.npm !== "string" ||
|
|
3710
|
+
typeof upgradeRecord.pnpm !== "string" ||
|
|
3711
|
+
typeof upgradeRecord.npx !== "string" ||
|
|
3712
|
+
typeof record.url !== "string") {
|
|
3713
|
+
throw new Error("CLI release manifest returned an incomplete response.");
|
|
3714
|
+
}
|
|
3715
|
+
return {
|
|
3716
|
+
latest: record.latest,
|
|
3717
|
+
minRecommended: record.minRecommended,
|
|
3718
|
+
minSupported: record.minSupported,
|
|
3719
|
+
severity,
|
|
3720
|
+
releasedAt: record.releasedAt,
|
|
3721
|
+
changes: record.changes.filter((change) => typeof change === "string"),
|
|
3722
|
+
upgrade: {
|
|
3723
|
+
npm: upgradeRecord.npm,
|
|
3724
|
+
pnpm: upgradeRecord.pnpm,
|
|
3725
|
+
npx: upgradeRecord.npx,
|
|
3726
|
+
},
|
|
3727
|
+
url: record.url,
|
|
3728
|
+
};
|
|
3729
|
+
}
|
|
3730
|
+
function buildFallbackUpdateManifest() {
|
|
3731
|
+
return {
|
|
3732
|
+
latest: CLI_VERSION,
|
|
3733
|
+
minRecommended: CLI_VERSION,
|
|
3734
|
+
minSupported: CLI_VERSION,
|
|
3735
|
+
severity: "notice",
|
|
3736
|
+
releasedAt: "2026-05-08",
|
|
3737
|
+
changes: [],
|
|
3738
|
+
upgrade: {
|
|
3739
|
+
npm: "npm i -g blitzreels@latest",
|
|
3740
|
+
pnpm: "pnpm add -g blitzreels@latest",
|
|
3741
|
+
npx: "npx blitzreels@latest",
|
|
3742
|
+
},
|
|
3743
|
+
url: "https://www.blitzreels.com/changelog",
|
|
3744
|
+
};
|
|
3745
|
+
}
|
|
3746
|
+
function buildUpdateNotice({ update }) {
|
|
3747
|
+
return [
|
|
3748
|
+
"",
|
|
3749
|
+
`Update available: blitzreels ${update.current} -> ${update.manifest.latest}`,
|
|
3750
|
+
"",
|
|
3751
|
+
"New:",
|
|
3752
|
+
...update.manifest.changes.slice(0, 3).map((change) => `- ${change}`),
|
|
3753
|
+
"",
|
|
3754
|
+
"Upgrade:",
|
|
3755
|
+
update.manifest.upgrade.npm,
|
|
3756
|
+
`Changelog: ${update.manifest.url}`,
|
|
3757
|
+
];
|
|
3758
|
+
}
|
|
3759
|
+
function buildVersionLines({ update }) {
|
|
3760
|
+
const lines = [`blitzreels ${CLI_VERSION}`];
|
|
3761
|
+
if (update.updateAvailable) {
|
|
3762
|
+
lines.push(`Latest: ${update.manifest.latest}`);
|
|
3763
|
+
lines.push("Update available.");
|
|
3764
|
+
lines.push(`Upgrade: ${update.manifest.upgrade.npm}`);
|
|
3765
|
+
lines.push(`Changelog: ${update.manifest.url}`);
|
|
3766
|
+
}
|
|
3767
|
+
else {
|
|
3768
|
+
lines.push(`Latest: ${update.manifest.latest}`);
|
|
3769
|
+
lines.push("Up to date.");
|
|
3770
|
+
}
|
|
3771
|
+
return lines;
|
|
3772
|
+
}
|
|
3773
|
+
function buildDoctorLines({ context, update, }) {
|
|
3774
|
+
const supported = !isVersionLessThan({
|
|
3775
|
+
left: CLI_VERSION,
|
|
3776
|
+
right: update.manifest.minSupported,
|
|
1871
3777
|
});
|
|
3778
|
+
const lines = [
|
|
3779
|
+
`CLI: ${CLI_VERSION}`,
|
|
3780
|
+
`Latest: ${update.manifest.latest}`,
|
|
3781
|
+
`Supported: ${supported ? "yes" : "no"}`,
|
|
3782
|
+
`API key: ${context.config.apiKey ? "configured" : "missing"}`,
|
|
3783
|
+
`Base URL: ${context.config.baseUrl ?? "https://www.blitzreels.com/api/v1"}`,
|
|
3784
|
+
`Key storage: ${context.config.keyStorage ?? "none"}`,
|
|
3785
|
+
];
|
|
3786
|
+
if (update.updateAvailable) {
|
|
3787
|
+
lines.push(`Upgrade: ${update.manifest.upgrade.npm}`);
|
|
3788
|
+
lines.push(`Changelog: ${update.manifest.url}`);
|
|
3789
|
+
}
|
|
3790
|
+
return lines;
|
|
3791
|
+
}
|
|
3792
|
+
function isVersionLessThan({ left, right, }) {
|
|
3793
|
+
const leftParts = parseSemver({ value: left });
|
|
3794
|
+
const rightParts = parseSemver({ value: right });
|
|
3795
|
+
for (let index = 0; index < 3; index++) {
|
|
3796
|
+
const leftPart = leftParts[index] ?? 0;
|
|
3797
|
+
const rightPart = rightParts[index] ?? 0;
|
|
3798
|
+
if (leftPart < rightPart) {
|
|
3799
|
+
return true;
|
|
3800
|
+
}
|
|
3801
|
+
if (leftPart > rightPart) {
|
|
3802
|
+
return false;
|
|
3803
|
+
}
|
|
3804
|
+
}
|
|
3805
|
+
return false;
|
|
3806
|
+
}
|
|
3807
|
+
function parseSemver({ value }) {
|
|
3808
|
+
return value
|
|
3809
|
+
.replace(/^v/, "")
|
|
3810
|
+
.split(".", 3)
|
|
3811
|
+
.map((part) => Number.parseInt(part.replace(/\D.*$/, ""), 10))
|
|
3812
|
+
.map((part) => (Number.isFinite(part) ? part : 0));
|
|
1872
3813
|
}
|
|
1873
3814
|
async function readJsonResponse({ response, url, action, }) {
|
|
1874
3815
|
const contentType = response.headers.get("content-type") ?? "";
|
|
@@ -1945,23 +3886,6 @@ function parseDeviceTokenResponse({ value, }) {
|
|
|
1945
3886
|
key_prefix: typeof record.key_prefix === "string" ? record.key_prefix : undefined,
|
|
1946
3887
|
};
|
|
1947
3888
|
}
|
|
1948
|
-
async function loadConfig() {
|
|
1949
|
-
try {
|
|
1950
|
-
const raw = await readFile(CONFIG_PATH, "utf8");
|
|
1951
|
-
const value = JSON.parse(raw);
|
|
1952
|
-
const keyStorage = value.keyStorage === "keychain" ? "keychain" : "config";
|
|
1953
|
-
const keychainApiKey = keyStorage === "keychain" ? await readKeychainApiKey() : "";
|
|
1954
|
-
return {
|
|
1955
|
-
apiKey: keychainApiKey ||
|
|
1956
|
-
(typeof value.apiKey === "string" ? value.apiKey : ""),
|
|
1957
|
-
baseUrl: typeof value.baseUrl === "string" ? value.baseUrl : undefined,
|
|
1958
|
-
keyStorage,
|
|
1959
|
-
};
|
|
1960
|
-
}
|
|
1961
|
-
catch {
|
|
1962
|
-
return { apiKey: "", baseUrl: undefined, keyStorage: undefined };
|
|
1963
|
-
}
|
|
1964
|
-
}
|
|
1965
3889
|
function parseArgs({ argv }) {
|
|
1966
3890
|
const command = [];
|
|
1967
3891
|
const flags = {};
|
|
@@ -1995,39 +3919,81 @@ function validateParsedArgs({ parsed }) {
|
|
|
1995
3919
|
if (!spec) {
|
|
1996
3920
|
throw buildUnknownCommandError({ command: parsed.command });
|
|
1997
3921
|
}
|
|
1998
|
-
const allowedFlags = new Set([
|
|
3922
|
+
const allowedFlags = new Set([
|
|
3923
|
+
...GLOBAL_FLAGS,
|
|
3924
|
+
...spec.flags.map((flag) => flag.name),
|
|
3925
|
+
]);
|
|
1999
3926
|
for (const flag of Object.keys(parsed.flags)) {
|
|
2000
3927
|
if (!allowedFlags.has(flag)) {
|
|
2001
3928
|
throw buildUnknownFlagError({ spec, flag });
|
|
2002
3929
|
}
|
|
2003
3930
|
}
|
|
3931
|
+
if (getBooleanFlag({ flags: parsed.flags, name: "dry-run" }) &&
|
|
3932
|
+
!spec.dryRunSupported) {
|
|
3933
|
+
throw new CliUsageError({
|
|
3934
|
+
code: "unsupported_dry_run",
|
|
3935
|
+
message: "--dry-run is not supported for this command.",
|
|
3936
|
+
usage: spec.usage,
|
|
3937
|
+
});
|
|
3938
|
+
}
|
|
2004
3939
|
}
|
|
2005
3940
|
function findCommandSpec({ command, }) {
|
|
2006
3941
|
return COMMAND_SPECS.find((spec) => commandsEqual(spec.command, command));
|
|
2007
3942
|
}
|
|
2008
|
-
function
|
|
2009
|
-
return (left.length === right.length &&
|
|
2010
|
-
left.every((part, index) => part === right[index]));
|
|
2011
|
-
}
|
|
2012
|
-
function commandLabel({ command }) {
|
|
2013
|
-
return command.join(" ");
|
|
2014
|
-
}
|
|
2015
|
-
function printCommandHelpOrThrow({ command }) {
|
|
3943
|
+
function printCommandOrGroupHelpOrThrow({ command, }) {
|
|
2016
3944
|
const spec = findCommandSpec({ command });
|
|
2017
|
-
if (
|
|
2018
|
-
|
|
3945
|
+
if (spec) {
|
|
3946
|
+
printCommandHelp({ spec });
|
|
3947
|
+
return;
|
|
3948
|
+
}
|
|
3949
|
+
const groupSpecs = findGroupCommandSpecs({ command });
|
|
3950
|
+
if (groupSpecs.length > 0) {
|
|
3951
|
+
printGroupHelp({ command, specs: groupSpecs });
|
|
3952
|
+
return;
|
|
2019
3953
|
}
|
|
2020
|
-
|
|
3954
|
+
throw buildUnknownCommandError({ command });
|
|
2021
3955
|
}
|
|
2022
3956
|
function printCommandHelp({ spec }) {
|
|
2023
|
-
const flags =
|
|
3957
|
+
const flags = flagsForCommand({ spec });
|
|
3958
|
+
const agentHelp = isAgentHelpEnvironment()
|
|
3959
|
+
? `\nAgent instructions:\n blitzreels prime --command ${JSON.stringify(commandLabel({ command: spec.command }))}\n blitzreels agent-context --command ${JSON.stringify(commandLabel({ command: spec.command }))} --json\n`
|
|
3960
|
+
: "";
|
|
2024
3961
|
console.log(`Usage:
|
|
2025
3962
|
${spec.usage}
|
|
2026
3963
|
|
|
3964
|
+
Description:
|
|
3965
|
+
${spec.description}
|
|
3966
|
+
|
|
2027
3967
|
Flags:
|
|
2028
|
-
${flags
|
|
3968
|
+
${flags
|
|
3969
|
+
.map((flag) => {
|
|
3970
|
+
const required = flag.required ? " required" : "";
|
|
3971
|
+
const values = flag.enum ? ` (${flag.enum.join("|")})` : "";
|
|
3972
|
+
const defaultValue = flag.default === undefined ? "" : ` default=${String(flag.default)}`;
|
|
3973
|
+
return ` --${flag.name} ${flag.type}${required}${values}${defaultValue} ${flag.description}`;
|
|
3974
|
+
})
|
|
3975
|
+
.join("\n")}
|
|
3976
|
+
|
|
3977
|
+
Examples:
|
|
3978
|
+
${spec.examples.map((example) => ` ${example}`).join("\n")}
|
|
3979
|
+
${agentHelp}
|
|
2029
3980
|
`);
|
|
2030
3981
|
}
|
|
3982
|
+
function isAgentHelpEnvironment() {
|
|
3983
|
+
const truthyEnv = [
|
|
3984
|
+
"BLITZREELS_AGENT_HELP",
|
|
3985
|
+
"CODEX_SHELL",
|
|
3986
|
+
"CLAUDECODE",
|
|
3987
|
+
"CLAUDE_CODE",
|
|
3988
|
+
"CURSOR_AGENT",
|
|
3989
|
+
"OPENCODE",
|
|
3990
|
+
"AIDER",
|
|
3991
|
+
];
|
|
3992
|
+
return truthyEnv.some((name) => {
|
|
3993
|
+
const value = process.env[name];
|
|
3994
|
+
return value !== undefined && value !== "" && value !== "0";
|
|
3995
|
+
});
|
|
3996
|
+
}
|
|
2031
3997
|
function buildUnknownCommandError({ command, }) {
|
|
2032
3998
|
const suggestions = suggestCommands({ command }).map((spec) => spec.usage);
|
|
2033
3999
|
return new CliUsageError({
|
|
@@ -2037,8 +4003,38 @@ function buildUnknownCommandError({ command, }) {
|
|
|
2037
4003
|
usage: suggestions[0],
|
|
2038
4004
|
});
|
|
2039
4005
|
}
|
|
4006
|
+
function findGroupCommandSpecs({ command, }) {
|
|
4007
|
+
if (command.length === 0) {
|
|
4008
|
+
return [];
|
|
4009
|
+
}
|
|
4010
|
+
return COMMAND_SPECS.filter((spec) => commandsEqual(spec.command.slice(0, command.length), command));
|
|
4011
|
+
}
|
|
4012
|
+
function printGroupHelp({ command, specs, }) {
|
|
4013
|
+
const label = commandLabel({ command });
|
|
4014
|
+
const agentHelp = isAgentHelpEnvironment()
|
|
4015
|
+
? `\nAgent instructions:\n blitzreels prime --command ${JSON.stringify(label)}\n blitzreels agent-context --command ${JSON.stringify(label)} --json\n`
|
|
4016
|
+
: "";
|
|
4017
|
+
console.log(`blitzreels ${label}
|
|
4018
|
+
|
|
4019
|
+
Available commands:
|
|
4020
|
+
${specs.map((spec) => ` ${spec.usage}`).join("\n")}
|
|
4021
|
+
|
|
4022
|
+
Examples:
|
|
4023
|
+
${specs
|
|
4024
|
+
.slice(0, 4)
|
|
4025
|
+
.flatMap((spec) => spec.examples.slice(0, 1))
|
|
4026
|
+
.map((example) => ` ${example}`)
|
|
4027
|
+
.join("\n")}
|
|
4028
|
+
${agentHelp}
|
|
4029
|
+
`);
|
|
4030
|
+
}
|
|
2040
4031
|
function buildUnknownFlagError({ spec, flag, }) {
|
|
2041
|
-
const flags = [
|
|
4032
|
+
const flags = [
|
|
4033
|
+
...new Set([
|
|
4034
|
+
...spec.flags.map((candidate) => candidate.name),
|
|
4035
|
+
...GLOBAL_FLAGS,
|
|
4036
|
+
]),
|
|
4037
|
+
];
|
|
2042
4038
|
const suggestions = suggestValues({ value: flag, candidates: flags }).map((candidate) => `--${candidate}`);
|
|
2043
4039
|
return new CliUsageError({
|
|
2044
4040
|
code: "unknown_flag",
|
|
@@ -2048,55 +4044,7 @@ function buildUnknownFlagError({ spec, flag, }) {
|
|
|
2048
4044
|
});
|
|
2049
4045
|
}
|
|
2050
4046
|
function suggestCommands({ command }) {
|
|
2051
|
-
|
|
2052
|
-
const prefixMatches = COMMAND_SPECS.filter((spec) => commandsEqual(spec.command.slice(0, command.length), command));
|
|
2053
|
-
if (prefixMatches.length > 0) {
|
|
2054
|
-
return prefixMatches.slice(0, 3);
|
|
2055
|
-
}
|
|
2056
|
-
return COMMAND_SPECS.map((spec) => ({
|
|
2057
|
-
spec,
|
|
2058
|
-
score: commandSimilarity({
|
|
2059
|
-
left: joined,
|
|
2060
|
-
right: commandLabel({ command: spec.command }),
|
|
2061
|
-
}),
|
|
2062
|
-
}))
|
|
2063
|
-
.filter((item) => item.score >= 0.45)
|
|
2064
|
-
.sort((left, right) => right.score - left.score)
|
|
2065
|
-
.slice(0, 3)
|
|
2066
|
-
.map((item) => item.spec);
|
|
2067
|
-
}
|
|
2068
|
-
function suggestValues({ value, candidates, }) {
|
|
2069
|
-
return candidates
|
|
2070
|
-
.map((candidate) => ({
|
|
2071
|
-
candidate,
|
|
2072
|
-
score: commandSimilarity({ left: value, right: candidate }),
|
|
2073
|
-
}))
|
|
2074
|
-
.filter((item) => item.score >= 0.45)
|
|
2075
|
-
.sort((left, right) => right.score - left.score)
|
|
2076
|
-
.slice(0, 3)
|
|
2077
|
-
.map((item) => item.candidate);
|
|
2078
|
-
}
|
|
2079
|
-
function commandSimilarity({ left, right, }) {
|
|
2080
|
-
const maxLength = Math.max(left.length, right.length);
|
|
2081
|
-
if (maxLength === 0) {
|
|
2082
|
-
return 1;
|
|
2083
|
-
}
|
|
2084
|
-
return 1 - levenshteinDistance({ left, right }) / maxLength;
|
|
2085
|
-
}
|
|
2086
|
-
function levenshteinDistance({ left, right, }) {
|
|
2087
|
-
const previous = Array.from({ length: right.length + 1 }, (_, index) => index);
|
|
2088
|
-
const current = Array.from({ length: right.length + 1 }, () => 0);
|
|
2089
|
-
for (let leftIndex = 1; leftIndex <= left.length; leftIndex++) {
|
|
2090
|
-
current[0] = leftIndex;
|
|
2091
|
-
for (let rightIndex = 1; rightIndex <= right.length; rightIndex++) {
|
|
2092
|
-
const substitutionCost = left[leftIndex - 1] === right[rightIndex - 1] ? 0 : 1;
|
|
2093
|
-
current[rightIndex] = Math.min((current[rightIndex - 1] ?? 0) + 1, (previous[rightIndex] ?? 0) + 1, (previous[rightIndex - 1] ?? 0) + substitutionCost);
|
|
2094
|
-
}
|
|
2095
|
-
for (let index = 0; index < previous.length; index++) {
|
|
2096
|
-
previous[index] = current[index] ?? 0;
|
|
2097
|
-
}
|
|
2098
|
-
}
|
|
2099
|
-
return previous[right.length] ?? 0;
|
|
4047
|
+
return suggestCommandSpecs({ command, specs: COMMAND_SPECS });
|
|
2100
4048
|
}
|
|
2101
4049
|
function usageForCurrentCommand() {
|
|
2102
4050
|
const parsed = parseArgs({ argv: process.argv.slice(2) });
|
|
@@ -2222,16 +4170,92 @@ function parseStringRecordFlag({ flags, name, }) {
|
|
|
2222
4170
|
}
|
|
2223
4171
|
return value;
|
|
2224
4172
|
}
|
|
2225
|
-
function
|
|
2226
|
-
|
|
2227
|
-
|
|
4173
|
+
function getTimelineStartFlag({ flags }) {
|
|
4174
|
+
const value = getStringFlag({ flags, name: "at" });
|
|
4175
|
+
return value === undefined ? undefined : parseTimecode({ value });
|
|
4176
|
+
}
|
|
4177
|
+
function parseEffectEasing({ value, }) {
|
|
4178
|
+
if (value === undefined) {
|
|
4179
|
+
return undefined;
|
|
2228
4180
|
}
|
|
2229
|
-
|
|
4181
|
+
if (value === "linear" ||
|
|
4182
|
+
value === "easeIn" ||
|
|
4183
|
+
value === "easeOut" ||
|
|
4184
|
+
value === "easeInOut") {
|
|
4185
|
+
return value;
|
|
4186
|
+
}
|
|
4187
|
+
throw new Error("--easing must be linear, easeIn, easeOut, or easeInOut.");
|
|
4188
|
+
}
|
|
4189
|
+
function parseZoomStyle({ value, }) {
|
|
4190
|
+
if (value === undefined) {
|
|
4191
|
+
return undefined;
|
|
4192
|
+
}
|
|
4193
|
+
if (value === "transition" || value === "hard") {
|
|
4194
|
+
return value;
|
|
4195
|
+
}
|
|
4196
|
+
throw new Error("--style must be transition or hard.");
|
|
4197
|
+
}
|
|
4198
|
+
function parseZoomPhase({ value, }) {
|
|
4199
|
+
if (value === undefined) {
|
|
4200
|
+
return undefined;
|
|
4201
|
+
}
|
|
4202
|
+
if (value === "in" ||
|
|
4203
|
+
value === "out" ||
|
|
4204
|
+
value === "both" ||
|
|
4205
|
+
value === "none") {
|
|
4206
|
+
return value;
|
|
4207
|
+
}
|
|
4208
|
+
throw new Error("--phase must be in, out, both, or none.");
|
|
2230
4209
|
}
|
|
2231
|
-
function
|
|
2232
|
-
if (
|
|
2233
|
-
|
|
4210
|
+
function maybePoint({ x, y, }) {
|
|
4211
|
+
if (x === undefined && y === undefined) {
|
|
4212
|
+
return undefined;
|
|
2234
4213
|
}
|
|
4214
|
+
return { x: x ?? 0, y: y ?? 0 };
|
|
4215
|
+
}
|
|
4216
|
+
function buildZoomEffectSettings({ flags, durationSeconds, }) {
|
|
4217
|
+
const fromJson = parseJsonObjectFlag({ flags, name: "settings-json" });
|
|
4218
|
+
const scaleStart = getNumberFlag({ flags, name: "scale-start" });
|
|
4219
|
+
const scaleEnd = getNumberFlag({ flags, name: "scale-end" });
|
|
4220
|
+
const translateStart = maybePoint({
|
|
4221
|
+
x: getNumberFlag({ flags, name: "translate-start-x" }),
|
|
4222
|
+
y: getNumberFlag({ flags, name: "translate-start-y" }),
|
|
4223
|
+
});
|
|
4224
|
+
const translateEnd = maybePoint({
|
|
4225
|
+
x: getNumberFlag({ flags, name: "translate-end-x" }),
|
|
4226
|
+
y: getNumberFlag({ flags, name: "translate-end-y" }),
|
|
4227
|
+
});
|
|
4228
|
+
const anchor = maybePoint({
|
|
4229
|
+
x: getNumberFlag({ flags, name: "anchor-x" }),
|
|
4230
|
+
y: getNumberFlag({ flags, name: "anchor-y" }),
|
|
4231
|
+
});
|
|
4232
|
+
return {
|
|
4233
|
+
...(fromJson ?? {}),
|
|
4234
|
+
scale: scaleStart === undefined && scaleEnd === undefined
|
|
4235
|
+
? fromJson?.scale
|
|
4236
|
+
: {
|
|
4237
|
+
start: scaleStart ?? 1,
|
|
4238
|
+
end: scaleEnd ?? scaleStart ?? 1,
|
|
4239
|
+
},
|
|
4240
|
+
translate: translateStart === undefined && translateEnd === undefined
|
|
4241
|
+
? fromJson?.translate
|
|
4242
|
+
: {
|
|
4243
|
+
start: translateStart ?? { x: 0, y: 0 },
|
|
4244
|
+
end: translateEnd ?? translateStart ?? { x: 0, y: 0 },
|
|
4245
|
+
},
|
|
4246
|
+
anchor: anchor ?? fromJson?.anchor,
|
|
4247
|
+
style: parseZoomStyle({ value: getStringFlag({ flags, name: "style" }) }) ??
|
|
4248
|
+
fromJson?.style,
|
|
4249
|
+
phase: parseZoomPhase({ value: getStringFlag({ flags, name: "phase" }) }) ??
|
|
4250
|
+
fromJson?.phase,
|
|
4251
|
+
inDurationSeconds: getNumberFlag({ flags, name: "in-duration" }) ??
|
|
4252
|
+
durationSeconds ??
|
|
4253
|
+
fromJson?.inDurationSeconds,
|
|
4254
|
+
outDurationSeconds: parseNullableNumberFlag({ flags, name: "out-duration" }) ??
|
|
4255
|
+
fromJson?.outDurationSeconds,
|
|
4256
|
+
outScale: parseNullableNumberFlag({ flags, name: "out-scale" }) ??
|
|
4257
|
+
fromJson?.outScale,
|
|
4258
|
+
};
|
|
2235
4259
|
}
|
|
2236
4260
|
function parseTimecode({ value }) {
|
|
2237
4261
|
const trimmed = value.trim();
|
|
@@ -2267,75 +4291,11 @@ function openBrowser({ url }) {
|
|
|
2267
4291
|
});
|
|
2268
4292
|
child.unref();
|
|
2269
4293
|
}
|
|
2270
|
-
function canUseKeychain() {
|
|
2271
|
-
return (process.platform === "darwin" &&
|
|
2272
|
-
process.env.BLITZREELS_DISABLE_KEYCHAIN !== "1");
|
|
2273
|
-
}
|
|
2274
|
-
function runSecurity({ args, }) {
|
|
2275
|
-
return new Promise((resolveCommand) => {
|
|
2276
|
-
const child = spawn("security", args, {
|
|
2277
|
-
stdio: ["ignore", "pipe", "ignore"],
|
|
2278
|
-
});
|
|
2279
|
-
const chunks = [];
|
|
2280
|
-
child.stdout.on("data", (chunk) => chunks.push(Buffer.from(chunk)));
|
|
2281
|
-
child.on("error", () => resolveCommand({ stdout: "", ok: false }));
|
|
2282
|
-
child.on("close", (code) => resolveCommand({
|
|
2283
|
-
stdout: Buffer.concat(chunks).toString("utf8").trim(),
|
|
2284
|
-
ok: code === 0,
|
|
2285
|
-
}));
|
|
2286
|
-
});
|
|
2287
|
-
}
|
|
2288
4294
|
function sleep({ ms }) {
|
|
2289
4295
|
return new Promise((resolveSleep) => {
|
|
2290
4296
|
setTimeout(resolveSleep, ms);
|
|
2291
4297
|
});
|
|
2292
4298
|
}
|
|
2293
|
-
async function saveApiKey({ apiKey }) {
|
|
2294
|
-
if (!canUseKeychain()) {
|
|
2295
|
-
return false;
|
|
2296
|
-
}
|
|
2297
|
-
const result = await runSecurity({
|
|
2298
|
-
args: [
|
|
2299
|
-
"add-generic-password",
|
|
2300
|
-
"-a",
|
|
2301
|
-
KEYCHAIN_ACCOUNT,
|
|
2302
|
-
"-s",
|
|
2303
|
-
KEYCHAIN_SERVICE,
|
|
2304
|
-
"-w",
|
|
2305
|
-
apiKey,
|
|
2306
|
-
"-U",
|
|
2307
|
-
],
|
|
2308
|
-
});
|
|
2309
|
-
return result.ok;
|
|
2310
|
-
}
|
|
2311
|
-
async function readKeychainApiKey() {
|
|
2312
|
-
if (!canUseKeychain()) {
|
|
2313
|
-
return "";
|
|
2314
|
-
}
|
|
2315
|
-
const result = await runSecurity({
|
|
2316
|
-
args: [
|
|
2317
|
-
"find-generic-password",
|
|
2318
|
-
"-a",
|
|
2319
|
-
KEYCHAIN_ACCOUNT,
|
|
2320
|
-
"-s",
|
|
2321
|
-
KEYCHAIN_SERVICE,
|
|
2322
|
-
"-w",
|
|
2323
|
-
],
|
|
2324
|
-
});
|
|
2325
|
-
return result.ok ? result.stdout : "";
|
|
2326
|
-
}
|
|
2327
|
-
async function persistLoggedInApiKey({ apiKey, baseUrl, }) {
|
|
2328
|
-
const savedToKeychain = await saveApiKey({ apiKey });
|
|
2329
|
-
const config = {
|
|
2330
|
-
apiKey: savedToKeychain ? "" : apiKey,
|
|
2331
|
-
baseUrl,
|
|
2332
|
-
keyStorage: savedToKeychain ? "keychain" : "config",
|
|
2333
|
-
};
|
|
2334
|
-
await mkdir(dirname(CONFIG_PATH), { recursive: true });
|
|
2335
|
-
await writeFile(CONFIG_PATH, JSON.stringify(config, null, 2), {
|
|
2336
|
-
mode: 0o600,
|
|
2337
|
-
});
|
|
2338
|
-
}
|
|
2339
4299
|
function parseImageFormat({ value, }) {
|
|
2340
4300
|
if (value === undefined) {
|
|
2341
4301
|
return undefined;
|
|
@@ -2388,6 +4348,110 @@ function parseAssetType({ value, }) {
|
|
|
2388
4348
|
}
|
|
2389
4349
|
throw new Error("--asset-type must be video, audio, image, document, or all.");
|
|
2390
4350
|
}
|
|
4351
|
+
function parseAudioLibraryProviderFilter({ value, }) {
|
|
4352
|
+
if (value === undefined) {
|
|
4353
|
+
return undefined;
|
|
4354
|
+
}
|
|
4355
|
+
if (value === "all" ||
|
|
4356
|
+
value === "openverse" ||
|
|
4357
|
+
value === "freesound" ||
|
|
4358
|
+
value === "wikimedia") {
|
|
4359
|
+
return value;
|
|
4360
|
+
}
|
|
4361
|
+
throw new Error("--provider must be all, openverse, freesound, or wikimedia.");
|
|
4362
|
+
}
|
|
4363
|
+
function parseAudioLibraryProvider({ value, }) {
|
|
4364
|
+
const parsed = parseAudioLibraryProviderFilter({ value });
|
|
4365
|
+
if (parsed === "openverse" ||
|
|
4366
|
+
parsed === "freesound" ||
|
|
4367
|
+
parsed === "wikimedia") {
|
|
4368
|
+
return parsed;
|
|
4369
|
+
}
|
|
4370
|
+
throw new Error("--provider must be openverse, freesound, or wikimedia.");
|
|
4371
|
+
}
|
|
4372
|
+
function parseAudioLibraryLicense({ value, }) {
|
|
4373
|
+
if (value === undefined) {
|
|
4374
|
+
return undefined;
|
|
4375
|
+
}
|
|
4376
|
+
if (value === "cc0" ||
|
|
4377
|
+
value === "public_domain" ||
|
|
4378
|
+
value === "attribution" ||
|
|
4379
|
+
value === "all") {
|
|
4380
|
+
return value;
|
|
4381
|
+
}
|
|
4382
|
+
throw new Error("--license must be cc0, public_domain, attribution, or all.");
|
|
4383
|
+
}
|
|
4384
|
+
function parseAudioLibraryType({ value, }) {
|
|
4385
|
+
if (value === undefined) {
|
|
4386
|
+
return undefined;
|
|
4387
|
+
}
|
|
4388
|
+
if (value === "music" ||
|
|
4389
|
+
value === "sound_effect" ||
|
|
4390
|
+
value === "ambient" ||
|
|
4391
|
+
value === "other") {
|
|
4392
|
+
return value;
|
|
4393
|
+
}
|
|
4394
|
+
throw new Error("--audio-type must be music, sound_effect, ambient, or other.");
|
|
4395
|
+
}
|
|
4396
|
+
function parseStockProvider({ value, }) {
|
|
4397
|
+
if (value === undefined) {
|
|
4398
|
+
return null;
|
|
4399
|
+
}
|
|
4400
|
+
if (value === "pexels" || value === "freesound") {
|
|
4401
|
+
return value;
|
|
4402
|
+
}
|
|
4403
|
+
throw new Error("--provider must be pexels or freesound.");
|
|
4404
|
+
}
|
|
4405
|
+
function parseRequiredStockProvider({ value, }) {
|
|
4406
|
+
const parsed = parseStockProvider({ value });
|
|
4407
|
+
if (parsed !== null) {
|
|
4408
|
+
return parsed;
|
|
4409
|
+
}
|
|
4410
|
+
throw new Error("--provider must be pexels or freesound.");
|
|
4411
|
+
}
|
|
4412
|
+
function parseStockAssetType({ value, }) {
|
|
4413
|
+
if (value === "image" || value === "video" || value === "audio") {
|
|
4414
|
+
return value;
|
|
4415
|
+
}
|
|
4416
|
+
throw new Error("--asset-type must be image, video, or audio.");
|
|
4417
|
+
}
|
|
4418
|
+
function parseNullableStockAssetType({ value, }) {
|
|
4419
|
+
return value === undefined ? null : parseStockAssetType({ value });
|
|
4420
|
+
}
|
|
4421
|
+
function parseStockOrientation({ value, }) {
|
|
4422
|
+
if (value === undefined) {
|
|
4423
|
+
return null;
|
|
4424
|
+
}
|
|
4425
|
+
if (value === "landscape" || value === "portrait" || value === "square") {
|
|
4426
|
+
return value;
|
|
4427
|
+
}
|
|
4428
|
+
throw new Error("--orientation must be landscape, portrait, or square.");
|
|
4429
|
+
}
|
|
4430
|
+
function parseStockAudioType({ value, }) {
|
|
4431
|
+
if (value === undefined) {
|
|
4432
|
+
return undefined;
|
|
4433
|
+
}
|
|
4434
|
+
if (value === "sound_effect" ||
|
|
4435
|
+
value === "ambient" ||
|
|
4436
|
+
value === "background_music" ||
|
|
4437
|
+
value === "other") {
|
|
4438
|
+
return value;
|
|
4439
|
+
}
|
|
4440
|
+
throw new Error("--audio-type must be sound_effect, ambient, background_music, or other.");
|
|
4441
|
+
}
|
|
4442
|
+
function parseGlobalBrollAudioType({ value, }) {
|
|
4443
|
+
if (value === undefined) {
|
|
4444
|
+
return null;
|
|
4445
|
+
}
|
|
4446
|
+
if (value === "music" ||
|
|
4447
|
+
value === "sound_effect" ||
|
|
4448
|
+
value === "ambient" ||
|
|
4449
|
+
value === "background_music" ||
|
|
4450
|
+
value === "other") {
|
|
4451
|
+
return value;
|
|
4452
|
+
}
|
|
4453
|
+
throw new Error("--audio-type must be music, sound_effect, ambient, background_music, or other.");
|
|
4454
|
+
}
|
|
2391
4455
|
function parseYoutubeQuality({ value, }) {
|
|
2392
4456
|
if (value === undefined) {
|
|
2393
4457
|
return undefined;
|
|
@@ -2409,10 +4473,13 @@ function parseMediaPositionPreset({ value, }) {
|
|
|
2409
4473
|
value === "top-right" ||
|
|
2410
4474
|
value === "bottom-left" ||
|
|
2411
4475
|
value === "bottom-right" ||
|
|
2412
|
-
value === "
|
|
4476
|
+
value === "fullscreen") {
|
|
2413
4477
|
return value;
|
|
2414
4478
|
}
|
|
2415
|
-
|
|
4479
|
+
if (value === "full-screen") {
|
|
4480
|
+
return "fullscreen";
|
|
4481
|
+
}
|
|
4482
|
+
throw new Error("--position-preset must be center, top-left, top-right, bottom-left, bottom-right, fullscreen, or full-screen.");
|
|
2416
4483
|
}
|
|
2417
4484
|
function parseMediaAnimationPreset({ value, }) {
|
|
2418
4485
|
if (value === undefined) {
|
|
@@ -2422,13 +4489,32 @@ function parseMediaAnimationPreset({ value, }) {
|
|
|
2422
4489
|
value === "fadeIn" ||
|
|
2423
4490
|
value === "fadeOut" ||
|
|
2424
4491
|
value === "zoomIn" ||
|
|
4492
|
+
value === "zoomOut" ||
|
|
2425
4493
|
value === "slideIn" ||
|
|
4494
|
+
value === "slideLeft" ||
|
|
4495
|
+
value === "slideRight" ||
|
|
4496
|
+
value === "slideUp" ||
|
|
4497
|
+
value === "slideDown" ||
|
|
2426
4498
|
value === "popIn" ||
|
|
2427
4499
|
value === "bounce" ||
|
|
2428
4500
|
value === "spin") {
|
|
2429
4501
|
return value;
|
|
2430
4502
|
}
|
|
2431
|
-
throw new Error("--animation-preset must be none, fadeIn, fadeOut, zoomIn, slideIn, popIn, bounce, or spin.");
|
|
4503
|
+
throw new Error("--animation-preset must be none, fadeIn, fadeOut, zoomIn, zoomOut, slideIn, slideLeft, slideRight, slideUp, slideDown, popIn, bounce, or spin.");
|
|
4504
|
+
}
|
|
4505
|
+
function toApiMediaPositionPreset({ value, }) {
|
|
4506
|
+
if (value === "top-left")
|
|
4507
|
+
return "pip-top-left";
|
|
4508
|
+
if (value === "top-right")
|
|
4509
|
+
return "pip-top-right";
|
|
4510
|
+
if (value === "bottom-left")
|
|
4511
|
+
return "pip-bottom-left";
|
|
4512
|
+
if (value === "bottom-right")
|
|
4513
|
+
return "pip-bottom-right";
|
|
4514
|
+
return value;
|
|
4515
|
+
}
|
|
4516
|
+
function toApiMediaAnimationPreset({ value, }) {
|
|
4517
|
+
return value === "slideIn" ? "slideUp" : value;
|
|
2432
4518
|
}
|
|
2433
4519
|
function parseCaptionMatchPattern({ value, }) {
|
|
2434
4520
|
if (value === undefined) {
|
|
@@ -2782,6 +4868,17 @@ function buildCaptionWordSelector({ flags }) {
|
|
|
2782
4868
|
value: getStringFlag({ flags, name: "match-pattern" }),
|
|
2783
4869
|
}),
|
|
2784
4870
|
timelineItemId: getStringFlag({ flags, name: "timeline-item-id" }),
|
|
4871
|
+
all: getOptionalBooleanFlag({ flags, name: "all" }),
|
|
4872
|
+
fromSeconds: getStringFlag({ flags, name: "from" }) === undefined
|
|
4873
|
+
? undefined
|
|
4874
|
+
: parseTimecode({
|
|
4875
|
+
value: requireStringFlag({ flags, name: "from" }),
|
|
4876
|
+
}),
|
|
4877
|
+
toSeconds: getStringFlag({ flags, name: "to" }) === undefined
|
|
4878
|
+
? undefined
|
|
4879
|
+
: parseTimecode({
|
|
4880
|
+
value: requireStringFlag({ flags, name: "to" }),
|
|
4881
|
+
}),
|
|
2785
4882
|
};
|
|
2786
4883
|
}
|
|
2787
4884
|
function buildCaptionWordStyleFromFlags({ flags, }) {
|
|
@@ -2876,6 +4973,58 @@ function buildCaptionWordStyleFromFlags({ flags, }) {
|
|
|
2876
4973
|
}
|
|
2877
4974
|
return style;
|
|
2878
4975
|
}
|
|
4976
|
+
function buildCaptionThemeSettingsPatch({ flags, }) {
|
|
4977
|
+
const settingsJson = parseJsonObjectFlag({ flags, name: "settings-json" });
|
|
4978
|
+
if (settingsJson !== undefined) {
|
|
4979
|
+
return settingsJson;
|
|
4980
|
+
}
|
|
4981
|
+
const settings = {};
|
|
4982
|
+
setIfDefined({
|
|
4983
|
+
target: settings,
|
|
4984
|
+
key: "textShadow",
|
|
4985
|
+
value: getStringFlag({ flags, name: "text-shadow" }),
|
|
4986
|
+
});
|
|
4987
|
+
setIfDefined({
|
|
4988
|
+
target: settings,
|
|
4989
|
+
key: "shadowMode",
|
|
4990
|
+
value: parseCaptionShadowMode({
|
|
4991
|
+
value: getStringFlag({ flags, name: "shadow-mode" }),
|
|
4992
|
+
}),
|
|
4993
|
+
});
|
|
4994
|
+
setIfDefined({
|
|
4995
|
+
target: settings,
|
|
4996
|
+
key: "textStrokeEnabled",
|
|
4997
|
+
value: getOptionalBooleanFlag({ flags, name: "text-stroke-enabled" }),
|
|
4998
|
+
});
|
|
4999
|
+
setIfDefined({
|
|
5000
|
+
target: settings,
|
|
5001
|
+
key: "textStrokeColor",
|
|
5002
|
+
value: getStringFlag({ flags, name: "text-stroke-color" }),
|
|
5003
|
+
});
|
|
5004
|
+
setIfDefined({
|
|
5005
|
+
target: settings,
|
|
5006
|
+
key: "textStrokeWidthPx",
|
|
5007
|
+
value: getNumberFlag({ flags, name: "text-stroke-width-px" }),
|
|
5008
|
+
});
|
|
5009
|
+
setIfDefined({
|
|
5010
|
+
target: settings,
|
|
5011
|
+
key: "textStrokeBlurPx",
|
|
5012
|
+
value: getNumberFlag({ flags, name: "text-stroke-blur-px" }),
|
|
5013
|
+
});
|
|
5014
|
+
return Object.keys(settings).length === 0 ? undefined : settings;
|
|
5015
|
+
}
|
|
5016
|
+
function parseCaptionShadowMode({ value, }) {
|
|
5017
|
+
if (value === undefined) {
|
|
5018
|
+
return undefined;
|
|
5019
|
+
}
|
|
5020
|
+
if (value === "null") {
|
|
5021
|
+
return null;
|
|
5022
|
+
}
|
|
5023
|
+
if (value === "text-shadow" || value === "drop-shadow") {
|
|
5024
|
+
return value;
|
|
5025
|
+
}
|
|
5026
|
+
throw new Error("--shadow-mode must be text-shadow, drop-shadow, or null.");
|
|
5027
|
+
}
|
|
2879
5028
|
function setIfDefined({ target, key, value, }) {
|
|
2880
5029
|
if (value !== undefined) {
|
|
2881
5030
|
target[key] = value;
|
|
@@ -2896,14 +5045,28 @@ function requireApiKey({ apiKey }) {
|
|
|
2896
5045
|
throw new Error("Missing API key. Run `blitzreels auth login`, `blitzreels auth set-key --api-key ...`, or set BLITZREELS_API_KEY.");
|
|
2897
5046
|
}
|
|
2898
5047
|
}
|
|
2899
|
-
function printResult({ json, value, human, }) {
|
|
5048
|
+
function printResult({ json, value, human, fields, }) {
|
|
2900
5049
|
if (json) {
|
|
2901
|
-
|
|
5050
|
+
const selectedFields = fields ??
|
|
5051
|
+
parseCsvFlag({
|
|
5052
|
+
flags: parseArgs({ argv: process.argv.slice(2) }).flags,
|
|
5053
|
+
name: "fields",
|
|
5054
|
+
});
|
|
5055
|
+
console.log(JSON.stringify(selectedFields && selectedFields.length > 0
|
|
5056
|
+
? applyFieldSelection({ value, fields: selectedFields })
|
|
5057
|
+
: value, null, 2));
|
|
2902
5058
|
return;
|
|
2903
5059
|
}
|
|
2904
5060
|
console.log(human.join("\n"));
|
|
2905
5061
|
}
|
|
2906
5062
|
function printHelp() {
|
|
5063
|
+
const agentHelp = isAgentHelpEnvironment()
|
|
5064
|
+
? `
|
|
5065
|
+
Agent instructions:
|
|
5066
|
+
blitzreels prime
|
|
5067
|
+
blitzreels agent-context --json
|
|
5068
|
+
`
|
|
5069
|
+
: "";
|
|
2907
5070
|
console.log(`blitzreels
|
|
2908
5071
|
|
|
2909
5072
|
Usage:
|
|
@@ -2913,6 +5076,7 @@ Global flags:
|
|
|
2913
5076
|
--api-key KEY
|
|
2914
5077
|
--base-url URL
|
|
2915
5078
|
--json
|
|
5079
|
+
${agentHelp}
|
|
2916
5080
|
`);
|
|
2917
5081
|
}
|
|
2918
5082
|
main().catch((error) => {
|