makeables 0.5.0 → 0.7.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 +1 -1
- package/dist/assets/stickers/community/brew-fx.webp +0 -0
- package/dist/assets/stickers/community/brew.webp +0 -0
- package/dist/assets/stickers/community/crafter-fx.webp +0 -0
- package/dist/assets/stickers/community/crafter.webp +0 -0
- package/dist/assets/stickers/community/friday-fx.webp +0 -0
- package/dist/assets/stickers/community/friday.webp +0 -0
- package/dist/assets/stickers/community/provenance.json +43 -0
- package/dist/assets/stickers/typingmind/absolutely-right-fx.webp +0 -0
- package/dist/assets/stickers/typingmind/absolutely-right.webp +0 -0
- package/dist/assets/stickers/typingmind/agi-disco-fx.webp +0 -0
- package/dist/assets/stickers/typingmind/agi-disco.webp +0 -0
- package/dist/assets/stickers/typingmind/agi-is-coming-fx.webp +0 -0
- package/dist/assets/stickers/typingmind/agi-is-coming.webp +0 -0
- package/dist/assets/stickers/typingmind/cmd-enter-fx.webp +0 -0
- package/dist/assets/stickers/typingmind/cmd-enter.webp +0 -0
- package/dist/assets/stickers/typingmind/emotional-support-fx.webp +0 -0
- package/dist/assets/stickers/typingmind/emotional-support.webp +0 -0
- package/dist/assets/stickers/typingmind/energy-fx.webp +0 -0
- package/dist/assets/stickers/typingmind/energy.webp +0 -0
- package/dist/assets/stickers/typingmind/motion-fx.webp +0 -0
- package/dist/assets/stickers/typingmind/motion.webp +0 -0
- package/dist/assets/stickers/typingmind/provenance.json +97 -0
- package/dist/assets/stickers/typingmind/typingmind-fx.webp +0 -0
- package/dist/assets/stickers/typingmind/typingmind.webp +0 -0
- package/dist/assets/stickers/typingmind/youre-cooked-fx.webp +0 -0
- package/dist/assets/stickers/typingmind/youre-cooked.webp +0 -0
- package/dist/{chunk-WVXA65H6.js → chunk-QJHEOZGN.js} +4633 -451
- package/dist/cli.js +437 -30
- package/dist/{mcp-LKLESPJC.js → mcp-AOJ6UJSC.js} +12 -2
- package/package.json +3 -3
package/dist/cli.js
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
cardStyles,
|
|
9
9
|
checkSubmittedSvg,
|
|
10
10
|
connectAccount,
|
|
11
|
+
createStickerSheet,
|
|
11
12
|
defaultStickerMaterial,
|
|
12
13
|
designCatalog,
|
|
13
14
|
designProfile,
|
|
@@ -22,16 +23,27 @@ import {
|
|
|
22
23
|
newBadge,
|
|
23
24
|
newCard,
|
|
24
25
|
openBrowser,
|
|
26
|
+
packSheet,
|
|
25
27
|
packageJson,
|
|
28
|
+
placeNear,
|
|
29
|
+
prepareSheetPrint,
|
|
26
30
|
readBadge,
|
|
27
31
|
readPackageJson,
|
|
28
32
|
removeBadgeLayer,
|
|
29
33
|
renderBadge,
|
|
30
34
|
setBadgeLayer,
|
|
35
|
+
sheetPrintPdf,
|
|
36
|
+
sheetSizes,
|
|
37
|
+
sheetSpec,
|
|
38
|
+
sheetTypeIds,
|
|
39
|
+
sheetTypes,
|
|
31
40
|
side,
|
|
32
41
|
skills,
|
|
33
42
|
startStudio,
|
|
43
|
+
stickerCut,
|
|
44
|
+
stickerCutSvg,
|
|
34
45
|
stickerMaterialSchema,
|
|
46
|
+
stickerSheetSchema,
|
|
35
47
|
stickerStyles,
|
|
36
48
|
studioRequest,
|
|
37
49
|
submitDesign,
|
|
@@ -40,15 +52,15 @@ import {
|
|
|
40
52
|
svgNodeXml,
|
|
41
53
|
version,
|
|
42
54
|
writeAtomic
|
|
43
|
-
} from "./chunk-
|
|
55
|
+
} from "./chunk-QJHEOZGN.js";
|
|
44
56
|
|
|
45
57
|
// src/cli.ts
|
|
46
58
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
47
59
|
import { constants as constants2 } from "fs";
|
|
48
|
-
import { access, lstat as lstat2, open as open2, readFile as
|
|
49
|
-
import { dirname as dirname3, extname as extname4, resolve as
|
|
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";
|
|
50
62
|
import { parseArgs } from "util";
|
|
51
|
-
import
|
|
63
|
+
import sharp8 from "sharp";
|
|
52
64
|
import { zodToJsonSchema } from "zod-to-json-schema";
|
|
53
65
|
|
|
54
66
|
// src/card-import.ts
|
|
@@ -908,14 +920,236 @@ async function referenceImage(command, file, options, out, reference) {
|
|
|
908
920
|
}
|
|
909
921
|
}
|
|
910
922
|
|
|
923
|
+
// src/sheet.ts
|
|
924
|
+
import { readFile as readFile4 } from "fs/promises";
|
|
925
|
+
import { resolve as resolve4 } from "path";
|
|
926
|
+
import { deflateSync } from "zlib";
|
|
927
|
+
import { createCanvas } from "@napi-rs/canvas";
|
|
928
|
+
import sharp4 from "sharp";
|
|
929
|
+
async function readSheet(file) {
|
|
930
|
+
const value = JSON.parse(await readFile4(resolve4(file), "utf8"));
|
|
931
|
+
const parsed = stickerSheetSchema.safeParse(value);
|
|
932
|
+
if (!parsed.success)
|
|
933
|
+
throw new InputError(
|
|
934
|
+
`Invalid sheet: ${parsed.error.issues.map((issue) => `${issue.path.join(".")}: ${issue.message}`).join("; ")}`
|
|
935
|
+
);
|
|
936
|
+
return parsed.data;
|
|
937
|
+
}
|
|
938
|
+
function newSheet(type, size, name) {
|
|
939
|
+
if (typeof type !== "string" || !sheetTypeIds.includes(type))
|
|
940
|
+
throw new InputError(`--type must be one of ${sheetTypeIds.join(", ")}.`);
|
|
941
|
+
const sheet = createStickerSheet(
|
|
942
|
+
type,
|
|
943
|
+
typeof name === "string" ? name : void 0
|
|
944
|
+
);
|
|
945
|
+
if (size !== void 0) {
|
|
946
|
+
if (typeof size !== "string" || !(size in sheetSizes))
|
|
947
|
+
throw new InputError(`--size must be one of ${Object.keys(sheetSizes).join(", ")}.`);
|
|
948
|
+
sheet.size = size;
|
|
949
|
+
}
|
|
950
|
+
return parse(sheet);
|
|
951
|
+
}
|
|
952
|
+
function parse(sheet) {
|
|
953
|
+
const parsed = stickerSheetSchema.safeParse(sheet);
|
|
954
|
+
if (!parsed.success)
|
|
955
|
+
throw new InputError(parsed.error.issues.map((issue) => issue.message).join("; "));
|
|
956
|
+
return parsed.data;
|
|
957
|
+
}
|
|
958
|
+
async function readSticker(source) {
|
|
959
|
+
const style2 = stickerStyles.find((entry) => entry.source === source);
|
|
960
|
+
if (style2) return badgeDesignSchema.parse(structuredClone(style2));
|
|
961
|
+
const design = badgeDesignSchema.parse(JSON.parse(await readFile4(resolve4(source), "utf8")));
|
|
962
|
+
if (design.product !== "sticker") throw new InputError(`${source} is not a sticker design.`);
|
|
963
|
+
return design;
|
|
964
|
+
}
|
|
965
|
+
var artCache = /* @__PURE__ */ new Map();
|
|
966
|
+
function flatArt(design) {
|
|
967
|
+
const key = JSON.stringify(design);
|
|
968
|
+
let art = artCache.get(key);
|
|
969
|
+
if (!art) {
|
|
970
|
+
art = renderBadge(design, { face: "front", person: {} }).then(async ({ png }) => {
|
|
971
|
+
const { data, info } = await sharp4(png).ensureAlpha().raw().toBuffer({ resolveWithObject: true });
|
|
972
|
+
return {
|
|
973
|
+
width: info.width,
|
|
974
|
+
height: info.height,
|
|
975
|
+
rgba: new Uint8ClampedArray(data.buffer, data.byteOffset, data.length)
|
|
976
|
+
};
|
|
977
|
+
});
|
|
978
|
+
artCache.set(key, art);
|
|
979
|
+
}
|
|
980
|
+
return art;
|
|
981
|
+
}
|
|
982
|
+
async function sheetArt(sheet) {
|
|
983
|
+
return new Map(
|
|
984
|
+
await Promise.all(
|
|
985
|
+
sheet.items.map(async (item) => [item.id, await flatArt(item.design)])
|
|
986
|
+
)
|
|
987
|
+
);
|
|
988
|
+
}
|
|
989
|
+
function bounds(sheet) {
|
|
990
|
+
const spec = sheetSpec(sheet);
|
|
991
|
+
return { widthMm: spec.widthMm ?? 0, heightMm: spec.heightMm ?? 0 };
|
|
992
|
+
}
|
|
993
|
+
async function addToSheet(sheet, source, widthMm) {
|
|
994
|
+
const type = sheetTypes[sheet.type];
|
|
995
|
+
if (type.cut === "die-cut" && sheet.items.length)
|
|
996
|
+
throw new InputError("A single sticker file holds one sticker. Start another sheet.");
|
|
997
|
+
const width = widthMm === void 0 ? type.stickerWidthMm : Number(widthMm);
|
|
998
|
+
if (!Number.isFinite(width) || width < 5 || width > 300)
|
|
999
|
+
throw new InputError("--width-mm must be a number from 5 to 300.");
|
|
1000
|
+
const design = await readSticker(source);
|
|
1001
|
+
const base = (design.source ?? "sticker").replace(/[^a-z0-9-]/g, "").slice(0, 40) || "sticker";
|
|
1002
|
+
let id = base;
|
|
1003
|
+
for (let n = 2; sheet.items.some((item2) => item2.id === id); n++) id = `${base}-${n}`;
|
|
1004
|
+
const spec = sheetSpec(sheet);
|
|
1005
|
+
const item = {
|
|
1006
|
+
id,
|
|
1007
|
+
design,
|
|
1008
|
+
x: (spec.widthMm ?? width) / 2,
|
|
1009
|
+
y: (spec.heightMm ?? width) / 2,
|
|
1010
|
+
rotation: 0,
|
|
1011
|
+
widthMm: width
|
|
1012
|
+
};
|
|
1013
|
+
const next = parse({ ...sheet, items: [...sheet.items, item] });
|
|
1014
|
+
if (type.cut === "die-cut") return { sheet: next, item, placed: true };
|
|
1015
|
+
const print = prepareSheetPrint(next, await sheetArt(next));
|
|
1016
|
+
const moved = placeNear(
|
|
1017
|
+
spec,
|
|
1018
|
+
bounds(next),
|
|
1019
|
+
next.items.map((entry) => ({ item: entry, shape: print.shapes.get(entry.id) })),
|
|
1020
|
+
id,
|
|
1021
|
+
[item.x, item.y]
|
|
1022
|
+
);
|
|
1023
|
+
if (moved) next.items[next.items.length - 1] = moved;
|
|
1024
|
+
return { sheet: next, item: moved ?? item, placed: Boolean(moved) };
|
|
1025
|
+
}
|
|
1026
|
+
async function packStickerSheet(sheet) {
|
|
1027
|
+
if (sheetTypes[sheet.type].cut === "die-cut") return { sheet, missing: [] };
|
|
1028
|
+
const print = prepareSheetPrint(sheet, await sheetArt(sheet));
|
|
1029
|
+
const packed = packSheet(
|
|
1030
|
+
sheetSpec(sheet),
|
|
1031
|
+
bounds(sheet),
|
|
1032
|
+
sheet.items.map((item) => ({ item, shape: print.shapes.get(item.id) })),
|
|
1033
|
+
sheet.hero
|
|
1034
|
+
);
|
|
1035
|
+
return { sheet: parse({ ...sheet, items: packed.items }), missing: packed.missing };
|
|
1036
|
+
}
|
|
1037
|
+
async function checkSheet(sheet) {
|
|
1038
|
+
return prepareSheetPrint(sheet, await sheetArt(sheet));
|
|
1039
|
+
}
|
|
1040
|
+
function sheetReport(print) {
|
|
1041
|
+
return {
|
|
1042
|
+
printable: print.printable,
|
|
1043
|
+
widthMm: Number(print.widthMm.toFixed(2)),
|
|
1044
|
+
heightMm: Number(print.heightMm.toFixed(2)),
|
|
1045
|
+
stickers: print.items.length,
|
|
1046
|
+
violations: print.violations,
|
|
1047
|
+
issues: print.issues.map(({ id, issue }) => ({ id, ...issue }))
|
|
1048
|
+
};
|
|
1049
|
+
}
|
|
1050
|
+
async function exportSheetPdf(sheet, print) {
|
|
1051
|
+
return sheetPrintPdf(sheet, print, async (bytes) => new Uint8Array(deflateSync(bytes)));
|
|
1052
|
+
}
|
|
1053
|
+
function sheetPreviewPng(print, dpi = 150) {
|
|
1054
|
+
const scale = dpi / 25.4;
|
|
1055
|
+
const canvas = createCanvas(
|
|
1056
|
+
Math.round(print.widthMm * scale),
|
|
1057
|
+
Math.round(print.heightMm * scale)
|
|
1058
|
+
);
|
|
1059
|
+
const context = canvas.getContext("2d");
|
|
1060
|
+
context.fillStyle = "#f4f3ef";
|
|
1061
|
+
context.fillRect(0, 0, canvas.width, canvas.height);
|
|
1062
|
+
for (const item of print.pdfItems) {
|
|
1063
|
+
const art = createCanvas(item.art.width, item.art.height);
|
|
1064
|
+
const data = art.getContext("2d").createImageData(item.art.width, item.art.height);
|
|
1065
|
+
data.data.set(item.art.rgba);
|
|
1066
|
+
art.getContext("2d").putImageData(data, 0, 0);
|
|
1067
|
+
context.save();
|
|
1068
|
+
context.translate(item.x * scale, item.y * scale);
|
|
1069
|
+
context.rotate(item.rotation * Math.PI / 180);
|
|
1070
|
+
context.drawImage(
|
|
1071
|
+
art,
|
|
1072
|
+
-item.widthMm / 2 * scale,
|
|
1073
|
+
-item.heightMm / 2 * scale,
|
|
1074
|
+
item.widthMm * scale,
|
|
1075
|
+
item.heightMm * scale
|
|
1076
|
+
);
|
|
1077
|
+
context.restore();
|
|
1078
|
+
context.strokeStyle = "#ff00ff";
|
|
1079
|
+
context.lineWidth = Math.max(1, 0.2 * scale);
|
|
1080
|
+
context.beginPath();
|
|
1081
|
+
for (const loop of item.cut) {
|
|
1082
|
+
for (const [i, [x, y]] of loop.entries())
|
|
1083
|
+
if (i) context.lineTo(x * scale, y * scale);
|
|
1084
|
+
else context.moveTo(x * scale, y * scale);
|
|
1085
|
+
context.closePath();
|
|
1086
|
+
}
|
|
1087
|
+
context.stroke();
|
|
1088
|
+
}
|
|
1089
|
+
return canvas.toBuffer("image/png");
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
// src/sticker-cut.ts
|
|
1093
|
+
import sharp5 from "sharp";
|
|
1094
|
+
async function stickerPrintCut(design) {
|
|
1095
|
+
const { png } = await renderBadge(design, { face: "front", person: {} });
|
|
1096
|
+
const { data, info } = await sharp5(png).ensureAlpha().raw().toBuffer({ resolveWithObject: true });
|
|
1097
|
+
const material = design.sticker ?? defaultStickerMaterial;
|
|
1098
|
+
const cut = stickerCut(
|
|
1099
|
+
new Uint8ClampedArray(data.buffer, data.byteOffset, data.length),
|
|
1100
|
+
info.width,
|
|
1101
|
+
info.height,
|
|
1102
|
+
material.border,
|
|
1103
|
+
material.widthMm
|
|
1104
|
+
);
|
|
1105
|
+
return { cut, svg: stickerCutSvg(cut) };
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
// src/sticker-material.ts
|
|
1109
|
+
import { readFile as readFile5, stat as stat3 } from "fs/promises";
|
|
1110
|
+
import { basename as basename2 } from "path";
|
|
1111
|
+
import sharp6 from "sharp";
|
|
1112
|
+
async function bindStickerMap(design, layerId, path) {
|
|
1113
|
+
if (design.product !== "sticker") throw new InputError("Optical maps require a sticker.");
|
|
1114
|
+
const layer = design.front.layers.find((layer2) => layer2.id === layerId);
|
|
1115
|
+
if (layer?.kind !== "image") throw new InputError("Choose a front image layer with --id.");
|
|
1116
|
+
const info = await stat3(path);
|
|
1117
|
+
if (!info.isFile() || info.size > 4e6)
|
|
1118
|
+
throw new InputError("Use an optical image below 4 MB.");
|
|
1119
|
+
const bytes = await readFile5(path);
|
|
1120
|
+
const meta = await sharp6(bytes, { limitInputPixels: 16777216 }).metadata();
|
|
1121
|
+
if (!meta.width || !meta.height || meta.width > 4096 || meta.height > 4096 || !["png", "webp"].includes(meta.format ?? ""))
|
|
1122
|
+
throw new InputError("Use an RGB PNG or WebP optical map up to 4096 \xD7 4096.");
|
|
1123
|
+
const asset = design.sticker?.effectMap?.asset ?? "optical-map";
|
|
1124
|
+
if (design.front.layers.some((item) => item.kind === "image" && item.asset === asset))
|
|
1125
|
+
throw new InputError(
|
|
1126
|
+
"The optical-map asset is used as artwork. Rename it before binding a map."
|
|
1127
|
+
);
|
|
1128
|
+
return badgeDesignSchema.parse({
|
|
1129
|
+
...design,
|
|
1130
|
+
assets: {
|
|
1131
|
+
...design.assets,
|
|
1132
|
+
[asset]: {
|
|
1133
|
+
source: "image",
|
|
1134
|
+
name: basename2(path).slice(0, 80),
|
|
1135
|
+
data: `data:image/${meta.format};base64,${bytes.toString("base64")}`
|
|
1136
|
+
}
|
|
1137
|
+
},
|
|
1138
|
+
sticker: {
|
|
1139
|
+
...design.sticker ?? defaultStickerMaterial,
|
|
1140
|
+
effectMap: { asset, layer: layerId }
|
|
1141
|
+
}
|
|
1142
|
+
});
|
|
1143
|
+
}
|
|
1144
|
+
|
|
911
1145
|
// src/wallet.ts
|
|
912
1146
|
import { spawn } from "child_process";
|
|
913
1147
|
import { createHash as createHash3 } from "crypto";
|
|
914
1148
|
import { existsSync as existsSync2 } from "fs";
|
|
915
|
-
import { chmod, lstat, mkdir as mkdir2, mkdtemp, readFile as
|
|
1149
|
+
import { chmod, lstat, mkdir as mkdir2, mkdtemp, readFile as readFile6, rename as rename2, rm as rm2, writeFile as writeFile2 } from "fs/promises";
|
|
916
1150
|
import { homedir as homedir2, tmpdir as tmpdir2 } from "os";
|
|
917
|
-
import { dirname as dirname2, extname as extname3, join as join3, resolve as
|
|
918
|
-
import
|
|
1151
|
+
import { dirname as dirname2, extname as extname3, join as join3, resolve as resolve5 } from "path";
|
|
1152
|
+
import sharp7 from "sharp";
|
|
919
1153
|
|
|
920
1154
|
// src/wallet-release.json
|
|
921
1155
|
var wallet_release_default = {
|
|
@@ -945,7 +1179,7 @@ var WalletError = class extends Error {
|
|
|
945
1179
|
function installation() {
|
|
946
1180
|
const platform = `${process.platform}-${process.arch}`;
|
|
947
1181
|
const cache = process.env.MAKEABLES_WALLET_CACHE ?? join3(homedir2(), "Library", "Caches", "Makeables", "wallet");
|
|
948
|
-
const root =
|
|
1182
|
+
const root = resolve5(cache, `${wallet_release_default.version}-${platform}`);
|
|
949
1183
|
return { platform, root, executable: join3(root, "makeables-wallet", "makeables-wallet") };
|
|
950
1184
|
}
|
|
951
1185
|
async function execute(command, args, progress) {
|
|
@@ -1096,7 +1330,7 @@ async function companion() {
|
|
|
1096
1330
|
return info.installed;
|
|
1097
1331
|
}
|
|
1098
1332
|
async function walletArtwork(file, side2 = "front") {
|
|
1099
|
-
const input =
|
|
1333
|
+
const input = resolve5(file);
|
|
1100
1334
|
const info = await lstat(input);
|
|
1101
1335
|
if (!info.isFile() || info.size > 12e6)
|
|
1102
1336
|
throw new InputError("Expected a local design or artwork file below 12 MB.");
|
|
@@ -1106,13 +1340,13 @@ async function walletArtwork(file, side2 = "front") {
|
|
|
1106
1340
|
if (design.product !== "card") throw new InputError("Wallet artwork requires a card document.");
|
|
1107
1341
|
source = (await renderBadge(design, { face: side2, person: {} })).png;
|
|
1108
1342
|
} else {
|
|
1109
|
-
source = await
|
|
1343
|
+
source = await readFile6(input);
|
|
1110
1344
|
if (extname3(input).toLowerCase() === ".svg") {
|
|
1111
1345
|
const result = checkSubmittedSvg(source.toString("utf8"));
|
|
1112
1346
|
if (!result.ok) throw new InputError(result.error);
|
|
1113
1347
|
}
|
|
1114
1348
|
}
|
|
1115
|
-
return
|
|
1349
|
+
return sharp7(source, { limitInputPixels: 24e6, density: 144 }).rotate().resize(1536, 969, { fit: "contain", background: "#000000" }).flatten({ background: "#000000" }).toColourspace("srgb").png().toBuffer();
|
|
1116
1350
|
}
|
|
1117
1351
|
async function walletCommand(command, values, progress) {
|
|
1118
1352
|
const need = (flag) => {
|
|
@@ -1163,7 +1397,7 @@ async function walletCommand(command, values, progress) {
|
|
|
1163
1397
|
try {
|
|
1164
1398
|
const raster = join3(work, "artwork.png");
|
|
1165
1399
|
await writeFile2(raster, png, { mode: 384 });
|
|
1166
|
-
const target = command === "wallet prep" ?
|
|
1400
|
+
const target = command === "wallet prep" ? resolve5(need("out")) : join3(work, "assets");
|
|
1167
1401
|
const prepared = await invoke(
|
|
1168
1402
|
executable,
|
|
1169
1403
|
["prepare", "--file", raster, "--out", target],
|
|
@@ -1187,12 +1421,19 @@ var commands = [
|
|
|
1187
1421
|
"designs list",
|
|
1188
1422
|
"new card --design <slug> | --style <id> --out <new-file> [--dry-run]",
|
|
1189
1423
|
"material set --file <design.json> --params <json|@file>",
|
|
1424
|
+
"material map --file <sticker.json> --id <image-layer> --ref <optical.png|optical.webp>",
|
|
1190
1425
|
"new badge --style <id> --out <new-file> [--dry-run]",
|
|
1191
1426
|
"new sticker --style <id> --out <new-file> [--dry-run]",
|
|
1192
1427
|
"layer list --file <design.json>",
|
|
1193
1428
|
"layer add --file <design.json> --sticker <petdex:slug|path> [--side front|back]",
|
|
1194
1429
|
"layer set --file <design.json> --id <layer> [--side front|back] [--x --y --rotation] [--params <json|@file>]",
|
|
1195
1430
|
"layer remove --file <design.json> --id <layer> [--side front|back]",
|
|
1431
|
+
"cut --file <sticker.json> [--out <cut.svg>] [--overwrite]",
|
|
1432
|
+
"sheet new --type classic|small|single|clear|holographic --out <new-file> [--size a5|a6|10x15] [--name <title>]",
|
|
1433
|
+
"sheet add --file <sheet.json> --sticker <sticker.json|style-id> [--width-mm <mm>]",
|
|
1434
|
+
"sheet pack --file <sheet.json>",
|
|
1435
|
+
"sheet check --file <sheet.json>",
|
|
1436
|
+
"sheet export --file <sheet.json> --out <sheet.pdf> [--preview <proof.png>] [--overwrite]",
|
|
1196
1437
|
"render --file <design.json|design.makeables.json> --out <png> [--side front|back] [--photo <image>] [--name --role --number] [--overwrite]",
|
|
1197
1438
|
"validate --file <design.json|artwork.svg> [--out <preview.png>]",
|
|
1198
1439
|
"new card --file <artwork.svg|image.png> --out <card.json> [--name <title>]",
|
|
@@ -1240,7 +1481,7 @@ function emit(data, next, human, json) {
|
|
|
1240
1481
|
);
|
|
1241
1482
|
}
|
|
1242
1483
|
async function newPath(file) {
|
|
1243
|
-
const path =
|
|
1484
|
+
const path = resolve6(file);
|
|
1244
1485
|
try {
|
|
1245
1486
|
await lstat2(path);
|
|
1246
1487
|
throw new InputError(`File already exists: ${path}. Choose a new --out path.`);
|
|
@@ -1294,7 +1535,11 @@ async function run() {
|
|
|
1294
1535
|
card: { type: "string" },
|
|
1295
1536
|
backup: { type: "string" },
|
|
1296
1537
|
device: { type: "string" },
|
|
1297
|
-
seconds: { type: "string" }
|
|
1538
|
+
seconds: { type: "string" },
|
|
1539
|
+
type: { type: "string" },
|
|
1540
|
+
size: { type: "string" },
|
|
1541
|
+
"width-mm": { type: "string" },
|
|
1542
|
+
preview: { type: "string" }
|
|
1298
1543
|
}
|
|
1299
1544
|
});
|
|
1300
1545
|
const json = machineOutput(Boolean(values.json));
|
|
@@ -1379,11 +1624,18 @@ From a built checkout: npm run studio -- <command>`,
|
|
|
1379
1624
|
"new card": ["design", "style", "file", "name", "out", "dry-run"],
|
|
1380
1625
|
"new sticker": ["style", "out", "dry-run"],
|
|
1381
1626
|
"material set": ["file", "params"],
|
|
1627
|
+
"material map": ["file", "id", "ref"],
|
|
1382
1628
|
"new badge": ["style", "out", "dry-run"],
|
|
1383
1629
|
"layer list": ["file"],
|
|
1384
1630
|
"layer add": ["file", "sticker", "side"],
|
|
1385
1631
|
"layer set": ["file", "id", "x", "y", "rotation", "side", "params"],
|
|
1386
1632
|
"layer remove": ["file", "id", "side"],
|
|
1633
|
+
cut: ["file", "out", "overwrite"],
|
|
1634
|
+
"sheet new": ["type", "size", "name", "out"],
|
|
1635
|
+
"sheet add": ["file", "sticker", "width-mm"],
|
|
1636
|
+
"sheet pack": ["file"],
|
|
1637
|
+
"sheet check": ["file"],
|
|
1638
|
+
"sheet export": ["file", "out", "preview", "overwrite"],
|
|
1387
1639
|
render: ["file", "out", "overwrite", "side", "photo", "name", "role", "number"],
|
|
1388
1640
|
validate: ["file", "out"]
|
|
1389
1641
|
};
|
|
@@ -1552,7 +1804,7 @@ Run makeables skills get core for setup.`,
|
|
|
1552
1804
|
}
|
|
1553
1805
|
if (command === "mcp") {
|
|
1554
1806
|
try {
|
|
1555
|
-
const { runMcp } = await import("./mcp-
|
|
1807
|
+
const { runMcp } = await import("./mcp-AOJ6UJSC.js");
|
|
1556
1808
|
await runMcp(values.root ?? process.cwd());
|
|
1557
1809
|
} catch (error) {
|
|
1558
1810
|
process.stderr.write(
|
|
@@ -1611,7 +1863,7 @@ Keep this process running while you design.`,
|
|
|
1611
1863
|
const info = await lstat2(path2);
|
|
1612
1864
|
if (!info.isFile() || info.size > 12e6)
|
|
1613
1865
|
throw new InputError("Expected a params file below 12 MB.");
|
|
1614
|
-
params = JSON.parse(await
|
|
1866
|
+
params = JSON.parse(await readFile7(path2, "utf8"));
|
|
1615
1867
|
} else if (values.params) params = JSON.parse(values.params);
|
|
1616
1868
|
}
|
|
1617
1869
|
const result2 = await studioRequest(
|
|
@@ -1653,7 +1905,7 @@ Keep this process running while you design.`,
|
|
|
1653
1905
|
emit(
|
|
1654
1906
|
{ styles },
|
|
1655
1907
|
[
|
|
1656
|
-
values.product === "sticker" ? "npx makeables new sticker --style
|
|
1908
|
+
values.product === "sticker" ? "npx makeables new sticker --style typingmind-absolutely-right --out sticker.json" : values.product === "card" ? "npx makeables new card --style pearl --out card.json" : "npx makeables design create --style opalo-lunar --out badge.json"
|
|
1657
1909
|
],
|
|
1658
1910
|
`${style(`${styles.length} starting points`, "1", !json)}
|
|
1659
1911
|
|
|
@@ -1717,14 +1969,14 @@ ${dryRun ? "Dry run. No files changed." : "2 faces \xB7 editable layers"}`,
|
|
|
1717
1969
|
return;
|
|
1718
1970
|
}
|
|
1719
1971
|
if (!values.file) throw new InputError("Supply --file to validate a document.");
|
|
1720
|
-
const path =
|
|
1972
|
+
const path = resolve6(values.file);
|
|
1721
1973
|
const handle = await open2(path, "r");
|
|
1722
1974
|
let input;
|
|
1723
1975
|
try {
|
|
1724
1976
|
const info = await handle.stat();
|
|
1725
1977
|
if (!info.isFile() || info.size > 2 * 1024 * 1024)
|
|
1726
1978
|
throw new InputError("Expected a JSON file smaller than 2 MB.");
|
|
1727
|
-
input = JSON.parse(await
|
|
1979
|
+
input = JSON.parse(await readFile7(handle, "utf8"));
|
|
1728
1980
|
} finally {
|
|
1729
1981
|
await handle.close();
|
|
1730
1982
|
}
|
|
@@ -1743,7 +1995,7 @@ Schema and layout constraints passed.`,
|
|
|
1743
1995
|
}
|
|
1744
1996
|
async function readParams(value) {
|
|
1745
1997
|
return JSON.parse(
|
|
1746
|
-
value.startsWith("@") ? await
|
|
1998
|
+
value.startsWith("@") ? await readFile7(resolve6(value.slice(1)), "utf8") : value
|
|
1747
1999
|
);
|
|
1748
2000
|
}
|
|
1749
2001
|
async function cardCommand(command, values, json) {
|
|
@@ -1754,7 +2006,9 @@ async function cardCommand(command, values, json) {
|
|
|
1754
2006
|
};
|
|
1755
2007
|
const renderHint = (path, face, design) => `npx makeables render --file ${JSON.stringify(path)} --side ${face} --out ${design.product ?? "badge"}.png --overwrite`;
|
|
1756
2008
|
if (command === "new sticker") {
|
|
1757
|
-
const preset = stickerStyles.find(
|
|
2009
|
+
const preset = stickerStyles.find(
|
|
2010
|
+
(entry) => entry.source === (values.style ?? "typingmind-absolutely-right")
|
|
2011
|
+
);
|
|
1758
2012
|
if (!preset)
|
|
1759
2013
|
throw new InputError("Unknown sticker style. Run makeables styles list --product sticker.");
|
|
1760
2014
|
const design = badgeDesignSchema.parse(structuredClone(preset));
|
|
@@ -1802,11 +2056,27 @@ ${path ?? `No file written. Add --out ${kind}.json.`}`,
|
|
|
1802
2056
|
);
|
|
1803
2057
|
return true;
|
|
1804
2058
|
}
|
|
2059
|
+
if (command === "material map") {
|
|
2060
|
+
const path = resolve6(need(file, "file"));
|
|
2061
|
+
const next = await bindStickerMap(
|
|
2062
|
+
await readBadge(path),
|
|
2063
|
+
need(values.id, "id"),
|
|
2064
|
+
need(values.ref, "ref")
|
|
2065
|
+
);
|
|
2066
|
+
await writeAtomic(path, JSON.stringify(next, null, 2));
|
|
2067
|
+
emit(
|
|
2068
|
+
{ material: next.sticker },
|
|
2069
|
+
["Preview the foil, glitter and relief in the sticker studio."],
|
|
2070
|
+
"Optical map attached.",
|
|
2071
|
+
json
|
|
2072
|
+
);
|
|
2073
|
+
return true;
|
|
2074
|
+
}
|
|
1805
2075
|
if (command === "material set") {
|
|
1806
|
-
const path =
|
|
2076
|
+
const path = resolve6(need(file, "file"));
|
|
1807
2077
|
const design = await readBadge(path);
|
|
1808
2078
|
const raw = need(values.params, "params");
|
|
1809
|
-
const input = JSON.parse(raw.startsWith("@") ? await
|
|
2079
|
+
const input = JSON.parse(raw.startsWith("@") ? await readFile7(raw.slice(1), "utf8") : raw);
|
|
1810
2080
|
if (design.product === "sticker") {
|
|
1811
2081
|
const patch2 = stickerMaterialSchema.partial().strict().safeParse(input);
|
|
1812
2082
|
if (!patch2.success)
|
|
@@ -1890,7 +2160,7 @@ ${path ?? `No file written. Add --out ${kind}.json.`}`,
|
|
|
1890
2160
|
await readBadge(path),
|
|
1891
2161
|
face,
|
|
1892
2162
|
need(values.sticker, "sticker"),
|
|
1893
|
-
dirname3(
|
|
2163
|
+
dirname3(resolve6(path))
|
|
1894
2164
|
);
|
|
1895
2165
|
await writeAtomic(path, `${JSON.stringify(design, null, 2)}
|
|
1896
2166
|
`);
|
|
@@ -1903,16 +2173,153 @@ ${path ?? `No file written. Add --out ${kind}.json.`}`,
|
|
|
1903
2173
|
);
|
|
1904
2174
|
return true;
|
|
1905
2175
|
}
|
|
2176
|
+
if (command.startsWith("sheet ")) {
|
|
2177
|
+
const write = async (target, bytes) => {
|
|
2178
|
+
const handle = await open2(target, values.overwrite ? "w" : "wx", 420);
|
|
2179
|
+
try {
|
|
2180
|
+
await handle.writeFile(bytes);
|
|
2181
|
+
} finally {
|
|
2182
|
+
await handle.close();
|
|
2183
|
+
}
|
|
2184
|
+
};
|
|
2185
|
+
const save = (path2, sheet2) => writeAtomic(path2, `${JSON.stringify(sheet2, null, 2)}
|
|
2186
|
+
`);
|
|
2187
|
+
const checkNext = (path2) => [
|
|
2188
|
+
`makeables sheet check --file ${JSON.stringify(path2)}`,
|
|
2189
|
+
`makeables sheet export --file ${JSON.stringify(path2)} --out sheet.pdf --preview proof.png`
|
|
2190
|
+
];
|
|
2191
|
+
if (command === "sheet new") {
|
|
2192
|
+
const sheet2 = newSheet(values.type, values.size, values.name);
|
|
2193
|
+
const path2 = await newPath(need(values.out, "out"));
|
|
2194
|
+
await writeAtomic(path2, `${JSON.stringify(sheet2, null, 2)}
|
|
2195
|
+
`, true);
|
|
2196
|
+
emit(
|
|
2197
|
+
{ path: path2, sheet: sheet2 },
|
|
2198
|
+
[`makeables sheet add --file ${JSON.stringify(path2)} --sticker <sticker.json|style-id>`],
|
|
2199
|
+
`${style("Sheet ready", "1", !json)} ${sheet2.name} ${path2}`,
|
|
2200
|
+
json
|
|
2201
|
+
);
|
|
2202
|
+
return true;
|
|
2203
|
+
}
|
|
2204
|
+
const path = need(file, "file");
|
|
2205
|
+
const sheet = await readSheet(path);
|
|
2206
|
+
if (command === "sheet add") {
|
|
2207
|
+
const result = await addToSheet(sheet, need(values.sticker, "sticker"), values["width-mm"]);
|
|
2208
|
+
await save(path, result.sheet);
|
|
2209
|
+
emit(
|
|
2210
|
+
{
|
|
2211
|
+
item: { ...result.item, design: void 0 },
|
|
2212
|
+
placed: result.placed,
|
|
2213
|
+
stickers: result.sheet.items.length
|
|
2214
|
+
},
|
|
2215
|
+
result.placed ? checkNext(path) : [
|
|
2216
|
+
`No free spot. Run makeables sheet pack --file ${JSON.stringify(path)} or choose a smaller --width-mm.`
|
|
2217
|
+
],
|
|
2218
|
+
`${style(result.placed ? "Added" : "Added, overlapping", "1", !json)} ${result.item.id} (${result.item.widthMm} mm)`,
|
|
2219
|
+
json
|
|
2220
|
+
);
|
|
2221
|
+
return true;
|
|
2222
|
+
}
|
|
2223
|
+
if (command === "sheet pack") {
|
|
2224
|
+
const result = await packStickerSheet(sheet);
|
|
2225
|
+
await save(path, result.sheet);
|
|
2226
|
+
emit(
|
|
2227
|
+
{ stickers: result.sheet.items.length, missing: result.missing },
|
|
2228
|
+
result.missing.length ? ["Remove stickers or make them smaller, then pack again.", ...checkNext(path)] : checkNext(path),
|
|
2229
|
+
`${style("Packed", "1", !json)} ${result.sheet.items.length - result.missing.length}/${result.sheet.items.length}${result.missing.length ? `, no room for ${result.missing.join(", ")}` : ""}`,
|
|
2230
|
+
json
|
|
2231
|
+
);
|
|
2232
|
+
return true;
|
|
2233
|
+
}
|
|
2234
|
+
const print = await checkSheet(sheet);
|
|
2235
|
+
const report = sheetReport(print);
|
|
2236
|
+
const lines = [
|
|
2237
|
+
...report.violations.map(
|
|
2238
|
+
(v) => v.code === "too-close" ? ` error: ${v.ids.join(" and ")} are ${v.distanceMm.toFixed(1)} mm apart; the gap is ${v.gapMm} mm.` : ` error: ${v.ids[0]} crosses the ${v.marginMm} mm margin.`
|
|
2239
|
+
),
|
|
2240
|
+
...report.issues.map((issue) => ` ${issue.severity}: ${issue.id}: ${issue.message}`)
|
|
2241
|
+
];
|
|
2242
|
+
const summary = `${style(report.printable ? "Printable" : "Not printable", "1", !json)} ${report.widthMm} \xD7 ${report.heightMm} mm, ${report.stickers} sticker${report.stickers === 1 ? "" : "s"}`;
|
|
2243
|
+
if (command === "sheet check") {
|
|
2244
|
+
emit(
|
|
2245
|
+
report,
|
|
2246
|
+
report.printable ? checkNext(path).slice(1) : ["Fix the errors, then check again."],
|
|
2247
|
+
[summary, ...lines].join("\n"),
|
|
2248
|
+
json
|
|
2249
|
+
);
|
|
2250
|
+
return true;
|
|
2251
|
+
}
|
|
2252
|
+
if (command === "sheet export") {
|
|
2253
|
+
if (!report.printable)
|
|
2254
|
+
throw new InputError(
|
|
2255
|
+
`The sheet is not printable. Run makeables sheet check --file ${JSON.stringify(path)}.`
|
|
2256
|
+
);
|
|
2257
|
+
const out = resolve6(need(values.out, "out"));
|
|
2258
|
+
if (!out.endsWith(".pdf")) throw new InputError("--out must end in .pdf.");
|
|
2259
|
+
const preview = typeof values.preview === "string" ? resolve6(values.preview) : void 0;
|
|
2260
|
+
if (preview && !preview.endsWith(".png")) throw new InputError("--preview must end in .png.");
|
|
2261
|
+
const targets = [out, ...preview ? [preview] : []];
|
|
2262
|
+
if (!values.overwrite) for (const target of targets) await newPath(target);
|
|
2263
|
+
await write(out, await exportSheetPdf(sheet, print));
|
|
2264
|
+
if (preview) await write(preview, sheetPreviewPng(print));
|
|
2265
|
+
emit(
|
|
2266
|
+
{ ...report, path: out, ...preview ? { preview } : {} },
|
|
2267
|
+
["Send the PDF to the printer. Confirm its spot names and template before the first run."],
|
|
2268
|
+
[`${summary} ${out}${preview ? ` ${preview}` : ""}`, ...lines].join("\n"),
|
|
2269
|
+
json
|
|
2270
|
+
);
|
|
2271
|
+
return true;
|
|
2272
|
+
}
|
|
2273
|
+
}
|
|
2274
|
+
if (command === "cut") {
|
|
2275
|
+
const design = await readBadge(need(file, "file"));
|
|
2276
|
+
if (design.product !== "sticker") throw new InputError("cut applies to sticker designs.");
|
|
2277
|
+
const out = typeof values.out === "string" ? resolve6(values.out) : void 0;
|
|
2278
|
+
if (out && !out.endsWith(".svg")) throw new InputError("--out must end in .svg.");
|
|
2279
|
+
const target = out && (values.overwrite ? out : await newPath(out));
|
|
2280
|
+
const { cut, svg } = await stickerPrintCut(design);
|
|
2281
|
+
if (target) {
|
|
2282
|
+
const handle = await open2(target, values.overwrite ? "w" : "wx", 420);
|
|
2283
|
+
try {
|
|
2284
|
+
await handle.writeFile(svg);
|
|
2285
|
+
} finally {
|
|
2286
|
+
await handle.close();
|
|
2287
|
+
}
|
|
2288
|
+
}
|
|
2289
|
+
const printable = !cut.issues.some((issue) => issue.severity === "error");
|
|
2290
|
+
const size = `${cut.widthMm.toFixed(1)} \xD7 ${cut.heightMm.toFixed(1)} mm, border ${cut.borderMm.toFixed(2)} mm`;
|
|
2291
|
+
emit(
|
|
2292
|
+
{
|
|
2293
|
+
...target ? { path: target } : {},
|
|
2294
|
+
printable,
|
|
2295
|
+
widthMm: cut.widthMm,
|
|
2296
|
+
heightMm: cut.heightMm,
|
|
2297
|
+
borderMm: cut.borderMm,
|
|
2298
|
+
paths: cut.paths.length,
|
|
2299
|
+
issues: cut.issues
|
|
2300
|
+
},
|
|
2301
|
+
[
|
|
2302
|
+
...cut.issues.map((issue) => issue.message),
|
|
2303
|
+
`Set the printed size with material set --params '{"widthMm":75}'.`
|
|
2304
|
+
],
|
|
2305
|
+
[
|
|
2306
|
+
`${style(printable ? "Printable" : "Not printable", "1", !json)} ${size}, ${cut.paths.length} cut path${cut.paths.length === 1 ? "" : "s"}${target ? ` ${target}` : ""}`,
|
|
2307
|
+
...cut.issues.map((issue) => ` ${issue.severity}: ${issue.message}`)
|
|
2308
|
+
].join("\n"),
|
|
2309
|
+
json
|
|
2310
|
+
);
|
|
2311
|
+
return true;
|
|
2312
|
+
}
|
|
1906
2313
|
if (command === "render") {
|
|
1907
2314
|
const path = need(file, "file");
|
|
1908
|
-
const out =
|
|
2315
|
+
const out = resolve6(need(values.out, "out"));
|
|
1909
2316
|
if (!out.endsWith(".png")) throw new InputError("--out must end in .png.");
|
|
1910
2317
|
const target = values.overwrite ? out : await newPath(out);
|
|
1911
2318
|
const number = values.number === void 0 ? void 0 : Number(values.number);
|
|
1912
2319
|
if (number !== void 0 && !Number.isInteger(number))
|
|
1913
2320
|
throw new InputError("--number must be an integer.");
|
|
1914
2321
|
const face = side(values.side);
|
|
1915
|
-
const value = JSON.parse(await
|
|
2322
|
+
const value = JSON.parse(await readFile7(resolve6(path), "utf8"));
|
|
1916
2323
|
const pkg = value?.format === "makeables-package" ? await readPackageJson(value) : void 0;
|
|
1917
2324
|
if (pkg && ["photo", "name", "role", "number"].some((key) => values[key] !== void 0))
|
|
1918
2325
|
throw new InputError(
|
|
@@ -1974,13 +2381,13 @@ ${path ?? `No file written. Add --out ${kind}.json.`}`,
|
|
|
1974
2381
|
return true;
|
|
1975
2382
|
}
|
|
1976
2383
|
if (command === "validate") {
|
|
1977
|
-
const path =
|
|
2384
|
+
const path = resolve6(need(file, "file"));
|
|
1978
2385
|
if (extname4(path).toLowerCase() === ".svg") {
|
|
1979
|
-
const source = await
|
|
2386
|
+
const source = await readFile7(path, "utf8");
|
|
1980
2387
|
const result2 = checkSubmittedSvg(source);
|
|
1981
2388
|
if (!result2.ok) throw new InputError(result2.error);
|
|
1982
2389
|
const preview = values.out ? await newPath(need(values.out, "out")) : void 0;
|
|
1983
|
-
if (preview) await
|
|
2390
|
+
if (preview) await sharp8(Buffer.from(result2.svg)).png().toFile(preview);
|
|
1984
2391
|
emit(
|
|
1985
2392
|
{
|
|
1986
2393
|
valid: true,
|
|
@@ -1997,7 +2404,7 @@ ${path ?? `No file written. Add --out ${kind}.json.`}`,
|
|
|
1997
2404
|
return true;
|
|
1998
2405
|
}
|
|
1999
2406
|
if (values.out) throw new InputError("--out is only used to preview SVG validation.");
|
|
2000
|
-
const input = JSON.parse(await
|
|
2407
|
+
const input = JSON.parse(await readFile7(path, "utf8"));
|
|
2001
2408
|
const result = badgeDesignSchema.safeParse(input);
|
|
2002
2409
|
if (!result.success)
|
|
2003
2410
|
throw new InputError(
|