lecodes-cli 0.20.2 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. package/README.md +56 -57
  2. package/dist/index.js +5918 -5020
  3. package/package.json +7 -5
  4. package/runtime/materials/decal-relief.mat +12 -1
  5. package/runtime/materials/decal.mat +17 -3
  6. package/runtime/materials/lightmap-baked-lite.mat +6 -1
  7. package/runtime/materials/lightmap-baked.mat +6 -1
  8. package/runtime/materials/lightmap.mat +3 -0
  9. package/runtime/scene-harness.json +1 -1
  10. package/runtime/sdk-types.json +1 -1
  11. package/runtime/web/assets/createViewerLite-DHqTFhWB.js +866 -0
  12. package/runtime/web/assets/{index-BMt7AnC5.js → index-Q06c6oHx.js} +1 -1
  13. package/runtime/web/embed.js +1 -1
  14. package/src/api.ts +1 -302
  15. package/src/cli/args.ts +37 -0
  16. package/src/cli/command.ts +113 -0
  17. package/src/cli/errors.ts +13 -0
  18. package/src/cli/help.ts +133 -0
  19. package/src/cli/index.ts +7 -0
  20. package/src/cli/output.ts +102 -0
  21. package/src/cli/run.ts +106 -0
  22. package/src/commands/{appAndroid.ts → app/android.ts} +451 -42
  23. package/src/commands/{appDesktop.ts → app/desktop.ts} +63 -32
  24. package/src/commands/{appDesktopMac.ts → app/desktopMac.ts} +24 -18
  25. package/src/commands/{appIcon.ts → app/icon.ts} +2 -2
  26. package/src/commands/{app.ts → app/index.ts} +397 -289
  27. package/src/commands/{appShared.ts → app/shared.ts} +30 -39
  28. package/src/commands/{appTemplatesAndroid.ts → app/templates/android.ts} +62 -28
  29. package/src/commands/{appTemplatesGradlew.ts → app/templates/gradlew.ts} +1 -1
  30. package/src/commands/{appTemplates.ts → app/templates/ios.ts} +1 -1
  31. package/src/commands/assets.ts +23 -21
  32. package/src/commands/clone.ts +48 -51
  33. package/src/commands/compile.ts +62 -88
  34. package/src/commands/create.ts +46 -47
  35. package/src/commands/design/comments.ts +361 -0
  36. package/src/commands/design/context.ts +101 -0
  37. package/src/commands/design/index.ts +391 -0
  38. package/src/commands/design/snapshot.ts +134 -0
  39. package/src/commands/{designTemplates.ts → design/templates.ts} +4 -3
  40. package/src/commands/desktop.ts +90 -82
  41. package/src/commands/dev.ts +201 -176
  42. package/src/commands/diff.ts +39 -51
  43. package/src/commands/index.ts +60 -0
  44. package/src/commands/{init.ts → init/index.ts} +199 -191
  45. package/src/commands/install.ts +80 -67
  46. package/src/commands/lightmap.ts +203 -171
  47. package/src/commands/link.ts +87 -94
  48. package/src/commands/login.ts +25 -23
  49. package/src/commands/navmesh.ts +168 -138
  50. package/src/commands/pn.ts +201 -244
  51. package/src/commands/pull.ts +49 -56
  52. package/src/commands/push.ts +48 -54
  53. package/src/commands/render.ts +158 -99
  54. package/src/commands/scene.ts +58 -55
  55. package/src/commands/{shaders.ts → shaders/index.ts} +61 -42
  56. package/src/commands/{shadersNew.ts → shaders/new.ts} +2 -2
  57. package/src/commands/shared.ts +79 -0
  58. package/src/commands/status.ts +22 -26
  59. package/src/commands/test.ts +398 -371
  60. package/src/commands/thumbs.ts +185 -175
  61. package/src/commands/update/index.ts +226 -0
  62. package/src/commands/{types.ts → update/types.ts} +31 -22
  63. package/src/compile/collect.ts +5 -5
  64. package/src/compile/collectLocal.ts +3 -3
  65. package/src/compile/designCompile.ts +3 -3
  66. package/src/compile/headlessBundle.ts +133 -129
  67. package/src/compile/projectCompile.ts +2 -2
  68. package/src/compile/sceneCompile.ts +10 -8
  69. package/src/compile/screenEntry.ts +123 -127
  70. package/src/compile/shaders.ts +3 -3
  71. package/src/dev/androidDev.ts +1 -1
  72. package/src/dev/devServer.ts +3 -3
  73. package/src/dev/webRunner.ts +1 -1
  74. package/src/{cmgenTool.ts → hosts/cmgenTool.ts} +1 -1
  75. package/src/{desktopRenderer.ts → hosts/desktopRenderer.ts} +6 -5
  76. package/src/{desktopScript.ts → hosts/desktopScript.ts} +1 -1
  77. package/src/{distRoot.ts → hosts/distRoot.ts} +9 -5
  78. package/src/{matcTool.ts → hosts/matcTool.ts} +1 -1
  79. package/src/{peerInstall.ts → hosts/peerInstall.ts} +2 -2
  80. package/src/{releases.ts → hosts/releases.ts} +1 -1
  81. package/src/index.ts +32 -480
  82. package/src/platform/api.ts +302 -0
  83. package/src/{browserAuth.ts → platform/browserAuth.ts} +1 -1
  84. package/src/{config.ts → platform/config.ts} +1 -1
  85. package/src/{serverDiff.ts → platform/serverDiff.ts} +3 -3
  86. package/src/{projectEnv.ts → project/env.ts} +33 -2
  87. package/src/{localFiles.ts → project/localFiles.ts} +1 -1
  88. package/src/{manifest.ts → project/manifest.ts} +1 -1
  89. package/src/{project.ts → project/materialize.ts} +2 -2
  90. package/src/project/paths.ts +20 -0
  91. package/src/{textDiff.ts → project/textDiff.ts} +1 -1
  92. package/src/{types.ts → project/types.ts} +0 -0
  93. package/runtime/web/assets/createViewerLite-Ct_PZdof.js +0 -852
  94. package/src/commands/design.ts +0 -846
  95. package/src/commands/update.ts +0 -211
  96. package/src/util.ts +0 -146
  97. /package/src/commands/{projectTemplates.ts → init/templates.ts} +0 -0
  98. /package/src/{lecodes-3d-editor.d.ts → declarations/lecodes-3d-editor.d.ts} +0 -0
  99. /package/src/{lecodes-assets.d.ts → declarations/lecodes-assets.d.ts} +0 -0
  100. /package/src/{lecodes-design.d.ts → declarations/lecodes-design.d.ts} +0 -0
  101. /package/src/{lecodes-renderer.d.ts → declarations/lecodes-renderer.d.ts} +0 -0
  102. /package/src/{qrcode-terminal.d.ts → declarations/qrcode-terminal.d.ts} +0 -0
  103. /package/src/{peers.ts → hosts/peers.ts} +0 -0
  104. /package/src/{designMeta.ts → project/designMeta.ts} +0 -0
  105. /package/src/{ignore.ts → project/ignore.ts} +0 -0
package/src/index.ts CHANGED
@@ -1,53 +1,32 @@
1
- import pkg from "../package.json"
2
- import { app } from "./commands/app"
3
- import { clone } from "./commands/clone"
4
- import { compile, previewLegacy } from "./commands/compile"
5
- import { create } from "./commands/create"
6
- import { desktop } from "./commands/desktop"
7
- import { design } from "./commands/design"
8
- import { dev } from "./commands/dev"
9
- import { diff } from "./commands/diff"
10
- import { init } from "./commands/init"
11
- import { link } from "./commands/link"
12
- import { lightmap } from "./commands/lightmap"
13
- import { navmesh } from "./commands/navmesh"
14
- import { login } from "./commands/login"
15
- import { shaders } from "./commands/shaders"
16
- import { render } from "./commands/render"
17
- import { pn } from "./commands/pn"
18
- import { pull } from "./commands/pull"
19
- import { push } from "./commands/push"
20
- import { scene } from "./commands/scene"
21
- import { thumbs } from "./commands/thumbs"
22
- import { assets } from "./commands/assets"
23
- import { status } from "./commands/status"
24
- import { test } from "./commands/test"
25
- import { update } from "./commands/update"
26
- import { existsSync, readFileSync, realpathSync } from "node:fs"
27
- import { createRequire } from "node:module"
1
+ import { existsSync, realpathSync } from "node:fs"
28
2
  import { delimiter, dirname, join } from "node:path"
29
- import { install } from "./commands/install"
30
- import { CliError, c, log, parseArgs, type Args } from "./util"
31
- import { installedDesktopVersions, selectedDesktopVersion } from "./desktopRenderer"
32
- import { installedMatcVersions } from "./matcTool"
33
- import { loadProjectEnv } from "./projectEnv"
34
- import { OPTIONAL_PEERS, peerSource, peerVersion } from "./peers"
3
+ import pkg from "../package.json"
4
+ import { c, log, reportError, runCli } from "./cli"
5
+ import { commandGroups } from "./commands"
6
+ import { installedDesktopVersions, selectedDesktopVersion } from "./hosts/desktopRenderer"
7
+ import { installedMatcVersions } from "./hosts/matcTool"
8
+ import { OPTIONAL_PEERS, peerSource, peerVersion } from "./hosts/peers"
9
+ import { loadProjectEnv } from "./project/env"
10
+
11
+ /*
12
+ * `lecodes` — the entry point. Everything a command is lives in src/commands (one file each, listed
13
+ * in src/commands/index.ts); this file only decides what the standalone binary shows and hands argv
14
+ * to the runner (src/cli/run.ts).
15
+ */
35
16
 
36
17
  /* `install` is a standalone-binary-only command: it installs the shipped bundle onto PATH, so it's only
37
18
  * meaningful when that bundle actually sits next to the executable — the `chisel` bundler + `runtime/`
38
19
  * that scripts/build-bin.ts lays down beside the compiled binary. For an npm install process.execPath
39
- * is `node`, and for `bun run` it's `bun`; neither has those companions, so `install` is unavailable
40
- * there (omitted from help and unregistered → `lecodes install` reports an unknown command). */
20
+ * is `node`, and for `bun run` it's `bun`; neither has those companions, so `install` stays out of the
21
+ * help there (it still runs, for repair / install-over-the-top). */
41
22
  const STANDALONE = (() => {
42
23
  const dir = dirname(process.execPath)
43
24
  const chisel = process.platform === "win32" ? "chisel.exe" : "chisel"
44
25
  return existsSync(join(dir, "runtime")) && existsSync(join(dir, chisel))
45
26
  })()
46
27
 
47
- /* Are we ALREADY the installed copy — i.e. is a `lecodes` on PATH resolving to this very executable?
48
- * If so, re-installing is pointless, so we drop `install` from help (but keep it runnable for repair /
49
- * install-over-the-top). Running the freshly-unpacked archive instead: no PATH entry points back to it
50
- * → this is false → `install` is offered. */
28
+ /* Already the installed copy — a `lecodes` on PATH resolves to this very executable? Then
29
+ * re-installing is pointless and `install` drops out of the help too. */
51
30
  const INSTALLED = STANDALONE && (() => {
52
31
  const exe = process.platform === "win32" ? "lecodes.exe" : "lecodes"
53
32
  const self = (() => { try { return realpathSync(process.execPath) } catch { return process.execPath } })()
@@ -57,455 +36,28 @@ const INSTALLED = STANDALONE && (() => {
57
36
  })
58
37
  })()
59
38
 
60
- const INSTALL_HELP = STANDALONE && !INSTALLED ? `
61
- install Install this standalone build so 'lecodes' is on your PATH
62
- (copies the bundle to a library folder + a symlink/PATH entry).
63
- --modify-path also add it to PATH (edits your shell rc / user PATH)
64
- --system install for all users (Linux/macOS; needs sudo)
65
- --dir <path> override the install/library folder
66
- --bin <path> override the symlink folder (Linux/macOS)` : ""
67
-
68
- const HELP = `${c.bold("lecodes")} — clone, edit and push LeCodes projects from your machine.
69
-
70
- ${c.bold("Usage:")} lecodes <command> [options]
71
-
72
- ${c.bold("Commands:")}
73
- init [dir] Scaffold a new LOCAL project (no login/server): a starter
74
- main.ts, a CLAUDE.md + passing flow test (the headless
75
- render/test loop), IDE types + tsconfig, a .lecodesignore.
76
- Run it with 'lecodes dev' or 'lecodes compile'.
77
- --desktop also write an app.json declaring the desktop
78
- target (window size; 'dev --desktop',
79
- 'desktop run' and headless render/test
80
- pick it up)${INSTALL_HELP}
81
- login Authorize in the browser and store an access token
82
- --token <pat> store a token from the web UI instead
83
- --api <url> API origin (default https://le.codes)
84
- --web <url> app origin for the browser flow
85
- create <name> [dir] Create a new project on the server and scaffold it locally
86
- (types from the server + tsconfig + a starter main.ts)
87
- --team <uuid> create it inside a team
88
- clone <uuid|url> [dir] Download a project's files + types into a new folder
89
- --no-types skip the .d.ts / tsconfig generation
90
- link [dir] Connect an existing local folder (e.g. one made by 'lecodes
91
- init') to the platform — the reverse of clone. Creates a
92
- project on the server, writes .lecodes/manifest.json and
93
- refreshes the types; nothing is uploaded until you push
94
- --name <s> project name (default: folder name)
95
- --team <uuid> create it inside a team
96
- --project <uuid|url> adopt an EXISTING project instead —
97
- prints what your next push would change
98
- on its server tree, and confirms first
99
- --push [-m <msg>] push right after linking (--publish to
100
- deploy it live in the same step)
101
- --force re-link a folder that's already linked
102
- --no-types skip the .d.ts / tsconfig refresh
103
- status Show local changes vs the last sync
104
- diff [path…] Show a unified diff of local vs the current server state
105
- --name-only list changed paths + status only
106
- pull [path…] Overwrite local files with the project's current state
107
- (with path… : pull only those files/folders, keep other edits)
108
- --force discard uncommitted local changes
109
- push Push local changes as one new checkpoint
110
- -m, --message checkpoint message
111
- --publish also deploy the project live (compile) with the push
112
- --force push even if the server moved on (shows + confirms
113
- which server files get overwritten)
114
- -y, --yes skip the --force overwrite confirmation
115
- dev Run the project on a device from this machine, with hot reload:
116
- starts a LAN dev server + prints a QR code; scan it with the
117
- LeCodes app. Saves recompile + hot-swap the running app; device
118
- logs/errors stream back here. No login needed.
119
- --port <n> server port (default 4499)
120
- --host <ip> LAN address to advertise (default: auto-detect)
121
- --entry <path> override the entrypoint
122
- --no-qr don't render the QR code
123
- --android open the dev url in the LeCodes app on the phone
124
- plugged in over adb (adb reverse: no Wi-Fi/LAN
125
- address needed). --serial <id> picks a device,
126
- --apk <file> installs first, --package <name>
127
- (default com.letary.lecodes). Device logs stream
128
- here. --time <s> --png <file>: bounded check —
129
- screenshot after <s> seconds, force-stop, exit
130
- (exit 1 on a crash). Ctrl+C force-stops the app.
131
- --web also open the app in the browser (Web Lite host:
132
- same bundle + hot reload, camera/map plugins
133
- have web twins; the /web/ URL is served always)
134
- --desktop also open the app in the native desktop host
135
- (same hot reload; window from app.json's
136
- desktop block; closing it keeps the server)
137
- --fps (--desktop) draw the host's frame counter
138
- compile Compile the local project to JS (prints to stdout)
139
- (was 'lecodes preview' — the old name still works)
140
- -o, --out <file> write to a file instead of stdout
141
- --entry <path> override the entrypoint
142
- --no-header omit the metadata header
143
- --no-minify keep the bundle readable (no compress/mangle)
144
- --no-sourcemap don't generate/inline the source map
145
- --public-url <url> asset URL prefix (default: the API origin)
146
- render [file] Render the local UI project to a semantic JSON description
147
- (needs the optional 'lecodes-renderer' package; UI projects only)
148
- [file] render ONE screen module instead of the app:
149
- a file that default-exports a UIScreen (or a
150
- (state) => UIScreen) — every 'lecodes design'
151
- screen is written that way. A bare id
152
- ("login") resolves in design/screens/, and a
153
- design screen renders at its meta.json frame
154
- --state <name> ([file]) the state to pass a
155
- (state) => UIScreen screen
156
- -o, --out <file> write JSON to a file instead of stdout
157
- --png [file] rasterize a PNG (default screenshot.png;
158
- needs @napi-rs/canvas)
159
- --map [file] draw the 3D scene's annotated top-down map
160
- (grid + XZ footprints + named markers) as
161
- <file>.png + <file>.json world-space data
162
- (default map.png; --label-all, --max-px N;
163
- also a { "map": "f.png" } script step)
164
- --script <file> drive the app through a step list (tap/type/
165
- scroll/wait/expect/screenshot/json steps;
166
- games: frames/drag/hold/key/keyDown/keyUp,
167
- expect.log, screenshot clip)
168
- --out-dir <dir> where --script step outputs land (default .)
169
- --clip <name|x,y,w,h> (--png) crop to one node's rect or a
170
- logical-px rect — a component/region render
171
- --fixtures <file> canned fetch responses (URL pattern →
172
- { status?, json?, text? }) — make
173
- backend-gated screens reachable headless
174
- --scale <n> PNG device-pixel scale (default 2)
175
- --width/--height viewport size (default 390x844)
176
- --entry <path> override the entrypoint
177
- --settle <ms> wait for promises/timers/fetch before snapshot
178
- --wait-network let data fetches hit the real network
179
- --logs forward the app's console output (to stderr)
180
- --desktop render through the NATIVE desktop host instead:
181
- real Filament 3D + tgfx UI + 2D, composited.
182
- PNG only (--png). Downloads the LeCodes
183
- Desktop renderer on first use into
184
- ~/.lecodes/renderer (Windows/Linux;
185
- LECODES_DESKTOP_EXE overrides)
186
- --frames <n> (--desktop) frames to run before the shot —
187
- headroom for async loads (default 60)
188
- --desktop --script <file> drive the scenario through the native
189
- host: tap/drag/hold take { x, y } coordinates
190
- (no names — the host has no UI tree; type/
191
- scroll/clip unavailable), expect.log is
192
- checked, text/node/screen/value are skipped;
193
- per-step screenshots + final --png
194
- --view <name> (--desktop) inspection camera auto-fitted to the
195
- whole 3D scene: iso | top | front | side | back
196
- [--fov N]; the app's camera is untouched
197
- --camera x,y,z --look-at x,y,z [--fov N]
198
- (--desktop) inspection camera at a fixed eye /
199
- look-at point (world units, Y up). In a
200
- --script run use { "view" } / { "camera" } steps
201
- --fixed-dt <ms> (--desktop) virtual app clock, ms per frame —
202
- deterministic game time (scripted runs
203
- default to 16; plain renders to wall clock)
204
- test [name…] Run headless flow tests: tests/*.flow.json scenarios, plus flows
205
- derived from the design graph (design/meta.json activator edges
206
- — checks the built app against the designed navigation; needs
207
- named elements + screen roots). Failing steps dump the frame
208
- (JSON + PNG) under tests/.artifacts/<flow>/.
209
- Design flows read tests/fixtures.json automatically (canned
210
- fetch responses) so backend-gated transitions run headless;
211
- file scenarios declare their own via the "fixtures" envelope.
212
- --flows run ONLY the design-derived flows
213
- --no-flows run ONLY the tests/*.flow.json files
214
- --fixtures <file> fixture pack for the design flows (overrides
215
- the tests/fixtures.json default)
216
- --json machine-readable report on stdout
217
- --dir <folder> tests folder (default tests/)
218
- --design-dir <dir> design folder (default design/)
219
- --artifacts <dir> failure-artifact folder override
220
- --desktop run the file scenarios through the NATIVE host
221
- (coordinates only; expect.log checked,
222
- text/node/screen/value skipped; design flows
223
- stay headless). --frames / --fixed-dt as in
224
- render --desktop
225
- (+ the render timing flags: --width/--height, --settle,
226
- --device, --safe-area, --wait-network, --logs, --entry)
227
- update Move everything a project depends on to its newest published
228
- state, in dependency order: the desktop host ('desktop
229
- update'), matc ('shaders update'), the project's shader
230
- compiles, the IDE types (.lecodes/types/ + tsconfig.json,
231
- from the server — the former 'lecodes types'), the
232
- ios/android shell pins ('app update'), then the GLOBAL npm
233
- installs: every installed optional peer (lecodes-renderer,
234
- lecodes-design, lecodes-3d-editor, lecodes-assets) and this
235
- CLI that is behind npm's latest gets 'npm install -g'.
236
- Pinned tools are skipped, not overridden: LECODES_DESKTOP_EXE
237
- / LECODES_MATC mean a local build. Keeps going when a step
238
- fails; the summary says which.
239
- --local types from the SDK bundled in this CLI
240
- --no-shells leave ios/android pins alone (no re-sync)
241
- --check npm step: report what's behind, install nothing
242
- --no-cli skip the npm step (offline)
243
- design [serve] LeCodes Design — a live canvas of screens for AI-driven prototyping
244
- (needs the optional 'lecodes-design' package; works inside a
245
- cloned project or standalone in any folder)
246
- --port <n> dev-server port (default 4477)
247
- --dir <folder> design folder (default design/)
248
- --no-open don't open the browser
249
- design init Scaffold the design folder (screens/, shared/, meta.json, CLAUDE.md)
250
- --desktop desktop-frame board (meta.json defaultSize
251
- [1280, 800] instead of the phone frame)
252
- design snapshot [screen…] Render design screens headless (needs 'lecodes-renderer')
253
- one screen → JSON to stdout; else files in design/.snapshots/
254
- --png also rasterize PNGs (needs @napi-rs/canvas)
255
- --out-dir <dir> output folder override
256
- --logs forward the screens' console output (to stderr)
257
- design comments Review comments pinned to the board, grouped by screen (open first)
258
- --all include resolved threads
259
- add "<text>" [--screen <id>] [--state <s>] [--element <name>]
260
- reply <number> "<text>" · resolve <number> [--reply "<text>"] [--reopen]
261
- (--ai signs a write as Claude; sends with the next "lecodes push")
262
- desktop [run|build|update] The desktop target — the host 'lecodes render --desktop' uses too:
263
- no args shows the version status (selected / downloaded /
264
- newest published)
265
- run compile the current project and open it in a real
266
- desktop window (size/min-size/title default from
267
- app.json's desktop block; --width/--height/--title
268
- override; --fullscreen/--windowed override the
269
- app.json fullscreen; --scale <0.5..4|fit> overrides
270
- the display scale — "fit" sizes a 1080p-designed app
271
- to the monitor with the density raised to match;
272
- --entry overrides the entrypoint; --fps draws the
273
- host's frame counter in the corner — composited fps,
274
- plus the 3D fps when they diverge; asset() resources
275
- load from the working tree — --remote-assets or
276
- --public-url <url> uses the server's copies instead);
277
- --server [--port N] [--max-clients N] launches the
278
- host as a multiplayer server, --connect ip:port as a
279
- client — both only set Net.launch (the app decides)
280
- build stage a portable app folder (default dist/<name>/):
281
- the cached host renamed to <Name>.exe + app.js +
282
- assets/ (the project's files) + runtime/ (the engine's)
283
- + app.json (the host reads title/size/fullscreen from
284
- it) + app.ico from app.json "icon". Duplicate asset
285
- basenames are a build error (they silently alias in
286
- the bundle). Also spelled 'lecodes app build desktop'.
287
- On macOS: a signed .app instead (the downloaded LeCodes
288
- Desktop.app + the project inside it); app.json
289
- desktop.macos = bundleId / buildNumber / category /
290
- signing.identity / notarize.profile (a notarytool
291
- keychain profile — notarized + stapled, so it opens
292
- on any Mac without "Open Anyway").
293
- -o <dir> output folder (default dist/<name>)
294
- --slug <name> folder/zip name override (Latin-only)
295
- --renderer <gl|vulkan> host + shader variant
296
- --probe headless-render the staged folder to PNG
297
- (macOS: a brief windowed run + snapshot)
298
- --zip also pack the result for sending
299
- --dmg macOS: also build a drag-to-Applications
300
- disk image (signed + notarized too)
301
- --sign <identity|adhoc> macOS codesign identity
302
- --notarize <profile> / --no-notarize macOS
303
- --zip <out>.zip, UTF-8/forward-slash entries
304
- update move to the newest published release
305
- (cached in ~/.lecodes/renderer)
306
- lightmap bake Bake static shadows + ambient occlusion into a lightmap atlas
307
- (assets/lightmap/lightmap.{png,bake}) by running the app in
308
- the desktop host: the statics are what the app registers
309
- with Lightmap.add, the bake fires at its Lightmap.load call.
310
- Props need UVs first: lecodes assets doctor … --lightmap-uv
311
- --out <dir> where the atlas lands (assets/lightmap)
312
- --size <px> atlas edge (4096) --texel <m> density (0.02)
313
- --sun-rays/--ao-rays <n> quality (32 / 64)
314
- --ao-distance <m> AO reach (2) --sun-angle <deg> (0.5)
315
- --frames <n> load headroom (900) --logs host output
316
- navmesh bake Build the level's navigation mesh (assets/nav/<scene>.navmesh)
317
- for NavAgent / NavMesh: the desktop host dumps every STATIC
318
- body's collision triangles at the scene's env.navmesh (or
319
- NavMesh.load) point, Recast builds the mesh here.
320
- --out <dir> where it lands (assets/nav)
321
- --name <n> file name (the scene file with env.navmesh)
322
- --radius/--height/--climb/--slope agent size (scene's)
323
- --cell <m> (0.25) --cell-height <m> (0.1) --tile <cells> (48)
324
- --obj also write <n>.navmesh.obj to inspect
325
- --frames <n> load headroom (900) --logs host output
326
- shaders [compile|update|new] The project's custom shaders (.mat) and their local compile:
327
- no args lists the shaders + their compile state; compile
328
- compiles them all into .lecodes/shaders (the same pass
329
- 'lecodes dev' runs on the fly — via Filament's matc,
330
- downloaded on first use to ~/.lecodes/matc); update
331
- downloads the newest matc (run after an engine upgrade);
332
- new <name> [--ref lit] [--dir d] [--force] writes a fresh
333
- .mat + .material.ts: the starter, or a copy of the engine's
334
- own lit / unlit / particles / … source to start from
335
- scene [path] LeCodes Scene Editor — visually edit a .scene.ts (tree, inspector,
336
- gizmo) on local files (needs the optional 'lecodes-3d-editor' package;
337
- works inside a cloned project or standalone in any folder)
338
- --port <n> editor-server port (default 4488)
339
- --no-open don't open the browser
340
- --no-mcp don't register the MCP server in .mcp.json
341
- thumbs [path…] Asset thumbnails for the scene editor, WITHOUT a browser: decodes
342
- the project's images (KTX2 too, via KTX-Software's ktx tool) into the
343
- editor's preview cache. Incremental — the cached name carries the
344
- asset's content hash, so unchanged files are skipped. Models,
345
- materials and prefabs are rendered by the editor, not here
346
- --force redo even the up-to-date ones
347
- --size <px> longest edge (default 256)
348
- assets convert <model.fbx|.glb> [--clips a.fbx b.fbx …] [-o out.glb] [--drop-model-clips]
349
- [--fix] [--ktx2|--ktx2-hq]
350
- FBX → LeCodes-ready GLB: meters/Y-up, textures embedded, clips from
351
- other FBX files merged by bone name (Mixamo: one FBX per animation);
352
- --fix/--ktx2 run the doctor passes (needs 'lecodes-assets')
353
- Unity .mat files next to the FBX are read for the real textures
354
- (metallic/roughness/ORM/emission packed the glTF way); --no-unity
355
- keeps the FBX references; --unity-index <unity-index.txt> resolves GUIDs
356
- --list-nodes / --keep <node> … / --split [dir]: carve one variant out of a
357
- multi-variant prop file, re-origined on the ground (or one GLB per node)
358
- --merge-skins b.fbx …: modular character pieces onto ONE skeleton
359
- assets unpack <pkg.unitypackage> [-o dir] [--only re …] [--list]
360
- Stream a .unitypackage to a folder: writes unity-index.txt (GUID → path,
361
- what convert resolves textures with) and the matching assets + .meta
362
- assets scene <map.unity> [-o level.json] [--filter re] [--group g] [--region x0,z0,x1,z1]
363
- Unity scene → placed prefabs / mesh objects with world TRS in LeCodes'
364
- frame + a prefab table (FBX + --keep node per prefab)
365
- assets probe <file.fbx|.glb> [--filmstrip [out.png]]
366
- Skeleton, clips (+ root motion / first-frame-vs-rest per clip), meshes,
367
- materials, textures, bind sanity, duplicate names; --filmstrip renders
368
- a CPU-skinned PNG strip per clip (no GPU)
369
- assets sky <env.exr|.hdr> [-o dir] [--size N] [--ibl-size N] [--ibl-only]
370
- Equirectangular HDR → the two cubemaps a scene needs: sky.ktx (sharp,
371
- for 'skybox: { texture }') and ibl.ktx (prefiltered + SH, lights the
372
- scene) — they are NOT interchangeable. Reports where the sun is in
373
- the environment so the scene's light can be aimed to match
374
- assets terrain-pack <layer> … [-o dir] [--normals n0 n1 …] [--names a b …] [--size N] [--png]
375
- A terrain's splat layers (≤ 4) → <name>.ktx2 per layer + the packed
376
- normal maps normals01/normals23.ktx2 (what Terrain.create's
377
- layers/normals read). A layer = an albedo file or a FOLDER holding
378
- *Albedo* + *Normal* (Unity packs). Prints the Terrain.create fragment
379
- assets doctor <in.glb|dir> [--fix] [--ktx2] [--dump]
380
- Analyze / fix a GLB against engine limits (bone cap 256, texture
381
- formats/size, KTX2 via toktx)
382
- app init <ios|android|desktop> Generate a native shell in <project>/ios/ or <project>/android/
383
- (offline-first: runs the bundled app.js; never pushed to LeCodes).
384
- desktop: no shell needed — writes app.json's desktop block
385
- (window size; --fullscreen for kiosks)
386
- --name <s> display name (default: app.json / folder name)
387
- --bundle-id <id> iOS bundle identifier
388
- --app-id <id> Android applicationId (default com.example.<slug>)
389
- --team <id> Apple development team id
390
- --variant <v> iOS SDK variant: auto (default — sync picks the
391
- smallest of core/2d/3d/full covering what
392
- the bundle uses), or one of those to pin
393
- --sdk <v> native SDK release (default: the newest one,
394
- pinned in app.json ios.sdk / android.sdk);
395
- 'latest' floats it — every sync re-resolves
396
- app sync [ios|android] Make every existing shell match the project: compile the bundle
397
- + embed its assets (ios/App/Resources/ resp. the android
398
- assets/; offline by default, also for server projects;
399
- app.json "resources": {"remote": [globs]} keeps heavy assets
400
- streaming), pick the iOS SDK variant from the bundle's engine
401
- use, vendor app.json plugins (pinned in plugins.lock.json),
402
- regenerate the CLI-owned shell files. app.json "update": true
403
- (or {"url": …}) enables native OTA JS updates at launch (iOS).
404
- app.json "icon" (square ≥1024px image) renders the app icons
405
- every sync ("iconBackground": adaptive/flatten backdrop)
406
- app update [ios|android] Move the shell's pins forward — app.json ios.sdk / android.sdk
407
- to the newest published SDK release, plugins.lock.json to each
408
- plugin's newest tag — then re-sync
409
- --sdk <v|latest> pin that release instead of the newest
410
- --check report available updates, write nothing
411
- --no-plugins only move the SDK pin
412
- --no-sync update the pins without re-syncing
413
- app plugin <list|add|remove> Manage native plugins in app.json (then run 'lecodes app sync')
414
- add takes a known id (camera, qr-scanner) or github:owner/repo[@tag]
415
- app open [ios|android] Open the native shell in its IDE (Xcode / Android Studio)
416
- app doctor Preflight checks for building/uploading the shells
417
- app build ios Sync, archive and export the .ipa (automatic signing)
418
- --configuration <c> build configuration (default Release)
419
- --method <m> export method (default app-store-connect)
420
- --no-export stop after the archive
421
- --no-sync skip the lecodes app sync step
422
- app build android Sync, then gradlew assembleRelease + bundleRelease (signing via
423
- android/keystore.properties or LECODES_ANDROID_* env vars)
424
- --configuration <c> Release (default) or Debug
425
- --no-aab skip the .aab (APK only)
426
- --no-sync skip the lecodes app sync step
427
- app build desktop Same as 'lecodes desktop build' (kept for symmetry with the
428
- ios/android shells) — see the desktop group above.
429
- app upload ios Upload the archive to App Store Connect (then bumps buildNumber)
430
- --key-path <p8> --key-id <id> --issuer-id <id>
431
- ASC API key; stored in ~/.lecodes/asc.json
432
- pn send --title <s> Push notifications for this project ('lecodes pn' for the group).
433
- Sends with your login — no push key needed to test.
434
- --user <ids> target Push.register({ user }) ids (comma-sep)
435
- --to <pa_…> target addresses (comma-separated)
436
- --body/--url/--badge/--data payload fields
437
- pn devices Which devices registered (the 'why didn't it arrive?' command)
438
- --user <id> only that user's devices
439
- pn keys [new|revoke] Push keys for YOUR server: list, mint (owner only, shown once
440
- — 'new --env' banks it in .env), revoke <id>
441
-
442
- Config lives in ~/.lecodes/config.json (override with LECODES_API / LECODES_TOKEN).
443
- A .env next to app.json is read at startup for LECODES_* / CREATOR_* keys (the shell's own
444
- environment wins; ./ paths resolve against the project; the file is never pushed) — e.g.
445
- LECODES_DESKTOP_EXE, LECODES_MATC, LECODES_NOTARY_PROFILE, LECODES_MACOS_SIGN_ID, LECODES_TOKEN.`
446
-
447
- // `preview` is the legacy name of `compile` — kept registered (and out of the help) for old scripts.
448
- const commands: Record<string, (args: Args) => Promise<void>> = { init, login, create, clone, link, status, diff, pull, push, compile, preview: previewLegacy, render, test, design, scene, thumbs, dev, app, pn, desktop, shaders, assets, lightmap, navmesh, update }
449
- if (STANDALONE) commands.install = install
450
-
451
- /** Package version, inlined at build time (the standalone binary has no package.json on disk). */
452
- const readVersion = (): string => pkg.version ?? "unknown"
453
-
454
- /** `--version`: the CLI version, every separately-installed optional peer (or "not installed"),
455
- * and the desktop render host `render --desktop` would use (no network — cache state only). */
456
- const printVersion = () => {
457
- const rows: [label: string, value: string][] = [["lecodes-cli", readVersion()]]
39
+ /** `--version`: the CLI, every separately-installed optional peer (or "not installed"), and the
40
+ * desktop host / matc `render --desktop` and the shader compile would use (cache state, no network). */
41
+ const printVersion = (): void => {
42
+ const rows: [string, string][] = [["lecodes-cli", pkg.version ?? "unknown"]]
458
43
  for (const [name, probe] of OPTIONAL_PEERS) {
459
44
  const version = peerVersion(name, probe)
460
45
  const where = peerSource(name, probe)
461
- rows.push([name, version
462
- ? (where === "prefix" ? `${version} ${c.dim("(~/.lecodes/peers)")}` : version)
463
- : c.dim("not installed — installed on first use")])
46
+ rows.push([name, version ? (where === "prefix" ? `${version} ${c.dim("(~/.lecodes/peers)")}` : version) : c.dim("not installed — installed on first use")])
464
47
  }
465
48
  const desktopVersion = selectedDesktopVersion()
466
- rows.push(["lecodes-desktop", installedDesktopVersions().includes(desktopVersion)
467
- ? desktopVersion
468
- : `${desktopVersion} ${c.dim("(not downloaded — fetched on first `render --desktop`)")}`])
49
+ rows.push(["lecodes-desktop", installedDesktopVersions().includes(desktopVersion) ? desktopVersion : `${desktopVersion} ${c.dim("(not downloaded — fetched on first `render --desktop`)")}`])
469
50
  const matcVersion = installedMatcVersions()[0]
470
51
  rows.push(["matc", matcVersion ?? c.dim("not downloaded — fetched on the first local shader compile")])
471
52
  const width = Math.max(...rows.map(([label]) => label.length))
472
53
  for (const [label, value] of rows) log(`${label.padEnd(width)} ${value}`)
473
54
  }
474
55
 
475
- const main = async () => {
476
- const argv = process.argv.slice(2)
477
- const command = argv[0]
478
- // The project's .env (LECODES_* / CREATOR_* next to app.json) — before any command reads the
479
- // environment; the real environment keeps precedence. See projectEnv.ts.
480
- loadProjectEnv()
481
- // `--yes` / `-y` anywhere on the line also pre-approves first-use installs of optional peers
482
- // (peerInstall.ts) — the same knob LECODES_YES=1 gives CI.
483
- if (argv.includes("--yes") || argv.includes("-y")) process.env.LECODES_YES = "1"
484
-
485
- if (!command || command === "help" || command === "--help" || command === "-h") {
486
- log(HELP)
487
- return
488
- }
489
-
490
- if (command === "--version" || command === "-v" || command === "version") {
491
- printVersion()
492
- return
493
- }
494
-
495
- const run = commands[command]
496
- if (!run) {
497
- log(c.red(`Unknown command: ${command}`))
498
- log(HELP)
499
- process.exitCode = 1
500
- return
501
- }
502
-
503
- // `assets` owns its own flag grammar (multi-value --clips …) — hand it the raw argv too.
504
- await run(Object.assign(parseArgs(argv.slice(1)), { _raw: argv.slice(1) }))
505
- }
506
-
507
- main().catch((e: unknown) => {
508
- if (e instanceof CliError) log(c.red("✗ ") + e.message)
509
- else log(c.red("✗ Unexpected error: ") + (e instanceof Error ? e.message : String(e)))
510
- process.exitCode = 1
511
- })
56
+ runCli({
57
+ version: pkg.version ?? "unknown",
58
+ groups: commandGroups({ install: STANDALONE && !INSTALLED }),
59
+ footer: "Config lives in ~/.lecodes/config.json (override with LECODES_API / LECODES_TOKEN). A .env next to app.json is read at startup for LECODES_* / CREATOR_* keys (the shell's own environment wins; ./ paths resolve against the project; the file is never pushed) — e.g. LECODES_DESKTOP_EXE, LECODES_MATC, LECODES_TOKEN.",
60
+ printVersion,
61
+ // The project's .env before any command reads the environment; the real environment keeps precedence.
62
+ before: () => { loadProjectEnv() },
63
+ }, process.argv.slice(2)).catch(reportError)