blitzreels 0.1.8 → 0.1.9

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.
Files changed (54) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +47 -2
  3. package/dist/cli-errors.d.ts.map +1 -1
  4. package/dist/cli-runtime.d.ts +1 -1
  5. package/dist/cli-runtime.d.ts.map +1 -1
  6. package/dist/cli-runtime.js +1 -1
  7. package/dist/cli-runtime.js.map +1 -1
  8. package/dist/command-metadata.d.ts.map +1 -1
  9. package/dist/command-registry.d.ts.map +1 -1
  10. package/dist/command-registry.js +2 -0
  11. package/dist/command-registry.js.map +1 -1
  12. package/dist/commands/auth-runtime.js +1 -1
  13. package/dist/commands/auth-runtime.js.map +1 -1
  14. package/dist/commands/caption-dry-run.d.ts.map +1 -1
  15. package/dist/commands/caption-dry-run.js +16 -0
  16. package/dist/commands/caption-dry-run.js.map +1 -1
  17. package/dist/commands/captions-specs.d.ts.map +1 -1
  18. package/dist/commands/captions-specs.js +8 -0
  19. package/dist/commands/captions-specs.js.map +1 -1
  20. package/dist/commands/captions.js +16 -0
  21. package/dist/commands/captions.js.map +1 -1
  22. package/dist/commands/clips.d.ts +3 -0
  23. package/dist/commands/clips.d.ts.map +1 -1
  24. package/dist/commands/clips.js +702 -7
  25. package/dist/commands/clips.js.map +1 -1
  26. package/dist/commands/editing-workflows.d.ts.map +1 -1
  27. package/dist/commands/editing-workflows.js +24 -6
  28. package/dist/commands/editing-workflows.js.map +1 -1
  29. package/dist/commands/media-assets.d.ts +4 -7
  30. package/dist/commands/media-assets.d.ts.map +1 -1
  31. package/dist/commands/media-assets.js +221 -2
  32. package/dist/commands/media-assets.js.map +1 -1
  33. package/dist/commands/media-runtime.d.ts +3 -7
  34. package/dist/commands/media-runtime.d.ts.map +1 -1
  35. package/dist/commands/media-runtime.js +6 -2
  36. package/dist/commands/media-runtime.js.map +1 -1
  37. package/dist/commands/projects.js +3 -0
  38. package/dist/commands/projects.js.map +1 -1
  39. package/dist/commands/recording-sets.d.ts +44 -0
  40. package/dist/commands/recording-sets.d.ts.map +1 -0
  41. package/dist/commands/recording-sets.js +385 -0
  42. package/dist/commands/recording-sets.js.map +1 -0
  43. package/dist/commands/system.js +1 -1
  44. package/dist/commands/system.js.map +1 -1
  45. package/dist/commands/text-overlay-spec.js +62 -2
  46. package/dist/commands/text-overlay-spec.js.map +1 -1
  47. package/dist/commands/timeline-runtime.d.ts.map +1 -1
  48. package/dist/index.js +13 -0
  49. package/dist/index.js.map +1 -1
  50. package/dist/preview-first-edit.d.ts.map +1 -1
  51. package/dist/update-notifier.d.ts.map +1 -1
  52. package/dist/update-notifier.js +7 -3
  53. package/dist/update-notifier.js.map +1 -1
  54. package/package.json +2 -2
@@ -1,7 +1,67 @@
1
1
  import { mkdir, writeFile } from "node:fs/promises";
2
2
  import { join, resolve } from "node:path";
3
+ import { CliOperationError, CliUsageError } from "../cli-errors.js";
3
4
  import { commandSpec } from "../command-metadata.js";
4
5
  export const CLIPS_COMMAND_SPECS = [
6
+ commandSpec({
7
+ command: ["clip-share-pages", "get"],
8
+ usage: "blitzreels clip-share-pages get --asset-id ID [--json]",
9
+ flags: ["asset-id"],
10
+ description: "Inspect the public clip share page and its hidden clip ids.",
11
+ }),
12
+ commandSpec({
13
+ command: ["clip-share-pages", "publish"],
14
+ usage: "blitzreels clip-share-pages publish --asset-id ID --idempotency-key KEY [--dry-run] [--json]",
15
+ flags: ["asset-id", { name: "idempotency-key", required: true }],
16
+ description: "Publish the clip share page for a source asset.",
17
+ mutates: true,
18
+ dryRunSupported: true,
19
+ }),
20
+ commandSpec({
21
+ command: ["clip-share-pages", "unpublish"],
22
+ usage: "blitzreels clip-share-pages unpublish --asset-id ID --idempotency-key KEY [--dry-run] [--json]",
23
+ flags: ["asset-id", { name: "idempotency-key", required: true }],
24
+ description: "Disable the public clip share page for a source asset.",
25
+ mutates: true,
26
+ dryRunSupported: true,
27
+ }),
28
+ commandSpec({
29
+ command: ["clip-share-pages", "set-clip-visibility"],
30
+ usage: "blitzreels clip-share-pages set-clip-visibility --asset-id ID --clip-id ID --visibility visible|hidden --idempotency-key KEY [--dry-run] [--json]",
31
+ flags: [
32
+ "asset-id",
33
+ "clip-id",
34
+ { name: "visibility", enum: ["visible", "hidden"], required: true },
35
+ { name: "idempotency-key", required: true },
36
+ ],
37
+ description: "Show or hide one clip on its source asset share page.",
38
+ mutates: true,
39
+ dryRunSupported: true,
40
+ }),
41
+ commandSpec({
42
+ command: ["clip-batches", "get"],
43
+ usage: "blitzreels clip-batches get --batch-id ID [--json]",
44
+ flags: ["batch-id"],
45
+ description: "Inspect clip batch progress, Studio Sound status, and terminal errors.",
46
+ }),
47
+ commandSpec({
48
+ command: ["clip-batches", "wait"],
49
+ usage: "blitzreels clip-batches wait --batch-id ID [--interval-ms 3000] [--timeout-ms 1200000] [--json]",
50
+ flags: ["batch-id", "interval-ms", "timeout-ms"],
51
+ description: "Wait for a clip batch to complete or fail, including asynchronous Studio Sound.",
52
+ }),
53
+ commandSpec({
54
+ command: ["clip-batches", "recover"],
55
+ usage: "blitzreels clip-batches recover --batch-id ID [--target-count 10] --idempotency-key KEY [--dry-run] [--json]",
56
+ flags: [
57
+ "batch-id",
58
+ { name: "target-count", type: "number" },
59
+ { name: "idempotency-key", required: true },
60
+ ],
61
+ description: "Preserve accepted clips and refill a terminal batch in its existing project folder.",
62
+ mutates: true,
63
+ dryRunSupported: true,
64
+ }),
5
65
  commandSpec({
6
66
  command: ["clip-batches", "delete"],
7
67
  usage: "blitzreels clip-batches delete --batch-id ID --retention retain_projects_and_exports|delete_projects_and_exports --idempotency-key KEY --confirm-delete [--dry-run] [--json]",
@@ -47,9 +107,37 @@ export const CLIPS_COMMAND_SPECS = [
47
107
  ],
48
108
  description: "List managed clip runs with bounded output.",
49
109
  }),
110
+ commandSpec({
111
+ command: ["clips", "attach-export"],
112
+ usage: "blitzreels clips attach-export --asset-id ID --project-id ID --export-id ID --confirm-visual-qa --idempotency-key KEY [--dry-run] [--json]",
113
+ flags: [
114
+ "asset-id",
115
+ "project-id",
116
+ "export-id",
117
+ { name: "confirm-visual-qa", required: true },
118
+ { name: "idempotency-key", required: true },
119
+ ],
120
+ description: "Attach one visually accepted completed project export to managed clips.",
121
+ mutates: true,
122
+ destructive: true,
123
+ dryRunSupported: true,
124
+ }),
125
+ commandSpec({
126
+ command: ["clips", "promote-export"],
127
+ usage: "blitzreels clips promote-export --clip-id ID --export-id ID --confirm-visual-qa --idempotency-key KEY [--dry-run] [--json]",
128
+ flags: [
129
+ "clip-id",
130
+ "export-id",
131
+ { name: "confirm-visual-qa", required: true },
132
+ { name: "idempotency-key", required: true },
133
+ ],
134
+ description: "Promote one visually accepted completed export as the managed clip delivery.",
135
+ mutates: true,
136
+ dryRunSupported: true,
137
+ }),
50
138
  commandSpec({
51
139
  command: ["clips", "create"],
52
- usage: "blitzreels clips create [--asset-id ID|--youtube-url URL] [--output ./clips] [--count 5] [--idempotency-key KEY]",
140
+ usage: "blitzreels clips create [--asset-id ID|--youtube-url URL] [--output ./clips] [--count 5] [--layout-mode people_first] [--content-type-hint podcast] [--idempotency-key KEY]",
53
141
  flags: [
54
142
  { name: "asset-id", enum: [] },
55
143
  "youtube-url",
@@ -62,7 +150,7 @@ export const CLIPS_COMMAND_SPECS = [
62
150
  name: "count",
63
151
  type: "number",
64
152
  default: 5,
65
- description: "Maximum rendered clips to download from the batch.",
153
+ description: "Maximum clips created by the batch.",
66
154
  },
67
155
  {
68
156
  name: "preset",
@@ -79,6 +167,23 @@ export const CLIPS_COMMAND_SPECS = [
79
167
  hidden: false,
80
168
  },
81
169
  { name: "caption-theme-id" },
170
+ {
171
+ name: "layout-mode",
172
+ enum: [
173
+ "auto",
174
+ "people_first",
175
+ "screen_first",
176
+ "preserve_full_source",
177
+ "prefer_split",
178
+ "prefer_focus",
179
+ "prefer_tutorial",
180
+ "prefer_demo",
181
+ ],
182
+ },
183
+ {
184
+ name: "content-type-hint",
185
+ enum: ["auto", "podcast", "tutorial", "demo", "generic"],
186
+ },
82
187
  {
83
188
  name: "idempotency-key",
84
189
  description: "Stable retry key; reuse only with identical inputs.",
@@ -90,6 +195,7 @@ export const CLIPS_COMMAND_SPECS = [
90
195
  examples: [
91
196
  "blitzreels clips create --asset-id ID --output ./clips",
92
197
  "blitzreels clips create --asset-id ID --logo-asset-id LOGO_ID --output ./clips --count 5",
198
+ "blitzreels clips create --asset-id ID --layout-mode people_first --content-type-hint podcast",
93
199
  "blitzreels clips create --youtube-url https://youtube.com/watch?v=...",
94
200
  ],
95
201
  }),
@@ -107,7 +213,7 @@ export const CLIPS_COMMAND_SPECS = [
107
213
  }),
108
214
  commandSpec({
109
215
  command: ["clips", "reselect"],
110
- usage: "blitzreels clips reselect --clip-id ID [--suggestion-id ID | --start-seconds 52 --end-seconds 64]",
216
+ usage: "blitzreels clips reselect --clip-id ID [--suggestion-id ID | --start-seconds 52 --end-seconds 64] [--layout-mode people_first] [--content-type-hint podcast] [--caption-style-id cinematic-film] [--caption-word-reveal-animation fade] [--caption-max-lines 2] [--caption-words-per-line 8] [--caption-page-combine-ms 3000]",
111
217
  flags: [
112
218
  "clip-id",
113
219
  "suggestion-id",
@@ -120,8 +226,54 @@ export const CLIPS_COMMAND_SPECS = [
120
226
  },
121
227
  { name: "min-duration", hidden: true },
122
228
  { name: "max-duration", hidden: true },
229
+ {
230
+ name: "layout-mode",
231
+ enum: [
232
+ "auto",
233
+ "people_first",
234
+ "screen_first",
235
+ "preserve_full_source",
236
+ "prefer_split",
237
+ "prefer_focus",
238
+ "prefer_tutorial",
239
+ "prefer_demo",
240
+ ],
241
+ },
242
+ {
243
+ name: "content-type-hint",
244
+ enum: ["auto", "podcast", "tutorial", "demo", "generic"],
245
+ },
246
+ {
247
+ name: "tutorial-stack-ratio",
248
+ enum: ["screen_70_face_30", "screen_50_face_50"],
249
+ hidden: true,
250
+ },
251
+ "captions-enabled",
252
+ "caption-style-id",
253
+ {
254
+ name: "caption-word-reveal-animation",
255
+ enum: [
256
+ "none",
257
+ "fade",
258
+ "scale",
259
+ "slide-up",
260
+ "slide-left",
261
+ "slide-mix",
262
+ "pop",
263
+ "drop",
264
+ "slam",
265
+ "typewriter",
266
+ "glitch",
267
+ "mask-reveal",
268
+ "bounce-up",
269
+ "split-reveal",
270
+ ],
271
+ },
272
+ "caption-max-lines",
273
+ "caption-words-per-line",
274
+ "caption-page-combine-ms",
123
275
  ],
124
- description: "Choose a different suggestion or time range for a clip.",
276
+ description: "Regenerate a clip from a suggestion or time range with optional layout and caption settings.",
125
277
  mutates: true,
126
278
  }),
127
279
  commandSpec({
@@ -163,10 +315,101 @@ export const CLIPS_COMMAND_SPECS = [
163
315
  mutates: true,
164
316
  }),
165
317
  ];
318
+ function parseClipShareVisibility({ value, }) {
319
+ if (value === "visible" || value === "hidden") {
320
+ return value;
321
+ }
322
+ throw new CliUsageError({
323
+ code: "invalid_flag_value",
324
+ message: "--visibility must be visible or hidden.",
325
+ });
326
+ }
327
+ function printClipBatchResult({ context, result, helpers, }) {
328
+ helpers.printResult({
329
+ json: context.json,
330
+ value: result,
331
+ human: [
332
+ `Batch ${result.batch.batch_id}: ${result.batch.status}`,
333
+ `Studio Sound: ${result.batch.studio_sound.status}`,
334
+ `Accepted: ${result.batch.rendered_count}/${result.batch.estimated_clip_count}`,
335
+ ...(result.batch.last_error_message === null
336
+ ? []
337
+ : [`Error: ${result.batch.last_error_message}`]),
338
+ ],
339
+ });
340
+ }
341
+ async function waitForClipBatch({ context, helpers, }) {
342
+ const batchId = helpers.requireStringFlag({
343
+ flags: context.args.flags,
344
+ name: "batch-id",
345
+ });
346
+ const intervalMs = helpers.getNumberFlag({
347
+ flags: context.args.flags,
348
+ name: "interval-ms",
349
+ }) ?? 3000;
350
+ const timeoutMs = helpers.getNumberFlag({
351
+ flags: context.args.flags,
352
+ name: "timeout-ms",
353
+ }) ?? 20 * 60 * 1000;
354
+ const startedAt = Date.now();
355
+ while (true) {
356
+ const result = await context.client.clipBatches.get({ batchId });
357
+ if (result.batch.studio_sound.status === "failed") {
358
+ throw new CliOperationError({
359
+ code: "studio_sound_failed",
360
+ exitCode: 5,
361
+ message: `Clip batch ${batchId} failed Studio Sound: ${result.batch.studio_sound.error_message ?? result.batch.last_error_message ?? "treatment was not applied"}`,
362
+ retryable: false,
363
+ suggestions: [
364
+ `blitzreels clip-batches recover --batch-id ${batchId} --idempotency-key NEW_KEY --json`,
365
+ ],
366
+ });
367
+ }
368
+ if (result.batch.status === "failed") {
369
+ throw new CliOperationError({
370
+ code: "clip_batch_failed",
371
+ exitCode: 5,
372
+ message: `Clip batch ${batchId} failed: ${result.batch.last_error_message ?? result.batch.studio_sound.error_message ?? "clip processing failed"}`,
373
+ retryable: false,
374
+ suggestions: [
375
+ `blitzreels clip-batches recover --batch-id ${batchId} --idempotency-key NEW_KEY --json`,
376
+ ],
377
+ });
378
+ }
379
+ if (result.batch.status === "completed_with_errors") {
380
+ throw new CliOperationError({
381
+ code: "clip_batch_incomplete",
382
+ exitCode: 5,
383
+ message: `Clip batch ${batchId} completed with ${result.batch.failed_count + result.batch.review_count} clips needing recovery.`,
384
+ retryable: false,
385
+ suggestions: [
386
+ `blitzreels clip-batches recover --batch-id ${batchId} --idempotency-key NEW_KEY --json`,
387
+ ],
388
+ });
389
+ }
390
+ if (result.batch.status === "completed") {
391
+ return result;
392
+ }
393
+ if (Date.now() - startedAt >= timeoutMs) {
394
+ throw new CliOperationError({
395
+ code: "clip_batch_wait_timeout",
396
+ exitCode: 7,
397
+ message: `Timed out waiting for clip batch ${batchId}.`,
398
+ retryable: true,
399
+ suggestions: [
400
+ `blitzreels clip-batches get --batch-id ${batchId} --json`,
401
+ ],
402
+ });
403
+ }
404
+ await helpers.sleep({ ms: intervalMs });
405
+ }
406
+ }
166
407
  export function createClipsCommandOperation({ helpers, }) {
167
408
  return {
168
409
  name: "clips",
169
- matches: ({ command }) => command[0] === "clips" || command[0] === "clip-batches",
410
+ matches: ({ command }) => command[0] === "clips" ||
411
+ command[0] === "clip-batches" ||
412
+ command[0] === "clip-share-pages",
170
413
  handle: ({ context }) => handleClipsCommand({ context, helpers }),
171
414
  buildDryRun: ({ context, spec }) => buildClipsDryRun({ context, spec, helpers }),
172
415
  };
@@ -174,6 +417,114 @@ export function createClipsCommandOperation({ helpers, }) {
174
417
  async function handleClipsCommand({ context, helpers, }) {
175
418
  const action = context.args.command[1];
176
419
  const flags = context.args.flags;
420
+ if (context.args.command[0] === "clip-share-pages") {
421
+ const assetId = helpers.requireStringFlag({ flags, name: "asset-id" });
422
+ if (action === "get") {
423
+ const result = await context.client.clipSharePages.get({ assetId });
424
+ helpers.printResult({
425
+ json: context.json,
426
+ value: result,
427
+ human: [
428
+ result.enabled
429
+ ? `Published: ${result.share_url}`
430
+ : "Share page is not published.",
431
+ `Hidden clips: ${result.hidden_clip_ids.length}`,
432
+ ],
433
+ });
434
+ return true;
435
+ }
436
+ if (action === "publish" || action === "unpublish") {
437
+ const result = await context.client.clipSharePages.setEnabled({
438
+ assetId,
439
+ enabled: action === "publish",
440
+ idempotencyKey: helpers.requireStringFlag({
441
+ flags,
442
+ name: "idempotency-key",
443
+ }),
444
+ });
445
+ helpers.printResult({
446
+ json: context.json,
447
+ value: result,
448
+ human: [
449
+ result.enabled
450
+ ? `Published: ${result.share_url}`
451
+ : "Share page unpublished.",
452
+ ],
453
+ });
454
+ return true;
455
+ }
456
+ if (action === "set-clip-visibility") {
457
+ const visibility = parseClipShareVisibility({
458
+ value: helpers.requireStringFlag({
459
+ flags,
460
+ name: "visibility",
461
+ }),
462
+ });
463
+ const result = await context.client.clipSharePages.setClipVisibility({
464
+ assetId,
465
+ clipId: helpers.requireStringFlag({ flags, name: "clip-id" }),
466
+ visibility,
467
+ idempotencyKey: helpers.requireStringFlag({
468
+ flags,
469
+ name: "idempotency-key",
470
+ }),
471
+ });
472
+ helpers.printResult({
473
+ json: context.json,
474
+ value: result,
475
+ human: [`Clip ${result.clip_id}: ${result.visibility}`],
476
+ });
477
+ return true;
478
+ }
479
+ }
480
+ if (context.args.command[0] === "clip-batches" && action === "get") {
481
+ const result = await context.client.clipBatches.get({
482
+ batchId: helpers.requireStringFlag({ flags, name: "batch-id" }),
483
+ });
484
+ printClipBatchResult({ context, result, helpers });
485
+ return true;
486
+ }
487
+ if (context.args.command[0] === "clip-batches" && action === "wait") {
488
+ const result = await waitForClipBatch({ context, helpers });
489
+ printClipBatchResult({ context, result, helpers });
490
+ return true;
491
+ }
492
+ if (context.args.command[0] === "clip-batches" && action === "recover") {
493
+ const result = await context.client.clipBatches.recover({
494
+ batchId: helpers.requireStringFlag({ flags, name: "batch-id" }),
495
+ targetClipCount: helpers.getNumberFlag({ flags, name: "target-count" }) ?? null,
496
+ idempotencyKey: helpers.requireStringFlag({
497
+ flags,
498
+ name: "idempotency-key",
499
+ }),
500
+ });
501
+ if (result.status === "failed") {
502
+ throw new CliOperationError({
503
+ code: "clip_batch_recovery_failed",
504
+ exitCode: 5,
505
+ message: `Recovery batch ${result.recovery_batch_id ?? result.source_batch_id} failed: ${result.queue_reason ?? "clip recovery failed"}`,
506
+ retryable: false,
507
+ suggestions: [
508
+ 'blitzreels agent-context --command "clip-batches recover" --json',
509
+ ],
510
+ });
511
+ }
512
+ const statusLine = result.status === "satisfied"
513
+ ? `Batch ${result.source_batch_id} already has ${result.retained_clip_count}/${result.target_clip_count} accepted clips.`
514
+ : result.status === "queued"
515
+ ? `Recovery batch ${result.recovery_batch_id} queued for ${result.refill_clip_count} missing clips.`
516
+ : `Recovery batch ${result.recovery_batch_id} created; waiting: ${result.queue_reason ?? "not ready to queue"}.`;
517
+ helpers.printResult({
518
+ json: context.json,
519
+ value: result,
520
+ human: [
521
+ statusLine,
522
+ `Folder: ${result.project_folder_id}`,
523
+ "Duration policy: quality first; suggestion boundaries preserved.",
524
+ ],
525
+ });
526
+ return true;
527
+ }
177
528
  if (context.args.command[0] === "clip-batches" && action === "delete") {
178
529
  if (helpers.getOptionalBooleanFlag({ flags, name: "confirm-delete" }) !== true) {
179
530
  throw new Error("Pass --confirm-delete to delete a clip batch.");
@@ -216,6 +567,66 @@ async function handleClipsCommand({ context, helpers, }) {
216
567
  });
217
568
  return true;
218
569
  }
570
+ if (action === "attach-export") {
571
+ if (helpers.getOptionalBooleanFlag({
572
+ flags,
573
+ name: "confirm-visual-qa",
574
+ }) !== true) {
575
+ throw new CliUsageError({
576
+ code: "missing_visual_qa_confirmation",
577
+ message: "Pass --confirm-visual-qa only after reviewing the completed MP4.",
578
+ usage: "blitzreels clips attach-export --asset-id ID --project-id ID --export-id ID --confirm-visual-qa --idempotency-key KEY",
579
+ });
580
+ }
581
+ const result = await context.client.clips.attachExport({
582
+ assetId: helpers.requireStringFlag({ flags, name: "asset-id" }),
583
+ projectId: helpers.requireStringFlag({ flags, name: "project-id" }),
584
+ exportId: helpers.requireStringFlag({ flags, name: "export-id" }),
585
+ confirmVisualQa: true,
586
+ idempotencyKey: helpers.requireStringFlag({
587
+ flags,
588
+ name: "idempotency-key",
589
+ }),
590
+ });
591
+ helpers.printResult({
592
+ json: context.json,
593
+ value: result,
594
+ human: [
595
+ `Attached export ${result.export_id} to managed clip ${result.clip_id}.`,
596
+ ],
597
+ });
598
+ return true;
599
+ }
600
+ if (action === "promote-export") {
601
+ if (helpers.getOptionalBooleanFlag({
602
+ flags,
603
+ name: "confirm-visual-qa",
604
+ }) !== true) {
605
+ throw new CliUsageError({
606
+ code: "missing_visual_qa_confirmation",
607
+ message: "Pass --confirm-visual-qa only after reviewing the completed MP4.",
608
+ usage: "blitzreels clips promote-export --clip-id ID --export-id ID --confirm-visual-qa --idempotency-key KEY",
609
+ });
610
+ }
611
+ const result = await context.client.clips.promoteExport({
612
+ clipId: helpers.requireStringFlag({ flags, name: "clip-id" }),
613
+ exportId: helpers.requireStringFlag({ flags, name: "export-id" }),
614
+ confirmVisualQa: true,
615
+ idempotencyKey: helpers.requireStringFlag({
616
+ flags,
617
+ name: "idempotency-key",
618
+ }),
619
+ });
620
+ helpers.printResult({
621
+ json: context.json,
622
+ value: result,
623
+ human: [
624
+ `Promoted export ${result.export_id} for managed clip ${result.clip_id}.`,
625
+ `Previous export retained: ${result.previous_export_id}.`,
626
+ ],
627
+ });
628
+ return true;
629
+ }
219
630
  if (action === "list") {
220
631
  const result = await context.client.clips.list({
221
632
  assetId: helpers.getStringFlag({ flags, name: "asset-id" }),
@@ -259,6 +670,8 @@ async function handleClipsCommand({ context, helpers, }) {
259
670
  flags,
260
671
  name: "selection-mode",
261
672
  });
673
+ const layout = buildReselectLayout({ flags, helpers });
674
+ const captions = buildReselectCaptions({ flags, helpers });
262
675
  const result = await context.client.clips.reselect({
263
676
  clipId,
264
677
  ...(selectionMode === undefined
@@ -267,10 +680,14 @@ async function handleClipsCommand({ context, helpers, }) {
267
680
  startSeconds: selection.startSeconds,
268
681
  endSeconds: selection.endSeconds,
269
682
  target,
683
+ layout,
684
+ captions,
270
685
  }
271
686
  : {
272
687
  selection,
273
688
  target,
689
+ layout,
690
+ captions,
274
691
  }),
275
692
  });
276
693
  helpers.printClipResult({ context, result });
@@ -342,6 +759,122 @@ function formatClipListLine({ clip }) {
342
759
  function buildClipsDryRun({ context, spec, helpers, }) {
343
760
  const action = context.args.command[1];
344
761
  const flags = context.args.flags;
762
+ if (context.args.command[0] === "clips" && action === "attach-export") {
763
+ if (helpers.getOptionalBooleanFlag({
764
+ flags,
765
+ name: "confirm-visual-qa",
766
+ }) !== true) {
767
+ throw new CliUsageError({
768
+ code: "missing_visual_qa_confirmation",
769
+ message: "Pass --confirm-visual-qa only after reviewing the completed MP4.",
770
+ usage: "blitzreels clips attach-export --asset-id ID --project-id ID --export-id ID --confirm-visual-qa --idempotency-key KEY",
771
+ });
772
+ }
773
+ return helpers.dryRunRequest({
774
+ spec,
775
+ method: "POST",
776
+ path: "/clips/attach-export",
777
+ headers: {
778
+ "Idempotency-Key": helpers.requireStringFlag({
779
+ flags,
780
+ name: "idempotency-key",
781
+ }),
782
+ },
783
+ body: {
784
+ asset_id: helpers.requireStringFlag({ flags, name: "asset-id" }),
785
+ project_id: helpers.requireStringFlag({ flags, name: "project-id" }),
786
+ export_id: helpers.requireStringFlag({ flags, name: "export-id" }),
787
+ confirm_visual_qa: true,
788
+ },
789
+ });
790
+ }
791
+ if (context.args.command[0] === "clips" && action === "promote-export") {
792
+ if (helpers.getOptionalBooleanFlag({
793
+ flags,
794
+ name: "confirm-visual-qa",
795
+ }) !== true) {
796
+ throw new CliUsageError({
797
+ code: "missing_visual_qa_confirmation",
798
+ message: "Pass --confirm-visual-qa only after reviewing the completed MP4.",
799
+ usage: "blitzreels clips promote-export --clip-id ID --export-id ID --confirm-visual-qa --idempotency-key KEY",
800
+ });
801
+ }
802
+ const clipId = helpers.requireStringFlag({ flags, name: "clip-id" });
803
+ return helpers.dryRunRequest({
804
+ spec,
805
+ method: "POST",
806
+ path: `/clips/${encodeURIComponent(clipId)}/promote-export`,
807
+ headers: {
808
+ "Idempotency-Key": helpers.requireStringFlag({
809
+ flags,
810
+ name: "idempotency-key",
811
+ }),
812
+ },
813
+ body: {
814
+ export_id: helpers.requireStringFlag({ flags, name: "export-id" }),
815
+ confirm_visual_qa: true,
816
+ },
817
+ });
818
+ }
819
+ if (context.args.command[0] === "clip-share-pages") {
820
+ const assetId = helpers.requireStringFlag({ flags, name: "asset-id" });
821
+ if (action === "publish" || action === "unpublish") {
822
+ return helpers.dryRunRequest({
823
+ spec,
824
+ method: "POST",
825
+ path: `/clip-share-pages/${encodeURIComponent(assetId)}`,
826
+ headers: {
827
+ "Idempotency-Key": helpers.requireStringFlag({
828
+ flags,
829
+ name: "idempotency-key",
830
+ }),
831
+ },
832
+ body: { enabled: action === "publish" },
833
+ });
834
+ }
835
+ if (action === "set-clip-visibility") {
836
+ const clipId = helpers.requireStringFlag({ flags, name: "clip-id" });
837
+ return helpers.dryRunRequest({
838
+ spec,
839
+ method: "POST",
840
+ path: `/clip-share-pages/${encodeURIComponent(assetId)}/clips/${encodeURIComponent(clipId)}/visibility`,
841
+ headers: {
842
+ "Idempotency-Key": helpers.requireStringFlag({
843
+ flags,
844
+ name: "idempotency-key",
845
+ }),
846
+ },
847
+ body: {
848
+ visibility: parseClipShareVisibility({
849
+ value: helpers.requireStringFlag({
850
+ flags,
851
+ name: "visibility",
852
+ }),
853
+ }),
854
+ },
855
+ });
856
+ }
857
+ }
858
+ if (context.args.command[0] === "clip-batches" && action === "recover") {
859
+ return helpers.dryRunRequest({
860
+ spec,
861
+ method: "POST",
862
+ path: `/clip-batches/${encodeURIComponent(helpers.requireStringFlag({ flags, name: "batch-id" }))}/recover`,
863
+ headers: {
864
+ "Idempotency-Key": helpers.requireStringFlag({
865
+ flags,
866
+ name: "idempotency-key",
867
+ }),
868
+ },
869
+ body: {
870
+ target_clip_count: helpers.getNumberFlag({ flags, name: "target-count" }) ?? null,
871
+ },
872
+ notes: [
873
+ "Accepted exports remain linked; only the missing accepted count is refilled.",
874
+ "Suggestion boundaries are preserved even when a coherent clip exceeds the target duration.",
875
+ ],
876
+ });
877
+ }
345
878
  if (context.args.command[0] === "clip-batches" && action === "delete") {
346
879
  return helpers.dryRunRequest({
347
880
  spec,
@@ -392,8 +925,9 @@ function buildClipsDryRun({ context, spec, helpers, }) {
392
925
  quality: null,
393
926
  },
394
927
  clip_preset_id: options.clipPresetId,
928
+ max_clip_count: options.count,
395
929
  caption_theme_id: options.captionThemeId,
396
- create_flow_options: {},
930
+ create_flow_options: buildClipWorkflowCreateFlowOptions({ options }),
397
931
  branding_options: buildClipWorkflowBrandingOptions({
398
932
  logoAssetId: options.logoAssetId,
399
933
  }),
@@ -426,6 +960,8 @@ function buildClipsDryRun({ context, spec, helpers, }) {
426
960
  flags,
427
961
  name: "selection-mode",
428
962
  });
963
+ const layout = buildReselectLayout({ flags, helpers });
964
+ const captions = buildReselectCaptions({ flags, helpers });
429
965
  return helpers.dryRunRequest({
430
966
  spec,
431
967
  method: "POST",
@@ -449,6 +985,12 @@ function buildClipsDryRun({ context, spec, helpers, }) {
449
985
  max_duration_seconds: target.maxDurationSeconds,
450
986
  },
451
987
  }),
988
+ ...(layout === null
989
+ ? {}
990
+ : { layout: toApiClipLayout({ layout }) }),
991
+ ...(captions === null
992
+ ? {}
993
+ : { captions: toApiClipCaptions({ captions }) }),
452
994
  }
453
995
  : {
454
996
  selection: toApiClipSelection({ selection }),
@@ -458,6 +1000,8 @@ function buildClipsDryRun({ context, spec, helpers, }) {
458
1000
  min_duration_seconds: target.minDurationSeconds,
459
1001
  max_duration_seconds: target.maxDurationSeconds,
460
1002
  },
1003
+ layout: layout === null ? null : toApiClipLayout({ layout }),
1004
+ captions: captions === null ? null : toApiClipCaptions({ captions }),
461
1005
  },
462
1006
  });
463
1007
  }
@@ -501,6 +1045,16 @@ function parseClipBatchDeleteRetention({ value, }) {
501
1045
  }
502
1046
  throw new Error("--retention must be retain_projects_and_exports or delete_projects_and_exports.");
503
1047
  }
1048
+ function buildClipWorkflowCreateFlowOptions({ options, }) {
1049
+ return {
1050
+ ...(options.layoutMode === undefined
1051
+ ? {}
1052
+ : { reframeLayoutMode: options.layoutMode }),
1053
+ ...(options.contentTypeHint === undefined
1054
+ ? {}
1055
+ : { reframeContentTypeHint: options.contentTypeHint }),
1056
+ };
1057
+ }
504
1058
  function parseClipWorkflowOptions({ flags, helpers, }) {
505
1059
  const count = helpers.getNumberFlag({ flags, name: "count" }) ?? 5;
506
1060
  if (!Number.isInteger(count) || count < 1 || count > 10) {
@@ -540,6 +1094,12 @@ function parseClipWorkflowOptions({ flags, helpers, }) {
540
1094
  captionThemeId: parseNullableWorkflowString({
541
1095
  value: helpers.getStringFlag({ flags, name: "caption-theme-id" }),
542
1096
  }),
1097
+ layoutMode: parseClipLayoutMode({
1098
+ value: helpers.getStringFlag({ flags, name: "layout-mode" }),
1099
+ }),
1100
+ contentTypeHint: parseClipContentTypeHint({
1101
+ value: helpers.getStringFlag({ flags, name: "content-type-hint" }),
1102
+ }),
543
1103
  idempotencyKey: helpers.getStringFlag({ flags, name: "idempotency-key" }) ?? null,
544
1104
  };
545
1105
  }
@@ -574,8 +1134,9 @@ async function createClipWorkflow({ context, helpers, }) {
574
1134
  quality: null,
575
1135
  },
576
1136
  clipPresetId: options.clipPresetId,
1137
+ maxClipCount: options.count,
577
1138
  captionThemeId: options.captionThemeId,
578
- createFlowOptions: {},
1139
+ createFlowOptions: buildClipWorkflowCreateFlowOptions({ options }),
579
1140
  brandingOptions: buildClipWorkflowBrandingOptions({
580
1141
  logoAssetId: options.logoAssetId,
581
1142
  }),
@@ -695,4 +1256,138 @@ function toApiClipSelection({ selection, }) {
695
1256
  end_seconds: selection.endSeconds,
696
1257
  };
697
1258
  }
1259
+ function parseClipLayoutMode({ value, }) {
1260
+ if (value === undefined ||
1261
+ value === "auto" ||
1262
+ value === "people_first" ||
1263
+ value === "screen_first" ||
1264
+ value === "preserve_full_source" ||
1265
+ value === "prefer_split" ||
1266
+ value === "prefer_focus" ||
1267
+ value === "prefer_tutorial" ||
1268
+ value === "prefer_demo") {
1269
+ return value;
1270
+ }
1271
+ throw new Error(`Unsupported clip layout mode: ${value}`);
1272
+ }
1273
+ function parseClipContentTypeHint({ value, }) {
1274
+ if (value === undefined ||
1275
+ value === "auto" ||
1276
+ value === "podcast" ||
1277
+ value === "tutorial" ||
1278
+ value === "demo" ||
1279
+ value === "generic") {
1280
+ return value;
1281
+ }
1282
+ throw new Error(`Unsupported clip content type hint: ${value}`);
1283
+ }
1284
+ function parseTutorialStackRatio({ value, }) {
1285
+ if (value === undefined ||
1286
+ value === "screen_70_face_30" ||
1287
+ value === "screen_50_face_50") {
1288
+ return value;
1289
+ }
1290
+ throw new Error(`Unsupported tutorial stack ratio: ${value}`);
1291
+ }
1292
+ function buildReselectLayout({ flags, helpers, }) {
1293
+ const mode = parseClipLayoutMode({
1294
+ value: helpers.getStringFlag({ flags, name: "layout-mode" }),
1295
+ });
1296
+ const contentTypeHint = parseClipContentTypeHint({
1297
+ value: helpers.getStringFlag({ flags, name: "content-type-hint" }),
1298
+ });
1299
+ const tutorialStackRatio = parseTutorialStackRatio({
1300
+ value: helpers.getStringFlag({ flags, name: "tutorial-stack-ratio" }),
1301
+ });
1302
+ if (mode === undefined &&
1303
+ contentTypeHint === undefined &&
1304
+ tutorialStackRatio === undefined) {
1305
+ return null;
1306
+ }
1307
+ return { mode, contentTypeHint, tutorialStackRatio };
1308
+ }
1309
+ function buildReselectCaptions({ flags, helpers, }) {
1310
+ const enabled = helpers.getOptionalBooleanFlag({
1311
+ flags,
1312
+ name: "captions-enabled",
1313
+ });
1314
+ const styleId = helpers.getStringFlag({
1315
+ flags,
1316
+ name: "caption-style-id",
1317
+ });
1318
+ const wordRevealAnimation = parseCaptionWordRevealAnimation({
1319
+ value: helpers.getStringFlag({
1320
+ flags,
1321
+ name: "caption-word-reveal-animation",
1322
+ }),
1323
+ });
1324
+ const maxLinesPerCaption = helpers.getNumberFlag({
1325
+ flags,
1326
+ name: "caption-max-lines",
1327
+ });
1328
+ const wordsPerLineLimit = helpers.getNumberFlag({
1329
+ flags,
1330
+ name: "caption-words-per-line",
1331
+ });
1332
+ const pageCombineMs = helpers.getNumberFlag({
1333
+ flags,
1334
+ name: "caption-page-combine-ms",
1335
+ });
1336
+ if (enabled === undefined &&
1337
+ styleId === undefined &&
1338
+ wordRevealAnimation === undefined &&
1339
+ maxLinesPerCaption === undefined &&
1340
+ wordsPerLineLimit === undefined &&
1341
+ pageCombineMs === undefined) {
1342
+ return null;
1343
+ }
1344
+ return {
1345
+ enabled,
1346
+ styleId,
1347
+ wordRevealAnimation,
1348
+ maxLinesPerCaption,
1349
+ wordsPerLineLimit,
1350
+ pageCombineMs,
1351
+ };
1352
+ }
1353
+ function parseCaptionWordRevealAnimation({ value, }) {
1354
+ const supported = [
1355
+ "none",
1356
+ "fade",
1357
+ "scale",
1358
+ "slide-up",
1359
+ "slide-left",
1360
+ "slide-mix",
1361
+ "pop",
1362
+ "drop",
1363
+ "slam",
1364
+ "typewriter",
1365
+ "glitch",
1366
+ "mask-reveal",
1367
+ "bounce-up",
1368
+ "split-reveal",
1369
+ ];
1370
+ if (value === undefined ||
1371
+ supported.some((candidate) => candidate === value)) {
1372
+ return value;
1373
+ }
1374
+ throw new Error(`Unsupported caption word reveal animation: ${value}`);
1375
+ }
1376
+ function toApiClipLayout({ layout }) {
1377
+ return {
1378
+ layout_mode: layout.mode,
1379
+ content_type_hint: layout.contentTypeHint,
1380
+ tutorial_stack_ratio: layout.tutorialStackRatio,
1381
+ };
1382
+ }
1383
+ function toApiClipCaptions({ captions }) {
1384
+ return {
1385
+ enabled: captions.enabled,
1386
+ style_id: captions.styleId,
1387
+ word_reveal_animation: captions.wordRevealAnimation,
1388
+ max_lines_per_caption: captions.maxLinesPerCaption,
1389
+ words_per_line_limit: captions.wordsPerLineLimit,
1390
+ page_combine_ms: captions.pageCombineMs,
1391
+ };
1392
+ }
698
1393
  //# sourceMappingURL=clips.js.map