lecodes-cli 0.19.0 → 0.20.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 (270) hide show
  1. package/README.md +1 -0
  2. package/dist/index.js +1069 -396
  3. package/package.json +14 -13
  4. package/runtime/scene-harness.json +1 -1
  5. package/runtime/sdk-types.json +1 -1
  6. package/runtime/web/assets/Roboto-Variable-DHm9jpd-.woff2 +0 -0
  7. package/runtime/web/assets/__vite-browser-external-BIHI7g3E.js +1 -0
  8. package/runtime/web/assets/basis-C64VHDVD.js +1 -0
  9. package/runtime/web/assets/basis_transcoder-VXdx5NbI.wasm +0 -0
  10. package/runtime/web/assets/createViewerLite-BvHobVhp.js +852 -0
  11. package/runtime/web/assets/draco-BiISTFcR.js +118 -0
  12. package/runtime/web/assets/draco_decoder-DsQ12WqX.wasm +0 -0
  13. package/runtime/web/assets/index-CfNCCBjN.js +2 -0
  14. package/runtime/web/assets/mapViewImpl-B2JcES8l.js +810 -0
  15. package/runtime/web/assets/maplibre-gl-worker-CJfwIrte.js +8 -0
  16. package/runtime/web/assets/neutral_ibl128-D-lzdkLN.ktx +0 -0
  17. package/runtime/web/assets/worker-Caf-yYEI.js +2 -0
  18. package/runtime/web/embed.html +32 -0
  19. package/runtime/web/embed.js +171 -0
  20. package/src/api.ts +302 -0
  21. package/src/browserAuth.ts +87 -0
  22. package/src/cmgenTool.ts +104 -0
  23. package/src/commands/app.ts +896 -0
  24. package/src/commands/appAndroid.ts +603 -0
  25. package/src/commands/appDesktop.ts +339 -0
  26. package/src/commands/appDesktopMac.ts +470 -0
  27. package/src/commands/appIcon.ts +187 -0
  28. package/src/commands/appShared.ts +542 -0
  29. package/src/commands/appTemplates.ts +907 -0
  30. package/src/commands/appTemplatesAndroid.ts +599 -0
  31. package/src/commands/appTemplatesGradlew.ts +9 -0
  32. package/src/commands/assets.ts +28 -0
  33. package/src/commands/clone.ts +60 -0
  34. package/src/commands/compile.ts +253 -0
  35. package/src/commands/create.ts +59 -0
  36. package/src/commands/design.ts +863 -0
  37. package/src/commands/designTemplates.ts +11 -0
  38. package/src/commands/desktop.ts +127 -0
  39. package/src/commands/dev.ts +236 -0
  40. package/src/commands/diff.ts +59 -0
  41. package/src/commands/init.ts +191 -0
  42. package/src/commands/install.ts +198 -0
  43. package/src/commands/lightmap.ts +290 -0
  44. package/src/commands/link.ts +147 -0
  45. package/src/commands/login.ts +24 -0
  46. package/src/commands/navmesh.ts +225 -0
  47. package/src/commands/pn.ts +305 -0
  48. package/src/commands/projectTemplates.ts +130 -0
  49. package/src/commands/pull.ts +109 -0
  50. package/src/commands/push.ts +173 -0
  51. package/src/commands/render.ts +416 -0
  52. package/src/commands/scene.ts +148 -0
  53. package/src/commands/shaders.ts +157 -0
  54. package/src/commands/status.ts +29 -0
  55. package/src/commands/test.ts +371 -0
  56. package/src/commands/thumbs.ts +178 -0
  57. package/src/commands/types.ts +87 -0
  58. package/src/commands/update.ts +190 -0
  59. package/src/compile/assetIcons.ts +214 -0
  60. package/src/compile/collect.ts +46 -0
  61. package/src/compile/collectLocal.ts +37 -0
  62. package/src/compile/designCompile.ts +104 -0
  63. package/src/compile/fonts.ts +156 -0
  64. package/src/compile/headlessBundle.ts +129 -0
  65. package/src/compile/nativeStack.ts +21 -0
  66. package/src/compile/projectCompile.ts +109 -0
  67. package/src/compile/sceneCompile.ts +113 -0
  68. package/src/compile/screenEntry.ts +127 -0
  69. package/src/compile/shaders.ts +245 -0
  70. package/src/config.ts +42 -0
  71. package/src/designMeta.ts +35 -0
  72. package/src/desktopRenderer.ts +545 -0
  73. package/src/desktopScript.ts +276 -0
  74. package/src/dev/clientTemplates.ts +160 -0
  75. package/src/dev/devServer.ts +333 -0
  76. package/src/dev/webRunner.ts +22 -0
  77. package/src/dev/wsServer.ts +144 -0
  78. package/src/distRoot.ts +20 -0
  79. package/src/ignore.ts +163 -0
  80. package/src/index.ts +494 -0
  81. package/src/lecodes-3d-editor.d.ts +41 -0
  82. package/src/lecodes-assets.d.ts +7 -0
  83. package/src/lecodes-design.d.ts +191 -0
  84. package/src/lecodes-renderer.d.ts +131 -0
  85. package/src/localFiles.ts +144 -0
  86. package/src/manifest.ts +46 -0
  87. package/src/matcTool.ts +137 -0
  88. package/src/peers.ts +40 -0
  89. package/src/project.ts +61 -0
  90. package/src/projectEnv.ts +94 -0
  91. package/src/qrcode-terminal.d.ts +9 -0
  92. package/src/releases.ts +125 -0
  93. package/src/serverDiff.ts +78 -0
  94. package/src/textDiff.ts +103 -0
  95. package/src/types.ts +0 -0
  96. package/src/util.ts +146 -0
  97. package/runtime/sdk/animate/animate.ts +0 -238
  98. package/runtime/sdk/animate/bezier.ts +0 -138
  99. package/runtime/sdk/animate/easings.ts +0 -126
  100. package/runtime/sdk/canvas/Canvas.ts +0 -305
  101. package/runtime/sdk/compile/aspectMacro.ts +0 -86
  102. package/runtime/sdk/compile/assetIconMacro.ts +0 -384
  103. package/runtime/sdk/compile/assetMacro.ts +0 -146
  104. package/runtime/sdk/compile/assetName.ts +0 -50
  105. package/runtime/sdk/compile/bundler.ts +0 -287
  106. package/runtime/sdk/compile/compileProject.ts +0 -129
  107. package/runtime/sdk/compile/detectEntry.ts +0 -128
  108. package/runtime/sdk/compile/fontMacro.ts +0 -459
  109. package/runtime/sdk/compile/fontRegistry.ts +0 -78
  110. package/runtime/sdk/compile/header.ts +0 -67
  111. package/runtime/sdk/compile/index.ts +0 -100
  112. package/runtime/sdk/compile/libraryImports.ts +0 -52
  113. package/runtime/sdk/compile/liteMaterial.ts +0 -247
  114. package/runtime/sdk/compile/sceneEditor.ts +0 -88
  115. package/runtime/sdk/compile/serverSplit.ts +0 -233
  116. package/runtime/sdk/compile/serverTypes.ts +0 -227
  117. package/runtime/sdk/compile/sfnt.ts +0 -98
  118. package/runtime/sdk/compile/shaderSchema.ts +0 -202
  119. package/runtime/sdk/compile/shaderTargets.ts +0 -81
  120. package/runtime/sdk/compile/sourcemap.ts +0 -25
  121. package/runtime/sdk/core/Aspect.ts +0 -568
  122. package/runtime/sdk/core/InspectorUI.ts +0 -212
  123. package/runtime/sdk/core/StateMachine.ts +0 -308
  124. package/runtime/sdk/core/color.ts +0 -66
  125. package/runtime/sdk/core/compWrite.ts +0 -42
  126. package/runtime/sdk/core/events.ts +0 -38
  127. package/runtime/sdk/core/fields.ts +0 -120
  128. package/runtime/sdk/core/registry.ts +0 -23
  129. package/runtime/sdk/core/signals.ts +0 -277
  130. package/runtime/sdk/core/time.ts +0 -84
  131. package/runtime/sdk/g2/Camera2D.ts +0 -40
  132. package/runtime/sdk/g2/CharacterController2D.ts +0 -276
  133. package/runtime/sdk/g2/Node2D.ts +0 -267
  134. package/runtime/sdk/g2/OneWay2D.ts +0 -66
  135. package/runtime/sdk/g2/Physics2D.ts +0 -346
  136. package/runtime/sdk/g2/Scene2D.ts +0 -209
  137. package/runtime/sdk/g2/Shape2D.ts +0 -259
  138. package/runtime/sdk/g2/Sprite.ts +0 -89
  139. package/runtime/sdk/g2/SpriteAnimation.ts +0 -171
  140. package/runtime/sdk/g2/SpriteSheet.ts +0 -166
  141. package/runtime/sdk/g2/Texture2D.ts +0 -47
  142. package/runtime/sdk/g2/Tilemap.ts +0 -41
  143. package/runtime/sdk/g2/Tileset.ts +0 -71
  144. package/runtime/sdk/g2/Trigger2D.ts +0 -77
  145. package/runtime/sdk/g2/autotile.ts +0 -433
  146. package/runtime/sdk/g2/cells.ts +0 -91
  147. package/runtime/sdk/g2/defineScene2d.ts +0 -381
  148. package/runtime/sdk/g2/groups2d.ts +0 -106
  149. package/runtime/sdk/g2/loop.ts +0 -50
  150. package/runtime/sdk/g2/scenarios2d.ts +0 -69
  151. package/runtime/sdk/g2/touch.ts +0 -83
  152. package/runtime/sdk/gl/Camera.ts +0 -160
  153. package/runtime/sdk/gl/CameraPlace.ts +0 -52
  154. package/runtime/sdk/gl/CharacterController.ts +0 -238
  155. package/runtime/sdk/gl/Gearbox.ts +0 -212
  156. package/runtime/sdk/gl/Geometry.ts +0 -279
  157. package/runtime/sdk/gl/IK.ts +0 -193
  158. package/runtime/sdk/gl/InstancedMesh.ts +0 -132
  159. package/runtime/sdk/gl/Light.ts +0 -107
  160. package/runtime/sdk/gl/Lightmap.ts +0 -256
  161. package/runtime/sdk/gl/Material.ts +0 -260
  162. package/runtime/sdk/gl/Mesh.ts +0 -83
  163. package/runtime/sdk/gl/Model.ts +0 -124
  164. package/runtime/sdk/gl/NavAgent.ts +0 -337
  165. package/runtime/sdk/gl/NavMesh.ts +0 -397
  166. package/runtime/sdk/gl/Node.ts +0 -350
  167. package/runtime/sdk/gl/Noise.ts +0 -30
  168. package/runtime/sdk/gl/Particles.ts +0 -676
  169. package/runtime/sdk/gl/Physics.ts +0 -222
  170. package/runtime/sdk/gl/Plane.ts +0 -53
  171. package/runtime/sdk/gl/Ragdoll.ts +0 -270
  172. package/runtime/sdk/gl/Ray.ts +0 -16
  173. package/runtime/sdk/gl/Scene.ts +0 -486
  174. package/runtime/sdk/gl/Shape.ts +0 -409
  175. package/runtime/sdk/gl/Terrain.ts +0 -1092
  176. package/runtime/sdk/gl/Texture.ts +0 -63
  177. package/runtime/sdk/gl/Trigger.ts +0 -45
  178. package/runtime/sdk/gl/Vehicle.ts +0 -473
  179. package/runtime/sdk/gl/Wheel.ts +0 -240
  180. package/runtime/sdk/gl/animation/AnimationClip.ts +0 -204
  181. package/runtime/sdk/gl/animation/Animator.ts +0 -120
  182. package/runtime/sdk/gl/animation/Layer.ts +0 -31
  183. package/runtime/sdk/gl/animation/Loop.ts +0 -30
  184. package/runtime/sdk/gl/animation/Playback.ts +0 -44
  185. package/runtime/sdk/gl/animation/core.ts +0 -336
  186. package/runtime/sdk/gl/controls.ts +0 -95
  187. package/runtime/sdk/gl/physicsEvents.ts +0 -20
  188. package/runtime/sdk/gl/scenarios.ts +0 -291
  189. package/runtime/sdk/gl/state.ts +0 -6
  190. package/runtime/sdk/gl/terrainMesh.ts +0 -219
  191. package/runtime/sdk/gl/touch.ts +0 -68
  192. package/runtime/sdk/inject.ts +0 -197
  193. package/runtime/sdk/math/Mathf.ts +0 -118
  194. package/runtime/sdk/math/mat4.ts +0 -278
  195. package/runtime/sdk/math/quat.ts +0 -232
  196. package/runtime/sdk/math/vec.ts +0 -255
  197. package/runtime/sdk/net/codec.ts +0 -119
  198. package/runtime/sdk/net/core.ts +0 -384
  199. package/runtime/sdk/net/index.ts +0 -181
  200. package/runtime/sdk/net/replication.ts +0 -622
  201. package/runtime/sdk/plugins/camera.ts +0 -81
  202. package/runtime/sdk/plugins/geolocation.ts +0 -123
  203. package/runtime/sdk/plugins/oauth.ts +0 -61
  204. package/runtime/sdk/plugins/permission.ts +0 -7
  205. package/runtime/sdk/plugins/push.ts +0 -132
  206. package/runtime/sdk/plugins/qr.ts +0 -73
  207. package/runtime/sdk/plugins/service.ts +0 -47
  208. package/runtime/sdk/runtime/app.ts +0 -101
  209. package/runtime/sdk/runtime/appEvents.ts +0 -54
  210. package/runtime/sdk/runtime/channel.ts +0 -61
  211. package/runtime/sdk/runtime/clipboard.ts +0 -20
  212. package/runtime/sdk/runtime/datetime.ts +0 -329
  213. package/runtime/sdk/runtime/device.ts +0 -293
  214. package/runtime/sdk/runtime/fetch.ts +0 -77
  215. package/runtime/sdk/runtime/files.ts +0 -23
  216. package/runtime/sdk/runtime/input.ts +0 -175
  217. package/runtime/sdk/runtime/media.ts +0 -111
  218. package/runtime/sdk/runtime/misc.ts +0 -16
  219. package/runtime/sdk/runtime/net.ts +0 -36
  220. package/runtime/sdk/runtime/rpc.ts +0 -218
  221. package/runtime/sdk/runtime/service.ts +0 -83
  222. package/runtime/sdk/runtime/share.ts +0 -9
  223. package/runtime/sdk/runtime/storage.ts +0 -13
  224. package/runtime/sdk/runtime/touch.ts +0 -76
  225. package/runtime/sdk/scene/defineScene.ts +0 -1353
  226. package/runtime/sdk/scene/editorPlugins.ts +0 -110
  227. package/runtime/sdk/scene/gizmos.ts +0 -148
  228. package/runtime/sdk/scene/grammar.ts +0 -120
  229. package/runtime/sdk/scene/material.ts +0 -188
  230. package/runtime/sdk/server/auth/appConfig.ts +0 -12
  231. package/runtime/sdk/server/auth/global.ts +0 -80
  232. package/runtime/sdk/server/auth/host.ts +0 -318
  233. package/runtime/sdk/server/auth/models.ts +0 -83
  234. package/runtime/sdk/server/auth/types.ts +0 -50
  235. package/runtime/sdk/server/channel.ts +0 -56
  236. package/runtime/sdk/server/context.ts +0 -36
  237. package/runtime/sdk/server/db/defineDb.ts +0 -237
  238. package/runtime/sdk/server/db/fields.ts +0 -132
  239. package/runtime/sdk/server/db/httpTransport.ts +0 -93
  240. package/runtime/sdk/server/db/index.ts +0 -7
  241. package/runtime/sdk/server/db/marci/query.ts +0 -412
  242. package/runtime/sdk/server/db/types.ts +0 -202
  243. package/runtime/sdk/server/errors.ts +0 -12
  244. package/runtime/sdk/server/host.ts +0 -74
  245. package/runtime/sdk/server/inject.ts +0 -13
  246. package/runtime/sdk/server/runtime.ts +0 -133
  247. package/runtime/sdk/server/validate.ts +0 -87
  248. package/runtime/sdk/ui/NativeView.ts +0 -142
  249. package/runtime/sdk/ui/UI.ts +0 -39
  250. package/runtime/sdk/ui/UIBottomSheet.ts +0 -139
  251. package/runtime/sdk/ui/UIButton.ts +0 -101
  252. package/runtime/sdk/ui/UIContainer.ts +0 -60
  253. package/runtime/sdk/ui/UIImage.ts +0 -83
  254. package/runtime/sdk/ui/UIInput.ts +0 -185
  255. package/runtime/sdk/ui/UIModal.ts +0 -139
  256. package/runtime/sdk/ui/UINode.ts +0 -830
  257. package/runtime/sdk/ui/UIPager.ts +0 -362
  258. package/runtime/sdk/ui/UIPopover.ts +0 -100
  259. package/runtime/sdk/ui/UIScreen.ts +0 -123
  260. package/runtime/sdk/ui/UIScrollable.ts +0 -87
  261. package/runtime/sdk/ui/UISpacer.ts +0 -14
  262. package/runtime/sdk/ui/UITabs.ts +0 -236
  263. package/runtime/sdk/ui/UIText.ts +0 -51
  264. package/runtime/sdk/ui/UIVideo.ts +0 -88
  265. package/runtime/sdk/ui/UIVirtualizedList.ts +0 -241
  266. package/runtime/sdk/ui/UIWidget.ts +0 -127
  267. package/runtime/sdk/ui/fonts.ts +0 -13
  268. package/runtime/sdk/ui/presentable.ts +0 -117
  269. package/runtime/sdk/ui/router.ts +0 -132
  270. package/runtime/sdk/ui/theme.ts +0 -84
package/src/index.ts ADDED
@@ -0,0 +1,494 @@
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"
28
+ 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, peerVersion } from "./peers"
35
+
36
+ /* `install` is a standalone-binary-only command: it installs the shipped bundle onto PATH, so it's only
37
+ * meaningful when that bundle actually sits next to the executable — the `chisel` bundler + `runtime/`
38
+ * 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). */
41
+ const STANDALONE = (() => {
42
+ const dir = dirname(process.execPath)
43
+ const chisel = process.platform === "win32" ? "chisel.exe" : "chisel"
44
+ return existsSync(join(dir, "runtime")) && existsSync(join(dir, chisel))
45
+ })()
46
+
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. */
51
+ const INSTALLED = STANDALONE && (() => {
52
+ const exe = process.platform === "win32" ? "lecodes.exe" : "lecodes"
53
+ const self = (() => { try { return realpathSync(process.execPath) } catch { return process.execPath } })()
54
+ return (process.env.PATH ?? "").split(delimiter).some(d => {
55
+ if (!d) return false
56
+ try { const cand = join(d, exe); return existsSync(cand) && realpathSync(cand) === self } catch { return false }
57
+ })
58
+ })()
59
+
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
+ --web also open the app in the browser (Web Lite host:
124
+ same bundle + hot reload, camera/map plugins
125
+ have web twins; the /web/ URL is served always)
126
+ --desktop also open the app in the native desktop host
127
+ (same hot reload; window from app.json's
128
+ desktop block; closing it keeps the server)
129
+ --fps (--desktop) draw the host's frame counter
130
+ compile Compile the local project to JS (prints to stdout)
131
+ (was 'lecodes preview' — the old name still works)
132
+ -o, --out <file> write to a file instead of stdout
133
+ --entry <path> override the entrypoint
134
+ --no-header omit the metadata header
135
+ --no-minify keep the bundle readable (no compress/mangle)
136
+ --no-sourcemap don't generate/inline the source map
137
+ --public-url <url> asset URL prefix (default: the API origin)
138
+ render [file] Render the local UI project to a semantic JSON description
139
+ (needs the optional 'lecodes-renderer' package; UI projects only)
140
+ [file] render ONE screen module instead of the app:
141
+ a file that default-exports a UIScreen (or a
142
+ (state) => UIScreen) — every 'lecodes design'
143
+ screen is written that way. A bare id
144
+ ("login") resolves in design/screens/, and a
145
+ design screen renders at its meta.json frame
146
+ --state <name> ([file]) the state to pass a
147
+ (state) => UIScreen screen
148
+ -o, --out <file> write JSON to a file instead of stdout
149
+ --png [file] rasterize a PNG (default screenshot.png;
150
+ needs @napi-rs/canvas)
151
+ --map [file] draw the 3D scene's annotated top-down map
152
+ (grid + XZ footprints + named markers) as
153
+ <file>.png + <file>.json world-space data
154
+ (default map.png; --label-all, --max-px N;
155
+ also a { "map": "f.png" } script step)
156
+ --script <file> drive the app through a step list (tap/type/
157
+ scroll/wait/expect/screenshot/json steps;
158
+ games: frames/drag/hold/key/keyDown/keyUp,
159
+ expect.log, screenshot clip)
160
+ --out-dir <dir> where --script step outputs land (default .)
161
+ --clip <name|x,y,w,h> (--png) crop to one node's rect or a
162
+ logical-px rect — a component/region render
163
+ --fixtures <file> canned fetch responses (URL pattern →
164
+ { status?, json?, text? }) — make
165
+ backend-gated screens reachable headless
166
+ --scale <n> PNG device-pixel scale (default 2)
167
+ --width/--height viewport size (default 390x844)
168
+ --entry <path> override the entrypoint
169
+ --settle <ms> wait for promises/timers/fetch before snapshot
170
+ --wait-network let data fetches hit the real network
171
+ --logs forward the app's console output (to stderr)
172
+ --desktop render through the NATIVE desktop host instead:
173
+ real Filament 3D + tgfx UI + 2D, composited.
174
+ PNG only (--png). Downloads the LeCodes
175
+ Desktop renderer on first use into
176
+ ~/.lecodes/renderer (Windows/Linux;
177
+ LECODES_DESKTOP_EXE overrides)
178
+ --frames <n> (--desktop) frames to run before the shot —
179
+ headroom for async loads (default 60)
180
+ --desktop --script <file> drive the scenario through the native
181
+ host: tap/drag/hold take { x, y } coordinates
182
+ (no names — the host has no UI tree; type/
183
+ scroll/clip unavailable), expect.log is
184
+ checked, text/node/screen/value are skipped;
185
+ per-step screenshots + final --png
186
+ --view <name> (--desktop) inspection camera auto-fitted to the
187
+ whole 3D scene: iso | top | front | side | back
188
+ [--fov N]; the app's camera is untouched
189
+ --camera x,y,z --look-at x,y,z [--fov N]
190
+ (--desktop) inspection camera at a fixed eye /
191
+ look-at point (world units, Y up). In a
192
+ --script run use { "view" } / { "camera" } steps
193
+ --fixed-dt <ms> (--desktop) virtual app clock, ms per frame —
194
+ deterministic game time (scripted runs
195
+ default to 16; plain renders to wall clock)
196
+ test [name…] Run headless flow tests: tests/*.flow.json scenarios, plus flows
197
+ derived from the design graph (design/meta.json activator edges
198
+ — checks the built app against the designed navigation; needs
199
+ named elements + screen roots). Failing steps dump the frame
200
+ (JSON + PNG) under tests/.artifacts/<flow>/.
201
+ Design flows read tests/fixtures.json automatically (canned
202
+ fetch responses) so backend-gated transitions run headless;
203
+ file scenarios declare their own via the "fixtures" envelope.
204
+ --flows run ONLY the design-derived flows
205
+ --no-flows run ONLY the tests/*.flow.json files
206
+ --fixtures <file> fixture pack for the design flows (overrides
207
+ the tests/fixtures.json default)
208
+ --json machine-readable report on stdout
209
+ --dir <folder> tests folder (default tests/)
210
+ --design-dir <dir> design folder (default design/)
211
+ --artifacts <dir> failure-artifact folder override
212
+ --desktop run the file scenarios through the NATIVE host
213
+ (coordinates only; expect.log checked,
214
+ text/node/screen/value skipped; design flows
215
+ stay headless). --frames / --fixed-dt as in
216
+ render --desktop
217
+ (+ the render timing flags: --width/--height, --settle,
218
+ --device, --safe-area, --wait-network, --logs, --entry)
219
+ update Move everything a project depends on to its newest published
220
+ state, in dependency order: the desktop host ('desktop
221
+ update'), matc ('shaders update'), the project's shader
222
+ compiles, the IDE types (.lecodes/types/ + tsconfig.json,
223
+ from the server — the former 'lecodes types'), the
224
+ ios/android shell pins ('app update'), then the GLOBAL npm
225
+ installs: every installed optional peer (lecodes-renderer,
226
+ lecodes-design, lecodes-3d-editor, lecodes-assets) and this
227
+ CLI that is behind npm's latest gets 'npm install -g'.
228
+ Pinned tools are skipped, not overridden: LECODES_DESKTOP_EXE
229
+ / LECODES_MATC mean a local build. Keeps going when a step
230
+ fails; the summary says which.
231
+ --local types from the SDK bundled in this CLI
232
+ --no-shells leave ios/android pins alone (no re-sync)
233
+ --check npm step: report what's behind, install nothing
234
+ --no-cli skip the npm step (offline)
235
+ design [serve] LeCodes Design — a live canvas of screens for AI-driven prototyping
236
+ (needs the optional 'lecodes-design' package; works inside a
237
+ cloned project or standalone in any folder)
238
+ --port <n> dev-server port (default 4477)
239
+ --dir <folder> design folder (default design/)
240
+ --no-open don't open the browser
241
+ design init Scaffold the design folder (screens/, shared/, meta.json, CLAUDE.md)
242
+ --desktop desktop-frame board (meta.json defaultSize
243
+ [1280, 800] instead of the phone frame)
244
+ design snapshot [screen…] Render design screens headless (needs 'lecodes-renderer')
245
+ one screen → JSON to stdout; else files in design/.snapshots/
246
+ --png also rasterize PNGs (needs @napi-rs/canvas)
247
+ --out-dir <dir> output folder override
248
+ --logs forward the screens' console output (to stderr)
249
+ design comments Review comments pinned to the board, grouped by screen (open first)
250
+ --all include resolved threads
251
+ add "<text>" [--screen <id>] [--state <s>] [--element <name>]
252
+ reply <number> "<text>" · resolve <number> [--reply "<text>"] [--reopen]
253
+ (--ai signs a write as Claude; sends with the next "lecodes push")
254
+ desktop [run|build|update] The desktop target — the host 'lecodes render --desktop' uses too:
255
+ no args shows the version status (selected / downloaded /
256
+ newest published)
257
+ run compile the current project and open it in a real
258
+ desktop window (size/min-size/title default from
259
+ app.json's desktop block; --width/--height/--title
260
+ override; --fullscreen/--windowed override the
261
+ app.json fullscreen; --scale <0.5..4|fit> overrides
262
+ the display scale — "fit" sizes a 1080p-designed app
263
+ to the monitor with the density raised to match;
264
+ --entry overrides the entrypoint; --fps draws the
265
+ host's frame counter in the corner — composited fps,
266
+ plus the 3D fps when they diverge; asset() resources
267
+ load from the working tree — --remote-assets or
268
+ --public-url <url> uses the server's copies instead);
269
+ --server [--port N] [--max-clients N] launches the
270
+ host as a multiplayer server, --connect ip:port as a
271
+ client — both only set Net.launch (the app decides)
272
+ build stage a portable app folder (default dist/<name>/):
273
+ the cached host renamed to <Name>.exe + app.js +
274
+ assets/ (the project's files) + runtime/ (the engine's)
275
+ + app.json (the host reads title/size/fullscreen from
276
+ it) + app.ico from app.json "icon". Duplicate asset
277
+ basenames are a build error (they silently alias in
278
+ the bundle). Also spelled 'lecodes app build desktop'.
279
+ On macOS: a signed .app instead (the downloaded LeCodes
280
+ Desktop.app + the project inside it); app.json
281
+ desktop.macos = bundleId / buildNumber / category /
282
+ signing.identity / notarize.profile (a notarytool
283
+ keychain profile — notarized + stapled, so it opens
284
+ on any Mac without "Open Anyway").
285
+ -o <dir> output folder (default dist/<name>)
286
+ --slug <name> folder/zip name override (Latin-only)
287
+ --renderer <gl|vulkan> host + shader variant
288
+ --probe headless-render the staged folder to PNG
289
+ (macOS: a brief windowed run + snapshot)
290
+ --zip also pack the result for sending
291
+ --dmg macOS: also build a drag-to-Applications
292
+ disk image (signed + notarized too)
293
+ --sign <identity|adhoc> macOS codesign identity
294
+ --notarize <profile> / --no-notarize macOS
295
+ --zip <out>.zip, UTF-8/forward-slash entries
296
+ update move to the newest published release
297
+ (cached in ~/.lecodes/renderer)
298
+ lightmap bake Bake static shadows + ambient occlusion into a lightmap atlas
299
+ (assets/lightmap/lightmap.{png,bake}) by running the app in
300
+ the desktop host: the statics are what the app registers
301
+ with Lightmap.add, the bake fires at its Lightmap.load call.
302
+ Props need UVs first: lecodes assets doctor … --lightmap-uv
303
+ --out <dir> where the atlas lands (assets/lightmap)
304
+ --size <px> atlas edge (4096) --texel <m> density (0.02)
305
+ --sun-rays/--ao-rays <n> quality (32 / 64)
306
+ --ao-distance <m> AO reach (2) --sun-angle <deg> (0.5)
307
+ --frames <n> load headroom (900) --logs host output
308
+ navmesh bake Build the level's navigation mesh (assets/nav/<scene>.navmesh)
309
+ for NavAgent / NavMesh: the desktop host dumps every STATIC
310
+ body's collision triangles at the scene's env.navmesh (or
311
+ NavMesh.load) point, Recast builds the mesh here.
312
+ --out <dir> where it lands (assets/nav)
313
+ --name <n> file name (the scene file with env.navmesh)
314
+ --radius/--height/--climb/--slope agent size (scene's)
315
+ --cell <m> (0.25) --cell-height <m> (0.1) --tile <cells> (48)
316
+ --obj also write <n>.navmesh.obj to inspect
317
+ --frames <n> load headroom (900) --logs host output
318
+ shaders [compile|update] The project's custom shaders (.mat) and their local compile:
319
+ no args lists the shaders + their compile state; compile
320
+ compiles them all into .lecodes/shaders (the same pass
321
+ 'lecodes dev' runs on the fly — via Filament's matc,
322
+ downloaded on first use to ~/.lecodes/matc); update
323
+ downloads the newest matc (run after an engine upgrade)
324
+ scene [path] LeCodes Scene Editor — visually edit a .scene.ts (tree, inspector,
325
+ gizmo) on local files (needs the optional 'lecodes-3d-editor' package;
326
+ works inside a cloned project or standalone in any folder)
327
+ --port <n> editor-server port (default 4488)
328
+ --no-open don't open the browser
329
+ --no-mcp don't register the MCP server in .mcp.json
330
+ thumbs [path…] Asset thumbnails for the scene editor, WITHOUT a browser: decodes
331
+ the project's images (KTX2 too, via KTX-Software's ktx tool) into the
332
+ editor's preview cache. Incremental — the cached name carries the
333
+ asset's content hash, so unchanged files are skipped. Models,
334
+ materials and prefabs are rendered by the editor, not here
335
+ --force redo even the up-to-date ones
336
+ --size <px> longest edge (default 256)
337
+ assets convert <model.fbx|.glb> [--clips a.fbx b.fbx …] [-o out.glb] [--drop-model-clips]
338
+ [--fix] [--ktx2|--ktx2-hq]
339
+ FBX → LeCodes-ready GLB: meters/Y-up, textures embedded, clips from
340
+ other FBX files merged by bone name (Mixamo: one FBX per animation);
341
+ --fix/--ktx2 run the doctor passes (needs 'lecodes-assets')
342
+ Unity .mat files next to the FBX are read for the real textures
343
+ (metallic/roughness/ORM/emission packed the glTF way); --no-unity
344
+ keeps the FBX references; --unity-index <unity-index.txt> resolves GUIDs
345
+ --list-nodes / --keep <node> … / --split [dir]: carve one variant out of a
346
+ multi-variant prop file, re-origined on the ground (or one GLB per node)
347
+ --merge-skins b.fbx …: modular character pieces onto ONE skeleton
348
+ assets unpack <pkg.unitypackage> [-o dir] [--only re …] [--list]
349
+ Stream a .unitypackage to a folder: writes unity-index.txt (GUID → path,
350
+ what convert resolves textures with) and the matching assets + .meta
351
+ assets scene <map.unity> [-o level.json] [--filter re] [--group g] [--region x0,z0,x1,z1]
352
+ Unity scene → placed prefabs / mesh objects with world TRS in LeCodes'
353
+ frame + a prefab table (FBX + --keep node per prefab)
354
+ assets probe <file.fbx|.glb> [--filmstrip [out.png]]
355
+ Skeleton, clips (+ root motion / first-frame-vs-rest per clip), meshes,
356
+ materials, textures, bind sanity, duplicate names; --filmstrip renders
357
+ a CPU-skinned PNG strip per clip (no GPU)
358
+ assets sky <env.exr|.hdr> [-o dir] [--size N] [--ibl-size N] [--ibl-only]
359
+ Equirectangular HDR → the two cubemaps a scene needs: sky.ktx (sharp,
360
+ for 'skybox: { texture }') and ibl.ktx (prefiltered + SH, lights the
361
+ scene) — they are NOT interchangeable. Reports where the sun is in
362
+ the environment so the scene's light can be aimed to match
363
+ assets terrain-pack <layer> … [-o dir] [--normals n0 n1 …] [--names a b …] [--size N] [--png]
364
+ A terrain's splat layers (≤ 4) → <name>.ktx2 per layer + the packed
365
+ normal maps normals01/normals23.ktx2 (what Terrain.create's
366
+ layers/normals read). A layer = an albedo file or a FOLDER holding
367
+ *Albedo* + *Normal* (Unity packs). Prints the Terrain.create fragment
368
+ assets doctor <in.glb|dir> [--fix] [--ktx2] [--dump]
369
+ Analyze / fix a GLB against engine limits (bone cap 256, texture
370
+ formats/size, KTX2 via toktx)
371
+ app init <ios|android|desktop> Generate a native shell in <project>/ios/ or <project>/android/
372
+ (offline-first: runs the bundled app.js; never pushed to LeCodes).
373
+ desktop: no shell needed — writes app.json's desktop block
374
+ (window size; --fullscreen for kiosks)
375
+ --name <s> display name (default: app.json / folder name)
376
+ --bundle-id <id> iOS bundle identifier
377
+ --app-id <id> Android applicationId (default com.example.<slug>)
378
+ --team <id> Apple development team id
379
+ --variant <v> iOS SDK variant: auto (default — sync picks the
380
+ smallest of core/2d/3d/full covering what
381
+ the bundle uses), or one of those to pin
382
+ --sdk <v> native SDK release (default: the newest one,
383
+ pinned in app.json ios.sdk / android.sdk);
384
+ 'latest' floats it — every sync re-resolves
385
+ app sync [ios|android] Make every existing shell match the project: compile the bundle
386
+ + embed its assets (ios/App/Resources/ resp. the android
387
+ assets/; offline by default, also for server projects;
388
+ app.json "resources": {"remote": [globs]} keeps heavy assets
389
+ streaming), pick the iOS SDK variant from the bundle's engine
390
+ use, vendor app.json plugins (pinned in plugins.lock.json),
391
+ regenerate the CLI-owned shell files. app.json "update": true
392
+ (or {"url": …}) enables native OTA JS updates at launch (iOS).
393
+ app.json "icon" (square ≥1024px image) renders the app icons
394
+ every sync ("iconBackground": adaptive/flatten backdrop)
395
+ app update [ios|android] Move the shell's pins forward — app.json ios.sdk / android.sdk
396
+ to the newest published SDK release, plugins.lock.json to each
397
+ plugin's newest tag — then re-sync
398
+ --sdk <v|latest> pin that release instead of the newest
399
+ --check report available updates, write nothing
400
+ --no-plugins only move the SDK pin
401
+ --no-sync update the pins without re-syncing
402
+ app plugin <list|add|remove> Manage native plugins in app.json (then run 'lecodes app sync')
403
+ add takes a known id (camera, qr-scanner) or github:owner/repo[@tag]
404
+ app open [ios|android] Open the native shell in its IDE (Xcode / Android Studio)
405
+ app doctor Preflight checks for building/uploading the shells
406
+ app build ios Sync, archive and export the .ipa (automatic signing)
407
+ --configuration <c> build configuration (default Release)
408
+ --method <m> export method (default app-store-connect)
409
+ --no-export stop after the archive
410
+ --no-sync skip the lecodes app sync step
411
+ app build android Sync, then gradlew assembleRelease + bundleRelease (signing via
412
+ android/keystore.properties or LECODES_ANDROID_* env vars)
413
+ --configuration <c> Release (default) or Debug
414
+ --no-aab skip the .aab (APK only)
415
+ --no-sync skip the lecodes app sync step
416
+ app build desktop Same as 'lecodes desktop build' (kept for symmetry with the
417
+ ios/android shells) — see the desktop group above.
418
+ app upload ios Upload the archive to App Store Connect (then bumps buildNumber)
419
+ --key-path <p8> --key-id <id> --issuer-id <id>
420
+ ASC API key; stored in ~/.lecodes/asc.json
421
+ pn send --title <s> Push notifications for this project ('lecodes pn' for the group).
422
+ Sends with your login — no push key needed to test.
423
+ --user <ids> target Push.register({ user }) ids (comma-sep)
424
+ --to <pa_…> target addresses (comma-separated)
425
+ --body/--url/--badge/--data payload fields
426
+ pn devices Which devices registered (the 'why didn't it arrive?' command)
427
+ --user <id> only that user's devices
428
+ pn keys [new|revoke] Push keys for YOUR server: list, mint (owner only, shown once
429
+ — 'new --env' banks it in .env), revoke <id>
430
+
431
+ Config lives in ~/.lecodes/config.json (override with LECODES_API / LECODES_TOKEN).
432
+ A .env next to app.json is read at startup for LECODES_* / CREATOR_* keys (the shell's own
433
+ environment wins; ./ paths resolve against the project; the file is never pushed) — e.g.
434
+ LECODES_DESKTOP_EXE, LECODES_MATC, LECODES_NOTARY_PROFILE, LECODES_MACOS_SIGN_ID, LECODES_TOKEN.`
435
+
436
+ // `preview` is the legacy name of `compile` — kept registered (and out of the help) for old scripts.
437
+ 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 }
438
+ if (STANDALONE) commands.install = install
439
+
440
+ /** Package version, inlined at build time (the standalone binary has no package.json on disk). */
441
+ const readVersion = (): string => pkg.version ?? "unknown"
442
+
443
+ /** `--version`: the CLI version, every separately-installed optional peer (or "not installed"),
444
+ * and the desktop render host `render --desktop` would use (no network — cache state only). */
445
+ const printVersion = () => {
446
+ const rows: [label: string, value: string][] = [["lecodes-cli", readVersion()]]
447
+ for (const [name, probe] of OPTIONAL_PEERS) {
448
+ const version = peerVersion(name, probe)
449
+ rows.push([name, version ?? c.dim("not installed")])
450
+ }
451
+ const desktopVersion = selectedDesktopVersion()
452
+ rows.push(["lecodes-desktop", installedDesktopVersions().includes(desktopVersion)
453
+ ? desktopVersion
454
+ : `${desktopVersion} ${c.dim("(not downloaded — fetched on first `render --desktop`)")}`])
455
+ const matcVersion = installedMatcVersions()[0]
456
+ rows.push(["matc", matcVersion ?? c.dim("not downloaded — fetched on the first local shader compile")])
457
+ const width = Math.max(...rows.map(([label]) => label.length))
458
+ for (const [label, value] of rows) log(`${label.padEnd(width)} ${value}`)
459
+ }
460
+
461
+ const main = async () => {
462
+ const argv = process.argv.slice(2)
463
+ const command = argv[0]
464
+ // The project's .env (LECODES_* / CREATOR_* next to app.json) — before any command reads the
465
+ // environment; the real environment keeps precedence. See projectEnv.ts.
466
+ loadProjectEnv()
467
+
468
+ if (!command || command === "help" || command === "--help" || command === "-h") {
469
+ log(HELP)
470
+ return
471
+ }
472
+
473
+ if (command === "--version" || command === "-v" || command === "version") {
474
+ printVersion()
475
+ return
476
+ }
477
+
478
+ const run = commands[command]
479
+ if (!run) {
480
+ log(c.red(`Unknown command: ${command}`))
481
+ log(HELP)
482
+ process.exitCode = 1
483
+ return
484
+ }
485
+
486
+ // `assets` owns its own flag grammar (multi-value --clips …) — hand it the raw argv too.
487
+ await run(Object.assign(parseArgs(argv.slice(1)), { _raw: argv.slice(1) }))
488
+ }
489
+
490
+ main().catch((e: unknown) => {
491
+ if (e instanceof CliError) log(c.red("✗ ") + e.message)
492
+ else log(c.red("✗ Unexpected error: ") + (e instanceof Error ? e.message : String(e)))
493
+ process.exitCode = 1
494
+ })
@@ -0,0 +1,41 @@
1
+ // Types for the optional `lecodes-3d-editor` package's server entry (lazy-loaded by the `scene`
2
+ // command). The real package is installed separately; this lets the dynamic import typecheck
3
+ // without it present. Keep in sync with lecodes-tools/packages/lecodes-3d-editor/src/server/index.ts (the CLI↔tool
4
+ // dist coupling: after a server-surface change, rebuild the editor's dist and update this file).
5
+ declare module "lecodes-3d-editor/server" {
6
+ export type CompiledScene = { js: string | null, error?: string }
7
+
8
+ export type SceneServerOptions = {
9
+ root: string
10
+ compile: (scenePath: string) => Promise<CompiledScene>
11
+ /** A shader's compiled artifact file for a platform ("webgl-lite" / "webgl") from the host's
12
+ * cache, null when absent (the server builds the lite JSON itself then). */
13
+ shaderArtifact?: (shaderText: string, platform: string) => string | null
14
+ port?: number
15
+ appDir?: string
16
+ log?: (message: string) => void
17
+ }
18
+
19
+ export type SceneServer = {
20
+ port: number
21
+ url: string
22
+ close: () => void
23
+ }
24
+
25
+ export function startSceneServer(options: SceneServerOptions): Promise<SceneServer>
26
+
27
+ /* The asset-preview cache (server/previewCache.ts) — `lecodes thumbs` writes into the same
28
+ * `.lecodes/preview/<asset>.<hash>.png` layout the editor reads. */
29
+
30
+ /** sha256 tag (10 hex) of an asset's current bytes; null when the file is gone. */
31
+ export function hashAsset(projectDir: string, assetPath: string): string | null
32
+ /** Whether the cached preview for THAT hash is already on disk (the incremental skip). */
33
+ export function previewIsCurrent(projectDir: string, assetPath: string, hash: string): boolean
34
+ /** Cache file name for an asset at a known hash. */
35
+ export function previewName(assetPath: string, hash: string): string
36
+ /** Store a preview PNG under the asset's current hash (older ones are dropped); returns the file
37
+ * name written, or null when the asset itself is missing. */
38
+ export function writePreviewFile(projectDir: string, assetPath: string, png: Uint8Array): string | null
39
+ /** Asset path → cache file name, for every preview on disk. */
40
+ export function listPreviews(projectDir: string): Record<string, string>
41
+ }
@@ -0,0 +1,7 @@
1
+ // Types for the optional `lecodes-assets` package's CLI entry (lazy-loaded by the `assets` command).
2
+ // The real package is installed separately; this lets the dynamic import typecheck without it
3
+ // present. Keep in sync with packages/lecodes-assets/src/cli.ts.
4
+ declare module "lecodes-assets/cli" {
5
+ /** Runs `convert | probe | doctor` with the given argv (sub-command first). Resolves to the exit code. */
6
+ export function runAssetsCli(argv: string[]): Promise<number>
7
+ }