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/cli.js CHANGED
@@ -665,10 +665,10 @@ import { createReadStream } from "fs";
665
665
  function sha256(data) {
666
666
  return createHash("sha256").update(data).digest("hex");
667
667
  }
668
- async function sha256File(path51) {
668
+ async function sha256File(path52) {
669
669
  return new Promise((resolve, reject) => {
670
670
  const hash = createHash("sha256");
671
- const stream = createReadStream(path51);
671
+ const stream = createReadStream(path52);
672
672
  stream.on("data", (chunk2) => hash.update(chunk2));
673
673
  stream.on("error", reject);
674
674
  stream.on("end", () => resolve(hash.digest("hex")));
@@ -4818,7 +4818,7 @@ async function confirm(question, auto) {
4818
4818
  }
4819
4819
 
4820
4820
  // src/cli/commands/account.ts
4821
- import path22 from "path";
4821
+ import path23 from "path";
4822
4822
  import { existsSync as existsSync16 } from "fs";
4823
4823
 
4824
4824
  // src/env.ts
@@ -6251,9 +6251,9 @@ var PixelLabClient = class {
6251
6251
  apiKey;
6252
6252
  http;
6253
6253
  /** Retries transport failures, 429, and 5xx; see http.ts for the policy. */
6254
- async request(path51, init) {
6254
+ async request(path52, init) {
6255
6255
  const auth = this.apiKey.startsWith("Bearer ") ? this.apiKey : `Bearer ${this.apiKey}`;
6256
- const res = await this.http(`${BASE}${path51}`, {
6256
+ const res = await this.http(`${BASE}${path52}`, {
6257
6257
  ...init,
6258
6258
  headers: {
6259
6259
  Authorization: auth,
@@ -6263,13 +6263,13 @@ var PixelLabClient = class {
6263
6263
  });
6264
6264
  const text = await res.text();
6265
6265
  if (!res.ok) {
6266
- throw new PixelLabError(`${init?.method ?? "GET"} ${path51} \u2192 ${res.status}`, res.status, text);
6266
+ throw new PixelLabError(`${init?.method ?? "GET"} ${path52} \u2192 ${res.status}`, res.status, text);
6267
6267
  }
6268
6268
  if (!text) return {};
6269
6269
  try {
6270
6270
  return JSON.parse(text);
6271
6271
  } catch {
6272
- throw new Error(`${init?.method ?? "GET"} ${path51} returned invalid JSON`);
6272
+ throw new Error(`${init?.method ?? "GET"} ${path52} returned invalid JSON`);
6273
6273
  }
6274
6274
  }
6275
6275
  async balance() {
@@ -6674,10 +6674,10 @@ var PixelLabClient = class {
6674
6674
  const palette = args.paletteSwatchBase64 ? { color_image: { type: "base64", base64: args.paletteSwatchBase64, format: "png" }, force_colors: true } : {};
6675
6675
  const encode = (image) => ({ type: "base64", base64: image.base64, format: image.format });
6676
6676
  const south = args.reference?.south;
6677
- let path51;
6677
+ let path52;
6678
6678
  let body;
6679
6679
  if (args.mode === "v3") {
6680
- path51 = "/create-character-v3";
6680
+ path52 = "/create-character-v3";
6681
6681
  body = {
6682
6682
  description: args.description,
6683
6683
  image_size: imageSize,
@@ -6691,7 +6691,7 @@ var PixelLabClient = class {
6691
6691
  ...args.enhancePrompt ? { enhance_prompt: true } : {}
6692
6692
  };
6693
6693
  } else if (args.mode === "pro-flash") {
6694
- path51 = "/create-character-pro-flash";
6694
+ path52 = "/create-character-pro-flash";
6695
6695
  const traits = args.styleTraits;
6696
6696
  body = {
6697
6697
  description: args.description,
@@ -6715,7 +6715,7 @@ var PixelLabClient = class {
6715
6715
  } : {}
6716
6716
  };
6717
6717
  } else if (args.mode === "pro") {
6718
- path51 = "/create-character-pro";
6718
+ path52 = "/create-character-pro";
6719
6719
  body = {
6720
6720
  description: args.description,
6721
6721
  image_size: imageSize,
@@ -6729,7 +6729,7 @@ var PixelLabClient = class {
6729
6729
  ...!south && args.styleCharacterId ? { style_character_id: args.styleCharacterId } : {}
6730
6730
  };
6731
6731
  } else {
6732
- path51 = args.directions === 4 ? "/create-character-with-4-directions" : "/create-character-with-8-directions";
6732
+ path52 = args.directions === 4 ? "/create-character-with-4-directions" : "/create-character-with-8-directions";
6733
6733
  body = {
6734
6734
  description: args.description,
6735
6735
  image_size: imageSize,
@@ -6746,8 +6746,8 @@ var PixelLabClient = class {
6746
6746
  ...palette
6747
6747
  };
6748
6748
  }
6749
- const raw = await this.request(path51, { method: "POST", body: JSON.stringify(body) });
6750
- return validateResponse(CharacterSubmitSchema, raw, path51.slice(1));
6749
+ const raw = await this.request(path52, { method: "POST", body: JSON.stringify(body) });
6750
+ return validateResponse(CharacterSubmitSchema, raw, path52.slice(1));
6751
6751
  }
6752
6752
  /** A text edit of an existing character, applied to every direction and stored as a sibling. */
6753
6753
  async createCharacterState(args) {
@@ -9725,9 +9725,9 @@ var RetroDiffusionClient = class {
9725
9725
  if (!response.ok) throw new ProviderError("retrodiffusion", `Retro Diffusion download failed (${response.status})`, { status: response.status });
9726
9726
  return Buffer.from(await response.arrayBuffer());
9727
9727
  }
9728
- async call(path51, init = {}) {
9728
+ async call(path52, init = {}) {
9729
9729
  if (!this.token) throw new Error("RD_API_KEY is not set");
9730
- const response = await this.request(`${this.baseUrl}${path51}`, {
9730
+ const response = await this.request(`${this.baseUrl}${path52}`, {
9731
9731
  ...init,
9732
9732
  headers: {
9733
9733
  "Content-Type": "application/json",
@@ -10036,8 +10036,13 @@ function retroError(value) {
10036
10036
  }
10037
10037
 
10038
10038
  // src/providers/scenario.ts
10039
+ import { createHash as createHash3 } from "crypto";
10040
+ import { mkdirSync as mkdirSync2, readFileSync as readFileSync3, renameSync, writeFileSync as writeFileSync2 } from "fs";
10041
+ import os2 from "os";
10042
+ import path13 from "path";
10039
10043
  var DEFAULT_BASE_URL3 = "https://api.cloud.scenario.com/v1";
10040
10044
  var SOURCE_PROTOCOL2 = "scenario:";
10045
+ var SUBMIT_RETRIES = 8;
10041
10046
  var PROTECTED_PARAMETERS = /* @__PURE__ */ new Set([
10042
10047
  "dryRun",
10043
10048
  "height",
@@ -10084,7 +10089,11 @@ var ScenarioClient = class {
10084
10089
  async submit(modelId, body, projectId) {
10085
10090
  const response = await this.call(
10086
10091
  `/generate/custom/${encodeURIComponent(modelId)}`,
10087
- { method: "POST", body: JSON.stringify(body) },
10092
+ // A 429 here means this account's concurrent-job limit refused the
10093
+ // request, so nothing was accepted or billed; wait it out instead of
10094
+ // failing the asset. The default four retries give up after about half
10095
+ // a minute, before a running job has finished.
10096
+ { method: "POST", body: JSON.stringify(body), retries: SUBMIT_RETRIES },
10088
10097
  { projectId }
10089
10098
  );
10090
10099
  const job = response?.job;
@@ -10092,6 +10101,29 @@ var ScenarioClient = class {
10092
10101
  if (!jobId) throw new Error("Scenario did not return an async job id");
10093
10102
  return jobId;
10094
10103
  }
10104
+ /**
10105
+ * Uploads one image (POST /assets) and returns its asset id, which a model's
10106
+ * reference input takes. Free, and idempotent per image: the id is kept by
10107
+ * content hash so a rerun does not upload the same bytes again.
10108
+ */
10109
+ async uploadImage(image, projectId) {
10110
+ const bytes = Buffer.from(image.base64, "base64");
10111
+ const digest2 = sha256(bytes);
10112
+ const cached = readUploadCache()[uploadCacheKey(this.baseUrl, projectId, digest2)];
10113
+ if (cached) return cached;
10114
+ const response = await this.call(
10115
+ "/assets",
10116
+ {
10117
+ method: "POST",
10118
+ body: JSON.stringify({ image: image.base64, name: `pixelkiln-${digest2.slice(0, 12)}.${image.format === "png" ? "png" : "jpg"}` })
10119
+ },
10120
+ { projectId }
10121
+ );
10122
+ const id = response?.asset?.id;
10123
+ if (typeof id !== "string" || !id) throw new Error("Scenario did not return an uploaded asset id");
10124
+ writeUploadCache(uploadCacheKey(this.baseUrl, projectId, digest2), id);
10125
+ return id;
10126
+ }
10095
10127
  async job(id, projectId) {
10096
10128
  const response = await this.call(`/jobs/${encodeURIComponent(id)}`, {}, { projectId });
10097
10129
  const raw = response?.job;
@@ -10218,6 +10250,14 @@ var ScenarioProvider = class _ScenarioProvider {
10218
10250
  supports(generator) {
10219
10251
  return generator === "map";
10220
10252
  }
10253
+ /**
10254
+ * An edit is a generation with the parent as a reference image, which the
10255
+ * editing models (GPT Image, Gemini, FLUX) take. Masked inpainting and the
10256
+ * PixelLab-specific modes have no common Scenario input.
10257
+ */
10258
+ supportsRevision(mode2) {
10259
+ return mode2 === "image-to-image";
10260
+ }
10221
10261
  estimate(spec) {
10222
10262
  const options = scenarioOptions(spec);
10223
10263
  return {
@@ -10238,11 +10278,31 @@ var ScenarioProvider = class _ScenarioProvider {
10238
10278
  if (!Number.isInteger(count) || count < 1 || count > 4) {
10239
10279
  throw new Error("Scenario numOutputs must be a whole number from 1 to 4");
10240
10280
  }
10241
- if (spec.width < 128 || spec.width > 2048 || spec.width % 16 !== 0 || spec.height < 128 || spec.height > 2048 || spec.height % 16 !== 0) {
10242
- throw new Error("Scenario width and height must be multiples of 16 from 128 to 2048");
10281
+ if (!Number.isInteger(spec.width) || spec.width < 16 || spec.width > 4096 || !Number.isInteger(spec.height) || spec.height < 16 || spec.height > 4096) {
10282
+ throw new Error("Scenario width and height must be whole numbers from 16 to 4096");
10243
10283
  }
10244
- if (styleImages.length) {
10245
- throw new Error("Scenario styleImages are not supported yet; use a reusable Scenario model");
10284
+ const revision = spec.revision;
10285
+ if (revision) {
10286
+ if (revision.sourceMembers?.length) {
10287
+ throw new Error(
10288
+ `Scenario edits one image; ${revision.sourceAssetId} is a ${revision.sourceMembers.length}-member set`
10289
+ );
10290
+ }
10291
+ if (revision.strength != null) {
10292
+ throw new Error("Scenario image-to-image takes no strength; use the model's own parameters");
10293
+ }
10294
+ }
10295
+ const parameter = referenceParameter(options);
10296
+ if ((styleImages.length || revision) && Object.hasOwn(options.parameters ?? {}, parameter)) {
10297
+ throw new Error(
10298
+ `Scenario parameters.${parameter} is the reference-image input and is filled from styleImages and revisions`
10299
+ );
10300
+ }
10301
+ if (styleImages.length + (revision ? 1 : 0) > 1 && options.referenceArray === false) {
10302
+ throw new Error(`Scenario ${parameter} takes one image; use one style image or a revision, not both`);
10303
+ }
10304
+ if (options.referenceParameter != null && (typeof options.referenceParameter !== "string" || !options.referenceParameter.trim())) {
10305
+ throw new Error("Scenario referenceParameter must be a non-empty string");
10246
10306
  }
10247
10307
  if (options.projectId != null && (typeof options.projectId !== "string" || !options.projectId.trim())) {
10248
10308
  throw new Error("Scenario projectId must be a non-empty string");
@@ -10255,7 +10315,12 @@ var ScenarioProvider = class _ScenarioProvider {
10255
10315
  async submit(spec, styleImages) {
10256
10316
  this.validate(spec, styleImages);
10257
10317
  const options = scenarioOptions(spec);
10258
- const body = requestBody(spec, options);
10318
+ const references = [];
10319
+ if (spec.revision) references.push(readRevisionSource(spec));
10320
+ for (const image of styleImages) references.push({ base64: image.base64, format: image.format });
10321
+ const referenceAssetIds = [];
10322
+ for (const image of references) referenceAssetIds.push(await this.client.uploadImage(image, options.projectId));
10323
+ const body = requestBody(spec, options, referenceAssetIds);
10259
10324
  const quote = await this.client.quote(options.modelId, body, options.projectId);
10260
10325
  if (quote.total > options.maxComputeUnits + Number.EPSILON) {
10261
10326
  throw new Error(
@@ -10272,7 +10337,8 @@ var ScenarioProvider = class _ScenarioProvider {
10272
10337
  quotedComputeUnits: quote.total,
10273
10338
  creativeUnitsCost: quote.creativeUnitsCost,
10274
10339
  costDetails: quote.costDetails,
10275
- ipDetectionCost: quote.ipDetectionCost
10340
+ ipDetectionCost: quote.ipDetectionCost,
10341
+ ...referenceAssetIds.length ? { referenceAssetIds } : {}
10276
10342
  }
10277
10343
  };
10278
10344
  }
@@ -10294,7 +10360,13 @@ var ScenarioProvider = class _ScenarioProvider {
10294
10360
  }
10295
10361
  const assetIds = scenarioAssetIds(job);
10296
10362
  if (!assetIds.length) return { status: "failed", error: "Scenario job returned no asset IDs" };
10297
- const assets = await this.assets(assetIds, reference.projectId);
10363
+ let assets;
10364
+ try {
10365
+ assets = await this.assets(assetIds, reference.projectId);
10366
+ } catch (error) {
10367
+ if (error instanceof UnsupportedScenarioOutput) return { status: "failed", error: error.message };
10368
+ throw error;
10369
+ }
10298
10370
  if (assets.length > 1) {
10299
10371
  return {
10300
10372
  status: "review",
@@ -10349,6 +10421,32 @@ var ScenarioProvider = class _ScenarioProvider {
10349
10421
  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));
10350
10422
  }
10351
10423
  };
10424
+ function uploadCachePath() {
10425
+ return process.env.SCENARIO_UPLOAD_CACHE ?? path13.join(os2.tmpdir(), "pixelkiln-scenario", "uploads.json");
10426
+ }
10427
+ function uploadCacheKey(baseUrl, projectId, digest2) {
10428
+ return createHash3("sha256").update(`${baseUrl}
10429
+ ${projectId ?? ""}
10430
+ ${digest2}`).digest("hex");
10431
+ }
10432
+ function readUploadCache() {
10433
+ try {
10434
+ const value = JSON.parse(readFileSync3(uploadCachePath(), "utf8"));
10435
+ return value && typeof value === "object" && !Array.isArray(value) ? value : {};
10436
+ } catch {
10437
+ return {};
10438
+ }
10439
+ }
10440
+ function writeUploadCache(key, assetId) {
10441
+ try {
10442
+ const file = uploadCachePath();
10443
+ mkdirSync2(path13.dirname(file), { recursive: true });
10444
+ const tmp = `${file}.${process.pid}.tmp`;
10445
+ writeFileSync2(tmp, JSON.stringify({ ...readUploadCache(), [key]: assetId }));
10446
+ renameSync(tmp, file);
10447
+ } catch {
10448
+ }
10449
+ }
10352
10450
  function scenarioOptions(spec) {
10353
10451
  return spec.providerOptions;
10354
10452
  }
@@ -10364,9 +10462,25 @@ function scenarioBaseUrl(value) {
10364
10462
  }
10365
10463
  return url.href.replace(/\/$/, "");
10366
10464
  }
10367
- function requestBody(spec, options) {
10465
+ function referenceParameter(options) {
10466
+ return options.referenceParameter?.trim() || "referenceImages";
10467
+ }
10468
+ function readRevisionSource(spec) {
10469
+ const revision = spec.revision;
10470
+ if (!revision.sourceFile || !revision.sourceSha256 || !revision.sourceFormat) {
10471
+ throw new Error(`${spec.styleId}/${spec.assetId}: revision source is not ready`);
10472
+ }
10473
+ const bytes = readFileSync3(revision.sourceFile);
10474
+ if (sha256(bytes) !== revision.sourceSha256) {
10475
+ throw new Error(`${spec.styleId}/${spec.assetId}: revision source changed after the manifest was resolved`);
10476
+ }
10477
+ return { base64: bytes.toString("base64"), format: revision.sourceFormat };
10478
+ }
10479
+ function requestBody(spec, options, referenceAssetIds = []) {
10480
+ const parameter = referenceParameter(options);
10368
10481
  return {
10369
10482
  ...options.parameters ?? {},
10483
+ ...referenceAssetIds.length ? { [parameter]: options.referenceArray === false ? referenceAssetIds[0] : referenceAssetIds } : {},
10370
10484
  prompt: spec.prompt,
10371
10485
  width: spec.width,
10372
10486
  height: spec.height,
@@ -10456,9 +10570,11 @@ function scenarioAssetIds(job) {
10456
10570
  function assetDownloadUrl(asset) {
10457
10571
  return asset.originalFileUrl || asset.url;
10458
10572
  }
10573
+ var UnsupportedScenarioOutput = class extends Error {
10574
+ };
10459
10575
  function assertPngAsset(asset) {
10460
10576
  if (asset.originalMimeType && asset.originalMimeType !== MediaType.PNG) {
10461
- throw new Error(
10577
+ throw new UnsupportedScenarioOutput(
10462
10578
  `Scenario asset ${asset.id} is ${asset.originalMimeType}; PixelKiln's Scenario MVP requires PNG output`
10463
10579
  );
10464
10580
  }
@@ -10586,7 +10702,7 @@ registerProvider({
10586
10702
  // src/manifest.ts
10587
10703
  import { readFile as readFile6 } from "fs/promises";
10588
10704
  import { existsSync as existsSync8 } from "fs";
10589
- import path13 from "path";
10705
+ import path14 from "path";
10590
10706
 
10591
10707
  // src/loop-directions.ts
10592
10708
  var SINGLE_OUTPUT_FIELDS = ["file", "cell", "remoteId", "source", "sourceByStyle", "outputRole"];
@@ -10675,7 +10791,7 @@ function isObject2(value) {
10675
10791
 
10676
10792
  // src/manifest.ts
10677
10793
  async function loadManifest(manifestPath) {
10678
- const abs = path13.resolve(manifestPath);
10794
+ const abs = path14.resolve(manifestPath);
10679
10795
  if (!existsSync8(abs)) throw new ProjectError(`No manifest at ${abs}`);
10680
10796
  const expansion = expandLoopDirections(JSON.parse(await readFile6(abs, "utf8")));
10681
10797
  if (expansion.issues.length) {
@@ -10768,7 +10884,7 @@ ${issues}`);
10768
10884
  ${unknownReferences.map((i) => ` ${i}`).join("\n")}`);
10769
10885
  }
10770
10886
  const loopFamilies = Object.keys(expansion.families).length ? { loopFamilies: expansion.families } : {};
10771
- return { manifest: parsed.data, root: path13.dirname(abs), path: abs, ...loopFamilies };
10887
+ return { manifest: parsed.data, root: path14.dirname(abs), path: abs, ...loopFamilies };
10772
10888
  }
10773
10889
  function parentAssetId(asset) {
10774
10890
  return asset?.revision?.from ?? asset?.state?.of ?? asset?.animation?.of ?? asset?.portrait?.of ?? asset?.outfit?.of;
@@ -10869,7 +10985,7 @@ async function resolveSpecs(loaded, filter) {
10869
10985
  }
10870
10986
  const styleImageCache = /* @__PURE__ */ new Map();
10871
10987
  async function loadStyleImage(rel, what = "Style image") {
10872
- const abs = path13.resolve(root, rel);
10988
+ const abs = path14.resolve(root, rel);
10873
10989
  let hit = styleImageCache.get(abs);
10874
10990
  if (!hit) {
10875
10991
  if (!existsSync8(abs)) throw new Error(`${what} not found: ${abs}`);
@@ -10899,7 +11015,7 @@ async function resolveSpecs(loaded, filter) {
10899
11015
  styleImageDimensions.push(loadedImage);
10900
11016
  }
10901
11017
  const resolvedImages = style.styleImages.map((image) => {
10902
- const hit = styleImageCache.get(path13.resolve(root, image.path));
11018
+ const hit = styleImageCache.get(path14.resolve(root, image.path));
10903
11019
  return { base64: hit.base64, width: hit.width, height: hit.height, format: hit.format };
10904
11020
  });
10905
11021
  const styleSpecs = /* @__PURE__ */ new Map();
@@ -11000,9 +11116,9 @@ async function resolveSpecs(loaded, filter) {
11000
11116
  } else {
11001
11117
  prompt = [style.promptPrefix, subject, style.promptSuffix].map((p) => p.trim()).filter(Boolean).join(", ");
11002
11118
  }
11003
- const relFile = asset.file ?? path13.join(asset.category ?? "", `${assetId}.png`);
11004
- const outFile = path13.resolve(root, style.outDir, relFile);
11005
- const qualityOutFile = style.quality ? path13.resolve(root, style.quality.outDir, pngPath(relFile)) : void 0;
11119
+ const relFile = asset.file ?? path14.join(asset.category ?? "", `${assetId}.png`);
11120
+ const outFile = path14.resolve(root, style.outDir, relFile);
11121
+ const qualityOutFile = style.quality ? path14.resolve(root, style.quality.outDir, pngPath(relFile)) : void 0;
11006
11122
  const tileSize = generator === "tiles" ? size : style.tileSize ?? 32;
11007
11123
  const tileVariations = tileFeatureOutputCount(generator === "tiles" ? style.tileFeature : void 0) ?? tileVariationCount(countNumberedDescriptions(prompt));
11008
11124
  const terrainTiles = generator === "terrain" ? terrainTileCount(style.terrainTransitionSize) : 0;
@@ -11097,7 +11213,7 @@ async function resolveSpecs(loaded, filter) {
11097
11213
  minGridConfidence: style.quality.minGridConfidence,
11098
11214
  ...style.quality.minTransparency == null ? {} : { minTransparency: style.quality.minTransparency },
11099
11215
  ...style.quality.fixerRevision ? { fixerRevision: style.quality.fixerRevision } : {},
11100
- ...style.quality.fixerPython ? { fixerPython: path13.resolve(root, style.quality.fixerPython) } : {},
11216
+ ...style.quality.fixerPython ? { fixerPython: path14.resolve(root, style.quality.fixerPython) } : {},
11101
11217
  ...style.quality.fps == null ? {} : { fps: style.quality.fps }
11102
11218
  }
11103
11219
  } : {},
@@ -11233,7 +11349,7 @@ async function resolveSpecs(loaded, filter) {
11233
11349
  animation: sourceSpec.character.animation,
11234
11350
  outfit: {
11235
11351
  reference: {
11236
- path: path13.resolve(root, asset.outfit.reference),
11352
+ path: path14.resolve(root, asset.outfit.reference),
11237
11353
  sha256: referenceImage.hash,
11238
11354
  width: referenceImage.width,
11239
11355
  height: referenceImage.height,
@@ -11255,23 +11371,23 @@ async function resolveSpecs(loaded, filter) {
11255
11371
  );
11256
11372
  }
11257
11373
  const sourceSpec = await finalize(asset.revision.from);
11258
- const sourceStem = sourceSpec.quality?.outFile ?? (sourceSpec.source ? path13.resolve(root, sourceSpec.source) : sourceSpec.outFile);
11374
+ const sourceStem = sourceSpec.quality?.outFile ?? (sourceSpec.source ? path14.resolve(root, sourceSpec.source) : sourceSpec.outFile);
11259
11375
  const sourceMembers = await revisionSourceMembers(sourceStem, `revision source ${asset.revision.from}`);
11260
11376
  const sourceFile = sourceMembers?.[0]?.file ?? sourceStem;
11261
11377
  const sourceImage = sourceMembers ? { ...sourceMembers[0], hash: sha256(JSON.stringify(sourceMembers.map((member) => member.sha256))) } : await optionalRevisionImage(sourceFile, "revision source");
11262
11378
  const sourceFps = sourceSpec.character?.animation?.fps ?? sourceSpec.objectPro?.animation?.fps ?? sourceSpec.revision?.fps ?? sourceSpec.revision?.sourceFps;
11263
- const maskFile = asset.revision.mask ? path13.resolve(root, asset.revision.mask) : void 0;
11379
+ const maskFile = asset.revision.mask ? path14.resolve(root, asset.revision.mask) : void 0;
11264
11380
  const maskImage = maskFile ? await optionalRevisionImage(maskFile, "revision mask", "png") : null;
11265
11381
  if (sourceImage && maskImage && (sourceImage.width !== maskImage.width || sourceImage.height !== maskImage.height)) {
11266
11382
  throw new Error(
11267
11383
  `Revision mask for ${styleId}/${assetId} is ${maskImage.width}x${maskImage.height}; source ${asset.revision.from} is ${sourceImage.width}x${sourceImage.height}`
11268
11384
  );
11269
11385
  }
11270
- const paletteImageFile = asset.revision.paletteImage ? path13.resolve(root, asset.revision.paletteImage) : void 0;
11386
+ const paletteImageFile = asset.revision.paletteImage ? path14.resolve(root, asset.revision.paletteImage) : void 0;
11271
11387
  const paletteImage = paletteImageFile ? await optionalRevisionImage(paletteImageFile, "revision palette image", "png") : null;
11272
- const lastFrameFile = asset.revision.lastFrame ? path13.resolve(root, asset.revision.lastFrame) : void 0;
11388
+ const lastFrameFile = asset.revision.lastFrame ? path14.resolve(root, asset.revision.lastFrame) : void 0;
11273
11389
  const lastFrame = lastFrameFile ? await optionalRevisionImage(lastFrameFile, "revision last frame") : null;
11274
- const keypointsFile = asset.revision.keypointsFile ? path13.resolve(root, asset.revision.keypointsFile) : void 0;
11390
+ const keypointsFile = asset.revision.keypointsFile ? path14.resolve(root, asset.revision.keypointsFile) : void 0;
11275
11391
  const keypoints = keypointsFile ? await optionalSkeletonSet(keypointsFile) : null;
11276
11392
  resolved.revision = {
11277
11393
  mode: asset.revision.mode,
@@ -11438,7 +11554,7 @@ async function resolveSpecs(loaded, filter) {
11438
11554
  return specs;
11439
11555
  }
11440
11556
  function pngPath(file) {
11441
- const extension = path13.extname(file);
11557
+ const extension = path14.extname(file);
11442
11558
  return extension ? `${file.slice(0, -extension.length)}.png` : `${file}.png`;
11443
11559
  }
11444
11560
  async function resolveMapScene(root, styleId, assetId, generator, scene, provider) {
@@ -11448,12 +11564,12 @@ async function resolveMapScene(root, styleId, assetId, generator, scene, provide
11448
11564
  if (!provider.supportsMapScene) {
11449
11565
  throw new Error(`assets.${assetId}.scene: provider "${provider.id}" cannot draw into a scene; PixelLab's /map-objects can`);
11450
11566
  }
11451
- const file = path13.resolve(root, scene.image);
11567
+ const file = path14.resolve(root, scene.image);
11452
11568
  const image = await optionalRevisionImage(file, "scene image", "png");
11453
11569
  const base = { file, sha256: image?.hash ?? null, width: image?.width ?? null, height: image?.height ?? null };
11454
11570
  const placement = scene.placement;
11455
11571
  if ("mask" in placement) {
11456
- const maskFile = path13.resolve(root, placement.mask);
11572
+ const maskFile = path14.resolve(root, placement.mask);
11457
11573
  const mask = await optionalRevisionImage(maskFile, "scene mask", "png");
11458
11574
  if (image && mask && (image.width !== mask.width || image.height !== mask.height)) {
11459
11575
  throw new Error(
@@ -11525,7 +11641,7 @@ async function resolveStyleImages(loaded, styleId) {
11525
11641
  if (!style) throw new Error(`Unknown style "${styleId}"`);
11526
11642
  const out = [];
11527
11643
  for (const img of style.styleImages) {
11528
- const file = path13.resolve(loaded.root, img.path);
11644
+ const file = path14.resolve(loaded.root, img.path);
11529
11645
  const buf = await readFile6(file);
11530
11646
  const metadata = imageMetadata(buf);
11531
11647
  if (!metadata) throw new Error(`Style image is not a readable PNG or JPEG: ${file}`);
@@ -11553,7 +11669,7 @@ async function resolveCharacterShape(asset, style, kind, files) {
11553
11669
  };
11554
11670
  if (asset.concept) {
11555
11671
  const image = await files.load(asset.concept, "Concept image");
11556
- shape.concept = { path: path13.resolve(files.root, asset.concept), sha256: image.hash, width: image.width, height: image.height, format: image.format };
11672
+ shape.concept = { path: path14.resolve(files.root, asset.concept), sha256: image.hash, width: image.width, height: image.height, format: image.format };
11557
11673
  }
11558
11674
  if (asset.reference) {
11559
11675
  const byDirection = typeof asset.reference === "string" ? { south: asset.reference } : asset.reference;
@@ -11561,7 +11677,7 @@ async function resolveCharacterShape(asset, style, kind, files) {
11561
11677
  for (const [direction, rel] of Object.entries(byDirection)) {
11562
11678
  const image = await files.load(rel, `Reference sprite (${direction})`);
11563
11679
  shape.reference[direction] = {
11564
- path: path13.resolve(files.root, rel),
11680
+ path: path14.resolve(files.root, rel),
11565
11681
  sha256: image.hash,
11566
11682
  width: image.width,
11567
11683
  height: image.height,
@@ -11582,7 +11698,7 @@ async function resolveCharacterShape(asset, style, kind, files) {
11582
11698
  const animationMode = asset.animation.mode ?? (asset.animation.template ? "template" : "v3");
11583
11699
  const pose = async (rel, what) => {
11584
11700
  const image = await files.load(rel, what);
11585
- return { path: path13.resolve(files.root, rel), sha256: image.hash, width: image.width, height: image.height, format: image.format };
11701
+ return { path: path14.resolve(files.root, rel), sha256: image.hash, width: image.width, height: image.height, format: image.format };
11586
11702
  };
11587
11703
  shape.animation = {
11588
11704
  mode: animationMode,
@@ -11613,7 +11729,7 @@ async function resolveObjectProShape(assetId, asset, style, kind, files) {
11613
11729
  const south = typeof asset.reference === "string" ? asset.reference : asset.reference.south;
11614
11730
  if (south) {
11615
11731
  const image = await files.load(south, "Reference sprite");
11616
- shape.reference = { path: path13.resolve(files.root, south), sha256: image.hash, width: image.width, height: image.height, format: image.format };
11732
+ shape.reference = { path: path14.resolve(files.root, south), sha256: image.hash, width: image.width, height: image.height, format: image.format };
11617
11733
  }
11618
11734
  }
11619
11735
  if (asset.state) {
@@ -11641,7 +11757,7 @@ async function resolveObjectProShape(assetId, asset, style, kind, files) {
11641
11757
  }
11642
11758
  const pose = async (rel, what) => {
11643
11759
  const image = await files.load(rel, what);
11644
- return { path: path13.resolve(files.root, rel), sha256: image.hash, width: image.width, height: image.height, format: image.format };
11760
+ return { path: path14.resolve(files.root, rel), sha256: image.hash, width: image.width, height: image.height, format: image.format };
11645
11761
  };
11646
11762
  shape.animation = {
11647
11763
  mode: asset.animation.mode ?? "v3",
@@ -11658,9 +11774,9 @@ async function resolveObjectProShape(assetId, asset, style, kind, files) {
11658
11774
  }
11659
11775
 
11660
11776
  // src/pipeline/fetch.ts
11661
- import { existsSync as existsSync9, readFileSync as readFileSync3 } from "fs";
11777
+ import { existsSync as existsSync9, readFileSync as readFileSync4 } from "fs";
11662
11778
  import { mkdir as mkdir4, readFile as readFile7, rename as rename4, rm as rm4, writeFile as writeFile4 } from "fs/promises";
11663
- import path14 from "path";
11779
+ import path15 from "path";
11664
11780
 
11665
11781
  // src/source-url.ts
11666
11782
  var SENSITIVE_QUERY_KEYS = /* @__PURE__ */ new Set([
@@ -11706,7 +11822,7 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
11706
11822
  const result = { downloaded: 0, skipped: 0, failed: 0, ...opts.refresh ? { unchanged: 0 } : {} };
11707
11823
  const specByKey = new Map(specs.map((s) => [lockKey(s.styleId, s.assetId), s]));
11708
11824
  normalizeLockOutputPaths(lock, specs);
11709
- const cacheDir = opts.cacheDir === false ? null : path14.resolve(opts.cacheDir ?? path14.join(path14.dirname(lockPath), ".pixelkiln", "cache"));
11825
+ const cacheDir = opts.cacheDir === false ? null : path15.resolve(opts.cacheDir ?? path15.join(path15.dirname(lockPath), ".pixelkiln", "cache"));
11710
11826
  const pending = Object.entries(lock.entries).filter(([key, e]) => {
11711
11827
  if (e.provider !== provider.id || !specByKey.has(key)) return false;
11712
11828
  if (specByKey.get(key)?.mirror) return false;
@@ -11717,7 +11833,7 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
11717
11833
  if (spec && e.specHash === spec.specHash && e.outputs.length && !postprocessCurrent(e, spec)) {
11718
11834
  return Boolean(opts.force) || e.outputs.every((output) => {
11719
11835
  const file = resolveOutputPath(output.path, spec.root);
11720
- return existsSync9(file) && sha256(readFileSync3(file)) === output.sha256;
11836
+ return existsSync9(file) && sha256(readFileSync4(file)) === output.sha256;
11721
11837
  });
11722
11838
  }
11723
11839
  if (!opts.repair) return false;
@@ -11725,7 +11841,7 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
11725
11841
  return e.outputs.some((output) => {
11726
11842
  const file = resolveOutputPath(output.path, spec.root);
11727
11843
  if (!existsSync9(file)) return true;
11728
- const current = sha256(readFileSync3(file));
11844
+ const current = sha256(readFileSync4(file));
11729
11845
  if (current === output.sha256) return false;
11730
11846
  return Boolean(opts.force) || (e.supersededOutputs ?? []).some((old) => old.sha256 === current);
11731
11847
  });
@@ -11808,7 +11924,7 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
11808
11924
  if (!source.url) throw new Error(`no durable source URL to refresh ${source.role ?? "asset"} from`);
11809
11925
  fresh = await provider.download(source.url);
11810
11926
  if (recorded && sha256(fresh) === recordedRaw && !reapply) {
11811
- log2(` current ${path14.relative(process.cwd(), target)}`);
11927
+ log2(` current ${path15.relative(process.cwd(), target)}`);
11812
11928
  outputs.push({ ...recorded, path: portableOutputPath(target, spec.root) });
11813
11929
  continue;
11814
11930
  }
@@ -11832,9 +11948,9 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
11832
11948
  }
11833
11949
  if (reapply && !fresh) {
11834
11950
  if (!recorded.raw) raw = await readFile7(target);
11835
- log2(` reapply ${path14.relative(process.cwd(), target)}`);
11951
+ log2(` reapply ${path15.relative(process.cwd(), target)}`);
11836
11952
  } else {
11837
- log2(` changed ${path14.relative(process.cwd(), target)} (upstream)`);
11953
+ log2(` changed ${path15.relative(process.cwd(), target)} (upstream)`);
11838
11954
  }
11839
11955
  } else {
11840
11956
  const superseded = (entry.supersededOutputs ?? []).find(
@@ -11862,7 +11978,7 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
11862
11978
  }
11863
11979
  }
11864
11980
  log2(
11865
- ` replace ${path14.relative(process.cwd(), target)}` + (opts.force ? " (--force)" : " (previous tracked generation)")
11981
+ ` replace ${path15.relative(process.cwd(), target)}` + (opts.force ? " (--force)" : " (previous tracked generation)")
11866
11982
  );
11867
11983
  }
11868
11984
  }
@@ -11871,7 +11987,7 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
11871
11987
  } else if (!raw && recorded && cacheDir) {
11872
11988
  if (!reapply) final = await readCachedMedia(cacheDir, recorded.sha256, expectedMediaType);
11873
11989
  if (!final && recordedRaw) raw = await readCachedMedia(cacheDir, recordedRaw, expectedMediaType);
11874
- if (final || raw) log2(` cached ${path14.relative(process.cwd(), target)}`);
11990
+ if (final || raw) log2(` cached ${path15.relative(process.cwd(), target)}`);
11875
11991
  }
11876
11992
  if (!final && !raw) {
11877
11993
  if (!source.url) {
@@ -11898,11 +12014,11 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
11898
12014
  buf = processed.bytes;
11899
12015
  if (processed.changed) {
11900
12016
  rawHash = sha256(raw);
11901
- log2(` snapped ${path14.relative(process.cwd(), target)} to ${wanted.palette.colors.length} colours`);
12017
+ log2(` snapped ${path15.relative(process.cwd(), target)} to ${wanted.palette.colors.length} colours`);
11902
12018
  }
11903
12019
  }
11904
12020
  if (cacheDir) await cacheMedia(cacheDir, buf, mediaType);
11905
- await mkdir4(path14.dirname(target), { recursive: true });
12021
+ await mkdir4(path15.dirname(target), { recursive: true });
11906
12022
  const tmp = `${target}.pixelkiln.tmp`;
11907
12023
  await writeFile4(tmp, buf);
11908
12024
  outputs.push({
@@ -11916,7 +12032,7 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
11916
12032
  await saveLock(lockPath, lock);
11917
12033
  await rename4(tmp, target);
11918
12034
  wrote = true;
11919
- log2(` wrote ${path14.relative(process.cwd(), target)}`);
12035
+ log2(` wrote ${path15.relative(process.cwd(), target)}`);
11920
12036
  }
11921
12037
  if (opts.refresh && !wrote) {
11922
12038
  if (rotated) {
@@ -11960,7 +12076,7 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
11960
12076
  return result;
11961
12077
  }
11962
12078
  async function readCachedMedia(cacheDir, hash, mediaType) {
11963
- const file = path14.join(cacheDir, cacheFileName(hash, mediaType));
12079
+ const file = path15.join(cacheDir, cacheFileName(hash, mediaType));
11964
12080
  if (!existsSync9(file)) return null;
11965
12081
  try {
11966
12082
  const buf = await readFile7(file);
@@ -11974,7 +12090,7 @@ async function readCachedMedia(cacheDir, hash, mediaType) {
11974
12090
  async function cacheMedia(cacheDir, buf, mediaType, knownHash) {
11975
12091
  const hash = knownHash ?? sha256(buf);
11976
12092
  validateMedia(buf, mediaType);
11977
- const file = path14.join(cacheDir, cacheFileName(hash, mediaType));
12093
+ const file = path15.join(cacheDir, cacheFileName(hash, mediaType));
11978
12094
  if (await readCachedMedia(cacheDir, hash, mediaType)) return;
11979
12095
  await mkdir4(cacheDir, { recursive: true });
11980
12096
  const tmp = `${file}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
@@ -12017,7 +12133,7 @@ async function pushTags(provider, specs, lock, opts = {}) {
12017
12133
  // src/pipeline/adopt.ts
12018
12134
  import { existsSync as existsSync11 } from "fs";
12019
12135
  import { readFile as readFile9, writeFile as writeFile6 } from "fs/promises";
12020
- import path15 from "path";
12136
+ import path16 from "path";
12021
12137
 
12022
12138
  // src/cache.ts
12023
12139
  import { mkdir as mkdir5, readFile as readFile8, rename as rename5, rm as rm5, writeFile as writeFile5 } from "fs/promises";
@@ -12036,10 +12152,10 @@ function isSha256Hash(value) {
12036
12152
  function parseCache(value) {
12037
12153
  return HashCacheSchema.parse(value);
12038
12154
  }
12039
- async function loadCache(path51) {
12040
- if (!existsSync10(path51)) return { version: 1, hashes: {} };
12155
+ async function loadCache(path52) {
12156
+ if (!existsSync10(path52)) return { version: 1, hashes: {} };
12041
12157
  try {
12042
- const parsed = HashCacheSchema.safeParse(JSON.parse(await readFile8(path51, "utf8")));
12158
+ const parsed = HashCacheSchema.safeParse(JSON.parse(await readFile8(path52, "utf8")));
12043
12159
  if (!parsed.success) return { version: 1, hashes: {} };
12044
12160
  return {
12045
12161
  version: 1,
@@ -12051,18 +12167,18 @@ async function loadCache(path51) {
12051
12167
  return { version: 1, hashes: {} };
12052
12168
  }
12053
12169
  }
12054
- async function saveCache(path51, cache) {
12170
+ async function saveCache(path52, cache) {
12055
12171
  const sorted = {};
12056
12172
  for (const key of Object.keys(cache.hashes).sort()) {
12057
12173
  const hash = cache.hashes[key];
12058
12174
  if (!isSha256Hash(hash)) throw new Error(`Refusing to cache invalid SHA-256 for ${key}`);
12059
12175
  sorted[key] = hash;
12060
12176
  }
12061
- await mkdir5(pathModule.dirname(pathModule.resolve(path51)), { recursive: true });
12062
- const tmp = `${path51}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
12177
+ await mkdir5(pathModule.dirname(pathModule.resolve(path52)), { recursive: true });
12178
+ const tmp = `${path52}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
12063
12179
  try {
12064
12180
  await writeFile5(tmp, JSON.stringify({ version: 1, hashes: sorted }, null, 2) + "\n");
12065
- await rename5(tmp, path51);
12181
+ await rename5(tmp, path52);
12066
12182
  } finally {
12067
12183
  await rm5(tmp, { force: true });
12068
12184
  }
@@ -12255,7 +12371,7 @@ function formatUnmatchedRemote(objects, limit = 15) {
12255
12371
  // src/pipeline/adopt-characters.ts
12256
12372
  import { existsSync as existsSync12 } from "fs";
12257
12373
  import { mkdir as mkdir6, readFile as readFile10, writeFile as writeFile7 } from "fs/promises";
12258
- import path16 from "path";
12374
+ import path17 from "path";
12259
12375
  async function adoptCharacters(provider, specs, lock, lockPath, opts) {
12260
12376
  const log2 = opts.onProgress ?? (() => {
12261
12377
  });
@@ -12307,11 +12423,11 @@ async function adoptCharacters(provider, specs, lock, lockPath, opts) {
12307
12423
  const hash = sha256(bytes);
12308
12424
  const file = expectedOutputPath(spec, source.role, index, sources.length);
12309
12425
  if (existsSync12(file)) {
12310
- if (sha256(await readFile10(file)) !== hash) result.differing.push(path16.relative(process.cwd(), file));
12426
+ if (sha256(await readFile10(file)) !== hash) result.differing.push(path17.relative(process.cwd(), file));
12311
12427
  } else {
12312
- await mkdir6(path16.dirname(file), { recursive: true });
12428
+ await mkdir6(path17.dirname(file), { recursive: true });
12313
12429
  await writeFile7(file, bytes);
12314
- result.written.push(path16.relative(process.cwd(), file));
12430
+ result.written.push(path17.relative(process.cwd(), file));
12315
12431
  }
12316
12432
  outputs.push({ path: portableOutputPath(file, spec.root), sha256: hash, ...source.role ? { role: source.role } : {} });
12317
12433
  }
@@ -12350,7 +12466,7 @@ async function adoptCharacters(provider, specs, lock, lockPath, opts) {
12350
12466
  } else {
12351
12467
  const south = expectedOutputPath(spec, "south", 0, spec.character.directions);
12352
12468
  if (!existsSync12(south)) {
12353
- result.unmatched.push(`${key}: no remoteId and no ${path16.relative(process.cwd(), south)} to match by`);
12469
+ result.unmatched.push(`${key}: no remoteId and no ${path17.relative(process.cwd(), south)} to match by`);
12354
12470
  return false;
12355
12471
  }
12356
12472
  const localHash = sha256(await readFile10(south));
@@ -12362,7 +12478,7 @@ async function adoptCharacters(provider, specs, lock, lockPath, opts) {
12362
12478
  }
12363
12479
  }
12364
12480
  if (!match) {
12365
- result.unmatched.push(`${key}: no character's south rotation has the bytes of ${path16.relative(process.cwd(), south)}`);
12481
+ result.unmatched.push(`${key}: no character's south rotation has the bytes of ${path17.relative(process.cwd(), south)}`);
12366
12482
  return false;
12367
12483
  }
12368
12484
  }
@@ -12432,7 +12548,7 @@ async function adoptCharacters(provider, specs, lock, lockPath, opts) {
12432
12548
  if (!existsSync12(first)) {
12433
12549
  const byName = candidates.find((a) => a.name === characterAnimationName(spec));
12434
12550
  if (!byName) {
12435
- result.unmatched.push(`${key}: no remoteId and no ${path16.relative(process.cwd(), first)} to match by`);
12551
+ result.unmatched.push(`${key}: no remoteId and no ${path17.relative(process.cwd(), first)} to match by`);
12436
12552
  return false;
12437
12553
  }
12438
12554
  match = byName;
@@ -12445,7 +12561,7 @@ async function adoptCharacters(provider, specs, lock, lockPath, opts) {
12445
12561
  }
12446
12562
  }
12447
12563
  if (!match) {
12448
- result.unmatched.push(`${key}: no ${animation.direction} animation of character ${parentId} starts with the bytes of ${path16.relative(process.cwd(), first)}`);
12564
+ result.unmatched.push(`${key}: no ${animation.direction} animation of character ${parentId} starts with the bytes of ${path17.relative(process.cwd(), first)}`);
12449
12565
  return false;
12450
12566
  }
12451
12567
  }
@@ -12507,7 +12623,7 @@ async function adoptCharacters(provider, specs, lock, lockPath, opts) {
12507
12623
  // src/pipeline/salvage.ts
12508
12624
  import { readFile as readFile11 } from "fs/promises";
12509
12625
  import { existsSync as existsSync13 } from "fs";
12510
- import path17 from "path";
12626
+ import path18 from "path";
12511
12627
  function providerClaimId(provider, objectId) {
12512
12628
  return `${encodeURIComponent(provider)}:${encodeURIComponent(objectId)}`;
12513
12629
  }
@@ -12684,20 +12800,20 @@ function matchStyleByPattern(prompt, manifest) {
12684
12800
  return null;
12685
12801
  }
12686
12802
  async function loadSiblingManifests(ownManifestPath, workspaceManifestPaths, claimPaths) {
12687
- const own = path17.resolve(ownManifestPath);
12803
+ const own = path18.resolve(ownManifestPath);
12688
12804
  const siblingManifestPaths = [
12689
12805
  .../* @__PURE__ */ new Set([
12690
12806
  ...workspaceManifestPaths,
12691
- ...claimPaths.map((c) => path17.join(path17.dirname(path17.resolve(c)), "pixelkiln.manifest.json"))
12807
+ ...claimPaths.map((c) => path18.join(path18.dirname(path18.resolve(c)), "pixelkiln.manifest.json"))
12692
12808
  ])
12693
12809
  ];
12694
12810
  const siblings = [];
12695
12811
  for (const siblingManifestPath of siblingManifestPaths) {
12696
- if (path17.resolve(siblingManifestPath) === own) continue;
12812
+ if (path18.resolve(siblingManifestPath) === own) continue;
12697
12813
  if (!existsSync13(siblingManifestPath)) continue;
12698
12814
  try {
12699
12815
  const { manifest } = await loadManifest(siblingManifestPath);
12700
- siblings.push({ label: path17.basename(path17.dirname(siblingManifestPath)), manifest });
12816
+ siblings.push({ label: path18.basename(path18.dirname(siblingManifestPath)), manifest });
12701
12817
  } catch {
12702
12818
  }
12703
12819
  }
@@ -12819,7 +12935,7 @@ async function applyTags(provider, decisions, existing, opts = {}) {
12819
12935
  // src/workspace.ts
12820
12936
  import { mkdir as mkdir7, readFile as readFile12, rename as rename6, rm as rm6, writeFile as writeFile8 } from "fs/promises";
12821
12937
  import { existsSync as existsSync14 } from "fs";
12822
- import path18 from "path";
12938
+ import path19 from "path";
12823
12939
  import { z as z5 } from "zod";
12824
12940
  var WorkspaceProjectSchema = z5.object({
12825
12941
  id: z5.string().min(1),
@@ -12861,7 +12977,7 @@ async function saveWorkspace(workspacePath, ws) {
12861
12977
  version: 1,
12862
12978
  projects: [...ws.projects].sort((a, b) => a.id.localeCompare(b.id))
12863
12979
  };
12864
- await mkdir7(path18.dirname(path18.resolve(workspacePath)), { recursive: true });
12980
+ await mkdir7(path19.dirname(path19.resolve(workspacePath)), { recursive: true });
12865
12981
  const tmp = `${workspacePath}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
12866
12982
  try {
12867
12983
  await writeFile8(tmp, JSON.stringify(sorted, null, 2) + "\n");
@@ -12871,12 +12987,12 @@ async function saveWorkspace(workspacePath, ws) {
12871
12987
  }
12872
12988
  }
12873
12989
  function toPortablePath(dir, absolute) {
12874
- return path18.relative(dir, absolute).split(path18.sep).join("/");
12990
+ return path19.relative(dir, absolute).split(path19.sep).join("/");
12875
12991
  }
12876
12992
  function resolveProject(dir, project) {
12877
12993
  return {
12878
- manifestPath: path18.resolve(dir, project.manifest.split("/").join(path18.sep)),
12879
- lockPath: path18.resolve(dir, project.lock.split("/").join(path18.sep))
12994
+ manifestPath: path19.resolve(dir, project.manifest.split("/").join(path19.sep)),
12995
+ lockPath: path19.resolve(dir, project.lock.split("/").join(path19.sep))
12880
12996
  };
12881
12997
  }
12882
12998
  function validateWorkspace(ws, dir) {
@@ -12889,7 +13005,7 @@ function validateWorkspace(ws, dir) {
12889
13005
  const { manifestPath, lockPath } = resolveProject(dir, project);
12890
13006
  lockOwners.set(lockPath, [...lockOwners.get(lockPath) ?? [], project.id]);
12891
13007
  manifestOwners.set(manifestPath, [...manifestOwners.get(manifestPath) ?? [], project.id]);
12892
- if (path18.isAbsolute(project.manifest) || path18.isAbsolute(project.lock)) {
13008
+ if (path19.isAbsolute(project.manifest) || path19.isAbsolute(project.lock)) {
12893
13009
  diagnostics.push({
12894
13010
  id: "absolute-path",
12895
13011
  level: "warning",
@@ -12951,7 +13067,7 @@ function validateWorkspace(ws, dir) {
12951
13067
 
12952
13068
  // src/pick/salvage-server.ts
12953
13069
  import { mkdir as mkdir8, writeFile as writeFile9, readFile as readFile14 } from "fs/promises";
12954
- import path19 from "path";
13070
+ import path20 from "path";
12955
13071
 
12956
13072
  // src/pick/salvage-sheet.ts
12957
13073
  var escapeHtml = (s) => s.replace(/[&<>"']/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[c]);
@@ -13216,7 +13332,7 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
13216
13332
  });
13217
13333
  const html = renderSalvageSheet(orphans, {
13218
13334
  styleId: ctx.styleId,
13219
- importDir: path19.relative(process.cwd(), ctx.importDir) || "."
13335
+ importDir: path20.relative(process.cwd(), ctx.importDir) || "."
13220
13336
  });
13221
13337
  const byId = new Map(orphans.map((o) => [o.id, o]));
13222
13338
  const existingTags = new Map(orphans.map((o) => [o.id, o.tags]));
@@ -13272,9 +13388,9 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
13272
13388
  if (!buf.subarray(0, 8).equals(PNG_SIGNATURE2)) throw new Error("not a PNG");
13273
13389
  decodePng(buf);
13274
13390
  const assetId = idFromPrompt(orphan.prompt, taken);
13275
- const rel = path19.join("_salvaged", `${assetId}.png`);
13276
- const outFile = path19.resolve(ctx.importDir, rel);
13277
- await mkdir8(path19.dirname(outFile), { recursive: true });
13391
+ const rel = path20.join("_salvaged", `${assetId}.png`);
13392
+ const outFile = path20.resolve(ctx.importDir, rel);
13393
+ await mkdir8(path20.dirname(outFile), { recursive: true });
13278
13394
  await writeFile9(outFile, buf);
13279
13395
  ctx.manifest.assets[assetId] = {
13280
13396
  prompt: orphan.prompt,
@@ -13307,7 +13423,7 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
13307
13423
  sourceUrl: durableSource,
13308
13424
  sourceUrls: durableSource ? [{ url: durableSource }] : [],
13309
13425
  outputs: [{
13310
- path: portableOutputPath(outFile, path19.dirname(ctx.manifestPath)),
13426
+ path: portableOutputPath(outFile, path20.dirname(ctx.manifestPath)),
13311
13427
  sha256: sha256(buf)
13312
13428
  }],
13313
13429
  submittedAt: orphan.createdAt,
@@ -13342,19 +13458,19 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
13342
13458
  }
13343
13459
 
13344
13460
  // src/cli/project.ts
13345
- import path21 from "path";
13461
+ import path22 from "path";
13346
13462
  import { existsSync as existsSync15 } from "fs";
13347
13463
 
13348
13464
  // src/project.ts
13349
- import path20 from "path";
13465
+ import path21 from "path";
13350
13466
  async function openProject(manifestPath = "pixelkiln.manifest.json", opts = {}) {
13351
- const absolute = path20.resolve(manifestPath);
13352
- const root = path20.dirname(absolute);
13467
+ const absolute = path21.resolve(manifestPath);
13468
+ const root = path21.dirname(absolute);
13353
13469
  if (opts.env !== false) {
13354
13470
  loadEnvFiles(root);
13355
- if (path20.resolve(process.cwd()) !== root) loadEnvFiles(process.cwd());
13471
+ if (path21.resolve(process.cwd()) !== root) loadEnvFiles(process.cwd());
13356
13472
  }
13357
- const lockPath = path20.resolve(opts.lockPath ?? path20.join(root, "pixelkiln.lock.json"));
13473
+ const lockPath = path21.resolve(opts.lockPath ?? path21.join(root, "pixelkiln.lock.json"));
13358
13474
  const loaded = await loadManifest(absolute);
13359
13475
  const specs = await resolveSpecs(loaded, { styles: opts.styles ?? [], assets: opts.assets ?? [] });
13360
13476
  const lock = await loadLock(lockPath);
@@ -13485,7 +13601,7 @@ async function workspaceStatus(ws, dir) {
13485
13601
 
13486
13602
  // src/cli/project.ts
13487
13603
  async function provenanceFile(id, file) {
13488
- const absolute = path21.resolve(file);
13604
+ const absolute = path22.resolve(file);
13489
13605
  return {
13490
13606
  id,
13491
13607
  path: absolute,
@@ -13564,7 +13680,7 @@ async function requireCompleteWorkspaceClaims(workspacePath) {
13564
13680
  if (!existsSync15(workspacePath)) {
13565
13681
  throw new ProjectError(`Workspace catalog not found: ${workspacePath}`);
13566
13682
  }
13567
- const dir = path21.dirname(path21.resolve(workspacePath));
13683
+ const dir = path22.dirname(path22.resolve(workspacePath));
13568
13684
  const ws = await loadWorkspace(workspacePath);
13569
13685
  const diagnostics = validateWorkspace(ws, dir);
13570
13686
  const errors = diagnostics.filter((d) => d.level === "error");
@@ -13587,9 +13703,9 @@ async function openAccountProject(args) {
13587
13703
  return project;
13588
13704
  }
13589
13705
  async function open(args) {
13590
- if (!existsSync15(path21.resolve(args.manifest))) {
13706
+ if (!existsSync15(path22.resolve(args.manifest))) {
13591
13707
  throw new ProjectError(
13592
- `No manifest at ${path21.resolve(args.manifest)}. Pass --manifest, or run \`pixelkiln init --from <dir>\`.`
13708
+ `No manifest at ${path22.resolve(args.manifest)}. Pass --manifest, or run \`pixelkiln init --from <dir>\`.`
13593
13709
  );
13594
13710
  }
13595
13711
  const project = await openProject(args.manifest, { lockPath: args.lock, styles: args.styles, assets: args.assets });
@@ -13631,7 +13747,7 @@ function providerCache(mode2) {
13631
13747
 
13632
13748
  // src/cli/commands/account.ts
13633
13749
  async function runBalance(args) {
13634
- loadEnvFiles(path22.dirname(path22.resolve(args.manifest)));
13750
+ loadEnvFiles(path23.dirname(path23.resolve(args.manifest)));
13635
13751
  loadEnvFiles(process.cwd());
13636
13752
  const loaded = await loadManifest(args.manifest);
13637
13753
  const selectedProvider = accountProviderId(loaded.manifest, args.provider, "balance");
@@ -13704,12 +13820,12 @@ async function runAdopt(args) {
13704
13820
  tagged ${n} object(s) upstream`);
13705
13821
  }
13706
13822
  if (args.writePrompts) {
13707
- const { filled, stillEmpty } = await writePromptsBack(path22.resolve(args.manifest), lock, {
13823
+ const { filled, stillEmpty } = await writePromptsBack(path23.resolve(args.manifest), lock, {
13708
13824
  onProgress: log,
13709
13825
  provider: provider.id,
13710
13826
  assetIds: specs.map((spec) => spec.assetId)
13711
13827
  });
13712
- log(` recovered ${filled} prompt(s) into ${path22.relative(process.cwd(), args.manifest)}`);
13828
+ log(` recovered ${filled} prompt(s) into ${path23.relative(process.cwd(), args.manifest)}`);
13713
13829
  const reloaded = await loadManifest(args.manifest);
13714
13830
  const rebased = (await resolveSpecs(reloaded, {
13715
13831
  styles: args.styles,
@@ -13739,12 +13855,12 @@ async function runSalvage2(args) {
13739
13855
  const provider = providerFor(accountProvider);
13740
13856
  const jsonMode = args.dryRun && args.json;
13741
13857
  const diag = jsonMode ? (msg = "") => console.error(msg) : log;
13742
- const ownLock = path22.resolve(args.lock);
13858
+ const ownLock = path23.resolve(args.lock);
13743
13859
  let workspaceProjects = [];
13744
13860
  let workspaceDir = "";
13745
13861
  if (args.workspace) {
13746
- const workspacePath = path22.resolve(args.workspace);
13747
- workspaceDir = path22.dirname(workspacePath);
13862
+ const workspacePath = path23.resolve(args.workspace);
13863
+ workspaceDir = path23.dirname(workspacePath);
13748
13864
  const complete = await requireCompleteWorkspaceClaims(workspacePath);
13749
13865
  workspaceProjects = complete.ws.projects;
13750
13866
  for (const d of complete.diagnostics) diag(` WARN ${d.id}: ${d.message}`);
@@ -13754,11 +13870,11 @@ async function runSalvage2(args) {
13754
13870
  .../* @__PURE__ */ new Set([
13755
13871
  ...workspaceLockPaths,
13756
13872
  ...existsSync16(ownLock) ? [ownLock] : [],
13757
- ...args.claims.map((c) => path22.resolve(c))
13873
+ ...args.claims.map((c) => path23.resolve(c))
13758
13874
  ])
13759
13875
  ];
13760
13876
  diag(` claim set (${lockPaths.length} lockfile(s)):`);
13761
- for (const p of lockPaths) diag(` ${path22.relative(process.cwd(), p)}`);
13877
+ for (const p of lockPaths) diag(` ${path23.relative(process.cwd(), p)}`);
13762
13878
  if (!args.claims.length && !args.workspace) {
13763
13879
  diag(
13764
13880
  `
@@ -13766,7 +13882,7 @@ async function runSalvage2(args) {
13766
13882
  pass every other project's lockfile via --claims a.json,b.json, or
13767
13883
  register every project in a workspace catalog and pass --workspace.`
13768
13884
  );
13769
- } else if (args.workspace && !workspaceProjects.some((p) => resolveProject(workspaceDir, p).manifestPath === path22.resolve(args.manifest))) {
13885
+ } else if (args.workspace && !workspaceProjects.some((p) => resolveProject(workspaceDir, p).manifestPath === path23.resolve(args.manifest))) {
13770
13886
  diag(
13771
13887
  `
13772
13888
  This project's manifest is not registered in the workspace catalog. Its own
@@ -13885,7 +14001,7 @@ async function runSalvage2(args) {
13885
14001
  manifestPath: loaded.path,
13886
14002
  manifest: accountManifest,
13887
14003
  styleId,
13888
- importDir: path22.resolve(loaded.root, style.outDir),
14004
+ importDir: path23.resolve(loaded.root, style.outDir),
13889
14005
  lock,
13890
14006
  lockPath: args.lock
13891
14007
  },
@@ -14012,20 +14128,20 @@ async function runTag(args) {
14012
14128
  }
14013
14129
 
14014
14130
  // src/cli/commands/edit.ts
14015
- import path25 from "path";
14131
+ import path26 from "path";
14016
14132
 
14017
14133
  // src/pipeline/hand-edit.ts
14018
14134
  import { existsSync as existsSync17 } from "fs";
14019
14135
  import { copyFile, mkdir as mkdir9, readFile as readFile16, rename as rename8, rm as rm8, writeFile as writeFile11 } from "fs/promises";
14020
- import path24 from "path";
14136
+ import path25 from "path";
14021
14137
  import { z as z7 } from "zod";
14022
14138
 
14023
14139
  // src/manifest-edit.ts
14024
14140
  import { readFile as readFile15, rename as rename7, rm as rm7, writeFile as writeFile10 } from "fs/promises";
14025
- import path23 from "path";
14141
+ import path24 from "path";
14026
14142
  import { z as z6 } from "zod";
14027
14143
  var HexSha = z6.string().regex(/^[0-9a-f]{64}$/);
14028
- var ProjectFileSchema = z6.string().min(1).refine((file) => !path23.isAbsolute(file) && !file.split(/[\\/]/).includes(".."), "must be a path inside the project");
14144
+ var ProjectFileSchema = z6.string().min(1).refine((file) => !path24.isAbsolute(file) && !file.split(/[\\/]/).includes(".."), "must be a path inside the project");
14029
14145
  var AssetPatchSchema = z6.object({
14030
14146
  prompt: z6.string().optional(),
14031
14147
  /** Per-style override for the style being viewed; null removes it. */
@@ -14444,7 +14560,7 @@ function applyEdit(raw, edit) {
14444
14560
  }
14445
14561
  }
14446
14562
  async function withCandidate(manifestPath, edit, use, unchanged) {
14447
- const absolute = path23.resolve(manifestPath);
14563
+ const absolute = path24.resolve(manifestPath);
14448
14564
  const original = await readFile15(absolute, "utf8");
14449
14565
  if (sha256(original) !== edit.expectedSha256) throw new ManifestDriftError(absolute);
14450
14566
  let raw;
@@ -14458,9 +14574,9 @@ async function withCandidate(manifestPath, edit, use, unchanged) {
14458
14574
  applyEdit(raw, edit);
14459
14575
  const next = serializeLike(original, raw);
14460
14576
  if (next === original) return unchanged(absolute, original);
14461
- const tmp = path23.join(
14462
- path23.dirname(absolute),
14463
- `.${path23.basename(absolute)}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`
14577
+ const tmp = path24.join(
14578
+ path24.dirname(absolute),
14579
+ `.${path24.basename(absolute)}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`
14464
14580
  );
14465
14581
  try {
14466
14582
  await writeFile10(tmp, next);
@@ -14533,11 +14649,11 @@ async function priceManifestEdit(manifestPath, edit, opts = {}) {
14533
14649
  var HAND_EDIT_DIR = "edits";
14534
14650
  function handEditPath(loaded, spec) {
14535
14651
  const style = loaded.manifest.styles[spec.styleId];
14536
- const outDir = path24.resolve(loaded.root, style?.outDir ?? ".");
14537
- const relative = path24.relative(outDir, spec.outFile);
14538
- const inside = relative && !relative.startsWith("..") && !path24.isAbsolute(relative);
14539
- const file = inside ? relative : path24.basename(spec.outFile);
14540
- return path24.join(outDir, HAND_EDIT_DIR, file.replace(/\.gif$/i, ".png"));
14652
+ const outDir = path25.resolve(loaded.root, style?.outDir ?? ".");
14653
+ const relative = path25.relative(outDir, spec.outFile);
14654
+ const inside = relative && !relative.startsWith("..") && !path25.isAbsolute(relative);
14655
+ const file = inside ? relative : path25.basename(spec.outFile);
14656
+ return path25.join(outDir, HAND_EDIT_DIR, file.replace(/\.gif$/i, ".png"));
14541
14657
  }
14542
14658
  function handEditBases(spec, lock) {
14543
14659
  const entry = lock.entries[lockKey(spec.styleId, spec.assetId)];
@@ -14579,7 +14695,7 @@ async function startHandEdit(loaded, lock, spec, opts = {}) {
14579
14695
  `${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`
14580
14696
  );
14581
14697
  }
14582
- const existing = spec.source ? path24.resolve(loaded.root, spec.source) : null;
14698
+ const existing = spec.source ? path25.resolve(loaded.root, spec.source) : null;
14583
14699
  const editPath = existing ?? handEditPath(loaded, spec);
14584
14700
  const members = handEditMembers(loaded, spec, lock, editPath);
14585
14701
  let created = false;
@@ -14590,7 +14706,7 @@ async function startHandEdit(loaded, lock, spec, opts = {}) {
14590
14706
  `${spec.styleId}/${spec.assetId} has no generated PNG on disk to start from; generate or restore it first`
14591
14707
  );
14592
14708
  }
14593
- await mkdir9(path24.dirname(member.path), { recursive: true });
14709
+ await mkdir9(path25.dirname(member.path), { recursive: true });
14594
14710
  await copyFile(member.base, member.path);
14595
14711
  created = true;
14596
14712
  }
@@ -14728,7 +14844,7 @@ async function saveHandEdit(loaded, lock, spec, input) {
14728
14844
  editor: input.editor,
14729
14845
  protocol: input.protocol,
14730
14846
  savedAt: (input.now ?? /* @__PURE__ */ new Date()).toISOString(),
14731
- project: projectPath ? path24.basename(projectPath) : null,
14847
+ project: projectPath ? path25.basename(projectPath) : null,
14732
14848
  outputs: incoming.map((frame, index) => ({
14733
14849
  role: frame.role,
14734
14850
  basedOn: basedOnHash(entry, index),
@@ -14788,7 +14904,7 @@ async function runEdit(args) {
14788
14904
  const set = started.members.length > 1;
14789
14905
  const kind = spec.generator === "frames" ? "frames" : "members";
14790
14906
  log(` ${started.created ? "created" : "found"} ${started.source}${set ? ` (${started.members.length} ${kind})` : ""}${started.declared ? " and declared it as the asset's source" : ""}`);
14791
- if (set) for (const member of started.members) log(` ${member.role ?? ""} ${path25.relative(process.cwd(), member.path)}`);
14907
+ if (set) for (const member of started.members) log(` ${member.role ?? ""} ${path26.relative(process.cwd(), member.path)}`);
14792
14908
  if (args.noOpen) {
14793
14909
  log(` edit ${set ? "them" : "it"} with your own tool, then run pixelkiln plan; mount and pack place ${set ? "them" : "it"} in place of the generated art`);
14794
14910
  } else {
@@ -14798,24 +14914,24 @@ async function runEdit(args) {
14798
14914
  }
14799
14915
 
14800
14916
  // src/cli/commands/gallery.ts
14801
- import path35 from "path";
14917
+ import path36 from "path";
14802
14918
  import { existsSync as existsSync27 } from "fs";
14803
14919
 
14804
14920
  // src/gallery/snapshot.ts
14805
- import { createHash as createHash3 } from "crypto";
14921
+ import { createHash as createHash4 } from "crypto";
14806
14922
  import { existsSync as existsSync22 } from "fs";
14807
14923
  import { readFile as readFile20, stat as stat2 } from "fs/promises";
14808
- import path31 from "path";
14924
+ import path32 from "path";
14809
14925
 
14810
14926
  // src/gallery/edit.ts
14811
14927
  import { readFile as readFile19, rename as rename9, writeFile as writeFile15 } from "fs/promises";
14812
- import path29 from "path";
14928
+ import path30 from "path";
14813
14929
  import { z as z11 } from "zod";
14814
14930
 
14815
14931
  // src/gallery/upload.ts
14816
14932
  import { existsSync as existsSync18 } from "fs";
14817
14933
  import { mkdir as mkdir10, writeFile as writeFile12 } from "fs/promises";
14818
- import path26 from "path";
14934
+ import path27 from "path";
14819
14935
  import { z as z8 } from "zod";
14820
14936
  var MAX_UPLOAD_BYTES = 8 * 1024 * 1024;
14821
14937
  var UploadImageSchema = z8.object({
@@ -14828,12 +14944,12 @@ var UploadImageSchema = z8.object({
14828
14944
  overwrite: z8.boolean().optional()
14829
14945
  }).strict();
14830
14946
  async function saveProjectImage(root, request) {
14831
- const file = path26.resolve(root, request.path);
14832
- const relative = path26.relative(root, file);
14833
- if (relative.startsWith("..") || path26.isAbsolute(relative)) {
14947
+ const file = path27.resolve(root, request.path);
14948
+ const relative = path27.relative(root, file);
14949
+ if (relative.startsWith("..") || path27.isAbsolute(relative)) {
14834
14950
  throw new ManifestEditError("an upload must stay inside the project");
14835
14951
  }
14836
- const extension = path26.extname(file).toLowerCase();
14952
+ const extension = path27.extname(file).toLowerCase();
14837
14953
  const expected = extension === ".png" ? "png" : extension === ".jpg" || extension === ".jpeg" ? "jpeg" : null;
14838
14954
  if (!expected) throw new ManifestEditError("an upload must be a .png, .jpg, or .jpeg path");
14839
14955
  const bytes = Buffer.from(request.base64, "base64");
@@ -14846,17 +14962,17 @@ async function saveProjectImage(root, request) {
14846
14962
  throw new ManifestEditError(`the upload is a ${metadata.format.toUpperCase()}; name it ${metadata.format === "png" ? ".png" : ".jpg"}`);
14847
14963
  }
14848
14964
  if (existsSync18(file) && !request.overwrite) {
14849
- throw new ManifestEditError(`${relative.split(path26.sep).join("/")} already exists; choose another name, or replace it`);
14965
+ throw new ManifestEditError(`${relative.split(path27.sep).join("/")} already exists; choose another name, or replace it`);
14850
14966
  }
14851
- await mkdir10(path26.dirname(file), { recursive: true });
14967
+ await mkdir10(path27.dirname(file), { recursive: true });
14852
14968
  await writeFile12(file, bytes);
14853
- return relative.split(path26.sep).join("/");
14969
+ return relative.split(path27.sep).join("/");
14854
14970
  }
14855
14971
 
14856
14972
  // src/gallery/image-revision.ts
14857
14973
  import { existsSync as existsSync19 } from "fs";
14858
14974
  import { readFile as readFile17, rm as rm9, writeFile as writeFile13 } from "fs/promises";
14859
- import path27 from "path";
14975
+ import path28 from "path";
14860
14976
  import { z as z9 } from "zod";
14861
14977
  var InpaintRevisionRequestSchema = z9.object({
14862
14978
  action: z9.literal("create-inpaint-revision"),
@@ -14875,14 +14991,14 @@ var InpaintRevisionRequestSchema = z9.object({
14875
14991
  overwrite: z9.boolean().optional()
14876
14992
  }).strict();
14877
14993
  async function createInpaintRevision(manifestPath, request) {
14878
- const root = path27.dirname(path27.resolve(manifestPath));
14879
- if (path27.extname(request.maskPath).toLowerCase() !== ".png") {
14994
+ const root = path28.dirname(path28.resolve(manifestPath));
14995
+ if (path28.extname(request.maskPath).toLowerCase() !== ".png") {
14880
14996
  throw new ManifestEditError("the mask must be a .png path inside the project");
14881
14997
  }
14882
14998
  const bytes = Buffer.from(request.maskBase64, "base64");
14883
14999
  const metadata = imageMetadata(bytes);
14884
15000
  if (!metadata || metadata.format !== "png") throw new ManifestEditError("the mask is not a readable PNG");
14885
- const file = path27.resolve(root, request.maskPath);
15001
+ const file = path28.resolve(root, request.maskPath);
14886
15002
  const previous = existsSync19(file) ? await readFile17(file) : null;
14887
15003
  const mask = await saveProjectImage(root, {
14888
15004
  action: "upload-image",
@@ -14912,7 +15028,7 @@ async function createInpaintRevision(manifestPath, request) {
14912
15028
  // src/gallery/skeleton.ts
14913
15029
  import { existsSync as existsSync20 } from "fs";
14914
15030
  import { mkdir as mkdir11, readFile as readFile18, rm as rm10, writeFile as writeFile14 } from "fs/promises";
14915
- import path28 from "path";
15031
+ import path29 from "path";
14916
15032
  import { z as z10 } from "zod";
14917
15033
  var SkeletonEstimateRequestSchema = z10.object({
14918
15034
  project: z10.string().min(1).optional(),
@@ -14951,7 +15067,7 @@ var SkeletonKeypointsUpdateSchema = z10.object({
14951
15067
  }).strict();
14952
15068
  var DEFAULT_ESTIMATE_LIMIT = 10;
14953
15069
  function skeletonSourceFile(spec) {
14954
- if (spec.source) return path28.resolve(spec.root, spec.source);
15070
+ if (spec.source) return path29.resolve(spec.root, spec.source);
14955
15071
  if (spec.quality && existsSync20(spec.quality.outFile)) return spec.quality.outFile;
14956
15072
  return spec.outFile;
14957
15073
  }
@@ -14976,7 +15092,7 @@ function createGallerySkeletonHandlers(opts) {
14976
15092
  if (!existsSync20(file)) throw new ManifestEditError(`${key} has no image on disk to estimate from`);
14977
15093
  const bytes = await readFile18(file);
14978
15094
  const metadata = imageMetadata(bytes);
14979
- if (!metadata) throw new ManifestEditError(`${path28.basename(file)} is not a readable PNG or JPEG`);
15095
+ if (!metadata) throw new ManifestEditError(`${path29.basename(file)} is not a readable PNG or JPEG`);
14980
15096
  const problem = estimateSkeletonSizeProblem(metadata.width, metadata.height);
14981
15097
  if (problem) throw new ManifestEditError(`${key} is ${problem}`);
14982
15098
  if (used >= limit) {
@@ -14993,7 +15109,7 @@ function createGallerySkeletonHandlers(opts) {
14993
15109
  log2(` estimated a skeleton for ${key} (PixelLab estimate-skeleton, ${used}/${limit} this session${billed ? `, billed ${billed.amount} ${billed.unit}` : ""})`);
14994
15110
  return {
14995
15111
  set: scaffoldSkeletonSet(res.keypoints, request.frames ?? DEFAULT_SCAFFOLD_FRAMES),
14996
- source: path28.relative(ctx.loaded.root, file).split(path28.sep).join("/"),
15112
+ source: path29.relative(ctx.loaded.root, file).split(path29.sep).join("/"),
14997
15113
  session: status()
14998
15114
  };
14999
15115
  }
@@ -15007,10 +15123,10 @@ function billedAmount(usage) {
15007
15123
  return typeof amount === "number" && Number.isFinite(amount) ? { unit: type, amount } : null;
15008
15124
  }
15009
15125
  async function createSkeletonAnimation(manifestPath, request) {
15010
- const root = path28.dirname(path28.resolve(manifestPath));
15011
- const file = path28.resolve(root, request.keypointsFile);
15012
- const relative = path28.relative(root, file);
15013
- if (!request.keypointsFile.endsWith(".json") || relative.startsWith("..") || path28.isAbsolute(relative)) {
15126
+ const root = path29.dirname(path29.resolve(manifestPath));
15127
+ const file = path29.resolve(root, request.keypointsFile);
15128
+ const relative = path29.relative(root, file);
15129
+ if (!request.keypointsFile.endsWith(".json") || relative.startsWith("..") || path29.isAbsolute(relative)) {
15014
15130
  throw new ManifestEditError("the keypoints file must be a .json path inside the project");
15015
15131
  }
15016
15132
  const existed = existsSync20(file);
@@ -15020,7 +15136,7 @@ async function createSkeletonAnimation(manifestPath, request) {
15020
15136
  throw new ManifestEditError(`${request.keypointsFile} already exists; choose another path, or replace it`);
15021
15137
  }
15022
15138
  if (existed) previous = await readFile18(file);
15023
- await mkdir11(path28.dirname(file), { recursive: true });
15139
+ await mkdir11(path29.dirname(file), { recursive: true });
15024
15140
  await writeFile14(file, JSON.stringify(request.set, null, 2) + "\n");
15025
15141
  } else if (!existed) {
15026
15142
  throw new ManifestEditError(`${request.keypointsFile} does not exist; paste poses or estimate them from the sprite`);
@@ -15036,7 +15152,7 @@ async function createSkeletonAnimation(manifestPath, request) {
15036
15152
  revision: {
15037
15153
  mode: "animate-skeleton",
15038
15154
  from: request.from,
15039
- keypointsFile: relative.split(path28.sep).join("/"),
15155
+ keypointsFile: relative.split(path29.sep).join("/"),
15040
15156
  direction: request.direction,
15041
15157
  ...request.description ? { description: request.description } : {},
15042
15158
  ...request.skeletonTemplate ? { skeletonTemplate: request.skeletonTemplate } : {}
@@ -15057,19 +15173,19 @@ async function updateSkeletonKeypoints(ctx, request) {
15057
15173
  if (!spec) throw new ManifestEditError(`"${key}" is not declared by the manifest`);
15058
15174
  const file = spec.revision?.mode === "animate-skeleton" ? spec.revision.keypointsFile : void 0;
15059
15175
  if (!file) throw new ManifestEditError(`${key} is not an animate-skeleton revision`);
15060
- const relative = path28.relative(ctx.loaded.root, file);
15061
- if (relative.startsWith("..") || path28.isAbsolute(relative)) {
15176
+ const relative = path29.relative(ctx.loaded.root, file);
15177
+ if (relative.startsWith("..") || path29.isAbsolute(relative)) {
15062
15178
  throw new ManifestEditError(`${key}'s keypoints file is outside the project; edit it by hand`);
15063
15179
  }
15064
15180
  const current = existsSync20(file) ? sha256(await readFile18(file)) : null;
15065
15181
  if (current !== request.expectedSha256) {
15066
15182
  throw Object.assign(
15067
- new Error(`${relative.split(path28.sep).join("/")} changed on disk since this page loaded it; press Refresh and edit again`),
15183
+ new Error(`${relative.split(path29.sep).join("/")} changed on disk since this page loaded it; press Refresh and edit again`),
15068
15184
  { status: 409 }
15069
15185
  );
15070
15186
  }
15071
15187
  await writeFile14(file, JSON.stringify(request.set, null, 2) + "\n");
15072
- return relative.split(path28.sep).join("/");
15188
+ return relative.split(path29.sep).join("/");
15073
15189
  }
15074
15190
 
15075
15191
  // src/gallery/edit.ts
@@ -15123,7 +15239,7 @@ function createGalleryEditHandler(opts) {
15123
15239
  const history = /* @__PURE__ */ new Map();
15124
15240
  const manifestOf = async (project) => {
15125
15241
  try {
15126
- return path29.resolve(await opts.manifestFor(typeof project === "string" ? project : void 0));
15242
+ return path30.resolve(await opts.manifestFor(typeof project === "string" ? project : void 0));
15127
15243
  } catch {
15128
15244
  return null;
15129
15245
  }
@@ -15146,7 +15262,7 @@ function createGalleryEditHandler(opts) {
15146
15262
  await writeFile15(tmp, last.before);
15147
15263
  await rename9(tmp, manifestPath2);
15148
15264
  stack.pop();
15149
- log2(` manifest edit undone in ${path29.relative(process.cwd(), manifestPath2) || manifestPath2}`);
15265
+ log2(` manifest edit undone in ${path30.relative(process.cwd(), manifestPath2) || manifestPath2}`);
15150
15266
  return opts.reload();
15151
15267
  }
15152
15268
  const manifestPath = await manifestOf(body?.project);
@@ -15208,7 +15324,7 @@ function createEditApplier(opts, log2) {
15208
15324
  } catch (error) {
15209
15325
  throw new ManifestEditError(error instanceof Error ? error.message : String(error));
15210
15326
  }
15211
- const written = await saveProjectImage(path29.dirname(path29.resolve(manifestPath2)), upload.data);
15327
+ const written = await saveProjectImage(path30.dirname(path30.resolve(manifestPath2)), upload.data);
15212
15328
  log2(` image added: ${written}`);
15213
15329
  return opts.reload();
15214
15330
  }
@@ -15275,7 +15391,7 @@ function createEditApplier(opts, log2) {
15275
15391
  }
15276
15392
  const result = await applyManifestEdit(manifestPath, parsed.data);
15277
15393
  if (result.changed) {
15278
- const relative = path29.relative(process.cwd(), result.manifestPath);
15394
+ const relative = path30.relative(process.cwd(), result.manifestPath);
15279
15395
  const shown = !relative || relative.startsWith("..") ? result.manifestPath : relative;
15280
15396
  log2(` manifest edited: ${describeEdit(parsed.data)} in ${shown}`);
15281
15397
  }
@@ -15327,7 +15443,7 @@ function createGalleryPriceHandler(opts) {
15327
15443
 
15328
15444
  // src/pipeline/history.ts
15329
15445
  import { existsSync as existsSync21 } from "fs";
15330
- import path30 from "path";
15446
+ import path31 from "path";
15331
15447
  var HISTORY_ENV = "PIXELKILN_HISTORY";
15332
15448
  function historyLimit(manifest, env = process.env) {
15333
15449
  if (manifest?.history !== void 0) return manifest.history;
@@ -15417,7 +15533,7 @@ async function revertGeneration(provider, spec, lock, lockPath, opts) {
15417
15533
  if (!existsSync21(file)) continue;
15418
15534
  if (await sha256File(file) !== entry.outputs[i].sha256 && !opts.force) {
15419
15535
  throw new OverwriteRefusedError(
15420
- `${path30.relative(process.cwd(), file)} was changed after download; keep it with pixelkiln edit, or pass --force to replace it`,
15536
+ `${path31.relative(process.cwd(), file)} was changed after download; keep it with pixelkiln edit, or pass --force to replace it`,
15421
15537
  [file]
15422
15538
  );
15423
15539
  }
@@ -15470,7 +15586,7 @@ async function revertGeneration(provider, spec, lock, lockPath, opts) {
15470
15586
  // src/gallery/snapshot.ts
15471
15587
  var PROJECT_FILE_TYPE = "application/octet-stream";
15472
15588
  function galleryMediaId(absolutePath) {
15473
- return createHash3("sha256").update(path31.resolve(absolutePath)).digest("hex").slice(0, 24);
15589
+ return createHash4("sha256").update(path32.resolve(absolutePath)).digest("hex").slice(0, 24);
15474
15590
  }
15475
15591
  function galleryMediaRoute(id) {
15476
15592
  return `/media/${id}`;
@@ -15489,7 +15605,7 @@ function describeHistory(media, entry, cacheDir) {
15489
15605
  return (entry.history ?? []).map((generation, i) => {
15490
15606
  const outputs = generation.outputs.map((output) => {
15491
15607
  const mediaType = output.mediaType ?? MediaType.PNG;
15492
- const file = path31.join(cacheDir, cacheFileName(output.sha256, mediaType));
15608
+ const file = path32.join(cacheDir, cacheFileName(output.sha256, mediaType));
15493
15609
  let url = null;
15494
15610
  if (existsSync22(file)) {
15495
15611
  const id = galleryMediaId(file);
@@ -15621,9 +15737,9 @@ async function describeOutput(media, root, absolutePath, recorded = {}) {
15621
15737
  async function describeQuality(media, root, inspection) {
15622
15738
  const outputPaths = inspection.outputs ?? [inspection.output];
15623
15739
  const outputs = await Promise.all(
15624
- outputPaths.map((file) => describeOutput(media, root, path31.resolve(root, file)))
15740
+ outputPaths.map((file) => describeOutput(media, root, path32.resolve(root, file)))
15625
15741
  );
15626
- const recordPath = path31.resolve(root, inspection.record);
15742
+ const recordPath = path32.resolve(root, inspection.record);
15627
15743
  const recordExists = existsSync22(recordPath);
15628
15744
  let options = null;
15629
15745
  let check = null;
@@ -15643,7 +15759,7 @@ async function describeQuality(media, root, inspection) {
15643
15759
  return {
15644
15760
  state: inspection.state,
15645
15761
  reason: inspection.reason,
15646
- output: portableOutputPath(path31.resolve(root, inspection.output), root),
15762
+ output: portableOutputPath(path32.resolve(root, inspection.output), root),
15647
15763
  record: portableOutputPath(recordPath, root),
15648
15764
  recordExists,
15649
15765
  outputs,
@@ -15683,7 +15799,7 @@ async function buildGallerySnapshot(opts) {
15683
15799
  const items = [];
15684
15800
  const manifestText = await readFile20(loaded.path, "utf8");
15685
15801
  const manifestSha256 = sha256(manifestText);
15686
- const cacheDir = path31.resolve(path31.dirname(opts.lockPath), ".pixelkiln", "cache");
15802
+ const cacheDir = path32.resolve(path32.dirname(opts.lockPath), ".pixelkiln", "cache");
15687
15803
  let rawStyles = {};
15688
15804
  try {
15689
15805
  const raw = JSON.parse(manifestText);
@@ -15707,7 +15823,7 @@ async function buildGallerySnapshot(opts) {
15707
15823
  )
15708
15824
  );
15709
15825
  if (spec.source) {
15710
- const editPath = path31.resolve(root, spec.source);
15826
+ const editPath = path32.resolve(root, spec.source);
15711
15827
  const memberCount = sourceIsStem(spec.source, entry, root) ? entry.outputs.length : 1;
15712
15828
  const editShas = await Promise.all(
15713
15829
  Array.from({ length: memberCount }, async (_, index) => {
@@ -15756,7 +15872,7 @@ async function buildGallerySnapshot(opts) {
15756
15872
  editStatus = edits.some((member) => !member.exists) ? "missing" : editChanged.every((changed) => !changed) ? "same" : regenerated ? "regenerated-since" : "edited";
15757
15873
  }
15758
15874
  } else if (spec.source) {
15759
- outputs = [await describeOutput(media, root, path31.resolve(root, spec.source))];
15875
+ outputs = [await describeOutput(media, root, path32.resolve(root, spec.source))];
15760
15876
  } else if (!entry && existsSync22(spec.outFile)) {
15761
15877
  outputs = [await describeOutput(media, root, spec.outFile)];
15762
15878
  } else {
@@ -15960,7 +16076,7 @@ async function buildGallerySnapshot(opts) {
15960
16076
  id: loaded.manifest.name,
15961
16077
  name: loaded.manifest.name,
15962
16078
  manifest: loaded.path,
15963
- lock: path31.resolve(opts.lockPath),
16079
+ lock: path32.resolve(opts.lockPath),
15964
16080
  root,
15965
16081
  provider: loaded.manifest.provider,
15966
16082
  account: null,
@@ -15986,7 +16102,7 @@ async function buildGallerySnapshot(opts) {
15986
16102
  return { snapshot, media };
15987
16103
  }
15988
16104
  async function buildWorkspaceGallerySnapshot(opts) {
15989
- const dir = path31.dirname(path31.resolve(opts.workspacePath));
16105
+ const dir = path32.dirname(path32.resolve(opts.workspacePath));
15990
16106
  const media = /* @__PURE__ */ new Map();
15991
16107
  const projects = [];
15992
16108
  const items = [];
@@ -16029,7 +16145,7 @@ async function buildWorkspaceGallerySnapshot(opts) {
16029
16145
  id: project.id,
16030
16146
  name: loaded.manifest.name,
16031
16147
  manifest: manifestPath,
16032
- lock: path31.resolve(lockPath),
16148
+ lock: path32.resolve(lockPath),
16033
16149
  root: loaded.root,
16034
16150
  provider: loaded.manifest.provider,
16035
16151
  account: project.account ?? null,
@@ -16045,8 +16161,8 @@ async function buildWorkspaceGallerySnapshot(opts) {
16045
16161
  id: project.id,
16046
16162
  name: project.id,
16047
16163
  manifest: manifestPath,
16048
- lock: path31.resolve(lockPath),
16049
- root: path31.dirname(manifestPath),
16164
+ lock: path32.resolve(lockPath),
16165
+ root: path32.dirname(manifestPath),
16050
16166
  provider: project.provider,
16051
16167
  account: project.account ?? null,
16052
16168
  manifestSha256: null,
@@ -16073,7 +16189,7 @@ async function buildWorkspaceGallerySnapshot(opts) {
16073
16189
  version: 1,
16074
16190
  generatedAt: (opts.now ?? (() => /* @__PURE__ */ new Date()))().toISOString(),
16075
16191
  project: null,
16076
- workspace: { path: path31.resolve(opts.workspacePath), dir, projects },
16192
+ workspace: { path: path32.resolve(opts.workspacePath), dir, projects },
16077
16193
  filter,
16078
16194
  totals: {
16079
16195
  entries: projects.reduce((sum, project) => sum + project.entries, 0),
@@ -16095,7 +16211,7 @@ import { createServer as createServer2 } from "http";
16095
16211
  import { readFile as readFile21 } from "fs/promises";
16096
16212
 
16097
16213
  // src/gallery/page.ts
16098
- import { existsSync as existsSync23, readFileSync as readFileSync4 } from "fs";
16214
+ import { existsSync as existsSync23, readFileSync as readFileSync5 } from "fs";
16099
16215
 
16100
16216
  // src/gallery/client-bundle.ts
16101
16217
  import { createRequire } from "module";
@@ -16135,8 +16251,8 @@ var client;
16135
16251
  function clientAssets() {
16136
16252
  const prebuilt = new URL("gallery.js", CLIENT_DIR);
16137
16253
  client ??= {
16138
- css: readFileSync4(new URL("gallery.css", CLIENT_DIR), "utf8"),
16139
- js: existsSync23(prebuilt) ? readFileSync4(prebuilt, "utf8") : bundleGalleryClient()
16254
+ css: readFileSync5(new URL("gallery.css", CLIENT_DIR), "utf8"),
16255
+ js: existsSync23(prebuilt) ? readFileSync5(prebuilt, "utf8") : bundleGalleryClient()
16140
16256
  };
16141
16257
  return client;
16142
16258
  }
@@ -16955,7 +17071,7 @@ import { readFile as readFile23 } from "fs/promises";
16955
17071
  // src/pipeline/init.ts
16956
17072
  import { readdir, readFile as readFile22, writeFile as writeFile16 } from "fs/promises";
16957
17073
  import { existsSync as existsSync24 } from "fs";
16958
- import path32 from "path";
17074
+ import path33 from "path";
16959
17075
  function pngSize(buf) {
16960
17076
  if (buf.length < 24) return null;
16961
17077
  const isPng = buf.readUInt32BE(0) === 2303741511 && buf.readUInt32BE(4) === 218765834;
@@ -16969,7 +17085,7 @@ function slugify(value) {
16969
17085
  async function walk(dir, exclude) {
16970
17086
  const out = [];
16971
17087
  for (const entry of await readdir(dir, { withFileTypes: true })) {
16972
- const full = path32.join(dir, entry.name);
17088
+ const full = path33.join(dir, entry.name);
16973
17089
  if (exclude.some((x) => full.includes(x))) continue;
16974
17090
  if (entry.isDirectory()) out.push(...await walk(full, exclude));
16975
17091
  else if (entry.name.toLowerCase().endsWith(".png")) out.push(full);
@@ -16988,9 +17104,9 @@ async function scanAssets(root, opts = {}) {
16988
17104
  skipped.push(`${file} (not a readable PNG)`);
16989
17105
  continue;
16990
17106
  }
16991
- const rel = path32.relative(root, file);
16992
- const category = path32.dirname(rel) === "." ? "" : path32.dirname(rel);
16993
- let id = slugify(path32.basename(rel));
17107
+ const rel = path33.relative(root, file);
17108
+ const category = path33.dirname(rel) === "." ? "" : path33.dirname(rel);
17109
+ let id = slugify(path33.basename(rel));
16994
17110
  if (seen.has(id)) {
16995
17111
  const n = seen.get(id) + 1;
16996
17112
  seen.set(id, n);
@@ -18004,14 +18120,14 @@ function createGenerateHandlers(opts) {
18004
18120
 
18005
18121
  // src/gallery/editor.ts
18006
18122
  import { readFile as readFile25 } from "fs/promises";
18007
- import path34 from "path";
18123
+ import path35 from "path";
18008
18124
 
18009
18125
  // src/editor/install.ts
18010
- import { createHash as createHash4 } from "crypto";
18126
+ import { createHash as createHash5 } from "crypto";
18011
18127
  import { existsSync as existsSync26 } from "fs";
18012
18128
  import { mkdir as mkdir12, readFile as readFile24, rename as rename10, rm as rm11, stat as stat3, writeFile as writeFile17 } from "fs/promises";
18013
- import os2 from "os";
18014
- import path33 from "path";
18129
+ import os3 from "os";
18130
+ import path34 from "path";
18015
18131
 
18016
18132
  // tools/pixelorama-bridge/pin.json
18017
18133
  var pin_default = {
@@ -18075,15 +18191,15 @@ var EditorInstallError = class extends Error {
18075
18191
  };
18076
18192
  function editorToolsRoot() {
18077
18193
  const override = process.env.PIXELKILN_TOOLS_DIR?.trim();
18078
- if (override) return path33.resolve(override);
18079
- if (process.platform === "darwin") return path33.join(os2.homedir(), "Library", "Caches", "pixelkiln", "tools");
18194
+ if (override) return path34.resolve(override);
18195
+ if (process.platform === "darwin") return path34.join(os3.homedir(), "Library", "Caches", "pixelkiln", "tools");
18080
18196
  if (process.platform === "win32") {
18081
- return path33.join(process.env.LOCALAPPDATA ?? path33.join(os2.homedir(), "AppData", "Local"), "pixelkiln", "tools");
18197
+ return path34.join(process.env.LOCALAPPDATA ?? path34.join(os3.homedir(), "AppData", "Local"), "pixelkiln", "tools");
18082
18198
  }
18083
- return path33.join(process.env.XDG_CACHE_HOME ?? path33.join(os2.homedir(), ".cache"), "pixelkiln", "tools");
18199
+ return path34.join(process.env.XDG_CACHE_HOME ?? path34.join(os3.homedir(), ".cache"), "pixelkiln", "tools");
18084
18200
  }
18085
18201
  function editorDir(pin = EDITOR_PIN, root = editorToolsRoot()) {
18086
- return path33.join(root, "pixelorama", pin.release ?? "unpublished");
18202
+ return path34.join(root, "pixelorama", pin.release ?? "unpublished");
18087
18203
  }
18088
18204
  function editorBaseUrl(pin = EDITOR_PIN) {
18089
18205
  const override = process.env.PIXELKILN_EDITOR_URL?.trim();
@@ -18093,7 +18209,7 @@ function editorBaseUrl(pin = EDITOR_PIN) {
18093
18209
  }
18094
18210
  async function sha256Of(file) {
18095
18211
  try {
18096
- return createHash4("sha256").update(await readFile24(file)).digest("hex");
18212
+ return createHash5("sha256").update(await readFile24(file)).digest("hex");
18097
18213
  } catch {
18098
18214
  return null;
18099
18215
  }
@@ -18106,7 +18222,7 @@ async function editorStatus(opts = {}) {
18106
18222
  let totalBytes = 0;
18107
18223
  for (const [name, expected] of Object.entries(pin.files)) {
18108
18224
  totalBytes += expected.bytes;
18109
- const file = path33.join(dir, name);
18225
+ const file = path34.join(dir, name);
18110
18226
  const info = existsSync26(file) ? await stat3(file).catch(() => null) : null;
18111
18227
  if (info && info.size === expected.bytes && await sha256Of(file) === expected.sha256) {
18112
18228
  installedBytes += expected.bytes;
@@ -18160,11 +18276,11 @@ async function installEditor(opts = {}) {
18160
18276
  if (bytes.length !== expected.bytes) {
18161
18277
  throw new EditorInstallError(`${name}: expected ${expected.bytes} bytes, got ${bytes.length}`, name);
18162
18278
  }
18163
- const digest2 = createHash4("sha256").update(bytes).digest("hex");
18279
+ const digest2 = createHash5("sha256").update(bytes).digest("hex");
18164
18280
  if (digest2 !== expected.sha256) {
18165
18281
  throw new EditorInstallError(`${name}: sha256 ${digest2.slice(0, 12)}\u2026 does not match the pinned ${expected.sha256.slice(0, 12)}\u2026`, name);
18166
18282
  }
18167
- const final = path33.join(dir, name);
18283
+ const final = path34.join(dir, name);
18168
18284
  const tmp = `${final}.${process.pid}.part`;
18169
18285
  try {
18170
18286
  await writeFile17(tmp, bytes);
@@ -18210,7 +18326,7 @@ var EDITOR_CONTENT_TYPES = {
18210
18326
  ".json": "application/json; charset=utf-8"
18211
18327
  };
18212
18328
  function editorContentType(name) {
18213
- return EDITOR_CONTENT_TYPES[path33.extname(name).toLowerCase()] ?? null;
18329
+ return EDITOR_CONTENT_TYPES[path34.extname(name).toLowerCase()] ?? null;
18214
18330
  }
18215
18331
 
18216
18332
  // src/gallery/editor.ts
@@ -18286,7 +18402,7 @@ function createGalleryEditorHandlers(opts = {}) {
18286
18402
  if (!status.installed) return null;
18287
18403
  let bytes;
18288
18404
  try {
18289
- bytes = await readFile25(path34.join(dir, name));
18405
+ bytes = await readFile25(path35.join(dir, name));
18290
18406
  } catch {
18291
18407
  verified = null;
18292
18408
  return null;
@@ -18326,7 +18442,7 @@ async function serveUntilStopped(initial, reload, args, access2) {
18326
18442
  };
18327
18443
  const loadProject = async (project) => {
18328
18444
  const ctx = await access2.loadProject(project);
18329
- loadProjectEnv(path35.dirname(ctx.loaded.path));
18445
+ loadProjectEnv(path36.dirname(ctx.loaded.path));
18330
18446
  return ctx;
18331
18447
  };
18332
18448
  const loadProjectEnv = (dir) => {
@@ -18335,7 +18451,7 @@ async function serveUntilStopped(initial, reload, args, access2) {
18335
18451
  for (const [name, value] of Object.entries(declared)) {
18336
18452
  if (credentialNames.has(name) && name in process.env && process.env[name] !== value) {
18337
18453
  throw new Error(
18338
- `${path35.relative(process.cwd(), dir) || "."} sets ${name} to a different value than the one this gallery already loaded; run a separate gallery for that project so its work uses its own account.`
18454
+ `${path36.relative(process.cwd(), dir) || "."} sets ${name} to a different value than the one this gallery already loaded; run a separate gallery for that project so its work uses its own account.`
18339
18455
  );
18340
18456
  }
18341
18457
  }
@@ -18343,7 +18459,7 @@ async function serveUntilStopped(initial, reload, args, access2) {
18343
18459
  };
18344
18460
  const quoters = /* @__PURE__ */ new Map();
18345
18461
  const quoteFor = async (project) => {
18346
- loadProjectEnv(path35.dirname(path35.resolve(await access2.manifestFor(project))));
18462
+ loadProjectEnv(path36.dirname(path36.resolve(await access2.manifestFor(project))));
18347
18463
  const key = process.env.PIXELLAB_API_KEY;
18348
18464
  if (!key) return null;
18349
18465
  let quoter = quoters.get(key);
@@ -18394,7 +18510,7 @@ async function serveUntilStopped(initial, reload, args, access2) {
18394
18510
  }
18395
18511
  async function runGallery(args) {
18396
18512
  if (args.workspace) {
18397
- const workspacePath = path35.resolve(args.workspace);
18513
+ const workspacePath = path36.resolve(args.workspace);
18398
18514
  if (!existsSync27(workspacePath)) {
18399
18515
  throw new Error(`Workspace catalog not found: ${workspacePath}`);
18400
18516
  }
@@ -18415,7 +18531,7 @@ async function runGallery(args) {
18415
18531
  const registered = async (projectId) => {
18416
18532
  const project = (await loadWorkspace(workspacePath)).projects.find((candidate) => candidate.id === projectId);
18417
18533
  if (!projectId || !project) throw new Error(`unknown workspace project "${projectId ?? ""}"`);
18418
- return resolveProject(path35.dirname(workspacePath), project);
18534
+ return resolveProject(path36.dirname(workspacePath), project);
18419
18535
  };
18420
18536
  await serveUntilStopped(initial, build2, args, {
18421
18537
  manifestFor: async (projectId) => (await registered(projectId)).manifestPath,
@@ -18440,7 +18556,7 @@ async function runGallery(args) {
18440
18556
  return buildGallerySnapshot({ ...fresh, filter });
18441
18557
  };
18442
18558
  await serveUntilStopped(await build(), reload, args, {
18443
- manifestFor: () => path35.resolve(args.manifest),
18559
+ manifestFor: () => path36.resolve(args.manifest),
18444
18560
  loadProject: () => openProject(args.manifest, { lockPath: args.lock, env: false })
18445
18561
  });
18446
18562
  }
@@ -18754,13 +18870,13 @@ import { existsSync as existsSync29 } from "fs";
18754
18870
  // src/pipeline/cache-health.ts
18755
18871
  import { existsSync as existsSync28 } from "fs";
18756
18872
  import { readFile as readFile26, readdir as readdir2, rm as rm12 } from "fs/promises";
18757
- import path36 from "path";
18873
+ import path37 from "path";
18758
18874
  async function inspectCaches(lock, lockPath, options = {}) {
18759
18875
  if (options.prune && !existsSync28(lockPath)) {
18760
- throw new Error(`Refusing to prune without an existing lockfile at ${path36.resolve(lockPath)}`);
18876
+ throw new Error(`Refusing to prune without an existing lockfile at ${path37.resolve(lockPath)}`);
18761
18877
  }
18762
- const contentDir = path36.resolve(path36.dirname(lockPath), ".pixelkiln", "cache");
18763
- const remotePath = path36.resolve(cachePathFor(lockPath));
18878
+ const contentDir = path37.resolve(path37.dirname(lockPath), ".pixelkiln", "cache");
18879
+ const remotePath = path37.resolve(cachePathFor(lockPath));
18764
18880
  const referenced = referencedHashes(lock);
18765
18881
  let content = await inspectContentCache(contentDir, referenced);
18766
18882
  let remoteHashes = await inspectRemoteHashCache(remotePath);
@@ -18772,7 +18888,7 @@ async function inspectCaches(lock, lockPath, options = {}) {
18772
18888
  ]);
18773
18889
  for (const name of names) {
18774
18890
  try {
18775
- await rm12(path36.join(contentDir, name), { force: true });
18891
+ await rm12(path37.join(contentDir, name), { force: true });
18776
18892
  removed.contentFiles++;
18777
18893
  } catch {
18778
18894
  }
@@ -18820,7 +18936,7 @@ async function inspectContentCache(contentDir, referenced) {
18820
18936
  continue;
18821
18937
  }
18822
18938
  report.files++;
18823
- const file = path36.join(contentDir, entry.name);
18939
+ const file = path37.join(contentDir, entry.name);
18824
18940
  const mediaType = mediaTypeFromExtension(entry.name);
18825
18941
  const expected = mediaType ? entry.name.slice(0, -4) : "";
18826
18942
  let bytes;
@@ -18991,42 +19107,42 @@ This drops their provenance from the lockfile. The art on disk is`);
18991
19107
  }
18992
19108
 
18993
19109
  // src/cli/commands/init.ts
18994
- import path37 from "path";
19110
+ import path38 from "path";
18995
19111
  import { existsSync as existsSync30 } from "fs";
18996
19112
  async function runInit(args) {
18997
19113
  if (!args.from) throw new Error("init needs --from <dir> pointing at your existing PNGs.");
18998
- const root = path37.resolve(args.from);
19114
+ const root = path38.resolve(args.from);
18999
19115
  if (!existsSync30(root)) throw new Error(`No directory at ${root}`);
19000
19116
  const generator = args.generator ?? "map";
19001
19117
  if (generator !== "1dir" && generator !== "map") {
19002
19118
  throw new Error(`--generator must be "1dir" or "map", got "${args.generator}".`);
19003
19119
  }
19004
- const target = path37.resolve(args.out ?? "pixelkiln.manifest.json");
19120
+ const target = path38.resolve(args.out ?? "pixelkiln.manifest.json");
19005
19121
  const { assets, skipped } = await scanAssets(root, { exclude: args.exclude });
19006
19122
  if (!assets.length) throw new Error(`No PNGs found under ${root}`);
19007
19123
  const manifest = buildManifest(
19008
- args.name ?? path37.basename(path37.dirname(target)),
19124
+ args.name ?? path38.basename(path38.dirname(target)),
19009
19125
  args.styles[0] ?? "base",
19010
19126
  generator,
19011
- path37.relative(path37.dirname(target), root) || ".",
19127
+ path38.relative(path38.dirname(target), root) || ".",
19012
19128
  assets
19013
19129
  );
19014
19130
  await writeManifestFile(target, manifest);
19015
- log(` scanned ${assets.length} PNG(s) under ${path37.relative(process.cwd(), root)}`);
19131
+ log(` scanned ${assets.length} PNG(s) under ${path38.relative(process.cwd(), root)}`);
19016
19132
  if (skipped.length) log(` skipped ${skipped.length} unreadable file(s)`);
19017
- log(` wrote ${path37.relative(process.cwd(), target)}`);
19133
+ log(` wrote ${path38.relative(process.cwd(), target)}`);
19018
19134
  log(`
19019
19135
  Prompts are intentionally empty. To recover the real ones from your`);
19020
19136
  log(` PixelLab account instead of inventing them:`);
19021
19137
  log(`
19022
- pixelkiln adopt --manifest ${path37.relative(process.cwd(), target)} --write-prompts
19138
+ pixelkiln adopt --manifest ${path38.relative(process.cwd(), target)} --write-prompts
19023
19139
  `);
19024
19140
  }
19025
19141
 
19026
19142
  // src/pipeline/doctor.ts
19027
19143
  import { constants, existsSync as existsSync31 } from "fs";
19028
19144
  import { access, stat as stat4 } from "fs/promises";
19029
- import path38 from "path";
19145
+ import path39 from "path";
19030
19146
  async function doctor(loaded, specs, lock, lockPath, opts = {}) {
19031
19147
  const checks = [];
19032
19148
  const add = (id, level, message6) => checks.push({ id, level, message: message6 });
@@ -19037,8 +19153,8 @@ async function doctor(loaded, specs, lock, lockPath, opts = {}) {
19037
19153
  );
19038
19154
  add("lock", "ok", `${Object.keys(lock.entries).length} valid v2 lock entr(ies)`);
19039
19155
  const dirs = [...new Set(specs.flatMap((spec) => [
19040
- path38.dirname(spec.outFile),
19041
- ...spec.quality ? [path38.dirname(spec.quality.outFile)] : []
19156
+ path39.dirname(spec.outFile),
19157
+ ...spec.quality ? [path39.dirname(spec.quality.outFile)] : []
19042
19158
  ]))];
19043
19159
  const unwritable = [];
19044
19160
  for (const dir of dirs) {
@@ -19061,7 +19177,7 @@ async function doctor(loaded, specs, lock, lockPath, opts = {}) {
19061
19177
  for (let index = 0; index < entry.outputs.length; index++) {
19062
19178
  const output = entry.outputs[index];
19063
19179
  const spec = specByKey.get(key);
19064
- const absolute = spec ? currentEntryOutputPath(entry, spec, index) : resolveOutputPath(output.path, path38.dirname(lockPath));
19180
+ const absolute = spec ? currentEntryOutputPath(entry, spec, index) : resolveOutputPath(output.path, path39.dirname(lockPath));
19065
19181
  const owner = outputOwners.get(absolute);
19066
19182
  if (owner && owner !== key) duplicateOutputs.push(`${output.path} (${owner}, ${key})`);
19067
19183
  outputOwners.set(absolute, key);
@@ -19076,7 +19192,7 @@ async function doctor(loaded, specs, lock, lockPath, opts = {}) {
19076
19192
  } else {
19077
19193
  add("lock-outputs", "ok", `${outputOwners.size} recorded output path(s) are uniquely owned`);
19078
19194
  }
19079
- const cacheDir = path38.join(path38.dirname(lockPath), ".pixelkiln", "cache");
19195
+ const cacheDir = path39.join(path39.dirname(lockPath), ".pixelkiln", "cache");
19080
19196
  const stranded = [];
19081
19197
  const invalidState = [];
19082
19198
  const staleMapUrls = [];
@@ -19092,7 +19208,7 @@ async function doctor(loaded, specs, lock, lockPath, opts = {}) {
19092
19208
  const hasSource = Boolean(entry.sourceUrl || entry.sourceUrls?.length);
19093
19209
  let hasCache = false;
19094
19210
  for (const output of entry.outputs) {
19095
- const cached = path38.join(
19211
+ const cached = path39.join(
19096
19212
  cacheDir,
19097
19213
  cacheFileName(output.sha256, output.mediaType ?? MediaType.PNG)
19098
19214
  );
@@ -19192,7 +19308,7 @@ async function nearestExistingDirectory(start) {
19192
19308
  if ((await stat4(current)).isDirectory()) return current;
19193
19309
  } catch {
19194
19310
  }
19195
- const parent = path38.dirname(current);
19311
+ const parent = path39.dirname(current);
19196
19312
  if (parent === current) return current;
19197
19313
  current = parent;
19198
19314
  }
@@ -19201,7 +19317,7 @@ async function nearestExistingDirectory(start) {
19201
19317
  // src/pipeline/audit.ts
19202
19318
  import { readFile as readFile27 } from "fs/promises";
19203
19319
  import { existsSync as existsSync32 } from "fs";
19204
- import path39 from "path";
19320
+ import path40 from "path";
19205
19321
  function paletteDistance(asset, reference) {
19206
19322
  if (!asset.length || !reference.length) return 0;
19207
19323
  let total = 0;
@@ -19266,7 +19382,7 @@ async function auditStyle(loaded, specs, styleId, lock) {
19266
19382
  let referenceFromStyleImages = false;
19267
19383
  const refPalettes = [];
19268
19384
  for (const img of style.styleImages) {
19269
- const abs = path39.resolve(loaded.root, img.path);
19385
+ const abs = path40.resolve(loaded.root, img.path);
19270
19386
  if (!existsSync32(abs)) continue;
19271
19387
  try {
19272
19388
  refPalettes.push(extractPalette(decodePng(await readFile27(abs)), 12));
@@ -19497,24 +19613,24 @@ async function runAudit(args) {
19497
19613
  }
19498
19614
 
19499
19615
  // src/cli/commands/pack.ts
19500
- import path41 from "path";
19616
+ import path42 from "path";
19501
19617
  import { readFile as readFile28 } from "fs/promises";
19502
19618
 
19503
19619
  // src/pipeline/pack.ts
19504
- import { readFileSync as readFileSync5 } from "fs";
19505
- import path40 from "path";
19620
+ import { readFileSync as readFileSync6 } from "fs";
19621
+ import path41 from "path";
19506
19622
  var DEFAULT_FRAME_SET_FPS = 12;
19507
19623
  function resolvePackInputs(raw, inputsFilePath) {
19508
19624
  if (!Array.isArray(raw) || !raw.length) {
19509
19625
  throw new Error("--inputs must be a non-empty JSON array of { id, path }");
19510
19626
  }
19511
- const baseDir = path40.dirname(path40.resolve(inputsFilePath));
19627
+ const baseDir = path41.dirname(path41.resolve(inputsFilePath));
19512
19628
  return raw.map((entry, i) => {
19513
19629
  const e = entry;
19514
19630
  if (typeof e.id !== "string" || typeof e.path !== "string") {
19515
19631
  throw new Error(`--inputs[${i}] needs string "id" and "path"`);
19516
19632
  }
19517
- return { id: e.id, path: path40.resolve(baseDir, e.path) };
19633
+ return { id: e.id, path: path41.resolve(baseDir, e.path) };
19518
19634
  });
19519
19635
  }
19520
19636
  function packSprites(inputs, options = {}) {
@@ -19535,7 +19651,7 @@ function packSprites(inputs, options = {}) {
19535
19651
  for (const input of ordered) {
19536
19652
  let sourceHash = null;
19537
19653
  try {
19538
- const bytes = readFileSync5(input.path);
19654
+ const bytes = readFileSync6(input.path);
19539
19655
  sourceHash = sha256(bytes);
19540
19656
  const png = decodePng(bytes);
19541
19657
  sprites.push({ id: input.id, width: png.width, height: png.height, pixels: png.pixels });
@@ -19598,7 +19714,7 @@ function packStyle(lock, styleId, manifestDir, options = {}) {
19598
19714
  }
19599
19715
  const inputs2 = Object.entries(options.sourceOverrides).map(([id, source]) => ({
19600
19716
  id,
19601
- path: path40.resolve(source)
19717
+ path: path41.resolve(source)
19602
19718
  }));
19603
19719
  if (!inputs2.length) throw new Error(`No approved quality outputs for style "${styleId}".`);
19604
19720
  const packed2 = packSprites(inputs2, options);
@@ -19622,7 +19738,7 @@ function packStyle(lock, styleId, manifestDir, options = {}) {
19622
19738
  let outputs = resolveEntryOutputs(entry, id, manifestDir);
19623
19739
  const source = sources[id];
19624
19740
  if (source && outputs.length <= 1) {
19625
- inputs.push({ id, path: path40.resolve(manifestDir, source) });
19741
+ inputs.push({ id, path: path41.resolve(manifestDir, source) });
19626
19742
  continue;
19627
19743
  }
19628
19744
  if (source && sourceIsStem(source, entry, manifestDir)) {
@@ -19656,7 +19772,7 @@ function packStyle(lock, styleId, manifestDir, options = {}) {
19656
19772
  }
19657
19773
  }
19658
19774
  for (const [id, source] of Object.entries(sources)) {
19659
- if (!locked.has(id)) inputs.push({ id, path: path40.resolve(manifestDir, source) });
19775
+ if (!locked.has(id)) inputs.push({ id, path: path41.resolve(manifestDir, source) });
19660
19776
  }
19661
19777
  if (!inputs.length) {
19662
19778
  throw new Error(`No readable sprites for style "${styleId}"; ${noOutput.length} skipped.`);
@@ -19693,7 +19809,7 @@ function mountSprites(placements, options) {
19693
19809
  for (const p of placements) {
19694
19810
  let sourceHash = null;
19695
19811
  try {
19696
- const bytes = readFileSync5(p.path);
19812
+ const bytes = readFileSync6(p.path);
19697
19813
  sourceHash = sha256(bytes);
19698
19814
  const png = decodePng(bytes);
19699
19815
  if (png.width > cellWidth || png.height > cellHeight) {
@@ -19764,7 +19880,7 @@ function mountStyle(lock, styleId, manifestDir, mount, cells, sources = {}, outp
19764
19880
  const source = sources[id];
19765
19881
  const entry = lock.entries[`${styleId}/${id}`];
19766
19882
  if (source && !(entry && sourceIsStem(source, entry, manifestDir))) {
19767
- placements.push({ id, path: path40.resolve(manifestDir, source), cell });
19883
+ placements.push({ id, path: path41.resolve(manifestDir, source), cell });
19768
19884
  continue;
19769
19885
  }
19770
19886
  if (source && entry) {
@@ -19797,7 +19913,7 @@ function mountStyle(lock, styleId, manifestDir, mount, cells, sources = {}, outp
19797
19913
  `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}`
19798
19914
  );
19799
19915
  }
19800
- const basePng = mount.base ? readFileSync5(path40.resolve(manifestDir, mount.base)) : void 0;
19916
+ const basePng = mount.base ? readFileSync6(path41.resolve(manifestDir, mount.base)) : void 0;
19801
19917
  const mounted = mountSprites(placements, {
19802
19918
  cellWidth: mount.cellWidth,
19803
19919
  cellHeight: mount.cellHeight,
@@ -19807,7 +19923,7 @@ function mountStyle(lock, styleId, manifestDir, mount, cells, sources = {}, outp
19807
19923
  ...mounted,
19808
19924
  sources: mount.base ? [{
19809
19925
  id: "$base",
19810
- path: path40.resolve(manifestDir, mount.base),
19926
+ path: path41.resolve(manifestDir, mount.base),
19811
19927
  sha256: sha256(basePng),
19812
19928
  included: true
19813
19929
  }, ...mounted.sources] : mounted.sources,
@@ -20211,12 +20327,12 @@ async function runPack(args) {
20211
20327
  if (args.primaryOnly || args.outputRoles.length) {
20212
20328
  throw new Error("--primary-only and --output-role require manifest-driven pack");
20213
20329
  }
20214
- const raw = JSON.parse(await readFile28(path41.resolve(args.inputs), "utf8"));
20330
+ const raw = JSON.parse(await readFile28(path42.resolve(args.inputs), "utf8"));
20215
20331
  const inputs = resolvePackInputs(raw, args.inputs);
20216
20332
  const { png, atlas, skipped, sources } = packSprites(inputs, { columns: args.columns });
20217
20333
  const format = sheetFormat(args);
20218
- const base = path41.resolve(args.out.replace(/\.(?:png|json|tres)$/i, ""));
20219
- const { extension, document } = renderSheetDocument(format, atlas, { imageName: path41.basename(`${base}.png`) });
20334
+ const base = path42.resolve(args.out.replace(/\.(?:png|json|tres)$/i, ""));
20335
+ const { extension, document } = renderSheetDocument(format, atlas, { imageName: path42.basename(`${base}.png`) });
20220
20336
  const outputs = [
20221
20337
  { path: `${base}.png`, data: png },
20222
20338
  { path: `${base}${extension}`, data: document }
@@ -20229,7 +20345,7 @@ async function runPack(args) {
20229
20345
  log(
20230
20346
  ` ${atlas.frames.length} sprite(s), ${atlas.sheet.width}x${atlas.sheet.height} in ${atlas.columns} column(s), ${(png.length / 1024).toFixed(1)} KB (${format})`
20231
20347
  );
20232
- log(` ${path41.relative(process.cwd(), base)}.png + ${extension} + .pixelkiln.json`);
20348
+ log(` ${path42.relative(process.cwd(), base)}.png + ${extension} + .pixelkiln.json`);
20233
20349
  for (const s of skipped) log(` skipped ${s.id}: ${s.reason}`);
20234
20350
  return;
20235
20351
  }
@@ -20237,7 +20353,7 @@ async function runPack(args) {
20237
20353
  if (args.primaryOnly && args.outputRoles.length) {
20238
20354
  throw new Error("pack accepts either --primary-only or --output-role, not both");
20239
20355
  }
20240
- const manifestDir = path41.dirname(path41.resolve(args.manifest));
20356
+ const manifestDir = path42.dirname(path42.resolve(args.manifest));
20241
20357
  const styleIds = args.styles.length ? args.styles : Object.keys(loaded.manifest.styles);
20242
20358
  for (const styleId of styleIds) {
20243
20359
  const style = loaded.manifest.styles[styleId];
@@ -20252,9 +20368,9 @@ async function runPack(args) {
20252
20368
  sourceOverrides: qualitySources,
20253
20369
  sources: manifestSources(loaded.manifest, styleId)
20254
20370
  });
20255
- const base = args.out ? path41.resolve(args.out.replace(/\.(?:png|json|tres)$/i, "")) : path41.resolve(manifestDir, style.outDir, `${styleId}-sheet`);
20371
+ const base = args.out ? path42.resolve(args.out.replace(/\.(?:png|json|tres)$/i, "")) : path42.resolve(manifestDir, style.outDir, `${styleId}-sheet`);
20256
20372
  const format = sheetFormat(args);
20257
- const { extension, document } = renderSheetDocument(format, atlas, { imageName: path41.basename(`${base}.png`) });
20373
+ const { extension, document } = renderSheetDocument(format, atlas, { imageName: path42.basename(`${base}.png`) });
20258
20374
  const outputs = [
20259
20375
  { path: `${base}.png`, data: png },
20260
20376
  { path: `${base}${extension}`, data: document }
@@ -20286,13 +20402,13 @@ async function runPack(args) {
20286
20402
  log(
20287
20403
  ` ${styleId}: ${atlas.frames.length} sprite(s), ${atlas.sheet.width}x${atlas.sheet.height} in ${atlas.columns} column(s)` + (atlas.sets?.length ? `, ${atlas.sets.length} set(s)` : "") + ` (${format})`
20288
20404
  );
20289
- log(` ${path41.relative(process.cwd(), base)}.png + ${extension} + .pixelkiln.json`);
20405
+ log(` ${path42.relative(process.cwd(), base)}.png + ${extension} + .pixelkiln.json`);
20290
20406
  for (const s of skipped) log(` skipped ${s.id}: ${s.reason}`);
20291
20407
  }
20292
20408
  }
20293
20409
  async function runMount(args) {
20294
20410
  const { loaded, lock } = await openProject2(args);
20295
- const manifestDir = path41.dirname(path41.resolve(args.manifest));
20411
+ const manifestDir = path42.dirname(path42.resolve(args.manifest));
20296
20412
  const styleIds = args.styles.length ? args.styles : Object.keys(loaded.manifest.styles);
20297
20413
  for (const styleId of styleIds) {
20298
20414
  const style = loaded.manifest.styles[styleId];
@@ -20331,9 +20447,9 @@ async function runMount(args) {
20331
20447
  sources,
20332
20448
  outputRoles
20333
20449
  );
20334
- const out = path41.resolve(manifestDir, style.mount.out);
20450
+ const out = path42.resolve(manifestDir, style.mount.out);
20335
20451
  const format = sheetFormat(args);
20336
- const { extension, document } = renderSheetDocument(format, atlas, { imageName: path41.basename(out) });
20452
+ const { extension, document } = renderSheetDocument(format, atlas, { imageName: path42.basename(out) });
20337
20453
  const metadata = out.replace(/\.png$/, "") + extension;
20338
20454
  const companion = out.replace(/\.png$/, "") + ".pixelkiln.json";
20339
20455
  const outputs = [
@@ -20353,7 +20469,7 @@ async function runMount(args) {
20353
20469
  await provenanceFile("$lock", args.lock),
20354
20470
  ...qualityRecords,
20355
20471
  ...artifactSources.filter(
20356
- (source) => source.id !== "$base" || path41.resolve(source.path) !== out
20472
+ (source) => source.id !== "$base" || path42.resolve(source.path) !== out
20357
20473
  )
20358
20474
  ],
20359
20475
  options: {
@@ -20367,14 +20483,14 @@ async function runMount(args) {
20367
20483
  log(
20368
20484
  ` ${styleId}: ${atlas.frames.length} cell(s) into ${atlas.sheet.width}x${atlas.sheet.height}` + (overBase ? ` over ${style.mount.base}` : " (new sheet)")
20369
20485
  );
20370
- log(` ${path41.relative(process.cwd(), out)} + atlas/provenance JSON`);
20486
+ log(` ${path42.relative(process.cwd(), out)} + atlas/provenance JSON`);
20371
20487
  for (const s of skipped) log(` skipped ${s.id}: ${s.reason}`);
20372
20488
  }
20373
20489
  }
20374
20490
  async function runExport(args) {
20375
20491
  const { loaded, specs, lock } = await openProject2(args);
20376
20492
  const format = args.format ?? "generic";
20377
- const manifestDir = path41.dirname(path41.resolve(args.manifest));
20493
+ const manifestDir = path42.dirname(path42.resolve(args.manifest));
20378
20494
  const selected = specs.filter((spec) => {
20379
20495
  if (spec.generator !== "tiles" && spec.generator !== "terrain") return false;
20380
20496
  if (args.styles.length && !args.styles.includes(spec.styleId)) return false;
@@ -20390,12 +20506,12 @@ async function runExport(args) {
20390
20506
  for (const spec of selected) {
20391
20507
  const entry = lock.entries[lockKey(spec.styleId, spec.assetId)];
20392
20508
  const style = loaded.manifest.styles[spec.styleId];
20393
- const defaultBase = path41.resolve(manifestDir, style.outDir, `${spec.assetId}-tileset`);
20394
- const base = args.out ? path41.resolve(args.out.replace(/\.(?:png|json|tsj|tres)$/i, "")) : defaultBase;
20509
+ const defaultBase = path42.resolve(manifestDir, style.outDir, `${spec.assetId}-tileset`);
20510
+ const base = args.out ? path42.resolve(args.out.replace(/\.(?:png|json|tsj|tres)$/i, "")) : defaultBase;
20395
20511
  const result = exportTileset(entry, spec, {
20396
20512
  format,
20397
20513
  manifestDir,
20398
- imageName: path41.basename(`${base}.png`),
20514
+ imageName: path42.basename(`${base}.png`),
20399
20515
  columns: args.columns
20400
20516
  });
20401
20517
  const outputs = [
@@ -20413,7 +20529,7 @@ async function runExport(args) {
20413
20529
  asset: spec.assetId,
20414
20530
  columns: args.columns ?? null,
20415
20531
  format,
20416
- image: path41.basename(`${base}.png`),
20532
+ image: path42.basename(`${base}.png`),
20417
20533
  providerRules: result.generic.providerRules,
20418
20534
  style: spec.styleId,
20419
20535
  tileType: spec.tileType ?? null
@@ -20423,7 +20539,7 @@ async function runExport(args) {
20423
20539
  ` ${spec.styleId}/${spec.assetId}: ${result.generic.tiles.length} tile(s), ${result.generic.sheet.width}x${result.generic.sheet.height} (${format})`
20424
20540
  );
20425
20541
  log(
20426
- ` ${path41.relative(process.cwd(), base)}.png + ${path41.basename(base)}${result.extension} + .pixelkiln.json`
20542
+ ` ${path42.relative(process.cwd(), base)}.png + ${path42.basename(base)}${result.extension} + .pixelkiln.json`
20427
20543
  );
20428
20544
  }
20429
20545
  }
@@ -20436,21 +20552,21 @@ function sheetFormat(args) {
20436
20552
  }
20437
20553
 
20438
20554
  // src/cli/commands/pixellab-utils.ts
20439
- import path43 from "path";
20555
+ import path44 from "path";
20440
20556
 
20441
20557
  // src/pixellab-utils.ts
20442
20558
  import { existsSync as existsSync33 } from "fs";
20443
20559
  import { mkdir as mkdir13, readFile as readFile29, writeFile as writeFile18 } from "fs/promises";
20444
- import path42 from "path";
20560
+ import path43 from "path";
20445
20561
  var UNZOOM_MIN_SIDE = 256;
20446
20562
  var UNZOOM_MAX_AREA = 2048 * 2048;
20447
20563
  function defaultUnzoomOut(input) {
20448
- const ext = path42.extname(input);
20564
+ const ext = path43.extname(input);
20449
20565
  return `${ext ? input.slice(0, -ext.length) : input}.unzoomed.png`;
20450
20566
  }
20451
20567
  async function unzoomFile(client2, input, opts = {}) {
20452
- const out = path42.resolve(opts.out ?? defaultUnzoomOut(input));
20453
- if (path42.resolve(input) === out) throw new Error("unzoom will not overwrite its own input; pass a different --out");
20568
+ const out = path43.resolve(opts.out ?? defaultUnzoomOut(input));
20569
+ if (path43.resolve(input) === out) throw new Error("unzoom will not overwrite its own input; pass a different --out");
20454
20570
  if (existsSync33(out) && !opts.force) throw new Error(`${out} already exists; pass --force to replace it`);
20455
20571
  const bytes = await readFile29(input);
20456
20572
  const metadata = imageMetadata(bytes);
@@ -20465,7 +20581,7 @@ async function unzoomFile(client2, input, opts = {}) {
20465
20581
  throw new Error(`${input} is ${width}x${height}; PixelLab's unzoom takes at most 2048x2048 worth of pixels`);
20466
20582
  }
20467
20583
  const res = await client2.unzoom({ image: { base64: bytes.toString("base64"), format }, quantize: opts.quantize });
20468
- await mkdir13(path42.dirname(out), { recursive: true });
20584
+ await mkdir13(path43.dirname(out), { recursive: true });
20469
20585
  await writeFile18(out, res.png);
20470
20586
  return { out, original: res.originalSize, unzoomed: res.unzoomedSize, zoomFactor: res.zoomFactor, usage: res.usage };
20471
20587
  }
@@ -20473,7 +20589,7 @@ var FONT_WEIGHTS = ["Bold", "Regular"];
20473
20589
  var FONT_GLYPH_SIZES = [8, 16, 32, 64];
20474
20590
  var FONT_COST_GENERATIONS = 25;
20475
20591
  function fontOutputPaths(out) {
20476
- const base = path42.resolve(out.replace(/\.(ttf|png)$/i, ""));
20592
+ const base = path43.resolve(out.replace(/\.(ttf|png)$/i, ""));
20477
20593
  return { ttf: `${base}.ttf`, atlas: `${base}.png` };
20478
20594
  }
20479
20595
  async function generateFont(client2, opts) {
@@ -20507,7 +20623,7 @@ async function generateFont(client2, opts) {
20507
20623
  client2.download(job.download_ttf_url),
20508
20624
  client2.download(job.download_atlas_url)
20509
20625
  ]);
20510
- await mkdir13(path42.dirname(ttf), { recursive: true });
20626
+ await mkdir13(path43.dirname(ttf), { recursive: true });
20511
20627
  await writeFile18(ttf, ttfBytes);
20512
20628
  await writeFile18(atlas, atlasBytes);
20513
20629
  return { jobId, ttf, atlas, glyphPx: job.glyph_px ?? null, usage: job.usage };
@@ -20523,7 +20639,7 @@ async function generateFont(client2, opts) {
20523
20639
 
20524
20640
  // src/cli/commands/pixellab-utils.ts
20525
20641
  function loadKeys(args) {
20526
- loadEnvFiles(path43.dirname(path43.resolve(args.manifest)));
20642
+ loadEnvFiles(path44.dirname(path44.resolve(args.manifest)));
20527
20643
  loadEnvFiles(process.cwd());
20528
20644
  }
20529
20645
  async function runUnzoom(args) {
@@ -20589,11 +20705,11 @@ async function runFont(args) {
20589
20705
  }
20590
20706
 
20591
20707
  // src/cli/commands/quality.ts
20592
- import path45 from "path";
20708
+ import path46 from "path";
20593
20709
  import { readFile as readFile31 } from "fs/promises";
20594
20710
 
20595
20711
  // src/pipeline/quality-regression.ts
20596
- import path44 from "path";
20712
+ import path45 from "path";
20597
20713
  import { existsSync as existsSync34 } from "fs";
20598
20714
  import { readFile as readFile30 } from "fs/promises";
20599
20715
  import { z as z13 } from "zod";
@@ -20601,7 +20717,7 @@ var SHA256_RE = /^[0-9a-f]{64}$/;
20601
20717
  var HEX_RE = /^#[0-9a-f]{6}$/;
20602
20718
  var MAX_REDMEAN_DISTANCE = 765;
20603
20719
  var QualityPathSchema = z13.string().min(1).refine(
20604
- (value) => !path44.posix.isAbsolute(value) && !value.includes("\\"),
20720
+ (value) => !path45.posix.isAbsolute(value) && !value.includes("\\"),
20605
20721
  "expected a portable relative path"
20606
20722
  );
20607
20723
  var DEFAULT_QUALITY_TOLERANCES = {
@@ -20748,7 +20864,7 @@ function measureDecodedImageQuality(png, digest2) {
20748
20864
  };
20749
20865
  }
20750
20866
  async function measureImageQuality(file) {
20751
- const absolute = path44.resolve(file);
20867
+ const absolute = path45.resolve(file);
20752
20868
  let bytes;
20753
20869
  try {
20754
20870
  bytes = await readFile30(absolute);
@@ -20770,11 +20886,11 @@ function resolveQualityInputs(raw, inputsFilePath) {
20770
20886
  }
20771
20887
  const ids = parsed.data.map((entry) => entry.id);
20772
20888
  if (new Set(ids).size !== ids.length) throw new Error("--inputs contains a duplicate quality case id.");
20773
- const root = path44.dirname(path44.resolve(inputsFilePath));
20889
+ const root = path45.dirname(path45.resolve(inputsFilePath));
20774
20890
  return parsed.data.map((entry) => ({
20775
20891
  id: entry.id,
20776
- file: path44.resolve(root, entry.path),
20777
- ...entry.record ? { record: path44.resolve(root, entry.record) } : {},
20892
+ file: path45.resolve(root, entry.path),
20893
+ ...entry.record ? { record: path45.resolve(root, entry.record) } : {},
20778
20894
  tolerances: QualityToleranceSchema.parse({
20779
20895
  ...DEFAULT_QUALITY_TOLERANCES,
20780
20896
  ...entry.tolerances
@@ -20782,7 +20898,7 @@ function resolveQualityInputs(raw, inputsFilePath) {
20782
20898
  }));
20783
20899
  }
20784
20900
  function portableRelative2(from, to) {
20785
- return path44.relative(from, path44.resolve(to)).split(path44.sep).join("/") || ".";
20901
+ return path45.relative(from, path45.resolve(to)).split(path45.sep).join("/") || ".";
20786
20902
  }
20787
20903
  async function verifyRecordForImage(recordPath, imagePath) {
20788
20904
  const verification = await verifyArtifactBundle(recordPath);
@@ -20797,20 +20913,20 @@ async function verifyRecordForImage(recordPath, imagePath) {
20797
20913
  if (record.kind !== "refine" || options === null || typeof options !== "object" || options.schema !== "pixelkiln-quality") {
20798
20914
  throw new Error(`Quality record ${recordPath} is not a PixelKiln refinement record.`);
20799
20915
  }
20800
- const outputs = record.outputs.map((output) => path44.resolve(path44.dirname(recordPath), output.path));
20801
- if (!outputs.includes(path44.resolve(imagePath))) {
20916
+ const outputs = record.outputs.map((output) => path45.resolve(path45.dirname(recordPath), output.path));
20917
+ if (!outputs.includes(path45.resolve(imagePath))) {
20802
20918
  throw new Error(`Quality record ${recordPath} does not own ${imagePath}.`);
20803
20919
  }
20804
20920
  }
20805
20921
  async function snapshotQualityBaseline(inputs, baselinePath, options = {}) {
20806
20922
  if (!inputs.length) throw new Error("Cannot snapshot an empty quality baseline.");
20807
- const absolute = path44.resolve(baselinePath);
20923
+ const absolute = path45.resolve(baselinePath);
20808
20924
  for (const input of inputs) {
20809
- if (path44.resolve(input.file) === absolute || input.record && path44.resolve(input.record) === absolute) {
20925
+ if (path45.resolve(input.file) === absolute || input.record && path45.resolve(input.record) === absolute) {
20810
20926
  throw new Error(`Quality baseline output would overwrite an input: ${absolute}.`);
20811
20927
  }
20812
20928
  }
20813
- const root = path44.dirname(absolute);
20929
+ const root = path45.dirname(absolute);
20814
20930
  const cases = [];
20815
20931
  for (const input of [...inputs].sort((a, b) => a.id.localeCompare(b.id))) {
20816
20932
  const expected = await measureImageQuality(input.file);
@@ -20850,7 +20966,7 @@ async function snapshotQualityBaseline(inputs, baselinePath, options = {}) {
20850
20966
  return { baseline, path: absolute, changed: result.changed.length > 0 };
20851
20967
  }
20852
20968
  async function readQualityBaseline(baselinePath) {
20853
- const absolute = path44.resolve(baselinePath);
20969
+ const absolute = path45.resolve(baselinePath);
20854
20970
  let raw;
20855
20971
  try {
20856
20972
  raw = JSON.parse(await readFile30(absolute, "utf8"));
@@ -20923,12 +21039,12 @@ function regressionViolations(expected, actual, tolerances) {
20923
21039
  return { violations, warnings };
20924
21040
  }
20925
21041
  async function checkQualityBaseline(baselinePath) {
20926
- const absolute = path44.resolve(baselinePath);
21042
+ const absolute = path45.resolve(baselinePath);
20927
21043
  const baseline = await readQualityBaseline(absolute);
20928
- const root = path44.dirname(absolute);
21044
+ const root = path45.dirname(absolute);
20929
21045
  const cases = [];
20930
21046
  for (const entry of baseline.cases) {
20931
- const file = path44.resolve(root, entry.file);
21047
+ const file = path45.resolve(root, entry.file);
20932
21048
  let actual = null;
20933
21049
  const violations = [];
20934
21050
  const warnings = [];
@@ -20941,7 +21057,7 @@ async function checkQualityBaseline(baselinePath) {
20941
21057
  violations.push(message4(error));
20942
21058
  }
20943
21059
  if (entry.record) {
20944
- const recordPath = path44.resolve(root, entry.record.path);
21060
+ const recordPath = path45.resolve(root, entry.record.path);
20945
21061
  try {
20946
21062
  const recordHash = await sha256File(recordPath);
20947
21063
  if (recordHash !== entry.record.sha256) violations.push("quality record hash changed");
@@ -20983,15 +21099,15 @@ async function runQuality(args) {
20983
21099
  if (!args.out) throw new Error("quality snapshot needs --out <pixelkiln.quality.json>.");
20984
21100
  let raw;
20985
21101
  try {
20986
- raw = JSON.parse(await readFile31(path45.resolve(args.inputs), "utf8"));
21102
+ raw = JSON.parse(await readFile31(path46.resolve(args.inputs), "utf8"));
20987
21103
  } catch (error) {
20988
21104
  throw new Error(
20989
- `Could not read quality inputs ${path45.resolve(args.inputs)}: ${error instanceof Error ? error.message : String(error)}`,
21105
+ `Could not read quality inputs ${path46.resolve(args.inputs)}: ${error instanceof Error ? error.message : String(error)}`,
20990
21106
  { cause: error }
20991
21107
  );
20992
21108
  }
20993
21109
  const inputs = resolveQualityInputs(raw, args.inputs);
20994
- if (path45.resolve(args.inputs) === path45.resolve(args.out)) {
21110
+ if (path46.resolve(args.inputs) === path46.resolve(args.out)) {
20995
21111
  throw new Error("quality snapshot --out must not overwrite its --inputs file.");
20996
21112
  }
20997
21113
  const result = await snapshotQualityBaseline(inputs, args.out, { force: args.force });
@@ -21004,7 +21120,7 @@ async function runQuality(args) {
21004
21120
  }, null, 2));
21005
21121
  } else {
21006
21122
  log(
21007
- ` ${result.changed ? "wrote" : "unchanged"} ${path45.relative(process.cwd(), result.path)} (${result.baseline.cases.length} case(s))`
21123
+ ` ${result.changed ? "wrote" : "unchanged"} ${path46.relative(process.cwd(), result.path)} (${result.baseline.cases.length} case(s))`
21008
21124
  );
21009
21125
  log(` Review the tolerances, commit the baseline, then run quality check in CI.`);
21010
21126
  }
@@ -21030,10 +21146,10 @@ async function runQuality(args) {
21030
21146
  }
21031
21147
 
21032
21148
  // src/cli/commands/recipe.ts
21033
- import path47 from "path";
21149
+ import path48 from "path";
21034
21150
 
21035
21151
  // src/recipes.ts
21036
- import path46 from "path";
21152
+ import path47 from "path";
21037
21153
  import { existsSync as existsSync35 } from "fs";
21038
21154
  import { readdir as readdir3, readFile as readFile32 } from "fs/promises";
21039
21155
  import { fileURLToPath as fileURLToPath2 } from "url";
@@ -21044,7 +21160,7 @@ var VERSION_RE = /^\d+\.\d+\.\d+$/;
21044
21160
  var RECIPE_FILE = "pixelkiln.recipe.json";
21045
21161
  var RECIPE_DIR_TOKEN = "{{recipeDir}}";
21046
21162
  var PortablePathSchema = z14.string().min(1).refine((value) => {
21047
- if (value.includes("\\") || path46.posix.isAbsolute(value)) return false;
21163
+ if (value.includes("\\") || path47.posix.isAbsolute(value)) return false;
21048
21164
  const parts = value.split("/");
21049
21165
  return !parts.includes("") && !parts.includes(".") && !parts.includes("..");
21050
21166
  }, "expected a portable relative path without '.', '..', or backslashes");
@@ -21220,7 +21336,7 @@ function message5(error) {
21220
21336
  return error instanceof Error ? error.message : String(error);
21221
21337
  }
21222
21338
  async function readRecipeFile(recipePath, bundled) {
21223
- const absolute = path46.resolve(recipePath);
21339
+ const absolute = path47.resolve(recipePath);
21224
21340
  let raw;
21225
21341
  try {
21226
21342
  raw = JSON.parse(await readFile32(absolute, "utf8"));
@@ -21234,13 +21350,13 @@ async function readRecipeFile(recipePath, bundled) {
21234
21350
  ` + parsed.error.issues.map((issue) => ` ${issue.path.join(".") || "$"}: ${issue.message}`).join("\n")
21235
21351
  );
21236
21352
  }
21237
- return { recipe: parsed.data, authored: raw, path: absolute, dir: path46.dirname(absolute), bundled };
21353
+ return { recipe: parsed.data, authored: raw, path: absolute, dir: path47.dirname(absolute), bundled };
21238
21354
  }
21239
21355
  async function walkRecipeFiles(root) {
21240
21356
  if (!existsSync35(root)) return [];
21241
21357
  const out = [];
21242
21358
  for (const entry of await readdir3(root, { withFileTypes: true })) {
21243
- const resolved = path46.join(root, entry.name);
21359
+ const resolved = path47.join(root, entry.name);
21244
21360
  if (entry.isDirectory()) out.push(...await walkRecipeFiles(resolved));
21245
21361
  else if (entry.isFile() && entry.name === RECIPE_FILE) out.push(resolved);
21246
21362
  }
@@ -21251,7 +21367,7 @@ function bundledRecipeRoot() {
21251
21367
  }
21252
21368
  async function listBundledRecipes(root = bundledRecipeRoot()) {
21253
21369
  const recipes = await Promise.all(
21254
- (await walkRecipeFiles(path46.resolve(root))).map((file) => readRecipeFile(file, true))
21370
+ (await walkRecipeFiles(path47.resolve(root))).map((file) => readRecipeFile(file, true))
21255
21371
  );
21256
21372
  return recipes.sort(
21257
21373
  (a, b) => a.recipe.id.localeCompare(b.recipe.id) || compareVersions(b.recipe.version, a.recipe.version)
@@ -21274,9 +21390,9 @@ function parseSelector(selector) {
21274
21390
  return { id: match[1], version: match[2] };
21275
21391
  }
21276
21392
  async function resolveRecipe(target, bundledRoot = bundledRecipeRoot()) {
21277
- const local = path46.resolve(target);
21393
+ const local = path47.resolve(target);
21278
21394
  if (existsSync35(local)) {
21279
- const recipePath = path46.basename(local) === RECIPE_FILE ? local : path46.join(local, RECIPE_FILE);
21395
+ const recipePath = path47.basename(local) === RECIPE_FILE ? local : path47.join(local, RECIPE_FILE);
21280
21396
  return readRecipeFile(recipePath, false);
21281
21397
  }
21282
21398
  if (target.startsWith(".") || target.startsWith("/") || target.includes("\\")) {
@@ -21290,7 +21406,7 @@ async function resolveRecipe(target, bundledRoot = bundledRecipeRoot()) {
21290
21406
  return matches[0];
21291
21407
  }
21292
21408
  async function verifyFile(root, file) {
21293
- const absolute = path46.resolve(root, ...file.path.split("/"));
21409
+ const absolute = path47.resolve(root, ...file.path.split("/"));
21294
21410
  if (!existsSync35(absolute)) {
21295
21411
  return { path: file.path, expectedSha256: file.sha256, actualSha256: null, status: "missing" };
21296
21412
  }
@@ -21306,7 +21422,7 @@ async function verifyRecipe(target, options = {}) {
21306
21422
  const loaded = await resolveRecipe(target, options.bundledRoot);
21307
21423
  const actualIntegrity = recipeDigest(loaded.authored);
21308
21424
  const files = await Promise.all(loaded.recipe.files.map((file) => verifyFile(loaded.dir, file)));
21309
- const modelRoot = options.modelRoot ? path46.resolve(options.modelRoot) : null;
21425
+ const modelRoot = options.modelRoot ? path47.resolve(options.modelRoot) : null;
21310
21426
  const models = modelRoot ? await Promise.all(loaded.recipe.models.map(async (model) => ({
21311
21427
  ...await verifyFile(modelRoot, model),
21312
21428
  source: model.source,
@@ -21340,7 +21456,7 @@ async function verifyRecipe(target, options = {}) {
21340
21456
  };
21341
21457
  }
21342
21458
  function renderStyle(style, destination, cwd) {
21343
- const portableDir = path46.relative(cwd, destination).split(path46.sep).join("/") || ".";
21459
+ const portableDir = path47.relative(cwd, destination).split(path47.sep).join("/") || ".";
21344
21460
  const replace = (value) => {
21345
21461
  if (typeof value === "string") return value.replaceAll(RECIPE_DIR_TOKEN, portableDir);
21346
21462
  if (Array.isArray(value)) return value.map(replace);
@@ -21352,19 +21468,19 @@ function renderStyle(style, destination, cwd) {
21352
21468
  return replace(style);
21353
21469
  }
21354
21470
  async function installRecipe(target, options = {}) {
21355
- const cwd = path46.resolve(options.cwd ?? process.cwd());
21471
+ const cwd = path47.resolve(options.cwd ?? process.cwd());
21356
21472
  const loaded = await resolveRecipe(target, options.bundledRoot);
21357
21473
  const verification = await verifyRecipe(loaded.path);
21358
21474
  if (!verification.ok) {
21359
21475
  throw new Error(`Recipe ${loaded.recipe.id}@${loaded.recipe.version} failed integrity verification.`);
21360
21476
  }
21361
- const destination = options.out ? path46.resolve(cwd, options.out) : path46.join(cwd, "pixelkiln-recipes", ...loaded.recipe.id.split("/"), loaded.recipe.version);
21477
+ const destination = options.out ? path47.resolve(cwd, options.out) : path47.join(cwd, "pixelkiln-recipes", ...loaded.recipe.id.split("/"), loaded.recipe.version);
21362
21478
  const sources = [
21363
21479
  { path: RECIPE_FILE, source: loaded.path },
21364
- ...loaded.recipe.files.map((file) => ({ path: file.path, source: path46.join(loaded.dir, ...file.path.split("/")) }))
21480
+ ...loaded.recipe.files.map((file) => ({ path: file.path, source: path47.join(loaded.dir, ...file.path.split("/")) }))
21365
21481
  ];
21366
21482
  const files = await Promise.all(sources.map(async (file) => ({
21367
- path: path46.join(destination, ...file.path.split("/")),
21483
+ path: path47.join(destination, ...file.path.split("/")),
21368
21484
  data: await readFile32(file.source)
21369
21485
  })));
21370
21486
  if (!options.force) {
@@ -21464,14 +21580,14 @@ async function runRecipe(args) {
21464
21580
  }, null, 2));
21465
21581
  } else {
21466
21582
  log(` installed ${result.recipe.id}@${result.recipe.version}`);
21467
- log(` ${path47.relative(process.cwd(), result.destination) || "."}`);
21583
+ log(` ${path48.relative(process.cwd(), result.destination) || "."}`);
21468
21584
  log(`
21469
21585
  Add this entry under your manifest's styles object:`);
21470
21586
  log(JSON.stringify({ [result.styleId]: result.style }, null, 2));
21471
21587
  if (result.recipe.models.length) {
21472
21588
  log(`
21473
21589
  Models are not downloaded automatically. Verify them with:`);
21474
- log(` pixelkiln recipe verify ${path47.relative(process.cwd(), result.destination)} --model-root <ComfyUI/models>`);
21590
+ log(` pixelkiln recipe verify ${path48.relative(process.cwd(), result.destination)} --model-root <ComfyUI/models>`);
21475
21591
  }
21476
21592
  }
21477
21593
  return;
@@ -21479,7 +21595,7 @@ async function runRecipe(args) {
21479
21595
  }
21480
21596
 
21481
21597
  // src/cli/commands/refine.ts
21482
- import path48 from "path";
21598
+ import path49 from "path";
21483
21599
  async function runRefine(args) {
21484
21600
  if (args.from || args.subcommand === "approve") {
21485
21601
  if (!args.from) {
@@ -21509,12 +21625,12 @@ async function runRefine(args) {
21509
21625
  ` recovered ${result3.detection.columns}x${result3.detection.rows} native grid (${result3.detection.confidence}: ${result3.detection.consensus})`
21510
21626
  );
21511
21627
  log(` enforced ${result3.palette.length}-color palette without dithering`);
21512
- log(` wrote ${path48.relative(process.cwd(), result3.output)}`);
21513
- log(` quality record: ${path48.relative(process.cwd(), result3.record)}`);
21628
+ log(` wrote ${path49.relative(process.cwd(), result3.output)}`);
21629
+ log(` quality record: ${path49.relative(process.cwd(), result3.record)}`);
21514
21630
  log(`
21515
21631
  Automated checks passed. Human 1\xD7 review is still required:`);
21516
21632
  log(
21517
- ` pixelkiln refine approve --from ${path48.relative(process.cwd(), result3.record)} --reviewer "Your Name"`
21633
+ ` pixelkiln refine approve --from ${path49.relative(process.cwd(), result3.record)} --reviewer "Your Name"`
21518
21634
  );
21519
21635
  }
21520
21636
  return;
@@ -21542,7 +21658,7 @@ async function runRefine(args) {
21542
21658
  if (args.json) log(JSON.stringify(result3, null, 2));
21543
21659
  else {
21544
21660
  log(
21545
- result3.options.frameSet ? ` approved ${result3.outputs.length} frames in ${path48.relative(process.cwd(), result3.record)} by ${args.reviewer.trim()}` : ` approved ${path48.relative(process.cwd(), result3.output)} by ${args.reviewer.trim()}`
21661
+ result3.options.frameSet ? ` approved ${result3.outputs.length} frames in ${path49.relative(process.cwd(), result3.record)} by ${args.reviewer.trim()}` : ` approved ${path49.relative(process.cwd(), result3.output)} by ${args.reviewer.trim()}`
21546
21662
  );
21547
21663
  }
21548
21664
  return;
@@ -21551,7 +21667,7 @@ async function runRefine(args) {
21551
21667
  if (args.json) {
21552
21668
  log(JSON.stringify(result2, null, 2));
21553
21669
  } else {
21554
- log(` ${result2.safe ? "release-ready" : "not release-ready"}: ${path48.relative(process.cwd(), result2.output || result2.record)}`);
21670
+ log(` ${result2.safe ? "release-ready" : "not release-ready"}: ${path49.relative(process.cwd(), result2.output || result2.record)}`);
21555
21671
  for (const reason of result2.reasons) log(` ${reason}`);
21556
21672
  }
21557
21673
  if (!result2.safe) process.exitCode = 1;
@@ -21597,7 +21713,7 @@ async function runRefine(args) {
21597
21713
  log(` ${item.state.padEnd(16)} ${item.key} ${item.reason}`);
21598
21714
  if (item.state === "needs-approval") {
21599
21715
  log(
21600
- ` pixelkiln refine approve --from ${path48.relative(process.cwd(), item.record)} --reviewer "Your Name"`
21716
+ ` pixelkiln refine approve --from ${path49.relative(process.cwd(), item.record)} --reviewer "Your Name"`
21601
21717
  );
21602
21718
  }
21603
21719
  }
@@ -21608,7 +21724,7 @@ async function runRefine(args) {
21608
21724
  }
21609
21725
 
21610
21726
  // src/cli/commands/skeleton.ts
21611
- import path49 from "path";
21727
+ import path50 from "path";
21612
21728
  import { existsSync as existsSync36 } from "fs";
21613
21729
  import { readFile as readFile33, writeFile as writeFile19 } from "fs/promises";
21614
21730
 
@@ -21721,12 +21837,12 @@ var Canvas = class {
21721
21837
  async function runEstimateSkeleton(args) {
21722
21838
  const image = args.target;
21723
21839
  if (!image) throw new UsageError("estimate-skeleton needs an image path: pixelkiln estimate-skeleton <image> [--out keypoints.json]");
21724
- if (args.out && existsSync36(path49.resolve(args.out)) && !args.force) {
21840
+ if (args.out && existsSync36(path50.resolve(args.out)) && !args.force) {
21725
21841
  throw new UsageError(`${args.out} already exists; pass --force to replace it`);
21726
21842
  }
21727
- loadEnvFiles(path49.dirname(path49.resolve(args.manifest)));
21843
+ loadEnvFiles(path50.dirname(path50.resolve(args.manifest)));
21728
21844
  loadEnvFiles(process.cwd());
21729
- const bytes = await readFile33(path49.resolve(image));
21845
+ const bytes = await readFile33(path50.resolve(image));
21730
21846
  const metadata = imageMetadata(bytes);
21731
21847
  if (!metadata) throw new UsageError(`${image} is not a readable PNG or JPEG`);
21732
21848
  const sizeProblem = estimateSkeletonSizeProblem(metadata.width, metadata.height);
@@ -21738,7 +21854,7 @@ async function runEstimateSkeleton(args) {
21738
21854
  const frames = args.frames ?? DEFAULT_SCAFFOLD_FRAMES;
21739
21855
  const json = JSON.stringify(scaffoldSkeletonSet(res.keypoints, frames), null, 2);
21740
21856
  if (args.out) {
21741
- await writeFile19(path49.resolve(args.out), json + "\n");
21857
+ await writeFile19(path50.resolve(args.out), json + "\n");
21742
21858
  log(` wrote ${args.out}: the estimated pose and ${frames} frames to edit`);
21743
21859
  log(` preview it with: pixelkiln skeleton-preview ${args.out} --from ${image}`);
21744
21860
  } else {
@@ -21751,8 +21867,8 @@ async function runSkeletonPreview(args) {
21751
21867
  let sourceFile;
21752
21868
  let label;
21753
21869
  if (target.endsWith(".json")) {
21754
- keypointsFile = path49.resolve(target);
21755
- sourceFile = args.from ? path49.resolve(args.from) : void 0;
21870
+ keypointsFile = path50.resolve(target);
21871
+ sourceFile = args.from ? path50.resolve(args.from) : void 0;
21756
21872
  label = target;
21757
21873
  } else {
21758
21874
  const loaded = await loadManifest(args.manifest);
@@ -21764,7 +21880,7 @@ async function runSkeletonPreview(args) {
21764
21880
  );
21765
21881
  }
21766
21882
  keypointsFile = spec.revision.keypointsFile;
21767
- sourceFile = args.from ? path49.resolve(args.from) : spec.revision.sourceFile;
21883
+ sourceFile = args.from ? path50.resolve(args.from) : spec.revision.sourceFile;
21768
21884
  label = `${spec.styleId}/${spec.assetId}`;
21769
21885
  }
21770
21886
  if (!existsSync36(keypointsFile)) throw new UsageError(`Keypoints file does not exist yet: ${keypointsFile}`);
@@ -21785,10 +21901,10 @@ async function runSkeletonPreview(args) {
21785
21901
  source = decodePng(bytes);
21786
21902
  }
21787
21903
  }
21788
- const out = path49.resolve(args.out ?? keypointsFile.replace(/\.json$/, "") + ".preview.png");
21904
+ const out = path50.resolve(args.out ?? keypointsFile.replace(/\.json$/, "") + ".preview.png");
21789
21905
  await writeFile19(out, renderSkeletonSheet(set, { source }));
21790
21906
  log(` ${label}: ${set.frames.length} frames after the starting pose`);
21791
- const shown = path49.relative(process.cwd(), out);
21907
+ const shown = path50.relative(process.cwd(), out);
21792
21908
  log(` wrote ${shown.startsWith("..") ? out : shown}`);
21793
21909
  }
21794
21910
 
@@ -21834,15 +21950,15 @@ async function runTools(args) {
21834
21950
  }
21835
21951
 
21836
21952
  // src/cli/commands/workspace.ts
21837
- import path50 from "path";
21953
+ import path51 from "path";
21838
21954
  import { existsSync as existsSync37 } from "fs";
21839
21955
  async function runWorkspace(args) {
21840
- const workspacePath = path50.resolve(args.workspace ?? "pixelkiln.workspace.json");
21841
- const dir = path50.dirname(workspacePath);
21956
+ const workspacePath = path51.resolve(args.workspace ?? "pixelkiln.workspace.json");
21957
+ const dir = path51.dirname(workspacePath);
21842
21958
  if (args.subcommand === "add") {
21843
- const manifestPath = path50.resolve(args.target);
21959
+ const manifestPath = path51.resolve(args.target);
21844
21960
  const loadedTarget = await loadManifest(manifestPath);
21845
- const lockPath = args.explicitLock ? path50.resolve(args.explicitLock) : path50.join(path50.dirname(manifestPath), "pixelkiln.lock.json");
21961
+ const lockPath = args.explicitLock ? path51.resolve(args.explicitLock) : path51.join(path51.dirname(manifestPath), "pixelkiln.lock.json");
21846
21962
  const ws = await loadWorkspace(workspacePath);
21847
21963
  const id = args.name ?? loadedTarget.manifest.name;
21848
21964
  if (ws.projects.some((p) => p.id === id)) {
@@ -21862,7 +21978,7 @@ async function runWorkspace(args) {
21862
21978
  ...args.account ? { account: args.account } : {}
21863
21979
  };
21864
21980
  await saveWorkspace(workspacePath, { version: 1, projects: [...ws.projects, project] });
21865
- log(` registered "${id}" in ${path50.relative(process.cwd(), workspacePath)}`);
21981
+ log(` registered "${id}" in ${path51.relative(process.cwd(), workspacePath)}`);
21866
21982
  log(` manifest: ${project.manifest}`);
21867
21983
  log(` lock: ${project.lock}`);
21868
21984
  if (!existsSync37(lockPath)) {
@@ -21874,7 +21990,7 @@ async function runWorkspace(args) {
21874
21990
  }
21875
21991
  if (args.subcommand === "remove") {
21876
21992
  const ws = await loadWorkspace(workspacePath);
21877
- const resolvedTarget = path50.resolve(args.target);
21993
+ const resolvedTarget = path51.resolve(args.target);
21878
21994
  const match = ws.projects.find(
21879
21995
  (p) => p.id === args.target || resolveProject(dir, p).manifestPath === resolvedTarget
21880
21996
  );
@@ -21885,7 +22001,7 @@ async function runWorkspace(args) {
21885
22001
  version: 1,
21886
22002
  projects: ws.projects.filter((p) => p !== match)
21887
22003
  });
21888
- log(` removed "${match.id}" from ${path50.relative(process.cwd(), workspacePath)}`);
22004
+ log(` removed "${match.id}" from ${path51.relative(process.cwd(), workspacePath)}`);
21889
22005
  return;
21890
22006
  }
21891
22007
  if (args.subcommand === "list") {
@@ -21897,9 +22013,9 @@ async function runWorkspace(args) {
21897
22013
  if (args.json) {
21898
22014
  log(JSON.stringify({ version: 1, workspace: workspacePath, projects: ws.projects, diagnostics }, null, 2));
21899
22015
  } else if (!ws.projects.length) {
21900
- log(` no projects registered in ${path50.relative(process.cwd(), workspacePath)}`);
22016
+ log(` no projects registered in ${path51.relative(process.cwd(), workspacePath)}`);
21901
22017
  } else {
21902
- log(` ${ws.projects.length} project(s) in ${path50.relative(process.cwd(), workspacePath)}:`);
22018
+ log(` ${ws.projects.length} project(s) in ${path51.relative(process.cwd(), workspacePath)}:`);
21903
22019
  for (const p of ws.projects) {
21904
22020
  log(` ${p.id.padEnd(24)} ${p.manifest.padEnd(40)} (${p.provider}${p.account ? `, ${p.account}` : ""})`);
21905
22021
  }
@@ -21917,7 +22033,7 @@ async function runWorkspace(args) {
21917
22033
  if (args.json) {
21918
22034
  log(JSON.stringify({ ...report, workspace: workspacePath }, null, 2));
21919
22035
  } else {
21920
- log(` workspace: ${path50.relative(process.cwd(), workspacePath)}`);
22036
+ log(` workspace: ${path51.relative(process.cwd(), workspacePath)}`);
21921
22037
  for (const p of report.projects) {
21922
22038
  if (p.error) {
21923
22039
  log(`