بنقرة واحدة
butler-deploy
// Publish or update an HTML5 game on itch.io via the butler CLI. Covers install, login, channel push with versioning, and first-time page setup. Use when deploying a web game build (e.g. dist/) to an itch.io channel.
// Publish or update an HTML5 game on itch.io via the butler CLI. Covers install, login, channel push with versioning, and first-time page setup. Use when deploying a web game build (e.g. dist/) to an itch.io channel.
Deploy, manage, and debug services on Railway via the Railway CLI. Covers authentication, project + service creation, env variables, private networking, logs, and config-as-code. Use whenever creating, deploying, or troubleshooting Railway services. See references/ for the full CLI command reference and a Next.js+Prisma deploy recipe.
Manage infrastructure via OpenTofu (Terraform fork). Use when: provisioning servers, DNS, or cloud resources. Covers: init, plan, apply, destroy, import.
| name | butler-deploy |
| description | Publish or update an HTML5 game on itch.io via the butler CLI. Covers install, login, channel push with versioning, and first-time page setup. Use when deploying a web game build (e.g. dist/) to an itch.io channel. |
Push HTML5 game builds to itch.io with the butler CLI. Butler does delta uploads (only changed files after the first push) and the game is live the moment the push completes.
# macOS
brew install butler
# Linux
curl -L -o butler.zip https://broth.itch.ovh/butler/linux-amd64/LATEST/archive/default
unzip butler.zip
mv butler /usr/local/bin/butler
# Windows
scoop install butler
# or download from https://itchio.itch.io/butler
butler login
# Opens browser → authorize → return to terminal.
For CI: set BUTLER_API_KEY instead (generate at
https://itch.io/user/settings/api-keys). No butler login needed.
Butler refuses the first push if the target page does not exist.
After the page exists, all future deploys are fully scripted.
butler push <build-dir> <user>/<game>:<channel> --userversion <version>
| Argument | Example | Meaning |
|---|---|---|
<build-dir> | dist/ | local directory to upload |
<user>/<game> | acme/spacelander | itch.io target |
<channel> | html5 | upload channel (html5, windows, linux, osx, …) |
--userversion | 1.4.2 | stamps the upload (optional but recommended) |
Example:
butler push dist/ acme/spacelander:html5 --userversion 1.4.2
Wire build + push together — pick whichever fits your stack:
# Direct
npm run build && butler push dist/ acme/spacelander:html5 --userversion "$(node -p "require('./package.json').version")"
# Make
make build && butler push build/web acme/spacelander:html5
# Project npm script (optional convenience)
# package.json: "deploy": "butler push dist/ $ITCH_GAME:html5 --userversion $npm_package_version"
ITCH_GAME=acme/spacelander npm run deploy
butler push dist/web acme/spacelander:html5
butler push dist/win acme/spacelander:windows
butler push dist/linux acme/spacelander:linux
butler push dist/osx acme/spacelander:osx
butler status acme/spacelander # all channels, latest version + state
butler status acme/spacelander:html5 # one channel
butler login # interactive auth (browser)
butler logout
butler push <dir> <user>/<game>:<channel> [--userversion VER] [--ignore PATTERN]
butler status <user>/<game>[:<channel>]
butler fetch <user>/<game>:<channel> <out-dir> # download a build
butler validate <dir> # sanity-check a build dir
butler --version
| Symptom | Likely cause | Fix |
|---|---|---|
butler: command not found | not installed | install per step 1 |
404 Not found on first push | itch.io page doesn't exist | create the project page in browser first |
401 Unauthorized | not logged in / bad API key | butler login, or fix BUTLER_API_KEY |
403 Forbidden | account doesn't own the target | check <user>/<game> slug matches a project you own |
| Push silently uploads everything every time | dist/ content non-deterministic (timestamps, build hashes) | rebuild with stable filenames, or --ignore volatile files |
--userversion shows up empty on itch.io | flag value missing/empty | confirm the version env / package.json field is set before invoking |