makefx 1.5.0 → 1.6.1

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 +278 -42
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -61,4 +61,4 @@ IDs, or runs. Retired commands and aliases are not redirected.
61
61
 
62
62
  Run `makefx --help` or `makefx <noun> --help` for current options.
63
63
 
64
- Version: 1.5.0
64
+ Version: 1.6.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 Make Effects 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 Make Effects Platform subscription at https://makefx.app/profile#billing, then try again."
664
+ };
665
+ if (code === "MANAGED_AI_UNAVAILABLE") return {
666
+ code,
667
+ message: "Managed generation is coming soon and cannot be selected yet.",
668
+ retryable: false,
669
+ remediation: "Connect your provider account and choose a supported model. Managed generation 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;
@@ -4358,6 +4437,16 @@ var import_websocket = /* @__PURE__ */ __toESM(require_websocket(), 1);
4358
4437
  require_websocket_server();
4359
4438
  var wrapper_default = import_websocket.default;
4360
4439
  //#endregion
4440
+ //#region src/cli/version.ts
4441
+ var CLI_VERSION = "1.6.1+00259d259e65";
4442
+ var CLI_VERSION_HEADER = "X-MakeFX-CLI-Version";
4443
+ function cliVersionHeaders() {
4444
+ return {
4445
+ [CLI_VERSION_HEADER]: CLI_VERSION,
4446
+ "User-Agent": `makefx/${CLI_VERSION}`
4447
+ };
4448
+ }
4449
+ //#endregion
4361
4450
  //#region src/cli/lib/websocket-client.ts
4362
4451
  /**
4363
4452
  * WebSocket Client for CLI
@@ -4444,7 +4533,10 @@ var WebSocketClient = class WebSocketClient {
4444
4533
  return new Promise((resolve, reject) => {
4445
4534
  const protocol = this.baseUrl.startsWith("https") ? "wss" : "ws";
4446
4535
  const url = `${protocol}://${this.baseUrl.replace(/^https?:\/\//, "")}/api/spaces/${this.spaceId}/ws`;
4447
- const wsOptions = { headers: { "Authorization": `Bearer ${this.accessToken}` } };
4536
+ const wsOptions = { headers: {
4537
+ "Authorization": `Bearer ${this.accessToken}`,
4538
+ ...cliVersionHeaders()
4539
+ } };
4448
4540
  if (this.env === "local" && protocol === "wss") wsOptions.agent = new https.Agent({ rejectUnauthorized: false });
4449
4541
  else if (this.env === "local" && protocol === "ws") wsOptions.agent = new http.Agent();
4450
4542
  this.ws = new wrapper_default(url, wsOptions);
@@ -4722,7 +4814,8 @@ var WebSocketClient = class WebSocketClient {
4722
4814
  success: variant.status === "completed",
4723
4815
  variant: variant.status === "completed" ? variant : void 0,
4724
4816
  error: variant.status === "failed" ? formatGenerationFailureMessage(variant.error_message, variant.provider_metadata) : void 0,
4725
- errorCode: variant.status === "failed" ? getGenerationFailureCode(variant.provider_metadata) : void 0
4817
+ errorCode: variant.status === "failed" ? getGenerationFailureCode(variant.provider_metadata) : void 0,
4818
+ errorProvider: variant.status === "failed" ? getGenerationFailureProvider(variant.provider_metadata) : void 0
4726
4819
  });
4727
4820
  }
4728
4821
  return handled;
@@ -5017,7 +5110,9 @@ var WebSocketClient = class WebSocketClient {
5017
5110
  videoDurationSeconds: params.videoDurationSeconds,
5018
5111
  videoTier: params.videoTier,
5019
5112
  seedanceDuration: params.seedanceDuration,
5020
- seedanceBitrateMode: params.seedanceBitrateMode
5113
+ seedanceBitrateMode: params.seedanceBitrateMode,
5114
+ avatarResolution: params.avatarResolution,
5115
+ avatarMode: params.avatarMode
5021
5116
  };
5022
5117
  try {
5023
5118
  this.send(message);
@@ -5870,6 +5965,16 @@ function readIsoBmffVideoMetadata(bytes) {
5870
5965
  return durationMs ? { durationMs } : {};
5871
5966
  }
5872
5967
  //#endregion
5968
+ //#region src/shared/avatarGenerationOptions.ts
5969
+ var AVATAR_MODEL_IDS = ["kling-avatar-v2"];
5970
+ var AVATAR_MODES = ["standard", "pro"];
5971
+ function isAvatarModelId(value) {
5972
+ return typeof value === "string" && AVATAR_MODEL_IDS.includes(value);
5973
+ }
5974
+ function isAvatarMode(value) {
5975
+ return typeof value === "string" && AVATAR_MODES.includes(value);
5976
+ }
5977
+ //#endregion
5873
5978
  //#region src/shared/seedance2Capabilities.ts
5874
5979
  var SEEDANCE_2_ASPECT_RATIOS = [
5875
5980
  "auto",
@@ -6381,6 +6486,20 @@ var MEDIA_OPERATION_MATRIX = [
6381
6486
  cliCommands: ["generate"],
6382
6487
  cliSupportsRefs: true
6383
6488
  },
6489
+ {
6490
+ mode: "avatar",
6491
+ label: "Avatar",
6492
+ shortLabel: "Avatar",
6493
+ mediaKind: "video",
6494
+ assetType: "avatar",
6495
+ promptNoun: "avatar video",
6496
+ inheritsReferenceAssetType: false,
6497
+ compatibleSlotMediaKinds: ["image", "audio"],
6498
+ supportsBatch: false,
6499
+ cliNamespace: null,
6500
+ cliCommands: [],
6501
+ cliSupportsRefs: false
6502
+ },
6384
6503
  {
6385
6504
  mode: "speech",
6386
6505
  label: "Speech",
@@ -6621,7 +6740,8 @@ var CLI_GENERATION_MEDIA_OPTIONS = {
6621
6740
  "last-frame",
6622
6741
  "image-refs",
6623
6742
  "video-refs",
6624
- "audio-refs"
6743
+ "audio-refs",
6744
+ "mode"
6625
6745
  ]
6626
6746
  };
6627
6747
  function rejectUnknownGenerationOptions(options, mediaKind) {
@@ -6669,12 +6789,12 @@ function validateAudioModeRequiredVoiceOptions(options, mode) {
6669
6789
  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");
6670
6790
  }
6671
6791
  async function executeGenerate(parsed, ctx, client, deps, mediaKind, followOptions = {}) {
6672
- const prompt = getPrompt(parsed, "generate");
6792
+ const videoOptions = parseVideoGenerationOptions(parsed, mediaKind);
6793
+ const prompt = isAvatarModelId(videoOptions.model) ? parsed.positionals.join(" ").trim() : getPrompt(parsed, "generate");
6673
6794
  const outputPath = getOutputPath(parsed);
6674
6795
  const name = requireOption(parsed, "name");
6675
6796
  const assetType = requireOption(parsed, "type");
6676
6797
  const musicProvider = parseMusicProviderOption(parsed, mediaKind, assetType);
6677
- const videoOptions = parseVideoGenerationOptions(parsed, mediaKind);
6678
6798
  const effectiveVideoModel = videoOptions.model ?? getVideoGenerationModelForSelection();
6679
6799
  const seedanceCapability = mediaKind === "video" ? getSeedance2CapabilityByEndpoint(effectiveVideoModel) : void 0;
6680
6800
  const videoFrameRefs = parseVideoFrameReferenceOptions(parsed, "generate", mediaKind).refs;
@@ -6693,7 +6813,7 @@ async function executeGenerate(parsed, ctx, client, deps, mediaKind, followOptio
6693
6813
  ...deps,
6694
6814
  waitForReferenceVariant: (variant) => waitForReferenceVariant(client, variant)
6695
6815
  };
6696
- const resolvedSeedanceRefs = state && seedanceReferenceCount > 0 ? await resolveSeedanceReferenceIds(seedanceRefs, ctx, referenceDeps, state) : {
6816
+ const resolvedSeedanceRefs = state && seedanceReferenceCount > 0 ? isAvatarModelId(effectiveVideoModel) ? await resolveAvatarReferenceIds(seedanceRefs, ctx, referenceDeps, state) : await resolveSeedanceReferenceIds(seedanceRefs, ctx, referenceDeps, state) : {
6697
6817
  refs: [],
6698
6818
  ids: []
6699
6819
  };
@@ -6847,6 +6967,10 @@ function parseSeedanceReferenceOptions(parsed) {
6847
6967
  };
6848
6968
  }
6849
6969
  function validateSeedanceReferenceOptions(options, model, command) {
6970
+ if (isAvatarModelId(model)) {
6971
+ 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");
6972
+ return;
6973
+ }
6850
6974
  const capability = getSeedance2CapabilityByEndpoint(model);
6851
6975
  const total = options.imageRefs.length + options.videoRefs.length + options.audioRefs.length;
6852
6976
  if (!capability) {
@@ -6864,6 +6988,15 @@ function validateSeedanceReferenceOptions(options, model, command) {
6864
6988
  });
6865
6989
  if (error) throw new Error(error.message);
6866
6990
  }
6991
+ async function resolveAvatarReferenceIds(options, ctx, deps, state) {
6992
+ const resolve = (refs, kind) => resolveReferenceVariantIds(refs, ctx, deps, state.variants, "video", state.assets, kind);
6993
+ const imageIds = await resolve(options.imageRefs, "image");
6994
+ const audioIds = await resolve(options.audioRefs, "audio");
6995
+ return {
6996
+ refs: [...options.imageRefs, ...options.audioRefs],
6997
+ ids: [...imageIds, ...audioIds]
6998
+ };
6999
+ }
6867
7000
  async function resolveSeedanceReferenceIds(options, ctx, deps, state, implicitVideoReferenceIds = []) {
6868
7001
  await preflightLocalSeedanceReferences({
6869
7002
  ...options,
@@ -7141,7 +7274,9 @@ function compactStartedVariantRef(started) {
7141
7274
  }
7142
7275
  function formatTerminalGenerationError(result) {
7143
7276
  const message = result.error || "Generation failed without a completed variant";
7144
- return result.errorCode ? `${result.errorCode}: ${message}` : message;
7277
+ if (!result.errorCode) return message;
7278
+ const blocker = getGenerationBlockerPresentation(result.errorCode, message, generationBlockerProviderFromService(result.errorProvider));
7279
+ return blocker ? `${blocker.code}: ${blocker.message}\n${blocker.remediation}` : formatGenerationBlockerForTerminal(result.errorCode, message);
7145
7280
  }
7146
7281
  function throwGenerationWaitErrorIfNeeded(input, params) {
7147
7282
  if (!isGenerationWaitTimeout(input)) throw input;
@@ -7196,7 +7331,7 @@ function validateVideoAudioOptions(parsed, mediaKind) {
7196
7331
  const model = parseVideoModelOption(modelValue, videoTier, parsed);
7197
7332
  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");
7198
7333
  if (parsed.options["no-audio"] !== void 0) {
7199
- if (!doesVideoGenerationModelSupportAudioToggle(model) && !isFalSeedance2VideoGenerationModel(model)) throw new Error(`${model} does not support --no-audio. Use the default audio-enabled output or omit the flag.`);
7334
+ 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.`);
7200
7335
  }
7201
7336
  }
7202
7337
  function parseVideoAudioOptions(parsed, mediaKind) {
@@ -7211,6 +7346,7 @@ function parseVideoGenerationOptions(parsed, mediaKind) {
7211
7346
  const durationValue = readOptionalOption(parsed, "duration");
7212
7347
  const tierValue = readOptionalOption(parsed, "tier");
7213
7348
  const bitrateValue = readOptionalOption(parsed, "bitrate");
7349
+ const modeValue = readOptionalOption(parsed, "mode");
7214
7350
  const modelValue = mediaKind === "video" ? readOptionalOption(parsed, "model") : void 0;
7215
7351
  const aspectValue = mediaKind === "video" ? readOptionalOption(parsed, "aspect") : void 0;
7216
7352
  if (mediaKind !== "video" && resolutionValue === void 0 && durationValue === void 0 && tierValue === void 0 && aspectValue === void 0 && bitrateValue === void 0) return {};
@@ -7220,6 +7356,17 @@ function parseVideoGenerationOptions(parsed, mediaKind) {
7220
7356
  const videoTier = tierValue === void 0 ? void 0 : normalizeVideoGenerationTier(tierValue);
7221
7357
  if (tierValue !== void 0 && !videoTier) throw new Error("--tier must be generate, fast, or lite");
7222
7358
  const model = parseVideoModelOption(modelValue, videoTier, parsed);
7359
+ if (isAvatarModelId(model)) {
7360
+ 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");
7361
+ 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");
7362
+ if (resolutionValue !== void 0) throw new Error("--resolution is not supported by Avatar models");
7363
+ if (modeValue !== void 0 && !isAvatarMode(modeValue)) throw new Error(`--mode must be ${AVATAR_MODES.join(" or ")}`);
7364
+ return {
7365
+ model,
7366
+ ...modeValue ? { avatarMode: modeValue } : {}
7367
+ };
7368
+ }
7369
+ if (modeValue !== void 0) throw new Error("--mode is only supported by --model kling-avatar-v2");
7223
7370
  const seedanceCapability = getSeedance2CapabilityByEndpoint(model);
7224
7371
  const seedanceDuration = durationValue === void 0 ? void 0 : durationValue === "auto" ? "auto" : normalizeVideoGenerationDurationSeconds(durationValue);
7225
7372
  const videoDurationSeconds = durationValue === void 0 || durationValue === "auto" ? void 0 : normalizeVideoGenerationDurationSeconds(durationValue);
@@ -7315,6 +7462,7 @@ function parseVideoModelOption(value, tier, parsed) {
7315
7462
  const normalized = normalizeCliOption(value);
7316
7463
  const effectiveTier = tier ?? "generate";
7317
7464
  if (!normalized) return getVideoGenerationModelForSelection(DEFAULT_VIDEO_MODEL_SELECTION, effectiveTier);
7465
+ if (isAvatarModelId(normalized)) return normalized;
7318
7466
  if (normalized === "seedance-2" || normalized === "seedance-2-fast") {
7319
7467
  const hasFrames = Boolean(parsed?.options["first-frame"] || parsed?.options["last-frame"]);
7320
7468
  const hasReferences = Boolean(parsed?.options.refs || parsed?.options["image-refs"] || parsed?.options["video-refs"] || parsed?.options["audio-refs"]);
@@ -7322,7 +7470,7 @@ function parseVideoModelOption(value, tier, parsed) {
7322
7470
  }
7323
7471
  const selection = normalizeVideoModelSelection(normalized === "seedance-1" ? "fal-seedance" : normalized);
7324
7472
  if (selection) return getVideoGenerationModelForSelection(selection, effectiveTier);
7325
- throw new Error("--model must be veo-3.1, omni-flash, kling, seedance-1, seedance-2, or seedance-2-fast");
7473
+ throw new Error("--model must be veo-3.1, omni-flash, kling, seedance-1, seedance-2, seedance-2-fast, or kling-avatar-v2");
7326
7474
  }
7327
7475
  function parseImageModelOption(value) {
7328
7476
  if (!value) return void 0;
@@ -7360,6 +7508,10 @@ function validateImageModelOperation(command, mediaKind, model) {
7360
7508
  function validateVideoModelOperation(command, mediaKind, model) {
7361
7509
  if (mediaKind !== "video") return;
7362
7510
  const effectiveModel = model ?? getVideoGenerationModelForSelection();
7511
+ if (isAvatarModelId(effectiveModel)) {
7512
+ if (command === "generate") return;
7513
+ throw new Error(`--model ${effectiveModel} does not support video ${command}`);
7514
+ }
7363
7515
  if (isVideoOperationSupportedByModel(effectiveModel, command)) return;
7364
7516
  throw new Error(`--model ${getVideoModelSelectionForModel(effectiveModel) ?? effectiveModel} does not support video ${command}`);
7365
7517
  }
@@ -7435,7 +7587,8 @@ async function executeRegenerate(mediaKind, parsed, deps = defaultDeps$11, audio
7435
7587
  headers: {
7436
7588
  Authorization: `Bearer ${config.token.accessToken}`,
7437
7589
  Accept: "application/json",
7438
- "Content-Type": "application/json"
7590
+ "Content-Type": "application/json",
7591
+ ...cliVersionHeaders()
7439
7592
  },
7440
7593
  body: JSON.stringify(body)
7441
7594
  });
@@ -7799,14 +7952,14 @@ async function handleBilling(parsed) {
7799
7952
  }
7800
7953
  function printBillingHelp() {
7801
7954
  console.log(`
7802
- Billing Commands - Polar.sh Usage Sync
7955
+ Billing Commands - Platform Subscription Operations
7803
7956
 
7804
7957
  Usage:
7805
7958
  makefx billing <subcommand> [--env <environment>]
7806
7959
 
7807
7960
  Subcommands:
7808
7961
  status Show sync status (pending, failed, synced events)
7809
- check Run operational checks for workers, Polar meters, and sync health
7962
+ check Run operational checks for workers, Polar product, and sync health
7810
7963
  reconcile Compare local billable usage with Polar usage for one user
7811
7964
  retry-failed Reset failed events for retry (next cron will sync them)
7812
7965
 
@@ -7953,6 +8106,16 @@ function secondsSummary(seconds) {
7953
8106
  const remainingSeconds = seconds % 60;
7954
8107
  return remainingSeconds > 0 ? `${minutes}m ${remainingSeconds}s` : `${minutes}m`;
7955
8108
  }
8109
+ function resolveBillingOpsResult(options) {
8110
+ const statuses = [
8111
+ ...options.workerStatuses,
8112
+ options.billingStatus,
8113
+ options.webhookStatus
8114
+ ];
8115
+ if (statuses.includes("critical")) return "critical";
8116
+ if (statuses.includes("warning")) return "warning";
8117
+ return "ok";
8118
+ }
7956
8119
  async function handleBillingOpsCheck(env) {
7957
8120
  console.log(`Running billing operational checks for ${env}...\n`);
7958
8121
  const workerChecks = await checkWorkerHealth(env);
@@ -7969,22 +8132,22 @@ async function handleBillingOpsCheck(env) {
7969
8132
  process$1.exitCode = 1;
7970
8133
  return;
7971
8134
  }
7972
- const meterCheck = billingChecks.checks.polarMeters;
7973
- console.log("\nPolar meters:");
7974
- console.log(` ${statusLabel(meterCheck.status)} configured=${meterCheck.configured} active=${meterCheck.active.length} expected=${meterCheck.expected.length}`);
7975
- if (meterCheck.error) console.log(` Error: ${meterCheck.error}`);
7976
- if (meterCheck.missing.length > 0) console.log(` Missing: ${meterCheck.missing.join(", ")}`);
7977
- if (meterCheck.invalid.length > 0) console.log(` Invalid: ${meterCheck.invalid.map((meter) => meter.name).join(", ")}`);
7978
- const productCheck = billingChecks.checks.paidGenerationProduct;
7979
- if (productCheck) {
7980
- console.log("\nPaid generation product:");
7981
- const product = productCheck.product;
7982
- console.log(` ${statusLabel(productCheck.status)} configured=${product?.configured ?? false} recurring=${product?.isRecurring ?? "n/a"} archived=${product?.isArchived ?? "n/a"}`);
7983
- if (product?.productId) console.log(` Product: ${product.productId}${product.name ? ` (${product.name})` : ""}`);
7984
- console.log(` Metered prices: ${product?.meteredPriceMeters.length ?? 0}/${productCheck.expectedMeteredPriceMeters.length}`);
7985
- if (productCheck.missingMeteredPriceMeters.length > 0) console.log(` Missing metered prices: ${productCheck.missingMeteredPriceMeters.join(", ")}`);
7986
- if (productCheck.unexpectedMeterCreditBenefitMeters.length > 0) console.log(` Unexpected meter-credit benefits: ${productCheck.unexpectedMeterCreditBenefitMeters.join(", ")}`);
7987
- }
8135
+ const productCheck = billingChecks.checks.platformSubscriptionProduct;
8136
+ console.log("\nPlatform subscription product:");
8137
+ const product = productCheck.product;
8138
+ console.log(` ${statusLabel(productCheck.status)} configured=${product?.configured ?? false} recurring=${product?.isRecurring ?? "n/a"} archived=${product?.isArchived ?? "n/a"}`);
8139
+ console.log(` Environment: ${productCheck.actualEnvironment} (expected ${productCheck.expectedEnvironment})`);
8140
+ if (product?.productId) console.log(` Product: ${product.productId}${product.name ? ` (${product.name})` : ""}`);
8141
+ if (product?.fixedPrices.length) console.log(` Fixed price: ${product.fixedPrices.map((price) => `${price.amount} ${price.currency}/${price.interval ?? "one-time"}`).join(", ")}`);
8142
+ for (const error of productCheck.errors) console.log(` Error: ${error}`);
8143
+ const webhookCheck = billingChecks.checks.webhook ?? {
8144
+ status: "critical",
8145
+ configured: false,
8146
+ error: "Webhook readiness was not reported by the application Worker"
8147
+ };
8148
+ console.log("\nWebhook verifier:");
8149
+ console.log(` ${statusLabel(webhookCheck.status)} configured=${webhookCheck.configured}`);
8150
+ if (webhookCheck.error) console.log(` Error: ${webhookCheck.error}`);
7988
8151
  const syncCheck = billingChecks.checks.syncHealth;
7989
8152
  console.log("\nSync health:");
7990
8153
  console.log(` ${statusLabel(syncCheck.status)} pending=${syncCheck.events.pending} failed=${syncCheck.events.failed} synced=${syncCheck.events.synced}`);
@@ -7993,13 +8156,15 @@ async function handleBillingOpsCheck(env) {
7993
8156
  const internalCheck = billingChecks.checks.internalUsers;
7994
8157
  console.log("\nInternal users:");
7995
8158
  console.log(` ${statusLabel(internalCheck.status)} users=${internalCheck.internalUsers} nonBillableEvents=${internalCheck.nonBillableEvents} billableEvents=${internalCheck.billableEvents}`);
7996
- const hasCriticalWorker = workerChecks.some((check) => check.status === "critical");
7997
- const hasCriticalBilling = billingChecks.status === "critical";
7998
- const hasWarning = workerChecks.some((check) => check.status === "warning") || billingChecks.status === "warning";
7999
- if (hasCriticalWorker || hasCriticalBilling) {
8159
+ const resultStatus = resolveBillingOpsResult({
8160
+ workerStatuses: workerChecks.map((check) => check.status),
8161
+ billingStatus: billingChecks.status,
8162
+ webhookStatus: webhookCheck.status
8163
+ });
8164
+ if (resultStatus === "critical") {
8000
8165
  console.log("\nResult: CRITICAL - billing operations need attention.");
8001
8166
  process$1.exitCode = 1;
8002
- } else if (hasWarning) console.log("\nResult: WARNING - billing is operational but has lag or cleanup work.");
8167
+ } else if (resultStatus === "warning") console.log("\nResult: WARNING - billing is operational but has lag or cleanup work.");
8003
8168
  else console.log("\nResult: OK - billing workers, meters, and sync health are ready.");
8004
8169
  }
8005
8170
  async function handleBillingReconcile(env, parsed) {
@@ -8537,7 +8702,6 @@ function imageGenerator(capability) {
8537
8702
  id: `image/${capability.selection}`,
8538
8703
  label: capability.label,
8539
8704
  mediaKind: "image",
8540
- provider: capability.provider === "gemini" ? "google_ai" : "fal",
8541
8705
  modelIds: [capability.modelId],
8542
8706
  defaultModelId: capability.modelId,
8543
8707
  operations,
@@ -8617,7 +8781,6 @@ function videoGenerator(selection) {
8617
8781
  id: `video/${selection}`,
8618
8782
  label: VIDEO_MODEL_LABELS[selection],
8619
8783
  mediaKind: "video",
8620
- provider: selection === "kling" ? "kling" : selection === "fal-seedance" ? "fal" : "google_ai",
8621
8784
  modelIds,
8622
8785
  defaultModelId: defaultModel,
8623
8786
  operations,
@@ -8714,7 +8877,6 @@ function seedanceVideoGenerator(capability) {
8714
8877
  id: capability.generatorId,
8715
8878
  label: capability.label,
8716
8879
  mediaKind: "video",
8717
- provider: "fal",
8718
8880
  modelIds: [capability.endpointId],
8719
8881
  defaultModelId: capability.endpointId,
8720
8882
  operations,
@@ -8743,6 +8905,72 @@ function seedanceVideoGenerator(capability) {
8743
8905
  ]
8744
8906
  };
8745
8907
  }
8908
+ function avatarVideoGenerator(model) {
8909
+ const parameters = [input("mode", "string", false, "Generation quality.", {
8910
+ allowedValues: AVATAR_MODES,
8911
+ defaultValue: "standard"
8912
+ })];
8913
+ return {
8914
+ id: `video/${model}`,
8915
+ label: "Kling Avatar V2",
8916
+ mediaKind: "video",
8917
+ modelIds: [model],
8918
+ defaultModelId: model,
8919
+ operations: [{
8920
+ operation: "derive",
8921
+ tool: "generate_video",
8922
+ description: "Create an Avatar video from one portrait image and one audio Variant.",
8923
+ inputs: [
8924
+ SPACE_INPUT,
8925
+ fixedInput("generator_id", `video/${model}`, "Selects this Avatar generator."),
8926
+ NAME_INPUT,
8927
+ input("asset_type", "string", true, "Asset classification stored in the Space."),
8928
+ input("prompt", "string", false, "Optional motion and expression guidance."),
8929
+ ...parameters,
8930
+ input("image_reference_variant_refs", "string_array", true, "Exactly one completed portrait image Variant.", {
8931
+ minItems: 1,
8932
+ maxItems: 1
8933
+ }),
8934
+ input("audio_reference_variant_refs", "string_array", true, "Exactly one completed audio Variant.", {
8935
+ minItems: 1,
8936
+ maxItems: 1
8937
+ })
8938
+ ]
8939
+ }],
8940
+ referenceRules: {
8941
+ mediaKind: null,
8942
+ completedOnly: true,
8943
+ maxCount: 2,
8944
+ maxTotalCount: 2,
8945
+ modalities: [{
8946
+ mediaKind: "image",
8947
+ minCount: 1,
8948
+ maxCount: 1,
8949
+ promptLabel: "Portrait",
8950
+ acceptedMimeTypes: [
8951
+ "image/jpeg",
8952
+ "image/png",
8953
+ "image/webp"
8954
+ ],
8955
+ maxBytesPerFile: 10 * 1024 * 1024
8956
+ }, {
8957
+ mediaKind: "audio",
8958
+ minCount: 1,
8959
+ maxCount: 1,
8960
+ promptLabel: "Audio",
8961
+ acceptedMimeTypes: [
8962
+ "audio/mpeg",
8963
+ "audio/wav",
8964
+ "audio/x-wav",
8965
+ "audio/mp4",
8966
+ "audio/aac"
8967
+ ],
8968
+ maxBytesPerFile: 5 * 1024 * 1024
8969
+ }]
8970
+ },
8971
+ notes: ["Outputs an ordinary video Variant. Provider routing remains internal."]
8972
+ };
8973
+ }
8746
8974
  function audioGenerator(input_) {
8747
8975
  const inputs = [
8748
8976
  SPACE_INPUT,
@@ -8758,7 +8986,6 @@ function audioGenerator(input_) {
8758
8986
  id: input_.id,
8759
8987
  label: input_.label,
8760
8988
  mediaKind: "audio",
8761
- provider: input_.provider,
8762
8989
  modelIds: [input_.modelId],
8763
8990
  defaultModelId: input_.modelId,
8764
8991
  operations: [{
@@ -8784,6 +9011,7 @@ function getGeneratorCatalog(overrides = {}) {
8784
9011
  ...Object.values(IMAGE_MODEL_CAPABILITIES).map(imageGenerator),
8785
9012
  ...VIDEO_MODEL_SELECTIONS.map(videoGenerator),
8786
9013
  ...SEEDANCE_2_SELECTIONS.map((selection) => seedanceVideoGenerator(SEEDANCE_2_CAPABILITIES[selection])),
9014
+ ...AVATAR_MODEL_IDS.map(avatarVideoGenerator),
8787
9015
  audioGenerator({
8788
9016
  id: "audio/elevenlabs-speech",
8789
9017
  label: "ElevenLabs Speech",
@@ -8863,6 +9091,11 @@ var DRAFT_RECIPE_INPUT_KEYS = {
8863
9091
  duration: "seedanceDuration",
8864
9092
  bitrate_mode: "seedanceBitrateMode"
8865
9093
  };
9094
+ function draftRecipeInputKey(inputName, generatorId) {
9095
+ if (generatorId === "video/p-video-avatar" && inputName === "resolution") return "avatarResolution";
9096
+ if (generatorId === "video/kling-avatar-v2" && inputName === "mode") return "avatarMode";
9097
+ return DRAFT_RECIPE_INPUT_KEYS[inputName];
9098
+ }
8866
9099
  var DRAFT_REFERENCE_FIELD = /(?:_variant_refs?|VariantRefs?)$/;
8867
9100
  /**
8868
9101
  * The one slot-naming convention used while authoring and resolving Bindings.
@@ -8883,6 +9116,7 @@ function generatorIdForDraftRecipe(mediaMode, recipe) {
8883
9116
  }
8884
9117
  if (mediaMode === "video") {
8885
9118
  const model = typeof recipe.model === "string" ? recipe.model : "veo-3.1";
9119
+ if (isAvatarModelId(model)) return `video/${model}`;
8886
9120
  const seedance = getSeedance2CapabilityByEndpoint(model);
8887
9121
  if (seedance) return seedance.generatorId;
8888
9122
  const selection = getVideoModelSelectionForModel(model);
@@ -8905,7 +9139,7 @@ function buildDraftMapping(args) {
8905
9139
  if (!operationDefinition) throw new Error(`${generatorId} does not support ${operation} drafts`);
8906
9140
  const recipeTemplate = { operation };
8907
9141
  for (const input of operationDefinition.inputs) {
8908
- const key = DRAFT_RECIPE_INPUT_KEYS[input.name];
9142
+ const key = draftRecipeInputKey(input.name, generatorId);
8909
9143
  if (!key) continue;
8910
9144
  const value = recipe[key];
8911
9145
  if (value !== void 0) recipeTemplate[key] = value;
@@ -10094,7 +10328,7 @@ async function executeModels(parsed, deps = defaultDeps$5) {
10094
10328
  const { models } = await response.json();
10095
10329
  if (parsed.options.json === "true") deps.print(JSON.stringify(models, null, 2));
10096
10330
  else for (const model of models) {
10097
- const readiness = model.availability.available ? "ready" : model.availability.reason ?? "unavailable";
10331
+ const readiness = model.availability.available ? "ready" : [model.availability.code, model.availability.reason].filter(Boolean).join(": ") || "unavailable";
10098
10332
  deps.print(`${model.id.padEnd(20)} ${model.media_kind.padEnd(6)} ${readiness}`);
10099
10333
  }
10100
10334
  return models;
@@ -10117,6 +10351,7 @@ function formatModelDetail(model) {
10117
10351
  `Availability: ${model.availability.available ? "ready" : model.availability.reason ?? "unavailable"}`,
10118
10352
  "Operations:"
10119
10353
  ];
10354
+ if (!model.availability.available && model.availability.guidance) lines.splice(3, 0, `Next step: ${model.availability.guidance}`);
10120
10355
  for (const operation of model.operations) {
10121
10356
  if (typeof operation === "string") {
10122
10357
  lines.push(` - ${operation}`);
@@ -11253,6 +11488,8 @@ var HELP = {
11253
11488
  [--refs <variant-ref-or-file,...>] [--first-frame <ref>] [--last-frame <ref>] [--image-refs <refs>] [--video-refs <refs>] [--audio-refs <refs>]
11254
11489
  [--aspect <ratio>] [--resolution 480p|720p|1080p|4k] [--duration <seconds|auto>] [--tier generate|fast|lite] [--bitrate standard|high]
11255
11490
  [--audio | --no-audio] [--collection <id>] [--space <id>]
11491
+ makefx video generate ["prompt"] --model kling-avatar-v2 --image-refs <portrait-ref> --audio-refs <audio-ref>
11492
+ --name <name> --type <type> -o <file> [--mode standard|pro] [--collection <id>] [--space <id>]
11256
11493
  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]
11257
11494
 
11258
11495
  Seedance mode is inferred: first/last frame selects frame mode, image/video/audio refs select reference mode, and no references selects text mode.`,
@@ -11333,7 +11570,6 @@ compact variant refs, each optionally suffixed :derived, :refined, or :forked
11333
11570
  };
11334
11571
  //#endregion
11335
11572
  //#region src/cli/index.ts
11336
- var CLI_VERSION = "1.5.0+27ffb5049678";
11337
11573
  function printHelp() {
11338
11574
  console.log(TOP_LEVEL_HELP);
11339
11575
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "makefx",
3
- "version": "1.5.0",
3
+ "version": "1.6.1",
4
4
  "description": "Command-line interface for AI-assisted game asset production with Make Effects.",
5
5
  "license": "MIT",
6
6
  "type": "module",