Mock API, fixtures, and tooling for recording presentation videos and
developing stakecli without access to a production Stake Engine instance.
| Path | What it does |
|---|---|
cmd/mockapi/ |
Standalone HTTP server implementing the subset of the Stake Engine API that stakecli talks to. Serves hand-crafted fixtures for reads (teams, games, stats, versions, balances) and tracks upload state in memory. |
cmd/demo-bundle/ |
Deterministic generator for a realistic math + front bundle. Output passes stakecli's math compliance check cleanly (no warnings). |
internal/auth/ |
Two new env-var hooks (STAKE_CONFIG_DIR, STAKE_KEYRING_DISABLE) that isolate the demo from the user's real config and OS keychain. |
Makefile |
Convenience targets for building, running, and resetting the demo. |
testdata/demo-bundle/ |
Output of cmd/demo-bundle, ~15 MiB across ~15 files. Gitignored. |
# One-time setup
make demo-build # build stakecli, mockapi, generator + generate bundle
# Terminal 1 — run the mock API
make mockapi
# Terminal 2 — run stakecli against it, from scratch including login
make demo-run
# enter "demo" (or anything) when the login screen appearsmake demo-reset # wipe mock's in-memory scratch + publish statedemo-reset is the only thing you need between takes. The login screen
shows on every make demo-run automatically (see "Auth flow" below), so
there's nothing to clean up on the client side.
The TUI does full SID-based auth on every launch:
auth.Init()reads the accounts config file- If there's an active account, fetch its SID from the OS keychain
- Call
GET /userswithCookie: sid=<value>to validate - On success, jump straight to the teams screen
- On failure, show the login screen
The mock already supports all four steps: GET /users accepts any non-empty
sid cookie and returns a demo user (Mnemoo (demo)). So the full pipeline
works out of the box.
The problem: if you already have a real stakecli account on your machine, steps 1–3 succeed against the mock (it accepts any SID), so the TUI skips the login screen on recording. Worse, the mock's demo user gets merged into your real accounts config file.
The fix — two new env vars, supported by the demo targets:
| Env var | What it does |
|---|---|
STAKE_CONFIG_DIR=/tmp/stakecli-demo |
Overrides os.UserConfigDir()/stakecli/. The demo writes a throwaway accounts.json here instead of mangling your real config. |
STAKE_KEYRING_DISABLE=1 |
Makes SetSID / DeleteSID no-ops and GetSID return ErrNotFound. The demo never touches your OS keychain. |
Both env vars are set automatically by every make demo-* target. The
combined effect:
- First run: config dir empty →
auth.Init()returnsneedLogin=true→ login screen shows → enter "demo" → validated against mock → account saved to/tmp/stakecli-demo/accounts.json, keyring untouched. - Next run: config still has the account, but keyring is disabled so
GetSIDfails → login screen shows again. No state carries between runs. - Cleanup:
make demo-logoutnukes/tmp/stakecli-demo, ormake cleannukes everything.
These env vars are also legitimately useful for CI, headless containers,
and multi-account workflows — they can be cherry-picked to main when you
want.
The mock ships with 4 teams and 20 games, hand-edited to look impressive on camera:
- Neon Labs — 8 games, ~3.8M monthly plays, the demo's "main" team
gods-of-neon,midnight-vault,thunder-empress,sakura-fortune-x,nebula-rush— published, full stats with 2-4 modes eachcyber-samurai,plasma-drift,ghost-protocol— unpublished, used as upload targets in the demo
- Aurora Studios — 5 games
- Crimson Games — 4 games
- Indie Collab — 3 games
Per-game stats include realistic RTP (~96%), turnover, and profit across
base, freespins, bonus_buy, and max_buy modes.
cmd/demo-bundle generates a seeded, reproducible bundle that passes the
math compliance check with clean output:
- 3 modes —
base(cost 1),freespins(cost 100),bonus_buy(cost 200) - RTP: 96.42% / 96.38% / 96.28% (cross-mode variation 0.14%, under the 0.5% limit)
- Max win: 5000.00x displayed uniformly across all modes
- Hit rate: 11% (safely above the 10% "non-paying" warning floor)
- Volatility:
Highfor base,Lowfor freespins/bonus_buy - Sizes: ~15 MiB total (~10 MiB math + ~5 MiB front) — visible upload progress without dragging the recording.
Math bundle layout:
testdata/demo-bundle/math/
├── index.json # required by compliance, references all modes
├── base_weights.csv # 120k entries, LUT for compliance
├── base_events.jsonl # ~3 MiB of padded event stream
├── freespins_weights.csv # 110k entries
├── freespins_events.jsonl # ~2.5 MiB
├── bonus_buy_weights.csv # 105k entries
├── bonus_buy_events.jsonl # ~2 MiB
├── config.json # game config
├── symbols.json # symbol metadata
└── paytable.json # paytable
Front bundle layout:
testdata/demo-bundle/front/
├── index.html # real HTML skeleton
├── manifest.json # real JSON referencing every asset
└── assets/
├── bundle.js # ~2 MiB padded JS
├── style.css # real CSS
├── img/logo.png # valid 1x1 PNG
├── img/spritesheet.webp # 500 KiB pseudo-random
└── audio/
├── theme.mp3 # 1.8 MiB
├── spin.mp3 # 210 KiB
└── win.mp3 # 310 KiB
The publish endpoint returns an error when the game slug ends with -fail:
POST /file/publish/math { "team": "neon-labs", "game": "cyber-samurai-fail" }
→ 200 { "code": "MATH_MISSING_MODE", "message": "missing weights file for mode 'bonus_buy'" }
Useful if you want to record a "publish fails → investigate → retry" sequence.
All flags are on the mock API and the Makefile:
# Slower upload (more dramatic progress bar) — ~50 seconds for 15 MiB
./mockapi -throughput 500KiB
# Faster upload (tighter cut) — ~3 seconds
./mockapi -throughput 5MiB
# Customize via make
THROUGHPUT=1MiB make mockapiPublish latency:
./mockapi -publish-delay 1200 # 1.2s artificial delay on /file/publish/*All demo targets set these automatically. Listed here for manual use:
export STAKE_API_URL=http://localhost:8080 # point at the mock
export STAKE_CONFIG_DIR=/tmp/stakecli-demo # isolated config (not your real one)
export STAKE_KEYRING_DISABLE=1 # no OS keychain writes
export STAKE_NO_UPDATE_CHECK=1 # suppress background update check
# Optional — set ONLY if you want to skip the login screen:
export STAKE_SID=demo # CLI path auto-authenticatesSTAKE_SID is checked by the CLI upload path (GetActiveSID) but NOT by
auth.Init(). So setting it bypasses auth for stakecli upload (and
make demo-upload) but doesn't affect TUI login. That's why make demo-run
deliberately leaves STAKE_SID unset.
| Target | What it does |
|---|---|
make demo-build |
Build everything + generate bundle |
make mockapi |
Run the mock API (foreground) |
make demo-run |
Launch TUI with login screen every run |
make demo-fast |
Launch TUI with STAKE_SID bypass (skips login) |
make demo-upload |
Non-interactive CLI upload of the math bundle |
make demo-shell |
Spawn a shell with demo env vars exported |
make demo-reset |
Wipe mock's in-memory state |
make demo-logout |
Nuke the isolated demo config dir |
make demo-health |
Ping the mock's health endpoint |
make clean |
Remove binaries + bundle + demo config |
- Real S3: PUT requests land on a throttled in-process handler, not a real bucket. That's the point — upload progress is deterministic.
- Persistence: all state is in-memory. Restart the mock or run
make demo-resetto zero it. - Auth validation: any non-empty
sidcookie is accepted as "logged in". - Rate limiting / quotas: no real-world constraints.
make clean # removes binaries, testdata/demo-bundle/, /tmp/stakecli-demo/