lecodes-cli 0.10.0 → 0.10.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -47,6 +47,41 @@ var __export = (target, all) => {
47
47
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
48
48
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
49
49
 
50
+ // ../sdk/src/compile/assetName.ts
51
+ var inRanges = (cp, ranges) => ranges.some(([from, to]) => cp >= from && cp <= to), INVISIBLE, ODD_SPACE, ILLEGAL = '/\\:*?"<>|', sanitizeAssetName = (name) => {
52
+ let out = "";
53
+ for (const ch of name.normalize("NFC")) {
54
+ const cp = ch.codePointAt(0);
55
+ if (inRanges(cp, INVISIBLE))
56
+ continue;
57
+ out += inRanges(cp, ODD_SPACE) ? " " : ILLEGAL.includes(ch) ? "_" : ch;
58
+ }
59
+ return out.replace(/ {2,}/g, " ").replace(/^ +/, "").replace(/[ .]+$/, "");
60
+ }, isCanonicalAssetName = (name) => sanitizeAssetName(name) === name;
61
+ var init_assetName = __esm(() => {
62
+ INVISIBLE = [
63
+ [0, 31],
64
+ [127, 159],
65
+ [173, 173],
66
+ [1564, 1564],
67
+ [6158, 6158],
68
+ [8203, 8207],
69
+ [8232, 8233],
70
+ [8234, 8238],
71
+ [8288, 8292],
72
+ [8294, 8303],
73
+ [65279, 65279]
74
+ ];
75
+ ODD_SPACE = [
76
+ [160, 160],
77
+ [5760, 5760],
78
+ [8192, 8202],
79
+ [8239, 8239],
80
+ [8287, 8287],
81
+ [12288, 12288]
82
+ ];
83
+ });
84
+
50
85
  // ../sdk/src/compile/assetMacro.ts
51
86
  var ASSET_RE, hasAssetMacro = (src) => src.includes("asset("), desugarAssetMacros = (src, filePath) => {
52
87
  const byPath = new Map;
@@ -129,18 +164,18 @@ var init_libraryImports = __esm(() => {
129
164
  });
130
165
 
131
166
  // ../sdk/src/compile/bundler.ts
132
- import { existsSync as existsSync4, readdirSync as readdirSync2, readFileSync as readFileSync7, statSync } from "node:fs";
167
+ import { existsSync as existsSync5, readdirSync as readdirSync2, readFileSync as readFileSync7, statSync } from "node:fs";
133
168
  import { spawnSync } from "node:child_process";
134
169
  import { createRequire as createRequire2 } from "node:module";
135
170
  import { dirname as dirname4, join as join7, relative as relative2 } from "node:path";
136
171
  import { fileURLToPath as fileURLToPath2 } from "node:url";
137
172
  var resolveInject = () => {
138
173
  const override = process.env.LECODES_SDK_INJECT;
139
- if (override && existsSync4(override))
174
+ if (override && existsSync5(override))
140
175
  return override;
141
176
  const here = dirname4(fileURLToPath2(import.meta.url));
142
177
  const src = join7(here, "..", "inject.ts");
143
- if (existsSync4(src))
178
+ if (existsSync5(src))
144
179
  return src;
145
180
  throw new Error("sdk: could not locate the inject source (LECODES_SDK_INJECT / src/inject.ts)");
146
181
  }, normalize = (path) => {
@@ -180,13 +215,13 @@ var resolveInject = () => {
180
215
  }, resolveChiselBin = () => {
181
216
  const override = process.env.LECODES_CHISEL_BIN;
182
217
  if (override) {
183
- if (existsSync4(override))
218
+ if (existsSync5(override))
184
219
  return override;
185
220
  throw new Error(`LECODES_CHISEL_BIN points at a missing file: ${override}`);
186
221
  }
187
222
  try {
188
223
  const bin = createRequire2(import.meta.url)("@letary/chisel").binaryPath();
189
- if (bin && existsSync4(bin))
224
+ if (bin && existsSync5(bin))
190
225
  return bin;
191
226
  } catch {}
192
227
  throw new Error("chisel binary not found. Install @letary/chisel, or set LECODES_CHISEL_BIN.");
@@ -247,7 +282,7 @@ var resolveInject = () => {
247
282
  inject: ["/__sdk/inject.ts"],
248
283
  format,
249
284
  minify,
250
- fuse: process.env.LECODES_CHISEL_FUSE === "1",
285
+ fuse: process.env.LECODES_CHISEL_FUSE !== "0",
251
286
  define: { ...CHISEL_DEFINE, EDITOR: editor ? "true" : "false" },
252
287
  keep: CHISEL_KEEP,
253
288
  sourcemap,
@@ -268,7 +303,10 @@ var resolveInject = () => {
268
303
  throw new Error(`chisel: ${parsed.error}`);
269
304
  if (parsed.code == null)
270
305
  throw new Error("chisel produced no code");
271
- return { code: parsed.code, map: parsed.map ?? null };
306
+ const diagnostics = parsed.diagnostics ?? [];
307
+ for (const d of diagnostics)
308
+ console.warn(`warning: ${d}`);
309
+ return { code: parsed.code, map: parsed.map ?? null, diagnostics };
272
310
  }, bundleProject = async (entrypoint, files, format = "esm") => (await bundleProjectChisel(entrypoint, files, format, false)).code, bundleProjectWithMap = async (entrypoint, files, format = "esm", minify, editor, libraries, sourcemap = true) => bundleProjectChisel(entrypoint, files, format, sourcemap, minify, editor, libraries);
273
311
  var init_bundler = __esm(() => {
274
312
  init_assetMacro();
@@ -459,11 +497,11 @@ var init_compileProject = __esm(() => {
459
497
  });
460
498
 
461
499
  // ../sdk/src/compile/sceneEditor.ts
462
- import { existsSync as existsSync5, readdirSync as readdirSync3, readFileSync as readFileSync8 } from "node:fs";
500
+ import { existsSync as existsSync6, readdirSync as readdirSync3, readFileSync as readFileSync8 } from "node:fs";
463
501
  import { join as join8 } from "node:path";
464
502
  var SCENE_HARNESS_DIR = "./.scene-editor", SCENE_ENTRY_PATH = "./.scene-entry.ts", loadSceneHarness = (fallbackDir) => {
465
503
  const override = process.env.LECODES_SCENE_HARNESS_DIR;
466
- const dir = override && existsSync5(override) ? override : fallbackDir && existsSync5(fallbackDir) ? fallbackDir : null;
504
+ const dir = override && existsSync6(override) ? override : fallbackDir && existsSync6(fallbackDir) ? fallbackDir : null;
467
505
  if (!dir)
468
506
  throw new Error("scene-editor harness sources not found (set LECODES_SCENE_HARNESS_DIR)");
469
507
  const out = {};
@@ -1040,9 +1078,11 @@ __export(exports_compile, {
1040
1078
  scanModulesFallback: () => scanModulesFallback,
1041
1079
  scanModulesChisel: () => scanModulesChisel,
1042
1080
  scanFontCalls: () => scanFontCalls,
1081
+ sanitizeAssetName: () => sanitizeAssetName,
1043
1082
  parseSfnt: () => parseSfnt,
1044
1083
  offsetSourceMap: () => offsetSourceMap,
1045
1084
  loadSceneHarness: () => loadSceneHarness,
1085
+ isCanonicalAssetName: () => isCanonicalAssetName,
1046
1086
  inlineSourceMapComment: () => inlineSourceMapComment,
1047
1087
  fontRegistryBase: () => fontRegistryBase,
1048
1088
  faceFileName: () => faceFileName,
@@ -1063,6 +1103,7 @@ __export(exports_compile, {
1063
1103
  var init_compile = __esm(() => {
1064
1104
  init_bundler();
1065
1105
  init_detectEntry();
1106
+ init_assetName();
1066
1107
  init_compileProject();
1067
1108
  init_sceneEditor();
1068
1109
  init_fontMacro();
@@ -2088,9 +2129,9 @@ var require_main = __commonJS((exports, module) => {
2088
2129
  // package.json
2089
2130
  var package_default = {
2090
2131
  name: "lecodes-cli",
2091
- version: "0.10.0",
2132
+ version: "0.10.2",
2092
2133
  dependencies: {
2093
- "@letary/chisel": "^0.5.1"
2134
+ "@letary/chisel": "^0.6.0"
2094
2135
  },
2095
2136
  devDependencies: {
2096
2137
  "lecodes-design": "workspace:*",
@@ -2147,7 +2188,7 @@ var package_default = {
2147
2188
 
2148
2189
  // src/commands/app.ts
2149
2190
  import { execFileSync } from "node:child_process";
2150
- import { cpSync, existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync10, readdirSync as readdirSync4, rmSync, statSync as statSync2, writeFileSync as writeFileSync7 } from "node:fs";
2191
+ import { cpSync, existsSync as existsSync9, mkdirSync as mkdirSync5, readFileSync as readFileSync10, readdirSync as readdirSync4, rmSync, statSync as statSync2, writeFileSync as writeFileSync7 } from "node:fs";
2151
2192
  import { homedir as homedir2 } from "node:os";
2152
2193
  import { basename as basename4, dirname as dirname7, join as join11, resolve as resolve2 } from "node:path";
2153
2194
 
@@ -2156,6 +2197,7 @@ import { dirname, join } from "node:path";
2156
2197
  import { existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs";
2157
2198
 
2158
2199
  // src/util.ts
2200
+ init_assetName();
2159
2201
  import { createHash } from "node:crypto";
2160
2202
  import { createInterface } from "node:readline";
2161
2203
  var useColor = process.stdout.isTTY && process.env.NO_COLOR === undefined;
@@ -2201,7 +2243,10 @@ var toKey = (path) => {
2201
2243
  const parts = path.replace(/\\/g, "/").split("/").map((p) => p.trim()).filter((p) => p && p !== ".");
2202
2244
  if (parts.some((p) => p === ".."))
2203
2245
  throw new CliError(`Unsafe path: ${path}`);
2204
- return "/" + parts.join("/");
2246
+ const names = parts.map((p) => sanitizeAssetName(p));
2247
+ if (names.some((name) => !name))
2248
+ throw new CliError(`Wrong path: ${path}`);
2249
+ return "/" + names.join("/");
2205
2250
  };
2206
2251
  var prompt = (question, fallback) => new Promise((resolve) => {
2207
2252
  const rl = createInterface({ input: process.stdin, output: process.stdout });
@@ -2490,7 +2535,8 @@ import { readFileSync as readFileSync5 } from "node:fs";
2490
2535
  import { basename } from "node:path";
2491
2536
 
2492
2537
  // src/localFiles.ts
2493
- import { readdirSync, readFileSync as readFileSync4 } from "node:fs";
2538
+ init_assetName();
2539
+ import { existsSync as existsSync3, readdirSync, readFileSync as readFileSync4 } from "node:fs";
2494
2540
  import { join as join4, relative } from "node:path";
2495
2541
  var SKIP_DIRS = new Set([".lecodes", ".git", "node_modules"]);
2496
2542
  var SKIP_FILES = new Set(["tsconfig.json", "jsconfig.json", ".gitignore", ".lecodesignore", ".DS_Store"]);
@@ -2572,6 +2618,23 @@ var scanLocal = (root, opts = {}) => {
2572
2618
  walk(root, false);
2573
2619
  return { files, dirs, ignored };
2574
2620
  };
2621
+ var resolveLocalPath = (root, key) => {
2622
+ const direct = join4(root, key.slice(1));
2623
+ if (existsSync3(direct))
2624
+ return direct;
2625
+ let dir = root;
2626
+ try {
2627
+ for (const name of key.slice(1).split("/")) {
2628
+ const match = readdirSync(dir).find((entry) => entry === name || sanitizeAssetName(entry) === name);
2629
+ if (!match)
2630
+ return null;
2631
+ dir = join4(dir, match);
2632
+ }
2633
+ } catch {
2634
+ return null;
2635
+ }
2636
+ return dir;
2637
+ };
2575
2638
  var scanProject = (root, manifest) => scanLocal(root, { ignore: loadIgnore(root), tracked: new Set(Object.keys(manifest.files)) });
2576
2639
  var diffLocal = (manifest, scan) => {
2577
2640
  const added = [];
@@ -2695,12 +2758,12 @@ var collectLocalEntries = (root, manifest, assetBase) => {
2695
2758
  };
2696
2759
 
2697
2760
  // src/compile/projectCompile.ts
2698
- import { existsSync as existsSync6 } from "node:fs";
2761
+ import { existsSync as existsSync7 } from "node:fs";
2699
2762
  import { createRequire as createRequire3 } from "node:module";
2700
2763
  import { dirname as dirname5, join as join9 } from "node:path";
2701
2764
 
2702
2765
  // src/distRoot.ts
2703
- import { existsSync as existsSync3 } from "node:fs";
2766
+ import { existsSync as existsSync4 } from "node:fs";
2704
2767
  import { dirname as dirname3, join as join6 } from "node:path";
2705
2768
  import { fileURLToPath } from "node:url";
2706
2769
  var distRoot = (() => {
@@ -2709,7 +2772,7 @@ var distRoot = (() => {
2709
2772
  dirname3(process.execPath)
2710
2773
  ];
2711
2774
  for (const c2 of candidates)
2712
- if (existsSync3(join6(c2, "runtime")))
2775
+ if (existsSync4(join6(c2, "runtime")))
2713
2776
  return c2;
2714
2777
  return candidates[0];
2715
2778
  })();
@@ -2717,7 +2780,7 @@ var distRoot = (() => {
2717
2780
  // src/compile/projectCompile.ts
2718
2781
  init_compile();
2719
2782
  var vendoredInject = join9(distRoot, "runtime", "sdk", "inject.ts");
2720
- if (existsSync6(vendoredInject))
2783
+ if (existsSync7(vendoredInject))
2721
2784
  process.env.LECODES_SDK_INJECT = vendoredInject;
2722
2785
  var resolveChiselBin2 = () => {
2723
2786
  if (process.env.LECODES_CHISEL_BIN)
@@ -2728,7 +2791,7 @@ var resolveChiselBin2 = () => {
2728
2791
  return fromPkg;
2729
2792
  } catch {}
2730
2793
  const local = join9(dirname5(process.execPath), process.platform === "win32" ? "chisel.exe" : "chisel");
2731
- return existsSync6(local) ? local : null;
2794
+ return existsSync7(local) ? local : null;
2732
2795
  };
2733
2796
  var chiselBin = resolveChiselBin2();
2734
2797
  if (chiselBin)
@@ -2756,7 +2819,7 @@ var compileProject2 = (opts) => compileProject({
2756
2819
  // src/compile/fonts.ts
2757
2820
  init_compile();
2758
2821
  import { createHash as createHash2 } from "node:crypto";
2759
- import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync9, writeFileSync as writeFileSync5 } from "node:fs";
2822
+ import { existsSync as existsSync8, mkdirSync as mkdirSync4, readFileSync as readFileSync9, writeFileSync as writeFileSync5 } from "node:fs";
2760
2823
  import { dirname as dirname6, join as join10 } from "node:path";
2761
2824
  var applyProjectFonts = async (entries, opts) => {
2762
2825
  const registry = (opts.registry ?? fontRegistryBase()).replace(/\/+$/, "");
@@ -2778,7 +2841,7 @@ var applyProjectFonts = async (entries, opts) => {
2778
2841
  const rel = `assets/fonts/${face.fileName}`;
2779
2842
  const abs = join10(vendorDir, rel);
2780
2843
  const entryPath = `${opts.vendorPrefix ?? ""}/${rel}`;
2781
- if (!existsSync7(abs))
2844
+ if (!existsSync8(abs))
2782
2845
  await downloadFace(registry, vendorDir, face, rel, abs);
2783
2846
  if (!byPath().has(entryPath.replace(/^\//, ""))) {
2784
2847
  entries.push(opts.makeResourceEntry?.(entryPath, abs) ?? { path: entryPath, type: "resource", absPath: abs });
@@ -3472,6 +3535,11 @@ ${keys.map((k) => ` <key>${k}</key>
3472
3535
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3473
3536
  <plist version="1.0">
3474
3537
  <dict>
3538
+ <!-- Standard-encryption-only (HTTPS/TLS): declares export-compliance exemption
3539
+ so App Store Connect skips the "Missing Compliance" prompt on every build.
3540
+ Flip to <true/> only if the app adds its own non-exempt cryptography. -->
3541
+ <key>ITSAppUsesNonExemptEncryption</key>
3542
+ <false/>
3475
3543
  <key>UIApplicationSceneManifest</key>
3476
3544
  <dict>
3477
3545
  <key>UIApplicationSupportsMultipleScenes</key>
@@ -3725,7 +3793,7 @@ var FIRST_PARTY_PLUGINS = {
3725
3793
  "qr-scanner": "letary/lecodes-plugins"
3726
3794
  };
3727
3795
  var readJson = (path) => {
3728
- if (!existsSync8(path))
3796
+ if (!existsSync9(path))
3729
3797
  return null;
3730
3798
  try {
3731
3799
  return JSON.parse(readFileSync10(path, "utf8"));
@@ -3765,7 +3833,7 @@ var fetchLatestSdkVersion = async () => {
3765
3833
  };
3766
3834
  var shellSdkVersion = (iosDir) => {
3767
3835
  const path = join11(iosDir, "LeCodesRuntime", "Package.swift");
3768
- if (!existsSync8(path))
3836
+ if (!existsSync9(path))
3769
3837
  return null;
3770
3838
  const m = /lecodes-ios-sdk\.git",\s*(?:exact|from):\s*"([^"]+)"/.exec(readFileSync10(path, "utf8"));
3771
3839
  return m && VERSION_RE.test(m[1]) ? m[1] : null;
@@ -3796,7 +3864,7 @@ var ensureRepo = (repo, wantTag, lock) => {
3796
3864
  }
3797
3865
  const cache = join11(homedir2(), ".lecodes", "plugins", repo.replace("/", "__"));
3798
3866
  const url = `https://github.com/${repo}.git`;
3799
- if (!existsSync8(join11(cache, ".git"))) {
3867
+ if (!existsSync9(join11(cache, ".git"))) {
3800
3868
  mkdirSync5(dirname7(cache), { recursive: true });
3801
3869
  note(`Cloning ${url}…`);
3802
3870
  git(dirname7(cache), "clone", "--quiet", url, cache);
@@ -3869,7 +3937,7 @@ var appInit = async (args) => {
3869
3937
  }
3870
3938
  const root = projectRoot();
3871
3939
  const iosDir = join11(root, "ios");
3872
- if (existsSync8(iosDir)) {
3940
+ if (existsSync9(iosDir)) {
3873
3941
  throw new CliError("ios/ already exists — run `lecodes app sync` to update it.");
3874
3942
  }
3875
3943
  const existing = readJson(join11(root, "app.json")) ?? {};
@@ -3897,7 +3965,7 @@ var appInit = async (args) => {
3897
3965
  writeJson(join11(root, "app.json"), config);
3898
3966
  log(`${c.green("written")} app.json`);
3899
3967
  const ignorePath = join11(root, ".lecodesignore");
3900
- const ignore = existsSync8(ignorePath) ? readFileSync10(ignorePath, "utf8") : DEFAULT_IGNORE_FILE;
3968
+ const ignore = existsSync9(ignorePath) ? readFileSync10(ignorePath, "utf8") : DEFAULT_IGNORE_FILE;
3901
3969
  if (!/^ios\/$/m.test(ignore)) {
3902
3970
  writeFileSync7(ignorePath, ignore.replace(/\n*$/, `
3903
3971
  `) + `
@@ -3974,7 +4042,7 @@ var ensureSdkResourcesBundle = async (sdkVersion) => {
3974
4042
  }
3975
4043
  const dir = join11(homedir2(), ".lecodes", "sdk-resources", sdkVersion);
3976
4044
  const bundle = join11(dir, "LeCodesSDKResources.bundle");
3977
- if (existsSync8(bundle))
4045
+ if (existsSync9(bundle))
3978
4046
  return bundle;
3979
4047
  const url = `https://github.com/${SDK_REPO}/releases/download/${sdkVersion}/LeCodesSDKResources.bundle.zip`;
3980
4048
  note(`Downloading LeCodesSDKResources.bundle (SDK ${sdkVersion})…`);
@@ -3986,7 +4054,7 @@ var ensureSdkResourcesBundle = async (sdkVersion) => {
3986
4054
  writeFileSync7(zip, new Uint8Array(await res.arrayBuffer()));
3987
4055
  execFileSync("ditto", ["-x", "-k", zip, dir]);
3988
4056
  rmSync(zip);
3989
- if (!existsSync8(bundle))
4057
+ if (!existsSync9(bundle))
3990
4058
  throw new CliError(`No LeCodesSDKResources.bundle inside the release asset ${url}`);
3991
4059
  return bundle;
3992
4060
  };
@@ -4013,7 +4081,7 @@ var appSync = async (_args) => {
4013
4081
  const root = projectRoot();
4014
4082
  const config = readJson(join11(root, "app.json"));
4015
4083
  const iosDir = join11(root, "ios");
4016
- if (!config?.ios || !existsSync8(iosDir)) {
4084
+ if (!config?.ios || !existsSync9(iosDir)) {
4017
4085
  throw new CliError("No iOS shell here — run `lecodes app init ios` first.");
4018
4086
  }
4019
4087
  const requestedVariant = config.ios.variant ?? "auto";
@@ -4038,7 +4106,7 @@ var appSync = async (_args) => {
4038
4106
  if (!module || !register)
4039
4107
  throw new CliError(`Plugin "${id}": manifest ios.module / ios.register missing`);
4040
4108
  const src = join11(plugin.dir, ios.sources ?? "ios");
4041
- if (!existsSync8(src))
4109
+ if (!existsSync9(src))
4042
4110
  throw new CliError(`Plugin "${id}": sources dir ${src} missing`);
4043
4111
  const dest = join11(sourcesDir, module);
4044
4112
  rmSync(dest, { recursive: true, force: true });
@@ -4054,7 +4122,7 @@ var appSync = async (_args) => {
4054
4122
  log(`${c.green("plugin")} ${id}@${plugin.lock.version} (${plugin.lock.tag} → Sources/${module})`);
4055
4123
  }
4056
4124
  const keep = new Set(["LeCodesRuntime", ...runtimePlugins.map((p) => p.module)]);
4057
- for (const entry of existsSync8(sourcesDir) ? readdirSync4(sourcesDir) : []) {
4125
+ for (const entry of existsSync9(sourcesDir) ? readdirSync4(sourcesDir) : []) {
4058
4126
  if (!keep.has(entry) && statSync2(join11(sourcesDir, entry)).isDirectory()) {
4059
4127
  rmSync(join11(sourcesDir, entry), { recursive: true });
4060
4128
  log(`${c.yellow("removed")} ios/LeCodesRuntime/Sources/${entry} (plugin uninstalled)`);
@@ -4128,7 +4196,7 @@ var appSync = async (_args) => {
4128
4196
  writeFileSync7(join11(iosDir, "App", "Info.plist"), infoPlistTemplate(mergedKeys, updateUrl));
4129
4197
  if (updateUrl)
4130
4198
  log(`${c.green("update")} OTA source ${updateUrl} — checked natively at launch, new code applies next launch`);
4131
- if (pluginSpecs.length > 0 || existsSync8(lockPath))
4199
+ if (pluginSpecs.length > 0 || existsSync9(lockPath))
4132
4200
  writeJson(lockPath, { plugins: newLock });
4133
4201
  const pbxPath = join11(iosDir, "App.xcodeproj", "project.pbxproj");
4134
4202
  const pbx = readFileSync10(pbxPath, "utf8").replace(/MARKETING_VERSION = [^;]+;/g, `MARKETING_VERSION = ${config.version ?? "1.0.0"};`).replace(/CURRENT_PROJECT_VERSION = [^;]+;/g, `CURRENT_PROJECT_VERSION = ${config.ios.buildNumber ?? 1};`);
@@ -4218,7 +4286,7 @@ var exportOptionsPlist = (method, teamId, destination) => `<?xml version="1.0" e
4218
4286
  var requireShell = () => {
4219
4287
  const root = projectRoot();
4220
4288
  const config = readJson(join11(root, "app.json"));
4221
- if (!config?.ios || !existsSync8(join11(root, "ios"))) {
4289
+ if (!config?.ios || !existsSync9(join11(root, "ios"))) {
4222
4290
  throw new CliError("No iOS shell here — run `lecodes app init ios` first.");
4223
4291
  }
4224
4292
  return { root, config };
@@ -4294,7 +4362,7 @@ var appUpdate = async (args) => {
4294
4362
  log(`${c.green("plugin")} ${id} ${entry?.tag ?? "—"} → ${c.bold(tag)}`);
4295
4363
  changed = true;
4296
4364
  }
4297
- if (!checkOnly && existsSync8(lockPath))
4365
+ if (!checkOnly && existsSync9(lockPath))
4298
4366
  writeJson(lockPath, { plugins: kept });
4299
4367
  }
4300
4368
  log("");
@@ -4328,18 +4396,18 @@ var appDoctor = async (_args) => {
4328
4396
  check(xcodeVersion !== "", `Xcode CLI (${xcodeVersion || "not found"})`, "install Xcode + command line tools");
4329
4397
  check(!!config.ios.teamId, "Development team set", "lecodes app init wrote app.json — fill ios.teamId", false);
4330
4398
  const iconDir = join11(root, "ios", "App", "Assets.xcassets", "AppIcon.appiconset");
4331
- const hasIcon = existsSync8(iconDir) && readdirSync4(iconDir).some((f2) => f2.endsWith(".png"));
4399
+ const hasIcon = existsSync9(iconDir) && readdirSync4(iconDir).some((f2) => f2.endsWith(".png"));
4332
4400
  check(hasIcon, "App icon present", "drop a 1024×1024 PNG into ios/App/Assets.xcassets/AppIcon.appiconset/ (App Store requires it)", false);
4333
4401
  const bundle = join11(root, "ios", "App", "Resources", "app.js");
4334
- check(existsSync8(bundle), "Bundled app.js", "run `lecodes app sync`");
4402
+ check(existsSync9(bundle), "Bundled app.js", "run `lecodes app sync`");
4335
4403
  const requestedVariant = config.ios.variant ?? "auto";
4336
4404
  const validVariant = requestedVariant === "auto" || SDK_VARIANTS.includes(requestedVariant);
4337
4405
  check(validVariant, `ios.variant "${requestedVariant}"`, `use "auto" or one of ${SDK_VARIANTS.join(", ")}`);
4338
- if (existsSync8(bundle) && validVariant) {
4406
+ if (existsSync9(bundle) && validVariant) {
4339
4407
  const { variant } = resolveVariant(requestedVariant, readFileSync10(bundle, "utf8"));
4340
4408
  const pkg = readFileSync10(join11(root, "ios", "LeCodesRuntime", "Package.swift"), "utf8");
4341
4409
  const needsResources = variant === "3d" || variant === "full";
4342
- const hasResources = existsSync8(join11(root, "ios", "App", "Resources", "LeCodesSDKResources.bundle"));
4410
+ const hasResources = existsSync9(join11(root, "ios", "App", "Resources", "LeCodesSDKResources.bundle"));
4343
4411
  check(pkg.includes(`"LeCodesSDK-${variant}"`) && (!needsResources || hasResources), `SDK variant ${variant}${needsResources ? " (+ filament resources)" : ""}`, "run `lecodes app sync`");
4344
4412
  }
4345
4413
  const linked = shellSdkVersion(join11(root, "ios"));
@@ -4411,7 +4479,7 @@ var appBuild = async (args) => {
4411
4479
  };
4412
4480
  var appUpload = async (args) => {
4413
4481
  const { root, config } = requireShell();
4414
- if (!existsSync8(join11(root, ARCHIVE_PATH))) {
4482
+ if (!existsSync9(join11(root, ARCHIVE_PATH))) {
4415
4483
  throw new CliError("No archive at ios/build/App.xcarchive — run `lecodes app build ios` first.");
4416
4484
  }
4417
4485
  const ascPath = join11(homedir2(), ".lecodes", "asc.json");
@@ -4423,7 +4491,7 @@ var appUpload = async (args) => {
4423
4491
  throw new CliError(`App Store Connect API key needed: --key-path <AuthKey.p8> --key-id <id> --issuer-id <id>
4424
4492
  (create one at appstoreconnect.apple.com → Users and Access → Integrations; stored in ~/.lecodes/asc.json after first use)`);
4425
4493
  }
4426
- if (!existsSync8(keyPath))
4494
+ if (!existsSync9(keyPath))
4427
4495
  throw new CliError(`API key file not found: ${keyPath}`);
4428
4496
  if (flagStr(args, "key-path")) {
4429
4497
  mkdirSync5(dirname7(ascPath), { recursive: true });
@@ -4461,7 +4529,7 @@ var appOpen = async (args) => {
4461
4529
  const platform = args._[1] ?? "ios";
4462
4530
  if (platform === "ios") {
4463
4531
  const proj = join11(root, "ios", "App.xcodeproj");
4464
- if (!existsSync8(proj))
4532
+ if (!existsSync9(proj))
4465
4533
  throw new CliError("No iOS shell here — run `lecodes app init ios` first.");
4466
4534
  if (process.platform !== "darwin")
4467
4535
  throw new CliError("Opening Xcode requires macOS.");
@@ -4496,12 +4564,12 @@ var app = async (args) => {
4496
4564
  };
4497
4565
 
4498
4566
  // src/commands/clone.ts
4499
- import { existsSync as existsSync10, mkdirSync as mkdirSync7, readdirSync as readdirSync6 } from "node:fs";
4567
+ import { existsSync as existsSync11, mkdirSync as mkdirSync7, readdirSync as readdirSync6 } from "node:fs";
4500
4568
  import { resolve as resolve3 } from "node:path";
4501
4569
 
4502
4570
  // src/types.ts
4503
4571
  import { dirname as dirname8, join as join12, relative as relative3, sep } from "node:path";
4504
- import { existsSync as existsSync9, mkdirSync as mkdirSync6, readdirSync as readdirSync5, readFileSync as readFileSync11, rmSync as rmSync2, statSync as statSync3, writeFileSync as writeFileSync8 } from "node:fs";
4572
+ import { existsSync as existsSync10, mkdirSync as mkdirSync6, readdirSync as readdirSync5, readFileSync as readFileSync11, rmSync as rmSync2, statSync as statSync3, writeFileSync as writeFileSync8 } from "node:fs";
4505
4573
  var TSCONFIG = {
4506
4574
  compilerOptions: {
4507
4575
  target: "ES2020",
@@ -4519,7 +4587,7 @@ var TSCONFIG = {
4519
4587
  exclude: ["node_modules"]
4520
4588
  };
4521
4589
  var listBundleFiles = (dir, base = dir) => {
4522
- if (!existsSync9(dir))
4590
+ if (!existsSync10(dir))
4523
4591
  return [];
4524
4592
  const out = [];
4525
4593
  for (const name of readdirSync5(dir)) {
@@ -4532,7 +4600,7 @@ var listBundleFiles = (dir, base = dir) => {
4532
4600
  return out;
4533
4601
  };
4534
4602
  var pruneEmptyDirs = (dir, root) => {
4535
- if (!existsSync9(dir) || !statSync3(dir).isDirectory())
4603
+ if (!existsSync10(dir) || !statSync3(dir).isDirectory())
4536
4604
  return;
4537
4605
  for (const name of readdirSync5(dir))
4538
4606
  pruneEmptyDirs(join12(dir, name), root);
@@ -4578,7 +4646,7 @@ var readLocalTypeBundle = () => {
4578
4646
  join12(distRoot, "..", "sdk", "dist", "types.json")
4579
4647
  ];
4580
4648
  for (const path of candidates) {
4581
- if (existsSync9(path)) {
4649
+ if (existsSync10(path)) {
4582
4650
  try {
4583
4651
  return JSON.parse(readFileSync11(path, "utf8"));
4584
4652
  } catch {}
@@ -4616,7 +4684,7 @@ var clone = async (args) => {
4616
4684
  const project = await getProject(apiUrl, token, uuid);
4617
4685
  const dir = args._[1] ?? slug(project.name);
4618
4686
  const root = resolve3(dir);
4619
- if (existsSync10(root) && readdirSync6(root).length > 0) {
4687
+ if (existsSync11(root) && readdirSync6(root).length > 0) {
4620
4688
  throw new CliError(`Directory "${dir}" already exists and is not empty.`);
4621
4689
  }
4622
4690
  mkdirSync7(root, { recursive: true });
@@ -4636,7 +4704,7 @@ var clone = async (args) => {
4636
4704
  };
4637
4705
 
4638
4706
  // src/commands/create.ts
4639
- import { existsSync as existsSync11, mkdirSync as mkdirSync8, readdirSync as readdirSync7, writeFileSync as writeFileSync9 } from "node:fs";
4707
+ import { existsSync as existsSync12, mkdirSync as mkdirSync8, readdirSync as readdirSync7, writeFileSync as writeFileSync9 } from "node:fs";
4640
4708
  import { join as join13, resolve as resolve4 } from "node:path";
4641
4709
 
4642
4710
  // src/commands/projectTemplates.ts
@@ -4723,7 +4791,7 @@ var create = async (args) => {
4723
4791
  throw new CliError("Usage: lecodes create <name> [directory]");
4724
4792
  const dir = args._[1] ?? slug2(name);
4725
4793
  const root = resolve4(dir);
4726
- if (existsSync11(root) && readdirSync7(root).length > 0) {
4794
+ if (existsSync12(root) && readdirSync7(root).length > 0) {
4727
4795
  throw new CliError(`Directory "${dir}" already exists and is not empty.`);
4728
4796
  }
4729
4797
  mkdirSync8(root, { recursive: true });
@@ -4742,7 +4810,7 @@ var create = async (args) => {
4742
4810
  };
4743
4811
 
4744
4812
  // src/commands/design.ts
4745
- import { existsSync as existsSync13, mkdirSync as mkdirSync10, readFileSync as readFileSync14, readdirSync as readdirSync8, writeFileSync as writeFileSync11, appendFileSync } from "node:fs";
4813
+ import { existsSync as existsSync14, mkdirSync as mkdirSync10, readFileSync as readFileSync14, readdirSync as readdirSync8, writeFileSync as writeFileSync11, appendFileSync } from "node:fs";
4746
4814
  import { basename as basename6, join as join15 } from "node:path";
4747
4815
 
4748
4816
  // src/compile/designCompile.ts
@@ -4751,7 +4819,7 @@ import { basename as basename5 } from "node:path";
4751
4819
 
4752
4820
  // src/compile/assetIcons.ts
4753
4821
  import { createHash as createHash3 } from "node:crypto";
4754
- import { existsSync as existsSync12, mkdirSync as mkdirSync9, readFileSync as readFileSync12, writeFileSync as writeFileSync10 } from "node:fs";
4822
+ import { existsSync as existsSync13, mkdirSync as mkdirSync9, readFileSync as readFileSync12, writeFileSync as writeFileSync10 } from "node:fs";
4755
4823
  import { dirname as dirname9, join as join14 } from "node:path";
4756
4824
  var DEFAULT_REGISTRY = process.env.LECODES_ICON_REGISTRY?.trim() || "https://icon-registry.jt3.ru/v1";
4757
4825
  var ICON_ID = /^([a-z0-9]+(?:-[a-z0-9]+)*):([a-z0-9]+(?:-[a-z0-9]+)*)$/;
@@ -5048,7 +5116,7 @@ var renderNotices2 = (lock) => {
5048
5116
  `);
5049
5117
  };
5050
5118
  var writeIfChanged2 = (path, text) => {
5051
- const current = existsSync12(path) ? safeRead(path) : null;
5119
+ const current = existsSync13(path) ? safeRead(path) : null;
5052
5120
  if (current === text)
5053
5121
  return false;
5054
5122
  writeFileSync10(path, text);
@@ -5727,7 +5795,7 @@ var getContext = (args) => {
5727
5795
  };
5728
5796
  var listScreens = (designDir) => {
5729
5797
  const dir = join15(designDir, "screens");
5730
- if (!existsSync13(dir))
5798
+ if (!existsSync14(dir))
5731
5799
  return [];
5732
5800
  return readdirSync8(dir).filter((f2) => f2.endsWith(".ts") && !f2.startsWith(".")).map((f2) => f2.slice(0, -3)).sort();
5733
5801
  };
@@ -5743,7 +5811,7 @@ var writeMcpConfig = (ctx, port) => {
5743
5811
  const path = join15(ctx.root, ".mcp.json");
5744
5812
  const entry = { type: "http", url: `http://localhost:${port}/mcp` };
5745
5813
  let config = {};
5746
- if (existsSync13(path)) {
5814
+ if (existsSync14(path)) {
5747
5815
  try {
5748
5816
  config = JSON.parse(readFileSync14(path, "utf8"));
5749
5817
  } catch {
@@ -5777,7 +5845,7 @@ var init = async (ctx, args) => {
5777
5845
  let created = 0;
5778
5846
  for (const [rel, content] of files) {
5779
5847
  const abs = join15(ctx.designDir, rel);
5780
- if (existsSync13(abs)) {
5848
+ if (existsSync14(abs)) {
5781
5849
  note(`kept ${ctx.dirName}/${rel.replace(/\\/g, "/")}`);
5782
5850
  continue;
5783
5851
  }
@@ -5787,7 +5855,7 @@ var init = async (ctx, args) => {
5787
5855
  }
5788
5856
  const ignorePath = join15(ctx.root, ".lecodesignore");
5789
5857
  const ignoreLine = `${ctx.dirName}/.snapshots/`;
5790
- const current = existsSync13(ignorePath) ? readFileSync14(ignorePath, "utf8") : "";
5858
+ const current = existsSync14(ignorePath) ? readFileSync14(ignorePath, "utf8") : "";
5791
5859
  if (!current.split(/\r?\n/).includes(ignoreLine)) {
5792
5860
  appendFileSync(ignorePath, (current && !current.endsWith(`
5793
5861
  `) ? `
@@ -5909,7 +5977,7 @@ var snapshot = async (ctx, args) => {
5909
5977
  }
5910
5978
  };
5911
5979
  var serve = async (ctx, args) => {
5912
- if (!existsSync13(ctx.designDir)) {
5980
+ if (!existsSync14(ctx.designDir)) {
5913
5981
  throw new CliError(`No ${ctx.dirName}/ folder here. Run "lecodes design init" to scaffold it.`);
5914
5982
  }
5915
5983
  let designServer;
@@ -5982,7 +6050,7 @@ var serve = async (ctx, args) => {
5982
6050
  openBrowser(server.url);
5983
6051
  };
5984
6052
  var arrange = async (ctx, args) => {
5985
- if (!existsSync13(ctx.designDir)) {
6053
+ if (!existsSync14(ctx.designDir)) {
5986
6054
  throw new CliError(`No ${ctx.dirName}/ folder here. Run "lecodes design init" to scaffold it.`);
5987
6055
  }
5988
6056
  let designServer;
@@ -6018,7 +6086,7 @@ var share = async (ctx, args) => {
6018
6086
  note("Turn it off with: lecodes design share --off");
6019
6087
  };
6020
6088
  var check = async (ctx, args) => {
6021
- if (!existsSync13(ctx.designDir)) {
6089
+ if (!existsSync14(ctx.designDir)) {
6022
6090
  throw new CliError(`No ${ctx.dirName}/ folder here. Run "lecodes design init" to scaffold it.`);
6023
6091
  }
6024
6092
  let designServer;
@@ -6094,12 +6162,11 @@ var design = async (args) => {
6094
6162
  import { existsSync as existsSync15, mkdirSync as mkdirSync11, readFileSync as readFileSync15, watch, writeFileSync as writeFileSync12 } from "node:fs";
6095
6163
  import { randomBytes as randomBytes2 } from "node:crypto";
6096
6164
  import { networkInterfaces } from "node:os";
6097
- import { basename as basename7, join as join17 } from "node:path";
6165
+ import { basename as basename7, join as join16 } from "node:path";
6098
6166
 
6099
6167
  // src/dev/devServer.ts
6100
6168
  import { createServer as createServer2 } from "node:http";
6101
- import { createReadStream, existsSync as existsSync14, statSync as statSync4 } from "node:fs";
6102
- import { join as join16 } from "node:path";
6169
+ import { createReadStream, statSync as statSync4 } from "node:fs";
6103
6170
 
6104
6171
  // src/dev/clientTemplates.ts
6105
6172
  var DEV_SCREEN_TS = `const title = "LeCodes Dev"
@@ -6468,8 +6535,8 @@ var startDevServer = async (options) => {
6468
6535
  } catch {
6469
6536
  return sendText(res, 400, "text/plain", "Bad path");
6470
6537
  }
6471
- const abs = join16(root, key.slice(1));
6472
- if (!existsSync14(abs) || !statSync4(abs).isFile())
6538
+ const abs = resolveLocalPath(root, key);
6539
+ if (!abs || !statSync4(abs).isFile())
6473
6540
  return sendText(res, 404, "text/plain", "Not found");
6474
6541
  res.writeHead(200, { "content-type": contentType(key), "cache-control": "no-store" });
6475
6542
  createReadStream(abs).pipe(res);
@@ -6552,14 +6619,14 @@ var pickLanAddress = () => {
6552
6619
  return null;
6553
6620
  };
6554
6621
  var loadDevToken = (root) => {
6555
- const path = join17(root, LECODES_DIR, "dev.json");
6622
+ const path = join16(root, LECODES_DIR, "dev.json");
6556
6623
  try {
6557
6624
  const token2 = JSON.parse(readFileSync15(path, "utf8")).token;
6558
6625
  if (token2 && /^[a-z0-9]{8,}$/.test(token2))
6559
6626
  return token2;
6560
6627
  } catch {}
6561
6628
  const token = randomBytes2(6).toString("hex");
6562
- mkdirSync11(join17(root, LECODES_DIR), { recursive: true });
6629
+ mkdirSync11(join16(root, LECODES_DIR), { recursive: true });
6563
6630
  writeFileSync12(path, JSON.stringify({ token }, null, 2) + `
6564
6631
  `);
6565
6632
  return token;
@@ -6672,7 +6739,7 @@ ${result.error}`);
6672
6739
  await printQr(server.url);
6673
6740
  log(`Scan the QR with the LeCodes app, or open: ${c.bold(server.url)}`);
6674
6741
  note(`Older desktop builds (no websocket bridge): ${server.url}?t=poll`);
6675
- if (existsSync15(join17(root, LECODES_DIR, "manifest.json")))
6742
+ if (existsSync15(join16(root, LECODES_DIR, "manifest.json")))
6676
6743
  note(`Token stored in ${LECODES_DIR}/dev.json (delete it to rotate the URL).`);
6677
6744
  log("");
6678
6745
  log(`Watching ${root} — save a file to hot-reload. Ctrl+C to stop.`);
@@ -6868,7 +6935,7 @@ var statusLabel = (e) => e.status === "added" ? c.green(" added ") : e.statu
6868
6935
 
6869
6936
  // src/commands/init.ts
6870
6937
  import { existsSync as existsSync16, mkdirSync as mkdirSync12, writeFileSync as writeFileSync13 } from "node:fs";
6871
- import { basename as basename9, dirname as dirname10, join as join18, resolve as resolve5 } from "node:path";
6938
+ import { basename as basename9, dirname as dirname10, join as join17, resolve as resolve5 } from "node:path";
6872
6939
  var STARTER2 = `// Welcome to LeCodes — the whole SDK is available as globals, no imports needed.
6873
6940
  // Run it live on a device with \`lecodes dev\`, or bundle it with \`lecodes compile -o app.js\`.
6874
6941
 
@@ -6970,7 +7037,7 @@ var init2 = async (args) => {
6970
7037
  mkdirSync12(root, { recursive: true });
6971
7038
  let created = 0;
6972
7039
  const write = (rel, content) => {
6973
- const abs = join18(root, rel);
7040
+ const abs = join17(root, rel);
6974
7041
  if (existsSync16(abs)) {
6975
7042
  note(`kept ${rel}`);
6976
7043
  return;
@@ -7001,7 +7068,7 @@ var init2 = async (args) => {
7001
7068
 
7002
7069
  // src/commands/link.ts
7003
7070
  import { existsSync as existsSync17 } from "node:fs";
7004
- import { basename as basename11, join as join19, resolve as resolve6 } from "node:path";
7071
+ import { basename as basename11, join as join18, resolve as resolve6 } from "node:path";
7005
7072
 
7006
7073
  // src/commands/push.ts
7007
7074
  import { basename as basename10 } from "node:path";
@@ -7164,7 +7231,7 @@ var link = async (args) => {
7164
7231
  const root = dirArg ? resolve6(dirArg) : process.cwd();
7165
7232
  if (!existsSync17(root))
7166
7233
  throw new CliError(`No such directory: ${root}`);
7167
- if (existsSync17(join19(root, LECODES_DIR, "manifest.json")) && !flagBool(args, "force")) {
7234
+ if (existsSync17(join18(root, LECODES_DIR, "manifest.json")) && !flagBool(args, "force")) {
7168
7235
  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.");
7169
7236
  }
7170
7237
  let enclosing = null;
@@ -7481,7 +7548,7 @@ var render = async (args) => {
7481
7548
 
7482
7549
  // src/commands/pull.ts
7483
7550
  import { existsSync as existsSync18, mkdirSync as mkdirSync14, rmSync as rmSync3, writeFileSync as writeFileSync15 } from "node:fs";
7484
- import { dirname as dirname12, join as join20 } from "node:path";
7551
+ import { dirname as dirname12, join as join19 } from "node:path";
7485
7552
  var pull = async (args) => {
7486
7553
  const root = findProjectRoot(process.cwd());
7487
7554
  const manifest = readManifest(root);
@@ -7501,7 +7568,7 @@ var pull = async (args) => {
7501
7568
  const remotePaths = new Set([...buildPaths(project.assets).entries()].filter(([id]) => project.assets.find((a) => a.id === id)?.type !== "folder").map(([, path]) => path));
7502
7569
  for (const path of Object.keys(manifest.files)) {
7503
7570
  if (!remotePaths.has(path)) {
7504
- const abs = join20(root, ...path.replace(/^\//, "").split("/"));
7571
+ const abs = join19(root, ...path.replace(/^\//, "").split("/"));
7505
7572
  if (existsSync18(abs))
7506
7573
  rmSync3(abs);
7507
7574
  }
@@ -7540,7 +7607,7 @@ var pullPaths = async (root, manifest, project, paths, apiUrl, token) => {
7540
7607
  const files = { ...manifest.files };
7541
7608
  for (const asset of targets) {
7542
7609
  const key = pathById.get(asset.id);
7543
- const abs = join20(root, ...key.replace(/^\//, "").split("/"));
7610
+ const abs = join19(root, ...key.replace(/^\//, "").split("/"));
7544
7611
  mkdirSync14(dirname12(abs), { recursive: true });
7545
7612
  let sha;
7546
7613
  if (asset.type === "file") {
@@ -7564,19 +7631,19 @@ var pullPaths = async (root, manifest, project, paths, apiUrl, token) => {
7564
7631
 
7565
7632
  // src/commands/scene.ts
7566
7633
  import { existsSync as existsSync20, readFileSync as readFileSync20, readdirSync as readdirSync9, statSync as statSync5, writeFileSync as writeFileSync16 } from "node:fs";
7567
- import { basename as basename12, dirname as dirname13, join as join22, resolve as resolve8 } from "node:path";
7634
+ import { basename as basename12, dirname as dirname13, join as join21, resolve as resolve8 } from "node:path";
7568
7635
 
7569
7636
  // src/compile/sceneCompile.ts
7570
7637
  import { existsSync as existsSync19, readFileSync as readFileSync19 } from "node:fs";
7571
- import { join as join21 } from "node:path";
7638
+ import { join as join20 } from "node:path";
7572
7639
  var TEXT_EXT = /\.(ts|js|json|svg|txt|md)$/i;
7573
7640
  var rel = (key) => key.replace(/^\//, "");
7574
7641
  var resolveHarness = (loadSceneHarness2) => {
7575
- const inRepo = join21(distRoot, "..", "projects", "src", "scene-editor");
7642
+ const inRepo = join20(distRoot, "..", "projects", "src", "scene-editor");
7576
7643
  try {
7577
7644
  return loadSceneHarness2(existsSync19(inRepo) ? inRepo : undefined);
7578
7645
  } catch {
7579
- const vendored = join21(distRoot, "runtime", "scene-harness.json");
7646
+ const vendored = join20(distRoot, "runtime", "scene-harness.json");
7580
7647
  if (existsSync19(vendored))
7581
7648
  return JSON.parse(readFileSync19(vendored, "utf8"));
7582
7649
  throw new CliError("scene-editor harness sources not found — reinstall the CLI or set LECODES_SCENE_HARNESS_DIR.");
@@ -7642,7 +7709,7 @@ var getContext2 = (args) => {
7642
7709
  return { root, manifest, name: manifest?.name ?? basename12(root) };
7643
7710
  };
7644
7711
  var writeMcpConfig2 = (root, port) => {
7645
- const path = join22(root, ".mcp.json");
7712
+ const path = join21(root, ".mcp.json");
7646
7713
  const entry = { type: "http", url: `http://localhost:${port}/mcp` };
7647
7714
  let config = {};
7648
7715
  if (existsSync20(path)) {
@@ -7669,7 +7736,7 @@ var countScenes = (dir) => {
7669
7736
  for (const name of readdirSync9(dir)) {
7670
7737
  if (name === "node_modules" || name.startsWith("."))
7671
7738
  continue;
7672
- const full = join22(dir, name);
7739
+ const full = join21(dir, name);
7673
7740
  if (statSync5(full).isDirectory())
7674
7741
  n += countScenes(full);
7675
7742
  else if (name.endsWith(".scene.ts"))
@@ -8036,13 +8103,13 @@ var types = async (args) => {
8036
8103
  // src/index.ts
8037
8104
  import { existsSync as existsSync23, readFileSync as readFileSync23, realpathSync as realpathSync2 } from "node:fs";
8038
8105
  import { createRequire as createRequire4 } from "node:module";
8039
- import { delimiter as delimiter2, dirname as dirname16, join as join24 } from "node:path";
8106
+ import { delimiter as delimiter2, dirname as dirname16, join as join23 } from "node:path";
8040
8107
 
8041
8108
  // src/commands/install.ts
8042
8109
  import { execFileSync as execFileSync2 } from "node:child_process";
8043
8110
  import { appendFileSync as appendFileSync2, chmodSync, cpSync as cpSync2, existsSync as existsSync22, mkdirSync as mkdirSync15, readFileSync as readFileSync22, realpathSync, rmSync as rmSync4, symlinkSync } from "node:fs";
8044
8111
  import { homedir as homedir3 } from "node:os";
8045
- import { basename as basename13, delimiter, dirname as dirname15, join as join23, resolve as resolve11, sep as sep2 } from "node:path";
8112
+ import { basename as basename13, delimiter, dirname as dirname15, join as join22, resolve as resolve11, sep as sep2 } from "node:path";
8046
8113
  var isWin = process.platform === "win32";
8047
8114
  var HOME = homedir3();
8048
8115
  var safeRealpath = (p) => {
@@ -8068,10 +8135,10 @@ var install = async (args) => {
8068
8135
  const bundle = dirname15(process.execPath);
8069
8136
  const exeName = basename13(process.execPath);
8070
8137
  const chiselName = isWin ? "chisel.exe" : "chisel";
8071
- if (!existsSync22(join23(bundle, "runtime")) || !existsSync22(join23(bundle, chiselName))) {
8138
+ if (!existsSync22(join22(bundle, "runtime")) || !existsSync22(join22(bundle, chiselName))) {
8072
8139
  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.");
8073
8140
  }
8074
- const companions = [exeName, chiselName, "runtime", "README.txt"].filter((f2) => existsSync22(join23(bundle, f2)));
8141
+ const companions = [exeName, chiselName, "runtime", "README.txt"].filter((f2) => existsSync22(join22(bundle, f2)));
8075
8142
  if (isWin)
8076
8143
  return installWindows(args, bundle, companions);
8077
8144
  return installUnix(args, bundle, companions);
@@ -8081,9 +8148,9 @@ var copyBundle = (bundle, target, companions) => {
8081
8148
  if (safeRealpath(target) === safeRealpath(bundle))
8082
8149
  return;
8083
8150
  for (const f2 of companions) {
8084
- const dest = join23(target, f2);
8151
+ const dest = join22(target, f2);
8085
8152
  rmSync4(dest, { recursive: true, force: true });
8086
- cpSync2(join23(bundle, f2), dest, { recursive: true });
8153
+ cpSync2(join22(bundle, f2), dest, { recursive: true });
8087
8154
  }
8088
8155
  };
8089
8156
  var permError = (e, dir, system) => {
@@ -8095,34 +8162,34 @@ var permError = (e, dir, system) => {
8095
8162
  };
8096
8163
  var installUnix = (args, bundle, companions) => {
8097
8164
  const system = flagBool(args, "system");
8098
- const libDir = resolve11(flagStr(args, "dir") ?? (system ? "/usr/local/lib/lecodes-cli" : join23(HOME, ".local", "lib", "lecodes-cli")));
8099
- const binDir = resolve11(flagStr(args, "bin") ?? (system ? "/usr/local/bin" : join23(HOME, ".local", "bin")));
8100
- const link2 = join23(binDir, "lecodes");
8165
+ const libDir = resolve11(flagStr(args, "dir") ?? (system ? "/usr/local/lib/lecodes-cli" : join22(HOME, ".local", "lib", "lecodes-cli")));
8166
+ const binDir = resolve11(flagStr(args, "bin") ?? (system ? "/usr/local/bin" : join22(HOME, ".local", "bin")));
8167
+ const link2 = join22(binDir, "lecodes");
8101
8168
  try {
8102
8169
  copyBundle(bundle, libDir, companions);
8103
- chmodSync(join23(libDir, "lecodes"), 493);
8104
- chmodSync(join23(libDir, "chisel"), 493);
8170
+ chmodSync(join22(libDir, "lecodes"), 493);
8171
+ chmodSync(join22(libDir, "chisel"), 493);
8105
8172
  } catch (e) {
8106
8173
  throw permError(e, libDir, system);
8107
8174
  }
8108
8175
  try {
8109
8176
  mkdirSync15(binDir, { recursive: true });
8110
8177
  rmSync4(link2, { force: true });
8111
- symlinkSync(join23(libDir, "lecodes"), link2);
8178
+ symlinkSync(join22(libDir, "lecodes"), link2);
8112
8179
  } catch (e) {
8113
8180
  throw permError(e, binDir, system);
8114
8181
  }
8115
8182
  success(`Installed to ${c.bold(libDir)}`);
8116
- log(` ${c.dim(link2 + " → " + join23(libDir, "lecodes"))}`);
8183
+ log(` ${c.dim(link2 + " → " + join22(libDir, "lecodes"))}`);
8117
8184
  finishPathUnix(args, binDir);
8118
8185
  };
8119
8186
  var shellRc = () => {
8120
8187
  const shell = basename13(process.env.SHELL ?? "");
8121
8188
  if (shell === "zsh")
8122
- return { file: join23(HOME, ".zshrc"), name: "~/.zshrc" };
8189
+ return { file: join22(HOME, ".zshrc"), name: "~/.zshrc" };
8123
8190
  if (shell === "bash")
8124
- return { file: join23(HOME, ".bashrc"), name: "~/.bashrc" };
8125
- return { file: join23(HOME, ".profile"), name: "~/.profile" };
8191
+ return { file: join22(HOME, ".bashrc"), name: "~/.bashrc" };
8192
+ return { file: join22(HOME, ".profile"), name: "~/.profile" };
8126
8193
  };
8127
8194
  var PATH_MARKER = "# Added by `lecodes install`";
8128
8195
  var appendPathToShellRc = (binDir) => {
@@ -8167,7 +8234,7 @@ var finishPathUnix = (args, binDir) => {
8167
8234
  log(` • or install system-wide (no PATH edit): ${c.bold("sudo lecodes install --system")}`);
8168
8235
  };
8169
8236
  var installWindows = (args, bundle, companions) => {
8170
- const target = resolve11(flagStr(args, "dir") ?? join23(process.env.LOCALAPPDATA ?? join23(HOME, "AppData", "Local"), "Programs", "lecodes-cli"));
8237
+ const target = resolve11(flagStr(args, "dir") ?? join22(process.env.LOCALAPPDATA ?? join22(HOME, "AppData", "Local"), "Programs", "lecodes-cli"));
8171
8238
  try {
8172
8239
  copyBundle(bundle, target, companions);
8173
8240
  } catch (e) {
@@ -8206,7 +8273,7 @@ var addToUserPathWindows = (dir) => {
8206
8273
  var STANDALONE = (() => {
8207
8274
  const dir = dirname16(process.execPath);
8208
8275
  const chisel = process.platform === "win32" ? "chisel.exe" : "chisel";
8209
- return existsSync23(join24(dir, "runtime")) && existsSync23(join24(dir, chisel));
8276
+ return existsSync23(join23(dir, "runtime")) && existsSync23(join23(dir, chisel));
8210
8277
  })();
8211
8278
  var INSTALLED = STANDALONE && (() => {
8212
8279
  const exe = process.platform === "win32" ? "lecodes.exe" : "lecodes";
@@ -8221,7 +8288,7 @@ var INSTALLED = STANDALONE && (() => {
8221
8288
  if (!d)
8222
8289
  return false;
8223
8290
  try {
8224
- const cand = join24(d, exe);
8291
+ const cand = join23(d, exe);
8225
8292
  return existsSync23(cand) && realpathSync2(cand) === self;
8226
8293
  } catch {
8227
8294
  return false;
@@ -8408,7 +8475,7 @@ var peerVersion = (name, probe) => {
8408
8475
  const require2 = createRequire4(import.meta.url);
8409
8476
  let dir = dirname16(require2.resolve(probe));
8410
8477
  for (let depth = 0;depth < 16; depth++) {
8411
- const manifest = join24(dir, "package.json");
8478
+ const manifest = join23(dir, "package.json");
8412
8479
  if (existsSync23(manifest)) {
8413
8480
  const parsed = JSON.parse(readFileSync23(manifest, "utf8"));
8414
8481
  if (parsed.name === name)