makefx 1.0.3 → 1.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +1 -1
  2. package/makefx.mjs +182 -38
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -176,4 +176,4 @@ Run `makefx --help` or `makefx help <command>` for command-specific options.
176
176
  - Media generation guide: https://makefx.app/docs/media-playbooks
177
177
  - Source: https://github.com/krasnoperov/inventory
178
178
 
179
- Version: 1.0.3
179
+ Version: 1.0.5
package/makefx.mjs CHANGED
@@ -7,7 +7,9 @@ import { spawn } from "node:child_process";
7
7
  import { mkdir, readFile, readdir, rename, rm, stat, writeFile } from "node:fs/promises";
8
8
  import os from "node:os";
9
9
  import path from "node:path";
10
- import { createReadStream } from "node:fs";
10
+ import { createReadStream, createWriteStream } from "node:fs";
11
+ import { Readable } from "node:stream";
12
+ import { pipeline } from "node:stream/promises";
11
13
  import https from "node:https";
12
14
  //#region \0rolldown/runtime.js
13
15
  var __create = Object.create;
@@ -3215,7 +3217,7 @@ var require_websocket = /* @__PURE__ */ __commonJSMin(((exports, module) => {
3215
3217
  var net = __require("net");
3216
3218
  var tls = __require("tls");
3217
3219
  var { randomBytes: randomBytes$1, createHash: createHash$2 } = __require("crypto");
3218
- var { Duplex: Duplex$2, Readable } = __require("stream");
3220
+ var { Duplex: Duplex$2, Readable: Readable$1 } = __require("stream");
3219
3221
  var { URL: URL$1 } = __require("url");
3220
3222
  var PerMessageDeflate = require_permessage_deflate();
3221
3223
  var Receiver = require_receiver();
@@ -5873,11 +5875,40 @@ async function downloadFile(input) {
5873
5875
  }
5874
5876
  const headers = {};
5875
5877
  if (input.accessToken) headers.Authorization = `Bearer ${input.accessToken}`;
5876
- const response = await fetch(`${input.baseUrl}${input.requestPath}`, { headers });
5877
- if (!response.ok) throw new Error(`Failed to download ${input.requestPath}: ${response.status} ${response.statusText}`);
5878
- const buffer = new Uint8Array(await response.arrayBuffer());
5878
+ const requestUrl = `${input.baseUrl}${input.requestPath}`;
5879
+ let response;
5880
+ try {
5881
+ response = await fetch(requestUrl, { headers });
5882
+ } catch (error) {
5883
+ throw new Error(`Failed to download ${input.requestPath}: ${formatFetchError(error)}`);
5884
+ }
5885
+ if (!response.ok) {
5886
+ const errorText = await readErrorText(response);
5887
+ throw new Error(`Failed to download ${input.requestPath}: ${response.status} ${response.statusText}${errorText ? ` - ${errorText}` : ""}`);
5888
+ }
5879
5889
  await mkdir(path.dirname(input.outputPath), { recursive: true });
5880
- await writeFile(input.outputPath, buffer);
5890
+ if (!response.body) throw new Error(`Failed to download ${input.requestPath}: response body was empty`);
5891
+ try {
5892
+ await pipeline(Readable.fromWeb(response.body), createWriteStream(input.outputPath));
5893
+ } catch (error) {
5894
+ throw new Error(`Failed to write download ${input.requestPath} to ${input.outputPath}: ${formatFetchError(error)}`);
5895
+ }
5896
+ return {
5897
+ headers: response.headers,
5898
+ requestUrl
5899
+ };
5900
+ }
5901
+ async function readErrorText(response) {
5902
+ try {
5903
+ return (await response.text()).slice(0, 500).trim();
5904
+ } catch {
5905
+ return "";
5906
+ }
5907
+ }
5908
+ function formatFetchError(error) {
5909
+ if (!(error instanceof Error)) return String(error);
5910
+ const cause = error.cause instanceof Error ? ` (${error.cause.message})` : "";
5911
+ return `${error.message}${cause}`;
5881
5912
  }
5882
5913
  async function downloadImage(input) {
5883
5914
  await downloadFile({
@@ -7235,8 +7266,8 @@ function validateAudioModeRequiredVoiceOptions(options, mode) {
7235
7266
  async function executeGenerate(parsed, ctx, client, deps, mediaKind, followOptions = {}) {
7236
7267
  const prompt = getPrompt(parsed, "generate");
7237
7268
  const outputPath = getOutputPath$1(parsed);
7238
- const name = requireOption(parsed, "name");
7239
- const assetType = requireOption(parsed, "type");
7269
+ const name = requireOption$1(parsed, "name");
7270
+ const assetType = requireOption$1(parsed, "type");
7240
7271
  const musicProvider = parseMusicProviderOption(parsed, mediaKind, assetType);
7241
7272
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
7242
7273
  const videoFrameRefs = parseVideoFrameReferenceOptions(parsed, "generate", mediaKind).refs;
@@ -7331,7 +7362,7 @@ function parseMusicProviderOption(parsed, mediaKind, assetType) {
7331
7362
  async function executeRefine(parsed, ctx, client, deps, mediaKind) {
7332
7363
  const prompt = getPrompt(parsed, "refine");
7333
7364
  const outputPath = getOutputPath$1(parsed);
7334
- const sourceVariantId = requireOption(parsed, "variant");
7365
+ const sourceVariantId = requireOption$1(parsed, "variant");
7335
7366
  const state = await requestSpaceState(client);
7336
7367
  const sourceVariant = state.variants.find((variant) => variant.id === sourceVariantId);
7337
7368
  if (!sourceVariant) throw new Error(`Variant not found in space sync state: ${sourceVariantId}`);
@@ -7400,10 +7431,10 @@ async function executeRefine(parsed, ctx, client, deps, mediaKind) {
7400
7431
  async function executeDerive(parsed, ctx, client, deps, mediaKind, followOptions = {}) {
7401
7432
  const prompt = getPrompt(parsed, "derive");
7402
7433
  const outputPath = getOutputPath$1(parsed);
7403
- const name = requireOption(parsed, "name");
7404
- const assetType = requireOption(parsed, "type");
7434
+ const name = requireOption$1(parsed, "name");
7435
+ const assetType = requireOption$1(parsed, "type");
7405
7436
  const videoFrameRefs = parseVideoFrameReferenceOptions(parsed, "derive", mediaKind).refs;
7406
- const refs = parsed.options.refs ? parseRefs(requireOption(parsed, "refs")) : [];
7437
+ const refs = parsed.options.refs ? parseRefs(requireOption$1(parsed, "refs")) : [];
7407
7438
  const referenceRefs = videoFrameRefs.length > 0 ? videoFrameRefs : refs;
7408
7439
  if (referenceRefs.length === 0) throw new Error("--refs is required");
7409
7440
  const imageOptions = parseImageGenerationOptions(parsed, mediaKind);
@@ -7614,10 +7645,10 @@ function timestampToIso(timestamp) {
7614
7645
  async function executeBatch(parsed, ctx, client, deps, mediaKind, saveBatchManifest) {
7615
7646
  const prompt = getPrompt(parsed, "batch");
7616
7647
  const outputDir = getOutputDir(parsed);
7617
- const name = requireOption(parsed, "name");
7618
- const assetType = requireOption(parsed, "type");
7648
+ const name = requireOption$1(parsed, "name");
7649
+ const assetType = requireOption$1(parsed, "type");
7619
7650
  const musicProvider = parseMusicProviderOption(parsed, mediaKind, assetType);
7620
- const count = parseBatchCount(requireOption(parsed, "count"));
7651
+ const count = parseBatchCount(requireOption$1(parsed, "count"));
7621
7652
  const mode = parseBatchMode(parsed.options.mode || "explore");
7622
7653
  const refs = parsed.options.refs ? parseRefs(parsed.options.refs) : [];
7623
7654
  const imageOptions = parseImageGenerationOptions(parsed, mediaKind);
@@ -8205,7 +8236,7 @@ function getOutputExtension(variant, mediaKind) {
8205
8236
  function isImageManifestMedia(media) {
8206
8237
  return media.mediaKind === "image" && Boolean(media.imageKey);
8207
8238
  }
8208
- function requireOption(parsed, name) {
8239
+ function requireOption$1(parsed, name) {
8209
8240
  const value = parsed.options[name];
8210
8241
  if (!value || value === "true") throw new Error(`--${name} is required`);
8211
8242
  return value;
@@ -8651,8 +8682,10 @@ async function handleAssets(parsed) {
8651
8682
  }
8652
8683
  }
8653
8684
  async function executeAssets(parsed, deps = defaultDeps$3) {
8654
- const ctx = await buildContext$3(parsed, deps);
8655
8685
  const subcommand = parsed.positionals[0] || "list";
8686
+ if (subcommand === "search") return searchAssets(parsed, deps);
8687
+ if (subcommand === "clone") return cloneAsset(parsed, deps);
8688
+ const ctx = await buildContext$3(parsed, deps);
8656
8689
  if (subcommand === "list") {
8657
8690
  const assets = await listAssets(ctx, deps);
8658
8691
  if (parsed.options.json === "true") deps.print(JSON.stringify(assets.map(toAssetJson), null, 2));
@@ -8677,24 +8710,24 @@ async function executeAssets(parsed, deps = defaultDeps$3) {
8677
8710
  const ref = parsed.positionals[1];
8678
8711
  if (!ref) throw new Error("Variant ID or legacy image key is required: makefx assets download <variant-id-or-legacy-image-key> -o <file>");
8679
8712
  const outputPath = getOutputPath(parsed);
8680
- const resolved = await resolveDownloadRef(ref, ctx, deps);
8681
- await deps.downloadFile({
8713
+ const resolved = resolveDownloadRef(ref, ctx);
8714
+ const metadata = getDownloadedVariantMetadata(resolved, await deps.downloadFile({
8682
8715
  baseUrl: ctx.baseUrl,
8683
8716
  accessToken: ctx.accessToken,
8684
8717
  requestPath: resolved.requestPath,
8685
8718
  outputPath,
8686
8719
  force: ctx.force
8687
- });
8720
+ }));
8688
8721
  await recordDownloadedMirror({
8689
8722
  ctx,
8690
8723
  deps,
8691
8724
  outputPath,
8692
- variant: resolved.variant
8725
+ variant: metadata.mirrorVariant
8693
8726
  });
8694
- deps.print(`Downloaded ${resolved.mediaKey} to ${outputPath}`);
8727
+ deps.print(`Downloaded ${metadata.mediaKey} to ${outputPath}`);
8695
8728
  return {
8696
8729
  type: "download",
8697
- mediaKey: resolved.mediaKey,
8730
+ mediaKey: metadata.mediaKey,
8698
8731
  outputPath,
8699
8732
  variant: resolved.variant
8700
8733
  };
@@ -8754,6 +8787,85 @@ async function buildContext$3(parsed, deps) {
8754
8787
  projectRoot: projectConfig?.projectRoot
8755
8788
  };
8756
8789
  }
8790
+ async function buildAuthContext(parsed, deps) {
8791
+ const env = resolveCommandEnvironment(parsed, await deps.loadProjectConfig());
8792
+ const config = await deps.loadConfig(env);
8793
+ if (!config) throw new Error(`Not logged in to ${env} environment. Run: ${loginCommandForEnvironment(env)}`);
8794
+ if (config.token.expiresAt < Date.now()) throw new Error(`Token expired for ${env} environment. Run: ${loginCommandForEnvironment(env)}`);
8795
+ if (env === "local") process$1.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
8796
+ return {
8797
+ env,
8798
+ baseUrl: deps.resolveBaseUrl(env),
8799
+ accessToken: config.token.accessToken
8800
+ };
8801
+ }
8802
+ function requireOption(parsed, names, usage) {
8803
+ for (const name of names) {
8804
+ const value = normalizeOption(parsed.options[name]);
8805
+ if (value) return value;
8806
+ }
8807
+ throw new Error(usage);
8808
+ }
8809
+ async function searchAssets(parsed, deps) {
8810
+ const ctx = await buildAuthContext(parsed, deps);
8811
+ const query = parsed.positionals.slice(1).join(" ").trim();
8812
+ const params = new URLSearchParams();
8813
+ if (query) params.set("q", query);
8814
+ const mediaKind = normalizeOption(parsed.options.media) ?? normalizeOption(parsed.options["media-kind"]);
8815
+ const assetType = normalizeOption(parsed.options.type);
8816
+ const limit = normalizeOption(parsed.options.limit);
8817
+ if (mediaKind) params.set("media_kind", mediaKind);
8818
+ if (assetType) params.set("type", assetType);
8819
+ if (limit) params.set("limit", limit);
8820
+ const response = await deps.fetch(`${ctx.baseUrl}/api/assets/search?${params.toString()}`, { headers: { Authorization: `Bearer ${ctx.accessToken}` } });
8821
+ if (!response.ok) throw new Error(`Search failed: ${response.status} - ${await response.text()}`);
8822
+ const results = (await response.json()).results ?? [];
8823
+ if (parsed.options.json === "true") deps.print(JSON.stringify(results, null, 2));
8824
+ else printAssetSearchResults(results, deps.print);
8825
+ return {
8826
+ type: "search",
8827
+ results
8828
+ };
8829
+ }
8830
+ async function cloneAsset(parsed, deps) {
8831
+ const ctx = await buildAuthContext(parsed, deps);
8832
+ const sourceVariantId = parsed.positionals[1] ?? normalizeOption(parsed.options.variant);
8833
+ if (!sourceVariantId) throw new Error("Usage: makefx assets clone <variant-id> --from-space <source-space-id> --to-space <target-space-id>");
8834
+ const sourceSpaceId = requireOption(parsed, ["from-space", "source-space"], "Usage: makefx assets clone <variant-id> --from-space <source-space-id> --to-space <target-space-id>");
8835
+ const targetSpaceId = requireOption(parsed, ["to-space", "target-space"], "Usage: makefx assets clone <variant-id> --from-space <source-space-id> --to-space <target-space-id>");
8836
+ const body = {
8837
+ sourceSpaceId,
8838
+ sourceVariantId
8839
+ };
8840
+ const name = normalizeOption(parsed.options.name);
8841
+ const type = normalizeOption(parsed.options.type);
8842
+ if (name) body.name = name;
8843
+ if (type) body.type = type;
8844
+ const response = await deps.fetch(`${ctx.baseUrl}/api/spaces/${encodeURIComponent(targetSpaceId)}/assets/clone`, {
8845
+ method: "POST",
8846
+ headers: {
8847
+ Authorization: `Bearer ${ctx.accessToken}`,
8848
+ "Content-Type": "application/json"
8849
+ },
8850
+ body: JSON.stringify(body)
8851
+ });
8852
+ if (!response.ok) throw new Error(`Clone failed: ${response.status} - ${await response.text()}`);
8853
+ const payload = await response.json();
8854
+ if (parsed.options.json === "true") deps.print(JSON.stringify(payload, null, 2));
8855
+ else {
8856
+ const asset = payload.asset;
8857
+ const variantPayload = payload.variant;
8858
+ const variant = variantPayload?.variant ?? variantPayload;
8859
+ deps.print(`Cloned variant ${sourceVariantId} into space ${targetSpaceId}`);
8860
+ if (asset?.id) deps.print(` Asset: ${asset.id}${asset.name ? ` (${asset.name})` : ""}`);
8861
+ if (variant?.id) deps.print(` Variant: ${variant.id}`);
8862
+ }
8863
+ return {
8864
+ type: "clone",
8865
+ asset: payload.asset,
8866
+ variant: payload.variant
8867
+ };
8868
+ }
8757
8869
  async function recordDownloadedMirror(input) {
8758
8870
  const variant = input.variant;
8759
8871
  if (!variant || !input.deps.recordMirrorForFile) return;
@@ -8800,7 +8912,7 @@ async function fetchJson(ctx, deps, apiPath) {
8800
8912
  }
8801
8913
  return response.json();
8802
8914
  }
8803
- async function resolveDownloadRef(ref, ctx, deps) {
8915
+ function resolveDownloadRef(ref, ctx) {
8804
8916
  if (looksLikeStorageKey(ref)) {
8805
8917
  if (!isLegacyImageStorageKey(ref)) throw new Error("Direct media key downloads are not supported. Pass a variant ID so the authenticated media endpoint can authorize the download.");
8806
8918
  return {
@@ -8808,19 +8920,32 @@ async function resolveDownloadRef(ref, ctx, deps) {
8808
8920
  requestPath: `/api/images/${ref}`
8809
8921
  };
8810
8922
  }
8811
- const assets = await listAssets(ctx, deps);
8812
- for (const asset of assets) {
8813
- const variant = (await getAssetDetails(ctx, deps, asset.id)).variants.find((candidate) => candidate.id === ref);
8814
- if (!variant) continue;
8815
- const mediaKey = variant.media_key || variant.image_key;
8816
- if (mediaKey) return {
8817
- mediaKey,
8818
- requestPath: `/api/spaces/${encodeURIComponent(ctx.spaceId)}/variants/${encodeURIComponent(variant.id)}/media`,
8819
- variant
8820
- };
8821
- throw new Error(`Variant ${ref} has no downloadable media; status is ${variant.status}`);
8822
- }
8823
- throw new Error(`Variant not found in space ${ctx.spaceId}: ${ref}`);
8923
+ return {
8924
+ mediaKey: ref,
8925
+ requestPath: `/api/spaces/${encodeURIComponent(ctx.spaceId)}/variants/${encodeURIComponent(ref)}/media`
8926
+ };
8927
+ }
8928
+ function getDownloadedVariantMetadata(resolved, download) {
8929
+ if (resolved.variant) return {
8930
+ mediaKey: resolved.variant.media_key || resolved.variant.image_key || resolved.mediaKey,
8931
+ mirrorVariant: resolved.variant
8932
+ };
8933
+ const headers = download?.headers;
8934
+ const variantId = headers?.get("x-makefx-variant-id");
8935
+ const assetId = headers?.get("x-makefx-asset-id");
8936
+ const mediaKind = headers?.get("x-makefx-media-kind");
8937
+ const mediaKey = headers?.get("x-makefx-media-key") || resolved.mediaKey;
8938
+ if (!variantId || !assetId) return { mediaKey };
8939
+ return {
8940
+ mediaKey,
8941
+ mirrorVariant: {
8942
+ id: variantId,
8943
+ asset_id: assetId,
8944
+ media_kind: mediaKind || "image",
8945
+ media_key: mediaKey,
8946
+ image_key: mediaKey.startsWith("images/") ? mediaKey : null
8947
+ }
8948
+ };
8824
8949
  }
8825
8950
  function looksLikeStorageKey(value) {
8826
8951
  return value.includes("/") || [
@@ -8859,6 +8984,19 @@ function printAssetList(assets, print) {
8859
8984
  print("-".repeat(113));
8860
8985
  for (const asset of assets) print(formatTimestamp(asset.updated_at).padEnd(21) + truncate(asset.type || "unknown", 12).padEnd(14) + truncate(asset.media_kind || "-", 7).padEnd(9) + truncate(asset.active_variant_id || "-", 24).padEnd(26) + truncate(asset.id, 26).padEnd(28) + truncate(asset.name, 32));
8861
8986
  }
8987
+ function printAssetSearchResults(results, print) {
8988
+ if (results.length === 0) {
8989
+ print("No matching assets found.");
8990
+ return;
8991
+ }
8992
+ for (const result of results) {
8993
+ print(`${result.assetName} (${result.assetId})`);
8994
+ print(` Space: ${result.spaceName} (${result.spaceId})`);
8995
+ print(` Type: ${result.assetType ?? "-"} Media: ${result.mediaKind}`);
8996
+ print(` Active: ${result.activeVariantId ?? "-"}${result.activeVariantStatus ? ` (${result.activeVariantStatus})` : ""}`);
8997
+ print(` Counts: variants=${result.variantCount} completed=${result.completedVariantCount} starred=${result.starredVariantCount}`);
8998
+ }
8999
+ }
8862
9000
  function printAssetDetails(details, ctx, print) {
8863
9001
  const { asset, variants, lineage } = details;
8864
9002
  print(`\nAsset ${asset.id}\n`);
@@ -8950,9 +9088,11 @@ function printUsage$2() {
8950
9088
  Usage:
8951
9089
  makefx assets
8952
9090
  makefx assets --json
9091
+ makefx assets search "query" [--media image|audio|video] [--type <type>] [--json]
8953
9092
  makefx assets show <asset-id>
8954
9093
  makefx assets show <asset-id> --json
8955
9094
  makefx assets download <variant-id|legacy-image-key> -o output-file
9095
+ makefx assets clone <variant-id> --from-space <source-space-id> --to-space <target-space-id>
8956
9096
  makefx assets delete <asset-id>
8957
9097
  makefx assets rename <asset-id> "<new-name>"
8958
9098
  makefx assets set-active <asset-id> <variant-id>
@@ -9364,7 +9504,7 @@ Options:
9364
9504
  }
9365
9505
  //#endregion
9366
9506
  //#region src/cli/index.ts
9367
- var CLI_VERSION = "1.0.3+9dc1e273aed1";
9507
+ var CLI_VERSION = "1.0.5+22056848942e";
9368
9508
  async function main() {
9369
9509
  const argv = process$1.argv.slice(2);
9370
9510
  if (argv[0] === "--") argv.shift();
@@ -9551,8 +9691,10 @@ Continue:
9551
9691
 
9552
9692
  Inspect:
9553
9693
  assets List assets
9694
+ assets search "query" Search assets across Spaces
9554
9695
  assets show <asset-id> Show variants and lineage
9555
9696
  assets download <variant-id> -o <file> Download variant media
9697
+ assets clone <variant-id> --from-space <id> --to-space <id>
9556
9698
  variants rate <variant-id> approved|rejected
9557
9699
  variants star <variant-id>
9558
9700
 
@@ -9939,9 +10081,11 @@ function printAssetsHelp() {
9939
10081
  Usage:
9940
10082
  makefx assets
9941
10083
  makefx assets --json
10084
+ makefx assets search "query" [--media image|audio|video] [--type <type>] [--json]
9942
10085
  makefx assets show <asset-id>
9943
10086
  makefx assets show <asset-id> --json
9944
10087
  makefx assets download <variant-id|legacy-image-key> -o output-file
10088
+ makefx assets clone <variant-id> --from-space <source-space-id> --to-space <target-space-id>
9945
10089
  makefx assets delete <asset-id>
9946
10090
  makefx assets rename <asset-id> "<new-name>"
9947
10091
  makefx assets set-active <asset-id> <variant-id>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "makefx",
3
- "version": "1.0.3",
3
+ "version": "1.0.5",
4
4
  "description": "Command-line interface for AI-assisted game asset production with Make Effects.",
5
5
  "license": "MIT",
6
6
  "type": "module",