Skip to main content
Glama
README.md
# PixelSmith

Pixel art as text. Claude draws sprites by writing rows of characters and drawing
commands through an MCP connector; every save returns a zoomed preview with
coordinates, real-size copies, a silhouette and automatic checks. Humans view and
paint the same sprites in a web page from any device.

**Try it:** https://pixelsmith.aleksanderbryhnreitan.no — a painter anyone can use
(it keeps everything in your own browser) and a step-by-step setup guide in
Norwegian.

## Run your own

Free on the free plans of GitHub, Vercel, Upstash and Claude; no programming.

1. **Make a secret**: a long random password, e.g. `openssl rand -base64 32`, or the
   generator on [the setup page](https://pixelsmith.aleksanderbryhnreitan.no/oppsett).
   Keep it somewhere safe.
2. **Deploy**: [Deploy to Vercel](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2FjustAleks0%2Fpixelsmith&project-name=pixelsmith&repository-name=pixelsmith&env=MCP_SECRET&envDescription=A%20long%20random%20password.%20Claude%20and%20your%20painter%20use%20it%20to%20prove%20it%20is%20you.&envLink=https%3A%2F%2Fgithub.com%2FjustAleks0%2Fpixelsmith%23run-your-own&stores=%5B%7B%22type%22%3A%22integration%22%2C%22integrationSlug%22%3A%22upstash%22%2C%22productSlug%22%3A%22upstash-kv%22%2C%22protocol%22%3A%22storage%22%7D%5D).
   Vercel copies this repository to your GitHub, creates a free **Upstash for Redis**
   database (pick the Free plan and a region near you) and asks for `MCP_SECRET`:
   paste the secret.
3. **Find your address**: the short one under **Domains** on the project's page,
   e.g. `pixelsmith-you.vercel.app`. The long per-deployment addresses sit behind
   Vercel's login and Claude can't reach them.
4. **Connect Claude**: in claude.ai or the Claude app, **Customize → Connectors →
   + → Add custom connector**. Name `PixelSmith`, URL
   `https://YOUR-ADDRESS/api/mcp?k=YOUR-SECRET`, authentication **No sign-in**.
   Switch it on in a chat under **+ → Connectors**. For Claude Code:

   ```
   claude mcp add --transport http --scope user pixelsmith "https://YOUR-ADDRESS/api/mcp?k=YOUR-SECRET"
   ```

5. **Paint by hand**: open `https://YOUR-ADDRESS/?k=YOUR-SECRET` once per browser. It
   swaps the secret for a year-long cookie and removes it from the address bar.

The secret travels in the address because custom connectors on most Claude plans
can't send any other kind of password yet, so treat both links like passwords.
To change it: edit `MCP_SECRET` in the Vercel project's settings, redeploy, and
update the connector's URL.

### Settings

| Variable | Needed | What it does |
|---|---|---|
| `MCP_SECRET` | yes | The one secret that guards the connector, the painter and the HTTP API. |
| `KV_REST_API_URL` / `KV_REST_API_TOKEN` | yes, on Vercel | The Upstash Redis database; set by Vercel when the database is connected. Any `*_KV_REST_API_*` or `*_REDIS_REST_*` pair works. Without one, a local run keeps everything in memory. |
| `PUBLIC_HOST` | no | An address (e.g. `pixelsmith.example.com`) whose front page shows the public showcase from `public/site/` instead of the private painter. |

### Pages

- `/` — your painter (behind the secret), or the showcase on `PUBLIC_HOST`.
- `/tegn` — the public painter: no secret, everything kept in the visitor's own
  browser, no connection to your sprites.
- `/oppsett` — the setup guide (Norwegian).

## The sprite format

A sprite is `w` × `h` rows of single-character colour keys; `.` is transparent.
Keys come from the project's palette plus the sprite's own `colors`. Projects hold
palettes, colour swaps (named recolourings, e.g. rarity tiers) and sprites. The
`guide` tool returns the full reference, including every drawing command.

A palette can also hold **ramps**: named runs of its keys from dark to light
(`steel = 12345`). The shading commands work along them — `shade` turns a flat
area into a lit, rounded form; `gradient`, `dither` and `texture` (wood grain,
stones, grit, bricks, cloth) add surface; `selout` colours the outline on the lit
side. Any command can be limited to certain existing colours (`in=`, `not=`) or
a rectangle (`box=`), so a shape becomes a brush that only paints onto, say, the
metal of a blade.

For animation, `stamp` can paste a part turned by any angle and `lift` can move
or turn a piece of the sprite in place (both RotSprite-style), and `arc` draws
curved strokes such as motion streaks. Turning pixel art by an odd angle still
roughens it, so for poses that matter the better way is to draw the moving part
again at its new angle. The `animate` tool plays sprites in order at 12 ticks a
second, lined up by their feet, and returns a filmstrip, an onion skin and a
looping GIF.

## Tools

`guide`, `projects`, `project`, `create_project`, `set_palette`, `set_swap`,
`put_sprite`, `edit_sprite`, `view_sprite`, `sheet`, `mockup`, `animate`,
`rename_sprite`, `copy_sprite`, `delete_sprite`, `history`, `restore_sprite`,
`export`.

The same tools are available over plain HTTP at `POST /api/tool/<name>` (JSON
arguments in, `{ ok, text, images[] }` out), which is what the helper uses.

## The PC helper

```
node scripts/ps.mjs tools
node scripts/ps.mjs view_sprite '{"project":"demo","name":"potion"}'
node scripts/ps.mjs edit_sprite < args.json
node scripts/ps.mjs pull <project> "<game folder>"      # PNGs + manifest into a game
```

Images come back as files in `%TEMP%\pixelsmith` (or `$PIXELSMITH_OUT`). `pull`
only ever deletes files a previous pull wrote (tracked through `manifest.json`),
plus Godot's `.import` file next to a removed PNG.

## Export layout

`png/NAME.png` (1x), `png/NAME@SWAP.png` for each swap that applies to the sprite,
`src/NAME.json` (the source), `project.json` (palettes and swaps), and
`manifest.json` listing every sprite with its size, tags and files.

## How it's built

- Next.js 15 on Vercel; `mcp-handler` for the connector (single secret, 403 on
  failure so MCP clients don't start an OAuth flow).
- `app/painter.tsx` — the web painter, in two modes (`lib/painter/backend.ts`): your
  sprites on the server behind the secret, with live updates while Claude draws, or
  the public `/tegn` one that keeps everything in the browser's local storage.
- `public/site/` — the showcase and setup guide, plain HTML built by
  `scripts/build_site.py` from the art in `brand/` (`scripts/brand.ts`,
  `scripts/brand_preview.py`).
- `lib/core/` — sprite model, drawing commands, checks. No Node-only imports; the
  web painter uses the same code.
- `lib/render/` — raster drawing, a 3x5 label font, PNG and GIF encoders, and the
  preview / sheet / mockup / animation images.
- `lib/store.ts` — Upstash Redis (connected through the Vercel project's Storage
  tab). Chosen over Vercel Blob because Blob's free tier allows 2,000 writes a month
  shared with every other connector on the account and locks them all for 30 days
  when exceeded. Falls back to an in-memory store when run locally.
- Every sprite keeps its last 20 versions, including after deletion.
- Server functions run in Frankfurt (`vercel.json` → `fra1`), next to the database
  and close to Norway. Keep the two in the same region.

## Working on it

```
npm test                   # offline checks of the drawing core; images in out-local/
npx next build             # production build
npx vercel@latest deploy --prod
```

Test locally against the production build only (`next build` then
`MCP_SECRET=... npx next start -p 3123`), never the dev server.