@flayerlabs/gamemode-cli 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/scaffold.js +88 -2
- package/dist/scaffold.js.map +1 -1
- package/package.json +1 -1
- package/src/scaffold.ts +95 -2
package/dist/scaffold.js
CHANGED
|
@@ -67,16 +67,19 @@ const files = async (name) => {
|
|
|
67
67
|
type: 'module',
|
|
68
68
|
scripts: {
|
|
69
69
|
dev: 'vite',
|
|
70
|
+
gate: 'tsx src/gate.ts',
|
|
70
71
|
test: 'gamemode check src/game/rules.ts && vitest run',
|
|
71
72
|
check: 'gamemode check src/game/rules.ts',
|
|
72
73
|
typecheck: 'tsc --noEmit',
|
|
73
74
|
},
|
|
74
75
|
dependencies: {
|
|
75
76
|
'@flayerlabs/gamemode-client': sdk,
|
|
77
|
+
'@flayerlabs/gamemode-gate': sdk,
|
|
76
78
|
'@flayerlabs/gamemode-spec': sdk,
|
|
77
79
|
},
|
|
78
80
|
devDependencies: {
|
|
79
81
|
'@flayerlabs/gamemode-cli': sdk,
|
|
82
|
+
tsx: '^4.23.12',
|
|
80
83
|
typescript: '^5.7.2',
|
|
81
84
|
vite: '^5.4.21',
|
|
82
85
|
vitest: '^2.1.8',
|
|
@@ -106,6 +109,28 @@ pnpm test
|
|
|
106
109
|
pnpm typecheck
|
|
107
110
|
\`\`\`
|
|
108
111
|
|
|
112
|
+
## Deploy your gate
|
|
113
|
+
|
|
114
|
+
Game Mode needs your game to bring its own round server — a gate — and its public https origin is
|
|
115
|
+
exactly the **Gate URL** you enter when you submit the game at flaunch.gg/game-mode/create. Flaunch
|
|
116
|
+
never runs a gate for you and never holds your signing key.
|
|
117
|
+
|
|
118
|
+
\`src/gate.ts\` is the whole entrypoint; \`.env.example\` is the whole contract. On Railway:
|
|
119
|
+
|
|
120
|
+
1. Create a project from this repo (\`railpack.json\` names the build and start commands) and
|
|
121
|
+
attach a Postgres whose URL lands in \`DATABASE_URL\`.
|
|
122
|
+
2. Set the variables from \`.env.example\`, generating the key and secret fresh.
|
|
123
|
+
3. Give the service a public domain, set it as \`GATE_ORIGIN\`, and smoke-test it:
|
|
124
|
+
|
|
125
|
+
\`\`\`bash
|
|
126
|
+
curl https://<your-gate>/health # { "ok": true }
|
|
127
|
+
curl https://<your-gate>/config # chainId must be the chain you meant; note "signer"
|
|
128
|
+
\`\`\`
|
|
129
|
+
|
|
130
|
+
A gate that boots is a gate whose signatures a swap can verify — boot refuses loudly on a
|
|
131
|
+
wrong-chain RPC, a demo key, or an economy your rewardBounds cannot honour. The full environment
|
|
132
|
+
table lives in \`@flayerlabs/gamemode-gate\`'s DEPLOY.md.
|
|
133
|
+
|
|
109
134
|
## Work with an agent
|
|
110
135
|
|
|
111
136
|
Ask the agent to read \`AGENTS.md\` before it edits the game. Use \`$build-game-mode\` when the
|
|
@@ -321,11 +346,72 @@ export interface PlayerView {
|
|
|
321
346
|
wasRight: boolean | null;
|
|
322
347
|
}
|
|
323
348
|
`,
|
|
324
|
-
'src/
|
|
349
|
+
'src/gate.ts': `import { startGate } from '@flayerlabs/gamemode-gate';
|
|
350
|
+
import { rules, type Config } from './game/rules.js';
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Your game's own server, as a deployable process.
|
|
354
|
+
*
|
|
355
|
+
* A gate holds YOUR signing key and runs YOUR rules: a creator who launches a coin through your
|
|
356
|
+
* game writes your signer's address into their pool, and the chain trusts it for that pool alone.
|
|
357
|
+
* Flaunch never holds the key. \`startGate\` does the wiring — database migrations, the chain
|
|
358
|
+
* address book, boot refusals for anything misconfigured, /config for launch tooling — and this
|
|
359
|
+
* file stays what it should be: your round's economy, stated once.
|
|
360
|
+
*
|
|
361
|
+
* Everything environment-shaped comes from the environment; see .env.example.
|
|
362
|
+
*/
|
|
363
|
+
const config: Config = { rounds: 3, ceiling: 10, points: 500, roundMs: 10_000 };
|
|
364
|
+
|
|
365
|
+
const { port, signer } = await startGate(rules, config);
|
|
366
|
+
|
|
367
|
+
// The signer address is what a launch must name for this gate to adopt it — it belongs in the
|
|
368
|
+
// deploy log, where an operator can read it back without a debugger.
|
|
369
|
+
console.log(\`[gate] listening on \${port} — signer \${signer}\`);
|
|
370
|
+
`,
|
|
371
|
+
'.env.example': `# The environment contract for \`pnpm gate\` (full table: @flayerlabs/gamemode-gate DEPLOY.md).
|
|
372
|
+
# Generate SIGNER_PRIVATE_KEY and SESSION_SECRET fresh for every environment — a key shared
|
|
373
|
+
# between two environments lets one gate's bug mint authorisations against the other's pools.
|
|
374
|
+
|
|
375
|
+
# The one chain this process serves. 84532 (Base Sepolia) is in the address book.
|
|
376
|
+
CHAIN_ID=84532
|
|
377
|
+
# An RPC endpoint for that chain; boot checks its eth_chainId matches.
|
|
378
|
+
RPC_URL=https://sepolia.base.org
|
|
379
|
+
# Your key, generated by you, never shared. openssl rand -hex 32 (prefix with 0x).
|
|
380
|
+
SIGNER_PRIVATE_KEY=
|
|
381
|
+
# Postgres. Migrations run at boot.
|
|
382
|
+
DATABASE_URL=
|
|
383
|
+
# At least 32 characters. openssl rand -hex 32
|
|
384
|
+
SESSION_SECRET=
|
|
385
|
+
# The page the player is looking at when they sign in — the embedding site, not this gate.
|
|
386
|
+
SIGN_IN_DOMAIN=flaunch.gg
|
|
387
|
+
# This gate's own public https origin — the exact value you submit as the Gate URL.
|
|
388
|
+
GATE_ORIGIN=
|
|
389
|
+
# Comma-separated origins allowed to call this gate: your game's hosted origin(s) and the pages
|
|
390
|
+
# that embed it. One * may span a single DNS label.
|
|
391
|
+
ALLOWED_ORIGINS=https://*.games.moongate.com,https://flaunch.gg
|
|
392
|
+
`,
|
|
393
|
+
'railpack.json': `${JSON.stringify({
|
|
394
|
+
$schema: 'https://schema.railpack.com',
|
|
395
|
+
build: { builder: 'RAILPACK', buildCommand: 'pnpm install' },
|
|
396
|
+
deploy: { startCommand: 'pnpm gate' },
|
|
397
|
+
}, null, 2)}\n`,
|
|
398
|
+
'src/play.ts': `import { connectHost, createMockRoom, joinRoom, type Snapshot } from '@flayerlabs/gamemode-client';
|
|
325
399
|
import { rules, type Action, type Config, type PlayerView, type PublicView } from './game/rules.js';
|
|
326
400
|
|
|
327
401
|
const config: Config = { rounds: 3, ceiling: 10, points: 500, roundMs: 10_000 };
|
|
328
|
-
|
|
402
|
+
|
|
403
|
+
// Embedded on flaunch.gg, the page tells this game which gate and round to join and services its
|
|
404
|
+
// wallet calls over the frame bridge. Standing alone (pnpm dev), there is no page to ask — null is
|
|
405
|
+
// that answer, and the mock room runs the same rules with the chain and wallet faked.
|
|
406
|
+
const embedded = await connectHost();
|
|
407
|
+
const room = embedded
|
|
408
|
+
? await joinRoom<PublicView, PlayerView, Action>({
|
|
409
|
+
gateUrl: embedded.context.gateUrl,
|
|
410
|
+
roundId: embedded.context.roundId,
|
|
411
|
+
host: embedded.host,
|
|
412
|
+
...(embedded.context.launch ? { launch: embedded.context.launch } : {}),
|
|
413
|
+
})
|
|
414
|
+
: createMockRoom(rules, { config, lobbyMs: 1_000, roundMs: 60_000 });
|
|
329
415
|
const app = document.querySelector<HTMLElement>('#app');
|
|
330
416
|
if (!app) throw new Error('the page needs an #app element');
|
|
331
417
|
const gameRoot = app;
|
package/dist/scaffold.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scaffold.js","sourceRoot":"","sources":["../src/scaffold.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"scaffold.js","sourceRoot":"","sources":["../src/scaffold.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/src/scaffold.ts
CHANGED
|
@@ -86,16 +86,19 @@ const files = async (name: string): Promise<Record<string, string>> => {
|
|
|
86
86
|
type: 'module',
|
|
87
87
|
scripts: {
|
|
88
88
|
dev: 'vite',
|
|
89
|
+
gate: 'tsx src/gate.ts',
|
|
89
90
|
test: 'gamemode check src/game/rules.ts && vitest run',
|
|
90
91
|
check: 'gamemode check src/game/rules.ts',
|
|
91
92
|
typecheck: 'tsc --noEmit',
|
|
92
93
|
},
|
|
93
94
|
dependencies: {
|
|
94
95
|
'@flayerlabs/gamemode-client': sdk,
|
|
96
|
+
'@flayerlabs/gamemode-gate': sdk,
|
|
95
97
|
'@flayerlabs/gamemode-spec': sdk,
|
|
96
98
|
},
|
|
97
99
|
devDependencies: {
|
|
98
100
|
'@flayerlabs/gamemode-cli': sdk,
|
|
101
|
+
tsx: '^4.23.12',
|
|
99
102
|
typescript: '^5.7.2',
|
|
100
103
|
vite: '^5.4.21',
|
|
101
104
|
vitest: '^2.1.8',
|
|
@@ -130,6 +133,28 @@ pnpm test
|
|
|
130
133
|
pnpm typecheck
|
|
131
134
|
\`\`\`
|
|
132
135
|
|
|
136
|
+
## Deploy your gate
|
|
137
|
+
|
|
138
|
+
Game Mode needs your game to bring its own round server — a gate — and its public https origin is
|
|
139
|
+
exactly the **Gate URL** you enter when you submit the game at flaunch.gg/game-mode/create. Flaunch
|
|
140
|
+
never runs a gate for you and never holds your signing key.
|
|
141
|
+
|
|
142
|
+
\`src/gate.ts\` is the whole entrypoint; \`.env.example\` is the whole contract. On Railway:
|
|
143
|
+
|
|
144
|
+
1. Create a project from this repo (\`railpack.json\` names the build and start commands) and
|
|
145
|
+
attach a Postgres whose URL lands in \`DATABASE_URL\`.
|
|
146
|
+
2. Set the variables from \`.env.example\`, generating the key and secret fresh.
|
|
147
|
+
3. Give the service a public domain, set it as \`GATE_ORIGIN\`, and smoke-test it:
|
|
148
|
+
|
|
149
|
+
\`\`\`bash
|
|
150
|
+
curl https://<your-gate>/health # { "ok": true }
|
|
151
|
+
curl https://<your-gate>/config # chainId must be the chain you meant; note "signer"
|
|
152
|
+
\`\`\`
|
|
153
|
+
|
|
154
|
+
A gate that boots is a gate whose signatures a swap can verify — boot refuses loudly on a
|
|
155
|
+
wrong-chain RPC, a demo key, or an economy your rewardBounds cannot honour. The full environment
|
|
156
|
+
table lives in \`@flayerlabs/gamemode-gate\`'s DEPLOY.md.
|
|
157
|
+
|
|
133
158
|
## Work with an agent
|
|
134
159
|
|
|
135
160
|
Ask the agent to read \`AGENTS.md\` before it edits the game. Use \`$build-game-mode\` when the
|
|
@@ -348,11 +373,79 @@ export interface PlayerView {
|
|
|
348
373
|
}
|
|
349
374
|
`,
|
|
350
375
|
|
|
351
|
-
'src/
|
|
376
|
+
'src/gate.ts': `import { startGate } from '@flayerlabs/gamemode-gate';
|
|
377
|
+
import { rules, type Config } from './game/rules.js';
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* Your game's own server, as a deployable process.
|
|
381
|
+
*
|
|
382
|
+
* A gate holds YOUR signing key and runs YOUR rules: a creator who launches a coin through your
|
|
383
|
+
* game writes your signer's address into their pool, and the chain trusts it for that pool alone.
|
|
384
|
+
* Flaunch never holds the key. \`startGate\` does the wiring — database migrations, the chain
|
|
385
|
+
* address book, boot refusals for anything misconfigured, /config for launch tooling — and this
|
|
386
|
+
* file stays what it should be: your round's economy, stated once.
|
|
387
|
+
*
|
|
388
|
+
* Everything environment-shaped comes from the environment; see .env.example.
|
|
389
|
+
*/
|
|
390
|
+
const config: Config = { rounds: 3, ceiling: 10, points: 500, roundMs: 10_000 };
|
|
391
|
+
|
|
392
|
+
const { port, signer } = await startGate(rules, config);
|
|
393
|
+
|
|
394
|
+
// The signer address is what a launch must name for this gate to adopt it — it belongs in the
|
|
395
|
+
// deploy log, where an operator can read it back without a debugger.
|
|
396
|
+
console.log(\`[gate] listening on \${port} — signer \${signer}\`);
|
|
397
|
+
`,
|
|
398
|
+
|
|
399
|
+
'.env.example': `# The environment contract for \`pnpm gate\` (full table: @flayerlabs/gamemode-gate DEPLOY.md).
|
|
400
|
+
# Generate SIGNER_PRIVATE_KEY and SESSION_SECRET fresh for every environment — a key shared
|
|
401
|
+
# between two environments lets one gate's bug mint authorisations against the other's pools.
|
|
402
|
+
|
|
403
|
+
# The one chain this process serves. 84532 (Base Sepolia) is in the address book.
|
|
404
|
+
CHAIN_ID=84532
|
|
405
|
+
# An RPC endpoint for that chain; boot checks its eth_chainId matches.
|
|
406
|
+
RPC_URL=https://sepolia.base.org
|
|
407
|
+
# Your key, generated by you, never shared. openssl rand -hex 32 (prefix with 0x).
|
|
408
|
+
SIGNER_PRIVATE_KEY=
|
|
409
|
+
# Postgres. Migrations run at boot.
|
|
410
|
+
DATABASE_URL=
|
|
411
|
+
# At least 32 characters. openssl rand -hex 32
|
|
412
|
+
SESSION_SECRET=
|
|
413
|
+
# The page the player is looking at when they sign in — the embedding site, not this gate.
|
|
414
|
+
SIGN_IN_DOMAIN=flaunch.gg
|
|
415
|
+
# This gate's own public https origin — the exact value you submit as the Gate URL.
|
|
416
|
+
GATE_ORIGIN=
|
|
417
|
+
# Comma-separated origins allowed to call this gate: your game's hosted origin(s) and the pages
|
|
418
|
+
# that embed it. One * may span a single DNS label.
|
|
419
|
+
ALLOWED_ORIGINS=https://*.games.moongate.com,https://flaunch.gg
|
|
420
|
+
`,
|
|
421
|
+
|
|
422
|
+
'railpack.json': `${JSON.stringify(
|
|
423
|
+
{
|
|
424
|
+
$schema: 'https://schema.railpack.com',
|
|
425
|
+
build: { builder: 'RAILPACK', buildCommand: 'pnpm install' },
|
|
426
|
+
deploy: { startCommand: 'pnpm gate' },
|
|
427
|
+
},
|
|
428
|
+
null,
|
|
429
|
+
2,
|
|
430
|
+
)}\n`,
|
|
431
|
+
|
|
432
|
+
'src/play.ts': `import { connectHost, createMockRoom, joinRoom, type Snapshot } from '@flayerlabs/gamemode-client';
|
|
352
433
|
import { rules, type Action, type Config, type PlayerView, type PublicView } from './game/rules.js';
|
|
353
434
|
|
|
354
435
|
const config: Config = { rounds: 3, ceiling: 10, points: 500, roundMs: 10_000 };
|
|
355
|
-
|
|
436
|
+
|
|
437
|
+
// Embedded on flaunch.gg, the page tells this game which gate and round to join and services its
|
|
438
|
+
// wallet calls over the frame bridge. Standing alone (pnpm dev), there is no page to ask — null is
|
|
439
|
+
// that answer, and the mock room runs the same rules with the chain and wallet faked.
|
|
440
|
+
const embedded = await connectHost();
|
|
441
|
+
const room = embedded
|
|
442
|
+
? await joinRoom<PublicView, PlayerView, Action>({
|
|
443
|
+
gateUrl: embedded.context.gateUrl,
|
|
444
|
+
roundId: embedded.context.roundId,
|
|
445
|
+
host: embedded.host,
|
|
446
|
+
...(embedded.context.launch ? { launch: embedded.context.launch } : {}),
|
|
447
|
+
})
|
|
448
|
+
: createMockRoom(rules, { config, lobbyMs: 1_000, roundMs: 60_000 });
|
|
356
449
|
const app = document.querySelector<HTMLElement>('#app');
|
|
357
450
|
if (!app) throw new Error('the page needs an #app element');
|
|
358
451
|
const gameRoot = app;
|