lecodes-cli 0.19.0 → 0.19.2

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 (254) hide show
  1. package/dist/index.js +730 -235
  2. package/package.json +14 -13
  3. package/runtime/scene-harness.json +1 -1
  4. package/runtime/sdk-types.json +1 -1
  5. package/src/api.ts +302 -0
  6. package/src/browserAuth.ts +87 -0
  7. package/src/cmgenTool.ts +104 -0
  8. package/src/commands/app.ts +891 -0
  9. package/src/commands/appAndroid.ts +603 -0
  10. package/src/commands/appDesktop.ts +326 -0
  11. package/src/commands/appDesktopMac.ts +470 -0
  12. package/src/commands/appIcon.ts +187 -0
  13. package/src/commands/appShared.ts +448 -0
  14. package/src/commands/appTemplates.ts +883 -0
  15. package/src/commands/appTemplatesAndroid.ts +599 -0
  16. package/src/commands/appTemplatesGradlew.ts +9 -0
  17. package/src/commands/assets.ts +28 -0
  18. package/src/commands/clone.ts +60 -0
  19. package/src/commands/compile.ts +253 -0
  20. package/src/commands/create.ts +59 -0
  21. package/src/commands/design.ts +863 -0
  22. package/src/commands/designTemplates.ts +11 -0
  23. package/src/commands/desktop.ts +122 -0
  24. package/src/commands/dev.ts +214 -0
  25. package/src/commands/diff.ts +59 -0
  26. package/src/commands/init.ts +191 -0
  27. package/src/commands/install.ts +198 -0
  28. package/src/commands/lightmap.ts +290 -0
  29. package/src/commands/link.ts +147 -0
  30. package/src/commands/login.ts +24 -0
  31. package/src/commands/navmesh.ts +225 -0
  32. package/src/commands/pn.ts +305 -0
  33. package/src/commands/projectTemplates.ts +129 -0
  34. package/src/commands/pull.ts +109 -0
  35. package/src/commands/push.ts +173 -0
  36. package/src/commands/render.ts +414 -0
  37. package/src/commands/scene.ts +148 -0
  38. package/src/commands/shaders.ts +157 -0
  39. package/src/commands/status.ts +29 -0
  40. package/src/commands/test.ts +370 -0
  41. package/src/commands/thumbs.ts +178 -0
  42. package/src/commands/types.ts +87 -0
  43. package/src/commands/update.ts +190 -0
  44. package/src/compile/assetIcons.ts +214 -0
  45. package/src/compile/collect.ts +46 -0
  46. package/src/compile/collectLocal.ts +37 -0
  47. package/src/compile/designCompile.ts +104 -0
  48. package/src/compile/fonts.ts +156 -0
  49. package/src/compile/headlessBundle.ts +129 -0
  50. package/src/compile/nativeStack.ts +21 -0
  51. package/src/compile/projectCompile.ts +109 -0
  52. package/src/compile/sceneCompile.ts +113 -0
  53. package/src/compile/screenEntry.ts +127 -0
  54. package/src/compile/shaders.ts +245 -0
  55. package/src/config.ts +42 -0
  56. package/src/designMeta.ts +35 -0
  57. package/src/desktopRenderer.ts +532 -0
  58. package/src/desktopScript.ts +276 -0
  59. package/src/dev/clientTemplates.ts +160 -0
  60. package/src/dev/devServer.ts +292 -0
  61. package/src/dev/wsServer.ts +144 -0
  62. package/src/distRoot.ts +20 -0
  63. package/src/ignore.ts +163 -0
  64. package/src/index.ts +491 -0
  65. package/src/lecodes-3d-editor.d.ts +41 -0
  66. package/src/lecodes-assets.d.ts +7 -0
  67. package/src/lecodes-design.d.ts +191 -0
  68. package/src/lecodes-renderer.d.ts +131 -0
  69. package/src/localFiles.ts +144 -0
  70. package/src/manifest.ts +46 -0
  71. package/src/matcTool.ts +137 -0
  72. package/src/peers.ts +40 -0
  73. package/src/project.ts +61 -0
  74. package/src/projectEnv.ts +94 -0
  75. package/src/qrcode-terminal.d.ts +9 -0
  76. package/src/releases.ts +125 -0
  77. package/src/serverDiff.ts +78 -0
  78. package/src/textDiff.ts +103 -0
  79. package/src/types.ts +0 -0
  80. package/src/util.ts +146 -0
  81. package/runtime/sdk/animate/animate.ts +0 -238
  82. package/runtime/sdk/animate/bezier.ts +0 -138
  83. package/runtime/sdk/animate/easings.ts +0 -126
  84. package/runtime/sdk/canvas/Canvas.ts +0 -305
  85. package/runtime/sdk/compile/aspectMacro.ts +0 -86
  86. package/runtime/sdk/compile/assetIconMacro.ts +0 -384
  87. package/runtime/sdk/compile/assetMacro.ts +0 -146
  88. package/runtime/sdk/compile/assetName.ts +0 -50
  89. package/runtime/sdk/compile/bundler.ts +0 -287
  90. package/runtime/sdk/compile/compileProject.ts +0 -129
  91. package/runtime/sdk/compile/detectEntry.ts +0 -128
  92. package/runtime/sdk/compile/fontMacro.ts +0 -459
  93. package/runtime/sdk/compile/fontRegistry.ts +0 -78
  94. package/runtime/sdk/compile/header.ts +0 -67
  95. package/runtime/sdk/compile/index.ts +0 -100
  96. package/runtime/sdk/compile/libraryImports.ts +0 -52
  97. package/runtime/sdk/compile/liteMaterial.ts +0 -247
  98. package/runtime/sdk/compile/sceneEditor.ts +0 -88
  99. package/runtime/sdk/compile/serverSplit.ts +0 -233
  100. package/runtime/sdk/compile/serverTypes.ts +0 -227
  101. package/runtime/sdk/compile/sfnt.ts +0 -98
  102. package/runtime/sdk/compile/shaderSchema.ts +0 -202
  103. package/runtime/sdk/compile/shaderTargets.ts +0 -81
  104. package/runtime/sdk/compile/sourcemap.ts +0 -25
  105. package/runtime/sdk/core/Aspect.ts +0 -568
  106. package/runtime/sdk/core/InspectorUI.ts +0 -212
  107. package/runtime/sdk/core/StateMachine.ts +0 -308
  108. package/runtime/sdk/core/color.ts +0 -66
  109. package/runtime/sdk/core/compWrite.ts +0 -42
  110. package/runtime/sdk/core/events.ts +0 -38
  111. package/runtime/sdk/core/fields.ts +0 -120
  112. package/runtime/sdk/core/registry.ts +0 -23
  113. package/runtime/sdk/core/signals.ts +0 -277
  114. package/runtime/sdk/core/time.ts +0 -84
  115. package/runtime/sdk/g2/Camera2D.ts +0 -40
  116. package/runtime/sdk/g2/CharacterController2D.ts +0 -276
  117. package/runtime/sdk/g2/Node2D.ts +0 -267
  118. package/runtime/sdk/g2/OneWay2D.ts +0 -66
  119. package/runtime/sdk/g2/Physics2D.ts +0 -346
  120. package/runtime/sdk/g2/Scene2D.ts +0 -209
  121. package/runtime/sdk/g2/Shape2D.ts +0 -259
  122. package/runtime/sdk/g2/Sprite.ts +0 -89
  123. package/runtime/sdk/g2/SpriteAnimation.ts +0 -171
  124. package/runtime/sdk/g2/SpriteSheet.ts +0 -166
  125. package/runtime/sdk/g2/Texture2D.ts +0 -47
  126. package/runtime/sdk/g2/Tilemap.ts +0 -41
  127. package/runtime/sdk/g2/Tileset.ts +0 -71
  128. package/runtime/sdk/g2/Trigger2D.ts +0 -77
  129. package/runtime/sdk/g2/autotile.ts +0 -433
  130. package/runtime/sdk/g2/cells.ts +0 -91
  131. package/runtime/sdk/g2/defineScene2d.ts +0 -381
  132. package/runtime/sdk/g2/groups2d.ts +0 -106
  133. package/runtime/sdk/g2/loop.ts +0 -50
  134. package/runtime/sdk/g2/scenarios2d.ts +0 -69
  135. package/runtime/sdk/g2/touch.ts +0 -83
  136. package/runtime/sdk/gl/Camera.ts +0 -160
  137. package/runtime/sdk/gl/CameraPlace.ts +0 -52
  138. package/runtime/sdk/gl/CharacterController.ts +0 -238
  139. package/runtime/sdk/gl/Gearbox.ts +0 -212
  140. package/runtime/sdk/gl/Geometry.ts +0 -279
  141. package/runtime/sdk/gl/IK.ts +0 -193
  142. package/runtime/sdk/gl/InstancedMesh.ts +0 -132
  143. package/runtime/sdk/gl/Light.ts +0 -107
  144. package/runtime/sdk/gl/Lightmap.ts +0 -256
  145. package/runtime/sdk/gl/Material.ts +0 -260
  146. package/runtime/sdk/gl/Mesh.ts +0 -83
  147. package/runtime/sdk/gl/Model.ts +0 -124
  148. package/runtime/sdk/gl/NavAgent.ts +0 -337
  149. package/runtime/sdk/gl/NavMesh.ts +0 -397
  150. package/runtime/sdk/gl/Node.ts +0 -350
  151. package/runtime/sdk/gl/Noise.ts +0 -30
  152. package/runtime/sdk/gl/Particles.ts +0 -676
  153. package/runtime/sdk/gl/Physics.ts +0 -222
  154. package/runtime/sdk/gl/Plane.ts +0 -53
  155. package/runtime/sdk/gl/Ragdoll.ts +0 -270
  156. package/runtime/sdk/gl/Ray.ts +0 -16
  157. package/runtime/sdk/gl/Scene.ts +0 -486
  158. package/runtime/sdk/gl/Shape.ts +0 -409
  159. package/runtime/sdk/gl/Terrain.ts +0 -1092
  160. package/runtime/sdk/gl/Texture.ts +0 -63
  161. package/runtime/sdk/gl/Trigger.ts +0 -45
  162. package/runtime/sdk/gl/Vehicle.ts +0 -473
  163. package/runtime/sdk/gl/Wheel.ts +0 -240
  164. package/runtime/sdk/gl/animation/AnimationClip.ts +0 -204
  165. package/runtime/sdk/gl/animation/Animator.ts +0 -120
  166. package/runtime/sdk/gl/animation/Layer.ts +0 -31
  167. package/runtime/sdk/gl/animation/Loop.ts +0 -30
  168. package/runtime/sdk/gl/animation/Playback.ts +0 -44
  169. package/runtime/sdk/gl/animation/core.ts +0 -336
  170. package/runtime/sdk/gl/controls.ts +0 -95
  171. package/runtime/sdk/gl/physicsEvents.ts +0 -20
  172. package/runtime/sdk/gl/scenarios.ts +0 -291
  173. package/runtime/sdk/gl/state.ts +0 -6
  174. package/runtime/sdk/gl/terrainMesh.ts +0 -219
  175. package/runtime/sdk/gl/touch.ts +0 -68
  176. package/runtime/sdk/inject.ts +0 -197
  177. package/runtime/sdk/math/Mathf.ts +0 -118
  178. package/runtime/sdk/math/mat4.ts +0 -278
  179. package/runtime/sdk/math/quat.ts +0 -232
  180. package/runtime/sdk/math/vec.ts +0 -255
  181. package/runtime/sdk/net/codec.ts +0 -119
  182. package/runtime/sdk/net/core.ts +0 -384
  183. package/runtime/sdk/net/index.ts +0 -181
  184. package/runtime/sdk/net/replication.ts +0 -622
  185. package/runtime/sdk/plugins/camera.ts +0 -81
  186. package/runtime/sdk/plugins/geolocation.ts +0 -123
  187. package/runtime/sdk/plugins/oauth.ts +0 -61
  188. package/runtime/sdk/plugins/permission.ts +0 -7
  189. package/runtime/sdk/plugins/push.ts +0 -132
  190. package/runtime/sdk/plugins/qr.ts +0 -73
  191. package/runtime/sdk/plugins/service.ts +0 -47
  192. package/runtime/sdk/runtime/app.ts +0 -101
  193. package/runtime/sdk/runtime/appEvents.ts +0 -54
  194. package/runtime/sdk/runtime/channel.ts +0 -61
  195. package/runtime/sdk/runtime/clipboard.ts +0 -20
  196. package/runtime/sdk/runtime/datetime.ts +0 -329
  197. package/runtime/sdk/runtime/device.ts +0 -293
  198. package/runtime/sdk/runtime/fetch.ts +0 -77
  199. package/runtime/sdk/runtime/files.ts +0 -23
  200. package/runtime/sdk/runtime/input.ts +0 -175
  201. package/runtime/sdk/runtime/media.ts +0 -111
  202. package/runtime/sdk/runtime/misc.ts +0 -16
  203. package/runtime/sdk/runtime/net.ts +0 -36
  204. package/runtime/sdk/runtime/rpc.ts +0 -218
  205. package/runtime/sdk/runtime/service.ts +0 -83
  206. package/runtime/sdk/runtime/share.ts +0 -9
  207. package/runtime/sdk/runtime/storage.ts +0 -13
  208. package/runtime/sdk/runtime/touch.ts +0 -76
  209. package/runtime/sdk/scene/defineScene.ts +0 -1353
  210. package/runtime/sdk/scene/editorPlugins.ts +0 -110
  211. package/runtime/sdk/scene/gizmos.ts +0 -148
  212. package/runtime/sdk/scene/grammar.ts +0 -120
  213. package/runtime/sdk/scene/material.ts +0 -188
  214. package/runtime/sdk/server/auth/appConfig.ts +0 -12
  215. package/runtime/sdk/server/auth/global.ts +0 -80
  216. package/runtime/sdk/server/auth/host.ts +0 -318
  217. package/runtime/sdk/server/auth/models.ts +0 -83
  218. package/runtime/sdk/server/auth/types.ts +0 -50
  219. package/runtime/sdk/server/channel.ts +0 -56
  220. package/runtime/sdk/server/context.ts +0 -36
  221. package/runtime/sdk/server/db/defineDb.ts +0 -237
  222. package/runtime/sdk/server/db/fields.ts +0 -132
  223. package/runtime/sdk/server/db/httpTransport.ts +0 -93
  224. package/runtime/sdk/server/db/index.ts +0 -7
  225. package/runtime/sdk/server/db/marci/query.ts +0 -412
  226. package/runtime/sdk/server/db/types.ts +0 -202
  227. package/runtime/sdk/server/errors.ts +0 -12
  228. package/runtime/sdk/server/host.ts +0 -74
  229. package/runtime/sdk/server/inject.ts +0 -13
  230. package/runtime/sdk/server/runtime.ts +0 -133
  231. package/runtime/sdk/server/validate.ts +0 -87
  232. package/runtime/sdk/ui/NativeView.ts +0 -142
  233. package/runtime/sdk/ui/UI.ts +0 -39
  234. package/runtime/sdk/ui/UIBottomSheet.ts +0 -139
  235. package/runtime/sdk/ui/UIButton.ts +0 -101
  236. package/runtime/sdk/ui/UIContainer.ts +0 -60
  237. package/runtime/sdk/ui/UIImage.ts +0 -83
  238. package/runtime/sdk/ui/UIInput.ts +0 -185
  239. package/runtime/sdk/ui/UIModal.ts +0 -139
  240. package/runtime/sdk/ui/UINode.ts +0 -830
  241. package/runtime/sdk/ui/UIPager.ts +0 -362
  242. package/runtime/sdk/ui/UIPopover.ts +0 -100
  243. package/runtime/sdk/ui/UIScreen.ts +0 -123
  244. package/runtime/sdk/ui/UIScrollable.ts +0 -87
  245. package/runtime/sdk/ui/UISpacer.ts +0 -14
  246. package/runtime/sdk/ui/UITabs.ts +0 -236
  247. package/runtime/sdk/ui/UIText.ts +0 -51
  248. package/runtime/sdk/ui/UIVideo.ts +0 -88
  249. package/runtime/sdk/ui/UIVirtualizedList.ts +0 -241
  250. package/runtime/sdk/ui/UIWidget.ts +0 -127
  251. package/runtime/sdk/ui/fonts.ts +0 -13
  252. package/runtime/sdk/ui/presentable.ts +0 -117
  253. package/runtime/sdk/ui/router.ts +0 -132
  254. package/runtime/sdk/ui/theme.ts +0 -84
package/dist/index.js CHANGED
@@ -389,7 +389,7 @@ design/comments/
389
389
  if (!existsSync2(file))
390
390
  writeFileSync3(file, DEFAULT_IGNORE_FILE);
391
391
  }, DEFAULT_GITIGNORE = `# LeCodes per-checkout state: generated IDE types + this machine's sync cursor
392
- # (.lecodes/manifest.json). Regenerate with \`lecodes types\` / re-attach with \`lecodes link\`.
392
+ # (.lecodes/manifest.json). Regenerate with \`lecodes update\` / re-attach with \`lecodes link\`.
393
393
  .lecodes/
394
394
 
395
395
  # Render / preview outputs and test failure frames
@@ -171654,10 +171654,7 @@ var init_detectEntry = __esm(() => {
171654
171654
  });
171655
171655
 
171656
171656
  // ../sdk/src/compile/shaderTargets.ts
171657
- var externalSamplerReg, PLATFORM_BUILD, LOCAL_PLATFORM_BUILD, ALL_PLATFORM_BUILD, LOCAL_ONLY_PLATFORMS, TARGET_MATRIX_GENERATION = 1, matcPlatformsFor = (text) => {
171658
- externalSamplerReg.lastIndex = 0;
171659
- return externalSamplerReg.test(text) ? ["opengl", "metal", "webgl", "desktop"] : ["opengl", "metal", "desktop"];
171660
- }, matcArgs = (platform, outPath, inPath) => {
171657
+ var externalSamplerReg, PLATFORM_BUILD, LOCAL_PLATFORM_BUILD, ALL_PLATFORM_BUILD, LOCAL_ONLY_PLATFORMS, TARGET_MATRIX_GENERATION = 1, MATC_PLATFORMS, matcArgs = (platform, outPath, inPath) => {
171661
171658
  const build = ALL_PLATFORM_BUILD[platform];
171662
171659
  return ["-p", build.profile, "--no-essl1", "-a", build.api, "-o", outPath, inPath];
171663
171660
  };
@@ -171666,7 +171663,6 @@ var init_shaderTargets = __esm(() => {
171666
171663
  PLATFORM_BUILD = {
171667
171664
  opengl: { profile: "mobile", api: "opengl", replaceExternal: false },
171668
171665
  metal: { profile: "mobile", api: "metal", replaceExternal: false },
171669
- webgl: { profile: "mobile", api: "opengl", replaceExternal: true },
171670
171666
  desktop: { profile: "desktop", api: "opengl", replaceExternal: true }
171671
171667
  };
171672
171668
  LOCAL_PLATFORM_BUILD = {
@@ -171674,6 +171670,7 @@ var init_shaderTargets = __esm(() => {
171674
171670
  };
171675
171671
  ALL_PLATFORM_BUILD = { ...PLATFORM_BUILD, ...LOCAL_PLATFORM_BUILD };
171676
171672
  LOCAL_ONLY_PLATFORMS = ["desktop-vulkan"];
171673
+ MATC_PLATFORMS = ["opengl", "metal", "desktop"];
171677
171674
  });
171678
171675
 
171679
171676
  // ../sdk/src/compile/liteMaterial.ts
@@ -172189,8 +172186,6 @@ var compileProject = async (opts) => {
172189
172186
  const name = shaders[0].src.split(/[\\/]/).pop() ?? "";
172190
172187
  const [prefix, suffix = ".filamat"] = name.split("_opengl");
172191
172188
  files[e.path] = `export default ("id:" + _creatorUtils.fetchLocal(${JSON.stringify(prefix + "_")} + _creator.backend + ${JSON.stringify(suffix)}))`;
172192
- } else if (shaders.length > 2) {
172193
- files[e.path] = `export default \`${opts.publicUrl}${shaders[0].src.replace("_opengl", "_${_creator.subBackend ?? _creator.backend}")}\``;
172194
172189
  } else if (shaders.length > 0) {
172195
172190
  files[e.path] = `export default \`${opts.publicUrl}${shaders[0].src.replace("_opengl", "_${_creator.backend}")}\``;
172196
172191
  } else {
@@ -173137,7 +173132,6 @@ __export(exports_compile, {
173137
173132
  parseSfnt: () => parseSfnt,
173138
173133
  parseJsonish: () => parseJsonish,
173139
173134
  offsetSourceMap: () => offsetSourceMap,
173140
- matcPlatformsFor: () => matcPlatformsFor,
173141
173135
  matcArgs: () => matcArgs,
173142
173136
  loadSceneHarness: () => loadSceneHarness,
173143
173137
  isServerPath: () => isServerPath,
@@ -173168,6 +173162,7 @@ __export(exports_compile, {
173168
173162
  SCENE_HARNESS_DIR: () => SCENE_HARNESS_DIR,
173169
173163
  SCENE_ENTRY_PATH: () => SCENE_ENTRY_PATH,
173170
173164
  PLATFORM_BUILD: () => PLATFORM_BUILD,
173165
+ MATC_PLATFORMS: () => MATC_PLATFORMS,
173171
173166
  LOCAL_PLATFORM_BUILD: () => LOCAL_PLATFORM_BUILD,
173172
173167
  LOCAL_ONLY_PLATFORMS: () => LOCAL_ONLY_PLATFORMS,
173173
173168
  ICON_ID: () => ICON_ID,
@@ -173198,7 +173193,13 @@ var init_compile = __esm(() => {
173198
173193
  import { existsSync as existsSync7 } from "node:fs";
173199
173194
  import { createRequire as createRequire3 } from "node:module";
173200
173195
  import { dirname as dirname5, join as join9 } from "node:path";
173201
- var vendoredInject, resolveChiselBin2 = () => {
173196
+ var resolveSdkInject = () => {
173197
+ try {
173198
+ return createRequire3(import.meta.url).resolve("lecodes-sdk/inject");
173199
+ } catch {}
173200
+ const vendored = join9(distRoot, "runtime", "sdk", "inject.ts");
173201
+ return existsSync7(vendored) ? vendored : null;
173202
+ }, sdkInject, resolveChiselBin2 = () => {
173202
173203
  if (process.env.LECODES_CHISEL_BIN)
173203
173204
  return process.env.LECODES_CHISEL_BIN;
173204
173205
  try {
@@ -173230,9 +173231,9 @@ var vendoredInject, resolveChiselBin2 = () => {
173230
173231
  var init_projectCompile = __esm(() => {
173231
173232
  init_distRoot();
173232
173233
  init_compile();
173233
- vendoredInject = join9(distRoot, "runtime", "sdk", "inject.ts");
173234
- if (existsSync7(vendoredInject))
173235
- process.env.LECODES_SDK_INJECT = vendoredInject;
173234
+ sdkInject = resolveSdkInject();
173235
+ if (sdkInject)
173236
+ process.env.LECODES_SDK_INJECT = sdkInject;
173236
173237
  chiselBin = resolveChiselBin2();
173237
173238
  if (chiselBin)
173238
173239
  process.env.LECODES_CHISEL_BIN = chiselBin;
@@ -214125,6 +214126,7 @@ var init_esm30 = __esm(() => {
214125
214126
  // src/types.ts
214126
214127
  import { dirname as dirname13, join as join22, relative as relative3, sep } from "node:path";
214127
214128
  import { existsSync as existsSync21, mkdirSync as mkdirSync15, readdirSync as readdirSync10, readFileSync as readFileSync18, rmSync as rmSync8, statSync as statSync8, writeFileSync as writeFileSync17 } from "node:fs";
214129
+ import { createRequire as createRequire4 } from "node:module";
214128
214130
  var TSCONFIG, listBundleFiles = (dir, base = dir) => {
214129
214131
  if (!existsSync21(dir))
214130
214132
  return [];
@@ -214174,8 +214176,16 @@ var TSCONFIG, listBundleFiles = (dir, base = dir) => {
214174
214176
  bundle[path2] = readFileSync18(join22(typesDir, path2), "utf8");
214175
214177
  return bundle;
214176
214178
  }, materializeTypes = async (root, apiUrl) => writeTypeBundle(root, await getTypes(apiUrl)), readLocalTypeBundle = () => {
214179
+ const fromPackage = (() => {
214180
+ try {
214181
+ return createRequire4(import.meta.url).resolve("lecodes-sdk/types.json");
214182
+ } catch {
214183
+ return null;
214184
+ }
214185
+ })();
214177
214186
  const candidates = [
214178
214187
  join22(distRoot, "runtime", "sdk-types.json"),
214188
+ ...fromPackage ? [fromPackage] : [],
214179
214189
  join22(distRoot, "..", "sdk", "dist", "types.json")
214180
214190
  ];
214181
214191
  for (const path2 of candidates) {
@@ -214358,7 +214368,7 @@ var init_browserAuth = __esm(() => {
214358
214368
  init_util();
214359
214369
  });
214360
214370
 
214361
- // ../lecodes-design/src/core/concept.ts
214371
+ // ../../../lecodes-tools/packages/lecodes-design/src/core/concept.ts
214362
214372
  var SPEC_TEMPLATE = `# Concept
214363
214373
 
214364
214374
  The narrative half of this design. \`meta.json\` + the canvas hold the *visual/flow* half (screens,
@@ -214394,7 +214404,7 @@ Behaviour that isn't visible in a single screen — validation, permissions, not
214394
214404
  cases — and the decisions you've made and why, so they aren't re-litigated later.
214395
214405
  `;
214396
214406
 
214397
- // ../lecodes-design/src/templates.ts
214407
+ // ../../../lecodes-tools/packages/lecodes-design/src/templates.ts
214398
214408
  var SPEC_MD, META_JSON = `{
214399
214409
  "screens": {
214400
214410
  "home": { "pos": [0, 0], "desc": "Example screen — replace me", "role": "list" }
@@ -216529,26 +216539,26 @@ var require_main = __commonJS((exports, module) => {
216529
216539
  // package.json
216530
216540
  var package_default = {
216531
216541
  name: "lecodes-cli",
216532
- version: "0.19.0",
216542
+ version: "0.19.2",
216533
216543
  dependencies: {
216534
216544
  "@letary/chisel": "^0.10.0",
216535
- jimp: "^1.6.1"
216545
+ jimp: "^1.6.1",
216546
+ "lecodes-sdk": "workspace:*"
216536
216547
  },
216537
216548
  devDependencies: {
216538
- "lecodes-design": "workspace:*",
216549
+ "lecodes-design": "link:lecodes-design",
216539
216550
  "lecodes-renderer": "workspace:*",
216540
216551
  "qrcode-terminal": "^0.12.0",
216541
- "lecodes-3d-editor": "workspace:*",
216552
+ "lecodes-3d-editor": "link:lecodes-3d-editor",
216542
216553
  "lecodes-assets": "workspace:*",
216543
- "creator-nav": "workspace:*",
216544
- sdk: "workspace:*",
216545
- "viewer-lite": "workspace:*"
216554
+ "lecodes-creator-nav": "workspace:*",
216555
+ "lecodes-viewer-lite": "workspace:*"
216546
216556
  },
216547
216557
  peerDependencies: {
216548
- "lecodes-design": "^0.8.1",
216549
- "lecodes-renderer": "^0.8.9",
216550
- "lecodes-3d-editor": "^0.6.0",
216551
- "lecodes-assets": "^0.1.0"
216558
+ "lecodes-design": "^0.9.0",
216559
+ "lecodes-renderer": "^0.19.2",
216560
+ "lecodes-3d-editor": "^0.7.0",
216561
+ "lecodes-assets": "^0.2.1"
216552
216562
  },
216553
216563
  bin: {
216554
216564
  lecodes: "dist/index.js"
@@ -216559,6 +216569,7 @@ var package_default = {
216559
216569
  },
216560
216570
  files: [
216561
216571
  "dist",
216572
+ "src",
216562
216573
  "runtime",
216563
216574
  "README.md"
216564
216575
  ],
@@ -216725,7 +216736,7 @@ init_fonts();
216725
216736
  init_compile();
216726
216737
  import { execFile } from "node:child_process";
216727
216738
  import { createHash as createHash4, randomBytes } from "node:crypto";
216728
- import { copyFileSync, existsSync as existsSync11, mkdirSync as mkdirSync6, readFileSync as readFileSync11, rmSync as rmSync2, statSync as statSync2, writeFileSync as writeFileSync7 } from "node:fs";
216739
+ import { existsSync as existsSync11, mkdirSync as mkdirSync6, readFileSync as readFileSync11, rmSync as rmSync2, statSync as statSync2, writeFileSync as writeFileSync7 } from "node:fs";
216729
216740
  import { tmpdir } from "node:os";
216730
216741
  import { basename as basename3, join as join13 } from "node:path";
216731
216742
 
@@ -216918,17 +216929,14 @@ var matcIdentity = (exe) => {
216918
216929
  return `${stat.size}:${Math.trunc(stat.mtimeMs)}`;
216919
216930
  };
216920
216931
  var shaderHash = (text, matcId) => createHash4("sha256").update(`${TARGET_MATRIX_GENERATION}\x00${matcId}\x00${text}`).digest("hex").slice(0, 16);
216921
- var artifactFamily = (text) => {
216922
- const matcPlatforms = [...matcPlatformsFor(text), ...LOCAL_ONLY_PLATFORMS];
216923
- return [...matcPlatforms, ...matcPlatforms.includes("webgl") ? [] : ["webgl"], "webgl-lite"];
216924
- };
216932
+ var artifactFamily = () => [...MATC_PLATFORMS, ...LOCAL_ONLY_PLATFORMS, "webgl-lite"];
216925
216933
  var shaderIsCompiled = (root, text, matcId) => {
216926
216934
  const hash = shaderHash(text, matcId);
216927
- return artifactFamily(text).every((p) => existsSync11(join13(shadersCacheDir(root), `${hash}_${p}.filamat`)));
216935
+ return artifactFamily().every((p) => existsSync11(join13(shadersCacheDir(root), `${hash}_${p}.filamat`)));
216928
216936
  };
216929
216937
  var shaderCompiledPlatforms = (root, text, matcId) => {
216930
216938
  const hash = shaderHash(text, matcId);
216931
- return artifactFamily(text).filter((p) => existsSync11(join13(shadersCacheDir(root), `${hash}_${p}.filamat`)));
216939
+ return artifactFamily().filter((p) => existsSync11(join13(shadersCacheDir(root), `${hash}_${p}.filamat`)));
216932
216940
  };
216933
216941
  var localShaderArtifact = (root, text, platform) => {
216934
216942
  const matcExe = resolveMatcExeCached();
@@ -216973,15 +216981,12 @@ var applyLocalShaders = async (entries, opts) => {
216973
216981
  for (const entry of shaderEntries) {
216974
216982
  const text = readFileSync11(entry.absPath, "utf8");
216975
216983
  const hash = shaderHash(text, matcId);
216976
- const family = artifactFamily(text);
216984
+ const family = artifactFamily();
216977
216985
  const artifact = (platform) => `${hash}_${platform}.filamat`;
216978
216986
  const has = (platform) => existsSync11(join13(cacheDir, artifact(platform)));
216979
216987
  const wanted = opts.platforms ? family.filter((p) => opts.platforms.includes(p)) : family;
216980
- if (wanted.includes("webgl") && !matcPlatformsFor(text).includes("webgl") && !wanted.includes("opengl")) {
216981
- wanted.push("opengl");
216982
- }
216983
216988
  const missing = wanted.filter((p) => !has(p));
216984
- const matcMissing = missing.filter((p) => p !== "webgl-lite" && !(p === "webgl" && !matcPlatformsFor(text).includes("webgl")));
216989
+ const matcMissing = missing.filter((p) => p !== "webgl-lite");
216985
216990
  if (matcMissing.length > 0) {
216986
216991
  const tempName = `lecodes-matc-${randomBytes(6).toString("hex")}.mat`;
216987
216992
  const tempFile = join13(tmpdir(), tempName);
@@ -217001,9 +217006,6 @@ ${cleanMatcOutput(stderr, tempName, basename3(entry.path))}`);
217001
217006
  rmSync2(tempFile, { force: true });
217002
217007
  }
217003
217008
  }
217004
- if (missing.includes("webgl") && !matcPlatformsFor(text).includes("webgl")) {
217005
- copyFileSync(join13(cacheDir, artifact("opengl")), join13(cacheDir, artifact("webgl")));
217006
- }
217007
217009
  if (missing.includes("webgl-lite")) {
217008
217010
  writeFileSync7(join13(cacheDir, artifact("webgl-lite")), JSON.stringify(buildLiteMaterialArtifact(text)));
217009
217011
  }
@@ -217018,7 +217020,7 @@ ${cleanMatcOutput(stderr, tempName, basename3(entry.path))}`);
217018
217020
  artifacts.push({ name: artifact(platform), absPath: join13(cacheDir, artifact(platform)) });
217019
217021
  }
217020
217022
  } else {
217021
- const order = ["opengl", "metal", "desktop", "webgl", "webgl-lite"];
217023
+ const order = ["opengl", "metal", "desktop", "webgl-lite"];
217022
217024
  entry.shaders = order.map((platform) => ({ platform, src: `${opts.srcPrefix}/${artifact(platform)}` }));
217023
217025
  }
217024
217026
  }
@@ -218630,6 +218632,12 @@ Sync links the smallest SDK artifact covering the engines the compiled bundle ac
218630
218632
  engine, sync switches automatically on the next run; set \`android.variant\` in \`../app.json\`
218631
218633
  to force one (needs lecodes-android-sdk >= 1.3.0; older releases ship only the full AAR).
218632
218634
 
218635
+ Physics, navigation meshes and multiplayer ship in \`lecodes-sdk\` (full) ONLY —
218636
+ \`lecodes-sdk-3d\` is 3D rendering without the game stack. Auto-selection reads which RENDERING
218637
+ engines the bundle uses and cannot see those, so a project using \`Physics\`,
218638
+ \`CharacterController\`, \`NavMesh\` or networking must set \`android.variant: "full"\` in
218639
+ \`../app.json\` — otherwise those aspects silently do nothing on device.
218640
+
218633
218641
  ## App-local native code
218634
218642
 
218635
218643
  Drop Kotlin files into \`app/src/main/java/\` and register them in \`MainActivity\` after the
@@ -219753,7 +219761,7 @@ import { basename as basename7, dirname as dirname11, join as join20, resolve as
219753
219761
 
219754
219762
  // src/desktopRenderer.ts
219755
219763
  import { execFileSync as execFileSync3, spawn as spawn2, spawnSync as spawnSync4 } from "node:child_process";
219756
- import { copyFileSync as copyFileSync2, existsSync as existsSync17, mkdirSync as mkdirSync11, mkdtempSync as mkdtempSync2, readdirSync as readdirSync6, readFileSync as readFileSync16, rmSync as rmSync4, statSync as statSync4, writeFileSync as writeFileSync13 } from "node:fs";
219764
+ import { copyFileSync, existsSync as existsSync17, mkdirSync as mkdirSync11, mkdtempSync as mkdtempSync2, readdirSync as readdirSync6, readFileSync as readFileSync16, rmSync as rmSync4, statSync as statSync4, writeFileSync as writeFileSync13 } from "node:fs";
219757
219765
  import { homedir as homedir6, tmpdir as tmpdir2 } from "node:os";
219758
219766
  import { join as join18, resolve as resolve5 } from "node:path";
219759
219767
  init_util();
@@ -220083,7 +220091,7 @@ var stageBundle = (js, resources) => {
220083
220091
  writeFileSync13(join18(runDir, "app.js"), js);
220084
220092
  for (const r2 of resources) {
220085
220093
  if (r2.absPath && existsSync17(r2.absPath) && statSync4(r2.absPath).isFile()) {
220086
- copyFileSync2(r2.absPath, join18(runDir, r2.name));
220094
+ copyFileSync(r2.absPath, join18(runDir, r2.name));
220087
220095
  }
220088
220096
  }
220089
220097
  return runDir;
@@ -220126,7 +220134,16 @@ var runDesktopApp = async (js, resources, opts = {}) => {
220126
220134
  });
220127
220135
  });
220128
220136
  } finally {
220129
- rmSync4(runDir, { recursive: true, force: true });
220137
+ for (let attempt = 0;; attempt++) {
220138
+ try {
220139
+ rmSync4(runDir, { recursive: true, force: true });
220140
+ break;
220141
+ } catch {
220142
+ if (attempt >= 5)
220143
+ break;
220144
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 200);
220145
+ }
220146
+ }
220130
220147
  }
220131
220148
  };
220132
220149
  var runDesktopDev = async (url, opts = {}) => {
@@ -220204,7 +220221,16 @@ ${tail}` : "";
220204
220221
  });
220205
220222
  });
220206
220223
  } finally {
220207
- rmSync4(runDir, { recursive: true, force: true });
220224
+ for (let attempt = 0;; attempt++) {
220225
+ try {
220226
+ rmSync4(runDir, { recursive: true, force: true });
220227
+ break;
220228
+ } catch {
220229
+ if (attempt >= 5)
220230
+ break;
220231
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 200);
220232
+ }
220233
+ }
220208
220234
  }
220209
220235
  };
220210
220236
 
@@ -220214,7 +220240,7 @@ init_util();
220214
220240
  // src/commands/appDesktopMac.ts
220215
220241
  init_util();
220216
220242
  import { execFileSync as execFileSync4, spawn as spawn3, spawnSync as spawnSync5 } from "node:child_process";
220217
- import { cpSync as cpSync2, existsSync as existsSync18, mkdirSync as mkdirSync12, mkdtempSync as mkdtempSync3, readdirSync as readdirSync7, rmSync as rmSync5, statSync as statSync5, writeFileSync as writeFileSync14 } from "node:fs";
220243
+ import { cpSync as cpSync2, existsSync as existsSync18, mkdirSync as mkdirSync12, mkdtempSync as mkdtempSync3, readdirSync as readdirSync7, rmSync as rmSync5, statSync as statSync5, symlinkSync, writeFileSync as writeFileSync14 } from "node:fs";
220218
220244
  import { tmpdir as tmpdir3 } from "node:os";
220219
220245
  import { basename as basename6, join as join19, resolve as resolve6 } from "node:path";
220220
220246
  var mb = (bytes) => `${(bytes / 1024 / 1024).toFixed(1)} MB`;
@@ -220336,11 +220362,12 @@ var signApp = async (app, signing, hostApp, tmp) => {
220336
220362
  throw new CliError(`The signature doesn't verify:
220337
220363
  ${(verify.stdout + verify.stderr).trim()}`);
220338
220364
  };
220339
- var notarizeApp = (app, profile, tmp) => {
220340
- const zip = join19(tmp, `${basename6(app, ".app")}.zip`);
220341
- execFileSync4("ditto", ["-c", "-k", "--keepParent", app, zip]);
220342
- note(`Notarizing with profile "${profile}" — this waits on Apple (usually 1–5 minutes)…`);
220343
- const r2 = spawnSync5("xcrun", ["notarytool", "submit", zip, "--keychain-profile", profile, "--wait"], { encoding: "utf8" });
220365
+ var notarize = (target, profile, tmp, what) => {
220366
+ const submission = target.endsWith(".app") ? join19(tmp, `${basename6(target, ".app")}.zip`) : target;
220367
+ if (submission !== target)
220368
+ execFileSync4("ditto", ["-c", "-k", "--keepParent", target, submission]);
220369
+ note(`Notarizing the ${what} with profile "${profile}" — this waits on Apple (usually 1–5 minutes)…`);
220370
+ const r2 = spawnSync5("xcrun", ["notarytool", "submit", submission, "--keychain-profile", profile, "--wait"], { encoding: "utf8" });
220344
220371
  const out = (r2.stdout + `
220345
220372
  ` + r2.stderr).trim();
220346
220373
  const id = /id: ([0-9a-f-]{36})/.exec(out)?.[1];
@@ -220349,11 +220376,44 @@ var notarizeApp = (app, profile, tmp) => {
220349
220376
  ${out}
220350
220377
  ` + (id ? ` Details: xcrun notarytool log ${id} --keychain-profile ${profile}` : ""));
220351
220378
  }
220352
- const staple = spawnSync5("xcrun", ["stapler", "staple", app], { encoding: "utf8" });
220379
+ const staple = spawnSync5("xcrun", ["stapler", "staple", target], { encoding: "utf8" });
220353
220380
  if (staple.status !== 0)
220354
220381
  throw new CliError(`Stapling failed:
220355
220382
  ${(staple.stdout + staple.stderr).trim()}`);
220356
- success("Notarized + stapled.");
220383
+ success(`Notarized + stapled (${what}).`);
220384
+ };
220385
+ var buildDmg = async (app, out, appName, signing, tmp) => {
220386
+ const dmg = join19(out, `${appName}.dmg`);
220387
+ rmSync5(dmg, { force: true });
220388
+ const stage = join19(tmp, "dmg");
220389
+ rmSync5(stage, { recursive: true, force: true });
220390
+ mkdirSync12(stage, { recursive: true });
220391
+ note("Building the disk image…");
220392
+ execFileSync4("cp", ["-R", app, join19(stage, basename6(app))]);
220393
+ symlinkSync("/Applications", join19(stage, "Applications"));
220394
+ const r2 = spawnSync5("hdiutil", [
220395
+ "create",
220396
+ "-volname",
220397
+ appName,
220398
+ "-srcfolder",
220399
+ stage,
220400
+ "-fs",
220401
+ "HFS+",
220402
+ "-format",
220403
+ "UDZO",
220404
+ "-ov",
220405
+ dmg
220406
+ ], { encoding: "utf8" });
220407
+ rmSync5(stage, { recursive: true, force: true });
220408
+ if (r2.status !== 0)
220409
+ throw new CliError(`hdiutil failed:
220410
+ ${(r2.stdout + r2.stderr).trim()}`);
220411
+ if (signing.identity !== "-") {
220412
+ await codesignRetrying(["--force", "--timestamp", "--sign", signing.identity, dmg]);
220413
+ if (signing.notaryProfile)
220414
+ notarize(dmg, signing.notaryProfile, tmp, "disk image");
220415
+ }
220416
+ success(`DMG: ${dmg} (${mb(statSync5(dmg).size)})`);
220357
220417
  };
220358
220418
  var gatekeeperVerdict = (app) => {
220359
220419
  const r2 = spawnSync5("spctl", ["-a", "-t", "exec", "-vv", app], { encoding: "utf8" });
@@ -220487,7 +220547,7 @@ var buildMacApp = async (input) => {
220487
220547
  }, developerIdInKeychain);
220488
220548
  await signApp(app, signing, hostApp, tmp);
220489
220549
  if (signing.notaryProfile) {
220490
- notarizeApp(app, signing.notaryProfile, tmp);
220550
+ notarize(app, signing.notaryProfile, tmp, "app");
220491
220551
  } else if (signing.identity !== "-") {
220492
220552
  warn("Signed with Developer ID but NOT notarized — Gatekeeper refuses a downloaded copy on every other Mac.");
220493
220553
  warn(" Set desktop.macos.notarize.profile in app.json (a `xcrun notarytool store-credentials` profile) or pass --notarize <profile>.");
@@ -220499,6 +220559,8 @@ var buildMacApp = async (input) => {
220499
220559
  }
220500
220560
  writeFileSync14(join19(out, "README.txt"), readmeText(appName, !!signing.notaryProfile, signing.identity !== "-"));
220501
220561
  success(`App: ${app} (${mb(dirSize(app))})`);
220562
+ if (flagBool(args, "dmg"))
220563
+ await buildDmg(app, out, appName, signing, tmp);
220502
220564
  if (flagBool(args, "probe"))
220503
220565
  await probeMacApp(app, out, args);
220504
220566
  if (flagBool(args, "zip")) {
@@ -220506,8 +220568,8 @@ var buildMacApp = async (input) => {
220506
220568
  rmSync5(zip, { force: true });
220507
220569
  execFileSync4("ditto", ["-c", "-k", "--keepParent", app, zip]);
220508
220570
  success(`Zip: ${zip} (${mb(statSync5(zip).size)})`);
220509
- } else {
220510
- note("Tip: --zip packs the .app for sending (ditto, structure-preserving).");
220571
+ } else if (!flagBool(args, "dmg")) {
220572
+ note("Tip: --zip packs the .app for sending; --dmg wraps it in a drag-to-Applications image.");
220511
220573
  }
220512
220574
  } finally {
220513
220575
  rmSync5(tmp, { recursive: true, force: true });
@@ -220608,6 +220670,8 @@ var buildDesktop = async (args) => {
220608
220670
  }
220609
220671
  if (mac)
220610
220672
  return buildMacApp({ root, config, name, slug, out, js, shipped, hostExe: exe, args });
220673
+ if (flagBool(args, "dmg"))
220674
+ warn("--dmg is a macOS format — ignored on this platform.");
220611
220675
  const version = process.env.LECODES_DESKTOP_EXE ? "local build" : selectedDesktopVersion();
220612
220676
  rmSync6(out, { recursive: true, force: true });
220613
220677
  mkdirSync13(out, { recursive: true });
@@ -221660,7 +221724,7 @@ channel for game state that has no UI.
221660
221724
  ## SDK documentation
221661
221725
 
221662
221726
  \`.lecodes/types/\` is the exact API surface (signatures only; generated and gitignored — run
221663
- \`lecodes types\` if it's missing, never commit \`.lecodes/\`). For semantics and examples,
221727
+ \`lecodes update\` if it's missing, never commit \`.lecodes/\`). For semantics and examples,
221664
221728
  fetch the docs — they are served as raw markdown for AI tools:
221665
221729
 
221666
221730
  - https://le.codes/llms.txt — the docs map; fetch it first to find the right page
@@ -222777,9 +222841,9 @@ lecodes app open # open it in Xcode, build & run
222777
222841
  - \`CLAUDE.md\` — guidance for AI assistants (the headless feedback loop + testability conventions)
222778
222842
  - \`assets/\` — images/fonts the app references (created on demand)
222779
222843
  - \`tsconfig.json\` + \`.lecodes/types/\` — generated IDE types (rewritten by the CLI; don't edit).
222780
- They come from the SDK bundled in the CLI; \`lecodes types\` refreshes them from the platform.
222844
+ They come from the SDK bundled in the CLI; \`lecodes update\` refreshes them from the platform.
222781
222845
  - \`.lecodes/\` is gitignored — per-checkout state (those types + \`manifest.json\`, this machine's
222782
- sync cursor once linked). After a fresh git clone: \`lecodes types\`, then
222846
+ sync cursor once linked). After a fresh git clone: \`lecodes update\`, then
222783
222847
  \`lecodes link --project <uuid>\` to re-attach.
222784
222848
  - \`.lecodesignore\` — files a future \`lecodes push\` would skip
222785
222849
 
@@ -223093,8 +223157,8 @@ var link = async (args) => {
223093
223157
 
223094
223158
  // src/commands/lightmap.ts
223095
223159
  init_manifest();
223096
- import { existsSync as existsSync28, mkdirSync as mkdirSync21, mkdtempSync as mkdtempSync4, rmSync as rmSync9, statSync as statSync10, unlinkSync, writeFileSync as writeFileSync22 } from "node:fs";
223097
- import { spawnSync as spawnSync6 } from "node:child_process";
223160
+ import { existsSync as existsSync28, mkdirSync as mkdirSync21, mkdtempSync as mkdtempSync4, readFileSync as readFileSync25, renameSync as renameSync3, rmSync as rmSync9, statSync as statSync10, unlinkSync, writeFileSync as writeFileSync22 } from "node:fs";
223161
+ import { spawn as spawn6, spawnSync as spawnSync6 } from "node:child_process";
223098
223162
  import { tmpdir as tmpdir4 } from "node:os";
223099
223163
  import { join as join30, relative as relative4, resolve as resolve13 } from "node:path";
223100
223164
  init_util();
@@ -223136,14 +223200,26 @@ var requireToktx = () => {
223136
223200
  }
223137
223201
  return toktx;
223138
223202
  };
223139
- var encodeKtx2 = (toktx, png2, ktx2) => {
223203
+ var encodeKtx2 = (toktx, png2, ktx2) => new Promise((resolve14, reject) => {
223140
223204
  const args = ["--t2", "--encode", "uastc", "--uastc_quality", "2", "--zcmp", "18", "--genmipmap", "--assign_oetf", "srgb", ktx2, png2];
223141
- const p2 = spawnSync6(toktx, args, { encoding: "utf8" });
223142
- if (p2.status !== 0) {
223143
- throw new CliError(`toktx failed (${p2.status ?? p2.error?.message ?? "?"}) while encoding ${ktx2}:
223144
- ${(p2.stderr || p2.stdout || "").trim()}`);
223145
- }
223146
- };
223205
+ const p2 = spawn6(toktx, args, { stdio: ["ignore", "pipe", "pipe"] });
223206
+ let out = "";
223207
+ p2.stdout.on("data", (d) => {
223208
+ out += String(d);
223209
+ });
223210
+ p2.stderr.on("data", (d) => {
223211
+ out += String(d);
223212
+ });
223213
+ p2.on("error", (e2) => reject(new CliError(`toktx failed (${e2.message}) while encoding ${ktx2}`)));
223214
+ p2.on("close", (status2) => {
223215
+ if (status2 === 0)
223216
+ resolve14();
223217
+ else
223218
+ reject(new CliError(`toktx failed (${status2 ?? "?"}) while encoding ${ktx2}:
223219
+ ${out.trim()}`));
223220
+ });
223221
+ });
223222
+ var pageFile = (stem, page, light, ext2 = ".ktx2") => `${stem}${light ? "-light" : ""}${page > 0 ? `_${page}` : ""}${ext2}`;
223147
223223
  var bake = async (args) => {
223148
223224
  let root;
223149
223225
  try {
@@ -223153,6 +223229,9 @@ var bake = async (args) => {
223153
223229
  }
223154
223230
  const toktx = requireToktx();
223155
223231
  const outDir = resolve13(root, flagStr(args, "out") ?? "assets/lightmap");
223232
+ const stem = flagStr(args, "name") ?? "lightmap";
223233
+ if (!/^[A-Za-z0-9_-]+$/.test(stem))
223234
+ throw new CliError(`--name wants a plain file stem (letters, digits, - and _), got "${stem}".`);
223156
223235
  const entryFlag = flagStr(args, "entry");
223157
223236
  const entryFile = entryFlag ?? "main.ts";
223158
223237
  if (!existsSync28(resolve13(root, entryFile))) {
@@ -223162,11 +223241,19 @@ var bake = async (args) => {
223162
223241
  outDir: outDir.replace(/\\/g, "/"),
223163
223242
  size: num(args, "size", 4096, 256, 16384),
223164
223243
  texel: num(args, "texel", 0.02, 0.001, 2),
223244
+ pages: num(args, "pages", 4, 1, 16),
223165
223245
  sunRays: num(args, "sun-rays", 32, 1, 1024),
223166
223246
  aoRays: num(args, "ao-rays", 64, 1, 4096),
223167
223247
  aoDistance: num(args, "ao-distance", 2, 0.01, 1000),
223168
223248
  bias: num(args, "bias", 0.02, 0.0001, 1),
223169
223249
  sunAngle: num(args, "sun-angle", 0.5, 0.01, 30),
223250
+ lightRays: num(args, "light-rays", 16, 1, 256),
223251
+ lightRadius: num(args, "light-radius", 0.1, 0.001, 5),
223252
+ bounce: num(args, "bounce", 0, 0, 1),
223253
+ bounceDistance: num(args, "bounce-distance", 8, 0.5, 100),
223254
+ bounceRays: num(args, "bounce-rays", 1, 1, 32),
223255
+ coplanarSkip: num(args, "coplanar-skip", 0.05, 0, 1),
223256
+ denoise: num(args, "denoise", 2, 0, 4),
223170
223257
  volumeCell: flagBool(args, "no-volume") ? 0 : num(args, "cell", 0.5, 0.05, 20)
223171
223258
  };
223172
223259
  const frames = num(args, "frames", 900, 30, 1e5);
@@ -223180,7 +223267,7 @@ var bake = async (args) => {
223180
223267
  import ${JSON.stringify(importPath)}
223181
223268
  `;
223182
223269
  mkdirSync21(outDir, { recursive: true });
223183
- ensurePlaceholders(outDir);
223270
+ ensurePlaceholders(outDir, stem);
223184
223271
  const desktopRenderer = resolveDesktopRenderer(root, args);
223185
223272
  const { js, resources } = await buildBundle(args, undefined, {
223186
223273
  localAssets: true,
@@ -223190,10 +223277,10 @@ import ${JSON.stringify(importPath)}
223190
223277
  });
223191
223278
  const scratch = mkdtempSync4(join30(tmpdir4(), "lecodes-lightmap-"));
223192
223279
  const outPng = join30(scratch, "frame.png");
223193
- note(`Baking ${cfg.size}² at ${(cfg.texel * 100).toFixed(1)} cm texels (${cfg.sunRays} sun + ${cfg.aoRays} AO rays)${cfg.volumeCell ? ` + a ${cfg.volumeCell} m light volume` : ""} → ${relative4(root, outDir) || "."}…`);
223280
+ note(`Baking up to ${cfg.pages} × ${cfg.size}² at ${(cfg.texel * 100).toFixed(1)} cm texels (${cfg.sunRays} sun + ${cfg.aoRays} AO + ${cfg.lightRays} light rays)${cfg.volumeCell ? ` + a ${cfg.volumeCell} m light volume` : ""} → ${relative4(root, outDir) || "."}/${stem}.*…`);
223194
223281
  let output = "";
223195
223282
  try {
223196
- const timeoutMs = Number(flagStr(args, "timeout")) || 600000;
223283
+ const timeoutMs = Number(flagStr(args, "timeout")) || 24 * 3600 * 1000;
223197
223284
  const res = await runDesktopRender(js, resources, {
223198
223285
  renderer: desktopRenderer,
223199
223286
  width,
@@ -223211,28 +223298,76 @@ import ${JSON.stringify(importPath)}
223211
223298
  throw e2;
223212
223299
  output = msg;
223213
223300
  } finally {
223214
- rmSync9(scratch, { recursive: true, force: true });
223301
+ try {
223302
+ rmSync9(scratch, { recursive: true, force: true });
223303
+ } catch {}
223215
223304
  }
223216
223305
  const lines = output.split(/\r?\n/).filter((l2) => l2.startsWith("[lightmap]"));
223217
223306
  for (const l2 of lines) {
223218
223307
  const text = l2.slice("[lightmap]".length).trim();
223219
223308
  if (text === "done")
223220
223309
  continue;
223221
- logErr(text.startsWith("error") ? c.red(`✗ ${text}`) : c.dim(` ${text}`));
223310
+ logErr(text.startsWith("error") ? c.red(`✗ ${text}`) : text.startsWith("warning") ? c.yellow(` ${text}`) : c.dim(` ${text}`));
223222
223311
  }
223223
223312
  if (/\[lightmap\] done/.test(output)) {
223224
- const png2 = join30(outDir, "lightmap.png");
223225
- const ktx2 = join30(outDir, "lightmap.ktx2");
223226
- if (!existsSync28(png2))
223227
- throw new CliError(`The host reported a bake but ${png2} is missing.`);
223313
+ const bakePath = join30(outDir, "lightmap.bake");
223314
+ if (!existsSync28(bakePath))
223315
+ throw new CliError(`The host reported a bake but ${bakePath} is missing.`);
223316
+ const data = JSON.parse(readFileSync25(bakePath, "utf8"));
223317
+ const pages = Math.max(1, data.pages ?? 1);
223318
+ const hasLight = !!data.lightTextures?.length;
223228
223319
  const t0 = Date.now();
223229
- encodeKtx2(toktx, png2, ktx2);
223230
- const pngKb = Math.round(statSync10(png2).size / 1024), ktxKb = Math.round(statSync10(ktx2).size / 1024);
223231
- unlinkSync(png2);
223232
- logErr(c.dim(` encoded lightmap.ktx2 (UASTC + mips, ${ktxKb} KB from a ${pngKb} KB raster) in ${((Date.now() - t0) / 1000).toFixed(1)} s`));
223233
- const hasVolume = cfg.volumeCell > 0 && existsSync28(join30(outDir, "lightmap.volume"));
223234
- logErr(c.green(`✓ baked → ${relative4(root, outDir) || "."}/lightmap.ktx2 + lightmap.bake${hasVolume ? " + lightmap.volume" : ""}`));
223235
- logErr(c.dim(` apply it with Lightmap.load(scene, { data: asset('<rel>/${relative4(root, join30(outDir, "lightmap.bake")).replace(/\\/g, "/")}'), texture: asset('<rel>/${relative4(root, ktx2).replace(/\\/g, "/")}')${hasVolume ? `, volume: asset('<rel>/${relative4(root, join30(outDir, "lightmap.volume")).replace(/\\/g, "/")}')` : ""} }) — asset() paths are relative to the calling file`));
223320
+ let pngKb = 0, ktxKb = 0;
223321
+ const encodePage = async (page, light) => {
223322
+ const png2 = join30(outDir, pageFile("lightmap", page, light, ".png"));
223323
+ const ktx2 = join30(outDir, pageFile(stem, page, light));
223324
+ if (!existsSync28(png2))
223325
+ throw new CliError(`The host reported a bake but ${png2} is missing.`);
223326
+ await encodeKtx2(toktx, png2, ktx2);
223327
+ pngKb += statSync10(png2).size / 1024;
223328
+ ktxKb += statSync10(ktx2).size / 1024;
223329
+ unlinkSync(png2);
223330
+ };
223331
+ const encodes = [];
223332
+ for (let p2 = 0;p2 < pages; p2++) {
223333
+ encodes.push(encodePage(p2, false));
223334
+ if (hasLight)
223335
+ encodes.push(encodePage(p2, true));
223336
+ }
223337
+ await Promise.all(encodes);
223338
+ for (let p2 = pages;p2 < 16; p2++) {
223339
+ for (const light of [false, true]) {
223340
+ const stale = join30(outDir, pageFile(stem, p2, light));
223341
+ if (existsSync28(stale) && statSync10(stale).size !== PLACEHOLDER_KTX2.length)
223342
+ writeFileSync22(stale, PLACEHOLDER_KTX2);
223343
+ }
223344
+ }
223345
+ if (!hasLight) {
223346
+ const light = join30(outDir, pageFile(stem, 0, true));
223347
+ if (existsSync28(light) && statSync10(light).size !== PLACEHOLDER_KTX2.length)
223348
+ writeFileSync22(light, PLACEHOLDER_KTX2);
223349
+ }
223350
+ data.texture = pageFile(stem, 0, false);
223351
+ data.textures = Array.from({ length: pages }, (_, p2) => pageFile(stem, p2, false));
223352
+ if (hasLight)
223353
+ data.lightTextures = Array.from({ length: pages }, (_, p2) => pageFile(stem, p2, true));
223354
+ if (data.volume)
223355
+ data.volume.file = `${stem}.volume`;
223356
+ writeFileSync22(join30(outDir, `${stem}.bake`), JSON.stringify(data, null, 1) + `
223357
+ `);
223358
+ if (stem !== "lightmap")
223359
+ unlinkSync(bakePath);
223360
+ const volumeSrc = join30(outDir, "lightmap.volume");
223361
+ const hasVolume = cfg.volumeCell > 0 && existsSync28(volumeSrc);
223362
+ if (hasVolume && stem !== "lightmap") {
223363
+ rmSync9(join30(outDir, `${stem}.volume`), { force: true });
223364
+ renameSync3(volumeSrc, join30(outDir, `${stem}.volume`));
223365
+ }
223366
+ logErr(c.dim(` encoded ${pages} page(s)${hasLight ? " + the light atlas" : ""} (UASTC + mips, ${Math.round(ktxKb)} KB from ${Math.round(pngKb)} KB of rasters) in ${((Date.now() - t0) / 1000).toFixed(1)} s`));
223367
+ logErr(c.green(`✓ baked → ${relative4(root, outDir) || "."}/${stem}.bake + ${data.textures.join(", ")}${hasLight ? ` + ${data.lightTextures.join(", ")}` : ""}${hasVolume ? ` + ${stem}.volume` : ""}`));
223368
+ const rel = (f3) => relative4(root, join30(outDir, f3)).replace(/\\/g, "/");
223369
+ const assets = (files) => files.length === 1 ? `asset('<rel>/${rel(files[0])}')` : `[${files.map((f3) => `asset('<rel>/${rel(f3)}')`).join(", ")}]`;
223370
+ logErr(c.dim(` scene file: env.lightmap: { data: asset('<rel>/${rel(`${stem}.bake`)}'), texture: ${assets(data.textures)}${hasLight ? `, light: ${assets(data.lightTextures)}` : ""}${hasVolume ? `, volume: asset('<rel>/${rel(`${stem}.volume`)}')` : ""} } — asset() paths are relative to the scene file`));
223236
223371
  return;
223237
223372
  }
223238
223373
  if (lines.some((l2) => /no bake module/.test(l2))) {
@@ -223259,28 +223394,32 @@ var placeholderVolume = () => {
223259
223394
  b[37] = 255;
223260
223395
  return b;
223261
223396
  };
223262
- var ensurePlaceholders = (outDir) => {
223263
- const bake2 = join30(outDir, "lightmap.bake");
223264
- const ktx2 = join30(outDir, "lightmap.ktx2");
223265
- const volume = join30(outDir, "lightmap.volume");
223266
- if (!existsSync28(bake2))
223267
- writeFileSync22(bake2, JSON.stringify({ version: 1, placeholder: true, size: 1, texel: 0, instances: [] }) + `
223397
+ var ensurePlaceholders = (outDir, stem) => {
223398
+ const missing = (f3) => !existsSync28(f3) || statSync10(f3).size === 0;
223399
+ const bake2 = join30(outDir, `${stem}.bake`);
223400
+ const ktx2 = join30(outDir, pageFile(stem, 0, false));
223401
+ const light = join30(outDir, pageFile(stem, 0, true));
223402
+ const volume = join30(outDir, `${stem}.volume`);
223403
+ if (missing(bake2))
223404
+ writeFileSync22(bake2, JSON.stringify({ version: 2, placeholder: true, size: 1, texel: 0, pages: 1, instances: [] }) + `
223268
223405
  `);
223269
- if (!existsSync28(ktx2))
223406
+ if (missing(ktx2))
223270
223407
  writeFileSync22(ktx2, PLACEHOLDER_KTX2);
223271
- if (!existsSync28(volume))
223408
+ if (missing(light))
223409
+ writeFileSync22(light, PLACEHOLDER_KTX2);
223410
+ if (missing(volume))
223272
223411
  writeFileSync22(volume, placeholderVolume());
223273
223412
  };
223274
223413
  var lightmap = async (args) => {
223275
223414
  const sub = args._[0];
223276
223415
  if (sub === "bake")
223277
223416
  return bake({ ...args, _: args._.slice(1) });
223278
- throw new CliError("Usage: lecodes lightmap bake [--out assets/lightmap] [--size 4096] [--texel 0.02] [--sun-rays 32] [--ao-rays 64] [--ao-distance 2] [--bias 0.02] [--sun-angle 0.5] [--cell 0.5 | --no-volume] [--frames 900] [--entry main.ts] [--logs] (needs KTX-Software's toktx)");
223417
+ throw new CliError("Usage: lecodes lightmap bake [--out assets/lightmap] [--name lightmap] [--size 4096] [--pages 4] [--texel 0.02] [--sun-rays 32] [--ao-rays 64] [--ao-distance 2] [--bias 0.02] [--sun-angle 0.5] [--light-rays 16] [--light-radius 0.1] [--bounce 0..1] [--denoise 2] [--cell 0.5 | --no-volume] [--frames 900] [--timeout <ms>] [--entry main.ts] [--logs] (needs KTX-Software's toktx)");
223279
223418
  };
223280
223419
 
223281
223420
  // src/commands/navmesh.ts
223282
223421
  init_manifest();
223283
- import { existsSync as existsSync29, mkdirSync as mkdirSync22, mkdtempSync as mkdtempSync5, readdirSync as readdirSync14, readFileSync as readFileSync25, rmSync as rmSync10, statSync as statSync11, unlinkSync as unlinkSync2, writeFileSync as writeFileSync23 } from "node:fs";
223422
+ import { existsSync as existsSync29, mkdirSync as mkdirSync22, mkdtempSync as mkdtempSync5, readdirSync as readdirSync14, readFileSync as readFileSync26, rmSync as rmSync10, statSync as statSync11, unlinkSync as unlinkSync2, writeFileSync as writeFileSync23 } from "node:fs";
223284
223423
  import { tmpdir as tmpdir5 } from "node:os";
223285
223424
  import { basename as basename16, join as join31, relative as relative5, resolve as resolve14 } from "node:path";
223286
223425
  init_util();
@@ -223299,8 +223438,8 @@ var createCreatorNav = (() => {
223299
223438
  var ENVIRONMENT_IS_WORKER = typeof WorkerGlobalScope != "undefined";
223300
223439
  var ENVIRONMENT_IS_NODE = typeof process == "object" && process.versions?.node && process.type != "renderer";
223301
223440
  if (ENVIRONMENT_IS_NODE) {
223302
- const { createRequire: createRequire4 } = await import("module");
223303
- var require2 = createRequire4(import.meta.url);
223441
+ const { createRequire: createRequire5 } = await import("module");
223442
+ var require2 = createRequire5(import.meta.url);
223304
223443
  }
223305
223444
  var arguments_ = [];
223306
223445
  var thisProgram = "./this.program";
@@ -224093,7 +224232,7 @@ var guessName = (root) => {
224093
224232
  if (!f3.endsWith(".scene.ts"))
224094
224233
  continue;
224095
224234
  try {
224096
- if (/\bnavmesh\s*:/.test(readFileSync25(join31(dir, f3), "utf8")))
224235
+ if (/\bnavmesh\s*:/.test(readFileSync26(join31(dir, f3), "utf8")))
224097
224236
  return f3.slice(0, -".scene.ts".length);
224098
224237
  } catch {}
224099
224238
  }
@@ -224221,7 +224360,7 @@ import ${JSON.stringify(importPath)}
224221
224360
  const geoPath = join31(outDir, `${name}.navgeo`);
224222
224361
  if (!existsSync29(geoPath))
224223
224362
  throw new CliError(`The host reported a dump but ${geoPath} is missing.`);
224224
- const geo = readNgeo(new Uint8Array(readFileSync25(geoPath)));
224363
+ const geo = readNgeo(new Uint8Array(readFileSync26(geoPath)));
224225
224364
  const params = {
224226
224365
  agentRadius: num2(args, "radius", config.agent.radius, 0.05, 10),
224227
224366
  agentHeight: num2(args, "height", config.agent.height, 0.1, 20),
@@ -224307,7 +224446,7 @@ var login = async (args) => {
224307
224446
 
224308
224447
  // src/commands/shaders.ts
224309
224448
  init_manifest();
224310
- import { readFileSync as readFileSync26 } from "node:fs";
224449
+ import { readFileSync as readFileSync27 } from "node:fs";
224311
224450
  init_util();
224312
224451
  init_compile();
224313
224452
  var collectShaders = () => {
@@ -224339,7 +224478,7 @@ var status2 = async () => {
224339
224478
  if (matcId === null)
224340
224479
  state = c.dim("unknown (matc not downloaded)");
224341
224480
  else {
224342
- const text = readFileSync26(shader.absPath, "utf8");
224481
+ const text = readFileSync27(shader.absPath, "utf8");
224343
224482
  if (shaderIsCompiled(root, text, matcId))
224344
224483
  state = "compiled";
224345
224484
  else {
@@ -224348,7 +224487,7 @@ var status2 = async () => {
224348
224487
  }
224349
224488
  }
224350
224489
  log(` ${shader.path.padEnd(width)} ${state}`);
224351
- const schema = parseShaderSchema(readFileSync26(shader.absPath, "utf8"));
224490
+ const schema = parseShaderSchema(readFileSync27(shader.absPath, "utf8"));
224352
224491
  for (const p2 of schema.params) {
224353
224492
  const range = p2.min !== undefined ? ` [${p2.min}..${p2.max}]` : "";
224354
224493
  const def = p2.default !== null && p2.default !== undefined ? ` = ${JSON.stringify(p2.default)}` : "";
@@ -224391,7 +224530,7 @@ var compile3 = async (args) => {
224391
224530
  }
224392
224531
  const matcExe = await resolveMatcExe();
224393
224532
  const matcId = matcIdentity(matcExe);
224394
- const wasCompiled = new Map(shaders.map((s2) => [s2.path, shaderIsCompiled(root, readFileSync26(s2.absPath, "utf8"), matcId)]));
224533
+ const wasCompiled = new Map(shaders.map((s2) => [s2.path, shaderIsCompiled(root, readFileSync27(s2.absPath, "utf8"), matcId)]));
224395
224534
  const errors2 = [];
224396
224535
  for (const shader of shaders) {
224397
224536
  try {
@@ -224425,7 +224564,7 @@ var shaders = async (args) => {
224425
224564
  };
224426
224565
 
224427
224566
  // src/commands/render.ts
224428
- import { mkdirSync as mkdirSync23, readFileSync as readFileSync28, writeFileSync as writeFileSync24 } from "node:fs";
224567
+ import { mkdirSync as mkdirSync23, readFileSync as readFileSync29, writeFileSync as writeFileSync24 } from "node:fs";
224429
224568
  import { dirname as dirname15, resolve as resolve17 } from "node:path";
224430
224569
 
224431
224570
  // src/compile/headlessBundle.ts
@@ -224510,7 +224649,7 @@ var compileHeadlessBundle = async (args, opts = {}) => {
224510
224649
  // src/compile/screenEntry.ts
224511
224650
  init_designMeta();
224512
224651
  init_util();
224513
- import { existsSync as existsSync30, readFileSync as readFileSync27, statSync as statSync12 } from "node:fs";
224652
+ import { existsSync as existsSync30, readFileSync as readFileSync28, statSync as statSync12 } from "node:fs";
224514
224653
  import { isAbsolute, join as join32, relative as relative6, resolve as resolve15 } from "node:path";
224515
224654
  var SCREEN_EXTS = [".ts", ".tsx", ".js", ".jsx", ".mjs"];
224516
224655
  var SCREEN_ENTRY_PATH = "/.screen-entry.ts";
@@ -224545,7 +224684,7 @@ var resolveScreenTarget = (root, file, opts = {}) => {
224545
224684
  throw new CliError(`"${file}" is outside the project (${root}) — a screen renders as part of its project, so its ` + `imports and asset() files resolve from the same tree.`);
224546
224685
  }
224547
224686
  const key = toKey(rel);
224548
- if (!/^\s*export\s+default\b/m.test(readFileSync27(abs, "utf8"))) {
224687
+ if (!/^\s*export\s+default\b/m.test(readFileSync28(abs, "utf8"))) {
224549
224688
  throw new CliError(`${key.slice(1)} has no default export. A screen file ends with ` + `\`export default UIScreen([...])\` — or \`export default (state) => UIScreen([...])\` for screen states.`);
224550
224689
  }
224551
224690
  const invoke = opts.state !== undefined ? `screen(${JSON.stringify(opts.state)})` : "screen()";
@@ -224852,7 +224991,7 @@ var evaluateDesktopRun = (plan, output) => {
224852
224991
  if (!error && st.expectLog !== undefined) {
224853
224992
  const seen = consoleLines.slice(0, cutoff);
224854
224993
  if (!seen.some((l2) => l2.includes(st.expectLog))) {
224855
- const tail = seen.filter((l2) => l2.startsWith("[DEBUG]") || l2.startsWith("[INFO]") || l2.startsWith("[WARN]") || l2.startsWith("[ERROR]")).slice(-3);
224994
+ const tail = seen.filter((l2) => l2.startsWith("[DEBUG]") || l2.startsWith("[INFO]") || l2.startsWith("[WARN]") || l2.startsWith("[ERROR]")).slice(-8);
224856
224995
  error = `expected a console line containing "${st.expectLog}" — ${tail.length === 0 ? "the app logged nothing" : `last lines: ${tail.map((l2) => `"${l2}"`).join(", ")}`}`;
224857
224996
  }
224858
224997
  }
@@ -224906,7 +225045,7 @@ var resolveFixtures = (value, baseDir) => {
224906
225045
  if (typeof value === "string") {
224907
225046
  const abs = resolve17(baseDir, value);
224908
225047
  try {
224909
- map = JSON.parse(readFileSync28(abs, "utf8"));
225048
+ map = JSON.parse(readFileSync29(abs, "utf8"));
224910
225049
  } catch (e2) {
224911
225050
  throw new CliError(`Can't read fixtures "${value}": ${e2 instanceof Error ? e2.message : String(e2)}`);
224912
225051
  }
@@ -224919,7 +225058,7 @@ var resolveFixtures = (value, baseDir) => {
224919
225058
  var readScenarioFile = (path2) => {
224920
225059
  let raw;
224921
225060
  try {
224922
- raw = JSON.parse(readFileSync28(resolve17(path2), "utf8"));
225061
+ raw = JSON.parse(readFileSync29(resolve17(path2), "utf8"));
224923
225062
  } catch (e2) {
224924
225063
  throw new CliError(`Can't read scenario "${path2}": ${e2 instanceof Error ? e2.message : String(e2)}`);
224925
225064
  }
@@ -225133,6 +225272,26 @@ var render = async (args) => {
225133
225272
  session.dispose();
225134
225273
  }
225135
225274
  }
225275
+ const mapFlag = flagStr(args, "map") ?? (flagBool(args, "map") ? "map.png" : undefined);
225276
+ if (mapFlag !== undefined) {
225277
+ note(`Rendering map…`);
225278
+ const title = root.split(/[\\/]/).pop();
225279
+ const { png: png2, data, warnings: mapWarnings } = await renderer.renderToMap(js, {
225280
+ ...timing,
225281
+ maxPx: Number(flagStr(args, "max-px")) || undefined,
225282
+ title,
225283
+ labelAll: flagBool(args, "label-all")
225284
+ });
225285
+ for (const w of [...warnings, ...mapWarnings])
225286
+ warnErr(w);
225287
+ const pngPath = mapFlag.endsWith(".png") ? mapFlag : mapFlag + ".png";
225288
+ mkdirSync23(dirname15(resolve17(pngPath)), { recursive: true });
225289
+ writeFileSync24(resolve17(pngPath), png2);
225290
+ const jsonPath = pngPath.slice(0, -4) + ".json";
225291
+ writeFileSync24(resolve17(jsonPath), JSON.stringify(data, null, 2));
225292
+ note(`Wrote ${pngPath} + ${jsonPath}`);
225293
+ return done((cb) => cb());
225294
+ }
225136
225295
  const pngFlag = flagStr(args, "png") ?? (flagBool(args, "png") ? "screenshot.png" : undefined);
225137
225296
  if (pngFlag !== undefined) {
225138
225297
  note(screen2 ? `Rendering ${screen2.label} (PNG)…` : "Rendering PNG…");
@@ -225180,7 +225339,7 @@ init_api();
225180
225339
  init_config();
225181
225340
  init_manifest();
225182
225341
  init_util();
225183
- import { appendFileSync as appendFileSync2, existsSync as existsSync31, readFileSync as readFileSync29, writeFileSync as writeFileSync25 } from "node:fs";
225342
+ import { appendFileSync as appendFileSync2, existsSync as existsSync31, readFileSync as readFileSync30, writeFileSync as writeFileSync25 } from "node:fs";
225184
225343
  import { join as join33 } from "node:path";
225185
225344
  var ENV_VAR = "LECODES_PUSH_KEY";
225186
225345
  var context = () => {
@@ -225327,7 +225486,7 @@ var writeEnv = (root, key) => {
225327
225486
  const touched = [];
225328
225487
  const envPath = join33(root, ".env");
225329
225488
  const line = `${ENV_VAR}=${key}`;
225330
- const existing = existsSync31(envPath) ? readFileSync29(envPath, "utf8") : "";
225489
+ const existing = existsSync31(envPath) ? readFileSync30(envPath, "utf8") : "";
225331
225490
  if (new RegExp(`^${ENV_VAR}=`, "m").test(existing)) {
225332
225491
  writeFileSync25(envPath, existing.replace(new RegExp(`^${ENV_VAR}=.*$`, "m"), line));
225333
225492
  } else {
@@ -225339,7 +225498,7 @@ var writeEnv = (root, key) => {
225339
225498
  }
225340
225499
  touched.push(".env");
225341
225500
  const ignorePath = join33(root, ".gitignore");
225342
- const ignore = existsSync31(ignorePath) ? readFileSync29(ignorePath, "utf8") : "";
225501
+ const ignore = existsSync31(ignorePath) ? readFileSync30(ignorePath, "utf8") : "";
225343
225502
  if (!ignore.split(`
225344
225503
  `).some((l2) => l2.trim() === ".env")) {
225345
225504
  const separator = !ignore || ignore.endsWith(`
@@ -225525,7 +225684,7 @@ var pullPaths = async (root, manifest, project, paths, apiUrl, token) => {
225525
225684
 
225526
225685
  // src/commands/scene.ts
225527
225686
  init_manifest();
225528
- import { existsSync as existsSync34, readFileSync as readFileSync31, readdirSync as readdirSync15, statSync as statSync13, writeFileSync as writeFileSync28 } from "node:fs";
225687
+ import { existsSync as existsSync34, readFileSync as readFileSync32, readdirSync as readdirSync15, statSync as statSync13, writeFileSync as writeFileSync28 } from "node:fs";
225529
225688
  import { basename as basename17, dirname as dirname17, join as join36, resolve as resolve18 } from "node:path";
225530
225689
 
225531
225690
  // src/compile/sceneCompile.ts
@@ -225535,7 +225694,7 @@ init_distRoot();
225535
225694
  init_util();
225536
225695
  init_projectCompile();
225537
225696
  import { createHash as createHash8 } from "node:crypto";
225538
- import { existsSync as existsSync33, mkdirSync as mkdirSync25, readFileSync as readFileSync30, writeFileSync as writeFileSync27 } from "node:fs";
225697
+ import { existsSync as existsSync33, mkdirSync as mkdirSync25, readFileSync as readFileSync31, writeFileSync as writeFileSync27 } from "node:fs";
225539
225698
  import { join as join35 } from "node:path";
225540
225699
  var TEXT_EXT = /\.(ts|js|json|svg|txt|md)$/i;
225541
225700
  var rel = (key) => key.replace(/^\//, "");
@@ -225546,7 +225705,7 @@ var resolveHarness = (loadSceneHarness2) => {
225546
225705
  } catch {
225547
225706
  const vendored = join35(distRoot, "runtime", "scene-harness.json");
225548
225707
  if (existsSync33(vendored))
225549
- return JSON.parse(readFileSync30(vendored, "utf8"));
225708
+ return JSON.parse(readFileSync31(vendored, "utf8"));
225550
225709
  throw new CliError("scene-editor harness sources not found — reinstall the CLI or set LECODES_SCENE_HARNESS_DIR.");
225551
225710
  }
225552
225711
  };
@@ -225558,16 +225717,16 @@ var compileSceneEditor = async (opts) => {
225558
225717
  for (const file of scan2.files) {
225559
225718
  const path2 = "./" + rel(file.path);
225560
225719
  if (TEXT_EXT.test(path2)) {
225561
- entries.push({ path: path2, type: "text", text: readFileSync30(file.absPath, "utf8") });
225720
+ entries.push({ path: path2, type: "text", text: readFileSync31(file.absPath, "utf8") });
225562
225721
  } else if (/\.mat$/i.test(path2)) {
225563
- entries.push({ path: path2, type: "shader", text: readFileSync30(file.absPath, "utf8"), absPath: file.absPath });
225722
+ entries.push({ path: path2, type: "shader", text: readFileSync31(file.absPath, "utf8"), absPath: file.absPath });
225564
225723
  } else {
225565
225724
  entries.push({ path: path2, type: "resource", fileSrc: "/project/" + rel(file.path) });
225566
225725
  }
225567
225726
  }
225568
225727
  const shaderEntries = entries.filter((e2) => e2.type === "shader");
225569
225728
  if (shaderEntries.length > 0) {
225570
- const { warnings } = await applyLocalShaders(shaderEntries, { root, srcPrefix: "/project/.lecodes/shaders", platforms: ["webgl", "webgl-lite"] });
225729
+ const { warnings } = await applyLocalShaders(shaderEntries, { root, srcPrefix: "/project/.lecodes/shaders", platforms: ["opengl", "webgl-lite"] });
225571
225730
  for (const w of warnings)
225572
225731
  warnErr(w);
225573
225732
  const { buildLiteMaterialArtifact: buildLiteMaterialArtifact2 } = await Promise.resolve().then(() => (init_compile(), exports_compile));
@@ -225581,7 +225740,7 @@ var compileSceneEditor = async (opts) => {
225581
225740
  if (!existsSync33(join35(dir, liteName)))
225582
225741
  writeFileSync27(join35(dir, liteName), JSON.stringify(buildLiteMaterialArtifact2(e2.text ?? "")));
225583
225742
  const src = `/project/.lecodes/shaders/${hash}_opengl.filamat`;
225584
- e2.shaders = ["opengl", "webgl", "webgl-lite"].map((platform) => ({ platform, src }));
225743
+ e2.shaders = ["opengl", "webgl-lite"].map((platform) => ({ platform, src }));
225585
225744
  }
225586
225745
  }
225587
225746
  const scene = scenePath.startsWith("./") ? scenePath : "./" + rel(scenePath);
@@ -225621,6 +225780,13 @@ var findAppRoot = (start) => {
225621
225780
  dir = parent;
225622
225781
  }
225623
225782
  };
225783
+ var projectRootOf = (dir) => {
225784
+ try {
225785
+ return findProjectRoot(dir);
225786
+ } catch {
225787
+ return findAppRoot(dir) ?? dir;
225788
+ }
225789
+ };
225624
225790
  var getContext2 = (args) => {
225625
225791
  let root;
225626
225792
  let manifest;
@@ -225651,7 +225817,7 @@ var writeMcpConfig2 = (root, port) => {
225651
225817
  let config = {};
225652
225818
  if (existsSync34(path2)) {
225653
225819
  try {
225654
- config = JSON.parse(readFileSync31(path2, "utf8"));
225820
+ config = JSON.parse(readFileSync32(path2, "utf8"));
225655
225821
  } catch {
225656
225822
  warnErr(".mcp.json exists but isn't valid JSON — left it untouched. Add the lecodes-scene server by hand.");
225657
225823
  return;
@@ -225715,13 +225881,170 @@ var scene = async (args) => {
225715
225881
  openBrowser(server2.url);
225716
225882
  };
225717
225883
 
225884
+ // src/commands/thumbs.ts
225885
+ import { createRequire as createRequire5 } from "node:module";
225886
+ import { execFileSync as execFileSync7 } from "node:child_process";
225887
+ import { existsSync as existsSync35, mkdtempSync as mkdtempSync6, readdirSync as readdirSync16, rmSync as rmSync12, statSync as statSync14 } from "node:fs";
225888
+ import { tmpdir as tmpdir6 } from "node:os";
225889
+ import { extname, join as join37, relative as relative7, resolve as resolve19 } from "node:path";
225890
+ init_util();
225891
+ var DEFAULT_SIZE = 256;
225892
+ var IMAGE_EXT = /\.(png|jpe?g|webp|bmp|gif|tiff?|ktx2)$/i;
225893
+ var RENDERED_EXT = /\.(glb|gltf|material\.ts|scene\.ts)$/i;
225894
+ var findKtx = () => {
225895
+ const exe = process.platform === "win32" ? "ktx.exe" : "ktx";
225896
+ const candidates = [
225897
+ process.env.LECODES_KTX,
225898
+ process.env.KTX_CLI,
225899
+ process.env.KTX_TOKTX?.replace(/toktx(\.exe)?$/i, exe),
225900
+ process.platform === "win32" ? join37(process.env.LOCALAPPDATA ?? "", "Programs", "KTX-Software", "bin", exe) : null,
225901
+ process.platform === "win32" ? join37(process.env.ProgramFiles ?? "", "KTX-Software", "bin", exe) : null,
225902
+ "/usr/local/bin/ktx",
225903
+ "/opt/homebrew/bin/ktx"
225904
+ ].filter((p2) => !!p2);
225905
+ for (const path2 of candidates) {
225906
+ if (existsSync35(path2))
225907
+ return path2;
225908
+ }
225909
+ try {
225910
+ execFileSync7(exe, ["--version"], { stdio: "ignore" });
225911
+ return exe;
225912
+ } catch {
225913
+ return null;
225914
+ }
225915
+ };
225916
+ var sharpResizer = () => {
225917
+ try {
225918
+ const req = createRequire5(import.meta.url);
225919
+ const sharp = createRequire5(req.resolve("lecodes-assets/cli"))("sharp");
225920
+ return async (file, size) => sharp(file).resize({ width: size, height: size, fit: "inside", withoutEnlargement: true }).png().toBuffer();
225921
+ } catch {
225922
+ return null;
225923
+ }
225924
+ };
225925
+ var jimpResizer = () => async (file, size) => {
225926
+ const { Jimp: Jimp2 } = await Promise.resolve().then(() => (init_esm30(), exports_esm2));
225927
+ const img = await Jimp2.read(file);
225928
+ if (Math.max(img.width, img.height) > size)
225929
+ img.scaleToFit({ w: size, h: size });
225930
+ return await img.getBuffer("image/png");
225931
+ };
225932
+ var ktxToPng = (ktx, file, out) => {
225933
+ const run2 = (args) => execFileSync7(ktx, args, { stdio: ["ignore", "ignore", "pipe"], encoding: "utf8" });
225934
+ try {
225935
+ run2(["extract", "--transcode", "rgba8", "--level", "0", file, out]);
225936
+ } catch (e2) {
225937
+ run2(["extract", "--level", "0", file, out]);
225938
+ if (!existsSync35(out))
225939
+ throw e2;
225940
+ }
225941
+ };
225942
+ var collect = (dir) => {
225943
+ const out = [];
225944
+ for (const name of readdirSync16(dir)) {
225945
+ if (name === "node_modules" || name.startsWith("."))
225946
+ continue;
225947
+ const full = join37(dir, name);
225948
+ if (statSync14(full).isDirectory())
225949
+ out.push(...collect(full));
225950
+ else if (IMAGE_EXT.test(name))
225951
+ out.push(full);
225952
+ }
225953
+ return out;
225954
+ };
225955
+ var thumbs = async (args) => {
225956
+ const root = projectRootOf(process.cwd());
225957
+ let sceneServer;
225958
+ try {
225959
+ sceneServer = await import("lecodes-3d-editor/server");
225960
+ } catch {
225961
+ throw new CliError("The scene editor isn't installed (it owns the preview cache). Run: npm install -g lecodes-3d-editor");
225962
+ }
225963
+ const size = Number(flagStr(args, "size")) || DEFAULT_SIZE;
225964
+ const force = flagBool(args, "force");
225965
+ let files = [];
225966
+ const rendered = [];
225967
+ if (args._.length > 0) {
225968
+ for (const arg of args._) {
225969
+ const abs = resolve19(process.cwd(), arg);
225970
+ if (!existsSync35(abs))
225971
+ throw new CliError(`No such file or folder: ${arg}`);
225972
+ if (statSync14(abs).isDirectory())
225973
+ files.push(...collect(abs));
225974
+ else if (IMAGE_EXT.test(abs))
225975
+ files.push(abs);
225976
+ else if (RENDERED_EXT.test(abs))
225977
+ rendered.push(arg);
225978
+ else
225979
+ throw new CliError(`Not a previewable asset: ${arg}`);
225980
+ }
225981
+ } else {
225982
+ files = collect(root);
225983
+ }
225984
+ files = [...new Set(files)];
225985
+ if (files.length === 0 && rendered.length === 0) {
225986
+ log("No images to thumbnail.");
225987
+ return;
225988
+ }
225989
+ const ktx = files.some((f3) => f3.toLowerCase().endsWith(".ktx2")) ? findKtx() : null;
225990
+ const sharp = sharpResizer();
225991
+ const resize = sharp ?? jimpResizer();
225992
+ const tmp = mkdtempSync6(join37(tmpdir6(), "lecodes-thumbs-"));
225993
+ let made = 0, fresh = 0;
225994
+ const skipped = [];
225995
+ try {
225996
+ for (const [i2, file] of files.entries()) {
225997
+ const asset = "./" + toKey(relative7(root, file));
225998
+ progress(i2, files.length, asset);
225999
+ const hash = sceneServer.hashAsset(root, asset);
226000
+ if (!hash)
226001
+ continue;
226002
+ if (!force && sceneServer.previewIsCurrent(root, asset, hash)) {
226003
+ fresh++;
226004
+ continue;
226005
+ }
226006
+ const ext2 = extname(file).toLowerCase();
226007
+ if (ext2 === ".webp" && !sharp) {
226008
+ skipped.push(`${asset} (webp needs sharp — npm install -g lecodes-assets)`);
226009
+ continue;
226010
+ }
226011
+ try {
226012
+ let source = file;
226013
+ if (ext2 === ".ktx2") {
226014
+ if (!ktx) {
226015
+ skipped.push(`${asset} (KTX2 needs KTX-Software's \`ktx\` — set LECODES_KTX or put it on PATH)`);
226016
+ continue;
226017
+ }
226018
+ source = join37(tmp, `${i2}.png`);
226019
+ ktxToPng(ktx, file, source);
226020
+ }
226021
+ sceneServer.writePreviewFile(root, asset, await resize(source, size));
226022
+ made++;
226023
+ } catch (e2) {
226024
+ skipped.push(`${asset} (${(e2?.message ?? String(e2)).split(`
226025
+ `)[0]})`);
226026
+ }
226027
+ }
226028
+ } finally {
226029
+ endProgress();
226030
+ rmSync12(tmp, { recursive: true, force: true });
226031
+ }
226032
+ log(`${c.bold(String(made))} generated · ${fresh} already current${skipped.length > 0 ? ` · ${skipped.length} skipped` : ""}`);
226033
+ for (const s2 of skipped)
226034
+ warnErr(s2);
226035
+ if (rendered.length > 0) {
226036
+ warnErr(`Models, materials and prefabs are RENDERED, not decoded — the scene editor does those: ${rendered.join(", ")}`);
226037
+ }
226038
+ note(`Cache: ${join37(root, ".lecodes", "preview")} (re-runs skip everything unchanged)`);
226039
+ };
226040
+
225718
226041
  // src/commands/assets.ts
225719
226042
  init_util();
225720
226043
 
225721
226044
  // src/cmgenTool.ts
225722
- import { existsSync as existsSync35, readdirSync as readdirSync16 } from "node:fs";
226045
+ import { existsSync as existsSync36, readdirSync as readdirSync17 } from "node:fs";
225723
226046
  import { homedir as homedir8 } from "node:os";
225724
- import { join as join37, resolve as resolve19 } from "node:path";
226047
+ import { join as join38, resolve as resolve20 } from "node:path";
225725
226048
  init_util();
225726
226049
  var exeName2 = () => process.platform === "win32" ? "cmgen.exe" : "cmgen";
225727
226050
  var platformKey3 = () => {
@@ -225731,10 +226054,10 @@ var platformKey3 = () => {
225731
226054
  return "linux-x64";
225732
226055
  throw new CliError(process.platform === "darwin" ? "cmgen isn't published for macOS yet — build filament's host tools locally and set LECODES_CMGEN." : `cmgen isn't published for ${process.platform}.`);
225733
226056
  };
225734
- var cacheRoot3 = () => join37(homedir8(), ".lecodes", "cmgen");
226057
+ var cacheRoot3 = () => join38(homedir8(), ".lecodes", "cmgen");
225735
226058
  var findCmgenIn = (dir) => {
225736
- for (const entry of readdirSync16(dir, { withFileTypes: true })) {
225737
- const p2 = join37(dir, entry.name);
226059
+ for (const entry of readdirSync17(dir, { withFileTypes: true })) {
226060
+ const p2 = join38(dir, entry.name);
225738
226061
  if (entry.isFile() && entry.name === exeName2())
225739
226062
  return p2;
225740
226063
  if (entry.isDirectory()) {
@@ -225747,7 +226070,7 @@ var findCmgenIn = (dir) => {
225747
226070
  };
225748
226071
  var installedCmgenVersions = () => {
225749
226072
  try {
225750
- return readdirSync16(cacheRoot3(), { withFileTypes: true }).filter((e2) => e2.isDirectory() && e2.name.startsWith("cmgen-")).map((e2) => e2.name.slice("cmgen-".length)).filter((v) => findCmgenIn(join37(cacheRoot3(), `cmgen-${v}`)) !== null).sort(semverNewer);
226073
+ return readdirSync17(cacheRoot3(), { withFileTypes: true }).filter((e2) => e2.isDirectory() && e2.name.startsWith("cmgen-")).map((e2) => e2.name.slice("cmgen-".length)).filter((v) => findCmgenIn(join38(cacheRoot3(), `cmgen-${v}`)) !== null).sort(semverNewer);
225751
226074
  } catch {
225752
226075
  return [];
225753
226076
  }
@@ -225769,19 +226092,19 @@ var downloadCmgen = async (version, versions, dest) => {
225769
226092
  var resolveCmgenExeCached = () => {
225770
226093
  const override = process.env.LECODES_CMGEN;
225771
226094
  if (override) {
225772
- if (!existsSync35(override))
226095
+ if (!existsSync36(override))
225773
226096
  throw new CliError(`LECODES_CMGEN points at "${override}" which doesn't exist.`);
225774
- return resolve19(override);
226097
+ return resolve20(override);
225775
226098
  }
225776
226099
  const cached = installedCmgenVersions()[0];
225777
- return cached ? findCmgenIn(join37(cacheRoot3(), `cmgen-${cached}`)) : null;
226100
+ return cached ? findCmgenIn(join38(cacheRoot3(), `cmgen-${cached}`)) : null;
225778
226101
  };
225779
226102
  var resolveCmgenExe = async () => {
225780
226103
  const cached = resolveCmgenExeCached();
225781
226104
  if (cached)
225782
226105
  return cached;
225783
226106
  const { latest, versions } = await fetchCmgenManifest();
225784
- const dir = join37(cacheRoot3(), `cmgen-${latest}`);
226107
+ const dir = join38(cacheRoot3(), `cmgen-${latest}`);
225785
226108
  await downloadCmgen(latest, versions, dir);
225786
226109
  const exe = findCmgenIn(dir);
225787
226110
  if (!exe)
@@ -225839,8 +226162,8 @@ var status3 = async (_args) => {
225839
226162
  };
225840
226163
 
225841
226164
  // src/commands/test.ts
225842
- import { existsSync as existsSync36, mkdirSync as mkdirSync26, readFileSync as readFileSync32, readdirSync as readdirSync17 } from "node:fs";
225843
- import { dirname as dirname18, resolve as resolve20 } from "node:path";
226165
+ import { existsSync as existsSync37, mkdirSync as mkdirSync26, readFileSync as readFileSync33, readdirSync as readdirSync18 } from "node:fs";
226166
+ import { dirname as dirname18, resolve as resolve21 } from "node:path";
225844
226167
  init_manifest();
225845
226168
  init_util();
225846
226169
  var deriveDesignFlows = (meta, entryScreen) => {
@@ -225903,7 +226226,7 @@ var slug3 = (s2) => s2.replace(/[^a-zA-Z0-9-_]+/g, "-").replace(/^-+|-+$/g, "").
225903
226226
  var readFlowFile = (abs, displayName) => {
225904
226227
  let raw;
225905
226228
  try {
225906
- raw = JSON.parse(readFileSync32(abs, "utf8"));
226229
+ raw = JSON.parse(readFileSync33(abs, "utf8"));
225907
226230
  } catch (e2) {
225908
226231
  throw new CliError(`Can't read "${displayName}": ${e2 instanceof Error ? e2.message : String(e2)}`);
225909
226232
  }
@@ -225958,40 +226281,40 @@ var test = async (args) => {
225958
226281
  if (!Number.isFinite(nativeFixedDt) || nativeFixedDt < 0 || nativeFixedDt > 1000)
225959
226282
  throw new CliError(`--fixed-dt wants a number of ms (0 = wall clock, max 1000), got "${fixedDtFlag}".`);
225960
226283
  const projectViewport = desktopDefaultViewport(root);
225961
- const testsDir = resolve20(root, flagStr(args, "dir") ?? "tests");
225962
- const designMetaPath = resolve20(root, flagStr(args, "design-dir") ?? "design", "meta.json");
226284
+ const testsDir = resolve21(root, flagStr(args, "dir") ?? "tests");
226285
+ const designMetaPath = resolve21(root, flagStr(args, "design-dir") ?? "design", "meta.json");
225963
226286
  const asJson = flagBool(args, "json");
225964
226287
  const out = asJson ? logErr : log;
225965
226288
  const cases = [];
225966
226289
  if (args._.length > 0) {
225967
226290
  for (const given of args._) {
225968
- const candidates = [resolve20(given), resolve20(testsDir, given), resolve20(testsDir, `${given}.flow.json`)];
225969
- const abs = candidates.find((p2) => existsSync36(p2));
226291
+ const candidates = [resolve21(given), resolve21(testsDir, given), resolve21(testsDir, `${given}.flow.json`)];
226292
+ const abs = candidates.find((p2) => existsSync37(p2));
225970
226293
  if (!abs)
225971
226294
  throw new CliError(`No such test: "${given}" (looked in ${testsDir}).`);
225972
226295
  cases.push(readFlowFile(abs, given));
225973
226296
  }
225974
- } else if (existsSync36(testsDir)) {
225975
- for (const f3 of readdirSync17(testsDir).filter((f4) => f4.endsWith(".flow.json")).sort()) {
225976
- cases.push(readFlowFile(resolve20(testsDir, f3), f3));
226297
+ } else if (existsSync37(testsDir)) {
226298
+ for (const f3 of readdirSync18(testsDir).filter((f4) => f4.endsWith(".flow.json")).sort()) {
226299
+ cases.push(readFlowFile(resolve21(testsDir, f3), f3));
225977
226300
  }
225978
226301
  }
225979
226302
  const fixturesFlag = flagStr(args, "fixtures");
225980
- const defaultPack = resolve20(testsDir, "fixtures.json");
225981
- const flowFixtures = fixturesFlag !== undefined ? resolveFixtures(fixturesFlag, ".") : existsSync36(defaultPack) ? resolveFixtures("fixtures.json", testsDir) : undefined;
226303
+ const defaultPack = resolve21(testsDir, "fixtures.json");
226304
+ const flowFixtures = fixturesFlag !== undefined ? resolveFixtures(fixturesFlag, ".") : existsSync37(defaultPack) ? resolveFixtures("fixtures.json", testsDir) : undefined;
225982
226305
  const flowsOnly = flagBool(args, "flows");
225983
226306
  const wantFlows = !flagBool(args, "no-flows") && args._.length === 0;
225984
226307
  if (flowsOnly)
225985
226308
  cases.length = 0;
225986
226309
  let skipped = [];
225987
226310
  let entryScreen = null;
225988
- if (desktop2 && wantFlows && existsSync36(designMetaPath)) {
226311
+ if (desktop2 && wantFlows && existsSync37(designMetaPath)) {
225989
226312
  note("Design-derived flows skipped with --desktop (they tap by name; run `lecodes test` headless for them).");
225990
226313
  }
225991
- if (!desktop2 && (wantFlows || flowsOnly) && existsSync36(designMetaPath)) {
226314
+ if (!desktop2 && (wantFlows || flowsOnly) && existsSync37(designMetaPath)) {
225992
226315
  let meta;
225993
226316
  try {
225994
- meta = JSON.parse(readFileSync32(designMetaPath, "utf8"));
226317
+ meta = JSON.parse(readFileSync33(designMetaPath, "utf8"));
225995
226318
  } catch (e2) {
225996
226319
  throw new CliError(`Can't read ${designMetaPath}: ${e2 instanceof Error ? e2.message : String(e2)}`);
225997
226320
  }
@@ -226023,16 +226346,16 @@ var test = async (args) => {
226023
226346
  }, timeoutMs);
226024
226347
  killer.unref?.();
226025
226348
  const onConsole = flagBool(args, "logs") ? (level, text) => logErr(`${c.dim(`[${level}]`)} ${text}`) : undefined;
226026
- const artifactsRoot = resolve20(root, flagStr(args, "artifacts") ?? resolve20(testsDir, ".artifacts"));
226349
+ const artifactsRoot = resolve21(root, flagStr(args, "artifacts") ?? resolve21(testsDir, ".artifacts"));
226027
226350
  const results = [];
226028
226351
  const runNativeCase = async (kase) => {
226029
- const caseDir = resolve20(artifactsRoot, slug3(kase.name));
226352
+ const caseDir = resolve21(artifactsRoot, slug3(kase.name));
226030
226353
  const plan = translateScenario(kase.steps, { outDir: caseDir, headroomFrames: nativeFrames });
226031
226354
  for (const st of plan.steps)
226032
226355
  if (st.screenshot)
226033
226356
  mkdirSync26(dirname18(st.screenshot), { recursive: true });
226034
226357
  mkdirSync26(caseDir, { recursive: true });
226035
- const finalPng = resolve20(caseDir, "final.png");
226358
+ const finalPng = resolve21(caseDir, "final.png");
226036
226359
  const { output } = await runDesktopRender(native.js, native.resources, {
226037
226360
  renderer: desktopRenderer,
226038
226361
  width: Number(flagStr(args, "width")) || kase.width || projectViewport?.width || 390,
@@ -226084,14 +226407,14 @@ var test = async (args) => {
226084
226407
  onConsole
226085
226408
  });
226086
226409
  try {
226087
- const io = fileScenarioIO(resolve20(artifactsRoot, slug3(kase.name)));
226410
+ const io = fileScenarioIO(resolve21(artifactsRoot, slug3(kase.name)));
226088
226411
  const outcome = await renderer.runScenario(session, steps, io);
226089
226412
  result = {
226090
226413
  name: kase.name,
226091
226414
  kind: kase.kind,
226092
226415
  ok: outcome.ok,
226093
226416
  steps: outcome.steps.map((s2) => ({ desc: s2.desc, ok: s2.ok, error: s2.error })),
226094
- artifacts: outcome.failureArtifacts.map((p2) => resolve20(artifactsRoot, slug3(kase.name), p2))
226417
+ artifacts: outcome.failureArtifacts.map((p2) => resolve21(artifactsRoot, slug3(kase.name), p2))
226095
226418
  };
226096
226419
  } finally {
226097
226420
  session.dispose();
@@ -226153,17 +226476,54 @@ var test = async (args) => {
226153
226476
  process.exit(failed > 0 ? 1 : 0);
226154
226477
  };
226155
226478
 
226479
+ // src/commands/update.ts
226480
+ import { spawnSync as spawnSync7 } from "node:child_process";
226481
+ import { existsSync as existsSync39, readdirSync as readdirSync19 } from "node:fs";
226482
+ import { homedir as homedir9 } from "node:os";
226483
+ import { dirname as dirname20, join as join40, resolve as resolve23 } from "node:path";
226484
+ init_util();
226485
+
226486
+ // src/peers.ts
226487
+ import { existsSync as existsSync38, readFileSync as readFileSync34 } from "node:fs";
226488
+ import { createRequire as createRequire6 } from "node:module";
226489
+ import { dirname as dirname19, join as join39 } from "node:path";
226490
+ var OPTIONAL_PEERS = [
226491
+ ["lecodes-renderer", "lecodes-renderer/headless"],
226492
+ ["lecodes-design", "lecodes-design/server"],
226493
+ ["lecodes-3d-editor", "lecodes-3d-editor/server"],
226494
+ ["lecodes-assets", "lecodes-assets/cli"]
226495
+ ];
226496
+ var peerVersion = (name, probe) => {
226497
+ try {
226498
+ const require2 = createRequire6(import.meta.url);
226499
+ let dir = dirname19(require2.resolve(probe));
226500
+ for (let depth = 0;depth < 16; depth++) {
226501
+ const manifest = join39(dir, "package.json");
226502
+ if (existsSync38(manifest)) {
226503
+ const parsed = JSON.parse(readFileSync34(manifest, "utf8"));
226504
+ if (parsed.name === name)
226505
+ return parsed.version ?? "unknown";
226506
+ }
226507
+ const parent = dirname19(dir);
226508
+ if (parent === dir)
226509
+ break;
226510
+ dir = parent;
226511
+ }
226512
+ } catch {}
226513
+ return null;
226514
+ };
226515
+
226156
226516
  // src/commands/types.ts
226157
226517
  init_api();
226158
226518
  init_config();
226159
226519
  init_manifest();
226160
226520
  init_types4();
226161
226521
  init_util();
226162
- import { resolve as resolve21 } from "node:path";
226522
+ import { resolve as resolve22 } from "node:path";
226163
226523
  var label = (bundle) => `${Object.keys(bundle).length} file(s)`;
226164
226524
  var types4 = async (args) => {
226165
226525
  const dirArg = args._[0];
226166
- const root = dirArg ? resolve21(dirArg) : (() => {
226526
+ const root = dirArg ? resolve22(dirArg) : (() => {
226167
226527
  try {
226168
226528
  return findProjectRoot(process.cwd());
226169
226529
  } catch {
@@ -226198,9 +226558,9 @@ var types4 = async (args) => {
226198
226558
  const same = bundleHash(onDisk) === bundleHash(bundle);
226199
226559
  if (checkOnly) {
226200
226560
  if (Object.keys(onDisk).length === 0)
226201
- throw new CliError(`No types in ${LECODES_DIR}/types/ — run \`lecodes types\`.`);
226561
+ throw new CliError(`No types in ${LECODES_DIR}/types/ — run \`lecodes update\`.`);
226202
226562
  if (!same)
226203
- throw new CliError(`Types are out of date (${label(onDisk)} on disk) — run \`lecodes types\`.`);
226563
+ throw new CliError(`Types are out of date (${label(onDisk)} on disk) — run \`lecodes update\`.`);
226204
226564
  success(`Types are up to date (${label(onDisk)}).`);
226205
226565
  return;
226206
226566
  }
@@ -226221,24 +226581,161 @@ var types4 = async (args) => {
226221
226581
  note("Reload the TypeScript server in your editor to pick them up.");
226222
226582
  };
226223
226583
 
226584
+ // src/commands/update.ts
226585
+ var findProjectDir = (start) => {
226586
+ const home = resolve23(homedir9());
226587
+ let dir = resolve23(start);
226588
+ while (true) {
226589
+ if (dir !== home && (existsSync39(join40(dir, "app.json")) || existsSync39(join40(dir, ".lecodes", "manifest.json")) || existsSync39(join40(dir, ".lecodes", "types"))))
226590
+ return dir;
226591
+ const parent = dirname20(dir);
226592
+ if (parent === dir)
226593
+ return null;
226594
+ dir = parent;
226595
+ }
226596
+ };
226597
+ var NPM = "https://registry.npmjs.org";
226598
+ var npmLatest = async (name) => {
226599
+ try {
226600
+ const res = await fetch(`${NPM}/${name}/latest`, { signal: AbortSignal.timeout(4000) });
226601
+ if (!res.ok)
226602
+ return null;
226603
+ const json = await res.json();
226604
+ return json.version ?? null;
226605
+ } catch {
226606
+ return null;
226607
+ }
226608
+ };
226609
+ var semverNewer2 = (a2, b) => {
226610
+ const pa = a2.split(/[.-]/).map(Number), pb = b.split(/[.-]/).map(Number);
226611
+ for (let i2 = 0;i2 < 3; i2++) {
226612
+ const d = (pa[i2] ?? 0) - (pb[i2] ?? 0);
226613
+ if (d)
226614
+ return d;
226615
+ }
226616
+ return 0;
226617
+ };
226618
+ var FROM_SOURCE = /[\\/]src[\\/]commands[\\/]/.test(import.meta.url);
226619
+ var STANDALONE = existsSync39(join40(dirname20(process.execPath), "runtime")) && existsSync39(join40(dirname20(process.execPath), process.platform === "win32" ? "chisel.exe" : "chisel"));
226620
+ var update = async (args) => {
226621
+ const steps = [];
226622
+ const heading = (name, what) => log(`
226623
+ ${c.bold(name)} ${c.dim(what)}`);
226624
+ const run2 = async (name, what, fn) => {
226625
+ heading(name, what);
226626
+ try {
226627
+ const r2 = await fn();
226628
+ const [outcome, detail] = Array.isArray(r2) ? r2 : [r2, undefined];
226629
+ steps.push({ name, outcome, detail });
226630
+ if (outcome === "skipped" && detail)
226631
+ note(`skipped — ${detail}`);
226632
+ } catch (e2) {
226633
+ const msg = e2 instanceof CliError ? e2.message : e2 instanceof Error ? e2.message : String(e2);
226634
+ log(c.red("✗ ") + msg);
226635
+ steps.push({ name, outcome: "failed", detail: msg });
226636
+ }
226637
+ };
226638
+ const root = findProjectDir(process.cwd());
226639
+ await run2("desktop", "LeCodes Desktop host (desktop run / render --desktop)", async () => {
226640
+ if (process.env.LECODES_DESKTOP_EXE)
226641
+ return ["skipped", `LECODES_DESKTOP_EXE=${process.env.LECODES_DESKTOP_EXE} (a local build; \`lecodes desktop update\` refreshes the cache anyway)`];
226642
+ await updateDesktop();
226643
+ return "ok";
226644
+ });
226645
+ await run2("matc", "Filament material compiler for local shader compiles", async () => {
226646
+ if (process.env.LECODES_MATC)
226647
+ return ["skipped", `LECODES_MATC=${process.env.LECODES_MATC} (a local matc)`];
226648
+ await updateMatc();
226649
+ return "ok";
226650
+ });
226651
+ await run2("shaders", "recompile the project's .mat shaders with the current matc", async () => {
226652
+ if (!root)
226653
+ return ["skipped", "not inside a project"];
226654
+ await shaders({ _: ["compile"], flags: {} });
226655
+ return "ok";
226656
+ });
226657
+ await run2("types", flagBool(args, "local") ? "IDE types from the SDK bundled in this CLI" : "IDE types from the server", async () => {
226658
+ if (!root)
226659
+ return ["skipped", "not inside a project"];
226660
+ await types4({ _: [], flags: flagBool(args, "local") ? { local: true } : {} });
226661
+ return "ok";
226662
+ });
226663
+ await run2("shells", "ios/ + android/ pins → newest SDK release + plugin tags, then re-sync", async () => {
226664
+ if (!root)
226665
+ return ["skipped", "not inside a project"];
226666
+ if (flagBool(args, "no-shells"))
226667
+ return ["skipped", "--no-shells"];
226668
+ const present = ["ios", "android"].filter((d) => existsSync39(join40(root, d)) && readdirSync19(join40(root, d)).length > 0);
226669
+ if (present.length === 0)
226670
+ return ["skipped", "no ios/ or android/ shell in this project (`lecodes app init ios|android`)"];
226671
+ await app({ _: ["update"], flags: {} });
226672
+ return "ok";
226673
+ });
226674
+ await run2("cli", "lecodes-cli + its optional peers (global npm installs)", async () => {
226675
+ if (flagBool(args, "no-cli"))
226676
+ return ["skipped", "--no-cli"];
226677
+ if (FROM_SOURCE)
226678
+ return ["skipped", "running from the repo checkout — the CLI and its peers are the repo's own"];
226679
+ const rows = [];
226680
+ for (const [name, probe] of OPTIONAL_PEERS) {
226681
+ const installed = peerVersion(name, probe);
226682
+ rows.push({ name, installed, latest: installed ? await npmLatest(name) : null });
226683
+ }
226684
+ rows.push({ name: "lecodes-cli", installed: package_default.version, latest: await npmLatest("lecodes-cli") });
226685
+ if (rows.every((r3) => r3.latest === null))
226686
+ return ["skipped", "npm unreachable"];
226687
+ const behind = rows.filter((r3) => r3.installed && r3.latest && semverNewer2(r3.latest, r3.installed) > 0);
226688
+ for (const r3 of rows) {
226689
+ const state = !r3.installed ? c.dim("not installed (optional — npm install -g " + r3.name + ")") : !r3.latest ? c.dim("(npm: unknown)") : behind.includes(r3) ? c.yellow("→ " + r3.latest) : c.dim("(up to date)");
226690
+ log(" " + r3.name.padEnd(18) + " " + (r3.installed ?? "").padEnd(8) + " " + state);
226691
+ }
226692
+ if (behind.length === 0)
226693
+ return "ok";
226694
+ const specs = behind.map((r3) => r3.name + "@latest");
226695
+ if (flagBool(args, "check"))
226696
+ return ["updated", behind.length + " behind — run: npm install -g " + specs.join(" ")];
226697
+ if (STANDALONE && behind.some((r3) => r3.name === "lecodes-cli")) {
226698
+ note("this is the standalone build — the CLI updates by re-installing the release archive, not through npm; peers still go through npm");
226699
+ }
226700
+ const toInstall = STANDALONE ? specs.filter((s2) => !s2.startsWith("lecodes-cli@")) : specs;
226701
+ if (toInstall.length === 0)
226702
+ return ["skipped", "only the standalone CLI itself is behind — download the release"];
226703
+ log(" " + c.dim("$") + " npm install -g " + toInstall.join(" "));
226704
+ const r2 = spawnSync7("npm", ["install", "-g", ...toInstall], { stdio: "inherit", shell: true });
226705
+ if (r2.status !== 0)
226706
+ throw new CliError("npm install -g exited with " + (r2.status ?? "a signal"));
226707
+ const names3 = toInstall.map((s2) => s2.replace("@latest", "")).join(", ");
226708
+ const cliToo = toInstall.some((s2) => s2.startsWith("lecodes-cli@"));
226709
+ return ["updated", names3 + " → latest" + (cliToo ? " (the new CLI runs from your next command)" : "")];
226710
+ });
226711
+ log(`
226712
+ ${c.bold("Summary")}`);
226713
+ const width = Math.max(...steps.map((s2) => s2.name.length));
226714
+ for (const s2 of steps) {
226715
+ const mark = s2.outcome === "failed" ? c.red("✗") : s2.outcome === "skipped" ? c.dim("–") : c.green("✓");
226716
+ log(` ${mark} ${s2.name.padEnd(width)} ${s2.outcome === "failed" ? c.red(s2.detail ?? "failed") : s2.outcome === "skipped" ? c.dim(s2.detail ?? "skipped") : s2.detail ?? s2.outcome}`);
226717
+ }
226718
+ if (steps.some((s2) => s2.outcome === "failed"))
226719
+ process.exitCode = 1;
226720
+ };
226721
+
226224
226722
  // src/index.ts
226225
- import { existsSync as existsSync39, readFileSync as readFileSync35, realpathSync as realpathSync2 } from "node:fs";
226226
- import { createRequire as createRequire4 } from "node:module";
226227
- import { delimiter as delimiter2, dirname as dirname21, join as join40 } from "node:path";
226723
+ import { existsSync as existsSync42, realpathSync as realpathSync2 } from "node:fs";
226724
+ import { delimiter as delimiter2, dirname as dirname23, join as join43 } from "node:path";
226228
226725
 
226229
226726
  // src/commands/install.ts
226230
226727
  init_util();
226231
- import { execFileSync as execFileSync7 } from "node:child_process";
226232
- import { appendFileSync as appendFileSync3, chmodSync as chmodSync2, cpSync as cpSync5, existsSync as existsSync37, mkdirSync as mkdirSync27, readFileSync as readFileSync33, realpathSync, rmSync as rmSync12, symlinkSync } from "node:fs";
226233
- import { homedir as homedir9 } from "node:os";
226234
- import { basename as basename18, delimiter, dirname as dirname19, join as join38, resolve as resolve22, sep as sep2 } from "node:path";
226728
+ import { execFileSync as execFileSync8 } from "node:child_process";
226729
+ import { appendFileSync as appendFileSync3, chmodSync as chmodSync2, cpSync as cpSync5, existsSync as existsSync40, mkdirSync as mkdirSync27, readFileSync as readFileSync35, realpathSync, rmSync as rmSync13, symlinkSync as symlinkSync2 } from "node:fs";
226730
+ import { homedir as homedir10 } from "node:os";
226731
+ import { basename as basename18, delimiter, dirname as dirname21, join as join41, resolve as resolve24, sep as sep2 } from "node:path";
226235
226732
  var isWin = process.platform === "win32";
226236
- var HOME = homedir9();
226733
+ var HOME = homedir10();
226237
226734
  var safeRealpath = (p2) => {
226238
226735
  try {
226239
226736
  return realpathSync(p2);
226240
226737
  } catch {
226241
- return resolve22(p2);
226738
+ return resolve24(p2);
226242
226739
  }
226243
226740
  };
226244
226741
  var onPath = (dir) => {
@@ -226254,13 +226751,13 @@ var onPath = (dir) => {
226254
226751
  });
226255
226752
  };
226256
226753
  var install = async (args) => {
226257
- const bundle = dirname19(process.execPath);
226754
+ const bundle = dirname21(process.execPath);
226258
226755
  const exeName3 = basename18(process.execPath);
226259
226756
  const chiselName = isWin ? "chisel.exe" : "chisel";
226260
- if (!existsSync37(join38(bundle, "runtime")) || !existsSync37(join38(bundle, chiselName))) {
226757
+ if (!existsSync40(join41(bundle, "runtime")) || !existsSync40(join41(bundle, chiselName))) {
226261
226758
  throw new CliError("`lecodes install` only works from the packaged release build (the archive containing lecodes + " + "chisel + runtime/). You appear to be running from source or an npm install — install globally " + "with your package manager there instead.");
226262
226759
  }
226263
- const companions = [exeName3, chiselName, "runtime", "README.txt"].filter((f3) => existsSync37(join38(bundle, f3)));
226760
+ const companions = [exeName3, chiselName, "runtime", "README.txt"].filter((f3) => existsSync40(join41(bundle, f3)));
226264
226761
  if (isWin)
226265
226762
  return installWindows(args, bundle, companions);
226266
226763
  return installUnix(args, bundle, companions);
@@ -226270,9 +226767,9 @@ var copyBundle = (bundle, target, companions) => {
226270
226767
  if (safeRealpath(target) === safeRealpath(bundle))
226271
226768
  return;
226272
226769
  for (const f3 of companions) {
226273
- const dest = join38(target, f3);
226274
- rmSync12(dest, { recursive: true, force: true });
226275
- cpSync5(join38(bundle, f3), dest, { recursive: true });
226770
+ const dest = join41(target, f3);
226771
+ rmSync13(dest, { recursive: true, force: true });
226772
+ cpSync5(join41(bundle, f3), dest, { recursive: true });
226276
226773
  }
226277
226774
  };
226278
226775
  var permError = (e2, dir, system) => {
@@ -226284,41 +226781,41 @@ var permError = (e2, dir, system) => {
226284
226781
  };
226285
226782
  var installUnix = (args, bundle, companions) => {
226286
226783
  const system = flagBool(args, "system");
226287
- const libDir = resolve22(flagStr(args, "dir") ?? (system ? "/usr/local/lib/lecodes-cli" : join38(HOME, ".local", "lib", "lecodes-cli")));
226288
- const binDir = resolve22(flagStr(args, "bin") ?? (system ? "/usr/local/bin" : join38(HOME, ".local", "bin")));
226289
- const link2 = join38(binDir, "lecodes");
226784
+ const libDir = resolve24(flagStr(args, "dir") ?? (system ? "/usr/local/lib/lecodes-cli" : join41(HOME, ".local", "lib", "lecodes-cli")));
226785
+ const binDir = resolve24(flagStr(args, "bin") ?? (system ? "/usr/local/bin" : join41(HOME, ".local", "bin")));
226786
+ const link2 = join41(binDir, "lecodes");
226290
226787
  try {
226291
226788
  copyBundle(bundle, libDir, companions);
226292
- chmodSync2(join38(libDir, "lecodes"), 493);
226293
- chmodSync2(join38(libDir, "chisel"), 493);
226789
+ chmodSync2(join41(libDir, "lecodes"), 493);
226790
+ chmodSync2(join41(libDir, "chisel"), 493);
226294
226791
  } catch (e2) {
226295
226792
  throw permError(e2, libDir, system);
226296
226793
  }
226297
226794
  try {
226298
226795
  mkdirSync27(binDir, { recursive: true });
226299
- rmSync12(link2, { force: true });
226300
- symlinkSync(join38(libDir, "lecodes"), link2);
226796
+ rmSync13(link2, { force: true });
226797
+ symlinkSync2(join41(libDir, "lecodes"), link2);
226301
226798
  } catch (e2) {
226302
226799
  throw permError(e2, binDir, system);
226303
226800
  }
226304
226801
  success(`Installed to ${c.bold(libDir)}`);
226305
- log(` ${c.dim(link2 + " → " + join38(libDir, "lecodes"))}`);
226802
+ log(` ${c.dim(link2 + " → " + join41(libDir, "lecodes"))}`);
226306
226803
  finishPathUnix(args, binDir);
226307
226804
  };
226308
226805
  var shellRc = () => {
226309
226806
  const shell = basename18(process.env.SHELL ?? "");
226310
226807
  if (shell === "zsh")
226311
- return { file: join38(HOME, ".zshrc"), name: "~/.zshrc" };
226808
+ return { file: join41(HOME, ".zshrc"), name: "~/.zshrc" };
226312
226809
  if (shell === "bash")
226313
- return { file: join38(HOME, ".bashrc"), name: "~/.bashrc" };
226314
- return { file: join38(HOME, ".profile"), name: "~/.profile" };
226810
+ return { file: join41(HOME, ".bashrc"), name: "~/.bashrc" };
226811
+ return { file: join41(HOME, ".profile"), name: "~/.profile" };
226315
226812
  };
226316
226813
  var PATH_MARKER = "# Added by `lecodes install`";
226317
226814
  var appendPathToShellRc = (binDir) => {
226318
226815
  const { file, name } = shellRc();
226319
226816
  const line = `export PATH="${binDir}:$PATH"`;
226320
226817
  try {
226321
- const existing = existsSync37(file) ? readFileSync33(file, "utf8") : "";
226818
+ const existing = existsSync40(file) ? readFileSync35(file, "utf8") : "";
226322
226819
  if (existing.includes(line))
226323
226820
  return name;
226324
226821
  appendFileSync3(file, `${existing && !existing.endsWith(`
@@ -226356,7 +226853,7 @@ var finishPathUnix = (args, binDir) => {
226356
226853
  log(` • or install system-wide (no PATH edit): ${c.bold("sudo lecodes install --system")}`);
226357
226854
  };
226358
226855
  var installWindows = (args, bundle, companions) => {
226359
- const target = resolve22(flagStr(args, "dir") ?? join38(process.env.LOCALAPPDATA ?? join38(HOME, "AppData", "Local"), "Programs", "lecodes-cli"));
226856
+ const target = resolve24(flagStr(args, "dir") ?? join41(process.env.LOCALAPPDATA ?? join41(HOME, "AppData", "Local"), "Programs", "lecodes-cli"));
226360
226857
  try {
226361
226858
  copyBundle(bundle, target, companions);
226362
226859
  } catch (e2) {
@@ -226384,7 +226881,7 @@ var installWindows = (args, bundle, companions) => {
226384
226881
  var addToUserPathWindows = (dir) => {
226385
226882
  const script = "$d = $env:LECODES_INSTALL_DIR;" + "$p = [Environment]::GetEnvironmentVariable('Path','User'); if (-not $p) { $p = '' };" + "$parts = @($p.Split(';') | Where-Object { $_ -ne '' });" + "if ($parts -notcontains $d) { [Environment]::SetEnvironmentVariable('Path', (@($parts + $d) -join ';'), 'User') }";
226386
226883
  try {
226387
- execFileSync7("powershell", ["-NoProfile", "-NonInteractive", "-Command", script], { stdio: "ignore", env: { ...process.env, LECODES_INSTALL_DIR: dir } });
226884
+ execFileSync8("powershell", ["-NoProfile", "-NonInteractive", "-Command", script], { stdio: "ignore", env: { ...process.env, LECODES_INSTALL_DIR: dir } });
226388
226885
  return true;
226389
226886
  } catch {
226390
226887
  return false;
@@ -226395,9 +226892,9 @@ var addToUserPathWindows = (dir) => {
226395
226892
  init_util();
226396
226893
 
226397
226894
  // src/projectEnv.ts
226398
- import { existsSync as existsSync38, readFileSync as readFileSync34 } from "node:fs";
226399
- import { homedir as homedir10 } from "node:os";
226400
- import { dirname as dirname20, join as join39, resolve as resolve23 } from "node:path";
226895
+ import { existsSync as existsSync41, readFileSync as readFileSync36 } from "node:fs";
226896
+ import { homedir as homedir11 } from "node:os";
226897
+ import { dirname as dirname22, join as join42, resolve as resolve25 } from "node:path";
226401
226898
  var KEY_RE = /^(LECODES|CREATOR)_[A-Z0-9_]+$/;
226402
226899
  var parseDotEnv = (text) => {
226403
226900
  const out = {};
@@ -226432,19 +226929,19 @@ var applyDotEnv = (parsed, dir, env) => {
226432
226929
  continue;
226433
226930
  let value = raw;
226434
226931
  if (value.startsWith("./") || value.startsWith("../"))
226435
- value = resolve23(dir, value);
226932
+ value = resolve25(dir, value);
226436
226933
  else if (value === "~" || value.startsWith("~/"))
226437
- value = join39(homedir10(), value.slice(1));
226934
+ value = join42(homedir11(), value.slice(1));
226438
226935
  out[key] = value;
226439
226936
  }
226440
226937
  return out;
226441
226938
  };
226442
226939
  var findEnvRoot = (start) => {
226443
- let dir = resolve23(start);
226940
+ let dir = resolve25(start);
226444
226941
  while (true) {
226445
- if (existsSync38(join39(dir, "app.json")) || existsSync38(join39(dir, ".lecodes")))
226942
+ if (existsSync41(join42(dir, "app.json")) || existsSync41(join42(dir, ".lecodes")))
226446
226943
  return dir;
226447
- const parent = dirname20(dir);
226944
+ const parent = dirname22(dir);
226448
226945
  if (parent === dir)
226449
226946
  return null;
226450
226947
  dir = parent;
@@ -226455,10 +226952,10 @@ var loadProjectEnv = (cwd = process.cwd()) => {
226455
226952
  const root = findEnvRoot(cwd);
226456
226953
  if (!root)
226457
226954
  return [];
226458
- const file = join39(root, ".env");
226459
- if (!existsSync38(file))
226955
+ const file = join42(root, ".env");
226956
+ if (!existsSync41(file))
226460
226957
  return [];
226461
- const added = applyDotEnv(parseDotEnv(readFileSync34(file, "utf8")), root, process.env);
226958
+ const added = applyDotEnv(parseDotEnv(readFileSync36(file, "utf8")), root, process.env);
226462
226959
  for (const [k, v] of Object.entries(added))
226463
226960
  process.env[k] = v;
226464
226961
  return Object.keys(added);
@@ -226468,12 +226965,12 @@ var loadProjectEnv = (cwd = process.cwd()) => {
226468
226965
  };
226469
226966
 
226470
226967
  // src/index.ts
226471
- var STANDALONE = (() => {
226472
- const dir = dirname21(process.execPath);
226968
+ var STANDALONE2 = (() => {
226969
+ const dir = dirname23(process.execPath);
226473
226970
  const chisel = process.platform === "win32" ? "chisel.exe" : "chisel";
226474
- return existsSync39(join40(dir, "runtime")) && existsSync39(join40(dir, chisel));
226971
+ return existsSync42(join43(dir, "runtime")) && existsSync42(join43(dir, chisel));
226475
226972
  })();
226476
- var INSTALLED = STANDALONE && (() => {
226973
+ var INSTALLED = STANDALONE2 && (() => {
226477
226974
  const exe = process.platform === "win32" ? "lecodes.exe" : "lecodes";
226478
226975
  const self2 = (() => {
226479
226976
  try {
@@ -226486,14 +226983,14 @@ var INSTALLED = STANDALONE && (() => {
226486
226983
  if (!d)
226487
226984
  return false;
226488
226985
  try {
226489
- const cand = join40(d, exe);
226490
- return existsSync39(cand) && realpathSync2(cand) === self2;
226986
+ const cand = join43(d, exe);
226987
+ return existsSync42(cand) && realpathSync2(cand) === self2;
226491
226988
  } catch {
226492
226989
  return false;
226493
226990
  }
226494
226991
  });
226495
226992
  })();
226496
- var INSTALL_HELP = STANDALONE && !INSTALLED ? `
226993
+ var INSTALL_HELP = STANDALONE2 && !INSTALLED ? `
226497
226994
  install Install this standalone build so 'lecodes' is on your PATH
226498
226995
  (copies the bundle to a library folder + a symlink/PATH entry).
226499
226996
  --modify-path also add it to PATH (edits your shell rc / user PATH)
@@ -226580,6 +227077,11 @@ ${c.bold("Commands:")}
226580
227077
  -o, --out <file> write JSON to a file instead of stdout
226581
227078
  --png [file] rasterize a PNG (default screenshot.png;
226582
227079
  needs @napi-rs/canvas)
227080
+ --map [file] draw the 3D scene's annotated top-down map
227081
+ (grid + XZ footprints + named markers) as
227082
+ <file>.png + <file>.json world-space data
227083
+ (default map.png; --label-all, --max-px N;
227084
+ also a { "map": "f.png" } script step)
226583
227085
  --script <file> drive the app through a step list (tap/type/
226584
227086
  scroll/wait/expect/screenshot/json steps;
226585
227087
  games: frames/drag/hold/key/keyDown/keyUp,
@@ -226643,13 +227145,22 @@ ${c.bold("Commands:")}
226643
227145
  render --desktop
226644
227146
  (+ the render timing flags: --width/--height, --settle,
226645
227147
  --device, --safe-area, --wait-network, --logs, --entry)
226646
- types [dir] Refresh the IDE types (.lecodes/types/ + tsconfig.json) — by
226647
- default from the server, so a project scaffolded offline picks
226648
- up the SDK the platform actually runs
226649
- --local use the SDK bundled in this CLI instead
226650
- --check report drift only, don't write (non-zero exit)
226651
- --api <url> server to fetch from (default: the logged-in
226652
- one, else https://le.codes)
227148
+ update Move everything a project depends on to its newest published
227149
+ state, in dependency order: the desktop host ('desktop
227150
+ update'), matc ('shaders update'), the project's shader
227151
+ compiles, the IDE types (.lecodes/types/ + tsconfig.json,
227152
+ from the server — the former 'lecodes types'), the
227153
+ ios/android shell pins ('app update'), then the GLOBAL npm
227154
+ installs: every installed optional peer (lecodes-renderer,
227155
+ lecodes-design, lecodes-3d-editor, lecodes-assets) and this
227156
+ CLI that is behind npm's latest gets 'npm install -g'.
227157
+ Pinned tools are skipped, not overridden: LECODES_DESKTOP_EXE
227158
+ / LECODES_MATC mean a local build. Keeps going when a step
227159
+ fails; the summary says which.
227160
+ --local types from the SDK bundled in this CLI
227161
+ --no-shells leave ios/android pins alone (no re-sync)
227162
+ --check npm step: report what's behind, install nothing
227163
+ --no-cli skip the npm step (offline)
226653
227164
  design [serve] LeCodes Design — a live canvas of screens for AI-driven prototyping
226654
227165
  (needs the optional 'lecodes-design' package; works inside a
226655
227166
  cloned project or standalone in any folder)
@@ -226706,6 +227217,8 @@ ${c.bold("Commands:")}
226706
227217
  --probe headless-render the staged folder to PNG
226707
227218
  (macOS: a brief windowed run + snapshot)
226708
227219
  --zip also pack the result for sending
227220
+ --dmg macOS: also build a drag-to-Applications
227221
+ disk image (signed + notarized too)
226709
227222
  --sign <identity|adhoc> macOS codesign identity
226710
227223
  --notarize <profile> / --no-notarize macOS
226711
227224
  --zip <out>.zip, UTF-8/forward-slash entries
@@ -226743,6 +227256,13 @@ ${c.bold("Commands:")}
226743
227256
  --port <n> editor-server port (default 4488)
226744
227257
  --no-open don't open the browser
226745
227258
  --no-mcp don't register the MCP server in .mcp.json
227259
+ thumbs [path…] Asset thumbnails for the scene editor, WITHOUT a browser: decodes
227260
+ the project's images (KTX2 too, via KTX-Software's ktx tool) into the
227261
+ editor's preview cache. Incremental — the cached name carries the
227262
+ asset's content hash, so unchanged files are skipped. Models,
227263
+ materials and prefabs are rendered by the editor, not here
227264
+ --force redo even the up-to-date ones
227265
+ --size <px> longest edge (default 256)
226746
227266
  assets convert <model.fbx|.glb> [--clips a.fbx b.fbx …] [-o out.glb] [--drop-model-clips]
226747
227267
  [--fix] [--ktx2|--ktx2-hq]
226748
227268
  FBX → LeCodes-ready GLB: meters/Y-up, textures embedded, clips from
@@ -226841,35 +227361,10 @@ Config lives in ~/.lecodes/config.json (override with LECODES_API / LECODES_TOKE
226841
227361
  A .env next to app.json is read at startup for LECODES_* / CREATOR_* keys (the shell's own
226842
227362
  environment wins; ./ paths resolve against the project; the file is never pushed) — e.g.
226843
227363
  LECODES_DESKTOP_EXE, LECODES_MATC, LECODES_NOTARY_PROFILE, LECODES_MACOS_SIGN_ID, LECODES_TOKEN.`;
226844
- var commands = { init: init2, login, create, clone: clone3, link, status: status3, diff: diff2, pull, push, compile: compile2, preview: previewLegacy, render, test, types: types4, design, scene, dev, app, pn, desktop, shaders, assets, lightmap, navmesh };
226845
- if (STANDALONE)
227364
+ var commands = { init: init2, login, create, clone: clone3, link, status: status3, diff: diff2, pull, push, compile: compile2, preview: previewLegacy, render, test, design, scene, thumbs, dev, app, pn, desktop, shaders, assets, lightmap, navmesh, update };
227365
+ if (STANDALONE2)
226846
227366
  commands.install = install;
226847
227367
  var readVersion = () => package_default.version ?? "unknown";
226848
- var OPTIONAL_PEERS = [
226849
- ["lecodes-renderer", "lecodes-renderer/headless"],
226850
- ["lecodes-design", "lecodes-design/server"],
226851
- ["lecodes-3d-editor", "lecodes-3d-editor/server"],
226852
- ["lecodes-assets", "lecodes-assets/cli"]
226853
- ];
226854
- var peerVersion = (name, probe) => {
226855
- try {
226856
- const require2 = createRequire4(import.meta.url);
226857
- let dir = dirname21(require2.resolve(probe));
226858
- for (let depth = 0;depth < 16; depth++) {
226859
- const manifest = join40(dir, "package.json");
226860
- if (existsSync39(manifest)) {
226861
- const parsed = JSON.parse(readFileSync35(manifest, "utf8"));
226862
- if (parsed.name === name)
226863
- return parsed.version ?? "unknown";
226864
- }
226865
- const parent = dirname21(dir);
226866
- if (parent === dir)
226867
- break;
226868
- dir = parent;
226869
- }
226870
- } catch {}
226871
- return null;
226872
- };
226873
227368
  var printVersion = () => {
226874
227369
  const rows = [["lecodes-cli", readVersion()]];
226875
227370
  for (const [name, probe] of OPTIONAL_PEERS) {