makefx 1.5.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 +263 -40
  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.0
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;
@@ -4722,7 +4801,8 @@ var WebSocketClient = class WebSocketClient {
4722
4801
  success: variant.status === "completed",
4723
4802
  variant: variant.status === "completed" ? variant : void 0,
4724
4803
  error: variant.status === "failed" ? formatGenerationFailureMessage(variant.error_message, variant.provider_metadata) : void 0,
4725
- 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
4726
4806
  });
4727
4807
  }
4728
4808
  return handled;
@@ -5017,7 +5097,9 @@ var WebSocketClient = class WebSocketClient {
5017
5097
  videoDurationSeconds: params.videoDurationSeconds,
5018
5098
  videoTier: params.videoTier,
5019
5099
  seedanceDuration: params.seedanceDuration,
5020
- seedanceBitrateMode: params.seedanceBitrateMode
5100
+ seedanceBitrateMode: params.seedanceBitrateMode,
5101
+ avatarResolution: params.avatarResolution,
5102
+ avatarMode: params.avatarMode
5021
5103
  };
5022
5104
  try {
5023
5105
  this.send(message);
@@ -5870,6 +5952,16 @@ function readIsoBmffVideoMetadata(bytes) {
5870
5952
  return durationMs ? { durationMs } : {};
5871
5953
  }
5872
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
5873
5965
  //#region src/shared/seedance2Capabilities.ts
5874
5966
  var SEEDANCE_2_ASPECT_RATIOS = [
5875
5967
  "auto",
@@ -6381,6 +6473,20 @@ var MEDIA_OPERATION_MATRIX = [
6381
6473
  cliCommands: ["generate"],
6382
6474
  cliSupportsRefs: true
6383
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
+ },
6384
6490
  {
6385
6491
  mode: "speech",
6386
6492
  label: "Speech",
@@ -6621,7 +6727,8 @@ var CLI_GENERATION_MEDIA_OPTIONS = {
6621
6727
  "last-frame",
6622
6728
  "image-refs",
6623
6729
  "video-refs",
6624
- "audio-refs"
6730
+ "audio-refs",
6731
+ "mode"
6625
6732
  ]
6626
6733
  };
6627
6734
  function rejectUnknownGenerationOptions(options, mediaKind) {
@@ -6669,12 +6776,12 @@ function validateAudioModeRequiredVoiceOptions(options, mode) {
6669
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");
6670
6777
  }
6671
6778
  async function executeGenerate(parsed, ctx, client, deps, mediaKind, followOptions = {}) {
6672
- const prompt = getPrompt(parsed, "generate");
6779
+ const videoOptions = parseVideoGenerationOptions(parsed, mediaKind);
6780
+ const prompt = isAvatarModelId(videoOptions.model) ? parsed.positionals.join(" ").trim() : getPrompt(parsed, "generate");
6673
6781
  const outputPath = getOutputPath(parsed);
6674
6782
  const name = requireOption(parsed, "name");
6675
6783
  const assetType = requireOption(parsed, "type");
6676
6784
  const musicProvider = parseMusicProviderOption(parsed, mediaKind, assetType);
6677
- const videoOptions = parseVideoGenerationOptions(parsed, mediaKind);
6678
6785
  const effectiveVideoModel = videoOptions.model ?? getVideoGenerationModelForSelection();
6679
6786
  const seedanceCapability = mediaKind === "video" ? getSeedance2CapabilityByEndpoint(effectiveVideoModel) : void 0;
6680
6787
  const videoFrameRefs = parseVideoFrameReferenceOptions(parsed, "generate", mediaKind).refs;
@@ -6693,7 +6800,7 @@ async function executeGenerate(parsed, ctx, client, deps, mediaKind, followOptio
6693
6800
  ...deps,
6694
6801
  waitForReferenceVariant: (variant) => waitForReferenceVariant(client, variant)
6695
6802
  };
6696
- 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) : {
6697
6804
  refs: [],
6698
6805
  ids: []
6699
6806
  };
@@ -6847,6 +6954,10 @@ function parseSeedanceReferenceOptions(parsed) {
6847
6954
  };
6848
6955
  }
6849
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
+ }
6850
6961
  const capability = getSeedance2CapabilityByEndpoint(model);
6851
6962
  const total = options.imageRefs.length + options.videoRefs.length + options.audioRefs.length;
6852
6963
  if (!capability) {
@@ -6864,6 +6975,15 @@ function validateSeedanceReferenceOptions(options, model, command) {
6864
6975
  });
6865
6976
  if (error) throw new Error(error.message);
6866
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
+ }
6867
6987
  async function resolveSeedanceReferenceIds(options, ctx, deps, state, implicitVideoReferenceIds = []) {
6868
6988
  await preflightLocalSeedanceReferences({
6869
6989
  ...options,
@@ -7141,7 +7261,9 @@ function compactStartedVariantRef(started) {
7141
7261
  }
7142
7262
  function formatTerminalGenerationError(result) {
7143
7263
  const message = result.error || "Generation failed without a completed variant";
7144
- 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);
7145
7267
  }
7146
7268
  function throwGenerationWaitErrorIfNeeded(input, params) {
7147
7269
  if (!isGenerationWaitTimeout(input)) throw input;
@@ -7196,7 +7318,7 @@ function validateVideoAudioOptions(parsed, mediaKind) {
7196
7318
  const model = parseVideoModelOption(modelValue, videoTier, parsed);
7197
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");
7198
7320
  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.`);
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.`);
7200
7322
  }
7201
7323
  }
7202
7324
  function parseVideoAudioOptions(parsed, mediaKind) {
@@ -7211,6 +7333,7 @@ function parseVideoGenerationOptions(parsed, mediaKind) {
7211
7333
  const durationValue = readOptionalOption(parsed, "duration");
7212
7334
  const tierValue = readOptionalOption(parsed, "tier");
7213
7335
  const bitrateValue = readOptionalOption(parsed, "bitrate");
7336
+ const modeValue = readOptionalOption(parsed, "mode");
7214
7337
  const modelValue = mediaKind === "video" ? readOptionalOption(parsed, "model") : void 0;
7215
7338
  const aspectValue = mediaKind === "video" ? readOptionalOption(parsed, "aspect") : void 0;
7216
7339
  if (mediaKind !== "video" && resolutionValue === void 0 && durationValue === void 0 && tierValue === void 0 && aspectValue === void 0 && bitrateValue === void 0) return {};
@@ -7220,6 +7343,17 @@ function parseVideoGenerationOptions(parsed, mediaKind) {
7220
7343
  const videoTier = tierValue === void 0 ? void 0 : normalizeVideoGenerationTier(tierValue);
7221
7344
  if (tierValue !== void 0 && !videoTier) throw new Error("--tier must be generate, fast, or lite");
7222
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");
7223
7357
  const seedanceCapability = getSeedance2CapabilityByEndpoint(model);
7224
7358
  const seedanceDuration = durationValue === void 0 ? void 0 : durationValue === "auto" ? "auto" : normalizeVideoGenerationDurationSeconds(durationValue);
7225
7359
  const videoDurationSeconds = durationValue === void 0 || durationValue === "auto" ? void 0 : normalizeVideoGenerationDurationSeconds(durationValue);
@@ -7315,6 +7449,7 @@ function parseVideoModelOption(value, tier, parsed) {
7315
7449
  const normalized = normalizeCliOption(value);
7316
7450
  const effectiveTier = tier ?? "generate";
7317
7451
  if (!normalized) return getVideoGenerationModelForSelection(DEFAULT_VIDEO_MODEL_SELECTION, effectiveTier);
7452
+ if (isAvatarModelId(normalized)) return normalized;
7318
7453
  if (normalized === "seedance-2" || normalized === "seedance-2-fast") {
7319
7454
  const hasFrames = Boolean(parsed?.options["first-frame"] || parsed?.options["last-frame"]);
7320
7455
  const hasReferences = Boolean(parsed?.options.refs || parsed?.options["image-refs"] || parsed?.options["video-refs"] || parsed?.options["audio-refs"]);
@@ -7322,7 +7457,7 @@ function parseVideoModelOption(value, tier, parsed) {
7322
7457
  }
7323
7458
  const selection = normalizeVideoModelSelection(normalized === "seedance-1" ? "fal-seedance" : normalized);
7324
7459
  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");
7460
+ throw new Error("--model must be veo-3.1, omni-flash, kling, seedance-1, seedance-2, seedance-2-fast, or kling-avatar-v2");
7326
7461
  }
7327
7462
  function parseImageModelOption(value) {
7328
7463
  if (!value) return void 0;
@@ -7360,6 +7495,10 @@ function validateImageModelOperation(command, mediaKind, model) {
7360
7495
  function validateVideoModelOperation(command, mediaKind, model) {
7361
7496
  if (mediaKind !== "video") return;
7362
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
+ }
7363
7502
  if (isVideoOperationSupportedByModel(effectiveModel, command)) return;
7364
7503
  throw new Error(`--model ${getVideoModelSelectionForModel(effectiveModel) ?? effectiveModel} does not support video ${command}`);
7365
7504
  }
@@ -7799,14 +7938,14 @@ async function handleBilling(parsed) {
7799
7938
  }
7800
7939
  function printBillingHelp() {
7801
7940
  console.log(`
7802
- Billing Commands - Polar.sh Usage Sync
7941
+ Billing Commands - Platform Subscription Operations
7803
7942
 
7804
7943
  Usage:
7805
7944
  makefx billing <subcommand> [--env <environment>]
7806
7945
 
7807
7946
  Subcommands:
7808
7947
  status Show sync status (pending, failed, synced events)
7809
- check Run operational checks for workers, Polar meters, and sync health
7948
+ check Run operational checks for workers, Polar product, and sync health
7810
7949
  reconcile Compare local billable usage with Polar usage for one user
7811
7950
  retry-failed Reset failed events for retry (next cron will sync them)
7812
7951
 
@@ -7953,6 +8092,16 @@ function secondsSummary(seconds) {
7953
8092
  const remainingSeconds = seconds % 60;
7954
8093
  return remainingSeconds > 0 ? `${minutes}m ${remainingSeconds}s` : `${minutes}m`;
7955
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
+ }
7956
8105
  async function handleBillingOpsCheck(env) {
7957
8106
  console.log(`Running billing operational checks for ${env}...\n`);
7958
8107
  const workerChecks = await checkWorkerHealth(env);
@@ -7969,22 +8118,22 @@ async function handleBillingOpsCheck(env) {
7969
8118
  process$1.exitCode = 1;
7970
8119
  return;
7971
8120
  }
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
- }
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}`);
7988
8137
  const syncCheck = billingChecks.checks.syncHealth;
7989
8138
  console.log("\nSync health:");
7990
8139
  console.log(` ${statusLabel(syncCheck.status)} pending=${syncCheck.events.pending} failed=${syncCheck.events.failed} synced=${syncCheck.events.synced}`);
@@ -7993,13 +8142,15 @@ async function handleBillingOpsCheck(env) {
7993
8142
  const internalCheck = billingChecks.checks.internalUsers;
7994
8143
  console.log("\nInternal users:");
7995
8144
  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) {
8145
+ const resultStatus = resolveBillingOpsResult({
8146
+ workerStatuses: workerChecks.map((check) => check.status),
8147
+ billingStatus: billingChecks.status,
8148
+ webhookStatus: webhookCheck.status
8149
+ });
8150
+ if (resultStatus === "critical") {
8000
8151
  console.log("\nResult: CRITICAL - billing operations need attention.");
8001
8152
  process$1.exitCode = 1;
8002
- } 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.");
8003
8154
  else console.log("\nResult: OK - billing workers, meters, and sync health are ready.");
8004
8155
  }
8005
8156
  async function handleBillingReconcile(env, parsed) {
@@ -8537,7 +8688,6 @@ function imageGenerator(capability) {
8537
8688
  id: `image/${capability.selection}`,
8538
8689
  label: capability.label,
8539
8690
  mediaKind: "image",
8540
- provider: capability.provider === "gemini" ? "google_ai" : "fal",
8541
8691
  modelIds: [capability.modelId],
8542
8692
  defaultModelId: capability.modelId,
8543
8693
  operations,
@@ -8617,7 +8767,6 @@ function videoGenerator(selection) {
8617
8767
  id: `video/${selection}`,
8618
8768
  label: VIDEO_MODEL_LABELS[selection],
8619
8769
  mediaKind: "video",
8620
- provider: selection === "kling" ? "kling" : selection === "fal-seedance" ? "fal" : "google_ai",
8621
8770
  modelIds,
8622
8771
  defaultModelId: defaultModel,
8623
8772
  operations,
@@ -8714,7 +8863,6 @@ function seedanceVideoGenerator(capability) {
8714
8863
  id: capability.generatorId,
8715
8864
  label: capability.label,
8716
8865
  mediaKind: "video",
8717
- provider: "fal",
8718
8866
  modelIds: [capability.endpointId],
8719
8867
  defaultModelId: capability.endpointId,
8720
8868
  operations,
@@ -8743,6 +8891,72 @@ function seedanceVideoGenerator(capability) {
8743
8891
  ]
8744
8892
  };
8745
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
+ }
8746
8960
  function audioGenerator(input_) {
8747
8961
  const inputs = [
8748
8962
  SPACE_INPUT,
@@ -8758,7 +8972,6 @@ function audioGenerator(input_) {
8758
8972
  id: input_.id,
8759
8973
  label: input_.label,
8760
8974
  mediaKind: "audio",
8761
- provider: input_.provider,
8762
8975
  modelIds: [input_.modelId],
8763
8976
  defaultModelId: input_.modelId,
8764
8977
  operations: [{
@@ -8784,6 +8997,7 @@ function getGeneratorCatalog(overrides = {}) {
8784
8997
  ...Object.values(IMAGE_MODEL_CAPABILITIES).map(imageGenerator),
8785
8998
  ...VIDEO_MODEL_SELECTIONS.map(videoGenerator),
8786
8999
  ...SEEDANCE_2_SELECTIONS.map((selection) => seedanceVideoGenerator(SEEDANCE_2_CAPABILITIES[selection])),
9000
+ ...AVATAR_MODEL_IDS.map(avatarVideoGenerator),
8787
9001
  audioGenerator({
8788
9002
  id: "audio/elevenlabs-speech",
8789
9003
  label: "ElevenLabs Speech",
@@ -8863,6 +9077,11 @@ var DRAFT_RECIPE_INPUT_KEYS = {
8863
9077
  duration: "seedanceDuration",
8864
9078
  bitrate_mode: "seedanceBitrateMode"
8865
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
+ }
8866
9085
  var DRAFT_REFERENCE_FIELD = /(?:_variant_refs?|VariantRefs?)$/;
8867
9086
  /**
8868
9087
  * The one slot-naming convention used while authoring and resolving Bindings.
@@ -8883,6 +9102,7 @@ function generatorIdForDraftRecipe(mediaMode, recipe) {
8883
9102
  }
8884
9103
  if (mediaMode === "video") {
8885
9104
  const model = typeof recipe.model === "string" ? recipe.model : "veo-3.1";
9105
+ if (isAvatarModelId(model)) return `video/${model}`;
8886
9106
  const seedance = getSeedance2CapabilityByEndpoint(model);
8887
9107
  if (seedance) return seedance.generatorId;
8888
9108
  const selection = getVideoModelSelectionForModel(model);
@@ -8905,7 +9125,7 @@ function buildDraftMapping(args) {
8905
9125
  if (!operationDefinition) throw new Error(`${generatorId} does not support ${operation} drafts`);
8906
9126
  const recipeTemplate = { operation };
8907
9127
  for (const input of operationDefinition.inputs) {
8908
- const key = DRAFT_RECIPE_INPUT_KEYS[input.name];
9128
+ const key = draftRecipeInputKey(input.name, generatorId);
8909
9129
  if (!key) continue;
8910
9130
  const value = recipe[key];
8911
9131
  if (value !== void 0) recipeTemplate[key] = value;
@@ -10094,7 +10314,7 @@ async function executeModels(parsed, deps = defaultDeps$5) {
10094
10314
  const { models } = await response.json();
10095
10315
  if (parsed.options.json === "true") deps.print(JSON.stringify(models, null, 2));
10096
10316
  else for (const model of models) {
10097
- 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";
10098
10318
  deps.print(`${model.id.padEnd(20)} ${model.media_kind.padEnd(6)} ${readiness}`);
10099
10319
  }
10100
10320
  return models;
@@ -10117,6 +10337,7 @@ function formatModelDetail(model) {
10117
10337
  `Availability: ${model.availability.available ? "ready" : model.availability.reason ?? "unavailable"}`,
10118
10338
  "Operations:"
10119
10339
  ];
10340
+ if (!model.availability.available && model.availability.guidance) lines.splice(3, 0, `Next step: ${model.availability.guidance}`);
10120
10341
  for (const operation of model.operations) {
10121
10342
  if (typeof operation === "string") {
10122
10343
  lines.push(` - ${operation}`);
@@ -11253,6 +11474,8 @@ var HELP = {
11253
11474
  [--refs <variant-ref-or-file,...>] [--first-frame <ref>] [--last-frame <ref>] [--image-refs <refs>] [--video-refs <refs>] [--audio-refs <refs>]
11254
11475
  [--aspect <ratio>] [--resolution 480p|720p|1080p|4k] [--duration <seconds|auto>] [--tier generate|fast|lite] [--bitrate standard|high]
11255
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>]
11256
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]
11257
11480
 
11258
11481
  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 +11556,7 @@ compact variant refs, each optionally suffixed :derived, :refined, or :forked
11333
11556
  };
11334
11557
  //#endregion
11335
11558
  //#region src/cli/index.ts
11336
- var CLI_VERSION = "1.5.0+27ffb5049678";
11559
+ var CLI_VERSION = "1.6.0+e2eab203b853";
11337
11560
  function printHelp() {
11338
11561
  console.log(TOP_LEVEL_HELP);
11339
11562
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "makefx",
3
- "version": "1.5.0",
3
+ "version": "1.6.0",
4
4
  "description": "Command-line interface for AI-assisted game asset production with Make Effects.",
5
5
  "license": "MIT",
6
6
  "type": "module",