pixelkiln 0.89.2 → 0.89.4

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
  }
@@ -19443,8 +19457,18 @@ GROUPS.forEach((g, gi) => {
19443
19457
  preview.loading = 'lazy';
19444
19458
  const index = document.createElement('span');
19445
19459
  index.className = 'idx';
19446
- index.textContent = (g.frameLabels?.[i] || String(i + 1)) + ' \xB7 ' +
19447
- g.width + '\xD7' + g.height + ' \xB7 ' + scaleLabel;
19460
+ // A frame's own size, not the manifest's: a 96px loop of a 64px
19461
+ // character is 96\xD796. Known up front for a cached file, else once loaded.
19462
+ const label = (w, h) => {
19463
+ index.textContent = (g.frameLabels?.[i] || String(i + 1)) + ' \xB7 ' + w + '\xD7' + h + ' \xB7 ' + scaleLabel;
19464
+ };
19465
+ const known = g.frameSizes?.[i];
19466
+ label(known ? known.width : g.width, known ? known.height : g.height);
19467
+ if (!known) {
19468
+ preview.addEventListener('load', () => {
19469
+ if (preview.naturalWidth && preview.naturalHeight) label(preview.naturalWidth, preview.naturalHeight);
19470
+ }, { once: true });
19471
+ }
19448
19472
  c.append(preview, index);
19449
19473
  if (g.recommendedIndex != null && i === recommended) {
19450
19474
  const badge = document.createElement('span');
@@ -19625,12 +19649,15 @@ async function prepareReview(provider, lock, opts = {}) {
19625
19649
  const urls = state.status === "review" ? state.candidateUrls : state.frameUrls;
19626
19650
  const frameUrls = [];
19627
19651
  const frameAssets = [];
19652
+ const frameSizes = [];
19628
19653
  for (const [index, url] of urls.entries()) {
19629
19654
  const cached = await cachedFrame(url);
19630
19655
  if (!cached) {
19631
19656
  frameUrls.push(url);
19657
+ frameSizes.push(null);
19632
19658
  continue;
19633
19659
  }
19660
+ frameSizes.push(cached.contentType === "image/png" ? pngSize(await readFile25(cached.path).catch(() => Buffer.alloc(0))) : null);
19634
19661
  const route = `${prefix}/review-frame/${encodeURIComponent(String(groups.length))}/${index}`;
19635
19662
  frameAssets.push([route, cached]);
19636
19663
  frameUrls.push(route);
@@ -19656,6 +19683,7 @@ async function prepareReview(provider, lock, opts = {}) {
19656
19683
  prompt: entry.prompt,
19657
19684
  reviewObjectId: entry.reviewObjectId,
19658
19685
  frameUrls,
19686
+ ...frameSizes.some(Boolean) ? { frameSizes } : {},
19659
19687
  width: entry.width,
19660
19688
  height: entry.height,
19661
19689
  ...state.status === "review-set" ? {