pixelkiln 0.84.0 → 0.86.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")));
@@ -777,6 +777,7 @@ function specHash(spec, styleImageHashes, providerOptionIdentity = spec.provider
777
777
  revision: spec.revision ? {
778
778
  mode: spec.revision.mode,
779
779
  from: spec.revision.sourceAssetId,
780
+ fromStyle: spec.revision.sourceStyleId,
780
781
  sourceSha256: spec.revision.sourceSha256,
781
782
  maskSha256: spec.revision.maskSha256,
782
783
  strength: spec.revision.strength,
@@ -1126,8 +1127,16 @@ var RevisionDitheringSchema = z.enum(["none", "2x2", "4x4", "8x8"]);
1126
1127
  var RevisionSchema = z.object({
1127
1128
  /** What kind of controlled change the provider workflow performs. */
1128
1129
  mode: RevisionModeSchema,
1129
- /** Parent asset id in the same style. */
1130
+ /** Parent asset id. It lives in this asset's own style unless `fromStyle` names another. */
1130
1131
  from: z.string().min(1),
1132
+ /**
1133
+ * The style the parent lives in, when it is not this asset's own. The
1134
+ * parent is generated by that style's provider and model, and this
1135
+ * revision starts from its output file, so a chain (generate with one
1136
+ * model, clean up with another) is declared instead of restated by hand.
1137
+ * Styles may depend on one another but not in a cycle.
1138
+ */
1139
+ fromStyle: z.string().min(1).optional(),
1131
1140
  /** Manifest-relative black/white PNG. Required only for masked inpainting. */
1132
1141
  mask: z.string().min(1).optional(),
1133
1142
  /** Provider-neutral edit strength. The active adapter must bind it explicitly. */
@@ -1988,6 +1997,7 @@ var LockEntrySchema = z.object({
1988
1997
  revision: z.object({
1989
1998
  mode: RevisionModeSchema,
1990
1999
  sourceAssetId: z.string().min(1),
2000
+ sourceStyleId: z.string().min(1).optional(),
1991
2001
  sourceSha256: z.string().regex(/^[0-9a-f]{64}$/),
1992
2002
  maskSha256: z.string().regex(/^[0-9a-f]{64}$/).optional(),
1993
2003
  strength: z.number().min(0).max(1).optional(),
@@ -4405,7 +4415,7 @@ async function inspectRevision(spec, lock, seen) {
4405
4415
  if (!dependency.ready) {
4406
4416
  return {
4407
4417
  ready: false,
4408
- reason: `revision source ${spec.styleId}/${revision.sourceAssetId} is not ready: ${dependency.reason}`
4418
+ reason: `revision source ${revision.sourceStyleId ?? spec.styleId}/${revision.sourceAssetId} is not ready: ${dependency.reason}`
4409
4419
  };
4410
4420
  }
4411
4421
  if (revision.sourceMembers) {
@@ -4818,7 +4828,7 @@ async function confirm(question, auto) {
4818
4828
  }
4819
4829
 
4820
4830
  // src/cli/commands/account.ts
4821
- import path22 from "path";
4831
+ import path23 from "path";
4822
4832
  import { existsSync as existsSync16 } from "fs";
4823
4833
 
4824
4834
  // src/env.ts
@@ -6251,9 +6261,9 @@ var PixelLabClient = class {
6251
6261
  apiKey;
6252
6262
  http;
6253
6263
  /** Retries transport failures, 429, and 5xx; see http.ts for the policy. */
6254
- async request(path51, init) {
6264
+ async request(path52, init) {
6255
6265
  const auth = this.apiKey.startsWith("Bearer ") ? this.apiKey : `Bearer ${this.apiKey}`;
6256
- const res = await this.http(`${BASE}${path51}`, {
6266
+ const res = await this.http(`${BASE}${path52}`, {
6257
6267
  ...init,
6258
6268
  headers: {
6259
6269
  Authorization: auth,
@@ -6263,13 +6273,13 @@ var PixelLabClient = class {
6263
6273
  });
6264
6274
  const text = await res.text();
6265
6275
  if (!res.ok) {
6266
- throw new PixelLabError(`${init?.method ?? "GET"} ${path51} \u2192 ${res.status}`, res.status, text);
6276
+ throw new PixelLabError(`${init?.method ?? "GET"} ${path52} \u2192 ${res.status}`, res.status, text);
6267
6277
  }
6268
6278
  if (!text) return {};
6269
6279
  try {
6270
6280
  return JSON.parse(text);
6271
6281
  } catch {
6272
- throw new Error(`${init?.method ?? "GET"} ${path51} returned invalid JSON`);
6282
+ throw new Error(`${init?.method ?? "GET"} ${path52} returned invalid JSON`);
6273
6283
  }
6274
6284
  }
6275
6285
  async balance() {
@@ -6674,10 +6684,10 @@ var PixelLabClient = class {
6674
6684
  const palette = args.paletteSwatchBase64 ? { color_image: { type: "base64", base64: args.paletteSwatchBase64, format: "png" }, force_colors: true } : {};
6675
6685
  const encode = (image) => ({ type: "base64", base64: image.base64, format: image.format });
6676
6686
  const south = args.reference?.south;
6677
- let path51;
6687
+ let path52;
6678
6688
  let body;
6679
6689
  if (args.mode === "v3") {
6680
- path51 = "/create-character-v3";
6690
+ path52 = "/create-character-v3";
6681
6691
  body = {
6682
6692
  description: args.description,
6683
6693
  image_size: imageSize,
@@ -6691,7 +6701,7 @@ var PixelLabClient = class {
6691
6701
  ...args.enhancePrompt ? { enhance_prompt: true } : {}
6692
6702
  };
6693
6703
  } else if (args.mode === "pro-flash") {
6694
- path51 = "/create-character-pro-flash";
6704
+ path52 = "/create-character-pro-flash";
6695
6705
  const traits = args.styleTraits;
6696
6706
  body = {
6697
6707
  description: args.description,
@@ -6715,7 +6725,7 @@ var PixelLabClient = class {
6715
6725
  } : {}
6716
6726
  };
6717
6727
  } else if (args.mode === "pro") {
6718
- path51 = "/create-character-pro";
6728
+ path52 = "/create-character-pro";
6719
6729
  body = {
6720
6730
  description: args.description,
6721
6731
  image_size: imageSize,
@@ -6729,7 +6739,7 @@ var PixelLabClient = class {
6729
6739
  ...!south && args.styleCharacterId ? { style_character_id: args.styleCharacterId } : {}
6730
6740
  };
6731
6741
  } else {
6732
- path51 = args.directions === 4 ? "/create-character-with-4-directions" : "/create-character-with-8-directions";
6742
+ path52 = args.directions === 4 ? "/create-character-with-4-directions" : "/create-character-with-8-directions";
6733
6743
  body = {
6734
6744
  description: args.description,
6735
6745
  image_size: imageSize,
@@ -6746,8 +6756,8 @@ var PixelLabClient = class {
6746
6756
  ...palette
6747
6757
  };
6748
6758
  }
6749
- const raw = await this.request(path51, { method: "POST", body: JSON.stringify(body) });
6750
- return validateResponse(CharacterSubmitSchema, raw, path51.slice(1));
6759
+ const raw = await this.request(path52, { method: "POST", body: JSON.stringify(body) });
6760
+ return validateResponse(CharacterSubmitSchema, raw, path52.slice(1));
6751
6761
  }
6752
6762
  /** A text edit of an existing character, applied to every direction and stored as a sibling. */
6753
6763
  async createCharacterState(args) {
@@ -9725,9 +9735,9 @@ var RetroDiffusionClient = class {
9725
9735
  if (!response.ok) throw new ProviderError("retrodiffusion", `Retro Diffusion download failed (${response.status})`, { status: response.status });
9726
9736
  return Buffer.from(await response.arrayBuffer());
9727
9737
  }
9728
- async call(path51, init = {}) {
9738
+ async call(path52, init = {}) {
9729
9739
  if (!this.token) throw new Error("RD_API_KEY is not set");
9730
- const response = await this.request(`${this.baseUrl}${path51}`, {
9740
+ const response = await this.request(`${this.baseUrl}${path52}`, {
9731
9741
  ...init,
9732
9742
  headers: {
9733
9743
  "Content-Type": "application/json",
@@ -10036,8 +10046,13 @@ function retroError(value) {
10036
10046
  }
10037
10047
 
10038
10048
  // src/providers/scenario.ts
10049
+ import { createHash as createHash3 } from "crypto";
10050
+ import { mkdirSync as mkdirSync2, readFileSync as readFileSync3, renameSync, writeFileSync as writeFileSync2 } from "fs";
10051
+ import os2 from "os";
10052
+ import path13 from "path";
10039
10053
  var DEFAULT_BASE_URL3 = "https://api.cloud.scenario.com/v1";
10040
10054
  var SOURCE_PROTOCOL2 = "scenario:";
10055
+ var SUBMIT_RETRIES = 8;
10041
10056
  var PROTECTED_PARAMETERS = /* @__PURE__ */ new Set([
10042
10057
  "dryRun",
10043
10058
  "height",
@@ -10084,7 +10099,11 @@ var ScenarioClient = class {
10084
10099
  async submit(modelId, body, projectId) {
10085
10100
  const response = await this.call(
10086
10101
  `/generate/custom/${encodeURIComponent(modelId)}`,
10087
- { method: "POST", body: JSON.stringify(body) },
10102
+ // A 429 here means this account's concurrent-job limit refused the
10103
+ // request, so nothing was accepted or billed; wait it out instead of
10104
+ // failing the asset. The default four retries give up after about half
10105
+ // a minute, before a running job has finished.
10106
+ { method: "POST", body: JSON.stringify(body), retries: SUBMIT_RETRIES },
10088
10107
  { projectId }
10089
10108
  );
10090
10109
  const job = response?.job;
@@ -10092,6 +10111,29 @@ var ScenarioClient = class {
10092
10111
  if (!jobId) throw new Error("Scenario did not return an async job id");
10093
10112
  return jobId;
10094
10113
  }
10114
+ /**
10115
+ * Uploads one image (POST /assets) and returns its asset id, which a model's
10116
+ * reference input takes. Free, and idempotent per image: the id is kept by
10117
+ * content hash so a rerun does not upload the same bytes again.
10118
+ */
10119
+ async uploadImage(image, projectId) {
10120
+ const bytes = Buffer.from(image.base64, "base64");
10121
+ const digest2 = sha256(bytes);
10122
+ const cached = readUploadCache()[uploadCacheKey(this.baseUrl, projectId, digest2)];
10123
+ if (cached) return cached;
10124
+ const response = await this.call(
10125
+ "/assets",
10126
+ {
10127
+ method: "POST",
10128
+ body: JSON.stringify({ image: image.base64, name: `pixelkiln-${digest2.slice(0, 12)}.${image.format === "png" ? "png" : "jpg"}` })
10129
+ },
10130
+ { projectId }
10131
+ );
10132
+ const id = response?.asset?.id;
10133
+ if (typeof id !== "string" || !id) throw new Error("Scenario did not return an uploaded asset id");
10134
+ writeUploadCache(uploadCacheKey(this.baseUrl, projectId, digest2), id);
10135
+ return id;
10136
+ }
10095
10137
  async job(id, projectId) {
10096
10138
  const response = await this.call(`/jobs/${encodeURIComponent(id)}`, {}, { projectId });
10097
10139
  const raw = response?.job;
@@ -10218,6 +10260,14 @@ var ScenarioProvider = class _ScenarioProvider {
10218
10260
  supports(generator) {
10219
10261
  return generator === "map";
10220
10262
  }
10263
+ /**
10264
+ * An edit is a generation with the parent as a reference image, which the
10265
+ * editing models (GPT Image, Gemini, FLUX) take. Masked inpainting and the
10266
+ * PixelLab-specific modes have no common Scenario input.
10267
+ */
10268
+ supportsRevision(mode2) {
10269
+ return mode2 === "image-to-image";
10270
+ }
10221
10271
  estimate(spec) {
10222
10272
  const options = scenarioOptions(spec);
10223
10273
  return {
@@ -10238,11 +10288,31 @@ var ScenarioProvider = class _ScenarioProvider {
10238
10288
  if (!Number.isInteger(count) || count < 1 || count > 4) {
10239
10289
  throw new Error("Scenario numOutputs must be a whole number from 1 to 4");
10240
10290
  }
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");
10291
+ if (!Number.isInteger(spec.width) || spec.width < 16 || spec.width > 4096 || !Number.isInteger(spec.height) || spec.height < 16 || spec.height > 4096) {
10292
+ throw new Error("Scenario width and height must be whole numbers from 16 to 4096");
10243
10293
  }
10244
- if (styleImages.length) {
10245
- throw new Error("Scenario styleImages are not supported yet; use a reusable Scenario model");
10294
+ const revision = spec.revision;
10295
+ if (revision) {
10296
+ if (revision.sourceMembers?.length) {
10297
+ throw new Error(
10298
+ `Scenario edits one image; ${revision.sourceAssetId} is a ${revision.sourceMembers.length}-member set`
10299
+ );
10300
+ }
10301
+ if (revision.strength != null) {
10302
+ throw new Error("Scenario image-to-image takes no strength; use the model's own parameters");
10303
+ }
10304
+ }
10305
+ const parameter = referenceParameter(options);
10306
+ if ((styleImages.length || revision) && Object.hasOwn(options.parameters ?? {}, parameter)) {
10307
+ throw new Error(
10308
+ `Scenario parameters.${parameter} is the reference-image input and is filled from styleImages and revisions`
10309
+ );
10310
+ }
10311
+ if (styleImages.length + (revision ? 1 : 0) > 1 && options.referenceArray === false) {
10312
+ throw new Error(`Scenario ${parameter} takes one image; use one style image or a revision, not both`);
10313
+ }
10314
+ if (options.referenceParameter != null && (typeof options.referenceParameter !== "string" || !options.referenceParameter.trim())) {
10315
+ throw new Error("Scenario referenceParameter must be a non-empty string");
10246
10316
  }
10247
10317
  if (options.projectId != null && (typeof options.projectId !== "string" || !options.projectId.trim())) {
10248
10318
  throw new Error("Scenario projectId must be a non-empty string");
@@ -10255,7 +10325,12 @@ var ScenarioProvider = class _ScenarioProvider {
10255
10325
  async submit(spec, styleImages) {
10256
10326
  this.validate(spec, styleImages);
10257
10327
  const options = scenarioOptions(spec);
10258
- const body = requestBody(spec, options);
10328
+ const references = [];
10329
+ if (spec.revision) references.push(readRevisionSource(spec));
10330
+ for (const image of styleImages) references.push({ base64: image.base64, format: image.format });
10331
+ const referenceAssetIds = [];
10332
+ for (const image of references) referenceAssetIds.push(await this.client.uploadImage(image, options.projectId));
10333
+ const body = requestBody(spec, options, referenceAssetIds);
10259
10334
  const quote = await this.client.quote(options.modelId, body, options.projectId);
10260
10335
  if (quote.total > options.maxComputeUnits + Number.EPSILON) {
10261
10336
  throw new Error(
@@ -10272,7 +10347,8 @@ var ScenarioProvider = class _ScenarioProvider {
10272
10347
  quotedComputeUnits: quote.total,
10273
10348
  creativeUnitsCost: quote.creativeUnitsCost,
10274
10349
  costDetails: quote.costDetails,
10275
- ipDetectionCost: quote.ipDetectionCost
10350
+ ipDetectionCost: quote.ipDetectionCost,
10351
+ ...referenceAssetIds.length ? { referenceAssetIds } : {}
10276
10352
  }
10277
10353
  };
10278
10354
  }
@@ -10294,7 +10370,13 @@ var ScenarioProvider = class _ScenarioProvider {
10294
10370
  }
10295
10371
  const assetIds = scenarioAssetIds(job);
10296
10372
  if (!assetIds.length) return { status: "failed", error: "Scenario job returned no asset IDs" };
10297
- const assets = await this.assets(assetIds, reference.projectId);
10373
+ let assets;
10374
+ try {
10375
+ assets = await this.assets(assetIds, reference.projectId);
10376
+ } catch (error) {
10377
+ if (error instanceof UnsupportedScenarioOutput) return { status: "failed", error: error.message };
10378
+ throw error;
10379
+ }
10298
10380
  if (assets.length > 1) {
10299
10381
  return {
10300
10382
  status: "review",
@@ -10349,6 +10431,32 @@ var ScenarioProvider = class _ScenarioProvider {
10349
10431
  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
10432
  }
10351
10433
  };
10434
+ function uploadCachePath() {
10435
+ return process.env.SCENARIO_UPLOAD_CACHE ?? path13.join(os2.tmpdir(), "pixelkiln-scenario", "uploads.json");
10436
+ }
10437
+ function uploadCacheKey(baseUrl, projectId, digest2) {
10438
+ return createHash3("sha256").update(`${baseUrl}
10439
+ ${projectId ?? ""}
10440
+ ${digest2}`).digest("hex");
10441
+ }
10442
+ function readUploadCache() {
10443
+ try {
10444
+ const value = JSON.parse(readFileSync3(uploadCachePath(), "utf8"));
10445
+ return value && typeof value === "object" && !Array.isArray(value) ? value : {};
10446
+ } catch {
10447
+ return {};
10448
+ }
10449
+ }
10450
+ function writeUploadCache(key, assetId) {
10451
+ try {
10452
+ const file = uploadCachePath();
10453
+ mkdirSync2(path13.dirname(file), { recursive: true });
10454
+ const tmp = `${file}.${process.pid}.tmp`;
10455
+ writeFileSync2(tmp, JSON.stringify({ ...readUploadCache(), [key]: assetId }));
10456
+ renameSync(tmp, file);
10457
+ } catch {
10458
+ }
10459
+ }
10352
10460
  function scenarioOptions(spec) {
10353
10461
  return spec.providerOptions;
10354
10462
  }
@@ -10364,9 +10472,25 @@ function scenarioBaseUrl(value) {
10364
10472
  }
10365
10473
  return url.href.replace(/\/$/, "");
10366
10474
  }
10367
- function requestBody(spec, options) {
10475
+ function referenceParameter(options) {
10476
+ return options.referenceParameter?.trim() || "referenceImages";
10477
+ }
10478
+ function readRevisionSource(spec) {
10479
+ const revision = spec.revision;
10480
+ if (!revision.sourceFile || !revision.sourceSha256 || !revision.sourceFormat) {
10481
+ throw new Error(`${spec.styleId}/${spec.assetId}: revision source is not ready`);
10482
+ }
10483
+ const bytes = readFileSync3(revision.sourceFile);
10484
+ if (sha256(bytes) !== revision.sourceSha256) {
10485
+ throw new Error(`${spec.styleId}/${spec.assetId}: revision source changed after the manifest was resolved`);
10486
+ }
10487
+ return { base64: bytes.toString("base64"), format: revision.sourceFormat };
10488
+ }
10489
+ function requestBody(spec, options, referenceAssetIds = []) {
10490
+ const parameter = referenceParameter(options);
10368
10491
  return {
10369
10492
  ...options.parameters ?? {},
10493
+ ...referenceAssetIds.length ? { [parameter]: options.referenceArray === false ? referenceAssetIds[0] : referenceAssetIds } : {},
10370
10494
  prompt: spec.prompt,
10371
10495
  width: spec.width,
10372
10496
  height: spec.height,
@@ -10456,9 +10580,11 @@ function scenarioAssetIds(job) {
10456
10580
  function assetDownloadUrl(asset) {
10457
10581
  return asset.originalFileUrl || asset.url;
10458
10582
  }
10583
+ var UnsupportedScenarioOutput = class extends Error {
10584
+ };
10459
10585
  function assertPngAsset(asset) {
10460
10586
  if (asset.originalMimeType && asset.originalMimeType !== MediaType.PNG) {
10461
- throw new Error(
10587
+ throw new UnsupportedScenarioOutput(
10462
10588
  `Scenario asset ${asset.id} is ${asset.originalMimeType}; PixelKiln's Scenario MVP requires PNG output`
10463
10589
  );
10464
10590
  }
@@ -10586,7 +10712,7 @@ registerProvider({
10586
10712
  // src/manifest.ts
10587
10713
  import { readFile as readFile6 } from "fs/promises";
10588
10714
  import { existsSync as existsSync8 } from "fs";
10589
- import path13 from "path";
10715
+ import path14 from "path";
10590
10716
 
10591
10717
  // src/loop-directions.ts
10592
10718
  var SINGLE_OUTPUT_FIELDS = ["file", "cell", "remoteId", "source", "sourceByStyle", "outputRole"];
@@ -10675,7 +10801,7 @@ function isObject2(value) {
10675
10801
 
10676
10802
  // src/manifest.ts
10677
10803
  async function loadManifest(manifestPath) {
10678
- const abs = path13.resolve(manifestPath);
10804
+ const abs = path14.resolve(manifestPath);
10679
10805
  if (!existsSync8(abs)) throw new ProjectError(`No manifest at ${abs}`);
10680
10806
  const expansion = expandLoopDirections(JSON.parse(await readFile6(abs, "utf8")));
10681
10807
  if (expansion.issues.length) {
@@ -10720,13 +10846,24 @@ ${issues}`);
10720
10846
  }
10721
10847
  }
10722
10848
  if (asset.revision) {
10723
- if (!parsed.data.assets[asset.revision.from]) {
10849
+ const parent = parsed.data.assets[asset.revision.from];
10850
+ if (!parent) {
10724
10851
  unknownReferences.push(
10725
10852
  `assets.${assetId}.revision.from: unknown asset "${asset.revision.from}"`
10726
10853
  );
10727
10854
  } else if (asset.revision.from === assetId) {
10728
10855
  unknownReferences.push(`assets.${assetId}.revision.from: an asset cannot revise itself`);
10729
10856
  }
10857
+ const fromStyle = asset.revision.fromStyle;
10858
+ if (fromStyle) {
10859
+ if (!styleIds.has(fromStyle)) {
10860
+ unknownReferences.push(`assets.${assetId}.revision.fromStyle: unknown style "${fromStyle}"`);
10861
+ } else if (parent && parent.styles.length && !parent.styles.includes(fromStyle)) {
10862
+ unknownReferences.push(
10863
+ `assets.${assetId}.revision.fromStyle: "${asset.revision.from}" is not in style "${fromStyle}"`
10864
+ );
10865
+ }
10866
+ }
10730
10867
  }
10731
10868
  for (const shape of ["state", "animation"]) {
10732
10869
  const of = asset[shape]?.of;
@@ -10768,7 +10905,7 @@ ${issues}`);
10768
10905
  ${unknownReferences.map((i) => ` ${i}`).join("\n")}`);
10769
10906
  }
10770
10907
  const loopFamilies = Object.keys(expansion.families).length ? { loopFamilies: expansion.families } : {};
10771
- return { manifest: parsed.data, root: path13.dirname(abs), path: abs, ...loopFamilies };
10908
+ return { manifest: parsed.data, root: path14.dirname(abs), path: abs, ...loopFamilies };
10772
10909
  }
10773
10910
  function parentAssetId(asset) {
10774
10911
  return asset?.revision?.from ?? asset?.state?.of ?? asset?.animation?.of ?? asset?.portrait?.of ?? asset?.outfit?.of;
@@ -10842,7 +10979,7 @@ async function resolveSpecs(loaded, filter) {
10842
10979
  return provider;
10843
10980
  };
10844
10981
  const specs = [];
10845
- const styleIds = Object.keys(manifest.styles).filter(
10982
+ const requestedStyleIds = Object.keys(manifest.styles).filter(
10846
10983
  (id) => !filter?.styles?.length || filter.styles.includes(id)
10847
10984
  );
10848
10985
  for (const unknownStyle of filter?.styles ?? []) {
@@ -10867,9 +11004,42 @@ async function resolveSpecs(loaded, filter) {
10867
11004
  current = parentAssetId(manifest.assets[current]);
10868
11005
  }
10869
11006
  }
11007
+ const styleDependencies = /* @__PURE__ */ new Map();
11008
+ for (const styleId of Object.keys(manifest.styles)) {
11009
+ const needed = /* @__PURE__ */ new Set();
11010
+ for (const assetId of resolutionAssetIds) {
11011
+ const asset = manifest.assets[assetId];
11012
+ if (asset?.styles.length && !asset.styles.includes(styleId)) continue;
11013
+ const fromStyle = asset?.revision?.fromStyle;
11014
+ if (fromStyle && fromStyle !== styleId) needed.add(fromStyle);
11015
+ }
11016
+ styleDependencies.set(styleId, needed);
11017
+ }
11018
+ const styleIds = [];
11019
+ const emitStyles = new Set(requestedStyleIds);
11020
+ {
11021
+ const placed = /* @__PURE__ */ new Set();
11022
+ const visiting = [];
11023
+ const place = (styleId) => {
11024
+ if (placed.has(styleId)) return;
11025
+ const cycleStart = visiting.indexOf(styleId);
11026
+ if (cycleStart >= 0) {
11027
+ throw new Error(
11028
+ `Styles revise each other in a cycle: ${[...visiting.slice(cycleStart), styleId].join(" -> ")}. revision.fromStyle must form a chain, not a loop.`
11029
+ );
11030
+ }
11031
+ visiting.push(styleId);
11032
+ for (const dependency of styleDependencies.get(styleId) ?? []) place(dependency);
11033
+ visiting.pop();
11034
+ placed.add(styleId);
11035
+ styleIds.push(styleId);
11036
+ };
11037
+ for (const styleId of requestedStyleIds) place(styleId);
11038
+ }
11039
+ const styleFinalizers = /* @__PURE__ */ new Map();
10870
11040
  const styleImageCache = /* @__PURE__ */ new Map();
10871
11041
  async function loadStyleImage(rel, what = "Style image") {
10872
- const abs = path13.resolve(root, rel);
11042
+ const abs = path14.resolve(root, rel);
10873
11043
  let hit = styleImageCache.get(abs);
10874
11044
  if (!hit) {
10875
11045
  if (!existsSync8(abs)) throw new Error(`${what} not found: ${abs}`);
@@ -10899,7 +11069,7 @@ async function resolveSpecs(loaded, filter) {
10899
11069
  styleImageDimensions.push(loadedImage);
10900
11070
  }
10901
11071
  const resolvedImages = style.styleImages.map((image) => {
10902
- const hit = styleImageCache.get(path13.resolve(root, image.path));
11072
+ const hit = styleImageCache.get(path14.resolve(root, image.path));
10903
11073
  return { base64: hit.base64, width: hit.width, height: hit.height, format: hit.format };
10904
11074
  });
10905
11075
  const styleSpecs = /* @__PURE__ */ new Map();
@@ -11000,9 +11170,9 @@ async function resolveSpecs(loaded, filter) {
11000
11170
  } else {
11001
11171
  prompt = [style.promptPrefix, subject, style.promptSuffix].map((p) => p.trim()).filter(Boolean).join(", ");
11002
11172
  }
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;
11173
+ const relFile = asset.file ?? path14.join(asset.category ?? "", `${assetId}.png`);
11174
+ const outFile = path14.resolve(root, style.outDir, relFile);
11175
+ const qualityOutFile = style.quality ? path14.resolve(root, style.quality.outDir, pngPath(relFile)) : void 0;
11006
11176
  const tileSize = generator === "tiles" ? size : style.tileSize ?? 32;
11007
11177
  const tileVariations = tileFeatureOutputCount(generator === "tiles" ? style.tileFeature : void 0) ?? tileVariationCount(countNumberedDescriptions(prompt));
11008
11178
  const terrainTiles = generator === "terrain" ? terrainTileCount(style.terrainTransitionSize) : 0;
@@ -11097,7 +11267,7 @@ async function resolveSpecs(loaded, filter) {
11097
11267
  minGridConfidence: style.quality.minGridConfidence,
11098
11268
  ...style.quality.minTransparency == null ? {} : { minTransparency: style.quality.minTransparency },
11099
11269
  ...style.quality.fixerRevision ? { fixerRevision: style.quality.fixerRevision } : {},
11100
- ...style.quality.fixerPython ? { fixerPython: path13.resolve(root, style.quality.fixerPython) } : {},
11270
+ ...style.quality.fixerPython ? { fixerPython: path14.resolve(root, style.quality.fixerPython) } : {},
11101
11271
  ...style.quality.fps == null ? {} : { fps: style.quality.fps }
11102
11272
  }
11103
11273
  } : {},
@@ -11233,7 +11403,7 @@ async function resolveSpecs(loaded, filter) {
11233
11403
  animation: sourceSpec.character.animation,
11234
11404
  outfit: {
11235
11405
  reference: {
11236
- path: path13.resolve(root, asset.outfit.reference),
11406
+ path: path14.resolve(root, asset.outfit.reference),
11237
11407
  sha256: referenceImage.hash,
11238
11408
  width: referenceImage.width,
11239
11409
  height: referenceImage.height,
@@ -11254,28 +11424,30 @@ async function resolveSpecs(loaded, filter) {
11254
11424
  `Provider "${activeProvider.id}" does not support ${asset.revision.mode} revisions`
11255
11425
  );
11256
11426
  }
11257
- const sourceSpec = await finalize(asset.revision.from);
11258
- const sourceStem = sourceSpec.quality?.outFile ?? (sourceSpec.source ? path13.resolve(root, sourceSpec.source) : sourceSpec.outFile);
11427
+ const sourceStyleId = asset.revision.fromStyle && asset.revision.fromStyle !== styleId ? asset.revision.fromStyle : void 0;
11428
+ const sourceSpec = sourceStyleId ? await styleFinalizers.get(sourceStyleId)(asset.revision.from) : await finalize(asset.revision.from);
11429
+ const sourceStem = sourceSpec.quality?.outFile ?? (sourceSpec.source ? path14.resolve(root, sourceSpec.source) : sourceSpec.outFile);
11259
11430
  const sourceMembers = await revisionSourceMembers(sourceStem, `revision source ${asset.revision.from}`);
11260
11431
  const sourceFile = sourceMembers?.[0]?.file ?? sourceStem;
11261
11432
  const sourceImage = sourceMembers ? { ...sourceMembers[0], hash: sha256(JSON.stringify(sourceMembers.map((member) => member.sha256))) } : await optionalRevisionImage(sourceFile, "revision source");
11262
11433
  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;
11434
+ const maskFile = asset.revision.mask ? path14.resolve(root, asset.revision.mask) : void 0;
11264
11435
  const maskImage = maskFile ? await optionalRevisionImage(maskFile, "revision mask", "png") : null;
11265
11436
  if (sourceImage && maskImage && (sourceImage.width !== maskImage.width || sourceImage.height !== maskImage.height)) {
11266
11437
  throw new Error(
11267
11438
  `Revision mask for ${styleId}/${assetId} is ${maskImage.width}x${maskImage.height}; source ${asset.revision.from} is ${sourceImage.width}x${sourceImage.height}`
11268
11439
  );
11269
11440
  }
11270
- const paletteImageFile = asset.revision.paletteImage ? path13.resolve(root, asset.revision.paletteImage) : void 0;
11441
+ const paletteImageFile = asset.revision.paletteImage ? path14.resolve(root, asset.revision.paletteImage) : void 0;
11271
11442
  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;
11443
+ const lastFrameFile = asset.revision.lastFrame ? path14.resolve(root, asset.revision.lastFrame) : void 0;
11273
11444
  const lastFrame = lastFrameFile ? await optionalRevisionImage(lastFrameFile, "revision last frame") : null;
11274
- const keypointsFile = asset.revision.keypointsFile ? path13.resolve(root, asset.revision.keypointsFile) : void 0;
11445
+ const keypointsFile = asset.revision.keypointsFile ? path14.resolve(root, asset.revision.keypointsFile) : void 0;
11275
11446
  const keypoints = keypointsFile ? await optionalSkeletonSet(keypointsFile) : null;
11276
11447
  resolved.revision = {
11277
11448
  mode: asset.revision.mode,
11278
11449
  sourceAssetId: asset.revision.from,
11450
+ ...sourceStyleId ? { sourceStyleId } : {},
11279
11451
  sourceFile,
11280
11452
  sourceSha256: sourceImage?.hash ?? null,
11281
11453
  sourceWidth: sourceImage?.width ?? null,
@@ -11397,6 +11569,8 @@ async function resolveSpecs(loaded, filter) {
11397
11569
  finalized.add(assetId);
11398
11570
  return resolved;
11399
11571
  };
11572
+ styleFinalizers.set(styleId, finalize);
11573
+ if (!emitStyles.has(styleId)) continue;
11400
11574
  for (const assetId of Object.keys(manifest.assets)) {
11401
11575
  if (!requestedAssetIds.has(assetId) || !styleSpecs.has(assetId)) continue;
11402
11576
  specs.push(await finalize(assetId));
@@ -11438,7 +11612,7 @@ async function resolveSpecs(loaded, filter) {
11438
11612
  return specs;
11439
11613
  }
11440
11614
  function pngPath(file) {
11441
- const extension = path13.extname(file);
11615
+ const extension = path14.extname(file);
11442
11616
  return extension ? `${file.slice(0, -extension.length)}.png` : `${file}.png`;
11443
11617
  }
11444
11618
  async function resolveMapScene(root, styleId, assetId, generator, scene, provider) {
@@ -11448,12 +11622,12 @@ async function resolveMapScene(root, styleId, assetId, generator, scene, provide
11448
11622
  if (!provider.supportsMapScene) {
11449
11623
  throw new Error(`assets.${assetId}.scene: provider "${provider.id}" cannot draw into a scene; PixelLab's /map-objects can`);
11450
11624
  }
11451
- const file = path13.resolve(root, scene.image);
11625
+ const file = path14.resolve(root, scene.image);
11452
11626
  const image = await optionalRevisionImage(file, "scene image", "png");
11453
11627
  const base = { file, sha256: image?.hash ?? null, width: image?.width ?? null, height: image?.height ?? null };
11454
11628
  const placement = scene.placement;
11455
11629
  if ("mask" in placement) {
11456
- const maskFile = path13.resolve(root, placement.mask);
11630
+ const maskFile = path14.resolve(root, placement.mask);
11457
11631
  const mask = await optionalRevisionImage(maskFile, "scene mask", "png");
11458
11632
  if (image && mask && (image.width !== mask.width || image.height !== mask.height)) {
11459
11633
  throw new Error(
@@ -11525,7 +11699,7 @@ async function resolveStyleImages(loaded, styleId) {
11525
11699
  if (!style) throw new Error(`Unknown style "${styleId}"`);
11526
11700
  const out = [];
11527
11701
  for (const img of style.styleImages) {
11528
- const file = path13.resolve(loaded.root, img.path);
11702
+ const file = path14.resolve(loaded.root, img.path);
11529
11703
  const buf = await readFile6(file);
11530
11704
  const metadata = imageMetadata(buf);
11531
11705
  if (!metadata) throw new Error(`Style image is not a readable PNG or JPEG: ${file}`);
@@ -11553,7 +11727,7 @@ async function resolveCharacterShape(asset, style, kind, files) {
11553
11727
  };
11554
11728
  if (asset.concept) {
11555
11729
  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 };
11730
+ shape.concept = { path: path14.resolve(files.root, asset.concept), sha256: image.hash, width: image.width, height: image.height, format: image.format };
11557
11731
  }
11558
11732
  if (asset.reference) {
11559
11733
  const byDirection = typeof asset.reference === "string" ? { south: asset.reference } : asset.reference;
@@ -11561,7 +11735,7 @@ async function resolveCharacterShape(asset, style, kind, files) {
11561
11735
  for (const [direction, rel] of Object.entries(byDirection)) {
11562
11736
  const image = await files.load(rel, `Reference sprite (${direction})`);
11563
11737
  shape.reference[direction] = {
11564
- path: path13.resolve(files.root, rel),
11738
+ path: path14.resolve(files.root, rel),
11565
11739
  sha256: image.hash,
11566
11740
  width: image.width,
11567
11741
  height: image.height,
@@ -11582,7 +11756,7 @@ async function resolveCharacterShape(asset, style, kind, files) {
11582
11756
  const animationMode = asset.animation.mode ?? (asset.animation.template ? "template" : "v3");
11583
11757
  const pose = async (rel, what) => {
11584
11758
  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 };
11759
+ return { path: path14.resolve(files.root, rel), sha256: image.hash, width: image.width, height: image.height, format: image.format };
11586
11760
  };
11587
11761
  shape.animation = {
11588
11762
  mode: animationMode,
@@ -11613,7 +11787,7 @@ async function resolveObjectProShape(assetId, asset, style, kind, files) {
11613
11787
  const south = typeof asset.reference === "string" ? asset.reference : asset.reference.south;
11614
11788
  if (south) {
11615
11789
  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 };
11790
+ shape.reference = { path: path14.resolve(files.root, south), sha256: image.hash, width: image.width, height: image.height, format: image.format };
11617
11791
  }
11618
11792
  }
11619
11793
  if (asset.state) {
@@ -11641,7 +11815,7 @@ async function resolveObjectProShape(assetId, asset, style, kind, files) {
11641
11815
  }
11642
11816
  const pose = async (rel, what) => {
11643
11817
  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 };
11818
+ return { path: path14.resolve(files.root, rel), sha256: image.hash, width: image.width, height: image.height, format: image.format };
11645
11819
  };
11646
11820
  shape.animation = {
11647
11821
  mode: asset.animation.mode ?? "v3",
@@ -11658,9 +11832,9 @@ async function resolveObjectProShape(assetId, asset, style, kind, files) {
11658
11832
  }
11659
11833
 
11660
11834
  // src/pipeline/fetch.ts
11661
- import { existsSync as existsSync9, readFileSync as readFileSync3 } from "fs";
11835
+ import { existsSync as existsSync9, readFileSync as readFileSync4 } from "fs";
11662
11836
  import { mkdir as mkdir4, readFile as readFile7, rename as rename4, rm as rm4, writeFile as writeFile4 } from "fs/promises";
11663
- import path14 from "path";
11837
+ import path15 from "path";
11664
11838
 
11665
11839
  // src/source-url.ts
11666
11840
  var SENSITIVE_QUERY_KEYS = /* @__PURE__ */ new Set([
@@ -11706,7 +11880,7 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
11706
11880
  const result = { downloaded: 0, skipped: 0, failed: 0, ...opts.refresh ? { unchanged: 0 } : {} };
11707
11881
  const specByKey = new Map(specs.map((s) => [lockKey(s.styleId, s.assetId), s]));
11708
11882
  normalizeLockOutputPaths(lock, specs);
11709
- const cacheDir = opts.cacheDir === false ? null : path14.resolve(opts.cacheDir ?? path14.join(path14.dirname(lockPath), ".pixelkiln", "cache"));
11883
+ const cacheDir = opts.cacheDir === false ? null : path15.resolve(opts.cacheDir ?? path15.join(path15.dirname(lockPath), ".pixelkiln", "cache"));
11710
11884
  const pending = Object.entries(lock.entries).filter(([key, e]) => {
11711
11885
  if (e.provider !== provider.id || !specByKey.has(key)) return false;
11712
11886
  if (specByKey.get(key)?.mirror) return false;
@@ -11717,7 +11891,7 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
11717
11891
  if (spec && e.specHash === spec.specHash && e.outputs.length && !postprocessCurrent(e, spec)) {
11718
11892
  return Boolean(opts.force) || e.outputs.every((output) => {
11719
11893
  const file = resolveOutputPath(output.path, spec.root);
11720
- return existsSync9(file) && sha256(readFileSync3(file)) === output.sha256;
11894
+ return existsSync9(file) && sha256(readFileSync4(file)) === output.sha256;
11721
11895
  });
11722
11896
  }
11723
11897
  if (!opts.repair) return false;
@@ -11725,7 +11899,7 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
11725
11899
  return e.outputs.some((output) => {
11726
11900
  const file = resolveOutputPath(output.path, spec.root);
11727
11901
  if (!existsSync9(file)) return true;
11728
- const current = sha256(readFileSync3(file));
11902
+ const current = sha256(readFileSync4(file));
11729
11903
  if (current === output.sha256) return false;
11730
11904
  return Boolean(opts.force) || (e.supersededOutputs ?? []).some((old) => old.sha256 === current);
11731
11905
  });
@@ -11808,7 +11982,7 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
11808
11982
  if (!source.url) throw new Error(`no durable source URL to refresh ${source.role ?? "asset"} from`);
11809
11983
  fresh = await provider.download(source.url);
11810
11984
  if (recorded && sha256(fresh) === recordedRaw && !reapply) {
11811
- log2(` current ${path14.relative(process.cwd(), target)}`);
11985
+ log2(` current ${path15.relative(process.cwd(), target)}`);
11812
11986
  outputs.push({ ...recorded, path: portableOutputPath(target, spec.root) });
11813
11987
  continue;
11814
11988
  }
@@ -11832,9 +12006,9 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
11832
12006
  }
11833
12007
  if (reapply && !fresh) {
11834
12008
  if (!recorded.raw) raw = await readFile7(target);
11835
- log2(` reapply ${path14.relative(process.cwd(), target)}`);
12009
+ log2(` reapply ${path15.relative(process.cwd(), target)}`);
11836
12010
  } else {
11837
- log2(` changed ${path14.relative(process.cwd(), target)} (upstream)`);
12011
+ log2(` changed ${path15.relative(process.cwd(), target)} (upstream)`);
11838
12012
  }
11839
12013
  } else {
11840
12014
  const superseded = (entry.supersededOutputs ?? []).find(
@@ -11862,7 +12036,7 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
11862
12036
  }
11863
12037
  }
11864
12038
  log2(
11865
- ` replace ${path14.relative(process.cwd(), target)}` + (opts.force ? " (--force)" : " (previous tracked generation)")
12039
+ ` replace ${path15.relative(process.cwd(), target)}` + (opts.force ? " (--force)" : " (previous tracked generation)")
11866
12040
  );
11867
12041
  }
11868
12042
  }
@@ -11871,7 +12045,7 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
11871
12045
  } else if (!raw && recorded && cacheDir) {
11872
12046
  if (!reapply) final = await readCachedMedia(cacheDir, recorded.sha256, expectedMediaType);
11873
12047
  if (!final && recordedRaw) raw = await readCachedMedia(cacheDir, recordedRaw, expectedMediaType);
11874
- if (final || raw) log2(` cached ${path14.relative(process.cwd(), target)}`);
12048
+ if (final || raw) log2(` cached ${path15.relative(process.cwd(), target)}`);
11875
12049
  }
11876
12050
  if (!final && !raw) {
11877
12051
  if (!source.url) {
@@ -11898,11 +12072,11 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
11898
12072
  buf = processed.bytes;
11899
12073
  if (processed.changed) {
11900
12074
  rawHash = sha256(raw);
11901
- log2(` snapped ${path14.relative(process.cwd(), target)} to ${wanted.palette.colors.length} colours`);
12075
+ log2(` snapped ${path15.relative(process.cwd(), target)} to ${wanted.palette.colors.length} colours`);
11902
12076
  }
11903
12077
  }
11904
12078
  if (cacheDir) await cacheMedia(cacheDir, buf, mediaType);
11905
- await mkdir4(path14.dirname(target), { recursive: true });
12079
+ await mkdir4(path15.dirname(target), { recursive: true });
11906
12080
  const tmp = `${target}.pixelkiln.tmp`;
11907
12081
  await writeFile4(tmp, buf);
11908
12082
  outputs.push({
@@ -11916,7 +12090,7 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
11916
12090
  await saveLock(lockPath, lock);
11917
12091
  await rename4(tmp, target);
11918
12092
  wrote = true;
11919
- log2(` wrote ${path14.relative(process.cwd(), target)}`);
12093
+ log2(` wrote ${path15.relative(process.cwd(), target)}`);
11920
12094
  }
11921
12095
  if (opts.refresh && !wrote) {
11922
12096
  if (rotated) {
@@ -11960,7 +12134,7 @@ async function fetchAssets(provider, specs, lock, lockPath, opts = {}) {
11960
12134
  return result;
11961
12135
  }
11962
12136
  async function readCachedMedia(cacheDir, hash, mediaType) {
11963
- const file = path14.join(cacheDir, cacheFileName(hash, mediaType));
12137
+ const file = path15.join(cacheDir, cacheFileName(hash, mediaType));
11964
12138
  if (!existsSync9(file)) return null;
11965
12139
  try {
11966
12140
  const buf = await readFile7(file);
@@ -11974,7 +12148,7 @@ async function readCachedMedia(cacheDir, hash, mediaType) {
11974
12148
  async function cacheMedia(cacheDir, buf, mediaType, knownHash) {
11975
12149
  const hash = knownHash ?? sha256(buf);
11976
12150
  validateMedia(buf, mediaType);
11977
- const file = path14.join(cacheDir, cacheFileName(hash, mediaType));
12151
+ const file = path15.join(cacheDir, cacheFileName(hash, mediaType));
11978
12152
  if (await readCachedMedia(cacheDir, hash, mediaType)) return;
11979
12153
  await mkdir4(cacheDir, { recursive: true });
11980
12154
  const tmp = `${file}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
@@ -12017,7 +12191,7 @@ async function pushTags(provider, specs, lock, opts = {}) {
12017
12191
  // src/pipeline/adopt.ts
12018
12192
  import { existsSync as existsSync11 } from "fs";
12019
12193
  import { readFile as readFile9, writeFile as writeFile6 } from "fs/promises";
12020
- import path15 from "path";
12194
+ import path16 from "path";
12021
12195
 
12022
12196
  // src/cache.ts
12023
12197
  import { mkdir as mkdir5, readFile as readFile8, rename as rename5, rm as rm5, writeFile as writeFile5 } from "fs/promises";
@@ -12036,10 +12210,10 @@ function isSha256Hash(value) {
12036
12210
  function parseCache(value) {
12037
12211
  return HashCacheSchema.parse(value);
12038
12212
  }
12039
- async function loadCache(path51) {
12040
- if (!existsSync10(path51)) return { version: 1, hashes: {} };
12213
+ async function loadCache(path52) {
12214
+ if (!existsSync10(path52)) return { version: 1, hashes: {} };
12041
12215
  try {
12042
- const parsed = HashCacheSchema.safeParse(JSON.parse(await readFile8(path51, "utf8")));
12216
+ const parsed = HashCacheSchema.safeParse(JSON.parse(await readFile8(path52, "utf8")));
12043
12217
  if (!parsed.success) return { version: 1, hashes: {} };
12044
12218
  return {
12045
12219
  version: 1,
@@ -12051,18 +12225,18 @@ async function loadCache(path51) {
12051
12225
  return { version: 1, hashes: {} };
12052
12226
  }
12053
12227
  }
12054
- async function saveCache(path51, cache) {
12228
+ async function saveCache(path52, cache) {
12055
12229
  const sorted = {};
12056
12230
  for (const key of Object.keys(cache.hashes).sort()) {
12057
12231
  const hash = cache.hashes[key];
12058
12232
  if (!isSha256Hash(hash)) throw new Error(`Refusing to cache invalid SHA-256 for ${key}`);
12059
12233
  sorted[key] = hash;
12060
12234
  }
12061
- await mkdir5(pathModule.dirname(pathModule.resolve(path51)), { recursive: true });
12062
- const tmp = `${path51}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
12235
+ await mkdir5(pathModule.dirname(pathModule.resolve(path52)), { recursive: true });
12236
+ const tmp = `${path52}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
12063
12237
  try {
12064
12238
  await writeFile5(tmp, JSON.stringify({ version: 1, hashes: sorted }, null, 2) + "\n");
12065
- await rename5(tmp, path51);
12239
+ await rename5(tmp, path52);
12066
12240
  } finally {
12067
12241
  await rm5(tmp, { force: true });
12068
12242
  }
@@ -12255,7 +12429,7 @@ function formatUnmatchedRemote(objects, limit = 15) {
12255
12429
  // src/pipeline/adopt-characters.ts
12256
12430
  import { existsSync as existsSync12 } from "fs";
12257
12431
  import { mkdir as mkdir6, readFile as readFile10, writeFile as writeFile7 } from "fs/promises";
12258
- import path16 from "path";
12432
+ import path17 from "path";
12259
12433
  async function adoptCharacters(provider, specs, lock, lockPath, opts) {
12260
12434
  const log2 = opts.onProgress ?? (() => {
12261
12435
  });
@@ -12307,11 +12481,11 @@ async function adoptCharacters(provider, specs, lock, lockPath, opts) {
12307
12481
  const hash = sha256(bytes);
12308
12482
  const file = expectedOutputPath(spec, source.role, index, sources.length);
12309
12483
  if (existsSync12(file)) {
12310
- if (sha256(await readFile10(file)) !== hash) result.differing.push(path16.relative(process.cwd(), file));
12484
+ if (sha256(await readFile10(file)) !== hash) result.differing.push(path17.relative(process.cwd(), file));
12311
12485
  } else {
12312
- await mkdir6(path16.dirname(file), { recursive: true });
12486
+ await mkdir6(path17.dirname(file), { recursive: true });
12313
12487
  await writeFile7(file, bytes);
12314
- result.written.push(path16.relative(process.cwd(), file));
12488
+ result.written.push(path17.relative(process.cwd(), file));
12315
12489
  }
12316
12490
  outputs.push({ path: portableOutputPath(file, spec.root), sha256: hash, ...source.role ? { role: source.role } : {} });
12317
12491
  }
@@ -12350,7 +12524,7 @@ async function adoptCharacters(provider, specs, lock, lockPath, opts) {
12350
12524
  } else {
12351
12525
  const south = expectedOutputPath(spec, "south", 0, spec.character.directions);
12352
12526
  if (!existsSync12(south)) {
12353
- result.unmatched.push(`${key}: no remoteId and no ${path16.relative(process.cwd(), south)} to match by`);
12527
+ result.unmatched.push(`${key}: no remoteId and no ${path17.relative(process.cwd(), south)} to match by`);
12354
12528
  return false;
12355
12529
  }
12356
12530
  const localHash = sha256(await readFile10(south));
@@ -12362,7 +12536,7 @@ async function adoptCharacters(provider, specs, lock, lockPath, opts) {
12362
12536
  }
12363
12537
  }
12364
12538
  if (!match) {
12365
- result.unmatched.push(`${key}: no character's south rotation has the bytes of ${path16.relative(process.cwd(), south)}`);
12539
+ result.unmatched.push(`${key}: no character's south rotation has the bytes of ${path17.relative(process.cwd(), south)}`);
12366
12540
  return false;
12367
12541
  }
12368
12542
  }
@@ -12432,7 +12606,7 @@ async function adoptCharacters(provider, specs, lock, lockPath, opts) {
12432
12606
  if (!existsSync12(first)) {
12433
12607
  const byName = candidates.find((a) => a.name === characterAnimationName(spec));
12434
12608
  if (!byName) {
12435
- result.unmatched.push(`${key}: no remoteId and no ${path16.relative(process.cwd(), first)} to match by`);
12609
+ result.unmatched.push(`${key}: no remoteId and no ${path17.relative(process.cwd(), first)} to match by`);
12436
12610
  return false;
12437
12611
  }
12438
12612
  match = byName;
@@ -12445,7 +12619,7 @@ async function adoptCharacters(provider, specs, lock, lockPath, opts) {
12445
12619
  }
12446
12620
  }
12447
12621
  if (!match) {
12448
- result.unmatched.push(`${key}: no ${animation.direction} animation of character ${parentId} starts with the bytes of ${path16.relative(process.cwd(), first)}`);
12622
+ result.unmatched.push(`${key}: no ${animation.direction} animation of character ${parentId} starts with the bytes of ${path17.relative(process.cwd(), first)}`);
12449
12623
  return false;
12450
12624
  }
12451
12625
  }
@@ -12507,7 +12681,7 @@ async function adoptCharacters(provider, specs, lock, lockPath, opts) {
12507
12681
  // src/pipeline/salvage.ts
12508
12682
  import { readFile as readFile11 } from "fs/promises";
12509
12683
  import { existsSync as existsSync13 } from "fs";
12510
- import path17 from "path";
12684
+ import path18 from "path";
12511
12685
  function providerClaimId(provider, objectId) {
12512
12686
  return `${encodeURIComponent(provider)}:${encodeURIComponent(objectId)}`;
12513
12687
  }
@@ -12684,20 +12858,20 @@ function matchStyleByPattern(prompt, manifest) {
12684
12858
  return null;
12685
12859
  }
12686
12860
  async function loadSiblingManifests(ownManifestPath, workspaceManifestPaths, claimPaths) {
12687
- const own = path17.resolve(ownManifestPath);
12861
+ const own = path18.resolve(ownManifestPath);
12688
12862
  const siblingManifestPaths = [
12689
12863
  .../* @__PURE__ */ new Set([
12690
12864
  ...workspaceManifestPaths,
12691
- ...claimPaths.map((c) => path17.join(path17.dirname(path17.resolve(c)), "pixelkiln.manifest.json"))
12865
+ ...claimPaths.map((c) => path18.join(path18.dirname(path18.resolve(c)), "pixelkiln.manifest.json"))
12692
12866
  ])
12693
12867
  ];
12694
12868
  const siblings = [];
12695
12869
  for (const siblingManifestPath of siblingManifestPaths) {
12696
- if (path17.resolve(siblingManifestPath) === own) continue;
12870
+ if (path18.resolve(siblingManifestPath) === own) continue;
12697
12871
  if (!existsSync13(siblingManifestPath)) continue;
12698
12872
  try {
12699
12873
  const { manifest } = await loadManifest(siblingManifestPath);
12700
- siblings.push({ label: path17.basename(path17.dirname(siblingManifestPath)), manifest });
12874
+ siblings.push({ label: path18.basename(path18.dirname(siblingManifestPath)), manifest });
12701
12875
  } catch {
12702
12876
  }
12703
12877
  }
@@ -12819,7 +12993,7 @@ async function applyTags(provider, decisions, existing, opts = {}) {
12819
12993
  // src/workspace.ts
12820
12994
  import { mkdir as mkdir7, readFile as readFile12, rename as rename6, rm as rm6, writeFile as writeFile8 } from "fs/promises";
12821
12995
  import { existsSync as existsSync14 } from "fs";
12822
- import path18 from "path";
12996
+ import path19 from "path";
12823
12997
  import { z as z5 } from "zod";
12824
12998
  var WorkspaceProjectSchema = z5.object({
12825
12999
  id: z5.string().min(1),
@@ -12861,7 +13035,7 @@ async function saveWorkspace(workspacePath, ws) {
12861
13035
  version: 1,
12862
13036
  projects: [...ws.projects].sort((a, b) => a.id.localeCompare(b.id))
12863
13037
  };
12864
- await mkdir7(path18.dirname(path18.resolve(workspacePath)), { recursive: true });
13038
+ await mkdir7(path19.dirname(path19.resolve(workspacePath)), { recursive: true });
12865
13039
  const tmp = `${workspacePath}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
12866
13040
  try {
12867
13041
  await writeFile8(tmp, JSON.stringify(sorted, null, 2) + "\n");
@@ -12871,12 +13045,12 @@ async function saveWorkspace(workspacePath, ws) {
12871
13045
  }
12872
13046
  }
12873
13047
  function toPortablePath(dir, absolute) {
12874
- return path18.relative(dir, absolute).split(path18.sep).join("/");
13048
+ return path19.relative(dir, absolute).split(path19.sep).join("/");
12875
13049
  }
12876
13050
  function resolveProject(dir, project) {
12877
13051
  return {
12878
- manifestPath: path18.resolve(dir, project.manifest.split("/").join(path18.sep)),
12879
- lockPath: path18.resolve(dir, project.lock.split("/").join(path18.sep))
13052
+ manifestPath: path19.resolve(dir, project.manifest.split("/").join(path19.sep)),
13053
+ lockPath: path19.resolve(dir, project.lock.split("/").join(path19.sep))
12880
13054
  };
12881
13055
  }
12882
13056
  function validateWorkspace(ws, dir) {
@@ -12889,7 +13063,7 @@ function validateWorkspace(ws, dir) {
12889
13063
  const { manifestPath, lockPath } = resolveProject(dir, project);
12890
13064
  lockOwners.set(lockPath, [...lockOwners.get(lockPath) ?? [], project.id]);
12891
13065
  manifestOwners.set(manifestPath, [...manifestOwners.get(manifestPath) ?? [], project.id]);
12892
- if (path18.isAbsolute(project.manifest) || path18.isAbsolute(project.lock)) {
13066
+ if (path19.isAbsolute(project.manifest) || path19.isAbsolute(project.lock)) {
12893
13067
  diagnostics.push({
12894
13068
  id: "absolute-path",
12895
13069
  level: "warning",
@@ -12951,7 +13125,7 @@ function validateWorkspace(ws, dir) {
12951
13125
 
12952
13126
  // src/pick/salvage-server.ts
12953
13127
  import { mkdir as mkdir8, writeFile as writeFile9, readFile as readFile14 } from "fs/promises";
12954
- import path19 from "path";
13128
+ import path20 from "path";
12955
13129
 
12956
13130
  // src/pick/salvage-sheet.ts
12957
13131
  var escapeHtml = (s) => s.replace(/[&<>"']/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[c]);
@@ -13216,7 +13390,7 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
13216
13390
  });
13217
13391
  const html = renderSalvageSheet(orphans, {
13218
13392
  styleId: ctx.styleId,
13219
- importDir: path19.relative(process.cwd(), ctx.importDir) || "."
13393
+ importDir: path20.relative(process.cwd(), ctx.importDir) || "."
13220
13394
  });
13221
13395
  const byId = new Map(orphans.map((o) => [o.id, o]));
13222
13396
  const existingTags = new Map(orphans.map((o) => [o.id, o.tags]));
@@ -13272,9 +13446,9 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
13272
13446
  if (!buf.subarray(0, 8).equals(PNG_SIGNATURE2)) throw new Error("not a PNG");
13273
13447
  decodePng(buf);
13274
13448
  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 });
13449
+ const rel = path20.join("_salvaged", `${assetId}.png`);
13450
+ const outFile = path20.resolve(ctx.importDir, rel);
13451
+ await mkdir8(path20.dirname(outFile), { recursive: true });
13278
13452
  await writeFile9(outFile, buf);
13279
13453
  ctx.manifest.assets[assetId] = {
13280
13454
  prompt: orphan.prompt,
@@ -13307,7 +13481,7 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
13307
13481
  sourceUrl: durableSource,
13308
13482
  sourceUrls: durableSource ? [{ url: durableSource }] : [],
13309
13483
  outputs: [{
13310
- path: portableOutputPath(outFile, path19.dirname(ctx.manifestPath)),
13484
+ path: portableOutputPath(outFile, path20.dirname(ctx.manifestPath)),
13311
13485
  sha256: sha256(buf)
13312
13486
  }],
13313
13487
  submittedAt: orphan.createdAt,
@@ -13342,19 +13516,19 @@ async function runSalvage(provider, orphans, ctx, opts = {}) {
13342
13516
  }
13343
13517
 
13344
13518
  // src/cli/project.ts
13345
- import path21 from "path";
13519
+ import path22 from "path";
13346
13520
  import { existsSync as existsSync15 } from "fs";
13347
13521
 
13348
13522
  // src/project.ts
13349
- import path20 from "path";
13523
+ import path21 from "path";
13350
13524
  async function openProject(manifestPath = "pixelkiln.manifest.json", opts = {}) {
13351
- const absolute = path20.resolve(manifestPath);
13352
- const root = path20.dirname(absolute);
13525
+ const absolute = path21.resolve(manifestPath);
13526
+ const root = path21.dirname(absolute);
13353
13527
  if (opts.env !== false) {
13354
13528
  loadEnvFiles(root);
13355
- if (path20.resolve(process.cwd()) !== root) loadEnvFiles(process.cwd());
13529
+ if (path21.resolve(process.cwd()) !== root) loadEnvFiles(process.cwd());
13356
13530
  }
13357
- const lockPath = path20.resolve(opts.lockPath ?? path20.join(root, "pixelkiln.lock.json"));
13531
+ const lockPath = path21.resolve(opts.lockPath ?? path21.join(root, "pixelkiln.lock.json"));
13358
13532
  const loaded = await loadManifest(absolute);
13359
13533
  const specs = await resolveSpecs(loaded, { styles: opts.styles ?? [], assets: opts.assets ?? [] });
13360
13534
  const lock = await loadLock(lockPath);
@@ -13485,7 +13659,7 @@ async function workspaceStatus(ws, dir) {
13485
13659
 
13486
13660
  // src/cli/project.ts
13487
13661
  async function provenanceFile(id, file) {
13488
- const absolute = path21.resolve(file);
13662
+ const absolute = path22.resolve(file);
13489
13663
  return {
13490
13664
  id,
13491
13665
  path: absolute,
@@ -13564,7 +13738,7 @@ async function requireCompleteWorkspaceClaims(workspacePath) {
13564
13738
  if (!existsSync15(workspacePath)) {
13565
13739
  throw new ProjectError(`Workspace catalog not found: ${workspacePath}`);
13566
13740
  }
13567
- const dir = path21.dirname(path21.resolve(workspacePath));
13741
+ const dir = path22.dirname(path22.resolve(workspacePath));
13568
13742
  const ws = await loadWorkspace(workspacePath);
13569
13743
  const diagnostics = validateWorkspace(ws, dir);
13570
13744
  const errors = diagnostics.filter((d) => d.level === "error");
@@ -13587,9 +13761,9 @@ async function openAccountProject(args) {
13587
13761
  return project;
13588
13762
  }
13589
13763
  async function open(args) {
13590
- if (!existsSync15(path21.resolve(args.manifest))) {
13764
+ if (!existsSync15(path22.resolve(args.manifest))) {
13591
13765
  throw new ProjectError(
13592
- `No manifest at ${path21.resolve(args.manifest)}. Pass --manifest, or run \`pixelkiln init --from <dir>\`.`
13766
+ `No manifest at ${path22.resolve(args.manifest)}. Pass --manifest, or run \`pixelkiln init --from <dir>\`.`
13593
13767
  );
13594
13768
  }
13595
13769
  const project = await openProject(args.manifest, { lockPath: args.lock, styles: args.styles, assets: args.assets });
@@ -13631,7 +13805,7 @@ function providerCache(mode2) {
13631
13805
 
13632
13806
  // src/cli/commands/account.ts
13633
13807
  async function runBalance(args) {
13634
- loadEnvFiles(path22.dirname(path22.resolve(args.manifest)));
13808
+ loadEnvFiles(path23.dirname(path23.resolve(args.manifest)));
13635
13809
  loadEnvFiles(process.cwd());
13636
13810
  const loaded = await loadManifest(args.manifest);
13637
13811
  const selectedProvider = accountProviderId(loaded.manifest, args.provider, "balance");
@@ -13704,12 +13878,12 @@ async function runAdopt(args) {
13704
13878
  tagged ${n} object(s) upstream`);
13705
13879
  }
13706
13880
  if (args.writePrompts) {
13707
- const { filled, stillEmpty } = await writePromptsBack(path22.resolve(args.manifest), lock, {
13881
+ const { filled, stillEmpty } = await writePromptsBack(path23.resolve(args.manifest), lock, {
13708
13882
  onProgress: log,
13709
13883
  provider: provider.id,
13710
13884
  assetIds: specs.map((spec) => spec.assetId)
13711
13885
  });
13712
- log(` recovered ${filled} prompt(s) into ${path22.relative(process.cwd(), args.manifest)}`);
13886
+ log(` recovered ${filled} prompt(s) into ${path23.relative(process.cwd(), args.manifest)}`);
13713
13887
  const reloaded = await loadManifest(args.manifest);
13714
13888
  const rebased = (await resolveSpecs(reloaded, {
13715
13889
  styles: args.styles,
@@ -13739,12 +13913,12 @@ async function runSalvage2(args) {
13739
13913
  const provider = providerFor(accountProvider);
13740
13914
  const jsonMode = args.dryRun && args.json;
13741
13915
  const diag = jsonMode ? (msg = "") => console.error(msg) : log;
13742
- const ownLock = path22.resolve(args.lock);
13916
+ const ownLock = path23.resolve(args.lock);
13743
13917
  let workspaceProjects = [];
13744
13918
  let workspaceDir = "";
13745
13919
  if (args.workspace) {
13746
- const workspacePath = path22.resolve(args.workspace);
13747
- workspaceDir = path22.dirname(workspacePath);
13920
+ const workspacePath = path23.resolve(args.workspace);
13921
+ workspaceDir = path23.dirname(workspacePath);
13748
13922
  const complete = await requireCompleteWorkspaceClaims(workspacePath);
13749
13923
  workspaceProjects = complete.ws.projects;
13750
13924
  for (const d of complete.diagnostics) diag(` WARN ${d.id}: ${d.message}`);
@@ -13754,11 +13928,11 @@ async function runSalvage2(args) {
13754
13928
  .../* @__PURE__ */ new Set([
13755
13929
  ...workspaceLockPaths,
13756
13930
  ...existsSync16(ownLock) ? [ownLock] : [],
13757
- ...args.claims.map((c) => path22.resolve(c))
13931
+ ...args.claims.map((c) => path23.resolve(c))
13758
13932
  ])
13759
13933
  ];
13760
13934
  diag(` claim set (${lockPaths.length} lockfile(s)):`);
13761
- for (const p of lockPaths) diag(` ${path22.relative(process.cwd(), p)}`);
13935
+ for (const p of lockPaths) diag(` ${path23.relative(process.cwd(), p)}`);
13762
13936
  if (!args.claims.length && !args.workspace) {
13763
13937
  diag(
13764
13938
  `
@@ -13766,7 +13940,7 @@ async function runSalvage2(args) {
13766
13940
  pass every other project's lockfile via --claims a.json,b.json, or
13767
13941
  register every project in a workspace catalog and pass --workspace.`
13768
13942
  );
13769
- } else if (args.workspace && !workspaceProjects.some((p) => resolveProject(workspaceDir, p).manifestPath === path22.resolve(args.manifest))) {
13943
+ } else if (args.workspace && !workspaceProjects.some((p) => resolveProject(workspaceDir, p).manifestPath === path23.resolve(args.manifest))) {
13770
13944
  diag(
13771
13945
  `
13772
13946
  This project's manifest is not registered in the workspace catalog. Its own
@@ -13885,7 +14059,7 @@ async function runSalvage2(args) {
13885
14059
  manifestPath: loaded.path,
13886
14060
  manifest: accountManifest,
13887
14061
  styleId,
13888
- importDir: path22.resolve(loaded.root, style.outDir),
14062
+ importDir: path23.resolve(loaded.root, style.outDir),
13889
14063
  lock,
13890
14064
  lockPath: args.lock
13891
14065
  },
@@ -14012,20 +14186,20 @@ async function runTag(args) {
14012
14186
  }
14013
14187
 
14014
14188
  // src/cli/commands/edit.ts
14015
- import path25 from "path";
14189
+ import path26 from "path";
14016
14190
 
14017
14191
  // src/pipeline/hand-edit.ts
14018
14192
  import { existsSync as existsSync17 } from "fs";
14019
14193
  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";
14194
+ import path25 from "path";
14021
14195
  import { z as z7 } from "zod";
14022
14196
 
14023
14197
  // src/manifest-edit.ts
14024
14198
  import { readFile as readFile15, rename as rename7, rm as rm7, writeFile as writeFile10 } from "fs/promises";
14025
- import path23 from "path";
14199
+ import path24 from "path";
14026
14200
  import { z as z6 } from "zod";
14027
14201
  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");
14202
+ var ProjectFileSchema = z6.string().min(1).refine((file) => !path24.isAbsolute(file) && !file.split(/[\\/]/).includes(".."), "must be a path inside the project");
14029
14203
  var AssetPatchSchema = z6.object({
14030
14204
  prompt: z6.string().optional(),
14031
14205
  /** Per-style override for the style being viewed; null removes it. */
@@ -14444,7 +14618,7 @@ function applyEdit(raw, edit) {
14444
14618
  }
14445
14619
  }
14446
14620
  async function withCandidate(manifestPath, edit, use, unchanged) {
14447
- const absolute = path23.resolve(manifestPath);
14621
+ const absolute = path24.resolve(manifestPath);
14448
14622
  const original = await readFile15(absolute, "utf8");
14449
14623
  if (sha256(original) !== edit.expectedSha256) throw new ManifestDriftError(absolute);
14450
14624
  let raw;
@@ -14458,9 +14632,9 @@ async function withCandidate(manifestPath, edit, use, unchanged) {
14458
14632
  applyEdit(raw, edit);
14459
14633
  const next = serializeLike(original, raw);
14460
14634
  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`
14635
+ const tmp = path24.join(
14636
+ path24.dirname(absolute),
14637
+ `.${path24.basename(absolute)}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`
14464
14638
  );
14465
14639
  try {
14466
14640
  await writeFile10(tmp, next);
@@ -14533,11 +14707,11 @@ async function priceManifestEdit(manifestPath, edit, opts = {}) {
14533
14707
  var HAND_EDIT_DIR = "edits";
14534
14708
  function handEditPath(loaded, spec) {
14535
14709
  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"));
14710
+ const outDir = path25.resolve(loaded.root, style?.outDir ?? ".");
14711
+ const relative = path25.relative(outDir, spec.outFile);
14712
+ const inside = relative && !relative.startsWith("..") && !path25.isAbsolute(relative);
14713
+ const file = inside ? relative : path25.basename(spec.outFile);
14714
+ return path25.join(outDir, HAND_EDIT_DIR, file.replace(/\.gif$/i, ".png"));
14541
14715
  }
14542
14716
  function handEditBases(spec, lock) {
14543
14717
  const entry = lock.entries[lockKey(spec.styleId, spec.assetId)];
@@ -14579,7 +14753,7 @@ async function startHandEdit(loaded, lock, spec, opts = {}) {
14579
14753
  `${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
14754
  );
14581
14755
  }
14582
- const existing = spec.source ? path24.resolve(loaded.root, spec.source) : null;
14756
+ const existing = spec.source ? path25.resolve(loaded.root, spec.source) : null;
14583
14757
  const editPath = existing ?? handEditPath(loaded, spec);
14584
14758
  const members = handEditMembers(loaded, spec, lock, editPath);
14585
14759
  let created = false;
@@ -14590,7 +14764,7 @@ async function startHandEdit(loaded, lock, spec, opts = {}) {
14590
14764
  `${spec.styleId}/${spec.assetId} has no generated PNG on disk to start from; generate or restore it first`
14591
14765
  );
14592
14766
  }
14593
- await mkdir9(path24.dirname(member.path), { recursive: true });
14767
+ await mkdir9(path25.dirname(member.path), { recursive: true });
14594
14768
  await copyFile(member.base, member.path);
14595
14769
  created = true;
14596
14770
  }
@@ -14728,7 +14902,7 @@ async function saveHandEdit(loaded, lock, spec, input) {
14728
14902
  editor: input.editor,
14729
14903
  protocol: input.protocol,
14730
14904
  savedAt: (input.now ?? /* @__PURE__ */ new Date()).toISOString(),
14731
- project: projectPath ? path24.basename(projectPath) : null,
14905
+ project: projectPath ? path25.basename(projectPath) : null,
14732
14906
  outputs: incoming.map((frame, index) => ({
14733
14907
  role: frame.role,
14734
14908
  basedOn: basedOnHash(entry, index),
@@ -14788,7 +14962,7 @@ async function runEdit(args) {
14788
14962
  const set = started.members.length > 1;
14789
14963
  const kind = spec.generator === "frames" ? "frames" : "members";
14790
14964
  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)}`);
14965
+ if (set) for (const member of started.members) log(` ${member.role ?? ""} ${path26.relative(process.cwd(), member.path)}`);
14792
14966
  if (args.noOpen) {
14793
14967
  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
14968
  } else {
@@ -14798,24 +14972,24 @@ async function runEdit(args) {
14798
14972
  }
14799
14973
 
14800
14974
  // src/cli/commands/gallery.ts
14801
- import path35 from "path";
14975
+ import path36 from "path";
14802
14976
  import { existsSync as existsSync27 } from "fs";
14803
14977
 
14804
14978
  // src/gallery/snapshot.ts
14805
- import { createHash as createHash3 } from "crypto";
14979
+ import { createHash as createHash4 } from "crypto";
14806
14980
  import { existsSync as existsSync22 } from "fs";
14807
14981
  import { readFile as readFile20, stat as stat2 } from "fs/promises";
14808
- import path31 from "path";
14982
+ import path32 from "path";
14809
14983
 
14810
14984
  // src/gallery/edit.ts
14811
14985
  import { readFile as readFile19, rename as rename9, writeFile as writeFile15 } from "fs/promises";
14812
- import path29 from "path";
14986
+ import path30 from "path";
14813
14987
  import { z as z11 } from "zod";
14814
14988
 
14815
14989
  // src/gallery/upload.ts
14816
14990
  import { existsSync as existsSync18 } from "fs";
14817
14991
  import { mkdir as mkdir10, writeFile as writeFile12 } from "fs/promises";
14818
- import path26 from "path";
14992
+ import path27 from "path";
14819
14993
  import { z as z8 } from "zod";
14820
14994
  var MAX_UPLOAD_BYTES = 8 * 1024 * 1024;
14821
14995
  var UploadImageSchema = z8.object({
@@ -14828,12 +15002,12 @@ var UploadImageSchema = z8.object({
14828
15002
  overwrite: z8.boolean().optional()
14829
15003
  }).strict();
14830
15004
  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)) {
15005
+ const file = path27.resolve(root, request.path);
15006
+ const relative = path27.relative(root, file);
15007
+ if (relative.startsWith("..") || path27.isAbsolute(relative)) {
14834
15008
  throw new ManifestEditError("an upload must stay inside the project");
14835
15009
  }
14836
- const extension = path26.extname(file).toLowerCase();
15010
+ const extension = path27.extname(file).toLowerCase();
14837
15011
  const expected = extension === ".png" ? "png" : extension === ".jpg" || extension === ".jpeg" ? "jpeg" : null;
14838
15012
  if (!expected) throw new ManifestEditError("an upload must be a .png, .jpg, or .jpeg path");
14839
15013
  const bytes = Buffer.from(request.base64, "base64");
@@ -14846,17 +15020,17 @@ async function saveProjectImage(root, request) {
14846
15020
  throw new ManifestEditError(`the upload is a ${metadata.format.toUpperCase()}; name it ${metadata.format === "png" ? ".png" : ".jpg"}`);
14847
15021
  }
14848
15022
  if (existsSync18(file) && !request.overwrite) {
14849
- throw new ManifestEditError(`${relative.split(path26.sep).join("/")} already exists; choose another name, or replace it`);
15023
+ throw new ManifestEditError(`${relative.split(path27.sep).join("/")} already exists; choose another name, or replace it`);
14850
15024
  }
14851
- await mkdir10(path26.dirname(file), { recursive: true });
15025
+ await mkdir10(path27.dirname(file), { recursive: true });
14852
15026
  await writeFile12(file, bytes);
14853
- return relative.split(path26.sep).join("/");
15027
+ return relative.split(path27.sep).join("/");
14854
15028
  }
14855
15029
 
14856
15030
  // src/gallery/image-revision.ts
14857
15031
  import { existsSync as existsSync19 } from "fs";
14858
15032
  import { readFile as readFile17, rm as rm9, writeFile as writeFile13 } from "fs/promises";
14859
- import path27 from "path";
15033
+ import path28 from "path";
14860
15034
  import { z as z9 } from "zod";
14861
15035
  var InpaintRevisionRequestSchema = z9.object({
14862
15036
  action: z9.literal("create-inpaint-revision"),
@@ -14875,14 +15049,14 @@ var InpaintRevisionRequestSchema = z9.object({
14875
15049
  overwrite: z9.boolean().optional()
14876
15050
  }).strict();
14877
15051
  async function createInpaintRevision(manifestPath, request) {
14878
- const root = path27.dirname(path27.resolve(manifestPath));
14879
- if (path27.extname(request.maskPath).toLowerCase() !== ".png") {
15052
+ const root = path28.dirname(path28.resolve(manifestPath));
15053
+ if (path28.extname(request.maskPath).toLowerCase() !== ".png") {
14880
15054
  throw new ManifestEditError("the mask must be a .png path inside the project");
14881
15055
  }
14882
15056
  const bytes = Buffer.from(request.maskBase64, "base64");
14883
15057
  const metadata = imageMetadata(bytes);
14884
15058
  if (!metadata || metadata.format !== "png") throw new ManifestEditError("the mask is not a readable PNG");
14885
- const file = path27.resolve(root, request.maskPath);
15059
+ const file = path28.resolve(root, request.maskPath);
14886
15060
  const previous = existsSync19(file) ? await readFile17(file) : null;
14887
15061
  const mask = await saveProjectImage(root, {
14888
15062
  action: "upload-image",
@@ -14912,7 +15086,7 @@ async function createInpaintRevision(manifestPath, request) {
14912
15086
  // src/gallery/skeleton.ts
14913
15087
  import { existsSync as existsSync20 } from "fs";
14914
15088
  import { mkdir as mkdir11, readFile as readFile18, rm as rm10, writeFile as writeFile14 } from "fs/promises";
14915
- import path28 from "path";
15089
+ import path29 from "path";
14916
15090
  import { z as z10 } from "zod";
14917
15091
  var SkeletonEstimateRequestSchema = z10.object({
14918
15092
  project: z10.string().min(1).optional(),
@@ -14951,7 +15125,7 @@ var SkeletonKeypointsUpdateSchema = z10.object({
14951
15125
  }).strict();
14952
15126
  var DEFAULT_ESTIMATE_LIMIT = 10;
14953
15127
  function skeletonSourceFile(spec) {
14954
- if (spec.source) return path28.resolve(spec.root, spec.source);
15128
+ if (spec.source) return path29.resolve(spec.root, spec.source);
14955
15129
  if (spec.quality && existsSync20(spec.quality.outFile)) return spec.quality.outFile;
14956
15130
  return spec.outFile;
14957
15131
  }
@@ -14976,7 +15150,7 @@ function createGallerySkeletonHandlers(opts) {
14976
15150
  if (!existsSync20(file)) throw new ManifestEditError(`${key} has no image on disk to estimate from`);
14977
15151
  const bytes = await readFile18(file);
14978
15152
  const metadata = imageMetadata(bytes);
14979
- if (!metadata) throw new ManifestEditError(`${path28.basename(file)} is not a readable PNG or JPEG`);
15153
+ if (!metadata) throw new ManifestEditError(`${path29.basename(file)} is not a readable PNG or JPEG`);
14980
15154
  const problem = estimateSkeletonSizeProblem(metadata.width, metadata.height);
14981
15155
  if (problem) throw new ManifestEditError(`${key} is ${problem}`);
14982
15156
  if (used >= limit) {
@@ -14993,7 +15167,7 @@ function createGallerySkeletonHandlers(opts) {
14993
15167
  log2(` estimated a skeleton for ${key} (PixelLab estimate-skeleton, ${used}/${limit} this session${billed ? `, billed ${billed.amount} ${billed.unit}` : ""})`);
14994
15168
  return {
14995
15169
  set: scaffoldSkeletonSet(res.keypoints, request.frames ?? DEFAULT_SCAFFOLD_FRAMES),
14996
- source: path28.relative(ctx.loaded.root, file).split(path28.sep).join("/"),
15170
+ source: path29.relative(ctx.loaded.root, file).split(path29.sep).join("/"),
14997
15171
  session: status()
14998
15172
  };
14999
15173
  }
@@ -15007,10 +15181,10 @@ function billedAmount(usage) {
15007
15181
  return typeof amount === "number" && Number.isFinite(amount) ? { unit: type, amount } : null;
15008
15182
  }
15009
15183
  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)) {
15184
+ const root = path29.dirname(path29.resolve(manifestPath));
15185
+ const file = path29.resolve(root, request.keypointsFile);
15186
+ const relative = path29.relative(root, file);
15187
+ if (!request.keypointsFile.endsWith(".json") || relative.startsWith("..") || path29.isAbsolute(relative)) {
15014
15188
  throw new ManifestEditError("the keypoints file must be a .json path inside the project");
15015
15189
  }
15016
15190
  const existed = existsSync20(file);
@@ -15020,7 +15194,7 @@ async function createSkeletonAnimation(manifestPath, request) {
15020
15194
  throw new ManifestEditError(`${request.keypointsFile} already exists; choose another path, or replace it`);
15021
15195
  }
15022
15196
  if (existed) previous = await readFile18(file);
15023
- await mkdir11(path28.dirname(file), { recursive: true });
15197
+ await mkdir11(path29.dirname(file), { recursive: true });
15024
15198
  await writeFile14(file, JSON.stringify(request.set, null, 2) + "\n");
15025
15199
  } else if (!existed) {
15026
15200
  throw new ManifestEditError(`${request.keypointsFile} does not exist; paste poses or estimate them from the sprite`);
@@ -15036,7 +15210,7 @@ async function createSkeletonAnimation(manifestPath, request) {
15036
15210
  revision: {
15037
15211
  mode: "animate-skeleton",
15038
15212
  from: request.from,
15039
- keypointsFile: relative.split(path28.sep).join("/"),
15213
+ keypointsFile: relative.split(path29.sep).join("/"),
15040
15214
  direction: request.direction,
15041
15215
  ...request.description ? { description: request.description } : {},
15042
15216
  ...request.skeletonTemplate ? { skeletonTemplate: request.skeletonTemplate } : {}
@@ -15057,19 +15231,19 @@ async function updateSkeletonKeypoints(ctx, request) {
15057
15231
  if (!spec) throw new ManifestEditError(`"${key}" is not declared by the manifest`);
15058
15232
  const file = spec.revision?.mode === "animate-skeleton" ? spec.revision.keypointsFile : void 0;
15059
15233
  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)) {
15234
+ const relative = path29.relative(ctx.loaded.root, file);
15235
+ if (relative.startsWith("..") || path29.isAbsolute(relative)) {
15062
15236
  throw new ManifestEditError(`${key}'s keypoints file is outside the project; edit it by hand`);
15063
15237
  }
15064
15238
  const current = existsSync20(file) ? sha256(await readFile18(file)) : null;
15065
15239
  if (current !== request.expectedSha256) {
15066
15240
  throw Object.assign(
15067
- new Error(`${relative.split(path28.sep).join("/")} changed on disk since this page loaded it; press Refresh and edit again`),
15241
+ new Error(`${relative.split(path29.sep).join("/")} changed on disk since this page loaded it; press Refresh and edit again`),
15068
15242
  { status: 409 }
15069
15243
  );
15070
15244
  }
15071
15245
  await writeFile14(file, JSON.stringify(request.set, null, 2) + "\n");
15072
- return relative.split(path28.sep).join("/");
15246
+ return relative.split(path29.sep).join("/");
15073
15247
  }
15074
15248
 
15075
15249
  // src/gallery/edit.ts
@@ -15123,7 +15297,7 @@ function createGalleryEditHandler(opts) {
15123
15297
  const history = /* @__PURE__ */ new Map();
15124
15298
  const manifestOf = async (project) => {
15125
15299
  try {
15126
- return path29.resolve(await opts.manifestFor(typeof project === "string" ? project : void 0));
15300
+ return path30.resolve(await opts.manifestFor(typeof project === "string" ? project : void 0));
15127
15301
  } catch {
15128
15302
  return null;
15129
15303
  }
@@ -15146,7 +15320,7 @@ function createGalleryEditHandler(opts) {
15146
15320
  await writeFile15(tmp, last.before);
15147
15321
  await rename9(tmp, manifestPath2);
15148
15322
  stack.pop();
15149
- log2(` manifest edit undone in ${path29.relative(process.cwd(), manifestPath2) || manifestPath2}`);
15323
+ log2(` manifest edit undone in ${path30.relative(process.cwd(), manifestPath2) || manifestPath2}`);
15150
15324
  return opts.reload();
15151
15325
  }
15152
15326
  const manifestPath = await manifestOf(body?.project);
@@ -15208,7 +15382,7 @@ function createEditApplier(opts, log2) {
15208
15382
  } catch (error) {
15209
15383
  throw new ManifestEditError(error instanceof Error ? error.message : String(error));
15210
15384
  }
15211
- const written = await saveProjectImage(path29.dirname(path29.resolve(manifestPath2)), upload.data);
15385
+ const written = await saveProjectImage(path30.dirname(path30.resolve(manifestPath2)), upload.data);
15212
15386
  log2(` image added: ${written}`);
15213
15387
  return opts.reload();
15214
15388
  }
@@ -15275,7 +15449,7 @@ function createEditApplier(opts, log2) {
15275
15449
  }
15276
15450
  const result = await applyManifestEdit(manifestPath, parsed.data);
15277
15451
  if (result.changed) {
15278
- const relative = path29.relative(process.cwd(), result.manifestPath);
15452
+ const relative = path30.relative(process.cwd(), result.manifestPath);
15279
15453
  const shown = !relative || relative.startsWith("..") ? result.manifestPath : relative;
15280
15454
  log2(` manifest edited: ${describeEdit(parsed.data)} in ${shown}`);
15281
15455
  }
@@ -15327,7 +15501,7 @@ function createGalleryPriceHandler(opts) {
15327
15501
 
15328
15502
  // src/pipeline/history.ts
15329
15503
  import { existsSync as existsSync21 } from "fs";
15330
- import path30 from "path";
15504
+ import path31 from "path";
15331
15505
  var HISTORY_ENV = "PIXELKILN_HISTORY";
15332
15506
  function historyLimit(manifest, env = process.env) {
15333
15507
  if (manifest?.history !== void 0) return manifest.history;
@@ -15417,7 +15591,7 @@ async function revertGeneration(provider, spec, lock, lockPath, opts) {
15417
15591
  if (!existsSync21(file)) continue;
15418
15592
  if (await sha256File(file) !== entry.outputs[i].sha256 && !opts.force) {
15419
15593
  throw new OverwriteRefusedError(
15420
- `${path30.relative(process.cwd(), file)} was changed after download; keep it with pixelkiln edit, or pass --force to replace it`,
15594
+ `${path31.relative(process.cwd(), file)} was changed after download; keep it with pixelkiln edit, or pass --force to replace it`,
15421
15595
  [file]
15422
15596
  );
15423
15597
  }
@@ -15470,7 +15644,7 @@ async function revertGeneration(provider, spec, lock, lockPath, opts) {
15470
15644
  // src/gallery/snapshot.ts
15471
15645
  var PROJECT_FILE_TYPE = "application/octet-stream";
15472
15646
  function galleryMediaId(absolutePath) {
15473
- return createHash3("sha256").update(path31.resolve(absolutePath)).digest("hex").slice(0, 24);
15647
+ return createHash4("sha256").update(path32.resolve(absolutePath)).digest("hex").slice(0, 24);
15474
15648
  }
15475
15649
  function galleryMediaRoute(id) {
15476
15650
  return `/media/${id}`;
@@ -15489,7 +15663,7 @@ function describeHistory(media, entry, cacheDir) {
15489
15663
  return (entry.history ?? []).map((generation, i) => {
15490
15664
  const outputs = generation.outputs.map((output) => {
15491
15665
  const mediaType = output.mediaType ?? MediaType.PNG;
15492
- const file = path31.join(cacheDir, cacheFileName(output.sha256, mediaType));
15666
+ const file = path32.join(cacheDir, cacheFileName(output.sha256, mediaType));
15493
15667
  let url = null;
15494
15668
  if (existsSync22(file)) {
15495
15669
  const id = galleryMediaId(file);
@@ -15621,9 +15795,9 @@ async function describeOutput(media, root, absolutePath, recorded = {}) {
15621
15795
  async function describeQuality(media, root, inspection) {
15622
15796
  const outputPaths = inspection.outputs ?? [inspection.output];
15623
15797
  const outputs = await Promise.all(
15624
- outputPaths.map((file) => describeOutput(media, root, path31.resolve(root, file)))
15798
+ outputPaths.map((file) => describeOutput(media, root, path32.resolve(root, file)))
15625
15799
  );
15626
- const recordPath = path31.resolve(root, inspection.record);
15800
+ const recordPath = path32.resolve(root, inspection.record);
15627
15801
  const recordExists = existsSync22(recordPath);
15628
15802
  let options = null;
15629
15803
  let check = null;
@@ -15643,7 +15817,7 @@ async function describeQuality(media, root, inspection) {
15643
15817
  return {
15644
15818
  state: inspection.state,
15645
15819
  reason: inspection.reason,
15646
- output: portableOutputPath(path31.resolve(root, inspection.output), root),
15820
+ output: portableOutputPath(path32.resolve(root, inspection.output), root),
15647
15821
  record: portableOutputPath(recordPath, root),
15648
15822
  recordExists,
15649
15823
  outputs,
@@ -15683,7 +15857,7 @@ async function buildGallerySnapshot(opts) {
15683
15857
  const items = [];
15684
15858
  const manifestText = await readFile20(loaded.path, "utf8");
15685
15859
  const manifestSha256 = sha256(manifestText);
15686
- const cacheDir = path31.resolve(path31.dirname(opts.lockPath), ".pixelkiln", "cache");
15860
+ const cacheDir = path32.resolve(path32.dirname(opts.lockPath), ".pixelkiln", "cache");
15687
15861
  let rawStyles = {};
15688
15862
  try {
15689
15863
  const raw = JSON.parse(manifestText);
@@ -15707,7 +15881,7 @@ async function buildGallerySnapshot(opts) {
15707
15881
  )
15708
15882
  );
15709
15883
  if (spec.source) {
15710
- const editPath = path31.resolve(root, spec.source);
15884
+ const editPath = path32.resolve(root, spec.source);
15711
15885
  const memberCount = sourceIsStem(spec.source, entry, root) ? entry.outputs.length : 1;
15712
15886
  const editShas = await Promise.all(
15713
15887
  Array.from({ length: memberCount }, async (_, index) => {
@@ -15756,7 +15930,7 @@ async function buildGallerySnapshot(opts) {
15756
15930
  editStatus = edits.some((member) => !member.exists) ? "missing" : editChanged.every((changed) => !changed) ? "same" : regenerated ? "regenerated-since" : "edited";
15757
15931
  }
15758
15932
  } else if (spec.source) {
15759
- outputs = [await describeOutput(media, root, path31.resolve(root, spec.source))];
15933
+ outputs = [await describeOutput(media, root, path32.resolve(root, spec.source))];
15760
15934
  } else if (!entry && existsSync22(spec.outFile)) {
15761
15935
  outputs = [await describeOutput(media, root, spec.outFile)];
15762
15936
  } else {
@@ -15797,7 +15971,7 @@ async function buildGallerySnapshot(opts) {
15797
15971
  recordedSpecHash: entry?.specHash ?? null,
15798
15972
  currentSpecHash: spec.specHash,
15799
15973
  revision: entry?.revision ?? null,
15800
- revisionParentKey: spec.revision ? lockKey(spec.styleId, spec.revision.sourceAssetId) : entry?.revision ? lockKey(spec.styleId, entry.revision.sourceAssetId) : null,
15974
+ revisionParentKey: spec.revision ? lockKey(spec.revision.sourceStyleId ?? spec.styleId, spec.revision.sourceAssetId) : entry?.revision ? lockKey(entry.revision.sourceStyleId ?? spec.styleId, entry.revision.sourceAssetId) : null,
15801
15975
  mirrorOfKey: spec.mirror ? lockKey(spec.styleId, spec.mirror.sourceAssetId) : entry?.mirror ? lockKey(spec.styleId, entry.mirror.sourceAssetId) : null,
15802
15976
  skeleton: spec.revision?.mode === "animate-skeleton" && spec.revision.keypointsFile ? {
15803
15977
  keypointsFile: portableOutputPath(spec.revision.keypointsFile, root),
@@ -15867,7 +16041,7 @@ async function buildGallerySnapshot(opts) {
15867
16041
  recordedSpecHash: entry.specHash,
15868
16042
  currentSpecHash: null,
15869
16043
  revision: entry.revision,
15870
- revisionParentKey: entry.revision ? lockKey(entry.styleId, entry.revision.sourceAssetId) : null,
16044
+ revisionParentKey: entry.revision ? lockKey(entry.revision.sourceStyleId ?? entry.styleId, entry.revision.sourceAssetId) : null,
15871
16045
  mirrorOfKey: entry.mirror ? lockKey(entry.styleId, entry.mirror.sourceAssetId) : null,
15872
16046
  skeleton: null,
15873
16047
  character: describeCharacter(void 0, entry),
@@ -15960,7 +16134,7 @@ async function buildGallerySnapshot(opts) {
15960
16134
  id: loaded.manifest.name,
15961
16135
  name: loaded.manifest.name,
15962
16136
  manifest: loaded.path,
15963
- lock: path31.resolve(opts.lockPath),
16137
+ lock: path32.resolve(opts.lockPath),
15964
16138
  root,
15965
16139
  provider: loaded.manifest.provider,
15966
16140
  account: null,
@@ -15986,7 +16160,7 @@ async function buildGallerySnapshot(opts) {
15986
16160
  return { snapshot, media };
15987
16161
  }
15988
16162
  async function buildWorkspaceGallerySnapshot(opts) {
15989
- const dir = path31.dirname(path31.resolve(opts.workspacePath));
16163
+ const dir = path32.dirname(path32.resolve(opts.workspacePath));
15990
16164
  const media = /* @__PURE__ */ new Map();
15991
16165
  const projects = [];
15992
16166
  const items = [];
@@ -16029,7 +16203,7 @@ async function buildWorkspaceGallerySnapshot(opts) {
16029
16203
  id: project.id,
16030
16204
  name: loaded.manifest.name,
16031
16205
  manifest: manifestPath,
16032
- lock: path31.resolve(lockPath),
16206
+ lock: path32.resolve(lockPath),
16033
16207
  root: loaded.root,
16034
16208
  provider: loaded.manifest.provider,
16035
16209
  account: project.account ?? null,
@@ -16045,8 +16219,8 @@ async function buildWorkspaceGallerySnapshot(opts) {
16045
16219
  id: project.id,
16046
16220
  name: project.id,
16047
16221
  manifest: manifestPath,
16048
- lock: path31.resolve(lockPath),
16049
- root: path31.dirname(manifestPath),
16222
+ lock: path32.resolve(lockPath),
16223
+ root: path32.dirname(manifestPath),
16050
16224
  provider: project.provider,
16051
16225
  account: project.account ?? null,
16052
16226
  manifestSha256: null,
@@ -16073,7 +16247,7 @@ async function buildWorkspaceGallerySnapshot(opts) {
16073
16247
  version: 1,
16074
16248
  generatedAt: (opts.now ?? (() => /* @__PURE__ */ new Date()))().toISOString(),
16075
16249
  project: null,
16076
- workspace: { path: path31.resolve(opts.workspacePath), dir, projects },
16250
+ workspace: { path: path32.resolve(opts.workspacePath), dir, projects },
16077
16251
  filter,
16078
16252
  totals: {
16079
16253
  entries: projects.reduce((sum, project) => sum + project.entries, 0),
@@ -16095,7 +16269,7 @@ import { createServer as createServer2 } from "http";
16095
16269
  import { readFile as readFile21 } from "fs/promises";
16096
16270
 
16097
16271
  // src/gallery/page.ts
16098
- import { existsSync as existsSync23, readFileSync as readFileSync4 } from "fs";
16272
+ import { existsSync as existsSync23, readFileSync as readFileSync5 } from "fs";
16099
16273
 
16100
16274
  // src/gallery/client-bundle.ts
16101
16275
  import { createRequire } from "module";
@@ -16135,8 +16309,8 @@ var client;
16135
16309
  function clientAssets() {
16136
16310
  const prebuilt = new URL("gallery.js", CLIENT_DIR);
16137
16311
  client ??= {
16138
- css: readFileSync4(new URL("gallery.css", CLIENT_DIR), "utf8"),
16139
- js: existsSync23(prebuilt) ? readFileSync4(prebuilt, "utf8") : bundleGalleryClient()
16312
+ css: readFileSync5(new URL("gallery.css", CLIENT_DIR), "utf8"),
16313
+ js: existsSync23(prebuilt) ? readFileSync5(prebuilt, "utf8") : bundleGalleryClient()
16140
16314
  };
16141
16315
  return client;
16142
16316
  }
@@ -16803,6 +16977,7 @@ async function submit(provider, loaded, items, lock, lockPath, opts = {}) {
16803
16977
  revision: spec.revision ? {
16804
16978
  mode: spec.revision.mode,
16805
16979
  sourceAssetId: spec.revision.sourceAssetId,
16980
+ ...spec.revision.sourceStyleId ? { sourceStyleId: spec.revision.sourceStyleId } : {},
16806
16981
  sourceSha256: spec.revision.sourceSha256,
16807
16982
  ...spec.revision.maskSha256 ? { maskSha256: spec.revision.maskSha256 } : {},
16808
16983
  ...spec.revision.strength == null ? {} : { strength: spec.revision.strength },
@@ -16955,7 +17130,7 @@ import { readFile as readFile23 } from "fs/promises";
16955
17130
  // src/pipeline/init.ts
16956
17131
  import { readdir, readFile as readFile22, writeFile as writeFile16 } from "fs/promises";
16957
17132
  import { existsSync as existsSync24 } from "fs";
16958
- import path32 from "path";
17133
+ import path33 from "path";
16959
17134
  function pngSize(buf) {
16960
17135
  if (buf.length < 24) return null;
16961
17136
  const isPng = buf.readUInt32BE(0) === 2303741511 && buf.readUInt32BE(4) === 218765834;
@@ -16969,7 +17144,7 @@ function slugify(value) {
16969
17144
  async function walk(dir, exclude) {
16970
17145
  const out = [];
16971
17146
  for (const entry of await readdir(dir, { withFileTypes: true })) {
16972
- const full = path32.join(dir, entry.name);
17147
+ const full = path33.join(dir, entry.name);
16973
17148
  if (exclude.some((x) => full.includes(x))) continue;
16974
17149
  if (entry.isDirectory()) out.push(...await walk(full, exclude));
16975
17150
  else if (entry.name.toLowerCase().endsWith(".png")) out.push(full);
@@ -16988,9 +17163,9 @@ async function scanAssets(root, opts = {}) {
16988
17163
  skipped.push(`${file} (not a readable PNG)`);
16989
17164
  continue;
16990
17165
  }
16991
- const rel = path32.relative(root, file);
16992
- const category = path32.dirname(rel) === "." ? "" : path32.dirname(rel);
16993
- let id = slugify(path32.basename(rel));
17166
+ const rel = path33.relative(root, file);
17167
+ const category = path33.dirname(rel) === "." ? "" : path33.dirname(rel);
17168
+ let id = slugify(path33.basename(rel));
16994
17169
  if (seen.has(id)) {
16995
17170
  const n = seen.get(id) + 1;
16996
17171
  seen.set(id, n);
@@ -18004,14 +18179,14 @@ function createGenerateHandlers(opts) {
18004
18179
 
18005
18180
  // src/gallery/editor.ts
18006
18181
  import { readFile as readFile25 } from "fs/promises";
18007
- import path34 from "path";
18182
+ import path35 from "path";
18008
18183
 
18009
18184
  // src/editor/install.ts
18010
- import { createHash as createHash4 } from "crypto";
18185
+ import { createHash as createHash5 } from "crypto";
18011
18186
  import { existsSync as existsSync26 } from "fs";
18012
18187
  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";
18188
+ import os3 from "os";
18189
+ import path34 from "path";
18015
18190
 
18016
18191
  // tools/pixelorama-bridge/pin.json
18017
18192
  var pin_default = {
@@ -18075,15 +18250,15 @@ var EditorInstallError = class extends Error {
18075
18250
  };
18076
18251
  function editorToolsRoot() {
18077
18252
  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");
18253
+ if (override) return path34.resolve(override);
18254
+ if (process.platform === "darwin") return path34.join(os3.homedir(), "Library", "Caches", "pixelkiln", "tools");
18080
18255
  if (process.platform === "win32") {
18081
- return path33.join(process.env.LOCALAPPDATA ?? path33.join(os2.homedir(), "AppData", "Local"), "pixelkiln", "tools");
18256
+ return path34.join(process.env.LOCALAPPDATA ?? path34.join(os3.homedir(), "AppData", "Local"), "pixelkiln", "tools");
18082
18257
  }
18083
- return path33.join(process.env.XDG_CACHE_HOME ?? path33.join(os2.homedir(), ".cache"), "pixelkiln", "tools");
18258
+ return path34.join(process.env.XDG_CACHE_HOME ?? path34.join(os3.homedir(), ".cache"), "pixelkiln", "tools");
18084
18259
  }
18085
18260
  function editorDir(pin = EDITOR_PIN, root = editorToolsRoot()) {
18086
- return path33.join(root, "pixelorama", pin.release ?? "unpublished");
18261
+ return path34.join(root, "pixelorama", pin.release ?? "unpublished");
18087
18262
  }
18088
18263
  function editorBaseUrl(pin = EDITOR_PIN) {
18089
18264
  const override = process.env.PIXELKILN_EDITOR_URL?.trim();
@@ -18093,7 +18268,7 @@ function editorBaseUrl(pin = EDITOR_PIN) {
18093
18268
  }
18094
18269
  async function sha256Of(file) {
18095
18270
  try {
18096
- return createHash4("sha256").update(await readFile24(file)).digest("hex");
18271
+ return createHash5("sha256").update(await readFile24(file)).digest("hex");
18097
18272
  } catch {
18098
18273
  return null;
18099
18274
  }
@@ -18106,7 +18281,7 @@ async function editorStatus(opts = {}) {
18106
18281
  let totalBytes = 0;
18107
18282
  for (const [name, expected] of Object.entries(pin.files)) {
18108
18283
  totalBytes += expected.bytes;
18109
- const file = path33.join(dir, name);
18284
+ const file = path34.join(dir, name);
18110
18285
  const info = existsSync26(file) ? await stat3(file).catch(() => null) : null;
18111
18286
  if (info && info.size === expected.bytes && await sha256Of(file) === expected.sha256) {
18112
18287
  installedBytes += expected.bytes;
@@ -18160,11 +18335,11 @@ async function installEditor(opts = {}) {
18160
18335
  if (bytes.length !== expected.bytes) {
18161
18336
  throw new EditorInstallError(`${name}: expected ${expected.bytes} bytes, got ${bytes.length}`, name);
18162
18337
  }
18163
- const digest2 = createHash4("sha256").update(bytes).digest("hex");
18338
+ const digest2 = createHash5("sha256").update(bytes).digest("hex");
18164
18339
  if (digest2 !== expected.sha256) {
18165
18340
  throw new EditorInstallError(`${name}: sha256 ${digest2.slice(0, 12)}\u2026 does not match the pinned ${expected.sha256.slice(0, 12)}\u2026`, name);
18166
18341
  }
18167
- const final = path33.join(dir, name);
18342
+ const final = path34.join(dir, name);
18168
18343
  const tmp = `${final}.${process.pid}.part`;
18169
18344
  try {
18170
18345
  await writeFile17(tmp, bytes);
@@ -18210,7 +18385,7 @@ var EDITOR_CONTENT_TYPES = {
18210
18385
  ".json": "application/json; charset=utf-8"
18211
18386
  };
18212
18387
  function editorContentType(name) {
18213
- return EDITOR_CONTENT_TYPES[path33.extname(name).toLowerCase()] ?? null;
18388
+ return EDITOR_CONTENT_TYPES[path34.extname(name).toLowerCase()] ?? null;
18214
18389
  }
18215
18390
 
18216
18391
  // src/gallery/editor.ts
@@ -18286,7 +18461,7 @@ function createGalleryEditorHandlers(opts = {}) {
18286
18461
  if (!status.installed) return null;
18287
18462
  let bytes;
18288
18463
  try {
18289
- bytes = await readFile25(path34.join(dir, name));
18464
+ bytes = await readFile25(path35.join(dir, name));
18290
18465
  } catch {
18291
18466
  verified = null;
18292
18467
  return null;
@@ -18326,7 +18501,7 @@ async function serveUntilStopped(initial, reload, args, access2) {
18326
18501
  };
18327
18502
  const loadProject = async (project) => {
18328
18503
  const ctx = await access2.loadProject(project);
18329
- loadProjectEnv(path35.dirname(ctx.loaded.path));
18504
+ loadProjectEnv(path36.dirname(ctx.loaded.path));
18330
18505
  return ctx;
18331
18506
  };
18332
18507
  const loadProjectEnv = (dir) => {
@@ -18335,7 +18510,7 @@ async function serveUntilStopped(initial, reload, args, access2) {
18335
18510
  for (const [name, value] of Object.entries(declared)) {
18336
18511
  if (credentialNames.has(name) && name in process.env && process.env[name] !== value) {
18337
18512
  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.`
18513
+ `${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
18514
  );
18340
18515
  }
18341
18516
  }
@@ -18343,7 +18518,7 @@ async function serveUntilStopped(initial, reload, args, access2) {
18343
18518
  };
18344
18519
  const quoters = /* @__PURE__ */ new Map();
18345
18520
  const quoteFor = async (project) => {
18346
- loadProjectEnv(path35.dirname(path35.resolve(await access2.manifestFor(project))));
18521
+ loadProjectEnv(path36.dirname(path36.resolve(await access2.manifestFor(project))));
18347
18522
  const key = process.env.PIXELLAB_API_KEY;
18348
18523
  if (!key) return null;
18349
18524
  let quoter = quoters.get(key);
@@ -18394,7 +18569,7 @@ async function serveUntilStopped(initial, reload, args, access2) {
18394
18569
  }
18395
18570
  async function runGallery(args) {
18396
18571
  if (args.workspace) {
18397
- const workspacePath = path35.resolve(args.workspace);
18572
+ const workspacePath = path36.resolve(args.workspace);
18398
18573
  if (!existsSync27(workspacePath)) {
18399
18574
  throw new Error(`Workspace catalog not found: ${workspacePath}`);
18400
18575
  }
@@ -18415,7 +18590,7 @@ async function runGallery(args) {
18415
18590
  const registered = async (projectId) => {
18416
18591
  const project = (await loadWorkspace(workspacePath)).projects.find((candidate) => candidate.id === projectId);
18417
18592
  if (!projectId || !project) throw new Error(`unknown workspace project "${projectId ?? ""}"`);
18418
- return resolveProject(path35.dirname(workspacePath), project);
18593
+ return resolveProject(path36.dirname(workspacePath), project);
18419
18594
  };
18420
18595
  await serveUntilStopped(initial, build2, args, {
18421
18596
  manifestFor: async (projectId) => (await registered(projectId)).manifestPath,
@@ -18440,7 +18615,7 @@ async function runGallery(args) {
18440
18615
  return buildGallerySnapshot({ ...fresh, filter });
18441
18616
  };
18442
18617
  await serveUntilStopped(await build(), reload, args, {
18443
- manifestFor: () => path35.resolve(args.manifest),
18618
+ manifestFor: () => path36.resolve(args.manifest),
18444
18619
  loadProject: () => openProject(args.manifest, { lockPath: args.lock, env: false })
18445
18620
  });
18446
18621
  }
@@ -18754,13 +18929,13 @@ import { existsSync as existsSync29 } from "fs";
18754
18929
  // src/pipeline/cache-health.ts
18755
18930
  import { existsSync as existsSync28 } from "fs";
18756
18931
  import { readFile as readFile26, readdir as readdir2, rm as rm12 } from "fs/promises";
18757
- import path36 from "path";
18932
+ import path37 from "path";
18758
18933
  async function inspectCaches(lock, lockPath, options = {}) {
18759
18934
  if (options.prune && !existsSync28(lockPath)) {
18760
- throw new Error(`Refusing to prune without an existing lockfile at ${path36.resolve(lockPath)}`);
18935
+ throw new Error(`Refusing to prune without an existing lockfile at ${path37.resolve(lockPath)}`);
18761
18936
  }
18762
- const contentDir = path36.resolve(path36.dirname(lockPath), ".pixelkiln", "cache");
18763
- const remotePath = path36.resolve(cachePathFor(lockPath));
18937
+ const contentDir = path37.resolve(path37.dirname(lockPath), ".pixelkiln", "cache");
18938
+ const remotePath = path37.resolve(cachePathFor(lockPath));
18764
18939
  const referenced = referencedHashes(lock);
18765
18940
  let content = await inspectContentCache(contentDir, referenced);
18766
18941
  let remoteHashes = await inspectRemoteHashCache(remotePath);
@@ -18772,7 +18947,7 @@ async function inspectCaches(lock, lockPath, options = {}) {
18772
18947
  ]);
18773
18948
  for (const name of names) {
18774
18949
  try {
18775
- await rm12(path36.join(contentDir, name), { force: true });
18950
+ await rm12(path37.join(contentDir, name), { force: true });
18776
18951
  removed.contentFiles++;
18777
18952
  } catch {
18778
18953
  }
@@ -18820,7 +18995,7 @@ async function inspectContentCache(contentDir, referenced) {
18820
18995
  continue;
18821
18996
  }
18822
18997
  report.files++;
18823
- const file = path36.join(contentDir, entry.name);
18998
+ const file = path37.join(contentDir, entry.name);
18824
18999
  const mediaType = mediaTypeFromExtension(entry.name);
18825
19000
  const expected = mediaType ? entry.name.slice(0, -4) : "";
18826
19001
  let bytes;
@@ -18991,42 +19166,42 @@ This drops their provenance from the lockfile. The art on disk is`);
18991
19166
  }
18992
19167
 
18993
19168
  // src/cli/commands/init.ts
18994
- import path37 from "path";
19169
+ import path38 from "path";
18995
19170
  import { existsSync as existsSync30 } from "fs";
18996
19171
  async function runInit(args) {
18997
19172
  if (!args.from) throw new Error("init needs --from <dir> pointing at your existing PNGs.");
18998
- const root = path37.resolve(args.from);
19173
+ const root = path38.resolve(args.from);
18999
19174
  if (!existsSync30(root)) throw new Error(`No directory at ${root}`);
19000
19175
  const generator = args.generator ?? "map";
19001
19176
  if (generator !== "1dir" && generator !== "map") {
19002
19177
  throw new Error(`--generator must be "1dir" or "map", got "${args.generator}".`);
19003
19178
  }
19004
- const target = path37.resolve(args.out ?? "pixelkiln.manifest.json");
19179
+ const target = path38.resolve(args.out ?? "pixelkiln.manifest.json");
19005
19180
  const { assets, skipped } = await scanAssets(root, { exclude: args.exclude });
19006
19181
  if (!assets.length) throw new Error(`No PNGs found under ${root}`);
19007
19182
  const manifest = buildManifest(
19008
- args.name ?? path37.basename(path37.dirname(target)),
19183
+ args.name ?? path38.basename(path38.dirname(target)),
19009
19184
  args.styles[0] ?? "base",
19010
19185
  generator,
19011
- path37.relative(path37.dirname(target), root) || ".",
19186
+ path38.relative(path38.dirname(target), root) || ".",
19012
19187
  assets
19013
19188
  );
19014
19189
  await writeManifestFile(target, manifest);
19015
- log(` scanned ${assets.length} PNG(s) under ${path37.relative(process.cwd(), root)}`);
19190
+ log(` scanned ${assets.length} PNG(s) under ${path38.relative(process.cwd(), root)}`);
19016
19191
  if (skipped.length) log(` skipped ${skipped.length} unreadable file(s)`);
19017
- log(` wrote ${path37.relative(process.cwd(), target)}`);
19192
+ log(` wrote ${path38.relative(process.cwd(), target)}`);
19018
19193
  log(`
19019
19194
  Prompts are intentionally empty. To recover the real ones from your`);
19020
19195
  log(` PixelLab account instead of inventing them:`);
19021
19196
  log(`
19022
- pixelkiln adopt --manifest ${path37.relative(process.cwd(), target)} --write-prompts
19197
+ pixelkiln adopt --manifest ${path38.relative(process.cwd(), target)} --write-prompts
19023
19198
  `);
19024
19199
  }
19025
19200
 
19026
19201
  // src/pipeline/doctor.ts
19027
19202
  import { constants, existsSync as existsSync31 } from "fs";
19028
19203
  import { access, stat as stat4 } from "fs/promises";
19029
- import path38 from "path";
19204
+ import path39 from "path";
19030
19205
  async function doctor(loaded, specs, lock, lockPath, opts = {}) {
19031
19206
  const checks = [];
19032
19207
  const add = (id, level, message6) => checks.push({ id, level, message: message6 });
@@ -19037,8 +19212,8 @@ async function doctor(loaded, specs, lock, lockPath, opts = {}) {
19037
19212
  );
19038
19213
  add("lock", "ok", `${Object.keys(lock.entries).length} valid v2 lock entr(ies)`);
19039
19214
  const dirs = [...new Set(specs.flatMap((spec) => [
19040
- path38.dirname(spec.outFile),
19041
- ...spec.quality ? [path38.dirname(spec.quality.outFile)] : []
19215
+ path39.dirname(spec.outFile),
19216
+ ...spec.quality ? [path39.dirname(spec.quality.outFile)] : []
19042
19217
  ]))];
19043
19218
  const unwritable = [];
19044
19219
  for (const dir of dirs) {
@@ -19061,7 +19236,7 @@ async function doctor(loaded, specs, lock, lockPath, opts = {}) {
19061
19236
  for (let index = 0; index < entry.outputs.length; index++) {
19062
19237
  const output = entry.outputs[index];
19063
19238
  const spec = specByKey.get(key);
19064
- const absolute = spec ? currentEntryOutputPath(entry, spec, index) : resolveOutputPath(output.path, path38.dirname(lockPath));
19239
+ const absolute = spec ? currentEntryOutputPath(entry, spec, index) : resolveOutputPath(output.path, path39.dirname(lockPath));
19065
19240
  const owner = outputOwners.get(absolute);
19066
19241
  if (owner && owner !== key) duplicateOutputs.push(`${output.path} (${owner}, ${key})`);
19067
19242
  outputOwners.set(absolute, key);
@@ -19076,7 +19251,7 @@ async function doctor(loaded, specs, lock, lockPath, opts = {}) {
19076
19251
  } else {
19077
19252
  add("lock-outputs", "ok", `${outputOwners.size} recorded output path(s) are uniquely owned`);
19078
19253
  }
19079
- const cacheDir = path38.join(path38.dirname(lockPath), ".pixelkiln", "cache");
19254
+ const cacheDir = path39.join(path39.dirname(lockPath), ".pixelkiln", "cache");
19080
19255
  const stranded = [];
19081
19256
  const invalidState = [];
19082
19257
  const staleMapUrls = [];
@@ -19092,7 +19267,7 @@ async function doctor(loaded, specs, lock, lockPath, opts = {}) {
19092
19267
  const hasSource = Boolean(entry.sourceUrl || entry.sourceUrls?.length);
19093
19268
  let hasCache = false;
19094
19269
  for (const output of entry.outputs) {
19095
- const cached = path38.join(
19270
+ const cached = path39.join(
19096
19271
  cacheDir,
19097
19272
  cacheFileName(output.sha256, output.mediaType ?? MediaType.PNG)
19098
19273
  );
@@ -19192,7 +19367,7 @@ async function nearestExistingDirectory(start) {
19192
19367
  if ((await stat4(current)).isDirectory()) return current;
19193
19368
  } catch {
19194
19369
  }
19195
- const parent = path38.dirname(current);
19370
+ const parent = path39.dirname(current);
19196
19371
  if (parent === current) return current;
19197
19372
  current = parent;
19198
19373
  }
@@ -19201,7 +19376,7 @@ async function nearestExistingDirectory(start) {
19201
19376
  // src/pipeline/audit.ts
19202
19377
  import { readFile as readFile27 } from "fs/promises";
19203
19378
  import { existsSync as existsSync32 } from "fs";
19204
- import path39 from "path";
19379
+ import path40 from "path";
19205
19380
  function paletteDistance(asset, reference) {
19206
19381
  if (!asset.length || !reference.length) return 0;
19207
19382
  let total = 0;
@@ -19266,7 +19441,7 @@ async function auditStyle(loaded, specs, styleId, lock) {
19266
19441
  let referenceFromStyleImages = false;
19267
19442
  const refPalettes = [];
19268
19443
  for (const img of style.styleImages) {
19269
- const abs = path39.resolve(loaded.root, img.path);
19444
+ const abs = path40.resolve(loaded.root, img.path);
19270
19445
  if (!existsSync32(abs)) continue;
19271
19446
  try {
19272
19447
  refPalettes.push(extractPalette(decodePng(await readFile27(abs)), 12));
@@ -19497,24 +19672,24 @@ async function runAudit(args) {
19497
19672
  }
19498
19673
 
19499
19674
  // src/cli/commands/pack.ts
19500
- import path41 from "path";
19675
+ import path42 from "path";
19501
19676
  import { readFile as readFile28 } from "fs/promises";
19502
19677
 
19503
19678
  // src/pipeline/pack.ts
19504
- import { readFileSync as readFileSync5 } from "fs";
19505
- import path40 from "path";
19679
+ import { readFileSync as readFileSync6 } from "fs";
19680
+ import path41 from "path";
19506
19681
  var DEFAULT_FRAME_SET_FPS = 12;
19507
19682
  function resolvePackInputs(raw, inputsFilePath) {
19508
19683
  if (!Array.isArray(raw) || !raw.length) {
19509
19684
  throw new Error("--inputs must be a non-empty JSON array of { id, path }");
19510
19685
  }
19511
- const baseDir = path40.dirname(path40.resolve(inputsFilePath));
19686
+ const baseDir = path41.dirname(path41.resolve(inputsFilePath));
19512
19687
  return raw.map((entry, i) => {
19513
19688
  const e = entry;
19514
19689
  if (typeof e.id !== "string" || typeof e.path !== "string") {
19515
19690
  throw new Error(`--inputs[${i}] needs string "id" and "path"`);
19516
19691
  }
19517
- return { id: e.id, path: path40.resolve(baseDir, e.path) };
19692
+ return { id: e.id, path: path41.resolve(baseDir, e.path) };
19518
19693
  });
19519
19694
  }
19520
19695
  function packSprites(inputs, options = {}) {
@@ -19535,7 +19710,7 @@ function packSprites(inputs, options = {}) {
19535
19710
  for (const input of ordered) {
19536
19711
  let sourceHash = null;
19537
19712
  try {
19538
- const bytes = readFileSync5(input.path);
19713
+ const bytes = readFileSync6(input.path);
19539
19714
  sourceHash = sha256(bytes);
19540
19715
  const png = decodePng(bytes);
19541
19716
  sprites.push({ id: input.id, width: png.width, height: png.height, pixels: png.pixels });
@@ -19598,7 +19773,7 @@ function packStyle(lock, styleId, manifestDir, options = {}) {
19598
19773
  }
19599
19774
  const inputs2 = Object.entries(options.sourceOverrides).map(([id, source]) => ({
19600
19775
  id,
19601
- path: path40.resolve(source)
19776
+ path: path41.resolve(source)
19602
19777
  }));
19603
19778
  if (!inputs2.length) throw new Error(`No approved quality outputs for style "${styleId}".`);
19604
19779
  const packed2 = packSprites(inputs2, options);
@@ -19622,7 +19797,7 @@ function packStyle(lock, styleId, manifestDir, options = {}) {
19622
19797
  let outputs = resolveEntryOutputs(entry, id, manifestDir);
19623
19798
  const source = sources[id];
19624
19799
  if (source && outputs.length <= 1) {
19625
- inputs.push({ id, path: path40.resolve(manifestDir, source) });
19800
+ inputs.push({ id, path: path41.resolve(manifestDir, source) });
19626
19801
  continue;
19627
19802
  }
19628
19803
  if (source && sourceIsStem(source, entry, manifestDir)) {
@@ -19656,7 +19831,7 @@ function packStyle(lock, styleId, manifestDir, options = {}) {
19656
19831
  }
19657
19832
  }
19658
19833
  for (const [id, source] of Object.entries(sources)) {
19659
- if (!locked.has(id)) inputs.push({ id, path: path40.resolve(manifestDir, source) });
19834
+ if (!locked.has(id)) inputs.push({ id, path: path41.resolve(manifestDir, source) });
19660
19835
  }
19661
19836
  if (!inputs.length) {
19662
19837
  throw new Error(`No readable sprites for style "${styleId}"; ${noOutput.length} skipped.`);
@@ -19693,7 +19868,7 @@ function mountSprites(placements, options) {
19693
19868
  for (const p of placements) {
19694
19869
  let sourceHash = null;
19695
19870
  try {
19696
- const bytes = readFileSync5(p.path);
19871
+ const bytes = readFileSync6(p.path);
19697
19872
  sourceHash = sha256(bytes);
19698
19873
  const png = decodePng(bytes);
19699
19874
  if (png.width > cellWidth || png.height > cellHeight) {
@@ -19764,7 +19939,7 @@ function mountStyle(lock, styleId, manifestDir, mount, cells, sources = {}, outp
19764
19939
  const source = sources[id];
19765
19940
  const entry = lock.entries[`${styleId}/${id}`];
19766
19941
  if (source && !(entry && sourceIsStem(source, entry, manifestDir))) {
19767
- placements.push({ id, path: path40.resolve(manifestDir, source), cell });
19942
+ placements.push({ id, path: path41.resolve(manifestDir, source), cell });
19768
19943
  continue;
19769
19944
  }
19770
19945
  if (source && entry) {
@@ -19797,7 +19972,7 @@ function mountStyle(lock, styleId, manifestDir, mount, cells, sources = {}, outp
19797
19972
  `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
19973
  );
19799
19974
  }
19800
- const basePng = mount.base ? readFileSync5(path40.resolve(manifestDir, mount.base)) : void 0;
19975
+ const basePng = mount.base ? readFileSync6(path41.resolve(manifestDir, mount.base)) : void 0;
19801
19976
  const mounted = mountSprites(placements, {
19802
19977
  cellWidth: mount.cellWidth,
19803
19978
  cellHeight: mount.cellHeight,
@@ -19807,7 +19982,7 @@ function mountStyle(lock, styleId, manifestDir, mount, cells, sources = {}, outp
19807
19982
  ...mounted,
19808
19983
  sources: mount.base ? [{
19809
19984
  id: "$base",
19810
- path: path40.resolve(manifestDir, mount.base),
19985
+ path: path41.resolve(manifestDir, mount.base),
19811
19986
  sha256: sha256(basePng),
19812
19987
  included: true
19813
19988
  }, ...mounted.sources] : mounted.sources,
@@ -20211,12 +20386,12 @@ async function runPack(args) {
20211
20386
  if (args.primaryOnly || args.outputRoles.length) {
20212
20387
  throw new Error("--primary-only and --output-role require manifest-driven pack");
20213
20388
  }
20214
- const raw = JSON.parse(await readFile28(path41.resolve(args.inputs), "utf8"));
20389
+ const raw = JSON.parse(await readFile28(path42.resolve(args.inputs), "utf8"));
20215
20390
  const inputs = resolvePackInputs(raw, args.inputs);
20216
20391
  const { png, atlas, skipped, sources } = packSprites(inputs, { columns: args.columns });
20217
20392
  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`) });
20393
+ const base = path42.resolve(args.out.replace(/\.(?:png|json|tres)$/i, ""));
20394
+ const { extension, document } = renderSheetDocument(format, atlas, { imageName: path42.basename(`${base}.png`) });
20220
20395
  const outputs = [
20221
20396
  { path: `${base}.png`, data: png },
20222
20397
  { path: `${base}${extension}`, data: document }
@@ -20229,7 +20404,7 @@ async function runPack(args) {
20229
20404
  log(
20230
20405
  ` ${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
20406
  );
20232
- log(` ${path41.relative(process.cwd(), base)}.png + ${extension} + .pixelkiln.json`);
20407
+ log(` ${path42.relative(process.cwd(), base)}.png + ${extension} + .pixelkiln.json`);
20233
20408
  for (const s of skipped) log(` skipped ${s.id}: ${s.reason}`);
20234
20409
  return;
20235
20410
  }
@@ -20237,7 +20412,7 @@ async function runPack(args) {
20237
20412
  if (args.primaryOnly && args.outputRoles.length) {
20238
20413
  throw new Error("pack accepts either --primary-only or --output-role, not both");
20239
20414
  }
20240
- const manifestDir = path41.dirname(path41.resolve(args.manifest));
20415
+ const manifestDir = path42.dirname(path42.resolve(args.manifest));
20241
20416
  const styleIds = args.styles.length ? args.styles : Object.keys(loaded.manifest.styles);
20242
20417
  for (const styleId of styleIds) {
20243
20418
  const style = loaded.manifest.styles[styleId];
@@ -20252,9 +20427,9 @@ async function runPack(args) {
20252
20427
  sourceOverrides: qualitySources,
20253
20428
  sources: manifestSources(loaded.manifest, styleId)
20254
20429
  });
20255
- const base = args.out ? path41.resolve(args.out.replace(/\.(?:png|json|tres)$/i, "")) : path41.resolve(manifestDir, style.outDir, `${styleId}-sheet`);
20430
+ const base = args.out ? path42.resolve(args.out.replace(/\.(?:png|json|tres)$/i, "")) : path42.resolve(manifestDir, style.outDir, `${styleId}-sheet`);
20256
20431
  const format = sheetFormat(args);
20257
- const { extension, document } = renderSheetDocument(format, atlas, { imageName: path41.basename(`${base}.png`) });
20432
+ const { extension, document } = renderSheetDocument(format, atlas, { imageName: path42.basename(`${base}.png`) });
20258
20433
  const outputs = [
20259
20434
  { path: `${base}.png`, data: png },
20260
20435
  { path: `${base}${extension}`, data: document }
@@ -20286,13 +20461,13 @@ async function runPack(args) {
20286
20461
  log(
20287
20462
  ` ${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
20463
  );
20289
- log(` ${path41.relative(process.cwd(), base)}.png + ${extension} + .pixelkiln.json`);
20464
+ log(` ${path42.relative(process.cwd(), base)}.png + ${extension} + .pixelkiln.json`);
20290
20465
  for (const s of skipped) log(` skipped ${s.id}: ${s.reason}`);
20291
20466
  }
20292
20467
  }
20293
20468
  async function runMount(args) {
20294
20469
  const { loaded, lock } = await openProject2(args);
20295
- const manifestDir = path41.dirname(path41.resolve(args.manifest));
20470
+ const manifestDir = path42.dirname(path42.resolve(args.manifest));
20296
20471
  const styleIds = args.styles.length ? args.styles : Object.keys(loaded.manifest.styles);
20297
20472
  for (const styleId of styleIds) {
20298
20473
  const style = loaded.manifest.styles[styleId];
@@ -20331,9 +20506,9 @@ async function runMount(args) {
20331
20506
  sources,
20332
20507
  outputRoles
20333
20508
  );
20334
- const out = path41.resolve(manifestDir, style.mount.out);
20509
+ const out = path42.resolve(manifestDir, style.mount.out);
20335
20510
  const format = sheetFormat(args);
20336
- const { extension, document } = renderSheetDocument(format, atlas, { imageName: path41.basename(out) });
20511
+ const { extension, document } = renderSheetDocument(format, atlas, { imageName: path42.basename(out) });
20337
20512
  const metadata = out.replace(/\.png$/, "") + extension;
20338
20513
  const companion = out.replace(/\.png$/, "") + ".pixelkiln.json";
20339
20514
  const outputs = [
@@ -20353,7 +20528,7 @@ async function runMount(args) {
20353
20528
  await provenanceFile("$lock", args.lock),
20354
20529
  ...qualityRecords,
20355
20530
  ...artifactSources.filter(
20356
- (source) => source.id !== "$base" || path41.resolve(source.path) !== out
20531
+ (source) => source.id !== "$base" || path42.resolve(source.path) !== out
20357
20532
  )
20358
20533
  ],
20359
20534
  options: {
@@ -20367,14 +20542,14 @@ async function runMount(args) {
20367
20542
  log(
20368
20543
  ` ${styleId}: ${atlas.frames.length} cell(s) into ${atlas.sheet.width}x${atlas.sheet.height}` + (overBase ? ` over ${style.mount.base}` : " (new sheet)")
20369
20544
  );
20370
- log(` ${path41.relative(process.cwd(), out)} + atlas/provenance JSON`);
20545
+ log(` ${path42.relative(process.cwd(), out)} + atlas/provenance JSON`);
20371
20546
  for (const s of skipped) log(` skipped ${s.id}: ${s.reason}`);
20372
20547
  }
20373
20548
  }
20374
20549
  async function runExport(args) {
20375
20550
  const { loaded, specs, lock } = await openProject2(args);
20376
20551
  const format = args.format ?? "generic";
20377
- const manifestDir = path41.dirname(path41.resolve(args.manifest));
20552
+ const manifestDir = path42.dirname(path42.resolve(args.manifest));
20378
20553
  const selected = specs.filter((spec) => {
20379
20554
  if (spec.generator !== "tiles" && spec.generator !== "terrain") return false;
20380
20555
  if (args.styles.length && !args.styles.includes(spec.styleId)) return false;
@@ -20390,12 +20565,12 @@ async function runExport(args) {
20390
20565
  for (const spec of selected) {
20391
20566
  const entry = lock.entries[lockKey(spec.styleId, spec.assetId)];
20392
20567
  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;
20568
+ const defaultBase = path42.resolve(manifestDir, style.outDir, `${spec.assetId}-tileset`);
20569
+ const base = args.out ? path42.resolve(args.out.replace(/\.(?:png|json|tsj|tres)$/i, "")) : defaultBase;
20395
20570
  const result = exportTileset(entry, spec, {
20396
20571
  format,
20397
20572
  manifestDir,
20398
- imageName: path41.basename(`${base}.png`),
20573
+ imageName: path42.basename(`${base}.png`),
20399
20574
  columns: args.columns
20400
20575
  });
20401
20576
  const outputs = [
@@ -20413,7 +20588,7 @@ async function runExport(args) {
20413
20588
  asset: spec.assetId,
20414
20589
  columns: args.columns ?? null,
20415
20590
  format,
20416
- image: path41.basename(`${base}.png`),
20591
+ image: path42.basename(`${base}.png`),
20417
20592
  providerRules: result.generic.providerRules,
20418
20593
  style: spec.styleId,
20419
20594
  tileType: spec.tileType ?? null
@@ -20423,7 +20598,7 @@ async function runExport(args) {
20423
20598
  ` ${spec.styleId}/${spec.assetId}: ${result.generic.tiles.length} tile(s), ${result.generic.sheet.width}x${result.generic.sheet.height} (${format})`
20424
20599
  );
20425
20600
  log(
20426
- ` ${path41.relative(process.cwd(), base)}.png + ${path41.basename(base)}${result.extension} + .pixelkiln.json`
20601
+ ` ${path42.relative(process.cwd(), base)}.png + ${path42.basename(base)}${result.extension} + .pixelkiln.json`
20427
20602
  );
20428
20603
  }
20429
20604
  }
@@ -20436,21 +20611,21 @@ function sheetFormat(args) {
20436
20611
  }
20437
20612
 
20438
20613
  // src/cli/commands/pixellab-utils.ts
20439
- import path43 from "path";
20614
+ import path44 from "path";
20440
20615
 
20441
20616
  // src/pixellab-utils.ts
20442
20617
  import { existsSync as existsSync33 } from "fs";
20443
20618
  import { mkdir as mkdir13, readFile as readFile29, writeFile as writeFile18 } from "fs/promises";
20444
- import path42 from "path";
20619
+ import path43 from "path";
20445
20620
  var UNZOOM_MIN_SIDE = 256;
20446
20621
  var UNZOOM_MAX_AREA = 2048 * 2048;
20447
20622
  function defaultUnzoomOut(input) {
20448
- const ext = path42.extname(input);
20623
+ const ext = path43.extname(input);
20449
20624
  return `${ext ? input.slice(0, -ext.length) : input}.unzoomed.png`;
20450
20625
  }
20451
20626
  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");
20627
+ const out = path43.resolve(opts.out ?? defaultUnzoomOut(input));
20628
+ if (path43.resolve(input) === out) throw new Error("unzoom will not overwrite its own input; pass a different --out");
20454
20629
  if (existsSync33(out) && !opts.force) throw new Error(`${out} already exists; pass --force to replace it`);
20455
20630
  const bytes = await readFile29(input);
20456
20631
  const metadata = imageMetadata(bytes);
@@ -20465,7 +20640,7 @@ async function unzoomFile(client2, input, opts = {}) {
20465
20640
  throw new Error(`${input} is ${width}x${height}; PixelLab's unzoom takes at most 2048x2048 worth of pixels`);
20466
20641
  }
20467
20642
  const res = await client2.unzoom({ image: { base64: bytes.toString("base64"), format }, quantize: opts.quantize });
20468
- await mkdir13(path42.dirname(out), { recursive: true });
20643
+ await mkdir13(path43.dirname(out), { recursive: true });
20469
20644
  await writeFile18(out, res.png);
20470
20645
  return { out, original: res.originalSize, unzoomed: res.unzoomedSize, zoomFactor: res.zoomFactor, usage: res.usage };
20471
20646
  }
@@ -20473,7 +20648,7 @@ var FONT_WEIGHTS = ["Bold", "Regular"];
20473
20648
  var FONT_GLYPH_SIZES = [8, 16, 32, 64];
20474
20649
  var FONT_COST_GENERATIONS = 25;
20475
20650
  function fontOutputPaths(out) {
20476
- const base = path42.resolve(out.replace(/\.(ttf|png)$/i, ""));
20651
+ const base = path43.resolve(out.replace(/\.(ttf|png)$/i, ""));
20477
20652
  return { ttf: `${base}.ttf`, atlas: `${base}.png` };
20478
20653
  }
20479
20654
  async function generateFont(client2, opts) {
@@ -20507,7 +20682,7 @@ async function generateFont(client2, opts) {
20507
20682
  client2.download(job.download_ttf_url),
20508
20683
  client2.download(job.download_atlas_url)
20509
20684
  ]);
20510
- await mkdir13(path42.dirname(ttf), { recursive: true });
20685
+ await mkdir13(path43.dirname(ttf), { recursive: true });
20511
20686
  await writeFile18(ttf, ttfBytes);
20512
20687
  await writeFile18(atlas, atlasBytes);
20513
20688
  return { jobId, ttf, atlas, glyphPx: job.glyph_px ?? null, usage: job.usage };
@@ -20523,7 +20698,7 @@ async function generateFont(client2, opts) {
20523
20698
 
20524
20699
  // src/cli/commands/pixellab-utils.ts
20525
20700
  function loadKeys(args) {
20526
- loadEnvFiles(path43.dirname(path43.resolve(args.manifest)));
20701
+ loadEnvFiles(path44.dirname(path44.resolve(args.manifest)));
20527
20702
  loadEnvFiles(process.cwd());
20528
20703
  }
20529
20704
  async function runUnzoom(args) {
@@ -20589,11 +20764,11 @@ async function runFont(args) {
20589
20764
  }
20590
20765
 
20591
20766
  // src/cli/commands/quality.ts
20592
- import path45 from "path";
20767
+ import path46 from "path";
20593
20768
  import { readFile as readFile31 } from "fs/promises";
20594
20769
 
20595
20770
  // src/pipeline/quality-regression.ts
20596
- import path44 from "path";
20771
+ import path45 from "path";
20597
20772
  import { existsSync as existsSync34 } from "fs";
20598
20773
  import { readFile as readFile30 } from "fs/promises";
20599
20774
  import { z as z13 } from "zod";
@@ -20601,7 +20776,7 @@ var SHA256_RE = /^[0-9a-f]{64}$/;
20601
20776
  var HEX_RE = /^#[0-9a-f]{6}$/;
20602
20777
  var MAX_REDMEAN_DISTANCE = 765;
20603
20778
  var QualityPathSchema = z13.string().min(1).refine(
20604
- (value) => !path44.posix.isAbsolute(value) && !value.includes("\\"),
20779
+ (value) => !path45.posix.isAbsolute(value) && !value.includes("\\"),
20605
20780
  "expected a portable relative path"
20606
20781
  );
20607
20782
  var DEFAULT_QUALITY_TOLERANCES = {
@@ -20748,7 +20923,7 @@ function measureDecodedImageQuality(png, digest2) {
20748
20923
  };
20749
20924
  }
20750
20925
  async function measureImageQuality(file) {
20751
- const absolute = path44.resolve(file);
20926
+ const absolute = path45.resolve(file);
20752
20927
  let bytes;
20753
20928
  try {
20754
20929
  bytes = await readFile30(absolute);
@@ -20770,11 +20945,11 @@ function resolveQualityInputs(raw, inputsFilePath) {
20770
20945
  }
20771
20946
  const ids = parsed.data.map((entry) => entry.id);
20772
20947
  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));
20948
+ const root = path45.dirname(path45.resolve(inputsFilePath));
20774
20949
  return parsed.data.map((entry) => ({
20775
20950
  id: entry.id,
20776
- file: path44.resolve(root, entry.path),
20777
- ...entry.record ? { record: path44.resolve(root, entry.record) } : {},
20951
+ file: path45.resolve(root, entry.path),
20952
+ ...entry.record ? { record: path45.resolve(root, entry.record) } : {},
20778
20953
  tolerances: QualityToleranceSchema.parse({
20779
20954
  ...DEFAULT_QUALITY_TOLERANCES,
20780
20955
  ...entry.tolerances
@@ -20782,7 +20957,7 @@ function resolveQualityInputs(raw, inputsFilePath) {
20782
20957
  }));
20783
20958
  }
20784
20959
  function portableRelative2(from, to) {
20785
- return path44.relative(from, path44.resolve(to)).split(path44.sep).join("/") || ".";
20960
+ return path45.relative(from, path45.resolve(to)).split(path45.sep).join("/") || ".";
20786
20961
  }
20787
20962
  async function verifyRecordForImage(recordPath, imagePath) {
20788
20963
  const verification = await verifyArtifactBundle(recordPath);
@@ -20797,20 +20972,20 @@ async function verifyRecordForImage(recordPath, imagePath) {
20797
20972
  if (record.kind !== "refine" || options === null || typeof options !== "object" || options.schema !== "pixelkiln-quality") {
20798
20973
  throw new Error(`Quality record ${recordPath} is not a PixelKiln refinement record.`);
20799
20974
  }
20800
- const outputs = record.outputs.map((output) => path44.resolve(path44.dirname(recordPath), output.path));
20801
- if (!outputs.includes(path44.resolve(imagePath))) {
20975
+ const outputs = record.outputs.map((output) => path45.resolve(path45.dirname(recordPath), output.path));
20976
+ if (!outputs.includes(path45.resolve(imagePath))) {
20802
20977
  throw new Error(`Quality record ${recordPath} does not own ${imagePath}.`);
20803
20978
  }
20804
20979
  }
20805
20980
  async function snapshotQualityBaseline(inputs, baselinePath, options = {}) {
20806
20981
  if (!inputs.length) throw new Error("Cannot snapshot an empty quality baseline.");
20807
- const absolute = path44.resolve(baselinePath);
20982
+ const absolute = path45.resolve(baselinePath);
20808
20983
  for (const input of inputs) {
20809
- if (path44.resolve(input.file) === absolute || input.record && path44.resolve(input.record) === absolute) {
20984
+ if (path45.resolve(input.file) === absolute || input.record && path45.resolve(input.record) === absolute) {
20810
20985
  throw new Error(`Quality baseline output would overwrite an input: ${absolute}.`);
20811
20986
  }
20812
20987
  }
20813
- const root = path44.dirname(absolute);
20988
+ const root = path45.dirname(absolute);
20814
20989
  const cases = [];
20815
20990
  for (const input of [...inputs].sort((a, b) => a.id.localeCompare(b.id))) {
20816
20991
  const expected = await measureImageQuality(input.file);
@@ -20850,7 +21025,7 @@ async function snapshotQualityBaseline(inputs, baselinePath, options = {}) {
20850
21025
  return { baseline, path: absolute, changed: result.changed.length > 0 };
20851
21026
  }
20852
21027
  async function readQualityBaseline(baselinePath) {
20853
- const absolute = path44.resolve(baselinePath);
21028
+ const absolute = path45.resolve(baselinePath);
20854
21029
  let raw;
20855
21030
  try {
20856
21031
  raw = JSON.parse(await readFile30(absolute, "utf8"));
@@ -20923,12 +21098,12 @@ function regressionViolations(expected, actual, tolerances) {
20923
21098
  return { violations, warnings };
20924
21099
  }
20925
21100
  async function checkQualityBaseline(baselinePath) {
20926
- const absolute = path44.resolve(baselinePath);
21101
+ const absolute = path45.resolve(baselinePath);
20927
21102
  const baseline = await readQualityBaseline(absolute);
20928
- const root = path44.dirname(absolute);
21103
+ const root = path45.dirname(absolute);
20929
21104
  const cases = [];
20930
21105
  for (const entry of baseline.cases) {
20931
- const file = path44.resolve(root, entry.file);
21106
+ const file = path45.resolve(root, entry.file);
20932
21107
  let actual = null;
20933
21108
  const violations = [];
20934
21109
  const warnings = [];
@@ -20941,7 +21116,7 @@ async function checkQualityBaseline(baselinePath) {
20941
21116
  violations.push(message4(error));
20942
21117
  }
20943
21118
  if (entry.record) {
20944
- const recordPath = path44.resolve(root, entry.record.path);
21119
+ const recordPath = path45.resolve(root, entry.record.path);
20945
21120
  try {
20946
21121
  const recordHash = await sha256File(recordPath);
20947
21122
  if (recordHash !== entry.record.sha256) violations.push("quality record hash changed");
@@ -20983,15 +21158,15 @@ async function runQuality(args) {
20983
21158
  if (!args.out) throw new Error("quality snapshot needs --out <pixelkiln.quality.json>.");
20984
21159
  let raw;
20985
21160
  try {
20986
- raw = JSON.parse(await readFile31(path45.resolve(args.inputs), "utf8"));
21161
+ raw = JSON.parse(await readFile31(path46.resolve(args.inputs), "utf8"));
20987
21162
  } catch (error) {
20988
21163
  throw new Error(
20989
- `Could not read quality inputs ${path45.resolve(args.inputs)}: ${error instanceof Error ? error.message : String(error)}`,
21164
+ `Could not read quality inputs ${path46.resolve(args.inputs)}: ${error instanceof Error ? error.message : String(error)}`,
20990
21165
  { cause: error }
20991
21166
  );
20992
21167
  }
20993
21168
  const inputs = resolveQualityInputs(raw, args.inputs);
20994
- if (path45.resolve(args.inputs) === path45.resolve(args.out)) {
21169
+ if (path46.resolve(args.inputs) === path46.resolve(args.out)) {
20995
21170
  throw new Error("quality snapshot --out must not overwrite its --inputs file.");
20996
21171
  }
20997
21172
  const result = await snapshotQualityBaseline(inputs, args.out, { force: args.force });
@@ -21004,7 +21179,7 @@ async function runQuality(args) {
21004
21179
  }, null, 2));
21005
21180
  } else {
21006
21181
  log(
21007
- ` ${result.changed ? "wrote" : "unchanged"} ${path45.relative(process.cwd(), result.path)} (${result.baseline.cases.length} case(s))`
21182
+ ` ${result.changed ? "wrote" : "unchanged"} ${path46.relative(process.cwd(), result.path)} (${result.baseline.cases.length} case(s))`
21008
21183
  );
21009
21184
  log(` Review the tolerances, commit the baseline, then run quality check in CI.`);
21010
21185
  }
@@ -21030,10 +21205,10 @@ async function runQuality(args) {
21030
21205
  }
21031
21206
 
21032
21207
  // src/cli/commands/recipe.ts
21033
- import path47 from "path";
21208
+ import path48 from "path";
21034
21209
 
21035
21210
  // src/recipes.ts
21036
- import path46 from "path";
21211
+ import path47 from "path";
21037
21212
  import { existsSync as existsSync35 } from "fs";
21038
21213
  import { readdir as readdir3, readFile as readFile32 } from "fs/promises";
21039
21214
  import { fileURLToPath as fileURLToPath2 } from "url";
@@ -21044,7 +21219,7 @@ var VERSION_RE = /^\d+\.\d+\.\d+$/;
21044
21219
  var RECIPE_FILE = "pixelkiln.recipe.json";
21045
21220
  var RECIPE_DIR_TOKEN = "{{recipeDir}}";
21046
21221
  var PortablePathSchema = z14.string().min(1).refine((value) => {
21047
- if (value.includes("\\") || path46.posix.isAbsolute(value)) return false;
21222
+ if (value.includes("\\") || path47.posix.isAbsolute(value)) return false;
21048
21223
  const parts = value.split("/");
21049
21224
  return !parts.includes("") && !parts.includes(".") && !parts.includes("..");
21050
21225
  }, "expected a portable relative path without '.', '..', or backslashes");
@@ -21220,7 +21395,7 @@ function message5(error) {
21220
21395
  return error instanceof Error ? error.message : String(error);
21221
21396
  }
21222
21397
  async function readRecipeFile(recipePath, bundled) {
21223
- const absolute = path46.resolve(recipePath);
21398
+ const absolute = path47.resolve(recipePath);
21224
21399
  let raw;
21225
21400
  try {
21226
21401
  raw = JSON.parse(await readFile32(absolute, "utf8"));
@@ -21234,13 +21409,13 @@ async function readRecipeFile(recipePath, bundled) {
21234
21409
  ` + parsed.error.issues.map((issue) => ` ${issue.path.join(".") || "$"}: ${issue.message}`).join("\n")
21235
21410
  );
21236
21411
  }
21237
- return { recipe: parsed.data, authored: raw, path: absolute, dir: path46.dirname(absolute), bundled };
21412
+ return { recipe: parsed.data, authored: raw, path: absolute, dir: path47.dirname(absolute), bundled };
21238
21413
  }
21239
21414
  async function walkRecipeFiles(root) {
21240
21415
  if (!existsSync35(root)) return [];
21241
21416
  const out = [];
21242
21417
  for (const entry of await readdir3(root, { withFileTypes: true })) {
21243
- const resolved = path46.join(root, entry.name);
21418
+ const resolved = path47.join(root, entry.name);
21244
21419
  if (entry.isDirectory()) out.push(...await walkRecipeFiles(resolved));
21245
21420
  else if (entry.isFile() && entry.name === RECIPE_FILE) out.push(resolved);
21246
21421
  }
@@ -21251,7 +21426,7 @@ function bundledRecipeRoot() {
21251
21426
  }
21252
21427
  async function listBundledRecipes(root = bundledRecipeRoot()) {
21253
21428
  const recipes = await Promise.all(
21254
- (await walkRecipeFiles(path46.resolve(root))).map((file) => readRecipeFile(file, true))
21429
+ (await walkRecipeFiles(path47.resolve(root))).map((file) => readRecipeFile(file, true))
21255
21430
  );
21256
21431
  return recipes.sort(
21257
21432
  (a, b) => a.recipe.id.localeCompare(b.recipe.id) || compareVersions(b.recipe.version, a.recipe.version)
@@ -21274,9 +21449,9 @@ function parseSelector(selector) {
21274
21449
  return { id: match[1], version: match[2] };
21275
21450
  }
21276
21451
  async function resolveRecipe(target, bundledRoot = bundledRecipeRoot()) {
21277
- const local = path46.resolve(target);
21452
+ const local = path47.resolve(target);
21278
21453
  if (existsSync35(local)) {
21279
- const recipePath = path46.basename(local) === RECIPE_FILE ? local : path46.join(local, RECIPE_FILE);
21454
+ const recipePath = path47.basename(local) === RECIPE_FILE ? local : path47.join(local, RECIPE_FILE);
21280
21455
  return readRecipeFile(recipePath, false);
21281
21456
  }
21282
21457
  if (target.startsWith(".") || target.startsWith("/") || target.includes("\\")) {
@@ -21290,7 +21465,7 @@ async function resolveRecipe(target, bundledRoot = bundledRecipeRoot()) {
21290
21465
  return matches[0];
21291
21466
  }
21292
21467
  async function verifyFile(root, file) {
21293
- const absolute = path46.resolve(root, ...file.path.split("/"));
21468
+ const absolute = path47.resolve(root, ...file.path.split("/"));
21294
21469
  if (!existsSync35(absolute)) {
21295
21470
  return { path: file.path, expectedSha256: file.sha256, actualSha256: null, status: "missing" };
21296
21471
  }
@@ -21306,7 +21481,7 @@ async function verifyRecipe(target, options = {}) {
21306
21481
  const loaded = await resolveRecipe(target, options.bundledRoot);
21307
21482
  const actualIntegrity = recipeDigest(loaded.authored);
21308
21483
  const files = await Promise.all(loaded.recipe.files.map((file) => verifyFile(loaded.dir, file)));
21309
- const modelRoot = options.modelRoot ? path46.resolve(options.modelRoot) : null;
21484
+ const modelRoot = options.modelRoot ? path47.resolve(options.modelRoot) : null;
21310
21485
  const models = modelRoot ? await Promise.all(loaded.recipe.models.map(async (model) => ({
21311
21486
  ...await verifyFile(modelRoot, model),
21312
21487
  source: model.source,
@@ -21340,7 +21515,7 @@ async function verifyRecipe(target, options = {}) {
21340
21515
  };
21341
21516
  }
21342
21517
  function renderStyle(style, destination, cwd) {
21343
- const portableDir = path46.relative(cwd, destination).split(path46.sep).join("/") || ".";
21518
+ const portableDir = path47.relative(cwd, destination).split(path47.sep).join("/") || ".";
21344
21519
  const replace = (value) => {
21345
21520
  if (typeof value === "string") return value.replaceAll(RECIPE_DIR_TOKEN, portableDir);
21346
21521
  if (Array.isArray(value)) return value.map(replace);
@@ -21352,19 +21527,19 @@ function renderStyle(style, destination, cwd) {
21352
21527
  return replace(style);
21353
21528
  }
21354
21529
  async function installRecipe(target, options = {}) {
21355
- const cwd = path46.resolve(options.cwd ?? process.cwd());
21530
+ const cwd = path47.resolve(options.cwd ?? process.cwd());
21356
21531
  const loaded = await resolveRecipe(target, options.bundledRoot);
21357
21532
  const verification = await verifyRecipe(loaded.path);
21358
21533
  if (!verification.ok) {
21359
21534
  throw new Error(`Recipe ${loaded.recipe.id}@${loaded.recipe.version} failed integrity verification.`);
21360
21535
  }
21361
- const destination = options.out ? path46.resolve(cwd, options.out) : path46.join(cwd, "pixelkiln-recipes", ...loaded.recipe.id.split("/"), loaded.recipe.version);
21536
+ const destination = options.out ? path47.resolve(cwd, options.out) : path47.join(cwd, "pixelkiln-recipes", ...loaded.recipe.id.split("/"), loaded.recipe.version);
21362
21537
  const sources = [
21363
21538
  { path: RECIPE_FILE, source: loaded.path },
21364
- ...loaded.recipe.files.map((file) => ({ path: file.path, source: path46.join(loaded.dir, ...file.path.split("/")) }))
21539
+ ...loaded.recipe.files.map((file) => ({ path: file.path, source: path47.join(loaded.dir, ...file.path.split("/")) }))
21365
21540
  ];
21366
21541
  const files = await Promise.all(sources.map(async (file) => ({
21367
- path: path46.join(destination, ...file.path.split("/")),
21542
+ path: path47.join(destination, ...file.path.split("/")),
21368
21543
  data: await readFile32(file.source)
21369
21544
  })));
21370
21545
  if (!options.force) {
@@ -21464,14 +21639,14 @@ async function runRecipe(args) {
21464
21639
  }, null, 2));
21465
21640
  } else {
21466
21641
  log(` installed ${result.recipe.id}@${result.recipe.version}`);
21467
- log(` ${path47.relative(process.cwd(), result.destination) || "."}`);
21642
+ log(` ${path48.relative(process.cwd(), result.destination) || "."}`);
21468
21643
  log(`
21469
21644
  Add this entry under your manifest's styles object:`);
21470
21645
  log(JSON.stringify({ [result.styleId]: result.style }, null, 2));
21471
21646
  if (result.recipe.models.length) {
21472
21647
  log(`
21473
21648
  Models are not downloaded automatically. Verify them with:`);
21474
- log(` pixelkiln recipe verify ${path47.relative(process.cwd(), result.destination)} --model-root <ComfyUI/models>`);
21649
+ log(` pixelkiln recipe verify ${path48.relative(process.cwd(), result.destination)} --model-root <ComfyUI/models>`);
21475
21650
  }
21476
21651
  }
21477
21652
  return;
@@ -21479,7 +21654,7 @@ async function runRecipe(args) {
21479
21654
  }
21480
21655
 
21481
21656
  // src/cli/commands/refine.ts
21482
- import path48 from "path";
21657
+ import path49 from "path";
21483
21658
  async function runRefine(args) {
21484
21659
  if (args.from || args.subcommand === "approve") {
21485
21660
  if (!args.from) {
@@ -21509,12 +21684,12 @@ async function runRefine(args) {
21509
21684
  ` recovered ${result3.detection.columns}x${result3.detection.rows} native grid (${result3.detection.confidence}: ${result3.detection.consensus})`
21510
21685
  );
21511
21686
  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)}`);
21687
+ log(` wrote ${path49.relative(process.cwd(), result3.output)}`);
21688
+ log(` quality record: ${path49.relative(process.cwd(), result3.record)}`);
21514
21689
  log(`
21515
21690
  Automated checks passed. Human 1\xD7 review is still required:`);
21516
21691
  log(
21517
- ` pixelkiln refine approve --from ${path48.relative(process.cwd(), result3.record)} --reviewer "Your Name"`
21692
+ ` pixelkiln refine approve --from ${path49.relative(process.cwd(), result3.record)} --reviewer "Your Name"`
21518
21693
  );
21519
21694
  }
21520
21695
  return;
@@ -21542,7 +21717,7 @@ async function runRefine(args) {
21542
21717
  if (args.json) log(JSON.stringify(result3, null, 2));
21543
21718
  else {
21544
21719
  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()}`
21720
+ 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
21721
  );
21547
21722
  }
21548
21723
  return;
@@ -21551,7 +21726,7 @@ async function runRefine(args) {
21551
21726
  if (args.json) {
21552
21727
  log(JSON.stringify(result2, null, 2));
21553
21728
  } else {
21554
- log(` ${result2.safe ? "release-ready" : "not release-ready"}: ${path48.relative(process.cwd(), result2.output || result2.record)}`);
21729
+ log(` ${result2.safe ? "release-ready" : "not release-ready"}: ${path49.relative(process.cwd(), result2.output || result2.record)}`);
21555
21730
  for (const reason of result2.reasons) log(` ${reason}`);
21556
21731
  }
21557
21732
  if (!result2.safe) process.exitCode = 1;
@@ -21597,7 +21772,7 @@ async function runRefine(args) {
21597
21772
  log(` ${item.state.padEnd(16)} ${item.key} ${item.reason}`);
21598
21773
  if (item.state === "needs-approval") {
21599
21774
  log(
21600
- ` pixelkiln refine approve --from ${path48.relative(process.cwd(), item.record)} --reviewer "Your Name"`
21775
+ ` pixelkiln refine approve --from ${path49.relative(process.cwd(), item.record)} --reviewer "Your Name"`
21601
21776
  );
21602
21777
  }
21603
21778
  }
@@ -21608,7 +21783,7 @@ async function runRefine(args) {
21608
21783
  }
21609
21784
 
21610
21785
  // src/cli/commands/skeleton.ts
21611
- import path49 from "path";
21786
+ import path50 from "path";
21612
21787
  import { existsSync as existsSync36 } from "fs";
21613
21788
  import { readFile as readFile33, writeFile as writeFile19 } from "fs/promises";
21614
21789
 
@@ -21721,12 +21896,12 @@ var Canvas = class {
21721
21896
  async function runEstimateSkeleton(args) {
21722
21897
  const image = args.target;
21723
21898
  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) {
21899
+ if (args.out && existsSync36(path50.resolve(args.out)) && !args.force) {
21725
21900
  throw new UsageError(`${args.out} already exists; pass --force to replace it`);
21726
21901
  }
21727
- loadEnvFiles(path49.dirname(path49.resolve(args.manifest)));
21902
+ loadEnvFiles(path50.dirname(path50.resolve(args.manifest)));
21728
21903
  loadEnvFiles(process.cwd());
21729
- const bytes = await readFile33(path49.resolve(image));
21904
+ const bytes = await readFile33(path50.resolve(image));
21730
21905
  const metadata = imageMetadata(bytes);
21731
21906
  if (!metadata) throw new UsageError(`${image} is not a readable PNG or JPEG`);
21732
21907
  const sizeProblem = estimateSkeletonSizeProblem(metadata.width, metadata.height);
@@ -21738,7 +21913,7 @@ async function runEstimateSkeleton(args) {
21738
21913
  const frames = args.frames ?? DEFAULT_SCAFFOLD_FRAMES;
21739
21914
  const json = JSON.stringify(scaffoldSkeletonSet(res.keypoints, frames), null, 2);
21740
21915
  if (args.out) {
21741
- await writeFile19(path49.resolve(args.out), json + "\n");
21916
+ await writeFile19(path50.resolve(args.out), json + "\n");
21742
21917
  log(` wrote ${args.out}: the estimated pose and ${frames} frames to edit`);
21743
21918
  log(` preview it with: pixelkiln skeleton-preview ${args.out} --from ${image}`);
21744
21919
  } else {
@@ -21751,8 +21926,8 @@ async function runSkeletonPreview(args) {
21751
21926
  let sourceFile;
21752
21927
  let label;
21753
21928
  if (target.endsWith(".json")) {
21754
- keypointsFile = path49.resolve(target);
21755
- sourceFile = args.from ? path49.resolve(args.from) : void 0;
21929
+ keypointsFile = path50.resolve(target);
21930
+ sourceFile = args.from ? path50.resolve(args.from) : void 0;
21756
21931
  label = target;
21757
21932
  } else {
21758
21933
  const loaded = await loadManifest(args.manifest);
@@ -21764,7 +21939,7 @@ async function runSkeletonPreview(args) {
21764
21939
  );
21765
21940
  }
21766
21941
  keypointsFile = spec.revision.keypointsFile;
21767
- sourceFile = args.from ? path49.resolve(args.from) : spec.revision.sourceFile;
21942
+ sourceFile = args.from ? path50.resolve(args.from) : spec.revision.sourceFile;
21768
21943
  label = `${spec.styleId}/${spec.assetId}`;
21769
21944
  }
21770
21945
  if (!existsSync36(keypointsFile)) throw new UsageError(`Keypoints file does not exist yet: ${keypointsFile}`);
@@ -21785,10 +21960,10 @@ async function runSkeletonPreview(args) {
21785
21960
  source = decodePng(bytes);
21786
21961
  }
21787
21962
  }
21788
- const out = path49.resolve(args.out ?? keypointsFile.replace(/\.json$/, "") + ".preview.png");
21963
+ const out = path50.resolve(args.out ?? keypointsFile.replace(/\.json$/, "") + ".preview.png");
21789
21964
  await writeFile19(out, renderSkeletonSheet(set, { source }));
21790
21965
  log(` ${label}: ${set.frames.length} frames after the starting pose`);
21791
- const shown = path49.relative(process.cwd(), out);
21966
+ const shown = path50.relative(process.cwd(), out);
21792
21967
  log(` wrote ${shown.startsWith("..") ? out : shown}`);
21793
21968
  }
21794
21969
 
@@ -21834,15 +22009,15 @@ async function runTools(args) {
21834
22009
  }
21835
22010
 
21836
22011
  // src/cli/commands/workspace.ts
21837
- import path50 from "path";
22012
+ import path51 from "path";
21838
22013
  import { existsSync as existsSync37 } from "fs";
21839
22014
  async function runWorkspace(args) {
21840
- const workspacePath = path50.resolve(args.workspace ?? "pixelkiln.workspace.json");
21841
- const dir = path50.dirname(workspacePath);
22015
+ const workspacePath = path51.resolve(args.workspace ?? "pixelkiln.workspace.json");
22016
+ const dir = path51.dirname(workspacePath);
21842
22017
  if (args.subcommand === "add") {
21843
- const manifestPath = path50.resolve(args.target);
22018
+ const manifestPath = path51.resolve(args.target);
21844
22019
  const loadedTarget = await loadManifest(manifestPath);
21845
- const lockPath = args.explicitLock ? path50.resolve(args.explicitLock) : path50.join(path50.dirname(manifestPath), "pixelkiln.lock.json");
22020
+ const lockPath = args.explicitLock ? path51.resolve(args.explicitLock) : path51.join(path51.dirname(manifestPath), "pixelkiln.lock.json");
21846
22021
  const ws = await loadWorkspace(workspacePath);
21847
22022
  const id = args.name ?? loadedTarget.manifest.name;
21848
22023
  if (ws.projects.some((p) => p.id === id)) {
@@ -21862,7 +22037,7 @@ async function runWorkspace(args) {
21862
22037
  ...args.account ? { account: args.account } : {}
21863
22038
  };
21864
22039
  await saveWorkspace(workspacePath, { version: 1, projects: [...ws.projects, project] });
21865
- log(` registered "${id}" in ${path50.relative(process.cwd(), workspacePath)}`);
22040
+ log(` registered "${id}" in ${path51.relative(process.cwd(), workspacePath)}`);
21866
22041
  log(` manifest: ${project.manifest}`);
21867
22042
  log(` lock: ${project.lock}`);
21868
22043
  if (!existsSync37(lockPath)) {
@@ -21874,7 +22049,7 @@ async function runWorkspace(args) {
21874
22049
  }
21875
22050
  if (args.subcommand === "remove") {
21876
22051
  const ws = await loadWorkspace(workspacePath);
21877
- const resolvedTarget = path50.resolve(args.target);
22052
+ const resolvedTarget = path51.resolve(args.target);
21878
22053
  const match = ws.projects.find(
21879
22054
  (p) => p.id === args.target || resolveProject(dir, p).manifestPath === resolvedTarget
21880
22055
  );
@@ -21885,7 +22060,7 @@ async function runWorkspace(args) {
21885
22060
  version: 1,
21886
22061
  projects: ws.projects.filter((p) => p !== match)
21887
22062
  });
21888
- log(` removed "${match.id}" from ${path50.relative(process.cwd(), workspacePath)}`);
22063
+ log(` removed "${match.id}" from ${path51.relative(process.cwd(), workspacePath)}`);
21889
22064
  return;
21890
22065
  }
21891
22066
  if (args.subcommand === "list") {
@@ -21897,9 +22072,9 @@ async function runWorkspace(args) {
21897
22072
  if (args.json) {
21898
22073
  log(JSON.stringify({ version: 1, workspace: workspacePath, projects: ws.projects, diagnostics }, null, 2));
21899
22074
  } else if (!ws.projects.length) {
21900
- log(` no projects registered in ${path50.relative(process.cwd(), workspacePath)}`);
22075
+ log(` no projects registered in ${path51.relative(process.cwd(), workspacePath)}`);
21901
22076
  } else {
21902
- log(` ${ws.projects.length} project(s) in ${path50.relative(process.cwd(), workspacePath)}:`);
22077
+ log(` ${ws.projects.length} project(s) in ${path51.relative(process.cwd(), workspacePath)}:`);
21903
22078
  for (const p of ws.projects) {
21904
22079
  log(` ${p.id.padEnd(24)} ${p.manifest.padEnd(40)} (${p.provider}${p.account ? `, ${p.account}` : ""})`);
21905
22080
  }
@@ -21917,7 +22092,7 @@ async function runWorkspace(args) {
21917
22092
  if (args.json) {
21918
22093
  log(JSON.stringify({ ...report, workspace: workspacePath }, null, 2));
21919
22094
  } else {
21920
- log(` workspace: ${path50.relative(process.cwd(), workspacePath)}`);
22095
+ log(` workspace: ${path51.relative(process.cwd(), workspacePath)}`);
21921
22096
  for (const p of report.projects) {
21922
22097
  if (p.error) {
21923
22098
  log(`