@flayerlabs/gamemode-cli 0.3.1 → 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 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/play.ts': `import { createMockRoom, type Snapshot } from '@flayerlabs/gamemode-client';
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
- const room = createMockRoom(rules, { config, lobbyMs: 1_000, roundMs: 60_000 });
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;
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flayerlabs/gamemode-cli",
3
- "version": "0.3.1",
3
+ "version": "0.4.0",
4
4
  "description": "Create and validate Flaunch Game Modes",
5
5
  "license": "MIT",
6
6
  "author": "Flayer Labs",
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/play.ts': `import { createMockRoom, type Snapshot } from '@flayerlabs/gamemode-client';
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
- const room = createMockRoom(rules, { config, lobbyMs: 1_000, roundMs: 60_000 });
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;