CLI
arcadey is the Cool GPT Games command-line tool. It publishes a game from a folder (cover image included), ships updates to an existing game, lists your games, shows moderation feedback and reports your earnings, from your terminal or a CI job. It's a small wrapper around the Publisher SDK, so anything it does can also be done with the SDK or the REST API overview endpoints.
Installation
npm i -g @coolgptgames/cliOr run it without installing:
npx -y @coolgptgames/cli --helpRequirements: Node.js 18 or later. The published package is a self-contained bundle — no TypeScript toolchain or build step. Check it works:
arcadey --helpAuthentication
The CLI authenticates with an API key. Create one in Dashboard → API keys (see API keys & scopes).
Commands | Scope the key needs |
|---|---|
|
|
|
|
A publish key also satisfies read, so one key with the publish scope covers every command. That's the right choice for a CI key. A key with neither read nor publish (for example one scoped only to webhooks) can't log in: the check fails with This API key lacks the "read" scope.
Where the key and API URL come from
The CLI talks to https://api.coolgptgames.com unless you tell it otherwise. For each setting it uses the first one of these it finds:
Setting | 1st | 2nd | 3rd | Default |
|---|---|---|---|---|
API key |
|
|
| none: the command fails with "Not logged in" |
API URL |
|
|
|
|
An empty environment variable counts as unset.
Config file
arcadey login writes ~/.arcadey/config.json:
{
"apiKey": "ak_…",
"baseUrl": "https://api.coolgptgames.com"
}The file holds your key in clear text. The CLI creates it readable by you only (mode
0600, in a0700directory) and resets the mode to0600each time it writes an existing file.baseUrlis the API URL that was in effect when you logged in (from--url,ARCADEY_API_URLor the default).Running
loginagain overwrites the file.To log out, delete the file (and revoke the key in the dashboard if you're done with it).
Commands
arcadey login <api-key> [--url <api>]
arcadey whoami
arcadey games
arcadey publish <dir> [--cover <path>] [--game <id>] [--title <t>] [--category <slug>]
[--changelog <text>] [--realtime] [--no-wait]
arcadey status <gameId>
arcadey earnings
arcadey helpGlobal flags, accepted by every command:
Flag | Meaning |
|---|---|
| Use this key for this run only (ignored by |
| Use this API origin for this run only |
| Print the usage summary and exit |
Flag syntax
Flags can go before or after the command and its arguments:
arcadey --url https://api.coolgptgames.com gamesandarcadey games --url https://api.coolgptgames.comare the same.Flags that take a value accept
--flag valueor--flag=value. With the space form, the next argument must not start with--; otherwise the CLI reports--<flag> needs a value.--realtime,--no-waitand--helpnever take a value, so they can't swallow a following argument (arcadey publish --realtime ./distworks). Writing--realtime=trueis an error.--ends flag parsing. Everything after it is treated as a positional argument, for example a folder whose name starts with--.An unknown flag is an error:
✖ Unknown flag --<name>. Run `arcadey --help`.(exit1).arcadeywith no arguments,arcadey help,arcadey --helpandarcadey -hall print the usage summary.
arcadey login <api-key>
Checks the key against the API (by listing your games) and saves it, with the API URL, to ~/.arcadey/config.json.
Flag | Meaning |
|---|---|
| API origin to use and save. Falls back to |
$ arcadey login ak_your_key_here
✓ Logged in to https://api.coolgptgames.com. You have 3 game(s).If the key is rejected, the CLI prints ✖ Key rejected: <reason> (<status>), saves nothing, and exits 1.
arcadey whoami
Confirms the credentials in use work, and shows which API they were checked against.
$ arcadey whoami
✓ Authenticated against https://api.coolgptgames.com. 3 game(s).arcadey games
Lists your games, drafts included, with their ids.
$ arcadey games
ID SLUG STATUS PLAYS TITLE
7c1f2a90-5b3e-4d21-9f0a-1e2d3c4b5a69 space-miner-3fa2c1 published 1824 Space Miner
0b8e4d17-2c6a-4f93-8e15-7a9b0c1d2e3f tiny-golf-81b0d2 pending_review 0 Tiny GolfSlugs and statuses longer than their column are cut short. Use the ID with status and publish --game.
If you have no games yet it prints No games yet. Publish one with `arcadey publish <dir> --cover <image>`.
arcadey publish <dir>
Uploads the files in <dir> as a game and waits for the review verdict. Without --game it creates a new game. With --game <id> (or "id" in game.json) it ships a new version of that existing game.
Flag | Meaning |
|---|---|
| Cover image: PNG, JPEG, GIF or WebP, at most 5 MB. Required for a new game. Optional for an update, where it replaces the current cover. The path is relative to the current directory. |
| Publish a new version of this existing game instead of creating one. Overrides |
| Title for a new game. Overrides |
| Category for a new game, for example |
| Release notes for the new version, up to 2,000 characters. Updates only. Works together with a |
| Enable multiplayer for this version (same as |
| Don't wait for review: check the status once and return |
| See Global flags |
game.json
<dir>/game.json is optional. If it exists it must be valid JSON, or the command fails before doing anything. The CLI reads these fields and ignores any others:
{
"id": "7c1f2a90-5b3e-4d21-9f0a-1e2d3c4b5a69",
"title": "Space Miner",
"category": "arcade",
"description": "Mine asteroids, dodge pirates.",
"instructions": "Arrow keys to fly, space to mine.",
"cover": "art/cover.png",
"screenshots": ["art/shot1.png", "art/shot2.png"],
"realtime": false,
"connectHosts": ["https://api.example.com"],
"gamification": {
"achievements": [
{ "key": "first_ore", "name": "First Ore", "description": "Mine your first asteroid" }
],
"xp": { "note": "XP per asteroid mined", "leaderboard": true }
}
}Field | Used for | Notes |
|---|---|---|
| Updates | The existing game to publish a new version of. |
| New games | Required for a new game unless you pass |
| New games | Category slug |
| New games | |
| New games | How to play |
| New games and updates | Path relative to |
| New games and updates | Paths relative to |
| New games and updates |
|
| New games and updates | External |
| New games and updates | Declared achievements and XP intent. See XP, achievements & social. A version uploaded without this block keeps the game's current achievements. |
To keep one game.json for both the first publish and later updates, add "id" after the first publish. The CLI prints the exact line to add.
What it does
Reads
game.json, then checks everything it can before calling the API, so a mistake never leaves a stray draft game behind:--changelogwithout a game id:✖ --changelog is for updates — pass --game <id> (or set "id" in game.json).New game without a title:
✖ No title — pass --title or add game.json with a title.New game without a cover:
✖ No cover image — every game needs one to publish. Pass --cover <path> or set "cover" in game.json (PNG, JPEG, GIF or WebP, max 5 MB).A cover or screenshot file that doesn't exist:
✖ Cover image not found: <path>/✖ Screenshot not found: <path>.A cover or screenshot that isn't a PNG, JPEG, GIF or WebP, or is over 5 MB.
An empty folder:
✖ No files found in <dir>.
Collects every file under
<dir>, recursively, exceptgame.jsonat the top level of<dir>. Everything else goes into the bundle, including hidden files and any cover or screenshot images kept in the folder. The platform silently skips__MACOSX/,.DS_Store,Thumbs.dbanddesktop.ini, but a file type it doesn't allow gets the bundle rejected, so publish a clean build folder.New game: creates the game, uploads the cover, then the screenshots. Update: uploads the new cover if one was given, then any screenshots.
Zips the files and uploads them as an
html5build, then completes the upload.Waits up to 30 seconds for the verdict. With
--no-waitit checks the status once instead.
The CLI always uses the html5 runtime. To upload for another runtime, use the Publisher SDK (runtime option) or the REST API.
Output
Creating a game:
$ arcadey publish ./dist --cover ./art/cover.png --title "Space Miner" --category arcade
↑ Publishing "Space Miner" (14 files)…
✓ Created game 7c1f2a90-5b3e-4d21-9f0a-1e2d3c4b5a69 (space-miner-3fa2c1)
Update it later with: arcadey publish ./dist --game 7c1f2a90-5b3e-4d21-9f0a-1e2d3c4b5a69 (or add "id": "7c1f2a90-5b3e-4d21-9f0a-1e2d3c4b5a69" to game.json)
✓ Published: space-miner-3fa2c1
Play: https://…?t=…Updating a game:
$ arcadey publish ./dist --game 7c1f2a90-5b3e-4d21-9f0a-1e2d3c4b5a69 --changelog "Faster levels"
↑ Publishing a new version of 7c1f2a90-5b3e-4d21-9f0a-1e2d3c4b5a69 (14 files)…
✓ Published: space-miner-3fa2c1
Play: https://…?t=…Still in review when the wait ends (exit 2):
… Uploaded (space-miner-3fa2c1), still in review. Check `arcadey status 7c1f2a90-5b3e-4d21-9f0a-1e2d3c4b5a69`.Rejected (exit 1):
✖ Rejected in moderation. Run `arcadey status 7c1f2a90-5b3e-4d21-9f0a-1e2d3c4b5a69` for details.The printed play link carries a ticket that expires after 15 minutes. The permanent page is https://coolgptgames.com/game/<slug>.
If a step fails after a new game was created (for example the bundle is rejected by the upload validator), the CLI tells you how to retry without creating a second game:
✖ <reason> (game 7c1f… was created — retry with publishVersion("7c1f…", …) / `arcadey publish --game 7c1f…`)
The game was created (id 7c1f…). Retry with: arcadey publish ./dist --game 7c1f…Updates to a live game
When you update a game that's already live, the game stays published and keeps serving the previous version while the new one is reviewed. If the new version is rejected, only that version fails and players keep the old one.
The CLI waits for the verdict on the version it just uploaded, not the game's status, so the output and exit code mean the same thing for a first publish and for an update:
✓ Published(exit0): the new version was approved and is now live.✖ Rejected in moderation(exit1): the new version was rejected. On an update, players still get the previous version.… still in review(exit2): no verdict yet, including a version held for a human moderator. Runarcadey status <gameId>later and look at the newest version (livemeans approved,failedmeans rejected), or use thegame.approved/game.rejectedwebhooks (see Webhooks).
Other notes:
Each run uses one of your hourly uploads (see Errors & limits).
Updating a game that has been delisted or taken down fails with
(409), and a deleted game with(404).
arcadey status <gameId>
Shows a game's status, earnings, open reports, and every version (newest first) with its moderation reasons and notes, whether the decision was automated or made by a moderator.
$ arcadey status 7c1f2a90-5b3e-4d21-9f0a-1e2d3c4b5a69
Space Miner — published (id 7c1f2a90-5b3e-4d21-9f0a-1e2d3c4b5a69, slug space-miner-3fa2c1)
Earnings $12.4 · open reports 0
v3: live
v2: live
v1: failed
reasons: blank_screen
notes: Rejected by automated moderation.Version statuses: uploaded or scanning (being checked), passed (waiting for a moderator, or approved but not served), live (approved; earlier approved versions keep showing live too, and status doesn't mark which one the game currently serves), failed (rejected). The game-level status is one of draft, scanning, pending_review, published, rejected, delisted or dmca_removed.
arcadey earnings
Your lifetime and last-30-day earnings, available balance and total plays. Earnings are in US dollars; coins aren't money and aren't included.
$ arcadey earnings
Lifetime: $41.2
Last 30d: $9.85
Available: $41.2 (min $50 to cash out)
Total plays: 5120arcadey help
Prints the usage summary. Same as --help or -h.
Exit codes
Code | When |
|---|---|
| Success. For |
| Any failure: bad usage, unknown command or flag, not logged in, key rejected, missing cover or file, API error, or the version |
|
|
Errors go to stderr, prefixed with ✖. API errors include the HTTP status, for example ✖ Upload limit reached (429).
CI usage
Create the game once from your machine (it needs a cover), note its id, then let CI publish updates to it with --game. Store the key as a secret and the game id as a variable.
# .github/workflows/publish.yml
name: Publish update to Cool GPT Games
on:
push:
tags: ["v*"]
jobs:
publish:
runs-on: ubuntu-latest
env:
ARCADEY_API_KEY: ${{ secrets.ARCADEY_API_KEY }} # a key with the "publish" scope
GAME_ID: ${{ vars.COOLGPTGAMES_GAME_ID }}
CHANGELOG: "Release ${{ github.ref_name }}"
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
- run: npm ci && npm run build # produces ./dist
# Install or link the arcadey CLI here (see Installation).
- run: arcadey whoami
- name: Publish new version
run: |
set +e
arcadey publish ./dist --game "$GAME_ID" --changelog "$CHANGELOG"
code=$?
if [ "$code" -eq 2 ]; then
echo "Uploaded; still in review."
exit 0
fi
exit "$code"Tips:
The job fails (exit
1) on a rejection or any error. Exit2(still in review) is treated as success above; drop that check if you'd rather fail the job.Passing the changelog through an environment variable, as above, keeps tag names or commit text from being interpreted by the shell.
If
game.jsonsets"id", you can leave out--game.Screenshots listed in
game.jsonare added on every run. Remove them fromgame.jsononce they're uploaded, or the gallery fills up (8 at most).Use a dedicated key per pipeline so you can revoke it on its own. See API keys & scopes.
ARCADEY_API_URLisn't needed for production. If you set it, it overrides the URL saved by an earlierarcadey loginon the runner.
See also
Publisher SDK: the library behind the CLI - Packaging & publishing your game: what a bundle must contain - Errors & limits: messages you might see and their causes - MCP server: the same operations for AI assistants