@pixelvibing/cli 0.54.0 → 0.56.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.
@@ -3428,8 +3428,9 @@ function setPieces(asset) {
3428
3428
  function namesOf(name, count) {
3429
3429
  return count === 1 ? [name] : Array.from({ length: count }, (_, index) => `${name}/${index}`);
3430
3430
  }
3431
- function nameFor(asset, record, given2) {
3432
- const name = given2 ?? record.assets[asset.id]?.name ?? asset.name ?? SET_NAMES[asset.kind] ?? `${asset.kind}-${asset.id.slice(0, 8)}`;
3431
+ function nameFor(asset, record, given2, key, hd) {
3432
+ const platform = asset.name ?? SET_NAMES[asset.kind] ?? `${asset.kind}-${asset.id.slice(0, 8)}`;
3433
+ const name = given2 ?? record.assets[key]?.name ?? (hd ? `${platform}-hd` : platform);
3433
3434
  const problem = nameProblem(name);
3434
3435
  if (problem !== null) throw new CliError(`${name} is not a name a picture can be landed under (${problem}): give one with --as`);
3435
3436
  return name;
@@ -3442,13 +3443,22 @@ async function pullAsset(platform, local, ref, given2, options = {}) {
3442
3443
  if (asset.kind === "terrain") {
3443
3444
  throw new CliError(`${asset.id} is a terrain set: land it from the editor's Library, which cuts a set into the cells its brush paints. Nothing was landed`);
3444
3445
  }
3445
- const record = await readRecord(local);
3446
- const name = nameFor(asset, record, given2);
3447
3446
  const pieces = setPieces(asset);
3447
+ const hd = options.hd === true;
3448
+ const drawnSmooth = asset.data.pixelArt === false;
3449
+ if (hd && pieces === null) throw new CliError(`${asset.id} is a ${asset.kind}: only a UI's pieces and icons have an HD version. Nothing was landed`);
3450
+ if (hd && !drawnSmooth && pieces?.some((piece) => piece.hd === void 0 || asset.attachments?.[piece.hd] === void 0)) {
3451
+ throw new CliError(`${asset.id} was made before its pieces were kept as drawn, so it has no HD version. Nothing was landed`);
3452
+ }
3453
+ const lifted = hd && !drawnSmooth;
3454
+ const key = lifted ? `${asset.id}/hd` : asset.id;
3455
+ const record = await readRecord(local);
3456
+ const name = nameFor(asset, record, given2, key, lifted);
3448
3457
  const reference = REFERENCE_KINDS.has(asset.kind);
3449
3458
  const names = pieces === null ? namesOf(name, asset.images.length) : pieceNames(name, pieces);
3450
3459
  const files = names.map((each) => `${reference ? "ref" : "out"}/${each}.png`);
3451
- const pulled = { id: asset.id, kind: asset.kind, name, hash: asset.hash, files, data: asset.data };
3460
+ const version2 = pieces === null ? {} : { version: drawnSmooth || lifted ? "hd" : "pixel-art" };
3461
+ const pulled = { id: asset.id, kind: asset.kind, name, hash: asset.hash, files, ...version2, data: asset.data };
3452
3462
  const base = `/projects/${encodeURIComponent(project)}`;
3453
3463
  const json2 = async (path, init2) => (await platform.call(path, init2)).json();
3454
3464
  const art = await json2(`${base}/art`);
@@ -3456,7 +3466,9 @@ async function pullAsset(platform, local, ref, given2, options = {}) {
3456
3466
  const held = (each) => reference ? references.get(each) : art.pictures[each];
3457
3467
  const landing = [];
3458
3468
  for (const [index, each] of names.entries()) {
3459
- const image = asset.images[index].id;
3469
+ const own = asset.images[index];
3470
+ const drawn = lifted ? asset.attachments?.[pieces?.[index]?.hd] : void 0;
3471
+ const image = drawn?.id ?? own.id;
3460
3472
  const holds = held(each);
3461
3473
  if (holds === image) continue;
3462
3474
  if (holds !== void 0) {
@@ -3464,7 +3476,8 @@ async function pullAsset(platform, local, ref, given2, options = {}) {
3464
3476
  `the project's art already holds ${reference ? "a reference" : "a picture"} named ${each}, of something else: nothing was landed. Give this one another name with --as`
3465
3477
  );
3466
3478
  }
3467
- landing.push({ name: each, image, piece: pieces?.[index] ?? null });
3479
+ const piece = pieces?.[index] ?? null;
3480
+ landing.push({ name: each, image, piece: piece === null || drawn === void 0 ? piece : asDrawn(piece, drawn.width / own.width) });
3468
3481
  }
3469
3482
  if (landing.length > 0) {
3470
3483
  if (reference) {
@@ -3478,13 +3491,24 @@ async function pullAsset(platform, local, ref, given2, options = {}) {
3478
3491
  await json2(`${base}/art/land`, { method: "POST", body: { revision: art.revision, assets } });
3479
3492
  }
3480
3493
  }
3481
- await writeRecord(local, { ...record, assets: { ...record.assets, [asset.id]: { kind: asset.kind, name, hash: asset.hash, files } } });
3494
+ await writeRecord(local, { ...record, assets: { ...record.assets, [key]: { kind: asset.kind, name, hash: asset.hash, files } } });
3482
3495
  await bringFolder(platform, local, { map: false, aside: options.aside === true });
3483
3496
  return { ...pulled, current: landing.length === 0 };
3484
3497
  }
3498
+ function asDrawn(piece, scale) {
3499
+ if (piece.nineGrid === void 0 || !Number.isFinite(scale) || scale <= 1) return piece;
3500
+ const grown = (inset) => Math.round(inset * scale);
3501
+ const { top, right, bottom, left } = piece.nineGrid;
3502
+ return { ...piece, nineGrid: { top: grown(top), right: grown(right), bottom: grown(bottom), left: grown(left) } };
3503
+ }
3485
3504
  function pulledWords(pulled) {
3486
3505
  const where = pulled.files.length === 1 ? `pixelvibing/${pulled.files[0]}` : `${pulled.files.length} pictures under pixelvibing/`;
3487
- return pulled.current ? `${pulled.name} is current: the project's art holds it, and ${where} is what the platform has.` : `Pulled ${pulled.name}, ${pulled.kind}, into the project's art, and wrote ${where}.`;
3506
+ const said = pulled.current ? `${pulled.name} is current: the project's art holds it, and ${where} is what the platform has.` : `Pulled ${pulled.name}, ${pulled.kind}, into the project's art, and wrote ${where}.`;
3507
+ if (pulled.version === "hd" && pulled.data.pixelArt === false)
3508
+ return `${said} It was drawn without pixel art, so its pieces are HD and there is no pixel-art version.`;
3509
+ if (pulled.version === "hd") return `${said} These are the pieces as drawn (HD).`;
3510
+ if (pulled.version === "pixel-art") return `${said} These are the pixel-art pieces; --hd lands them as drawn.`;
3511
+ return said;
3488
3512
  }
3489
3513
  const date = (at) => at.slice(0, 10);
3490
3514
  function listed(assets) {
@@ -3518,9 +3542,10 @@ More: pixel-vibing-cli list${kind === void 0 ? "" : ` ${kind}`}${tag2 === null ?
3518
3542
  const pull = {
3519
3543
  name: "pull",
3520
3544
  summary: "Land one asset, by its id or its name, in the project's art, and write it into the local project",
3521
- usage: "<id|name> [--as <name>] [--aside]",
3545
+ usage: "<id|name> [--as <name>] [--aside] [--hd]",
3522
3546
  options: {
3523
3547
  as: { type: "string", value: "<name>", description: "land it under this name instead of the platform's" },
3548
+ hd: { type: "boolean", description: "land a UI's pieces or icons as drawn (HD), beside the pixel-art ones, instead of read back on the pixel grid" },
3524
3549
  aside: { type: "boolean", description: "keep a folder's own map and art under pixelvibing/before-platform/, and write it from the platform" }
3525
3550
  },
3526
3551
  positionals: [1, 1],
@@ -3528,7 +3553,10 @@ const pull = {
3528
3553
  const local = await projectHere(context.io.cwd);
3529
3554
  linkedOf(local);
3530
3555
  const platform = await signedIn(context);
3531
- const pulled = await pullAsset(platform, local, args.positionals[0], stringOf(args, "as"), { aside: flagOf(args, "aside") });
3556
+ const pulled = await pullAsset(platform, local, args.positionals[0], stringOf(args, "as"), {
3557
+ aside: flagOf(args, "aside"),
3558
+ hd: flagOf(args, "hd")
3559
+ });
3532
3560
  answer(context, pulled, pulledWords(pulled));
3533
3561
  }
3534
3562
  };
@@ -4508,33 +4536,58 @@ function shapeProblem(shape, index, canvas) {
4508
4536
  return null;
4509
4537
  }
4510
4538
  function sketchProblem(sketch) {
4511
- if (typeof sketch.instruction !== "string" || sketch.instruction.trim() === "") return "the sketch has no description";
4512
- if (sketch.instruction.length > MAX_SKETCH_INSTRUCTION)
4513
- return `the description is ${sketch.instruction.length} characters; it is at most ${MAX_SKETCH_INSTRUCTION}`;
4539
+ const problems = sketchProblems(sketch);
4540
+ return problems.length === 0 ? null : problems.join("; ");
4541
+ }
4542
+ function sketchProblems(sketch) {
4543
+ const problems = [];
4544
+ if (typeof sketch.instruction !== "string" || sketch.instruction.trim() === "") problems.push("the sketch has no description");
4545
+ else if (sketch.instruction.length > MAX_SKETCH_INSTRUCTION)
4546
+ problems.push(`the description is ${sketch.instruction.length} characters; it is at most ${MAX_SKETCH_INSTRUCTION}`);
4514
4547
  const { canvas, shapes } = sketch;
4515
4548
  if (!SKETCH_CANVASES.some((size) => size.width === canvas.width && size.height === canvas.height)) {
4516
- return `the canvas is ${canvas.width} × ${canvas.height}; a sketch is ${SKETCH_CANVASES.map((size) => `${size.width} × ${size.height}`).join(", ")}`;
4549
+ problems.push(
4550
+ `the canvas is ${canvas.width} × ${canvas.height}; a sketch is ${SKETCH_CANVASES.map((size) => `${size.width} × ${size.height}`).join(", ")}`
4551
+ );
4552
+ return problems;
4517
4553
  }
4518
- if (shapes.length === 0) return "the sketch has no shape";
4519
- if (shapes.length > MAX_SKETCH_SHAPES) return `the sketch has ${shapes.length} shapes; it holds at most ${MAX_SKETCH_SHAPES}`;
4520
- const labels = /* @__PURE__ */ new Map();
4554
+ if (shapes.length === 0) problems.push("the sketch has no shape");
4555
+ if (shapes.length > MAX_SKETCH_SHAPES) problems.push(`the sketch has ${shapes.length} shapes; it holds at most ${MAX_SKETCH_SHAPES}`);
4556
+ const labels = /* @__PURE__ */ new Set();
4557
+ const twice = /* @__PURE__ */ new Set();
4558
+ const sound = [];
4521
4559
  for (const [index, shape] of shapes.entries()) {
4522
4560
  const problem = shapeProblem(shape, index, canvas);
4523
- if (problem !== null) return problem;
4561
+ if (problem !== null) {
4562
+ problems.push(problem);
4563
+ continue;
4564
+ }
4565
+ sound.push(index);
4524
4566
  const key = shape.label.trim().toLowerCase();
4525
- if (labels.has(key)) return `"${shape.label.trim()}" is the label of two shapes`;
4526
- labels.set(key, shape.label.trim());
4567
+ if (labels.has(key) && !twice.has(key)) {
4568
+ twice.add(key);
4569
+ problems.push(`"${shape.label.trim()}" is the label of two shapes`);
4570
+ }
4571
+ labels.add(key);
4527
4572
  }
4528
- for (const [index, shape] of shapes.entries()) {
4573
+ const overlapping = /* @__PURE__ */ new Set();
4574
+ for (const index of sound) {
4575
+ const shape = shapes[index];
4529
4576
  for (let y = shape.y; y < shape.y + shape.height; y += 1) {
4530
4577
  for (let x = shape.x; x < shape.x + shape.width; x += 1) {
4531
4578
  if (!inShape(shape, x + 0.5, y + 0.5)) continue;
4532
- const other = shapes.findIndex((them, at) => at < index && inShape(them, x + 0.5, y + 0.5));
4533
- if (other >= 0) return `"${shapes[other]?.label.trim()}" and "${shape.label.trim()}" overlap`;
4579
+ for (const other of sound) {
4580
+ if (other >= index || !inShape(shapes[other], x + 0.5, y + 0.5)) continue;
4581
+ const pair = `"${shapes[other]?.label.trim()}" and "${shape.label.trim()}" overlap`;
4582
+ if (!overlapping.has(pair)) {
4583
+ overlapping.add(pair);
4584
+ problems.push(pair);
4585
+ }
4586
+ }
4534
4587
  }
4535
4588
  }
4536
4589
  }
4537
- return null;
4590
+ return problems;
4538
4591
  }
4539
4592
  function sentShape(shape) {
4540
4593
  const caption = shape.caption?.trim() ?? "";
@@ -6555,7 +6608,7 @@ const move = {
6555
6608
  );
6556
6609
  }
6557
6610
  };
6558
- const version = "0.54.0";
6611
+ const version = "0.56.0";
6559
6612
  const manifest = {
6560
6613
  version
6561
6614
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pixelvibing/cli",
3
- "version": "0.54.0",
3
+ "version": "0.56.0",
4
4
  "description": "PixelVibing: pixel art game assets from the command line, and the Skills that teach coding assistants to make them.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://pixelvibing.com",
@@ -22,7 +22,9 @@ pixel-vibing-cli generate icons --names "<name>,<name>,..." --purpose <skill|ite
22
22
  "icon_size": 32, "instruction": "<theme>", "enrich": false, "max_percent": 40 }
23
23
  ```
24
24
 
25
- `get_job` follows the job; then `pixel-vibing-cli pull <job id>` lands each icon.
25
+ `get_job` follows the job; then `pixel-vibing-cli pull <job id>` lands each icon as pixel art, and
26
+ `pull <job id> --hd` lands them as drawn (HD), beside them; `get_asset` with `"version": "hd"` reads
27
+ them so.
26
28
 
27
29
  ## Writing it
28
30
 
@@ -30,7 +30,10 @@ The layout goes in the call itself, as `canvas` and `shapes`, with the same fiel
30
30
  "reference_images": ["<image id>"], "max_percent": 40 }
31
31
  ```
32
32
 
33
- `get_job` follows the job; then `pixel-vibing-cli pull <job id>` lands each piece.
33
+ `get_job` follows the job; then `pixel-vibing-cli pull <job id>` lands each piece as pixel art, and
34
+ `pull <job id> --hd` lands them as drawn (HD), beside them; `get_asset` with `"version": "hd"` reads
35
+ them so. A UI drawn with `--no-pixel-art` is HD alone. A shape nothing was drawn for is named in the
36
+ result's `missing`: generate the UI again for it rather than retrying the whole set blind.
34
37
 
35
38
  ## Writing it
36
39