lecodes-cli 0.20.0 → 0.20.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 (44) hide show
  1. package/dist/index.js +1065 -577
  2. package/package.json +7 -7
  3. package/runtime/materials/decal-relief.mat +178 -0
  4. package/runtime/materials/decal.mat +172 -0
  5. package/runtime/materials/lightmap-baked-lite.mat +175 -0
  6. package/runtime/materials/lightmap-baked.mat +176 -0
  7. package/runtime/materials/lightmap.mat +181 -0
  8. package/runtime/materials/lit.mat +43 -0
  9. package/runtime/materials/particles-quad.mat +179 -0
  10. package/runtime/materials/particles.mat +129 -0
  11. package/runtime/materials/shadow.mat +21 -0
  12. package/runtime/materials/terrain.mat +134 -0
  13. package/runtime/materials/unlit-transparent.mat +42 -0
  14. package/runtime/materials/unlit.mat +44 -0
  15. package/runtime/materials/video.mat +31 -0
  16. package/runtime/scene-harness.json +1 -1
  17. package/runtime/sdk-types.json +1 -1
  18. package/runtime/web/assets/{createViewerLite-BvHobVhp.js → createViewerLite-Ct_PZdof.js} +22 -22
  19. package/runtime/web/assets/{index-CfNCCBjN.js → index-BMt7AnC5.js} +1 -1
  20. package/runtime/web/embed.js +2 -2
  21. package/src/cmgenTool.ts +17 -9
  22. package/src/commands/appDesktop.ts +12 -2
  23. package/src/commands/appShared.ts +48 -0
  24. package/src/commands/assets.ts +3 -7
  25. package/src/commands/design.ts +9 -26
  26. package/src/commands/desktop.ts +3 -0
  27. package/src/commands/dev.ts +42 -0
  28. package/src/commands/projectTemplates.ts +13 -1
  29. package/src/commands/render.ts +4 -8
  30. package/src/commands/scene.ts +2 -6
  31. package/src/commands/shaders.ts +17 -1
  32. package/src/commands/shadersNew.ts +133 -0
  33. package/src/commands/thumbs.ts +3 -6
  34. package/src/commands/update.ts +42 -21
  35. package/src/desktopRenderer.ts +17 -0
  36. package/src/desktopScript.ts +1 -1
  37. package/src/dev/androidDev.ts +154 -0
  38. package/src/dev/devServer.ts +8 -2
  39. package/src/index.ts +21 -4
  40. package/src/matcTool.ts +31 -19
  41. package/src/peerInstall.ts +160 -0
  42. package/src/peers.ts +48 -14
  43. package/src/projectEnv.ts +4 -0
  44. package/src/releases.ts +16 -0
package/dist/index.js CHANGED
@@ -47,6 +47,78 @@ var __export = (target, all) => {
47
47
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
48
48
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
49
49
 
50
+ // package.json
51
+ var package_default;
52
+ var init_package = __esm(() => {
53
+ package_default = {
54
+ name: "lecodes-cli",
55
+ version: "0.20.2",
56
+ dependencies: {
57
+ "@letary/chisel": "^0.10.0",
58
+ jimp: "^1.6.1",
59
+ "lecodes-sdk": "workspace:*"
60
+ },
61
+ devDependencies: {
62
+ "lecodes-design": "^0.9.0",
63
+ "lecodes-renderer": "workspace:*",
64
+ "qrcode-terminal": "^0.12.0",
65
+ "lecodes-3d-editor": "^0.7.0",
66
+ "lecodes-assets": "workspace:*",
67
+ "lecodes-creator-nav": "workspace:*",
68
+ "lecodes-viewer-lite": "workspace:*"
69
+ },
70
+ peerDependencies: {
71
+ "lecodes-design": "^0.9.0",
72
+ "lecodes-renderer": "^0.20.2",
73
+ "lecodes-3d-editor": "^0.7.0",
74
+ "lecodes-assets": "^0.2.1"
75
+ },
76
+ bin: {
77
+ lecodes: "dist/index.js"
78
+ },
79
+ description: "Clone, edit and push LeCodes projects from your machine.",
80
+ engines: {
81
+ node: ">=18"
82
+ },
83
+ files: [
84
+ "dist",
85
+ "src",
86
+ "runtime",
87
+ "README.md"
88
+ ],
89
+ keywords: [
90
+ "lecodes",
91
+ "cli"
92
+ ],
93
+ license: "MIT",
94
+ peerDependenciesMeta: {
95
+ "lecodes-design": {
96
+ optional: true
97
+ },
98
+ "lecodes-renderer": {
99
+ optional: true
100
+ },
101
+ "lecodes-3d-editor": {
102
+ optional: true
103
+ },
104
+ "lecodes-assets": {
105
+ optional: true
106
+ }
107
+ },
108
+ scripts: {
109
+ icon: "bun scripts/make-icon.ts",
110
+ "vendor-runtime": "bun scripts/vendor-runtime.ts",
111
+ "sync-peers": "bun scripts/sync-peer-versions.ts",
112
+ build: 'bun scripts/vendor-runtime.ts && bun build src/index.ts --target=node --outfile dist/index.js --external @letary/chisel --external lecodes-renderer --external lecodes-design --external lecodes-3d-editor --external lecodes-assets --banner="#!/usr/bin/env node"',
113
+ "build:bin": "bun scripts/build-bin.ts",
114
+ typecheck: "tsc --noEmit",
115
+ dev: "bun run src/index.ts",
116
+ prepublishOnly: "bun run sync-peers && bun run build"
117
+ },
118
+ type: "module"
119
+ };
120
+ });
121
+
50
122
  // ../sdk/src/compile/assetName.ts
51
123
  var inRanges = (cp, ranges) => ranges.some(([from, to]) => cp >= from && cp <= to), INVISIBLE, ODD_SPACE, ILLEGAL = '/\\:*?"<>|', sanitizeAssetName = (name) => {
52
124
  let out = "";
@@ -3177,7 +3249,7 @@ var require_source_map_support = __commonJS((exports, module) => {
3177
3249
 
3178
3250
  // ../../node_modules/.bun/typescript@5.8.3/node_modules/typescript/lib/typescript.js
3179
3251
  var require_typescript = __commonJS((exports, module) => {
3180
- var __dirname = "/Users/den/Desktop/Ingenuity/le-codes/node_modules/.bun/typescript@5.8.3/node_modules/typescript/lib", __filename = "/Users/den/Desktop/Ingenuity/le-codes/node_modules/.bun/typescript@5.8.3/node_modules/typescript/lib/typescript.js";
3252
+ var __dirname = "C:\\Users\\Den\\Desktop\\LeCodes\\lecodes\\node_modules\\.bun\\typescript@5.8.3\\node_modules\\typescript\\lib", __filename = "C:\\Users\\Den\\Desktop\\LeCodes\\lecodes\\node_modules\\.bun\\typescript@5.8.3\\node_modules\\typescript\\lib\\typescript.js";
3181
3253
  /*! *****************************************************************************
3182
3254
  Copyright (c) Microsoft Corporation. All rights reserved.
3183
3255
  Licensed under the Apache License, Version 2.0 (the "License"); you may not use
@@ -172165,17 +172237,12 @@ var buildHeader = (name, code, opts = {}) => {
172165
172237
  };
172166
172238
 
172167
172239
  // ../sdk/src/compile/compileProject.ts
172168
- var compileProject = async (opts) => {
172169
- const entries = stubServerEntries(opts.entries, opts.serverUrl ?? "");
172170
- const entry = opts.entryOverride ?? detectEntry(entries, scanModulesChisel).entry;
172171
- if (!entry) {
172172
- throw new Error("Couldn't find an entrypoint (no .ts/.js project file — name yours main.ts). Pass entryOverride.");
172173
- }
172240
+ var entriesToFiles = (entries, opts) => {
172174
172241
  const files = {};
172175
172242
  for (const e of entries) {
172176
172243
  if (e.type === "resource") {
172177
172244
  if (opts.localAssets && !e.remote) {
172178
- const name = e.path.split(/[\\/]/).pop() ?? e.path;
172245
+ const name = e.path.split(/[\/]/).pop() ?? e.path;
172179
172246
  files[e.path] = `export default ("id:" + _creatorUtils.fetchLocal(${JSON.stringify(name)}))`;
172180
172247
  } else {
172181
172248
  files[e.path] = `export default "${opts.publicUrl}${e.fileSrc ?? ""}"`;
@@ -172183,7 +172250,7 @@ var compileProject = async (opts) => {
172183
172250
  } else if (e.type === "shader") {
172184
172251
  const shaders = e.shaders ?? [];
172185
172252
  if (e.shadersLocal && shaders.length > 0) {
172186
- const name = shaders[0].src.split(/[\\/]/).pop() ?? "";
172253
+ const name = shaders[0].src.split(/[\/]/).pop() ?? "";
172187
172254
  const [prefix, suffix = ".filamat"] = name.split("_opengl");
172188
172255
  files[e.path] = `export default ("id:" + _creatorUtils.fetchLocal(${JSON.stringify(prefix + "_")} + _creator.backend + ${JSON.stringify(suffix)}))`;
172189
172256
  } else if (shaders.length > 0) {
@@ -172199,17 +172266,24 @@ var compileProject = async (opts) => {
172199
172266
  files[e.path] = e.text ?? "";
172200
172267
  }
172201
172268
  }
172202
- const { code, map } = await bundleProjectWithMap(entry, files, opts.format ?? "esm", opts.minify, opts.editor, opts.libraries, opts.sourcemap ?? true);
172203
- if (opts.header === false) {
172204
- return map ? `${code}
172205
- ${inlineSourceMapComment(map)}` : code;
172269
+ return files;
172270
+ }, compileProjectWithMap = async (opts) => {
172271
+ const entries = stubServerEntries(opts.entries, opts.serverUrl ?? "");
172272
+ const entry = opts.entryOverride ?? detectEntry(entries, scanModulesChisel).entry;
172273
+ if (!entry) {
172274
+ throw new Error("Couldn't find an entrypoint (no .ts/.js project file — name yours main.ts). Pass entryOverride.");
172206
172275
  }
172276
+ const files = entriesToFiles(entries, opts);
172277
+ const { code, map } = await bundleProjectWithMap(entry, files, opts.format ?? "esm", opts.minify, opts.editor, opts.libraries, opts.sourcemap ?? true);
172278
+ if (opts.header === false)
172279
+ return { code, map, entry };
172207
172280
  const header = buildHeader(opts.name, code, { icon: opts.icon, fonts: opts.fontsHeader, icons: opts.iconsHeader });
172208
- if (!map)
172209
- return header + code;
172210
172281
  const headerLines = (header.match(/\n/g) ?? []).length;
172211
- return `${header}${code}
172212
- ${inlineSourceMapComment(offsetSourceMap(map, headerLines))}`;
172282
+ return { code: header + code, map: map ? offsetSourceMap(map, headerLines) : null, entry };
172283
+ }, compileProject = async (opts) => {
172284
+ const { code, map } = await compileProjectWithMap(opts);
172285
+ return map ? `${code}
172286
+ ${inlineSourceMapComment(map)}` : code;
172213
172287
  };
172214
172288
  var init_compileProject = __esm(() => {
172215
172289
  init_bundler();
@@ -173144,10 +173218,12 @@ __export(exports_compile, {
173144
173218
  faceFileName: () => faceFileName,
173145
173219
  extractMatBlocks: () => extractMatBlocks,
173146
173220
  externalSamplerReg: () => externalSamplerReg,
173221
+ entriesToFiles: () => entriesToFiles,
173147
173222
  detectProjectKind: () => detectProjectKind,
173148
173223
  detectEntry: () => detectEntry,
173149
173224
  deriveEndpointParams: () => deriveEndpointParams,
173150
173225
  compileServerBundle: () => compileServerBundle,
173226
+ compileProjectWithMap: () => compileProjectWithMap,
173151
173227
  compileProject: () => compileProject,
173152
173228
  collectVendoredIcons: () => collectVendoredIcons,
173153
173229
  bundleProjectWithMap: () => bundleProjectWithMap,
@@ -214370,7 +214446,7 @@ var init_browserAuth = __esm(() => {
214370
214446
  init_util();
214371
214447
  });
214372
214448
 
214373
- // ../../../lecodes-tools/packages/lecodes-design/src/core/concept.ts
214449
+ // ../../node_modules/.bun/lecodes-design@0.9.0+02a965cc85c9e554/node_modules/lecodes-design/src/core/concept.ts
214374
214450
  var SPEC_TEMPLATE = `# Concept
214375
214451
 
214376
214452
  The narrative half of this design. \`meta.json\` + the canvas hold the *visual/flow* half (screens,
@@ -214406,7 +214482,7 @@ Behaviour that isn't visible in a single screen — validation, permissions, not
214406
214482
  cases — and the decisions you've made and why, so they aren't re-litigated later.
214407
214483
  `;
214408
214484
 
214409
- // ../../../lecodes-tools/packages/lecodes-design/src/templates.ts
214485
+ // ../../node_modules/.bun/lecodes-design@0.9.0+02a965cc85c9e554/node_modules/lecodes-design/src/templates.ts
214410
214486
  var SPEC_MD, META_JSON = `{
214411
214487
  "screens": {
214412
214488
  "home": { "pos": [0, 0], "desc": "Example screen — replace me", "role": "list" }
@@ -214845,12 +214921,179 @@ var init_designTemplates = __esm(() => {
214845
214921
  init_templates();
214846
214922
  });
214847
214923
 
214924
+ // src/peers.ts
214925
+ import { existsSync as existsSync24, readFileSync as readFileSync20 } from "node:fs";
214926
+ import { createRequire as createRequire5 } from "node:module";
214927
+ import { homedir as homedir8 } from "node:os";
214928
+ import { dirname as dirname14, join as join24, resolve as resolve11 } from "node:path";
214929
+ var OPTIONAL_PEERS, peersDir = () => process.env.LECODES_PEERS_DIR ? resolve11(process.env.LECODES_PEERS_DIR) : join24(homedir8(), ".lecodes", "peers"), peerDirInPrefix = (name) => join24(peersDir(), "node_modules", ...name.split("/")), readVersion = (dir) => {
214930
+ try {
214931
+ return JSON.parse(readFileSync20(join24(dir, "package.json"), "utf8")).version ?? "unknown";
214932
+ } catch {
214933
+ return null;
214934
+ }
214935
+ }, peerVersionGlobal = (name, probe) => {
214936
+ try {
214937
+ const require2 = createRequire5(import.meta.url);
214938
+ let dir = dirname14(require2.resolve(probe));
214939
+ for (let depth = 0;depth < 16; depth++) {
214940
+ const manifest = join24(dir, "package.json");
214941
+ if (existsSync24(manifest)) {
214942
+ const parsed = JSON.parse(readFileSync20(manifest, "utf8"));
214943
+ if (parsed.name === name)
214944
+ return parsed.version ?? "unknown";
214945
+ }
214946
+ const parent = dirname14(dir);
214947
+ if (parent === dir)
214948
+ break;
214949
+ dir = parent;
214950
+ }
214951
+ } catch {}
214952
+ return null;
214953
+ }, peerVersionPrefix = (name) => readVersion(peerDirInPrefix(name)), peerVersion = (name, probe) => peerVersionGlobal(name, probe) ?? peerVersionPrefix(name), peerSource = (name, probe) => peerVersionGlobal(name, probe) !== null ? "global" : peerVersionPrefix(name) !== null ? "prefix" : null;
214954
+ var init_peers = __esm(() => {
214955
+ OPTIONAL_PEERS = [
214956
+ ["lecodes-renderer", "lecodes-renderer/headless"],
214957
+ ["lecodes-design", "lecodes-design/server"],
214958
+ ["lecodes-3d-editor", "lecodes-3d-editor/server"],
214959
+ ["lecodes-assets", "lecodes-assets/cli"],
214960
+ ["@napi-rs/canvas", "@napi-rs/canvas"]
214961
+ ];
214962
+ });
214963
+
214964
+ // src/peerInstall.ts
214965
+ import { spawnSync as spawnSync6 } from "node:child_process";
214966
+ import { existsSync as existsSync25, mkdirSync as mkdirSync18, readFileSync as readFileSync21, writeFileSync as writeFileSync19 } from "node:fs";
214967
+ import { join as join25 } from "node:path";
214968
+ import { pathToFileURL } from "node:url";
214969
+ async function loadPeer(name, subpath, opts) {
214970
+ const spec = subpath ? `${name}/${subpath}` : name;
214971
+ try {
214972
+ return await import(spec);
214973
+ } catch (e2) {
214974
+ if (!isNotFound(e2, spec))
214975
+ throw e2;
214976
+ }
214977
+ const dir = peerDirInPrefix(name);
214978
+ if (!existsSync25(join25(dir, "package.json"))) {
214979
+ if (opts.install === false)
214980
+ return null;
214981
+ if (peerVersionGlobal(name, spec) !== null)
214982
+ throw new CliError(`${name} is installed but "${spec}" can't be loaded from it — reinstall it: npm install -g ${peerSpec(name)}`);
214983
+ await installPeers(companions(name), opts.for);
214984
+ }
214985
+ return await import(pathToFileURL(resolveEntry(dir, subpath)).href);
214986
+ }
214987
+ var CANVAS_RANGE_FALLBACK = "^0.1.65", readJson2 = (path2) => {
214988
+ try {
214989
+ return JSON.parse(readFileSync21(path2, "utf8"));
214990
+ } catch {
214991
+ return null;
214992
+ }
214993
+ }, peerSpec = (name) => {
214994
+ if (name === "lecodes-renderer")
214995
+ return `lecodes-renderer@${package_default.version}`;
214996
+ if (name === "@napi-rs/canvas") {
214997
+ const renderer = readJson2(join25(peerDirInPrefix("lecodes-renderer"), "package.json"));
214998
+ const range2 = renderer?.peerDependencies?.["@napi-rs/canvas"] ?? renderer?.optionalDependencies?.["@napi-rs/canvas"] ?? CANVAS_RANGE_FALLBACK;
214999
+ return `@napi-rs/canvas@${range2}`;
215000
+ }
215001
+ const range = package_default.peerDependencies?.[name];
215002
+ return range ? `${name}@${range}` : `${name}@latest`;
215003
+ }, companions = (name) => name === "lecodes-renderer" ? ["lecodes-renderer", "@napi-rs/canvas"] : [name], onPath = (cmd) => {
215004
+ try {
215005
+ return spawnSync6(cmd, ["--version"], { stdio: "ignore", shell: process.platform === "win32" }).status === 0;
215006
+ } catch {
215007
+ return false;
215008
+ }
215009
+ }, pickInstaller = () => onPath("npm") ? "npm" : onPath("bun") ? "bun" : null, manualCommand = (dir, specs) => `npm install --prefix "${dir}" ${specs.join(" ")}`, installPeers = async (names3, why, opts = {}) => {
215010
+ const dir = peersDir();
215011
+ mkdirSync18(dir, { recursive: true });
215012
+ const manifest = join25(dir, "package.json");
215013
+ if (!existsSync25(manifest)) {
215014
+ writeFileSync19(manifest, JSON.stringify({
215015
+ name: "lecodes-peers",
215016
+ private: true,
215017
+ description: "Optional lecodes-cli peers, installed on first use. Safe to delete — the CLI re-installs what a command needs."
215018
+ }, null, 2) + `
215019
+ `);
215020
+ }
215021
+ const ordered = [...names3].sort((a2, b) => a2 === "lecodes-renderer" ? -1 : b === "lecodes-renderer" ? 1 : 0);
215022
+ const specsNow = ordered.map(peerSpec);
215023
+ const yes = opts.yes === true || !!process.env.LECODES_YES;
215024
+ if (!yes) {
215025
+ const interactive = process.stdin.isTTY && process.stdout.isTTY;
215026
+ if (!interactive) {
215027
+ throw new CliError(`${why} needs ${names3.join(" + ")}, which isn't installed. Re-run with --yes to install it into ${dir}, or install by hand: ${manualCommand(dir, specsNow)}`);
215028
+ }
215029
+ log(`${why} needs ${c.bold(names3.join(" + "))}.`);
215030
+ log(c.dim(`It installs once into ${dir} (no admin rights); a newer CLI re-installs its own version.`));
215031
+ if (!await confirm("Install now?", true)) {
215032
+ throw new CliError(`Not installed. When you want it: ${manualCommand(dir, specsNow)}`);
215033
+ }
215034
+ }
215035
+ const tool = pickInstaller();
215036
+ if (!tool)
215037
+ throw new CliError(`Neither npm nor bun is on PATH, so ${names3.join(" + ")} can't be installed automatically. Install one, or run on a machine with Node: ${manualCommand(dir, specsNow)}`);
215038
+ for (const name of ordered) {
215039
+ const spec = peerSpec(name);
215040
+ const argv = tool === "npm" ? ["install", "--prefix", dir, "--no-audit", "--no-fund", "--loglevel=error", spec] : ["add", "--cwd", dir, spec];
215041
+ note(`$ ${tool} ${argv.join(" ")}`);
215042
+ const r2 = spawnSync6(tool, argv, { stdio: "inherit", shell: process.platform === "win32", timeout: 10 * 60000 });
215043
+ if (r2.error)
215044
+ throw new CliError(`${tool} could not run: ${r2.error.message}`);
215045
+ if (r2.status !== 0)
215046
+ throw new CliError(`${tool} exited with ${r2.status ?? "a signal"} while installing ${spec}. By hand: ${manualCommand(dir, [spec])}`);
215047
+ if (!existsSync25(join25(peerDirInPrefix(name), "package.json")))
215048
+ throw new CliError(`${name} did not land in ${dir} — the installer finished but the package is missing`);
215049
+ }
215050
+ }, resolveEntry = (pkgDir, subpath) => {
215051
+ const manifest = readJson2(join25(pkgDir, "package.json"));
215052
+ if (!manifest)
215053
+ throw new CliError(`${pkgDir} holds no package.json`);
215054
+ const key = subpath ? `./${subpath}` : ".";
215055
+ const pick = (target) => {
215056
+ if (typeof target === "string")
215057
+ return target;
215058
+ if (target && typeof target === "object") {
215059
+ const t2 = target;
215060
+ for (const cond of ["import", "node", "default", "require"])
215061
+ if (cond in t2) {
215062
+ const p2 = pick(t2[cond]);
215063
+ if (p2)
215064
+ return p2;
215065
+ }
215066
+ }
215067
+ return null;
215068
+ };
215069
+ let rel = null;
215070
+ const exp = manifest.exports;
215071
+ if (typeof exp === "string")
215072
+ rel = key === "." ? exp : null;
215073
+ else if (exp && typeof exp === "object")
215074
+ rel = pick(exp[key]);
215075
+ if (!rel && key === ".")
215076
+ rel = typeof manifest.main === "string" ? manifest.main : "index.js";
215077
+ if (!rel)
215078
+ throw new CliError(`${manifest.name} has no export "${key}"`);
215079
+ return join25(pkgDir, rel);
215080
+ }, isNotFound = (e2, spec) => {
215081
+ const code = e2?.code;
215082
+ const msg = e2 instanceof Error ? e2.message : String(e2);
215083
+ return (code === "ERR_MODULE_NOT_FOUND" || code === "MODULE_NOT_FOUND" || /Cannot find (module|package)/i.test(msg)) && msg.includes(spec.split("/")[0]);
215084
+ };
215085
+ var init_peerInstall = __esm(() => {
215086
+ init_package();
215087
+ init_peers();
215088
+ init_util();
215089
+ });
215090
+
214848
215091
  // src/designMeta.ts
214849
- import { readFileSync as readFileSync20 } from "node:fs";
214850
- import { join as join24 } from "node:path";
215092
+ import { readFileSync as readFileSync22 } from "node:fs";
215093
+ import { join as join26 } from "node:path";
214851
215094
  var PHONE_SIZE, readMetaFrames = (designDir) => {
214852
215095
  try {
214853
- const meta = JSON.parse(readFileSync20(join24(designDir, "meta.json"), "utf8"));
215096
+ const meta = JSON.parse(readFileSync22(join26(designDir, "meta.json"), "utf8"));
214854
215097
  const d = meta?.defaultSize;
214855
215098
  const valid = Array.isArray(d) && d.length === 2 && d.every((n2) => typeof n2 === "number" && Number.isFinite(n2) && n2 >= 64 && n2 <= 8192);
214856
215099
  return {
@@ -214874,8 +215117,8 @@ __export(exports_design, {
214874
215117
  flushDesignComments: () => flushDesignComments,
214875
215118
  design: () => design
214876
215119
  });
214877
- import { existsSync as existsSync24, mkdirSync as mkdirSync18, readFileSync as readFileSync21, readdirSync as readdirSync14, writeFileSync as writeFileSync19, appendFileSync } from "node:fs";
214878
- import { basename as basename11, join as join25 } from "node:path";
215120
+ import { existsSync as existsSync26, mkdirSync as mkdirSync19, readFileSync as readFileSync23, readdirSync as readdirSync14, writeFileSync as writeFileSync20, appendFileSync } from "node:fs";
215121
+ import { basename as basename11, join as join27 } from "node:path";
214879
215122
  var DEFAULT_DIR = "design", DEFAULT_PORT = 4477, getContext = (args) => {
214880
215123
  let root;
214881
215124
  let manifest;
@@ -214892,19 +215135,14 @@ var DEFAULT_DIR = "design", DEFAULT_PORT = 4477, getContext = (args) => {
214892
215135
  manifest,
214893
215136
  name: manifest?.name ?? basename11(root),
214894
215137
  dirName,
214895
- designDir: join25(root, dirName),
215138
+ designDir: join27(root, dirName),
214896
215139
  designKey: "/" + dirName.replace(/\\/g, "/")
214897
215140
  };
214898
215141
  }, loadDesignServer = async (ctx, needs = []) => {
214899
- if (!existsSync24(ctx.designDir)) {
215142
+ if (!existsSync26(ctx.designDir)) {
214900
215143
  throw new CliError(`No ${ctx.dirName}/ folder here. Run "lecodes design init" to scaffold it.`);
214901
215144
  }
214902
- let mod;
214903
- try {
214904
- mod = await import("lecodes-design/server");
214905
- } catch {
214906
- throw new CliError("The design canvas isn't installed. Run: npm install -g lecodes-design");
214907
- }
215145
+ const mod = await loadPeer("lecodes-design", "server", { for: "lecodes design" });
214908
215146
  const missing = needs.filter((name) => typeof mod[name] !== "function");
214909
215147
  if (missing.length) {
214910
215148
  throw new CliError(`This needs a newer lecodes-design — the installed one has no \`${missing[0]}\`. Run: npm install -g lecodes-design@latest`);
@@ -214912,7 +215150,7 @@ var DEFAULT_DIR = "design", DEFAULT_PORT = 4477, getContext = (args) => {
214912
215150
  return mod;
214913
215151
  }, requirePlatform = (ctx, verb) => {
214914
215152
  if (!ctx.manifest) {
214915
- throw new CliError(`\`lecodes design comments ${verb}\` needs a cloned project. This folder is standalone, so ${ctx.dirName}/comments/ is already the only store — there is nothing to sync with.`);
215153
+ throw new CliError(`\`lecodes design comments ${verb}\` needs a cloned project. This folder is standalone, so ` + `${ctx.dirName}/comments/ is already the only store — there is nothing to sync with.`);
214916
215154
  }
214917
215155
  const config = loadConfig();
214918
215156
  return { apiUrl: requireApiUrl(config), token: requireToken(config), uuid: ctx.manifest.uuid };
@@ -214928,17 +215166,17 @@ var DEFAULT_DIR = "design", DEFAULT_PORT = 4477, getContext = (args) => {
214928
215166
  `).find((l2) => l2.trim())?.trim() ?? "";
214929
215167
  return line.length > max ? line.slice(0, max - 1) + "…" : line;
214930
215168
  }, listScreens = (designDir) => {
214931
- const dir = join25(designDir, "screens");
214932
- if (!existsSync24(dir))
215169
+ const dir = join27(designDir, "screens");
215170
+ if (!existsSync26(dir))
214933
215171
  return [];
214934
215172
  return readdirSync14(dir).filter((f3) => f3.endsWith(".ts") && !f3.startsWith(".")).map((f3) => f3.slice(0, -3)).sort();
214935
215173
  }, writeMcpConfig = (ctx, port) => {
214936
- const path2 = join25(ctx.root, ".mcp.json");
215174
+ const path2 = join27(ctx.root, ".mcp.json");
214937
215175
  const entry = { type: "http", url: `http://localhost:${port}/mcp` };
214938
215176
  let config = {};
214939
- if (existsSync24(path2)) {
215177
+ if (existsSync26(path2)) {
214940
215178
  try {
214941
- config = JSON.parse(readFileSync21(path2, "utf8"));
215179
+ config = JSON.parse(readFileSync23(path2, "utf8"));
214942
215180
  } catch {
214943
215181
  warnErr(".mcp.json exists but isn't valid JSON — left it untouched. Add the lecodes-design server by hand.");
214944
215182
  return;
@@ -214951,7 +215189,7 @@ var DEFAULT_DIR = "design", DEFAULT_PORT = 4477, getContext = (args) => {
214951
215189
  return;
214952
215190
  }
214953
215191
  config.mcpServers["lecodes-design"] = entry;
214954
- writeFileSync19(path2, JSON.stringify(config, null, 2) + `
215192
+ writeFileSync20(path2, JSON.stringify(config, null, 2) + `
214955
215193
  `);
214956
215194
  log(`${c.green(existing ? "updated" : "created")} .mcp.json (lecodes-design MCP → ${entry.url})`);
214957
215195
  }, init = async (ctx, args) => {
@@ -214960,26 +215198,26 @@ var DEFAULT_DIR = "design", DEFAULT_PORT = 4477, getContext = (args) => {
214960
215198
  ["spec.md", SPEC_MD],
214961
215199
  ["README.md", README_HINT],
214962
215200
  ["CLAUDE.md", CLAUDE_MD],
214963
- [join25("shared", "tokens.ts"), TOKENS_TS],
214964
- [join25("shared", "tabs.ts"), TABS_TS],
214965
- [join25("screens", "home.ts"), HOME_SCREEN_TS]
215201
+ [join27("shared", "tokens.ts"), TOKENS_TS],
215202
+ [join27("shared", "tabs.ts"), TABS_TS],
215203
+ [join27("screens", "home.ts"), HOME_SCREEN_TS]
214966
215204
  ];
214967
- mkdirSync18(join25(ctx.designDir, "screens"), { recursive: true });
214968
- mkdirSync18(join25(ctx.designDir, "shared"), { recursive: true });
215205
+ mkdirSync19(join27(ctx.designDir, "screens"), { recursive: true });
215206
+ mkdirSync19(join27(ctx.designDir, "shared"), { recursive: true });
214969
215207
  let created = 0;
214970
215208
  for (const [rel, content] of files) {
214971
- const abs = join25(ctx.designDir, rel);
214972
- if (existsSync24(abs)) {
215209
+ const abs = join27(ctx.designDir, rel);
215210
+ if (existsSync26(abs)) {
214973
215211
  note(`kept ${ctx.dirName}/${rel.replace(/\\/g, "/")}`);
214974
215212
  continue;
214975
215213
  }
214976
- writeFileSync19(abs, content);
215214
+ writeFileSync20(abs, content);
214977
215215
  log(`${c.green("created")} ${ctx.dirName}/${rel.replace(/\\/g, "/")}`);
214978
215216
  created++;
214979
215217
  }
214980
- const ignorePath = join25(ctx.root, ".lecodesignore");
215218
+ const ignorePath = join27(ctx.root, ".lecodesignore");
214981
215219
  const ignoreLines = [`${ctx.dirName}/.snapshots/`, `${ctx.dirName}/comments/`];
214982
- const current = existsSync24(ignorePath) ? readFileSync21(ignorePath, "utf8") : "";
215220
+ const current = existsSync26(ignorePath) ? readFileSync23(ignorePath, "utf8") : "";
214983
215221
  const present = new Set(current.split(/\r?\n/));
214984
215222
  const missing = ignoreLines.filter((line) => !present.has(line));
214985
215223
  if (missing.length) {
@@ -214994,8 +215232,8 @@ var DEFAULT_DIR = "design", DEFAULT_PORT = 4477, getContext = (args) => {
214994
215232
  writeMcpConfig(ctx, Number(flagStr(args, "port")) || DEFAULT_PORT);
214995
215233
  const wroteTypes = materializeTypesLocal(ctx.designDir);
214996
215234
  writeTsconfig(ctx.designDir);
214997
- mkdirSync18(join25(ctx.designDir, ".lecodes", "types"), { recursive: true });
214998
- writeFileSync19(join25(ctx.designDir, ".lecodes", "types", "design-macros.d.ts"), DESIGN_MACROS_DTS);
215235
+ mkdirSync19(join27(ctx.designDir, ".lecodes", "types"), { recursive: true });
215236
+ writeFileSync20(join27(ctx.designDir, ".lecodes", "types", "design-macros.d.ts"), DESIGN_MACROS_DTS);
214999
215237
  log(`${c.green("created")} ${ctx.dirName}/tsconfig.json${wroteTypes ? ` + ${ctx.dirName}/.lecodes/types/` : ""} (IDE types)`);
215000
215238
  if (!wroteTypes)
215001
215239
  warnErr("Bundled SDK types not found — tsconfig written, but globals may show unresolved until the CLI is rebuilt with vendored types.");
@@ -215018,23 +215256,14 @@ var DEFAULT_DIR = "design", DEFAULT_PORT = 4477, getContext = (args) => {
215018
215256
  const allStates = flagBool(args, "all-states");
215019
215257
  if (stateFlag !== undefined && allStates)
215020
215258
  throw new CliError("Use either --state <name> or --all-states, not both.");
215021
- let renderer;
215022
- try {
215023
- renderer = await import("lecodes-renderer/headless");
215024
- } catch {
215025
- throw new CliError("The renderer isn't installed. Run: npm install -g lecodes-renderer");
215026
- }
215259
+ const renderer = await loadPeer("lecodes-renderer", "headless", { for: "lecodes design render" });
215027
215260
  let discover = null;
215028
215261
  if (stateFlag !== undefined || allStates) {
215029
- try {
215030
- const ds = await import("lecodes-design/server");
215031
- discover = (src) => {
215032
- const d = ds.discoverScreenStates(src);
215033
- return { states: d.states, canonical: d.canonical };
215034
- };
215035
- } catch {
215036
- throw new CliError("Rendering specific states needs the design canvas. Run: npm install -g lecodes-design");
215037
- }
215262
+ const ds = await loadPeer("lecodes-design", "server", { for: "lecodes design render --state" });
215263
+ discover = (src) => {
215264
+ const d = ds.discoverScreenStates(src);
215265
+ return { states: d.states, canonical: d.canonical };
215266
+ };
215038
215267
  }
215039
215268
  const killer = setTimeout(() => {
215040
215269
  warnErr("Snapshot timed out after 120s.");
@@ -215043,16 +215272,16 @@ var DEFAULT_DIR = "design", DEFAULT_PORT = 4477, getContext = (args) => {
215043
215272
  killer.unref?.();
215044
215273
  const png2 = flagBool(args, "png") || flagStr(args, "png") !== undefined;
215045
215274
  const frames = readMetaFrames(ctx.designDir);
215046
- const outDir = flagStr(args, "out-dir") ?? join25(ctx.designDir, ".snapshots");
215275
+ const outDir = flagStr(args, "out-dir") ?? join27(ctx.designDir, ".snapshots");
215047
215276
  const single = targets.length === 1 && requested.length === 1 && !png2 && !allStates && !flagStr(args, "out-dir");
215048
215277
  const onConsole = flagBool(args, "logs") ? (level, text) => logErr(`${c.dim(`[${level}]`)} ${text}`) : undefined;
215049
215278
  if (!single)
215050
- mkdirSync18(outDir, { recursive: true });
215279
+ mkdirSync19(outDir, { recursive: true });
215051
215280
  for (const id of targets) {
215052
215281
  const [width, height] = frames.screens[id]?.size ?? frames.defaultSize;
215053
215282
  let plan = [{ state: undefined, bases: [id] }];
215054
215283
  if (discover) {
215055
- const { states, canonical } = discover(readFileSync21(join25(ctx.designDir, "screens", `${id}.ts`), "utf8"));
215284
+ const { states, canonical } = discover(readFileSync23(join27(ctx.designDir, "screens", `${id}.ts`), "utf8"));
215056
215285
  if (stateFlag !== undefined) {
215057
215286
  if (!states.includes(stateFlag)) {
215058
215287
  if (requested.length === 1)
@@ -215093,11 +215322,11 @@ var DEFAULT_DIR = "design", DEFAULT_PORT = 4477, getContext = (args) => {
215093
215322
  for (const w of pngResult.warnings)
215094
215323
  warnErr(`${tag}: ${w}`);
215095
215324
  for (const base of bases) {
215096
- writeFileSync19(join25(outDir, `${base}.json`), result.text);
215097
- log(`${c.green("✓")} ${tag} → ${join25(outDir, `${base}.json`)}`);
215325
+ writeFileSync20(join27(outDir, `${base}.json`), result.text);
215326
+ log(`${c.green("✓")} ${tag} → ${join27(outDir, `${base}.json`)}`);
215098
215327
  if (pngResult) {
215099
- writeFileSync19(join25(outDir, `${base}.png`), pngResult.png);
215100
- log(`${c.green("✓")} ${tag} → ${join25(outDir, `${base}.png`)}`);
215328
+ writeFileSync20(join27(outDir, `${base}.png`), pngResult.png);
215329
+ log(`${c.green("✓")} ${tag} → ${join27(outDir, `${base}.png`)}`);
215101
215330
  }
215102
215331
  }
215103
215332
  }
@@ -215105,12 +215334,7 @@ var DEFAULT_DIR = "design", DEFAULT_PORT = 4477, getContext = (args) => {
215105
215334
  }, serve = async (ctx, args) => {
215106
215335
  const designServer = await loadDesignServer(ctx);
215107
215336
  const port = Number(flagStr(args, "port")) || DEFAULT_PORT;
215108
- let renderer = null;
215109
- try {
215110
- renderer = await import("lecodes-renderer/headless");
215111
- } catch {
215112
- renderer = null;
215113
- }
215337
+ const renderer = await loadPeer("lecodes-renderer", "headless", { for: "lecodes design (render_screen)", install: false });
215114
215338
  const renderScreen = renderer ? async (id, format, state) => {
215115
215339
  const r2 = renderer;
215116
215340
  const js = await compileDesignScreen({
@@ -215399,17 +215623,14 @@ var DEFAULT_DIR = "design", DEFAULT_PORT = 4477, getContext = (args) => {
215399
215623
  note("Read them with: lecodes design comments");
215400
215624
  }, flushDesignComments = async (args) => {
215401
215625
  const ctx = getContext(args);
215402
- if (!existsSync24(ctx.designDir))
215626
+ if (!existsSync26(ctx.designDir))
215403
215627
  return null;
215404
215628
  const platform = optionalPlatform(ctx);
215405
215629
  if (!platform)
215406
215630
  return null;
215407
- let designServer;
215408
- try {
215409
- designServer = await import("lecodes-design/server");
215410
- } catch {
215631
+ const designServer = await loadPeer("lecodes-design", "server", { for: "lecodes design", install: false });
215632
+ if (!designServer)
215411
215633
  return null;
215412
- }
215413
215634
  if (typeof designServer.queuedThreads !== "function")
215414
215635
  return null;
215415
215636
  if (typeof designServer.settleQueued !== "function" || designServer.settleQueued.length < 4)
@@ -215516,6 +215737,7 @@ var init_design = __esm(() => {
215516
215737
  init_browserAuth();
215517
215738
  init_designTemplates();
215518
215739
  init_util();
215740
+ init_peerInstall();
215519
215741
  init_config();
215520
215742
  init_api();
215521
215743
  init_types4();
@@ -216538,74 +216760,9 @@ var require_main = __commonJS((exports, module) => {
216538
216760
  }
216539
216761
  };
216540
216762
  });
216541
- // package.json
216542
- var package_default = {
216543
- name: "lecodes-cli",
216544
- version: "0.20.0",
216545
- dependencies: {
216546
- "@letary/chisel": "^0.10.0",
216547
- jimp: "^1.6.1",
216548
- "lecodes-sdk": "workspace:*"
216549
- },
216550
- devDependencies: {
216551
- "lecodes-design": "link:lecodes-design",
216552
- "lecodes-renderer": "workspace:*",
216553
- "qrcode-terminal": "^0.12.0",
216554
- "lecodes-3d-editor": "link:lecodes-3d-editor",
216555
- "lecodes-assets": "workspace:*",
216556
- "lecodes-creator-nav": "workspace:*",
216557
- "lecodes-viewer-lite": "workspace:*"
216558
- },
216559
- peerDependencies: {
216560
- "lecodes-design": "^0.9.0",
216561
- "lecodes-renderer": "^0.20.0",
216562
- "lecodes-3d-editor": "^0.7.0",
216563
- "lecodes-assets": "^0.2.1"
216564
- },
216565
- bin: {
216566
- lecodes: "dist/index.js"
216567
- },
216568
- description: "Clone, edit and push LeCodes projects from your machine.",
216569
- engines: {
216570
- node: ">=18"
216571
- },
216572
- files: [
216573
- "dist",
216574
- "src",
216575
- "runtime",
216576
- "README.md"
216577
- ],
216578
- keywords: [
216579
- "lecodes",
216580
- "cli"
216581
- ],
216582
- license: "MIT",
216583
- peerDependenciesMeta: {
216584
- "lecodes-design": {
216585
- optional: true
216586
- },
216587
- "lecodes-renderer": {
216588
- optional: true
216589
- },
216590
- "lecodes-3d-editor": {
216591
- optional: true
216592
- },
216593
- "lecodes-assets": {
216594
- optional: true
216595
- }
216596
- },
216597
- scripts: {
216598
- icon: "bun scripts/make-icon.ts",
216599
- "vendor-runtime": "bun scripts/vendor-runtime.ts",
216600
- "sync-peers": "bun scripts/sync-peer-versions.ts",
216601
- build: 'bun scripts/vendor-runtime.ts && bun build src/index.ts --target=node --outfile dist/index.js --external @letary/chisel --external lecodes-renderer --external lecodes-design --external lecodes-3d-editor --external lecodes-assets --banner="#!/usr/bin/env node"',
216602
- "build:bin": "bun scripts/build-bin.ts",
216603
- typecheck: "tsc --noEmit",
216604
- dev: "bun run src/index.ts",
216605
- prepublishOnly: "bun run sync-peers && bun run build"
216606
- },
216607
- type: "module"
216608
- };
216763
+
216764
+ // src/index.ts
216765
+ init_package();
216609
216766
 
216610
216767
  // src/commands/app.ts
216611
216768
  init_manifest();
@@ -216769,6 +216926,11 @@ var semverNewer = (a, b) => {
216769
216926
  return (pb[i] ?? 0) - (pa[i] ?? 0);
216770
216927
  return 0;
216771
216928
  };
216929
+ var newestForPlatform = (versions, key) => {
216930
+ const carrying = versions.filter((v) => v.files[key]).sort((a, b) => semverNewer(a.version, b.version));
216931
+ const newest = carrying[0];
216932
+ return newest ? { version: newest.version, file: newest.files[key] } : null;
216933
+ };
216772
216934
  var fetchReleaseProduct = async (product, label) => {
216773
216935
  let manifest;
216774
216936
  try {
@@ -216873,12 +217035,15 @@ var installedMatcVersions = () => {
216873
217035
  }
216874
217036
  };
216875
217037
  var fetchMatcManifest = () => fetchReleaseProduct("matc", "matc (shader compiler)");
216876
- var downloadMatc = async (version, versions, dest) => {
217038
+ var pickMatc = (versions) => {
216877
217039
  const key = platformKey();
216878
- const file = versions.find((v) => v.version === version)?.files[key];
216879
- if (!file) {
216880
- throw new CliError(`matc ${version} [${key}] isn't published — push the project once so shaders compile on the ` + `server, or set LECODES_MATC to a locally built matc.`);
217040
+ const hit = newestForPlatform(versions, key);
217041
+ if (!hit) {
217042
+ throw new CliError(`matc isn't published for ${key} — push the project once so shaders compile on the ` + `server, or set LECODES_MATC to a locally built matc.`);
216881
217043
  }
217044
+ return hit;
217045
+ };
217046
+ var downloadMatc = async (version, file, dest) => {
216882
217047
  note(`Downloading the shader compiler (matc ${version}, ${(file.size / 1024 / 1024).toFixed(1)} MB)…`);
216883
217048
  await downloadReleaseArchive(file, {
216884
217049
  cacheRoot: cacheRoot(),
@@ -216900,24 +217065,26 @@ var resolveMatcExe = async () => {
216900
217065
  const cached = resolveMatcExeCached();
216901
217066
  if (cached)
216902
217067
  return cached;
216903
- const { latest, versions } = await fetchMatcManifest();
216904
- const dir = join12(cacheRoot(), `matc-${latest}`);
216905
- await downloadMatc(latest, versions, dir);
217068
+ const { versions } = await fetchMatcManifest();
217069
+ const { version, file } = pickMatc(versions);
217070
+ const dir = join12(cacheRoot(), `matc-${version}`);
217071
+ await downloadMatc(version, file, dir);
216906
217072
  const exe = findMatc(dir);
216907
217073
  if (!exe)
216908
217074
  throw new CliError(`matc cache at ${dir} is broken — delete the folder and retry.`);
216909
217075
  return exe;
216910
217076
  };
216911
217077
  var updateMatc = async () => {
216912
- const { latest, versions } = await fetchMatcManifest();
217078
+ const { versions } = await fetchMatcManifest();
217079
+ const { version, file } = pickMatc(versions);
216913
217080
  const before = installedMatcVersions()[0];
216914
- const dir = join12(cacheRoot(), `matc-${latest}`);
217081
+ const dir = join12(cacheRoot(), `matc-${version}`);
216915
217082
  if (existsSync10(dir) && findMatc(dir) !== null) {
216916
- note(`Already up to date: matc ${latest}.`);
217083
+ note(`Already up to date: matc ${version}.`);
216917
217084
  return;
216918
217085
  }
216919
- await downloadMatc(latest, versions, dir);
216920
- note(before ? `matc updated to ${latest} (was ${before}).` : `matc ${latest} installed.`);
217086
+ await downloadMatc(version, file, dir);
217087
+ note(before ? `matc updated to ${version} (was ${before}).` : `matc ${version} installed.`);
216921
217088
  };
216922
217089
 
216923
217090
  // src/compile/shaders.ts
@@ -220133,6 +220300,8 @@ var windowEnv = (opts) => {
220133
220300
  env.CREATOR_MIN_WINDOW_SIZE = `${opts.minWidth}x${opts.minHeight}`;
220134
220301
  if (opts.title)
220135
220302
  env.CREATOR_WINDOW_TITLE = opts.title;
220303
+ if (opts.storageScope)
220304
+ env.CREATOR_STORAGE_SCOPE = opts.storageScope;
220136
220305
  if (opts.resizable === false)
220137
220306
  env.CREATOR_WINDOW_FIXED = "1";
220138
220307
  if (opts.fullscreen !== undefined)
@@ -220148,6 +220317,12 @@ var windowEnv = (opts) => {
220148
220317
  }
220149
220318
  if (opts.fps)
220150
220319
  env.CREATOR_FPS = "1";
220320
+ if (opts.mouseEmulateTouch !== undefined && process.env.CREATOR_MOUSE_TOUCH === undefined) {
220321
+ env.CREATOR_MOUSE_TOUCH = opts.mouseEmulateTouch ? "1" : "0";
220322
+ }
220323
+ if (opts.virtualKeyboard !== undefined && process.env.CREATOR_VIRTUAL_KEYBOARD === undefined) {
220324
+ env.CREATOR_VIRTUAL_KEYBOARD = typeof opts.virtualKeyboard === "boolean" ? opts.virtualKeyboard ? "1" : "0" : JSON.stringify(opts.virtualKeyboard);
220325
+ }
220151
220326
  return env;
220152
220327
  };
220153
220328
  var netArgs = (net) => {
@@ -220783,7 +220958,15 @@ var buildDesktop = async (args) => {
220783
220958
  assetCount++;
220784
220959
  }
220785
220960
  note(` assets: ${assetCount} files, ${mb2(assetBytes)} → ${ASSETS_DIR}/`);
220786
- writeJson(join20(out, "app.json"), { name, ...config.desktop ? { desktop: { window: config.desktop.window } } : {} });
220961
+ const desktop = config.desktop;
220962
+ writeJson(join20(out, "app.json"), {
220963
+ name,
220964
+ ...desktop ? { desktop: {
220965
+ window: desktop.window,
220966
+ ...desktop.mouseEmulateTouch !== undefined ? { mouseEmulateTouch: desktop.mouseEmulateTouch } : {},
220967
+ ...desktop.virtualKeyboard !== undefined ? { virtualKeyboard: desktop.virtualKeyboard } : {}
220968
+ } } : {}
220969
+ });
220787
220970
  if (config.icon) {
220788
220971
  await writeIco(root, config, join20(out, "app.ico"));
220789
220972
  note(" app.ico rendered from " + config.icon);
@@ -221741,7 +221924,7 @@ from \`main.ts\`.
221741
221924
 
221742
221925
  \`\`\`
221743
221926
  lecodes render # semantic JSON of the launched UI (structure, text, names, values)
221744
- lecodes render --png shot.png # pixels (needs @napi-rs/canvas installed)
221927
+ lecodes render --png shot.png # pixels (the renderer + @napi-rs/canvas install themselves on first use; --yes skips the prompt)
221745
221928
  lecodes render --png card.png --clip card # ONE component/region: a node name or x,y,w,h
221746
221929
  lecodes render src/screens/cart.ts # ONE screen module, without booting the app: any file that
221747
221930
  # \`export default\`s a UIScreen (or a (state) => UIScreen —
@@ -221761,6 +221944,11 @@ lecodes render --desktop --script <file> # the same scenario on the NATIVE host
221761
221944
  lecodes test # all tests/*.flow.json + design-derived flows; run before "done"
221762
221945
  lecodes test --desktop # the file flows on the NATIVE host (3D/Jolt/real GPU; coordinate
221763
221946
  # flows only — design flows stay headless)
221947
+ lecodes dev --android --time 12 --png phone.png # the REAL phone over adb (USB): launches the app on
221948
+ # the dev url via adb reverse, streams device logs as [device:log],
221949
+ # screenshots after 12 s, force-stops, exit 1 on a crash. --serial
221950
+ # <id> with several devices, --apk <file> to install a build first.
221951
+ # Without --time it runs until Ctrl+C (which stops the app).
221764
221952
  \`\`\`
221765
221953
 
221766
221954
  Iterate with \`--script\` scenarios and keep the useful ones as \`tests/<name>.flow.json\` — they
@@ -221815,6 +222003,13 @@ channel for game state that has no UI.
221815
222003
  with \`"fixtures": "fixtures.json"\` in its envelope.
221816
222004
  - \`fill\` fills every empty input — use it before tapping a disabled-until-filled submit.
221817
222005
 
222006
+ ## 3D scenes
222007
+
222008
+ - A HUD over a \`Scene\` is a widget ATTACHED to it: \`const hud = UIWidget(...).style({ left: 16,
222009
+ bottom: 16, pointerEvents: "none" }); hud.attachTo(scene); hud.show()\`. Never a \`UIScreen\`:
222010
+ a screen is a destination, so \`UIScreen(...).open()\` after \`scene.open()\` CLOSES the scene
222011
+ (the host logs "3D scene closed" and you see only the screen) — no background makes it transparent.
222012
+
221818
222013
  ## SDK documentation
221819
222014
 
221820
222015
  \`.lecodes/types/\` is the exact API surface (signatures only; generated and gitignored — run
@@ -221938,10 +222133,13 @@ var run = async (args) => {
221938
222133
  minHeight: win?.minHeight,
221939
222134
  resizable: win?.resizable,
221940
222135
  title: flagStr(args, "title") ?? win?.title ?? appCfg?.name,
222136
+ storageScope: appCfg?.name,
221941
222137
  fullscreen: flagBool(args, "fullscreen") ? true : flagBool(args, "windowed") ? false : win?.fullscreen,
221942
222138
  scale: resolveScale(flagStr(args, "scale"), win?.scale),
221943
222139
  renderScale: resolveRenderScale(flagStr(args, "render-scale"), appCfg?.desktop?.renderScale),
221944
222140
  maxRenderSize: appCfg?.desktop?.maxRenderSize,
222141
+ mouseEmulateTouch: appCfg?.desktop?.mouseEmulateTouch,
222142
+ virtualKeyboard: appCfg?.desktop?.virtualKeyboard,
221945
222143
  fps: flagBool(args, "fps"),
221946
222144
  net: flagBool(args, "server") ? { role: "server", port: Number(flagStr(args, "port")) || undefined, maxClients: Number(flagStr(args, "max-clients")) || undefined } : flagStr(args, "connect") ? { role: "client", address: flagStr(args, "connect") } : undefined
221947
222145
  });
@@ -221985,16 +222183,16 @@ init_design();
221985
222183
  // src/commands/dev.ts
221986
222184
  init_manifest();
221987
222185
  init_ignore();
221988
- import { existsSync as existsSync26, mkdirSync as mkdirSync19, readFileSync as readFileSync22, watch, writeFileSync as writeFileSync20 } from "node:fs";
222186
+ import { existsSync as existsSync29, mkdirSync as mkdirSync20, readFileSync as readFileSync24, watch, writeFileSync as writeFileSync22 } from "node:fs";
221989
222187
  import { randomBytes as randomBytes3 } from "node:crypto";
221990
222188
  import { networkInterfaces } from "node:os";
221991
- import { basename as basename12, join as join28 } from "node:path";
222189
+ import { basename as basename12, join as join31 } from "node:path";
221992
222190
 
221993
222191
  // src/dev/devServer.ts
221994
222192
  init_localFiles();
221995
222193
  import { createServer as createServer2 } from "node:http";
221996
222194
  import { createReadStream, statSync as statSync10 } from "node:fs";
221997
- import { join as join26 } from "node:path";
222195
+ import { join as join28 } from "node:path";
221998
222196
  init_projectCompile();
221999
222197
  init_fonts();
222000
222198
  init_assetIcons();
@@ -222273,14 +222471,14 @@ var sendText = (res, status2, type, body) => {
222273
222471
  res.end(body);
222274
222472
  };
222275
222473
  var sendJson = (res, status2, body) => sendText(res, status2, "application/json", JSON.stringify(body));
222276
- var readBody = (req) => new Promise((resolve11) => {
222474
+ var readBody = (req) => new Promise((resolve12) => {
222277
222475
  let body = "";
222278
222476
  req.on("data", (c4) => {
222279
222477
  if (body.length < 256 * 1024)
222280
222478
  body += c4.toString("utf8");
222281
222479
  });
222282
- req.on("end", () => resolve11(body));
222283
- req.on("error", () => resolve11(body));
222480
+ req.on("end", () => resolve12(body));
222481
+ req.on("error", () => resolve12(body));
222284
222482
  });
222285
222483
  var startDevServer = async (options) => {
222286
222484
  const { root, manifest, name, origin, token, entryOverride } = options;
@@ -222361,8 +222559,8 @@ var startDevServer = async (options) => {
222361
222559
  const t2 = url.searchParams.get("t");
222362
222560
  return t2 === "poll" ? "poll" : t2 === "web" ? "web" : "ws";
222363
222561
  };
222364
- const withPrelude = (js, url, v2) => {
222365
- const transport = transportOf(url);
222562
+ const withPrelude = (js, url, v2, forced) => {
222563
+ const transport = forced ?? transportOf(url);
222366
222564
  return transport === "web" ? js : js + `
222367
222565
  ` + renderPrelude({ origin, base, transport, v: v2 });
222368
222566
  };
@@ -222381,6 +222579,9 @@ var startDevServer = async (options) => {
222381
222579
  if (route === "/dev.js") {
222382
222580
  return sendText(res, 200, "text/javascript", withPrelude(devJs, url, -1));
222383
222581
  }
222582
+ if (route === "/dev-poll.js") {
222583
+ return sendText(res, 200, "text/javascript", withPrelude(devJs, url, -1, "poll"));
222584
+ }
222384
222585
  if (route === "/bundle.js") {
222385
222586
  if (bundleJs === null)
222386
222587
  return sendText(res, 503, "text/plain", "No successful compile yet — check the terminal.");
@@ -222398,7 +222599,7 @@ var startDevServer = async (options) => {
222398
222599
  const rel = route === "/web/" ? "embed.html" : route.slice("/web/".length);
222399
222600
  if (rel.split("/").some((seg) => seg === ".." || seg === "" || seg === "."))
222400
222601
  return sendText(res, 400, "text/plain", "Bad path");
222401
- const abs = join26(webDir, ...rel.split("/"));
222602
+ const abs = join28(webDir, ...rel.split("/"));
222402
222603
  try {
222403
222604
  if (!statSync10(abs).isFile())
222404
222605
  throw new Error("not a file");
@@ -222421,7 +222622,7 @@ var startDevServer = async (options) => {
222421
222622
  const name2 = route.slice("/shaders/".length);
222422
222623
  if (!isShaderArtifactName(name2))
222423
222624
  return sendText(res, 400, "text/plain", "Bad path");
222424
- const abs = join26(shadersCacheDir(root), name2);
222625
+ const abs = join28(shadersCacheDir(root), name2);
222425
222626
  try {
222426
222627
  if (!statSync10(abs).isFile())
222427
222628
  throw new Error("not a file");
@@ -222483,11 +222684,11 @@ var startDevServer = async (options) => {
222483
222684
  if (conn)
222484
222685
  clients.add(conn);
222485
222686
  });
222486
- await new Promise((resolve11, reject) => {
222687
+ await new Promise((resolve12, reject) => {
222487
222688
  server2.once("error", reject);
222488
222689
  server2.listen(options.port, options.bind ?? "0.0.0.0", () => {
222489
222690
  server2.removeListener("error", reject);
222490
- resolve11();
222691
+ resolve12();
222491
222692
  });
222492
222693
  });
222493
222694
  const address = server2.address();
@@ -222510,23 +222711,146 @@ var startDevServer = async (options) => {
222510
222711
 
222511
222712
  // src/dev/webRunner.ts
222512
222713
  init_distRoot();
222513
- import { existsSync as existsSync25 } from "node:fs";
222514
- import { createRequire as createRequire5 } from "node:module";
222515
- import { dirname as dirname14, join as join27 } from "node:path";
222714
+ import { existsSync as existsSync27 } from "node:fs";
222715
+ import { createRequire as createRequire6 } from "node:module";
222716
+ import { dirname as dirname15, join as join29 } from "node:path";
222516
222717
  var resolveWebRunnerDir = () => {
222517
- const candidates = [join27(distRoot, "runtime", "web")];
222718
+ const candidates = [join29(distRoot, "runtime", "web")];
222518
222719
  try {
222519
- const pkg = createRequire5(import.meta.url).resolve("lecodes-viewer-lite/package.json");
222520
- candidates.push(join27(dirname14(pkg), "dist-embed"));
222720
+ const pkg = createRequire6(import.meta.url).resolve("lecodes-viewer-lite/package.json");
222721
+ candidates.push(join29(dirname15(pkg), "dist-embed"));
222521
222722
  } catch {}
222522
222723
  for (const dir of candidates)
222523
- if (existsSync25(join27(dir, "embed.html")) && existsSync25(join27(dir, "embed.js")))
222724
+ if (existsSync27(join29(dir, "embed.html")) && existsSync27(join29(dir, "embed.js")))
222524
222725
  return dir;
222525
222726
  return null;
222526
222727
  };
222527
222728
 
222528
222729
  // src/commands/dev.ts
222529
222730
  init_browserAuth();
222731
+
222732
+ // src/dev/androidDev.ts
222733
+ init_util();
222734
+ import { existsSync as existsSync28, writeFileSync as writeFileSync21 } from "node:fs";
222735
+ import { join as join30 } from "node:path";
222736
+ import { spawnSync as spawnSync7 } from "node:child_process";
222737
+ var DEFAULT_ANDROID_PACKAGE = "com.letary.lecodes";
222738
+ var adbCandidates = () => {
222739
+ const out = [];
222740
+ if (process.env.LECODES_ADB)
222741
+ out.push(process.env.LECODES_ADB);
222742
+ out.push("adb");
222743
+ const exe = process.platform === "win32" ? "adb.exe" : "adb";
222744
+ for (const home of [process.env.ANDROID_HOME, process.env.ANDROID_SDK_ROOT]) {
222745
+ if (home)
222746
+ out.push(join30(home, "platform-tools", exe));
222747
+ }
222748
+ if (process.platform === "win32" && process.env.LOCALAPPDATA)
222749
+ out.push(join30(process.env.LOCALAPPDATA, "Android", "Sdk", "platform-tools", exe));
222750
+ if (process.env.HOME)
222751
+ out.push(join30(process.env.HOME, "Android", "Sdk", "platform-tools", exe), join30(process.env.HOME, "Library", "Android", "sdk", "platform-tools", exe));
222752
+ return out;
222753
+ };
222754
+ var run2 = (adb, args, input) => {
222755
+ const r2 = spawnSync7(adb, args, { encoding: "utf8", input, windowsHide: true, maxBuffer: 64 * 1024 * 1024 });
222756
+ const out = `${r2.stdout ?? ""}${r2.stderr ?? ""}`.trim();
222757
+ return { ok: r2.status === 0, out };
222758
+ };
222759
+ var resolveAdb = () => {
222760
+ for (const cand of adbCandidates()) {
222761
+ if (cand !== "adb" && !existsSync28(cand))
222762
+ continue;
222763
+ const r2 = spawnSync7(cand, ["version"], { encoding: "utf8", windowsHide: true });
222764
+ if (r2.status === 0)
222765
+ return cand;
222766
+ }
222767
+ throw new CliError("adb not found — install Android platform-tools (or point LECODES_ADB at adb).");
222768
+ };
222769
+ var listDevices = (adb) => {
222770
+ const { out } = run2(adb, ["devices", "-l"]);
222771
+ const devices = [];
222772
+ for (const line of out.split(/\r?\n/).slice(1)) {
222773
+ const m = /^(\S+)\s+(\S+)(.*)$/.exec(line.trim());
222774
+ if (!m)
222775
+ continue;
222776
+ const model = /model:(\S+)/.exec(m[3] ?? "")?.[1];
222777
+ devices.push({ serial: m[1], state: m[2], model });
222778
+ }
222779
+ return devices;
222780
+ };
222781
+ var pickDevice = (adb, serial) => {
222782
+ const all = listDevices(adb);
222783
+ if (serial) {
222784
+ const d = all.find((x2) => x2.serial === serial);
222785
+ if (!d)
222786
+ throw new CliError(`No device "${serial}" — adb sees: ${all.map((x2) => `${x2.serial} (${x2.state})`).join(", ") || "nothing"}.`);
222787
+ if (d.state !== "device")
222788
+ throw new CliError(`Device ${serial} is "${d.state}" — accept the USB-debugging prompt on the phone.`);
222789
+ return serial;
222790
+ }
222791
+ const ready = all.filter((d) => d.state === "device");
222792
+ if (ready.length === 1)
222793
+ return ready[0].serial;
222794
+ if (ready.length === 0) {
222795
+ const other = all.map((d) => `${d.serial} (${d.state})`).join(", ");
222796
+ throw new CliError(other ? `No usable device: adb sees ${other}. "unauthorized" = accept the USB-debugging prompt on the phone; "offline" = replug.` : "No Android device: enable USB debugging (Developer options), plug the phone in, accept the prompt — or `adb connect <ip>:5555` for Wi-Fi debugging.");
222797
+ }
222798
+ throw new CliError(`Several devices: ${ready.map((d) => `${d.serial}${d.model ? ` (${d.model})` : ""}`).join(", ")} — pick one with --serial <id>.`);
222799
+ };
222800
+ var launchOnAndroid = (opts) => {
222801
+ const adb = resolveAdb();
222802
+ const serial = pickDevice(adb, opts.serial);
222803
+ const packageName = opts.packageName ?? process.env.LECODES_ANDROID_PACKAGE ?? DEFAULT_ANDROID_PACKAGE;
222804
+ const dev = (args) => run2(adb, ["-s", serial, ...args]);
222805
+ if (opts.apk) {
222806
+ if (!existsSync28(opts.apk))
222807
+ throw new CliError(`--apk: ${opts.apk} does not exist.`);
222808
+ note(`Installing ${opts.apk}…`);
222809
+ const r2 = dev(["install", "-r", opts.apk]);
222810
+ if (!r2.ok || !/Success/.test(r2.out)) {
222811
+ throw new CliError(`adb install failed: ${r2.out}${/USER_RESTRICTED/.test(r2.out) ? `
222812
+ (MIUI: enable "Install via USB" in Developer options.)` : ""}`);
222813
+ }
222814
+ }
222815
+ const installed = dev(["shell", "pm", "path", packageName]);
222816
+ if (!installed.ok || !installed.out.includes("package:")) {
222817
+ throw new CliError(`${packageName} is not installed on ${serial} — pass --apk <file> (lecodes-android: .\\gradlew :app:assembleDebug → app/build/outputs/apk/debug/app-debug.apk).`);
222818
+ }
222819
+ const rev = dev(["reverse", `tcp:${opts.port}`, `tcp:${opts.port}`]);
222820
+ if (!rev.ok)
222821
+ warnErr(`adb reverse failed (${rev.out}) — the phone must reach the LAN url instead.`);
222822
+ dev(["shell", "am", "force-stop", packageName]);
222823
+ dev(["logcat", "-c"]);
222824
+ const start = dev(["shell", "am", "start", "-n", `${packageName}/.MainActivity`, "-a", "android.intent.action.VIEW", "-d", opts.url]);
222825
+ if (!start.ok || /Error/.test(start.out))
222826
+ throw new CliError(`Could not launch ${packageName}: ${start.out}`);
222827
+ let stopped = false;
222828
+ return {
222829
+ serial,
222830
+ packageName,
222831
+ adb,
222832
+ screenshot: (file) => {
222833
+ const r2 = spawnSync7(adb, ["-s", serial, "exec-out", "screencap", "-p"], { windowsHide: true, maxBuffer: 64 * 1024 * 1024 });
222834
+ if (r2.status !== 0 || !r2.stdout || r2.stdout.length === 0)
222835
+ return false;
222836
+ writeFileSync21(file, r2.stdout);
222837
+ return true;
222838
+ },
222839
+ stop: () => {
222840
+ if (stopped)
222841
+ return;
222842
+ stopped = true;
222843
+ dev(["shell", "am", "force-stop", packageName]);
222844
+ dev(["reverse", "--remove", `tcp:${opts.port}`]);
222845
+ }
222846
+ };
222847
+ };
222848
+ var androidCrashLines = (session) => {
222849
+ const { out } = run2(session.adb, ["-s", session.serial, "logcat", "-d", "-v", "brief"]);
222850
+ return out.split(/\r?\n/).filter((l2) => /FATAL EXCEPTION|Fatal signal|\[crash\]/.test(l2) || /AndroidRuntime/.test(l2) && l2.includes(session.packageName)).slice(-20);
222851
+ };
222852
+
222853
+ // src/commands/dev.ts
222530
222854
  init_util();
222531
222855
  var DEFAULT_PORT2 = 4499;
222532
222856
  var DEBOUNCE_MS = 120;
@@ -222554,24 +222878,24 @@ var lanCandidates = () => {
222554
222878
  return out.sort((x2, y2) => x2.rank - y2.rank).map(({ address, iface }) => ({ address, iface }));
222555
222879
  };
222556
222880
  var loadDevToken = (root) => {
222557
- const path2 = join28(root, LECODES_DIR, "dev.json");
222881
+ const path2 = join31(root, LECODES_DIR, "dev.json");
222558
222882
  try {
222559
- const token2 = JSON.parse(readFileSync22(path2, "utf8")).token;
222883
+ const token2 = JSON.parse(readFileSync24(path2, "utf8")).token;
222560
222884
  if (token2 && /^[a-z0-9]{8,}$/.test(token2))
222561
222885
  return token2;
222562
222886
  } catch {}
222563
222887
  const token = randomBytes3(6).toString("hex");
222564
- mkdirSync19(join28(root, LECODES_DIR), { recursive: true });
222565
- writeFileSync20(path2, JSON.stringify({ token }, null, 2) + `
222888
+ mkdirSync20(join31(root, LECODES_DIR), { recursive: true });
222889
+ writeFileSync22(path2, JSON.stringify({ token }, null, 2) + `
222566
222890
  `);
222567
222891
  return token;
222568
222892
  };
222569
222893
  var printQr = async (url) => {
222570
222894
  try {
222571
222895
  const qrcode = (await Promise.resolve().then(() => __toESM(require_main(), 1))).default;
222572
- await new Promise((resolve11) => qrcode.generate(url, { small: true }, (out) => {
222896
+ await new Promise((resolve12) => qrcode.generate(url, { small: true }, (out) => {
222573
222897
  log(out);
222574
- resolve11();
222898
+ resolve12();
222575
222899
  }));
222576
222900
  } catch {
222577
222901
  note("(couldn't render a QR code — open the URL below on the device)");
@@ -222645,6 +222969,7 @@ ${first.error}`);
222645
222969
  return true;
222646
222970
  };
222647
222971
  const debug2 = process.env.LECODES_DEV_DEBUG === "1";
222972
+ const underBun = typeof globalThis.Bun !== "undefined";
222648
222973
  let timer = null;
222649
222974
  const onChange = (file) => {
222650
222975
  if (debug2)
@@ -222687,7 +223012,7 @@ ${result.error}`);
222687
223012
  if (alternatives.length > 0) {
222688
223013
  note(`Device can't reach it? This machine also has ${alternatives.map((a2) => `${a2.address} (${a2.iface})`).join(", ")} — retry with --host <ip>.`);
222689
223014
  }
222690
- if (existsSync26(join28(root, LECODES_DIR, "manifest.json")))
223015
+ if (existsSync29(join31(root, LECODES_DIR, "manifest.json")))
222691
223016
  note(`Token stored in ${LECODES_DIR}/dev.json (delete it to rotate the URL).`);
222692
223017
  log("");
222693
223018
  log(`Watching ${root} — save a file to hot-reload. Ctrl+C to stop.`);
@@ -222695,6 +223020,48 @@ ${result.error}`);
222695
223020
  note("Opening the browser…");
222696
223021
  openBrowser(webLocal);
222697
223022
  }
223023
+ if (flagBool(args, "android")) {
223024
+ const seconds = Number(flagStr(args, "time")) || 0;
223025
+ const png2 = flagStr(args, "png");
223026
+ const deviceUrl = `http://127.0.0.1:${port}${server2.url.slice(origin.length)}`.replace(/\/dev\.js$/, underBun ? "/dev-poll.js" : "/dev.js");
223027
+ let session;
223028
+ try {
223029
+ session = launchOnAndroid({ port, url: deviceUrl, serial: flagStr(args, "serial"), apk: flagStr(args, "apk"), packageName: flagStr(args, "package") });
223030
+ } catch (e2) {
223031
+ server2.close();
223032
+ watcher?.close();
223033
+ throw e2;
223034
+ }
223035
+ note(`Android: ${session.serial} → ${deviceUrl} (adb reverse; device logs stream here as [device:log])`);
223036
+ const finish = (code) => {
223037
+ session.stop();
223038
+ server2.close();
223039
+ watcher?.close();
223040
+ process.exit(code);
223041
+ };
223042
+ process.on("SIGINT", () => {
223043
+ note("Stopping the app on the phone…");
223044
+ finish(0);
223045
+ });
223046
+ if (seconds > 0) {
223047
+ setTimeout(() => {
223048
+ const crashes = androidCrashLines(session);
223049
+ if (png2) {
223050
+ if (session.screenshot(png2))
223051
+ note(`Wrote ${png2} (device screenshot after ${seconds} s)`);
223052
+ else
223053
+ warnErr(`Screenshot failed (adb screencap).`);
223054
+ }
223055
+ if (crashes.length > 0) {
223056
+ warnErr(`The app crashed on the phone:
223057
+ ${crashes.join(`
223058
+ `)}`);
223059
+ finish(1);
223060
+ }
223061
+ finish(0);
223062
+ }, seconds * 1000);
223063
+ }
223064
+ }
222698
223065
  if (flagBool(args, "desktop")) {
222699
223066
  const appCfg = readAppConfigAt(root);
222700
223067
  const win = appCfg?.desktop?.window;
@@ -222720,7 +223087,7 @@ init_localFiles();
222720
223087
 
222721
223088
  // src/serverDiff.ts
222722
223089
  init_localFiles();
222723
- import { readFileSync as readFileSync23 } from "node:fs";
223090
+ import { readFileSync as readFileSync25 } from "node:fs";
222724
223091
  import { basename as basename13 } from "node:path";
222725
223092
  var isTextAsset = (t2) => t2 === "text" || t2 === "shader" || t2 === "scene";
222726
223093
  var computeServerDiff = (project, scan2) => {
@@ -222739,7 +223106,7 @@ var computeServerDiff = (project, scan2) => {
222739
223106
  const local = localByPath.get(path2);
222740
223107
  if (local && !server2) {
222741
223108
  const text = isTextKind(classifyKind(basename13(path2)));
222742
- entries.push({ path: path2, status: "added", binary: !text, serverText: "", localText: text ? readFileSync23(local.absPath, "utf8") : undefined });
223109
+ entries.push({ path: path2, status: "added", binary: !text, serverText: "", localText: text ? readFileSync25(local.absPath, "utf8") : undefined });
222743
223110
  continue;
222744
223111
  }
222745
223112
  if (server2 && !local) {
@@ -222750,12 +223117,12 @@ var computeServerDiff = (project, scan2) => {
222750
223117
  if (server2 && local) {
222751
223118
  if (isTextAsset(server2.type)) {
222752
223119
  const serverText = server2.text ?? "";
222753
- const localText = readFileSync23(local.absPath, "utf8");
223120
+ const localText = readFileSync25(local.absPath, "utf8");
222754
223121
  if (serverText !== localText)
222755
223122
  entries.push({ path: path2, status: "modified", binary: false, serverText, localText });
222756
223123
  } else {
222757
223124
  const serverSize = server2.file?.size;
222758
- const localSize = Buffer.byteLength(readFileSync23(local.absPath));
223125
+ const localSize = Buffer.byteLength(readFileSync25(local.absPath));
222759
223126
  if (serverSize !== undefined && serverSize !== localSize) {
222760
223127
  entries.push({ path: path2, status: "modified", binary: true });
222761
223128
  }
@@ -222913,8 +223280,8 @@ var statusLabel = (e2) => e2.status === "added" ? c.green(" added ") : e2.st
222913
223280
  init_manifest();
222914
223281
  init_types4();
222915
223282
  init_ignore();
222916
- import { existsSync as existsSync27, mkdirSync as mkdirSync20, writeFileSync as writeFileSync21 } from "node:fs";
222917
- import { basename as basename14, dirname as dirname15, join as join29, resolve as resolve11 } from "node:path";
223283
+ import { existsSync as existsSync30, mkdirSync as mkdirSync21, writeFileSync as writeFileSync23 } from "node:fs";
223284
+ import { basename as basename14, dirname as dirname16, join as join32, resolve as resolve12 } from "node:path";
222918
223285
  init_util();
222919
223286
  var STARTER2 = `// Welcome to LeCodes — the whole SDK is available as globals, no imports needed.
222920
223287
  // Run it live on a device with \`lecodes dev\`, or bundle it with \`lecodes compile -o app.js\`.
@@ -223022,18 +223389,18 @@ Full command list: \`lecodes --help\`.
223022
223389
  `;
223023
223390
  var init2 = async (args) => {
223024
223391
  const dirArg = (args._[0] ?? ".").replace(/[/\\]+$/, "") || ".";
223025
- const root = resolve11(dirArg);
223392
+ const root = resolve12(dirArg);
223026
223393
  const name = basename14(root);
223027
- mkdirSync20(root, { recursive: true });
223394
+ mkdirSync21(root, { recursive: true });
223028
223395
  let created = 0;
223029
223396
  const write = (rel, content) => {
223030
- const abs = join29(root, rel);
223031
- if (existsSync27(abs)) {
223397
+ const abs = join32(root, rel);
223398
+ if (existsSync30(abs)) {
223032
223399
  note(`kept ${rel}`);
223033
223400
  return;
223034
223401
  }
223035
- mkdirSync20(dirname15(abs), { recursive: true });
223036
- writeFileSync21(abs, content);
223402
+ mkdirSync21(dirname16(abs), { recursive: true });
223403
+ writeFileSync23(abs, content);
223037
223404
  log(`${c.green("created")} ${rel}`);
223038
223405
  created++;
223039
223406
  };
@@ -223080,8 +223447,8 @@ init_config();
223080
223447
  init_ignore();
223081
223448
  init_localFiles();
223082
223449
  init_manifest();
223083
- import { existsSync as existsSync28 } from "node:fs";
223084
- import { basename as basename16, join as join30, resolve as resolve12 } from "node:path";
223450
+ import { existsSync as existsSync31 } from "node:fs";
223451
+ import { basename as basename16, join as join33, resolve as resolve13 } from "node:path";
223085
223452
  init_types4();
223086
223453
  init_util();
223087
223454
 
@@ -223091,7 +223458,7 @@ init_config();
223091
223458
  init_manifest();
223092
223459
  init_localFiles();
223093
223460
  import { basename as basename15 } from "node:path";
223094
- import { readFileSync as readFileSync24 } from "node:fs";
223461
+ import { readFileSync as readFileSync26 } from "node:fs";
223095
223462
  init_util();
223096
223463
  var push = async (args) => {
223097
223464
  const root = findProjectRoot(process.cwd());
@@ -223152,9 +223519,9 @@ var push = async (args) => {
223152
223519
  if (source)
223153
223520
  entry.assetId = source.id;
223154
223521
  if (isTextKind(kind)) {
223155
- entry.text = readFileSync24(file.absPath, "utf8");
223522
+ entry.text = readFileSync26(file.absPath, "utf8");
223156
223523
  } else if (!source || source.sha !== file.sha) {
223157
- const blob = await uploadBlob(apiUrl, token, manifest.uuid, basename15(file.path), readFileSync24(file.absPath));
223524
+ const blob = await uploadBlob(apiUrl, token, manifest.uuid, basename15(file.path), readFileSync26(file.absPath));
223158
223525
  entry.blobFileId = blob.id;
223159
223526
  uploaded++;
223160
223527
  }
@@ -223261,10 +223628,10 @@ var link = async (args) => {
223261
223628
  const apiUrl = requireApiUrl(config);
223262
223629
  const token = requireToken(config);
223263
223630
  const dirArg = args._[0];
223264
- const root = dirArg ? resolve12(dirArg) : process.cwd();
223265
- if (!existsSync28(root))
223631
+ const root = dirArg ? resolve13(dirArg) : process.cwd();
223632
+ if (!existsSync31(root))
223266
223633
  throw new CliError(`No such directory: ${root}`);
223267
- if (existsSync28(join30(root, LECODES_DIR, "manifest.json")) && !flagBool(args, "force")) {
223634
+ if (existsSync31(join33(root, LECODES_DIR, "manifest.json")) && !flagBool(args, "force")) {
223268
223635
  throw new CliError("This folder is already linked (.lecodes/manifest.json exists) — use `lecodes push`, or `lecodes link --force` to re-link it to another project.");
223269
223636
  }
223270
223637
  let enclosing = null;
@@ -223328,10 +223695,10 @@ var link = async (args) => {
223328
223695
 
223329
223696
  // src/commands/lightmap.ts
223330
223697
  init_manifest();
223331
- import { existsSync as existsSync29, mkdirSync as mkdirSync21, mkdtempSync as mkdtempSync4, readFileSync as readFileSync25, renameSync as renameSync3, rmSync as rmSync9, statSync as statSync11, unlinkSync, writeFileSync as writeFileSync22 } from "node:fs";
223332
- import { spawn as spawn6, spawnSync as spawnSync6 } from "node:child_process";
223698
+ import { existsSync as existsSync32, mkdirSync as mkdirSync22, mkdtempSync as mkdtempSync4, readFileSync as readFileSync27, renameSync as renameSync3, rmSync as rmSync9, statSync as statSync11, unlinkSync, writeFileSync as writeFileSync24 } from "node:fs";
223699
+ import { spawn as spawn6, spawnSync as spawnSync8 } from "node:child_process";
223333
223700
  import { tmpdir as tmpdir4 } from "node:os";
223334
- import { join as join31, relative as relative4, resolve as resolve13 } from "node:path";
223701
+ import { join as join34, relative as relative4, resolve as resolve14 } from "node:path";
223335
223702
  init_util();
223336
223703
  var num = (args, key, fallback, min, max) => {
223337
223704
  const raw = flagStr(args, key);
@@ -223344,20 +223711,20 @@ var num = (args, key, fallback, min, max) => {
223344
223711
  };
223345
223712
  var findToktx = () => {
223346
223713
  const env = process.env.KTX_TOKTX;
223347
- if (env && existsSync29(env))
223714
+ if (env && existsSync32(env))
223348
223715
  return env;
223349
- for (const base of [process.env.LOCALAPPDATA && join31(process.env.LOCALAPPDATA, "Programs"), process.env.ProgramFiles]) {
223716
+ for (const base of [process.env.LOCALAPPDATA && join34(process.env.LOCALAPPDATA, "Programs"), process.env.ProgramFiles]) {
223350
223717
  if (!base)
223351
223718
  continue;
223352
- const guess = join31(base, "KTX-Software", "bin", "toktx.exe");
223353
- if (existsSync29(guess))
223719
+ const guess = join34(base, "KTX-Software", "bin", "toktx.exe");
223720
+ if (existsSync32(guess))
223354
223721
  return guess;
223355
223722
  }
223356
223723
  return "toktx";
223357
223724
  };
223358
223725
  var checkToktx = (toktx) => {
223359
223726
  try {
223360
- const p2 = spawnSync6(toktx, ["--version"], { encoding: "utf8" });
223727
+ const p2 = spawnSync8(toktx, ["--version"], { encoding: "utf8" });
223361
223728
  if (p2.status === 0)
223362
223729
  return (p2.stdout ?? "").toString().trim() || "toktx";
223363
223730
  } catch {}
@@ -223371,7 +223738,7 @@ var requireToktx = () => {
223371
223738
  }
223372
223739
  return toktx;
223373
223740
  };
223374
- var encodeKtx2 = (toktx, png2, ktx2) => new Promise((resolve14, reject) => {
223741
+ var encodeKtx2 = (toktx, png2, ktx2) => new Promise((resolve15, reject) => {
223375
223742
  const args = ["--t2", "--encode", "uastc", "--uastc_quality", "2", "--zcmp", "18", "--genmipmap", "--assign_oetf", "srgb", ktx2, png2];
223376
223743
  const p2 = spawn6(toktx, args, { stdio: ["ignore", "pipe", "pipe"] });
223377
223744
  let out = "";
@@ -223384,7 +223751,7 @@ var encodeKtx2 = (toktx, png2, ktx2) => new Promise((resolve14, reject) => {
223384
223751
  p2.on("error", (e2) => reject(new CliError(`toktx failed (${e2.message}) while encoding ${ktx2}`)));
223385
223752
  p2.on("close", (status2) => {
223386
223753
  if (status2 === 0)
223387
- resolve14();
223754
+ resolve15();
223388
223755
  else
223389
223756
  reject(new CliError(`toktx failed (${status2 ?? "?"}) while encoding ${ktx2}:
223390
223757
  ${out.trim()}`));
@@ -223399,13 +223766,13 @@ var bake = async (args) => {
223399
223766
  root = process.cwd();
223400
223767
  }
223401
223768
  const toktx = requireToktx();
223402
- const outDir = resolve13(root, flagStr(args, "out") ?? "assets/lightmap");
223769
+ const outDir = resolve14(root, flagStr(args, "out") ?? "assets/lightmap");
223403
223770
  const stem = flagStr(args, "name") ?? "lightmap";
223404
223771
  if (!/^[A-Za-z0-9_-]+$/.test(stem))
223405
223772
  throw new CliError(`--name wants a plain file stem (letters, digits, - and _), got "${stem}".`);
223406
223773
  const entryFlag = flagStr(args, "entry");
223407
223774
  const entryFile = entryFlag ?? "main.ts";
223408
- if (!existsSync29(resolve13(root, entryFile))) {
223775
+ if (!existsSync32(resolve14(root, entryFile))) {
223409
223776
  throw new CliError(`No ${entryFile} in ${root} — pass --entry <file> (the file that builds the scene and calls Lightmap.load).`);
223410
223777
  }
223411
223778
  const cfg = {
@@ -223437,7 +223804,7 @@ var bake = async (args) => {
223437
223804
  const entryText = `globalThis.__lecodesLightmap = ${JSON.stringify(cfg)}
223438
223805
  import ${JSON.stringify(importPath)}
223439
223806
  `;
223440
- mkdirSync21(outDir, { recursive: true });
223807
+ mkdirSync22(outDir, { recursive: true });
223441
223808
  ensurePlaceholders(outDir, stem);
223442
223809
  const desktopRenderer = resolveDesktopRenderer(root, args);
223443
223810
  const { js, resources } = await buildBundle(args, undefined, {
@@ -223446,8 +223813,8 @@ import ${JSON.stringify(importPath)}
223446
223813
  extraEntries: [{ path: entryPath, type: "text", text: entryText }],
223447
223814
  entryOverride: entryPath
223448
223815
  });
223449
- const scratch = mkdtempSync4(join31(tmpdir4(), "lecodes-lightmap-"));
223450
- const outPng = join31(scratch, "frame.png");
223816
+ const scratch = mkdtempSync4(join34(tmpdir4(), "lecodes-lightmap-"));
223817
+ const outPng = join34(scratch, "frame.png");
223451
223818
  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}.*…`);
223452
223819
  let output = "";
223453
223820
  try {
@@ -223481,18 +223848,18 @@ import ${JSON.stringify(importPath)}
223481
223848
  logErr(text.startsWith("error") ? c.red(`✗ ${text}`) : text.startsWith("warning") ? c.yellow(` ${text}`) : c.dim(` ${text}`));
223482
223849
  }
223483
223850
  if (/\[lightmap\] done/.test(output)) {
223484
- const bakePath = join31(outDir, "lightmap.bake");
223485
- if (!existsSync29(bakePath))
223851
+ const bakePath = join34(outDir, "lightmap.bake");
223852
+ if (!existsSync32(bakePath))
223486
223853
  throw new CliError(`The host reported a bake but ${bakePath} is missing.`);
223487
- const data = JSON.parse(readFileSync25(bakePath, "utf8"));
223854
+ const data = JSON.parse(readFileSync27(bakePath, "utf8"));
223488
223855
  const pages = Math.max(1, data.pages ?? 1);
223489
223856
  const hasLight = !!data.lightTextures?.length;
223490
223857
  const t0 = Date.now();
223491
223858
  let pngKb = 0, ktxKb = 0;
223492
223859
  const encodePage = async (page, light) => {
223493
- const png2 = join31(outDir, pageFile("lightmap", page, light, ".png"));
223494
- const ktx2 = join31(outDir, pageFile(stem, page, light));
223495
- if (!existsSync29(png2))
223860
+ const png2 = join34(outDir, pageFile("lightmap", page, light, ".png"));
223861
+ const ktx2 = join34(outDir, pageFile(stem, page, light));
223862
+ if (!existsSync32(png2))
223496
223863
  throw new CliError(`The host reported a bake but ${png2} is missing.`);
223497
223864
  await encodeKtx2(toktx, png2, ktx2);
223498
223865
  pngKb += statSync11(png2).size / 1024;
@@ -223508,15 +223875,15 @@ import ${JSON.stringify(importPath)}
223508
223875
  await Promise.all(encodes);
223509
223876
  for (let p2 = pages;p2 < 16; p2++) {
223510
223877
  for (const light of [false, true]) {
223511
- const stale = join31(outDir, pageFile(stem, p2, light));
223512
- if (existsSync29(stale) && statSync11(stale).size !== PLACEHOLDER_KTX2.length)
223513
- writeFileSync22(stale, PLACEHOLDER_KTX2);
223878
+ const stale = join34(outDir, pageFile(stem, p2, light));
223879
+ if (existsSync32(stale) && statSync11(stale).size !== PLACEHOLDER_KTX2.length)
223880
+ writeFileSync24(stale, PLACEHOLDER_KTX2);
223514
223881
  }
223515
223882
  }
223516
223883
  if (!hasLight) {
223517
- const light = join31(outDir, pageFile(stem, 0, true));
223518
- if (existsSync29(light) && statSync11(light).size !== PLACEHOLDER_KTX2.length)
223519
- writeFileSync22(light, PLACEHOLDER_KTX2);
223884
+ const light = join34(outDir, pageFile(stem, 0, true));
223885
+ if (existsSync32(light) && statSync11(light).size !== PLACEHOLDER_KTX2.length)
223886
+ writeFileSync24(light, PLACEHOLDER_KTX2);
223520
223887
  }
223521
223888
  data.texture = pageFile(stem, 0, false);
223522
223889
  data.textures = Array.from({ length: pages }, (_, p2) => pageFile(stem, p2, false));
@@ -223524,19 +223891,19 @@ import ${JSON.stringify(importPath)}
223524
223891
  data.lightTextures = Array.from({ length: pages }, (_, p2) => pageFile(stem, p2, true));
223525
223892
  if (data.volume)
223526
223893
  data.volume.file = `${stem}.volume`;
223527
- writeFileSync22(join31(outDir, `${stem}.bake`), JSON.stringify(data, null, 1) + `
223894
+ writeFileSync24(join34(outDir, `${stem}.bake`), JSON.stringify(data, null, 1) + `
223528
223895
  `);
223529
223896
  if (stem !== "lightmap")
223530
223897
  unlinkSync(bakePath);
223531
- const volumeSrc = join31(outDir, "lightmap.volume");
223532
- const hasVolume = cfg.volumeCell > 0 && existsSync29(volumeSrc);
223898
+ const volumeSrc = join34(outDir, "lightmap.volume");
223899
+ const hasVolume = cfg.volumeCell > 0 && existsSync32(volumeSrc);
223533
223900
  if (hasVolume && stem !== "lightmap") {
223534
- rmSync9(join31(outDir, `${stem}.volume`), { force: true });
223535
- renameSync3(volumeSrc, join31(outDir, `${stem}.volume`));
223901
+ rmSync9(join34(outDir, `${stem}.volume`), { force: true });
223902
+ renameSync3(volumeSrc, join34(outDir, `${stem}.volume`));
223536
223903
  }
223537
223904
  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`));
223538
223905
  logErr(c.green(`✓ baked → ${relative4(root, outDir) || "."}/${stem}.bake + ${data.textures.join(", ")}${hasLight ? ` + ${data.lightTextures.join(", ")}` : ""}${hasVolume ? ` + ${stem}.volume` : ""}`));
223539
- const rel = (f3) => relative4(root, join31(outDir, f3)).replace(/\\/g, "/");
223906
+ const rel = (f3) => relative4(root, join34(outDir, f3)).replace(/\\/g, "/");
223540
223907
  const assets = (files) => files.length === 1 ? `asset('<rel>/${rel(files[0])}')` : `[${files.map((f3) => `asset('<rel>/${rel(f3)}')`).join(", ")}]`;
223541
223908
  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`));
223542
223909
  return;
@@ -223566,20 +223933,20 @@ var placeholderVolume = () => {
223566
223933
  return b;
223567
223934
  };
223568
223935
  var ensurePlaceholders = (outDir, stem) => {
223569
- const missing = (f3) => !existsSync29(f3) || statSync11(f3).size === 0;
223570
- const bake2 = join31(outDir, `${stem}.bake`);
223571
- const ktx2 = join31(outDir, pageFile(stem, 0, false));
223572
- const light = join31(outDir, pageFile(stem, 0, true));
223573
- const volume = join31(outDir, `${stem}.volume`);
223936
+ const missing = (f3) => !existsSync32(f3) || statSync11(f3).size === 0;
223937
+ const bake2 = join34(outDir, `${stem}.bake`);
223938
+ const ktx2 = join34(outDir, pageFile(stem, 0, false));
223939
+ const light = join34(outDir, pageFile(stem, 0, true));
223940
+ const volume = join34(outDir, `${stem}.volume`);
223574
223941
  if (missing(bake2))
223575
- writeFileSync22(bake2, JSON.stringify({ version: 2, placeholder: true, size: 1, texel: 0, pages: 1, instances: [] }) + `
223942
+ writeFileSync24(bake2, JSON.stringify({ version: 2, placeholder: true, size: 1, texel: 0, pages: 1, instances: [] }) + `
223576
223943
  `);
223577
223944
  if (missing(ktx2))
223578
- writeFileSync22(ktx2, PLACEHOLDER_KTX2);
223945
+ writeFileSync24(ktx2, PLACEHOLDER_KTX2);
223579
223946
  if (missing(light))
223580
- writeFileSync22(light, PLACEHOLDER_KTX2);
223947
+ writeFileSync24(light, PLACEHOLDER_KTX2);
223581
223948
  if (missing(volume))
223582
- writeFileSync22(volume, placeholderVolume());
223949
+ writeFileSync24(volume, placeholderVolume());
223583
223950
  };
223584
223951
  var lightmap = async (args) => {
223585
223952
  const sub = args._[0];
@@ -223590,9 +223957,9 @@ var lightmap = async (args) => {
223590
223957
 
223591
223958
  // src/commands/navmesh.ts
223592
223959
  init_manifest();
223593
- import { existsSync as existsSync30, mkdirSync as mkdirSync22, mkdtempSync as mkdtempSync5, readdirSync as readdirSync15, readFileSync as readFileSync26, rmSync as rmSync10, statSync as statSync12, unlinkSync as unlinkSync2, writeFileSync as writeFileSync23 } from "node:fs";
223960
+ import { existsSync as existsSync33, mkdirSync as mkdirSync23, mkdtempSync as mkdtempSync5, readdirSync as readdirSync15, readFileSync as readFileSync28, rmSync as rmSync10, statSync as statSync12, unlinkSync as unlinkSync2, writeFileSync as writeFileSync25 } from "node:fs";
223594
223961
  import { tmpdir as tmpdir5 } from "node:os";
223595
- import { basename as basename17, join as join32, relative as relative5, resolve as resolve14 } from "node:path";
223962
+ import { basename as basename17, join as join35, relative as relative5, resolve as resolve15 } from "node:path";
223596
223963
  init_util();
223597
223964
 
223598
223965
  // ../creator-nav/dist/creatorNav.js
@@ -223601,16 +223968,16 @@ var createCreatorNav = (() => {
223601
223968
  var moduleRtn;
223602
223969
  var Module = moduleArg;
223603
223970
  var readyPromiseResolve, readyPromiseReject;
223604
- var readyPromise = new Promise((resolve14, reject) => {
223605
- readyPromiseResolve = resolve14;
223971
+ var readyPromise = new Promise((resolve15, reject) => {
223972
+ readyPromiseResolve = resolve15;
223606
223973
  readyPromiseReject = reject;
223607
223974
  });
223608
223975
  var ENVIRONMENT_IS_WEB = typeof window == "object";
223609
223976
  var ENVIRONMENT_IS_WORKER = typeof WorkerGlobalScope != "undefined";
223610
223977
  var ENVIRONMENT_IS_NODE = typeof process == "object" && process.versions?.node && process.type != "renderer";
223611
223978
  if (ENVIRONMENT_IS_NODE) {
223612
- const { createRequire: createRequire6 } = await import("module");
223613
- var require2 = createRequire6(import.meta.url);
223979
+ const { createRequire: createRequire7 } = await import("module");
223980
+ var require2 = createRequire7(import.meta.url);
223614
223981
  }
223615
223982
  var arguments_ = [];
223616
223983
  var thisProgram = "./this.program";
@@ -223780,9 +224147,9 @@ var createCreatorNav = (() => {
223780
224147
  }
223781
224148
  var info2 = getWasmImports();
223782
224149
  if (Module["instantiateWasm"]) {
223783
- return new Promise((resolve14, reject) => {
224150
+ return new Promise((resolve15, reject) => {
223784
224151
  Module["instantiateWasm"](info2, (mod, inst) => {
223785
- resolve14(receiveInstance(mod, inst));
224152
+ resolve15(receiveInstance(mod, inst));
223786
224153
  });
223787
224154
  });
223788
224155
  }
@@ -224138,14 +224505,14 @@ var createCreatorNav = (() => {
224138
224505
  var __emscripten_stack_restore = wasmExports["_emscripten_stack_restore"];
224139
224506
  var __emscripten_stack_alloc = wasmExports["_emscripten_stack_alloc"];
224140
224507
  var _emscripten_stack_get_current = wasmExports["emscripten_stack_get_current"];
224141
- function run2() {
224508
+ function run3() {
224142
224509
  if (runDependencies > 0) {
224143
- dependenciesFulfilled = run2;
224510
+ dependenciesFulfilled = run3;
224144
224511
  return;
224145
224512
  }
224146
224513
  preRun();
224147
224514
  if (runDependencies > 0) {
224148
- dependenciesFulfilled = run2;
224515
+ dependenciesFulfilled = run3;
224149
224516
  return;
224150
224517
  }
224151
224518
  function doRun() {
@@ -224177,7 +224544,7 @@ var createCreatorNav = (() => {
224177
224544
  }
224178
224545
  }
224179
224546
  preInit();
224180
- run2();
224547
+ run3();
224181
224548
  moduleRtn = readyPromise;
224182
224549
  return moduleRtn;
224183
224550
  };
@@ -224389,9 +224756,9 @@ var num2 = (args, key, fallback, min, max) => {
224389
224756
  return n2;
224390
224757
  };
224391
224758
  var guessName = (root) => {
224392
- const dirs = [join32(root, "src"), root];
224759
+ const dirs = [join35(root, "src"), root];
224393
224760
  for (const dir of dirs) {
224394
- if (!existsSync30(dir))
224761
+ if (!existsSync33(dir))
224395
224762
  continue;
224396
224763
  let entries;
224397
224764
  try {
@@ -224403,7 +224770,7 @@ var guessName = (root) => {
224403
224770
  if (!f3.endsWith(".scene.ts"))
224404
224771
  continue;
224405
224772
  try {
224406
- if (/\bnavmesh\s*:/.test(readFileSync26(join32(dir, f3), "utf8")))
224773
+ if (/\bnavmesh\s*:/.test(readFileSync28(join35(dir, f3), "utf8")))
224407
224774
  return f3.slice(0, -".scene.ts".length);
224408
224775
  } catch {}
224409
224776
  }
@@ -224432,7 +224799,7 @@ var writeObj = (path2, tris) => {
224432
224799
  lines.push(`v ${tris[i2].toFixed(4)} ${tris[i2 + 1].toFixed(4)} ${tris[i2 + 2].toFixed(4)}`);
224433
224800
  for (let i2 = 0;i2 < tris.length / 3; i2 += 3)
224434
224801
  lines.push(`f ${i2 + 1} ${i2 + 2} ${i2 + 3}`);
224435
- writeFileSync23(path2, lines.join(`
224802
+ writeFileSync25(path2, lines.join(`
224436
224803
  `) + `
224437
224804
  `);
224438
224805
  };
@@ -224443,10 +224810,10 @@ var bake2 = async (args) => {
224443
224810
  } catch {
224444
224811
  root = process.cwd();
224445
224812
  }
224446
- const outDir = resolve14(root, flagStr(args, "out") ?? "assets/nav");
224813
+ const outDir = resolve15(root, flagStr(args, "out") ?? "assets/nav");
224447
224814
  const entryFlag = flagStr(args, "entry");
224448
224815
  const entryFile = entryFlag ?? "main.ts";
224449
- if (!existsSync30(resolve14(root, entryFile))) {
224816
+ if (!existsSync33(resolve15(root, entryFile))) {
224450
224817
  throw new CliError(`No ${entryFile} in ${root} — pass --entry <file> (the file that builds the level: a scene file with env.navmesh, or code calling NavMesh.load).`);
224451
224818
  }
224452
224819
  const name = flagStr(args, "name") ?? guessName(root) ?? "scene";
@@ -224457,10 +224824,10 @@ var bake2 = async (args) => {
224457
224824
  const logs = flagBool(args, "logs");
224458
224825
  const keepGeometry = flagBool(args, "keep-geometry");
224459
224826
  const obj = flagBool(args, "obj");
224460
- mkdirSync22(outDir, { recursive: true });
224461
- const navmeshPath = join32(outDir, `${name}.navmesh`);
224462
- if (!existsSync30(navmeshPath))
224463
- writeFileSync23(navmeshPath, placeholderLnav());
224827
+ mkdirSync23(outDir, { recursive: true });
224828
+ const navmeshPath = join35(outDir, `${name}.navmesh`);
224829
+ if (!existsSync33(navmeshPath))
224830
+ writeFileSync25(navmeshPath, placeholderLnav());
224464
224831
  const importPath = "./" + entryFile.replace(/\\/g, "/").replace(/\.tsx?$/, "");
224465
224832
  const entryPath = "/.navmesh-entry.ts";
224466
224833
  const cfg = { outDir: outDir.replace(/\\/g, "/"), name };
@@ -224474,8 +224841,8 @@ import ${JSON.stringify(importPath)}
224474
224841
  extraEntries: [{ path: entryPath, type: "text", text: entryText }],
224475
224842
  entryOverride: entryPath
224476
224843
  });
224477
- const scratch = mkdtempSync5(join32(tmpdir5(), "lecodes-navmesh-"));
224478
- const outPng = join32(scratch, "frame.png");
224844
+ const scratch = mkdtempSync5(join35(tmpdir5(), "lecodes-navmesh-"));
224845
+ const outPng = join35(scratch, "frame.png");
224479
224846
  note(`Dumping the level's static collision geometry through the desktop host → ${relative5(root, outDir) || "."}/${name}.navgeo…`);
224480
224847
  let output = "";
224481
224848
  try {
@@ -224528,10 +224895,10 @@ import ${JSON.stringify(importPath)}
224528
224895
  }
224529
224896
  if (!config)
224530
224897
  throw new CliError("The host reported a dump but no `[navmesh] config` line — SDK/host mismatch?");
224531
- const geoPath = join32(outDir, `${name}.navgeo`);
224532
- if (!existsSync30(geoPath))
224898
+ const geoPath = join35(outDir, `${name}.navgeo`);
224899
+ if (!existsSync33(geoPath))
224533
224900
  throw new CliError(`The host reported a dump but ${geoPath} is missing.`);
224534
- const geo = readNgeo(new Uint8Array(readFileSync26(geoPath)));
224901
+ const geo = readNgeo(new Uint8Array(readFileSync28(geoPath)));
224535
224902
  const params = {
224536
224903
  agentRadius: num2(args, "radius", config.agent.radius, 0.05, 10),
224537
224904
  agentHeight: num2(args, "height", config.agent.height, 0.1, 20),
@@ -224577,9 +224944,9 @@ import ${JSON.stringify(importPath)}
224577
224944
  if (result.polys === 0) {
224578
224945
  throw new CliError("The build produced no polygons — nothing walkable for this agent. Check the cell size against the level's scale, the slope, and that the floor is a STATIC body (Shape + Physics { motion: 'static' }).");
224579
224946
  }
224580
- writeFileSync23(navmeshPath, result.data);
224947
+ writeFileSync25(navmeshPath, result.data);
224581
224948
  if (obj) {
224582
- const objPath = join32(outDir, `${name}.navmesh.obj`);
224949
+ const objPath = join35(outDir, `${name}.navmesh.obj`);
224583
224950
  writeObj(objPath, navMeshTriangles(mod, result.data));
224584
224951
  logErr(c.dim(` wrote ${relative5(root, objPath)} (the polygon mesh, for inspection)`));
224585
224952
  }
@@ -224617,8 +224984,131 @@ var login = async (args) => {
224617
224984
 
224618
224985
  // src/commands/shaders.ts
224619
224986
  init_manifest();
224620
- import { readFileSync as readFileSync27 } from "node:fs";
224987
+ import { readFileSync as readFileSync30 } from "node:fs";
224988
+ import { relative as relative7 } from "node:path";
224621
224989
  init_util();
224990
+
224991
+ // src/commands/shadersNew.ts
224992
+ init_compile();
224993
+ init_distRoot();
224994
+ init_util();
224995
+ import { existsSync as existsSync34, mkdirSync as mkdirSync24, readdirSync as readdirSync16, readFileSync as readFileSync29, writeFileSync as writeFileSync26 } from "node:fs";
224996
+ import { basename as basename18, join as join36, relative as relative6, resolve as resolve16 } from "node:path";
224997
+ var engineMaterialsDir = () => {
224998
+ const candidates = [
224999
+ join36(distRoot, "runtime", "materials"),
225000
+ join36(distRoot, "..", "creator-gl", "materials", "src")
225001
+ ];
225002
+ for (const c4 of candidates)
225003
+ if (existsSync34(join36(c4, "unlit.mat")))
225004
+ return c4;
225005
+ return null;
225006
+ };
225007
+ var engineMaterialNames = () => {
225008
+ const dir = engineMaterialsDir();
225009
+ if (!dir)
225010
+ return [];
225011
+ return readdirSync16(dir).filter((f3) => f3.endsWith(".mat") && !f3.startsWith("camera") && f3 !== "shader360.mat").map((f3) => f3.slice(0, -4)).sort();
225012
+ };
225013
+ var NAME_RE = /^[a-z][a-z0-9_-]*$/i;
225014
+ var matName = (name) => name.replace(/-/g, "_");
225015
+ var starterMat = (name) => `// ${name}.mat — a custom material (lecodes shaders new).
225016
+ //
225017
+ // Filament's material language: the \`material\` block declares the parameters and the render state,
225018
+ // \`fragment\` computes the surface. Read it with \`Material.load(asset('./${name}.mat'))\` or the
225019
+ // ${name}.material.ts asset beside it; set parameters with material.set('tint', '#ff8800').
225020
+ // \`lecodes shaders compile\` (or \`lecodes dev\`) compiles it for every host into .lecodes/shaders.
225021
+ //
225022
+ // The comment on a parameter's line is what the scene editor shows for it:
225023
+ // @color a colour picker (also inferred from a name like tint / color / emissive)
225024
+ // @range(min, max) a slider, @step(s) its step, @default(v) the value a new material starts with
225025
+ // the rest of the line is the tooltip
225026
+
225027
+ material {
225028
+ name : ${matName(name)},
225029
+ shadingModel : unlit,
225030
+ parameters : [
225031
+ { type : float3, name : tint }, // @color @default(#ffffff) the flat colour
225032
+ { type : float, name : glow }, // @range(0, 2) @step(0.05) @default(1) brightness multiplier
225033
+ { type : sampler2d, name : map } // the texture, white when unset
225034
+ ],
225035
+ requires : [
225036
+ uv0
225037
+ ]
225038
+ }
225039
+
225040
+ fragment {
225041
+ void material(inout MaterialInputs material) {
225042
+ prepareMaterial(material);
225043
+ vec4 base = texture(materialParams_map, getUV0());
225044
+ material.baseColor.rgb = base.rgb * materialParams.tint * materialParams.glow;
225045
+ }
225046
+ }
225047
+ `;
225048
+ var refMat = (name, ref, source) => {
225049
+ const renamed = source.replace(/(\bname\s*:\s*)([A-Za-z_][A-Za-z0-9_]*)/, `$1${matName(name)}`);
225050
+ return `// ${name}.mat — started from the engine's ${ref}.mat (lecodes shaders new --ref ${ref}); edit freely,
225051
+ // the engine keeps its own. \`lecodes shaders compile\` builds it for every host into .lecodes/shaders.
225052
+ // Load it with Material.load(asset('./${name}.mat')) or the ${name}.material.ts asset beside it.
225053
+
225054
+ ${renamed}`;
225055
+ };
225056
+ var materialAsset = (name, mat) => {
225057
+ const schema = parseShaderSchema(mat);
225058
+ const lines = [];
225059
+ for (const p2 of schema.params) {
225060
+ if (p2.editor === "texture") {
225061
+ lines.push(` // ${p2.name}: asset('./texture.png'), // a sampler takes an image asset`);
225062
+ continue;
225063
+ }
225064
+ if (p2.default === null || p2.default === undefined)
225065
+ continue;
225066
+ lines.push(` ${p2.name}: ${JSON.stringify(p2.default)},${p2.tooltip ? ` // ${p2.tooltip}` : ""}`);
225067
+ }
225068
+ return `// ${name}.material.ts — the material as a project asset: ONE shared instance a scene file takes as
225069
+ // \`material: ${camel(name)}\` and code loads with \`await ${camel(name)}.load()\`. The parameters and their
225070
+ // widgets come from the annotations in ${name}.mat.
225071
+ export default defineMaterial({
225072
+ shader: asset('./${name}.mat'),
225073
+ params: {
225074
+ ${lines.join(`
225075
+ `)}
225076
+ },
225077
+ })
225078
+ `;
225079
+ };
225080
+ var camel = (name) => name.replace(/[-_]+(.)/g, (_, ch) => ch.toUpperCase());
225081
+ var writeNewShader = (o2) => {
225082
+ const name = o2.name.endsWith(".mat") ? o2.name.slice(0, -4) : o2.name;
225083
+ if (!NAME_RE.test(name))
225084
+ throw new CliError(`"${o2.name}" is not a shader name — letters, digits, - and _ (starting with a letter), e.g. hologram or rim-light`);
225085
+ const dir = resolve16(o2.cwd ?? process.cwd(), o2.dir ?? ".");
225086
+ const matPath = join36(dir, `${name}.mat`), assetPath = join36(dir, `${name}.material.ts`);
225087
+ if (!o2.force) {
225088
+ for (const p2 of [matPath, assetPath])
225089
+ if (existsSync34(p2))
225090
+ throw new CliError(`${relative6(o2.cwd ?? process.cwd(), p2) || basename18(p2)} exists — pick another name or pass --force to overwrite`);
225091
+ }
225092
+ let mat;
225093
+ let ref = null;
225094
+ if (o2.ref) {
225095
+ ref = o2.ref.endsWith(".mat") ? o2.ref.slice(0, -4) : o2.ref;
225096
+ const srcDir = engineMaterialsDir();
225097
+ if (!srcDir)
225098
+ throw new CliError("this CLI carries no engine material sources (runtime/materials) — --ref needs a CLI built with them");
225099
+ const known = engineMaterialNames();
225100
+ if (!known.includes(ref))
225101
+ throw new CliError(`no engine material "${ref}". Known: ${known.join(", ")}`);
225102
+ mat = refMat(name, ref, readFileSync29(join36(srcDir, `${ref}.mat`), "utf8"));
225103
+ } else
225104
+ mat = starterMat(name);
225105
+ mkdirSync24(dir, { recursive: true });
225106
+ writeFileSync26(matPath, mat);
225107
+ writeFileSync26(assetPath, materialAsset(name, mat));
225108
+ return { mat: matPath, asset: assetPath, ref };
225109
+ };
225110
+
225111
+ // src/commands/shaders.ts
224622
225112
  init_compile();
224623
225113
  var collectShaders = () => {
224624
225114
  let root;
@@ -224649,7 +225139,7 @@ var status2 = async () => {
224649
225139
  if (matcId === null)
224650
225140
  state = c.dim("unknown (matc not downloaded)");
224651
225141
  else {
224652
- const text = readFileSync27(shader.absPath, "utf8");
225142
+ const text = readFileSync30(shader.absPath, "utf8");
224653
225143
  if (shaderIsCompiled(root, text, matcId))
224654
225144
  state = "compiled";
224655
225145
  else {
@@ -224658,7 +225148,7 @@ var status2 = async () => {
224658
225148
  }
224659
225149
  }
224660
225150
  log(` ${shader.path.padEnd(width)} ${state}`);
224661
- const schema = parseShaderSchema(readFileSync27(shader.absPath, "utf8"));
225151
+ const schema = parseShaderSchema(readFileSync30(shader.absPath, "utf8"));
224662
225152
  for (const p2 of schema.params) {
224663
225153
  const range = p2.min !== undefined ? ` [${p2.min}..${p2.max}]` : "";
224664
225154
  const def = p2.default !== null && p2.default !== undefined ? ` = ${JSON.stringify(p2.default)}` : "";
@@ -224701,7 +225191,7 @@ var compile3 = async (args) => {
224701
225191
  }
224702
225192
  const matcExe = await resolveMatcExe();
224703
225193
  const matcId = matcIdentity(matcExe);
224704
- const wasCompiled = new Map(shaders.map((s2) => [s2.path, shaderIsCompiled(root, readFileSync27(s2.absPath, "utf8"), matcId)]));
225194
+ const wasCompiled = new Map(shaders.map((s2) => [s2.path, shaderIsCompiled(root, readFileSync30(s2.absPath, "utf8"), matcId)]));
224705
225195
  const errors2 = [];
224706
225196
  for (const shader of shaders) {
224707
225197
  try {
@@ -224723,6 +225213,16 @@ ${c.red("✗")} ${e2}`);
224723
225213
  const scope = platforms ? ` [${platforms.join(", ")}]` : "";
224724
225214
  note(`${shaders.length} shader${shaders.length === 1 ? "" : "s"} compiled${scope} → ${shadersCacheDir(root)}`);
224725
225215
  };
225216
+ var newShader = (args) => {
225217
+ const name = args._[1];
225218
+ if (!name)
225219
+ throw new CliError(`lecodes shaders new <name> [--ref ${engineMaterialNames().join("|") || "lit"}] [--dir <folder>] [--force]`);
225220
+ const res = writeNewShader({ name, ref: flagStr(args, "ref"), dir: flagStr(args, "dir"), force: args.flags.force === true });
225221
+ const rel = (p2) => relative7(process.cwd(), p2);
225222
+ log(`${rel(res.mat)} ${c.dim(res.ref ? `(from the engine's ${res.ref}.mat)` : "(the starter template)")}`);
225223
+ log(`${rel(res.asset)} ${c.dim("(defineMaterial asset — a scene takes it as `material:`)")}`);
225224
+ note(`next: edit it, then \`lecodes shaders compile\` (or just \`lecodes dev\`); in code: Material.load(asset('./${name.replace(/\.mat$/, "")}.mat'))`);
225225
+ };
224726
225226
  var shaders = async (args) => {
224727
225227
  const sub = args._[0];
224728
225228
  if (sub === undefined)
@@ -224731,12 +225231,14 @@ var shaders = async (args) => {
224731
225231
  return compile3(args);
224732
225232
  if (sub === "update")
224733
225233
  return updateMatc();
224734
- throw new CliError(`Unknown subcommand "shaders ${sub}". Use \`lecodes shaders\` (status), \`lecodes shaders compile [--platform a,b]\` or \`lecodes shaders update\`.`);
225234
+ if (sub === "new")
225235
+ return newShader(args);
225236
+ throw new CliError(`Unknown subcommand "shaders ${sub}". Use \`lecodes shaders\` (status), \`lecodes shaders compile [--platform a,b]\`, \`lecodes shaders new <name> [--ref lit]\` or \`lecodes shaders update\`.`);
224735
225237
  };
224736
225238
 
224737
225239
  // src/commands/render.ts
224738
- import { mkdirSync as mkdirSync23, readFileSync as readFileSync29, writeFileSync as writeFileSync24 } from "node:fs";
224739
- import { dirname as dirname16, resolve as resolve17 } from "node:path";
225240
+ import { mkdirSync as mkdirSync25, readFileSync as readFileSync32, writeFileSync as writeFileSync27 } from "node:fs";
225241
+ import { dirname as dirname17, resolve as resolve19 } from "node:path";
224740
225242
 
224741
225243
  // src/compile/headlessBundle.ts
224742
225244
  init_api();
@@ -224820,23 +225322,23 @@ var compileHeadlessBundle = async (args, opts = {}) => {
224820
225322
  // src/compile/screenEntry.ts
224821
225323
  init_designMeta();
224822
225324
  init_util();
224823
- import { existsSync as existsSync31, readFileSync as readFileSync28, statSync as statSync13 } from "node:fs";
224824
- import { isAbsolute, join as join33, relative as relative6, resolve as resolve15 } from "node:path";
225325
+ import { existsSync as existsSync35, readFileSync as readFileSync31, statSync as statSync13 } from "node:fs";
225326
+ import { isAbsolute, join as join37, relative as relative8, resolve as resolve17 } from "node:path";
224825
225327
  var SCREEN_EXTS = [".ts", ".tsx", ".js", ".jsx", ".mjs"];
224826
225328
  var SCREEN_ENTRY_PATH = "/.screen-entry.ts";
224827
225329
  var DEFAULT_DESIGN_DIR = "design";
224828
225330
  var screenArg = (args) => args._[0];
224829
225331
  var hasSeparator = (file) => file.includes("/") || file.includes("\\");
224830
225332
  var findScreenFile = (root, file, dirFlag) => {
224831
- const candidates = [resolve15(process.cwd(), file)];
225333
+ const candidates = [resolve17(process.cwd(), file)];
224832
225334
  if (!hasSeparator(file)) {
224833
- candidates.push(join33(root, (dirFlag ?? DEFAULT_DESIGN_DIR).replace(/[/\\]+$/, ""), "screens", file));
225335
+ candidates.push(join37(root, (dirFlag ?? DEFAULT_DESIGN_DIR).replace(/[/\\]+$/, ""), "screens", file));
224834
225336
  }
224835
225337
  for (const base of candidates) {
224836
- if (existsSync31(base) && statSync13(base).isFile())
225338
+ if (existsSync35(base) && statSync13(base).isFile())
224837
225339
  return base;
224838
225340
  for (const ext2 of SCREEN_EXTS)
224839
- if (existsSync31(base + ext2))
225341
+ if (existsSync35(base + ext2))
224840
225342
  return base + ext2;
224841
225343
  }
224842
225344
  throw new CliError(`No screen file at "${file}".` + (hasSeparator(file) ? "" : ` Pass a path, or a screen id that exists in ${dirFlag ?? DEFAULT_DESIGN_DIR}/screens/.`));
@@ -224845,17 +225347,17 @@ var designDirOf = (root, key) => {
224845
225347
  const m = /^\/(.*)screens\/([^/]+)\.[^./]+$/.exec(key);
224846
225348
  if (!m)
224847
225349
  return null;
224848
- const dir = join33(root, m[1]);
224849
- return existsSync31(join33(dir, "meta.json")) ? { dir, id: m[2] } : null;
225350
+ const dir = join37(root, m[1]);
225351
+ return existsSync35(join37(dir, "meta.json")) ? { dir, id: m[2] } : null;
224850
225352
  };
224851
225353
  var resolveScreenTarget = (root, file, opts = {}) => {
224852
225354
  const abs = findScreenFile(root, file, opts.dir);
224853
- const rel = relative6(root, abs);
225355
+ const rel = relative8(root, abs);
224854
225356
  if (rel.startsWith("..") || isAbsolute(rel)) {
224855
225357
  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.`);
224856
225358
  }
224857
225359
  const key = toKey(rel);
224858
- if (!/^\s*export\s+default\b/m.test(readFileSync28(abs, "utf8"))) {
225360
+ if (!/^\s*export\s+default\b/m.test(readFileSync31(abs, "utf8"))) {
224859
225361
  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.`);
224860
225362
  }
224861
225363
  const invoke = opts.state !== undefined ? `screen(${JSON.stringify(opts.state)})` : "screen()";
@@ -224894,11 +225396,11 @@ init_manifest();
224894
225396
 
224895
225397
  // src/desktopScript.ts
224896
225398
  init_util();
224897
- import { resolve as resolve16 } from "node:path";
225399
+ import { resolve as resolve18 } from "node:path";
224898
225400
  var isPoint = (v) => typeof v === "object" && v !== null && typeof v.x === "number" && typeof v.y === "number";
224899
225401
  var FRAME_MS = 16;
224900
225402
  var ACTIONS = ["tap", "type", "fill", "scroll", "wait", "frames", "drag", "hold", "key", "keyDown", "keyUp", "look", "mouseTo", "wheel", "stick", "gamepad", "camera", "view"];
224901
- var SEMANTIC_EXPECT = ["text", "notText", "node", "notNode", "screen", "value"];
225403
+ var SEMANTIC_EXPECT = ["text", "notText", "node", "notNode", "screen", "value", "audio"];
224902
225404
  var VIEW_NAMES = ["iso", "top", "front", "side", "back"];
224903
225405
  var isVec3 = (v) => Array.isArray(v) && v.length === 3 && v.every((n2) => typeof n2 === "number");
224904
225406
  var pt = (p2) => `(${p2.x},${p2.y})`;
@@ -225095,7 +225597,7 @@ var translateScenario = (rawSteps, opts) => {
225095
225597
  if (s2.screenshot !== undefined) {
225096
225598
  if (typeof s2.screenshot !== "string")
225097
225599
  throw new CliError(`${at}: "screenshot" must be a path.`);
225098
- plan.screenshot = resolve16(opts.outDir, s2.screenshot);
225600
+ plan.screenshot = resolve18(opts.outDir, s2.screenshot);
225099
225601
  plan.lines.push(emit(`frames 2`));
225100
225602
  plan.lines.push(emit(`shot ${plan.screenshot}`));
225101
225603
  budgetMs += 3 * FRAME_MS;
@@ -225175,6 +225677,7 @@ var evaluateDesktopRun = (plan, output) => {
225175
225677
 
225176
225678
  // src/commands/render.ts
225177
225679
  init_util();
225680
+ init_peerInstall();
225178
225681
  var DEVICE_SAFE_AREA = {
225179
225682
  none: { insets: [0, 0, 0, 0], bars: [] },
225180
225683
  iphone: { insets: [59, 0, 34, 0], bars: [] },
@@ -225195,18 +225698,12 @@ var resolveSafeAreaChoice = (explicit, device) => {
225195
225698
  }
225196
225699
  return;
225197
225700
  };
225198
- var requireRenderer = async () => {
225199
- try {
225200
- return await import("lecodes-renderer/headless");
225201
- } catch {
225202
- throw new CliError("The renderer isn't installed. Run: npm install -g lecodes-renderer");
225203
- }
225204
- };
225701
+ var requireRenderer = async () => loadPeer("lecodes-renderer", "headless", { for: "lecodes render" });
225205
225702
  var fileScenarioIO = (dir) => ({
225206
225703
  write: (relPath, data) => {
225207
- const abs = resolve17(dir, relPath);
225208
- mkdirSync23(dirname16(abs), { recursive: true });
225209
- writeFileSync24(abs, data);
225704
+ const abs = resolve19(dir, relPath);
225705
+ mkdirSync25(dirname17(abs), { recursive: true });
225706
+ writeFileSync27(abs, data);
225210
225707
  }
225211
225708
  });
225212
225709
  var resolveFixtures = (value, baseDir) => {
@@ -225214,9 +225711,9 @@ var resolveFixtures = (value, baseDir) => {
225214
225711
  return;
225215
225712
  let map = value;
225216
225713
  if (typeof value === "string") {
225217
- const abs = resolve17(baseDir, value);
225714
+ const abs = resolve19(baseDir, value);
225218
225715
  try {
225219
- map = JSON.parse(readFileSync29(abs, "utf8"));
225716
+ map = JSON.parse(readFileSync32(abs, "utf8"));
225220
225717
  } catch (e2) {
225221
225718
  throw new CliError(`Can't read fixtures "${value}": ${e2 instanceof Error ? e2.message : String(e2)}`);
225222
225719
  }
@@ -225229,7 +225726,7 @@ var resolveFixtures = (value, baseDir) => {
225229
225726
  var readScenarioFile = (path2) => {
225230
225727
  let raw;
225231
225728
  try {
225232
- raw = JSON.parse(readFileSync29(resolve17(path2), "utf8"));
225729
+ raw = JSON.parse(readFileSync32(resolve19(path2), "utf8"));
225233
225730
  } catch (e2) {
225234
225731
  throw new CliError(`Can't read scenario "${path2}": ${e2 instanceof Error ? e2.message : String(e2)}`);
225235
225732
  }
@@ -225287,13 +225784,13 @@ var renderDesktop = async (args) => {
225287
225784
  extraEntries: screen2 ? [screen2.entry] : undefined,
225288
225785
  entryOverride: screen2?.entryPath
225289
225786
  });
225290
- mkdirSync23(dirname16(resolve17(pngFlag)), { recursive: true });
225787
+ mkdirSync25(dirname17(resolve19(pngFlag)), { recursive: true });
225291
225788
  if (scenario) {
225292
225789
  const outDir = flagStr(args, "out-dir") ?? ".";
225293
225790
  const plan = translateScenario(scenario.steps, { outDir, headroomFrames: frames });
225294
225791
  for (const st of plan.steps)
225295
225792
  if (st.screenshot)
225296
- mkdirSync23(dirname16(st.screenshot), { recursive: true });
225793
+ mkdirSync25(dirname17(st.screenshot), { recursive: true });
225297
225794
  const timeoutMs2 = Number(flagStr(args, "timeout")) || 60000 + plan.steps.length * 5000;
225298
225795
  note(`Running ${plan.steps.length} step${plan.steps.length === 1 ? "" : "s"} (native)…`);
225299
225796
  const { output } = await runDesktopRender(js, resources, {
@@ -225399,7 +225896,7 @@ var render = async (args) => {
225399
225896
  const settleStr = flagStr(args, "settle") ?? (scenario?.settle !== undefined ? String(scenario.settle) : undefined);
225400
225897
  const timeStr = flagStr(args, "time");
225401
225898
  const onConsole = flagBool(args, "logs") ? (level, text2) => logErr(`${c.dim(`[${level}]`)} ${text2}`) : undefined;
225402
- const envelopeFixtures = resolveFixtures(scenario?.fixtures, scriptFlag !== undefined ? dirname16(resolve17(scriptFlag)) : ".");
225899
+ const envelopeFixtures = resolveFixtures(scenario?.fixtures, scriptFlag !== undefined ? dirname17(resolve19(scriptFlag)) : ".");
225403
225900
  const flagFixtures = resolveFixtures(flagStr(args, "fixtures"), ".");
225404
225901
  const fixtures = envelopeFixtures || flagFixtures ? { ...envelopeFixtures, ...flagFixtures } : undefined;
225405
225902
  if (fixtures) {
@@ -225438,7 +225935,7 @@ var render = async (args) => {
225438
225935
  ${c.red(st.error ?? "failed")}`);
225439
225936
  }
225440
225937
  if (!result2.ok && result2.failureArtifacts.length > 0) {
225441
- logErr(c.dim(` failing frame: ${result2.failureArtifacts.map((p2) => resolve17(outDir, p2)).join(", ")}`));
225938
+ logErr(c.dim(` failing frame: ${result2.failureArtifacts.map((p2) => resolve19(outDir, p2)).join(", ")}`));
225442
225939
  }
225443
225940
  logErr(result2.ok ? c.green(`✓ ${result2.steps.length}/${steps.length} steps passed`) : c.red(`✗ failed at step ${result2.steps.length} of ${steps.length}`));
225444
225941
  return done((cb) => cb(), result2.ok ? 0 : 1);
@@ -225459,10 +225956,10 @@ var render = async (args) => {
225459
225956
  for (const w of [...warnings, ...mapWarnings])
225460
225957
  warnErr(w);
225461
225958
  const pngPath = mapFlag.endsWith(".png") ? mapFlag : mapFlag + ".png";
225462
- mkdirSync23(dirname16(resolve17(pngPath)), { recursive: true });
225463
- writeFileSync24(resolve17(pngPath), png2);
225959
+ mkdirSync25(dirname17(resolve19(pngPath)), { recursive: true });
225960
+ writeFileSync27(resolve19(pngPath), png2);
225464
225961
  const jsonPath = pngPath.slice(0, -4) + ".json";
225465
- writeFileSync24(resolve17(jsonPath), JSON.stringify(data, null, 2));
225962
+ writeFileSync27(resolve19(jsonPath), JSON.stringify(data, null, 2));
225466
225963
  note(`Wrote ${pngPath} + ${jsonPath}`);
225467
225964
  return done((cb) => cb());
225468
225965
  }
@@ -225482,8 +225979,8 @@ var render = async (args) => {
225482
225979
  if (isNullSink(pngFlag)) {
225483
225980
  note(`Discarded PNG (${pngFlag})`);
225484
225981
  } else {
225485
- mkdirSync23(dirname16(resolve17(pngFlag)), { recursive: true });
225486
- writeFileSync24(resolve17(pngFlag), png2);
225982
+ mkdirSync25(dirname17(resolve19(pngFlag)), { recursive: true });
225983
+ writeFileSync27(resolve19(pngFlag), png2);
225487
225984
  note(`Wrote ${pngFlag} (${png2.length} bytes, ${outW}x${outH}${clip !== undefined ? ", clipped" : ""})`);
225488
225985
  }
225489
225986
  return done((cb) => cb());
@@ -225498,7 +225995,7 @@ var render = async (args) => {
225498
225995
  return done((cb) => cb());
225499
225996
  }
225500
225997
  if (out) {
225501
- writeFileSync24(resolve17(out), result.text);
225998
+ writeFileSync27(resolve19(out), result.text);
225502
225999
  note(`Wrote ${out} (${result.text.length} bytes)`);
225503
226000
  return done((cb) => cb());
225504
226001
  }
@@ -225513,8 +226010,8 @@ init_api();
225513
226010
  init_config();
225514
226011
  init_manifest();
225515
226012
  init_util();
225516
- import { appendFileSync as appendFileSync2, existsSync as existsSync32, readFileSync as readFileSync30, writeFileSync as writeFileSync25 } from "node:fs";
225517
- import { join as join34 } from "node:path";
226013
+ import { appendFileSync as appendFileSync2, existsSync as existsSync36, readFileSync as readFileSync33, writeFileSync as writeFileSync28 } from "node:fs";
226014
+ import { join as join38 } from "node:path";
225518
226015
  var ENV_VAR = "LECODES_PUSH_KEY";
225519
226016
  var context = () => {
225520
226017
  const root = findProjectRoot(process.cwd());
@@ -225658,11 +226155,11 @@ var pnKeysList = async (ctx) => {
225658
226155
  };
225659
226156
  var writeEnv = (root, key) => {
225660
226157
  const touched = [];
225661
- const envPath = join34(root, ".env");
226158
+ const envPath = join38(root, ".env");
225662
226159
  const line = `${ENV_VAR}=${key}`;
225663
- const existing = existsSync32(envPath) ? readFileSync30(envPath, "utf8") : "";
226160
+ const existing = existsSync36(envPath) ? readFileSync33(envPath, "utf8") : "";
225664
226161
  if (new RegExp(`^${ENV_VAR}=`, "m").test(existing)) {
225665
- writeFileSync25(envPath, existing.replace(new RegExp(`^${ENV_VAR}=.*$`, "m"), line));
226162
+ writeFileSync28(envPath, existing.replace(new RegExp(`^${ENV_VAR}=.*$`, "m"), line));
225666
226163
  } else {
225667
226164
  const separator = !existing || existing.endsWith(`
225668
226165
  `) ? "" : `
@@ -225671,8 +226168,8 @@ var writeEnv = (root, key) => {
225671
226168
  `);
225672
226169
  }
225673
226170
  touched.push(".env");
225674
- const ignorePath = join34(root, ".gitignore");
225675
- const ignore = existsSync32(ignorePath) ? readFileSync30(ignorePath, "utf8") : "";
226171
+ const ignorePath = join38(root, ".gitignore");
226172
+ const ignore = existsSync36(ignorePath) ? readFileSync33(ignorePath, "utf8") : "";
225676
226173
  if (!ignore.split(`
225677
226174
  `).some((l2) => l2.trim() === ".env")) {
225678
226175
  const separator = !ignore || ignore.endsWith(`
@@ -225771,8 +226268,8 @@ var pn = async (args) => {
225771
226268
  init_api();
225772
226269
  init_config();
225773
226270
  init_manifest();
225774
- import { existsSync as existsSync33, mkdirSync as mkdirSync24, rmSync as rmSync11, writeFileSync as writeFileSync26 } from "node:fs";
225775
- import { dirname as dirname17, join as join35 } from "node:path";
226271
+ import { existsSync as existsSync37, mkdirSync as mkdirSync26, rmSync as rmSync11, writeFileSync as writeFileSync29 } from "node:fs";
226272
+ import { dirname as dirname18, join as join39 } from "node:path";
225776
226273
  init_types4();
225777
226274
  init_localFiles();
225778
226275
  init_util();
@@ -225795,8 +226292,8 @@ var pull = async (args) => {
225795
226292
  const remotePaths = new Set([...buildPaths(project.assets).entries()].filter(([id]) => project.assets.find((a2) => a2.id === id)?.type !== "folder").map(([, path2]) => path2));
225796
226293
  for (const path2 of Object.keys(manifest.files)) {
225797
226294
  if (!remotePaths.has(path2)) {
225798
- const abs = join35(root, ...path2.replace(/^\//, "").split("/"));
225799
- if (existsSync33(abs))
226295
+ const abs = join39(root, ...path2.replace(/^\//, "").split("/"));
226296
+ if (existsSync37(abs))
225800
226297
  rmSync11(abs);
225801
226298
  }
225802
226299
  }
@@ -225834,18 +226331,18 @@ var pullPaths = async (root, manifest, project, paths, apiUrl, token) => {
225834
226331
  const files = { ...manifest.files };
225835
226332
  for (const asset of targets) {
225836
226333
  const key = pathById.get(asset.id);
225837
- const abs = join35(root, ...key.replace(/^\//, "").split("/"));
225838
- mkdirSync24(dirname17(abs), { recursive: true });
226334
+ const abs = join39(root, ...key.replace(/^\//, "").split("/"));
226335
+ mkdirSync26(dirname18(abs), { recursive: true });
225839
226336
  let sha;
225840
226337
  if (asset.type === "file") {
225841
226338
  if (!asset.file?.src)
225842
226339
  continue;
225843
226340
  const bytes = await downloadBlob(apiUrl, asset.file.src);
225844
- writeFileSync26(abs, bytes);
226341
+ writeFileSync29(abs, bytes);
225845
226342
  sha = sha256(bytes);
225846
226343
  } else {
225847
226344
  const text = asset.text ?? "";
225848
- writeFileSync26(abs, text);
226345
+ writeFileSync29(abs, text);
225849
226346
  sha = sha256(text);
225850
226347
  }
225851
226348
  files[key] = { id: asset.id, type: kindOf3(asset.type), sha };
@@ -225858,8 +226355,8 @@ var pullPaths = async (root, manifest, project, paths, apiUrl, token) => {
225858
226355
 
225859
226356
  // src/commands/scene.ts
225860
226357
  init_manifest();
225861
- import { existsSync as existsSync35, readFileSync as readFileSync32, readdirSync as readdirSync16, statSync as statSync14, writeFileSync as writeFileSync28 } from "node:fs";
225862
- import { basename as basename18, dirname as dirname18, join as join37, resolve as resolve18 } from "node:path";
226358
+ import { existsSync as existsSync39, readFileSync as readFileSync35, readdirSync as readdirSync17, statSync as statSync14, writeFileSync as writeFileSync31 } from "node:fs";
226359
+ import { basename as basename19, dirname as dirname19, join as join41, resolve as resolve20 } from "node:path";
225863
226360
 
225864
226361
  // src/compile/sceneCompile.ts
225865
226362
  init_localFiles();
@@ -225868,18 +226365,18 @@ init_distRoot();
225868
226365
  init_util();
225869
226366
  init_projectCompile();
225870
226367
  import { createHash as createHash8 } from "node:crypto";
225871
- import { existsSync as existsSync34, mkdirSync as mkdirSync25, readFileSync as readFileSync31, writeFileSync as writeFileSync27 } from "node:fs";
225872
- import { join as join36 } from "node:path";
226368
+ import { existsSync as existsSync38, mkdirSync as mkdirSync27, readFileSync as readFileSync34, writeFileSync as writeFileSync30 } from "node:fs";
226369
+ import { join as join40 } from "node:path";
225873
226370
  var TEXT_EXT = /\.(ts|js|json|svg|txt|md)$/i;
225874
226371
  var rel = (key) => key.replace(/^\//, "");
225875
226372
  var resolveHarness = (loadSceneHarness2) => {
225876
- const inRepo = join36(distRoot, "..", "projects", "src", "scene-editor");
226373
+ const inRepo = join40(distRoot, "..", "projects", "src", "scene-editor");
225877
226374
  try {
225878
- return loadSceneHarness2(existsSync34(inRepo) ? inRepo : undefined);
226375
+ return loadSceneHarness2(existsSync38(inRepo) ? inRepo : undefined);
225879
226376
  } catch {
225880
- const vendored = join36(distRoot, "runtime", "scene-harness.json");
225881
- if (existsSync34(vendored))
225882
- return JSON.parse(readFileSync31(vendored, "utf8"));
226377
+ const vendored = join40(distRoot, "runtime", "scene-harness.json");
226378
+ if (existsSync38(vendored))
226379
+ return JSON.parse(readFileSync34(vendored, "utf8"));
225883
226380
  throw new CliError("scene-editor harness sources not found — reinstall the CLI or set LECODES_SCENE_HARNESS_DIR.");
225884
226381
  }
225885
226382
  };
@@ -225891,9 +226388,9 @@ var compileSceneEditor = async (opts) => {
225891
226388
  for (const file of scan2.files) {
225892
226389
  const path2 = "./" + rel(file.path);
225893
226390
  if (TEXT_EXT.test(path2)) {
225894
- entries.push({ path: path2, type: "text", text: readFileSync31(file.absPath, "utf8") });
226391
+ entries.push({ path: path2, type: "text", text: readFileSync34(file.absPath, "utf8") });
225895
226392
  } else if (/\.mat$/i.test(path2)) {
225896
- entries.push({ path: path2, type: "shader", text: readFileSync31(file.absPath, "utf8"), absPath: file.absPath });
226393
+ entries.push({ path: path2, type: "shader", text: readFileSync34(file.absPath, "utf8"), absPath: file.absPath });
225897
226394
  } else {
225898
226395
  entries.push({ path: path2, type: "resource", fileSrc: "/project/" + rel(file.path) });
225899
226396
  }
@@ -225909,10 +226406,10 @@ var compileSceneEditor = async (opts) => {
225909
226406
  continue;
225910
226407
  const hash = createHash8("sha256").update(e2.text ?? "").digest("hex").slice(0, 16);
225911
226408
  const dir = shadersCacheDir(root);
225912
- mkdirSync25(dir, { recursive: true });
226409
+ mkdirSync27(dir, { recursive: true });
225913
226410
  const liteName = `${hash}_webgl-lite.filamat`;
225914
- if (!existsSync34(join36(dir, liteName)))
225915
- writeFileSync27(join36(dir, liteName), JSON.stringify(buildLiteMaterialArtifact2(e2.text ?? "")));
226411
+ if (!existsSync38(join40(dir, liteName)))
226412
+ writeFileSync30(join40(dir, liteName), JSON.stringify(buildLiteMaterialArtifact2(e2.text ?? "")));
225916
226413
  const src = `/project/.lecodes/shaders/${hash}_opengl.filamat`;
225917
226414
  e2.shaders = ["opengl", "webgl-lite"].map((platform) => ({ platform, src }));
225918
226415
  }
@@ -225942,13 +226439,14 @@ var compileSceneEditor = async (opts) => {
225942
226439
  // src/commands/scene.ts
225943
226440
  init_browserAuth();
225944
226441
  init_util();
226442
+ init_peerInstall();
225945
226443
  var DEFAULT_PORT3 = 4488;
225946
226444
  var findAppRoot = (start) => {
225947
226445
  let dir = start;
225948
226446
  while (true) {
225949
- if (existsSync35(join37(dir, "app.json")))
226447
+ if (existsSync39(join41(dir, "app.json")))
225950
226448
  return dir;
225951
- const parent = dirname18(dir);
226449
+ const parent = dirname19(dir);
225952
226450
  if (parent === dir)
225953
226451
  return null;
225954
226452
  dir = parent;
@@ -225972,26 +226470,26 @@ var getContext2 = (args) => {
225972
226470
  manifest = null;
225973
226471
  const pathArg = args._[0];
225974
226472
  if (pathArg) {
225975
- const abs = resolve18(process.cwd(), pathArg);
225976
- if (existsSync35(abs) && statSync14(abs).isDirectory())
226473
+ const abs = resolve20(process.cwd(), pathArg);
226474
+ if (existsSync39(abs) && statSync14(abs).isDirectory())
225977
226475
  root = abs;
225978
- else if (abs.endsWith(".scene.ts") && existsSync35(abs))
225979
- root = findAppRoot(dirname18(abs)) ?? dirname18(abs);
226476
+ else if (abs.endsWith(".scene.ts") && existsSync39(abs))
226477
+ root = findAppRoot(dirname19(abs)) ?? dirname19(abs);
225980
226478
  else
225981
226479
  throw new CliError(`No such folder or .scene.ts: ${pathArg}`);
225982
226480
  } else {
225983
226481
  root = findAppRoot(root) ?? root;
225984
226482
  }
225985
226483
  }
225986
- return { root, manifest, name: manifest?.name ?? basename18(root) };
226484
+ return { root, manifest, name: manifest?.name ?? basename19(root) };
225987
226485
  };
225988
226486
  var writeMcpConfig2 = (root, port) => {
225989
- const path2 = join37(root, ".mcp.json");
226487
+ const path2 = join41(root, ".mcp.json");
225990
226488
  const entry = { type: "http", url: `http://localhost:${port}/mcp` };
225991
226489
  let config = {};
225992
- if (existsSync35(path2)) {
226490
+ if (existsSync39(path2)) {
225993
226491
  try {
225994
- config = JSON.parse(readFileSync32(path2, "utf8"));
226492
+ config = JSON.parse(readFileSync35(path2, "utf8"));
225995
226493
  } catch {
225996
226494
  warnErr(".mcp.json exists but isn't valid JSON — left it untouched. Add the lecodes-scene server by hand.");
225997
226495
  return;
@@ -226004,16 +226502,16 @@ var writeMcpConfig2 = (root, port) => {
226004
226502
  return;
226005
226503
  }
226006
226504
  config.mcpServers["lecodes-scene"] = entry;
226007
- writeFileSync28(path2, JSON.stringify(config, null, 2) + `
226505
+ writeFileSync31(path2, JSON.stringify(config, null, 2) + `
226008
226506
  `);
226009
226507
  note(`${existing ? "updated" : "created"} .mcp.json (lecodes-scene MCP → ${entry.url})`);
226010
226508
  };
226011
226509
  var countScenes = (dir) => {
226012
226510
  let n2 = 0;
226013
- for (const name of readdirSync16(dir)) {
226511
+ for (const name of readdirSync17(dir)) {
226014
226512
  if (name === "node_modules" || name.startsWith("."))
226015
226513
  continue;
226016
- const full = join37(dir, name);
226514
+ const full = join41(dir, name);
226017
226515
  if (statSync14(full).isDirectory())
226018
226516
  n2 += countScenes(full);
226019
226517
  else if (name.endsWith(".scene.ts"))
@@ -226023,12 +226521,7 @@ var countScenes = (dir) => {
226023
226521
  };
226024
226522
  var scene = async (args) => {
226025
226523
  const ctx = getContext2(args);
226026
- let sceneServer;
226027
- try {
226028
- sceneServer = await import("lecodes-3d-editor/server");
226029
- } catch {
226030
- throw new CliError("The scene editor isn't installed. Run: npm install -g lecodes-3d-editor");
226031
- }
226524
+ const sceneServer = await loadPeer("lecodes-3d-editor", "server", { for: "lecodes scene" });
226032
226525
  const port = Number(flagStr(args, "port")) || DEFAULT_PORT3;
226033
226526
  if (countScenes(ctx.root) === 0) {
226034
226527
  warnErr(`No .scene.ts file under ${ctx.root} yet — add one and the editor will pick it up.`);
@@ -226056,11 +226549,12 @@ var scene = async (args) => {
226056
226549
  };
226057
226550
 
226058
226551
  // src/commands/thumbs.ts
226059
- import { createRequire as createRequire6 } from "node:module";
226552
+ init_peerInstall();
226553
+ import { createRequire as createRequire7 } from "node:module";
226060
226554
  import { execFileSync as execFileSync7 } from "node:child_process";
226061
- import { existsSync as existsSync36, mkdtempSync as mkdtempSync6, readdirSync as readdirSync17, rmSync as rmSync12, statSync as statSync15 } from "node:fs";
226555
+ import { existsSync as existsSync40, mkdtempSync as mkdtempSync6, readdirSync as readdirSync18, rmSync as rmSync12, statSync as statSync15 } from "node:fs";
226062
226556
  import { tmpdir as tmpdir6 } from "node:os";
226063
- import { extname, join as join38, relative as relative7, resolve as resolve19 } from "node:path";
226557
+ import { extname, join as join42, relative as relative9, resolve as resolve21 } from "node:path";
226064
226558
  init_util();
226065
226559
  var DEFAULT_SIZE = 256;
226066
226560
  var IMAGE_EXT = /\.(png|jpe?g|webp|bmp|gif|tiff?|ktx2)$/i;
@@ -226071,13 +226565,13 @@ var findKtx = () => {
226071
226565
  process.env.LECODES_KTX,
226072
226566
  process.env.KTX_CLI,
226073
226567
  process.env.KTX_TOKTX?.replace(/toktx(\.exe)?$/i, exe),
226074
- process.platform === "win32" ? join38(process.env.LOCALAPPDATA ?? "", "Programs", "KTX-Software", "bin", exe) : null,
226075
- process.platform === "win32" ? join38(process.env.ProgramFiles ?? "", "KTX-Software", "bin", exe) : null,
226568
+ process.platform === "win32" ? join42(process.env.LOCALAPPDATA ?? "", "Programs", "KTX-Software", "bin", exe) : null,
226569
+ process.platform === "win32" ? join42(process.env.ProgramFiles ?? "", "KTX-Software", "bin", exe) : null,
226076
226570
  "/usr/local/bin/ktx",
226077
226571
  "/opt/homebrew/bin/ktx"
226078
226572
  ].filter((p2) => !!p2);
226079
226573
  for (const path2 of candidates) {
226080
- if (existsSync36(path2))
226574
+ if (existsSync40(path2))
226081
226575
  return path2;
226082
226576
  }
226083
226577
  try {
@@ -226089,8 +226583,8 @@ var findKtx = () => {
226089
226583
  };
226090
226584
  var sharpResizer = () => {
226091
226585
  try {
226092
- const req = createRequire6(import.meta.url);
226093
- const sharp = createRequire6(req.resolve("lecodes-assets/cli"))("sharp");
226586
+ const req = createRequire7(import.meta.url);
226587
+ const sharp = createRequire7(req.resolve("lecodes-assets/cli"))("sharp");
226094
226588
  return async (file, size) => sharp(file).resize({ width: size, height: size, fit: "inside", withoutEnlargement: true }).png().toBuffer();
226095
226589
  } catch {
226096
226590
  return null;
@@ -226104,21 +226598,21 @@ var jimpResizer = () => async (file, size) => {
226104
226598
  return await img.getBuffer("image/png");
226105
226599
  };
226106
226600
  var ktxToPng = (ktx, file, out) => {
226107
- const run2 = (args) => execFileSync7(ktx, args, { stdio: ["ignore", "ignore", "pipe"], encoding: "utf8" });
226601
+ const run3 = (args) => execFileSync7(ktx, args, { stdio: ["ignore", "ignore", "pipe"], encoding: "utf8" });
226108
226602
  try {
226109
- run2(["extract", "--transcode", "rgba8", "--level", "0", file, out]);
226603
+ run3(["extract", "--transcode", "rgba8", "--level", "0", file, out]);
226110
226604
  } catch (e2) {
226111
- run2(["extract", "--level", "0", file, out]);
226112
- if (!existsSync36(out))
226605
+ run3(["extract", "--level", "0", file, out]);
226606
+ if (!existsSync40(out))
226113
226607
  throw e2;
226114
226608
  }
226115
226609
  };
226116
226610
  var collect = (dir) => {
226117
226611
  const out = [];
226118
- for (const name of readdirSync17(dir)) {
226612
+ for (const name of readdirSync18(dir)) {
226119
226613
  if (name === "node_modules" || name.startsWith("."))
226120
226614
  continue;
226121
- const full = join38(dir, name);
226615
+ const full = join42(dir, name);
226122
226616
  if (statSync15(full).isDirectory())
226123
226617
  out.push(...collect(full));
226124
226618
  else if (IMAGE_EXT.test(name))
@@ -226128,20 +226622,15 @@ var collect = (dir) => {
226128
226622
  };
226129
226623
  var thumbs = async (args) => {
226130
226624
  const root = projectRootOf(process.cwd());
226131
- let sceneServer;
226132
- try {
226133
- sceneServer = await import("lecodes-3d-editor/server");
226134
- } catch {
226135
- throw new CliError("The scene editor isn't installed (it owns the preview cache). Run: npm install -g lecodes-3d-editor");
226136
- }
226625
+ const sceneServer = await loadPeer("lecodes-3d-editor", "server", { for: "lecodes thumbs" });
226137
226626
  const size = Number(flagStr(args, "size")) || DEFAULT_SIZE;
226138
226627
  const force = flagBool(args, "force");
226139
226628
  let files = [];
226140
226629
  const rendered = [];
226141
226630
  if (args._.length > 0) {
226142
226631
  for (const arg of args._) {
226143
- const abs = resolve19(process.cwd(), arg);
226144
- if (!existsSync36(abs))
226632
+ const abs = resolve21(process.cwd(), arg);
226633
+ if (!existsSync40(abs))
226145
226634
  throw new CliError(`No such file or folder: ${arg}`);
226146
226635
  if (statSync15(abs).isDirectory())
226147
226636
  files.push(...collect(abs));
@@ -226163,12 +226652,12 @@ var thumbs = async (args) => {
226163
226652
  const ktx = files.some((f3) => f3.toLowerCase().endsWith(".ktx2")) ? findKtx() : null;
226164
226653
  const sharp = sharpResizer();
226165
226654
  const resize = sharp ?? jimpResizer();
226166
- const tmp = mkdtempSync6(join38(tmpdir6(), "lecodes-thumbs-"));
226655
+ const tmp = mkdtempSync6(join42(tmpdir6(), "lecodes-thumbs-"));
226167
226656
  let made = 0, fresh = 0;
226168
226657
  const skipped = [];
226169
226658
  try {
226170
226659
  for (const [i2, file] of files.entries()) {
226171
- const asset = "./" + toKey(relative7(root, file));
226660
+ const asset = "./" + toKey(relative9(root, file));
226172
226661
  progress(i2, files.length, asset);
226173
226662
  const hash = sceneServer.hashAsset(root, asset);
226174
226663
  if (!hash)
@@ -226189,7 +226678,7 @@ var thumbs = async (args) => {
226189
226678
  skipped.push(`${asset} (KTX2 needs KTX-Software's \`ktx\` — set LECODES_KTX or put it on PATH)`);
226190
226679
  continue;
226191
226680
  }
226192
- source = join38(tmp, `${i2}.png`);
226681
+ source = join42(tmp, `${i2}.png`);
226193
226682
  ktxToPng(ktx, file, source);
226194
226683
  }
226195
226684
  sceneServer.writePreviewFile(root, asset, await resize(source, size));
@@ -226209,16 +226698,13 @@ var thumbs = async (args) => {
226209
226698
  if (rendered.length > 0) {
226210
226699
  warnErr(`Models, materials and prefabs are RENDERED, not decoded — the scene editor does those: ${rendered.join(", ")}`);
226211
226700
  }
226212
- note(`Cache: ${join38(root, ".lecodes", "preview")} (re-runs skip everything unchanged)`);
226701
+ note(`Cache: ${join42(root, ".lecodes", "preview")} (re-runs skip everything unchanged)`);
226213
226702
  };
226214
226703
 
226215
- // src/commands/assets.ts
226216
- init_util();
226217
-
226218
226704
  // src/cmgenTool.ts
226219
- import { existsSync as existsSync37, readdirSync as readdirSync18 } from "node:fs";
226220
- import { homedir as homedir8 } from "node:os";
226221
- import { join as join39, resolve as resolve20 } from "node:path";
226705
+ import { existsSync as existsSync41, readdirSync as readdirSync19 } from "node:fs";
226706
+ import { homedir as homedir9 } from "node:os";
226707
+ import { join as join43, resolve as resolve22 } from "node:path";
226222
226708
  init_util();
226223
226709
  var exeName2 = () => process.platform === "win32" ? "cmgen.exe" : "cmgen";
226224
226710
  var platformKey3 = () => {
@@ -226228,10 +226714,10 @@ var platformKey3 = () => {
226228
226714
  return "linux-x64";
226229
226715
  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}.`);
226230
226716
  };
226231
- var cacheRoot3 = () => join39(homedir8(), ".lecodes", "cmgen");
226717
+ var cacheRoot3 = () => join43(homedir9(), ".lecodes", "cmgen");
226232
226718
  var findCmgenIn = (dir) => {
226233
- for (const entry of readdirSync18(dir, { withFileTypes: true })) {
226234
- const p2 = join39(dir, entry.name);
226719
+ for (const entry of readdirSync19(dir, { withFileTypes: true })) {
226720
+ const p2 = join43(dir, entry.name);
226235
226721
  if (entry.isFile() && entry.name === exeName2())
226236
226722
  return p2;
226237
226723
  if (entry.isDirectory()) {
@@ -226244,18 +226730,21 @@ var findCmgenIn = (dir) => {
226244
226730
  };
226245
226731
  var installedCmgenVersions = () => {
226246
226732
  try {
226247
- return readdirSync18(cacheRoot3(), { withFileTypes: true }).filter((e2) => e2.isDirectory() && e2.name.startsWith("cmgen-")).map((e2) => e2.name.slice("cmgen-".length)).filter((v) => findCmgenIn(join39(cacheRoot3(), `cmgen-${v}`)) !== null).sort(semverNewer);
226733
+ return readdirSync19(cacheRoot3(), { withFileTypes: true }).filter((e2) => e2.isDirectory() && e2.name.startsWith("cmgen-")).map((e2) => e2.name.slice("cmgen-".length)).filter((v) => findCmgenIn(join43(cacheRoot3(), `cmgen-${v}`)) !== null).sort(semverNewer);
226248
226734
  } catch {
226249
226735
  return [];
226250
226736
  }
226251
226737
  };
226252
226738
  var fetchCmgenManifest = () => fetchReleaseProduct("cmgen", "cmgen (environment tool)");
226253
- var downloadCmgen = async (version, versions, dest) => {
226739
+ var pickCmgen = (versions) => {
226254
226740
  const key = platformKey3();
226255
- const file = versions.find((v) => v.version === version)?.files[key];
226256
- if (!file) {
226257
- throw new CliError(`cmgen ${version} [${key}] isn't published — build filament's host tools locally and point ` + `LECODES_CMGEN at the binary.`);
226741
+ const hit = newestForPlatform(versions, key);
226742
+ if (!hit) {
226743
+ throw new CliError(`cmgen isn't published for ${key} — build filament's host tools locally and point ` + `LECODES_CMGEN at the binary.`);
226258
226744
  }
226745
+ return hit;
226746
+ };
226747
+ var downloadCmgen = async (version, file, dest) => {
226259
226748
  note(`Downloading the environment tool (cmgen ${version}, ${(file.size / 1024 / 1024).toFixed(1)} MB)…`);
226260
226749
  await downloadReleaseArchive(file, {
226261
226750
  cacheRoot: cacheRoot3(),
@@ -226266,20 +226755,21 @@ var downloadCmgen = async (version, versions, dest) => {
226266
226755
  var resolveCmgenExeCached = () => {
226267
226756
  const override = process.env.LECODES_CMGEN;
226268
226757
  if (override) {
226269
- if (!existsSync37(override))
226758
+ if (!existsSync41(override))
226270
226759
  throw new CliError(`LECODES_CMGEN points at "${override}" which doesn't exist.`);
226271
- return resolve20(override);
226760
+ return resolve22(override);
226272
226761
  }
226273
226762
  const cached = installedCmgenVersions()[0];
226274
- return cached ? findCmgenIn(join39(cacheRoot3(), `cmgen-${cached}`)) : null;
226763
+ return cached ? findCmgenIn(join43(cacheRoot3(), `cmgen-${cached}`)) : null;
226275
226764
  };
226276
226765
  var resolveCmgenExe = async () => {
226277
226766
  const cached = resolveCmgenExeCached();
226278
226767
  if (cached)
226279
226768
  return cached;
226280
- const { latest, versions } = await fetchCmgenManifest();
226281
- const dir = join39(cacheRoot3(), `cmgen-${latest}`);
226282
- await downloadCmgen(latest, versions, dir);
226769
+ const { versions } = await fetchCmgenManifest();
226770
+ const { version, file } = pickCmgen(versions);
226771
+ const dir = join43(cacheRoot3(), `cmgen-${version}`);
226772
+ await downloadCmgen(version, file, dir);
226283
226773
  const exe = findCmgenIn(dir);
226284
226774
  if (!exe)
226285
226775
  throw new CliError(`cmgen cache at ${dir} is broken — delete the folder and retry.`);
@@ -226287,13 +226777,9 @@ var resolveCmgenExe = async () => {
226287
226777
  };
226288
226778
 
226289
226779
  // src/commands/assets.ts
226780
+ init_peerInstall();
226290
226781
  var assets = async (args) => {
226291
- let mod;
226292
- try {
226293
- mod = await import("lecodes-assets/cli");
226294
- } catch {
226295
- throw new CliError("The asset pipeline isn't installed. Run: npm install -g lecodes-assets");
226296
- }
226782
+ const mod = await loadPeer("lecodes-assets", "cli", { for: "lecodes assets" });
226297
226783
  const raw = args._raw ?? args._;
226298
226784
  if (raw[0] === "sky" && !process.env.LECODES_CMGEN) {
226299
226785
  process.env.LECODES_CMGEN = await resolveCmgenExe();
@@ -226336,8 +226822,8 @@ var status3 = async (_args) => {
226336
226822
  };
226337
226823
 
226338
226824
  // src/commands/test.ts
226339
- import { existsSync as existsSync38, mkdirSync as mkdirSync26, readFileSync as readFileSync33, readdirSync as readdirSync19 } from "node:fs";
226340
- import { dirname as dirname19, resolve as resolve21 } from "node:path";
226825
+ import { existsSync as existsSync42, mkdirSync as mkdirSync28, readFileSync as readFileSync36, readdirSync as readdirSync20 } from "node:fs";
226826
+ import { dirname as dirname20, resolve as resolve23 } from "node:path";
226341
226827
  init_manifest();
226342
226828
  init_util();
226343
226829
  var deriveDesignFlows = (meta, entryScreen) => {
@@ -226400,7 +226886,7 @@ var slug3 = (s2) => s2.replace(/[^a-zA-Z0-9-_]+/g, "-").replace(/^-+|-+$/g, "").
226400
226886
  var readFlowFile = (abs, displayName) => {
226401
226887
  let raw;
226402
226888
  try {
226403
- raw = JSON.parse(readFileSync33(abs, "utf8"));
226889
+ raw = JSON.parse(readFileSync36(abs, "utf8"));
226404
226890
  } catch (e2) {
226405
226891
  throw new CliError(`Can't read "${displayName}": ${e2 instanceof Error ? e2.message : String(e2)}`);
226406
226892
  }
@@ -226414,7 +226900,7 @@ var readFlowFile = (abs, displayName) => {
226414
226900
  name: file.name ?? displayName,
226415
226901
  kind: "file",
226416
226902
  steps: file.steps,
226417
- fixtures: resolveFixtures(file.fixtures, dirname19(abs)),
226903
+ fixtures: resolveFixtures(file.fixtures, dirname20(abs)),
226418
226904
  width: file.width,
226419
226905
  height: file.height,
226420
226906
  settle: file.settle,
@@ -226455,40 +226941,40 @@ var test = async (args) => {
226455
226941
  if (!Number.isFinite(nativeFixedDt) || nativeFixedDt < 0 || nativeFixedDt > 1000)
226456
226942
  throw new CliError(`--fixed-dt wants a number of ms (0 = wall clock, max 1000), got "${fixedDtFlag}".`);
226457
226943
  const projectViewport = desktopDefaultViewport(root);
226458
- const testsDir = resolve21(root, flagStr(args, "dir") ?? "tests");
226459
- const designMetaPath = resolve21(root, flagStr(args, "design-dir") ?? "design", "meta.json");
226944
+ const testsDir = resolve23(root, flagStr(args, "dir") ?? "tests");
226945
+ const designMetaPath = resolve23(root, flagStr(args, "design-dir") ?? "design", "meta.json");
226460
226946
  const asJson = flagBool(args, "json");
226461
226947
  const out = asJson ? logErr : log;
226462
226948
  const cases = [];
226463
226949
  if (args._.length > 0) {
226464
226950
  for (const given of args._) {
226465
- const candidates = [resolve21(given), resolve21(testsDir, given), resolve21(testsDir, `${given}.flow.json`)];
226466
- const abs = candidates.find((p2) => existsSync38(p2));
226951
+ const candidates = [resolve23(given), resolve23(testsDir, given), resolve23(testsDir, `${given}.flow.json`)];
226952
+ const abs = candidates.find((p2) => existsSync42(p2));
226467
226953
  if (!abs)
226468
226954
  throw new CliError(`No such test: "${given}" (looked in ${testsDir}).`);
226469
226955
  cases.push(readFlowFile(abs, given));
226470
226956
  }
226471
- } else if (existsSync38(testsDir)) {
226472
- for (const f3 of readdirSync19(testsDir).filter((f4) => f4.endsWith(".flow.json")).sort()) {
226473
- cases.push(readFlowFile(resolve21(testsDir, f3), f3));
226957
+ } else if (existsSync42(testsDir)) {
226958
+ for (const f3 of readdirSync20(testsDir).filter((f4) => f4.endsWith(".flow.json")).sort()) {
226959
+ cases.push(readFlowFile(resolve23(testsDir, f3), f3));
226474
226960
  }
226475
226961
  }
226476
226962
  const fixturesFlag = flagStr(args, "fixtures");
226477
- const defaultPack = resolve21(testsDir, "fixtures.json");
226478
- const flowFixtures = fixturesFlag !== undefined ? resolveFixtures(fixturesFlag, ".") : existsSync38(defaultPack) ? resolveFixtures("fixtures.json", testsDir) : undefined;
226963
+ const defaultPack = resolve23(testsDir, "fixtures.json");
226964
+ const flowFixtures = fixturesFlag !== undefined ? resolveFixtures(fixturesFlag, ".") : existsSync42(defaultPack) ? resolveFixtures("fixtures.json", testsDir) : undefined;
226479
226965
  const flowsOnly = flagBool(args, "flows");
226480
226966
  const wantFlows = !flagBool(args, "no-flows") && args._.length === 0;
226481
226967
  if (flowsOnly)
226482
226968
  cases.length = 0;
226483
226969
  let skipped = [];
226484
226970
  let entryScreen = null;
226485
- if (desktop2 && wantFlows && existsSync38(designMetaPath)) {
226971
+ if (desktop2 && wantFlows && existsSync42(designMetaPath)) {
226486
226972
  note("Design-derived flows skipped with --desktop (they tap by name; run `lecodes test` headless for them).");
226487
226973
  }
226488
- if (!desktop2 && (wantFlows || flowsOnly) && existsSync38(designMetaPath)) {
226974
+ if (!desktop2 && (wantFlows || flowsOnly) && existsSync42(designMetaPath)) {
226489
226975
  let meta;
226490
226976
  try {
226491
- meta = JSON.parse(readFileSync33(designMetaPath, "utf8"));
226977
+ meta = JSON.parse(readFileSync36(designMetaPath, "utf8"));
226492
226978
  } catch (e2) {
226493
226979
  throw new CliError(`Can't read ${designMetaPath}: ${e2 instanceof Error ? e2.message : String(e2)}`);
226494
226980
  }
@@ -226520,16 +227006,16 @@ var test = async (args) => {
226520
227006
  }, timeoutMs);
226521
227007
  killer.unref?.();
226522
227008
  const onConsole = flagBool(args, "logs") ? (level, text) => logErr(`${c.dim(`[${level}]`)} ${text}`) : undefined;
226523
- const artifactsRoot = resolve21(root, flagStr(args, "artifacts") ?? resolve21(testsDir, ".artifacts"));
227009
+ const artifactsRoot = resolve23(root, flagStr(args, "artifacts") ?? resolve23(testsDir, ".artifacts"));
226524
227010
  const results = [];
226525
227011
  const runNativeCase = async (kase) => {
226526
- const caseDir = resolve21(artifactsRoot, slug3(kase.name));
227012
+ const caseDir = resolve23(artifactsRoot, slug3(kase.name));
226527
227013
  const plan = translateScenario(kase.steps, { outDir: caseDir, headroomFrames: nativeFrames });
226528
227014
  for (const st of plan.steps)
226529
227015
  if (st.screenshot)
226530
- mkdirSync26(dirname19(st.screenshot), { recursive: true });
226531
- mkdirSync26(caseDir, { recursive: true });
226532
- const finalPng = resolve21(caseDir, "final.png");
227016
+ mkdirSync28(dirname20(st.screenshot), { recursive: true });
227017
+ mkdirSync28(caseDir, { recursive: true });
227018
+ const finalPng = resolve23(caseDir, "final.png");
226533
227019
  const { output } = await runDesktopRender(native.js, native.resources, {
226534
227020
  renderer: desktopRenderer,
226535
227021
  pluginDirs: desktopPluginLibraries(root).dirs,
@@ -226582,14 +227068,14 @@ var test = async (args) => {
226582
227068
  onConsole
226583
227069
  });
226584
227070
  try {
226585
- const io = fileScenarioIO(resolve21(artifactsRoot, slug3(kase.name)));
227071
+ const io = fileScenarioIO(resolve23(artifactsRoot, slug3(kase.name)));
226586
227072
  const outcome = await renderer.runScenario(session, steps, io);
226587
227073
  result = {
226588
227074
  name: kase.name,
226589
227075
  kind: kase.kind,
226590
227076
  ok: outcome.ok,
226591
227077
  steps: outcome.steps.map((s2) => ({ desc: s2.desc, ok: s2.ok, error: s2.error })),
226592
- artifacts: outcome.failureArtifacts.map((p2) => resolve21(artifactsRoot, slug3(kase.name), p2))
227078
+ artifacts: outcome.failureArtifacts.map((p2) => resolve23(artifactsRoot, slug3(kase.name), p2))
226593
227079
  };
226594
227080
  } finally {
226595
227081
  session.dispose();
@@ -226652,41 +227138,14 @@ var test = async (args) => {
226652
227138
  };
226653
227139
 
226654
227140
  // src/commands/update.ts
226655
- import { spawnSync as spawnSync7 } from "node:child_process";
226656
- import { existsSync as existsSync40, readdirSync as readdirSync20 } from "node:fs";
226657
- import { homedir as homedir9 } from "node:os";
226658
- import { dirname as dirname21, join as join41, resolve as resolve23 } from "node:path";
227141
+ init_package();
226659
227142
  init_util();
226660
-
226661
- // src/peers.ts
226662
- import { existsSync as existsSync39, readFileSync as readFileSync34 } from "node:fs";
226663
- import { createRequire as createRequire7 } from "node:module";
226664
- import { dirname as dirname20, join as join40 } from "node:path";
226665
- var OPTIONAL_PEERS = [
226666
- ["lecodes-renderer", "lecodes-renderer/headless"],
226667
- ["lecodes-design", "lecodes-design/server"],
226668
- ["lecodes-3d-editor", "lecodes-3d-editor/server"],
226669
- ["lecodes-assets", "lecodes-assets/cli"]
226670
- ];
226671
- var peerVersion = (name, probe) => {
226672
- try {
226673
- const require2 = createRequire7(import.meta.url);
226674
- let dir = dirname20(require2.resolve(probe));
226675
- for (let depth = 0;depth < 16; depth++) {
226676
- const manifest = join40(dir, "package.json");
226677
- if (existsSync39(manifest)) {
226678
- const parsed = JSON.parse(readFileSync34(manifest, "utf8"));
226679
- if (parsed.name === name)
226680
- return parsed.version ?? "unknown";
226681
- }
226682
- const parent = dirname20(dir);
226683
- if (parent === dir)
226684
- break;
226685
- dir = parent;
226686
- }
226687
- } catch {}
226688
- return null;
226689
- };
227143
+ init_peers();
227144
+ init_peerInstall();
227145
+ import { spawnSync as spawnSync9 } from "node:child_process";
227146
+ import { existsSync as existsSync43, readdirSync as readdirSync21 } from "node:fs";
227147
+ import { homedir as homedir10 } from "node:os";
227148
+ import { dirname as dirname21, join as join44, resolve as resolve25 } from "node:path";
226690
227149
 
226691
227150
  // src/commands/types.ts
226692
227151
  init_api();
@@ -226694,11 +227153,11 @@ init_config();
226694
227153
  init_manifest();
226695
227154
  init_types4();
226696
227155
  init_util();
226697
- import { resolve as resolve22 } from "node:path";
227156
+ import { resolve as resolve24 } from "node:path";
226698
227157
  var label = (bundle) => `${Object.keys(bundle).length} file(s)`;
226699
227158
  var types4 = async (args) => {
226700
227159
  const dirArg = args._[0];
226701
- const root = dirArg ? resolve22(dirArg) : (() => {
227160
+ const root = dirArg ? resolve24(dirArg) : (() => {
226702
227161
  try {
226703
227162
  return findProjectRoot(process.cwd());
226704
227163
  } catch {
@@ -226758,10 +227217,10 @@ var types4 = async (args) => {
226758
227217
 
226759
227218
  // src/commands/update.ts
226760
227219
  var findProjectDir = (start) => {
226761
- const home = resolve23(homedir9());
226762
- let dir = resolve23(start);
227220
+ const home = resolve25(homedir10());
227221
+ let dir = resolve25(start);
226763
227222
  while (true) {
226764
- if (dir !== home && (existsSync40(join41(dir, "app.json")) || existsSync40(join41(dir, ".lecodes", "manifest.json")) || existsSync40(join41(dir, ".lecodes", "types"))))
227223
+ if (dir !== home && (existsSync43(join44(dir, "app.json")) || existsSync43(join44(dir, ".lecodes", "manifest.json")) || existsSync43(join44(dir, ".lecodes", "types"))))
226765
227224
  return dir;
226766
227225
  const parent = dirname21(dir);
226767
227226
  if (parent === dir)
@@ -226791,12 +227250,12 @@ var semverNewer2 = (a2, b) => {
226791
227250
  return 0;
226792
227251
  };
226793
227252
  var FROM_SOURCE = /[\\/]src[\\/]commands[\\/]/.test(import.meta.url);
226794
- var STANDALONE = existsSync40(join41(dirname21(process.execPath), "runtime")) && existsSync40(join41(dirname21(process.execPath), process.platform === "win32" ? "chisel.exe" : "chisel"));
227253
+ var STANDALONE = existsSync43(join44(dirname21(process.execPath), "runtime")) && existsSync43(join44(dirname21(process.execPath), process.platform === "win32" ? "chisel.exe" : "chisel"));
226795
227254
  var update = async (args) => {
226796
227255
  const steps = [];
226797
227256
  const heading = (name, what) => log(`
226798
227257
  ${c.bold(name)} ${c.dim(what)}`);
226799
- const run2 = async (name, what, fn) => {
227258
+ const run3 = async (name, what, fn) => {
226800
227259
  heading(name, what);
226801
227260
  try {
226802
227261
  const r2 = await fn();
@@ -226811,77 +227270,88 @@ ${c.bold(name)} ${c.dim(what)}`);
226811
227270
  }
226812
227271
  };
226813
227272
  const root = findProjectDir(process.cwd());
226814
- await run2("desktop", "LeCodes Desktop host (desktop run / render --desktop)", async () => {
227273
+ await run3("desktop", "LeCodes Desktop host (desktop run / render --desktop)", async () => {
226815
227274
  if (process.env.LECODES_DESKTOP_EXE)
226816
227275
  return ["skipped", `LECODES_DESKTOP_EXE=${process.env.LECODES_DESKTOP_EXE} (a local build; \`lecodes desktop update\` refreshes the cache anyway)`];
226817
227276
  await updateDesktop();
226818
227277
  return "ok";
226819
227278
  });
226820
- await run2("matc", "Filament material compiler for local shader compiles", async () => {
227279
+ await run3("matc", "Filament material compiler for local shader compiles", async () => {
226821
227280
  if (process.env.LECODES_MATC)
226822
227281
  return ["skipped", `LECODES_MATC=${process.env.LECODES_MATC} (a local matc)`];
226823
227282
  await updateMatc();
226824
227283
  return "ok";
226825
227284
  });
226826
- await run2("shaders", "recompile the project's .mat shaders with the current matc", async () => {
227285
+ await run3("shaders", "recompile the project's .mat shaders with the current matc", async () => {
226827
227286
  if (!root)
226828
227287
  return ["skipped", "not inside a project"];
226829
227288
  await shaders({ _: ["compile"], flags: {} });
226830
227289
  return "ok";
226831
227290
  });
226832
- await run2("types", flagBool(args, "local") ? "IDE types from the SDK bundled in this CLI" : "IDE types from the server", async () => {
227291
+ await run3("types", flagBool(args, "local") ? "IDE types from the SDK bundled in this CLI" : "IDE types from the server", async () => {
226833
227292
  if (!root)
226834
227293
  return ["skipped", "not inside a project"];
226835
227294
  await types4({ _: [], flags: flagBool(args, "local") ? { local: true } : {} });
226836
227295
  return "ok";
226837
227296
  });
226838
- await run2("shells", "ios/ + android/ pins → newest SDK release + plugin tags, then re-sync", async () => {
227297
+ await run3("shells", "ios/ + android/ pins → newest SDK release + plugin tags, then re-sync", async () => {
226839
227298
  if (!root)
226840
227299
  return ["skipped", "not inside a project"];
226841
227300
  if (flagBool(args, "no-shells"))
226842
227301
  return ["skipped", "--no-shells"];
226843
- const present = ["ios", "android"].filter((d) => existsSync40(join41(root, d)) && readdirSync20(join41(root, d)).length > 0);
227302
+ const present = ["ios", "android"].filter((d) => existsSync43(join44(root, d)) && readdirSync21(join44(root, d)).length > 0);
226844
227303
  if (present.length === 0)
226845
227304
  return ["skipped", "no ios/ or android/ shell in this project (`lecodes app init ios|android`)"];
226846
227305
  await app({ _: ["update"], flags: {} });
226847
227306
  return "ok";
226848
227307
  });
226849
- await run2("cli", "lecodes-cli + its optional peers (global npm installs)", async () => {
227308
+ await run3("cli", "lecodes-cli + its optional peers (global npm installs)", async () => {
226850
227309
  if (flagBool(args, "no-cli"))
226851
227310
  return ["skipped", "--no-cli"];
226852
227311
  if (FROM_SOURCE)
226853
227312
  return ["skipped", "running from the repo checkout — the CLI and its peers are the repo's own"];
226854
227313
  const rows = [];
226855
227314
  for (const [name, probe] of OPTIONAL_PEERS) {
227315
+ const where = peerSource(name, probe);
226856
227316
  const installed = peerVersion(name, probe);
226857
- rows.push({ name, installed, latest: installed ? await npmLatest(name) : null });
227317
+ const latest = !installed ? null : where === "prefix" && name === "lecodes-renderer" ? package_default.version : await npmLatest(name);
227318
+ rows.push({ name, installed, latest, where });
226858
227319
  }
226859
- rows.push({ name: "lecodes-cli", installed: package_default.version, latest: await npmLatest("lecodes-cli") });
226860
- if (rows.every((r3) => r3.latest === null))
227320
+ rows.push({ name: "lecodes-cli", installed: package_default.version, latest: await npmLatest("lecodes-cli"), where: "global" });
227321
+ if (rows.every((r2) => r2.latest === null))
226861
227322
  return ["skipped", "npm unreachable"];
226862
- const behind = rows.filter((r3) => r3.installed && r3.latest && semverNewer2(r3.latest, r3.installed) > 0);
226863
- for (const r3 of rows) {
226864
- 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)");
226865
- log(" " + r3.name.padEnd(18) + " " + (r3.installed ?? "").padEnd(8) + " " + state);
227323
+ const behind = rows.filter((r2) => r2.installed && r2.latest && semverNewer2(r2.latest, r2.installed) > 0);
227324
+ for (const r2 of rows) {
227325
+ const state = !r2.installed ? c.dim("not installed (optional — installed on first use)") : !r2.latest ? c.dim("(npm: unknown)") : behind.includes(r2) ? c.yellow("→ " + r2.latest) : c.dim("(up to date)");
227326
+ log(" " + r2.name.padEnd(18) + " " + (r2.installed ?? "").padEnd(8) + " " + (r2.where === "prefix" ? c.dim("peers ") : "") + state);
226866
227327
  }
226867
227328
  if (behind.length === 0)
226868
227329
  return "ok";
226869
- const specs = behind.map((r3) => r3.name + "@latest");
226870
- if (flagBool(args, "check"))
226871
- return ["updated", behind.length + " behind — run: npm install -g " + specs.join(" ")];
226872
- if (STANDALONE && behind.some((r3) => r3.name === "lecodes-cli")) {
227330
+ const prefixBehind = behind.filter((r2) => r2.where === "prefix");
227331
+ const globalSpecs = behind.filter((r2) => r2.where !== "prefix").map((r2) => r2.name + "@latest");
227332
+ if (flagBool(args, "check")) {
227333
+ return ["updated", behind.length + " behind — run: lecodes update" + (globalSpecs.length ? " (global: npm install -g " + globalSpecs.join(" ") + ")" : "")];
227334
+ }
227335
+ const done = [];
227336
+ if (prefixBehind.length) {
227337
+ await installPeers(prefixBehind.map((r2) => r2.name), "lecodes update", { yes: true });
227338
+ done.push(...prefixBehind.map((r2) => r2.name));
227339
+ }
227340
+ if (STANDALONE && globalSpecs.some((s2) => s2.startsWith("lecodes-cli@"))) {
226873
227341
  note("this is the standalone build — the CLI updates by re-installing the release archive, not through npm; peers still go through npm");
226874
227342
  }
226875
- const toInstall = STANDALONE ? specs.filter((s2) => !s2.startsWith("lecodes-cli@")) : specs;
226876
- if (toInstall.length === 0)
227343
+ const toInstall = STANDALONE ? globalSpecs.filter((s2) => !s2.startsWith("lecodes-cli@")) : globalSpecs;
227344
+ if (toInstall.length) {
227345
+ log(" " + c.dim("$") + " npm install -g " + toInstall.join(" "));
227346
+ const r2 = spawnSync9("npm", ["install", "-g", ...toInstall], { stdio: "inherit", shell: true });
227347
+ if (r2.status !== 0)
227348
+ throw new CliError("npm install -g exited with " + (r2.status ?? "a signal"));
227349
+ done.push(...toInstall.map((s2) => s2.replace("@latest", "")));
227350
+ }
227351
+ if (done.length === 0)
226877
227352
  return ["skipped", "only the standalone CLI itself is behind — download the release"];
226878
- log(" " + c.dim("$") + " npm install -g " + toInstall.join(" "));
226879
- const r2 = spawnSync7("npm", ["install", "-g", ...toInstall], { stdio: "inherit", shell: true });
226880
- if (r2.status !== 0)
226881
- throw new CliError("npm install -g exited with " + (r2.status ?? "a signal"));
226882
- const names3 = toInstall.map((s2) => s2.replace("@latest", "")).join(", ");
226883
- const cliToo = toInstall.some((s2) => s2.startsWith("lecodes-cli@"));
226884
- return ["updated", names3 + " → latest" + (cliToo ? " (the new CLI runs from your next command)" : "")];
227353
+ const cliToo = done.includes("lecodes-cli");
227354
+ return ["updated", done.join(", ") + " → latest" + (cliToo ? " (the new CLI runs from your next command)" : "")];
226885
227355
  });
226886
227356
  log(`
226887
227357
  ${c.bold("Summary")}`);
@@ -226895,25 +227365,25 @@ ${c.bold("Summary")}`);
226895
227365
  };
226896
227366
 
226897
227367
  // src/index.ts
226898
- import { existsSync as existsSync43, realpathSync as realpathSync2 } from "node:fs";
226899
- import { delimiter as delimiter3, dirname as dirname24, join as join44 } from "node:path";
227368
+ import { existsSync as existsSync46, realpathSync as realpathSync2 } from "node:fs";
227369
+ import { delimiter as delimiter3, dirname as dirname24, join as join47 } from "node:path";
226900
227370
 
226901
227371
  // src/commands/install.ts
226902
227372
  init_util();
226903
227373
  import { execFileSync as execFileSync8 } from "node:child_process";
226904
- import { appendFileSync as appendFileSync3, chmodSync as chmodSync2, cpSync as cpSync5, existsSync as existsSync41, mkdirSync as mkdirSync27, readFileSync as readFileSync35, realpathSync, rmSync as rmSync13, symlinkSync as symlinkSync2 } from "node:fs";
226905
- import { homedir as homedir10 } from "node:os";
226906
- import { basename as basename19, delimiter as delimiter2, dirname as dirname22, join as join42, resolve as resolve24, sep as sep2 } from "node:path";
227374
+ import { appendFileSync as appendFileSync3, chmodSync as chmodSync2, cpSync as cpSync5, existsSync as existsSync44, mkdirSync as mkdirSync29, readFileSync as readFileSync37, realpathSync, rmSync as rmSync13, symlinkSync as symlinkSync2 } from "node:fs";
227375
+ import { homedir as homedir11 } from "node:os";
227376
+ import { basename as basename20, delimiter as delimiter2, dirname as dirname22, join as join45, resolve as resolve26, sep as sep2 } from "node:path";
226907
227377
  var isWin = process.platform === "win32";
226908
- var HOME = homedir10();
227378
+ var HOME = homedir11();
226909
227379
  var safeRealpath = (p2) => {
226910
227380
  try {
226911
227381
  return realpathSync(p2);
226912
227382
  } catch {
226913
- return resolve24(p2);
227383
+ return resolve26(p2);
226914
227384
  }
226915
227385
  };
226916
- var onPath = (dir) => {
227386
+ var onPath2 = (dir) => {
226917
227387
  const want = safeRealpath(dir);
226918
227388
  return (process.env.PATH ?? "").split(delimiter2).some((p2) => {
226919
227389
  if (!p2)
@@ -226927,24 +227397,24 @@ var onPath = (dir) => {
226927
227397
  };
226928
227398
  var install = async (args) => {
226929
227399
  const bundle = dirname22(process.execPath);
226930
- const exeName3 = basename19(process.execPath);
227400
+ const exeName3 = basename20(process.execPath);
226931
227401
  const chiselName = isWin ? "chisel.exe" : "chisel";
226932
- if (!existsSync41(join42(bundle, "runtime")) || !existsSync41(join42(bundle, chiselName))) {
227402
+ if (!existsSync44(join45(bundle, "runtime")) || !existsSync44(join45(bundle, chiselName))) {
226933
227403
  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.");
226934
227404
  }
226935
- const companions = [exeName3, chiselName, "runtime", "README.txt"].filter((f3) => existsSync41(join42(bundle, f3)));
227405
+ const companions2 = [exeName3, chiselName, "runtime", "README.txt"].filter((f3) => existsSync44(join45(bundle, f3)));
226936
227406
  if (isWin)
226937
- return installWindows(args, bundle, companions);
226938
- return installUnix(args, bundle, companions);
227407
+ return installWindows(args, bundle, companions2);
227408
+ return installUnix(args, bundle, companions2);
226939
227409
  };
226940
- var copyBundle = (bundle, target, companions) => {
226941
- mkdirSync27(target, { recursive: true });
227410
+ var copyBundle = (bundle, target, companions2) => {
227411
+ mkdirSync29(target, { recursive: true });
226942
227412
  if (safeRealpath(target) === safeRealpath(bundle))
226943
227413
  return;
226944
- for (const f3 of companions) {
226945
- const dest = join42(target, f3);
227414
+ for (const f3 of companions2) {
227415
+ const dest = join45(target, f3);
226946
227416
  rmSync13(dest, { recursive: true, force: true });
226947
- cpSync5(join42(bundle, f3), dest, { recursive: true });
227417
+ cpSync5(join45(bundle, f3), dest, { recursive: true });
226948
227418
  }
226949
227419
  };
226950
227420
  var permError = (e2, dir, system) => {
@@ -226954,43 +227424,43 @@ var permError = (e2, dir, system) => {
226954
227424
  }
226955
227425
  return new CliError(`Install failed: ${e2 instanceof Error ? e2.message : String(e2)}`);
226956
227426
  };
226957
- var installUnix = (args, bundle, companions) => {
227427
+ var installUnix = (args, bundle, companions2) => {
226958
227428
  const system = flagBool(args, "system");
226959
- const libDir = resolve24(flagStr(args, "dir") ?? (system ? "/usr/local/lib/lecodes-cli" : join42(HOME, ".local", "lib", "lecodes-cli")));
226960
- const binDir = resolve24(flagStr(args, "bin") ?? (system ? "/usr/local/bin" : join42(HOME, ".local", "bin")));
226961
- const link2 = join42(binDir, "lecodes");
227429
+ const libDir = resolve26(flagStr(args, "dir") ?? (system ? "/usr/local/lib/lecodes-cli" : join45(HOME, ".local", "lib", "lecodes-cli")));
227430
+ const binDir = resolve26(flagStr(args, "bin") ?? (system ? "/usr/local/bin" : join45(HOME, ".local", "bin")));
227431
+ const link2 = join45(binDir, "lecodes");
226962
227432
  try {
226963
- copyBundle(bundle, libDir, companions);
226964
- chmodSync2(join42(libDir, "lecodes"), 493);
226965
- chmodSync2(join42(libDir, "chisel"), 493);
227433
+ copyBundle(bundle, libDir, companions2);
227434
+ chmodSync2(join45(libDir, "lecodes"), 493);
227435
+ chmodSync2(join45(libDir, "chisel"), 493);
226966
227436
  } catch (e2) {
226967
227437
  throw permError(e2, libDir, system);
226968
227438
  }
226969
227439
  try {
226970
- mkdirSync27(binDir, { recursive: true });
227440
+ mkdirSync29(binDir, { recursive: true });
226971
227441
  rmSync13(link2, { force: true });
226972
- symlinkSync2(join42(libDir, "lecodes"), link2);
227442
+ symlinkSync2(join45(libDir, "lecodes"), link2);
226973
227443
  } catch (e2) {
226974
227444
  throw permError(e2, binDir, system);
226975
227445
  }
226976
227446
  success(`Installed to ${c.bold(libDir)}`);
226977
- log(` ${c.dim(link2 + " → " + join42(libDir, "lecodes"))}`);
227447
+ log(` ${c.dim(link2 + " → " + join45(libDir, "lecodes"))}`);
226978
227448
  finishPathUnix(args, binDir);
226979
227449
  };
226980
227450
  var shellRc = () => {
226981
- const shell = basename19(process.env.SHELL ?? "");
227451
+ const shell = basename20(process.env.SHELL ?? "");
226982
227452
  if (shell === "zsh")
226983
- return { file: join42(HOME, ".zshrc"), name: "~/.zshrc" };
227453
+ return { file: join45(HOME, ".zshrc"), name: "~/.zshrc" };
226984
227454
  if (shell === "bash")
226985
- return { file: join42(HOME, ".bashrc"), name: "~/.bashrc" };
226986
- return { file: join42(HOME, ".profile"), name: "~/.profile" };
227455
+ return { file: join45(HOME, ".bashrc"), name: "~/.bashrc" };
227456
+ return { file: join45(HOME, ".profile"), name: "~/.profile" };
226987
227457
  };
226988
227458
  var PATH_MARKER = "# Added by `lecodes install`";
226989
227459
  var appendPathToShellRc = (binDir) => {
226990
227460
  const { file, name } = shellRc();
226991
227461
  const line = `export PATH="${binDir}:$PATH"`;
226992
227462
  try {
226993
- const existing = existsSync41(file) ? readFileSync35(file, "utf8") : "";
227463
+ const existing = existsSync44(file) ? readFileSync37(file, "utf8") : "";
226994
227464
  if (existing.includes(line))
226995
227465
  return name;
226996
227466
  appendFileSync3(file, `${existing && !existing.endsWith(`
@@ -227005,7 +227475,7 @@ ${line}
227005
227475
  }
227006
227476
  };
227007
227477
  var finishPathUnix = (args, binDir) => {
227008
- if (onPath(binDir)) {
227478
+ if (onPath2(binDir)) {
227009
227479
  log(` Run ${c.bold("lecodes --version")} in a new shell to verify.`);
227010
227480
  return;
227011
227481
  }
@@ -227027,15 +227497,15 @@ var finishPathUnix = (args, binDir) => {
227027
227497
  log(` • let install do it: re-run with ${c.bold("--modify-path")}`);
227028
227498
  log(` • or install system-wide (no PATH edit): ${c.bold("sudo lecodes install --system")}`);
227029
227499
  };
227030
- var installWindows = (args, bundle, companions) => {
227031
- const target = resolve24(flagStr(args, "dir") ?? join42(process.env.LOCALAPPDATA ?? join42(HOME, "AppData", "Local"), "Programs", "lecodes-cli"));
227500
+ var installWindows = (args, bundle, companions2) => {
227501
+ const target = resolve26(flagStr(args, "dir") ?? join45(process.env.LOCALAPPDATA ?? join45(HOME, "AppData", "Local"), "Programs", "lecodes-cli"));
227032
227502
  try {
227033
- copyBundle(bundle, target, companions);
227503
+ copyBundle(bundle, target, companions2);
227034
227504
  } catch (e2) {
227035
227505
  throw new CliError(`Install failed writing to ${target}: ${e2 instanceof Error ? e2.message : String(e2)}`);
227036
227506
  }
227037
227507
  success(`Installed to ${c.bold(target)}`);
227038
- if (onPath(target)) {
227508
+ if (onPath2(target)) {
227039
227509
  log(` Already on PATH — run ${c.bold("lecodes --version")} in a new terminal.`);
227040
227510
  return;
227041
227511
  }
@@ -227067,9 +227537,9 @@ var addToUserPathWindows = (dir) => {
227067
227537
  init_util();
227068
227538
 
227069
227539
  // src/projectEnv.ts
227070
- import { existsSync as existsSync42, readFileSync as readFileSync36 } from "node:fs";
227071
- import { homedir as homedir11 } from "node:os";
227072
- import { dirname as dirname23, join as join43, resolve as resolve25 } from "node:path";
227540
+ import { existsSync as existsSync45, readFileSync as readFileSync38 } from "node:fs";
227541
+ import { homedir as homedir12 } from "node:os";
227542
+ import { dirname as dirname23, join as join46, resolve as resolve27 } from "node:path";
227073
227543
  var KEY_RE = /^(LECODES|CREATOR)_[A-Z0-9_]+$/;
227074
227544
  var parseDotEnv = (text) => {
227075
227545
  const out = {};
@@ -227104,17 +227574,20 @@ var applyDotEnv = (parsed, dir, env) => {
227104
227574
  continue;
227105
227575
  let value = raw;
227106
227576
  if (value.startsWith("./") || value.startsWith("../"))
227107
- value = resolve25(dir, value);
227577
+ value = resolve27(dir, value);
227108
227578
  else if (value === "~" || value.startsWith("~/"))
227109
- value = join43(homedir11(), value.slice(1));
227579
+ value = join46(homedir12(), value.slice(1));
227110
227580
  out[key] = value;
227111
227581
  }
227112
227582
  return out;
227113
227583
  };
227114
227584
  var findEnvRoot = (start) => {
227115
- let dir = resolve25(start);
227585
+ let dir = resolve27(start);
227586
+ const home = resolve27(homedir12());
227116
227587
  while (true) {
227117
- if (existsSync42(join43(dir, "app.json")) || existsSync42(join43(dir, ".lecodes")))
227588
+ if (dir === home)
227589
+ return null;
227590
+ if (existsSync45(join46(dir, "app.json")) || existsSync45(join46(dir, ".lecodes")))
227118
227591
  return dir;
227119
227592
  const parent = dirname23(dir);
227120
227593
  if (parent === dir)
@@ -227127,10 +227600,10 @@ var loadProjectEnv = (cwd = process.cwd()) => {
227127
227600
  const root = findEnvRoot(cwd);
227128
227601
  if (!root)
227129
227602
  return [];
227130
- const file = join43(root, ".env");
227131
- if (!existsSync42(file))
227603
+ const file = join46(root, ".env");
227604
+ if (!existsSync45(file))
227132
227605
  return [];
227133
- const added = applyDotEnv(parseDotEnv(readFileSync36(file, "utf8")), root, process.env);
227606
+ const added = applyDotEnv(parseDotEnv(readFileSync38(file, "utf8")), root, process.env);
227134
227607
  for (const [k, v] of Object.entries(added))
227135
227608
  process.env[k] = v;
227136
227609
  return Object.keys(added);
@@ -227140,10 +227613,11 @@ var loadProjectEnv = (cwd = process.cwd()) => {
227140
227613
  };
227141
227614
 
227142
227615
  // src/index.ts
227616
+ init_peers();
227143
227617
  var STANDALONE2 = (() => {
227144
227618
  const dir = dirname24(process.execPath);
227145
227619
  const chisel = process.platform === "win32" ? "chisel.exe" : "chisel";
227146
- return existsSync43(join44(dir, "runtime")) && existsSync43(join44(dir, chisel));
227620
+ return existsSync46(join47(dir, "runtime")) && existsSync46(join47(dir, chisel));
227147
227621
  })();
227148
227622
  var INSTALLED = STANDALONE2 && (() => {
227149
227623
  const exe = process.platform === "win32" ? "lecodes.exe" : "lecodes";
@@ -227158,8 +227632,8 @@ var INSTALLED = STANDALONE2 && (() => {
227158
227632
  if (!d)
227159
227633
  return false;
227160
227634
  try {
227161
- const cand = join44(d, exe);
227162
- return existsSync43(cand) && realpathSync2(cand) === self2;
227635
+ const cand = join47(d, exe);
227636
+ return existsSync46(cand) && realpathSync2(cand) === self2;
227163
227637
  } catch {
227164
227638
  return false;
227165
227639
  }
@@ -227227,6 +227701,14 @@ ${c.bold("Commands:")}
227227
227701
  --host <ip> LAN address to advertise (default: auto-detect)
227228
227702
  --entry <path> override the entrypoint
227229
227703
  --no-qr don't render the QR code
227704
+ --android open the dev url in the LeCodes app on the phone
227705
+ plugged in over adb (adb reverse: no Wi-Fi/LAN
227706
+ address needed). --serial <id> picks a device,
227707
+ --apk <file> installs first, --package <name>
227708
+ (default com.letary.lecodes). Device logs stream
227709
+ here. --time <s> --png <file>: bounded check —
227710
+ screenshot after <s> seconds, force-stop, exit
227711
+ (exit 1 on a crash). Ctrl+C force-stops the app.
227230
227712
  --web also open the app in the browser (Web Lite host:
227231
227713
  same bundle + hot reload, camera/map plugins
227232
227714
  have web twins; the /web/ URL is served always)
@@ -227422,12 +227904,15 @@ ${c.bold("Commands:")}
227422
227904
  --cell <m> (0.25) --cell-height <m> (0.1) --tile <cells> (48)
227423
227905
  --obj also write <n>.navmesh.obj to inspect
227424
227906
  --frames <n> load headroom (900) --logs host output
227425
- shaders [compile|update] The project's custom shaders (.mat) and their local compile:
227907
+ shaders [compile|update|new] The project's custom shaders (.mat) and their local compile:
227426
227908
  no args lists the shaders + their compile state; compile
227427
227909
  compiles them all into .lecodes/shaders (the same pass
227428
227910
  'lecodes dev' runs on the fly — via Filament's matc,
227429
227911
  downloaded on first use to ~/.lecodes/matc); update
227430
- downloads the newest matc (run after an engine upgrade)
227912
+ downloads the newest matc (run after an engine upgrade);
227913
+ new <name> [--ref lit] [--dir d] [--force] writes a fresh
227914
+ .mat + .material.ts: the starter, or a copy of the engine's
227915
+ own lit / unlit / particles / … source to start from
227431
227916
  scene [path] LeCodes Scene Editor — visually edit a .scene.ts (tree, inspector,
227432
227917
  gizmo) on local files (needs the optional 'lecodes-3d-editor' package;
227433
227918
  works inside a cloned project or standalone in any folder)
@@ -227542,12 +228027,13 @@ LECODES_DESKTOP_EXE, LECODES_MATC, LECODES_NOTARY_PROFILE, LECODES_MACOS_SIGN_ID
227542
228027
  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 };
227543
228028
  if (STANDALONE2)
227544
228029
  commands.install = install;
227545
- var readVersion = () => package_default.version ?? "unknown";
228030
+ var readVersion2 = () => package_default.version ?? "unknown";
227546
228031
  var printVersion = () => {
227547
- const rows = [["lecodes-cli", readVersion()]];
228032
+ const rows = [["lecodes-cli", readVersion2()]];
227548
228033
  for (const [name, probe] of OPTIONAL_PEERS) {
227549
228034
  const version = peerVersion(name, probe);
227550
- rows.push([name, version ?? c.dim("not installed")]);
228035
+ const where = peerSource(name, probe);
228036
+ rows.push([name, version ? where === "prefix" ? `${version} ${c.dim("(~/.lecodes/peers)")}` : version : c.dim("not installed — installed on first use")]);
227551
228037
  }
227552
228038
  const desktopVersion = selectedDesktopVersion();
227553
228039
  rows.push(["lecodes-desktop", installedDesktopVersions().includes(desktopVersion) ? desktopVersion : `${desktopVersion} ${c.dim("(not downloaded — fetched on first `render --desktop`)")}`]);
@@ -227561,6 +228047,8 @@ var main = async () => {
227561
228047
  const argv = process.argv.slice(2);
227562
228048
  const command = argv[0];
227563
228049
  loadProjectEnv();
228050
+ if (argv.includes("--yes") || argv.includes("-y"))
228051
+ process.env.LECODES_YES = "1";
227564
228052
  if (!command || command === "help" || command === "--help" || command === "-h") {
227565
228053
  log(HELP);
227566
228054
  return;
@@ -227569,14 +228057,14 @@ var main = async () => {
227569
228057
  printVersion();
227570
228058
  return;
227571
228059
  }
227572
- const run2 = commands[command];
227573
- if (!run2) {
228060
+ const run3 = commands[command];
228061
+ if (!run3) {
227574
228062
  log(c.red(`Unknown command: ${command}`));
227575
228063
  log(HELP);
227576
228064
  process.exitCode = 1;
227577
228065
  return;
227578
228066
  }
227579
- await run2(Object.assign(parseArgs(argv.slice(1)), { _raw: argv.slice(1) }));
228067
+ await run3(Object.assign(parseArgs(argv.slice(1)), { _raw: argv.slice(1) }));
227580
228068
  };
227581
228069
  main().catch((e2) => {
227582
228070
  if (e2 instanceof CliError)