@bitmagic/cli 0.1.22 → 0.1.24

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.
Files changed (49) hide show
  1. package/README.md +53 -19
  2. package/dist/cli.d.ts +10 -6
  3. package/dist/cli.js +2 -2
  4. package/dist/cli.js.map +1 -1
  5. package/dist/commands/dev.d.ts +5 -1
  6. package/dist/commands/dev.js +82 -13
  7. package/dist/commands/dev.js.map +1 -1
  8. package/dist/commands/generate.js +3 -3
  9. package/dist/commands/generate.js.map +1 -1
  10. package/dist/commands/reload.d.ts +10 -0
  11. package/dist/commands/reload.js +82 -0
  12. package/dist/commands/reload.js.map +1 -0
  13. package/dist/commands/upgrade.js +6 -0
  14. package/dist/commands/upgrade.js.map +1 -1
  15. package/dist/editor/journal.js +2 -2
  16. package/dist/editor/reload-bus.d.ts +38 -0
  17. package/dist/editor/reload-bus.js +66 -0
  18. package/dist/editor/reload-bus.js.map +1 -0
  19. package/dist/editor/server.d.ts +7 -0
  20. package/dist/editor/server.js +51 -9
  21. package/dist/editor/server.js.map +1 -1
  22. package/dist/editor/shell-page.d.ts +24 -5
  23. package/dist/editor/shell-page.js +295 -36
  24. package/dist/editor/shell-page.js.map +1 -1
  25. package/dist/editor/watch.d.ts +101 -0
  26. package/dist/editor/watch.js +228 -0
  27. package/dist/editor/watch.js.map +1 -0
  28. package/dist/local-port.d.ts +10 -0
  29. package/dist/local-port.js +23 -0
  30. package/dist/local-port.js.map +1 -1
  31. package/dist/project/dev-handle.d.ts +14 -0
  32. package/dist/project/dev-handle.js +53 -0
  33. package/dist/project/dev-handle.js.map +1 -0
  34. package/dist/scaffold/claude-settings.d.ts +41 -0
  35. package/dist/scaffold/claude-settings.js +98 -0
  36. package/dist/scaffold/claude-settings.js.map +1 -0
  37. package/dist/scaffold/project-files.js +20 -8
  38. package/dist/scaffold/project-files.js.map +1 -1
  39. package/dist/scaffold/project.js +5 -0
  40. package/dist/scaffold/project.js.map +1 -1
  41. package/dist/scaffold/upgrade-project.d.ts +6 -0
  42. package/dist/scaffold/upgrade-project.js +19 -1
  43. package/dist/scaffold/upgrade-project.js.map +1 -1
  44. package/dist/verify/browser.js +3 -1
  45. package/dist/verify/browser.js.map +1 -1
  46. package/package.json +1 -1
  47. package/dist/commands/edit.d.ts +0 -10
  48. package/dist/commands/edit.js +0 -148
  49. package/dist/commands/edit.js.map +0 -1
@@ -0,0 +1,228 @@
1
+ /**
2
+ * Deciding *when* the browser should reload.
3
+ *
4
+ * The hard part is not noticing a change — `fs.watch` does that — it is not reloading at the wrong
5
+ * moment. Three wrong moments, each of which this file exists to avoid:
6
+ *
7
+ * 1. **Mid-burst.** An agent editing five files produces five reloads unless the writes are let
8
+ * go quiet first. Hence the debounce.
9
+ * 2. **Mid-compile.** A `.ts` edit changes `src/` long before `tsc --watch` finishes emitting
10
+ * `dist/`, and every module the project loads resolves into `dist/`. Reloading on the source
11
+ * write shows the creator the *old* build and reads as "my change did nothing". Hence the
12
+ * compile gate: reloads are held while a compile is open and released when it lands clean.
13
+ * 3. **After a failed compile.** `dist/` still holds the last good emit, so a reload would show
14
+ * working code for broken source — the most confusing outcome of the three. The pending reload
15
+ * is dropped and the shell is told the build failed instead.
16
+ *
17
+ * `ReloadCoordinator` holds all of that and touches no I/O, so it is testable with fake timers and
18
+ * no filesystem. `watchProject` and `pipeTscWatch` are the thin I/O shells that feed it.
19
+ */
20
+ import { spawn } from 'child_process';
21
+ import * as fs from 'fs';
22
+ import * as path from 'path';
23
+ /**
24
+ * How long the writes must stay quiet before a reload fires.
25
+ *
26
+ * Long enough that `tsc --watch` (which has its own ~250 ms debounce before it reports
27
+ * `File change detected`) has almost always opened its compile by the time this elapses, so the
28
+ * gate below actually gets to hold the reload. Too short and a `.ts` edit produces two reloads —
29
+ * one stale, one correct — instead of one.
30
+ */
31
+ export const RELOAD_QUIET_MS = 700;
32
+ /**
33
+ * How long after one of our own writes to ignore file events.
34
+ *
35
+ * `/api/scene/save` and the HQ generation both write `world.json`, and a reload fired at the
36
+ * creator's own gizmo drag would throw away the drag that caused it. `fs.watch` delivers
37
+ * asynchronously but promptly, so this only has to outlast the delivery, not the write.
38
+ */
39
+ export const OWN_WRITE_GRACE_MS = 400;
40
+ export class ReloadCoordinator {
41
+ options;
42
+ timer = null;
43
+ pendingReason = null;
44
+ compiling = false;
45
+ suppressUntil = 0;
46
+ stopped = false;
47
+ constructor(options) {
48
+ this.options = { quietMs: RELOAD_QUIET_MS, ...options };
49
+ }
50
+ /** A file under a watched directory changed. Arms — or re-arms — the quiet window. */
51
+ noteFileChange(reason) {
52
+ if (this.stopped)
53
+ return;
54
+ if (Date.now() < this.suppressUntil)
55
+ return;
56
+ this.pendingReason = reason;
57
+ if (this.timer)
58
+ clearTimeout(this.timer);
59
+ this.timer = setTimeout(() => {
60
+ this.timer = null;
61
+ this.release();
62
+ }, this.options.quietMs);
63
+ }
64
+ /**
65
+ * Reload as soon as it is safe to — now, or the moment the compile in flight lands clean.
66
+ *
67
+ * The wait is the whole point for `bitmagic reload`: an agent's stop hook fires immediately after
68
+ * its last edit, which is precisely when `tsc` is still compiling it. Returns whether the reload
69
+ * was queued behind a compile rather than sent, so the command can say which happened.
70
+ */
71
+ requestNow(reason) {
72
+ if (this.stopped)
73
+ return { queued: false };
74
+ if (this.timer) {
75
+ clearTimeout(this.timer);
76
+ this.timer = null;
77
+ }
78
+ this.pendingReason = reason;
79
+ this.release();
80
+ return { queued: this.pendingReason !== null };
81
+ }
82
+ /** The sidecar wrote `world.json` itself; the resulting file events are not news. */
83
+ noteOwnWrite() {
84
+ this.suppressUntil = Date.now() + OWN_WRITE_GRACE_MS;
85
+ }
86
+ noteCompileStart() {
87
+ if (this.stopped || this.compiling)
88
+ return;
89
+ this.compiling = true;
90
+ this.options.onBuild('building');
91
+ }
92
+ noteCompileEnd(errorCount) {
93
+ if (this.stopped)
94
+ return;
95
+ this.compiling = false;
96
+ if (errorCount > 0) {
97
+ // Dropped, not deferred: `dist/` holds the last good emit, so reloading would show working
98
+ // code for broken source. The shell says so instead, and the next clean compile reloads.
99
+ this.pendingReason = null;
100
+ this.options.onBuild('failed', `${errorCount} TypeScript error${errorCount === 1 ? '' : 's'} — the game was not rebuilt.`);
101
+ return;
102
+ }
103
+ this.options.onBuild('ok');
104
+ this.release();
105
+ }
106
+ stop() {
107
+ this.stopped = true;
108
+ if (this.timer)
109
+ clearTimeout(this.timer);
110
+ this.timer = null;
111
+ this.pendingReason = null;
112
+ }
113
+ /** Send the pending reload, unless a compile is still open — then it waits for `noteCompileEnd`. */
114
+ release() {
115
+ if (this.pendingReason === null || this.compiling)
116
+ return;
117
+ const reason = this.pendingReason;
118
+ this.pendingReason = null;
119
+ this.options.onReload(reason);
120
+ }
121
+ }
122
+ /** Path segments that never mean "the project changed". */
123
+ const IGNORED_SEGMENTS = new Set(['node_modules', '.bitmagic', '.vite-cache', '.git']);
124
+ /**
125
+ * Editors write through temporary siblings — vim's `4913`/`~`, JetBrains' `___jb_tmp___`, an
126
+ * atomic-rename `.tmp`. Reloading on those means reloading on a file that no longer exists.
127
+ */
128
+ function isIgnoredPath(relative) {
129
+ const segments = relative.split(path.sep);
130
+ if (segments.some((segment) => IGNORED_SEGMENTS.has(segment)))
131
+ return true;
132
+ const name = segments[segments.length - 1] ?? '';
133
+ return name.startsWith('.') || name.endsWith('~') || name.includes('___jb_') || name.endsWith('.tmp');
134
+ }
135
+ /**
136
+ * Watch the two directories that decide what the browser shows: `src/` (what the agent and the
137
+ * creator edit — including `work/world.json`) and `dist/` (what the page actually loads).
138
+ *
139
+ * `dist/` is watched as well as `src/` even though the compile gate already covers the normal
140
+ * `tsc --watch` path, because it is what a build run outside this process — a manual `tsc`, an
141
+ * editor's own compiler — lands in, and that should reach the browser too.
142
+ *
143
+ * A directory that cannot be watched is skipped with a note rather than failing the command: a
144
+ * missing `dist/` means "not built yet", and losing auto-reload is a smaller loss than losing
145
+ * `bitmagic dev`.
146
+ */
147
+ export function watchProject(root, coordinator, log) {
148
+ const watchers = [];
149
+ for (const dir of ['src', 'dist']) {
150
+ const absolute = path.join(root, dir);
151
+ if (!fs.existsSync(absolute))
152
+ continue;
153
+ try {
154
+ const watcher = fs.watch(absolute, { recursive: true }, (_event, filename) => {
155
+ const relative = filename === null ? '' : String(filename);
156
+ if (relative !== '' && isIgnoredPath(relative))
157
+ return;
158
+ coordinator.noteFileChange(path.posix.join(dir, relative.split(path.sep).join('/')));
159
+ });
160
+ watcher.on('error', (error) => {
161
+ log(`[dev] stopped watching ${dir}/: ${error.message}`);
162
+ });
163
+ watchers.push(watcher);
164
+ }
165
+ catch (error) {
166
+ const message = error instanceof Error ? error.message : String(error);
167
+ log(`[dev] could not watch ${dir}/ (${message}) — reload on change is off for it.`);
168
+ }
169
+ }
170
+ return {
171
+ close: () => {
172
+ for (const watcher of watchers)
173
+ watcher.close();
174
+ },
175
+ };
176
+ }
177
+ /** tsc colourises when it thinks it is on a terminal; the markers below must match either way. */
178
+ // eslint-disable-next-line no-control-regex
179
+ const ANSI = /\u001B\[[0-9;]*m/g;
180
+ const COMPILE_STARTED = /(File change detected|Starting compilation in watch mode)/;
181
+ const COMPILE_FINISHED = /Found (\d+) error/;
182
+ /**
183
+ * Feed one line of `tsc --watch` output to the coordinator. Exported for its test — the markers are
184
+ * tsc's own wording, and a version that changes them would otherwise break auto-reload silently.
185
+ */
186
+ export function noteTscLine(line, coordinator) {
187
+ const plain = line.replace(ANSI, '');
188
+ const finished = COMPILE_FINISHED.exec(plain);
189
+ if (finished) {
190
+ coordinator.noteCompileEnd(Number(finished[1]));
191
+ return;
192
+ }
193
+ if (COMPILE_STARTED.test(plain))
194
+ coordinator.noteCompileStart();
195
+ }
196
+ /**
197
+ * Spawn `tsc --watch` with its output piped through us instead of inherited.
198
+ *
199
+ * Piping is what makes the compile gate possible — the only signal that a build finished, and
200
+ * whether it finished clean, is tsc's own `Found N errors` line. Every line is re-echoed verbatim,
201
+ * so the creator sees exactly what an inherited stdio would have shown, and `--pretty` is forced
202
+ * back on when our own stdout is a terminal (tsc drops colour the moment it is piped).
203
+ */
204
+ export function pipeTscWatch(tsc, cwd, coordinator) {
205
+ const args = ['--watch', '--preserveWatchOutput'];
206
+ if (process.stdout.isTTY)
207
+ args.push('--pretty');
208
+ const child = spawn(tsc, args, { cwd, stdio: ['inherit', 'pipe', 'pipe'] });
209
+ const consume = (stream, echo) => {
210
+ if (!stream)
211
+ return;
212
+ let buffered = '';
213
+ stream.setEncoding('utf-8');
214
+ stream.on('data', (chunk) => {
215
+ echo.write(chunk);
216
+ buffered += chunk;
217
+ const lines = buffered.split('\n');
218
+ // The last element is whatever came after the final newline — an incomplete line, held over.
219
+ buffered = lines.pop() ?? '';
220
+ for (const line of lines)
221
+ noteTscLine(line, coordinator);
222
+ });
223
+ };
224
+ consume(child.stdout, process.stdout);
225
+ consume(child.stderr, process.stderr);
226
+ return child;
227
+ }
228
+ //# sourceMappingURL=watch.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"watch.js","sourceRoot":"","sources":["../../src/editor/watch.ts"],"names":[],"mappings":"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"}
@@ -16,6 +16,16 @@
16
16
  */
17
17
  export declare const CORS_ALLOWED_PORT_MIN = 3000;
18
18
  export declare const CORS_ALLOWED_PORT_MAX = 3199;
19
+ /**
20
+ * A `--port`-style flag parsed and checked against the window above.
21
+ *
22
+ * Every port `bitmagic dev` binds goes through here, the game port for the reason documented above
23
+ * and the shell port because the two are adjacent by default: a creator who moves one will move the
24
+ * other, and being told the rule once, at the point of the mistake, beats discovering it as a
25
+ * terrain-less world. (The shell itself fetches nothing from the CDN, so its own check is purely
26
+ * that kindness.)
27
+ */
28
+ export declare function parseLocalPort(value: string | undefined, fallback: number, flag: string): number;
19
29
  /**
20
30
  * A free port the CDN will serve assets to, or `null` when the whole window is taken.
21
31
  *
@@ -1,4 +1,5 @@
1
1
  import * as net from 'net';
2
+ import { CliError } from './errors.js';
2
3
  /**
3
4
  * The port window the platform's bucket CORS policy allowlists as an origin.
4
5
  *
@@ -17,6 +18,28 @@ import * as net from 'net';
17
18
  */
18
19
  export const CORS_ALLOWED_PORT_MIN = 3000;
19
20
  export const CORS_ALLOWED_PORT_MAX = 3199;
21
+ /**
22
+ * A `--port`-style flag parsed and checked against the window above.
23
+ *
24
+ * Every port `bitmagic dev` binds goes through here, the game port for the reason documented above
25
+ * and the shell port because the two are adjacent by default: a creator who moves one will move the
26
+ * other, and being told the rule once, at the point of the mistake, beats discovering it as a
27
+ * terrain-less world. (The shell itself fetches nothing from the CDN, so its own check is purely
28
+ * that kindness.)
29
+ */
30
+ export function parseLocalPort(value, fallback, flag) {
31
+ if (value === undefined)
32
+ return fallback;
33
+ const port = Number(value);
34
+ if (!Number.isInteger(port)) {
35
+ throw new CliError(`\`${flag}\` must be a whole number, got "${value}".`);
36
+ }
37
+ if (port < CORS_ALLOWED_PORT_MIN || port > CORS_ALLOWED_PORT_MAX) {
38
+ throw new CliError(`\`${flag}\` must be between ${CORS_ALLOWED_PORT_MIN} and ${CORS_ALLOWED_PORT_MAX}. `
39
+ + 'The asset CDN only allows those origins; outside that window the game loads with no terrain.');
40
+ }
41
+ return port;
42
+ }
20
43
  function tryListen(port) {
21
44
  return new Promise((resolve) => {
22
45
  const server = net.createServer();
@@ -1 +1 @@
1
- {"version":3,"file":"local-port.js","sourceRoot":"","sources":["../src/local-port.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,GAAG,MAAM,KAAK,CAAC;AAE3B;;;;;;;;;;;;;;;GAeG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,IAAI,CAAC;AAC1C,MAAM,CAAC,MAAM,qBAAqB,GAAG,IAAI,CAAC;AAE1C,SAAS,SAAS,CAAC,IAAY;IAC7B,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,MAAM,MAAM,GAAG,GAAG,CAAC,YAAY,EAAE,CAAC;QAClC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;QAC1C,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,WAAW,EAAE,GAAG,EAAE;YACpC,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,EAAE,CAAC;YACjC,MAAM,KAAK,GAAG,OAAO,KAAK,IAAI,IAAI,OAAO,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;YACpF,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC;QACrC,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,sBAAsB;IAC1C,MAAM,IAAI,GAAG,qBAAqB,GAAG,qBAAqB,GAAG,CAAC,CAAC;IAC/D,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,IAAI,CAAC,CAAC;IAC/C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QACjC,MAAM,IAAI,GAAG,qBAAqB,GAAG,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;QAC1D,MAAM,KAAK,GAAG,MAAM,SAAS,CAAC,IAAI,CAAC,CAAC;QACpC,IAAI,KAAK,KAAK,IAAI;YAAE,OAAO,KAAK,CAAC;IACnC,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
1
+ {"version":3,"file":"local-port.js","sourceRoot":"","sources":["../src/local-port.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,GAAG,MAAM,KAAK,CAAC;AAC3B,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAEvC;;;;;;;;;;;;;;;GAeG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,IAAI,CAAC;AAC1C,MAAM,CAAC,MAAM,qBAAqB,GAAG,IAAI,CAAC;AAE1C;;;;;;;;GAQG;AACH,MAAM,UAAU,cAAc,CAAC,KAAyB,EAAE,QAAgB,EAAE,IAAY;IACtF,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,QAAQ,CAAC;IACzC,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IAC3B,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC;QAC5B,MAAM,IAAI,QAAQ,CAAC,KAAK,IAAI,mCAAmC,KAAK,IAAI,CAAC,CAAC;IAC5E,CAAC;IACD,IAAI,IAAI,GAAG,qBAAqB,IAAI,IAAI,GAAG,qBAAqB,EAAE,CAAC;QACjE,MAAM,IAAI,QAAQ,CAChB,KAAK,IAAI,sBAAsB,qBAAqB,QAAQ,qBAAqB,IAAI;cACnF,8FAA8F,CACjG,CAAC;IACJ,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,SAAS,CAAC,IAAY;IAC7B,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,MAAM,MAAM,GAAG,GAAG,CAAC,YAAY,EAAE,CAAC;QAClC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;QAC1C,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,WAAW,EAAE,GAAG,EAAE;YACpC,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,EAAE,CAAC;YACjC,MAAM,KAAK,GAAG,OAAO,KAAK,IAAI,IAAI,OAAO,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;YACpF,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC;QACrC,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,sBAAsB;IAC1C,MAAM,IAAI,GAAG,qBAAqB,GAAG,qBAAqB,GAAG,CAAC,CAAC;IAC/D,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,IAAI,CAAC,CAAC;IAC/C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QACjC,MAAM,IAAI,GAAG,qBAAqB,GAAG,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;QAC1D,MAAM,KAAK,GAAG,MAAM,SAAS,CAAC,IAAI,CAAC,CAAC;QACpC,IAAI,KAAK,KAAK,IAAI;YAAE,OAAO,KAAK,CAAC;IACnC,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
@@ -0,0 +1,14 @@
1
+ export interface DevHandle {
2
+ /** The `--editor-port` value: where the tabbed Game/Editor shell is served — the URL to open. */
3
+ editorPort: number;
4
+ /** The `--port` value: where vite serves the game itself. */
5
+ gamePort: number;
6
+ /** The `bitmagic dev` process, for a human reading the file. Never used as a liveness check. */
7
+ pid: number;
8
+ }
9
+ export declare function devHandlePath(root: string): string;
10
+ export declare function writeDevHandle(root: string, handle: DevHandle): void;
11
+ /** Best-effort: a handle we cannot remove is a stale file, never a failed shutdown. */
12
+ export declare function removeDevHandle(root: string): void;
13
+ /** The recorded handle, or null when there is no readable one. */
14
+ export declare function readDevHandle(root: string): DevHandle | null;
@@ -0,0 +1,53 @@
1
+ /**
2
+ * `.bitmagic/dev.json` — how a second process finds the `bitmagic dev` already running here.
3
+ *
4
+ * `bitmagic reload` needs the shell's port, and hard-coding 3011 would break the moment a creator
5
+ * passes `--editor-port`. So `dev` writes what it bound the instant it is listening, and removes the
6
+ * file on shutdown. `.bitmagic/` is already gitignored (scaffold/project-files.ts renderGitignore),
7
+ * so this never reaches a commit.
8
+ *
9
+ * A stale file is expected, not exceptional: a `dev` killed with SIGKILL leaves one behind. Nothing
10
+ * here tries to detect that — `reload` simply fails to connect to the recorded port and reports that
11
+ * no dev server is running, which is the same outcome and the same message as no file at all. The
12
+ * pid is recorded for a human reading the file, not for liveness checks.
13
+ */
14
+ import * as fs from 'fs';
15
+ import * as path from 'path';
16
+ export function devHandlePath(root) {
17
+ return path.join(root, '.bitmagic', 'dev.json');
18
+ }
19
+ export function writeDevHandle(root, handle) {
20
+ const file = devHandlePath(root);
21
+ fs.mkdirSync(path.dirname(file), { recursive: true });
22
+ fs.writeFileSync(file, `${JSON.stringify(handle, null, 2)}\n`);
23
+ }
24
+ /** Best-effort: a handle we cannot remove is a stale file, never a failed shutdown. */
25
+ export function removeDevHandle(root) {
26
+ try {
27
+ fs.rmSync(devHandlePath(root), { force: true });
28
+ }
29
+ catch {
30
+ // See above.
31
+ }
32
+ }
33
+ /** The recorded handle, or null when there is no readable one. */
34
+ export function readDevHandle(root) {
35
+ let parsed;
36
+ try {
37
+ parsed = JSON.parse(fs.readFileSync(devHandlePath(root), 'utf-8'));
38
+ }
39
+ catch {
40
+ return null;
41
+ }
42
+ if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed))
43
+ return null;
44
+ const record = parsed;
45
+ if (typeof record.editorPort !== 'number' || typeof record.gamePort !== 'number')
46
+ return null;
47
+ return {
48
+ editorPort: record.editorPort,
49
+ gamePort: record.gamePort,
50
+ pid: typeof record.pid === 'number' ? record.pid : 0,
51
+ };
52
+ }
53
+ //# sourceMappingURL=dev-handle.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dev-handle.js","sourceRoot":"","sources":["../../src/project/dev-handle.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AACzB,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAW7B,MAAM,UAAU,aAAa,CAAC,IAAY;IACxC,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,WAAW,EAAE,UAAU,CAAC,CAAC;AAClD,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,IAAY,EAAE,MAAiB;IAC5D,MAAM,IAAI,GAAG,aAAa,CAAC,IAAI,CAAC,CAAC;IACjC,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACtD,EAAE,CAAC,aAAa,CAAC,IAAI,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC;AACjE,CAAC;AAED,uFAAuF;AACvF,MAAM,UAAU,eAAe,CAAC,IAAY;IAC1C,IAAI,CAAC;QACH,EAAE,CAAC,MAAM,CAAC,aAAa,CAAC,IAAI,CAAC,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IAClD,CAAC;IAAC,MAAM,CAAC;QACP,aAAa;IACf,CAAC;AACH,CAAC;AAED,kEAAkE;AAClE,MAAM,UAAU,aAAa,CAAC,IAAY;IACxC,IAAI,MAAe,CAAC;IACpB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,aAAa,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC;IACrE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;IACD,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC;QAAE,OAAO,IAAI,CAAC;IACxF,MAAM,MAAM,GAAG,MAAiC,CAAC;IACjD,IAAI,OAAO,MAAM,CAAC,UAAU,KAAK,QAAQ,IAAI,OAAO,MAAM,CAAC,QAAQ,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IAC9F,OAAO;QACL,UAAU,EAAE,MAAM,CAAC,UAAU;QAC7B,QAAQ,EAAE,MAAM,CAAC,QAAQ;QACzB,GAAG,EAAE,OAAO,MAAM,CAAC,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;KACrD,CAAC;AACJ,CAAC"}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * `.claude/settings.json` — the one line that makes the browser reload when the agent stops.
3
+ *
4
+ * `bitmagic dev` reloads on its own once writes go quiet, which is right for a human saving a file
5
+ * and a guess for an agent, because an agent pauses to think and a long enough pause reads as
6
+ * "finished". Claude Code's `Stop` hook removes the guess: it fires exactly once, when the turn
7
+ * ends, and running `bitmagic reload` there means the creator sees the finished work rather than a
8
+ * frame from the middle of it.
9
+ *
10
+ * ── Why this file is merged and not rendered ─────────────────────────────────────────────────
11
+ *
12
+ * Every other file the CLI ships into `.claude/` is in PLATFORM_GENERATED_FILES, which `upgrade`
13
+ * overwrites wholesale — correct for a skill that documents the CLI, since a stale copy is worse
14
+ * than a lost edit. `settings.json` is the opposite: it is where a creator puts their permissions,
15
+ * their env, their own hooks. Overwriting it would delete work that was never ours, so `upgrade`
16
+ * adds our hook to whatever is there and touches nothing else, in the same read-modify-write shape
17
+ * `bitmagic.json` already uses.
18
+ *
19
+ * Anything that cannot be merged confidently is left alone entirely. A creator with a hand-written
20
+ * settings file that we cannot parse keeps their file; they lose auto-reload-on-stop, which the
21
+ * watcher in `editor/watch.ts` mostly covers anyway.
22
+ */
23
+ export declare const CLAUDE_SETTINGS_FILE = ".claude/settings.json";
24
+ /** What the hook runs. One definition, so the scaffold and the merge cannot disagree. */
25
+ export declare const RELOAD_HOOK_COMMAND = "bitmagic reload";
26
+ export interface ReloadHookMerge {
27
+ /** The text to write, or null to leave the file exactly as it is. */
28
+ contents: string | null;
29
+ /** Present only when `contents` is null *because* the file could not be merged. */
30
+ skipped?: string;
31
+ }
32
+ /** The settings a project with no `.claude/settings.json` at all gets. */
33
+ export declare function renderClaudeSettings(): string;
34
+ /**
35
+ * Add the Stop hook to an existing settings file, preserving everything else in it.
36
+ *
37
+ * `existing` is the file's text, or undefined when there is no file. Returns `contents: null` when
38
+ * there is nothing to do — the hook is already there, or the file is not something we can safely
39
+ * rewrite.
40
+ */
41
+ export declare function mergeReloadHook(existing: string | undefined): ReloadHookMerge;
@@ -0,0 +1,98 @@
1
+ /**
2
+ * `.claude/settings.json` — the one line that makes the browser reload when the agent stops.
3
+ *
4
+ * `bitmagic dev` reloads on its own once writes go quiet, which is right for a human saving a file
5
+ * and a guess for an agent, because an agent pauses to think and a long enough pause reads as
6
+ * "finished". Claude Code's `Stop` hook removes the guess: it fires exactly once, when the turn
7
+ * ends, and running `bitmagic reload` there means the creator sees the finished work rather than a
8
+ * frame from the middle of it.
9
+ *
10
+ * ── Why this file is merged and not rendered ─────────────────────────────────────────────────
11
+ *
12
+ * Every other file the CLI ships into `.claude/` is in PLATFORM_GENERATED_FILES, which `upgrade`
13
+ * overwrites wholesale — correct for a skill that documents the CLI, since a stale copy is worse
14
+ * than a lost edit. `settings.json` is the opposite: it is where a creator puts their permissions,
15
+ * their env, their own hooks. Overwriting it would delete work that was never ours, so `upgrade`
16
+ * adds our hook to whatever is there and touches nothing else, in the same read-modify-write shape
17
+ * `bitmagic.json` already uses.
18
+ *
19
+ * Anything that cannot be merged confidently is left alone entirely. A creator with a hand-written
20
+ * settings file that we cannot parse keeps their file; they lose auto-reload-on-stop, which the
21
+ * watcher in `editor/watch.ts` mostly covers anyway.
22
+ */
23
+ export const CLAUDE_SETTINGS_FILE = '.claude/settings.json';
24
+ /** What the hook runs. One definition, so the scaffold and the merge cannot disagree. */
25
+ export const RELOAD_HOOK_COMMAND = 'bitmagic reload';
26
+ function isRecord(value) {
27
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
28
+ }
29
+ function stringify(settings) {
30
+ return `${JSON.stringify(settings, null, 2)}\n`;
31
+ }
32
+ /** The `Stop` matcher group this CLI owns. */
33
+ function reloadMatcher() {
34
+ return { hooks: [{ type: 'command', command: RELOAD_HOOK_COMMAND }] };
35
+ }
36
+ /** The settings a project with no `.claude/settings.json` at all gets. */
37
+ export function renderClaudeSettings() {
38
+ return stringify({ hooks: { Stop: [reloadMatcher()] } });
39
+ }
40
+ /**
41
+ * Does this `Stop` group already run our command?
42
+ *
43
+ * Matched by substring rather than equality on purpose: a creator who wrapped it — `cd app &&
44
+ * bitmagic reload`, `bitmagic reload --port 3021` — has the hook, and adding a second one would
45
+ * reload twice per turn.
46
+ */
47
+ function runsReload(matcher) {
48
+ if (!isRecord(matcher))
49
+ return false;
50
+ const hooks = matcher.hooks;
51
+ if (!Array.isArray(hooks))
52
+ return false;
53
+ return hooks.some((hook) => {
54
+ if (!isRecord(hook))
55
+ return false;
56
+ const command = hook.command;
57
+ return typeof command === 'string' && command.includes(RELOAD_HOOK_COMMAND);
58
+ });
59
+ }
60
+ /**
61
+ * Add the Stop hook to an existing settings file, preserving everything else in it.
62
+ *
63
+ * `existing` is the file's text, or undefined when there is no file. Returns `contents: null` when
64
+ * there is nothing to do — the hook is already there, or the file is not something we can safely
65
+ * rewrite.
66
+ */
67
+ export function mergeReloadHook(existing) {
68
+ if (existing === undefined || existing.trim() === '') {
69
+ return { contents: renderClaudeSettings() };
70
+ }
71
+ let parsed;
72
+ try {
73
+ parsed = JSON.parse(existing);
74
+ }
75
+ catch {
76
+ return { contents: null, skipped: `${CLAUDE_SETTINGS_FILE} is not valid JSON` };
77
+ }
78
+ if (!isRecord(parsed)) {
79
+ return { contents: null, skipped: `${CLAUDE_SETTINGS_FILE} does not contain a settings object` };
80
+ }
81
+ const hooks = parsed.hooks === undefined ? {} : parsed.hooks;
82
+ if (!isRecord(hooks)) {
83
+ return { contents: null, skipped: `${CLAUDE_SETTINGS_FILE} has a "hooks" value we cannot merge into` };
84
+ }
85
+ const stop = hooks.Stop === undefined ? [] : hooks.Stop;
86
+ if (!Array.isArray(stop)) {
87
+ return { contents: null, skipped: `${CLAUDE_SETTINGS_FILE} has a "hooks.Stop" value we cannot merge into` };
88
+ }
89
+ if (stop.some(runsReload))
90
+ return { contents: null };
91
+ return {
92
+ contents: stringify({
93
+ ...parsed,
94
+ hooks: { ...hooks, Stop: [...stop, reloadMatcher()] },
95
+ }),
96
+ };
97
+ }
98
+ //# sourceMappingURL=claude-settings.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claude-settings.js","sourceRoot":"","sources":["../../src/scaffold/claude-settings.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,MAAM,CAAC,MAAM,oBAAoB,GAAG,uBAAuB,CAAC;AAE5D,yFAAyF;AACzF,MAAM,CAAC,MAAM,mBAAmB,GAAG,iBAAiB,CAAC;AAkBrD,SAAS,QAAQ,CAAC,KAAc;IAC9B,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAC9E,CAAC;AAED,SAAS,SAAS,CAAC,QAAiB;IAClC,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC;AAClD,CAAC;AAED,8CAA8C;AAC9C,SAAS,aAAa;IACpB,OAAO,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,mBAAmB,EAAE,CAAC,EAAE,CAAC;AACxE,CAAC;AAED,0EAA0E;AAC1E,MAAM,UAAU,oBAAoB;IAClC,OAAO,SAAS,CAAC,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,CAAC,aAAa,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;AAC3D,CAAC;AAED;;;;;;GAMG;AACH,SAAS,UAAU,CAAC,OAAgB;IAClC,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC;QAAE,OAAO,KAAK,CAAC;IACrC,MAAM,KAAK,GAAI,OAAuB,CAAC,KAAK,CAAC;IAC7C,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IACxC,OAAO,KAAK,CAAC,IAAI,CAAC,CAAC,IAAa,EAAE,EAAE;QAClC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC;YAAE,OAAO,KAAK,CAAC;QAClC,MAAM,OAAO,GAAI,IAAkB,CAAC,OAAO,CAAC;QAC5C,OAAO,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAC,CAAC;IAC9E,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,eAAe,CAAC,QAA4B;IAC1D,IAAI,QAAQ,KAAK,SAAS,IAAI,QAAQ,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;QACrD,OAAO,EAAE,QAAQ,EAAE,oBAAoB,EAAE,EAAE,CAAC;IAC9C,CAAC;IAED,IAAI,MAAe,CAAC;IACpB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;IAChC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,oBAAoB,oBAAoB,EAAE,CAAC;IAClF,CAAC;IACD,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;QACtB,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,oBAAoB,qCAAqC,EAAE,CAAC;IACnG,CAAC;IAED,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;IAC7D,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QACrB,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,oBAAoB,2CAA2C,EAAE,CAAC;IACzG,CAAC;IAED,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC;IACxD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;QACzB,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,oBAAoB,gDAAgD,EAAE,CAAC;IAC9G,CAAC;IACD,IAAI,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC;QAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAErD,OAAO;QACL,QAAQ,EAAE,SAAS,CAAC;YAClB,GAAG,MAAM;YACT,KAAK,EAAE,EAAE,GAAG,KAAK,EAAE,IAAI,EAAE,CAAC,GAAG,IAAI,EAAE,aAAa,EAAE,CAAC,EAAE;SACtD,CAAC;KACH,CAAC;AACJ,CAAC"}
@@ -307,6 +307,9 @@ export function renderAgentsMd() {
307
307
  - \`.claude/skills/generating-assets/\` — **shipped by the CLI, re-rendered by \`bitmagic upgrade\`.**
308
308
  It documents the CLI's own commands, so it stays current with them; edits to it are lost. Skills
309
309
  you add yourself are never touched.
310
+ - \`.claude/settings.json\` — **yours**, seeded with one Stop hook that runs \`bitmagic reload\` so
311
+ the owner's browser refreshes when you finish a turn. \`upgrade\` adds that hook if it is missing
312
+ and changes nothing else, so your permissions and your own hooks are safe here.
310
313
  - \`AGENTS.md\` — this file. Yours to edit, and \`upgrade\` will never overwrite your edits. It
311
314
  refreshes this file only while it is still byte-identical to what the CLI wrote; once you change
312
315
  anything, it stops and instead tells you which sections of the current template you are missing,
@@ -358,12 +361,20 @@ the two yaw conventions differ. Guessing produces code that compiles and moves t
358
361
 
359
362
  \`\`\`
360
363
  bitmagic check # typecheck against the vendored engine
361
- bitmagic dev # build, then serve with live rebuilds
364
+ bitmagic dev # build, then serve the game + editor with auto-reload (leave this running)
365
+ bitmagic reload # tell the open browser you have finished a round of edits
362
366
  bitmagic verify # boot the game in a real browser and report what broke
363
367
  \`\`\`
364
368
 
365
369
  The engine is large; \`check\` is the fast way to find out whether an edit holds together.
366
370
 
371
+ **Run \`bitmagic reload\` when you finish a round of edits.** \`bitmagic dev\` watches the project
372
+ and reloads the browser once writes go quiet, which is a guess about you — you pause to think, and
373
+ a long enough pause looks like "done". \`bitmagic reload\` replaces the guess with a fact, and it
374
+ waits for the build in flight rather than showing the previous one. It exits 0 and does nothing when
375
+ no \`bitmagic dev\` is running, so it is safe to run unconditionally. If this project has a Claude
376
+ Code Stop hook (\`.claude/settings.json\`), that already runs it for you.
377
+
367
378
  **\`check\` passing does not mean the game runs.** The engine's most common failure is a clean
368
379
  compile that dies on load — a missing asset, a bad \`world.json\` value, a null container. Run
369
380
  \`bitmagic verify\` after a change you cannot eyeball: it loads the game in a headless browser and
@@ -371,9 +382,10 @@ fails with what actually broke, leaving a console log and a screenshot in \`.bit
371
382
 
372
383
  ## The human may be moving objects too
373
384
 
374
- \`bitmagic edit\` gives this project's owner a visual scene editor they click an object and drag
375
- a gizmo, and the new transform is written straight into \`src/work/world.json\`, one entry per
376
- object touched. So \`world.json\` is shared ground, not yours alone:
385
+ \`bitmagic dev\` serves one page with two tabs **Game**, where the owner plays, and **Editor**,
386
+ where they click an object and drag a gizmo. A drag writes the new transform straight into
387
+ \`src/work/world.json\`, one entry per object touched. So \`world.json\` is shared ground, not yours
388
+ alone:
377
389
 
378
390
  - **Re-read \`src/work/world.json\` before editing it.** The copy you read earlier in the session
379
391
  may be stale, and a whole-file rewrite from that copy silently reverts what they just placed.
@@ -382,7 +394,7 @@ object touched. So \`world.json\` is shared ground, not yours alone:
382
394
 
383
395
  ### \`.bitmagic/edit/events.jsonl\`
384
396
 
385
- Every editor action is appended there as one JSON object per line, and printed to \`bitmagic edit\`'s
397
+ Every editor action is appended there as one JSON object per line, and printed to \`bitmagic dev\`'s
386
398
  own output. Read it to find out what the human did — and, for one event, what they want YOU to do.
387
399
 
388
400
  \`\`\`
@@ -503,8 +515,8 @@ Four things worth knowing before you use these:
503
515
  refuses before generating and tells you what it needed.
504
516
  3. **It only works on the game this project points at.** \`bitmagic.json\` holds the \`gameId\`;
505
517
  generation is refused for a game the logged-in account does not own.
506
- 4. **A running \`bitmagic dev\` will not show the new asset until the browser reloads.** The
507
- command reminds you.
518
+ 4. **A running \`bitmagic dev\` picks the new asset up on its own.** It reloads the browser once
519
+ the write settles; \`bitmagic reload\` forces it if you want it now.
508
520
 
509
521
  **Not available as \`generate\` subcommands:** individual 3D models, voxel assets and block types.
510
522
  Those need a browser to voxelize and upload. Do not work around it by hand-editing
@@ -600,7 +612,7 @@ running before you start implementing rather than after.
600
612
  are holding it in memory.
601
613
  2. Run \`bitmagic check\`, then \`bitmagic verify\` if the asset is load-bearing — a bad asset URL
602
614
  typechecks fine and fails at runtime.
603
- 3. If \`bitmagic dev\` is running, the browser needs a reload.
615
+ 3. A running \`bitmagic dev\` reloads the browser by itself once the write settles.
604
616
 
605
617
  ## When it refuses
606
618
 
@@ -1 +1 @@
1
- {"version":3,"file":"project-files.js","sourceRoot":"","sources":["../../src/scaffold/project-files.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"project-files.js","sourceRoot":"","sources":["../../src/scaffold/project-files.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAEjF;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,YAAY,GAA2B;IAClD,2BAA2B,EAAE,SAAS;IACtC,2BAA2B,EAAE,SAAS;IACtC,kBAAkB,EAAE,QAAQ;IAC5B,MAAM,EAAE,QAAQ;IAChB,OAAO,EAAE,SAAS;IAClB,kCAAkC,EAAE,QAAQ;IAC5C,KAAK,EAAE,SAAS;IAChB,KAAK,EAAE,UAAU;CAClB,CAAC;AAEF,MAAM,CAAC,MAAM,gBAAgB,GAA2B;IACtD,aAAa,EAAE,UAAU;IACzB,cAAc,EAAE,UAAU;IAC1B,8FAA8F;IAC9F,cAAc,EAAE,QAAQ;IACxB,4FAA4F;IAC5F,mFAAmF;IACnF,eAAe,EAAE,SAAS;IAC1B,UAAU,EAAE,QAAQ;IACpB,IAAI,EAAE,QAAQ;IACd,wBAAwB,EAAE,QAAQ;CACnC,CAAC;AAEF;;;;;GAKG;AACH,MAAM,aAAa,GAAG,SAAS,CAAC;AAEhC;;;;;;;;;GASG;AACH,MAAM,UAAU,GAAG;IACjB,uBAAuB;IACvB,oCAAoC,aAAa,WAAW;IAC5D,kDAAkD,aAAa,2CAA2C;IAC1G,8CAA8C,aAAa,uCAAuC;IAClG,gDAAgD,aAAa,wCAAwC;CACtG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AAEX,oGAAoG;AACpG,MAAM,WAAW,GAA2B;IAC1C,2FAA2F;IAC3F,4FAA4F;IAC5F,4FAA4F;IAC5F,KAAK,EAAE,UAAU;IACjB,cAAc,EAAE,wBAAwB,aAAa,SAAS;IAC9D,WAAW,EAAE,wBAAwB,aAAa,MAAM;IACxD,2FAA2F;IAC3F,6FAA6F;IAC7F,+FAA+F;IAC/F,4FAA4F;IAC5F,6FAA6F;IAC7F,6FAA6F;IAC7F,eAAe,EAAE,yBAAyB,aAAa,UAAU;IACjE,qBAAqB,EAAE,yBAAyB,aAAa,gBAAgB;IAC7E,QAAQ,EAAE,wBAAwB,aAAa,GAAG;IAClD,2BAA2B,EAAE,iDAAiD;IAC9E,2BAA2B,EAAE,iDAAiD;IAC9E,OAAO,EAAE,+BAA+B;IACxC,kCAAkC,EAAE,uDAAuD;IAC3F,kBAAkB,EAAE,uCAAuC;IAC3D,MAAM,EAAE,6BAA6B;CACtC,CAAC;AAEF,sFAAsF;AACtF,SAAS,UAAU,CAAC,KAAc;IAChC,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC;AAC/C,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,IAAY;IAC5C,OAAO,UAAU,CAAC;QAChB,IAAI;QACJ,OAAO,EAAE,OAAO;QAChB,OAAO,EAAE,IAAI;QACb,IAAI,EAAE,QAAQ;QACd,OAAO,EAAE;YACP,KAAK,EAAE,KAAK;YACZ,KAAK,EAAE,cAAc;YACrB,GAAG,EAAE,MAAM;SACZ;QACD,YAAY,EAAE,YAAY;QAC1B,eAAe,EAAE,gBAAgB;KAClC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,cAAc;IAC5B,OAAO,UAAU,CAAC;QAChB,eAAe,EAAE;YACf,MAAM,EAAE,QAAQ;YAChB,MAAM,EAAE,QAAQ;YAChB,GAAG,EAAE,CAAC,QAAQ,EAAE,KAAK,CAAC;YACtB,OAAO,EAAE,GAAG;YACZ,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,GAAG;YACZ,KAAK,EAAE,aAAa,EAAE;YACtB,gBAAgB,EAAE,SAAS;YAC3B,MAAM,EAAE,IAAI;YACZ,wBAAwB,EAAE,IAAI;YAC9B,0BAA0B,EAAE,KAAK;YACjC,oBAAoB,EAAE,IAAI;YAC1B,eAAe,EAAE,IAAI;YACrB,eAAe,EAAE,OAAO;YACxB,YAAY,EAAE,IAAI;YAClB,eAAe,EAAE,IAAI;YACrB,4BAA4B,EAAE,IAAI;YAClC,gCAAgC,EAAE,IAAI;YACtC,aAAa,EAAE,IAAI;YACnB,iBAAiB,EAAE,IAAI;YACvB,SAAS,EAAE,IAAI;YACf,wFAAwF;YACxF,yFAAyF;YACzF,wFAAwF;YACxF,0FAA0F;YAC1F,sFAAsF;YACtF,yFAAyF;YACzF,gFAAgF;YAChF,KAAK,EAAE,CAAC,eAAe,CAAC;YACxB,SAAS,EAAE,CAAC,cAAc,EAAE,qBAAqB,EAAE,wCAAwC,CAAC;SAC7F;QACD,OAAO,EAAE,CAAC,UAAU,EAAE,aAAa,CAAC;QACpC,uFAAuF;QACvF,uFAAuF;QACvF,oFAAoF;QACpF,wFAAwF;QACxF,wDAAwD;QACxD,OAAO,EAAE;YACP,cAAc;YACd,MAAM;YACN,qBAAqB;YACrB,0BAA0B;YAC1B,kBAAkB;YAClB,uBAAuB;YACvB,oBAAoB;SACrB;KACF,CAAC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,eAAe;IAC7B,MAAM,YAAY,GAAG,gBAAgB,EAAE,CAAC;IACxC,MAAM,OAAO,GAAG,EAAE,GAAG,WAAW,EAAE,GAAG,YAAY,EAAE,CAAC;IACpD,6FAA6F;IAC7F,yFAAyF;IACzF,MAAM,gBAAgB,GAAG,GAAG,YAAY,CAAC,SAAS,CAAC,iBAAiB,CAAC;IACrE,0FAA0F;IAC1F,0FAA0F;IAC1F,wFAAwF;IACxF,2FAA2F;IAC3F,mDAAmD;IACnD,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;EA2BP,IAAI,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,MAAM,CAAC;;;iCAG5B,gBAAgB;;;CAGhD,CAAC;AACF,CAAC;AAED;;;;;GAKG;AACH,SAAS,kBAAkB;IACzB,OAAO,MAAM,CAAC,OAAO,CAAC,gBAAgB,EAAE,CAAC;SACtC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,MAAM,CAAC,EAAE,EAAE,CAAC,UAAU,GAAG,OAAO,MAAM,GAAG,CAAC;SACrD,IAAI,CAAC,KAAK,CAAC,CAAC;AACjB,CAAC;AAED,MAAM,UAAU,gBAAgB;IAC9B,MAAM,OAAO,GAAG,kBAAkB,EAAE,CAAC;IACrC,OAAO;;;;;;;;;;;EAWP,OAAO;;;;;;;CAOR,CAAC;AACF,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,uBAAuB;IACrC,MAAM,OAAO,GAAG,kBAAkB,EAAE,CAAC;IACrC,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA4BP,OAAO;;;;;;;;;;;;;;CAcR,CAAC;AACF,CAAC;AAED,MAAM,UAAU,eAAe;IAC7B,OAAO,CAAC,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,cAAc,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACjF,CAAC;AAiBD,MAAM,UAAU,kBAAkB,CAAC,QAAyB;IAC1D,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC;AAC9B,CAAC;AAED,MAAM,UAAU,cAAc;IAC5B,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAyQR,CAAC;AACF,CAAC;AAED,MAAM,UAAU,mBAAmB;IACjC,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAuER,CAAC;AACF,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,kBAAkB,CAAC,IAAa;IAC9C,MAAM,KAAK,GAAG,IAAI,EAAE,IAAI,EAAE,CAAC;IAC3B,OAAO;;EAEP,KAAK;QACH,CAAC,CAAC,8GAA8G;QAChH,CAAC,CAAC,0KAA0K;;;;EAI9K,KAAK,IAAI,iEAAiE;;;;;;;;;;;;;;CAc3E,CAAC;AACF,CAAC;AAED,MAAM,UAAU,cAAc;IAC5B,OAAO,cAAc,CAAC;AACxB,CAAC;AAED,MAAM,UAAU,kBAAkB;IAChC,OAAO;;;;;;;;;;;;;;CAcR,CAAC;AACF,CAAC"}
@@ -4,6 +4,7 @@ import { CliError } from '../errors.js';
4
4
  import { aliasSourceDir } from './aliases.js';
5
5
  import { renderPackageJson, renderTsconfig, renderIndexHtml, renderViteConfig, renderVitePublishConfig, renderGitignore, renderBitmagicJson, renderAgentsMd, renderGameDesignMd, renderClaudeMd, renderSkillsReadme, renderGenerateSkill, } from './project-files.js';
6
6
  import { hashAgentsMd } from './agents-md.js';
7
+ import { CLAUDE_SETTINGS_FILE, renderClaudeSettings } from './claude-settings.js';
7
8
  /**
8
9
  * Directories moved out of the extracted tree into the project's vendored engine/.
9
10
  *
@@ -146,6 +147,10 @@ export function scaffoldProject(options) {
146
147
  ['GAME-DESIGN.md', renderGameDesignMd(idea)],
147
148
  ['CLAUDE.md', renderClaudeMd()],
148
149
  ['.gitignore', renderGitignore()],
150
+ // Also creator-owned, and merged rather than rewritten by `upgrade` for that reason — this is
151
+ // the only place it is written whole, because the project is new and there is nothing to keep.
152
+ // See scaffold/claude-settings.ts.
153
+ [CLAUDE_SETTINGS_FILE, renderClaudeSettings()],
149
154
  ];
150
155
  for (const [name, contents] of files) {
151
156
  const filePath = path.join(targetDir, name);