pixelkiln 0.35.0 → 0.36.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -474,6 +474,72 @@ function lockKey(styleId, assetId) {
474
474
  return `${styleId}/${assetId}`;
475
475
  }
476
476
 
477
+ // src/errors.ts
478
+ var EXIT_CODES = {
479
+ usage: 2,
480
+ project: 3,
481
+ provider: 4,
482
+ "refused-overwrite": 5,
483
+ budget: 6,
484
+ capability: 7
485
+ };
486
+ var PixelKilnError = class extends Error {
487
+ code;
488
+ hint;
489
+ constructor(message6, code, opts = {}) {
490
+ super(message6, opts.cause === void 0 ? void 0 : { cause: opts.cause });
491
+ this.name = "PixelKilnError";
492
+ this.code = code;
493
+ if (opts.hint) this.hint = opts.hint;
494
+ }
495
+ };
496
+ var UsageError = class extends PixelKilnError {
497
+ constructor(message6, opts) {
498
+ super(message6, "usage", opts);
499
+ this.name = "UsageError";
500
+ }
501
+ };
502
+ var ProjectError = class extends PixelKilnError {
503
+ constructor(message6, opts) {
504
+ super(message6, "project", opts);
505
+ this.name = "ProjectError";
506
+ }
507
+ };
508
+ var ProviderError = class extends PixelKilnError {
509
+ /** Registry id of the provider that failed. */
510
+ provider;
511
+ /** HTTP status when the failure was a response, otherwise undefined. */
512
+ status;
513
+ constructor(provider, message6, opts = {}) {
514
+ super(message6, "provider", opts);
515
+ this.name = "ProviderError";
516
+ this.provider = provider;
517
+ if (opts.status !== void 0) this.status = opts.status;
518
+ }
519
+ };
520
+ var OverwriteRefusedError = class extends PixelKilnError {
521
+ /** The file(s) left untouched. */
522
+ files;
523
+ constructor(message6, files, opts) {
524
+ super(message6, "refused-overwrite", opts);
525
+ this.name = "OverwriteRefusedError";
526
+ this.files = files;
527
+ }
528
+ };
529
+ var BudgetError = class extends PixelKilnError {
530
+ constructor(message6, opts) {
531
+ super(message6, "budget", opts);
532
+ this.name = "BudgetError";
533
+ }
534
+ };
535
+ function errorCode(err) {
536
+ return err instanceof PixelKilnError ? err.code : void 0;
537
+ }
538
+ function exitCodeFor(err) {
539
+ const code = errorCode(err);
540
+ return code ? EXIT_CODES[code] : 1;
541
+ }
542
+
477
543
  // src/client.ts
478
544
  import { z as z2 } from "zod";
479
545
 
@@ -590,14 +656,12 @@ function validateResponse(schema, raw, operation) {
590
656
  const issues = parsed.error.issues.slice(0, 4).map((i) => `${i.path.join(".") || "response"}: ${i.message}`).join("; ");
591
657
  throw new Error(`Invalid PixelLab response for ${operation}: ${issues}`);
592
658
  }
593
- var PixelLabError = class extends Error {
659
+ var PixelLabError = class extends ProviderError {
594
660
  constructor(message6, status, body) {
595
- super(message6);
596
- this.status = status;
661
+ super("pixellab", message6, { status });
597
662
  this.body = body;
598
663
  this.name = "PixelLabError";
599
664
  }
600
- status;
601
665
  body;
602
666
  };
603
667
  var PixelLabClient = class {
@@ -877,10 +941,11 @@ function measureBalanceChange(before, after) {
877
941
  };
878
942
  }
879
943
  var DEFAULT_RATE_LIMIT = { spacingMs: 2500, maxInFlight: 8 };
880
- var UnsupportedCapabilityError = class extends Error {
944
+ var UnsupportedCapabilityError = class extends PixelKilnError {
881
945
  constructor(providerId, capability) {
882
946
  super(
883
- `Provider "${providerId}" does not support ${capability}. That command is unavailable with this backend.`
947
+ `Provider "${providerId}" does not support ${capability}. That command is unavailable with this backend.`,
948
+ "capability"
884
949
  );
885
950
  this.name = "UnsupportedCapabilityError";
886
951
  }
@@ -1705,7 +1770,7 @@ var RetroDiffusionClient = class {
1705
1770
  /** Result URLs are signed; no token header, and the same retry policy as the API. */
1706
1771
  async download(url) {
1707
1772
  const response = await this.request(url);
1708
- if (!response.ok) throw new Error(`Retro Diffusion download failed (${response.status})`);
1773
+ if (!response.ok) throw new ProviderError("retrodiffusion", `Retro Diffusion download failed (${response.status})`, { status: response.status });
1709
1774
  return Buffer.from(await response.arrayBuffer());
1710
1775
  }
1711
1776
  async call(path32, init = {}) {
@@ -1728,8 +1793,10 @@ var RetroDiffusionClient = class {
1728
1793
  if (!response.ok) {
1729
1794
  const retry = response.headers.get("retry-after");
1730
1795
  const detail = retroError(value);
1731
- throw new Error(
1732
- `Retro Diffusion request failed (${response.status}): ${detail}` + (retry ? `; retry after ${retry}s` : "")
1796
+ throw new ProviderError(
1797
+ "retrodiffusion",
1798
+ `Retro Diffusion request failed (${response.status}): ${detail}` + (retry ? `; retry after ${retry}s` : ""),
1799
+ { status: response.status }
1733
1800
  );
1734
1801
  }
1735
1802
  return value;
@@ -2121,7 +2188,7 @@ var ScenarioClient = class {
2121
2188
  throw new Error("Scenario downloads must use HTTP or HTTPS");
2122
2189
  }
2123
2190
  const response = await this.request(parsed);
2124
- if (!response.ok) throw new Error(`Scenario download failed (${response.status})`);
2191
+ if (!response.ok) throw new ProviderError("scenario", `Scenario download failed (${response.status})`, { status: response.status });
2125
2192
  const declared = Number(response.headers.get("content-length"));
2126
2193
  if (Number.isFinite(declared) && declared > MAX_DOWNLOAD_BYTES) {
2127
2194
  throw new Error(`Scenario download exceeds the ${MAX_DOWNLOAD_BYTES}-byte safety limit`);
@@ -2168,8 +2235,10 @@ var ScenarioClient = class {
2168
2235
  if (!response.ok) {
2169
2236
  const retry = response.headers.get("retry-after");
2170
2237
  const detail = redactScenarioError(value, [this.apiKey, this.apiSecret, auth]);
2171
- throw new Error(
2172
- `Scenario request failed (${response.status}): ${detail}` + (retry ? `; retry after ${retry}s` : "")
2238
+ throw new ProviderError(
2239
+ "scenario",
2240
+ `Scenario request failed (${response.status}): ${detail}` + (retry ? `; retry after ${retry}s` : ""),
2241
+ { status: response.status }
2173
2242
  );
2174
2243
  }
2175
2244
  return value;
@@ -2619,8 +2688,10 @@ var ComfyUIClient = class {
2619
2688
  value = null;
2620
2689
  }
2621
2690
  if (!response.ok) {
2622
- throw new Error(
2623
- `ComfyUI image upload failed (${response.status})` + (apiError(value) ? `: ${apiError(value)}` : "")
2691
+ throw new ProviderError(
2692
+ "comfyui",
2693
+ `ComfyUI image upload failed (${response.status})` + (apiError(value) ? `: ${apiError(value)}` : ""),
2694
+ { status: response.status }
2624
2695
  );
2625
2696
  }
2626
2697
  if (!isObject(value) || typeof value.name !== "string" || !value.name) {
@@ -2643,7 +2714,7 @@ var ComfyUIClient = class {
2643
2714
  let target = source;
2644
2715
  if (source.startsWith("comfyui://")) target = this.viewUrl(parseSource(source));
2645
2716
  const response = await this.request(target);
2646
- if (!response.ok) throw new Error(`ComfyUI download failed (${response.status})`);
2717
+ if (!response.ok) throw new ProviderError("comfyui", `ComfyUI download failed (${response.status})`, { status: response.status });
2647
2718
  return Buffer.from(await response.arrayBuffer());
2648
2719
  }
2649
2720
  endpoint(route) {
@@ -2668,8 +2739,10 @@ var ComfyUIClient = class {
2668
2739
  }
2669
2740
  if (!response.ok) {
2670
2741
  const detail = apiError(value);
2671
- throw new Error(
2672
- `ComfyUI request failed (${response.status}) at ${url.pathname}` + (detail ? `: ${detail}` : "")
2742
+ throw new ProviderError(
2743
+ "comfyui",
2744
+ `ComfyUI request failed (${response.status}) at ${url.pathname}` + (detail ? `: ${detail}` : ""),
2745
+ { status: response.status }
2673
2746
  );
2674
2747
  }
2675
2748
  return value;
@@ -3659,9 +3732,10 @@ async function loadLock(lockPath) {
3659
3732
  try {
3660
3733
  return parseLock(JSON.parse(await readFile2(lockPath, "utf8")));
3661
3734
  } catch (err) {
3662
- throw new Error(
3735
+ throw new ProjectError(
3663
3736
  `Lockfile at ${lockPath} is malformed:
3664
- ${err instanceof Error ? err.message : String(err)}`
3737
+ ${err instanceof Error ? err.message : String(err)}`,
3738
+ { cause: err }
3665
3739
  );
3666
3740
  }
3667
3741
  }
@@ -3695,8 +3769,9 @@ async function writeLockWhileHeld(file, lock) {
3695
3769
  try {
3696
3770
  disk = parseLock(JSON.parse(await readFile2(file, "utf8")));
3697
3771
  } catch (err) {
3698
- throw new Error(
3699
- `Refusing to overwrite malformed lockfile at ${file}: ${err instanceof Error ? err.message : String(err)}`
3772
+ throw new ProjectError(
3773
+ `Refusing to overwrite malformed lockfile at ${file}: ${err instanceof Error ? err.message : String(err)}`,
3774
+ { cause: err }
3700
3775
  );
3701
3776
  }
3702
3777
  }
@@ -3952,18 +4027,18 @@ function resolveSpecOutputs(spec, lock, manifestDir) {
3952
4027
  // src/manifest.ts
3953
4028
  async function loadManifest(manifestPath) {
3954
4029
  const abs = path5.resolve(manifestPath);
3955
- if (!existsSync4(abs)) throw new Error(`No manifest at ${abs}`);
4030
+ if (!existsSync4(abs)) throw new ProjectError(`No manifest at ${abs}`);
3956
4031
  const input = ManifestInputSchema.safeParse(JSON.parse(await readFile3(abs, "utf8")));
3957
4032
  if (!input.success) {
3958
4033
  const issues = formatManifestIssues(input.error.issues);
3959
- throw new Error(`Manifest at ${abs} is invalid:
4034
+ throw new ProjectError(`Manifest at ${abs} is invalid:
3960
4035
  ${issues}`);
3961
4036
  }
3962
4037
  let styles;
3963
4038
  try {
3964
4039
  styles = resolveStyleInheritance(input.data.styles);
3965
4040
  } catch (error) {
3966
- throw new Error(
4041
+ throw new ProjectError(
3967
4042
  `Manifest at ${abs} is invalid:
3968
4043
  ${error instanceof Error ? error.message : String(error)}`
3969
4044
  );
@@ -3971,7 +4046,7 @@ ${issues}`);
3971
4046
  const parsed = ManifestSchema.safeParse({ ...input.data, styles });
3972
4047
  if (!parsed.success) {
3973
4048
  const issues = formatManifestIssues(parsed.error.issues);
3974
- throw new Error(`Manifest at ${abs} is invalid:
4049
+ throw new ProjectError(`Manifest at ${abs} is invalid:
3975
4050
  ${issues}`);
3976
4051
  }
3977
4052
  const styleIds = new Set(Object.keys(parsed.data.styles));
@@ -4021,7 +4096,7 @@ ${issues}`);
4021
4096
  };
4022
4097
  for (const assetId of Object.keys(parsed.data.assets)) visitRevision(assetId, []);
4023
4098
  if (unknownReferences.length) {
4024
- throw new Error(`Manifest at ${abs} is invalid:
4099
+ throw new ProjectError(`Manifest at ${abs} is invalid:
4025
4100
  ${unknownReferences.map((i) => ` ${i}`).join("\n")}`);
4026
4101
  }
4027
4102
  return { manifest: parsed.data, root: path5.dirname(abs), path: abs };
@@ -4100,14 +4175,14 @@ async function resolveSpecs(loaded, filter) {
4100
4175
  );
4101
4176
  for (const unknownStyle of filter?.styles ?? []) {
4102
4177
  if (!manifest.styles[unknownStyle]) {
4103
- throw new Error(
4178
+ throw new UsageError(
4104
4179
  `Unknown style "${unknownStyle}". Defined: ${Object.keys(manifest.styles).join(", ") || "(none)"}`
4105
4180
  );
4106
4181
  }
4107
4182
  }
4108
4183
  for (const unknownAsset of filter?.assets ?? []) {
4109
4184
  if (!manifest.assets[unknownAsset]) {
4110
- throw new Error(`Unknown asset "${unknownAsset}".`);
4185
+ throw new UsageError(`Unknown asset "${unknownAsset}".`);
4111
4186
  }
4112
4187
  }
4113
4188
  const requestedAssetIds = new Set(filter?.assets?.length ? filter.assets : Object.keys(manifest.assets));
@@ -4718,10 +4793,11 @@ async function writeManagedArtifactBundle(manifestPath, outputs, provenance, opt
4718
4793
  if (!expected || digest(current) !== expected) conflicts.push(destination);
4719
4794
  }
4720
4795
  if (conflicts.length) {
4721
- throw new Error(
4796
+ throw new OverwriteRefusedError(
4722
4797
  `Refusing to overwrite modified or unowned artifact file(s):
4723
4798
  ` + conflicts.map((file) => ` ${file}`).join("\n") + `
4724
- Pass { force: true } (CLI: --force) to take ownership and replace the complete bundle.`
4799
+ Pass { force: true } (CLI: --force) to take ownership and replace the complete bundle.`,
4800
+ conflicts
4725
4801
  );
4726
4802
  }
4727
4803
  }
@@ -6519,8 +6595,9 @@ async function installRecipe(target, options = {}) {
6519
6595
  if (!existsSync10(file.path)) continue;
6520
6596
  const current = await readFile7(file.path);
6521
6597
  if (!current.equals(file.data)) {
6522
- throw new Error(
6523
- `Recipe destination has local changes: ${file.path}. Choose another --out or pass --force to replace declared recipe files.`
6598
+ throw new OverwriteRefusedError(
6599
+ `Recipe destination has local changes: ${file.path}. Choose another --out or pass --force to replace declared recipe files.`,
6600
+ [file.path]
6524
6601
  );
6525
6602
  }
6526
6603
  }
@@ -6787,7 +6864,10 @@ async function snapshotQualityBaseline(inputs, baselinePath, options = {}) {
6787
6864
  if (existsSync11(absolute) && !options.force) {
6788
6865
  const current = await readFile8(absolute);
6789
6866
  if (!current.equals(data)) {
6790
- throw new Error(`Quality baseline already exists with different content: ${absolute}. Pass --force to replace it.`);
6867
+ throw new OverwriteRefusedError(
6868
+ `Quality baseline already exists with different content: ${absolute}. Pass --force to replace it.`,
6869
+ [absolute]
6870
+ );
6791
6871
  }
6792
6872
  }
6793
6873
  const result = await writeArtifactBundle([{ path: absolute, data }]);
@@ -7312,8 +7392,9 @@ async function revertGeneration(provider, spec, lock, lockPath, opts) {
7312
7392
  const file = currentEntryOutputPath(entry, spec, i);
7313
7393
  if (!existsSync14(file)) continue;
7314
7394
  if (await sha256File(file) !== entry.outputs[i].sha256 && !opts.force) {
7315
- throw new Error(
7316
- `${path17.relative(process.cwd(), file)} was changed after download; keep it with pixelkiln edit, or pass --force to replace it`
7395
+ throw new OverwriteRefusedError(
7396
+ `${path17.relative(process.cwd(), file)} was changed after download; keep it with pixelkiln edit, or pass --force to replace it`,
7397
+ [file]
7317
7398
  );
7318
7399
  }
7319
7400
  }
@@ -8063,7 +8144,7 @@ async function submit(provider, loaded, items, lock, lockPath, opts = {}) {
8063
8144
  if (opts.budget != null) {
8064
8145
  const cost = [...estimates.values()].reduce((sum, estimate) => sum + estimate.amount, 0);
8065
8146
  if (cost > opts.budget) {
8066
- throw new Error(
8147
+ throw new BudgetError(
8067
8148
  `This run would spend ${formatCost(unit, cost)} but the budget is ${formatCost(unit, opts.budget)}. Narrow it with --only/--style, or raise --budget.`
8068
8149
  );
8069
8150
  }
@@ -9390,7 +9471,7 @@ var WorkspaceSchema = z6.object({
9390
9471
  function parseWorkspace(raw) {
9391
9472
  const parsed = WorkspaceSchema.safeParse(raw);
9392
9473
  if (parsed.success) return parsed.data;
9393
- throw new Error(
9474
+ throw new ProjectError(
9394
9475
  `Workspace catalog is not valid v1:
9395
9476
  ${parsed.error.issues.slice(0, 5).map((i) => ` ${i.path.join(".")}: ${i.message}`).join("\n")}`
9396
9477
  );
@@ -9401,7 +9482,7 @@ async function loadWorkspace(workspacePath) {
9401
9482
  try {
9402
9483
  raw = JSON.parse(await readFile16(workspacePath, "utf8"));
9403
9484
  } catch (err) {
9404
- throw new Error(
9485
+ throw new ProjectError(
9405
9486
  `Workspace catalog at ${workspacePath} is malformed:
9406
9487
  ${err instanceof Error ? err.message : String(err)}`
9407
9488
  );
@@ -12193,6 +12274,7 @@ async function workspaceStatus(ws, dir) {
12193
12274
  }
12194
12275
  export {
12195
12276
  AssetSchema,
12277
+ BudgetError,
12196
12278
  ComfyUIProvider,
12197
12279
  DEFAULT_HISTORY_LIMIT,
12198
12280
  DEFAULT_QUALITY_TOLERANCES,
@@ -12200,6 +12282,7 @@ export {
12200
12282
  DEFAULT_TIMEOUT_MS,
12201
12283
  EDITOR_PIN,
12202
12284
  EDITOR_RELEASE_BASE,
12285
+ EXIT_CODES,
12203
12286
  EditorInstallError,
12204
12287
  FAKE_PNG,
12205
12288
  FakeProvider,
@@ -12224,11 +12307,15 @@ export {
12224
12307
  ManifestInputSchema,
12225
12308
  ManifestSchema,
12226
12309
  MediaType,
12310
+ OverwriteRefusedError,
12227
12311
  PIXEL_ART_FIXER_REVISION,
12228
12312
  PIXEL_ART_FIXER_URL,
12313
+ PixelKilnError,
12229
12314
  PixelLabClient,
12230
12315
  PixelLabError,
12231
12316
  PixelLabProvider,
12317
+ ProjectError,
12318
+ ProviderError,
12232
12319
  QualityBaselineSchema,
12233
12320
  QualityProfileSchema,
12234
12321
  QualityToleranceSchema,
@@ -12240,6 +12327,7 @@ export {
12240
12327
  StyleInputSchema,
12241
12328
  StyleSchema,
12242
12329
  UnsupportedCapabilityError,
12330
+ UsageError,
12243
12331
  WorkspaceProjectSchema,
12244
12332
  WorkspaceSchema,
12245
12333
  adopt,
@@ -12277,7 +12365,9 @@ export {
12277
12365
  editorRoute,
12278
12366
  editorStatus,
12279
12367
  editorToolsRoot,
12368
+ errorCode,
12280
12369
  evaluateAudit,
12370
+ exitCodeFor,
12281
12371
  expectedOutputPath,
12282
12372
  exportTileset,
12283
12373
  fallbackOutputRole,