@genex-ai/cli-demo 0.62.0-dev.143 → 0.63.0-dev.146

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
@@ -1929,11 +1929,69 @@ async function runMakeRemixable(opts) {
1929
1929
  }
1930
1930
 
1931
1931
  // src/lib/detect-features.ts
1932
+ import fs11 from "fs/promises";
1933
+ import path12 from "path";
1934
+
1935
+ // src/lib/generation-ledger.ts
1932
1936
  import fs10 from "fs/promises";
1933
1937
  import path11 from "path";
1938
+ var ledgerPath = (cwd) => path11.join(cwd, ".genex", "generations.ndjson");
1939
+ async function append(cwd, event) {
1940
+ try {
1941
+ await fs10.access(path11.join(cwd, ".genex"));
1942
+ await fs10.appendFile(ledgerPath(cwd), `${JSON.stringify(event)}
1943
+ `, "utf8");
1944
+ } catch {
1945
+ }
1946
+ }
1947
+ async function readLedger(cwd = process.cwd()) {
1948
+ let raw;
1949
+ try {
1950
+ raw = await fs10.readFile(ledgerPath(cwd), "utf8");
1951
+ } catch {
1952
+ return [];
1953
+ }
1954
+ const entries = /* @__PURE__ */ new Map();
1955
+ for (const line of raw.split("\n")) {
1956
+ if (!line.trim()) continue;
1957
+ try {
1958
+ const e = JSON.parse(line);
1959
+ if (e.t === "q" && typeof e.id === "string" && typeof e.kind === "string") {
1960
+ if (!entries.has(e.id)) {
1961
+ entries.set(e.id, {
1962
+ id: e.id,
1963
+ kind: e.kind,
1964
+ prompt: typeof e.prompt === "string" ? e.prompt : "",
1965
+ queuedAt: typeof e.at === "string" ? e.at : "",
1966
+ status: "queued"
1967
+ });
1968
+ }
1969
+ } else if (e.t === "done" && typeof e.id === "string") {
1970
+ const entry = entries.get(e.id);
1971
+ if (entry && (e.status === "completed" || e.status === "failed")) {
1972
+ entry.status = e.status;
1973
+ if (Array.isArray(e.urls)) entry.urls = e.urls.filter((u) => typeof u === "string");
1974
+ }
1975
+ }
1976
+ } catch {
1977
+ }
1978
+ }
1979
+ return [...entries.values()];
1980
+ }
1981
+ async function recordQueued(id, kind, prompt, cwd = process.cwd()) {
1982
+ await append(cwd, { t: "q", id, kind, prompt: prompt.slice(0, 120), at: (/* @__PURE__ */ new Date()).toISOString() });
1983
+ }
1984
+ async function recordTerminal(id, status, urls, cwd = process.cwd()) {
1985
+ await append(cwd, { t: "done", id, status, ...urls?.length ? { urls } : {} });
1986
+ }
1987
+ async function countFailed(kind, cwd = process.cwd()) {
1988
+ return (await readLedger(cwd)).filter((e) => e.kind === kind && e.status === "failed").length;
1989
+ }
1990
+
1991
+ // src/lib/detect-features.ts
1934
1992
  async function detectEmbedSdkVersion(cwd = process.cwd()) {
1935
1993
  try {
1936
- const raw = await fs10.readFile(path11.join(cwd, "package.json"), "utf8");
1994
+ const raw = await fs11.readFile(path12.join(cwd, "package.json"), "utf8");
1937
1995
  const pkg = JSON.parse(raw);
1938
1996
  const version = pkg.dependencies?.["@genex-ai/embed-sdk"] ?? pkg.devDependencies?.["@genex-ai/embed-sdk"];
1939
1997
  return typeof version === "string" && version ? version : null;
@@ -1943,7 +2001,7 @@ async function detectEmbedSdkVersion(cwd = process.cwd()) {
1943
2001
  }
1944
2002
  async function detectMultiplayer(cwd = process.cwd()) {
1945
2003
  try {
1946
- const raw = await fs10.readFile(path11.join(cwd, "package.json"), "utf8");
2004
+ const raw = await fs11.readFile(path12.join(cwd, "package.json"), "utf8");
1947
2005
  const pkg = JSON.parse(raw);
1948
2006
  return Boolean(
1949
2007
  pkg.dependencies?.["@genex-ai/multiplayer"] ?? pkg.devDependencies?.["@genex-ai/multiplayer"]
@@ -1955,7 +2013,7 @@ async function detectMultiplayer(cwd = process.cwd()) {
1955
2013
  async function detectMatchmaking(log, cwd = process.cwd()) {
1956
2014
  let pkg;
1957
2015
  try {
1958
- pkg = JSON.parse(await fs10.readFile(path11.join(cwd, "package.json"), "utf8"));
2016
+ pkg = JSON.parse(await fs11.readFile(path12.join(cwd, "package.json"), "utf8"));
1959
2017
  } catch (err) {
1960
2018
  log.dim(` (skipping matchmaking \u2014 couldn't read package.json: ${String(err)})`);
1961
2019
  return null;
@@ -1972,10 +2030,10 @@ async function detectMatchmaking(log, cwd = process.cwd()) {
1972
2030
  }
1973
2031
  var GAME_STATE_CALLS = /savePlayerState|saveWorldState|submitScore/;
1974
2032
  async function detectGameStateUsage(cwd = process.cwd()) {
1975
- const srcDir = path11.join(cwd, "src");
2033
+ const srcDir = path12.join(cwd, "src");
1976
2034
  let entries;
1977
2035
  try {
1978
- entries = await fs10.readdir(srcDir, { recursive: true });
2036
+ entries = await fs11.readdir(srcDir, { recursive: true });
1979
2037
  } catch {
1980
2038
  return false;
1981
2039
  }
@@ -1983,7 +2041,7 @@ async function detectGameStateUsage(cwd = process.cwd()) {
1983
2041
  if (rel.includes("node_modules")) continue;
1984
2042
  if (!/\.(ts|js|mts|mjs|tsx|jsx)$/.test(rel)) continue;
1985
2043
  try {
1986
- const content = await fs10.readFile(path11.join(srcDir, rel), "utf8");
2044
+ const content = await fs11.readFile(path12.join(srcDir, rel), "utf8");
1987
2045
  if (GAME_STATE_CALLS.test(content)) return true;
1988
2046
  } catch {
1989
2047
  }
@@ -1993,14 +2051,14 @@ async function detectGameStateUsage(cwd = process.cwd()) {
1993
2051
  var UI_PHASE_MARKERS = /data-phase|setPhase/;
1994
2052
  async function detectUiPhases(cwd = process.cwd()) {
1995
2053
  try {
1996
- const html = await fs10.readFile(path11.join(cwd, "index.html"), "utf8").catch(() => "");
2054
+ const html = await fs11.readFile(path12.join(cwd, "index.html"), "utf8").catch(() => "");
1997
2055
  if (UI_PHASE_MARKERS.test(html)) return true;
1998
- const srcDir = path11.join(cwd, "src");
1999
- const entries = await fs10.readdir(srcDir, { recursive: true });
2056
+ const srcDir = path12.join(cwd, "src");
2057
+ const entries = await fs11.readdir(srcDir, { recursive: true });
2000
2058
  for (const rel of entries) {
2001
2059
  if (rel.includes("node_modules")) continue;
2002
2060
  if (!/\.(ts|js|mts|mjs|tsx|jsx|html|css)$/.test(rel)) continue;
2003
- const content = await fs10.readFile(path11.join(srcDir, rel), "utf8").catch(() => "");
2061
+ const content = await fs11.readFile(path12.join(srcDir, rel), "utf8").catch(() => "");
2004
2062
  if (UI_PHASE_MARKERS.test(content)) return true;
2005
2063
  }
2006
2064
  return false;
@@ -2045,17 +2103,17 @@ var lineOf = (content, index) => content.slice(0, index).split("\n").length;
2045
2103
  var DEPTH_RATIO_LIMIT = 1e6;
2046
2104
  async function detectSurfaceScan(cwd = process.cwd()) {
2047
2105
  const found = { guessedRepeat: [], squarePoints: [], depthRange: [] };
2048
- const srcDir = path11.join(cwd, "src");
2106
+ const srcDir = path12.join(cwd, "src");
2049
2107
  let entries;
2050
2108
  try {
2051
- entries = await fs10.readdir(srcDir, { recursive: true });
2109
+ entries = await fs11.readdir(srcDir, { recursive: true });
2052
2110
  } catch {
2053
2111
  return found;
2054
2112
  }
2055
2113
  for (const rel of entries) {
2056
2114
  if (rel.includes("node_modules")) continue;
2057
2115
  if (!/\.(ts|js|mts|mjs|tsx|jsx)$/.test(rel)) continue;
2058
- const raw = await fs10.readFile(path11.join(srcDir, rel), "utf8").catch(() => "");
2116
+ const raw = await fs11.readFile(path12.join(srcDir, rel), "utf8").catch(() => "");
2059
2117
  if (!raw) continue;
2060
2118
  const content = raw.replace(/\/\*[\s\S]*?\*\//g, (m2) => m2.replace(/[^\n]/g, " ")).replace(/(^|[^:])\/\/[^\n]*/gm, (m2, p1) => p1 + " ".repeat(m2.length - p1.length));
2061
2119
  const repeatRe = /\.repeat\.set\(\s*(-?\d+(?:\.\d+)?)\s*,\s*(-?\d+(?:\.\d+)?)\s*\)/g;
@@ -2093,6 +2151,54 @@ async function detectSurfaceScan(cwd = process.cwd()) {
2093
2151
  }
2094
2152
  return found;
2095
2153
  }
2154
+ var WIRED_BY_URL = /* @__PURE__ */ new Set(["model", "skybox", "sfx", "texture", "video"]);
2155
+ var UNPICKED_AFTER_MS = 15 * 60 * 1e3;
2156
+ async function detectGenerationAudit(cwd = process.cwd()) {
2157
+ const ledger = await readLedger(cwd);
2158
+ const audited = ledger.filter((e) => WIRED_BY_URL.has(e.kind));
2159
+ if (audited.length === 0) return { unwired: [], unpicked: [] };
2160
+ let haystack = "";
2161
+ const read = async (file) => {
2162
+ try {
2163
+ haystack += await fs11.readFile(file, "utf8");
2164
+ } catch {
2165
+ }
2166
+ };
2167
+ try {
2168
+ for (const entry of await fs11.readdir(cwd, { withFileTypes: true })) {
2169
+ if (entry.isFile() && /\.(ts|tsx|js|jsx|css|html|json)$/.test(entry.name)) {
2170
+ await read(path12.join(cwd, entry.name));
2171
+ }
2172
+ }
2173
+ } catch {
2174
+ }
2175
+ for (const sub of ["src", "public"]) {
2176
+ try {
2177
+ const entries = await fs11.readdir(path12.join(cwd, sub), { recursive: true });
2178
+ for (const rel of entries) {
2179
+ if (rel.includes("node_modules")) continue;
2180
+ if (!/\.(ts|tsx|js|jsx|css|html|json|txt)$/.test(rel)) continue;
2181
+ await read(path12.join(cwd, sub, rel));
2182
+ }
2183
+ } catch {
2184
+ }
2185
+ }
2186
+ const ref = ({ id, kind, prompt }) => ({
2187
+ id,
2188
+ kind,
2189
+ prompt
2190
+ });
2191
+ const now = Date.now();
2192
+ return {
2193
+ unwired: audited.filter((e) => e.status === "completed" && !haystack.includes(e.id)).map(ref),
2194
+ // An id already referenced in the game was obviously picked up — a stale
2195
+ // "queued" record (e.g. `genex wait` ran from another directory) must not
2196
+ // nag about an asset that's wired in and working.
2197
+ unpicked: audited.filter(
2198
+ (e) => e.status === "queued" && !haystack.includes(e.id) && now - Date.parse(e.queuedAt) > UNPICKED_AFTER_MS
2199
+ ).map(ref)
2200
+ };
2201
+ }
2096
2202
  async function detectFeatures(log, cwd = process.cwd()) {
2097
2203
  return {
2098
2204
  embedSdkVersion: await detectEmbedSdkVersion(cwd),
@@ -2100,7 +2206,8 @@ async function detectFeatures(log, cwd = process.cwd()) {
2100
2206
  matchmaking: await detectMatchmaking(log, cwd),
2101
2207
  gameStateUsed: await detectGameStateUsage(cwd),
2102
2208
  uiPhases: await detectUiPhases(cwd),
2103
- surfaces: await detectSurfaceScan(cwd)
2209
+ surfaces: await detectSurfaceScan(cwd),
2210
+ generations: await detectGenerationAudit(cwd)
2104
2211
  };
2105
2212
  }
2106
2213
  function advisoryNudges(log, d) {
@@ -2125,6 +2232,20 @@ function advisoryNudges(log, d) {
2125
2232
  );
2126
2233
  }
2127
2234
  surfaceNudges(log, d.surfaces);
2235
+ generationNudges(log, d.generations);
2236
+ }
2237
+ function generationNudges(log, g) {
2238
+ const describe = (refs) => refs.map((r) => `${r.kind} ${r.id} ("${r.prompt.slice(0, 48)}")`).join("; ");
2239
+ if (g.unpicked.length) {
2240
+ log.warn(
2241
+ `${g.unpicked.length} generation(s) enqueued 15+ minutes ago were never picked up: ${describe(g.unpicked)}. Run npx genex wait <id> on each \u2014 landed assets get wired in before the last push of a session, never parked for "later".`
2242
+ );
2243
+ }
2244
+ if (g.unwired.length) {
2245
+ log.warn(
2246
+ `${g.unwired.length} finished generation(s) are wired nowhere in the game: ${describe(g.unwired)}. Each was paid for \u2014 wire it in (its URL embeds the id) or tell the user in one plain line why it stays unused.`
2247
+ );
2248
+ }
2128
2249
  }
2129
2250
  function surfaceNudges(log, s) {
2130
2251
  if (s.guessedRepeat.length) {
@@ -2518,6 +2639,7 @@ async function runGenerate(kind, opts) {
2518
2639
  process.exitCode = 1;
2519
2640
  return;
2520
2641
  }
2642
+ await recordQueued(id, kind, prompt);
2521
2643
  if (opts.noWait) {
2522
2644
  log.success(`Queued (${id}).`);
2523
2645
  log.plain(` Pick it up any time with: ${c.cyan(`npx genex wait ${id}`)}`);
@@ -2544,6 +2666,13 @@ async function awaitAndReport(apiUrl, token, id, kind, log, open = false) {
2544
2666
  async function reportTerminal(kind, view, log, open = false) {
2545
2667
  if (view.status !== "completed") {
2546
2668
  log.error(`Generation ${view.status}${view.error ? `: ${view.error}` : ""}.`);
2669
+ if (view.status === "failed") {
2670
+ await recordTerminal(view.id, "failed");
2671
+ if (kind === "video") {
2672
+ const failures = await countFailed("video");
2673
+ (failures >= 2 ? log.plain : log.dim)(videoFailureAdvice(failures));
2674
+ }
2675
+ }
2547
2676
  process.exitCode = 1;
2548
2677
  return;
2549
2678
  }
@@ -2553,6 +2682,7 @@ async function reportTerminal(kind, view, log, open = false) {
2553
2682
  process.exitCode = 1;
2554
2683
  return;
2555
2684
  }
2685
+ await recordTerminal(view.id, "completed", files.map((f) => f.url));
2556
2686
  log.plain("");
2557
2687
  log.success("Done \u2014 live on R2 (nothing downloaded or committed):");
2558
2688
  for (const f of files) {
@@ -2572,6 +2702,12 @@ async function reportTerminal(kind, view, log, open = false) {
2572
2702
  printHint(kind, view, files, log);
2573
2703
  await firstPreviewNudge(log);
2574
2704
  }
2705
+ function videoFailureAdvice(failures) {
2706
+ if (failures >= 2) {
2707
+ return ` ${c.bold(`\u270B ${failures} failed videos in this project \u2014 don't queue another attempt of the same clip.`)} One retry per clip is the budget. Use its key-art still as the backdrop instead (the genex-ai-menu skill's built-in fallback; a slow CSS pan gives it life), tell the user in one plain line, and spend the minutes in the game.`;
2708
+ }
2709
+ return " Video renders fail server-side sometimes. One retry is fair \u2014 shorter clip (4\u20136 s), simpler motion. After a second failure, switch to the key-art still (genex-ai-menu's fallback) instead of a third attempt.";
2710
+ }
2575
2711
  var WAIT_TIMEOUT_MS = 10 * 60 * 1e3;
2576
2712
  var KIND_WAIT_TIMEOUT_MS = {
2577
2713
  video: 15 * 60 * 1e3,
@@ -2752,8 +2888,8 @@ async function runWait(opts) {
2752
2888
  }
2753
2889
 
2754
2890
  // src/commands/controller.ts
2755
- import fs12 from "fs/promises";
2756
- import path13 from "path";
2891
+ import fs13 from "fs/promises";
2892
+ import path14 from "path";
2757
2893
 
2758
2894
  // ../../packages/meshy-animation-catalog/src/index.ts
2759
2895
  import { createHash } from "crypto";
@@ -11883,9 +12019,9 @@ function searchMeshyAnimations(query, options = {}) {
11883
12019
  }
11884
12020
 
11885
12021
  // src/lib/anims.ts
11886
- import fs11 from "fs/promises";
11887
- import path12 from "path";
11888
- var ANIMS_DEST = path12.join("public", "assets", "anims");
12022
+ import fs12 from "fs/promises";
12023
+ import path13 from "path";
12024
+ var ANIMS_DEST = path13.join("public", "assets", "anims");
11889
12025
  var HIDDEN_TAG = "reference";
11890
12026
  async function runAnims(opts) {
11891
12027
  const log = createLogger({ quiet: opts.quiet });
@@ -11901,7 +12037,7 @@ async function runAnims(opts) {
11901
12037
  printCatalog(log, manifest, selectors);
11902
12038
  return;
11903
12039
  }
11904
- const controllerMarker = path12.join(root, "src", "controllers", "character");
12040
+ const controllerMarker = path13.join(root, "src", "controllers", "character");
11905
12041
  if (!await exists2(controllerMarker)) {
11906
12042
  log.error(
11907
12043
  `No character controller in this game (missing ${c.cyan("src/controllers/character/")}).`
@@ -11910,11 +12046,11 @@ async function runAnims(opts) {
11910
12046
  process.exitCode = 1;
11911
12047
  return;
11912
12048
  }
11913
- const destDir = path12.join(root, ANIMS_DEST);
11914
- const gameManifestPath = path12.join(destDir, "manifest.json");
12049
+ const destDir = path13.join(root, ANIMS_DEST);
12050
+ const gameManifestPath = path13.join(destDir, "manifest.json");
11915
12051
  if (opts.reset) {
11916
- await fs11.rm(destDir, { recursive: true, force: true });
11917
- log.step(`Cleared ${c.cyan(ANIMS_DEST + path12.sep)} (--reset)`);
12052
+ await fs12.rm(destDir, { recursive: true, force: true });
12053
+ log.step(`Cleared ${c.cyan(ANIMS_DEST + path13.sep)} (--reset)`);
11918
12054
  }
11919
12055
  if (selectors.length === 0) {
11920
12056
  const installed = await readGameManifest(gameManifestPath);
@@ -11952,35 +12088,35 @@ async function runAnims(opts) {
11952
12088
  }
11953
12089
  }
11954
12090
  const wanted = [...byName.values()].sort((a, b) => a.name.localeCompare(b.name));
11955
- const cacheDir = path12.join(
12091
+ const cacheDir = path13.join(
11956
12092
  opts.cacheDir ?? getAnimsCacheDir(),
11957
12093
  `${manifest.library}-v${manifest.version}`
11958
12094
  );
11959
- await fs11.mkdir(cacheDir, { recursive: true });
11960
- await fs11.mkdir(destDir, { recursive: true });
12095
+ await fs12.mkdir(cacheDir, { recursive: true });
12096
+ await fs12.mkdir(destDir, { recursive: true });
11961
12097
  const base = getAnimsBase(opts.animsBase);
11962
12098
  let installedCount = 0;
11963
12099
  let presentCount = 0;
11964
12100
  let addedBytes = 0;
11965
12101
  const failures = [];
11966
12102
  for (const entry of wanted) {
11967
- const dest = path12.join(destDir, entry.file);
12103
+ const dest = path13.join(destDir, entry.file);
11968
12104
  if (await hasSize(dest, entry.bytes)) {
11969
12105
  presentCount++;
11970
12106
  continue;
11971
12107
  }
11972
12108
  try {
11973
- const cached = path12.join(cacheDir, entry.file);
12109
+ const cached = path13.join(cacheDir, entry.file);
11974
12110
  if (!await hasSize(cached, entry.bytes)) {
11975
12111
  const res = await fetch(base + entry.file);
11976
12112
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
11977
12113
  const buf = Buffer.from(await res.arrayBuffer());
11978
- await fs11.writeFile(cached, buf);
12114
+ await fs12.writeFile(cached, buf);
11979
12115
  }
11980
- await fs11.copyFile(cached, dest);
12116
+ await fs12.copyFile(cached, dest);
11981
12117
  installedCount++;
11982
12118
  addedBytes += entry.bytes;
11983
- log.dim(` ${path12.join(ANIMS_DEST, entry.file)} (${formatMb(entry.bytes)})`);
12119
+ log.dim(` ${path13.join(ANIMS_DEST, entry.file)} (${formatMb(entry.bytes)})`);
11984
12120
  } catch (err) {
11985
12121
  failures.push(`${entry.name} (${err instanceof Error ? err.message : String(err)})`);
11986
12122
  }
@@ -11996,13 +12132,13 @@ async function runAnims(opts) {
11996
12132
  version: manifest.version,
11997
12133
  clips: [...union].sort((a, b) => a.localeCompare(b))
11998
12134
  };
11999
- await fs11.writeFile(gameManifestPath, JSON.stringify(gameManifest, null, 2) + "\n");
12135
+ await fs12.writeFile(gameManifestPath, JSON.stringify(gameManifest, null, 2) + "\n");
12000
12136
  log.plain("");
12001
12137
  const parts = [`${installedCount} clip${installedCount === 1 ? "" : "s"} installed`];
12002
12138
  if (presentCount > 0) parts.push(`${presentCount} already present`);
12003
12139
  if (bundledSkips > 0) parts.push(`${bundledSkips} already bundled in animation-library.glb`);
12004
12140
  log.success(
12005
- `${parts.join(", ")} \u2192 ${c.cyan(ANIMS_DEST + path12.sep)}${addedBytes > 0 ? ` (+${formatMb(addedBytes)})` : ""}`
12141
+ `${parts.join(", ")} \u2192 ${c.cyan(ANIMS_DEST + path13.sep)}${addedBytes > 0 ? ` (+${formatMb(addedBytes)})` : ""}`
12006
12142
  );
12007
12143
  for (const [selector, entries] of resolved) {
12008
12144
  const names = entries.filter((e) => !coreNames.has(e.name)).map((e) => e.name);
@@ -12034,8 +12170,8 @@ async function loadManifest(baseOverride) {
12034
12170
  }
12035
12171
  } catch {
12036
12172
  }
12037
- const snapshotPath = path12.join(getTemplatesDir(), "controllers", "assets", "anims-manifest.json");
12038
- const manifest = JSON.parse(await fs11.readFile(snapshotPath, "utf8"));
12173
+ const snapshotPath = path13.join(getTemplatesDir(), "controllers", "assets", "anims-manifest.json");
12174
+ const manifest = JSON.parse(await fs12.readFile(snapshotPath, "utf8"));
12039
12175
  return { manifest, source: "snapshot" };
12040
12176
  }
12041
12177
  function resolveSelectors(manifest, selectors) {
@@ -12153,21 +12289,21 @@ function printCatalog(log, manifest, selectors) {
12153
12289
  }
12154
12290
  async function readGameManifest(file) {
12155
12291
  try {
12156
- return JSON.parse(await fs11.readFile(file, "utf8"));
12292
+ return JSON.parse(await fs12.readFile(file, "utf8"));
12157
12293
  } catch {
12158
12294
  return null;
12159
12295
  }
12160
12296
  }
12161
12297
  async function hasSize(file, bytes) {
12162
12298
  try {
12163
- return (await fs11.stat(file)).size === bytes;
12299
+ return (await fs12.stat(file)).size === bytes;
12164
12300
  } catch {
12165
12301
  return false;
12166
12302
  }
12167
12303
  }
12168
12304
  async function exists2(p) {
12169
12305
  try {
12170
- await fs11.access(p);
12306
+ await fs12.access(p);
12171
12307
  return true;
12172
12308
  } catch {
12173
12309
  return false;
@@ -12221,15 +12357,19 @@ var CONTROLLER_FILE_SETS = {
12221
12357
  ...INPUT_AND_CAMERA,
12222
12358
  NOTICE
12223
12359
  ],
12224
- // The player's VRM is written to public/assets/avatar.vrm at install time by
12360
+ // The FALLBACK VRM is written to public/assets/avatar.vrm at install time by
12225
12361
  // installOwnerAvatar (owner's avatar, or the bundled default) — so it is NOT
12226
- // a static manifest asset. animation-library.glb (the 12-clip core) still is;
12227
- // extra clips arrive via `genex controller anims` into public/assets/anims/.
12362
+ // a static manifest asset; at runtime the game loads the playing user's own
12363
+ // avatar (user.avatarUrl from the embed identity, AG-804) and this file
12364
+ // covers local dev + load failures. animation-library.glb (the 12-clip core)
12365
+ // still is a manifest asset; extra clips arrive via `genex controller anims`
12366
+ // into public/assets/anims/.
12228
12367
  assets: ["assets/animation-library.glb"],
12229
12368
  skill: "genex-threejs-character-controller",
12230
12369
  sketch: [
12231
12370
  `const physics = await PhysicsWorld.create();`,
12232
- `const { scene, vrm } = await loadVrm("./assets/avatar.vrm");`,
12371
+ `const { user } = await waitForPlayer(); // @genex-ai/embed-sdk \u2014 the player's own avatar`,
12372
+ `const { scene, vrm } = await loadVrm(user.avatarUrl ?? "./assets/avatar.vrm").catch(() => loadVrm("./assets/avatar.vrm"));`,
12233
12373
  `const clips = await loadCharacterClips(vrm); // core library + every genex-controller-anims pack`,
12234
12374
  `const character = new CharacterController(physics.world, camera, { ...characterPresets["default"].options, ...capsuleFromModel(scene), position: { x: 0, y: 2, z: 0 } });`,
12235
12375
  `character.root.add(scene); const anims = new CharacterAnimations(scene, clips);`,
@@ -12300,8 +12440,8 @@ var CONTROLLER_FILE_SETS = {
12300
12440
  ]
12301
12441
  }
12302
12442
  };
12303
- var CODE_DEST = path13.join("src", "controllers");
12304
- var ASSETS_DEST = path13.join("public", "assets");
12443
+ var CODE_DEST = path14.join("src", "controllers");
12444
+ var ASSETS_DEST = path14.join("public", "assets");
12305
12445
  async function runController(opts) {
12306
12446
  const log = createLogger({ quiet: opts.quiet });
12307
12447
  if (opts.kind?.trim() === "anims") {
@@ -12318,31 +12458,31 @@ async function runController(opts) {
12318
12458
  process.exitCode = 1;
12319
12459
  return;
12320
12460
  }
12321
- const srcDir = path13.join(getTemplatesDir(), "controllers");
12461
+ const srcDir = path14.join(getTemplatesDir(), "controllers");
12322
12462
  const root = opts.cwd ?? process.cwd();
12323
12463
  const set = CONTROLLER_FILE_SETS[kind];
12324
12464
  log.plain(c.bold(`genex controller ${kind}`));
12325
12465
  log.plain("");
12326
- log.step(`Installing the ${kind} controller into ${c.cyan(CODE_DEST + path13.sep)}`);
12466
+ log.step(`Installing the ${kind} controller into ${c.cyan(CODE_DEST + path14.sep)}`);
12327
12467
  const plan = [
12328
- ...set.code.map((rel) => ({ from: rel, rel: path13.join(CODE_DEST, rel) })),
12468
+ ...set.code.map((rel) => ({ from: rel, rel: path14.join(CODE_DEST, rel) })),
12329
12469
  ...set.assets.map((rel) => ({
12330
12470
  from: rel,
12331
- rel: path13.join(ASSETS_DEST, path13.basename(rel))
12471
+ rel: path14.join(ASSETS_DEST, path14.basename(rel))
12332
12472
  }))
12333
12473
  ];
12334
12474
  let copied = 0;
12335
12475
  let skipped = 0;
12336
12476
  try {
12337
12477
  for (const file of plan) {
12338
- const dest = path13.join(root, file.rel);
12478
+ const dest = path14.join(root, file.rel);
12339
12479
  if (!opts.force && await exists3(dest)) {
12340
12480
  skipped++;
12341
12481
  log.dim(` skipped ${file.rel} (exists \u2014 use --force to overwrite)`);
12342
12482
  continue;
12343
12483
  }
12344
- await fs12.mkdir(path13.dirname(dest), { recursive: true });
12345
- await fs12.copyFile(path13.join(srcDir, file.from), dest);
12484
+ await fs13.mkdir(path14.dirname(dest), { recursive: true });
12485
+ await fs13.copyFile(path14.join(srcDir, file.from), dest);
12346
12486
  copied++;
12347
12487
  log.dim(` ${file.rel}`);
12348
12488
  }
@@ -12427,9 +12567,9 @@ async function installMeshyCharacterManifest(args) {
12427
12567
  throw new Error("The API returned an invalid Meshy character manifest.");
12428
12568
  }
12429
12569
  assertCompleteMeshyControllerPack(manifest);
12430
- const destination = path13.join(args.root, ASSETS_DEST, "meshy-character.json");
12431
- await fs12.mkdir(path13.dirname(destination), { recursive: true });
12432
- await fs12.writeFile(
12570
+ const destination = path14.join(args.root, ASSETS_DEST, "meshy-character.json");
12571
+ await fs13.mkdir(path14.dirname(destination), { recursive: true });
12572
+ await fs13.writeFile(
12433
12573
  destination,
12434
12574
  `${JSON.stringify(manifest, null, 2)}
12435
12575
  `
@@ -12550,8 +12690,8 @@ function assertCompleteMeshyControllerPack(manifest) {
12550
12690
  }
12551
12691
  async function installOwnerAvatar(args) {
12552
12692
  const { root, srcDir, apiUrl, token, log } = args;
12553
- const dest = path13.join(root, ASSETS_DEST, "avatar.vrm");
12554
- await fs12.mkdir(path13.dirname(dest), { recursive: true });
12693
+ const dest = path14.join(root, ASSETS_DEST, "avatar.vrm");
12694
+ await fs13.mkdir(path14.dirname(dest), { recursive: true });
12555
12695
  if (token) {
12556
12696
  try {
12557
12697
  const res = await apiFetch(`${apiUrl}/api/avatars/me`, {
@@ -12563,7 +12703,7 @@ async function installOwnerAvatar(args) {
12563
12703
  const vrmRes = await fetch(me.vrmUrl);
12564
12704
  if (vrmRes.ok) {
12565
12705
  const buf = Buffer.from(await vrmRes.arrayBuffer());
12566
- await fs12.writeFile(dest, buf);
12706
+ await fs13.writeFile(dest, buf);
12567
12707
  log.dim(` public/assets/avatar.vrm (your avatar \u2014 ${(buf.length / 1e6).toFixed(1)} MB)`);
12568
12708
  return;
12569
12709
  }
@@ -12574,14 +12714,14 @@ async function installOwnerAvatar(args) {
12574
12714
  log.dim(" avatar fetch failed (offline?); using the bundled default.");
12575
12715
  }
12576
12716
  }
12577
- await fs12.copyFile(path13.join(srcDir, "assets", "default-avatar.vrm"), dest);
12717
+ await fs13.copyFile(path14.join(srcDir, "assets", "default-avatar.vrm"), dest);
12578
12718
  log.dim(
12579
12719
  token ? " public/assets/avatar.vrm (bundled default)" : " public/assets/avatar.vrm (bundled default \u2014 sign in and re-run for your own)"
12580
12720
  );
12581
12721
  }
12582
12722
  async function exists3(p) {
12583
12723
  try {
12584
- await fs12.access(p);
12724
+ await fs13.access(p);
12585
12725
  return true;
12586
12726
  } catch {
12587
12727
  return false;
@@ -12866,12 +13006,12 @@ function rank(items, query) {
12866
13006
  }
12867
13007
 
12868
13008
  // src/commands/ui.ts
12869
- import fs14 from "fs/promises";
12870
- import path14 from "path";
13009
+ import fs15 from "fs/promises";
13010
+ import path15 from "path";
12871
13011
  import { PNG as PNG3 } from "pngjs";
12872
13012
 
12873
13013
  // src/lib/png-tools.ts
12874
- import fs13 from "fs/promises";
13014
+ import fs14 from "fs/promises";
12875
13015
  import { PNG as PNG2 } from "pngjs";
12876
13016
  var ALPHA_TRANSPARENT_MAX = 16;
12877
13017
  var isHttpUrl = (s) => /^https?:\/\//i.test(s);
@@ -12882,12 +13022,12 @@ async function loadPng(input) {
12882
13022
  if (!res.ok) throw new Error(`Couldn't fetch ${input} (HTTP ${res.status}).`);
12883
13023
  buf = Buffer.from(await res.arrayBuffer());
12884
13024
  } else {
12885
- buf = await fs13.readFile(input);
13025
+ buf = await fs14.readFile(input);
12886
13026
  }
12887
13027
  return PNG2.sync.read(buf);
12888
13028
  }
12889
13029
  async function writePng(file, png) {
12890
- await fs13.writeFile(file, PNG2.sync.write(png));
13030
+ await fs14.writeFile(file, PNG2.sync.write(png));
12891
13031
  }
12892
13032
  function cropPng(image, box) {
12893
13033
  const out = new PNG2({ width: box.w, height: box.h });
@@ -13093,7 +13233,7 @@ async function uiExtract(opts, log) {
13093
13233
  const dilatePx = opts.dilate ?? 0;
13094
13234
  const sheet = await loadPng(input);
13095
13235
  const { width: W, height: H, data } = sheet;
13096
- await fs14.mkdir(outDir, { recursive: true });
13236
+ await fs15.mkdir(outDir, { recursive: true });
13097
13237
  log.plain(c.bold("genex ui extract"));
13098
13238
  log.dim(` ${input} (${W}x${H}) \u2192 ${outDir}, ${names.length} names`);
13099
13239
  let hasTransparency = false;
@@ -13252,7 +13392,7 @@ async function uiExtract(opts, log) {
13252
13392
  }
13253
13393
  }
13254
13394
  }
13255
- const outPath = path14.join(outDir, `${name}.png`);
13395
+ const outPath = path15.join(outDir, `${name}.png`);
13256
13396
  await writePng(outPath, out);
13257
13397
  const sidecar = {
13258
13398
  name,
@@ -13270,7 +13410,7 @@ async function uiExtract(opts, log) {
13270
13410
  componentPixels: comp.pixels
13271
13411
  };
13272
13412
  const { name: _n, out: _o, ...sidecarBody } = sidecar;
13273
- await fs14.writeFile(
13413
+ await fs15.writeFile(
13274
13414
  outPath.replace(/\.png$/i, "") + ".bbox.json",
13275
13415
  JSON.stringify(sidecarBody, null, 2)
13276
13416
  );
@@ -13279,8 +13419,8 @@ async function uiExtract(opts, log) {
13279
13419
  `${name}.png ${cropW}x${cropH} (ar ${sidecar.aspectRatio}) at sheet ${padX0},${padY0}`
13280
13420
  );
13281
13421
  }
13282
- const debugPath = path14.join(outDir, "extract-debug.json");
13283
- await fs14.writeFile(
13422
+ const debugPath = path15.join(outDir, "extract-debug.json");
13423
+ await fs15.writeFile(
13284
13424
  debugPath,
13285
13425
  JSON.stringify(
13286
13426
  {
@@ -13514,7 +13654,7 @@ async function uiMasks(opts, log) {
13514
13654
  const registrationTolerance = opts.registrationTolerance ?? 0.04;
13515
13655
  const edgeFlushMax = opts.edgeFlushMax ?? 0.04;
13516
13656
  const image = await loadPng(input);
13517
- await fs14.mkdir(outDir, { recursive: true });
13657
+ await fs15.mkdir(outDir, { recursive: true });
13518
13658
  log.plain(c.bold("genex ui masks"));
13519
13659
  log.dim(` ${input} (${image.width}x${image.height}), ${pairs.length} pair(s) \u2192 ${outDir}`);
13520
13660
  const results = [];
@@ -13557,11 +13697,11 @@ async function uiMasks(opts, log) {
13557
13697
  });
13558
13698
  }
13559
13699
  const overlay = makeOverlay(clean2, converted.png);
13560
- const framePath = path14.join(outDir, `${pair.name}-frame.png`);
13561
- const maskPath = path14.join(outDir, `${pair.name}-mask.png`);
13562
- const annotatedPath = path14.join(outDir, `${pair.name}-annotated-source.png`);
13563
- const overlayPath = path14.join(outDir, `${pair.name}-overlay.png`);
13564
- const metaPath = path14.join(outDir, `${pair.name}.annotated-progress.json`);
13700
+ const framePath = path15.join(outDir, `${pair.name}-frame.png`);
13701
+ const maskPath = path15.join(outDir, `${pair.name}-mask.png`);
13702
+ const annotatedPath = path15.join(outDir, `${pair.name}-annotated-source.png`);
13703
+ const overlayPath = path15.join(outDir, `${pair.name}-overlay.png`);
13704
+ const metaPath = path15.join(outDir, `${pair.name}.annotated-progress.json`);
13565
13705
  await writePng(framePath, clean2);
13566
13706
  await writePng(maskPath, converted.png);
13567
13707
  await writePng(annotatedPath, annotated);
@@ -13600,7 +13740,7 @@ async function uiMasks(opts, log) {
13600
13740
  },
13601
13741
  overlay: overlayPath
13602
13742
  };
13603
- await fs14.writeFile(metaPath, `${JSON.stringify(meta, null, 2)}
13743
+ await fs15.writeFile(metaPath, `${JSON.stringify(meta, null, 2)}
13604
13744
  `);
13605
13745
  results.push(meta);
13606
13746
  const fb = converted.bbox;
@@ -13609,8 +13749,8 @@ async function uiMasks(opts, log) {
13609
13749
  `${pair.name}: coverage ${meta.mask.coverage},${fbText} ${components.length} component(s), registration \u0394 ${meta.registration.bboxDelta}`
13610
13750
  );
13611
13751
  }
13612
- const indexPath = path14.join(outDir, "annotated-progress.json");
13613
- await fs14.writeFile(indexPath, `${JSON.stringify({ input, pairs: results }, null, 2)}
13752
+ const indexPath = path15.join(outDir, "annotated-progress.json");
13753
+ await fs15.writeFile(indexPath, `${JSON.stringify({ input, pairs: results }, null, 2)}
13614
13754
  `);
13615
13755
  log.plain("");
13616
13756
  log.success(`Wrote ${pairs.length} frame+mask pair(s) \u2192 ${outDir} (index: ${indexPath}).`);
@@ -13731,7 +13871,7 @@ async function uiTextColor(opts, log) {
13731
13871
  };
13732
13872
  process.stdout.write(`${JSON.stringify(result, null, 2)}
13733
13873
  `);
13734
- if (opts.out) await fs14.writeFile(opts.out, `${JSON.stringify(result, null, 2)}
13874
+ if (opts.out) await fs15.writeFile(opts.out, `${JSON.stringify(result, null, 2)}
13735
13875
  `);
13736
13876
  if (opts.cropPath) log.dim(` crop \u2192 ${opts.cropPath}`);
13737
13877
  }
@@ -13754,7 +13894,7 @@ async function uiTrim(opts, log) {
13754
13894
  await writePng(outPath, trimmed);
13755
13895
  const sidecar = computeBBoxes(trimmed);
13756
13896
  const sidecarPath = outPath.replace(/\.png$/i, "") + ".bbox.json";
13757
- await fs14.writeFile(sidecarPath, JSON.stringify(sidecar, null, 2));
13897
+ await fs15.writeFile(sidecarPath, JSON.stringify(sidecar, null, 2));
13758
13898
  log.success(
13759
13899
  `Trimmed ${png.width}x${png.height} \u2192 ${trimmed.width}x${trimmed.height} (${outPath}).`
13760
13900
  );