Pixelkiln featured image

Pixelkiln

Published on September 17, 2026

CLINodeTooling

Generate pixel art in bulk. Never lose track of what you paid for it.

Pixelkiln is a manifest-driven CLI and library for bulk-generating pixel art through PixelLab’s API, with a lockfile that tracks exactly what you got and where it came from.


πŸš€ What is Pixelkiln?

A Manifest-Driven Pipeline for AI-Generated Pixel Art

Pixelkiln is a CLI and library for bulk-generating pixel art through PixelLab’s API, built around a manifest-and-lockfile model borrowed directly from package managers. You hand-author what you want in a manifest; the tool writes down what you got and where it came from in a lockfile. Everything in between – submitting jobs, polling, downloading, filing – runs as deterministic scripted mechanics with no LLM anywhere in the loop. The one step that needs a human is picking among generated candidates, which happens in a small local web page (a “contact sheet”) you can clear in seconds.

The point of this tool is that no LLM is in the loop. It’s MIT licensed.

The problem, before and after

A single 65-badge achievement-icon set, generated conversationally one prompt at a time, consumed 350 separate account objects with zero record of which object produced which shipped file. That’s the failure mode Pixelkiln was built around:

BeforeAfter
Generation happened conversationally, one bespoke prompt at a timeA manifest declares every asset once; a single command drives all of them
Objects on the API account and files in the repo were two unrelated pilesThe lockfile maps every style-and-asset pair to the exact object and file, hashed on both ends
Style consistency depended on remembering the last prompt’s wordingA style block (prompt template plus reference images) is the enforced contract every asset in it inherits

✨ Key Capabilities

A manifest and a lockfile, not a chat transcript

pixelkiln.manifest.json is hand-authored and committed to version control. It declares styles (the visual contract) and assets (just a subject and short prompt each):

{
  "name": "my-game",
  "styles": {
    "default": {
      "generator": "1dir",
      "size": 64,
      "promptPrefix": "Premium indie-game achievement icon for X: one centered",
      "promptSuffix": "bold dark outline, isolated subject, transparent background",
      "styleImages": [{ "path": "art/ref-01.png" }],
      "outDir": "public/badges",
      "tags": ["my-game"]
    }
  },
  "assets": {
    "first_review": {
      "prompt": "a speech bubble with a single glowing star inside",
      "category": "milestones"
    }
  }
}

pixelkiln.lock.json is machine-written and also committed. It maps every style-and-asset key to the PixelLab object that satisfies it and the file it produced, with hashes on both sides – so the tool can always tell “unchanged,” “prompt changed since this was generated,” “someone hand-edited the file afterward,” or “generated but never downloaded.”

Styles are namespaces

Because lock keys are <style>/<asset>, a style is a namespace. Adding a second style to a manifest re-derives the entire asset list into a separate output directory under separate lock keys automatically, so restyling a whole collection can’t clobber the original:

"styles": {
  "premium": { "...": "..." },
  "neon": {
    "generator": "1dir", "size": 64,
    "promptPrefix": "Neon-noir achievement icon: one centered",
    "promptSuffix": "hot magenta and cyan rim light on near-black, bold outline, transparent background",
    "styleImages": [{ "path": "art/neon-ref.png" }],
    "outDir": "public/badges/variants/neon"
  }
}
pixelkiln gen --style neon

Every asset is defined once, then rendered in as many visual styles as you add.

A full command tour

  • plan – diffs manifest vs. lockfile vs. disk. Free, does no network I/O to a paid endpoint, safe to run constantly. The habit worth forming.
  • gen – the everyday command: submit, poll, pick (contact sheet), fetch, in one run. Supports --only <id> (regenerate exactly one asset), --style <id> (one variant set), --budget <n> (a hard spending cap that refuses to proceed if the plan would exceed it), and --force.
  • adopt – reconciles an existing account against files already on disk by image hash, recovering real prompts into the manifest.
  • accept – after rewording a style’s prompt, keeps the existing art as satisfying the new spec instead of triggering a regeneration you don’t actually want.
  • audit – measures how consistently a style’s generated assets actually hold together, as a number instead of an eyeball, by comparing each asset’s palette against the style’s reference images (or the set’s own collective average, if no references are set). Plain color-distance math, entirely offline, no model call.
  • pack – composites a style’s sprites into one PNG sheet plus a JSON atlas, offline and free, reading straight from the lockfile so it packs exactly what was actually generated. Frames are keyed by asset id, not a fragile array index. Also supports an explicit input list (--inputs) for packing sprites that were never part of any manifest at all.
  • salvage – recovers paid-for objects that never made it into any lockfile. Its own section below.
  • purge – the only destructive command, and it’s deliberately hard to run by accident: it lists exactly what it will delete, requires --yes to run non-interactively, and only ever touches objects a human has explicitly tagged for discard in salvage first.
  • balance / status – remaining generations and a lockfile summary.

salvage: recovering what already got paid for

Every generation-heavy account accumulates objects that were paid for and never landed in any repo: a rejected candidate, an experiment, work from a project that later got restructured. salvage is explicitly a recovery tool, not a cleanup tool. Point it at every other project’s lockfile via --claims and it tells you what’s genuinely unclaimed, sorted newest-first.

pixelkiln salvage --claims ../other-project/pixelkiln.lock.json --dry-run

pixelkiln salvage --claims ../other-project/pixelkiln.lock.json

For every unclaimed object, salvage opens a small local review page: one of three verdicts per item, keyboard-driven (hover a card and press a key).

VerdictWhat actually happens
importDownloads the image, adds it as a real tracked asset in the manifest and lockfile with its recovered prompt
keepTags it upstream and leaves it – a bookkeeping note, nothing local changes
discardTags it upstream only. Does not delete anything – deletion is a wholly separate, explicit purge step later

On a shared account (multiple projects drawing from the same API account, common for a studio or a person running several projects at once), salvage automatically figures out which style each unclaimed object most likely belongs to by matching its prompt against every known style’s template, then opens one correctly-scoped review session per style rather than one undifferentiated pile that risks importing another project’s art under the wrong label. Objects that don’t match any known style get called out separately rather than silently absorbed.

The cost economics that shape all of this

The single biggest cost lever, verified against a live account rather than inferred from documentation:

GeneratorCostCandidates returnedShape
map (default)1, any size1arbitrary width x height
1dir20-40, by canvas size tier4-64square only

That’s a 40x gap for a 64×64 icon. map is the right default for standalone props with a transparent background, which covers most icons, badges, and items. Reach for 1dir specifically when you need rotations or animation frames of the same object.

The counterintuitive part, worth its own callout: 1dir‘s multiple candidates aren’t the expensive part – the generator choice is. Generating smaller with 1dir actually returns more free candidates, not fewer:

Canvas size (1dir)Candidates returned
up to 42px64
up to 85px16
up to 170px4
over 170px1

Because forty re-rolls of a cheap map object cost the same as a single 1dir call, re-rolling is almost always the cheaper way to explore options unless you specifically need to compare several candidates side by side in one sitting:

65 icons via map   ->     65 generations
65 icons via 1dir  ->  2,600 generations

Two specific findings worth keeping in mind, because they’re genuinely surprising:

  • A prompt asking for “strict black and white, no colour” on the cheaper map generator, which has no style-image anchoring, came back with a yellow star and a brown chocolate bar. The same subjects, generated with a forced palette via the pixflux generator, came back containing exactly the requested two hex values and nothing else. Prose is a request; a forced palette is a constraint. pixflux exists for any style built on a fixed palette, like a Game Boy-style four-color look.
  • bitforge looked like the ideal endpoint on paper – palette and style reference and synchronous one-generation delivery – and returned unrecognizable output for a plain “blacksmith anvil” prompt at every style-strength setting tested. Measured, not assumed, is a running theme in how this project gets built.

What makes this different

Several comparable “manifest-driven AI asset generation” tools already exist, so here’s what’s actually different rather than a claim to be first:

  • Built specifically for PixelLab, a model that generates on a real pixel grid, rather than wrapping a general image model and downscaling the result afterward.
  • Explicitly exploits PixelLab’s pricing shape – fixed cost per call, candidate count that scales inversely with output size – as the core design decision, not an afterthought.
  • A genuine adopt reconciliation step: retroactively matching an existing account and an existing repo by image hash, for teams that already have generated art with no formal tracking. Not found in comparable tools surveyed.
  • A real lockfile, in the package-manager sense: committed, sorted for clean diffs, hash-verified on both the source object and the output file. Comparable tools use a SQLite database or an append-only log for the same job; a text lockfile is more transparent and diffable in code review.

What it doesn’t do yet

Two limitations from launch are already resolved: PixelLab is no longer the only provider, and animated characters are no longer out of scope. What changed and why is covered in What Actually Shipped in Pixelkiln’s First Month. What’s still actually missing:

  • Retro Diffusion’s multi-candidate, tileset, GIF, and spritesheet paths are mock-tested but haven’t had a live paid run yet.
  • ComfyUI’s pose-to-frame character workflow needs a live, pinned benchmark before it’s more than infrastructure.
  • Midjourney: still not a target. No public developer API, and every wrapper automates its Discord client against Midjourney’s own terms of service.

Roadmap

Retro Diffusion, a self-hosted ComfyUI adapter, and Scenario have since joined PixelLab as supported providers, each marked experimental; see the provider comparison doc for what each is actually good for. Midjourney remains ruled out: it has no public developer API, and every “Midjourney API” available today works by automating the Discord client, which violates Midjourney’s own terms of service and risks account termination.

🎯 Who It’s For

  • Solo indie dev – generating icons, badges, or props in bulk without babysitting a chat window for every one of them.
  • Small studio or team – sharing one PixelLab account across projects and needing to know, at a glance, whose art is whose.
  • Tool builders and pipeline engineers – anyone who wants generated assets to behave like a dependency: versioned, diffable, and reproducible, not a one-off chat export.

⚑ Quick Start

The repo’s public on GitHub. Setup is three lines: Node 22+, one environment variable, one JSON manifest.

git clone https://github.com/gfargo/pixelkiln.git && cd pixelkiln
npm install
npm link   # optional: makes the pixelkiln command global; otherwise prefix commands with node bin/pixelkiln.js
export PIXELLAB_API_KEY=...

pixelkiln plan   # free - shows exactly what a run would cost, before spending anything

pixelkiln gen --budget 120   # generates, opens a contact sheet to pick candidates, downloads the winners

A three-asset starter manifest (anvil, potion, compass) plans out to “3 assets x 40 generations = 120, returning 16 candidates each” – a concrete, copy-pasteable first run.

Already have art you generated by hand or conversationally, with no manifest? That’s a real, tested onboarding path, not an edge case:

pixelkiln init --from assets/sprites --exclude characters,gifs --generator map

pixelkiln adopt --write-prompts

init scans an existing folder of PNGs and writes a manifest with deliberately empty prompts – for art that already exists, the accurate prompt is whatever was actually used upstream, not a plausible-sounding guess. adopt then matches every local file to its account object by exact SHA-256 image hash and backfills the real prompt that produced it.

πŸ“Š Real-World Usage

  • Salvage recovery: on one audited production account, 190 of 361 total objects were unclaimed by any project’s lockfile, and a visual sample of the recovered set showed genuinely usable art – character portraits, tree variants, terrain tiles, UI icons – not rejects.
  • Adopt onboarding: a real 111-asset project onboarded through init and adopt recovered 98 assets with their real prompts backfilled. The remaining 13 weren’t lost data – they’d been hand-retouched locally after the original download, so their bytes no longer matched anything upstream, and the tool correctly reported them as untracked with unknown provenance rather than lying about their prompt or billing for a regeneration they didn’t need.
  • Audit catching drift: switching a tightly-styled set from one generator to a cheaper one dropped palette conformance from a median distance of 6.6 to 41.9, with 35 of 65 badges landing visibly off-palette. audit was built after that regression, specifically to catch that kind of drift with a number instead of a guess.

Pixelkiln started from one specific failure: a folder of shipped art with no record of what it had cost or where it came from. The manifest and lockfile exist to make that failure structurally hard to repeat. If bulk AI generation without a paper trail sounds familiar, I’d be glad to talk through whether this fits what you’re building.

Like what you saw?

There's more where that came from.

Browse all projects