pixelkiln 0.84.0 → 0.85.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
@@ -1816,9 +1816,9 @@ var PixelLabClient = class {
1816
1816
  apiKey;
1817
1817
  http;
1818
1818
  /** Retries transport failures, 429, and 5xx; see http.ts for the policy. */
1819
- async request(path37, init) {
1819
+ async request(path38, init) {
1820
1820
  const auth = this.apiKey.startsWith("Bearer ") ? this.apiKey : `Bearer ${this.apiKey}`;
1821
- const res = await this.http(`${BASE}${path37}`, {
1821
+ const res = await this.http(`${BASE}${path38}`, {
1822
1822
  ...init,
1823
1823
  headers: {
1824
1824
  Authorization: auth,
@@ -1828,13 +1828,13 @@ var PixelLabClient = class {
1828
1828
  });
1829
1829
  const text = await res.text();
1830
1830
  if (!res.ok) {
1831
- throw new PixelLabError(`${init?.method ?? "GET"} ${path37} \u2192 ${res.status}`, res.status, text);
1831
+ throw new PixelLabError(`${init?.method ?? "GET"} ${path38} \u2192 ${res.status}`, res.status, text);
1832
1832
  }
1833
1833
  if (!text) return {};
1834
1834
  try {
1835
1835
  return JSON.parse(text);
1836
1836
  } catch {
1837
- throw new Error(`${init?.method ?? "GET"} ${path37} returned invalid JSON`);
1837
+ throw new Error(`${init?.method ?? "GET"} ${path38} returned invalid JSON`);
1838
1838
  }
1839
1839
  }
1840
1840
  async balance() {
@@ -2239,10 +2239,10 @@ var PixelLabClient = class {
2239
2239
  const palette = args.paletteSwatchBase64 ? { color_image: { type: "base64", base64: args.paletteSwatchBase64, format: "png" }, force_colors: true } : {};
2240
2240
  const encode = (image) => ({ type: "base64", base64: image.base64, format: image.format });
2241
2241
  const south = args.reference?.south;
2242
- let path37;
2242
+ let path38;
2243
2243
  let body;
2244
2244
  if (args.mode === "v3") {
2245
- path37 = "/create-character-v3";
2245
+ path38 = "/create-character-v3";
2246
2246
  body = {
2247
2247
  description: args.description,
2248
2248
  image_size: imageSize,
@@ -2256,7 +2256,7 @@ var PixelLabClient = class {
2256
2256
  ...args.enhancePrompt ? { enhance_prompt: true } : {}
2257
2257
  };
2258
2258
  } else if (args.mode === "pro-flash") {
2259
- path37 = "/create-character-pro-flash";
2259
+ path38 = "/create-character-pro-flash";
2260
2260
  const traits = args.styleTraits;
2261
2261
  body = {
2262
2262
  description: args.description,
@@ -2280,7 +2280,7 @@ var PixelLabClient = class {
2280
2280
  } : {}
2281
2281
  };
2282
2282
  } else if (args.mode === "pro") {
2283
- path37 = "/create-character-pro";
2283
+ path38 = "/create-character-pro";
2284
2284
  body = {
2285
2285
  description: args.description,
2286
2286
  image_size: imageSize,
@@ -2294,7 +2294,7 @@ var PixelLabClient = class {
2294
2294
  ...!south && args.styleCharacterId ? { style_character_id: args.styleCharacterId } : {}
2295
2295
  };
2296
2296
  } else {
2297
- path37 = args.directions === 4 ? "/create-character-with-4-directions" : "/create-character-with-8-directions";
2297
+ path38 = args.directions === 4 ? "/create-character-with-4-directions" : "/create-character-with-8-directions";
2298
2298
  body = {
2299
2299
  description: args.description,
2300
2300
  image_size: imageSize,
@@ -2311,8 +2311,8 @@ var PixelLabClient = class {
2311
2311
  ...palette
2312
2312
  };
2313
2313
  }
2314
- const raw = await this.request(path37, { method: "POST", body: JSON.stringify(body) });
2315
- return validateResponse(CharacterSubmitSchema, raw, path37.slice(1));
2314
+ const raw = await this.request(path38, { method: "POST", body: JSON.stringify(body) });
2315
+ return validateResponse(CharacterSubmitSchema, raw, path38.slice(1));
2316
2316
  }
2317
2317
  /** A text edit of an existing character, applied to every direction and stored as a sibling. */
2318
2318
  async createCharacterState(args) {
@@ -3525,10 +3525,10 @@ import { createReadStream } from "fs";
3525
3525
  function sha256(data) {
3526
3526
  return createHash("sha256").update(data).digest("hex");
3527
3527
  }
3528
- async function sha256File(path37) {
3528
+ async function sha256File(path38) {
3529
3529
  return new Promise((resolve, reject) => {
3530
3530
  const hash = createHash("sha256");
3531
- const stream = createReadStream(path37);
3531
+ const stream = createReadStream(path38);
3532
3532
  stream.on("data", (chunk2) => hash.update(chunk2));
3533
3533
  stream.on("error", reject);
3534
3534
  stream.on("end", () => resolve(hash.digest("hex")));
@@ -5932,9 +5932,9 @@ var RetroDiffusionClient = class {
5932
5932
  if (!response.ok) throw new ProviderError("retrodiffusion", `Retro Diffusion download failed (${response.status})`, { status: response.status });
5933
5933
  return Buffer.from(await response.arrayBuffer());
5934
5934
  }
5935
- async call(path37, init = {}) {
5935
+ async call(path38, init = {}) {
5936
5936
  if (!this.token) throw new Error("RD_API_KEY is not set");
5937
- const response = await this.request(`${this.baseUrl}${path37}`, {
5937
+ const response = await this.request(`${this.baseUrl}${path38}`, {
5938
5938
  ...init,
5939
5939
  headers: {
5940
5940
  "Content-Type": "application/json",
@@ -6243,8 +6243,13 @@ function retroError(value) {
6243
6243
  }
6244
6244
 
6245
6245
  // src/providers/scenario.ts
6246
+ import { createHash as createHash2 } from "crypto";
6247
+ import { mkdirSync as mkdirSync2, readFileSync as readFileSync2, renameSync, writeFileSync as writeFileSync2 } from "fs";
6248
+ import os2 from "os";
6249
+ import path2 from "path";
6246
6250
  var DEFAULT_BASE_URL2 = "https://api.cloud.scenario.com/v1";
6247
6251
  var SOURCE_PROTOCOL2 = "scenario:";
6252
+ var SUBMIT_RETRIES = 8;
6248
6253
  var PROTECTED_PARAMETERS = /* @__PURE__ */ new Set([
6249
6254
  "dryRun",
6250
6255
  "height",
@@ -6291,7 +6296,11 @@ var ScenarioClient = class {
6291
6296
  async submit(modelId, body, projectId) {
6292
6297
  const response = await this.call(
6293
6298
  `/generate/custom/${encodeURIComponent(modelId)}`,
6294
- { method: "POST", body: JSON.stringify(body) },
6299
+ // A 429 here means this account's concurrent-job limit refused the
6300
+ // request, so nothing was accepted or billed; wait it out instead of
6301
+ // failing the asset. The default four retries give up after about half
6302
+ // a minute, before a running job has finished.
6303
+ { method: "POST", body: JSON.stringify(body), retries: SUBMIT_RETRIES },
6295
6304
  { projectId }
6296
6305
  );
6297
6306
  const job = response?.job;
@@ -6299,6 +6308,29 @@ var ScenarioClient = class {
6299
6308
  if (!jobId) throw new Error("Scenario did not return an async job id");
6300
6309
  return jobId;
6301
6310
  }
6311
+ /**
6312
+ * Uploads one image (POST /assets) and returns its asset id, which a model's
6313
+ * reference input takes. Free, and idempotent per image: the id is kept by
6314
+ * content hash so a rerun does not upload the same bytes again.
6315
+ */
6316
+ async uploadImage(image, projectId) {
6317
+ const bytes = Buffer.from(image.base64, "base64");
6318
+ const digest2 = sha256(bytes);
6319
+ const cached = readUploadCache()[uploadCacheKey(this.baseUrl, projectId, digest2)];
6320
+ if (cached) return cached;
6321
+ const response = await this.call(
6322
+ "/assets",
6323
+ {
6324
+ method: "POST",
6325
+ body: JSON.stringify({ image: image.base64, name: `pixelkiln-${digest2.slice(0, 12)}.${image.format === "png" ? "png" : "jpg"}` })
6326
+ },
6327
+ { projectId }
6328
+ );
6329
+ const id = response?.asset?.id;
6330
+ if (typeof id !== "string" || !id) throw new Error("Scenario did not return an uploaded asset id");
6331
+ writeUploadCache(uploadCacheKey(this.baseUrl, projectId, digest2), id);
6332
+ return id;
6333
+ }
6302
6334
  async job(id, projectId) {
6303
6335
  const response = await this.call(`/jobs/${encodeURIComponent(id)}`, {}, { projectId });
6304
6336
  const raw = response?.job;
@@ -6425,6 +6457,14 @@ var ScenarioProvider = class _ScenarioProvider {
6425
6457
  supports(generator) {
6426
6458
  return generator === "map";
6427
6459
  }
6460
+ /**
6461
+ * An edit is a generation with the parent as a reference image, which the
6462
+ * editing models (GPT Image, Gemini, FLUX) take. Masked inpainting and the
6463
+ * PixelLab-specific modes have no common Scenario input.
6464
+ */
6465
+ supportsRevision(mode2) {
6466
+ return mode2 === "image-to-image";
6467
+ }
6428
6468
  estimate(spec) {
6429
6469
  const options = scenarioOptions(spec);
6430
6470
  return {
@@ -6445,11 +6485,31 @@ var ScenarioProvider = class _ScenarioProvider {
6445
6485
  if (!Number.isInteger(count) || count < 1 || count > 4) {
6446
6486
  throw new Error("Scenario numOutputs must be a whole number from 1 to 4");
6447
6487
  }
6448
- if (spec.width < 128 || spec.width > 2048 || spec.width % 16 !== 0 || spec.height < 128 || spec.height > 2048 || spec.height % 16 !== 0) {
6449
- throw new Error("Scenario width and height must be multiples of 16 from 128 to 2048");
6488
+ if (!Number.isInteger(spec.width) || spec.width < 16 || spec.width > 4096 || !Number.isInteger(spec.height) || spec.height < 16 || spec.height > 4096) {
6489
+ throw new Error("Scenario width and height must be whole numbers from 16 to 4096");
6450
6490
  }
6451
- if (styleImages.length) {
6452
- throw new Error("Scenario styleImages are not supported yet; use a reusable Scenario model");
6491
+ const revision = spec.revision;
6492
+ if (revision) {
6493
+ if (revision.sourceMembers?.length) {
6494
+ throw new Error(
6495
+ `Scenario edits one image; ${revision.sourceAssetId} is a ${revision.sourceMembers.length}-member set`
6496
+ );
6497
+ }
6498
+ if (revision.strength != null) {
6499
+ throw new Error("Scenario image-to-image takes no strength; use the model's own parameters");
6500
+ }
6501
+ }
6502
+ const parameter = referenceParameter(options);
6503
+ if ((styleImages.length || revision) && Object.hasOwn(options.parameters ?? {}, parameter)) {
6504
+ throw new Error(
6505
+ `Scenario parameters.${parameter} is the reference-image input and is filled from styleImages and revisions`
6506
+ );
6507
+ }
6508
+ if (styleImages.length + (revision ? 1 : 0) > 1 && options.referenceArray === false) {
6509
+ throw new Error(`Scenario ${parameter} takes one image; use one style image or a revision, not both`);
6510
+ }
6511
+ if (options.referenceParameter != null && (typeof options.referenceParameter !== "string" || !options.referenceParameter.trim())) {
6512
+ throw new Error("Scenario referenceParameter must be a non-empty string");
6453
6513
  }
6454
6514
  if (options.projectId != null && (typeof options.projectId !== "string" || !options.projectId.trim())) {
6455
6515
  throw new Error("Scenario projectId must be a non-empty string");
@@ -6462,7 +6522,12 @@ var ScenarioProvider = class _ScenarioProvider {
6462
6522
  async submit(spec, styleImages) {
6463
6523
  this.validate(spec, styleImages);
6464
6524
  const options = scenarioOptions(spec);
6465
- const body = requestBody(spec, options);
6525
+ const references = [];
6526
+ if (spec.revision) references.push(readRevisionSource(spec));
6527
+ for (const image of styleImages) references.push({ base64: image.base64, format: image.format });
6528
+ const referenceAssetIds = [];
6529
+ for (const image of references) referenceAssetIds.push(await this.client.uploadImage(image, options.projectId));
6530
+ const body = requestBody(spec, options, referenceAssetIds);
6466
6531
  const quote = await this.client.quote(options.modelId, body, options.projectId);
6467
6532
  if (quote.total > options.maxComputeUnits + Number.EPSILON) {
6468
6533
  throw new Error(
@@ -6479,7 +6544,8 @@ var ScenarioProvider = class _ScenarioProvider {
6479
6544
  quotedComputeUnits: quote.total,
6480
6545
  creativeUnitsCost: quote.creativeUnitsCost,
6481
6546
  costDetails: quote.costDetails,
6482
- ipDetectionCost: quote.ipDetectionCost
6547
+ ipDetectionCost: quote.ipDetectionCost,
6548
+ ...referenceAssetIds.length ? { referenceAssetIds } : {}
6483
6549
  }
6484
6550
  };
6485
6551
  }
@@ -6501,7 +6567,13 @@ var ScenarioProvider = class _ScenarioProvider {
6501
6567
  }
6502
6568
  const assetIds = scenarioAssetIds(job);
6503
6569
  if (!assetIds.length) return { status: "failed", error: "Scenario job returned no asset IDs" };
6504
- const assets = await this.assets(assetIds, reference.projectId);
6570
+ let assets;
6571
+ try {
6572
+ assets = await this.assets(assetIds, reference.projectId);
6573
+ } catch (error) {
6574
+ if (error instanceof UnsupportedScenarioOutput) return { status: "failed", error: error.message };
6575
+ throw error;
6576
+ }
6505
6577
  if (assets.length > 1) {
6506
6578
  return {
6507
6579
  status: "review",
@@ -6556,6 +6628,32 @@ var ScenarioProvider = class _ScenarioProvider {
6556
6628
  return assets.sort((left, right) => left.outputIndex - right.outputIndex || (jobOrder.get(left.id) ?? Number.MAX_SAFE_INTEGER) - (jobOrder.get(right.id) ?? Number.MAX_SAFE_INTEGER) || left.id.localeCompare(right.id));
6557
6629
  }
6558
6630
  };
6631
+ function uploadCachePath() {
6632
+ return process.env.SCENARIO_UPLOAD_CACHE ?? path2.join(os2.tmpdir(), "pixelkiln-scenario", "uploads.json");
6633
+ }
6634
+ function uploadCacheKey(baseUrl, projectId, digest2) {
6635
+ return createHash2("sha256").update(`${baseUrl}
6636
+ ${projectId ?? ""}
6637
+ ${digest2}`).digest("hex");
6638
+ }
6639
+ function readUploadCache() {
6640
+ try {
6641
+ const value = JSON.parse(readFileSync2(uploadCachePath(), "utf8"));
6642
+ return value && typeof value === "object" && !Array.isArray(value) ? value : {};
6643
+ } catch {
6644
+ return {};
6645
+ }
6646
+ }
6647
+ function writeUploadCache(key, assetId) {
6648
+ try {
6649
+ const file = uploadCachePath();
6650
+ mkdirSync2(path2.dirname(file), { recursive: true });
6651
+ const tmp = `${file}.${process.pid}.tmp`;
6652
+ writeFileSync2(tmp, JSON.stringify({ ...readUploadCache(), [key]: assetId }));
6653
+ renameSync(tmp, file);
6654
+ } catch {
6655
+ }
6656
+ }
6559
6657
  function scenarioOptions(spec) {
6560
6658
  return spec.providerOptions;
6561
6659
  }
@@ -6571,9 +6669,25 @@ function scenarioBaseUrl(value) {
6571
6669
  }
6572
6670
  return url.href.replace(/\/$/, "");
6573
6671
  }
6574
- function requestBody(spec, options) {
6672
+ function referenceParameter(options) {
6673
+ return options.referenceParameter?.trim() || "referenceImages";
6674
+ }
6675
+ function readRevisionSource(spec) {
6676
+ const revision = spec.revision;
6677
+ if (!revision.sourceFile || !revision.sourceSha256 || !revision.sourceFormat) {
6678
+ throw new Error(`${spec.styleId}/${spec.assetId}: revision source is not ready`);
6679
+ }
6680
+ const bytes = readFileSync2(revision.sourceFile);
6681
+ if (sha256(bytes) !== revision.sourceSha256) {
6682
+ throw new Error(`${spec.styleId}/${spec.assetId}: revision source changed after the manifest was resolved`);
6683
+ }
6684
+ return { base64: bytes.toString("base64"), format: revision.sourceFormat };
6685
+ }
6686
+ function requestBody(spec, options, referenceAssetIds = []) {
6687
+ const parameter = referenceParameter(options);
6575
6688
  return {
6576
6689
  ...options.parameters ?? {},
6690
+ ...referenceAssetIds.length ? { [parameter]: options.referenceArray === false ? referenceAssetIds[0] : referenceAssetIds } : {},
6577
6691
  prompt: spec.prompt,
6578
6692
  width: spec.width,
6579
6693
  height: spec.height,
@@ -6663,9 +6777,11 @@ function scenarioAssetIds(job) {
6663
6777
  function assetDownloadUrl(asset) {
6664
6778
  return asset.originalFileUrl || asset.url;
6665
6779
  }
6780
+ var UnsupportedScenarioOutput = class extends Error {
6781
+ };
6666
6782
  function assertPngAsset(asset) {
6667
6783
  if (asset.originalMimeType && asset.originalMimeType !== MediaType.PNG) {
6668
- throw new Error(
6784
+ throw new UnsupportedScenarioOutput(
6669
6785
  `Scenario asset ${asset.id} is ${asset.originalMimeType}; PixelKiln's Scenario MVP requires PNG output`
6670
6786
  );
6671
6787
  }
@@ -6732,7 +6848,7 @@ function redactScenarioError(value, secrets) {
6732
6848
  // src/providers/comfyui.ts
6733
6849
  import { randomUUID as randomUUID2 } from "crypto";
6734
6850
  import { readFile } from "fs/promises";
6735
- import path2 from "path";
6851
+ import path3 from "path";
6736
6852
  var DEFAULT_BASE_URL3 = "http://127.0.0.1:8188";
6737
6853
  var BUILTIN_BINDINGS = /* @__PURE__ */ new Set([
6738
6854
  "prompt",
@@ -6869,7 +6985,7 @@ var ComfyUIProvider = class _ComfyUIProvider {
6869
6985
  }
6870
6986
  async resolveOptions(value, context) {
6871
6987
  const options = parseOptions(value);
6872
- const workflowPath = path2.resolve(context.root, options.workflowFile);
6988
+ const workflowPath = path3.resolve(context.root, options.workflowFile);
6873
6989
  let raw;
6874
6990
  try {
6875
6991
  raw = await readFile(workflowPath, "utf8");
@@ -6924,7 +7040,7 @@ var ComfyUIProvider = class _ComfyUIProvider {
6924
7040
  if (typeof item !== "string") {
6925
7041
  throw new Error(`ComfyUI ${label} must be a manifest-relative PNG or JPEG path`);
6926
7042
  }
6927
- const file = path2.resolve(context.root, item);
7043
+ const file = path3.resolve(context.root, item);
6928
7044
  let bytes;
6929
7045
  try {
6930
7046
  bytes = await readFile(file);
@@ -7636,7 +7752,7 @@ function canonical(value) {
7636
7752
  }
7637
7753
 
7638
7754
  // src/providers/fake.ts
7639
- import { createHash as createHash2 } from "crypto";
7755
+ import { createHash as createHash3 } from "crypto";
7640
7756
  var FAKE_PNG = Buffer.from(
7641
7757
  "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==",
7642
7758
  "base64"
@@ -7738,7 +7854,7 @@ var FakeProvider = class {
7738
7854
  }
7739
7855
  async download(url) {
7740
7856
  if (!url.startsWith("fake://")) throw new Error(`fake provider cannot download ${url}`);
7741
- const salt = createHash2("sha256").update(url).digest();
7857
+ const salt = createHash3("sha256").update(url).digest();
7742
7858
  return Buffer.concat([FAKE_PNG, salt.subarray(0, 4)]);
7743
7859
  }
7744
7860
  async balance() {
@@ -7887,7 +8003,7 @@ registerProvider({
7887
8003
  // src/manifest.ts
7888
8004
  import { readFile as readFile3 } from "fs/promises";
7889
8005
  import { existsSync as existsSync4 } from "fs";
7890
- import path5 from "path";
8006
+ import path6 from "path";
7891
8007
 
7892
8008
  // src/loop-directions.ts
7893
8009
  var SINGLE_OUTPUT_FIELDS = ["file", "cell", "remoteId", "source", "sourceByStyle", "outputRole"];
@@ -7976,12 +8092,12 @@ function isObject2(value) {
7976
8092
 
7977
8093
  // src/outputs.ts
7978
8094
  import { existsSync as existsSync3 } from "fs";
7979
- import path4 from "path";
8095
+ import path5 from "path";
7980
8096
 
7981
8097
  // src/lock.ts
7982
8098
  import { readFile as readFile2, writeFile, rename, mkdir, rmdir, rm, stat } from "fs/promises";
7983
8099
  import { existsSync as existsSync2 } from "fs";
7984
- import path3 from "path";
8100
+ import path4 from "path";
7985
8101
  async function loadLock(lockPath) {
7986
8102
  if (!existsSync2(lockPath)) return { version: 2, entries: {} };
7987
8103
  try {
@@ -7998,7 +8114,7 @@ var saveQueues = /* @__PURE__ */ new Map();
7998
8114
  var dirtyPatches = /* @__PURE__ */ new WeakMap();
7999
8115
  var dirtyDeletes = /* @__PURE__ */ new WeakMap();
8000
8116
  function saveLock(lockPath, lock) {
8001
- const queueKey = path3.resolve(lockPath);
8117
+ const queueKey = path4.resolve(lockPath);
8002
8118
  const previous = saveQueues.get(queueKey) ?? Promise.resolve();
8003
8119
  const next = previous.catch(() => {
8004
8120
  }).then(() => writeLockNow(lockPath, lock));
@@ -8010,7 +8126,7 @@ function saveLock(lockPath, lock) {
8010
8126
  return next;
8011
8127
  }
8012
8128
  async function writeLockNow(lockPath, lock) {
8013
- await mkdir(path3.dirname(path3.resolve(lockPath)), { recursive: true });
8129
+ await mkdir(path4.dirname(path4.resolve(lockPath)), { recursive: true });
8014
8130
  const release = await acquireFileLock(lockPath);
8015
8131
  try {
8016
8132
  await writeLockWhileHeld(lockPath, lock);
@@ -8155,22 +8271,22 @@ function totalSpend(lock, unit = "generations") {
8155
8271
 
8156
8272
  // src/outputs.ts
8157
8273
  function portableOutputPath(file, manifestDir) {
8158
- const root = path4.resolve(manifestDir);
8159
- const absolute = path4.isAbsolute(file) ? path4.normalize(file) : path4.resolve(root, file);
8160
- const relative = path4.relative(root, absolute);
8161
- const value = path4.isAbsolute(relative) ? absolute : relative;
8162
- return value.split(path4.sep).join("/");
8274
+ const root = path5.resolve(manifestDir);
8275
+ const absolute = path5.isAbsolute(file) ? path5.normalize(file) : path5.resolve(root, file);
8276
+ const relative = path5.relative(root, absolute);
8277
+ const value = path5.isAbsolute(relative) ? absolute : relative;
8278
+ return value.split(path5.sep).join("/");
8163
8279
  }
8164
8280
  function resolveOutputPath(recordedPath, manifestDir) {
8165
- if (path4.isAbsolute(recordedPath)) return path4.normalize(recordedPath);
8166
- if (path4.win32.isAbsolute(recordedPath)) return recordedPath;
8167
- return path4.resolve(manifestDir, recordedPath.split(/[\\/]/).join(path4.sep));
8281
+ if (path5.isAbsolute(recordedPath)) return path5.normalize(recordedPath);
8282
+ if (path5.win32.isAbsolute(recordedPath)) return recordedPath;
8283
+ return path5.resolve(manifestDir, recordedPath.split(/[\\/]/).join(path5.sep));
8168
8284
  }
8169
8285
  function expectedOutputPath(spec, role, index, total, mediaType) {
8170
8286
  return memberPath(spec.outFile, role, index, total, mediaType);
8171
8287
  }
8172
8288
  function memberPath(file, role, index, total, mediaType) {
8173
- const originalExt = path4.extname(file);
8289
+ const originalExt = path5.extname(file);
8174
8290
  const ext = mediaType ? mediaExtension(mediaType) : originalExt || ".png";
8175
8291
  const stem = originalExt ? file.slice(0, -originalExt.length) : file;
8176
8292
  if (total === 1) return `${stem}${ext}`;
@@ -8184,10 +8300,10 @@ function isFrameSetEntry(entry) {
8184
8300
  return entry.generator === "frames" && isMemberSetEntry(entry);
8185
8301
  }
8186
8302
  function sourceIsStem(source, entry, manifestDir) {
8187
- return isMemberSetEntry(entry) && !existsSync3(path4.resolve(manifestDir, source));
8303
+ return isMemberSetEntry(entry) && !existsSync3(path5.resolve(manifestDir, source));
8188
8304
  }
8189
8305
  function sourceOutputPath(source, entry, index, manifestDir) {
8190
- const absolute = path4.resolve(manifestDir, source);
8306
+ const absolute = path5.resolve(manifestDir, source);
8191
8307
  if (!sourceIsStem(source, entry, manifestDir)) return absolute;
8192
8308
  const output = entry.outputs[index];
8193
8309
  return memberPath(absolute, output.role, index, entry.outputs.length, output.mediaType);
@@ -8274,7 +8390,7 @@ function resolveSpecOutputs(spec, lock, manifestDir) {
8274
8390
  id: spec.assetId,
8275
8391
  index: 0,
8276
8392
  path: spec.outFile,
8277
- absolutePath: path4.resolve(spec.outFile),
8393
+ absolutePath: path5.resolve(spec.outFile),
8278
8394
  sha256: ""
8279
8395
  }];
8280
8396
  }
@@ -8290,7 +8406,7 @@ function frameSetFps(entry) {
8290
8406
 
8291
8407
  // src/manifest.ts
8292
8408
  async function loadManifest(manifestPath) {
8293
- const abs = path5.resolve(manifestPath);
8409
+ const abs = path6.resolve(manifestPath);
8294
8410
  if (!existsSync4(abs)) throw new ProjectError(`No manifest at ${abs}`);
8295
8411
  const expansion = expandLoopDirections(JSON.parse(await readFile3(abs, "utf8")));
8296
8412
  if (expansion.issues.length) {
@@ -8383,7 +8499,7 @@ ${issues}`);
8383
8499
  ${unknownReferences.map((i) => ` ${i}`).join("\n")}`);
8384
8500
  }
8385
8501
  const loopFamilies = Object.keys(expansion.families).length ? { loopFamilies: expansion.families } : {};
8386
- return { manifest: parsed.data, root: path5.dirname(abs), path: abs, ...loopFamilies };
8502
+ return { manifest: parsed.data, root: path6.dirname(abs), path: abs, ...loopFamilies };
8387
8503
  }
8388
8504
  function parentAssetId(asset) {
8389
8505
  return asset?.revision?.from ?? asset?.state?.of ?? asset?.animation?.of ?? asset?.portrait?.of ?? asset?.outfit?.of;
@@ -8484,7 +8600,7 @@ async function resolveSpecs(loaded, filter) {
8484
8600
  }
8485
8601
  const styleImageCache = /* @__PURE__ */ new Map();
8486
8602
  async function loadStyleImage(rel, what = "Style image") {
8487
- const abs = path5.resolve(root, rel);
8603
+ const abs = path6.resolve(root, rel);
8488
8604
  let hit = styleImageCache.get(abs);
8489
8605
  if (!hit) {
8490
8606
  if (!existsSync4(abs)) throw new Error(`${what} not found: ${abs}`);
@@ -8514,7 +8630,7 @@ async function resolveSpecs(loaded, filter) {
8514
8630
  styleImageDimensions.push(loadedImage);
8515
8631
  }
8516
8632
  const resolvedImages = style.styleImages.map((image) => {
8517
- const hit = styleImageCache.get(path5.resolve(root, image.path));
8633
+ const hit = styleImageCache.get(path6.resolve(root, image.path));
8518
8634
  return { base64: hit.base64, width: hit.width, height: hit.height, format: hit.format };
8519
8635
  });
8520
8636
  const styleSpecs = /* @__PURE__ */ new Map();
@@ -8615,9 +8731,9 @@ async function resolveSpecs(loaded, filter) {
8615
8731
  } else {
8616
8732
  prompt = [style.promptPrefix, subject, style.promptSuffix].map((p) => p.trim()).filter(Boolean).join(", ");
8617
8733
  }
8618
- const relFile = asset.file ?? path5.join(asset.category ?? "", `${assetId}.png`);
8619
- const outFile = path5.resolve(root, style.outDir, relFile);
8620
- const qualityOutFile = style.quality ? path5.resolve(root, style.quality.outDir, pngPath(relFile)) : void 0;
8734
+ const relFile = asset.file ?? path6.join(asset.category ?? "", `${assetId}.png`);
8735
+ const outFile = path6.resolve(root, style.outDir, relFile);
8736
+ const qualityOutFile = style.quality ? path6.resolve(root, style.quality.outDir, pngPath(relFile)) : void 0;
8621
8737
  const tileSize = generator === "tiles" ? size : style.tileSize ?? 32;
8622
8738
  const tileVariations = tileFeatureOutputCount(generator === "tiles" ? style.tileFeature : void 0) ?? tileVariationCount(countNumberedDescriptions(prompt));
8623
8739
  const terrainTiles = generator === "terrain" ? terrainTileCount(style.terrainTransitionSize) : 0;
@@ -8712,7 +8828,7 @@ async function resolveSpecs(loaded, filter) {
8712
8828
  minGridConfidence: style.quality.minGridConfidence,
8713
8829
  ...style.quality.minTransparency == null ? {} : { minTransparency: style.quality.minTransparency },
8714
8830
  ...style.quality.fixerRevision ? { fixerRevision: style.quality.fixerRevision } : {},
8715
- ...style.quality.fixerPython ? { fixerPython: path5.resolve(root, style.quality.fixerPython) } : {},
8831
+ ...style.quality.fixerPython ? { fixerPython: path6.resolve(root, style.quality.fixerPython) } : {},
8716
8832
  ...style.quality.fps == null ? {} : { fps: style.quality.fps }
8717
8833
  }
8718
8834
  } : {},
@@ -8848,7 +8964,7 @@ async function resolveSpecs(loaded, filter) {
8848
8964
  animation: sourceSpec.character.animation,
8849
8965
  outfit: {
8850
8966
  reference: {
8851
- path: path5.resolve(root, asset.outfit.reference),
8967
+ path: path6.resolve(root, asset.outfit.reference),
8852
8968
  sha256: referenceImage.hash,
8853
8969
  width: referenceImage.width,
8854
8970
  height: referenceImage.height,
@@ -8870,23 +8986,23 @@ async function resolveSpecs(loaded, filter) {
8870
8986
  );
8871
8987
  }
8872
8988
  const sourceSpec = await finalize(asset.revision.from);
8873
- const sourceStem = sourceSpec.quality?.outFile ?? (sourceSpec.source ? path5.resolve(root, sourceSpec.source) : sourceSpec.outFile);
8989
+ const sourceStem = sourceSpec.quality?.outFile ?? (sourceSpec.source ? path6.resolve(root, sourceSpec.source) : sourceSpec.outFile);
8874
8990
  const sourceMembers = await revisionSourceMembers(sourceStem, `revision source ${asset.revision.from}`);
8875
8991
  const sourceFile = sourceMembers?.[0]?.file ?? sourceStem;
8876
8992
  const sourceImage = sourceMembers ? { ...sourceMembers[0], hash: sha256(JSON.stringify(sourceMembers.map((member) => member.sha256))) } : await optionalRevisionImage(sourceFile, "revision source");
8877
8993
  const sourceFps = sourceSpec.character?.animation?.fps ?? sourceSpec.objectPro?.animation?.fps ?? sourceSpec.revision?.fps ?? sourceSpec.revision?.sourceFps;
8878
- const maskFile = asset.revision.mask ? path5.resolve(root, asset.revision.mask) : void 0;
8994
+ const maskFile = asset.revision.mask ? path6.resolve(root, asset.revision.mask) : void 0;
8879
8995
  const maskImage = maskFile ? await optionalRevisionImage(maskFile, "revision mask", "png") : null;
8880
8996
  if (sourceImage && maskImage && (sourceImage.width !== maskImage.width || sourceImage.height !== maskImage.height)) {
8881
8997
  throw new Error(
8882
8998
  `Revision mask for ${styleId}/${assetId} is ${maskImage.width}x${maskImage.height}; source ${asset.revision.from} is ${sourceImage.width}x${sourceImage.height}`
8883
8999
  );
8884
9000
  }
8885
- const paletteImageFile = asset.revision.paletteImage ? path5.resolve(root, asset.revision.paletteImage) : void 0;
9001
+ const paletteImageFile = asset.revision.paletteImage ? path6.resolve(root, asset.revision.paletteImage) : void 0;
8886
9002
  const paletteImage = paletteImageFile ? await optionalRevisionImage(paletteImageFile, "revision palette image", "png") : null;
8887
- const lastFrameFile = asset.revision.lastFrame ? path5.resolve(root, asset.revision.lastFrame) : void 0;
9003
+ const lastFrameFile = asset.revision.lastFrame ? path6.resolve(root, asset.revision.lastFrame) : void 0;
8888
9004
  const lastFrame = lastFrameFile ? await optionalRevisionImage(lastFrameFile, "revision last frame") : null;
8889
- const keypointsFile = asset.revision.keypointsFile ? path5.resolve(root, asset.revision.keypointsFile) : void 0;
9005
+ const keypointsFile = asset.revision.keypointsFile ? path6.resolve(root, asset.revision.keypointsFile) : void 0;
8890
9006
  const keypoints = keypointsFile ? await optionalSkeletonSet(keypointsFile) : null;
8891
9007
  resolved.revision = {
8892
9008
  mode: asset.revision.mode,
@@ -9053,7 +9169,7 @@ async function resolveSpecs(loaded, filter) {
9053
9169
  return specs;
9054
9170
  }
9055
9171
  function pngPath(file) {
9056
- const extension = path5.extname(file);
9172
+ const extension = path6.extname(file);
9057
9173
  return extension ? `${file.slice(0, -extension.length)}.png` : `${file}.png`;
9058
9174
  }
9059
9175
  async function resolveMapScene(root, styleId, assetId, generator, scene, provider) {
@@ -9063,12 +9179,12 @@ async function resolveMapScene(root, styleId, assetId, generator, scene, provide
9063
9179
  if (!provider.supportsMapScene) {
9064
9180
  throw new Error(`assets.${assetId}.scene: provider "${provider.id}" cannot draw into a scene; PixelLab's /map-objects can`);
9065
9181
  }
9066
- const file = path5.resolve(root, scene.image);
9182
+ const file = path6.resolve(root, scene.image);
9067
9183
  const image = await optionalRevisionImage(file, "scene image", "png");
9068
9184
  const base = { file, sha256: image?.hash ?? null, width: image?.width ?? null, height: image?.height ?? null };
9069
9185
  const placement = scene.placement;
9070
9186
  if ("mask" in placement) {
9071
- const maskFile = path5.resolve(root, placement.mask);
9187
+ const maskFile = path6.resolve(root, placement.mask);
9072
9188
  const mask = await optionalRevisionImage(maskFile, "scene mask", "png");
9073
9189
  if (image && mask && (image.width !== mask.width || image.height !== mask.height)) {
9074
9190
  throw new Error(
@@ -9140,7 +9256,7 @@ async function resolveStyleImages(loaded, styleId) {
9140
9256
  if (!style) throw new Error(`Unknown style "${styleId}"`);
9141
9257
  const out = [];
9142
9258
  for (const img of style.styleImages) {
9143
- const file = path5.resolve(loaded.root, img.path);
9259
+ const file = path6.resolve(loaded.root, img.path);
9144
9260
  const buf = await readFile3(file);
9145
9261
  const metadata = imageMetadata(buf);
9146
9262
  if (!metadata) throw new Error(`Style image is not a readable PNG or JPEG: ${file}`);
@@ -9171,7 +9287,7 @@ async function resolveCharacterShape(asset, style, kind, files) {
9171
9287
  };
9172
9288
  if (asset.concept) {
9173
9289
  const image = await files.load(asset.concept, "Concept image");
9174
- shape.concept = { path: path5.resolve(files.root, asset.concept), sha256: image.hash, width: image.width, height: image.height, format: image.format };
9290
+ shape.concept = { path: path6.resolve(files.root, asset.concept), sha256: image.hash, width: image.width, height: image.height, format: image.format };
9175
9291
  }
9176
9292
  if (asset.reference) {
9177
9293
  const byDirection = typeof asset.reference === "string" ? { south: asset.reference } : asset.reference;
@@ -9179,7 +9295,7 @@ async function resolveCharacterShape(asset, style, kind, files) {
9179
9295
  for (const [direction, rel] of Object.entries(byDirection)) {
9180
9296
  const image = await files.load(rel, `Reference sprite (${direction})`);
9181
9297
  shape.reference[direction] = {
9182
- path: path5.resolve(files.root, rel),
9298
+ path: path6.resolve(files.root, rel),
9183
9299
  sha256: image.hash,
9184
9300
  width: image.width,
9185
9301
  height: image.height,
@@ -9200,7 +9316,7 @@ async function resolveCharacterShape(asset, style, kind, files) {
9200
9316
  const animationMode = asset.animation.mode ?? (asset.animation.template ? "template" : "v3");
9201
9317
  const pose = async (rel, what) => {
9202
9318
  const image = await files.load(rel, what);
9203
- return { path: path5.resolve(files.root, rel), sha256: image.hash, width: image.width, height: image.height, format: image.format };
9319
+ return { path: path6.resolve(files.root, rel), sha256: image.hash, width: image.width, height: image.height, format: image.format };
9204
9320
  };
9205
9321
  shape.animation = {
9206
9322
  mode: animationMode,
@@ -9231,7 +9347,7 @@ async function resolveObjectProShape(assetId, asset, style, kind, files) {
9231
9347
  const south = typeof asset.reference === "string" ? asset.reference : asset.reference.south;
9232
9348
  if (south) {
9233
9349
  const image = await files.load(south, "Reference sprite");
9234
- shape.reference = { path: path5.resolve(files.root, south), sha256: image.hash, width: image.width, height: image.height, format: image.format };
9350
+ shape.reference = { path: path6.resolve(files.root, south), sha256: image.hash, width: image.width, height: image.height, format: image.format };
9235
9351
  }
9236
9352
  }
9237
9353
  if (asset.state) {
@@ -9259,7 +9375,7 @@ async function resolveObjectProShape(assetId, asset, style, kind, files) {
9259
9375
  }
9260
9376
  const pose = async (rel, what) => {
9261
9377
  const image = await files.load(rel, what);
9262
- return { path: path5.resolve(files.root, rel), sha256: image.hash, width: image.width, height: image.height, format: image.format };
9378
+ return { path: path6.resolve(files.root, rel), sha256: image.hash, width: image.width, height: image.height, format: image.format };
9263
9379
  };
9264
9380
  shape.animation = {
9265
9381
  mode: asset.animation.mode ?? "v3",
@@ -9279,11 +9395,11 @@ function characterDirections(directions) {
9279
9395
  }
9280
9396
 
9281
9397
  // src/project.ts
9282
- import path13 from "path";
9398
+ import path14 from "path";
9283
9399
 
9284
9400
  // src/env.ts
9285
- import { readFileSync as readFileSync2, existsSync as existsSync5 } from "fs";
9286
- import path6 from "path";
9401
+ import { readFileSync as readFileSync3, existsSync as existsSync5 } from "fs";
9402
+ import path7 from "path";
9287
9403
  function loadEnvFiles(dir) {
9288
9404
  const loaded = [];
9289
9405
  for (const [file, vars] of envFiles(dir)) {
@@ -9297,10 +9413,10 @@ function loadEnvFiles(dir) {
9297
9413
  function envFiles(dir) {
9298
9414
  const out = [];
9299
9415
  for (const name of [".env.local", ".env"]) {
9300
- const file = path6.join(dir, name);
9416
+ const file = path7.join(dir, name);
9301
9417
  if (!existsSync5(file)) continue;
9302
9418
  try {
9303
- out.push([file, parseEnv(readFileSync2(file, "utf8"))]);
9419
+ out.push([file, parseEnv(readFileSync3(file, "utf8"))]);
9304
9420
  } catch {
9305
9421
  }
9306
9422
  }
@@ -9327,11 +9443,11 @@ function parseEnv(contents) {
9327
9443
 
9328
9444
  // src/pipeline/plan.ts
9329
9445
  import { existsSync as existsSync8 } from "fs";
9330
- import path12 from "path";
9446
+ import path13 from "path";
9331
9447
 
9332
9448
  // src/pipeline/mirror.ts
9333
9449
  import { mkdir as mkdir2, readFile as readFile4, rename as rename2, writeFile as writeFile2 } from "fs/promises";
9334
- import path7 from "path";
9450
+ import path8 from "path";
9335
9451
 
9336
9452
  // src/palette.ts
9337
9453
  function normalizePalette(values) {
@@ -9470,7 +9586,7 @@ async function deriveMirror(spec, lock, lockPath, opts = {}) {
9470
9586
  for (const [index, member] of members.entries()) {
9471
9587
  const bytes = flipHorizontal(await readFile4(member.file));
9472
9588
  const target = expectedOutputPath(spec, member.role, index, members.length, MediaType.PNG);
9473
- await mkdir2(path7.dirname(target), { recursive: true });
9589
+ await mkdir2(path8.dirname(target), { recursive: true });
9474
9590
  const tmp = `${target}.pixelkiln.tmp`;
9475
9591
  await writeFile2(tmp, bytes);
9476
9592
  await rename2(tmp, target);
@@ -9533,28 +9649,28 @@ async function deriveMirror(spec, lock, lockPath, opts = {}) {
9533
9649
 
9534
9650
  // src/pipeline/quality-profile.ts
9535
9651
  import { existsSync as existsSync6 } from "fs";
9536
- import path10 from "path";
9652
+ import path11 from "path";
9537
9653
 
9538
9654
  // src/pipeline/refine.ts
9539
- import path9 from "path";
9655
+ import path10 from "path";
9540
9656
  import { execFile } from "child_process";
9541
9657
  import { mkdtemp, readFile as readFile6, rm as rm3 } from "fs/promises";
9542
9658
  import { tmpdir } from "os";
9543
9659
  import { promisify } from "util";
9544
9660
 
9545
9661
  // src/artifacts.ts
9546
- import path8 from "path";
9547
- import { createHash as createHash3, randomUUID as randomUUID3 } from "crypto";
9662
+ import path9 from "path";
9663
+ import { createHash as createHash4, randomUUID as randomUUID3 } from "crypto";
9548
9664
  import { mkdir as mkdir3, readFile as readFile5, rename as rename3, rm as rm2, writeFile as writeFile3 } from "fs/promises";
9549
9665
  var activeTransactions = /* @__PURE__ */ new Set();
9550
9666
  function message(error) {
9551
9667
  return error instanceof Error ? error.message : String(error);
9552
9668
  }
9553
9669
  function digest(data) {
9554
- return createHash3("sha256").update(data).digest("hex");
9670
+ return createHash4("sha256").update(data).digest("hex");
9555
9671
  }
9556
9672
  function portableRelative(from, to) {
9557
- return path8.relative(from, path8.resolve(to)).split(path8.sep).join("/") || ".";
9673
+ return path9.relative(from, path9.resolve(to)).split(path9.sep).join("/") || ".";
9558
9674
  }
9559
9675
  function canonical2(value) {
9560
9676
  if (value === null || typeof value === "string" || typeof value === "boolean") return value;
@@ -9604,7 +9720,7 @@ function parseArtifactManifest(absolute, data) {
9604
9720
  return raw;
9605
9721
  }
9606
9722
  async function readArtifactBundleManifest(manifestPath) {
9607
- const absolute = path8.resolve(manifestPath);
9723
+ const absolute = path9.resolve(manifestPath);
9608
9724
  return parseArtifactManifest(absolute, await readFile5(absolute, "utf8"));
9609
9725
  }
9610
9726
  async function readOptional(file) {
@@ -9628,8 +9744,8 @@ function processIsAlive(pid) {
9628
9744
  }
9629
9745
  }
9630
9746
  function validTemporaryPath(candidate, destination, type) {
9631
- return path8.dirname(candidate) === path8.dirname(destination) && path8.basename(candidate).startsWith(
9632
- `.${path8.basename(destination)}.pixelkiln-${type}-`
9747
+ return path9.dirname(candidate) === path9.dirname(destination) && path9.basename(candidate).startsWith(
9748
+ `.${path9.basename(destination)}.pixelkiln-${type}-`
9633
9749
  );
9634
9750
  }
9635
9751
  function parseTransaction(journal, data) {
@@ -9649,7 +9765,7 @@ async function removeTransactionFiles(journal) {
9649
9765
  await rm2(transactionMarker(journal), { force: true });
9650
9766
  }
9651
9767
  async function recoverArtifactTransaction(recoveryFile, allowedDestinations) {
9652
- const journal = path8.resolve(recoveryFile);
9768
+ const journal = path9.resolve(recoveryFile);
9653
9769
  const bytes = await readOptional(journal);
9654
9770
  if (!bytes) {
9655
9771
  await rm2(transactionMarker(journal), { force: true });
@@ -9665,7 +9781,7 @@ async function recoverArtifactTransaction(recoveryFile, allowedDestinations) {
9665
9781
  );
9666
9782
  }
9667
9783
  for (const entry of transaction.entries) {
9668
- if (typeof entry.destination !== "string" || typeof entry.stage !== "string" || typeof entry.sha256 !== "string" || entry.backup !== void 0 && typeof entry.backup !== "string" || !allowedDestinations.has(path8.resolve(entry.destination)) || !validTemporaryPath(entry.stage, entry.destination, "stage") || entry.backup !== void 0 && !validTemporaryPath(entry.backup, entry.destination, "backup")) {
9784
+ if (typeof entry.destination !== "string" || typeof entry.stage !== "string" || typeof entry.sha256 !== "string" || entry.backup !== void 0 && typeof entry.backup !== "string" || !allowedDestinations.has(path9.resolve(entry.destination)) || !validTemporaryPath(entry.stage, entry.destination, "stage") || entry.backup !== void 0 && !validTemporaryPath(entry.backup, entry.destination, "backup")) {
9669
9785
  throw new Error(
9670
9786
  `Refusing unsafe artifact recovery from ${journal}; its destinations or temporary paths do not match the current bundle.`
9671
9787
  );
@@ -9711,7 +9827,7 @@ async function recoverArtifactTransaction(recoveryFile, allowedDestinations) {
9711
9827
  await removeTransactionFiles(journal);
9712
9828
  }
9713
9829
  function createArtifactBundleManifest(manifestPath, outputs, provenance) {
9714
- const root = path8.dirname(path8.resolve(manifestPath));
9830
+ const root = path9.dirname(path9.resolve(manifestPath));
9715
9831
  const sources = provenance.sources.map((source) => ({
9716
9832
  ...source,
9717
9833
  path: portableRelative(root, source.path)
@@ -9741,11 +9857,11 @@ function withArtifactManifest(manifestPath, outputs, provenance) {
9741
9857
  ];
9742
9858
  }
9743
9859
  async function writeManagedArtifactBundle(manifestPath, outputs, provenance, options = {}) {
9744
- const absoluteManifest = path8.resolve(manifestPath);
9860
+ const absoluteManifest = path9.resolve(manifestPath);
9745
9861
  const recoveryFile = `${absoluteManifest}.transaction`;
9746
9862
  const allowedDestinations = /* @__PURE__ */ new Set([
9747
9863
  absoluteManifest,
9748
- ...outputs.map((output) => path8.resolve(output.path))
9864
+ ...outputs.map((output) => path9.resolve(output.path))
9749
9865
  ]);
9750
9866
  await recoverArtifactTransaction(recoveryFile, allowedDestinations);
9751
9867
  const existingManifestBytes = await readOptional(absoluteManifest);
@@ -9763,13 +9879,13 @@ async function writeManagedArtifactBundle(manifestPath, outputs, provenance, opt
9763
9879
  }
9764
9880
  }
9765
9881
  if (!options.force) {
9766
- const root = path8.dirname(absoluteManifest);
9882
+ const root = path9.dirname(absoluteManifest);
9767
9883
  const recorded = new Map(
9768
- previous?.outputs.map((output) => [path8.resolve(root, output.path), output.sha256]) ?? []
9884
+ previous?.outputs.map((output) => [path9.resolve(root, output.path), output.sha256]) ?? []
9769
9885
  );
9770
9886
  const conflicts = [];
9771
9887
  for (const output of outputs) {
9772
- const destination = path8.resolve(output.path);
9888
+ const destination = path9.resolve(output.path);
9773
9889
  const current = await readOptional(destination);
9774
9890
  if (!current || digest(current) === digest(output.data)) continue;
9775
9891
  const expected = recorded.get(destination);
@@ -9791,15 +9907,15 @@ Pass { force: true } (CLI: --force) to take ownership and replace the complete b
9791
9907
  );
9792
9908
  }
9793
9909
  async function verifyArtifactBundle(manifestPath) {
9794
- const absolute = path8.resolve(manifestPath);
9910
+ const absolute = path9.resolve(manifestPath);
9795
9911
  const raw = await readArtifactBundleManifest(absolute);
9796
- const root = path8.dirname(absolute);
9912
+ const root = path9.dirname(absolute);
9797
9913
  const changedSources = [];
9798
9914
  const changedOutputs = [];
9799
9915
  for (const source of raw.sources) {
9800
9916
  let actual = null;
9801
9917
  try {
9802
- actual = digest(await readFile5(path8.resolve(root, source.path)));
9918
+ actual = digest(await readFile5(path9.resolve(root, source.path)));
9803
9919
  } catch (error) {
9804
9920
  if (!isCode(error, "ENOENT")) throw error;
9805
9921
  }
@@ -9807,7 +9923,7 @@ async function verifyArtifactBundle(manifestPath) {
9807
9923
  }
9808
9924
  for (const output of raw.outputs) {
9809
9925
  try {
9810
- if (digest(await readFile5(path8.resolve(root, output.path))) !== output.sha256) {
9926
+ if (digest(await readFile5(path9.resolve(root, output.path))) !== output.sha256) {
9811
9927
  changedOutputs.push(output.path);
9812
9928
  }
9813
9929
  } catch (error) {
@@ -9863,7 +9979,7 @@ async function writeArtifactBundle(files, options = {}) {
9863
9979
  if (!files.length) throw new Error("An artifact bundle must contain at least one file.");
9864
9980
  const normalized = files.map((file) => {
9865
9981
  if (!file.path.trim()) throw new Error("Artifact paths cannot be empty.");
9866
- return { destination: path8.resolve(file.path), data: Buffer.from(file.data) };
9982
+ return { destination: path9.resolve(file.path), data: Buffer.from(file.data) };
9867
9983
  });
9868
9984
  const destinations = /* @__PURE__ */ new Set();
9869
9985
  for (const artifact of normalized) {
@@ -9893,21 +10009,21 @@ async function writeArtifactBundle(files, options = {}) {
9893
10009
  if (!changed.length) return { changed: [], unchanged };
9894
10010
  const token = randomUUID3();
9895
10011
  for (const [index, artifact] of changed.entries()) {
9896
- const basename = path8.basename(artifact.destination);
9897
- artifact.stage = path8.join(
9898
- path8.dirname(artifact.destination),
10012
+ const basename = path9.basename(artifact.destination);
10013
+ artifact.stage = path9.join(
10014
+ path9.dirname(artifact.destination),
9899
10015
  `.${basename}.pixelkiln-stage-${token}-${index}`
9900
10016
  );
9901
10017
  if (artifact.existed) {
9902
- artifact.backup = path8.join(
9903
- path8.dirname(artifact.destination),
10018
+ artifact.backup = path9.join(
10019
+ path9.dirname(artifact.destination),
9904
10020
  `.${basename}.pixelkiln-backup-${token}-${index}`
9905
10021
  );
9906
10022
  }
9907
10023
  }
9908
- const journal = options.recoveryFile ? path8.resolve(options.recoveryFile) : null;
10024
+ const journal = options.recoveryFile ? path9.resolve(options.recoveryFile) : null;
9909
10025
  if (journal) {
9910
- await mkdir3(path8.dirname(journal), { recursive: true });
10026
+ await mkdir3(path9.dirname(journal), { recursive: true });
9911
10027
  const transaction = {
9912
10028
  format: "pixelkiln-artifact-transaction",
9913
10029
  version: 1,
@@ -9932,7 +10048,7 @@ async function writeArtifactBundle(files, options = {}) {
9932
10048
  }
9933
10049
  try {
9934
10050
  for (const [index, artifact] of changed.entries()) {
9935
- await mkdir3(path8.dirname(artifact.destination), { recursive: true });
10051
+ await mkdir3(path9.dirname(artifact.destination), { recursive: true });
9936
10052
  await options.beforeStage?.(artifact.destination, index);
9937
10053
  await writeFile3(artifact.stage, artifact.data, { flag: "wx" });
9938
10054
  }
@@ -10198,10 +10314,10 @@ function sameFrameGrid(first, next) {
10198
10314
  return next.detection.stepX === first.detection.stepX && next.detection.stepY === first.detection.stepY && next.detection.columns === first.detection.columns && next.detection.rows === first.detection.rows && phaseMatches(next.detection.phaseX, first.detection.phaseX, first.detection.stepX) && phaseMatches(next.detection.phaseY, first.detection.phaseY, first.detection.stepY) && next.audit.width === first.audit.width && next.audit.height === first.audit.height && next.sourceWidth === first.sourceWidth && next.sourceHeight === first.sourceHeight;
10199
10315
  }
10200
10316
  async function refineAsset(options) {
10201
- const source = path9.resolve(options.source);
10202
- const output = path9.resolve(options.output);
10317
+ const source = path10.resolve(options.source);
10318
+ const output = path10.resolve(options.output);
10203
10319
  if (source === output) throw new Error("Refinement source and output must be different files.");
10204
- const record = path9.resolve(qualityRecordPath(output));
10320
+ const record = path10.resolve(qualityRecordPath(output));
10205
10321
  const palette = normalizePalette(options.palette).map((color) => color.hex);
10206
10322
  const minGridConfidence = requireConfidence(options.minGridConfidence ?? "high");
10207
10323
  if (options.minTransparency !== void 0 && (!Number.isFinite(options.minTransparency) || options.minTransparency < 0 || options.minTransparency > 1)) throw new Error("minTransparency must be between 0 and 1.");
@@ -10224,8 +10340,8 @@ async function refineAsset(options) {
10224
10340
  );
10225
10341
  }
10226
10342
  }
10227
- const temp = await mkdtemp(path9.join(tmpdir(), "pixelkiln-refine-"));
10228
- const recovered = path9.join(temp, "native.png");
10343
+ const temp = await mkdtemp(path10.join(tmpdir(), "pixelkiln-refine-"));
10344
+ const recovered = path10.join(temp, "native.png");
10229
10345
  const fixerCommand = compatibilityCommand ?? options.fixerPython ?? process.env.PIXELKILN_PIXEL_FIXER_PYTHON ?? "python3";
10230
10346
  const args = compatibilityCommand ? [...options.fixerArgsPrefix ?? [], source, "--extract", recovered] : ["-c", PYTHON_FIXER_BRIDGE, source, recovered];
10231
10347
  try {
@@ -10310,18 +10426,18 @@ async function refineFrameSet(options) {
10310
10426
  if (roles.some((role) => !role.trim()) || new Set(roles).size !== roles.length) {
10311
10427
  throw new Error("Frame-set roles must be non-empty and unique.");
10312
10428
  }
10313
- const output = path9.resolve(options.output);
10314
- const record = path9.resolve(qualityRecordPath(output));
10429
+ const output = path10.resolve(options.output);
10430
+ const record = path10.resolve(qualityRecordPath(output));
10315
10431
  const palette = normalizeRefinementPalette(options.palette);
10316
10432
  const finalOutputs = options.sources.map((source, index) => qualityFrameOutputPath(output, source.role, index));
10317
10433
  if (new Set(finalOutputs).size !== finalOutputs.length) {
10318
10434
  throw new Error("Frame-set roles resolve to duplicate output filenames.");
10319
10435
  }
10320
- const sourceSet = new Set(options.sources.map((source) => path9.resolve(source.path)));
10436
+ const sourceSet = new Set(options.sources.map((source) => path10.resolve(source.path)));
10321
10437
  if (finalOutputs.some((file) => sourceSet.has(file))) {
10322
10438
  throw new Error("Frame-set refinement sources and outputs must be different files.");
10323
10439
  }
10324
- const temp = await mkdtemp(path9.join(tmpdir(), "pixelkiln-refine-frames-"));
10440
+ const temp = await mkdtemp(path10.join(tmpdir(), "pixelkiln-refine-frames-"));
10325
10441
  try {
10326
10442
  const frames = [];
10327
10443
  const outputs = [];
@@ -10329,8 +10445,8 @@ async function refineFrameSet(options) {
10329
10445
  const sourcePaths = [];
10330
10446
  let firstOptions = null;
10331
10447
  for (const [index, sourceEntry] of options.sources.entries()) {
10332
- const source = path9.resolve(sourceEntry.path);
10333
- const temporaryOutput = path9.join(temp, `${String(index).padStart(2, "0")}.png`);
10448
+ const source = path10.resolve(sourceEntry.path);
10449
+ const temporaryOutput = path10.join(temp, `${String(index).padStart(2, "0")}.png`);
10334
10450
  const refined = await refineAsset({
10335
10451
  source,
10336
10452
  output: temporaryOutput,
@@ -10405,7 +10521,7 @@ async function refineFrameSet(options) {
10405
10521
  );
10406
10522
  return {
10407
10523
  sources: sourcePaths,
10408
- outputs: outputs.map((item) => path9.resolve(item.path)),
10524
+ outputs: outputs.map((item) => path10.resolve(item.path)),
10409
10525
  record,
10410
10526
  frames,
10411
10527
  palette,
@@ -10416,12 +10532,12 @@ async function refineFrameSet(options) {
10416
10532
  }
10417
10533
  }
10418
10534
  async function checkQualityRecord(recordPath) {
10419
- const record = path9.resolve(recordPath);
10535
+ const record = path10.resolve(recordPath);
10420
10536
  const manifest = await readArtifactBundleManifest(record);
10421
10537
  const options = parseRecordOptions(manifest, record);
10422
10538
  const verification = await verifyArtifactBundle(record);
10423
- const sources = manifest.sources.map((source2) => path9.resolve(path9.dirname(record), source2.path));
10424
- const outputs = manifest.outputs.map((output2) => path9.resolve(path9.dirname(record), output2.path));
10539
+ const sources = manifest.sources.map((source2) => path10.resolve(path10.dirname(record), source2.path));
10540
+ const outputs = manifest.outputs.map((output2) => path10.resolve(path10.dirname(record), output2.path));
10425
10541
  const source = sources[0] ?? "";
10426
10542
  const output = outputs[0] ?? "";
10427
10543
  const expectedCount = options.frameSet?.count ?? 1;
@@ -10454,7 +10570,7 @@ async function checkQualityRecord(recordPath) {
10454
10570
  async function approveQualityRecord(recordPath, approval) {
10455
10571
  const reviewer = approval.reviewer.trim();
10456
10572
  if (!reviewer) throw new Error("A human reviewer name is required.");
10457
- const record = path9.resolve(recordPath);
10573
+ const record = path10.resolve(recordPath);
10458
10574
  const manifest = await readArtifactBundleManifest(record);
10459
10575
  const options = parseRecordOptions(manifest, record);
10460
10576
  const verification = await verifyArtifactBundle(record);
@@ -10475,14 +10591,14 @@ async function approveQualityRecord(recordPath, approval) {
10475
10591
  `A refinement record must contain exactly ${expectedCount} source(s) and output(s).`
10476
10592
  );
10477
10593
  }
10478
- const root = path9.dirname(record);
10594
+ const root = path10.dirname(record);
10479
10595
  const outputs = await Promise.all(manifest.outputs.map(async (entry) => {
10480
- const outputPath = path9.resolve(root, entry.path);
10596
+ const outputPath = path10.resolve(root, entry.path);
10481
10597
  return { path: outputPath, data: await readFile6(outputPath) };
10482
10598
  }));
10483
10599
  const sources = manifest.sources.map((source) => ({
10484
10600
  ...source,
10485
- path: path9.resolve(root, source.path)
10601
+ path: path10.resolve(root, source.path)
10486
10602
  }));
10487
10603
  const approved = {
10488
10604
  ...options,
@@ -10514,7 +10630,7 @@ async function qualitySource(spec, lock) {
10514
10630
  if (spec.generator === "frames") {
10515
10631
  return { items: null, reason: "frame-set quality requires ordered downloaded provider outputs" };
10516
10632
  }
10517
- const source = path10.resolve(spec.root, spec.source);
10633
+ const source = path11.resolve(spec.root, spec.source);
10518
10634
  return existsSync6(source) ? { items: [{ role: "source", path: source }] } : { items: null, reason: `declared source is missing: ${spec.source}` };
10519
10635
  }
10520
10636
  const entry = lock.entries[lockKey(spec.styleId, spec.assetId)];
@@ -10581,7 +10697,7 @@ async function inspectQualityProfile(spec, lock) {
10581
10697
  const expectedRevision = spec.quality.fixerRevision ?? PIXEL_ART_FIXER_REVISION;
10582
10698
  const expectedTransparency = spec.quality.minTransparency ?? null;
10583
10699
  const expectedFrameSet = spec.generator === "frames" ? { fps: spec.quality.fps ?? 12, count: source.items.length, roles: source.items.map((item) => item.role) } : null;
10584
- const contractChanged = JSON.stringify(check.sources.map((item) => path10.resolve(item))) !== JSON.stringify(sourcePaths.map((item) => path10.resolve(item))) || JSON.stringify(check.outputs.map((item) => path10.resolve(item))) !== JSON.stringify(expectedOutputs.map((item) => path10.resolve(item))) || JSON.stringify(check.options.palette.colors) !== JSON.stringify(expectedPalette) || check.options.nativeGrid.revision !== expectedRevision || check.options.audit.thresholds.minGridConfidence !== spec.quality.minGridConfidence || check.options.audit.thresholds.minTransparency !== expectedTransparency || (expectedFrameSet ? !check.options.frameSet || check.options.frameSet.fps !== expectedFrameSet.fps || check.options.frameSet.count !== expectedFrameSet.count || JSON.stringify(check.options.frameSet.roles) !== JSON.stringify(expectedFrameSet.roles) : check.options.frameSet !== void 0);
10700
+ const contractChanged = JSON.stringify(check.sources.map((item) => path11.resolve(item))) !== JSON.stringify(sourcePaths.map((item) => path11.resolve(item))) || JSON.stringify(check.outputs.map((item) => path11.resolve(item))) !== JSON.stringify(expectedOutputs.map((item) => path11.resolve(item))) || JSON.stringify(check.options.palette.colors) !== JSON.stringify(expectedPalette) || check.options.nativeGrid.revision !== expectedRevision || check.options.audit.thresholds.minGridConfidence !== spec.quality.minGridConfidence || check.options.audit.thresholds.minTransparency !== expectedTransparency || (expectedFrameSet ? !check.options.frameSet || check.options.frameSet.fps !== expectedFrameSet.fps || check.options.frameSet.count !== expectedFrameSet.count || JSON.stringify(check.options.frameSet.roles) !== JSON.stringify(expectedFrameSet.roles) : check.options.frameSet !== void 0);
10585
10701
  if (contractChanged) {
10586
10702
  return {
10587
10703
  key,
@@ -10745,7 +10861,7 @@ async function requireApprovedQualitySources(specs, lock, assetIds, outputRoles)
10745
10861
 
10746
10862
  // src/pipeline/revision.ts
10747
10863
  import { existsSync as existsSync7 } from "fs";
10748
- import path11 from "path";
10864
+ import path12 from "path";
10749
10865
  async function inspectRevisionReadiness(spec, lock) {
10750
10866
  if (spec.mirror) return inspectMirror(spec, lock, /* @__PURE__ */ new Set());
10751
10867
  if (spec.character?.kind === "outfit") return inspectOutfit(spec, lock, /* @__PURE__ */ new Set());
@@ -10888,8 +11004,8 @@ async function inspectSourceMembers(spec, lock) {
10888
11004
  if (parent.source) return { ready: true, reason: "declared source set is present" };
10889
11005
  const entry = lock.entries[lockKey(parent.styleId, parent.assetId)];
10890
11006
  if (!entry) return { ready: false, reason: "revision source is not in the lockfile" };
10891
- const recorded = entry.outputs.map((_, index) => path11.resolve(currentEntryOutputPath(entry, parent, index))).sort();
10892
- const found = members.map((member) => path11.resolve(member.file)).sort();
11007
+ const recorded = entry.outputs.map((_, index) => path12.resolve(currentEntryOutputPath(entry, parent, index))).sort();
11008
+ const found = members.map((member) => path12.resolve(member.file)).sort();
10893
11009
  if (recorded.length !== found.length || recorded.some((file, index) => file !== found[index])) {
10894
11010
  return {
10895
11011
  ready: false,
@@ -10938,7 +11054,7 @@ async function inspectParent(spec, lock, seen) {
10938
11054
  };
10939
11055
  }
10940
11056
  if (spec.source) {
10941
- const source = path11.resolve(spec.root, spec.source);
11057
+ const source = path12.resolve(spec.root, spec.source);
10942
11058
  return existsSync7(source) ? { ready: true, reason: "declared source is present" } : { ready: false, reason: `declared source is missing: ${spec.source}` };
10943
11059
  }
10944
11060
  const entry = lock.entries[key];
@@ -11017,7 +11133,7 @@ async function buildPlan(specs, lock, opts = {}) {
11017
11133
  state = "missing";
11018
11134
  reason = "--force";
11019
11135
  } else if (!entry && spec.source && spec.generator !== "frames") {
11020
- if (existsSync8(path12.resolve(spec.root, spec.source))) {
11136
+ if (existsSync8(path13.resolve(spec.root, spec.source))) {
11021
11137
  state = "ok";
11022
11138
  reason = `placed from ${spec.source}; not generated`;
11023
11139
  } else {
@@ -11159,13 +11275,13 @@ function summarize(plan) {
11159
11275
 
11160
11276
  // src/project.ts
11161
11277
  async function openProject(manifestPath = "pixelkiln.manifest.json", opts = {}) {
11162
- const absolute = path13.resolve(manifestPath);
11163
- const root = path13.dirname(absolute);
11278
+ const absolute = path14.resolve(manifestPath);
11279
+ const root = path14.dirname(absolute);
11164
11280
  if (opts.env !== false) {
11165
11281
  loadEnvFiles(root);
11166
- if (path13.resolve(process.cwd()) !== root) loadEnvFiles(process.cwd());
11282
+ if (path14.resolve(process.cwd()) !== root) loadEnvFiles(process.cwd());
11167
11283
  }
11168
- const lockPath = path13.resolve(opts.lockPath ?? path13.join(root, "pixelkiln.lock.json"));
11284
+ const lockPath = path14.resolve(opts.lockPath ?? path14.join(root, "pixelkiln.lock.json"));
11169
11285
  const loaded = await loadManifest(absolute);
11170
11286
  const specs = await resolveSpecs(loaded, { styles: opts.styles ?? [], assets: opts.assets ?? [] });
11171
11287
  const lock = await loadLock(lockPath);
@@ -11232,7 +11348,7 @@ function shouldPersistSourceUrl(value) {
11232
11348
  }
11233
11349
 
11234
11350
  // src/recipes.ts
11235
- import path14 from "path";
11351
+ import path15 from "path";
11236
11352
  import { existsSync as existsSync9 } from "fs";
11237
11353
  import { readdir, readFile as readFile7 } from "fs/promises";
11238
11354
  import { fileURLToPath } from "url";
@@ -11243,7 +11359,7 @@ var VERSION_RE = /^\d+\.\d+\.\d+$/;
11243
11359
  var RECIPE_FILE = "pixelkiln.recipe.json";
11244
11360
  var RECIPE_DIR_TOKEN = "{{recipeDir}}";
11245
11361
  var PortablePathSchema = z4.string().min(1).refine((value) => {
11246
- if (value.includes("\\") || path14.posix.isAbsolute(value)) return false;
11362
+ if (value.includes("\\") || path15.posix.isAbsolute(value)) return false;
11247
11363
  const parts = value.split("/");
11248
11364
  return !parts.includes("") && !parts.includes(".") && !parts.includes("..");
11249
11365
  }, "expected a portable relative path without '.', '..', or backslashes");
@@ -11419,7 +11535,7 @@ function message3(error) {
11419
11535
  return error instanceof Error ? error.message : String(error);
11420
11536
  }
11421
11537
  async function readRecipeFile(recipePath, bundled) {
11422
- const absolute = path14.resolve(recipePath);
11538
+ const absolute = path15.resolve(recipePath);
11423
11539
  let raw;
11424
11540
  try {
11425
11541
  raw = JSON.parse(await readFile7(absolute, "utf8"));
@@ -11433,13 +11549,13 @@ async function readRecipeFile(recipePath, bundled) {
11433
11549
  ` + parsed.error.issues.map((issue) => ` ${issue.path.join(".") || "$"}: ${issue.message}`).join("\n")
11434
11550
  );
11435
11551
  }
11436
- return { recipe: parsed.data, authored: raw, path: absolute, dir: path14.dirname(absolute), bundled };
11552
+ return { recipe: parsed.data, authored: raw, path: absolute, dir: path15.dirname(absolute), bundled };
11437
11553
  }
11438
11554
  async function walkRecipeFiles(root) {
11439
11555
  if (!existsSync9(root)) return [];
11440
11556
  const out = [];
11441
11557
  for (const entry of await readdir(root, { withFileTypes: true })) {
11442
- const resolved = path14.join(root, entry.name);
11558
+ const resolved = path15.join(root, entry.name);
11443
11559
  if (entry.isDirectory()) out.push(...await walkRecipeFiles(resolved));
11444
11560
  else if (entry.isFile() && entry.name === RECIPE_FILE) out.push(resolved);
11445
11561
  }
@@ -11450,7 +11566,7 @@ function bundledRecipeRoot() {
11450
11566
  }
11451
11567
  async function listBundledRecipes(root = bundledRecipeRoot()) {
11452
11568
  const recipes = await Promise.all(
11453
- (await walkRecipeFiles(path14.resolve(root))).map((file) => readRecipeFile(file, true))
11569
+ (await walkRecipeFiles(path15.resolve(root))).map((file) => readRecipeFile(file, true))
11454
11570
  );
11455
11571
  return recipes.sort(
11456
11572
  (a, b) => a.recipe.id.localeCompare(b.recipe.id) || compareVersions(b.recipe.version, a.recipe.version)
@@ -11473,9 +11589,9 @@ function parseSelector(selector) {
11473
11589
  return { id: match[1], version: match[2] };
11474
11590
  }
11475
11591
  async function resolveRecipe(target, bundledRoot = bundledRecipeRoot()) {
11476
- const local = path14.resolve(target);
11592
+ const local = path15.resolve(target);
11477
11593
  if (existsSync9(local)) {
11478
- const recipePath = path14.basename(local) === RECIPE_FILE ? local : path14.join(local, RECIPE_FILE);
11594
+ const recipePath = path15.basename(local) === RECIPE_FILE ? local : path15.join(local, RECIPE_FILE);
11479
11595
  return readRecipeFile(recipePath, false);
11480
11596
  }
11481
11597
  if (target.startsWith(".") || target.startsWith("/") || target.includes("\\")) {
@@ -11489,7 +11605,7 @@ async function resolveRecipe(target, bundledRoot = bundledRecipeRoot()) {
11489
11605
  return matches[0];
11490
11606
  }
11491
11607
  async function verifyFile(root, file) {
11492
- const absolute = path14.resolve(root, ...file.path.split("/"));
11608
+ const absolute = path15.resolve(root, ...file.path.split("/"));
11493
11609
  if (!existsSync9(absolute)) {
11494
11610
  return { path: file.path, expectedSha256: file.sha256, actualSha256: null, status: "missing" };
11495
11611
  }
@@ -11505,7 +11621,7 @@ async function verifyRecipe(target, options = {}) {
11505
11621
  const loaded = await resolveRecipe(target, options.bundledRoot);
11506
11622
  const actualIntegrity = recipeDigest(loaded.authored);
11507
11623
  const files = await Promise.all(loaded.recipe.files.map((file) => verifyFile(loaded.dir, file)));
11508
- const modelRoot = options.modelRoot ? path14.resolve(options.modelRoot) : null;
11624
+ const modelRoot = options.modelRoot ? path15.resolve(options.modelRoot) : null;
11509
11625
  const models = modelRoot ? await Promise.all(loaded.recipe.models.map(async (model) => ({
11510
11626
  ...await verifyFile(modelRoot, model),
11511
11627
  source: model.source,
@@ -11539,7 +11655,7 @@ async function verifyRecipe(target, options = {}) {
11539
11655
  };
11540
11656
  }
11541
11657
  function renderStyle(style, destination, cwd) {
11542
- const portableDir = path14.relative(cwd, destination).split(path14.sep).join("/") || ".";
11658
+ const portableDir = path15.relative(cwd, destination).split(path15.sep).join("/") || ".";
11543
11659
  const replace = (value) => {
11544
11660
  if (typeof value === "string") return value.replaceAll(RECIPE_DIR_TOKEN, portableDir);
11545
11661
  if (Array.isArray(value)) return value.map(replace);
@@ -11551,19 +11667,19 @@ function renderStyle(style, destination, cwd) {
11551
11667
  return replace(style);
11552
11668
  }
11553
11669
  async function installRecipe(target, options = {}) {
11554
- const cwd = path14.resolve(options.cwd ?? process.cwd());
11670
+ const cwd = path15.resolve(options.cwd ?? process.cwd());
11555
11671
  const loaded = await resolveRecipe(target, options.bundledRoot);
11556
11672
  const verification = await verifyRecipe(loaded.path);
11557
11673
  if (!verification.ok) {
11558
11674
  throw new Error(`Recipe ${loaded.recipe.id}@${loaded.recipe.version} failed integrity verification.`);
11559
11675
  }
11560
- const destination = options.out ? path14.resolve(cwd, options.out) : path14.join(cwd, "pixelkiln-recipes", ...loaded.recipe.id.split("/"), loaded.recipe.version);
11676
+ const destination = options.out ? path15.resolve(cwd, options.out) : path15.join(cwd, "pixelkiln-recipes", ...loaded.recipe.id.split("/"), loaded.recipe.version);
11561
11677
  const sources = [
11562
11678
  { path: RECIPE_FILE, source: loaded.path },
11563
- ...loaded.recipe.files.map((file) => ({ path: file.path, source: path14.join(loaded.dir, ...file.path.split("/")) }))
11679
+ ...loaded.recipe.files.map((file) => ({ path: file.path, source: path15.join(loaded.dir, ...file.path.split("/")) }))
11564
11680
  ];
11565
11681
  const files = await Promise.all(sources.map(async (file) => ({
11566
- path: path14.join(destination, ...file.path.split("/")),
11682
+ path: path15.join(destination, ...file.path.split("/")),
11567
11683
  data: await readFile7(file.source)
11568
11684
  })));
11569
11685
  if (!options.force) {
@@ -11595,7 +11711,7 @@ async function installRecipe(target, options = {}) {
11595
11711
  // src/pipeline/audit.ts
11596
11712
  import { readFile as readFile8 } from "fs/promises";
11597
11713
  import { existsSync as existsSync10 } from "fs";
11598
- import path15 from "path";
11714
+ import path16 from "path";
11599
11715
  function paletteDistance(asset, reference) {
11600
11716
  if (!asset.length || !reference.length) return 0;
11601
11717
  let total = 0;
@@ -11660,7 +11776,7 @@ async function auditStyle(loaded, specs, styleId, lock) {
11660
11776
  let referenceFromStyleImages = false;
11661
11777
  const refPalettes = [];
11662
11778
  for (const img of style.styleImages) {
11663
- const abs = path15.resolve(loaded.root, img.path);
11779
+ const abs = path16.resolve(loaded.root, img.path);
11664
11780
  if (!existsSync10(abs)) continue;
11665
11781
  try {
11666
11782
  refPalettes.push(extractPalette(decodePng(await readFile8(abs)), 12));
@@ -11741,16 +11857,16 @@ function hex(c) {
11741
11857
  // src/pixellab-utils.ts
11742
11858
  import { existsSync as existsSync11 } from "fs";
11743
11859
  import { mkdir as mkdir4, readFile as readFile9, writeFile as writeFile4 } from "fs/promises";
11744
- import path16 from "path";
11860
+ import path17 from "path";
11745
11861
  var UNZOOM_MIN_SIDE = 256;
11746
11862
  var UNZOOM_MAX_AREA = 2048 * 2048;
11747
11863
  function defaultUnzoomOut(input) {
11748
- const ext = path16.extname(input);
11864
+ const ext = path17.extname(input);
11749
11865
  return `${ext ? input.slice(0, -ext.length) : input}.unzoomed.png`;
11750
11866
  }
11751
11867
  async function unzoomFile(client2, input, opts = {}) {
11752
- const out = path16.resolve(opts.out ?? defaultUnzoomOut(input));
11753
- if (path16.resolve(input) === out) throw new Error("unzoom will not overwrite its own input; pass a different --out");
11868
+ const out = path17.resolve(opts.out ?? defaultUnzoomOut(input));
11869
+ if (path17.resolve(input) === out) throw new Error("unzoom will not overwrite its own input; pass a different --out");
11754
11870
  if (existsSync11(out) && !opts.force) throw new Error(`${out} already exists; pass --force to replace it`);
11755
11871
  const bytes = await readFile9(input);
11756
11872
  const metadata = imageMetadata(bytes);
@@ -11765,7 +11881,7 @@ async function unzoomFile(client2, input, opts = {}) {
11765
11881
  throw new Error(`${input} is ${width}x${height}; PixelLab's unzoom takes at most 2048x2048 worth of pixels`);
11766
11882
  }
11767
11883
  const res = await client2.unzoom({ image: { base64: bytes.toString("base64"), format }, quantize: opts.quantize });
11768
- await mkdir4(path16.dirname(out), { recursive: true });
11884
+ await mkdir4(path17.dirname(out), { recursive: true });
11769
11885
  await writeFile4(out, res.png);
11770
11886
  return { out, original: res.originalSize, unzoomed: res.unzoomedSize, zoomFactor: res.zoomFactor, usage: res.usage };
11771
11887
  }
@@ -11773,7 +11889,7 @@ var FONT_WEIGHTS = ["Bold", "Regular"];
11773
11889
  var FONT_GLYPH_SIZES = [8, 16, 32, 64];
11774
11890
  var FONT_COST_GENERATIONS = 25;
11775
11891
  function fontOutputPaths(out) {
11776
- const base = path16.resolve(out.replace(/\.(ttf|png)$/i, ""));
11892
+ const base = path17.resolve(out.replace(/\.(ttf|png)$/i, ""));
11777
11893
  return { ttf: `${base}.ttf`, atlas: `${base}.png` };
11778
11894
  }
11779
11895
  async function generateFont(client2, opts) {
@@ -11807,7 +11923,7 @@ async function generateFont(client2, opts) {
11807
11923
  client2.download(job.download_ttf_url),
11808
11924
  client2.download(job.download_atlas_url)
11809
11925
  ]);
11810
- await mkdir4(path16.dirname(ttf), { recursive: true });
11926
+ await mkdir4(path17.dirname(ttf), { recursive: true });
11811
11927
  await writeFile4(ttf, ttfBytes);
11812
11928
  await writeFile4(atlas, atlasBytes);
11813
11929
  return { jobId, ttf, atlas, glyphPx: job.glyph_px ?? null, usage: job.usage };
@@ -11822,7 +11938,7 @@ async function generateFont(client2, opts) {
11822
11938
  }
11823
11939
 
11824
11940
  // src/pipeline/quality-regression.ts
11825
- import path17 from "path";
11941
+ import path18 from "path";
11826
11942
  import { existsSync as existsSync12 } from "fs";
11827
11943
  import { readFile as readFile10 } from "fs/promises";
11828
11944
  import { z as z5 } from "zod";
@@ -11830,7 +11946,7 @@ var SHA256_RE2 = /^[0-9a-f]{64}$/;
11830
11946
  var HEX_RE = /^#[0-9a-f]{6}$/;
11831
11947
  var MAX_REDMEAN_DISTANCE = 765;
11832
11948
  var QualityPathSchema = z5.string().min(1).refine(
11833
- (value) => !path17.posix.isAbsolute(value) && !value.includes("\\"),
11949
+ (value) => !path18.posix.isAbsolute(value) && !value.includes("\\"),
11834
11950
  "expected a portable relative path"
11835
11951
  );
11836
11952
  var DEFAULT_QUALITY_TOLERANCES = {
@@ -11977,7 +12093,7 @@ function measureDecodedImageQuality(png, digest2) {
11977
12093
  };
11978
12094
  }
11979
12095
  async function measureImageQuality(file) {
11980
- const absolute = path17.resolve(file);
12096
+ const absolute = path18.resolve(file);
11981
12097
  let bytes;
11982
12098
  try {
11983
12099
  bytes = await readFile10(absolute);
@@ -11999,11 +12115,11 @@ function resolveQualityInputs(raw, inputsFilePath) {
11999
12115
  }
12000
12116
  const ids = parsed.data.map((entry) => entry.id);
12001
12117
  if (new Set(ids).size !== ids.length) throw new Error("--inputs contains a duplicate quality case id.");
12002
- const root = path17.dirname(path17.resolve(inputsFilePath));
12118
+ const root = path18.dirname(path18.resolve(inputsFilePath));
12003
12119
  return parsed.data.map((entry) => ({
12004
12120
  id: entry.id,
12005
- file: path17.resolve(root, entry.path),
12006
- ...entry.record ? { record: path17.resolve(root, entry.record) } : {},
12121
+ file: path18.resolve(root, entry.path),
12122
+ ...entry.record ? { record: path18.resolve(root, entry.record) } : {},
12007
12123
  tolerances: QualityToleranceSchema.parse({
12008
12124
  ...DEFAULT_QUALITY_TOLERANCES,
12009
12125
  ...entry.tolerances
@@ -12011,7 +12127,7 @@ function resolveQualityInputs(raw, inputsFilePath) {
12011
12127
  }));
12012
12128
  }
12013
12129
  function portableRelative2(from, to) {
12014
- return path17.relative(from, path17.resolve(to)).split(path17.sep).join("/") || ".";
12130
+ return path18.relative(from, path18.resolve(to)).split(path18.sep).join("/") || ".";
12015
12131
  }
12016
12132
  async function verifyRecordForImage(recordPath, imagePath) {
12017
12133
  const verification = await verifyArtifactBundle(recordPath);
@@ -12026,20 +12142,20 @@ async function verifyRecordForImage(recordPath, imagePath) {
12026
12142
  if (record.kind !== "refine" || options === null || typeof options !== "object" || options.schema !== "pixelkiln-quality") {
12027
12143
  throw new Error(`Quality record ${recordPath} is not a PixelKiln refinement record.`);
12028
12144
  }
12029
- const outputs = record.outputs.map((output) => path17.resolve(path17.dirname(recordPath), output.path));
12030
- if (!outputs.includes(path17.resolve(imagePath))) {
12145
+ const outputs = record.outputs.map((output) => path18.resolve(path18.dirname(recordPath), output.path));
12146
+ if (!outputs.includes(path18.resolve(imagePath))) {
12031
12147
  throw new Error(`Quality record ${recordPath} does not own ${imagePath}.`);
12032
12148
  }
12033
12149
  }
12034
12150
  async function snapshotQualityBaseline(inputs, baselinePath, options = {}) {
12035
12151
  if (!inputs.length) throw new Error("Cannot snapshot an empty quality baseline.");
12036
- const absolute = path17.resolve(baselinePath);
12152
+ const absolute = path18.resolve(baselinePath);
12037
12153
  for (const input of inputs) {
12038
- if (path17.resolve(input.file) === absolute || input.record && path17.resolve(input.record) === absolute) {
12154
+ if (path18.resolve(input.file) === absolute || input.record && path18.resolve(input.record) === absolute) {
12039
12155
  throw new Error(`Quality baseline output would overwrite an input: ${absolute}.`);
12040
12156
  }
12041
12157
  }
12042
- const root = path17.dirname(absolute);
12158
+ const root = path18.dirname(absolute);
12043
12159
  const cases = [];
12044
12160
  for (const input of [...inputs].sort((a, b) => a.id.localeCompare(b.id))) {
12045
12161
  const expected = await measureImageQuality(input.file);
@@ -12079,7 +12195,7 @@ async function snapshotQualityBaseline(inputs, baselinePath, options = {}) {
12079
12195
  return { baseline, path: absolute, changed: result.changed.length > 0 };
12080
12196
  }
12081
12197
  async function readQualityBaseline(baselinePath) {
12082
- const absolute = path17.resolve(baselinePath);
12198
+ const absolute = path18.resolve(baselinePath);
12083
12199
  let raw;
12084
12200
  try {
12085
12201
  raw = JSON.parse(await readFile10(absolute, "utf8"));
@@ -12152,12 +12268,12 @@ function regressionViolations(expected, actual, tolerances) {
12152
12268
  return { violations, warnings };
12153
12269
  }
12154
12270
  async function checkQualityBaseline(baselinePath) {
12155
- const absolute = path17.resolve(baselinePath);
12271
+ const absolute = path18.resolve(baselinePath);
12156
12272
  const baseline = await readQualityBaseline(absolute);
12157
- const root = path17.dirname(absolute);
12273
+ const root = path18.dirname(absolute);
12158
12274
  const cases = [];
12159
12275
  for (const entry of baseline.cases) {
12160
- const file = path17.resolve(root, entry.file);
12276
+ const file = path18.resolve(root, entry.file);
12161
12277
  let actual = null;
12162
12278
  const violations = [];
12163
12279
  const warnings = [];
@@ -12170,7 +12286,7 @@ async function checkQualityBaseline(baselinePath) {
12170
12286
  violations.push(message4(error));
12171
12287
  }
12172
12288
  if (entry.record) {
12173
- const recordPath = path17.resolve(root, entry.record.path);
12289
+ const recordPath = path18.resolve(root, entry.record.path);
12174
12290
  try {
12175
12291
  const recordHash = await sha256File(recordPath);
12176
12292
  if (recordHash !== entry.record.sha256) violations.push("quality record hash changed");
@@ -12208,7 +12324,7 @@ async function checkQualityBaseline(baselinePath) {
12208
12324
  // src/pipeline/cache-health.ts
12209
12325
  import { existsSync as existsSync16 } from "fs";
12210
12326
  import { readFile as readFile13, readdir as readdir2, rm as rm6 } from "fs/promises";
12211
- import path20 from "path";
12327
+ import path21 from "path";
12212
12328
 
12213
12329
  // src/cache.ts
12214
12330
  import { mkdir as mkdir5, readFile as readFile11, rename as rename4, rm as rm4, writeFile as writeFile5 } from "fs/promises";
@@ -12227,10 +12343,10 @@ function isSha256Hash(value) {
12227
12343
  function parseCache(value) {
12228
12344
  return HashCacheSchema.parse(value);
12229
12345
  }
12230
- async function loadCache(path37) {
12231
- if (!existsSync13(path37)) return { version: 1, hashes: {} };
12346
+ async function loadCache(path38) {
12347
+ if (!existsSync13(path38)) return { version: 1, hashes: {} };
12232
12348
  try {
12233
- const parsed = HashCacheSchema.safeParse(JSON.parse(await readFile11(path37, "utf8")));
12349
+ const parsed = HashCacheSchema.safeParse(JSON.parse(await readFile11(path38, "utf8")));
12234
12350
  if (!parsed.success) return { version: 1, hashes: {} };
12235
12351
  return {
12236
12352
  version: 1,
@@ -12242,18 +12358,18 @@ async function loadCache(path37) {
12242
12358
  return { version: 1, hashes: {} };
12243
12359
  }
12244
12360
  }
12245
- async function saveCache(path37, cache) {
12361
+ async function saveCache(path38, cache) {
12246
12362
  const sorted = {};
12247
12363
  for (const key of Object.keys(cache.hashes).sort()) {
12248
12364
  const hash = cache.hashes[key];
12249
12365
  if (!isSha256Hash(hash)) throw new Error(`Refusing to cache invalid SHA-256 for ${key}`);
12250
12366
  sorted[key] = hash;
12251
12367
  }
12252
- await mkdir5(pathModule.dirname(pathModule.resolve(path37)), { recursive: true });
12253
- const tmp = `${path37}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
12368
+ await mkdir5(pathModule.dirname(pathModule.resolve(path38)), { recursive: true });
12369
+ const tmp = `${path38}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
12254
12370
  try {
12255
12371
  await writeFile5(tmp, JSON.stringify({ version: 1, hashes: sorted }, null, 2) + "\n");
12256
- await rename4(tmp, path37);
12372
+ await rename4(tmp, path38);
12257
12373
  } finally {
12258
12374
  await rm4(tmp, { force: true });
12259
12375
  }
@@ -12278,19 +12394,19 @@ function cachePathFor(lockPath) {
12278
12394
 
12279
12395
  // src/pipeline/history.ts
12280
12396
  import { existsSync as existsSync15 } from "fs";
12281
- import path19 from "path";
12397
+ import path20 from "path";
12282
12398
 
12283
12399
  // src/pipeline/fetch.ts
12284
- import { existsSync as existsSync14, readFileSync as readFileSync3 } from "fs";
12400
+ import { existsSync as existsSync14, readFileSync as readFileSync4 } from "fs";
12285
12401
  import { mkdir as mkdir6, readFile as readFile12, rename as rename5, rm as rm5, writeFile as writeFile6 } from "fs/promises";
12286
- import path18 from "path";
12402
+ import path19 from "path";
12287
12403
  async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
12288
12404
  const log = opts.onProgress ?? (() => {
12289
12405
  });
12290
12406
  const result = { downloaded: 0, skipped: 0, failed: 0, ...opts.refresh ? { unchanged: 0 } : {} };
12291
12407
  const specByKey = new Map(specs.map((s) => [lockKey(s.styleId, s.assetId), s]));
12292
12408
  normalizeLockOutputPaths(lock, specs);
12293
- const cacheDir = opts.cacheDir === false ? null : path18.resolve(opts.cacheDir ?? path18.join(path18.dirname(lockPath), ".pixelkiln", "cache"));
12409
+ const cacheDir = opts.cacheDir === false ? null : path19.resolve(opts.cacheDir ?? path19.join(path19.dirname(lockPath), ".pixelkiln", "cache"));
12294
12410
  const pending = Object.entries(lock.entries).filter(([key, e]) => {
12295
12411
  if (e.provider !== provider.id || !specByKey.has(key)) return false;
12296
12412
  if (specByKey.get(key)?.mirror) return false;
@@ -12301,7 +12417,7 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
12301
12417
  if (spec && e.specHash === spec.specHash && e.outputs.length && !postprocessCurrent(e, spec)) {
12302
12418
  return Boolean(opts.force) || e.outputs.every((output) => {
12303
12419
  const file = resolveOutputPath(output.path, spec.root);
12304
- return existsSync14(file) && sha256(readFileSync3(file)) === output.sha256;
12420
+ return existsSync14(file) && sha256(readFileSync4(file)) === output.sha256;
12305
12421
  });
12306
12422
  }
12307
12423
  if (!opts.repair) return false;
@@ -12309,7 +12425,7 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
12309
12425
  return e.outputs.some((output) => {
12310
12426
  const file = resolveOutputPath(output.path, spec.root);
12311
12427
  if (!existsSync14(file)) return true;
12312
- const current = sha256(readFileSync3(file));
12428
+ const current = sha256(readFileSync4(file));
12313
12429
  if (current === output.sha256) return false;
12314
12430
  return Boolean(opts.force) || (e.supersededOutputs ?? []).some((old) => old.sha256 === current);
12315
12431
  });
@@ -12392,7 +12508,7 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
12392
12508
  if (!source.url) throw new Error(`no durable source URL to refresh ${source.role ?? "asset"} from`);
12393
12509
  fresh = await provider.download(source.url);
12394
12510
  if (recorded && sha256(fresh) === recordedRaw && !reapply) {
12395
- log(` current ${path18.relative(process.cwd(), target)}`);
12511
+ log(` current ${path19.relative(process.cwd(), target)}`);
12396
12512
  outputs.push({ ...recorded, path: portableOutputPath(target, spec.root) });
12397
12513
  continue;
12398
12514
  }
@@ -12416,9 +12532,9 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
12416
12532
  }
12417
12533
  if (reapply && !fresh) {
12418
12534
  if (!recorded.raw) raw = await readFile12(target);
12419
- log(` reapply ${path18.relative(process.cwd(), target)}`);
12535
+ log(` reapply ${path19.relative(process.cwd(), target)}`);
12420
12536
  } else {
12421
- log(` changed ${path18.relative(process.cwd(), target)} (upstream)`);
12537
+ log(` changed ${path19.relative(process.cwd(), target)} (upstream)`);
12422
12538
  }
12423
12539
  } else {
12424
12540
  const superseded = (entry.supersededOutputs ?? []).find(
@@ -12446,7 +12562,7 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
12446
12562
  }
12447
12563
  }
12448
12564
  log(
12449
- ` replace ${path18.relative(process.cwd(), target)}` + (opts.force ? " (--force)" : " (previous tracked generation)")
12565
+ ` replace ${path19.relative(process.cwd(), target)}` + (opts.force ? " (--force)" : " (previous tracked generation)")
12450
12566
  );
12451
12567
  }
12452
12568
  }
@@ -12455,7 +12571,7 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
12455
12571
  } else if (!raw && recorded && cacheDir) {
12456
12572
  if (!reapply) final = await readCachedMedia(cacheDir, recorded.sha256, expectedMediaType);
12457
12573
  if (!final && recordedRaw) raw = await readCachedMedia(cacheDir, recordedRaw, expectedMediaType);
12458
- if (final || raw) log(` cached ${path18.relative(process.cwd(), target)}`);
12574
+ if (final || raw) log(` cached ${path19.relative(process.cwd(), target)}`);
12459
12575
  }
12460
12576
  if (!final && !raw) {
12461
12577
  if (!source.url) {
@@ -12482,11 +12598,11 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
12482
12598
  buf = processed.bytes;
12483
12599
  if (processed.changed) {
12484
12600
  rawHash = sha256(raw);
12485
- log(` snapped ${path18.relative(process.cwd(), target)} to ${wanted.palette.colors.length} colours`);
12601
+ log(` snapped ${path19.relative(process.cwd(), target)} to ${wanted.palette.colors.length} colours`);
12486
12602
  }
12487
12603
  }
12488
12604
  if (cacheDir) await cacheMedia(cacheDir, buf, mediaType);
12489
- await mkdir6(path18.dirname(target), { recursive: true });
12605
+ await mkdir6(path19.dirname(target), { recursive: true });
12490
12606
  const tmp = `${target}.pixelkiln.tmp`;
12491
12607
  await writeFile6(tmp, buf);
12492
12608
  outputs.push({
@@ -12500,7 +12616,7 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
12500
12616
  await saveLock(lockPath, lock);
12501
12617
  await rename5(tmp, target);
12502
12618
  wrote = true;
12503
- log(` wrote ${path18.relative(process.cwd(), target)}`);
12619
+ log(` wrote ${path19.relative(process.cwd(), target)}`);
12504
12620
  }
12505
12621
  if (opts.refresh && !wrote) {
12506
12622
  if (rotated) {
@@ -12544,7 +12660,7 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
12544
12660
  return result;
12545
12661
  }
12546
12662
  async function readCachedMedia(cacheDir, hash, mediaType) {
12547
- const file = path18.join(cacheDir, cacheFileName(hash, mediaType));
12663
+ const file = path19.join(cacheDir, cacheFileName(hash, mediaType));
12548
12664
  if (!existsSync14(file)) return null;
12549
12665
  try {
12550
12666
  const buf = await readFile12(file);
@@ -12558,7 +12674,7 @@ async function readCachedMedia(cacheDir, hash, mediaType) {
12558
12674
  async function cacheMedia(cacheDir, buf, mediaType, knownHash) {
12559
12675
  const hash = knownHash ?? sha256(buf);
12560
12676
  validateMedia(buf, mediaType);
12561
- const file = path18.join(cacheDir, cacheFileName(hash, mediaType));
12677
+ const file = path19.join(cacheDir, cacheFileName(hash, mediaType));
12562
12678
  if (await readCachedMedia(cacheDir, hash, mediaType)) return;
12563
12679
  await mkdir6(cacheDir, { recursive: true });
12564
12680
  const tmp = `${file}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
@@ -12688,7 +12804,7 @@ async function revertGeneration(provider, spec, lock, lockPath, opts) {
12688
12804
  if (!existsSync15(file)) continue;
12689
12805
  if (await sha256File(file) !== entry.outputs[i].sha256 && !opts.force) {
12690
12806
  throw new OverwriteRefusedError(
12691
- `${path19.relative(process.cwd(), file)} was changed after download; keep it with pixelkiln edit, or pass --force to replace it`,
12807
+ `${path20.relative(process.cwd(), file)} was changed after download; keep it with pixelkiln edit, or pass --force to replace it`,
12692
12808
  [file]
12693
12809
  );
12694
12810
  }
@@ -12741,10 +12857,10 @@ async function revertGeneration(provider, spec, lock, lockPath, opts) {
12741
12857
  // src/pipeline/cache-health.ts
12742
12858
  async function inspectCaches(lock, lockPath, options = {}) {
12743
12859
  if (options.prune && !existsSync16(lockPath)) {
12744
- throw new Error(`Refusing to prune without an existing lockfile at ${path20.resolve(lockPath)}`);
12860
+ throw new Error(`Refusing to prune without an existing lockfile at ${path21.resolve(lockPath)}`);
12745
12861
  }
12746
- const contentDir = path20.resolve(path20.dirname(lockPath), ".pixelkiln", "cache");
12747
- const remotePath = path20.resolve(cachePathFor(lockPath));
12862
+ const contentDir = path21.resolve(path21.dirname(lockPath), ".pixelkiln", "cache");
12863
+ const remotePath = path21.resolve(cachePathFor(lockPath));
12748
12864
  const referenced = referencedHashes(lock);
12749
12865
  let content = await inspectContentCache(contentDir, referenced);
12750
12866
  let remoteHashes = await inspectRemoteHashCache(remotePath);
@@ -12756,7 +12872,7 @@ async function inspectCaches(lock, lockPath, options = {}) {
12756
12872
  ]);
12757
12873
  for (const name of names) {
12758
12874
  try {
12759
- await rm6(path20.join(contentDir, name), { force: true });
12875
+ await rm6(path21.join(contentDir, name), { force: true });
12760
12876
  removed.contentFiles++;
12761
12877
  } catch {
12762
12878
  }
@@ -12804,7 +12920,7 @@ async function inspectContentCache(contentDir, referenced) {
12804
12920
  continue;
12805
12921
  }
12806
12922
  report.files++;
12807
- const file = path20.join(contentDir, entry.name);
12923
+ const file = path21.join(contentDir, entry.name);
12808
12924
  const mediaType = mediaTypeFromExtension(entry.name);
12809
12925
  const expected = mediaType ? entry.name.slice(0, -4) : "";
12810
12926
  let bytes;
@@ -12871,20 +12987,20 @@ async function inspectRemoteHashCache(remotePath) {
12871
12987
  }
12872
12988
 
12873
12989
  // src/pipeline/pack.ts
12874
- import { readFileSync as readFileSync4 } from "fs";
12875
- import path21 from "path";
12990
+ import { readFileSync as readFileSync5 } from "fs";
12991
+ import path22 from "path";
12876
12992
  var DEFAULT_FRAME_SET_FPS = 12;
12877
12993
  function resolvePackInputs(raw, inputsFilePath) {
12878
12994
  if (!Array.isArray(raw) || !raw.length) {
12879
12995
  throw new Error("--inputs must be a non-empty JSON array of { id, path }");
12880
12996
  }
12881
- const baseDir = path21.dirname(path21.resolve(inputsFilePath));
12997
+ const baseDir = path22.dirname(path22.resolve(inputsFilePath));
12882
12998
  return raw.map((entry, i) => {
12883
12999
  const e = entry;
12884
13000
  if (typeof e.id !== "string" || typeof e.path !== "string") {
12885
13001
  throw new Error(`--inputs[${i}] needs string "id" and "path"`);
12886
13002
  }
12887
- return { id: e.id, path: path21.resolve(baseDir, e.path) };
13003
+ return { id: e.id, path: path22.resolve(baseDir, e.path) };
12888
13004
  });
12889
13005
  }
12890
13006
  function packSprites(inputs, options = {}) {
@@ -12905,7 +13021,7 @@ function packSprites(inputs, options = {}) {
12905
13021
  for (const input of ordered) {
12906
13022
  let sourceHash = null;
12907
13023
  try {
12908
- const bytes = readFileSync4(input.path);
13024
+ const bytes = readFileSync5(input.path);
12909
13025
  sourceHash = sha256(bytes);
12910
13026
  const png = decodePng(bytes);
12911
13027
  sprites.push({ id: input.id, width: png.width, height: png.height, pixels: png.pixels });
@@ -12968,7 +13084,7 @@ function packStyle(lock, styleId, manifestDir, options = {}) {
12968
13084
  }
12969
13085
  const inputs2 = Object.entries(options.sourceOverrides).map(([id, source]) => ({
12970
13086
  id,
12971
- path: path21.resolve(source)
13087
+ path: path22.resolve(source)
12972
13088
  }));
12973
13089
  if (!inputs2.length) throw new Error(`No approved quality outputs for style "${styleId}".`);
12974
13090
  const packed2 = packSprites(inputs2, options);
@@ -12992,7 +13108,7 @@ function packStyle(lock, styleId, manifestDir, options = {}) {
12992
13108
  let outputs = resolveEntryOutputs(entry, id, manifestDir);
12993
13109
  const source = sources[id];
12994
13110
  if (source && outputs.length <= 1) {
12995
- inputs.push({ id, path: path21.resolve(manifestDir, source) });
13111
+ inputs.push({ id, path: path22.resolve(manifestDir, source) });
12996
13112
  continue;
12997
13113
  }
12998
13114
  if (source && sourceIsStem(source, entry, manifestDir)) {
@@ -13026,7 +13142,7 @@ function packStyle(lock, styleId, manifestDir, options = {}) {
13026
13142
  }
13027
13143
  }
13028
13144
  for (const [id, source] of Object.entries(sources)) {
13029
- if (!locked.has(id)) inputs.push({ id, path: path21.resolve(manifestDir, source) });
13145
+ if (!locked.has(id)) inputs.push({ id, path: path22.resolve(manifestDir, source) });
13030
13146
  }
13031
13147
  if (!inputs.length) {
13032
13148
  throw new Error(`No readable sprites for style "${styleId}"; ${noOutput.length} skipped.`);
@@ -13063,7 +13179,7 @@ function mountSprites(placements, options) {
13063
13179
  for (const p of placements) {
13064
13180
  let sourceHash = null;
13065
13181
  try {
13066
- const bytes = readFileSync4(p.path);
13182
+ const bytes = readFileSync5(p.path);
13067
13183
  sourceHash = sha256(bytes);
13068
13184
  const png = decodePng(bytes);
13069
13185
  if (png.width > cellWidth || png.height > cellHeight) {
@@ -13134,7 +13250,7 @@ function mountStyle(lock, styleId, manifestDir, mount, cells, sources = {}, outp
13134
13250
  const source = sources[id];
13135
13251
  const entry = lock.entries[`${styleId}/${id}`];
13136
13252
  if (source && !(entry && sourceIsStem(source, entry, manifestDir))) {
13137
- placements.push({ id, path: path21.resolve(manifestDir, source), cell });
13253
+ placements.push({ id, path: path22.resolve(manifestDir, source), cell });
13138
13254
  continue;
13139
13255
  }
13140
13256
  if (source && entry) {
@@ -13167,7 +13283,7 @@ function mountStyle(lock, styleId, manifestDir, mount, cells, sources = {}, outp
13167
13283
  `Nothing to mount for style "${styleId}": ${ids.length} asset(s) declare a cell, but none has generated output or a \`source\`. Run \`pixelkiln gen --style ${styleId}\`, or point each at a file.${detail}`
13168
13284
  );
13169
13285
  }
13170
- const basePng = mount.base ? readFileSync4(path21.resolve(manifestDir, mount.base)) : void 0;
13286
+ const basePng = mount.base ? readFileSync5(path22.resolve(manifestDir, mount.base)) : void 0;
13171
13287
  const mounted = mountSprites(placements, {
13172
13288
  cellWidth: mount.cellWidth,
13173
13289
  cellHeight: mount.cellHeight,
@@ -13177,7 +13293,7 @@ function mountStyle(lock, styleId, manifestDir, mount, cells, sources = {}, outp
13177
13293
  ...mounted,
13178
13294
  sources: mount.base ? [{
13179
13295
  id: "$base",
13180
- path: path21.resolve(manifestDir, mount.base),
13296
+ path: path22.resolve(manifestDir, mount.base),
13181
13297
  sha256: sha256(basePng),
13182
13298
  included: true
13183
13299
  }, ...mounted.sources] : mounted.sources,
@@ -13989,7 +14105,7 @@ async function poll(provider, lock, lockPath, opts = {}) {
13989
14105
  // src/pipeline/doctor.ts
13990
14106
  import { constants, existsSync as existsSync17 } from "fs";
13991
14107
  import { access, stat as stat2 } from "fs/promises";
13992
- import path22 from "path";
14108
+ import path23 from "path";
13993
14109
  async function doctor(loaded, specs, lock, lockPath, opts = {}) {
13994
14110
  const checks = [];
13995
14111
  const add = (id, level, message6) => checks.push({ id, level, message: message6 });
@@ -14000,8 +14116,8 @@ async function doctor(loaded, specs, lock, lockPath, opts = {}) {
14000
14116
  );
14001
14117
  add("lock", "ok", `${Object.keys(lock.entries).length} valid v2 lock entr(ies)`);
14002
14118
  const dirs = [...new Set(specs.flatMap((spec) => [
14003
- path22.dirname(spec.outFile),
14004
- ...spec.quality ? [path22.dirname(spec.quality.outFile)] : []
14119
+ path23.dirname(spec.outFile),
14120
+ ...spec.quality ? [path23.dirname(spec.quality.outFile)] : []
14005
14121
  ]))];
14006
14122
  const unwritable = [];
14007
14123
  for (const dir of dirs) {
@@ -14024,7 +14140,7 @@ async function doctor(loaded, specs, lock, lockPath, opts = {}) {
14024
14140
  for (let index = 0; index < entry.outputs.length; index++) {
14025
14141
  const output = entry.outputs[index];
14026
14142
  const spec = specByKey.get(key);
14027
- const absolute = spec ? currentEntryOutputPath(entry, spec, index) : resolveOutputPath(output.path, path22.dirname(lockPath));
14143
+ const absolute = spec ? currentEntryOutputPath(entry, spec, index) : resolveOutputPath(output.path, path23.dirname(lockPath));
14028
14144
  const owner = outputOwners.get(absolute);
14029
14145
  if (owner && owner !== key) duplicateOutputs.push(`${output.path} (${owner}, ${key})`);
14030
14146
  outputOwners.set(absolute, key);
@@ -14039,7 +14155,7 @@ async function doctor(loaded, specs, lock, lockPath, opts = {}) {
14039
14155
  } else {
14040
14156
  add("lock-outputs", "ok", `${outputOwners.size} recorded output path(s) are uniquely owned`);
14041
14157
  }
14042
- const cacheDir = path22.join(path22.dirname(lockPath), ".pixelkiln", "cache");
14158
+ const cacheDir = path23.join(path23.dirname(lockPath), ".pixelkiln", "cache");
14043
14159
  const stranded = [];
14044
14160
  const invalidState = [];
14045
14161
  const staleMapUrls = [];
@@ -14055,7 +14171,7 @@ async function doctor(loaded, specs, lock, lockPath, opts = {}) {
14055
14171
  const hasSource = Boolean(entry.sourceUrl || entry.sourceUrls?.length);
14056
14172
  let hasCache = false;
14057
14173
  for (const output of entry.outputs) {
14058
- const cached = path22.join(
14174
+ const cached = path23.join(
14059
14175
  cacheDir,
14060
14176
  cacheFileName(output.sha256, output.mediaType ?? MediaType.PNG)
14061
14177
  );
@@ -14155,7 +14271,7 @@ async function nearestExistingDirectory(start) {
14155
14271
  if ((await stat2(current)).isDirectory()) return current;
14156
14272
  } catch {
14157
14273
  }
14158
- const parent = path22.dirname(current);
14274
+ const parent = path23.dirname(current);
14159
14275
  if (parent === current) return current;
14160
14276
  current = parent;
14161
14277
  }
@@ -14164,7 +14280,7 @@ async function nearestExistingDirectory(start) {
14164
14280
  // src/pipeline/adopt.ts
14165
14281
  import { existsSync as existsSync18 } from "fs";
14166
14282
  import { readFile as readFile14, writeFile as writeFile7 } from "fs/promises";
14167
- import path23 from "path";
14283
+ import path24 from "path";
14168
14284
  async function adopt(provider, specs, lock, lockPath, opts = {}) {
14169
14285
  const log = opts.onProgress ?? (() => {
14170
14286
  });
@@ -14303,7 +14419,7 @@ import { readFile as readFile17 } from "fs/promises";
14303
14419
  // src/pipeline/init.ts
14304
14420
  import { readdir as readdir3, readFile as readFile15, writeFile as writeFile8 } from "fs/promises";
14305
14421
  import { existsSync as existsSync19 } from "fs";
14306
- import path24 from "path";
14422
+ import path25 from "path";
14307
14423
  function pngSize(buf) {
14308
14424
  if (buf.length < 24) return null;
14309
14425
  const isPng = buf.readUInt32BE(0) === 2303741511 && buf.readUInt32BE(4) === 218765834;
@@ -14317,7 +14433,7 @@ function slugify(value) {
14317
14433
  async function walk(dir, exclude) {
14318
14434
  const out = [];
14319
14435
  for (const entry of await readdir3(dir, { withFileTypes: true })) {
14320
- const full = path24.join(dir, entry.name);
14436
+ const full = path25.join(dir, entry.name);
14321
14437
  if (exclude.some((x) => full.includes(x))) continue;
14322
14438
  if (entry.isDirectory()) out.push(...await walk(full, exclude));
14323
14439
  else if (entry.name.toLowerCase().endsWith(".png")) out.push(full);
@@ -14336,9 +14452,9 @@ async function scanAssets(root, opts = {}) {
14336
14452
  skipped.push(`${file} (not a readable PNG)`);
14337
14453
  continue;
14338
14454
  }
14339
- const rel = path24.relative(root, file);
14340
- const category = path24.dirname(rel) === "." ? "" : path24.dirname(rel);
14341
- let id = slugify(path24.basename(rel));
14455
+ const rel = path25.relative(root, file);
14456
+ const category = path25.dirname(rel) === "." ? "" : path25.dirname(rel);
14457
+ let id = slugify(path25.basename(rel));
14342
14458
  if (seen.has(id)) {
14343
14459
  const n = seen.get(id) + 1;
14344
14460
  seen.set(id, n);
@@ -15061,15 +15177,15 @@ async function runPicker(provider, lock, lockPath, opts = {}) {
15061
15177
  }
15062
15178
 
15063
15179
  // src/gallery/snapshot.ts
15064
- import { createHash as createHash4 } from "crypto";
15180
+ import { createHash as createHash5 } from "crypto";
15065
15181
  import { existsSync as existsSync26 } from "fs";
15066
15182
  import { readFile as readFile24, stat as stat3 } from "fs/promises";
15067
- import path32 from "path";
15183
+ import path33 from "path";
15068
15184
 
15069
15185
  // src/workspace.ts
15070
15186
  import { mkdir as mkdir7, readFile as readFile18, rename as rename6, rm as rm7, writeFile as writeFile9 } from "fs/promises";
15071
15187
  import { existsSync as existsSync21 } from "fs";
15072
- import path25 from "path";
15188
+ import path26 from "path";
15073
15189
  import { z as z7 } from "zod";
15074
15190
  var WorkspaceProjectSchema = z7.object({
15075
15191
  id: z7.string().min(1),
@@ -15111,7 +15227,7 @@ async function saveWorkspace(workspacePath, ws) {
15111
15227
  version: 1,
15112
15228
  projects: [...ws.projects].sort((a, b) => a.id.localeCompare(b.id))
15113
15229
  };
15114
- await mkdir7(path25.dirname(path25.resolve(workspacePath)), { recursive: true });
15230
+ await mkdir7(path26.dirname(path26.resolve(workspacePath)), { recursive: true });
15115
15231
  const tmp = `${workspacePath}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
15116
15232
  try {
15117
15233
  await writeFile9(tmp, JSON.stringify(sorted, null, 2) + "\n");
@@ -15121,12 +15237,12 @@ async function saveWorkspace(workspacePath, ws) {
15121
15237
  }
15122
15238
  }
15123
15239
  function toPortablePath(dir, absolute) {
15124
- return path25.relative(dir, absolute).split(path25.sep).join("/");
15240
+ return path26.relative(dir, absolute).split(path26.sep).join("/");
15125
15241
  }
15126
15242
  function resolveProject(dir, project) {
15127
15243
  return {
15128
- manifestPath: path25.resolve(dir, project.manifest.split("/").join(path25.sep)),
15129
- lockPath: path25.resolve(dir, project.lock.split("/").join(path25.sep))
15244
+ manifestPath: path26.resolve(dir, project.manifest.split("/").join(path26.sep)),
15245
+ lockPath: path26.resolve(dir, project.lock.split("/").join(path26.sep))
15130
15246
  };
15131
15247
  }
15132
15248
  function validateWorkspace(ws, dir) {
@@ -15139,7 +15255,7 @@ function validateWorkspace(ws, dir) {
15139
15255
  const { manifestPath, lockPath } = resolveProject(dir, project);
15140
15256
  lockOwners.set(lockPath, [...lockOwners.get(lockPath) ?? [], project.id]);
15141
15257
  manifestOwners.set(manifestPath, [...manifestOwners.get(manifestPath) ?? [], project.id]);
15142
- if (path25.isAbsolute(project.manifest) || path25.isAbsolute(project.lock)) {
15258
+ if (path26.isAbsolute(project.manifest) || path26.isAbsolute(project.lock)) {
15143
15259
  diagnostics.push({
15144
15260
  id: "absolute-path",
15145
15261
  level: "warning",
@@ -15201,15 +15317,15 @@ function validateWorkspace(ws, dir) {
15201
15317
 
15202
15318
  // src/gallery/edit.ts
15203
15319
  import { readFile as readFile23, rename as rename9, writeFile as writeFile15 } from "fs/promises";
15204
- import path31 from "path";
15320
+ import path32 from "path";
15205
15321
  import { z as z13 } from "zod";
15206
15322
 
15207
15323
  // src/manifest-edit.ts
15208
15324
  import { readFile as readFile19, rename as rename7, rm as rm8, writeFile as writeFile10 } from "fs/promises";
15209
- import path26 from "path";
15325
+ import path27 from "path";
15210
15326
  import { z as z8 } from "zod";
15211
15327
  var HexSha = z8.string().regex(/^[0-9a-f]{64}$/);
15212
- var ProjectFileSchema = z8.string().min(1).refine((file) => !path26.isAbsolute(file) && !file.split(/[\\/]/).includes(".."), "must be a path inside the project");
15328
+ var ProjectFileSchema = z8.string().min(1).refine((file) => !path27.isAbsolute(file) && !file.split(/[\\/]/).includes(".."), "must be a path inside the project");
15213
15329
  var AssetPatchSchema = z8.object({
15214
15330
  prompt: z8.string().optional(),
15215
15331
  /** Per-style override for the style being viewed; null removes it. */
@@ -15628,7 +15744,7 @@ function applyEdit(raw, edit) {
15628
15744
  }
15629
15745
  }
15630
15746
  async function withCandidate(manifestPath, edit, use, unchanged) {
15631
- const absolute = path26.resolve(manifestPath);
15747
+ const absolute = path27.resolve(manifestPath);
15632
15748
  const original = await readFile19(absolute, "utf8");
15633
15749
  if (sha256(original) !== edit.expectedSha256) throw new ManifestDriftError(absolute);
15634
15750
  let raw;
@@ -15642,9 +15758,9 @@ async function withCandidate(manifestPath, edit, use, unchanged) {
15642
15758
  applyEdit(raw, edit);
15643
15759
  const next = serializeLike(original, raw);
15644
15760
  if (next === original) return unchanged(absolute, original);
15645
- const tmp = path26.join(
15646
- path26.dirname(absolute),
15647
- `.${path26.basename(absolute)}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`
15761
+ const tmp = path27.join(
15762
+ path27.dirname(absolute),
15763
+ `.${path27.basename(absolute)}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`
15648
15764
  );
15649
15765
  try {
15650
15766
  await writeFile10(tmp, next);
@@ -15675,16 +15791,16 @@ async function applyManifestEdit(manifestPath, edit) {
15675
15791
  // src/pipeline/hand-edit.ts
15676
15792
  import { existsSync as existsSync22 } from "fs";
15677
15793
  import { copyFile, mkdir as mkdir8, readFile as readFile20, rename as rename8, rm as rm9, writeFile as writeFile11 } from "fs/promises";
15678
- import path27 from "path";
15794
+ import path28 from "path";
15679
15795
  import { z as z9 } from "zod";
15680
15796
  var HAND_EDIT_DIR = "edits";
15681
15797
  function handEditPath(loaded, spec) {
15682
15798
  const style = loaded.manifest.styles[spec.styleId];
15683
- const outDir = path27.resolve(loaded.root, style?.outDir ?? ".");
15684
- const relative = path27.relative(outDir, spec.outFile);
15685
- const inside = relative && !relative.startsWith("..") && !path27.isAbsolute(relative);
15686
- const file = inside ? relative : path27.basename(spec.outFile);
15687
- return path27.join(outDir, HAND_EDIT_DIR, file.replace(/\.gif$/i, ".png"));
15799
+ const outDir = path28.resolve(loaded.root, style?.outDir ?? ".");
15800
+ const relative = path28.relative(outDir, spec.outFile);
15801
+ const inside = relative && !relative.startsWith("..") && !path28.isAbsolute(relative);
15802
+ const file = inside ? relative : path28.basename(spec.outFile);
15803
+ return path28.join(outDir, HAND_EDIT_DIR, file.replace(/\.gif$/i, ".png"));
15688
15804
  }
15689
15805
  function handEditBases(spec, lock) {
15690
15806
  const entry = lock.entries[lockKey(spec.styleId, spec.assetId)];
@@ -15730,7 +15846,7 @@ async function startHandEdit(loaded, lock, spec, opts = {}) {
15730
15846
  `${spec.styleId}/${spec.assetId} declares ${spec.source}, one committed file placed for the whole set; clear that source (pixelkiln edit detach) to hand-edit the members one by one`
15731
15847
  );
15732
15848
  }
15733
- const existing = spec.source ? path27.resolve(loaded.root, spec.source) : null;
15849
+ const existing = spec.source ? path28.resolve(loaded.root, spec.source) : null;
15734
15850
  const editPath = existing ?? handEditPath(loaded, spec);
15735
15851
  const members = handEditMembers(loaded, spec, lock, editPath);
15736
15852
  let created = false;
@@ -15741,7 +15857,7 @@ async function startHandEdit(loaded, lock, spec, opts = {}) {
15741
15857
  `${spec.styleId}/${spec.assetId} has no generated PNG on disk to start from; generate or restore it first`
15742
15858
  );
15743
15859
  }
15744
- await mkdir8(path27.dirname(member.path), { recursive: true });
15860
+ await mkdir8(path28.dirname(member.path), { recursive: true });
15745
15861
  await copyFile(member.base, member.path);
15746
15862
  created = true;
15747
15863
  }
@@ -15879,7 +15995,7 @@ async function saveHandEdit(loaded, lock, spec, input) {
15879
15995
  editor: input.editor,
15880
15996
  protocol: input.protocol,
15881
15997
  savedAt: (input.now ?? /* @__PURE__ */ new Date()).toISOString(),
15882
- project: projectPath ? path27.basename(projectPath) : null,
15998
+ project: projectPath ? path28.basename(projectPath) : null,
15883
15999
  outputs: incoming.map((frame, index) => ({
15884
16000
  role: frame.role,
15885
16001
  basedOn: basedOnHash(entry, index),
@@ -15923,7 +16039,7 @@ function openInEditor(file, editor = process.env.PIXELKILN_EDITOR) {
15923
16039
  // src/gallery/upload.ts
15924
16040
  import { existsSync as existsSync23 } from "fs";
15925
16041
  import { mkdir as mkdir9, writeFile as writeFile12 } from "fs/promises";
15926
- import path28 from "path";
16042
+ import path29 from "path";
15927
16043
  import { z as z10 } from "zod";
15928
16044
  var MAX_UPLOAD_BYTES = 8 * 1024 * 1024;
15929
16045
  var UploadImageSchema = z10.object({
@@ -15936,12 +16052,12 @@ var UploadImageSchema = z10.object({
15936
16052
  overwrite: z10.boolean().optional()
15937
16053
  }).strict();
15938
16054
  async function saveProjectImage(root, request) {
15939
- const file = path28.resolve(root, request.path);
15940
- const relative = path28.relative(root, file);
15941
- if (relative.startsWith("..") || path28.isAbsolute(relative)) {
16055
+ const file = path29.resolve(root, request.path);
16056
+ const relative = path29.relative(root, file);
16057
+ if (relative.startsWith("..") || path29.isAbsolute(relative)) {
15942
16058
  throw new ManifestEditError("an upload must stay inside the project");
15943
16059
  }
15944
- const extension = path28.extname(file).toLowerCase();
16060
+ const extension = path29.extname(file).toLowerCase();
15945
16061
  const expected = extension === ".png" ? "png" : extension === ".jpg" || extension === ".jpeg" ? "jpeg" : null;
15946
16062
  if (!expected) throw new ManifestEditError("an upload must be a .png, .jpg, or .jpeg path");
15947
16063
  const bytes = Buffer.from(request.base64, "base64");
@@ -15954,17 +16070,17 @@ async function saveProjectImage(root, request) {
15954
16070
  throw new ManifestEditError(`the upload is a ${metadata.format.toUpperCase()}; name it ${metadata.format === "png" ? ".png" : ".jpg"}`);
15955
16071
  }
15956
16072
  if (existsSync23(file) && !request.overwrite) {
15957
- throw new ManifestEditError(`${relative.split(path28.sep).join("/")} already exists; choose another name, or replace it`);
16073
+ throw new ManifestEditError(`${relative.split(path29.sep).join("/")} already exists; choose another name, or replace it`);
15958
16074
  }
15959
- await mkdir9(path28.dirname(file), { recursive: true });
16075
+ await mkdir9(path29.dirname(file), { recursive: true });
15960
16076
  await writeFile12(file, bytes);
15961
- return relative.split(path28.sep).join("/");
16077
+ return relative.split(path29.sep).join("/");
15962
16078
  }
15963
16079
 
15964
16080
  // src/gallery/image-revision.ts
15965
16081
  import { existsSync as existsSync24 } from "fs";
15966
16082
  import { readFile as readFile21, rm as rm10, writeFile as writeFile13 } from "fs/promises";
15967
- import path29 from "path";
16083
+ import path30 from "path";
15968
16084
  import { z as z11 } from "zod";
15969
16085
  var InpaintRevisionRequestSchema = z11.object({
15970
16086
  action: z11.literal("create-inpaint-revision"),
@@ -15983,14 +16099,14 @@ var InpaintRevisionRequestSchema = z11.object({
15983
16099
  overwrite: z11.boolean().optional()
15984
16100
  }).strict();
15985
16101
  async function createInpaintRevision(manifestPath, request) {
15986
- const root = path29.dirname(path29.resolve(manifestPath));
15987
- if (path29.extname(request.maskPath).toLowerCase() !== ".png") {
16102
+ const root = path30.dirname(path30.resolve(manifestPath));
16103
+ if (path30.extname(request.maskPath).toLowerCase() !== ".png") {
15988
16104
  throw new ManifestEditError("the mask must be a .png path inside the project");
15989
16105
  }
15990
16106
  const bytes = Buffer.from(request.maskBase64, "base64");
15991
16107
  const metadata = imageMetadata(bytes);
15992
16108
  if (!metadata || metadata.format !== "png") throw new ManifestEditError("the mask is not a readable PNG");
15993
- const file = path29.resolve(root, request.maskPath);
16109
+ const file = path30.resolve(root, request.maskPath);
15994
16110
  const previous = existsSync24(file) ? await readFile21(file) : null;
15995
16111
  const mask = await saveProjectImage(root, {
15996
16112
  action: "upload-image",
@@ -16020,7 +16136,7 @@ async function createInpaintRevision(manifestPath, request) {
16020
16136
  // src/gallery/skeleton.ts
16021
16137
  import { existsSync as existsSync25 } from "fs";
16022
16138
  import { mkdir as mkdir10, readFile as readFile22, rm as rm11, writeFile as writeFile14 } from "fs/promises";
16023
- import path30 from "path";
16139
+ import path31 from "path";
16024
16140
  import { z as z12 } from "zod";
16025
16141
  var SkeletonEstimateRequestSchema = z12.object({
16026
16142
  project: z12.string().min(1).optional(),
@@ -16058,10 +16174,10 @@ var SkeletonKeypointsUpdateSchema = z12.object({
16058
16174
  set: SkeletonSetSchema
16059
16175
  }).strict();
16060
16176
  async function createSkeletonAnimation(manifestPath, request) {
16061
- const root = path30.dirname(path30.resolve(manifestPath));
16062
- const file = path30.resolve(root, request.keypointsFile);
16063
- const relative = path30.relative(root, file);
16064
- if (!request.keypointsFile.endsWith(".json") || relative.startsWith("..") || path30.isAbsolute(relative)) {
16177
+ const root = path31.dirname(path31.resolve(manifestPath));
16178
+ const file = path31.resolve(root, request.keypointsFile);
16179
+ const relative = path31.relative(root, file);
16180
+ if (!request.keypointsFile.endsWith(".json") || relative.startsWith("..") || path31.isAbsolute(relative)) {
16065
16181
  throw new ManifestEditError("the keypoints file must be a .json path inside the project");
16066
16182
  }
16067
16183
  const existed = existsSync25(file);
@@ -16071,7 +16187,7 @@ async function createSkeletonAnimation(manifestPath, request) {
16071
16187
  throw new ManifestEditError(`${request.keypointsFile} already exists; choose another path, or replace it`);
16072
16188
  }
16073
16189
  if (existed) previous = await readFile22(file);
16074
- await mkdir10(path30.dirname(file), { recursive: true });
16190
+ await mkdir10(path31.dirname(file), { recursive: true });
16075
16191
  await writeFile14(file, JSON.stringify(request.set, null, 2) + "\n");
16076
16192
  } else if (!existed) {
16077
16193
  throw new ManifestEditError(`${request.keypointsFile} does not exist; paste poses or estimate them from the sprite`);
@@ -16087,7 +16203,7 @@ async function createSkeletonAnimation(manifestPath, request) {
16087
16203
  revision: {
16088
16204
  mode: "animate-skeleton",
16089
16205
  from: request.from,
16090
- keypointsFile: relative.split(path30.sep).join("/"),
16206
+ keypointsFile: relative.split(path31.sep).join("/"),
16091
16207
  direction: request.direction,
16092
16208
  ...request.description ? { description: request.description } : {},
16093
16209
  ...request.skeletonTemplate ? { skeletonTemplate: request.skeletonTemplate } : {}
@@ -16108,19 +16224,19 @@ async function updateSkeletonKeypoints(ctx, request) {
16108
16224
  if (!spec) throw new ManifestEditError(`"${key}" is not declared by the manifest`);
16109
16225
  const file = spec.revision?.mode === "animate-skeleton" ? spec.revision.keypointsFile : void 0;
16110
16226
  if (!file) throw new ManifestEditError(`${key} is not an animate-skeleton revision`);
16111
- const relative = path30.relative(ctx.loaded.root, file);
16112
- if (relative.startsWith("..") || path30.isAbsolute(relative)) {
16227
+ const relative = path31.relative(ctx.loaded.root, file);
16228
+ if (relative.startsWith("..") || path31.isAbsolute(relative)) {
16113
16229
  throw new ManifestEditError(`${key}'s keypoints file is outside the project; edit it by hand`);
16114
16230
  }
16115
16231
  const current = existsSync25(file) ? sha256(await readFile22(file)) : null;
16116
16232
  if (current !== request.expectedSha256) {
16117
16233
  throw Object.assign(
16118
- new Error(`${relative.split(path30.sep).join("/")} changed on disk since this page loaded it; press Refresh and edit again`),
16234
+ new Error(`${relative.split(path31.sep).join("/")} changed on disk since this page loaded it; press Refresh and edit again`),
16119
16235
  { status: 409 }
16120
16236
  );
16121
16237
  }
16122
16238
  await writeFile14(file, JSON.stringify(request.set, null, 2) + "\n");
16123
- return relative.split(path30.sep).join("/");
16239
+ return relative.split(path31.sep).join("/");
16124
16240
  }
16125
16241
 
16126
16242
  // src/gallery/edit.ts
@@ -16174,7 +16290,7 @@ function createGalleryEditHandler(opts) {
16174
16290
  const history = /* @__PURE__ */ new Map();
16175
16291
  const manifestOf = async (project) => {
16176
16292
  try {
16177
- return path31.resolve(await opts.manifestFor(typeof project === "string" ? project : void 0));
16293
+ return path32.resolve(await opts.manifestFor(typeof project === "string" ? project : void 0));
16178
16294
  } catch {
16179
16295
  return null;
16180
16296
  }
@@ -16197,7 +16313,7 @@ function createGalleryEditHandler(opts) {
16197
16313
  await writeFile15(tmp, last.before);
16198
16314
  await rename9(tmp, manifestPath2);
16199
16315
  stack.pop();
16200
- log(` manifest edit undone in ${path31.relative(process.cwd(), manifestPath2) || manifestPath2}`);
16316
+ log(` manifest edit undone in ${path32.relative(process.cwd(), manifestPath2) || manifestPath2}`);
16201
16317
  return opts.reload();
16202
16318
  }
16203
16319
  const manifestPath = await manifestOf(body?.project);
@@ -16259,7 +16375,7 @@ function createEditApplier(opts, log) {
16259
16375
  } catch (error) {
16260
16376
  throw new ManifestEditError(error instanceof Error ? error.message : String(error));
16261
16377
  }
16262
- const written = await saveProjectImage(path31.dirname(path31.resolve(manifestPath2)), upload.data);
16378
+ const written = await saveProjectImage(path32.dirname(path32.resolve(manifestPath2)), upload.data);
16263
16379
  log(` image added: ${written}`);
16264
16380
  return opts.reload();
16265
16381
  }
@@ -16326,7 +16442,7 @@ function createEditApplier(opts, log) {
16326
16442
  }
16327
16443
  const result = await applyManifestEdit(manifestPath, parsed.data);
16328
16444
  if (result.changed) {
16329
- const relative = path31.relative(process.cwd(), result.manifestPath);
16445
+ const relative = path32.relative(process.cwd(), result.manifestPath);
16330
16446
  const shown = !relative || relative.startsWith("..") ? result.manifestPath : relative;
16331
16447
  log(` manifest edited: ${describeEdit(parsed.data)} in ${shown}`);
16332
16448
  }
@@ -16354,7 +16470,7 @@ function describeEdit(edit) {
16354
16470
  // src/gallery/snapshot.ts
16355
16471
  var PROJECT_FILE_TYPE = "application/octet-stream";
16356
16472
  function galleryMediaId(absolutePath) {
16357
- return createHash4("sha256").update(path32.resolve(absolutePath)).digest("hex").slice(0, 24);
16473
+ return createHash5("sha256").update(path33.resolve(absolutePath)).digest("hex").slice(0, 24);
16358
16474
  }
16359
16475
  function galleryMediaRoute(id) {
16360
16476
  return `/media/${id}`;
@@ -16373,7 +16489,7 @@ function describeHistory(media, entry, cacheDir) {
16373
16489
  return (entry.history ?? []).map((generation, i) => {
16374
16490
  const outputs = generation.outputs.map((output) => {
16375
16491
  const mediaType = output.mediaType ?? MediaType.PNG;
16376
- const file = path32.join(cacheDir, cacheFileName(output.sha256, mediaType));
16492
+ const file = path33.join(cacheDir, cacheFileName(output.sha256, mediaType));
16377
16493
  let url = null;
16378
16494
  if (existsSync26(file)) {
16379
16495
  const id = galleryMediaId(file);
@@ -16505,9 +16621,9 @@ async function describeOutput(media, root, absolutePath, recorded = {}) {
16505
16621
  async function describeQuality(media, root, inspection) {
16506
16622
  const outputPaths = inspection.outputs ?? [inspection.output];
16507
16623
  const outputs = await Promise.all(
16508
- outputPaths.map((file) => describeOutput(media, root, path32.resolve(root, file)))
16624
+ outputPaths.map((file) => describeOutput(media, root, path33.resolve(root, file)))
16509
16625
  );
16510
- const recordPath = path32.resolve(root, inspection.record);
16626
+ const recordPath = path33.resolve(root, inspection.record);
16511
16627
  const recordExists = existsSync26(recordPath);
16512
16628
  let options = null;
16513
16629
  let check = null;
@@ -16527,7 +16643,7 @@ async function describeQuality(media, root, inspection) {
16527
16643
  return {
16528
16644
  state: inspection.state,
16529
16645
  reason: inspection.reason,
16530
- output: portableOutputPath(path32.resolve(root, inspection.output), root),
16646
+ output: portableOutputPath(path33.resolve(root, inspection.output), root),
16531
16647
  record: portableOutputPath(recordPath, root),
16532
16648
  recordExists,
16533
16649
  outputs,
@@ -16567,7 +16683,7 @@ async function buildGallerySnapshot(opts) {
16567
16683
  const items = [];
16568
16684
  const manifestText = await readFile24(loaded.path, "utf8");
16569
16685
  const manifestSha256 = sha256(manifestText);
16570
- const cacheDir = path32.resolve(path32.dirname(opts.lockPath), ".pixelkiln", "cache");
16686
+ const cacheDir = path33.resolve(path33.dirname(opts.lockPath), ".pixelkiln", "cache");
16571
16687
  let rawStyles = {};
16572
16688
  try {
16573
16689
  const raw = JSON.parse(manifestText);
@@ -16591,7 +16707,7 @@ async function buildGallerySnapshot(opts) {
16591
16707
  )
16592
16708
  );
16593
16709
  if (spec.source) {
16594
- const editPath = path32.resolve(root, spec.source);
16710
+ const editPath = path33.resolve(root, spec.source);
16595
16711
  const memberCount = sourceIsStem(spec.source, entry, root) ? entry.outputs.length : 1;
16596
16712
  const editShas = await Promise.all(
16597
16713
  Array.from({ length: memberCount }, async (_, index) => {
@@ -16640,7 +16756,7 @@ async function buildGallerySnapshot(opts) {
16640
16756
  editStatus = edits.some((member) => !member.exists) ? "missing" : editChanged.every((changed) => !changed) ? "same" : regenerated ? "regenerated-since" : "edited";
16641
16757
  }
16642
16758
  } else if (spec.source) {
16643
- outputs = [await describeOutput(media, root, path32.resolve(root, spec.source))];
16759
+ outputs = [await describeOutput(media, root, path33.resolve(root, spec.source))];
16644
16760
  } else if (!entry && existsSync26(spec.outFile)) {
16645
16761
  outputs = [await describeOutput(media, root, spec.outFile)];
16646
16762
  } else {
@@ -16844,7 +16960,7 @@ async function buildGallerySnapshot(opts) {
16844
16960
  id: loaded.manifest.name,
16845
16961
  name: loaded.manifest.name,
16846
16962
  manifest: loaded.path,
16847
- lock: path32.resolve(opts.lockPath),
16963
+ lock: path33.resolve(opts.lockPath),
16848
16964
  root,
16849
16965
  provider: loaded.manifest.provider,
16850
16966
  account: null,
@@ -16870,7 +16986,7 @@ async function buildGallerySnapshot(opts) {
16870
16986
  return { snapshot, media };
16871
16987
  }
16872
16988
  async function buildWorkspaceGallerySnapshot(opts) {
16873
- const dir = path32.dirname(path32.resolve(opts.workspacePath));
16989
+ const dir = path33.dirname(path33.resolve(opts.workspacePath));
16874
16990
  const media = /* @__PURE__ */ new Map();
16875
16991
  const projects = [];
16876
16992
  const items = [];
@@ -16913,7 +17029,7 @@ async function buildWorkspaceGallerySnapshot(opts) {
16913
17029
  id: project.id,
16914
17030
  name: loaded.manifest.name,
16915
17031
  manifest: manifestPath,
16916
- lock: path32.resolve(lockPath),
17032
+ lock: path33.resolve(lockPath),
16917
17033
  root: loaded.root,
16918
17034
  provider: loaded.manifest.provider,
16919
17035
  account: project.account ?? null,
@@ -16929,8 +17045,8 @@ async function buildWorkspaceGallerySnapshot(opts) {
16929
17045
  id: project.id,
16930
17046
  name: project.id,
16931
17047
  manifest: manifestPath,
16932
- lock: path32.resolve(lockPath),
16933
- root: path32.dirname(manifestPath),
17048
+ lock: path33.resolve(lockPath),
17049
+ root: path33.dirname(manifestPath),
16934
17050
  provider: project.provider,
16935
17051
  account: project.account ?? null,
16936
17052
  manifestSha256: null,
@@ -16957,7 +17073,7 @@ async function buildWorkspaceGallerySnapshot(opts) {
16957
17073
  version: 1,
16958
17074
  generatedAt: (opts.now ?? (() => /* @__PURE__ */ new Date()))().toISOString(),
16959
17075
  project: null,
16960
- workspace: { path: path32.resolve(opts.workspacePath), dir, projects },
17076
+ workspace: { path: path33.resolve(opts.workspacePath), dir, projects },
16961
17077
  filter,
16962
17078
  totals: {
16963
17079
  entries: projects.reduce((sum, project) => sum + project.entries, 0),
@@ -16974,7 +17090,7 @@ async function buildWorkspaceGallerySnapshot(opts) {
16974
17090
  }
16975
17091
 
16976
17092
  // src/gallery/page.ts
16977
- import { existsSync as existsSync27, readFileSync as readFileSync5 } from "fs";
17093
+ import { existsSync as existsSync27, readFileSync as readFileSync6 } from "fs";
16978
17094
 
16979
17095
  // src/gallery/client-bundle.ts
16980
17096
  import { createRequire } from "module";
@@ -17014,8 +17130,8 @@ var client;
17014
17130
  function clientAssets() {
17015
17131
  const prebuilt = new URL("gallery.js", CLIENT_DIR);
17016
17132
  client ??= {
17017
- css: readFileSync5(new URL("gallery.css", CLIENT_DIR), "utf8"),
17018
- js: existsSync27(prebuilt) ? readFileSync5(prebuilt, "utf8") : bundleGalleryClient()
17133
+ css: readFileSync6(new URL("gallery.css", CLIENT_DIR), "utf8"),
17134
+ js: existsSync27(prebuilt) ? readFileSync6(prebuilt, "utf8") : bundleGalleryClient()
17019
17135
  };
17020
17136
  return client;
17021
17137
  }
@@ -17157,11 +17273,11 @@ var EDITOR_PIN = pin_default;
17157
17273
  var EDITOR_RELEASE_BASE = "https://github.com/gfargo/pixelkiln/releases/download";
17158
17274
 
17159
17275
  // src/editor/install.ts
17160
- import { createHash as createHash5 } from "crypto";
17276
+ import { createHash as createHash6 } from "crypto";
17161
17277
  import { existsSync as existsSync28 } from "fs";
17162
17278
  import { mkdir as mkdir11, readFile as readFile25, rename as rename10, rm as rm12, stat as stat4, writeFile as writeFile16 } from "fs/promises";
17163
- import os2 from "os";
17164
- import path33 from "path";
17279
+ import os3 from "os";
17280
+ import path34 from "path";
17165
17281
  var EditorInstallError = class extends Error {
17166
17282
  constructor(message6, file) {
17167
17283
  super(message6);
@@ -17172,15 +17288,15 @@ var EditorInstallError = class extends Error {
17172
17288
  };
17173
17289
  function editorToolsRoot() {
17174
17290
  const override = process.env.PIXELKILN_TOOLS_DIR?.trim();
17175
- if (override) return path33.resolve(override);
17176
- if (process.platform === "darwin") return path33.join(os2.homedir(), "Library", "Caches", "pixelkiln", "tools");
17291
+ if (override) return path34.resolve(override);
17292
+ if (process.platform === "darwin") return path34.join(os3.homedir(), "Library", "Caches", "pixelkiln", "tools");
17177
17293
  if (process.platform === "win32") {
17178
- return path33.join(process.env.LOCALAPPDATA ?? path33.join(os2.homedir(), "AppData", "Local"), "pixelkiln", "tools");
17294
+ return path34.join(process.env.LOCALAPPDATA ?? path34.join(os3.homedir(), "AppData", "Local"), "pixelkiln", "tools");
17179
17295
  }
17180
- return path33.join(process.env.XDG_CACHE_HOME ?? path33.join(os2.homedir(), ".cache"), "pixelkiln", "tools");
17296
+ return path34.join(process.env.XDG_CACHE_HOME ?? path34.join(os3.homedir(), ".cache"), "pixelkiln", "tools");
17181
17297
  }
17182
17298
  function editorDir(pin = EDITOR_PIN, root = editorToolsRoot()) {
17183
- return path33.join(root, "pixelorama", pin.release ?? "unpublished");
17299
+ return path34.join(root, "pixelorama", pin.release ?? "unpublished");
17184
17300
  }
17185
17301
  function editorBaseUrl(pin = EDITOR_PIN) {
17186
17302
  const override = process.env.PIXELKILN_EDITOR_URL?.trim();
@@ -17190,7 +17306,7 @@ function editorBaseUrl(pin = EDITOR_PIN) {
17190
17306
  }
17191
17307
  async function sha256Of(file) {
17192
17308
  try {
17193
- return createHash5("sha256").update(await readFile25(file)).digest("hex");
17309
+ return createHash6("sha256").update(await readFile25(file)).digest("hex");
17194
17310
  } catch {
17195
17311
  return null;
17196
17312
  }
@@ -17203,7 +17319,7 @@ async function editorStatus(opts = {}) {
17203
17319
  let totalBytes = 0;
17204
17320
  for (const [name, expected] of Object.entries(pin.files)) {
17205
17321
  totalBytes += expected.bytes;
17206
- const file = path33.join(dir, name);
17322
+ const file = path34.join(dir, name);
17207
17323
  const info = existsSync28(file) ? await stat4(file).catch(() => null) : null;
17208
17324
  if (info && info.size === expected.bytes && await sha256Of(file) === expected.sha256) {
17209
17325
  installedBytes += expected.bytes;
@@ -17257,11 +17373,11 @@ async function installEditor(opts = {}) {
17257
17373
  if (bytes.length !== expected.bytes) {
17258
17374
  throw new EditorInstallError(`${name}: expected ${expected.bytes} bytes, got ${bytes.length}`, name);
17259
17375
  }
17260
- const digest2 = createHash5("sha256").update(bytes).digest("hex");
17376
+ const digest2 = createHash6("sha256").update(bytes).digest("hex");
17261
17377
  if (digest2 !== expected.sha256) {
17262
17378
  throw new EditorInstallError(`${name}: sha256 ${digest2.slice(0, 12)}\u2026 does not match the pinned ${expected.sha256.slice(0, 12)}\u2026`, name);
17263
17379
  }
17264
- const final = path33.join(dir, name);
17380
+ const final = path34.join(dir, name);
17265
17381
  const tmp = `${final}.${process.pid}.part`;
17266
17382
  try {
17267
17383
  await writeFile16(tmp, bytes);
@@ -17307,12 +17423,12 @@ var EDITOR_CONTENT_TYPES = {
17307
17423
  ".json": "application/json; charset=utf-8"
17308
17424
  };
17309
17425
  function editorContentType(name) {
17310
- return EDITOR_CONTENT_TYPES[path33.extname(name).toLowerCase()] ?? null;
17426
+ return EDITOR_CONTENT_TYPES[path34.extname(name).toLowerCase()] ?? null;
17311
17427
  }
17312
17428
 
17313
17429
  // src/gallery/editor.ts
17314
17430
  import { readFile as readFile26 } from "fs/promises";
17315
- import path34 from "path";
17431
+ import path35 from "path";
17316
17432
  function editorRoute(release, name = "index.html") {
17317
17433
  return `/editor/${encodeURIComponent(release)}/${encodeURIComponent(name)}`;
17318
17434
  }
@@ -17385,7 +17501,7 @@ function createGalleryEditorHandlers(opts = {}) {
17385
17501
  if (!status.installed) return null;
17386
17502
  let bytes;
17387
17503
  try {
17388
- bytes = await readFile26(path34.join(dir, name));
17504
+ bytes = await readFile26(path35.join(dir, name));
17389
17505
  } catch {
17390
17506
  verified = null;
17391
17507
  return null;
@@ -18078,7 +18194,7 @@ function createGenerateHandlers(opts) {
18078
18194
  // src/pipeline/salvage.ts
18079
18195
  import { readFile as readFile28 } from "fs/promises";
18080
18196
  import { existsSync as existsSync29 } from "fs";
18081
- import path35 from "path";
18197
+ import path36 from "path";
18082
18198
  function providerClaimId(provider, objectId) {
18083
18199
  return `${encodeURIComponent(provider)}:${encodeURIComponent(objectId)}`;
18084
18200
  }
@@ -18192,20 +18308,20 @@ function matchOrphanStyle(prompt, manifest) {
18192
18308
  return matchStyleByPattern(prompt, manifest);
18193
18309
  }
18194
18310
  async function loadSiblingManifests(ownManifestPath, workspaceManifestPaths, claimPaths) {
18195
- const own = path35.resolve(ownManifestPath);
18311
+ const own = path36.resolve(ownManifestPath);
18196
18312
  const siblingManifestPaths = [
18197
18313
  .../* @__PURE__ */ new Set([
18198
18314
  ...workspaceManifestPaths,
18199
- ...claimPaths.map((c) => path35.join(path35.dirname(path35.resolve(c)), "pixelkiln.manifest.json"))
18315
+ ...claimPaths.map((c) => path36.join(path36.dirname(path36.resolve(c)), "pixelkiln.manifest.json"))
18200
18316
  ])
18201
18317
  ];
18202
18318
  const siblings = [];
18203
18319
  for (const siblingManifestPath of siblingManifestPaths) {
18204
- if (path35.resolve(siblingManifestPath) === own) continue;
18320
+ if (path36.resolve(siblingManifestPath) === own) continue;
18205
18321
  if (!existsSync29(siblingManifestPath)) continue;
18206
18322
  try {
18207
18323
  const { manifest } = await loadManifest(siblingManifestPath);
18208
- siblings.push({ label: path35.basename(path35.dirname(siblingManifestPath)), manifest });
18324
+ siblings.push({ label: path36.basename(path36.dirname(siblingManifestPath)), manifest });
18209
18325
  } catch {
18210
18326
  }
18211
18327
  }
@@ -18326,7 +18442,7 @@ async function applyTags(provider, decisions, existing, opts = {}) {
18326
18442
 
18327
18443
  // src/pick/salvage-server.ts
18328
18444
  import { mkdir as mkdir12, writeFile as writeFile17, readFile as readFile29 } from "fs/promises";
18329
- import path36 from "path";
18445
+ import path37 from "path";
18330
18446
 
18331
18447
  // src/pick/salvage-sheet.ts
18332
18448
  var escapeHtml3 = (s) => s.replace(/[&<>"']/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[c]);
@@ -18492,7 +18608,7 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
18492
18608
  });
18493
18609
  const html = renderSalvageSheet(orphans, {
18494
18610
  styleId: ctx.styleId,
18495
- importDir: path36.relative(process.cwd(), ctx.importDir) || "."
18611
+ importDir: path37.relative(process.cwd(), ctx.importDir) || "."
18496
18612
  });
18497
18613
  const byId = new Map(orphans.map((o) => [o.id, o]));
18498
18614
  const existingTags = new Map(orphans.map((o) => [o.id, o.tags]));
@@ -18548,9 +18664,9 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
18548
18664
  if (!buf.subarray(0, 8).equals(PNG_SIGNATURE2)) throw new Error("not a PNG");
18549
18665
  decodePng(buf);
18550
18666
  const assetId = idFromPrompt(orphan.prompt, taken);
18551
- const rel = path36.join("_salvaged", `${assetId}.png`);
18552
- const outFile = path36.resolve(ctx.importDir, rel);
18553
- await mkdir12(path36.dirname(outFile), { recursive: true });
18667
+ const rel = path37.join("_salvaged", `${assetId}.png`);
18668
+ const outFile = path37.resolve(ctx.importDir, rel);
18669
+ await mkdir12(path37.dirname(outFile), { recursive: true });
18554
18670
  await writeFile17(outFile, buf);
18555
18671
  ctx.manifest.assets[assetId] = {
18556
18672
  prompt: orphan.prompt,
@@ -18583,7 +18699,7 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
18583
18699
  sourceUrl: durableSource,
18584
18700
  sourceUrls: durableSource ? [{ url: durableSource }] : [],
18585
18701
  outputs: [{
18586
- path: portableOutputPath(outFile, path36.dirname(ctx.manifestPath)),
18702
+ path: portableOutputPath(outFile, path37.dirname(ctx.manifestPath)),
18587
18703
  sha256: sha256(buf)
18588
18704
  }],
18589
18705
  submittedAt: orphan.createdAt,