makefx 2.0.1 → 2.0.3

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.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,15 @@
3
3
  The 1.x packages were built from the first version of makefx.app and do not
4
4
  work with the current service.
5
5
 
6
+ ## 2.0.3 — 2026-09-26
7
+
8
+ - `create` refuses a prompt longer than the model's published `prompt_max_chars` before spending credits, and counts prompt length in characters rather than UTF-16 units, as the service does.
9
+
10
+ ## 2.0.2 — 2026-09-26
11
+
12
+ - `create --wait --json` now prints each asset's final ready or failed state instead of the queued state captured before waiting, and exits 1 with the failed asset's error when waiting ends in failure.
13
+ - Usage and unexpected errors are now also written as JSON to stdout under `--json`, and the `upload` error for an unrecognized file extension now names the supported upload types.
14
+
6
15
  ## 2.0.1 — 2026-09-24
7
16
 
8
17
  - Always print the login authorization URL, callback port, and an SSH tunnel command with the detected hostname so remote login can be completed from a local browser.
package/dist/makefx.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- import process4 from "node:process";
4
+ import process5 from "node:process";
5
5
 
6
6
  // src/lib/project.ts
7
7
  var SERVICE_NAME = "makefx.app";
@@ -13,7 +13,7 @@ var ENVIRONMENT_ORIGINS = {
13
13
  };
14
14
 
15
15
  // src/lib/version.ts
16
- var CLI_VERSION = true ? "2.0.1" : "0.0.0-dev";
16
+ var CLI_VERSION = true ? "2.0.3" : "0.0.0-dev";
17
17
 
18
18
  // src/commands/convenience.ts
19
19
  import { randomUUID } from "node:crypto";
@@ -870,6 +870,7 @@ async function handleOpen(parsed, dependencies = defaults) {
870
870
 
871
871
  // src/commands/create.ts
872
872
  import { randomUUID as randomUUID2 } from "node:crypto";
873
+ import process2 from "node:process";
873
874
 
874
875
  // src/lib/json-schema.ts
875
876
  import { isDeepStrictEqual } from "node:util";
@@ -1093,7 +1094,7 @@ function validateJsonSchema(schema, input) {
1093
1094
  // src/commands/create.ts
1094
1095
  var defaults2 = {
1095
1096
  client: authenticatedToolClient,
1096
- write: (text2) => process.stdout.write(`${text2}
1097
+ write: (text2) => process2.stdout.write(`${text2}
1097
1098
  `),
1098
1099
  id: randomUUID2
1099
1100
  };
@@ -1246,7 +1247,7 @@ function prepareCreate(parsed) {
1246
1247
  throw new CliUsageError("--request-id must be at most 64 characters.");
1247
1248
  }
1248
1249
  const prompt = optional2(parsed, "prompt") ?? "";
1249
- if (prompt.length > 8e3) throw new CliUsageError("--prompt must be at most 8000 characters.");
1250
+ if (Array.from(prompt).length > 8e3) throw new CliUsageError("--prompt must be at most 8000 characters.");
1250
1251
  return {
1251
1252
  spaceId: required2(parsed, "space"),
1252
1253
  kind,
@@ -1307,12 +1308,19 @@ function catalogModels(document) {
1307
1308
  `list_models returned a malformed catalog: ${error instanceof Error ? error.message : "invalid params_schema."}`
1308
1309
  );
1309
1310
  }
1311
+ const promptMaxChars = model.prompt_max_chars;
1312
+ if (promptMaxChars !== void 0 && (typeof promptMaxChars !== "number" || !Number.isSafeInteger(promptMaxChars) || promptMaxChars < 1)) {
1313
+ throw new Error(
1314
+ `list_models returned a malformed catalog: model "${model.id}" prompt_max_chars must be a positive integer.`
1315
+ );
1316
+ }
1310
1317
  return {
1311
1318
  id: model.id,
1312
1319
  kind: model.kind,
1313
1320
  availability: model.availability,
1314
1321
  hidden: model.hidden,
1315
- paramsSchema: model.params_schema
1322
+ paramsSchema: model.params_schema,
1323
+ ...promptMaxChars === void 0 ? {} : { promptMaxChars }
1316
1324
  };
1317
1325
  });
1318
1326
  }
@@ -1325,6 +1333,11 @@ function createToolArguments(prepared, entry, requestId) {
1325
1333
  `Model "${prepared.model}" is unavailable. Run models --space ${prepared.spaceId} to list the catalog.`
1326
1334
  );
1327
1335
  }
1336
+ if (entry.promptMaxChars !== void 0 && prepared.recipeMode !== "exact" && Array.from(prepared.prompt).length > entry.promptMaxChars) {
1337
+ throw new CliUsageError(
1338
+ `--prompt must be at most ${entry.promptMaxChars} characters for model "${prepared.model}".`
1339
+ );
1340
+ }
1328
1341
  const validation = validateJsonSchema(entry.paramsSchema, prepared.params);
1329
1342
  if (!validation.ok) {
1330
1343
  const field = validation.issue.field.replace(/^params\.?/, "") || "params";
@@ -1360,13 +1373,13 @@ async function handleCreate(parsed, dependencies = defaults2) {
1360
1373
  }
1361
1374
  const args = createToolArguments(prepared, entry, dependencies.id());
1362
1375
  const created = await client.call("create_asset", args);
1363
- const assets = Array.isArray(created.assets) ? created.assets : [];
1376
+ let assets = Array.isArray(created.assets) ? created.assets : [];
1364
1377
  if (parsed.options.wait === "true" && !assets.some(({ renders_in }) => renders_in === "browser")) {
1365
1378
  const spaceId2 = prepared.spaceId;
1366
- const failures = await Promise.all(
1379
+ assets = await Promise.all(
1367
1380
  assets.map(async (asset) => {
1368
1381
  const assetId = asset.asset_id;
1369
- if (typeof assetId !== "string") return null;
1382
+ if (typeof assetId !== "string") return asset;
1370
1383
  while (true) {
1371
1384
  const result = await client.call("get_asset", {
1372
1385
  space_id: spaceId2,
@@ -1374,16 +1387,26 @@ async function handleCreate(parsed, dependencies = defaults2) {
1374
1387
  wait_seconds: 60
1375
1388
  });
1376
1389
  const current = result.asset;
1377
- if (current?.status === "ready") return null;
1378
- if (current?.status === "failed") {
1379
- const error = current.error;
1380
- return error?.message ?? `Asset ${assetId} failed.`;
1390
+ if (current?.status === "ready" || current?.status === "failed") {
1391
+ return current ? { ...asset, ...current } : asset;
1381
1392
  }
1382
1393
  }
1383
1394
  })
1384
1395
  );
1385
- const messages = failures.filter((message) => message !== null);
1386
- if (messages.length > 0) throw new Error(messages.join("\n"));
1396
+ created.assets = assets;
1397
+ const failed = assets.filter((asset) => asset.status === "failed");
1398
+ if (failed.length > 0) {
1399
+ if (parsed.options.json === "true") {
1400
+ dependencies.write(JSON.stringify(created, null, 2));
1401
+ process2.exitCode = 1;
1402
+ return;
1403
+ }
1404
+ const messages = failed.map((asset) => {
1405
+ const error = asset.error;
1406
+ return error?.message ?? `Asset ${String(asset.asset_id)} failed.`;
1407
+ });
1408
+ throw new Error(messages.join("\n"));
1409
+ }
1387
1410
  }
1388
1411
  if (parsed.options.json === "true") {
1389
1412
  dependencies.write(JSON.stringify(created, null, 2));
@@ -1397,7 +1420,7 @@ async function handleCreate(parsed, dependencies = defaults2) {
1397
1420
  }
1398
1421
 
1399
1422
  // src/commands/login.ts
1400
- import process2 from "node:process";
1423
+ import process3 from "node:process";
1401
1424
  import { hostname } from "node:os";
1402
1425
  async function handleLogin(parsed) {
1403
1426
  const isLocal = parsed.options.local === "true";
@@ -1408,7 +1431,7 @@ async function handleLogin(parsed) {
1408
1431
  const insecure = isLocal;
1409
1432
  if (insecure) {
1410
1433
  console.log("\u26A0\uFE0F SSL certificate verification disabled (local dev mode)");
1411
- process2.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
1434
+ process3.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
1412
1435
  }
1413
1436
  console.log(`Starting login for environment "${env}" using ${baseUrl}`);
1414
1437
  const server = await discoverAuthorizationServer(baseUrl);
@@ -1496,20 +1519,20 @@ async function handleLogout(parsed) {
1496
1519
  }
1497
1520
 
1498
1521
  // src/commands/mcp.ts
1499
- import process3 from "node:process";
1522
+ import process4 from "node:process";
1500
1523
  async function handleMcp(parsed) {
1501
1524
  const env = parsed.options.local === "true" ? "local" : parsed.options.env ?? DEFAULT_ENVIRONMENT;
1502
1525
  const loginHint = `Run: ${cliCommand()} login --env ${env}`;
1503
1526
  const stored = await loadStoredConfig(env);
1504
1527
  if (!stored) {
1505
1528
  console.error(`Not logged in for "${env}". ${loginHint}`);
1506
- process3.exitCode = 1;
1529
+ process4.exitCode = 1;
1507
1530
  return;
1508
1531
  }
1509
1532
  const config = await ensureFreshConfig(stored);
1510
1533
  if (!config) {
1511
1534
  console.error(`Credentials for "${env}" have expired. ${loginHint}`);
1512
- process3.exitCode = 1;
1535
+ process4.exitCode = 1;
1513
1536
  return;
1514
1537
  }
1515
1538
  const bridge = createMcpBridge({ config });
@@ -1519,15 +1542,15 @@ async function handleMcp(parsed) {
1519
1542
  case "accepted":
1520
1543
  return;
1521
1544
  case "response":
1522
- if (outcome.text) process3.stdout.write(`${outcome.text}
1545
+ if (outcome.text) process4.stdout.write(`${outcome.text}
1523
1546
  `);
1524
1547
  return;
1525
1548
  case "unauthorized":
1526
- if (outcome.text) process3.stdout.write(`${outcome.text}
1549
+ if (outcome.text) process4.stdout.write(`${outcome.text}
1527
1550
  `);
1528
1551
  console.error(`Access to "${env}" was revoked or has expired. ${loginHint}`);
1529
- process3.exitCode = 1;
1530
- process3.stdin.destroy();
1552
+ process4.exitCode = 1;
1553
+ process4.stdin.destroy();
1531
1554
  return;
1532
1555
  case "unreachable":
1533
1556
  console.error(`MCP request failed: ${outcome.message}`);
@@ -1536,8 +1559,8 @@ async function handleMcp(parsed) {
1536
1559
  }
1537
1560
  let buffer = "";
1538
1561
  const pending = [];
1539
- process3.stdin.setEncoding("utf8");
1540
- for await (const chunk of process3.stdin) {
1562
+ process4.stdin.setEncoding("utf8");
1563
+ for await (const chunk of process4.stdin) {
1541
1564
  buffer += chunk;
1542
1565
  let newline = buffer.indexOf("\n");
1543
1566
  while (newline !== -1) {
@@ -2339,7 +2362,12 @@ function uploadMime(parsed, path2) {
2339
2362
  const explicit = optional5(parsed, "mime");
2340
2363
  if (explicit) return explicit;
2341
2364
  const inferred = MIME_BY_EXTENSION[extname(path2).toLowerCase()];
2342
- if (!inferred) throw new CliUsageError("Cannot infer media type from --file; pass --mime <type>.");
2365
+ if (!inferred) {
2366
+ const supported = Object.keys(MIME_BY_EXTENSION).sort().join(", ");
2367
+ throw new CliUsageError(
2368
+ `Cannot infer media type from --file; supported uploads are ${supported} \u2014 pass --mime <type> to override.`
2369
+ );
2370
+ }
2343
2371
  return inferred;
2344
2372
  }
2345
2373
  function declaredRecipe(parsed) {
@@ -2563,11 +2591,16 @@ function reportCommandError(error, json2, output) {
2563
2591
  return 1;
2564
2592
  }
2565
2593
  if (error instanceof CliUsageError) {
2594
+ if (json2) output.stdout(`${JSON.stringify({ code: "usage", message: error.message })}
2595
+ `);
2566
2596
  output.stderr(`Error: ${error.message}
2567
2597
  `);
2568
2598
  return 2;
2569
2599
  }
2570
- output.stderr(`Error: ${error instanceof Error ? error.message : "Unexpected error occurred"}
2600
+ const message = error instanceof Error ? error.message : "Unexpected error occurred";
2601
+ if (json2) output.stdout(`${JSON.stringify({ code: "error", message })}
2602
+ `);
2603
+ output.stderr(`Error: ${message}
2571
2604
  `);
2572
2605
  return 1;
2573
2606
  }
@@ -2862,7 +2895,7 @@ async function handleSpaceVisibilityCommand(command, parsed, dependencies = defa
2862
2895
 
2863
2896
  // src/index.ts
2864
2897
  async function main() {
2865
- const [, , command, ...args] = process4.argv;
2898
+ const [, , command, ...args] = process5.argv;
2866
2899
  try {
2867
2900
  if (!command || command === "--help") {
2868
2901
  printHelp();
@@ -2882,9 +2915,9 @@ async function main() {
2882
2915
  await dispatchCommand(command, parsed);
2883
2916
  } catch (error) {
2884
2917
  const parsed = parseArgs(args);
2885
- process4.exitCode = reportCommandError(error, parsed.options.json === "true", {
2886
- stdout: (text2) => process4.stdout.write(text2),
2887
- stderr: (text2) => process4.stderr.write(text2)
2918
+ process5.exitCode = reportCommandError(error, parsed.options.json === "true", {
2919
+ stdout: (text2) => process5.stdout.write(text2),
2920
+ stderr: (text2) => process5.stderr.write(text2)
2888
2921
  });
2889
2922
  }
2890
2923
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "makefx",
3
- "version": "2.0.1",
3
+ "version": "2.0.3",
4
4
  "description": "Make images, video, and audio on makefx.app from your terminal or coding agent.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",