makefx 2.0.0 → 2.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +9 -0
- package/README.md +13 -0
- package/dist/makefx.js +64 -32
- package/package.json +1 -1
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.2 — 2026-09-26
|
|
7
|
+
|
|
8
|
+
- `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.
|
|
9
|
+
- 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.
|
|
10
|
+
|
|
11
|
+
## 2.0.1 — 2026-09-24
|
|
12
|
+
|
|
13
|
+
- 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.
|
|
14
|
+
|
|
6
15
|
## 2.0.0 — 2026-09-24
|
|
7
16
|
|
|
8
17
|
### One canvas for you and your agent
|
package/README.md
CHANGED
|
@@ -16,6 +16,19 @@ npm install -g makefx
|
|
|
16
16
|
makefx login
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
+
Login always prints the authorization URL and callback address
|
|
20
|
+
(`http://127.0.0.1:8765/callback`), then tries to open a browser. If the CLI is
|
|
21
|
+
running on a remote host, keep login running and open a separate terminal on
|
|
22
|
+
the computer with your browser. Start the printed SSH tunnel before opening
|
|
23
|
+
the authorization URL:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
ssh -N -L 8765:127.0.0.1:8765 user@remote-host
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
The CLI suggests the detected hostname; replace it with your usual SSH
|
|
30
|
+
destination or alias if needed. Keep the tunnel open until login finishes.
|
|
31
|
+
|
|
19
32
|
Use `--env production|stage|local` to keep separate credentials and select the
|
|
20
33
|
endpoint. Production is the default. `makefx logout` revokes the grant when the
|
|
21
34
|
service is reachable and always removes the stored credentials; a grant can
|
package/dist/makefx.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
-
import
|
|
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.
|
|
16
|
+
var CLI_VERSION = true ? "2.0.2" : "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) =>
|
|
1097
|
+
write: (text2) => process2.stdout.write(`${text2}
|
|
1097
1098
|
`),
|
|
1098
1099
|
id: randomUUID2
|
|
1099
1100
|
};
|
|
@@ -1360,13 +1361,13 @@ async function handleCreate(parsed, dependencies = defaults2) {
|
|
|
1360
1361
|
}
|
|
1361
1362
|
const args = createToolArguments(prepared, entry, dependencies.id());
|
|
1362
1363
|
const created = await client.call("create_asset", args);
|
|
1363
|
-
|
|
1364
|
+
let assets = Array.isArray(created.assets) ? created.assets : [];
|
|
1364
1365
|
if (parsed.options.wait === "true" && !assets.some(({ renders_in }) => renders_in === "browser")) {
|
|
1365
1366
|
const spaceId2 = prepared.spaceId;
|
|
1366
|
-
|
|
1367
|
+
assets = await Promise.all(
|
|
1367
1368
|
assets.map(async (asset) => {
|
|
1368
1369
|
const assetId = asset.asset_id;
|
|
1369
|
-
if (typeof assetId !== "string") return
|
|
1370
|
+
if (typeof assetId !== "string") return asset;
|
|
1370
1371
|
while (true) {
|
|
1371
1372
|
const result = await client.call("get_asset", {
|
|
1372
1373
|
space_id: spaceId2,
|
|
@@ -1374,16 +1375,26 @@ async function handleCreate(parsed, dependencies = defaults2) {
|
|
|
1374
1375
|
wait_seconds: 60
|
|
1375
1376
|
});
|
|
1376
1377
|
const current = result.asset;
|
|
1377
|
-
if (current?.status === "ready")
|
|
1378
|
-
|
|
1379
|
-
const error = current.error;
|
|
1380
|
-
return error?.message ?? `Asset ${assetId} failed.`;
|
|
1378
|
+
if (current?.status === "ready" || current?.status === "failed") {
|
|
1379
|
+
return current ? { ...asset, ...current } : asset;
|
|
1381
1380
|
}
|
|
1382
1381
|
}
|
|
1383
1382
|
})
|
|
1384
1383
|
);
|
|
1385
|
-
|
|
1386
|
-
|
|
1384
|
+
created.assets = assets;
|
|
1385
|
+
const failed = assets.filter((asset) => asset.status === "failed");
|
|
1386
|
+
if (failed.length > 0) {
|
|
1387
|
+
if (parsed.options.json === "true") {
|
|
1388
|
+
dependencies.write(JSON.stringify(created, null, 2));
|
|
1389
|
+
process2.exitCode = 1;
|
|
1390
|
+
return;
|
|
1391
|
+
}
|
|
1392
|
+
const messages = failed.map((asset) => {
|
|
1393
|
+
const error = asset.error;
|
|
1394
|
+
return error?.message ?? `Asset ${String(asset.asset_id)} failed.`;
|
|
1395
|
+
});
|
|
1396
|
+
throw new Error(messages.join("\n"));
|
|
1397
|
+
}
|
|
1387
1398
|
}
|
|
1388
1399
|
if (parsed.options.json === "true") {
|
|
1389
1400
|
dependencies.write(JSON.stringify(created, null, 2));
|
|
@@ -1397,7 +1408,8 @@ async function handleCreate(parsed, dependencies = defaults2) {
|
|
|
1397
1408
|
}
|
|
1398
1409
|
|
|
1399
1410
|
// src/commands/login.ts
|
|
1400
|
-
import
|
|
1411
|
+
import process3 from "node:process";
|
|
1412
|
+
import { hostname } from "node:os";
|
|
1401
1413
|
async function handleLogin(parsed) {
|
|
1402
1414
|
const isLocal = parsed.options.local === "true";
|
|
1403
1415
|
const env = isLocal ? "local" : parsed.options.env ?? DEFAULT_ENVIRONMENT;
|
|
@@ -1407,7 +1419,7 @@ async function handleLogin(parsed) {
|
|
|
1407
1419
|
const insecure = isLocal;
|
|
1408
1420
|
if (insecure) {
|
|
1409
1421
|
console.log("\u26A0\uFE0F SSL certificate verification disabled (local dev mode)");
|
|
1410
|
-
|
|
1422
|
+
process3.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
|
|
1411
1423
|
}
|
|
1412
1424
|
console.log(`Starting login for environment "${env}" using ${baseUrl}`);
|
|
1413
1425
|
const server = await discoverAuthorizationServer(baseUrl);
|
|
@@ -1424,12 +1436,22 @@ async function handleLogin(parsed) {
|
|
|
1424
1436
|
authUrl.searchParams.set("code_challenge_method", "S256");
|
|
1425
1437
|
authUrl.searchParams.set("state", state);
|
|
1426
1438
|
authUrl.searchParams.set("resource", mcpResourceFor(baseUrl));
|
|
1439
|
+
console.log(`
|
|
1440
|
+
Open this URL in your browser to authenticate:
|
|
1441
|
+
|
|
1442
|
+
${authUrl.toString()}
|
|
1443
|
+
`);
|
|
1444
|
+
console.log(`The OAuth callback uses ${redirectUri} (port ${redirectPort}).`);
|
|
1445
|
+
const detectedHost = hostname();
|
|
1446
|
+
const sshHost = /^[a-zA-Z0-9][a-zA-Z0-9.-]*$/.test(detectedHost) ? detectedHost : "YOUR_SSH_HOST";
|
|
1447
|
+
console.log("If this CLI is on a remote host, run this in a separate terminal on the computer with your browser before opening the URL:");
|
|
1448
|
+
console.log(` ssh -N -L ${redirectPort}:127.0.0.1:${redirectPort} ${sshHost}`);
|
|
1449
|
+
console.log("Use your usual SSH destination (user@host or SSH alias) if the detected hostname is not reachable. Keep the tunnel open until login finishes.\n");
|
|
1427
1450
|
console.log("Opening browser for Google authentication...");
|
|
1428
1451
|
try {
|
|
1429
1452
|
await openBrowser(authUrl.toString());
|
|
1430
1453
|
} catch {
|
|
1431
|
-
console.warn("Unable to open browser automatically. Please
|
|
1432
|
-
console.log(authUrl.toString());
|
|
1454
|
+
console.warn("Unable to open browser automatically. Please open the URL above in your browser.");
|
|
1433
1455
|
}
|
|
1434
1456
|
const { code } = await waitForAuthorizationCode(redirectPort, state);
|
|
1435
1457
|
console.log("Received authorization code. Exchanging for access token...");
|
|
@@ -1485,20 +1507,20 @@ async function handleLogout(parsed) {
|
|
|
1485
1507
|
}
|
|
1486
1508
|
|
|
1487
1509
|
// src/commands/mcp.ts
|
|
1488
|
-
import
|
|
1510
|
+
import process4 from "node:process";
|
|
1489
1511
|
async function handleMcp(parsed) {
|
|
1490
1512
|
const env = parsed.options.local === "true" ? "local" : parsed.options.env ?? DEFAULT_ENVIRONMENT;
|
|
1491
1513
|
const loginHint = `Run: ${cliCommand()} login --env ${env}`;
|
|
1492
1514
|
const stored = await loadStoredConfig(env);
|
|
1493
1515
|
if (!stored) {
|
|
1494
1516
|
console.error(`Not logged in for "${env}". ${loginHint}`);
|
|
1495
|
-
|
|
1517
|
+
process4.exitCode = 1;
|
|
1496
1518
|
return;
|
|
1497
1519
|
}
|
|
1498
1520
|
const config = await ensureFreshConfig(stored);
|
|
1499
1521
|
if (!config) {
|
|
1500
1522
|
console.error(`Credentials for "${env}" have expired. ${loginHint}`);
|
|
1501
|
-
|
|
1523
|
+
process4.exitCode = 1;
|
|
1502
1524
|
return;
|
|
1503
1525
|
}
|
|
1504
1526
|
const bridge = createMcpBridge({ config });
|
|
@@ -1508,15 +1530,15 @@ async function handleMcp(parsed) {
|
|
|
1508
1530
|
case "accepted":
|
|
1509
1531
|
return;
|
|
1510
1532
|
case "response":
|
|
1511
|
-
if (outcome.text)
|
|
1533
|
+
if (outcome.text) process4.stdout.write(`${outcome.text}
|
|
1512
1534
|
`);
|
|
1513
1535
|
return;
|
|
1514
1536
|
case "unauthorized":
|
|
1515
|
-
if (outcome.text)
|
|
1537
|
+
if (outcome.text) process4.stdout.write(`${outcome.text}
|
|
1516
1538
|
`);
|
|
1517
1539
|
console.error(`Access to "${env}" was revoked or has expired. ${loginHint}`);
|
|
1518
|
-
|
|
1519
|
-
|
|
1540
|
+
process4.exitCode = 1;
|
|
1541
|
+
process4.stdin.destroy();
|
|
1520
1542
|
return;
|
|
1521
1543
|
case "unreachable":
|
|
1522
1544
|
console.error(`MCP request failed: ${outcome.message}`);
|
|
@@ -1525,8 +1547,8 @@ async function handleMcp(parsed) {
|
|
|
1525
1547
|
}
|
|
1526
1548
|
let buffer = "";
|
|
1527
1549
|
const pending = [];
|
|
1528
|
-
|
|
1529
|
-
for await (const chunk of
|
|
1550
|
+
process4.stdin.setEncoding("utf8");
|
|
1551
|
+
for await (const chunk of process4.stdin) {
|
|
1530
1552
|
buffer += chunk;
|
|
1531
1553
|
let newline = buffer.indexOf("\n");
|
|
1532
1554
|
while (newline !== -1) {
|
|
@@ -2328,7 +2350,12 @@ function uploadMime(parsed, path2) {
|
|
|
2328
2350
|
const explicit = optional5(parsed, "mime");
|
|
2329
2351
|
if (explicit) return explicit;
|
|
2330
2352
|
const inferred = MIME_BY_EXTENSION[extname(path2).toLowerCase()];
|
|
2331
|
-
if (!inferred)
|
|
2353
|
+
if (!inferred) {
|
|
2354
|
+
const supported = Object.keys(MIME_BY_EXTENSION).sort().join(", ");
|
|
2355
|
+
throw new CliUsageError(
|
|
2356
|
+
`Cannot infer media type from --file; supported uploads are ${supported} \u2014 pass --mime <type> to override.`
|
|
2357
|
+
);
|
|
2358
|
+
}
|
|
2332
2359
|
return inferred;
|
|
2333
2360
|
}
|
|
2334
2361
|
function declaredRecipe(parsed) {
|
|
@@ -2552,11 +2579,16 @@ function reportCommandError(error, json2, output) {
|
|
|
2552
2579
|
return 1;
|
|
2553
2580
|
}
|
|
2554
2581
|
if (error instanceof CliUsageError) {
|
|
2582
|
+
if (json2) output.stdout(`${JSON.stringify({ code: "usage", message: error.message })}
|
|
2583
|
+
`);
|
|
2555
2584
|
output.stderr(`Error: ${error.message}
|
|
2556
2585
|
`);
|
|
2557
2586
|
return 2;
|
|
2558
2587
|
}
|
|
2559
|
-
|
|
2588
|
+
const message = error instanceof Error ? error.message : "Unexpected error occurred";
|
|
2589
|
+
if (json2) output.stdout(`${JSON.stringify({ code: "error", message })}
|
|
2590
|
+
`);
|
|
2591
|
+
output.stderr(`Error: ${message}
|
|
2560
2592
|
`);
|
|
2561
2593
|
return 1;
|
|
2562
2594
|
}
|
|
@@ -2851,7 +2883,7 @@ async function handleSpaceVisibilityCommand(command, parsed, dependencies = defa
|
|
|
2851
2883
|
|
|
2852
2884
|
// src/index.ts
|
|
2853
2885
|
async function main() {
|
|
2854
|
-
const [, , command, ...args] =
|
|
2886
|
+
const [, , command, ...args] = process5.argv;
|
|
2855
2887
|
try {
|
|
2856
2888
|
if (!command || command === "--help") {
|
|
2857
2889
|
printHelp();
|
|
@@ -2871,9 +2903,9 @@ async function main() {
|
|
|
2871
2903
|
await dispatchCommand(command, parsed);
|
|
2872
2904
|
} catch (error) {
|
|
2873
2905
|
const parsed = parseArgs(args);
|
|
2874
|
-
|
|
2875
|
-
stdout: (text2) =>
|
|
2876
|
-
stderr: (text2) =>
|
|
2906
|
+
process5.exitCode = reportCommandError(error, parsed.options.json === "true", {
|
|
2907
|
+
stdout: (text2) => process5.stdout.write(text2),
|
|
2908
|
+
stderr: (text2) => process5.stderr.write(text2)
|
|
2877
2909
|
});
|
|
2878
2910
|
}
|
|
2879
2911
|
}
|
|
@@ -2958,7 +2990,7 @@ Run makefx <command> --help, makefx space help, makefx asset help, makefx audio
|
|
|
2958
2990
|
}
|
|
2959
2991
|
function helpForCommand(command) {
|
|
2960
2992
|
const staticUsage = {
|
|
2961
|
-
login: "Usage: makefx login [--env production|stage|local] [--local]",
|
|
2993
|
+
login: "Usage: makefx login [--env production|stage|local] [--local]\n\nPrints the browser authorization URL and loopback callback port (8765).\nFor remote hosts, also prints an SSH tunnel command to run on your browser computer.",
|
|
2962
2994
|
logout: "Usage: makefx logout [--env production|stage|local] [--local]",
|
|
2963
2995
|
mcp: "Usage: makefx mcp [--env production|stage|local] [--local]",
|
|
2964
2996
|
create: "Usage: makefx create --space ACCOUNT/SPACE --kind image|video|audio --model MODEL [--prompt TEXT] [--ref ASSET:SLOT]... [--param NAME=VALUE]... [--count 1..8] [--name TEXT] [--seed INTEGER] [--position JSON] [--tags JSON] [--note TEXT] [--from-asset ASSET] [--recipe-mode current|exact] [--request-id ID] [--wait] [--json]\nRepeated --ref values preserve left-to-right order as reference order 0, 1, and so on. Reads the live catalog for the paying Space before calling create_asset.",
|