CLI

Embed
Preview of the card
<a href="https://coolgptgames.com/docs/cli"><img src="https://coolgptgames.com/docs/_api/public/orgs/062b11d9-2b86-4627-9e86-96816a67d7a1/spaces/07d97e67-936e-4223-9eaf-6998a46fee24/pages/2a67f410-0b53-411c-84b6-9d3d0d69e3f7/social-card.svg?v=3e7ae33eec8b" alt="CLI" width="600" height="315"></a>

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/cli

Or run it without installing:

npx -y @coolgptgames/cli --help

Requirements: Node.js 18 or later. The published package is a self-contained bundle — no TypeScript toolchain or build step. Check it works:

arcadey --help

Authentication

The CLI authenticates with an API key. Create one in Dashboard → API keys (see API keys & scopes).

Commands

Scope the key needs

login, whoami, games, status, earnings

read

publish

publish

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

--key <key>

ARCADEY_API_KEY environment variable

apiKey in the config file

none: the command fails with "Not logged in"

API URL

--url <origin>

ARCADEY_API_URL environment variable

baseUrl in the config file

https://api.coolgptgames.com

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 a 0700 directory) and resets the mode to 0600 each time it writes an existing file.

  • baseUrl is the API URL that was in effect when you logged in (from --url, ARCADEY_API_URL or the default).

  • Running login again 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 help

Global flags, accepted by every command:

Flag

Meaning

--key <api-key>

Use this key for this run only (ignored by login, which takes the key as its argument)

--url <api-origin>

Use this API origin for this run only

--help, -h

Print the usage summary and exit 0, whatever else is on the line

Flag syntax

  • Flags can go before or after the command and its arguments: arcadey --url https://api.coolgptgames.com games and arcadey games --url https://api.coolgptgames.com are the same.

  • Flags that take a value accept --flag value or --flag=value. With the space form, the next argument must not start with --; otherwise the CLI reports --<flag> needs a value.

  • --realtime, --no-wait and --help never take a value, so they can't swallow a following argument (arcadey publish --realtime ./dist works). Writing --realtime=true is 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`. (exit 1).

  • arcadey with no arguments, arcadey help, arcadey --help and arcadey -h all 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

--url <origin>

API origin to use and save. Falls back to ARCADEY_API_URL, then the URL already in the config file, then https://api.coolgptgames.com.

$ 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 Golf

Slugs 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 <path>

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.

--game <id>

Publish a new version of this existing game instead of creating one. Overrides "id" in game.json.

--title <title>

Title for a new game. Overrides title in game.json. Ignored for updates.

--category <slug>

Category for a new game, for example arcade. Overrides category in game.json. Ignored for updates.

--changelog <text>

Release notes for the new version, up to 2,000 characters. Updates only. Works together with a gamification block in game.json.

--realtime

Enable multiplayer for this version (same as "realtime": true in game.json)

--no-wait

Don't wait for review: check the status once and return

--key, --url

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

id

Updates

The existing game to publish a new version of. --game overrides it. Leave it out to create a new game.

title

New games

Required for a new game unless you pass --title

category

New games

Category slug

description

New games

instructions

New games

How to play

cover

New games and updates

Path relative to <dir>. --cover overrides it.

screenshots

New games and updates

Paths relative to <dir>. Each is added to the game's gallery (at most 8 per game), including on every update.

realtime

New games and updates

true enables multiplayer. --realtime also enables it.

connectHosts

New games and updates

External https:// hosts the game may fetch from at runtime, at most 10. See Packaging & publishing your game.

gamification

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

  1. Reads game.json, then checks everything it can before calling the API, so a mistake never leaves a stray draft game behind:

    • --changelog without 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>.

  2. Collects every file under <dir>, recursively, except game.json at 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.db and desktop.ini, but a file type it doesn't allow gets the bundle rejected, so publish a clean build folder.

  3. New game: creates the game, uploads the cover, then the screenshots. Update: uploads the new cover if one was given, then any screenshots.

  4. Zips the files and uploads them as an html5 build, then completes the upload.

  5. Waits up to 30 seconds for the verdict. With --no-wait it 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 (exit 0): the new version was approved and is now live.

  • ✖ Rejected in moderation (exit 1): the new version was rejected. On an update, players still get the previous version.

  • … still in review (exit 2): no verdict yet, including a version held for a human moderator. Run arcadey status <gameId> later and look at the newest version (live means approved, failed means rejected), or use the game.approved / game.rejected webhooks (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: 5120

arcadey help

Prints the usage summary. Same as --help or -h.

Exit codes

Code

When

0

Success. For publish: the version you uploaded was approved and is live.

1

Any failure: bad usage, unknown command or flag, not logged in, key rejected, missing cover or file, API error, or the version publish uploaded was rejected

2

publish uploaded fine, but the version is still in review (the 30-second wait ran out, or --no-wait)

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. Exit 2 (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.json sets "id", you can leave out --game.

  • Screenshots listed in game.json are added on every run. Remove them from game.json once 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_URL isn't needed for production. If you set it, it overrides the URL saved by an earlier arcadey login on the runner.

See also

Updated