@genex-ai/cli-demo 1.2.4 → 1.3.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
@@ -991,7 +991,7 @@ var CONTRACT_END = "<!-- genex:contract:end -->";
991
991
  var GENEX_CONTRACT_BLOCK = `${CONTRACT_BEGIN}
992
992
  # Genex build contract (always in effect for this game)
993
993
 
994
- Your capabilities (all via \`npx genex \u2026\`): generate \`model\` \xB7 \`skybox\` \xB7 \`sfx\` \xB7 \`music\` \xB7 \`voice\` \xB7 \`texture\` \xB7 \`image\` (\`--edit\` \xB7 \`--inpaint\` \xB7 \`--glass\` \xB7 \`--clean\` \xB7 \`--upscale\`) \xB7 \`video\` \xB7 rigged \`character\` / \`creature\` \xB7 \`character animate <id> "<verb>"\` (also \`creature animate\`; \`--locomotion\` for the 8-way movement set, \`--video\` for your own footage) \xB7 the pixel toolbox \`ui extract|masks|plate|text-color|trim|audit\` \xB7 vendored \`controller character|car|drone|touch|quality\` \xB7 \`animations search\` \xB7 \`wait <id>\` / \`wait --all\` \xB7 \`preview\` / \`publish\`. Full options: \`npx genex --help\`. Task\u2192lane routing lives in the \`genex-game-director\` skill's routing map \u2014 re-load it whenever you're unsure which lane owns a task.
994
+ Your capabilities (all via \`npx genex \u2026\`): generate \`model\` \xB7 \`skybox\` \xB7 \`sfx\` \xB7 \`music\` \xB7 \`voice\` \xB7 \`texture\` \xB7 \`image\` (\`--edit\` \xB7 \`--inpaint\` \xB7 \`--glass\` \xB7 \`--clean\` \xB7 \`--upscale\`) \xB7 \`video\` \xB7 rigged \`character\` / \`creature\` \xB7 \`character animate <id> "<verb>"\` (also \`creature animate\`; \`--locomotion\` for the 8-way movement set, \`--video\` for your own footage) \xB7 the pixel toolbox \`ui extract|masks|plate|text-color|trim|audit\` \xB7 vendored \`controller character|car|drone|touch|quality|chat\` \xB7 \`animations search\` \xB7 \`wait <id>\` / \`wait --all\` \xB7 \`preview\` / \`publish\` \xB7 \`rename <name>\` (move the game to a new address \u2014 keeps its plays and likes, kills the old link, needs \`--yes\` once published, and the game must be rebuilt after). Full options: \`npx genex --help\`. Task\u2192lane routing lives in the \`genex-game-director\` skill's routing map \u2014 re-load it whenever you're unsure which lane owns a task.
995
995
 
996
996
  1. Load the \`genex-game-director\` skill before starting the requested work. Route from the player's latest clear request: a focused request starts directly without replaying discovery or commissioning unrelated lanes. After ANY context compaction or session resume, re-read this file and \`DESIGN.md\`, re-load the skill for the stage you are executing, and continue from the Build plan's \`Now:\` line \u2014 never from memory alone.
997
997
  2. Ask only when one unresolved answer materially changes the work. If the request is clear, do not repeat an interview, confirm the pitch, force a concept round, or ask whole-game-vs-one-part again. For a genuinely broad new game, ask one decision: whole coordinated build or one request-relevant part first. If there is no clear request \u2014 a setup prompt pasted with nothing of their own \u2014 ask in PLAIN CHAT what they want to make, in their own words, and never fill the blank by pitching concepts. That opening question stays in chat on purpose: their reply carries the whole request, including anything they say about HOW you should work, and a menu of options answers a narrower question than the one they need to answer. Once you know what you are building, ask one decision at a time with your built-in question tool with clickable answer options when you have one; use a short plain-chat question otherwise. If the player stays silent after a necessary question, proceed on stated assumptions where reasonable and record each as "assumed \u2014 player didn't answer" in DESIGN.md \u2192 Decisions.
@@ -1891,6 +1891,124 @@ async function listOwnSlugs(apiUrl, token, log) {
1891
1891
  }
1892
1892
  }
1893
1893
 
1894
+ // src/commands/rename.ts
1895
+ import fs12 from "fs/promises";
1896
+ import path12 from "path";
1897
+ async function runRename(opts) {
1898
+ const log = createLogger({ quiet: opts.quiet });
1899
+ log.plain(c.bold("genex rename"));
1900
+ log.plain("");
1901
+ const name = opts.name?.trim();
1902
+ if (!name) {
1903
+ log.error("Rename it to what? Usage: genex rename <name>");
1904
+ process.exitCode = 1;
1905
+ return;
1906
+ }
1907
+ const meta = await readProject();
1908
+ if (!meta) {
1909
+ log.error("No genex project here. Run `genex init` in this directory first.");
1910
+ process.exitCode = 1;
1911
+ return;
1912
+ }
1913
+ const token = await readUserToken(opts.envPath);
1914
+ if (!token) {
1915
+ log.error("Not signed in. Run `genex auth` first.");
1916
+ process.exitCode = 1;
1917
+ return;
1918
+ }
1919
+ const apiUrl = getApiUrl(opts.apiUrl ?? meta.apiUrl);
1920
+ const from = meta.slug;
1921
+ if (meta.status === "published" && !opts.yes) {
1922
+ log.warn(`This retires ${c.cyan(`https://${from}.genex.technology/`)} \u2014 links already shared to it will stop working.`);
1923
+ log.dim(" Plays, likes, and comments move with the game. Re-run with --yes to go ahead.");
1924
+ process.exitCode = 1;
1925
+ return;
1926
+ }
1927
+ log.step(`Renaming ${c.cyan(from)}\u2026`);
1928
+ let res;
1929
+ try {
1930
+ res = await apiFetch(`${apiUrl}/api/projects/${meta.id}/rename`, {
1931
+ method: "POST",
1932
+ headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` },
1933
+ body: JSON.stringify({ name })
1934
+ });
1935
+ } catch (err) {
1936
+ log.error(`Couldn't reach the API at ${apiUrl}.`);
1937
+ log.dim(` ${String(err)}`);
1938
+ process.exitCode = 1;
1939
+ return;
1940
+ }
1941
+ if (res.status === 409) {
1942
+ log.error("That name is taken \u2014 every game needs its own address. Try another.");
1943
+ process.exitCode = 1;
1944
+ return;
1945
+ }
1946
+ if (res.status === 404) {
1947
+ log.error("That project isn't yours (or no longer exists).");
1948
+ process.exitCode = 1;
1949
+ return;
1950
+ }
1951
+ if (!res.ok) {
1952
+ log.error(`Rename failed (HTTP ${res.status}). Your game is untouched at ${c.cyan(from)}.`);
1953
+ process.exitCode = 1;
1954
+ return;
1955
+ }
1956
+ const data = await res.json().catch(() => null);
1957
+ const project = data?.project;
1958
+ if (!project) {
1959
+ log.error("Unexpected API response \u2014 rename may not have applied. Check `genex list`.");
1960
+ process.exitCode = 1;
1961
+ return;
1962
+ }
1963
+ await writeProject({
1964
+ ...meta,
1965
+ slug: project.slug,
1966
+ cloneUrl: project.cloneUrl ?? meta.cloneUrl,
1967
+ playUrl: project.playUrl ?? meta.playUrl
1968
+ });
1969
+ await rewriteSlugEnv(from, project.slug, log);
1970
+ await rewriteBakedSlug(from, project.slug, log);
1971
+ log.plain("");
1972
+ log.success(`Renamed to ${c.cyan(project.slug)}.`);
1973
+ if (project.playUrl) log.dim(` play: ${project.playUrl}`);
1974
+ if (meta.status === "published") {
1975
+ const dashboard = meta.dashboardOrigins?.[0];
1976
+ if (dashboard) log.dim(` world page: ${dashboard}/world/${project.slug}`);
1977
+ }
1978
+ log.plain("");
1979
+ log.info("Run `genex preview` (or `publish`) to rebuild \u2014 the new slug is baked into the bundle.");
1980
+ }
1981
+ async function rewriteSlugEnv(from, to, log, cwd = process.cwd()) {
1982
+ const file = path12.join(cwd, ".env");
1983
+ let content;
1984
+ try {
1985
+ content = await fs12.readFile(file, "utf8");
1986
+ } catch {
1987
+ return;
1988
+ }
1989
+ const re = /^(\s*VITE_GENEX_SLUG=)(.*)$/m;
1990
+ if (!re.test(content)) return;
1991
+ await fs12.writeFile(file, content.replace(re, `$1${to}`));
1992
+ log.dim(` .env: VITE_GENEX_SLUG=${to} (was ${from})`);
1993
+ }
1994
+ async function rewriteBakedSlug(from, to, log, cwd = process.cwd()) {
1995
+ const file = path12.join(cwd, "src", "genex.config.ts");
1996
+ let content;
1997
+ try {
1998
+ content = await fs12.readFile(file, "utf8");
1999
+ } catch {
2000
+ return;
2001
+ }
2002
+ const quoted = new RegExp(`(["'])${from.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\1`, "g");
2003
+ const hits = content.match(quoted)?.length ?? 0;
2004
+ if (hits === 0) {
2005
+ log.warn(` src/genex.config.ts has no "${from}" literal \u2014 check its slug by hand.`);
2006
+ return;
2007
+ }
2008
+ await fs12.writeFile(file, content.replace(quoted, `"${to}"`));
2009
+ log.dim(` src/genex.config.ts: baked slug -> ${to}`);
2010
+ }
2011
+
1894
2012
  // src/commands/list.ts
1895
2013
  async function runList(opts) {
1896
2014
  const log = createLogger({ quiet: opts.quiet });
@@ -2017,9 +2135,9 @@ function relTime(iso) {
2017
2135
  // src/lib/deploy.ts
2018
2136
  import { spawn as spawn3 } from "child_process";
2019
2137
  import crypto3 from "crypto";
2020
- import fs14 from "fs/promises";
2138
+ import fs15 from "fs/promises";
2021
2139
  import os5 from "os";
2022
- import path13 from "path";
2140
+ import path14 from "path";
2023
2141
 
2024
2142
  // ../../packages/mobile-scan/src/image-dims.ts
2025
2143
  function u32be(b, o) {
@@ -2189,7 +2307,8 @@ function scanBundle(files) {
2189
2307
  ktx2Loader: text.includes("KTX2Loader") || text.includes(".ktx2"),
2190
2308
  touchHints: /joystick/i.test(text) || text.includes("touchstart") && text.includes("touch-action"),
2191
2309
  adaptiveQuality: text.includes("__GENEX_QUALITY__"),
2192
- modelRungs: text.includes("__GENEX_MODEL_RUNGS__")
2310
+ modelRungs: text.includes("__GENEX_MODEL_RUNGS__"),
2311
+ voice: text.includes("__GENEX_VOICE__")
2193
2312
  };
2194
2313
  return {
2195
2314
  images,
@@ -2282,12 +2401,12 @@ function tierFor(estVramMb) {
2282
2401
  }
2283
2402
 
2284
2403
  // src/commands/ui.ts
2285
- import fs13 from "fs/promises";
2286
- import path12 from "path";
2404
+ import fs14 from "fs/promises";
2405
+ import path13 from "path";
2287
2406
  import { PNG as PNG2 } from "pngjs";
2288
2407
 
2289
2408
  // src/lib/png-tools.ts
2290
- import fs12 from "fs/promises";
2409
+ import fs13 from "fs/promises";
2291
2410
  import { PNG } from "pngjs";
2292
2411
  var ALPHA_TRANSPARENT_MAX = 16;
2293
2412
  var isHttpUrl = (s) => /^https?:\/\//i.test(s);
@@ -2298,12 +2417,12 @@ async function loadPng(input) {
2298
2417
  if (!res.ok) throw new Error(`Couldn't fetch ${input} (HTTP ${res.status}).`);
2299
2418
  buf = Buffer.from(await res.arrayBuffer());
2300
2419
  } else {
2301
- buf = await fs12.readFile(input);
2420
+ buf = await fs13.readFile(input);
2302
2421
  }
2303
2422
  return PNG.sync.read(buf);
2304
2423
  }
2305
2424
  async function writePng(file, png) {
2306
- await fs12.writeFile(file, PNG.sync.write(png));
2425
+ await fs13.writeFile(file, PNG.sync.write(png));
2307
2426
  }
2308
2427
  function cropPng(image, box) {
2309
2428
  const out = new PNG({ width: box.w, height: box.h });
@@ -2603,7 +2722,7 @@ async function uiExtract(opts, log) {
2603
2722
  const dilatePx = opts.dilate ?? 0;
2604
2723
  const sheet = await loadPng(input);
2605
2724
  const { width: W, height: H, data } = sheet;
2606
- await fs13.mkdir(outDir, { recursive: true });
2725
+ await fs14.mkdir(outDir, { recursive: true });
2607
2726
  log.plain(c.bold("genex ui extract"));
2608
2727
  log.dim(` ${input} (${W}x${H}) \u2192 ${outDir}, ${names.length} names`);
2609
2728
  let hasTransparency = false;
@@ -2832,7 +2951,7 @@ async function uiExtract(opts, log) {
2832
2951
  rimPixels: speckle.sampled
2833
2952
  });
2834
2953
  }
2835
- const outPath = path12.join(outDir, `${name}.png`);
2954
+ const outPath = path13.join(outDir, `${name}.png`);
2836
2955
  await writePng(outPath, out);
2837
2956
  const sidecar = {
2838
2957
  name,
@@ -2851,7 +2970,7 @@ async function uiExtract(opts, log) {
2851
2970
  defringed
2852
2971
  };
2853
2972
  const { name: _n, out: _o, ...sidecarBody } = sidecar;
2854
- await fs13.writeFile(
2973
+ await fs14.writeFile(
2855
2974
  outPath.replace(/\.png$/i, "") + ".bbox.json",
2856
2975
  JSON.stringify(sidecarBody, null, 2)
2857
2976
  );
@@ -2860,8 +2979,8 @@ async function uiExtract(opts, log) {
2860
2979
  `${name}.png ${cropW}x${cropH} (ar ${sidecar.aspectRatio}) at sheet ${padX0},${padY0}`
2861
2980
  );
2862
2981
  }
2863
- const debugPath = path12.join(outDir, "extract-debug.json");
2864
- await fs13.writeFile(
2982
+ const debugPath = path13.join(outDir, "extract-debug.json");
2983
+ await fs14.writeFile(
2865
2984
  debugPath,
2866
2985
  JSON.stringify(
2867
2986
  {
@@ -3323,7 +3442,7 @@ async function uiMasks(opts, log) {
3323
3442
  const registrationTolerance = opts.registrationTolerance ?? 0.04;
3324
3443
  const edgeFlushMax = opts.edgeFlushMax ?? 0.04;
3325
3444
  const loosened = registrationTolerance > 0.04 || edgeFlushMax > 0.04 || minCoverage < 0.01 || maxCoverage > 0.85;
3326
- await fs13.mkdir(outDir, { recursive: true });
3445
+ await fs14.mkdir(outDir, { recursive: true });
3327
3446
  log.plain(c.bold("genex ui masks"));
3328
3447
  log.dim(` ${input} (${image.width}x${image.height}), ${pairs.length} pair(s) \u2192 ${outDir}`);
3329
3448
  const sheetComponents = detectSheetComponents(image, opts.minPixels ?? 2e3);
@@ -3376,11 +3495,11 @@ async function uiMasks(opts, log) {
3376
3495
  });
3377
3496
  }
3378
3497
  const overlay = makeOverlay(clean2, converted.png);
3379
- const framePath = path12.join(outDir, `${pair.name}-frame.png`);
3380
- const maskPath = path12.join(outDir, `${pair.name}-mask.png`);
3381
- const annotatedPath = path12.join(outDir, `${pair.name}-annotated-source.png`);
3382
- const overlayPath = path12.join(outDir, `${pair.name}-overlay.png`);
3383
- const metaPath = path12.join(outDir, `${pair.name}.annotated-progress.json`);
3498
+ const framePath = path13.join(outDir, `${pair.name}-frame.png`);
3499
+ const maskPath = path13.join(outDir, `${pair.name}-mask.png`);
3500
+ const annotatedPath = path13.join(outDir, `${pair.name}-annotated-source.png`);
3501
+ const overlayPath = path13.join(outDir, `${pair.name}-overlay.png`);
3502
+ const metaPath = path13.join(outDir, `${pair.name}.annotated-progress.json`);
3384
3503
  await writePng(framePath, clean2);
3385
3504
  await writePng(maskPath, converted.png);
3386
3505
  await writePng(annotatedPath, annotated);
@@ -3427,7 +3546,7 @@ async function uiMasks(opts, log) {
3427
3546
  },
3428
3547
  overlay: overlayPath
3429
3548
  };
3430
- await fs13.writeFile(metaPath, `${JSON.stringify(meta, null, 2)}
3549
+ await fs14.writeFile(metaPath, `${JSON.stringify(meta, null, 2)}
3431
3550
  `);
3432
3551
  results.push(meta);
3433
3552
  const fb = converted.bbox;
@@ -3443,8 +3562,8 @@ async function uiMasks(opts, log) {
3443
3562
  );
3444
3563
  }
3445
3564
  }
3446
- const indexPath = path12.join(outDir, "annotated-progress.json");
3447
- await fs13.writeFile(indexPath, `${JSON.stringify({ input, loosened, pairs: results }, null, 2)}
3565
+ const indexPath = path13.join(outDir, "annotated-progress.json");
3566
+ await fs14.writeFile(indexPath, `${JSON.stringify({ input, loosened, pairs: results }, null, 2)}
3448
3567
  `);
3449
3568
  log.plain("");
3450
3569
  log.success(`Wrote ${pairs.length} frame+mask pair(s) \u2192 ${outDir} (index: ${indexPath}).`);
@@ -3570,7 +3689,7 @@ async function uiTextColor(opts, log) {
3570
3689
  };
3571
3690
  process.stdout.write(`${JSON.stringify(result, null, 2)}
3572
3691
  `);
3573
- if (opts.out) await fs13.writeFile(opts.out, `${JSON.stringify(result, null, 2)}
3692
+ if (opts.out) await fs14.writeFile(opts.out, `${JSON.stringify(result, null, 2)}
3574
3693
  `);
3575
3694
  if (opts.cropPath) log.dim(` crop \u2192 ${opts.cropPath}`);
3576
3695
  }
@@ -3602,7 +3721,7 @@ async function uiTrim(opts, log) {
3602
3721
  const sidecar = computeBBoxes(trimmed);
3603
3722
  const speckleAllowed = !!(speckle && speckle.ratio > SPECKLE_MAX_RATIO);
3604
3723
  const sidecarPath = outPath.replace(/\.png$/i, "") + ".bbox.json";
3605
- await fs13.writeFile(
3724
+ await fs14.writeFile(
3606
3725
  sidecarPath,
3607
3726
  JSON.stringify(speckleAllowed ? { ...sidecar, speckleAllowed: true } : sidecar, null, 2)
3608
3727
  );
@@ -3709,7 +3828,7 @@ async function uiPlate(opts, log) {
3709
3828
  fail("No interior found \u2014 the image is fully transparent (or erode ate everything). Check --in / lower --erode.");
3710
3829
  }
3711
3830
  await writePng(outPath, out);
3712
- const name = path12.basename(outPath);
3831
+ const name = path13.basename(outPath);
3713
3832
  log.plain(c.bold("genex ui plate"));
3714
3833
  log.success(`${outPath} ${W}x${H}, interior ${(count / (W * H) * 100).toFixed(1)}% (erode ${erode}px)`);
3715
3834
  log.dim(" Wire it as the plate's silhouette (same box as the frame <img>, plate UNDER the art):");
@@ -3874,13 +3993,13 @@ async function walkFiles(dir) {
3874
3993
  const out = [];
3875
3994
  let entries;
3876
3995
  try {
3877
- entries = await fs13.readdir(dir, { withFileTypes: true });
3996
+ entries = await fs14.readdir(dir, { withFileTypes: true });
3878
3997
  } catch {
3879
3998
  return out;
3880
3999
  }
3881
4000
  for (const entry of entries) {
3882
4001
  if (entry.name === "node_modules" || entry.name.startsWith(".")) continue;
3883
- const p = path12.join(dir, entry.name);
4002
+ const p = path13.join(dir, entry.name);
3884
4003
  if (entry.isDirectory()) out.push(...await walkFiles(p));
3885
4004
  else out.push(p);
3886
4005
  }
@@ -3889,7 +4008,7 @@ async function walkFiles(dir) {
3889
4008
  async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
3890
4009
  const viewportFindings = [];
3891
4010
  try {
3892
- const indexHtml = await fs13.readFile(path12.join(cwd, "index.html"), "utf8");
4011
+ const indexHtml = await fs14.readFile(path13.join(cwd, "index.html"), "utf8");
3893
4012
  if (!/<meta[^>]+name=["']viewport["']/i.test(indexHtml)) {
3894
4013
  viewportFindings.push({
3895
4014
  kind: "viewport-meta",
@@ -3903,28 +4022,28 @@ async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
3903
4022
  }
3904
4023
  } catch {
3905
4024
  }
3906
- const absAssets = path12.resolve(cwd, assetDir);
4025
+ const absAssets = path13.resolve(cwd, assetDir);
3907
4026
  try {
3908
- if (!(await fs13.stat(absAssets)).isDirectory()) {
4027
+ if (!(await fs14.stat(absAssets)).isDirectory()) {
3909
4028
  return viewportFindings.length > 0 ? viewportFindings : null;
3910
4029
  }
3911
4030
  } catch {
3912
4031
  return viewportFindings.length > 0 ? viewportFindings : null;
3913
4032
  }
3914
- const srcFiles = (await walkFiles(path12.resolve(cwd, srcDir))).filter(
3915
- (p) => AUDIT_SRC_EXTS.has(path12.extname(p).toLowerCase())
4033
+ const srcFiles = (await walkFiles(path13.resolve(cwd, srcDir))).filter(
4034
+ (p) => AUDIT_SRC_EXTS.has(path13.extname(p).toLowerCase())
3916
4035
  );
3917
4036
  try {
3918
- for (const name of await fs13.readdir(cwd)) {
3919
- const ext = path12.extname(name).toLowerCase();
3920
- if (ext === ".html" || ext === ".css") srcFiles.push(path12.join(cwd, name));
4037
+ for (const name of await fs14.readdir(cwd)) {
4038
+ const ext = path13.extname(name).toLowerCase();
4039
+ if (ext === ".html" || ext === ".css") srcFiles.push(path13.join(cwd, name));
3921
4040
  }
3922
4041
  } catch {
3923
4042
  }
3924
4043
  const sources = [];
3925
4044
  for (const p of srcFiles) {
3926
4045
  try {
3927
- sources.push({ rel: path12.relative(cwd, p), text: await fs13.readFile(p, "utf8") });
4046
+ sources.push({ rel: path13.relative(cwd, p), text: await fs14.readFile(p, "utf8") });
3928
4047
  } catch {
3929
4048
  }
3930
4049
  }
@@ -3934,11 +4053,11 @@ async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
3934
4053
  const metaByName = /* @__PURE__ */ new Map();
3935
4054
  const bboxByPng = /* @__PURE__ */ new Map();
3936
4055
  for (const p of assetFiles) {
3937
- const base = path12.basename(p);
4056
+ const base = path13.basename(p);
3938
4057
  const metaMatch = /^(.+)\.annotated-progress\.json$/.exec(base);
3939
4058
  if (metaMatch) {
3940
4059
  try {
3941
- const meta = JSON.parse(await fs13.readFile(p, "utf8"));
4060
+ const meta = JSON.parse(await fs14.readFile(p, "utf8"));
3942
4061
  metaByName.set(metaMatch[1], {
3943
4062
  cleanCrop: meta.clean?.crop ?? null,
3944
4063
  loosened: meta.loosened === true
@@ -3949,7 +4068,7 @@ async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
3949
4068
  }
3950
4069
  if (base.endsWith(".bbox.json")) {
3951
4070
  try {
3952
- const sidecar = JSON.parse(await fs13.readFile(p, "utf8"));
4071
+ const sidecar = JSON.parse(await fs14.readFile(p, "utf8"));
3953
4072
  if (sidecar.sheetBBox) bboxByPng.set(base.replace(/\.bbox\.json$/, ".png"), sidecar.sheetBBox);
3954
4073
  } catch {
3955
4074
  }
@@ -3958,7 +4077,7 @@ async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
3958
4077
  for (const [name, meta] of metaByName) {
3959
4078
  if (!meta.cleanCrop || !referenced(`${name}-mask.png`)) continue;
3960
4079
  for (const p of assetFiles) {
3961
- const base = path12.basename(p);
4080
+ const base = path13.basename(p);
3962
4081
  if (!base.toLowerCase().endsWith(".png")) continue;
3963
4082
  if (base !== `${name}.png` && !base.startsWith(`${name}-`)) continue;
3964
4083
  if (/-mask\.png$|-frame\.png$|-overlay\.png$|-annotated-source\.png$/.test(base)) continue;
@@ -3982,7 +4101,7 @@ async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
3982
4101
  }
3983
4102
  const pngByBase = /* @__PURE__ */ new Map();
3984
4103
  for (const p of assetFiles) {
3985
- const base = path12.basename(p);
4104
+ const base = path13.basename(p);
3986
4105
  if (base.toLowerCase().endsWith(".png")) pngByBase.set(base, p);
3987
4106
  }
3988
4107
  for (const [maskBase, maskPath] of pngByBase) {
@@ -3995,8 +4114,8 @@ async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
3995
4114
  let frame;
3996
4115
  let mask;
3997
4116
  try {
3998
- frame = PNG2.sync.read(await fs13.readFile(framePath));
3999
- mask = PNG2.sync.read(await fs13.readFile(maskPath));
4117
+ frame = PNG2.sync.read(await fs14.readFile(framePath));
4118
+ mask = PNG2.sync.read(await fs14.readFile(maskPath));
4000
4119
  } catch {
4001
4120
  continue;
4002
4121
  }
@@ -4015,7 +4134,7 @@ async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
4015
4134
  if (!referenced(base)) continue;
4016
4135
  let png;
4017
4136
  try {
4018
- png = PNG2.sync.read(await fs13.readFile(p));
4137
+ png = PNG2.sync.read(await fs14.readFile(p));
4019
4138
  } catch {
4020
4139
  continue;
4021
4140
  }
@@ -4035,7 +4154,7 @@ async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
4035
4154
  }
4036
4155
  const maskReported = /* @__PURE__ */ new Set();
4037
4156
  for (const p of assetFiles) {
4038
- const m = /^(.+)\.annotated-progress\.json$/.exec(path12.basename(p));
4157
+ const m = /^(.+)\.annotated-progress\.json$/.exec(path13.basename(p));
4039
4158
  if (!m) continue;
4040
4159
  const maskBase = `${m[1]}-mask.png`;
4041
4160
  if (!referenced(maskBase)) {
@@ -4047,14 +4166,14 @@ async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
4047
4166
  }
4048
4167
  }
4049
4168
  for (const p of assetFiles) {
4050
- const base = path12.basename(p);
4169
+ const base = path13.basename(p);
4051
4170
  if (!base.toLowerCase().endsWith(".png")) continue;
4052
4171
  if (/-annotated-source\.png$|-overlay\.png$/.test(base)) continue;
4053
4172
  if (maskReported.has(base)) continue;
4054
4173
  if (!referenced(base)) {
4055
4174
  findings.push({
4056
4175
  kind: "unwired-sprite",
4057
- message: `${path12.relative(cwd, p)} is on disk but never referenced in ${srcDir}/index.html/CSS \u2014 wire it, or record the one-line reason it was cut. (Computed-string references are invisible here \u2014 check.)`
4176
+ message: `${path13.relative(cwd, p)} is on disk but never referenced in ${srcDir}/index.html/CSS \u2014 wire it, or record the one-line reason it was cut. (Computed-string references are invisible here \u2014 check.)`
4058
4177
  });
4059
4178
  }
4060
4179
  }
@@ -4188,7 +4307,7 @@ async function printUiAuditPreflight(log) {
4188
4307
  }
4189
4308
  async function printPipelineStatePreflight(log, cwd = process.cwd()) {
4190
4309
  try {
4191
- const design = await fs14.readFile(path13.join(cwd, "DESIGN.md"), "utf8").catch(() => "");
4310
+ const design = await fs15.readFile(path14.join(cwd, "DESIGN.md"), "utf8").catch(() => "");
4192
4311
  const warnings = [];
4193
4312
  if (design.length > 0 && !/##\s*build plan & status/i.test(design)) {
4194
4313
  warnings.push(
@@ -4196,7 +4315,7 @@ async function printPipelineStatePreflight(log, cwd = process.cwd()) {
4196
4315
  );
4197
4316
  }
4198
4317
  if (!/player character:/i.test(design)) {
4199
- const hasCharacter = await fs14.access(path13.join(cwd, "public", "assets", "meshy-character.json")).then(() => true, () => false);
4318
+ const hasCharacter = await fs15.access(path14.join(cwd, "public", "assets", "meshy-character.json")).then(() => true, () => false);
4200
4319
  if (!hasCharacter && await loadsPlayerBody(cwd)) {
4201
4320
  warnings.push(
4202
4321
  `Player character is the stock avatar \u2014 no generated character is wired. The game's own generated character is the player's body wherever a human body appears on screen, first-person included (genex-ai-character). Generate it, or record "Player character: VRM \u2014 <reason>" in DESIGN.md (no human body in this game / out of credits / player declined).`
@@ -4210,12 +4329,12 @@ async function printPipelineStatePreflight(log, cwd = process.cwd()) {
4210
4329
  async function loadsPlayerBody(cwd) {
4211
4330
  const BODY_LOADERS = /\bloadPlayerCharacter\s*\(|\bloadVrm(?:Clone)?\s*\(/;
4212
4331
  try {
4213
- const entries = await fs14.readdir(path13.join(cwd, "src"), { recursive: true });
4332
+ const entries = await fs15.readdir(path14.join(cwd, "src"), { recursive: true });
4214
4333
  for (const rel of entries) {
4215
4334
  if (rel.includes("node_modules")) continue;
4216
- if (rel.split(path13.sep)[0] === "controllers" || rel.startsWith("controllers/")) continue;
4335
+ if (rel.split(path14.sep)[0] === "controllers" || rel.startsWith("controllers/")) continue;
4217
4336
  if (!/\.(ts|tsx|js|jsx)$/.test(rel)) continue;
4218
- const text = await fs14.readFile(path13.join(cwd, "src", rel), "utf8").catch(() => "");
4337
+ const text = await fs15.readFile(path14.join(cwd, "src", rel), "utf8").catch(() => "");
4219
4338
  if (BODY_LOADERS.test(text)) return true;
4220
4339
  }
4221
4340
  } catch {
@@ -4239,9 +4358,9 @@ async function deployGame(ctx, opts, log) {
4239
4358
  }
4240
4359
  log.success("Built.");
4241
4360
  }
4242
- const distDir = path13.join(cwd, "dist");
4361
+ const distDir = path14.join(cwd, "dist");
4243
4362
  const siteDir = await isDir2(distDir) ? distDir : cwd;
4244
- const rel = path13.relative(cwd, siteDir) || ".";
4363
+ const rel = path14.relative(cwd, siteDir) || ".";
4245
4364
  if (siteDir === cwd) await writeGitignore(cwd, log);
4246
4365
  const files = await collectFiles(siteDir);
4247
4366
  if (files.length === 0) {
@@ -4308,7 +4427,7 @@ async function deployGame(ctx, opts, log) {
4308
4427
  }
4309
4428
  async function hasBuildScript(cwd) {
4310
4429
  try {
4311
- const pkg = JSON.parse(await fs14.readFile(path13.join(cwd, "package.json"), "utf8"));
4430
+ const pkg = JSON.parse(await fs15.readFile(path14.join(cwd, "package.json"), "utf8"));
4312
4431
  return Boolean(pkg.scripts?.build);
4313
4432
  } catch {
4314
4433
  return false;
@@ -4317,12 +4436,12 @@ async function hasBuildScript(cwd) {
4317
4436
  async function collectFiles(root) {
4318
4437
  const out = [];
4319
4438
  const walk2 = async (dir, prefix) => {
4320
- for (const e of await fs14.readdir(dir, { withFileTypes: true })) {
4439
+ for (const e of await fs15.readdir(dir, { withFileTypes: true })) {
4321
4440
  const relPath = prefix ? `${prefix}/${e.name}` : e.name;
4322
4441
  if (e.isDirectory()) {
4323
- if (!EXCLUDE_DIRS.has(e.name)) await walk2(path13.join(dir, e.name), relPath);
4442
+ if (!EXCLUDE_DIRS.has(e.name)) await walk2(path14.join(dir, e.name), relPath);
4324
4443
  } else if (e.isFile() && !isSecretEnvFile(e.name)) {
4325
- out.push({ relPath, bytes: await fs14.readFile(path13.join(dir, e.name)) });
4444
+ out.push({ relPath, bytes: await fs15.readFile(path14.join(dir, e.name)) });
4326
4445
  }
4327
4446
  }
4328
4447
  };
@@ -4560,7 +4679,7 @@ async function pushWorktree(cwd, pushUrl, managed, log, branch = "main") {
4560
4679
  log.error("Couldn't save your game's source \u2014 please try again.");
4561
4680
  return false;
4562
4681
  };
4563
- const gitDir = await fs14.mkdtemp(path13.join(os5.tmpdir(), "genex-source-"));
4682
+ const gitDir = await fs15.mkdtemp(path14.join(os5.tmpdir(), "genex-source-"));
4564
4683
  const base = { GIT_DIR: gitDir };
4565
4684
  if (urlHasEmbeddedCredentials(pushUrl)) {
4566
4685
  base.GIT_CONFIG_COUNT = "1";
@@ -4575,12 +4694,12 @@ async function pushWorktree(cwd, pushUrl, managed, log, branch = "main") {
4575
4694
  };
4576
4695
  try {
4577
4696
  if ((await run("git", ["init", "-q"], base)).code !== 0) return failed();
4578
- await fs14.writeFile(
4579
- path13.join(gitDir, "info", "exclude"),
4697
+ await fs15.writeFile(
4698
+ path14.join(gitDir, "info", "exclude"),
4580
4699
  // .env* are secrets — never publish them; `!` keeps the non-secret template.
4581
4700
  ["node_modules/", "dist/", ".git/", ".genex/", ".env", ".env.*", "!.env.example", ""].join("\n")
4582
4701
  );
4583
- const env = { ...base, GIT_WORK_TREE: cwd, GIT_INDEX_FILE: path13.join(gitDir, "index-source") };
4702
+ const env = { ...base, GIT_WORK_TREE: cwd, GIT_INDEX_FILE: path14.join(gitDir, "index-source") };
4584
4703
  let lfs = (await run("git", ["lfs", "version"], base)).code !== 0 ? false : true;
4585
4704
  if (!lfs) {
4586
4705
  log.step("Installing git-lfs (keeps large binary assets out of the source push)\u2026");
@@ -4631,7 +4750,7 @@ async function pushWorktree(cwd, pushUrl, managed, log, branch = "main") {
4631
4750
  } catch {
4632
4751
  return failed();
4633
4752
  } finally {
4634
- await fs14.rm(gitDir, { recursive: true, force: true }).catch(() => {
4753
+ await fs15.rm(gitDir, { recursive: true, force: true }).catch(() => {
4635
4754
  });
4636
4755
  }
4637
4756
  }
@@ -4667,7 +4786,7 @@ async function fetchPushUrl(ctx, log) {
4667
4786
  }
4668
4787
  async function isDir2(p) {
4669
4788
  try {
4670
- return (await fs14.stat(p)).isDirectory();
4789
+ return (await fs15.stat(p)).isDirectory();
4671
4790
  } catch {
4672
4791
  return false;
4673
4792
  }
@@ -4802,17 +4921,17 @@ async function promoteBuild(apiUrl, projectId, token, log) {
4802
4921
  }
4803
4922
 
4804
4923
  // src/lib/detect-features.ts
4805
- import fs16 from "fs/promises";
4806
- import path15 from "path";
4924
+ import fs17 from "fs/promises";
4925
+ import path16 from "path";
4807
4926
 
4808
4927
  // src/lib/generation-ledger.ts
4809
- import fs15 from "fs/promises";
4810
- import path14 from "path";
4811
- var ledgerPath = (cwd) => path14.join(cwd, ".genex", "generations.ndjson");
4928
+ import fs16 from "fs/promises";
4929
+ import path15 from "path";
4930
+ var ledgerPath = (cwd) => path15.join(cwd, ".genex", "generations.ndjson");
4812
4931
  async function append(cwd, event) {
4813
4932
  try {
4814
- await fs15.access(path14.join(cwd, ".genex"));
4815
- await fs15.appendFile(ledgerPath(cwd), `${JSON.stringify(event)}
4933
+ await fs16.access(path15.join(cwd, ".genex"));
4934
+ await fs16.appendFile(ledgerPath(cwd), `${JSON.stringify(event)}
4816
4935
  `, "utf8");
4817
4936
  } catch {
4818
4937
  }
@@ -4820,7 +4939,7 @@ async function append(cwd, event) {
4820
4939
  async function readLedger(cwd = process.cwd()) {
4821
4940
  let raw;
4822
4941
  try {
4823
- raw = await fs15.readFile(ledgerPath(cwd), "utf8");
4942
+ raw = await fs16.readFile(ledgerPath(cwd), "utf8");
4824
4943
  } catch {
4825
4944
  return [];
4826
4945
  }
@@ -4874,7 +4993,7 @@ async function countFailed(kind, cwd = process.cwd()) {
4874
4993
  // src/lib/detect-features.ts
4875
4994
  async function detectEmbedSdkVersion(cwd = process.cwd()) {
4876
4995
  try {
4877
- const raw = await fs16.readFile(path15.join(cwd, "package.json"), "utf8");
4996
+ const raw = await fs17.readFile(path16.join(cwd, "package.json"), "utf8");
4878
4997
  const pkg = JSON.parse(raw);
4879
4998
  const version = pkg.dependencies?.["@genex-ai/embed-sdk"] ?? pkg.devDependencies?.["@genex-ai/embed-sdk"];
4880
4999
  return typeof version === "string" && version ? version : null;
@@ -4884,7 +5003,7 @@ async function detectEmbedSdkVersion(cwd = process.cwd()) {
4884
5003
  }
4885
5004
  async function detectMultiplayer(cwd = process.cwd()) {
4886
5005
  try {
4887
- const raw = await fs16.readFile(path15.join(cwd, "package.json"), "utf8");
5006
+ const raw = await fs17.readFile(path16.join(cwd, "package.json"), "utf8");
4888
5007
  const pkg = JSON.parse(raw);
4889
5008
  return Boolean(
4890
5009
  pkg.dependencies?.["@genex-ai/multiplayer"] ?? pkg.devDependencies?.["@genex-ai/multiplayer"]
@@ -4896,7 +5015,7 @@ async function detectMultiplayer(cwd = process.cwd()) {
4896
5015
  async function detectMatchmaking(log, cwd = process.cwd()) {
4897
5016
  let pkg;
4898
5017
  try {
4899
- pkg = JSON.parse(await fs16.readFile(path15.join(cwd, "package.json"), "utf8"));
5018
+ pkg = JSON.parse(await fs17.readFile(path16.join(cwd, "package.json"), "utf8"));
4900
5019
  } catch (err) {
4901
5020
  log.dim(` (skipping matchmaking \u2014 couldn't read package.json: ${String(err)})`);
4902
5021
  return null;
@@ -4914,15 +5033,15 @@ async function detectMatchmaking(log, cwd = process.cwd()) {
4914
5033
  var TOUCH_KIT_MARKERS = /controllers\/touch\/|controllers\/character\/touch-joystick|TouchJoystick|VirtualButton|DragZone|RotateOverlay/;
4915
5034
  async function detectMobileControls(cwd = process.cwd()) {
4916
5035
  try {
4917
- const raw = await fs16.readFile(path15.join(cwd, "package.json"), "utf8");
5036
+ const raw = await fs17.readFile(path16.join(cwd, "package.json"), "utf8");
4918
5037
  const pkg = JSON.parse(raw);
4919
5038
  if (pkg.genex?.mobileControls === true) return true;
4920
5039
  } catch {
4921
5040
  }
4922
- const srcDir = path15.join(cwd, "src");
5041
+ const srcDir = path16.join(cwd, "src");
4923
5042
  let entries;
4924
5043
  try {
4925
- entries = await fs16.readdir(srcDir, { recursive: true });
5044
+ entries = await fs17.readdir(srcDir, { recursive: true });
4926
5045
  } catch {
4927
5046
  return false;
4928
5047
  }
@@ -4930,7 +5049,7 @@ async function detectMobileControls(cwd = process.cwd()) {
4930
5049
  if (rel.includes("node_modules")) continue;
4931
5050
  if (!/\.(ts|js|mts|mjs|tsx|jsx)$/.test(rel)) continue;
4932
5051
  try {
4933
- const content = await fs16.readFile(path15.join(srcDir, rel), "utf8");
5052
+ const content = await fs17.readFile(path16.join(srcDir, rel), "utf8");
4934
5053
  if (TOUCH_KIT_MARKERS.test(content)) return true;
4935
5054
  } catch {
4936
5055
  }
@@ -4939,10 +5058,10 @@ async function detectMobileControls(cwd = process.cwd()) {
4939
5058
  }
4940
5059
  var GAME_STATE_CALLS = /savePlayerState|saveWorldState|submitScore/;
4941
5060
  async function detectGameStateUsage(cwd = process.cwd()) {
4942
- const srcDir = path15.join(cwd, "src");
5061
+ const srcDir = path16.join(cwd, "src");
4943
5062
  let entries;
4944
5063
  try {
4945
- entries = await fs16.readdir(srcDir, { recursive: true });
5064
+ entries = await fs17.readdir(srcDir, { recursive: true });
4946
5065
  } catch {
4947
5066
  return false;
4948
5067
  }
@@ -4950,7 +5069,7 @@ async function detectGameStateUsage(cwd = process.cwd()) {
4950
5069
  if (rel.includes("node_modules")) continue;
4951
5070
  if (!/\.(ts|js|mts|mjs|tsx|jsx)$/.test(rel)) continue;
4952
5071
  try {
4953
- const content = await fs16.readFile(path15.join(srcDir, rel), "utf8");
5072
+ const content = await fs17.readFile(path16.join(srcDir, rel), "utf8");
4954
5073
  if (GAME_STATE_CALLS.test(content)) return true;
4955
5074
  } catch {
4956
5075
  }
@@ -4999,19 +5118,19 @@ async function detectSurfaceScan(cwd = process.cwd()) {
4999
5118
  depthRange: [],
5000
5119
  mediaElementAudio: []
5001
5120
  };
5002
- const srcDir = path15.join(cwd, "src");
5121
+ const srcDir = path16.join(cwd, "src");
5003
5122
  let entries;
5004
5123
  try {
5005
- entries = await fs16.readdir(srcDir, { recursive: true });
5124
+ entries = await fs17.readdir(srcDir, { recursive: true });
5006
5125
  } catch {
5007
5126
  return found;
5008
5127
  }
5009
5128
  for (const nativeRel of entries) {
5010
5129
  if (nativeRel.includes("node_modules")) continue;
5011
5130
  if (!/\.(ts|js|mts|mjs|tsx|jsx)$/.test(nativeRel)) continue;
5012
- const raw = await fs16.readFile(path15.join(srcDir, nativeRel), "utf8").catch(() => "");
5131
+ const raw = await fs17.readFile(path16.join(srcDir, nativeRel), "utf8").catch(() => "");
5013
5132
  if (!raw) continue;
5014
- const rel = nativeRel.split(path15.sep).join("/");
5133
+ const rel = nativeRel.split(path16.sep).join("/");
5015
5134
  const content = raw.replace(/\/\*[\s\S]*?\*\//g, (m2) => m2.replace(/[^\n]/g, " ")).replace(/(^|[^:])\/\/[^\n]*/gm, (m2, p1) => p1 + " ".repeat(m2.length - p1.length));
5016
5135
  const repeatRe = /\.repeat\.set\(\s*(-?\d+(?:\.\d+)?)\s*,\s*(-?\d+(?:\.\d+)?)\s*\)/g;
5017
5136
  let m;
@@ -5069,25 +5188,25 @@ async function detectGenerationAudit(cwd = process.cwd()) {
5069
5188
  let haystack = "";
5070
5189
  const read = async (file) => {
5071
5190
  try {
5072
- haystack += await fs16.readFile(file, "utf8");
5191
+ haystack += await fs17.readFile(file, "utf8");
5073
5192
  } catch {
5074
5193
  }
5075
5194
  };
5076
5195
  try {
5077
- for (const entry of await fs16.readdir(cwd, { withFileTypes: true })) {
5196
+ for (const entry of await fs17.readdir(cwd, { withFileTypes: true })) {
5078
5197
  if (entry.isFile() && /\.(ts|tsx|js|jsx|css|html|json)$/.test(entry.name)) {
5079
- await read(path15.join(cwd, entry.name));
5198
+ await read(path16.join(cwd, entry.name));
5080
5199
  }
5081
5200
  }
5082
5201
  } catch {
5083
5202
  }
5084
5203
  for (const sub of ["src", "public"]) {
5085
5204
  try {
5086
- const entries = await fs16.readdir(path15.join(cwd, sub), { recursive: true });
5205
+ const entries = await fs17.readdir(path16.join(cwd, sub), { recursive: true });
5087
5206
  for (const rel of entries) {
5088
5207
  if (rel.includes("node_modules")) continue;
5089
5208
  if (!/\.(ts|tsx|js|jsx|css|html|json|txt)$/.test(rel)) continue;
5090
- await read(path15.join(cwd, sub, rel));
5209
+ await read(path16.join(cwd, sub, rel));
5091
5210
  }
5092
5211
  } catch {
5093
5212
  }
@@ -5219,7 +5338,7 @@ async function borrowEvidence(meta, cwd) {
5219
5338
  } catch {
5220
5339
  return true;
5221
5340
  }
5222
- const gitConfig = await fs16.readFile(path15.join(cwd, ".git", "config"), "utf8").catch(() => "");
5341
+ const gitConfig = await fs17.readFile(path16.join(cwd, ".git", "config"), "utf8").catch(() => "");
5223
5342
  for (const m of gitConfig.matchAll(/url\s*=\s*(\S+)/g)) {
5224
5343
  try {
5225
5344
  const u = new URL(m[1]);
@@ -5227,7 +5346,7 @@ async function borrowEvidence(meta, cwd) {
5227
5346
  } catch {
5228
5347
  }
5229
5348
  }
5230
- const readme = await fs16.readFile(path15.join(cwd, "README.md"), "utf8").catch(() => "");
5349
+ const readme = await fs17.readFile(path16.join(cwd, "README.md"), "utf8").catch(() => "");
5231
5350
  return readme.includes(host) || /\b(upstream|originally by|ported from|borrowed from)\b/i.test(readme);
5232
5351
  }
5233
5352
 
@@ -5477,8 +5596,8 @@ async function runPromote(opts) {
5477
5596
  }
5478
5597
 
5479
5598
  // src/commands/generate.ts
5480
- import fs17 from "fs/promises";
5481
- import path16 from "path";
5599
+ import fs18 from "fs/promises";
5600
+ import path17 from "path";
5482
5601
  import { PNG as PNG4 } from "pngjs";
5483
5602
 
5484
5603
  // src/lib/glass.ts
@@ -5648,7 +5767,7 @@ var isRemoteRef = (value) => /^(https?:\/\/|data:)/i.test(value);
5648
5767
  async function inlineLocalImage(filePath, flag) {
5649
5768
  let bytes;
5650
5769
  try {
5651
- bytes = await fs17.readFile(filePath);
5770
+ bytes = await fs18.readFile(filePath);
5652
5771
  } catch {
5653
5772
  return { ok: false, error: `Couldn't read the ${flag} file at ${filePath}.` };
5654
5773
  }
@@ -5658,7 +5777,7 @@ async function inlineLocalImage(filePath, flag) {
5658
5777
  error: `${flag} file is ${(bytes.length / 1048576).toFixed(1)} MB \u2014 over the ~4 MB inline limit. Downscale/compress it first, or pass an asset URL instead.`
5659
5778
  };
5660
5779
  }
5661
- const mime = IMAGE_MIME_BY_EXT[path16.extname(filePath).toLowerCase()] ?? "image/png";
5780
+ const mime = IMAGE_MIME_BY_EXT[path17.extname(filePath).toLowerCase()] ?? "image/png";
5662
5781
  return { ok: true, dataUri: `data:${mime};base64,${bytes.toString("base64")}` };
5663
5782
  }
5664
5783
  var SKYBOX_ENVIRONMENT_SUFFIX = ". The image contains ONLY sky: cloud, atmosphere, light, weather and distant haze at the horizon. Every structure, object, plant and ground surface is outside the frame.";
@@ -5854,7 +5973,7 @@ async function runGenerate(kind, opts) {
5854
5973
  return;
5855
5974
  }
5856
5975
  try {
5857
- const bytes = await fs17.readFile(opts.inpaintUrl);
5976
+ const bytes = await fs18.readFile(opts.inpaintUrl);
5858
5977
  opts = { ...opts, inpaintUrl: `data:image/png;base64,${bytes.toString("base64")}` };
5859
5978
  } catch {
5860
5979
  log.error(`Couldn't read the --inpaint mask at ${opts.inpaintUrl}.`);
@@ -5978,7 +6097,7 @@ async function reportGlassTerminal(view, outDir, log, json) {
5978
6097
  return;
5979
6098
  }
5980
6099
  await recordTerminal(view.id, "completed", files.map((f) => f.url));
5981
- await fs17.mkdir(outDir, { recursive: true });
6100
+ await fs18.mkdir(outDir, { recursive: true });
5982
6101
  const solved = [];
5983
6102
  for (let i = 0; i < files.length; i++) {
5984
6103
  const f = files[i];
@@ -6010,8 +6129,8 @@ async function reportGlassTerminal(view, outDir, log, json) {
6010
6129
  });
6011
6130
  continue;
6012
6131
  }
6013
- const outPath = path16.join(outDir, `glass-${i + 1}.png`);
6014
- await fs17.writeFile(outPath, PNG4.sync.write(r.png));
6132
+ const outPath = path17.join(outDir, `glass-${i + 1}.png`);
6133
+ await fs18.writeFile(outPath, PNG4.sync.write(r.png));
6015
6134
  solved.push({
6016
6135
  path: outPath,
6017
6136
  url: f.url,
@@ -6605,8 +6724,8 @@ async function toRow(e, v, cwd) {
6605
6724
  }
6606
6725
 
6607
6726
  // src/commands/controller.ts
6608
- import fs19 from "fs/promises";
6609
- import path18 from "path";
6727
+ import fs20 from "fs/promises";
6728
+ import path19 from "path";
6610
6729
 
6611
6730
  // ../../packages/meshy-animation-catalog/src/index.ts
6612
6731
  import { createHash } from "crypto";
@@ -15736,9 +15855,9 @@ function searchMeshyAnimations(query, options = {}) {
15736
15855
  }
15737
15856
 
15738
15857
  // src/lib/anims.ts
15739
- import fs18 from "fs/promises";
15740
- import path17 from "path";
15741
- var ANIMS_DEST = path17.join("public", "assets", "anims");
15858
+ import fs19 from "fs/promises";
15859
+ import path18 from "path";
15860
+ var ANIMS_DEST = path18.join("public", "assets", "anims");
15742
15861
  var HIDDEN_TAG = "reference";
15743
15862
  async function runAnims(opts) {
15744
15863
  const log = createLogger({ quiet: opts.quiet });
@@ -15754,7 +15873,7 @@ async function runAnims(opts) {
15754
15873
  printCatalog(log, manifest, selectors);
15755
15874
  return;
15756
15875
  }
15757
- const controllerMarker = path17.join(root, "src", "controllers", "character");
15876
+ const controllerMarker = path18.join(root, "src", "controllers", "character");
15758
15877
  if (!await exists2(controllerMarker)) {
15759
15878
  log.error(
15760
15879
  `No character controller in this game (missing ${c.cyan("src/controllers/character/")}).`
@@ -15763,11 +15882,11 @@ async function runAnims(opts) {
15763
15882
  process.exitCode = 1;
15764
15883
  return;
15765
15884
  }
15766
- const destDir = path17.join(root, ANIMS_DEST);
15767
- const gameManifestPath = path17.join(destDir, "manifest.json");
15885
+ const destDir = path18.join(root, ANIMS_DEST);
15886
+ const gameManifestPath = path18.join(destDir, "manifest.json");
15768
15887
  if (opts.reset) {
15769
- await fs18.rm(destDir, { recursive: true, force: true });
15770
- log.step(`Cleared ${c.cyan(ANIMS_DEST + path17.sep)} (--reset)`);
15888
+ await fs19.rm(destDir, { recursive: true, force: true });
15889
+ log.step(`Cleared ${c.cyan(ANIMS_DEST + path18.sep)} (--reset)`);
15771
15890
  }
15772
15891
  if (selectors.length === 0) {
15773
15892
  const installed = await readGameManifest(gameManifestPath);
@@ -15805,35 +15924,35 @@ async function runAnims(opts) {
15805
15924
  }
15806
15925
  }
15807
15926
  const wanted = [...byName.values()].sort((a, b) => a.name.localeCompare(b.name));
15808
- const cacheDir = path17.join(
15927
+ const cacheDir = path18.join(
15809
15928
  opts.cacheDir ?? getAnimsCacheDir(),
15810
15929
  `${manifest.library}-v${manifest.version}`
15811
15930
  );
15812
- await fs18.mkdir(cacheDir, { recursive: true });
15813
- await fs18.mkdir(destDir, { recursive: true });
15931
+ await fs19.mkdir(cacheDir, { recursive: true });
15932
+ await fs19.mkdir(destDir, { recursive: true });
15814
15933
  const base = getAnimsBase(opts.animsBase);
15815
15934
  let installedCount = 0;
15816
15935
  let presentCount = 0;
15817
15936
  let addedBytes = 0;
15818
15937
  const failures = [];
15819
15938
  for (const entry of wanted) {
15820
- const dest = path17.join(destDir, entry.file);
15939
+ const dest = path18.join(destDir, entry.file);
15821
15940
  if (await hasSize(dest, entry.bytes)) {
15822
15941
  presentCount++;
15823
15942
  continue;
15824
15943
  }
15825
15944
  try {
15826
- const cached = path17.join(cacheDir, entry.file);
15945
+ const cached = path18.join(cacheDir, entry.file);
15827
15946
  if (!await hasSize(cached, entry.bytes)) {
15828
15947
  const res = await fetch(base + entry.file);
15829
15948
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
15830
15949
  const buf = Buffer.from(await res.arrayBuffer());
15831
- await fs18.writeFile(cached, buf);
15950
+ await fs19.writeFile(cached, buf);
15832
15951
  }
15833
- await fs18.copyFile(cached, dest);
15952
+ await fs19.copyFile(cached, dest);
15834
15953
  installedCount++;
15835
15954
  addedBytes += entry.bytes;
15836
- log.dim(` ${path17.join(ANIMS_DEST, entry.file)} (${formatMb(entry.bytes)})`);
15955
+ log.dim(` ${path18.join(ANIMS_DEST, entry.file)} (${formatMb(entry.bytes)})`);
15837
15956
  } catch (err) {
15838
15957
  failures.push(`${entry.name} (${err instanceof Error ? err.message : String(err)})`);
15839
15958
  }
@@ -15849,13 +15968,13 @@ async function runAnims(opts) {
15849
15968
  version: manifest.version,
15850
15969
  clips: [...union].sort((a, b) => a.localeCompare(b))
15851
15970
  };
15852
- await fs18.writeFile(gameManifestPath, JSON.stringify(gameManifest, null, 2) + "\n");
15971
+ await fs19.writeFile(gameManifestPath, JSON.stringify(gameManifest, null, 2) + "\n");
15853
15972
  log.plain("");
15854
15973
  const parts = [`${installedCount} clip${installedCount === 1 ? "" : "s"} installed`];
15855
15974
  if (presentCount > 0) parts.push(`${presentCount} already present`);
15856
15975
  if (bundledSkips > 0) parts.push(`${bundledSkips} already bundled in animation-library.glb`);
15857
15976
  log.success(
15858
- `${parts.join(", ")} \u2192 ${c.cyan(ANIMS_DEST + path17.sep)}${addedBytes > 0 ? ` (+${formatMb(addedBytes)})` : ""}`
15977
+ `${parts.join(", ")} \u2192 ${c.cyan(ANIMS_DEST + path18.sep)}${addedBytes > 0 ? ` (+${formatMb(addedBytes)})` : ""}`
15859
15978
  );
15860
15979
  for (const [selector, entries] of resolved) {
15861
15980
  const names = entries.filter((e) => !coreNames.has(e.name)).map((e) => e.name);
@@ -15887,8 +16006,8 @@ async function loadManifest(baseOverride) {
15887
16006
  }
15888
16007
  } catch {
15889
16008
  }
15890
- const snapshotPath = path17.join(getTemplatesDir(), "controllers", "assets", "anims-manifest.json");
15891
- const manifest = JSON.parse(await fs18.readFile(snapshotPath, "utf8"));
16009
+ const snapshotPath = path18.join(getTemplatesDir(), "controllers", "assets", "anims-manifest.json");
16010
+ const manifest = JSON.parse(await fs19.readFile(snapshotPath, "utf8"));
15892
16011
  return { manifest, source: "snapshot" };
15893
16012
  }
15894
16013
  function resolveSelectors(manifest, selectors) {
@@ -16006,21 +16125,21 @@ function printCatalog(log, manifest, selectors) {
16006
16125
  }
16007
16126
  async function readGameManifest(file) {
16008
16127
  try {
16009
- return JSON.parse(await fs18.readFile(file, "utf8"));
16128
+ return JSON.parse(await fs19.readFile(file, "utf8"));
16010
16129
  } catch {
16011
16130
  return null;
16012
16131
  }
16013
16132
  }
16014
16133
  async function hasSize(file, bytes) {
16015
16134
  try {
16016
- return (await fs18.stat(file)).size === bytes;
16135
+ return (await fs19.stat(file)).size === bytes;
16017
16136
  } catch {
16018
16137
  return false;
16019
16138
  }
16020
16139
  }
16021
16140
  async function exists2(p) {
16022
16141
  try {
16023
- await fs18.access(p);
16142
+ await fs19.access(p);
16024
16143
  return true;
16025
16144
  } catch {
16026
16145
  return false;
@@ -16037,7 +16156,9 @@ var CONTROLLER_KINDS = [
16037
16156
  "drone",
16038
16157
  "touch",
16039
16158
  "networked-physics",
16040
- "quality"
16159
+ "quality",
16160
+ "chat",
16161
+ "voice"
16041
16162
  ];
16042
16163
  var SHARED = [
16043
16164
  "shared/math.ts",
@@ -16160,6 +16281,26 @@ var CONTROLLER_FILE_SETS = {
16160
16281
  `const gov = new QualityGovernor(tier, { setDprScale: (m) => renderer.setPixelRatio(Math.min(window.devicePixelRatio, tier.dprCap * m)), setShadowQuality: (l) => sun.shadow.mapSize.setScalar(l === 'full' ? tier.shadowMapSize : tier.shadowMapSize / 2) }, renderer);`
16161
16282
  ]
16162
16283
  },
16284
+ voice: {
16285
+ code: ["voice/voice-proximity.ts", NOTICE],
16286
+ assets: [],
16287
+ skill: "genex-threejs-multiplayer",
16288
+ sketch: [
16289
+ `const voice = new ProximityVoice({ room, listener: camera.children.find(c => c.type === "AudioListener") });`,
16290
+ `await voice.join(); // MUST be inside a click/tap handler \u2014 browsers gate the mic prompt on a gesture`,
16291
+ `// per frame: voice.update((id) => remoteBodies.get(id)?.position ?? null)`
16292
+ ]
16293
+ },
16294
+ chat: {
16295
+ code: ["chat/chat-overlay.ts", NOTICE],
16296
+ assets: [],
16297
+ skill: "genex-threejs-multiplayer",
16298
+ sketch: [
16299
+ `const room = await connect({ gameId: "<slug>", auth: getColyseusAuth }); // @genex-ai/multiplayer`,
16300
+ `const chat = new ChatOverlay({ room, onTypingChange: (t) => { keyboard.enabled = !t; } });`,
16301
+ `// that onTypingChange is REQUIRED \u2014 without it "w" walks the player while someone types`
16302
+ ]
16303
+ },
16163
16304
  touch: {
16164
16305
  code: [...TOUCH_KIT, NOTICE],
16165
16306
  assets: [],
@@ -16189,8 +16330,8 @@ var CONTROLLER_FILE_SETS = {
16189
16330
  ]
16190
16331
  }
16191
16332
  };
16192
- var CODE_DEST = path18.join("src", "controllers");
16193
- var ASSETS_DEST = path18.join("public", "assets");
16333
+ var CODE_DEST = path19.join("src", "controllers");
16334
+ var ASSETS_DEST = path19.join("public", "assets");
16194
16335
  async function runController(opts) {
16195
16336
  const log = createLogger({ quiet: opts.quiet });
16196
16337
  if (opts.kind?.trim() === "anims") {
@@ -16207,31 +16348,31 @@ async function runController(opts) {
16207
16348
  process.exitCode = 1;
16208
16349
  return;
16209
16350
  }
16210
- const srcDir = path18.join(getTemplatesDir(), "controllers");
16351
+ const srcDir = path19.join(getTemplatesDir(), "controllers");
16211
16352
  const root = opts.cwd ?? process.cwd();
16212
16353
  const set = CONTROLLER_FILE_SETS[kind];
16213
16354
  log.plain(c.bold(`genex controller ${kind}`));
16214
16355
  log.plain("");
16215
- log.step(`Installing the ${kind} controller into ${c.cyan(CODE_DEST + path18.sep)}`);
16356
+ log.step(`Installing the ${kind} controller into ${c.cyan(CODE_DEST + path19.sep)}`);
16216
16357
  const plan = [
16217
- ...set.code.map((rel) => ({ from: rel, rel: path18.join(CODE_DEST, rel) })),
16358
+ ...set.code.map((rel) => ({ from: rel, rel: path19.join(CODE_DEST, rel) })),
16218
16359
  ...set.assets.map((rel) => ({
16219
16360
  from: rel,
16220
- rel: path18.join(ASSETS_DEST, path18.basename(rel))
16361
+ rel: path19.join(ASSETS_DEST, path19.basename(rel))
16221
16362
  }))
16222
16363
  ];
16223
16364
  let copied = 0;
16224
16365
  let skipped = 0;
16225
16366
  try {
16226
16367
  for (const file of plan) {
16227
- const dest = path18.join(root, file.rel);
16368
+ const dest = path19.join(root, file.rel);
16228
16369
  if (!opts.force && await exists3(dest)) {
16229
16370
  skipped++;
16230
16371
  log.dim(` skipped ${file.rel} (exists \u2014 use --force to overwrite)`);
16231
16372
  continue;
16232
16373
  }
16233
- await fs19.mkdir(path18.dirname(dest), { recursive: true });
16234
- await fs19.copyFile(path18.join(srcDir, file.from), dest);
16374
+ await fs20.mkdir(path19.dirname(dest), { recursive: true });
16375
+ await fs20.copyFile(path19.join(srcDir, file.from), dest);
16235
16376
  copied++;
16236
16377
  log.dim(` ${file.rel}`);
16237
16378
  }
@@ -16284,7 +16425,7 @@ async function runController(opts) {
16284
16425
  for (const line of set.sketch) {
16285
16426
  log.dim(` ${line}`);
16286
16427
  }
16287
- if (kind === "character" && !await exists3(path18.join(root, ASSETS_DEST, "meshy-character.json"))) {
16428
+ if (kind === "character" && !await exists3(path19.join(root, ASSETS_DEST, "meshy-character.json"))) {
16288
16429
  log.plain("");
16289
16430
  log.plain(
16290
16431
  ` ${stepOffset + 3}. This game has no generated character yet \u2014 ${c.cyan(
@@ -16316,9 +16457,9 @@ async function installMeshyCharacterManifest(args) {
16316
16457
  throw new Error("The API returned an invalid Meshy character manifest.");
16317
16458
  }
16318
16459
  assertCompleteMeshyControllerPack(manifest);
16319
- const destination = path18.join(args.root, ASSETS_DEST, "meshy-character.json");
16320
- await fs19.mkdir(path18.dirname(destination), { recursive: true });
16321
- await fs19.writeFile(
16460
+ const destination = path19.join(args.root, ASSETS_DEST, "meshy-character.json");
16461
+ await fs20.mkdir(path19.dirname(destination), { recursive: true });
16462
+ await fs20.writeFile(
16322
16463
  destination,
16323
16464
  `${JSON.stringify(manifest, null, 2)}
16324
16465
  `
@@ -16456,14 +16597,14 @@ function assertCompleteMeshyControllerPack(manifest) {
16456
16597
  }
16457
16598
  async function installFallbackAvatar(args) {
16458
16599
  const { root, srcDir, log } = args;
16459
- const dest = path18.join(root, ASSETS_DEST, "avatar.vrm");
16460
- await fs19.mkdir(path18.dirname(dest), { recursive: true });
16461
- await fs19.copyFile(path18.join(srcDir, "assets", "default-avatar.vrm"), dest);
16600
+ const dest = path19.join(root, ASSETS_DEST, "avatar.vrm");
16601
+ await fs20.mkdir(path19.dirname(dest), { recursive: true });
16602
+ await fs20.copyFile(path19.join(srcDir, "assets", "default-avatar.vrm"), dest);
16462
16603
  log.dim(" public/assets/avatar.vrm (fallback avatar \u2014 bundled CC0 default)");
16463
16604
  }
16464
16605
  async function exists3(p) {
16465
16606
  try {
16466
- await fs19.access(p);
16607
+ await fs20.access(p);
16467
16608
  return true;
16468
16609
  } catch {
16469
16610
  return false;
@@ -16957,22 +17098,22 @@ async function context2(opts) {
16957
17098
  const project = await readProject();
16958
17099
  return { token, apiUrl: getApiUrl(opts.apiUrl ?? project?.apiUrl) };
16959
17100
  }
16960
- async function readVideo(path20, log) {
17101
+ async function readVideo(path21, log) {
16961
17102
  let bytes;
16962
17103
  try {
16963
- bytes = await readFile(path20);
17104
+ bytes = await readFile(path21);
16964
17105
  } catch {
16965
- log.error(`Can't read ${path20}.`);
17106
+ log.error(`Can't read ${path21}.`);
16966
17107
  return null;
16967
17108
  }
16968
17109
  if (bytes.byteLength > MAX_VIDEO_BYTES) {
16969
- log.error(`${basename(path20)} is ${(bytes.byteLength / 1e6).toFixed(0)} MB \u2014 the limit is ${MAX_VIDEO_BYTES / 1e6} MB.`);
17110
+ log.error(`${basename(path21)} is ${(bytes.byteLength / 1e6).toFixed(0)} MB \u2014 the limit is ${MAX_VIDEO_BYTES / 1e6} MB.`);
16970
17111
  return null;
16971
17112
  }
16972
17113
  return bytes;
16973
17114
  }
16974
- async function uploadVideo(apiUrl, token, characterId, path20, bytes, log) {
16975
- const contentType = /\.mov$/i.test(path20) ? "video/quicktime" : "video/mp4";
17115
+ async function uploadVideo(apiUrl, token, characterId, path21, bytes, log) {
17116
+ const contentType = /\.mov$/i.test(path21) ? "video/quicktime" : "video/mp4";
16976
17117
  const minted = await apiFetch(
16977
17118
  `${apiUrl}/api/characters/${encodeURIComponent(characterId)}/motions/video-url`,
16978
17119
  {
@@ -16987,7 +17128,7 @@ async function uploadVideo(apiUrl, token, characterId, path20, bytes, log) {
16987
17128
  return null;
16988
17129
  }
16989
17130
  const { uploadUrl, videoUrl } = await minted.json();
16990
- log.dim(` uploading ${basename(path20)} (${(bytes.byteLength / 1e6).toFixed(1)} MB)\u2026`);
17131
+ log.dim(` uploading ${basename(path21)} (${(bytes.byteLength / 1e6).toFixed(1)} MB)\u2026`);
16991
17132
  const put = await fetch(uploadUrl, {
16992
17133
  method: "PUT",
16993
17134
  headers: { "Content-Type": contentType, "Content-Length": String(bytes.byteLength) },
@@ -17303,8 +17444,8 @@ function rank(items, query) {
17303
17444
  }
17304
17445
 
17305
17446
  // src/commands/motion.ts
17306
- import fs20 from "fs/promises";
17307
- import path19 from "path";
17447
+ import fs21 from "fs/promises";
17448
+ import path20 from "path";
17308
17449
 
17309
17450
  // src/lib/motion/npz.ts
17310
17451
  import zlib from "zlib";
@@ -18555,7 +18696,7 @@ async function motionGen(opts, log) {
18555
18696
  }
18556
18697
  if (opts.constraintsPath !== void 0) {
18557
18698
  try {
18558
- const raw = await fs20.readFile(opts.constraintsPath, "utf8");
18699
+ const raw = await fs21.readFile(opts.constraintsPath, "utf8");
18559
18700
  generationOptions.constraints = JSON.parse(raw);
18560
18701
  } catch {
18561
18702
  log.error(`Couldn't read the --constraints JSON at ${opts.constraintsPath}.`);
@@ -18579,10 +18720,10 @@ async function motionGen(opts, log) {
18579
18720
  async function expandTakes(selectors) {
18580
18721
  const out = [];
18581
18722
  for (const sel of selectors) {
18582
- const st = await fs20.stat(sel).catch(() => null);
18723
+ const st = await fs21.stat(sel).catch(() => null);
18583
18724
  if (st?.isDirectory()) {
18584
- const names = await fs20.readdir(sel);
18585
- for (const n of names.sort()) if (n.endsWith(".npz")) out.push(path19.join(sel, n));
18725
+ const names = await fs21.readdir(sel);
18726
+ for (const n of names.sort()) if (n.endsWith(".npz")) out.push(path20.join(sel, n));
18586
18727
  } else if (st?.isFile()) {
18587
18728
  out.push(sel);
18588
18729
  } else {
@@ -18617,7 +18758,7 @@ async function motionVerify(opts, log) {
18617
18758
  let gates = DEFAULT_GATES;
18618
18759
  if (opts.gatesPath) {
18619
18760
  try {
18620
- gates = mergeGates(DEFAULT_GATES, JSON.parse(await fs20.readFile(opts.gatesPath, "utf8")));
18761
+ gates = mergeGates(DEFAULT_GATES, JSON.parse(await fs21.readFile(opts.gatesPath, "utf8")));
18621
18762
  } catch {
18622
18763
  log.error(`Couldn't read the --gates JSON at ${opts.gatesPath}.`);
18623
18764
  process.exitCode = 1;
@@ -18639,9 +18780,9 @@ async function motionVerify(opts, log) {
18639
18780
  }
18640
18781
  const reports = [];
18641
18782
  for (const file of files) {
18642
- const stem = path19.basename(file).replace(/\.npz$/, "");
18783
+ const stem = path20.basename(file).replace(/\.npz$/, "");
18643
18784
  try {
18644
- reports.push(analyzeTake(stem, await fs20.readFile(file), gates));
18785
+ reports.push(analyzeTake(stem, await fs21.readFile(file), gates));
18645
18786
  } catch (err) {
18646
18787
  reports.push({
18647
18788
  take: stem,
@@ -18679,7 +18820,7 @@ async function motionCompile(opts, log) {
18679
18820
  let cfg = DEFAULT_MOTION_CONFIG;
18680
18821
  if (opts.configPath) {
18681
18822
  try {
18682
- const patch = JSON.parse(await fs20.readFile(opts.configPath, "utf8"));
18823
+ const patch = JSON.parse(await fs21.readFile(opts.configPath, "utf8"));
18683
18824
  cfg = { ...DEFAULT_MOTION_CONFIG, ...patch, idleLoop: { ...DEFAULT_MOTION_CONFIG.idleLoop, ...patch.idleLoop } };
18684
18825
  } catch {
18685
18826
  log.error(`Couldn't read the --config JSON at ${opts.configPath}.`);
@@ -18697,16 +18838,16 @@ async function motionCompile(opts, log) {
18697
18838
  }
18698
18839
  const inputs = [];
18699
18840
  for (const file of files) {
18700
- const stem = path19.basename(file).replace(/\.npz$/, "");
18841
+ const stem = path20.basename(file).replace(/\.npz$/, "");
18701
18842
  try {
18702
- inputs.push({ stem, take: loadTake(await fs20.readFile(file)) });
18843
+ inputs.push({ stem, take: loadTake(await fs21.readFile(file)) });
18703
18844
  } catch (err) {
18704
18845
  log.error(`${stem}: ${err instanceof Error ? err.message : String(err)}`);
18705
18846
  process.exitCode = 1;
18706
18847
  return;
18707
18848
  }
18708
18849
  }
18709
- const setName = opts.set ?? path19.basename(opts.out).replace(/\.json$/, "");
18850
+ const setName = opts.set ?? path20.basename(opts.out).replace(/\.json$/, "");
18710
18851
  let result;
18711
18852
  try {
18712
18853
  result = compileSet(inputs, setName, cfg);
@@ -18721,9 +18862,9 @@ async function motionCompile(opts, log) {
18721
18862
  process.exitCode = 1;
18722
18863
  return;
18723
18864
  }
18724
- await fs20.mkdir(path19.dirname(path19.resolve(opts.out)), { recursive: true });
18865
+ await fs21.mkdir(path20.dirname(path20.resolve(opts.out)), { recursive: true });
18725
18866
  const json = JSON.stringify(result.data);
18726
- await fs20.writeFile(opts.out, json);
18867
+ await fs21.writeFile(opts.out, json);
18727
18868
  if (opts.json) {
18728
18869
  writeJson({ out: opts.out, set: setName, tracks: Object.keys(result.data.gaits), bytes: json.length });
18729
18870
  return;
@@ -18741,9 +18882,9 @@ var MOTION_RUNTIME_FILES = [
18741
18882
  var MOTION_PRESETS = {
18742
18883
  rifle: ["sets/rifle.json", "sets/jumps.json"]
18743
18884
  };
18744
- var MOTION_DEST = path19.join("src", "motion");
18885
+ var MOTION_DEST = path20.join("src", "motion");
18745
18886
  async function motionInstall(opts, log) {
18746
- const srcDir = path19.join(getTemplatesDir(), "motion");
18887
+ const srcDir = path20.join(getTemplatesDir(), "motion");
18747
18888
  const root = opts.cwd ?? process.cwd();
18748
18889
  const preset = opts.set;
18749
18890
  if (preset !== void 0 && !MOTION_PRESETS[preset]) {
@@ -18754,21 +18895,21 @@ async function motionInstall(opts, log) {
18754
18895
  const files = [...MOTION_RUNTIME_FILES, ...preset ? MOTION_PRESETS[preset] : []];
18755
18896
  log.plain(c.bold(`genex motion install${preset ? ` --set ${preset}` : ""}`));
18756
18897
  log.plain("");
18757
- log.step(`Vendoring the motion runtime into ${c.cyan(MOTION_DEST + path19.sep)}`);
18898
+ log.step(`Vendoring the motion runtime into ${c.cyan(MOTION_DEST + path20.sep)}`);
18758
18899
  let copied = 0, skipped = 0;
18759
18900
  try {
18760
18901
  for (const rel of files) {
18761
- const dest = path19.join(root, MOTION_DEST, rel);
18762
- const exists4 = await fs20.access(dest).then(() => true, () => false);
18902
+ const dest = path20.join(root, MOTION_DEST, rel);
18903
+ const exists4 = await fs21.access(dest).then(() => true, () => false);
18763
18904
  if (!opts.force && exists4) {
18764
18905
  skipped++;
18765
- log.dim(` skipped ${path19.join(MOTION_DEST, rel)} (exists \u2014 use --force to overwrite)`);
18906
+ log.dim(` skipped ${path20.join(MOTION_DEST, rel)} (exists \u2014 use --force to overwrite)`);
18766
18907
  continue;
18767
18908
  }
18768
- await fs20.mkdir(path19.dirname(dest), { recursive: true });
18769
- await fs20.copyFile(path19.join(srcDir, rel), dest);
18909
+ await fs21.mkdir(path20.dirname(dest), { recursive: true });
18910
+ await fs21.copyFile(path20.join(srcDir, rel), dest);
18770
18911
  copied++;
18771
- log.dim(` ${path19.join(MOTION_DEST, rel)}`);
18912
+ log.dim(` ${path20.join(MOTION_DEST, rel)}`);
18772
18913
  }
18773
18914
  } catch (err) {
18774
18915
  log.error(`Copy failed: ${String(err)}`);
@@ -18809,7 +18950,7 @@ async function motionConstraints(opts, log) {
18809
18950
  }
18810
18951
  const doc = directionConstraint(dir, speed, duration);
18811
18952
  const out = opts.out ?? "constraints.json";
18812
- await fs20.writeFile(out, JSON.stringify(doc));
18953
+ await fs21.writeFile(out, JSON.stringify(doc));
18813
18954
  if (opts.json) {
18814
18955
  writeJson({ out, dir: dirName, speed, duration, waypoints: doc[0].frame_indices.length });
18815
18956
  return;
@@ -18859,6 +19000,11 @@ ${c.bold("Usage")}
18859
19000
  genex link <slug> [options] Re-link THIS folder to an existing game of yours
18860
19001
  (lost folder / new machine); preview/publish then
18861
19002
  update the same live game. Never creates a project.
19003
+ genex rename <name> [options] Rename THIS game \u2014 its play address, its source repo,
19004
+ and its gallery name. Keeps the game (plays, likes,
19005
+ comments); the OLD address stops working, so a
19006
+ published game needs --yes. Rebuild after with
19007
+ 'genex preview'/'publish'.
18862
19008
  genex list [options] List your games \u2014 slug, status, and page link for
18863
19009
  each. Signs you in if needed; --json for raw data.
18864
19010
  Find the slug to reconnect with 'genex link' here.
@@ -18916,13 +19062,18 @@ ${c.bold("Usage")}
18916
19062
  enqueued (done/running/queued/failed) \u2014 a single
18917
19063
  API refresh, never blocks, never bills.
18918
19064
  genex controller <type> [--force] Install a bundled controller
18919
- (character|car|drone|touch|networked-physics|quality)
18920
- into src/controllers (+ assets into public/assets).
19065
+ (character|car|drone|touch|networked-physics|
19066
+ quality|chat|voice) into src/controllers (+ assets
19067
+ into public/assets).
18921
19068
  "touch" = the standalone mobile touch kit (joystick,
18922
19069
  buttons, drag zone, rotate overlay) for games
18923
19070
  without a physics controller. "quality" = the
18924
19071
  adaptive-quality kit (device tier, runtime governor,
18925
19072
  per-tier asset rungs) every game wires at boot.
19073
+ "chat" = the room text-chat overlay for multiplayer
19074
+ games (transport is room.chat in the SDK).
19075
+ "voice" = proximity voice for multiplayer games
19076
+ (mesh WebRTC via room.voice; party-sized, max 6).
18926
19077
  Character's player body is the game's own generated
18927
19078
  character, with a VRM avatar as the fallback.
18928
19079
  genex controller character --character <id>
@@ -19025,6 +19176,12 @@ ${c.bold("Options for `link`")}
19025
19176
  off to 'genex auth' (default: 100). The code itself stays
19026
19177
  valid for 15 minutes either way.
19027
19178
 
19179
+ ${c.bold("Options for `rename`")}
19180
+ <name> The new name (positional). Slugified the same way init does it.
19181
+ --yes Confirm retiring the current address (required once published).
19182
+ --env <path> Token env file (default: ~/.genex/env).
19183
+ --api-url <url> Override the API base URL.
19184
+
19028
19185
  ${c.bold("Options for `preview` / `publish`")}
19029
19186
  --no-build Skip the build step; deploy whatever is already built.
19030
19187
  --no-push (publish) Skip build + push; only flip the gallery flag.
@@ -19132,6 +19289,8 @@ ${c.bold("Examples")}
19132
19289
  genex controller anims sword pistol
19133
19290
  genex controller touch
19134
19291
  genex controller networked-physics
19292
+ genex controller chat
19293
+ genex controller voice
19135
19294
  `;
19136
19295
  function parseArgs(argv) {
19137
19296
  const parsed = {
@@ -19283,6 +19442,9 @@ function parseArgs(argv) {
19283
19442
  case "--force":
19284
19443
  parsed.options.force = true;
19285
19444
  break;
19445
+ case "--yes":
19446
+ parsed.options.yes = true;
19447
+ break;
19286
19448
  case "--list":
19287
19449
  parsed.options.list = true;
19288
19450
  break;
@@ -19677,6 +19839,9 @@ async function main() {
19677
19839
  case "link":
19678
19840
  await runLink(parsed.options);
19679
19841
  break;
19842
+ case "rename":
19843
+ await runRename(parsed.options);
19844
+ break;
19680
19845
  case "list":
19681
19846
  await runList(parsed.options);
19682
19847
  break;