makefx 1.1.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +1 -1
  2. package/makefx.mjs +1004 -884
  3. package/package.json +1 -1
package/makefx.mjs CHANGED
@@ -8,9 +8,9 @@ import { mkdir, readFile, readdir, rename, rm, stat, writeFile } from "node:fs/p
8
8
  import os from "node:os";
9
9
  import path from "node:path";
10
10
  import { createReadStream, createWriteStream } from "node:fs";
11
+ import https from "node:https";
11
12
  import { Readable } from "node:stream";
12
13
  import { pipeline } from "node:stream/promises";
13
- import https from "node:https";
14
14
  //#region \0rolldown/runtime.js
15
15
  var __create = Object.create;
16
16
  var __defProp = Object.defineProperty;
@@ -831,6 +831,78 @@ function validateProjectConfig(config, configPath) {
831
831
  if (!config.spaceId || typeof config.spaceId !== "string") throw new Error(`Make Effects project config is missing spaceId: ${configPath}`);
832
832
  }
833
833
  //#endregion
834
+ //#region src/shared/compact-references.ts
835
+ var COMPACT_ID_LENGTH = 8;
836
+ var CompactReferenceError = class extends Error {
837
+ code;
838
+ constructor(code, message) {
839
+ super(message);
840
+ this.code = code;
841
+ this.name = "CompactReferenceError";
842
+ }
843
+ };
844
+ function normalizeId(value) {
845
+ return value.toLowerCase().replaceAll(/[^a-z0-9]/gu, "");
846
+ }
847
+ function slugify$1(value) {
848
+ return value.normalize("NFKD").replaceAll(/\p{Mark}/gu, "").toLowerCase().replaceAll(/[^\p{Letter}\p{Number}]+/gu, "-").replaceAll(/^-+|-+$/gu, "").slice(0, 32).replaceAll(/-+$/gu, "") || "asset";
849
+ }
850
+ function compactIdPrefix(id) {
851
+ return normalizeId(id).slice(0, COMPACT_ID_LENGTH);
852
+ }
853
+ function createAssetRef(name, assetId) {
854
+ return `asset:${slugify$1(name)}~${compactIdPrefix(assetId)}`;
855
+ }
856
+ function createVariantRef(name, assetId, variantId) {
857
+ return `${createAssetRef(name, assetId)}@${compactIdPrefix(variantId)}`;
858
+ }
859
+ function parseAssetRef(value) {
860
+ const match = /^asset:[^~]+~([a-z0-9]{4,8})$/u.exec(value);
861
+ return match ? { idPrefix: match[1] } : null;
862
+ }
863
+ function parseVariantRef(value) {
864
+ const separator = value.lastIndexOf("@");
865
+ if (separator < 0) return null;
866
+ const assetRef = value.slice(0, separator);
867
+ const variant = value.slice(separator + 1);
868
+ if (!parseAssetRef(assetRef) || variant !== "active" && !/^[a-z0-9]{4,8}$/u.test(variant)) return null;
869
+ return {
870
+ assetRef,
871
+ variant
872
+ };
873
+ }
874
+ function idMatchesPrefix(id, prefix) {
875
+ return normalizeId(id).startsWith(prefix.toLowerCase());
876
+ }
877
+ function resolveAssetRef(identifier, assets) {
878
+ const parsed = parseAssetRef(identifier);
879
+ if (!parsed) {
880
+ if (identifier.startsWith("asset:")) throw new CompactReferenceError("MALFORMED_REFERENCE", `Asset reference ${identifier} is malformed`);
881
+ return identifier;
882
+ }
883
+ const matches = assets.filter((asset) => idMatchesPrefix(asset.id, parsed.idPrefix));
884
+ if (matches.length === 0) throw new CompactReferenceError("NOT_FOUND", `Asset reference ${identifier} was not found`);
885
+ if (matches.length > 1) throw new CompactReferenceError("AMBIGUOUS_REFERENCE", `Asset reference ${identifier} is ambiguous`);
886
+ return matches[0].id;
887
+ }
888
+ function resolveVariantRef(identifier, assets, variants) {
889
+ const parsed = parseVariantRef(identifier);
890
+ if (!parsed) {
891
+ if (identifier.startsWith("asset:")) throw new CompactReferenceError("MALFORMED_REFERENCE", `Variant reference ${identifier} is malformed`);
892
+ return identifier;
893
+ }
894
+ const assetId = resolveAssetRef(parsed.assetRef, assets);
895
+ const asset = assets.find((candidate) => candidate.id === assetId);
896
+ if (parsed.variant === "active") {
897
+ if (!asset?.active_variant_id) throw new CompactReferenceError("NOT_FOUND", `Asset reference ${parsed.assetRef} has no active variant`);
898
+ return asset.active_variant_id;
899
+ }
900
+ const matches = variants.filter((variant) => variant.asset_id === assetId && idMatchesPrefix(variant.id, parsed.variant));
901
+ if (matches.length === 0) throw new CompactReferenceError("NOT_FOUND", `Variant reference ${identifier} was not found`);
902
+ if (matches.length > 1) throw new CompactReferenceError("AMBIGUOUS_REFERENCE", `Variant reference ${identifier} is ambiguous`);
903
+ return matches[0].id;
904
+ }
905
+ //#endregion
834
906
  //#region src/cli/commands/spaces.ts
835
907
  /**
836
908
  * Spaces Command - List and manage user's spaces
@@ -933,7 +1005,14 @@ async function executeSpaces(parsed, deps = defaultDeps$11) {
933
1005
  }
934
1006
  if (spaceId) {
935
1007
  const details = await getSpaceDetails(ctx, deps, spaceId);
936
- if (jsonOutput) deps.print(JSON.stringify(details, null, 2));
1008
+ if (jsonOutput) deps.print(JSON.stringify({
1009
+ ...details,
1010
+ assets: details.assets.map((asset) => ({
1011
+ ...asset,
1012
+ assetRef: createAssetRef(asset.name, asset.id),
1013
+ variantRef: asset.active_variant_id ? createVariantRef(asset.name, asset.id, asset.active_variant_id) : null
1014
+ }))
1015
+ }, null, 2));
937
1016
  else printSpaceDetails(details, deps.print);
938
1017
  return {
939
1018
  type: "show",
@@ -1144,8 +1223,8 @@ function printSpaceDetails(details, print) {
1144
1223
  for (const [type, typeAssets] of byType) {
1145
1224
  print(`${type.charAt(0).toUpperCase() + type.slice(1)}s (${typeAssets.length}):`);
1146
1225
  for (const asset of typeAssets) {
1147
- print(` ${asset.active_variant_id ? "✓" : "○"} ${asset.name}`);
1148
- print(` ID: ${asset.id}`);
1226
+ print(` ${asset.active_variant_id ? "✓" : "○"} ${createAssetRef(asset.name, asset.id)} — ${asset.name}`);
1227
+ if (asset.active_variant_id) print(` Active: ${createVariantRef(asset.name, asset.id, asset.active_variant_id)}`);
1149
1228
  }
1150
1229
  print("");
1151
1230
  }
@@ -5377,553 +5456,42 @@ function isMirrorEntry(value) {
5377
5456
  return entry.version === MIRROR_REGISTRY_VERSION && typeof entry.baseUrl === "string" && (entry.environment === void 0 || typeof entry.environment === "string") && typeof entry.spaceId === "string" && typeof entry.sha256 === "string" && typeof entry.sizeBytes === "number" && Array.isArray(entry.paths) && entry.paths.every((item) => typeof item === "string") && typeof entry.assetId === "string" && typeof entry.variantId === "string" && typeof entry.mediaKind === "string" && (entry.mediaKey === void 0 || entry.mediaKey === null || typeof entry.mediaKey === "string") && typeof entry.updatedAt === "string";
5378
5457
  }
5379
5458
  //#endregion
5380
- //#region src/cli/commands/upload.ts
5459
+ //#region src/cli/lib/websocket-client.ts
5381
5460
  /**
5382
- * Upload Command - Upload one media file to create variants
5461
+ * WebSocket Client for CLI
5383
5462
  *
5384
- * Usage:
5385
- * makefx upload <file> --space <id> --asset <id> Upload to existing asset
5386
- * makefx upload <file> --space <id> --name <name> Upload as new asset
5387
- * makefx upload <file> --space <id> --name <name> --type <type>
5463
+ * Provides WebSocket-based communication with the Space Durable Object.
5464
+ * Replaces HTTP API calls with WebSocket messages for chat and generation.
5388
5465
  */
5389
- var defaultDeps$10 = {
5390
- loadConfig: loadStoredConfig,
5391
- loadProjectConfig,
5392
- resolveBaseUrl,
5393
- fetch,
5394
- readFile,
5395
- stat,
5396
- recordMirrorForFile,
5397
- print: console.log
5398
- };
5399
- var UploadUsageError = class extends Error {};
5400
- var RETIRED_UPLOAD_ORGANIZATION_OPTIONS = [
5401
- ["collection", "--collection"],
5402
- ["collection-name", "--collection-name"],
5403
- ["collectionName", "--collection-name"],
5404
- ["collection-role", "--collection-role"],
5405
- ["collectionRole", "--collection-role"],
5406
- ["collection-subject", "--collection-subject"],
5407
- ["collectionSubject", "--collection-subject"],
5408
- ["manual-relation", "--manual-relation"],
5409
- ["manualRelation", "--manual-relation"],
5410
- ["manual-relation-context", "--manual-relation-context"],
5411
- ["manualRelationContext", "--manual-relation-context"]
5412
- ];
5413
- async function handleUpload(parsed) {
5414
- try {
5415
- await executeUpload(parsed);
5416
- } catch (error) {
5417
- console.error(error instanceof Error ? `Error: ${error.message}` : "Error: Upload failed");
5418
- if (error instanceof UploadUsageError) printUsage$10();
5419
- process$1.exitCode = 1;
5420
- }
5421
- }
5422
- async function executeUpload(parsed, deps = defaultDeps$10) {
5423
- const filePath = parsed.positionals[0];
5424
- if (!filePath) throw new UploadUsageError("File path is required");
5425
- rejectRetiredUploadOrganizationOptions(parsed);
5426
- const projectConfig = await deps.loadProjectConfig();
5427
- const env = resolveCommandEnvironment(parsed, projectConfig);
5428
- const spaceId = resolveCommandSpace(parsed, projectConfig);
5429
- const assetId = parsed.options.asset;
5430
- const assetName = parsed.options.name;
5431
- const assetType = parsed.options.type || "character";
5432
- const requestedMediaKind = parsed.options["media-kind"] || parsed.options.mediaKind;
5433
- const prompt = parsed.options.prompt;
5434
- const model = parsed.options.model;
5435
- const provider = parsed.options.provider;
5436
- const rawProviderMetadata = parsed.options["provider-metadata"] ?? parsed.options.providerMetadata ?? parsed.options.provider_metadata;
5437
- const rawGenerationProvenance = parsed.options["generation-provenance"] ?? parsed.options.generationProvenance ?? parsed.options.generation_provenance;
5438
- const rawSourceVariantIds = parsed.options["source-variants"] ?? parsed.options.sourceVariants ?? parsed.options.sourceVariantIds ?? parsed.options["source-variant"] ?? parsed.options.sourceVariantId ?? parsed.options.parentVariantId;
5439
- const rawRelationType = parsed.options["relation-type"] ?? parsed.options.relationType;
5440
- const rawActiveVariantBehavior = parsed.options["active-variant-behavior"] ?? parsed.options.activeVariantBehavior;
5441
- const jsonOutput = parsed.options.json === "true";
5442
- if (parsed.options["dry-run"] === "true" || parsed.options.dryRun === "true") throw new UploadUsageError("--dry-run is not supported for direct file upload");
5443
- if (!spaceId) throw new UploadUsageError("--space is required, or run: makefx init --space <id>");
5444
- if (!assetId && !assetName) throw new UploadUsageError("Either --asset or --name is required");
5445
- const providerMetadata = parseJsonObjectOption(rawProviderMetadata, "--provider-metadata");
5446
- const generationProvenance = parseJsonObjectOption(rawGenerationProvenance, "--generation-provenance");
5447
- const sourceVariantIds = parseCsvOption(rawSourceVariantIds, "--source-variant");
5448
- const relationType = normalizeRelationType(rawRelationType);
5449
- const activeVariantBehavior = normalizeActiveVariantBehavior(rawActiveVariantBehavior);
5450
- if (sourceVariantIds.length === 0 && (parsed.options["relation-type"] || parsed.options.relationType)) throw new UploadUsageError("--relation-type requires --source-variant or --source-variants");
5451
- const mediaType = resolveMediaType(path.extname(filePath).toLowerCase(), requestedMediaKind);
5452
- const config = await deps.loadConfig(env);
5453
- if (!config) throw new Error(`Not logged in to ${env} environment. Run: ${loginCommandForEnvironment(env)}`);
5454
- if (config.token.expiresAt < Date.now()) throw new Error(`Token expired for ${env} environment. Run: ${loginCommandForEnvironment(env)}`);
5455
- const baseUrl = deps.resolveBaseUrl(env);
5456
- const accessToken = config.token.accessToken;
5457
- if (env === "local") process$1.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
5458
- try {
5459
- const fileStat = await deps.stat(filePath);
5460
- if (!fileStat.isFile()) throw new Error(`"${filePath}" is not a file`);
5461
- if (fileStat.size > 10485760) throw new Error(`File too large (${(fileStat.size / 1024 / 1024).toFixed(2)}MB). Maximum size: 10MB`);
5462
- const fileBuffer = await deps.readFile(filePath);
5463
- const fileName = path.basename(filePath);
5464
- const formData = new FormData();
5465
- const blob = new Blob([fileBuffer], { type: mediaType.mimeType });
5466
- formData.append("file", blob, fileName);
5467
- formData.append("mediaKind", mediaType.mediaKind);
5468
- formData.append("activeVariantBehavior", activeVariantBehavior);
5469
- if (assetId) formData.append("assetId", assetId);
5470
- else {
5471
- formData.append("assetName", assetName);
5472
- formData.append("assetType", assetType);
5473
- }
5474
- if (prompt) formData.append("prompt", prompt);
5475
- if (model) formData.append("model", model);
5476
- if (provider) formData.append("provider", provider);
5477
- if (providerMetadata) formData.append("providerMetadata", JSON.stringify(providerMetadata));
5478
- if (generationProvenance) formData.append("generationProvenance", JSON.stringify(generationProvenance));
5479
- if (sourceVariantIds.length > 0) formData.append("lineage", JSON.stringify(sourceVariantIds.map((parentVariantId) => ({
5480
- parentVariantId,
5481
- relationType
5482
- }))));
5483
- if (!jsonOutput) {
5484
- deps.print(`\nUploading "${fileName}" to space ${spaceId}...`);
5485
- deps.print(` Media kind: ${mediaType.mediaKind}`);
5486
- if (assetId) deps.print(` Target asset: ${assetId}`);
5487
- else deps.print(` Creating asset: "${assetName}" (${assetType})`);
5488
- if (sourceVariantIds.length === 1) deps.print(` Source variant: ${sourceVariantIds[0]} (${relationType})`);
5489
- else if (sourceVariantIds.length > 1) deps.print(` Source variants: ${sourceVariantIds.join(", ")} (${relationType})`);
5490
- }
5491
- const response = await deps.fetch(`${baseUrl}/api/spaces/${spaceId}/upload`, {
5492
- method: "POST",
5493
- headers: { "Authorization": `Bearer ${accessToken}` },
5494
- body: formData
5495
- });
5496
- const data = await response.json();
5497
- if (!response.ok) throw new Error(`Upload failed: ${"error" in data ? data.error : response.statusText}`);
5498
- const upload = data;
5499
- const result = {
5500
- asset: upload.asset,
5501
- variant: upload.variant,
5502
- lineage: upload.lineage
5503
- };
5504
- await recordUploadMirror({
5505
- deps,
5506
- projectRoot: projectConfig?.projectRoot,
5507
- env,
5508
- baseUrl,
5509
- spaceId,
5510
- filePath,
5511
- assetId: upload.asset?.id ?? upload.variant.asset_id,
5512
- variantId: upload.variant.id,
5513
- mediaKind: upload.variant.media_kind || mediaType.mediaKind,
5514
- mediaKey: upload.variant.media_key || upload.variant.image_key
5515
- });
5516
- if (jsonOutput) {
5517
- deps.print(JSON.stringify(result, null, 2));
5518
- return result;
5519
- }
5520
- deps.print("\nUpload successful!\n");
5521
- if (upload.asset) {
5522
- deps.print("New Asset:");
5523
- deps.print(` ID: ${upload.asset.id}`);
5524
- deps.print(` Name: ${upload.asset.name}`);
5525
- deps.print(` Type: ${upload.asset.type}`);
5526
- deps.print("");
5527
- }
5528
- deps.print("Variant:");
5529
- deps.print(` ID: ${upload.variant.id}`);
5530
- deps.print(` Asset: ${upload.variant.asset_id}`);
5531
- deps.print(` Status: ${upload.variant.status}`);
5532
- deps.print(` Media: ${upload.variant.media_kind || mediaType.mediaKind}`);
5533
- deps.print(` File: ${upload.variant.media_key || upload.variant.image_key || "-"}`);
5534
- if (upload.variant.image_key) deps.print(` Image: ${upload.variant.image_key}`);
5535
- if (upload.variant.thumb_key) deps.print(` Thumb: ${upload.variant.thumb_key}`);
5536
- if (upload.variant.media_mime_type) deps.print(` MIME: ${upload.variant.media_mime_type}`);
5537
- if (upload.variant.recipe) try {
5538
- const recipe = JSON.parse(upload.variant.recipe);
5539
- deps.print("\nRecipe:");
5540
- deps.print(` Operation: ${recipe.operation}`);
5541
- deps.print(` Original: ${recipe.originalFilename}`);
5542
- deps.print(` Uploaded: ${recipe.uploadedAt}`);
5543
- } catch {}
5544
- deps.print("\nTo inspect:");
5545
- deps.print(` makefx assets show ${upload.variant.asset_id} --space ${spaceId}`);
5546
- return result;
5547
- } catch (error) {
5548
- if (error.code === "ENOENT") throw new Error(`File not found: ${filePath}`);
5549
- throw error;
5550
- }
5551
- }
5552
- async function recordUploadMirror(input) {
5553
- if (!input.deps.recordMirrorForFile) return;
5554
- try {
5555
- await input.deps.recordMirrorForFile({
5556
- projectRoot: input.projectRoot,
5557
- baseUrl: input.baseUrl,
5558
- environment: input.env,
5559
- spaceId: input.spaceId,
5560
- filePath: input.filePath,
5561
- assetId: input.assetId,
5562
- variantId: input.variantId,
5563
- mediaKind: input.mediaKind,
5564
- mediaKey: input.mediaKey
5565
- });
5566
- } catch (error) {
5567
- console.warn(`Warning: upload succeeded but mirror registry was not updated: ${error instanceof Error ? error.message : String(error)}`);
5568
- }
5466
+ var GENERATION_REQUEST_TIMEOUT_MS = 3e5;
5467
+ var VIDEO_GENERATION_REQUEST_TIMEOUT_MS = 72e4;
5468
+ function getGenerationRequestTimeoutMs(mediaKind) {
5469
+ return mediaKind === "video" ? VIDEO_GENERATION_REQUEST_TIMEOUT_MS : GENERATION_REQUEST_TIMEOUT_MS;
5569
5470
  }
5570
- function parseJsonObjectOption(value, optionName) {
5571
- if (value === void 0) return void 0;
5572
- let parsed;
5573
- try {
5574
- parsed = JSON.parse(value);
5575
- } catch {
5576
- throw new UploadUsageError(`${optionName} must be valid JSON`);
5471
+ /**
5472
+ * Thrown when a mutation's confirmation broadcast does not arrive within the
5473
+ * timeout. The server write may still have landed — callers can re-read state
5474
+ * to confirm. Distinct from server-rejection errors (PERMISSION_DENIED,
5475
+ * VALIDATION_ERROR, …), which reject with their own message and must not be
5476
+ * treated as a missing confirmation.
5477
+ */
5478
+ var ServerConfirmationTimeoutError = class extends Error {
5479
+ constructor(message = "Timed out waiting for server confirmation") {
5480
+ super(message);
5481
+ this.name = "ServerConfirmationTimeoutError";
5577
5482
  }
5578
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new UploadUsageError(`${optionName} must be a JSON object`);
5579
- return parsed;
5580
- }
5581
- function normalizeRelationType(value) {
5582
- if (!value) return "derived";
5583
- if (value === "derived" || value === "refined" || value === "forked") return value;
5584
- throw new UploadUsageError("--relation-type must be derived, refined, or forked");
5585
- }
5586
- function normalizeActiveVariantBehavior(value) {
5587
- if (!value || value === "if-missing" || value === "if_missing") return "if-missing";
5588
- if (value === "set-active" || value === "set_active") return "set-active";
5589
- if (value === "keep") return "keep";
5590
- throw new UploadUsageError("--active-variant-behavior must be if-missing, set-active, or keep");
5483
+ };
5484
+ function booleanFromSqlite(value) {
5485
+ return value === true || value === 1;
5591
5486
  }
5592
- function rejectRetiredUploadOrganizationOptions(parsed) {
5593
- const usedOptions = RETIRED_UPLOAD_ORGANIZATION_OPTIONS.filter(([key]) => parsed.options[key] !== void 0).map(([, flag]) => flag);
5594
- if (usedOptions.length === 0) return;
5595
- throw new UploadUsageError(`${Array.from(new Set(usedOptions)).join(", ")} ${usedOptions.length === 1 ? "was" : "were"} removed. Upload creates assets or variants only; organize the result visually on the Space canvas. Use --source-variant for immutable lineage provenance.`);
5596
- }
5597
- function parseCsvOption(value, optionName) {
5598
- if (!value) return [];
5599
- if (value === "true") throw new UploadUsageError(`${optionName} requires a value`);
5600
- return value.split(",").map((entry) => entry.trim()).filter(Boolean);
5601
- }
5602
- function printUsage$10() {
5603
- console.log(`
5604
- Usage:
5605
- makefx upload <file> --asset <id> [--space <id>] Upload media to existing asset
5606
- makefx upload <file> --name <name> [--space <id>] Upload media as a new asset
5607
-
5608
- Options:
5609
- --space <id> Target space ID; defaults from initialized project
5610
- --asset <id> Target asset ID (upload as new variant)
5611
- --name <name> New asset name (creates asset + variant)
5612
- --type <type> Asset type for new assets (default: character)
5613
- --media-kind <k> Optional explicit kind: image, audio, or video
5614
- --prompt <text> Prompt provenance for uploaded media
5615
- --model <model> Model provenance for uploaded media
5616
- --provider <name> Provider provenance for uploaded media
5617
- --provider-metadata <json> Provider metadata JSON object
5618
- --generation-provenance <json> Extra provenance JSON object
5619
- --source-variant <ids> Comma-separated source variants for upload lineage
5620
- --source-variants <ids> Alias for --source-variant
5621
- --relation-type <type> Lineage type: derived, refined, or forked (default: derived)
5622
- --active-variant-behavior <b> if-missing, set-active, or keep
5623
- --json Print machine-readable upload output with Space IDs
5624
- --env <env> Environment (production|stage|local)
5625
- --local Shortcut for --env local
5626
-
5627
- Examples:
5628
- makefx upload hero.png --space abc123 --name "Hero Character"
5629
- makefx upload hero.png --space abc123 --name "Hero" --prompt "external render" --provider blender
5630
- makefx upload paintover.png --space abc123 --asset def456 --source-variant var123 --relation-type refined
5631
- makefx upload scene.png --space abc123 --name "Cocina" --type scene --source-variants anna,roman,bg
5632
- makefx upload theme.mp3 --space abc123 --name "Theme Music" --type audio
5633
- makefx upload cutscene.mp4 --space abc123 --name "Opening Cutscene" --type video
5634
- makefx upload variant.jpg --space abc123 --asset def456
5635
- makefx upload hero.png --space abc123 --name "Hero" --json
5636
- `);
5637
- }
5638
- //#endregion
5639
- //#region src/cli/lib/run-manifest.ts
5640
- function createRunId(date = /* @__PURE__ */ new Date()) {
5641
- return `${date.toISOString().replace(/[-:]/g, "").replace(/\.\d{3}Z$/, "Z")}-${crypto.randomUUID().slice(0, 8)}`;
5642
- }
5643
- async function saveRunManifest(manifest, cwd = process.cwd()) {
5644
- const manifestPath = path.join(cwd, ".inventory", "runs", `${manifest.runId}.json`);
5645
- await mkdir(path.dirname(manifestPath), { recursive: true });
5646
- await writeFile(manifestPath, JSON.stringify(manifest, null, 2) + "\n", "utf8");
5647
- return manifestPath;
5648
- }
5649
- function getRunManifestDir(projectRoot = process.cwd()) {
5650
- return path.join(projectRoot, ".inventory", "runs");
5651
- }
5652
- async function listRunManifests(projectRoot = process.cwd()) {
5653
- const runsDir = getRunManifestDir(projectRoot);
5654
- let entries;
5655
- try {
5656
- entries = await readdir(runsDir);
5657
- } catch (error) {
5658
- if (error.code === "ENOENT") return [];
5659
- throw error;
5660
- }
5661
- return (await Promise.all(entries.filter((entry) => entry.endsWith(".json")).map((entry) => readRunManifest(path.join(runsDir, entry))))).sort((a, b) => {
5662
- const byCreatedAt = Date.parse(b.manifest.createdAt) - Date.parse(a.manifest.createdAt);
5663
- if (Number.isFinite(byCreatedAt) && byCreatedAt !== 0) return byCreatedAt;
5664
- return b.manifestPath.localeCompare(a.manifestPath);
5665
- });
5666
- }
5667
- async function readRunManifest(manifestPath) {
5668
- const raw = await readFile(manifestPath, "utf8");
5669
- return {
5670
- manifest: normalizeRunManifest(JSON.parse(raw), manifestPath),
5671
- manifestPath
5672
- };
5673
- }
5674
- async function resolveRunManifest(input) {
5675
- if (input.latest) {
5676
- const manifests = await listRunManifests(input.projectRoot);
5677
- if (manifests.length === 0) throw new Error(`No run manifests found in ${getRunManifestDir(input.projectRoot)}`);
5678
- return manifests[0];
5679
- }
5680
- const selector = input.runIdOrPath;
5681
- if (!selector) throw new Error("Run ID, manifest path, or --latest is required");
5682
- return readRunManifest(looksLikeManifestPath(selector) ? path.resolve(selector) : path.join(getRunManifestDir(input.projectRoot), `${selector}.json`));
5683
- }
5684
- function createMediaRunExport(record, projectRoot) {
5685
- const pathBase = record.manifest.workingDir || projectRoot;
5686
- return {
5687
- version: 1,
5688
- format: "media-handoff",
5689
- runId: record.manifest.runId,
5690
- manifestPath: record.manifestPath,
5691
- projectRoot,
5692
- success: record.manifest.success,
5693
- prompt: record.manifest.prompt,
5694
- name: record.manifest.name,
5695
- assetType: record.manifest.assetType,
5696
- spaceId: record.manifest.spaceId,
5697
- baseUrl: record.manifest.baseUrl,
5698
- refs: record.manifest.refs,
5699
- referenceVariantIds: record.manifest.referenceVariantIds,
5700
- createdAt: record.manifest.createdAt,
5701
- completedAt: record.manifest.completedAt,
5702
- media: sortedMedia(record.manifest).map((media) => ({
5703
- ...media,
5704
- absolutePath: path.resolve(pathBase, media.localPath)
5705
- })),
5706
- images: [...record.manifest.images].sort((a, b) => a.index - b.index).map((image) => ({
5707
- ...image,
5708
- absolutePath: path.resolve(pathBase, image.localPath)
5709
- })),
5710
- failed: record.manifest.failed
5711
- };
5712
- }
5713
- function createRemotionRunExport(record, projectRoot) {
5714
- return {
5715
- ...createMediaRunExport(record, projectRoot),
5716
- format: "remotion-keyframes"
5717
- };
5718
- }
5719
- function manifestMediaFromVariant(input) {
5720
- const mediaKind = input.variant.media_kind || "image";
5721
- const mediaKey = input.variant.media_key || input.variant.image_key;
5722
- if (!mediaKey) throw new Error(`Variant has no media key: ${input.variant.id}`);
5723
- return {
5724
- index: input.index,
5725
- mediaKind,
5726
- assetId: input.variant.asset_id,
5727
- variantId: input.variant.id,
5728
- mediaKey,
5729
- imageKey: input.variant.image_key,
5730
- thumbKey: input.variant.thumb_key,
5731
- mimeType: input.variant.media_mime_type || null,
5732
- sizeBytes: input.variant.media_size_bytes ?? null,
5733
- width: input.variant.media_width ?? null,
5734
- height: input.variant.media_height ?? null,
5735
- durationMs: input.variant.media_duration_ms ?? null,
5736
- localPath: input.localPath,
5737
- webUrl: `${input.baseUrl}/spaces/${input.spaceId}/assets/${input.variant.asset_id}`
5738
- };
5739
- }
5740
- function looksLikeManifestPath(value) {
5741
- return path.isAbsolute(value) || value.endsWith(".json") || value.includes("/") || value.includes("\\");
5742
- }
5743
- function normalizeRunManifest(manifest, manifestPath) {
5744
- if (manifest.version !== 1) throw new Error(`Unsupported run manifest version in ${manifestPath}`);
5745
- if (!isSupportedCommand(manifest.command)) throw new Error(`Unsupported run manifest command in ${manifestPath}`);
5746
- if (!manifest.runId || typeof manifest.runId !== "string") throw new Error(`Run manifest is missing runId: ${manifestPath}`);
5747
- const media = Array.isArray(manifest.media) ? manifest.media : manifest.images;
5748
- if (!Array.isArray(media)) throw new Error(`Run manifest is missing media: ${manifestPath}`);
5749
- if (!Array.isArray(manifest.failed)) throw new Error(`Run manifest is missing failed entries: ${manifestPath}`);
5750
- const normalizedMedia = media.map((entry) => normalizeManifestMedia(entry));
5751
- return {
5752
- ...manifest,
5753
- mediaKind: manifest.mediaKind || inferManifestMediaKind(normalizedMedia),
5754
- media: normalizedMedia,
5755
- images: normalizedMedia.filter(isRunManifestImage)
5756
- };
5757
- }
5758
- function sortedMedia(manifest) {
5759
- return [...manifest.media].sort((a, b) => a.index - b.index);
5760
- }
5761
- function isSupportedCommand(command) {
5762
- return command === "generate" || command === "refine" || command === "derive" || command === "batch";
5763
- }
5764
- function normalizeManifestMedia(entry) {
5765
- const legacyImageKey = "imageKey" in entry ? entry.imageKey : null;
5766
- const mediaKey = "mediaKey" in entry ? entry.mediaKey : legacyImageKey;
5767
- return {
5768
- index: entry.index,
5769
- mediaKind: entry.mediaKind || "image",
5770
- assetId: entry.assetId,
5771
- variantId: entry.variantId,
5772
- mediaKey: mediaKey || "",
5773
- imageKey: legacyImageKey,
5774
- thumbKey: entry.thumbKey,
5775
- mimeType: "mimeType" in entry ? entry.mimeType : null,
5776
- sizeBytes: "sizeBytes" in entry ? entry.sizeBytes : null,
5777
- width: "width" in entry ? entry.width : null,
5778
- height: "height" in entry ? entry.height : null,
5779
- durationMs: "durationMs" in entry ? entry.durationMs : null,
5780
- localPath: entry.localPath,
5781
- webUrl: entry.webUrl
5782
- };
5783
- }
5784
- function isRunManifestImage(entry) {
5785
- return entry.mediaKind === "image" && Boolean(entry.imageKey);
5786
- }
5787
- function inferManifestMediaKind(media) {
5788
- return media[0]?.mediaKind || "image";
5789
- }
5790
- //#endregion
5791
- //#region src/cli/lib/image-transfer.ts
5792
- var MAX_FILE_SIZE_MB = 10;
5793
- var MAX_FILE_SIZE_BYTES = MAX_FILE_SIZE_MB * 1024 * 1024;
5794
- var ALLOWED_EXTENSIONS = [
5795
- ".jpg",
5796
- ".jpeg",
5797
- ".png",
5798
- ".gif",
5799
- ".webp"
5800
- ];
5801
- var EXT_TO_MIME = {
5802
- ".jpg": "image/jpeg",
5803
- ".jpeg": "image/jpeg",
5804
- ".png": "image/png",
5805
- ".gif": "image/gif",
5806
- ".webp": "image/webp"
5807
- };
5808
- function isSupportedImagePath(filePath) {
5809
- return ALLOWED_EXTENSIONS.includes(path.extname(filePath).toLowerCase());
5810
- }
5811
- function looksLikeFilePath(value) {
5812
- return value.startsWith(".") || value.includes("/") || value.includes("\\") || isSupportedImagePath(value);
5813
- }
5814
- async function uploadLocalImageAsReference(input) {
5815
- const ext = path.extname(input.filePath).toLowerCase();
5816
- if (!ALLOWED_EXTENSIONS.includes(ext)) throw new Error(`Invalid reference file type "${ext}". Allowed: ${ALLOWED_EXTENSIONS.join(", ")}`);
5817
- const fileStat = await stat(input.filePath);
5818
- if (!fileStat.isFile()) throw new Error(`Reference is not a file: ${input.filePath}`);
5819
- if (fileStat.size > MAX_FILE_SIZE_BYTES) throw new Error(`Reference file too large (${(fileStat.size / 1024 / 1024).toFixed(2)}MB). Maximum size: ${MAX_FILE_SIZE_MB}MB`);
5820
- const fileBuffer = await readFile(input.filePath);
5821
- const filename = path.basename(input.filePath);
5822
- const mimeType = EXT_TO_MIME[ext] || "image/png";
5823
- const assetName = input.assetName || `Reference: ${filename.replace(/\.[^/.]+$/, "")}`;
5824
- const formData = new FormData();
5825
- formData.append("file", new Blob([fileBuffer], { type: mimeType }), filename);
5826
- formData.append("assetName", assetName);
5827
- formData.append("assetType", "reference");
5828
- formData.append("mediaKind", DEFAULT_MEDIA_KIND);
5829
- const response = await fetch(`${input.baseUrl}/api/spaces/${input.spaceId}/upload`, {
5830
- method: "POST",
5831
- headers: { "Authorization": `Bearer ${input.accessToken}` },
5832
- body: formData
5833
- });
5834
- const data = await response.json();
5835
- if (!response.ok || !data.success || !data.variant) throw new Error(`Failed to upload reference "${input.filePath}": ${data.error || response.statusText}`);
5836
- return {
5837
- asset: data.asset,
5838
- variant: data.variant
5839
- };
5840
- }
5841
- async function downloadFile(input) {
5842
- if (!input.force) try {
5843
- if ((await stat(input.outputPath)).isFile()) throw new Error(`Output file already exists: ${input.outputPath}. Pass --force to overwrite.`);
5844
- } catch (error) {
5845
- if (error.code !== "ENOENT") throw error;
5846
- }
5847
- const headers = {};
5848
- if (input.accessToken) headers.Authorization = `Bearer ${input.accessToken}`;
5849
- const requestUrl = `${input.baseUrl}${input.requestPath}`;
5850
- let response;
5851
- try {
5852
- response = await fetch(requestUrl, { headers });
5853
- } catch (error) {
5854
- throw new Error(`Failed to download ${input.requestPath}: ${formatFetchError(error)}`);
5855
- }
5856
- if (!response.ok) {
5857
- const errorText = await readErrorText(response);
5858
- throw new Error(`Failed to download ${input.requestPath}: ${response.status} ${response.statusText}${errorText ? ` - ${errorText}` : ""}`);
5859
- }
5860
- await mkdir(path.dirname(input.outputPath), { recursive: true });
5861
- if (!response.body) throw new Error(`Failed to download ${input.requestPath}: response body was empty`);
5862
- try {
5863
- await pipeline(Readable.fromWeb(response.body), createWriteStream(input.outputPath));
5864
- } catch (error) {
5865
- throw new Error(`Failed to write download ${input.requestPath} to ${input.outputPath}: ${formatFetchError(error)}`);
5866
- }
5867
- return {
5868
- headers: response.headers,
5869
- requestUrl
5870
- };
5871
- }
5872
- async function readErrorText(response) {
5873
- try {
5874
- return (await response.text()).slice(0, 500).trim();
5875
- } catch {
5876
- return "";
5877
- }
5878
- }
5879
- function formatFetchError(error) {
5880
- if (!(error instanceof Error)) return String(error);
5881
- const cause = error.cause instanceof Error ? ` (${error.cause.message})` : "";
5882
- return `${error.message}${cause}`;
5883
- }
5884
- async function downloadImage(input) {
5885
- await downloadFile({
5886
- baseUrl: input.baseUrl,
5887
- accessToken: input.accessToken,
5888
- requestPath: `/api/images/${input.imageKey}`,
5889
- outputPath: input.outputPath,
5890
- force: input.force
5891
- });
5892
- }
5893
- //#endregion
5894
- //#region src/cli/lib/websocket-client.ts
5895
- /**
5896
- * WebSocket Client for CLI
5897
- *
5898
- * Provides WebSocket-based communication with the Space Durable Object.
5899
- * Replaces HTTP API calls with WebSocket messages for chat and generation.
5900
- */
5901
- var GENERATION_REQUEST_TIMEOUT_MS = 3e5;
5902
- var VIDEO_GENERATION_REQUEST_TIMEOUT_MS = 72e4;
5903
- function getGenerationRequestTimeoutMs(mediaKind) {
5904
- return mediaKind === "video" ? VIDEO_GENERATION_REQUEST_TIMEOUT_MS : GENERATION_REQUEST_TIMEOUT_MS;
5905
- }
5906
- /**
5907
- * Thrown when a mutation's confirmation broadcast does not arrive within the
5908
- * timeout. The server write may still have landed — callers can re-read state
5909
- * to confirm. Distinct from server-rejection errors (PERMISSION_DENIED,
5910
- * VALIDATION_ERROR, …), which reject with their own message and must not be
5911
- * treated as a missing confirmation.
5912
- */
5913
- var ServerConfirmationTimeoutError = class extends Error {
5914
- constructor(message = "Timed out waiting for server confirmation") {
5915
- super(message);
5916
- this.name = "ServerConfirmationTimeoutError";
5917
- }
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;
5487
+ function collectionItemMatchesInput(item, input) {
5488
+ if (item.subject_type !== input.subjectType) return false;
5489
+ if (input.subjectType === "asset" && item.asset_id !== input.assetId) return false;
5490
+ if (input.subjectType === "variant" && item.variant_id !== input.variantId) return false;
5491
+ if (input.role !== void 0 && item.role !== input.role) return false;
5492
+ if (input.pinnedVariantId !== void 0 && item.pinned_variant_id !== input.pinnedVariantId) return false;
5493
+ if (input.sortIndex !== void 0 && item.sort_index !== input.sortIndex) return false;
5494
+ return true;
5927
5495
  }
5928
5496
  var WebSocketClient = class WebSocketClient {
5929
5497
  ws = null;
@@ -5935,7 +5503,6 @@ var WebSocketClient = class WebSocketClient {
5935
5503
  chatHandlers = /* @__PURE__ */ new Map();
5936
5504
  generateHandlers = /* @__PURE__ */ new Map();
5937
5505
  batchHandlers = /* @__PURE__ */ new Map();
5938
- estimateHandlers = /* @__PURE__ */ new Map();
5939
5506
  batchVariantToRequestId = /* @__PURE__ */ new Map();
5940
5507
  variantCompletionHandlers = /* @__PURE__ */ new Map();
5941
5508
  describeHandlers = /* @__PURE__ */ new Map();
@@ -6131,16 +5698,6 @@ var WebSocketClient = class WebSocketClient {
6131
5698
  }
6132
5699
  break;
6133
5700
  }
6134
- case "generation:estimate": {
6135
- const estimateMsg = message;
6136
- const handler = this.estimateHandlers.get(estimateMsg.requestId);
6137
- if (handler) {
6138
- this.estimateHandlers.delete(estimateMsg.requestId);
6139
- clearTimeout(handler.timeout);
6140
- handler.resolve(estimateMsg);
6141
- }
6142
- break;
6143
- }
6144
5701
  case "variant:updated": {
6145
5702
  const variant = message.variant;
6146
5703
  const batchRequestId = this.batchVariantToRequestId.get(variant.id);
@@ -6296,13 +5853,6 @@ var WebSocketClient = class WebSocketClient {
6296
5853
  return true;
6297
5854
  }
6298
5855
  failRequestHandler(requestId, error) {
6299
- const estimateHandler = this.estimateHandlers.get(requestId);
6300
- if (estimateHandler) {
6301
- this.estimateHandlers.delete(requestId);
6302
- clearTimeout(estimateHandler.timeout);
6303
- estimateHandler.reject(error);
6304
- return true;
6305
- }
6306
5856
  const generateHandler = this.generateHandlers.get(requestId);
6307
5857
  if (generateHandler) {
6308
5858
  this.generateHandlers.delete(requestId);
@@ -6395,11 +5945,15 @@ var WebSocketClient = class WebSocketClient {
6395
5945
  }
6396
5946
  /** Star or unstar a variant. Resolves with the updated variant. */
6397
5947
  async starVariant(variantId, starred) {
6398
- return (await this.awaitServerMessage({
5948
+ const result = await this.awaitServerMessage({
6399
5949
  type: "variant:star",
6400
5950
  variantId,
6401
5951
  starred
6402
- }, (msg) => msg.type === "variant:updated" && msg.variant.id === variantId && msg.variant.starred === starred)).variant;
5952
+ }, (msg) => msg.type === "variant:updated" && msg.variant.id === variantId && booleanFromSqlite(msg.variant.starred) === starred);
5953
+ return {
5954
+ ...result.variant,
5955
+ starred: booleanFromSqlite(result.variant.starred)
5956
+ };
6403
5957
  }
6404
5958
  /** Rate a variant (approved/rejected). Resolves with the updated variant. */
6405
5959
  async rateVariant(variantId, rating) {
@@ -6460,69 +6014,28 @@ var WebSocketClient = class WebSocketClient {
6460
6014
  return new Promise((resolve, reject) => {
6461
6015
  this.chatHandlers.set(requestId, {
6462
6016
  resolve,
6463
- reject
6464
- });
6465
- const message = {
6466
- type: "chat:request",
6467
- requestId,
6468
- message: params.message,
6469
- mode: params.mode,
6470
- forgeContext: params.forgeContext,
6471
- viewingContext: params.viewingContext
6472
- };
6473
- try {
6474
- this.send(message);
6475
- } catch (err) {
6476
- this.chatHandlers.delete(requestId);
6477
- reject(err);
6478
- }
6479
- setTimeout(() => {
6480
- if (this.chatHandlers.has(requestId)) {
6481
- this.chatHandlers.delete(requestId);
6482
- reject(/* @__PURE__ */ new Error("Chat request timed out"));
6483
- }
6484
- }, 12e4);
6485
- });
6486
- }
6487
- async sendGenerationEstimateRequest(params) {
6488
- const requestId = crypto.randomUUID();
6489
- return new Promise((resolve, reject) => {
6490
- const timeout = setTimeout(() => {
6491
- if (this.estimateHandlers.has(requestId)) {
6492
- this.estimateHandlers.delete(requestId);
6493
- reject(/* @__PURE__ */ new Error("Generation estimate request timed out"));
6494
- }
6495
- }, 3e4);
6496
- this.estimateHandlers.set(requestId, {
6497
- resolve,
6498
- reject,
6499
- timeout
6017
+ reject
6500
6018
  });
6501
6019
  const message = {
6502
- type: "generation:estimate",
6020
+ type: "chat:request",
6503
6021
  requestId,
6504
- operation: params.operation,
6505
- assetId: params.assetId,
6506
- assetType: params.assetType,
6507
- mediaKind: params.mediaKind,
6508
- prompt: params.prompt,
6509
- count: params.count,
6510
- model: params.model,
6511
- aspectRatio: params.aspectRatio,
6512
- imageSize: params.imageSize,
6513
- musicProvider: params.musicProvider,
6514
- generateAudio: params.generateAudio,
6515
- videoResolution: params.videoResolution,
6516
- videoDurationSeconds: params.videoDurationSeconds,
6517
- videoTier: params.videoTier
6022
+ message: params.message,
6023
+ mode: params.mode,
6024
+ forgeContext: params.forgeContext,
6025
+ viewingContext: params.viewingContext
6518
6026
  };
6519
6027
  try {
6520
6028
  this.send(message);
6521
6029
  } catch (err) {
6522
- this.estimateHandlers.delete(requestId);
6523
- clearTimeout(timeout);
6030
+ this.chatHandlers.delete(requestId);
6524
6031
  reject(err);
6525
6032
  }
6033
+ setTimeout(() => {
6034
+ if (this.chatHandlers.has(requestId)) {
6035
+ this.chatHandlers.delete(requestId);
6036
+ reject(/* @__PURE__ */ new Error("Chat request timed out"));
6037
+ }
6038
+ }, 12e4);
6526
6039
  });
6527
6040
  }
6528
6041
  /**
@@ -6742,88 +6255,653 @@ var WebSocketClient = class WebSocketClient {
6742
6255
  }, 6e4);
6743
6256
  });
6744
6257
  }
6745
- /**
6746
- * Send a compare images request and wait for response
6747
- */
6748
- async sendCompareRequest(params) {
6749
- const requestId = crypto.randomUUID();
6750
- return new Promise((resolve, reject) => {
6751
- this.compareHandlers.set(requestId, {
6752
- resolve,
6753
- reject
6754
- });
6755
- const message = {
6756
- type: "compare:request",
6757
- requestId,
6758
- variantIds: params.variantIds,
6759
- aspects: params.aspects
6760
- };
6761
- try {
6762
- this.send(message);
6763
- } catch (err) {
6764
- this.compareHandlers.delete(requestId);
6765
- reject(err);
6766
- }
6767
- setTimeout(() => {
6768
- if (this.compareHandlers.has(requestId)) {
6769
- this.compareHandlers.delete(requestId);
6770
- reject(/* @__PURE__ */ new Error("Compare request timed out"));
6771
- }
6772
- }, 6e4);
6258
+ /**
6259
+ * Send a compare images request and wait for response
6260
+ */
6261
+ async sendCompareRequest(params) {
6262
+ const requestId = crypto.randomUUID();
6263
+ return new Promise((resolve, reject) => {
6264
+ this.compareHandlers.set(requestId, {
6265
+ resolve,
6266
+ reject
6267
+ });
6268
+ const message = {
6269
+ type: "compare:request",
6270
+ requestId,
6271
+ variantIds: params.variantIds,
6272
+ aspects: params.aspects
6273
+ };
6274
+ try {
6275
+ this.send(message);
6276
+ } catch (err) {
6277
+ this.compareHandlers.delete(requestId);
6278
+ reject(err);
6279
+ }
6280
+ setTimeout(() => {
6281
+ if (this.compareHandlers.has(requestId)) {
6282
+ this.compareHandlers.delete(requestId);
6283
+ reject(/* @__PURE__ */ new Error("Compare request timed out"));
6284
+ }
6285
+ }, 6e4);
6286
+ });
6287
+ }
6288
+ /**
6289
+ * Request sync state (initial state load)
6290
+ */
6291
+ requestSync() {
6292
+ this.send({ type: "sync:request" });
6293
+ }
6294
+ /**
6295
+ * Approve a pending approval
6296
+ */
6297
+ approveApproval(approvalId) {
6298
+ this.send({
6299
+ type: "approval:approve",
6300
+ approvalId
6301
+ });
6302
+ }
6303
+ /**
6304
+ * Reject a pending approval
6305
+ */
6306
+ rejectApproval(approvalId) {
6307
+ this.send({
6308
+ type: "approval:reject",
6309
+ approvalId
6310
+ });
6311
+ }
6312
+ /**
6313
+ * Request list of pending approvals
6314
+ */
6315
+ listApprovals() {
6316
+ this.send({ type: "approval:list" });
6317
+ }
6318
+ /**
6319
+ * Get current session state
6320
+ */
6321
+ getSession() {
6322
+ this.send({ type: "session:get" });
6323
+ }
6324
+ /**
6325
+ * Update session context (viewing, forge tray)
6326
+ */
6327
+ updateSession(data) {
6328
+ this.send({
6329
+ type: "session:update",
6330
+ ...data
6331
+ });
6332
+ }
6333
+ /**
6334
+ * Check if connected
6335
+ */
6336
+ isConnected() {
6337
+ return this.ws !== null && this.ws.readyState === wrapper_default.OPEN;
6338
+ }
6339
+ };
6340
+ //#endregion
6341
+ //#region src/cli/lib/reference-resolver.ts
6342
+ async function resolveCliAssetIdentifier(identifier, createClient) {
6343
+ if (!identifier.startsWith("asset:")) return identifier;
6344
+ return resolveAssetRef(identifier, (await readReferenceSpaceState(createClient)).assets);
6345
+ }
6346
+ async function resolveCliVariantIdentifier(identifier, createClient) {
6347
+ if (!identifier.startsWith("asset:")) return identifier;
6348
+ const state = await readReferenceSpaceState(createClient);
6349
+ return resolveVariantRef(identifier, state.assets, state.variants);
6350
+ }
6351
+ async function resolveCliVariantIdentifiers(identifiers, createClient) {
6352
+ if (!identifiers.some((identifier) => identifier.startsWith("asset:"))) return [...identifiers];
6353
+ const state = await readReferenceSpaceState(createClient);
6354
+ return identifiers.map((identifier) => resolveVariantRef(identifier, state.assets, state.variants));
6355
+ }
6356
+ async function readReferenceSpaceState(createClient) {
6357
+ const client = await createClient();
6358
+ await client.connect();
6359
+ try {
6360
+ const syncClient = client;
6361
+ if (typeof syncClient.setOnSyncState !== "function" || typeof syncClient.requestSync !== "function") throw new Error("Space sync is not available");
6362
+ return await new Promise((resolve, reject) => {
6363
+ let settled = false;
6364
+ const finish = (callback) => {
6365
+ if (settled) return;
6366
+ settled = true;
6367
+ clearTimeout(timer);
6368
+ callback();
6369
+ };
6370
+ const timer = setTimeout(() => finish(() => reject(/* @__PURE__ */ new Error("Timed out waiting for space sync state"))), 3e4);
6371
+ syncClient.setOnSyncState?.((state) => finish(() => resolve(state)));
6372
+ try {
6373
+ syncClient.requestSync?.();
6374
+ } catch (error) {
6375
+ finish(() => reject(error));
6376
+ }
6377
+ });
6378
+ } finally {
6379
+ client.disconnect();
6380
+ }
6381
+ }
6382
+ //#endregion
6383
+ //#region src/cli/commands/upload.ts
6384
+ /**
6385
+ * Upload Command - Upload one media file to create variants
6386
+ *
6387
+ * Usage:
6388
+ * makefx upload <file> --space <id> --asset <id> Upload to existing asset
6389
+ * makefx upload <file> --space <id> --name <name> Upload as new asset
6390
+ * makefx upload <file> --space <id> --name <name> --type <type>
6391
+ */
6392
+ var defaultDeps$10 = {
6393
+ loadConfig: loadStoredConfig,
6394
+ loadProjectConfig,
6395
+ resolveBaseUrl,
6396
+ fetch,
6397
+ readFile,
6398
+ stat,
6399
+ recordMirrorForFile,
6400
+ createStateClient: (env, spaceId) => WebSocketClient.create(env, spaceId),
6401
+ print: console.log
6402
+ };
6403
+ var UploadUsageError = class extends Error {};
6404
+ var RETIRED_UPLOAD_ORGANIZATION_OPTIONS = [
6405
+ ["collection", "--collection"],
6406
+ ["collection-name", "--collection-name"],
6407
+ ["collectionName", "--collection-name"],
6408
+ ["collection-role", "--collection-role"],
6409
+ ["collectionRole", "--collection-role"],
6410
+ ["collection-subject", "--collection-subject"],
6411
+ ["collectionSubject", "--collection-subject"],
6412
+ ["manual-relation", "--manual-relation"],
6413
+ ["manualRelation", "--manual-relation"],
6414
+ ["manual-relation-context", "--manual-relation-context"],
6415
+ ["manualRelationContext", "--manual-relation-context"]
6416
+ ];
6417
+ async function handleUpload(parsed) {
6418
+ try {
6419
+ await executeUpload(parsed);
6420
+ } catch (error) {
6421
+ console.error(error instanceof Error ? `Error: ${error.message}` : "Error: Upload failed");
6422
+ if (error instanceof UploadUsageError) printUsage$10();
6423
+ process$1.exitCode = 1;
6424
+ }
6425
+ }
6426
+ async function executeUpload(parsed, deps = defaultDeps$10) {
6427
+ const filePath = parsed.positionals[0];
6428
+ if (!filePath) throw new UploadUsageError("File path is required");
6429
+ rejectRetiredUploadOrganizationOptions(parsed);
6430
+ const projectConfig = await deps.loadProjectConfig();
6431
+ const env = resolveCommandEnvironment(parsed, projectConfig);
6432
+ const spaceId = resolveCommandSpace(parsed, projectConfig);
6433
+ let assetId = parsed.options.asset;
6434
+ const assetName = parsed.options.name;
6435
+ const assetType = parsed.options.type || "character";
6436
+ const requestedMediaKind = parsed.options["media-kind"] || parsed.options.mediaKind;
6437
+ const prompt = parsed.options.prompt;
6438
+ const model = parsed.options.model;
6439
+ const provider = parsed.options.provider;
6440
+ const rawProviderMetadata = parsed.options["provider-metadata"] ?? parsed.options.providerMetadata ?? parsed.options.provider_metadata;
6441
+ const rawGenerationProvenance = parsed.options["generation-provenance"] ?? parsed.options.generationProvenance ?? parsed.options.generation_provenance;
6442
+ const rawSourceVariantIds = parsed.options["source-variants"] ?? parsed.options.sourceVariants ?? parsed.options.sourceVariantIds ?? parsed.options["source-variant"] ?? parsed.options.sourceVariantId ?? parsed.options.parentVariantId;
6443
+ const rawRelationType = parsed.options["relation-type"] ?? parsed.options.relationType;
6444
+ const rawActiveVariantBehavior = parsed.options["active-variant-behavior"] ?? parsed.options.activeVariantBehavior;
6445
+ const jsonOutput = parsed.options.json === "true";
6446
+ if (parsed.options["dry-run"] === "true" || parsed.options.dryRun === "true") throw new UploadUsageError("--dry-run is not supported for direct file upload");
6447
+ if (!spaceId) throw new UploadUsageError("--space is required, or run: makefx init --space <id>");
6448
+ if (!assetId && !assetName) throw new UploadUsageError("Either --asset or --name is required");
6449
+ const providerMetadata = parseJsonObjectOption(rawProviderMetadata, "--provider-metadata");
6450
+ const generationProvenance = parseJsonObjectOption(rawGenerationProvenance, "--generation-provenance");
6451
+ let sourceVariantIds = parseCsvOption(rawSourceVariantIds, "--source-variant");
6452
+ const relationType = normalizeRelationType(rawRelationType);
6453
+ const activeVariantBehavior = normalizeActiveVariantBehavior(rawActiveVariantBehavior);
6454
+ if (sourceVariantIds.length === 0 && (parsed.options["relation-type"] || parsed.options.relationType)) throw new UploadUsageError("--relation-type requires --source-variant or --source-variants");
6455
+ const mediaType = resolveMediaType(path.extname(filePath).toLowerCase(), requestedMediaKind);
6456
+ const config = await deps.loadConfig(env);
6457
+ if (!config) throw new Error(`Not logged in to ${env} environment. Run: ${loginCommandForEnvironment(env)}`);
6458
+ if (config.token.expiresAt < Date.now()) throw new Error(`Token expired for ${env} environment. Run: ${loginCommandForEnvironment(env)}`);
6459
+ const baseUrl = deps.resolveBaseUrl(env);
6460
+ const accessToken = config.token.accessToken;
6461
+ const createStateClient = () => (deps.createStateClient ?? defaultDeps$10.createStateClient)(env, spaceId);
6462
+ if (assetId) assetId = await resolveCliAssetIdentifier(assetId, createStateClient);
6463
+ sourceVariantIds = await resolveCliVariantIdentifiers(sourceVariantIds, createStateClient);
6464
+ if (env === "local") process$1.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
6465
+ try {
6466
+ const fileStat = await deps.stat(filePath);
6467
+ if (!fileStat.isFile()) throw new Error(`"${filePath}" is not a file`);
6468
+ if (fileStat.size > 10485760) throw new Error(`File too large (${(fileStat.size / 1024 / 1024).toFixed(2)}MB). Maximum size: 10MB`);
6469
+ const fileBuffer = await deps.readFile(filePath);
6470
+ const fileName = path.basename(filePath);
6471
+ const formData = new FormData();
6472
+ const blob = new Blob([fileBuffer], { type: mediaType.mimeType });
6473
+ formData.append("file", blob, fileName);
6474
+ formData.append("mediaKind", mediaType.mediaKind);
6475
+ formData.append("activeVariantBehavior", activeVariantBehavior);
6476
+ if (assetId) formData.append("assetId", assetId);
6477
+ else {
6478
+ formData.append("assetName", assetName);
6479
+ formData.append("assetType", assetType);
6480
+ }
6481
+ if (prompt) formData.append("prompt", prompt);
6482
+ if (model) formData.append("model", model);
6483
+ if (provider) formData.append("provider", provider);
6484
+ if (providerMetadata) formData.append("providerMetadata", JSON.stringify(providerMetadata));
6485
+ if (generationProvenance) formData.append("generationProvenance", JSON.stringify(generationProvenance));
6486
+ if (sourceVariantIds.length > 0) formData.append("lineage", JSON.stringify(sourceVariantIds.map((parentVariantId) => ({
6487
+ parentVariantId,
6488
+ relationType
6489
+ }))));
6490
+ if (!jsonOutput) {
6491
+ deps.print(`\nUploading "${fileName}" to space ${spaceId}...`);
6492
+ deps.print(` Media kind: ${mediaType.mediaKind}`);
6493
+ if (assetId) deps.print(` Target asset: ${assetId}`);
6494
+ else deps.print(` Creating asset: "${assetName}" (${assetType})`);
6495
+ if (sourceVariantIds.length === 1) deps.print(` Source variant: ${sourceVariantIds[0]} (${relationType})`);
6496
+ else if (sourceVariantIds.length > 1) deps.print(` Source variants: ${sourceVariantIds.join(", ")} (${relationType})`);
6497
+ }
6498
+ const response = await deps.fetch(`${baseUrl}/api/spaces/${spaceId}/upload`, {
6499
+ method: "POST",
6500
+ headers: { "Authorization": `Bearer ${accessToken}` },
6501
+ body: formData
6502
+ });
6503
+ const data = await response.json();
6504
+ if (!response.ok) throw new Error(`Upload failed: ${"error" in data ? data.error : response.statusText}`);
6505
+ const upload = data;
6506
+ const result = {
6507
+ asset: upload.asset,
6508
+ variant: upload.variant,
6509
+ lineage: upload.lineage
6510
+ };
6511
+ const resultAssetName = upload.asset?.name ?? assetName ?? "asset";
6512
+ result.assetRef = createAssetRef(resultAssetName, upload.asset?.id ?? upload.variant.asset_id);
6513
+ result.variantRef = createVariantRef(resultAssetName, upload.variant.asset_id, upload.variant.id);
6514
+ await recordUploadMirror({
6515
+ deps,
6516
+ projectRoot: projectConfig?.projectRoot,
6517
+ env,
6518
+ baseUrl,
6519
+ spaceId,
6520
+ filePath,
6521
+ assetId: upload.asset?.id ?? upload.variant.asset_id,
6522
+ variantId: upload.variant.id,
6523
+ mediaKind: upload.variant.media_kind || mediaType.mediaKind,
6524
+ mediaKey: upload.variant.media_key || upload.variant.image_key
6525
+ });
6526
+ if (jsonOutput) {
6527
+ deps.print(JSON.stringify(result, null, 2));
6528
+ return result;
6529
+ }
6530
+ deps.print("\nUpload successful!\n");
6531
+ if (upload.asset) {
6532
+ deps.print("New Asset:");
6533
+ deps.print(` Ref: ${result.assetRef}`);
6534
+ deps.print(` ID: ${upload.asset.id}`);
6535
+ deps.print(` Name: ${upload.asset.name}`);
6536
+ deps.print(` Type: ${upload.asset.type}`);
6537
+ deps.print("");
6538
+ }
6539
+ deps.print("Variant:");
6540
+ deps.print(` Ref: ${result.variantRef}`);
6541
+ deps.print(` ID: ${upload.variant.id}`);
6542
+ deps.print(` Asset: ${upload.variant.asset_id}`);
6543
+ deps.print(` Status: ${upload.variant.status}`);
6544
+ deps.print(` Media: ${upload.variant.media_kind || mediaType.mediaKind}`);
6545
+ deps.print(` File: ${upload.variant.media_key || upload.variant.image_key || "-"}`);
6546
+ if (upload.variant.image_key) deps.print(` Image: ${upload.variant.image_key}`);
6547
+ if (upload.variant.thumb_key) deps.print(` Thumb: ${upload.variant.thumb_key}`);
6548
+ if (upload.variant.media_mime_type) deps.print(` MIME: ${upload.variant.media_mime_type}`);
6549
+ if (upload.variant.recipe) try {
6550
+ const recipe = JSON.parse(upload.variant.recipe);
6551
+ deps.print("\nRecipe:");
6552
+ deps.print(` Operation: ${recipe.operation}`);
6553
+ deps.print(` Original: ${recipe.originalFilename}`);
6554
+ deps.print(` Uploaded: ${recipe.uploadedAt}`);
6555
+ } catch {}
6556
+ deps.print("\nTo inspect:");
6557
+ deps.print(` makefx assets show ${result.assetRef} --space ${spaceId}`);
6558
+ return result;
6559
+ } catch (error) {
6560
+ if (error.code === "ENOENT") throw new Error(`File not found: ${filePath}`);
6561
+ throw error;
6562
+ }
6563
+ }
6564
+ async function recordUploadMirror(input) {
6565
+ if (!input.deps.recordMirrorForFile) return;
6566
+ try {
6567
+ await input.deps.recordMirrorForFile({
6568
+ projectRoot: input.projectRoot,
6569
+ baseUrl: input.baseUrl,
6570
+ environment: input.env,
6571
+ spaceId: input.spaceId,
6572
+ filePath: input.filePath,
6573
+ assetId: input.assetId,
6574
+ variantId: input.variantId,
6575
+ mediaKind: input.mediaKind,
6576
+ mediaKey: input.mediaKey
6773
6577
  });
6578
+ } catch (error) {
6579
+ console.warn(`Warning: upload succeeded but mirror registry was not updated: ${error instanceof Error ? error.message : String(error)}`);
6774
6580
  }
6775
- /**
6776
- * Request sync state (initial state load)
6777
- */
6778
- requestSync() {
6779
- this.send({ type: "sync:request" });
6581
+ }
6582
+ function parseJsonObjectOption(value, optionName) {
6583
+ if (value === void 0) return void 0;
6584
+ let parsed;
6585
+ try {
6586
+ parsed = JSON.parse(value);
6587
+ } catch {
6588
+ throw new UploadUsageError(`${optionName} must be valid JSON`);
6780
6589
  }
6781
- /**
6782
- * Approve a pending approval
6783
- */
6784
- approveApproval(approvalId) {
6785
- this.send({
6786
- type: "approval:approve",
6787
- approvalId
6788
- });
6590
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new UploadUsageError(`${optionName} must be a JSON object`);
6591
+ return parsed;
6592
+ }
6593
+ function normalizeRelationType(value) {
6594
+ if (!value) return "derived";
6595
+ if (value === "derived" || value === "refined" || value === "forked") return value;
6596
+ throw new UploadUsageError("--relation-type must be derived, refined, or forked");
6597
+ }
6598
+ function normalizeActiveVariantBehavior(value) {
6599
+ if (!value || value === "if-missing" || value === "if_missing") return "if-missing";
6600
+ if (value === "set-active" || value === "set_active") return "set-active";
6601
+ if (value === "keep") return "keep";
6602
+ throw new UploadUsageError("--active-variant-behavior must be if-missing, set-active, or keep");
6603
+ }
6604
+ function rejectRetiredUploadOrganizationOptions(parsed) {
6605
+ const usedOptions = RETIRED_UPLOAD_ORGANIZATION_OPTIONS.filter(([key]) => parsed.options[key] !== void 0).map(([, flag]) => flag);
6606
+ if (usedOptions.length === 0) return;
6607
+ throw new UploadUsageError(`${Array.from(new Set(usedOptions)).join(", ")} ${usedOptions.length === 1 ? "was" : "were"} removed. Upload creates assets or variants only; organize the result visually on the Space canvas. Use --source-variant for immutable lineage provenance.`);
6608
+ }
6609
+ function parseCsvOption(value, optionName) {
6610
+ if (!value) return [];
6611
+ if (value === "true") throw new UploadUsageError(`${optionName} requires a value`);
6612
+ return value.split(",").map((entry) => entry.trim()).filter(Boolean);
6613
+ }
6614
+ function printUsage$10() {
6615
+ console.log(`
6616
+ Usage:
6617
+ makefx upload <file> --asset <id> [--space <id>] Upload media to existing asset
6618
+ makefx upload <file> --name <name> [--space <id>] Upload media as a new asset
6619
+
6620
+ Options:
6621
+ --space <id> Target space ID; defaults from initialized project
6622
+ --asset <id> Target asset ID (upload as new variant)
6623
+ --name <name> New asset name (creates asset + variant)
6624
+ --type <type> Asset type for new assets (default: character)
6625
+ --media-kind <k> Optional explicit kind: image, audio, or video
6626
+ --prompt <text> Prompt provenance for uploaded media
6627
+ --model <model> Model provenance for uploaded media
6628
+ --provider <name> Provider provenance for uploaded media
6629
+ --provider-metadata <json> Provider metadata JSON object
6630
+ --generation-provenance <json> Extra provenance JSON object
6631
+ --source-variant <ids> Comma-separated source variants for upload lineage
6632
+ --source-variants <ids> Alias for --source-variant
6633
+ --relation-type <type> Lineage type: derived, refined, or forked (default: derived)
6634
+ --active-variant-behavior <b> if-missing, set-active, or keep
6635
+ --json Print machine-readable upload output with Space IDs
6636
+ --env <env> Environment (production|stage|local)
6637
+ --local Shortcut for --env local
6638
+
6639
+ Examples:
6640
+ makefx upload hero.png --space abc123 --name "Hero Character"
6641
+ makefx upload hero.png --space abc123 --name "Hero" --prompt "external render" --provider blender
6642
+ makefx upload paintover.png --space abc123 --asset def456 --source-variant var123 --relation-type refined
6643
+ makefx upload scene.png --space abc123 --name "Cocina" --type scene --source-variants anna,roman,bg
6644
+ makefx upload theme.mp3 --space abc123 --name "Theme Music" --type audio
6645
+ makefx upload cutscene.mp4 --space abc123 --name "Opening Cutscene" --type video
6646
+ makefx upload variant.jpg --space abc123 --asset def456
6647
+ makefx upload hero.png --space abc123 --name "Hero" --json
6648
+ `);
6649
+ }
6650
+ //#endregion
6651
+ //#region src/cli/lib/run-manifest.ts
6652
+ function createRunId(date = /* @__PURE__ */ new Date()) {
6653
+ return `${date.toISOString().replace(/[-:]/g, "").replace(/\.\d{3}Z$/, "Z")}-${crypto.randomUUID().slice(0, 8)}`;
6654
+ }
6655
+ async function saveRunManifest(manifest, cwd = process.cwd()) {
6656
+ const manifestPath = path.join(cwd, ".inventory", "runs", `${manifest.runId}.json`);
6657
+ await mkdir(path.dirname(manifestPath), { recursive: true });
6658
+ await writeFile(manifestPath, JSON.stringify(manifest, null, 2) + "\n", "utf8");
6659
+ return manifestPath;
6660
+ }
6661
+ function getRunManifestDir(projectRoot = process.cwd()) {
6662
+ return path.join(projectRoot, ".inventory", "runs");
6663
+ }
6664
+ async function listRunManifests(projectRoot = process.cwd()) {
6665
+ const runsDir = getRunManifestDir(projectRoot);
6666
+ let entries;
6667
+ try {
6668
+ entries = await readdir(runsDir);
6669
+ } catch (error) {
6670
+ if (error.code === "ENOENT") return [];
6671
+ throw error;
6789
6672
  }
6790
- /**
6791
- * Reject a pending approval
6792
- */
6793
- rejectApproval(approvalId) {
6794
- this.send({
6795
- type: "approval:reject",
6796
- approvalId
6797
- });
6673
+ return (await Promise.all(entries.filter((entry) => entry.endsWith(".json")).map((entry) => readRunManifest(path.join(runsDir, entry))))).sort((a, b) => {
6674
+ const byCreatedAt = Date.parse(b.manifest.createdAt) - Date.parse(a.manifest.createdAt);
6675
+ if (Number.isFinite(byCreatedAt) && byCreatedAt !== 0) return byCreatedAt;
6676
+ return b.manifestPath.localeCompare(a.manifestPath);
6677
+ });
6678
+ }
6679
+ async function readRunManifest(manifestPath) {
6680
+ const raw = await readFile(manifestPath, "utf8");
6681
+ return {
6682
+ manifest: normalizeRunManifest(JSON.parse(raw), manifestPath),
6683
+ manifestPath
6684
+ };
6685
+ }
6686
+ async function resolveRunManifest(input) {
6687
+ if (input.latest) {
6688
+ const manifests = await listRunManifests(input.projectRoot);
6689
+ if (manifests.length === 0) throw new Error(`No run manifests found in ${getRunManifestDir(input.projectRoot)}`);
6690
+ return manifests[0];
6691
+ }
6692
+ const selector = input.runIdOrPath;
6693
+ if (!selector) throw new Error("Run ID, manifest path, or --latest is required");
6694
+ return readRunManifest(looksLikeManifestPath(selector) ? path.resolve(selector) : path.join(getRunManifestDir(input.projectRoot), `${selector}.json`));
6695
+ }
6696
+ function createMediaRunExport(record, projectRoot) {
6697
+ const pathBase = record.manifest.workingDir || projectRoot;
6698
+ return {
6699
+ version: 1,
6700
+ format: "media-handoff",
6701
+ runId: record.manifest.runId,
6702
+ manifestPath: record.manifestPath,
6703
+ projectRoot,
6704
+ success: record.manifest.success,
6705
+ prompt: record.manifest.prompt,
6706
+ name: record.manifest.name,
6707
+ assetType: record.manifest.assetType,
6708
+ spaceId: record.manifest.spaceId,
6709
+ baseUrl: record.manifest.baseUrl,
6710
+ refs: record.manifest.refs,
6711
+ referenceVariantIds: record.manifest.referenceVariantIds,
6712
+ createdAt: record.manifest.createdAt,
6713
+ completedAt: record.manifest.completedAt,
6714
+ media: sortedMedia(record.manifest).map((media) => ({
6715
+ ...media,
6716
+ absolutePath: path.resolve(pathBase, media.localPath)
6717
+ })),
6718
+ images: [...record.manifest.images].sort((a, b) => a.index - b.index).map((image) => ({
6719
+ ...image,
6720
+ absolutePath: path.resolve(pathBase, image.localPath)
6721
+ })),
6722
+ failed: record.manifest.failed
6723
+ };
6724
+ }
6725
+ function createRemotionRunExport(record, projectRoot) {
6726
+ return {
6727
+ ...createMediaRunExport(record, projectRoot),
6728
+ format: "remotion-keyframes"
6729
+ };
6730
+ }
6731
+ function manifestMediaFromVariant(input) {
6732
+ const mediaKind = input.variant.media_kind || "image";
6733
+ const mediaKey = input.variant.media_key || input.variant.image_key;
6734
+ if (!mediaKey) throw new Error(`Variant has no media key: ${input.variant.id}`);
6735
+ return {
6736
+ index: input.index,
6737
+ mediaKind,
6738
+ assetId: input.variant.asset_id,
6739
+ variantId: input.variant.id,
6740
+ mediaKey,
6741
+ imageKey: input.variant.image_key,
6742
+ thumbKey: input.variant.thumb_key,
6743
+ mimeType: input.variant.media_mime_type || null,
6744
+ sizeBytes: input.variant.media_size_bytes ?? null,
6745
+ width: input.variant.media_width ?? null,
6746
+ height: input.variant.media_height ?? null,
6747
+ durationMs: input.variant.media_duration_ms ?? null,
6748
+ localPath: input.localPath,
6749
+ webUrl: `${input.baseUrl}/spaces/${input.spaceId}/assets/${input.variant.asset_id}`
6750
+ };
6751
+ }
6752
+ function looksLikeManifestPath(value) {
6753
+ return path.isAbsolute(value) || value.endsWith(".json") || value.includes("/") || value.includes("\\");
6754
+ }
6755
+ function normalizeRunManifest(manifest, manifestPath) {
6756
+ if (manifest.version !== 1) throw new Error(`Unsupported run manifest version in ${manifestPath}`);
6757
+ if (!isSupportedCommand(manifest.command)) throw new Error(`Unsupported run manifest command in ${manifestPath}`);
6758
+ if (!manifest.runId || typeof manifest.runId !== "string") throw new Error(`Run manifest is missing runId: ${manifestPath}`);
6759
+ const media = Array.isArray(manifest.media) ? manifest.media : manifest.images;
6760
+ if (!Array.isArray(media)) throw new Error(`Run manifest is missing media: ${manifestPath}`);
6761
+ if (!Array.isArray(manifest.failed)) throw new Error(`Run manifest is missing failed entries: ${manifestPath}`);
6762
+ const normalizedMedia = media.map((entry) => normalizeManifestMedia(entry));
6763
+ return {
6764
+ ...manifest,
6765
+ mediaKind: manifest.mediaKind || inferManifestMediaKind(normalizedMedia),
6766
+ media: normalizedMedia,
6767
+ images: normalizedMedia.filter(isRunManifestImage)
6768
+ };
6769
+ }
6770
+ function sortedMedia(manifest) {
6771
+ return [...manifest.media].sort((a, b) => a.index - b.index);
6772
+ }
6773
+ function isSupportedCommand(command) {
6774
+ return command === "generate" || command === "refine" || command === "derive" || command === "batch";
6775
+ }
6776
+ function normalizeManifestMedia(entry) {
6777
+ const legacyImageKey = "imageKey" in entry ? entry.imageKey : null;
6778
+ const mediaKey = "mediaKey" in entry ? entry.mediaKey : legacyImageKey;
6779
+ return {
6780
+ index: entry.index,
6781
+ mediaKind: entry.mediaKind || "image",
6782
+ assetId: entry.assetId,
6783
+ variantId: entry.variantId,
6784
+ mediaKey: mediaKey || "",
6785
+ imageKey: legacyImageKey,
6786
+ thumbKey: entry.thumbKey,
6787
+ mimeType: "mimeType" in entry ? entry.mimeType : null,
6788
+ sizeBytes: "sizeBytes" in entry ? entry.sizeBytes : null,
6789
+ width: "width" in entry ? entry.width : null,
6790
+ height: "height" in entry ? entry.height : null,
6791
+ durationMs: "durationMs" in entry ? entry.durationMs : null,
6792
+ localPath: entry.localPath,
6793
+ webUrl: entry.webUrl
6794
+ };
6795
+ }
6796
+ function isRunManifestImage(entry) {
6797
+ return entry.mediaKind === "image" && Boolean(entry.imageKey);
6798
+ }
6799
+ function inferManifestMediaKind(media) {
6800
+ return media[0]?.mediaKind || "image";
6801
+ }
6802
+ //#endregion
6803
+ //#region src/cli/lib/image-transfer.ts
6804
+ var MAX_FILE_SIZE_MB = 10;
6805
+ var MAX_FILE_SIZE_BYTES = MAX_FILE_SIZE_MB * 1024 * 1024;
6806
+ var ALLOWED_EXTENSIONS = [
6807
+ ".jpg",
6808
+ ".jpeg",
6809
+ ".png",
6810
+ ".gif",
6811
+ ".webp"
6812
+ ];
6813
+ var EXT_TO_MIME = {
6814
+ ".jpg": "image/jpeg",
6815
+ ".jpeg": "image/jpeg",
6816
+ ".png": "image/png",
6817
+ ".gif": "image/gif",
6818
+ ".webp": "image/webp"
6819
+ };
6820
+ function isSupportedImagePath(filePath) {
6821
+ return ALLOWED_EXTENSIONS.includes(path.extname(filePath).toLowerCase());
6822
+ }
6823
+ function looksLikeFilePath(value) {
6824
+ return value.startsWith(".") || value.includes("/") || value.includes("\\") || isSupportedImagePath(value);
6825
+ }
6826
+ async function uploadLocalImageAsReference(input) {
6827
+ const ext = path.extname(input.filePath).toLowerCase();
6828
+ if (!ALLOWED_EXTENSIONS.includes(ext)) throw new Error(`Invalid reference file type "${ext}". Allowed: ${ALLOWED_EXTENSIONS.join(", ")}`);
6829
+ const fileStat = await stat(input.filePath);
6830
+ if (!fileStat.isFile()) throw new Error(`Reference is not a file: ${input.filePath}`);
6831
+ if (fileStat.size > MAX_FILE_SIZE_BYTES) throw new Error(`Reference file too large (${(fileStat.size / 1024 / 1024).toFixed(2)}MB). Maximum size: ${MAX_FILE_SIZE_MB}MB`);
6832
+ const fileBuffer = await readFile(input.filePath);
6833
+ const filename = path.basename(input.filePath);
6834
+ const mimeType = EXT_TO_MIME[ext] || "image/png";
6835
+ const assetName = input.assetName || `Reference: ${filename.replace(/\.[^/.]+$/, "")}`;
6836
+ const formData = new FormData();
6837
+ formData.append("file", new Blob([fileBuffer], { type: mimeType }), filename);
6838
+ formData.append("assetName", assetName);
6839
+ formData.append("assetType", "reference");
6840
+ formData.append("mediaKind", DEFAULT_MEDIA_KIND);
6841
+ const response = await fetch(`${input.baseUrl}/api/spaces/${input.spaceId}/upload`, {
6842
+ method: "POST",
6843
+ headers: { "Authorization": `Bearer ${input.accessToken}` },
6844
+ body: formData
6845
+ });
6846
+ const data = await response.json();
6847
+ if (!response.ok || !data.success || !data.variant) throw new Error(`Failed to upload reference "${input.filePath}": ${data.error || response.statusText}`);
6848
+ return {
6849
+ asset: data.asset,
6850
+ variant: data.variant
6851
+ };
6852
+ }
6853
+ async function downloadFile(input) {
6854
+ if (!input.force) try {
6855
+ if ((await stat(input.outputPath)).isFile()) throw new Error(`Output file already exists: ${input.outputPath}. Pass --force to overwrite.`);
6856
+ } catch (error) {
6857
+ if (error.code !== "ENOENT") throw error;
6798
6858
  }
6799
- /**
6800
- * Request list of pending approvals
6801
- */
6802
- listApprovals() {
6803
- this.send({ type: "approval:list" });
6859
+ const headers = {};
6860
+ if (input.accessToken) headers.Authorization = `Bearer ${input.accessToken}`;
6861
+ const requestUrl = `${input.baseUrl}${input.requestPath}`;
6862
+ let response;
6863
+ try {
6864
+ response = await fetch(requestUrl, { headers });
6865
+ } catch (error) {
6866
+ throw new Error(`Failed to download ${input.requestPath}: ${formatFetchError(error)}`);
6804
6867
  }
6805
- /**
6806
- * Get current session state
6807
- */
6808
- getSession() {
6809
- this.send({ type: "session:get" });
6868
+ if (!response.ok) {
6869
+ const errorText = await readErrorText(response);
6870
+ throw new Error(`Failed to download ${input.requestPath}: ${response.status} ${response.statusText}${errorText ? ` - ${errorText}` : ""}`);
6810
6871
  }
6811
- /**
6812
- * Update session context (viewing, forge tray)
6813
- */
6814
- updateSession(data) {
6815
- this.send({
6816
- type: "session:update",
6817
- ...data
6818
- });
6872
+ await mkdir(path.dirname(input.outputPath), { recursive: true });
6873
+ if (!response.body) throw new Error(`Failed to download ${input.requestPath}: response body was empty`);
6874
+ try {
6875
+ await pipeline(Readable.fromWeb(response.body), createWriteStream(input.outputPath));
6876
+ } catch (error) {
6877
+ throw new Error(`Failed to write download ${input.requestPath} to ${input.outputPath}: ${formatFetchError(error)}`);
6819
6878
  }
6820
- /**
6821
- * Check if connected
6822
- */
6823
- isConnected() {
6824
- return this.ws !== null && this.ws.readyState === wrapper_default.OPEN;
6879
+ return {
6880
+ headers: response.headers,
6881
+ requestUrl
6882
+ };
6883
+ }
6884
+ async function readErrorText(response) {
6885
+ try {
6886
+ return (await response.text()).slice(0, 500).trim();
6887
+ } catch {
6888
+ return "";
6825
6889
  }
6826
- };
6890
+ }
6891
+ function formatFetchError(error) {
6892
+ if (!(error instanceof Error)) return String(error);
6893
+ const cause = error.cause instanceof Error ? ` (${error.cause.message})` : "";
6894
+ return `${error.message}${cause}`;
6895
+ }
6896
+ async function downloadImage(input) {
6897
+ await downloadFile({
6898
+ baseUrl: input.baseUrl,
6899
+ accessToken: input.accessToken,
6900
+ requestPath: `/api/images/${input.imageKey}`,
6901
+ outputPath: input.outputPath,
6902
+ force: input.force
6903
+ });
6904
+ }
6827
6905
  //#endregion
6828
6906
  //#region src/shared/videoGenerationOptions.ts
6829
6907
  var VIDEO_GENERATION_ASPECT_RATIOS = ["16:9", "9:16"];
@@ -6832,12 +6910,27 @@ var VIDEO_GENERATION_RESOLUTIONS = [
6832
6910
  "1080p",
6833
6911
  "4k"
6834
6912
  ];
6913
+ var ALL_VIDEO_GENERATION_RESOLUTIONS = ["480p", ...VIDEO_GENERATION_RESOLUTIONS];
6835
6914
  var KLING_VIDEO_GENERATION_RESOLUTIONS = ["720p", "1080p"];
6836
6915
  var VIDEO_GENERATION_DURATION_SECONDS = [
6837
6916
  4,
6838
6917
  6,
6839
6918
  8
6840
6919
  ];
6920
+ var SEEDANCE_2_DURATION_SECONDS = [
6921
+ 4,
6922
+ 5,
6923
+ 6,
6924
+ 7,
6925
+ 8,
6926
+ 9,
6927
+ 10,
6928
+ 11,
6929
+ 12,
6930
+ 13,
6931
+ 14,
6932
+ 15
6933
+ ];
6841
6934
  var VIDEO_GENERATION_TIERS = [
6842
6935
  "generate",
6843
6936
  "fast",
@@ -6847,7 +6940,9 @@ var VIDEO_MODEL_SELECTIONS = [
6847
6940
  "veo-3.1",
6848
6941
  "omni-flash",
6849
6942
  "kling",
6850
- "fal-seedance"
6943
+ "fal-seedance",
6944
+ "seedance-2",
6945
+ "seedance-2-fast"
6851
6946
  ];
6852
6947
  var VIDEO_MODEL_SUPPORTED_OPERATIONS = {
6853
6948
  "veo-3.1": [
@@ -6865,10 +6960,14 @@ var VIDEO_MODEL_SUPPORTED_OPERATIONS = {
6865
6960
  "refine",
6866
6961
  "derive"
6867
6962
  ],
6868
- "fal-seedance": ["generate"]
6963
+ "fal-seedance": ["generate"],
6964
+ "seedance-2": ["generate", "derive"],
6965
+ "seedance-2-fast": ["generate", "derive"]
6869
6966
  };
6870
6967
  var KLING_VIDEO_MODEL = "kling-v3";
6871
6968
  var FAL_VIDEO_MODEL = "fal-ai/bytedance/seedance/v1/pro/text-to-video";
6969
+ var FAL_SEEDANCE_2_MODEL = "bytedance/seedance-2.0/image-to-video";
6970
+ var FAL_SEEDANCE_2_FAST_MODEL = "bytedance/seedance-2.0/fast/image-to-video";
6872
6971
  var GEMINI_OMNI_FLASH_VIDEO_MODEL = "gemini-omni-flash-preview";
6873
6972
  var VIDEO_GENERATION_AUDIO_TOGGLE_MODELS = [];
6874
6973
  var VIDEO_GENERATION_RESOLUTIONS_BY_TIER = {
@@ -6887,11 +6986,11 @@ var VIDEO_GENERATION_TIER_MODELS = {
6887
6986
  lite: "veo-3.1-lite-generate-preview"
6888
6987
  };
6889
6988
  function normalizeVideoGenerationResolution(value) {
6890
- return VIDEO_GENERATION_RESOLUTIONS.includes(value) ? value : void 0;
6989
+ return ALL_VIDEO_GENERATION_RESOLUTIONS.includes(value) ? value : void 0;
6891
6990
  }
6892
6991
  function normalizeVideoGenerationDurationSeconds(value) {
6893
6992
  const numeric = typeof value === "string" ? Number(value) : value;
6894
- return VIDEO_GENERATION_DURATION_SECONDS.includes(numeric) ? numeric : void 0;
6993
+ return SEEDANCE_2_DURATION_SECONDS.includes(numeric) ? numeric : void 0;
6895
6994
  }
6896
6995
  function normalizeVideoGenerationTier(value) {
6897
6996
  return VIDEO_GENERATION_TIERS.includes(value) ? value : void 0;
@@ -6906,6 +7005,8 @@ function getVideoGenerationModelForSelection(selection = DEFAULT_VIDEO_MODEL_SEL
6906
7005
  if (selection === "omni-flash") return GEMINI_OMNI_FLASH_VIDEO_MODEL;
6907
7006
  if (selection === "kling") return KLING_VIDEO_MODEL;
6908
7007
  if (selection === "fal-seedance") return FAL_VIDEO_MODEL;
7008
+ if (selection === "seedance-2") return FAL_SEEDANCE_2_MODEL;
7009
+ if (selection === "seedance-2-fast") return FAL_SEEDANCE_2_FAST_MODEL;
6909
7010
  return getVideoGenerationModelForTier(tier);
6910
7011
  }
6911
7012
  function getVideoGenerationResolutionsForTier(tier = DEFAULT_VIDEO_GENERATION_TIER) {
@@ -6915,7 +7016,14 @@ function isVideoGenerationResolutionSupportedForTier(resolution, tier = DEFAULT_
6915
7016
  return VIDEO_GENERATION_RESOLUTIONS_BY_TIER[tier].includes(resolution);
6916
7017
  }
6917
7018
  function getVideoGenerationResolutionsForModel(model) {
6918
- return isKlingVideoGenerationModel(model) || isFalVideoGenerationModel(model) ? KLING_VIDEO_GENERATION_RESOLUTIONS : getVideoGenerationResolutionsForTier(getVideoGenerationTierForModel(model));
7019
+ if (isKlingVideoGenerationModel(model) || isFalSeedanceV1VideoGenerationModel(model)) return KLING_VIDEO_GENERATION_RESOLUTIONS;
7020
+ if (model === "bytedance/seedance-2.0/fast/image-to-video") return ["480p", "720p"];
7021
+ if (isFalSeedance2VideoGenerationModel(model)) return [
7022
+ "480p",
7023
+ "720p",
7024
+ "1080p"
7025
+ ];
7026
+ return getVideoGenerationResolutionsForTier(getVideoGenerationTierForModel(model));
6919
7027
  }
6920
7028
  function isVideoGenerationResolutionSupportedForModel(model, resolution) {
6921
7029
  return getVideoGenerationResolutionsForModel(model).includes(resolution);
@@ -6928,6 +7036,8 @@ function getVideoModelSelectionForModel(model) {
6928
7036
  if (model === "gemini-omni-flash-preview") return "omni-flash";
6929
7037
  if (model === "kling-v3") return "kling";
6930
7038
  if (model === "fal-ai/bytedance/seedance/v1/pro/text-to-video") return "fal-seedance";
7039
+ if (model === "bytedance/seedance-2.0/image-to-video") return "seedance-2";
7040
+ if (model === "bytedance/seedance-2.0/fast/image-to-video") return "seedance-2-fast";
6931
7041
  return getVideoGenerationTierForModel(model) ? "veo-3.1" : void 0;
6932
7042
  }
6933
7043
  function isVeoVideoGenerationModel(model) {
@@ -6940,8 +7050,14 @@ function isKlingVideoGenerationModel(model) {
6940
7050
  return model === KLING_VIDEO_MODEL;
6941
7051
  }
6942
7052
  function isFalVideoGenerationModel(model) {
7053
+ return isFalSeedanceV1VideoGenerationModel(model) || isFalSeedance2VideoGenerationModel(model);
7054
+ }
7055
+ function isFalSeedanceV1VideoGenerationModel(model) {
6943
7056
  return model === FAL_VIDEO_MODEL;
6944
7057
  }
7058
+ function isFalSeedance2VideoGenerationModel(model) {
7059
+ return model === "bytedance/seedance-2.0/image-to-video" || model === "bytedance/seedance-2.0/fast/image-to-video";
7060
+ }
6945
7061
  function isVideoGenerationModel(model) {
6946
7062
  return isVeoVideoGenerationModel(model) || isGeminiOmniVideoGenerationModel(model) || isKlingVideoGenerationModel(model) || isFalVideoGenerationModel(model);
6947
7063
  }
@@ -7022,7 +7138,7 @@ var IMAGE_MODEL_CAPABILITIES = {
7022
7138
  flux: {
7023
7139
  selection: "flux",
7024
7140
  modelId: IMAGE_MODEL_IDS.flux,
7025
- label: "FLUX.1 Dev",
7141
+ label: "fal.ai FLUX.1 Dev",
7026
7142
  provider: "fal",
7027
7143
  supportedOperations: ["generate"],
7028
7144
  maxReferenceImages: 0,
@@ -7248,43 +7364,6 @@ var defaultDeps$9 = {
7248
7364
  }
7249
7365
  }
7250
7366
  };
7251
- var ESTIMATE_METER_LABELS = {
7252
- gemini_images: "Gemini image",
7253
- gemini_videos: "Veo video unit",
7254
- gemini_audio: "Lyria generation",
7255
- elevenlabs_audio: "ElevenLabs unit"
7256
- };
7257
- function formatEstimatedUsd(microUsd) {
7258
- if (!Number.isFinite(microUsd) || microUsd <= 0) return "$0.00";
7259
- if (microUsd < 1e4) return "<$0.01";
7260
- return `$${(microUsd / 1e6).toFixed(2)}`;
7261
- }
7262
- function formatEstimateQuantity(quantity, singular) {
7263
- const normalized = Number.isFinite(quantity) ? quantity : 0;
7264
- const suffix = normalized === 1 ? singular : `${singular}s`;
7265
- return `${normalized.toLocaleString()} ${suffix}`;
7266
- }
7267
- async function printPreflightEstimate(client, params) {
7268
- let result;
7269
- try {
7270
- result = await client.sendGenerationEstimateRequest(params);
7271
- } catch (error) {
7272
- console.warn(`Preflight estimate unavailable: ${error instanceof Error ? error.message : String(error)}`);
7273
- return;
7274
- }
7275
- if (!result.success || !result.estimate) {
7276
- console.warn(`Preflight estimate unavailable: ${result.error || "unknown error"}`);
7277
- return;
7278
- }
7279
- const estimate = result.estimate;
7280
- const meterLabel = ESTIMATE_METER_LABELS[estimate.meterEventName] ?? estimate.meterEventName;
7281
- const quotaLine = estimate.quota?.limit === null || estimate.quota?.limit === void 0 ? void 0 : `${estimate.quota.used.toLocaleString()} used / ${estimate.quota.limit.toLocaleString()} limit`;
7282
- console.log("Preflight estimate:");
7283
- console.log(` Usage: ${formatEstimateQuantity(estimate.quotaQuantity, meterLabel)}, ${formatEstimateQuantity(estimate.platformWorkflowRuns, "workflow")}`);
7284
- console.log(` Estimated provider cost: ${formatEstimatedUsd(estimate.providerCostMicroUsd)}${estimate.billingMode === "byok" ? " (BYOK)" : ""}`);
7285
- if (quotaLine) console.log(` Quota: ${quotaLine}`);
7286
- if (!estimate.allowed) throw new Error(estimate.denyMessage || "Preflight check denied this generation request");
7287
- }
7288
7367
  async function handleGenerate(parsed) {
7289
7368
  await handleForgeCommand("generate", parsed);
7290
7369
  }
@@ -7398,32 +7477,19 @@ async function executeGenerate(parsed, ctx, client, deps, mediaKind, followOptio
7398
7477
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
7399
7478
  const videoFrameRefs = parseVideoFrameReferenceOptions(parsed, "generate", mediaKind).refs;
7400
7479
  const state = videoFrameRefs.length > 0 ? await requestSpaceState$1(client) : void 0;
7401
- const referenceVariantIds = videoFrameRefs.length > 0 ? await resolveReferenceVariantIds(videoFrameRefs, ctx, deps, state?.variants, CLI_GENERATION_MEDIA_KIND) : [];
7480
+ const referenceVariantIds = videoFrameRefs.length > 0 ? await resolveReferenceVariantIds(videoFrameRefs, ctx, deps, state?.variants, CLI_GENERATION_MEDIA_KIND, state?.assets) : [];
7402
7481
  const imageOptions = parseImageGenerationOptions(parsed, mediaKind);
7403
7482
  const audioModelOptions = parseAudioGenerationOptions(parsed, mediaKind);
7404
7483
  const videoAudioOptions = parseVideoAudioOptions(parsed, mediaKind);
7405
7484
  const videoOptions = parseVideoGenerationOptions(parsed, mediaKind);
7406
7485
  const effectiveVideoModel = videoOptions.model ?? getVideoGenerationModelForSelection();
7407
7486
  if (mediaKind === "video" && isKlingVideoGenerationModel(effectiveVideoModel) && videoFrameRefs.length > 1) throw new Error("--model kling supports at most one image reference");
7408
- if (mediaKind === "video" && isFalVideoGenerationModel(effectiveVideoModel) && videoFrameRefs.length > 0) throw new Error(`--model ${getVideoModelSelectionForModel(effectiveVideoModel) ?? effectiveVideoModel} does not support image references`);
7487
+ if (mediaKind === "video" && isFalVideoGenerationModel(effectiveVideoModel) && !isFalSeedance2VideoGenerationModel(effectiveVideoModel) && videoFrameRefs.length > 0) throw new Error(`--model ${getVideoModelSelectionForModel(effectiveVideoModel) ?? effectiveVideoModel} does not support image references`);
7409
7488
  const collectionPlacement = parseCollectionPlacementOptions(parsed);
7410
7489
  const followCommandOptions = {
7411
7490
  ...followOptions,
7412
7491
  ...collectionPlacement
7413
7492
  };
7414
- await printPreflightEstimate(client, {
7415
- operation: "generate",
7416
- assetType,
7417
- mediaKind,
7418
- prompt,
7419
- count: 1,
7420
- model: imageOptions.model ?? audioModelOptions.model ?? videoOptions.model,
7421
- aspectRatio: imageOptions.aspectRatio ?? videoOptions.aspectRatio,
7422
- imageSize: imageOptions.imageSize,
7423
- ...musicProvider ? { musicProvider } : {},
7424
- ...videoAudioOptions,
7425
- ...videoOptions
7426
- });
7427
7493
  console.log(`Generating "${name}" in space ${ctx.spaceId}...`);
7428
7494
  const audioVoiceOptions = mediaKind === "audio" ? parseAudioVoiceOptions(parsed) : {};
7429
7495
  let startedVariantId;
@@ -7443,7 +7509,7 @@ async function executeGenerate(parsed, ctx, client, deps, mediaKind, followOptio
7443
7509
  ...videoAudioOptions,
7444
7510
  ...videoOptions,
7445
7511
  onStarted: (started) => {
7446
- startedVariantId = started.jobId;
7512
+ startedVariantId = compactStartedVariantRef(started);
7447
7513
  printFollowHint(started, ctx, outputPath, "generate", mediaKind, followCommandOptions);
7448
7514
  }
7449
7515
  });
@@ -7498,9 +7564,11 @@ function parseMusicProviderOption(parsed, mediaKind, assetType) {
7498
7564
  async function executeRefine(parsed, ctx, client, deps, mediaKind) {
7499
7565
  const prompt = getPrompt(parsed, "refine");
7500
7566
  const outputPath = getOutputPath$1(parsed);
7501
- const sourceVariantId = requireOption$1(parsed, "variant");
7567
+ const sourceVariantIdentifier = requireOption$1(parsed, "variant");
7502
7568
  const state = await requestSpaceState$1(client);
7503
- const sourceVariant = state.variants.find((variant) => variant.id === sourceVariantId);
7569
+ const variants = state.variants;
7570
+ const sourceVariantId = resolveVariantRef(sourceVariantIdentifier, state.assets, variants);
7571
+ const sourceVariant = variants.find((variant) => variant.id === sourceVariantId);
7504
7572
  if (!sourceVariant) throw new Error(`Variant not found in space sync state: ${sourceVariantId}`);
7505
7573
  const sourceAsset = state.assets.find((asset) => asset.id === sourceVariant.asset_id);
7506
7574
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
@@ -7509,19 +7577,6 @@ async function executeRefine(parsed, ctx, client, deps, mediaKind) {
7509
7577
  const videoOptions = parseVideoGenerationOptions(parsed, mediaKind);
7510
7578
  const collectionPlacement = parseCollectionPlacementOptions(parsed);
7511
7579
  const followCommandOptions = { ...collectionPlacement };
7512
- await printPreflightEstimate(client, {
7513
- operation: "refine",
7514
- assetId: sourceVariant.asset_id,
7515
- assetType: sourceAsset?.type || void 0,
7516
- mediaKind,
7517
- prompt,
7518
- count: 1,
7519
- model: imageOptions.model ?? videoOptions.model,
7520
- aspectRatio: imageOptions.aspectRatio ?? videoOptions.aspectRatio,
7521
- imageSize: imageOptions.imageSize,
7522
- ...videoAudioOptions,
7523
- ...videoOptions
7524
- });
7525
7580
  console.log(`Refining variant ${sourceVariantId}...`);
7526
7581
  let startedVariantId;
7527
7582
  let result;
@@ -7536,7 +7591,7 @@ async function executeRefine(parsed, ctx, client, deps, mediaKind) {
7536
7591
  ...videoAudioOptions,
7537
7592
  ...videoOptions,
7538
7593
  onStarted: (started) => {
7539
- startedVariantId = started.jobId;
7594
+ startedVariantId = compactStartedVariantRef(started);
7540
7595
  printFollowHint(started, ctx, outputPath, "refine", mediaKind, followCommandOptions);
7541
7596
  }
7542
7597
  });
@@ -7580,30 +7635,19 @@ async function executeDerive(parsed, ctx, client, deps, mediaKind, followOptions
7580
7635
  if (referenceRefs.length === 0) throw new Error("--refs is required");
7581
7636
  const imageOptions = parseImageGenerationOptions(parsed, mediaKind);
7582
7637
  if (mediaKind === "image") validateImageReferenceCount(imageOptions.model, referenceRefs.length);
7583
- const referenceVariantIds = await resolveReferenceVariantIds(referenceRefs, ctx, deps, (await requestSpaceState$1(client)).variants, videoFrameRefs.length > 0 ? CLI_GENERATION_MEDIA_KIND : mediaKind);
7638
+ const state = await requestSpaceState$1(client);
7639
+ const referenceVariantIds = await resolveReferenceVariantIds(referenceRefs, ctx, deps, state.variants, videoFrameRefs.length > 0 ? CLI_GENERATION_MEDIA_KIND : mediaKind, state.assets);
7584
7640
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
7585
7641
  const videoAudioOptions = parseVideoAudioOptions(parsed, mediaKind);
7586
7642
  const videoOptions = parseVideoGenerationOptions(parsed, mediaKind);
7587
7643
  const effectiveVideoModel = videoOptions.model ?? getVideoGenerationModelForSelection();
7588
7644
  if (mediaKind === "video" && isKlingVideoGenerationModel(effectiveVideoModel) && referenceRefs.length > 1) throw new Error("--model kling supports at most one image reference");
7589
- if (mediaKind === "video" && isFalVideoGenerationModel(effectiveVideoModel) && referenceRefs.length > 0) throw new Error(`--model ${getVideoModelSelectionForModel(effectiveVideoModel) ?? effectiveVideoModel} does not support ${referenceRefs.length} image references`);
7645
+ if (mediaKind === "video" && isFalVideoGenerationModel(effectiveVideoModel) && !isFalSeedance2VideoGenerationModel(effectiveVideoModel) && referenceRefs.length > 0) throw new Error(`--model ${getVideoModelSelectionForModel(effectiveVideoModel) ?? effectiveVideoModel} does not support ${referenceRefs.length} image references`);
7590
7646
  const collectionPlacement = parseCollectionPlacementOptions(parsed);
7591
7647
  const followCommandOptions = {
7592
7648
  ...followOptions,
7593
7649
  ...collectionPlacement
7594
7650
  };
7595
- await printPreflightEstimate(client, {
7596
- operation: "derive",
7597
- assetType,
7598
- mediaKind,
7599
- prompt,
7600
- count: 1,
7601
- model: imageOptions.model ?? videoOptions.model,
7602
- aspectRatio: imageOptions.aspectRatio ?? videoOptions.aspectRatio,
7603
- imageSize: imageOptions.imageSize,
7604
- ...videoAudioOptions,
7605
- ...videoOptions
7606
- });
7607
7651
  console.log(`Deriving "${name}" from ${referenceVariantIds.length} reference(s)...`);
7608
7652
  let startedVariantId;
7609
7653
  let result;
@@ -7619,7 +7663,7 @@ async function executeDerive(parsed, ctx, client, deps, mediaKind, followOptions
7619
7663
  ...videoAudioOptions,
7620
7664
  ...videoOptions,
7621
7665
  onStarted: (started) => {
7622
- startedVariantId = started.jobId;
7666
+ startedVariantId = compactStartedVariantRef(started);
7623
7667
  printFollowHint(started, ctx, outputPath, "derive", mediaKind, followCommandOptions);
7624
7668
  }
7625
7669
  });
@@ -7653,8 +7697,8 @@ async function executeDerive(parsed, ctx, client, deps, mediaKind, followOptions
7653
7697
  return result;
7654
7698
  }
7655
7699
  async function executeFollow(parsed, ctx, client, deps, command, mediaKind, followOptions = {}) {
7656
- const variantId = normalizeCliOption(parsed.options.follow);
7657
- if (!variantId) throw new Error("--follow requires a variant ID");
7700
+ const variantIdentifier = normalizeCliOption(parsed.options.follow);
7701
+ if (!variantIdentifier) throw new Error("--follow requires a variant ID");
7658
7702
  const outputPath = getOutputPath$1(parsed);
7659
7703
  const timeoutMs = parseFollowTimeoutMs(parsed, mediaKind);
7660
7704
  const collectionPlacement = parseCollectionPlacementOptions(parsed);
@@ -7662,9 +7706,11 @@ async function executeFollow(parsed, ctx, client, deps, command, mediaKind, foll
7662
7706
  ...followOptions,
7663
7707
  ...collectionPlacement
7664
7708
  };
7665
- console.log(`Following variant ${variantId} in space ${ctx.spaceId}...`);
7666
7709
  const state = await requestSpaceState$1(client);
7667
- const initialVariant = state.variants.find((variant) => variant.id === variantId);
7710
+ const variants = state.variants;
7711
+ const variantId = resolveVariantRef(variantIdentifier, state.assets, variants);
7712
+ console.log(`Following variant ${variantId} in space ${ctx.spaceId}...`);
7713
+ const initialVariant = variants.find((variant) => variant.id === variantId);
7668
7714
  if (!initialVariant) throw new Error(`Variant not found in space sync state: ${variantId}`);
7669
7715
  const initialStatus = initialVariant.status;
7670
7716
  const requestId = `follow:${variantId}`;
@@ -7812,21 +7858,10 @@ async function executeBatch(parsed, ctx, client, deps, mediaKind, saveBatchManif
7812
7858
  const audioModelOptions = parseAudioGenerationOptions(parsed, mediaKind);
7813
7859
  if (mediaKind === "image") validateImageReferenceCount(imageOptions.model, refs.length);
7814
7860
  const state = await requestSpaceState$1(client);
7815
- const referenceVariantIds = refs.length > 0 ? await resolveReferenceVariantIds(refs, ctx, deps, state.variants, mediaKind) : void 0;
7861
+ const referenceVariantIds = refs.length > 0 ? await resolveReferenceVariantIds(refs, ctx, deps, state.variants, mediaKind, state.assets) : void 0;
7816
7862
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
7817
7863
  const runId = deps.createRunId();
7818
7864
  const collectionPlacement = parseCollectionPlacementOptions(parsed);
7819
- await printPreflightEstimate(client, {
7820
- operation: "batch",
7821
- assetType,
7822
- mediaKind,
7823
- prompt,
7824
- count,
7825
- model: imageOptions.model ?? audioModelOptions.model,
7826
- aspectRatio: imageOptions.aspectRatio,
7827
- imageSize: imageOptions.imageSize,
7828
- ...musicProvider ? { musicProvider } : {}
7829
- });
7830
7865
  console.log(`Batch generating ${count} ${mediaKind === "image" ? "image" : mediaKind === "video" ? "video" : "audio file"}(s) for "${name}"...`);
7831
7866
  const audioVoiceOptions = mediaKind === "audio" ? parseAudioVoiceOptions(parsed) : {};
7832
7867
  let startedBatch;
@@ -8027,7 +8062,7 @@ function parseRefs(value) {
8027
8062
  if (refs.length === 0) throw new Error("--refs must include at least one reference");
8028
8063
  return refs;
8029
8064
  }
8030
- async function resolveReferenceVariantIds(refs, ctx, deps, variants = [], mediaKind = CLI_GENERATION_MEDIA_KIND) {
8065
+ async function resolveReferenceVariantIds(refs, ctx, deps, variants = [], mediaKind = CLI_GENERATION_MEDIA_KIND, assets = []) {
8031
8066
  const variantIds = [];
8032
8067
  for (const ref of refs) {
8033
8068
  if (await deps.fileExists(ref)) {
@@ -8070,8 +8105,9 @@ async function resolveReferenceVariantIds(refs, ctx, deps, variants = [], mediaK
8070
8105
  continue;
8071
8106
  }
8072
8107
  if (looksLikeFilePath(ref)) throw new Error(`Reference file not found: ${ref}`);
8073
- validateReferenceVariant(ref, variants, mediaKind);
8074
- variantIds.push(ref);
8108
+ const variantId = resolveVariantRef(ref, assets, variants);
8109
+ validateReferenceVariant(variantId, variants, mediaKind);
8110
+ variantIds.push(variantId);
8075
8111
  }
8076
8112
  return variantIds;
8077
8113
  }
@@ -8159,8 +8195,8 @@ function printResult(result, outputPath, ctx, manifestPath) {
8159
8195
  const variant = result.variant;
8160
8196
  if (!variant) return;
8161
8197
  console.log("\nDone.\n");
8162
- console.log(` Asset: ${variant.asset_id}`);
8163
- console.log(` Variant: ${variant.id}`);
8198
+ console.log(` Asset: ${createAssetRef("asset", variant.asset_id)}`);
8199
+ console.log(` Variant: ${createVariantRef("asset", variant.asset_id, variant.id)}`);
8164
8200
  console.log(` Media: ${variant.media_key || variant.image_key || "-"}`);
8165
8201
  if (variant.image_key && variant.media_key !== variant.image_key) console.log(` Image: ${variant.image_key}`);
8166
8202
  console.log(` Local: ${outputPath}`);
@@ -8168,8 +8204,12 @@ function printResult(result, outputPath, ctx, manifestPath) {
8168
8204
  console.log(` Web: ${ctx.baseUrl}/spaces/${ctx.spaceId}/assets/${variant.asset_id}`);
8169
8205
  }
8170
8206
  function printFollowHint(started, ctx, outputPath, command, mediaKind, options = {}) {
8171
- console.log(` Started variant: ${started.jobId}`);
8172
- console.log(` Follow: ${formatFollowCommand(started.jobId, outputPath, ctx, command, mediaKind, options)}`);
8207
+ const variantRef = compactStartedVariantRef(started);
8208
+ console.log(` Started variant: ${variantRef}`);
8209
+ console.log(` Follow: ${formatFollowCommand(variantRef, outputPath, ctx, command, mediaKind, options)}`);
8210
+ }
8211
+ function compactStartedVariantRef(started) {
8212
+ return createVariantRef(started.assetName || "asset", started.assetId || "asset", started.jobId);
8173
8213
  }
8174
8214
  function throwGenerationWaitErrorIfNeeded(input, params) {
8175
8215
  if (!isGenerationWaitTimeout(input)) throw input;
@@ -8237,7 +8277,7 @@ function formatFollowCommand(variantId, outputPath, ctx, command, mediaKind, opt
8237
8277
  ].map(shellQuote).join(" ");
8238
8278
  }
8239
8279
  function shellQuote(value) {
8240
- if (/^[A-Za-z0-9_./:=@-]+$/.test(value)) return value;
8280
+ if (/^[A-Za-z0-9_./:=@~-]+$/.test(value)) return value;
8241
8281
  return `'${value.replace(/'/g, `'\\''`)}'`;
8242
8282
  }
8243
8283
  function getPrompt(parsed, command) {
@@ -8309,16 +8349,21 @@ function parseVideoGenerationOptions(parsed, mediaKind) {
8309
8349
  if (mediaKind !== "video" && resolutionValue === void 0 && durationValue === void 0 && tierValue === void 0 && aspectValue === void 0) return {};
8310
8350
  if (mediaKind !== "video") throw new Error("--resolution, --duration, and --tier are only supported for video generation");
8311
8351
  const videoResolution = resolutionValue === void 0 ? void 0 : normalizeVideoGenerationResolution(resolutionValue);
8312
- if (resolutionValue !== void 0 && !videoResolution) throw new Error("--resolution must be 720p, 1080p, or 4k");
8352
+ if (resolutionValue !== void 0 && !videoResolution) throw new Error("--resolution must be 480p, 720p, 1080p, or 4k");
8313
8353
  const videoDurationSeconds = durationValue === void 0 ? void 0 : normalizeVideoGenerationDurationSeconds(durationValue);
8314
- if (durationValue !== void 0 && !videoDurationSeconds) throw new Error("--duration must be 4, 6, or 8");
8354
+ if (durationValue !== void 0 && !videoDurationSeconds) throw new Error("--duration must be an integer from 4 to 15");
8315
8355
  const videoTier = tierValue === void 0 ? void 0 : normalizeVideoGenerationTier(tierValue);
8316
8356
  if (tierValue !== void 0 && !videoTier) throw new Error("--tier must be generate, fast, or lite");
8317
8357
  const model = parseVideoModelOption(modelValue, videoTier);
8318
8358
  if (isGeminiOmniVideoGenerationModel(model) && (resolutionValue !== void 0 || durationValue !== void 0 || tierValue !== void 0)) throw new Error("--resolution, --duration, and --tier are only supported with --model veo-3.1");
8319
8359
  if ((isKlingVideoGenerationModel(model) || isFalVideoGenerationModel(model)) && tierValue !== void 0) throw new Error("--tier is only supported with --model veo-3.1");
8320
8360
  if (videoResolution && videoTier && !isVideoGenerationResolutionSupportedForTier(videoResolution, videoTier)) throw new Error("--resolution 4k is not supported with --tier lite");
8321
- if (videoResolution && (isKlingVideoGenerationModel(model) || isFalVideoGenerationModel(model)) && !isVideoGenerationResolutionSupportedForModel(model, videoResolution)) throw new Error(`--model ${getVideoModelSelectionForModel(model) ?? model} supports only --resolution 720p or 1080p`);
8361
+ if (videoResolution && (isKlingVideoGenerationModel(model) || isFalVideoGenerationModel(model)) && !isVideoGenerationResolutionSupportedForModel(model, videoResolution)) throw new Error(`--model ${getVideoModelSelectionForModel(model) ?? model} does not support --resolution ${videoResolution}`);
8362
+ if (!isFalSeedance2VideoGenerationModel(model) && videoDurationSeconds && ![
8363
+ 4,
8364
+ 6,
8365
+ 8
8366
+ ].includes(videoDurationSeconds)) throw new Error("--duration must be 4, 6, or 8 for this model");
8322
8367
  const aspectRatio = aspectValue === void 0 ? void 0 : normalizeVideoGenerationAspectRatio(aspectValue);
8323
8368
  if (aspectValue !== void 0 && !aspectRatio) throw new Error("--aspect must be 16:9 or 9:16");
8324
8369
  return {
@@ -8346,7 +8391,8 @@ function validateVideoFrameReferenceOptions(command, parsed, mediaKind) {
8346
8391
  const tierValue = readOptionalOption(parsed, "tier", "tier") ?? readOptionalOption(parsed, "video-tier", "videoTier");
8347
8392
  const videoTier = tierValue === void 0 ? void 0 : normalizeVideoGenerationTier(tierValue);
8348
8393
  const model = parseVideoModelOption(readOptionalOption(parsed, "model", "model"), videoTier);
8349
- if (isFalVideoGenerationModel(model) || isGeminiOmniVideoGenerationModel(model) || isKlingVideoGenerationModel(model) && Boolean(lastFrame)) throw new Error("--first-frame and --last-frame are only supported with --model veo-3.1");
8394
+ if (isFalSeedance2VideoGenerationModel(model) && !firstFrame) throw new Error("--model seedance-2 requires --first-frame");
8395
+ if (!isFalSeedance2VideoGenerationModel(model) && (isFalVideoGenerationModel(model) || isGeminiOmniVideoGenerationModel(model) || isKlingVideoGenerationModel(model) && Boolean(lastFrame))) throw new Error("--first-frame and --last-frame are only supported with --model veo-3.1 or seedance-2");
8350
8396
  }
8351
8397
  function readVideoFrameOption(parsed, kebabName, camelName) {
8352
8398
  const value = parsed.options[kebabName] ?? parsed.options[camelName];
@@ -8669,8 +8715,8 @@ function parseVideoCommand(value) {
8669
8715
  }
8670
8716
  function printUsage$7() {
8671
8717
  const aspectValues = VIDEO_GENERATION_ASPECT_RATIOS.join("|");
8672
- const resolutionValues = Array.from(new Set(VIDEO_GENERATION_TIERS.flatMap((tier) => getVideoGenerationResolutionsForTier(tier)))).join("|");
8673
- const durationValues = VIDEO_GENERATION_DURATION_SECONDS.join("|");
8718
+ const resolutionValues = Array.from(new Set(["480p", ...VIDEO_GENERATION_TIERS.flatMap((tier) => getVideoGenerationResolutionsForTier(tier))])).join("|");
8719
+ const durationValues = Array.from(new Set([...VIDEO_GENERATION_DURATION_SECONDS, ...SEEDANCE_2_DURATION_SECONDS])).join("|");
8674
8720
  const tierValues = VIDEO_GENERATION_TIERS.join("|");
8675
8721
  const modelValues = (operation) => VIDEO_MODEL_SELECTIONS.filter((model) => VIDEO_MODEL_SUPPORTED_OPERATIONS[model].includes(operation)).join("|");
8676
8722
  const resolutionByTier = VIDEO_GENERATION_TIERS.map((tier) => `${tier}: ${getVideoGenerationResolutionsForTier(tier).join(", ")}`).join("; ");
@@ -8681,21 +8727,22 @@ Usage:
8681
8727
  makefx video refine --variant <variant_id> "prompt" -o <file> [--model ${modelValues("refine")}] [--aspect ${aspectValues}] [--resolution ${resolutionValues}] [--duration ${durationValues}] [--tier ${tierValues}] [--audio] [--space <id>]
8682
8728
  makefx video refine --follow <variant_id> -o <file> [--space <id>]
8683
8729
  makefx video derive --refs <variant_or_file,variant_or_file> --name <name> --type <type> "prompt" -o <file> [--model ${modelValues("derive")}] [--aspect ${aspectValues}] [--resolution ${resolutionValues}] [--duration ${durationValues}] [--tier ${tierValues}] [--audio] [--space <id>]
8684
- 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 ${aspectValues}] [--resolution ${resolutionValues}] [--duration ${durationValues}] [--tier ${tierValues}] [--audio] [--space <id>]
8730
+ makefx video derive --first-frame <variant_or_file> [--last-frame <variant_or_file>] --name <name> --type <type> "prompt" -o <file> [--model veo-3.1|seedance-2|seedance-2-fast] [--aspect ${aspectValues}] [--resolution ${resolutionValues}] [--duration ${durationValues}] [--tier ${tierValues}] [--audio] [--space <id>]
8685
8731
  makefx video derive --follow <variant_id> -o <file> [--space <id>]
8686
8732
 
8687
8733
  Video:
8688
8734
  --model <value> Generate: ${modelValues("generate")}; refine/derive: ${modelValues("refine")}
8689
8735
  --aspect <ratio> Video aspect ratio: ${aspectValues}
8690
- --resolution <value> Veo: ${resolutionValues}; Kling/fal Seedance: 720p|1080p (${resolutionByTier})
8691
- --duration <seconds> Veo/Kling/fal Seedance output duration: ${durationValues}
8736
+ --resolution <value> Supported: ${resolutionValues}; Veo: 720p|1080p|4k; Kling: 720p|1080p; Seedance 2: standard 480p|720p|1080p, fast 480p|720p (${resolutionByTier})
8737
+ --duration <seconds> Veo/Kling: ${VIDEO_GENERATION_DURATION_SECONDS.join("|")}; Seedance 2: ${SEEDANCE_2_DURATION_SECONDS.join("|")}
8692
8738
  --tier <tier> Veo-only model tier: ${tierValues}
8693
- --first-frame <ref> Veo start frame image variant or local image path
8694
- --last-frame <ref> Veo final frame image variant or local image path
8739
+ --first-frame <ref> Veo or Seedance 2 start frame image variant or local image path
8740
+ --last-frame <ref> Veo or Seedance 2 final frame image variant or local image path
8695
8741
 
8696
- Veo 3.1 frames:
8742
+ Frame inputs:
8697
8743
  --first-frame resolves to the first referenceVariantId and becomes the Veo top-level image input.
8698
- --last-frame resolves to the second referenceVariantId and becomes Veo config.lastFrame.
8744
+ --last-frame resolves to the second referenceVariantId and becomes Veo config.lastFrame or Seedance 2 end_image_url.
8745
+ Seedance 2 requires --first-frame and supports an optional --last-frame.
8699
8746
  Use both flags together for first/last-frame generation; --last-frame requires --first-frame.
8700
8747
  Do not combine frame flags with --refs. Use --refs when you want generic Veo reference images instead of first/last-frame inputs.
8701
8748
 
@@ -8944,10 +8991,10 @@ async function executeAssets(parsed, deps = defaultDeps$5) {
8944
8991
  };
8945
8992
  }
8946
8993
  if (subcommand === "show") {
8947
- const assetId = parsed.positionals[1];
8948
- if (!assetId) throw new Error("Asset ID is required: makefx assets show <asset-id>");
8949
- const details = await getAssetDetails(ctx, deps, assetId);
8950
- if (parsed.options.json === "true") deps.print(JSON.stringify(details, null, 2));
8994
+ const identifier = parsed.positionals[1];
8995
+ if (!identifier) throw new Error("Asset ID is required: makefx assets show <asset-id>");
8996
+ const details = await getAssetDetails(ctx, deps, await resolveAssetAtBoundary(ctx, deps, identifier));
8997
+ if (parsed.options.json === "true") deps.print(JSON.stringify(toAssetDetailsJson(details), null, 2));
8951
8998
  else printAssetDetails(details, ctx, deps.print);
8952
8999
  return {
8953
9000
  type: "show",
@@ -8955,10 +9002,10 @@ async function executeAssets(parsed, deps = defaultDeps$5) {
8955
9002
  };
8956
9003
  }
8957
9004
  if (subcommand === "download") {
8958
- const ref = parsed.positionals[1];
8959
- if (!ref) throw new Error("Variant ID or legacy image key is required: makefx assets download <variant-id-or-legacy-image-key> -o <file>");
9005
+ const identifier = parsed.positionals[1];
9006
+ if (!identifier) throw new Error("Variant ID or legacy image key is required: makefx assets download <variant-id-or-legacy-image-key> -o <file>");
8960
9007
  const outputPath = getOutputPath(parsed);
8961
- const resolved = resolveDownloadRef(ref, ctx);
9008
+ const resolved = resolveDownloadRef(await resolveVariantAtBoundary$1(ctx, deps, identifier), ctx);
8962
9009
  let download;
8963
9010
  try {
8964
9011
  download = await deps.downloadFile({
@@ -8989,8 +9036,9 @@ async function executeAssets(parsed, deps = defaultDeps$5) {
8989
9036
  };
8990
9037
  }
8991
9038
  if (subcommand === "delete") {
8992
- const assetId = parsed.positionals[1];
8993
- if (!assetId) throw new Error("Asset ID is required: makefx assets delete <asset-id>");
9039
+ const identifier = parsed.positionals[1];
9040
+ if (!identifier) throw new Error("Asset ID is required: makefx assets delete <asset-id>");
9041
+ const assetId = await resolveAssetAtBoundary(ctx, deps, identifier);
8994
9042
  await withAssetClient(ctx, deps, (client) => client.deleteAsset(assetId));
8995
9043
  deps.print(`Deleted asset ${assetId}`);
8996
9044
  return {
@@ -8999,9 +9047,10 @@ async function executeAssets(parsed, deps = defaultDeps$5) {
8999
9047
  };
9000
9048
  }
9001
9049
  if (subcommand === "rename") {
9002
- const assetId = parsed.positionals[1];
9050
+ const identifier = parsed.positionals[1];
9003
9051
  const name = parsed.positionals[2];
9004
- if (!assetId || !name) throw new Error("Usage: makefx assets rename <asset-id> \"<new-name>\"");
9052
+ if (!identifier || !name) throw new Error("Usage: makefx assets rename <asset-id> \"<new-name>\"");
9053
+ const assetId = await resolveAssetAtBoundary(ctx, deps, identifier);
9005
9054
  const asset = await withAssetClient(ctx, deps, (client) => client.renameAsset(assetId, name));
9006
9055
  deps.print(`Renamed asset ${assetId} -> "${asset.name}"`);
9007
9056
  return {
@@ -9010,10 +9059,11 @@ async function executeAssets(parsed, deps = defaultDeps$5) {
9010
9059
  };
9011
9060
  }
9012
9061
  if (subcommand === "set-active") {
9013
- const assetId = parsed.positionals[1];
9014
- const variantId = parsed.positionals[2] || normalizeOption$1(parsed.options.variant);
9015
- if (!assetId || !variantId) throw new Error("Usage: makefx assets set-active <asset-id> <variant-id>");
9016
- const asset = await setActiveVariant(ctx, deps, assetId, variantId);
9062
+ const assetIdentifier = parsed.positionals[1];
9063
+ const variantIdentifier = parsed.positionals[2] || normalizeOption$1(parsed.options.variant);
9064
+ if (!assetIdentifier || !variantIdentifier) throw new Error("Usage: makefx assets set-active <asset-id> <variant-id>");
9065
+ const assetId = await resolveAssetAtBoundary(ctx, deps, assetIdentifier);
9066
+ const asset = await setActiveVariant(ctx, deps, assetId, await resolveVariantAtBoundary$1(ctx, deps, variantIdentifier));
9017
9067
  deps.print(`Set active variant of asset ${assetId} to ${asset.active_variant_id}`);
9018
9068
  return {
9019
9069
  type: "set-active",
@@ -9043,6 +9093,15 @@ async function buildContext$5(parsed, deps) {
9043
9093
  projectRoot: projectConfig?.projectRoot
9044
9094
  };
9045
9095
  }
9096
+ function createReferenceClient(ctx, deps) {
9097
+ return () => deps.createMutationClient(ctx.env, ctx.spaceId);
9098
+ }
9099
+ function resolveAssetAtBoundary(ctx, deps, identifier) {
9100
+ return resolveCliAssetIdentifier(identifier, createReferenceClient(ctx, deps));
9101
+ }
9102
+ function resolveVariantAtBoundary$1(ctx, deps, identifier) {
9103
+ return resolveCliVariantIdentifier(identifier, createReferenceClient(ctx, deps));
9104
+ }
9046
9105
  async function buildAuthContext(parsed, deps) {
9047
9106
  const env = resolveCommandEnvironment(parsed, await deps.loadProjectConfig());
9048
9107
  const config = await deps.loadConfig(env);
@@ -9076,7 +9135,11 @@ async function searchAssets(parsed, deps) {
9076
9135
  const response = await deps.fetch(`${ctx.baseUrl}/api/assets/search?${params.toString()}`, { headers: { Authorization: `Bearer ${ctx.accessToken}` } });
9077
9136
  if (!response.ok) throw new Error(`Search failed: ${response.status} - ${await response.text()}`);
9078
9137
  const results = (await response.json()).results ?? [];
9079
- if (parsed.options.json === "true") deps.print(JSON.stringify(results, null, 2));
9138
+ if (parsed.options.json === "true") deps.print(JSON.stringify(results.map((result) => ({
9139
+ ...result,
9140
+ assetRef: createAssetRef(result.assetName, result.assetId),
9141
+ variantRef: result.activeVariantId ? createVariantRef(result.assetName, result.assetId, result.activeVariantId) : null
9142
+ })), null, 2));
9080
9143
  else printAssetSearchResults(results, deps.print);
9081
9144
  return {
9082
9145
  type: "search",
@@ -9085,10 +9148,11 @@ async function searchAssets(parsed, deps) {
9085
9148
  }
9086
9149
  async function cloneAsset(parsed, deps) {
9087
9150
  const ctx = await buildAuthContext(parsed, deps);
9088
- const sourceVariantId = parsed.positionals[1] ?? normalizeOption$1(parsed.options.variant);
9089
- if (!sourceVariantId) throw new Error("Usage: makefx assets clone <variant-id> --from-space <source-space-id> --to-space <target-space-id>");
9151
+ const sourceVariantIdentifier = parsed.positionals[1] ?? normalizeOption$1(parsed.options.variant);
9152
+ if (!sourceVariantIdentifier) throw new Error("Usage: makefx assets clone <variant-id> --from-space <source-space-id> --to-space <target-space-id>");
9090
9153
  const sourceSpaceId = requireOption(parsed, ["from-space", "source-space"], "Usage: makefx assets clone <variant-id> --from-space <source-space-id> --to-space <target-space-id>");
9091
9154
  const targetSpaceId = requireOption(parsed, ["to-space", "target-space"], "Usage: makefx assets clone <variant-id> --from-space <source-space-id> --to-space <target-space-id>");
9155
+ const sourceVariantId = await resolveCliVariantIdentifier(sourceVariantIdentifier, () => deps.createMutationClient(ctx.env, sourceSpaceId));
9092
9156
  const body = {
9093
9157
  sourceSpaceId,
9094
9158
  sourceVariantId
@@ -9107,14 +9171,20 @@ async function cloneAsset(parsed, deps) {
9107
9171
  });
9108
9172
  if (!response.ok) throw new Error(`Clone failed: ${response.status} - ${await response.text()}`);
9109
9173
  const payload = await response.json();
9110
- if (parsed.options.json === "true") deps.print(JSON.stringify(payload, null, 2));
9174
+ const clonedAsset = payload.asset;
9175
+ const clonedVariantPayload = payload.variant;
9176
+ const clonedVariant = clonedVariantPayload?.variant ?? clonedVariantPayload;
9177
+ const clonedAssetRef = clonedAsset?.id ? createAssetRef(clonedAsset.name ?? "asset", clonedAsset.id) : void 0;
9178
+ const clonedVariantRef = clonedAsset?.id && clonedVariant?.id ? createVariantRef(clonedAsset.name ?? "asset", clonedAsset.id, clonedVariant.id) : void 0;
9179
+ if (parsed.options.json === "true") deps.print(JSON.stringify({
9180
+ ...payload,
9181
+ assetRef: clonedAssetRef,
9182
+ variantRef: clonedVariantRef
9183
+ }, null, 2));
9111
9184
  else {
9112
- const asset = payload.asset;
9113
- const variantPayload = payload.variant;
9114
- const variant = variantPayload?.variant ?? variantPayload;
9115
9185
  deps.print(`Cloned variant ${sourceVariantId} into space ${targetSpaceId}`);
9116
- if (asset?.id) deps.print(` Asset: ${asset.id}${asset.name ? ` (${asset.name})` : ""}`);
9117
- if (variant?.id) deps.print(` Variant: ${variant.id}`);
9186
+ if (clonedAssetRef) deps.print(` Asset: ${clonedAssetRef}`);
9187
+ if (clonedVariantRef) deps.print(` Variant: ${clonedVariantRef}`);
9118
9188
  }
9119
9189
  return {
9120
9190
  type: "clone",
@@ -9292,9 +9362,13 @@ function printAssetList(assets, print) {
9292
9362
  return;
9293
9363
  }
9294
9364
  print(`Found ${assets.length} asset(s):\n`);
9295
- print("Updated".padEnd(21) + "Type".padEnd(14) + "Media".padEnd(9) + "Active Variant".padEnd(26) + "Asset".padEnd(28) + "Name");
9296
- print("-".repeat(113));
9297
- for (const asset of assets) print(formatTimestamp(asset.updated_at).padEnd(21) + truncate(asset.type || "unknown", 12).padEnd(14) + truncate(asset.media_kind || "-", 7).padEnd(9) + truncate(asset.active_variant_id || "-", 24).padEnd(26) + truncate(asset.id, 26).padEnd(28) + truncate(asset.name, 32));
9365
+ print("Updated".padEnd(21) + "Type".padEnd(14) + "Media".padEnd(9) + "Active Variant Reference".padEnd(54) + "Asset Reference".padEnd(54) + "Name");
9366
+ print("-".repeat(167));
9367
+ for (const asset of assets) {
9368
+ const assetRef = createAssetRef(asset.name, asset.id);
9369
+ const activeVariantRef = asset.active_variant_id ? createVariantRef(asset.name, asset.id, asset.active_variant_id) : "-";
9370
+ print(formatTimestamp(asset.updated_at).padEnd(21) + truncate(asset.type || "unknown", 12).padEnd(14) + truncate(asset.media_kind || "-", 7).padEnd(9) + activeVariantRef.padEnd(54) + assetRef.padEnd(54) + truncate(asset.name, 32));
9371
+ }
9298
9372
  }
9299
9373
  function printAssetSearchResults(results, print) {
9300
9374
  if (results.length === 0) {
@@ -9302,16 +9376,17 @@ function printAssetSearchResults(results, print) {
9302
9376
  return;
9303
9377
  }
9304
9378
  for (const result of results) {
9305
- print(`${result.assetName} (${result.assetId})`);
9379
+ print(`${createAssetRef(result.assetName, result.assetId)} — ${result.assetName}`);
9306
9380
  print(` Space: ${result.spaceName} (${result.spaceId})`);
9307
9381
  print(` Type: ${result.assetType ?? "-"} Media: ${result.mediaKind}`);
9308
- print(` Active: ${result.activeVariantId ?? "-"}${result.activeVariantStatus ? ` (${result.activeVariantStatus})` : ""}`);
9382
+ print(` Active: ${result.activeVariantId ? createVariantRef(result.assetName, result.assetId, result.activeVariantId) : "-"}${result.activeVariantStatus ? ` (${result.activeVariantStatus})` : ""}`);
9309
9383
  print(` Counts: variants=${result.variantCount} completed=${result.completedVariantCount} starred=${result.starredVariantCount}`);
9310
9384
  }
9311
9385
  }
9312
9386
  function printAssetDetails(details, ctx, print) {
9313
9387
  const { asset, variants, lineage } = details;
9314
- print(`\nAsset ${asset.id}\n`);
9388
+ print(`\nAsset ${createAssetRef(asset.name, asset.id)}\n`);
9389
+ print(` ID: ${asset.id}`);
9315
9390
  print(` Name: ${asset.name}`);
9316
9391
  print(` Type: ${asset.type || "unknown"}`);
9317
9392
  print(` Media: ${asset.media_kind || "-"}`);
@@ -9322,7 +9397,8 @@ function printAssetDetails(details, ctx, print) {
9322
9397
  if (variants.length > 0) {
9323
9398
  print("\nVariants:");
9324
9399
  for (const variant of [...variants].sort((a, b) => (b.updated_at || 0) - (a.updated_at || 0))) {
9325
- print(` ${variant.id === asset.active_variant_id ? "*" : " "} ${variant.id}`);
9400
+ print(` ${variant.id === asset.active_variant_id ? "*" : " "} ${createVariantRef(asset.name, asset.id, variant.id)}`);
9401
+ print(` ID: ${variant.id}`);
9326
9402
  print(` Status: ${variant.status}`);
9327
9403
  print(` Media: ${variant.media_kind || "-"}`);
9328
9404
  print(` File: ${variant.media_key || variant.image_key || "-"}`);
@@ -9365,7 +9441,21 @@ function toAssetJson(asset) {
9365
9441
  media_kind: asset.media_kind || null,
9366
9442
  activeVariantId: asset.active_variant_id,
9367
9443
  createdAt: asset.created_at || null,
9368
- updatedAt: asset.updated_at || null
9444
+ updatedAt: asset.updated_at || null,
9445
+ assetRef: createAssetRef(asset.name, asset.id)
9446
+ };
9447
+ }
9448
+ function toAssetDetailsJson(details) {
9449
+ return {
9450
+ ...details,
9451
+ asset: {
9452
+ ...details.asset,
9453
+ assetRef: createAssetRef(details.asset.name, details.asset.id)
9454
+ },
9455
+ variants: details.variants.map((variant) => ({
9456
+ ...variant,
9457
+ variantRef: createVariantRef(details.asset.name, details.asset.id, variant.id)
9458
+ }))
9369
9459
  };
9370
9460
  }
9371
9461
  function formatMetadataSummary(value, preferredKeys) {
@@ -9432,7 +9522,7 @@ async function executeVariants(parsed, deps = defaultDeps$4) {
9432
9522
  const ctx = await buildContext$4(parsed, deps);
9433
9523
  const subcommand = parsed.positionals[0];
9434
9524
  if (subcommand === "delete") {
9435
- const variantId = requireVariantId(parsed, "delete");
9525
+ const variantId = await resolveVariantAtBoundary(ctx, deps, requireVariantId(parsed, "delete"));
9436
9526
  await withVariantClient(ctx, deps, (client) => client.deleteVariant(variantId));
9437
9527
  deps.print(`Deleted variant ${variantId}`);
9438
9528
  return {
@@ -9441,9 +9531,11 @@ async function executeVariants(parsed, deps = defaultDeps$4) {
9441
9531
  };
9442
9532
  }
9443
9533
  if (subcommand === "retry") {
9444
- const variantId = requireVariantId(parsed, "retry");
9534
+ const variantId = await resolveVariantAtBoundary(ctx, deps, requireVariantId(parsed, "retry"));
9445
9535
  const variant = await withVariantClient(ctx, deps, (client) => client.retryVariant(variantId));
9446
- deps.print(`Re-queued variant ${variantId} (status: ${variant.status}). Watch progress with: assets show ${variant.asset_id}`);
9536
+ const variantRef = createVariantRef("asset", variant.asset_id, variant.id);
9537
+ const assetRef = createAssetRef("asset", variant.asset_id);
9538
+ deps.print(`Re-queued variant ${variantRef} (status: ${variant.status}). Watch progress with: assets show ${assetRef}`);
9447
9539
  return {
9448
9540
  type: "retry",
9449
9541
  variant
@@ -9451,7 +9543,7 @@ async function executeVariants(parsed, deps = defaultDeps$4) {
9451
9543
  }
9452
9544
  if (subcommand === "star" || subcommand === "unstar") {
9453
9545
  const starred = subcommand === "star";
9454
- const variantId = requireVariantId(parsed, subcommand);
9546
+ const variantId = await resolveVariantAtBoundary(ctx, deps, requireVariantId(parsed, subcommand));
9455
9547
  const variant = await withVariantClient(ctx, deps, (client) => client.starVariant(variantId, starred));
9456
9548
  deps.print(`${starred ? "Starred" : "Unstarred"} variant ${variantId}`);
9457
9549
  return {
@@ -9460,9 +9552,10 @@ async function executeVariants(parsed, deps = defaultDeps$4) {
9460
9552
  };
9461
9553
  }
9462
9554
  if (subcommand === "rate") {
9463
- const variantId = parsed.positionals[1];
9555
+ const variantIdentifier = parsed.positionals[1];
9464
9556
  const rating = parsed.positionals[2];
9465
- if (!variantId || rating !== "approved" && rating !== "rejected") throw new Error("Usage: makefx variants rate <variant-id> <approved|rejected>");
9557
+ if (!variantIdentifier || rating !== "approved" && rating !== "rejected") throw new Error("Usage: makefx variants rate <variant-id> <approved|rejected>");
9558
+ const variantId = await resolveVariantAtBoundary(ctx, deps, variantIdentifier);
9466
9559
  const variant = await withVariantClient(ctx, deps, (client) => client.rateVariant(variantId, rating));
9467
9560
  deps.print(`Rated variant ${variantId} as ${rating}`);
9468
9561
  return {
@@ -9491,6 +9584,9 @@ async function buildContext$4(parsed, deps) {
9491
9584
  spaceId
9492
9585
  };
9493
9586
  }
9587
+ function resolveVariantAtBoundary(ctx, deps, identifier) {
9588
+ return resolveCliVariantIdentifier(identifier, () => deps.createMutationClient(ctx.env, ctx.spaceId));
9589
+ }
9494
9590
  async function withVariantClient(ctx, deps, run) {
9495
9591
  const client = await deps.createMutationClient(ctx.env, ctx.spaceId);
9496
9592
  await client.connect();
@@ -9522,6 +9618,7 @@ var defaultDeps$3 = {
9522
9618
  loadProjectConfig,
9523
9619
  resolveBaseUrl,
9524
9620
  fetch,
9621
+ createStateClient: (env, spaceId) => WebSocketClient.create(env, spaceId),
9525
9622
  print: console.log
9526
9623
  };
9527
9624
  async function handleRecipes(parsed) {
@@ -9536,15 +9633,22 @@ async function handleRecipes(parsed) {
9536
9633
  async function executeRecipes(parsed, deps = defaultDeps$3) {
9537
9634
  const subcommand = parsed.positionals[0];
9538
9635
  if (subcommand !== "run") throw new Error(`Unknown recipes command: ${subcommand ?? "(none)"}`);
9539
- const sourceVariantId = parsed.positionals[1];
9540
- if (!sourceVariantId) throw new Error("Variant ID is required: makefx recipes run <variant-id>");
9541
- const rerun = await runRecipe(await buildContext$3(parsed, deps), deps, sourceVariantId);
9542
- if (parsed.options.json === "true") deps.print(JSON.stringify(rerun, null, 2));
9636
+ const sourceVariantIdentifier = parsed.positionals[1];
9637
+ if (!sourceVariantIdentifier) throw new Error("Variant ID is required: makefx recipes run <variant-id>");
9638
+ const ctx = await buildContext$3(parsed, deps);
9639
+ const rerun = await runRecipe(ctx, deps, await resolveCliVariantIdentifier(sourceVariantIdentifier, () => (deps.createStateClient ?? defaultDeps$3.createStateClient)(ctx.env, ctx.spaceId)));
9640
+ const assetRef = createAssetRef("asset", rerun.variant.asset_id);
9641
+ const variantRef = createVariantRef("asset", rerun.variant.asset_id, rerun.variant.id);
9642
+ if (parsed.options.json === "true") deps.print(JSON.stringify({
9643
+ ...rerun,
9644
+ assetRef,
9645
+ variantRef
9646
+ }, null, 2));
9543
9647
  else {
9544
9648
  deps.print(`Re-ran recipe from variant ${rerun.sourceVariantId}`);
9545
- deps.print(` Variant: ${rerun.variant.id} (status: ${rerun.variant.status})`);
9649
+ deps.print(` Variant: ${variantRef} (status: ${rerun.variant.status})`);
9546
9650
  deps.print(` Request: ${rerun.requestId}`);
9547
- deps.print(`Watch progress with: makefx assets show ${rerun.variant.asset_id}`);
9651
+ deps.print(`Watch progress with: makefx assets show ${assetRef}`);
9548
9652
  }
9549
9653
  return {
9550
9654
  type: "run",
@@ -9561,6 +9665,7 @@ async function buildContext$3(parsed, deps) {
9561
9665
  if (config.token.expiresAt < Date.now()) throw new Error(`Token expired for ${env} environment. Run: ${loginCommandForEnvironment(env)}`);
9562
9666
  if (env === "local") process$1.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
9563
9667
  return {
9668
+ env,
9564
9669
  spaceId,
9565
9670
  baseUrl: deps.resolveBaseUrl(env),
9566
9671
  accessToken: config.token.accessToken
@@ -9675,13 +9780,16 @@ async function executeCollections(parsed, deps = defaultDeps$2) {
9675
9780
  const collectionId = parsed.positionals[1];
9676
9781
  const assetId = parsed.positionals[2] || normalizeOption(parsed.options.asset);
9677
9782
  if (!collectionId || !assetId) throw new Error("Usage: makefx collections add-asset <collection-id> <asset-id>");
9783
+ const resolvedAssetId = await resolveCliAssetIdentifier(assetId, () => deps.createMutationClient(ctx.env, ctx.spaceId));
9784
+ const pinnedIdentifier = parseNullableOption(parsed.options.variant ?? parsed.options["pinned-variant"]);
9785
+ const pinnedVariantId = pinnedIdentifier ? await resolveCliVariantIdentifier(pinnedIdentifier, () => deps.createMutationClient(ctx.env, ctx.spaceId)) : null;
9678
9786
  const item = await withCollectionClient(ctx, deps, (client) => client.createCollectionItem({
9679
9787
  collectionId,
9680
9788
  id: normalizeOption(parsed.options.id),
9681
9789
  subjectType: "asset",
9682
- assetId,
9790
+ assetId: resolvedAssetId,
9683
9791
  role: normalizeOption(parsed.options.role),
9684
- pinnedVariantId: parseNullableOption(parsed.options.variant ?? parsed.options["pinned-variant"]),
9792
+ pinnedVariantId,
9685
9793
  sortIndex: parseOptionalInteger(parsed.options["sort-index"], "sort-index")
9686
9794
  }));
9687
9795
  printJsonOrLine(parsed, deps, item, `Added asset ${assetId} to collection ${collectionId} as item ${item.id}`);
@@ -9694,11 +9802,12 @@ async function executeCollections(parsed, deps = defaultDeps$2) {
9694
9802
  const collectionId = parsed.positionals[1];
9695
9803
  const variantId = parsed.positionals[2] || normalizeOption(parsed.options.variant);
9696
9804
  if (!collectionId || !variantId) throw new Error("Usage: makefx collections add-variant <collection-id> <variant-id>");
9805
+ const resolvedVariantId = await resolveCliVariantIdentifier(variantId, () => deps.createMutationClient(ctx.env, ctx.spaceId));
9697
9806
  const item = await withCollectionClient(ctx, deps, (client) => client.createCollectionItem({
9698
9807
  collectionId,
9699
9808
  id: normalizeOption(parsed.options.id),
9700
9809
  subjectType: "variant",
9701
- variantId,
9810
+ variantId: resolvedVariantId,
9702
9811
  role: normalizeOption(parsed.options.role),
9703
9812
  sortIndex: parseOptionalInteger(parsed.options["sort-index"], "sort-index")
9704
9813
  }));
@@ -9910,6 +10019,7 @@ function printUsageSummary(summary, print) {
9910
10019
  print(` Storage: ${formatBytes(summary.totals.storageBytes)}`);
9911
10020
  print(` Workflow runs: ${summary.totals.workflowRuns}`);
9912
10021
  print(` Delivery: ${formatBytes(summary.totals.deliveryBytes)}`);
10022
+ print(` Provider spend: $${summary.providerSpend.amountUsd.toFixed(6)} (${summary.providerSpend.entries} recorded entr${summary.providerSpend.entries === 1 ? "y" : "ies"}, ${summary.providerSpend.unpricedEntries} unpriced)`);
9913
10023
  if (summary.byMediaKind.length > 0) {
9914
10024
  print("\nBy media kind:");
9915
10025
  print("Media".padEnd(10) + "Storage".padEnd(14) + "Workflows".padEnd(12) + "Delivery".padEnd(14) + "Events");
@@ -10139,7 +10249,7 @@ Options:
10139
10249
  }
10140
10250
  //#endregion
10141
10251
  //#region src/cli/index.ts
10142
- var CLI_VERSION = "1.1.0+e479be18b9f5";
10252
+ var CLI_VERSION = "1.3.0+89c141a8883e";
10143
10253
  async function main() {
10144
10254
  const argv = process$1.argv.slice(2);
10145
10255
  if (argv[0] === "--") argv.shift();
@@ -10193,19 +10303,19 @@ function imageCapabilityHelp(operation) {
10193
10303
  --model <value> Image model: ${imageModelValues(operation)}
10194
10304
  --size <value> Output size (${sizeLines})
10195
10305
  --aspect <ratio> Aspect ratio: ${imageAspectValues()}
10196
- --refs <refs> Reference limit (${referenceLines})
10306
+ --refs <refs> Compact variant refs, raw IDs, or local image paths; limit (${referenceLines})
10197
10307
  Model operations: ${operationLines}
10198
- --follow <id> Resume/download a variant after a long generation or disconnect
10308
+ --follow <ref-or-id> Resume/download a variant after a long generation or disconnect
10199
10309
  --collection <id> Place the completed asset in a Space collection`;
10200
10310
  }
10201
10311
  function videoResolutionValues() {
10202
- return optionValues(uniqueValues(VIDEO_GENERATION_TIERS.flatMap((tier) => getVideoGenerationResolutionsForTier(tier))));
10312
+ return optionValues(uniqueValues(["480p", ...VIDEO_GENERATION_TIERS.flatMap((tier) => getVideoGenerationResolutionsForTier(tier))]));
10203
10313
  }
10204
10314
  function videoAspectValues() {
10205
10315
  return optionValues(VIDEO_GENERATION_ASPECT_RATIOS);
10206
10316
  }
10207
10317
  function videoDurationValues() {
10208
- return optionValues(VIDEO_GENERATION_DURATION_SECONDS);
10318
+ return optionValues(uniqueValues([...VIDEO_GENERATION_DURATION_SECONDS, ...SEEDANCE_2_DURATION_SECONDS]));
10209
10319
  }
10210
10320
  function videoTierValues() {
10211
10321
  return optionValues(VIDEO_GENERATION_TIERS);
@@ -10219,18 +10329,19 @@ function videoCapabilityHelp(operation) {
10219
10329
  return `Video:
10220
10330
  --model <value> Video model: ${videoModelValues(operation)}
10221
10331
  --aspect <ratio> Video aspect ratio: ${videoAspectValues()}
10222
- --resolution <value> Veo: ${videoResolutionValues()}; Kling/fal Seedance: 720p|1080p (${resolutionLines})
10223
- --duration <seconds> Veo/Kling/fal Seedance output duration: ${videoDurationValues()}
10332
+ --resolution <value> Supported: ${videoResolutionValues()}; Veo: 720p|1080p|4k; Kling: 720p|1080p; Seedance 2: standard 480p|720p|1080p, fast 480p|720p (${resolutionLines})
10333
+ --duration <seconds> Veo/Kling: ${optionValues(VIDEO_GENERATION_DURATION_SECONDS)}; Seedance 2: ${optionValues(SEEDANCE_2_DURATION_SECONDS)}
10224
10334
  --tier <tier> Veo-only model tier: ${videoTierValues()}
10225
- --first-frame <ref> Veo start frame image variant or local image path
10226
- --last-frame <ref> Veo final frame image variant or local image path
10335
+ --first-frame <ref> Veo or Seedance 2 start frame compact ref, raw ID, or local image path
10336
+ --last-frame <ref> Veo or Seedance 2 final frame compact ref, raw ID, or local image path
10227
10337
  Model operations: ${operationLines}
10228
- --follow <id> Resume/download a variant after a long generation or disconnect
10338
+ --follow <ref-or-id> Resume/download a variant after a long generation or disconnect
10229
10339
  --collection <id> Place the completed asset in a Space collection
10230
10340
 
10231
- Veo 3.1 frames:
10341
+ Frame inputs:
10232
10342
  --first-frame resolves to the first referenceVariantId and becomes the Veo top-level image input.
10233
- --last-frame resolves to the second referenceVariantId and becomes Veo config.lastFrame.
10343
+ --last-frame resolves to the second referenceVariantId and becomes Veo config.lastFrame or Seedance 2 end_image_url.
10344
+ Seedance 2 requires --first-frame and supports an optional --last-frame.
10234
10345
  Use both flags together for first/last-frame generation; --last-frame requires --first-frame.
10235
10346
  Do not combine frame flags with --refs. Use --refs when you want generic Veo reference images instead of first/last-frame inputs.`;
10236
10347
  }
@@ -10324,6 +10435,7 @@ Make Effects CLI
10324
10435
  Generation model:
10325
10436
  Assets are named things in a Space. Variants are generated or uploaded versions of one asset.
10326
10437
  References are ordinary completed variants or local files. Lineage records how a variant was made.
10438
+ Asset and variant arguments accept compact refs (asset:name~prefix[@variant|@active]) or raw IDs.
10327
10439
 
10328
10440
  Start:
10329
10441
  login Authenticate
@@ -10469,7 +10581,7 @@ Usage:
10469
10581
 
10470
10582
  Options:
10471
10583
  --space <id> Target space ID; defaults from initialized project
10472
- --asset <id> Target asset ID (upload as new variant)
10584
+ --asset <ref|id> Target asset compact ref or raw ID (upload as new variant)
10473
10585
  --name <name> New asset name (creates asset + variant)
10474
10586
  --type <type> Asset type for new assets (default: character)
10475
10587
  --media-kind <k> Optional explicit kind: image, audio, or video
@@ -10478,8 +10590,8 @@ Options:
10478
10590
  --provider <name> Provider provenance for uploaded media
10479
10591
  --provider-metadata <json> Provider metadata JSON object
10480
10592
  --generation-provenance <json> Extra provenance JSON object
10481
- --source-variant <ids> Comma-separated source variants for upload lineage
10482
- --source-variants <ids> Alias for --source-variant
10593
+ --source-variant <refs> Comma-separated compact refs or raw IDs for upload lineage
10594
+ --source-variants <refs> Alias for --source-variant
10483
10595
  --relation-type <type> Lineage type: derived, refined, or forked (default: derived)
10484
10596
  --active-variant-behavior <b> if-missing, set-active, or keep
10485
10597
  --json Print machine-readable upload output with Space IDs
@@ -10679,7 +10791,7 @@ Long runs:
10679
10791
  console.log(`
10680
10792
  Usage:
10681
10793
  makefx video derive --refs <variant_or_file,variant_or_file> --name <name> --type <type> "prompt" -o <file> [--model ${videoModelValues("derive")}] [--aspect ${videoAspectValues()}] [--resolution ${videoResolutionValues()}] [--duration ${videoDurationValues()}] [--tier ${videoTierValues()}] [--audio] [--collection <id>] [--space <id>]
10682
- 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>]
10794
+ makefx video derive --first-frame <variant_or_file> [--last-frame <variant_or_file>] --name <name> --type <type> "prompt" -o <file> [--model veo-3.1|seedance-2|seedance-2-fast] [--aspect ${videoAspectValues()}] [--resolution ${videoResolutionValues()}] [--duration ${videoDurationValues()}] [--tier ${videoTierValues()}] [--audio] [--collection <id>] [--space <id>]
10683
10795
  makefx video derive --follow <variant_id> -o <file> [--collection <id>] [--space <id>]
10684
10796
 
10685
10797
  ${videoCapabilityHelp("derive")}
@@ -10699,12 +10811,12 @@ Usage:
10699
10811
  makefx video generate --follow <variant_id> -o <file> [--collection <id>] [--space <id>]
10700
10812
  makefx video refine --variant <variant_id> "prompt" -o <file> [--model ${videoModelValues()}] [--aspect ${videoAspectValues()}] [--resolution ${videoResolutionValues()}] [--duration ${videoDurationValues()}] [--tier ${videoTierValues()}] [--audio] [--collection <id>] [--space <id>]
10701
10813
  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>]
10702
- 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>]
10814
+ makefx video derive --first-frame <variant_or_file> [--last-frame <variant_or_file>] --name <name> --type <type> "prompt" -o <file> [--model veo-3.1|seedance-2|seedance-2-fast] [--aspect ${videoAspectValues()}] [--resolution ${videoResolutionValues()}] [--duration ${videoDurationValues()}] [--tier ${videoTierValues()}] [--audio] [--collection <id>] [--space <id>]
10703
10815
 
10704
10816
  ${videoCapabilityHelp()}
10705
10817
 
10706
10818
  Audio:
10707
- Veo and Kling default to generated audio. fal Seedance is silent and generate-only.
10819
+ Veo and Kling default to generated audio. Seedance v1 is silent and generate-only; Seedance 2 is silent and supports generate and derive.
10708
10820
  Current Veo models do not support --no-audio; the CLI rejects it before creating a job.
10709
10821
 
10710
10822
  Long runs:
@@ -10738,6 +10850,8 @@ Usage:
10738
10850
  makefx variants unstar <variant-id>
10739
10851
  makefx variants rate <variant-id> <approved|rejected>
10740
10852
 
10853
+ Variant arguments accept compact refs (asset:name~prefix@variant) or raw IDs.
10854
+
10741
10855
  Options:
10742
10856
  --space <id> Target space ID; defaults from the initialized project
10743
10857
  --env <env> Environment (production|stage|local)
@@ -10752,6 +10866,8 @@ Usage:
10752
10866
 
10753
10867
  Re-runs the stored generation recipe as a new sibling variant of the same asset.
10754
10868
 
10869
+ The variant argument accepts a compact ref (asset:name~prefix@variant) or raw ID.
10870
+
10755
10871
  Options:
10756
10872
  --space <id> Target space ID; defaults from the initialized project
10757
10873
  --json Print machine-readable output
@@ -10772,6 +10888,8 @@ Usage:
10772
10888
  makefx assets delete <asset-id>
10773
10889
  makefx assets rename <asset-id> "<new-name>"
10774
10890
  makefx assets set-active <asset-id> <variant-id>
10891
+
10892
+ Asset and variant arguments accept compact refs (asset:name~prefix[@variant|@active]) or raw IDs.
10775
10893
  `);
10776
10894
  }
10777
10895
  function printCollectionsHelp() {
@@ -10787,6 +10905,8 @@ Usage:
10787
10905
  makefx collections remove <collection-id> <item-id>
10788
10906
  makefx collections reorder <collection-id> <item-id,item-id,...>
10789
10907
 
10908
+ Asset and variant arguments accept compact refs (asset:name~prefix[@variant|@active]) or raw IDs.
10909
+
10790
10910
  Options:
10791
10911
  --space <id> Target space ID; defaults from the initialized project
10792
10912
  --env <env> Environment (production|stage|local)