makefx 0.2.0 → 0.2.1

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 +350 -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,97 @@ 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 sourceVariantId = 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
+ if (!sourceVariantId && (parsed.options["relation-type"] || parsed.options.relationType)) throw new UploadUsageError("--relation-type requires --source-variant");
5132
+ const providerMetadata = parseJsonObjectOption(rawProviderMetadata, "--provider-metadata");
5133
+ const generationProvenance = parseJsonObjectOption(rawGenerationProvenance, "--generation-provenance");
5134
+ const relationType = normalizeRelationType(rawRelationType);
5135
+ const activeVariantBehavior = normalizeActiveVariantBehavior(rawActiveVariantBehavior);
5136
+ const collectionPlacements = parseCollectionPlacements({
5137
+ collectionIds: rawCollectionIds,
5138
+ collectionNames: rawCollectionNames,
5139
+ role: rawCollectionRole,
5140
+ subjectType: rawCollectionSubject,
5141
+ pinnedVariant: rawCollectionPinnedVariant
5142
+ });
5143
+ const manualRelations = parseManualRelations({
5144
+ relations: rawManualRelation,
5145
+ subjectType: rawManualRelationSubject,
5146
+ label: rawManualRelationLabel,
5147
+ context: rawManualRelationContext,
5148
+ metadata: rawManualRelationMetadata
5149
+ });
5090
5150
  const mediaType = resolveMediaType(path.extname(filePath).toLowerCase(), requestedMediaKind);
5091
5151
  const config = await deps.loadConfig(env);
5092
5152
  if (!config) throw new Error(`Not logged in to ${env} environment. Run: ${loginCommandForEnvironment(env)}`);
5093
5153
  if (config.token.expiresAt < Date.now()) throw new Error(`Token expired for ${env} environment. Run: ${loginCommandForEnvironment(env)}`);
5094
5154
  const baseUrl = deps.resolveBaseUrl(env);
5095
5155
  const accessToken = config.token.accessToken;
5156
+ const ctx = {
5157
+ spaceId,
5158
+ baseUrl,
5159
+ accessToken
5160
+ };
5096
5161
  if (env === "local") process$1.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
5097
5162
  try {
5098
5163
  const fileStat = await deps.stat(filePath);
5099
5164
  if (!fileStat.isFile()) throw new Error(`"${filePath}" is not a file`);
5100
5165
  if (fileStat.size > 10485760) throw new Error(`File too large (${(fileStat.size / 1024 / 1024).toFixed(2)}MB). Maximum size: 10MB`);
5166
+ await preflightUploadOrganization(ctx, deps, {
5167
+ assetId,
5168
+ sourceVariantId,
5169
+ collectionPlacements,
5170
+ manualRelations
5171
+ });
5101
5172
  const fileBuffer = await deps.readFile(filePath);
5102
5173
  const fileName = path.basename(filePath);
5103
5174
  const formData = new FormData();
5104
5175
  const blob = new Blob([fileBuffer], { type: mediaType.mimeType });
5105
5176
  formData.append("file", blob, fileName);
5106
5177
  formData.append("mediaKind", mediaType.mediaKind);
5178
+ formData.append("activeVariantBehavior", activeVariantBehavior);
5107
5179
  if (assetId) formData.append("assetId", assetId);
5108
5180
  else {
5109
5181
  formData.append("assetName", assetName);
5110
5182
  formData.append("assetType", assetType);
5111
5183
  }
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})`);
5184
+ if (prompt) formData.append("prompt", prompt);
5185
+ if (model) formData.append("model", model);
5186
+ if (provider) formData.append("provider", provider);
5187
+ if (providerMetadata) formData.append("providerMetadata", JSON.stringify(providerMetadata));
5188
+ if (generationProvenance) formData.append("generationProvenance", JSON.stringify(generationProvenance));
5189
+ if (sourceVariantId) formData.append("lineage", JSON.stringify([{
5190
+ parentVariantId: sourceVariantId,
5191
+ relationType
5192
+ }]));
5193
+ if (!jsonOutput) {
5194
+ deps.print(`\nUploading "${fileName}" to space ${spaceId}...`);
5195
+ deps.print(` Media kind: ${mediaType.mediaKind}`);
5196
+ if (assetId) deps.print(` Target asset: ${assetId}`);
5197
+ else deps.print(` Creating asset: "${assetName}" (${assetType})`);
5198
+ if (sourceVariantId) deps.print(` Source variant: ${sourceVariantId} (${relationType})`);
5199
+ }
5116
5200
  const response = await deps.fetch(`${baseUrl}/api/spaces/${spaceId}/upload`, {
5117
5201
  method: "POST",
5118
5202
  headers: { "Authorization": `Bearer ${accessToken}` },
@@ -5121,6 +5205,29 @@ async function executeUpload(parsed, deps = defaultDeps$12) {
5121
5205
  const data = await response.json();
5122
5206
  if (!response.ok) throw new Error(`Upload failed: ${"error" in data ? data.error : response.statusText}`);
5123
5207
  const upload = data;
5208
+ let organization = {
5209
+ collectionItemIds: [],
5210
+ relationIds: []
5211
+ };
5212
+ try {
5213
+ organization = await applyUploadOrganization(ctx, deps, upload, {
5214
+ collectionPlacements,
5215
+ manualRelations
5216
+ });
5217
+ } catch (error) {
5218
+ const message = error instanceof Error ? error.message : String(error);
5219
+ throw new Error(`Upload created asset ${upload.variant.asset_id} and variant ${upload.variant.id}, but organization failed: ${message}`);
5220
+ }
5221
+ const result = {
5222
+ asset: upload.asset,
5223
+ variant: upload.variant,
5224
+ lineage: upload.lineage,
5225
+ organization
5226
+ };
5227
+ if (jsonOutput) {
5228
+ deps.print(JSON.stringify(result, null, 2));
5229
+ return result;
5230
+ }
5124
5231
  deps.print("\nUpload successful!\n");
5125
5232
  if (upload.asset) {
5126
5233
  deps.print("New Asset:");
@@ -5138,6 +5245,11 @@ async function executeUpload(parsed, deps = defaultDeps$12) {
5138
5245
  if (upload.variant.image_key) deps.print(` Image: ${upload.variant.image_key}`);
5139
5246
  if (upload.variant.thumb_key) deps.print(` Thumb: ${upload.variant.thumb_key}`);
5140
5247
  if (upload.variant.media_mime_type) deps.print(` MIME: ${upload.variant.media_mime_type}`);
5248
+ if (organization.collectionItemIds.length > 0 || organization.relationIds.length > 0) {
5249
+ deps.print("\nOrganization:");
5250
+ if (organization.collectionItemIds.length > 0) deps.print(` Collection items: ${organization.collectionItemIds.join(", ")}`);
5251
+ if (organization.relationIds.length > 0) deps.print(` Relations: ${organization.relationIds.join(", ")}`);
5252
+ }
5141
5253
  if (upload.variant.recipe) try {
5142
5254
  const recipe = JSON.parse(upload.variant.recipe);
5143
5255
  deps.print("\nRecipe:");
@@ -5147,429 +5259,45 @@ async function executeUpload(parsed, deps = defaultDeps$12) {
5147
5259
  } catch {}
5148
5260
  deps.print("\nTo inspect:");
5149
5261
  deps.print(` makefx assets show ${upload.variant.asset_id} --space ${spaceId}`);
5150
- return {
5151
- asset: upload.asset,
5152
- variant: upload.variant
5153
- };
5262
+ return result;
5154
5263
  } catch (error) {
5155
5264
  if (error.code === "ENOENT") throw new Error(`File not found: ${filePath}`);
5156
5265
  throw error;
5157
5266
  }
5158
5267
  }
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));
5268
+ function parseJsonObjectOption(value, optionName) {
5269
+ if (value === void 0) return void 0;
5297
5270
  let parsed;
5298
5271
  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);
5272
+ parsed = JSON.parse(value);
5273
+ } catch {
5274
+ throw new UploadUsageError(`${optionName} must be valid JSON`);
5311
5275
  }
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
- });
5276
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new UploadUsageError(`${optionName} must be a JSON object`);
5277
+ return parsed;
5507
5278
  }
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
- });
5279
+ function parseJsonObjectOrStringOption(value, optionName) {
5280
+ if (value === void 0) return void 0;
5281
+ if (!value.trim().startsWith("{")) return value;
5282
+ return parseJsonObjectOption(value, optionName);
5529
5283
  }
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
- });
5284
+ function normalizeRelationType(value) {
5285
+ if (!value) return "derived";
5286
+ if (value === "derived" || value === "refined" || value === "forked") return value;
5287
+ throw new UploadUsageError("--relation-type must be derived, refined, or forked");
5555
5288
  }
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
- };
5289
+ function normalizeActiveVariantBehavior(value) {
5290
+ if (!value || value === "if-missing" || value === "if_missing") return "if-missing";
5291
+ if (value === "set-active" || value === "set_active") return "set-active";
5292
+ if (value === "keep") return "keep";
5293
+ throw new UploadUsageError("--active-variant-behavior must be if-missing, set-active, or keep");
5567
5294
  }
5568
- function normalizeSubjectType(value, label) {
5295
+ function normalizeSpaceSubjectType(value, optionName, defaultValue) {
5296
+ if (!value) return defaultValue;
5569
5297
  if (value === "asset" || value === "variant") return value;
5570
- throw new Error(`${label} must be asset or variant`);
5298
+ throw new UploadUsageError(`${optionName} must be asset or variant`);
5571
5299
  }
5572
- function normalizeRelationType(value, label) {
5300
+ function normalizeManualRelationType(value, optionName) {
5573
5301
  if ([
5574
5302
  "appears_in",
5575
5303
  "background_for",
@@ -5582,425 +5310,156 @@ function normalizeRelationType(value, label) {
5582
5310
  "reference_for",
5583
5311
  "custom"
5584
5312
  ].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`);
5313
+ throw new UploadUsageError(`${optionName} relation type is invalid`);
5600
5314
  }
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`);
5315
+ function parseCsvOption$1(value, optionName) {
5316
+ if (!value) return [];
5317
+ if (value === "true") throw new UploadUsageError(`${optionName} requires a value`);
5318
+ return value.split(",").map((entry) => entry.trim()).filter(Boolean);
5610
5319
  }
5611
- function requireObject(value, label) {
5612
- if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error(`${label} must be an object`);
5320
+ function parseCollectionPlacements(input) {
5321
+ const collectionIds = parseCsvOption$1(input.collectionIds, "--collection");
5322
+ const collectionNames = parseCsvOption$1(input.collectionNames, "--collection-name");
5323
+ if (collectionIds.length === 0 && collectionNames.length === 0) return [];
5324
+ const subjectType = normalizeSpaceSubjectType(input.subjectType, "--collection-subject", "asset");
5325
+ if (subjectType === "variant" && input.pinnedVariant && input.pinnedVariant !== "true") throw new UploadUsageError("--collection-pinned-variant can only be used when --collection-subject is asset");
5326
+ const pinnedVariantId = normalizeCollectionPinnedVariant(input.pinnedVariant);
5327
+ const placement = {
5328
+ role: input.role && input.role !== "true" ? input.role : "member",
5329
+ subjectType,
5330
+ pinnedVariantId
5331
+ };
5332
+ return [...collectionIds.map((collectionId) => ({
5333
+ ...placement,
5334
+ collectionId
5335
+ })), ...collectionNames.map((collectionName) => ({
5336
+ ...placement,
5337
+ collectionName
5338
+ }))];
5339
+ }
5340
+ function normalizeCollectionPinnedVariant(value) {
5341
+ if (!value || value === "true" || value === "uploaded") return void 0;
5342
+ if (value === "none" || value === "unpin") return null;
5613
5343
  return value;
5614
5344
  }
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
- }
5345
+ function parseManualRelations(input) {
5346
+ const relationSpecs = parseCsvOption$1(input.relations, "--manual-relation");
5347
+ if (relationSpecs.length === 0) return [];
5348
+ const subjectType = normalizeSpaceSubjectType(input.subjectType, "--manual-relation-subject", "variant");
5349
+ const context = parseJsonObjectOrStringOption(input.context, "--manual-relation-context");
5350
+ const metadata = parseJsonObjectOption(input.metadata, "--manual-relation-metadata");
5351
+ return relationSpecs.map((spec) => {
5352
+ const parts = spec.split(":");
5353
+ if (parts.length !== 3) throw new UploadUsageError("--manual-relation entries must use <relation-type>:asset:<asset-id> or <relation-type>:variant:<variant-id>");
5354
+ const [relationTypeValue, objectTypeValue, objectId] = parts;
5355
+ const objectType = normalizeSpaceSubjectType(objectTypeValue, "--manual-relation object type", "asset");
5356
+ if (!objectId) throw new UploadUsageError("--manual-relation object id is required");
5357
+ return {
5358
+ relationType: normalizeManualRelationType(relationTypeValue, "--manual-relation"),
5359
+ object: objectType === "asset" ? {
5360
+ subjectType: "asset",
5361
+ assetId: objectId
5362
+ } : {
5363
+ subjectType: "variant",
5364
+ variantId: objectId
5365
+ },
5366
+ subjectType,
5367
+ label: input.label && input.label !== "true" ? input.label : void 0,
5368
+ context,
5369
+ metadata
5370
+ };
5371
+ });
5643
5372
  }
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 ?? [];
5373
+ async function preflightUploadOrganization(ctx, deps, options) {
5374
+ const needsCollections = options.collectionPlacements.length > 0;
5375
+ const needsAssetState = Boolean(options.sourceVariantId) || options.manualRelations.length > 0 || options.collectionPlacements.some((placement) => placement.pinnedVariantId);
5376
+ const [collections, state] = await Promise.all([needsCollections ? fetchJson$1(ctx, deps, `/api/spaces/${ctx.spaceId}/collections`) : void 0, needsAssetState ? fetchAssetState(ctx, deps) : void 0]);
5377
+ if (collections) {
5378
+ const collectionList = collections.collections ?? [];
5379
+ const collectionIds = new Set(collectionList.map((collection) => collection.id));
5380
+ for (const placement of options.collectionPlacements) {
5381
+ if (placement.collectionId && !collectionIds.has(placement.collectionId)) throw new Error(`Collection not found: ${placement.collectionId}`);
5382
+ if (!placement.collectionName) continue;
5383
+ const matches = collectionList.filter((collection) => collection.name === placement.collectionName);
5384
+ if (matches.length === 0) throw new Error(`Collection not found by exact name: ${placement.collectionName}`);
5385
+ if (matches.length > 1) throw new Error(`Collection name is ambiguous: ${placement.collectionName}; use --collection <id>`);
5386
+ placement.collectionId = matches[0].id;
5387
+ }
5388
+ }
5389
+ if (!state) return;
5390
+ if (options.sourceVariantId && !state.variantsById.has(options.sourceVariantId)) throw new Error(`Source variant not found in space: ${options.sourceVariantId}`);
5391
+ for (const placement of options.collectionPlacements) {
5392
+ if (!placement.pinnedVariantId) continue;
5393
+ const pinned = state.variantsById.get(placement.pinnedVariantId);
5394
+ if (!pinned) throw new Error(`Collection pinned variant not found: ${placement.pinnedVariantId}`);
5395
+ if (placement.subjectType === "asset") {
5396
+ if (!options.assetId) throw new Error("--collection-pinned-variant cannot reference an existing variant when creating a new asset");
5397
+ if (pinned.asset_id !== options.assetId) throw new Error("--collection-pinned-variant must reference a variant on the target asset");
5398
+ }
5399
+ }
5400
+ for (const relation of options.manualRelations) if (relation.object.subjectType === "asset") {
5401
+ if (!relation.object.assetId || !state.assetsById.has(relation.object.assetId)) throw new Error(`Manual relation object asset not found: ${relation.object.assetId ?? ""}`);
5402
+ } else if (!relation.object.variantId || !state.variantsById.has(relation.object.variantId)) throw new Error(`Manual relation object variant not found: ${relation.object.variantId ?? ""}`);
5403
+ }
5404
+ async function fetchAssetState(ctx, deps) {
5405
+ const assets = (await fetchJson$1(ctx, deps, `/api/spaces/${ctx.spaceId}/assets`)).assets ?? [];
5652
5406
  const variantsById = /* @__PURE__ */ new Map();
5653
- for (const asset of assets) {
5407
+ await Promise.all(assets.map(async (asset) => {
5654
5408
  const details = await fetchJson$1(ctx, deps, `/api/spaces/${ctx.spaceId}/assets/${encodeURIComponent(asset.id)}`);
5655
5409
  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
5410
  }));
5411
+ return {
5412
+ assetsById: new Map(assets.map((asset) => [asset.id, asset])),
5413
+ variantsById
5414
+ };
5782
5415
  }
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 = [];
5416
+ async function applyUploadOrganization(ctx, deps, upload, options) {
5855
5417
  const collectionItemIds = [];
5856
5418
  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));
5419
+ const uploadedSubject = {
5420
+ assetId: upload.variant.asset_id,
5421
+ variantId: upload.variant.id
5422
+ };
5423
+ for (const placement of options.collectionPlacements) {
5424
+ if (!placement.collectionId) throw new Error(`Collection name was not resolved: ${placement.collectionName ?? ""}`);
5425
+ const body = placement.subjectType === "asset" ? {
5426
+ subjectType: "asset",
5427
+ assetId: uploadedSubject.assetId,
5428
+ role: placement.role,
5429
+ pinnedVariantId: placement.pinnedVariantId === void 0 ? uploadedSubject.variantId : placement.pinnedVariantId
5430
+ } : {
5431
+ subjectType: "variant",
5432
+ variantId: uploadedSubject.variantId,
5433
+ role: placement.role
5434
+ };
5435
+ const created = await postJson(ctx, deps, `/api/spaces/${ctx.spaceId}/collections/${encodeURIComponent(placement.collectionId)}/items`, body);
5875
5436
  collectionItemIds.push(created.item.id);
5876
5437
  }
5877
- for (const relation of plan.relations) {
5438
+ for (const relation of options.manualRelations) {
5878
5439
  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),
5440
+ subject: relation.subjectType === "asset" ? {
5441
+ subjectType: "asset",
5442
+ assetId: uploadedSubject.assetId
5443
+ } : {
5444
+ subjectType: "variant",
5445
+ variantId: uploadedSubject.variantId
5446
+ },
5447
+ object: relation.object,
5882
5448
  relationType: relation.relationType,
5883
5449
  label: relation.label,
5884
5450
  context: relation.context,
5885
- metadata: relation.metadata,
5886
- sortIndex: relation.sortIndex
5451
+ metadata: relation.metadata
5887
5452
  });
5888
5453
  relationIds.push(created.relation.id);
5889
5454
  }
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
5455
  return {
5941
- collectionIds,
5942
5456
  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
5457
+ relationIds
5993
5458
  };
5994
5459
  }
5995
5460
  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
5461
  const response = await deps.fetch(`${ctx.baseUrl}${requestPath}`, {
6003
- method,
5462
+ method: "POST",
6004
5463
  headers: {
6005
5464
  Authorization: `Bearer ${ctx.accessToken}`,
6006
5465
  Accept: "application/json",
@@ -6012,9 +5471,6 @@ async function sendJson(ctx, deps, method, requestPath, json) {
6012
5471
  if (!response.ok) throw new Error("error" in data ? data.error : `Request failed: ${response.status}`);
6013
5472
  return data;
6014
5473
  }
6015
- function stripUndefined(value) {
6016
- return Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== void 0));
6017
- }
6018
5474
  async function fetchJson$1(ctx, deps, requestPath) {
6019
5475
  const response = await deps.fetch(`${ctx.baseUrl}${requestPath}`, { headers: {
6020
5476
  Authorization: `Bearer ${ctx.accessToken}`,
@@ -6024,40 +5480,58 @@ async function fetchJson$1(ctx, deps, requestPath) {
6024
5480
  if (!response.ok) throw new Error("error" in data ? data.error : `Request failed: ${response.status}`);
6025
5481
  return data;
6026
5482
  }
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}`);
5483
+ function stripUndefined(value) {
5484
+ return Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== void 0));
6042
5485
  }
6043
5486
  function printUsage$11() {
6044
5487
  console.log(`
6045
5488
  Usage:
6046
- makefx import <manifest.json> [--space <id>]
6047
- makefx import <manifest.json> --dry-run [--json]
5489
+ makefx upload <file> --asset <id> [--space <id>] Upload media to existing asset
5490
+ makefx upload <file> --name <name> [--space <id>] Upload media as a new asset
5491
+
5492
+ Options:
5493
+ --space <id> Target space ID; defaults from initialized project
5494
+ --asset <id> Target asset ID (upload as new variant)
5495
+ --name <name> New asset name (creates asset + variant)
5496
+ --type <type> Asset type for new assets (default: character)
5497
+ --media-kind <k> Optional explicit kind: image, audio, or video
5498
+ --prompt <text> Prompt provenance for uploaded media
5499
+ --model <model> Model provenance for uploaded media
5500
+ --provider <name> Provider provenance for uploaded media
5501
+ --provider-metadata <json> Provider metadata JSON object
5502
+ --generation-provenance <json> Extra provenance JSON object
5503
+ --source-variant <id> Existing source variant for upload lineage
5504
+ --relation-type <type> Lineage type: derived, refined, or forked (default: derived)
5505
+ --active-variant-behavior <b> if-missing, set-active, or keep
5506
+ --collection <ids> Comma-separated collection IDs for the uploaded asset or variant
5507
+ --collection-name <names> Comma-separated exact collection names for lookup
5508
+ --collection-role <role> Collection item role (default: member)
5509
+ --collection-subject <type> Collection subject: asset or variant (default: asset)
5510
+ --collection-pinned-variant <id|uploaded|none>
5511
+ Pin an asset collection item to the uploaded variant by default
5512
+ --manual-relation <spec> Comma-separated <type>:asset:<id> or <type>:variant:<id>
5513
+ --manual-relation-subject <type>
5514
+ Relation subject: uploaded asset or variant (default: variant)
5515
+ --manual-relation-label <text> Optional manual relation label
5516
+ --manual-relation-context <json|string>
5517
+ Optional manual relation context
5518
+ --manual-relation-metadata <json>
5519
+ Optional manual relation metadata object
5520
+ --json Print machine-readable upload output with Space IDs
5521
+ --env <env> Environment (production|stage|local)
5522
+ --local Shortcut for --env local
6048
5523
 
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.
5524
+ Examples:
5525
+ makefx upload hero.png --space abc123 --name "Hero Character"
5526
+ makefx upload hero.png --space abc123 --name "Hero" --prompt "external render" --provider blender
5527
+ makefx upload paintover.png --space abc123 --asset def456 --source-variant var123 --relation-type refined
5528
+ makefx upload hero.png --space abc123 --name "Hero" --collection collection_cast --collection-role character
5529
+ makefx upload hero.png --space abc123 --name "Hero" --collection-name "Cast" --collection-role character
5530
+ makefx upload thumbnail.png --space abc123 --asset asset_thumb --manual-relation thumbnail_for:asset:asset_target
5531
+ makefx upload theme.mp3 --space abc123 --name "Theme Music" --type audio
5532
+ makefx upload cutscene.mp4 --space abc123 --name "Opening Cutscene" --type video
5533
+ makefx upload variant.jpg --space abc123 --asset def456
5534
+ makefx upload hero.png --space abc123 --name "Hero" --json
6061
5535
  `);
6062
5536
  }
6063
5537
  //#endregion
@@ -26243,7 +25717,7 @@ Options:
26243
25717
  }
26244
25718
  //#endregion
26245
25719
  //#region src/cli/index.ts
26246
- var CLI_VERSION = "0.2.0+a98a786097bf";
25720
+ var CLI_VERSION = "0.2.1+2f99bf2b725b";
26247
25721
  async function main() {
26248
25722
  const argv = process$1.argv.slice(2);
26249
25723
  if (argv[0] === "--") argv.shift();
@@ -26352,9 +25826,6 @@ function printCommandHelp(command, positionals) {
26352
25826
  case "upload":
26353
25827
  printUploadHelp();
26354
25828
  return;
26355
- case "import":
26356
- printImportHelp();
26357
- return;
26358
25829
  case "generate":
26359
25830
  case "refine":
26360
25831
  case "derive":
@@ -26471,8 +25942,10 @@ Listen:
26471
25942
 
26472
25943
  Upload:
26473
25944
  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
25945
+ upload <file> --name <name> [--space <id>] Upload image, audio, or video as a new asset
25946
+ upload <file> --collection <id> Place uploaded media in a collection
25947
+ upload <file> --collection-name <name> Place uploaded media in an exact-name collection
25948
+ upload <file> --manual-relation <spec> Create a manual relation from uploaded media
26476
25949
 
26477
25950
  Forge:
26478
25951
  generate "prompt" --name <name> --type <type> -o <file>
@@ -26514,8 +25987,9 @@ Examples:
26514
25987
  makefx audio sfx generate "A short brass victory sting" --name "Victory Sting" -o victory.wav
26515
25988
  makefx video generate "A looping idle animation" --name "Idle Animation" --type animation --duration 6 --resolution 1080p --tier fast -o idle.mp4
26516
25989
  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
25990
+ makefx upload hero.png --name "Hero" --collection collection_cast
25991
+ makefx upload hero.png --name "Hero" --collection-name "Cast" --json
25992
+ makefx upload thumbnail.png --asset asset_thumb --manual-relation thumbnail_for:asset:asset_target
26519
25993
  makefx assets
26520
25994
  makefx usage --from 2026-06-01
26521
25995
  makefx spend --from 2026-06-01 --provider gemini
@@ -26592,7 +26066,7 @@ function printUploadHelp() {
26592
26066
  console.log(`
26593
26067
  Usage:
26594
26068
  makefx upload <file> --asset <id> [--space <id>] Upload media to existing asset
26595
- makefx upload <file> --name <name> [--space <id>] Create new asset
26069
+ makefx upload <file> --name <name> [--space <id>] Upload media as a new asset
26596
26070
 
26597
26071
  Options:
26598
26072
  --space <id> Target space ID; defaults from initialized project
@@ -26600,35 +26074,33 @@ Options:
26600
26074
  --name <name> New asset name (creates asset + variant)
26601
26075
  --type <type> Asset type for new assets (default: character)
26602
26076
  --media-kind <k> Optional explicit kind: image, audio, or video
26077
+ --prompt <text> Prompt provenance for uploaded media
26078
+ --model <model> Model provenance for uploaded media
26079
+ --provider <name> Provider provenance for uploaded media
26080
+ --provider-metadata <json> Provider metadata JSON object
26081
+ --generation-provenance <json> Extra provenance JSON object
26082
+ --source-variant <id> Existing source variant for upload lineage
26083
+ --relation-type <type> Lineage type: derived, refined, or forked (default: derived)
26084
+ --active-variant-behavior <b> if-missing, set-active, or keep
26085
+ --collection <ids> Comma-separated collection IDs for the uploaded asset or variant
26086
+ --collection-name <names> Comma-separated exact collection names for lookup
26087
+ --collection-role <role> Collection item role (default: member)
26088
+ --collection-subject <type> Collection subject: asset or variant (default: asset)
26089
+ --collection-pinned-variant <id|uploaded|none>
26090
+ Pin an asset collection item to the uploaded variant by default
26091
+ --manual-relation <spec> Comma-separated <type>:asset:<id> or <type>:variant:<id>
26092
+ --manual-relation-subject <type>
26093
+ Relation subject: uploaded asset or variant (default: variant)
26094
+ --manual-relation-label <text> Optional manual relation label
26095
+ --manual-relation-context <json|string>
26096
+ Optional manual relation context
26097
+ --manual-relation-metadata <json>
26098
+ Optional manual relation metadata object
26099
+ --json Print machine-readable upload output with Space IDs
26603
26100
  --env <env> Environment (production|stage|local)
26604
26101
  --local Shortcut for --env local
26605
26102
  `);
26606
26103
  }
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
26104
  function printForgeHelp(command) {
26633
26105
  if (command === "generate") {
26634
26106
  console.log(`
@@ -27044,9 +26516,6 @@ async function dispatchCommand(command, parsed) {
27044
26516
  case "upload":
27045
26517
  await handleUpload(parsed);
27046
26518
  break;
27047
- case "import":
27048
- await handleImport(parsed);
27049
- break;
27050
26519
  case "generate":
27051
26520
  await handleGenerate(parsed);
27052
26521
  break;