# CLI reference

This page lists every `gamestage` command, what it does and what it leaves
behind. It follows `gamestage <command> --help`, and where the two disagree the
tool is correct.

This page writes every command as `gamestage <command>`. That is the form the
shell installer puts on your PATH. Running from npm it is `npx gamestage
<command>`, and running the downloaded bundle it is `node gamestage.mjs
<command>`. `gamestage start` reports which form it is answering to.

`--json` is a global option and goes before the command: `gamestage --json
verify`. Use it when a script or an agent will branch on the result instead of
reading prose.

Commands marked "Needs sign-in" reach Backstage, so run `login` first. Signing
in is public and self-serve, so a new account stops at approval instead: the
commands answer `access_pending` until a person approves it.

## Commands

### `start`

Reads the current directory and names the right next step. Run it when you do
not know which command to run; it works at every stage of a migration.

It prints the version it is, and reaches nothing over the network to do it. Read
that number first when a command refuses something this page says it accepts: a
cached bundle presents as a documentation error rather than as an old tool.

### `doctor`

Checks Node, the bundled schema, and whether a manifest is present. Exits `1`
when something blocking is wrong, `0` otherwise.

### `inspect [target]`

Reads an existing game and produces an inspection report. `target` is a game
file or a project directory, defaulting to `.`.

| Option | Meaning |
| --- | --- |
| `--write [path]` | Write the generated manifest, defaulting to `gamestage.yaml` |
| `--force` | Replace an existing manifest, losing whatever it holds |

The report also names how the prototype already sits on the screen: `screen`,
`canvas`, `card` or `page`, with the file and line it read that from, and
"(possible)" when it is a guess rather than a certainty. `--write` puts it in
the manifest as `experience.layout`, exactly as `create --layout` would have.
See [layout and loading](/docs/layout-and-loading) for what each value means.

The inspection is read-only and registers nothing, however many times you run
it. Only `--write` touches the disk, and it refuses to overwrite an existing
manifest without `--force`, because a later manifest may hold a complete
container the inspection cannot reconstruct. When the CLI is signed in,
`--write` also registers the manifest it wrote, after writing it, so the id in
your `gamestage.yaml` is the id your workspace holds; a failed registration
does not undo the file.

### `formats [format]`

Explains the formats before you choose one: what the fan does, what it suits,
what the Engine keeps secret, what a producer writes in Studio, and a deployed
game of that format to play. No account needed.

```
gamestage formats          every format you can build, then the ones with no Engine yet
gamestage formats hunt     one format in full
gamestage formats trivia   a format with no Engine yet, and how to ask for it
```

If none fits, `gamestage suggest "what you want to build"` tells us.

### `create`

Starts a new game, choosing its authority before scaffolding anything. Run by
hand with no `--format` or `--name`, it asks for them, then for who runs the
game and their privacy policy for the consent card. An agent, a pipeline or
`--json` is never asked: with no format it prints the menu and exits.

| Option | Meaning |
| --- | --- |
| `--name <name>` | The game's name |
| `--format <format>` | How the game is played, and so the Engine rules it runs. `gamestage formats` explains each |
| `--brand <name>` | Who runs the game, named when a fan is asked for consent |
| `--privacy-url <url>` | Your privacy policy's address, linked from the consent card |
| `--here` | Write into this folder even when it holds other things |
| `--host <host>` | Where it runs: `fullscreen` (the default), `article-launch`, `article-embed` or `app-webview`. See [layout and loading](/docs/layout-and-loading) |
| `--layout <layout>` | How it sits on the screen: `screen`, `canvas`, `launch`, `card`, `page` or `sheet`, defaulting from the host and format |
| `--write [path]` | Manifest path, defaulting to `gamestage.yaml` |
| `--force` | Replace files that are already there, losing what they hold |

It writes `gamestage.yaml`, `index.html`, `gamestage.js`, `gamestage-player.js`
and `favicon.svg`, and scaffolds a playable page for `hunt`, `push`, `group`,
`predict`, `bingo` and `shoot`.

**Where it writes.** Into the current folder when that folder is empty or
already holds a page (`index.html`) or a manifest. Anywhere else, such as a
folder holding another project, it makes a folder named after the game and
writes there, so your other files are left alone. `--here` writes into the
current folder regardless. `--json` reports the folder as `directory`.

**It never replaces a file it did not write.** A file already in the directory
is kept and named in the output, so running `create` beside a prototype gives
you the manifest and the clients while your own page is left alone. A
`gamestage.yaml` that is already there stops the command outright, because that
file holds a game and a second one minted beside it would leave the directory
with two. `--force` overrides both.

### `client`

Writes the browser clients into your project in the form a browser can load:
`gamestage.js`, the Gamestage client, and `gamestage-player.js`, the smaller
player API client. Both are regenerated when the command runs again, so keep
game logic out of them.

| Option | Meaning |
| --- | --- |
| `--out <dir>` | Where to write them, defaulting to the current directory |

### `dev`

Serves your round locally, through the same Engine rules that serve it in the
cloud. Prints `window.GAMESTAGE_API` and `window.GAMESTAGE_GAME` for the page.
There is no watch mode: stop and restart after changing the container. Needs
sign-in.

| Option | Meaning |
| --- | --- |
| `--port <port>` | Local HTTP port, default `4000` |
| `--manifest <path>` | Manifest the round is read from, default `gamestage.yaml` |
| `--fixture` | Serve the contract fixture instead of your own round |
| `--scenario <name>` | Which fixture scenario, for contract work |
| `--as <player>` | Play as a named signed-in player, with a keypair minted for this run |

With no `backend.round`, which is the normal case because a game's rounds are
written in Studio, `dev` plays a sample round for the game's format, the same
one `review` plays, and says it is a sample. Nothing is written for it, so a
deploy cannot publish it. With no readable manifest at all, `dev` serves the
contract fixture and says so. The fixture is useful for wire work. It is not
your game.
`--as` mints a temporary keypair and loopback issuer; the manifest validator
refuses that issuer in a deployed game.

### `verify`

Runs the game against a real Engine and proves the migration is done. Needs
sign-in. It runs a set of checks: whether the round played here is honestly
this game's own, whether the attempt cap was chosen rather than defaulted,
whether the answers are absent from everything a deploy would publish,
whether every producer setting the page offers is read by the game, whether a
generated client is present and the game reaches Monterosa through it rather
than around it, whether a deadline comes from the server's clock, and, when a
browser is available, whether the screen shows what the server decided and
notices the connection going and coming back.

The first check walks the publish set rather than the named page, using the same
call `deploy --dir` uploads with, so a manifest, data file or source map holding
the answers beside a clean page is a fault rather than an oversight. Files are
read on their contents rather than their extension; binaries and very large files
are listed but not opened.

| Option | Meaning |
| --- | --- |
| `--file <path>` | The game to verify, normally `index.html` |
| `--manifest <path>` | Manifest the round is read from, default `gamestage.yaml` |
| `--dir <path>` | The directory a deploy would publish, default the game's own |
| `--no-browser` | Skip the browser proof |

### `review`

Plays the built game against a local Engine, in a headless browser at phone
size, and gives advice on whether fans will come back. Run it from the game's
folder before `deploy`:

```bash
npx gamestage@latest review --file dist/index.html
```

Every line is `ok`, `advice` (with the rule and a fix) or `skipped` (with why).
It never fails and always exits 0: these are judgements, not faults. The rules
and the research behind them are in [Design a game fans come back to](/docs/game-design).

| Check | What it looks at |
| --- | --- |
| First answer | Seconds and taps from arriving to the first answer, against 30 seconds and 3 taps |
| Play next | Whether Play next (or a greyed Come back tomorrow) is the main button after a round |
| Story | Whether the story opens on its own, whether it runs past 3 slides, and whether a story button opens anything. Found by what it does: a full-screen layer with a row of progress segments, whether it is Gamestage UI's Story or the game's own |
| Come back | Whether the result screen shows a streak or when the next round opens |
| Share | Whether the page declares a share card and offers a Share button |
| Ties | How many distinct scores a round can award, from its scoring rules |
| Options | Six options for a one-answer pick |
| Rounds | Rounds ready in the live pool, when you are signed in and the game is deployed |

A game whose rounds live in Studio has no round in its manifest, so the tie
estimate uses a representative round of its format and says so. The game's own
`gamestage.settings.json` stands in for Studio, as it does for `dev --serve`.

| Option | Meaning |
| --- | --- |
| `--file <path>` | The game's page, default `index.html` |
| `--manifest <path>` | The manifest, default `gamestage.yaml` |

Set `GAMESTAGE_REVIEW_SCREENSHOT=/tmp/result.png` to save the result screen as
it was reviewed, to check the advice by eye.

### `suggest [what]`

Tells us about a format or feature that does not exist. No account needed, and
the right moment is the one where you ran out of road.

**Pipe it in when it runs to more than one line.** A newline inside a quoted
argument ends the command at your shell, so a report typed across several lines
arrives as its first line and nothing says so:

```
gamestage suggest --send < suggestion.txt
```

The argument still works and is still the shortest way to send one line.

Without `--send` it prints the exact payload and sends nothing. That payload is
four strings: what you were building, the format that came nearest, the CLI
version, and a marker saying it is a suggestion. **Never the game.** No
manifest, no round, no answers, no source, and there is nowhere in the shape to
put one.

| Option | Meaning |
| --- | --- |
| `--closest <format>` | The format that came nearest, if one did |
| `--send` | Send it, having seen what it says |

### `issue [what]`

Reports something that went wrong. No account needed, and it lands in the same
backlog a suggestion does, titled so a bug is not read as a wish.

**Pipe it in when it runs to more than one line.** A newline inside a quoted
argument ends the command at your shell, so a report typed across several lines
arrives as its first line and nothing says so. That is how two real reports were
lost. Write the report to a file, or pipe it from whatever wrote it:

```
gamestage issue --send < report.txt
type report.txt | gamestage issue --send    # Windows
```

The argument still works and is still the shortest way to send one line. Nothing
here opens a file of its own: the pipe carries what you chose to put in it, and
`--game` is an id you type rather than a file the command reads.

Without `--send` it prints the exact payload and sends nothing. That payload is
six strings: what went wrong, the game you named, the CLI version, the node
version, the operating system, and a marker saying it is a bug report. **Never
the game.** No manifest, no round, no answers, no source, no file paths, and
there is nowhere in the shape to put one. `--game` is the id you type, not a
file the command reads.

| Option | Meaning |
| --- | --- |
| `--game <id>` | The game it happened on, if it was about one |
| `--send` | Send it, having seen what it says |

### `manifest validate [path]`

Validates a manifest and reports the build stage its evidence supports. Path
defaults to `gamestage.yaml`. `--level <level>` requires a conformance level
from L0 to L5 and exits `1` when the manifest does not reach it.

### `manifest register [path]`

Registers an existing manifest with Backstage. Safe to repeat, and needed
when a manifest was created while signed out. Needs sign-in.

### `login`, `logout`, `whoami`

`login` signs in, and creates an account when none exists: there is no
separate signup and no website step first. It starts a WorkOS device flow, so
a person has to approve it in a browser. `logout` forgets the stored session.
`whoami` prints who the CLI is signed in as.

| Option | Meaning |
| --- | --- |
| `--workspace <id>` | The workspace you expect to land in, from the welcome screen |

### `link github`

Links your GitHub account through a second device flow, which a first deploy
requires. Needs sign-in.

### `open <game> [where]`

Opens the game in the browser. `where` is `play` (the default: the game as a
fan plays it), `studio` (its Studio project, where rounds and words are
written) or `stage` (its page on gamestage.ai). With `--json`, or on a machine
with no browser, it prints the address instead. A game not yet deployed has no
Studio project, and the command says so.

### `docs [chapter]`

Prints a chapter of this documentation in the terminal, fetched from
gamestage.ai so it is never out of date. With no chapter it lists them.
`gamestage docs game-formats` and `gamestage docs cli` are the two most used;
`formats`, `deploy` and `manifest` work as short names. No account needed.

### A newer version

After a command you run by hand, the CLI checks npm at most once a day and,
when a newer `gamestage` is out, prints one line saying so and how to update.
It never checks for `--json`, a pipe, CI or `GAMESTAGE_NO_UPDATE_CHECK=1`.

### `status [game]`

`gamestage list` is the same command: with no game named, it lists your games.

What is deployed, what state it is in and which limits are near. One game when
named, the whole workspace when not. Needs sign-in.

Name a game and it also says whether that game is serving as dev or prod, read
with `effectiveStage` rather than inferred from `promote` having succeeded. On
dev it names which of the two gates is still shut: your own approval
(`gamestage promote`), your workspace's plan, or both. An archived game is told
neither, because it serves nobody and `promote` would refuse it.

The workspace list does not repeat that per row. Every row would carry the same
sentence, and a game's state and its serving stage are different questions, so
"live, dev" side by side reads as a contradiction rather than as two answers.
`--json` does carry it for every game, because an agent branches on a field
rather than reading a column.

### `deploy <game>`

Publishes a snapshot to a Playground, and the round it puts on. Needs sign-in.

| Option | Meaning |
| --- | --- |
| `--file <path>` | The game's HTML, uploaded before the deploy gate runs |
| `--dir <path>` | The game's directory, uploaded with relative paths |
| `--manifest <path>` | Manifest the round is read from |
| `--revision <revision>` | Manifest revision being published |
| `--hash <hash>` | Content hash of the snapshot |
| `--level <level>` | Evidenced conformance level, L0 to L5 |
| `--prize` | The game offers a prize, which is a paid plan only |
| `--notes <text>` | What changed, said in the deploy announcement and in `logs` |
| `--no-check` | Skip loading the published page in a browser afterwards |
| `--allow <path...>` | Publish a file the deploy gate suspects; never clears a certain finding |

`--dir` uploads everything under the directory, withholding the manifest
(`gamestage.yaml` or `.yml`), environment files (`.env`, `.env.local`,
`.env.*`) and the downloaded CLI (`gamestage.mjs`, `cli.mjs`), and removes
stale files an older deploy left. The generated clients, `gamestage.js` and
`gamestage-player.js`, are walked but not uploaded either: whatever imports
them has that import rewritten to the hosted client at a pinned version
instead. Upload happens before the deployment gate, so a refused deploy can
leave new files in storage without making the game live. The picture that
stands for the game in Stage comes from its format and is there from
registration, so a deploy has nothing to run and nobody has a brief to write.

**A first deploy needs no round.** It puts the game online and creates its
project in Studio, which is where its rounds are then written. With no round
yet, deploy says so, `No round yet: add one in Studio`, with the link to the
game's Studio project, and skips the browser check, because a page with
nothing on is not a broken page. The round appears without another deploy
once a producer writes it.

### `reference push <name> <file>`

Uploads the private values a round is scored against. `name` is what a producer
types in Studio, such as `the-limit/careers.json`, and `file` is the JSON file
holding it. Needs sign-in.

The file is read and validated on your machine before anything leaves it,
because the server never sees the bytes: they go to a private prefix from a
signed URL. One push writes two objects, the pointer at the name a producer
uses and a copy keyed by the digest of those exact bytes. A round records the
digest it was provisioned against, so asking what a round was scored on is a
lookup rather than an argument.

A round provisioned after the push uses the new values. A round already being
played keeps the values it opened with, because a fan's page load will not
re-value a round underneath them, and the change goes on at the next deploy or
explicit refresh. [Dataset](/docs/data) has the file's shape and what
must never go in it.

| Option | Meaning |
| --- | --- |
| `--drop-scoring` | Push anyway, when the file would remove a measure's `scoring` block that a live round depends on |

Without `--drop-scoring`, a push that would stop a measure scoring rounds is
refused and names the measure: see [Scoring a push round by how close it
came](/docs/data#scoring-a-push-round-by-how-close-it-came).

### `reference check <file> [keys...]`

Reports what a round would fail on, before a producer finds out by having one
refused. It prints how many values each measure holds, and when you name keys
it says which of them are absent.

A key the dataset does not hold refuses the whole round rather than
scoring as nothing, so one typo costs the round. This is that same check, run
against the file, by the person who can fix it. It names a missing key and
never prints what a present one is worth. Nothing leaves the machine and no
account is needed.

### `sources`

Lists where a game's real-world values can come from, and which of them your
workspace can use now. Needs an approved account.

```
npx gamestage sources
```

For each source it prints what it offers, what it covers, what it costs, and
the licence position, then its state for you: **connected** (data from it is
already in your workspace), **available**, **available, needs a key** (sign up
with the provider, then connect your key through the page it gives you), or
**not available to you**, with the reason. `--json` prints the same for an
agent.

Run it before designing a game that needs real figures. It never asks for a
key, and you should never paste one into a chat: a key goes in through a
Gamestage page.

### `sources connect <source>`

Prints the Stage page where you add your own key for a source, such as
`sportmonks`. Needs an approved account for the page itself.

```
npx gamestage sources connect sportmonks
```

Open the link in your browser and paste the key there. Gamestage checks it with
the provider once, then stores it in AWS Secrets Manager for your workspace and
uses it only to fetch your data on a schedule. The command never takes the key,
not as an option and not as input, so a coding agent running it never sees it.
A wrong key is refused on the page with the reason, and nothing is stored.

**At your own terminal, from 1Password.** If you cannot use the page, you can
read the key from your own 1Password instead:

```
npx gamestage sources connect sportmonks --from-op "op://Private/Sportmonks/credential"
```

| Option | Meaning |
| --- | --- |
| `--from-op <reference>` | A 1Password secret reference, `op://vault/item/field`. The CLI runs `op read` itself and sends the key straight to Gamestage |

It needs the 1Password CLI, signed in, and 1Password asks you to approve the
read. The key is never printed, logged or written to disk: you see
"Connected", its last four characters, and the competitions your plan covers.
There is no way to pass a key itself, as an option, on input or in an
environment variable. This route is for a person at their own terminal: a
coding agent must never run it or write the reference for you. Every attempt
to connect a key, by either route, is recorded against your workspace without
the key, and five attempts an hour is the limit.

### `sources status <source>`

Says whether your key for a source is connected, when it was connected, and
which competitions your plan covers. It never shows the key.

```
npx gamestage sources status sportmonks
```

### `sources disconnect <source>`

Removes your key for a source. Its scheduled pulls stop at once; data already
in your workspace stays. Connect again with `sources connect`.

```
npx gamestage sources disconnect sportmonks
```

### `round push <game>`

Writes the round in your manifest into Studio, where a producer owns it from
then on. Needs sign-in.

| Option | Meaning |
| --- | --- |
| `--manifest <path>` | Manifest the round is read from, default `gamestage.yaml` |
| `--yes` | Do not ask before replacing what a producer has typed, default off |

It prints what is about to go in, then asks. Without a terminal and without
`--yes` it refuses and writes nothing, because it replaces what a producer
typed and nobody was there to be asked. That refusal comes before it reaches
the control plane, so an unattended run is turned away whether or not the CLI
is signed in.

What travels differs by format. A `push` round sends only its public half: an
entry's point value would be broadcast to every connected browser, so the
values stay in your dataset where only the Engine reads them. A `hunt`
or `group` round sends its answer too, into a field Studio withholds from a
fan's browser. A `predict` round is refused by name, because a prediction's
question and options are the platform's own and are authored in Studio itself.
A `shoot` round sends its settings; the keeper is the Engine's and never
travels.

There is a push and there is no pull. Once the round is in Studio a producer's
edits live there, and a pull would put them back into a manifest, which is the
one thing a manifest must not hold. `status <game>` reports the round a game is
serving without writing anything.

### `round seed <game> --count <n>`

Builds candidate rounds from the game's connected dataset, then creates one
new element per round in its current Studio edition. Needs sign-in and a game
that has been deployed with a dataset connected. Existing rounds are preserved;
a producer reviews and publishes the new elements in Studio, where each is
named and annotated like every other round. This does not publish them or
refresh the Engine.

```sh
gamestage round seed points --count 5 --measure season_points --dry-run
gamestage round seed points --count 5 --measure season_points --yes
gamestage round seed almanac --count 3 --measure assists --position Goalkeeper --dry-run
```

| Option | Meaning |
| --- | --- |
| `--count <n>` | Required, 1–80 distinct rounds |
| `--measure <key>` | Dataset column; required when the dataset has several measures |
| `--position <position>` | Exact position filter, ignoring case |
| `--team <team>` | Exact team filter, ignoring case |
| `--dry-run` | Print complete candidate rounds as JSON, without writing files or Studio elements |
| `--yes` | Create the rounds without the interactive consent prompt |

Dry-run is explicit, including when stdout is redirected. This follows the CLI's
existing convention: a non-interactive stdin requires `--yes` to write, while
`--dry-run` needs no consent. Dry-run still reads the connected dataset through
the authenticated control plane. The private dataset stays on the server.

For **push**, each position gets a slate of up to four entries (at least two,
at most eight positions). The target has **one to five exact squads**, counting
all combinations of one pick per slate, including different picks with equal
values. Values never go onto the element; the selected measure and target do.

For **computed hunt**, each board has four distinct players and asks for the
highest figure. “Clearly separates” means the answer is strictly greater than
the runner-up and **at least 1.5 times its figure**. A positive answer against
three zeroes qualifies; an all-zero board or a tie for highest never does.
The element carries dataset keys and the highest-value rule, so the Engine
recomputes the answer during provisioning. Dataset changes can invalidate an
old candidate's separation; the preview describes the data read at that time.

Filters run before selection. Missing names, missing push positions, insufficient
entries or ambiguous measures produce an explanation. Selection is reproducible
for the same dataset and options. Search stops after 5,000 candidate attempts;
if it cannot fill the requested batch, nothing is written. Reduce `--count` or
widen the filters. Re-running a successful seed creates additional elements.
An edition's 80-element limit still applies, including existing elements.
If a write fails partway through, the command reports how many were created;
inspect Studio before retrying. The control plane must include this command's
candidate endpoint and additive round-writing support.

### `round refresh <game>`

Reads Studio and the dataset again now and provisions what it finds, rather
than waiting for the next fan's visit. Needs sign-in.

It changes nothing a producer wrote: it re-reads and re-provisions exactly as a
fan's visit past the cooldown would, and prints the last lines the game logged
so you see what it did. Use it after pushing a new dataset with
`reference push`, or when a producer's edit should reach fans before anyone
opens the game.

### `graphic templates`

Lists the screens a graphic can be cut for. No account needed, no network call.

```
gamestage graphic templates
```

| Template | Type | Made for |
| --- | --- | --- |
| `lowerthird` | Leaderboard | A lower third over live video, for TV and streaming. Transparent, three rows, keyed |
| `panel` | Leaderboard | A right-hand panel pulled out over the picture, for TV and CTV. Eight rows |
| `bigscreen` | Leaderboard | A stadium screen or a Jumbotron. Ten rows at large type. The default |
| `vertical` | Leaderboard | A portrait panel in a concourse, 9:16 |
| `promo` | QR | A QR code that sends a crowd to the game. No live data |

A template sets the graphic's size, type scale and safe margins. It does not
set its colours: that is your stylesheet, and `graphic dev` is how you write
one.

### `graphic create <game>`

Puts the game's leaderboard on a screen. The control plane composes the graphic
in Monterosa Live Graphics and hands you back an address.

```
gamestage graphic create kickoff --template bigscreen --consent
```

```
On screen
  Game      Kick Off (kickoff)
  Template  bigscreen - A stadium screen or a Jumbotron
  Made for  A crowd looking up at a screen from a long way away
  Address   https://<space-domain>/r/embed.html?slug=5aItiwK&bigscreen=true
```

| Option | What it does |
| --- | --- |
| `--template <id>` | Which screen to cut it for. Default `bigscreen`. See `graphic templates` |
| `--consent` | Confirms the people named agreed to appear on a screen. No default |

Open the address on the screen. It is a web page, it needs no sign-in, and it
follows the game's leaderboard by itself: fans play, the graphic redraws, and
nothing is redeployed for a new round.

**`--consent` has no default and `--json` does not imply it.** A leaderboard
puts fans' names on a screen other people can see, and somebody has to say
those people agreed to that. Your account is stored on the graphic as the one
who confirmed it. A flag that defaulted to true would make that record say
somebody agreed when nobody was asked, which is worse than having no gate.

| Refusal | What to do |
| --- | --- |
| `Nobody has said these names can be shown` | Run it again with `--consent`, once that is true |
| `No template called <id>` | Run `gamestage graphic templates` |
| `This game has not been deployed` | A graphic follows the game's own leaderboard, and a QR code sends people to the game's page. Neither exists before a deploy. Run `gamestage deploy <game> --dir .` |
| `This deployment cannot make graphics` | The control plane holds no Live Graphics key. That is a deployment setting, not anything about your game |

**Your machine never holds a graphics credential.** Live Graphics takes one key
per deployment, and that key can write to every graphic in it. The control
plane holds it, checks the game is yours, and makes the call. Nothing is
written to your game's files and nothing about your game changes.

### `graphic list <game>`

The graphics this game has, their ids, and where to watch them.

```
gamestage graphic list kickoff
```

Each row carries the template, the graphic's id, its address, and who confirmed
the names could be shown. The id is what `graphic remove` takes.

### `graphic remove <game> <id>`

Takes a graphic off the air. The address stops answering, so take the screen
down first if somebody is watching it.

```
gamestage graphic remove kickoff 42a57df0-6caf-46a8-ad5f-a7aeb8a7030f
```

A graphic this game does not hold is refused, whoever asks. That refusal is the
only thing between one creator and another customer's screen, because the key
that deletes a graphic has no tenancy of its own.

### `graphic dev`

Serves the Live Graphics renderer on your own machine, drawing a made-up
leaderboard with your stylesheet applied, so you can restyle a graphic without
looking at a production screen to find out whether a colour was right. Needs no
account.

| Option | Meaning |
| --- | --- |
| `--css <path>` | The stylesheet to preview, default `graphic.css` |
| `--port <port>` | Port to serve on, default `4321` |
| `--rows <rows>` | How many rows to draw, default `10` |
| `--bigscreen` | Draw it as a big screen rather than an overlay, default off |
| `--accent <colour>` | Accent colour, as `#rrggbb` |
| `--renderer <url>` | Where the renderer is served from |

**The page is the real renderer, not a copy of it.** The markup and the script
come from the deployed graphic, so what you see is the thing itself rather than
something that resembles it and quietly stops matching.

Save the stylesheet and the page picks it up within five seconds. There is no
watcher and no reload: a graphic re-reads its stylesheet on every poll, so this
gets live editing by not getting in the way of behaviour the renderer already
has.

It reads no graphic and writes none. The rows are invented, so there is no
production board to get wrong.

**The structure is not yours to change, only the paint.** A graphic's type is
built by Monterosa and its markup is fixed, which is what stops a stadium
screen rendering something nobody designed. Your stylesheet is applied over it.

### `graphic push <game>`

Hands a producer the address of your stylesheet, so the graphic they composed
can follow it. Needs an approved account.

| Option | Meaning |
| --- | --- |
| `--css <path>` | The stylesheet to publish, default `graphic.css` |

```
gamestage graphic push kickoff --css graphic.css
```

```
Ready for a producer

  Game        Kick Off (kickoff)
  Stylesheet  graphic.css (3,204 of 10,000 characters)
  Address     https://<space-domain>/games/kickoff/graphic.css
```

**Nothing is written to Live Graphics, and that is deliberate rather than a
limitation we are working around.** Writing to a graphic needs a key belonging
to a whole deployment, or a Studio operator's own token, and a creator has
neither. Giving a creator one would make Gamestage the only thing standing
between them and another customer's stadium screen.

So the stylesheet travels the way the leaderboard already does. `deploy`
publishes it along with every other file in the directory, a producer pastes
its address into the graphic beside the feed address, and the graphic follows
it from then on. You restyle a live screen by deploying. The producer takes it
back by clearing one field.

What this command does is make that usable: it reads the stylesheet, refuses it
if it will not fit, checks the address is actually being served, and prints the
line to hand over.

| It stops with | When |
| --- | --- |
| `No stylesheet to publish` | The path does not exist |
| `The stylesheet is too long for a graphic` | Over 10,000 characters, which is the field Live Graphics stores it in. Minify it |
| `This game has not been deployed` | Nothing is served yet, so there is no address to give |
| `The stylesheet is not being served yet` | Nothing answers at the address, usually because the file was written after the last deploy or sits outside the deployed directory. The playground says 403 rather than 404 for a file that is not there, so the command reports whatever it got rather than guessing |

**Deploy first.** The address only starts working once a deploy has published
the file, and a deploy publishes the directory it is given, so the stylesheet
has to be inside it.

**Live Graphics does not read a stylesheet address.** A producer opens the
address this command prints and copies the stylesheet out of it by hand,
rather than the graphic following it directly. See
[Live Graphics](/docs/live-graphics) for how a graphic gets its style today.

### `endpoint <game> [which]`

Prints where a deployed game answers. `which` is `player`, `control` or
`playground`; all three when omitted. Needs sign-in.

### `logs <game>`

Tails a game's runtime log. Needs sign-in.

| Option | Meaning |
| --- | --- |
| `--limit <n>` | How many lines, newest kept, default `100` |
| `--since <iso>` | Only lines after this timestamp |
| `--level <level>` | `info`, `warn` or `error` |

### `plan <plan>`

Changes this workspace's plan, and so its limits. Takes `runtime`, `live` or
`enterprise`, which a creator reads as Starter, Publisher and Enterprise.
Needs sign-in.

Only `runtime` can be set here. Publisher and Enterprise are arranged with a
person and no payment is captured on this command, so asking for either is
refused with `operator_required` and your workspace is left as it was.

This changes a record. It is not evidence that commercial approval, a
Monterosa org or dedicated infrastructure exists, and none of those can be
created by the service account. See [Plans and limits](/docs/starter-plan).

### `promote <game>`

Approves your own game to move from dev to prod. Needs your account approved.

Succeeds for a game that exists, is not archived and is not already approved:
approving your own game is not a purchase, so there is nothing for a plan to
refuse here. Run it again on an approved game and it reports there is nothing
to do. It does not put the game live on its own. Reaching fans on prod also
needs your workspace on a paid plan, which only Monterosa can do, and the
command tells you which of the two states you are actually in afterwards.

Stage carries the same action, under "Dev and prod" on a game's own page. It
shows both gates, which of them is open, and which of the two states the game
is serving right now.

### `suspend <game>`, `wake <game>`, `archive <game>`

Take a game off the air and free its live slot; put a suspended game back on
the air; take a game off the air for good while keeping its record, id and
files. All three change what fans can reach, so they belong to a person rather
than an agent. There is no creator-facing delete. Needs sign-in.

### `prune`

Deletes archived games that never deployed. Needs sign-in.

| Option | Meaning |
| --- | --- |
| `--yes` | Do not ask before deleting, default off |

It lists what it found, grouped by name, and then asks. Without a terminal and
without `--yes` it deletes nothing. A game that is live, or that ever deployed,
is refused by the server rather than by the command, so a mistaken `--yes`
cannot take a real game away.

### `admin approve <workspace>`, `admin refuse <workspace>`, `admin plan <workspace> <plan>`, `admin refresh <workspace> <dataset> <hours>`, `admin creator-sync`

Ours, not yours. Approving an account and moving it onto a paid plan are
decisions we take, so these are refused for anybody who is not on the operator
allowlist, and the refusal says so rather than pretending the command does not
exist. They are listed here because they are in `gamestage --help` and an
unexplained command is worse than a documented one you cannot run.

`admin plan` is how a person moves you when `plan` tells you a plan is arranged
with a person. It changes your limits and nothing already running: a live
round keeps the caps it was given until you deploy the game again.

`admin refresh` sets how often one switched-on source, such as ESPN, is
fetched for one workspace: a whole number of hours from 1 to 168, or `default`
to go back to every 12 hours. It is how we answer a game that needs fresher
numbers than twice a day.

`admin creator-sync` refreshes the list we use to email creators about their
application and Gamestage, now rather than on the hour, and prints only how many
were updated.

If your account is waiting, nothing you type changes that. `gamestage status`
re-checks it and says where you stand.

## Exit codes

There are three exit codes.

| Code | Means |
| --- | --- |
| `0` | The command did what it said. A refusal is also `0`. |
| `1` | It failed: a bad manifest, a missing file, a check that is open. |
| `2` | Nothing failed and nothing was proved either. |

`verify` is where all three appear. It exits `0` only when every check
verified, `1` when a check is open and the game failed verification, and `2`
when no check failed but one was skipped, which leaves the result unverified.
`--no-browser` therefore yields `2`, because a skipped browser check leaves
the rendered outcome unproved.

Starter turning down a second live game, or a deploy refused because
GitHub is not linked, is the product working as designed, so it exits `0` and
carries a stable `code` in `--json` output. Branch on the code and fix the
cause rather than retrying.
