lecodes-cli 0.19.2 → 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -3177,7 +3177,7 @@ var require_source_map_support = __commonJS((exports, module) => {
3177
3177
 
3178
3178
  // ../../node_modules/.bun/typescript@5.8.3/node_modules/typescript/lib/typescript.js
3179
3179
  var require_typescript = __commonJS((exports, module) => {
3180
- 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";
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";
3181
3181
  /*! *****************************************************************************
3182
3182
  Copyright (c) Microsoft Corporation. All rights reserved.
3183
3183
  Licensed under the Apache License, Version 2.0 (the "License"); you may not use
@@ -172193,6 +172193,8 @@ var compileProject = async (opts) => {
172193
172193
  }
172194
172194
  } else if (e.path.toLowerCase().endsWith(".svg")) {
172195
172195
  files[e.path] = `export default SvgSource(\`${e.text ?? ""}\`)`;
172196
+ } else if (e.path.toLowerCase().endsWith(".json")) {
172197
+ files[e.path] = `export default ${e.text?.trim() || "null"}`;
172196
172198
  } else {
172197
172199
  files[e.path] = e.text ?? "";
172198
172200
  }
@@ -214125,26 +214127,26 @@ var init_esm30 = __esm(() => {
214125
214127
 
214126
214128
  // src/types.ts
214127
214129
  import { dirname as dirname13, join as join22, relative as relative3, sep } from "node:path";
214128
- import { existsSync as existsSync21, mkdirSync as mkdirSync15, readdirSync as readdirSync10, readFileSync as readFileSync18, rmSync as rmSync8, statSync as statSync8, writeFileSync as writeFileSync17 } from "node:fs";
214130
+ import { existsSync as existsSync21, mkdirSync as mkdirSync15, readdirSync as readdirSync11, readFileSync as readFileSync18, rmSync as rmSync8, statSync as statSync9, writeFileSync as writeFileSync17 } from "node:fs";
214129
214131
  import { createRequire as createRequire4 } from "node:module";
214130
214132
  var TSCONFIG, listBundleFiles = (dir, base = dir) => {
214131
214133
  if (!existsSync21(dir))
214132
214134
  return [];
214133
214135
  const out = [];
214134
- for (const name of readdirSync10(dir)) {
214136
+ for (const name of readdirSync11(dir)) {
214135
214137
  const path2 = join22(dir, name);
214136
- if (statSync8(path2).isDirectory())
214138
+ if (statSync9(path2).isDirectory())
214137
214139
  out.push(...listBundleFiles(path2, base));
214138
214140
  else
214139
214141
  out.push(relative3(base, path2).split(sep).join("/"));
214140
214142
  }
214141
214143
  return out;
214142
214144
  }, pruneEmptyDirs = (dir, root) => {
214143
- if (!existsSync21(dir) || !statSync8(dir).isDirectory())
214145
+ if (!existsSync21(dir) || !statSync9(dir).isDirectory())
214144
214146
  return;
214145
- for (const name of readdirSync10(dir))
214147
+ for (const name of readdirSync11(dir))
214146
214148
  pruneEmptyDirs(join22(dir, name), root);
214147
- if (dir !== root && readdirSync10(dir).length === 0)
214149
+ if (dir !== root && readdirSync11(dir).length === 0)
214148
214150
  rmSync8(dir, { recursive: true });
214149
214151
  }, writeTypeBundle = (root, bundle) => {
214150
214152
  const typesDir = join22(root, LECODES_DIR, "types");
@@ -214230,13 +214232,13 @@ var init_types4 = __esm(() => {
214230
214232
 
214231
214233
  // src/compile/designCompile.ts
214232
214234
  import { readFileSync as readFileSync19 } from "node:fs";
214233
- import { basename as basename9 } from "node:path";
214235
+ import { basename as basename10 } from "node:path";
214234
214236
  var LOCAL_ASSET_SCHEME = "lecodesfile://", compileDesignScreen = async (opts) => {
214235
214237
  const { root, manifest, designDir, designKey, screenId, state } = opts;
214236
214238
  const entries = [];
214237
214239
  const scan2 = scanLocal(root, { ignore: loadIgnore(root), tracked: new Set(Object.keys(manifest?.files ?? {})) });
214238
214240
  for (const file of scan2.files) {
214239
- const type = manifest?.files[file.path]?.type ?? classifyKind(basename9(file.path));
214241
+ const type = manifest?.files[file.path]?.type ?? classifyKind(basename10(file.path));
214240
214242
  if (type === "resource") {
214241
214243
  entries.push({
214242
214244
  path: file.path,
@@ -214872,8 +214874,8 @@ __export(exports_design, {
214872
214874
  flushDesignComments: () => flushDesignComments,
214873
214875
  design: () => design
214874
214876
  });
214875
- import { existsSync as existsSync24, mkdirSync as mkdirSync18, readFileSync as readFileSync21, readdirSync as readdirSync13, writeFileSync as writeFileSync19, appendFileSync } from "node:fs";
214876
- import { basename as basename10, join as join25 } from "node:path";
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";
214877
214879
  var DEFAULT_DIR = "design", DEFAULT_PORT = 4477, getContext = (args) => {
214878
214880
  let root;
214879
214881
  let manifest;
@@ -214888,7 +214890,7 @@ var DEFAULT_DIR = "design", DEFAULT_PORT = 4477, getContext = (args) => {
214888
214890
  return {
214889
214891
  root,
214890
214892
  manifest,
214891
- name: manifest?.name ?? basename10(root),
214893
+ name: manifest?.name ?? basename11(root),
214892
214894
  dirName,
214893
214895
  designDir: join25(root, dirName),
214894
214896
  designKey: "/" + dirName.replace(/\\/g, "/")
@@ -214929,7 +214931,7 @@ var DEFAULT_DIR = "design", DEFAULT_PORT = 4477, getContext = (args) => {
214929
214931
  const dir = join25(designDir, "screens");
214930
214932
  if (!existsSync24(dir))
214931
214933
  return [];
214932
- return readdirSync13(dir).filter((f3) => f3.endsWith(".ts") && !f3.startsWith(".")).map((f3) => f3.slice(0, -3)).sort();
214934
+ return readdirSync14(dir).filter((f3) => f3.endsWith(".ts") && !f3.startsWith(".")).map((f3) => f3.slice(0, -3)).sort();
214933
214935
  }, writeMcpConfig = (ctx, port) => {
214934
214936
  const path2 = join25(ctx.root, ".mcp.json");
214935
214937
  const entry = { type: "http", url: `http://localhost:${port}/mcp` };
@@ -216539,7 +216541,7 @@ var require_main = __commonJS((exports, module) => {
216539
216541
  // package.json
216540
216542
  var package_default = {
216541
216543
  name: "lecodes-cli",
216542
- version: "0.19.2",
216544
+ version: "0.20.0",
216543
216545
  dependencies: {
216544
216546
  "@letary/chisel": "^0.10.0",
216545
216547
  jimp: "^1.6.1",
@@ -216556,7 +216558,7 @@ var package_default = {
216556
216558
  },
216557
216559
  peerDependencies: {
216558
216560
  "lecodes-design": "^0.9.0",
216559
- "lecodes-renderer": "^0.19.2",
216561
+ "lecodes-renderer": "^0.20.0",
216560
216562
  "lecodes-3d-editor": "^0.7.0",
216561
216563
  "lecodes-assets": "^0.2.1"
216562
216564
  },
@@ -216609,7 +216611,7 @@ var package_default = {
216609
216611
  init_manifest();
216610
216612
  init_config();
216611
216613
  import { execFileSync as execFileSync6 } from "node:child_process";
216612
- import { cpSync as cpSync4, existsSync as existsSync20, mkdirSync as mkdirSync14, readFileSync as readFileSync17, readdirSync as readdirSync9, rmSync as rmSync7, statSync as statSync7, writeFileSync as writeFileSync16 } from "node:fs";
216614
+ import { cpSync as cpSync4, existsSync as existsSync20, mkdirSync as mkdirSync14, readFileSync as readFileSync17, readdirSync as readdirSync10, rmSync as rmSync7, statSync as statSync8, writeFileSync as writeFileSync16 } from "node:fs";
216613
216615
  import { homedir as homedir7 } from "node:os";
216614
216616
  import { basename as basename8, dirname as dirname12, join as join21, resolve as resolve8 } from "node:path";
216615
216617
 
@@ -217036,12 +217038,13 @@ init_manifest();
217036
217038
  init_ignore();
217037
217039
  init_util();
217038
217040
  import { execFileSync } from "node:child_process";
217039
- import { existsSync as existsSync13, mkdirSync as mkdirSync8, readFileSync as readFileSync13, writeFileSync as writeFileSync9 } from "node:fs";
217041
+ import { existsSync as existsSync13, mkdirSync as mkdirSync8, readdirSync as readdirSync5, readFileSync as readFileSync13, statSync as statSync3, writeFileSync as writeFileSync9 } from "node:fs";
217040
217042
  import { homedir as homedir4 } from "node:os";
217041
217043
  import { dirname as dirname8, join as join15, resolve as resolve2 } from "node:path";
217042
217044
  var FIRST_PARTY_PLUGINS = {
217043
217045
  camera: "letary/lecodes-plugins",
217044
217046
  "qr-scanner": "letary/lecodes-plugins",
217047
+ map: "letary/lecodes-plugins",
217045
217048
  geolocation: "letary/lecodes-plugins",
217046
217049
  push: "letary/lecodes-plugins"
217047
217050
  };
@@ -217072,6 +217075,7 @@ var desktopDefaultViewport = (root) => {
217072
217075
  height: cfg.desktop.window?.height ?? DESKTOP_DEFAULT_WINDOW.height
217073
217076
  };
217074
217077
  };
217078
+ var swiftPackageName = (url) => (url.replace(/\/+$/, "").split("/").pop() ?? url).replace(/\.git$/, "");
217075
217079
  var readJson = (path) => {
217076
217080
  if (!existsSync13(path))
217077
217081
  return null;
@@ -217225,7 +217229,8 @@ var ensureRepo = (repo, wantTag, lock) => {
217225
217229
  git(cache, "checkout", "--quiet", "--detach", commit);
217226
217230
  return { dir: cache, tag: tag ?? "unknown", commit };
217227
217231
  };
217228
- var resolvePlugin = (id, spec, lock, platform) => {
217232
+ var PLUGIN_PLATFORM_LABEL = { ios: "iOS", android: "Android", desktop: "desktop" };
217233
+ function resolvePlugin(id, spec, lock, platform, opts) {
217229
217234
  let repo;
217230
217235
  let subdir;
217231
217236
  let wantTag = null;
@@ -217249,7 +217254,9 @@ var resolvePlugin = (id, spec, lock, platform) => {
217249
217254
  if (manifest.id !== id)
217250
217255
  throw new CliError(`Plugin "${id}": manifest declares id "${manifest.id}"`);
217251
217256
  if (!manifest[platform]) {
217252
- throw new CliError(`Plugin "${id}" does not support ${platform === "ios" ? "iOS" : "Android"} (manifest "${platform}" is null)`);
217257
+ if (opts?.optional)
217258
+ return null;
217259
+ throw new CliError(`Plugin "${id}" does not support ${PLUGIN_PLATFORM_LABEL[platform]} (manifest "${platform}" is null)`);
217253
217260
  }
217254
217261
  if (spec.startsWith("github:")) {
217255
217262
  const names = [...manifest.provides?.views ?? [], ...manifest.provides?.services ?? []];
@@ -217264,7 +217271,7 @@ var resolvePlugin = (id, spec, lock, platform) => {
217264
217271
  manifest,
217265
217272
  lock: { repo, tag: checkout.tag, commit: checkout.commit, version: manifest.version ?? "unknown" }
217266
217273
  };
217267
- };
217274
+ }
217268
217275
  var latestPluginTag = (repo) => {
217269
217276
  try {
217270
217277
  return ensureRepo(repo, null, undefined).tag;
@@ -217272,6 +217279,55 @@ var latestPluginTag = (repo) => {
217272
217279
  return null;
217273
217280
  }
217274
217281
  };
217282
+ var desktopPluginTarget = () => `${process.platform}-${process.arch}`;
217283
+ var isDesktopPluginLibrary = (name) => {
217284
+ const n = name.toLowerCase();
217285
+ return process.platform === "win32" ? n.endsWith(".dll") : n.endsWith(".so");
217286
+ };
217287
+ var desktopPluginLibraries = (root, opts = {}) => {
217288
+ const dirs = [];
217289
+ const files = [];
217290
+ const local = join15(root, "plugins");
217291
+ if (existsSync13(local) && statSync3(local).isDirectory()) {
217292
+ dirs.push(local);
217293
+ for (const f2 of readdirSync5(local))
217294
+ if (isDesktopPluginLibrary(f2))
217295
+ files.push(join15(local, f2));
217296
+ }
217297
+ const config = readJson(join15(root, "app.json"));
217298
+ const specs = Object.entries(config?.plugins ?? {});
217299
+ if (specs.length === 0)
217300
+ return { dirs, files };
217301
+ const lock = readJson(join15(root, "plugins.lock.json"))?.plugins ?? {};
217302
+ const target = desktopPluginTarget();
217303
+ for (const [id, spec] of specs) {
217304
+ let plugin;
217305
+ try {
217306
+ plugin = resolvePlugin(id, spec, lock[id], "desktop", { optional: true });
217307
+ } catch (e) {
217308
+ if (opts.strict)
217309
+ throw e;
217310
+ warn(`Plugin "${id}": ${e.message} — skipped for this desktop run`);
217311
+ continue;
217312
+ }
217313
+ const rel = plugin?.manifest.desktop?.library?.[target];
217314
+ if (!plugin || !rel)
217315
+ continue;
217316
+ const abs = resolve2(plugin.dir, rel);
217317
+ if (!existsSync13(abs)) {
217318
+ const msg = `Plugin "${id}": desktop library ${abs} is missing (manifest desktop.library["${target}"])`;
217319
+ if (opts.strict)
217320
+ throw new CliError(msg);
217321
+ warn(`${msg} — skipped for this desktop run`);
217322
+ continue;
217323
+ }
217324
+ files.push(abs);
217325
+ const dir = dirname8(abs);
217326
+ if (!dirs.includes(dir))
217327
+ dirs.push(dir);
217328
+ }
217329
+ return { dirs, files };
217330
+ };
217275
217331
 
217276
217332
  // src/commands/compile.ts
217277
217333
  init_util();
@@ -218076,13 +218132,31 @@ var APP_ICON_JSON = `{
218076
218132
  `;
218077
218133
  var runtimePackageTemplate = (sdkVersion, variant, plugins) => {
218078
218134
  const product = `LeCodesSDK-${variant}`;
218079
- const pluginTargets = plugins.map((p) => `
218135
+ const packages = new Map;
218136
+ for (const p of plugins) {
218137
+ for (const pkg of p.packages ?? []) {
218138
+ const prior = packages.get(pkg.url);
218139
+ if (prior && (prior.exact !== pkg.exact || prior.from !== pkg.from)) {
218140
+ throw new Error(`Plugin "${p.id}" pins ${pkg.url} at ${pkg.exact ?? `from ${pkg.from}`} but another plugin pins ${prior.exact ?? `from ${prior.from}`} — the shell can link one version.`);
218141
+ }
218142
+ packages.set(pkg.url, pkg);
218143
+ }
218144
+ }
218145
+ const packageDecls = [...packages.values()].map((pkg) => `
218146
+ .package(url: "${pkg.url}", ${pkg.exact ? `exact: "${pkg.exact}"` : `from: "${pkg.from}"`}),`).join("");
218147
+ const pluginTargets = plugins.map((p) => {
218148
+ const deps = [
218149
+ `.product(name: "${product}", package: "lecodes-ios-sdk")`,
218150
+ ...(p.packages ?? []).flatMap((pkg) => pkg.products.map((name) => `.product(name: "${name}", package: "${swiftPackageName(pkg.url)}")`))
218151
+ ];
218152
+ return `
218080
218153
  // plugin: ${p.id} (vendored — see plugins.lock.json)
218081
218154
  .target(
218082
218155
  name: "${p.module}",
218083
- dependencies: [.product(name: "${product}", package: "lecodes-ios-sdk")],
218156
+ dependencies: [${deps.join(", ")}],
218084
218157
  path: "Sources/${p.module}"
218085
- ),`).join("");
218158
+ ),`;
218159
+ }).join("");
218086
218160
  const runtimeDeps = [`.product(name: "${product}", package: "lecodes-ios-sdk")`, ...plugins.map((p) => `"${p.module}"`)];
218087
218161
  return `// swift-tools-version:5.9
218088
218162
  //
@@ -218101,7 +218175,7 @@ let package = Package(
218101
218175
  .library(name: "LeCodesRuntime", targets: ["LeCodesRuntime"]),
218102
218176
  ],
218103
218177
  dependencies: [
218104
- .package(url: "https://github.com/letary/lecodes-ios-sdk.git", exact: "${sdkVersion}"),
218178
+ .package(url: "https://github.com/letary/lecodes-ios-sdk.git", exact: "${sdkVersion}"),${packageDecls}
218105
218179
  ],
218106
218180
  targets: [
218107
218181
  .target(
@@ -218247,7 +218321,7 @@ buildNumber) — edit it there, then re-run \`lecodes app sync\`. Plugins are ma
218247
218321
  init_manifest();
218248
218322
  import { execFileSync as execFileSync2, spawn, spawnSync as spawnSync3 } from "node:child_process";
218249
218323
  import { createHash as createHash6 } from "node:crypto";
218250
- import { chmodSync, cpSync, existsSync as existsSync16, mkdirSync as mkdirSync10, readFileSync as readFileSync15, readdirSync as readdirSync5, renameSync as renameSync2, rmSync as rmSync3, statSync as statSync3, writeFileSync as writeFileSync12 } from "node:fs";
218324
+ import { chmodSync, cpSync, existsSync as existsSync16, mkdirSync as mkdirSync10, readFileSync as readFileSync15, readdirSync as readdirSync6, renameSync as renameSync2, rmSync as rmSync3, statSync as statSync4, writeFileSync as writeFileSync12 } from "node:fs";
218251
218325
  import { homedir as homedir5 } from "node:os";
218252
218326
  import { basename as basename5, dirname as dirname10, join as join17 } from "node:path";
218253
218327
  init_util();
@@ -219494,7 +219568,7 @@ var syncAndroid = async (root, config, prior) => {
219494
219568
  }
219495
219569
  const namespace = register.split(".").slice(0, -1).join(".");
219496
219570
  const libsDir = join17(dest, "libs");
219497
- const aarNames = existsSync16(libsDir) ? readdirSync5(libsDir).filter((f3) => f3.endsWith(".aar")) : [];
219571
+ const aarNames = existsSync16(libsDir) ? readdirSync6(libsDir).filter((f3) => f3.endsWith(".aar")) : [];
219498
219572
  for (const aar of aarNames) {
219499
219573
  const base = aar.replace(/\.aar$/, "");
219500
219574
  const wrapDir = join17(libsDir, base);
@@ -219510,8 +219584,8 @@ var syncAndroid = async (root, config, prior) => {
219510
219584
  log(`${c.green("plugin")} ${id}@${plugin.lock.version} (${plugin.lock.tag} → plugins/${module})`);
219511
219585
  }
219512
219586
  const keep = new Set(runtimePlugins.map((p2) => p2.module));
219513
- for (const entry of existsSync16(pluginsDir) ? readdirSync5(pluginsDir) : []) {
219514
- if (!keep.has(entry) && statSync3(join17(pluginsDir, entry)).isDirectory()) {
219587
+ for (const entry of existsSync16(pluginsDir) ? readdirSync6(pluginsDir) : []) {
219588
+ if (!keep.has(entry) && statSync4(join17(pluginsDir, entry)).isDirectory()) {
219515
219589
  rmrf(join17(pluginsDir, entry));
219516
219590
  log(`${c.yellow("removed")} android/plugins/${entry} (plugin uninstalled)`);
219517
219591
  }
@@ -219676,7 +219750,7 @@ var buildAndroid = async (args) => {
219676
219750
  for (const dir of [apkDir, aabDir]) {
219677
219751
  if (!existsSync16(dir))
219678
219752
  continue;
219679
- for (const f3 of readdirSync5(dir)) {
219753
+ for (const f3 of readdirSync6(dir)) {
219680
219754
  if (f3.endsWith(".apk") || f3.endsWith(".aab"))
219681
219755
  outputs.push(join17(dir, f3));
219682
219756
  }
@@ -219733,7 +219807,7 @@ var doctorAndroidChecks = async (root, config, check) => {
219733
219807
  const validVariant = requestedVariant === "auto" || SDK_VARIANTS.includes(requestedVariant);
219734
219808
  check(validVariant, `android.variant "${requestedVariant}"`, `use "auto" or one of ${SDK_VARIANTS.join(", ")}`);
219735
219809
  const wrapperJar = join17(androidDir, "gradle", "wrapper", "gradle-wrapper.jar");
219736
- check(existsSync16(wrapperJar) && statSync3(wrapperJar).size > 0, "Gradle wrapper jar", `run \`gradle wrapper --gradle-version ${GRADLE_VERSION}\` in android/ (or let Android Studio repair it)`);
219810
+ check(existsSync16(wrapperJar) && statSync4(wrapperJar).size > 0, "Gradle wrapper jar", `run \`gradle wrapper --gradle-version ${GRADLE_VERSION}\` in android/ (or let Android Studio repair it)`);
219737
219811
  check(existsSync16(join17(androidDir, "app", "src", "main", "assets", "app.js")), "Bundled app.js", "run `lecodes app sync`");
219738
219812
  const linked = shellSdkVersionAndroid(androidDir);
219739
219813
  const pin = config.android?.sdk ?? "latest";
@@ -219756,14 +219830,14 @@ var doctorAndroidChecks = async (root, config, check) => {
219756
219830
 
219757
219831
  // src/commands/appDesktop.ts
219758
219832
  import { execFileSync as execFileSync5, spawn as spawn4 } from "node:child_process";
219759
- import { cpSync as cpSync3, existsSync as existsSync19, mkdirSync as mkdirSync13, readdirSync as readdirSync8, rmSync as rmSync6, statSync as statSync6, writeFileSync as writeFileSync15 } from "node:fs";
219833
+ import { cpSync as cpSync3, existsSync as existsSync19, mkdirSync as mkdirSync13, readdirSync as readdirSync9, rmSync as rmSync6, statSync as statSync7, writeFileSync as writeFileSync15 } from "node:fs";
219760
219834
  import { basename as basename7, dirname as dirname11, join as join20, resolve as resolve7 } from "node:path";
219761
219835
 
219762
219836
  // src/desktopRenderer.ts
219763
219837
  import { execFileSync as execFileSync3, spawn as spawn2, spawnSync as spawnSync4 } from "node:child_process";
219764
- import { copyFileSync, existsSync as existsSync17, mkdirSync as mkdirSync11, mkdtempSync as mkdtempSync2, readdirSync as readdirSync6, readFileSync as readFileSync16, rmSync as rmSync4, statSync as statSync4, writeFileSync as writeFileSync13 } from "node:fs";
219838
+ import { copyFileSync, existsSync as existsSync17, mkdirSync as mkdirSync11, mkdtempSync as mkdtempSync2, readdirSync as readdirSync7, readFileSync as readFileSync16, rmSync as rmSync4, statSync as statSync5, writeFileSync as writeFileSync13 } from "node:fs";
219765
219839
  import { homedir as homedir6, tmpdir as tmpdir2 } from "node:os";
219766
- import { join as join18, resolve as resolve5 } from "node:path";
219840
+ import { delimiter, join as join18, resolve as resolve5 } from "node:path";
219767
219841
  init_util();
219768
219842
 
219769
219843
  // ../viewer-lite/src/sourcemapError.ts
@@ -219883,7 +219957,7 @@ var isMac = () => process.platform === "darwin";
219883
219957
  var exeName = () => process.platform === "win32" ? "lecodes-desktop.exe" : isMac() ? "lecodes-app" : "lecodes-desktop";
219884
219958
  var findExe = (dir) => {
219885
219959
  const name = exeName();
219886
- for (const entry of readdirSync6(dir, { withFileTypes: true })) {
219960
+ for (const entry of readdirSync7(dir, { withFileTypes: true })) {
219887
219961
  const p2 = join18(dir, entry.name);
219888
219962
  if (entry.isFile() && entry.name === name)
219889
219963
  return p2;
@@ -219899,7 +219973,7 @@ var cacheRoot2 = () => join18(homedir6(), ".lecodes", "renderer");
219899
219973
  var currentFile = () => join18(cacheRoot2(), "current.json");
219900
219974
  var installedDesktopVersions = () => {
219901
219975
  try {
219902
- return readdirSync6(cacheRoot2(), { withFileTypes: true }).filter((e2) => e2.isDirectory() && e2.name.startsWith("desktop-")).map((e2) => e2.name.slice("desktop-".length)).filter((v) => findExe(join18(cacheRoot2(), `desktop-${v}`)) !== null).sort(semverNewer);
219976
+ return readdirSync7(cacheRoot2(), { withFileTypes: true }).filter((e2) => e2.isDirectory() && e2.name.startsWith("desktop-")).map((e2) => e2.name.slice("desktop-".length)).filter((v) => findExe(join18(cacheRoot2(), `desktop-${v}`)) !== null).sort(semverNewer);
219903
219977
  } catch {
219904
219978
  return [];
219905
219979
  }
@@ -219924,7 +219998,7 @@ var extractDmg = (archive, stage) => {
219924
219998
  mkdirSync11(mount, { recursive: true });
219925
219999
  execFileSync3("hdiutil", ["attach", "-nobrowse", "-readonly", "-quiet", "-mountpoint", mount, archive]);
219926
220000
  try {
219927
- const app = readdirSync6(mount).find((n2) => n2.endsWith(".app"));
220001
+ const app = readdirSync7(mount).find((n2) => n2.endsWith(".app"));
219928
220002
  if (!app)
219929
220003
  throw new CliError("The downloaded DMG holds no .app bundle.");
219930
220004
  execFileSync3("cp", ["-R", join18(mount, app), join18(stage, app)]);
@@ -220045,8 +220119,12 @@ var updateDesktop = async () => {
220045
220119
  else
220046
220120
  note(`Desktop renderer set to ${latest} (was ${before}).`);
220047
220121
  };
220122
+ var pluginEnv = (dirs) => {
220123
+ const all = [...dirs ?? [], ...process.env.CREATOR_PLUGIN_DIRS ? [process.env.CREATOR_PLUGIN_DIRS] : []];
220124
+ return all.length > 0 ? { CREATOR_PLUGIN_DIRS: all.join(delimiter) } : {};
220125
+ };
220048
220126
  var windowEnv = (opts) => {
220049
- const env = {};
220127
+ const env = { ...pluginEnv(opts.pluginDirs) };
220050
220128
  const width = opts.width ?? (opts.height !== undefined ? 1280 : undefined);
220051
220129
  const height = opts.height ?? (opts.width !== undefined ? 720 : undefined);
220052
220130
  if (width && height)
@@ -220090,7 +220168,7 @@ var stageBundle = (js, resources) => {
220090
220168
  const runDir = mkdtempSync2(join18(tmpdir2(), "lecodes-render-"));
220091
220169
  writeFileSync13(join18(runDir, "app.js"), js);
220092
220170
  for (const r2 of resources) {
220093
- if (r2.absPath && existsSync17(r2.absPath) && statSync4(r2.absPath).isFile()) {
220171
+ if (r2.absPath && existsSync17(r2.absPath) && statSync5(r2.absPath).isFile()) {
220094
220172
  copyFileSync(r2.absPath, join18(runDir, r2.name));
220095
220173
  }
220096
220174
  }
@@ -220182,6 +220260,7 @@ var runDesktopRender = async (js, resources, opts) => {
220182
220260
  env.CREATOR_CAMERA = opts.camera;
220183
220261
  if (opts.view)
220184
220262
  env.CREATOR_VIEW = opts.view;
220263
+ Object.assign(env, pluginEnv(opts.pluginDirs));
220185
220264
  const demangle = makeStackDemangler(js) ?? ((text) => text);
220186
220265
  return await new Promise((done, fail) => {
220187
220266
  const child = spawn2(exe, [appJs], { cwd: runDir, env, stdio: ["ignore", "pipe", "pipe"] });
@@ -220240,13 +220319,13 @@ init_util();
220240
220319
  // src/commands/appDesktopMac.ts
220241
220320
  init_util();
220242
220321
  import { execFileSync as execFileSync4, spawn as spawn3, spawnSync as spawnSync5 } from "node:child_process";
220243
- import { cpSync as cpSync2, existsSync as existsSync18, mkdirSync as mkdirSync12, mkdtempSync as mkdtempSync3, readdirSync as readdirSync7, rmSync as rmSync5, statSync as statSync5, symlinkSync, writeFileSync as writeFileSync14 } from "node:fs";
220322
+ import { cpSync as cpSync2, existsSync as existsSync18, mkdirSync as mkdirSync12, mkdtempSync as mkdtempSync3, readdirSync as readdirSync8, rmSync as rmSync5, statSync as statSync6, symlinkSync, writeFileSync as writeFileSync14 } from "node:fs";
220244
220323
  import { tmpdir as tmpdir3 } from "node:os";
220245
220324
  import { basename as basename6, join as join19, resolve as resolve6 } from "node:path";
220246
220325
  var mb = (bytes) => `${(bytes / 1024 / 1024).toFixed(1)} MB`;
220247
- var dirSize = (dir) => readdirSync7(dir, { withFileTypes: true }).reduce((sum, e2) => {
220326
+ var dirSize = (dir) => readdirSync8(dir, { withFileTypes: true }).reduce((sum, e2) => {
220248
220327
  const p2 = join19(dir, e2.name);
220249
- return sum + (e2.isDirectory() ? dirSize(p2) : e2.isSymbolicLink() ? 0 : statSync5(p2).size);
220328
+ return sum + (e2.isDirectory() ? dirSize(p2) : e2.isSymbolicLink() ? 0 : statSync6(p2).size);
220250
220329
  }, 0);
220251
220330
  var cleanBundleName = (name) => name.replace(/[/:]/g, "").trim() || "App";
220252
220331
  var macHostAppOf = (exe) => {
@@ -220259,7 +220338,7 @@ var macHostAppOf = (exe) => {
220259
220338
  var macHostProvidedNames = (exe) => {
220260
220339
  const names3 = new Set;
220261
220340
  const walk = (dir) => {
220262
- for (const e2 of readdirSync7(dir, { withFileTypes: true })) {
220341
+ for (const e2 of readdirSync8(dir, { withFileTypes: true })) {
220263
220342
  if (e2.isDirectory())
220264
220343
  walk(join19(dir, e2.name));
220265
220344
  else
@@ -220413,7 +220492,7 @@ ${(r2.stdout + r2.stderr).trim()}`);
220413
220492
  if (signing.notaryProfile)
220414
220493
  notarize(dmg, signing.notaryProfile, tmp, "disk image");
220415
220494
  }
220416
- success(`DMG: ${dmg} (${mb(statSync5(dmg).size)})`);
220495
+ success(`DMG: ${dmg} (${mb(statSync6(dmg).size)})`);
220417
220496
  };
220418
220497
  var gatekeeperVerdict = (app) => {
220419
220498
  const r2 = spawnSync5("spctl", ["-a", "-t", "exec", "-vv", app], { encoding: "utf8" });
@@ -220514,12 +220593,12 @@ var buildMacApp = async (input) => {
220514
220593
  writeFileSync14(join19(proj, "app.js"), js);
220515
220594
  let assetBytes = 0, assetCount = 0;
220516
220595
  for (const r2 of shipped) {
220517
- if (!r2.absPath || !existsSync18(r2.absPath) || !statSync5(r2.absPath).isFile()) {
220596
+ if (!r2.absPath || !existsSync18(r2.absPath) || !statSync6(r2.absPath).isFile()) {
220518
220597
  warn(`asset "${r2.name}" (${r2.path}) has no local file — skipped`);
220519
220598
  continue;
220520
220599
  }
220521
220600
  cpSync2(r2.absPath, join19(proj, "assets", r2.name));
220522
- assetBytes += statSync5(r2.absPath).size;
220601
+ assetBytes += statSync6(r2.absPath).size;
220523
220602
  assetCount++;
220524
220603
  }
220525
220604
  note(` assets: ${assetCount} files, ${mb(assetBytes)} → Contents/Resources/project/assets/`);
@@ -220567,7 +220646,7 @@ var buildMacApp = async (input) => {
220567
220646
  const zip = join19(out, `${appName}.zip`);
220568
220647
  rmSync5(zip, { force: true });
220569
220648
  execFileSync4("ditto", ["-c", "-k", "--keepParent", app, zip]);
220570
- success(`Zip: ${zip} (${mb(statSync5(zip).size)})`);
220649
+ success(`Zip: ${zip} (${mb(statSync6(zip).size)})`);
220571
220650
  } else if (!flagBool(args, "dmg")) {
220572
220651
  note("Tip: --zip packs the .app for sending; --dmg wraps it in a drag-to-Applications image.");
220573
220652
  }
@@ -220591,9 +220670,9 @@ or remove the quarantine flag in Terminal: xattr -dr com.apple.quarantine "` +
220591
220670
 
220592
220671
  // src/commands/appDesktop.ts
220593
220672
  var mb2 = (bytes) => `${(bytes / 1024 / 1024).toFixed(1)} MB`;
220594
- var dirSize2 = (dir) => readdirSync8(dir, { withFileTypes: true }).reduce((sum, e2) => {
220673
+ var dirSize2 = (dir) => readdirSync9(dir, { withFileTypes: true }).reduce((sum, e2) => {
220595
220674
  const p2 = join20(dir, e2.name);
220596
- return sum + (e2.isDirectory() ? dirSize2(p2) : statSync6(p2).size);
220675
+ return sum + (e2.isDirectory() ? dirSize2(p2) : statSync7(p2).size);
220597
220676
  }, 0);
220598
220677
  var initDesktop = async (args) => {
220599
220678
  const root = projectRoot();
@@ -220622,7 +220701,7 @@ var buildDesktop = async (args) => {
220622
220701
  const slug = flagStr(args, "slug") ?? cleanFileName(name);
220623
220702
  const out = resolve7(root, flagStr(args, "o", "out") ?? join20(root, "dist", slug));
220624
220703
  if (existsSync19(out)) {
220625
- const entries = readdirSync8(out);
220704
+ const entries = readdirSync9(out);
220626
220705
  const looksLikeBuild = entries.length === 0 || entries.includes("app.js") || entries.some((e2) => e2.endsWith(".app"));
220627
220706
  if (!looksLikeBuild) {
220628
220707
  throw new CliError(`${out} exists and doesn't look like a previous desktop build — pick another -o or clear it yourself.`);
@@ -220659,7 +220738,7 @@ var buildDesktop = async (args) => {
220659
220738
  const hostProvided = mac ? macHostProvidedNames(exe) : new Set;
220660
220739
  if (!mac) {
220661
220740
  try {
220662
- for (const f3 of readdirSync8(join20(hostDir, "assets")))
220741
+ for (const f3 of readdirSync9(join20(hostDir, "assets")))
220663
220742
  hostProvided.add(f3);
220664
220743
  } catch {}
220665
220744
  }
@@ -220675,7 +220754,7 @@ var buildDesktop = async (args) => {
220675
220754
  const version = process.env.LECODES_DESKTOP_EXE ? "local build" : selectedDesktopVersion();
220676
220755
  rmSync6(out, { recursive: true, force: true });
220677
220756
  mkdirSync13(out, { recursive: true });
220678
- for (const entry of readdirSync8(hostDir, { withFileTypes: true })) {
220757
+ for (const entry of readdirSync9(hostDir, { withFileTypes: true })) {
220679
220758
  if (entry.isFile() && entry.name.toLowerCase().endsWith(".dll")) {
220680
220759
  cpSync3(join20(hostDir, entry.name), join20(out, entry.name));
220681
220760
  } else if (entry.isDirectory() && entry.name === "assets") {
@@ -220695,12 +220774,12 @@ var buildDesktop = async (args) => {
220695
220774
  if (shipped.length > 0)
220696
220775
  mkdirSync13(join20(out, ASSETS_DIR), { recursive: true });
220697
220776
  for (const r2 of shipped) {
220698
- if (!r2.absPath || !existsSync19(r2.absPath) || !statSync6(r2.absPath).isFile()) {
220777
+ if (!r2.absPath || !existsSync19(r2.absPath) || !statSync7(r2.absPath).isFile()) {
220699
220778
  warn(`asset "${r2.name}" (${r2.path}) has no local file — skipped`);
220700
220779
  continue;
220701
220780
  }
220702
220781
  cpSync3(r2.absPath, join20(out, ASSETS_DIR, r2.name));
220703
- assetBytes += statSync6(r2.absPath).size;
220782
+ assetBytes += statSync7(r2.absPath).size;
220704
220783
  assetCount++;
220705
220784
  }
220706
220785
  note(` assets: ${assetCount} files, ${mb2(assetBytes)} → ${ASSETS_DIR}/`);
@@ -220709,6 +220788,15 @@ var buildDesktop = async (args) => {
220709
220788
  await writeIco(root, config, join20(out, "app.ico"));
220710
220789
  note(" app.ico rendered from " + config.icon);
220711
220790
  }
220791
+ {
220792
+ const { files } = desktopPluginLibraries(root, { strict: true });
220793
+ if (files.length > 0) {
220794
+ mkdirSync13(join20(out, "plugins"), { recursive: true });
220795
+ for (const f3 of files)
220796
+ cpSync3(f3, join20(out, "plugins", basename7(f3)));
220797
+ note(` plugins: ${files.map((f3) => basename7(f3)).join(", ")} → plugins/`);
220798
+ }
220799
+ }
220712
220800
  writeFileSync15(join20(out, "README.txt"), "\uFEFF" + readmeText2(name, exeName2, version));
220713
220801
  success(`Portable build: ${out} (${mb2(dirSize2(out))})`);
220714
220802
  if (flagBool(args, "probe"))
@@ -220808,7 +220896,7 @@ var zipBuild = (out) => {
220808
220896
  } else {
220809
220897
  execFileSync5("zip", ["-rq", zip, basename7(out)], { cwd: dirname11(out) });
220810
220898
  }
220811
- success(`Zip: ${zip} (${mb2(statSync6(zip).size)})`);
220899
+ success(`Zip: ${zip} (${mb2(statSync7(zip).size)})`);
220812
220900
  };
220813
220901
  var readmeText2 = (name, exeName2, version) => `${name}
220814
220902
  ${"=".repeat(Math.max(name.length, 4))}
@@ -221015,13 +221103,18 @@ var syncIos = async (root, config) => {
221015
221103
  for (const [key, value] of Object.entries(ios.entitlements ?? {})) {
221016
221104
  (entitlementValues[key] ??= []).push(value);
221017
221105
  }
221018
- runtimePlugins.push({ id, module, register });
221106
+ for (const pkg of ios.packages ?? []) {
221107
+ if (!pkg.url || !Array.isArray(pkg.products) || pkg.products.length === 0 || !pkg.exact && !pkg.from || pkg.exact && pkg.from) {
221108
+ throw new CliError(`Plugin "${id}": manifest ios.packages entries need { url, exact | from, products: [...] } — got ${JSON.stringify(pkg)}`);
221109
+ }
221110
+ }
221111
+ runtimePlugins.push({ id, module, register, packages: ios.packages });
221019
221112
  newLock[id] = plugin.lock;
221020
221113
  log(`${c.green("plugin")} ${id}@${plugin.lock.version} (${plugin.lock.tag} → Sources/${module})`);
221021
221114
  }
221022
221115
  const keep = new Set(["LeCodesRuntime", ...runtimePlugins.map((p2) => p2.module)]);
221023
- for (const entry of existsSync20(sourcesDir) ? readdirSync9(sourcesDir) : []) {
221024
- if (!keep.has(entry) && statSync7(join21(sourcesDir, entry)).isDirectory()) {
221116
+ for (const entry of existsSync20(sourcesDir) ? readdirSync10(sourcesDir) : []) {
221117
+ if (!keep.has(entry) && statSync8(join21(sourcesDir, entry)).isDirectory()) {
221025
221118
  rmSync7(join21(sourcesDir, entry), { recursive: true });
221026
221119
  log(`${c.yellow("removed")} ios/LeCodesRuntime/Sources/${entry} (plugin uninstalled)`);
221027
221120
  }
@@ -221049,7 +221142,7 @@ var syncIos = async (root, config) => {
221049
221142
  if (variant === "3d" || variant === "full") {
221050
221143
  const sdkResources = await ensureSdkResourcesBundle(sdkVersion);
221051
221144
  cpSync4(sdkResources, join21(resourcesDir, "LeCodesSDKResources.bundle"), { recursive: true });
221052
- for (const f3 of readdirSync9(sdkResources))
221145
+ for (const f3 of readdirSync10(sdkResources))
221053
221146
  if (f3 !== "Info.plist")
221054
221147
  sdkProvided.add(f3);
221055
221148
  log(`${c.green("resources")} LeCodesSDKResources.bundle (filament) → ios/App/Resources/`);
@@ -221387,7 +221480,7 @@ var doctorIosChecks = async (root, config, check) => {
221387
221480
  check(xcodeVersion !== "", `Xcode CLI (${xcodeVersion || "not found"})`, "install Xcode + command line tools");
221388
221481
  check(!!config.ios.teamId, "Development team set", "lecodes app init wrote app.json — fill ios.teamId", false);
221389
221482
  const iconDir = join21(root, "ios", "App", "Assets.xcassets", "AppIcon.appiconset");
221390
- const hasIcon = existsSync20(iconDir) && readdirSync9(iconDir).some((f3) => f3.endsWith(".png"));
221483
+ const hasIcon = existsSync20(iconDir) && readdirSync10(iconDir).some((f3) => f3.endsWith(".png"));
221391
221484
  check(hasIcon, "App icon present", "drop a 1024×1024 PNG into ios/App/Assets.xcassets/AppIcon.appiconset/ (App Store requires it)", false);
221392
221485
  const bundle = join21(root, "ios", "App", "Resources", "app.js");
221393
221486
  check(existsSync20(bundle), "Bundled app.js", "run `lecodes app sync`");
@@ -221430,7 +221523,7 @@ var doctorIosChecks = async (root, config, check) => {
221430
221523
  const plist = readFileSync17(join21(root, "ios", "App", "Info.plist"), "utf8");
221431
221524
  check(plist.includes("<key>LeCodesUpdateURL</key>"), "OTA update URL in Info.plist", "run `lecodes app sync`", false);
221432
221525
  const appDir = join21(root, "ios", "App");
221433
- const callsUpdater = readdirSync9(appDir).some((f3) => f3.endsWith(".swift") && readFileSync17(join21(appDir, f3), "utf8").includes("LeCodesUpdater.activeBundle"));
221526
+ const callsUpdater = readdirSync10(appDir).some((f3) => f3.endsWith(".swift") && readFileSync17(join21(appDir, f3), "utf8").includes("LeCodesUpdater.activeBundle"));
221434
221527
  check(callsUpdater, "App code launches via LeCodesUpdater", `no App/*.swift calls LeCodesUpdater.activeBundle() — downloaded updates would never run; see "Over-the-air updates" in ios/README.md for the ViewController snippet`, false);
221435
221528
  }
221436
221529
  const asc = readJson(join21(homedir7(), ".lecodes", "asc.json"));
@@ -221583,7 +221676,7 @@ var app = async (args) => {
221583
221676
  init_api();
221584
221677
  init_config();
221585
221678
  init_manifest();
221586
- import { existsSync as existsSync22, mkdirSync as mkdirSync16, readdirSync as readdirSync11 } from "node:fs";
221679
+ import { existsSync as existsSync22, mkdirSync as mkdirSync16, readdirSync as readdirSync12 } from "node:fs";
221587
221680
  import { resolve as resolve9 } from "node:path";
221588
221681
  init_types4();
221589
221682
  init_ignore();
@@ -221606,7 +221699,7 @@ var clone3 = async (args) => {
221606
221699
  const project = await getProject(apiUrl, token, uuid);
221607
221700
  const dir = args._[1] ?? slug(project.name);
221608
221701
  const root = resolve9(dir);
221609
- if (existsSync22(root) && readdirSync11(root).length > 0) {
221702
+ if (existsSync22(root) && readdirSync12(root).length > 0) {
221610
221703
  throw new CliError(`Directory "${dir}" already exists and is not empty.`);
221611
221704
  }
221612
221705
  mkdirSync16(root, { recursive: true });
@@ -221632,7 +221725,7 @@ init_config();
221632
221725
  init_manifest();
221633
221726
  init_types4();
221634
221727
  init_ignore();
221635
- import { existsSync as existsSync23, mkdirSync as mkdirSync17, readdirSync as readdirSync12, writeFileSync as writeFileSync18 } from "node:fs";
221728
+ import { existsSync as existsSync23, mkdirSync as mkdirSync17, readdirSync as readdirSync13, writeFileSync as writeFileSync18 } from "node:fs";
221636
221729
  import { join as join23, resolve as resolve10 } from "node:path";
221637
221730
 
221638
221731
  // src/commands/projectTemplates.ts
@@ -221655,7 +221748,7 @@ lecodes render src/screens/cart.ts # ONE screen module, without booting the ap
221655
221748
  # --state <name> picks the branch). Same flags as above.
221656
221749
  lecodes render --desktop --png shot.png # NATIVE render: real Filament 3D + UI + 2D composited —
221657
221750
  # use this to SEE 3D scenes / procedural geometry (headless
221658
- # renders record 3D as JSON only). Windows/Linux; downloads
221751
+ # renders record 3D as JSON — simulated, not drawn). Windows/Linux; downloads
221659
221752
  # the renderer on first use. --frames <n> for slow loads.
221660
221753
  lecodes render --script <file> # drive the app through a scenario (format below)
221661
221754
  lecodes render --desktop --view iso --png iso.png # 3D: inspection camera fitted to the WHOLE
@@ -221704,8 +221797,9 @@ advance in fixed 1/60 steps driven by \`wait\`/\`frames\`/\`drag\`/\`key\` — n
221704
221797
  so a flow that passes once passes every time. \`Input.key(code)\` reads the keys a flow holds and
221705
221798
  \`Input.on('keydown' | 'keyup', e => e.code)\` fires for each \`key\`/\`keyDown\`/\`keyUp\` step
221706
221799
  (events land before the next loop tick); \`stick\` values are what \`Input.gamepad(0).axis()\` reads.
221707
- Headless draws UI + the 2D scene (sprites, tilemaps, Box2D); 3D is captured as JSON and Jolt does
221708
- not simulate — use \`--desktop\` for 3D pixels. \`console.log\` + \`expect.log\` is the assertion
221800
+ Headless draws UI + the 2D scene (sprites, tilemaps, Box2D); 3D is captured as JSON with REAL Jolt
221801
+ physics (bodies fall, characters collide, triggers fire, ragdolls drop — a character with no ground
221802
+ under it falls, like on device) — use \`--desktop\` for 3D pixels. \`console.log\` + \`expect.log\` is the assertion
221709
221803
  channel for game state that has no UI.
221710
221804
 
221711
221805
  ## Make the app testable
@@ -221774,7 +221868,7 @@ var create = async (args) => {
221774
221868
  throw new CliError("Usage: lecodes create <name> [directory]");
221775
221869
  const dir = args._[1] ?? slug2(name);
221776
221870
  const root = resolve10(dir);
221777
- if (existsSync23(root) && readdirSync12(root).length > 0) {
221871
+ if (existsSync23(root) && readdirSync13(root).length > 0) {
221778
221872
  throw new CliError(`Directory "${dir}" already exists and is not empty.`);
221779
221873
  }
221780
221874
  mkdirSync17(root, { recursive: true });
@@ -221797,6 +221891,7 @@ var create = async (args) => {
221797
221891
  // src/commands/desktop.ts
221798
221892
  init_util();
221799
221893
  init_manifest();
221894
+ import { basename as basename9 } from "node:path";
221800
221895
  var resolveScale = (flag, cfg) => {
221801
221896
  const raw = flag ?? cfg;
221802
221897
  if (raw === undefined || raw === "system")
@@ -221830,9 +221925,13 @@ var run = async (args) => {
221830
221925
  const renderer = resolveDesktopRenderer(root, args);
221831
221926
  const { js, resources } = await buildBundle(args, undefined, { localAssets: true, desktopRenderer: renderer });
221832
221927
  assertRendererSupportsBundle(renderer, js);
221928
+ const { dirs: pluginDirs, files: pluginFiles } = desktopPluginLibraries(root);
221929
+ if (pluginFiles.length > 0)
221930
+ note(`Native plugins: ${pluginFiles.map((f3) => basename9(f3)).join(", ")}`);
221833
221931
  note(`Launching LeCodes Desktop${renderer === "vulkan" ? " (Vulkan)" : ""}… (close the window to return)`);
221834
221932
  await runDesktopApp(js, resources, {
221835
221933
  renderer,
221934
+ pluginDirs,
221836
221935
  width: Number(flagStr(args, "width")) || win?.width,
221837
221936
  height: Number(flagStr(args, "height")) || win?.height,
221838
221937
  minWidth: win?.minWidth,
@@ -221886,15 +221985,15 @@ init_design();
221886
221985
  // src/commands/dev.ts
221887
221986
  init_manifest();
221888
221987
  init_ignore();
221889
- import { existsSync as existsSync25, mkdirSync as mkdirSync19, readFileSync as readFileSync22, watch, writeFileSync as writeFileSync20 } from "node:fs";
221988
+ import { existsSync as existsSync26, mkdirSync as mkdirSync19, readFileSync as readFileSync22, watch, writeFileSync as writeFileSync20 } from "node:fs";
221890
221989
  import { randomBytes as randomBytes3 } from "node:crypto";
221891
221990
  import { networkInterfaces } from "node:os";
221892
- import { basename as basename11, join as join27 } from "node:path";
221991
+ import { basename as basename12, join as join28 } from "node:path";
221893
221992
 
221894
221993
  // src/dev/devServer.ts
221895
221994
  init_localFiles();
221896
221995
  import { createServer as createServer2 } from "node:http";
221897
- import { createReadStream, statSync as statSync9 } from "node:fs";
221996
+ import { createReadStream, statSync as statSync10 } from "node:fs";
221898
221997
  import { join as join26 } from "node:path";
221899
221998
  init_projectCompile();
221900
221999
  init_fonts();
@@ -222158,7 +222257,12 @@ var MIME = {
222158
222257
  woff2: "font/woff2",
222159
222258
  json: "application/json",
222160
222259
  txt: "text/plain",
222161
- js: "text/javascript"
222260
+ js: "text/javascript",
222261
+ mjs: "text/javascript",
222262
+ css: "text/css",
222263
+ html: "text/html",
222264
+ wasm: "application/wasm",
222265
+ map: "application/json"
222162
222266
  };
222163
222267
  var contentType = (path2) => {
222164
222268
  const i2 = path2.lastIndexOf(".");
@@ -222253,22 +222357,58 @@ var startDevServer = async (options) => {
222253
222357
  else if (m.type === "error")
222254
222358
  log2(`[device:error] ${demangle(m.text ?? "")}`);
222255
222359
  };
222256
- const transportOf = (url) => url.searchParams.get("t") === "poll" ? "poll" : "ws";
222360
+ const transportOf = (url) => {
222361
+ const t2 = url.searchParams.get("t");
222362
+ return t2 === "poll" ? "poll" : t2 === "web" ? "web" : "ws";
222363
+ };
222364
+ const withPrelude = (js, url, v2) => {
222365
+ const transport = transportOf(url);
222366
+ return transport === "web" ? js : js + `
222367
+ ` + renderPrelude({ origin, base, transport, v: v2 });
222368
+ };
222369
+ const webDir = options.webDir ?? null;
222257
222370
  const handle = async (req, res) => {
222258
222371
  const url = new URL(req.url ?? "/", origin);
222259
222372
  const path2 = url.pathname;
222373
+ if (path2 === "/favicon.ico") {
222374
+ res.writeHead(204);
222375
+ res.end();
222376
+ return;
222377
+ }
222260
222378
  if (!path2.startsWith(base + "/"))
222261
222379
  return sendText(res, 404, "text/plain", "Not found");
222262
222380
  const route = path2.slice(base.length);
222263
222381
  if (route === "/dev.js") {
222264
- return sendText(res, 200, "text/javascript", devJs + `
222265
- ` + renderPrelude({ origin, base, transport: transportOf(url), v: -1 }));
222382
+ return sendText(res, 200, "text/javascript", withPrelude(devJs, url, -1));
222266
222383
  }
222267
222384
  if (route === "/bundle.js") {
222268
222385
  if (bundleJs === null)
222269
222386
  return sendText(res, 503, "text/plain", "No successful compile yet — check the terminal.");
222270
- return sendText(res, 200, "text/javascript", bundleJs + `
222271
- ` + renderPrelude({ origin, base, transport: transportOf(url), v }));
222387
+ res.setHeader("x-lecodes-v", String(v));
222388
+ return sendText(res, 200, "text/javascript", withPrelude(bundleJs, url, v));
222389
+ }
222390
+ if (route === "/web") {
222391
+ res.writeHead(302, { location: `${base}/web/${url.search}` });
222392
+ res.end();
222393
+ return;
222394
+ }
222395
+ if (route.startsWith("/web/")) {
222396
+ if (!webDir)
222397
+ return sendText(res, 404, "text/plain", "No Web Lite runner vendored with this CLI (lecodes-viewer-lite dist-embed).");
222398
+ const rel = route === "/web/" ? "embed.html" : route.slice("/web/".length);
222399
+ if (rel.split("/").some((seg) => seg === ".." || seg === "" || seg === "."))
222400
+ return sendText(res, 400, "text/plain", "Bad path");
222401
+ const abs = join26(webDir, ...rel.split("/"));
222402
+ try {
222403
+ if (!statSync10(abs).isFile())
222404
+ throw new Error("not a file");
222405
+ } catch {
222406
+ return sendText(res, 404, "text/plain", "Not found");
222407
+ }
222408
+ const hashed = rel.startsWith("assets/");
222409
+ res.writeHead(200, { "content-type": contentType(rel), "cache-control": hashed ? "max-age=31536000, immutable" : "no-store" });
222410
+ createReadStream(abs).pipe(res);
222411
+ return;
222272
222412
  }
222273
222413
  if (route === "/version") {
222274
222414
  return sendJson(res, 200, { v });
@@ -222283,7 +222423,7 @@ var startDevServer = async (options) => {
222283
222423
  return sendText(res, 400, "text/plain", "Bad path");
222284
222424
  const abs = join26(shadersCacheDir(root), name2);
222285
222425
  try {
222286
- if (!statSync9(abs).isFile())
222426
+ if (!statSync10(abs).isFile())
222287
222427
  throw new Error("not a file");
222288
222428
  } catch {
222289
222429
  return sendText(res, 404, "text/plain", "Not found");
@@ -222300,7 +222440,7 @@ var startDevServer = async (options) => {
222300
222440
  return sendText(res, 400, "text/plain", "Bad path");
222301
222441
  }
222302
222442
  const abs = resolveLocalPath(root, key);
222303
- if (!abs || !statSync9(abs).isFile())
222443
+ if (!abs || !statSync10(abs).isFile())
222304
222444
  return sendText(res, 404, "text/plain", "Not found");
222305
222445
  res.writeHead(200, { "content-type": contentType(key), "cache-control": "no-store" });
222306
222446
  createReadStream(abs).pipe(res);
@@ -222354,6 +222494,7 @@ var startDevServer = async (options) => {
222354
222494
  const boundPort = typeof address === "object" && address !== null ? address.port : options.port;
222355
222495
  return {
222356
222496
  url: `${origin}${base}/dev.js`,
222497
+ webUrl: webDir ? `${origin}${base}/web/` : null,
222357
222498
  base,
222358
222499
  port: boundPort,
222359
222500
  v: () => v,
@@ -222367,7 +222508,25 @@ var startDevServer = async (options) => {
222367
222508
  };
222368
222509
  };
222369
222510
 
222511
+ // src/dev/webRunner.ts
222512
+ 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";
222516
+ var resolveWebRunnerDir = () => {
222517
+ const candidates = [join27(distRoot, "runtime", "web")];
222518
+ try {
222519
+ const pkg = createRequire5(import.meta.url).resolve("lecodes-viewer-lite/package.json");
222520
+ candidates.push(join27(dirname14(pkg), "dist-embed"));
222521
+ } catch {}
222522
+ for (const dir of candidates)
222523
+ if (existsSync25(join27(dir, "embed.html")) && existsSync25(join27(dir, "embed.js")))
222524
+ return dir;
222525
+ return null;
222526
+ };
222527
+
222370
222528
  // src/commands/dev.ts
222529
+ init_browserAuth();
222371
222530
  init_util();
222372
222531
  var DEFAULT_PORT2 = 4499;
222373
222532
  var DEBOUNCE_MS = 120;
@@ -222395,14 +222554,14 @@ var lanCandidates = () => {
222395
222554
  return out.sort((x2, y2) => x2.rank - y2.rank).map(({ address, iface }) => ({ address, iface }));
222396
222555
  };
222397
222556
  var loadDevToken = (root) => {
222398
- const path2 = join27(root, LECODES_DIR, "dev.json");
222557
+ const path2 = join28(root, LECODES_DIR, "dev.json");
222399
222558
  try {
222400
222559
  const token2 = JSON.parse(readFileSync22(path2, "utf8")).token;
222401
222560
  if (token2 && /^[a-z0-9]{8,}$/.test(token2))
222402
222561
  return token2;
222403
222562
  } catch {}
222404
222563
  const token = randomBytes3(6).toString("hex");
222405
- mkdirSync19(join27(root, LECODES_DIR), { recursive: true });
222564
+ mkdirSync19(join28(root, LECODES_DIR), { recursive: true });
222406
222565
  writeFileSync20(path2, JSON.stringify({ token }, null, 2) + `
222407
222566
  `);
222408
222567
  return token;
@@ -222428,7 +222587,7 @@ var dev = async (args) => {
222428
222587
  root = process.cwd();
222429
222588
  manifest = null;
222430
222589
  }
222431
- const name = manifest?.name ?? basename11(root);
222590
+ const name = manifest?.name ?? basename12(root);
222432
222591
  if (typeof globalThis.Bun !== "undefined") {
222433
222592
  warnErr("Running under Bun: the websocket reload channel doesn't work here — run `lecodes dev` with Node (or use ?t=poll on the device URL).");
222434
222593
  }
@@ -222440,6 +222599,10 @@ var dev = async (args) => {
222440
222599
  const alternatives = candidates.filter((c4) => c4.address !== host);
222441
222600
  const origin = `http://${host}:${port}`;
222442
222601
  const token = loadDevToken(root);
222602
+ const webDir = resolveWebRunnerDir();
222603
+ if (flagBool(args, "web") && !webDir) {
222604
+ throw new CliError("No Web Lite runner found for --web: this CLI has no runtime/web (a monorepo checkout needs `bun run build:embed` in packages/viewer-lite).");
222605
+ }
222443
222606
  const server2 = await startDevServer({
222444
222607
  root,
222445
222608
  manifest,
@@ -222448,6 +222611,7 @@ var dev = async (args) => {
222448
222611
  port,
222449
222612
  token,
222450
222613
  entryOverride: flagStr(args, "entry"),
222614
+ webDir,
222451
222615
  log: note
222452
222616
  }).catch((e2) => {
222453
222617
  const msg = e2 instanceof Error ? e2.message : String(e2);
@@ -222516,14 +222680,21 @@ ${result.error}`);
222516
222680
  if (!flagBool(args, "no-qr"))
222517
222681
  await printQr(server2.url);
222518
222682
  log(`Scan the QR with the LeCodes app, or open: ${c.bold(server2.url)}`);
222683
+ const webLocal = server2.webUrl ? server2.webUrl.replace(`http://${host}:`, "http://localhost:") : null;
222684
+ if (server2.webUrl && webLocal)
222685
+ log(`In the browser (Web Lite): ${c.bold(webLocal)} (LAN: ${server2.webUrl})`);
222519
222686
  note(`Older desktop builds (no websocket bridge): ${server2.url}?t=poll`);
222520
222687
  if (alternatives.length > 0) {
222521
222688
  note(`Device can't reach it? This machine also has ${alternatives.map((a2) => `${a2.address} (${a2.iface})`).join(", ")} — retry with --host <ip>.`);
222522
222689
  }
222523
- if (existsSync25(join27(root, LECODES_DIR, "manifest.json")))
222690
+ if (existsSync26(join28(root, LECODES_DIR, "manifest.json")))
222524
222691
  note(`Token stored in ${LECODES_DIR}/dev.json (delete it to rotate the URL).`);
222525
222692
  log("");
222526
222693
  log(`Watching ${root} — save a file to hot-reload. Ctrl+C to stop.`);
222694
+ if (flagBool(args, "web") && webLocal) {
222695
+ note("Opening the browser…");
222696
+ openBrowser(webLocal);
222697
+ }
222527
222698
  if (flagBool(args, "desktop")) {
222528
222699
  const appCfg = readAppConfigAt(root);
222529
222700
  const win = appCfg?.desktop?.window;
@@ -222550,7 +222721,7 @@ init_localFiles();
222550
222721
  // src/serverDiff.ts
222551
222722
  init_localFiles();
222552
222723
  import { readFileSync as readFileSync23 } from "node:fs";
222553
- import { basename as basename12 } from "node:path";
222724
+ import { basename as basename13 } from "node:path";
222554
222725
  var isTextAsset = (t2) => t2 === "text" || t2 === "shader" || t2 === "scene";
222555
222726
  var computeServerDiff = (project, scan2) => {
222556
222727
  const pathById = buildPaths(project.assets);
@@ -222567,7 +222738,7 @@ var computeServerDiff = (project, scan2) => {
222567
222738
  const server2 = serverByPath.get(path2);
222568
222739
  const local = localByPath.get(path2);
222569
222740
  if (local && !server2) {
222570
- const text = isTextKind(classifyKind(basename12(path2)));
222741
+ const text = isTextKind(classifyKind(basename13(path2)));
222571
222742
  entries.push({ path: path2, status: "added", binary: !text, serverText: "", localText: text ? readFileSync23(local.absPath, "utf8") : undefined });
222572
222743
  continue;
222573
222744
  }
@@ -222742,8 +222913,8 @@ var statusLabel = (e2) => e2.status === "added" ? c.green(" added ") : e2.st
222742
222913
  init_manifest();
222743
222914
  init_types4();
222744
222915
  init_ignore();
222745
- import { existsSync as existsSync26, mkdirSync as mkdirSync20, writeFileSync as writeFileSync21 } from "node:fs";
222746
- import { basename as basename13, dirname as dirname14, join as join28, resolve as resolve11 } from "node:path";
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";
222747
222918
  init_util();
222748
222919
  var STARTER2 = `// Welcome to LeCodes — the whole SDK is available as globals, no imports needed.
222749
222920
  // Run it live on a device with \`lecodes dev\`, or bundle it with \`lecodes compile -o app.js\`.
@@ -222852,16 +223023,16 @@ Full command list: \`lecodes --help\`.
222852
223023
  var init2 = async (args) => {
222853
223024
  const dirArg = (args._[0] ?? ".").replace(/[/\\]+$/, "") || ".";
222854
223025
  const root = resolve11(dirArg);
222855
- const name = basename13(root);
223026
+ const name = basename14(root);
222856
223027
  mkdirSync20(root, { recursive: true });
222857
223028
  let created = 0;
222858
223029
  const write = (rel, content) => {
222859
- const abs = join28(root, rel);
222860
- if (existsSync26(abs)) {
223030
+ const abs = join29(root, rel);
223031
+ if (existsSync27(abs)) {
222861
223032
  note(`kept ${rel}`);
222862
223033
  return;
222863
223034
  }
222864
- mkdirSync20(dirname14(abs), { recursive: true });
223035
+ mkdirSync20(dirname15(abs), { recursive: true });
222865
223036
  writeFileSync21(abs, content);
222866
223037
  log(`${c.green("created")} ${rel}`);
222867
223038
  created++;
@@ -222909,8 +223080,8 @@ init_config();
222909
223080
  init_ignore();
222910
223081
  init_localFiles();
222911
223082
  init_manifest();
222912
- import { existsSync as existsSync27 } from "node:fs";
222913
- import { basename as basename15, join as join29, resolve as resolve12 } from "node:path";
223083
+ import { existsSync as existsSync28 } from "node:fs";
223084
+ import { basename as basename16, join as join30, resolve as resolve12 } from "node:path";
222914
223085
  init_types4();
222915
223086
  init_util();
222916
223087
 
@@ -222919,7 +223090,7 @@ init_api();
222919
223090
  init_config();
222920
223091
  init_manifest();
222921
223092
  init_localFiles();
222922
- import { basename as basename14 } from "node:path";
223093
+ import { basename as basename15 } from "node:path";
222923
223094
  import { readFileSync as readFileSync24 } from "node:fs";
222924
223095
  init_util();
222925
223096
  var push = async (args) => {
@@ -222976,14 +223147,14 @@ var push = async (args) => {
222976
223147
  let uploaded = 0;
222977
223148
  for (const file of scan2.files) {
222978
223149
  const source = sourceFor(file.path);
222979
- const kind = source?.type ?? classifyKind(basename14(file.path));
223150
+ const kind = source?.type ?? classifyKind(basename15(file.path));
222980
223151
  const entry = { path: file.path, type: kind };
222981
223152
  if (source)
222982
223153
  entry.assetId = source.id;
222983
223154
  if (isTextKind(kind)) {
222984
223155
  entry.text = readFileSync24(file.absPath, "utf8");
222985
223156
  } else if (!source || source.sha !== file.sha) {
222986
- const blob = await uploadBlob(apiUrl, token, manifest.uuid, basename14(file.path), readFileSync24(file.absPath));
223157
+ const blob = await uploadBlob(apiUrl, token, manifest.uuid, basename15(file.path), readFileSync24(file.absPath));
222987
223158
  entry.blobFileId = blob.id;
222988
223159
  uploaded++;
222989
223160
  }
@@ -223091,9 +223262,9 @@ var link = async (args) => {
223091
223262
  const token = requireToken(config);
223092
223263
  const dirArg = args._[0];
223093
223264
  const root = dirArg ? resolve12(dirArg) : process.cwd();
223094
- if (!existsSync27(root))
223265
+ if (!existsSync28(root))
223095
223266
  throw new CliError(`No such directory: ${root}`);
223096
- if (existsSync27(join29(root, LECODES_DIR, "manifest.json")) && !flagBool(args, "force")) {
223267
+ if (existsSync28(join30(root, LECODES_DIR, "manifest.json")) && !flagBool(args, "force")) {
223097
223268
  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.");
223098
223269
  }
223099
223270
  let enclosing = null;
@@ -223120,7 +223291,7 @@ var link = async (args) => {
223120
223291
  }
223121
223292
  name = project.name;
223122
223293
  } else {
223123
- name = flagStr(args, "name") ?? basename15(root);
223294
+ name = flagStr(args, "name") ?? basename16(root);
223124
223295
  info(c.dim(`Creating project ${c.bold(name)}…`));
223125
223296
  uuid = (await createProject(apiUrl, token, name, flagStr(args, "team"))).uuid;
223126
223297
  files = {};
@@ -223157,10 +223328,10 @@ var link = async (args) => {
223157
223328
 
223158
223329
  // src/commands/lightmap.ts
223159
223330
  init_manifest();
223160
- import { existsSync as existsSync28, mkdirSync as mkdirSync21, mkdtempSync as mkdtempSync4, readFileSync as readFileSync25, renameSync as renameSync3, rmSync as rmSync9, statSync as statSync10, unlinkSync, writeFileSync as writeFileSync22 } from "node:fs";
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";
223161
223332
  import { spawn as spawn6, spawnSync as spawnSync6 } from "node:child_process";
223162
223333
  import { tmpdir as tmpdir4 } from "node:os";
223163
- import { join as join30, relative as relative4, resolve as resolve13 } from "node:path";
223334
+ import { join as join31, relative as relative4, resolve as resolve13 } from "node:path";
223164
223335
  init_util();
223165
223336
  var num = (args, key, fallback, min, max) => {
223166
223337
  const raw = flagStr(args, key);
@@ -223173,13 +223344,13 @@ var num = (args, key, fallback, min, max) => {
223173
223344
  };
223174
223345
  var findToktx = () => {
223175
223346
  const env = process.env.KTX_TOKTX;
223176
- if (env && existsSync28(env))
223347
+ if (env && existsSync29(env))
223177
223348
  return env;
223178
- for (const base of [process.env.LOCALAPPDATA && join30(process.env.LOCALAPPDATA, "Programs"), process.env.ProgramFiles]) {
223349
+ for (const base of [process.env.LOCALAPPDATA && join31(process.env.LOCALAPPDATA, "Programs"), process.env.ProgramFiles]) {
223179
223350
  if (!base)
223180
223351
  continue;
223181
- const guess = join30(base, "KTX-Software", "bin", "toktx.exe");
223182
- if (existsSync28(guess))
223352
+ const guess = join31(base, "KTX-Software", "bin", "toktx.exe");
223353
+ if (existsSync29(guess))
223183
223354
  return guess;
223184
223355
  }
223185
223356
  return "toktx";
@@ -223234,7 +223405,7 @@ var bake = async (args) => {
223234
223405
  throw new CliError(`--name wants a plain file stem (letters, digits, - and _), got "${stem}".`);
223235
223406
  const entryFlag = flagStr(args, "entry");
223236
223407
  const entryFile = entryFlag ?? "main.ts";
223237
- if (!existsSync28(resolve13(root, entryFile))) {
223408
+ if (!existsSync29(resolve13(root, entryFile))) {
223238
223409
  throw new CliError(`No ${entryFile} in ${root} — pass --entry <file> (the file that builds the scene and calls Lightmap.load).`);
223239
223410
  }
223240
223411
  const cfg = {
@@ -223275,8 +223446,8 @@ import ${JSON.stringify(importPath)}
223275
223446
  extraEntries: [{ path: entryPath, type: "text", text: entryText }],
223276
223447
  entryOverride: entryPath
223277
223448
  });
223278
- const scratch = mkdtempSync4(join30(tmpdir4(), "lecodes-lightmap-"));
223279
- const outPng = join30(scratch, "frame.png");
223449
+ const scratch = mkdtempSync4(join31(tmpdir4(), "lecodes-lightmap-"));
223450
+ const outPng = join31(scratch, "frame.png");
223280
223451
  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}.*…`);
223281
223452
  let output = "";
223282
223453
  try {
@@ -223310,8 +223481,8 @@ import ${JSON.stringify(importPath)}
223310
223481
  logErr(text.startsWith("error") ? c.red(`✗ ${text}`) : text.startsWith("warning") ? c.yellow(` ${text}`) : c.dim(` ${text}`));
223311
223482
  }
223312
223483
  if (/\[lightmap\] done/.test(output)) {
223313
- const bakePath = join30(outDir, "lightmap.bake");
223314
- if (!existsSync28(bakePath))
223484
+ const bakePath = join31(outDir, "lightmap.bake");
223485
+ if (!existsSync29(bakePath))
223315
223486
  throw new CliError(`The host reported a bake but ${bakePath} is missing.`);
223316
223487
  const data = JSON.parse(readFileSync25(bakePath, "utf8"));
223317
223488
  const pages = Math.max(1, data.pages ?? 1);
@@ -223319,13 +223490,13 @@ import ${JSON.stringify(importPath)}
223319
223490
  const t0 = Date.now();
223320
223491
  let pngKb = 0, ktxKb = 0;
223321
223492
  const encodePage = async (page, light) => {
223322
- const png2 = join30(outDir, pageFile("lightmap", page, light, ".png"));
223323
- const ktx2 = join30(outDir, pageFile(stem, page, light));
223324
- if (!existsSync28(png2))
223493
+ const png2 = join31(outDir, pageFile("lightmap", page, light, ".png"));
223494
+ const ktx2 = join31(outDir, pageFile(stem, page, light));
223495
+ if (!existsSync29(png2))
223325
223496
  throw new CliError(`The host reported a bake but ${png2} is missing.`);
223326
223497
  await encodeKtx2(toktx, png2, ktx2);
223327
- pngKb += statSync10(png2).size / 1024;
223328
- ktxKb += statSync10(ktx2).size / 1024;
223498
+ pngKb += statSync11(png2).size / 1024;
223499
+ ktxKb += statSync11(ktx2).size / 1024;
223329
223500
  unlinkSync(png2);
223330
223501
  };
223331
223502
  const encodes = [];
@@ -223337,14 +223508,14 @@ import ${JSON.stringify(importPath)}
223337
223508
  await Promise.all(encodes);
223338
223509
  for (let p2 = pages;p2 < 16; p2++) {
223339
223510
  for (const light of [false, true]) {
223340
- const stale = join30(outDir, pageFile(stem, p2, light));
223341
- if (existsSync28(stale) && statSync10(stale).size !== PLACEHOLDER_KTX2.length)
223511
+ const stale = join31(outDir, pageFile(stem, p2, light));
223512
+ if (existsSync29(stale) && statSync11(stale).size !== PLACEHOLDER_KTX2.length)
223342
223513
  writeFileSync22(stale, PLACEHOLDER_KTX2);
223343
223514
  }
223344
223515
  }
223345
223516
  if (!hasLight) {
223346
- const light = join30(outDir, pageFile(stem, 0, true));
223347
- if (existsSync28(light) && statSync10(light).size !== PLACEHOLDER_KTX2.length)
223517
+ const light = join31(outDir, pageFile(stem, 0, true));
223518
+ if (existsSync29(light) && statSync11(light).size !== PLACEHOLDER_KTX2.length)
223348
223519
  writeFileSync22(light, PLACEHOLDER_KTX2);
223349
223520
  }
223350
223521
  data.texture = pageFile(stem, 0, false);
@@ -223353,19 +223524,19 @@ import ${JSON.stringify(importPath)}
223353
223524
  data.lightTextures = Array.from({ length: pages }, (_, p2) => pageFile(stem, p2, true));
223354
223525
  if (data.volume)
223355
223526
  data.volume.file = `${stem}.volume`;
223356
- writeFileSync22(join30(outDir, `${stem}.bake`), JSON.stringify(data, null, 1) + `
223527
+ writeFileSync22(join31(outDir, `${stem}.bake`), JSON.stringify(data, null, 1) + `
223357
223528
  `);
223358
223529
  if (stem !== "lightmap")
223359
223530
  unlinkSync(bakePath);
223360
- const volumeSrc = join30(outDir, "lightmap.volume");
223361
- const hasVolume = cfg.volumeCell > 0 && existsSync28(volumeSrc);
223531
+ const volumeSrc = join31(outDir, "lightmap.volume");
223532
+ const hasVolume = cfg.volumeCell > 0 && existsSync29(volumeSrc);
223362
223533
  if (hasVolume && stem !== "lightmap") {
223363
- rmSync9(join30(outDir, `${stem}.volume`), { force: true });
223364
- renameSync3(volumeSrc, join30(outDir, `${stem}.volume`));
223534
+ rmSync9(join31(outDir, `${stem}.volume`), { force: true });
223535
+ renameSync3(volumeSrc, join31(outDir, `${stem}.volume`));
223365
223536
  }
223366
223537
  logErr(c.dim(` encoded ${pages} page(s)${hasLight ? " + the light atlas" : ""} (UASTC + mips, ${Math.round(ktxKb)} KB from ${Math.round(pngKb)} KB of rasters) in ${((Date.now() - t0) / 1000).toFixed(1)} s`));
223367
223538
  logErr(c.green(`✓ baked → ${relative4(root, outDir) || "."}/${stem}.bake + ${data.textures.join(", ")}${hasLight ? ` + ${data.lightTextures.join(", ")}` : ""}${hasVolume ? ` + ${stem}.volume` : ""}`));
223368
- const rel = (f3) => relative4(root, join30(outDir, f3)).replace(/\\/g, "/");
223539
+ const rel = (f3) => relative4(root, join31(outDir, f3)).replace(/\\/g, "/");
223369
223540
  const assets = (files) => files.length === 1 ? `asset('<rel>/${rel(files[0])}')` : `[${files.map((f3) => `asset('<rel>/${rel(f3)}')`).join(", ")}]`;
223370
223541
  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`));
223371
223542
  return;
@@ -223395,11 +223566,11 @@ var placeholderVolume = () => {
223395
223566
  return b;
223396
223567
  };
223397
223568
  var ensurePlaceholders = (outDir, stem) => {
223398
- const missing = (f3) => !existsSync28(f3) || statSync10(f3).size === 0;
223399
- const bake2 = join30(outDir, `${stem}.bake`);
223400
- const ktx2 = join30(outDir, pageFile(stem, 0, false));
223401
- const light = join30(outDir, pageFile(stem, 0, true));
223402
- const volume = join30(outDir, `${stem}.volume`);
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`);
223403
223574
  if (missing(bake2))
223404
223575
  writeFileSync22(bake2, JSON.stringify({ version: 2, placeholder: true, size: 1, texel: 0, pages: 1, instances: [] }) + `
223405
223576
  `);
@@ -223419,9 +223590,9 @@ var lightmap = async (args) => {
223419
223590
 
223420
223591
  // src/commands/navmesh.ts
223421
223592
  init_manifest();
223422
- import { existsSync as existsSync29, mkdirSync as mkdirSync22, mkdtempSync as mkdtempSync5, readdirSync as readdirSync14, readFileSync as readFileSync26, rmSync as rmSync10, statSync as statSync11, unlinkSync as unlinkSync2, writeFileSync as writeFileSync23 } from "node:fs";
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";
223423
223594
  import { tmpdir as tmpdir5 } from "node:os";
223424
- import { basename as basename16, join as join31, relative as relative5, resolve as resolve14 } from "node:path";
223595
+ import { basename as basename17, join as join32, relative as relative5, resolve as resolve14 } from "node:path";
223425
223596
  init_util();
223426
223597
 
223427
223598
  // ../creator-nav/dist/creatorNav.js
@@ -223438,8 +223609,8 @@ var createCreatorNav = (() => {
223438
223609
  var ENVIRONMENT_IS_WORKER = typeof WorkerGlobalScope != "undefined";
223439
223610
  var ENVIRONMENT_IS_NODE = typeof process == "object" && process.versions?.node && process.type != "renderer";
223440
223611
  if (ENVIRONMENT_IS_NODE) {
223441
- const { createRequire: createRequire5 } = await import("module");
223442
- var require2 = createRequire5(import.meta.url);
223612
+ const { createRequire: createRequire6 } = await import("module");
223613
+ var require2 = createRequire6(import.meta.url);
223443
223614
  }
223444
223615
  var arguments_ = [];
223445
223616
  var thisProgram = "./this.program";
@@ -224218,13 +224389,13 @@ var num2 = (args, key, fallback, min, max) => {
224218
224389
  return n2;
224219
224390
  };
224220
224391
  var guessName = (root) => {
224221
- const dirs = [join31(root, "src"), root];
224392
+ const dirs = [join32(root, "src"), root];
224222
224393
  for (const dir of dirs) {
224223
- if (!existsSync29(dir))
224394
+ if (!existsSync30(dir))
224224
224395
  continue;
224225
224396
  let entries;
224226
224397
  try {
224227
- entries = readdirSync14(dir);
224398
+ entries = readdirSync15(dir);
224228
224399
  } catch {
224229
224400
  continue;
224230
224401
  }
@@ -224232,7 +224403,7 @@ var guessName = (root) => {
224232
224403
  if (!f3.endsWith(".scene.ts"))
224233
224404
  continue;
224234
224405
  try {
224235
- if (/\bnavmesh\s*:/.test(readFileSync26(join31(dir, f3), "utf8")))
224406
+ if (/\bnavmesh\s*:/.test(readFileSync26(join32(dir, f3), "utf8")))
224236
224407
  return f3.slice(0, -".scene.ts".length);
224237
224408
  } catch {}
224238
224409
  }
@@ -224275,7 +224446,7 @@ var bake2 = async (args) => {
224275
224446
  const outDir = resolve14(root, flagStr(args, "out") ?? "assets/nav");
224276
224447
  const entryFlag = flagStr(args, "entry");
224277
224448
  const entryFile = entryFlag ?? "main.ts";
224278
- if (!existsSync29(resolve14(root, entryFile))) {
224449
+ if (!existsSync30(resolve14(root, entryFile))) {
224279
224450
  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).`);
224280
224451
  }
224281
224452
  const name = flagStr(args, "name") ?? guessName(root) ?? "scene";
@@ -224287,8 +224458,8 @@ var bake2 = async (args) => {
224287
224458
  const keepGeometry = flagBool(args, "keep-geometry");
224288
224459
  const obj = flagBool(args, "obj");
224289
224460
  mkdirSync22(outDir, { recursive: true });
224290
- const navmeshPath = join31(outDir, `${name}.navmesh`);
224291
- if (!existsSync29(navmeshPath))
224461
+ const navmeshPath = join32(outDir, `${name}.navmesh`);
224462
+ if (!existsSync30(navmeshPath))
224292
224463
  writeFileSync23(navmeshPath, placeholderLnav());
224293
224464
  const importPath = "./" + entryFile.replace(/\\/g, "/").replace(/\.tsx?$/, "");
224294
224465
  const entryPath = "/.navmesh-entry.ts";
@@ -224303,8 +224474,8 @@ import ${JSON.stringify(importPath)}
224303
224474
  extraEntries: [{ path: entryPath, type: "text", text: entryText }],
224304
224475
  entryOverride: entryPath
224305
224476
  });
224306
- const scratch = mkdtempSync5(join31(tmpdir5(), "lecodes-navmesh-"));
224307
- const outPng = join31(scratch, "frame.png");
224477
+ const scratch = mkdtempSync5(join32(tmpdir5(), "lecodes-navmesh-"));
224478
+ const outPng = join32(scratch, "frame.png");
224308
224479
  note(`Dumping the level's static collision geometry through the desktop host → ${relative5(root, outDir) || "."}/${name}.navgeo…`);
224309
224480
  let output = "";
224310
224481
  try {
@@ -224357,8 +224528,8 @@ import ${JSON.stringify(importPath)}
224357
224528
  }
224358
224529
  if (!config)
224359
224530
  throw new CliError("The host reported a dump but no `[navmesh] config` line — SDK/host mismatch?");
224360
- const geoPath = join31(outDir, `${name}.navgeo`);
224361
- if (!existsSync29(geoPath))
224531
+ const geoPath = join32(outDir, `${name}.navgeo`);
224532
+ if (!existsSync30(geoPath))
224362
224533
  throw new CliError(`The host reported a dump but ${geoPath} is missing.`);
224363
224534
  const geo = readNgeo(new Uint8Array(readFileSync26(geoPath)));
224364
224535
  const params = {
@@ -224408,18 +224579,18 @@ import ${JSON.stringify(importPath)}
224408
224579
  }
224409
224580
  writeFileSync23(navmeshPath, result.data);
224410
224581
  if (obj) {
224411
- const objPath = join31(outDir, `${name}.navmesh.obj`);
224582
+ const objPath = join32(outDir, `${name}.navmesh.obj`);
224412
224583
  writeObj(objPath, navMeshTriangles(mod, result.data));
224413
224584
  logErr(c.dim(` wrote ${relative5(root, objPath)} (the polygon mesh, for inspection)`));
224414
224585
  }
224415
224586
  if (!keepGeometry)
224416
224587
  unlinkSync2(geoPath);
224417
- const kb = Math.round(statSync11(navmeshPath).size / 1024);
224588
+ const kb = Math.round(statSync12(navmeshPath).size / 1024);
224418
224589
  const b = result.bounds;
224419
224590
  logErr(c.dim(` ${result.tiles} tiles, ${result.polys} polygons, ${(b.max[0] - b.min[0]).toFixed(0)} × ${(b.max[2] - b.min[2]).toFixed(0)} m, ${result.time.toFixed(1)} s`));
224420
224591
  logErr(c.green(`✓ baked → ${relative5(root, navmeshPath).replace(/\\/g, "/")} (${kb} KB)`));
224421
224592
  if (!config.data) {
224422
- logErr(c.dim(` load it with env.navmesh: { data: asset('<rel>/${basename16(navmeshPath)}') } in the scene file, or NavMesh.load(scene, { data: asset(...) })`));
224593
+ logErr(c.dim(` load it with env.navmesh: { data: asset('<rel>/${basename17(navmeshPath)}') } in the scene file, or NavMesh.load(scene, { data: asset(...) })`));
224423
224594
  }
224424
224595
  };
224425
224596
  var navmesh = async (args) => {
@@ -224565,7 +224736,7 @@ var shaders = async (args) => {
224565
224736
 
224566
224737
  // src/commands/render.ts
224567
224738
  import { mkdirSync as mkdirSync23, readFileSync as readFileSync29, writeFileSync as writeFileSync24 } from "node:fs";
224568
- import { dirname as dirname15, resolve as resolve17 } from "node:path";
224739
+ import { dirname as dirname16, resolve as resolve17 } from "node:path";
224569
224740
 
224570
224741
  // src/compile/headlessBundle.ts
224571
224742
  init_api();
@@ -224649,8 +224820,8 @@ var compileHeadlessBundle = async (args, opts = {}) => {
224649
224820
  // src/compile/screenEntry.ts
224650
224821
  init_designMeta();
224651
224822
  init_util();
224652
- import { existsSync as existsSync30, readFileSync as readFileSync28, statSync as statSync12 } from "node:fs";
224653
- import { isAbsolute, join as join32, relative as relative6, resolve as resolve15 } from "node:path";
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";
224654
224825
  var SCREEN_EXTS = [".ts", ".tsx", ".js", ".jsx", ".mjs"];
224655
224826
  var SCREEN_ENTRY_PATH = "/.screen-entry.ts";
224656
224827
  var DEFAULT_DESIGN_DIR = "design";
@@ -224659,13 +224830,13 @@ var hasSeparator = (file) => file.includes("/") || file.includes("\\");
224659
224830
  var findScreenFile = (root, file, dirFlag) => {
224660
224831
  const candidates = [resolve15(process.cwd(), file)];
224661
224832
  if (!hasSeparator(file)) {
224662
- candidates.push(join32(root, (dirFlag ?? DEFAULT_DESIGN_DIR).replace(/[/\\]+$/, ""), "screens", file));
224833
+ candidates.push(join33(root, (dirFlag ?? DEFAULT_DESIGN_DIR).replace(/[/\\]+$/, ""), "screens", file));
224663
224834
  }
224664
224835
  for (const base of candidates) {
224665
- if (existsSync30(base) && statSync12(base).isFile())
224836
+ if (existsSync31(base) && statSync13(base).isFile())
224666
224837
  return base;
224667
224838
  for (const ext2 of SCREEN_EXTS)
224668
- if (existsSync30(base + ext2))
224839
+ if (existsSync31(base + ext2))
224669
224840
  return base + ext2;
224670
224841
  }
224671
224842
  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/.`));
@@ -224674,8 +224845,8 @@ var designDirOf = (root, key) => {
224674
224845
  const m = /^\/(.*)screens\/([^/]+)\.[^./]+$/.exec(key);
224675
224846
  if (!m)
224676
224847
  return null;
224677
- const dir = join32(root, m[1]);
224678
- return existsSync30(join32(dir, "meta.json")) ? { dir, id: m[2] } : null;
224848
+ const dir = join33(root, m[1]);
224849
+ return existsSync31(join33(dir, "meta.json")) ? { dir, id: m[2] } : null;
224679
224850
  };
224680
224851
  var resolveScreenTarget = (root, file, opts = {}) => {
224681
224852
  const abs = findScreenFile(root, file, opts.dir);
@@ -225034,7 +225205,7 @@ var requireRenderer = async () => {
225034
225205
  var fileScenarioIO = (dir) => ({
225035
225206
  write: (relPath, data) => {
225036
225207
  const abs = resolve17(dir, relPath);
225037
- mkdirSync23(dirname15(abs), { recursive: true });
225208
+ mkdirSync23(dirname16(abs), { recursive: true });
225038
225209
  writeFileSync24(abs, data);
225039
225210
  }
225040
225211
  });
@@ -225109,23 +225280,25 @@ var renderDesktop = async (args) => {
225109
225280
  if ((camera || view) && scenario)
225110
225281
  throw new CliError('--camera/--view apply to a plain render; in a --script run use a { "view": "iso" } or { "camera": { "at", "lookAt" } } step before the screenshot.');
225111
225282
  const desktopRenderer = resolveDesktopRenderer(root, args);
225283
+ const { dirs: pluginDirs } = desktopPluginLibraries(root);
225112
225284
  const { js, resources } = await buildBundle(args, undefined, {
225113
225285
  localAssets: true,
225114
225286
  desktopRenderer,
225115
225287
  extraEntries: screen2 ? [screen2.entry] : undefined,
225116
225288
  entryOverride: screen2?.entryPath
225117
225289
  });
225118
- mkdirSync23(dirname15(resolve17(pngFlag)), { recursive: true });
225290
+ mkdirSync23(dirname16(resolve17(pngFlag)), { recursive: true });
225119
225291
  if (scenario) {
225120
225292
  const outDir = flagStr(args, "out-dir") ?? ".";
225121
225293
  const plan = translateScenario(scenario.steps, { outDir, headroomFrames: frames });
225122
225294
  for (const st of plan.steps)
225123
225295
  if (st.screenshot)
225124
- mkdirSync23(dirname15(st.screenshot), { recursive: true });
225296
+ mkdirSync23(dirname16(st.screenshot), { recursive: true });
225125
225297
  const timeoutMs2 = Number(flagStr(args, "timeout")) || 60000 + plan.steps.length * 5000;
225126
225298
  note(`Running ${plan.steps.length} step${plan.steps.length === 1 ? "" : "s"} (native)…`);
225127
225299
  const { output } = await runDesktopRender(js, resources, {
225128
225300
  renderer: desktopRenderer,
225301
+ pluginDirs,
225129
225302
  width,
225130
225303
  height,
225131
225304
  frames: plan.frameCap,
@@ -225158,6 +225331,7 @@ var renderDesktop = async (args) => {
225158
225331
  note(screen2 ? `Rendering ${screen2.label} (native)…` : "Rendering (native)…");
225159
225332
  await runDesktopRender(js, resources, {
225160
225333
  renderer: desktopRenderer,
225334
+ pluginDirs,
225161
225335
  width,
225162
225336
  height,
225163
225337
  frames,
@@ -225225,7 +225399,7 @@ var render = async (args) => {
225225
225399
  const settleStr = flagStr(args, "settle") ?? (scenario?.settle !== undefined ? String(scenario.settle) : undefined);
225226
225400
  const timeStr = flagStr(args, "time");
225227
225401
  const onConsole = flagBool(args, "logs") ? (level, text2) => logErr(`${c.dim(`[${level}]`)} ${text2}`) : undefined;
225228
- const envelopeFixtures = resolveFixtures(scenario?.fixtures, scriptFlag !== undefined ? dirname15(resolve17(scriptFlag)) : ".");
225402
+ const envelopeFixtures = resolveFixtures(scenario?.fixtures, scriptFlag !== undefined ? dirname16(resolve17(scriptFlag)) : ".");
225229
225403
  const flagFixtures = resolveFixtures(flagStr(args, "fixtures"), ".");
225230
225404
  const fixtures = envelopeFixtures || flagFixtures ? { ...envelopeFixtures, ...flagFixtures } : undefined;
225231
225405
  if (fixtures) {
@@ -225285,7 +225459,7 @@ var render = async (args) => {
225285
225459
  for (const w of [...warnings, ...mapWarnings])
225286
225460
  warnErr(w);
225287
225461
  const pngPath = mapFlag.endsWith(".png") ? mapFlag : mapFlag + ".png";
225288
- mkdirSync23(dirname15(resolve17(pngPath)), { recursive: true });
225462
+ mkdirSync23(dirname16(resolve17(pngPath)), { recursive: true });
225289
225463
  writeFileSync24(resolve17(pngPath), png2);
225290
225464
  const jsonPath = pngPath.slice(0, -4) + ".json";
225291
225465
  writeFileSync24(resolve17(jsonPath), JSON.stringify(data, null, 2));
@@ -225308,7 +225482,7 @@ var render = async (args) => {
225308
225482
  if (isNullSink(pngFlag)) {
225309
225483
  note(`Discarded PNG (${pngFlag})`);
225310
225484
  } else {
225311
- mkdirSync23(dirname15(resolve17(pngFlag)), { recursive: true });
225485
+ mkdirSync23(dirname16(resolve17(pngFlag)), { recursive: true });
225312
225486
  writeFileSync24(resolve17(pngFlag), png2);
225313
225487
  note(`Wrote ${pngFlag} (${png2.length} bytes, ${outW}x${outH}${clip !== undefined ? ", clipped" : ""})`);
225314
225488
  }
@@ -225339,8 +225513,8 @@ init_api();
225339
225513
  init_config();
225340
225514
  init_manifest();
225341
225515
  init_util();
225342
- import { appendFileSync as appendFileSync2, existsSync as existsSync31, readFileSync as readFileSync30, writeFileSync as writeFileSync25 } from "node:fs";
225343
- import { join as join33 } from "node:path";
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";
225344
225518
  var ENV_VAR = "LECODES_PUSH_KEY";
225345
225519
  var context = () => {
225346
225520
  const root = findProjectRoot(process.cwd());
@@ -225484,9 +225658,9 @@ var pnKeysList = async (ctx) => {
225484
225658
  };
225485
225659
  var writeEnv = (root, key) => {
225486
225660
  const touched = [];
225487
- const envPath = join33(root, ".env");
225661
+ const envPath = join34(root, ".env");
225488
225662
  const line = `${ENV_VAR}=${key}`;
225489
- const existing = existsSync31(envPath) ? readFileSync30(envPath, "utf8") : "";
225663
+ const existing = existsSync32(envPath) ? readFileSync30(envPath, "utf8") : "";
225490
225664
  if (new RegExp(`^${ENV_VAR}=`, "m").test(existing)) {
225491
225665
  writeFileSync25(envPath, existing.replace(new RegExp(`^${ENV_VAR}=.*$`, "m"), line));
225492
225666
  } else {
@@ -225497,8 +225671,8 @@ var writeEnv = (root, key) => {
225497
225671
  `);
225498
225672
  }
225499
225673
  touched.push(".env");
225500
- const ignorePath = join33(root, ".gitignore");
225501
- const ignore = existsSync31(ignorePath) ? readFileSync30(ignorePath, "utf8") : "";
225674
+ const ignorePath = join34(root, ".gitignore");
225675
+ const ignore = existsSync32(ignorePath) ? readFileSync30(ignorePath, "utf8") : "";
225502
225676
  if (!ignore.split(`
225503
225677
  `).some((l2) => l2.trim() === ".env")) {
225504
225678
  const separator = !ignore || ignore.endsWith(`
@@ -225597,8 +225771,8 @@ var pn = async (args) => {
225597
225771
  init_api();
225598
225772
  init_config();
225599
225773
  init_manifest();
225600
- import { existsSync as existsSync32, mkdirSync as mkdirSync24, rmSync as rmSync11, writeFileSync as writeFileSync26 } from "node:fs";
225601
- import { dirname as dirname16, join as join34 } from "node:path";
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";
225602
225776
  init_types4();
225603
225777
  init_localFiles();
225604
225778
  init_util();
@@ -225621,8 +225795,8 @@ var pull = async (args) => {
225621
225795
  const remotePaths = new Set([...buildPaths(project.assets).entries()].filter(([id]) => project.assets.find((a2) => a2.id === id)?.type !== "folder").map(([, path2]) => path2));
225622
225796
  for (const path2 of Object.keys(manifest.files)) {
225623
225797
  if (!remotePaths.has(path2)) {
225624
- const abs = join34(root, ...path2.replace(/^\//, "").split("/"));
225625
- if (existsSync32(abs))
225798
+ const abs = join35(root, ...path2.replace(/^\//, "").split("/"));
225799
+ if (existsSync33(abs))
225626
225800
  rmSync11(abs);
225627
225801
  }
225628
225802
  }
@@ -225660,8 +225834,8 @@ var pullPaths = async (root, manifest, project, paths, apiUrl, token) => {
225660
225834
  const files = { ...manifest.files };
225661
225835
  for (const asset of targets) {
225662
225836
  const key = pathById.get(asset.id);
225663
- const abs = join34(root, ...key.replace(/^\//, "").split("/"));
225664
- mkdirSync24(dirname16(abs), { recursive: true });
225837
+ const abs = join35(root, ...key.replace(/^\//, "").split("/"));
225838
+ mkdirSync24(dirname17(abs), { recursive: true });
225665
225839
  let sha;
225666
225840
  if (asset.type === "file") {
225667
225841
  if (!asset.file?.src)
@@ -225684,8 +225858,8 @@ var pullPaths = async (root, manifest, project, paths, apiUrl, token) => {
225684
225858
 
225685
225859
  // src/commands/scene.ts
225686
225860
  init_manifest();
225687
- import { existsSync as existsSync34, readFileSync as readFileSync32, readdirSync as readdirSync15, statSync as statSync13, writeFileSync as writeFileSync28 } from "node:fs";
225688
- import { basename as basename17, dirname as dirname17, join as join36, resolve as resolve18 } from "node:path";
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";
225689
225863
 
225690
225864
  // src/compile/sceneCompile.ts
225691
225865
  init_localFiles();
@@ -225694,17 +225868,17 @@ init_distRoot();
225694
225868
  init_util();
225695
225869
  init_projectCompile();
225696
225870
  import { createHash as createHash8 } from "node:crypto";
225697
- import { existsSync as existsSync33, mkdirSync as mkdirSync25, readFileSync as readFileSync31, writeFileSync as writeFileSync27 } from "node:fs";
225698
- import { join as join35 } from "node:path";
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";
225699
225873
  var TEXT_EXT = /\.(ts|js|json|svg|txt|md)$/i;
225700
225874
  var rel = (key) => key.replace(/^\//, "");
225701
225875
  var resolveHarness = (loadSceneHarness2) => {
225702
- const inRepo = join35(distRoot, "..", "projects", "src", "scene-editor");
225876
+ const inRepo = join36(distRoot, "..", "projects", "src", "scene-editor");
225703
225877
  try {
225704
- return loadSceneHarness2(existsSync33(inRepo) ? inRepo : undefined);
225878
+ return loadSceneHarness2(existsSync34(inRepo) ? inRepo : undefined);
225705
225879
  } catch {
225706
- const vendored = join35(distRoot, "runtime", "scene-harness.json");
225707
- if (existsSync33(vendored))
225880
+ const vendored = join36(distRoot, "runtime", "scene-harness.json");
225881
+ if (existsSync34(vendored))
225708
225882
  return JSON.parse(readFileSync31(vendored, "utf8"));
225709
225883
  throw new CliError("scene-editor harness sources not found — reinstall the CLI or set LECODES_SCENE_HARNESS_DIR.");
225710
225884
  }
@@ -225737,8 +225911,8 @@ var compileSceneEditor = async (opts) => {
225737
225911
  const dir = shadersCacheDir(root);
225738
225912
  mkdirSync25(dir, { recursive: true });
225739
225913
  const liteName = `${hash}_webgl-lite.filamat`;
225740
- if (!existsSync33(join35(dir, liteName)))
225741
- writeFileSync27(join35(dir, liteName), JSON.stringify(buildLiteMaterialArtifact2(e2.text ?? "")));
225914
+ if (!existsSync34(join36(dir, liteName)))
225915
+ writeFileSync27(join36(dir, liteName), JSON.stringify(buildLiteMaterialArtifact2(e2.text ?? "")));
225742
225916
  const src = `/project/.lecodes/shaders/${hash}_opengl.filamat`;
225743
225917
  e2.shaders = ["opengl", "webgl-lite"].map((platform) => ({ platform, src }));
225744
225918
  }
@@ -225772,9 +225946,9 @@ var DEFAULT_PORT3 = 4488;
225772
225946
  var findAppRoot = (start) => {
225773
225947
  let dir = start;
225774
225948
  while (true) {
225775
- if (existsSync34(join36(dir, "app.json")))
225949
+ if (existsSync35(join37(dir, "app.json")))
225776
225950
  return dir;
225777
- const parent = dirname17(dir);
225951
+ const parent = dirname18(dir);
225778
225952
  if (parent === dir)
225779
225953
  return null;
225780
225954
  dir = parent;
@@ -225799,23 +225973,23 @@ var getContext2 = (args) => {
225799
225973
  const pathArg = args._[0];
225800
225974
  if (pathArg) {
225801
225975
  const abs = resolve18(process.cwd(), pathArg);
225802
- if (existsSync34(abs) && statSync13(abs).isDirectory())
225976
+ if (existsSync35(abs) && statSync14(abs).isDirectory())
225803
225977
  root = abs;
225804
- else if (abs.endsWith(".scene.ts") && existsSync34(abs))
225805
- root = findAppRoot(dirname17(abs)) ?? dirname17(abs);
225978
+ else if (abs.endsWith(".scene.ts") && existsSync35(abs))
225979
+ root = findAppRoot(dirname18(abs)) ?? dirname18(abs);
225806
225980
  else
225807
225981
  throw new CliError(`No such folder or .scene.ts: ${pathArg}`);
225808
225982
  } else {
225809
225983
  root = findAppRoot(root) ?? root;
225810
225984
  }
225811
225985
  }
225812
- return { root, manifest, name: manifest?.name ?? basename17(root) };
225986
+ return { root, manifest, name: manifest?.name ?? basename18(root) };
225813
225987
  };
225814
225988
  var writeMcpConfig2 = (root, port) => {
225815
- const path2 = join36(root, ".mcp.json");
225989
+ const path2 = join37(root, ".mcp.json");
225816
225990
  const entry = { type: "http", url: `http://localhost:${port}/mcp` };
225817
225991
  let config = {};
225818
- if (existsSync34(path2)) {
225992
+ if (existsSync35(path2)) {
225819
225993
  try {
225820
225994
  config = JSON.parse(readFileSync32(path2, "utf8"));
225821
225995
  } catch {
@@ -225836,11 +226010,11 @@ var writeMcpConfig2 = (root, port) => {
225836
226010
  };
225837
226011
  var countScenes = (dir) => {
225838
226012
  let n2 = 0;
225839
- for (const name of readdirSync15(dir)) {
226013
+ for (const name of readdirSync16(dir)) {
225840
226014
  if (name === "node_modules" || name.startsWith("."))
225841
226015
  continue;
225842
- const full = join36(dir, name);
225843
- if (statSync13(full).isDirectory())
226016
+ const full = join37(dir, name);
226017
+ if (statSync14(full).isDirectory())
225844
226018
  n2 += countScenes(full);
225845
226019
  else if (name.endsWith(".scene.ts"))
225846
226020
  n2++;
@@ -225882,11 +226056,11 @@ var scene = async (args) => {
225882
226056
  };
225883
226057
 
225884
226058
  // src/commands/thumbs.ts
225885
- import { createRequire as createRequire5 } from "node:module";
226059
+ import { createRequire as createRequire6 } from "node:module";
225886
226060
  import { execFileSync as execFileSync7 } from "node:child_process";
225887
- import { existsSync as existsSync35, mkdtempSync as mkdtempSync6, readdirSync as readdirSync16, rmSync as rmSync12, statSync as statSync14 } from "node:fs";
226061
+ import { existsSync as existsSync36, mkdtempSync as mkdtempSync6, readdirSync as readdirSync17, rmSync as rmSync12, statSync as statSync15 } from "node:fs";
225888
226062
  import { tmpdir as tmpdir6 } from "node:os";
225889
- import { extname, join as join37, relative as relative7, resolve as resolve19 } from "node:path";
226063
+ import { extname, join as join38, relative as relative7, resolve as resolve19 } from "node:path";
225890
226064
  init_util();
225891
226065
  var DEFAULT_SIZE = 256;
225892
226066
  var IMAGE_EXT = /\.(png|jpe?g|webp|bmp|gif|tiff?|ktx2)$/i;
@@ -225897,13 +226071,13 @@ var findKtx = () => {
225897
226071
  process.env.LECODES_KTX,
225898
226072
  process.env.KTX_CLI,
225899
226073
  process.env.KTX_TOKTX?.replace(/toktx(\.exe)?$/i, exe),
225900
- process.platform === "win32" ? join37(process.env.LOCALAPPDATA ?? "", "Programs", "KTX-Software", "bin", exe) : null,
225901
- process.platform === "win32" ? join37(process.env.ProgramFiles ?? "", "KTX-Software", "bin", exe) : null,
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,
225902
226076
  "/usr/local/bin/ktx",
225903
226077
  "/opt/homebrew/bin/ktx"
225904
226078
  ].filter((p2) => !!p2);
225905
226079
  for (const path2 of candidates) {
225906
- if (existsSync35(path2))
226080
+ if (existsSync36(path2))
225907
226081
  return path2;
225908
226082
  }
225909
226083
  try {
@@ -225915,8 +226089,8 @@ var findKtx = () => {
225915
226089
  };
225916
226090
  var sharpResizer = () => {
225917
226091
  try {
225918
- const req = createRequire5(import.meta.url);
225919
- const sharp = createRequire5(req.resolve("lecodes-assets/cli"))("sharp");
226092
+ const req = createRequire6(import.meta.url);
226093
+ const sharp = createRequire6(req.resolve("lecodes-assets/cli"))("sharp");
225920
226094
  return async (file, size) => sharp(file).resize({ width: size, height: size, fit: "inside", withoutEnlargement: true }).png().toBuffer();
225921
226095
  } catch {
225922
226096
  return null;
@@ -225935,17 +226109,17 @@ var ktxToPng = (ktx, file, out) => {
225935
226109
  run2(["extract", "--transcode", "rgba8", "--level", "0", file, out]);
225936
226110
  } catch (e2) {
225937
226111
  run2(["extract", "--level", "0", file, out]);
225938
- if (!existsSync35(out))
226112
+ if (!existsSync36(out))
225939
226113
  throw e2;
225940
226114
  }
225941
226115
  };
225942
226116
  var collect = (dir) => {
225943
226117
  const out = [];
225944
- for (const name of readdirSync16(dir)) {
226118
+ for (const name of readdirSync17(dir)) {
225945
226119
  if (name === "node_modules" || name.startsWith("."))
225946
226120
  continue;
225947
- const full = join37(dir, name);
225948
- if (statSync14(full).isDirectory())
226121
+ const full = join38(dir, name);
226122
+ if (statSync15(full).isDirectory())
225949
226123
  out.push(...collect(full));
225950
226124
  else if (IMAGE_EXT.test(name))
225951
226125
  out.push(full);
@@ -225967,9 +226141,9 @@ var thumbs = async (args) => {
225967
226141
  if (args._.length > 0) {
225968
226142
  for (const arg of args._) {
225969
226143
  const abs = resolve19(process.cwd(), arg);
225970
- if (!existsSync35(abs))
226144
+ if (!existsSync36(abs))
225971
226145
  throw new CliError(`No such file or folder: ${arg}`);
225972
- if (statSync14(abs).isDirectory())
226146
+ if (statSync15(abs).isDirectory())
225973
226147
  files.push(...collect(abs));
225974
226148
  else if (IMAGE_EXT.test(abs))
225975
226149
  files.push(abs);
@@ -225989,7 +226163,7 @@ var thumbs = async (args) => {
225989
226163
  const ktx = files.some((f3) => f3.toLowerCase().endsWith(".ktx2")) ? findKtx() : null;
225990
226164
  const sharp = sharpResizer();
225991
226165
  const resize = sharp ?? jimpResizer();
225992
- const tmp = mkdtempSync6(join37(tmpdir6(), "lecodes-thumbs-"));
226166
+ const tmp = mkdtempSync6(join38(tmpdir6(), "lecodes-thumbs-"));
225993
226167
  let made = 0, fresh = 0;
225994
226168
  const skipped = [];
225995
226169
  try {
@@ -226015,7 +226189,7 @@ var thumbs = async (args) => {
226015
226189
  skipped.push(`${asset} (KTX2 needs KTX-Software's \`ktx\` — set LECODES_KTX or put it on PATH)`);
226016
226190
  continue;
226017
226191
  }
226018
- source = join37(tmp, `${i2}.png`);
226192
+ source = join38(tmp, `${i2}.png`);
226019
226193
  ktxToPng(ktx, file, source);
226020
226194
  }
226021
226195
  sceneServer.writePreviewFile(root, asset, await resize(source, size));
@@ -226035,16 +226209,16 @@ var thumbs = async (args) => {
226035
226209
  if (rendered.length > 0) {
226036
226210
  warnErr(`Models, materials and prefabs are RENDERED, not decoded — the scene editor does those: ${rendered.join(", ")}`);
226037
226211
  }
226038
- note(`Cache: ${join37(root, ".lecodes", "preview")} (re-runs skip everything unchanged)`);
226212
+ note(`Cache: ${join38(root, ".lecodes", "preview")} (re-runs skip everything unchanged)`);
226039
226213
  };
226040
226214
 
226041
226215
  // src/commands/assets.ts
226042
226216
  init_util();
226043
226217
 
226044
226218
  // src/cmgenTool.ts
226045
- import { existsSync as existsSync36, readdirSync as readdirSync17 } from "node:fs";
226219
+ import { existsSync as existsSync37, readdirSync as readdirSync18 } from "node:fs";
226046
226220
  import { homedir as homedir8 } from "node:os";
226047
- import { join as join38, resolve as resolve20 } from "node:path";
226221
+ import { join as join39, resolve as resolve20 } from "node:path";
226048
226222
  init_util();
226049
226223
  var exeName2 = () => process.platform === "win32" ? "cmgen.exe" : "cmgen";
226050
226224
  var platformKey3 = () => {
@@ -226054,10 +226228,10 @@ var platformKey3 = () => {
226054
226228
  return "linux-x64";
226055
226229
  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}.`);
226056
226230
  };
226057
- var cacheRoot3 = () => join38(homedir8(), ".lecodes", "cmgen");
226231
+ var cacheRoot3 = () => join39(homedir8(), ".lecodes", "cmgen");
226058
226232
  var findCmgenIn = (dir) => {
226059
- for (const entry of readdirSync17(dir, { withFileTypes: true })) {
226060
- const p2 = join38(dir, entry.name);
226233
+ for (const entry of readdirSync18(dir, { withFileTypes: true })) {
226234
+ const p2 = join39(dir, entry.name);
226061
226235
  if (entry.isFile() && entry.name === exeName2())
226062
226236
  return p2;
226063
226237
  if (entry.isDirectory()) {
@@ -226070,7 +226244,7 @@ var findCmgenIn = (dir) => {
226070
226244
  };
226071
226245
  var installedCmgenVersions = () => {
226072
226246
  try {
226073
- return readdirSync17(cacheRoot3(), { withFileTypes: true }).filter((e2) => e2.isDirectory() && e2.name.startsWith("cmgen-")).map((e2) => e2.name.slice("cmgen-".length)).filter((v) => findCmgenIn(join38(cacheRoot3(), `cmgen-${v}`)) !== null).sort(semverNewer);
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);
226074
226248
  } catch {
226075
226249
  return [];
226076
226250
  }
@@ -226092,19 +226266,19 @@ var downloadCmgen = async (version, versions, dest) => {
226092
226266
  var resolveCmgenExeCached = () => {
226093
226267
  const override = process.env.LECODES_CMGEN;
226094
226268
  if (override) {
226095
- if (!existsSync36(override))
226269
+ if (!existsSync37(override))
226096
226270
  throw new CliError(`LECODES_CMGEN points at "${override}" which doesn't exist.`);
226097
226271
  return resolve20(override);
226098
226272
  }
226099
226273
  const cached = installedCmgenVersions()[0];
226100
- return cached ? findCmgenIn(join38(cacheRoot3(), `cmgen-${cached}`)) : null;
226274
+ return cached ? findCmgenIn(join39(cacheRoot3(), `cmgen-${cached}`)) : null;
226101
226275
  };
226102
226276
  var resolveCmgenExe = async () => {
226103
226277
  const cached = resolveCmgenExeCached();
226104
226278
  if (cached)
226105
226279
  return cached;
226106
226280
  const { latest, versions } = await fetchCmgenManifest();
226107
- const dir = join38(cacheRoot3(), `cmgen-${latest}`);
226281
+ const dir = join39(cacheRoot3(), `cmgen-${latest}`);
226108
226282
  await downloadCmgen(latest, versions, dir);
226109
226283
  const exe = findCmgenIn(dir);
226110
226284
  if (!exe)
@@ -226162,8 +226336,8 @@ var status3 = async (_args) => {
226162
226336
  };
226163
226337
 
226164
226338
  // src/commands/test.ts
226165
- import { existsSync as existsSync37, mkdirSync as mkdirSync26, readFileSync as readFileSync33, readdirSync as readdirSync18 } from "node:fs";
226166
- import { dirname as dirname18, resolve as resolve21 } from "node:path";
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";
226167
226341
  init_manifest();
226168
226342
  init_util();
226169
226343
  var deriveDesignFlows = (meta, entryScreen) => {
@@ -226240,7 +226414,7 @@ var readFlowFile = (abs, displayName) => {
226240
226414
  name: file.name ?? displayName,
226241
226415
  kind: "file",
226242
226416
  steps: file.steps,
226243
- fixtures: resolveFixtures(file.fixtures, dirname18(abs)),
226417
+ fixtures: resolveFixtures(file.fixtures, dirname19(abs)),
226244
226418
  width: file.width,
226245
226419
  height: file.height,
226246
226420
  settle: file.settle,
@@ -226289,29 +226463,29 @@ var test = async (args) => {
226289
226463
  if (args._.length > 0) {
226290
226464
  for (const given of args._) {
226291
226465
  const candidates = [resolve21(given), resolve21(testsDir, given), resolve21(testsDir, `${given}.flow.json`)];
226292
- const abs = candidates.find((p2) => existsSync37(p2));
226466
+ const abs = candidates.find((p2) => existsSync38(p2));
226293
226467
  if (!abs)
226294
226468
  throw new CliError(`No such test: "${given}" (looked in ${testsDir}).`);
226295
226469
  cases.push(readFlowFile(abs, given));
226296
226470
  }
226297
- } else if (existsSync37(testsDir)) {
226298
- for (const f3 of readdirSync18(testsDir).filter((f4) => f4.endsWith(".flow.json")).sort()) {
226471
+ } else if (existsSync38(testsDir)) {
226472
+ for (const f3 of readdirSync19(testsDir).filter((f4) => f4.endsWith(".flow.json")).sort()) {
226299
226473
  cases.push(readFlowFile(resolve21(testsDir, f3), f3));
226300
226474
  }
226301
226475
  }
226302
226476
  const fixturesFlag = flagStr(args, "fixtures");
226303
226477
  const defaultPack = resolve21(testsDir, "fixtures.json");
226304
- const flowFixtures = fixturesFlag !== undefined ? resolveFixtures(fixturesFlag, ".") : existsSync37(defaultPack) ? resolveFixtures("fixtures.json", testsDir) : undefined;
226478
+ const flowFixtures = fixturesFlag !== undefined ? resolveFixtures(fixturesFlag, ".") : existsSync38(defaultPack) ? resolveFixtures("fixtures.json", testsDir) : undefined;
226305
226479
  const flowsOnly = flagBool(args, "flows");
226306
226480
  const wantFlows = !flagBool(args, "no-flows") && args._.length === 0;
226307
226481
  if (flowsOnly)
226308
226482
  cases.length = 0;
226309
226483
  let skipped = [];
226310
226484
  let entryScreen = null;
226311
- if (desktop2 && wantFlows && existsSync37(designMetaPath)) {
226485
+ if (desktop2 && wantFlows && existsSync38(designMetaPath)) {
226312
226486
  note("Design-derived flows skipped with --desktop (they tap by name; run `lecodes test` headless for them).");
226313
226487
  }
226314
- if (!desktop2 && (wantFlows || flowsOnly) && existsSync37(designMetaPath)) {
226488
+ if (!desktop2 && (wantFlows || flowsOnly) && existsSync38(designMetaPath)) {
226315
226489
  let meta;
226316
226490
  try {
226317
226491
  meta = JSON.parse(readFileSync33(designMetaPath, "utf8"));
@@ -226353,11 +226527,12 @@ var test = async (args) => {
226353
226527
  const plan = translateScenario(kase.steps, { outDir: caseDir, headroomFrames: nativeFrames });
226354
226528
  for (const st of plan.steps)
226355
226529
  if (st.screenshot)
226356
- mkdirSync26(dirname18(st.screenshot), { recursive: true });
226530
+ mkdirSync26(dirname19(st.screenshot), { recursive: true });
226357
226531
  mkdirSync26(caseDir, { recursive: true });
226358
226532
  const finalPng = resolve21(caseDir, "final.png");
226359
226533
  const { output } = await runDesktopRender(native.js, native.resources, {
226360
226534
  renderer: desktopRenderer,
226535
+ pluginDirs: desktopPluginLibraries(root).dirs,
226361
226536
  width: Number(flagStr(args, "width")) || kase.width || projectViewport?.width || 390,
226362
226537
  height: Number(flagStr(args, "height")) || kase.height || projectViewport?.height || 844,
226363
226538
  frames: plan.frameCap,
@@ -226478,15 +226653,15 @@ var test = async (args) => {
226478
226653
 
226479
226654
  // src/commands/update.ts
226480
226655
  import { spawnSync as spawnSync7 } from "node:child_process";
226481
- import { existsSync as existsSync39, readdirSync as readdirSync19 } from "node:fs";
226656
+ import { existsSync as existsSync40, readdirSync as readdirSync20 } from "node:fs";
226482
226657
  import { homedir as homedir9 } from "node:os";
226483
- import { dirname as dirname20, join as join40, resolve as resolve23 } from "node:path";
226658
+ import { dirname as dirname21, join as join41, resolve as resolve23 } from "node:path";
226484
226659
  init_util();
226485
226660
 
226486
226661
  // src/peers.ts
226487
- import { existsSync as existsSync38, readFileSync as readFileSync34 } from "node:fs";
226488
- import { createRequire as createRequire6 } from "node:module";
226489
- import { dirname as dirname19, join as join39 } from "node:path";
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";
226490
226665
  var OPTIONAL_PEERS = [
226491
226666
  ["lecodes-renderer", "lecodes-renderer/headless"],
226492
226667
  ["lecodes-design", "lecodes-design/server"],
@@ -226495,16 +226670,16 @@ var OPTIONAL_PEERS = [
226495
226670
  ];
226496
226671
  var peerVersion = (name, probe) => {
226497
226672
  try {
226498
- const require2 = createRequire6(import.meta.url);
226499
- let dir = dirname19(require2.resolve(probe));
226673
+ const require2 = createRequire7(import.meta.url);
226674
+ let dir = dirname20(require2.resolve(probe));
226500
226675
  for (let depth = 0;depth < 16; depth++) {
226501
- const manifest = join39(dir, "package.json");
226502
- if (existsSync38(manifest)) {
226676
+ const manifest = join40(dir, "package.json");
226677
+ if (existsSync39(manifest)) {
226503
226678
  const parsed = JSON.parse(readFileSync34(manifest, "utf8"));
226504
226679
  if (parsed.name === name)
226505
226680
  return parsed.version ?? "unknown";
226506
226681
  }
226507
- const parent = dirname19(dir);
226682
+ const parent = dirname20(dir);
226508
226683
  if (parent === dir)
226509
226684
  break;
226510
226685
  dir = parent;
@@ -226586,9 +226761,9 @@ var findProjectDir = (start) => {
226586
226761
  const home = resolve23(homedir9());
226587
226762
  let dir = resolve23(start);
226588
226763
  while (true) {
226589
- if (dir !== home && (existsSync39(join40(dir, "app.json")) || existsSync39(join40(dir, ".lecodes", "manifest.json")) || existsSync39(join40(dir, ".lecodes", "types"))))
226764
+ if (dir !== home && (existsSync40(join41(dir, "app.json")) || existsSync40(join41(dir, ".lecodes", "manifest.json")) || existsSync40(join41(dir, ".lecodes", "types"))))
226590
226765
  return dir;
226591
- const parent = dirname20(dir);
226766
+ const parent = dirname21(dir);
226592
226767
  if (parent === dir)
226593
226768
  return null;
226594
226769
  dir = parent;
@@ -226616,7 +226791,7 @@ var semverNewer2 = (a2, b) => {
226616
226791
  return 0;
226617
226792
  };
226618
226793
  var FROM_SOURCE = /[\\/]src[\\/]commands[\\/]/.test(import.meta.url);
226619
- var STANDALONE = existsSync39(join40(dirname20(process.execPath), "runtime")) && existsSync39(join40(dirname20(process.execPath), process.platform === "win32" ? "chisel.exe" : "chisel"));
226794
+ var STANDALONE = existsSync40(join41(dirname21(process.execPath), "runtime")) && existsSync40(join41(dirname21(process.execPath), process.platform === "win32" ? "chisel.exe" : "chisel"));
226620
226795
  var update = async (args) => {
226621
226796
  const steps = [];
226622
226797
  const heading = (name, what) => log(`
@@ -226665,7 +226840,7 @@ ${c.bold(name)} ${c.dim(what)}`);
226665
226840
  return ["skipped", "not inside a project"];
226666
226841
  if (flagBool(args, "no-shells"))
226667
226842
  return ["skipped", "--no-shells"];
226668
- const present = ["ios", "android"].filter((d) => existsSync39(join40(root, d)) && readdirSync19(join40(root, d)).length > 0);
226843
+ const present = ["ios", "android"].filter((d) => existsSync40(join41(root, d)) && readdirSync20(join41(root, d)).length > 0);
226669
226844
  if (present.length === 0)
226670
226845
  return ["skipped", "no ios/ or android/ shell in this project (`lecodes app init ios|android`)"];
226671
226846
  await app({ _: ["update"], flags: {} });
@@ -226720,15 +226895,15 @@ ${c.bold("Summary")}`);
226720
226895
  };
226721
226896
 
226722
226897
  // src/index.ts
226723
- import { existsSync as existsSync42, realpathSync as realpathSync2 } from "node:fs";
226724
- import { delimiter as delimiter2, dirname as dirname23, join as join43 } from "node:path";
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";
226725
226900
 
226726
226901
  // src/commands/install.ts
226727
226902
  init_util();
226728
226903
  import { execFileSync as execFileSync8 } from "node:child_process";
226729
- import { appendFileSync as appendFileSync3, chmodSync as chmodSync2, cpSync as cpSync5, existsSync as existsSync40, mkdirSync as mkdirSync27, readFileSync as readFileSync35, realpathSync, rmSync as rmSync13, symlinkSync as symlinkSync2 } from "node:fs";
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";
226730
226905
  import { homedir as homedir10 } from "node:os";
226731
- import { basename as basename18, delimiter, dirname as dirname21, join as join41, resolve as resolve24, sep as sep2 } from "node:path";
226906
+ import { basename as basename19, delimiter as delimiter2, dirname as dirname22, join as join42, resolve as resolve24, sep as sep2 } from "node:path";
226732
226907
  var isWin = process.platform === "win32";
226733
226908
  var HOME = homedir10();
226734
226909
  var safeRealpath = (p2) => {
@@ -226740,7 +226915,7 @@ var safeRealpath = (p2) => {
226740
226915
  };
226741
226916
  var onPath = (dir) => {
226742
226917
  const want = safeRealpath(dir);
226743
- return (process.env.PATH ?? "").split(delimiter).some((p2) => {
226918
+ return (process.env.PATH ?? "").split(delimiter2).some((p2) => {
226744
226919
  if (!p2)
226745
226920
  return false;
226746
226921
  try {
@@ -226751,13 +226926,13 @@ var onPath = (dir) => {
226751
226926
  });
226752
226927
  };
226753
226928
  var install = async (args) => {
226754
- const bundle = dirname21(process.execPath);
226755
- const exeName3 = basename18(process.execPath);
226929
+ const bundle = dirname22(process.execPath);
226930
+ const exeName3 = basename19(process.execPath);
226756
226931
  const chiselName = isWin ? "chisel.exe" : "chisel";
226757
- if (!existsSync40(join41(bundle, "runtime")) || !existsSync40(join41(bundle, chiselName))) {
226932
+ if (!existsSync41(join42(bundle, "runtime")) || !existsSync41(join42(bundle, chiselName))) {
226758
226933
  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.");
226759
226934
  }
226760
- const companions = [exeName3, chiselName, "runtime", "README.txt"].filter((f3) => existsSync40(join41(bundle, f3)));
226935
+ const companions = [exeName3, chiselName, "runtime", "README.txt"].filter((f3) => existsSync41(join42(bundle, f3)));
226761
226936
  if (isWin)
226762
226937
  return installWindows(args, bundle, companions);
226763
226938
  return installUnix(args, bundle, companions);
@@ -226767,9 +226942,9 @@ var copyBundle = (bundle, target, companions) => {
226767
226942
  if (safeRealpath(target) === safeRealpath(bundle))
226768
226943
  return;
226769
226944
  for (const f3 of companions) {
226770
- const dest = join41(target, f3);
226945
+ const dest = join42(target, f3);
226771
226946
  rmSync13(dest, { recursive: true, force: true });
226772
- cpSync5(join41(bundle, f3), dest, { recursive: true });
226947
+ cpSync5(join42(bundle, f3), dest, { recursive: true });
226773
226948
  }
226774
226949
  };
226775
226950
  var permError = (e2, dir, system) => {
@@ -226781,41 +226956,41 @@ var permError = (e2, dir, system) => {
226781
226956
  };
226782
226957
  var installUnix = (args, bundle, companions) => {
226783
226958
  const system = flagBool(args, "system");
226784
- const libDir = resolve24(flagStr(args, "dir") ?? (system ? "/usr/local/lib/lecodes-cli" : join41(HOME, ".local", "lib", "lecodes-cli")));
226785
- const binDir = resolve24(flagStr(args, "bin") ?? (system ? "/usr/local/bin" : join41(HOME, ".local", "bin")));
226786
- const link2 = join41(binDir, "lecodes");
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");
226787
226962
  try {
226788
226963
  copyBundle(bundle, libDir, companions);
226789
- chmodSync2(join41(libDir, "lecodes"), 493);
226790
- chmodSync2(join41(libDir, "chisel"), 493);
226964
+ chmodSync2(join42(libDir, "lecodes"), 493);
226965
+ chmodSync2(join42(libDir, "chisel"), 493);
226791
226966
  } catch (e2) {
226792
226967
  throw permError(e2, libDir, system);
226793
226968
  }
226794
226969
  try {
226795
226970
  mkdirSync27(binDir, { recursive: true });
226796
226971
  rmSync13(link2, { force: true });
226797
- symlinkSync2(join41(libDir, "lecodes"), link2);
226972
+ symlinkSync2(join42(libDir, "lecodes"), link2);
226798
226973
  } catch (e2) {
226799
226974
  throw permError(e2, binDir, system);
226800
226975
  }
226801
226976
  success(`Installed to ${c.bold(libDir)}`);
226802
- log(` ${c.dim(link2 + " → " + join41(libDir, "lecodes"))}`);
226977
+ log(` ${c.dim(link2 + " → " + join42(libDir, "lecodes"))}`);
226803
226978
  finishPathUnix(args, binDir);
226804
226979
  };
226805
226980
  var shellRc = () => {
226806
- const shell = basename18(process.env.SHELL ?? "");
226981
+ const shell = basename19(process.env.SHELL ?? "");
226807
226982
  if (shell === "zsh")
226808
- return { file: join41(HOME, ".zshrc"), name: "~/.zshrc" };
226983
+ return { file: join42(HOME, ".zshrc"), name: "~/.zshrc" };
226809
226984
  if (shell === "bash")
226810
- return { file: join41(HOME, ".bashrc"), name: "~/.bashrc" };
226811
- return { file: join41(HOME, ".profile"), name: "~/.profile" };
226985
+ return { file: join42(HOME, ".bashrc"), name: "~/.bashrc" };
226986
+ return { file: join42(HOME, ".profile"), name: "~/.profile" };
226812
226987
  };
226813
226988
  var PATH_MARKER = "# Added by `lecodes install`";
226814
226989
  var appendPathToShellRc = (binDir) => {
226815
226990
  const { file, name } = shellRc();
226816
226991
  const line = `export PATH="${binDir}:$PATH"`;
226817
226992
  try {
226818
- const existing = existsSync40(file) ? readFileSync35(file, "utf8") : "";
226993
+ const existing = existsSync41(file) ? readFileSync35(file, "utf8") : "";
226819
226994
  if (existing.includes(line))
226820
226995
  return name;
226821
226996
  appendFileSync3(file, `${existing && !existing.endsWith(`
@@ -226853,7 +227028,7 @@ var finishPathUnix = (args, binDir) => {
226853
227028
  log(` • or install system-wide (no PATH edit): ${c.bold("sudo lecodes install --system")}`);
226854
227029
  };
226855
227030
  var installWindows = (args, bundle, companions) => {
226856
- const target = resolve24(flagStr(args, "dir") ?? join41(process.env.LOCALAPPDATA ?? join41(HOME, "AppData", "Local"), "Programs", "lecodes-cli"));
227031
+ const target = resolve24(flagStr(args, "dir") ?? join42(process.env.LOCALAPPDATA ?? join42(HOME, "AppData", "Local"), "Programs", "lecodes-cli"));
226857
227032
  try {
226858
227033
  copyBundle(bundle, target, companions);
226859
227034
  } catch (e2) {
@@ -226892,9 +227067,9 @@ var addToUserPathWindows = (dir) => {
226892
227067
  init_util();
226893
227068
 
226894
227069
  // src/projectEnv.ts
226895
- import { existsSync as existsSync41, readFileSync as readFileSync36 } from "node:fs";
227070
+ import { existsSync as existsSync42, readFileSync as readFileSync36 } from "node:fs";
226896
227071
  import { homedir as homedir11 } from "node:os";
226897
- import { dirname as dirname22, join as join42, resolve as resolve25 } from "node:path";
227072
+ import { dirname as dirname23, join as join43, resolve as resolve25 } from "node:path";
226898
227073
  var KEY_RE = /^(LECODES|CREATOR)_[A-Z0-9_]+$/;
226899
227074
  var parseDotEnv = (text) => {
226900
227075
  const out = {};
@@ -226931,7 +227106,7 @@ var applyDotEnv = (parsed, dir, env) => {
226931
227106
  if (value.startsWith("./") || value.startsWith("../"))
226932
227107
  value = resolve25(dir, value);
226933
227108
  else if (value === "~" || value.startsWith("~/"))
226934
- value = join42(homedir11(), value.slice(1));
227109
+ value = join43(homedir11(), value.slice(1));
226935
227110
  out[key] = value;
226936
227111
  }
226937
227112
  return out;
@@ -226939,9 +227114,9 @@ var applyDotEnv = (parsed, dir, env) => {
226939
227114
  var findEnvRoot = (start) => {
226940
227115
  let dir = resolve25(start);
226941
227116
  while (true) {
226942
- if (existsSync41(join42(dir, "app.json")) || existsSync41(join42(dir, ".lecodes")))
227117
+ if (existsSync42(join43(dir, "app.json")) || existsSync42(join43(dir, ".lecodes")))
226943
227118
  return dir;
226944
- const parent = dirname22(dir);
227119
+ const parent = dirname23(dir);
226945
227120
  if (parent === dir)
226946
227121
  return null;
226947
227122
  dir = parent;
@@ -226952,8 +227127,8 @@ var loadProjectEnv = (cwd = process.cwd()) => {
226952
227127
  const root = findEnvRoot(cwd);
226953
227128
  if (!root)
226954
227129
  return [];
226955
- const file = join42(root, ".env");
226956
- if (!existsSync41(file))
227130
+ const file = join43(root, ".env");
227131
+ if (!existsSync42(file))
226957
227132
  return [];
226958
227133
  const added = applyDotEnv(parseDotEnv(readFileSync36(file, "utf8")), root, process.env);
226959
227134
  for (const [k, v] of Object.entries(added))
@@ -226966,9 +227141,9 @@ var loadProjectEnv = (cwd = process.cwd()) => {
226966
227141
 
226967
227142
  // src/index.ts
226968
227143
  var STANDALONE2 = (() => {
226969
- const dir = dirname23(process.execPath);
227144
+ const dir = dirname24(process.execPath);
226970
227145
  const chisel = process.platform === "win32" ? "chisel.exe" : "chisel";
226971
- return existsSync42(join43(dir, "runtime")) && existsSync42(join43(dir, chisel));
227146
+ return existsSync43(join44(dir, "runtime")) && existsSync43(join44(dir, chisel));
226972
227147
  })();
226973
227148
  var INSTALLED = STANDALONE2 && (() => {
226974
227149
  const exe = process.platform === "win32" ? "lecodes.exe" : "lecodes";
@@ -226979,12 +227154,12 @@ var INSTALLED = STANDALONE2 && (() => {
226979
227154
  return process.execPath;
226980
227155
  }
226981
227156
  })();
226982
- return (process.env.PATH ?? "").split(delimiter2).some((d) => {
227157
+ return (process.env.PATH ?? "").split(delimiter3).some((d) => {
226983
227158
  if (!d)
226984
227159
  return false;
226985
227160
  try {
226986
- const cand = join43(d, exe);
226987
- return existsSync42(cand) && realpathSync2(cand) === self2;
227161
+ const cand = join44(d, exe);
227162
+ return existsSync43(cand) && realpathSync2(cand) === self2;
226988
227163
  } catch {
226989
227164
  return false;
226990
227165
  }
@@ -227052,6 +227227,9 @@ ${c.bold("Commands:")}
227052
227227
  --host <ip> LAN address to advertise (default: auto-detect)
227053
227228
  --entry <path> override the entrypoint
227054
227229
  --no-qr don't render the QR code
227230
+ --web also open the app in the browser (Web Lite host:
227231
+ same bundle + hot reload, camera/map plugins
227232
+ have web twins; the /web/ URL is served always)
227055
227233
  --desktop also open the app in the native desktop host
227056
227234
  (same hot reload; window from app.json's
227057
227235
  desktop block; closing it keeps the server)