pixelkiln 0.69.0 → 0.70.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/index.js CHANGED
@@ -1732,9 +1732,9 @@ var PixelLabClient = class {
1732
1732
  apiKey;
1733
1733
  http;
1734
1734
  /** Retries transport failures, 429, and 5xx; see http.ts for the policy. */
1735
- async request(path34, init) {
1735
+ async request(path35, init) {
1736
1736
  const auth = this.apiKey.startsWith("Bearer ") ? this.apiKey : `Bearer ${this.apiKey}`;
1737
- const res = await this.http(`${BASE}${path34}`, {
1737
+ const res = await this.http(`${BASE}${path35}`, {
1738
1738
  ...init,
1739
1739
  headers: {
1740
1740
  Authorization: auth,
@@ -1744,13 +1744,13 @@ var PixelLabClient = class {
1744
1744
  });
1745
1745
  const text = await res.text();
1746
1746
  if (!res.ok) {
1747
- throw new PixelLabError(`${init?.method ?? "GET"} ${path34} \u2192 ${res.status}`, res.status, text);
1747
+ throw new PixelLabError(`${init?.method ?? "GET"} ${path35} \u2192 ${res.status}`, res.status, text);
1748
1748
  }
1749
1749
  if (!text) return {};
1750
1750
  try {
1751
1751
  return JSON.parse(text);
1752
1752
  } catch {
1753
- throw new Error(`${init?.method ?? "GET"} ${path34} returned invalid JSON`);
1753
+ throw new Error(`${init?.method ?? "GET"} ${path35} returned invalid JSON`);
1754
1754
  }
1755
1755
  }
1756
1756
  async balance() {
@@ -2142,10 +2142,10 @@ var PixelLabClient = class {
2142
2142
  const palette = args.paletteSwatchBase64 ? { color_image: { type: "base64", base64: args.paletteSwatchBase64, format: "png" }, force_colors: true } : {};
2143
2143
  const encode = (image) => ({ type: "base64", base64: image.base64, format: image.format });
2144
2144
  const south = args.reference?.south;
2145
- let path34;
2145
+ let path35;
2146
2146
  let body;
2147
2147
  if (args.mode === "v3") {
2148
- path34 = "/create-character-v3";
2148
+ path35 = "/create-character-v3";
2149
2149
  body = {
2150
2150
  description: args.description,
2151
2151
  image_size: imageSize,
@@ -2159,7 +2159,7 @@ var PixelLabClient = class {
2159
2159
  ...args.enhancePrompt ? { enhance_prompt: true } : {}
2160
2160
  };
2161
2161
  } else if (args.mode === "pro-flash") {
2162
- path34 = "/create-character-pro-flash";
2162
+ path35 = "/create-character-pro-flash";
2163
2163
  const traits = args.styleTraits;
2164
2164
  body = {
2165
2165
  description: args.description,
@@ -2183,7 +2183,7 @@ var PixelLabClient = class {
2183
2183
  } : {}
2184
2184
  };
2185
2185
  } else if (args.mode === "pro") {
2186
- path34 = "/create-character-pro";
2186
+ path35 = "/create-character-pro";
2187
2187
  body = {
2188
2188
  description: args.description,
2189
2189
  image_size: imageSize,
@@ -2197,7 +2197,7 @@ var PixelLabClient = class {
2197
2197
  ...!south && args.styleCharacterId ? { style_character_id: args.styleCharacterId } : {}
2198
2198
  };
2199
2199
  } else {
2200
- path34 = args.directions === 4 ? "/create-character-with-4-directions" : "/create-character-with-8-directions";
2200
+ path35 = args.directions === 4 ? "/create-character-with-4-directions" : "/create-character-with-8-directions";
2201
2201
  body = {
2202
2202
  description: args.description,
2203
2203
  image_size: imageSize,
@@ -2214,8 +2214,8 @@ var PixelLabClient = class {
2214
2214
  ...palette
2215
2215
  };
2216
2216
  }
2217
- const raw = await this.request(path34, { method: "POST", body: JSON.stringify(body) });
2218
- return validateResponse(CharacterSubmitSchema, raw, path34.slice(1));
2217
+ const raw = await this.request(path35, { method: "POST", body: JSON.stringify(body) });
2218
+ return validateResponse(CharacterSubmitSchema, raw, path35.slice(1));
2219
2219
  }
2220
2220
  /** A text edit of an existing character, applied to every direction and stored as a sibling. */
2221
2221
  async createCharacterState(args) {
@@ -3393,10 +3393,10 @@ import { createReadStream } from "fs";
3393
3393
  function sha256(data) {
3394
3394
  return createHash("sha256").update(data).digest("hex");
3395
3395
  }
3396
- async function sha256File(path34) {
3396
+ async function sha256File(path35) {
3397
3397
  return new Promise((resolve, reject) => {
3398
3398
  const hash = createHash("sha256");
3399
- const stream = createReadStream(path34);
3399
+ const stream = createReadStream(path35);
3400
3400
  stream.on("data", (chunk2) => hash.update(chunk2));
3401
3401
  stream.on("error", reject);
3402
3402
  stream.on("end", () => resolve(hash.digest("hex")));
@@ -5759,9 +5759,9 @@ var RetroDiffusionClient = class {
5759
5759
  if (!response.ok) throw new ProviderError("retrodiffusion", `Retro Diffusion download failed (${response.status})`, { status: response.status });
5760
5760
  return Buffer.from(await response.arrayBuffer());
5761
5761
  }
5762
- async call(path34, init = {}) {
5762
+ async call(path35, init = {}) {
5763
5763
  if (!this.token) throw new Error("RD_API_KEY is not set");
5764
- const response = await this.request(`${this.baseUrl}${path34}`, {
5764
+ const response = await this.request(`${this.baseUrl}${path35}`, {
5765
5765
  ...init,
5766
5766
  headers: {
5767
5767
  "Content-Type": "application/json",
@@ -12050,10 +12050,10 @@ function isSha256Hash(value) {
12050
12050
  function parseCache(value) {
12051
12051
  return HashCacheSchema.parse(value);
12052
12052
  }
12053
- async function loadCache(path34) {
12054
- if (!existsSync13(path34)) return { version: 1, hashes: {} };
12053
+ async function loadCache(path35) {
12054
+ if (!existsSync13(path35)) return { version: 1, hashes: {} };
12055
12055
  try {
12056
- const parsed = HashCacheSchema.safeParse(JSON.parse(await readFile11(path34, "utf8")));
12056
+ const parsed = HashCacheSchema.safeParse(JSON.parse(await readFile11(path35, "utf8")));
12057
12057
  if (!parsed.success) return { version: 1, hashes: {} };
12058
12058
  return {
12059
12059
  version: 1,
@@ -12065,18 +12065,18 @@ async function loadCache(path34) {
12065
12065
  return { version: 1, hashes: {} };
12066
12066
  }
12067
12067
  }
12068
- async function saveCache(path34, cache) {
12068
+ async function saveCache(path35, cache) {
12069
12069
  const sorted = {};
12070
12070
  for (const key of Object.keys(cache.hashes).sort()) {
12071
12071
  const hash = cache.hashes[key];
12072
12072
  if (!isSha256Hash(hash)) throw new Error(`Refusing to cache invalid SHA-256 for ${key}`);
12073
12073
  sorted[key] = hash;
12074
12074
  }
12075
- await mkdir5(pathModule.dirname(pathModule.resolve(path34)), { recursive: true });
12076
- const tmp = `${path34}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
12075
+ await mkdir5(pathModule.dirname(pathModule.resolve(path35)), { recursive: true });
12076
+ const tmp = `${path35}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
12077
12077
  try {
12078
12078
  await writeFile5(tmp, JSON.stringify({ version: 1, hashes: sorted }, null, 2) + "\n");
12079
- await rename4(tmp, path34);
12079
+ await rename4(tmp, path35);
12080
12080
  } finally {
12081
12081
  await rm4(tmp, { force: true });
12082
12082
  }
@@ -14881,9 +14881,9 @@ async function runPicker(provider, lock, lockPath, opts = {}) {
14881
14881
 
14882
14882
  // src/gallery/snapshot.ts
14883
14883
  import { createHash as createHash4 } from "crypto";
14884
- import { existsSync as existsSync23 } from "fs";
14885
- import { readFile as readFile21, stat as stat3 } from "fs/promises";
14886
- import path29 from "path";
14884
+ import { existsSync as existsSync24 } from "fs";
14885
+ import { readFile as readFile22, stat as stat3 } from "fs/promises";
14886
+ import path30 from "path";
14887
14887
 
14888
14888
  // src/workspace.ts
14889
14889
  import { mkdir as mkdir7, readFile as readFile18, rename as rename6, rm as rm7, writeFile as writeFile9 } from "fs/promises";
@@ -15019,8 +15019,8 @@ function validateWorkspace(ws, dir) {
15019
15019
  }
15020
15020
 
15021
15021
  // src/gallery/edit.ts
15022
- import path28 from "path";
15023
- import { z as z10 } from "zod";
15022
+ import path29 from "path";
15023
+ import { z as z11 } from "zod";
15024
15024
 
15025
15025
  // src/manifest-edit.ts
15026
15026
  import { readFile as readFile19, rename as rename7, rm as rm8, writeFile as writeFile10 } from "fs/promises";
@@ -15068,15 +15068,27 @@ var NewAssetSchema = z8.object({
15068
15068
  /**
15069
15069
  * A controlled regeneration of another asset in the same manifest,
15070
15070
  * instead of a fresh generation. `inpaint` needs a mask upload the
15071
- * gallery does not offer yet, so only `image-to-image` can be created
15072
- * here; hand-edit the manifest for the other modes.
15071
+ * gallery does not offer yet, so only `image-to-image` and
15072
+ * `animate-skeleton` (whose keypoints file the gallery writes first) can
15073
+ * be created here; hand-edit the manifest for the other modes.
15073
15074
  */
15074
- revision: z8.object({
15075
- mode: z8.literal("image-to-image"),
15076
- /** Asset id of the parent this revises; must already exist. */
15077
- from: z8.string().min(1),
15078
- strength: z8.number().min(0).max(1).optional()
15079
- }).strict().optional(),
15075
+ revision: z8.discriminatedUnion("mode", [
15076
+ z8.object({
15077
+ mode: z8.literal("image-to-image"),
15078
+ /** Asset id of the parent this revises; must already exist. */
15079
+ from: z8.string().min(1),
15080
+ strength: z8.number().min(0).max(1).optional()
15081
+ }).strict(),
15082
+ z8.object({
15083
+ mode: z8.literal("animate-skeleton"),
15084
+ from: z8.string().min(1),
15085
+ /** Manifest-relative; checked when the manifest is loaded, like every revision input. */
15086
+ keypointsFile: z8.string().min(1),
15087
+ direction: CharacterDirectionSchema,
15088
+ description: z8.string().min(1).optional(),
15089
+ skeletonTemplate: z8.string().min(1).optional()
15090
+ }).strict()
15091
+ ]).optional(),
15080
15092
  /** A pose or outfit of an existing `character`/`objectPro` base or state. */
15081
15093
  state: NewStateSchema.optional(),
15082
15094
  /** A loop of an existing `character`/`objectPro` base or state. */
@@ -15580,41 +15592,118 @@ function openInEditor(file, editor = process.env.PIXELKILN_EDITOR) {
15580
15592
  return openExternal(file, command);
15581
15593
  }
15582
15594
 
15595
+ // src/gallery/skeleton.ts
15596
+ import { existsSync as existsSync23 } from "fs";
15597
+ import { mkdir as mkdir9, readFile as readFile21, rm as rm10, writeFile as writeFile12 } from "fs/promises";
15598
+ import path28 from "path";
15599
+ import { z as z10 } from "zod";
15600
+ var SkeletonEstimateRequestSchema = z10.object({
15601
+ project: z10.string().min(1).optional(),
15602
+ styleId: z10.string().min(1),
15603
+ assetId: z10.string().min(1),
15604
+ frames: z10.number().int().min(3).max(15).optional()
15605
+ }).strict();
15606
+ var SkeletonAnimationRequestSchema = z10.object({
15607
+ action: z10.literal("create-skeleton-animation"),
15608
+ project: z10.string().min(1).optional(),
15609
+ expectedSha256: z10.string().regex(/^[0-9a-f]{64}$/),
15610
+ styleId: z10.string().min(1),
15611
+ /** The sprite the animation poses. */
15612
+ from: z10.string().min(1),
15613
+ assetId: z10.string().min(1).regex(/^[^/\\]+$/, "asset ids cannot contain slashes"),
15614
+ /** The motion, sent as PixelLab's `action`. */
15615
+ prompt: z10.string().min(1),
15616
+ direction: CharacterDirectionSchema,
15617
+ description: z10.string().min(1).optional(),
15618
+ skeletonTemplate: z10.string().min(1).optional(),
15619
+ /** Manifest-relative JSON path inside the project. */
15620
+ keypointsFile: z10.string().min(1),
15621
+ /** Poses to write to `keypointsFile`; omitted to use a file that already exists. */
15622
+ set: SkeletonSetSchema.optional(),
15623
+ /** Replace an existing `keypointsFile` with `set`. */
15624
+ overwrite: z10.boolean().optional()
15625
+ }).strict();
15626
+ async function createSkeletonAnimation(manifestPath, request) {
15627
+ const root = path28.dirname(path28.resolve(manifestPath));
15628
+ const file = path28.resolve(root, request.keypointsFile);
15629
+ const relative = path28.relative(root, file);
15630
+ if (!request.keypointsFile.endsWith(".json") || relative.startsWith("..") || path28.isAbsolute(relative)) {
15631
+ throw new ManifestEditError("the keypoints file must be a .json path inside the project");
15632
+ }
15633
+ const existed = existsSync23(file);
15634
+ let previous = null;
15635
+ if (request.set) {
15636
+ if (existed && !request.overwrite) {
15637
+ throw new ManifestEditError(`${request.keypointsFile} already exists; choose another path, or replace it`);
15638
+ }
15639
+ if (existed) previous = await readFile21(file);
15640
+ await mkdir9(path28.dirname(file), { recursive: true });
15641
+ await writeFile12(file, JSON.stringify(request.set, null, 2) + "\n");
15642
+ } else if (!existed) {
15643
+ throw new ManifestEditError(`${request.keypointsFile} does not exist; paste poses or estimate them from the sprite`);
15644
+ }
15645
+ try {
15646
+ await applyManifestEdit(manifestPath, {
15647
+ action: "add-asset",
15648
+ assetId: request.assetId,
15649
+ expectedSha256: request.expectedSha256,
15650
+ asset: {
15651
+ prompt: request.prompt,
15652
+ styles: [request.styleId],
15653
+ revision: {
15654
+ mode: "animate-skeleton",
15655
+ from: request.from,
15656
+ keypointsFile: relative.split(path28.sep).join("/"),
15657
+ direction: request.direction,
15658
+ ...request.description ? { description: request.description } : {},
15659
+ ...request.skeletonTemplate ? { skeletonTemplate: request.skeletonTemplate } : {}
15660
+ }
15661
+ }
15662
+ });
15663
+ } catch (error) {
15664
+ if (request.set) {
15665
+ if (previous) await writeFile12(file, previous);
15666
+ else await rm10(file, { force: true });
15667
+ }
15668
+ throw error;
15669
+ }
15670
+ }
15671
+
15583
15672
  // src/gallery/edit.ts
15584
- var Base64 = z10.string().regex(/^[A-Za-z0-9+/]*={0,2}$/).max(Math.ceil(MAX_HAND_EDIT_BYTES / 3) * 4);
15585
- var HandEditRequestSchema = z10.discriminatedUnion("action", [
15586
- z10.object({
15587
- action: z10.literal("start-edit"),
15588
- project: z10.string().min(1).optional(),
15589
- assetId: z10.string().min(1),
15590
- styleId: z10.string().min(1),
15591
- expectedSha256: z10.string().regex(/^[0-9a-f]{64}$/),
15673
+ var Base64 = z11.string().regex(/^[A-Za-z0-9+/]*={0,2}$/).max(Math.ceil(MAX_HAND_EDIT_BYTES / 3) * 4);
15674
+ var HandEditRequestSchema = z11.discriminatedUnion("action", [
15675
+ z11.object({
15676
+ action: z11.literal("start-edit"),
15677
+ project: z11.string().min(1).optional(),
15678
+ assetId: z11.string().min(1),
15679
+ styleId: z11.string().min(1),
15680
+ expectedSha256: z11.string().regex(/^[0-9a-f]{64}$/),
15592
15681
  /** Also launch the author's editor on the file. */
15593
- open: z10.boolean().optional()
15682
+ open: z11.boolean().optional()
15594
15683
  }).strict(),
15595
- z10.object({
15596
- action: z10.literal("detach-edit"),
15597
- project: z10.string().min(1).optional(),
15598
- assetId: z10.string().min(1),
15599
- styleId: z10.string().min(1),
15600
- expectedSha256: z10.string().regex(/^[0-9a-f]{64}$/)
15684
+ z11.object({
15685
+ action: z11.literal("detach-edit"),
15686
+ project: z11.string().min(1).optional(),
15687
+ assetId: z11.string().min(1),
15688
+ styleId: z11.string().min(1),
15689
+ expectedSha256: z11.string().regex(/^[0-9a-f]{64}$/)
15601
15690
  }).strict(),
15602
15691
  /** From the in-browser editor: the page holds the bytes, the server validates and writes. */
15603
- z10.object({
15604
- action: z10.literal("save-edit"),
15605
- project: z10.string().min(1).optional(),
15606
- assetId: z10.string().min(1),
15607
- styleId: z10.string().min(1),
15608
- expectedSha256: z10.string().regex(/^[0-9a-f]{64}$/),
15692
+ z11.object({
15693
+ action: z11.literal("save-edit"),
15694
+ project: z11.string().min(1).optional(),
15695
+ assetId: z11.string().min(1),
15696
+ styleId: z11.string().min(1),
15697
+ expectedSha256: z11.string().regex(/^[0-9a-f]{64}$/),
15609
15698
  /** The flattened image; for a frame set, `frames` instead. */
15610
15699
  png: Base64.min(1).optional(),
15611
15700
  /** Every frame of a set, tagged with the role it was opened under (null for one added in the editor). */
15612
- frames: z10.array(z10.object({ role: z10.string().min(1).nullable(), png: Base64.min(1) }).strict()).min(1).max(256).optional(),
15701
+ frames: z11.array(z11.object({ role: z11.string().min(1).nullable(), png: Base64.min(1) }).strict()).min(1).max(256).optional(),
15613
15702
  /** The editor's layered project file, kept beside the edit. */
15614
15703
  pxo: Base64.optional(),
15615
15704
  /** Editor identity from the bridge's ready message. */
15616
- editor: z10.string().min(1).max(200),
15617
- protocol: z10.number().int().positive()
15705
+ editor: z11.string().min(1).max(200),
15706
+ protocol: z11.number().int().positive()
15618
15707
  }).strict()
15619
15708
  ]);
15620
15709
  function createGalleryEditHandler(opts) {
@@ -15655,6 +15744,23 @@ function createGalleryEditHandler(opts) {
15655
15744
  }
15656
15745
  return opts.reload();
15657
15746
  }
15747
+ const skeleton = SkeletonAnimationRequestSchema.safeParse(body);
15748
+ if (skeleton.success) {
15749
+ let manifestPath2;
15750
+ try {
15751
+ manifestPath2 = await opts.manifestFor(skeleton.data.project);
15752
+ } catch (error) {
15753
+ throw new ManifestEditError(error instanceof Error ? error.message : String(error));
15754
+ }
15755
+ await createSkeletonAnimation(manifestPath2, skeleton.data);
15756
+ log(` manifest edited: added skeleton animation ${skeleton.data.assetId} (${skeleton.data.keypointsFile})`);
15757
+ return opts.reload();
15758
+ }
15759
+ if (body?.action === "create-skeleton-animation") {
15760
+ throw new ManifestEditError(
15761
+ "invalid skeleton animation: " + skeleton.error.issues.slice(0, 3).map((issue) => `${issue.path.join(".")}: ${issue.message}`).join("; ")
15762
+ );
15763
+ }
15658
15764
  const parsed = ManifestEditSchema.safeParse(body);
15659
15765
  if (!parsed.success) {
15660
15766
  throw new ManifestEditError(
@@ -15669,7 +15775,7 @@ function createGalleryEditHandler(opts) {
15669
15775
  }
15670
15776
  const result = await applyManifestEdit(manifestPath, parsed.data);
15671
15777
  if (result.changed) {
15672
- const relative = path28.relative(process.cwd(), result.manifestPath);
15778
+ const relative = path29.relative(process.cwd(), result.manifestPath);
15673
15779
  const shown = !relative || relative.startsWith("..") ? result.manifestPath : relative;
15674
15780
  const subject = parsed.data.action === "patch-style" ? `style ${parsed.data.styleId}` : parsed.data.action === "set-source" || parsed.data.action === "clear-source" ? `${parsed.data.styleId}/${parsed.data.assetId} source` : parsed.data.assetId;
15675
15781
  log(` manifest edited: ${parsed.data.action === "add-asset" ? "added" : "changed"} ${subject} in ${shown}`);
@@ -15681,7 +15787,7 @@ function createGalleryEditHandler(opts) {
15681
15787
  // src/gallery/snapshot.ts
15682
15788
  var PROJECT_FILE_TYPE = "application/octet-stream";
15683
15789
  function galleryMediaId(absolutePath) {
15684
- return createHash4("sha256").update(path29.resolve(absolutePath)).digest("hex").slice(0, 24);
15790
+ return createHash4("sha256").update(path30.resolve(absolutePath)).digest("hex").slice(0, 24);
15685
15791
  }
15686
15792
  function galleryMediaRoute(id) {
15687
15793
  return `/media/${id}`;
@@ -15700,9 +15806,9 @@ function describeHistory(media, entry, cacheDir) {
15700
15806
  return (entry.history ?? []).map((generation, i) => {
15701
15807
  const outputs = generation.outputs.map((output) => {
15702
15808
  const mediaType = output.mediaType ?? MediaType.PNG;
15703
- const file = path29.join(cacheDir, cacheFileName(output.sha256, mediaType));
15809
+ const file = path30.join(cacheDir, cacheFileName(output.sha256, mediaType));
15704
15810
  let url = null;
15705
- if (existsSync23(file)) {
15811
+ if (existsSync24(file)) {
15706
15812
  const id = galleryMediaId(file);
15707
15813
  media.set(id, { path: file, contentType: mediaType });
15708
15814
  url = `${galleryMediaRoute(id)}?v=${output.sha256.slice(0, 24)}`;
@@ -15734,7 +15840,7 @@ function describeHistory(media, entry, cacheDir) {
15734
15840
  var MAX_COMPARED_BYTES = 8 * 1024 * 1024;
15735
15841
  async function samePixels(a, b) {
15736
15842
  try {
15737
- const [left, right] = await Promise.all([readFile21(a), readFile21(b)]);
15843
+ const [left, right] = await Promise.all([readFile22(a), readFile22(b)]);
15738
15844
  if (left.length > MAX_COMPARED_BYTES || right.length > MAX_COMPARED_BYTES) return false;
15739
15845
  const x = decodePng(left);
15740
15846
  const y = decodePng(right);
@@ -15832,10 +15938,10 @@ async function describeOutput(media, root, absolutePath, recorded = {}) {
15832
15938
  async function describeQuality(media, root, inspection) {
15833
15939
  const outputPaths = inspection.outputs ?? [inspection.output];
15834
15940
  const outputs = await Promise.all(
15835
- outputPaths.map((file) => describeOutput(media, root, path29.resolve(root, file)))
15941
+ outputPaths.map((file) => describeOutput(media, root, path30.resolve(root, file)))
15836
15942
  );
15837
- const recordPath = path29.resolve(root, inspection.record);
15838
- const recordExists = existsSync23(recordPath);
15943
+ const recordPath = path30.resolve(root, inspection.record);
15944
+ const recordExists = existsSync24(recordPath);
15839
15945
  let options = null;
15840
15946
  let check = null;
15841
15947
  if (recordExists) {
@@ -15854,7 +15960,7 @@ async function describeQuality(media, root, inspection) {
15854
15960
  return {
15855
15961
  state: inspection.state,
15856
15962
  reason: inspection.reason,
15857
- output: portableOutputPath(path29.resolve(root, inspection.output), root),
15963
+ output: portableOutputPath(path30.resolve(root, inspection.output), root),
15858
15964
  record: portableOutputPath(recordPath, root),
15859
15965
  recordExists,
15860
15966
  outputs,
@@ -15892,9 +15998,9 @@ async function buildGallerySnapshot(opts) {
15892
15998
  const media = /* @__PURE__ */ new Map();
15893
15999
  const plan = await buildPlan(specs, lock);
15894
16000
  const items = [];
15895
- const manifestText = await readFile21(loaded.path, "utf8");
16001
+ const manifestText = await readFile22(loaded.path, "utf8");
15896
16002
  const manifestSha256 = sha256(manifestText);
15897
- const cacheDir = path29.resolve(path29.dirname(opts.lockPath), ".pixelkiln", "cache");
16003
+ const cacheDir = path30.resolve(path30.dirname(opts.lockPath), ".pixelkiln", "cache");
15898
16004
  let rawStyles = {};
15899
16005
  try {
15900
16006
  const raw = JSON.parse(manifestText);
@@ -15918,12 +16024,12 @@ async function buildGallerySnapshot(opts) {
15918
16024
  )
15919
16025
  );
15920
16026
  if (spec.source) {
15921
- const editPath = path29.resolve(root, spec.source);
16027
+ const editPath = path30.resolve(root, spec.source);
15922
16028
  const memberCount = sourceIsStem(spec.source, entry, root) ? entry.outputs.length : 1;
15923
16029
  const editShas = await Promise.all(
15924
16030
  Array.from({ length: memberCount }, async (_, index) => {
15925
16031
  const file = sourceOutputPath(spec.source, entry, index, root);
15926
- return { file, sha256: existsSync23(file) ? await sha256File(file) : null };
16032
+ return { file, sha256: existsSync24(file) ? await sha256File(file) : null };
15927
16033
  })
15928
16034
  );
15929
16035
  edits = await Promise.all(editShas.map(({ file, sha256: editSha }) => describeOutput(media, root, file, { sha256: editSha })));
@@ -15931,7 +16037,7 @@ async function buildGallerySnapshot(opts) {
15931
16037
  const companion = await readHandEditCompanion(editPath);
15932
16038
  if (companion) {
15933
16039
  const projectPath = handEditProjectPath(editPath);
15934
- const project = companion.project && existsSync23(projectPath) ? await fileInfo(projectPath) : null;
16040
+ const project = companion.project && existsSync24(projectPath) ? await fileInfo(projectPath) : null;
15935
16041
  let projectUrl = null;
15936
16042
  if (project) {
15937
16043
  const id = galleryMediaId(projectPath);
@@ -15967,8 +16073,8 @@ async function buildGallerySnapshot(opts) {
15967
16073
  editStatus = edits.some((member) => !member.exists) ? "missing" : editChanged.every((changed) => !changed) ? "same" : regenerated ? "regenerated-since" : "edited";
15968
16074
  }
15969
16075
  } else if (spec.source) {
15970
- outputs = [await describeOutput(media, root, path29.resolve(root, spec.source))];
15971
- } else if (!entry && existsSync23(spec.outFile)) {
16076
+ outputs = [await describeOutput(media, root, path30.resolve(root, spec.source))];
16077
+ } else if (!entry && existsSync24(spec.outFile)) {
15972
16078
  outputs = [await describeOutput(media, root, spec.outFile)];
15973
16079
  } else {
15974
16080
  outputs = [];
@@ -16009,6 +16115,10 @@ async function buildGallerySnapshot(opts) {
16009
16115
  revision: entry?.revision ?? null,
16010
16116
  revisionParentKey: spec.revision ? lockKey(spec.styleId, spec.revision.sourceAssetId) : entry?.revision ? lockKey(spec.styleId, entry.revision.sourceAssetId) : null,
16011
16117
  mirrorOfKey: spec.mirror ? lockKey(spec.styleId, spec.mirror.sourceAssetId) : entry?.mirror ? lockKey(spec.styleId, entry.mirror.sourceAssetId) : null,
16118
+ skeleton: spec.revision?.mode === "animate-skeleton" && spec.revision.keypointsFile ? {
16119
+ keypointsFile: portableOutputPath(spec.revision.keypointsFile, root),
16120
+ set: spec.revision.skeleton ?? null
16121
+ } : null,
16012
16122
  character: describeCharacter(spec, entry),
16013
16123
  outputs,
16014
16124
  quality,
@@ -16073,6 +16183,7 @@ async function buildGallerySnapshot(opts) {
16073
16183
  revision: entry.revision,
16074
16184
  revisionParentKey: entry.revision ? lockKey(entry.styleId, entry.revision.sourceAssetId) : null,
16075
16185
  mirrorOfKey: entry.mirror ? lockKey(entry.styleId, entry.mirror.sourceAssetId) : null,
16186
+ skeleton: null,
16076
16187
  character: describeCharacter(void 0, entry),
16077
16188
  outputs,
16078
16189
  quality: null,
@@ -16163,7 +16274,7 @@ async function buildGallerySnapshot(opts) {
16163
16274
  id: loaded.manifest.name,
16164
16275
  name: loaded.manifest.name,
16165
16276
  manifest: loaded.path,
16166
- lock: path29.resolve(opts.lockPath),
16277
+ lock: path30.resolve(opts.lockPath),
16167
16278
  root,
16168
16279
  provider: loaded.manifest.provider,
16169
16280
  account: null,
@@ -16189,7 +16300,7 @@ async function buildGallerySnapshot(opts) {
16189
16300
  return { snapshot, media };
16190
16301
  }
16191
16302
  async function buildWorkspaceGallerySnapshot(opts) {
16192
- const dir = path29.dirname(path29.resolve(opts.workspacePath));
16303
+ const dir = path30.dirname(path30.resolve(opts.workspacePath));
16193
16304
  const media = /* @__PURE__ */ new Map();
16194
16305
  const projects = [];
16195
16306
  const items = [];
@@ -16232,11 +16343,11 @@ async function buildWorkspaceGallerySnapshot(opts) {
16232
16343
  id: project.id,
16233
16344
  name: loaded.manifest.name,
16234
16345
  manifest: manifestPath,
16235
- lock: path29.resolve(lockPath),
16346
+ lock: path30.resolve(lockPath),
16236
16347
  root: loaded.root,
16237
16348
  provider: loaded.manifest.provider,
16238
16349
  account: project.account ?? null,
16239
- manifestSha256: manifestSha256 ?? sha256(await readFile21(manifestPath, "utf8")),
16350
+ manifestSha256: manifestSha256 ?? sha256(await readFile22(manifestPath, "utf8")),
16240
16351
  historyLimit: historyLimit(loaded.manifest),
16241
16352
  entries: Object.keys(lock.entries).length,
16242
16353
  items: projectItems.length,
@@ -16248,8 +16359,8 @@ async function buildWorkspaceGallerySnapshot(opts) {
16248
16359
  id: project.id,
16249
16360
  name: project.id,
16250
16361
  manifest: manifestPath,
16251
- lock: path29.resolve(lockPath),
16252
- root: path29.dirname(manifestPath),
16362
+ lock: path30.resolve(lockPath),
16363
+ root: path30.dirname(manifestPath),
16253
16364
  provider: project.provider,
16254
16365
  account: project.account ?? null,
16255
16366
  manifestSha256: null,
@@ -16276,7 +16387,7 @@ async function buildWorkspaceGallerySnapshot(opts) {
16276
16387
  version: 1,
16277
16388
  generatedAt: (opts.now ?? (() => /* @__PURE__ */ new Date()))().toISOString(),
16278
16389
  project: null,
16279
- workspace: { path: path29.resolve(opts.workspacePath), dir, projects },
16390
+ workspace: { path: path30.resolve(opts.workspacePath), dir, projects },
16280
16391
  filter,
16281
16392
  totals: {
16282
16393
  entries: projects.reduce((sum, project) => sum + project.entries, 0),
@@ -16425,10 +16536,10 @@ var EDITOR_RELEASE_BASE = "https://github.com/gfargo/pixelkiln/releases/download
16425
16536
 
16426
16537
  // src/editor/install.ts
16427
16538
  import { createHash as createHash5 } from "crypto";
16428
- import { existsSync as existsSync24 } from "fs";
16429
- import { mkdir as mkdir9, readFile as readFile22, rename as rename9, rm as rm10, stat as stat4, writeFile as writeFile12 } from "fs/promises";
16539
+ import { existsSync as existsSync25 } from "fs";
16540
+ import { mkdir as mkdir10, readFile as readFile23, rename as rename9, rm as rm11, stat as stat4, writeFile as writeFile13 } from "fs/promises";
16430
16541
  import os2 from "os";
16431
- import path30 from "path";
16542
+ import path31 from "path";
16432
16543
  var EditorInstallError = class extends Error {
16433
16544
  constructor(message6, file) {
16434
16545
  super(message6);
@@ -16439,15 +16550,15 @@ var EditorInstallError = class extends Error {
16439
16550
  };
16440
16551
  function editorToolsRoot() {
16441
16552
  const override = process.env.PIXELKILN_TOOLS_DIR?.trim();
16442
- if (override) return path30.resolve(override);
16443
- if (process.platform === "darwin") return path30.join(os2.homedir(), "Library", "Caches", "pixelkiln", "tools");
16553
+ if (override) return path31.resolve(override);
16554
+ if (process.platform === "darwin") return path31.join(os2.homedir(), "Library", "Caches", "pixelkiln", "tools");
16444
16555
  if (process.platform === "win32") {
16445
- return path30.join(process.env.LOCALAPPDATA ?? path30.join(os2.homedir(), "AppData", "Local"), "pixelkiln", "tools");
16556
+ return path31.join(process.env.LOCALAPPDATA ?? path31.join(os2.homedir(), "AppData", "Local"), "pixelkiln", "tools");
16446
16557
  }
16447
- return path30.join(process.env.XDG_CACHE_HOME ?? path30.join(os2.homedir(), ".cache"), "pixelkiln", "tools");
16558
+ return path31.join(process.env.XDG_CACHE_HOME ?? path31.join(os2.homedir(), ".cache"), "pixelkiln", "tools");
16448
16559
  }
16449
16560
  function editorDir(pin = EDITOR_PIN, root = editorToolsRoot()) {
16450
- return path30.join(root, "pixelorama", pin.release ?? "unpublished");
16561
+ return path31.join(root, "pixelorama", pin.release ?? "unpublished");
16451
16562
  }
16452
16563
  function editorBaseUrl(pin = EDITOR_PIN) {
16453
16564
  const override = process.env.PIXELKILN_EDITOR_URL?.trim();
@@ -16457,7 +16568,7 @@ function editorBaseUrl(pin = EDITOR_PIN) {
16457
16568
  }
16458
16569
  async function sha256Of(file) {
16459
16570
  try {
16460
- return createHash5("sha256").update(await readFile22(file)).digest("hex");
16571
+ return createHash5("sha256").update(await readFile23(file)).digest("hex");
16461
16572
  } catch {
16462
16573
  return null;
16463
16574
  }
@@ -16470,8 +16581,8 @@ async function editorStatus(opts = {}) {
16470
16581
  let totalBytes = 0;
16471
16582
  for (const [name, expected] of Object.entries(pin.files)) {
16472
16583
  totalBytes += expected.bytes;
16473
- const file = path30.join(dir, name);
16474
- const info = existsSync24(file) ? await stat4(file).catch(() => null) : null;
16584
+ const file = path31.join(dir, name);
16585
+ const info = existsSync25(file) ? await stat4(file).catch(() => null) : null;
16475
16586
  if (info && info.size === expected.bytes && await sha256Of(file) === expected.sha256) {
16476
16587
  installedBytes += expected.bytes;
16477
16588
  } else {
@@ -16500,7 +16611,7 @@ async function installEditor(opts = {}) {
16500
16611
  );
16501
16612
  }
16502
16613
  if (!baseUrl) throw new EditorInstallError("no download location for the editor build");
16503
- await mkdir9(dir, { recursive: true });
16614
+ await mkdir10(dir, { recursive: true });
16504
16615
  const downloaded = [];
16505
16616
  const skipped = Object.keys(pin.files).filter((name) => !before.missing.includes(name));
16506
16617
  let fetchedBytes = before.installedBytes;
@@ -16528,13 +16639,13 @@ async function installEditor(opts = {}) {
16528
16639
  if (digest2 !== expected.sha256) {
16529
16640
  throw new EditorInstallError(`${name}: sha256 ${digest2.slice(0, 12)}\u2026 does not match the pinned ${expected.sha256.slice(0, 12)}\u2026`, name);
16530
16641
  }
16531
- const final = path30.join(dir, name);
16642
+ const final = path31.join(dir, name);
16532
16643
  const tmp = `${final}.${process.pid}.part`;
16533
16644
  try {
16534
- await writeFile12(tmp, bytes);
16645
+ await writeFile13(tmp, bytes);
16535
16646
  await rename9(tmp, final);
16536
16647
  } finally {
16537
- await rm10(tmp, { force: true });
16648
+ await rm11(tmp, { force: true });
16538
16649
  }
16539
16650
  fetchedBytes += bytes.length;
16540
16651
  downloaded.push(name);
@@ -16574,12 +16685,12 @@ var EDITOR_CONTENT_TYPES = {
16574
16685
  ".json": "application/json; charset=utf-8"
16575
16686
  };
16576
16687
  function editorContentType(name) {
16577
- return EDITOR_CONTENT_TYPES[path30.extname(name).toLowerCase()] ?? null;
16688
+ return EDITOR_CONTENT_TYPES[path31.extname(name).toLowerCase()] ?? null;
16578
16689
  }
16579
16690
 
16580
16691
  // src/gallery/editor.ts
16581
- import { readFile as readFile23 } from "fs/promises";
16582
- import path31 from "path";
16692
+ import { readFile as readFile24 } from "fs/promises";
16693
+ import path32 from "path";
16583
16694
  function editorRoute(release, name = "index.html") {
16584
16695
  return `/editor/${encodeURIComponent(release)}/${encodeURIComponent(name)}`;
16585
16696
  }
@@ -16652,7 +16763,7 @@ function createGalleryEditorHandlers(opts = {}) {
16652
16763
  if (!status.installed) return null;
16653
16764
  let bytes;
16654
16765
  try {
16655
- bytes = await readFile23(path31.join(dir, name));
16766
+ bytes = await readFile24(path32.join(dir, name));
16656
16767
  } catch {
16657
16768
  verified = null;
16658
16769
  return null;
@@ -16669,7 +16780,7 @@ function createGalleryEditorHandlers(opts = {}) {
16669
16780
  // src/gallery/server.ts
16670
16781
  import { randomBytes } from "crypto";
16671
16782
  import { createServer as createServer2 } from "http";
16672
- import { readFile as readFile24 } from "fs/promises";
16783
+ import { readFile as readFile25 } from "fs/promises";
16673
16784
  var MAX_EDIT_BYTES = 48 * 1024 * 1024;
16674
16785
  var EDITOR_CSP = [
16675
16786
  "default-src 'self'",
@@ -16697,7 +16808,7 @@ async function readJsonBody(req) {
16697
16808
  async function serveGallery(opts) {
16698
16809
  const log = opts.onProgress ?? (() => {
16699
16810
  });
16700
- const session = opts.edit || opts.generate || opts.editor ? randomBytes(16).toString("hex") : null;
16811
+ const session = opts.edit || opts.generate || opts.editor || opts.skeleton ? randomBytes(16).toString("hex") : null;
16701
16812
  let media = /* @__PURE__ */ new Map();
16702
16813
  let loading = null;
16703
16814
  const reviewAssets = /* @__PURE__ */ new Map();
@@ -16772,6 +16883,17 @@ async function serveGallery(opts) {
16772
16883
  }
16773
16884
  return;
16774
16885
  }
16886
+ if (req.method === "POST" && url2.pathname === "/api/skeleton/estimate") {
16887
+ if (!opts.skeleton || !session) return fail(405, "skeleton estimates need the gallery started with --edit");
16888
+ try {
16889
+ const body = await guardedBody("skeleton estimates");
16890
+ if (body === void 0) return;
16891
+ json(200, await opts.skeleton.estimate(body));
16892
+ } catch (err) {
16893
+ reportError("skeleton estimate", err);
16894
+ }
16895
+ return;
16896
+ }
16775
16897
  if (req.method === "POST" && url2.pathname === "/api/editor/install") {
16776
16898
  if (!opts.editor || !session) return fail(405, "the in-browser editor is off for this gallery");
16777
16899
  try {
@@ -16828,7 +16950,7 @@ async function serveGallery(opts) {
16828
16950
  const asset = reviewAssets.get(jobId)?.get(url2.pathname);
16829
16951
  if (!asset) return fail(404, "no such review asset");
16830
16952
  try {
16831
- const bytes = await readFile24(asset.path);
16953
+ const bytes = await readFile25(asset.path);
16832
16954
  res.writeHead(200, {
16833
16955
  "Content-Type": asset.contentType,
16834
16956
  "Cache-Control": "no-store",
@@ -16904,7 +17026,7 @@ async function serveGallery(opts) {
16904
17026
  if (!asset) return fail(404, "no such gallery media");
16905
17027
  let bytes;
16906
17028
  try {
16907
- bytes = await readFile24(asset.path);
17029
+ bytes = await readFile25(asset.path);
16908
17030
  } catch {
16909
17031
  return fail(404, "gallery media is no longer on disk; refresh the page");
16910
17032
  }
@@ -16948,27 +17070,27 @@ async function serveGallery(opts) {
16948
17070
 
16949
17071
  // src/gallery/generate.ts
16950
17072
  import { randomBytes as randomBytes2 } from "crypto";
16951
- import { z as z11 } from "zod";
16952
- var GenerateRequestSchema = z11.object({
16953
- project: z11.string().min(1).optional(),
16954
- keys: z11.array(z11.string().min(1)).min(1).max(500),
17073
+ import { z as z12 } from "zod";
17074
+ var GenerateRequestSchema = z12.object({
17075
+ project: z12.string().min(1).optional(),
17076
+ keys: z12.array(z12.string().min(1)).min(1).max(500),
16955
17077
  /**
16956
17078
  * Regenerate work that is up to date, as `gen --force` does, and let the
16957
17079
  * fetch replace a locally modified or untracked file. With `restore`, put
16958
17080
  * the recorded bytes back over a modified file.
16959
17081
  */
16960
- force: z11.boolean().optional(),
17082
+ force: z12.boolean().optional(),
16961
17083
  /** Advance in-flight, review, or selected work without submitting anything. */
16962
- resume: z11.boolean().optional(),
17084
+ resume: z12.boolean().optional(),
16963
17085
  /** Re-download downloaded outputs and replace files whose object changed upstream. */
16964
- refresh: z11.boolean().optional(),
17086
+ refresh: z12.boolean().optional(),
16965
17087
  /** Put recorded outputs back on disk from the cache or the provider, without generating. */
16966
- restore: z11.boolean().optional(),
17088
+ restore: z12.boolean().optional(),
16967
17089
  /**
16968
17090
  * Bring a replaced generation back: 1-based position in the record's
16969
17091
  * history (newest first) or an output hash prefix. One key at a time.
16970
17092
  */
16971
- revert: z11.string().min(1).max(64).optional()
17093
+ revert: z12.string().min(1).max(64).optional()
16972
17094
  }).strict().refine((request) => [request.resume, request.refresh, request.restore, request.revert !== void 0].filter(Boolean).length <= 1, {
16973
17095
  message: "resume, refresh, restore, and revert are exclusive"
16974
17096
  }).refine((request) => request.revert === void 0 || request.keys.length === 1, { message: "revert takes one key" });
@@ -17278,19 +17400,19 @@ function createGenerateHandlers(opts) {
17278
17400
  }
17279
17401
 
17280
17402
  // src/pipeline/salvage.ts
17281
- import { readFile as readFile25 } from "fs/promises";
17282
- import { existsSync as existsSync25 } from "fs";
17283
- import path32 from "path";
17403
+ import { readFile as readFile26 } from "fs/promises";
17404
+ import { existsSync as existsSync26 } from "fs";
17405
+ import path33 from "path";
17284
17406
  function providerClaimId(provider, objectId) {
17285
17407
  return `${encodeURIComponent(provider)}:${encodeURIComponent(objectId)}`;
17286
17408
  }
17287
17409
  async function loadClaims(lockPaths, opts = {}) {
17288
17410
  const claimed = /* @__PURE__ */ new Set();
17289
17411
  for (const p of lockPaths) {
17290
- if (!existsSync25(p)) throw new Error(`Claim lockfile not found: ${p}`);
17412
+ if (!existsSync26(p)) throw new Error(`Claim lockfile not found: ${p}`);
17291
17413
  let parsed;
17292
17414
  try {
17293
- parsed = parseLock(JSON.parse(await readFile25(p, "utf8")));
17415
+ parsed = parseLock(JSON.parse(await readFile26(p, "utf8")));
17294
17416
  } catch {
17295
17417
  throw new Error(`Claim lockfile is malformed: ${p}`);
17296
17418
  }
@@ -17394,20 +17516,20 @@ function matchOrphanStyle(prompt, manifest) {
17394
17516
  return matchStyleByPattern(prompt, manifest);
17395
17517
  }
17396
17518
  async function loadSiblingManifests(ownManifestPath, workspaceManifestPaths, claimPaths) {
17397
- const own = path32.resolve(ownManifestPath);
17519
+ const own = path33.resolve(ownManifestPath);
17398
17520
  const siblingManifestPaths = [
17399
17521
  .../* @__PURE__ */ new Set([
17400
17522
  ...workspaceManifestPaths,
17401
- ...claimPaths.map((c) => path32.join(path32.dirname(path32.resolve(c)), "pixelkiln.manifest.json"))
17523
+ ...claimPaths.map((c) => path33.join(path33.dirname(path33.resolve(c)), "pixelkiln.manifest.json"))
17402
17524
  ])
17403
17525
  ];
17404
17526
  const siblings = [];
17405
17527
  for (const siblingManifestPath of siblingManifestPaths) {
17406
- if (path32.resolve(siblingManifestPath) === own) continue;
17407
- if (!existsSync25(siblingManifestPath)) continue;
17528
+ if (path33.resolve(siblingManifestPath) === own) continue;
17529
+ if (!existsSync26(siblingManifestPath)) continue;
17408
17530
  try {
17409
17531
  const { manifest } = await loadManifest(siblingManifestPath);
17410
- siblings.push({ label: path32.basename(path32.dirname(siblingManifestPath)), manifest });
17532
+ siblings.push({ label: path33.basename(path33.dirname(siblingManifestPath)), manifest });
17411
17533
  } catch {
17412
17534
  }
17413
17535
  }
@@ -17527,8 +17649,8 @@ async function applyTags(provider, decisions, existing, opts = {}) {
17527
17649
  }
17528
17650
 
17529
17651
  // src/pick/salvage-server.ts
17530
- import { mkdir as mkdir10, writeFile as writeFile13, readFile as readFile26 } from "fs/promises";
17531
- import path33 from "path";
17652
+ import { mkdir as mkdir11, writeFile as writeFile14, readFile as readFile27 } from "fs/promises";
17653
+ import path34 from "path";
17532
17654
 
17533
17655
  // src/pick/salvage-sheet.ts
17534
17656
  var escapeHtml3 = (s) => s.replace(/[&<>"']/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[c]);
@@ -17694,7 +17816,7 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
17694
17816
  });
17695
17817
  const html = renderSalvageSheet(orphans, {
17696
17818
  styleId: ctx.styleId,
17697
- importDir: path33.relative(process.cwd(), ctx.importDir) || "."
17819
+ importDir: path34.relative(process.cwd(), ctx.importDir) || "."
17698
17820
  });
17699
17821
  const byId = new Map(orphans.map((o) => [o.id, o]));
17700
17822
  const existingTags = new Map(orphans.map((o) => [o.id, o.tags]));
@@ -17750,10 +17872,10 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
17750
17872
  if (!buf.subarray(0, 8).equals(PNG_SIGNATURE2)) throw new Error("not a PNG");
17751
17873
  decodePng(buf);
17752
17874
  const assetId = idFromPrompt(orphan.prompt, taken);
17753
- const rel = path33.join("_salvaged", `${assetId}.png`);
17754
- const outFile = path33.resolve(ctx.importDir, rel);
17755
- await mkdir10(path33.dirname(outFile), { recursive: true });
17756
- await writeFile13(outFile, buf);
17875
+ const rel = path34.join("_salvaged", `${assetId}.png`);
17876
+ const outFile = path34.resolve(ctx.importDir, rel);
17877
+ await mkdir11(path34.dirname(outFile), { recursive: true });
17878
+ await writeFile14(outFile, buf);
17757
17879
  ctx.manifest.assets[assetId] = {
17758
17880
  prompt: orphan.prompt,
17759
17881
  promptByStyle: {},
@@ -17785,7 +17907,7 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
17785
17907
  sourceUrl: durableSource,
17786
17908
  sourceUrls: durableSource ? [{ url: durableSource }] : [],
17787
17909
  outputs: [{
17788
- path: portableOutputPath(outFile, path33.dirname(ctx.manifestPath)),
17910
+ path: portableOutputPath(outFile, path34.dirname(ctx.manifestPath)),
17789
17911
  sha256: sha256(buf)
17790
17912
  }],
17791
17913
  submittedAt: orphan.createdAt,
@@ -17809,9 +17931,9 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
17809
17931
  }
17810
17932
  }
17811
17933
  await applyTags(provider, decisions, existingTags, { onProgress: log, characters: characterMembers });
17812
- const raw = JSON.parse(await readFile26(ctx.manifestPath, "utf8"));
17934
+ const raw = JSON.parse(await readFile27(ctx.manifestPath, "utf8"));
17813
17935
  for (const id of importedAssetIds) raw.assets[id] = ctx.manifest.assets[id];
17814
- await writeFile13(ctx.manifestPath, JSON.stringify(raw, null, 2) + "\n");
17936
+ await writeFile14(ctx.manifestPath, JSON.stringify(raw, null, 2) + "\n");
17815
17937
  await saveLock(ctx.lockPath, ctx.lock);
17816
17938
  if (characterAssetIds.length) result.characterAssetIds = characterAssetIds;
17817
17939
  return result;