Repository navigation
EN Bridge CLI
Français · Type: reference · Audience: admin · Context: Reality Bridge · Status: ready · Applies from: 4.3.0
The Reality Bridge,
localprofile (SQLite) · contract: stable · verified by CI (support matrix of 4.4.0, 2026-10-23): Node 22.13+ with node:sqlite, on Ubuntu; Chromium and WebKit end to end · automated checks: jobsreality,reality-xcheck; CI planreality; 2 test files · people's passes: none · known limits:bridge-init-store-urlThe Reality Bridge,
distributedprofile (Postgres) · contract: experimental · verified by CI (support matrix of 4.4.0, 2026-10-23): Postgres 16 in CI; The qualification profile: HTTPS, three instances (ops/qualify/) · automated checks: jobsbridge-postgres,deployment-smoke; CI planreality; 1 test file · people's passes:bridge-postgres-httpsnot run (#77) · known limits:bridge-init-store-url
Every subcommand of the reference Reality Bridge, with its options, defaults, output and exit codes, read from
bridge/src/cli.ts and bridge/src/cli-store.ts. How to use them in order:
Install and run the Bridge.
feature since 4.1.1
| Where | Command |
|---|---|
| The engine repository |
npm run bridge -- <subcommand> [options] (the current game, or GAME=<id>) |
| A created project |
npx quest-n-click bridge <subcommand> [options] (needs quest-n-click-bridge installed beside the engine) |
| The standalone package | npx quest-n-click-bridge <subcommand> [options] |
An option is --name=value or --name value. Every subcommand takes --dir=<folder>, the Bridge's folder holding
config.json, .cache/bridge by default (relative to the current folder).
Subcommands: init, serve, grant, rotate, revoke, doctor, compact, migrate, tenant, backup,
restore. The store a command works on is, first found: --store=, the variable BRIDGE_STORE, the file named by
BRIDGE_STORE_FILE, the store of config.json, else the journal (jsonl). A store is jsonl, sqlite (the file
bridge.sqlite beside config.json), sqlite:<file> (relative to the Bridge's folder) or postgres://….
Another subcommand prints usage: quest-n-click-bridge <init|serve|grant|rotate|revoke|doctor|compact|migrate|tenant|backup|restore>
and exits 2. Outside the engine repository, without the package: ✖ the Reality Bridge is a separate package: npm install quest-n-click-bridge, exit 1.
# in the engine repository
GAME=signals npm run bridge -- doctorAn option no subcommand reads is ignored.
4.1.1 (init, serve, grant, rotate, revoke); doctor and compact 4.1.2; migrate, tenant,
backup, restore 4.1.10; the two restore modes 4.1.20; the quest-n-click-bridge name 4.2.5.
feature since 4.1.1
init [--dir=.cache/bridge] [--manifest=<file>] [--audience=bridge.local] [--origin=<url>,…] [--no-demo-webhooks] [--demo-days=30] [--store=sqlite|sqlite:<file>] [--tenant=<id>] [--environment=prod|staging|dev] [--hosts=<host>,…] [--signal-version=2] [--force]
Makes the keys and the configuration of one tenant, under --dir, mode 600: config.json, root.key (the Biscuit
root's private half, for grant only), admin-token (the operator's token, the only copy in clear).
-
--manifest: the game's built manifest (dist/reality-manifest.json); without it, the current game's content. -
--audience: who the Bridge is in its connectors' tokens (bridge.local). -
--origin: the game's sites, for CORS (http://127.0.0.1:5173,http://localhost:5173). - One demonstration webhook per source of the game's signals unless
--no-demo-webhooks, each with its secret and a token that may send any signal of that source for any player and confirm pairings, for--demo-daysdays. -
--store:sqlitewritessqlite:bridge.sqlite; absent, the journal (journal.jsonl). -
--tenant,--environment(prodwhen absent),--hosts: for several tenants.--signal-version=2: a single tenant signsWorldSignalV2.
Prints the paths written and the webhooks' routes, never a secret. Exit 0.
-
✖ the game declares no reality.signals: nothing to bridge, exit 1. -
✖ <dir>/config.json exists (--force replaces it, and every key and token with it), exit 1.
Limit ·
bridge-init-store-url--storeis written intoconfig.jsonas given. Never pass a Postgres URL with its password toinit: give it toserveand the other commands throughBRIDGE_STOREorBRIDGE_STORE_FILE, which are never written.
# in the engine repository
npm run bridge -- init --store=sqlite --no-demo-webhooks --origin=https://game.example.orgIgnored.
4.1.1; --store, --tenant, --environment, --hosts, --signal-version 4.1.10.
feature since 4.1.1
serve [--dir=…] [--tenants=<dir>,<dir>] [--store=…] [--port=8787] [--host=127.0.0.1] [--trust-proxy[=<proxy>,…]] [--tenant-header]
Serves the Bridge over HTTP until SIGINT or SIGTERM, then ends the streams, closes the server, releases the store and
its locks (five seconds at most) and exits 0. It logs JSON lines on standard output, the first
{"event":"bridge.listening","url":…,"game":…,"tenants":…,"store":…,"pid":…}.
-
--tenants: one folder per tenant, all on one SQL store. -
--trust-proxyalone trusts the loopback; with a list, those addresses or IPv4 networks. -
--tenant-header: a request may name its tenant inX-Web-Scumm-Tenant. - The variables
BRIDGE_STORE,BRIDGE_STORE_FILEandOTEL_EXPORTER_OTLP_ENDPOINT.
Exit 1, said on standard error, when: two folders configure one tenant; several tenants on the journal; a runs or
daily section on the journal; a moderation token file missing, open to others or shorter than 32 characters; a
maintenance lease refuses the store (✖ … : not serving). It refuses to start on a manifest other than the one
configured, on a limit that is not one (limits), and on a journal another live process holds.
# in the engine repository
npm run bridge -- serve --port=8787 --host=127.0.0.1 --trust-proxyIgnored.
4.1.1; --tenants, --tenant-header, --trust-proxy=<list> 4.1.10; the serving lease 4.1.20.
feature since 4.1.1
grant --connector=<id> --source=<source> --signals=<a>,<b> [--players=any|p-…,p-…] [--pair] [--days=30] [--dir=…]
Mints a connector's Biscuit token from root.key and prints it on standard output, one line: redirect it to a file.
The token names the game, the source, the signals, the players (any player of the game by default), the Bridge's
audience, the tenant of the folder, and an expiry --days days from now (to the second; refused from that second on).
--pair lets it confirm pairing codes. Exit 0.
root.key must be in the folder (or the 4.1.1 configuration's field): <dir>/root.key is missing: the root key signs connector tokens, and nothing else can. Give each connector only the signals it proposes. A connector can attenuate
its own token (fewer players, an earlier expiry) before handing it on; it can never widen it.
# in the engine repository
npm run bridge -- grant --connector=shed-telnet --source=terminal --signals=terminal.lamp --pair > telnet.tokenIgnored.
4.1.1; bound to its tenant since 4.1.10.
feature since 4.1.1
rotate [--keep-days=30] [--dir=…]
Makes a new event-signing key, keeps the current one in previousKeys for --keep-days days, rewrites config.json
and prints ✔ new event key k-…; the previous one trusted until <date> (restart the Bridge). Exit 0. It takes effect
when the Bridge restarts. Why and when: Security.
None beyond a readable config.json.
# in the engine repository
npm run bridge -- rotate --keep-days=30Ignored.
4.1.1; what waits for a player signed again under the new key since 4.1.2.
feature since 4.1.1
revoke [--url=http://127.0.0.1:8787/] (--player=<p-…> | --token=<revocation id>) [--dir=…]
Asks the running Bridge to revoke a player's link, or a connector's token by any of its revocation ids (hexadecimal),
with the operator's token read from <dir>/admin-token (POST /v1/admin/revoke). Prints ✔ revoked, exit 0;
otherwise ✖ <status> <answer>, exit 1.
The Bridge must be running and reachable at --url. With several tenants, the URL's host name chooses the tenant.
# in the engine repository
npm run bridge -- revoke --url=https://bridge.example.org/ --player=p-0123456789abcdefIgnored.
4.1.1.
feature since 4.1.2
doctor [--dir=…] [--store=…]
Reads the store without changing it. On the journal: one line {"event":"journal","lines":…,"players":…,"signals":…, "torn":…}, then ✔ journal readable, or ⚠ when the last line was cut short by a crash (serve drops it and says
so). On a SQL store: {"event":"store","kind":…,"schema":…,"tenants":[{tenant, players, signals, gaps, quarantined}]}, a ⚠ quarantined: … line per quarantined row, then ✔ sqlite store readable (schema 5).
Exit 1 when the journal holds a corrupt line (✖ <what>: the Bridge will not start on it), or when a player's
sequence has a gap (✖ a player has a gap in its sequence).
# in the engine repository
npm run bridge -- doctorIgnored.
4.1.2; every field of a journal line checked 4.1.8; SQL stores 4.1.10.
feature since 4.1.2
compact [--retention-days=90] [--dir=…], the Bridge stopped.
Rewrites the journal without the pairings past their time, the earlier versions of a player's line, and the signals
acknowledged and older than the retention. A player's last signal and everything not yet acknowledged stay. Prints
✔ journal compacted: <before> → <after> lines (…).
It takes the journal's lock, so it refuses while a Bridge runs. On a SQL store: ✖ compact rewrites the JSON-lines journal; a SQL store keeps every signal in 4.1.10 (no retention yet), exit 1.
# in the engine repository, the Bridge stopped
npm run bridge -- compact --retention-days=90Ignored.
4.1.2; under the journal's lock 4.1.8.
feature since 4.1.10
migrate --from=jsonl --to=sqlite[:<file>]|postgres://… [--tenant=<id>] [--no-config], or
migrate [--schema=N] [--lock-timeout=30000].
- From the journal, the Bridge stopped: reads it under its lock, writes every player, signal, acknowledgement and
revocation into the target as one tenant (
default, or--tenant), reads them back, then names the new store inconfig.json(not for a URL, nor with--no-config). The journal is kept as it was. Prints✔ <n> players and <m> signals moved to <store> as tenant <id>; the journal is kept as it was. - The SQL schema: up to the latest (
✔ schema 5 (nothing to do)), or to--schema=N, up or down (--schema=0undoes everything). Going down takes the exclusive maintenance lease; up, a serving one.
-
✖ the target already holds the tenant <id>: nothing written, exit 1. -
✖ the target does not hold what was written: the configuration is left on the journal, exit 1. -
✖ the JSON-lines journal has no schema: migrate --from=jsonl --to=sqlite moves it, exit 1. - A lease refused:
✖ <who holds it>; nothing migrated, exit 1. A bad--lock-timeoutor usage: exit 2. - A database newer than the Bridge is refused. Below schema 5 there is no lease table: stop every writer.
# in the engine repository, the Bridge stopped
npm run bridge -- migrate --from=jsonl --to=sqliteIgnored.
4.1.10; the leases 4.1.20.
feature since 4.1.10
tenant export --tenant=<id> [--out=<file>] · tenant delete --tenant=<id> --yes
export writes every row of one tenant, in every table, as JSON: to --out (mode 600) or standard output. delete
removes them. --tenant defaults to the folder's tenant (default).
delete without --yes: ✖ deleting the tenant <id> removes every row it has: add --yes, exit 1. Another
sub-subcommand: usage, exit 2.
# in the engine repository
npm run bridge -- tenant export --tenant=a --out=tenant-a.jsonIgnored.
4.1.10.
feature since 4.1.10
backup --out=<file> [--max-bytes=268435456] [--max-rows=2000000]
Writes the whole store as one snapshot, schema 4, to --out (mode 600, a temporary file renamed). Prints the counts,
the schema and the start of the SHA-256. Exit 0. The procedure, the file's contents and the memory it needs:
Backup and restore.
Without --out: usage, exit 2. A bound that is not a positive integer: exit 2. A store over --max-rows, or a file
over --max-bytes: ✖ … : no file written, exit 1.
# in the engine repository
npm run bridge -- backup --out=bridge.jsonIgnored.
4.1.10; one snapshot (schema 3) 4.1.19; schema 4 4.1.20.
feature since 4.1.10
restore --from=<file> --mode=merge [--force] [--max-bytes=<n>] [--lock-timeout=<ms>]
restore --from=<file> --mode=replace-store --yes-replace-store --safety-out=<new file> [--max-bytes=<n>] [--max-rows=<n>] [--lock-timeout=<ms>]
All or nothing, in the mode you name: merge keeps what the file does not speak of; replace-store makes the store
the file, after a safety backup. The file is checked whole before the store is opened, written in one transaction,
compared before the commit and audited after it. --lock-timeout: how long the Postgres advisory lock is tried
(30 000 ms).
| Exit | When |
|---|---|
| 0 | Restored and checked |
| 1 | Refused or failed, nothing restored (a conflict, a file refused, the journal, a lease held, a safety backup that could not be written) |
| 2 | Usage: no --mode, --force with replace-store, --safety-out or --yes-replace-store with merge, a missing confirmation or safety path, a bad bound |
| 3 | A difference found after the commit (P0), or the commit's answer lost |
# in the engine repository, every Bridge on that store stopped
npm run bridge -- restore --from=bridge.json --mode=mergeIgnored. A file with an unknown field is refused (strict schemas).
4.1.10; all or nothing 4.1.19; the modes 4.1.20.
Applies from 4.3.0 · feature since 4.1.1
An object of integers in a tenant's config.json, "limits": { "perMinutePerConnector": 240 }. Each name left out
keeps its default.
| Limit | Default | Least |
|---|---|---|
bodyBytes: a request body, in bytes |
8 192 | 1 |
perMinutePerConnector: signals one connector may propose per minute, per instance |
120 | 0 |
pendingPerPlayer: signals waiting for a player's acknowledgement |
1 000 | 0 |
pairingMs: how long a pairing code waits |
600 000 (10 min) | 0 |
capabilityMs: a capability's life without an acknowledgement |
2 592 000 000 (30 days) | 0 |
capabilityMaxMs: a capability's longest life, renewals included |
15 552 000 000 (180 days) | 0 |
streamsPerPlayer: open event streams per player |
4 | 0 |
streamsPerInstance: open event streams on one instance |
10 000 | 0 |
streamBufferBytes: bytes a stream may hold unsent |
65 536 | 1 |
pendingPairings: codes waiting for a confirmation, all players |
1 000 | 0 |
resignedCache: signals signed again after a rotation, kept in memory |
10 000 | 0 |
streamPage: signals a stream reads from the store at once |
100 | 1 |
From 4.3.0 the Bridge checks them before it starts: limits.<name> must be an integer of at least <n>, not <value>,
or limits.<name> is no limit of the Bridge (<the names>). On a shared Bridge, one tenant's wrong limit stops every
tenant: read Upgrade notes 4.3 before upgrading.
"limits": { "perMinutePerConnector": 240, "pendingPerPlayer": 2000 }Refused (from 4.3.0).
4.1.1; checked since 4.3.0.
feature since 4.1.9
Five places, in this order:
- On the Bridge:
DELETE /v1/admin/players/<id>with the operator's token (the link, the signals, the acknowledgements).GETon the same route exports them first. - In an IMAP mailbox: delete the player's flagged messages, if any (accepted ones are already gone with
keepDays: 0). - In the SSH connector's configuration: remove the player's declared key, if any, and restart the connector.
- In memory (email senders linked by a pairing code, Telnet and SSH resume words): restarting the connectors forgets them all.
- On the device: the player erases the save or the site's data.
DELETE answers 204; an unknown player, 404.
The operator's token (admin-token). Backup files already written still hold the player: they are yours to expire.
# on the Bridge's machine, in the engine repository
curl -s -X DELETE -H "Authorization: Bearer $(cat .cache/bridge/admin-token)" https://bridge.example.org/v1/admin/players/p-0123456789abcdefThe route takes a p- and 16 hexadecimal digits; anything else is a 404.
The Bridge's route 4.1.1; the connectors' part 4.1.9. What each place keeps: Privacy.
-
npm run connector(quest-n-click-connector): Connectors overview. -
npm run bridge:load(threeserveon one store under load;docs/dev/BENCH-BRIDGE.md): Benchmarks. -
npm run ops:qualify: The reference deployment. - The other CLIs of the project: CLIs.
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