pixellab-cli
Generate 2D game assets from the command line. Sprites, characters with eight rotations, animations, tilesets, UI panels, pixel fonts and portraits come from PixelLab's REST v2 API; concept images and box art come from GPT Image 2.5 on fal.
Every call that costs money is estimated before it is made, recorded after it resolves, and written to disk beside a manifest that says how the file was produced.
Install
Requires uv and Python 3.12 or newer.
uv tool install pixellab-cli
pixellab-cli --version
That puts one command on the path: pixellab-cli. The distribution and the command
share a name because pixellab on PyPI is PixelLab's own SDK and ships a script of
that name — installing both should not leave one shadowing the other.
To run what is on main, or before the first published release:
uv tool install git+https://github.com/protonspy/pixellab-cli
From a clone, for development:
uv sync --all-groups
uv run pixellab-cli --help
Setup, in one command
pixellab-cli setup
It offers the agent harnesses it finds evidence of — Claude Code, Codex, opencode — installs the skill where each of them reads it, and then asks for whichever credentials are not already set, without echoing them. A key it can already find is reported with its source and never asked for again.
Flags for when you know what you want: --claude, --codex, --opencode,
--global to install for every project rather than this one, --non-interactive for
a machine with nobody at the keyboard.
Credentials
Two providers and two separate accounts. PIXELLAB_SECRET comes from
https://www.pixellab.ai/account and FAL_KEY from https://fal.ai/dashboard/keys.
Either can live in an environment variable or in a .pixellab.json, and four places
are consulted, per credential, first hit winning:
FAL_KEY in the environment CI exports this; nothing on disk outranks it
./.pixellab.json … up to the project root this game's key
~/.pixellab.json the one you set once
pixellab-cli config set fal-key # prompts, without echo; never pass --value
pixellab-cli config show # which are set and where each came from, never the value
pixellab-cli config path # every file that would be consulted
A home file may also name a command instead of a value —
{"fal_key_command": "op read op://vault/fal/key"} — for keys that live in a
password manager. A project file may not: a project file arrives with a clone, and
running a command out of one would make git clone enough to execute it.
The PixelLab credential is the bearer token from the account page. A browser session cookie is a different thing and will not work. Neither value is ever written to a manifest, a ledger entry, or an error message.
The first command
Nothing here is free, so start with a dry run. It performs the same route choice and the same argument validation as the real call, then stops.
pixellab-cli --dry-run sprite "a healing potion" --size 64 --transparent
pixellab-cli sprite "a healing potion" --size 64 --transparent
route: create-image-pixflux
pixellab-out/2026-09-14T2131-a-healing-potion/a-healing-potion.png
cost: 1 generations, $0.0079 (reported)
What it can make
pixellab-cli character new "a knight in red armour" --name knight
pixellab-cli character animate <character-id> -a walking -d south -d north
pixellab-cli tiles terrain --lower grass --upper stone
pixellab-cli tiles platform --material "stone bricks"
pixellab-cli edit knight.png -p "give him a blue cape"
pixellab-cli inpaint knight.png --mask mask.png -p "a horned helmet"
pixellab-cli ui "wooden RPG panel with gold trim"
pixellab-cli font "warm orange arcade font" --bold
pixellab-cli art boxart "a knight at dawn over a burning keep"
pixellab-cli clean unzoom downloaded-sprite.png
pixellab-cli --help lists everything; every command takes --dry-run, --json and
--workspace.
Recipes
The point of one tool over two is the sequence across both providers:
pixellab-cli recipe run character "a knight in red armour" -a walking -a attacking
A concept image on fal, converted to pixel art, background removed, eight rotations,
then one animation per action. Every step is recorded as it finishes, a failure keeps
everything before it, and pixellab-cli recipe resume <recipe.json> carries on without
paying for the steps that already completed.
--max-generations N stops before the first call if the estimate is over N.
Where the output goes
pixellab-out/
ledger.jsonl
2026-09-14T2131-a-knight/
a-knight.png
2026-09-14T2131-a-knight.manifest.json
Nothing is ever overwritten — a second write becomes a-knight-2.png. The manifest
names the route, the parameters, the seed and the identifiers PixelLab assigned. The
ledger records every call, before it is made and again when it resolves.
pixellab-cli balance # what is left to spend
pixellab-cli ledger # what has been spent, by route, and what never resolved
The ledger prints the estimate and the reported cost side by side and never adds them: the gap between the two is what corrects the price table.
Using it from an agent
pixellab-cli setup installs a skill that teaches this CLI, and an agent with it can
generate assets without learning a single endpoint name. Claude Code gets the skill
directory; Codex and opencode get a delimited block in AGENTS.md with the references
beside it, and nothing outside that block is touched.
There is no MCP server, and docs/adr/0003-a-cli-and-a-skill-rather-than-an-mcp-server.md
says why. docs/wiki/pages/harness-instructions.md records where each harness reads
its instructions from.
Documentation
docs/wiki/pages/pixellab-asset-routing.md— which endpoint answers which requestdocs/wiki/pages/pixellab-cost-model.md— what a call costs and which number to believedocs/wiki/pages/concept-to-sprite.md— the recipe from a concept image to an animated spritedocs/adr/— the decisions behind the shape of the toolspecs/— what each feature does, and the tasks that built it
Development
uv run pytest # the suite
uv run python scripts/test_gate.py # the suite, with the coverage report
uv run ruff check . && uv run ruff format --check .
uv run python scripts/refresh_reference.py # report provider schema drift
No test touches the network: provider traffic is replayed through respx.
Releasing
A tag publishes, and nothing else does — see
docs/adr/0006-publish-to-pypi-from-a-tag.md.
- Bump
versioninpyproject.toml, and commit it. git tag v0.2.0 && git push --tags, with the tag matching that version. The pipeline refuses a tag that does not.- The workflow runs the four gates, builds a wheel and an sdist, checks the metadata
PyPI will render, installs the built wheel into a clean environment and runs
pixellabfrom it, and only then uploads.
Uploading needs PYPI_API_TOKEN as a repository secret — a token from
https://pypi.org/manage/account/token/, scoped to this project once it exists:
gh secret set PYPI_API_TOKEN
That prompts for the value rather than taking it as an argument, so the token stays out of your shell history.
0.1.0 was published by hand, from a checkout, before this pipeline existed — that
upload is what created the project on PyPI. A version cannot be uploaded twice, so
tagging v0.1.0 now would run the pipeline and fail at the upload step. The next
release is the first one the tag flow actually performs, and it starts from a bumped
version.
The first release, and a key that is not account-wide
A token scoped to one project can only be made after that project exists, and a project on PyPI exists once something has been uploaded to it. So the first release is the one that uses a wider key, and the key is narrowed immediately after:
- Create an account-scoped token at
https://pypi.org/manage/account/token/, put it inPYPI_API_TOKEN, and tagv0.1.0. That upload creates the project. - Create a token scoped to pixellab-cli only, replace the secret with it, and delete the account-scoped one. From here a leaked token can publish this package and nothing else you own.
There is a way to skip tokens entirely: PyPI accepts a pending publisher for a
project that does not exist yet, at
https://pypi.org/manage/account/publishing/. Registering this repository, the
workflow filename and the environment there lets the first upload authenticate over
OIDC and creates the project in the same act — no secret in the repository at all.
docs/adr/0006-publish-to-pypi-from-a-tag.md records why the token was chosen for
now and keeps that as the upgrade path.
Two more things live in repository settings rather than in any file here, and a pipeline cannot check that you did them:
- Required reviewers on the
pypienvironment. The release job names it, so adding reviewers there turns a tag push into a request rather than a publication. - A tag protection rule for
v*. Without one, anyone who can push can publish, and a published version cannot be taken back.
Licence
MIT.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pixellab_cli-0.2.1.tar.gz.
File metadata
- Download URL: pixellab_cli-0.2.1.tar.gz
- Upload date:
- Size: 276.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b15cff3177bbd9a11c080df85a656fcb8b36d34120ad8b991c2ececcac9acdcb
|
|
| MD5 |
f8ae767697807379a2aabd698b59ec41
|
|
| BLAKE2b-256 |
2e8f7a16f3dfcdea95f74638f3ead20712962e41c962a9ce21ec666448ef13c1
|
File details
Details for the file pixellab_cli-0.2.1-py3-none-any.whl.
File metadata
- Download URL: pixellab_cli-0.2.1-py3-none-any.whl
- Upload date:
- Size: 134.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
57d2035a919311f3d8a0a7c4c116af14c860d3dcac80de107c1efa1e0aae21fc
|
|
| MD5 |
20432c78c1e0d95bb7e54ac987146229
|
|
| BLAKE2b-256 |
243e9f60a7959de3593e569dcb90856472e909e117e601d83cd3c442a317b2af
|