@flayerlabs/gamemode-cli 0.1.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/LICENSE ADDED
@@ -0,0 +1,9 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Flayer Labs
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
+
7
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
+
9
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,50 @@
1
+ # @flayerlabs/gamemode-cli
2
+
3
+ Create a playable Flaunch Game Mode and check that its rules use deterministic inputs.
4
+
5
+ ## Requirements
6
+
7
+ You need Node.js 20 or later and pnpm.
8
+
9
+ ## Create a game
10
+
11
+ Run:
12
+
13
+ ```bash
14
+ pnpm dlx @flayerlabs/gamemode-cli new my-game
15
+ cd my-game
16
+ pnpm install
17
+ pnpm dev
18
+ ```
19
+
20
+ The command creates a working number game. It includes:
21
+
22
+ - server-authoritative rules in `src/game/rules.ts`
23
+ - a browser game that uses the offline room
24
+ - rule tests and a deterministic rules check
25
+ - an `AGENTS.md` file with the rules an agent must follow
26
+ - a `$build-game-mode` repository skill for supported agents
27
+
28
+ The offline room runs the real rules without a gate, chain or wallet. The scaffold does not create a
29
+ live gate or trusted wallet integration.
30
+
31
+ ## Check an existing rules file
32
+
33
+ Install the CLI in the game project, then run the check:
34
+
35
+ ```bash
36
+ pnpm add --save-dev @flayerlabs/gamemode-cli
37
+ pnpm exec gamemode check src/game/rules.ts
38
+ ```
39
+
40
+ The check rejects known uses of clocks, random values, network access and system access. It also
41
+ rejects top-level `let` and `var` declarations. Run the project's tests and type check as well.
42
+
43
+ ## Build a secure game
44
+
45
+ Use the repository guidance to continue:
46
+
47
+ - [build a Game Mode](https://github.com/flayerlabs/gamemode-sdk/blob/main/docs/guides/build-a-game.md)
48
+ - [read the generated-game agent rules](https://github.com/flayerlabs/gamemode-sdk/blob/main/packages/cli/templates/game/AGENTS.template.md)
49
+ - [compare the quiz and arrow examples](https://github.com/flayerlabs/gamemode-sdk/blob/main/examples/README.md)
50
+ - [review the Game Mode SDK repository](https://github.com/flayerlabs/gamemode-sdk)
package/dist/AGENTS.md ADDED
@@ -0,0 +1,213 @@
1
+ # Build a Flaunch Game Mode
2
+
3
+ This repository contains a game that awards points during a Flaunch token launch. The game can use
4
+ any presentation or control scheme. The server rules decide which actions earn points.
5
+
6
+ Use `$build-game-mode` when the skill is available. It gives you the safest order for changing the
7
+ rules, tests and browser game.
8
+
9
+ ## Know what runs where
10
+
11
+ The project starts with these files:
12
+
13
+ ```text
14
+ src/game/rules.ts pure server rules and scoring
15
+ src/play.ts browser game and room subscriptions
16
+ test/rules.test.ts replay, scoring and secrecy checks
17
+ ```
18
+
19
+ The browser proposes actions. It never awards points.
20
+
21
+ The gate runs `src/game/rules.ts` and stores every accepted event. A replay of the same commands
22
+ must always produce the same state and score.
23
+
24
+ ## Use the development loop
25
+
26
+ Run these commands:
27
+
28
+ ```bash
29
+ pnpm dev
30
+ pnpm test
31
+ pnpm typecheck
32
+ ```
33
+
34
+ `pnpm dev` runs the real rules in a local mock room. It needs no chain, server or wallet.
35
+
36
+ `pnpm test` runs the rules check and game tests. `pnpm typecheck` checks the whole project. Both
37
+ must pass before you finish.
38
+
39
+ The scaffold does not include a live gate. Add `dev:live` only when you have a real gate harness to
40
+ run. Follow the [live gate guide](https://github.com/flayerlabs/gamemode-sdk/blob/main/docs/guides/run-a-gate.md)
41
+ instead of inventing a second protocol.
42
+
43
+ ## Keep the rules deterministic
44
+
45
+ Keep `decide()` and `evolve()` pure.
46
+
47
+ Do not use these values or operations in the rules module:
48
+
49
+ - a process or device clock
50
+ - random values that do not come from the supplied seed
51
+ - network, file or database access
52
+ - environment variables
53
+ - mutable module-level state
54
+
55
+ Time arrives as `command.at`. Player randomness arrives as a join seed. Derive any game variation
56
+ from those values.
57
+
58
+ `gamemode check` catches common nondeterministic calls, Node imports and module-level `let` or
59
+ `var`. It is a guard, not a proof. Keep replay tests for every scoring path.
60
+
61
+ ## Treat every action as untrusted
62
+
63
+ `room.send(action)` proposes an action. `decide()` accepts or refuses it.
64
+
65
+ Do not send a score, hit, kill or trusted timestamp from the browser. Send the smallest input the
66
+ server needs to verify the result.
67
+
68
+ For a shooter, send bounded movement, aim and fire input. The rules should enforce sequence,
69
+ cooldown, ammunition and hit logic. The browser may predict and animate at its own frame rate.
70
+
71
+ Use `parseAction()` to reject malformed and oversized values before `decide()` sees them. Use game
72
+ state in `decide()` to enforce limits such as one answer, one shot or one batch at a time.
73
+
74
+ ## Keep private state out of public types
75
+
76
+ `publicView()` goes to every player and spectator. `playerView()` goes to one player.
77
+
78
+ If a value must stay private, leave it out of the `publicView()` return type. Do not return it and
79
+ filter it later.
80
+
81
+ For example, keep a quiz answer out of the public type until the reveal. Add a test that serialises
82
+ the public view and proves the secret is absent.
83
+
84
+ ## Choose when to award points
85
+
86
+ Award points when the result should become known.
87
+
88
+ An arrow score can be public as soon as the arrow lands. Award it with the accepted action.
89
+
90
+ A quiz answer should stay private until the reveal. Store the answer event first. Award points from
91
+ the later wake that reveals the result. Otherwise the player's allowance exposes the answer early.
92
+
93
+ ## Keep wallets outside the game
94
+
95
+ Creator code must not access a wallet, private key, RPC endpoint or transaction calldata.
96
+
97
+ Use the room economy:
98
+
99
+ ```ts
100
+ await room.economy.buy(maxSpendWei)
101
+ ```
102
+
103
+ The trusted parent validates the gate authorisation, builds the transaction and asks the player to
104
+ approve it. Game code never handles `hookData`.
105
+
106
+ ## Declare the maximum score
107
+
108
+ `rewardBounds(config)` must return the highest score one player can earn.
109
+
110
+ Derive the number from the rules. For example, multiply the shot budget by the best shot, or add
111
+ the points for every question.
112
+
113
+ Do not use an estimate. The platform uses this bound to check the launch wallet cap. Points above
114
+ an understated bound cannot become spending allowance.
115
+
116
+ ## Build fixed timelines once
117
+
118
+ Use `scheduleWithin()` for a fixed series of phases:
119
+
120
+ ```ts
121
+ import { scheduleWithin } from '@flayerlabs/gamemode-spec/schedule'
122
+ ```
123
+
124
+ It checks that the full schedule fits between `opensAt` and `closesAt`. It also gives each phase an
125
+ absolute time, so a delayed server wake does not move every later phase.
126
+
127
+ Every `nextWakeAt()` value must be later than the wake being served. Return `null` when the game has
128
+ no more timed work.
129
+
130
+ ## Model readiness as a game rule
131
+
132
+ Presence tells you how many players are connected. It does not start the game.
133
+
134
+ If players must ready up, define a ready action and store it in game state:
135
+
136
+ ```ts
137
+ { kind: 'ready', ready: true }
138
+ ```
139
+
140
+ This lets `decide()` apply the rule. It also makes readiness replayable after a restart.
141
+
142
+ ## Use the module contract
143
+
144
+ Implement the complete rules module:
145
+
146
+ ```ts
147
+ export const rules = defineGame<Config, State, Event, Action, PublicView, PlayerView>({
148
+ id: 'my-game',
149
+ parseAction,
150
+ initRound,
151
+ decide,
152
+ evolve,
153
+ publicView,
154
+ playerView,
155
+ nextWakeAt,
156
+ rewardBounds,
157
+ })
158
+ ```
159
+
160
+ Commands reaching `decide()` are `join`, `leave`, `action` and `wake`. Each command includes the
161
+ authoritative `at` time.
162
+
163
+ A decision returns events and may return awards. `evolve()` applies events in order. A refusal
164
+ returns a stable code:
165
+
166
+ ```ts
167
+ return { refuse: 'answer.window_closed' }
168
+ ```
169
+
170
+ Do not rename a refusal code after use. Add a new code. Keep player-facing copy in the browser.
171
+
172
+ ## Use the room capabilities
173
+
174
+ The browser uses this surface:
175
+
176
+ ```ts
177
+ room.subscribe(render)
178
+ await room.send(action)
179
+ room.now()
180
+
181
+ room.launch.current()
182
+ room.connection.subscribe(renderConnection)
183
+ room.economy.subscribe(renderEconomy)
184
+ room.economy.buy(maxSpendWei)
185
+ room.market.subscribe(renderMarket)
186
+ await room.identity.resolve(playerIds)
187
+ room.presence.subscribe(renderPresence)
188
+ room.social.react(id)
189
+ room.social.onReaction(renderReaction)
190
+ ```
191
+
192
+ Use `room.now()` for every visible timer. Do not use `Date.now()` in a countdown.
193
+
194
+ The local mock implements the same room surface. Build against it before adding live
195
+ infrastructure.
196
+
197
+ ## Finish with evidence
198
+
199
+ Before you finish, check that:
200
+
201
+ - correct play earns the expected points
202
+ - invalid and repeated actions are refused
203
+ - action sizes and rates are bounded
204
+ - public views contain no secrets
205
+ - private views contain only that player's data
206
+ - delayed wakes keep the authored schedule
207
+ - replaying the same commands gives the same result
208
+ - `rewardBounds()` matches the best possible score
209
+ - `pnpm test` passes
210
+ - `pnpm typecheck` passes
211
+
212
+ If the SDK contract does not fit the game, stop and explain the missing capability. Do not bypass a
213
+ trust boundary in browser code.
@@ -0,0 +1,57 @@
1
+ ---
2
+ name: build-game-mode
3
+ description: Builds or changes a creator Game Mode with @flayerlabs/gamemode-spec and @flayerlabs/gamemode-client. Use when working on rules, actions, views, scoring, room capabilities, browser play, mock rooms or conformance tests in this SDK or a generated game. Do not use for trusted parent, registry writer or platform-owned gate changes.
4
+ ---
5
+
6
+ # Build a Game Mode
7
+
8
+ Follow the local `AGENTS.md` as the contract. Keep this skill focused on the workflow and do not replace those rules with guesses.
9
+
10
+ ## Find the working context
11
+
12
+ 1. Read the nearest `AGENTS.md` in full before editing anything.
13
+ 2. Inspect `package.json`, the game rules and their tests.
14
+ 3. If this is the SDK, read the closest reference game. Use `examples/arrow` for continuous public scoring. Use `examples/quiz` for hidden answers, phases and deferred awards.
15
+ 4. If this is a generated game, keep the shipped rules and tests as the working starting point.
16
+
17
+ ## Define the game before editing
18
+
19
+ State assumptions and set verifiable success criteria for:
20
+
21
+ - the authoritative state and accepted commands
22
+ - the source of time and deterministic variation
23
+ - hidden values and their reveal point
24
+ - action shape, size, cooldown and round limits
25
+ - acceptance, scoring and award timing
26
+ - the maximum possible reward
27
+ - refusal codes and observable outcomes
28
+
29
+ If the SDK interface cannot represent the game safely, stop and describe the missing seam. Do not build a parallel protocol.
30
+
31
+ ## Build the smallest complete loop
32
+
33
+ 1. Write or update tests for the intended rule change.
34
+ 2. Define every shared shape once. Import SDK contract types instead of copying them.
35
+ 3. Bound malformed and oversized input in `parseAction`.
36
+ 4. Implement `decide` and `evolve` as pure functions. Derive time and variation only from supplied inputs.
37
+ 5. Make hidden data absent from public view types. Reveal it only when the rules allow.
38
+ 6. Keep action acceptance separate from awards when an early balance change would leak a result.
39
+ 7. Calculate `rewardBounds` from the rules, including the best possible play.
40
+ 8. Build the browser around `Room` capabilities. Keep scoring, wallets, keys and calldata out of creator client code.
41
+ 9. Prove the loop with the mock room before adding a project-specific live harness.
42
+
43
+ ## Verify the result
44
+
45
+ In a generated game:
46
+
47
+ 1. Run `pnpm test`.
48
+ 2. Run `pnpm typecheck`.
49
+ 3. Run `pnpm dev` and check the changed play loop when presentation changed.
50
+
51
+ In the SDK:
52
+
53
+ 1. Run the affected example or package tests and typecheck.
54
+ 2. Run the root typecheck and required repository tests.
55
+ 3. Run `pnpm release:check` when a published contract or package surface changed.
56
+
57
+ Check replay, reconnect, hidden-data leaks, late actions, duplicate actions, oversized input, wake timing and the reward ceiling where they apply. If infrastructure blocks a check, report the exact check you could not run. Do not weaken or skip it.
@@ -0,0 +1,4 @@
1
+ interface:
2
+ display_name: "Build Game Mode"
3
+ short_description: "Build secure creator games with the SDK"
4
+ default_prompt: "Use $build-game-mode to build or change this Flaunch Game Mode and verify its rules."
package/dist/lint.d.ts ADDED
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Checking that a game's rules are pure.
3
+ *
4
+ * `decide` and `evolve` must give the same answer for the same input, forever. Replay, reconnect,
5
+ * audit and anti-cheat all rest on it, and every one of them fails quietly rather than loudly when
6
+ * it stops being true — a round that scores differently on replay does not announce itself.
7
+ *
8
+ * Text rather than a full parse, deliberately. An agent writing `Date.now()` in a reducer is the
9
+ * failure this catches, and it catches that perfectly. A syntax tree would catch cleverer evasions
10
+ * that nobody is attempting, at the cost of a dependency and a lot of code.
11
+ */
12
+ export interface Finding {
13
+ line: number;
14
+ found: string;
15
+ why: string;
16
+ }
17
+ export declare function lintRules(source: string): Finding[];
18
+ /** What a person reads. No codes, no rule names — the line, what is there, and what to do. */
19
+ export declare function report(file: string, findings: Finding[]): string;
20
+ //# sourceMappingURL=lint.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lint.d.ts","sourceRoot":"","sources":["../src/lint.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;CACb;AAmCD,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,EAAE,CAoBnD;AAED,8FAA8F;AAC9F,wBAAgB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAG,MAAM,CAUhE"}
package/dist/lint.js ADDED
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Checking that a game's rules are pure.
3
+ *
4
+ * `decide` and `evolve` must give the same answer for the same input, forever. Replay, reconnect,
5
+ * audit and anti-cheat all rest on it, and every one of them fails quietly rather than loudly when
6
+ * it stops being true — a round that scores differently on replay does not announce itself.
7
+ *
8
+ * Text rather than a full parse, deliberately. An agent writing `Date.now()` in a reducer is the
9
+ * failure this catches, and it catches that perfectly. A syntax tree would catch cleverer evasions
10
+ * that nobody is attempting, at the cost of a dependency and a lot of code.
11
+ */
12
+ const RULES = [
13
+ { pattern: /\bDate\s*\.\s*now\s*\(/, why: 'time must come from the command, as command.at' },
14
+ { pattern: /\bnew\s+Date\s*\(/, why: 'time must come from the command, as command.at' },
15
+ { pattern: /\bperformance\s*\.\s*now\s*\(/, why: 'time must come from the command, as command.at' },
16
+ { pattern: /\bMath\s*\.\s*random\s*\(/, why: 'randomness must be derived from the seed on the join command' },
17
+ { pattern: /\bcrypto\s*\.\s*(getRandomValues|randomUUID)\s*\(/, why: 'randomness must be derived from the seed on the join command' },
18
+ { pattern: /\bfetch\s*\(/, why: 'rules cannot reach the network' },
19
+ { pattern: /\brequire\s*\(\s*['"]node:/, why: 'rules cannot reach the system' },
20
+ { pattern: /\bfrom\s+['"]node:/, why: 'rules cannot reach the system' },
21
+ { pattern: /\bprocess\s*\.\s*env\b/, why: 'rules cannot read configuration; it belongs in the round config' },
22
+ { pattern: /\bglobalThis\b/, why: 'rules cannot reach outside themselves' },
23
+ ];
24
+ /** Lines that are only a comment. A rule named in prose is documentation, not a violation. */
25
+ function isComment(line) {
26
+ const trimmed = line.trim();
27
+ return trimmed.startsWith('//') || trimmed.startsWith('*') || trimmed.startsWith('/*');
28
+ }
29
+ /**
30
+ * Module-level mutable state: a `let` or `var` at column zero.
31
+ *
32
+ * Two calls with the same arguments must not differ because of something one of them left behind.
33
+ */
34
+ function moduleState(line) {
35
+ return /^(let|var)\s+\w/.test(line);
36
+ }
37
+ export function lintRules(source) {
38
+ const findings = [];
39
+ source.split('\n').forEach((line, index) => {
40
+ if (isComment(line))
41
+ return;
42
+ for (const rule of RULES) {
43
+ const match = rule.pattern.exec(line);
44
+ if (match)
45
+ findings.push({ line: index + 1, found: match[0].replace(/\s+/g, ''), why: rule.why });
46
+ }
47
+ if (moduleState(line)) {
48
+ findings.push({
49
+ line: index + 1,
50
+ found: line.trim().split(/\s+/).slice(0, 2).join(' '),
51
+ why: 'rules cannot keep state between calls; it belongs in the game state',
52
+ });
53
+ }
54
+ });
55
+ return findings;
56
+ }
57
+ /** What a person reads. No codes, no rule names — the line, what is there, and what to do. */
58
+ export function report(file, findings) {
59
+ if (findings.length === 0)
60
+ return `${file}: rules look pure.`;
61
+ return [
62
+ `${file}: ${findings.length} thing${findings.length === 1 ? '' : 's'} to fix.`,
63
+ '',
64
+ ...findings.map((f) => ` line ${f.line}: ${f.found} — ${f.why}`),
65
+ '',
66
+ 'These have to go. Same input, same answer, every time — replay, reconnect and anti-cheat',
67
+ 'all depend on it, and each of them fails quietly rather than loudly when it stops being true.',
68
+ ].join('\n');
69
+ }
70
+ //# sourceMappingURL=lint.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lint.js","sourceRoot":"","sources":["../src/lint.ts"],"names":[],"mappings":"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"}
package/dist/main.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=main.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":""}
package/dist/main.js ADDED
@@ -0,0 +1,65 @@
1
+ #!/usr/bin/env node
2
+ import { readFileSync } from 'node:fs';
3
+ import { lintRules, report } from './lint.js';
4
+ import { scaffold } from './scaffold.js';
5
+ /**
6
+ * The whole command line.
7
+ *
8
+ * Four things, because four is what it takes to go from nothing to a game somebody can play, and a
9
+ * fifth would be something to learn rather than something to use.
10
+ */
11
+ const USAGE = `
12
+ gamemode — build a game for a Flaunch launch
13
+
14
+ gamemode new <name> start a new game you can play straight away
15
+ gamemode check [file] check your rules are pure (default: src/game/rules.ts)
16
+
17
+ Once you have a game:
18
+
19
+ pnpm dev play it, with no server and no wallet
20
+ pnpm test check it still works
21
+ `;
22
+ async function main(argv) {
23
+ const [command, argument] = argv;
24
+ if (!command || command === 'help' || command === '--help') {
25
+ console.log(USAGE.trim());
26
+ return 0;
27
+ }
28
+ if (command === 'new') {
29
+ if (!argument) {
30
+ console.error('What should it be called? gamemode new my-game');
31
+ return 1;
32
+ }
33
+ const created = await scaffold(argument);
34
+ console.log(`\n Made ${created}\n`);
35
+ console.log(' Next:');
36
+ console.log(` cd ${argument}`);
37
+ console.log(' pnpm install');
38
+ console.log(' pnpm dev\n');
39
+ return 0;
40
+ }
41
+ if (command === 'check') {
42
+ const file = argument ?? 'src/game/rules.ts';
43
+ let source;
44
+ try {
45
+ source = readFileSync(file, 'utf8');
46
+ }
47
+ catch {
48
+ console.error(`Could not read ${file}.`);
49
+ return 1;
50
+ }
51
+ const findings = lintRules(source);
52
+ console.log(report(file, findings));
53
+ return findings.length === 0 ? 0 : 1;
54
+ }
55
+ console.error(`Not a command: ${command}`);
56
+ console.error(USAGE.trim());
57
+ return 1;
58
+ }
59
+ main(process.argv.slice(2))
60
+ .then((code) => process.exit(code))
61
+ .catch((error) => {
62
+ console.error(error instanceof Error ? error.message : error);
63
+ process.exit(1);
64
+ });
65
+ //# sourceMappingURL=main.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"main.js","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAC9C,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAEzC;;;;;GAKG;AACH,MAAM,KAAK,GAAG;;;;;;;;;;CAUb,CAAC;AAEF,KAAK,UAAU,IAAI,CAAC,IAAc;IAChC,MAAM,CAAC,OAAO,EAAE,QAAQ,CAAC,GAAG,IAAI,CAAC;IAEjC,IAAI,CAAC,OAAO,IAAI,OAAO,KAAK,MAAM,IAAI,OAAO,KAAK,QAAQ,EAAE,CAAC;QAC3D,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;QAC1B,OAAO,CAAC,CAAC;IACX,CAAC;IAED,IAAI,OAAO,KAAK,KAAK,EAAE,CAAC;QACtB,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,OAAO,CAAC,KAAK,CAAC,iDAAiD,CAAC,CAAC;YACjE,OAAO,CAAC,CAAC;QACX,CAAC;QACD,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,QAAQ,CAAC,CAAC;QACzC,OAAO,CAAC,GAAG,CAAC,YAAY,OAAO,IAAI,CAAC,CAAC;QACrC,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QACvB,OAAO,CAAC,GAAG,CAAC,UAAU,QAAQ,EAAE,CAAC,CAAC;QAClC,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC;QAChC,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC;QAC9B,OAAO,CAAC,CAAC;IACX,CAAC;IAED,IAAI,OAAO,KAAK,OAAO,EAAE,CAAC;QACxB,MAAM,IAAI,GAAG,QAAQ,IAAI,mBAAmB,CAAC;QAC7C,IAAI,MAAc,CAAC;QACnB,IAAI,CAAC;YACH,MAAM,GAAG,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QACtC,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,CAAC,KAAK,CAAC,kBAAkB,IAAI,GAAG,CAAC,CAAC;YACzC,OAAO,CAAC,CAAC;QACX,CAAC;QACD,MAAM,QAAQ,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC;QACnC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC;QACpC,OAAO,QAAQ,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACvC,CAAC;IAED,OAAO,CAAC,KAAK,CAAC,kBAAkB,OAAO,EAAE,CAAC,CAAC;IAC3C,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;IAC5B,OAAO,CAAC,CAAC;AACX,CAAC;AAED,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;KACxB,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;KAClC,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;IACxB,OAAO,CAAC,KAAK,CAAC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;IAC9D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * A new game, playable before it does anything.
3
+ *
4
+ * The first command has to produce something that runs. A scaffold that needs a database, a chain
5
+ * and a wallet before it shows anything is a scaffold most people abandon, and an agent given one
6
+ * has nothing to check its work against.
7
+ *
8
+ * So what comes out is a complete, working guessing game: rules, a page, and tests that pass. It is
9
+ * meant to be edited into something else, not read and replaced.
10
+ */
11
+ export declare function scaffold(name: string): Promise<string>;
12
+ export declare function dependencyVersionFor(version: unknown): string;
13
+ //# sourceMappingURL=scaffold.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scaffold.d.ts","sourceRoot":"","sources":["../src/scaffold.ts"],"names":[],"mappings":"AAGA;;;;;;;;;GASG;AACH,wBAAsB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAc5D;AA+BD,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAW7D"}