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.cjs CHANGED
@@ -31,6 +31,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
33
  AssetSchema: () => AssetSchema,
34
+ BudgetError: () => BudgetError,
34
35
  ComfyUIProvider: () => ComfyUIProvider,
35
36
  DEFAULT_HISTORY_LIMIT: () => DEFAULT_HISTORY_LIMIT,
36
37
  DEFAULT_QUALITY_TOLERANCES: () => DEFAULT_QUALITY_TOLERANCES,
@@ -38,6 +39,7 @@ __export(index_exports, {
38
39
  DEFAULT_TIMEOUT_MS: () => DEFAULT_TIMEOUT_MS,
39
40
  EDITOR_PIN: () => EDITOR_PIN,
40
41
  EDITOR_RELEASE_BASE: () => EDITOR_RELEASE_BASE,
42
+ EXIT_CODES: () => EXIT_CODES,
41
43
  EditorInstallError: () => EditorInstallError,
42
44
  FAKE_PNG: () => FAKE_PNG,
43
45
  FakeProvider: () => FakeProvider,
@@ -62,11 +64,15 @@ __export(index_exports, {
62
64
  ManifestInputSchema: () => ManifestInputSchema,
63
65
  ManifestSchema: () => ManifestSchema,
64
66
  MediaType: () => MediaType,
67
+ OverwriteRefusedError: () => OverwriteRefusedError,
65
68
  PIXEL_ART_FIXER_REVISION: () => PIXEL_ART_FIXER_REVISION,
66
69
  PIXEL_ART_FIXER_URL: () => PIXEL_ART_FIXER_URL,
70
+ PixelKilnError: () => PixelKilnError,
67
71
  PixelLabClient: () => PixelLabClient,
68
72
  PixelLabError: () => PixelLabError,
69
73
  PixelLabProvider: () => PixelLabProvider,
74
+ ProjectError: () => ProjectError,
75
+ ProviderError: () => ProviderError,
70
76
  QualityBaselineSchema: () => QualityBaselineSchema,
71
77
  QualityProfileSchema: () => QualityProfileSchema,
72
78
  QualityToleranceSchema: () => QualityToleranceSchema,
@@ -78,6 +84,7 @@ __export(index_exports, {
78
84
  StyleInputSchema: () => StyleInputSchema,
79
85
  StyleSchema: () => StyleSchema,
80
86
  UnsupportedCapabilityError: () => UnsupportedCapabilityError,
87
+ UsageError: () => UsageError,
81
88
  WorkspaceProjectSchema: () => WorkspaceProjectSchema,
82
89
  WorkspaceSchema: () => WorkspaceSchema,
83
90
  adopt: () => adopt,
@@ -115,7 +122,9 @@ __export(index_exports, {
115
122
  editorRoute: () => editorRoute,
116
123
  editorStatus: () => editorStatus,
117
124
  editorToolsRoot: () => editorToolsRoot,
125
+ errorCode: () => errorCode,
118
126
  evaluateAudit: () => evaluateAudit,
127
+ exitCodeFor: () => exitCodeFor,
119
128
  expectedOutputPath: () => expectedOutputPath,
120
129
  exportTileset: () => exportTileset,
121
130
  fallbackOutputRole: () => fallbackOutputRole,
@@ -745,6 +754,72 @@ function lockKey(styleId, assetId) {
745
754
  return `${styleId}/${assetId}`;
746
755
  }
747
756
 
757
+ // src/errors.ts
758
+ var EXIT_CODES = {
759
+ usage: 2,
760
+ project: 3,
761
+ provider: 4,
762
+ "refused-overwrite": 5,
763
+ budget: 6,
764
+ capability: 7
765
+ };
766
+ var PixelKilnError = class extends Error {
767
+ code;
768
+ hint;
769
+ constructor(message6, code, opts = {}) {
770
+ super(message6, opts.cause === void 0 ? void 0 : { cause: opts.cause });
771
+ this.name = "PixelKilnError";
772
+ this.code = code;
773
+ if (opts.hint) this.hint = opts.hint;
774
+ }
775
+ };
776
+ var UsageError = class extends PixelKilnError {
777
+ constructor(message6, opts) {
778
+ super(message6, "usage", opts);
779
+ this.name = "UsageError";
780
+ }
781
+ };
782
+ var ProjectError = class extends PixelKilnError {
783
+ constructor(message6, opts) {
784
+ super(message6, "project", opts);
785
+ this.name = "ProjectError";
786
+ }
787
+ };
788
+ var ProviderError = class extends PixelKilnError {
789
+ /** Registry id of the provider that failed. */
790
+ provider;
791
+ /** HTTP status when the failure was a response, otherwise undefined. */
792
+ status;
793
+ constructor(provider, message6, opts = {}) {
794
+ super(message6, "provider", opts);
795
+ this.name = "ProviderError";
796
+ this.provider = provider;
797
+ if (opts.status !== void 0) this.status = opts.status;
798
+ }
799
+ };
800
+ var OverwriteRefusedError = class extends PixelKilnError {
801
+ /** The file(s) left untouched. */
802
+ files;
803
+ constructor(message6, files, opts) {
804
+ super(message6, "refused-overwrite", opts);
805
+ this.name = "OverwriteRefusedError";
806
+ this.files = files;
807
+ }
808
+ };
809
+ var BudgetError = class extends PixelKilnError {
810
+ constructor(message6, opts) {
811
+ super(message6, "budget", opts);
812
+ this.name = "BudgetError";
813
+ }
814
+ };
815
+ function errorCode(err) {
816
+ return err instanceof PixelKilnError ? err.code : void 0;
817
+ }
818
+ function exitCodeFor(err) {
819
+ const code = errorCode(err);
820
+ return code ? EXIT_CODES[code] : 1;
821
+ }
822
+
748
823
  // src/client.ts
749
824
  var import_zod2 = require("zod");
750
825
 
@@ -861,14 +936,12 @@ function validateResponse(schema, raw, operation) {
861
936
  const issues = parsed.error.issues.slice(0, 4).map((i) => `${i.path.join(".") || "response"}: ${i.message}`).join("; ");
862
937
  throw new Error(`Invalid PixelLab response for ${operation}: ${issues}`);
863
938
  }
864
- var PixelLabError = class extends Error {
939
+ var PixelLabError = class extends ProviderError {
865
940
  constructor(message6, status, body) {
866
- super(message6);
867
- this.status = status;
941
+ super("pixellab", message6, { status });
868
942
  this.body = body;
869
943
  this.name = "PixelLabError";
870
944
  }
871
- status;
872
945
  body;
873
946
  };
874
947
  var PixelLabClient = class {
@@ -1148,10 +1221,11 @@ function measureBalanceChange(before, after) {
1148
1221
  };
1149
1222
  }
1150
1223
  var DEFAULT_RATE_LIMIT = { spacingMs: 2500, maxInFlight: 8 };
1151
- var UnsupportedCapabilityError = class extends Error {
1224
+ var UnsupportedCapabilityError = class extends PixelKilnError {
1152
1225
  constructor(providerId, capability) {
1153
1226
  super(
1154
- `Provider "${providerId}" does not support ${capability}. That command is unavailable with this backend.`
1227
+ `Provider "${providerId}" does not support ${capability}. That command is unavailable with this backend.`,
1228
+ "capability"
1155
1229
  );
1156
1230
  this.name = "UnsupportedCapabilityError";
1157
1231
  }
@@ -1976,7 +2050,7 @@ var RetroDiffusionClient = class {
1976
2050
  /** Result URLs are signed; no token header, and the same retry policy as the API. */
1977
2051
  async download(url) {
1978
2052
  const response = await this.request(url);
1979
- if (!response.ok) throw new Error(`Retro Diffusion download failed (${response.status})`);
2053
+ if (!response.ok) throw new ProviderError("retrodiffusion", `Retro Diffusion download failed (${response.status})`, { status: response.status });
1980
2054
  return Buffer.from(await response.arrayBuffer());
1981
2055
  }
1982
2056
  async call(path32, init = {}) {
@@ -1999,8 +2073,10 @@ var RetroDiffusionClient = class {
1999
2073
  if (!response.ok) {
2000
2074
  const retry = response.headers.get("retry-after");
2001
2075
  const detail = retroError(value);
2002
- throw new Error(
2003
- `Retro Diffusion request failed (${response.status}): ${detail}` + (retry ? `; retry after ${retry}s` : "")
2076
+ throw new ProviderError(
2077
+ "retrodiffusion",
2078
+ `Retro Diffusion request failed (${response.status}): ${detail}` + (retry ? `; retry after ${retry}s` : ""),
2079
+ { status: response.status }
2004
2080
  );
2005
2081
  }
2006
2082
  return value;
@@ -2392,7 +2468,7 @@ var ScenarioClient = class {
2392
2468
  throw new Error("Scenario downloads must use HTTP or HTTPS");
2393
2469
  }
2394
2470
  const response = await this.request(parsed);
2395
- if (!response.ok) throw new Error(`Scenario download failed (${response.status})`);
2471
+ if (!response.ok) throw new ProviderError("scenario", `Scenario download failed (${response.status})`, { status: response.status });
2396
2472
  const declared = Number(response.headers.get("content-length"));
2397
2473
  if (Number.isFinite(declared) && declared > MAX_DOWNLOAD_BYTES) {
2398
2474
  throw new Error(`Scenario download exceeds the ${MAX_DOWNLOAD_BYTES}-byte safety limit`);
@@ -2439,8 +2515,10 @@ var ScenarioClient = class {
2439
2515
  if (!response.ok) {
2440
2516
  const retry = response.headers.get("retry-after");
2441
2517
  const detail = redactScenarioError(value, [this.apiKey, this.apiSecret, auth]);
2442
- throw new Error(
2443
- `Scenario request failed (${response.status}): ${detail}` + (retry ? `; retry after ${retry}s` : "")
2518
+ throw new ProviderError(
2519
+ "scenario",
2520
+ `Scenario request failed (${response.status}): ${detail}` + (retry ? `; retry after ${retry}s` : ""),
2521
+ { status: response.status }
2444
2522
  );
2445
2523
  }
2446
2524
  return value;
@@ -2890,8 +2968,10 @@ var ComfyUIClient = class {
2890
2968
  value = null;
2891
2969
  }
2892
2970
  if (!response.ok) {
2893
- throw new Error(
2894
- `ComfyUI image upload failed (${response.status})` + (apiError(value) ? `: ${apiError(value)}` : "")
2971
+ throw new ProviderError(
2972
+ "comfyui",
2973
+ `ComfyUI image upload failed (${response.status})` + (apiError(value) ? `: ${apiError(value)}` : ""),
2974
+ { status: response.status }
2895
2975
  );
2896
2976
  }
2897
2977
  if (!isObject(value) || typeof value.name !== "string" || !value.name) {
@@ -2914,7 +2994,7 @@ var ComfyUIClient = class {
2914
2994
  let target = source;
2915
2995
  if (source.startsWith("comfyui://")) target = this.viewUrl(parseSource(source));
2916
2996
  const response = await this.request(target);
2917
- if (!response.ok) throw new Error(`ComfyUI download failed (${response.status})`);
2997
+ if (!response.ok) throw new ProviderError("comfyui", `ComfyUI download failed (${response.status})`, { status: response.status });
2918
2998
  return Buffer.from(await response.arrayBuffer());
2919
2999
  }
2920
3000
  endpoint(route) {
@@ -2939,8 +3019,10 @@ var ComfyUIClient = class {
2939
3019
  }
2940
3020
  if (!response.ok) {
2941
3021
  const detail = apiError(value);
2942
- throw new Error(
2943
- `ComfyUI request failed (${response.status}) at ${url.pathname}` + (detail ? `: ${detail}` : "")
3022
+ throw new ProviderError(
3023
+ "comfyui",
3024
+ `ComfyUI request failed (${response.status}) at ${url.pathname}` + (detail ? `: ${detail}` : ""),
3025
+ { status: response.status }
2944
3026
  );
2945
3027
  }
2946
3028
  return value;
@@ -3930,9 +4012,10 @@ async function loadLock(lockPath) {
3930
4012
  try {
3931
4013
  return parseLock(JSON.parse(await (0, import_promises2.readFile)(lockPath, "utf8")));
3932
4014
  } catch (err) {
3933
- throw new Error(
4015
+ throw new ProjectError(
3934
4016
  `Lockfile at ${lockPath} is malformed:
3935
- ${err instanceof Error ? err.message : String(err)}`
4017
+ ${err instanceof Error ? err.message : String(err)}`,
4018
+ { cause: err }
3936
4019
  );
3937
4020
  }
3938
4021
  }
@@ -3966,8 +4049,9 @@ async function writeLockWhileHeld(file, lock) {
3966
4049
  try {
3967
4050
  disk = parseLock(JSON.parse(await (0, import_promises2.readFile)(file, "utf8")));
3968
4051
  } catch (err) {
3969
- throw new Error(
3970
- `Refusing to overwrite malformed lockfile at ${file}: ${err instanceof Error ? err.message : String(err)}`
4052
+ throw new ProjectError(
4053
+ `Refusing to overwrite malformed lockfile at ${file}: ${err instanceof Error ? err.message : String(err)}`,
4054
+ { cause: err }
3971
4055
  );
3972
4056
  }
3973
4057
  }
@@ -4223,18 +4307,18 @@ function resolveSpecOutputs(spec, lock, manifestDir) {
4223
4307
  // src/manifest.ts
4224
4308
  async function loadManifest(manifestPath) {
4225
4309
  const abs = import_node_path5.default.resolve(manifestPath);
4226
- if (!(0, import_node_fs5.existsSync)(abs)) throw new Error(`No manifest at ${abs}`);
4310
+ if (!(0, import_node_fs5.existsSync)(abs)) throw new ProjectError(`No manifest at ${abs}`);
4227
4311
  const input = ManifestInputSchema.safeParse(JSON.parse(await (0, import_promises3.readFile)(abs, "utf8")));
4228
4312
  if (!input.success) {
4229
4313
  const issues = formatManifestIssues(input.error.issues);
4230
- throw new Error(`Manifest at ${abs} is invalid:
4314
+ throw new ProjectError(`Manifest at ${abs} is invalid:
4231
4315
  ${issues}`);
4232
4316
  }
4233
4317
  let styles;
4234
4318
  try {
4235
4319
  styles = resolveStyleInheritance(input.data.styles);
4236
4320
  } catch (error) {
4237
- throw new Error(
4321
+ throw new ProjectError(
4238
4322
  `Manifest at ${abs} is invalid:
4239
4323
  ${error instanceof Error ? error.message : String(error)}`
4240
4324
  );
@@ -4242,7 +4326,7 @@ ${issues}`);
4242
4326
  const parsed = ManifestSchema.safeParse({ ...input.data, styles });
4243
4327
  if (!parsed.success) {
4244
4328
  const issues = formatManifestIssues(parsed.error.issues);
4245
- throw new Error(`Manifest at ${abs} is invalid:
4329
+ throw new ProjectError(`Manifest at ${abs} is invalid:
4246
4330
  ${issues}`);
4247
4331
  }
4248
4332
  const styleIds = new Set(Object.keys(parsed.data.styles));
@@ -4292,7 +4376,7 @@ ${issues}`);
4292
4376
  };
4293
4377
  for (const assetId of Object.keys(parsed.data.assets)) visitRevision(assetId, []);
4294
4378
  if (unknownReferences.length) {
4295
- throw new Error(`Manifest at ${abs} is invalid:
4379
+ throw new ProjectError(`Manifest at ${abs} is invalid:
4296
4380
  ${unknownReferences.map((i) => ` ${i}`).join("\n")}`);
4297
4381
  }
4298
4382
  return { manifest: parsed.data, root: import_node_path5.default.dirname(abs), path: abs };
@@ -4371,14 +4455,14 @@ async function resolveSpecs(loaded, filter) {
4371
4455
  );
4372
4456
  for (const unknownStyle of filter?.styles ?? []) {
4373
4457
  if (!manifest.styles[unknownStyle]) {
4374
- throw new Error(
4458
+ throw new UsageError(
4375
4459
  `Unknown style "${unknownStyle}". Defined: ${Object.keys(manifest.styles).join(", ") || "(none)"}`
4376
4460
  );
4377
4461
  }
4378
4462
  }
4379
4463
  for (const unknownAsset of filter?.assets ?? []) {
4380
4464
  if (!manifest.assets[unknownAsset]) {
4381
- throw new Error(`Unknown asset "${unknownAsset}".`);
4465
+ throw new UsageError(`Unknown asset "${unknownAsset}".`);
4382
4466
  }
4383
4467
  }
4384
4468
  const requestedAssetIds = new Set(filter?.assets?.length ? filter.assets : Object.keys(manifest.assets));
@@ -4989,10 +5073,11 @@ async function writeManagedArtifactBundle(manifestPath, outputs, provenance, opt
4989
5073
  if (!expected || digest(current) !== expected) conflicts.push(destination);
4990
5074
  }
4991
5075
  if (conflicts.length) {
4992
- throw new Error(
5076
+ throw new OverwriteRefusedError(
4993
5077
  `Refusing to overwrite modified or unowned artifact file(s):
4994
5078
  ` + conflicts.map((file) => ` ${file}`).join("\n") + `
4995
- Pass { force: true } (CLI: --force) to take ownership and replace the complete bundle.`
5079
+ Pass { force: true } (CLI: --force) to take ownership and replace the complete bundle.`,
5080
+ conflicts
4996
5081
  );
4997
5082
  }
4998
5083
  }
@@ -6790,8 +6875,9 @@ async function installRecipe(target, options = {}) {
6790
6875
  if (!(0, import_node_fs11.existsSync)(file.path)) continue;
6791
6876
  const current = await (0, import_promises7.readFile)(file.path);
6792
6877
  if (!current.equals(file.data)) {
6793
- throw new Error(
6794
- `Recipe destination has local changes: ${file.path}. Choose another --out or pass --force to replace declared recipe files.`
6878
+ throw new OverwriteRefusedError(
6879
+ `Recipe destination has local changes: ${file.path}. Choose another --out or pass --force to replace declared recipe files.`,
6880
+ [file.path]
6795
6881
  );
6796
6882
  }
6797
6883
  }
@@ -7058,7 +7144,10 @@ async function snapshotQualityBaseline(inputs, baselinePath, options = {}) {
7058
7144
  if ((0, import_node_fs12.existsSync)(absolute) && !options.force) {
7059
7145
  const current = await (0, import_promises8.readFile)(absolute);
7060
7146
  if (!current.equals(data)) {
7061
- throw new Error(`Quality baseline already exists with different content: ${absolute}. Pass --force to replace it.`);
7147
+ throw new OverwriteRefusedError(
7148
+ `Quality baseline already exists with different content: ${absolute}. Pass --force to replace it.`,
7149
+ [absolute]
7150
+ );
7062
7151
  }
7063
7152
  }
7064
7153
  const result = await writeArtifactBundle([{ path: absolute, data }]);
@@ -7583,8 +7672,9 @@ async function revertGeneration(provider, spec, lock, lockPath, opts) {
7583
7672
  const file = currentEntryOutputPath(entry, spec, i);
7584
7673
  if (!(0, import_node_fs15.existsSync)(file)) continue;
7585
7674
  if (await sha256File(file) !== entry.outputs[i].sha256 && !opts.force) {
7586
- throw new Error(
7587
- `${import_node_path18.default.relative(process.cwd(), file)} was changed after download; keep it with pixelkiln edit, or pass --force to replace it`
7675
+ throw new OverwriteRefusedError(
7676
+ `${import_node_path18.default.relative(process.cwd(), file)} was changed after download; keep it with pixelkiln edit, or pass --force to replace it`,
7677
+ [file]
7588
7678
  );
7589
7679
  }
7590
7680
  }
@@ -8334,7 +8424,7 @@ async function submit(provider, loaded, items, lock, lockPath, opts = {}) {
8334
8424
  if (opts.budget != null) {
8335
8425
  const cost = [...estimates.values()].reduce((sum, estimate) => sum + estimate.amount, 0);
8336
8426
  if (cost > opts.budget) {
8337
- throw new Error(
8427
+ throw new BudgetError(
8338
8428
  `This run would spend ${formatCost(unit, cost)} but the budget is ${formatCost(unit, opts.budget)}. Narrow it with --only/--style, or raise --budget.`
8339
8429
  );
8340
8430
  }
@@ -9661,7 +9751,7 @@ var WorkspaceSchema = import_zod6.z.object({
9661
9751
  function parseWorkspace(raw) {
9662
9752
  const parsed = WorkspaceSchema.safeParse(raw);
9663
9753
  if (parsed.success) return parsed.data;
9664
- throw new Error(
9754
+ throw new ProjectError(
9665
9755
  `Workspace catalog is not valid v1:
9666
9756
  ${parsed.error.issues.slice(0, 5).map((i) => ` ${i.path.join(".")}: ${i.message}`).join("\n")}`
9667
9757
  );
@@ -9672,7 +9762,7 @@ async function loadWorkspace(workspacePath) {
9672
9762
  try {
9673
9763
  raw = JSON.parse(await (0, import_promises17.readFile)(workspacePath, "utf8"));
9674
9764
  } catch (err) {
9675
- throw new Error(
9765
+ throw new ProjectError(
9676
9766
  `Workspace catalog at ${workspacePath} is malformed:
9677
9767
  ${err instanceof Error ? err.message : String(err)}`
9678
9768
  );
@@ -12465,6 +12555,7 @@ async function workspaceStatus(ws, dir) {
12465
12555
  // Annotate the CommonJS export names for ESM import in node:
12466
12556
  0 && (module.exports = {
12467
12557
  AssetSchema,
12558
+ BudgetError,
12468
12559
  ComfyUIProvider,
12469
12560
  DEFAULT_HISTORY_LIMIT,
12470
12561
  DEFAULT_QUALITY_TOLERANCES,
@@ -12472,6 +12563,7 @@ async function workspaceStatus(ws, dir) {
12472
12563
  DEFAULT_TIMEOUT_MS,
12473
12564
  EDITOR_PIN,
12474
12565
  EDITOR_RELEASE_BASE,
12566
+ EXIT_CODES,
12475
12567
  EditorInstallError,
12476
12568
  FAKE_PNG,
12477
12569
  FakeProvider,
@@ -12496,11 +12588,15 @@ async function workspaceStatus(ws, dir) {
12496
12588
  ManifestInputSchema,
12497
12589
  ManifestSchema,
12498
12590
  MediaType,
12591
+ OverwriteRefusedError,
12499
12592
  PIXEL_ART_FIXER_REVISION,
12500
12593
  PIXEL_ART_FIXER_URL,
12594
+ PixelKilnError,
12501
12595
  PixelLabClient,
12502
12596
  PixelLabError,
12503
12597
  PixelLabProvider,
12598
+ ProjectError,
12599
+ ProviderError,
12504
12600
  QualityBaselineSchema,
12505
12601
  QualityProfileSchema,
12506
12602
  QualityToleranceSchema,
@@ -12512,6 +12608,7 @@ async function workspaceStatus(ws, dir) {
12512
12608
  StyleInputSchema,
12513
12609
  StyleSchema,
12514
12610
  UnsupportedCapabilityError,
12611
+ UsageError,
12515
12612
  WorkspaceProjectSchema,
12516
12613
  WorkspaceSchema,
12517
12614
  adopt,
@@ -12549,7 +12646,9 @@ async function workspaceStatus(ws, dir) {
12549
12646
  editorRoute,
12550
12647
  editorStatus,
12551
12648
  editorToolsRoot,
12649
+ errorCode,
12552
12650
  evaluateAudit,
12651
+ exitCodeFor,
12553
12652
  expectedOutputPath,
12554
12653
  exportTileset,
12555
12654
  fallbackOutputRole,