{
  "markdown": "# MINIPAINTER\n\n> The paint bench, indexed.\n\n[![CI](https://github.com/ArturSkowronski/minipainter/actions/workflows/ci.yml/badge.svg)](https://github.com/ArturSkowronski/minipainter/actions/workflows/ci.yml)\n[![npm](https://img.shields.io/npm/v/minipainter.svg)](https://www.npmjs.com/package/minipainter)\n\nSite: [arturskowronski.github.io/minipainter](https://arturskowronski.github.io/minipainter/)\n\n`minipainter` is a local-first paint registry for miniature-painting workflows. It exists for one practical reason: AI paint suggestions are much more useful when they understand the paints you actually own.\n\nThe project gives you:\n\n- a deterministic local catalog (1,607 paints across Citadel, Army Painter, Vallejo, AK) and inventory\n- owned-first paint lookup and cross-brand color matching\n- a colored terminal UI (TUI) that shows each paint's real RGB as a swatch\n- MCP servers for both Claude Desktop and ChatGPT\n- a CLI surface designed for both humans and agent workflows\n\n## The colored TUI\n\nThe ledger TUI is a real colored terminal app: a `MINIPAINTER` banner, gold section\nframes, green `OWNED` / red `MISSING` status, and a truecolor swatch of every paint's own\nRGB. Color turns on for a TTY and honors `NO_COLOR`.\n\n![MINIPAINTER terminal UI — catalog](docs/assets/hero.svg)\n\n## Why This Exists\n\nMost paint advice workflows break at the same point: they recommend paints you do not have on hand.\n\n`minipainter` is built to solve that exact problem:\n\n- keep a local record of what is in your paint rack\n- search it quickly by name, role, family, and approximate color\n- prepare a stable inventory foundation for a future AI skill that can inspect links, photos, and model images\n\nThe long-term goal is not “AI picks random colors for miniatures.” The goal is “AI reasons from your actual inventory first, then suggests stronger alternatives only when useful.”\n\n## Feature Highlights\n\n- `Owned-first matching`: lookups and recommendations can prioritize paints you already have.\n- `Catalog in repo, inventory in your home`: paint records live in `data/catalog/`; what you own lives in `~/.minipainting/inventory.json` and follows you across projects.\n- `RGB-aware search`: approximate RGB values help with nearest-color matching.\n- `Colored TUI`: terminal ledger with per-paint RGB swatches and OWNED/MISSING status.\n- `Agent-friendly CLI`: deterministic command output for AI integration (Claude + ChatGPT MCP).\n\n## Screenshots\n\n### Hero Screen\n\nFull-screen TUI with banner, catalog, detail panel, and command strip.\n\n```text\nSee: docs/assets/hero.txt\n```\n\n![Hero Screen](docs/assets/hero.svg)\n\n### Search View\n\nFiltered lookup for a semantic search like `bone`.\n\n```text\nSee: docs/assets/search.txt\n```\n\n![Search View](docs/assets/search.svg)\n\n### Owned View\n\nInventory-only presentation focused on what is already bound to your collection.\n\n```text\nSee: docs/assets/owned.txt\n```\n\n![Owned View](docs/assets/owned.svg)\n\n### CLI Flow\n\nRepresentative command-line usage for search, ownership updates, and color matching.\n\n```text\nSee: docs/assets/cli.txt\n```\n\n![CLI Flow](docs/assets/cli.svg)\n\n## Run with Docker (Postgres)\n\nThe whole stack — MCP/HTTP server plus a Postgres that stores your inventory — starts with\none command. Inventory persists in a named volume, so it **survives container restarts and\n`docker compose down` / recreation** (only `down -v` wipes it).\n\n```bash\ndocker compose up --build        # http://localhost:3000\n```\n\n- `GET /health` — liveness\n- `GET /api/inventory` — owned paints (from Postgres)\n- `POST /mcp` — MCP for Claude Desktop · `POST /mcp/v3` — MCP for ChatGPT (`search`/`fetch`)\n\nStorage is selected by `DATABASE_URL`: set it (as `docker-compose.yml` does) for Postgres,\nleave it unset to use a local JSON inventory file (unchanged local behavior). See `.env.example`.\n\n## Deploy\n\nAny Docker + Postgres host works (Fly.io, Railway, a VPS…). For a one-click remote MCP\nserver with a managed database, the repo ships a **Render Blueprint** (`render.yaml`) that\nprovisions the web service and Postgres together and wires `DATABASE_URL` automatically:\n\n[![Deploy to Render](https://render.com/images/deploy-to-render-button.svg)](https://render.com/deploy?repo=https://github.com/arturskowronski/minipainter)\n\nThe reference deployment `warpaint-mcp.fly.dev` runs on Fly.io with Fly Managed Postgres — see\n[`docs/deploy-fly.md`](docs/deploy-fly.md) for the `fly mpg attach` + migration steps.\n\n## Install\n\nThe fastest way — run it straight from npm with `npx`, no clone, no install:\n\n```bash\nnpx minipainter paint search bone\nnpx minipainter match color \"#d2c29b\"\nnpx minipainter tui\n```\n\nOr install it globally to get the short `mpaint` command everywhere:\n\n```bash\nnpm install -g minipainter\nmpaint paint search bone\nmpaint match color \"#d2c29b\"\n```\n\nThe catalog is bundled, so search and matching work on first run with nothing to configure. Your inventory lives at `~/.minipainting/inventory.json`, created automatically the first time you mark a paint owned (legacy `~/.warpaint/` is auto-migrated).\n\nRequirements:\n\n- Node.js 18 or newer\n- POSIX-ish shell (Linux, macOS, WSL)\n\n### From source\n\nTo hack on it, clone and run against the working tree:\n\n```bash\ngit clone https://github.com/ArturSkowronski/minipainter.git\ncd minipainter\nnpm install\nnode src/cli.mjs paint search bone\n```\n\nAfter that you have four usage modes:\n\n- **CLI / TUI** — see Quickstart below\n- **Self-hosted HTTP server** — a single Docker-friendly runtime with JSON storage and API endpoints\n- **Local MCP for Claude Desktop** — see [Claude Desktop MCP Setup](#claude-desktop-mcp-setup)\n- **Remote MCP for Claude mobile/web** — see [Remote MCP](#remote-mcp-claude-mobile)\n\n## Quickstart\n\nInitialize the local inventory at `~/.minipainting/inventory.json`:\n\n```bash\nnode src/cli.mjs catalog sync\n```\n\nSearch paints:\n\n```bash\nnode src/cli.mjs paint search black\nnode src/cli.mjs paint search bone --json\n```\n\nInspect one paint:\n\n```bash\nnode src/cli.mjs paint show \"Abaddon Black\" --json\n```\n\nMark paints as owned or missing:\n\n```bash\nnode src/cli.mjs inventory own \"Abaddon Black\"\nnode src/cli.mjs inventory unown \"Abaddon Black\"\nnode src/cli.mjs inventory list\n```\n\nRun semantic or color matching:\n\n```bash\nnode src/cli.mjs match describe bone\nnode src/cli.mjs match color \"#d2c29b\"\n```\n\nLaunch the TUI:\n\n```bash\nnode src/cli.mjs tui\n```\n\nRun the MCP server locally:\n\n```bash\nnode src/mcp-server.mjs\n```\n\nRun the self-hosted HTTP server locally:\n\n```bash\nDATA_DIR=.minipainting-data node src/mcp-http-server.mjs\n```\n\n## TUI Workflow\n\nThe TUI is centered around three presentation areas:\n\n- `FORGE CATALOG`: visible paints in the current scope\n- `SELECTED PIGMENT`: the currently highlighted paint with provider, families, usage, and RGB\n- `RITUAL COMMANDS`: the command legend for the active session\n\nCurrent TUI commands:\n\n- `search <text>`\n- `owned`\n- `catalog`\n- `toggle`\n- `quit`\n\nRecommended use:\n\n1. start with `catalog`\n2. narrow with `search bone`, `search black`, or similar queries\n3. inspect the selected pigment panel\n4. toggle ownership as your collection changes\n\n## Project Direction\n\nImplemented now:\n\n- local JSON registry\n- starter provider catalogs for Citadel and Army Painter\n- owned / missing inventory tracking\n- deterministic search and color matching\n- colored terminal presentation with per-paint swatches\n- local MCP server for Claude Desktop\n\nPlanned later:\n\n- a separate skill for parsing paint-set links\n- image-driven inventory fill from paint bottle photos\n- model-photo analysis that recommends owned paints first\n- stronger cross-provider equivalents and matching hints\n\n## Technical Notes\n\n- Built-in catalog data lives in `data/catalog/` (Citadel and Army Painter, kept in version control)\n- Inventory file: `~/.minipainting/inventory.json` — stores only owned paint ids in the form `{ \"version\": 1, \"owned\": [\"citadel/abaddon-black\", ...] }`\n- Self-hosted server data directory: `DATA_DIR` (defaults to `/data` in Docker); inventory lives at `<DATA_DIR>/inventory.json`\n- The catalog and inventory are composed at runtime; saving never rewrites the catalog\n- IDs are stable by convention (provider + name slug); on load, owned ids missing from the catalog are reported as warnings instead of being silently dropped\n- A pre-existing project-local `.minipainting/registry.json` next to the inventory path is auto-migrated on first run\n- Legacy `.warpaint/` data directories are auto-renamed to `.minipainting/` on first run (both home and project-local variants)\n- Override the inventory location at the API surface with `{ inventoryPath }` or `{ cwd }` (the latter resolves to `<cwd>/.minipainting/inventory.json`, which is what the test suite uses for isolation)\n- RGB values are approximate reference colors for matching, not a guarantee of final painted appearance\n- MCP entrypoint: `node src/mcp-server.mjs`\n- MCP helper script: `npm run mcp`\n- HTTP server helper script: `npm run server`\n- README demo captures are reproducible via:\n\n```bash\nnpm run generate:demo\n```\n\n## Claude Desktop MCP Setup\n\n`minipainter` now includes a local MCP server so Claude Desktop can use your paint registry directly.\n\nExample local MCP config:\n\n```json\n{\n  \"mcpServers\": {\n    \"minipainter\": {\n      \"command\": \"node\",\n      \"args\": [\"/absolute/path/to/minipainter/src/mcp-server.mjs\"]\n    }\n  }\n}\n```\n\nAfter adding the server, Claude Desktop can call tools such as:\n\n- `paint_search`\n- `paint_show`\n- `inventory_list`\n- `inventory_mark_owned`\n- `inventory_mark_unowned`\n- `match_color`\n- `match_describe`\n\nSuggested local flow:\n\n1. initialize your registry once with `node src/cli.mjs catalog sync`\n2. add the MCP server to Claude Desktop\n3. ask Claude to search paints or update ownership through the exposed tools\n\n## Agent Skill\n\nFor Claude Code, fetch the skill straight from the site, no clone required. It ships with the\nright guardrails baked in: JSON-only reads, `product_format` rules, failure handling.\n\n```bash\n# project-scoped\nmkdir -p .claude/skills/minipainter\ncurl -fsSL https://arturskowronski.github.io/minipainter/SKILL.md \\\n  -o .claude/skills/minipainter/SKILL.md\n```\n\nOr save it to `~/.claude/skills/minipainter/SKILL.md` to use it everywhere.\n\n## Self-Hosted Docker\n\nThe Docker image runs a single HTTP server runtime designed for self-hosted use. Build it from\nthe repo (no image is published to a registry yet):\n\n```bash\ndocker build -t minipainter .\ndocker run -p 3000:3000 -v minipainting-data:/data minipainter\n```\n\nOr bring up the server together with Postgres in one step with `docker compose up -d`.\n\nThe server exposes:\n\n- `GET /health`\n- `GET /api/paints`\n- `GET /api/paints/:paint`\n- `GET /api/inventory`\n- `PUT /api/inventory/:paint`\n- `DELETE /api/inventory/:paint`\n- `POST /api/match/color`\n- `POST /api/match/describe`\n- `POST /mcp`\n\nOptional runtime configuration:\n\n- `PORT` — listen port, defaults to `3000`\n- `DATA_DIR` — persistent state directory, defaults to `/data` in Docker\n- `AUTH_TOKEN` — protects `/api/*` and `/mcp` with `Authorization: Bearer ...`\n- `INVENTORY_SYNC_TOKEN` — protects the legacy `/inventory` sync endpoint\n\n## Remote MCP (Claude Mobile)\n\nFor Claude mobile or web, the stdio MCP server above is not reachable. Run\n`minipainter-mcp-http` instead — a Streamable HTTP MCP transport exposing the\nsame tools, plus `GET`/`POST /inventory` for syncing the local inventory.\n\n### Local smoke test\n\n```bash\nexport INVENTORY_SYNC_TOKEN=$(openssl rand -hex 32)\nexport PORT=3000\nexport INVENTORY_PATH=$HOME/.minipainting/inventory.json\nnpm run mcp:http\n```\n\nThen in another shell:\n\n```bash\ncurl -s http://localhost:3000/health\ncurl -s -X POST http://localhost:3000/mcp \\\n  -H \"Content-Type: application/json\" \\\n  -H \"Accept: application/json, text/event-stream\" \\\n  -d '{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"tools/list\"}'\n\n# Sync endpoint (bearer-token protected)\ncurl -s -H \"Authorization: Bearer $INVENTORY_SYNC_TOKEN\" \\\n     http://localhost:3000/inventory\n```\n\n### Deploy to Fly.io\n\nThe repo ships a `Dockerfile` and `fly.toml`. Full recipe in\n[docs/deploy-fly.md](docs/deploy-fly.md). Short version:\n\n```bash\nfly launch --no-deploy --copy-config --name <your-app-name>\nfly volumes create inventory_data --region <your-region> --size 1\nfly secrets set INVENTORY_SYNC_TOKEN=\"$(openssl rand -hex 32)\"\nfly deploy\n```\n\n### Connect Claude\n\nIn Claude (mobile or web), add a custom connector:\n\n- URL: `https://<your-app-name>.fly.dev/mcp`\n\nThe `/mcp` endpoint currently has no authentication — anyone with the URL can\ncall tools. Use the obscurity of the URL plus Fly's network controls for now;\nadd per-user auth before sharing the URL.\n\n### Environment variables\n\n| Variable | Required | Purpose |\n|---|---|---|\n| `INVENTORY_SYNC_TOKEN` | for `/inventory` | Bearer token protecting `GET`/`POST /inventory`; when unset, sync returns 503 |\n| `INVENTORY_PATH` | no | Path to `inventory.json`; default `~/.minipainting/inventory.json` locally, `/data/inventory.json` in the Docker image |\n| `INVENTORY_JSON` | no | One-time seed JSON; only used when `INVENTORY_PATH` is absent on first boot |\n| `WARPAINT_INVENTORY_JSON` | no | Legacy alias of `INVENTORY_JSON` |\n| `MCP_SERVER_NAME` | no | Server name in MCP handshake + startup log; default `paint-inventory` |\n| `PORT` | no (default `3000`) | TCP port to listen on |\n\n### Known limitations\n\n- `/mcp` has no authentication yet. The bearer token only protects `/inventory`.\n- Stateless transport: no long-running SSE tool streams (the tools are\n  fast so this is fine).\n\n## Self-hosting your own MCP\n\nThe MCP server is generic — only the CLI (`mpaint`) is branded. To run your\nown instance:\n\n1. Fork or clone the repo.\n2. (Optional) rename your Fly app in `fly.toml`.\n3. Create a Fly volume and set secrets:\n\n   ```bash\n   fly volumes create inventory_data --size 1 --region <your-region>\n   fly secrets set INVENTORY_SYNC_TOKEN=$(openssl rand -hex 24)\n   # Optional one-time seed:\n   fly secrets set INVENTORY_JSON=\"$(cat ~/.minipainting/inventory.json)\"\n   ```\n\n4. (Optional) name your MCP server (shown in the MCP handshake and startup\n   logs):\n\n   ```bash\n   fly secrets set MCP_SERVER_NAME=my-paints\n   ```\n\n5. Deploy:\n\n   ```bash\n   fly deploy\n   ```\n\n6. Register the remote in your local CLI and sync:\n\n   ```bash\n   mpaint sync add default \\\n     --url https://my-app.fly.dev \\\n     --token <token-from-step-3>\n   mpaint sync push\n   ```\n\nAfter this, your local inventory and the deployed MCP stay in sync via\n`mpaint sync push` (upload local → remote) and `mpaint sync pull --force`\n(overwrite local from remote).\n\n## License\n\n[MIT](LICENSE) © Artur Skowronski\n",
  "bytes": 14613,
  "sha": "5d97fef28fa64c2cc8997e23539c3052576f9e1f6773dbbe1ef3f560ee4d1813",
  "repo_slug": "arturskowronski/minipainter",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_arturskowronski_minipainter_2a7684e9/readme"
}