makefx 1.4.0 → 1.6.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.
Files changed (3) hide show
  1. package/README.md +1 -1
  2. package/makefx.mjs +1288 -147
  3. package/package.json +1 -1
package/makefx.mjs CHANGED
@@ -621,9 +621,85 @@ function isProviderAuthMessage(message, providerErrorCode, provider) {
621
621
  return providerErrorCode?.toLowerCase() === "api_key_invalid" || message.includes("api_key_invalid") || message.includes("api key not valid") || message.includes("invalid api key") || message.includes("api key is required") || message.includes("not configured") || message.includes("unauthenticated");
622
622
  }
623
623
  //#endregion
624
+ //#region src/shared/generation-blockers.ts
625
+ var GENERATION_BLOCKER_CODES = [
626
+ "SUBSCRIPTION_REQUIRED",
627
+ "PROVIDER_KEY_REQUIRED",
628
+ "BYOK_CREDENTIAL_FAILED",
629
+ "MANAGED_AI_UNAVAILABLE"
630
+ ];
631
+ var PROVIDER_LABELS = {
632
+ google_ai: "Google AI",
633
+ elevenlabs: "ElevenLabs",
634
+ lyria: "Lyria",
635
+ kling: "Kling",
636
+ fal: "fal.ai",
637
+ replicate: "Replicate"
638
+ };
639
+ function isGenerationBlockerCode(code) {
640
+ return GENERATION_BLOCKER_CODES.includes(code);
641
+ }
642
+ function inferGenerationBlockerProvider(message) {
643
+ const normalized = message?.toLowerCase() ?? "";
644
+ if (normalized.includes("google ai") || normalized.includes("gemini") || normalized.includes("veo")) return "google_ai";
645
+ if (normalized.includes("elevenlabs")) return "elevenlabs";
646
+ if (normalized.includes("lyria")) return "lyria";
647
+ if (normalized.includes("kling")) return "kling";
648
+ if (normalized.includes("fal.ai") || normalized.includes("fal provider")) return "fal";
649
+ if (normalized.includes("replicate")) return "replicate";
650
+ }
651
+ function generationBlockerProviderFromService(provider) {
652
+ if (provider === "google_ai" || provider === "veo") return "google_ai";
653
+ if (provider === "elevenlabs" || provider === "lyria" || provider === "kling" || provider === "fal" || provider === "replicate") return provider;
654
+ }
655
+ function getGenerationBlockerPresentation(code, serverMessage, provider) {
656
+ if (!isGenerationBlockerCode(code)) return null;
657
+ if (code === "SUBSCRIPTION_REQUIRED") return {
658
+ code,
659
+ message: "A BYOK Platform subscription is required before generation can start.",
660
+ retryable: false,
661
+ actionLabel: "View billing",
662
+ actionPath: "/profile#billing",
663
+ remediation: "Start or restore the BYOK Platform subscription at https://makefx.app/profile#billing, then try again."
664
+ };
665
+ if (code === "MANAGED_AI_UNAVAILABLE") return {
666
+ code,
667
+ message: "Managed AI is coming soon and cannot be selected for customer generation.",
668
+ retryable: false,
669
+ remediation: "Choose a BYOK model after connecting its provider key. Managed AI is not available yet."
670
+ };
671
+ const resolvedProvider = provider ?? inferGenerationBlockerProvider(serverMessage);
672
+ const providerLabel = resolvedProvider ? PROVIDER_LABELS[resolvedProvider] : "provider";
673
+ const actionPath = resolvedProvider ? `/profile#provider-key-${resolvedProvider}` : "/profile#provider-keys";
674
+ const setupUrl = `https://makefx.app${actionPath}`;
675
+ if (code === "PROVIDER_KEY_REQUIRED") return {
676
+ code,
677
+ message: `A ${providerLabel} account key is required before generation can start.`,
678
+ retryable: false,
679
+ actionLabel: "Configure key",
680
+ actionPath,
681
+ remediation: `Configure the ${providerLabel} account key at ${setupUrl}, then try again.`,
682
+ ...resolvedProvider ? { provider: resolvedProvider } : {}
683
+ };
684
+ return {
685
+ code,
686
+ message: `The ${providerLabel} account key could not be used. Replace or reconnect it before trying again.`,
687
+ retryable: false,
688
+ actionLabel: "Reconnect key",
689
+ actionPath,
690
+ remediation: `Replace or reconnect the ${providerLabel} account key at ${setupUrl}, then try again.`,
691
+ ...resolvedProvider ? { provider: resolvedProvider } : {}
692
+ };
693
+ }
694
+ function formatGenerationBlockerForTerminal(code, message) {
695
+ const blocker = getGenerationBlockerPresentation(code, message);
696
+ if (!blocker) return `${code}: ${message}`;
697
+ return `${blocker.code}: ${blocker.message}\n${blocker.remediation}`;
698
+ }
699
+ //#endregion
624
700
  //#region src/cli/lib/error-format.ts
625
701
  function formatServerErrorMessage(code, message) {
626
- return `${code}: ${formatGenerationFailureMessage(message)}`;
702
+ return formatGenerationBlockerForTerminal(code, formatGenerationFailureMessage(message));
627
703
  }
628
704
  function formatGenerationFailureMessage(message, providerMetadata) {
629
705
  return sanitizeGenerationFailureMessage(message, providerMetadata);
@@ -631,6 +707,9 @@ function formatGenerationFailureMessage(message, providerMetadata) {
631
707
  function getGenerationFailureCode(providerMetadata) {
632
708
  return extractGenerationDiagnostic(providerMetadata)?.code ?? "GENERATION_FAILED";
633
709
  }
710
+ function getGenerationFailureProvider(providerMetadata) {
711
+ return extractGenerationDiagnostic(providerMetadata)?.provider;
712
+ }
634
713
  //#endregion
635
714
  //#region src/shared/compact-references.ts
636
715
  var COMPACT_ID_LENGTH = 8;
@@ -660,6 +739,9 @@ function createVariantRef(name, assetId, variantId) {
660
739
  function createDraftRef(name, draftId) {
661
740
  return `draft:${slugify(name, "draft")}~${compactIdPrefix(draftId)}`;
662
741
  }
742
+ function createGraphRunRef(runId) {
743
+ return `graph-run:${compactIdPrefix(runId)}`;
744
+ }
663
745
  function parseAssetRef(value) {
664
746
  const match = /^asset:[^~]+~([a-z0-9]{4,8})$/u.exec(value);
665
747
  return match ? { idPrefix: match[1] } : null;
@@ -4399,6 +4481,7 @@ var WebSocketClient = class WebSocketClient {
4399
4481
  chatHandlers = /* @__PURE__ */ new Map();
4400
4482
  generateHandlers = /* @__PURE__ */ new Map();
4401
4483
  earlyTerminalVariants = /* @__PURE__ */ new Map();
4484
+ draftRunStartsAwaitingVariant = /* @__PURE__ */ new Set();
4402
4485
  variantCompletionHandlers = /* @__PURE__ */ new Map();
4403
4486
  describeHandlers = /* @__PURE__ */ new Map();
4404
4487
  compareHandlers = /* @__PURE__ */ new Map();
@@ -4718,13 +4801,14 @@ var WebSocketClient = class WebSocketClient {
4718
4801
  success: variant.status === "completed",
4719
4802
  variant: variant.status === "completed" ? variant : void 0,
4720
4803
  error: variant.status === "failed" ? formatGenerationFailureMessage(variant.error_message, variant.provider_metadata) : void 0,
4721
- errorCode: variant.status === "failed" ? getGenerationFailureCode(variant.provider_metadata) : void 0
4804
+ errorCode: variant.status === "failed" ? getGenerationFailureCode(variant.provider_metadata) : void 0,
4805
+ errorProvider: variant.status === "failed" ? getGenerationFailureProvider(variant.provider_metadata) : void 0
4722
4806
  });
4723
4807
  }
4724
4808
  return handled;
4725
4809
  }
4726
4810
  hasRequestsAwaitingStarted() {
4727
- return this.generateHandlers.size > 0;
4811
+ return this.generateHandlers.size > 0 || this.draftRunStartsAwaitingVariant.size > 0;
4728
4812
  }
4729
4813
  clearOrphanedEarlyTerminalVariants() {
4730
4814
  if (!this.hasRequestsAwaitingStarted()) this.earlyTerminalVariants.clear();
@@ -4869,6 +4953,58 @@ var WebSocketClient = class WebSocketClient {
4869
4953
  bindingId
4870
4954
  }, (msg) => msg.type === "binding:cleared" && msg.draftId === draftId && msg.bindingId === bindingId);
4871
4955
  }
4956
+ async runDraft(draftId, requestId, onStarted) {
4957
+ if (onStarted) this.draftRunStartsAwaitingVariant.add(requestId);
4958
+ try {
4959
+ const result = await this.awaitServerMessage({
4960
+ type: "drafts.run",
4961
+ draftId,
4962
+ requestId,
4963
+ source: "cli"
4964
+ }, (msg) => (msg.type === "drafts.run_started" || msg.type === "drafts.run_error") && msg.requestId === requestId);
4965
+ if (result.type === "drafts.run_error") throw new Error(`${result.code}: ${result.error}`);
4966
+ onStarted?.(result);
4967
+ return result;
4968
+ } finally {
4969
+ this.draftRunStartsAwaitingVariant.delete(requestId);
4970
+ this.clearOrphanedEarlyTerminalVariants();
4971
+ }
4972
+ }
4973
+ async runGraph(input) {
4974
+ const result = await this.awaitServerMessage({
4975
+ type: "graph.run",
4976
+ ...input,
4977
+ source: "cli"
4978
+ }, (msg) => msg.type === "graph.run_started" && msg.run.request_id === input.requestId || msg.type === "graph.run_error" && msg.requestId === input.requestId);
4979
+ if (result.type === "graph.run_error") {
4980
+ const detail = result.failures.map((failure) => `${failure.draftId}: ${failure.error}`).join("; ");
4981
+ throw new Error(`${result.code}: ${detail || result.error}`);
4982
+ }
4983
+ return {
4984
+ type: "graph.run_status",
4985
+ run: result.run,
4986
+ drafts: result.drafts
4987
+ };
4988
+ }
4989
+ async getGraphRunStatus(runRef) {
4990
+ return this.awaitServerMessage({
4991
+ type: "graph.status",
4992
+ runRef
4993
+ }, (msg) => msg.type === "graph.run_status");
4994
+ }
4995
+ async cancelGraphRun(runId) {
4996
+ return this.awaitServerMessage({
4997
+ type: "graph.cancel",
4998
+ runId
4999
+ }, (msg) => msg.type === "graph.run_status" && msg.run.id === runId);
5000
+ }
5001
+ async waitForGraphRun(runRef, runId, timeoutMs = GENERATION_REQUEST_TIMEOUT_MS) {
5002
+ await this.awaitServerMessage({
5003
+ type: "graph.status",
5004
+ runRef
5005
+ }, (msg) => (msg.type === "graph.run_status" && msg.run.id === runId || msg.type === "graph.run_progress" && msg.run.id === runId) && msg.run.status !== "running", timeoutMs);
5006
+ return this.getGraphRunStatus(runRef);
5007
+ }
4872
5008
  async setCanvasLens(lens) {
4873
5009
  return (await this.awaitServerMessage({
4874
5010
  type: "canvas.set_lens",
@@ -4961,7 +5097,9 @@ var WebSocketClient = class WebSocketClient {
4961
5097
  videoDurationSeconds: params.videoDurationSeconds,
4962
5098
  videoTier: params.videoTier,
4963
5099
  seedanceDuration: params.seedanceDuration,
4964
- seedanceBitrateMode: params.seedanceBitrateMode
5100
+ seedanceBitrateMode: params.seedanceBitrateMode,
5101
+ avatarResolution: params.avatarResolution,
5102
+ avatarMode: params.avatarMode
4965
5103
  };
4966
5104
  try {
4967
5105
  this.send(message);
@@ -4992,6 +5130,11 @@ var WebSocketClient = class WebSocketClient {
4992
5130
  reject,
4993
5131
  timeout
4994
5132
  });
5133
+ const earlyTerminal = this.earlyTerminalVariants.get(params.variantId);
5134
+ if (earlyTerminal) {
5135
+ this.earlyTerminalVariants.delete(params.variantId);
5136
+ this.handleTerminalVariant(earlyTerminal);
5137
+ }
4995
5138
  });
4996
5139
  }
4997
5140
  cancelFollowVariant(variantId, requestId) {
@@ -5809,6 +5952,16 @@ function readIsoBmffVideoMetadata(bytes) {
5809
5952
  return durationMs ? { durationMs } : {};
5810
5953
  }
5811
5954
  //#endregion
5955
+ //#region src/shared/avatarGenerationOptions.ts
5956
+ var AVATAR_MODEL_IDS = ["kling-avatar-v2"];
5957
+ var AVATAR_MODES = ["standard", "pro"];
5958
+ function isAvatarModelId(value) {
5959
+ return typeof value === "string" && AVATAR_MODEL_IDS.includes(value);
5960
+ }
5961
+ function isAvatarMode(value) {
5962
+ return typeof value === "string" && AVATAR_MODES.includes(value);
5963
+ }
5964
+ //#endregion
5812
5965
  //#region src/shared/seedance2Capabilities.ts
5813
5966
  var SEEDANCE_2_ASPECT_RATIOS = [
5814
5967
  "auto",
@@ -6017,6 +6170,11 @@ var VIDEO_GENERATION_RESOLUTIONS = [
6017
6170
  ];
6018
6171
  var ALL_VIDEO_GENERATION_RESOLUTIONS = ["480p", ...VIDEO_GENERATION_RESOLUTIONS];
6019
6172
  var KLING_VIDEO_GENERATION_RESOLUTIONS = ["720p", "1080p"];
6173
+ var VIDEO_GENERATION_DURATION_SECONDS = [
6174
+ 4,
6175
+ 6,
6176
+ 8
6177
+ ];
6020
6178
  var SEEDANCE_2_DURATION_SECONDS = [
6021
6179
  4,
6022
6180
  5,
@@ -6042,6 +6200,12 @@ var VIDEO_MODEL_SELECTIONS = [
6042
6200
  "kling",
6043
6201
  "fal-seedance"
6044
6202
  ];
6203
+ var VIDEO_MODEL_LABELS = {
6204
+ "veo-3.1": "Veo 3.1",
6205
+ "omni-flash": "Omni Flash",
6206
+ kling: "Kling 3.0",
6207
+ "fal-seedance": "fal.ai Seedance v1 (text-to-video)"
6208
+ };
6045
6209
  var VIDEO_MODEL_SUPPORTED_OPERATIONS = {
6046
6210
  "veo-3.1": [
6047
6211
  "generate",
@@ -6069,6 +6233,7 @@ var VIDEO_GENERATION_RESOLUTIONS_BY_TIER = {
6069
6233
  fast: VIDEO_GENERATION_RESOLUTIONS,
6070
6234
  lite: ["720p", "1080p"]
6071
6235
  };
6236
+ var DEFAULT_VIDEO_GENERATION_RESOLUTION = "720p";
6072
6237
  var DEFAULT_VIDEO_GENERATION_TIER = "generate";
6073
6238
  var DEFAULT_VIDEO_MODEL_SELECTION = "veo-3.1";
6074
6239
  function normalizeVideoGenerationAspectRatio(value) {
@@ -6144,6 +6309,12 @@ function isFalSeedanceV1VideoGenerationModel(model) {
6144
6309
  function isFalSeedance2VideoGenerationModel(model) {
6145
6310
  return typeof model === "string" && getSeedance2CapabilityByEndpoint(model) !== void 0;
6146
6311
  }
6312
+ function getVideoGenerationMaxReferenceImages(model) {
6313
+ if (isFalSeedanceV1VideoGenerationModel(model)) return 0;
6314
+ if (isFalSeedance2VideoGenerationModel(model)) return getSeedance2CapabilityByEndpoint(model)?.references.find((reference) => reference.mediaKind === "image")?.maxCount ?? 0;
6315
+ if (isKlingVideoGenerationModel(model)) return 1;
6316
+ return 3;
6317
+ }
6147
6318
  function isVideoOperationSupportedByModel(model, operation) {
6148
6319
  const seedanceCapability = getSeedance2CapabilityByEndpoint(model);
6149
6320
  if (seedanceCapability) return seedanceCapability.operations.includes(operation);
@@ -6234,6 +6405,9 @@ var IMAGE_MODEL_CAPABILITIES = {
6234
6405
  function isImageModelSelection(value) {
6235
6406
  return IMAGE_MODEL_SELECTIONS.includes(value);
6236
6407
  }
6408
+ function isImageModelId(value) {
6409
+ return Object.values(IMAGE_MODEL_IDS).includes(value);
6410
+ }
6237
6411
  function isImageSize(value) {
6238
6412
  return IMAGE_SIZES.includes(value);
6239
6413
  }
@@ -6299,6 +6473,20 @@ var MEDIA_OPERATION_MATRIX = [
6299
6473
  cliCommands: ["generate"],
6300
6474
  cliSupportsRefs: true
6301
6475
  },
6476
+ {
6477
+ mode: "avatar",
6478
+ label: "Avatar",
6479
+ shortLabel: "Avatar",
6480
+ mediaKind: "video",
6481
+ assetType: "avatar",
6482
+ promptNoun: "avatar video",
6483
+ inheritsReferenceAssetType: false,
6484
+ compatibleSlotMediaKinds: ["image", "audio"],
6485
+ supportsBatch: false,
6486
+ cliNamespace: null,
6487
+ cliCommands: [],
6488
+ cliSupportsRefs: false
6489
+ },
6302
6490
  {
6303
6491
  mode: "speech",
6304
6492
  label: "Speech",
@@ -6382,9 +6570,21 @@ var AUDIO_FORGE_MEDIA_MODES = MEDIA_OPERATION_MATRIX.filter((entry) => entry.med
6382
6570
  function getMediaOperationEntry(mode) {
6383
6571
  return ENTRY_BY_MODE.get(mode) ?? MEDIA_OPERATION_MATRIX[0];
6384
6572
  }
6573
+ function getMediaKindForForgeMode(mode) {
6574
+ return getMediaOperationEntry(mode).mediaKind;
6575
+ }
6385
6576
  function isAudioForgeMediaMode(value) {
6386
6577
  return AUDIO_FORGE_MEDIA_MODES.includes(value);
6387
6578
  }
6579
+ function getForgeModeForAudioAssetType(assetType) {
6580
+ switch (assetType) {
6581
+ case "speech":
6582
+ case "dialogue":
6583
+ case "music":
6584
+ case "sfx": return assetType;
6585
+ default: return "speech";
6586
+ }
6587
+ }
6388
6588
  function getCliGenerationProfile(namespace) {
6389
6589
  const profile = PROFILE_BY_NAMESPACE.get(namespace);
6390
6590
  if (!profile) throw new Error(`Unknown CLI generation namespace: ${namespace}`);
@@ -6489,49 +6689,50 @@ async function executeForgeCommand(command, parsed, deps = defaultDeps$12, optio
6489
6689
  client.disconnect();
6490
6690
  }
6491
6691
  }
6692
+ var CLI_GENERATION_COMMON_OPTIONS = [
6693
+ "env",
6694
+ "local",
6695
+ "space",
6696
+ "force",
6697
+ "name",
6698
+ "type",
6699
+ "o",
6700
+ "output",
6701
+ "collection"
6702
+ ];
6703
+ var CLI_GENERATION_MEDIA_OPTIONS = {
6704
+ image: [
6705
+ "model",
6706
+ "refs",
6707
+ "aspect",
6708
+ "size"
6709
+ ],
6710
+ audio: [
6711
+ "model",
6712
+ "input",
6713
+ "voice",
6714
+ "dialogue-voices"
6715
+ ],
6716
+ video: [
6717
+ "model",
6718
+ "refs",
6719
+ "aspect",
6720
+ "audio",
6721
+ "no-audio",
6722
+ "resolution",
6723
+ "duration",
6724
+ "tier",
6725
+ "bitrate",
6726
+ "first-frame",
6727
+ "last-frame",
6728
+ "image-refs",
6729
+ "video-refs",
6730
+ "audio-refs",
6731
+ "mode"
6732
+ ]
6733
+ };
6492
6734
  function rejectUnknownGenerationOptions(options, mediaKind) {
6493
- const common = [
6494
- "env",
6495
- "local",
6496
- "space",
6497
- "force",
6498
- "name",
6499
- "type",
6500
- "o",
6501
- "output",
6502
- "collection"
6503
- ];
6504
- const byKind = {
6505
- image: [
6506
- "model",
6507
- "refs",
6508
- "aspect",
6509
- "size"
6510
- ],
6511
- audio: [
6512
- "model",
6513
- "input",
6514
- "voice",
6515
- "dialogue-voices"
6516
- ],
6517
- video: [
6518
- "model",
6519
- "refs",
6520
- "aspect",
6521
- "audio",
6522
- "no-audio",
6523
- "resolution",
6524
- "duration",
6525
- "tier",
6526
- "bitrate",
6527
- "first-frame",
6528
- "last-frame",
6529
- "image-refs",
6530
- "video-refs",
6531
- "audio-refs"
6532
- ]
6533
- };
6534
- const allowed = new Set([...common, ...byKind[mediaKind]]);
6735
+ const allowed = new Set([...CLI_GENERATION_COMMON_OPTIONS, ...CLI_GENERATION_MEDIA_OPTIONS[mediaKind]]);
6535
6736
  const unknown = Object.keys(options).find((name) => !allowed.has(name));
6536
6737
  if (unknown) throw new Error(`Unknown option: --${unknown}`);
6537
6738
  }
@@ -6575,36 +6776,40 @@ function validateAudioModeRequiredVoiceOptions(options, mode) {
6575
6776
  if (mode === "dialogue" && !normalizeCliOption(options["dialogue-voices"] ?? options.dialogueVoices)) throw new Error("Dialogue generation requires --dialogue-voices <voice_id,voice_id>. Run: makefx audio voices");
6576
6777
  }
6577
6778
  async function executeGenerate(parsed, ctx, client, deps, mediaKind, followOptions = {}) {
6578
- const prompt = getPrompt(parsed, "generate");
6779
+ const videoOptions = parseVideoGenerationOptions(parsed, mediaKind);
6780
+ const prompt = isAvatarModelId(videoOptions.model) ? parsed.positionals.join(" ").trim() : getPrompt(parsed, "generate");
6579
6781
  const outputPath = getOutputPath(parsed);
6580
6782
  const name = requireOption(parsed, "name");
6581
6783
  const assetType = requireOption(parsed, "type");
6582
6784
  const musicProvider = parseMusicProviderOption(parsed, mediaKind, assetType);
6583
- const videoOptions = parseVideoGenerationOptions(parsed, mediaKind);
6584
6785
  const effectiveVideoModel = videoOptions.model ?? getVideoGenerationModelForSelection();
6585
6786
  const seedanceCapability = mediaKind === "video" ? getSeedance2CapabilityByEndpoint(effectiveVideoModel) : void 0;
6586
6787
  const videoFrameRefs = parseVideoFrameReferenceOptions(parsed, "generate", mediaKind).refs;
6587
6788
  const seedanceRefs = parseSeedanceReferenceOptions(parsed);
6789
+ const plainRefs = parseOptionalRefs(parsed, "refs");
6588
6790
  validateSeedanceReferenceOptions(seedanceRefs, effectiveVideoModel, "generate");
6589
6791
  if (seedanceCapability?.mode === "frame" && videoFrameRefs.length === 0) throw new Error(`--model ${seedanceCapability.selection} requires --first-frame`);
6590
6792
  if (seedanceCapability?.mode === "text" && videoFrameRefs.length > 0) throw new Error(`--model ${seedanceCapability.selection} does not accept references`);
6591
6793
  if (seedanceCapability?.mode === "reference" && videoFrameRefs.length > 0) throw new Error(`--model ${seedanceCapability.selection} uses --image-refs, --video-refs, and --audio-refs`);
6794
+ if (seedanceCapability && plainRefs.length > 0) throw new Error(`--model ${seedanceCapability.selection} uses --image-refs, --video-refs, and --audio-refs instead of --refs`);
6795
+ if (mediaKind === "video" && isKlingVideoGenerationModel(effectiveVideoModel) && videoFrameRefs.length + plainRefs.length > 1) throw new Error("--model kling supports at most one image reference");
6796
+ if (mediaKind === "video" && isFalVideoGenerationModel(effectiveVideoModel) && !isFalSeedance2VideoGenerationModel(effectiveVideoModel) && (videoFrameRefs.length > 0 || plainRefs.length > 0)) throw new Error(`--model ${getVideoModelSelectionForModel(effectiveVideoModel) ?? effectiveVideoModel} does not support image references`);
6592
6797
  const seedanceReferenceCount = seedanceRefs.imageRefs.length + seedanceRefs.videoRefs.length + seedanceRefs.audioRefs.length;
6593
- const state = videoFrameRefs.length > 0 || seedanceReferenceCount > 0 ? await requestSpaceState(client) : void 0;
6798
+ const state = videoFrameRefs.length > 0 || seedanceReferenceCount > 0 || plainRefs.length > 0 ? await requestSpaceState(client) : void 0;
6594
6799
  const referenceDeps = {
6595
6800
  ...deps,
6596
6801
  waitForReferenceVariant: (variant) => waitForReferenceVariant(client, variant)
6597
6802
  };
6598
- const resolvedSeedanceRefs = state && seedanceReferenceCount > 0 ? await resolveSeedanceReferenceIds(seedanceRefs, ctx, referenceDeps, state) : {
6803
+ const resolvedSeedanceRefs = state && seedanceReferenceCount > 0 ? isAvatarModelId(effectiveVideoModel) ? await resolveAvatarReferenceIds(seedanceRefs, ctx, referenceDeps, state) : await resolveSeedanceReferenceIds(seedanceRefs, ctx, referenceDeps, state) : {
6599
6804
  refs: [],
6600
6805
  ids: []
6601
6806
  };
6602
- const referenceVariantIds = resolvedSeedanceRefs.ids.length > 0 ? resolvedSeedanceRefs.ids : videoFrameRefs.length > 0 ? await resolveReferenceVariantIds(videoFrameRefs, ctx, referenceDeps, state?.variants, CLI_GENERATION_MEDIA_KIND, state?.assets) : [];
6807
+ const directRefs = videoFrameRefs.length > 0 ? videoFrameRefs : plainRefs;
6808
+ const directRefMediaKind = videoFrameRefs.length > 0 ? CLI_GENERATION_MEDIA_KIND : mediaKind;
6809
+ const referenceVariantIds = resolvedSeedanceRefs.ids.length > 0 ? resolvedSeedanceRefs.ids : directRefs.length > 0 ? await resolveReferenceVariantIds(directRefs, ctx, referenceDeps, state?.variants, directRefMediaKind, state?.assets) : [];
6603
6810
  const imageOptions = parseImageGenerationOptions(parsed, mediaKind);
6604
6811
  const audioModelOptions = parseAudioGenerationOptions(parsed, mediaKind);
6605
6812
  const videoAudioOptions = parseVideoAudioOptions(parsed, mediaKind);
6606
- if (mediaKind === "video" && isKlingVideoGenerationModel(effectiveVideoModel) && videoFrameRefs.length > 1) throw new Error("--model kling supports at most one image reference");
6607
- if (mediaKind === "video" && isFalVideoGenerationModel(effectiveVideoModel) && !isFalSeedance2VideoGenerationModel(effectiveVideoModel) && videoFrameRefs.length > 0) throw new Error(`--model ${getVideoModelSelectionForModel(effectiveVideoModel) ?? effectiveVideoModel} does not support image references`);
6608
6813
  const collectionPlacement = parseCollectionPlacementOptions(parsed);
6609
6814
  const followCommandOptions = {
6610
6815
  ...followOptions,
@@ -6749,6 +6954,10 @@ function parseSeedanceReferenceOptions(parsed) {
6749
6954
  };
6750
6955
  }
6751
6956
  function validateSeedanceReferenceOptions(options, model, command) {
6957
+ if (isAvatarModelId(model)) {
6958
+ if (options.imageRefs.length !== 1 || options.audioRefs.length !== 1 || options.videoRefs.length !== 0) throw new Error("Avatar generation requires exactly one --image-refs portrait and one --audio-refs input");
6959
+ return;
6960
+ }
6752
6961
  const capability = getSeedance2CapabilityByEndpoint(model);
6753
6962
  const total = options.imageRefs.length + options.videoRefs.length + options.audioRefs.length;
6754
6963
  if (!capability) {
@@ -6766,6 +6975,15 @@ function validateSeedanceReferenceOptions(options, model, command) {
6766
6975
  });
6767
6976
  if (error) throw new Error(error.message);
6768
6977
  }
6978
+ async function resolveAvatarReferenceIds(options, ctx, deps, state) {
6979
+ const resolve = (refs, kind) => resolveReferenceVariantIds(refs, ctx, deps, state.variants, "video", state.assets, kind);
6980
+ const imageIds = await resolve(options.imageRefs, "image");
6981
+ const audioIds = await resolve(options.audioRefs, "audio");
6982
+ return {
6983
+ refs: [...options.imageRefs, ...options.audioRefs],
6984
+ ids: [...imageIds, ...audioIds]
6985
+ };
6986
+ }
6769
6987
  async function resolveSeedanceReferenceIds(options, ctx, deps, state, implicitVideoReferenceIds = []) {
6770
6988
  await preflightLocalSeedanceReferences({
6771
6989
  ...options,
@@ -7043,7 +7261,9 @@ function compactStartedVariantRef(started) {
7043
7261
  }
7044
7262
  function formatTerminalGenerationError(result) {
7045
7263
  const message = result.error || "Generation failed without a completed variant";
7046
- return result.errorCode ? `${result.errorCode}: ${message}` : message;
7264
+ if (!result.errorCode) return message;
7265
+ const blocker = getGenerationBlockerPresentation(result.errorCode, message, generationBlockerProviderFromService(result.errorProvider));
7266
+ return blocker ? `${blocker.code}: ${blocker.message}\n${blocker.remediation}` : formatGenerationBlockerForTerminal(result.errorCode, message);
7047
7267
  }
7048
7268
  function throwGenerationWaitErrorIfNeeded(input, params) {
7049
7269
  if (!isGenerationWaitTimeout(input)) throw input;
@@ -7098,7 +7318,7 @@ function validateVideoAudioOptions(parsed, mediaKind) {
7098
7318
  const model = parseVideoModelOption(modelValue, videoTier, parsed);
7099
7319
  if (parsed.options.audio !== void 0 && (isGeminiOmniVideoGenerationModel(model) || isFalSeedanceV1VideoGenerationModel(model))) throw new Error("--audio is only supported with --model veo-3.1, kling, or Seedance 2");
7100
7320
  if (parsed.options["no-audio"] !== void 0) {
7101
- if (!doesVideoGenerationModelSupportAudioToggle(model) && !isFalSeedance2VideoGenerationModel(model)) throw new Error(`${model} does not support --no-audio. Use the default audio-enabled output or omit the flag.`);
7321
+ if (isAvatarModelId(model) || !doesVideoGenerationModelSupportAudioToggle(model) && !isFalSeedance2VideoGenerationModel(model)) throw new Error(`${model} does not support --no-audio. Use the default audio-enabled output or omit the flag.`);
7102
7322
  }
7103
7323
  }
7104
7324
  function parseVideoAudioOptions(parsed, mediaKind) {
@@ -7113,6 +7333,7 @@ function parseVideoGenerationOptions(parsed, mediaKind) {
7113
7333
  const durationValue = readOptionalOption(parsed, "duration");
7114
7334
  const tierValue = readOptionalOption(parsed, "tier");
7115
7335
  const bitrateValue = readOptionalOption(parsed, "bitrate");
7336
+ const modeValue = readOptionalOption(parsed, "mode");
7116
7337
  const modelValue = mediaKind === "video" ? readOptionalOption(parsed, "model") : void 0;
7117
7338
  const aspectValue = mediaKind === "video" ? readOptionalOption(parsed, "aspect") : void 0;
7118
7339
  if (mediaKind !== "video" && resolutionValue === void 0 && durationValue === void 0 && tierValue === void 0 && aspectValue === void 0 && bitrateValue === void 0) return {};
@@ -7122,6 +7343,17 @@ function parseVideoGenerationOptions(parsed, mediaKind) {
7122
7343
  const videoTier = tierValue === void 0 ? void 0 : normalizeVideoGenerationTier(tierValue);
7123
7344
  if (tierValue !== void 0 && !videoTier) throw new Error("--tier must be generate, fast, or lite");
7124
7345
  const model = parseVideoModelOption(modelValue, videoTier, parsed);
7346
+ if (isAvatarModelId(model)) {
7347
+ if (parsed.options.refs !== void 0 || parsed.options["first-frame"] !== void 0 || parsed.options["last-frame"] !== void 0) throw new Error("Avatar models accept only --image-refs and --audio-refs");
7348
+ if (aspectValue !== void 0 || durationValue !== void 0 || tierValue !== void 0 || bitrateValue !== void 0 || parsed.options.audio !== void 0 || parsed.options["no-audio"] !== void 0) throw new Error("Avatar models do not accept --aspect, --duration, --tier, --bitrate, --audio, or --no-audio");
7349
+ if (resolutionValue !== void 0) throw new Error("--resolution is not supported by Avatar models");
7350
+ if (modeValue !== void 0 && !isAvatarMode(modeValue)) throw new Error(`--mode must be ${AVATAR_MODES.join(" or ")}`);
7351
+ return {
7352
+ model,
7353
+ ...modeValue ? { avatarMode: modeValue } : {}
7354
+ };
7355
+ }
7356
+ if (modeValue !== void 0) throw new Error("--mode is only supported by --model kling-avatar-v2");
7125
7357
  const seedanceCapability = getSeedance2CapabilityByEndpoint(model);
7126
7358
  const seedanceDuration = durationValue === void 0 ? void 0 : durationValue === "auto" ? "auto" : normalizeVideoGenerationDurationSeconds(durationValue);
7127
7359
  const videoDurationSeconds = durationValue === void 0 || durationValue === "auto" ? void 0 : normalizeVideoGenerationDurationSeconds(durationValue);
@@ -7217,6 +7449,7 @@ function parseVideoModelOption(value, tier, parsed) {
7217
7449
  const normalized = normalizeCliOption(value);
7218
7450
  const effectiveTier = tier ?? "generate";
7219
7451
  if (!normalized) return getVideoGenerationModelForSelection(DEFAULT_VIDEO_MODEL_SELECTION, effectiveTier);
7452
+ if (isAvatarModelId(normalized)) return normalized;
7220
7453
  if (normalized === "seedance-2" || normalized === "seedance-2-fast") {
7221
7454
  const hasFrames = Boolean(parsed?.options["first-frame"] || parsed?.options["last-frame"]);
7222
7455
  const hasReferences = Boolean(parsed?.options.refs || parsed?.options["image-refs"] || parsed?.options["video-refs"] || parsed?.options["audio-refs"]);
@@ -7224,7 +7457,7 @@ function parseVideoModelOption(value, tier, parsed) {
7224
7457
  }
7225
7458
  const selection = normalizeVideoModelSelection(normalized === "seedance-1" ? "fal-seedance" : normalized);
7226
7459
  if (selection) return getVideoGenerationModelForSelection(selection, effectiveTier);
7227
- throw new Error("--model must be veo-3.1, omni-flash, kling, seedance-1, seedance-2, or seedance-2-fast");
7460
+ throw new Error("--model must be veo-3.1, omni-flash, kling, seedance-1, seedance-2, seedance-2-fast, or kling-avatar-v2");
7228
7461
  }
7229
7462
  function parseImageModelOption(value) {
7230
7463
  if (!value) return void 0;
@@ -7262,6 +7495,10 @@ function validateImageModelOperation(command, mediaKind, model) {
7262
7495
  function validateVideoModelOperation(command, mediaKind, model) {
7263
7496
  if (mediaKind !== "video") return;
7264
7497
  const effectiveModel = model ?? getVideoGenerationModelForSelection();
7498
+ if (isAvatarModelId(effectiveModel)) {
7499
+ if (command === "generate") return;
7500
+ throw new Error(`--model ${effectiveModel} does not support video ${command}`);
7501
+ }
7265
7502
  if (isVideoOperationSupportedByModel(effectiveModel, command)) return;
7266
7503
  throw new Error(`--model ${getVideoModelSelectionForModel(effectiveModel) ?? effectiveModel} does not support video ${command}`);
7267
7504
  }
@@ -7290,7 +7527,7 @@ function cliImageModelValues(command) {
7290
7527
  function printUsage$6(command) {
7291
7528
  console.log(`
7292
7529
  Usage:
7293
- makefx image generate "prompt" --name <name> --type <type> -o <file> [--model ${cliImageModelValues(command)}] [--size ${cliImageSizeValues()}] [--aspect ${cliImageAspectValues()}] [--collection <id>] [--space <id>]
7530
+ makefx image generate "prompt" --name <name> --type <type> -o <file> [--model ${cliImageModelValues(command)}] [--refs <variant-ref-or-file,...>] [--size ${cliImageSizeValues()}] [--aspect ${cliImageAspectValues()}] [--collection <id>] [--space <id>]
7294
7531
  `);
7295
7532
  }
7296
7533
  //#endregion
@@ -7431,7 +7668,7 @@ function resolveVideoRegenerationModel(model, parsed, sourceRecipe) {
7431
7668
  "video-refs",
7432
7669
  "audio-refs"
7433
7670
  ].some((name) => parsed.options[name] !== void 0);
7434
- const storedRecipe = parseStoredRecipe$1(sourceRecipe);
7671
+ const storedRecipe = parseStoredRecipe$2(sourceRecipe);
7435
7672
  return getSeedance2RegenerationCapability(model, {
7436
7673
  model: storedRecipe?.model,
7437
7674
  references: storedRecipe?.references ?? [],
@@ -7442,7 +7679,7 @@ function resolveVideoRegenerationModel(model, parsed, sourceRecipe) {
7442
7679
  if (!selection) throw new Error("Unsupported video model. Expected veo-3.1, omni-flash, kling, seedance-1, seedance-2, or seedance-2-fast");
7443
7680
  return getVideoGenerationModelForSelection(selection, tier);
7444
7681
  }
7445
- function parseStoredRecipe$1(recipe) {
7682
+ function parseStoredRecipe$2(recipe) {
7446
7683
  if (!recipe) return void 0;
7447
7684
  try {
7448
7685
  const parsed = JSON.parse(recipe);
@@ -7701,14 +7938,14 @@ async function handleBilling(parsed) {
7701
7938
  }
7702
7939
  function printBillingHelp() {
7703
7940
  console.log(`
7704
- Billing Commands - Polar.sh Usage Sync
7941
+ Billing Commands - Platform Subscription Operations
7705
7942
 
7706
7943
  Usage:
7707
7944
  makefx billing <subcommand> [--env <environment>]
7708
7945
 
7709
7946
  Subcommands:
7710
7947
  status Show sync status (pending, failed, synced events)
7711
- check Run operational checks for workers, Polar meters, and sync health
7948
+ check Run operational checks for workers, Polar product, and sync health
7712
7949
  reconcile Compare local billable usage with Polar usage for one user
7713
7950
  retry-failed Reset failed events for retry (next cron will sync them)
7714
7951
 
@@ -7855,6 +8092,16 @@ function secondsSummary(seconds) {
7855
8092
  const remainingSeconds = seconds % 60;
7856
8093
  return remainingSeconds > 0 ? `${minutes}m ${remainingSeconds}s` : `${minutes}m`;
7857
8094
  }
8095
+ function resolveBillingOpsResult(options) {
8096
+ const statuses = [
8097
+ ...options.workerStatuses,
8098
+ options.billingStatus,
8099
+ options.webhookStatus
8100
+ ];
8101
+ if (statuses.includes("critical")) return "critical";
8102
+ if (statuses.includes("warning")) return "warning";
8103
+ return "ok";
8104
+ }
7858
8105
  async function handleBillingOpsCheck(env) {
7859
8106
  console.log(`Running billing operational checks for ${env}...\n`);
7860
8107
  const workerChecks = await checkWorkerHealth(env);
@@ -7871,22 +8118,22 @@ async function handleBillingOpsCheck(env) {
7871
8118
  process$1.exitCode = 1;
7872
8119
  return;
7873
8120
  }
7874
- const meterCheck = billingChecks.checks.polarMeters;
7875
- console.log("\nPolar meters:");
7876
- console.log(` ${statusLabel(meterCheck.status)} configured=${meterCheck.configured} active=${meterCheck.active.length} expected=${meterCheck.expected.length}`);
7877
- if (meterCheck.error) console.log(` Error: ${meterCheck.error}`);
7878
- if (meterCheck.missing.length > 0) console.log(` Missing: ${meterCheck.missing.join(", ")}`);
7879
- if (meterCheck.invalid.length > 0) console.log(` Invalid: ${meterCheck.invalid.map((meter) => meter.name).join(", ")}`);
7880
- const productCheck = billingChecks.checks.paidGenerationProduct;
7881
- if (productCheck) {
7882
- console.log("\nPaid generation product:");
7883
- const product = productCheck.product;
7884
- console.log(` ${statusLabel(productCheck.status)} configured=${product?.configured ?? false} recurring=${product?.isRecurring ?? "n/a"} archived=${product?.isArchived ?? "n/a"}`);
7885
- if (product?.productId) console.log(` Product: ${product.productId}${product.name ? ` (${product.name})` : ""}`);
7886
- console.log(` Metered prices: ${product?.meteredPriceMeters.length ?? 0}/${productCheck.expectedMeteredPriceMeters.length}`);
7887
- if (productCheck.missingMeteredPriceMeters.length > 0) console.log(` Missing metered prices: ${productCheck.missingMeteredPriceMeters.join(", ")}`);
7888
- if (productCheck.unexpectedMeterCreditBenefitMeters.length > 0) console.log(` Unexpected meter-credit benefits: ${productCheck.unexpectedMeterCreditBenefitMeters.join(", ")}`);
7889
- }
8121
+ const productCheck = billingChecks.checks.platformSubscriptionProduct;
8122
+ console.log("\nPlatform subscription product:");
8123
+ const product = productCheck.product;
8124
+ console.log(` ${statusLabel(productCheck.status)} configured=${product?.configured ?? false} recurring=${product?.isRecurring ?? "n/a"} archived=${product?.isArchived ?? "n/a"}`);
8125
+ console.log(` Environment: ${productCheck.actualEnvironment} (expected ${productCheck.expectedEnvironment})`);
8126
+ if (product?.productId) console.log(` Product: ${product.productId}${product.name ? ` (${product.name})` : ""}`);
8127
+ if (product?.fixedPrices.length) console.log(` Fixed price: ${product.fixedPrices.map((price) => `${price.amount} ${price.currency}/${price.interval ?? "one-time"}`).join(", ")}`);
8128
+ for (const error of productCheck.errors) console.log(` Error: ${error}`);
8129
+ const webhookCheck = billingChecks.checks.webhook ?? {
8130
+ status: "critical",
8131
+ configured: false,
8132
+ error: "Webhook readiness was not reported by the application Worker"
8133
+ };
8134
+ console.log("\nWebhook verifier:");
8135
+ console.log(` ${statusLabel(webhookCheck.status)} configured=${webhookCheck.configured}`);
8136
+ if (webhookCheck.error) console.log(` Error: ${webhookCheck.error}`);
7890
8137
  const syncCheck = billingChecks.checks.syncHealth;
7891
8138
  console.log("\nSync health:");
7892
8139
  console.log(` ${statusLabel(syncCheck.status)} pending=${syncCheck.events.pending} failed=${syncCheck.events.failed} synced=${syncCheck.events.synced}`);
@@ -7895,13 +8142,15 @@ async function handleBillingOpsCheck(env) {
7895
8142
  const internalCheck = billingChecks.checks.internalUsers;
7896
8143
  console.log("\nInternal users:");
7897
8144
  console.log(` ${statusLabel(internalCheck.status)} users=${internalCheck.internalUsers} nonBillableEvents=${internalCheck.nonBillableEvents} billableEvents=${internalCheck.billableEvents}`);
7898
- const hasCriticalWorker = workerChecks.some((check) => check.status === "critical");
7899
- const hasCriticalBilling = billingChecks.status === "critical";
7900
- const hasWarning = workerChecks.some((check) => check.status === "warning") || billingChecks.status === "warning";
7901
- if (hasCriticalWorker || hasCriticalBilling) {
8145
+ const resultStatus = resolveBillingOpsResult({
8146
+ workerStatuses: workerChecks.map((check) => check.status),
8147
+ billingStatus: billingChecks.status,
8148
+ webhookStatus: webhookCheck.status
8149
+ });
8150
+ if (resultStatus === "critical") {
7902
8151
  console.log("\nResult: CRITICAL - billing operations need attention.");
7903
8152
  process$1.exitCode = 1;
7904
- } else if (hasWarning) console.log("\nResult: WARNING - billing is operational but has lag or cleanup work.");
8153
+ } else if (resultStatus === "warning") console.log("\nResult: WARNING - billing is operational but has lag or cleanup work.");
7905
8154
  else console.log("\nResult: OK - billing workers, meters, and sync health are ready.");
7906
8155
  }
7907
8156
  async function handleBillingReconcile(env, parsed) {
@@ -8360,13 +8609,644 @@ async function executeSpaceLens(parsed, deps = defaultDeps$8) {
8360
8609
  return result;
8361
8610
  }
8362
8611
  //#endregion
8612
+ //#region src/shared/generatorCapabilities.ts
8613
+ function input(name, type, required, description, options = {}) {
8614
+ return {
8615
+ name,
8616
+ type,
8617
+ required,
8618
+ description,
8619
+ ...options
8620
+ };
8621
+ }
8622
+ var SPACE_INPUT = input("space_id", "string", true, "Target Space ID from list_spaces.");
8623
+ var NAME_INPUT = input("name", "string", true, "Human-readable asset name.");
8624
+ var PROMPT_INPUT = input("prompt", "string", true, "Generation instruction.");
8625
+ function fixedInput(name, value, description) {
8626
+ return input(name, "string", true, description, { allowedValues: [value] });
8627
+ }
8628
+ function imageGenerator(capability) {
8629
+ const referenceInput = input("reference_variant_refs", "string_array", false, "Completed image variant references from find_assets or get_asset.", {
8630
+ minItems: 1,
8631
+ maxItems: capability.maxReferenceImages
8632
+ });
8633
+ const generateInputs = [
8634
+ SPACE_INPUT,
8635
+ fixedInput("generator_id", `image/${capability.selection}`, "Selects this image generator."),
8636
+ NAME_INPUT,
8637
+ input("asset_type", "string", true, "Asset classification stored in the Space."),
8638
+ PROMPT_INPUT,
8639
+ input("aspect_ratio", "string", false, "Output aspect ratio.", {
8640
+ allowedValues: capability.supportedAspectRatios,
8641
+ defaultValue: "1:1"
8642
+ }),
8643
+ input("image_size", "string", false, "Output image size.", {
8644
+ allowedValues: capability.supportedImageSizes,
8645
+ defaultValue: "1K"
8646
+ })
8647
+ ];
8648
+ const operations = [{
8649
+ operation: "generate",
8650
+ tool: "generate_image",
8651
+ description: "Create a new image asset from text.",
8652
+ inputs: generateInputs
8653
+ }];
8654
+ if (capability.supportedOperations.includes("derive")) operations.push({
8655
+ operation: "derive",
8656
+ tool: "generate_image",
8657
+ description: "Create a new image asset using completed image variants as references.",
8658
+ inputs: [...generateInputs, {
8659
+ ...referenceInput,
8660
+ required: true
8661
+ }]
8662
+ });
8663
+ if (capability.supportedOperations.includes("refine")) operations.push({
8664
+ operation: "refine",
8665
+ tool: "edit_image",
8666
+ description: "Create a new variant on an existing image asset.",
8667
+ inputs: [
8668
+ SPACE_INPUT,
8669
+ fixedInput("generator_id", `image/${capability.selection}`, "Selects this image generator."),
8670
+ input("asset_ref", "string", true, "Target image asset reference from find_assets."),
8671
+ PROMPT_INPUT,
8672
+ input("source_variant_ref", "string", true, "Completed image variant to edit."),
8673
+ input("reference_variant_refs", "string_array", false, "Additional completed image references.", {
8674
+ minItems: 1,
8675
+ maxItems: Math.max(0, capability.maxReferenceImages - 1)
8676
+ }),
8677
+ input("aspect_ratio", "string", false, "Output aspect ratio.", {
8678
+ allowedValues: capability.supportedAspectRatios,
8679
+ defaultValue: "1:1"
8680
+ }),
8681
+ input("image_size", "string", false, "Output image size.", {
8682
+ allowedValues: capability.supportedImageSizes,
8683
+ defaultValue: "1K"
8684
+ })
8685
+ ]
8686
+ });
8687
+ return {
8688
+ id: `image/${capability.selection}`,
8689
+ label: capability.label,
8690
+ mediaKind: "image",
8691
+ modelIds: [capability.modelId],
8692
+ defaultModelId: capability.modelId,
8693
+ operations,
8694
+ referenceRules: {
8695
+ mediaKind: capability.maxReferenceImages > 0 ? "image" : null,
8696
+ completedOnly: capability.maxReferenceImages > 0,
8697
+ maxCount: capability.maxReferenceImages
8698
+ },
8699
+ notes: capability.maxReferenceImages > 0 ? ["Use compact variant references; binary image input is not accepted."] : ["Text-to-image only; reference inputs are not supported."]
8700
+ };
8701
+ }
8702
+ function videoGenerator(selection) {
8703
+ const defaultModel = getVideoGenerationModelForSelection(selection);
8704
+ const isVeo = selection === "veo-3.1";
8705
+ const isOmni = selection === "omni-flash";
8706
+ const isKling = selection === "kling";
8707
+ const modelIds = isVeo ? Object.values(VIDEO_GENERATION_TIER_MODELS) : [defaultModel];
8708
+ const resolutions = isVeo ? VIDEO_GENERATION_RESOLUTIONS : getVideoGenerationResolutionsForModel(defaultModel);
8709
+ const maxReferences = getVideoGenerationMaxReferenceImages(defaultModel);
8710
+ const parameterInputs = [input("aspect_ratio", "string", false, "Output aspect ratio.", {
8711
+ allowedValues: VIDEO_GENERATION_ASPECT_RATIOS,
8712
+ defaultValue: "16:9"
8713
+ })];
8714
+ if (!isOmni) parameterInputs.push(input("video_resolution", "string", false, "Output resolution.", {
8715
+ allowedValues: resolutions,
8716
+ defaultValue: DEFAULT_VIDEO_GENERATION_RESOLUTION
8717
+ }), input("video_duration_seconds", "integer", false, "Output duration in seconds.", {
8718
+ allowedValues: VIDEO_GENERATION_DURATION_SECONDS,
8719
+ defaultValue: 8
8720
+ }));
8721
+ if (isVeo) parameterInputs.push(input("video_tier", "string", false, "Veo model tier.", {
8722
+ allowedValues: VIDEO_GENERATION_TIERS,
8723
+ defaultValue: DEFAULT_VIDEO_GENERATION_TIER
8724
+ }));
8725
+ if (isKling) parameterInputs.push(input("generate_audio", "boolean", false, "Generate synchronized audio.", {
8726
+ allowedValues: [true, false],
8727
+ defaultValue: true
8728
+ }));
8729
+ const commonInputs = [
8730
+ SPACE_INPUT,
8731
+ fixedInput("generator_id", `video/${selection}`, "Selects this video generator."),
8732
+ NAME_INPUT,
8733
+ input("asset_type", "string", true, "Asset classification stored in the Space."),
8734
+ PROMPT_INPUT,
8735
+ ...parameterInputs
8736
+ ];
8737
+ const operations = [{
8738
+ operation: "generate",
8739
+ tool: "generate_video",
8740
+ description: "Create a new video asset from text.",
8741
+ inputs: commonInputs
8742
+ }];
8743
+ if (VIDEO_MODEL_SUPPORTED_OPERATIONS[selection].includes("refine")) operations.push({
8744
+ operation: "refine",
8745
+ tool: "edit_video",
8746
+ description: "Create a new sibling variant from a completed video variant.",
8747
+ inputs: [
8748
+ SPACE_INPUT,
8749
+ fixedInput("generator_id", `video/${selection}`, "Selects this video generator."),
8750
+ input("asset_ref", "string", true, "Target video asset reference from find_assets."),
8751
+ input("source_variant_ref", "string", true, "Completed video variant to refine."),
8752
+ PROMPT_INPUT,
8753
+ ...parameterInputs
8754
+ ]
8755
+ });
8756
+ if (VIDEO_MODEL_SUPPORTED_OPERATIONS[selection].includes("derive")) operations.push({
8757
+ operation: "derive",
8758
+ tool: "generate_video",
8759
+ description: "Create a new video from completed image variants.",
8760
+ inputs: [...commonInputs, input("reference_variant_refs", "string_array", true, "Completed image variant references from find_assets or get_asset.", {
8761
+ minItems: 1,
8762
+ maxItems: maxReferences
8763
+ })]
8764
+ });
8765
+ const notes = isVeo ? [`Resolution support by tier: ${VIDEO_GENERATION_TIERS.map((tier) => `${tier}=${VIDEO_GENERATION_RESOLUTIONS_BY_TIER[tier].join("/")}`).join(", ")}.`, "Synchronized audio is always enabled."] : isOmni ? ["Resolution, duration, tier, and generated-audio options are not accepted."] : selection === "fal-seedance" ? ["Text-to-video only; generated audio is not supported."] : ["At most one completed image reference is accepted."];
8766
+ return {
8767
+ id: `video/${selection}`,
8768
+ label: VIDEO_MODEL_LABELS[selection],
8769
+ mediaKind: "video",
8770
+ modelIds,
8771
+ defaultModelId: defaultModel,
8772
+ operations,
8773
+ referenceRules: {
8774
+ mediaKind: maxReferences > 0 ? "image" : null,
8775
+ completedOnly: maxReferences > 0,
8776
+ maxCount: maxReferences
8777
+ },
8778
+ notes
8779
+ };
8780
+ }
8781
+ function seedanceVideoGenerator(capability) {
8782
+ const fixedGenerator = fixedInput("generator_id", capability.generatorId, "Selects this exact Seedance 2 mode and tier.");
8783
+ const parameters = [
8784
+ input("aspect_ratio", "string", false, "Output aspect ratio; auto lets Seedance infer it.", {
8785
+ allowedValues: capability.aspectRatios,
8786
+ defaultValue: capability.defaultAspectRatio
8787
+ }),
8788
+ input("resolution", "string", false, "Output resolution.", {
8789
+ allowedValues: capability.resolutions,
8790
+ defaultValue: capability.defaultResolution
8791
+ }),
8792
+ input("duration", "string_or_integer", false, "Output seconds from 4 to 15, or auto.", {
8793
+ allowedValues: capability.durations,
8794
+ defaultValue: capability.defaultDuration
8795
+ }),
8796
+ input("generate_audio", "boolean", false, "Generate synchronized native audio.", {
8797
+ allowedValues: [true, false],
8798
+ defaultValue: capability.generateAudio.default
8799
+ }),
8800
+ input("bitrate_mode", "string", false, "Output bitrate quality.", {
8801
+ allowedValues: capability.bitrateModes,
8802
+ defaultValue: capability.defaultBitrateMode
8803
+ })
8804
+ ];
8805
+ const common = [
8806
+ SPACE_INPUT,
8807
+ fixedGenerator,
8808
+ NAME_INPUT,
8809
+ input("asset_type", "string", true, "Asset classification stored in the Space."),
8810
+ PROMPT_INPUT,
8811
+ ...parameters
8812
+ ];
8813
+ const frameInputs = [input("start_frame_variant_ref", "string", true, "Completed image variant used as the authoritative first frame; cannot be combined with extended references."), input("end_frame_variant_ref", "string", false, "Completed image variant used as the authoritative final frame; cannot be combined with extended references.")];
8814
+ const referenceInputs = [
8815
+ input("image_reference_variant_refs", "string_array", false, "Ordered completed images addressed as @Image1 through @Image9 for soft subject, style, or geography guidance; not authoritative boundary frames.", {
8816
+ minItems: 1,
8817
+ maxItems: 9
8818
+ }),
8819
+ input("video_reference_variant_refs", "string_array", false, "Ordered completed videos addressed as @Video1 through @Video3.", {
8820
+ minItems: 1,
8821
+ maxItems: 3
8822
+ }),
8823
+ input("audio_reference_variant_refs", "string_array", false, "Ordered completed audio clips addressed as @Audio1 through @Audio3.", {
8824
+ minItems: 1,
8825
+ maxItems: 3
8826
+ })
8827
+ ];
8828
+ const modeInputs = capability.mode === "frame" ? frameInputs : capability.mode === "reference" ? referenceInputs : [];
8829
+ const operations = capability.operations.filter((operation) => operation !== "refine").map((operation) => ({
8830
+ operation,
8831
+ tool: "generate_video",
8832
+ description: capability.mode === "text" ? "Create a video from text." : capability.mode === "frame" ? "Animate a required authoritative start frame and optional authoritative end frame; extended references cannot be combined with them." : "Direct a video with ordered image, video, and audio references; these guide generation but cannot serve as authoritative start/end frames.",
8833
+ inputs: [...common, ...modeInputs]
8834
+ }));
8835
+ if (capability.operations.includes("refine")) {
8836
+ const budget = getSeedance2ReferenceBudget(capability, "refine");
8837
+ const editReferenceInputs = referenceInputs.map((referenceInput) => referenceInput.name === "video_reference_variant_refs" ? {
8838
+ ...referenceInput,
8839
+ maxItems: 2,
8840
+ description: "Up to two additional completed videos addressed as @Video2 and @Video3; the source video is @Video1."
8841
+ } : referenceInput);
8842
+ operations.push({
8843
+ operation: "refine",
8844
+ tool: "edit_video",
8845
+ description: "Edit or continue a completed video using it as @Video1 plus optional ordered references.",
8846
+ referenceLimits: {
8847
+ maxAdditionalCount: budget.maxAdditionalFiles,
8848
+ implicitSourceCount: 1,
8849
+ maxAdditionalByKind: budget.maxAdditionalByKind
8850
+ },
8851
+ inputs: [
8852
+ SPACE_INPUT,
8853
+ fixedGenerator,
8854
+ input("asset_ref", "string", true, "Target video asset reference from find_assets."),
8855
+ input("source_variant_ref", "string", true, "Completed target video used as @Video1."),
8856
+ PROMPT_INPUT,
8857
+ ...parameters,
8858
+ ...editReferenceInputs
8859
+ ]
8860
+ });
8861
+ }
8862
+ return {
8863
+ id: capability.generatorId,
8864
+ label: capability.label,
8865
+ mediaKind: "video",
8866
+ modelIds: [capability.endpointId],
8867
+ defaultModelId: capability.endpointId,
8868
+ operations,
8869
+ referenceRules: {
8870
+ mediaKind: capability.mode === "frame" ? "image" : null,
8871
+ completedOnly: capability.mode !== "text",
8872
+ maxCount: capability.maxReferenceFiles,
8873
+ maxTotalCount: capability.maxReferenceFiles,
8874
+ requiresVisualWithAudio: capability.requiresVisualReferenceWithAudio,
8875
+ modalities: capability.references.map((reference) => ({
8876
+ mediaKind: reference.mediaKind,
8877
+ minCount: reference.minCount,
8878
+ maxCount: reference.maxCount,
8879
+ promptLabel: reference.promptLabel,
8880
+ acceptedMimeTypes: reference.acceptedMimeTypes,
8881
+ maxBytesPerFile: reference.maxBytesPerFile,
8882
+ ...reference.combinedMaxBytes !== void 0 ? { combinedMaxBytes: reference.combinedMaxBytes } : {},
8883
+ ...reference.combinedDurationSeconds ? { combinedDurationSeconds: reference.combinedDurationSeconds } : {}
8884
+ }))
8885
+ },
8886
+ notes: [
8887
+ "Use compact variant references; binary media input is not accepted.",
8888
+ capability.mode === "reference" ? "Prompt labels are modality-specific and one-based: @ImageN, @VideoN, and @AudioN. @ImageN guides content, style, or geography but is not an authoritative start/end frame." : capability.mode === "frame" ? "The start and end images are authoritative boundary frames. The provider does not allow extended image, video, or audio references in this mode." : "This mode rejects all reference inputs.",
8889
+ "Seedance 2 cannot combine authoritative start/end frames with extended multimodal references in one request.",
8890
+ capability.resolutions.includes("4k") ? "Standard supports 480p, 720p, 1080p, and 4K output." : "Fast supports 480p and 720p output."
8891
+ ]
8892
+ };
8893
+ }
8894
+ function avatarVideoGenerator(model) {
8895
+ const parameters = [input("mode", "string", false, "Generation quality.", {
8896
+ allowedValues: AVATAR_MODES,
8897
+ defaultValue: "standard"
8898
+ })];
8899
+ return {
8900
+ id: `video/${model}`,
8901
+ label: "Kling Avatar V2",
8902
+ mediaKind: "video",
8903
+ modelIds: [model],
8904
+ defaultModelId: model,
8905
+ operations: [{
8906
+ operation: "derive",
8907
+ tool: "generate_video",
8908
+ description: "Create an Avatar video from one portrait image and one audio Variant.",
8909
+ inputs: [
8910
+ SPACE_INPUT,
8911
+ fixedInput("generator_id", `video/${model}`, "Selects this Avatar generator."),
8912
+ NAME_INPUT,
8913
+ input("asset_type", "string", true, "Asset classification stored in the Space."),
8914
+ input("prompt", "string", false, "Optional motion and expression guidance."),
8915
+ ...parameters,
8916
+ input("image_reference_variant_refs", "string_array", true, "Exactly one completed portrait image Variant.", {
8917
+ minItems: 1,
8918
+ maxItems: 1
8919
+ }),
8920
+ input("audio_reference_variant_refs", "string_array", true, "Exactly one completed audio Variant.", {
8921
+ minItems: 1,
8922
+ maxItems: 1
8923
+ })
8924
+ ]
8925
+ }],
8926
+ referenceRules: {
8927
+ mediaKind: null,
8928
+ completedOnly: true,
8929
+ maxCount: 2,
8930
+ maxTotalCount: 2,
8931
+ modalities: [{
8932
+ mediaKind: "image",
8933
+ minCount: 1,
8934
+ maxCount: 1,
8935
+ promptLabel: "Portrait",
8936
+ acceptedMimeTypes: [
8937
+ "image/jpeg",
8938
+ "image/png",
8939
+ "image/webp"
8940
+ ],
8941
+ maxBytesPerFile: 10 * 1024 * 1024
8942
+ }, {
8943
+ mediaKind: "audio",
8944
+ minCount: 1,
8945
+ maxCount: 1,
8946
+ promptLabel: "Audio",
8947
+ acceptedMimeTypes: [
8948
+ "audio/mpeg",
8949
+ "audio/wav",
8950
+ "audio/x-wav",
8951
+ "audio/mp4",
8952
+ "audio/aac"
8953
+ ],
8954
+ maxBytesPerFile: 5 * 1024 * 1024
8955
+ }]
8956
+ },
8957
+ notes: ["Outputs an ordinary video Variant. Provider routing remains internal."]
8958
+ };
8959
+ }
8960
+ function audioGenerator(input_) {
8961
+ const inputs = [
8962
+ SPACE_INPUT,
8963
+ fixedInput("generator_id", input_.id, "Selects this audio generator and mode."),
8964
+ NAME_INPUT,
8965
+ PROMPT_INPUT
8966
+ ];
8967
+ if (input_.provider === "elevenlabs" && (input_.assetType === "speech" || input_.assetType === "dialogue")) inputs.push(input("model", "string", false, "Optional ElevenLabs model override.", { defaultValue: input_.modelId }));
8968
+ if (input_.assetType === "speech") inputs.push(input("voice_id", "string", true, "Voice ID returned by list_voices."));
8969
+ else if (input_.assetType === "dialogue") inputs.push(input("dialogue_voice_ids", "string_array", true, "One voice ID per distinct speaker, in first-appearance order.", { minItems: 2 }));
8970
+ const notes = input_.assetType === "dialogue" ? ["Prompt must contain at least two `Speaker: line` entries and at least two distinct speakers."] : input_.assetType === "speech" ? ["Prompt must be plain speech text, not `Speaker: line` dialogue."] : ["Prompt describes the desired audio; voice inputs and image references are not accepted."];
8971
+ return {
8972
+ id: input_.id,
8973
+ label: input_.label,
8974
+ mediaKind: "audio",
8975
+ modelIds: [input_.modelId],
8976
+ defaultModelId: input_.modelId,
8977
+ operations: [{
8978
+ operation: "generate",
8979
+ tool: "generate_audio",
8980
+ description: `Create a new ${input_.assetType} audio asset.`,
8981
+ inputs
8982
+ }],
8983
+ referenceRules: {
8984
+ mediaKind: null,
8985
+ completedOnly: false,
8986
+ maxCount: 0
8987
+ },
8988
+ notes
8989
+ };
8990
+ }
8991
+ function getGeneratorCatalog(overrides = {}) {
8992
+ const elevenLabsSpeech = overrides.elevenLabsSpeech ?? "eleven_v3";
8993
+ const elevenLabsMusic = overrides.elevenLabsMusic ?? "music_v1";
8994
+ const elevenLabsSfx = overrides.elevenLabsSfx ?? "eleven_text_to_sound_v2";
8995
+ const lyria = overrides.lyria ?? "lyria-3-clip-preview";
8996
+ return [
8997
+ ...Object.values(IMAGE_MODEL_CAPABILITIES).map(imageGenerator),
8998
+ ...VIDEO_MODEL_SELECTIONS.map(videoGenerator),
8999
+ ...SEEDANCE_2_SELECTIONS.map((selection) => seedanceVideoGenerator(SEEDANCE_2_CAPABILITIES[selection])),
9000
+ ...AVATAR_MODEL_IDS.map(avatarVideoGenerator),
9001
+ audioGenerator({
9002
+ id: "audio/elevenlabs-speech",
9003
+ label: "ElevenLabs Speech",
9004
+ provider: "elevenlabs",
9005
+ assetType: "speech",
9006
+ modelId: elevenLabsSpeech
9007
+ }),
9008
+ audioGenerator({
9009
+ id: "audio/elevenlabs-dialogue",
9010
+ label: "ElevenLabs Dialogue",
9011
+ provider: "elevenlabs",
9012
+ assetType: "dialogue",
9013
+ modelId: elevenLabsSpeech
9014
+ }),
9015
+ audioGenerator({
9016
+ id: "audio/elevenlabs-music",
9017
+ label: "ElevenLabs Music",
9018
+ provider: "elevenlabs",
9019
+ assetType: "music",
9020
+ modelId: elevenLabsMusic
9021
+ }),
9022
+ audioGenerator({
9023
+ id: "audio/elevenlabs-sfx",
9024
+ label: "ElevenLabs Sound Effects",
9025
+ provider: "elevenlabs",
9026
+ assetType: "sfx",
9027
+ modelId: elevenLabsSfx
9028
+ }),
9029
+ audioGenerator({
9030
+ id: "audio/lyria-music",
9031
+ label: "Lyria Music",
9032
+ provider: "lyria",
9033
+ assetType: "music",
9034
+ modelId: lyria
9035
+ })
9036
+ ];
9037
+ }
9038
+ function getGeneratorDefinition(id, overrides = {}) {
9039
+ return getGeneratorCatalog(overrides).find((generator) => generator.id === id);
9040
+ }
9041
+ //#endregion
9042
+ //#region src/shared/generationReferences.ts
9043
+ function isGenerationReference(value) {
9044
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
9045
+ const reference = value;
9046
+ return typeof reference.variantId === "string" && reference.variantId.length > 0 && (reference.mediaKind === "image" || reference.mediaKind === "video" || reference.mediaKind === "audio") && typeof reference.storageKey === "string" && reference.storageKey.length > 0 && Number.isInteger(reference.sequenceIndex) && Number.isInteger(reference.modalityIndex) && (reference.mimeType === void 0 || typeof reference.mimeType === "string") && (reference.sizeBytes === void 0 || typeof reference.sizeBytes === "number") && (reference.width === void 0 || typeof reference.width === "number") && (reference.height === void 0 || typeof reference.height === "number") && (reference.durationMs === void 0 || typeof reference.durationMs === "number");
9047
+ }
9048
+ function areGenerationReferencesIndexed(value) {
9049
+ if (!Array.isArray(value)) return false;
9050
+ const modalityCounts = {
9051
+ image: 0,
9052
+ video: 0,
9053
+ audio: 0
9054
+ };
9055
+ return value.every((reference, sequenceIndex) => {
9056
+ if (!isGenerationReference(reference) || reference.sequenceIndex !== sequenceIndex) return false;
9057
+ const expectedModalityIndex = modalityCounts[reference.mediaKind]++;
9058
+ return reference.modalityIndex === expectedModalityIndex;
9059
+ });
9060
+ }
9061
+ //#endregion
9062
+ //#region src/shared/draftMapping.ts
9063
+ /** Generator input names map to the canonical camel-case keys stored in recipes. */
9064
+ var DRAFT_RECIPE_INPUT_KEYS = {
9065
+ asset_type: "assetType",
9066
+ prompt: "prompt",
9067
+ aspect_ratio: "aspectRatio",
9068
+ image_size: "imageSize",
9069
+ model: "model",
9070
+ voice_id: "voiceId",
9071
+ dialogue_voice_ids: "dialogueVoiceIds",
9072
+ generate_audio: "generateAudio",
9073
+ video_resolution: "videoResolution",
9074
+ resolution: "videoResolution",
9075
+ video_duration_seconds: "videoDurationSeconds",
9076
+ video_tier: "videoTier",
9077
+ duration: "seedanceDuration",
9078
+ bitrate_mode: "seedanceBitrateMode"
9079
+ };
9080
+ function draftRecipeInputKey(inputName, generatorId) {
9081
+ if (generatorId === "video/p-video-avatar" && inputName === "resolution") return "avatarResolution";
9082
+ if (generatorId === "video/kling-avatar-v2" && inputName === "mode") return "avatarMode";
9083
+ return DRAFT_RECIPE_INPUT_KEYS[inputName];
9084
+ }
9085
+ var DRAFT_REFERENCE_FIELD = /(?:_variant_refs?|VariantRefs?)$/;
9086
+ /**
9087
+ * The one slot-naming convention used while authoring and resolving Bindings.
9088
+ * Unknown reference slots inherit the Draft output kind.
9089
+ */
9090
+ function mediaKindForDraftSlot(slot, fallback) {
9091
+ if (slot.startsWith("image_") || slot === "start_frame_variant_ref" || slot === "end_frame_variant_ref" || slot === "reference_variant_refs") return "image";
9092
+ if (slot.startsWith("video_")) return "video";
9093
+ if (slot.startsWith("audio_")) return "audio";
9094
+ return fallback;
9095
+ }
9096
+ function generatorIdForDraftRecipe(mediaMode, recipe) {
9097
+ if (mediaMode === "image") {
9098
+ if (recipe.modelProvider === "custom") throw new Error("Custom-provider image Recipes do not map to a Draft generator");
9099
+ const model = typeof recipe.model === "string" ? recipe.model : "pro";
9100
+ if (!isImageModelSelection(model) && !isImageModelId(model)) throw new Error(`Stored image model ${model} does not map to a Draft generator`);
9101
+ return `image/${getImageModelSelection(model)}`;
9102
+ }
9103
+ if (mediaMode === "video") {
9104
+ const model = typeof recipe.model === "string" ? recipe.model : "veo-3.1";
9105
+ if (isAvatarModelId(model)) return `video/${model}`;
9106
+ const seedance = getSeedance2CapabilityByEndpoint(model);
9107
+ if (seedance) return seedance.generatorId;
9108
+ const selection = getVideoModelSelectionForModel(model);
9109
+ if (!selection) throw new Error(`Stored video model ${model} does not map to a Draft generator`);
9110
+ return `video/${selection}`;
9111
+ }
9112
+ if (mediaMode === "speech") return "audio/elevenlabs-speech";
9113
+ if (mediaMode === "dialogue") return "audio/elevenlabs-dialogue";
9114
+ if (mediaMode === "sfx") return "audio/elevenlabs-sfx";
9115
+ return recipe.musicProvider === "lyria" ? "audio/lyria-music" : "audio/elevenlabs-music";
9116
+ }
9117
+ function mediaModeForDraftRecipe(mediaKind, assetType) {
9118
+ if (mediaKind === "image" || mediaKind === "video") return mediaKind;
9119
+ return getForgeModeForAudioAssetType(assetType);
9120
+ }
9121
+ function buildDraftMapping(args) {
9122
+ const { name, mediaMode, generatorId, recipe, references, destinationKind, destinationAssetId, sourceVariantId } = args;
9123
+ const operation = destinationKind === "sibling" ? "refine" : references.length > 0 ? "derive" : "generate";
9124
+ const operationDefinition = getGeneratorDefinition(generatorId)?.operations.find((candidate) => candidate.operation === operation);
9125
+ if (!operationDefinition) throw new Error(`${generatorId} does not support ${operation} drafts`);
9126
+ const recipeTemplate = { operation };
9127
+ for (const input of operationDefinition.inputs) {
9128
+ const key = draftRecipeInputKey(input.name, generatorId);
9129
+ if (!key) continue;
9130
+ const value = recipe[key];
9131
+ if (value !== void 0) recipeTemplate[key] = value;
9132
+ }
9133
+ const availableSlots = operationDefinition.inputs.filter((input) => DRAFT_REFERENCE_FIELD.test(input.name)).map((input) => input.name);
9134
+ const bindings = [];
9135
+ if (destinationKind === "sibling" && sourceVariantId && availableSlots.includes("source_variant_ref")) bindings.push({
9136
+ slot: "source_variant_ref",
9137
+ sourceKind: "variant",
9138
+ sourceVariantId,
9139
+ sortIndex: 0
9140
+ });
9141
+ const outputMediaKind = getMediaKindForForgeMode(mediaMode);
9142
+ const additionalSlots = availableSlots.filter((slot) => slot !== "source_variant_ref");
9143
+ const usedScalarSlots = /* @__PURE__ */ new Set();
9144
+ for (const [index, reference] of references.entries()) {
9145
+ const targetSlot = additionalSlots.filter((slot) => mediaKindForDraftSlot(slot, outputMediaKind) === reference.mediaKind).find((slot) => slot.endsWith("_refs") || !usedScalarSlots.has(slot));
9146
+ if (!targetSlot) throw new Error(`No ${reference.mediaKind} input slot is available for ${generatorId}`);
9147
+ if (!targetSlot.endsWith("_refs")) usedScalarSlots.add(targetSlot);
9148
+ bindings.push({
9149
+ id: reference.id,
9150
+ slot: targetSlot,
9151
+ sourceKind: reference.sourceKind,
9152
+ sourceVariantId: reference.sourceVariantId,
9153
+ sourceAssetId: reference.sourceAssetId,
9154
+ sourceDraftId: reference.sourceDraftId,
9155
+ sortIndex: index
9156
+ });
9157
+ }
9158
+ return {
9159
+ name,
9160
+ mediaMode,
9161
+ generatorId,
9162
+ recipeTemplate,
9163
+ destinationKind,
9164
+ destinationAssetId: destinationKind === "sibling" ? destinationAssetId : null,
9165
+ bindings
9166
+ };
9167
+ }
9168
+ function materializeDraftFromVariant(args) {
9169
+ const storedRecipe = parseStoredRecipe$1(args.recipe);
9170
+ if (!areGenerationReferencesIndexed(storedRecipe.references)) throw new Error("Variant recipe has invalid references");
9171
+ const mediaMode = args.mediaMode ?? mediaModeForDraftRecipe(args.variantMediaKind, args.assetType ?? stringValue(storedRecipe.assetType));
9172
+ const recipe = args.recipeOverride ?? storedRecipe;
9173
+ const generatorId = args.generatorId ?? generatorIdForDraftRecipe(mediaMode, storedRecipe);
9174
+ const storedOperation = stringValue(storedRecipe.operation);
9175
+ const destinationKind = args.destinationKind ?? (storedOperation === "refine" ? "sibling" : "new_asset");
9176
+ const storedReferences = storedRecipe.references;
9177
+ if (storedOperation === "refine" && destinationKind === "sibling" && storedReferences.length === 0) throw new Error("Stored refine Recipe has no source Variant reference");
9178
+ const preservesStoredRefine = storedOperation === "refine" && destinationKind === "sibling";
9179
+ const sourceVariantId = preservesStoredRefine ? storedReferences[0]?.variantId : destinationKind === "sibling" ? args.variantId : void 0;
9180
+ const additionalReferences = preservesStoredRefine ? storedReferences.slice(1) : storedReferences;
9181
+ return buildDraftMapping({
9182
+ name: args.name?.trim() || args.assetName,
9183
+ mediaMode,
9184
+ generatorId,
9185
+ recipe,
9186
+ references: additionalReferences.map((reference) => ({
9187
+ mediaKind: reference.mediaKind,
9188
+ sourceKind: "variant",
9189
+ sourceVariantId: reference.variantId
9190
+ })),
9191
+ destinationKind,
9192
+ destinationAssetId: destinationKind === "sibling" ? args.destinationAssetId ?? args.assetId : null,
9193
+ sourceVariantId
9194
+ });
9195
+ }
9196
+ function parseStoredRecipe$1(value) {
9197
+ let parsed = value;
9198
+ if (typeof value === "string") try {
9199
+ parsed = JSON.parse(value);
9200
+ } catch {
9201
+ throw new Error("Variant recipe is not valid JSON");
9202
+ }
9203
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error("Variant recipe is not a JSON object");
9204
+ return parsed;
9205
+ }
9206
+ function stringValue(value) {
9207
+ return typeof value === "string" && value.trim() ? value : void 0;
9208
+ }
9209
+ //#endregion
8363
9210
  //#region src/cli/commands/drafts.ts
9211
+ var CLI_DRAFT_OPTIONS = {
9212
+ list: [],
9213
+ show: [],
9214
+ create: [
9215
+ "from",
9216
+ "name",
9217
+ "media",
9218
+ "generator",
9219
+ "recipe",
9220
+ "destination",
9221
+ "asset",
9222
+ "x",
9223
+ "y"
9224
+ ],
9225
+ update: [
9226
+ "name",
9227
+ "media",
9228
+ "generator",
9229
+ "recipe",
9230
+ "destination",
9231
+ "asset",
9232
+ "x",
9233
+ "y"
9234
+ ],
9235
+ delete: [],
9236
+ run: ["request-id", "wait"]
9237
+ };
8364
9238
  var defaultDeps$7 = {
8365
9239
  loadConfig: loadStoredConfig,
8366
9240
  loadProjectConfig,
8367
9241
  createMutationClient: (env, spaceId) => WebSocketClient.create(env, spaceId),
8368
9242
  print: console.log
8369
9243
  };
9244
+ var defaultGraphDeps = {
9245
+ loadConfig: loadStoredConfig,
9246
+ loadProjectConfig,
9247
+ createClient: (env, spaceId) => WebSocketClient.create(env, spaceId),
9248
+ print: console.log
9249
+ };
8370
9250
  async function handleDrafts(parsed) {
8371
9251
  try {
8372
9252
  await executeDrafts(parsed);
@@ -8383,9 +9263,17 @@ async function handleBindings(parsed) {
8383
9263
  process$1.exitCode = 1;
8384
9264
  }
8385
9265
  }
9266
+ async function handleGraph(parsed) {
9267
+ try {
9268
+ await executeGraph(parsed);
9269
+ } catch (error) {
9270
+ console.error("Error:", error instanceof Error ? error.message : error);
9271
+ process$1.exitCode = 1;
9272
+ }
9273
+ }
8386
9274
  async function executeDrafts(parsed, deps = defaultDeps$7) {
8387
9275
  const subcommand = parsed.positionals[0];
8388
- if (!subcommand) throw new Error("Drafts command is required: list, create, update, or delete");
9276
+ if (!subcommand) throw new Error("Drafts command is required: list, show, create, update, delete, or run");
8389
9277
  rejectUnknownOptions("drafts", subcommand, parsed.options);
8390
9278
  const ctx = await buildContext$3(parsed, deps);
8391
9279
  if (subcommand === "list") {
@@ -8395,12 +9283,75 @@ async function executeDrafts(parsed, deps = defaultDeps$7) {
8395
9283
  return drafts;
8396
9284
  }
8397
9285
  if (subcommand === "create") {
8398
- const name = parsed.positionals.slice(1).join(" ").trim();
9286
+ const fromVariantRef = optionValue$2(parsed.options.from);
9287
+ const name = optionValue$2(parsed.options.name) ?? parsed.positionals.slice(1).join(" ").trim();
9288
+ if (fromVariantRef) {
9289
+ const state = await readDraftState(ctx, deps);
9290
+ const sourceVariantId = requireExactVariantRef$1(fromVariantRef, state);
9291
+ const sourceVariant = state.variants.find((variant) => variant.id === sourceVariantId);
9292
+ const sourceAsset = sourceVariant ? state.assets.find((asset) => asset.id === sourceVariant.asset_id) : void 0;
9293
+ if (!sourceVariant || !sourceAsset) throw new Error("Source Variant is unavailable");
9294
+ if (sourceVariant.status !== "completed") throw new Error("Source Variant must be completed");
9295
+ const destinationKind = parseDestination(parsed.options.destination);
9296
+ const destinationAssetRef = optionValue$2(parsed.options.asset);
9297
+ const mapped = materializeDraftFromVariant({
9298
+ variantId: sourceVariant.id,
9299
+ variantMediaKind: sourceVariant.media_kind,
9300
+ recipe: sourceVariant.recipe,
9301
+ assetName: sourceAsset.name,
9302
+ assetType: sourceAsset.type,
9303
+ assetId: sourceAsset.id,
9304
+ name,
9305
+ mediaMode: parseMediaMode(parsed.options.media),
9306
+ generatorId: optionValue$2(parsed.options.generator),
9307
+ recipeOverride: parseRecipe(parsed.options.recipe),
9308
+ destinationKind,
9309
+ destinationAssetId: destinationAssetRef ? requireAssetRef(destinationAssetRef, state.assets) : void 0
9310
+ });
9311
+ for (const binding of mapped.bindings) {
9312
+ const variantId = binding.sourceVariantId;
9313
+ if (variantId && !state.variants.some((variant) => variant.id === variantId)) throw new Error(`Variant recipe reference ${variantId} is unavailable`);
9314
+ }
9315
+ const created = await withClient(ctx, deps, async (client) => {
9316
+ const draft = await client.createDraft({
9317
+ name: mapped.name,
9318
+ mediaMode: mapped.mediaMode,
9319
+ generatorId: mapped.generatorId,
9320
+ recipeTemplate: mapped.recipeTemplate,
9321
+ destinationKind: mapped.destinationKind,
9322
+ destinationAssetId: mapped.destinationAssetId,
9323
+ canvasX: parseNullableFinite(parsed.options.x, "--x"),
9324
+ canvasY: parseNullableFinite(parsed.options.y, "--y")
9325
+ });
9326
+ const bindings = [];
9327
+ try {
9328
+ for (const binding of mapped.bindings) bindings.push(await client.setBinding({
9329
+ draftId: draft.id,
9330
+ ...binding
9331
+ }));
9332
+ } catch (error) {
9333
+ await client.deleteDraft(draft.id);
9334
+ throw error;
9335
+ }
9336
+ return {
9337
+ draft,
9338
+ bindings
9339
+ };
9340
+ });
9341
+ const resultState = {
9342
+ ...state,
9343
+ drafts: [...state.drafts, created.draft],
9344
+ bindings: [...state.bindings, ...created.bindings]
9345
+ };
9346
+ const result = publicDraft(created.draft, resultState);
9347
+ printJsonOrValue(parsed, deps, result, () => deps.print(`Created Draft ${result.draftRef}`));
9348
+ return created.draft;
9349
+ }
8399
9350
  const mediaMode = parseMediaMode(parsed.options.media);
8400
9351
  const generatorId = optionValue$2(parsed.options.generator);
8401
9352
  const recipeTemplate = parseRecipe(parsed.options.recipe);
8402
9353
  const destinationKind = parseDestination(parsed.options.destination);
8403
- if (!name || !mediaMode || !generatorId || recipeTemplate === void 0 || !destinationKind) throw new Error("Usage: makefx drafts create \"<name>\" --media <mode> --generator <id> --recipe <json> --destination new_asset|sibling [--asset <asset-ref>]");
9354
+ if (!name || !mediaMode || !generatorId || recipeTemplate === void 0 || !destinationKind) throw new Error("Usage: makefx drafts create \"<name>\" --media <mode> --generator <id> --recipe <json> --destination new_asset|sibling [--asset <asset-ref>], or makefx drafts create --from <variant-ref> [--name <name>]");
8404
9355
  const state = await readDraftState(ctx, deps);
8405
9356
  const destinationAssetRef = optionValue$2(parsed.options.asset);
8406
9357
  const destinationAssetId = destinationAssetRef ? requireAssetRef(destinationAssetRef, state.assets) : null;
@@ -8425,6 +9376,52 @@ async function executeDrafts(parsed, deps = defaultDeps$7) {
8425
9376
  if (!draftRef) throw new Error(`Usage: makefx drafts ${subcommand} <draft-ref>`);
8426
9377
  const state = await readDraftState(ctx, deps);
8427
9378
  const draftId = requireDraftRef(draftRef, state.drafts);
9379
+ if (subcommand === "show") {
9380
+ const draft = state.drafts.find((candidate) => candidate.id === draftId);
9381
+ if (!draft) throw new Error("Draft is unavailable");
9382
+ const result = publicDraft(draft, state);
9383
+ printJsonOrValue(parsed, deps, result, () => printDraft(result, deps.print));
9384
+ return result;
9385
+ }
9386
+ if (subcommand === "run") {
9387
+ const requestId = optionValue$2(parsed.options["request-id"]) ?? crypto.randomUUID();
9388
+ const waitSeconds = parsed.options.wait === void 0 ? void 0 : parseGraphWaitSeconds(parsed.options.wait);
9389
+ const run = await withClient(ctx, deps, async (client) => {
9390
+ if (!client.runDraft) throw new Error("The connected client does not support Draft runs");
9391
+ if (waitSeconds !== void 0 && !client.followVariant) throw new Error("The connected client does not support waiting for Draft runs");
9392
+ let outcomePromise;
9393
+ const started = await client.runDraft(draftId, requestId, waitSeconds === void 0 ? void 0 : (runStarted) => {
9394
+ if (runStarted.status !== "completed" && runStarted.status !== "failed") outcomePromise = client.followVariant({
9395
+ variantId: runStarted.variantId,
9396
+ requestId,
9397
+ timeoutMs: waitSeconds * 1e3
9398
+ });
9399
+ });
9400
+ if (waitSeconds === void 0 || started.status === "completed" || started.status === "failed") return {
9401
+ started,
9402
+ terminalStatus: started.status
9403
+ };
9404
+ if (!outcomePromise) throw new Error("Draft run wait was not initialized");
9405
+ return {
9406
+ started,
9407
+ terminalStatus: (await outcomePromise).success ? "completed" : "failed"
9408
+ };
9409
+ });
9410
+ const updatedState = await readDraftState(ctx, deps);
9411
+ const variantRef = variantRefForId(run.started.variantId, updatedState.assets, updatedState.variants);
9412
+ if (!variantRef) throw new Error("Draft run Variant is unavailable");
9413
+ const result = {
9414
+ draftRef,
9415
+ requestId,
9416
+ variantRef,
9417
+ status: run.terminalStatus
9418
+ };
9419
+ printJsonOrValue(parsed, deps, result, () => {
9420
+ deps.print(`${parsed.options.wait === void 0 ? "Started" : "Finished"} ${draftRef} (${run.terminalStatus})`);
9421
+ if (variantRef) deps.print(` Variant: ${variantRef}`);
9422
+ });
9423
+ return result;
9424
+ }
8428
9425
  if (subcommand === "update") {
8429
9426
  const destinationAssetRef = optionValue$2(parsed.options.asset);
8430
9427
  const changes = {
@@ -8495,6 +9492,47 @@ async function executeBindings(parsed, deps = defaultDeps$7) {
8495
9492
  printJsonOrValue(parsed, deps, publicBinding(binding, state), () => deps.print(`Set ${slot}[${sortIndex}] on ${draftRef}`));
8496
9493
  return binding;
8497
9494
  }
9495
+ async function executeGraph(parsed, deps = defaultGraphDeps) {
9496
+ const subcommand = parsed.positionals[0];
9497
+ if (subcommand !== "run" && subcommand !== "status" && subcommand !== "cancel") throw new Error("Graph command is required: run, status, or cancel");
9498
+ rejectGraphUnknownOptions(subcommand, parsed.options);
9499
+ const ctx = await buildContext$3(parsed, deps);
9500
+ const state = await readDraftState(ctx, { createMutationClient: deps.createClient });
9501
+ const client = await deps.createClient(ctx.env, ctx.spaceId);
9502
+ await client.connect();
9503
+ try {
9504
+ if (subcommand === "status" || subcommand === "cancel") {
9505
+ const runRef = parsed.positionals[1];
9506
+ if (!runRef) throw new Error(`Usage: makefx graph ${subcommand} <graph-run-ref>`);
9507
+ const current = await client.getGraphRunStatus(runRef);
9508
+ const status = subcommand === "cancel" ? await client.cancelGraphRun(current.run.id) : current;
9509
+ const result = publicGraphRun(status.run, status.drafts, state);
9510
+ printJsonOrValue(parsed, deps, result, () => {
9511
+ if (subcommand === "cancel") deps.print(`Cancelled pending work in ${runRef}`);
9512
+ printGraphRun(result, deps.print);
9513
+ });
9514
+ return result;
9515
+ }
9516
+ const draftRefs = parsed.positionals.slice(1);
9517
+ if (draftRefs.length === 0) throw new Error("Usage: makefx graph run <draft-ref> [draft-ref...] [--upstream] [--downstream] [--from-scratch] [--wait]");
9518
+ const requestId = optionValue$2(parsed.options["request-id"]) ?? crypto.randomUUID();
9519
+ let status = await client.runGraph({
9520
+ draftIds: draftRefs.map((draftRef) => requireDraftRef(draftRef, state.drafts)),
9521
+ requestId,
9522
+ includeUpstream: parsed.options.upstream === "true",
9523
+ includeDownstream: parsed.options.downstream === "true",
9524
+ fromScratch: parsed.options["from-scratch"] === "true"
9525
+ });
9526
+ const runRef = createGraphRunRef(status.run.id);
9527
+ if (parsed.options.wait !== void 0 && status.run.status === "running") status = await client.waitForGraphRun(runRef, status.run.id, parseGraphWaitSeconds(parsed.options.wait) * 1e3);
9528
+ const freshState = await readDraftState(ctx, { createMutationClient: deps.createClient });
9529
+ const result = publicGraphRun(status.run, status.drafts, freshState);
9530
+ printJsonOrValue(parsed, deps, result, () => printGraphRun(result, deps.print));
9531
+ return result;
9532
+ } finally {
9533
+ client.disconnect();
9534
+ }
9535
+ }
8498
9536
  async function buildContext$3(parsed, deps) {
8499
9537
  const project = await deps.loadProjectConfig();
8500
9538
  const env = resolveCommandEnvironment(parsed, project);
@@ -8655,29 +9693,7 @@ function rejectUnknownOptions(noun, subcommand, options) {
8655
9693
  "local",
8656
9694
  "json"
8657
9695
  ];
8658
- const commands = noun === "drafts" ? {
8659
- list: [],
8660
- create: [
8661
- "media",
8662
- "generator",
8663
- "recipe",
8664
- "destination",
8665
- "asset",
8666
- "x",
8667
- "y"
8668
- ],
8669
- update: [
8670
- "name",
8671
- "media",
8672
- "generator",
8673
- "recipe",
8674
- "destination",
8675
- "asset",
8676
- "x",
8677
- "y"
8678
- ],
8679
- delete: []
8680
- } : {
9696
+ const commands = noun === "drafts" ? CLI_DRAFT_OPTIONS : {
8681
9697
  set: [
8682
9698
  "source",
8683
9699
  "ref",
@@ -8689,6 +9705,50 @@ function rejectUnknownOptions(noun, subcommand, options) {
8689
9705
  const unknown = Object.keys(options).find((name) => !allowed.has(name));
8690
9706
  if (unknown) throw new Error(`Unknown option for ${noun} ${subcommand}: --${unknown}`);
8691
9707
  }
9708
+ function rejectGraphUnknownOptions(subcommand, options) {
9709
+ const allowed = new Set([...[
9710
+ "space",
9711
+ "env",
9712
+ "local",
9713
+ "json"
9714
+ ], ...subcommand === "run" ? [
9715
+ "upstream",
9716
+ "downstream",
9717
+ "from-scratch",
9718
+ "wait",
9719
+ "request-id"
9720
+ ] : []]);
9721
+ const unknown = Object.keys(options).find((name) => !allowed.has(name));
9722
+ if (unknown) throw new Error(`Unknown option for graph ${subcommand}: --${unknown}`);
9723
+ }
9724
+ function parseGraphWaitSeconds(value) {
9725
+ if (value === void 0 || value === "true") return 1800;
9726
+ const seconds = Number(value);
9727
+ if (!Number.isFinite(seconds) || seconds <= 0 || seconds > 3600) throw new Error("--wait must be between 1 and 3600 seconds");
9728
+ return seconds;
9729
+ }
9730
+ function publicGraphRun(run, drafts, state) {
9731
+ return {
9732
+ runRef: createGraphRunRef(run.id),
9733
+ requestId: run.request_id,
9734
+ status: run.status,
9735
+ drafts: drafts.map((item) => {
9736
+ const draft = state.drafts.find((candidate) => candidate.id === item.draft_id);
9737
+ const ancestor = state.drafts.find((candidate) => candidate.id === item.failing_ancestor_draft_id);
9738
+ return {
9739
+ draftRef: draft ? createDraftRef(draft.name, draft.id) : null,
9740
+ status: item.status,
9741
+ variantRef: item.variant_id ? variantRefForId(item.variant_id, state.assets, state.variants) : null,
9742
+ reused: item.reused,
9743
+ failingAncestorDraftRef: ancestor ? createDraftRef(ancestor.name, ancestor.id) : null
9744
+ };
9745
+ })
9746
+ };
9747
+ }
9748
+ function printGraphRun(result, print) {
9749
+ print(`${result.runRef} (${result.status})`);
9750
+ for (const draft of result.drafts) print(` ${draft.draftRef ?? "unavailable draft"}: ${draft.status}${draft.variantRef ? ` → ${draft.variantRef}` : ""}`);
9751
+ }
8692
9752
  function printJsonOrValue(parsed, deps, value, printHuman) {
8693
9753
  if (parsed.options.json === "true") deps.print(JSON.stringify(value, null, 2));
8694
9754
  else printHuman();
@@ -8700,6 +9760,20 @@ function printDrafts(drafts, print) {
8700
9760
  }
8701
9761
  for (const draft of drafts) print(`${draft.draftRef} ${draft.mediaMode} ${draft.status} ${draft.name}`);
8702
9762
  }
9763
+ function printDraft(draft, print) {
9764
+ print(`${draft.draftRef} ${draft.name}`);
9765
+ print(` Media: ${draft.mediaMode} (${draft.generatorId})`);
9766
+ print(` Destination: ${draft.destination}${draft.destinationAssetRef ? ` → ${draft.destinationAssetRef}` : ""}`);
9767
+ print(` Recipe: ${JSON.stringify(draft.recipeTemplate)}`);
9768
+ const bindings = draft.bindings;
9769
+ print(" Bindings:");
9770
+ if (bindings.length === 0) print(" none");
9771
+ else for (const binding of bindings) {
9772
+ const broken = binding.broken ? ` [broken${binding.brokenReason ? `: ${binding.brokenReason}` : ""}]` : "";
9773
+ print(` ${binding.slot}[${binding.sortIndex}]: ${binding.sourceKind} → ${binding.sourceRef ?? "unavailable"}${broken}`);
9774
+ }
9775
+ print(` Last run: ${draft.lastRunVariantRef ?? "none"}`);
9776
+ }
8703
9777
  //#endregion
8704
9778
  //#region src/cli/commands/image.ts
8705
9779
  async function handleImage(parsed) {
@@ -9240,7 +10314,7 @@ async function executeModels(parsed, deps = defaultDeps$5) {
9240
10314
  const { models } = await response.json();
9241
10315
  if (parsed.options.json === "true") deps.print(JSON.stringify(models, null, 2));
9242
10316
  else for (const model of models) {
9243
- const readiness = model.availability.available ? "ready" : model.availability.reason ?? "unavailable";
10317
+ const readiness = model.availability.available ? "ready" : [model.availability.code, model.availability.reason].filter(Boolean).join(": ") || "unavailable";
9244
10318
  deps.print(`${model.id.padEnd(20)} ${model.media_kind.padEnd(6)} ${readiness}`);
9245
10319
  }
9246
10320
  return models;
@@ -9263,6 +10337,7 @@ function formatModelDetail(model) {
9263
10337
  `Availability: ${model.availability.available ? "ready" : model.availability.reason ?? "unavailable"}`,
9264
10338
  "Operations:"
9265
10339
  ];
10340
+ if (!model.availability.available && model.availability.guidance) lines.splice(3, 0, `Next step: ${model.availability.guidance}`);
9266
10341
  for (const operation of model.operations) {
9267
10342
  if (typeof operation === "string") {
9268
10343
  lines.push(` - ${operation}`);
@@ -9749,6 +10824,33 @@ var defaultDeps$2 = {
9749
10824
  print: console.log
9750
10825
  };
9751
10826
  var UploadUsageError = class extends Error {};
10827
+ var LINEAGE_RELATION_TYPES = new Set([
10828
+ "derived",
10829
+ "refined",
10830
+ "forked"
10831
+ ]);
10832
+ function parseLineageOption(raw, defaultRelationType) {
10833
+ if (!raw) return [];
10834
+ const entries = raw.split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0);
10835
+ if (entries.length === 0) throw new UploadUsageError("--lineage requires at least one compact variant ref");
10836
+ return entries.map((entry) => {
10837
+ const atIndex = entry.lastIndexOf("@");
10838
+ const separator = entry.lastIndexOf(":");
10839
+ let ref = entry;
10840
+ let relationType = defaultRelationType;
10841
+ if (atIndex !== -1 && separator > atIndex) {
10842
+ const suffix = entry.slice(separator + 1);
10843
+ if (!LINEAGE_RELATION_TYPES.has(suffix)) throw new UploadUsageError(`--lineage relation in "${entry}" must be derived, refined, or forked`);
10844
+ ref = entry.slice(0, separator);
10845
+ relationType = suffix;
10846
+ }
10847
+ if (!ref.startsWith("asset:")) throw new UploadUsageError(`--lineage requires compact variant refs, got "${ref}"`);
10848
+ return {
10849
+ ref,
10850
+ relationType
10851
+ };
10852
+ });
10853
+ }
9752
10854
  async function handleUpload(parsed) {
9753
10855
  try {
9754
10856
  await executeUpload(parsed);
@@ -9773,6 +10875,7 @@ async function executeUpload(parsed, deps = defaultDeps$2) {
9773
10875
  const jsonOutput = parsed.options.json === "true";
9774
10876
  if (!spaceId) throw new UploadUsageError("--space is required, or run: makefx init --space <id>");
9775
10877
  if (!assetId && !assetName) throw new UploadUsageError("Either --asset or --name is required");
10878
+ const lineageEntries = parseLineageOption(parsed.options.lineage, assetId ? "refined" : "derived");
9776
10879
  const mediaType = resolveMediaType(path.extname(filePath).toLowerCase(), requestedMediaKind);
9777
10880
  const config = await deps.loadConfig(env);
9778
10881
  if (!config) throw new Error(`Not logged in to ${env} environment. Run: ${loginCommandForEnvironment(env)}`);
@@ -9780,7 +10883,16 @@ async function executeUpload(parsed, deps = defaultDeps$2) {
9780
10883
  const baseUrl = deps.resolveBaseUrl(env);
9781
10884
  const accessToken = config.token.accessToken;
9782
10885
  const createStateClient = () => (deps.createStateClient ?? defaultDeps$2.createStateClient)(env, spaceId);
9783
- if (assetId) assetId = await resolveCliAssetIdentifier(assetId, createStateClient);
10886
+ let lineage = [];
10887
+ if (assetId || lineageEntries.length > 0) {
10888
+ if (assetId && !assetId.startsWith("asset:")) throw new Error("A compact asset ref is required");
10889
+ const state = await readReferenceSpaceState(createStateClient);
10890
+ if (assetId) assetId = resolveAssetRef(assetId, state.assets);
10891
+ lineage = lineageEntries.map((entry) => ({
10892
+ parentVariantId: resolveVariantRef(entry.ref, state.assets, state.variants),
10893
+ relationType: entry.relationType
10894
+ }));
10895
+ }
9784
10896
  if (env === "local") process$1.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
9785
10897
  try {
9786
10898
  const fileStat = await deps.stat(filePath);
@@ -9798,11 +10910,13 @@ async function executeUpload(parsed, deps = defaultDeps$2) {
9798
10910
  formData.append("assetName", assetName);
9799
10911
  formData.append("assetType", assetType);
9800
10912
  }
10913
+ if (lineage.length > 0) formData.append("lineage", JSON.stringify(lineage));
9801
10914
  if (!jsonOutput) {
9802
10915
  deps.print(`\nUploading "${fileName}" to space ${spaceId}...`);
9803
10916
  deps.print(` Media kind: ${mediaType.mediaKind}`);
9804
10917
  if (assetId) deps.print(` Target asset: ${requestedAssetRef}`);
9805
10918
  else deps.print(` Creating asset: "${assetName}" (${assetType})`);
10919
+ for (const entry of lineageEntries) deps.print(` Lineage: ${entry.relationType} from ${entry.ref}`);
9806
10920
  }
9807
10921
  const response = await deps.fetch(`${baseUrl}/api/spaces/${spaceId}/upload`, {
9808
10922
  method: "POST",
@@ -9888,6 +11002,7 @@ function rejectUnknownUploadOptions(options) {
9888
11002
  "name",
9889
11003
  "type",
9890
11004
  "media-kind",
11005
+ "lineage",
9891
11006
  "json"
9892
11007
  ]);
9893
11008
  const unknown = Object.keys(options).find((name) => !allowed.has(name));
@@ -9905,6 +11020,9 @@ Options:
9905
11020
  --name <name> New asset name (creates asset + variant)
9906
11021
  --type <type> Asset type for new assets (default: character)
9907
11022
  --media-kind <k> Optional explicit kind: image, audio, or video
11023
+ --lineage <refs> Source variant refs this upload was made from, comma-separated,
11024
+ each optionally suffixed :derived, :refined, or :forked
11025
+ (default: refined with --asset, derived with --name)
9908
11026
  --json Print machine-readable output
9909
11027
  --env <env> Environment (production|stage|local)
9910
11028
  --local Shortcut for --env local
@@ -9912,6 +11030,7 @@ Options:
9912
11030
  Examples:
9913
11031
  makefx assets upload hero.png --name "Hero Character"
9914
11032
  makefx assets upload paintover.png --asset asset:hero~27f8f176
11033
+ makefx assets upload paintover.png --asset asset:hero~27f8f176 --lineage asset:hero~27f8f176@active
9915
11034
  `);
9916
11035
  }
9917
11036
  //#endregion
@@ -10317,15 +11436,47 @@ async function handleVideo(parsed) {
10317
11436
  }
10318
11437
  }
10319
11438
  //#endregion
10320
- //#region src/cli/index.ts
10321
- var CLI_VERSION = "1.4.0+d9f20045c1b1";
11439
+ //#region src/cli/help.ts
11440
+ var TOP_LEVEL_HELP = `Make Effects CLI
11441
+
11442
+ Creative:
11443
+ models list | show
11444
+ image generate | regenerate
11445
+ video generate | regenerate
11446
+ audio speech|dialogue|music|sfx generate|regenerate
11447
+ audio voices
11448
+ audio align
11449
+
11450
+ Assets:
11451
+ assets list | show | update | delete | upload | download
11452
+ variants show | update | activate | retry | delete
11453
+ collections list | show | create | update | delete | add | remove | pin | unpin
11454
+ drafts list | show | create | update | delete | run
11455
+ bindings set | clear
11456
+ graph run | status | cancel
11457
+
11458
+ Workspace:
11459
+ spaces list | show | create | delete | lens
11460
+ init | login | logout
11461
+ usage | spend | billing
11462
+
11463
+ Assets and variants use compact refs:
11464
+ asset:name~prefix
11465
+ asset:name~prefix@variant
11466
+
11467
+ Run makefx help <noun> for details.`;
10322
11468
  var HELP = {
10323
11469
  image: `Usage:
10324
- makefx image generate "prompt" --name <name> --type <type> -o <file> [--model pro|flash|flux] [--refs <variant-ref-or-file,...>]
11470
+ makefx image generate "prompt" --name <name> --type <type> -o <file> [--model pro|flash|flux] [--refs <variant-ref-or-file,...>] [--aspect <ratio>] [--size 1K|2K|4K] [--collection <id>] [--space <id>]
10325
11471
  makefx image regenerate <variant-ref> ["prompt"] [--model pro|flash|flux] [--aspect <ratio>] [--size <size>] [--refs <refs>] [--no-activate] [--wait]`,
10326
11472
  video: `Usage:
10327
11473
  makefx video generate "prompt" --name <name> --type <type> -o <file> [--model veo-3.1|omni-flash|kling|seedance-1|seedance-2|seedance-2-fast]
10328
- makefx video regenerate <variant-ref> ["prompt"] [--model <model>] [--aspect <ratio>] [--resolution <value>] [--duration <seconds>] [--no-activate] [--wait]
11474
+ [--refs <variant-ref-or-file,...>] [--first-frame <ref>] [--last-frame <ref>] [--image-refs <refs>] [--video-refs <refs>] [--audio-refs <refs>]
11475
+ [--aspect <ratio>] [--resolution 480p|720p|1080p|4k] [--duration <seconds|auto>] [--tier generate|fast|lite] [--bitrate standard|high]
11476
+ [--audio | --no-audio] [--collection <id>] [--space <id>]
11477
+ makefx video generate ["prompt"] --model kling-avatar-v2 --image-refs <portrait-ref> --audio-refs <audio-ref>
11478
+ --name <name> --type <type> -o <file> [--mode standard|pro] [--collection <id>] [--space <id>]
11479
+ makefx video regenerate <variant-ref> ["prompt"] [--model <model>] [--aspect <ratio>] [--resolution <value>] [--duration <seconds>] [--tier <tier>] [--bitrate standard|high] [--audio | --no-audio] [--image-refs <refs>] [--video-refs <refs>] [--audio-refs <refs>] [--no-activate] [--wait]
10329
11480
 
10330
11481
  Seedance mode is inferred: first/last frame selects frame mode, image/video/audio refs select reference mode, and no references selects text mode.`,
10331
11482
  audio: `Usage:
@@ -10339,7 +11490,9 @@ Seedance mode is inferred: first/last frame selects frame mode, image/video/audi
10339
11490
  makefx audio music generate "prompt" --model eleven-music|lyria-3 --name <name> -o <file>
10340
11491
  makefx audio music regenerate <variant-ref> ["prompt"] [--model eleven-music|lyria-3] [--no-activate] [--wait]
10341
11492
  makefx audio sfx generate "prompt" --name <name> -o <file>
10342
- makefx audio sfx regenerate <variant-ref> ["prompt"] [--no-activate] [--wait]`,
11493
+ makefx audio sfx regenerate <variant-ref> ["prompt"] [--no-activate] [--wait]
11494
+
11495
+ Generate commands also accept [--collection <id>] [--space <id>].`,
10343
11496
  models: `Usage:
10344
11497
  makefx models list [--media image|video|audio] [--available] [--json]
10345
11498
  makefx models show <model> [--json]`,
@@ -10348,8 +11501,12 @@ Seedance mode is inferred: first/last frame selects frame mode, image/video/audi
10348
11501
  makefx assets show <asset-ref> [--json]
10349
11502
  makefx assets update <asset-ref> [--name <name>] [--type <type>] [--tags <tag,...>]
10350
11503
  makefx assets delete <asset-ref> [--yes]
10351
- makefx assets upload <file> (--name <name> | --asset <asset-ref>) [--type <type>]
10352
- makefx assets download <variant-ref> -o <file>`,
11504
+ makefx assets upload <file> (--name <name> | --asset <asset-ref>) [--type <type>] [--lineage <variant-ref[:relation],...>]
11505
+ makefx assets download <variant-ref> -o <file>
11506
+
11507
+ Upload --lineage records which variants the file was made from: comma-separated
11508
+ compact variant refs, each optionally suffixed :derived, :refined, or :forked
11509
+ (default: refined when uploading to --asset, derived when creating with --name).`,
10353
11510
  variants: `Usage:
10354
11511
  makefx variants show <variant-ref> [--wait <seconds>] [--json]
10355
11512
  makefx variants update <variant-ref> [--starred true|false] [--rating approved|rejected|none]
@@ -10368,9 +11525,15 @@ Seedance mode is inferred: first/last frame selects frame mode, image/video/audi
10368
11525
  makefx collections unpin <collection-id>`,
10369
11526
  drafts: `Usage:
10370
11527
  makefx drafts list [--json]
10371
- makefx drafts create "<name>" --media <mode> --generator <id> --recipe <json> --destination new_asset|sibling [--asset <asset-ref>]
10372
- makefx drafts update <draft-ref> [--name <name>] [--recipe <json>] [--destination new_asset|sibling] [--asset <asset-ref|none>]
10373
- makefx drafts delete <draft-ref>`,
11528
+ makefx drafts show <draft-ref> [--json]
11529
+ makefx drafts create ("<name>" --media <mode> --generator <id> --recipe <json> | --from <variant-ref> [--name <name>]) [--destination new_asset|sibling] [--asset <asset-ref>] [--x <number>] [--y <number>]
11530
+ makefx drafts update <draft-ref> [--name <name>] [--media <mode>] [--generator <id>] [--recipe <json>] [--destination new_asset|sibling] [--asset <asset-ref|none>] [--x <number|null>] [--y <number|null>]
11531
+ makefx drafts delete <draft-ref>
11532
+ makefx drafts run <draft-ref> [--request-id <id>] [--wait [seconds]]`,
11533
+ graph: `Usage:
11534
+ makefx graph run <draft-ref> [draft-ref...] [--upstream] [--downstream] [--from-scratch] [--wait [seconds]]
11535
+ makefx graph status <graph-run-ref> [--json]
11536
+ makefx graph cancel <graph-run-ref> [--json]`,
10374
11537
  bindings: `Usage:
10375
11538
  makefx bindings set <draft-ref> <slot> --source variant|asset-active|draft-output --ref <ref> [--sort <index>]
10376
11539
  makefx bindings clear <draft-ref> <slot> [--sort <index>]`,
@@ -10391,34 +11554,11 @@ Seedance mode is inferred: first/last frame selects frame mode, image/video/audi
10391
11554
  makefx billing reconcile
10392
11555
  makefx billing retry-failed`
10393
11556
  };
11557
+ //#endregion
11558
+ //#region src/cli/index.ts
11559
+ var CLI_VERSION = "1.6.0+e2eab203b853";
10394
11560
  function printHelp() {
10395
- console.log(`Make Effects CLI
10396
-
10397
- Creative:
10398
- models list | show
10399
- image generate | regenerate
10400
- video generate | regenerate
10401
- audio speech|dialogue|music|sfx generate|regenerate
10402
- audio voices
10403
- audio align
10404
-
10405
- Assets:
10406
- assets list | show | update | delete | upload | download
10407
- variants show | update | activate | retry | delete
10408
- collections list | show | create | update | delete | add | remove | pin | unpin
10409
- drafts list | create | update | delete
10410
- bindings set | clear
10411
-
10412
- Workspace:
10413
- spaces list | show | create | delete | lens
10414
- init | login | logout
10415
- usage | spend | billing
10416
-
10417
- Assets and variants use compact refs:
10418
- asset:name~prefix
10419
- asset:name~prefix@variant
10420
-
10421
- Run makefx help <noun> for details.`);
11561
+ console.log(TOP_LEVEL_HELP);
10422
11562
  }
10423
11563
  function printCommandHelp(noun) {
10424
11564
  if (!noun) {
@@ -10449,6 +11589,7 @@ async function dispatchCommand(command, parsed) {
10449
11589
  case "collections": return handleCollections(parsed);
10450
11590
  case "drafts": return handleDrafts(parsed);
10451
11591
  case "bindings": return handleBindings(parsed);
11592
+ case "graph": return handleGraph(parsed);
10452
11593
  case "usage": return handleUsage(parsed);
10453
11594
  case "spend": return handleSpend(parsed);
10454
11595
  case "variants": return handleVariants(parsed);