makefx 0.2.0 → 0.2.2

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 +2 -2
  2. package/makefx.mjs +356 -881
  3. package/package.json +1 -1
package/makefx.mjs CHANGED
@@ -839,7 +839,7 @@ function validateProjectConfig(config, configPath) {
839
839
  * makefx spaces --id <space_id> Show details for a specific space
840
840
  * makefx spaces create <name> Create a new space
841
841
  */
842
- var defaultDeps$13 = {
842
+ var defaultDeps$12 = {
843
843
  loadConfig: loadStoredConfig,
844
844
  resolveBaseUrl,
845
845
  saveProjectConfig,
@@ -854,7 +854,7 @@ async function handleSpaces(parsed) {
854
854
  process$1.exitCode = 1;
855
855
  }
856
856
  }
857
- async function executeSpaces(parsed, deps = defaultDeps$13) {
857
+ async function executeSpaces(parsed, deps = defaultDeps$12) {
858
858
  const env = resolveCommandEnvironment(parsed);
859
859
  const showDetails = parsed.options.details === "true";
860
860
  const spaceId = parsed.options.id;
@@ -5056,7 +5056,28 @@ var EXT_TO_MEDIA_TYPE = {
5056
5056
  }
5057
5057
  };
5058
5058
  var ALLOWED_EXTENSIONS$1 = Object.keys(EXT_TO_MEDIA_TYPE).sort();
5059
- var defaultDeps$12 = {
5059
+ function resolveMediaType(ext, requestedMediaKind) {
5060
+ const mediaType = EXT_TO_MEDIA_TYPE[ext];
5061
+ if (!mediaType) throw new Error(`Invalid file type "${ext}". Allowed types: ${ALLOWED_EXTENSIONS$1.join(", ")}`);
5062
+ if (requestedMediaKind !== void 0 && requestedMediaKind !== "image" && requestedMediaKind !== "audio" && requestedMediaKind !== "video") throw new Error("Invalid --media-kind. Expected image, audio, or video");
5063
+ if (ext === ".webm" && requestedMediaKind === "audio") return {
5064
+ mediaKind: "audio",
5065
+ mimeType: "audio/webm"
5066
+ };
5067
+ if (requestedMediaKind && requestedMediaKind !== mediaType.mediaKind) throw new Error(`--media-kind ${requestedMediaKind} does not match ${ext} (${mediaType.mediaKind})`);
5068
+ return mediaType;
5069
+ }
5070
+ //#endregion
5071
+ //#region src/cli/commands/upload.ts
5072
+ /**
5073
+ * Upload Command - Upload one media file to create variants
5074
+ *
5075
+ * Usage:
5076
+ * makefx upload <file> --space <id> --asset <id> Upload to existing asset
5077
+ * makefx upload <file> --space <id> --name <name> Upload as new asset
5078
+ * makefx upload <file> --space <id> --name <name> --type <type>
5079
+ */
5080
+ var defaultDeps$11 = {
5060
5081
  loadConfig: loadStoredConfig,
5061
5082
  loadProjectConfig,
5062
5083
  resolveBaseUrl,
@@ -5071,11 +5092,13 @@ async function handleUpload(parsed) {
5071
5092
  await executeUpload(parsed);
5072
5093
  } catch (error) {
5073
5094
  console.error(error instanceof Error ? `Error: ${error.message}` : "Error: Upload failed");
5074
- if (error instanceof UploadUsageError) printUsage$12();
5095
+ if (error instanceof UploadUsageError) printUsage$11();
5075
5096
  process$1.exitCode = 1;
5076
5097
  }
5077
5098
  }
5078
- async function executeUpload(parsed, deps = defaultDeps$12) {
5099
+ async function executeUpload(parsed, deps = defaultDeps$11) {
5100
+ const filePath = parsed.positionals[0];
5101
+ if (!filePath) throw new UploadUsageError("File path is required");
5079
5102
  const projectConfig = await deps.loadProjectConfig();
5080
5103
  const env = resolveCommandEnvironment(parsed, projectConfig);
5081
5104
  const spaceId = resolveCommandSpace(parsed, projectConfig);
@@ -5083,36 +5106,99 @@ async function executeUpload(parsed, deps = defaultDeps$12) {
5083
5106
  const assetName = parsed.options.name;
5084
5107
  const assetType = parsed.options.type || "character";
5085
5108
  const requestedMediaKind = parsed.options["media-kind"] || parsed.options.mediaKind;
5086
- const filePath = parsed.positionals[0];
5087
- if (!filePath) throw new UploadUsageError("File path is required");
5109
+ const prompt = parsed.options.prompt;
5110
+ const model = parsed.options.model;
5111
+ const provider = parsed.options.provider;
5112
+ const rawProviderMetadata = parsed.options["provider-metadata"] ?? parsed.options.providerMetadata ?? parsed.options.provider_metadata;
5113
+ const rawGenerationProvenance = parsed.options["generation-provenance"] ?? parsed.options.generationProvenance ?? parsed.options.generation_provenance;
5114
+ const rawSourceVariantIds = parsed.options["source-variants"] ?? parsed.options.sourceVariants ?? parsed.options.sourceVariantIds ?? parsed.options["source-variant"] ?? parsed.options.sourceVariantId ?? parsed.options.parentVariantId;
5115
+ const rawRelationType = parsed.options["relation-type"] ?? parsed.options.relationType;
5116
+ const rawActiveVariantBehavior = parsed.options["active-variant-behavior"] ?? parsed.options.activeVariantBehavior;
5117
+ const rawCollectionIds = parsed.options.collection ?? parsed.options.collections;
5118
+ const rawCollectionNames = parsed.options["collection-name"] ?? parsed.options.collectionName;
5119
+ const rawCollectionRole = parsed.options["collection-role"] ?? parsed.options.collectionRole;
5120
+ const rawCollectionSubject = parsed.options["collection-subject"] ?? parsed.options.collectionSubject;
5121
+ const rawCollectionPinnedVariant = parsed.options["collection-pinned-variant"] ?? parsed.options.collectionPinnedVariant;
5122
+ const rawManualRelation = parsed.options["manual-relation"] ?? parsed.options.manualRelation;
5123
+ const rawManualRelationSubject = parsed.options["manual-relation-subject"] ?? parsed.options.manualRelationSubject;
5124
+ const rawManualRelationLabel = parsed.options["manual-relation-label"] ?? parsed.options.manualRelationLabel;
5125
+ const rawManualRelationContext = parsed.options["manual-relation-context"] ?? parsed.options.manualRelationContext;
5126
+ const rawManualRelationMetadata = parsed.options["manual-relation-metadata"] ?? parsed.options.manualRelationMetadata;
5127
+ const jsonOutput = parsed.options.json === "true";
5128
+ if (parsed.options["dry-run"] === "true" || parsed.options.dryRun === "true") throw new UploadUsageError("--dry-run is not supported for direct file upload");
5088
5129
  if (!spaceId) throw new UploadUsageError("--space is required, or run: makefx init --space <id>");
5089
5130
  if (!assetId && !assetName) throw new UploadUsageError("Either --asset or --name is required");
5131
+ const providerMetadata = parseJsonObjectOption(rawProviderMetadata, "--provider-metadata");
5132
+ const generationProvenance = parseJsonObjectOption(rawGenerationProvenance, "--generation-provenance");
5133
+ const sourceVariantIds = parseCsvOption$1(rawSourceVariantIds, "--source-variant");
5134
+ const relationType = normalizeRelationType(rawRelationType);
5135
+ const activeVariantBehavior = normalizeActiveVariantBehavior(rawActiveVariantBehavior);
5136
+ if (sourceVariantIds.length === 0 && (parsed.options["relation-type"] || parsed.options.relationType)) throw new UploadUsageError("--relation-type requires --source-variant or --source-variants");
5137
+ const collectionPlacements = parseCollectionPlacements({
5138
+ collectionIds: rawCollectionIds,
5139
+ collectionNames: rawCollectionNames,
5140
+ role: rawCollectionRole,
5141
+ subjectType: rawCollectionSubject,
5142
+ pinnedVariant: rawCollectionPinnedVariant
5143
+ });
5144
+ const manualRelations = parseManualRelations({
5145
+ relations: rawManualRelation,
5146
+ subjectType: rawManualRelationSubject,
5147
+ label: rawManualRelationLabel,
5148
+ context: rawManualRelationContext,
5149
+ metadata: rawManualRelationMetadata
5150
+ });
5090
5151
  const mediaType = resolveMediaType(path.extname(filePath).toLowerCase(), requestedMediaKind);
5091
5152
  const config = await deps.loadConfig(env);
5092
5153
  if (!config) throw new Error(`Not logged in to ${env} environment. Run: ${loginCommandForEnvironment(env)}`);
5093
5154
  if (config.token.expiresAt < Date.now()) throw new Error(`Token expired for ${env} environment. Run: ${loginCommandForEnvironment(env)}`);
5094
5155
  const baseUrl = deps.resolveBaseUrl(env);
5095
5156
  const accessToken = config.token.accessToken;
5157
+ const ctx = {
5158
+ spaceId,
5159
+ baseUrl,
5160
+ accessToken
5161
+ };
5096
5162
  if (env === "local") process$1.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
5097
5163
  try {
5098
5164
  const fileStat = await deps.stat(filePath);
5099
5165
  if (!fileStat.isFile()) throw new Error(`"${filePath}" is not a file`);
5100
5166
  if (fileStat.size > 10485760) throw new Error(`File too large (${(fileStat.size / 1024 / 1024).toFixed(2)}MB). Maximum size: 10MB`);
5167
+ await preflightUploadOrganization(ctx, deps, {
5168
+ assetId,
5169
+ sourceVariantIds,
5170
+ collectionPlacements,
5171
+ manualRelations
5172
+ });
5101
5173
  const fileBuffer = await deps.readFile(filePath);
5102
5174
  const fileName = path.basename(filePath);
5103
5175
  const formData = new FormData();
5104
5176
  const blob = new Blob([fileBuffer], { type: mediaType.mimeType });
5105
5177
  formData.append("file", blob, fileName);
5106
5178
  formData.append("mediaKind", mediaType.mediaKind);
5179
+ formData.append("activeVariantBehavior", activeVariantBehavior);
5107
5180
  if (assetId) formData.append("assetId", assetId);
5108
5181
  else {
5109
5182
  formData.append("assetName", assetName);
5110
5183
  formData.append("assetType", assetType);
5111
5184
  }
5112
- deps.print(`\nUploading "${fileName}" to space ${spaceId}...`);
5113
- deps.print(` Media kind: ${mediaType.mediaKind}`);
5114
- if (assetId) deps.print(` Target asset: ${assetId}`);
5115
- else deps.print(` Creating asset: "${assetName}" (${assetType})`);
5185
+ if (prompt) formData.append("prompt", prompt);
5186
+ if (model) formData.append("model", model);
5187
+ if (provider) formData.append("provider", provider);
5188
+ if (providerMetadata) formData.append("providerMetadata", JSON.stringify(providerMetadata));
5189
+ if (generationProvenance) formData.append("generationProvenance", JSON.stringify(generationProvenance));
5190
+ if (sourceVariantIds.length > 0) formData.append("lineage", JSON.stringify(sourceVariantIds.map((parentVariantId) => ({
5191
+ parentVariantId,
5192
+ relationType
5193
+ }))));
5194
+ if (!jsonOutput) {
5195
+ deps.print(`\nUploading "${fileName}" to space ${spaceId}...`);
5196
+ deps.print(` Media kind: ${mediaType.mediaKind}`);
5197
+ if (assetId) deps.print(` Target asset: ${assetId}`);
5198
+ else deps.print(` Creating asset: "${assetName}" (${assetType})`);
5199
+ if (sourceVariantIds.length === 1) deps.print(` Source variant: ${sourceVariantIds[0]} (${relationType})`);
5200
+ else if (sourceVariantIds.length > 1) deps.print(` Source variants: ${sourceVariantIds.join(", ")} (${relationType})`);
5201
+ }
5116
5202
  const response = await deps.fetch(`${baseUrl}/api/spaces/${spaceId}/upload`, {
5117
5203
  method: "POST",
5118
5204
  headers: { "Authorization": `Bearer ${accessToken}` },
@@ -5121,6 +5207,29 @@ async function executeUpload(parsed, deps = defaultDeps$12) {
5121
5207
  const data = await response.json();
5122
5208
  if (!response.ok) throw new Error(`Upload failed: ${"error" in data ? data.error : response.statusText}`);
5123
5209
  const upload = data;
5210
+ let organization = {
5211
+ collectionItemIds: [],
5212
+ relationIds: []
5213
+ };
5214
+ try {
5215
+ organization = await applyUploadOrganization(ctx, deps, upload, {
5216
+ collectionPlacements,
5217
+ manualRelations
5218
+ });
5219
+ } catch (error) {
5220
+ const message = error instanceof Error ? error.message : String(error);
5221
+ throw new Error(`Upload created asset ${upload.variant.asset_id} and variant ${upload.variant.id}, but organization failed: ${message}`);
5222
+ }
5223
+ const result = {
5224
+ asset: upload.asset,
5225
+ variant: upload.variant,
5226
+ lineage: upload.lineage,
5227
+ organization
5228
+ };
5229
+ if (jsonOutput) {
5230
+ deps.print(JSON.stringify(result, null, 2));
5231
+ return result;
5232
+ }
5124
5233
  deps.print("\nUpload successful!\n");
5125
5234
  if (upload.asset) {
5126
5235
  deps.print("New Asset:");
@@ -5138,6 +5247,11 @@ async function executeUpload(parsed, deps = defaultDeps$12) {
5138
5247
  if (upload.variant.image_key) deps.print(` Image: ${upload.variant.image_key}`);
5139
5248
  if (upload.variant.thumb_key) deps.print(` Thumb: ${upload.variant.thumb_key}`);
5140
5249
  if (upload.variant.media_mime_type) deps.print(` MIME: ${upload.variant.media_mime_type}`);
5250
+ if (organization.collectionItemIds.length > 0 || organization.relationIds.length > 0) {
5251
+ deps.print("\nOrganization:");
5252
+ if (organization.collectionItemIds.length > 0) deps.print(` Collection items: ${organization.collectionItemIds.join(", ")}`);
5253
+ if (organization.relationIds.length > 0) deps.print(` Relations: ${organization.relationIds.join(", ")}`);
5254
+ }
5141
5255
  if (upload.variant.recipe) try {
5142
5256
  const recipe = JSON.parse(upload.variant.recipe);
5143
5257
  deps.print("\nRecipe:");
@@ -5147,429 +5261,45 @@ async function executeUpload(parsed, deps = defaultDeps$12) {
5147
5261
  } catch {}
5148
5262
  deps.print("\nTo inspect:");
5149
5263
  deps.print(` makefx assets show ${upload.variant.asset_id} --space ${spaceId}`);
5150
- return {
5151
- asset: upload.asset,
5152
- variant: upload.variant
5153
- };
5264
+ return result;
5154
5265
  } catch (error) {
5155
5266
  if (error.code === "ENOENT") throw new Error(`File not found: ${filePath}`);
5156
5267
  throw error;
5157
5268
  }
5158
5269
  }
5159
- function resolveMediaType(ext, requestedMediaKind) {
5160
- const mediaType = EXT_TO_MEDIA_TYPE[ext];
5161
- if (!mediaType) throw new Error(`Invalid file type "${ext}". Allowed types: ${ALLOWED_EXTENSIONS$1.join(", ")}`);
5162
- if (requestedMediaKind !== void 0 && requestedMediaKind !== "image" && requestedMediaKind !== "audio" && requestedMediaKind !== "video") throw new Error("Invalid --media-kind. Expected image, audio, or video");
5163
- if (ext === ".webm" && requestedMediaKind === "audio") return {
5164
- mediaKind: "audio",
5165
- mimeType: "audio/webm"
5166
- };
5167
- if (requestedMediaKind && requestedMediaKind !== mediaType.mediaKind) throw new Error(`--media-kind ${requestedMediaKind} does not match ${ext} (${mediaType.mediaKind})`);
5168
- return mediaType;
5169
- }
5170
- function printUsage$12() {
5171
- console.log(`
5172
- Usage:
5173
- makefx upload <file> --asset <id> [--space <id>] Upload media to existing asset
5174
- makefx upload <file> --name <name> [--space <id>] Create new asset
5175
-
5176
- Options:
5177
- --space <id> Target space ID; defaults from initialized project
5178
- --asset <id> Target asset ID (upload as new variant)
5179
- --name <name> New asset name (creates asset + variant)
5180
- --type <type> Asset type for new assets (default: character)
5181
- --media-kind <k> Optional explicit kind: image, audio, or video
5182
- --env <env> Environment (production|stage|local)
5183
- --local Shortcut for --env local
5184
-
5185
- Examples:
5186
- makefx upload hero.png --space abc123 --name "Hero Character"
5187
- makefx upload theme.mp3 --space abc123 --name "Theme Music" --type audio
5188
- makefx upload cutscene.mp4 --space abc123 --name "Opening Cutscene" --type video
5189
- makefx upload variant.jpg --space abc123 --asset def456
5190
- `);
5191
- }
5192
- //#endregion
5193
- //#region src/cli/commands/import.ts
5194
- var defaultDeps$11 = {
5195
- loadConfig: loadStoredConfig,
5196
- loadProjectConfig,
5197
- resolveBaseUrl,
5198
- fetch,
5199
- readFile,
5200
- stat,
5201
- print: console.log
5202
- };
5203
- async function handleImport(parsed) {
5204
- try {
5205
- await executeImport(parsed);
5206
- } catch (error) {
5207
- console.error(error instanceof Error ? `Error: ${error.message}` : "Error: Import failed");
5208
- printUsage$11();
5209
- process$1.exitCode = 1;
5210
- }
5211
- }
5212
- async function executeImport(parsed, deps = defaultDeps$11) {
5213
- const manifestPath = parsed.positionals[0] || parsed.options.manifest;
5214
- if (!manifestPath || manifestPath === "true") throw new Error("Manifest path is required: makefx import <manifest.json>");
5215
- const ctx = await buildContext$8(parsed, deps);
5216
- const manifest = await readManifest(manifestPath, deps);
5217
- await validateLocalRecords(manifest.records, deps);
5218
- const plan = validateManifestPlan(manifest, await fetchSpaceState(ctx, deps));
5219
- const uploadOrder = buildUploadOrder(manifest.records);
5220
- if (parsed.options["dry-run"] === "true" || parsed.options.dryRun === "true") {
5221
- const result = {
5222
- dryRun: true,
5223
- records: uploadOrder.map((record) => ({
5224
- key: record.localKey,
5225
- file: record.displayPath,
5226
- target: record.assetId ?? `new:${record.assetName}`,
5227
- lineageInputs: record.lineage.length
5228
- })),
5229
- collections: plan.collections.map((collection) => ({
5230
- name: collection.name,
5231
- create: collection.create
5232
- })),
5233
- collectionItems: plan.collectionItems.length,
5234
- relations: plan.relations.length,
5235
- compositions: plan.compositions.map((composition) => ({
5236
- name: composition.name,
5237
- create: composition.create
5238
- })),
5239
- compositionItems: plan.compositionItems.length,
5240
- stylePresets: plan.stylePresets.map((preset) => ({
5241
- name: preset.name,
5242
- action: preset.action
5243
- }))
5244
- };
5245
- printResult$1(result, parsed, deps);
5246
- return result;
5247
- }
5248
- const uploadedByKey = /* @__PURE__ */ new Map();
5249
- const imported = [];
5250
- const jsonOutput = parsed.options.json === "true";
5251
- for (const record of uploadOrder) {
5252
- const lineage = record.lineage.map((input) => ({
5253
- parentVariantId: input.sourceVariantId ?? uploadedByKey.get(input.sourceFile)?.variantId,
5254
- relationType: input.relationType
5255
- }));
5256
- if (lineage.find((input) => !input.parentVariantId)) throw new Error(`Could not resolve same-batch lineage for ${record.localKey}`);
5257
- const response = await uploadRecord(ctx, deps, record, lineage, { quiet: jsonOutput });
5258
- uploadedByKey.set(record.localKey, {
5259
- assetId: response.variant.asset_id,
5260
- variantId: response.variant.id
5261
- });
5262
- imported.push({
5263
- key: record.localKey,
5264
- file: record.displayPath,
5265
- assetId: response.variant.asset_id,
5266
- variantId: response.variant.id,
5267
- lineageIds: (response.lineage ?? []).map((lineageRecord) => lineageRecord.id)
5268
- });
5269
- }
5270
- const result = {
5271
- dryRun: false,
5272
- records: imported,
5273
- ...await applyOrganizationPlan(ctx, deps, plan, uploadedByKey)
5274
- };
5275
- printResult$1(result, parsed, deps);
5276
- return result;
5277
- }
5278
- async function buildContext$8(parsed, deps) {
5279
- const projectConfig = await deps.loadProjectConfig();
5280
- const env = resolveCommandEnvironment(parsed, projectConfig);
5281
- const spaceId = resolveCommandSpace(parsed, projectConfig);
5282
- if (!spaceId) throw new Error("--space is required, or run: makefx init --space <id>");
5283
- const config = await deps.loadConfig(env);
5284
- if (!config) throw new Error(`Not logged in to ${env} environment. Run: ${loginCommandForEnvironment(env)}`);
5285
- if (config.token.expiresAt < Date.now()) throw new Error(`Token expired for ${env} environment. Run: ${loginCommandForEnvironment(env)}`);
5286
- if (env === "local") process$1.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
5287
- return {
5288
- env,
5289
- spaceId,
5290
- baseUrl: deps.resolveBaseUrl(env),
5291
- accessToken: config.token.accessToken
5292
- };
5293
- }
5294
- async function readManifest(manifestPath, deps) {
5295
- const manifestText = await deps.readFile(manifestPath, "utf8");
5296
- const manifestDir = path.dirname(path.resolve(manifestPath));
5270
+ function parseJsonObjectOption(value, optionName) {
5271
+ if (value === void 0) return void 0;
5297
5272
  let parsed;
5298
5273
  try {
5299
- parsed = JSON.parse(manifestText);
5300
- } catch (error) {
5301
- throw new Error(`Manifest is not valid JSON: ${error instanceof Error ? error.message : String(error)}`);
5302
- }
5303
- const root = Array.isArray(parsed) ? {} : parsed;
5304
- const rawRecords = Array.isArray(parsed) ? parsed : parsed && typeof parsed === "object" ? root.records ?? root.files : null;
5305
- if (!Array.isArray(rawRecords)) throw new Error("Manifest must be an array or contain a records/files array");
5306
- const records = rawRecords.map((raw, index) => normalizeRecord(raw, index, manifestDir));
5307
- const keys = /* @__PURE__ */ new Set();
5308
- for (const record of records) {
5309
- if (keys.has(record.localKey)) throw new Error(`Duplicate local key in manifest: ${record.localKey}`);
5310
- keys.add(record.localKey);
5274
+ parsed = JSON.parse(value);
5275
+ } catch {
5276
+ throw new UploadUsageError(`${optionName} must be valid JSON`);
5311
5277
  }
5312
- return {
5313
- records,
5314
- collections: normalizeCollections(readArray(root.collections, "collections"), false),
5315
- styleCollections: normalizeStyleCollections(readArray(root.styleCollections, "styleCollections")),
5316
- collectionItems: [...records.flatMap((record) => record.collectionItems), ...normalizeCollectionItems(readArray(root.collectionItems, "collectionItems"), "collectionItems")],
5317
- relations: [...records.flatMap((record) => record.relations), ...normalizeRelations(readArray(root.relations, "relations"), "relations")],
5318
- compositions: normalizeCompositions(readArray(root.compositions, "compositions")),
5319
- compositionItems: [...records.flatMap((record) => record.compositionItems), ...normalizeCompositionItems(readArray(root.compositionItems, "compositionItems"), "compositionItems")],
5320
- stylePresets: normalizeStylePresets(readArray(root.stylePresets, "stylePresets"))
5321
- };
5322
- }
5323
- function normalizeRecord(raw, index, manifestDir) {
5324
- if (!raw || typeof raw !== "object" || Array.isArray(raw)) throw new Error(`Record ${index + 1} must be an object`);
5325
- const record = raw;
5326
- const displayPath = record.file ?? record.path;
5327
- if (!displayPath) throw new Error(`Record ${index + 1} is missing file`);
5328
- const filePath = path.resolve(manifestDir, displayPath);
5329
- const mediaType = resolveMediaType(path.extname(filePath).toLowerCase(), record.mediaKind);
5330
- const assetId = record.assetId ?? record.targetAssetId;
5331
- const assetName = record.assetName ?? record.name;
5332
- if (assetId && assetName) throw new Error(`Record ${index + 1} must set either assetId or name, not both`);
5333
- if (!assetId && !assetName) throw new Error(`Record ${index + 1} must set assetId for an existing asset or name for a new asset`);
5334
- const lineage = normalizeLineage(record.lineage ?? [], index);
5335
- const activeVariantBehavior = normalizeActiveBehavior(record.activeVariantBehavior, index);
5336
- const providerMetadata = record.providerMetadata ?? record.provider_metadata;
5337
- const generationProvenance = record.generationProvenance ?? record.generation_provenance ?? {};
5338
- validateObjectField(providerMetadata, `Record ${index + 1} providerMetadata`);
5339
- validateObjectField(generationProvenance, `Record ${index + 1} generationProvenance`);
5340
- const localKey = record.localKey ?? record.key ?? displayPath;
5341
- return {
5342
- index,
5343
- localKey,
5344
- filePath,
5345
- displayPath,
5346
- fileName: path.basename(filePath),
5347
- assetName,
5348
- assetType: record.assetType ?? record.type ?? "character",
5349
- assetId,
5350
- mediaKind: mediaType.mediaKind,
5351
- mimeType: mediaType.mimeType,
5352
- activeVariantBehavior,
5353
- prompt: record.prompt,
5354
- model: record.model,
5355
- provider: record.provider,
5356
- providerMetadata,
5357
- generationProvenance,
5358
- lineage,
5359
- collectionItems: normalizeCollectionItems([...readArray(record.collections, `Record ${index + 1} collections`), ...readArray(record.collectionItems, `Record ${index + 1} collectionItems`)], `Record ${index + 1} collections`, localKey),
5360
- relations: normalizeRelations(readArray(record.relations, `Record ${index + 1} relations`), `Record ${index + 1} relations`, localKey),
5361
- compositionItems: normalizeCompositionItems(readArray(record.compositionItems, `Record ${index + 1} compositionItems`), `Record ${index + 1} compositionItems`, localKey),
5362
- styleCollections: normalizeStyleCollections(readArray(record.styleCollections, `Record ${index + 1} styleCollections`), localKey)
5363
- };
5364
- }
5365
- function readArray(value, label) {
5366
- if (value === void 0) return [];
5367
- if (!Array.isArray(value)) throw new Error(`${label} must be an array`);
5368
- return value;
5369
- }
5370
- function normalizeLineage(inputs, recordIndex) {
5371
- if (!Array.isArray(inputs)) throw new Error(`Record ${recordIndex + 1} lineage must be an array`);
5372
- return inputs.map((input, index) => {
5373
- if (!input || typeof input !== "object" || Array.isArray(input)) throw new Error(`Record ${recordIndex + 1} lineage[${index}] must be an object`);
5374
- const sourceVariantId = input.sourceVariantId ?? input.parentVariantId;
5375
- if (sourceVariantId && input.sourceFile || !sourceVariantId && !input.sourceFile) throw new Error(`Record ${recordIndex + 1} lineage[${index}] must set exactly one of sourceVariantId or sourceFile`);
5376
- if (input.relationType !== "derived" && input.relationType !== "refined" && input.relationType !== "forked") throw new Error(`Record ${recordIndex + 1} lineage[${index}] relationType must be derived, refined, or forked`);
5377
- return {
5378
- relationType: input.relationType,
5379
- ...sourceVariantId ? { sourceVariantId } : { sourceFile: input.sourceFile }
5380
- };
5381
- });
5382
- }
5383
- function normalizeCollections(inputs, styleOnly) {
5384
- return inputs.map((input, index) => {
5385
- const collection = requireObject(input, `collections[${index}]`);
5386
- const name = collection.name ?? collection.collectionName ?? collection.collection;
5387
- if (!name || typeof name !== "string") throw new Error(`collections[${index}] name is required`);
5388
- if (collection.description !== void 0 && collection.description !== null && typeof collection.description !== "string") throw new Error(`collections[${index}] description must be a string or null`);
5389
- validateSortIndex(collection.sortIndex, `collections[${index}] sortIndex`);
5390
- return {
5391
- key: collection.id ?? name,
5392
- name,
5393
- description: collection.description,
5394
- sortIndex: collection.sortIndex,
5395
- create: styleOnly || collection.create === true || collection.createIfMissing === true
5396
- };
5397
- });
5398
- }
5399
- function normalizeStyleCollections(inputs, recordKey) {
5400
- return inputs.map((input, index) => {
5401
- if (typeof input === "string") return {
5402
- key: input,
5403
- name: input,
5404
- create: false,
5405
- refs: recordKey ? [recordKey] : []
5406
- };
5407
- const collection = requireObject(input, labelFor("styleCollections", index, recordKey));
5408
- const name = collection.name ?? collection.collectionName ?? collection.collection;
5409
- if (!name || typeof name !== "string") throw new Error(`${labelFor("styleCollections", index, recordKey)} name is required`);
5410
- validateSortIndex(collection.sortIndex, `${labelFor("styleCollections", index, recordKey)} sortIndex`);
5411
- const refs = [
5412
- ...collection.refs ?? [],
5413
- ...collection.records ?? [],
5414
- ...recordKey ? [recordKey] : []
5415
- ];
5416
- if (!refs.every((ref) => typeof ref === "string" && ref.trim())) throw new Error(`${labelFor("styleCollections", index, recordKey)} refs must be strings`);
5417
- return {
5418
- key: collection.id ?? name,
5419
- name,
5420
- description: collection.description,
5421
- sortIndex: collection.sortIndex,
5422
- create: collection.create === true || collection.createIfMissing === true,
5423
- refs
5424
- };
5425
- });
5426
- }
5427
- function normalizeCollectionItems(inputs, label, defaultRecordKey) {
5428
- return inputs.map((input, index) => {
5429
- const item = requireObject(input, `${label}[${index}]`);
5430
- const collectionName = item.collectionName ?? item.collection ?? item.collectionId;
5431
- if (!collectionName || typeof collectionName !== "string") throw new Error(`${label}[${index}] collection is required`);
5432
- if (item.role !== void 0 && typeof item.role !== "string") throw new Error(`${label}[${index}] role must be a string`);
5433
- const subjectType = normalizeSubjectType(item.subjectType ?? (item.variantId ? "variant" : "asset"), `${label}[${index}] subjectType`);
5434
- const recordKey = item.recordKey ?? item.sourceFile ?? defaultRecordKey;
5435
- validateSortIndex(item.sortIndex, `${label}[${index}] sortIndex`);
5436
- if (item.pinnedVariantId !== void 0 && item.pinnedVariantId !== null && typeof item.pinnedVariantId !== "string") throw new Error(`${label}[${index}] pinnedVariantId must be a string or null`);
5437
- return {
5438
- id: item.id,
5439
- collectionName,
5440
- role: item.role ?? "member",
5441
- subjectType,
5442
- assetId: item.assetId,
5443
- variantId: item.variantId,
5444
- recordKey,
5445
- pinnedVariantId: item.pinnedVariantId,
5446
- pinnedVariantBehavior: normalizePinnedVariantBehavior(item.pinnedVariantBehavior, `${label}[${index}] pinnedVariantBehavior`),
5447
- sortIndex: item.sortIndex
5448
- };
5449
- });
5450
- }
5451
- function normalizeRelations(inputs, label, defaultSubjectRecordKey) {
5452
- return inputs.map((input, index) => {
5453
- const relation = requireObject(input, `${label}[${index}]`);
5454
- const subject = relation.subject ? normalizeSubject(relation.subject, `${label}[${index}] subject`) : normalizeSubject({
5455
- subjectType: relation.subjectType,
5456
- assetId: relation.subjectAssetId,
5457
- variantId: relation.subjectVariantId,
5458
- recordKey: defaultSubjectRecordKey
5459
- }, `${label}[${index}] subject`);
5460
- const object = normalizeSubject(relation.object ?? relation.target ?? {
5461
- subjectType: relation.objectType,
5462
- assetId: relation.objectAssetId,
5463
- variantId: relation.objectVariantId
5464
- }, `${label}[${index}] object`);
5465
- const relationType = normalizeRelationType(relation.relationType, `${label}[${index}] relationType`);
5466
- validateObjectField(relation.metadata, `${label}[${index}] metadata`);
5467
- validateSortIndex(relation.sortIndex, `${label}[${index}] sortIndex`);
5468
- if (relation.label !== void 0 && relation.label !== null && typeof relation.label !== "string") throw new Error(`${label}[${index}] label must be a string or null`);
5469
- if (relation.context !== void 0 && relation.context !== null && typeof relation.context !== "string" && (typeof relation.context !== "object" || Array.isArray(relation.context))) throw new Error(`${label}[${index}] context must be a string, object, or null`);
5470
- return {
5471
- id: relation.id,
5472
- subject,
5473
- object,
5474
- relationType,
5475
- label: relation.label,
5476
- context: relation.context,
5477
- metadata: relation.metadata,
5478
- sortIndex: relation.sortIndex
5479
- };
5480
- });
5481
- }
5482
- function normalizeCompositions(inputs) {
5483
- return inputs.map((input, index) => {
5484
- const composition = requireObject(input, `compositions[${index}]`);
5485
- const name = composition.name ?? composition.compositionName ?? composition.composition;
5486
- if (!name || typeof name !== "string") throw new Error(`compositions[${index}] name is required`);
5487
- if (composition.description !== void 0 && composition.description !== null && typeof composition.description !== "string") throw new Error(`compositions[${index}] description must be a string or null`);
5488
- validateObjectField(composition.metadata, `compositions[${index}] metadata`);
5489
- validateSortIndex(composition.sortIndex, `compositions[${index}] sortIndex`);
5490
- const output = composition.output ? normalizeSubject(composition.output, `compositions[${index}] output`) : composition.outputRecordKey || composition.outputAssetId || composition.outputVariantId ? normalizeSubject({
5491
- subjectType: composition.outputVariantId || composition.outputRecordKey ? "variant" : "asset",
5492
- assetId: composition.outputAssetId ?? void 0,
5493
- variantId: composition.outputVariantId ?? void 0,
5494
- recordKey: composition.outputRecordKey
5495
- }, `compositions[${index}] output`) : void 0;
5496
- return {
5497
- key: composition.id ?? name,
5498
- name,
5499
- description: composition.description,
5500
- status: normalizeCompositionStatus(composition.status, `compositions[${index}] status`),
5501
- output,
5502
- metadata: composition.metadata,
5503
- sortIndex: composition.sortIndex,
5504
- create: composition.create === true || composition.createIfMissing === true
5505
- };
5506
- });
5278
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new UploadUsageError(`${optionName} must be a JSON object`);
5279
+ return parsed;
5507
5280
  }
5508
- function normalizeCompositionItems(inputs, label, defaultRecordKey) {
5509
- return inputs.map((input, index) => {
5510
- const item = requireObject(input, `${label}[${index}]`);
5511
- const compositionName = item.compositionName ?? item.composition ?? item.compositionId;
5512
- if (!compositionName || typeof compositionName !== "string") throw new Error(`${label}[${index}] composition is required`);
5513
- const role = normalizeCompositionItemRole(item.role, `${label}[${index}] role`);
5514
- validateObjectField(item.metadata, `${label}[${index}] metadata`);
5515
- validateSortIndex(item.sortIndex, `${label}[${index}] sortIndex`);
5516
- if (item.label !== void 0 && item.label !== null && typeof item.label !== "string") throw new Error(`${label}[${index}] label must be a string or null`);
5517
- return {
5518
- id: item.id,
5519
- compositionName,
5520
- role,
5521
- label: item.label,
5522
- assetId: item.assetId,
5523
- variantId: item.variantId,
5524
- recordKey: item.recordKey ?? item.sourceFile ?? defaultRecordKey,
5525
- metadata: item.metadata,
5526
- sortIndex: item.sortIndex
5527
- };
5528
- });
5281
+ function parseJsonObjectOrStringOption(value, optionName) {
5282
+ if (value === void 0) return void 0;
5283
+ if (!value.trim().startsWith("{")) return value;
5284
+ return parseJsonObjectOption(value, optionName);
5529
5285
  }
5530
- function normalizeStylePresets(inputs) {
5531
- return inputs.map((input, index) => {
5532
- const preset = requireObject(input, `stylePresets[${index}]`);
5533
- const name = preset.name ?? preset.presetName;
5534
- if (!name || typeof name !== "string") throw new Error(`stylePresets[${index}] name is required`);
5535
- const stylePrompt = preset.stylePrompt ?? preset.prompt;
5536
- if (stylePrompt !== void 0 && typeof stylePrompt !== "string") throw new Error(`stylePresets[${index}] stylePrompt must be a string`);
5537
- if (preset.description !== void 0 && preset.description !== null && typeof preset.description !== "string") throw new Error(`stylePresets[${index}] description must be a string or null`);
5538
- if ([
5539
- preset.create,
5540
- preset.update,
5541
- preset.upsert
5542
- ].filter((value) => value === true).length !== 1) throw new Error(`stylePresets[${index}] must set exactly one of create, update, or upsert`);
5543
- return {
5544
- id: preset.id,
5545
- name,
5546
- description: preset.description,
5547
- stylePrompt,
5548
- collectionName: preset.collectionName ?? preset.collection ?? void 0,
5549
- collectionId: preset.collectionId,
5550
- enabled: preset.enabled,
5551
- isDefault: preset.isDefault ?? preset.default,
5552
- action: preset.create ? "create" : preset.update ? "update" : "upsert"
5553
- };
5554
- });
5286
+ function normalizeRelationType(value) {
5287
+ if (!value) return "derived";
5288
+ if (value === "derived" || value === "refined" || value === "forked") return value;
5289
+ throw new UploadUsageError("--relation-type must be derived, refined, or forked");
5555
5290
  }
5556
- function normalizeSubject(input, label) {
5557
- const subjectType = normalizeSubjectType(input.subjectType ?? input.type ?? (input.assetId ? "asset" : "variant"), `${label} subjectType`);
5558
- const recordKey = input.recordKey ?? input.sourceFile;
5559
- if (subjectType === "asset" && input.variantId && !recordKey && !input.assetId) throw new Error(`${label} asset subject must use assetId or recordKey`);
5560
- if (subjectType === "variant" && input.assetId && !input.variantId && !recordKey) throw new Error(`${label} variant subject must use variantId or recordKey`);
5561
- return {
5562
- subjectType,
5563
- assetId: input.assetId,
5564
- variantId: input.variantId,
5565
- recordKey
5566
- };
5291
+ function normalizeActiveVariantBehavior(value) {
5292
+ if (!value || value === "if-missing" || value === "if_missing") return "if-missing";
5293
+ if (value === "set-active" || value === "set_active") return "set-active";
5294
+ if (value === "keep") return "keep";
5295
+ throw new UploadUsageError("--active-variant-behavior must be if-missing, set-active, or keep");
5567
5296
  }
5568
- function normalizeSubjectType(value, label) {
5297
+ function normalizeSpaceSubjectType(value, optionName, defaultValue) {
5298
+ if (!value) return defaultValue;
5569
5299
  if (value === "asset" || value === "variant") return value;
5570
- throw new Error(`${label} must be asset or variant`);
5300
+ throw new UploadUsageError(`${optionName} must be asset or variant`);
5571
5301
  }
5572
- function normalizeRelationType(value, label) {
5302
+ function normalizeManualRelationType(value, optionName) {
5573
5303
  if ([
5574
5304
  "appears_in",
5575
5305
  "background_for",
@@ -5582,425 +5312,156 @@ function normalizeRelationType(value, label) {
5582
5312
  "reference_for",
5583
5313
  "custom"
5584
5314
  ].includes(value)) return value;
5585
- throw new Error(`${label} is invalid`);
5586
- }
5587
- function normalizeCompositionItemRole(value, label) {
5588
- if ([
5589
- "output",
5590
- "background",
5591
- "character",
5592
- "prop",
5593
- "style_ref",
5594
- "overlay",
5595
- "map",
5596
- "thumbnail",
5597
- "custom"
5598
- ].includes(value)) return value;
5599
- throw new Error(`${label} is invalid`);
5315
+ throw new UploadUsageError(`${optionName} relation type is invalid`);
5600
5316
  }
5601
- function normalizeCompositionStatus(value, label) {
5602
- if (value === void 0) return void 0;
5603
- if (value === "draft" || value === "final") return value;
5604
- throw new Error(`${label} must be draft or final`);
5605
- }
5606
- function normalizePinnedVariantBehavior(value, label) {
5607
- if (!value || value === "imported") return "imported";
5608
- if (value === "none" || value === "unpin") return "none";
5609
- throw new Error(`${label} must be imported or none`);
5317
+ function parseCsvOption$1(value, optionName) {
5318
+ if (!value) return [];
5319
+ if (value === "true") throw new UploadUsageError(`${optionName} requires a value`);
5320
+ return value.split(",").map((entry) => entry.trim()).filter(Boolean);
5610
5321
  }
5611
- function requireObject(value, label) {
5612
- if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error(`${label} must be an object`);
5322
+ function parseCollectionPlacements(input) {
5323
+ const collectionIds = parseCsvOption$1(input.collectionIds, "--collection");
5324
+ const collectionNames = parseCsvOption$1(input.collectionNames, "--collection-name");
5325
+ if (collectionIds.length === 0 && collectionNames.length === 0) return [];
5326
+ const subjectType = normalizeSpaceSubjectType(input.subjectType, "--collection-subject", "asset");
5327
+ if (subjectType === "variant" && input.pinnedVariant && input.pinnedVariant !== "true") throw new UploadUsageError("--collection-pinned-variant can only be used when --collection-subject is asset");
5328
+ const pinnedVariantId = normalizeCollectionPinnedVariant(input.pinnedVariant);
5329
+ const placement = {
5330
+ role: input.role && input.role !== "true" ? input.role : "member",
5331
+ subjectType,
5332
+ pinnedVariantId
5333
+ };
5334
+ return [...collectionIds.map((collectionId) => ({
5335
+ ...placement,
5336
+ collectionId
5337
+ })), ...collectionNames.map((collectionName) => ({
5338
+ ...placement,
5339
+ collectionName
5340
+ }))];
5341
+ }
5342
+ function normalizeCollectionPinnedVariant(value) {
5343
+ if (!value || value === "true" || value === "uploaded") return void 0;
5344
+ if (value === "none" || value === "unpin") return null;
5613
5345
  return value;
5614
5346
  }
5615
- function validateSortIndex(value, label) {
5616
- if (value !== void 0 && !Number.isInteger(value)) throw new Error(`${label} must be an integer`);
5617
- }
5618
- function labelFor(section, index, recordKey) {
5619
- return recordKey ? `Record ${recordKey} ${section}[${index}]` : `${section}[${index}]`;
5620
- }
5621
- function normalizeActiveBehavior(value, recordIndex) {
5622
- if (!value || value === "if-missing" || value === "if_missing") return "if-missing";
5623
- if (value === "set-active" || value === "set_active") return "set-active";
5624
- if (value === "keep") return "keep";
5625
- throw new Error(`Record ${recordIndex + 1} activeVariantBehavior must be if-missing, set-active, or keep`);
5626
- }
5627
- function validateObjectField(value, label) {
5628
- if (value === void 0) return;
5629
- if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error(`${label} must be an object`);
5630
- }
5631
- async function validateLocalRecords(records, deps) {
5632
- for (const record of records) {
5633
- let fileStat;
5634
- try {
5635
- fileStat = await deps.stat(record.filePath);
5636
- } catch (error) {
5637
- if (error.code === "ENOENT") throw new Error(`File not found for ${record.localKey}: ${record.displayPath}`);
5638
- throw error;
5639
- }
5640
- if (!fileStat.isFile()) throw new Error(`Manifest file is not a file for ${record.localKey}: ${record.displayPath}`);
5641
- if (fileStat.size > 10485760) throw new Error(`File too large for ${record.localKey} (${(fileStat.size / 1024 / 1024).toFixed(2)}MB). Maximum size: 10MB`);
5642
- }
5347
+ function parseManualRelations(input) {
5348
+ const relationSpecs = parseCsvOption$1(input.relations, "--manual-relation");
5349
+ if (relationSpecs.length === 0) return [];
5350
+ const subjectType = normalizeSpaceSubjectType(input.subjectType, "--manual-relation-subject", "variant");
5351
+ const context = parseJsonObjectOrStringOption(input.context, "--manual-relation-context");
5352
+ const metadata = parseJsonObjectOption(input.metadata, "--manual-relation-metadata");
5353
+ return relationSpecs.map((spec) => {
5354
+ const parts = spec.split(":");
5355
+ if (parts.length !== 3) throw new UploadUsageError("--manual-relation entries must use <relation-type>:asset:<asset-id> or <relation-type>:variant:<variant-id>");
5356
+ const [relationTypeValue, objectTypeValue, objectId] = parts;
5357
+ const objectType = normalizeSpaceSubjectType(objectTypeValue, "--manual-relation object type", "asset");
5358
+ if (!objectId) throw new UploadUsageError("--manual-relation object id is required");
5359
+ return {
5360
+ relationType: normalizeManualRelationType(relationTypeValue, "--manual-relation"),
5361
+ object: objectType === "asset" ? {
5362
+ subjectType: "asset",
5363
+ assetId: objectId
5364
+ } : {
5365
+ subjectType: "variant",
5366
+ variantId: objectId
5367
+ },
5368
+ subjectType,
5369
+ label: input.label && input.label !== "true" ? input.label : void 0,
5370
+ context,
5371
+ metadata
5372
+ };
5373
+ });
5643
5374
  }
5644
- async function fetchSpaceState(ctx, deps) {
5645
- const [assetsData, collectionsData, compositionsData, stylePresetsData] = await Promise.all([
5646
- fetchJson$1(ctx, deps, `/api/spaces/${ctx.spaceId}/assets`),
5647
- fetchJson$1(ctx, deps, `/api/spaces/${ctx.spaceId}/collections`),
5648
- fetchJson$1(ctx, deps, `/api/spaces/${ctx.spaceId}/compositions`),
5649
- fetchJson$1(ctx, deps, `/api/spaces/${ctx.spaceId}/style-presets`)
5650
- ]);
5651
- const assets = assetsData.assets ?? [];
5375
+ async function preflightUploadOrganization(ctx, deps, options) {
5376
+ const needsCollections = options.collectionPlacements.length > 0;
5377
+ const needsAssetState = options.sourceVariantIds.length > 0 || options.manualRelations.length > 0 || options.collectionPlacements.some((placement) => placement.pinnedVariantId);
5378
+ const [collections, state] = await Promise.all([needsCollections ? fetchJson$1(ctx, deps, `/api/spaces/${ctx.spaceId}/collections`) : void 0, needsAssetState ? fetchAssetState(ctx, deps) : void 0]);
5379
+ if (collections) {
5380
+ const collectionList = collections.collections ?? [];
5381
+ const collectionIds = new Set(collectionList.map((collection) => collection.id));
5382
+ for (const placement of options.collectionPlacements) {
5383
+ if (placement.collectionId && !collectionIds.has(placement.collectionId)) throw new Error(`Collection not found: ${placement.collectionId}`);
5384
+ if (!placement.collectionName) continue;
5385
+ const matches = collectionList.filter((collection) => collection.name === placement.collectionName);
5386
+ if (matches.length === 0) throw new Error(`Collection not found by exact name: ${placement.collectionName}`);
5387
+ if (matches.length > 1) throw new Error(`Collection name is ambiguous: ${placement.collectionName}; use --collection <id>`);
5388
+ placement.collectionId = matches[0].id;
5389
+ }
5390
+ }
5391
+ if (!state) return;
5392
+ for (const sourceVariantId of options.sourceVariantIds) if (!state.variantsById.has(sourceVariantId)) throw new Error(`Source variant not found in space: ${sourceVariantId}`);
5393
+ for (const placement of options.collectionPlacements) {
5394
+ if (!placement.pinnedVariantId) continue;
5395
+ const pinned = state.variantsById.get(placement.pinnedVariantId);
5396
+ if (!pinned) throw new Error(`Collection pinned variant not found: ${placement.pinnedVariantId}`);
5397
+ if (placement.subjectType === "asset") {
5398
+ if (!options.assetId) throw new Error("--collection-pinned-variant cannot reference an existing variant when creating a new asset");
5399
+ if (pinned.asset_id !== options.assetId) throw new Error("--collection-pinned-variant must reference a variant on the target asset");
5400
+ }
5401
+ }
5402
+ for (const relation of options.manualRelations) if (relation.object.subjectType === "asset") {
5403
+ if (!relation.object.assetId || !state.assetsById.has(relation.object.assetId)) throw new Error(`Manual relation object asset not found: ${relation.object.assetId ?? ""}`);
5404
+ } else if (!relation.object.variantId || !state.variantsById.has(relation.object.variantId)) throw new Error(`Manual relation object variant not found: ${relation.object.variantId ?? ""}`);
5405
+ }
5406
+ async function fetchAssetState(ctx, deps) {
5407
+ const assets = (await fetchJson$1(ctx, deps, `/api/spaces/${ctx.spaceId}/assets`)).assets ?? [];
5652
5408
  const variantsById = /* @__PURE__ */ new Map();
5653
- for (const asset of assets) {
5409
+ await Promise.all(assets.map(async (asset) => {
5654
5410
  const details = await fetchJson$1(ctx, deps, `/api/spaces/${ctx.spaceId}/assets/${encodeURIComponent(asset.id)}`);
5655
5411
  for (const variant of details.variants ?? []) variantsById.set(variant.id, variant);
5656
- }
5657
- return {
5658
- assets,
5659
- variantsById,
5660
- collections: collectionsData.collections ?? [],
5661
- compositions: compositionsData.compositions ?? [],
5662
- stylePresets: stylePresetsData.presets ?? []
5663
- };
5664
- }
5665
- function validateSpaceReferences(records, state) {
5666
- const assetsById = new Map(state.assets.map((asset) => [asset.id, asset]));
5667
- const recordsByKey = new Map(records.map((record) => [record.localKey, record]));
5668
- for (const record of records) {
5669
- if (record.assetId) {
5670
- const asset = assetsById.get(record.assetId);
5671
- if (!asset) throw new Error(`Target asset not found for ${record.localKey}: ${record.assetId}`);
5672
- if (asset.media_kind && asset.media_kind !== record.mediaKind) throw new Error(`Target asset ${record.assetId} is ${asset.media_kind}, but ${record.localKey} is ${record.mediaKind}`);
5673
- }
5674
- for (const lineage of record.lineage) {
5675
- if (lineage.sourceVariantId && !state.variantsById.has(lineage.sourceVariantId)) throw new Error(`Source variant not found in space for ${record.localKey}: ${lineage.sourceVariantId}`);
5676
- if (lineage.sourceFile) {
5677
- const sourceRecord = recordsByKey.get(lineage.sourceFile);
5678
- if (!sourceRecord) throw new Error(`Source file key not found for ${record.localKey}: ${lineage.sourceFile}`);
5679
- if (sourceRecord.localKey === record.localKey) throw new Error(`Record ${record.localKey} cannot use itself as a lineage source`);
5680
- }
5681
- }
5682
- }
5683
- }
5684
- function validateManifestPlan(manifest, state) {
5685
- validateSpaceReferences(manifest.records, state);
5686
- const recordsByKey = new Map(manifest.records.map((record) => [record.localKey, record]));
5687
- const collectionsByName = new Map(state.collections.map((collection) => [collection.name, collection]));
5688
- const compositionsByName = new Map(state.compositions.map((composition) => [composition.name, composition]));
5689
- const stylePresetsByName = new Map(state.stylePresets.map((preset) => [preset.name, preset]));
5690
- const stylePresetsById = new Map(state.stylePresets.map((preset) => [preset.id, preset]));
5691
- const collections = dedupeCollections([
5692
- ...manifest.collections,
5693
- ...manifest.styleCollections,
5694
- ...manifest.records.flatMap((record) => record.styleCollections)
5695
- ]);
5696
- const collectionItems = [
5697
- ...manifest.collectionItems,
5698
- ...manifest.styleCollections.flatMap((collection) => styleCollectionItems(collection)),
5699
- ...manifest.records.flatMap((record) => record.styleCollections.flatMap((collection) => styleCollectionItems(collection)))
5700
- ];
5701
- const compositions = dedupeCompositions(manifest.compositions);
5702
- const compositionItems = manifest.compositionItems;
5703
- for (const collection of collections) if (!collectionsByName.has(collection.name) && !collection.create) throw new Error(`Collection not found: ${collection.name}. Add it to manifest collections with create: true.`);
5704
- for (const item of collectionItems) {
5705
- if (!collectionsByName.has(item.collectionName) && !collections.some((collection) => collection.name === item.collectionName && collection.create)) throw new Error(`Collection not found for collection item: ${item.collectionName}`);
5706
- validateCollectionSubject(item, recordsByKey, state);
5707
- }
5708
- for (const relation of manifest.relations) {
5709
- validateSubjectReference(relation.subject, recordsByKey, state, "Relation subject");
5710
- validateSubjectReference(relation.object, recordsByKey, state, "Relation object");
5711
- }
5712
- for (const composition of compositions) {
5713
- if (!compositionsByName.has(composition.name) && !composition.create) throw new Error(`Composition not found: ${composition.name}. Add it to manifest compositions with create: true.`);
5714
- if (composition.output) {
5715
- validateSubjectReference(composition.output, recordsByKey, state, `Composition ${composition.name} output`);
5716
- if (composition.output.subjectType !== "variant") throw new Error(`Composition ${composition.name} output must reference a variant`);
5717
- }
5718
- }
5719
- for (const item of compositionItems) {
5720
- if (!compositionsByName.has(item.compositionName) && !compositions.some((composition) => composition.name === item.compositionName && composition.create)) throw new Error(`Composition not found for composition item: ${item.compositionName}`);
5721
- if (item.recordKey) {
5722
- if (!recordsByKey.has(item.recordKey)) throw new Error(`Composition item references unknown same-batch record: ${item.recordKey}`);
5723
- } else if (!item.variantId) throw new Error(`Composition item for ${item.compositionName} must set variantId or recordKey`);
5724
- else if (!state.variantsById.has(item.variantId)) throw new Error(`Composition item references unknown variant: ${item.variantId}`);
5725
- if (item.assetId && !state.assets.some((asset) => asset.id === item.assetId)) throw new Error(`Composition item references unknown asset: ${item.assetId}`);
5726
- }
5727
- for (const preset of manifest.stylePresets) {
5728
- const existingPresetById = preset.id ? stylePresetsById.get(preset.id) : void 0;
5729
- const existingPresetByName = stylePresetsByName.get(preset.name);
5730
- const existingPreset = preset.id ? existingPresetById : existingPresetByName;
5731
- const createConflict = preset.id ? existingPresetById ?? existingPresetByName : existingPresetByName;
5732
- if (preset.action === "create" && createConflict) throw new Error(`Style preset already exists: ${preset.name}`);
5733
- if (preset.action === "update" && !existingPreset) throw new Error(`Style preset not found for update: ${preset.id ?? preset.name}`);
5734
- if (preset.collectionName) {
5735
- const willCreate = collections.some((collection) => collection.name === preset.collectionName && collection.create);
5736
- if (!collectionsByName.has(preset.collectionName) && !willCreate) throw new Error(`Style preset ${preset.name} references missing collection: ${preset.collectionName}`);
5737
- }
5738
- }
5739
- return {
5740
- ...manifest,
5741
- collections,
5742
- collectionItems,
5743
- compositions,
5744
- compositionItems
5745
- };
5746
- }
5747
- function dedupeCollections(collections) {
5748
- const byName = /* @__PURE__ */ new Map();
5749
- for (const collection of collections) {
5750
- const existing = byName.get(collection.name);
5751
- byName.set(collection.name, {
5752
- key: collection.key,
5753
- name: collection.name,
5754
- description: collection.description ?? existing?.description,
5755
- sortIndex: collection.sortIndex ?? existing?.sortIndex,
5756
- create: Boolean(existing?.create || collection.create)
5757
- });
5758
- }
5759
- return [...byName.values()];
5760
- }
5761
- function dedupeCompositions(compositions) {
5762
- const byName = /* @__PURE__ */ new Map();
5763
- for (const composition of compositions) {
5764
- const existing = byName.get(composition.name);
5765
- byName.set(composition.name, {
5766
- ...existing,
5767
- ...composition,
5768
- create: Boolean(existing?.create || composition.create)
5769
- });
5770
- }
5771
- return [...byName.values()];
5772
- }
5773
- function styleCollectionItems(collection) {
5774
- return collection.refs.map((recordKey, index) => ({
5775
- collectionName: collection.name,
5776
- role: "style_ref",
5777
- subjectType: "asset",
5778
- recordKey,
5779
- pinnedVariantBehavior: "imported",
5780
- sortIndex: index
5781
5412
  }));
5413
+ return {
5414
+ assetsById: new Map(assets.map((asset) => [asset.id, asset])),
5415
+ variantsById
5416
+ };
5782
5417
  }
5783
- function validateCollectionSubject(item, recordsByKey, state) {
5784
- if (item.recordKey) {
5785
- if (!recordsByKey.has(item.recordKey)) throw new Error(`Collection item references unknown same-batch record: ${item.recordKey}`);
5786
- return;
5787
- }
5788
- if (item.subjectType === "asset") {
5789
- if (!item.assetId) throw new Error(`Collection item for ${item.collectionName} must set assetId or recordKey`);
5790
- if (!state.assets.some((asset) => asset.id === item.assetId)) throw new Error(`Collection item references unknown asset: ${item.assetId}`);
5791
- if (item.pinnedVariantId && !state.variantsById.has(item.pinnedVariantId)) throw new Error(`Collection item references unknown pinned variant: ${item.pinnedVariantId}`);
5792
- return;
5793
- }
5794
- if (!item.variantId) throw new Error(`Collection item for ${item.collectionName} must set variantId or recordKey`);
5795
- if (!state.variantsById.has(item.variantId)) throw new Error(`Collection item references unknown variant: ${item.variantId}`);
5796
- }
5797
- function validateSubjectReference(subject, recordsByKey, state, label) {
5798
- if (subject.recordKey) {
5799
- if (!recordsByKey.has(subject.recordKey)) throw new Error(`${label} references unknown same-batch record: ${subject.recordKey}`);
5800
- return;
5801
- }
5802
- if (subject.subjectType === "asset") {
5803
- if (!subject.assetId) throw new Error(`${label} must set assetId or recordKey`);
5804
- if (!state.assets.some((asset) => asset.id === subject.assetId)) throw new Error(`${label} references unknown asset: ${subject.assetId}`);
5805
- return;
5806
- }
5807
- if (!subject.variantId) throw new Error(`${label} must set variantId or recordKey`);
5808
- if (!state.variantsById.has(subject.variantId)) throw new Error(`${label} references unknown variant: ${subject.variantId}`);
5809
- }
5810
- function buildUploadOrder(records) {
5811
- const pending = new Map(records.map((record) => [record.localKey, record]));
5812
- const uploaded = /* @__PURE__ */ new Set();
5813
- const order = [];
5814
- while (pending.size > 0) {
5815
- let progressed = false;
5816
- for (const [key, record] of pending) {
5817
- if (!record.lineage.every((lineage) => !lineage.sourceFile || uploaded.has(lineage.sourceFile))) continue;
5818
- order.push(record);
5819
- uploaded.add(key);
5820
- pending.delete(key);
5821
- progressed = true;
5822
- }
5823
- if (!progressed) throw new Error(`Same-batch lineage contains a cycle or unresolved dependency: ${[...pending.keys()].join(", ")}`);
5824
- }
5825
- return order;
5826
- }
5827
- async function uploadRecord(ctx, deps, record, lineage, options) {
5828
- const fileBuffer = await deps.readFile(record.filePath);
5829
- const formData = new FormData();
5830
- formData.append("file", new Blob([fileBuffer], { type: record.mimeType }), record.fileName);
5831
- formData.append("operation", "import");
5832
- formData.append("mediaKind", record.mediaKind);
5833
- formData.append("assetType", record.assetType);
5834
- formData.append("activeVariantBehavior", record.activeVariantBehavior);
5835
- formData.append("generationProvenance", JSON.stringify(record.generationProvenance));
5836
- if (record.assetId) formData.append("assetId", record.assetId);
5837
- else formData.append("assetName", record.assetName);
5838
- if (record.prompt) formData.append("prompt", record.prompt);
5839
- if (record.model) formData.append("model", record.model);
5840
- if (record.provider) formData.append("provider", record.provider);
5841
- if (record.providerMetadata) formData.append("providerMetadata", JSON.stringify(record.providerMetadata));
5842
- if (lineage.length > 0) formData.append("lineage", JSON.stringify(lineage));
5843
- if (!options.quiet) deps.print(`Importing ${record.displayPath}...`);
5844
- const response = await deps.fetch(`${ctx.baseUrl}/api/spaces/${ctx.spaceId}/upload`, {
5845
- method: "POST",
5846
- headers: { Authorization: `Bearer ${ctx.accessToken}` },
5847
- body: formData
5848
- });
5849
- const data = await response.json();
5850
- if (!response.ok) throw new Error(`Import failed for ${record.localKey}: ${"error" in data ? data.error : response.statusText}`);
5851
- return data;
5852
- }
5853
- async function applyOrganizationPlan(ctx, deps, plan, uploadedByKey) {
5854
- const collectionIds = [];
5418
+ async function applyUploadOrganization(ctx, deps, upload, options) {
5855
5419
  const collectionItemIds = [];
5856
5420
  const relationIds = [];
5857
- const compositionIds = [];
5858
- const compositionItemIds = [];
5859
- const stylePresetIds = [];
5860
- const collections = await fetchJson$1(ctx, deps, `/api/spaces/${ctx.spaceId}/collections`);
5861
- const collectionsByName = new Map((collections.collections ?? []).map((collection) => [collection.name, collection]));
5862
- for (const collection of plan.collections) if (!collectionsByName.has(collection.name) && collection.create) {
5863
- const created = await postJson(ctx, deps, `/api/spaces/${ctx.spaceId}/collections`, {
5864
- name: collection.name,
5865
- description: collection.description,
5866
- sortIndex: collection.sortIndex
5867
- });
5868
- collectionsByName.set(created.collection.name, created.collection);
5869
- collectionIds.push(created.collection.id);
5870
- }
5871
- for (const item of plan.collectionItems) {
5872
- const collection = collectionsByName.get(item.collectionName);
5873
- if (!collection) throw new Error(`Collection not found during import: ${item.collectionName}`);
5874
- const created = await postJson(ctx, deps, `/api/spaces/${ctx.spaceId}/collections/${encodeURIComponent(collection.id)}/items`, resolveCollectionItemRequest(item, uploadedByKey));
5421
+ const uploadedSubject = {
5422
+ assetId: upload.variant.asset_id,
5423
+ variantId: upload.variant.id
5424
+ };
5425
+ for (const placement of options.collectionPlacements) {
5426
+ if (!placement.collectionId) throw new Error(`Collection name was not resolved: ${placement.collectionName ?? ""}`);
5427
+ const body = placement.subjectType === "asset" ? {
5428
+ subjectType: "asset",
5429
+ assetId: uploadedSubject.assetId,
5430
+ role: placement.role,
5431
+ pinnedVariantId: placement.pinnedVariantId === void 0 ? uploadedSubject.variantId : placement.pinnedVariantId
5432
+ } : {
5433
+ subjectType: "variant",
5434
+ variantId: uploadedSubject.variantId,
5435
+ role: placement.role
5436
+ };
5437
+ const created = await postJson(ctx, deps, `/api/spaces/${ctx.spaceId}/collections/${encodeURIComponent(placement.collectionId)}/items`, body);
5875
5438
  collectionItemIds.push(created.item.id);
5876
5439
  }
5877
- for (const relation of plan.relations) {
5440
+ for (const relation of options.manualRelations) {
5878
5441
  const created = await postJson(ctx, deps, `/api/spaces/${ctx.spaceId}/relations`, {
5879
- id: relation.id,
5880
- subject: resolveSubject(relation.subject, uploadedByKey),
5881
- object: resolveSubject(relation.object, uploadedByKey),
5442
+ subject: relation.subjectType === "asset" ? {
5443
+ subjectType: "asset",
5444
+ assetId: uploadedSubject.assetId
5445
+ } : {
5446
+ subjectType: "variant",
5447
+ variantId: uploadedSubject.variantId
5448
+ },
5449
+ object: relation.object,
5882
5450
  relationType: relation.relationType,
5883
5451
  label: relation.label,
5884
5452
  context: relation.context,
5885
- metadata: relation.metadata,
5886
- sortIndex: relation.sortIndex
5453
+ metadata: relation.metadata
5887
5454
  });
5888
5455
  relationIds.push(created.relation.id);
5889
5456
  }
5890
- const compositions = await fetchJson$1(ctx, deps, `/api/spaces/${ctx.spaceId}/compositions`);
5891
- const compositionsByName = new Map((compositions.compositions ?? []).map((composition) => [composition.name, composition]));
5892
- for (const composition of plan.compositions) if (!compositionsByName.has(composition.name) && composition.create) {
5893
- const output = composition.output ? resolveSubject(composition.output, uploadedByKey) : void 0;
5894
- const created = await postJson(ctx, deps, `/api/spaces/${ctx.spaceId}/compositions`, {
5895
- name: composition.name,
5896
- description: composition.description,
5897
- status: composition.status,
5898
- outputAssetId: output?.assetId,
5899
- outputVariantId: output?.variantId,
5900
- metadata: composition.metadata,
5901
- sortIndex: composition.sortIndex
5902
- });
5903
- compositionsByName.set(created.composition.name, created.composition);
5904
- compositionIds.push(created.composition.id);
5905
- }
5906
- for (const item of plan.compositionItems) {
5907
- const composition = compositionsByName.get(item.compositionName);
5908
- if (!composition) throw new Error(`Composition not found during import: ${item.compositionName}`);
5909
- const created = await postJson(ctx, deps, `/api/spaces/${ctx.spaceId}/compositions/${encodeURIComponent(composition.id)}/items`, resolveCompositionItemRequest(item, uploadedByKey));
5910
- compositionItemIds.push(created.item.id);
5911
- }
5912
- const presets = await fetchJson$1(ctx, deps, `/api/spaces/${ctx.spaceId}/style-presets`);
5913
- const presetsByName = new Map((presets.presets ?? []).map((preset) => [preset.name, preset]));
5914
- const presetsById = new Map((presets.presets ?? []).map((preset) => [preset.id, preset]));
5915
- for (const preset of plan.stylePresets) {
5916
- const collectionId = preset.collectionId === null ? null : preset.collectionId ?? (preset.collectionName ? collectionsByName.get(preset.collectionName)?.id : void 0);
5917
- if (preset.collectionName && collectionId === void 0) throw new Error(`Style preset ${preset.name} references missing collection during import: ${preset.collectionName}`);
5918
- const request = {
5919
- id: preset.id,
5920
- name: preset.name,
5921
- description: preset.description,
5922
- stylePrompt: preset.stylePrompt,
5923
- collectionId,
5924
- enabled: preset.enabled,
5925
- isDefault: preset.isDefault
5926
- };
5927
- const existing = preset.id ? presetsById.get(preset.id) : presetsByName.get(preset.name);
5928
- if (preset.action === "update" || preset.action === "upsert" && existing) {
5929
- const updated = await patchJson(ctx, deps, `/api/spaces/${ctx.spaceId}/style-presets/${encodeURIComponent(existing.id)}`, request);
5930
- stylePresetIds.push(updated.preset.id);
5931
- presetsByName.set(updated.preset.name, updated.preset);
5932
- presetsById.set(updated.preset.id, updated.preset);
5933
- } else {
5934
- const created = await postJson(ctx, deps, `/api/spaces/${ctx.spaceId}/style-presets`, request);
5935
- stylePresetIds.push(created.preset.id);
5936
- presetsByName.set(created.preset.name, created.preset);
5937
- presetsById.set(created.preset.id, created.preset);
5938
- }
5939
- }
5940
5457
  return {
5941
- collectionIds,
5942
5458
  collectionItemIds,
5943
- relationIds,
5944
- compositionIds,
5945
- compositionItemIds,
5946
- stylePresetIds
5947
- };
5948
- }
5949
- function resolveCollectionItemRequest(item, uploadedByKey) {
5950
- const subject = item.recordKey ? resolveSubject({
5951
- subjectType: item.subjectType,
5952
- recordKey: item.recordKey
5953
- }, uploadedByKey) : {
5954
- subjectType: item.subjectType,
5955
- assetId: item.assetId,
5956
- variantId: item.variantId
5957
- };
5958
- return {
5959
- id: item.id,
5960
- ...subject,
5961
- role: item.role,
5962
- pinnedVariantId: item.pinnedVariantBehavior === "none" ? item.pinnedVariantId ?? null : item.pinnedVariantId ?? (item.recordKey ? uploadedByKey.get(item.recordKey).variantId : void 0),
5963
- sortIndex: item.sortIndex
5964
- };
5965
- }
5966
- function resolveCompositionItemRequest(item, uploadedByKey) {
5967
- const uploaded = item.recordKey ? uploadedByKey.get(item.recordKey) : void 0;
5968
- if (item.recordKey && !uploaded) throw new Error(`Could not resolve same-batch composition item: ${item.recordKey}`);
5969
- return {
5970
- id: item.id,
5971
- role: item.role,
5972
- label: item.label,
5973
- assetId: item.assetId ?? uploaded?.assetId,
5974
- variantId: item.variantId ?? uploaded.variantId,
5975
- metadata: item.metadata,
5976
- sortIndex: item.sortIndex
5977
- };
5978
- }
5979
- function resolveSubject(subject, uploadedByKey) {
5980
- if (!subject.recordKey) return {
5981
- subjectType: subject.subjectType,
5982
- assetId: subject.assetId,
5983
- variantId: subject.variantId
5984
- };
5985
- const uploaded = uploadedByKey.get(subject.recordKey);
5986
- if (!uploaded) throw new Error(`Could not resolve same-batch reference: ${subject.recordKey}`);
5987
- return subject.subjectType === "asset" ? {
5988
- subjectType: "asset",
5989
- assetId: uploaded.assetId
5990
- } : {
5991
- subjectType: "variant",
5992
- variantId: uploaded.variantId
5459
+ relationIds
5993
5460
  };
5994
5461
  }
5995
5462
  async function postJson(ctx, deps, requestPath, json) {
5996
- return sendJson(ctx, deps, "POST", requestPath, json);
5997
- }
5998
- async function patchJson(ctx, deps, requestPath, json) {
5999
- return sendJson(ctx, deps, "PATCH", requestPath, json);
6000
- }
6001
- async function sendJson(ctx, deps, method, requestPath, json) {
6002
5463
  const response = await deps.fetch(`${ctx.baseUrl}${requestPath}`, {
6003
- method,
5464
+ method: "POST",
6004
5465
  headers: {
6005
5466
  Authorization: `Bearer ${ctx.accessToken}`,
6006
5467
  Accept: "application/json",
@@ -6012,9 +5473,6 @@ async function sendJson(ctx, deps, method, requestPath, json) {
6012
5473
  if (!response.ok) throw new Error("error" in data ? data.error : `Request failed: ${response.status}`);
6013
5474
  return data;
6014
5475
  }
6015
- function stripUndefined(value) {
6016
- return Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== void 0));
6017
- }
6018
5476
  async function fetchJson$1(ctx, deps, requestPath) {
6019
5477
  const response = await deps.fetch(`${ctx.baseUrl}${requestPath}`, { headers: {
6020
5478
  Authorization: `Bearer ${ctx.accessToken}`,
@@ -6024,40 +5482,60 @@ async function fetchJson$1(ctx, deps, requestPath) {
6024
5482
  if (!response.ok) throw new Error("error" in data ? data.error : `Request failed: ${response.status}`);
6025
5483
  return data;
6026
5484
  }
6027
- function printResult$1(result, parsed, deps) {
6028
- if (parsed.options.json === "true") {
6029
- deps.print(JSON.stringify(result, null, 2));
6030
- return;
6031
- }
6032
- if (result.dryRun) {
6033
- deps.print(`Dry run passed for ${result.records.length} import records.`);
6034
- const organizationCount = result.collections.length + result.collectionItems + result.relations + result.compositions.length + result.compositionItems + result.stylePresets.length;
6035
- if (organizationCount > 0) deps.print(` Organization changes: ${organizationCount}`);
6036
- return;
6037
- }
6038
- deps.print(`Imported ${result.records.length} records.`);
6039
- for (const record of result.records) deps.print(` ${record.key}: asset ${record.assetId}, variant ${record.variantId}`);
6040
- const organizationCount = result.collectionIds.length + result.collectionItemIds.length + result.relationIds.length + result.compositionIds.length + result.compositionItemIds.length + result.stylePresetIds.length;
6041
- if (organizationCount > 0) deps.print(` Organization records: ${organizationCount}`);
5485
+ function stripUndefined(value) {
5486
+ return Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== void 0));
6042
5487
  }
6043
5488
  function printUsage$11() {
6044
5489
  console.log(`
6045
5490
  Usage:
6046
- makefx import <manifest.json> [--space <id>]
6047
- makefx import <manifest.json> --dry-run [--json]
5491
+ makefx upload <file> --asset <id> [--space <id>] Upload media to existing asset
5492
+ makefx upload <file> --name <name> [--space <id>] Upload media as a new asset
5493
+
5494
+ Options:
5495
+ --space <id> Target space ID; defaults from initialized project
5496
+ --asset <id> Target asset ID (upload as new variant)
5497
+ --name <name> New asset name (creates asset + variant)
5498
+ --type <type> Asset type for new assets (default: character)
5499
+ --media-kind <k> Optional explicit kind: image, audio, or video
5500
+ --prompt <text> Prompt provenance for uploaded media
5501
+ --model <model> Model provenance for uploaded media
5502
+ --provider <name> Provider provenance for uploaded media
5503
+ --provider-metadata <json> Provider metadata JSON object
5504
+ --generation-provenance <json> Extra provenance JSON object
5505
+ --source-variant <ids> Comma-separated source variants for upload lineage
5506
+ --source-variants <ids> Alias for --source-variant
5507
+ --relation-type <type> Lineage type: derived, refined, or forked (default: derived)
5508
+ --active-variant-behavior <b> if-missing, set-active, or keep
5509
+ --collection <ids> Comma-separated collection IDs for the uploaded asset or variant
5510
+ --collection-name <names> Comma-separated exact collection names for lookup
5511
+ --collection-role <role> Collection item role (default: member)
5512
+ --collection-subject <type> Collection subject: asset or variant (default: asset)
5513
+ --collection-pinned-variant <id|uploaded|none>
5514
+ Pin an asset collection item to the uploaded variant by default
5515
+ --manual-relation <spec> Comma-separated <type>:asset:<id> or <type>:variant:<id>
5516
+ --manual-relation-subject <type>
5517
+ Relation subject: uploaded asset or variant (default: variant)
5518
+ --manual-relation-label <text> Optional manual relation label
5519
+ --manual-relation-context <json|string>
5520
+ Optional manual relation context
5521
+ --manual-relation-metadata <json>
5522
+ Optional manual relation metadata object
5523
+ --json Print machine-readable upload output with Space IDs
5524
+ --env <env> Environment (production|stage|local)
5525
+ --local Shortcut for --env local
6048
5526
 
6049
- Manifest:
6050
- Top-level array, or { "records": [...] }. Each record sets file plus either
6051
- assetId for an existing asset or name for a new asset. Records can include
6052
- prompt, model, provider, providerMetadata, and generationProvenance for
6053
- immutable import provenance. Lineage entries use sourceVariantId or sourceFile
6054
- with relationType derived, refined, or forked; lineage is not editable
6055
- organization state. Optional collections, collectionItems, relations,
6056
- compositions, compositionItems, styleCollections, and stylePresets sections
6057
- organize imported records after upload. Style references are normal assets in
6058
- collections and presets point to those collections. Missing
6059
- collections/compositions are only created when their manifest entry sets
6060
- create: true.
5527
+ Examples:
5528
+ makefx upload hero.png --space abc123 --name "Hero Character"
5529
+ makefx upload hero.png --space abc123 --name "Hero" --prompt "external render" --provider blender
5530
+ makefx upload paintover.png --space abc123 --asset def456 --source-variant var123 --relation-type refined
5531
+ makefx upload scene.png --space abc123 --name "Cocina" --type scene --source-variants anna,roman,bg
5532
+ makefx upload hero.png --space abc123 --name "Hero" --collection collection_cast --collection-role character
5533
+ makefx upload hero.png --space abc123 --name "Hero" --collection-name "Cast" --collection-role character
5534
+ makefx upload thumbnail.png --space abc123 --asset asset_thumb --manual-relation thumbnail_for:asset:asset_target
5535
+ makefx upload theme.mp3 --space abc123 --name "Theme Music" --type audio
5536
+ makefx upload cutscene.mp4 --space abc123 --name "Opening Cutscene" --type video
5537
+ makefx upload variant.jpg --space abc123 --asset def456
5538
+ makefx upload hero.png --space abc123 --name "Hero" --json
6061
5539
  `);
6062
5540
  }
6063
5541
  //#endregion
@@ -19997,6 +19475,7 @@ var UserProfileSchema = object({
19997
19475
  var SessionConfigSchema = object({
19998
19476
  googleClientId: string$1(),
19999
19477
  environment: string$1(),
19478
+ mediaCdnBaseUrl: string$1().nullable().optional(),
20000
19479
  features: object({ rotation: boolean$1() })
20001
19480
  }).openapi("SessionConfig");
20002
19481
  var AuthSessionResponseSchema = object({
@@ -26243,7 +25722,7 @@ Options:
26243
25722
  }
26244
25723
  //#endregion
26245
25724
  //#region src/cli/index.ts
26246
- var CLI_VERSION = "0.2.0+a98a786097bf";
25725
+ var CLI_VERSION = "0.2.2+eac63fce1aba";
26247
25726
  async function main() {
26248
25727
  const argv = process$1.argv.slice(2);
26249
25728
  if (argv[0] === "--") argv.shift();
@@ -26352,9 +25831,6 @@ function printCommandHelp(command, positionals) {
26352
25831
  case "upload":
26353
25832
  printUploadHelp();
26354
25833
  return;
26355
- case "import":
26356
- printImportHelp();
26357
- return;
26358
25834
  case "generate":
26359
25835
  case "refine":
26360
25836
  case "derive":
@@ -26471,8 +25947,10 @@ Listen:
26471
25947
 
26472
25948
  Upload:
26473
25949
  upload <file> --asset <id> [--space <id>] Upload image, audio, or video to existing asset
26474
- upload <file> --name <name> [--space <id>] Upload media and create new asset
26475
- import <manifest.json> [--space <id>] Import media with provenance, lineage, and organization metadata
25950
+ upload <file> --name <name> [--space <id>] Upload image, audio, or video as a new asset
25951
+ upload <file> --collection <id> Place uploaded media in a collection
25952
+ upload <file> --collection-name <name> Place uploaded media in an exact-name collection
25953
+ upload <file> --manual-relation <spec> Create a manual relation from uploaded media
26476
25954
 
26477
25955
  Forge:
26478
25956
  generate "prompt" --name <name> --type <type> -o <file>
@@ -26514,8 +25992,9 @@ Examples:
26514
25992
  makefx audio sfx generate "A short brass victory sting" --name "Victory Sting" -o victory.wav
26515
25993
  makefx video generate "A looping idle animation" --name "Idle Animation" --type animation --duration 6 --resolution 1080p --tier fast -o idle.mp4
26516
25994
  makefx productions export --production-id s01e01-a2
26517
- makefx import import-manifest.json --dry-run --json
26518
- makefx import external-renders.json --space space_123
25995
+ makefx upload hero.png --name "Hero" --collection collection_cast
25996
+ makefx upload hero.png --name "Hero" --collection-name "Cast" --json
25997
+ makefx upload thumbnail.png --asset asset_thumb --manual-relation thumbnail_for:asset:asset_target
26519
25998
  makefx assets
26520
25999
  makefx usage --from 2026-06-01
26521
26000
  makefx spend --from 2026-06-01 --provider gemini
@@ -26592,7 +26071,7 @@ function printUploadHelp() {
26592
26071
  console.log(`
26593
26072
  Usage:
26594
26073
  makefx upload <file> --asset <id> [--space <id>] Upload media to existing asset
26595
- makefx upload <file> --name <name> [--space <id>] Create new asset
26074
+ makefx upload <file> --name <name> [--space <id>] Upload media as a new asset
26596
26075
 
26597
26076
  Options:
26598
26077
  --space <id> Target space ID; defaults from initialized project
@@ -26600,35 +26079,34 @@ Options:
26600
26079
  --name <name> New asset name (creates asset + variant)
26601
26080
  --type <type> Asset type for new assets (default: character)
26602
26081
  --media-kind <k> Optional explicit kind: image, audio, or video
26082
+ --prompt <text> Prompt provenance for uploaded media
26083
+ --model <model> Model provenance for uploaded media
26084
+ --provider <name> Provider provenance for uploaded media
26085
+ --provider-metadata <json> Provider metadata JSON object
26086
+ --generation-provenance <json> Extra provenance JSON object
26087
+ --source-variant <ids> Comma-separated source variants for upload lineage
26088
+ --source-variants <ids> Alias for --source-variant
26089
+ --relation-type <type> Lineage type: derived, refined, or forked (default: derived)
26090
+ --active-variant-behavior <b> if-missing, set-active, or keep
26091
+ --collection <ids> Comma-separated collection IDs for the uploaded asset or variant
26092
+ --collection-name <names> Comma-separated exact collection names for lookup
26093
+ --collection-role <role> Collection item role (default: member)
26094
+ --collection-subject <type> Collection subject: asset or variant (default: asset)
26095
+ --collection-pinned-variant <id|uploaded|none>
26096
+ Pin an asset collection item to the uploaded variant by default
26097
+ --manual-relation <spec> Comma-separated <type>:asset:<id> or <type>:variant:<id>
26098
+ --manual-relation-subject <type>
26099
+ Relation subject: uploaded asset or variant (default: variant)
26100
+ --manual-relation-label <text> Optional manual relation label
26101
+ --manual-relation-context <json|string>
26102
+ Optional manual relation context
26103
+ --manual-relation-metadata <json>
26104
+ Optional manual relation metadata object
26105
+ --json Print machine-readable upload output with Space IDs
26603
26106
  --env <env> Environment (production|stage|local)
26604
26107
  --local Shortcut for --env local
26605
26108
  `);
26606
26109
  }
26607
- function printImportHelp() {
26608
- console.log(`
26609
- Usage:
26610
- makefx import <manifest.json> [--space <id>]
26611
- makefx import <manifest.json> --dry-run [--json]
26612
-
26613
- Options:
26614
- --space <id> Target space ID; defaults from initialized project
26615
- --dry-run Validate manifest, files, targets, membership, lineage, and organization references
26616
- --json Print machine-readable import or dry-run output
26617
- --env <env> Environment (production|stage|local)
26618
- --local Shortcut for --env local
26619
-
26620
- Manifest:
26621
- Records import files with prompt, model, provider, providerMetadata, and
26622
- generationProvenance. Lineage entries record immutable source provenance using
26623
- sourceFile or sourceVariantId; use collections, relations, and compositions
26624
- for editable organization metadata. Style references are normal assets grouped
26625
- into collections and selected through style presets.
26626
-
26627
- Examples:
26628
- makefx import external-renders.json --dry-run --json
26629
- makefx import external-renders.json --space space_123
26630
- `);
26631
- }
26632
26110
  function printForgeHelp(command) {
26633
26111
  if (command === "generate") {
26634
26112
  console.log(`
@@ -27044,9 +26522,6 @@ async function dispatchCommand(command, parsed) {
27044
26522
  case "upload":
27045
26523
  await handleUpload(parsed);
27046
26524
  break;
27047
- case "import":
27048
- await handleImport(parsed);
27049
- break;
27050
26525
  case "generate":
27051
26526
  await handleGenerate(parsed);
27052
26527
  break;