makeables 0.7.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 +54 -9
- package/dist/assets/stickers/moraleja/claude-01.webp +0 -0
- package/dist/assets/stickers/moraleja/claude-02.webp +0 -0
- package/dist/assets/stickers/moraleja/claude-03.webp +0 -0
- package/dist/assets/stickers/moraleja/code-brew-pereira.webp +0 -0
- package/dist/assets/stickers/moraleja/code-brew-san-01.webp +0 -0
- package/dist/assets/stickers/moraleja/code-brew-san-02.webp +0 -0
- package/dist/assets/stickers/moraleja/cooking.webp +0 -0
- package/dist/assets/stickers/moraleja/crafter-01.webp +0 -0
- package/dist/assets/stickers/moraleja/crafter-station.webp +0 -0
- package/dist/assets/stickers/moraleja/curious.webp +0 -0
- package/dist/assets/stickers/moraleja/jsi.webp +0 -0
- package/dist/assets/stickers/moraleja/kebo-01.webp +0 -0
- package/dist/assets/stickers/moraleja/kebo-02.webp +0 -0
- package/dist/assets/stickers/moraleja/kebo-03.webp +0 -0
- package/dist/assets/stickers/moraleja/kebo-04.webp +0 -0
- package/dist/assets/stickers/moraleja/kebo-brand.webp +0 -0
- package/dist/assets/stickers/moraleja/maca.webp +0 -0
- package/dist/assets/stickers/moraleja/moraleja-01.webp +0 -0
- package/dist/assets/stickers/moraleja/moraleja-lfg.webp +0 -0
- package/dist/assets/stickers/moraleja/pawboard.webp +0 -0
- package/dist/assets/stickers/moraleja/provenance.json +223 -0
- package/dist/assets/stickers/moraleja/qwstzw.webp +0 -0
- package/dist/assets/stickers/moraleja/repo-o-fake.webp +0 -0
- package/dist/assets/stickers/moraleja/she-ships.webp +0 -0
- package/dist/assets/stickers/moraleja/ship-or-sink.webp +0 -0
- package/dist/assets/stickers/moraleja/shippeando.webp +0 -0
- package/dist/assets/stickers/moraleja/shippervivbes.webp +0 -0
- package/dist/assets/stickers/moraleja/shipping.webp +0 -0
- package/dist/assets/stickers/moraleja/taste.webp +0 -0
- package/dist/assets/stickers/moraleja/tsb.webp +0 -0
- package/dist/assets/stickers/moraleja/vibing.webp +0 -0
- package/dist/{chunk-QJHEOZGN.js → chunk-RFIQNWNF.js} +256 -154
- package/dist/cli.js +700 -109
- package/dist/{mcp-AOJ6UJSC.js → mcp-HS2XZXSO.js} +1 -1
- package/package.json +1 -1
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.
|
|
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.
|
|
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.
|
|
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) => {
|
|
@@ -1219,13 +1443,24 @@ async function execute(command, args, progress) {
|
|
|
1219
1443
|
});
|
|
1220
1444
|
}
|
|
1221
1445
|
async function invoke(executable, args, progress) {
|
|
1222
|
-
|
|
1446
|
+
let result;
|
|
1447
|
+
try {
|
|
1448
|
+
result = JSON.parse(await execute(executable, args, progress));
|
|
1449
|
+
if (!result || typeof result.ok !== "boolean") throw new Error("Missing companion result.");
|
|
1450
|
+
} catch {
|
|
1451
|
+
throw new WalletError({
|
|
1452
|
+
code: "WALLET_COMPANION_FAILED",
|
|
1453
|
+
message: "The Wallet companion ended without a valid receipt. Inspect wallet backups and doctor before another device operation."
|
|
1454
|
+
});
|
|
1455
|
+
}
|
|
1223
1456
|
if (!result.ok) {
|
|
1224
|
-
if (result.error?.code === "INVALID_INPUT")
|
|
1225
|
-
throw new InputError(result.error.message ?? "Invalid Wallet input.");
|
|
1226
1457
|
throw new WalletError(result.error ?? {});
|
|
1227
1458
|
}
|
|
1228
|
-
if (!result.data
|
|
1459
|
+
if (!result.data || typeof result.data !== "object" || Array.isArray(result.data))
|
|
1460
|
+
throw new WalletError({
|
|
1461
|
+
code: "WALLET_COMPANION_FAILED",
|
|
1462
|
+
message: "The Wallet companion returned no receipt. Inspect wallet backups before another device operation."
|
|
1463
|
+
});
|
|
1229
1464
|
return result.data;
|
|
1230
1465
|
}
|
|
1231
1466
|
async function walletDoctor() {
|
|
@@ -1278,6 +1513,7 @@ async function setupWallet(progress) {
|
|
|
1278
1513
|
const url = `https://github.com/${wallet_release_default.repository}/releases/download/wallet-v${wallet_release_default.version}/${artifact.asset}`;
|
|
1279
1514
|
progress?.({
|
|
1280
1515
|
event: "wallet_progress",
|
|
1516
|
+
phase: "setup",
|
|
1281
1517
|
message: `Downloading the Wallet companion ${wallet_release_default.version} for ${platform}.`
|
|
1282
1518
|
});
|
|
1283
1519
|
const response = await fetch(url, { signal: AbortSignal.timeout(18e4) });
|
|
@@ -1299,48 +1535,43 @@ async function setupWallet(progress) {
|
|
|
1299
1535
|
const bytes = Buffer.concat(chunks);
|
|
1300
1536
|
if (createHash3("sha256").update(bytes).digest("hex") !== artifact.sha256)
|
|
1301
1537
|
throw new Error("Wallet companion checksum did not match. Nothing was installed.");
|
|
1302
|
-
await
|
|
1303
|
-
const stage = await
|
|
1538
|
+
await mkdir3(dirname3(root), { recursive: true, mode: 448 });
|
|
1539
|
+
const stage = await mkdtemp2(join6(dirname3(root), ".install-"));
|
|
1304
1540
|
try {
|
|
1305
|
-
const archive =
|
|
1306
|
-
await
|
|
1541
|
+
const archive = join6(stage, "companion.tar.gz");
|
|
1542
|
+
await writeFile3(archive, bytes, { mode: 384, flag: "wx" });
|
|
1307
1543
|
const members = (await execute("tar", ["-tzf", archive])).trim().split("\n");
|
|
1308
1544
|
if (members.some(
|
|
1309
1545
|
(path) => !path.startsWith("makeables-wallet/") || path.split("/").includes("..")
|
|
1310
1546
|
))
|
|
1311
1547
|
throw new Error("Unexpected companion archive contents.");
|
|
1312
1548
|
await execute("tar", ["-xzf", archive, "-C", stage]);
|
|
1313
|
-
await
|
|
1314
|
-
await
|
|
1315
|
-
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"]);
|
|
1316
1552
|
if (info.version !== wallet_release_default.version || info.protocol !== wallet_release_default.protocol)
|
|
1317
1553
|
throw new Error("Companion version does not match the installed CLI.");
|
|
1318
|
-
await
|
|
1554
|
+
await rename3(stage, root);
|
|
1319
1555
|
} finally {
|
|
1320
|
-
await
|
|
1556
|
+
await rm4(stage, { recursive: true, force: true });
|
|
1321
1557
|
}
|
|
1322
1558
|
return walletDoctor();
|
|
1323
1559
|
}
|
|
1324
|
-
async function companion() {
|
|
1325
|
-
const info = await walletDoctor();
|
|
1326
|
-
if (!info.ready || !("installed" in info) || typeof info.installed !== "string")
|
|
1327
|
-
throw new InputError(
|
|
1328
|
-
"Run makeables wallet setup first. It installs the macOS companion without a source checkout."
|
|
1329
|
-
);
|
|
1330
|
-
return info.installed;
|
|
1331
|
-
}
|
|
1332
1560
|
async function walletArtwork(file, side2 = "front") {
|
|
1333
|
-
const input =
|
|
1334
|
-
const info = await
|
|
1335
|
-
if (!info.isFile() || info.size >
|
|
1336
|
-
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.");
|
|
1337
1565
|
let source;
|
|
1338
|
-
if (extname3(input).toLowerCase()
|
|
1339
|
-
const
|
|
1340
|
-
if (
|
|
1341
|
-
|
|
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]);
|
|
1342
1573
|
} else {
|
|
1343
|
-
source = await
|
|
1574
|
+
source = await readFile7(input);
|
|
1344
1575
|
if (extname3(input).toLowerCase() === ".svg") {
|
|
1345
1576
|
const result = checkSubmittedSvg(source.toString("utf8"));
|
|
1346
1577
|
if (!result.ok) throw new InputError(result.error);
|
|
@@ -1348,73 +1579,392 @@ async function walletArtwork(file, side2 = "front") {
|
|
|
1348
1579
|
}
|
|
1349
1580
|
return sharp7(source, { limitInputPixels: 24e6, density: 144 }).rotate().resize(1536, 969, { fit: "contain", background: "#000000" }).flatten({ background: "#000000" }).toColourspace("srgb").png().toBuffer();
|
|
1350
1581
|
}
|
|
1582
|
+
async function preparedArtwork(directory) {
|
|
1583
|
+
const path = resolve6(directory);
|
|
1584
|
+
if (!(await lstat3(path)).isDirectory())
|
|
1585
|
+
throw new InputError("--assets must be a prepared artwork directory.");
|
|
1586
|
+
for (const name of [
|
|
1587
|
+
"cardBackgroundCombined@3x.png",
|
|
1588
|
+
"cardBackgroundCombined@2x.png",
|
|
1589
|
+
"cardBackgroundCombined.pdf"
|
|
1590
|
+
]) {
|
|
1591
|
+
const file = join6(path, name);
|
|
1592
|
+
const info = await lstat3(file);
|
|
1593
|
+
if (!info.isFile() || info.size === 0 || info.size > 16 * 1024 * 1024)
|
|
1594
|
+
throw new InputError(`Invalid prepared asset: ${name}. Run wallet prep again.`);
|
|
1595
|
+
const signature = Buffer.from(name.endsWith(".pdf") ? "%PDF-" : "\x89PNG\r\n\n", "latin1");
|
|
1596
|
+
if (!(await readFile7(file)).subarray(0, signature.length).equals(signature))
|
|
1597
|
+
throw new InputError(`Invalid prepared asset: ${name}. Run wallet prep again.`);
|
|
1598
|
+
}
|
|
1599
|
+
return { path, preview: join6(path, "cardBackgroundCombined@3x.png") };
|
|
1600
|
+
}
|
|
1351
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) {
|
|
1633
|
+
const started = performance.now();
|
|
1634
|
+
const elapsed = () => Math.round(performance.now() - started);
|
|
1635
|
+
let latestPhase;
|
|
1636
|
+
const report = (phase, message) => {
|
|
1637
|
+
latestPhase = phase;
|
|
1638
|
+
progress({
|
|
1639
|
+
event: "wallet_progress",
|
|
1640
|
+
phase,
|
|
1641
|
+
message,
|
|
1642
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1643
|
+
elapsedMs: elapsed()
|
|
1644
|
+
});
|
|
1645
|
+
};
|
|
1646
|
+
const forward = (data) => {
|
|
1647
|
+
if (data && typeof data === "object") {
|
|
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";
|
|
1651
|
+
progress({
|
|
1652
|
+
...data,
|
|
1653
|
+
phase: latestPhase,
|
|
1654
|
+
...ready ? { ready: true, message: "Listening. Open Wallet and tap the exact card to customize." } : {},
|
|
1655
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1656
|
+
elapsedMs: elapsed()
|
|
1657
|
+
});
|
|
1658
|
+
}
|
|
1659
|
+
};
|
|
1352
1660
|
const need = (flag) => {
|
|
1353
1661
|
const value = values[flag];
|
|
1354
1662
|
if (typeof value !== "string" || !value) throw new InputError(`Supply --${flag}.`);
|
|
1355
1663
|
return value;
|
|
1356
1664
|
};
|
|
1357
1665
|
if (command === "wallet doctor") return walletDoctor();
|
|
1358
|
-
if (command === "wallet setup") return setupWallet(
|
|
1359
|
-
if (command === "wallet
|
|
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
|
+
}
|
|
1708
|
+
const installing = command === "wallet install";
|
|
1709
|
+
const applying = command === "wallet apply" || installing && Boolean(values.card || values.backup);
|
|
1710
|
+
if (applying || command === "wallet restore" || command === "wallet recover") {
|
|
1360
1711
|
if (!values.yes)
|
|
1361
1712
|
throw new InputError(
|
|
1362
1713
|
"Use --yes only after the user requests this operation on the selected card."
|
|
1363
1714
|
);
|
|
1364
1715
|
}
|
|
1365
|
-
if (
|
|
1716
|
+
if (applying && Boolean(values.card) === Boolean(values.backup))
|
|
1366
1717
|
throw new InputError("Choose exactly one --card selection or --backup original.");
|
|
1367
|
-
|
|
1718
|
+
if (installing && values.yes && !applying)
|
|
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
|
+
);
|
|
1728
|
+
if (installing && applying && (values.device || values.seconds))
|
|
1729
|
+
throw new InputError(
|
|
1730
|
+
"--device and --seconds are for selection; --target, --card or --backup already binds the target."
|
|
1731
|
+
);
|
|
1732
|
+
if (values.seconds && (!/^\d+$/.test(String(values.seconds)) || Number(values.seconds) < 1 || Number(values.seconds) > 300))
|
|
1733
|
+
throw new InputError("--seconds must be between 1 and 300.");
|
|
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
|
+
);
|
|
1739
|
+
if (values.assets && values.side)
|
|
1740
|
+
throw new InputError("--side applies to --file or --submission, not prepared --assets.");
|
|
1741
|
+
}
|
|
1742
|
+
if (values.side && !["front", "back"].includes(String(values.side)))
|
|
1743
|
+
throw new InputError("--side must be front or back.");
|
|
1744
|
+
if (command === "wallet prep") {
|
|
1745
|
+
need("out");
|
|
1746
|
+
}
|
|
1747
|
+
if (command === "wallet restore") need("backup");
|
|
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
|
+
}
|
|
1755
|
+
if (installing) report("setup", "Checking the Wallet companion.");
|
|
1756
|
+
const info = installing || command === "wallet prep" ? await setupWallet(forward) : await walletDoctor();
|
|
1757
|
+
if (!info.ready || !("installed" in info) || typeof info.installed !== "string")
|
|
1758
|
+
throw new InputError("Run makeables wallet setup first.");
|
|
1759
|
+
const executable = info.installed;
|
|
1760
|
+
const capabilities = "capabilities" in info && Array.isArray(info.capabilities) ? info.capabilities : [];
|
|
1368
1761
|
if (command === "wallet list") {
|
|
1369
1762
|
const args = ["list"];
|
|
1370
1763
|
for (const flag of ["device", "seconds"])
|
|
1371
1764
|
if (typeof values[flag] === "string") args.push(`--${flag}`, String(values[flag]));
|
|
1372
|
-
|
|
1765
|
+
if (values.first) {
|
|
1766
|
+
if (!capabilities.includes("first-selection"))
|
|
1767
|
+
throw new InputError(
|
|
1768
|
+
"This companion needs an update for --first. Omit --first for a full scan."
|
|
1769
|
+
);
|
|
1770
|
+
args.push("--first");
|
|
1771
|
+
}
|
|
1772
|
+
return invoke(executable, args, forward);
|
|
1373
1773
|
}
|
|
1374
1774
|
if (command === "wallet backups") {
|
|
1375
1775
|
return invoke(
|
|
1376
1776
|
executable,
|
|
1377
1777
|
["backups", ...values.backup ? ["--backup", need("backup")] : []],
|
|
1378
|
-
|
|
1778
|
+
forward
|
|
1379
1779
|
);
|
|
1380
1780
|
}
|
|
1381
1781
|
if (command === "wallet recover") {
|
|
1382
1782
|
return invoke(
|
|
1383
1783
|
executable,
|
|
1384
1784
|
["recover", "--backup", need("backup"), "--device", need("device"), "--yes"],
|
|
1385
|
-
|
|
1785
|
+
forward
|
|
1386
1786
|
);
|
|
1387
1787
|
}
|
|
1388
1788
|
if (command === "wallet restore") {
|
|
1389
|
-
|
|
1390
|
-
}
|
|
1391
|
-
if (command === "wallet prep" || command === "wallet apply") {
|
|
1392
|
-
const file = need("file");
|
|
1393
|
-
if (values.side && !["front", "back"].includes(String(values.side)))
|
|
1394
|
-
throw new InputError("--side must be front or back.");
|
|
1395
|
-
const png = await walletArtwork(file, values.side === "back" ? "back" : "front");
|
|
1396
|
-
const work = await mkdtemp(join3(tmpdir2(), "makeables-wallet-"));
|
|
1789
|
+
report("preflight", "Checking the original artwork backup and connected iPhone.");
|
|
1397
1790
|
try {
|
|
1398
|
-
const
|
|
1399
|
-
await writeFile2(raster, png, { mode: 384 });
|
|
1400
|
-
const target = command === "wallet prep" ? resolve5(need("out")) : join3(work, "assets");
|
|
1401
|
-
const prepared = await invoke(
|
|
1791
|
+
const result = await invoke(
|
|
1402
1792
|
executable,
|
|
1403
|
-
["
|
|
1404
|
-
|
|
1793
|
+
["restore", "--backup", need("backup"), "--yes"],
|
|
1794
|
+
forward
|
|
1405
1795
|
);
|
|
1406
|
-
|
|
1407
|
-
|
|
1796
|
+
return { ...result, elapsedMs: elapsed() };
|
|
1797
|
+
} catch (error) {
|
|
1798
|
+
if (error instanceof WalletError)
|
|
1799
|
+
throw new WalletError({
|
|
1800
|
+
...error.details,
|
|
1801
|
+
phase: error.details.phase ?? latestPhase ?? "preflight",
|
|
1802
|
+
deviceWriteAttempted: error.details.deviceWriteAttempted ?? true,
|
|
1803
|
+
nextAction: error.details.nextAction ?? "inspect-backups"
|
|
1804
|
+
});
|
|
1805
|
+
throw error;
|
|
1806
|
+
}
|
|
1807
|
+
}
|
|
1808
|
+
if (command === "wallet prep" || applying || installing) {
|
|
1809
|
+
const work = await mkdtemp2(join6(tmpdir2(), "makeables-wallet-"));
|
|
1810
|
+
let phase = "prepare";
|
|
1811
|
+
let deviceWriteAttempted = false;
|
|
1812
|
+
let persistent;
|
|
1813
|
+
let prepared;
|
|
1814
|
+
try {
|
|
1815
|
+
let target;
|
|
1816
|
+
if (existing) {
|
|
1817
|
+
target = resolve6(need("assets"));
|
|
1818
|
+
prepared = existing;
|
|
1819
|
+
} else {
|
|
1820
|
+
report("prepare", "Preparing the complete card artwork.");
|
|
1821
|
+
const png = await walletArtwork(need("file"), values.side === "back" ? "back" : "front");
|
|
1822
|
+
const raster = join6(work, "artwork.png");
|
|
1823
|
+
await writeFile3(raster, png, { mode: 384 });
|
|
1824
|
+
if (command === "wallet prep") {
|
|
1825
|
+
target = resolve6(need("out"));
|
|
1826
|
+
} else if (installing && !applying) {
|
|
1827
|
+
const root = await privateDirectory(
|
|
1828
|
+
join6(await privateDirectory(walletHome()), "prepared")
|
|
1829
|
+
);
|
|
1830
|
+
persistent = await mkdtemp2(join6(root, "install-"));
|
|
1831
|
+
target = join6(persistent, "assets");
|
|
1832
|
+
} else {
|
|
1833
|
+
target = join6(work, "assets");
|
|
1834
|
+
}
|
|
1835
|
+
prepared = await invoke(
|
|
1836
|
+
executable,
|
|
1837
|
+
["prepare", "--file", raster, "--out", target],
|
|
1838
|
+
forward
|
|
1839
|
+
);
|
|
1840
|
+
await preparedArtwork(target);
|
|
1841
|
+
}
|
|
1842
|
+
if (command === "wallet prep") return { ...prepared, source, elapsedMs: elapsed() };
|
|
1843
|
+
if (installing && !applying) {
|
|
1844
|
+
phase = "selection";
|
|
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.");
|
|
1868
|
+
const args2 = ["list"];
|
|
1869
|
+
for (const flag of ["device", "seconds"])
|
|
1870
|
+
if (typeof values[flag] === "string") args2.push(`--${flag}`, String(values[flag]));
|
|
1871
|
+
if (capabilities.includes("first-selection")) args2.push("--first");
|
|
1872
|
+
const selection = await invoke(executable, args2, forward);
|
|
1873
|
+
const hasCards = Array.isArray(selection.cards) && selection.cards.length > 0;
|
|
1874
|
+
const status = selection.needsDevice ? "needs-device" : hasCards ? "needs-selection" : "needs-card";
|
|
1875
|
+
return {
|
|
1876
|
+
...selection,
|
|
1877
|
+
status,
|
|
1878
|
+
assets: target,
|
|
1879
|
+
preview: prepared.preview,
|
|
1880
|
+
source,
|
|
1881
|
+
deviceWriteAttempted: false,
|
|
1882
|
+
nextAction: selection.needsDevice ? "select-device" : hasCards ? "confirm-card" : "tap-card",
|
|
1883
|
+
next: {
|
|
1884
|
+
command: "wallet install",
|
|
1885
|
+
args: !hasCards && (values.scan || saved.length) ? [...continuation, "--scan"] : continuation,
|
|
1886
|
+
required: selection.needsDevice ? ["device"] : hasCards ? ["card", "yes"] : []
|
|
1887
|
+
},
|
|
1888
|
+
elapsedMs: elapsed()
|
|
1889
|
+
};
|
|
1890
|
+
}
|
|
1891
|
+
report("preflight", "Artwork is ready. Keep the selected iPhone connected.");
|
|
1408
1892
|
const args = ["apply", "--assets", target, "--yes"];
|
|
1409
1893
|
if (values.card) args.push("--card", need("card"));
|
|
1410
1894
|
if (values.backup) args.push("--backup", need("backup"));
|
|
1411
|
-
|
|
1895
|
+
phase = "apply";
|
|
1896
|
+
deviceWriteAttempted = true;
|
|
1897
|
+
const result = await invoke(executable, args, forward);
|
|
1898
|
+
return {
|
|
1899
|
+
...result,
|
|
1900
|
+
...values.target ? { target: values.target } : {},
|
|
1901
|
+
source,
|
|
1902
|
+
elapsedMs: elapsed()
|
|
1903
|
+
};
|
|
1904
|
+
} catch (error) {
|
|
1905
|
+
if (error instanceof WalletError) {
|
|
1906
|
+
throw new WalletError({
|
|
1907
|
+
...error.details,
|
|
1908
|
+
phase: error.details.phase ?? latestPhase ?? phase,
|
|
1909
|
+
deviceWriteAttempted: error.details.deviceWriteAttempted ?? deviceWriteAttempted,
|
|
1910
|
+
...phase === "selection" && prepared ? {
|
|
1911
|
+
assets: String(prepared.path),
|
|
1912
|
+
preview: String(prepared.preview),
|
|
1913
|
+
nextAction: "retry-selection"
|
|
1914
|
+
} : {}
|
|
1915
|
+
});
|
|
1916
|
+
}
|
|
1917
|
+
throw error;
|
|
1412
1918
|
} finally {
|
|
1413
|
-
await
|
|
1919
|
+
await rm4(work, { recursive: true, force: true });
|
|
1920
|
+
if (persistent && phase === "prepare") await rm4(persistent, { recursive: true, force: true });
|
|
1414
1921
|
}
|
|
1415
1922
|
}
|
|
1416
1923
|
throw new InputError("Unknown Wallet command.");
|
|
1417
1924
|
}
|
|
1925
|
+
function walletSummary(result) {
|
|
1926
|
+
if (result.status === "restored") {
|
|
1927
|
+
const seconds = typeof result.elapsedMs === "number" ? ` in ${(result.elapsedMs / 1e3).toFixed(1)}s` : "";
|
|
1928
|
+
return [
|
|
1929
|
+
`Original Wallet artwork restored${seconds}.`,
|
|
1930
|
+
...result.backupVerified ? ["Original backup preserved and verified."] : [],
|
|
1931
|
+
...typeof result.backup === "string" ? [`Backup: ${result.backup}`] : [],
|
|
1932
|
+
"Force-close Wallet, reopen it and check the original design."
|
|
1933
|
+
].join("\n");
|
|
1934
|
+
}
|
|
1935
|
+
if (result.status === "complete") {
|
|
1936
|
+
const seconds = typeof result.elapsedMs === "number" ? ` in ${(result.elapsedMs / 1e3).toFixed(1)}s` : "";
|
|
1937
|
+
return [
|
|
1938
|
+
`Wallet artwork installed${seconds}.`,
|
|
1939
|
+
result.backupVerified ? "Original artwork backup saved and verified." : "Original artwork backup saved.",
|
|
1940
|
+
...typeof result.backup === "string" ? [`Backup: ${result.backup}`] : [],
|
|
1941
|
+
typeof result.refresh === "string" ? result.refresh : "Close and reopen Wallet to see the design.",
|
|
1942
|
+
...typeof result.warning === "string" ? [result.warning] : []
|
|
1943
|
+
].join("\n");
|
|
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");
|
|
1954
|
+
if (["needs-device", "needs-selection", "needs-card"].includes(String(result.status)))
|
|
1955
|
+
return [
|
|
1956
|
+
"Artwork prepared. No card has been changed.",
|
|
1957
|
+
`Preview: ${String(result.preview)}`,
|
|
1958
|
+
result.status === "needs-device" ? "Connect an unlocked iPhone or choose a listed device." : result.status === "needs-card" ? "No card was detected. Open Wallet, tap the card, and repeat using the prepared assets." : "Confirm which observed card you want to customize, then use its selection handle with --yes.",
|
|
1959
|
+
JSON.stringify(
|
|
1960
|
+
{ assets: result.assets, devices: result.devices, cards: result.cards },
|
|
1961
|
+
null,
|
|
1962
|
+
2
|
|
1963
|
+
),
|
|
1964
|
+
`Continue: makeables wallet install --assets '${String(result.assets).replaceAll("'", "'\\''")}'${result.status === "needs-selection" ? " --card <selection> --yes" : result.status === "needs-device" ? " --device <selection>" : ""}`
|
|
1965
|
+
].join("\n");
|
|
1966
|
+
return JSON.stringify(result, null, 2);
|
|
1967
|
+
}
|
|
1418
1968
|
|
|
1419
1969
|
// src/cli.ts
|
|
1420
1970
|
var commands = [
|
|
@@ -1442,9 +1992,13 @@ var commands = [
|
|
|
1442
1992
|
),
|
|
1443
1993
|
"wallet setup",
|
|
1444
1994
|
"wallet doctor",
|
|
1445
|
-
"wallet
|
|
1446
|
-
"wallet
|
|
1447
|
-
"wallet
|
|
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>",
|
|
1999
|
+
"wallet list [--device <selection>] [--seconds 45] [--first]",
|
|
2000
|
+
"wallet prep (--submission <UUID|URL> | --file <card.json|package.zip|artwork.svg|image.png>) --out <new-directory> [--side front|back]",
|
|
2001
|
+
"wallet apply (--file <artwork> | --assets <directory>) (--card <selection> | --backup <id|path>) --yes",
|
|
1448
2002
|
"wallet restore --backup <id|path> --yes",
|
|
1449
2003
|
"wallet backups [--backup <id|path>]",
|
|
1450
2004
|
"wallet recover --backup <transaction-path> --device <selection> --yes",
|
|
@@ -1481,14 +2035,14 @@ function emit(data, next, human, json) {
|
|
|
1481
2035
|
);
|
|
1482
2036
|
}
|
|
1483
2037
|
async function newPath(file) {
|
|
1484
|
-
const path =
|
|
2038
|
+
const path = resolve7(file);
|
|
1485
2039
|
try {
|
|
1486
|
-
await
|
|
2040
|
+
await lstat4(path);
|
|
1487
2041
|
throw new InputError(`File already exists: ${path}. Choose a new --out path.`);
|
|
1488
2042
|
} catch (error) {
|
|
1489
2043
|
if (error.code !== "ENOENT") throw error;
|
|
1490
2044
|
}
|
|
1491
|
-
await access(
|
|
2045
|
+
await access(dirname4(path), constants3.W_OK);
|
|
1492
2046
|
return path;
|
|
1493
2047
|
}
|
|
1494
2048
|
async function run() {
|
|
@@ -1536,6 +2090,13 @@ async function run() {
|
|
|
1536
2090
|
backup: { type: "string" },
|
|
1537
2091
|
device: { type: "string" },
|
|
1538
2092
|
seconds: { type: "string" },
|
|
2093
|
+
assets: { type: "string" },
|
|
2094
|
+
first: { type: "boolean" },
|
|
2095
|
+
submission: { type: "string" },
|
|
2096
|
+
scan: { type: "boolean" },
|
|
2097
|
+
remember: { type: "string" },
|
|
2098
|
+
label: { type: "string" },
|
|
2099
|
+
last4: { type: "string" },
|
|
1539
2100
|
type: { type: "string" },
|
|
1540
2101
|
size: { type: "string" },
|
|
1541
2102
|
"width-mm": { type: "string" },
|
|
@@ -1548,16 +2109,19 @@ async function run() {
|
|
|
1548
2109
|
return;
|
|
1549
2110
|
}
|
|
1550
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}`);
|
|
1551
2115
|
if (!json) banner(version);
|
|
1552
2116
|
emit(
|
|
1553
2117
|
{
|
|
1554
|
-
commands,
|
|
2118
|
+
commands: helpCommands,
|
|
1555
2119
|
flags: ["--json", "--help", "--version"],
|
|
1556
2120
|
exitCodes: { success: 0, invalidInput: 2, systemFailure: 1 }
|
|
1557
2121
|
},
|
|
1558
2122
|
nextSteps,
|
|
1559
2123
|
`${style("Commands", "1", !json)}
|
|
1560
|
-
${
|
|
2124
|
+
${helpCommands.map((command2) => ` npx makeables ${command2}`).join("\n")}
|
|
1561
2125
|
|
|
1562
2126
|
JSON is automatic when piped. --out creates new files only.
|
|
1563
2127
|
Install globally: npm install --global makeables
|
|
@@ -1573,9 +2137,28 @@ From a built checkout: npm run studio -- <command>`,
|
|
|
1573
2137
|
),
|
|
1574
2138
|
"wallet setup": [],
|
|
1575
2139
|
"wallet doctor": [],
|
|
1576
|
-
"wallet
|
|
1577
|
-
|
|
1578
|
-
|
|
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"],
|
|
2159
|
+
"wallet list": ["device", "seconds", "first"],
|
|
2160
|
+
"wallet prep": ["submission", "file", "out", "side"],
|
|
2161
|
+
"wallet apply": ["file", "assets", "card", "backup", "side", "yes"],
|
|
1579
2162
|
"wallet restore": ["backup", "yes"],
|
|
1580
2163
|
"wallet backups": ["backup"],
|
|
1581
2164
|
"wallet recover": ["backup", "device", "yes"],
|
|
@@ -1648,8 +2231,16 @@ From a built checkout: npm run studio -- <command>`,
|
|
|
1648
2231
|
if (values.product && !["card", "badge", "sticker"].includes(values.product))
|
|
1649
2232
|
throw new InputError("--product must be card, badge or sticker.");
|
|
1650
2233
|
if (command.startsWith("wallet ")) {
|
|
1651
|
-
const
|
|
1652
|
-
|
|
2234
|
+
const walletProgress = (data) => {
|
|
2235
|
+
if (json) return progress(data);
|
|
2236
|
+
if (data && typeof data === "object" && "message" in data) {
|
|
2237
|
+
const phase = "phase" in data ? `[${String(data.phase)}] ` : "";
|
|
2238
|
+
process.stderr.write(`${phase}${String(data.message)}
|
|
2239
|
+
`);
|
|
2240
|
+
}
|
|
2241
|
+
};
|
|
2242
|
+
const result2 = await walletCommand(command, values, walletProgress);
|
|
2243
|
+
emit(result2, ["makeables skills get wallet"], walletSummary(result2), json);
|
|
1653
2244
|
return;
|
|
1654
2245
|
}
|
|
1655
2246
|
if (command.startsWith("image ") && imageTools.some((tool) => command === `image ${tool}`)) {
|
|
@@ -1804,7 +2395,7 @@ Run makeables skills get core for setup.`,
|
|
|
1804
2395
|
}
|
|
1805
2396
|
if (command === "mcp") {
|
|
1806
2397
|
try {
|
|
1807
|
-
const { runMcp } = await import("./mcp-
|
|
2398
|
+
const { runMcp } = await import("./mcp-HS2XZXSO.js");
|
|
1808
2399
|
await runMcp(values.root ?? process.cwd());
|
|
1809
2400
|
} catch (error) {
|
|
1810
2401
|
process.stderr.write(
|
|
@@ -1860,10 +2451,10 @@ Keep this process running while you design.`,
|
|
|
1860
2451
|
if (positionals.length !== 3) throw new InputError("Supply one discovered tool name.");
|
|
1861
2452
|
if (values.params?.startsWith("@")) {
|
|
1862
2453
|
const path2 = values.params.slice(1);
|
|
1863
|
-
const info = await
|
|
2454
|
+
const info = await lstat4(path2);
|
|
1864
2455
|
if (!info.isFile() || info.size > 12e6)
|
|
1865
2456
|
throw new InputError("Expected a params file below 12 MB.");
|
|
1866
|
-
params = JSON.parse(await
|
|
2457
|
+
params = JSON.parse(await readFile8(path2, "utf8"));
|
|
1867
2458
|
} else if (values.params) params = JSON.parse(values.params);
|
|
1868
2459
|
}
|
|
1869
2460
|
const result2 = await studioRequest(
|
|
@@ -1946,7 +2537,7 @@ ${styles.map((entry) => `${column(entry.id ?? "", 25)}${entry.name}`).join("\n")
|
|
|
1946
2537
|
const path2 = values.out ? await newPath(values.out) : null;
|
|
1947
2538
|
const dryRun = Boolean(values["dry-run"]);
|
|
1948
2539
|
if (path2 && !dryRun) {
|
|
1949
|
-
const handle2 = await
|
|
2540
|
+
const handle2 = await open3(path2, "wx", 384);
|
|
1950
2541
|
try {
|
|
1951
2542
|
await handle2.writeFile(`${JSON.stringify(design, null, 2)}
|
|
1952
2543
|
`);
|
|
@@ -1969,14 +2560,14 @@ ${dryRun ? "Dry run. No files changed." : "2 faces \xB7 editable layers"}`,
|
|
|
1969
2560
|
return;
|
|
1970
2561
|
}
|
|
1971
2562
|
if (!values.file) throw new InputError("Supply --file to validate a document.");
|
|
1972
|
-
const path =
|
|
1973
|
-
const handle = await
|
|
2563
|
+
const path = resolve7(values.file);
|
|
2564
|
+
const handle = await open3(path, "r");
|
|
1974
2565
|
let input;
|
|
1975
2566
|
try {
|
|
1976
2567
|
const info = await handle.stat();
|
|
1977
2568
|
if (!info.isFile() || info.size > 2 * 1024 * 1024)
|
|
1978
2569
|
throw new InputError("Expected a JSON file smaller than 2 MB.");
|
|
1979
|
-
input = JSON.parse(await
|
|
2570
|
+
input = JSON.parse(await readFile8(handle, "utf8"));
|
|
1980
2571
|
} finally {
|
|
1981
2572
|
await handle.close();
|
|
1982
2573
|
}
|
|
@@ -1995,7 +2586,7 @@ Schema and layout constraints passed.`,
|
|
|
1995
2586
|
}
|
|
1996
2587
|
async function readParams(value) {
|
|
1997
2588
|
return JSON.parse(
|
|
1998
|
-
value.startsWith("@") ? await
|
|
2589
|
+
value.startsWith("@") ? await readFile8(resolve7(value.slice(1)), "utf8") : value
|
|
1999
2590
|
);
|
|
2000
2591
|
}
|
|
2001
2592
|
async function cardCommand(command, values, json) {
|
|
@@ -2057,7 +2648,7 @@ ${path ?? `No file written. Add --out ${kind}.json.`}`,
|
|
|
2057
2648
|
return true;
|
|
2058
2649
|
}
|
|
2059
2650
|
if (command === "material map") {
|
|
2060
|
-
const path =
|
|
2651
|
+
const path = resolve7(need(file, "file"));
|
|
2061
2652
|
const next = await bindStickerMap(
|
|
2062
2653
|
await readBadge(path),
|
|
2063
2654
|
need(values.id, "id"),
|
|
@@ -2073,10 +2664,10 @@ ${path ?? `No file written. Add --out ${kind}.json.`}`,
|
|
|
2073
2664
|
return true;
|
|
2074
2665
|
}
|
|
2075
2666
|
if (command === "material set") {
|
|
2076
|
-
const path =
|
|
2667
|
+
const path = resolve7(need(file, "file"));
|
|
2077
2668
|
const design = await readBadge(path);
|
|
2078
2669
|
const raw = need(values.params, "params");
|
|
2079
|
-
const input = JSON.parse(raw.startsWith("@") ? await
|
|
2670
|
+
const input = JSON.parse(raw.startsWith("@") ? await readFile8(raw.slice(1), "utf8") : raw);
|
|
2080
2671
|
if (design.product === "sticker") {
|
|
2081
2672
|
const patch2 = stickerMaterialSchema.partial().strict().safeParse(input);
|
|
2082
2673
|
if (!patch2.success)
|
|
@@ -2160,7 +2751,7 @@ ${path ?? `No file written. Add --out ${kind}.json.`}`,
|
|
|
2160
2751
|
await readBadge(path),
|
|
2161
2752
|
face,
|
|
2162
2753
|
need(values.sticker, "sticker"),
|
|
2163
|
-
|
|
2754
|
+
dirname4(resolve7(path))
|
|
2164
2755
|
);
|
|
2165
2756
|
await writeAtomic(path, `${JSON.stringify(design, null, 2)}
|
|
2166
2757
|
`);
|
|
@@ -2175,7 +2766,7 @@ ${path ?? `No file written. Add --out ${kind}.json.`}`,
|
|
|
2175
2766
|
}
|
|
2176
2767
|
if (command.startsWith("sheet ")) {
|
|
2177
2768
|
const write = async (target, bytes) => {
|
|
2178
|
-
const handle = await
|
|
2769
|
+
const handle = await open3(target, values.overwrite ? "w" : "wx", 420);
|
|
2179
2770
|
try {
|
|
2180
2771
|
await handle.writeFile(bytes);
|
|
2181
2772
|
} finally {
|
|
@@ -2254,9 +2845,9 @@ ${path ?? `No file written. Add --out ${kind}.json.`}`,
|
|
|
2254
2845
|
throw new InputError(
|
|
2255
2846
|
`The sheet is not printable. Run makeables sheet check --file ${JSON.stringify(path)}.`
|
|
2256
2847
|
);
|
|
2257
|
-
const out =
|
|
2848
|
+
const out = resolve7(need(values.out, "out"));
|
|
2258
2849
|
if (!out.endsWith(".pdf")) throw new InputError("--out must end in .pdf.");
|
|
2259
|
-
const preview = typeof values.preview === "string" ?
|
|
2850
|
+
const preview = typeof values.preview === "string" ? resolve7(values.preview) : void 0;
|
|
2260
2851
|
if (preview && !preview.endsWith(".png")) throw new InputError("--preview must end in .png.");
|
|
2261
2852
|
const targets = [out, ...preview ? [preview] : []];
|
|
2262
2853
|
if (!values.overwrite) for (const target of targets) await newPath(target);
|
|
@@ -2274,12 +2865,12 @@ ${path ?? `No file written. Add --out ${kind}.json.`}`,
|
|
|
2274
2865
|
if (command === "cut") {
|
|
2275
2866
|
const design = await readBadge(need(file, "file"));
|
|
2276
2867
|
if (design.product !== "sticker") throw new InputError("cut applies to sticker designs.");
|
|
2277
|
-
const out = typeof values.out === "string" ?
|
|
2868
|
+
const out = typeof values.out === "string" ? resolve7(values.out) : void 0;
|
|
2278
2869
|
if (out && !out.endsWith(".svg")) throw new InputError("--out must end in .svg.");
|
|
2279
2870
|
const target = out && (values.overwrite ? out : await newPath(out));
|
|
2280
2871
|
const { cut, svg } = await stickerPrintCut(design);
|
|
2281
2872
|
if (target) {
|
|
2282
|
-
const handle = await
|
|
2873
|
+
const handle = await open3(target, values.overwrite ? "w" : "wx", 420);
|
|
2283
2874
|
try {
|
|
2284
2875
|
await handle.writeFile(svg);
|
|
2285
2876
|
} finally {
|
|
@@ -2312,14 +2903,14 @@ ${path ?? `No file written. Add --out ${kind}.json.`}`,
|
|
|
2312
2903
|
}
|
|
2313
2904
|
if (command === "render") {
|
|
2314
2905
|
const path = need(file, "file");
|
|
2315
|
-
const out =
|
|
2906
|
+
const out = resolve7(need(values.out, "out"));
|
|
2316
2907
|
if (!out.endsWith(".png")) throw new InputError("--out must end in .png.");
|
|
2317
2908
|
const target = values.overwrite ? out : await newPath(out);
|
|
2318
2909
|
const number = values.number === void 0 ? void 0 : Number(values.number);
|
|
2319
2910
|
if (number !== void 0 && !Number.isInteger(number))
|
|
2320
2911
|
throw new InputError("--number must be an integer.");
|
|
2321
2912
|
const face = side(values.side);
|
|
2322
|
-
const value = JSON.parse(await
|
|
2913
|
+
const value = JSON.parse(await readFile8(resolve7(path), "utf8"));
|
|
2323
2914
|
const pkg = value?.format === "makeables-package" ? await readPackageJson(value) : void 0;
|
|
2324
2915
|
if (pkg && ["photo", "name", "role", "number"].some((key) => values[key] !== void 0))
|
|
2325
2916
|
throw new InputError(
|
|
@@ -2343,7 +2934,7 @@ ${path ?? `No file written. Add --out ${kind}.json.`}`,
|
|
|
2343
2934
|
...number === void 0 ? {} : { number }
|
|
2344
2935
|
}
|
|
2345
2936
|
});
|
|
2346
|
-
const handle = await
|
|
2937
|
+
const handle = await open3(target, values.overwrite ? "w" : "wx", 420);
|
|
2347
2938
|
try {
|
|
2348
2939
|
await handle.writeFile(png);
|
|
2349
2940
|
} finally {
|
|
@@ -2381,9 +2972,9 @@ ${path ?? `No file written. Add --out ${kind}.json.`}`,
|
|
|
2381
2972
|
return true;
|
|
2382
2973
|
}
|
|
2383
2974
|
if (command === "validate") {
|
|
2384
|
-
const path =
|
|
2975
|
+
const path = resolve7(need(file, "file"));
|
|
2385
2976
|
if (extname4(path).toLowerCase() === ".svg") {
|
|
2386
|
-
const source = await
|
|
2977
|
+
const source = await readFile8(path, "utf8");
|
|
2387
2978
|
const result2 = checkSubmittedSvg(source);
|
|
2388
2979
|
if (!result2.ok) throw new InputError(result2.error);
|
|
2389
2980
|
const preview = values.out ? await newPath(need(values.out, "out")) : void 0;
|
|
@@ -2404,7 +2995,7 @@ ${path ?? `No file written. Add --out ${kind}.json.`}`,
|
|
|
2404
2995
|
return true;
|
|
2405
2996
|
}
|
|
2406
2997
|
if (values.out) throw new InputError("--out is only used to preview SVG validation.");
|
|
2407
|
-
const input = JSON.parse(await
|
|
2998
|
+
const input = JSON.parse(await readFile8(path, "utf8"));
|
|
2408
2999
|
const result = badgeDesignSchema.safeParse(input);
|
|
2409
3000
|
if (!result.success)
|
|
2410
3001
|
throw new InputError(
|
|
@@ -2422,7 +3013,7 @@ ${path ?? `No file written. Add --out ${kind}.json.`}`,
|
|
|
2422
3013
|
return false;
|
|
2423
3014
|
}
|
|
2424
3015
|
run().catch((error) => {
|
|
2425
|
-
const userError = error instanceof InputError || error instanceof SyntaxError || [
|
|
3016
|
+
const userError = error instanceof InputError || error instanceof WalletError && error.details.code === "INVALID_INPUT" || error instanceof SyntaxError || [
|
|
2426
3017
|
"ERR_PARSE_ARGS_UNKNOWN_OPTION",
|
|
2427
3018
|
"ERR_PARSE_ARGS_INVALID_OPTION_VALUE",
|
|
2428
3019
|
"ERR_PARSE_ARGS_UNEXPECTED_POSITIONAL",
|