makefx 1.0.5 → 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 -178
  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);
@@ -5454,17 +5454,11 @@ async function executeUpload(parsed, deps = defaultDeps$8) {
5454
5454
  if (config.token.expiresAt < Date.now()) throw new Error(`Token expired for ${env} environment. Run: ${loginCommandForEnvironment(env)}`);
5455
5455
  const baseUrl = deps.resolveBaseUrl(env);
5456
5456
  const accessToken = config.token.accessToken;
5457
- const ctx = {
5458
- spaceId,
5459
- baseUrl,
5460
- accessToken
5461
- };
5462
5457
  if (env === "local") process$1.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
5463
5458
  try {
5464
5459
  const fileStat = await deps.stat(filePath);
5465
5460
  if (!fileStat.isFile()) throw new Error(`"${filePath}" is not a file`);
5466
5461
  if (fileStat.size > 10485760) throw new Error(`File too large (${(fileStat.size / 1024 / 1024).toFixed(2)}MB). Maximum size: 10MB`);
5467
- await preflightSourceVariants(ctx, deps, sourceVariantIds);
5468
5462
  const fileBuffer = await deps.readFile(filePath);
5469
5463
  const fileName = path.basename(filePath);
5470
5464
  const formData = new FormData();
@@ -5605,30 +5599,7 @@ function parseCsvOption(value, optionName) {
5605
5599
  if (value === "true") throw new UploadUsageError(`${optionName} requires a value`);
5606
5600
  return value.split(",").map((entry) => entry.trim()).filter(Boolean);
5607
5601
  }
5608
- async function preflightSourceVariants(ctx, deps, sourceVariantIds) {
5609
- if (sourceVariantIds.length === 0) return;
5610
- const state = await fetchVariantState(ctx, deps);
5611
- for (const sourceVariantId of sourceVariantIds) if (!state.variantsById.has(sourceVariantId)) throw new Error(`Source variant not found in space: ${sourceVariantId}`);
5612
- }
5613
- async function fetchVariantState(ctx, deps) {
5614
- const assets = (await fetchJson$1(ctx, deps, `/api/spaces/${ctx.spaceId}/assets`)).assets ?? [];
5615
- const variantsById = /* @__PURE__ */ new Map();
5616
- await Promise.all(assets.map(async (asset) => {
5617
- const details = await fetchJson$1(ctx, deps, `/api/spaces/${ctx.spaceId}/assets/${encodeURIComponent(asset.id)}`);
5618
- for (const variant of details.variants ?? []) variantsById.set(variant.id, variant);
5619
- }));
5620
- return { variantsById };
5621
- }
5622
- async function fetchJson$1(ctx, deps, requestPath) {
5623
- const response = await deps.fetch(`${ctx.baseUrl}${requestPath}`, { headers: {
5624
- Authorization: `Bearer ${ctx.accessToken}`,
5625
- Accept: "application/json"
5626
- } });
5627
- const data = await response.json().catch(() => ({}));
5628
- if (!response.ok) throw new Error("error" in data ? data.error : `Request failed: ${response.status}`);
5629
- return data;
5630
- }
5631
- function printUsage$8() {
5602
+ function printUsage$9() {
5632
5603
  console.log(`
5633
5604
  Usage:
5634
5605
  makefx upload <file> --asset <id> [--space <id>] Upload media to existing asset
@@ -5945,6 +5916,15 @@ var ServerConfirmationTimeoutError = class extends Error {
5945
5916
  this.name = "ServerConfirmationTimeoutError";
5946
5917
  }
5947
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
+ }
5948
5928
  var WebSocketClient = class WebSocketClient {
5949
5929
  ws = null;
5950
5930
  baseUrl;
@@ -6248,9 +6228,9 @@ var WebSocketClient = class WebSocketClient {
6248
6228
  case "error": {
6249
6229
  const errorMsg = message;
6250
6230
  const error = /* @__PURE__ */ new Error(`${errorMsg.code}: ${errorMsg.message}`);
6251
- const mutationConsumed = this.failMutationWaiters(error);
6252
- const estimateConsumed = this.failEstimateHandlers(error);
6253
- 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) {
6254
6234
  console.error(`Server error: ${formatServerErrorMessage(errorMsg.code, errorMsg.message)}`);
6255
6235
  this.onError?.(new Error(errorMsg.message));
6256
6236
  }
@@ -6315,14 +6295,36 @@ var WebSocketClient = class WebSocketClient {
6315
6295
  }
6316
6296
  return true;
6317
6297
  }
6318
- failEstimateHandlers(error) {
6319
- if (this.estimateHandlers.size === 0) return false;
6320
- for (const [requestId, handler] of this.estimateHandlers) {
6321
- clearTimeout(handler.timeout);
6298
+ failRequestHandler(requestId, error) {
6299
+ const estimateHandler = this.estimateHandlers.get(requestId);
6300
+ if (estimateHandler) {
6322
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);
6323
6324
  handler.reject(error);
6325
+ return true;
6324
6326
  }
6325
- return true;
6327
+ return false;
6326
6328
  }
6327
6329
  /**
6328
6330
  * Send a mutation message and resolve when a matching broadcast arrives, or
@@ -6407,6 +6409,42 @@ var WebSocketClient = class WebSocketClient {
6407
6409
  rating
6408
6410
  }, (msg) => msg.type === "variant:updated" && msg.variant.id === variantId && msg.variant.quality_rating === rating)).variant;
6409
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
+ }
6410
6448
  /**
6411
6449
  * Send a message through the WebSocket
6412
6450
  */
@@ -6990,7 +7028,7 @@ var MEDIA_OPERATION_MATRIX = [
6990
7028
  assetType: "speech",
6991
7029
  promptNoun: "speech clip",
6992
7030
  inheritsReferenceAssetType: false,
6993
- compatibleSlotMediaKinds: ["audio"],
7031
+ compatibleSlotMediaKinds: [],
6994
7032
  supportsBatch: true,
6995
7033
  cliNamespace: "audio",
6996
7034
  cliCommands: ["generate", "batch"],
@@ -7005,7 +7043,7 @@ var MEDIA_OPERATION_MATRIX = [
7005
7043
  assetType: "dialogue",
7006
7044
  promptNoun: "dialogue clip",
7007
7045
  inheritsReferenceAssetType: false,
7008
- compatibleSlotMediaKinds: ["audio"],
7046
+ compatibleSlotMediaKinds: [],
7009
7047
  supportsBatch: true,
7010
7048
  cliNamespace: "audio",
7011
7049
  cliCommands: ["generate", "batch"],
@@ -7020,7 +7058,7 @@ var MEDIA_OPERATION_MATRIX = [
7020
7058
  assetType: "music",
7021
7059
  promptNoun: "music cue",
7022
7060
  inheritsReferenceAssetType: false,
7023
- compatibleSlotMediaKinds: ["audio"],
7061
+ compatibleSlotMediaKinds: [],
7024
7062
  supportsBatch: true,
7025
7063
  cliNamespace: "audio",
7026
7064
  cliCommands: ["generate", "batch"],
@@ -7035,7 +7073,7 @@ var MEDIA_OPERATION_MATRIX = [
7035
7073
  assetType: "sfx",
7036
7074
  promptNoun: "sound effect",
7037
7075
  inheritsReferenceAssetType: false,
7038
- compatibleSlotMediaKinds: ["audio"],
7076
+ compatibleSlotMediaKinds: [],
7039
7077
  supportsBatch: true,
7040
7078
  cliNamespace: "audio",
7041
7079
  cliCommands: ["generate", "batch"],
@@ -7101,7 +7139,7 @@ function cliGenerationSupportsRefs(namespace) {
7101
7139
  //#endregion
7102
7140
  //#region src/cli/commands/forge.ts
7103
7141
  var CLI_GENERATION_MEDIA_KIND = getCliGenerationMediaKind("top-level");
7104
- var defaultDeps$7 = {
7142
+ var defaultDeps$8 = {
7105
7143
  loadConfig: loadStoredConfig,
7106
7144
  loadProjectConfig,
7107
7145
  resolveBaseUrl,
@@ -7175,10 +7213,10 @@ async function handleBatch(parsed) {
7175
7213
  }
7176
7214
  async function handleForgeCommand(command, parsed) {
7177
7215
  try {
7178
- await executeForgeCommand(command, parsed, defaultDeps$7);
7216
+ await executeForgeCommand(command, parsed, defaultDeps$8);
7179
7217
  } catch (error) {
7180
7218
  console.error("Error:", error instanceof Error ? error.message : error);
7181
- if (shouldPrintUsageForForgeError(error)) printUsage$7(command);
7219
+ if (shouldPrintUsageForForgeError(error)) printUsage$8(command);
7182
7220
  process$1.exitCode = 1;
7183
7221
  }
7184
7222
  }
@@ -7187,11 +7225,11 @@ function shouldPrintUsageForForgeError(error) {
7187
7225
  const message = error.message;
7188
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");
7189
7227
  }
7190
- async function executeForgeCommand(command, parsed, deps = defaultDeps$7, options = {}) {
7228
+ async function executeForgeCommand(command, parsed, deps = defaultDeps$8, options = {}) {
7191
7229
  const mediaKind = options.mediaKind || CLI_GENERATION_MEDIA_KIND;
7192
7230
  if (parsed.options.follow) {
7193
7231
  if (command === "batch") throw new Error("--follow is only supported for single-output generation commands");
7194
- const ctx = await buildContext$4(parsed, deps);
7232
+ const ctx = await buildContext$5(parsed, deps);
7195
7233
  const client = await deps.createClient(ctx.env, ctx.spaceId);
7196
7234
  try {
7197
7235
  await client.connect();
@@ -7205,7 +7243,7 @@ async function executeForgeCommand(command, parsed, deps = defaultDeps$7, option
7205
7243
  validateVideoAudioOptions(parsed, mediaKind);
7206
7244
  parseVideoGenerationOptions(parsed, mediaKind);
7207
7245
  validateVideoFrameReferenceOptions(command, parsed, mediaKind);
7208
- const ctx = await buildContext$4(parsed, deps);
7246
+ const ctx = await buildContext$5(parsed, deps);
7209
7247
  const client = await deps.createClient(ctx.env, ctx.spaceId);
7210
7248
  try {
7211
7249
  await client.connect();
@@ -7219,7 +7257,7 @@ async function executeForgeCommand(command, parsed, deps = defaultDeps$7, option
7219
7257
  client.disconnect();
7220
7258
  }
7221
7259
  }
7222
- async function executeAudioCommand(command, parsed, deps = defaultDeps$7, options = {}) {
7260
+ async function executeAudioCommand(command, parsed, deps = defaultDeps$8, options = {}) {
7223
7261
  const audioProfile = getCliGenerationProfile("audio");
7224
7262
  if (!audioProfile.commands.includes(command)) throw new Error(`Audio generation does not support ${command}`);
7225
7263
  const modeEntry = options.mode ? getMediaOperationEntry(options.mode) : void 0;
@@ -7232,7 +7270,7 @@ async function executeAudioCommand(command, parsed, deps = defaultDeps$7, option
7232
7270
  audioMode: options.mode
7233
7271
  });
7234
7272
  }
7235
- async function executeVideoCommand(command, parsed, deps = defaultDeps$7) {
7273
+ async function executeVideoCommand(command, parsed, deps = defaultDeps$8) {
7236
7274
  const videoProfile = getCliGenerationProfile("video");
7237
7275
  if (!videoProfile.commands.includes(command)) throw new Error(`Video generation does not support ${command}`);
7238
7276
  return executeForgeCommand(command, parsed, deps, {
@@ -7271,12 +7309,17 @@ async function executeGenerate(parsed, ctx, client, deps, mediaKind, followOptio
7271
7309
  const musicProvider = parseMusicProviderOption(parsed, mediaKind, assetType);
7272
7310
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
7273
7311
  const videoFrameRefs = parseVideoFrameReferenceOptions(parsed, "generate", mediaKind).refs;
7274
- const state = videoFrameRefs.length > 0 ? await requestSpaceState(client) : void 0;
7312
+ const state = videoFrameRefs.length > 0 ? await requestSpaceState$1(client) : void 0;
7275
7313
  const referenceVariantIds = videoFrameRefs.length > 0 ? await resolveReferenceVariantIds(videoFrameRefs, ctx, deps, state?.variants, CLI_GENERATION_MEDIA_KIND) : [];
7276
7314
  const imageOptions = parseImageGenerationOptions(parsed, mediaKind);
7277
7315
  const audioModelOptions = parseAudioGenerationOptions(parsed, mediaKind);
7278
7316
  const videoAudioOptions = parseVideoAudioOptions(parsed, mediaKind);
7279
7317
  const videoOptions = parseVideoGenerationOptions(parsed, mediaKind);
7318
+ const collectionPlacement = parseCollectionPlacementOptions(parsed);
7319
+ const followCommandOptions = {
7320
+ ...followOptions,
7321
+ ...collectionPlacement
7322
+ };
7280
7323
  await printPreflightEstimate(client, {
7281
7324
  operation: "generate",
7282
7325
  assetType,
@@ -7309,7 +7352,7 @@ async function executeGenerate(parsed, ctx, client, deps, mediaKind, followOptio
7309
7352
  ...videoOptions,
7310
7353
  onStarted: (started) => {
7311
7354
  startedVariantId = started.jobId;
7312
- printFollowHint(started, ctx, outputPath, "generate", mediaKind, followOptions);
7355
+ printFollowHint(started, ctx, outputPath, "generate", mediaKind, followCommandOptions);
7313
7356
  }
7314
7357
  });
7315
7358
  } catch (error) {
@@ -7319,7 +7362,7 @@ async function executeGenerate(parsed, ctx, client, deps, mediaKind, followOptio
7319
7362
  ctx,
7320
7363
  command: "generate",
7321
7364
  mediaKind,
7322
- followOptions
7365
+ followOptions: followCommandOptions
7323
7366
  });
7324
7367
  }
7325
7368
  const manifestPath = await saveGenerationManifest({
@@ -7336,6 +7379,7 @@ async function executeGenerate(parsed, ctx, client, deps, mediaKind, followOptio
7336
7379
  refs: videoFrameRefs,
7337
7380
  referenceVariantIds
7338
7381
  });
7382
+ await placeGeneratedAssetInCollection(result, ctx, deps, collectionPlacement);
7339
7383
  await downloadResult(result, outputPath, ctx, deps);
7340
7384
  printResult(result, outputPath, ctx, manifestPath);
7341
7385
  return result;
@@ -7363,7 +7407,7 @@ async function executeRefine(parsed, ctx, client, deps, mediaKind) {
7363
7407
  const prompt = getPrompt(parsed, "refine");
7364
7408
  const outputPath = getOutputPath$1(parsed);
7365
7409
  const sourceVariantId = requireOption$1(parsed, "variant");
7366
- const state = await requestSpaceState(client);
7410
+ const state = await requestSpaceState$1(client);
7367
7411
  const sourceVariant = state.variants.find((variant) => variant.id === sourceVariantId);
7368
7412
  if (!sourceVariant) throw new Error(`Variant not found in space sync state: ${sourceVariantId}`);
7369
7413
  const sourceAsset = state.assets.find((asset) => asset.id === sourceVariant.asset_id);
@@ -7371,6 +7415,8 @@ async function executeRefine(parsed, ctx, client, deps, mediaKind) {
7371
7415
  const imageOptions = parseImageGenerationOptions(parsed, mediaKind);
7372
7416
  const videoAudioOptions = parseVideoAudioOptions(parsed, mediaKind);
7373
7417
  const videoOptions = parseVideoGenerationOptions(parsed, mediaKind);
7418
+ const collectionPlacement = parseCollectionPlacementOptions(parsed);
7419
+ const followCommandOptions = { ...collectionPlacement };
7374
7420
  await printPreflightEstimate(client, {
7375
7421
  operation: "refine",
7376
7422
  assetId: sourceVariant.asset_id,
@@ -7398,7 +7444,7 @@ async function executeRefine(parsed, ctx, client, deps, mediaKind) {
7398
7444
  ...videoOptions,
7399
7445
  onStarted: (started) => {
7400
7446
  startedVariantId = started.jobId;
7401
- printFollowHint(started, ctx, outputPath, "refine", mediaKind);
7447
+ printFollowHint(started, ctx, outputPath, "refine", mediaKind, followCommandOptions);
7402
7448
  }
7403
7449
  });
7404
7450
  } catch (error) {
@@ -7407,7 +7453,8 @@ async function executeRefine(parsed, ctx, client, deps, mediaKind) {
7407
7453
  outputPath,
7408
7454
  ctx,
7409
7455
  command: "refine",
7410
- mediaKind
7456
+ mediaKind,
7457
+ followOptions: followCommandOptions
7411
7458
  });
7412
7459
  }
7413
7460
  const manifestPath = await saveGenerationManifest({
@@ -7424,6 +7471,7 @@ async function executeRefine(parsed, ctx, client, deps, mediaKind) {
7424
7471
  refs: [sourceVariantId],
7425
7472
  referenceVariantIds: [sourceVariantId]
7426
7473
  });
7474
+ await placeGeneratedAssetInCollection(result, ctx, deps, collectionPlacement);
7427
7475
  await downloadResult(result, outputPath, ctx, deps);
7428
7476
  printResult(result, outputPath, ctx, manifestPath);
7429
7477
  return result;
@@ -7439,10 +7487,15 @@ async function executeDerive(parsed, ctx, client, deps, mediaKind, followOptions
7439
7487
  if (referenceRefs.length === 0) throw new Error("--refs is required");
7440
7488
  const imageOptions = parseImageGenerationOptions(parsed, mediaKind);
7441
7489
  if (mediaKind === "image") validateImageReferenceCount(imageOptions.model, referenceRefs.length);
7442
- 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);
7443
7491
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
7444
7492
  const videoAudioOptions = parseVideoAudioOptions(parsed, mediaKind);
7445
7493
  const videoOptions = parseVideoGenerationOptions(parsed, mediaKind);
7494
+ const collectionPlacement = parseCollectionPlacementOptions(parsed);
7495
+ const followCommandOptions = {
7496
+ ...followOptions,
7497
+ ...collectionPlacement
7498
+ };
7446
7499
  await printPreflightEstimate(client, {
7447
7500
  operation: "derive",
7448
7501
  assetType,
@@ -7470,7 +7523,7 @@ async function executeDerive(parsed, ctx, client, deps, mediaKind, followOptions
7470
7523
  ...videoOptions,
7471
7524
  onStarted: (started) => {
7472
7525
  startedVariantId = started.jobId;
7473
- printFollowHint(started, ctx, outputPath, "derive", mediaKind, followOptions);
7526
+ printFollowHint(started, ctx, outputPath, "derive", mediaKind, followCommandOptions);
7474
7527
  }
7475
7528
  });
7476
7529
  } catch (error) {
@@ -7480,7 +7533,7 @@ async function executeDerive(parsed, ctx, client, deps, mediaKind, followOptions
7480
7533
  ctx,
7481
7534
  command: "derive",
7482
7535
  mediaKind,
7483
- followOptions
7536
+ followOptions: followCommandOptions
7484
7537
  });
7485
7538
  }
7486
7539
  const manifestPath = await saveGenerationManifest({
@@ -7497,6 +7550,7 @@ async function executeDerive(parsed, ctx, client, deps, mediaKind, followOptions
7497
7550
  refs: referenceRefs,
7498
7551
  referenceVariantIds
7499
7552
  });
7553
+ await placeGeneratedAssetInCollection(result, ctx, deps, collectionPlacement);
7500
7554
  await downloadResult(result, outputPath, ctx, deps);
7501
7555
  printResult(result, outputPath, ctx, manifestPath);
7502
7556
  return result;
@@ -7506,8 +7560,13 @@ async function executeFollow(parsed, ctx, client, deps, command, mediaKind, foll
7506
7560
  if (!variantId) throw new Error("--follow requires a variant ID");
7507
7561
  const outputPath = getOutputPath$1(parsed);
7508
7562
  const timeoutMs = parseFollowTimeoutMs(parsed, mediaKind);
7563
+ const collectionPlacement = parseCollectionPlacementOptions(parsed);
7564
+ const followCommandOptions = {
7565
+ ...followOptions,
7566
+ ...collectionPlacement
7567
+ };
7509
7568
  console.log(`Following variant ${variantId} in space ${ctx.spaceId}...`);
7510
- const state = await requestSpaceState(client);
7569
+ const state = await requestSpaceState$1(client);
7511
7570
  const initialVariant = state.variants.find((variant) => variant.id === variantId);
7512
7571
  if (!initialVariant) throw new Error(`Variant not found in space sync state: ${variantId}`);
7513
7572
  const initialStatus = initialVariant.status;
@@ -7527,7 +7586,7 @@ async function executeFollow(parsed, ctx, client, deps, command, mediaKind, foll
7527
7586
  }
7528
7587
  }
7529
7588
  });
7530
- const refreshedVariant = (await requestSpaceState(client)).variants.find((variant) => variant.id === variantId);
7589
+ const refreshedVariant = (await requestSpaceState$1(client)).variants.find((variant) => variant.id === variantId);
7531
7590
  result = refreshedVariant ? resultFromTerminalVariant(refreshedVariant, requestId) : void 0;
7532
7591
  if (result) client.cancelFollowVariant?.(variantId, requestId);
7533
7592
  else try {
@@ -7539,7 +7598,7 @@ async function executeFollow(parsed, ctx, client, deps, command, mediaKind, foll
7539
7598
  ctx,
7540
7599
  command,
7541
7600
  mediaKind,
7542
- followOptions
7601
+ followOptions: followCommandOptions
7543
7602
  });
7544
7603
  }
7545
7604
  }
@@ -7562,6 +7621,7 @@ async function executeFollow(parsed, ctx, client, deps, command, mediaKind, foll
7562
7621
  referenceVariantIds
7563
7622
  });
7564
7623
  await downloadResult(result, outputPath, ctx, deps);
7624
+ await placeGeneratedAssetInCollection(result, ctx, deps, collectionPlacement);
7565
7625
  printResult(result, outputPath, ctx, manifestPath);
7566
7626
  return result;
7567
7627
  }
@@ -7654,10 +7714,11 @@ async function executeBatch(parsed, ctx, client, deps, mediaKind, saveBatchManif
7654
7714
  const imageOptions = parseImageGenerationOptions(parsed, mediaKind);
7655
7715
  const audioModelOptions = parseAudioGenerationOptions(parsed, mediaKind);
7656
7716
  if (mediaKind === "image") validateImageReferenceCount(imageOptions.model, refs.length);
7657
- const state = await requestSpaceState(client);
7717
+ const state = await requestSpaceState$1(client);
7658
7718
  const referenceVariantIds = refs.length > 0 ? await resolveReferenceVariantIds(refs, ctx, deps, state.variants, mediaKind) : void 0;
7659
7719
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
7660
7720
  const runId = deps.createRunId();
7721
+ const collectionPlacement = parseCollectionPlacementOptions(parsed);
7661
7722
  await printPreflightEstimate(client, {
7662
7723
  operation: "batch",
7663
7724
  assetType,
@@ -7696,6 +7757,7 @@ async function executeBatch(parsed, ctx, client, deps, mediaKind, saveBatchManif
7696
7757
  }
7697
7758
  const sortedVariants = [...result.variants].sort((a, b) => a.created_at - b.created_at);
7698
7759
  const media = [];
7760
+ await placeGeneratedBatchAssetsInCollection(result, ctx, deps, collectionPlacement);
7699
7761
  for (let index = 0; index < sortedVariants.length; index += 1) {
7700
7762
  const variant = sortedVariants[index];
7701
7763
  const outputPath = path.join(outputDir, `${slugify(name)}-${String(index + 1).padStart(2, "0")}.${getOutputExtension(variant, mediaKind)}`);
@@ -7781,7 +7843,69 @@ async function saveGenerationManifest(input) {
7781
7843
  failed: []
7782
7844
  }, ctx.projectRoot);
7783
7845
  }
7784
- 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) {
7785
7909
  const projectConfig = await deps.loadProjectConfig();
7786
7910
  const env = resolveCommandEnvironment(parsed, projectConfig);
7787
7911
  const spaceId = resolveCommandSpace(parsed, projectConfig);
@@ -7898,7 +8022,7 @@ function validateReferenceVariant(ref, variants, mediaKind) {
7898
8022
  if (!variant.image_key && !variant.media_key) throw new Error(`Reference variant has no image or video media: ${ref}`);
7899
8023
  }
7900
8024
  }
7901
- function requestSpaceState(client) {
8025
+ function requestSpaceState$1(client) {
7902
8026
  return new Promise((resolve, reject) => {
7903
8027
  const timer = setTimeout(() => {
7904
8028
  reject(/* @__PURE__ */ new Error("Timed out waiting for space sync state"));
@@ -7951,12 +8075,13 @@ function printFollowHint(started, ctx, outputPath, command, mediaKind, options =
7951
8075
  }
7952
8076
  function throwGenerationWaitErrorIfNeeded(input, params) {
7953
8077
  if (!isGenerationWaitTimeout(input)) throw input;
7954
- 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"));
7955
8079
  const followCommand = formatFollowCommand(params.variantId, params.outputPath, params.ctx, params.command, params.mediaKind, params.followOptions);
7956
8080
  throw new Error([
7957
- "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.",
7958
8083
  "",
7959
- "Resume the download when the variant is ready:",
8084
+ "Resume the same job and download the variant when it is ready:",
7960
8085
  ` ${followCommand}`,
7961
8086
  "",
7962
8087
  `Variant: ${params.variantId}`
@@ -7964,7 +8089,7 @@ function throwGenerationWaitErrorIfNeeded(input, params) {
7964
8089
  }
7965
8090
  function throwBatchWaitErrorIfNeeded(input, started, ctx) {
7966
8091
  if (!isBatchWaitTimeout(input)) throw input;
7967
- 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"));
7968
8093
  const downloadExample = [
7969
8094
  "makefx assets download <variant_id> -o <file>",
7970
8095
  ...ctx.env === "local" ? ["--local"] : ["--env", ctx.env],
@@ -7972,7 +8097,8 @@ function throwBatchWaitErrorIfNeeded(input, started, ctx) {
7972
8097
  ctx.spaceId
7973
8098
  ].join(" ");
7974
8099
  throw new Error([
7975
- "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.",
7976
8102
  "",
7977
8103
  `Started variants: ${started.jobIds.join(", ")}`,
7978
8104
  "",
@@ -7999,6 +8125,7 @@ function formatFollowCommand(variantId, outputPath, ctx, command, mediaKind, opt
7999
8125
  command
8000
8126
  ] : ["makefx", command];
8001
8127
  const envArgs = ctx.env === "local" ? ["--local"] : ["--env", ctx.env];
8128
+ const collectionArgs = options.collectionId ? ["--collection", options.collectionId] : [];
8002
8129
  return [
8003
8130
  ...baseCommand,
8004
8131
  "--follow",
@@ -8007,7 +8134,8 @@ function formatFollowCommand(variantId, outputPath, ctx, command, mediaKind, opt
8007
8134
  outputPath,
8008
8135
  ...envArgs,
8009
8136
  "--space",
8010
- ctx.spaceId
8137
+ ctx.spaceId,
8138
+ ...collectionArgs
8011
8139
  ].map(shellQuote).join(" ");
8012
8140
  }
8013
8141
  function shellQuote(value) {
@@ -8250,34 +8378,34 @@ function cliImageSizeValues() {
8250
8378
  function cliImageAspectValues() {
8251
8379
  return cliOptionValues(getImageModelCapabilities().supportedAspectRatios);
8252
8380
  }
8253
- function printUsage$7(command) {
8381
+ function printUsage$8(command) {
8254
8382
  if (command === "generate") {
8255
8383
  console.log(`
8256
8384
  Usage:
8257
- makefx generate "prompt" --name <name> --type <type> -o <file> [--model pro|flash] [--size ${cliImageSizeValues()}] [--aspect ${cliImageAspectValues()}] [--space <id>]
8258
- 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>]
8259
8387
  `);
8260
8388
  return;
8261
8389
  }
8262
8390
  if (command === "refine") {
8263
8391
  console.log(`
8264
8392
  Usage:
8265
- makefx refine --variant <variant_id> "prompt" -o <file> [--model pro|flash] [--size ${cliImageSizeValues()}] [--aspect ${cliImageAspectValues()}] [--space <id>]
8266
- 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>]
8267
8395
  `);
8268
8396
  return;
8269
8397
  }
8270
8398
  if (command === "batch") {
8271
8399
  console.log(`
8272
8400
  Usage:
8273
- 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>]
8274
8402
  `);
8275
8403
  return;
8276
8404
  }
8277
8405
  console.log(`
8278
8406
  Usage:
8279
- 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>]
8280
- 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>]
8281
8409
  `);
8282
8410
  }
8283
8411
  function printBatchResult(result, outputDir, manifestPath, ctx, mediaKind) {
@@ -8291,7 +8419,7 @@ function printBatchResult(result, outputDir, manifestPath, ctx, mediaKind) {
8291
8419
  }
8292
8420
  //#endregion
8293
8421
  //#region src/cli/commands/audio.ts
8294
- var defaultDeps$6 = {
8422
+ var defaultDeps$7 = {
8295
8423
  loadConfig: loadStoredConfig,
8296
8424
  loadProjectConfig,
8297
8425
  resolveBaseUrl,
@@ -8304,11 +8432,11 @@ async function handleAudio(parsed) {
8304
8432
  await executeAudio(parsed);
8305
8433
  } catch (error) {
8306
8434
  console.error("Error:", error instanceof Error ? error.message : error);
8307
- printUsage$6();
8435
+ printUsage$7();
8308
8436
  process$1.exitCode = 1;
8309
8437
  }
8310
8438
  }
8311
- async function executeAudio(parsed, deps = defaultDeps$6) {
8439
+ async function executeAudio(parsed, deps = defaultDeps$7) {
8312
8440
  const invocation = parseAudioInvocation(parsed.positionals);
8313
8441
  if (invocation.command === "voices") return executeAudioVoices({
8314
8442
  ...parsed,
@@ -8381,7 +8509,7 @@ function printVoices(response, print) {
8381
8509
  function formatLabels(labels) {
8382
8510
  return Object.entries(labels).map(([key, value]) => `${key}:${value}`).join(", ");
8383
8511
  }
8384
- function printUsage$6() {
8512
+ function printUsage$7() {
8385
8513
  const modes = AUDIO_FORGE_MEDIA_MODES.join("|");
8386
8514
  console.log(`
8387
8515
  Usage:
@@ -8410,7 +8538,7 @@ async function handleVideo(parsed) {
8410
8538
  });
8411
8539
  } catch (error) {
8412
8540
  console.error("Error:", error instanceof Error ? error.message : error);
8413
- printUsage$5();
8541
+ printUsage$6();
8414
8542
  process$1.exitCode = 1;
8415
8543
  }
8416
8544
  }
@@ -8419,7 +8547,7 @@ function parseVideoCommand(value) {
8419
8547
  if (value && commands.includes(value)) return value;
8420
8548
  throw new Error("Video command is required: generate, refine, or derive");
8421
8549
  }
8422
- function printUsage$5() {
8550
+ function printUsage$6() {
8423
8551
  const aspectValues = VIDEO_GENERATION_ASPECT_RATIOS.join("|");
8424
8552
  const resolutionValues = Array.from(new Set(VIDEO_GENERATION_TIERS.flatMap((tier) => getVideoGenerationResolutionsForTier(tier)))).join("|");
8425
8553
  const durationValues = VIDEO_GENERATION_DURATION_SECONDS.join("|");
@@ -8458,17 +8586,17 @@ Audio:
8458
8586
  }
8459
8587
  //#endregion
8460
8588
  //#region src/cli/commands/init.ts
8461
- var defaultDeps$5 = { saveProjectConfig };
8589
+ var defaultDeps$6 = { saveProjectConfig };
8462
8590
  async function handleInit(parsed) {
8463
8591
  try {
8464
8592
  await executeInit(parsed);
8465
8593
  } catch (error) {
8466
8594
  console.error("Error:", error instanceof Error ? error.message : error);
8467
- printUsage$4();
8595
+ printUsage$5();
8468
8596
  process$1.exitCode = 1;
8469
8597
  }
8470
8598
  }
8471
- async function executeInit(parsed, deps = defaultDeps$5) {
8599
+ async function executeInit(parsed, deps = defaultDeps$6) {
8472
8600
  const environment = resolveCommandEnvironment(parsed);
8473
8601
  const spaceId = resolveCommandSpace(parsed);
8474
8602
  if (!spaceId) throw new Error("--space is required");
@@ -8494,7 +8622,7 @@ async function executeInit(parsed, deps = defaultDeps$5) {
8494
8622
  spaceId
8495
8623
  };
8496
8624
  }
8497
- function printUsage$4() {
8625
+ function printUsage$5() {
8498
8626
  console.log(`
8499
8627
  Usage:
8500
8628
  makefx init --space <id> [--env production|stage|local]
@@ -8503,7 +8631,7 @@ Usage:
8503
8631
  }
8504
8632
  //#endregion
8505
8633
  //#region src/cli/commands/runs.ts
8506
- var defaultDeps$4 = {
8634
+ var defaultDeps$5 = {
8507
8635
  loadProjectConfig,
8508
8636
  listRunManifests,
8509
8637
  readRunManifest,
@@ -8516,11 +8644,11 @@ async function handleRuns(parsed) {
8516
8644
  await executeRuns(parsed);
8517
8645
  } catch (error) {
8518
8646
  console.error("Error:", error instanceof Error ? error.message : error);
8519
- printUsage$3();
8647
+ printUsage$4();
8520
8648
  process$1.exitCode = 1;
8521
8649
  }
8522
8650
  }
8523
- async function executeRuns(parsed, deps = defaultDeps$4) {
8651
+ async function executeRuns(parsed, deps = defaultDeps$5) {
8524
8652
  const projectConfig = await deps.loadProjectConfig();
8525
8653
  if (!projectConfig?.projectRoot) throw new Error("Make Effects project config not found. Run: makefx init --space <id>");
8526
8654
  requireDebugAcknowledgement(parsed);
@@ -8638,7 +8766,7 @@ function formatCreated(value) {
8638
8766
  if (Number.isNaN(date.getTime())) return value;
8639
8767
  return value.replace("T", " ").replace(/\.\d{3}Z$/, "Z").slice(0, 20);
8640
8768
  }
8641
- function printUsage$3() {
8769
+ function printUsage$4() {
8642
8770
  console.log(`
8643
8771
  Usage:
8644
8772
  makefx runs --debug
@@ -8653,7 +8781,7 @@ Local run manifests are debug-only artifacts, not a source of truth.
8653
8781
  }
8654
8782
  //#endregion
8655
8783
  //#region src/cli/commands/assets.ts
8656
- var defaultDeps$3 = {
8784
+ var defaultDeps$4 = {
8657
8785
  loadConfig: loadStoredConfig,
8658
8786
  loadProjectConfig,
8659
8787
  resolveBaseUrl,
@@ -8677,15 +8805,15 @@ async function handleAssets(parsed) {
8677
8805
  await executeAssets(parsed);
8678
8806
  } catch (error) {
8679
8807
  console.error("Error:", error instanceof Error ? error.message : error);
8680
- printUsage$2();
8808
+ printUsage$3();
8681
8809
  process$1.exitCode = 1;
8682
8810
  }
8683
8811
  }
8684
- async function executeAssets(parsed, deps = defaultDeps$3) {
8812
+ async function executeAssets(parsed, deps = defaultDeps$4) {
8685
8813
  const subcommand = parsed.positionals[0] || "list";
8686
8814
  if (subcommand === "search") return searchAssets(parsed, deps);
8687
8815
  if (subcommand === "clone") return cloneAsset(parsed, deps);
8688
- const ctx = await buildContext$3(parsed, deps);
8816
+ const ctx = await buildContext$4(parsed, deps);
8689
8817
  if (subcommand === "list") {
8690
8818
  const assets = await listAssets(ctx, deps);
8691
8819
  if (parsed.options.json === "true") deps.print(JSON.stringify(assets.map(toAssetJson), null, 2));
@@ -8711,13 +8839,21 @@ async function executeAssets(parsed, deps = defaultDeps$3) {
8711
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>");
8712
8840
  const outputPath = getOutputPath(parsed);
8713
8841
  const resolved = resolveDownloadRef(ref, ctx);
8714
- const metadata = getDownloadedVariantMetadata(resolved, await deps.downloadFile({
8715
- baseUrl: ctx.baseUrl,
8716
- accessToken: ctx.accessToken,
8717
- requestPath: resolved.requestPath,
8718
- outputPath,
8719
- force: ctx.force
8720
- }));
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);
8721
8857
  await recordDownloadedMirror({
8722
8858
  ctx,
8723
8859
  deps,
@@ -8755,7 +8891,7 @@ async function executeAssets(parsed, deps = defaultDeps$3) {
8755
8891
  }
8756
8892
  if (subcommand === "set-active") {
8757
8893
  const assetId = parsed.positionals[1];
8758
- const variantId = parsed.positionals[2] || normalizeOption(parsed.options.variant);
8894
+ const variantId = parsed.positionals[2] || normalizeOption$1(parsed.options.variant);
8759
8895
  if (!assetId || !variantId) throw new Error("Usage: makefx assets set-active <asset-id> <variant-id>");
8760
8896
  const asset = await setActiveVariant(ctx, deps, assetId, variantId);
8761
8897
  deps.print(`Set active variant of asset ${assetId} to ${asset.active_variant_id}`);
@@ -8766,10 +8902,10 @@ async function executeAssets(parsed, deps = defaultDeps$3) {
8766
8902
  }
8767
8903
  throw new Error(`Unknown assets command: ${subcommand}`);
8768
8904
  }
8769
- function normalizeOption(value) {
8905
+ function normalizeOption$1(value) {
8770
8906
  return !value || value === "true" ? void 0 : value;
8771
8907
  }
8772
- async function buildContext$3(parsed, deps) {
8908
+ async function buildContext$4(parsed, deps) {
8773
8909
  const projectConfig = await deps.loadProjectConfig();
8774
8910
  const env = resolveCommandEnvironment(parsed, projectConfig);
8775
8911
  const spaceId = resolveCommandSpace(parsed, projectConfig);
@@ -8801,7 +8937,7 @@ async function buildAuthContext(parsed, deps) {
8801
8937
  }
8802
8938
  function requireOption(parsed, names, usage) {
8803
8939
  for (const name of names) {
8804
- const value = normalizeOption(parsed.options[name]);
8940
+ const value = normalizeOption$1(parsed.options[name]);
8805
8941
  if (value) return value;
8806
8942
  }
8807
8943
  throw new Error(usage);
@@ -8811,9 +8947,9 @@ async function searchAssets(parsed, deps) {
8811
8947
  const query = parsed.positionals.slice(1).join(" ").trim();
8812
8948
  const params = new URLSearchParams();
8813
8949
  if (query) params.set("q", query);
8814
- const mediaKind = normalizeOption(parsed.options.media) ?? normalizeOption(parsed.options["media-kind"]);
8815
- const assetType = normalizeOption(parsed.options.type);
8816
- 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);
8817
8953
  if (mediaKind) params.set("media_kind", mediaKind);
8818
8954
  if (assetType) params.set("type", assetType);
8819
8955
  if (limit) params.set("limit", limit);
@@ -8829,7 +8965,7 @@ async function searchAssets(parsed, deps) {
8829
8965
  }
8830
8966
  async function cloneAsset(parsed, deps) {
8831
8967
  const ctx = await buildAuthContext(parsed, deps);
8832
- const sourceVariantId = parsed.positionals[1] ?? normalizeOption(parsed.options.variant);
8968
+ const sourceVariantId = parsed.positionals[1] ?? normalizeOption$1(parsed.options.variant);
8833
8969
  if (!sourceVariantId) throw new Error("Usage: makefx assets clone <variant-id> --from-space <source-space-id> --to-space <target-space-id>");
8834
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>");
8835
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>");
@@ -8837,8 +8973,8 @@ async function cloneAsset(parsed, deps) {
8837
8973
  sourceSpaceId,
8838
8974
  sourceVariantId
8839
8975
  };
8840
- const name = normalizeOption(parsed.options.name);
8841
- const type = normalizeOption(parsed.options.type);
8976
+ const name = normalizeOption$1(parsed.options.name);
8977
+ const type = normalizeOption$1(parsed.options.type);
8842
8978
  if (name) body.name = name;
8843
8979
  if (type) body.type = type;
8844
8980
  const response = await deps.fetch(`${ctx.baseUrl}/api/spaces/${encodeURIComponent(targetSpaceId)}/assets/clone`, {
@@ -8886,10 +9022,10 @@ async function recordDownloadedMirror(input) {
8886
9022
  }
8887
9023
  }
8888
9024
  async function listAssets(ctx, deps) {
8889
- return (await fetchJson(ctx, deps, `/api/spaces/${ctx.spaceId}/assets`)).assets || [];
9025
+ return (await fetchJson$1(ctx, deps, `/api/spaces/${ctx.spaceId}/assets`)).assets || [];
8890
9026
  }
8891
9027
  async function getAssetDetails(ctx, deps, assetId) {
8892
- return fetchJson(ctx, deps, `/api/spaces/${ctx.spaceId}/assets/${assetId}`);
9028
+ return fetchJson$1(ctx, deps, `/api/spaces/${ctx.spaceId}/assets/${assetId}`);
8893
9029
  }
8894
9030
  async function setActiveVariant(ctx, deps, assetId, variantId) {
8895
9031
  try {
@@ -8901,7 +9037,63 @@ async function setActiveVariant(ctx, deps, assetId, variantId) {
8901
9037
  throw error;
8902
9038
  }
8903
9039
  }
8904
- 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) {
8905
9097
  const response = await deps.fetch(`${ctx.baseUrl}${apiPath}`, { headers: {
8906
9098
  "Authorization": `Bearer ${ctx.accessToken}`,
8907
9099
  "Accept": "application/json"
@@ -9083,7 +9275,7 @@ function formatTimestamp(value) {
9083
9275
  if (Number.isNaN(date.getTime())) return String(value);
9084
9276
  return date.toISOString().replace("T", " ").replace(/\.\d{3}Z$/, "Z").slice(0, 20);
9085
9277
  }
9086
- function printUsage$2() {
9278
+ function printUsage$3() {
9087
9279
  console.log(`
9088
9280
  Usage:
9089
9281
  makefx assets
@@ -9100,7 +9292,7 @@ Usage:
9100
9292
  }
9101
9293
  //#endregion
9102
9294
  //#region src/cli/commands/variants.ts
9103
- var defaultDeps$2 = {
9295
+ var defaultDeps$3 = {
9104
9296
  loadConfig: loadStoredConfig,
9105
9297
  loadProjectConfig,
9106
9298
  resolveBaseUrl,
@@ -9112,12 +9304,12 @@ async function handleVariants(parsed) {
9112
9304
  await executeVariants(parsed);
9113
9305
  } catch (error) {
9114
9306
  console.error("Error:", error instanceof Error ? error.message : error);
9115
- printUsage$1();
9307
+ printUsage$2();
9116
9308
  process$1.exitCode = 1;
9117
9309
  }
9118
9310
  }
9119
- async function executeVariants(parsed, deps = defaultDeps$2) {
9120
- const ctx = await buildContext$2(parsed, deps);
9311
+ async function executeVariants(parsed, deps = defaultDeps$3) {
9312
+ const ctx = await buildContext$3(parsed, deps);
9121
9313
  const subcommand = parsed.positionals[0];
9122
9314
  if (subcommand === "delete") {
9123
9315
  const variantId = requireVariantId(parsed, "delete");
@@ -9165,7 +9357,7 @@ function requireVariantId(parsed, verb) {
9165
9357
  if (!variantId) throw new Error(`Variant ID is required: makefx variants ${verb} <variant-id>`);
9166
9358
  return variantId;
9167
9359
  }
9168
- async function buildContext$2(parsed, deps) {
9360
+ async function buildContext$3(parsed, deps) {
9169
9361
  const projectConfig = await deps.loadProjectConfig();
9170
9362
  const env = resolveCommandEnvironment(parsed, projectConfig);
9171
9363
  const spaceId = resolveCommandSpace(parsed, projectConfig);
@@ -9188,7 +9380,7 @@ async function withVariantClient(ctx, deps, run) {
9188
9380
  client.disconnect();
9189
9381
  }
9190
9382
  }
9191
- function printUsage$1() {
9383
+ function printUsage$2() {
9192
9384
  console.log(`
9193
9385
  Usage:
9194
9386
  makefx variants delete <variant-id>
@@ -9204,6 +9396,251 @@ Options:
9204
9396
  `);
9205
9397
  }
9206
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
9207
9644
  //#region src/cli/commands/usage.ts
9208
9645
  var defaultDeps$1 = {
9209
9646
  loadConfig: loadStoredConfig,
@@ -9504,7 +9941,7 @@ Options:
9504
9941
  }
9505
9942
  //#endregion
9506
9943
  //#region src/cli/index.ts
9507
- var CLI_VERSION = "1.0.5+22056848942e";
9944
+ var CLI_VERSION = "1.0.7+686298a71cee";
9508
9945
  async function main() {
9509
9946
  const argv = process$1.argv.slice(2);
9510
9947
  if (argv[0] === "--") argv.shift();
@@ -9555,7 +9992,8 @@ function imageCapabilityHelp() {
9555
9992
  --size <value> Output size (${sizeLines})
9556
9993
  --aspect <ratio> Aspect ratio: ${imageAspectValues()}
9557
9994
  --refs <refs> Reference limit (${referenceLines})
9558
- --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`;
9559
9997
  }
9560
9998
  function videoResolutionValues() {
9561
9999
  return optionValues(uniqueValues(VIDEO_GENERATION_TIERS.flatMap((tier) => getVideoGenerationResolutionsForTier(tier))));
@@ -9583,6 +10021,7 @@ function videoCapabilityHelp() {
9583
10021
  --first-frame <ref> Veo start frame image variant or local image path
9584
10022
  --last-frame <ref> Veo final frame image variant or local image path
9585
10023
  --follow <id> Resume/download a variant after a long generation or disconnect
10024
+ --collection <id> Place the completed asset in a Space collection
9586
10025
 
9587
10026
  Veo 3.1 frames:
9588
10027
  --first-frame resolves to the first referenceVariantId and becomes the Veo top-level image input.
@@ -9649,6 +10088,10 @@ function printCommandHelp(command, positionals) {
9649
10088
  case "assets":
9650
10089
  printAssetsHelp();
9651
10090
  return;
10091
+ case "collections":
10092
+ case "collection":
10093
+ printCollectionsHelp();
10094
+ return;
9652
10095
  case "usage":
9653
10096
  printUsageHelp();
9654
10097
  return;
@@ -9695,6 +10138,8 @@ Inspect:
9695
10138
  assets show <asset-id> Show variants and lineage
9696
10139
  assets download <variant-id> -o <file> Download variant media
9697
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>
9698
10143
  variants rate <variant-id> approved|rejected
9699
10144
  variants star <variant-id>
9700
10145
 
@@ -9703,6 +10148,7 @@ Details:
9703
10148
  help audio Speech, dialogue, music, and SFX
9704
10149
  help video Video generate/refine/derive and keyframes
9705
10150
  help assets Asset inspection and downloads
10151
+ help collections Space collection organization
9706
10152
  help spaces Space setup and selection
9707
10153
  help upload Upload local media into a Space
9708
10154
 
@@ -9740,7 +10186,8 @@ Examples:
9740
10186
  makefx derive --refs character.png,background.png --name "Hero Scene" --type scene "Place the character in the background" -o hero-scene.png
9741
10187
  makefx refine --variant variant_123 "Keep composition, make the lighting warmer" -o warm.png
9742
10188
 
9743
- 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.
9744
10191
  `);
9745
10192
  }
9746
10193
  function printInitHelp() {
@@ -9834,31 +10281,33 @@ function printForgeHelp(command) {
9834
10281
  if (command === "generate") {
9835
10282
  console.log(`
9836
10283
  Usage:
9837
- makefx generate "prompt" --name <name> --type <type> -o <file> [--model ${optionValues(IMAGE_MODEL_SELECTIONS)}] [--size ${imageSizeValues()}] [--aspect <ratio>] [--space <id>]
9838
- 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>]
9839
10286
 
9840
10287
  ${imageCapabilityHelp()}
9841
10288
 
9842
- 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.
9843
10291
  `);
9844
10292
  return;
9845
10293
  }
9846
10294
  if (command === "refine") {
9847
10295
  console.log(`
9848
10296
  Usage:
9849
- makefx refine --variant <variant_id> "prompt" -o <file> [--model ${optionValues(IMAGE_MODEL_SELECTIONS)}] [--size ${imageSizeValues()}] [--aspect <ratio>] [--space <id>]
9850
- 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>]
9851
10299
 
9852
10300
  ${imageCapabilityHelp()}
9853
10301
 
9854
- 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.
9855
10304
  `);
9856
10305
  return;
9857
10306
  }
9858
10307
  if (command === "batch") {
9859
10308
  console.log(`
9860
10309
  Usage:
9861
- 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>]
9862
10311
 
9863
10312
  ${imageCapabilityHelp()}
9864
10313
 
@@ -9869,12 +10318,13 @@ Batch creates several variants. If waiting stops, inspect the Space or download
9869
10318
  }
9870
10319
  console.log(`
9871
10320
  Usage:
9872
- 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>]
9873
- 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>]
9874
10323
 
9875
10324
  ${imageCapabilityHelp()}
9876
10325
 
9877
- 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.
9878
10328
  `);
9879
10329
  }
9880
10330
  function printAudioHelp(positionals) {
@@ -9893,23 +10343,24 @@ Lists ElevenLabs voices available to the connected account when ElevenLabs is th
9893
10343
  if (second === "generate") {
9894
10344
  console.log(`
9895
10345
  Usage:
9896
- makefx audio ${first} generate "prompt" --name <name> -o <file> [--space <id>]
9897
- makefx audio ${first} generate --input <file> --name <name> -o <file> [--space <id>]
9898
- makefx audio ${first} generate --follow <variant_id> -o <file> [--space <id>]
9899
- ${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" : ""}
9900
10350
  ${first === "music" ? "Provider selection:\n --provider <elevenlabs|lyria> Music provider (default: server default)\n" : ""}
9901
10351
  ${first === "speech" || first === "dialogue" ? "Model selection:\n --model <model_id> ElevenLabs model (default: eleven_v3)\n" : ""}
9902
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" : ""}
9903
10353
  Long runs:
9904
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
9905
10356
  `);
9906
10357
  return;
9907
10358
  }
9908
10359
  if (second === "batch") {
9909
10360
  console.log(`
9910
10361
  Usage:
9911
- makefx audio ${first} batch "prompt" --name <name> --count <2-8> --output-dir <dir> [--space <id>]
9912
- ${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" : ""}
9913
10364
  ${first === "music" ? "Provider selection:\n --provider <elevenlabs|lyria> Music provider (default: server default)\n" : ""}
9914
10365
  ${first === "speech" || first === "dialogue" ? "Model selection:\n --model <model_id> ElevenLabs model (default: eleven_v3)\n" : ""}
9915
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" : ""}
@@ -9918,8 +10369,8 @@ ${first === "speech" || first === "dialogue" ? "Voice selection:\n --voice <voi
9918
10369
  }
9919
10370
  console.log(`
9920
10371
  Usage:
9921
- makefx audio ${first} generate "prompt" --name <name> -o <file> [--space <id>]
9922
- 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>]
9923
10374
  ${first === "music" ? "\nProvider selection:\n --provider <elevenlabs|lyria> Music provider (default: server default)\n" : ""}
9924
10375
  ${first === "speech" || first === "dialogue" ? "\nModel selection:\n --model <model_id> ElevenLabs model (default: eleven_v3)\n" : ""}
9925
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" : ""}
@@ -9929,31 +10380,31 @@ ${first === "speech" || first === "dialogue" ? "\nVoice selection:\n --voice <v
9929
10380
  if (first === "generate") {
9930
10381
  console.log(`
9931
10382
  Usage:
9932
- 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>]
9933
10384
 
9934
10385
  Preferred mode form:
9935
- 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>]
9936
10387
  `);
9937
10388
  return;
9938
10389
  }
9939
10390
  if (first === "batch") {
9940
10391
  console.log(`
9941
10392
  Usage:
9942
- 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>]
9943
10394
 
9944
10395
  Preferred mode form:
9945
- 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>]
9946
10397
  `);
9947
10398
  return;
9948
10399
  }
9949
10400
  console.log(`
9950
10401
  Usage:
9951
- makefx audio <${modes}> generate "prompt" --name <name> -o <file> [--space <id>]
9952
- makefx audio <${modes}> generate --input <file> --name <name> -o <file> [--space <id>]
9953
- makefx audio <${modes}> generate --follow <variant_id> -o <file> [--space <id>]
9954
- 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>]
9955
10406
  makefx audio voices [--json]
9956
- 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>]
9957
10408
 
9958
10409
  Modes:
9959
10410
  speech Spoken narration or voiceover
@@ -9969,11 +10420,12 @@ Model selection:
9969
10420
  --model <model_id> ElevenLabs speech/dialogue model (default: eleven_v3)
9970
10421
 
9971
10422
  Low-level compatibility:
9972
- makefx audio generate "prompt" --name <name> --type <type> -o <file> [--space <id>]
9973
- 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>]
9974
10425
 
9975
10426
  Long runs:
9976
- 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.
9977
10429
  `);
9978
10430
  }
9979
10431
  function printVideoHelp(positionals) {
@@ -9981,8 +10433,8 @@ function printVideoHelp(positionals) {
9981
10433
  if (subcommand === "generate") {
9982
10434
  console.log(`
9983
10435
  Usage:
9984
- 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>]
9985
- 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>]
9986
10438
 
9987
10439
  ${videoCapabilityHelp()}
9988
10440
 
@@ -9990,15 +10442,16 @@ Audio:
9990
10442
  Video defaults to generated audio. Current Veo models do not support --no-audio; the CLI rejects it before creating a job.
9991
10443
 
9992
10444
  Long runs:
9993
- 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.
9994
10447
  `);
9995
10448
  return;
9996
10449
  }
9997
10450
  if (subcommand === "refine") {
9998
10451
  console.log(`
9999
10452
  Usage:
10000
- makefx video refine --variant <variant_id> "prompt" -o <file> [--model ${videoModelValues()}] [--aspect ${videoAspectValues()}] [--resolution ${videoResolutionValues()}] [--duration ${videoDurationValues()}] [--tier ${videoTierValues()}] [--audio] [--space <id>]
10001
- 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>]
10002
10455
 
10003
10456
  ${videoCapabilityHelp()}
10004
10457
 
@@ -10006,16 +10459,17 @@ Audio:
10006
10459
  Video defaults to generated audio. Current Veo models do not support --no-audio; the CLI rejects it before creating a job.
10007
10460
 
10008
10461
  Long runs:
10009
- 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.
10010
10464
  `);
10011
10465
  return;
10012
10466
  }
10013
10467
  if (subcommand === "derive") {
10014
10468
  console.log(`
10015
10469
  Usage:
10016
- 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>]
10017
- 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>]
10018
- 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>]
10019
10473
 
10020
10474
  ${videoCapabilityHelp()}
10021
10475
 
@@ -10023,17 +10477,18 @@ Audio:
10023
10477
  Video defaults to generated audio. Current Veo models do not support --no-audio; the CLI rejects it before creating a job.
10024
10478
 
10025
10479
  Long runs:
10026
- 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.
10027
10482
  `);
10028
10483
  return;
10029
10484
  }
10030
10485
  console.log(`
10031
10486
  Usage:
10032
- 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>]
10033
- makefx video generate --follow <variant_id> -o <file> [--space <id>]
10034
- makefx video refine --variant <variant_id> "prompt" -o <file> [--model ${videoModelValues()}] [--aspect ${videoAspectValues()}] [--resolution ${videoResolutionValues()}] [--duration ${videoDurationValues()}] [--tier ${videoTierValues()}] [--audio] [--space <id>]
10035
- 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>]
10036
- 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>]
10037
10492
 
10038
10493
  ${videoCapabilityHelp()}
10039
10494
 
@@ -10041,7 +10496,8 @@ Audio:
10041
10496
  Video defaults to generated audio. Current Veo models do not support --no-audio; the CLI rejects it before creating a job.
10042
10497
 
10043
10498
  Long runs:
10044
- 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.
10045
10501
  `);
10046
10502
  }
10047
10503
  function printRunsHelp() {
@@ -10091,6 +10547,26 @@ Usage:
10091
10547
  makefx assets set-active <asset-id> <variant-id>
10092
10548
  `);
10093
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
+ }
10094
10570
  function printUsageHelp() {
10095
10571
  console.log(`
10096
10572
  Usage:
@@ -10173,6 +10649,10 @@ async function dispatchCommand(command, parsed) {
10173
10649
  case "assets":
10174
10650
  await handleAssets(parsed);
10175
10651
  break;
10652
+ case "collections":
10653
+ case "collection":
10654
+ await handleCollections(parsed);
10655
+ break;
10176
10656
  case "usage":
10177
10657
  await handleUsage(parsed);
10178
10658
  break;