Repository navigation
EN Team Server CLI
Français · Type: reference · Audience: admin · Context: hosted Studio · Status: ready · Applies from: 4.2.3
The Studio Server (hosted team Studio) · contract: experimental · verified by CI (support matrix of 4.4.0, 2026-10-23): Postgres, S3 and Keycloak in CI; Chromium and Firefox (job studio-hosted); WebKit on macOS, by hand only · automated checks: jobs
studio-hosted,studio-install-smoke; CI planstudioHosted; 3 test files · people's passes:studio-hostednot run (#149),studio-collabnot run (#150),studio-previewnot run (#166),studio-assets-hostednot run (#167),studio-mcp-remotenot run (#196),studio-oidc-genericnot run (#197) · known limits:export-game-only
studio-server is the Studio Server's one command: it serves, imports and exports the game, invites the first owner,
logs the CLI in, backs up, restores and collects. This page lists every subcommand of
studio-server/src/server/main.ts, its options, what it prints and its exit
code.
# in the engine repository
npm run studio-server -- <subcommand> [options]
# from the release archives, in the folder where they are installed
npx quest-n-click-studio-server <subcommand> [options]Both run the same program. On Clever Cloud, run it in clever ssh --alias <studio app>, after cd "$APP_HOME": that
shell already has the app's variables.
Every subcommand but login, token, deps and worker-keys reads the server's configuration, from the first of:
-
--config <file>, anywhere in the arguments; - the variable
STUDIO_CONFIG, a file's path; - the variable
STUDIO_CONFIG_JSON, the configuration itself on one line (Clever Cloud).
Without any: usage: studio-server <serve|import <dir>|export <dir>> --config <file> (or STUDIO_CONFIG_JSON), exit
code 2. An unknown or missing key of the configuration stops the command with configuration: <file>: …, exit code 1.
The secrets (STUDIO_DATABASE_URL, the S3 keys, STUDIO_OIDC_CLIENT_SECRET, STUDIO_SESSION_KEY, STUDIO_WORKER_KEY,
STUDIO_METRICS_TOKEN, STUDIO_GIT_KEY) are read from <NAME>_FILE (a file of mode 0600) or from the environment
(on Clever Cloud also the add-ons' POSTGRESQL_ADDON_URI, CELLAR_ADDON_KEY_ID, CELLAR_ADDON_KEY_SECRET), then
removed from the environment before anything else runs. See
Environment variables.
| Code | Meaning |
|---|---|
| 0 | done; serve also exits 0 after a SIGTERM or SIGINT once it has drained |
| 1 | refused or failed: a configuration error (configuration: …), import on an instance that has a project, replay without a restore under way, token not logged in, any other error (its message, or its stack) |
| 2 | usage: a subcommand's argument or flag missing or wrong, an unknown subcommand, serve on a non-loopback address without OIDC |
Most subcommands print one JSON line on stdout ({"event": …}), documented below; invite, login, token and
worker-keys print plain text. Errors go to stderr.
npm run studio-server -- serve --config studio.config.jsonStarts the server: the API, the probes, the game's origin, the service lease. It prints JSON log lines (lease-taken,
listening, one request line per request, stopping…). It listens on the configuration's port, else PORT, else
8080, on host (0.0.0.0 by default).
- Without
oidcin the configuration, it is the local server and refuses anyhostbut the loopback:serve: <host> is refused without authentication (configure oidc); use 127.0.0.1(exit 2). - With
oidcit needsSTUDIO_OIDC_CLIENT_SECRET,STUDIO_SESSION_KEY(32 characters or more, several comma-separated to rotate),workerwithSTUDIO_WORKER_KEY, the built Studio page (npm run studio-client), and the provider's discovery reachable; any missing is aconfiguration:error (exit 1). -
STUDIO_METRICS_TOKEN, when set, must be 32 characters or more. - It refuses to start while a restore is under way:
a restore is under way: end it with replay. - On
SIGTERMorSIGINTit stops taking requests, lets the running ones finish (5 s at most), gives the service lease back, and exits 0.
npm run studio-server -- import games/my-game --config studio.config.jsonMakes a game folder (with its index.ts) the instance's project: copied beside, then renamed into place, and adopted
by the journal as revision 1. On a hosted instance every file goes to the bucket, and the journal is shipped before the
command ends. Prints {"event":"imported","game","revision"}.
- No folder, or no
index.ts:import: a game folder (with its index.ts) is required(exit 2). - The instance already has a project (in its database, or a non-empty work folder):
import: this instance already has a project(exit 1). Import once, on a new instance; afterwards use Git (The game in Git) or a restore.
npm run studio-server -- export ./my-game-export --config studio.config.jsonWrites the game at the head into a new or empty folder, with export-manifest.json:
{"game", "epoch", "revision", "files": [{"path", "blob"}]}, where blob is each file's sha256. The files and the
revision come from one snapshot of the database, and each file's content is checked against its hash. Prints
{"event":"exported","revision","files"}. It reads only: it can run while the server serves.
- A folder that exists and is not empty:
export: an empty or new folder is required(exit 2). - A content that does not match the head: an error naming the file (exit 1).
Guarantee ·
export-same-files· referenced · scope self-checked by the author (AI), not reviewed A game imported, then exported at its head, comes out with a manifest of its files' hashes, and each exported file matches its hash in the manifest.
Limit ·
export-game-onlyAn export holds the game's files at the head, nothing else: no history, no members, no comments, no published builds, no prepared assets. A backup holds those (see Backups). On Clever Cloud the folder is written on the instance's disk, which does not survive a restart: copy it out (see Exporting the game and leaving the installation).
npm run studio-server -- invite --email you@example.fr --role ownerPrints a one-time invitation link (<publicUrl>/auth/login?invite=…, 7 days, for that verified email). It is how the
first owner gets in; the audit log names its author studio-server invite. Each flag needs its value, never another
flag; otherwise usage: studio-server invite --email <email> --role <owner|dev|reviewer> --config <file> (exit 2).
See Invitations.
npm run studio-server -- login --server https://studio.example.fr # the device flow: open the link, type the code
npm run studio-server -- token --server https://studio.example.fr # an access token for scripts and the MCPNeeds no configuration. Asks the Studio which provider and client to use (GET /auth/cli), then runs the device flow:
it prints Open <url> and enter the code <code>, waits, then logged in. The tokens are kept in
~/.config/web-scumm/studio-tokens.json (mode 0600), one entry per server, and only sent over https.
- The provider refuses:
the login was refused: <error> (<description>), for instancenot_allowed (Offline tokens not allowed for the user or client)when the member lacksoffline_access(see Keycloak). - The Studio names no CLI client:
<url> names no CLI client: ask its owner to configure oidc.cliClientId. - An
http://URL:<url> is not https: the tokens would travel in clear.
npm run studio-server -- token --server https://studio.example.fr # an access token for scripts and the MCPPrints a valid access token for that server, renewed first when it has less than a minute left. Send it as
Authorization: Bearer <token>. The tokens must carry the Studio's oidc.audience. Not logged in:
not logged in to <url>: run studio-server login --server <url> (exit 1); refresh token gone: the login to <url> expired: log in again. An AI agent uses it through the Studio's /mcp, or through the local MCP server pointed at
the Studio (npm run -s mcp -- --server <url>): see Remote MCP.
Needs no configuration. Prints the server's own dependencies as they resolve from where it runs:
{"openid-client": {"version", "from"}, "jose": {…}, "pg": {…}}. A missing one fails here, not at the first login.
Useful after installing from the archives, where pg takes the latest 8.x its range allows.
mkdir -p ~/secrets/my-game && npm run studio-server -- worker-keys --out ~/secrets/my-gameNeeds no configuration. Writes the job keys: studio-worker.key (mode 0600, the Studio's secret
STUDIO_WORKER_KEY) and studio-worker.pub (the worker's WORKER_PUBLIC_KEY). Never prints a key. The folder must
exist (usage: studio-server worker-keys --out <existing folder>, exit 2); an existing key file is never overwritten
(exit 1).
npm run studio-server -- backup ./backup.json --config studio.config.json
npm run studio-server -- backup --to-bucket --keep 14 --config studio.config.jsonWrites the project's tables in one snapshot (members, the journal, the head, the previews, builds and prepared sets; not the sessions nor the leases), with the blobs they name and a digest.
-
backup <file>: a new file (mode 0600). An existing file or none:backup: a new file is required (or --to-bucket [--keep n])(exit 2). Prints{"event":"backup","revision","blobs","digest"}. -
--to-bucket: to the instance's bucket asbackups/<date>-r<revision>.json(UTC,2026-10-19T03-17-00Z); needsblobs.kinds3. With--keep n(1 to 9999), the backups this command wrote past thenmost recent are deleted, never another object; without--keep, none is deleted. Prints{"event":"backup","key","revision","digest", "removed"}. A bad--keep:backup: --keep is the number of backups kept (1 or more)(exit 2).
What a backup holds, the daily cron and retention: Backups.
npm run studio-server -- restore ./backup.json --replay --config studio.config.json
npm run studio-server -- replay --config studio.config.jsonrestore puts a backup into an empty hosted database (store.kind postgres; a SQLite one is refused), checks
its digest, tables and blobs, then with --replay replays the journal shipped to the bucket since the backup.
replay resumes a replay that stopped and ends the restore. Both print {"event":"restored", "rows", "dropped", "backup", "revision", "stopped"}. replay without a restore under way: replay: no restore is under way (exit 1).
The procedure: Restore the Studio on a fresh installation.
npm run studio-server -- gc --config studio.config.jsonLists the blobs no row names and older than a day, and the worker's staging older than an hour; --delete deletes
them. Prints {"event":"collectable"|"collected", "referenced", "unreferenced", "removed", "staging"}. The head's and
the journal's blobs are never collected. Never run gc --delete from a drill's database: it would collect blobs
production still names.
The processing worker is another program: npm run studio-worker in the repository, npx quest-n-click-studio-worker
from the archives. It has no subcommand; it reads WORKER_PUBLIC_KEY (or WORKER_PUBLIC_KEY_FILE), WORKER_INSTANCE and PORT. See
Command-line tools and Environment variables.
- Health and metrics routes, Roles and actions.
-
tests/studio-server.test.ts(import, serve, export through the command).
Generated publication · publication générée — current documentation · documentation courante: quest'n'click 4.4.0 r1, documentation revision · révision de la documentation 8dc717c69acb62c4742b69c54a7e7dfb620f75d8.
Earlier versions and permalinks · versions précédentes et liens permanents: https://wanoo.github.io/quest-n-click/docs/
An edit made in this wiki is overwritten at the next publication · une modification faite dans ce wiki est écrasée à la publication suivante.
Edit the source · Modifier la source · Report an error · Signaler une erreur
Index (6)
Start (6)
Play (9)
Install (17)
- Requirements
- Install from the release archive
- Install from npm, once published
- Install from the repository to work on the engine
- doctor: checking your machine
- Image toolchain
- Audio toolchain: ffmpeg, fluidsynth, Furnace, uv
- Upgrade
- Upgrade notes: 4.4
- Upgrade notes: 4.3
- Upgrade notes: 4.2
- Upgrade notes: 4.1
- Upgrade notes: v3
- Upgrade notes: v2 to v3
- Coming from web-scumm
- Package reference
- Install troubleshooting
Create a game (55)
- Your first game
- Layout of a created project and of the engine repository
- The working loop
- Content principles and the identity contract
- Rooms
- Geometry: placement, walk zones, masks, layers, camera
- Something is visible but unreachable
- Reactions
- Game commands
- Conditions
- Flags, variables and events: which to use
- Custom code
- Characters
- Items and inventory
- World map
- Dialogues
- Scripts and events
- Moving characters: patrols and walks
- Exits
- Chapters and objectives
- State and saves: persistent or temporary, renaming an id, migrations
- Staging: wide rooms and animations
- The stage: lights, particles, floors
- Skin and fonts
- Title and credits
- Heroes: several characters and hero choice
- Built-in minigames
- A custom minigame
- Translations
- Music in stems
- Sound effects
- Voices: per language and per hero
- Images: prompts, cutting sheets, transparency, scale
- Asset provenance
- The storyboard, source of truth
- Puzzle design
- Classic mechanics
- Review pages
- DSL reference
- Checking your game
- What the solver proves
- Solver options
- Playtests
- Remix
- Speedrun for authors
- The sealed ending
- Production template
- Example games
- Working with an AI
- Build and release a game
- Publish on GitHub Pages
- The static game on Clever Cloud
- Other static hosts
- Sub-path, cache and service worker, incomplete deploys, going back
- Testing on a phone over the LAN
Local Studio (24)
- The local Studio: overview and launch
- Edit a room
- Placement and stage
- Preview, apply, undo
- Storyboard
- Assets and cutting
- Check: validator, solver, critical path, heat, health, coverage
- Before and after captures
- Semantic diff
- Play and replay a session
- Rule explainer
- Language and objectives
- Voices and the heroes × languages matrix
- Map
- Notes
- Remix in the Studio
- Music
- The Assistant: bring your own key
- Demo mode and studio-apply
- Local and LAN security
- Studio API
- Local MCP: setting up each client
- MCP tools
- Studio troubleshooting
Team Studio (21)
- What runs where
- Concepts: revision, transaction, span, lease, preview, build
- Starting as an invited dev
- Working together day to day
- Roles and actions: the full matrix
- Invitations
- Managing members
- Write refused on the hosted Studio: stale base revision (409)
- Write refused on the hosted Studio: span locked (423)
- Recovering after a lost lease, a disconnection or a stale preview
- Comments
- Map and semantic diff
- Publishing builds
- Remote MCP
- The game in Git
- Upgrading the engine of a hosted game
- Hosted assets
- The server's command line
- Health and metrics routes
- Trying it locally with compose.yml
- Known limits
Clever Cloud (23)
- Clever Cloud: overview, cost and prerequisites
- Apps and add-ons
- Domains and TLS
- Keycloak
- Generic OIDC and GitLab
- Configuration
- First deployment
- Backups
- RPO and RTO
- Restore the Studio on a fresh installation
- Redeploys
- Upgrading the server and checking state after a migration
- Limits of going back
- Monitoring
- Security
- Rotating keys
- Recovering admin access
- Changing domain or OIDC provider
- Worker, PostgreSQL or Cellar down
- Exporting the game and leaving the installation
- Retention
- Clever Cloud troubleshooting
- Costs and teardown
Reality Bridge and connectors (16)
- The Reality Bridge: what it is
- Quick start
- Guarantees
- Install and run the Bridge
- Backup and restore
- Several tenants
- Signals and the manifest
- The Bridge's command line
- Connectors overview
- Email connector
- Telnet connector
- SSH connector
- Open Badges connector
- The connector SDK
- Security: Biscuit and signatures
- Bridge and connector troubleshooting
Contribute (25)
- Welcome
- Developer setup
- The dev loop
- Before a pull request
- Conventions
- Writing documentation
- Architecture
- Code tour
- Import boundaries
- Which tests run when
- Mutation testing
- The coverage ratchet
- The quality baseline
- CI explained
- Field passes
- Benchmarks
- Releasing
- npm packages
- Decisions and ADRs
- Governance
- Bot pull requests
- Contributing with AI agents
- Security policy
- Code of conduct
- Licences and credits