makefx 1.2.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 +976 -765
  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,556 +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");
5483
+ };
5484
+ function booleanFromSqlite(value) {
5485
+ return value === true || value === 1;
5585
5486
  }
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");
5591
- }
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 booleanFromSqlite(value) {
5920
- return value === true || value === 1;
5921
- }
5922
- function collectionItemMatchesInput(item, input) {
5923
- if (item.subject_type !== input.subjectType) return false;
5924
- if (input.subjectType === "asset" && item.asset_id !== input.assetId) return false;
5925
- if (input.subjectType === "variant" && item.variant_id !== input.variantId) return false;
5926
- if (input.role !== void 0 && item.role !== input.role) return false;
5927
- if (input.pinnedVariantId !== void 0 && item.pinned_variant_id !== input.pinnedVariantId) return false;
5928
- if (input.sortIndex !== void 0 && item.sort_index !== input.sortIndex) return false;
5929
- 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;
5930
5495
  }
5931
5496
  var WebSocketClient = class WebSocketClient {
5932
5497
  ws = null;
@@ -6602,176 +6167,741 @@ var WebSocketClient = class WebSocketClient {
6602
6167
  try {
6603
6168
  this.send(message);
6604
6169
  } catch (err) {
6605
- this.generateHandlers.delete(requestId);
6606
- clearTimeout(timeout);
6170
+ this.generateHandlers.delete(requestId);
6171
+ clearTimeout(timeout);
6172
+ reject(err);
6173
+ }
6174
+ });
6175
+ }
6176
+ /**
6177
+ * Send a batch generation request and wait for all variants to finish.
6178
+ */
6179
+ async sendBatchRequest(params) {
6180
+ const requestId = crypto.randomUUID();
6181
+ return new Promise((resolve, reject) => {
6182
+ const timeout = setTimeout(() => {
6183
+ const handler = this.batchHandlers.get(requestId);
6184
+ if (handler) {
6185
+ this.batchHandlers.delete(requestId);
6186
+ for (const variantId of handler.pending) this.batchVariantToRequestId.delete(variantId);
6187
+ reject(/* @__PURE__ */ new Error("Batch request timed out"));
6188
+ }
6189
+ }, 3e5);
6190
+ this.batchHandlers.set(requestId, {
6191
+ onStarted: params.onStarted,
6192
+ onResult: resolve,
6193
+ reject,
6194
+ timeout,
6195
+ pending: /* @__PURE__ */ new Set(),
6196
+ completed: [],
6197
+ failed: []
6198
+ });
6199
+ const message = {
6200
+ type: "batch:request",
6201
+ requestId,
6202
+ name: params.name,
6203
+ assetType: params.assetType,
6204
+ prompt: params.prompt,
6205
+ count: params.count,
6206
+ mode: params.mode,
6207
+ referenceVariantIds: params.referenceVariantIds,
6208
+ model: params.model,
6209
+ aspectRatio: params.aspectRatio,
6210
+ imageSize: params.imageSize,
6211
+ mediaKind: params.mediaKind,
6212
+ voiceId: params.voiceId,
6213
+ dialogueVoiceIds: params.dialogueVoiceIds,
6214
+ musicProvider: params.musicProvider
6215
+ };
6216
+ try {
6217
+ this.send(message);
6218
+ } catch (err) {
6219
+ this.batchHandlers.delete(requestId);
6220
+ clearTimeout(timeout);
6221
+ reject(err);
6222
+ }
6223
+ });
6224
+ }
6225
+ /**
6226
+ * Send a describe image request and wait for response
6227
+ */
6228
+ async sendDescribeRequest(params) {
6229
+ const requestId = crypto.randomUUID();
6230
+ return new Promise((resolve, reject) => {
6231
+ this.describeHandlers.set(requestId, {
6232
+ resolve,
6233
+ reject
6234
+ });
6235
+ const message = {
6236
+ type: "describe:request",
6237
+ requestId,
6238
+ assetId: params.assetId,
6239
+ variantId: params.variantId,
6240
+ assetName: params.assetName,
6241
+ focus: params.focus,
6242
+ question: params.question
6243
+ };
6244
+ try {
6245
+ this.send(message);
6246
+ } catch (err) {
6247
+ this.describeHandlers.delete(requestId);
6607
6248
  reject(err);
6608
6249
  }
6250
+ setTimeout(() => {
6251
+ if (this.describeHandlers.has(requestId)) {
6252
+ this.describeHandlers.delete(requestId);
6253
+ reject(/* @__PURE__ */ new Error("Describe request timed out"));
6254
+ }
6255
+ }, 6e4);
6609
6256
  });
6610
6257
  }
6611
6258
  /**
6612
- * Send a batch generation request and wait for all variants to finish.
6259
+ * Send a compare images request and wait for response
6613
6260
  */
6614
- async sendBatchRequest(params) {
6261
+ async sendCompareRequest(params) {
6615
6262
  const requestId = crypto.randomUUID();
6616
6263
  return new Promise((resolve, reject) => {
6617
- const timeout = setTimeout(() => {
6618
- const handler = this.batchHandlers.get(requestId);
6619
- if (handler) {
6620
- this.batchHandlers.delete(requestId);
6621
- for (const variantId of handler.pending) this.batchVariantToRequestId.delete(variantId);
6622
- reject(/* @__PURE__ */ new Error("Batch request timed out"));
6623
- }
6624
- }, 3e5);
6625
- this.batchHandlers.set(requestId, {
6626
- onStarted: params.onStarted,
6627
- onResult: resolve,
6628
- reject,
6629
- timeout,
6630
- pending: /* @__PURE__ */ new Set(),
6631
- completed: [],
6632
- failed: []
6264
+ this.compareHandlers.set(requestId, {
6265
+ resolve,
6266
+ reject
6633
6267
  });
6634
6268
  const message = {
6635
- type: "batch:request",
6269
+ type: "compare:request",
6636
6270
  requestId,
6637
- name: params.name,
6638
- assetType: params.assetType,
6639
- prompt: params.prompt,
6640
- count: params.count,
6641
- mode: params.mode,
6642
- referenceVariantIds: params.referenceVariantIds,
6643
- model: params.model,
6644
- aspectRatio: params.aspectRatio,
6645
- imageSize: params.imageSize,
6646
- mediaKind: params.mediaKind,
6647
- voiceId: params.voiceId,
6648
- dialogueVoiceIds: params.dialogueVoiceIds,
6649
- musicProvider: params.musicProvider
6271
+ variantIds: params.variantIds,
6272
+ aspects: params.aspects
6650
6273
  };
6651
6274
  try {
6652
6275
  this.send(message);
6653
6276
  } catch (err) {
6654
- this.batchHandlers.delete(requestId);
6655
- clearTimeout(timeout);
6277
+ this.compareHandlers.delete(requestId);
6656
6278
  reject(err);
6657
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
6658
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;
6659
6562
  }
6660
- /**
6661
- * Send a describe image request and wait for response
6662
- */
6663
- async sendDescribeRequest(params) {
6664
- const requestId = crypto.randomUUID();
6665
- return new Promise((resolve, reject) => {
6666
- this.describeHandlers.set(requestId, {
6667
- resolve,
6668
- reject
6669
- });
6670
- const message = {
6671
- type: "describe:request",
6672
- requestId,
6673
- assetId: params.assetId,
6674
- variantId: params.variantId,
6675
- assetName: params.assetName,
6676
- focus: params.focus,
6677
- question: params.question
6678
- };
6679
- try {
6680
- this.send(message);
6681
- } catch (err) {
6682
- this.describeHandlers.delete(requestId);
6683
- reject(err);
6684
- }
6685
- setTimeout(() => {
6686
- if (this.describeHandlers.has(requestId)) {
6687
- this.describeHandlers.delete(requestId);
6688
- reject(/* @__PURE__ */ new Error("Describe request timed out"));
6689
- }
6690
- }, 6e4);
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
6691
6577
  });
6578
+ } catch (error) {
6579
+ console.warn(`Warning: upload succeeded but mirror registry was not updated: ${error instanceof Error ? error.message : String(error)}`);
6692
6580
  }
6693
- /**
6694
- * Send a compare images request and wait for response
6695
- */
6696
- async sendCompareRequest(params) {
6697
- const requestId = crypto.randomUUID();
6698
- return new Promise((resolve, reject) => {
6699
- this.compareHandlers.set(requestId, {
6700
- resolve,
6701
- reject
6702
- });
6703
- const message = {
6704
- type: "compare:request",
6705
- requestId,
6706
- variantIds: params.variantIds,
6707
- aspects: params.aspects
6708
- };
6709
- try {
6710
- this.send(message);
6711
- } catch (err) {
6712
- this.compareHandlers.delete(requestId);
6713
- reject(err);
6714
- }
6715
- setTimeout(() => {
6716
- if (this.compareHandlers.has(requestId)) {
6717
- this.compareHandlers.delete(requestId);
6718
- reject(/* @__PURE__ */ new Error("Compare request timed out"));
6719
- }
6720
- }, 6e4);
6721
- });
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`);
6722
6589
  }
6723
- /**
6724
- * Request sync state (initial state load)
6725
- */
6726
- requestSync() {
6727
- this.send({ type: "sync:request" });
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;
6728
6672
  }
6729
- /**
6730
- * Approve a pending approval
6731
- */
6732
- approveApproval(approvalId) {
6733
- this.send({
6734
- type: "approval:approve",
6735
- approvalId
6736
- });
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];
6737
6691
  }
6738
- /**
6739
- * Reject a pending approval
6740
- */
6741
- rejectApproval(approvalId) {
6742
- this.send({
6743
- type: "approval:reject",
6744
- approvalId
6745
- });
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;
6746
6858
  }
6747
- /**
6748
- * Request list of pending approvals
6749
- */
6750
- listApprovals() {
6751
- 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)}`);
6752
6867
  }
6753
- /**
6754
- * Get current session state
6755
- */
6756
- getSession() {
6757
- 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}` : ""}`);
6758
6871
  }
6759
- /**
6760
- * Update session context (viewing, forge tray)
6761
- */
6762
- updateSession(data) {
6763
- this.send({
6764
- type: "session:update",
6765
- ...data
6766
- });
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)}`);
6767
6878
  }
6768
- /**
6769
- * Check if connected
6770
- */
6771
- isConnected() {
6772
- 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 "";
6773
6889
  }
6774
- };
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
+ }
6775
6905
  //#endregion
6776
6906
  //#region src/shared/videoGenerationOptions.ts
6777
6907
  var VIDEO_GENERATION_ASPECT_RATIOS = ["16:9", "9:16"];
@@ -7347,7 +7477,7 @@ async function executeGenerate(parsed, ctx, client, deps, mediaKind, followOptio
7347
7477
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
7348
7478
  const videoFrameRefs = parseVideoFrameReferenceOptions(parsed, "generate", mediaKind).refs;
7349
7479
  const state = videoFrameRefs.length > 0 ? await requestSpaceState$1(client) : void 0;
7350
- 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) : [];
7351
7481
  const imageOptions = parseImageGenerationOptions(parsed, mediaKind);
7352
7482
  const audioModelOptions = parseAudioGenerationOptions(parsed, mediaKind);
7353
7483
  const videoAudioOptions = parseVideoAudioOptions(parsed, mediaKind);
@@ -7379,7 +7509,7 @@ async function executeGenerate(parsed, ctx, client, deps, mediaKind, followOptio
7379
7509
  ...videoAudioOptions,
7380
7510
  ...videoOptions,
7381
7511
  onStarted: (started) => {
7382
- startedVariantId = started.jobId;
7512
+ startedVariantId = compactStartedVariantRef(started);
7383
7513
  printFollowHint(started, ctx, outputPath, "generate", mediaKind, followCommandOptions);
7384
7514
  }
7385
7515
  });
@@ -7434,9 +7564,11 @@ function parseMusicProviderOption(parsed, mediaKind, assetType) {
7434
7564
  async function executeRefine(parsed, ctx, client, deps, mediaKind) {
7435
7565
  const prompt = getPrompt(parsed, "refine");
7436
7566
  const outputPath = getOutputPath$1(parsed);
7437
- const sourceVariantId = requireOption$1(parsed, "variant");
7567
+ const sourceVariantIdentifier = requireOption$1(parsed, "variant");
7438
7568
  const state = await requestSpaceState$1(client);
7439
- 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);
7440
7572
  if (!sourceVariant) throw new Error(`Variant not found in space sync state: ${sourceVariantId}`);
7441
7573
  const sourceAsset = state.assets.find((asset) => asset.id === sourceVariant.asset_id);
7442
7574
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
@@ -7459,7 +7591,7 @@ async function executeRefine(parsed, ctx, client, deps, mediaKind) {
7459
7591
  ...videoAudioOptions,
7460
7592
  ...videoOptions,
7461
7593
  onStarted: (started) => {
7462
- startedVariantId = started.jobId;
7594
+ startedVariantId = compactStartedVariantRef(started);
7463
7595
  printFollowHint(started, ctx, outputPath, "refine", mediaKind, followCommandOptions);
7464
7596
  }
7465
7597
  });
@@ -7503,7 +7635,8 @@ async function executeDerive(parsed, ctx, client, deps, mediaKind, followOptions
7503
7635
  if (referenceRefs.length === 0) throw new Error("--refs is required");
7504
7636
  const imageOptions = parseImageGenerationOptions(parsed, mediaKind);
7505
7637
  if (mediaKind === "image") validateImageReferenceCount(imageOptions.model, referenceRefs.length);
7506
- 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);
7507
7640
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
7508
7641
  const videoAudioOptions = parseVideoAudioOptions(parsed, mediaKind);
7509
7642
  const videoOptions = parseVideoGenerationOptions(parsed, mediaKind);
@@ -7530,7 +7663,7 @@ async function executeDerive(parsed, ctx, client, deps, mediaKind, followOptions
7530
7663
  ...videoAudioOptions,
7531
7664
  ...videoOptions,
7532
7665
  onStarted: (started) => {
7533
- startedVariantId = started.jobId;
7666
+ startedVariantId = compactStartedVariantRef(started);
7534
7667
  printFollowHint(started, ctx, outputPath, "derive", mediaKind, followCommandOptions);
7535
7668
  }
7536
7669
  });
@@ -7564,8 +7697,8 @@ async function executeDerive(parsed, ctx, client, deps, mediaKind, followOptions
7564
7697
  return result;
7565
7698
  }
7566
7699
  async function executeFollow(parsed, ctx, client, deps, command, mediaKind, followOptions = {}) {
7567
- const variantId = normalizeCliOption(parsed.options.follow);
7568
- 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");
7569
7702
  const outputPath = getOutputPath$1(parsed);
7570
7703
  const timeoutMs = parseFollowTimeoutMs(parsed, mediaKind);
7571
7704
  const collectionPlacement = parseCollectionPlacementOptions(parsed);
@@ -7573,9 +7706,11 @@ async function executeFollow(parsed, ctx, client, deps, command, mediaKind, foll
7573
7706
  ...followOptions,
7574
7707
  ...collectionPlacement
7575
7708
  };
7576
- console.log(`Following variant ${variantId} in space ${ctx.spaceId}...`);
7577
7709
  const state = await requestSpaceState$1(client);
7578
- 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);
7579
7714
  if (!initialVariant) throw new Error(`Variant not found in space sync state: ${variantId}`);
7580
7715
  const initialStatus = initialVariant.status;
7581
7716
  const requestId = `follow:${variantId}`;
@@ -7723,7 +7858,7 @@ async function executeBatch(parsed, ctx, client, deps, mediaKind, saveBatchManif
7723
7858
  const audioModelOptions = parseAudioGenerationOptions(parsed, mediaKind);
7724
7859
  if (mediaKind === "image") validateImageReferenceCount(imageOptions.model, refs.length);
7725
7860
  const state = await requestSpaceState$1(client);
7726
- 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;
7727
7862
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
7728
7863
  const runId = deps.createRunId();
7729
7864
  const collectionPlacement = parseCollectionPlacementOptions(parsed);
@@ -7927,7 +8062,7 @@ function parseRefs(value) {
7927
8062
  if (refs.length === 0) throw new Error("--refs must include at least one reference");
7928
8063
  return refs;
7929
8064
  }
7930
- 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 = []) {
7931
8066
  const variantIds = [];
7932
8067
  for (const ref of refs) {
7933
8068
  if (await deps.fileExists(ref)) {
@@ -7970,8 +8105,9 @@ async function resolveReferenceVariantIds(refs, ctx, deps, variants = [], mediaK
7970
8105
  continue;
7971
8106
  }
7972
8107
  if (looksLikeFilePath(ref)) throw new Error(`Reference file not found: ${ref}`);
7973
- validateReferenceVariant(ref, variants, mediaKind);
7974
- variantIds.push(ref);
8108
+ const variantId = resolveVariantRef(ref, assets, variants);
8109
+ validateReferenceVariant(variantId, variants, mediaKind);
8110
+ variantIds.push(variantId);
7975
8111
  }
7976
8112
  return variantIds;
7977
8113
  }
@@ -8059,8 +8195,8 @@ function printResult(result, outputPath, ctx, manifestPath) {
8059
8195
  const variant = result.variant;
8060
8196
  if (!variant) return;
8061
8197
  console.log("\nDone.\n");
8062
- console.log(` Asset: ${variant.asset_id}`);
8063
- 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)}`);
8064
8200
  console.log(` Media: ${variant.media_key || variant.image_key || "-"}`);
8065
8201
  if (variant.image_key && variant.media_key !== variant.image_key) console.log(` Image: ${variant.image_key}`);
8066
8202
  console.log(` Local: ${outputPath}`);
@@ -8068,8 +8204,12 @@ function printResult(result, outputPath, ctx, manifestPath) {
8068
8204
  console.log(` Web: ${ctx.baseUrl}/spaces/${ctx.spaceId}/assets/${variant.asset_id}`);
8069
8205
  }
8070
8206
  function printFollowHint(started, ctx, outputPath, command, mediaKind, options = {}) {
8071
- console.log(` Started variant: ${started.jobId}`);
8072
- 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);
8073
8213
  }
8074
8214
  function throwGenerationWaitErrorIfNeeded(input, params) {
8075
8215
  if (!isGenerationWaitTimeout(input)) throw input;
@@ -8137,7 +8277,7 @@ function formatFollowCommand(variantId, outputPath, ctx, command, mediaKind, opt
8137
8277
  ].map(shellQuote).join(" ");
8138
8278
  }
8139
8279
  function shellQuote(value) {
8140
- if (/^[A-Za-z0-9_./:=@-]+$/.test(value)) return value;
8280
+ if (/^[A-Za-z0-9_./:=@~-]+$/.test(value)) return value;
8141
8281
  return `'${value.replace(/'/g, `'\\''`)}'`;
8142
8282
  }
8143
8283
  function getPrompt(parsed, command) {
@@ -8851,10 +8991,10 @@ async function executeAssets(parsed, deps = defaultDeps$5) {
8851
8991
  };
8852
8992
  }
8853
8993
  if (subcommand === "show") {
8854
- const assetId = parsed.positionals[1];
8855
- if (!assetId) throw new Error("Asset ID is required: makefx assets show <asset-id>");
8856
- const details = await getAssetDetails(ctx, deps, assetId);
8857
- 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));
8858
8998
  else printAssetDetails(details, ctx, deps.print);
8859
8999
  return {
8860
9000
  type: "show",
@@ -8862,10 +9002,10 @@ async function executeAssets(parsed, deps = defaultDeps$5) {
8862
9002
  };
8863
9003
  }
8864
9004
  if (subcommand === "download") {
8865
- const ref = parsed.positionals[1];
8866
- 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>");
8867
9007
  const outputPath = getOutputPath(parsed);
8868
- const resolved = resolveDownloadRef(ref, ctx);
9008
+ const resolved = resolveDownloadRef(await resolveVariantAtBoundary$1(ctx, deps, identifier), ctx);
8869
9009
  let download;
8870
9010
  try {
8871
9011
  download = await deps.downloadFile({
@@ -8896,8 +9036,9 @@ async function executeAssets(parsed, deps = defaultDeps$5) {
8896
9036
  };
8897
9037
  }
8898
9038
  if (subcommand === "delete") {
8899
- const assetId = parsed.positionals[1];
8900
- 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);
8901
9042
  await withAssetClient(ctx, deps, (client) => client.deleteAsset(assetId));
8902
9043
  deps.print(`Deleted asset ${assetId}`);
8903
9044
  return {
@@ -8906,9 +9047,10 @@ async function executeAssets(parsed, deps = defaultDeps$5) {
8906
9047
  };
8907
9048
  }
8908
9049
  if (subcommand === "rename") {
8909
- const assetId = parsed.positionals[1];
9050
+ const identifier = parsed.positionals[1];
8910
9051
  const name = parsed.positionals[2];
8911
- 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);
8912
9054
  const asset = await withAssetClient(ctx, deps, (client) => client.renameAsset(assetId, name));
8913
9055
  deps.print(`Renamed asset ${assetId} -> "${asset.name}"`);
8914
9056
  return {
@@ -8917,10 +9059,11 @@ async function executeAssets(parsed, deps = defaultDeps$5) {
8917
9059
  };
8918
9060
  }
8919
9061
  if (subcommand === "set-active") {
8920
- const assetId = parsed.positionals[1];
8921
- const variantId = parsed.positionals[2] || normalizeOption$1(parsed.options.variant);
8922
- if (!assetId || !variantId) throw new Error("Usage: makefx assets set-active <asset-id> <variant-id>");
8923
- 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));
8924
9067
  deps.print(`Set active variant of asset ${assetId} to ${asset.active_variant_id}`);
8925
9068
  return {
8926
9069
  type: "set-active",
@@ -8950,6 +9093,15 @@ async function buildContext$5(parsed, deps) {
8950
9093
  projectRoot: projectConfig?.projectRoot
8951
9094
  };
8952
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
+ }
8953
9105
  async function buildAuthContext(parsed, deps) {
8954
9106
  const env = resolveCommandEnvironment(parsed, await deps.loadProjectConfig());
8955
9107
  const config = await deps.loadConfig(env);
@@ -8983,7 +9135,11 @@ async function searchAssets(parsed, deps) {
8983
9135
  const response = await deps.fetch(`${ctx.baseUrl}/api/assets/search?${params.toString()}`, { headers: { Authorization: `Bearer ${ctx.accessToken}` } });
8984
9136
  if (!response.ok) throw new Error(`Search failed: ${response.status} - ${await response.text()}`);
8985
9137
  const results = (await response.json()).results ?? [];
8986
- 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));
8987
9143
  else printAssetSearchResults(results, deps.print);
8988
9144
  return {
8989
9145
  type: "search",
@@ -8992,10 +9148,11 @@ async function searchAssets(parsed, deps) {
8992
9148
  }
8993
9149
  async function cloneAsset(parsed, deps) {
8994
9150
  const ctx = await buildAuthContext(parsed, deps);
8995
- const sourceVariantId = parsed.positionals[1] ?? normalizeOption$1(parsed.options.variant);
8996
- 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>");
8997
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>");
8998
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));
8999
9156
  const body = {
9000
9157
  sourceSpaceId,
9001
9158
  sourceVariantId
@@ -9014,14 +9171,20 @@ async function cloneAsset(parsed, deps) {
9014
9171
  });
9015
9172
  if (!response.ok) throw new Error(`Clone failed: ${response.status} - ${await response.text()}`);
9016
9173
  const payload = await response.json();
9017
- 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));
9018
9184
  else {
9019
- const asset = payload.asset;
9020
- const variantPayload = payload.variant;
9021
- const variant = variantPayload?.variant ?? variantPayload;
9022
9185
  deps.print(`Cloned variant ${sourceVariantId} into space ${targetSpaceId}`);
9023
- if (asset?.id) deps.print(` Asset: ${asset.id}${asset.name ? ` (${asset.name})` : ""}`);
9024
- if (variant?.id) deps.print(` Variant: ${variant.id}`);
9186
+ if (clonedAssetRef) deps.print(` Asset: ${clonedAssetRef}`);
9187
+ if (clonedVariantRef) deps.print(` Variant: ${clonedVariantRef}`);
9025
9188
  }
9026
9189
  return {
9027
9190
  type: "clone",
@@ -9199,9 +9362,13 @@ function printAssetList(assets, print) {
9199
9362
  return;
9200
9363
  }
9201
9364
  print(`Found ${assets.length} asset(s):\n`);
9202
- print("Updated".padEnd(21) + "Type".padEnd(14) + "Media".padEnd(9) + "Active Variant".padEnd(26) + "Asset".padEnd(28) + "Name");
9203
- print("-".repeat(113));
9204
- 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
+ }
9205
9372
  }
9206
9373
  function printAssetSearchResults(results, print) {
9207
9374
  if (results.length === 0) {
@@ -9209,16 +9376,17 @@ function printAssetSearchResults(results, print) {
9209
9376
  return;
9210
9377
  }
9211
9378
  for (const result of results) {
9212
- print(`${result.assetName} (${result.assetId})`);
9379
+ print(`${createAssetRef(result.assetName, result.assetId)} — ${result.assetName}`);
9213
9380
  print(` Space: ${result.spaceName} (${result.spaceId})`);
9214
9381
  print(` Type: ${result.assetType ?? "-"} Media: ${result.mediaKind}`);
9215
- print(` Active: ${result.activeVariantId ?? "-"}${result.activeVariantStatus ? ` (${result.activeVariantStatus})` : ""}`);
9382
+ print(` Active: ${result.activeVariantId ? createVariantRef(result.assetName, result.assetId, result.activeVariantId) : "-"}${result.activeVariantStatus ? ` (${result.activeVariantStatus})` : ""}`);
9216
9383
  print(` Counts: variants=${result.variantCount} completed=${result.completedVariantCount} starred=${result.starredVariantCount}`);
9217
9384
  }
9218
9385
  }
9219
9386
  function printAssetDetails(details, ctx, print) {
9220
9387
  const { asset, variants, lineage } = details;
9221
- print(`\nAsset ${asset.id}\n`);
9388
+ print(`\nAsset ${createAssetRef(asset.name, asset.id)}\n`);
9389
+ print(` ID: ${asset.id}`);
9222
9390
  print(` Name: ${asset.name}`);
9223
9391
  print(` Type: ${asset.type || "unknown"}`);
9224
9392
  print(` Media: ${asset.media_kind || "-"}`);
@@ -9229,7 +9397,8 @@ function printAssetDetails(details, ctx, print) {
9229
9397
  if (variants.length > 0) {
9230
9398
  print("\nVariants:");
9231
9399
  for (const variant of [...variants].sort((a, b) => (b.updated_at || 0) - (a.updated_at || 0))) {
9232
- 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}`);
9233
9402
  print(` Status: ${variant.status}`);
9234
9403
  print(` Media: ${variant.media_kind || "-"}`);
9235
9404
  print(` File: ${variant.media_key || variant.image_key || "-"}`);
@@ -9272,7 +9441,21 @@ function toAssetJson(asset) {
9272
9441
  media_kind: asset.media_kind || null,
9273
9442
  activeVariantId: asset.active_variant_id,
9274
9443
  createdAt: asset.created_at || null,
9275
- 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
+ }))
9276
9459
  };
9277
9460
  }
9278
9461
  function formatMetadataSummary(value, preferredKeys) {
@@ -9339,7 +9522,7 @@ async function executeVariants(parsed, deps = defaultDeps$4) {
9339
9522
  const ctx = await buildContext$4(parsed, deps);
9340
9523
  const subcommand = parsed.positionals[0];
9341
9524
  if (subcommand === "delete") {
9342
- const variantId = requireVariantId(parsed, "delete");
9525
+ const variantId = await resolveVariantAtBoundary(ctx, deps, requireVariantId(parsed, "delete"));
9343
9526
  await withVariantClient(ctx, deps, (client) => client.deleteVariant(variantId));
9344
9527
  deps.print(`Deleted variant ${variantId}`);
9345
9528
  return {
@@ -9348,9 +9531,11 @@ async function executeVariants(parsed, deps = defaultDeps$4) {
9348
9531
  };
9349
9532
  }
9350
9533
  if (subcommand === "retry") {
9351
- const variantId = requireVariantId(parsed, "retry");
9534
+ const variantId = await resolveVariantAtBoundary(ctx, deps, requireVariantId(parsed, "retry"));
9352
9535
  const variant = await withVariantClient(ctx, deps, (client) => client.retryVariant(variantId));
9353
- 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}`);
9354
9539
  return {
9355
9540
  type: "retry",
9356
9541
  variant
@@ -9358,7 +9543,7 @@ async function executeVariants(parsed, deps = defaultDeps$4) {
9358
9543
  }
9359
9544
  if (subcommand === "star" || subcommand === "unstar") {
9360
9545
  const starred = subcommand === "star";
9361
- const variantId = requireVariantId(parsed, subcommand);
9546
+ const variantId = await resolveVariantAtBoundary(ctx, deps, requireVariantId(parsed, subcommand));
9362
9547
  const variant = await withVariantClient(ctx, deps, (client) => client.starVariant(variantId, starred));
9363
9548
  deps.print(`${starred ? "Starred" : "Unstarred"} variant ${variantId}`);
9364
9549
  return {
@@ -9367,9 +9552,10 @@ async function executeVariants(parsed, deps = defaultDeps$4) {
9367
9552
  };
9368
9553
  }
9369
9554
  if (subcommand === "rate") {
9370
- const variantId = parsed.positionals[1];
9555
+ const variantIdentifier = parsed.positionals[1];
9371
9556
  const rating = parsed.positionals[2];
9372
- 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);
9373
9559
  const variant = await withVariantClient(ctx, deps, (client) => client.rateVariant(variantId, rating));
9374
9560
  deps.print(`Rated variant ${variantId} as ${rating}`);
9375
9561
  return {
@@ -9398,6 +9584,9 @@ async function buildContext$4(parsed, deps) {
9398
9584
  spaceId
9399
9585
  };
9400
9586
  }
9587
+ function resolveVariantAtBoundary(ctx, deps, identifier) {
9588
+ return resolveCliVariantIdentifier(identifier, () => deps.createMutationClient(ctx.env, ctx.spaceId));
9589
+ }
9401
9590
  async function withVariantClient(ctx, deps, run) {
9402
9591
  const client = await deps.createMutationClient(ctx.env, ctx.spaceId);
9403
9592
  await client.connect();
@@ -9429,6 +9618,7 @@ var defaultDeps$3 = {
9429
9618
  loadProjectConfig,
9430
9619
  resolveBaseUrl,
9431
9620
  fetch,
9621
+ createStateClient: (env, spaceId) => WebSocketClient.create(env, spaceId),
9432
9622
  print: console.log
9433
9623
  };
9434
9624
  async function handleRecipes(parsed) {
@@ -9443,15 +9633,22 @@ async function handleRecipes(parsed) {
9443
9633
  async function executeRecipes(parsed, deps = defaultDeps$3) {
9444
9634
  const subcommand = parsed.positionals[0];
9445
9635
  if (subcommand !== "run") throw new Error(`Unknown recipes command: ${subcommand ?? "(none)"}`);
9446
- const sourceVariantId = parsed.positionals[1];
9447
- if (!sourceVariantId) throw new Error("Variant ID is required: makefx recipes run <variant-id>");
9448
- const rerun = await runRecipe(await buildContext$3(parsed, deps), deps, sourceVariantId);
9449
- 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));
9450
9647
  else {
9451
9648
  deps.print(`Re-ran recipe from variant ${rerun.sourceVariantId}`);
9452
- deps.print(` Variant: ${rerun.variant.id} (status: ${rerun.variant.status})`);
9649
+ deps.print(` Variant: ${variantRef} (status: ${rerun.variant.status})`);
9453
9650
  deps.print(` Request: ${rerun.requestId}`);
9454
- deps.print(`Watch progress with: makefx assets show ${rerun.variant.asset_id}`);
9651
+ deps.print(`Watch progress with: makefx assets show ${assetRef}`);
9455
9652
  }
9456
9653
  return {
9457
9654
  type: "run",
@@ -9468,6 +9665,7 @@ async function buildContext$3(parsed, deps) {
9468
9665
  if (config.token.expiresAt < Date.now()) throw new Error(`Token expired for ${env} environment. Run: ${loginCommandForEnvironment(env)}`);
9469
9666
  if (env === "local") process$1.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
9470
9667
  return {
9668
+ env,
9471
9669
  spaceId,
9472
9670
  baseUrl: deps.resolveBaseUrl(env),
9473
9671
  accessToken: config.token.accessToken
@@ -9582,13 +9780,16 @@ async function executeCollections(parsed, deps = defaultDeps$2) {
9582
9780
  const collectionId = parsed.positionals[1];
9583
9781
  const assetId = parsed.positionals[2] || normalizeOption(parsed.options.asset);
9584
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;
9585
9786
  const item = await withCollectionClient(ctx, deps, (client) => client.createCollectionItem({
9586
9787
  collectionId,
9587
9788
  id: normalizeOption(parsed.options.id),
9588
9789
  subjectType: "asset",
9589
- assetId,
9790
+ assetId: resolvedAssetId,
9590
9791
  role: normalizeOption(parsed.options.role),
9591
- pinnedVariantId: parseNullableOption(parsed.options.variant ?? parsed.options["pinned-variant"]),
9792
+ pinnedVariantId,
9592
9793
  sortIndex: parseOptionalInteger(parsed.options["sort-index"], "sort-index")
9593
9794
  }));
9594
9795
  printJsonOrLine(parsed, deps, item, `Added asset ${assetId} to collection ${collectionId} as item ${item.id}`);
@@ -9601,11 +9802,12 @@ async function executeCollections(parsed, deps = defaultDeps$2) {
9601
9802
  const collectionId = parsed.positionals[1];
9602
9803
  const variantId = parsed.positionals[2] || normalizeOption(parsed.options.variant);
9603
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));
9604
9806
  const item = await withCollectionClient(ctx, deps, (client) => client.createCollectionItem({
9605
9807
  collectionId,
9606
9808
  id: normalizeOption(parsed.options.id),
9607
9809
  subjectType: "variant",
9608
- variantId,
9810
+ variantId: resolvedVariantId,
9609
9811
  role: normalizeOption(parsed.options.role),
9610
9812
  sortIndex: parseOptionalInteger(parsed.options["sort-index"], "sort-index")
9611
9813
  }));
@@ -10047,7 +10249,7 @@ Options:
10047
10249
  }
10048
10250
  //#endregion
10049
10251
  //#region src/cli/index.ts
10050
- var CLI_VERSION = "1.2.0+10cb109b7379";
10252
+ var CLI_VERSION = "1.3.0+89c141a8883e";
10051
10253
  async function main() {
10052
10254
  const argv = process$1.argv.slice(2);
10053
10255
  if (argv[0] === "--") argv.shift();
@@ -10101,9 +10303,9 @@ function imageCapabilityHelp(operation) {
10101
10303
  --model <value> Image model: ${imageModelValues(operation)}
10102
10304
  --size <value> Output size (${sizeLines})
10103
10305
  --aspect <ratio> Aspect ratio: ${imageAspectValues()}
10104
- --refs <refs> Reference limit (${referenceLines})
10306
+ --refs <refs> Compact variant refs, raw IDs, or local image paths; limit (${referenceLines})
10105
10307
  Model operations: ${operationLines}
10106
- --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
10107
10309
  --collection <id> Place the completed asset in a Space collection`;
10108
10310
  }
10109
10311
  function videoResolutionValues() {
@@ -10130,10 +10332,10 @@ function videoCapabilityHelp(operation) {
10130
10332
  --resolution <value> Supported: ${videoResolutionValues()}; Veo: 720p|1080p|4k; Kling: 720p|1080p; Seedance 2: standard 480p|720p|1080p, fast 480p|720p (${resolutionLines})
10131
10333
  --duration <seconds> Veo/Kling: ${optionValues(VIDEO_GENERATION_DURATION_SECONDS)}; Seedance 2: ${optionValues(SEEDANCE_2_DURATION_SECONDS)}
10132
10334
  --tier <tier> Veo-only model tier: ${videoTierValues()}
10133
- --first-frame <ref> Veo or Seedance 2 start frame image variant or local image path
10134
- --last-frame <ref> Veo or Seedance 2 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
10135
10337
  Model operations: ${operationLines}
10136
- --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
10137
10339
  --collection <id> Place the completed asset in a Space collection
10138
10340
 
10139
10341
  Frame inputs:
@@ -10233,6 +10435,7 @@ Make Effects CLI
10233
10435
  Generation model:
10234
10436
  Assets are named things in a Space. Variants are generated or uploaded versions of one asset.
10235
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.
10236
10439
 
10237
10440
  Start:
10238
10441
  login Authenticate
@@ -10378,7 +10581,7 @@ Usage:
10378
10581
 
10379
10582
  Options:
10380
10583
  --space <id> Target space ID; defaults from initialized project
10381
- --asset <id> Target asset ID (upload as new variant)
10584
+ --asset <ref|id> Target asset compact ref or raw ID (upload as new variant)
10382
10585
  --name <name> New asset name (creates asset + variant)
10383
10586
  --type <type> Asset type for new assets (default: character)
10384
10587
  --media-kind <k> Optional explicit kind: image, audio, or video
@@ -10387,8 +10590,8 @@ Options:
10387
10590
  --provider <name> Provider provenance for uploaded media
10388
10591
  --provider-metadata <json> Provider metadata JSON object
10389
10592
  --generation-provenance <json> Extra provenance JSON object
10390
- --source-variant <ids> Comma-separated source variants for upload lineage
10391
- --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
10392
10595
  --relation-type <type> Lineage type: derived, refined, or forked (default: derived)
10393
10596
  --active-variant-behavior <b> if-missing, set-active, or keep
10394
10597
  --json Print machine-readable upload output with Space IDs
@@ -10647,6 +10850,8 @@ Usage:
10647
10850
  makefx variants unstar <variant-id>
10648
10851
  makefx variants rate <variant-id> <approved|rejected>
10649
10852
 
10853
+ Variant arguments accept compact refs (asset:name~prefix@variant) or raw IDs.
10854
+
10650
10855
  Options:
10651
10856
  --space <id> Target space ID; defaults from the initialized project
10652
10857
  --env <env> Environment (production|stage|local)
@@ -10661,6 +10866,8 @@ Usage:
10661
10866
 
10662
10867
  Re-runs the stored generation recipe as a new sibling variant of the same asset.
10663
10868
 
10869
+ The variant argument accepts a compact ref (asset:name~prefix@variant) or raw ID.
10870
+
10664
10871
  Options:
10665
10872
  --space <id> Target space ID; defaults from the initialized project
10666
10873
  --json Print machine-readable output
@@ -10681,6 +10888,8 @@ Usage:
10681
10888
  makefx assets delete <asset-id>
10682
10889
  makefx assets rename <asset-id> "<new-name>"
10683
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.
10684
10893
  `);
10685
10894
  }
10686
10895
  function printCollectionsHelp() {
@@ -10696,6 +10905,8 @@ Usage:
10696
10905
  makefx collections remove <collection-id> <item-id>
10697
10906
  makefx collections reorder <collection-id> <item-id,item-id,...>
10698
10907
 
10908
+ Asset and variant arguments accept compact refs (asset:name~prefix[@variant|@active]) or raw IDs.
10909
+
10699
10910
  Options:
10700
10911
  --space <id> Target space ID; defaults from the initialized project
10701
10912
  --env <env> Environment (production|stage|local)