@genex-ai/cli-demo 1.7.0-dev.449 → 1.7.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
@@ -8,7 +8,7 @@ import fs from "fs";
8
8
  import os from "os";
9
9
  import path from "path";
10
10
  import { fileURLToPath } from "url";
11
- var RAW_CHANNEL = "dev";
11
+ var RAW_CHANNEL = "latest";
12
12
  var CLI_CHANNEL = RAW_CHANNEL === "dev" ? "dev" : "latest";
13
13
  var STANDS = {
14
14
  prod: { api: "https://api.genex.games", dashboard: "https://genex.games" },
@@ -1014,7 +1014,6 @@ Important note: put soul into your creations, with many details and love. Aim to
1014
1014
  16. Never add debug-only code to the game to check your own work \u2014 no hidden test modes, no special URL parameters, no forced-visible flags, no auth mocks, no pixel-sampling hooks. \`?genex_local_test=1\` is the platform's own supported mode and is fine; your own bypass is not. (The multiplayer skill's small build identifier, token-free status line, and connected-quorum watchdog are production supportability, not a bypass \u2014 keep those.)
1015
1015
  17. Input directions match their labels: A/\u2190 moves or turns the player screen-LEFT, D/\u2192 screen-RIGHT, mouse-up looks up, and drag-pan axes share ONE convention. The cursor is either the gameplay tool (RTS, card, builder) or locked away during play \u2014 keyboard-only games included. Check it in every milestone's smoke pass.
1016
1016
  18. NEVER delete, empty, move, rename, or overwrite anything you did not create yourself. This folder may hold the player's own reference images, notes, sketches, or an earlier attempt \u2014 files that exist nowhere else and have no undo, no trash, no backup. A non-empty folder is normal and is NEVER something to clean up, and "start clean" is never a reason. That rules out \`rm\`/\`rm -rf\`, \`git clean\`, \`git checkout -- .\`, \`git reset --hard\` over their work, deleting to resolve a conflict or a stuck interactive prompt, and every setup tool's offer to empty a directory (\`--force\`, \`--overwrite\`, "Remove existing files") \u2014 scaffold into a fresh subfolder and copy in instead. You may add files and edit the ones you wrote. If a step genuinely cannot continue without removing something of theirs, STOP and ask, naming the exact files, and wait for a yes \u2014 "it looks like junk" is never that yes. This binds hardest during setup, where it runs fast and automatically before the player has asked for anything at all.
1017
- 19. If the game sells anything, it sells it for coin at a fixed, visible price, and NEVER sells chance. Run this test on any purchasable thing before building it: does the player pay (with coin, or with anything coin bought, directly or indirectly), is the outcome uncertain when they pay, and is there a prize they wanted \u2014 all three yes means it is paid randomness, and you build the deterministic version instead. That rules out loot boxes, gacha, mystery boxes, crates, card packs, prize wheels and raffles; wagering, staking, betting, coinflips and casino or slot mechanics denominated in coin; and donation prompts, tip jars or any player-to-player coin transfer, because coin buys goods and never just moves. Randomness the player EARNS by playing is gameplay, not commerce \u2014 an enemy dropping a random item, a chest found in the level, a procedural layout, a crit roll \u2014 and is completely fine. Every coin price renders its real-money equivalent beside it (the server sends one with every item), item prices sit on the platform's price grid so no player is left holding change they cannot spend, and nothing in a shop carries a countdown, "limited time", or a stock counter. When a request crosses one of these lines, name the mechanic, give the one-sentence reason, propose a specific compliant alternative, and build that \u2014 never the banned version "as an option", never a partial one, and never after asking the player to confirm they want it. Load \`$genex-monetization\` before building a shop.
1018
1017
  ${CONTRACT_END}
1019
1018
  `;
1020
1019
  var CLAUDE_IMPORT_LINE = "@AGENTS.md";
@@ -1476,7 +1475,7 @@ async function createDraftProject(opts) {
1476
1475
  log.dim(` ${String(err)}`);
1477
1476
  return { meta: null };
1478
1477
  }
1479
- if ((res.status === 409 || res.status === 400) && i === 0) continue;
1478
+ if (res.status === 409 && i === 0) continue;
1480
1479
  if (res.status === 401) {
1481
1480
  log.warn("Not authorized to create the project (token rejected).");
1482
1481
  return { meta: null, unauthorized: true };
@@ -1829,159 +1828,8 @@ async function runInit(opts) {
1829
1828
  }
1830
1829
 
1831
1830
  // src/commands/link.ts
1832
- import fs12 from "fs/promises";
1833
- import os7 from "os";
1834
- import path12 from "path";
1835
-
1836
- // src/lib/source-sync.ts
1837
1831
  import fs11 from "fs/promises";
1838
1832
  import path11 from "path";
1839
- import os6 from "os";
1840
-
1841
- // src/utils/run.ts
1842
- import { spawn as spawn3 } from "child_process";
1843
- var WIN_SHELL_COMMANDS = /* @__PURE__ */ new Set(["npm", "npx"]);
1844
- function run(cmd, args, env) {
1845
- const shell = process.platform === "win32" && WIN_SHELL_COMMANDS.has(cmd);
1846
- return new Promise((resolve) => {
1847
- let child;
1848
- try {
1849
- child = spawn3(cmd, args, { env: env ? { ...process.env, ...env } : process.env, shell });
1850
- } catch {
1851
- resolve({ code: -1, out: "", err: `${cmd} not found` });
1852
- return;
1853
- }
1854
- let out = "";
1855
- let err = "";
1856
- child.stdout?.on("data", (d) => out += String(d));
1857
- child.stderr?.on("data", (d) => err += String(d));
1858
- child.on("error", () => resolve({ code: -1, out, err: `${cmd} not found` }));
1859
- child.on("close", (code2) => resolve({ code: code2 ?? -1, out, err }));
1860
- });
1861
- }
1862
-
1863
- // src/lib/source-sync.ts
1864
- async function readRemoteSource(apiUrl, token, projectId) {
1865
- try {
1866
- const res = await apiFetch(`${apiUrl}/api/projects/${projectId}`, {
1867
- headers: { Authorization: `Bearer ${token}` }
1868
- });
1869
- if (!res.ok) return null;
1870
- const data = await res.json().catch(() => null);
1871
- if (!data?.project) return null;
1872
- return {
1873
- stagingCommit: data.project.stagingCommitSha ?? null,
1874
- commit: data.project.commitSha ?? null
1875
- };
1876
- } catch {
1877
- return null;
1878
- }
1879
- }
1880
- function isStale(local, remote) {
1881
- if (!local || !remote) return false;
1882
- return local !== remote;
1883
- }
1884
- function reportStale(log, slug, local, remote) {
1885
- log.error(`${c.cyan(slug)} was updated from another device \u2014 nothing was deployed.`);
1886
- if (remote && local) {
1887
- log.dim(` the draft is on ${remote.slice(0, 7)}, this folder last shipped ${local.slice(0, 7)}`);
1888
- }
1889
- log.dim(` ${c.cyan("npx genex pull")} \u2014 take the other device's work (refuses if you have unshipped changes)`);
1890
- log.dim(` ${c.cyan("npx genex preview --force")} \u2014 keep yours and replace theirs`);
1891
- }
1892
- async function sourceTreeHash(cwd) {
1893
- const gitDir = await fs11.mkdtemp(path11.join(os6.tmpdir(), "genex-tree-"));
1894
- const base = { GIT_DIR: gitDir };
1895
- try {
1896
- if ((await run("git", ["init", "-q"], base)).code !== 0) return null;
1897
- await fs11.writeFile(
1898
- path11.join(gitDir, "info", "exclude"),
1899
- ["node_modules/", "dist/", ".git/", ".genex/", ".env", ".env.*", "!.env.example", ""].join("\n")
1900
- );
1901
- if ((await run("git", ["lfs", "version"], base)).code === 0) {
1902
- const filters = [
1903
- ["filter.lfs.clean", "git-lfs clean -- %f"],
1904
- ["filter.lfs.smudge", "git-lfs smudge -- %f"],
1905
- ["filter.lfs.process", "git-lfs filter-process"],
1906
- ["filter.lfs.required", "true"]
1907
- ];
1908
- for (const [key, value] of filters) await run("git", ["config", key, value], base);
1909
- }
1910
- const env = { ...base, GIT_WORK_TREE: cwd, GIT_INDEX_FILE: path11.join(gitDir, "index-tree") };
1911
- if ((await run("git", ["add", "-A"], env)).code !== 0) return null;
1912
- const tree = (await run("git", ["write-tree"], env)).out.trim();
1913
- return /^[0-9a-f]{40}$/.test(tree) ? tree : null;
1914
- } catch {
1915
- return null;
1916
- } finally {
1917
- await fs11.rm(gitDir, { recursive: true, force: true }).catch(() => {
1918
- });
1919
- }
1920
- }
1921
- function urlHasEmbeddedCredentials(url) {
1922
- return /^[a-z][a-z0-9+.-]*:\/\/[^/@]+@/i.test(url);
1923
- }
1924
- function credentialHelperOff(url) {
1925
- if (!urlHasEmbeddedCredentials(url)) return {};
1926
- return { GIT_CONFIG_COUNT: "1", GIT_CONFIG_KEY_0: "credential.helper", GIT_CONFIG_VALUE_0: "" };
1927
- }
1928
- async function fetchCloneGrant(apiUrl, token, projectId, log) {
1929
- let res;
1930
- try {
1931
- res = await apiFetch(`${apiUrl}/api/projects/${projectId}/push-token`, {
1932
- method: "POST",
1933
- headers: { Authorization: `Bearer ${token}` }
1934
- });
1935
- } catch (err) {
1936
- log.error(`Couldn't reach the API to authorize the source read: ${String(err)}`);
1937
- return null;
1938
- }
1939
- if (res.status === 401) {
1940
- log.error("Not authorized \u2014 your token may have expired. Re-run `genex auth`.");
1941
- return null;
1942
- }
1943
- if (!res.ok) {
1944
- log.error(`Couldn't authorize the source read (HTTP ${res.status}).`);
1945
- return null;
1946
- }
1947
- const data = await res.json().catch(() => null);
1948
- const url = data?.pushUrl ?? data?.cloneUrl;
1949
- if (!url) {
1950
- log.error("The API didn't return a source URL.");
1951
- return null;
1952
- }
1953
- return { cloneUrl: url, sourceRef: data?.sourceRef ?? null };
1954
- }
1955
- async function cloneSource(grant, dest, log) {
1956
- const args = grant.sourceRef ? ["clone", "--branch", grant.sourceRef, grant.cloneUrl, dest] : ["clone", grant.cloneUrl, dest];
1957
- const env = {
1958
- GIT_LFS_SKIP_SMUDGE: "1",
1959
- GIT_TERMINAL_PROMPT: "0",
1960
- ...credentialHelperOff(grant.cloneUrl)
1961
- };
1962
- const cloned = await run("git", args, env);
1963
- if (cloned.code !== 0) {
1964
- log.error("Couldn't download the game's source.");
1965
- log.dim(` git clone exited ${cloned.code}`);
1966
- return false;
1967
- }
1968
- const repo = { ...env, GIT_DIR: path11.join(dest, ".git"), GIT_WORK_TREE: dest };
1969
- const filters = [
1970
- ["filter.lfs.clean", "git-lfs clean -- %f"],
1971
- ["filter.lfs.smudge", "git-lfs smudge -- %f"],
1972
- ["filter.lfs.process", "git-lfs filter-process"],
1973
- ["filter.lfs.required", "true"]
1974
- ];
1975
- for (const [key, value] of filters) await run("git", ["config", key, value], repo);
1976
- const pulled = await run("git", ["lfs", "pull"], repo);
1977
- if (pulled.code !== 0) {
1978
- log.warn("Binary assets (models, textures, audio) are still pointer files \u2014 `git lfs pull` failed.");
1979
- log.dim(" The source is there; run `git lfs pull` in this folder once git-lfs works.");
1980
- }
1981
- return true;
1982
- }
1983
-
1984
- // src/commands/link.ts
1985
1833
  async function runLink(opts) {
1986
1834
  const log = createLogger({ quiet: opts.quiet });
1987
1835
  log.plain(c.bold("genex link"));
@@ -2029,9 +1877,7 @@ async function runLink(opts) {
2029
1877
  process.exitCode = 1;
2030
1878
  return;
2031
1879
  }
2032
- const wasEmpty = await isEmptyDir(process.cwd());
2033
1880
  await writeGitignore(process.cwd(), log);
2034
- const cloned = wasEmpty ? await downloadSource(apiUrl, token, project, log) : false;
2035
1881
  const meta = {
2036
1882
  id: project.id,
2037
1883
  slug: project.slug,
@@ -2045,72 +1891,30 @@ async function runLink(opts) {
2045
1891
  log.dim(` saved ${c.cyan(metaPath)}`);
2046
1892
  await writeGameConfigFiles(meta, log);
2047
1893
  await ensureSlugEnv(project.slug, log);
2048
- const remote = await readRemoteSource(apiUrl, token, project.id);
2049
- if (remote?.stagingCommit) meta.stagingCommit = remote.stagingCommit;
2050
- if (cloned) {
2051
- const tree = await sourceTreeHash(process.cwd());
2052
- if (tree) meta.sourceTree = tree;
2053
- }
2054
- await writeProject(meta);
2055
1894
  log.plain("");
2056
1895
  log.success(
2057
1896
  `Linked. This folder now updates ${c.cyan(project.slug)} \u2014 \`npx genex preview\` / \`publish\` ship to the same live game.`
2058
1897
  );
2059
- if (cloned) log.dim(` Run ${c.cyan("npm install")}, then keep building.`);
2060
1898
  if (project.playUrl) log.dim(` play URL: ${project.playUrl}`);
2061
1899
  }
2062
- async function isEmptyDir(cwd) {
2063
- try {
2064
- const entries = await fs12.readdir(cwd);
2065
- return entries.every((e) => e === ".git" || e === ".genex" || e === ".DS_Store");
2066
- } catch {
2067
- return false;
2068
- }
2069
- }
2070
- async function downloadSource(apiUrl, token, project, log) {
2071
- const grant = await fetchCloneGrant(apiUrl, token, project.id, log);
2072
- if (!grant) return false;
2073
- if (!grant.sourceRef) {
2074
- log.dim(" (no source saved for this game yet \u2014 nothing to download)");
2075
- return false;
2076
- }
2077
- log.step(`Downloading the ${grant.sourceRef === "preview" ? "draft" : "published"} source\u2026`);
2078
- const staging = await fs12.mkdtemp(path12.join(os7.tmpdir(), "genex-link-"));
2079
- const fresh = path12.join(staging, "source");
2080
- try {
2081
- if (!await cloneSource(grant, fresh, log)) return false;
2082
- await fs12.rm(path12.join(fresh, ".git"), { recursive: true, force: true });
2083
- for (const entry of await fs12.readdir(fresh)) {
2084
- await fs12.cp(path12.join(fresh, entry), path12.join(process.cwd(), entry), {
2085
- recursive: true,
2086
- force: true
2087
- });
2088
- }
2089
- log.success("Downloaded.");
2090
- return true;
2091
- } finally {
2092
- await fs12.rm(staging, { recursive: true, force: true }).catch(() => {
2093
- });
2094
- }
2095
- }
2096
1900
  async function ensureSlugEnv(slug, log, cwd = process.cwd()) {
2097
- const file = path12.join(cwd, ".env");
1901
+ const file = path11.join(cwd, ".env");
2098
1902
  let content;
2099
1903
  try {
2100
- content = await fs12.readFile(file, "utf8");
1904
+ content = await fs11.readFile(file, "utf8");
2101
1905
  } catch {
2102
1906
  return;
2103
1907
  }
2104
1908
  const re = /^(\s*VITE_GENEX_SLUG=)(.*)$/m;
2105
1909
  const m = content.match(re);
2106
1910
  if (!m) {
2107
- await fs12.appendFile(file, `${content.endsWith("\n") ? "" : "\n"}VITE_GENEX_SLUG=${slug}
1911
+ await fs11.appendFile(file, `${content.endsWith("\n") ? "" : "\n"}VITE_GENEX_SLUG=${slug}
2108
1912
  `);
2109
1913
  log.dim(` added VITE_GENEX_SLUG=${slug} to .env`);
2110
1914
  return;
2111
1915
  }
2112
1916
  if (m[2].trim() === slug) return;
2113
- await fs12.writeFile(file, content.replace(re, `$1${slug}`));
1917
+ await fs11.writeFile(file, content.replace(re, `$1${slug}`));
2114
1918
  log.dim(` updated .env: VITE_GENEX_SLUG=${slug} (was ${m[2].trim()})`);
2115
1919
  }
2116
1920
  async function fetchOwnProject(apiUrl, token, slug, log) {
@@ -2163,132 +1967,9 @@ async function listOwnSlugs(apiUrl, token, log) {
2163
1967
  }
2164
1968
  }
2165
1969
 
2166
- // src/commands/pull.ts
2167
- import fs13 from "fs/promises";
2168
- import path13 from "path";
2169
- import os8 from "os";
2170
- function isMachineLocal(entry) {
2171
- if (entry === ".genex" || entry === "node_modules" || entry === ".git") return true;
2172
- if (entry === ".env.example") return false;
2173
- return entry === ".env" || entry.startsWith(".env.");
2174
- }
2175
- async function runPull(opts) {
2176
- const log = createLogger({ quiet: opts.quiet });
2177
- const cwd = process.cwd();
2178
- log.plain(c.bold("genex pull"));
2179
- log.plain("");
2180
- const meta = await readProject(cwd);
2181
- if (!meta) {
2182
- log.error("This folder isn't linked to a game.");
2183
- log.dim(` Run ${c.cyan("npx genex link <slug>")} here, or in an empty folder to download it.`);
2184
- process.exitCode = 1;
2185
- return;
2186
- }
2187
- const token = await readUserToken(opts.envPath);
2188
- if (!token) {
2189
- log.error(`Not signed in. Run ${c.cyan("npx genex auth")} first.`);
2190
- process.exitCode = 1;
2191
- return;
2192
- }
2193
- const apiUrl = getApiUrl(opts.apiUrl ?? meta.apiUrl);
2194
- if (!opts.force) {
2195
- const tree2 = await sourceTreeHash(cwd);
2196
- if (!meta.sourceTree) {
2197
- log.error("This folder has never deployed, so there's no way to tell what's unsaved here.");
2198
- log.dim(` ${c.cyan("npx genex preview")} first to save it, or ${c.cyan("npx genex pull --force")} to discard it.`);
2199
- process.exitCode = 1;
2200
- return;
2201
- }
2202
- if (tree2 === null) {
2203
- log.error("Couldn't check this folder for unsaved changes (git is unavailable).");
2204
- log.dim(` ${c.cyan("npx genex pull --force")} replaces it anyway.`);
2205
- process.exitCode = 1;
2206
- return;
2207
- }
2208
- if (tree2 !== meta.sourceTree) {
2209
- log.error("This folder has changes that were never deployed.");
2210
- log.dim(` ${c.cyan("npx genex preview")} ships them first (add ${c.cyan("--force")} if it says another device updated this)`);
2211
- log.dim(` ${c.cyan("npx genex pull --force")} takes the draft and keeps a copy of what is here`);
2212
- process.exitCode = 1;
2213
- return;
2214
- }
2215
- }
2216
- log.step(`Fetching ${c.cyan(meta.slug)}\u2026`);
2217
- const grant = await fetchCloneGrant(apiUrl, token, meta.id, log);
2218
- if (!grant) {
2219
- process.exitCode = 1;
2220
- return;
2221
- }
2222
- if (!grant.sourceRef) {
2223
- log.error(`${c.cyan(meta.slug)} has no source saved yet \u2014 nothing to pull.`);
2224
- log.dim(` Run ${c.cyan("npx genex preview")} on the machine that has the game.`);
2225
- process.exitCode = 1;
2226
- return;
2227
- }
2228
- const staging = await fs13.mkdtemp(path13.join(os8.tmpdir(), "genex-pull-"));
2229
- const fresh = path13.join(staging, "source");
2230
- try {
2231
- if (!await cloneSource(grant, fresh, log)) {
2232
- process.exitCode = 1;
2233
- return;
2234
- }
2235
- await fs13.rm(path13.join(fresh, ".git"), { recursive: true, force: true });
2236
- const kept = await keepReplaced(cwd, log);
2237
- await replaceTree(cwd, fresh);
2238
- if (kept) {
2239
- log.plain("");
2240
- log.info(`What was here is kept at ${c.cyan(path13.relative(cwd, kept) || kept)}`);
2241
- log.dim(" Nothing was thrown away \u2014 re-apply from there, or delete it when you are done.");
2242
- }
2243
- } finally {
2244
- await fs13.rm(staging, { recursive: true, force: true }).catch(() => {
2245
- });
2246
- }
2247
- const remote = await readRemoteSource(apiUrl, token, meta.id);
2248
- const tree = await sourceTreeHash(cwd);
2249
- await writeProject(
2250
- {
2251
- ...meta,
2252
- ...remote?.stagingCommit ? { stagingCommit: remote.stagingCommit } : {},
2253
- ...tree ? { sourceTree: tree } : {}
2254
- },
2255
- cwd
2256
- );
2257
- log.plain("");
2258
- log.success(`Pulled ${c.cyan(meta.slug)}${grant.sourceRef === "preview" ? " (draft)" : ""}.`);
2259
- log.dim(` Run ${c.cyan("npm install")} if dependencies changed, then keep building.`);
2260
- }
2261
- async function keepReplaced(cwd, log) {
2262
- const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
2263
- const dest = path13.join(cwd, ".genex", `replaced-${stamp}`);
2264
- try {
2265
- const entries = (await fs13.readdir(cwd)).filter((e) => !isMachineLocal(e));
2266
- if (entries.length === 0) return null;
2267
- await fs13.mkdir(dest, { recursive: true });
2268
- for (const entry of entries) {
2269
- await fs13.cp(path13.join(cwd, entry), path13.join(dest, entry), { recursive: true });
2270
- }
2271
- return dest;
2272
- } catch (err) {
2273
- log.warn("Couldn't keep a copy of what's being replaced \u2014 continuing with the pull.");
2274
- log.dim(` ${String(err)}`);
2275
- return null;
2276
- }
2277
- }
2278
- async function replaceTree(dest, src) {
2279
- for (const entry of await fs13.readdir(dest)) {
2280
- if (isMachineLocal(entry)) continue;
2281
- await fs13.rm(path13.join(dest, entry), { recursive: true, force: true });
2282
- }
2283
- for (const entry of await fs13.readdir(src)) {
2284
- if (isMachineLocal(entry)) continue;
2285
- await fs13.cp(path13.join(src, entry), path13.join(dest, entry), { recursive: true });
2286
- }
2287
- }
2288
-
2289
1970
  // src/commands/rename.ts
2290
- import fs14 from "fs/promises";
2291
- import path14 from "path";
1971
+ import fs12 from "fs/promises";
1972
+ import path12 from "path";
2292
1973
  async function runRename(opts) {
2293
1974
  const log = createLogger({ quiet: opts.quiet });
2294
1975
  log.plain(c.bold("genex rename"));
@@ -2314,9 +1995,8 @@ async function runRename(opts) {
2314
1995
  const apiUrl = getApiUrl(opts.apiUrl ?? meta.apiUrl);
2315
1996
  const from = meta.slug;
2316
1997
  if (meta.status === "published" && !opts.yes) {
2317
- log.warn(`This retires ${c.cyan(from)} for good \u2014 it will redirect here, and nobody can reuse it.`);
2318
- log.dim(" Links already shared keep working. Plays, likes and comments move with the game.");
2319
- log.dim(" Games get a limited number of renames. Re-run with --yes to spend one.");
1998
+ log.warn(`This retires ${c.cyan(`https://${from}.genex.technology/`)} \u2014 links already shared to it will stop working.`);
1999
+ log.dim(" Plays, likes, and comments move with the game. Re-run with --yes to go ahead.");
2320
2000
  process.exitCode = 1;
2321
2001
  return;
2322
2002
  }
@@ -2366,33 +2046,32 @@ async function runRename(opts) {
2366
2046
  await rewriteBakedSlug(from, project.slug, log);
2367
2047
  log.plain("");
2368
2048
  log.success(`Renamed to ${c.cyan(project.slug)}.`);
2369
- log.dim(` ${from} still works \u2014 it redirects here.`);
2370
2049
  if (project.playUrl) log.dim(` play: ${project.playUrl}`);
2371
2050
  if (meta.status === "published") {
2372
2051
  const dashboard = meta.dashboardOrigins?.[0];
2373
- if (dashboard) log.dim(` page: ${dashboard}/${project.slug}`);
2052
+ if (dashboard) log.dim(` world page: ${dashboard}/world/${project.slug}`);
2374
2053
  }
2375
2054
  log.plain("");
2376
2055
  log.info("Run `genex preview` (or `publish`) to rebuild \u2014 the new slug is baked into the bundle.");
2377
2056
  }
2378
2057
  async function rewriteSlugEnv(from, to, log, cwd = process.cwd()) {
2379
- const file = path14.join(cwd, ".env");
2058
+ const file = path12.join(cwd, ".env");
2380
2059
  let content;
2381
2060
  try {
2382
- content = await fs14.readFile(file, "utf8");
2061
+ content = await fs12.readFile(file, "utf8");
2383
2062
  } catch {
2384
2063
  return;
2385
2064
  }
2386
2065
  const re = /^(\s*VITE_GENEX_SLUG=)(.*)$/m;
2387
2066
  if (!re.test(content)) return;
2388
- await fs14.writeFile(file, content.replace(re, `$1${to}`));
2067
+ await fs12.writeFile(file, content.replace(re, `$1${to}`));
2389
2068
  log.dim(` .env: VITE_GENEX_SLUG=${to} (was ${from})`);
2390
2069
  }
2391
2070
  async function rewriteBakedSlug(from, to, log, cwd = process.cwd()) {
2392
- const file = path14.join(cwd, "src", "genex.config.ts");
2071
+ const file = path12.join(cwd, "src", "genex.config.ts");
2393
2072
  let content;
2394
2073
  try {
2395
- content = await fs14.readFile(file, "utf8");
2074
+ content = await fs12.readFile(file, "utf8");
2396
2075
  } catch {
2397
2076
  return;
2398
2077
  }
@@ -2402,7 +2081,7 @@ async function rewriteBakedSlug(from, to, log, cwd = process.cwd()) {
2402
2081
  log.warn(` src/genex.config.ts has no "${from}" literal \u2014 check its slug by hand.`);
2403
2082
  return;
2404
2083
  }
2405
- await fs14.writeFile(file, content.replace(quoted, `"${to}"`));
2084
+ await fs12.writeFile(file, content.replace(quoted, `"${to}"`));
2406
2085
  log.dim(` src/genex.config.ts: baked slug -> ${to}`);
2407
2086
  }
2408
2087
 
@@ -2530,11 +2209,11 @@ function relTime(iso) {
2530
2209
  }
2531
2210
 
2532
2211
  // src/lib/deploy.ts
2533
- import "child_process";
2212
+ import { spawn as spawn3 } from "child_process";
2534
2213
  import crypto3 from "crypto";
2535
- import fs17 from "fs/promises";
2536
- import os9 from "os";
2537
- import path16 from "path";
2214
+ import fs15 from "fs/promises";
2215
+ import os6 from "os";
2216
+ import path14 from "path";
2538
2217
 
2539
2218
  // ../../packages/mobile-scan/src/image-dims.ts
2540
2219
  function u32be(b, o) {
@@ -2798,12 +2477,12 @@ function tierFor(estVramMb) {
2798
2477
  }
2799
2478
 
2800
2479
  // src/commands/ui.ts
2801
- import fs16 from "fs/promises";
2802
- import path15 from "path";
2480
+ import fs14 from "fs/promises";
2481
+ import path13 from "path";
2803
2482
  import { PNG as PNG2 } from "pngjs";
2804
2483
 
2805
2484
  // src/lib/png-tools.ts
2806
- import fs15 from "fs/promises";
2485
+ import fs13 from "fs/promises";
2807
2486
  import { PNG } from "pngjs";
2808
2487
  var ALPHA_TRANSPARENT_MAX = 16;
2809
2488
  var isHttpUrl = (s) => /^https?:\/\//i.test(s);
@@ -2814,12 +2493,12 @@ async function loadPng(input) {
2814
2493
  if (!res.ok) throw new Error(`Couldn't fetch ${input} (HTTP ${res.status}).`);
2815
2494
  buf = Buffer.from(await res.arrayBuffer());
2816
2495
  } else {
2817
- buf = await fs15.readFile(input);
2496
+ buf = await fs13.readFile(input);
2818
2497
  }
2819
2498
  return PNG.sync.read(buf);
2820
2499
  }
2821
2500
  async function writePng(file, png) {
2822
- await fs15.writeFile(file, PNG.sync.write(png));
2501
+ await fs13.writeFile(file, PNG.sync.write(png));
2823
2502
  }
2824
2503
  function cropPng(image, box) {
2825
2504
  const out = new PNG({ width: box.w, height: box.h });
@@ -3119,7 +2798,7 @@ async function uiExtract(opts, log) {
3119
2798
  const dilatePx = opts.dilate ?? 0;
3120
2799
  const sheet = await loadPng(input);
3121
2800
  const { width: W, height: H, data } = sheet;
3122
- await fs16.mkdir(outDir, { recursive: true });
2801
+ await fs14.mkdir(outDir, { recursive: true });
3123
2802
  log.plain(c.bold("genex ui extract"));
3124
2803
  log.dim(` ${input} (${W}x${H}) \u2192 ${outDir}, ${names.length} names`);
3125
2804
  let hasTransparency = false;
@@ -3348,7 +3027,7 @@ async function uiExtract(opts, log) {
3348
3027
  rimPixels: speckle.sampled
3349
3028
  });
3350
3029
  }
3351
- const outPath = path15.join(outDir, `${name}.png`);
3030
+ const outPath = path13.join(outDir, `${name}.png`);
3352
3031
  await writePng(outPath, out);
3353
3032
  const sidecar = {
3354
3033
  name,
@@ -3367,7 +3046,7 @@ async function uiExtract(opts, log) {
3367
3046
  defringed
3368
3047
  };
3369
3048
  const { name: _n, out: _o, ...sidecarBody } = sidecar;
3370
- await fs16.writeFile(
3049
+ await fs14.writeFile(
3371
3050
  outPath.replace(/\.png$/i, "") + ".bbox.json",
3372
3051
  JSON.stringify(sidecarBody, null, 2)
3373
3052
  );
@@ -3376,8 +3055,8 @@ async function uiExtract(opts, log) {
3376
3055
  `${name}.png ${cropW}x${cropH} (ar ${sidecar.aspectRatio}) at sheet ${padX0},${padY0}`
3377
3056
  );
3378
3057
  }
3379
- const debugPath = path15.join(outDir, "extract-debug.json");
3380
- await fs16.writeFile(
3058
+ const debugPath = path13.join(outDir, "extract-debug.json");
3059
+ await fs14.writeFile(
3381
3060
  debugPath,
3382
3061
  JSON.stringify(
3383
3062
  {
@@ -3839,7 +3518,7 @@ async function uiMasks(opts, log) {
3839
3518
  const registrationTolerance = opts.registrationTolerance ?? 0.04;
3840
3519
  const edgeFlushMax = opts.edgeFlushMax ?? 0.04;
3841
3520
  const loosened = registrationTolerance > 0.04 || edgeFlushMax > 0.04 || minCoverage < 0.01 || maxCoverage > 0.85;
3842
- await fs16.mkdir(outDir, { recursive: true });
3521
+ await fs14.mkdir(outDir, { recursive: true });
3843
3522
  log.plain(c.bold("genex ui masks"));
3844
3523
  log.dim(` ${input} (${image.width}x${image.height}), ${pairs.length} pair(s) \u2192 ${outDir}`);
3845
3524
  const sheetComponents = detectSheetComponents(image, opts.minPixels ?? 2e3);
@@ -3892,11 +3571,11 @@ async function uiMasks(opts, log) {
3892
3571
  });
3893
3572
  }
3894
3573
  const overlay = makeOverlay(clean2, converted.png);
3895
- const framePath = path15.join(outDir, `${pair.name}-frame.png`);
3896
- const maskPath = path15.join(outDir, `${pair.name}-mask.png`);
3897
- const annotatedPath = path15.join(outDir, `${pair.name}-annotated-source.png`);
3898
- const overlayPath = path15.join(outDir, `${pair.name}-overlay.png`);
3899
- const metaPath = path15.join(outDir, `${pair.name}.annotated-progress.json`);
3574
+ const framePath = path13.join(outDir, `${pair.name}-frame.png`);
3575
+ const maskPath = path13.join(outDir, `${pair.name}-mask.png`);
3576
+ const annotatedPath = path13.join(outDir, `${pair.name}-annotated-source.png`);
3577
+ const overlayPath = path13.join(outDir, `${pair.name}-overlay.png`);
3578
+ const metaPath = path13.join(outDir, `${pair.name}.annotated-progress.json`);
3900
3579
  await writePng(framePath, clean2);
3901
3580
  await writePng(maskPath, converted.png);
3902
3581
  await writePng(annotatedPath, annotated);
@@ -3943,7 +3622,7 @@ async function uiMasks(opts, log) {
3943
3622
  },
3944
3623
  overlay: overlayPath
3945
3624
  };
3946
- await fs16.writeFile(metaPath, `${JSON.stringify(meta, null, 2)}
3625
+ await fs14.writeFile(metaPath, `${JSON.stringify(meta, null, 2)}
3947
3626
  `);
3948
3627
  results.push(meta);
3949
3628
  const fb = converted.bbox;
@@ -3959,8 +3638,8 @@ async function uiMasks(opts, log) {
3959
3638
  );
3960
3639
  }
3961
3640
  }
3962
- const indexPath = path15.join(outDir, "annotated-progress.json");
3963
- await fs16.writeFile(indexPath, `${JSON.stringify({ input, loosened, pairs: results }, null, 2)}
3641
+ const indexPath = path13.join(outDir, "annotated-progress.json");
3642
+ await fs14.writeFile(indexPath, `${JSON.stringify({ input, loosened, pairs: results }, null, 2)}
3964
3643
  `);
3965
3644
  log.plain("");
3966
3645
  log.success(`Wrote ${pairs.length} frame+mask pair(s) \u2192 ${outDir} (index: ${indexPath}).`);
@@ -4086,7 +3765,7 @@ async function uiTextColor(opts, log) {
4086
3765
  };
4087
3766
  process.stdout.write(`${JSON.stringify(result, null, 2)}
4088
3767
  `);
4089
- if (opts.out) await fs16.writeFile(opts.out, `${JSON.stringify(result, null, 2)}
3768
+ if (opts.out) await fs14.writeFile(opts.out, `${JSON.stringify(result, null, 2)}
4090
3769
  `);
4091
3770
  if (opts.cropPath) log.dim(` crop \u2192 ${opts.cropPath}`);
4092
3771
  }
@@ -4118,7 +3797,7 @@ async function uiTrim(opts, log) {
4118
3797
  const sidecar = computeBBoxes(trimmed);
4119
3798
  const speckleAllowed = !!(speckle && speckle.ratio > SPECKLE_MAX_RATIO);
4120
3799
  const sidecarPath = outPath.replace(/\.png$/i, "") + ".bbox.json";
4121
- await fs16.writeFile(
3800
+ await fs14.writeFile(
4122
3801
  sidecarPath,
4123
3802
  JSON.stringify(speckleAllowed ? { ...sidecar, speckleAllowed: true } : sidecar, null, 2)
4124
3803
  );
@@ -4225,7 +3904,7 @@ async function uiPlate(opts, log) {
4225
3904
  fail("No interior found \u2014 the image is fully transparent (or erode ate everything). Check --in / lower --erode.");
4226
3905
  }
4227
3906
  await writePng(outPath, out);
4228
- const name = path15.basename(outPath);
3907
+ const name = path13.basename(outPath);
4229
3908
  log.plain(c.bold("genex ui plate"));
4230
3909
  log.success(`${outPath} ${W}x${H}, interior ${(count / (W * H) * 100).toFixed(1)}% (erode ${erode}px)`);
4231
3910
  log.dim(" Wire it as the plate's silhouette (same box as the frame <img>, plate UNDER the art):");
@@ -4390,13 +4069,13 @@ async function walkFiles(dir) {
4390
4069
  const out = [];
4391
4070
  let entries;
4392
4071
  try {
4393
- entries = await fs16.readdir(dir, { withFileTypes: true });
4072
+ entries = await fs14.readdir(dir, { withFileTypes: true });
4394
4073
  } catch {
4395
4074
  return out;
4396
4075
  }
4397
4076
  for (const entry of entries) {
4398
4077
  if (entry.name === "node_modules" || entry.name.startsWith(".")) continue;
4399
- const p = path15.join(dir, entry.name);
4078
+ const p = path13.join(dir, entry.name);
4400
4079
  if (entry.isDirectory()) out.push(...await walkFiles(p));
4401
4080
  else out.push(p);
4402
4081
  }
@@ -4405,7 +4084,7 @@ async function walkFiles(dir) {
4405
4084
  async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
4406
4085
  const viewportFindings = [];
4407
4086
  try {
4408
- const indexHtml = await fs16.readFile(path15.join(cwd, "index.html"), "utf8");
4087
+ const indexHtml = await fs14.readFile(path13.join(cwd, "index.html"), "utf8");
4409
4088
  if (!/<meta[^>]+name=["']viewport["']/i.test(indexHtml)) {
4410
4089
  viewportFindings.push({
4411
4090
  kind: "viewport-meta",
@@ -4419,28 +4098,28 @@ async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
4419
4098
  }
4420
4099
  } catch {
4421
4100
  }
4422
- const absAssets = path15.resolve(cwd, assetDir);
4101
+ const absAssets = path13.resolve(cwd, assetDir);
4423
4102
  try {
4424
- if (!(await fs16.stat(absAssets)).isDirectory()) {
4103
+ if (!(await fs14.stat(absAssets)).isDirectory()) {
4425
4104
  return viewportFindings.length > 0 ? viewportFindings : null;
4426
4105
  }
4427
4106
  } catch {
4428
4107
  return viewportFindings.length > 0 ? viewportFindings : null;
4429
4108
  }
4430
- const srcFiles = (await walkFiles(path15.resolve(cwd, srcDir))).filter(
4431
- (p) => AUDIT_SRC_EXTS.has(path15.extname(p).toLowerCase())
4109
+ const srcFiles = (await walkFiles(path13.resolve(cwd, srcDir))).filter(
4110
+ (p) => AUDIT_SRC_EXTS.has(path13.extname(p).toLowerCase())
4432
4111
  );
4433
4112
  try {
4434
- for (const name of await fs16.readdir(cwd)) {
4435
- const ext = path15.extname(name).toLowerCase();
4436
- if (ext === ".html" || ext === ".css") srcFiles.push(path15.join(cwd, name));
4113
+ for (const name of await fs14.readdir(cwd)) {
4114
+ const ext = path13.extname(name).toLowerCase();
4115
+ if (ext === ".html" || ext === ".css") srcFiles.push(path13.join(cwd, name));
4437
4116
  }
4438
4117
  } catch {
4439
4118
  }
4440
4119
  const sources = [];
4441
4120
  for (const p of srcFiles) {
4442
4121
  try {
4443
- sources.push({ rel: path15.relative(cwd, p), text: await fs16.readFile(p, "utf8") });
4122
+ sources.push({ rel: path13.relative(cwd, p), text: await fs14.readFile(p, "utf8") });
4444
4123
  } catch {
4445
4124
  }
4446
4125
  }
@@ -4450,11 +4129,11 @@ async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
4450
4129
  const metaByName = /* @__PURE__ */ new Map();
4451
4130
  const bboxByPng = /* @__PURE__ */ new Map();
4452
4131
  for (const p of assetFiles) {
4453
- const base = path15.basename(p);
4132
+ const base = path13.basename(p);
4454
4133
  const metaMatch = /^(.+)\.annotated-progress\.json$/.exec(base);
4455
4134
  if (metaMatch) {
4456
4135
  try {
4457
- const meta = JSON.parse(await fs16.readFile(p, "utf8"));
4136
+ const meta = JSON.parse(await fs14.readFile(p, "utf8"));
4458
4137
  metaByName.set(metaMatch[1], {
4459
4138
  cleanCrop: meta.clean?.crop ?? null,
4460
4139
  loosened: meta.loosened === true
@@ -4465,7 +4144,7 @@ async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
4465
4144
  }
4466
4145
  if (base.endsWith(".bbox.json")) {
4467
4146
  try {
4468
- const sidecar = JSON.parse(await fs16.readFile(p, "utf8"));
4147
+ const sidecar = JSON.parse(await fs14.readFile(p, "utf8"));
4469
4148
  if (sidecar.sheetBBox) bboxByPng.set(base.replace(/\.bbox\.json$/, ".png"), sidecar.sheetBBox);
4470
4149
  } catch {
4471
4150
  }
@@ -4474,7 +4153,7 @@ async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
4474
4153
  for (const [name, meta] of metaByName) {
4475
4154
  if (!meta.cleanCrop || !referenced(`${name}-mask.png`)) continue;
4476
4155
  for (const p of assetFiles) {
4477
- const base = path15.basename(p);
4156
+ const base = path13.basename(p);
4478
4157
  if (!base.toLowerCase().endsWith(".png")) continue;
4479
4158
  if (base !== `${name}.png` && !base.startsWith(`${name}-`)) continue;
4480
4159
  if (/-mask\.png$|-frame\.png$|-overlay\.png$|-annotated-source\.png$/.test(base)) continue;
@@ -4498,7 +4177,7 @@ async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
4498
4177
  }
4499
4178
  const pngByBase = /* @__PURE__ */ new Map();
4500
4179
  for (const p of assetFiles) {
4501
- const base = path15.basename(p);
4180
+ const base = path13.basename(p);
4502
4181
  if (base.toLowerCase().endsWith(".png")) pngByBase.set(base, p);
4503
4182
  }
4504
4183
  for (const [maskBase, maskPath] of pngByBase) {
@@ -4511,8 +4190,8 @@ async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
4511
4190
  let frame;
4512
4191
  let mask;
4513
4192
  try {
4514
- frame = PNG2.sync.read(await fs16.readFile(framePath));
4515
- mask = PNG2.sync.read(await fs16.readFile(maskPath));
4193
+ frame = PNG2.sync.read(await fs14.readFile(framePath));
4194
+ mask = PNG2.sync.read(await fs14.readFile(maskPath));
4516
4195
  } catch {
4517
4196
  continue;
4518
4197
  }
@@ -4531,7 +4210,7 @@ async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
4531
4210
  if (!referenced(base)) continue;
4532
4211
  let png;
4533
4212
  try {
4534
- png = PNG2.sync.read(await fs16.readFile(p));
4213
+ png = PNG2.sync.read(await fs14.readFile(p));
4535
4214
  } catch {
4536
4215
  continue;
4537
4216
  }
@@ -4551,7 +4230,7 @@ async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
4551
4230
  }
4552
4231
  const maskReported = /* @__PURE__ */ new Set();
4553
4232
  for (const p of assetFiles) {
4554
- const m = /^(.+)\.annotated-progress\.json$/.exec(path15.basename(p));
4233
+ const m = /^(.+)\.annotated-progress\.json$/.exec(path13.basename(p));
4555
4234
  if (!m) continue;
4556
4235
  const maskBase = `${m[1]}-mask.png`;
4557
4236
  if (!referenced(maskBase)) {
@@ -4563,14 +4242,14 @@ async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
4563
4242
  }
4564
4243
  }
4565
4244
  for (const p of assetFiles) {
4566
- const base = path15.basename(p);
4245
+ const base = path13.basename(p);
4567
4246
  if (!base.toLowerCase().endsWith(".png")) continue;
4568
4247
  if (/-annotated-source\.png$|-overlay\.png$/.test(base)) continue;
4569
4248
  if (maskReported.has(base)) continue;
4570
4249
  if (!referenced(base)) {
4571
4250
  findings.push({
4572
4251
  kind: "unwired-sprite",
4573
- message: `${path15.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.)`
4252
+ 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.)`
4574
4253
  });
4575
4254
  }
4576
4255
  }
@@ -4623,6 +4302,25 @@ async function uiAudit(opts, log) {
4623
4302
  }
4624
4303
 
4625
4304
  // src/lib/deploy.ts
4305
+ var WIN_SHELL_COMMANDS = /* @__PURE__ */ new Set(["npm", "npx"]);
4306
+ function run(cmd, args, env) {
4307
+ const shell = process.platform === "win32" && WIN_SHELL_COMMANDS.has(cmd);
4308
+ return new Promise((resolve) => {
4309
+ let child;
4310
+ try {
4311
+ child = spawn3(cmd, args, { env: env ? { ...process.env, ...env } : process.env, shell });
4312
+ } catch {
4313
+ resolve({ code: -1, out: "", err: `${cmd} not found` });
4314
+ return;
4315
+ }
4316
+ let out = "";
4317
+ let err = "";
4318
+ child.stdout?.on("data", (d) => out += String(d));
4319
+ child.stderr?.on("data", (d) => err += String(d));
4320
+ child.on("error", () => resolve({ code: -1, out, err: `${cmd} not found` }));
4321
+ child.on("close", (code2) => resolve({ code: code2 ?? -1, out, err }));
4322
+ });
4323
+ }
4626
4324
  function printMobilePreflight(files, log) {
4627
4325
  try {
4628
4326
  const scan = scanBundle(files.map((f) => ({ relPath: f.relPath, bytes: f.bytes })));
@@ -4685,7 +4383,7 @@ async function printUiAuditPreflight(log) {
4685
4383
  }
4686
4384
  async function printPipelineStatePreflight(log, cwd = process.cwd()) {
4687
4385
  try {
4688
- const design = await fs17.readFile(path16.join(cwd, "DESIGN.md"), "utf8").catch(() => "");
4386
+ const design = await fs15.readFile(path14.join(cwd, "DESIGN.md"), "utf8").catch(() => "");
4689
4387
  const warnings = [];
4690
4388
  if (design.length > 0 && !/##\s*build plan & status/i.test(design)) {
4691
4389
  warnings.push(
@@ -4693,7 +4391,7 @@ async function printPipelineStatePreflight(log, cwd = process.cwd()) {
4693
4391
  );
4694
4392
  }
4695
4393
  if (!/player character:/i.test(design)) {
4696
- const hasCharacter = await fs17.access(path16.join(cwd, "public", "assets", "meshy-character.json")).then(() => true, () => false);
4394
+ const hasCharacter = await fs15.access(path14.join(cwd, "public", "assets", "meshy-character.json")).then(() => true, () => false);
4697
4395
  if (!hasCharacter && await loadsPlayerBody(cwd)) {
4698
4396
  warnings.push(
4699
4397
  `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).`
@@ -4707,12 +4405,12 @@ async function printPipelineStatePreflight(log, cwd = process.cwd()) {
4707
4405
  async function loadsPlayerBody(cwd) {
4708
4406
  const BODY_LOADERS = /\bloadPlayerCharacter\s*\(|\bloadVrm(?:Clone)?\s*\(/;
4709
4407
  try {
4710
- const entries = await fs17.readdir(path16.join(cwd, "src"), { recursive: true });
4408
+ const entries = await fs15.readdir(path14.join(cwd, "src"), { recursive: true });
4711
4409
  for (const rel of entries) {
4712
4410
  if (rel.includes("node_modules")) continue;
4713
- if (rel.split(path16.sep)[0] === "controllers" || rel.startsWith("controllers/")) continue;
4411
+ if (rel.split(path14.sep)[0] === "controllers" || rel.startsWith("controllers/")) continue;
4714
4412
  if (!/\.(ts|tsx|js|jsx)$/.test(rel)) continue;
4715
- const text = await fs17.readFile(path16.join(cwd, "src", rel), "utf8").catch(() => "");
4413
+ const text = await fs15.readFile(path14.join(cwd, "src", rel), "utf8").catch(() => "");
4716
4414
  if (BODY_LOADERS.test(text)) return true;
4717
4415
  }
4718
4416
  } catch {
@@ -4725,15 +4423,6 @@ function isSecretEnvFile(name) {
4725
4423
  }
4726
4424
  async function deployGame(ctx, opts, log) {
4727
4425
  const cwd = process.cwd();
4728
- const meta = await readProject(cwd);
4729
- const expectStagingCommit = opts.force ? void 0 : meta?.stagingCommit;
4730
- if (expectStagingCommit) {
4731
- const remote = await readRemoteSource(ctx.apiUrl, ctx.token, ctx.projectId);
4732
- if (remote && isStale(expectStagingCommit, remote.stagingCommit)) {
4733
- reportStale(log, ctx.slug ?? meta?.slug ?? "this game", expectStagingCommit, remote.stagingCommit);
4734
- return false;
4735
- }
4736
- }
4737
4426
  if (!opts.noBuild && await hasBuildScript(cwd)) {
4738
4427
  log.step("Building the production bundle\u2026");
4739
4428
  const built = await run("npm", ["run", "build"]);
@@ -4745,9 +4434,9 @@ async function deployGame(ctx, opts, log) {
4745
4434
  }
4746
4435
  log.success("Built.");
4747
4436
  }
4748
- const distDir = path16.join(cwd, "dist");
4437
+ const distDir = path14.join(cwd, "dist");
4749
4438
  const siteDir = await isDir2(distDir) ? distDir : cwd;
4750
- const rel = path16.relative(cwd, siteDir) || ".";
4439
+ const rel = path14.relative(cwd, siteDir) || ".";
4751
4440
  if (siteDir === cwd) await writeGitignore(cwd, log);
4752
4441
  const files = await collectFiles(siteDir);
4753
4442
  if (files.length === 0) {
@@ -4803,28 +4492,10 @@ async function deployGame(ctx, opts, log) {
4803
4492
  log.error("Couldn't upload your game \u2014 please try again.");
4804
4493
  return false;
4805
4494
  }
4806
- const pushed = await pushSource(
4807
- cwd,
4808
- ctx,
4809
- log,
4810
- opts.channel === "staging" ? "preview" : "main",
4811
- expectStagingCommit
4812
- );
4813
- if (pushed !== true) return false;
4495
+ if (!await pushSource(cwd, ctx, log, opts.channel === "staging" ? "preview" : "main")) return false;
4814
4496
  log.step("Publishing\u2026");
4815
4497
  const published = await callPublish(ctx, commit, opts, log);
4816
4498
  if (!published) return false;
4817
- if (opts.channel === "staging") {
4818
- const tree = await sourceTreeHash(cwd);
4819
- const current = await readProject(cwd);
4820
- if (current) {
4821
- await writeProject(
4822
- { ...current, stagingCommit: commit, ...tree ? { sourceTree: tree } : {} },
4823
- cwd
4824
- ).catch(() => {
4825
- });
4826
- }
4827
- }
4828
4499
  const index = files.find((f) => f.relPath === "index.html");
4829
4500
  const liveUrl = published.url || grant.playUrl;
4830
4501
  await waitUntilLive(liveUrl, fingerprintOf(index.bytes.toString("utf8")), opts.liveTimeoutMs ?? 2e4, log);
@@ -4832,7 +4503,7 @@ async function deployGame(ctx, opts, log) {
4832
4503
  }
4833
4504
  async function hasBuildScript(cwd) {
4834
4505
  try {
4835
- const pkg = JSON.parse(await fs17.readFile(path16.join(cwd, "package.json"), "utf8"));
4506
+ const pkg = JSON.parse(await fs15.readFile(path14.join(cwd, "package.json"), "utf8"));
4836
4507
  return Boolean(pkg.scripts?.build);
4837
4508
  } catch {
4838
4509
  return false;
@@ -4841,12 +4512,12 @@ async function hasBuildScript(cwd) {
4841
4512
  async function collectFiles(root) {
4842
4513
  const out = [];
4843
4514
  const walk2 = async (dir, prefix) => {
4844
- for (const e of await fs17.readdir(dir, { withFileTypes: true })) {
4515
+ for (const e of await fs15.readdir(dir, { withFileTypes: true })) {
4845
4516
  const relPath = prefix ? `${prefix}/${e.name}` : e.name;
4846
4517
  if (e.isDirectory()) {
4847
- if (!EXCLUDE_DIRS.has(e.name)) await walk2(path16.join(dir, e.name), relPath);
4518
+ if (!EXCLUDE_DIRS.has(e.name)) await walk2(path14.join(dir, e.name), relPath);
4848
4519
  } else if (e.isFile() && !isSecretEnvFile(e.name)) {
4849
- out.push({ relPath, bytes: await fs17.readFile(path16.join(dir, e.name)) });
4520
+ out.push({ relPath, bytes: await fs15.readFile(path14.join(dir, e.name)) });
4850
4521
  }
4851
4522
  }
4852
4523
  };
@@ -5063,27 +4734,28 @@ async function callPublish(ctx, commit, opts, log) {
5063
4734
  }
5064
4735
  return { url: body?.url ?? "" };
5065
4736
  }
5066
- async function pushSource(cwd, ctx, log, branch = "main", expectStagingCommit) {
5067
- const target = await fetchPushUrl(ctx, log, expectStagingCommit);
5068
- if (target === "stale") return "stale";
4737
+ async function pushSource(cwd, ctx, log, branch = "main") {
4738
+ const target = await fetchPushUrl(ctx, log);
5069
4739
  if (!target) return false;
5070
4740
  const ref = target.managed ? branch : "main";
5071
4741
  if (await pushWorktree(cwd, target.pushUrl, target.managed, log, ref)) return true;
5072
4742
  if (!target.managed) return false;
5073
4743
  log.info("Retrying the source push\u2026");
5074
4744
  await new Promise((r) => setTimeout(r, 2e3));
5075
- const fresh = await fetchPushUrl(ctx, log, expectStagingCommit);
5076
- if (fresh === "stale") return "stale";
4745
+ const fresh = await fetchPushUrl(ctx, log);
5077
4746
  if (!fresh) return false;
5078
4747
  return pushWorktree(cwd, fresh.pushUrl, fresh.managed, log, ref);
5079
4748
  }
4749
+ function urlHasEmbeddedCredentials(pushUrl) {
4750
+ return /^[a-z][a-z0-9+.-]*:\/\/[^/@]+@/i.test(pushUrl);
4751
+ }
5080
4752
  async function pushWorktree(cwd, pushUrl, managed, log, branch = "main") {
5081
4753
  const EMPTY_TREE = "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
5082
4754
  const failed = () => {
5083
4755
  log.error("Couldn't save your game's source \u2014 please try again.");
5084
4756
  return false;
5085
4757
  };
5086
- const gitDir = await fs17.mkdtemp(path16.join(os9.tmpdir(), "genex-source-"));
4758
+ const gitDir = await fs15.mkdtemp(path14.join(os6.tmpdir(), "genex-source-"));
5087
4759
  const base = { GIT_DIR: gitDir };
5088
4760
  if (urlHasEmbeddedCredentials(pushUrl)) {
5089
4761
  base.GIT_CONFIG_COUNT = "1";
@@ -5098,12 +4770,12 @@ async function pushWorktree(cwd, pushUrl, managed, log, branch = "main") {
5098
4770
  };
5099
4771
  try {
5100
4772
  if ((await run("git", ["init", "-q"], base)).code !== 0) return failed();
5101
- await fs17.writeFile(
5102
- path16.join(gitDir, "info", "exclude"),
4773
+ await fs15.writeFile(
4774
+ path14.join(gitDir, "info", "exclude"),
5103
4775
  // .env* are secrets — never publish them; `!` keeps the non-secret template.
5104
4776
  ["node_modules/", "dist/", ".git/", ".genex/", ".env", ".env.*", "!.env.example", ""].join("\n")
5105
4777
  );
5106
- const env = { ...base, GIT_WORK_TREE: cwd, GIT_INDEX_FILE: path16.join(gitDir, "index-source") };
4778
+ const env = { ...base, GIT_WORK_TREE: cwd, GIT_INDEX_FILE: path14.join(gitDir, "index-source") };
5107
4779
  let lfs = (await run("git", ["lfs", "version"], base)).code !== 0 ? false : true;
5108
4780
  if (!lfs) {
5109
4781
  log.step("Installing git-lfs (keeps large binary assets out of the source push)\u2026");
@@ -5154,19 +4826,16 @@ async function pushWorktree(cwd, pushUrl, managed, log, branch = "main") {
5154
4826
  } catch {
5155
4827
  return failed();
5156
4828
  } finally {
5157
- await fs17.rm(gitDir, { recursive: true, force: true }).catch(() => {
4829
+ await fs15.rm(gitDir, { recursive: true, force: true }).catch(() => {
5158
4830
  });
5159
4831
  }
5160
4832
  }
5161
- async function fetchPushUrl(ctx, log, expectStagingCommit) {
4833
+ async function fetchPushUrl(ctx, log) {
5162
4834
  let res;
5163
4835
  try {
5164
4836
  res = await apiFetch(`${ctx.apiUrl}/api/projects/${ctx.projectId}/push-token`, {
5165
4837
  method: "POST",
5166
- headers: {
5167
- Authorization: `Bearer ${ctx.token}`,
5168
- ...expectStagingCommit ? { "If-Match": expectStagingCommit } : {}
5169
- }
4838
+ headers: { Authorization: `Bearer ${ctx.token}` }
5170
4839
  });
5171
4840
  } catch (err) {
5172
4841
  log.error(`Couldn't reach the API to authorize the source push: ${String(err)}`);
@@ -5176,11 +4845,6 @@ async function fetchPushUrl(ctx, log, expectStagingCommit) {
5176
4845
  log.error("Not authorized \u2014 your token may have expired. Re-run `genex init`.");
5177
4846
  return null;
5178
4847
  }
5179
- if (res.status === 409) {
5180
- const body = await res.json().catch(() => null);
5181
- reportStale(log, ctx.slug ?? "this game", expectStagingCommit, body?.stagingCommit ?? null);
5182
- return "stale";
5183
- }
5184
4848
  if (res.status === 429) {
5185
4849
  log.error(`Rate limited authorizing the source push (HTTP 429).${retryAfterHint(res)}`);
5186
4850
  return null;
@@ -5194,11 +4858,11 @@ async function fetchPushUrl(ctx, log, expectStagingCommit) {
5194
4858
  log.error("The API didn't return a push URL.");
5195
4859
  return null;
5196
4860
  }
5197
- return { pushUrl: data.pushUrl, managed: data.managed !== false, sourceRef: data.sourceRef ?? null };
4861
+ return { pushUrl: data.pushUrl, managed: data.managed !== false };
5198
4862
  }
5199
4863
  async function isDir2(p) {
5200
4864
  try {
5201
- return (await fs17.stat(p)).isDirectory();
4865
+ return (await fs15.stat(p)).isDirectory();
5202
4866
  } catch {
5203
4867
  return false;
5204
4868
  }
@@ -5299,318 +4963,6 @@ async function runMakeRemixable(opts) {
5299
4963
  }
5300
4964
  }
5301
4965
 
5302
- // src/commands/domain.ts
5303
- var SUBCOMMANDS = ["add", "list", "verify", "remove"];
5304
- function statusWord(d) {
5305
- if (d.status === "active") return c.green("live");
5306
- if (d.status === "failed") return c.red("stopped");
5307
- return c.dim("waiting for DNS");
5308
- }
5309
- async function runDomain(opts) {
5310
- const log = createLogger({ quiet: opts.quiet });
5311
- const sub = (opts.name ?? "list").trim();
5312
- if (!SUBCOMMANDS.includes(sub)) {
5313
- log.error(`Unknown subcommand \`${sub}\`. Use: ${SUBCOMMANDS.join(", ")}.`);
5314
- process.exitCode = 1;
5315
- return;
5316
- }
5317
- const needsHost = sub !== "list";
5318
- const hostname = opts.hostname?.trim();
5319
- if (needsHost && !hostname) {
5320
- log.error(`\`genex domain ${sub}\` needs a hostname, e.g. ${c.cyan(`genex domain ${sub} play.yourdomain.com`)}.`);
5321
- process.exitCode = 1;
5322
- return;
5323
- }
5324
- const meta = await readProject();
5325
- if (!meta?.id) {
5326
- log.error("This folder isn't linked to a game.");
5327
- log.dim(` Run ${c.cyan("genex link")} (or ${c.cyan("genex list")} to find the slug) first.`);
5328
- process.exitCode = 1;
5329
- return;
5330
- }
5331
- const apiUrl = getApiUrl(opts.apiUrl ?? meta.apiUrl);
5332
- let token = opts.token ?? await readUserToken(opts.envPath);
5333
- if (!token) {
5334
- if (opts.noAuth) {
5335
- log.error("Not signed in. Re-run without --no-auth to connect.");
5336
- process.exitCode = 1;
5337
- return;
5338
- }
5339
- log.plain("Not signed in \u2014 connecting\u2026");
5340
- try {
5341
- token = await authorize(apiUrl, getAuthUrl(opts.authUrl), {
5342
- log,
5343
- inlineWaitMs: opts.timeoutSec ? opts.timeoutSec * 1e3 : void 0
5344
- });
5345
- } catch (err) {
5346
- if (err instanceof AuthPendingError) {
5347
- printAuthHandoff(log, err);
5348
- log.dim(`Then re-run ${c.cyan(`genex domain ${sub}`)}.`);
5349
- return;
5350
- }
5351
- log.error(`Sign-in didn't complete: ${err instanceof Error ? err.message : String(err)}`);
5352
- process.exitCode = 1;
5353
- return;
5354
- }
5355
- await writeUserToken(token, opts.envPath);
5356
- }
5357
- const base = `${apiUrl}/api/projects/${encodeURIComponent(meta.id)}/domains`;
5358
- const auth = { Authorization: `Bearer ${token}` };
5359
- const call = (url, init2) => apiFetch(url, { ...init2, headers: { ...auth, "Content-Type": "application/json", ...init2?.headers ?? {} } });
5360
- let res;
5361
- try {
5362
- if (sub === "list") res = await call(base);
5363
- else if (sub === "add") res = await call(base, { method: "POST", body: JSON.stringify({ hostname }) });
5364
- else if (sub === "verify")
5365
- res = await call(`${base}/${encodeURIComponent(hostname)}/verify`, { method: "POST" });
5366
- else res = await call(`${base}/${encodeURIComponent(hostname)}`, { method: "DELETE" });
5367
- } catch (err) {
5368
- log.error(`Couldn't reach the API at ${apiUrl}.`);
5369
- log.dim(` ${String(err)}`);
5370
- process.exitCode = 1;
5371
- return;
5372
- }
5373
- if (!res.ok) {
5374
- if (await printedStructuredError(res)) {
5375
- process.exitCode = 1;
5376
- return;
5377
- }
5378
- const body = await res.json().catch(() => null);
5379
- if (res.status === 503) {
5380
- log.error("Custom domains aren't available on this Genex environment yet.");
5381
- } else if (res.status === 404) {
5382
- log.error(sub === "list" || sub === "add" ? "That game wasn't found on this account." : `${hostname} isn't connected to this game.`);
5383
- } else {
5384
- log.error(body?.message ?? body?.error ?? `Request failed (${res.status}).`);
5385
- }
5386
- process.exitCode = 1;
5387
- return;
5388
- }
5389
- const data = await res.json();
5390
- if (opts.json) {
5391
- log.plain(JSON.stringify(data, null, 2));
5392
- return;
5393
- }
5394
- if (sub === "list") {
5395
- const rows = data.domains ?? [];
5396
- if (rows.length === 0) {
5397
- log.plain("No domains connected to this game.");
5398
- log.dim(` ${c.cyan("genex domain add play.yourdomain.com")} to connect one.`);
5399
- return;
5400
- }
5401
- for (const d of rows) log.plain(` ${d.hostname.padEnd(34)} ${statusWord(d)}`);
5402
- return;
5403
- }
5404
- if (sub === "add") {
5405
- if (data.supported === false) {
5406
- log.plain(String(data.message ?? "That domain's DNS host doesn't support one-click setup."));
5407
- const manual = data.manual;
5408
- const records = Array.isArray(manual?.records) ? manual.records : [];
5409
- if (records.length > 0) {
5410
- log.plain("");
5411
- log.plain(" Add these two records at your DNS host:");
5412
- log.plain("");
5413
- for (const r of records) {
5414
- log.plain(` ${c.cyan(String(r.type ?? ""))} ${String(r.name ?? "")}`);
5415
- log.plain(` ${String(r.value ?? "")}`);
5416
- }
5417
- log.plain("");
5418
- if (manual?.apexHint) {
5419
- log.dim(" At a root domain your host may call the first one ALIAS or ANAME.");
5420
- }
5421
- log.dim(` Then run: genex domain verify ${String(data.hostname ?? "")}`);
5422
- log.dim(" DNS can take a few minutes to spread.");
5423
- return;
5424
- }
5425
- log.dim(" Your game stays reachable at its usual address.");
5426
- return;
5427
- }
5428
- const applyUrl = String(data.applyUrl ?? "");
5429
- const providerName = String(data.providerName ?? "your DNS host");
5430
- log.success(`${data.hostname} can be connected through ${providerName}.`);
5431
- log.plain("");
5432
- log.plain(` Approve the DNS change here: ${c.cyan(applyUrl)}`);
5433
- log.plain("");
5434
- log.dim(" One click writes the records. Nothing to copy or paste.");
5435
- if (!applyUrl.startsWith("https://")) {
5436
- log.warn("That DNS host returned a setup link we could not verify \u2014 not opening it.");
5437
- } else if (!opts.noOpen) {
5438
- openBrowser(applyUrl, () => log.dim(" (Couldn't open a browser \u2014 use the link above.)"));
5439
- }
5440
- log.dim(` Then ${c.cyan(`genex domain verify ${String(data.hostname)}`)} once you have approved it.`);
5441
- return;
5442
- }
5443
- if (sub === "verify") {
5444
- const status = String(data.status ?? "");
5445
- if (status === "active") log.success(`${hostname} is live.`);
5446
- else if (status === "failed") log.error(`${hostname} is stopped \u2014 its verification record is missing.`);
5447
- else log.plain(`${hostname} isn't verified yet \u2014 DNS changes can take a few minutes to spread.`);
5448
- return;
5449
- }
5450
- log.success(`${hostname} disconnected.`);
5451
- }
5452
-
5453
- // src/commands/shop.ts
5454
- var SUBS = ["list", "add", "set", "remove", "test"];
5455
- function money(cents) {
5456
- return cents === void 0 ? "" : ` ($${(cents / 100).toFixed(2)})`;
5457
- }
5458
- async function runShop(opts) {
5459
- const log = createLogger({ quiet: opts.quiet });
5460
- const sub = opts.name?.trim() || "list";
5461
- if (!SUBS.includes(sub)) {
5462
- log.error(`Unknown subcommand ${c.cyan(sub)}. Use: ${SUBS.join(", ")}.`);
5463
- process.exitCode = 1;
5464
- return;
5465
- }
5466
- const meta = await readProject();
5467
- if (!meta?.id) {
5468
- log.error("This folder isn't linked to a game.");
5469
- log.dim(` Run ${c.cyan("genex link")} (or ${c.cyan("genex list")} to find the slug) first.`);
5470
- process.exitCode = 1;
5471
- return;
5472
- }
5473
- const apiUrl = getApiUrl(opts.apiUrl ?? meta.apiUrl);
5474
- let token = opts.token ?? await readUserToken(opts.envPath);
5475
- if (!token) {
5476
- if (opts.noAuth) {
5477
- log.error("Not signed in. Re-run without --no-auth to connect.");
5478
- process.exitCode = 1;
5479
- return;
5480
- }
5481
- log.plain("Not signed in \u2014 connecting\u2026");
5482
- try {
5483
- token = await authorize(apiUrl, getAuthUrl(opts.authUrl), {
5484
- log,
5485
- inlineWaitMs: opts.timeoutSec ? opts.timeoutSec * 1e3 : void 0
5486
- });
5487
- } catch (err) {
5488
- if (err instanceof AuthPendingError) {
5489
- printAuthHandoff(log, err);
5490
- log.dim(`Then re-run ${c.cyan(`genex shop ${sub}`)}.`);
5491
- return;
5492
- }
5493
- log.error(`Sign-in didn't complete: ${err instanceof Error ? err.message : String(err)}`);
5494
- process.exitCode = 1;
5495
- return;
5496
- }
5497
- await writeUserToken(token, opts.envPath);
5498
- }
5499
- const auth = { Authorization: `Bearer ${token}`, "Content-Type": "application/json" };
5500
- const call = (url, init2) => apiFetch(url, { ...init2, headers: { ...auth, ...init2?.headers ?? {} } });
5501
- const projectSkus = `${apiUrl}/api/coin/projects/${encodeURIComponent(meta.id)}/skus`;
5502
- async function fail3(res2, what) {
5503
- if (printedStructuredError(res2)) {
5504
- process.exitCode = 1;
5505
- return;
5506
- }
5507
- process.exitCode = 1;
5508
- const body = await res2.json().catch(() => ({}));
5509
- if (res2.status === 404 && body.error === "not_found") {
5510
- log.error("In-game purchases aren't enabled on this environment yet.");
5511
- return;
5512
- }
5513
- log.error(body.message ?? `Couldn't ${what} (HTTP ${res2.status}).`);
5514
- if (body.error === "price_off_grid") {
5515
- log.dim(` See the full list any time: ${c.cyan("genex shop list")}`);
5516
- }
5517
- }
5518
- if (sub === "list") {
5519
- const res2 = await call(projectSkus);
5520
- if (!res2.ok) return fail3(res2, "read your shop");
5521
- const body = await res2.json();
5522
- if (opts.json) {
5523
- log.plain(JSON.stringify(body, null, 2));
5524
- return;
5525
- }
5526
- if (body.items.length === 0) {
5527
- log.plain("This game sells nothing yet.");
5528
- log.dim(` Add something: ${c.cyan('genex shop add "Iron Key" --price 100')}`);
5529
- log.dim(` Prices available: ${body.priceGrid.join(", ")} coin`);
5530
- return;
5531
- }
5532
- log.plain(`${body.items.length} item${body.items.length === 1 ? "" : "s"}:`);
5533
- for (const s of body.items) {
5534
- const state = s.active ? "" : c.dim(" (retired)");
5535
- log.plain(
5536
- ` ${c.cyan(s.id)} ${s.name} \u2014 ${s.priceCoins} coin${money(s.priceDisplayUsdCents)} ${c.dim(s.type)}${state}`
5537
- );
5538
- }
5539
- log.dim(`Prices available: ${body.priceGrid.join(", ")} coin`);
5540
- return;
5541
- }
5542
- if (sub === "add") {
5543
- const name = opts.hostname?.trim();
5544
- if (!name) {
5545
- log.error(`\`genex shop add\` needs a name, e.g. ${c.cyan('genex shop add "Iron Key" --price 100')}.`);
5546
- process.exitCode = 1;
5547
- return;
5548
- }
5549
- if (!opts.price) {
5550
- log.error(`\`genex shop add\` needs ${c.cyan("--price")}, in coin.`);
5551
- process.exitCode = 1;
5552
- return;
5553
- }
5554
- const res2 = await call(projectSkus, {
5555
- method: "POST",
5556
- body: JSON.stringify({
5557
- name,
5558
- priceCoins: opts.price,
5559
- type: opts.type === "durable" ? "durable" : "consumable",
5560
- ...opts.icon ? { iconUrl: opts.icon } : {}
5561
- })
5562
- });
5563
- if (!res2.ok) return fail3(res2, "add that item");
5564
- const sku = await res2.json();
5565
- if (opts.json) {
5566
- log.plain(JSON.stringify(sku, null, 2));
5567
- return;
5568
- }
5569
- log.success(`Added ${c.cyan(sku.name)} \u2014 ${sku.priceCoins} coin.`);
5570
- log.plain(` id: ${c.cyan(sku.id)}`);
5571
- log.dim(` Use it in the game: buy({ skuId: "${sku.id}" })`);
5572
- return;
5573
- }
5574
- const skuId = opts.hostname?.trim();
5575
- if (!skuId) {
5576
- log.error(`\`genex shop ${sub}\` needs an item id \u2014 see ${c.cyan("genex shop list")}.`);
5577
- process.exitCode = 1;
5578
- return;
5579
- }
5580
- const skuUrl = `${apiUrl}/api/coin/skus/${encodeURIComponent(skuId)}`;
5581
- if (sub === "test") {
5582
- const res2 = await call(`${skuUrl}/test-grant`, { method: "POST" });
5583
- if (!res2.ok) return fail3(res2, "grant yourself a test copy");
5584
- const got = await res2.json();
5585
- log.success(`You now own ${c.cyan(got.name)} in this game.`);
5586
- log.dim(" It cost nothing and no sale was recorded \u2014 it is a test copy.");
5587
- log.dim(
5588
- got.skuType === "durable" ? " Reload the game: it should appear in your inventory." : " Reload the game: it should be delivered and consumed once."
5589
- );
5590
- return;
5591
- }
5592
- if (sub === "set") {
5593
- const patch = {};
5594
- if (opts.rename) patch.name = opts.rename;
5595
- if (opts.price) patch.priceCoins = opts.price;
5596
- if (opts.icon) patch.iconUrl = opts.icon;
5597
- if (Object.keys(patch).length === 0) {
5598
- log.error(`Nothing to change. Pass ${c.cyan("--rename")}, ${c.cyan("--price")} or ${c.cyan("--icon")}.`);
5599
- process.exitCode = 1;
5600
- return;
5601
- }
5602
- const res2 = await call(skuUrl, { method: "PATCH", body: JSON.stringify(patch) });
5603
- if (!res2.ok) return fail3(res2, "update that item");
5604
- const sku = await res2.json();
5605
- log.success(`Updated ${c.cyan(sku.name)} \u2014 ${sku.priceCoins} coin.`);
5606
- return;
5607
- }
5608
- const res = await call(skuUrl, { method: "DELETE" });
5609
- if (!res.ok) return fail3(res, "remove that item");
5610
- log.success("Removed from the shop.");
5611
- log.dim(" Players who already bought it keep it.");
5612
- }
5613
-
5614
4966
  // src/lib/promote.ts
5615
4967
  async function promoteBuild(apiUrl, projectId, token, log) {
5616
4968
  let res;
@@ -5645,17 +4997,17 @@ async function promoteBuild(apiUrl, projectId, token, log) {
5645
4997
  }
5646
4998
 
5647
4999
  // src/lib/detect-features.ts
5648
- import fs19 from "fs/promises";
5649
- import path18 from "path";
5000
+ import fs17 from "fs/promises";
5001
+ import path16 from "path";
5650
5002
 
5651
5003
  // src/lib/generation-ledger.ts
5652
- import fs18 from "fs/promises";
5653
- import path17 from "path";
5654
- var ledgerPath = (cwd) => path17.join(cwd, ".genex", "generations.ndjson");
5004
+ import fs16 from "fs/promises";
5005
+ import path15 from "path";
5006
+ var ledgerPath = (cwd) => path15.join(cwd, ".genex", "generations.ndjson");
5655
5007
  async function append(cwd, event) {
5656
5008
  try {
5657
- await fs18.access(path17.join(cwd, ".genex"));
5658
- await fs18.appendFile(ledgerPath(cwd), `${JSON.stringify(event)}
5009
+ await fs16.access(path15.join(cwd, ".genex"));
5010
+ await fs16.appendFile(ledgerPath(cwd), `${JSON.stringify(event)}
5659
5011
  `, "utf8");
5660
5012
  } catch {
5661
5013
  }
@@ -5663,7 +5015,7 @@ async function append(cwd, event) {
5663
5015
  async function readLedger(cwd = process.cwd()) {
5664
5016
  let raw;
5665
5017
  try {
5666
- raw = await fs18.readFile(ledgerPath(cwd), "utf8");
5018
+ raw = await fs16.readFile(ledgerPath(cwd), "utf8");
5667
5019
  } catch {
5668
5020
  return [];
5669
5021
  }
@@ -5717,7 +5069,7 @@ async function countFailed(kind, cwd = process.cwd()) {
5717
5069
  // src/lib/detect-features.ts
5718
5070
  async function detectEmbedSdkVersion(cwd = process.cwd()) {
5719
5071
  try {
5720
- const raw = await fs19.readFile(path18.join(cwd, "package.json"), "utf8");
5072
+ const raw = await fs17.readFile(path16.join(cwd, "package.json"), "utf8");
5721
5073
  const pkg = JSON.parse(raw);
5722
5074
  const version = pkg.dependencies?.["@genex-ai/embed-sdk"] ?? pkg.devDependencies?.["@genex-ai/embed-sdk"];
5723
5075
  return typeof version === "string" && version ? version : null;
@@ -5727,7 +5079,7 @@ async function detectEmbedSdkVersion(cwd = process.cwd()) {
5727
5079
  }
5728
5080
  async function detectMultiplayer(cwd = process.cwd()) {
5729
5081
  try {
5730
- const raw = await fs19.readFile(path18.join(cwd, "package.json"), "utf8");
5082
+ const raw = await fs17.readFile(path16.join(cwd, "package.json"), "utf8");
5731
5083
  const pkg = JSON.parse(raw);
5732
5084
  return Boolean(
5733
5085
  pkg.dependencies?.["@genex-ai/multiplayer"] ?? pkg.devDependencies?.["@genex-ai/multiplayer"]
@@ -5739,7 +5091,7 @@ async function detectMultiplayer(cwd = process.cwd()) {
5739
5091
  async function detectMatchmaking(log, cwd = process.cwd()) {
5740
5092
  let pkg;
5741
5093
  try {
5742
- pkg = JSON.parse(await fs19.readFile(path18.join(cwd, "package.json"), "utf8"));
5094
+ pkg = JSON.parse(await fs17.readFile(path16.join(cwd, "package.json"), "utf8"));
5743
5095
  } catch (err) {
5744
5096
  log.dim(` (skipping matchmaking \u2014 couldn't read package.json: ${String(err)})`);
5745
5097
  return null;
@@ -5757,15 +5109,15 @@ async function detectMatchmaking(log, cwd = process.cwd()) {
5757
5109
  var TOUCH_KIT_MARKERS = /controllers\/touch\/|controllers\/character\/touch-joystick|TouchJoystick|VirtualButton|DragZone|RotateOverlay/;
5758
5110
  async function detectMobileControls(cwd = process.cwd()) {
5759
5111
  try {
5760
- const raw = await fs19.readFile(path18.join(cwd, "package.json"), "utf8");
5112
+ const raw = await fs17.readFile(path16.join(cwd, "package.json"), "utf8");
5761
5113
  const pkg = JSON.parse(raw);
5762
5114
  if (pkg.genex?.mobileControls === true) return true;
5763
5115
  } catch {
5764
5116
  }
5765
- const srcDir = path18.join(cwd, "src");
5117
+ const srcDir = path16.join(cwd, "src");
5766
5118
  let entries;
5767
5119
  try {
5768
- entries = await fs19.readdir(srcDir, { recursive: true });
5120
+ entries = await fs17.readdir(srcDir, { recursive: true });
5769
5121
  } catch {
5770
5122
  return false;
5771
5123
  }
@@ -5773,7 +5125,7 @@ async function detectMobileControls(cwd = process.cwd()) {
5773
5125
  if (rel.includes("node_modules")) continue;
5774
5126
  if (!/\.(ts|js|mts|mjs|tsx|jsx)$/.test(rel)) continue;
5775
5127
  try {
5776
- const content = await fs19.readFile(path18.join(srcDir, rel), "utf8");
5128
+ const content = await fs17.readFile(path16.join(srcDir, rel), "utf8");
5777
5129
  if (TOUCH_KIT_MARKERS.test(content)) return true;
5778
5130
  } catch {
5779
5131
  }
@@ -5782,10 +5134,10 @@ async function detectMobileControls(cwd = process.cwd()) {
5782
5134
  }
5783
5135
  var GAME_STATE_CALLS = /savePlayerState|saveWorldState|submitScore/;
5784
5136
  async function detectGameStateUsage(cwd = process.cwd()) {
5785
- const srcDir = path18.join(cwd, "src");
5137
+ const srcDir = path16.join(cwd, "src");
5786
5138
  let entries;
5787
5139
  try {
5788
- entries = await fs19.readdir(srcDir, { recursive: true });
5140
+ entries = await fs17.readdir(srcDir, { recursive: true });
5789
5141
  } catch {
5790
5142
  return false;
5791
5143
  }
@@ -5793,7 +5145,7 @@ async function detectGameStateUsage(cwd = process.cwd()) {
5793
5145
  if (rel.includes("node_modules")) continue;
5794
5146
  if (!/\.(ts|js|mts|mjs|tsx|jsx)$/.test(rel)) continue;
5795
5147
  try {
5796
- const content = await fs19.readFile(path18.join(srcDir, rel), "utf8");
5148
+ const content = await fs17.readFile(path16.join(srcDir, rel), "utf8");
5797
5149
  if (GAME_STATE_CALLS.test(content)) return true;
5798
5150
  } catch {
5799
5151
  }
@@ -5842,19 +5194,19 @@ async function detectSurfaceScan(cwd = process.cwd()) {
5842
5194
  depthRange: [],
5843
5195
  mediaElementAudio: []
5844
5196
  };
5845
- const srcDir = path18.join(cwd, "src");
5197
+ const srcDir = path16.join(cwd, "src");
5846
5198
  let entries;
5847
5199
  try {
5848
- entries = await fs19.readdir(srcDir, { recursive: true });
5200
+ entries = await fs17.readdir(srcDir, { recursive: true });
5849
5201
  } catch {
5850
5202
  return found;
5851
5203
  }
5852
5204
  for (const nativeRel of entries) {
5853
5205
  if (nativeRel.includes("node_modules")) continue;
5854
5206
  if (!/\.(ts|js|mts|mjs|tsx|jsx)$/.test(nativeRel)) continue;
5855
- const raw = await fs19.readFile(path18.join(srcDir, nativeRel), "utf8").catch(() => "");
5207
+ const raw = await fs17.readFile(path16.join(srcDir, nativeRel), "utf8").catch(() => "");
5856
5208
  if (!raw) continue;
5857
- const rel = nativeRel.split(path18.sep).join("/");
5209
+ const rel = nativeRel.split(path16.sep).join("/");
5858
5210
  const content = raw.replace(/\/\*[\s\S]*?\*\//g, (m2) => m2.replace(/[^\n]/g, " ")).replace(/(^|[^:])\/\/[^\n]*/gm, (m2, p1) => p1 + " ".repeat(m2.length - p1.length));
5859
5211
  const repeatRe = /\.repeat\.set\(\s*(-?\d+(?:\.\d+)?)\s*,\s*(-?\d+(?:\.\d+)?)\s*\)/g;
5860
5212
  let m;
@@ -5912,25 +5264,25 @@ async function detectGenerationAudit(cwd = process.cwd()) {
5912
5264
  let haystack = "";
5913
5265
  const read = async (file) => {
5914
5266
  try {
5915
- haystack += await fs19.readFile(file, "utf8");
5267
+ haystack += await fs17.readFile(file, "utf8");
5916
5268
  } catch {
5917
5269
  }
5918
5270
  };
5919
5271
  try {
5920
- for (const entry of await fs19.readdir(cwd, { withFileTypes: true })) {
5272
+ for (const entry of await fs17.readdir(cwd, { withFileTypes: true })) {
5921
5273
  if (entry.isFile() && /\.(ts|tsx|js|jsx|css|html|json)$/.test(entry.name)) {
5922
- await read(path18.join(cwd, entry.name));
5274
+ await read(path16.join(cwd, entry.name));
5923
5275
  }
5924
5276
  }
5925
5277
  } catch {
5926
5278
  }
5927
5279
  for (const sub of ["src", "public"]) {
5928
5280
  try {
5929
- const entries = await fs19.readdir(path18.join(cwd, sub), { recursive: true });
5281
+ const entries = await fs17.readdir(path16.join(cwd, sub), { recursive: true });
5930
5282
  for (const rel of entries) {
5931
5283
  if (rel.includes("node_modules")) continue;
5932
5284
  if (!/\.(ts|tsx|js|jsx|css|html|json|txt)$/.test(rel)) continue;
5933
- await read(path18.join(cwd, sub, rel));
5285
+ await read(path16.join(cwd, sub, rel));
5934
5286
  }
5935
5287
  } catch {
5936
5288
  }
@@ -6062,7 +5414,7 @@ async function borrowEvidence(meta, cwd) {
6062
5414
  } catch {
6063
5415
  return true;
6064
5416
  }
6065
- const gitConfig = await fs19.readFile(path18.join(cwd, ".git", "config"), "utf8").catch(() => "");
5417
+ const gitConfig = await fs17.readFile(path16.join(cwd, ".git", "config"), "utf8").catch(() => "");
6066
5418
  for (const m of gitConfig.matchAll(/url\s*=\s*(\S+)/g)) {
6067
5419
  try {
6068
5420
  const u = new URL(m[1]);
@@ -6070,7 +5422,7 @@ async function borrowEvidence(meta, cwd) {
6070
5422
  } catch {
6071
5423
  }
6072
5424
  }
6073
- const readme = await fs19.readFile(path18.join(cwd, "README.md"), "utf8").catch(() => "");
5425
+ const readme = await fs17.readFile(path16.join(cwd, "README.md"), "utf8").catch(() => "");
6074
5426
  return readme.includes(host) || /\b(upstream|originally by|ported from|borrowed from)\b/i.test(readme);
6075
5427
  }
6076
5428
 
@@ -6136,7 +5488,7 @@ async function runPublish(opts) {
6136
5488
  advisoryNudges(log, detections);
6137
5489
  if (!opts.noPush) {
6138
5490
  const ok = await deployGame(
6139
- { projectId: meta.id, apiUrl, token, slug: meta.slug },
5491
+ { projectId: meta.id, apiUrl, token },
6140
5492
  {
6141
5493
  // Publish deploys to staging and then promotes, so both channels end up
6142
5494
  // naming the same build. Deploying straight to production instead would
@@ -6144,7 +5496,6 @@ async function runPublish(opts) {
6144
5496
  // staging — the owner would publish and then see a stale game.
6145
5497
  channel: "staging",
6146
5498
  noBuild: opts.noBuild,
6147
- force: opts.force,
6148
5499
  matchmaking: detections.matchmaking,
6149
5500
  embedSdkVersion: detections.embedSdkVersion,
6150
5501
  multiplayer: detections.multiplayer,
@@ -6205,7 +5556,7 @@ async function runPublish(opts) {
6205
5556
  log.plain("");
6206
5557
  log.success("Published. \u{1F389}");
6207
5558
  const dashboard = meta.dashboardOrigins?.[0];
6208
- if (dashboard) log.plain(` game page (share this link): ${c.cyan(`${dashboard}/${meta.slug}`)}`);
5559
+ if (dashboard) log.plain(` world page (share this link): ${c.cyan(`${dashboard}/world/${meta.slug}`)}`);
6209
5560
  if (meta.playUrl) log.dim(` play: ${meta.playUrl}`);
6210
5561
  await verifyGamePage({
6211
5562
  apiUrl,
@@ -6238,7 +5589,7 @@ async function runPreview(opts) {
6238
5589
  await exploreReuseNudge(log, meta);
6239
5590
  const apiUrl = getApiUrl(meta.apiUrl);
6240
5591
  const ok = await deployGame(
6241
- { projectId: meta.id, apiUrl, token, slug: meta.slug },
5592
+ { projectId: meta.id, apiUrl, token },
6242
5593
  // Detections reach the server on every preview too: matchmaking so a draft's
6243
5594
  // declared preset doesn't silently run the default (null clears a removed
6244
5595
  // config), embedSdkVersion/multiplayer so the dashboard's Publish button can
@@ -6249,7 +5600,6 @@ async function runPreview(opts) {
6249
5600
  // was inside, and the only protection was a warning line below.
6250
5601
  channel: "staging",
6251
5602
  noBuild: opts.noBuild,
6252
- force: opts.force,
6253
5603
  matchmaking: detections.matchmaking,
6254
5604
  embedSdkVersion: detections.embedSdkVersion,
6255
5605
  multiplayer: detections.multiplayer,
@@ -6322,8 +5672,8 @@ async function runPromote(opts) {
6322
5672
  }
6323
5673
 
6324
5674
  // src/commands/generate.ts
6325
- import fs20 from "fs/promises";
6326
- import path19 from "path";
5675
+ import fs18 from "fs/promises";
5676
+ import path17 from "path";
6327
5677
  import { PNG as PNG4 } from "pngjs";
6328
5678
 
6329
5679
  // src/lib/glass.ts
@@ -6404,7 +5754,7 @@ function solveMagentaGlass(source) {
6404
5754
  }
6405
5755
 
6406
5756
  // src/lib/open.ts
6407
- import { spawn as spawn5 } from "child_process";
5757
+ import { spawn as spawn4 } from "child_process";
6408
5758
  function tokenize(cmd) {
6409
5759
  return cmd.trim().split(/\s+/).filter(Boolean);
6410
5760
  }
@@ -6433,7 +5783,7 @@ function openUrl(url) {
6433
5783
  }
6434
5784
  }
6435
5785
  try {
6436
- const child = spawn5(command, args, { stdio: "ignore", detached: true });
5786
+ const child = spawn4(command, args, { stdio: "ignore", detached: true });
6437
5787
  child.on("error", () => {
6438
5788
  });
6439
5789
  child.unref();
@@ -6493,7 +5843,7 @@ var isRemoteRef = (value) => /^(https?:\/\/|data:)/i.test(value);
6493
5843
  async function inlineLocalImage(filePath, flag) {
6494
5844
  let bytes;
6495
5845
  try {
6496
- bytes = await fs20.readFile(filePath);
5846
+ bytes = await fs18.readFile(filePath);
6497
5847
  } catch {
6498
5848
  return { ok: false, error: `Couldn't read the ${flag} file at ${filePath}.` };
6499
5849
  }
@@ -6503,7 +5853,7 @@ async function inlineLocalImage(filePath, flag) {
6503
5853
  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.`
6504
5854
  };
6505
5855
  }
6506
- const mime = IMAGE_MIME_BY_EXT[path19.extname(filePath).toLowerCase()] ?? "image/png";
5856
+ const mime = IMAGE_MIME_BY_EXT[path17.extname(filePath).toLowerCase()] ?? "image/png";
6507
5857
  return { ok: true, dataUri: `data:${mime};base64,${bytes.toString("base64")}` };
6508
5858
  }
6509
5859
  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.";
@@ -6699,7 +6049,7 @@ async function runGenerate(kind, opts) {
6699
6049
  return;
6700
6050
  }
6701
6051
  try {
6702
- const bytes = await fs20.readFile(opts.inpaintUrl);
6052
+ const bytes = await fs18.readFile(opts.inpaintUrl);
6703
6053
  opts = { ...opts, inpaintUrl: `data:image/png;base64,${bytes.toString("base64")}` };
6704
6054
  } catch {
6705
6055
  log.error(`Couldn't read the --inpaint mask at ${opts.inpaintUrl}.`);
@@ -6823,7 +6173,7 @@ async function reportGlassTerminal(view, outDir, log, json) {
6823
6173
  return;
6824
6174
  }
6825
6175
  await recordTerminal(view.id, "completed", files.map((f) => f.url));
6826
- await fs20.mkdir(outDir, { recursive: true });
6176
+ await fs18.mkdir(outDir, { recursive: true });
6827
6177
  const solved = [];
6828
6178
  for (let i = 0; i < files.length; i++) {
6829
6179
  const f = files[i];
@@ -6855,8 +6205,8 @@ async function reportGlassTerminal(view, outDir, log, json) {
6855
6205
  });
6856
6206
  continue;
6857
6207
  }
6858
- const outPath = path19.join(outDir, `glass-${i + 1}.png`);
6859
- await fs20.writeFile(outPath, PNG4.sync.write(r.png));
6208
+ const outPath = path17.join(outDir, `glass-${i + 1}.png`);
6209
+ await fs18.writeFile(outPath, PNG4.sync.write(r.png));
6860
6210
  solved.push({
6861
6211
  path: outPath,
6862
6212
  url: f.url,
@@ -7453,8 +6803,8 @@ async function toRow(e, v, cwd) {
7453
6803
  }
7454
6804
 
7455
6805
  // src/commands/controller.ts
7456
- import fs22 from "fs/promises";
7457
- import path21 from "path";
6806
+ import fs20 from "fs/promises";
6807
+ import path19 from "path";
7458
6808
 
7459
6809
  // ../../packages/meshy-animation-catalog/src/index.ts
7460
6810
  import { createHash } from "crypto";
@@ -16584,9 +15934,9 @@ function searchMeshyAnimations(query, options = {}) {
16584
15934
  }
16585
15935
 
16586
15936
  // src/lib/anims.ts
16587
- import fs21 from "fs/promises";
16588
- import path20 from "path";
16589
- var ANIMS_DEST = path20.join("public", "assets", "anims");
15937
+ import fs19 from "fs/promises";
15938
+ import path18 from "path";
15939
+ var ANIMS_DEST = path18.join("public", "assets", "anims");
16590
15940
  var HIDDEN_TAG = "reference";
16591
15941
  async function runAnims(opts) {
16592
15942
  const log = createLogger({ quiet: opts.quiet });
@@ -16602,7 +15952,7 @@ async function runAnims(opts) {
16602
15952
  printCatalog(log, manifest, selectors);
16603
15953
  return;
16604
15954
  }
16605
- const controllerMarker = path20.join(root, "src", "controllers", "character");
15955
+ const controllerMarker = path18.join(root, "src", "controllers", "character");
16606
15956
  if (!await exists2(controllerMarker)) {
16607
15957
  log.error(
16608
15958
  `No character controller in this game (missing ${c.cyan("src/controllers/character/")}).`
@@ -16611,11 +15961,11 @@ async function runAnims(opts) {
16611
15961
  process.exitCode = 1;
16612
15962
  return;
16613
15963
  }
16614
- const destDir = path20.join(root, ANIMS_DEST);
16615
- const gameManifestPath = path20.join(destDir, "manifest.json");
15964
+ const destDir = path18.join(root, ANIMS_DEST);
15965
+ const gameManifestPath = path18.join(destDir, "manifest.json");
16616
15966
  if (opts.reset) {
16617
- await fs21.rm(destDir, { recursive: true, force: true });
16618
- log.step(`Cleared ${c.cyan(ANIMS_DEST + path20.sep)} (--reset)`);
15967
+ await fs19.rm(destDir, { recursive: true, force: true });
15968
+ log.step(`Cleared ${c.cyan(ANIMS_DEST + path18.sep)} (--reset)`);
16619
15969
  }
16620
15970
  if (selectors.length === 0) {
16621
15971
  const installed = await readGameManifest(gameManifestPath);
@@ -16653,35 +16003,35 @@ async function runAnims(opts) {
16653
16003
  }
16654
16004
  }
16655
16005
  const wanted = [...byName.values()].sort((a, b) => a.name.localeCompare(b.name));
16656
- const cacheDir = path20.join(
16006
+ const cacheDir = path18.join(
16657
16007
  opts.cacheDir ?? getAnimsCacheDir(),
16658
16008
  `${manifest.library}-v${manifest.version}`
16659
16009
  );
16660
- await fs21.mkdir(cacheDir, { recursive: true });
16661
- await fs21.mkdir(destDir, { recursive: true });
16010
+ await fs19.mkdir(cacheDir, { recursive: true });
16011
+ await fs19.mkdir(destDir, { recursive: true });
16662
16012
  const base = getAnimsBase(opts.animsBase);
16663
16013
  let installedCount = 0;
16664
16014
  let presentCount = 0;
16665
16015
  let addedBytes = 0;
16666
16016
  const failures = [];
16667
16017
  for (const entry of wanted) {
16668
- const dest = path20.join(destDir, entry.file);
16018
+ const dest = path18.join(destDir, entry.file);
16669
16019
  if (await hasSize(dest, entry.bytes)) {
16670
16020
  presentCount++;
16671
16021
  continue;
16672
16022
  }
16673
16023
  try {
16674
- const cached = path20.join(cacheDir, entry.file);
16024
+ const cached = path18.join(cacheDir, entry.file);
16675
16025
  if (!await hasSize(cached, entry.bytes)) {
16676
16026
  const res = await fetch(base + entry.file);
16677
16027
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
16678
16028
  const buf = Buffer.from(await res.arrayBuffer());
16679
- await fs21.writeFile(cached, buf);
16029
+ await fs19.writeFile(cached, buf);
16680
16030
  }
16681
- await fs21.copyFile(cached, dest);
16031
+ await fs19.copyFile(cached, dest);
16682
16032
  installedCount++;
16683
16033
  addedBytes += entry.bytes;
16684
- log.dim(` ${path20.join(ANIMS_DEST, entry.file)} (${formatMb(entry.bytes)})`);
16034
+ log.dim(` ${path18.join(ANIMS_DEST, entry.file)} (${formatMb(entry.bytes)})`);
16685
16035
  } catch (err) {
16686
16036
  failures.push(`${entry.name} (${err instanceof Error ? err.message : String(err)})`);
16687
16037
  }
@@ -16697,13 +16047,13 @@ async function runAnims(opts) {
16697
16047
  version: manifest.version,
16698
16048
  clips: [...union].sort((a, b) => a.localeCompare(b))
16699
16049
  };
16700
- await fs21.writeFile(gameManifestPath, JSON.stringify(gameManifest, null, 2) + "\n");
16050
+ await fs19.writeFile(gameManifestPath, JSON.stringify(gameManifest, null, 2) + "\n");
16701
16051
  log.plain("");
16702
16052
  const parts = [`${installedCount} clip${installedCount === 1 ? "" : "s"} installed`];
16703
16053
  if (presentCount > 0) parts.push(`${presentCount} already present`);
16704
16054
  if (bundledSkips > 0) parts.push(`${bundledSkips} already bundled in animation-library.glb`);
16705
16055
  log.success(
16706
- `${parts.join(", ")} \u2192 ${c.cyan(ANIMS_DEST + path20.sep)}${addedBytes > 0 ? ` (+${formatMb(addedBytes)})` : ""}`
16056
+ `${parts.join(", ")} \u2192 ${c.cyan(ANIMS_DEST + path18.sep)}${addedBytes > 0 ? ` (+${formatMb(addedBytes)})` : ""}`
16707
16057
  );
16708
16058
  for (const [selector, entries] of resolved) {
16709
16059
  const names = entries.filter((e) => !coreNames.has(e.name)).map((e) => e.name);
@@ -16735,8 +16085,8 @@ async function loadManifest(baseOverride) {
16735
16085
  }
16736
16086
  } catch {
16737
16087
  }
16738
- const snapshotPath = path20.join(getTemplatesDir(), "controllers", "assets", "anims-manifest.json");
16739
- const manifest = JSON.parse(await fs21.readFile(snapshotPath, "utf8"));
16088
+ const snapshotPath = path18.join(getTemplatesDir(), "controllers", "assets", "anims-manifest.json");
16089
+ const manifest = JSON.parse(await fs19.readFile(snapshotPath, "utf8"));
16740
16090
  return { manifest, source: "snapshot" };
16741
16091
  }
16742
16092
  function resolveSelectors(manifest, selectors) {
@@ -16854,21 +16204,21 @@ function printCatalog(log, manifest, selectors) {
16854
16204
  }
16855
16205
  async function readGameManifest(file) {
16856
16206
  try {
16857
- return JSON.parse(await fs21.readFile(file, "utf8"));
16207
+ return JSON.parse(await fs19.readFile(file, "utf8"));
16858
16208
  } catch {
16859
16209
  return null;
16860
16210
  }
16861
16211
  }
16862
16212
  async function hasSize(file, bytes) {
16863
16213
  try {
16864
- return (await fs21.stat(file)).size === bytes;
16214
+ return (await fs19.stat(file)).size === bytes;
16865
16215
  } catch {
16866
16216
  return false;
16867
16217
  }
16868
16218
  }
16869
16219
  async function exists2(p) {
16870
16220
  try {
16871
- await fs21.access(p);
16221
+ await fs19.access(p);
16872
16222
  return true;
16873
16223
  } catch {
16874
16224
  return false;
@@ -17062,8 +16412,8 @@ var CONTROLLER_FILE_SETS = {
17062
16412
  ]
17063
16413
  }
17064
16414
  };
17065
- var CODE_DEST = path21.join("src", "controllers");
17066
- var ASSETS_DEST = path21.join("public", "assets");
16415
+ var CODE_DEST = path19.join("src", "controllers");
16416
+ var ASSETS_DEST = path19.join("public", "assets");
17067
16417
  async function runController(opts) {
17068
16418
  const log = createLogger({ quiet: opts.quiet });
17069
16419
  if (opts.kind?.trim() === "anims") {
@@ -17080,31 +16430,31 @@ async function runController(opts) {
17080
16430
  process.exitCode = 1;
17081
16431
  return;
17082
16432
  }
17083
- const srcDir = path21.join(getTemplatesDir(), "controllers");
16433
+ const srcDir = path19.join(getTemplatesDir(), "controllers");
17084
16434
  const root = opts.cwd ?? process.cwd();
17085
16435
  const set = CONTROLLER_FILE_SETS[kind];
17086
16436
  log.plain(c.bold(`genex controller ${kind}`));
17087
16437
  log.plain("");
17088
- log.step(`Installing the ${kind} controller into ${c.cyan(CODE_DEST + path21.sep)}`);
16438
+ log.step(`Installing the ${kind} controller into ${c.cyan(CODE_DEST + path19.sep)}`);
17089
16439
  const plan = [
17090
- ...set.code.map((rel) => ({ from: rel, rel: path21.join(CODE_DEST, rel) })),
16440
+ ...set.code.map((rel) => ({ from: rel, rel: path19.join(CODE_DEST, rel) })),
17091
16441
  ...set.assets.map((rel) => ({
17092
16442
  from: rel,
17093
- rel: path21.join(ASSETS_DEST, path21.basename(rel))
16443
+ rel: path19.join(ASSETS_DEST, path19.basename(rel))
17094
16444
  }))
17095
16445
  ];
17096
16446
  let copied = 0;
17097
16447
  let skipped = 0;
17098
16448
  try {
17099
16449
  for (const file of plan) {
17100
- const dest = path21.join(root, file.rel);
16450
+ const dest = path19.join(root, file.rel);
17101
16451
  if (!opts.force && await exists3(dest)) {
17102
16452
  skipped++;
17103
16453
  log.dim(` skipped ${file.rel} (exists \u2014 use --force to overwrite)`);
17104
16454
  continue;
17105
16455
  }
17106
- await fs22.mkdir(path21.dirname(dest), { recursive: true });
17107
- await fs22.copyFile(path21.join(srcDir, file.from), dest);
16456
+ await fs20.mkdir(path19.dirname(dest), { recursive: true });
16457
+ await fs20.copyFile(path19.join(srcDir, file.from), dest);
17108
16458
  copied++;
17109
16459
  log.dim(` ${file.rel}`);
17110
16460
  }
@@ -17157,7 +16507,7 @@ async function runController(opts) {
17157
16507
  for (const line of set.sketch) {
17158
16508
  log.dim(` ${line}`);
17159
16509
  }
17160
- if (kind === "character" && !await exists3(path21.join(root, ASSETS_DEST, "meshy-character.json"))) {
16510
+ if (kind === "character" && !await exists3(path19.join(root, ASSETS_DEST, "meshy-character.json"))) {
17161
16511
  log.plain("");
17162
16512
  log.plain(
17163
16513
  ` ${stepOffset + 3}. This game has no generated character yet \u2014 ${c.cyan(
@@ -17189,9 +16539,9 @@ async function installMeshyCharacterManifest(args) {
17189
16539
  throw new Error("The API returned an invalid Meshy character manifest.");
17190
16540
  }
17191
16541
  assertCompleteMeshyControllerPack(manifest);
17192
- const destination = path21.join(args.root, ASSETS_DEST, "meshy-character.json");
17193
- await fs22.mkdir(path21.dirname(destination), { recursive: true });
17194
- await fs22.writeFile(
16542
+ const destination = path19.join(args.root, ASSETS_DEST, "meshy-character.json");
16543
+ await fs20.mkdir(path19.dirname(destination), { recursive: true });
16544
+ await fs20.writeFile(
17195
16545
  destination,
17196
16546
  `${JSON.stringify(manifest, null, 2)}
17197
16547
  `
@@ -17329,14 +16679,14 @@ function assertCompleteMeshyControllerPack(manifest) {
17329
16679
  }
17330
16680
  async function installFallbackAvatar(args) {
17331
16681
  const { root, srcDir, log } = args;
17332
- const dest = path21.join(root, ASSETS_DEST, "avatar.vrm");
17333
- await fs22.mkdir(path21.dirname(dest), { recursive: true });
17334
- await fs22.copyFile(path21.join(srcDir, "assets", "default-avatar.vrm"), dest);
16682
+ const dest = path19.join(root, ASSETS_DEST, "avatar.vrm");
16683
+ await fs20.mkdir(path19.dirname(dest), { recursive: true });
16684
+ await fs20.copyFile(path19.join(srcDir, "assets", "default-avatar.vrm"), dest);
17335
16685
  log.dim(" public/assets/avatar.vrm (fallback avatar \u2014 bundled CC0 default)");
17336
16686
  }
17337
16687
  async function exists3(p) {
17338
16688
  try {
17339
- await fs22.access(p);
16689
+ await fs20.access(p);
17340
16690
  return true;
17341
16691
  } catch {
17342
16692
  return false;
@@ -17344,8 +16694,8 @@ async function exists3(p) {
17344
16694
  }
17345
16695
 
17346
16696
  // src/commands/character.ts
17347
- import fs23 from "fs/promises";
17348
- import path22 from "path";
16697
+ import fs21 from "fs/promises";
16698
+ import path20 from "path";
17349
16699
  function exactAnimation(selector) {
17350
16700
  const trimmed = selector.trim();
17351
16701
  if (/^-?\d+$/.test(trimmed)) return animationById(Number(trimmed));
@@ -17425,7 +16775,7 @@ function showAmbiguity(selector, candidates, log, json = false) {
17425
16775
  }
17426
16776
  process.exitCode = 1;
17427
16777
  }
17428
- var INSTALLED_MANIFEST = path22.join("public", "assets", "meshy-character.json");
16778
+ var INSTALLED_MANIFEST = path20.join("public", "assets", "meshy-character.json");
17429
16779
  async function resolveAdoptTarget(selector) {
17430
16780
  const trimmed = selector?.trim();
17431
16781
  if (trimmed && !trimmed.endsWith(".json")) {
@@ -17434,7 +16784,7 @@ async function resolveAdoptTarget(selector) {
17434
16784
  const file = trimmed ?? INSTALLED_MANIFEST;
17435
16785
  let raw;
17436
16786
  try {
17437
- raw = await fs23.readFile(file, "utf8");
16787
+ raw = await fs21.readFile(file, "utf8");
17438
16788
  } catch {
17439
16789
  return {
17440
16790
  ok: false,
@@ -17912,22 +17262,22 @@ async function context2(opts) {
17912
17262
  const project = await readProject();
17913
17263
  return { token, apiUrl: getApiUrl(opts.apiUrl ?? project?.apiUrl) };
17914
17264
  }
17915
- async function readVideo(path25, log) {
17265
+ async function readVideo(path23, log) {
17916
17266
  let bytes;
17917
17267
  try {
17918
- bytes = await readFile(path25);
17268
+ bytes = await readFile(path23);
17919
17269
  } catch {
17920
- log.error(`Can't read ${path25}.`);
17270
+ log.error(`Can't read ${path23}.`);
17921
17271
  return null;
17922
17272
  }
17923
17273
  if (bytes.byteLength > MAX_VIDEO_BYTES) {
17924
- log.error(`${basename(path25)} is ${(bytes.byteLength / 1e6).toFixed(0)} MB \u2014 the limit is ${MAX_VIDEO_BYTES / 1e6} MB.`);
17274
+ log.error(`${basename(path23)} is ${(bytes.byteLength / 1e6).toFixed(0)} MB \u2014 the limit is ${MAX_VIDEO_BYTES / 1e6} MB.`);
17925
17275
  return null;
17926
17276
  }
17927
17277
  return bytes;
17928
17278
  }
17929
- async function uploadVideo(apiUrl, token, characterId, path25, bytes, log) {
17930
- const contentType = /\.mov$/i.test(path25) ? "video/quicktime" : "video/mp4";
17279
+ async function uploadVideo(apiUrl, token, characterId, path23, bytes, log) {
17280
+ const contentType = /\.mov$/i.test(path23) ? "video/quicktime" : "video/mp4";
17931
17281
  const minted = await apiFetch(
17932
17282
  `${apiUrl}/api/characters/${encodeURIComponent(characterId)}/motions/video-url`,
17933
17283
  {
@@ -17942,7 +17292,7 @@ async function uploadVideo(apiUrl, token, characterId, path25, bytes, log) {
17942
17292
  return null;
17943
17293
  }
17944
17294
  const { uploadUrl, videoUrl } = await minted.json();
17945
- log.dim(` uploading ${basename(path25)} (${(bytes.byteLength / 1e6).toFixed(1)} MB)\u2026`);
17295
+ log.dim(` uploading ${basename(path23)} (${(bytes.byteLength / 1e6).toFixed(1)} MB)\u2026`);
17946
17296
  const put = await fetch(uploadUrl, {
17947
17297
  method: "PUT",
17948
17298
  headers: { "Content-Type": contentType, "Content-Length": String(bytes.byteLength) },
@@ -18258,8 +17608,8 @@ function rank(items, query) {
18258
17608
  }
18259
17609
 
18260
17610
  // src/commands/motion.ts
18261
- import fs24 from "fs/promises";
18262
- import path23 from "path";
17611
+ import fs22 from "fs/promises";
17612
+ import path21 from "path";
18263
17613
 
18264
17614
  // src/lib/motion/npz.ts
18265
17615
  import zlib from "zlib";
@@ -19510,7 +18860,7 @@ async function motionGen(opts, log) {
19510
18860
  }
19511
18861
  if (opts.constraintsPath !== void 0) {
19512
18862
  try {
19513
- const raw = await fs24.readFile(opts.constraintsPath, "utf8");
18863
+ const raw = await fs22.readFile(opts.constraintsPath, "utf8");
19514
18864
  generationOptions.constraints = JSON.parse(raw);
19515
18865
  } catch {
19516
18866
  log.error(`Couldn't read the --constraints JSON at ${opts.constraintsPath}.`);
@@ -19534,10 +18884,10 @@ async function motionGen(opts, log) {
19534
18884
  async function expandTakes(selectors) {
19535
18885
  const out = [];
19536
18886
  for (const sel of selectors) {
19537
- const st = await fs24.stat(sel).catch(() => null);
18887
+ const st = await fs22.stat(sel).catch(() => null);
19538
18888
  if (st?.isDirectory()) {
19539
- const names = await fs24.readdir(sel);
19540
- for (const n of names.sort()) if (n.endsWith(".npz")) out.push(path23.join(sel, n));
18889
+ const names = await fs22.readdir(sel);
18890
+ for (const n of names.sort()) if (n.endsWith(".npz")) out.push(path21.join(sel, n));
19541
18891
  } else if (st?.isFile()) {
19542
18892
  out.push(sel);
19543
18893
  } else {
@@ -19572,7 +18922,7 @@ async function motionVerify(opts, log) {
19572
18922
  let gates = DEFAULT_GATES;
19573
18923
  if (opts.gatesPath) {
19574
18924
  try {
19575
- gates = mergeGates(DEFAULT_GATES, JSON.parse(await fs24.readFile(opts.gatesPath, "utf8")));
18925
+ gates = mergeGates(DEFAULT_GATES, JSON.parse(await fs22.readFile(opts.gatesPath, "utf8")));
19576
18926
  } catch {
19577
18927
  log.error(`Couldn't read the --gates JSON at ${opts.gatesPath}.`);
19578
18928
  process.exitCode = 1;
@@ -19594,9 +18944,9 @@ async function motionVerify(opts, log) {
19594
18944
  }
19595
18945
  const reports = [];
19596
18946
  for (const file of files) {
19597
- const stem = path23.basename(file).replace(/\.npz$/, "");
18947
+ const stem = path21.basename(file).replace(/\.npz$/, "");
19598
18948
  try {
19599
- reports.push(analyzeTake(stem, await fs24.readFile(file), gates));
18949
+ reports.push(analyzeTake(stem, await fs22.readFile(file), gates));
19600
18950
  } catch (err) {
19601
18951
  reports.push({
19602
18952
  take: stem,
@@ -19634,7 +18984,7 @@ async function motionCompile(opts, log) {
19634
18984
  let cfg = DEFAULT_MOTION_CONFIG;
19635
18985
  if (opts.configPath) {
19636
18986
  try {
19637
- const patch = JSON.parse(await fs24.readFile(opts.configPath, "utf8"));
18987
+ const patch = JSON.parse(await fs22.readFile(opts.configPath, "utf8"));
19638
18988
  cfg = { ...DEFAULT_MOTION_CONFIG, ...patch, idleLoop: { ...DEFAULT_MOTION_CONFIG.idleLoop, ...patch.idleLoop } };
19639
18989
  } catch {
19640
18990
  log.error(`Couldn't read the --config JSON at ${opts.configPath}.`);
@@ -19652,16 +19002,16 @@ async function motionCompile(opts, log) {
19652
19002
  }
19653
19003
  const inputs = [];
19654
19004
  for (const file of files) {
19655
- const stem = path23.basename(file).replace(/\.npz$/, "");
19005
+ const stem = path21.basename(file).replace(/\.npz$/, "");
19656
19006
  try {
19657
- inputs.push({ stem, take: loadTake(await fs24.readFile(file)) });
19007
+ inputs.push({ stem, take: loadTake(await fs22.readFile(file)) });
19658
19008
  } catch (err) {
19659
19009
  log.error(`${stem}: ${err instanceof Error ? err.message : String(err)}`);
19660
19010
  process.exitCode = 1;
19661
19011
  return;
19662
19012
  }
19663
19013
  }
19664
- const setName = opts.set ?? path23.basename(opts.out).replace(/\.json$/, "");
19014
+ const setName = opts.set ?? path21.basename(opts.out).replace(/\.json$/, "");
19665
19015
  let result;
19666
19016
  try {
19667
19017
  result = compileSet(inputs, setName, cfg);
@@ -19676,9 +19026,9 @@ async function motionCompile(opts, log) {
19676
19026
  process.exitCode = 1;
19677
19027
  return;
19678
19028
  }
19679
- await fs24.mkdir(path23.dirname(path23.resolve(opts.out)), { recursive: true });
19029
+ await fs22.mkdir(path21.dirname(path21.resolve(opts.out)), { recursive: true });
19680
19030
  const json = JSON.stringify(result.data);
19681
- await fs24.writeFile(opts.out, json);
19031
+ await fs22.writeFile(opts.out, json);
19682
19032
  if (opts.json) {
19683
19033
  writeJson({ out: opts.out, set: setName, tracks: Object.keys(result.data.gaits), bytes: json.length });
19684
19034
  return;
@@ -19696,9 +19046,9 @@ var MOTION_RUNTIME_FILES = [
19696
19046
  var MOTION_PRESETS = {
19697
19047
  rifle: ["sets/rifle.json", "sets/jumps.json"]
19698
19048
  };
19699
- var MOTION_DEST = path23.join("src", "motion");
19049
+ var MOTION_DEST = path21.join("src", "motion");
19700
19050
  async function motionInstall(opts, log) {
19701
- const srcDir = path23.join(getTemplatesDir(), "motion");
19051
+ const srcDir = path21.join(getTemplatesDir(), "motion");
19702
19052
  const root = opts.cwd ?? process.cwd();
19703
19053
  const preset = opts.set;
19704
19054
  if (preset !== void 0 && !MOTION_PRESETS[preset]) {
@@ -19709,21 +19059,21 @@ async function motionInstall(opts, log) {
19709
19059
  const files = [...MOTION_RUNTIME_FILES, ...preset ? MOTION_PRESETS[preset] : []];
19710
19060
  log.plain(c.bold(`genex motion install${preset ? ` --set ${preset}` : ""}`));
19711
19061
  log.plain("");
19712
- log.step(`Vendoring the motion runtime into ${c.cyan(MOTION_DEST + path23.sep)}`);
19062
+ log.step(`Vendoring the motion runtime into ${c.cyan(MOTION_DEST + path21.sep)}`);
19713
19063
  let copied = 0, skipped = 0;
19714
19064
  try {
19715
19065
  for (const rel of files) {
19716
- const dest = path23.join(root, MOTION_DEST, rel);
19717
- const exists4 = await fs24.access(dest).then(() => true, () => false);
19066
+ const dest = path21.join(root, MOTION_DEST, rel);
19067
+ const exists4 = await fs22.access(dest).then(() => true, () => false);
19718
19068
  if (!opts.force && exists4) {
19719
19069
  skipped++;
19720
- log.dim(` skipped ${path23.join(MOTION_DEST, rel)} (exists \u2014 use --force to overwrite)`);
19070
+ log.dim(` skipped ${path21.join(MOTION_DEST, rel)} (exists \u2014 use --force to overwrite)`);
19721
19071
  continue;
19722
19072
  }
19723
- await fs24.mkdir(path23.dirname(dest), { recursive: true });
19724
- await fs24.copyFile(path23.join(srcDir, rel), dest);
19073
+ await fs22.mkdir(path21.dirname(dest), { recursive: true });
19074
+ await fs22.copyFile(path21.join(srcDir, rel), dest);
19725
19075
  copied++;
19726
- log.dim(` ${path23.join(MOTION_DEST, rel)}`);
19076
+ log.dim(` ${path21.join(MOTION_DEST, rel)}`);
19727
19077
  }
19728
19078
  } catch (err) {
19729
19079
  log.error(`Copy failed: ${String(err)}`);
@@ -19764,7 +19114,7 @@ async function motionConstraints(opts, log) {
19764
19114
  }
19765
19115
  const doc = directionConstraint(dir, speed, duration);
19766
19116
  const out = opts.out ?? "constraints.json";
19767
- await fs24.writeFile(out, JSON.stringify(doc));
19117
+ await fs22.writeFile(out, JSON.stringify(doc));
19768
19118
  if (opts.json) {
19769
19119
  writeJson({ out, dir: dirName, speed, duration, waypoints: doc[0].frame_indices.length });
19770
19120
  return;
@@ -19801,9 +19151,9 @@ async function runMotion(opts) {
19801
19151
  }
19802
19152
 
19803
19153
  // src/commands/asset-new.ts
19804
- import fs25 from "fs";
19154
+ import fs23 from "fs";
19805
19155
  import fsp from "fs/promises";
19806
- import path24 from "path";
19156
+ import path22 from "path";
19807
19157
  import { pathToFileURL } from "url";
19808
19158
  var EXTRA_FILES = [
19809
19159
  "genex-asset.example.json",
@@ -19896,7 +19246,7 @@ Every milestone ends with \`npm run verify\`, then \`npx genex preview --no-buil
19896
19246
  }
19897
19247
  async function runAssetNew(options) {
19898
19248
  const log = createLogger();
19899
- const cwd = options.dir ? path24.resolve(options.dir) : process.cwd();
19249
+ const cwd = options.dir ? path22.resolve(options.dir) : process.cwd();
19900
19250
  const slug = options.assetSlug;
19901
19251
  if (!slug) {
19902
19252
  log.error('Usage: genex asset new <slug> [--title "<Name>"] [--dims WxDxH] [--tri-band LOW-HIGH] [--holder "<your name>"] [--out <dir>]');
@@ -19906,14 +19256,14 @@ async function runAssetNew(options) {
19906
19256
  log.error(`The slug must be kebab-case (letters, digits, dashes), got: ${slug}`);
19907
19257
  return 1;
19908
19258
  }
19909
- const templateDir = path24.join(getTemplatesDir(), "asset-viewer");
19910
- if (!fs25.existsSync(templateDir)) {
19259
+ const templateDir = path22.join(getTemplatesDir(), "asset-viewer");
19260
+ if (!fs23.existsSync(templateDir)) {
19911
19261
  log.error(`Vendored asset-viewer template not found at ${templateDir} \u2014 reinstall the CLI.`);
19912
19262
  return 1;
19913
19263
  }
19914
- const manifestTools = await import(pathToFileURL(path24.join(templateDir, "tools", "emit-manifest.mjs")).href);
19264
+ const manifestTools = await import(pathToFileURL(path22.join(templateDir, "tools", "emit-manifest.mjs")).href);
19915
19265
  const { SHARED_FILES, PARITY_FILENAME, hashSharedFiles, pascalCase } = manifestTools;
19916
- const lockPath = path24.join(templateDir, "shared-files.sha256.json");
19266
+ const lockPath = path22.join(templateDir, "shared-files.sha256.json");
19917
19267
  const lock = JSON.parse(await fsp.readFile(lockPath, "utf8"));
19918
19268
  const actual = hashSharedFiles(templateDir);
19919
19269
  const drifted = SHARED_FILES.filter((rel) => lock.files[rel] !== actual[rel]);
@@ -19926,8 +19276,8 @@ async function runAssetNew(options) {
19926
19276
  const triBand = parseBand(options.triBand ?? "500-8000");
19927
19277
  const holder = options.holder?.trim() || HOLDER_PLACEHOLDER;
19928
19278
  const year = (/* @__PURE__ */ new Date()).getUTCFullYear();
19929
- const outDir = path24.resolve(cwd, options.out ?? slug);
19930
- if (fs25.existsSync(outDir) && fs25.readdirSync(outDir).length > 0 && !options.force) {
19279
+ const outDir = path22.resolve(cwd, options.out ?? slug);
19280
+ if (fs23.existsSync(outDir) && fs23.readdirSync(outDir).length > 0 && !options.force) {
19931
19281
  log.error(`${outDir} already holds files. Nothing was touched \u2014 pass --force to write into it anyway.`);
19932
19282
  return 1;
19933
19283
  }
@@ -19955,24 +19305,24 @@ async function runAssetNew(options) {
19955
19305
  };
19956
19306
  await fsp.mkdir(outDir, { recursive: true });
19957
19307
  for (const rel of [...SHARED_FILES, ...EXTRA_FILES]) {
19958
- const to = path24.join(outDir, rel);
19959
- await fsp.mkdir(path24.dirname(to), { recursive: true });
19960
- await fsp.copyFile(path24.join(templateDir, rel), to);
19308
+ const to = path22.join(outDir, rel);
19309
+ await fsp.mkdir(path22.dirname(to), { recursive: true });
19310
+ await fsp.copyFile(path22.join(templateDir, rel), to);
19961
19311
  }
19962
- const pkg = fillTemplate(await fsp.readFile(path24.join(templateDir, "package.json"), "utf8"), {
19312
+ const pkg = fillTemplate(await fsp.readFile(path22.join(templateDir, "package.json"), "utf8"), {
19963
19313
  slug,
19964
19314
  name,
19965
19315
  version
19966
19316
  });
19967
- await fsp.writeFile(path24.join(outDir, "package.json"), pkg, "utf8");
19968
- await fsp.writeFile(path24.join(outDir, "asset.config.json"), JSON.stringify(config, null, 2) + "\n", "utf8");
19969
- await fsp.writeFile(path24.join(outDir, ".gitignore"), GITIGNORE, "utf8");
19317
+ await fsp.writeFile(path22.join(outDir, "package.json"), pkg, "utf8");
19318
+ await fsp.writeFile(path22.join(outDir, "asset.config.json"), JSON.stringify(config, null, 2) + "\n", "utf8");
19319
+ await fsp.writeFile(path22.join(outDir, ".gitignore"), GITIGNORE, "utf8");
19970
19320
  await fsp.writeFile(
19971
- path24.join(outDir, "DESIGN.md"),
19321
+ path22.join(outDir, "DESIGN.md"),
19972
19322
  designDoc({ name, slug, sizeMeters, triBand, holder }),
19973
19323
  "utf8"
19974
19324
  );
19975
- const placeholder = await fsp.readFile(path24.join(templateDir, "src", "asset", "PLACEHOLDER.ts"), "utf8");
19325
+ const placeholder = await fsp.readFile(path22.join(templateDir, "src", "asset", "PLACEHOLDER.ts"), "utf8");
19976
19326
  const seeded = seedAssetSource(placeholder, {
19977
19327
  slug,
19978
19328
  name,
@@ -19983,8 +19333,8 @@ async function runAssetNew(options) {
19983
19333
  pascalCase
19984
19334
  });
19985
19335
  const assetFilename = `${slug.replace(/-([a-z0-9])/g, (_, ch) => ch.toUpperCase())}.ts`;
19986
- await fsp.mkdir(path24.join(outDir, "src", "asset"), { recursive: true });
19987
- await fsp.writeFile(path24.join(outDir, "src", "asset", assetFilename), seeded, "utf8");
19336
+ await fsp.mkdir(path22.join(outDir, "src", "asset"), { recursive: true });
19337
+ await fsp.writeFile(path22.join(outDir, "src", "asset", assetFilename), seeded, "utf8");
19988
19338
  const copied = hashSharedFiles(outDir);
19989
19339
  const mismatched = SHARED_FILES.filter((rel) => copied[rel] !== lock.files[rel]);
19990
19340
  if (mismatched.length) {
@@ -19992,7 +19342,7 @@ async function runAssetNew(options) {
19992
19342
  return 1;
19993
19343
  }
19994
19344
  await fsp.writeFile(
19995
- path24.join(outDir, PARITY_FILENAME),
19345
+ path22.join(outDir, PARITY_FILENAME),
19996
19346
  JSON.stringify(
19997
19347
  {
19998
19348
  note: "Recorded by `genex asset new`. Gate G13 recomputes these; do not hand-edit the viewer.",
@@ -20046,15 +19396,9 @@ ${c.bold("Usage")}
20046
19396
  resumes the same code, so an interrupted
20047
19397
  'genex init' costs nothing. --force switches
20048
19398
  accounts.
20049
- genex link <slug> [options] Point THIS folder at an existing game of yours;
20050
- preview/publish then update the same live game.
20051
- In an EMPTY folder it downloads the game too \u2014
20052
- that's how you pick a game up on a new machine.
20053
- Never creates a project.
20054
- genex pull [options] Update THIS folder with the game's latest draft,
20055
- for when it was built from somewhere else. Refuses
20056
- if this folder has changes you never deployed;
20057
- --force replaces them.
19399
+ genex link <slug> [options] Re-link THIS folder to an existing game of yours
19400
+ (lost folder / new machine); preview/publish then
19401
+ update the same live game. Never creates a project.
20058
19402
  genex rename <name> [options] Rename THIS game \u2014 its play address, its source repo,
20059
19403
  and its gallery name. Keeps the game (plays, likes,
20060
19404
  comments); the OLD address stops working, so a
@@ -20070,18 +19414,6 @@ ${c.bold("Usage")}
20070
19414
  genex publish [options] Build + push + make live, then list it in the gallery.
20071
19415
  genex make-remixable [options] Make THIS game remixable by everyone \u2014 migrates a
20072
19416
  private source onto a public managed genex repo.
20073
- genex domain <sub> [host] Play this game on a domain you own:
20074
- add | list | verify | remove. "add" opens a
20075
- one-click approval at your DNS host \u2014 no
20076
- records to copy or paste.
20077
- genex shop <sub> [name|id] What this game sells: list | add | set | remove |
20078
- test. "add" prints the item id your game passes
20079
- to buy({ skuId }) \u2014 the platform owns the
20080
- catalog, so this is the only way one exists.
20081
- Prices come off a fixed grid, printed on any
20082
- refusal. "test <id>" gives you a free copy of
20083
- your own item: you can't buy from your own game,
20084
- so this is how you check the shop works.
20085
19417
  genex model "<prompt>" [options] Generate a 3D model (GLB); prints a public asset URL.
20086
19418
  genex sfx "<prompt>" [options] Generate a sound effect (mp3); prints a public asset URL.
20087
19419
  genex music "<prompt>" [options] Generate an instrumental music track (mp3); prints a
@@ -20329,13 +19661,6 @@ ${c.bold("Examples")}
20329
19661
  genex publish
20330
19662
  genex publish --categories games,vfx
20331
19663
  genex make-remixable
20332
- genex domain add play.mygame.com
20333
- genex domain list
20334
- genex shop list
20335
- genex shop add "Iron Key" --price 100 --type durable
20336
- genex shop set sku_123 --price 200
20337
- genex shop test sku_123
20338
- genex shop remove sku_123
20339
19664
  genex publish --no-push --title "My Game"
20340
19665
  genex model "weathered wooden barrel with iron bands"
20341
19666
  genex sfx "punchy laser zap" --duration 2
@@ -20387,10 +19712,6 @@ function parseArgs(argv) {
20387
19712
  "--name",
20388
19713
  "--repo",
20389
19714
  "--remixed-from",
20390
- "--price",
20391
- "--icon",
20392
- "--rename",
20393
- "--type",
20394
19715
  "--title",
20395
19716
  "--description",
20396
19717
  "--categories",
@@ -20468,9 +19789,6 @@ function parseArgs(argv) {
20468
19789
  case "--no-auth":
20469
19790
  parsed.options.noAuth = true;
20470
19791
  break;
20471
- case "--no-open":
20472
- parsed.options.noOpen = true;
20473
- break;
20474
19792
  case "--no-push":
20475
19793
  parsed.options.noPush = true;
20476
19794
  break;
@@ -20605,18 +19923,6 @@ function parseArgs(argv) {
20605
19923
  } else {
20606
19924
  (parsed.options.selectors ??= []).push(arg);
20607
19925
  }
20608
- } else if (parsed.command === "shop") {
20609
- if (!parsed.options.hostname) parsed.options.hostname = arg;
20610
- else {
20611
- parsed.error = `Unexpected argument: ${arg}`;
20612
- return parsed;
20613
- }
20614
- } else if (parsed.command === "domain") {
20615
- if (!parsed.options.hostname) parsed.options.hostname = arg;
20616
- else {
20617
- parsed.error = `Unexpected argument: ${arg}`;
20618
- return parsed;
20619
- }
20620
19926
  } else if (parsed.command === "animations") {
20621
19927
  parsed.options.query = parsed.options.query ? `${parsed.options.query} ${arg}` : arg;
20622
19928
  } else if (parsed.command === "asset") {
@@ -20809,27 +20115,6 @@ function applyValueFlag(options, flag, value) {
20809
20115
  options.duration = n;
20810
20116
  break;
20811
20117
  }
20812
- case "--price": {
20813
- const n = Number(value);
20814
- if (!Number.isInteger(n) || n <= 0) {
20815
- throw new Error(`Invalid --price value: ${value} (whole coin, e.g. 100)`);
20816
- }
20817
- options.price = n;
20818
- break;
20819
- }
20820
- case "--icon":
20821
- options.icon = value;
20822
- break;
20823
- case "--type": {
20824
- if (value !== "consumable" && value !== "durable") {
20825
- throw new Error(`Invalid --type value: ${value} (consumable or durable)`);
20826
- }
20827
- options.type = value;
20828
- break;
20829
- }
20830
- case "--rename":
20831
- options.rename = value;
20832
- break;
20833
20118
  case "--aspect":
20834
20119
  options.aspect = value;
20835
20120
  break;
@@ -21001,9 +20286,6 @@ async function main() {
21001
20286
  case "link":
21002
20287
  await runLink(parsed.options);
21003
20288
  break;
21004
- case "pull":
21005
- await runPull(parsed.options);
21006
- break;
21007
20289
  case "rename":
21008
20290
  await runRename(parsed.options);
21009
20291
  break;
@@ -21013,12 +20295,6 @@ async function main() {
21013
20295
  case "make-remixable":
21014
20296
  await runMakeRemixable(parsed.options);
21015
20297
  break;
21016
- case "shop":
21017
- await runShop(parsed.options);
21018
- break;
21019
- case "domain":
21020
- await runDomain(parsed.options);
21021
- break;
21022
20298
  case "controller":
21023
20299
  await runController({ ...parsed.options, kind: parsed.options.name });
21024
20300
  break;