pixelkiln 0.68.1 → 0.69.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/cli.js +250 -18
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +31 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +73 -58
- package/dist/index.d.ts +73 -58
- package/dist/index.js +31 -3
- package/dist/index.js.map +1 -1
- package/docs/CLI.md +41 -13
- package/docs/REVISIONS.md +10 -4
- package/package.json +1 -1
- package/skills/pixelkiln/SKILL.md +2 -2
- package/skills/pixelkiln/references/pixellab.md +2 -1
- package/skills/pixelkiln/references/revisions.md +5 -1
package/README.md
CHANGED
|
@@ -55,7 +55,7 @@ provenance and no long-lived npm token.
|
|
|
55
55
|
| Hand edits | Touch-ups in your own editor or a pinned in-browser Pixelorama, kept beside the generated art with the generation still the record; frame and tile sets member by member. |
|
|
56
56
|
| Controlled inputs | Hashed revision lineage (image-to-image and inpaint; on PixelLab also palette reduction, pixel cleanup, animation, skeleton posing, interpolation, and animation edits), fail-closed parent approval, source-versus-candidate review, and content-addressed per-asset ComfyUI bindings. |
|
|
57
57
|
| Characters | A base in 4 or 8 directions from a prompt or your own sprite (four PixelLab engines), poses, loops, portraits, and outfit transfers as dependent assets generated in waves, free mirrored directions (one `directions` list declares a loop and mirrors the rest), account adoption and salvage, and SpriteFrames export. |
|
|
58
|
-
| PixelLab utilities | `unzoom` returns upscaled reference art to its native grid, `font` generates a `.ttf` and glyph atlas,
|
|
58
|
+
| PixelLab utilities | `unzoom` returns upscaled reference art to its native grid, `font` generates a `.ttf` and glyph atlas, `estimate-skeleton` starts a keypoints file for skeleton animation, and `skeleton-preview` draws its poses over the sprite for free. |
|
|
59
59
|
| Existing-art onboarding | Manifest scaffolding, exact-hash account adoption, and prompt recovery. |
|
|
60
60
|
| Recovery | Safe stale-output replacement, validated caches, durable references, resumable paid jobs, and per-asset generation history with free restores. |
|
|
61
61
|
| Shared-account safety | Cross-project claim files or a registered workspace catalog, sibling-style exclusion, reviewed salvage, keep/discard tags, separate confirmed purge. |
|
package/dist/cli.js
CHANGED
|
@@ -67,7 +67,7 @@ function exitCodeFor(err) {
|
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
// src/cli/main.ts
|
|
70
|
-
import { existsSync as
|
|
70
|
+
import { existsSync as existsSync34 } from "fs";
|
|
71
71
|
import { readFile as readFile31 } from "fs/promises";
|
|
72
72
|
|
|
73
73
|
// src/cli/args.ts
|
|
@@ -107,7 +107,8 @@ var VALUE_FLAGS = [
|
|
|
107
107
|
"--quantize",
|
|
108
108
|
"--description",
|
|
109
109
|
"--weight",
|
|
110
|
-
"--glyph-px"
|
|
110
|
+
"--glyph-px",
|
|
111
|
+
"--frames"
|
|
111
112
|
];
|
|
112
113
|
var BOOL_FLAGS = [
|
|
113
114
|
"--force",
|
|
@@ -158,6 +159,7 @@ var COMMANDS = [
|
|
|
158
159
|
"recipe",
|
|
159
160
|
"workspace",
|
|
160
161
|
"estimate-skeleton",
|
|
162
|
+
"skeleton-preview",
|
|
161
163
|
"unzoom",
|
|
162
164
|
"font",
|
|
163
165
|
"help",
|
|
@@ -256,6 +258,12 @@ function parseArgs(argv) {
|
|
|
256
258
|
target = rest[0]?.startsWith("-") ? void 0 : rest[0];
|
|
257
259
|
if (target === void 0) throw new UsageError("estimate-skeleton needs an image path");
|
|
258
260
|
rest = rest.slice(1);
|
|
261
|
+
} else if (command === "skeleton-preview") {
|
|
262
|
+
target = rest[0]?.startsWith("-") ? void 0 : rest[0];
|
|
263
|
+
if (target === void 0) {
|
|
264
|
+
throw new UsageError("skeleton-preview needs a keypoints file or an animate-skeleton asset id");
|
|
265
|
+
}
|
|
266
|
+
rest = rest.slice(1);
|
|
259
267
|
} else if (command === "refine") {
|
|
260
268
|
subcommand = rest[0]?.startsWith("-") || rest[0] === void 0 ? "run" : rest[0];
|
|
261
269
|
if (!REFINE_SUBCOMMANDS.includes(subcommand)) {
|
|
@@ -421,7 +429,8 @@ function parseArgs(argv) {
|
|
|
421
429
|
quantize: numberOption("--quantize", { min: -1, max: 256, integer: true }),
|
|
422
430
|
description: get("--description"),
|
|
423
431
|
weight: get("--weight"),
|
|
424
|
-
glyphPx: numberOption("--glyph-px", { min: 8, max: 64, integer: true })
|
|
432
|
+
glyphPx: numberOption("--glyph-px", { min: 8, max: 64, integer: true }),
|
|
433
|
+
frames: numberOption("--frames", { min: 3, max: 15, integer: true })
|
|
425
434
|
};
|
|
426
435
|
}
|
|
427
436
|
|
|
@@ -461,11 +470,16 @@ Commands
|
|
|
461
470
|
first. Local art is untouched.
|
|
462
471
|
tag Push manifest tags to the objects upstream (free).
|
|
463
472
|
balance Show the provider's remaining balance.
|
|
464
|
-
estimate-skeleton <image> [--out <file>]
|
|
465
|
-
PixelLab only: derive an 18-joint skeleton from
|
|
466
|
-
and un-budgeted (like balance, not a generation).
|
|
467
|
-
|
|
468
|
-
|
|
473
|
+
estimate-skeleton <image> [--out <file>] [--frames <n>] [--force]
|
|
474
|
+
PixelLab only: derive an 18-joint skeleton from a square 16-256px
|
|
475
|
+
image, direct and un-budgeted (like balance, not a generation).
|
|
476
|
+
Prints, or writes with --out, a ready keypoints file for an
|
|
477
|
+
animate-skeleton revision: the estimated pose plus --frames copies
|
|
478
|
+
(default 4) to edit. --out refuses to replace a file without --force.
|
|
479
|
+
skeleton-preview <keypoints.json | asset> [--from <image>] [--out <file>]
|
|
480
|
+
Draw every pose of a keypoints file over its source image as one
|
|
481
|
+
PNG, locally and free. Given an animate-skeleton asset id, reads
|
|
482
|
+
its keypointsFile and source from the manifest.
|
|
469
483
|
status Summarise the lockfile.
|
|
470
484
|
edit Hand-edit an asset in your own editor: copies the generated PNG to
|
|
471
485
|
<outDir>/edits/, declares it as the asset's source, and opens it.
|
|
@@ -5756,14 +5770,61 @@ import { z as z3 } from "zod";
|
|
|
5756
5770
|
|
|
5757
5771
|
// src/skeleton.ts
|
|
5758
5772
|
import { z as z2 } from "zod";
|
|
5773
|
+
var SKELETON_LABELS = [
|
|
5774
|
+
"NOSE",
|
|
5775
|
+
"NECK",
|
|
5776
|
+
"RIGHT SHOULDER",
|
|
5777
|
+
"RIGHT ELBOW",
|
|
5778
|
+
"RIGHT ARM",
|
|
5779
|
+
"LEFT SHOULDER",
|
|
5780
|
+
"LEFT ELBOW",
|
|
5781
|
+
"LEFT ARM",
|
|
5782
|
+
"RIGHT HIP",
|
|
5783
|
+
"RIGHT KNEE",
|
|
5784
|
+
"RIGHT LEG",
|
|
5785
|
+
"LEFT HIP",
|
|
5786
|
+
"LEFT KNEE",
|
|
5787
|
+
"LEFT LEG",
|
|
5788
|
+
"RIGHT EYE",
|
|
5789
|
+
"LEFT EYE",
|
|
5790
|
+
"RIGHT EAR",
|
|
5791
|
+
"LEFT EAR"
|
|
5792
|
+
];
|
|
5793
|
+
var SKELETON_BONES = [
|
|
5794
|
+
["NOSE", "NECK"],
|
|
5795
|
+
["NECK", "RIGHT SHOULDER"],
|
|
5796
|
+
["RIGHT SHOULDER", "RIGHT ELBOW"],
|
|
5797
|
+
["RIGHT ELBOW", "RIGHT ARM"],
|
|
5798
|
+
["NECK", "LEFT SHOULDER"],
|
|
5799
|
+
["LEFT SHOULDER", "LEFT ELBOW"],
|
|
5800
|
+
["LEFT ELBOW", "LEFT ARM"],
|
|
5801
|
+
["NECK", "RIGHT HIP"],
|
|
5802
|
+
["RIGHT HIP", "RIGHT KNEE"],
|
|
5803
|
+
["RIGHT KNEE", "RIGHT LEG"],
|
|
5804
|
+
["NECK", "LEFT HIP"],
|
|
5805
|
+
["LEFT HIP", "LEFT KNEE"],
|
|
5806
|
+
["LEFT KNEE", "LEFT LEG"],
|
|
5807
|
+
["NOSE", "RIGHT EYE"],
|
|
5808
|
+
["RIGHT EYE", "RIGHT EAR"],
|
|
5809
|
+
["NOSE", "LEFT EYE"],
|
|
5810
|
+
["LEFT EYE", "LEFT EAR"]
|
|
5811
|
+
];
|
|
5759
5812
|
var SkeletonKeypointSchema = z2.object({
|
|
5760
|
-
label: z2.
|
|
5813
|
+
label: z2.enum(SKELETON_LABELS),
|
|
5761
5814
|
x: z2.number().min(0).max(1),
|
|
5762
5815
|
y: z2.number().min(0).max(1),
|
|
5763
5816
|
z_index: z2.number(),
|
|
5764
5817
|
depth: z2.number().optional()
|
|
5765
5818
|
}).strict();
|
|
5766
|
-
var SkeletonFrameSchema = z2.array(SkeletonKeypointSchema).length(18)
|
|
5819
|
+
var SkeletonFrameSchema = z2.array(SkeletonKeypointSchema).length(18).superRefine((frame, context) => {
|
|
5820
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5821
|
+
for (const [index, joint] of frame.entries()) {
|
|
5822
|
+
if (seen.has(joint.label)) {
|
|
5823
|
+
context.addIssue({ code: z2.ZodIssueCode.custom, message: `"${joint.label}" appears twice`, path: [index, "label"] });
|
|
5824
|
+
}
|
|
5825
|
+
seen.add(joint.label);
|
|
5826
|
+
}
|
|
5827
|
+
});
|
|
5767
5828
|
var SkeletonSetSchema = z2.object({
|
|
5768
5829
|
firstFrameKeypoints: SkeletonFrameSchema,
|
|
5769
5830
|
frames: z2.array(SkeletonFrameSchema).min(3).max(15)
|
|
@@ -5776,6 +5837,11 @@ function parseSkeletonSet(json, file) {
|
|
|
5776
5837
|
}
|
|
5777
5838
|
return result.data;
|
|
5778
5839
|
}
|
|
5840
|
+
var DEFAULT_SCAFFOLD_FRAMES = 4;
|
|
5841
|
+
function scaffoldSkeletonSet(keypoints, frames = DEFAULT_SCAFFOLD_FRAMES) {
|
|
5842
|
+
const copy = () => keypoints.map((joint) => ({ ...joint }));
|
|
5843
|
+
return { firstFrameKeypoints: copy(), frames: Array.from({ length: frames }, copy) };
|
|
5844
|
+
}
|
|
5779
5845
|
|
|
5780
5846
|
// src/client.ts
|
|
5781
5847
|
var BASE = process.env.PIXELLAB_API_BASE ?? "https://api.pixellab.ai/v2";
|
|
@@ -7128,7 +7194,7 @@ var PixelLabClient = class {
|
|
|
7128
7194
|
* account (see `EstimateSkeletonResponseSchema`'s own caveat).
|
|
7129
7195
|
*/
|
|
7130
7196
|
async estimateSkeleton(args) {
|
|
7131
|
-
const body = { image: args.image };
|
|
7197
|
+
const body = { image: { type: "base64", base64: args.image.base64, format: args.image.format } };
|
|
7132
7198
|
const res = validateResponse(
|
|
7133
7199
|
EstimateSkeletonResponseSchema,
|
|
7134
7200
|
await this.request("/estimate-skeleton", { method: "POST", body: JSON.stringify(body) }),
|
|
@@ -20537,27 +20603,192 @@ async function runRefine(args) {
|
|
|
20537
20603
|
|
|
20538
20604
|
// src/cli/commands/skeleton.ts
|
|
20539
20605
|
import path46 from "path";
|
|
20606
|
+
import { existsSync as existsSync32 } from "fs";
|
|
20540
20607
|
import { readFile as readFile30, writeFile as writeFile15 } from "fs/promises";
|
|
20608
|
+
|
|
20609
|
+
// src/skeleton-preview.ts
|
|
20610
|
+
var RIGHT = [255, 140, 40, 255];
|
|
20611
|
+
var LEFT = [60, 150, 255, 255];
|
|
20612
|
+
var CENTER = [240, 240, 240, 255];
|
|
20613
|
+
var GROUND = [28, 28, 34, 255];
|
|
20614
|
+
var OUTLINE = [0, 0, 0, 255];
|
|
20615
|
+
var GUTTER = 4;
|
|
20616
|
+
var MIN_CELL = 192;
|
|
20617
|
+
function renderSkeletonSheet(set, options = {}) {
|
|
20618
|
+
const poses = [set.firstFrameKeypoints, ...set.frames];
|
|
20619
|
+
const source = options.source;
|
|
20620
|
+
const scale = source ? Math.max(1, Math.ceil(MIN_CELL / Math.max(source.width, source.height))) : 1;
|
|
20621
|
+
const cellW = source ? source.width * scale : options.cellSize ?? MIN_CELL;
|
|
20622
|
+
const cellH = source ? source.height * scale : options.cellSize ?? MIN_CELL;
|
|
20623
|
+
const columns = Math.min(options.columns ?? 8, poses.length);
|
|
20624
|
+
const rows = Math.ceil(poses.length / columns);
|
|
20625
|
+
const width = columns * cellW + (columns + 1) * GUTTER;
|
|
20626
|
+
const height = rows * cellH + (rows + 1) * GUTTER;
|
|
20627
|
+
const canvas = new Canvas(width, height);
|
|
20628
|
+
canvas.fill(0, 0, width, height, [12, 12, 16, 255]);
|
|
20629
|
+
poses.forEach((pose, index) => {
|
|
20630
|
+
const left = GUTTER + index % columns * (cellW + GUTTER);
|
|
20631
|
+
const top = GUTTER + Math.floor(index / columns) * (cellH + GUTTER);
|
|
20632
|
+
canvas.fill(left, top, cellW, cellH, GROUND);
|
|
20633
|
+
if (source) canvas.drawImage(source, left, top, scale, index === 0 ? 1 : 0.45);
|
|
20634
|
+
drawPose(canvas, pose, left, top, cellW, cellH, Math.max(1, Math.round(Math.min(cellW, cellH) / 128)));
|
|
20635
|
+
});
|
|
20636
|
+
return encodeRgbaPng(width, height, canvas.data);
|
|
20637
|
+
}
|
|
20638
|
+
function drawPose(canvas, pose, left, top, w, h, stroke) {
|
|
20639
|
+
const at = new Map(pose.map((joint) => [joint.label, joint]));
|
|
20640
|
+
const point = (joint) => [left + joint.x * (w - 1), top + joint.y * (h - 1)];
|
|
20641
|
+
for (const outline of [true, false]) {
|
|
20642
|
+
for (const [from, to] of SKELETON_BONES) {
|
|
20643
|
+
const a = at.get(from);
|
|
20644
|
+
const b = at.get(to);
|
|
20645
|
+
if (!a || !b) continue;
|
|
20646
|
+
const [x0, y0] = point(a);
|
|
20647
|
+
const [x1, y1] = point(b);
|
|
20648
|
+
canvas.line(x0, y0, x1, y1, outline ? OUTLINE : colorOf(to), outline ? stroke + 2 : stroke);
|
|
20649
|
+
}
|
|
20650
|
+
for (const joint of pose) {
|
|
20651
|
+
const [x, y] = point(joint);
|
|
20652
|
+
canvas.dot(x, y, outline ? stroke + 3 : stroke + 1, outline ? OUTLINE : colorOf(joint.label));
|
|
20653
|
+
}
|
|
20654
|
+
}
|
|
20655
|
+
}
|
|
20656
|
+
function colorOf(label) {
|
|
20657
|
+
if (label.startsWith("RIGHT")) return RIGHT;
|
|
20658
|
+
if (label.startsWith("LEFT")) return LEFT;
|
|
20659
|
+
return CENTER;
|
|
20660
|
+
}
|
|
20661
|
+
var Canvas = class {
|
|
20662
|
+
constructor(width, height) {
|
|
20663
|
+
this.width = width;
|
|
20664
|
+
this.height = height;
|
|
20665
|
+
this.data = Buffer.alloc(width * height * 4);
|
|
20666
|
+
}
|
|
20667
|
+
width;
|
|
20668
|
+
height;
|
|
20669
|
+
data;
|
|
20670
|
+
set(x, y, [r, g, b, a]) {
|
|
20671
|
+
const px = Math.round(x);
|
|
20672
|
+
const py = Math.round(y);
|
|
20673
|
+
if (px < 0 || py < 0 || px >= this.width || py >= this.height) return;
|
|
20674
|
+
const i = (py * this.width + px) * 4;
|
|
20675
|
+
this.data[i] = r;
|
|
20676
|
+
this.data[i + 1] = g;
|
|
20677
|
+
this.data[i + 2] = b;
|
|
20678
|
+
this.data[i + 3] = a;
|
|
20679
|
+
}
|
|
20680
|
+
fill(x, y, w, h, color) {
|
|
20681
|
+
for (let dy = 0; dy < h; dy++) for (let dx = 0; dx < w; dx++) this.set(x + dx, y + dy, color);
|
|
20682
|
+
}
|
|
20683
|
+
/** Nearest-neighbour upscale, composited over what is there, at `strength`. */
|
|
20684
|
+
drawImage(image, left, top, scale, strength) {
|
|
20685
|
+
for (let y = 0; y < image.height; y++) {
|
|
20686
|
+
for (let x = 0; x < image.width; x++) {
|
|
20687
|
+
const i = (y * image.width + x) * 4;
|
|
20688
|
+
const alpha = image.pixels[i + 3] / 255 * strength;
|
|
20689
|
+
if (alpha === 0) continue;
|
|
20690
|
+
const src = [image.pixels[i], image.pixels[i + 1], image.pixels[i + 2]];
|
|
20691
|
+
for (let dy = 0; dy < scale; dy++) {
|
|
20692
|
+
for (let dx = 0; dx < scale; dx++) {
|
|
20693
|
+
const o = ((top + y * scale + dy) * this.width + left + x * scale + dx) * 4;
|
|
20694
|
+
for (let c = 0; c < 3; c++) this.data[o + c] = Math.round(src[c] * alpha + this.data[o + c] * (1 - alpha));
|
|
20695
|
+
}
|
|
20696
|
+
}
|
|
20697
|
+
}
|
|
20698
|
+
}
|
|
20699
|
+
}
|
|
20700
|
+
line(x0, y0, x1, y1, color, stroke) {
|
|
20701
|
+
const steps = Math.max(1, Math.ceil(Math.max(Math.abs(x1 - x0), Math.abs(y1 - y0))));
|
|
20702
|
+
for (let step = 0; step <= steps; step++) {
|
|
20703
|
+
const t = step / steps;
|
|
20704
|
+
this.dot(x0 + (x1 - x0) * t, y0 + (y1 - y0) * t, stroke, color);
|
|
20705
|
+
}
|
|
20706
|
+
}
|
|
20707
|
+
/** A filled square `size` wide centred on (x, y): crisp at pixel-art scale. */
|
|
20708
|
+
dot(x, y, size, color) {
|
|
20709
|
+
const half = Math.floor(size / 2);
|
|
20710
|
+
for (let dy = -half; dy < size - half; dy++) for (let dx = -half; dx < size - half; dx++) this.set(x + dx, y + dy, color);
|
|
20711
|
+
}
|
|
20712
|
+
};
|
|
20713
|
+
|
|
20714
|
+
// src/cli/commands/skeleton.ts
|
|
20715
|
+
var ESTIMATE_SKELETON_SIZES = [16, 32, 64, 128, 256];
|
|
20541
20716
|
async function runEstimateSkeleton(args) {
|
|
20542
20717
|
const image = args.target;
|
|
20543
20718
|
if (!image) throw new UsageError("estimate-skeleton needs an image path: pixelkiln estimate-skeleton <image> [--out keypoints.json]");
|
|
20719
|
+
if (args.out && existsSync32(path46.resolve(args.out)) && !args.force) {
|
|
20720
|
+
throw new UsageError(`${args.out} already exists; pass --force to replace it`);
|
|
20721
|
+
}
|
|
20544
20722
|
loadEnvFiles(path46.dirname(path46.resolve(args.manifest)));
|
|
20545
20723
|
loadEnvFiles(process.cwd());
|
|
20546
20724
|
const bytes = await readFile30(path46.resolve(image));
|
|
20547
20725
|
const metadata = imageMetadata(bytes);
|
|
20548
20726
|
if (!metadata) throw new UsageError(`${image} is not a readable PNG or JPEG`);
|
|
20727
|
+
if (metadata.width !== metadata.height || !ESTIMATE_SKELETON_SIZES.includes(metadata.width)) {
|
|
20728
|
+
throw new UsageError(
|
|
20729
|
+
`${image} is ${metadata.width}x${metadata.height}; estimate-skeleton takes a square image of ${ESTIMATE_SKELETON_SIZES.join(", ")} pixels`
|
|
20730
|
+
);
|
|
20731
|
+
}
|
|
20549
20732
|
const client2 = clientFromEnv();
|
|
20550
20733
|
const res = await client2.estimateSkeleton({
|
|
20551
20734
|
image: { base64: bytes.toString("base64"), format: metadata.format }
|
|
20552
20735
|
});
|
|
20553
|
-
const
|
|
20736
|
+
const frames = args.frames ?? DEFAULT_SCAFFOLD_FRAMES;
|
|
20737
|
+
const json = JSON.stringify(scaffoldSkeletonSet(res.keypoints, frames), null, 2);
|
|
20554
20738
|
if (args.out) {
|
|
20555
20739
|
await writeFile15(path46.resolve(args.out), json + "\n");
|
|
20556
|
-
log(` wrote ${
|
|
20740
|
+
log(` wrote ${args.out}: the estimated pose and ${frames} frames to edit`);
|
|
20741
|
+
log(` preview it with: pixelkiln skeleton-preview ${args.out} --from ${image}`);
|
|
20557
20742
|
} else {
|
|
20558
20743
|
log(json);
|
|
20559
20744
|
}
|
|
20560
20745
|
}
|
|
20746
|
+
async function runSkeletonPreview(args) {
|
|
20747
|
+
const target = args.target;
|
|
20748
|
+
let keypointsFile;
|
|
20749
|
+
let sourceFile;
|
|
20750
|
+
let label;
|
|
20751
|
+
if (target.endsWith(".json")) {
|
|
20752
|
+
keypointsFile = path46.resolve(target);
|
|
20753
|
+
sourceFile = args.from ? path46.resolve(args.from) : void 0;
|
|
20754
|
+
label = target;
|
|
20755
|
+
} else {
|
|
20756
|
+
const loaded = await loadManifest(args.manifest);
|
|
20757
|
+
const specs = (await resolveSpecs(loaded, { styles: args.styles, assets: [target] })).filter((spec2) => spec2.assetId === target);
|
|
20758
|
+
const spec = specs.find((candidate) => candidate.revision?.mode === "animate-skeleton");
|
|
20759
|
+
if (!spec) {
|
|
20760
|
+
throw new UsageError(
|
|
20761
|
+
specs.length ? `${target} is not an animate-skeleton revision; pass a keypoints file instead` : `No asset "${target}" in ${args.manifest}`
|
|
20762
|
+
);
|
|
20763
|
+
}
|
|
20764
|
+
keypointsFile = spec.revision.keypointsFile;
|
|
20765
|
+
sourceFile = args.from ? path46.resolve(args.from) : spec.revision.sourceFile;
|
|
20766
|
+
label = `${spec.styleId}/${spec.assetId}`;
|
|
20767
|
+
}
|
|
20768
|
+
if (!existsSync32(keypointsFile)) throw new UsageError(`Keypoints file does not exist yet: ${keypointsFile}`);
|
|
20769
|
+
let json;
|
|
20770
|
+
try {
|
|
20771
|
+
json = JSON.parse(await readFile30(keypointsFile, "utf8"));
|
|
20772
|
+
} catch (error) {
|
|
20773
|
+
throw new UsageError(`${keypointsFile} is not valid JSON: ${error instanceof Error ? error.message : String(error)}`);
|
|
20774
|
+
}
|
|
20775
|
+
const set = parseSkeletonSet(json, keypointsFile);
|
|
20776
|
+
let source;
|
|
20777
|
+
if (sourceFile) {
|
|
20778
|
+
if (!existsSync32(sourceFile)) {
|
|
20779
|
+
log(` source image does not exist yet, drawing on a plain ground: ${sourceFile}`);
|
|
20780
|
+
} else {
|
|
20781
|
+
const bytes = await readFile30(sourceFile);
|
|
20782
|
+
if (imageMetadata(bytes)?.format !== "png") throw new UsageError(`${sourceFile} must be a PNG to preview over`);
|
|
20783
|
+
source = decodePng(bytes);
|
|
20784
|
+
}
|
|
20785
|
+
}
|
|
20786
|
+
const out = path46.resolve(args.out ?? keypointsFile.replace(/\.json$/, "") + ".preview.png");
|
|
20787
|
+
await writeFile15(out, renderSkeletonSheet(set, { source }));
|
|
20788
|
+
log(` ${label}: ${set.frames.length} frames after the starting pose`);
|
|
20789
|
+
const shown = path46.relative(process.cwd(), out);
|
|
20790
|
+
log(` wrote ${shown.startsWith("..") ? out : shown}`);
|
|
20791
|
+
}
|
|
20561
20792
|
|
|
20562
20793
|
// src/cli/commands/tools.ts
|
|
20563
20794
|
async function runTools(args) {
|
|
@@ -20602,7 +20833,7 @@ async function runTools(args) {
|
|
|
20602
20833
|
|
|
20603
20834
|
// src/cli/commands/workspace.ts
|
|
20604
20835
|
import path47 from "path";
|
|
20605
|
-
import { existsSync as
|
|
20836
|
+
import { existsSync as existsSync33 } from "fs";
|
|
20606
20837
|
async function runWorkspace(args) {
|
|
20607
20838
|
const workspacePath = path47.resolve(args.workspace ?? "pixelkiln.workspace.json");
|
|
20608
20839
|
const dir = path47.dirname(workspacePath);
|
|
@@ -20632,7 +20863,7 @@ async function runWorkspace(args) {
|
|
|
20632
20863
|
log(` registered "${id}" in ${path47.relative(process.cwd(), workspacePath)}`);
|
|
20633
20864
|
log(` manifest: ${project.manifest}`);
|
|
20634
20865
|
log(` lock: ${project.lock}`);
|
|
20635
|
-
if (!
|
|
20866
|
+
if (!existsSync33(lockPath)) {
|
|
20636
20867
|
log(
|
|
20637
20868
|
` warning: no lockfile there yet; this project contributes no claims until one is generated`
|
|
20638
20869
|
);
|
|
@@ -20656,7 +20887,7 @@ async function runWorkspace(args) {
|
|
|
20656
20887
|
return;
|
|
20657
20888
|
}
|
|
20658
20889
|
if (args.subcommand === "list") {
|
|
20659
|
-
if (!
|
|
20890
|
+
if (!existsSync33(workspacePath)) {
|
|
20660
20891
|
throw new Error(`Workspace catalog not found: ${workspacePath}`);
|
|
20661
20892
|
}
|
|
20662
20893
|
const ws = await loadWorkspace(workspacePath);
|
|
@@ -20676,7 +20907,7 @@ async function runWorkspace(args) {
|
|
|
20676
20907
|
return;
|
|
20677
20908
|
}
|
|
20678
20909
|
if (args.subcommand === "status") {
|
|
20679
|
-
if (!
|
|
20910
|
+
if (!existsSync33(workspacePath)) {
|
|
20680
20911
|
throw new Error(`Workspace catalog not found: ${workspacePath}`);
|
|
20681
20912
|
}
|
|
20682
20913
|
const ws = await loadWorkspace(workspacePath);
|
|
@@ -20738,7 +20969,7 @@ async function runHelp() {
|
|
|
20738
20969
|
async function runVersion() {
|
|
20739
20970
|
for (const up of ["../package.json", "../../package.json"]) {
|
|
20740
20971
|
const url = new URL(up, import.meta.url);
|
|
20741
|
-
if (!
|
|
20972
|
+
if (!existsSync34(url)) continue;
|
|
20742
20973
|
const pkg = JSON.parse(await readFile31(url, "utf8"));
|
|
20743
20974
|
if (pkg.name === "pixelkiln") {
|
|
20744
20975
|
log(`${pkg.name} ${pkg.version}`);
|
|
@@ -20784,6 +21015,7 @@ var RUNNERS = {
|
|
|
20784
21015
|
recipe: runRecipe,
|
|
20785
21016
|
workspace: runWorkspace,
|
|
20786
21017
|
"estimate-skeleton": runEstimateSkeleton,
|
|
21018
|
+
"skeleton-preview": runSkeletonPreview,
|
|
20787
21019
|
unzoom: runUnzoom,
|
|
20788
21020
|
font: runFont
|
|
20789
21021
|
};
|