@scenerok/cli 2.7.0 → 2.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,6 +5,13 @@
5
5
  * validate run the *server's* static checks locally (no network, no auth)
6
6
  * publish validate, build the canonical tarball, upload, queue a build
7
7
  * status show one build's stage, checks and resulting version
8
+ * search / show / run the marketplace surface
9
+ * vidscript print or save a run's filled VidScript (#136)
10
+ * draft / render-draft paid drafts: script + preview first, render later (#194)
11
+ *
12
+ * The agent edit loop: `plugin run … --out promo.vid` → edit `promo.vid` (your
13
+ * coding agent can do this) → `scenerok render promo.vid`. `plugin vidscript
14
+ * <runId>` reads the same script back later through `GET /api/plugins/runs/[id]`.
8
15
  *
9
16
  * `validate` and `publish` share `src/lib/plugins/cli-checks-entry.ts` with the
10
17
  * server and the `plugin-builder` service, bundled into the CLI at build time
@@ -78,12 +85,45 @@ export interface PluginRunOptions {
78
85
  set?: string[];
79
86
  maxTokens?: string | number;
80
87
  dryRun?: boolean;
88
+ /** Write the filled VidScript here so it can be edited and re-rendered. */
89
+ out?: string;
90
+ json?: boolean;
91
+ write?: Write;
92
+ }
93
+ export interface PluginVidscriptOptions {
94
+ runId: string;
95
+ /** Write the script to this file instead of stdout. */
96
+ out?: string;
97
+ json?: boolean;
98
+ write?: Write;
99
+ }
100
+ export interface PluginDraftOptions {
101
+ ref: string;
102
+ input?: string;
103
+ set?: string[];
104
+ maxTokens?: string | number;
105
+ /** Client retry key: a retried draft with the same key is free. */
106
+ idempotencyKey?: string;
107
+ /** Save the filled VidScript here so it can be edited before rendering. */
108
+ out?: string;
109
+ json?: boolean;
110
+ write?: Write;
111
+ }
112
+ export interface PluginRenderDraftOptions {
113
+ draftId: string;
114
+ /** Render this edited script instead of the one the draft produced. */
115
+ vidscript?: string;
81
116
  json?: boolean;
82
117
  write?: Write;
83
118
  }
84
119
  export declare function runPluginSearch(options?: PluginSearchOptions): Promise<number>;
85
120
  export declare function runPluginShow(options: PluginShowOptions): Promise<number>;
86
121
  export declare function runPluginRunCommand(options: PluginRunOptions): Promise<number>;
122
+ export declare function runPluginVidscriptCommand(options: PluginVidscriptOptions): Promise<number>;
123
+ /** `scenerok plugin draft` — run the plugin, pay for the script, render later. */
124
+ export declare function runPluginDraftCommand(options: PluginDraftOptions): Promise<number>;
125
+ /** `scenerok plugin render-draft` — render seconds only; the fee is already paid. */
126
+ export declare function runPluginRenderDraftCommand(options: PluginRenderDraftOptions): Promise<number>;
87
127
  export declare const pluginCommand: Command;
88
128
  export {};
89
129
  //# sourceMappingURL=plugin.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../../src/commands/plugin.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../../src/commands/plugin.ts"],"names":[],"mappings":"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"}
@@ -5,6 +5,13 @@
5
5
  * validate run the *server's* static checks locally (no network, no auth)
6
6
  * publish validate, build the canonical tarball, upload, queue a build
7
7
  * status show one build's stage, checks and resulting version
8
+ * search / show / run the marketplace surface
9
+ * vidscript print or save a run's filled VidScript (#136)
10
+ * draft / render-draft paid drafts: script + preview first, render later (#194)
11
+ *
12
+ * The agent edit loop: `plugin run … --out promo.vid` → edit `promo.vid` (your
13
+ * coding agent can do this) → `scenerok render promo.vid`. `plugin vidscript
14
+ * <runId>` reads the same script back later through `GET /api/plugins/runs/[id]`.
8
15
  *
9
16
  * `validate` and `publish` share `src/lib/plugins/cli-checks-entry.ts` with the
10
17
  * server and the `plugin-builder` service, bundled into the CLI at build time
@@ -18,7 +25,7 @@ import chalk from 'chalk';
18
25
  import ora from 'ora';
19
26
  import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from 'node:fs';
20
27
  import { dirname, join, resolve } from 'node:path';
21
- import { getPlugin, getPluginBuild, runPlugin, searchPlugins, publishPluginPackage, } from '../lib/api.js';
28
+ import { ApiError, draftPlugin, getPlugin, getPluginBuild, getPluginRun, renderPluginDraft, runPlugin, searchPlugins, publishPluginPackage, } from '../lib/api.js';
22
29
  import { getBaseUrl, isAuthenticated } from '../lib/config.js';
23
30
  import { formatApiErrorMessage } from '../lib/format-api-error.js';
24
31
  import { logError } from '../lib/logger.js';
@@ -155,6 +162,7 @@ export async function runPluginValidate(options = {}) {
155
162
  write('');
156
163
  }
157
164
  write(chalk.dim(` ${read.files.size} files · ${formatBytes(read.bytes)} · ${elapsedMs} ms · local checks`));
165
+ write(chalk.dim(' Source-only: SceneRok builds every build.json entry itself and serves only those artifacts, by hash.'));
158
166
  write('');
159
167
  return report.ok ? EXIT_OK : EXIT_FAILED;
160
168
  }
@@ -211,6 +219,7 @@ export async function runPluginPublish(options = {}) {
211
219
  }
212
220
  write(chalk.green(` ✔ Local checks passed`) +
213
221
  chalk.dim(` (${Date.now() - validateStarted} ms · ${read.files.size} files · ${formatBytes(read.bytes)})`));
222
+ write(chalk.dim(' Source-only: uploading source and build.json; SceneRok builds it twice and serves the built artifacts by hash.'));
214
223
  // 2. Canonical tarball + content hash (the server recomputes it).
215
224
  const checks = await loadPluginChecks();
216
225
  const tarball = checks.buildCanonicalTarGz(read.files);
@@ -500,6 +509,20 @@ function printPluginRunResult(result, write) {
500
509
  write(`Download: ${result.render.downloadUrl}`);
501
510
  write(chalk.dim(`Next: scenerok status ${result.render.renderId}`));
502
511
  }
512
+ if (result.runId && result.vidscript) {
513
+ write('');
514
+ write(chalk.cyan(' Edit this video:'));
515
+ write(` scenerok plugin vidscript ${result.runId} --out promo.vid`);
516
+ write(chalk.dim(' # then change the script and render the edited copy'));
517
+ write(' scenerok render promo.vid --watch');
518
+ }
519
+ }
520
+ /** Save a filled VidScript so it can be edited and re-rendered. Returns the absolute path. */
521
+ function saveVidscript(path, vidscript) {
522
+ const absolute = resolve(path);
523
+ mkdirSync(dirname(absolute), { recursive: true });
524
+ writeFileSync(absolute, vidscript, 'utf-8');
525
+ return absolute;
503
526
  }
504
527
  export async function runPluginSearch(options = {}) {
505
528
  const write = options.write ?? ((line) => console.log(line));
@@ -563,6 +586,30 @@ export async function runPluginRunCommand(options) {
563
586
  maxTokens,
564
587
  dryRun: options.dryRun,
565
588
  });
589
+ if (options.out) {
590
+ if (!result.vidscript) {
591
+ write(chalk.red(result.dryRun
592
+ ? 'Error: a dry run creates no render, so there is no VidScript to save. Drop --dry-run.'
593
+ : 'Error: this run returned no VidScript to save.'));
594
+ write('');
595
+ return EXIT_FAILED;
596
+ }
597
+ const saved = saveVidscript(options.out, result.vidscript);
598
+ if (options.json) {
599
+ write(JSON.stringify({ ...result, vidscriptPath: saved }, null, 2));
600
+ }
601
+ else {
602
+ write('');
603
+ write(chalk.green('✔ Saved VidScript') + chalk.dim(` (${result.ref.raw})`));
604
+ write(chalk.dim(` ${saved}`));
605
+ write('');
606
+ write(chalk.cyan(' Next:'));
607
+ write(chalk.dim(` # edit ${options.out} — ask your coding agent, or open it yourself`));
608
+ write(` scenerok render ${options.out} --watch`);
609
+ write('');
610
+ }
611
+ return EXIT_OK;
612
+ }
566
613
  if (options.json)
567
614
  write(JSON.stringify(result, null, 2));
568
615
  else
@@ -576,6 +623,223 @@ export async function runPluginRunCommand(options) {
576
623
  }
577
624
  }
578
625
  // ---------------------------------------------------------------------------
626
+ // vidscript — read back the filled script of a finished run
627
+ // ---------------------------------------------------------------------------
628
+ export async function runPluginVidscriptCommand(options) {
629
+ const write = options.write ?? ((line) => console.log(line));
630
+ if (!isAuthenticated()) {
631
+ write(chalk.yellow('Not authenticated'));
632
+ write(chalk.dim('Run: scenerok auth login'));
633
+ return EXIT_USAGE;
634
+ }
635
+ const runId = (options.runId ?? '').trim();
636
+ if (!runId) {
637
+ return fail(write, 'missing run id. Usage: scenerok plugin vidscript <runId> [--out <file.vid>]');
638
+ }
639
+ try {
640
+ const view = await getPluginRun(runId);
641
+ if (options.json) {
642
+ const saved = options.out && view.vidscript ? saveVidscript(options.out, view.vidscript) : null;
643
+ write(JSON.stringify(saved ? { ...view, vidscriptPath: saved } : view, null, 2));
644
+ return view.vidscript ? EXIT_OK : EXIT_FAILED;
645
+ }
646
+ if (!view.vidscript) {
647
+ write(chalk.yellow(` Run ${view.run.id} has no VidScript yet (${view.run.status}).`));
648
+ write(chalk.dim(' The script is stored once the run has created a render.'));
649
+ write('');
650
+ return EXIT_FAILED;
651
+ }
652
+ if (options.out) {
653
+ const saved = saveVidscript(options.out, view.vidscript);
654
+ write(chalk.green('✔ Saved VidScript') + chalk.dim(` (${view.run.ref.ref})`));
655
+ write(chalk.dim(` ${saved}`));
656
+ if (view.render)
657
+ write(chalk.dim(` Original render: ${view.render.renderUrl}`));
658
+ write('');
659
+ write(chalk.cyan(' Next:'));
660
+ write(chalk.dim(` # edit ${options.out} — ask your coding agent, or open it yourself`));
661
+ write(` scenerok render ${options.out} --watch`);
662
+ write('');
663
+ return EXIT_OK;
664
+ }
665
+ // Raw script on stdout, so it can be piped or pasted into an agent.
666
+ write(view.vidscript);
667
+ return EXIT_OK;
668
+ }
669
+ catch (error) {
670
+ write(chalk.red(` ${formatApiErrorMessage(error)}`));
671
+ write('');
672
+ return EXIT_FAILED;
673
+ }
674
+ }
675
+ // ---------------------------------------------------------------------------
676
+ // draft / render-draft — paid drafts (#194)
677
+ // ---------------------------------------------------------------------------
678
+ function printDraftResult(result, write) {
679
+ const { estimate } = result;
680
+ write(chalk.green(`Draft ${result.draftId}`) +
681
+ chalk.dim(` · ${result.ref.raw}${result.duplicate ? ' · replayed (not charged)' : ''}`));
682
+ write('');
683
+ write(` ${chalk.bold('Charged now')} ${estimate.draft.totalTokens} tokens`);
684
+ write(chalk.dim(` providers ${estimate.draft.providerTokens} · plugin fee ${estimate.draft.pluginFeeTokens}`));
685
+ write(` ${chalk.bold('On render')} ${estimate.render.renderTokens} tokens`);
686
+ write(chalk.dim(` about ${estimate.render.renderSeconds}s of video · no plugin fee again`));
687
+ write(` ${chalk.bold('Full run')} ${estimate.totalTokens} tokens`);
688
+ write('');
689
+ const { summary } = result.preview;
690
+ write(chalk.dim(` Preview: ${result.preview.durationSeconds}s · ${summary.clips} clip${summary.clips === 1 ? '' : 's'} · ` +
691
+ `${summary.textLayers} text layer${summary.textLayers === 1 ? '' : 's'} · ` +
692
+ `${summary.audioTracks} audio track${summary.audioTracks === 1 ? '' : 's'}`));
693
+ if (result.preview.warnings.length > 0) {
694
+ write(chalk.yellow(` ${result.preview.warnings.slice(0, 3).join(' · ')}`));
695
+ }
696
+ write('');
697
+ write(chalk.cyan(' Next:'));
698
+ write(' # the filled VidScript is below (or save it with --out draft.vid)');
699
+ write(` scenerok plugin render-draft ${result.draftId} --vidscript draft.vid`);
700
+ write(chalk.dim(' # or render it unchanged:'));
701
+ write(` scenerok plugin render-draft ${result.draftId}`);
702
+ write('');
703
+ }
704
+ /** The plain-object view of an unknown JSON body. */
705
+ function asRecord(value) {
706
+ return typeof value === 'object' && value !== null && !Array.isArray(value)
707
+ ? value
708
+ : {};
709
+ }
710
+ /**
711
+ * A **payable** draft 402 (#194): the VidScript is produced and stored, the
712
+ * provider calls are paid for, and only the plugin fee is outstanding. The retry
713
+ * must reuse the *same* key — a fresh key would pay for a second draft — so the
714
+ * key is printed as a ready-to-run command.
715
+ */
716
+ function printPayableDraftHint(error, options, write) {
717
+ if (!(error instanceof ApiError))
718
+ return false;
719
+ const body = asRecord(error.responseBody);
720
+ const details = asRecord(body.details);
721
+ if (body.payable !== true && details.payable !== true)
722
+ return false;
723
+ const text = (value) => (typeof value === 'string' && value ? value : undefined);
724
+ const count = (value) => (typeof value === 'number' ? value : undefined);
725
+ const draftId = text(body.draftId) ?? text(details.draftId);
726
+ const key = text(body.idempotencyKey) ?? text(details.idempotencyKey) ?? options.idempotencyKey;
727
+ const fee = count(body.tokensRequired) ?? count(details.tokensRequired);
728
+ const balance = count(body.tokensAvailable) ?? count(details.tokensAvailable);
729
+ write(chalk.yellow(' Top up to finish this draft.'));
730
+ write(chalk.dim(' The VidScript is already produced and stored; only the plugin fee is left to charge.'));
731
+ if (draftId)
732
+ write(` Draft: ${draftId}`);
733
+ if (fee !== undefined) {
734
+ write(` Fee: ${fee} tokens${balance === undefined ? '' : chalk.dim(` (balance ${balance})`)}`);
735
+ }
736
+ if (key) {
737
+ write(chalk.cyan(' Then retry with the same key (it finishes this draft, it does not charge a new one):'));
738
+ const maxTokens = options.maxTokens === undefined ? '' : ` --max-tokens ${options.maxTokens}`;
739
+ write(` scenerok plugin draft ${options.ref}${maxTokens} --idempotency-key ${key}${options.out ? ` --out ${options.out}` : ''}`);
740
+ }
741
+ write('');
742
+ return true;
743
+ }
744
+ /** `scenerok plugin draft` — run the plugin, pay for the script, render later. */
745
+ export async function runPluginDraftCommand(options) {
746
+ const write = options.write ?? ((line) => console.log(line));
747
+ if (!isAuthenticated()) {
748
+ write(chalk.yellow('Not authenticated'));
749
+ write(chalk.dim('Run: scenerok auth login'));
750
+ return EXIT_USAGE;
751
+ }
752
+ try {
753
+ const inputs = loadRunInputs(options);
754
+ const maxTokens = parseMaxTokens(options.maxTokens);
755
+ const result = await draftPlugin({
756
+ ref: options.ref,
757
+ inputs,
758
+ maxTokens,
759
+ ...(options.idempotencyKey ? { idempotencyKey: options.idempotencyKey } : {}),
760
+ });
761
+ if (options.out) {
762
+ const saved = saveVidscript(options.out, result.vidscript);
763
+ if (options.json) {
764
+ write(JSON.stringify({ ...result, vidscriptPath: saved }, null, 2));
765
+ return EXIT_OK;
766
+ }
767
+ write('');
768
+ write(chalk.green('✔ Draft ready') + chalk.dim(` (${result.ref.raw})`));
769
+ write(chalk.dim(` charged ${result.estimate.draft.totalTokens} tokens now · ` +
770
+ `${result.estimate.render.renderTokens} tokens when rendered`));
771
+ write('');
772
+ write(chalk.green('✔ Saved VidScript') + chalk.dim(` (${options.out})`));
773
+ write(chalk.dim(` ${saved}`));
774
+ write('');
775
+ write(chalk.cyan(' Next:'));
776
+ write(chalk.dim(` # edit ${options.out} — ask your coding agent, or open it yourself`));
777
+ write(` scenerok plugin render-draft ${result.draftId} --vidscript ${options.out}`);
778
+ write('');
779
+ return EXIT_OK;
780
+ }
781
+ if (options.json) {
782
+ write(JSON.stringify(result, null, 2));
783
+ return EXIT_OK;
784
+ }
785
+ printDraftResult(result, write);
786
+ // The script on stdout, so it can be piped or saved.
787
+ write(result.vidscript);
788
+ return EXIT_OK;
789
+ }
790
+ catch (error) {
791
+ write(chalk.red(` ${formatApiErrorMessage(error)}`));
792
+ write('');
793
+ printPayableDraftHint(error, options, write);
794
+ return EXIT_FAILED;
795
+ }
796
+ }
797
+ /** `scenerok plugin render-draft` — render seconds only; the fee is already paid. */
798
+ export async function runPluginRenderDraftCommand(options) {
799
+ const write = options.write ?? ((line) => console.log(line));
800
+ if (!isAuthenticated()) {
801
+ write(chalk.yellow('Not authenticated'));
802
+ write(chalk.dim('Run: scenerok auth login'));
803
+ return EXIT_USAGE;
804
+ }
805
+ const draftId = (options.draftId ?? '').trim();
806
+ if (!draftId) {
807
+ return fail(write, 'missing draft id. Usage: scenerok plugin render-draft <draftId> [--vidscript edited.vid]');
808
+ }
809
+ try {
810
+ let vidscript;
811
+ if (options.vidscript) {
812
+ const path = resolve(options.vidscript);
813
+ if (!existsSync(path)) {
814
+ return fail(write, `--vidscript ${options.vidscript} does not exist`);
815
+ }
816
+ vidscript = readFileSync(path, 'utf-8');
817
+ }
818
+ const result = await renderPluginDraft(draftId, { vidscript });
819
+ if (options.json) {
820
+ write(JSON.stringify(result, null, 2));
821
+ return EXIT_OK;
822
+ }
823
+ write('');
824
+ write(chalk.green(`Render ${result.render.renderId}`) +
825
+ chalk.dim(` · ${result.render.status}${result.edited ? ' · edited script' : ''}`));
826
+ if (result.render.renderUrl)
827
+ write(`Page: ${result.render.renderUrl}`);
828
+ if (result.render.downloadUrl)
829
+ write(`Download: ${result.render.downloadUrl}`);
830
+ write(chalk.dim(` render only: about ${result.estimate.renderSeconds}s · ${result.estimate.renderTokens} tokens. ` +
831
+ `The plugin fee (${result.feeTokens} tokens) was charged with the draft.`));
832
+ write('');
833
+ write(chalk.dim(`Next: scenerok status ${result.render.renderId}`));
834
+ return EXIT_OK;
835
+ }
836
+ catch (error) {
837
+ write(chalk.red(` ${formatApiErrorMessage(error)}`));
838
+ write('');
839
+ return EXIT_FAILED;
840
+ }
841
+ }
842
+ // ---------------------------------------------------------------------------
579
843
  // command wiring
580
844
  // ---------------------------------------------------------------------------
581
845
  export const pluginCommand = new Command('plugin')
@@ -645,7 +909,39 @@ export const pluginCommand = new Command('plugin')
645
909
  ])
646
910
  .requiredOption('--max-tokens <n>', 'Hard token cap; refused before debit if worst-case is higher')
647
911
  .option('--dry-run', 'Return estimate and bound without creating a render')
912
+ .option('--out <file>', 'Save the filled VidScript so you can edit and re-render it')
648
913
  .option('--json', 'Machine-readable output')
649
914
  .action(async (ref, options) => {
650
915
  process.exitCode = await runPluginRunCommand({ ...options, ref });
916
+ }))
917
+ .addCommand(new Command('draft')
918
+ .description('Run a plugin and pay for the filled VidScript — preview it, then render')
919
+ .argument('<ref>', 'Pinned plugin ref')
920
+ .option('--input <file>', 'JSON input object')
921
+ .option('--set <key=value>', 'Set or override an input value (repeatable)', (value, previous = []) => [
922
+ ...previous,
923
+ value,
924
+ ])
925
+ .requiredOption('--max-tokens <n>', 'Hard cap for the draft (providers + plugin fee)')
926
+ .option('--idempotency-key <key>', 'Retry key: the same key returns the same draft for free')
927
+ .option('--out <file>', 'Save the filled VidScript so you can edit it before rendering')
928
+ .option('--json', 'Machine-readable output')
929
+ .action(async (ref, options) => {
930
+ process.exitCode = await runPluginDraftCommand({ ...options, ref });
931
+ }))
932
+ .addCommand(new Command('render-draft')
933
+ .description('Render a paid draft (render tokens only); optionally an edited VidScript')
934
+ .argument('<draftId>', 'Draft id printed by `scenerok plugin draft`')
935
+ .option('--vidscript <file>', 'Render this edited script instead of the draft’s own')
936
+ .option('--json', 'Machine-readable output')
937
+ .action(async (draftId, options) => {
938
+ process.exitCode = await runPluginRenderDraftCommand({ ...options, draftId });
939
+ }))
940
+ .addCommand(new Command('vidscript')
941
+ .description('Print or save a run’s filled VidScript; edit it, then `scenerok render` it')
942
+ .argument('<runId>', 'Run id printed by `scenerok plugin run`')
943
+ .option('--out <file>', 'Write the script to a .vid file instead of stdout')
944
+ .option('--json', 'Machine-readable run detail')
945
+ .action(async (runId, options) => {
946
+ process.exitCode = await runPluginVidscriptCommand({ ...options, runId });
651
947
  }));
package/dist/lib/api.d.ts CHANGED
@@ -396,6 +396,43 @@ export interface PluginRunResult {
396
396
  renderUrl?: string;
397
397
  downloadUrl?: string;
398
398
  } | null;
399
+ /**
400
+ * The filled, validated VidScript the render was created from. Save it with
401
+ * `--out <file.vid>`, edit it, then render the edited copy with
402
+ * `scenerok render <file.vid>`.
403
+ */
404
+ vidscript?: string | null;
405
+ }
406
+ /** `GET /api/plugins/runs/[id]` — the run detail an agent edits from. */
407
+ export interface PluginRunDetail {
408
+ run: {
409
+ id: string;
410
+ status: string;
411
+ source: string;
412
+ createdAt: string;
413
+ ref: {
414
+ namespace: string;
415
+ slug: string;
416
+ version: number;
417
+ versionLabel: string | null;
418
+ ref: string;
419
+ };
420
+ estimate: unknown;
421
+ };
422
+ render: {
423
+ renderId: number;
424
+ status: string;
425
+ progress: number;
426
+ outputUrl: string | null;
427
+ renderUrl: string;
428
+ downloadUrl: string | null;
429
+ downloadReady: boolean;
430
+ error: string | null;
431
+ completedAt: string | null;
432
+ } | null;
433
+ /** The filled VidScript to edit and re-render; null when no render was created. */
434
+ vidscript: string | null;
435
+ inputs: Record<string, unknown>;
399
436
  }
400
437
  export declare function searchPlugins(options?: {
401
438
  q?: string;
@@ -415,4 +452,148 @@ export declare function runPlugin(options: {
415
452
  maxTokens: number;
416
453
  dryRun?: boolean;
417
454
  }): Promise<PluginRunResult>;
455
+ /** Read one run: `{ run, render, vidscript, inputs }`, owner-scoped. */
456
+ export declare function getPluginRun(runId: string): Promise<PluginRunDetail>;
457
+ /**
458
+ * A draft's estimate splits the full run cost into the part charged when the
459
+ * draft is produced (providers + plugin fee) and the part charged later, when
460
+ * it is rendered (render seconds only).
461
+ */
462
+ export interface PluginDraftEstimate {
463
+ draft: {
464
+ providerTokens: number;
465
+ pluginFeeTokens: number;
466
+ totalTokens: number;
467
+ };
468
+ render: {
469
+ renderSeconds: number;
470
+ renderSecondsSource: string;
471
+ renderTokens: number;
472
+ totalTokens: number;
473
+ };
474
+ totalTokens: number;
475
+ worstCase: {
476
+ draftTokens: number;
477
+ renderTokens: number;
478
+ totalTokens: number;
479
+ };
480
+ providerLines: Array<{
481
+ route: string;
482
+ tokens: number;
483
+ priced: boolean;
484
+ }>;
485
+ }
486
+ /**
487
+ * Cheap preview data: the compiled IR the web preview player consumes, plus a
488
+ * summary. `estimated: true` means plugin return values inside `ir` came from
489
+ * the dry-run loader — timings and assets are real.
490
+ */
491
+ export interface PluginDraftPreview {
492
+ ir: unknown | null;
493
+ durationSeconds: number;
494
+ width: number;
495
+ height: number;
496
+ fps: number;
497
+ renderSeconds: number;
498
+ warnings: string[];
499
+ summary: {
500
+ clips: number;
501
+ textLayers: number;
502
+ surfaces: number;
503
+ audioTracks: number;
504
+ groups: number;
505
+ markers: number;
506
+ };
507
+ estimated: true;
508
+ }
509
+ /** `POST /api/plugins/drafts` — the paid draft: script + preview, no render. */
510
+ export interface PluginDraftResult {
511
+ mode: 'draft';
512
+ draftId: string;
513
+ /** pending | succeeded | failed */
514
+ status: string;
515
+ pluginVersionId: number;
516
+ ref: PluginRunResult['ref'];
517
+ /** The filled, validated VidScript — the edit surface. */
518
+ vidscript: string;
519
+ preview: PluginDraftPreview;
520
+ inputs: Record<string, unknown>;
521
+ estimate: PluginDraftEstimate;
522
+ chargedProviderTokens: number;
523
+ feeTokens: number;
524
+ duplicate: boolean;
525
+ next: {
526
+ render: string;
527
+ detail: string;
528
+ };
529
+ }
530
+ export interface PluginDraftRenderView {
531
+ renderId: number;
532
+ status: string;
533
+ progress: number;
534
+ outputUrl: string | null;
535
+ renderUrl: string;
536
+ downloadUrl: string | null;
537
+ downloadReady: boolean;
538
+ error: string | null;
539
+ completedAt: string | null;
540
+ }
541
+ /** `GET /api/plugins/drafts/[id]` — owner-scoped draft detail. */
542
+ export interface PluginDraftDetail {
543
+ draft: {
544
+ id: string;
545
+ status: string;
546
+ source: string;
547
+ createdAt: string;
548
+ ref: {
549
+ namespace: string;
550
+ slug: string;
551
+ version: number;
552
+ versionLabel: string | null;
553
+ ref: string;
554
+ pluginVersionId: number;
555
+ };
556
+ estimate: unknown;
557
+ chargedProviderTokens: number;
558
+ feeTokens: number;
559
+ error: unknown;
560
+ };
561
+ /** The filled script; null until the draft succeeded. */
562
+ vidscript: string | null;
563
+ preview: PluginDraftPreview | null;
564
+ inputs: Record<string, unknown>;
565
+ renders: PluginDraftRenderView[];
566
+ }
567
+ /** `POST /api/plugins/drafts/[id]/render` — render seconds only, no fee. */
568
+ export interface PluginDraftRenderResult {
569
+ mode: 'draft';
570
+ draftId: string;
571
+ pluginVersionId: number;
572
+ ref: string;
573
+ render: {
574
+ renderId: number;
575
+ status: string;
576
+ resolution?: string;
577
+ renderUrl?: string;
578
+ downloadUrl?: string;
579
+ };
580
+ vidscript: string;
581
+ edited: boolean;
582
+ estimate: {
583
+ renderSeconds: number;
584
+ renderTokens: number;
585
+ };
586
+ feeTokens: number;
587
+ feeChargedWithDraft: true;
588
+ }
589
+ export declare function draftPlugin(options: {
590
+ ref: string;
591
+ inputs: Record<string, unknown>;
592
+ maxTokens: number;
593
+ idempotencyKey?: string;
594
+ }): Promise<PluginDraftResult>;
595
+ export declare function getPluginDraft(draftId: string): Promise<PluginDraftDetail>;
596
+ export declare function renderPluginDraft(draftId: string, options?: {
597
+ vidscript?: string;
598
+ }): Promise<PluginDraftRenderResult>;
418
599
  //# sourceMappingURL=api.d.ts.map