@doodle-engine/cli 0.0.19 → 0.0.21

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/cli.js CHANGED
@@ -9,73 +9,73 @@ import { join as h, extname as f, relative as E, dirname as U } from "path";
9
9
  import { parse as y } from "yaml";
10
10
  import { extractAssetPaths as W, getAssetType as R, parseDialogue as k } from "@doodle-engine/core";
11
11
  import J from "prompts";
12
- function C(t, o) {
12
+ function C(n, o) {
13
13
  const s = [];
14
- for (const r of Object.values(t.dialogues)) {
15
- const n = o.get(r.id) || `dialogue:${r.id}`;
16
- s.push(...Q(r, n));
14
+ for (const r of Object.values(n.dialogues)) {
15
+ const t = o.get(r.id) || `dialogue:${r.id}`;
16
+ s.push(...Q(r, t));
17
17
  }
18
- for (const r of Object.values(t.characters))
19
- if (r.dialogue && !t.dialogues[r.dialogue]) {
20
- const n = o.get(r.id) || `character:${r.id}`;
18
+ for (const r of Object.values(n.characters))
19
+ if (r.dialogue && !n.dialogues[r.dialogue]) {
20
+ const t = o.get(r.id) || `character:${r.id}`;
21
21
  s.push({
22
- file: n,
22
+ file: t,
23
23
  message: `Character "${r.id}" references non-existent dialogue "${r.dialogue}"`,
24
24
  suggestion: `Create dialogue "${r.dialogue}" or fix the reference`
25
25
  });
26
26
  }
27
- return s.push(...K(t, o)), s;
27
+ return s.push(...K(n, o)), s;
28
28
  }
29
- function Q(t, o) {
29
+ function Q(n, o) {
30
30
  const s = [], r = /* @__PURE__ */ new Set();
31
- for (const n of t.nodes)
32
- r.has(n.id) && s.push({
31
+ for (const t of n.nodes)
32
+ r.has(t.id) && s.push({
33
33
  file: o,
34
- message: `Duplicate node ID "${n.id}"`,
34
+ message: `Duplicate node ID "${t.id}"`,
35
35
  suggestion: "Node IDs must be unique within a dialogue"
36
- }), r.add(n.id);
37
- r.has(t.startNode) || s.push({
36
+ }), r.add(t.id);
37
+ r.has(n.startNode) || s.push({
38
38
  file: o,
39
- message: `Start node "${t.startNode}" not found`,
40
- suggestion: `Add a NODE ${t.startNode} or fix the startNode reference`
39
+ message: `Start node "${n.startNode}" not found`,
40
+ suggestion: `Add a NODE ${n.startNode} or fix the startNode reference`
41
41
  });
42
- for (const n of t.nodes)
43
- s.push(...Y(n, r, o));
42
+ for (const t of n.nodes)
43
+ s.push(...Y(t, r, o));
44
44
  return s;
45
45
  }
46
- function Y(t, o, s) {
46
+ function Y(n, o, s) {
47
47
  const r = [];
48
- if (t.next && !o.has(t.next) && r.push({
48
+ if (n.next && !o.has(n.next) && r.push({
49
49
  file: s,
50
- message: `Node "${t.id}" GOTO "${t.next}" points to non-existent node`,
51
- suggestion: `Add NODE ${t.next} or fix the GOTO target`
52
- }), t.conditionalNext)
53
- for (const n of t.conditionalNext)
54
- o.has(n.next) || r.push({
50
+ message: `Node "${n.id}" GOTO "${n.next}" points to non-existent node`,
51
+ suggestion: `Add NODE ${n.next} or fix the GOTO target`
52
+ }), n.conditionalNext)
53
+ for (const t of n.conditionalNext)
54
+ o.has(t.next) || r.push({
55
55
  file: s,
56
- message: `Node "${t.id}" IF block GOTO "${n.next}" points to non-existent node`,
57
- suggestion: `Add NODE ${n.next} or fix the GOTO target`
58
- }), r.push(...O(n.condition, t.id, s));
59
- for (const n of t.choices) {
60
- if (!n.effects?.some(
56
+ message: `Node "${n.id}" IF block GOTO "${t.next}" points to non-existent node`,
57
+ suggestion: `Add NODE ${t.next} or fix the GOTO target`
58
+ }), r.push(...O(t.condition, n.id, s));
59
+ for (const t of n.choices) {
60
+ if (!t.effects?.some(
61
61
  (e) => e.type === "endDialogue" || e.type === "goToLocation" || e.type === "startDialogue"
62
- ) && n.next && !o.has(n.next) && r.push({
62
+ ) && t.next && !o.has(t.next) && r.push({
63
63
  file: s,
64
- message: `Node "${t.id}" choice "${n.id}" GOTO "${n.next}" points to non-existent node`,
65
- suggestion: `Add NODE ${n.next} or fix the GOTO target`
66
- }), n.conditions)
67
- for (const e of n.conditions)
68
- r.push(...O(e, t.id, s));
69
- if (n.effects)
70
- for (const e of n.effects)
71
- r.push(...A(e, t.id, s));
64
+ message: `Node "${n.id}" choice "${t.id}" GOTO "${t.next}" points to non-existent node`,
65
+ suggestion: `Add NODE ${t.next} or fix the GOTO target`
66
+ }), t.conditions)
67
+ for (const e of t.conditions)
68
+ r.push(...O(e, n.id, s));
69
+ if (t.effects)
70
+ for (const e of t.effects)
71
+ r.push(...A(e, n.id, s));
72
72
  }
73
- if (t.conditions)
74
- for (const n of t.conditions)
75
- r.push(...O(n, t.id, s));
76
- if (t.effects)
77
- for (const n of t.effects)
78
- r.push(...A(n, t.id, s));
73
+ if (n.conditions)
74
+ for (const t of n.conditions)
75
+ r.push(...O(t, n.id, s));
76
+ if (n.effects)
77
+ for (const t of n.effects)
78
+ r.push(...A(t, n.id, s));
79
79
  return r;
80
80
  }
81
81
  const z = {
@@ -123,51 +123,51 @@ const z = {
123
123
  showInterlude: ["interludeId"],
124
124
  roll: ["variable", "min", "max"]
125
125
  };
126
- function O(t, o, s) {
126
+ function O(n, o, s) {
127
127
  const r = [];
128
- if (!t.type)
128
+ if (!n.type)
129
129
  return r.push({
130
130
  file: s,
131
131
  message: `Node "${o}" has condition with missing type`
132
132
  }), r;
133
- if (t.type === "timeIs")
134
- return t.hour === void 0 && t.day === void 0 && r.push({
133
+ if (n.type === "timeIs")
134
+ return n.hour === void 0 && n.day === void 0 && r.push({
135
135
  file: s,
136
136
  message: `Node "${o}" condition "timeIs" must have at least one of "hour" or "day" argument`
137
137
  }), r;
138
- const n = z[t.type];
139
- if (!n)
138
+ const t = z[n.type];
139
+ if (!t)
140
140
  return r;
141
- for (const a of n)
142
- (t[a] === void 0 || t[a] === null || t[a] === "") && r.push({
141
+ for (const a of t)
142
+ (n[a] === void 0 || n[a] === null || n[a] === "") && r.push({
143
143
  file: s,
144
- message: `Node "${o}" condition "${t.type}" missing required "${a}" argument`
144
+ message: `Node "${o}" condition "${n.type}" missing required "${a}" argument`
145
145
  });
146
146
  return r;
147
147
  }
148
- function A(t, o, s) {
148
+ function A(n, o, s) {
149
149
  const r = [];
150
- if (!t.type)
150
+ if (!n.type)
151
151
  return r.push({
152
152
  file: s,
153
153
  message: `Node "${o}" has effect with missing type`
154
154
  }), r;
155
- const n = V[t.type];
156
- if (!n)
155
+ const t = V[n.type];
156
+ if (!t)
157
157
  return r;
158
- for (const a of n)
159
- (t[a] === void 0 || t[a] === null || t[a] === "") && r.push({
158
+ for (const a of t)
159
+ (n[a] === void 0 || n[a] === null || n[a] === "") && r.push({
160
160
  file: s,
161
- message: `Node "${o}" effect "${t.type}" missing required "${a}" argument`
161
+ message: `Node "${o}" effect "${n.type}" missing required "${a}" argument`
162
162
  });
163
163
  return r;
164
164
  }
165
- function K(t, o) {
165
+ function K(n, o) {
166
166
  const s = [], r = /* @__PURE__ */ new Set();
167
- for (const e of Object.values(t.locales))
167
+ for (const e of Object.values(n.locales))
168
168
  for (const i of Object.keys(e))
169
169
  r.add(i);
170
- const n = (e) => e.startsWith("@"), a = (e, i, l) => {
170
+ const t = (e) => e.startsWith("@"), a = (e, i, l) => {
171
171
  const d = e.slice(1);
172
172
  if (!r.has(d)) {
173
173
  const u = o.get(i) || `${l}:${i}`;
@@ -178,44 +178,54 @@ function K(t, o) {
178
178
  });
179
179
  }
180
180
  };
181
- for (const e of Object.values(t.locations))
182
- n(e.name) && a(e.name, e.id, "location"), n(e.description) && a(e.description, e.id, "location");
183
- for (const e of Object.values(t.characters))
184
- n(e.name) && a(e.name, e.id, "character"), n(e.biography) && a(e.biography, e.id, "character");
185
- for (const e of Object.values(t.items))
186
- n(e.name) && a(e.name, e.id, "item"), n(e.description) && a(e.description, e.id, "item");
187
- for (const e of Object.values(t.quests)) {
188
- n(e.name) && a(e.name, e.id, "quest"), n(e.description) && a(e.description, e.id, "quest");
181
+ for (const e of Object.values(n.locations))
182
+ t(e.name) && a(e.name, e.id, "location"), t(e.description) && a(e.description, e.id, "location");
183
+ for (const e of Object.values(n.characters))
184
+ t(e.name) && a(e.name, e.id, "character"), t(e.biography) && a(e.biography, e.id, "character");
185
+ for (const e of Object.values(n.items))
186
+ t(e.name) && a(e.name, e.id, "item"), t(e.description) && a(e.description, e.id, "item");
187
+ for (const e of Object.values(n.quests)) {
188
+ t(e.name) && a(e.name, e.id, "quest"), t(e.description) && a(e.description, e.id, "quest");
189
189
  for (const i of e.stages)
190
- n(i.description) && a(i.description, e.id, "quest");
190
+ t(i.description) && a(i.description, e.id, "quest");
191
191
  }
192
- for (const e of Object.values(t.journalEntries))
193
- n(e.title) && a(e.title, e.id, "journal"), n(e.text) && a(e.text, e.id, "journal");
194
- for (const e of Object.values(t.dialogues))
192
+ for (const e of Object.values(n.journalEntries))
193
+ t(e.title) && a(e.title, e.id, "journal"), t(e.text) && a(e.text, e.id, "journal");
194
+ for (const e of Object.values(n.dialogues))
195
195
  for (const i of e.nodes) {
196
- n(i.text) && a(i.text, e.id, "dialogue");
196
+ t(i.text) && a(i.text, e.id, "dialogue");
197
197
  for (const l of i.choices)
198
- n(l.text) && a(l.text, e.id, "dialogue");
198
+ t(l.text) && a(l.text, e.id, "dialogue");
199
199
  }
200
- for (const e of Object.values(t.interludes))
201
- n(e.text) && a(e.text, e.id, "interlude");
200
+ for (const e of Object.values(n.interludes))
201
+ t(e.text) && a(e.text, e.id, "interlude");
202
202
  return s;
203
203
  }
204
- function I(t) {
205
- if (t.length === 0) {
204
+ function I(n) {
205
+ if (n.length === 0) {
206
206
  console.log(c.green("✓ No validation errors"));
207
207
  return;
208
208
  }
209
- console.log(c.red(`
210
- ✗ Found ${t.length} validation error${t.length === 1 ? "" : "s"}:
211
- `));
212
- for (const o of t)
209
+ console.log(
210
+ c.red(
211
+ `
212
+ ✗ Found ${n.length} validation error${n.length === 1 ? "" : "s"}:
213
+ `
214
+ )
215
+ );
216
+ for (const o of n)
213
217
  console.log(c.bold(o.file) + (o.line ? `:${o.line}` : "")), console.log(" " + c.red(o.message)), o.suggestion && console.log(" " + c.dim(o.suggestion)), console.log();
214
218
  }
215
- async function q(t, o, s, r, n = Date.now().toString()) {
216
- const { shell: a, game: e } = W(s, r);
219
+ async function q(n, o, s, r, t = Date.now().toString()) {
220
+ const { shell: a, game: e } = W(
221
+ s,
222
+ r
223
+ );
217
224
  async function i(g) {
218
- const _ = h(o, g.startsWith("/") ? g.slice(1) : g);
225
+ const _ = h(
226
+ o,
227
+ g.startsWith("/") ? g.slice(1) : g
228
+ );
219
229
  try {
220
230
  return (await B(_)).size;
221
231
  } catch {
@@ -223,22 +233,26 @@ async function q(t, o, s, r, n = Date.now().toString()) {
223
233
  }
224
234
  }
225
235
  const l = await Promise.all(
226
- a.map(async (g) => ({
227
- path: g,
228
- type: R(g),
229
- size: await i(g),
230
- tier: 1
231
- }))
236
+ a.map(
237
+ async (g) => ({
238
+ path: g,
239
+ type: R(g),
240
+ size: await i(g),
241
+ tier: 1
242
+ })
243
+ )
232
244
  ), d = await Promise.all(
233
- e.map(async (g) => ({
234
- path: g,
235
- type: R(g),
236
- size: await i(g),
237
- tier: 2
238
- }))
245
+ e.map(
246
+ async (g) => ({
247
+ path: g,
248
+ type: R(g),
249
+ size: await i(g),
250
+ tier: 2
251
+ })
252
+ )
239
253
  ), u = l.reduce((g, _) => g + (_.size ?? 0), 0), m = d.reduce((g, _) => g + (_.size ?? 0), 0);
240
254
  return {
241
- version: n,
255
+ version: t,
242
256
  shell: l,
243
257
  game: d,
244
258
  shellSize: u,
@@ -246,24 +260,24 @@ async function q(t, o, s, r, n = Date.now().toString()) {
246
260
  };
247
261
  }
248
262
  const S = "ðŸū", X = "âœĻ", Z = "✏ïļ", ee = "➕";
249
- async function te() {
250
- const t = process.cwd(), o = h(t, "content");
263
+ async function ne() {
264
+ const n = process.cwd(), o = h(n, "content");
251
265
  console.log(""), console.log(c.bold.magenta(` ${S} Doodle Engine Dev Server ${S}`)), console.log("");
252
266
  const s = {
253
267
  name: "doodle-content-loader",
254
- configureServer(n) {
255
- n.middlewares.use("/api/content", async (d, u) => {
268
+ configureServer(t) {
269
+ t.middlewares.use("/api/content", async (d, u) => {
256
270
  try {
257
271
  const m = await j(o);
258
272
  u.setHeader("Content-Type", "application/json"), u.end(JSON.stringify(m));
259
273
  } catch (m) {
260
274
  console.error(c.red(" Error loading content:"), m), u.statusCode = 500, u.end(JSON.stringify({ error: "Failed to load content" }));
261
275
  }
262
- }), n.middlewares.use("/api/manifest", async (d, u) => {
276
+ }), t.middlewares.use("/api/manifest", async (d, u) => {
263
277
  try {
264
278
  const { registry: m, config: g } = await j(o), _ = await q(
265
- h(t, "assets"),
266
- t,
279
+ h(n, "assets"),
280
+ n,
267
281
  m,
268
282
  g,
269
283
  "dev"
@@ -284,7 +298,7 @@ async function te() {
284
298
  let i = null;
285
299
  const l = (d) => {
286
300
  i && clearTimeout(i), i = setTimeout(async () => {
287
- i = null, console.log(d), await ne(o), n.ws.send({ type: "full-reload", path: "*" });
301
+ i = null, console.log(d), await te(o), t.ws.send({ type: "full-reload", path: "*" });
288
302
  }, 50);
289
303
  };
290
304
  a.on("change", (d) => {
@@ -294,7 +308,7 @@ async function te() {
294
308
  });
295
309
  }
296
310
  }, r = await F({
297
- root: t,
311
+ root: n,
298
312
  plugins: [G(), s],
299
313
  server: {
300
314
  port: 3e3,
@@ -303,7 +317,7 @@ async function te() {
303
317
  });
304
318
  await r.listen(), r.printUrls(), console.log(""), console.log(c.dim(` ${X} Watching content files for changes...`)), console.log("");
305
319
  }
306
- async function j(t) {
320
+ async function j(n) {
307
321
  const o = {
308
322
  locations: {},
309
323
  characters: {},
@@ -325,8 +339,8 @@ async function j(t) {
325
339
  { dir: "journal", key: "journalEntries" },
326
340
  { dir: "interludes", key: "interludes" }
327
341
  ];
328
- for (const { dir: n, key: a } of r) {
329
- const e = h(t, n);
342
+ for (const { dir: t, key: a } of r) {
343
+ const e = h(n, t);
330
344
  try {
331
345
  const i = await b(e);
332
346
  for (const l of i)
@@ -338,25 +352,25 @@ async function j(t) {
338
352
  }
339
353
  }
340
354
  try {
341
- const n = h(t, "locales"), a = await b(n);
355
+ const t = h(n, "locales"), a = await b(t);
342
356
  for (const e of a)
343
357
  if (f(e) === ".yaml" || f(e) === ".yml") {
344
- const i = h(n, e), l = await p(i, "utf-8"), d = y(l), u = e.replace(/\.(yaml|yml)$/, "");
358
+ const i = h(t, e), l = await p(i, "utf-8"), d = y(l), u = e.replace(/\.(yaml|yml)$/, "");
345
359
  o.locales[u] = d ?? {};
346
360
  }
347
361
  } catch {
348
362
  }
349
363
  try {
350
- const n = h(t, "dialogues"), a = await b(n);
364
+ const t = h(n, "dialogues"), a = await b(t);
351
365
  for (const e of a)
352
366
  if (f(e) === ".dlg") {
353
- const i = h(n, e), l = await p(i, "utf-8"), d = e.replace(".dlg", ""), u = k(l, d);
367
+ const i = h(t, e), l = await p(i, "utf-8"), d = e.replace(".dlg", ""), u = k(l, d);
354
368
  o.dialogues[u.id] = u;
355
369
  }
356
370
  } catch {
357
371
  }
358
372
  try {
359
- const n = h(t, "game.yaml"), a = await p(n, "utf-8");
373
+ const t = h(n, "game.yaml"), a = await p(t, "utf-8");
360
374
  s = y(a);
361
375
  } catch {
362
376
  console.warn(c.yellow(" No game.yaml found, using defaults")), s = {
@@ -369,15 +383,15 @@ async function j(t) {
369
383
  }
370
384
  return { registry: o, config: s };
371
385
  }
372
- async function ne(t) {
386
+ async function te(n) {
373
387
  try {
374
- const { registry: o, fileMap: s } = await oe(t), r = C(o, s);
388
+ const { registry: o, fileMap: s } = await oe(n), r = C(o, s);
375
389
  r.length > 0 && (console.log(""), I(r), console.log(""));
376
390
  } catch (o) {
377
391
  console.error(c.red(" Error running validation:"), o);
378
392
  }
379
393
  }
380
- async function oe(t) {
394
+ async function oe(n) {
381
395
  const o = {
382
396
  locations: {},
383
397
  characters: {},
@@ -397,8 +411,8 @@ async function oe(t) {
397
411
  { dir: "journal", key: "journalEntries" },
398
412
  { dir: "interludes", key: "interludes" }
399
413
  ];
400
- for (const { dir: n, key: a } of r) {
401
- const e = h(t, n);
414
+ for (const { dir: t, key: a } of r) {
415
+ const e = h(n, t);
402
416
  try {
403
417
  const i = await b(e);
404
418
  for (const l of i)
@@ -410,34 +424,34 @@ async function oe(t) {
410
424
  }
411
425
  }
412
426
  try {
413
- const n = h(t, "locales"), a = await b(n);
427
+ const t = h(n, "locales"), a = await b(t);
414
428
  for (const e of a)
415
429
  if (f(e) === ".yaml" || f(e) === ".yml") {
416
- const i = h(n, e), l = await p(i, "utf-8"), d = y(l), u = e.replace(/\.(yaml|yml)$/, "");
430
+ const i = h(t, e), l = await p(i, "utf-8"), d = y(l), u = e.replace(/\.(yaml|yml)$/, "");
417
431
  o.locales[u] = d ?? {};
418
432
  }
419
433
  } catch {
420
434
  }
421
435
  try {
422
- const n = h(t, "dialogues"), a = await b(n);
436
+ const t = h(n, "dialogues"), a = await b(t);
423
437
  for (const e of a)
424
438
  if (f(e) === ".dlg") {
425
- const i = h(n, e), l = await p(i, "utf-8"), d = e.replace(".dlg", ""), u = k(l, d);
439
+ const i = h(t, e), l = await p(i, "utf-8"), d = e.replace(".dlg", ""), u = k(l, d);
426
440
  o.dialogues[u.id] = u, s.set(u.id, E(process.cwd(), i));
427
441
  }
428
442
  } catch {
429
443
  }
430
444
  return { registry: o, fileMap: s };
431
445
  }
432
- function ae(t) {
433
- const o = `doodle-engine-assets-${t.version}`, s = [
434
- ...t.shell.map((n) => n.path),
435
- ...t.game.map((n) => n.path)
446
+ function ae(n) {
447
+ const o = `doodle-engine-assets-${n.version}`, s = [
448
+ ...n.shell.map((t) => t.path),
449
+ ...n.game.map((t) => t.path)
436
450
  ], r = JSON.stringify(s, null, 2);
437
451
  return `/**
438
452
  * Doodle Engine Service Worker
439
453
  * Generated at build time — do not edit manually.
440
- * Cache version: ${t.version}
454
+ * Cache version: ${n.version}
441
455
  */
442
456
 
443
457
  const CACHE_NAME = ${JSON.stringify(o)};
@@ -505,44 +519,47 @@ self.addEventListener('fetch', (event) => {
505
519
  `;
506
520
  }
507
521
  async function re() {
508
- const t = process.cwd(), o = h(t, "content");
522
+ const n = process.cwd(), o = h(n, "content");
509
523
  console.log(""), console.log(c.bold.magenta("🐕 Building Doodle Engine game...")), console.log(""), console.log(c.dim("Validating content..."));
510
- let s, r, n;
524
+ let s, r, t;
511
525
  try {
512
526
  const a = await se(o);
513
- r = a.registry, n = a.config;
527
+ r = a.registry, t = a.config;
514
528
  const { fileMap: e } = a, i = C(r, e);
515
- I(i), i.length > 0 && (console.log(c.red("Build failed due to validation errors.")), console.log(""), process.exit(1)), s = { registry: r, config: n };
529
+ I(i), i.length > 0 && (console.log(c.red("Build failed due to validation errors.")), console.log(""), process.exit(1)), s = { registry: r, config: t };
516
530
  } catch (a) {
517
531
  console.error(c.red("Error loading content:"), a), process.exit(1);
518
532
  }
519
533
  console.log("");
520
534
  try {
521
535
  await P({
522
- root: t,
536
+ root: n,
523
537
  plugins: [G()],
524
538
  build: {
525
539
  outDir: "dist",
526
540
  emptyOutDir: !0
527
541
  }
528
542
  });
529
- const a = h(t, "dist"), e = t;
543
+ const a = h(n, "dist"), e = n;
530
544
  console.log(c.dim("Generating asset manifest..."));
531
545
  const i = await q(
532
- h(t, "assets"),
546
+ h(n, "assets"),
533
547
  e,
534
548
  r,
535
- n,
549
+ t,
536
550
  Date.now().toString()
537
551
  ), l = h(a, "api");
538
- await T(l, { recursive: !0 }), await w(h(l, "content"), JSON.stringify(s)), await w(h(l, "manifest"), JSON.stringify(i)), await w(h(a, "asset-manifest.json"), JSON.stringify(i, null, 2)), console.log(c.dim("Generating service worker..."));
552
+ await T(l, { recursive: !0 }), await w(h(l, "content"), JSON.stringify(s)), await w(h(l, "manifest"), JSON.stringify(i)), await w(
553
+ h(a, "asset-manifest.json"),
554
+ JSON.stringify(i, null, 2)
555
+ ), console.log(c.dim("Generating service worker..."));
539
556
  const d = ae(i);
540
557
  await w(h(a, "sw.js"), d), console.log(""), console.log(c.green("✅ Build complete! Output in dist/")), console.log(""), console.log("To preview the build:"), console.log(c.dim(" yarn preview")), console.log("");
541
558
  } catch (a) {
542
559
  console.error(c.red("Build failed:"), a), process.exit(1);
543
560
  }
544
561
  }
545
- async function se(t) {
562
+ async function se(n) {
546
563
  const o = {
547
564
  locations: {},
548
565
  characters: {},
@@ -563,7 +580,7 @@ async function se(t) {
563
580
  { dir: "interludes", key: "interludes" }
564
581
  ];
565
582
  for (const { dir: a, key: e } of r) {
566
- const i = h(t, a);
583
+ const i = h(n, a);
567
584
  try {
568
585
  const l = await b(i);
569
586
  for (const d of l)
@@ -575,7 +592,7 @@ async function se(t) {
575
592
  }
576
593
  }
577
594
  try {
578
- const a = h(t, "locales"), e = await b(a);
595
+ const a = h(n, "locales"), e = await b(a);
579
596
  for (const i of e)
580
597
  if (f(i) === ".yaml" || f(i) === ".yml") {
581
598
  const l = h(a, i), d = await p(l, "utf-8"), u = y(d), m = i.replace(/\.(yaml|yml)$/, "");
@@ -584,7 +601,7 @@ async function se(t) {
584
601
  } catch {
585
602
  }
586
603
  try {
587
- const a = h(t, "dialogues"), e = await b(a);
604
+ const a = h(n, "dialogues"), e = await b(a);
588
605
  for (const i of e)
589
606
  if (f(i) === ".dlg") {
590
607
  const l = h(a, i), d = await p(l, "utf-8"), u = i.replace(".dlg", ""), m = k(d, u);
@@ -592,26 +609,33 @@ async function se(t) {
592
609
  }
593
610
  } catch {
594
611
  }
595
- let n = null;
612
+ let t = null;
596
613
  try {
597
- const a = h(t, "game.yaml"), e = await p(a, "utf-8");
598
- n = y(e);
614
+ const a = h(n, "game.yaml"), e = await p(a, "utf-8");
615
+ t = y(e);
599
616
  } catch {
600
- n = { id: "game", startLocation: "", startTime: { day: 1, hour: 8 }, startFlags: {}, startVariables: {}, startInventory: [] };
617
+ t = {
618
+ id: "game",
619
+ startLocation: "",
620
+ startTime: { day: 1, hour: 8 },
621
+ startFlags: {},
622
+ startVariables: {},
623
+ startInventory: []
624
+ };
601
625
  }
602
- return { registry: o, fileMap: s, config: n };
626
+ return { registry: o, fileMap: s, config: t };
603
627
  }
604
628
  async function ie() {
605
- const t = process.cwd(), o = h(t, "content");
629
+ const n = process.cwd(), o = h(n, "content");
606
630
  console.log(""), console.log(c.bold.magenta("ðŸū Validating Doodle Engine content...")), console.log("");
607
631
  try {
608
- const { registry: s, fileMap: r } = await ce(o), n = C(s, r);
609
- I(n), n.length > 0 && process.exit(1);
632
+ const { registry: s, fileMap: r } = await ce(o), t = C(s, r);
633
+ I(t), t.length > 0 && process.exit(1);
610
634
  } catch (s) {
611
635
  console.error(c.red("Error loading content:"), s), process.exit(1);
612
636
  }
613
637
  }
614
- async function ce(t) {
638
+ async function ce(n) {
615
639
  const o = {
616
640
  locations: {},
617
641
  characters: {},
@@ -629,8 +653,8 @@ async function ce(t) {
629
653
  { dir: "quests", key: "quests" },
630
654
  { dir: "journal", key: "journalEntries" }
631
655
  ];
632
- for (const { dir: n, key: a } of r) {
633
- const e = h(t, n);
656
+ for (const { dir: t, key: a } of r) {
657
+ const e = h(n, t);
634
658
  try {
635
659
  const i = await b(e);
636
660
  for (const l of i)
@@ -642,19 +666,19 @@ async function ce(t) {
642
666
  }
643
667
  }
644
668
  try {
645
- const n = h(t, "locales"), a = await b(n);
669
+ const t = h(n, "locales"), a = await b(t);
646
670
  for (const e of a)
647
671
  if (f(e) === ".yaml" || f(e) === ".yml") {
648
- const i = h(n, e), l = await p(i, "utf-8"), d = y(l), u = e.replace(/\.(yaml|yml)$/, "");
672
+ const i = h(t, e), l = await p(i, "utf-8"), d = y(l), u = e.replace(/\.(yaml|yml)$/, "");
649
673
  o.locales[u] = d ?? {};
650
674
  }
651
675
  } catch {
652
676
  }
653
677
  try {
654
- const n = h(t, "dialogues"), a = await b(n);
678
+ const t = h(n, "dialogues"), a = await b(t);
655
679
  for (const e of a)
656
680
  if (f(e) === ".dlg") {
657
- const i = h(n, e), l = await p(i, "utf-8"), d = e.replace(".dlg", ""), u = k(l, d);
681
+ const i = h(t, e), l = await p(i, "utf-8"), d = e.replace(".dlg", ""), u = k(l, d);
658
682
  o.dialogues[u.id] = u, s.set(u.id, E(process.cwd(), i));
659
683
  }
660
684
  } catch {
@@ -1364,52 +1388,82 @@ stages:
1364
1388
  description: "@quest.odd_jobs.stage.talked_to_merchant"
1365
1389
  - id: complete
1366
1390
  description: "@quest.odd_jobs.stage.complete"
1367
- `, Re = `import { useEffect, useState } from 'react'
1368
- import { Engine } from '@doodle-engine/core'
1369
- import type { GameState, Snapshot } from '@doodle-engine/core'
1370
- import { GameProvider, useGame } from '@doodle-engine/react'
1391
+ `, Re = `import { useEffect, useState } from "react";
1392
+ import { Engine } from "@doodle-engine/core";
1393
+ import type { GameState, Snapshot } from "@doodle-engine/core";
1394
+ import { GameProvider, useGame } from "@doodle-engine/react";
1371
1395
 
1372
1396
  export function App() {
1373
- const [game, setGame] = useState<{ engine: Engine; snapshot: Snapshot } | null>(null)
1397
+ const [game, setGame] = useState<{
1398
+ engine: Engine;
1399
+ snapshot: Snapshot;
1400
+ } | null>(null);
1374
1401
 
1375
1402
  useEffect(() => {
1376
- fetch('/api/content')
1377
- .then(res => res.json())
1378
- .then(data => {
1379
- const engine = new Engine(data.registry, createEmptyState())
1380
- const snapshot = engine.newGame(data.config)
1381
- setGame({ engine, snapshot })
1382
- })
1383
- }, [])
1403
+ fetch("/api/content")
1404
+ .then((res) => res.json())
1405
+ .then((data) => {
1406
+ const engine = new Engine(data.registry, createEmptyState());
1407
+ const snapshot = engine.newGame(data.config);
1408
+ setGame({ engine, snapshot });
1409
+ });
1410
+ }, []);
1384
1411
 
1385
1412
  if (!game) {
1386
- return <div className="app-bootstrap"><div className="spinner" /></div>
1413
+ return (
1414
+ <div className="app-bootstrap">
1415
+ <div className="spinner" />
1416
+ </div>
1417
+ );
1387
1418
  }
1388
1419
 
1389
1420
  return (
1390
- <GameProvider engine={game.engine} initialSnapshot={game.snapshot} devTools={import.meta.env.DEV}>
1421
+ <GameProvider
1422
+ engine={game.engine}
1423
+ initialSnapshot={game.snapshot}
1424
+ devTools={import.meta.env.DEV}
1425
+ >
1391
1426
  <GameUI />
1392
1427
  </GameProvider>
1393
- )
1428
+ );
1394
1429
  }
1395
1430
 
1396
1431
  function GameUI() {
1397
- const { snapshot, actions } = useGame()
1432
+ const { snapshot, actions } = useGame();
1398
1433
 
1399
1434
  return (
1400
- <div style={{ padding: '2rem', fontFamily: 'sans-serif', maxWidth: '800px', margin: '0 auto' }}>
1435
+ <div
1436
+ style={{
1437
+ padding: "2rem",
1438
+ fontFamily: "sans-serif",
1439
+ maxWidth: "800px",
1440
+ margin: "0 auto",
1441
+ }}
1442
+ >
1401
1443
  <h1>{snapshot.location.name}</h1>
1402
1444
  <p>{snapshot.location.description}</p>
1403
1445
 
1404
1446
  {snapshot.dialogue && (
1405
- <div style={{ background: '#f0f0f0', padding: '1rem', borderRadius: '8px', margin: '1rem 0' }}>
1447
+ <div
1448
+ style={{
1449
+ background: "#f0f0f0",
1450
+ padding: "1rem",
1451
+ borderRadius: "8px",
1452
+ margin: "1rem 0",
1453
+ }}
1454
+ >
1406
1455
  <strong>{snapshot.dialogue.speakerName}:</strong>
1407
1456
  <p>{snapshot.dialogue.text}</p>
1408
- {snapshot.choices.map(choice => (
1457
+ {snapshot.choices.map((choice) => (
1409
1458
  <button
1410
1459
  key={choice.id}
1411
1460
  onClick={() => actions.selectChoice(choice.id)}
1412
- style={{ display: 'block', margin: '0.5rem 0', padding: '0.5rem 1rem', cursor: 'pointer' }}
1461
+ style={{
1462
+ display: "block",
1463
+ margin: "0.5rem 0",
1464
+ padding: "0.5rem 1rem",
1465
+ cursor: "pointer",
1466
+ }}
1413
1467
  >
1414
1468
  {choice.text}
1415
1469
  </button>
@@ -1420,11 +1474,16 @@ function GameUI() {
1420
1474
  {!snapshot.dialogue && snapshot.charactersHere.length > 0 && (
1421
1475
  <div>
1422
1476
  <h2>Characters here</h2>
1423
- {snapshot.charactersHere.map(char => (
1477
+ {snapshot.charactersHere.map((char) => (
1424
1478
  <button
1425
1479
  key={char.id}
1426
1480
  onClick={() => actions.talkTo(char.id)}
1427
- style={{ display: 'block', margin: '0.5rem 0', padding: '0.5rem 1rem', cursor: 'pointer' }}
1481
+ style={{
1482
+ display: "block",
1483
+ margin: "0.5rem 0",
1484
+ padding: "0.5rem 1rem",
1485
+ cursor: "pointer",
1486
+ }}
1428
1487
  >
1429
1488
  Talk to {char.name}
1430
1489
  </button>
@@ -1432,12 +1491,12 @@ function GameUI() {
1432
1491
  </div>
1433
1492
  )}
1434
1493
  </div>
1435
- )
1494
+ );
1436
1495
  }
1437
1496
 
1438
1497
  function createEmptyState(): GameState {
1439
1498
  return {
1440
- currentLocation: '',
1499
+ currentLocation: "",
1441
1500
  currentTime: { day: 1, hour: 0 },
1442
1501
  flags: {},
1443
1502
  variables: {},
@@ -1453,29 +1512,36 @@ function createEmptyState(): GameState {
1453
1512
  pendingSounds: [],
1454
1513
  pendingVideo: null,
1455
1514
  pendingInterlude: null,
1456
- currentLocale: 'en',
1457
- }
1515
+ currentLocale: "en",
1516
+ };
1458
1517
  }
1459
- `, Ae = `import { useEffect, useState } from 'react'
1460
- import type { ContentRegistry, GameConfig, AssetManifest } from '@doodle-engine/core'
1461
- import { GameShell } from '@doodle-engine/react'
1518
+ `, Ae = `import { useEffect, useState } from "react";
1519
+ import type {
1520
+ ContentRegistry,
1521
+ GameConfig,
1522
+ AssetManifest,
1523
+ } from "@doodle-engine/core";
1524
+ import { GameShell } from "@doodle-engine/react";
1462
1525
 
1463
1526
  export function App() {
1464
1527
  const [content, setContent] = useState<{
1465
- registry: ContentRegistry
1466
- config: GameConfig
1467
- } | null>(null)
1468
- const [manifest, setManifest] = useState<AssetManifest | null>(null)
1528
+ registry: ContentRegistry;
1529
+ config: GameConfig;
1530
+ } | null>(null);
1531
+ const [manifest, setManifest] = useState<AssetManifest | null>(null);
1469
1532
 
1470
1533
  useEffect(() => {
1471
1534
  Promise.all([
1472
- fetch('/api/content').then(res => res.json()),
1473
- fetch('/api/manifest').then(res => res.json()),
1535
+ fetch("/api/content").then((res) => res.json()),
1536
+ fetch("/api/manifest").then((res) => res.json()),
1474
1537
  ]).then(([contentData, manifestData]) => {
1475
- setContent({ registry: contentData.registry, config: contentData.config })
1476
- setManifest(manifestData)
1477
- })
1478
- }, [])
1538
+ setContent({
1539
+ registry: contentData.registry,
1540
+ config: contentData.config,
1541
+ });
1542
+ setManifest(manifestData);
1543
+ });
1544
+ }, []);
1479
1545
 
1480
1546
  // Minimal bootstrap state while fetching manifest/content
1481
1547
  if (!content || !manifest) {
@@ -1483,7 +1549,7 @@ export function App() {
1483
1549
  <div className="app-bootstrap">
1484
1550
  <div className="spinner" />
1485
1551
  </div>
1486
- )
1552
+ );
1487
1553
  }
1488
1554
 
1489
1555
  return (
@@ -1493,73 +1559,49 @@ export function App() {
1493
1559
  manifest={manifest}
1494
1560
  title="My Doodle Game"
1495
1561
  subtitle="A text-based adventure"
1496
- availableLocales={[{ code: 'en', label: 'English' }]}
1562
+ availableLocales={[{ code: "en", label: "English" }]}
1497
1563
  devTools={import.meta.env.DEV}
1498
1564
  />
1499
- )
1565
+ );
1500
1566
  }
1501
1567
  `, Se = `body {
1502
1568
  margin: 0;
1503
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
1504
- 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
1505
- sans-serif;
1569
+ font-family:
1570
+ -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu",
1571
+ "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
1506
1572
  -webkit-font-smoothing: antialiased;
1507
1573
  -moz-osx-font-smoothing: grayscale;
1508
1574
  }
1509
1575
 
1510
- /* ── Shell CSS variables ──────────────────────────────────────────── */
1511
- /* Override these to theme the loading, splash, and title screens. */
1576
+ /* ── Theme overrides ──────────────────────────────────────────────── */
1577
+ /* Override these variables to customize the shell screens. */
1578
+ /* Full variable list: node_modules/@doodle-engine/react/dist/shell.css */
1512
1579
 
1580
+ /*
1513
1581
  :root {
1514
1582
  --doodle-bg-primary: #0f0f23;
1515
1583
  --doodle-bg-secondary: #1a1a2e;
1516
- --doodle-text-primary: #ffffff;
1517
- --doodle-text-secondary: #a0a0b0;
1518
1584
  --doodle-accent: #6366f1;
1519
- --doodle-accent-hover: #818cf8;
1520
- --doodle-font-family: system-ui, -apple-system, sans-serif;
1521
1585
  }
1522
-
1523
- /* ── Bootstrap loader (shown before manifest/content are fetched) ─── */
1524
-
1525
- .app-bootstrap {
1526
- position: fixed;
1527
- inset: 0;
1528
- display: flex;
1529
- align-items: center;
1530
- justify-content: center;
1531
- background: var(--doodle-bg-primary);
1532
- }
1533
-
1534
- .app-bootstrap .spinner {
1535
- width: 40px;
1536
- height: 40px;
1537
- border: 3px solid rgba(255, 255, 255, 0.1);
1538
- border-top-color: var(--doodle-accent);
1539
- border-radius: 50%;
1540
- animation: spin 0.8s linear infinite;
1541
- }
1542
-
1543
- @keyframes spin {
1544
- to { transform: rotate(360deg); }
1545
- }
1546
- `, je = `import { StrictMode } from 'react'
1547
- import { createRoot } from 'react-dom/client'
1548
- import { App } from './App'
1549
- import './index.css'
1586
+ */
1587
+ `, je = `import { StrictMode } from "react";
1588
+ import { createRoot } from "react-dom/client";
1589
+ import "@doodle-engine/react/style.css";
1590
+ import { App } from "./App";
1591
+ import "./index.css";
1550
1592
 
1551
1593
  // Register service worker in production for offline asset caching
1552
- if ('serviceWorker' in navigator && import.meta.env.PROD) {
1553
- navigator.serviceWorker.register('/sw.js').catch(() => {
1594
+ if ("serviceWorker" in navigator && import.meta.env.PROD) {
1595
+ navigator.serviceWorker.register("/sw.js").catch(() => {
1554
1596
  // SW registration failure is non-fatal
1555
- })
1597
+ });
1556
1598
  }
1557
1599
 
1558
- createRoot(document.getElementById('root')!).render(
1600
+ createRoot(document.getElementById("root")!).render(
1559
1601
  <StrictMode>
1560
1602
  <App />
1561
1603
  </StrictMode>,
1562
- )
1604
+ );
1563
1605
  `, D = "ðŸū", xe = "🐕", H = "ðŸĶī", Ge = "âœĻ", $ = "📁", N = "✅", qe = "🚀", x = /* @__PURE__ */ Object.assign({
1564
1606
  "./templates/_root/_gitignore": le,
1565
1607
  "./templates/_root/index.html": de,
@@ -1587,28 +1629,35 @@ createRoot(document.getElementById('root')!).render(
1587
1629
  "./templates/src/index.css": Se,
1588
1630
  "./templates/src/main.tsx": je
1589
1631
  });
1590
- function He(t) {
1591
- const o = t.replace("./templates/", "");
1592
- if (o === "src/App.default.tsx" || o === "src/App.custom.tsx") return null;
1632
+ function He(n) {
1633
+ const o = n.replace("./templates/", "");
1634
+ if (o === "src/App.default.tsx" || o === "src/App.custom.tsx")
1635
+ return null;
1593
1636
  if (o.startsWith("_root/")) {
1594
1637
  const s = o.slice(6);
1595
1638
  return s.startsWith("_") ? "." + s.slice(1) : s;
1596
1639
  }
1597
1640
  return o;
1598
1641
  }
1599
- async function $e(t) {
1600
- const o = h(process.cwd(), t);
1601
- console.log(""), console.log(c.bold.magenta(` ${D} Doodle Engine ${D}`)), console.log(c.dim(" Text-based RPG and Adventure Game Scaffolder")), console.log(""), console.log(` ${xe} Creating new game: ${c.bold.cyan(t)}`), console.log("");
1642
+ async function $e(n) {
1643
+ const o = h(process.cwd(), n);
1644
+ console.log(""), console.log(c.bold.magenta(` ${D} Doodle Engine ${D}`)), console.log(c.dim(" Text-based RPG and Adventure Game Scaffolder")), console.log(""), console.log(` ${xe} Creating new game: ${c.bold.cyan(n)}`), console.log("");
1602
1645
  const { useDefaultRenderer: s } = await J({
1603
1646
  type: "confirm",
1604
1647
  name: "useDefaultRenderer",
1605
1648
  message: "Use default renderer?",
1606
1649
  initial: !0
1607
1650
  });
1608
- s === void 0 && (console.log(c.yellow(`
1609
- ${H} No worries, maybe next time! Woof!`)), process.exit(0)), console.log(""), await Me(o, t, s), console.log(""), console.log(c.bold.green(` ${N} Project created successfully!`)), console.log(""), console.log(c.dim(` ${$} ${o}`)), console.log(""), console.log(c.bold(" Next steps:")), console.log(c.cyan(` cd ${t}`)), console.log(c.cyan(" npm install ") + c.dim("# or: yarn install / pnpm install")), console.log(c.cyan(" npm run dev ") + c.dim("# or: yarn dev / pnpm dev")), console.log(""), console.log(c.dim(` ${qe} Happy game making! ${D}`)), console.log("");
1651
+ s === void 0 && (console.log(
1652
+ c.yellow(`
1653
+ ${H} No worries, maybe next time! Woof!`)
1654
+ ), process.exit(0)), console.log(""), await Me(o, n, s), console.log(""), console.log(c.bold.green(` ${N} Project created successfully!`)), console.log(""), console.log(c.dim(` ${$} ${o}`)), console.log(""), console.log(c.bold(" Next steps:")), console.log(c.cyan(` cd ${n}`)), console.log(
1655
+ c.cyan(" npm install ") + c.dim("# or: yarn install / pnpm install")
1656
+ ), console.log(
1657
+ c.cyan(" npm run dev ") + c.dim("# or: yarn dev / pnpm dev")
1658
+ ), console.log(""), console.log(c.dim(` ${qe} Happy game making! ${D}`)), console.log("");
1610
1659
  }
1611
- async function Me(t, o, s) {
1660
+ async function Me(n, o, s) {
1612
1661
  const r = [
1613
1662
  "content/locations",
1614
1663
  "content/characters",
@@ -1630,9 +1679,9 @@ async function Me(t, o, s) {
1630
1679
  ];
1631
1680
  console.log(` ${$} ${c.bold("Creating directories...")}`);
1632
1681
  for (const e of r)
1633
- await T(h(t, e), { recursive: !0 });
1682
+ await T(h(n, e), { recursive: !0 });
1634
1683
  console.log(c.green(` ${N} Directories created`)), console.log("");
1635
- const n = {
1684
+ const t = {
1636
1685
  name: o,
1637
1686
  version: "0.1.0",
1638
1687
  type: "module",
@@ -1656,23 +1705,34 @@ async function Me(t, o, s) {
1656
1705
  vite: "^6.0.0"
1657
1706
  }
1658
1707
  };
1659
- console.log(` ${Ge} ${c.bold("Writing project files...")}`), await w(h(t, "package.json"), JSON.stringify(n, null, 2));
1708
+ console.log(` ${Ge} ${c.bold("Writing project files...")}`), await w(
1709
+ h(n, "package.json"),
1710
+ JSON.stringify(t, null, 2)
1711
+ );
1660
1712
  for (const [e, i] of Object.entries(x)) {
1661
1713
  const l = He(e);
1662
1714
  if (l === null) continue;
1663
- const d = h(t, l);
1715
+ const d = h(n, l);
1664
1716
  await T(U(d), { recursive: !0 }), await w(d, i);
1665
1717
  }
1666
1718
  const a = s ? "./templates/src/App.default.tsx" : "./templates/src/App.custom.tsx";
1667
- await w(h(t, "src/App.tsx"), x[a]), console.log(c.green(` ${N} Source files created`)), console.log(""), console.log(` ${H} ${c.bold("Starter content written")}`), console.log(""), console.log(c.dim(" Content includes:")), console.log(c.dim(" 2 locations (tavern, market)")), console.log(c.dim(" 2 characters (bartender, merchant)")), console.log(c.dim(" 1 item (old coin)")), console.log(c.dim(" 1 map (town with 2 locations)")), console.log(c.dim(" 1 quest (odd jobs, 3 stages)")), console.log(c.dim(" 3 journal entries")), console.log(c.dim(" 1 interlude (chapter one, auto-triggers at tavern)")), console.log(c.dim(" 5 dialogues (2 narrator intros, 2 NPC conversations, 1 skill check)")), console.log(c.dim(" English locale with all strings"));
1719
+ await w(h(n, "src/App.tsx"), x[a]), console.log(c.green(` ${N} Source files created`)), console.log(""), console.log(` ${H} ${c.bold("Starter content written")}`), console.log(""), console.log(c.dim(" Content includes:")), console.log(c.dim(" 2 locations (tavern, market)")), console.log(c.dim(" 2 characters (bartender, merchant)")), console.log(c.dim(" 1 item (old coin)")), console.log(c.dim(" 1 map (town with 2 locations)")), console.log(c.dim(" 1 quest (odd jobs, 3 stages)")), console.log(c.dim(" 3 journal entries")), console.log(
1720
+ c.dim(" 1 interlude (chapter one, auto-triggers at tavern)")
1721
+ ), console.log(
1722
+ c.dim(
1723
+ " 5 dialogues (2 narrator intros, 2 NPC conversations, 1 skill check)"
1724
+ )
1725
+ ), console.log(c.dim(" English locale with all strings"));
1668
1726
  }
1669
1727
  const v = new M();
1670
- v.name("doodle").description(c.magenta("ðŸū Doodle Engine") + c.dim(" — Narrative RPG development tools")).version("0.0.1");
1671
- v.command("create <project-name>").description("Scaffold a new Doodle Engine game project").action(async (t) => {
1672
- await $e(t);
1728
+ v.name("doodle").description(
1729
+ c.magenta("ðŸū Doodle Engine") + c.dim(" — Narrative RPG development tools")
1730
+ ).version("0.0.1");
1731
+ v.command("create <project-name>").description("Scaffold a new Doodle Engine game project").action(async (n) => {
1732
+ await $e(n);
1673
1733
  });
1674
1734
  v.command("dev").description("Start development server with hot reload").action(async () => {
1675
- await te();
1735
+ await ne();
1676
1736
  });
1677
1737
  v.command("build").description("Build game for production").action(async () => {
1678
1738
  await re();