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/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-NOES67KG.js";
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 constants2 } from "fs";
60
- import { access, lstat as lstat2, open as open2, readFile as readFile7 } from "fs/promises";
61
- import { dirname as dirname3, extname as extname4, resolve as resolve6 } from "path";
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 ("success" in value) {
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 mkdir2, mkdtemp, readFile as readFile6, rename as rename2, rm as rm2, writeFile as writeFile2 } from "fs/promises";
1150
- import { homedir as homedir2, tmpdir as tmpdir2 } from "os";
1151
- import { dirname as dirname2, extname as extname3, join as join3, resolve as resolve5 } from "path";
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.0",
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.0-darwin-arm64.tar.gz",
1162
- sha256: "ebd275228d84c75836820e12a999bcaa7e586dfbb215ba06882ae161a2e4a09f"
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.0-darwin-x64.tar.gz",
1166
- sha256: "c5bd51863895e6612be838b5023f7a1322424568ea4249651125930a66ff4ccb"
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 ?? join3(homedir2(), "Library", "Caches", "Makeables", "wallet");
1182
- const root = resolve5(cache, `${wallet_release_default.version}-${platform}`);
1183
- return { platform, root, executable: join3(root, "makeables-wallet", "makeables-wallet") };
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 mkdir2(dirname2(root), { recursive: true, mode: 448 });
1315
- const stage = await mkdtemp(join3(dirname2(root), ".install-"));
1538
+ await mkdir3(dirname3(root), { recursive: true, mode: 448 });
1539
+ const stage = await mkdtemp2(join6(dirname3(root), ".install-"));
1316
1540
  try {
1317
- const archive = join3(stage, "companion.tar.gz");
1318
- await writeFile2(archive, bytes, { mode: 384, flag: "wx" });
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 rm2(archive);
1326
- await chmod(join3(stage, "makeables-wallet", "makeables-wallet"), 448);
1327
- const info = await invoke(join3(stage, "makeables-wallet", "makeables-wallet"), ["version"]);
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 rename2(stage, root);
1554
+ await rename3(stage, root);
1331
1555
  } finally {
1332
- await rm2(stage, { recursive: true, force: true });
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 = resolve5(file);
1338
- const info = await lstat(input);
1339
- if (!info.isFile() || info.size > 12e6)
1340
- throw new InputError("Expected a local design or artwork file below 12 MB.");
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() === ".json") {
1343
- const design = await readBadge(input);
1344
- if (design.product !== "card") throw new InputError("Wallet artwork requires a card document.");
1345
- source = (await renderBadge(design, { face: side2, person: {} })).png;
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 readFile6(input);
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 = resolve5(directory);
1357
- if (!(await lstat(path)).isDirectory())
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 = join3(path, name);
1365
- const info = await lstat(file);
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 readFile6(file)).subarray(0, signature.length).equals(signature))
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: join3(path, "cardBackgroundCombined@3x.png") };
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 (Boolean(values.file) === Boolean(values.assets))
1426
- throw new InputError("Supply exactly one --file artwork or --assets prepared directory.");
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 already prepared --assets.");
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 mkdtemp(join3(tmpdir2(), "makeables-wallet-"));
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 = resolve5(need("assets"));
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 = join3(work, "artwork.png");
1506
- await writeFile2(raster, png, { mode: 384 });
1822
+ const raster = join6(work, "artwork.png");
1823
+ await writeFile3(raster, png, { mode: 384 });
1507
1824
  if (command === "wallet prep") {
1508
- target = resolve5(need("out"));
1825
+ target = resolve6(need("out"));
1509
1826
  } else if (installing && !applying) {
1510
- const root = join3(
1511
- process.env.MAKEABLES_WALLET_HOME ?? join3(homedir2(), "Library", "Application Support", "Makeables", "Wallet"),
1512
- "prepared"
1827
+ const root = await privateDirectory(
1828
+ join6(await privateDirectory(walletHome()), "prepared")
1513
1829
  );
1514
- await mkdir2(root, { recursive: true, mode: 448 });
1515
- if ((await lstat(root)).isSymbolicLink())
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 = join3(work, "assets");
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
- report("selection", "Artwork is ready. Open Wallet and tap the card to customize.");
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: ["--assets", target],
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 { ...result, elapsedMs: elapsed() };
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 rm2(work, { recursive: true, force: true });
1579
- if (persistent && phase === "prepare") await rm2(persistent, { recursive: true, force: true });
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 = resolve6(file);
2038
+ const path = resolve7(file);
1685
2039
  try {
1686
- await lstat2(path);
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(dirname3(path), constants2.W_OK);
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
- ${commands.map((command2) => ` npx makeables ${command2}`).join("\n")}
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": ["file", "assets", "card", "backup", "side", "device", "seconds", "yes"],
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-54KV55QM.js");
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 lstat2(path2);
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 readFile7(path2, "utf8"));
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 open2(path2, "wx", 384);
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 = resolve6(values.file);
2184
- const handle = await open2(path, "r");
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 readFile7(handle, "utf8"));
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 readFile7(resolve6(value.slice(1)), "utf8") : value
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 = resolve6(need(file, "file"));
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 = resolve6(need(file, "file"));
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 readFile7(raw.slice(1), "utf8") : raw);
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
- dirname3(resolve6(path))
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 open2(target, values.overwrite ? "w" : "wx", 420);
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 = resolve6(need(values.out, "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" ? resolve6(values.preview) : void 0;
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" ? resolve6(values.out) : void 0;
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 open2(target, values.overwrite ? "w" : "wx", 420);
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 = resolve6(need(values.out, "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 readFile7(resolve6(path), "utf8"));
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 open2(target, values.overwrite ? "w" : "wx", 420);
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 = resolve6(need(file, "file"));
2975
+ const path = resolve7(need(file, "file"));
2596
2976
  if (extname4(path).toLowerCase() === ".svg") {
2597
- const source = await readFile7(path, "utf8");
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 readFile7(path, "utf8"));
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(