pixelkiln 0.66.0 → 0.68.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")));
@@ -679,6 +689,11 @@ function specHash(spec, styleImageHashes, providerOptionIdentity = spec.provider
679
689
  uiPieces: spec.uiPieces,
680
690
  uiElements: spec.uiElements,
681
691
  uiColorPalette: spec.uiColorPalette,
692
+ // Scene and mask enter as their bytes' hashes, never their paths.
693
+ scene: spec.scene ? {
694
+ image: spec.scene.sha256,
695
+ placement: spec.scene.placement.type === "mask" ? { type: "mask", mask: spec.scene.placement.sha256 } : spec.scene.placement
696
+ } : void 0,
682
697
  styleTraits: spec.styleTraits,
683
698
  styleImages: styleImageHashes,
684
699
  // A character family: the engine and rotations of a base, the parent's
@@ -757,6 +772,9 @@ function specHash(spec, styleImageHashes, providerOptionIdentity = spec.provider
757
772
  lastFrameSha256: spec.revision.lastFrameSha256,
758
773
  direction: spec.revision.direction,
759
774
  enhancePrompt: spec.revision.enhancePrompt,
775
+ keypointsSha256: spec.revision.keypointsSha256,
776
+ skeletonTemplate: spec.revision.skeletonTemplate,
777
+ description: spec.revision.description,
760
778
  engine: spec.revision.engine
761
779
  } : void 0
762
780
  })
@@ -785,6 +803,7 @@ var RevisionModeSchema = z.enum([
785
803
  "correct-pixelart",
786
804
  "animate",
787
805
  "animate-pixminimax",
806
+ "animate-skeleton",
788
807
  "interpolate",
789
808
  "edit-animation"
790
809
  ]);
@@ -973,6 +992,16 @@ var CharacterPortraitSchema = z.object({
973
992
  of: z.string().min(1),
974
993
  size: CharacterPortraitSizeSchema.default(64)
975
994
  }).strict();
995
+ var MapScenePlacementSchema = z.union([
996
+ z.object({ oval: z.number().min(0.05).max(0.95) }).strict(),
997
+ z.object({ rectangle: z.number().min(0.05).max(0.95) }).strict(),
998
+ z.object({ mask: z.string().min(1) }).strict()
999
+ ]);
1000
+ var MapSceneSchema = z.object({
1001
+ /** Manifest-relative PNG of the scene. Any earlier asset's output file works. */
1002
+ image: z.string().min(1),
1003
+ placement: MapScenePlacementSchema.default({ oval: 0.3 })
1004
+ }).strict();
976
1005
  var CharacterOutfitSchema = z.object({
977
1006
  /** The loop (a character animation asset) to re-clothe, in the same style. */
978
1007
  of: z.string().min(1),
@@ -1103,6 +1132,30 @@ var RevisionSchema = z.object({
1103
1132
  direction: CharacterDirectionSchema.optional(),
1104
1133
  /** `animate`/`animate-pixminimax` only: let PixelLab expand the action into a fuller motion description first. */
1105
1134
  enhancePrompt: z.boolean().optional(),
1135
+ /**
1136
+ * `animate-skeleton` only: manifest-relative path to a committed
1137
+ * `SkeletonSet` JSON file (see `src/skeleton.ts`) — the pose the parent
1138
+ * image is already in, plus 3-15 per-frame poses for the motion.
1139
+ * Authored via `pixelkiln estimate-skeleton`, then hand-tweaked; never
1140
+ * inline in the manifest, the same reasoning `mask`/`lastFrame` are
1141
+ * committed files rather than inline pixel data.
1142
+ */
1143
+ keypointsFile: z.string().min(1).optional(),
1144
+ /**
1145
+ * `animate-skeleton` only: the body a joint's missing `depth` is taken
1146
+ * from. PixelLab's default is `mannequin`; free string like `character`'s
1147
+ * own `template` field, validated at the provider layer against the
1148
+ * same `mannequin`/`bear`/`cat`/`dog`/`horse`/`lion` vocabulary.
1149
+ */
1150
+ skeletonTemplate: z.string().min(1).optional(),
1151
+ /**
1152
+ * `animate-skeleton` only: what the character *looks like*, a noun
1153
+ * phrase (colours, clothing, held items) — distinct from the asset's
1154
+ * own `prompt`, which this mode sends as the motion's short label
1155
+ * (PixelLab's `action`), the same `prompt`-as-motion convention
1156
+ * `animate`/`animate-pixminimax` already use.
1157
+ */
1158
+ description: z.string().min(1).optional(),
1106
1159
  /**
1107
1160
  * `image-to-image`/`inpaint` only: which provider engine draws the
1108
1161
  * edit. `pro-flash` selects PixelLab's Pro Flash edit and inpaint tier
@@ -1193,18 +1246,41 @@ var RevisionSchema = z.object({
1193
1246
  path: ["fps"]
1194
1247
  });
1195
1248
  }
1196
- if (revision.direction !== void 0 && revision.mode !== "animate-pixminimax") {
1249
+ if (revision.direction !== void 0 && revision.mode !== "animate-pixminimax" && revision.mode !== "animate-skeleton") {
1250
+ context.addIssue({
1251
+ code: z.ZodIssueCode.custom,
1252
+ message: "direction applies to animate-pixminimax/animate-skeleton revisions only",
1253
+ path: ["direction"]
1254
+ });
1255
+ }
1256
+ if (revision.mode === "animate-skeleton" && revision.direction === void 0) {
1197
1257
  context.addIssue({
1198
1258
  code: z.ZodIssueCode.custom,
1199
- message: "direction applies to animate-pixminimax revisions only",
1259
+ message: "animate-skeleton revisions require a direction",
1200
1260
  path: ["direction"]
1201
1261
  });
1202
1262
  }
1203
1263
  if (revision.frames !== void 0 && revision.frames % 2 !== 0) {
1204
1264
  context.addIssue({ code: z.ZodIssueCode.custom, message: "frames must be even", path: ["frames"] });
1205
1265
  }
1266
+ if (revision.mode === "animate-skeleton" && !revision.keypointsFile) {
1267
+ context.addIssue({
1268
+ code: z.ZodIssueCode.custom,
1269
+ message: "animate-skeleton revisions require keypointsFile",
1270
+ path: ["keypointsFile"]
1271
+ });
1272
+ }
1273
+ for (const field of ["keypointsFile", "skeletonTemplate", "description"]) {
1274
+ if (revision[field] !== void 0 && revision.mode !== "animate-skeleton") {
1275
+ context.addIssue({
1276
+ code: z.ZodIssueCode.custom,
1277
+ message: `${field} applies to animate-skeleton revisions only`,
1278
+ path: [field]
1279
+ });
1280
+ }
1281
+ }
1206
1282
  });
1207
- var FRAME_SET_REVISION_MODES = ["animate", "animate-pixminimax", "interpolate", "edit-animation"];
1283
+ var FRAME_SET_REVISION_MODES = ["animate", "animate-pixminimax", "animate-skeleton", "interpolate", "edit-animation"];
1208
1284
  var MEMBER_SET_REVISION_MODES = ["reduce-colors", "correct-pixelart", "edit-animation"];
1209
1285
  var StyleObjectSchema = z.object({
1210
1286
  /** Generation backend for this style. Omit to inherit the manifest default. */
@@ -1620,6 +1696,8 @@ var AssetSchema = z.object({
1620
1696
  portrait: CharacterPortraitSchema.optional(),
1621
1697
  /** `character` styles: this asset re-clothes an existing loop's frames with a reference outfit. */
1622
1698
  outfit: CharacterOutfitSchema.optional(),
1699
+ /** `map` styles only: draw this object in the style of a scene image. See `MapSceneSchema`. */
1700
+ scene: MapSceneSchema.optional(),
1623
1701
  /** `character` styles, `standard` humanoid bases: this character's proportions, over the style's. */
1624
1702
  proportions: CharacterProportionsSchema.optional(),
1625
1703
  /**
@@ -1696,7 +1774,7 @@ var AssetSchema = z.object({
1696
1774
  path: ["revision"]
1697
1775
  });
1698
1776
  }
1699
- const shapes = [asset.revision && "revision", asset.state && "state", asset.animation && "animation", asset.mirror && "mirror", asset.batch && "batch", asset.portrait && "portrait", asset.outfit && "outfit"].filter(Boolean);
1777
+ const shapes = [asset.revision && "revision", asset.state && "state", asset.animation && "animation", asset.mirror && "mirror", asset.batch && "batch", asset.portrait && "portrait", asset.outfit && "outfit", asset.scene && "scene"].filter(Boolean);
1700
1778
  if (shapes.length > 1) {
1701
1779
  context.addIssue({
1702
1780
  code: z.ZodIssueCode.custom,
@@ -1836,11 +1914,16 @@ var LockEntrySchema = z.object({
1836
1914
  paletteImageSha256: z.string().regex(/^[0-9a-f]{64}$/).optional(),
1837
1915
  dithering: RevisionDitheringSchema.optional(),
1838
1916
  ditheringStrength: z.number().min(0).max(10).optional(),
1839
- frames: z.number().int().min(4).max(40).optional(),
1917
+ // 4-40 for animate/animate-pixminimax; animate-skeleton's own 3-15
1918
+ // range extends the floor down to 3, hence min(3) here rather than 4.
1919
+ frames: z.number().int().min(3).max(40).optional(),
1840
1920
  fps: z.number().int().min(1).max(60).optional(),
1841
1921
  lastFrameSha256: z.string().regex(/^[0-9a-f]{64}$/).optional(),
1842
1922
  direction: CharacterDirectionSchema.optional(),
1843
1923
  enhancePrompt: z.boolean().optional(),
1924
+ keypointsSha256: z.string().regex(/^[0-9a-f]{64}$/).optional(),
1925
+ skeletonTemplate: z.string().min(1).optional(),
1926
+ description: z.string().min(1).optional(),
1844
1927
  engine: z.enum(["pro-flash"]).optional()
1845
1928
  }).strict().nullable().default(null),
1846
1929
  /** For a mirror: the asset it flips and a hash over that asset's output hashes when this was made. */
@@ -4146,6 +4229,7 @@ async function inspectRevisionReadiness(spec, lock) {
4146
4229
  if (spec.character?.parentSpec) return inspectCharacterParent(spec, lock, /* @__PURE__ */ new Set());
4147
4230
  if (spec.character?.styleAnchor) return inspectStyleAnchor(spec, lock, /* @__PURE__ */ new Set());
4148
4231
  if (spec.objectPro?.parentSpec) return inspectObjectProParent(spec, lock, /* @__PURE__ */ new Set());
4232
+ if (spec.scene) return inspectScene(spec);
4149
4233
  if (!spec.revision) return null;
4150
4234
  return inspectRevision(spec, lock, /* @__PURE__ */ new Set());
4151
4235
  }
@@ -4163,6 +4247,16 @@ async function inspectStyleAnchor(spec, lock, seen) {
4163
4247
  }
4164
4248
  return { ready: true, reason: `${label} is current` };
4165
4249
  }
4250
+ async function inspectScene(spec) {
4251
+ const scene = spec.scene;
4252
+ const files = [{ label: "scene image", file: scene.file, sha256: scene.sha256 }];
4253
+ if (scene.placement.type === "mask") files.push({ label: "scene mask", file: scene.placement.file, sha256: scene.placement.sha256 });
4254
+ for (const { label, file, sha256: sha2562 } of files) {
4255
+ if (!sha2562 || !existsSync4(file)) return { ready: false, reason: `${label} does not exist yet: ${file}` };
4256
+ if (await sha256File(file) !== sha2562) return { ready: false, reason: `${label} changed after the manifest was resolved` };
4257
+ }
4258
+ return { ready: true, reason: "scene is current" };
4259
+ }
4166
4260
  async function inspectMirror(spec, lock, seen) {
4167
4261
  const source = spec.mirror.sourceSpec;
4168
4262
  const label = `mirror source ${spec.styleId}/${spec.mirror.sourceAssetId}`;
@@ -4993,11 +5087,12 @@ var ComfyUIProvider = class _ComfyUIProvider {
4993
5087
  /**
4994
5088
  * Everything else resolves generically to whatever the user's own
4995
5089
  * 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.
5090
+ * (`animate`, `animate-pixminimax`, `animate-skeleton`, `interpolate`,
5091
+ * `edit-animation`) are the exception: they produce an ordered frame set,
5092
+ * and this adapter's revision path always writes a single output image
5093
+ * (see `submit`/`fetch` below) — a real structural gap, not a missing
5094
+ * binding, so they are rejected here rather than only failing once
5095
+ * `validate` runs.
5001
5096
  */
5002
5097
  supportsRevision(mode2) {
5003
5098
  return !FRAME_SET_REVISION_MODES.includes(mode2);
@@ -5657,262 +5752,291 @@ import os from "os";
5657
5752
  import path12 from "path";
5658
5753
 
5659
5754
  // src/client.ts
5755
+ import { z as z3 } from "zod";
5756
+
5757
+ // src/skeleton.ts
5660
5758
  import { z as z2 } from "zod";
5759
+ var SkeletonKeypointSchema = z2.object({
5760
+ label: z2.string().min(1),
5761
+ x: z2.number().min(0).max(1),
5762
+ y: z2.number().min(0).max(1),
5763
+ z_index: z2.number(),
5764
+ depth: z2.number().optional()
5765
+ }).strict();
5766
+ var SkeletonFrameSchema = z2.array(SkeletonKeypointSchema).length(18);
5767
+ var SkeletonSetSchema = z2.object({
5768
+ firstFrameKeypoints: SkeletonFrameSchema,
5769
+ frames: z2.array(SkeletonFrameSchema).min(3).max(15)
5770
+ }).strict();
5771
+ function parseSkeletonSet(json, file) {
5772
+ const result = SkeletonSetSchema.safeParse(json);
5773
+ if (!result.success) {
5774
+ const issues = result.error.issues.slice(0, 4).map((issue) => `${issue.path.join(".") || "root"}: ${issue.message}`).join("; ");
5775
+ throw new Error(`revision keypoints file ${file} does not match the expected shape: ${issues}`);
5776
+ }
5777
+ return result.data;
5778
+ }
5779
+
5780
+ // src/client.ts
5661
5781
  var BASE = process.env.PIXELLAB_API_BASE ?? "https://api.pixellab.ai/v2";
5662
5782
  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()
5783
+ var BalanceResponseSchema = z3.object({
5784
+ credits: z3.object({ usd: z3.number() }).passthrough(),
5785
+ subscription: z3.object({
5786
+ generations: z3.number(),
5787
+ total: z3.number(),
5788
+ plan: z3.string().nullable().optional()
5669
5789
  }).passthrough()
5670
5790
  }).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)
5791
+ var ObjectSubmitSchema = z3.object({
5792
+ background_job_id: z3.string().min(1),
5793
+ object_id: z3.string().min(1),
5794
+ status: z3.string().default("queued"),
5795
+ n_frames: z3.number().int().min(0)
5676
5796
  }).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")
5797
+ var MapSubmitSchema = z3.object({
5798
+ background_job_id: z3.string().min(1),
5799
+ object_id: z3.string().min(1),
5800
+ status: z3.string().default("processing")
5681
5801
  }).passthrough();
5682
- var UsageSchema = z2.object({
5683
- type: z2.string().optional(),
5684
- usd: z2.number().nullable().optional(),
5685
- generations: z2.number().nullable().optional()
5802
+ var UsageSchema = z3.object({
5803
+ type: z3.string().optional(),
5804
+ usd: z3.number().nullable().optional(),
5805
+ generations: z3.number().nullable().optional()
5686
5806
  }).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")
5807
+ var TilesSubmitSchema = z3.object({
5808
+ tile_id: z3.string().min(1),
5809
+ background_job_id: z3.string().min(1),
5810
+ status: z3.literal("processing").default("processing")
5691
5811
  }).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()
5812
+ var TilesProSchema = z3.object({
5813
+ storage_urls: z3.record(z3.string()),
5814
+ kind: z3.string().nullable().default(null),
5815
+ tile_rules: z3.record(z3.unknown()).nullable().optional()
5696
5816
  }).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")
5817
+ var TilesetSubmitSchema = z3.object({
5818
+ tileset_id: z3.string().min(1),
5819
+ background_job_id: z3.string().min(1),
5820
+ status: z3.literal("processing").default("processing")
5701
5821
  }).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())
5822
+ var TilesetTileSchema = z3.object({
5823
+ id: z3.string().min(1),
5824
+ name: z3.string(),
5825
+ image: z3.object({ base64: z3.string().min(1), format: z3.string().default("png") }).passthrough(),
5826
+ corners: z3.object({ NW: z3.string(), NE: z3.string(), SW: z3.string(), SE: z3.string() }).passthrough(),
5827
+ pattern_4x4: z3.object({
5828
+ row_0: z3.array(z3.number()),
5829
+ row_1: z3.array(z3.number()),
5830
+ row_2: z3.array(z3.number()),
5831
+ row_3: z3.array(z3.number())
5712
5832
  }).passthrough()
5713
5833
  }).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)
5834
+ var TilesetGetSchema = z3.object({
5835
+ tileset: z3.object({
5836
+ total_tiles: z3.number().int().min(1),
5837
+ tile_size: z3.object({ width: z3.number().int().positive(), height: z3.number().int().positive() }).passthrough(),
5838
+ terrain_types: z3.array(z3.string()),
5839
+ tiles: z3.array(TilesetTileSchema).min(1)
5720
5840
  }).passthrough(),
5721
5841
  usage: UsageSchema
5722
5842
  }).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")
5843
+ var IsometricTileSubmitSchema = z3.object({
5844
+ tile_id: z3.string().min(1),
5845
+ background_job_id: z3.string().min(1),
5846
+ status: z3.literal("processing").default("processing")
5727
5847
  }).passthrough();
5728
- var IsometricTileGetSchema = z2.object({
5729
- image: z2.object({ base64: z2.string().min(1), format: z2.string().default("png") }).passthrough(),
5848
+ var IsometricTileGetSchema = z3.object({
5849
+ image: z3.object({ base64: z3.string().min(1), format: z3.string().default("png") }).passthrough(),
5730
5850
  usage: UsageSchema
5731
5851
  }).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"),
5852
+ var UiAssetSubmitSchema = z3.object({
5853
+ ui_asset_id: z3.string().min(1),
5854
+ background_job_id: z3.string().min(1),
5855
+ status: z3.string().default("processing"),
5736
5856
  usage: UsageSchema
5737
5857
  }).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()
5858
+ var UiAssetGetSchema = z3.object({
5859
+ id: z3.string().min(1),
5860
+ status: z3.string().nullable().default(null),
5861
+ image_url: z3.string().nullable().optional(),
5862
+ progress_percent: z3.number().nullable().optional(),
5863
+ eta_seconds: z3.number().nullable().optional()
5744
5864
  }).passthrough();
5745
- var RevisionJobSubmitSchema = z2.object({
5746
- background_job_id: z2.string().min(1),
5747
- status: z2.string().default("processing")
5865
+ var RevisionJobSubmitSchema = z3.object({
5866
+ background_job_id: z3.string().min(1),
5867
+ status: z3.string().default("processing")
5748
5868
  }).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()
5869
+ var ObjectAnimationGroupSchema = z3.object({
5870
+ animation_group_id: z3.string().min(1),
5871
+ display_name: z3.string().nullable().default(null),
5872
+ description: z3.string().default(""),
5873
+ frame_count: z3.number().int().default(0),
5874
+ directions: z3.array(
5875
+ z3.object({
5876
+ direction: z3.string(),
5877
+ storage_urls: z3.record(z3.unknown()).default({}),
5878
+ created_at: z3.string()
5759
5879
  }).passthrough()
5760
5880
  ).default([])
5761
5881
  }).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([])
5882
+ var PixelLabObjectSchema = z3.object({
5883
+ id: z3.string().min(1),
5884
+ name: z3.string().nullable().default(null),
5885
+ state_name: z3.string().nullable().optional(),
5886
+ prompt: z3.string().default(""),
5887
+ size: z3.object({ width: z3.number().int().positive(), height: z3.number().int().positive() }),
5888
+ directions: z3.number().default(0),
5889
+ created_at: z3.string(),
5890
+ view: z3.string().nullable().default(null),
5891
+ preview_url: z3.string().nullable().optional(),
5892
+ rotation_urls: z3.record(z3.string().nullable()).nullable().optional(),
5893
+ frame_urls: z3.array(z3.string()).nullable().optional(),
5894
+ tags: z3.array(z3.string()).default([]),
5895
+ status: z3.string().nullable().default(null),
5896
+ progress_percent: z3.number().nullable().optional(),
5897
+ eta_seconds: z3.number().nullable().optional(),
5898
+ group_id: z3.string().nullable().optional(),
5899
+ animations: z3.array(ObjectAnimationGroupSchema).default([])
5900
+ }).passthrough();
5901
+ var MapObjectSchema = z3.object({
5902
+ object_id: z3.string().min(1),
5903
+ status: z3.string(),
5904
+ description: z3.string().nullable().default(null),
5905
+ width: z3.number().nullable().default(null),
5906
+ height: z3.number().nullable().default(null),
5907
+ download_url: z3.string().nullable().default(null)
5780
5908
  }).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)
5909
+ var ObjectListSchema = z3.object({ objects: z3.array(PixelLabObjectSchema), total: z3.number().int().min(0) }).passthrough();
5910
+ var PixfluxResponseSchema = z3.object({ image: z3.object({ base64: z3.string().min(1) }).passthrough(), usage: z3.unknown().optional() }).passthrough();
5911
+ var ReduceColorsResponseSchema = z3.object({
5912
+ images: z3.array(z3.object({ base64: z3.string().min(1) }).passthrough()).min(1),
5913
+ palette: z3.object({ base64: z3.string().min(1) }).passthrough(),
5914
+ n_colors: z3.number().int(),
5915
+ usage: z3.unknown().optional()
5788
5916
  }).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()
5917
+ var CorrectPixelartResponseSchema = z3.object({
5918
+ images: z3.array(z3.object({ base64: z3.string().min(1) }).passthrough()).min(1),
5919
+ usage: z3.unknown().optional()
5796
5920
  }).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()
5921
+ var ProFlashImageSubmitSchema = z3.object({
5922
+ background_job_id: z3.string().min(1),
5923
+ image_id: z3.string().min(1),
5924
+ source_image_id: z3.string().min(1),
5925
+ status: z3.string().default("processing"),
5926
+ estimated_generations: z3.number().nullable().optional(),
5927
+ usage: z3.unknown().optional()
5800
5928
  }).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()
5929
+ var UnzoomResponseSchema = z3.object({
5930
+ image: z3.object({ base64: z3.string().min(1) }).passthrough(),
5931
+ original_size: z3.object({ width: z3.number().int(), height: z3.number().int() }).passthrough(),
5932
+ unzoomed_size: z3.object({ width: z3.number().int(), height: z3.number().int() }).passthrough(),
5933
+ zoom_factor_detected: z3.number(),
5934
+ usage: z3.unknown().optional()
5808
5935
  }).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()
5936
+ var FontJobSchema = z3.object({
5937
+ job_id: z3.string().min(1),
5938
+ status: z3.string(),
5939
+ glyph_px: z3.number().int().nullable().optional(),
5940
+ download_atlas_url: z3.string().nullable().optional(),
5941
+ download_ttf_url: z3.string().nullable().optional(),
5942
+ usage: z3.unknown().optional()
5815
5943
  }).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()
5944
+ var SelectFramesSchema = z3.object({ created_object_ids: z3.array(z3.string()) }).passthrough();
5945
+ var EstimateSkeletonResponseSchema = z3.object({
5946
+ keypoints: z3.array(SkeletonKeypointSchema).length(18),
5947
+ usage: z3.unknown().optional()
5823
5948
  }).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"),
5949
+ var CharacterSubmitSchema = z3.object({
5950
+ background_job_id: z3.string().min(1),
5951
+ character_id: z3.string().min(1),
5952
+ status: z3.string().default("processing"),
5829
5953
  usage: UsageSchema
5830
5954
  }).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"),
5955
+ var AnimateSubmitSchema = z3.object({
5956
+ background_job_ids: z3.array(z3.string()),
5957
+ directions: z3.array(z3.string()),
5958
+ status: z3.string().default("processing"),
5835
5959
  usage: UsageSchema
5836
5960
  }).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"),
5961
+ var ObjectProSubmitSchema = z3.object({
5962
+ object_id: z3.string().min(1),
5963
+ background_job_id: z3.string().min(1),
5964
+ status: z3.string().default("processing"),
5841
5965
  usage: UsageSchema
5842
5966
  }).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)
5967
+ var DirectionSubmissionSchema = z3.object({
5968
+ direction: z3.string(),
5969
+ status: z3.string(),
5970
+ background_job_id: z3.string().nullable().default(null),
5971
+ animation_id: z3.string().nullable().default(null)
5848
5972
  }).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),
5973
+ var AnimateObjectSubmitSchema = z3.object({
5974
+ animation_group_id: z3.string().min(1),
5975
+ object_id: z3.string().min(1),
5976
+ mode: z3.string(),
5977
+ frame_count: z3.number().int(),
5978
+ display_name: z3.string().nullable().default(null),
5979
+ description: z3.string().default(""),
5980
+ submissions: z3.array(DirectionSubmissionSchema),
5857
5981
  usage: UsageSchema
5858
5982
  }).passthrough();
5859
- var PortraitSubmitSchema = z2.object({
5860
- background_job_id: z2.string().min(1),
5861
- status: z2.string().default("processing"),
5983
+ var PortraitSubmitSchema = z3.object({
5984
+ background_job_id: z3.string().min(1),
5985
+ status: z3.string().default("processing"),
5862
5986
  usage: UsageSchema
5863
5987
  }).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()
5988
+ var PortraitJobSchema = z3.object({
5989
+ status: z3.string(),
5990
+ download_url: z3.string().nullable().optional(),
5991
+ width: z3.number().int().nullable().optional(),
5992
+ height: z3.number().int().nullable().optional()
5869
5993
  }).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),
5994
+ var SetPortraitSchema = z3.object({
5995
+ character_id: z3.string().min(1),
5996
+ size: z3.number().int().positive(),
5997
+ url: z3.string().min(1),
5874
5998
  usage: UsageSchema
5875
5999
  }).passthrough();
5876
- var TransferOutfitSubmitSchema = z2.object({
5877
- background_job_id: z2.string().min(1),
5878
- status: z2.string().default("processing"),
6000
+ var TransferOutfitSubmitSchema = z3.object({
6001
+ background_job_id: z3.string().min(1),
6002
+ status: z3.string().default("processing"),
5879
6003
  usage: UsageSchema
5880
6004
  }).passthrough();
5881
- var CharacterAnimationDirectionSchema = z2.object({
5882
- direction: z2.string(),
5883
- frame_count: z2.number().int().min(0),
5884
- frames: z2.array(z2.string())
6005
+ var CharacterAnimationDirectionSchema = z3.object({
6006
+ direction: z3.string(),
6007
+ frame_count: z3.number().int().min(0),
6008
+ frames: z3.array(z3.string())
5885
6009
  }).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)
6010
+ var CharacterAnimationSchema2 = z3.object({
6011
+ animation_type: z3.string(),
6012
+ display_name: z3.string().nullable().optional(),
6013
+ animation_group_id: z3.string().nullable().optional(),
6014
+ directions: z3.array(CharacterAnimationDirectionSchema)
5891
6015
  }).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()
6016
+ var CharacterSchema = z3.object({
6017
+ id: z3.string().min(1),
6018
+ name: z3.string().default(""),
6019
+ state_name: z3.string().nullable().optional(),
6020
+ prompt: z3.string().default(""),
6021
+ size: z3.object({ width: z3.number().int().positive(), height: z3.number().int().positive() }),
6022
+ directions: z3.number().int(),
6023
+ created_at: z3.string(),
6024
+ updated_at: z3.string().nullable().optional(),
6025
+ animation_count: z3.number().int().default(0),
6026
+ template_id: z3.string().default("mannequin"),
6027
+ view: z3.string().nullable().optional(),
6028
+ status: z3.string().default("completed"),
6029
+ rotation_urls: z3.record(z3.string().nullable()).nullable().optional(),
6030
+ tags: z3.array(z3.string()).default([]),
6031
+ group_id: z3.string().nullable().optional(),
6032
+ animations: z3.array(CharacterAnimationSchema2).default([]),
6033
+ preview_url: z3.string().nullable().optional()
5910
6034
  }).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(),
6035
+ var CharacterListSchema = z3.object({ characters: z3.array(CharacterSchema), total: z3.number().int().min(0) }).passthrough();
6036
+ var BackgroundJobSchema = z3.object({
6037
+ id: z3.string(),
6038
+ status: z3.string(),
6039
+ last_response: z3.record(z3.unknown()).nullable().optional(),
5916
6040
  /**
5917
6041
  * The billed amount, confirmed live for a `map` object and a standard
5918
6042
  * character base (docs/ENDPOINTS.md, "Limits and billing"); duplicated at
@@ -5920,7 +6044,7 @@ var BackgroundJobSchema = z2.object({
5920
6044
  */
5921
6045
  usage: UsageSchema
5922
6046
  }).passthrough();
5923
- var DeleteAnimationsSchema = z2.object({ success: z2.boolean(), deleted_count: z2.number().int().default(0), error: z2.string().nullable().optional() }).passthrough();
6047
+ var DeleteAnimationsSchema = z3.object({ success: z3.boolean(), deleted_count: z3.number().int().default(0), error: z3.string().nullable().optional() }).passthrough();
5924
6048
  function validateResponse(schema, raw, operation) {
5925
6049
  const parsed = schema.safeParse(raw);
5926
6050
  if (parsed.success) return parsed.data;
@@ -5966,9 +6090,9 @@ var PixelLabClient = class {
5966
6090
  apiKey;
5967
6091
  http;
5968
6092
  /** Retries transport failures, 429, and 5xx; see http.ts for the policy. */
5969
- async request(path47, init) {
6093
+ async request(path48, init) {
5970
6094
  const auth = this.apiKey.startsWith("Bearer ") ? this.apiKey : `Bearer ${this.apiKey}`;
5971
- const res = await this.http(`${BASE}${path47}`, {
6095
+ const res = await this.http(`${BASE}${path48}`, {
5972
6096
  ...init,
5973
6097
  headers: {
5974
6098
  Authorization: auth,
@@ -5978,13 +6102,13 @@ var PixelLabClient = class {
5978
6102
  });
5979
6103
  const text = await res.text();
5980
6104
  if (!res.ok) {
5981
- throw new PixelLabError(`${init?.method ?? "GET"} ${path47} \u2192 ${res.status}`, res.status, text);
6105
+ throw new PixelLabError(`${init?.method ?? "GET"} ${path48} \u2192 ${res.status}`, res.status, text);
5982
6106
  }
5983
6107
  if (!text) return {};
5984
6108
  try {
5985
6109
  return JSON.parse(text);
5986
6110
  } catch {
5987
- throw new Error(`${init?.method ?? "GET"} ${path47} returned invalid JSON`);
6111
+ throw new Error(`${init?.method ?? "GET"} ${path48} returned invalid JSON`);
5988
6112
  }
5989
6113
  }
5990
6114
  async balance() {
@@ -6040,6 +6164,12 @@ var PixelLabClient = class {
6040
6164
  if (args.shading) body.shading = args.shading;
6041
6165
  if (args.detail) body.detail = args.detail;
6042
6166
  if (args.seed != null) body.seed = args.seed;
6167
+ if (args.scene) {
6168
+ const encode = (image) => ({ type: "base64", base64: image.base64, format: image.format });
6169
+ const placement = args.scene.placement;
6170
+ body.background_image = encode(args.scene.image);
6171
+ body.inpainting = placement.type === "mask" ? { type: "mask", mask_image: encode(placement.mask) } : { type: placement.type, fraction: placement.fraction };
6172
+ }
6043
6173
  return validateResponse(
6044
6174
  MapSubmitSchema,
6045
6175
  await this.request("/map-objects", { method: "POST", body: JSON.stringify(body) }),
@@ -6370,10 +6500,10 @@ var PixelLabClient = class {
6370
6500
  const palette = args.paletteSwatchBase64 ? { color_image: { type: "base64", base64: args.paletteSwatchBase64, format: "png" }, force_colors: true } : {};
6371
6501
  const encode = (image) => ({ type: "base64", base64: image.base64, format: image.format });
6372
6502
  const south = args.reference?.south;
6373
- let path47;
6503
+ let path48;
6374
6504
  let body;
6375
6505
  if (args.mode === "v3") {
6376
- path47 = "/create-character-v3";
6506
+ path48 = "/create-character-v3";
6377
6507
  body = {
6378
6508
  description: args.description,
6379
6509
  image_size: imageSize,
@@ -6387,7 +6517,7 @@ var PixelLabClient = class {
6387
6517
  ...args.enhancePrompt ? { enhance_prompt: true } : {}
6388
6518
  };
6389
6519
  } else if (args.mode === "pro-flash") {
6390
- path47 = "/create-character-pro-flash";
6520
+ path48 = "/create-character-pro-flash";
6391
6521
  const traits = args.styleTraits;
6392
6522
  body = {
6393
6523
  description: args.description,
@@ -6411,7 +6541,7 @@ var PixelLabClient = class {
6411
6541
  } : {}
6412
6542
  };
6413
6543
  } else if (args.mode === "pro") {
6414
- path47 = "/create-character-pro";
6544
+ path48 = "/create-character-pro";
6415
6545
  body = {
6416
6546
  description: args.description,
6417
6547
  image_size: imageSize,
@@ -6425,7 +6555,7 @@ var PixelLabClient = class {
6425
6555
  ...!south && args.styleCharacterId ? { style_character_id: args.styleCharacterId } : {}
6426
6556
  };
6427
6557
  } else {
6428
- path47 = args.directions === 4 ? "/create-character-with-4-directions" : "/create-character-with-8-directions";
6558
+ path48 = args.directions === 4 ? "/create-character-with-4-directions" : "/create-character-with-8-directions";
6429
6559
  body = {
6430
6560
  description: args.description,
6431
6561
  image_size: imageSize,
@@ -6442,8 +6572,8 @@ var PixelLabClient = class {
6442
6572
  ...palette
6443
6573
  };
6444
6574
  }
6445
- const raw = await this.request(path47, { method: "POST", body: JSON.stringify(body) });
6446
- return validateResponse(CharacterSubmitSchema, raw, path47.slice(1));
6575
+ const raw = await this.request(path48, { method: "POST", body: JSON.stringify(body) });
6576
+ return validateResponse(CharacterSubmitSchema, raw, path48.slice(1));
6447
6577
  }
6448
6578
  /** A text edit of an existing character, applied to every direction and stored as a sibling. */
6449
6579
  async createCharacterState(args) {
@@ -6951,6 +7081,61 @@ var PixelLabClient = class {
6951
7081
  "animate-pixminimax"
6952
7082
  );
6953
7083
  }
7084
+ /**
7085
+ * `/animate-with-skeleton-v3`, beta (tier 1 subscription or higher): poses
7086
+ * one reference image frame-by-frame from a supplied skeleton per frame,
7087
+ * rather than a text motion description. Confirmed against the live
7088
+ * `animate_with_skeleton_v3` MCP tool schema (the same authoritative
7089
+ * source this codebase already treats as ground truth for the
7090
+ * character/object family) — `action`/`description` are both real,
7091
+ * optional wire fields, contrary to an initial guess that this endpoint
7092
+ * took no text input at all; `direction`/`keypoints`/`firstFrameKeypoints`
7093
+ * are required. The example keypoint from that same tool schema,
7094
+ * `{"label": "RIGHT KNEE", "x": 0.52, "y": 0.71, "z_index": 9}`, is what
7095
+ * `SkeletonKeypointSchema` (`src/skeleton.ts`) models. Same
7096
+ * unverified-completed-shape caveat as `animateWithTextV3`/
7097
+ * `animatePixminimax` above — this endpoint has never been called against
7098
+ * a live account by this codebase.
7099
+ */
7100
+ async animateWithSkeletonV3(args) {
7101
+ const body = {
7102
+ first_frame: args.firstFrame,
7103
+ first_frame_keypoints: args.firstFrameKeypoints,
7104
+ keypoints: args.keypoints,
7105
+ direction: args.direction
7106
+ };
7107
+ if (args.templateId) body.template_id = args.templateId;
7108
+ if (args.action) body.action = args.action;
7109
+ if (args.description) body.description = args.description;
7110
+ if (args.seed != null) body.seed = args.seed;
7111
+ if (args.noBackground != null) body.no_background = args.noBackground;
7112
+ return validateResponse(
7113
+ RevisionJobSubmitSchema,
7114
+ await this.request("/animate-with-skeleton-v3", { method: "POST", body: JSON.stringify(body) }),
7115
+ "animate-with-skeleton-v3"
7116
+ );
7117
+ }
7118
+ /**
7119
+ * `/estimate-skeleton`: auto-derives a reference image's own 18-joint
7120
+ * keypoints, directly usable as `animateWithSkeletonV3`'s
7121
+ * `firstFrameKeypoints` (or, run against a candidate pose image, as one
7122
+ * entry of its `keypoints` sequence). Not called anywhere in this
7123
+ * codebase's own submit/poll pipeline — deliberately kept outside it (see
7124
+ * `runEstimateSkeleton` in `src/cli/commands`) so the only PixelLab
7125
+ * endpoint composed into `animate-with-skeleton-v3`'s submission is that
7126
+ * one endpoint itself, not two unverified calls chained together.
7127
+ * Synchronous per this endpoint's own framing; unverified against a live
7128
+ * account (see `EstimateSkeletonResponseSchema`'s own caveat).
7129
+ */
7130
+ async estimateSkeleton(args) {
7131
+ const body = { image: args.image };
7132
+ const res = validateResponse(
7133
+ EstimateSkeletonResponseSchema,
7134
+ await this.request("/estimate-skeleton", { method: "POST", body: JSON.stringify(body) }),
7135
+ "estimate-skeleton"
7136
+ );
7137
+ return { keypoints: res.keypoints, usage: res.usage };
7138
+ }
6954
7139
  async getBackgroundJob(jobId) {
6955
7140
  const raw = await this.request(`/background-jobs/${encodeURIComponent(jobId)}`);
6956
7141
  return validateResponse(BackgroundJobSchema, raw, "background-jobs/{id}");
@@ -7084,6 +7269,18 @@ function objectProCost(spec) {
7084
7269
  const south = object.reference;
7085
7270
  return south ? proFlashObjectCost(south.width, south.height, object.directions, true) : proFlashObjectCost(spec.width, spec.height, object.directions, false);
7086
7271
  }
7272
+ function animateSkeletonCost(frames) {
7273
+ const anchors = [[3, 2], [8, 3], [15, 4]];
7274
+ for (let i = 0; i < anchors.length - 1; i++) {
7275
+ const [lowFrames, lowCost] = anchors[i];
7276
+ const [highFrames, highCost] = anchors[i + 1];
7277
+ if (frames >= lowFrames && frames <= highFrames) {
7278
+ const t = (frames - lowFrames) / (highFrames - lowFrames);
7279
+ return lowCost + t * (highCost - lowCost);
7280
+ }
7281
+ }
7282
+ return frames <= anchors[0][0] ? anchors[0][1] : anchors[anchors.length - 1][1];
7283
+ }
7087
7284
  var CHARACTER_TEMPLATES = ["mannequin", "bear", "cat", "dog", "horse", "lion"];
7088
7285
  var PRO_FLASH_TEMPLATES = [...CHARACTER_TEMPLATES, "custom"];
7089
7286
  function encodeAnimationJob(job) {
@@ -7134,6 +7331,8 @@ var PixelLabProvider = class _PixelLabProvider {
7134
7331
  supports(generator) {
7135
7332
  return generator === "1dir" || generator === "map" || generator === "pixflux" || generator === "tiles" || generator === "terrain" || generator === "imagePro" || generator === "character" || generator === "isometricTile" || generator === "objectPro" || generator === "uiAsset" || generator === "uiElement" || generator === "imageProFlash";
7136
7333
  }
7334
+ /** `/map-objects` style matching: `background_image` plus `inpainting`. */
7335
+ supportsMapScene = true;
7137
7336
  /**
7138
7337
  * `inpaint` (`/inpaint-v3`, a mask) and `image-to-image` (`/edit-images-v2`,
7139
7338
  * no mask) both exist on PixelLab, and so do `reduce-colors`
@@ -7142,7 +7341,9 @@ var PixelLabProvider = class _PixelLabProvider {
7142
7341
  * rather than a described edit — and `animate`/`animate-pixminimax`
7143
7342
  * (`/animate-with-text-v3`, `/animate-pixminimax`), which animate any loose
7144
7343
  * image from a text description with no character/object resource
7145
- * required — and `interpolate` (`/interpolation-v2`, the in-betweens
7344
+ * required — `animate-skeleton` (`/animate-with-skeleton-v3`, posing the
7345
+ * source from a supplied per-frame skeleton instead of a text description)
7346
+ * — and `interpolate` (`/interpolation-v2`, the in-betweens
7146
7347
  * from one keyframe to another) and `edit-animation`
7147
7348
  * (`/edit-animation-v2`, one text edit applied across a whole frame set).
7148
7349
  * `outpaint` does not: there is no canvas-expansion endpoint in the API,
@@ -7150,7 +7351,7 @@ var PixelLabProvider = class _PixelLabProvider {
7150
7351
  * outpaint path yet.
7151
7352
  */
7152
7353
  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";
7354
+ 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
7355
  }
7155
7356
  /** PixelLab's own constraints: submissions must be >2s apart, and
7156
7357
  * background jobs in flight are capped by subscription tier (Tier 1=8,
@@ -7181,6 +7382,10 @@ var PixelLabProvider = class _PixelLabProvider {
7181
7382
  if (spec.revision?.mode === "reduce-colors" || spec.revision?.mode === "correct-pixelart") {
7182
7383
  return { unit: "generations", amount: 0.1, candidates: 1 };
7183
7384
  }
7385
+ if (spec.revision?.mode === "animate-skeleton") {
7386
+ const frames = spec.revision.frames ?? 8;
7387
+ return { unit: "generations", amount: Math.ceil(animateSkeletonCost(frames)), candidates: 1 };
7388
+ }
7184
7389
  if (spec.revision?.mode === "interpolate" || spec.revision?.mode === "edit-animation") {
7185
7390
  const width = spec.revision.sourceWidth ?? spec.width;
7186
7391
  const height = spec.revision.sourceHeight ?? spec.height;
@@ -7262,6 +7467,18 @@ var PixelLabProvider = class _PixelLabProvider {
7262
7467
  throw new Error(`PixelLab correct-pixelart source is ${width}x${height}; the API takes at most 1024 pixels per side`);
7263
7468
  }
7264
7469
  }
7470
+ if (spec.revision?.mode === "animate-skeleton") {
7471
+ const { sourceWidth: width, sourceHeight: height, frames, skeletonTemplate } = spec.revision;
7472
+ if (width != null && height != null && (width > 256 || height > 256)) {
7473
+ throw new Error(`PixelLab animate-skeleton source is ${width}x${height}; the API takes at most 256 pixels per side`);
7474
+ }
7475
+ if (frames != null && (frames < 3 || frames > 15)) {
7476
+ throw new Error(`PixelLab animate-skeleton takes 3 to 15 frames; the keypoints file has ${frames}`);
7477
+ }
7478
+ if (skeletonTemplate && !CHARACTER_TEMPLATES.includes(skeletonTemplate)) {
7479
+ throw new Error(`PixelLab animate-skeleton template "${skeletonTemplate}" is not one of ${CHARACTER_TEMPLATES.join(", ")}`);
7480
+ }
7481
+ }
7265
7482
  if (spec.revision?.mode === "animate" || spec.revision?.mode === "animate-pixminimax") {
7266
7483
  const { sourceWidth: width, sourceHeight: height, mode: mode2, frames, lastFrameWidth, lastFrameHeight } = spec.revision;
7267
7484
  if (width != null && height != null && (width > 256 || height > 256)) {
@@ -8261,7 +8478,8 @@ var PixelLabProvider = class _PixelLabProvider {
8261
8478
  outline: spec.outline,
8262
8479
  shading: spec.shading,
8263
8480
  detail: spec.detail,
8264
- seed: spec.seed
8481
+ seed: spec.seed,
8482
+ scene: spec.scene ? mapScenePayload(spec) : void 0
8265
8483
  });
8266
8484
  return { jobId: res.object_id, metadata: { backgroundJobId: res.background_job_id } };
8267
8485
  }
@@ -8411,6 +8629,27 @@ var PixelLabProvider = class _PixelLabProvider {
8411
8629
  });
8412
8630
  return { jobId: res2.background_job_id };
8413
8631
  }
8632
+ if (revision.mode === "animate-skeleton") {
8633
+ if (!revision.keypointsFile || !revision.keypointsSha256 || !revision.skeleton) {
8634
+ throw new Error(`${spec.styleId}/${spec.assetId}: revision keypoints are not ready`);
8635
+ }
8636
+ const keypointsBytes = readFileSync2(revision.keypointsFile);
8637
+ if (sha256(keypointsBytes) !== revision.keypointsSha256) {
8638
+ throw new Error(`${spec.styleId}/${spec.assetId}: revision keypoints changed after the manifest was resolved`);
8639
+ }
8640
+ const res2 = await this.client.animateWithSkeletonV3({
8641
+ firstFrame: image,
8642
+ firstFrameKeypoints: revision.skeleton.firstFrameKeypoints,
8643
+ keypoints: revision.skeleton.frames,
8644
+ direction: revision.direction,
8645
+ templateId: revision.skeletonTemplate,
8646
+ action: spec.prompt,
8647
+ description: revision.description,
8648
+ seed: spec.seed,
8649
+ noBackground: spec.noBackground
8650
+ });
8651
+ return { jobId: res2.background_job_id };
8652
+ }
8414
8653
  if (revision.strength != null) {
8415
8654
  throw new Error(
8416
8655
  `${spec.styleId}/${spec.assetId}: PixelLab image-to-image revisions take no strength; edit-images-v2 always applies the full instruction`
@@ -8476,7 +8715,7 @@ var PixelLabProvider = class _PixelLabProvider {
8476
8715
  if (revisionMode === "reduce-colors" || revisionMode === "correct-pixelart") {
8477
8716
  return this.pollCachedRevision(jobId, context);
8478
8717
  }
8479
- if (revisionMode === "animate" || revisionMode === "animate-pixminimax" || revisionMode === "interpolate" || revisionMode === "edit-animation") {
8718
+ if (revisionMode === "animate" || revisionMode === "animate-pixminimax" || revisionMode === "animate-skeleton" || revisionMode === "interpolate" || revisionMode === "edit-animation") {
8480
8719
  return this.pollAnimateRevision(jobId, context);
8481
8720
  }
8482
8721
  if (context?.spec?.revision) return this.pollRevision(jobId);
@@ -9117,6 +9356,21 @@ function findObjectAnimation(object, name, direction, animationId) {
9117
9356
  }
9118
9357
  return null;
9119
9358
  }
9359
+ function mapScenePayload(spec) {
9360
+ const scene = spec.scene;
9361
+ const read = (file, hash, label) => {
9362
+ const bytes = readFileSync2(file);
9363
+ if (!hash || sha256(bytes) !== hash) {
9364
+ throw new Error(`${spec.styleId}/${spec.assetId}: ${label} changed after the manifest was resolved`);
9365
+ }
9366
+ return { base64: bytes.toString("base64"), format: "png" };
9367
+ };
9368
+ const placement = scene.placement;
9369
+ return {
9370
+ image: read(scene.file, scene.sha256, "scene image"),
9371
+ placement: placement.type === "mask" ? { type: "mask", mask: read(placement.file, placement.sha256, "scene mask") } : placement
9372
+ };
9373
+ }
9120
9374
 
9121
9375
  // src/providers/retrodiffusion.ts
9122
9376
  var DEFAULT_BASE_URL2 = "https://api.retrodiffusion.ai/v1";
@@ -9166,9 +9420,9 @@ var RetroDiffusionClient = class {
9166
9420
  if (!response.ok) throw new ProviderError("retrodiffusion", `Retro Diffusion download failed (${response.status})`, { status: response.status });
9167
9421
  return Buffer.from(await response.arrayBuffer());
9168
9422
  }
9169
- async call(path47, init = {}) {
9423
+ async call(path48, init = {}) {
9170
9424
  if (!this.token) throw new Error("RD_API_KEY is not set");
9171
- const response = await this.request(`${this.baseUrl}${path47}`, {
9425
+ const response = await this.request(`${this.baseUrl}${path48}`, {
9172
9426
  ...init,
9173
9427
  headers: {
9174
9428
  "Content-Type": "application/json",
@@ -10712,6 +10966,8 @@ async function resolveSpecs(loaded, filter) {
10712
10966
  const paletteImage = paletteImageFile ? await optionalRevisionImage(paletteImageFile, "revision palette image", "png") : null;
10713
10967
  const lastFrameFile = asset.revision.lastFrame ? path13.resolve(root, asset.revision.lastFrame) : void 0;
10714
10968
  const lastFrame = lastFrameFile ? await optionalRevisionImage(lastFrameFile, "revision last frame") : null;
10969
+ const keypointsFile = asset.revision.keypointsFile ? path13.resolve(root, asset.revision.keypointsFile) : void 0;
10970
+ const keypoints = keypointsFile ? await optionalSkeletonSet(keypointsFile) : null;
10715
10971
  resolved.revision = {
10716
10972
  mode: asset.revision.mode,
10717
10973
  sourceAssetId: asset.revision.from,
@@ -10752,6 +11008,18 @@ async function resolveSpecs(loaded, filter) {
10752
11008
  } : {},
10753
11009
  ...asset.revision.direction ? { direction: asset.revision.direction } : {},
10754
11010
  ...asset.revision.enhancePrompt == null ? {} : { enhancePrompt: asset.revision.enhancePrompt },
11011
+ ...keypointsFile ? {
11012
+ keypointsFile,
11013
+ keypointsSha256: keypoints?.hash ?? null,
11014
+ skeleton: keypoints?.skeleton ?? null,
11015
+ // The manifest's own `frames` field stays rejected for this
11016
+ // mode (superRefine, types.ts) — the real frame count comes
11017
+ // from the keypoints file once it exists, not a manifest
11018
+ // number, so plan can only know it once authored.
11019
+ ...keypoints ? { frames: keypoints.skeleton.frames.length } : {}
11020
+ } : {},
11021
+ ...asset.revision.skeletonTemplate ? { skeletonTemplate: asset.revision.skeletonTemplate } : {},
11022
+ ...asset.revision.description ? { description: asset.revision.description } : {},
10755
11023
  ...asset.revision.engine ? { engine: asset.revision.engine } : {}
10756
11024
  };
10757
11025
  }
@@ -10805,6 +11073,7 @@ async function resolveSpecs(loaded, filter) {
10805
11073
  };
10806
11074
  }
10807
11075
  }
11076
+ if (asset.scene) resolved.scene = await resolveMapScene(root, styleId, assetId, resolved.generator, asset.scene, activeProvider);
10808
11077
  resolved.specHash = specHash(
10809
11078
  resolved,
10810
11079
  styleImageHashes,
@@ -10863,6 +11132,32 @@ function pngPath(file) {
10863
11132
  const extension = path13.extname(file);
10864
11133
  return extension ? `${file.slice(0, -extension.length)}.png` : `${file}.png`;
10865
11134
  }
11135
+ async function resolveMapScene(root, styleId, assetId, generator, scene, provider) {
11136
+ if (generator !== "map") {
11137
+ throw new Error(`assets.${assetId}.scene: only a map object can be drawn into a scene; style "${styleId}" uses "${generator}"`);
11138
+ }
11139
+ if (!provider.supportsMapScene) {
11140
+ throw new Error(`assets.${assetId}.scene: provider "${provider.id}" cannot draw into a scene; PixelLab's /map-objects can`);
11141
+ }
11142
+ const file = path13.resolve(root, scene.image);
11143
+ const image = await optionalRevisionImage(file, "scene image", "png");
11144
+ const base = { file, sha256: image?.hash ?? null, width: image?.width ?? null, height: image?.height ?? null };
11145
+ const placement = scene.placement;
11146
+ if ("mask" in placement) {
11147
+ const maskFile = path13.resolve(root, placement.mask);
11148
+ const mask = await optionalRevisionImage(maskFile, "scene mask", "png");
11149
+ if (image && mask && (image.width !== mask.width || image.height !== mask.height)) {
11150
+ throw new Error(
11151
+ `assets.${assetId}.scene: the mask is ${mask.width}x${mask.height}; the scene ${scene.image} is ${image.width}x${image.height}`
11152
+ );
11153
+ }
11154
+ return {
11155
+ ...base,
11156
+ placement: { type: "mask", file: maskFile, sha256: mask?.hash ?? null, width: mask?.width ?? null, height: mask?.height ?? null }
11157
+ };
11158
+ }
11159
+ return "oval" in placement ? { ...base, placement: { type: "oval", fraction: placement.oval } } : { ...base, placement: { type: "rectangle", fraction: placement.rectangle } };
11160
+ }
10866
11161
  async function optionalRevisionImage(file, label, requiredFormat) {
10867
11162
  if (!existsSync8(file)) return null;
10868
11163
  const bytes = await readFile6(file);
@@ -10873,6 +11168,17 @@ async function optionalRevisionImage(file, label, requiredFormat) {
10873
11168
  }
10874
11169
  return { hash: sha256(bytes), ...metadata };
10875
11170
  }
11171
+ async function optionalSkeletonSet(file) {
11172
+ if (!existsSync8(file)) return null;
11173
+ const bytes = await readFile6(file);
11174
+ let json;
11175
+ try {
11176
+ json = JSON.parse(bytes.toString("utf8"));
11177
+ } catch (error) {
11178
+ throw new Error(`revision keypoints file ${file} is not valid JSON: ${error instanceof Error ? error.message : String(error)}`);
11179
+ }
11180
+ return { hash: sha256(bytes), skeleton: parseSkeletonSet(json, file) };
11181
+ }
10876
11182
  async function revisionSourceMembers(stem, label) {
10877
11183
  if (existsSync8(stem)) return null;
10878
11184
  const at = (role) => memberPath(stem, role, 0, 2, MediaType.PNG);
@@ -11408,11 +11714,11 @@ import path15 from "path";
11408
11714
  import { mkdir as mkdir5, readFile as readFile8, rename as rename5, rm as rm5, writeFile as writeFile5 } from "fs/promises";
11409
11715
  import { existsSync as existsSync10 } from "fs";
11410
11716
  import pathModule from "path";
11411
- import { z as z3 } from "zod";
11412
- var HashCacheSchema = z3.object({
11413
- version: z3.literal(1),
11717
+ import { z as z4 } from "zod";
11718
+ var HashCacheSchema = z4.object({
11719
+ version: z4.literal(1),
11414
11720
  /** objectId → sha256 of that object's image bytes. */
11415
- hashes: z3.record(z3.string())
11721
+ hashes: z4.record(z4.string())
11416
11722
  });
11417
11723
  var SHA256_HEX = /^[a-f0-9]{64}$/;
11418
11724
  function isSha256Hash(value) {
@@ -11421,10 +11727,10 @@ function isSha256Hash(value) {
11421
11727
  function parseCache(value) {
11422
11728
  return HashCacheSchema.parse(value);
11423
11729
  }
11424
- async function loadCache(path47) {
11425
- if (!existsSync10(path47)) return { version: 1, hashes: {} };
11730
+ async function loadCache(path48) {
11731
+ if (!existsSync10(path48)) return { version: 1, hashes: {} };
11426
11732
  try {
11427
- const parsed = HashCacheSchema.safeParse(JSON.parse(await readFile8(path47, "utf8")));
11733
+ const parsed = HashCacheSchema.safeParse(JSON.parse(await readFile8(path48, "utf8")));
11428
11734
  if (!parsed.success) return { version: 1, hashes: {} };
11429
11735
  return {
11430
11736
  version: 1,
@@ -11436,18 +11742,18 @@ async function loadCache(path47) {
11436
11742
  return { version: 1, hashes: {} };
11437
11743
  }
11438
11744
  }
11439
- async function saveCache(path47, cache) {
11745
+ async function saveCache(path48, cache) {
11440
11746
  const sorted = {};
11441
11747
  for (const key of Object.keys(cache.hashes).sort()) {
11442
11748
  const hash = cache.hashes[key];
11443
11749
  if (!isSha256Hash(hash)) throw new Error(`Refusing to cache invalid SHA-256 for ${key}`);
11444
11750
  sorted[key] = hash;
11445
11751
  }
11446
- await mkdir5(pathModule.dirname(pathModule.resolve(path47)), { recursive: true });
11447
- const tmp = `${path47}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
11752
+ await mkdir5(pathModule.dirname(pathModule.resolve(path48)), { recursive: true });
11753
+ const tmp = `${path48}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
11448
11754
  try {
11449
11755
  await writeFile5(tmp, JSON.stringify({ version: 1, hashes: sorted }, null, 2) + "\n");
11450
- await rename5(tmp, path47);
11756
+ await rename5(tmp, path48);
11451
11757
  } finally {
11452
11758
  await rm5(tmp, { force: true });
11453
11759
  }
@@ -12205,20 +12511,20 @@ async function applyTags(provider, decisions, existing, opts = {}) {
12205
12511
  import { mkdir as mkdir7, readFile as readFile12, rename as rename6, rm as rm6, writeFile as writeFile8 } from "fs/promises";
12206
12512
  import { existsSync as existsSync14 } from "fs";
12207
12513
  import path18 from "path";
12208
- import { z as z4 } from "zod";
12209
- var WorkspaceProjectSchema = z4.object({
12210
- id: z4.string().min(1),
12514
+ import { z as z5 } from "zod";
12515
+ var WorkspaceProjectSchema = z5.object({
12516
+ id: z5.string().min(1),
12211
12517
  /** Manifest path, relative to the catalog file's own directory. */
12212
- manifest: z4.string().min(1),
12518
+ manifest: z5.string().min(1),
12213
12519
  /** Lockfile path, relative to the catalog file's own directory. */
12214
- lock: z4.string().min(1),
12215
- provider: z4.string().min(1).default("pixellab"),
12520
+ lock: z5.string().min(1),
12521
+ provider: z5.string().min(1).default("pixellab"),
12216
12522
  /** Free-form label for a shared account, e.g. distinguishing sandboxes. */
12217
- account: z4.string().optional()
12523
+ account: z5.string().optional()
12218
12524
  }).strict();
12219
- var WorkspaceSchema = z4.object({
12220
- version: z4.literal(1),
12221
- projects: z4.array(WorkspaceProjectSchema).default([])
12525
+ var WorkspaceSchema = z5.object({
12526
+ version: z5.literal(1),
12527
+ projects: z5.array(WorkspaceProjectSchema).default([])
12222
12528
  }).strict();
12223
12529
  function parseWorkspace(raw) {
12224
12530
  const parsed = WorkspaceSchema.safeParse(raw);
@@ -13403,62 +13709,62 @@ import path25 from "path";
13403
13709
  import { existsSync as existsSync17 } from "fs";
13404
13710
  import { copyFile, mkdir as mkdir9, readFile as readFile16, rename as rename8, rm as rm8, writeFile as writeFile11 } from "fs/promises";
13405
13711
  import path24 from "path";
13406
- import { z as z6 } from "zod";
13712
+ import { z as z7 } from "zod";
13407
13713
 
13408
13714
  // src/manifest-edit.ts
13409
13715
  import { readFile as readFile15, rename as rename7, rm as rm7, writeFile as writeFile10 } from "fs/promises";
13410
13716
  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(),
13717
+ import { z as z6 } from "zod";
13718
+ var HexSha = z6.string().regex(/^[0-9a-f]{64}$/);
13719
+ var AssetPatchSchema = z6.object({
13720
+ prompt: z6.string().optional(),
13415
13721
  /** 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()
13722
+ promptForStyle: z6.object({ styleId: z6.string().min(1), prompt: z6.string().nullable() }).optional(),
13723
+ category: z6.string().nullable().optional(),
13724
+ width: z6.number().int().nullable().optional(),
13725
+ height: z6.number().int().nullable().optional(),
13726
+ size: z6.number().int().nullable().optional(),
13727
+ tags: z6.array(z6.string()).optional()
13422
13728
  }).strict();
13423
- var NewStateSchema = z5.object({
13729
+ var NewStateSchema = z6.object({
13424
13730
  /** 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()
13731
+ of: z6.string().min(1),
13732
+ paletteFromReference: z6.boolean().optional(),
13733
+ canvas: z6.object({ width: z6.number().int().min(16).max(256), height: z6.number().int().min(16).max(256) }).strict().optional()
13428
13734
  }).strict();
13429
- var NewAnimationSchema = z5.object({
13735
+ var NewAnimationSchema = z6.object({
13430
13736
  /** Asset id of the parent this animates; must already exist. */
13431
- of: z5.string().min(1),
13737
+ of: z6.string().min(1),
13432
13738
  /** A PixelLab template id (`character` only; `objectPro` has no template concept). */
13433
- template: z5.string().min(1).optional(),
13739
+ template: z6.string().min(1).optional(),
13434
13740
  direction: CharacterDirectionSchema.optional(),
13435
- frames: z5.number().int().min(4).max(16).optional(),
13436
- fps: z5.number().int().min(1).max(60).optional(),
13741
+ frames: z6.number().int().min(4).max(16).optional(),
13742
+ fps: z6.number().int().min(1).max(60).optional(),
13437
13743
  mode: CharacterAnimationModeSchema.optional(),
13438
13744
  /** 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()
13745
+ subject: z6.string().min(1).optional(),
13746
+ enhancePrompt: z6.boolean().optional()
13441
13747
  }).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(),
13748
+ var NewAssetSchema = z6.object({
13749
+ prompt: z6.string(),
13750
+ category: z6.string().optional(),
13751
+ width: z6.number().int().optional(),
13752
+ height: z6.number().int().optional(),
13753
+ size: z6.number().int().optional(),
13754
+ tags: z6.array(z6.string()).optional(),
13449
13755
  /** Restrict to these styles; empty or omitted means every style. */
13450
- styles: z5.array(z5.string().min(1)).optional(),
13756
+ styles: z6.array(z6.string().min(1)).optional(),
13451
13757
  /**
13452
13758
  * A controlled regeneration of another asset in the same manifest,
13453
13759
  * instead of a fresh generation. `inpaint` needs a mask upload the
13454
13760
  * gallery does not offer yet, so only `image-to-image` can be created
13455
13761
  * here; hand-edit the manifest for the other modes.
13456
13762
  */
13457
- revision: z5.object({
13458
- mode: z5.literal("image-to-image"),
13763
+ revision: z6.object({
13764
+ mode: z6.literal("image-to-image"),
13459
13765
  /** 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()
13766
+ from: z6.string().min(1),
13767
+ strength: z6.number().min(0).max(1).optional()
13462
13768
  }).strict().optional(),
13463
13769
  /** A pose or outfit of an existing `character`/`objectPro` base or state. */
13464
13770
  state: NewStateSchema.optional(),
@@ -13474,58 +13780,58 @@ var CANDIDATE_OPTION = {
13474
13780
  comfyui: "numImages",
13475
13781
  scenario: "numOutputs"
13476
13782
  };
13477
- var ManifestEditSchema = z5.discriminatedUnion("action", [
13478
- z5.object({
13479
- action: z5.literal("patch-asset"),
13783
+ var ManifestEditSchema = z6.discriminatedUnion("action", [
13784
+ z6.object({
13785
+ action: z6.literal("patch-asset"),
13480
13786
  /** Workspace project id; omitted for a single-project gallery. */
13481
- project: z5.string().min(1).optional(),
13482
- assetId: z5.string().min(1),
13787
+ project: z6.string().min(1).optional(),
13788
+ assetId: z6.string().min(1),
13483
13789
  /** Manifest bytes the page last saw; a mismatch refuses the write. */
13484
13790
  expectedSha256: HexSha,
13485
13791
  patch: AssetPatchSchema
13486
13792
  }).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"),
13793
+ z6.object({
13794
+ action: z6.literal("add-asset"),
13795
+ project: z6.string().min(1).optional(),
13796
+ assetId: z6.string().min(1).regex(/^[^/\\]+$/, "asset ids cannot contain slashes"),
13491
13797
  expectedSha256: HexSha,
13492
13798
  asset: NewAssetSchema
13493
13799
  }).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),
13800
+ z6.object({
13801
+ action: z6.literal("set-source"),
13802
+ project: z6.string().min(1).optional(),
13803
+ assetId: z6.string().min(1),
13804
+ styleId: z6.string().min(1),
13499
13805
  /** Manifest-relative path of the art that stands in for this asset in this style. */
13500
- source: z5.string().min(1),
13806
+ source: z6.string().min(1),
13501
13807
  expectedSha256: HexSha
13502
13808
  }).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),
13809
+ z6.object({
13810
+ action: z6.literal("clear-source"),
13811
+ project: z6.string().min(1).optional(),
13812
+ assetId: z6.string().min(1),
13813
+ styleId: z6.string().min(1),
13508
13814
  expectedSha256: HexSha
13509
13815
  }).strict(),
13510
- z5.object({
13511
- action: z5.literal("patch-style"),
13512
- project: z5.string().min(1).optional(),
13513
- styleId: z5.string().min(1),
13816
+ z6.object({
13817
+ action: z6.literal("patch-style"),
13818
+ project: z6.string().min(1).optional(),
13819
+ styleId: z6.string().min(1),
13514
13820
  expectedSha256: HexSha,
13515
- patch: z5.object({
13821
+ patch: z6.object({
13516
13822
  /** Candidates per generation, written to the provider's own option. */
13517
- candidates: z5.number().int().min(1).max(64).optional(),
13823
+ candidates: z6.number().int().min(1).max(64).optional(),
13518
13824
  /** Empty clears the style's own value (inherit, or the default). */
13519
- promptPrefix: z5.string().optional(),
13520
- promptSuffix: z5.string().optional(),
13825
+ promptPrefix: z6.string().optional(),
13826
+ promptSuffix: z6.string().optional(),
13521
13827
  /** `#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(),
13828
+ palette: z6.array(z6.string().regex(/^#?[0-9a-f]{6}$/i, "expected a six-digit hex colour")).max(256).nullable().optional(),
13523
13829
  /** Snap downloaded art to the palette; false clears the style's own value. */
13524
- enforcePalette: z5.boolean().optional(),
13830
+ enforcePalette: z6.boolean().optional(),
13525
13831
  /** Provider view name (PixelLab: low top-down, high top-down, side); empty clears. */
13526
- view: z5.string().max(64).nullable().optional(),
13832
+ view: z6.string().max(64).nullable().optional(),
13527
13833
  /** pixflux only; null clears the style's own value. */
13528
- noBackground: z5.boolean().nullable().optional()
13834
+ noBackground: z6.boolean().nullable().optional()
13529
13835
  }).strict().refine((patch) => Object.keys(patch).length > 0, { message: "nothing to change" })
13530
13836
  }).strict()
13531
13837
  ]);
@@ -13808,35 +14114,35 @@ async function detachHandEdit(loaded, spec, opts = {}) {
13808
14114
  });
13809
14115
  return { manifestSha256: result.sha256, changed: result.changed };
13810
14116
  }
13811
- var CompanionOutputSchema = z6.object({
14117
+ var CompanionOutputSchema = z7.object({
13812
14118
  /** Output role for a set member; null for a single image. */
13813
- role: z6.string().nullable(),
14119
+ role: z7.string().nullable(),
13814
14120
  /** 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(),
14121
+ basedOn: z7.string().regex(/^[0-9a-f]{64}$/).nullable(),
13816
14122
  /** sha256 of the member as saved, so a later change by another tool is visible. */
13817
- sha256: z6.string().regex(/^[0-9a-f]{64}$/)
14123
+ sha256: z7.string().regex(/^[0-9a-f]{64}$/)
13818
14124
  }).strict();
13819
- var CompanionV2Schema = z6.object({
13820
- version: z6.literal(2),
14125
+ var CompanionV2Schema = z7.object({
14126
+ version: z7.literal(2),
13821
14127
  /** 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(),
14128
+ editor: z7.string().min(1),
14129
+ protocol: z7.number().int().positive(),
14130
+ savedAt: z7.string().datetime(),
13825
14131
  /** Basename of the layered project file beside the edit, when one was kept. */
13826
- project: z6.string().min(1).nullable(),
14132
+ project: z7.string().min(1).nullable(),
13827
14133
  /** One entry per file, in output order. */
13828
- outputs: z6.array(CompanionOutputSchema).min(1)
14134
+ outputs: z7.array(CompanionOutputSchema).min(1)
13829
14135
  }).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()
14136
+ var CompanionV1Schema = z7.object({
14137
+ version: z7.literal(1),
14138
+ basedOn: z7.string().regex(/^[0-9a-f]{64}$/).nullable(),
14139
+ sha256: z7.string().regex(/^[0-9a-f]{64}$/),
14140
+ editor: z7.string().min(1),
14141
+ protocol: z7.number().int().positive(),
14142
+ savedAt: z7.string().datetime(),
14143
+ project: z7.string().min(1).nullable()
13838
14144
  }).strict();
13839
- var HandEditCompanionSchema = z6.union([CompanionV2Schema, CompanionV1Schema]).transform(
14145
+ var HandEditCompanionSchema = z7.union([CompanionV2Schema, CompanionV1Schema]).transform(
13840
14146
  (value) => value.version === 2 ? value : {
13841
14147
  version: 2,
13842
14148
  editor: value.editor,
@@ -13995,41 +14301,41 @@ import path28 from "path";
13995
14301
 
13996
14302
  // src/gallery/edit.ts
13997
14303
  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}$/),
14304
+ import { z as z8 } from "zod";
14305
+ var Base64 = z8.string().regex(/^[A-Za-z0-9+/]*={0,2}$/).max(Math.ceil(MAX_HAND_EDIT_BYTES / 3) * 4);
14306
+ var HandEditRequestSchema = z8.discriminatedUnion("action", [
14307
+ z8.object({
14308
+ action: z8.literal("start-edit"),
14309
+ project: z8.string().min(1).optional(),
14310
+ assetId: z8.string().min(1),
14311
+ styleId: z8.string().min(1),
14312
+ expectedSha256: z8.string().regex(/^[0-9a-f]{64}$/),
14007
14313
  /** Also launch the author's editor on the file. */
14008
- open: z7.boolean().optional()
14314
+ open: z8.boolean().optional()
14009
14315
  }).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}$/)
14316
+ z8.object({
14317
+ action: z8.literal("detach-edit"),
14318
+ project: z8.string().min(1).optional(),
14319
+ assetId: z8.string().min(1),
14320
+ styleId: z8.string().min(1),
14321
+ expectedSha256: z8.string().regex(/^[0-9a-f]{64}$/)
14016
14322
  }).strict(),
14017
14323
  /** 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}$/),
14324
+ z8.object({
14325
+ action: z8.literal("save-edit"),
14326
+ project: z8.string().min(1).optional(),
14327
+ assetId: z8.string().min(1),
14328
+ styleId: z8.string().min(1),
14329
+ expectedSha256: z8.string().regex(/^[0-9a-f]{64}$/),
14024
14330
  /** The flattened image; for a frame set, `frames` instead. */
14025
14331
  png: Base64.min(1).optional(),
14026
14332
  /** 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(),
14333
+ frames: z8.array(z8.object({ role: z8.string().min(1).nullable(), png: Base64.min(1) }).strict()).min(1).max(256).optional(),
14028
14334
  /** The editor's layered project file, kept beside the edit. */
14029
14335
  pxo: Base64.optional(),
14030
14336
  /** Editor identity from the bridge's ready message. */
14031
- editor: z7.string().min(1).max(200),
14032
- protocol: z7.number().int().positive()
14337
+ editor: z8.string().min(1).max(200),
14338
+ protocol: z8.number().int().positive()
14033
14339
  }).strict()
14034
14340
  ]);
14035
14341
  function createGalleryEditHandler(opts) {
@@ -15213,7 +15519,7 @@ async function serveGallery(opts) {
15213
15519
 
15214
15520
  // src/gallery/generate.ts
15215
15521
  import { randomBytes as randomBytes2 } from "crypto";
15216
- import { z as z8 } from "zod";
15522
+ import { z as z9 } from "zod";
15217
15523
 
15218
15524
  // src/pipeline/poll.ts
15219
15525
  var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
@@ -15494,6 +15800,9 @@ async function submit(provider, loaded, items, lock, lockPath, opts = {}) {
15494
15800
  ...spec.revision.lastFrameSha256 ? { lastFrameSha256: spec.revision.lastFrameSha256 } : {},
15495
15801
  ...spec.revision.direction ? { direction: spec.revision.direction } : {},
15496
15802
  ...spec.revision.enhancePrompt == null ? {} : { enhancePrompt: spec.revision.enhancePrompt },
15803
+ ...spec.revision.keypointsSha256 ? { keypointsSha256: spec.revision.keypointsSha256 } : {},
15804
+ ...spec.revision.skeletonTemplate ? { skeletonTemplate: spec.revision.skeletonTemplate } : {},
15805
+ ...spec.revision.description ? { description: spec.revision.description } : {},
15497
15806
  ...spec.revision.engine ? { engine: spec.revision.engine } : {}
15498
15807
  } : null,
15499
15808
  outfit: spec.character?.kind === "outfit" && outfitParentEntry ? {
@@ -16310,26 +16619,26 @@ async function runPicker(provider, lock, lockPath, opts = {}) {
16310
16619
  }
16311
16620
 
16312
16621
  // 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),
16622
+ var GenerateRequestSchema = z9.object({
16623
+ project: z9.string().min(1).optional(),
16624
+ keys: z9.array(z9.string().min(1)).min(1).max(500),
16316
16625
  /**
16317
16626
  * Regenerate work that is up to date, as `gen --force` does, and let the
16318
16627
  * fetch replace a locally modified or untracked file. With `restore`, put
16319
16628
  * the recorded bytes back over a modified file.
16320
16629
  */
16321
- force: z8.boolean().optional(),
16630
+ force: z9.boolean().optional(),
16322
16631
  /** Advance in-flight, review, or selected work without submitting anything. */
16323
- resume: z8.boolean().optional(),
16632
+ resume: z9.boolean().optional(),
16324
16633
  /** Re-download downloaded outputs and replace files whose object changed upstream. */
16325
- refresh: z8.boolean().optional(),
16634
+ refresh: z9.boolean().optional(),
16326
16635
  /** Put recorded outputs back on disk from the cache or the provider, without generating. */
16327
- restore: z8.boolean().optional(),
16636
+ restore: z9.boolean().optional(),
16328
16637
  /**
16329
16638
  * Bring a replaced generation back: 1-based position in the record's
16330
16639
  * history (newest first) or an output hash prefix. One key at a time.
16331
16640
  */
16332
- revert: z8.string().min(1).max(64).optional()
16641
+ revert: z9.string().min(1).max(64).optional()
16333
16642
  }).strict().refine((request) => [request.resume, request.refresh, request.restore, request.revert !== void 0].filter(Boolean).length <= 1, {
16334
16643
  message: "resume, refresh, restore, and revert are exclusive"
16335
16644
  }).refine((request) => request.revert === void 0 || request.keys.length === 1, { message: "revert takes one key" });
@@ -19215,11 +19524,11 @@ import { readFile as readFile28 } from "fs/promises";
19215
19524
  import path41 from "path";
19216
19525
  import { existsSync as existsSync30 } from "fs";
19217
19526
  import { readFile as readFile27 } from "fs/promises";
19218
- import { z as z9 } from "zod";
19527
+ import { z as z10 } from "zod";
19219
19528
  var SHA256_RE = /^[0-9a-f]{64}$/;
19220
19529
  var HEX_RE = /^#[0-9a-f]{6}$/;
19221
19530
  var MAX_REDMEAN_DISTANCE = 765;
19222
- var QualityPathSchema = z9.string().min(1).refine(
19531
+ var QualityPathSchema = z10.string().min(1).refine(
19223
19532
  (value) => !path41.posix.isAbsolute(value) && !value.includes("\\"),
19224
19533
  "expected a portable relative path"
19225
19534
  );
@@ -19233,53 +19542,53 @@ var DEFAULT_QUALITY_TOLERANCES = {
19233
19542
  maxEdgeContrastDrop: 0.03,
19234
19543
  maxIsolatedPixelIncrease: 5e-3
19235
19544
  };
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)
19545
+ var QualityToleranceSchema = z10.object({
19546
+ requireExactHash: z10.boolean(),
19547
+ maxColorCountIncrease: z10.number().int().min(0),
19548
+ maxNewColors: z10.number().int().min(0),
19549
+ maxTransparencyDelta: z10.number().min(0).max(1),
19550
+ maxPartialAlphaIncrease: z10.number().min(0).max(1),
19551
+ maxEdgeDensityDelta: z10.number().min(0).max(1),
19552
+ maxEdgeContrastDrop: z10.number().min(0).max(1),
19553
+ maxIsolatedPixelIncrease: z10.number().min(0).max(1)
19245
19554
  }).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)
19555
+ var ImageQualityMetricsSchema = z10.object({
19556
+ sha256: z10.string().regex(SHA256_RE),
19557
+ width: z10.number().int().min(1),
19558
+ height: z10.number().int().min(1),
19559
+ colorCount: z10.number().int().min(0),
19560
+ colors: z10.array(z10.string().regex(HEX_RE)),
19561
+ transparency: z10.number().min(0).max(1),
19562
+ partialAlpha: z10.number().min(0).max(1),
19563
+ edgeDensity: z10.number().min(0).max(1),
19564
+ meanEdgeContrast: z10.number().min(0).max(1),
19565
+ isolatedPixelRatio: z10.number().min(0).max(1)
19257
19566
  }).strict();
19258
- var QualityBaselineCaseSchema = z9.object({
19259
- id: z9.string().regex(/^[a-z0-9][a-z0-9/_-]*$/),
19567
+ var QualityBaselineCaseSchema = z10.object({
19568
+ id: z10.string().regex(/^[a-z0-9][a-z0-9/_-]*$/),
19260
19569
  file: QualityPathSchema,
19261
- record: z9.object({
19570
+ record: z10.object({
19262
19571
  path: QualityPathSchema,
19263
- sha256: z9.string().regex(SHA256_RE)
19572
+ sha256: z10.string().regex(SHA256_RE)
19264
19573
  }).strict().optional(),
19265
19574
  expected: ImageQualityMetricsSchema,
19266
19575
  tolerances: QualityToleranceSchema
19267
19576
  }).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)
19577
+ var QualityBaselineSchema = z10.object({
19578
+ $schema: z10.string().url().optional(),
19579
+ format: z10.literal("pixelkiln-quality-baseline"),
19580
+ schemaVersion: z10.literal(1),
19581
+ cases: z10.array(QualityBaselineCaseSchema).min(1)
19273
19582
  }).strict().superRefine((baseline, context) => {
19274
19583
  const ids = baseline.cases.map((entry) => entry.id);
19275
19584
  if (new Set(ids).size !== ids.length) {
19276
- context.addIssue({ code: z9.ZodIssueCode.custom, path: ["cases"], message: "duplicate case id" });
19585
+ context.addIssue({ code: z10.ZodIssueCode.custom, path: ["cases"], message: "duplicate case id" });
19277
19586
  }
19278
19587
  });
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(),
19588
+ var QualityInputSchema = z10.object({
19589
+ id: z10.string().regex(/^[a-z0-9][a-z0-9/_-]*$/),
19590
+ path: z10.string().min(1),
19591
+ record: z10.string().min(1).optional(),
19283
19592
  tolerances: QualityToleranceSchema.partial().optional()
19284
19593
  }).strict();
19285
19594
  function message4(error) {
@@ -19381,7 +19690,7 @@ async function measureImageQuality(file) {
19381
19690
  }
19382
19691
  }
19383
19692
  function resolveQualityInputs(raw, inputsFilePath) {
19384
- const parsed = z9.array(QualityInputSchema).min(1).safeParse(raw);
19693
+ const parsed = z10.array(QualityInputSchema).min(1).safeParse(raw);
19385
19694
  if (!parsed.success) {
19386
19695
  throw new Error(
19387
19696
  "--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 +19965,47 @@ import path43 from "path";
19656
19965
  import { existsSync as existsSync31 } from "fs";
19657
19966
  import { readdir as readdir3, readFile as readFile29 } from "fs/promises";
19658
19967
  import { fileURLToPath } from "url";
19659
- import { z as z10 } from "zod";
19968
+ import { z as z11 } from "zod";
19660
19969
  var SHA256_RE2 = /^[0-9a-f]{64}$/;
19661
19970
  var RECIPE_ID_RE = /^[a-z0-9][a-z0-9-]*\/[a-z0-9][a-z0-9-]*$/;
19662
19971
  var VERSION_RE = /^\d+\.\d+\.\d+$/;
19663
19972
  var RECIPE_FILE = "pixelkiln.recipe.json";
19664
19973
  var RECIPE_DIR_TOKEN = "{{recipeDir}}";
19665
- var PortablePathSchema = z10.string().min(1).refine((value) => {
19974
+ var PortablePathSchema = z11.string().min(1).refine((value) => {
19666
19975
  if (value.includes("\\") || path43.posix.isAbsolute(value)) return false;
19667
19976
  const parts = value.split("/");
19668
19977
  return !parts.includes("") && !parts.includes(".") && !parts.includes("..");
19669
19978
  }, "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)
19979
+ var RecipeBindingSchema = z11.object({
19980
+ nodeId: z11.string().min(1),
19981
+ input: z11.string().min(1)
19673
19982
  }).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({
19983
+ var RecipeSchema = z11.object({
19984
+ $schema: z11.string().url().optional(),
19985
+ format: z11.literal("pixelkiln-recipe"),
19986
+ schemaVersion: z11.literal(1),
19987
+ id: z11.string().regex(RECIPE_ID_RE, "expected provider/name"),
19988
+ version: z11.string().regex(VERSION_RE, "expected x.y.z"),
19989
+ provider: z11.string().regex(/^[a-z0-9][a-z0-9-]*$/),
19990
+ summary: z11.string().min(1).max(240),
19991
+ files: z11.array(z11.object({
19683
19992
  path: PortablePathSchema,
19684
- role: z10.enum(["workflow", "reference"]),
19685
- sha256: z10.string().regex(SHA256_RE2)
19993
+ role: z11.enum(["workflow", "reference"]),
19994
+ sha256: z11.string().regex(SHA256_RE2)
19686
19995
  }).strict()).default([]),
19687
- models: z10.array(z10.object({
19996
+ models: z11.array(z11.object({
19688
19997
  path: PortablePathSchema,
19689
- sha256: z10.string().regex(SHA256_RE2),
19690
- source: z10.string().url(),
19691
- license: z10.string().min(1)
19998
+ sha256: z11.string().regex(SHA256_RE2),
19999
+ source: z11.string().url(),
20000
+ license: z11.string().min(1)
19692
20001
  }).strict()).default([]),
19693
- styleId: z10.string().regex(/^[a-z0-9][a-z0-9-]*$/),
20002
+ styleId: z11.string().regex(/^[a-z0-9][a-z0-9-]*$/),
19694
20003
  style: StyleSchema,
19695
- workflow: z10.object({
20004
+ workflow: z11.object({
19696
20005
  path: PortablePathSchema,
19697
- outputNodeId: z10.string().min(1),
19698
- numImages: z10.number().int().min(1).max(16),
19699
- bindings: z10.object({
20006
+ outputNodeId: z11.string().min(1),
20007
+ numImages: z11.number().int().min(1).max(16),
20008
+ bindings: z11.object({
19700
20009
  prompt: RecipeBindingSchema,
19701
20010
  width: RecipeBindingSchema.optional(),
19702
20011
  height: RecipeBindingSchema.optional(),
@@ -19707,67 +20016,67 @@ var RecipeSchema = z10.object({
19707
20016
  strength: RecipeBindingSchema.optional()
19708
20017
  }).strict()
19709
20018
  }).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)
20019
+ quality: z11.object({
20020
+ stage: z11.enum(["composition-source", "production-candidate"]),
20021
+ workingCanvas: z11.object({
20022
+ width: z11.number().int().min(16),
20023
+ height: z11.number().int().min(16)
19715
20024
  }).strict(),
19716
- recommendedNativeSize: z10.object({
19717
- min: z10.number().int().min(1),
19718
- max: z10.number().int().min(1)
20025
+ recommendedNativeSize: z11.object({
20026
+ min: z11.number().int().min(1),
20027
+ max: z11.number().int().min(1)
19719
20028
  }).strict(),
19720
- paletteColors: z10.object({
19721
- min: z10.number().int().min(1),
19722
- max: z10.number().int().min(1)
20029
+ paletteColors: z11.object({
20030
+ min: z11.number().int().min(1),
20031
+ max: z11.number().int().min(1)
19723
20032
  }).strict(),
19724
- background: z10.enum(["opaque", "transparent", "full-bleed"]),
19725
- checks: z10.array(z10.enum([
20033
+ background: z11.enum(["opaque", "transparent", "full-bleed"]),
20034
+ checks: z11.array(z11.enum([
19726
20035
  "prompt-coverage",
19727
20036
  "native-grid",
19728
20037
  "final-palette",
19729
20038
  "alpha-edge",
19730
20039
  "human-1x"
19731
20040
  ])).min(1),
19732
- notes: z10.array(z10.string().min(1)).default([])
20041
+ notes: z11.array(z11.string().min(1)).default([])
19733
20042
  }).strict(),
19734
- integrity: z10.object({
19735
- algorithm: z10.literal("sha256"),
19736
- digest: z10.string().regex(SHA256_RE2)
20043
+ integrity: z11.object({
20044
+ algorithm: z11.literal("sha256"),
20045
+ digest: z11.string().regex(SHA256_RE2)
19737
20046
  }).strict()
19738
20047
  }).strict().superRefine((recipe, context) => {
19739
20048
  if (recipe.style.provider && recipe.style.provider !== recipe.provider) {
19740
20049
  context.addIssue({
19741
- code: z10.ZodIssueCode.custom,
20050
+ code: z11.ZodIssueCode.custom,
19742
20051
  path: ["style", "provider"],
19743
20052
  message: "must match the recipe provider"
19744
20053
  });
19745
20054
  }
19746
20055
  const filePaths = recipe.files.map((file) => file.path);
19747
20056
  if (new Set(filePaths).size !== filePaths.length) {
19748
- context.addIssue({ code: z10.ZodIssueCode.custom, path: ["files"], message: "duplicate file path" });
20057
+ context.addIssue({ code: z11.ZodIssueCode.custom, path: ["files"], message: "duplicate file path" });
19749
20058
  }
19750
20059
  const modelPaths = recipe.models.map((model) => model.path);
19751
20060
  if (new Set(modelPaths).size !== modelPaths.length) {
19752
- context.addIssue({ code: z10.ZodIssueCode.custom, path: ["models"], message: "duplicate model path" });
20061
+ context.addIssue({ code: z11.ZodIssueCode.custom, path: ["models"], message: "duplicate model path" });
19753
20062
  }
19754
20063
  if (filePaths.includes(RECIPE_FILE)) {
19755
20064
  context.addIssue({
19756
- code: z10.ZodIssueCode.custom,
20065
+ code: z11.ZodIssueCode.custom,
19757
20066
  path: ["files"],
19758
20067
  message: `${RECIPE_FILE} cannot hash itself`
19759
20068
  });
19760
20069
  }
19761
20070
  if (recipe.quality.recommendedNativeSize.min > recipe.quality.recommendedNativeSize.max) {
19762
20071
  context.addIssue({
19763
- code: z10.ZodIssueCode.custom,
20072
+ code: z11.ZodIssueCode.custom,
19764
20073
  path: ["quality", "recommendedNativeSize"],
19765
20074
  message: "min cannot exceed max"
19766
20075
  });
19767
20076
  }
19768
20077
  if (recipe.quality.paletteColors.min > recipe.quality.paletteColors.max) {
19769
20078
  context.addIssue({
19770
- code: z10.ZodIssueCode.custom,
20079
+ code: z11.ZodIssueCode.custom,
19771
20080
  path: ["quality", "paletteColors"],
19772
20081
  message: "min cannot exceed max"
19773
20082
  });
@@ -19776,7 +20085,7 @@ var RecipeSchema = z10.object({
19776
20085
  (file) => file.path === recipe.workflow.path && file.role === "workflow"
19777
20086
  )) {
19778
20087
  context.addIssue({
19779
- code: z10.ZodIssueCode.custom,
20088
+ code: z11.ZodIssueCode.custom,
19780
20089
  path: ["workflow", "path"],
19781
20090
  message: "must name a file whose role is workflow"
19782
20091
  });
@@ -19784,7 +20093,7 @@ var RecipeSchema = z10.object({
19784
20093
  if (recipe.provider === "comfyui") {
19785
20094
  if (!recipe.workflow) {
19786
20095
  context.addIssue({
19787
- code: z10.ZodIssueCode.custom,
20096
+ code: z11.ZodIssueCode.custom,
19788
20097
  path: ["workflow"],
19789
20098
  message: "ComfyUI recipes require workflow metadata"
19790
20099
  });
@@ -19793,7 +20102,7 @@ var RecipeSchema = z10.object({
19793
20102
  const options = recipe.style.providerOptions.comfyui;
19794
20103
  if (!options || typeof options !== "object" || Array.isArray(options)) {
19795
20104
  context.addIssue({
19796
- code: z10.ZodIssueCode.custom,
20105
+ code: z11.ZodIssueCode.custom,
19797
20106
  path: ["style", "providerOptions", "comfyui"],
19798
20107
  message: "ComfyUI recipes require ComfyUI provider options"
19799
20108
  });
@@ -19807,7 +20116,7 @@ var RecipeSchema = z10.object({
19807
20116
  };
19808
20117
  if (JSON.stringify(canonical3(options)) !== JSON.stringify(canonical3(expected))) {
19809
20118
  context.addIssue({
19810
- code: z10.ZodIssueCode.custom,
20119
+ code: z11.ZodIssueCode.custom,
19811
20120
  path: ["style", "providerOptions", "comfyui"],
19812
20121
  message: "must match workflow metadata and use {{recipeDir}} for workflowFile"
19813
20122
  });
@@ -20226,6 +20535,30 @@ async function runRefine(args) {
20226
20535
  if (result.failed) process.exitCode = 1;
20227
20536
  }
20228
20537
 
20538
+ // src/cli/commands/skeleton.ts
20539
+ import path46 from "path";
20540
+ import { readFile as readFile30, writeFile as writeFile15 } from "fs/promises";
20541
+ async function runEstimateSkeleton(args) {
20542
+ const image = args.target;
20543
+ if (!image) throw new UsageError("estimate-skeleton needs an image path: pixelkiln estimate-skeleton <image> [--out keypoints.json]");
20544
+ loadEnvFiles(path46.dirname(path46.resolve(args.manifest)));
20545
+ loadEnvFiles(process.cwd());
20546
+ const bytes = await readFile30(path46.resolve(image));
20547
+ const metadata = imageMetadata(bytes);
20548
+ if (!metadata) throw new UsageError(`${image} is not a readable PNG or JPEG`);
20549
+ const client2 = clientFromEnv();
20550
+ const res = await client2.estimateSkeleton({
20551
+ image: { base64: bytes.toString("base64"), format: metadata.format }
20552
+ });
20553
+ const json = JSON.stringify(res.keypoints, null, 2);
20554
+ if (args.out) {
20555
+ await writeFile15(path46.resolve(args.out), json + "\n");
20556
+ log(` wrote ${res.keypoints.length} keypoints to ${args.out}`);
20557
+ } else {
20558
+ log(json);
20559
+ }
20560
+ }
20561
+
20229
20562
  // src/cli/commands/tools.ts
20230
20563
  async function runTools(args) {
20231
20564
  const mb = (bytes) => bytes < 1e6 ? `${(bytes / 1e3).toFixed(0)} kB` : `${(bytes / 1e6).toFixed(1)} MB`;
@@ -20268,15 +20601,15 @@ async function runTools(args) {
20268
20601
  }
20269
20602
 
20270
20603
  // src/cli/commands/workspace.ts
20271
- import path46 from "path";
20604
+ import path47 from "path";
20272
20605
  import { existsSync as existsSync32 } from "fs";
20273
20606
  async function runWorkspace(args) {
20274
- const workspacePath = path46.resolve(args.workspace ?? "pixelkiln.workspace.json");
20275
- const dir = path46.dirname(workspacePath);
20607
+ const workspacePath = path47.resolve(args.workspace ?? "pixelkiln.workspace.json");
20608
+ const dir = path47.dirname(workspacePath);
20276
20609
  if (args.subcommand === "add") {
20277
- const manifestPath = path46.resolve(args.target);
20610
+ const manifestPath = path47.resolve(args.target);
20278
20611
  const loadedTarget = await loadManifest(manifestPath);
20279
- const lockPath = args.explicitLock ? path46.resolve(args.explicitLock) : path46.join(path46.dirname(manifestPath), "pixelkiln.lock.json");
20612
+ const lockPath = args.explicitLock ? path47.resolve(args.explicitLock) : path47.join(path47.dirname(manifestPath), "pixelkiln.lock.json");
20280
20613
  const ws = await loadWorkspace(workspacePath);
20281
20614
  const id = args.name ?? loadedTarget.manifest.name;
20282
20615
  if (ws.projects.some((p) => p.id === id)) {
@@ -20296,7 +20629,7 @@ async function runWorkspace(args) {
20296
20629
  ...args.account ? { account: args.account } : {}
20297
20630
  };
20298
20631
  await saveWorkspace(workspacePath, { version: 1, projects: [...ws.projects, project] });
20299
- log(` registered "${id}" in ${path46.relative(process.cwd(), workspacePath)}`);
20632
+ log(` registered "${id}" in ${path47.relative(process.cwd(), workspacePath)}`);
20300
20633
  log(` manifest: ${project.manifest}`);
20301
20634
  log(` lock: ${project.lock}`);
20302
20635
  if (!existsSync32(lockPath)) {
@@ -20308,7 +20641,7 @@ async function runWorkspace(args) {
20308
20641
  }
20309
20642
  if (args.subcommand === "remove") {
20310
20643
  const ws = await loadWorkspace(workspacePath);
20311
- const resolvedTarget = path46.resolve(args.target);
20644
+ const resolvedTarget = path47.resolve(args.target);
20312
20645
  const match = ws.projects.find(
20313
20646
  (p) => p.id === args.target || resolveProject(dir, p).manifestPath === resolvedTarget
20314
20647
  );
@@ -20319,7 +20652,7 @@ async function runWorkspace(args) {
20319
20652
  version: 1,
20320
20653
  projects: ws.projects.filter((p) => p !== match)
20321
20654
  });
20322
- log(` removed "${match.id}" from ${path46.relative(process.cwd(), workspacePath)}`);
20655
+ log(` removed "${match.id}" from ${path47.relative(process.cwd(), workspacePath)}`);
20323
20656
  return;
20324
20657
  }
20325
20658
  if (args.subcommand === "list") {
@@ -20331,9 +20664,9 @@ async function runWorkspace(args) {
20331
20664
  if (args.json) {
20332
20665
  log(JSON.stringify({ version: 1, workspace: workspacePath, projects: ws.projects, diagnostics }, null, 2));
20333
20666
  } else if (!ws.projects.length) {
20334
- log(` no projects registered in ${path46.relative(process.cwd(), workspacePath)}`);
20667
+ log(` no projects registered in ${path47.relative(process.cwd(), workspacePath)}`);
20335
20668
  } else {
20336
- log(` ${ws.projects.length} project(s) in ${path46.relative(process.cwd(), workspacePath)}:`);
20669
+ log(` ${ws.projects.length} project(s) in ${path47.relative(process.cwd(), workspacePath)}:`);
20337
20670
  for (const p of ws.projects) {
20338
20671
  log(` ${p.id.padEnd(24)} ${p.manifest.padEnd(40)} (${p.provider}${p.account ? `, ${p.account}` : ""})`);
20339
20672
  }
@@ -20351,7 +20684,7 @@ async function runWorkspace(args) {
20351
20684
  if (args.json) {
20352
20685
  log(JSON.stringify({ ...report, workspace: workspacePath }, null, 2));
20353
20686
  } else {
20354
- log(` workspace: ${path46.relative(process.cwd(), workspacePath)}`);
20687
+ log(` workspace: ${path47.relative(process.cwd(), workspacePath)}`);
20355
20688
  for (const p of report.projects) {
20356
20689
  if (p.error) {
20357
20690
  log(`
@@ -20406,7 +20739,7 @@ async function runVersion() {
20406
20739
  for (const up of ["../package.json", "../../package.json"]) {
20407
20740
  const url = new URL(up, import.meta.url);
20408
20741
  if (!existsSync33(url)) continue;
20409
- const pkg = JSON.parse(await readFile30(url, "utf8"));
20742
+ const pkg = JSON.parse(await readFile31(url, "utf8"));
20410
20743
  if (pkg.name === "pixelkiln") {
20411
20744
  log(`${pkg.name} ${pkg.version}`);
20412
20745
  return;
@@ -20450,6 +20783,7 @@ var RUNNERS = {
20450
20783
  refine: runRefine,
20451
20784
  recipe: runRecipe,
20452
20785
  workspace: runWorkspace,
20786
+ "estimate-skeleton": runEstimateSkeleton,
20453
20787
  unzoom: runUnzoom,
20454
20788
  font: runFont
20455
20789
  };