pixelkiln 0.89.3 → 0.89.5

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/CONTRIBUTING.md CHANGED
@@ -42,6 +42,12 @@ npm run website:dev
42
42
  The site reads canonical Markdown from `docs/` and the root policy files at
43
43
  build time. Do not create a second documentation copy under `website/`.
44
44
 
45
+ The home page's "Latest updates from our providers" section reads
46
+ `website/app/lib/provider-updates.ts`. When a provider ships a feature
47
+ PixelKiln can use, add an entry at the top of that list. The newest entry
48
+ shows its figures and code sample; earlier ones collapse to a short row. Show
49
+ only output from a real call, with the bill the docs record.
50
+
45
51
  Website sprites are generated from `website/art/pixelkiln.manifest.json`.
46
52
  Treat those requests as paid work: run `plan` and `audit` first, use an explicit
47
53
  hard budget, and commit the manifest, lockfile, and reviewed outputs together.
package/dist/cli.js CHANGED
@@ -6704,7 +6704,8 @@ var PixelLabClient = class {
6704
6704
  });
6705
6705
  const text = await res.text();
6706
6706
  if (!res.ok) {
6707
- throw new PixelLabError(`${init?.method ?? "GET"} ${path53} \u2192 ${res.status}`, res.status, text);
6707
+ const hint = res.status === 500 && text.includes("Failed to start any animation jobs") ? '; a skeleton-v3 loop needs a character with a stored 3D skeleton (a v3 base), use mode: "template" on a standard one' : "";
6708
+ throw new PixelLabError(`${init?.method ?? "GET"} ${path53} \u2192 ${res.status}${hint}`, res.status, text);
6708
6709
  }
6709
6710
  if (!text) return {};
6710
6711
  try {
@@ -9598,6 +9599,11 @@ var PixelLabProvider = class _PixelLabProvider {
9598
9599
  if (character.animation.mode !== "template" && character.animation.mode !== "skeleton-v3" && !spec.prompt.trim()) {
9599
9600
  throw new Error(`${spec.styleId}/${spec.assetId} needs a prompt describing the motion, or a template`);
9600
9601
  }
9602
+ if (character.animation.mode === "skeleton-v3" && character.mode === "standard") {
9603
+ throw new Error(
9604
+ `${spec.styleId}/${spec.assetId}: skeleton-v3 poses a template onto a 3D skeleton, which a ${character.mode} character has none of; use a v3 base (style mode: "v3"), or mode: "template" for this loop`
9605
+ );
9606
+ }
9601
9607
  }
9602
9608
  }
9603
9609
  validateObjectPro(spec, styleImages = []) {
@@ -9904,7 +9910,10 @@ var PixelLabProvider = class _PixelLabProvider {
9904
9910
  stateName: character.state_name ?? null,
9905
9911
  directions: character.directions,
9906
9912
  size: character.size,
9907
- updatedAt: character.updated_at ?? null
9913
+ updatedAt: character.updated_at ?? null,
9914
+ // Kept: this metadata replaces the submit-time `character` entry,
9915
+ // and the job id is the only way to read the bill again later.
9916
+ ...backgroundJobId ? { backgroundJobId } : {}
9908
9917
  }
9909
9918
  },
9910
9919
  billed: await this.billedForJob(backgroundJobId)
@@ -10555,7 +10564,12 @@ var PixelLabProvider = class _PixelLabProvider {
10555
10564
  if (!backgroundJobId) return null;
10556
10565
  try {
10557
10566
  const job = await this.client.getBackgroundJob(backgroundJobId);
10558
- return billedFromUsage(job.usage);
10567
+ const nested = job.last_response?.billing_usage;
10568
+ for (const usage of [job.usage, nested]) {
10569
+ const billed = billedFromUsage(usage);
10570
+ if (billed && !(billed.unit === "usd" && billed.amount === 0)) return billed;
10571
+ }
10572
+ return null;
10559
10573
  } catch {
10560
10574
  return null;
10561
10575
  }
@@ -18024,6 +18038,7 @@ async function buildGallerySnapshot(opts) {
18024
18038
  view: style?.view ?? null,
18025
18039
  noBackground: style?.noBackground ?? true,
18026
18040
  quality: Boolean(style?.quality),
18041
+ characterMode: style?.generator === "character" ? style.mode ?? "standard" : null,
18027
18042
  characters: style?.generator === "character" || style?.generator === "objectPro" ? {
18028
18043
  bases: styleItems.filter((item) => item.character?.kind === "base").length,
18029
18044
  states: styleItems.filter((item) => item.character?.kind === "state").length,
@@ -19443,8 +19458,18 @@ GROUPS.forEach((g, gi) => {
19443
19458
  preview.loading = 'lazy';
19444
19459
  const index = document.createElement('span');
19445
19460
  index.className = 'idx';
19446
- index.textContent = (g.frameLabels?.[i] || String(i + 1)) + ' \xB7 ' +
19447
- g.width + '\xD7' + g.height + ' \xB7 ' + scaleLabel;
19461
+ // A frame's own size, not the manifest's: a 96px loop of a 64px
19462
+ // character is 96\xD796. Known up front for a cached file, else once loaded.
19463
+ const label = (w, h) => {
19464
+ index.textContent = (g.frameLabels?.[i] || String(i + 1)) + ' \xB7 ' + w + '\xD7' + h + ' \xB7 ' + scaleLabel;
19465
+ };
19466
+ const known = g.frameSizes?.[i];
19467
+ label(known ? known.width : g.width, known ? known.height : g.height);
19468
+ if (!known) {
19469
+ preview.addEventListener('load', () => {
19470
+ if (preview.naturalWidth && preview.naturalHeight) label(preview.naturalWidth, preview.naturalHeight);
19471
+ }, { once: true });
19472
+ }
19448
19473
  c.append(preview, index);
19449
19474
  if (g.recommendedIndex != null && i === recommended) {
19450
19475
  const badge = document.createElement('span');
@@ -19625,12 +19650,15 @@ async function prepareReview(provider, lock, opts = {}) {
19625
19650
  const urls = state.status === "review" ? state.candidateUrls : state.frameUrls;
19626
19651
  const frameUrls = [];
19627
19652
  const frameAssets = [];
19653
+ const frameSizes = [];
19628
19654
  for (const [index, url] of urls.entries()) {
19629
19655
  const cached = await cachedFrame(url);
19630
19656
  if (!cached) {
19631
19657
  frameUrls.push(url);
19658
+ frameSizes.push(null);
19632
19659
  continue;
19633
19660
  }
19661
+ frameSizes.push(cached.contentType === "image/png" ? pngSize(await readFile25(cached.path).catch(() => Buffer.alloc(0))) : null);
19634
19662
  const route = `${prefix}/review-frame/${encodeURIComponent(String(groups.length))}/${index}`;
19635
19663
  frameAssets.push([route, cached]);
19636
19664
  frameUrls.push(route);
@@ -19656,6 +19684,7 @@ async function prepareReview(provider, lock, opts = {}) {
19656
19684
  prompt: entry.prompt,
19657
19685
  reviewObjectId: entry.reviewObjectId,
19658
19686
  frameUrls,
19687
+ ...frameSizes.some(Boolean) ? { frameSizes } : {},
19659
19688
  width: entry.width,
19660
19689
  height: entry.height,
19661
19690
  ...state.status === "review-set" ? {