makefx 1.0.6 → 1.0.7

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 +658 -149
  3. package/package.json +1 -1
package/makefx.mjs CHANGED
@@ -844,7 +844,7 @@ function validateProjectConfig(config, configPath) {
844
844
  * makefx spaces inspect-deleted <space_id> Inspect a soft-deleted space
845
845
  * makefx spaces restore <space_id> Restore a soft-deleted space
846
846
  */
847
- var defaultDeps$9 = {
847
+ var defaultDeps$10 = {
848
848
  loadConfig: loadStoredConfig,
849
849
  resolveBaseUrl,
850
850
  saveProjectConfig,
@@ -859,7 +859,7 @@ async function handleSpaces(parsed) {
859
859
  process$1.exitCode = 1;
860
860
  }
861
861
  }
862
- async function executeSpaces(parsed, deps = defaultDeps$9) {
862
+ async function executeSpaces(parsed, deps = defaultDeps$10) {
863
863
  const env = resolveCommandEnvironment(parsed);
864
864
  const showDetails = parsed.options.details === "true";
865
865
  const spaceId = parsed.options.id;
@@ -5386,7 +5386,7 @@ function isMirrorEntry(value) {
5386
5386
  * makefx upload <file> --space <id> --name <name> Upload as new asset
5387
5387
  * makefx upload <file> --space <id> --name <name> --type <type>
5388
5388
  */
5389
- var defaultDeps$8 = {
5389
+ var defaultDeps$9 = {
5390
5390
  loadConfig: loadStoredConfig,
5391
5391
  loadProjectConfig,
5392
5392
  resolveBaseUrl,
@@ -5415,11 +5415,11 @@ async function handleUpload(parsed) {
5415
5415
  await executeUpload(parsed);
5416
5416
  } catch (error) {
5417
5417
  console.error(error instanceof Error ? `Error: ${error.message}` : "Error: Upload failed");
5418
- if (error instanceof UploadUsageError) printUsage$8();
5418
+ if (error instanceof UploadUsageError) printUsage$9();
5419
5419
  process$1.exitCode = 1;
5420
5420
  }
5421
5421
  }
5422
- async function executeUpload(parsed, deps = defaultDeps$8) {
5422
+ async function executeUpload(parsed, deps = defaultDeps$9) {
5423
5423
  const filePath = parsed.positionals[0];
5424
5424
  if (!filePath) throw new UploadUsageError("File path is required");
5425
5425
  rejectRetiredUploadOrganizationOptions(parsed);
@@ -5599,7 +5599,7 @@ function parseCsvOption(value, optionName) {
5599
5599
  if (value === "true") throw new UploadUsageError(`${optionName} requires a value`);
5600
5600
  return value.split(",").map((entry) => entry.trim()).filter(Boolean);
5601
5601
  }
5602
- function printUsage$8() {
5602
+ function printUsage$9() {
5603
5603
  console.log(`
5604
5604
  Usage:
5605
5605
  makefx upload <file> --asset <id> [--space <id>] Upload media to existing asset
@@ -5916,6 +5916,15 @@ var ServerConfirmationTimeoutError = class extends Error {
5916
5916
  this.name = "ServerConfirmationTimeoutError";
5917
5917
  }
5918
5918
  };
5919
+ function collectionItemMatchesInput(item, input) {
5920
+ if (item.subject_type !== input.subjectType) return false;
5921
+ if (input.subjectType === "asset" && item.asset_id !== input.assetId) return false;
5922
+ if (input.subjectType === "variant" && item.variant_id !== input.variantId) return false;
5923
+ if (input.role !== void 0 && item.role !== input.role) return false;
5924
+ if (input.pinnedVariantId !== void 0 && item.pinned_variant_id !== input.pinnedVariantId) return false;
5925
+ if (input.sortIndex !== void 0 && item.sort_index !== input.sortIndex) return false;
5926
+ return true;
5927
+ }
5919
5928
  var WebSocketClient = class WebSocketClient {
5920
5929
  ws = null;
5921
5930
  baseUrl;
@@ -6219,9 +6228,9 @@ var WebSocketClient = class WebSocketClient {
6219
6228
  case "error": {
6220
6229
  const errorMsg = message;
6221
6230
  const error = /* @__PURE__ */ new Error(`${errorMsg.code}: ${errorMsg.message}`);
6222
- const mutationConsumed = this.failMutationWaiters(error);
6223
- const estimateConsumed = this.failEstimateHandlers(error);
6224
- if (!(mutationConsumed || estimateConsumed)) {
6231
+ const requestConsumed = errorMsg.requestId ? this.failRequestHandler(errorMsg.requestId, error) : false;
6232
+ const mutationConsumed = requestConsumed ? false : this.failMutationWaiters(error);
6233
+ if (!requestConsumed && !mutationConsumed) {
6225
6234
  console.error(`Server error: ${formatServerErrorMessage(errorMsg.code, errorMsg.message)}`);
6226
6235
  this.onError?.(new Error(errorMsg.message));
6227
6236
  }
@@ -6286,14 +6295,36 @@ var WebSocketClient = class WebSocketClient {
6286
6295
  }
6287
6296
  return true;
6288
6297
  }
6289
- failEstimateHandlers(error) {
6290
- if (this.estimateHandlers.size === 0) return false;
6291
- for (const [requestId, handler] of this.estimateHandlers) {
6292
- clearTimeout(handler.timeout);
6298
+ failRequestHandler(requestId, error) {
6299
+ const estimateHandler = this.estimateHandlers.get(requestId);
6300
+ if (estimateHandler) {
6293
6301
  this.estimateHandlers.delete(requestId);
6302
+ clearTimeout(estimateHandler.timeout);
6303
+ estimateHandler.reject(error);
6304
+ return true;
6305
+ }
6306
+ const generateHandler = this.generateHandlers.get(requestId);
6307
+ if (generateHandler) {
6308
+ this.generateHandlers.delete(requestId);
6309
+ clearTimeout(generateHandler.timeout);
6310
+ generateHandler.reject(error);
6311
+ return true;
6312
+ }
6313
+ const batchHandler = this.batchHandlers.get(requestId);
6314
+ if (batchHandler) {
6315
+ this.batchHandlers.delete(requestId);
6316
+ clearTimeout(batchHandler.timeout);
6317
+ for (const variantId of batchHandler.pending) this.batchVariantToRequestId.delete(variantId);
6318
+ batchHandler.reject(error);
6319
+ return true;
6320
+ }
6321
+ for (const [variantId, handler] of this.variantCompletionHandlers) if (handler.requestId === requestId) {
6322
+ this.variantCompletionHandlers.delete(variantId);
6323
+ clearTimeout(handler.timeout);
6294
6324
  handler.reject(error);
6325
+ return true;
6295
6326
  }
6296
- return true;
6327
+ return false;
6297
6328
  }
6298
6329
  /**
6299
6330
  * Send a mutation message and resolve when a matching broadcast arrives, or
@@ -6378,6 +6409,42 @@ var WebSocketClient = class WebSocketClient {
6378
6409
  rating
6379
6410
  }, (msg) => msg.type === "variant:updated" && msg.variant.id === variantId && msg.variant.quality_rating === rating)).variant;
6380
6411
  }
6412
+ async createCollection(input) {
6413
+ const id = input.id ?? crypto.randomUUID();
6414
+ return (await this.awaitServerMessage({
6415
+ type: "collection:create",
6416
+ ...input,
6417
+ id
6418
+ }, (msg) => msg.type === "collection:created" && msg.collection.id === id)).collection;
6419
+ }
6420
+ async deleteCollection(collectionId) {
6421
+ await this.awaitServerMessage({
6422
+ type: "collection:delete",
6423
+ collectionId
6424
+ }, (msg) => msg.type === "collection:deleted" && msg.collectionId === collectionId);
6425
+ }
6426
+ async createCollectionItem(input) {
6427
+ const id = input.id ?? crypto.randomUUID();
6428
+ return (await this.awaitServerMessage({
6429
+ type: "collection_item:create",
6430
+ ...input,
6431
+ id
6432
+ }, (msg) => msg.type === "collection_item:created" && msg.item.collection_id === input.collectionId && msg.item.id === id && collectionItemMatchesInput(msg.item, input))).item;
6433
+ }
6434
+ async deleteCollectionItem(collectionId, itemId) {
6435
+ await this.awaitServerMessage({
6436
+ type: "collection_item:delete",
6437
+ collectionId,
6438
+ itemId
6439
+ }, (msg) => msg.type === "collection_item:deleted" && msg.collectionId === collectionId && msg.itemId === itemId);
6440
+ }
6441
+ async reorderCollectionItems(collectionId, itemIds) {
6442
+ return (await this.awaitServerMessage({
6443
+ type: "collection_items:reorder",
6444
+ collectionId,
6445
+ itemIds
6446
+ }, (msg) => msg.type === "collection_items:reordered" && msg.collectionId === collectionId && itemIds.every((itemId, index) => msg.items[index]?.id === itemId))).items;
6447
+ }
6381
6448
  /**
6382
6449
  * Send a message through the WebSocket
6383
6450
  */
@@ -6961,7 +7028,7 @@ var MEDIA_OPERATION_MATRIX = [
6961
7028
  assetType: "speech",
6962
7029
  promptNoun: "speech clip",
6963
7030
  inheritsReferenceAssetType: false,
6964
- compatibleSlotMediaKinds: ["audio"],
7031
+ compatibleSlotMediaKinds: [],
6965
7032
  supportsBatch: true,
6966
7033
  cliNamespace: "audio",
6967
7034
  cliCommands: ["generate", "batch"],
@@ -6976,7 +7043,7 @@ var MEDIA_OPERATION_MATRIX = [
6976
7043
  assetType: "dialogue",
6977
7044
  promptNoun: "dialogue clip",
6978
7045
  inheritsReferenceAssetType: false,
6979
- compatibleSlotMediaKinds: ["audio"],
7046
+ compatibleSlotMediaKinds: [],
6980
7047
  supportsBatch: true,
6981
7048
  cliNamespace: "audio",
6982
7049
  cliCommands: ["generate", "batch"],
@@ -6991,7 +7058,7 @@ var MEDIA_OPERATION_MATRIX = [
6991
7058
  assetType: "music",
6992
7059
  promptNoun: "music cue",
6993
7060
  inheritsReferenceAssetType: false,
6994
- compatibleSlotMediaKinds: ["audio"],
7061
+ compatibleSlotMediaKinds: [],
6995
7062
  supportsBatch: true,
6996
7063
  cliNamespace: "audio",
6997
7064
  cliCommands: ["generate", "batch"],
@@ -7006,7 +7073,7 @@ var MEDIA_OPERATION_MATRIX = [
7006
7073
  assetType: "sfx",
7007
7074
  promptNoun: "sound effect",
7008
7075
  inheritsReferenceAssetType: false,
7009
- compatibleSlotMediaKinds: ["audio"],
7076
+ compatibleSlotMediaKinds: [],
7010
7077
  supportsBatch: true,
7011
7078
  cliNamespace: "audio",
7012
7079
  cliCommands: ["generate", "batch"],
@@ -7072,7 +7139,7 @@ function cliGenerationSupportsRefs(namespace) {
7072
7139
  //#endregion
7073
7140
  //#region src/cli/commands/forge.ts
7074
7141
  var CLI_GENERATION_MEDIA_KIND = getCliGenerationMediaKind("top-level");
7075
- var defaultDeps$7 = {
7142
+ var defaultDeps$8 = {
7076
7143
  loadConfig: loadStoredConfig,
7077
7144
  loadProjectConfig,
7078
7145
  resolveBaseUrl,
@@ -7146,10 +7213,10 @@ async function handleBatch(parsed) {
7146
7213
  }
7147
7214
  async function handleForgeCommand(command, parsed) {
7148
7215
  try {
7149
- await executeForgeCommand(command, parsed, defaultDeps$7);
7216
+ await executeForgeCommand(command, parsed, defaultDeps$8);
7150
7217
  } catch (error) {
7151
7218
  console.error("Error:", error instanceof Error ? error.message : error);
7152
- if (shouldPrintUsageForForgeError(error)) printUsage$7(command);
7219
+ if (shouldPrintUsageForForgeError(error)) printUsage$8(command);
7153
7220
  process$1.exitCode = 1;
7154
7221
  }
7155
7222
  }
@@ -7158,11 +7225,11 @@ function shouldPrintUsageForForgeError(error) {
7158
7225
  const message = error.message;
7159
7226
  return message.startsWith("--") || message.startsWith("Output path is required") || message.startsWith("Output directory is required") || message.startsWith("Pass either prompt text") || message.startsWith("Speech generation requires") || message.startsWith("Dialogue generation requires") || message.startsWith("Prompt is required");
7160
7227
  }
7161
- async function executeForgeCommand(command, parsed, deps = defaultDeps$7, options = {}) {
7228
+ async function executeForgeCommand(command, parsed, deps = defaultDeps$8, options = {}) {
7162
7229
  const mediaKind = options.mediaKind || CLI_GENERATION_MEDIA_KIND;
7163
7230
  if (parsed.options.follow) {
7164
7231
  if (command === "batch") throw new Error("--follow is only supported for single-output generation commands");
7165
- const ctx = await buildContext$4(parsed, deps);
7232
+ const ctx = await buildContext$5(parsed, deps);
7166
7233
  const client = await deps.createClient(ctx.env, ctx.spaceId);
7167
7234
  try {
7168
7235
  await client.connect();
@@ -7176,7 +7243,7 @@ async function executeForgeCommand(command, parsed, deps = defaultDeps$7, option
7176
7243
  validateVideoAudioOptions(parsed, mediaKind);
7177
7244
  parseVideoGenerationOptions(parsed, mediaKind);
7178
7245
  validateVideoFrameReferenceOptions(command, parsed, mediaKind);
7179
- const ctx = await buildContext$4(parsed, deps);
7246
+ const ctx = await buildContext$5(parsed, deps);
7180
7247
  const client = await deps.createClient(ctx.env, ctx.spaceId);
7181
7248
  try {
7182
7249
  await client.connect();
@@ -7190,7 +7257,7 @@ async function executeForgeCommand(command, parsed, deps = defaultDeps$7, option
7190
7257
  client.disconnect();
7191
7258
  }
7192
7259
  }
7193
- async function executeAudioCommand(command, parsed, deps = defaultDeps$7, options = {}) {
7260
+ async function executeAudioCommand(command, parsed, deps = defaultDeps$8, options = {}) {
7194
7261
  const audioProfile = getCliGenerationProfile("audio");
7195
7262
  if (!audioProfile.commands.includes(command)) throw new Error(`Audio generation does not support ${command}`);
7196
7263
  const modeEntry = options.mode ? getMediaOperationEntry(options.mode) : void 0;
@@ -7203,7 +7270,7 @@ async function executeAudioCommand(command, parsed, deps = defaultDeps$7, option
7203
7270
  audioMode: options.mode
7204
7271
  });
7205
7272
  }
7206
- async function executeVideoCommand(command, parsed, deps = defaultDeps$7) {
7273
+ async function executeVideoCommand(command, parsed, deps = defaultDeps$8) {
7207
7274
  const videoProfile = getCliGenerationProfile("video");
7208
7275
  if (!videoProfile.commands.includes(command)) throw new Error(`Video generation does not support ${command}`);
7209
7276
  return executeForgeCommand(command, parsed, deps, {
@@ -7242,12 +7309,17 @@ async function executeGenerate(parsed, ctx, client, deps, mediaKind, followOptio
7242
7309
  const musicProvider = parseMusicProviderOption(parsed, mediaKind, assetType);
7243
7310
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
7244
7311
  const videoFrameRefs = parseVideoFrameReferenceOptions(parsed, "generate", mediaKind).refs;
7245
- const state = videoFrameRefs.length > 0 ? await requestSpaceState(client) : void 0;
7312
+ const state = videoFrameRefs.length > 0 ? await requestSpaceState$1(client) : void 0;
7246
7313
  const referenceVariantIds = videoFrameRefs.length > 0 ? await resolveReferenceVariantIds(videoFrameRefs, ctx, deps, state?.variants, CLI_GENERATION_MEDIA_KIND) : [];
7247
7314
  const imageOptions = parseImageGenerationOptions(parsed, mediaKind);
7248
7315
  const audioModelOptions = parseAudioGenerationOptions(parsed, mediaKind);
7249
7316
  const videoAudioOptions = parseVideoAudioOptions(parsed, mediaKind);
7250
7317
  const videoOptions = parseVideoGenerationOptions(parsed, mediaKind);
7318
+ const collectionPlacement = parseCollectionPlacementOptions(parsed);
7319
+ const followCommandOptions = {
7320
+ ...followOptions,
7321
+ ...collectionPlacement
7322
+ };
7251
7323
  await printPreflightEstimate(client, {
7252
7324
  operation: "generate",
7253
7325
  assetType,
@@ -7280,7 +7352,7 @@ async function executeGenerate(parsed, ctx, client, deps, mediaKind, followOptio
7280
7352
  ...videoOptions,
7281
7353
  onStarted: (started) => {
7282
7354
  startedVariantId = started.jobId;
7283
- printFollowHint(started, ctx, outputPath, "generate", mediaKind, followOptions);
7355
+ printFollowHint(started, ctx, outputPath, "generate", mediaKind, followCommandOptions);
7284
7356
  }
7285
7357
  });
7286
7358
  } catch (error) {
@@ -7290,7 +7362,7 @@ async function executeGenerate(parsed, ctx, client, deps, mediaKind, followOptio
7290
7362
  ctx,
7291
7363
  command: "generate",
7292
7364
  mediaKind,
7293
- followOptions
7365
+ followOptions: followCommandOptions
7294
7366
  });
7295
7367
  }
7296
7368
  const manifestPath = await saveGenerationManifest({
@@ -7307,6 +7379,7 @@ async function executeGenerate(parsed, ctx, client, deps, mediaKind, followOptio
7307
7379
  refs: videoFrameRefs,
7308
7380
  referenceVariantIds
7309
7381
  });
7382
+ await placeGeneratedAssetInCollection(result, ctx, deps, collectionPlacement);
7310
7383
  await downloadResult(result, outputPath, ctx, deps);
7311
7384
  printResult(result, outputPath, ctx, manifestPath);
7312
7385
  return result;
@@ -7334,7 +7407,7 @@ async function executeRefine(parsed, ctx, client, deps, mediaKind) {
7334
7407
  const prompt = getPrompt(parsed, "refine");
7335
7408
  const outputPath = getOutputPath$1(parsed);
7336
7409
  const sourceVariantId = requireOption$1(parsed, "variant");
7337
- const state = await requestSpaceState(client);
7410
+ const state = await requestSpaceState$1(client);
7338
7411
  const sourceVariant = state.variants.find((variant) => variant.id === sourceVariantId);
7339
7412
  if (!sourceVariant) throw new Error(`Variant not found in space sync state: ${sourceVariantId}`);
7340
7413
  const sourceAsset = state.assets.find((asset) => asset.id === sourceVariant.asset_id);
@@ -7342,6 +7415,8 @@ async function executeRefine(parsed, ctx, client, deps, mediaKind) {
7342
7415
  const imageOptions = parseImageGenerationOptions(parsed, mediaKind);
7343
7416
  const videoAudioOptions = parseVideoAudioOptions(parsed, mediaKind);
7344
7417
  const videoOptions = parseVideoGenerationOptions(parsed, mediaKind);
7418
+ const collectionPlacement = parseCollectionPlacementOptions(parsed);
7419
+ const followCommandOptions = { ...collectionPlacement };
7345
7420
  await printPreflightEstimate(client, {
7346
7421
  operation: "refine",
7347
7422
  assetId: sourceVariant.asset_id,
@@ -7369,7 +7444,7 @@ async function executeRefine(parsed, ctx, client, deps, mediaKind) {
7369
7444
  ...videoOptions,
7370
7445
  onStarted: (started) => {
7371
7446
  startedVariantId = started.jobId;
7372
- printFollowHint(started, ctx, outputPath, "refine", mediaKind);
7447
+ printFollowHint(started, ctx, outputPath, "refine", mediaKind, followCommandOptions);
7373
7448
  }
7374
7449
  });
7375
7450
  } catch (error) {
@@ -7378,7 +7453,8 @@ async function executeRefine(parsed, ctx, client, deps, mediaKind) {
7378
7453
  outputPath,
7379
7454
  ctx,
7380
7455
  command: "refine",
7381
- mediaKind
7456
+ mediaKind,
7457
+ followOptions: followCommandOptions
7382
7458
  });
7383
7459
  }
7384
7460
  const manifestPath = await saveGenerationManifest({
@@ -7395,6 +7471,7 @@ async function executeRefine(parsed, ctx, client, deps, mediaKind) {
7395
7471
  refs: [sourceVariantId],
7396
7472
  referenceVariantIds: [sourceVariantId]
7397
7473
  });
7474
+ await placeGeneratedAssetInCollection(result, ctx, deps, collectionPlacement);
7398
7475
  await downloadResult(result, outputPath, ctx, deps);
7399
7476
  printResult(result, outputPath, ctx, manifestPath);
7400
7477
  return result;
@@ -7410,10 +7487,15 @@ async function executeDerive(parsed, ctx, client, deps, mediaKind, followOptions
7410
7487
  if (referenceRefs.length === 0) throw new Error("--refs is required");
7411
7488
  const imageOptions = parseImageGenerationOptions(parsed, mediaKind);
7412
7489
  if (mediaKind === "image") validateImageReferenceCount(imageOptions.model, referenceRefs.length);
7413
- const referenceVariantIds = await resolveReferenceVariantIds(referenceRefs, ctx, deps, (await requestSpaceState(client)).variants, videoFrameRefs.length > 0 ? CLI_GENERATION_MEDIA_KIND : mediaKind);
7490
+ const referenceVariantIds = await resolveReferenceVariantIds(referenceRefs, ctx, deps, (await requestSpaceState$1(client)).variants, videoFrameRefs.length > 0 ? CLI_GENERATION_MEDIA_KIND : mediaKind);
7414
7491
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
7415
7492
  const videoAudioOptions = parseVideoAudioOptions(parsed, mediaKind);
7416
7493
  const videoOptions = parseVideoGenerationOptions(parsed, mediaKind);
7494
+ const collectionPlacement = parseCollectionPlacementOptions(parsed);
7495
+ const followCommandOptions = {
7496
+ ...followOptions,
7497
+ ...collectionPlacement
7498
+ };
7417
7499
  await printPreflightEstimate(client, {
7418
7500
  operation: "derive",
7419
7501
  assetType,
@@ -7441,7 +7523,7 @@ async function executeDerive(parsed, ctx, client, deps, mediaKind, followOptions
7441
7523
  ...videoOptions,
7442
7524
  onStarted: (started) => {
7443
7525
  startedVariantId = started.jobId;
7444
- printFollowHint(started, ctx, outputPath, "derive", mediaKind, followOptions);
7526
+ printFollowHint(started, ctx, outputPath, "derive", mediaKind, followCommandOptions);
7445
7527
  }
7446
7528
  });
7447
7529
  } catch (error) {
@@ -7451,7 +7533,7 @@ async function executeDerive(parsed, ctx, client, deps, mediaKind, followOptions
7451
7533
  ctx,
7452
7534
  command: "derive",
7453
7535
  mediaKind,
7454
- followOptions
7536
+ followOptions: followCommandOptions
7455
7537
  });
7456
7538
  }
7457
7539
  const manifestPath = await saveGenerationManifest({
@@ -7468,6 +7550,7 @@ async function executeDerive(parsed, ctx, client, deps, mediaKind, followOptions
7468
7550
  refs: referenceRefs,
7469
7551
  referenceVariantIds
7470
7552
  });
7553
+ await placeGeneratedAssetInCollection(result, ctx, deps, collectionPlacement);
7471
7554
  await downloadResult(result, outputPath, ctx, deps);
7472
7555
  printResult(result, outputPath, ctx, manifestPath);
7473
7556
  return result;
@@ -7477,8 +7560,13 @@ async function executeFollow(parsed, ctx, client, deps, command, mediaKind, foll
7477
7560
  if (!variantId) throw new Error("--follow requires a variant ID");
7478
7561
  const outputPath = getOutputPath$1(parsed);
7479
7562
  const timeoutMs = parseFollowTimeoutMs(parsed, mediaKind);
7563
+ const collectionPlacement = parseCollectionPlacementOptions(parsed);
7564
+ const followCommandOptions = {
7565
+ ...followOptions,
7566
+ ...collectionPlacement
7567
+ };
7480
7568
  console.log(`Following variant ${variantId} in space ${ctx.spaceId}...`);
7481
- const state = await requestSpaceState(client);
7569
+ const state = await requestSpaceState$1(client);
7482
7570
  const initialVariant = state.variants.find((variant) => variant.id === variantId);
7483
7571
  if (!initialVariant) throw new Error(`Variant not found in space sync state: ${variantId}`);
7484
7572
  const initialStatus = initialVariant.status;
@@ -7498,7 +7586,7 @@ async function executeFollow(parsed, ctx, client, deps, command, mediaKind, foll
7498
7586
  }
7499
7587
  }
7500
7588
  });
7501
- const refreshedVariant = (await requestSpaceState(client)).variants.find((variant) => variant.id === variantId);
7589
+ const refreshedVariant = (await requestSpaceState$1(client)).variants.find((variant) => variant.id === variantId);
7502
7590
  result = refreshedVariant ? resultFromTerminalVariant(refreshedVariant, requestId) : void 0;
7503
7591
  if (result) client.cancelFollowVariant?.(variantId, requestId);
7504
7592
  else try {
@@ -7510,7 +7598,7 @@ async function executeFollow(parsed, ctx, client, deps, command, mediaKind, foll
7510
7598
  ctx,
7511
7599
  command,
7512
7600
  mediaKind,
7513
- followOptions
7601
+ followOptions: followCommandOptions
7514
7602
  });
7515
7603
  }
7516
7604
  }
@@ -7533,6 +7621,7 @@ async function executeFollow(parsed, ctx, client, deps, command, mediaKind, foll
7533
7621
  referenceVariantIds
7534
7622
  });
7535
7623
  await downloadResult(result, outputPath, ctx, deps);
7624
+ await placeGeneratedAssetInCollection(result, ctx, deps, collectionPlacement);
7536
7625
  printResult(result, outputPath, ctx, manifestPath);
7537
7626
  return result;
7538
7627
  }
@@ -7625,10 +7714,11 @@ async function executeBatch(parsed, ctx, client, deps, mediaKind, saveBatchManif
7625
7714
  const imageOptions = parseImageGenerationOptions(parsed, mediaKind);
7626
7715
  const audioModelOptions = parseAudioGenerationOptions(parsed, mediaKind);
7627
7716
  if (mediaKind === "image") validateImageReferenceCount(imageOptions.model, refs.length);
7628
- const state = await requestSpaceState(client);
7717
+ const state = await requestSpaceState$1(client);
7629
7718
  const referenceVariantIds = refs.length > 0 ? await resolveReferenceVariantIds(refs, ctx, deps, state.variants, mediaKind) : void 0;
7630
7719
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
7631
7720
  const runId = deps.createRunId();
7721
+ const collectionPlacement = parseCollectionPlacementOptions(parsed);
7632
7722
  await printPreflightEstimate(client, {
7633
7723
  operation: "batch",
7634
7724
  assetType,
@@ -7667,6 +7757,7 @@ async function executeBatch(parsed, ctx, client, deps, mediaKind, saveBatchManif
7667
7757
  }
7668
7758
  const sortedVariants = [...result.variants].sort((a, b) => a.created_at - b.created_at);
7669
7759
  const media = [];
7760
+ await placeGeneratedBatchAssetsInCollection(result, ctx, deps, collectionPlacement);
7670
7761
  for (let index = 0; index < sortedVariants.length; index += 1) {
7671
7762
  const variant = sortedVariants[index];
7672
7763
  const outputPath = path.join(outputDir, `${slugify(name)}-${String(index + 1).padStart(2, "0")}.${getOutputExtension(variant, mediaKind)}`);
@@ -7752,7 +7843,69 @@ async function saveGenerationManifest(input) {
7752
7843
  failed: []
7753
7844
  }, ctx.projectRoot);
7754
7845
  }
7755
- async function buildContext$4(parsed, deps) {
7846
+ function parseCollectionPlacementOptions(parsed) {
7847
+ const collectionId = normalizeCliOption(readOptionalOption(parsed, "collection", "collection") ?? readOptionalOption(parsed, "collection-id", "collectionId"));
7848
+ if (parsed.options.collection !== void 0 || parsed.options.collectionId !== void 0 || parsed.options["collection-id"] !== void 0) {
7849
+ if (!collectionId) throw new Error("--collection requires a collection ID");
7850
+ }
7851
+ return collectionId ? { collectionId } : {};
7852
+ }
7853
+ async function placeGeneratedAssetInCollection(result, ctx, deps, options) {
7854
+ if (!options.collectionId || !result.success || !result.variant) return;
7855
+ await createCollectionAssetItem({
7856
+ ctx,
7857
+ deps,
7858
+ collectionId: options.collectionId,
7859
+ assetId: result.variant.asset_id,
7860
+ pinnedVariantId: result.variant.id
7861
+ });
7862
+ }
7863
+ async function placeGeneratedBatchAssetsInCollection(result, ctx, deps, options) {
7864
+ if (!options.collectionId || !result.success) return;
7865
+ const firstVariantByAsset = /* @__PURE__ */ new Map();
7866
+ for (const variant of [...result.variants].sort((a, b) => a.created_at - b.created_at)) if (!firstVariantByAsset.has(variant.asset_id)) firstVariantByAsset.set(variant.asset_id, variant);
7867
+ for (const variant of firstVariantByAsset.values()) await createCollectionAssetItem({
7868
+ ctx,
7869
+ deps,
7870
+ collectionId: options.collectionId,
7871
+ assetId: variant.asset_id,
7872
+ pinnedVariantId: variant.id
7873
+ });
7874
+ }
7875
+ async function createCollectionAssetItem(input) {
7876
+ const fetchImpl = input.deps.fetch ?? fetch;
7877
+ const itemsUrl = `${input.ctx.baseUrl}/api/spaces/${encodeURIComponent(input.ctx.spaceId)}/collections/${encodeURIComponent(input.collectionId)}/items`;
7878
+ const listResponse = await fetchImpl(itemsUrl, { headers: {
7879
+ "Authorization": `Bearer ${input.ctx.accessToken}`,
7880
+ "Accept": "application/json"
7881
+ } });
7882
+ if (!listResponse.ok) {
7883
+ const errorText = await listResponse.text();
7884
+ throw new Error(`Failed to inspect collection ${input.collectionId} (${listResponse.status}): ${errorText || listResponse.statusText}`);
7885
+ }
7886
+ const existing = await listResponse.json();
7887
+ if (existing.items?.some((item) => item.asset_id === input.assetId)) return;
7888
+ const sortIndex = Math.max(-1, ...(existing.items ?? []).map((item) => typeof item.sort_index === "number" && Number.isFinite(item.sort_index) ? item.sort_index : -1)) + 1;
7889
+ const response = await fetchImpl(itemsUrl, {
7890
+ method: "POST",
7891
+ headers: {
7892
+ "Authorization": `Bearer ${input.ctx.accessToken}`,
7893
+ "Content-Type": "application/json",
7894
+ "Accept": "application/json"
7895
+ },
7896
+ body: JSON.stringify({
7897
+ subjectType: "asset",
7898
+ assetId: input.assetId,
7899
+ pinnedVariantId: input.pinnedVariantId,
7900
+ sortIndex
7901
+ })
7902
+ });
7903
+ if (!response.ok) {
7904
+ const errorText = await response.text();
7905
+ throw new Error(`Failed to place asset ${input.assetId} in collection ${input.collectionId} (${response.status}): ${errorText || response.statusText}`);
7906
+ }
7907
+ }
7908
+ async function buildContext$5(parsed, deps) {
7756
7909
  const projectConfig = await deps.loadProjectConfig();
7757
7910
  const env = resolveCommandEnvironment(parsed, projectConfig);
7758
7911
  const spaceId = resolveCommandSpace(parsed, projectConfig);
@@ -7869,7 +8022,7 @@ function validateReferenceVariant(ref, variants, mediaKind) {
7869
8022
  if (!variant.image_key && !variant.media_key) throw new Error(`Reference variant has no image or video media: ${ref}`);
7870
8023
  }
7871
8024
  }
7872
- function requestSpaceState(client) {
8025
+ function requestSpaceState$1(client) {
7873
8026
  return new Promise((resolve, reject) => {
7874
8027
  const timer = setTimeout(() => {
7875
8028
  reject(/* @__PURE__ */ new Error("Timed out waiting for space sync state"));
@@ -7922,12 +8075,13 @@ function printFollowHint(started, ctx, outputPath, command, mediaKind, options =
7922
8075
  }
7923
8076
  function throwGenerationWaitErrorIfNeeded(input, params) {
7924
8077
  if (!isGenerationWaitTimeout(input)) throw input;
7925
- if (!params.variantId) throw new Error(["Make Effects did not confirm the generation start before the CLI stopped waiting.", "Check the Space in the web app before retrying, so you do not create a duplicate."].join("\n"));
8078
+ if (!params.variantId) throw new Error(["The CLI timed out before Make Effects returned a variant ID.", "The generation may still have started. Check the Space in the web app before retrying, so you do not create a duplicate."].join("\n"));
7926
8079
  const followCommand = formatFollowCommand(params.variantId, params.outputPath, params.ctx, params.command, params.mediaKind, params.followOptions);
7927
8080
  throw new Error([
7928
- "Still generating in Make Effects.",
8081
+ "The CLI stopped waiting, but the Make Effects generation job is still running.",
8082
+ "Do not retry this generation unless you want a duplicate asset.",
7929
8083
  "",
7930
- "Resume the download when the variant is ready:",
8084
+ "Resume the same job and download the variant when it is ready:",
7931
8085
  ` ${followCommand}`,
7932
8086
  "",
7933
8087
  `Variant: ${params.variantId}`
@@ -7935,7 +8089,7 @@ function throwGenerationWaitErrorIfNeeded(input, params) {
7935
8089
  }
7936
8090
  function throwBatchWaitErrorIfNeeded(input, started, ctx) {
7937
8091
  if (!isBatchWaitTimeout(input)) throw input;
7938
- if (!started) throw new Error(["Make Effects did not confirm the batch start before the CLI stopped waiting.", "Check the Space in the web app before retrying, so you do not create duplicate assets."].join("\n"));
8092
+ if (!started) throw new Error(["The CLI timed out before Make Effects returned batch variant IDs.", "The batch may still have started. Check the Space in the web app before retrying, so you do not create duplicate assets."].join("\n"));
7939
8093
  const downloadExample = [
7940
8094
  "makefx assets download <variant_id> -o <file>",
7941
8095
  ...ctx.env === "local" ? ["--local"] : ["--env", ctx.env],
@@ -7943,7 +8097,8 @@ function throwBatchWaitErrorIfNeeded(input, started, ctx) {
7943
8097
  ctx.spaceId
7944
8098
  ].join(" ");
7945
8099
  throw new Error([
7946
- "Batch generation is still running in Make Effects.",
8100
+ "The CLI stopped waiting, but the Make Effects batch jobs are still running.",
8101
+ "Do not retry this batch unless you want duplicate assets.",
7947
8102
  "",
7948
8103
  `Started variants: ${started.jobIds.join(", ")}`,
7949
8104
  "",
@@ -7970,6 +8125,7 @@ function formatFollowCommand(variantId, outputPath, ctx, command, mediaKind, opt
7970
8125
  command
7971
8126
  ] : ["makefx", command];
7972
8127
  const envArgs = ctx.env === "local" ? ["--local"] : ["--env", ctx.env];
8128
+ const collectionArgs = options.collectionId ? ["--collection", options.collectionId] : [];
7973
8129
  return [
7974
8130
  ...baseCommand,
7975
8131
  "--follow",
@@ -7978,7 +8134,8 @@ function formatFollowCommand(variantId, outputPath, ctx, command, mediaKind, opt
7978
8134
  outputPath,
7979
8135
  ...envArgs,
7980
8136
  "--space",
7981
- ctx.spaceId
8137
+ ctx.spaceId,
8138
+ ...collectionArgs
7982
8139
  ].map(shellQuote).join(" ");
7983
8140
  }
7984
8141
  function shellQuote(value) {
@@ -8221,34 +8378,34 @@ function cliImageSizeValues() {
8221
8378
  function cliImageAspectValues() {
8222
8379
  return cliOptionValues(getImageModelCapabilities().supportedAspectRatios);
8223
8380
  }
8224
- function printUsage$7(command) {
8381
+ function printUsage$8(command) {
8225
8382
  if (command === "generate") {
8226
8383
  console.log(`
8227
8384
  Usage:
8228
- makefx generate "prompt" --name <name> --type <type> -o <file> [--model pro|flash] [--size ${cliImageSizeValues()}] [--aspect ${cliImageAspectValues()}] [--space <id>]
8229
- makefx generate --follow <variant_id> -o <file> [--space <id>]
8385
+ makefx generate "prompt" --name <name> --type <type> -o <file> [--model pro|flash] [--size ${cliImageSizeValues()}] [--aspect ${cliImageAspectValues()}] [--collection <id>] [--space <id>]
8386
+ makefx generate --follow <variant_id> -o <file> [--collection <id>] [--space <id>]
8230
8387
  `);
8231
8388
  return;
8232
8389
  }
8233
8390
  if (command === "refine") {
8234
8391
  console.log(`
8235
8392
  Usage:
8236
- makefx refine --variant <variant_id> "prompt" -o <file> [--model pro|flash] [--size ${cliImageSizeValues()}] [--aspect ${cliImageAspectValues()}] [--space <id>]
8237
- makefx refine --follow <variant_id> -o <file> [--space <id>]
8393
+ makefx refine --variant <variant_id> "prompt" -o <file> [--model pro|flash] [--size ${cliImageSizeValues()}] [--aspect ${cliImageAspectValues()}] [--collection <id>] [--space <id>]
8394
+ makefx refine --follow <variant_id> -o <file> [--collection <id>] [--space <id>]
8238
8395
  `);
8239
8396
  return;
8240
8397
  }
8241
8398
  if (command === "batch") {
8242
8399
  console.log(`
8243
8400
  Usage:
8244
- makefx batch "prompt" --name <name> --type <type> --count <2-8> --output-dir <dir> [--model pro|flash] [--size ${cliImageSizeValues()}] [--aspect ${cliImageAspectValues()}]
8401
+ makefx batch "prompt" --name <name> --type <type> --count <2-8> --output-dir <dir> [--model pro|flash] [--size ${cliImageSizeValues()}] [--aspect ${cliImageAspectValues()}] [--collection <id>]
8245
8402
  `);
8246
8403
  return;
8247
8404
  }
8248
8405
  console.log(`
8249
8406
  Usage:
8250
- makefx derive --refs <variant_or_file,variant_or_file> --name <name> --type <type> "prompt" -o <file> [--model pro|flash] [--size ${cliImageSizeValues()}] [--aspect ${cliImageAspectValues()}] [--space <id>]
8251
- makefx derive --follow <variant_id> -o <file> [--space <id>]
8407
+ makefx derive --refs <variant_or_file,variant_or_file> --name <name> --type <type> "prompt" -o <file> [--model pro|flash] [--size ${cliImageSizeValues()}] [--aspect ${cliImageAspectValues()}] [--collection <id>] [--space <id>]
8408
+ makefx derive --follow <variant_id> -o <file> [--collection <id>] [--space <id>]
8252
8409
  `);
8253
8410
  }
8254
8411
  function printBatchResult(result, outputDir, manifestPath, ctx, mediaKind) {
@@ -8262,7 +8419,7 @@ function printBatchResult(result, outputDir, manifestPath, ctx, mediaKind) {
8262
8419
  }
8263
8420
  //#endregion
8264
8421
  //#region src/cli/commands/audio.ts
8265
- var defaultDeps$6 = {
8422
+ var defaultDeps$7 = {
8266
8423
  loadConfig: loadStoredConfig,
8267
8424
  loadProjectConfig,
8268
8425
  resolveBaseUrl,
@@ -8275,11 +8432,11 @@ async function handleAudio(parsed) {
8275
8432
  await executeAudio(parsed);
8276
8433
  } catch (error) {
8277
8434
  console.error("Error:", error instanceof Error ? error.message : error);
8278
- printUsage$6();
8435
+ printUsage$7();
8279
8436
  process$1.exitCode = 1;
8280
8437
  }
8281
8438
  }
8282
- async function executeAudio(parsed, deps = defaultDeps$6) {
8439
+ async function executeAudio(parsed, deps = defaultDeps$7) {
8283
8440
  const invocation = parseAudioInvocation(parsed.positionals);
8284
8441
  if (invocation.command === "voices") return executeAudioVoices({
8285
8442
  ...parsed,
@@ -8352,7 +8509,7 @@ function printVoices(response, print) {
8352
8509
  function formatLabels(labels) {
8353
8510
  return Object.entries(labels).map(([key, value]) => `${key}:${value}`).join(", ");
8354
8511
  }
8355
- function printUsage$6() {
8512
+ function printUsage$7() {
8356
8513
  const modes = AUDIO_FORGE_MEDIA_MODES.join("|");
8357
8514
  console.log(`
8358
8515
  Usage:
@@ -8381,7 +8538,7 @@ async function handleVideo(parsed) {
8381
8538
  });
8382
8539
  } catch (error) {
8383
8540
  console.error("Error:", error instanceof Error ? error.message : error);
8384
- printUsage$5();
8541
+ printUsage$6();
8385
8542
  process$1.exitCode = 1;
8386
8543
  }
8387
8544
  }
@@ -8390,7 +8547,7 @@ function parseVideoCommand(value) {
8390
8547
  if (value && commands.includes(value)) return value;
8391
8548
  throw new Error("Video command is required: generate, refine, or derive");
8392
8549
  }
8393
- function printUsage$5() {
8550
+ function printUsage$6() {
8394
8551
  const aspectValues = VIDEO_GENERATION_ASPECT_RATIOS.join("|");
8395
8552
  const resolutionValues = Array.from(new Set(VIDEO_GENERATION_TIERS.flatMap((tier) => getVideoGenerationResolutionsForTier(tier)))).join("|");
8396
8553
  const durationValues = VIDEO_GENERATION_DURATION_SECONDS.join("|");
@@ -8429,17 +8586,17 @@ Audio:
8429
8586
  }
8430
8587
  //#endregion
8431
8588
  //#region src/cli/commands/init.ts
8432
- var defaultDeps$5 = { saveProjectConfig };
8589
+ var defaultDeps$6 = { saveProjectConfig };
8433
8590
  async function handleInit(parsed) {
8434
8591
  try {
8435
8592
  await executeInit(parsed);
8436
8593
  } catch (error) {
8437
8594
  console.error("Error:", error instanceof Error ? error.message : error);
8438
- printUsage$4();
8595
+ printUsage$5();
8439
8596
  process$1.exitCode = 1;
8440
8597
  }
8441
8598
  }
8442
- async function executeInit(parsed, deps = defaultDeps$5) {
8599
+ async function executeInit(parsed, deps = defaultDeps$6) {
8443
8600
  const environment = resolveCommandEnvironment(parsed);
8444
8601
  const spaceId = resolveCommandSpace(parsed);
8445
8602
  if (!spaceId) throw new Error("--space is required");
@@ -8465,7 +8622,7 @@ async function executeInit(parsed, deps = defaultDeps$5) {
8465
8622
  spaceId
8466
8623
  };
8467
8624
  }
8468
- function printUsage$4() {
8625
+ function printUsage$5() {
8469
8626
  console.log(`
8470
8627
  Usage:
8471
8628
  makefx init --space <id> [--env production|stage|local]
@@ -8474,7 +8631,7 @@ Usage:
8474
8631
  }
8475
8632
  //#endregion
8476
8633
  //#region src/cli/commands/runs.ts
8477
- var defaultDeps$4 = {
8634
+ var defaultDeps$5 = {
8478
8635
  loadProjectConfig,
8479
8636
  listRunManifests,
8480
8637
  readRunManifest,
@@ -8487,11 +8644,11 @@ async function handleRuns(parsed) {
8487
8644
  await executeRuns(parsed);
8488
8645
  } catch (error) {
8489
8646
  console.error("Error:", error instanceof Error ? error.message : error);
8490
- printUsage$3();
8647
+ printUsage$4();
8491
8648
  process$1.exitCode = 1;
8492
8649
  }
8493
8650
  }
8494
- async function executeRuns(parsed, deps = defaultDeps$4) {
8651
+ async function executeRuns(parsed, deps = defaultDeps$5) {
8495
8652
  const projectConfig = await deps.loadProjectConfig();
8496
8653
  if (!projectConfig?.projectRoot) throw new Error("Make Effects project config not found. Run: makefx init --space <id>");
8497
8654
  requireDebugAcknowledgement(parsed);
@@ -8609,7 +8766,7 @@ function formatCreated(value) {
8609
8766
  if (Number.isNaN(date.getTime())) return value;
8610
8767
  return value.replace("T", " ").replace(/\.\d{3}Z$/, "Z").slice(0, 20);
8611
8768
  }
8612
- function printUsage$3() {
8769
+ function printUsage$4() {
8613
8770
  console.log(`
8614
8771
  Usage:
8615
8772
  makefx runs --debug
@@ -8624,7 +8781,7 @@ Local run manifests are debug-only artifacts, not a source of truth.
8624
8781
  }
8625
8782
  //#endregion
8626
8783
  //#region src/cli/commands/assets.ts
8627
- var defaultDeps$3 = {
8784
+ var defaultDeps$4 = {
8628
8785
  loadConfig: loadStoredConfig,
8629
8786
  loadProjectConfig,
8630
8787
  resolveBaseUrl,
@@ -8648,15 +8805,15 @@ async function handleAssets(parsed) {
8648
8805
  await executeAssets(parsed);
8649
8806
  } catch (error) {
8650
8807
  console.error("Error:", error instanceof Error ? error.message : error);
8651
- printUsage$2();
8808
+ printUsage$3();
8652
8809
  process$1.exitCode = 1;
8653
8810
  }
8654
8811
  }
8655
- async function executeAssets(parsed, deps = defaultDeps$3) {
8812
+ async function executeAssets(parsed, deps = defaultDeps$4) {
8656
8813
  const subcommand = parsed.positionals[0] || "list";
8657
8814
  if (subcommand === "search") return searchAssets(parsed, deps);
8658
8815
  if (subcommand === "clone") return cloneAsset(parsed, deps);
8659
- const ctx = await buildContext$3(parsed, deps);
8816
+ const ctx = await buildContext$4(parsed, deps);
8660
8817
  if (subcommand === "list") {
8661
8818
  const assets = await listAssets(ctx, deps);
8662
8819
  if (parsed.options.json === "true") deps.print(JSON.stringify(assets.map(toAssetJson), null, 2));
@@ -8682,13 +8839,21 @@ async function executeAssets(parsed, deps = defaultDeps$3) {
8682
8839
  if (!ref) throw new Error("Variant ID or legacy image key is required: makefx assets download <variant-id-or-legacy-image-key> -o <file>");
8683
8840
  const outputPath = getOutputPath(parsed);
8684
8841
  const resolved = resolveDownloadRef(ref, ctx);
8685
- const metadata = getDownloadedVariantMetadata(resolved, await deps.downloadFile({
8686
- baseUrl: ctx.baseUrl,
8687
- accessToken: ctx.accessToken,
8688
- requestPath: resolved.requestPath,
8689
- outputPath,
8690
- force: ctx.force
8691
- }));
8842
+ let download;
8843
+ try {
8844
+ download = await deps.downloadFile({
8845
+ baseUrl: ctx.baseUrl,
8846
+ accessToken: ctx.accessToken,
8847
+ requestPath: resolved.requestPath,
8848
+ outputPath,
8849
+ force: ctx.force
8850
+ });
8851
+ } catch (error) {
8852
+ const failedVariantMessage = await getFailedDownloadVariantMessage(error, resolved, ctx, deps);
8853
+ if (failedVariantMessage) throw new Error(failedVariantMessage);
8854
+ throw error;
8855
+ }
8856
+ const metadata = getDownloadedVariantMetadata(resolved, download);
8692
8857
  await recordDownloadedMirror({
8693
8858
  ctx,
8694
8859
  deps,
@@ -8726,7 +8891,7 @@ async function executeAssets(parsed, deps = defaultDeps$3) {
8726
8891
  }
8727
8892
  if (subcommand === "set-active") {
8728
8893
  const assetId = parsed.positionals[1];
8729
- const variantId = parsed.positionals[2] || normalizeOption(parsed.options.variant);
8894
+ const variantId = parsed.positionals[2] || normalizeOption$1(parsed.options.variant);
8730
8895
  if (!assetId || !variantId) throw new Error("Usage: makefx assets set-active <asset-id> <variant-id>");
8731
8896
  const asset = await setActiveVariant(ctx, deps, assetId, variantId);
8732
8897
  deps.print(`Set active variant of asset ${assetId} to ${asset.active_variant_id}`);
@@ -8737,10 +8902,10 @@ async function executeAssets(parsed, deps = defaultDeps$3) {
8737
8902
  }
8738
8903
  throw new Error(`Unknown assets command: ${subcommand}`);
8739
8904
  }
8740
- function normalizeOption(value) {
8905
+ function normalizeOption$1(value) {
8741
8906
  return !value || value === "true" ? void 0 : value;
8742
8907
  }
8743
- async function buildContext$3(parsed, deps) {
8908
+ async function buildContext$4(parsed, deps) {
8744
8909
  const projectConfig = await deps.loadProjectConfig();
8745
8910
  const env = resolveCommandEnvironment(parsed, projectConfig);
8746
8911
  const spaceId = resolveCommandSpace(parsed, projectConfig);
@@ -8772,7 +8937,7 @@ async function buildAuthContext(parsed, deps) {
8772
8937
  }
8773
8938
  function requireOption(parsed, names, usage) {
8774
8939
  for (const name of names) {
8775
- const value = normalizeOption(parsed.options[name]);
8940
+ const value = normalizeOption$1(parsed.options[name]);
8776
8941
  if (value) return value;
8777
8942
  }
8778
8943
  throw new Error(usage);
@@ -8782,9 +8947,9 @@ async function searchAssets(parsed, deps) {
8782
8947
  const query = parsed.positionals.slice(1).join(" ").trim();
8783
8948
  const params = new URLSearchParams();
8784
8949
  if (query) params.set("q", query);
8785
- const mediaKind = normalizeOption(parsed.options.media) ?? normalizeOption(parsed.options["media-kind"]);
8786
- const assetType = normalizeOption(parsed.options.type);
8787
- const limit = normalizeOption(parsed.options.limit);
8950
+ const mediaKind = normalizeOption$1(parsed.options.media) ?? normalizeOption$1(parsed.options["media-kind"]);
8951
+ const assetType = normalizeOption$1(parsed.options.type);
8952
+ const limit = normalizeOption$1(parsed.options.limit);
8788
8953
  if (mediaKind) params.set("media_kind", mediaKind);
8789
8954
  if (assetType) params.set("type", assetType);
8790
8955
  if (limit) params.set("limit", limit);
@@ -8800,7 +8965,7 @@ async function searchAssets(parsed, deps) {
8800
8965
  }
8801
8966
  async function cloneAsset(parsed, deps) {
8802
8967
  const ctx = await buildAuthContext(parsed, deps);
8803
- const sourceVariantId = parsed.positionals[1] ?? normalizeOption(parsed.options.variant);
8968
+ const sourceVariantId = parsed.positionals[1] ?? normalizeOption$1(parsed.options.variant);
8804
8969
  if (!sourceVariantId) throw new Error("Usage: makefx assets clone <variant-id> --from-space <source-space-id> --to-space <target-space-id>");
8805
8970
  const sourceSpaceId = requireOption(parsed, ["from-space", "source-space"], "Usage: makefx assets clone <variant-id> --from-space <source-space-id> --to-space <target-space-id>");
8806
8971
  const targetSpaceId = requireOption(parsed, ["to-space", "target-space"], "Usage: makefx assets clone <variant-id> --from-space <source-space-id> --to-space <target-space-id>");
@@ -8808,8 +8973,8 @@ async function cloneAsset(parsed, deps) {
8808
8973
  sourceSpaceId,
8809
8974
  sourceVariantId
8810
8975
  };
8811
- const name = normalizeOption(parsed.options.name);
8812
- const type = normalizeOption(parsed.options.type);
8976
+ const name = normalizeOption$1(parsed.options.name);
8977
+ const type = normalizeOption$1(parsed.options.type);
8813
8978
  if (name) body.name = name;
8814
8979
  if (type) body.type = type;
8815
8980
  const response = await deps.fetch(`${ctx.baseUrl}/api/spaces/${encodeURIComponent(targetSpaceId)}/assets/clone`, {
@@ -8857,10 +9022,10 @@ async function recordDownloadedMirror(input) {
8857
9022
  }
8858
9023
  }
8859
9024
  async function listAssets(ctx, deps) {
8860
- return (await fetchJson(ctx, deps, `/api/spaces/${ctx.spaceId}/assets`)).assets || [];
9025
+ return (await fetchJson$1(ctx, deps, `/api/spaces/${ctx.spaceId}/assets`)).assets || [];
8861
9026
  }
8862
9027
  async function getAssetDetails(ctx, deps, assetId) {
8863
- return fetchJson(ctx, deps, `/api/spaces/${ctx.spaceId}/assets/${assetId}`);
9028
+ return fetchJson$1(ctx, deps, `/api/spaces/${ctx.spaceId}/assets/${assetId}`);
8864
9029
  }
8865
9030
  async function setActiveVariant(ctx, deps, assetId, variantId) {
8866
9031
  try {
@@ -8872,7 +9037,63 @@ async function setActiveVariant(ctx, deps, assetId, variantId) {
8872
9037
  throw error;
8873
9038
  }
8874
9039
  }
8875
- async function fetchJson(ctx, deps, apiPath) {
9040
+ async function getFailedDownloadVariantMessage(error, resolved, ctx, deps) {
9041
+ if (resolved.variant || !resolved.requestPath.includes("/variants/")) return null;
9042
+ if (!isVariantMediaUnavailableError(error)) return null;
9043
+ const variantId = resolved.mediaKey;
9044
+ let state;
9045
+ try {
9046
+ state = await requestSpaceState(ctx, deps);
9047
+ } catch {
9048
+ return null;
9049
+ }
9050
+ const variant = state.variants.find((candidate) => {
9051
+ return isVariantRecord(candidate) && candidate.id === variantId;
9052
+ });
9053
+ if (!variant || variant.status !== "failed") return null;
9054
+ const reason = formatGenerationFailureMessage(variant.error_message, variant.provider_metadata);
9055
+ return `Variant ${variant.id} failed and has no downloadable media${reason ? `: ${reason}` : "."}`;
9056
+ }
9057
+ function isVariantMediaUnavailableError(error) {
9058
+ return error instanceof Error && error.message.includes("Variant media not available");
9059
+ }
9060
+ function isVariantRecord(value) {
9061
+ return Boolean(value && typeof value === "object" && typeof value.id === "string");
9062
+ }
9063
+ async function requestSpaceState(ctx, deps) {
9064
+ const client = await deps.createMutationClient(ctx.env, ctx.spaceId);
9065
+ await client.connect();
9066
+ try {
9067
+ return await new Promise((resolve, reject) => {
9068
+ const syncClient = client;
9069
+ if (typeof syncClient.setOnSyncState !== "function" || typeof syncClient.requestSync !== "function") {
9070
+ reject(/* @__PURE__ */ new Error("Space sync is not available"));
9071
+ return;
9072
+ }
9073
+ let settled = false;
9074
+ const finish = (callback) => {
9075
+ if (settled) return;
9076
+ settled = true;
9077
+ clearTimeout(timer);
9078
+ callback();
9079
+ };
9080
+ const timer = setTimeout(() => {
9081
+ finish(() => reject(/* @__PURE__ */ new Error("Timed out waiting for space sync state")));
9082
+ }, 3e4);
9083
+ syncClient.setOnSyncState((state) => {
9084
+ finish(() => resolve(state));
9085
+ });
9086
+ try {
9087
+ syncClient.requestSync();
9088
+ } catch (error) {
9089
+ finish(() => reject(error));
9090
+ }
9091
+ });
9092
+ } finally {
9093
+ client.disconnect();
9094
+ }
9095
+ }
9096
+ async function fetchJson$1(ctx, deps, apiPath) {
8876
9097
  const response = await deps.fetch(`${ctx.baseUrl}${apiPath}`, { headers: {
8877
9098
  "Authorization": `Bearer ${ctx.accessToken}`,
8878
9099
  "Accept": "application/json"
@@ -9054,7 +9275,7 @@ function formatTimestamp(value) {
9054
9275
  if (Number.isNaN(date.getTime())) return String(value);
9055
9276
  return date.toISOString().replace("T", " ").replace(/\.\d{3}Z$/, "Z").slice(0, 20);
9056
9277
  }
9057
- function printUsage$2() {
9278
+ function printUsage$3() {
9058
9279
  console.log(`
9059
9280
  Usage:
9060
9281
  makefx assets
@@ -9071,7 +9292,7 @@ Usage:
9071
9292
  }
9072
9293
  //#endregion
9073
9294
  //#region src/cli/commands/variants.ts
9074
- var defaultDeps$2 = {
9295
+ var defaultDeps$3 = {
9075
9296
  loadConfig: loadStoredConfig,
9076
9297
  loadProjectConfig,
9077
9298
  resolveBaseUrl,
@@ -9083,12 +9304,12 @@ async function handleVariants(parsed) {
9083
9304
  await executeVariants(parsed);
9084
9305
  } catch (error) {
9085
9306
  console.error("Error:", error instanceof Error ? error.message : error);
9086
- printUsage$1();
9307
+ printUsage$2();
9087
9308
  process$1.exitCode = 1;
9088
9309
  }
9089
9310
  }
9090
- async function executeVariants(parsed, deps = defaultDeps$2) {
9091
- const ctx = await buildContext$2(parsed, deps);
9311
+ async function executeVariants(parsed, deps = defaultDeps$3) {
9312
+ const ctx = await buildContext$3(parsed, deps);
9092
9313
  const subcommand = parsed.positionals[0];
9093
9314
  if (subcommand === "delete") {
9094
9315
  const variantId = requireVariantId(parsed, "delete");
@@ -9136,7 +9357,7 @@ function requireVariantId(parsed, verb) {
9136
9357
  if (!variantId) throw new Error(`Variant ID is required: makefx variants ${verb} <variant-id>`);
9137
9358
  return variantId;
9138
9359
  }
9139
- async function buildContext$2(parsed, deps) {
9360
+ async function buildContext$3(parsed, deps) {
9140
9361
  const projectConfig = await deps.loadProjectConfig();
9141
9362
  const env = resolveCommandEnvironment(parsed, projectConfig);
9142
9363
  const spaceId = resolveCommandSpace(parsed, projectConfig);
@@ -9159,7 +9380,7 @@ async function withVariantClient(ctx, deps, run) {
9159
9380
  client.disconnect();
9160
9381
  }
9161
9382
  }
9162
- function printUsage$1() {
9383
+ function printUsage$2() {
9163
9384
  console.log(`
9164
9385
  Usage:
9165
9386
  makefx variants delete <variant-id>
@@ -9175,6 +9396,251 @@ Options:
9175
9396
  `);
9176
9397
  }
9177
9398
  //#endregion
9399
+ //#region src/cli/commands/collections.ts
9400
+ var COLLECTION_KINDS = new Set([
9401
+ "cast",
9402
+ "backgrounds",
9403
+ "scenes",
9404
+ "thumbnails",
9405
+ "maps",
9406
+ "deliverables",
9407
+ "custom"
9408
+ ]);
9409
+ var defaultDeps$2 = {
9410
+ loadConfig: loadStoredConfig,
9411
+ loadProjectConfig,
9412
+ resolveBaseUrl,
9413
+ fetch,
9414
+ createMutationClient: (env, spaceId) => WebSocketClient.create(env, spaceId),
9415
+ print: console.log
9416
+ };
9417
+ async function handleCollections(parsed) {
9418
+ try {
9419
+ await executeCollections(parsed);
9420
+ } catch (error) {
9421
+ console.error("Error:", error instanceof Error ? error.message : error);
9422
+ printUsage$1();
9423
+ process$1.exitCode = 1;
9424
+ }
9425
+ }
9426
+ async function executeCollections(parsed, deps = defaultDeps$2) {
9427
+ const subcommand = parsed.positionals[0] || "list";
9428
+ const ctx = await buildContext$2(parsed, deps);
9429
+ if (subcommand === "list") {
9430
+ const collections = await listCollections(ctx, deps);
9431
+ if (parsed.options.json === "true") deps.print(JSON.stringify(collections, null, 2));
9432
+ else printCollections(collections, deps.print);
9433
+ return {
9434
+ type: "list",
9435
+ collections
9436
+ };
9437
+ }
9438
+ if (subcommand === "items") {
9439
+ const collectionId = parsed.positionals[1];
9440
+ if (!collectionId) throw new Error("Collection ID is required: makefx collections items <collection-id>");
9441
+ const items = await listCollectionItems(ctx, deps, collectionId);
9442
+ if (parsed.options.json === "true") deps.print(JSON.stringify(items, null, 2));
9443
+ else printCollectionItems(items, deps.print);
9444
+ return {
9445
+ type: "items",
9446
+ items
9447
+ };
9448
+ }
9449
+ if (subcommand === "create") {
9450
+ const name = parsed.positionals.slice(1).join(" ").trim() || normalizeOption(parsed.options.name);
9451
+ if (!name) throw new Error("Usage: makefx collections create \"<name>\"");
9452
+ const collection = await withCollectionClient(ctx, deps, (client) => client.createCollection({
9453
+ id: normalizeOption(parsed.options.id),
9454
+ name,
9455
+ kind: parseKind(normalizeOption(parsed.options.kind)),
9456
+ color: parseNullableOption(parsed.options.color),
9457
+ description: parseNullableOption(parsed.options.description),
9458
+ sortIndex: parseOptionalInteger(parsed.options["sort-index"], "sort-index")
9459
+ }));
9460
+ printJsonOrLine(parsed, deps, collection, `Created collection ${collection.id} (${collection.name})`);
9461
+ return {
9462
+ type: "create",
9463
+ collection
9464
+ };
9465
+ }
9466
+ if (subcommand === "delete") {
9467
+ const collectionId = parsed.positionals[1];
9468
+ if (!collectionId) throw new Error("Collection ID is required: makefx collections delete <collection-id>");
9469
+ await withCollectionClient(ctx, deps, (client) => client.deleteCollection(collectionId));
9470
+ deps.print(`Deleted collection ${collectionId}`);
9471
+ return {
9472
+ type: "delete",
9473
+ collectionId
9474
+ };
9475
+ }
9476
+ if (subcommand === "add-asset") {
9477
+ const collectionId = parsed.positionals[1];
9478
+ const assetId = parsed.positionals[2] || normalizeOption(parsed.options.asset);
9479
+ if (!collectionId || !assetId) throw new Error("Usage: makefx collections add-asset <collection-id> <asset-id>");
9480
+ const item = await withCollectionClient(ctx, deps, (client) => client.createCollectionItem({
9481
+ collectionId,
9482
+ id: normalizeOption(parsed.options.id),
9483
+ subjectType: "asset",
9484
+ assetId,
9485
+ role: normalizeOption(parsed.options.role),
9486
+ pinnedVariantId: parseNullableOption(parsed.options.variant ?? parsed.options["pinned-variant"]),
9487
+ sortIndex: parseOptionalInteger(parsed.options["sort-index"], "sort-index")
9488
+ }));
9489
+ printJsonOrLine(parsed, deps, item, `Added asset ${assetId} to collection ${collectionId} as item ${item.id}`);
9490
+ return {
9491
+ type: "add",
9492
+ item
9493
+ };
9494
+ }
9495
+ if (subcommand === "add-variant") {
9496
+ const collectionId = parsed.positionals[1];
9497
+ const variantId = parsed.positionals[2] || normalizeOption(parsed.options.variant);
9498
+ if (!collectionId || !variantId) throw new Error("Usage: makefx collections add-variant <collection-id> <variant-id>");
9499
+ const item = await withCollectionClient(ctx, deps, (client) => client.createCollectionItem({
9500
+ collectionId,
9501
+ id: normalizeOption(parsed.options.id),
9502
+ subjectType: "variant",
9503
+ variantId,
9504
+ role: normalizeOption(parsed.options.role),
9505
+ sortIndex: parseOptionalInteger(parsed.options["sort-index"], "sort-index")
9506
+ }));
9507
+ printJsonOrLine(parsed, deps, item, `Added variant ${variantId} to collection ${collectionId} as item ${item.id}`);
9508
+ return {
9509
+ type: "add",
9510
+ item
9511
+ };
9512
+ }
9513
+ if (subcommand === "remove") {
9514
+ const collectionId = parsed.positionals[1];
9515
+ const itemId = parsed.positionals[2] || normalizeOption(parsed.options.item);
9516
+ if (!collectionId || !itemId) throw new Error("Usage: makefx collections remove <collection-id> <item-id>");
9517
+ await withCollectionClient(ctx, deps, (client) => client.deleteCollectionItem(collectionId, itemId));
9518
+ deps.print(`Removed item ${itemId} from collection ${collectionId}`);
9519
+ return {
9520
+ type: "remove",
9521
+ collectionId,
9522
+ itemId
9523
+ };
9524
+ }
9525
+ if (subcommand === "reorder") {
9526
+ const collectionId = parsed.positionals[1];
9527
+ const itemIds = splitIds(parsed.positionals[2] || normalizeOption(parsed.options.items));
9528
+ if (!collectionId || itemIds.length === 0) throw new Error("Usage: makefx collections reorder <collection-id> <item-id,item-id,...>");
9529
+ const items = await withCollectionClient(ctx, deps, (client) => client.reorderCollectionItems(collectionId, itemIds));
9530
+ if (parsed.options.json === "true") deps.print(JSON.stringify(items, null, 2));
9531
+ else deps.print(`Reordered ${items.length} item(s) in collection ${collectionId}`);
9532
+ return {
9533
+ type: "reorder",
9534
+ items
9535
+ };
9536
+ }
9537
+ throw new Error(`Unknown collections command: ${subcommand}`);
9538
+ }
9539
+ async function buildContext$2(parsed, deps) {
9540
+ const projectConfig = await deps.loadProjectConfig();
9541
+ const env = resolveCommandEnvironment(parsed, projectConfig);
9542
+ const spaceId = resolveCommandSpace(parsed, projectConfig);
9543
+ if (!spaceId) throw new Error("--space is required, or run: makefx init --space <id>");
9544
+ const config = await deps.loadConfig(env);
9545
+ if (!config) throw new Error(`Not logged in to ${env} environment. Run: ${loginCommandForEnvironment(env)}`);
9546
+ if (config.token.expiresAt < Date.now()) throw new Error(`Token expired for ${env} environment. Run: ${loginCommandForEnvironment(env)}`);
9547
+ if (env === "local") process$1.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
9548
+ return {
9549
+ env,
9550
+ spaceId,
9551
+ baseUrl: deps.resolveBaseUrl(env),
9552
+ accessToken: config.token.accessToken
9553
+ };
9554
+ }
9555
+ async function withCollectionClient(ctx, deps, run) {
9556
+ const client = await deps.createMutationClient(ctx.env, ctx.spaceId);
9557
+ await client.connect();
9558
+ try {
9559
+ return await run(client);
9560
+ } finally {
9561
+ client.disconnect();
9562
+ }
9563
+ }
9564
+ async function listCollections(ctx, deps) {
9565
+ return (await fetchJson(ctx, deps, `/api/spaces/${encodeURIComponent(ctx.spaceId)}/collections`)).collections || [];
9566
+ }
9567
+ async function listCollectionItems(ctx, deps, collectionId) {
9568
+ return (await fetchJson(ctx, deps, `/api/spaces/${encodeURIComponent(ctx.spaceId)}/collections/${encodeURIComponent(collectionId)}/items`)).items || [];
9569
+ }
9570
+ async function fetchJson(ctx, deps, apiPath) {
9571
+ const response = await deps.fetch(`${ctx.baseUrl}${apiPath}`, { headers: {
9572
+ "Authorization": `Bearer ${ctx.accessToken}`,
9573
+ "Accept": "application/json"
9574
+ } });
9575
+ if (!response.ok) {
9576
+ const errorText = await response.text();
9577
+ throw new Error(`Request failed (${response.status}): ${errorText || response.statusText}`);
9578
+ }
9579
+ return response.json();
9580
+ }
9581
+ function normalizeOption(value) {
9582
+ return !value || value === "true" ? void 0 : value;
9583
+ }
9584
+ function parseNullableOption(value) {
9585
+ if (value === void 0) return void 0;
9586
+ if (value === "true") return void 0;
9587
+ if (value === "" || value === "null" || value === "none") return null;
9588
+ return value;
9589
+ }
9590
+ function parseKind(value) {
9591
+ if (!value) return void 0;
9592
+ if (!COLLECTION_KINDS.has(value)) throw new Error(`Invalid collection kind "${value}". Expected one of: ${Array.from(COLLECTION_KINDS).join(", ")}`);
9593
+ return value;
9594
+ }
9595
+ function parseOptionalInteger(value, name) {
9596
+ const normalized = normalizeOption(value);
9597
+ if (!normalized) return void 0;
9598
+ const parsed = Number.parseInt(normalized, 10);
9599
+ if (!Number.isInteger(parsed) || String(parsed) !== normalized) throw new Error(`--${name} must be an integer`);
9600
+ return parsed;
9601
+ }
9602
+ function splitIds(value) {
9603
+ return (value ?? "").split(",").map((item) => item.trim()).filter(Boolean);
9604
+ }
9605
+ function printJsonOrLine(parsed, deps, value, line) {
9606
+ if (parsed.options.json === "true") deps.print(JSON.stringify(value, null, 2));
9607
+ else deps.print(line);
9608
+ }
9609
+ function printCollections(collections, print) {
9610
+ if (collections.length === 0) {
9611
+ print("No collections found.");
9612
+ return;
9613
+ }
9614
+ print(`Found ${collections.length} collection(s):\n`);
9615
+ print("Sort".padEnd(8) + "Kind".padEnd(15) + "Collection".padEnd(38) + "Name");
9616
+ print("-".repeat(85));
9617
+ for (const collection of collections) print(String(collection.sort_index).padEnd(8) + truncate(collection.kind, 13).padEnd(15) + truncate(collection.id, 36).padEnd(38) + truncate(collection.name, 40));
9618
+ }
9619
+ function printCollectionItems(items, print) {
9620
+ if (items.length === 0) {
9621
+ print("No collection items found.");
9622
+ return;
9623
+ }
9624
+ print(`Found ${items.length} collection item(s):\n`);
9625
+ print("Sort".padEnd(8) + "Subject".padEnd(10) + "Item".padEnd(34) + "Asset".padEnd(28) + "Variant".padEnd(28) + "Role");
9626
+ print("-".repeat(128));
9627
+ for (const item of items) print(String(item.sort_index).padEnd(8) + item.subject_type.padEnd(10) + truncate(item.id, 32).padEnd(34) + truncate(item.asset_id || "-", 26).padEnd(28) + truncate(item.variant_id || item.pinned_variant_id || "-", 26).padEnd(28) + truncate(item.role, 24));
9628
+ }
9629
+ function printUsage$1() {
9630
+ console.log(`
9631
+ Usage:
9632
+ makefx collections
9633
+ makefx collections --json
9634
+ makefx collections create "<name>" [--kind custom]
9635
+ makefx collections delete <collection-id>
9636
+ makefx collections items <collection-id>
9637
+ makefx collections add-asset <collection-id> <asset-id> [--variant <variant-id>] [--role <role>]
9638
+ makefx collections add-variant <collection-id> <variant-id> [--role <role>]
9639
+ makefx collections remove <collection-id> <item-id>
9640
+ makefx collections reorder <collection-id> <item-id,item-id,...>
9641
+ `);
9642
+ }
9643
+ //#endregion
9178
9644
  //#region src/cli/commands/usage.ts
9179
9645
  var defaultDeps$1 = {
9180
9646
  loadConfig: loadStoredConfig,
@@ -9475,7 +9941,7 @@ Options:
9475
9941
  }
9476
9942
  //#endregion
9477
9943
  //#region src/cli/index.ts
9478
- var CLI_VERSION = "1.0.6+d87c3032db88";
9944
+ var CLI_VERSION = "1.0.7+686298a71cee";
9479
9945
  async function main() {
9480
9946
  const argv = process$1.argv.slice(2);
9481
9947
  if (argv[0] === "--") argv.shift();
@@ -9526,7 +9992,8 @@ function imageCapabilityHelp() {
9526
9992
  --size <value> Output size (${sizeLines})
9527
9993
  --aspect <ratio> Aspect ratio: ${imageAspectValues()}
9528
9994
  --refs <refs> Reference limit (${referenceLines})
9529
- --follow <id> Resume/download a variant after a long generation or disconnect`;
9995
+ --follow <id> Resume/download a variant after a long generation or disconnect
9996
+ --collection <id> Place the completed asset in a Space collection`;
9530
9997
  }
9531
9998
  function videoResolutionValues() {
9532
9999
  return optionValues(uniqueValues(VIDEO_GENERATION_TIERS.flatMap((tier) => getVideoGenerationResolutionsForTier(tier))));
@@ -9554,6 +10021,7 @@ function videoCapabilityHelp() {
9554
10021
  --first-frame <ref> Veo start frame image variant or local image path
9555
10022
  --last-frame <ref> Veo final frame image variant or local image path
9556
10023
  --follow <id> Resume/download a variant after a long generation or disconnect
10024
+ --collection <id> Place the completed asset in a Space collection
9557
10025
 
9558
10026
  Veo 3.1 frames:
9559
10027
  --first-frame resolves to the first referenceVariantId and becomes the Veo top-level image input.
@@ -9620,6 +10088,10 @@ function printCommandHelp(command, positionals) {
9620
10088
  case "assets":
9621
10089
  printAssetsHelp();
9622
10090
  return;
10091
+ case "collections":
10092
+ case "collection":
10093
+ printCollectionsHelp();
10094
+ return;
9623
10095
  case "usage":
9624
10096
  printUsageHelp();
9625
10097
  return;
@@ -9666,6 +10138,8 @@ Inspect:
9666
10138
  assets show <asset-id> Show variants and lineage
9667
10139
  assets download <variant-id> -o <file> Download variant media
9668
10140
  assets clone <variant-id> --from-space <id> --to-space <id>
10141
+ collections add-asset <collection-id> <asset-id>
10142
+ collections add-variant <collection-id> <variant-id>
9669
10143
  variants rate <variant-id> approved|rejected
9670
10144
  variants star <variant-id>
9671
10145
 
@@ -9674,6 +10148,7 @@ Details:
9674
10148
  help audio Speech, dialogue, music, and SFX
9675
10149
  help video Video generate/refine/derive and keyframes
9676
10150
  help assets Asset inspection and downloads
10151
+ help collections Space collection organization
9677
10152
  help spaces Space setup and selection
9678
10153
  help upload Upload local media into a Space
9679
10154
 
@@ -9711,7 +10186,8 @@ Examples:
9711
10186
  makefx derive --refs character.png,background.png --name "Hero Scene" --type scene "Place the character in the background" -o hero-scene.png
9712
10187
  makefx refine --variant variant_123 "Keep composition, make the lighting warmer" -o warm.png
9713
10188
 
9714
- If the CLI stops waiting after a job has started, rerun the printed --follow command to download the variant.
10189
+ If the CLI stops waiting after a job has started, the job keeps running in Make Effects.
10190
+ Rerun the printed --follow command to resume the same variant instead of starting a duplicate.
9715
10191
  `);
9716
10192
  }
9717
10193
  function printInitHelp() {
@@ -9805,31 +10281,33 @@ function printForgeHelp(command) {
9805
10281
  if (command === "generate") {
9806
10282
  console.log(`
9807
10283
  Usage:
9808
- makefx generate "prompt" --name <name> --type <type> -o <file> [--model ${optionValues(IMAGE_MODEL_SELECTIONS)}] [--size ${imageSizeValues()}] [--aspect <ratio>] [--space <id>]
9809
- makefx generate --follow <variant_id> -o <file> [--space <id>]
10284
+ makefx generate "prompt" --name <name> --type <type> -o <file> [--model ${optionValues(IMAGE_MODEL_SELECTIONS)}] [--size ${imageSizeValues()}] [--aspect <ratio>] [--collection <id>] [--space <id>]
10285
+ makefx generate --follow <variant_id> -o <file> [--collection <id>] [--space <id>]
9810
10286
 
9811
10287
  ${imageCapabilityHelp()}
9812
10288
 
9813
- If the CLI stops waiting after a job has started, rerun the printed --follow command to download the variant.
10289
+ If the CLI stops waiting after a job has started, the job keeps running in Make Effects.
10290
+ Rerun the printed --follow command to resume the same variant instead of starting a duplicate.
9814
10291
  `);
9815
10292
  return;
9816
10293
  }
9817
10294
  if (command === "refine") {
9818
10295
  console.log(`
9819
10296
  Usage:
9820
- makefx refine --variant <variant_id> "prompt" -o <file> [--model ${optionValues(IMAGE_MODEL_SELECTIONS)}] [--size ${imageSizeValues()}] [--aspect <ratio>] [--space <id>]
9821
- makefx refine --follow <variant_id> -o <file> [--space <id>]
10297
+ makefx refine --variant <variant_id> "prompt" -o <file> [--model ${optionValues(IMAGE_MODEL_SELECTIONS)}] [--size ${imageSizeValues()}] [--aspect <ratio>] [--collection <id>] [--space <id>]
10298
+ makefx refine --follow <variant_id> -o <file> [--collection <id>] [--space <id>]
9822
10299
 
9823
10300
  ${imageCapabilityHelp()}
9824
10301
 
9825
- If the CLI stops waiting after a job has started, rerun the printed --follow command to download the variant.
10302
+ If the CLI stops waiting after a job has started, the job keeps running in Make Effects.
10303
+ Rerun the printed --follow command to resume the same variant instead of starting a duplicate.
9826
10304
  `);
9827
10305
  return;
9828
10306
  }
9829
10307
  if (command === "batch") {
9830
10308
  console.log(`
9831
10309
  Usage:
9832
- makefx batch "prompt" --name <name> --type <type> --count <2-8> --output-dir <dir> [--model ${optionValues(IMAGE_MODEL_SELECTIONS)}] [--size ${imageSizeValues()}] [--aspect <ratio>]
10310
+ makefx batch "prompt" --name <name> --type <type> --count <2-8> --output-dir <dir> [--model ${optionValues(IMAGE_MODEL_SELECTIONS)}] [--size ${imageSizeValues()}] [--aspect <ratio>] [--collection <id>]
9833
10311
 
9834
10312
  ${imageCapabilityHelp()}
9835
10313
 
@@ -9840,12 +10318,13 @@ Batch creates several variants. If waiting stops, inspect the Space or download
9840
10318
  }
9841
10319
  console.log(`
9842
10320
  Usage:
9843
- makefx derive --refs <variant_or_file,variant_or_file> --name <name> --type <type> "prompt" -o <file> [--model ${optionValues(IMAGE_MODEL_SELECTIONS)}] [--size ${imageSizeValues()}] [--aspect <ratio>] [--space <id>]
9844
- makefx derive --follow <variant_id> -o <file> [--space <id>]
10321
+ makefx derive --refs <variant_or_file,variant_or_file> --name <name> --type <type> "prompt" -o <file> [--model ${optionValues(IMAGE_MODEL_SELECTIONS)}] [--size ${imageSizeValues()}] [--aspect <ratio>] [--collection <id>] [--space <id>]
10322
+ makefx derive --follow <variant_id> -o <file> [--collection <id>] [--space <id>]
9845
10323
 
9846
10324
  ${imageCapabilityHelp()}
9847
10325
 
9848
- If the CLI stops waiting after a job has started, rerun the printed --follow command to download the variant.
10326
+ If the CLI stops waiting after a job has started, the job keeps running in Make Effects.
10327
+ Rerun the printed --follow command to resume the same variant instead of starting a duplicate.
9849
10328
  `);
9850
10329
  }
9851
10330
  function printAudioHelp(positionals) {
@@ -9864,23 +10343,24 @@ Lists ElevenLabs voices available to the connected account when ElevenLabs is th
9864
10343
  if (second === "generate") {
9865
10344
  console.log(`
9866
10345
  Usage:
9867
- makefx audio ${first} generate "prompt" --name <name> -o <file> [--space <id>]
9868
- makefx audio ${first} generate --input <file> --name <name> -o <file> [--space <id>]
9869
- makefx audio ${first} generate --follow <variant_id> -o <file> [--space <id>]
9870
- ${first === "music" ? " makefx audio music generate \"prompt\" --provider lyria --name <name> -o <file> [--space <id>]\n" : ""}
10346
+ makefx audio ${first} generate "prompt" --name <name> -o <file> [--collection <id>] [--space <id>]
10347
+ makefx audio ${first} generate --input <file> --name <name> -o <file> [--collection <id>] [--space <id>]
10348
+ makefx audio ${first} generate --follow <variant_id> -o <file> [--collection <id>] [--space <id>]
10349
+ ${first === "music" ? " makefx audio music generate \"prompt\" --provider lyria --name <name> -o <file> [--collection <id>] [--space <id>]\n" : ""}
9871
10350
  ${first === "music" ? "Provider selection:\n --provider <elevenlabs|lyria> Music provider (default: server default)\n" : ""}
9872
10351
  ${first === "speech" || first === "dialogue" ? "Model selection:\n --model <model_id> ElevenLabs model (default: eleven_v3)\n" : ""}
9873
10352
  ${first === "speech" || first === "dialogue" ? "Voice selection:\n --voice <voice_id> Speech voice, or dialogue fallback voice\n --dialogue-voices <id,id,...> Dialogue voices ordered by first speaker appearance\n" : ""}
9874
10353
  Long runs:
9875
10354
  --follow <variant_id> Resume/download a variant after a long generation or disconnect
10355
+ --collection <id> Place the completed asset in a Space collection
9876
10356
  `);
9877
10357
  return;
9878
10358
  }
9879
10359
  if (second === "batch") {
9880
10360
  console.log(`
9881
10361
  Usage:
9882
- makefx audio ${first} batch "prompt" --name <name> --count <2-8> --output-dir <dir> [--space <id>]
9883
- ${first === "music" ? " makefx audio music batch \"prompt\" --provider lyria --name <name> --count <2-8> --output-dir <dir> [--space <id>]\n" : ""}
10362
+ makefx audio ${first} batch "prompt" --name <name> --count <2-8> --output-dir <dir> [--collection <id>] [--space <id>]
10363
+ ${first === "music" ? " makefx audio music batch \"prompt\" --provider lyria --name <name> --count <2-8> --output-dir <dir> [--collection <id>] [--space <id>]\n" : ""}
9884
10364
  ${first === "music" ? "Provider selection:\n --provider <elevenlabs|lyria> Music provider (default: server default)\n" : ""}
9885
10365
  ${first === "speech" || first === "dialogue" ? "Model selection:\n --model <model_id> ElevenLabs model (default: eleven_v3)\n" : ""}
9886
10366
  ${first === "speech" || first === "dialogue" ? "Voice selection:\n --voice <voice_id> Speech voice, or dialogue fallback voice\n --dialogue-voices <id,id,...> Dialogue voices ordered by first speaker appearance\n" : ""}
@@ -9889,8 +10369,8 @@ ${first === "speech" || first === "dialogue" ? "Voice selection:\n --voice <voi
9889
10369
  }
9890
10370
  console.log(`
9891
10371
  Usage:
9892
- makefx audio ${first} generate "prompt" --name <name> -o <file> [--space <id>]
9893
- makefx audio ${first} batch "prompt" --name <name> --count <2-8> --output-dir <dir> [--space <id>]
10372
+ makefx audio ${first} generate "prompt" --name <name> -o <file> [--collection <id>] [--space <id>]
10373
+ makefx audio ${first} batch "prompt" --name <name> --count <2-8> --output-dir <dir> [--collection <id>] [--space <id>]
9894
10374
  ${first === "music" ? "\nProvider selection:\n --provider <elevenlabs|lyria> Music provider (default: server default)\n" : ""}
9895
10375
  ${first === "speech" || first === "dialogue" ? "\nModel selection:\n --model <model_id> ElevenLabs model (default: eleven_v3)\n" : ""}
9896
10376
  ${first === "speech" || first === "dialogue" ? "\nVoice selection:\n --voice <voice_id> Speech voice, or dialogue fallback voice\n --dialogue-voices <id,id,...> Dialogue voices ordered by first speaker appearance\n" : ""}
@@ -9900,31 +10380,31 @@ ${first === "speech" || first === "dialogue" ? "\nVoice selection:\n --voice <v
9900
10380
  if (first === "generate") {
9901
10381
  console.log(`
9902
10382
  Usage:
9903
- makefx audio generate "prompt" --name <name> --type <type> -o <file> [--space <id>]
10383
+ makefx audio generate "prompt" --name <name> --type <type> -o <file> [--collection <id>] [--space <id>]
9904
10384
 
9905
10385
  Preferred mode form:
9906
- makefx audio <${modes}> generate "prompt" --name <name> -o <file> [--space <id>]
10386
+ makefx audio <${modes}> generate "prompt" --name <name> -o <file> [--collection <id>] [--space <id>]
9907
10387
  `);
9908
10388
  return;
9909
10389
  }
9910
10390
  if (first === "batch") {
9911
10391
  console.log(`
9912
10392
  Usage:
9913
- makefx audio batch "prompt" --name <name> --type <type> --count <2-8> --output-dir <dir> [--space <id>]
10393
+ makefx audio batch "prompt" --name <name> --type <type> --count <2-8> --output-dir <dir> [--collection <id>] [--space <id>]
9914
10394
 
9915
10395
  Preferred mode form:
9916
- makefx audio <${modes}> batch "prompt" --name <name> --count <2-8> --output-dir <dir> [--space <id>]
10396
+ makefx audio <${modes}> batch "prompt" --name <name> --count <2-8> --output-dir <dir> [--collection <id>] [--space <id>]
9917
10397
  `);
9918
10398
  return;
9919
10399
  }
9920
10400
  console.log(`
9921
10401
  Usage:
9922
- makefx audio <${modes}> generate "prompt" --name <name> -o <file> [--space <id>]
9923
- makefx audio <${modes}> generate --input <file> --name <name> -o <file> [--space <id>]
9924
- makefx audio <${modes}> generate --follow <variant_id> -o <file> [--space <id>]
9925
- makefx audio <${modes}> batch "prompt" --name <name> --count <2-8> --output-dir <dir> [--space <id>]
10402
+ makefx audio <${modes}> generate "prompt" --name <name> -o <file> [--collection <id>] [--space <id>]
10403
+ makefx audio <${modes}> generate --input <file> --name <name> -o <file> [--collection <id>] [--space <id>]
10404
+ makefx audio <${modes}> generate --follow <variant_id> -o <file> [--collection <id>] [--space <id>]
10405
+ makefx audio <${modes}> batch "prompt" --name <name> --count <2-8> --output-dir <dir> [--collection <id>] [--space <id>]
9926
10406
  makefx audio voices [--json]
9927
- makefx audio music generate "prompt" --provider <elevenlabs|lyria> --name <name> -o <file> [--space <id>]
10407
+ makefx audio music generate "prompt" --provider <elevenlabs|lyria> --name <name> -o <file> [--collection <id>] [--space <id>]
9928
10408
 
9929
10409
  Modes:
9930
10410
  speech Spoken narration or voiceover
@@ -9940,11 +10420,12 @@ Model selection:
9940
10420
  --model <model_id> ElevenLabs speech/dialogue model (default: eleven_v3)
9941
10421
 
9942
10422
  Low-level compatibility:
9943
- makefx audio generate "prompt" --name <name> --type <type> -o <file> [--space <id>]
9944
- makefx audio batch "prompt" --name <name> --type <type> --count <2-8> --output-dir <dir> [--space <id>]
10423
+ makefx audio generate "prompt" --name <name> --type <type> -o <file> [--collection <id>] [--space <id>]
10424
+ makefx audio batch "prompt" --name <name> --type <type> --count <2-8> --output-dir <dir> [--collection <id>] [--space <id>]
9945
10425
 
9946
10426
  Long runs:
9947
- If the CLI stops waiting after a job has started, rerun the printed --follow command to download the variant.
10427
+ If the CLI stops waiting after a job has started, the job keeps running in Make Effects.
10428
+ Rerun the printed --follow command to resume the same variant instead of starting a duplicate.
9948
10429
  `);
9949
10430
  }
9950
10431
  function printVideoHelp(positionals) {
@@ -9952,8 +10433,8 @@ function printVideoHelp(positionals) {
9952
10433
  if (subcommand === "generate") {
9953
10434
  console.log(`
9954
10435
  Usage:
9955
- makefx video generate "prompt" --name <name> --type <type> -o <file> [--model ${videoModelValues()}] [--first-frame <variant_or_file>] [--last-frame <variant_or_file>] [--aspect ${videoAspectValues()}] [--resolution ${videoResolutionValues()}] [--duration ${videoDurationValues()}] [--tier ${videoTierValues()}] [--audio] [--space <id>]
9956
- makefx video generate --follow <variant_id> -o <file> [--space <id>]
10436
+ makefx video generate "prompt" --name <name> --type <type> -o <file> [--model ${videoModelValues()}] [--first-frame <variant_or_file>] [--last-frame <variant_or_file>] [--aspect ${videoAspectValues()}] [--resolution ${videoResolutionValues()}] [--duration ${videoDurationValues()}] [--tier ${videoTierValues()}] [--audio] [--collection <id>] [--space <id>]
10437
+ makefx video generate --follow <variant_id> -o <file> [--collection <id>] [--space <id>]
9957
10438
 
9958
10439
  ${videoCapabilityHelp()}
9959
10440
 
@@ -9961,15 +10442,16 @@ Audio:
9961
10442
  Video defaults to generated audio. Current Veo models do not support --no-audio; the CLI rejects it before creating a job.
9962
10443
 
9963
10444
  Long runs:
9964
- If the CLI stops waiting after a job has started, rerun the printed --follow command to download the variant.
10445
+ If the CLI stops waiting after a job has started, the job keeps running in Make Effects.
10446
+ Rerun the printed --follow command to resume the same variant instead of starting a duplicate.
9965
10447
  `);
9966
10448
  return;
9967
10449
  }
9968
10450
  if (subcommand === "refine") {
9969
10451
  console.log(`
9970
10452
  Usage:
9971
- makefx video refine --variant <variant_id> "prompt" -o <file> [--model ${videoModelValues()}] [--aspect ${videoAspectValues()}] [--resolution ${videoResolutionValues()}] [--duration ${videoDurationValues()}] [--tier ${videoTierValues()}] [--audio] [--space <id>]
9972
- makefx video refine --follow <variant_id> -o <file> [--space <id>]
10453
+ makefx video refine --variant <variant_id> "prompt" -o <file> [--model ${videoModelValues()}] [--aspect ${videoAspectValues()}] [--resolution ${videoResolutionValues()}] [--duration ${videoDurationValues()}] [--tier ${videoTierValues()}] [--audio] [--collection <id>] [--space <id>]
10454
+ makefx video refine --follow <variant_id> -o <file> [--collection <id>] [--space <id>]
9973
10455
 
9974
10456
  ${videoCapabilityHelp()}
9975
10457
 
@@ -9977,16 +10459,17 @@ Audio:
9977
10459
  Video defaults to generated audio. Current Veo models do not support --no-audio; the CLI rejects it before creating a job.
9978
10460
 
9979
10461
  Long runs:
9980
- If the CLI stops waiting after a job has started, rerun the printed --follow command to download the variant.
10462
+ If the CLI stops waiting after a job has started, the job keeps running in Make Effects.
10463
+ Rerun the printed --follow command to resume the same variant instead of starting a duplicate.
9981
10464
  `);
9982
10465
  return;
9983
10466
  }
9984
10467
  if (subcommand === "derive") {
9985
10468
  console.log(`
9986
10469
  Usage:
9987
- makefx video derive --refs <variant_or_file,variant_or_file> --name <name> --type <type> "prompt" -o <file> [--model ${videoModelValues()}] [--aspect ${videoAspectValues()}] [--resolution ${videoResolutionValues()}] [--duration ${videoDurationValues()}] [--tier ${videoTierValues()}] [--audio] [--space <id>]
9988
- makefx video derive --first-frame <variant_or_file> [--last-frame <variant_or_file>] --name <name> --type <type> "prompt" -o <file> [--model veo-3.1] [--aspect ${videoAspectValues()}] [--resolution ${videoResolutionValues()}] [--duration ${videoDurationValues()}] [--tier ${videoTierValues()}] [--audio] [--space <id>]
9989
- makefx video derive --follow <variant_id> -o <file> [--space <id>]
10470
+ makefx video derive --refs <variant_or_file,variant_or_file> --name <name> --type <type> "prompt" -o <file> [--model ${videoModelValues()}] [--aspect ${videoAspectValues()}] [--resolution ${videoResolutionValues()}] [--duration ${videoDurationValues()}] [--tier ${videoTierValues()}] [--audio] [--collection <id>] [--space <id>]
10471
+ makefx video derive --first-frame <variant_or_file> [--last-frame <variant_or_file>] --name <name> --type <type> "prompt" -o <file> [--model veo-3.1] [--aspect ${videoAspectValues()}] [--resolution ${videoResolutionValues()}] [--duration ${videoDurationValues()}] [--tier ${videoTierValues()}] [--audio] [--collection <id>] [--space <id>]
10472
+ makefx video derive --follow <variant_id> -o <file> [--collection <id>] [--space <id>]
9990
10473
 
9991
10474
  ${videoCapabilityHelp()}
9992
10475
 
@@ -9994,17 +10477,18 @@ Audio:
9994
10477
  Video defaults to generated audio. Current Veo models do not support --no-audio; the CLI rejects it before creating a job.
9995
10478
 
9996
10479
  Long runs:
9997
- If the CLI stops waiting after a job has started, rerun the printed --follow command to download the variant.
10480
+ If the CLI stops waiting after a job has started, the job keeps running in Make Effects.
10481
+ Rerun the printed --follow command to resume the same variant instead of starting a duplicate.
9998
10482
  `);
9999
10483
  return;
10000
10484
  }
10001
10485
  console.log(`
10002
10486
  Usage:
10003
- makefx video generate "prompt" --name <name> --type <type> -o <file> [--model ${videoModelValues()}] [--first-frame <variant_or_file>] [--last-frame <variant_or_file>] [--aspect ${videoAspectValues()}] [--resolution ${videoResolutionValues()}] [--duration ${videoDurationValues()}] [--tier ${videoTierValues()}] [--audio] [--space <id>]
10004
- makefx video generate --follow <variant_id> -o <file> [--space <id>]
10005
- makefx video refine --variant <variant_id> "prompt" -o <file> [--model ${videoModelValues()}] [--aspect ${videoAspectValues()}] [--resolution ${videoResolutionValues()}] [--duration ${videoDurationValues()}] [--tier ${videoTierValues()}] [--audio] [--space <id>]
10006
- makefx video derive --refs <variant_or_file,variant_or_file> --name <name> --type <type> "prompt" -o <file> [--model ${videoModelValues()}] [--aspect ${videoAspectValues()}] [--resolution ${videoResolutionValues()}] [--duration ${videoDurationValues()}] [--tier ${videoTierValues()}] [--audio] [--space <id>]
10007
- makefx video derive --first-frame <variant_or_file> [--last-frame <variant_or_file>] --name <name> --type <type> "prompt" -o <file> [--model veo-3.1] [--aspect ${videoAspectValues()}] [--resolution ${videoResolutionValues()}] [--duration ${videoDurationValues()}] [--tier ${videoTierValues()}] [--audio] [--space <id>]
10487
+ makefx video generate "prompt" --name <name> --type <type> -o <file> [--model ${videoModelValues()}] [--first-frame <variant_or_file>] [--last-frame <variant_or_file>] [--aspect ${videoAspectValues()}] [--resolution ${videoResolutionValues()}] [--duration ${videoDurationValues()}] [--tier ${videoTierValues()}] [--audio] [--collection <id>] [--space <id>]
10488
+ makefx video generate --follow <variant_id> -o <file> [--collection <id>] [--space <id>]
10489
+ makefx video refine --variant <variant_id> "prompt" -o <file> [--model ${videoModelValues()}] [--aspect ${videoAspectValues()}] [--resolution ${videoResolutionValues()}] [--duration ${videoDurationValues()}] [--tier ${videoTierValues()}] [--audio] [--collection <id>] [--space <id>]
10490
+ makefx video derive --refs <variant_or_file,variant_or_file> --name <name> --type <type> "prompt" -o <file> [--model ${videoModelValues()}] [--aspect ${videoAspectValues()}] [--resolution ${videoResolutionValues()}] [--duration ${videoDurationValues()}] [--tier ${videoTierValues()}] [--audio] [--collection <id>] [--space <id>]
10491
+ makefx video derive --first-frame <variant_or_file> [--last-frame <variant_or_file>] --name <name> --type <type> "prompt" -o <file> [--model veo-3.1] [--aspect ${videoAspectValues()}] [--resolution ${videoResolutionValues()}] [--duration ${videoDurationValues()}] [--tier ${videoTierValues()}] [--audio] [--collection <id>] [--space <id>]
10008
10492
 
10009
10493
  ${videoCapabilityHelp()}
10010
10494
 
@@ -10012,7 +10496,8 @@ Audio:
10012
10496
  Video defaults to generated audio. Current Veo models do not support --no-audio; the CLI rejects it before creating a job.
10013
10497
 
10014
10498
  Long runs:
10015
- If the CLI stops waiting after a job has started, rerun the printed --follow command to download the variant.
10499
+ If the CLI stops waiting after a job has started, the job keeps running in Make Effects.
10500
+ Rerun the printed --follow command to resume the same variant instead of starting a duplicate.
10016
10501
  `);
10017
10502
  }
10018
10503
  function printRunsHelp() {
@@ -10062,6 +10547,26 @@ Usage:
10062
10547
  makefx assets set-active <asset-id> <variant-id>
10063
10548
  `);
10064
10549
  }
10550
+ function printCollectionsHelp() {
10551
+ console.log(`
10552
+ Usage:
10553
+ makefx collections
10554
+ makefx collections --json
10555
+ makefx collections create "<name>" [--kind cast|backgrounds|scenes|thumbnails|maps|deliverables|custom]
10556
+ makefx collections delete <collection-id>
10557
+ makefx collections items <collection-id>
10558
+ makefx collections add-asset <collection-id> <asset-id> [--variant <variant-id>] [--role <role>]
10559
+ makefx collections add-variant <collection-id> <variant-id> [--role <role>]
10560
+ makefx collections remove <collection-id> <item-id>
10561
+ makefx collections reorder <collection-id> <item-id,item-id,...>
10562
+
10563
+ Options:
10564
+ --space <id> Target space ID; defaults from the initialized project
10565
+ --env <env> Environment (production|stage|local)
10566
+ --local Shortcut for --env local
10567
+ --json Print machine-readable output where supported
10568
+ `);
10569
+ }
10065
10570
  function printUsageHelp() {
10066
10571
  console.log(`
10067
10572
  Usage:
@@ -10144,6 +10649,10 @@ async function dispatchCommand(command, parsed) {
10144
10649
  case "assets":
10145
10650
  await handleAssets(parsed);
10146
10651
  break;
10652
+ case "collections":
10653
+ case "collection":
10654
+ await handleCollections(parsed);
10655
+ break;
10147
10656
  case "usage":
10148
10657
  await handleUsage(parsed);
10149
10658
  break;