lecodes-cli 1.0.0 → 2.0.1

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 (104) hide show
  1. package/dist/index.js +6211 -3649
  2. package/package.json +12 -8
  3. package/runtime/materials/lightmap.mat +115 -139
  4. package/runtime/materials/particles-quad.mat +6 -2
  5. package/runtime/materials/terrain-lightmap.mat +123 -0
  6. package/runtime/materials/terrain.mat +36 -55
  7. package/runtime/sdk-types.json +1 -1
  8. package/runtime/web/VERSION.json +5 -0
  9. package/runtime/web/assets/camera-5XOM8eHl.js +1 -0
  10. package/runtime/web/assets/creator-2d-D9WKYM6h.js +1 -0
  11. package/runtime/web/assets/creator-2d-ukvEmNEp.wasm +0 -0
  12. package/runtime/web/assets/creator-full-BBXzQlhq.js +1 -0
  13. package/runtime/web/assets/creator-full-D2dSDdDj.wasm +0 -0
  14. package/runtime/web/assets/creator-full.js-BYswGsqR.symbols +8124 -0
  15. package/runtime/web/assets/creator-ui-DIc92ion.wasm +0 -0
  16. package/runtime/web/assets/creator-ui-_1Z_AX2M.js +1 -0
  17. package/runtime/web/assets/geolocation-BjTKN9_P.js +1 -0
  18. package/runtime/web/assets/inspect-C7x78PNs.js +2 -0
  19. package/runtime/web/assets/map-CyBxOSV0.js +1 -0
  20. package/runtime/web/assets/map-XzoOFt0E.js +2 -0
  21. package/runtime/web/assets/mapImpl-bxvQ2h1Z.js +823 -0
  22. package/runtime/web/assets/maplibre-gl-worker-B7LXXUkR.js +8 -0
  23. package/runtime/web/assets/materials-uri4FvPE.bin +0 -0
  24. package/runtime/web/assets/materials-world-CQWt8TAm.bin +0 -0
  25. package/runtime/web/assets/qr-scanner-CK_cYoJR.js +1 -0
  26. package/runtime/web/assets/uberarchive-DLH-kBhM.bin +0 -0
  27. package/runtime/web/index.html +36 -0
  28. package/runtime/web/player.js +21 -0
  29. package/runtime/web-page/index.html +41 -0
  30. package/runtime/web-page/page.css +1 -0
  31. package/runtime/web-page/page.js +31 -0
  32. package/src/cli/help.ts +25 -0
  33. package/src/cli/index.ts +7 -7
  34. package/src/commands/app/android.ts +3 -1
  35. package/src/commands/app/desktop.ts +1 -1
  36. package/src/commands/app/desktopMac.ts +10 -9
  37. package/src/commands/app/icon.ts +9 -6
  38. package/src/commands/app/index.ts +3 -1
  39. package/src/commands/app/shared.ts +13 -5
  40. package/src/commands/app/templates/android.ts +10 -11
  41. package/src/commands/app/templates/ios.ts +1 -1
  42. package/src/commands/assets.ts +1 -1
  43. package/src/commands/compile.ts +15 -2
  44. package/src/commands/design/index.ts +4 -4
  45. package/src/commands/design/snapshot.ts +3 -3
  46. package/src/commands/desktop.ts +5 -1
  47. package/src/commands/dev.ts +82 -29
  48. package/src/commands/index.ts +64 -60
  49. package/src/commands/init/index.ts +11 -15
  50. package/src/commands/init/templates.ts +64 -21
  51. package/src/commands/lightmap.ts +459 -200
  52. package/src/commands/navmesh.ts +2 -2
  53. package/src/commands/plugin.ts +46 -0
  54. package/src/commands/render.ts +22 -16
  55. package/src/commands/scene.ts +6 -1
  56. package/src/commands/serve.ts +58 -0
  57. package/src/commands/shaders/new.ts +1 -1
  58. package/src/commands/test.ts +402 -398
  59. package/src/commands/update/index.ts +3 -3
  60. package/src/compile/designCompile.ts +1 -1
  61. package/src/compile/headlessBundle.ts +133 -133
  62. package/src/compile/nativeStack.ts +1 -1
  63. package/src/compile/projectCompile.ts +10 -2
  64. package/src/compile/sceneCompile.ts +8 -22
  65. package/src/declarations/lecodes-3d-editor.d.ts +4 -0
  66. package/src/declarations/lecodes-assets.d.ts +1 -1
  67. package/src/declarations/{lecodes-renderer.d.ts → lecodes-headless.d.ts} +3 -3
  68. package/src/dev/clientTemplates.ts +18 -14
  69. package/src/dev/devServer.ts +75 -38
  70. package/src/dev/iosDev.ts +180 -0
  71. package/src/dev/webRunner.ts +82 -14
  72. package/src/hosts/cmgenTool.ts +1 -1
  73. package/src/hosts/desktopRenderer.ts +98 -27
  74. package/src/hosts/desktopScript.ts +150 -35
  75. package/src/hosts/distRoot.ts +1 -1
  76. package/src/hosts/matcTool.ts +1 -1
  77. package/src/hosts/peerInstall.ts +5 -5
  78. package/src/hosts/peers.ts +2 -2
  79. package/src/index.ts +4 -1
  80. package/src/plugin/contract.ts +438 -0
  81. package/src/plugin/emitKotlin.ts +357 -0
  82. package/src/plugin/emitSdk.ts +259 -0
  83. package/src/plugin/emitSwift.ts +336 -0
  84. package/src/plugin/emitWeb.ts +383 -0
  85. package/src/plugin/generate.ts +157 -0
  86. package/src/plugin/lines.ts +28 -0
  87. package/src/plugin/lower.ts +231 -0
  88. package/src/plugin/manifest.ts +192 -0
  89. package/src/project/ignore.ts +164 -163
  90. package/runtime/materials/lightmap-baked-lite.mat +0 -180
  91. package/runtime/materials/lightmap-baked.mat +0 -181
  92. package/runtime/scene-harness.json +0 -1
  93. package/runtime/web/assets/__vite-browser-external-BIHI7g3E.js +0 -1
  94. package/runtime/web/assets/basis-C64VHDVD.js +0 -1
  95. package/runtime/web/assets/basis_transcoder-VXdx5NbI.wasm +0 -0
  96. package/runtime/web/assets/createViewerLite-DHqTFhWB.js +0 -866
  97. package/runtime/web/assets/draco-BiISTFcR.js +0 -118
  98. package/runtime/web/assets/draco_decoder-DsQ12WqX.wasm +0 -0
  99. package/runtime/web/assets/index-Q06c6oHx.js +0 -2
  100. package/runtime/web/assets/mapViewImpl-B2JcES8l.js +0 -810
  101. package/runtime/web/assets/maplibre-gl-worker-CJfwIrte.js +0 -8
  102. package/runtime/web/assets/worker-Caf-yYEI.js +0 -2
  103. package/runtime/web/embed.html +0 -32
  104. package/runtime/web/embed.js +0 -171
@@ -1,60 +1,64 @@
1
- import type { Group } from "../cli"
2
- import app from "./app"
3
- import assets from "./assets"
4
- import clone from "./clone"
5
- import compile from "./compile"
6
- import create from "./create"
7
- import design from "./design"
8
- import desktop from "./desktop"
9
- import dev from "./dev"
10
- import diff from "./diff"
11
- import init from "./init"
12
- import install from "./install"
13
- import lightmap from "./lightmap"
14
- import link from "./link"
15
- import login from "./login"
16
- import navmesh from "./navmesh"
17
- import pn from "./pn"
18
- import pull from "./pull"
19
- import push from "./push"
20
- import render from "./render"
21
- import scene from "./scene"
22
- import shaders from "./shaders"
23
- import status from "./status"
24
- import test from "./test"
25
- import thumbs from "./thumbs"
26
- import update from "./update"
27
-
28
- /*
29
- * THE command list. Adding a command = one file with a `defineCommand` default export + one line
30
- * here, in the group it belongs to (CONTRIBUTING.md). Order within a group is the help's order.
31
- */
32
- export const commandGroups = (opts: { install: boolean }): Group[] => [
33
- { title: "Project", commands: [init, create, clone, link, status, diff, pull, push, login] },
34
- { title: "Run & test", commands: [
35
- dev,
36
- compile,
37
- render,
38
- test,
39
- desktop,
40
- ] },
41
- { title: "Editors", commands: [
42
- design,
43
- scene,
44
- thumbs,
45
- ] },
46
- { title: "Content", commands: [
47
- assets,
48
- shaders,
49
- lightmap,
50
- navmesh,
51
- ] },
52
- { title: "Native apps", commands: [
53
- app,
54
- pn,
55
- ] },
56
- { title: "Maintenance", commands: [
57
- update,
58
- { ...install, hidden: !opts.install },
59
- ] },
60
- ]
1
+ import type { Group } from "../cli"
2
+ import app from "./app"
3
+ import assets from "./assets"
4
+ import clone from "./clone"
5
+ import compile from "./compile"
6
+ import create from "./create"
7
+ import design from "./design"
8
+ import desktop from "./desktop"
9
+ import dev from "./dev"
10
+ import diff from "./diff"
11
+ import init from "./init"
12
+ import install from "./install"
13
+ import lightmap from "./lightmap"
14
+ import link from "./link"
15
+ import login from "./login"
16
+ import navmesh from "./navmesh"
17
+ import plugin from "./plugin"
18
+ import pn from "./pn"
19
+ import pull from "./pull"
20
+ import push from "./push"
21
+ import render from "./render"
22
+ import scene from "./scene"
23
+ import serve from "./serve"
24
+ import shaders from "./shaders"
25
+ import status from "./status"
26
+ import test from "./test"
27
+ import thumbs from "./thumbs"
28
+ import update from "./update"
29
+
30
+ /*
31
+ * THE command list. Adding a command = one file with a `defineCommand` default export + one line
32
+ * here, in the group it belongs to (CONTRIBUTING.md). Order within a group is the help's order.
33
+ */
34
+ export const commandGroups = (opts: { install: boolean }): Group[] => [
35
+ { title: "Project", commands: [init, create, clone, link, status, diff, pull, push, login] },
36
+ { title: "Run & test", commands: [
37
+ dev,
38
+ compile,
39
+ render,
40
+ test,
41
+ desktop,
42
+ serve,
43
+ ] },
44
+ { title: "Editors", commands: [
45
+ design,
46
+ scene,
47
+ thumbs,
48
+ ] },
49
+ { title: "Content", commands: [
50
+ assets,
51
+ shaders,
52
+ lightmap,
53
+ navmesh,
54
+ ] },
55
+ { title: "Native apps", commands: [
56
+ app,
57
+ plugin,
58
+ pn,
59
+ ] },
60
+ { title: "Maintenance", commands: [
61
+ update,
62
+ { ...install, hidden: !opts.install },
63
+ ] },
64
+ ]
@@ -25,10 +25,10 @@ const STARTER = `// Welcome to LeCodes — the whole SDK is available as globals
25
25
 
26
26
  let count = 0
27
27
 
28
- const counter = UIText({ color: "#9fb0d0", fontSize: 22, marginTop: 18 }, "Taps: 0")
28
+ const counter = UIText("Taps: 0").style({ color: "#9fb0d0", fontSize: 22, marginTop: 18 })
29
29
 
30
- const button = UIButton(
31
- {
30
+ const button = UIButton(UIText("Tap me").style({ color: "#ffffff", fontSize: 22 }))
31
+ .style({
32
32
  backgroundColor: "#4f7cff",
33
33
  borderRadius: 14,
34
34
  paddingVertical: 16,
@@ -36,24 +36,20 @@ const button = UIButton(
36
36
  marginTop: 24,
37
37
  alignItems: "center",
38
38
  justifyContent: "center",
39
- },
40
- [UIText({ color: "#ffffff", fontSize: 22 }, "Tap me")],
41
- )
42
- .style({ name: "tap" }) // names make elements targetable in tests/*.flow.json (see CLAUDE.md)
39
+ $hovered: { backgroundColor: "#6b91ff" }, // the mouse over it (desktop); $pressed is the press
40
+ })
41
+ .named("tap") // names make elements targetable in tests/*.flow.json (see CLAUDE.md)
43
42
  .onClick(() => {
44
43
  count++
45
44
  counter.text = "Taps: " + count
46
45
  })
47
46
 
48
47
  const screen = UIScreen(
49
- { backgroundColor: "#1e2230", alignItems: "center", justifyContent: "center", padding: 40 },
50
- [
51
- UIColumn(
52
- { backgroundColor: "#2b3550", borderRadius: 24, padding: 40, alignItems: "center" },
53
- [UIText({ color: "#ffffff", fontSize: 32 }, "Hello, LeCodes"), button, counter],
54
- ),
55
- ],
56
- ).style({ name: "home" }) // the screen root's name is what tests' \`expect: { screen: … }\` reads
48
+ UIColumn(UIText("Hello, LeCodes").style({ color: "#ffffff", fontSize: 32 }), button, counter)
49
+ .style({ backgroundColor: "#2b3550", borderRadius: 24, padding: 40, alignItems: "center" }),
50
+ )
51
+ .style({ backgroundColor: "#1e2230", alignItems: "center", justifyContent: "center", padding: 40 })
52
+ .named("home") // the screen root's name is what tests' \`expect: { screen: … }\` reads
57
53
 
58
54
  screen.open()
59
55
  `
@@ -2,7 +2,7 @@
2
2
  * has its own set — designTemplates.ts). CLAUDE.md is the AI-assistant counterpart of README.md:
3
3
  * it teaches the headless feedback loop (render / --script / test) and the conventions that make
4
4
  * an app testable, so an assistant opening the project drives the real app instead of guessing.
5
- * Facts here mirror lecodes-renderer's scenario engine — update both when the format changes. */
5
+ * Facts here mirror lecodes-headless's scenario engine — update both when the format changes. */
6
6
 
7
7
  export const appClaudeMd = (name: string) => `# ${name} — LeCodes app
8
8
 
@@ -30,17 +30,22 @@ lecodes render --desktop --view iso --png iso.png # 3D: inspection camera fitt
30
30
  # scene (iso|top|front|side|back [--fov N]) — or --camera x,y,z
31
31
  # --look-at x,y,z; the app's camera is untouched. In scripts:
32
32
  # { "view": "iso", "screenshot": "iso.png" } then { "view": "off" }
33
- lecodes render --desktop --script <file> # the same scenario on the NATIVE host (3D/Jolt, real
34
- # GPU): coordinates only ({ x, y }), expect.log checked,
35
- # text/node/screen expects skipped; deterministic 16 ms frames
33
+ lecodes render --desktop --script <file> # the same scenario on the NATIVE host (real GPU, the
34
+ # real hit-test): the same steps, targets and expects as
35
+ # headless (only expect.audio / the json output are headless-only)
36
+ lecodes render --sim --script <file> # the WINDOWLESS native host (lecodes-server): the real
37
+ # engines, no GPU, no pixels, no UI hit-test — expect.log /
38
+ # expect.paint only; runs on CI and beside an open desktop window
36
39
  lecodes test # all tests/*.flow.json + design-derived flows; run before "done"
37
- lecodes test --desktop # the file flows on the NATIVE host (3D/Jolt/real GPU; coordinate
38
- # flows only — design flows stay headless)
40
+ lecodes test --desktop # the file flows on the NATIVE host (design flows stay headless)
41
+ lecodes test --sim # the windowless twin: coordinates + expect.log / expect.paint only
39
42
  lecodes dev --android --time 12 --png phone.png # the REAL phone over adb (USB): launches the app on
40
43
  # the dev url via adb reverse, streams device logs as [device:log],
41
44
  # screenshots after 12 s, force-stops, exit 1 on a crash. --serial
42
45
  # <id> with several devices, --apk <file> to install a build first.
43
46
  # Without --time it runs until Ctrl+C (which stops the app).
47
+ lecodes dev --ios --time 12 --png sim.png # the same on a booted iOS simulator or a plugged-in iPhone
48
+ # (Mac + Xcode): --udid <id|name>, --app <LeCodesApp.app> to install.
44
49
  \`\`\`
45
50
 
46
51
  Iterate with \`--script\` scenarios and keep the useful ones as \`tests/<name>.flow.json\` — they
@@ -49,7 +54,8 @@ are the regression suite. A failing step dumps the failing frame (JSON + PNG) un
49
54
 
50
55
  ## Scenario format (tests/*.flow.json and --script files)
51
56
 
52
- One action per step (\`tap\`, \`type\`+\`text\`, \`fill\`, \`scroll\` [+\`by\`], \`wait\`, and for
57
+ One action per step (\`tap\`, \`type\`+\`text\`, \`fill\`, \`scroll\` [+\`by\`], \`wait\`, \`hover\` (the
58
+ mouse over a node: \`$hovered\` + \`onMouseEnter\`; hovering elsewhere ends it), and for
53
59
  games: \`frames\` (advance N fixed 1/60 steps, no sleeping), \`drag\`+\`to\` [+\`ms\`], \`hold\`+\`ms\`,
54
60
  \`key\`+\`ms\` / \`keyDown\` / \`keyUp\` (KeyboardEvent.code: "ArrowRight", "KeyW", "Space" — mouse
55
61
  buttons are keys too: "MouseLeft"/"MouseRight"), \`look: [dx, dy]\` [+\`ms\`] (mouse motion for
@@ -57,12 +63,29 @@ buttons are keys too: "MouseLeft"/"MouseRight"), \`look: [dx, dy]\` [+\`ms\`] (m
57
63
  \`stick: { left: [x, y], right: [x, y], triggers: [l, r] }\` (values persist; \`index\` = pad),
58
64
  \`gamepad: "connect" | "disconnect"\`, pad buttons as keys: \`keyDown: "GamepadSouth"\`), then an
59
65
  optional \`expect\` (\`screen\`, \`node\`/\`notNode\`, \`text\`/\`notText\`,
60
- \`value: { target, equals }\`, \`log\` — a \`console.log\` line contains it), then outputs
61
- (\`screenshot\` [+\`clip\`: a node name or \`{ x, y, width, height }\`], \`json\`). Targets resolve by
62
- \`name\` → exact visible text → substring → input placeholder; a tap/drag on empty space reaches
66
+ \`value: { target, equals }\`, \`log\` — a \`console.log\` line contains it,
67
+ \`paint: { "<name>": { color: "#rrggbbaa", fontSize: 18, present: [...], absent: [...] } }\` — the
68
+ resolved style of a named node, \`audio: { played, playing }\`), then outputs
69
+ (\`screenshot\` [+\`clip\`: a node name or \`{ x, y, width, height }\`], \`json\`, \`dump\` — the
70
+ runtime's tree with every node's box + resolved paint, identical on every host). Targets resolve
71
+ by \`name\` → exact visible text → substring → input placeholder; a tap/drag on empty space reaches
63
72
  the 2D scene (sprite \`click\`/\`touchstart\`, \`ev.track()\`). Envelope keys: \`name\`,
64
73
  \`width\`/\`height\`, \`settle\`, \`fixtures\`, \`steps\`.
65
74
 
75
+ Three hosts run the same file, at a fixed 16 ms clock on all of them:
76
+
77
+ | host | flag | what it is | targets | expects |
78
+ |---|---|---|---|---|
79
+ | headless | (none) | Bun: the runtime + UI renderer + a recording 3D core (no GPU) | names or \`{ x, y }\` | all |
80
+ | sim | \`--sim\` | the windowless native server: real engines, no pixels, no UI | \`{ x, y }\`, straight to the scene | \`log\`, \`paint\` |
81
+ | desktop | \`--desktop\` | the hidden desktop window: pixels, GL materials, the real hit-test | names or \`{ x, y }\` | all but \`audio\` |
82
+
83
+ One flow file, the same verdict on headless and desktop: names resolve the same way (\`name\` →
84
+ exact text → substring → placeholder), every step runs (\`hover\`, \`type\`, \`fill\`, \`scroll\`,
85
+ \`clip\` included) and the semantic expects read the same tree. Only \`expect.audio\` and the
86
+ \`json\` output are the headless renderer's. On the desktop \`type\` taps the input and types into
87
+ it (appends); the sim has no UI, so it takes coordinates and \`expect.log\` / \`expect.paint\` only.
88
+
66
89
  \`\`\`json
67
90
  { "steps": [
68
91
  { "expect": { "screen": "home", "text": "Taps: 0" } },
@@ -72,19 +95,21 @@ the 2D scene (sprite \`click\`/\`touchstart\`, \`ev.track()\`). Envelope keys: \
72
95
  ] }
73
96
  \`\`\`
74
97
 
75
- Game time is deterministic headless: \`setLoop\`, 2D physics/animation and aspect \`update(dt)\`
76
- advance in fixed 1/60 steps driven by \`wait\`/\`frames\`/\`drag\`/\`key\` — never by a wall clock —
77
- so a flow that passes once passes every time. \`Input.key(code)\` reads the keys a flow holds and
78
- \`Input.on('keydown' | 'keyup', e => e.code)\` fires for each \`key\`/\`keyDown\`/\`keyUp\` step
79
- (events land before the next loop tick); \`stick\` values are what \`Input.gamepad(0).axis()\` reads.
80
- Headless draws UI + the 2D scene (sprites, tilemaps, Box2D); 3D is captured as JSON with REAL Jolt
81
- physics (bodies fall, characters collide, triggers fire, ragdolls drop — a character with no ground
82
- under it falls, like on device) — use \`--desktop\` for 3D pixels. \`console.log\` + \`expect.log\` is the assertion
83
- channel for game state that has no UI.
98
+ Game time is deterministic on every host: the Aspect / System phases (\`updateBefore\` / \`update\`),
99
+ 2D physics and animation advance in fixed 1/60 steps driven by \`wait\`/\`frames\`/\`drag\`/\`key\` —
100
+ never by a wall clock — so a flow that passes once passes every time. \`Input.key(code)\` reads the
101
+ keys a flow holds and \`Input.on('keydown' | 'keyup', e => e.code)\` fires for each
102
+ \`key\`/\`keyDown\`/\`keyUp\` step (events land before the next loop tick); \`stick\` values are what
103
+ \`Input.gamepad(0).axis()\` reads. Headless draws UI + the 2D scene (sprites, tilemaps, Box2D); 3D is
104
+ captured as JSON with REAL Jolt physics (bodies fall, characters collide, triggers fire, ragdolls
105
+ drop — a character with no ground under it falls, like on device) — use \`--desktop\` for 3D pixels,
106
+ \`--sim\` for engine behaviour on a box without a GPU. \`console.log\` + \`expect.log\` is the
107
+ assertion channel for game state that has no UI.
84
108
 
85
109
  ## Make the app testable
86
110
 
87
- - Give interactive elements a \`name\`: \`UIButton(...).style({ name: "login" })\`.
111
+ - Give interactive elements a \`name\`: \`UIButton(...).named("login")\` (or \`el.name = "login"\`;
112
+ it is not a style key).
88
113
  - Name every screen's ROOT node with the screen's id — \`expect.screen\` reads it.
89
114
  - If the project has a \`design/\` folder, keep the design's screen ids and activator \`name\`s
90
115
  in the app code: \`lecodes test\` then checks the built app against the designed navigation
@@ -94,6 +119,16 @@ channel for game state that has no UI.
94
119
  match wins). \`lecodes test\` auto-loads it for design-derived flows; a scenario file opts in
95
120
  with \`"fixtures": "fixtures.json"\` in its envelope.
96
121
  - \`fill\` fills every empty input — use it before tapping a disabled-until-filled submit.
122
+ - Visual state is a \`$class\` in the style (\`$pressed\`, \`$hovered\`, \`$focused\`, your own
123
+ \`$selected\` set with \`el.class.selected = true\` or bound with \`.class({ selected: () => … })\`);
124
+ the interaction states read back as \`button.isPressed\` / \`button.isHovered\`.
125
+ A flow asserts the EFFECT through \`expect.paint\` on a named node (the resolved \`color\` /
126
+ \`backgroundColor\` from the dump), never the class name — that works headless and native alike.
127
+ - Hover is the mouse's: \`{ "hover": "login" }\`, a \`frames\` step, then \`expect.paint\` on the node's
128
+ resolved colour (\`$hovered\`) and \`expect.log\` from \`onMouseEnter\`; hover somewhere else to see
129
+ it end. Works headless and on \`--desktop\`; \`mouseTo: [x, y]\` is the same cursor by coordinates.
130
+ \`$focused\` is the desktop renderer's; a keyboard-driven highlight (your own \`$focus\` class moved
131
+ by arrow keys) is assertable everywhere: \`key\` steps, then \`expect.paint\` on the highlighted node.
97
132
 
98
133
  ## 3D scenes
99
134
 
@@ -108,7 +143,15 @@ channel for game state that has no UI.
108
143
  \`lecodes update\` if it's missing, never commit \`.lecodes/\`). For semantics and examples,
109
144
  fetch the docs — they are served as raw markdown for AI tools:
110
145
 
111
- - https://le.codes/llms.txt — the docs map; fetch it first to find the right page
146
+ - https://le.codes/llms.txt — the docs map; fetch it first to find the right page. It opens with the
147
+ CLI: every \`lecodes\` command with what it is for, the agent page (running without a terminal,
148
+ \`--entry\`, login) and the editors that expose MCP tools — \`lecodes design\` (the screen board)
149
+ and \`lecodes scene\` (the 3D scene editor for \`.scene.ts\` files). \`lecodes <command> --help\`
150
+ is the authority on flags.
151
+ **Building a whole thing — a game, enemies, a crowd of characters, a native plugin? llms.txt opens
152
+ with a COOKBOOK: read the matching recipe BEFORE you design the structure.** It has the file
153
+ layout, a skeleton per part, how to verify, and the traps already paid for (start with
154
+ "Project architecture for a game" for any game bigger than one file).
112
155
  - https://le.codes/en/docs/api/<area>/<page>.md — one reference page as raw markdown
113
156
  (e.g. \`ui/styling.md\`, \`2d/scene2d.md\`, \`3d/scene.md\`, \`runtime/storage.md\`)
114
157
  - https://le.codes/en/docs/guide/<page>.md — guide pages (project anatomy, CLI, tracks)