pixelkiln 0.66.0 → 0.67.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -68,7 +68,7 @@ function exitCodeFor(err) {
68
68
 
69
69
  // src/cli/main.ts
70
70
  import { existsSync as existsSync33 } from "fs";
71
- import { readFile as readFile30 } from "fs/promises";
71
+ import { readFile as readFile31 } from "fs/promises";
72
72
 
73
73
  // src/cli/args.ts
74
74
  import path from "path";
@@ -157,6 +157,7 @@ var COMMANDS = [
157
157
  "refine",
158
158
  "recipe",
159
159
  "workspace",
160
+ "estimate-skeleton",
160
161
  "unzoom",
161
162
  "font",
162
163
  "help",
@@ -251,6 +252,10 @@ function parseArgs(argv) {
251
252
  }
252
253
  rest = rest.slice(1);
253
254
  }
255
+ } else if (command === "estimate-skeleton") {
256
+ target = rest[0]?.startsWith("-") ? void 0 : rest[0];
257
+ if (target === void 0) throw new UsageError("estimate-skeleton needs an image path");
258
+ rest = rest.slice(1);
254
259
  } else if (command === "refine") {
255
260
  subcommand = rest[0]?.startsWith("-") || rest[0] === void 0 ? "run" : rest[0];
256
261
  if (!REFINE_SUBCOMMANDS.includes(subcommand)) {
@@ -456,6 +461,11 @@ Commands
456
461
  first. Local art is untouched.
457
462
  tag Push manifest tags to the objects upstream (free).
458
463
  balance Show the provider's remaining balance.
464
+ estimate-skeleton <image> [--out <file>]
465
+ PixelLab only: derive an 18-joint skeleton from an image, direct
466
+ and un-budgeted (like balance, not a generation). Prints the
467
+ keypoints JSON, or writes it with --out \u2014 the way to bootstrap
468
+ an animate-skeleton revision's keypointsFile.
459
469
  status Summarise the lockfile.
460
470
  edit Hand-edit an asset in your own editor: copies the generated PNG to
461
471
  <outDir>/edits/, declares it as the asset's source, and opens it.
@@ -638,10 +648,10 @@ import { createReadStream } from "fs";
638
648
  function sha256(data) {
639
649
  return createHash("sha256").update(data).digest("hex");
640
650
  }
641
- async function sha256File(path47) {
651
+ async function sha256File(path48) {
642
652
  return new Promise((resolve, reject) => {
643
653
  const hash = createHash("sha256");
644
- const stream = createReadStream(path47);
654
+ const stream = createReadStream(path48);
645
655
  stream.on("data", (chunk2) => hash.update(chunk2));
646
656
  stream.on("error", reject);
647
657
  stream.on("end", () => resolve(hash.digest("hex")));
@@ -757,6 +767,9 @@ function specHash(spec, styleImageHashes, providerOptionIdentity = spec.provider
757
767
  lastFrameSha256: spec.revision.lastFrameSha256,
758
768
  direction: spec.revision.direction,
759
769
  enhancePrompt: spec.revision.enhancePrompt,
770
+ keypointsSha256: spec.revision.keypointsSha256,
771
+ skeletonTemplate: spec.revision.skeletonTemplate,
772
+ description: spec.revision.description,
760
773
  engine: spec.revision.engine
761
774
  } : void 0
762
775
  })
@@ -785,6 +798,7 @@ var RevisionModeSchema = z.enum([
785
798
  "correct-pixelart",
786
799
  "animate",
787
800
  "animate-pixminimax",
801
+ "animate-skeleton",
788
802
  "interpolate",
789
803
  "edit-animation"
790
804
  ]);
@@ -1103,6 +1117,30 @@ var RevisionSchema = z.object({
1103
1117
  direction: CharacterDirectionSchema.optional(),
1104
1118
  /** `animate`/`animate-pixminimax` only: let PixelLab expand the action into a fuller motion description first. */
1105
1119
  enhancePrompt: z.boolean().optional(),
1120
+ /**
1121
+ * `animate-skeleton` only: manifest-relative path to a committed
1122
+ * `SkeletonSet` JSON file (see `src/skeleton.ts`) — the pose the parent
1123
+ * image is already in, plus 3-15 per-frame poses for the motion.
1124
+ * Authored via `pixelkiln estimate-skeleton`, then hand-tweaked; never
1125
+ * inline in the manifest, the same reasoning `mask`/`lastFrame` are
1126
+ * committed files rather than inline pixel data.
1127
+ */
1128
+ keypointsFile: z.string().min(1).optional(),
1129
+ /**
1130
+ * `animate-skeleton` only: the body a joint's missing `depth` is taken
1131
+ * from. PixelLab's default is `mannequin`; free string like `character`'s
1132
+ * own `template` field, validated at the provider layer against the
1133
+ * same `mannequin`/`bear`/`cat`/`dog`/`horse`/`lion` vocabulary.
1134
+ */
1135
+ skeletonTemplate: z.string().min(1).optional(),
1136
+ /**
1137
+ * `animate-skeleton` only: what the character *looks like*, a noun
1138
+ * phrase (colours, clothing, held items) — distinct from the asset's
1139
+ * own `prompt`, which this mode sends as the motion's short label
1140
+ * (PixelLab's `action`), the same `prompt`-as-motion convention
1141
+ * `animate`/`animate-pixminimax` already use.
1142
+ */
1143
+ description: z.string().min(1).optional(),
1106
1144
  /**
1107
1145
  * `image-to-image`/`inpaint` only: which provider engine draws the
1108
1146
  * edit. `pro-flash` selects PixelLab's Pro Flash edit and inpaint tier
@@ -1193,18 +1231,41 @@ var RevisionSchema = z.object({
1193
1231
  path: ["fps"]
1194
1232
  });
1195
1233
  }
1196
- if (revision.direction !== void 0 && revision.mode !== "animate-pixminimax") {
1234
+ if (revision.direction !== void 0 && revision.mode !== "animate-pixminimax" && revision.mode !== "animate-skeleton") {
1235
+ context.addIssue({
1236
+ code: z.ZodIssueCode.custom,
1237
+ message: "direction applies to animate-pixminimax/animate-skeleton revisions only",
1238
+ path: ["direction"]
1239
+ });
1240
+ }
1241
+ if (revision.mode === "animate-skeleton" && revision.direction === void 0) {
1197
1242
  context.addIssue({
1198
1243
  code: z.ZodIssueCode.custom,
1199
- message: "direction applies to animate-pixminimax revisions only",
1244
+ message: "animate-skeleton revisions require a direction",
1200
1245
  path: ["direction"]
1201
1246
  });
1202
1247
  }
1203
1248
  if (revision.frames !== void 0 && revision.frames % 2 !== 0) {
1204
1249
  context.addIssue({ code: z.ZodIssueCode.custom, message: "frames must be even", path: ["frames"] });
1205
1250
  }
1251
+ if (revision.mode === "animate-skeleton" && !revision.keypointsFile) {
1252
+ context.addIssue({
1253
+ code: z.ZodIssueCode.custom,
1254
+ message: "animate-skeleton revisions require keypointsFile",
1255
+ path: ["keypointsFile"]
1256
+ });
1257
+ }
1258
+ for (const field of ["keypointsFile", "skeletonTemplate", "description"]) {
1259
+ if (revision[field] !== void 0 && revision.mode !== "animate-skeleton") {
1260
+ context.addIssue({
1261
+ code: z.ZodIssueCode.custom,
1262
+ message: `${field} applies to animate-skeleton revisions only`,
1263
+ path: [field]
1264
+ });
1265
+ }
1266
+ }
1206
1267
  });
1207
- var FRAME_SET_REVISION_MODES = ["animate", "animate-pixminimax", "interpolate", "edit-animation"];
1268
+ var FRAME_SET_REVISION_MODES = ["animate", "animate-pixminimax", "animate-skeleton", "interpolate", "edit-animation"];
1208
1269
  var MEMBER_SET_REVISION_MODES = ["reduce-colors", "correct-pixelart", "edit-animation"];
1209
1270
  var StyleObjectSchema = z.object({
1210
1271
  /** Generation backend for this style. Omit to inherit the manifest default. */
@@ -1836,11 +1897,16 @@ var LockEntrySchema = z.object({
1836
1897
  paletteImageSha256: z.string().regex(/^[0-9a-f]{64}$/).optional(),
1837
1898
  dithering: RevisionDitheringSchema.optional(),
1838
1899
  ditheringStrength: z.number().min(0).max(10).optional(),
1839
- frames: z.number().int().min(4).max(40).optional(),
1900
+ // 4-40 for animate/animate-pixminimax; animate-skeleton's own 3-15
1901
+ // range extends the floor down to 3, hence min(3) here rather than 4.
1902
+ frames: z.number().int().min(3).max(40).optional(),
1840
1903
  fps: z.number().int().min(1).max(60).optional(),
1841
1904
  lastFrameSha256: z.string().regex(/^[0-9a-f]{64}$/).optional(),
1842
1905
  direction: CharacterDirectionSchema.optional(),
1843
1906
  enhancePrompt: z.boolean().optional(),
1907
+ keypointsSha256: z.string().regex(/^[0-9a-f]{64}$/).optional(),
1908
+ skeletonTemplate: z.string().min(1).optional(),
1909
+ description: z.string().min(1).optional(),
1844
1910
  engine: z.enum(["pro-flash"]).optional()
1845
1911
  }).strict().nullable().default(null),
1846
1912
  /** For a mirror: the asset it flips and a hash over that asset's output hashes when this was made. */
@@ -4993,11 +5059,12 @@ var ComfyUIProvider = class _ComfyUIProvider {
4993
5059
  /**
4994
5060
  * Everything else resolves generically to whatever the user's own
4995
5061
  * workflow does with `bindings.sourceImage`. The frame-set modes
4996
- * (`animate`, `animate-pixminimax`, `interpolate`, `edit-animation`) are
4997
- * the exception: they produce an ordered frame set, and this adapter's
4998
- * revision path always writes a single output image (see `submit`/`fetch`
4999
- * below) — a real structural gap, not a missing binding, so they are
5000
- * rejected here rather than only failing once `validate` runs.
5062
+ * (`animate`, `animate-pixminimax`, `animate-skeleton`, `interpolate`,
5063
+ * `edit-animation`) are the exception: they produce an ordered frame set,
5064
+ * and this adapter's revision path always writes a single output image
5065
+ * (see `submit`/`fetch` below) — a real structural gap, not a missing
5066
+ * binding, so they are rejected here rather than only failing once
5067
+ * `validate` runs.
5001
5068
  */
5002
5069
  supportsRevision(mode2) {
5003
5070
  return !FRAME_SET_REVISION_MODES.includes(mode2);
@@ -5657,262 +5724,291 @@ import os from "os";
5657
5724
  import path12 from "path";
5658
5725
 
5659
5726
  // src/client.ts
5727
+ import { z as z3 } from "zod";
5728
+
5729
+ // src/skeleton.ts
5660
5730
  import { z as z2 } from "zod";
5731
+ var SkeletonKeypointSchema = z2.object({
5732
+ label: z2.string().min(1),
5733
+ x: z2.number().min(0).max(1),
5734
+ y: z2.number().min(0).max(1),
5735
+ z_index: z2.number(),
5736
+ depth: z2.number().optional()
5737
+ }).strict();
5738
+ var SkeletonFrameSchema = z2.array(SkeletonKeypointSchema).length(18);
5739
+ var SkeletonSetSchema = z2.object({
5740
+ firstFrameKeypoints: SkeletonFrameSchema,
5741
+ frames: z2.array(SkeletonFrameSchema).min(3).max(15)
5742
+ }).strict();
5743
+ function parseSkeletonSet(json, file) {
5744
+ const result = SkeletonSetSchema.safeParse(json);
5745
+ if (!result.success) {
5746
+ const issues = result.error.issues.slice(0, 4).map((issue) => `${issue.path.join(".") || "root"}: ${issue.message}`).join("; ");
5747
+ throw new Error(`revision keypoints file ${file} does not match the expected shape: ${issues}`);
5748
+ }
5749
+ return result.data;
5750
+ }
5751
+
5752
+ // src/client.ts
5661
5753
  var BASE = process.env.PIXELLAB_API_BASE ?? "https://api.pixellab.ai/v2";
5662
5754
  var MAX_DOWNLOAD_BYTES = 64 * 1024 * 1024;
5663
- var BalanceResponseSchema = z2.object({
5664
- credits: z2.object({ usd: z2.number() }).passthrough(),
5665
- subscription: z2.object({
5666
- generations: z2.number(),
5667
- total: z2.number(),
5668
- plan: z2.string().nullable().optional()
5755
+ var BalanceResponseSchema = z3.object({
5756
+ credits: z3.object({ usd: z3.number() }).passthrough(),
5757
+ subscription: z3.object({
5758
+ generations: z3.number(),
5759
+ total: z3.number(),
5760
+ plan: z3.string().nullable().optional()
5669
5761
  }).passthrough()
5670
5762
  }).passthrough();
5671
- var ObjectSubmitSchema = z2.object({
5672
- background_job_id: z2.string().min(1),
5673
- object_id: z2.string().min(1),
5674
- status: z2.string().default("queued"),
5675
- n_frames: z2.number().int().min(0)
5763
+ var ObjectSubmitSchema = z3.object({
5764
+ background_job_id: z3.string().min(1),
5765
+ object_id: z3.string().min(1),
5766
+ status: z3.string().default("queued"),
5767
+ n_frames: z3.number().int().min(0)
5676
5768
  }).passthrough();
5677
- var MapSubmitSchema = z2.object({
5678
- background_job_id: z2.string().min(1),
5679
- object_id: z2.string().min(1),
5680
- status: z2.string().default("processing")
5769
+ var MapSubmitSchema = z3.object({
5770
+ background_job_id: z3.string().min(1),
5771
+ object_id: z3.string().min(1),
5772
+ status: z3.string().default("processing")
5681
5773
  }).passthrough();
5682
- var UsageSchema = z2.object({
5683
- type: z2.string().optional(),
5684
- usd: z2.number().nullable().optional(),
5685
- generations: z2.number().nullable().optional()
5774
+ var UsageSchema = z3.object({
5775
+ type: z3.string().optional(),
5776
+ usd: z3.number().nullable().optional(),
5777
+ generations: z3.number().nullable().optional()
5686
5778
  }).passthrough().nullable().optional();
5687
- var TilesSubmitSchema = z2.object({
5688
- tile_id: z2.string().min(1),
5689
- background_job_id: z2.string().min(1),
5690
- status: z2.literal("processing").default("processing")
5779
+ var TilesSubmitSchema = z3.object({
5780
+ tile_id: z3.string().min(1),
5781
+ background_job_id: z3.string().min(1),
5782
+ status: z3.literal("processing").default("processing")
5691
5783
  }).passthrough();
5692
- var TilesProSchema = z2.object({
5693
- storage_urls: z2.record(z2.string()),
5694
- kind: z2.string().nullable().default(null),
5695
- tile_rules: z2.record(z2.unknown()).nullable().optional()
5784
+ var TilesProSchema = z3.object({
5785
+ storage_urls: z3.record(z3.string()),
5786
+ kind: z3.string().nullable().default(null),
5787
+ tile_rules: z3.record(z3.unknown()).nullable().optional()
5696
5788
  }).passthrough();
5697
- var TilesetSubmitSchema = z2.object({
5698
- tileset_id: z2.string().min(1),
5699
- background_job_id: z2.string().min(1),
5700
- status: z2.literal("processing").default("processing")
5789
+ var TilesetSubmitSchema = z3.object({
5790
+ tileset_id: z3.string().min(1),
5791
+ background_job_id: z3.string().min(1),
5792
+ status: z3.literal("processing").default("processing")
5701
5793
  }).passthrough();
5702
- var TilesetTileSchema = z2.object({
5703
- id: z2.string().min(1),
5704
- name: z2.string(),
5705
- image: z2.object({ base64: z2.string().min(1), format: z2.string().default("png") }).passthrough(),
5706
- corners: z2.object({ NW: z2.string(), NE: z2.string(), SW: z2.string(), SE: z2.string() }).passthrough(),
5707
- pattern_4x4: z2.object({
5708
- row_0: z2.array(z2.number()),
5709
- row_1: z2.array(z2.number()),
5710
- row_2: z2.array(z2.number()),
5711
- row_3: z2.array(z2.number())
5794
+ var TilesetTileSchema = z3.object({
5795
+ id: z3.string().min(1),
5796
+ name: z3.string(),
5797
+ image: z3.object({ base64: z3.string().min(1), format: z3.string().default("png") }).passthrough(),
5798
+ corners: z3.object({ NW: z3.string(), NE: z3.string(), SW: z3.string(), SE: z3.string() }).passthrough(),
5799
+ pattern_4x4: z3.object({
5800
+ row_0: z3.array(z3.number()),
5801
+ row_1: z3.array(z3.number()),
5802
+ row_2: z3.array(z3.number()),
5803
+ row_3: z3.array(z3.number())
5712
5804
  }).passthrough()
5713
5805
  }).passthrough();
5714
- var TilesetGetSchema = z2.object({
5715
- tileset: z2.object({
5716
- total_tiles: z2.number().int().min(1),
5717
- tile_size: z2.object({ width: z2.number().int().positive(), height: z2.number().int().positive() }).passthrough(),
5718
- terrain_types: z2.array(z2.string()),
5719
- tiles: z2.array(TilesetTileSchema).min(1)
5806
+ var TilesetGetSchema = z3.object({
5807
+ tileset: z3.object({
5808
+ total_tiles: z3.number().int().min(1),
5809
+ tile_size: z3.object({ width: z3.number().int().positive(), height: z3.number().int().positive() }).passthrough(),
5810
+ terrain_types: z3.array(z3.string()),
5811
+ tiles: z3.array(TilesetTileSchema).min(1)
5720
5812
  }).passthrough(),
5721
5813
  usage: UsageSchema
5722
5814
  }).passthrough();
5723
- var IsometricTileSubmitSchema = z2.object({
5724
- tile_id: z2.string().min(1),
5725
- background_job_id: z2.string().min(1),
5726
- status: z2.literal("processing").default("processing")
5815
+ var IsometricTileSubmitSchema = z3.object({
5816
+ tile_id: z3.string().min(1),
5817
+ background_job_id: z3.string().min(1),
5818
+ status: z3.literal("processing").default("processing")
5727
5819
  }).passthrough();
5728
- var IsometricTileGetSchema = z2.object({
5729
- image: z2.object({ base64: z2.string().min(1), format: z2.string().default("png") }).passthrough(),
5820
+ var IsometricTileGetSchema = z3.object({
5821
+ image: z3.object({ base64: z3.string().min(1), format: z3.string().default("png") }).passthrough(),
5730
5822
  usage: UsageSchema
5731
5823
  }).passthrough();
5732
- var UiAssetSubmitSchema = z2.object({
5733
- ui_asset_id: z2.string().min(1),
5734
- background_job_id: z2.string().min(1),
5735
- status: z2.string().default("processing"),
5824
+ var UiAssetSubmitSchema = z3.object({
5825
+ ui_asset_id: z3.string().min(1),
5826
+ background_job_id: z3.string().min(1),
5827
+ status: z3.string().default("processing"),
5736
5828
  usage: UsageSchema
5737
5829
  }).passthrough();
5738
- var UiAssetGetSchema = z2.object({
5739
- id: z2.string().min(1),
5740
- status: z2.string().nullable().default(null),
5741
- image_url: z2.string().nullable().optional(),
5742
- progress_percent: z2.number().nullable().optional(),
5743
- eta_seconds: z2.number().nullable().optional()
5830
+ var UiAssetGetSchema = z3.object({
5831
+ id: z3.string().min(1),
5832
+ status: z3.string().nullable().default(null),
5833
+ image_url: z3.string().nullable().optional(),
5834
+ progress_percent: z3.number().nullable().optional(),
5835
+ eta_seconds: z3.number().nullable().optional()
5744
5836
  }).passthrough();
5745
- var RevisionJobSubmitSchema = z2.object({
5746
- background_job_id: z2.string().min(1),
5747
- status: z2.string().default("processing")
5837
+ var RevisionJobSubmitSchema = z3.object({
5838
+ background_job_id: z3.string().min(1),
5839
+ status: z3.string().default("processing")
5748
5840
  }).passthrough();
5749
- var ObjectAnimationGroupSchema = z2.object({
5750
- animation_group_id: z2.string().min(1),
5751
- display_name: z2.string().nullable().default(null),
5752
- description: z2.string().default(""),
5753
- frame_count: z2.number().int().default(0),
5754
- directions: z2.array(
5755
- z2.object({
5756
- direction: z2.string(),
5757
- storage_urls: z2.record(z2.unknown()).default({}),
5758
- created_at: z2.string()
5841
+ var ObjectAnimationGroupSchema = z3.object({
5842
+ animation_group_id: z3.string().min(1),
5843
+ display_name: z3.string().nullable().default(null),
5844
+ description: z3.string().default(""),
5845
+ frame_count: z3.number().int().default(0),
5846
+ directions: z3.array(
5847
+ z3.object({
5848
+ direction: z3.string(),
5849
+ storage_urls: z3.record(z3.unknown()).default({}),
5850
+ created_at: z3.string()
5759
5851
  }).passthrough()
5760
5852
  ).default([])
5761
5853
  }).passthrough();
5762
- var PixelLabObjectSchema = z2.object({
5763
- id: z2.string().min(1),
5764
- name: z2.string().nullable().default(null),
5765
- state_name: z2.string().nullable().optional(),
5766
- prompt: z2.string().default(""),
5767
- size: z2.object({ width: z2.number().int().positive(), height: z2.number().int().positive() }),
5768
- directions: z2.number().default(0),
5769
- created_at: z2.string(),
5770
- view: z2.string().nullable().default(null),
5771
- preview_url: z2.string().nullable().optional(),
5772
- rotation_urls: z2.record(z2.string().nullable()).nullable().optional(),
5773
- frame_urls: z2.array(z2.string()).nullable().optional(),
5774
- tags: z2.array(z2.string()).default([]),
5775
- status: z2.string().nullable().default(null),
5776
- progress_percent: z2.number().nullable().optional(),
5777
- eta_seconds: z2.number().nullable().optional(),
5778
- group_id: z2.string().nullable().optional(),
5779
- animations: z2.array(ObjectAnimationGroupSchema).default([])
5854
+ var PixelLabObjectSchema = z3.object({
5855
+ id: z3.string().min(1),
5856
+ name: z3.string().nullable().default(null),
5857
+ state_name: z3.string().nullable().optional(),
5858
+ prompt: z3.string().default(""),
5859
+ size: z3.object({ width: z3.number().int().positive(), height: z3.number().int().positive() }),
5860
+ directions: z3.number().default(0),
5861
+ created_at: z3.string(),
5862
+ view: z3.string().nullable().default(null),
5863
+ preview_url: z3.string().nullable().optional(),
5864
+ rotation_urls: z3.record(z3.string().nullable()).nullable().optional(),
5865
+ frame_urls: z3.array(z3.string()).nullable().optional(),
5866
+ tags: z3.array(z3.string()).default([]),
5867
+ status: z3.string().nullable().default(null),
5868
+ progress_percent: z3.number().nullable().optional(),
5869
+ eta_seconds: z3.number().nullable().optional(),
5870
+ group_id: z3.string().nullable().optional(),
5871
+ animations: z3.array(ObjectAnimationGroupSchema).default([])
5872
+ }).passthrough();
5873
+ var MapObjectSchema = z3.object({
5874
+ object_id: z3.string().min(1),
5875
+ status: z3.string(),
5876
+ description: z3.string().nullable().default(null),
5877
+ width: z3.number().nullable().default(null),
5878
+ height: z3.number().nullable().default(null),
5879
+ download_url: z3.string().nullable().default(null)
5780
5880
  }).passthrough();
5781
- var MapObjectSchema = z2.object({
5782
- object_id: z2.string().min(1),
5783
- status: z2.string(),
5784
- description: z2.string().nullable().default(null),
5785
- width: z2.number().nullable().default(null),
5786
- height: z2.number().nullable().default(null),
5787
- download_url: z2.string().nullable().default(null)
5881
+ var ObjectListSchema = z3.object({ objects: z3.array(PixelLabObjectSchema), total: z3.number().int().min(0) }).passthrough();
5882
+ var PixfluxResponseSchema = z3.object({ image: z3.object({ base64: z3.string().min(1) }).passthrough(), usage: z3.unknown().optional() }).passthrough();
5883
+ var ReduceColorsResponseSchema = z3.object({
5884
+ images: z3.array(z3.object({ base64: z3.string().min(1) }).passthrough()).min(1),
5885
+ palette: z3.object({ base64: z3.string().min(1) }).passthrough(),
5886
+ n_colors: z3.number().int(),
5887
+ usage: z3.unknown().optional()
5788
5888
  }).passthrough();
5789
- var ObjectListSchema = z2.object({ objects: z2.array(PixelLabObjectSchema), total: z2.number().int().min(0) }).passthrough();
5790
- var PixfluxResponseSchema = z2.object({ image: z2.object({ base64: z2.string().min(1) }).passthrough(), usage: z2.unknown().optional() }).passthrough();
5791
- var ReduceColorsResponseSchema = z2.object({
5792
- images: z2.array(z2.object({ base64: z2.string().min(1) }).passthrough()).min(1),
5793
- palette: z2.object({ base64: z2.string().min(1) }).passthrough(),
5794
- n_colors: z2.number().int(),
5795
- usage: z2.unknown().optional()
5889
+ var CorrectPixelartResponseSchema = z3.object({
5890
+ images: z3.array(z3.object({ base64: z3.string().min(1) }).passthrough()).min(1),
5891
+ usage: z3.unknown().optional()
5796
5892
  }).passthrough();
5797
- var CorrectPixelartResponseSchema = z2.object({
5798
- images: z2.array(z2.object({ base64: z2.string().min(1) }).passthrough()).min(1),
5799
- usage: z2.unknown().optional()
5893
+ var ProFlashImageSubmitSchema = z3.object({
5894
+ background_job_id: z3.string().min(1),
5895
+ image_id: z3.string().min(1),
5896
+ source_image_id: z3.string().min(1),
5897
+ status: z3.string().default("processing"),
5898
+ estimated_generations: z3.number().nullable().optional(),
5899
+ usage: z3.unknown().optional()
5800
5900
  }).passthrough();
5801
- var ProFlashImageSubmitSchema = z2.object({
5802
- background_job_id: z2.string().min(1),
5803
- image_id: z2.string().min(1),
5804
- source_image_id: z2.string().min(1),
5805
- status: z2.string().default("processing"),
5806
- estimated_generations: z2.number().nullable().optional(),
5807
- usage: z2.unknown().optional()
5901
+ var UnzoomResponseSchema = z3.object({
5902
+ image: z3.object({ base64: z3.string().min(1) }).passthrough(),
5903
+ original_size: z3.object({ width: z3.number().int(), height: z3.number().int() }).passthrough(),
5904
+ unzoomed_size: z3.object({ width: z3.number().int(), height: z3.number().int() }).passthrough(),
5905
+ zoom_factor_detected: z3.number(),
5906
+ usage: z3.unknown().optional()
5808
5907
  }).passthrough();
5809
- var UnzoomResponseSchema = z2.object({
5810
- image: z2.object({ base64: z2.string().min(1) }).passthrough(),
5811
- original_size: z2.object({ width: z2.number().int(), height: z2.number().int() }).passthrough(),
5812
- unzoomed_size: z2.object({ width: z2.number().int(), height: z2.number().int() }).passthrough(),
5813
- zoom_factor_detected: z2.number(),
5814
- usage: z2.unknown().optional()
5908
+ var FontJobSchema = z3.object({
5909
+ job_id: z3.string().min(1),
5910
+ status: z3.string(),
5911
+ glyph_px: z3.number().int().nullable().optional(),
5912
+ download_atlas_url: z3.string().nullable().optional(),
5913
+ download_ttf_url: z3.string().nullable().optional(),
5914
+ usage: z3.unknown().optional()
5815
5915
  }).passthrough();
5816
- var FontJobSchema = z2.object({
5817
- job_id: z2.string().min(1),
5818
- status: z2.string(),
5819
- glyph_px: z2.number().int().nullable().optional(),
5820
- download_atlas_url: z2.string().nullable().optional(),
5821
- download_ttf_url: z2.string().nullable().optional(),
5822
- usage: z2.unknown().optional()
5916
+ var SelectFramesSchema = z3.object({ created_object_ids: z3.array(z3.string()) }).passthrough();
5917
+ var EstimateSkeletonResponseSchema = z3.object({
5918
+ keypoints: z3.array(SkeletonKeypointSchema).length(18),
5919
+ usage: z3.unknown().optional()
5823
5920
  }).passthrough();
5824
- var SelectFramesSchema = z2.object({ created_object_ids: z2.array(z2.string()) }).passthrough();
5825
- var CharacterSubmitSchema = z2.object({
5826
- background_job_id: z2.string().min(1),
5827
- character_id: z2.string().min(1),
5828
- status: z2.string().default("processing"),
5921
+ var CharacterSubmitSchema = z3.object({
5922
+ background_job_id: z3.string().min(1),
5923
+ character_id: z3.string().min(1),
5924
+ status: z3.string().default("processing"),
5829
5925
  usage: UsageSchema
5830
5926
  }).passthrough();
5831
- var AnimateSubmitSchema = z2.object({
5832
- background_job_ids: z2.array(z2.string()),
5833
- directions: z2.array(z2.string()),
5834
- status: z2.string().default("processing"),
5927
+ var AnimateSubmitSchema = z3.object({
5928
+ background_job_ids: z3.array(z3.string()),
5929
+ directions: z3.array(z3.string()),
5930
+ status: z3.string().default("processing"),
5835
5931
  usage: UsageSchema
5836
5932
  }).passthrough();
5837
- var ObjectProSubmitSchema = z2.object({
5838
- object_id: z2.string().min(1),
5839
- background_job_id: z2.string().min(1),
5840
- status: z2.string().default("processing"),
5933
+ var ObjectProSubmitSchema = z3.object({
5934
+ object_id: z3.string().min(1),
5935
+ background_job_id: z3.string().min(1),
5936
+ status: z3.string().default("processing"),
5841
5937
  usage: UsageSchema
5842
5938
  }).passthrough();
5843
- var DirectionSubmissionSchema = z2.object({
5844
- direction: z2.string(),
5845
- status: z2.string(),
5846
- background_job_id: z2.string().nullable().default(null),
5847
- animation_id: z2.string().nullable().default(null)
5939
+ var DirectionSubmissionSchema = z3.object({
5940
+ direction: z3.string(),
5941
+ status: z3.string(),
5942
+ background_job_id: z3.string().nullable().default(null),
5943
+ animation_id: z3.string().nullable().default(null)
5848
5944
  }).passthrough();
5849
- var AnimateObjectSubmitSchema = z2.object({
5850
- animation_group_id: z2.string().min(1),
5851
- object_id: z2.string().min(1),
5852
- mode: z2.string(),
5853
- frame_count: z2.number().int(),
5854
- display_name: z2.string().nullable().default(null),
5855
- description: z2.string().default(""),
5856
- submissions: z2.array(DirectionSubmissionSchema),
5945
+ var AnimateObjectSubmitSchema = z3.object({
5946
+ animation_group_id: z3.string().min(1),
5947
+ object_id: z3.string().min(1),
5948
+ mode: z3.string(),
5949
+ frame_count: z3.number().int(),
5950
+ display_name: z3.string().nullable().default(null),
5951
+ description: z3.string().default(""),
5952
+ submissions: z3.array(DirectionSubmissionSchema),
5857
5953
  usage: UsageSchema
5858
5954
  }).passthrough();
5859
- var PortraitSubmitSchema = z2.object({
5860
- background_job_id: z2.string().min(1),
5861
- status: z2.string().default("processing"),
5955
+ var PortraitSubmitSchema = z3.object({
5956
+ background_job_id: z3.string().min(1),
5957
+ status: z3.string().default("processing"),
5862
5958
  usage: UsageSchema
5863
5959
  }).passthrough();
5864
- var PortraitJobSchema = z2.object({
5865
- status: z2.string(),
5866
- download_url: z2.string().nullable().optional(),
5867
- width: z2.number().int().nullable().optional(),
5868
- height: z2.number().int().nullable().optional()
5960
+ var PortraitJobSchema = z3.object({
5961
+ status: z3.string(),
5962
+ download_url: z3.string().nullable().optional(),
5963
+ width: z3.number().int().nullable().optional(),
5964
+ height: z3.number().int().nullable().optional()
5869
5965
  }).passthrough();
5870
- var SetPortraitSchema = z2.object({
5871
- character_id: z2.string().min(1),
5872
- size: z2.number().int().positive(),
5873
- url: z2.string().min(1),
5966
+ var SetPortraitSchema = z3.object({
5967
+ character_id: z3.string().min(1),
5968
+ size: z3.number().int().positive(),
5969
+ url: z3.string().min(1),
5874
5970
  usage: UsageSchema
5875
5971
  }).passthrough();
5876
- var TransferOutfitSubmitSchema = z2.object({
5877
- background_job_id: z2.string().min(1),
5878
- status: z2.string().default("processing"),
5972
+ var TransferOutfitSubmitSchema = z3.object({
5973
+ background_job_id: z3.string().min(1),
5974
+ status: z3.string().default("processing"),
5879
5975
  usage: UsageSchema
5880
5976
  }).passthrough();
5881
- var CharacterAnimationDirectionSchema = z2.object({
5882
- direction: z2.string(),
5883
- frame_count: z2.number().int().min(0),
5884
- frames: z2.array(z2.string())
5977
+ var CharacterAnimationDirectionSchema = z3.object({
5978
+ direction: z3.string(),
5979
+ frame_count: z3.number().int().min(0),
5980
+ frames: z3.array(z3.string())
5885
5981
  }).passthrough();
5886
- var CharacterAnimationSchema2 = z2.object({
5887
- animation_type: z2.string(),
5888
- display_name: z2.string().nullable().optional(),
5889
- animation_group_id: z2.string().nullable().optional(),
5890
- directions: z2.array(CharacterAnimationDirectionSchema)
5982
+ var CharacterAnimationSchema2 = z3.object({
5983
+ animation_type: z3.string(),
5984
+ display_name: z3.string().nullable().optional(),
5985
+ animation_group_id: z3.string().nullable().optional(),
5986
+ directions: z3.array(CharacterAnimationDirectionSchema)
5891
5987
  }).passthrough();
5892
- var CharacterSchema = z2.object({
5893
- id: z2.string().min(1),
5894
- name: z2.string().default(""),
5895
- state_name: z2.string().nullable().optional(),
5896
- prompt: z2.string().default(""),
5897
- size: z2.object({ width: z2.number().int().positive(), height: z2.number().int().positive() }),
5898
- directions: z2.number().int(),
5899
- created_at: z2.string(),
5900
- updated_at: z2.string().nullable().optional(),
5901
- animation_count: z2.number().int().default(0),
5902
- template_id: z2.string().default("mannequin"),
5903
- view: z2.string().nullable().optional(),
5904
- status: z2.string().default("completed"),
5905
- rotation_urls: z2.record(z2.string().nullable()).nullable().optional(),
5906
- tags: z2.array(z2.string()).default([]),
5907
- group_id: z2.string().nullable().optional(),
5908
- animations: z2.array(CharacterAnimationSchema2).default([]),
5909
- preview_url: z2.string().nullable().optional()
5988
+ var CharacterSchema = z3.object({
5989
+ id: z3.string().min(1),
5990
+ name: z3.string().default(""),
5991
+ state_name: z3.string().nullable().optional(),
5992
+ prompt: z3.string().default(""),
5993
+ size: z3.object({ width: z3.number().int().positive(), height: z3.number().int().positive() }),
5994
+ directions: z3.number().int(),
5995
+ created_at: z3.string(),
5996
+ updated_at: z3.string().nullable().optional(),
5997
+ animation_count: z3.number().int().default(0),
5998
+ template_id: z3.string().default("mannequin"),
5999
+ view: z3.string().nullable().optional(),
6000
+ status: z3.string().default("completed"),
6001
+ rotation_urls: z3.record(z3.string().nullable()).nullable().optional(),
6002
+ tags: z3.array(z3.string()).default([]),
6003
+ group_id: z3.string().nullable().optional(),
6004
+ animations: z3.array(CharacterAnimationSchema2).default([]),
6005
+ preview_url: z3.string().nullable().optional()
5910
6006
  }).passthrough();
5911
- var CharacterListSchema = z2.object({ characters: z2.array(CharacterSchema), total: z2.number().int().min(0) }).passthrough();
5912
- var BackgroundJobSchema = z2.object({
5913
- id: z2.string(),
5914
- status: z2.string(),
5915
- last_response: z2.record(z2.unknown()).nullable().optional(),
6007
+ var CharacterListSchema = z3.object({ characters: z3.array(CharacterSchema), total: z3.number().int().min(0) }).passthrough();
6008
+ var BackgroundJobSchema = z3.object({
6009
+ id: z3.string(),
6010
+ status: z3.string(),
6011
+ last_response: z3.record(z3.unknown()).nullable().optional(),
5916
6012
  /**
5917
6013
  * The billed amount, confirmed live for a `map` object and a standard
5918
6014
  * character base (docs/ENDPOINTS.md, "Limits and billing"); duplicated at
@@ -5920,7 +6016,7 @@ var BackgroundJobSchema = z2.object({
5920
6016
  */
5921
6017
  usage: UsageSchema
5922
6018
  }).passthrough();
5923
- var DeleteAnimationsSchema = z2.object({ success: z2.boolean(), deleted_count: z2.number().int().default(0), error: z2.string().nullable().optional() }).passthrough();
6019
+ var DeleteAnimationsSchema = z3.object({ success: z3.boolean(), deleted_count: z3.number().int().default(0), error: z3.string().nullable().optional() }).passthrough();
5924
6020
  function validateResponse(schema, raw, operation) {
5925
6021
  const parsed = schema.safeParse(raw);
5926
6022
  if (parsed.success) return parsed.data;
@@ -5966,9 +6062,9 @@ var PixelLabClient = class {
5966
6062
  apiKey;
5967
6063
  http;
5968
6064
  /** Retries transport failures, 429, and 5xx; see http.ts for the policy. */
5969
- async request(path47, init) {
6065
+ async request(path48, init) {
5970
6066
  const auth = this.apiKey.startsWith("Bearer ") ? this.apiKey : `Bearer ${this.apiKey}`;
5971
- const res = await this.http(`${BASE}${path47}`, {
6067
+ const res = await this.http(`${BASE}${path48}`, {
5972
6068
  ...init,
5973
6069
  headers: {
5974
6070
  Authorization: auth,
@@ -5978,13 +6074,13 @@ var PixelLabClient = class {
5978
6074
  });
5979
6075
  const text = await res.text();
5980
6076
  if (!res.ok) {
5981
- throw new PixelLabError(`${init?.method ?? "GET"} ${path47} \u2192 ${res.status}`, res.status, text);
6077
+ throw new PixelLabError(`${init?.method ?? "GET"} ${path48} \u2192 ${res.status}`, res.status, text);
5982
6078
  }
5983
6079
  if (!text) return {};
5984
6080
  try {
5985
6081
  return JSON.parse(text);
5986
6082
  } catch {
5987
- throw new Error(`${init?.method ?? "GET"} ${path47} returned invalid JSON`);
6083
+ throw new Error(`${init?.method ?? "GET"} ${path48} returned invalid JSON`);
5988
6084
  }
5989
6085
  }
5990
6086
  async balance() {
@@ -6370,10 +6466,10 @@ var PixelLabClient = class {
6370
6466
  const palette = args.paletteSwatchBase64 ? { color_image: { type: "base64", base64: args.paletteSwatchBase64, format: "png" }, force_colors: true } : {};
6371
6467
  const encode = (image) => ({ type: "base64", base64: image.base64, format: image.format });
6372
6468
  const south = args.reference?.south;
6373
- let path47;
6469
+ let path48;
6374
6470
  let body;
6375
6471
  if (args.mode === "v3") {
6376
- path47 = "/create-character-v3";
6472
+ path48 = "/create-character-v3";
6377
6473
  body = {
6378
6474
  description: args.description,
6379
6475
  image_size: imageSize,
@@ -6387,7 +6483,7 @@ var PixelLabClient = class {
6387
6483
  ...args.enhancePrompt ? { enhance_prompt: true } : {}
6388
6484
  };
6389
6485
  } else if (args.mode === "pro-flash") {
6390
- path47 = "/create-character-pro-flash";
6486
+ path48 = "/create-character-pro-flash";
6391
6487
  const traits = args.styleTraits;
6392
6488
  body = {
6393
6489
  description: args.description,
@@ -6411,7 +6507,7 @@ var PixelLabClient = class {
6411
6507
  } : {}
6412
6508
  };
6413
6509
  } else if (args.mode === "pro") {
6414
- path47 = "/create-character-pro";
6510
+ path48 = "/create-character-pro";
6415
6511
  body = {
6416
6512
  description: args.description,
6417
6513
  image_size: imageSize,
@@ -6425,7 +6521,7 @@ var PixelLabClient = class {
6425
6521
  ...!south && args.styleCharacterId ? { style_character_id: args.styleCharacterId } : {}
6426
6522
  };
6427
6523
  } else {
6428
- path47 = args.directions === 4 ? "/create-character-with-4-directions" : "/create-character-with-8-directions";
6524
+ path48 = args.directions === 4 ? "/create-character-with-4-directions" : "/create-character-with-8-directions";
6429
6525
  body = {
6430
6526
  description: args.description,
6431
6527
  image_size: imageSize,
@@ -6442,8 +6538,8 @@ var PixelLabClient = class {
6442
6538
  ...palette
6443
6539
  };
6444
6540
  }
6445
- const raw = await this.request(path47, { method: "POST", body: JSON.stringify(body) });
6446
- return validateResponse(CharacterSubmitSchema, raw, path47.slice(1));
6541
+ const raw = await this.request(path48, { method: "POST", body: JSON.stringify(body) });
6542
+ return validateResponse(CharacterSubmitSchema, raw, path48.slice(1));
6447
6543
  }
6448
6544
  /** A text edit of an existing character, applied to every direction and stored as a sibling. */
6449
6545
  async createCharacterState(args) {
@@ -6951,6 +7047,61 @@ var PixelLabClient = class {
6951
7047
  "animate-pixminimax"
6952
7048
  );
6953
7049
  }
7050
+ /**
7051
+ * `/animate-with-skeleton-v3`, beta (tier 1 subscription or higher): poses
7052
+ * one reference image frame-by-frame from a supplied skeleton per frame,
7053
+ * rather than a text motion description. Confirmed against the live
7054
+ * `animate_with_skeleton_v3` MCP tool schema (the same authoritative
7055
+ * source this codebase already treats as ground truth for the
7056
+ * character/object family) — `action`/`description` are both real,
7057
+ * optional wire fields, contrary to an initial guess that this endpoint
7058
+ * took no text input at all; `direction`/`keypoints`/`firstFrameKeypoints`
7059
+ * are required. The example keypoint from that same tool schema,
7060
+ * `{"label": "RIGHT KNEE", "x": 0.52, "y": 0.71, "z_index": 9}`, is what
7061
+ * `SkeletonKeypointSchema` (`src/skeleton.ts`) models. Same
7062
+ * unverified-completed-shape caveat as `animateWithTextV3`/
7063
+ * `animatePixminimax` above — this endpoint has never been called against
7064
+ * a live account by this codebase.
7065
+ */
7066
+ async animateWithSkeletonV3(args) {
7067
+ const body = {
7068
+ first_frame: args.firstFrame,
7069
+ first_frame_keypoints: args.firstFrameKeypoints,
7070
+ keypoints: args.keypoints,
7071
+ direction: args.direction
7072
+ };
7073
+ if (args.templateId) body.template_id = args.templateId;
7074
+ if (args.action) body.action = args.action;
7075
+ if (args.description) body.description = args.description;
7076
+ if (args.seed != null) body.seed = args.seed;
7077
+ if (args.noBackground != null) body.no_background = args.noBackground;
7078
+ return validateResponse(
7079
+ RevisionJobSubmitSchema,
7080
+ await this.request("/animate-with-skeleton-v3", { method: "POST", body: JSON.stringify(body) }),
7081
+ "animate-with-skeleton-v3"
7082
+ );
7083
+ }
7084
+ /**
7085
+ * `/estimate-skeleton`: auto-derives a reference image's own 18-joint
7086
+ * keypoints, directly usable as `animateWithSkeletonV3`'s
7087
+ * `firstFrameKeypoints` (or, run against a candidate pose image, as one
7088
+ * entry of its `keypoints` sequence). Not called anywhere in this
7089
+ * codebase's own submit/poll pipeline — deliberately kept outside it (see
7090
+ * `runEstimateSkeleton` in `src/cli/commands`) so the only PixelLab
7091
+ * endpoint composed into `animate-with-skeleton-v3`'s submission is that
7092
+ * one endpoint itself, not two unverified calls chained together.
7093
+ * Synchronous per this endpoint's own framing; unverified against a live
7094
+ * account (see `EstimateSkeletonResponseSchema`'s own caveat).
7095
+ */
7096
+ async estimateSkeleton(args) {
7097
+ const body = { image: args.image };
7098
+ const res = validateResponse(
7099
+ EstimateSkeletonResponseSchema,
7100
+ await this.request("/estimate-skeleton", { method: "POST", body: JSON.stringify(body) }),
7101
+ "estimate-skeleton"
7102
+ );
7103
+ return { keypoints: res.keypoints, usage: res.usage };
7104
+ }
6954
7105
  async getBackgroundJob(jobId) {
6955
7106
  const raw = await this.request(`/background-jobs/${encodeURIComponent(jobId)}`);
6956
7107
  return validateResponse(BackgroundJobSchema, raw, "background-jobs/{id}");
@@ -7084,6 +7235,18 @@ function objectProCost(spec) {
7084
7235
  const south = object.reference;
7085
7236
  return south ? proFlashObjectCost(south.width, south.height, object.directions, true) : proFlashObjectCost(spec.width, spec.height, object.directions, false);
7086
7237
  }
7238
+ function animateSkeletonCost(frames) {
7239
+ const anchors = [[3, 2], [8, 3], [15, 4]];
7240
+ for (let i = 0; i < anchors.length - 1; i++) {
7241
+ const [lowFrames, lowCost] = anchors[i];
7242
+ const [highFrames, highCost] = anchors[i + 1];
7243
+ if (frames >= lowFrames && frames <= highFrames) {
7244
+ const t = (frames - lowFrames) / (highFrames - lowFrames);
7245
+ return lowCost + t * (highCost - lowCost);
7246
+ }
7247
+ }
7248
+ return frames <= anchors[0][0] ? anchors[0][1] : anchors[anchors.length - 1][1];
7249
+ }
7087
7250
  var CHARACTER_TEMPLATES = ["mannequin", "bear", "cat", "dog", "horse", "lion"];
7088
7251
  var PRO_FLASH_TEMPLATES = [...CHARACTER_TEMPLATES, "custom"];
7089
7252
  function encodeAnimationJob(job) {
@@ -7142,7 +7305,9 @@ var PixelLabProvider = class _PixelLabProvider {
7142
7305
  * rather than a described edit — and `animate`/`animate-pixminimax`
7143
7306
  * (`/animate-with-text-v3`, `/animate-pixminimax`), which animate any loose
7144
7307
  * image from a text description with no character/object resource
7145
- * required — and `interpolate` (`/interpolation-v2`, the in-betweens
7308
+ * required — `animate-skeleton` (`/animate-with-skeleton-v3`, posing the
7309
+ * source from a supplied per-frame skeleton instead of a text description)
7310
+ * — and `interpolate` (`/interpolation-v2`, the in-betweens
7146
7311
  * from one keyframe to another) and `edit-animation`
7147
7312
  * (`/edit-animation-v2`, one text edit applied across a whole frame set).
7148
7313
  * `outpaint` does not: there is no canvas-expansion endpoint in the API,
@@ -7150,7 +7315,7 @@ var PixelLabProvider = class _PixelLabProvider {
7150
7315
  * outpaint path yet.
7151
7316
  */
7152
7317
  supportsRevision(mode2) {
7153
- return mode2 === "inpaint" || mode2 === "image-to-image" || mode2 === "reduce-colors" || mode2 === "correct-pixelart" || mode2 === "animate" || mode2 === "animate-pixminimax" || mode2 === "interpolate" || mode2 === "edit-animation";
7318
+ return mode2 === "inpaint" || mode2 === "image-to-image" || mode2 === "reduce-colors" || mode2 === "correct-pixelart" || mode2 === "animate" || mode2 === "animate-pixminimax" || mode2 === "animate-skeleton" || mode2 === "interpolate" || mode2 === "edit-animation";
7154
7319
  }
7155
7320
  /** PixelLab's own constraints: submissions must be >2s apart, and
7156
7321
  * background jobs in flight are capped by subscription tier (Tier 1=8,
@@ -7181,6 +7346,10 @@ var PixelLabProvider = class _PixelLabProvider {
7181
7346
  if (spec.revision?.mode === "reduce-colors" || spec.revision?.mode === "correct-pixelart") {
7182
7347
  return { unit: "generations", amount: 0.1, candidates: 1 };
7183
7348
  }
7349
+ if (spec.revision?.mode === "animate-skeleton") {
7350
+ const frames = spec.revision.frames ?? 8;
7351
+ return { unit: "generations", amount: Math.ceil(animateSkeletonCost(frames)), candidates: 1 };
7352
+ }
7184
7353
  if (spec.revision?.mode === "interpolate" || spec.revision?.mode === "edit-animation") {
7185
7354
  const width = spec.revision.sourceWidth ?? spec.width;
7186
7355
  const height = spec.revision.sourceHeight ?? spec.height;
@@ -7262,6 +7431,18 @@ var PixelLabProvider = class _PixelLabProvider {
7262
7431
  throw new Error(`PixelLab correct-pixelart source is ${width}x${height}; the API takes at most 1024 pixels per side`);
7263
7432
  }
7264
7433
  }
7434
+ if (spec.revision?.mode === "animate-skeleton") {
7435
+ const { sourceWidth: width, sourceHeight: height, frames, skeletonTemplate } = spec.revision;
7436
+ if (width != null && height != null && (width > 256 || height > 256)) {
7437
+ throw new Error(`PixelLab animate-skeleton source is ${width}x${height}; the API takes at most 256 pixels per side`);
7438
+ }
7439
+ if (frames != null && (frames < 3 || frames > 15)) {
7440
+ throw new Error(`PixelLab animate-skeleton takes 3 to 15 frames; the keypoints file has ${frames}`);
7441
+ }
7442
+ if (skeletonTemplate && !CHARACTER_TEMPLATES.includes(skeletonTemplate)) {
7443
+ throw new Error(`PixelLab animate-skeleton template "${skeletonTemplate}" is not one of ${CHARACTER_TEMPLATES.join(", ")}`);
7444
+ }
7445
+ }
7265
7446
  if (spec.revision?.mode === "animate" || spec.revision?.mode === "animate-pixminimax") {
7266
7447
  const { sourceWidth: width, sourceHeight: height, mode: mode2, frames, lastFrameWidth, lastFrameHeight } = spec.revision;
7267
7448
  if (width != null && height != null && (width > 256 || height > 256)) {
@@ -8411,6 +8592,27 @@ var PixelLabProvider = class _PixelLabProvider {
8411
8592
  });
8412
8593
  return { jobId: res2.background_job_id };
8413
8594
  }
8595
+ if (revision.mode === "animate-skeleton") {
8596
+ if (!revision.keypointsFile || !revision.keypointsSha256 || !revision.skeleton) {
8597
+ throw new Error(`${spec.styleId}/${spec.assetId}: revision keypoints are not ready`);
8598
+ }
8599
+ const keypointsBytes = readFileSync2(revision.keypointsFile);
8600
+ if (sha256(keypointsBytes) !== revision.keypointsSha256) {
8601
+ throw new Error(`${spec.styleId}/${spec.assetId}: revision keypoints changed after the manifest was resolved`);
8602
+ }
8603
+ const res2 = await this.client.animateWithSkeletonV3({
8604
+ firstFrame: image,
8605
+ firstFrameKeypoints: revision.skeleton.firstFrameKeypoints,
8606
+ keypoints: revision.skeleton.frames,
8607
+ direction: revision.direction,
8608
+ templateId: revision.skeletonTemplate,
8609
+ action: spec.prompt,
8610
+ description: revision.description,
8611
+ seed: spec.seed,
8612
+ noBackground: spec.noBackground
8613
+ });
8614
+ return { jobId: res2.background_job_id };
8615
+ }
8414
8616
  if (revision.strength != null) {
8415
8617
  throw new Error(
8416
8618
  `${spec.styleId}/${spec.assetId}: PixelLab image-to-image revisions take no strength; edit-images-v2 always applies the full instruction`
@@ -8476,7 +8678,7 @@ var PixelLabProvider = class _PixelLabProvider {
8476
8678
  if (revisionMode === "reduce-colors" || revisionMode === "correct-pixelart") {
8477
8679
  return this.pollCachedRevision(jobId, context);
8478
8680
  }
8479
- if (revisionMode === "animate" || revisionMode === "animate-pixminimax" || revisionMode === "interpolate" || revisionMode === "edit-animation") {
8681
+ if (revisionMode === "animate" || revisionMode === "animate-pixminimax" || revisionMode === "animate-skeleton" || revisionMode === "interpolate" || revisionMode === "edit-animation") {
8480
8682
  return this.pollAnimateRevision(jobId, context);
8481
8683
  }
8482
8684
  if (context?.spec?.revision) return this.pollRevision(jobId);
@@ -9166,9 +9368,9 @@ var RetroDiffusionClient = class {
9166
9368
  if (!response.ok) throw new ProviderError("retrodiffusion", `Retro Diffusion download failed (${response.status})`, { status: response.status });
9167
9369
  return Buffer.from(await response.arrayBuffer());
9168
9370
  }
9169
- async call(path47, init = {}) {
9371
+ async call(path48, init = {}) {
9170
9372
  if (!this.token) throw new Error("RD_API_KEY is not set");
9171
- const response = await this.request(`${this.baseUrl}${path47}`, {
9373
+ const response = await this.request(`${this.baseUrl}${path48}`, {
9172
9374
  ...init,
9173
9375
  headers: {
9174
9376
  "Content-Type": "application/json",
@@ -10712,6 +10914,8 @@ async function resolveSpecs(loaded, filter) {
10712
10914
  const paletteImage = paletteImageFile ? await optionalRevisionImage(paletteImageFile, "revision palette image", "png") : null;
10713
10915
  const lastFrameFile = asset.revision.lastFrame ? path13.resolve(root, asset.revision.lastFrame) : void 0;
10714
10916
  const lastFrame = lastFrameFile ? await optionalRevisionImage(lastFrameFile, "revision last frame") : null;
10917
+ const keypointsFile = asset.revision.keypointsFile ? path13.resolve(root, asset.revision.keypointsFile) : void 0;
10918
+ const keypoints = keypointsFile ? await optionalSkeletonSet(keypointsFile) : null;
10715
10919
  resolved.revision = {
10716
10920
  mode: asset.revision.mode,
10717
10921
  sourceAssetId: asset.revision.from,
@@ -10752,6 +10956,18 @@ async function resolveSpecs(loaded, filter) {
10752
10956
  } : {},
10753
10957
  ...asset.revision.direction ? { direction: asset.revision.direction } : {},
10754
10958
  ...asset.revision.enhancePrompt == null ? {} : { enhancePrompt: asset.revision.enhancePrompt },
10959
+ ...keypointsFile ? {
10960
+ keypointsFile,
10961
+ keypointsSha256: keypoints?.hash ?? null,
10962
+ skeleton: keypoints?.skeleton ?? null,
10963
+ // The manifest's own `frames` field stays rejected for this
10964
+ // mode (superRefine, types.ts) — the real frame count comes
10965
+ // from the keypoints file once it exists, not a manifest
10966
+ // number, so plan can only know it once authored.
10967
+ ...keypoints ? { frames: keypoints.skeleton.frames.length } : {}
10968
+ } : {},
10969
+ ...asset.revision.skeletonTemplate ? { skeletonTemplate: asset.revision.skeletonTemplate } : {},
10970
+ ...asset.revision.description ? { description: asset.revision.description } : {},
10755
10971
  ...asset.revision.engine ? { engine: asset.revision.engine } : {}
10756
10972
  };
10757
10973
  }
@@ -10873,6 +11089,17 @@ async function optionalRevisionImage(file, label, requiredFormat) {
10873
11089
  }
10874
11090
  return { hash: sha256(bytes), ...metadata };
10875
11091
  }
11092
+ async function optionalSkeletonSet(file) {
11093
+ if (!existsSync8(file)) return null;
11094
+ const bytes = await readFile6(file);
11095
+ let json;
11096
+ try {
11097
+ json = JSON.parse(bytes.toString("utf8"));
11098
+ } catch (error) {
11099
+ throw new Error(`revision keypoints file ${file} is not valid JSON: ${error instanceof Error ? error.message : String(error)}`);
11100
+ }
11101
+ return { hash: sha256(bytes), skeleton: parseSkeletonSet(json, file) };
11102
+ }
10876
11103
  async function revisionSourceMembers(stem, label) {
10877
11104
  if (existsSync8(stem)) return null;
10878
11105
  const at = (role) => memberPath(stem, role, 0, 2, MediaType.PNG);
@@ -11408,11 +11635,11 @@ import path15 from "path";
11408
11635
  import { mkdir as mkdir5, readFile as readFile8, rename as rename5, rm as rm5, writeFile as writeFile5 } from "fs/promises";
11409
11636
  import { existsSync as existsSync10 } from "fs";
11410
11637
  import pathModule from "path";
11411
- import { z as z3 } from "zod";
11412
- var HashCacheSchema = z3.object({
11413
- version: z3.literal(1),
11638
+ import { z as z4 } from "zod";
11639
+ var HashCacheSchema = z4.object({
11640
+ version: z4.literal(1),
11414
11641
  /** objectId → sha256 of that object's image bytes. */
11415
- hashes: z3.record(z3.string())
11642
+ hashes: z4.record(z4.string())
11416
11643
  });
11417
11644
  var SHA256_HEX = /^[a-f0-9]{64}$/;
11418
11645
  function isSha256Hash(value) {
@@ -11421,10 +11648,10 @@ function isSha256Hash(value) {
11421
11648
  function parseCache(value) {
11422
11649
  return HashCacheSchema.parse(value);
11423
11650
  }
11424
- async function loadCache(path47) {
11425
- if (!existsSync10(path47)) return { version: 1, hashes: {} };
11651
+ async function loadCache(path48) {
11652
+ if (!existsSync10(path48)) return { version: 1, hashes: {} };
11426
11653
  try {
11427
- const parsed = HashCacheSchema.safeParse(JSON.parse(await readFile8(path47, "utf8")));
11654
+ const parsed = HashCacheSchema.safeParse(JSON.parse(await readFile8(path48, "utf8")));
11428
11655
  if (!parsed.success) return { version: 1, hashes: {} };
11429
11656
  return {
11430
11657
  version: 1,
@@ -11436,18 +11663,18 @@ async function loadCache(path47) {
11436
11663
  return { version: 1, hashes: {} };
11437
11664
  }
11438
11665
  }
11439
- async function saveCache(path47, cache) {
11666
+ async function saveCache(path48, cache) {
11440
11667
  const sorted = {};
11441
11668
  for (const key of Object.keys(cache.hashes).sort()) {
11442
11669
  const hash = cache.hashes[key];
11443
11670
  if (!isSha256Hash(hash)) throw new Error(`Refusing to cache invalid SHA-256 for ${key}`);
11444
11671
  sorted[key] = hash;
11445
11672
  }
11446
- await mkdir5(pathModule.dirname(pathModule.resolve(path47)), { recursive: true });
11447
- const tmp = `${path47}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
11673
+ await mkdir5(pathModule.dirname(pathModule.resolve(path48)), { recursive: true });
11674
+ const tmp = `${path48}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
11448
11675
  try {
11449
11676
  await writeFile5(tmp, JSON.stringify({ version: 1, hashes: sorted }, null, 2) + "\n");
11450
- await rename5(tmp, path47);
11677
+ await rename5(tmp, path48);
11451
11678
  } finally {
11452
11679
  await rm5(tmp, { force: true });
11453
11680
  }
@@ -12205,20 +12432,20 @@ async function applyTags(provider, decisions, existing, opts = {}) {
12205
12432
  import { mkdir as mkdir7, readFile as readFile12, rename as rename6, rm as rm6, writeFile as writeFile8 } from "fs/promises";
12206
12433
  import { existsSync as existsSync14 } from "fs";
12207
12434
  import path18 from "path";
12208
- import { z as z4 } from "zod";
12209
- var WorkspaceProjectSchema = z4.object({
12210
- id: z4.string().min(1),
12435
+ import { z as z5 } from "zod";
12436
+ var WorkspaceProjectSchema = z5.object({
12437
+ id: z5.string().min(1),
12211
12438
  /** Manifest path, relative to the catalog file's own directory. */
12212
- manifest: z4.string().min(1),
12439
+ manifest: z5.string().min(1),
12213
12440
  /** Lockfile path, relative to the catalog file's own directory. */
12214
- lock: z4.string().min(1),
12215
- provider: z4.string().min(1).default("pixellab"),
12441
+ lock: z5.string().min(1),
12442
+ provider: z5.string().min(1).default("pixellab"),
12216
12443
  /** Free-form label for a shared account, e.g. distinguishing sandboxes. */
12217
- account: z4.string().optional()
12444
+ account: z5.string().optional()
12218
12445
  }).strict();
12219
- var WorkspaceSchema = z4.object({
12220
- version: z4.literal(1),
12221
- projects: z4.array(WorkspaceProjectSchema).default([])
12446
+ var WorkspaceSchema = z5.object({
12447
+ version: z5.literal(1),
12448
+ projects: z5.array(WorkspaceProjectSchema).default([])
12222
12449
  }).strict();
12223
12450
  function parseWorkspace(raw) {
12224
12451
  const parsed = WorkspaceSchema.safeParse(raw);
@@ -13403,62 +13630,62 @@ import path25 from "path";
13403
13630
  import { existsSync as existsSync17 } from "fs";
13404
13631
  import { copyFile, mkdir as mkdir9, readFile as readFile16, rename as rename8, rm as rm8, writeFile as writeFile11 } from "fs/promises";
13405
13632
  import path24 from "path";
13406
- import { z as z6 } from "zod";
13633
+ import { z as z7 } from "zod";
13407
13634
 
13408
13635
  // src/manifest-edit.ts
13409
13636
  import { readFile as readFile15, rename as rename7, rm as rm7, writeFile as writeFile10 } from "fs/promises";
13410
13637
  import path23 from "path";
13411
- import { z as z5 } from "zod";
13412
- var HexSha = z5.string().regex(/^[0-9a-f]{64}$/);
13413
- var AssetPatchSchema = z5.object({
13414
- prompt: z5.string().optional(),
13638
+ import { z as z6 } from "zod";
13639
+ var HexSha = z6.string().regex(/^[0-9a-f]{64}$/);
13640
+ var AssetPatchSchema = z6.object({
13641
+ prompt: z6.string().optional(),
13415
13642
  /** Per-style override for the style being viewed; null removes it. */
13416
- promptForStyle: z5.object({ styleId: z5.string().min(1), prompt: z5.string().nullable() }).optional(),
13417
- category: z5.string().nullable().optional(),
13418
- width: z5.number().int().nullable().optional(),
13419
- height: z5.number().int().nullable().optional(),
13420
- size: z5.number().int().nullable().optional(),
13421
- tags: z5.array(z5.string()).optional()
13643
+ promptForStyle: z6.object({ styleId: z6.string().min(1), prompt: z6.string().nullable() }).optional(),
13644
+ category: z6.string().nullable().optional(),
13645
+ width: z6.number().int().nullable().optional(),
13646
+ height: z6.number().int().nullable().optional(),
13647
+ size: z6.number().int().nullable().optional(),
13648
+ tags: z6.array(z6.string()).optional()
13422
13649
  }).strict();
13423
- var NewStateSchema = z5.object({
13650
+ var NewStateSchema = z6.object({
13424
13651
  /** Asset id of the parent this states; must already exist. */
13425
- of: z5.string().min(1),
13426
- paletteFromReference: z5.boolean().optional(),
13427
- canvas: z5.object({ width: z5.number().int().min(16).max(256), height: z5.number().int().min(16).max(256) }).strict().optional()
13652
+ of: z6.string().min(1),
13653
+ paletteFromReference: z6.boolean().optional(),
13654
+ canvas: z6.object({ width: z6.number().int().min(16).max(256), height: z6.number().int().min(16).max(256) }).strict().optional()
13428
13655
  }).strict();
13429
- var NewAnimationSchema = z5.object({
13656
+ var NewAnimationSchema = z6.object({
13430
13657
  /** Asset id of the parent this animates; must already exist. */
13431
- of: z5.string().min(1),
13658
+ of: z6.string().min(1),
13432
13659
  /** A PixelLab template id (`character` only; `objectPro` has no template concept). */
13433
- template: z5.string().min(1).optional(),
13660
+ template: z6.string().min(1).optional(),
13434
13661
  direction: CharacterDirectionSchema.optional(),
13435
- frames: z5.number().int().min(4).max(16).optional(),
13436
- fps: z5.number().int().min(1).max(60).optional(),
13662
+ frames: z6.number().int().min(4).max(16).optional(),
13663
+ fps: z6.number().int().min(1).max(60).optional(),
13437
13664
  mode: CharacterAnimationModeSchema.optional(),
13438
13665
  /** What is being animated, when the parent's own description would mislead the model (`character` only). */
13439
- subject: z5.string().min(1).optional(),
13440
- enhancePrompt: z5.boolean().optional()
13666
+ subject: z6.string().min(1).optional(),
13667
+ enhancePrompt: z6.boolean().optional()
13441
13668
  }).strict();
13442
- var NewAssetSchema = z5.object({
13443
- prompt: z5.string(),
13444
- category: z5.string().optional(),
13445
- width: z5.number().int().optional(),
13446
- height: z5.number().int().optional(),
13447
- size: z5.number().int().optional(),
13448
- tags: z5.array(z5.string()).optional(),
13669
+ var NewAssetSchema = z6.object({
13670
+ prompt: z6.string(),
13671
+ category: z6.string().optional(),
13672
+ width: z6.number().int().optional(),
13673
+ height: z6.number().int().optional(),
13674
+ size: z6.number().int().optional(),
13675
+ tags: z6.array(z6.string()).optional(),
13449
13676
  /** Restrict to these styles; empty or omitted means every style. */
13450
- styles: z5.array(z5.string().min(1)).optional(),
13677
+ styles: z6.array(z6.string().min(1)).optional(),
13451
13678
  /**
13452
13679
  * A controlled regeneration of another asset in the same manifest,
13453
13680
  * instead of a fresh generation. `inpaint` needs a mask upload the
13454
13681
  * gallery does not offer yet, so only `image-to-image` can be created
13455
13682
  * here; hand-edit the manifest for the other modes.
13456
13683
  */
13457
- revision: z5.object({
13458
- mode: z5.literal("image-to-image"),
13684
+ revision: z6.object({
13685
+ mode: z6.literal("image-to-image"),
13459
13686
  /** Asset id of the parent this revises; must already exist. */
13460
- from: z5.string().min(1),
13461
- strength: z5.number().min(0).max(1).optional()
13687
+ from: z6.string().min(1),
13688
+ strength: z6.number().min(0).max(1).optional()
13462
13689
  }).strict().optional(),
13463
13690
  /** A pose or outfit of an existing `character`/`objectPro` base or state. */
13464
13691
  state: NewStateSchema.optional(),
@@ -13474,58 +13701,58 @@ var CANDIDATE_OPTION = {
13474
13701
  comfyui: "numImages",
13475
13702
  scenario: "numOutputs"
13476
13703
  };
13477
- var ManifestEditSchema = z5.discriminatedUnion("action", [
13478
- z5.object({
13479
- action: z5.literal("patch-asset"),
13704
+ var ManifestEditSchema = z6.discriminatedUnion("action", [
13705
+ z6.object({
13706
+ action: z6.literal("patch-asset"),
13480
13707
  /** Workspace project id; omitted for a single-project gallery. */
13481
- project: z5.string().min(1).optional(),
13482
- assetId: z5.string().min(1),
13708
+ project: z6.string().min(1).optional(),
13709
+ assetId: z6.string().min(1),
13483
13710
  /** Manifest bytes the page last saw; a mismatch refuses the write. */
13484
13711
  expectedSha256: HexSha,
13485
13712
  patch: AssetPatchSchema
13486
13713
  }).strict(),
13487
- z5.object({
13488
- action: z5.literal("add-asset"),
13489
- project: z5.string().min(1).optional(),
13490
- assetId: z5.string().min(1).regex(/^[^/\\]+$/, "asset ids cannot contain slashes"),
13714
+ z6.object({
13715
+ action: z6.literal("add-asset"),
13716
+ project: z6.string().min(1).optional(),
13717
+ assetId: z6.string().min(1).regex(/^[^/\\]+$/, "asset ids cannot contain slashes"),
13491
13718
  expectedSha256: HexSha,
13492
13719
  asset: NewAssetSchema
13493
13720
  }).strict(),
13494
- z5.object({
13495
- action: z5.literal("set-source"),
13496
- project: z5.string().min(1).optional(),
13497
- assetId: z5.string().min(1),
13498
- styleId: z5.string().min(1),
13721
+ z6.object({
13722
+ action: z6.literal("set-source"),
13723
+ project: z6.string().min(1).optional(),
13724
+ assetId: z6.string().min(1),
13725
+ styleId: z6.string().min(1),
13499
13726
  /** Manifest-relative path of the art that stands in for this asset in this style. */
13500
- source: z5.string().min(1),
13727
+ source: z6.string().min(1),
13501
13728
  expectedSha256: HexSha
13502
13729
  }).strict(),
13503
- z5.object({
13504
- action: z5.literal("clear-source"),
13505
- project: z5.string().min(1).optional(),
13506
- assetId: z5.string().min(1),
13507
- styleId: z5.string().min(1),
13730
+ z6.object({
13731
+ action: z6.literal("clear-source"),
13732
+ project: z6.string().min(1).optional(),
13733
+ assetId: z6.string().min(1),
13734
+ styleId: z6.string().min(1),
13508
13735
  expectedSha256: HexSha
13509
13736
  }).strict(),
13510
- z5.object({
13511
- action: z5.literal("patch-style"),
13512
- project: z5.string().min(1).optional(),
13513
- styleId: z5.string().min(1),
13737
+ z6.object({
13738
+ action: z6.literal("patch-style"),
13739
+ project: z6.string().min(1).optional(),
13740
+ styleId: z6.string().min(1),
13514
13741
  expectedSha256: HexSha,
13515
- patch: z5.object({
13742
+ patch: z6.object({
13516
13743
  /** Candidates per generation, written to the provider's own option. */
13517
- candidates: z5.number().int().min(1).max(64).optional(),
13744
+ candidates: z6.number().int().min(1).max(64).optional(),
13518
13745
  /** Empty clears the style's own value (inherit, or the default). */
13519
- promptPrefix: z5.string().optional(),
13520
- promptSuffix: z5.string().optional(),
13746
+ promptPrefix: z6.string().optional(),
13747
+ promptSuffix: z6.string().optional(),
13521
13748
  /** `#rrggbb` values; null or empty clears the style's own palette. */
13522
- palette: z5.array(z5.string().regex(/^#?[0-9a-f]{6}$/i, "expected a six-digit hex colour")).max(256).nullable().optional(),
13749
+ palette: z6.array(z6.string().regex(/^#?[0-9a-f]{6}$/i, "expected a six-digit hex colour")).max(256).nullable().optional(),
13523
13750
  /** Snap downloaded art to the palette; false clears the style's own value. */
13524
- enforcePalette: z5.boolean().optional(),
13751
+ enforcePalette: z6.boolean().optional(),
13525
13752
  /** Provider view name (PixelLab: low top-down, high top-down, side); empty clears. */
13526
- view: z5.string().max(64).nullable().optional(),
13753
+ view: z6.string().max(64).nullable().optional(),
13527
13754
  /** pixflux only; null clears the style's own value. */
13528
- noBackground: z5.boolean().nullable().optional()
13755
+ noBackground: z6.boolean().nullable().optional()
13529
13756
  }).strict().refine((patch) => Object.keys(patch).length > 0, { message: "nothing to change" })
13530
13757
  }).strict()
13531
13758
  ]);
@@ -13808,35 +14035,35 @@ async function detachHandEdit(loaded, spec, opts = {}) {
13808
14035
  });
13809
14036
  return { manifestSha256: result.sha256, changed: result.changed };
13810
14037
  }
13811
- var CompanionOutputSchema = z6.object({
14038
+ var CompanionOutputSchema = z7.object({
13812
14039
  /** Output role for a set member; null for a single image. */
13813
- role: z6.string().nullable(),
14040
+ role: z7.string().nullable(),
13814
14041
  /** sha256 of the generated file this member started from; null when it started from untracked art. */
13815
- basedOn: z6.string().regex(/^[0-9a-f]{64}$/).nullable(),
14042
+ basedOn: z7.string().regex(/^[0-9a-f]{64}$/).nullable(),
13816
14043
  /** sha256 of the member as saved, so a later change by another tool is visible. */
13817
- sha256: z6.string().regex(/^[0-9a-f]{64}$/)
14044
+ sha256: z7.string().regex(/^[0-9a-f]{64}$/)
13818
14045
  }).strict();
13819
- var CompanionV2Schema = z6.object({
13820
- version: z6.literal(2),
14046
+ var CompanionV2Schema = z7.object({
14047
+ version: z7.literal(2),
13821
14048
  /** Editor identity the bridge reported, e.g. `pixelorama@v1.2.2-stable`. */
13822
- editor: z6.string().min(1),
13823
- protocol: z6.number().int().positive(),
13824
- savedAt: z6.string().datetime(),
14049
+ editor: z7.string().min(1),
14050
+ protocol: z7.number().int().positive(),
14051
+ savedAt: z7.string().datetime(),
13825
14052
  /** Basename of the layered project file beside the edit, when one was kept. */
13826
- project: z6.string().min(1).nullable(),
14053
+ project: z7.string().min(1).nullable(),
13827
14054
  /** One entry per file, in output order. */
13828
- outputs: z6.array(CompanionOutputSchema).min(1)
14055
+ outputs: z7.array(CompanionOutputSchema).min(1)
13829
14056
  }).strict();
13830
- var CompanionV1Schema = z6.object({
13831
- version: z6.literal(1),
13832
- basedOn: z6.string().regex(/^[0-9a-f]{64}$/).nullable(),
13833
- sha256: z6.string().regex(/^[0-9a-f]{64}$/),
13834
- editor: z6.string().min(1),
13835
- protocol: z6.number().int().positive(),
13836
- savedAt: z6.string().datetime(),
13837
- project: z6.string().min(1).nullable()
14057
+ var CompanionV1Schema = z7.object({
14058
+ version: z7.literal(1),
14059
+ basedOn: z7.string().regex(/^[0-9a-f]{64}$/).nullable(),
14060
+ sha256: z7.string().regex(/^[0-9a-f]{64}$/),
14061
+ editor: z7.string().min(1),
14062
+ protocol: z7.number().int().positive(),
14063
+ savedAt: z7.string().datetime(),
14064
+ project: z7.string().min(1).nullable()
13838
14065
  }).strict();
13839
- var HandEditCompanionSchema = z6.union([CompanionV2Schema, CompanionV1Schema]).transform(
14066
+ var HandEditCompanionSchema = z7.union([CompanionV2Schema, CompanionV1Schema]).transform(
13840
14067
  (value) => value.version === 2 ? value : {
13841
14068
  version: 2,
13842
14069
  editor: value.editor,
@@ -13995,41 +14222,41 @@ import path28 from "path";
13995
14222
 
13996
14223
  // src/gallery/edit.ts
13997
14224
  import path26 from "path";
13998
- import { z as z7 } from "zod";
13999
- var Base64 = z7.string().regex(/^[A-Za-z0-9+/]*={0,2}$/).max(Math.ceil(MAX_HAND_EDIT_BYTES / 3) * 4);
14000
- var HandEditRequestSchema = z7.discriminatedUnion("action", [
14001
- z7.object({
14002
- action: z7.literal("start-edit"),
14003
- project: z7.string().min(1).optional(),
14004
- assetId: z7.string().min(1),
14005
- styleId: z7.string().min(1),
14006
- expectedSha256: z7.string().regex(/^[0-9a-f]{64}$/),
14225
+ import { z as z8 } from "zod";
14226
+ var Base64 = z8.string().regex(/^[A-Za-z0-9+/]*={0,2}$/).max(Math.ceil(MAX_HAND_EDIT_BYTES / 3) * 4);
14227
+ var HandEditRequestSchema = z8.discriminatedUnion("action", [
14228
+ z8.object({
14229
+ action: z8.literal("start-edit"),
14230
+ project: z8.string().min(1).optional(),
14231
+ assetId: z8.string().min(1),
14232
+ styleId: z8.string().min(1),
14233
+ expectedSha256: z8.string().regex(/^[0-9a-f]{64}$/),
14007
14234
  /** Also launch the author's editor on the file. */
14008
- open: z7.boolean().optional()
14235
+ open: z8.boolean().optional()
14009
14236
  }).strict(),
14010
- z7.object({
14011
- action: z7.literal("detach-edit"),
14012
- project: z7.string().min(1).optional(),
14013
- assetId: z7.string().min(1),
14014
- styleId: z7.string().min(1),
14015
- expectedSha256: z7.string().regex(/^[0-9a-f]{64}$/)
14237
+ z8.object({
14238
+ action: z8.literal("detach-edit"),
14239
+ project: z8.string().min(1).optional(),
14240
+ assetId: z8.string().min(1),
14241
+ styleId: z8.string().min(1),
14242
+ expectedSha256: z8.string().regex(/^[0-9a-f]{64}$/)
14016
14243
  }).strict(),
14017
14244
  /** From the in-browser editor: the page holds the bytes, the server validates and writes. */
14018
- z7.object({
14019
- action: z7.literal("save-edit"),
14020
- project: z7.string().min(1).optional(),
14021
- assetId: z7.string().min(1),
14022
- styleId: z7.string().min(1),
14023
- expectedSha256: z7.string().regex(/^[0-9a-f]{64}$/),
14245
+ z8.object({
14246
+ action: z8.literal("save-edit"),
14247
+ project: z8.string().min(1).optional(),
14248
+ assetId: z8.string().min(1),
14249
+ styleId: z8.string().min(1),
14250
+ expectedSha256: z8.string().regex(/^[0-9a-f]{64}$/),
14024
14251
  /** The flattened image; for a frame set, `frames` instead. */
14025
14252
  png: Base64.min(1).optional(),
14026
14253
  /** Every frame of a set, tagged with the role it was opened under (null for one added in the editor). */
14027
- frames: z7.array(z7.object({ role: z7.string().min(1).nullable(), png: Base64.min(1) }).strict()).min(1).max(256).optional(),
14254
+ frames: z8.array(z8.object({ role: z8.string().min(1).nullable(), png: Base64.min(1) }).strict()).min(1).max(256).optional(),
14028
14255
  /** The editor's layered project file, kept beside the edit. */
14029
14256
  pxo: Base64.optional(),
14030
14257
  /** Editor identity from the bridge's ready message. */
14031
- editor: z7.string().min(1).max(200),
14032
- protocol: z7.number().int().positive()
14258
+ editor: z8.string().min(1).max(200),
14259
+ protocol: z8.number().int().positive()
14033
14260
  }).strict()
14034
14261
  ]);
14035
14262
  function createGalleryEditHandler(opts) {
@@ -15213,7 +15440,7 @@ async function serveGallery(opts) {
15213
15440
 
15214
15441
  // src/gallery/generate.ts
15215
15442
  import { randomBytes as randomBytes2 } from "crypto";
15216
- import { z as z8 } from "zod";
15443
+ import { z as z9 } from "zod";
15217
15444
 
15218
15445
  // src/pipeline/poll.ts
15219
15446
  var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
@@ -15494,6 +15721,9 @@ async function submit(provider, loaded, items, lock, lockPath, opts = {}) {
15494
15721
  ...spec.revision.lastFrameSha256 ? { lastFrameSha256: spec.revision.lastFrameSha256 } : {},
15495
15722
  ...spec.revision.direction ? { direction: spec.revision.direction } : {},
15496
15723
  ...spec.revision.enhancePrompt == null ? {} : { enhancePrompt: spec.revision.enhancePrompt },
15724
+ ...spec.revision.keypointsSha256 ? { keypointsSha256: spec.revision.keypointsSha256 } : {},
15725
+ ...spec.revision.skeletonTemplate ? { skeletonTemplate: spec.revision.skeletonTemplate } : {},
15726
+ ...spec.revision.description ? { description: spec.revision.description } : {},
15497
15727
  ...spec.revision.engine ? { engine: spec.revision.engine } : {}
15498
15728
  } : null,
15499
15729
  outfit: spec.character?.kind === "outfit" && outfitParentEntry ? {
@@ -16310,26 +16540,26 @@ async function runPicker(provider, lock, lockPath, opts = {}) {
16310
16540
  }
16311
16541
 
16312
16542
  // src/gallery/generate.ts
16313
- var GenerateRequestSchema = z8.object({
16314
- project: z8.string().min(1).optional(),
16315
- keys: z8.array(z8.string().min(1)).min(1).max(500),
16543
+ var GenerateRequestSchema = z9.object({
16544
+ project: z9.string().min(1).optional(),
16545
+ keys: z9.array(z9.string().min(1)).min(1).max(500),
16316
16546
  /**
16317
16547
  * Regenerate work that is up to date, as `gen --force` does, and let the
16318
16548
  * fetch replace a locally modified or untracked file. With `restore`, put
16319
16549
  * the recorded bytes back over a modified file.
16320
16550
  */
16321
- force: z8.boolean().optional(),
16551
+ force: z9.boolean().optional(),
16322
16552
  /** Advance in-flight, review, or selected work without submitting anything. */
16323
- resume: z8.boolean().optional(),
16553
+ resume: z9.boolean().optional(),
16324
16554
  /** Re-download downloaded outputs and replace files whose object changed upstream. */
16325
- refresh: z8.boolean().optional(),
16555
+ refresh: z9.boolean().optional(),
16326
16556
  /** Put recorded outputs back on disk from the cache or the provider, without generating. */
16327
- restore: z8.boolean().optional(),
16557
+ restore: z9.boolean().optional(),
16328
16558
  /**
16329
16559
  * Bring a replaced generation back: 1-based position in the record's
16330
16560
  * history (newest first) or an output hash prefix. One key at a time.
16331
16561
  */
16332
- revert: z8.string().min(1).max(64).optional()
16562
+ revert: z9.string().min(1).max(64).optional()
16333
16563
  }).strict().refine((request) => [request.resume, request.refresh, request.restore, request.revert !== void 0].filter(Boolean).length <= 1, {
16334
16564
  message: "resume, refresh, restore, and revert are exclusive"
16335
16565
  }).refine((request) => request.revert === void 0 || request.keys.length === 1, { message: "revert takes one key" });
@@ -19215,11 +19445,11 @@ import { readFile as readFile28 } from "fs/promises";
19215
19445
  import path41 from "path";
19216
19446
  import { existsSync as existsSync30 } from "fs";
19217
19447
  import { readFile as readFile27 } from "fs/promises";
19218
- import { z as z9 } from "zod";
19448
+ import { z as z10 } from "zod";
19219
19449
  var SHA256_RE = /^[0-9a-f]{64}$/;
19220
19450
  var HEX_RE = /^#[0-9a-f]{6}$/;
19221
19451
  var MAX_REDMEAN_DISTANCE = 765;
19222
- var QualityPathSchema = z9.string().min(1).refine(
19452
+ var QualityPathSchema = z10.string().min(1).refine(
19223
19453
  (value) => !path41.posix.isAbsolute(value) && !value.includes("\\"),
19224
19454
  "expected a portable relative path"
19225
19455
  );
@@ -19233,53 +19463,53 @@ var DEFAULT_QUALITY_TOLERANCES = {
19233
19463
  maxEdgeContrastDrop: 0.03,
19234
19464
  maxIsolatedPixelIncrease: 5e-3
19235
19465
  };
19236
- var QualityToleranceSchema = z9.object({
19237
- requireExactHash: z9.boolean(),
19238
- maxColorCountIncrease: z9.number().int().min(0),
19239
- maxNewColors: z9.number().int().min(0),
19240
- maxTransparencyDelta: z9.number().min(0).max(1),
19241
- maxPartialAlphaIncrease: z9.number().min(0).max(1),
19242
- maxEdgeDensityDelta: z9.number().min(0).max(1),
19243
- maxEdgeContrastDrop: z9.number().min(0).max(1),
19244
- maxIsolatedPixelIncrease: z9.number().min(0).max(1)
19466
+ var QualityToleranceSchema = z10.object({
19467
+ requireExactHash: z10.boolean(),
19468
+ maxColorCountIncrease: z10.number().int().min(0),
19469
+ maxNewColors: z10.number().int().min(0),
19470
+ maxTransparencyDelta: z10.number().min(0).max(1),
19471
+ maxPartialAlphaIncrease: z10.number().min(0).max(1),
19472
+ maxEdgeDensityDelta: z10.number().min(0).max(1),
19473
+ maxEdgeContrastDrop: z10.number().min(0).max(1),
19474
+ maxIsolatedPixelIncrease: z10.number().min(0).max(1)
19245
19475
  }).strict();
19246
- var ImageQualityMetricsSchema = z9.object({
19247
- sha256: z9.string().regex(SHA256_RE),
19248
- width: z9.number().int().min(1),
19249
- height: z9.number().int().min(1),
19250
- colorCount: z9.number().int().min(0),
19251
- colors: z9.array(z9.string().regex(HEX_RE)),
19252
- transparency: z9.number().min(0).max(1),
19253
- partialAlpha: z9.number().min(0).max(1),
19254
- edgeDensity: z9.number().min(0).max(1),
19255
- meanEdgeContrast: z9.number().min(0).max(1),
19256
- isolatedPixelRatio: z9.number().min(0).max(1)
19476
+ var ImageQualityMetricsSchema = z10.object({
19477
+ sha256: z10.string().regex(SHA256_RE),
19478
+ width: z10.number().int().min(1),
19479
+ height: z10.number().int().min(1),
19480
+ colorCount: z10.number().int().min(0),
19481
+ colors: z10.array(z10.string().regex(HEX_RE)),
19482
+ transparency: z10.number().min(0).max(1),
19483
+ partialAlpha: z10.number().min(0).max(1),
19484
+ edgeDensity: z10.number().min(0).max(1),
19485
+ meanEdgeContrast: z10.number().min(0).max(1),
19486
+ isolatedPixelRatio: z10.number().min(0).max(1)
19257
19487
  }).strict();
19258
- var QualityBaselineCaseSchema = z9.object({
19259
- id: z9.string().regex(/^[a-z0-9][a-z0-9/_-]*$/),
19488
+ var QualityBaselineCaseSchema = z10.object({
19489
+ id: z10.string().regex(/^[a-z0-9][a-z0-9/_-]*$/),
19260
19490
  file: QualityPathSchema,
19261
- record: z9.object({
19491
+ record: z10.object({
19262
19492
  path: QualityPathSchema,
19263
- sha256: z9.string().regex(SHA256_RE)
19493
+ sha256: z10.string().regex(SHA256_RE)
19264
19494
  }).strict().optional(),
19265
19495
  expected: ImageQualityMetricsSchema,
19266
19496
  tolerances: QualityToleranceSchema
19267
19497
  }).strict();
19268
- var QualityBaselineSchema = z9.object({
19269
- $schema: z9.string().url().optional(),
19270
- format: z9.literal("pixelkiln-quality-baseline"),
19271
- schemaVersion: z9.literal(1),
19272
- cases: z9.array(QualityBaselineCaseSchema).min(1)
19498
+ var QualityBaselineSchema = z10.object({
19499
+ $schema: z10.string().url().optional(),
19500
+ format: z10.literal("pixelkiln-quality-baseline"),
19501
+ schemaVersion: z10.literal(1),
19502
+ cases: z10.array(QualityBaselineCaseSchema).min(1)
19273
19503
  }).strict().superRefine((baseline, context) => {
19274
19504
  const ids = baseline.cases.map((entry) => entry.id);
19275
19505
  if (new Set(ids).size !== ids.length) {
19276
- context.addIssue({ code: z9.ZodIssueCode.custom, path: ["cases"], message: "duplicate case id" });
19506
+ context.addIssue({ code: z10.ZodIssueCode.custom, path: ["cases"], message: "duplicate case id" });
19277
19507
  }
19278
19508
  });
19279
- var QualityInputSchema = z9.object({
19280
- id: z9.string().regex(/^[a-z0-9][a-z0-9/_-]*$/),
19281
- path: z9.string().min(1),
19282
- record: z9.string().min(1).optional(),
19509
+ var QualityInputSchema = z10.object({
19510
+ id: z10.string().regex(/^[a-z0-9][a-z0-9/_-]*$/),
19511
+ path: z10.string().min(1),
19512
+ record: z10.string().min(1).optional(),
19283
19513
  tolerances: QualityToleranceSchema.partial().optional()
19284
19514
  }).strict();
19285
19515
  function message4(error) {
@@ -19381,7 +19611,7 @@ async function measureImageQuality(file) {
19381
19611
  }
19382
19612
  }
19383
19613
  function resolveQualityInputs(raw, inputsFilePath) {
19384
- const parsed = z9.array(QualityInputSchema).min(1).safeParse(raw);
19614
+ const parsed = z10.array(QualityInputSchema).min(1).safeParse(raw);
19385
19615
  if (!parsed.success) {
19386
19616
  throw new Error(
19387
19617
  "--inputs must be a non-empty JSON array of quality cases:\n" + parsed.error.issues.map((issue) => ` ${issue.path.join(".") || "$"}: ${issue.message}`).join("\n")
@@ -19656,47 +19886,47 @@ import path43 from "path";
19656
19886
  import { existsSync as existsSync31 } from "fs";
19657
19887
  import { readdir as readdir3, readFile as readFile29 } from "fs/promises";
19658
19888
  import { fileURLToPath } from "url";
19659
- import { z as z10 } from "zod";
19889
+ import { z as z11 } from "zod";
19660
19890
  var SHA256_RE2 = /^[0-9a-f]{64}$/;
19661
19891
  var RECIPE_ID_RE = /^[a-z0-9][a-z0-9-]*\/[a-z0-9][a-z0-9-]*$/;
19662
19892
  var VERSION_RE = /^\d+\.\d+\.\d+$/;
19663
19893
  var RECIPE_FILE = "pixelkiln.recipe.json";
19664
19894
  var RECIPE_DIR_TOKEN = "{{recipeDir}}";
19665
- var PortablePathSchema = z10.string().min(1).refine((value) => {
19895
+ var PortablePathSchema = z11.string().min(1).refine((value) => {
19666
19896
  if (value.includes("\\") || path43.posix.isAbsolute(value)) return false;
19667
19897
  const parts = value.split("/");
19668
19898
  return !parts.includes("") && !parts.includes(".") && !parts.includes("..");
19669
19899
  }, "expected a portable relative path without '.', '..', or backslashes");
19670
- var RecipeBindingSchema = z10.object({
19671
- nodeId: z10.string().min(1),
19672
- input: z10.string().min(1)
19900
+ var RecipeBindingSchema = z11.object({
19901
+ nodeId: z11.string().min(1),
19902
+ input: z11.string().min(1)
19673
19903
  }).strict();
19674
- var RecipeSchema = z10.object({
19675
- $schema: z10.string().url().optional(),
19676
- format: z10.literal("pixelkiln-recipe"),
19677
- schemaVersion: z10.literal(1),
19678
- id: z10.string().regex(RECIPE_ID_RE, "expected provider/name"),
19679
- version: z10.string().regex(VERSION_RE, "expected x.y.z"),
19680
- provider: z10.string().regex(/^[a-z0-9][a-z0-9-]*$/),
19681
- summary: z10.string().min(1).max(240),
19682
- files: z10.array(z10.object({
19904
+ var RecipeSchema = z11.object({
19905
+ $schema: z11.string().url().optional(),
19906
+ format: z11.literal("pixelkiln-recipe"),
19907
+ schemaVersion: z11.literal(1),
19908
+ id: z11.string().regex(RECIPE_ID_RE, "expected provider/name"),
19909
+ version: z11.string().regex(VERSION_RE, "expected x.y.z"),
19910
+ provider: z11.string().regex(/^[a-z0-9][a-z0-9-]*$/),
19911
+ summary: z11.string().min(1).max(240),
19912
+ files: z11.array(z11.object({
19683
19913
  path: PortablePathSchema,
19684
- role: z10.enum(["workflow", "reference"]),
19685
- sha256: z10.string().regex(SHA256_RE2)
19914
+ role: z11.enum(["workflow", "reference"]),
19915
+ sha256: z11.string().regex(SHA256_RE2)
19686
19916
  }).strict()).default([]),
19687
- models: z10.array(z10.object({
19917
+ models: z11.array(z11.object({
19688
19918
  path: PortablePathSchema,
19689
- sha256: z10.string().regex(SHA256_RE2),
19690
- source: z10.string().url(),
19691
- license: z10.string().min(1)
19919
+ sha256: z11.string().regex(SHA256_RE2),
19920
+ source: z11.string().url(),
19921
+ license: z11.string().min(1)
19692
19922
  }).strict()).default([]),
19693
- styleId: z10.string().regex(/^[a-z0-9][a-z0-9-]*$/),
19923
+ styleId: z11.string().regex(/^[a-z0-9][a-z0-9-]*$/),
19694
19924
  style: StyleSchema,
19695
- workflow: z10.object({
19925
+ workflow: z11.object({
19696
19926
  path: PortablePathSchema,
19697
- outputNodeId: z10.string().min(1),
19698
- numImages: z10.number().int().min(1).max(16),
19699
- bindings: z10.object({
19927
+ outputNodeId: z11.string().min(1),
19928
+ numImages: z11.number().int().min(1).max(16),
19929
+ bindings: z11.object({
19700
19930
  prompt: RecipeBindingSchema,
19701
19931
  width: RecipeBindingSchema.optional(),
19702
19932
  height: RecipeBindingSchema.optional(),
@@ -19707,67 +19937,67 @@ var RecipeSchema = z10.object({
19707
19937
  strength: RecipeBindingSchema.optional()
19708
19938
  }).strict()
19709
19939
  }).strict().optional(),
19710
- quality: z10.object({
19711
- stage: z10.enum(["composition-source", "production-candidate"]),
19712
- workingCanvas: z10.object({
19713
- width: z10.number().int().min(16),
19714
- height: z10.number().int().min(16)
19940
+ quality: z11.object({
19941
+ stage: z11.enum(["composition-source", "production-candidate"]),
19942
+ workingCanvas: z11.object({
19943
+ width: z11.number().int().min(16),
19944
+ height: z11.number().int().min(16)
19715
19945
  }).strict(),
19716
- recommendedNativeSize: z10.object({
19717
- min: z10.number().int().min(1),
19718
- max: z10.number().int().min(1)
19946
+ recommendedNativeSize: z11.object({
19947
+ min: z11.number().int().min(1),
19948
+ max: z11.number().int().min(1)
19719
19949
  }).strict(),
19720
- paletteColors: z10.object({
19721
- min: z10.number().int().min(1),
19722
- max: z10.number().int().min(1)
19950
+ paletteColors: z11.object({
19951
+ min: z11.number().int().min(1),
19952
+ max: z11.number().int().min(1)
19723
19953
  }).strict(),
19724
- background: z10.enum(["opaque", "transparent", "full-bleed"]),
19725
- checks: z10.array(z10.enum([
19954
+ background: z11.enum(["opaque", "transparent", "full-bleed"]),
19955
+ checks: z11.array(z11.enum([
19726
19956
  "prompt-coverage",
19727
19957
  "native-grid",
19728
19958
  "final-palette",
19729
19959
  "alpha-edge",
19730
19960
  "human-1x"
19731
19961
  ])).min(1),
19732
- notes: z10.array(z10.string().min(1)).default([])
19962
+ notes: z11.array(z11.string().min(1)).default([])
19733
19963
  }).strict(),
19734
- integrity: z10.object({
19735
- algorithm: z10.literal("sha256"),
19736
- digest: z10.string().regex(SHA256_RE2)
19964
+ integrity: z11.object({
19965
+ algorithm: z11.literal("sha256"),
19966
+ digest: z11.string().regex(SHA256_RE2)
19737
19967
  }).strict()
19738
19968
  }).strict().superRefine((recipe, context) => {
19739
19969
  if (recipe.style.provider && recipe.style.provider !== recipe.provider) {
19740
19970
  context.addIssue({
19741
- code: z10.ZodIssueCode.custom,
19971
+ code: z11.ZodIssueCode.custom,
19742
19972
  path: ["style", "provider"],
19743
19973
  message: "must match the recipe provider"
19744
19974
  });
19745
19975
  }
19746
19976
  const filePaths = recipe.files.map((file) => file.path);
19747
19977
  if (new Set(filePaths).size !== filePaths.length) {
19748
- context.addIssue({ code: z10.ZodIssueCode.custom, path: ["files"], message: "duplicate file path" });
19978
+ context.addIssue({ code: z11.ZodIssueCode.custom, path: ["files"], message: "duplicate file path" });
19749
19979
  }
19750
19980
  const modelPaths = recipe.models.map((model) => model.path);
19751
19981
  if (new Set(modelPaths).size !== modelPaths.length) {
19752
- context.addIssue({ code: z10.ZodIssueCode.custom, path: ["models"], message: "duplicate model path" });
19982
+ context.addIssue({ code: z11.ZodIssueCode.custom, path: ["models"], message: "duplicate model path" });
19753
19983
  }
19754
19984
  if (filePaths.includes(RECIPE_FILE)) {
19755
19985
  context.addIssue({
19756
- code: z10.ZodIssueCode.custom,
19986
+ code: z11.ZodIssueCode.custom,
19757
19987
  path: ["files"],
19758
19988
  message: `${RECIPE_FILE} cannot hash itself`
19759
19989
  });
19760
19990
  }
19761
19991
  if (recipe.quality.recommendedNativeSize.min > recipe.quality.recommendedNativeSize.max) {
19762
19992
  context.addIssue({
19763
- code: z10.ZodIssueCode.custom,
19993
+ code: z11.ZodIssueCode.custom,
19764
19994
  path: ["quality", "recommendedNativeSize"],
19765
19995
  message: "min cannot exceed max"
19766
19996
  });
19767
19997
  }
19768
19998
  if (recipe.quality.paletteColors.min > recipe.quality.paletteColors.max) {
19769
19999
  context.addIssue({
19770
- code: z10.ZodIssueCode.custom,
20000
+ code: z11.ZodIssueCode.custom,
19771
20001
  path: ["quality", "paletteColors"],
19772
20002
  message: "min cannot exceed max"
19773
20003
  });
@@ -19776,7 +20006,7 @@ var RecipeSchema = z10.object({
19776
20006
  (file) => file.path === recipe.workflow.path && file.role === "workflow"
19777
20007
  )) {
19778
20008
  context.addIssue({
19779
- code: z10.ZodIssueCode.custom,
20009
+ code: z11.ZodIssueCode.custom,
19780
20010
  path: ["workflow", "path"],
19781
20011
  message: "must name a file whose role is workflow"
19782
20012
  });
@@ -19784,7 +20014,7 @@ var RecipeSchema = z10.object({
19784
20014
  if (recipe.provider === "comfyui") {
19785
20015
  if (!recipe.workflow) {
19786
20016
  context.addIssue({
19787
- code: z10.ZodIssueCode.custom,
20017
+ code: z11.ZodIssueCode.custom,
19788
20018
  path: ["workflow"],
19789
20019
  message: "ComfyUI recipes require workflow metadata"
19790
20020
  });
@@ -19793,7 +20023,7 @@ var RecipeSchema = z10.object({
19793
20023
  const options = recipe.style.providerOptions.comfyui;
19794
20024
  if (!options || typeof options !== "object" || Array.isArray(options)) {
19795
20025
  context.addIssue({
19796
- code: z10.ZodIssueCode.custom,
20026
+ code: z11.ZodIssueCode.custom,
19797
20027
  path: ["style", "providerOptions", "comfyui"],
19798
20028
  message: "ComfyUI recipes require ComfyUI provider options"
19799
20029
  });
@@ -19807,7 +20037,7 @@ var RecipeSchema = z10.object({
19807
20037
  };
19808
20038
  if (JSON.stringify(canonical3(options)) !== JSON.stringify(canonical3(expected))) {
19809
20039
  context.addIssue({
19810
- code: z10.ZodIssueCode.custom,
20040
+ code: z11.ZodIssueCode.custom,
19811
20041
  path: ["style", "providerOptions", "comfyui"],
19812
20042
  message: "must match workflow metadata and use {{recipeDir}} for workflowFile"
19813
20043
  });
@@ -20226,6 +20456,30 @@ async function runRefine(args) {
20226
20456
  if (result.failed) process.exitCode = 1;
20227
20457
  }
20228
20458
 
20459
+ // src/cli/commands/skeleton.ts
20460
+ import path46 from "path";
20461
+ import { readFile as readFile30, writeFile as writeFile15 } from "fs/promises";
20462
+ async function runEstimateSkeleton(args) {
20463
+ const image = args.target;
20464
+ if (!image) throw new UsageError("estimate-skeleton needs an image path: pixelkiln estimate-skeleton <image> [--out keypoints.json]");
20465
+ loadEnvFiles(path46.dirname(path46.resolve(args.manifest)));
20466
+ loadEnvFiles(process.cwd());
20467
+ const bytes = await readFile30(path46.resolve(image));
20468
+ const metadata = imageMetadata(bytes);
20469
+ if (!metadata) throw new UsageError(`${image} is not a readable PNG or JPEG`);
20470
+ const client2 = clientFromEnv();
20471
+ const res = await client2.estimateSkeleton({
20472
+ image: { base64: bytes.toString("base64"), format: metadata.format }
20473
+ });
20474
+ const json = JSON.stringify(res.keypoints, null, 2);
20475
+ if (args.out) {
20476
+ await writeFile15(path46.resolve(args.out), json + "\n");
20477
+ log(` wrote ${res.keypoints.length} keypoints to ${args.out}`);
20478
+ } else {
20479
+ log(json);
20480
+ }
20481
+ }
20482
+
20229
20483
  // src/cli/commands/tools.ts
20230
20484
  async function runTools(args) {
20231
20485
  const mb = (bytes) => bytes < 1e6 ? `${(bytes / 1e3).toFixed(0)} kB` : `${(bytes / 1e6).toFixed(1)} MB`;
@@ -20268,15 +20522,15 @@ async function runTools(args) {
20268
20522
  }
20269
20523
 
20270
20524
  // src/cli/commands/workspace.ts
20271
- import path46 from "path";
20525
+ import path47 from "path";
20272
20526
  import { existsSync as existsSync32 } from "fs";
20273
20527
  async function runWorkspace(args) {
20274
- const workspacePath = path46.resolve(args.workspace ?? "pixelkiln.workspace.json");
20275
- const dir = path46.dirname(workspacePath);
20528
+ const workspacePath = path47.resolve(args.workspace ?? "pixelkiln.workspace.json");
20529
+ const dir = path47.dirname(workspacePath);
20276
20530
  if (args.subcommand === "add") {
20277
- const manifestPath = path46.resolve(args.target);
20531
+ const manifestPath = path47.resolve(args.target);
20278
20532
  const loadedTarget = await loadManifest(manifestPath);
20279
- const lockPath = args.explicitLock ? path46.resolve(args.explicitLock) : path46.join(path46.dirname(manifestPath), "pixelkiln.lock.json");
20533
+ const lockPath = args.explicitLock ? path47.resolve(args.explicitLock) : path47.join(path47.dirname(manifestPath), "pixelkiln.lock.json");
20280
20534
  const ws = await loadWorkspace(workspacePath);
20281
20535
  const id = args.name ?? loadedTarget.manifest.name;
20282
20536
  if (ws.projects.some((p) => p.id === id)) {
@@ -20296,7 +20550,7 @@ async function runWorkspace(args) {
20296
20550
  ...args.account ? { account: args.account } : {}
20297
20551
  };
20298
20552
  await saveWorkspace(workspacePath, { version: 1, projects: [...ws.projects, project] });
20299
- log(` registered "${id}" in ${path46.relative(process.cwd(), workspacePath)}`);
20553
+ log(` registered "${id}" in ${path47.relative(process.cwd(), workspacePath)}`);
20300
20554
  log(` manifest: ${project.manifest}`);
20301
20555
  log(` lock: ${project.lock}`);
20302
20556
  if (!existsSync32(lockPath)) {
@@ -20308,7 +20562,7 @@ async function runWorkspace(args) {
20308
20562
  }
20309
20563
  if (args.subcommand === "remove") {
20310
20564
  const ws = await loadWorkspace(workspacePath);
20311
- const resolvedTarget = path46.resolve(args.target);
20565
+ const resolvedTarget = path47.resolve(args.target);
20312
20566
  const match = ws.projects.find(
20313
20567
  (p) => p.id === args.target || resolveProject(dir, p).manifestPath === resolvedTarget
20314
20568
  );
@@ -20319,7 +20573,7 @@ async function runWorkspace(args) {
20319
20573
  version: 1,
20320
20574
  projects: ws.projects.filter((p) => p !== match)
20321
20575
  });
20322
- log(` removed "${match.id}" from ${path46.relative(process.cwd(), workspacePath)}`);
20576
+ log(` removed "${match.id}" from ${path47.relative(process.cwd(), workspacePath)}`);
20323
20577
  return;
20324
20578
  }
20325
20579
  if (args.subcommand === "list") {
@@ -20331,9 +20585,9 @@ async function runWorkspace(args) {
20331
20585
  if (args.json) {
20332
20586
  log(JSON.stringify({ version: 1, workspace: workspacePath, projects: ws.projects, diagnostics }, null, 2));
20333
20587
  } else if (!ws.projects.length) {
20334
- log(` no projects registered in ${path46.relative(process.cwd(), workspacePath)}`);
20588
+ log(` no projects registered in ${path47.relative(process.cwd(), workspacePath)}`);
20335
20589
  } else {
20336
- log(` ${ws.projects.length} project(s) in ${path46.relative(process.cwd(), workspacePath)}:`);
20590
+ log(` ${ws.projects.length} project(s) in ${path47.relative(process.cwd(), workspacePath)}:`);
20337
20591
  for (const p of ws.projects) {
20338
20592
  log(` ${p.id.padEnd(24)} ${p.manifest.padEnd(40)} (${p.provider}${p.account ? `, ${p.account}` : ""})`);
20339
20593
  }
@@ -20351,7 +20605,7 @@ async function runWorkspace(args) {
20351
20605
  if (args.json) {
20352
20606
  log(JSON.stringify({ ...report, workspace: workspacePath }, null, 2));
20353
20607
  } else {
20354
- log(` workspace: ${path46.relative(process.cwd(), workspacePath)}`);
20608
+ log(` workspace: ${path47.relative(process.cwd(), workspacePath)}`);
20355
20609
  for (const p of report.projects) {
20356
20610
  if (p.error) {
20357
20611
  log(`
@@ -20406,7 +20660,7 @@ async function runVersion() {
20406
20660
  for (const up of ["../package.json", "../../package.json"]) {
20407
20661
  const url = new URL(up, import.meta.url);
20408
20662
  if (!existsSync33(url)) continue;
20409
- const pkg = JSON.parse(await readFile30(url, "utf8"));
20663
+ const pkg = JSON.parse(await readFile31(url, "utf8"));
20410
20664
  if (pkg.name === "pixelkiln") {
20411
20665
  log(`${pkg.name} ${pkg.version}`);
20412
20666
  return;
@@ -20450,6 +20704,7 @@ var RUNNERS = {
20450
20704
  refine: runRefine,
20451
20705
  recipe: runRecipe,
20452
20706
  workspace: runWorkspace,
20707
+ "estimate-skeleton": runEstimateSkeleton,
20453
20708
  unzoom: runUnzoom,
20454
20709
  font: runFont
20455
20710
  };