makeables 0.8.0 → 0.9.0
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/README.md +29 -3
- package/dist/{chunk-NOES67KG.js → chunk-RFIQNWNF.js} +6 -3
- package/dist/cli.js +482 -102
- package/dist/{mcp-54KV55QM.js → mcp-HS2XZXSO.js} +1 -1
- package/package.json +3 -3
package/dist/cli.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import {
|
|
3
3
|
CloudError,
|
|
4
4
|
InputError,
|
|
5
|
+
PACKAGE_LIMIT,
|
|
5
6
|
addBadgeSticker,
|
|
6
7
|
badgeDesignObjectSchema,
|
|
7
8
|
badgeDesignSchema,
|
|
@@ -29,6 +30,8 @@ import {
|
|
|
29
30
|
prepareSheetPrint,
|
|
30
31
|
readBadge,
|
|
31
32
|
readPackageJson,
|
|
33
|
+
readPackageZip,
|
|
34
|
+
readSubmission,
|
|
32
35
|
removeBadgeLayer,
|
|
33
36
|
renderBadge,
|
|
34
37
|
setBadgeLayer,
|
|
@@ -52,13 +55,13 @@ import {
|
|
|
52
55
|
svgNodeXml,
|
|
53
56
|
version,
|
|
54
57
|
writeAtomic
|
|
55
|
-
} from "./chunk-
|
|
58
|
+
} from "./chunk-RFIQNWNF.js";
|
|
56
59
|
|
|
57
60
|
// src/cli.ts
|
|
58
61
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
59
|
-
import { constants as
|
|
60
|
-
import { access, lstat as
|
|
61
|
-
import { dirname as
|
|
62
|
+
import { constants as constants3 } from "fs";
|
|
63
|
+
import { access, lstat as lstat4, open as open3, readFile as readFile8 } from "fs/promises";
|
|
64
|
+
import { dirname as dirname4, extname as extname4, resolve as resolve7 } from "path";
|
|
62
65
|
import { parseArgs } from "util";
|
|
63
66
|
import sharp8 from "sharp";
|
|
64
67
|
import { zodToJsonSchema } from "zod-to-json-schema";
|
|
@@ -222,12 +225,13 @@ async function readToolResult(path) {
|
|
|
222
225
|
if (!info.isFile() || info.size > 12e6)
|
|
223
226
|
throw new InputError("Expected a result file below 12 MB.");
|
|
224
227
|
let value = JSON.parse(await readFile2(path, "utf8"));
|
|
225
|
-
if ("
|
|
228
|
+
if (typeof value === "string") value = JSON.parse(value);
|
|
229
|
+
if (value && typeof value === "object" && "success" in value) {
|
|
226
230
|
if (!value.success) throw new InputError("The browser command failed.");
|
|
227
231
|
value = value.data.output;
|
|
228
232
|
}
|
|
229
233
|
if (typeof value === "string") value = JSON.parse(value);
|
|
230
|
-
if (!value?.ok)
|
|
234
|
+
if (!value?.ok || !value.data || typeof value.data !== "object")
|
|
231
235
|
throw new InputError(value?.error?.message || "The tool returned an invalid result.");
|
|
232
236
|
return value.data;
|
|
233
237
|
}
|
|
@@ -328,8 +332,12 @@ async function saveBundle(url, out) {
|
|
|
328
332
|
} finally {
|
|
329
333
|
await file.close();
|
|
330
334
|
}
|
|
335
|
+
const saved = await readFile3(path);
|
|
331
336
|
return {
|
|
332
337
|
path,
|
|
338
|
+
bytes: saved.length,
|
|
339
|
+
sha256: await hashBytes(saved),
|
|
340
|
+
downloadVerified: true,
|
|
333
341
|
snapshotHash: await hashBytes(jsonBytes(pkg.manifest)),
|
|
334
342
|
name: pkg.manifest.title,
|
|
335
343
|
complete: true,
|
|
@@ -1146,28 +1154,244 @@ async function bindStickerMap(design, layerId, path) {
|
|
|
1146
1154
|
import { spawn } from "child_process";
|
|
1147
1155
|
import { createHash as createHash3 } from "crypto";
|
|
1148
1156
|
import { existsSync as existsSync2 } from "fs";
|
|
1149
|
-
import { chmod, lstat, mkdir as
|
|
1150
|
-
import { homedir as
|
|
1151
|
-
import { dirname as
|
|
1157
|
+
import { chmod as chmod2, lstat as lstat3, mkdir as mkdir3, mkdtemp as mkdtemp2, readFile as readFile7, rename as rename3, rm as rm4, writeFile as writeFile3 } from "fs/promises";
|
|
1158
|
+
import { homedir as homedir3, tmpdir as tmpdir2 } from "os";
|
|
1159
|
+
import { dirname as dirname3, extname as extname3, join as join6, resolve as resolve6 } from "path";
|
|
1152
1160
|
import sharp7 from "sharp";
|
|
1153
1161
|
|
|
1154
1162
|
// src/wallet-release.json
|
|
1155
1163
|
var wallet_release_default = {
|
|
1156
|
-
version: "0.3.
|
|
1164
|
+
version: "0.3.1",
|
|
1157
1165
|
protocol: 1,
|
|
1158
1166
|
repository: "crafter-station/makeables-community",
|
|
1159
1167
|
platforms: {
|
|
1160
1168
|
"darwin-arm64": {
|
|
1161
|
-
asset: "makeables-wallet-0.3.
|
|
1162
|
-
sha256: "
|
|
1169
|
+
asset: "makeables-wallet-0.3.1-darwin-arm64.tar.gz",
|
|
1170
|
+
sha256: "4ab38b2f451492f5365c4d8800c375d4c0112c246b7f6bb8e46ca85772298afc"
|
|
1163
1171
|
},
|
|
1164
1172
|
"darwin-x64": {
|
|
1165
|
-
asset: "makeables-wallet-0.3.
|
|
1166
|
-
sha256: "
|
|
1173
|
+
asset: "makeables-wallet-0.3.1-darwin-x64.tar.gz",
|
|
1174
|
+
sha256: "f301d7be01bdeda2976b182cdf10b3eb30611f2bb88ef3edb520bfa29b814048"
|
|
1167
1175
|
}
|
|
1168
1176
|
}
|
|
1169
1177
|
};
|
|
1170
1178
|
|
|
1179
|
+
// src/wallet-storage.ts
|
|
1180
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
1181
|
+
import { constants as constants2 } from "fs";
|
|
1182
|
+
import { chmod, lstat, mkdir as mkdir2, open as open2, rename as rename2, rm as rm2 } from "fs/promises";
|
|
1183
|
+
import { homedir as homedir2 } from "os";
|
|
1184
|
+
import { dirname as dirname2, join as join3, resolve as resolve5 } from "path";
|
|
1185
|
+
function walletHome() {
|
|
1186
|
+
return resolve5(
|
|
1187
|
+
process.env.MAKEABLES_WALLET_HOME ?? join3(homedir2(), "Library", "Application Support", "Makeables", "Wallet")
|
|
1188
|
+
);
|
|
1189
|
+
}
|
|
1190
|
+
async function privateDirectory(path) {
|
|
1191
|
+
const absolute = resolve5(path);
|
|
1192
|
+
const parent = dirname2(absolute);
|
|
1193
|
+
if (parent !== absolute) {
|
|
1194
|
+
try {
|
|
1195
|
+
const info = await lstat(parent);
|
|
1196
|
+
if (info.isSymbolicLink()) throw new InputError("Wallet storage cannot use symbolic links.");
|
|
1197
|
+
if (!info.isDirectory()) throw new InputError("Wallet storage must be a directory.");
|
|
1198
|
+
} catch (error) {
|
|
1199
|
+
if (error.code !== "ENOENT") throw error;
|
|
1200
|
+
await privateDirectory(parent);
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1203
|
+
await mkdir2(absolute, { recursive: true, mode: 448 });
|
|
1204
|
+
if (!(await lstat(absolute)).isDirectory())
|
|
1205
|
+
throw new InputError("Wallet storage must be a directory, not a symbolic link.");
|
|
1206
|
+
await chmod(absolute, 448);
|
|
1207
|
+
return absolute;
|
|
1208
|
+
}
|
|
1209
|
+
async function readPrivateJson(path) {
|
|
1210
|
+
const handle = await open2(path, constants2.O_RDONLY | constants2.O_NOFOLLOW);
|
|
1211
|
+
try {
|
|
1212
|
+
const info = await handle.stat();
|
|
1213
|
+
if (!info.isFile() || info.size > 1e6)
|
|
1214
|
+
throw new InputError("Invalid Wallet target registry.");
|
|
1215
|
+
return JSON.parse(await handle.readFile("utf8"));
|
|
1216
|
+
} finally {
|
|
1217
|
+
await handle.close();
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
async function writePrivateJson(path, value) {
|
|
1221
|
+
const temporary = join3(dirname2(path), `.${randomUUID2()}.json`);
|
|
1222
|
+
try {
|
|
1223
|
+
const handle = await open2(temporary, "wx", 384);
|
|
1224
|
+
try {
|
|
1225
|
+
await handle.writeFile(`${JSON.stringify(value)}
|
|
1226
|
+
`);
|
|
1227
|
+
await handle.sync();
|
|
1228
|
+
} finally {
|
|
1229
|
+
await handle.close();
|
|
1230
|
+
}
|
|
1231
|
+
await rename2(temporary, path);
|
|
1232
|
+
} finally {
|
|
1233
|
+
await rm2(temporary, { force: true });
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
// src/wallet-submission.ts
|
|
1238
|
+
import { mkdtemp, readFile as readFile6, rm as rm3, writeFile as writeFile2 } from "fs/promises";
|
|
1239
|
+
import { join as join4 } from "path";
|
|
1240
|
+
var uuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
1241
|
+
function submissionId(input) {
|
|
1242
|
+
if (uuid.test(input)) return input.toLowerCase();
|
|
1243
|
+
let url;
|
|
1244
|
+
try {
|
|
1245
|
+
url = new URL(input);
|
|
1246
|
+
} catch {
|
|
1247
|
+
throw new InputError("Supply a submission UUID or official Makeables card URL.");
|
|
1248
|
+
}
|
|
1249
|
+
if (url.origin !== "https://makeables.dev" || url.username || url.password || url.hash)
|
|
1250
|
+
throw new InputError("Submission URLs must use https://makeables.dev.");
|
|
1251
|
+
const id = url.pathname === "/make/card" ? url.searchParams.get("submission") : /^\/submissions\/([^/]+)$/.exec(url.pathname)?.[1];
|
|
1252
|
+
if (!id || !uuid.test(id))
|
|
1253
|
+
throw new InputError("This URL does not identify a Makeables card submission.");
|
|
1254
|
+
return id.toLowerCase();
|
|
1255
|
+
}
|
|
1256
|
+
async function downloadWalletSubmission(input) {
|
|
1257
|
+
const started = performance.now();
|
|
1258
|
+
const id = submissionId(input);
|
|
1259
|
+
const response = await fetch(`https://makeables.dev/api/submissions/${id}/files?download=1`, {
|
|
1260
|
+
redirect: "error",
|
|
1261
|
+
signal: AbortSignal.timeout(3e4)
|
|
1262
|
+
});
|
|
1263
|
+
if (!response.ok || !response.body)
|
|
1264
|
+
throw new InputError(
|
|
1265
|
+
`Submission download failed (${response.status}). Use an approved public card or a local exported package.`
|
|
1266
|
+
);
|
|
1267
|
+
const reader = response.body.getReader();
|
|
1268
|
+
const chunks = [];
|
|
1269
|
+
let size = 0;
|
|
1270
|
+
try {
|
|
1271
|
+
if (Number(response.headers.get("content-length")) > PACKAGE_LIMIT)
|
|
1272
|
+
throw new InputError("Submission package exceeds 24 MB.");
|
|
1273
|
+
while (true) {
|
|
1274
|
+
const { done, value } = await reader.read();
|
|
1275
|
+
if (done) break;
|
|
1276
|
+
size += value.length;
|
|
1277
|
+
if (size > PACKAGE_LIMIT) throw new InputError("Submission package exceeds 24 MB.");
|
|
1278
|
+
chunks.push(value);
|
|
1279
|
+
}
|
|
1280
|
+
} finally {
|
|
1281
|
+
await reader.cancel();
|
|
1282
|
+
}
|
|
1283
|
+
const bytes = Buffer.concat(chunks);
|
|
1284
|
+
const pkg = await readPackageZip(bytes);
|
|
1285
|
+
if (pkg.manifest.product !== "card")
|
|
1286
|
+
throw new InputError("Wallet artwork requires a card submission.");
|
|
1287
|
+
const directory = await mkdtemp(
|
|
1288
|
+
join4(
|
|
1289
|
+
await privateDirectory(join4(await privateDirectory(walletHome()), "submissions")),
|
|
1290
|
+
"download-"
|
|
1291
|
+
)
|
|
1292
|
+
);
|
|
1293
|
+
try {
|
|
1294
|
+
const path = join4(directory, `${id}.makeables.zip`);
|
|
1295
|
+
await writeFile2(path, bytes, { mode: 384, flag: "wx" });
|
|
1296
|
+
const saved = await readFile6(path);
|
|
1297
|
+
const sha256 = await hashBytes(saved);
|
|
1298
|
+
if (saved.length !== bytes.length || sha256 !== await hashBytes(bytes))
|
|
1299
|
+
throw new Error("Saved submission checksum does not match.");
|
|
1300
|
+
return {
|
|
1301
|
+
path,
|
|
1302
|
+
bytes: saved.length,
|
|
1303
|
+
sha256,
|
|
1304
|
+
submissionId: id,
|
|
1305
|
+
title: pkg.manifest.title,
|
|
1306
|
+
downloadVerified: true,
|
|
1307
|
+
elapsedMs: Math.round(performance.now() - started)
|
|
1308
|
+
};
|
|
1309
|
+
} catch (error) {
|
|
1310
|
+
await rm3(directory, { recursive: true, force: true });
|
|
1311
|
+
throw error;
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1315
|
+
// src/wallet-targets.ts
|
|
1316
|
+
import { lstat as lstat2 } from "fs/promises";
|
|
1317
|
+
import { join as join5 } from "path";
|
|
1318
|
+
import lockfile from "proper-lockfile";
|
|
1319
|
+
import { z as z3 } from "zod";
|
|
1320
|
+
var alias = z3.string().regex(/^[a-z][a-z0-9-]{0,47}$/).refine((value) => !/\d{5}/.test(value));
|
|
1321
|
+
var label = z3.string().trim().min(1).max(60).refine(
|
|
1322
|
+
(value) => !/\d{5}/.test(value) && [...value].every(
|
|
1323
|
+
(character) => character.charCodeAt(0) >= 32 && character.charCodeAt(0) !== 127
|
|
1324
|
+
)
|
|
1325
|
+
);
|
|
1326
|
+
var targetSchema = z3.object({
|
|
1327
|
+
target: alias,
|
|
1328
|
+
label,
|
|
1329
|
+
last4: z3.string().regex(/^\d{4}$/).optional(),
|
|
1330
|
+
identitySource: z3.literal("user-confirmed"),
|
|
1331
|
+
backup: z3.string().min(1),
|
|
1332
|
+
createdAt: z3.string().datetime()
|
|
1333
|
+
}).strict();
|
|
1334
|
+
var registrySchema = z3.object({ version: z3.literal(1), targets: z3.array(targetSchema).max(100) }).strict();
|
|
1335
|
+
function targetIdentity(values, key = "remember") {
|
|
1336
|
+
const result = targetSchema.pick({ target: true, label: true, last4: true }).safeParse({
|
|
1337
|
+
target: values[key],
|
|
1338
|
+
label: values.label,
|
|
1339
|
+
last4: values.last4
|
|
1340
|
+
});
|
|
1341
|
+
if (!result.success)
|
|
1342
|
+
throw new InputError(
|
|
1343
|
+
`Supply --${key} with a lowercase alias and --label with the user-confirmed card name. Optional --last4 must be exactly four digits; never supply a full card number.`
|
|
1344
|
+
);
|
|
1345
|
+
return result.data;
|
|
1346
|
+
}
|
|
1347
|
+
function registryPath() {
|
|
1348
|
+
return join5(walletHome(), "targets", "registry.json");
|
|
1349
|
+
}
|
|
1350
|
+
async function walletTargets() {
|
|
1351
|
+
const path = registryPath();
|
|
1352
|
+
try {
|
|
1353
|
+
for (const parent of [walletHome(), join5(walletHome(), "targets")]) {
|
|
1354
|
+
if (!(await lstat2(parent)).isDirectory())
|
|
1355
|
+
throw new InputError("Invalid Wallet target storage.");
|
|
1356
|
+
}
|
|
1357
|
+
const result = registrySchema.safeParse(await readPrivateJson(path));
|
|
1358
|
+
if (!result.success || new Set(result.data.targets.map((entry) => entry.target)).size !== result.data.targets.length)
|
|
1359
|
+
throw new InputError("Invalid Wallet target registry. Inspect local Wallet storage.");
|
|
1360
|
+
return result.data.targets;
|
|
1361
|
+
} catch (error) {
|
|
1362
|
+
if (error.code === "ENOENT") return [];
|
|
1363
|
+
throw error;
|
|
1364
|
+
}
|
|
1365
|
+
}
|
|
1366
|
+
function targetChoices(targets) {
|
|
1367
|
+
return targets.map(({ target, label: label2, last4, identitySource }) => ({
|
|
1368
|
+
target,
|
|
1369
|
+
label: label2,
|
|
1370
|
+
...last4 ? { last4 } : {},
|
|
1371
|
+
displayName: `${label2}${last4 ? ` \u2022\u2022\u2022\u2022${last4}` : ""}`,
|
|
1372
|
+
identitySource
|
|
1373
|
+
}));
|
|
1374
|
+
}
|
|
1375
|
+
async function withWalletTargets(action) {
|
|
1376
|
+
const directory = await privateDirectory(join5(await privateDirectory(walletHome()), "targets"));
|
|
1377
|
+
const release = await lockfile.lock(directory, { realpath: false, retries: 0 });
|
|
1378
|
+
try {
|
|
1379
|
+
return await action(await walletTargets(), async (targets) => {
|
|
1380
|
+
const registry = registrySchema.parse({ version: 1, targets });
|
|
1381
|
+
await writePrivateJson(registryPath(), registry);
|
|
1382
|
+
});
|
|
1383
|
+
} finally {
|
|
1384
|
+
await release();
|
|
1385
|
+
}
|
|
1386
|
+
}
|
|
1387
|
+
function requireNewTarget(targets, target) {
|
|
1388
|
+
if (targets.some((entry) => entry.target === target))
|
|
1389
|
+
throw new InputError(
|
|
1390
|
+
`Target "${target}" already exists. Choose a new alias; existing bindings are never replaced.`
|
|
1391
|
+
);
|
|
1392
|
+
if (targets.length >= 100) throw new InputError("Remove an unused target before adding another.");
|
|
1393
|
+
}
|
|
1394
|
+
|
|
1171
1395
|
// src/wallet.ts
|
|
1172
1396
|
var WalletError = class extends Error {
|
|
1173
1397
|
constructor(details) {
|
|
@@ -1178,9 +1402,9 @@ var WalletError = class extends Error {
|
|
|
1178
1402
|
};
|
|
1179
1403
|
function installation() {
|
|
1180
1404
|
const platform = `${process.platform}-${process.arch}`;
|
|
1181
|
-
const cache = process.env.MAKEABLES_WALLET_CACHE ??
|
|
1182
|
-
const root =
|
|
1183
|
-
return { platform, root, executable:
|
|
1405
|
+
const cache = process.env.MAKEABLES_WALLET_CACHE ?? join6(homedir3(), "Library", "Caches", "Makeables", "wallet");
|
|
1406
|
+
const root = resolve6(cache, `${wallet_release_default.version}-${platform}`);
|
|
1407
|
+
return { platform, root, executable: join6(root, "makeables-wallet", "makeables-wallet") };
|
|
1184
1408
|
}
|
|
1185
1409
|
async function execute(command, args, progress) {
|
|
1186
1410
|
return new Promise((accept, reject) => {
|
|
@@ -1311,40 +1535,43 @@ async function setupWallet(progress) {
|
|
|
1311
1535
|
const bytes = Buffer.concat(chunks);
|
|
1312
1536
|
if (createHash3("sha256").update(bytes).digest("hex") !== artifact.sha256)
|
|
1313
1537
|
throw new Error("Wallet companion checksum did not match. Nothing was installed.");
|
|
1314
|
-
await
|
|
1315
|
-
const stage = await
|
|
1538
|
+
await mkdir3(dirname3(root), { recursive: true, mode: 448 });
|
|
1539
|
+
const stage = await mkdtemp2(join6(dirname3(root), ".install-"));
|
|
1316
1540
|
try {
|
|
1317
|
-
const archive =
|
|
1318
|
-
await
|
|
1541
|
+
const archive = join6(stage, "companion.tar.gz");
|
|
1542
|
+
await writeFile3(archive, bytes, { mode: 384, flag: "wx" });
|
|
1319
1543
|
const members = (await execute("tar", ["-tzf", archive])).trim().split("\n");
|
|
1320
1544
|
if (members.some(
|
|
1321
1545
|
(path) => !path.startsWith("makeables-wallet/") || path.split("/").includes("..")
|
|
1322
1546
|
))
|
|
1323
1547
|
throw new Error("Unexpected companion archive contents.");
|
|
1324
1548
|
await execute("tar", ["-xzf", archive, "-C", stage]);
|
|
1325
|
-
await
|
|
1326
|
-
await
|
|
1327
|
-
const info = await invoke(
|
|
1549
|
+
await rm4(archive);
|
|
1550
|
+
await chmod2(join6(stage, "makeables-wallet", "makeables-wallet"), 448);
|
|
1551
|
+
const info = await invoke(join6(stage, "makeables-wallet", "makeables-wallet"), ["version"]);
|
|
1328
1552
|
if (info.version !== wallet_release_default.version || info.protocol !== wallet_release_default.protocol)
|
|
1329
1553
|
throw new Error("Companion version does not match the installed CLI.");
|
|
1330
|
-
await
|
|
1554
|
+
await rename3(stage, root);
|
|
1331
1555
|
} finally {
|
|
1332
|
-
await
|
|
1556
|
+
await rm4(stage, { recursive: true, force: true });
|
|
1333
1557
|
}
|
|
1334
1558
|
return walletDoctor();
|
|
1335
1559
|
}
|
|
1336
1560
|
async function walletArtwork(file, side2 = "front") {
|
|
1337
|
-
const input =
|
|
1338
|
-
const info = await
|
|
1339
|
-
if (!info.isFile() || info.size >
|
|
1340
|
-
throw new InputError("Expected
|
|
1561
|
+
const input = resolve6(file);
|
|
1562
|
+
const info = await lstat3(input);
|
|
1563
|
+
if (!info.isFile() || info.size > 336e5)
|
|
1564
|
+
throw new InputError("Expected artwork or a complete Makeables package below 32 MB.");
|
|
1341
1565
|
let source;
|
|
1342
|
-
if (extname3(input).toLowerCase()
|
|
1343
|
-
const
|
|
1344
|
-
if (
|
|
1345
|
-
|
|
1566
|
+
if ([".json", ".zip"].includes(extname3(input).toLowerCase())) {
|
|
1567
|
+
const pkg = await readSubmission(input);
|
|
1568
|
+
if (pkg.manifest.product !== "card")
|
|
1569
|
+
throw new InputError("Wallet artwork requires a card document.");
|
|
1570
|
+
const path = side2 === "front" ? pkg.manifest.front : pkg.manifest.back;
|
|
1571
|
+
if (!path) throw new InputError("This package has no back artwork.");
|
|
1572
|
+
source = Buffer.from(pkg.files[path]);
|
|
1346
1573
|
} else {
|
|
1347
|
-
source = await
|
|
1574
|
+
source = await readFile7(input);
|
|
1348
1575
|
if (extname3(input).toLowerCase() === ".svg") {
|
|
1349
1576
|
const result = checkSubmittedSvg(source.toString("utf8"));
|
|
1350
1577
|
if (!result.ok) throw new InputError(result.error);
|
|
@@ -1353,25 +1580,56 @@ async function walletArtwork(file, side2 = "front") {
|
|
|
1353
1580
|
return sharp7(source, { limitInputPixels: 24e6, density: 144 }).rotate().resize(1536, 969, { fit: "contain", background: "#000000" }).flatten({ background: "#000000" }).toColourspace("srgb").png().toBuffer();
|
|
1354
1581
|
}
|
|
1355
1582
|
async function preparedArtwork(directory) {
|
|
1356
|
-
const path =
|
|
1357
|
-
if (!(await
|
|
1583
|
+
const path = resolve6(directory);
|
|
1584
|
+
if (!(await lstat3(path)).isDirectory())
|
|
1358
1585
|
throw new InputError("--assets must be a prepared artwork directory.");
|
|
1359
1586
|
for (const name of [
|
|
1360
1587
|
"cardBackgroundCombined@3x.png",
|
|
1361
1588
|
"cardBackgroundCombined@2x.png",
|
|
1362
1589
|
"cardBackgroundCombined.pdf"
|
|
1363
1590
|
]) {
|
|
1364
|
-
const file =
|
|
1365
|
-
const info = await
|
|
1591
|
+
const file = join6(path, name);
|
|
1592
|
+
const info = await lstat3(file);
|
|
1366
1593
|
if (!info.isFile() || info.size === 0 || info.size > 16 * 1024 * 1024)
|
|
1367
1594
|
throw new InputError(`Invalid prepared asset: ${name}. Run wallet prep again.`);
|
|
1368
1595
|
const signature = Buffer.from(name.endsWith(".pdf") ? "%PDF-" : "\x89PNG\r\n\n", "latin1");
|
|
1369
|
-
if (!(await
|
|
1596
|
+
if (!(await readFile7(file)).subarray(0, signature.length).equals(signature))
|
|
1370
1597
|
throw new InputError(`Invalid prepared asset: ${name}. Run wallet prep again.`);
|
|
1371
1598
|
}
|
|
1372
|
-
return { path, preview:
|
|
1599
|
+
return { path, preview: join6(path, "cardBackgroundCombined@3x.png") };
|
|
1373
1600
|
}
|
|
1374
1601
|
async function walletCommand(command, values, progress) {
|
|
1602
|
+
if (values.remember) {
|
|
1603
|
+
const identity = targetIdentity(values);
|
|
1604
|
+
return withWalletTargets(async (targets, save) => {
|
|
1605
|
+
requireNewTarget(targets, identity.target);
|
|
1606
|
+
const result = await runWalletCommand(command, values, progress);
|
|
1607
|
+
if (result.status !== "complete") return result;
|
|
1608
|
+
try {
|
|
1609
|
+
if (!result.backupVerified || typeof result.backupId !== "string")
|
|
1610
|
+
throw new Error("The companion did not provide a verified backup binding.");
|
|
1611
|
+
await save([
|
|
1612
|
+
...targets,
|
|
1613
|
+
{
|
|
1614
|
+
...identity,
|
|
1615
|
+
backup: result.backupId,
|
|
1616
|
+
identitySource: "user-confirmed",
|
|
1617
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1618
|
+
}
|
|
1619
|
+
]);
|
|
1620
|
+
return { ...result, targetSaved: true, target: identity.target };
|
|
1621
|
+
} catch {
|
|
1622
|
+
return {
|
|
1623
|
+
...result,
|
|
1624
|
+
targetSaved: false,
|
|
1625
|
+
warning: "Artwork installed, but the card label was not saved. Use wallet targets add with the verified backup; do not repeat the installation."
|
|
1626
|
+
};
|
|
1627
|
+
}
|
|
1628
|
+
});
|
|
1629
|
+
}
|
|
1630
|
+
return runWalletCommand(command, values, progress);
|
|
1631
|
+
}
|
|
1632
|
+
async function runWalletCommand(command, values, progress) {
|
|
1375
1633
|
const started = performance.now();
|
|
1376
1634
|
const elapsed = () => Math.round(performance.now() - started);
|
|
1377
1635
|
let latestPhase;
|
|
@@ -1388,9 +1646,12 @@ async function walletCommand(command, values, progress) {
|
|
|
1388
1646
|
const forward = (data) => {
|
|
1389
1647
|
if (data && typeof data === "object") {
|
|
1390
1648
|
if ("phase" in data && typeof data.phase === "string") latestPhase = data.phase;
|
|
1649
|
+
const ready = latestPhase === "selection-ready" || "message" in data && data.message === "Connected to iPhone activity stream.";
|
|
1650
|
+
if (ready) latestPhase = "selection-ready";
|
|
1391
1651
|
progress({
|
|
1392
1652
|
...data,
|
|
1393
1653
|
phase: latestPhase,
|
|
1654
|
+
...ready ? { ready: true, message: "Listening. Open Wallet and tap the exact card to customize." } : {},
|
|
1394
1655
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1395
1656
|
elapsedMs: elapsed()
|
|
1396
1657
|
});
|
|
@@ -1403,6 +1664,47 @@ async function walletCommand(command, values, progress) {
|
|
|
1403
1664
|
};
|
|
1404
1665
|
if (command === "wallet doctor") return walletDoctor();
|
|
1405
1666
|
if (command === "wallet setup") return setupWallet(forward);
|
|
1667
|
+
if (command === "wallet targets list") return { targets: targetChoices(await walletTargets()) };
|
|
1668
|
+
if (command === "wallet targets remove") {
|
|
1669
|
+
const name = need("target");
|
|
1670
|
+
return withWalletTargets(async (targets, save) => {
|
|
1671
|
+
if (!targets.some((entry) => entry.target === name))
|
|
1672
|
+
throw new InputError("Unknown saved target.");
|
|
1673
|
+
await save(targets.filter((entry) => entry.target !== name));
|
|
1674
|
+
return { removed: name, backupPreserved: true };
|
|
1675
|
+
});
|
|
1676
|
+
}
|
|
1677
|
+
if (command === "wallet targets add") {
|
|
1678
|
+
const identity = targetIdentity(values, "target");
|
|
1679
|
+
const backup = need("backup");
|
|
1680
|
+
if (!values.yes)
|
|
1681
|
+
throw new InputError("Use --yes after the user confirms which card this backup belongs to.");
|
|
1682
|
+
return withWalletTargets(async (targets, save) => {
|
|
1683
|
+
requireNewTarget(targets, identity.target);
|
|
1684
|
+
const info2 = await walletDoctor();
|
|
1685
|
+
if (!info2.ready || !("installed" in info2) || typeof info2.installed !== "string")
|
|
1686
|
+
throw new InputError("Run makeables wallet setup first.");
|
|
1687
|
+
const verified = await invoke(info2.installed, ["backups", "--backup", backup], forward);
|
|
1688
|
+
if (verified.verified !== true || typeof verified.path !== "string" || !["complete", "restored"].includes(String(verified.status)))
|
|
1689
|
+
throw new InputError("Choose a verified original backup from a completed operation.");
|
|
1690
|
+
const entry = {
|
|
1691
|
+
...identity,
|
|
1692
|
+
backup: verified.path,
|
|
1693
|
+
identitySource: "user-confirmed",
|
|
1694
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1695
|
+
};
|
|
1696
|
+
await save([...targets, entry]);
|
|
1697
|
+
return { targetSaved: true, ...targetChoices([entry])[0] };
|
|
1698
|
+
});
|
|
1699
|
+
}
|
|
1700
|
+
const targetCount = [values.target, values.card, values.backup].filter(Boolean).length;
|
|
1701
|
+
if (targetCount > 1)
|
|
1702
|
+
throw new InputError("Choose exactly one --target, --card selection or --backup original.");
|
|
1703
|
+
if (values.target) {
|
|
1704
|
+
const target = (await walletTargets()).find((entry) => entry.target === values.target);
|
|
1705
|
+
if (!target) throw new InputError("Unknown saved target. Run makeables wallet targets list.");
|
|
1706
|
+
values = { ...values, backup: target.backup };
|
|
1707
|
+
}
|
|
1406
1708
|
const installing = command === "wallet install";
|
|
1407
1709
|
const applying = command === "wallet apply" || installing && Boolean(values.card || values.backup);
|
|
1408
1710
|
if (applying || command === "wallet restore" || command === "wallet recover") {
|
|
@@ -1414,29 +1716,44 @@ async function walletCommand(command, values, progress) {
|
|
|
1414
1716
|
if (applying && Boolean(values.card) === Boolean(values.backup))
|
|
1415
1717
|
throw new InputError("Choose exactly one --card selection or --backup original.");
|
|
1416
1718
|
if (installing && values.yes && !applying)
|
|
1417
|
-
throw new InputError("Select an explicit --card or --backup before using --yes.");
|
|
1719
|
+
throw new InputError("Select an explicit --target, --card or --backup before using --yes.");
|
|
1720
|
+
if (values.scan && (applying || values.yes))
|
|
1721
|
+
throw new InputError("--scan discovers a new card; omit --target, --card, --backup and --yes.");
|
|
1722
|
+
if ((values.label || values.last4) && !values.remember)
|
|
1723
|
+
throw new InputError("--label and --last4 require --remember.");
|
|
1724
|
+
if (values.remember && (!installing || values.target || values.backup))
|
|
1725
|
+
throw new InputError(
|
|
1726
|
+
"--remember is for a newly selected card. Use wallet targets add for an existing backup."
|
|
1727
|
+
);
|
|
1418
1728
|
if (installing && applying && (values.device || values.seconds))
|
|
1419
1729
|
throw new InputError(
|
|
1420
|
-
"--device and --seconds are for selection; --card or --backup already binds the target."
|
|
1730
|
+
"--device and --seconds are for selection; --target, --card or --backup already binds the target."
|
|
1421
1731
|
);
|
|
1422
1732
|
if (values.seconds && (!/^\d+$/.test(String(values.seconds)) || Number(values.seconds) < 1 || Number(values.seconds) > 300))
|
|
1423
1733
|
throw new InputError("--seconds must be between 1 and 300.");
|
|
1424
|
-
if (installing || applying) {
|
|
1425
|
-
if (
|
|
1426
|
-
throw new InputError(
|
|
1734
|
+
if (installing || applying || command === "wallet prep") {
|
|
1735
|
+
if ([values.file, values.assets, values.submission].filter(Boolean).length !== 1)
|
|
1736
|
+
throw new InputError(
|
|
1737
|
+
"Supply exactly one --file artwork, --submission URL or UUID, or --assets prepared directory."
|
|
1738
|
+
);
|
|
1427
1739
|
if (values.assets && values.side)
|
|
1428
|
-
throw new InputError("--side applies to --file, not
|
|
1740
|
+
throw new InputError("--side applies to --file or --submission, not prepared --assets.");
|
|
1429
1741
|
}
|
|
1430
1742
|
if (values.side && !["front", "back"].includes(String(values.side)))
|
|
1431
1743
|
throw new InputError("--side must be front or back.");
|
|
1432
1744
|
if (command === "wallet prep") {
|
|
1433
|
-
need("file");
|
|
1434
1745
|
need("out");
|
|
1435
1746
|
}
|
|
1436
1747
|
if (command === "wallet restore") need("backup");
|
|
1437
1748
|
const existing = values.assets ? await preparedArtwork(need("assets")) : void 0;
|
|
1749
|
+
let source;
|
|
1750
|
+
if (values.submission) {
|
|
1751
|
+
report("download", "Downloading and verifying the complete card package.");
|
|
1752
|
+
source = await downloadWalletSubmission(need("submission"));
|
|
1753
|
+
values = { ...values, file: String(source.path) };
|
|
1754
|
+
}
|
|
1438
1755
|
if (installing) report("setup", "Checking the Wallet companion.");
|
|
1439
|
-
const info = installing ? await setupWallet(forward) : await walletDoctor();
|
|
1756
|
+
const info = installing || command === "wallet prep" ? await setupWallet(forward) : await walletDoctor();
|
|
1440
1757
|
if (!info.ready || !("installed" in info) || typeof info.installed !== "string")
|
|
1441
1758
|
throw new InputError("Run makeables wallet setup first.");
|
|
1442
1759
|
const executable = info.installed;
|
|
@@ -1489,7 +1806,7 @@ async function walletCommand(command, values, progress) {
|
|
|
1489
1806
|
}
|
|
1490
1807
|
}
|
|
1491
1808
|
if (command === "wallet prep" || applying || installing) {
|
|
1492
|
-
const work = await
|
|
1809
|
+
const work = await mkdtemp2(join6(tmpdir2(), "makeables-wallet-"));
|
|
1493
1810
|
let phase = "prepare";
|
|
1494
1811
|
let deviceWriteAttempted = false;
|
|
1495
1812
|
let persistent;
|
|
@@ -1497,27 +1814,23 @@ async function walletCommand(command, values, progress) {
|
|
|
1497
1814
|
try {
|
|
1498
1815
|
let target;
|
|
1499
1816
|
if (existing) {
|
|
1500
|
-
target =
|
|
1817
|
+
target = resolve6(need("assets"));
|
|
1501
1818
|
prepared = existing;
|
|
1502
1819
|
} else {
|
|
1503
1820
|
report("prepare", "Preparing the complete card artwork.");
|
|
1504
1821
|
const png = await walletArtwork(need("file"), values.side === "back" ? "back" : "front");
|
|
1505
|
-
const raster =
|
|
1506
|
-
await
|
|
1822
|
+
const raster = join6(work, "artwork.png");
|
|
1823
|
+
await writeFile3(raster, png, { mode: 384 });
|
|
1507
1824
|
if (command === "wallet prep") {
|
|
1508
|
-
target =
|
|
1825
|
+
target = resolve6(need("out"));
|
|
1509
1826
|
} else if (installing && !applying) {
|
|
1510
|
-
const root =
|
|
1511
|
-
|
|
1512
|
-
"prepared"
|
|
1827
|
+
const root = await privateDirectory(
|
|
1828
|
+
join6(await privateDirectory(walletHome()), "prepared")
|
|
1513
1829
|
);
|
|
1514
|
-
await
|
|
1515
|
-
|
|
1516
|
-
throw new InputError("Prepared artwork storage cannot be a symbolic link.");
|
|
1517
|
-
persistent = await mkdtemp(join3(root, "install-"));
|
|
1518
|
-
target = join3(persistent, "assets");
|
|
1830
|
+
persistent = await mkdtemp2(join6(root, "install-"));
|
|
1831
|
+
target = join6(persistent, "assets");
|
|
1519
1832
|
} else {
|
|
1520
|
-
target =
|
|
1833
|
+
target = join6(work, "assets");
|
|
1521
1834
|
}
|
|
1522
1835
|
prepared = await invoke(
|
|
1523
1836
|
executable,
|
|
@@ -1526,10 +1839,32 @@ async function walletCommand(command, values, progress) {
|
|
|
1526
1839
|
);
|
|
1527
1840
|
await preparedArtwork(target);
|
|
1528
1841
|
}
|
|
1529
|
-
if (command === "wallet prep") return prepared;
|
|
1842
|
+
if (command === "wallet prep") return { ...prepared, source, elapsedMs: elapsed() };
|
|
1530
1843
|
if (installing && !applying) {
|
|
1531
1844
|
phase = "selection";
|
|
1532
|
-
|
|
1845
|
+
const continuation = ["--assets", target];
|
|
1846
|
+
for (const flag of ["remember", "label", "last4"])
|
|
1847
|
+
if (typeof values[flag] === "string")
|
|
1848
|
+
continuation.push(`--${flag}`, String(values[flag]));
|
|
1849
|
+
const saved = await walletTargets();
|
|
1850
|
+
if (saved.length && !values.scan && !values.device)
|
|
1851
|
+
return {
|
|
1852
|
+
status: "needs-target",
|
|
1853
|
+
targets: targetChoices(saved),
|
|
1854
|
+
source,
|
|
1855
|
+
assets: target,
|
|
1856
|
+
preview: prepared.preview,
|
|
1857
|
+
deviceWriteAttempted: false,
|
|
1858
|
+
nextAction: "choose-saved-target-or-scan",
|
|
1859
|
+
next: {
|
|
1860
|
+
command: "wallet install",
|
|
1861
|
+
args: ["--assets", target],
|
|
1862
|
+
required: ["target", "yes"],
|
|
1863
|
+
alternative: { args: [...continuation, "--scan"], required: [] }
|
|
1864
|
+
},
|
|
1865
|
+
elapsedMs: elapsed()
|
|
1866
|
+
};
|
|
1867
|
+
report("selection", "Artwork is ready. Connecting to the iPhone activity stream.");
|
|
1533
1868
|
const args2 = ["list"];
|
|
1534
1869
|
for (const flag of ["device", "seconds"])
|
|
1535
1870
|
if (typeof values[flag] === "string") args2.push(`--${flag}`, String(values[flag]));
|
|
@@ -1542,11 +1877,12 @@ async function walletCommand(command, values, progress) {
|
|
|
1542
1877
|
status,
|
|
1543
1878
|
assets: target,
|
|
1544
1879
|
preview: prepared.preview,
|
|
1880
|
+
source,
|
|
1545
1881
|
deviceWriteAttempted: false,
|
|
1546
1882
|
nextAction: selection.needsDevice ? "select-device" : hasCards ? "confirm-card" : "tap-card",
|
|
1547
1883
|
next: {
|
|
1548
1884
|
command: "wallet install",
|
|
1549
|
-
args: ["--
|
|
1885
|
+
args: !hasCards && (values.scan || saved.length) ? [...continuation, "--scan"] : continuation,
|
|
1550
1886
|
required: selection.needsDevice ? ["device"] : hasCards ? ["card", "yes"] : []
|
|
1551
1887
|
},
|
|
1552
1888
|
elapsedMs: elapsed()
|
|
@@ -1559,7 +1895,12 @@ async function walletCommand(command, values, progress) {
|
|
|
1559
1895
|
phase = "apply";
|
|
1560
1896
|
deviceWriteAttempted = true;
|
|
1561
1897
|
const result = await invoke(executable, args, forward);
|
|
1562
|
-
return {
|
|
1898
|
+
return {
|
|
1899
|
+
...result,
|
|
1900
|
+
...values.target ? { target: values.target } : {},
|
|
1901
|
+
source,
|
|
1902
|
+
elapsedMs: elapsed()
|
|
1903
|
+
};
|
|
1563
1904
|
} catch (error) {
|
|
1564
1905
|
if (error instanceof WalletError) {
|
|
1565
1906
|
throw new WalletError({
|
|
@@ -1575,8 +1916,8 @@ async function walletCommand(command, values, progress) {
|
|
|
1575
1916
|
}
|
|
1576
1917
|
throw error;
|
|
1577
1918
|
} finally {
|
|
1578
|
-
await
|
|
1579
|
-
if (persistent && phase === "prepare") await
|
|
1919
|
+
await rm4(work, { recursive: true, force: true });
|
|
1920
|
+
if (persistent && phase === "prepare") await rm4(persistent, { recursive: true, force: true });
|
|
1580
1921
|
}
|
|
1581
1922
|
}
|
|
1582
1923
|
throw new InputError("Unknown Wallet command.");
|
|
@@ -1597,9 +1938,19 @@ function walletSummary(result) {
|
|
|
1597
1938
|
`Wallet artwork installed${seconds}.`,
|
|
1598
1939
|
result.backupVerified ? "Original artwork backup saved and verified." : "Original artwork backup saved.",
|
|
1599
1940
|
...typeof result.backup === "string" ? [`Backup: ${result.backup}`] : [],
|
|
1600
|
-
typeof result.refresh === "string" ? result.refresh : "Close and reopen Wallet to see the design."
|
|
1941
|
+
typeof result.refresh === "string" ? result.refresh : "Close and reopen Wallet to see the design.",
|
|
1942
|
+
...typeof result.warning === "string" ? [result.warning] : []
|
|
1601
1943
|
].join("\n");
|
|
1602
1944
|
}
|
|
1945
|
+
if (result.status === "needs-target")
|
|
1946
|
+
return [
|
|
1947
|
+
"Artwork prepared. No card has been changed.",
|
|
1948
|
+
...(result.targets ?? []).map(
|
|
1949
|
+
(entry) => `${entry.target}: ${entry.displayName} (user-confirmed)`
|
|
1950
|
+
),
|
|
1951
|
+
"Choose a saved --target with --yes, or use --scan and tap a different card after selection-ready.",
|
|
1952
|
+
`Prepared assets: ${String(result.assets)}`
|
|
1953
|
+
].join("\n");
|
|
1603
1954
|
if (["needs-device", "needs-selection", "needs-card"].includes(String(result.status)))
|
|
1604
1955
|
return [
|
|
1605
1956
|
"Artwork prepared. No card has been changed.",
|
|
@@ -1641,9 +1992,12 @@ var commands = [
|
|
|
1641
1992
|
),
|
|
1642
1993
|
"wallet setup",
|
|
1643
1994
|
"wallet doctor",
|
|
1644
|
-
"wallet install (--file <artwork> | --assets <directory>) [(--card <selection> | --backup <id|path>) --yes]",
|
|
1995
|
+
"wallet install (--submission <UUID|URL> | --file <artwork|package> | --assets <directory>) [(--target <alias> | --card <selection> | --backup <id|path>) --yes] [--scan] [--remember <alias> --label <name> --last4 <digits>]",
|
|
1996
|
+
"wallet targets list",
|
|
1997
|
+
"wallet targets add --target <alias> --backup <id|path> --label <name> [--last4 <digits>] --yes",
|
|
1998
|
+
"wallet targets remove --target <alias>",
|
|
1645
1999
|
"wallet list [--device <selection>] [--seconds 45] [--first]",
|
|
1646
|
-
"wallet prep --file <card.json|artwork.svg|image.png> --out <new-directory> [--side front|back]",
|
|
2000
|
+
"wallet prep (--submission <UUID|URL> | --file <card.json|package.zip|artwork.svg|image.png>) --out <new-directory> [--side front|back]",
|
|
1647
2001
|
"wallet apply (--file <artwork> | --assets <directory>) (--card <selection> | --backup <id|path>) --yes",
|
|
1648
2002
|
"wallet restore --backup <id|path> --yes",
|
|
1649
2003
|
"wallet backups [--backup <id|path>]",
|
|
@@ -1681,14 +2035,14 @@ function emit(data, next, human, json) {
|
|
|
1681
2035
|
);
|
|
1682
2036
|
}
|
|
1683
2037
|
async function newPath(file) {
|
|
1684
|
-
const path =
|
|
2038
|
+
const path = resolve7(file);
|
|
1685
2039
|
try {
|
|
1686
|
-
await
|
|
2040
|
+
await lstat4(path);
|
|
1687
2041
|
throw new InputError(`File already exists: ${path}. Choose a new --out path.`);
|
|
1688
2042
|
} catch (error) {
|
|
1689
2043
|
if (error.code !== "ENOENT") throw error;
|
|
1690
2044
|
}
|
|
1691
|
-
await access(
|
|
2045
|
+
await access(dirname4(path), constants3.W_OK);
|
|
1692
2046
|
return path;
|
|
1693
2047
|
}
|
|
1694
2048
|
async function run() {
|
|
@@ -1738,6 +2092,11 @@ async function run() {
|
|
|
1738
2092
|
seconds: { type: "string" },
|
|
1739
2093
|
assets: { type: "string" },
|
|
1740
2094
|
first: { type: "boolean" },
|
|
2095
|
+
submission: { type: "string" },
|
|
2096
|
+
scan: { type: "boolean" },
|
|
2097
|
+
remember: { type: "string" },
|
|
2098
|
+
label: { type: "string" },
|
|
2099
|
+
last4: { type: "string" },
|
|
1741
2100
|
type: { type: "string" },
|
|
1742
2101
|
size: { type: "string" },
|
|
1743
2102
|
"width-mm": { type: "string" },
|
|
@@ -1750,16 +2109,19 @@ async function run() {
|
|
|
1750
2109
|
return;
|
|
1751
2110
|
}
|
|
1752
2111
|
if (values.help || !positionals.length) {
|
|
2112
|
+
const prefix = positionals.join(" ");
|
|
2113
|
+
const helpCommands = prefix ? commands.filter((entry) => entry === prefix || entry.startsWith(`${prefix} `)) : commands;
|
|
2114
|
+
if (!helpCommands.length) throw new InputError(`Unknown command: ${prefix}`);
|
|
1753
2115
|
if (!json) banner(version);
|
|
1754
2116
|
emit(
|
|
1755
2117
|
{
|
|
1756
|
-
commands,
|
|
2118
|
+
commands: helpCommands,
|
|
1757
2119
|
flags: ["--json", "--help", "--version"],
|
|
1758
2120
|
exitCodes: { success: 0, invalidInput: 2, systemFailure: 1 }
|
|
1759
2121
|
},
|
|
1760
2122
|
nextSteps,
|
|
1761
2123
|
`${style("Commands", "1", !json)}
|
|
1762
|
-
${
|
|
2124
|
+
${helpCommands.map((command2) => ` npx makeables ${command2}`).join("\n")}
|
|
1763
2125
|
|
|
1764
2126
|
JSON is automatic when piped. --out creates new files only.
|
|
1765
2127
|
Install globally: npm install --global makeables
|
|
@@ -1775,9 +2137,27 @@ From a built checkout: npm run studio -- <command>`,
|
|
|
1775
2137
|
),
|
|
1776
2138
|
"wallet setup": [],
|
|
1777
2139
|
"wallet doctor": [],
|
|
1778
|
-
"wallet install": [
|
|
2140
|
+
"wallet install": [
|
|
2141
|
+
"submission",
|
|
2142
|
+
"file",
|
|
2143
|
+
"assets",
|
|
2144
|
+
"target",
|
|
2145
|
+
"card",
|
|
2146
|
+
"backup",
|
|
2147
|
+
"side",
|
|
2148
|
+
"device",
|
|
2149
|
+
"seconds",
|
|
2150
|
+
"yes",
|
|
2151
|
+
"scan",
|
|
2152
|
+
"remember",
|
|
2153
|
+
"label",
|
|
2154
|
+
"last4"
|
|
2155
|
+
],
|
|
2156
|
+
"wallet targets list": [],
|
|
2157
|
+
"wallet targets add": ["target", "backup", "label", "last4", "yes"],
|
|
2158
|
+
"wallet targets remove": ["target"],
|
|
1779
2159
|
"wallet list": ["device", "seconds", "first"],
|
|
1780
|
-
"wallet prep": ["file", "out", "side"],
|
|
2160
|
+
"wallet prep": ["submission", "file", "out", "side"],
|
|
1781
2161
|
"wallet apply": ["file", "assets", "card", "backup", "side", "yes"],
|
|
1782
2162
|
"wallet restore": ["backup", "yes"],
|
|
1783
2163
|
"wallet backups": ["backup"],
|
|
@@ -2015,7 +2395,7 @@ Run makeables skills get core for setup.`,
|
|
|
2015
2395
|
}
|
|
2016
2396
|
if (command === "mcp") {
|
|
2017
2397
|
try {
|
|
2018
|
-
const { runMcp } = await import("./mcp-
|
|
2398
|
+
const { runMcp } = await import("./mcp-HS2XZXSO.js");
|
|
2019
2399
|
await runMcp(values.root ?? process.cwd());
|
|
2020
2400
|
} catch (error) {
|
|
2021
2401
|
process.stderr.write(
|
|
@@ -2071,10 +2451,10 @@ Keep this process running while you design.`,
|
|
|
2071
2451
|
if (positionals.length !== 3) throw new InputError("Supply one discovered tool name.");
|
|
2072
2452
|
if (values.params?.startsWith("@")) {
|
|
2073
2453
|
const path2 = values.params.slice(1);
|
|
2074
|
-
const info = await
|
|
2454
|
+
const info = await lstat4(path2);
|
|
2075
2455
|
if (!info.isFile() || info.size > 12e6)
|
|
2076
2456
|
throw new InputError("Expected a params file below 12 MB.");
|
|
2077
|
-
params = JSON.parse(await
|
|
2457
|
+
params = JSON.parse(await readFile8(path2, "utf8"));
|
|
2078
2458
|
} else if (values.params) params = JSON.parse(values.params);
|
|
2079
2459
|
}
|
|
2080
2460
|
const result2 = await studioRequest(
|
|
@@ -2157,7 +2537,7 @@ ${styles.map((entry) => `${column(entry.id ?? "", 25)}${entry.name}`).join("\n")
|
|
|
2157
2537
|
const path2 = values.out ? await newPath(values.out) : null;
|
|
2158
2538
|
const dryRun = Boolean(values["dry-run"]);
|
|
2159
2539
|
if (path2 && !dryRun) {
|
|
2160
|
-
const handle2 = await
|
|
2540
|
+
const handle2 = await open3(path2, "wx", 384);
|
|
2161
2541
|
try {
|
|
2162
2542
|
await handle2.writeFile(`${JSON.stringify(design, null, 2)}
|
|
2163
2543
|
`);
|
|
@@ -2180,14 +2560,14 @@ ${dryRun ? "Dry run. No files changed." : "2 faces \xB7 editable layers"}`,
|
|
|
2180
2560
|
return;
|
|
2181
2561
|
}
|
|
2182
2562
|
if (!values.file) throw new InputError("Supply --file to validate a document.");
|
|
2183
|
-
const path =
|
|
2184
|
-
const handle = await
|
|
2563
|
+
const path = resolve7(values.file);
|
|
2564
|
+
const handle = await open3(path, "r");
|
|
2185
2565
|
let input;
|
|
2186
2566
|
try {
|
|
2187
2567
|
const info = await handle.stat();
|
|
2188
2568
|
if (!info.isFile() || info.size > 2 * 1024 * 1024)
|
|
2189
2569
|
throw new InputError("Expected a JSON file smaller than 2 MB.");
|
|
2190
|
-
input = JSON.parse(await
|
|
2570
|
+
input = JSON.parse(await readFile8(handle, "utf8"));
|
|
2191
2571
|
} finally {
|
|
2192
2572
|
await handle.close();
|
|
2193
2573
|
}
|
|
@@ -2206,7 +2586,7 @@ Schema and layout constraints passed.`,
|
|
|
2206
2586
|
}
|
|
2207
2587
|
async function readParams(value) {
|
|
2208
2588
|
return JSON.parse(
|
|
2209
|
-
value.startsWith("@") ? await
|
|
2589
|
+
value.startsWith("@") ? await readFile8(resolve7(value.slice(1)), "utf8") : value
|
|
2210
2590
|
);
|
|
2211
2591
|
}
|
|
2212
2592
|
async function cardCommand(command, values, json) {
|
|
@@ -2268,7 +2648,7 @@ ${path ?? `No file written. Add --out ${kind}.json.`}`,
|
|
|
2268
2648
|
return true;
|
|
2269
2649
|
}
|
|
2270
2650
|
if (command === "material map") {
|
|
2271
|
-
const path =
|
|
2651
|
+
const path = resolve7(need(file, "file"));
|
|
2272
2652
|
const next = await bindStickerMap(
|
|
2273
2653
|
await readBadge(path),
|
|
2274
2654
|
need(values.id, "id"),
|
|
@@ -2284,10 +2664,10 @@ ${path ?? `No file written. Add --out ${kind}.json.`}`,
|
|
|
2284
2664
|
return true;
|
|
2285
2665
|
}
|
|
2286
2666
|
if (command === "material set") {
|
|
2287
|
-
const path =
|
|
2667
|
+
const path = resolve7(need(file, "file"));
|
|
2288
2668
|
const design = await readBadge(path);
|
|
2289
2669
|
const raw = need(values.params, "params");
|
|
2290
|
-
const input = JSON.parse(raw.startsWith("@") ? await
|
|
2670
|
+
const input = JSON.parse(raw.startsWith("@") ? await readFile8(raw.slice(1), "utf8") : raw);
|
|
2291
2671
|
if (design.product === "sticker") {
|
|
2292
2672
|
const patch2 = stickerMaterialSchema.partial().strict().safeParse(input);
|
|
2293
2673
|
if (!patch2.success)
|
|
@@ -2371,7 +2751,7 @@ ${path ?? `No file written. Add --out ${kind}.json.`}`,
|
|
|
2371
2751
|
await readBadge(path),
|
|
2372
2752
|
face,
|
|
2373
2753
|
need(values.sticker, "sticker"),
|
|
2374
|
-
|
|
2754
|
+
dirname4(resolve7(path))
|
|
2375
2755
|
);
|
|
2376
2756
|
await writeAtomic(path, `${JSON.stringify(design, null, 2)}
|
|
2377
2757
|
`);
|
|
@@ -2386,7 +2766,7 @@ ${path ?? `No file written. Add --out ${kind}.json.`}`,
|
|
|
2386
2766
|
}
|
|
2387
2767
|
if (command.startsWith("sheet ")) {
|
|
2388
2768
|
const write = async (target, bytes) => {
|
|
2389
|
-
const handle = await
|
|
2769
|
+
const handle = await open3(target, values.overwrite ? "w" : "wx", 420);
|
|
2390
2770
|
try {
|
|
2391
2771
|
await handle.writeFile(bytes);
|
|
2392
2772
|
} finally {
|
|
@@ -2465,9 +2845,9 @@ ${path ?? `No file written. Add --out ${kind}.json.`}`,
|
|
|
2465
2845
|
throw new InputError(
|
|
2466
2846
|
`The sheet is not printable. Run makeables sheet check --file ${JSON.stringify(path)}.`
|
|
2467
2847
|
);
|
|
2468
|
-
const out =
|
|
2848
|
+
const out = resolve7(need(values.out, "out"));
|
|
2469
2849
|
if (!out.endsWith(".pdf")) throw new InputError("--out must end in .pdf.");
|
|
2470
|
-
const preview = typeof values.preview === "string" ?
|
|
2850
|
+
const preview = typeof values.preview === "string" ? resolve7(values.preview) : void 0;
|
|
2471
2851
|
if (preview && !preview.endsWith(".png")) throw new InputError("--preview must end in .png.");
|
|
2472
2852
|
const targets = [out, ...preview ? [preview] : []];
|
|
2473
2853
|
if (!values.overwrite) for (const target of targets) await newPath(target);
|
|
@@ -2485,12 +2865,12 @@ ${path ?? `No file written. Add --out ${kind}.json.`}`,
|
|
|
2485
2865
|
if (command === "cut") {
|
|
2486
2866
|
const design = await readBadge(need(file, "file"));
|
|
2487
2867
|
if (design.product !== "sticker") throw new InputError("cut applies to sticker designs.");
|
|
2488
|
-
const out = typeof values.out === "string" ?
|
|
2868
|
+
const out = typeof values.out === "string" ? resolve7(values.out) : void 0;
|
|
2489
2869
|
if (out && !out.endsWith(".svg")) throw new InputError("--out must end in .svg.");
|
|
2490
2870
|
const target = out && (values.overwrite ? out : await newPath(out));
|
|
2491
2871
|
const { cut, svg } = await stickerPrintCut(design);
|
|
2492
2872
|
if (target) {
|
|
2493
|
-
const handle = await
|
|
2873
|
+
const handle = await open3(target, values.overwrite ? "w" : "wx", 420);
|
|
2494
2874
|
try {
|
|
2495
2875
|
await handle.writeFile(svg);
|
|
2496
2876
|
} finally {
|
|
@@ -2523,14 +2903,14 @@ ${path ?? `No file written. Add --out ${kind}.json.`}`,
|
|
|
2523
2903
|
}
|
|
2524
2904
|
if (command === "render") {
|
|
2525
2905
|
const path = need(file, "file");
|
|
2526
|
-
const out =
|
|
2906
|
+
const out = resolve7(need(values.out, "out"));
|
|
2527
2907
|
if (!out.endsWith(".png")) throw new InputError("--out must end in .png.");
|
|
2528
2908
|
const target = values.overwrite ? out : await newPath(out);
|
|
2529
2909
|
const number = values.number === void 0 ? void 0 : Number(values.number);
|
|
2530
2910
|
if (number !== void 0 && !Number.isInteger(number))
|
|
2531
2911
|
throw new InputError("--number must be an integer.");
|
|
2532
2912
|
const face = side(values.side);
|
|
2533
|
-
const value = JSON.parse(await
|
|
2913
|
+
const value = JSON.parse(await readFile8(resolve7(path), "utf8"));
|
|
2534
2914
|
const pkg = value?.format === "makeables-package" ? await readPackageJson(value) : void 0;
|
|
2535
2915
|
if (pkg && ["photo", "name", "role", "number"].some((key) => values[key] !== void 0))
|
|
2536
2916
|
throw new InputError(
|
|
@@ -2554,7 +2934,7 @@ ${path ?? `No file written. Add --out ${kind}.json.`}`,
|
|
|
2554
2934
|
...number === void 0 ? {} : { number }
|
|
2555
2935
|
}
|
|
2556
2936
|
});
|
|
2557
|
-
const handle = await
|
|
2937
|
+
const handle = await open3(target, values.overwrite ? "w" : "wx", 420);
|
|
2558
2938
|
try {
|
|
2559
2939
|
await handle.writeFile(png);
|
|
2560
2940
|
} finally {
|
|
@@ -2592,9 +2972,9 @@ ${path ?? `No file written. Add --out ${kind}.json.`}`,
|
|
|
2592
2972
|
return true;
|
|
2593
2973
|
}
|
|
2594
2974
|
if (command === "validate") {
|
|
2595
|
-
const path =
|
|
2975
|
+
const path = resolve7(need(file, "file"));
|
|
2596
2976
|
if (extname4(path).toLowerCase() === ".svg") {
|
|
2597
|
-
const source = await
|
|
2977
|
+
const source = await readFile8(path, "utf8");
|
|
2598
2978
|
const result2 = checkSubmittedSvg(source);
|
|
2599
2979
|
if (!result2.ok) throw new InputError(result2.error);
|
|
2600
2980
|
const preview = values.out ? await newPath(need(values.out, "out")) : void 0;
|
|
@@ -2615,7 +2995,7 @@ ${path ?? `No file written. Add --out ${kind}.json.`}`,
|
|
|
2615
2995
|
return true;
|
|
2616
2996
|
}
|
|
2617
2997
|
if (values.out) throw new InputError("--out is only used to preview SVG validation.");
|
|
2618
|
-
const input = JSON.parse(await
|
|
2998
|
+
const input = JSON.parse(await readFile8(path, "utf8"));
|
|
2619
2999
|
const result = badgeDesignSchema.safeParse(input);
|
|
2620
3000
|
if (!result.success)
|
|
2621
3001
|
throw new InputError(
|