@genex-ai/cli-demo 1.14.3-dev.576 → 1.15.1-dev.578

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
@@ -2,27 +2,42 @@
2
2
  import {
3
3
  RENDER_MODES,
4
4
  SHEET_FORMATS,
5
+ acceptUrl,
5
6
  acquireSeat,
6
7
  apiFetch,
7
8
  blenderCall,
8
9
  blenderEndpoint,
9
10
  blenderSetupHint,
11
+ cloneSource,
12
+ fetchCloneGrant,
10
13
  fetchSignedInEmail,
11
14
  getWorkspacePath,
15
+ inHostedSession,
12
16
  isRenderMode,
17
+ isStale,
18
+ isTermsRefusal,
19
+ mayForceOverAnotherDevice,
13
20
  printedStructuredError,
14
21
  readProject,
22
+ readRemoteSource,
15
23
  readUserToken,
16
24
  readWorkspace,
25
+ reportForceRefused,
26
+ reportStale,
27
+ reportTermsRefusal,
17
28
  restrictFilePermissions,
18
29
  rotateRejectedEnv,
30
+ run,
31
+ runAccept,
19
32
  setWorkspaceHeader,
20
33
  sheetExt,
21
34
  sheetOf,
35
+ sourceTreeHash,
36
+ urlHasEmbeddedCredentials,
22
37
  writeProject,
23
38
  writeUserToken,
24
39
  writeWorkspace
25
- } from "./chunk-TFRTQ37H.js";
40
+ } from "./chunk-JGX6YRC7.js";
26
41
  import {
27
42
  CLI_CHANNEL,
28
43
  DEFAULT_API_URL,
@@ -873,10 +888,22 @@ import os4 from "os";
873
888
  import path4 from "path";
874
889
 
875
890
  // src/lib/workspace.ts
876
- async function workspaceMode(cwd = process.cwd()) {
877
- if (await readProject(cwd)) return "game";
891
+ async function resolveWorkspace(cwd = process.cwd()) {
892
+ const project = await readProject(cwd);
893
+ if (project) {
894
+ const slug = typeof project.slug === "string" && project.slug.trim() ? project.slug.trim() : null;
895
+ return { mode: "game", slug };
896
+ }
878
897
  const ws = await readWorkspace(cwd);
879
- return ws?.mode === "tools" ? "tools" : null;
898
+ return { mode: ws?.mode === "tools" ? "tools" : null, slug: null };
899
+ }
900
+ async function workspaceMode(cwd = process.cwd()) {
901
+ return (await resolveWorkspace(cwd)).mode;
902
+ }
903
+ function workspaceHeaderLabel(ws) {
904
+ if (ws.mode === "tools") return "tools";
905
+ if (ws.mode === "game" && ws.slug) return { game: ws.slug };
906
+ return null;
880
907
  }
881
908
  var REFUSED_IN_TOOLS = /* @__PURE__ */ new Set([
882
909
  "link",
@@ -1748,177 +1775,9 @@ async function runInit(opts) {
1748
1775
  }
1749
1776
 
1750
1777
  // src/commands/link.ts
1751
- import fs9 from "fs/promises";
1752
- import os6 from "os";
1753
- import path9 from "path";
1754
-
1755
- // src/lib/source-sync.ts
1756
1778
  import fs8 from "fs/promises";
1757
- import path8 from "path";
1758
1779
  import os5 from "os";
1759
-
1760
- // src/utils/run.ts
1761
- import { spawn as spawn2 } from "child_process";
1762
- var WIN_SHELL_COMMANDS = /* @__PURE__ */ new Set(["npm", "npx"]);
1763
- function run(cmd, args, env) {
1764
- const shell = process.platform === "win32" && WIN_SHELL_COMMANDS.has(cmd);
1765
- return new Promise((resolve) => {
1766
- let child;
1767
- try {
1768
- child = spawn2(cmd, args, { env: env ? { ...process.env, ...env } : process.env, shell });
1769
- } catch {
1770
- resolve({ code: -1, out: "", err: `${cmd} not found` });
1771
- return;
1772
- }
1773
- let out = "";
1774
- let err = "";
1775
- child.stdout?.on("data", (d) => out += String(d));
1776
- child.stderr?.on("data", (d) => err += String(d));
1777
- child.on("error", () => resolve({ code: -1, out, err: `${cmd} not found` }));
1778
- child.on("close", (code) => resolve({ code: code ?? -1, out, err }));
1779
- });
1780
- }
1781
-
1782
- // src/lib/source-sync.ts
1783
- async function readRemoteSource(apiUrl, token, projectId) {
1784
- try {
1785
- const res = await apiFetch(`${apiUrl}/api/projects/${projectId}`, {
1786
- headers: { Authorization: `Bearer ${token}` }
1787
- });
1788
- if (!res.ok) return null;
1789
- const data = await res.json().catch(() => null);
1790
- if (!data?.project) return null;
1791
- return {
1792
- stagingCommit: data.project.stagingCommitSha ?? null,
1793
- commit: data.project.commitSha ?? null
1794
- };
1795
- } catch {
1796
- return null;
1797
- }
1798
- }
1799
- function isStale(local, remote) {
1800
- if (!local || !remote) return false;
1801
- return local !== remote;
1802
- }
1803
- function inHostedSession() {
1804
- return process.env.GENEX_HOSTED_SESSION === "1";
1805
- }
1806
- function mayForceOverAnotherDevice() {
1807
- return !inHostedSession();
1808
- }
1809
- function reportForceRefused(log) {
1810
- log.error("`--force` is not available inside a Genex chat session.");
1811
- log.dim(" It would replace work shipped from another machine, and that is not yours to discard here.");
1812
- log.dim(` Do this instead \u2014 it keeps both sides:`);
1813
- log.dim(` 1. ${c.cyan("npx genex pull --force")} takes the other machine's work (a copy of this folder is kept under .genex/replaced-*)`);
1814
- log.dim(` 2. redo your change on top of it \u2014 you know what you just changed`);
1815
- log.dim(` 3. ${c.cyan("npx genex preview")}`);
1816
- log.dim(" If the user explicitly wants their chat version to win, ask them to run `npx genex preview --force` themselves.");
1817
- }
1818
- function reportStale(log, slug, local, remote) {
1819
- log.error(`${c.cyan(slug)} was updated from another device \u2014 nothing was deployed.`);
1820
- if (remote && local) {
1821
- const short = remote.slice(0, 7) !== local.slice(0, 7);
1822
- const r = short ? remote.slice(0, 7) : remote;
1823
- const l = short ? local.slice(0, 7) : local;
1824
- log.dim(` the draft is on ${r}, this folder last shipped ${l}`);
1825
- }
1826
- log.dim(` ${c.cyan("npx genex pull")} \u2014 take the other device's work (refuses if you have unshipped changes)`);
1827
- log.dim(` ${c.cyan("npx genex preview --force")} \u2014 keep yours and replace theirs`);
1828
- }
1829
- async function sourceTreeHash(cwd) {
1830
- const gitDir = await fs8.mkdtemp(path8.join(os5.tmpdir(), "genex-tree-"));
1831
- const base = { GIT_DIR: gitDir };
1832
- try {
1833
- if ((await run("git", ["init", "-q"], base)).code !== 0) return null;
1834
- await fs8.writeFile(
1835
- path8.join(gitDir, "info", "exclude"),
1836
- ["node_modules/", "dist/", ".git/", ".genex/", ".env", ".env.*", "!.env.example", ""].join("\n")
1837
- );
1838
- if ((await run("git", ["lfs", "version"], base)).code === 0) {
1839
- const filters = [
1840
- ["filter.lfs.clean", "git-lfs clean -- %f"],
1841
- ["filter.lfs.smudge", "git-lfs smudge -- %f"],
1842
- ["filter.lfs.process", "git-lfs filter-process"],
1843
- ["filter.lfs.required", "true"]
1844
- ];
1845
- for (const [key, value] of filters) await run("git", ["config", key, value], base);
1846
- }
1847
- const env = { ...base, GIT_WORK_TREE: cwd, GIT_INDEX_FILE: path8.join(gitDir, "index-tree") };
1848
- if ((await run("git", ["add", "-A"], env)).code !== 0) return null;
1849
- const tree = (await run("git", ["write-tree"], env)).out.trim();
1850
- return /^[0-9a-f]{40}$/.test(tree) ? tree : null;
1851
- } catch {
1852
- return null;
1853
- } finally {
1854
- await fs8.rm(gitDir, { recursive: true, force: true }).catch(() => {
1855
- });
1856
- }
1857
- }
1858
- function urlHasEmbeddedCredentials(url) {
1859
- return /^[a-z][a-z0-9+.-]*:\/\/[^/@]+@/i.test(url);
1860
- }
1861
- function credentialHelperOff(url) {
1862
- if (!urlHasEmbeddedCredentials(url)) return {};
1863
- return { GIT_CONFIG_COUNT: "1", GIT_CONFIG_KEY_0: "credential.helper", GIT_CONFIG_VALUE_0: "" };
1864
- }
1865
- async function fetchCloneGrant(apiUrl, token, projectId, log) {
1866
- let res;
1867
- try {
1868
- res = await apiFetch(`${apiUrl}/api/projects/${projectId}/push-token`, {
1869
- method: "POST",
1870
- headers: { Authorization: `Bearer ${token}`, "X-Genex-Source-Intent": "read" }
1871
- });
1872
- } catch (err) {
1873
- log.error(`Couldn't reach the API to authorize the source read: ${String(err)}`);
1874
- return null;
1875
- }
1876
- if (res.status === 401) {
1877
- log.error("Not authorized \u2014 your token may have expired. Re-run `genex auth`.");
1878
- return null;
1879
- }
1880
- if (!res.ok) {
1881
- log.error(`Couldn't authorize the source read (HTTP ${res.status}).`);
1882
- return null;
1883
- }
1884
- const data = await res.json().catch(() => null);
1885
- const url = data?.pushUrl ?? data?.cloneUrl;
1886
- if (!url) {
1887
- log.error("The API didn't return a source URL.");
1888
- return null;
1889
- }
1890
- return { cloneUrl: url, sourceRef: data?.sourceRef ?? null };
1891
- }
1892
- async function cloneSource(grant, dest, log) {
1893
- const args = grant.sourceRef ? ["clone", "--branch", grant.sourceRef, grant.cloneUrl, dest] : ["clone", grant.cloneUrl, dest];
1894
- const env = {
1895
- GIT_LFS_SKIP_SMUDGE: "1",
1896
- GIT_TERMINAL_PROMPT: "0",
1897
- ...credentialHelperOff(grant.cloneUrl)
1898
- };
1899
- const cloned = await run("git", args, env);
1900
- if (cloned.code !== 0) {
1901
- log.error("Couldn't download the game's source.");
1902
- log.dim(` git clone exited ${cloned.code}`);
1903
- return false;
1904
- }
1905
- const repo = { ...env, GIT_DIR: path8.join(dest, ".git"), GIT_WORK_TREE: dest };
1906
- const filters = [
1907
- ["filter.lfs.clean", "git-lfs clean -- %f"],
1908
- ["filter.lfs.smudge", "git-lfs smudge -- %f"],
1909
- ["filter.lfs.process", "git-lfs filter-process"],
1910
- ["filter.lfs.required", "true"]
1911
- ];
1912
- for (const [key, value] of filters) await run("git", ["config", key, value], repo);
1913
- const pulled = await run("git", ["lfs", "pull"], repo);
1914
- if (pulled.code !== 0) {
1915
- log.warn("Binary assets (models, textures, audio) are still pointer files \u2014 `git lfs pull` failed.");
1916
- log.dim(" The source is there; run `git lfs pull` in this folder once git-lfs works.");
1917
- }
1918
- return true;
1919
- }
1920
-
1921
- // src/commands/link.ts
1780
+ import path8 from "path";
1922
1781
  async function runLink(opts) {
1923
1782
  const log = createLogger({ quiet: opts.quiet });
1924
1783
  log.plain(c.bold("genex link"));
@@ -1998,7 +1857,7 @@ async function runLink(opts) {
1998
1857
  }
1999
1858
  async function isEmptyDir(cwd) {
2000
1859
  try {
2001
- const entries = await fs9.readdir(cwd);
1860
+ const entries = await fs8.readdir(cwd);
2002
1861
  return entries.every((e) => e === ".git" || e === ".genex" || e === ".DS_Store");
2003
1862
  } catch {
2004
1863
  return false;
@@ -2012,13 +1871,13 @@ async function downloadSource(apiUrl, token, project, log) {
2012
1871
  return false;
2013
1872
  }
2014
1873
  log.step(`Downloading the ${grant.sourceRef === "preview" ? "draft" : "published"} source\u2026`);
2015
- const staging = await fs9.mkdtemp(path9.join(os6.tmpdir(), "genex-link-"));
2016
- const fresh = path9.join(staging, "source");
1874
+ const staging = await fs8.mkdtemp(path8.join(os5.tmpdir(), "genex-link-"));
1875
+ const fresh = path8.join(staging, "source");
2017
1876
  try {
2018
1877
  if (!await cloneSource(grant, fresh, log)) return false;
2019
- await fs9.rm(path9.join(fresh, ".git"), { recursive: true, force: true });
2020
- for (const entry of await fs9.readdir(fresh)) {
2021
- await fs9.cp(path9.join(fresh, entry), path9.join(process.cwd(), entry), {
1878
+ await fs8.rm(path8.join(fresh, ".git"), { recursive: true, force: true });
1879
+ for (const entry of await fs8.readdir(fresh)) {
1880
+ await fs8.cp(path8.join(fresh, entry), path8.join(process.cwd(), entry), {
2022
1881
  recursive: true,
2023
1882
  force: true
2024
1883
  });
@@ -2026,28 +1885,28 @@ async function downloadSource(apiUrl, token, project, log) {
2026
1885
  log.success("Downloaded.");
2027
1886
  return true;
2028
1887
  } finally {
2029
- await fs9.rm(staging, { recursive: true, force: true }).catch(() => {
1888
+ await fs8.rm(staging, { recursive: true, force: true }).catch(() => {
2030
1889
  });
2031
1890
  }
2032
1891
  }
2033
1892
  async function ensureSlugEnv(slug, log, cwd = process.cwd()) {
2034
- const file = path9.join(cwd, ".env");
1893
+ const file = path8.join(cwd, ".env");
2035
1894
  let content;
2036
1895
  try {
2037
- content = await fs9.readFile(file, "utf8");
1896
+ content = await fs8.readFile(file, "utf8");
2038
1897
  } catch {
2039
1898
  return;
2040
1899
  }
2041
1900
  const re = /^(\s*VITE_GENEX_SLUG=)(.*)$/m;
2042
1901
  const m = content.match(re);
2043
1902
  if (!m) {
2044
- await fs9.appendFile(file, `${content.endsWith("\n") ? "" : "\n"}VITE_GENEX_SLUG=${slug}
1903
+ await fs8.appendFile(file, `${content.endsWith("\n") ? "" : "\n"}VITE_GENEX_SLUG=${slug}
2045
1904
  `);
2046
1905
  log.dim(` added VITE_GENEX_SLUG=${slug} to .env`);
2047
1906
  return;
2048
1907
  }
2049
1908
  if (m[2].trim() === slug) return;
2050
- await fs9.writeFile(file, content.replace(re, `$1${slug}`));
1909
+ await fs8.writeFile(file, content.replace(re, `$1${slug}`));
2051
1910
  log.dim(` updated .env: VITE_GENEX_SLUG=${slug} (was ${m[2].trim()})`);
2052
1911
  }
2053
1912
  async function fetchOwnProject(apiUrl, token, slug, log) {
@@ -2101,9 +1960,9 @@ async function listOwnSlugs(apiUrl, token, log) {
2101
1960
  }
2102
1961
 
2103
1962
  // src/commands/pull.ts
2104
- import fs10 from "fs/promises";
2105
- import path10 from "path";
2106
- import os7 from "os";
1963
+ import fs9 from "fs/promises";
1964
+ import path9 from "path";
1965
+ import os6 from "os";
2107
1966
  function isMachineLocal(entry) {
2108
1967
  if (entry === ".genex" || entry === "node_modules" || entry === ".git") return true;
2109
1968
  if (entry === ".env.example") return false;
@@ -2162,23 +2021,23 @@ async function runPull(opts) {
2162
2021
  process.exitCode = 1;
2163
2022
  return;
2164
2023
  }
2165
- const staging = await fs10.mkdtemp(path10.join(os7.tmpdir(), "genex-pull-"));
2166
- const fresh = path10.join(staging, "source");
2024
+ const staging = await fs9.mkdtemp(path9.join(os6.tmpdir(), "genex-pull-"));
2025
+ const fresh = path9.join(staging, "source");
2167
2026
  try {
2168
2027
  if (!await cloneSource(grant, fresh, log)) {
2169
2028
  process.exitCode = 1;
2170
2029
  return;
2171
2030
  }
2172
- await fs10.rm(path10.join(fresh, ".git"), { recursive: true, force: true });
2031
+ await fs9.rm(path9.join(fresh, ".git"), { recursive: true, force: true });
2173
2032
  const kept = await keepReplaced(cwd, log);
2174
2033
  await replaceTree(cwd, fresh);
2175
2034
  if (kept) {
2176
2035
  log.plain("");
2177
- log.info(`What was here is kept at ${c.cyan(path10.relative(cwd, kept) || kept)}`);
2036
+ log.info(`What was here is kept at ${c.cyan(path9.relative(cwd, kept) || kept)}`);
2178
2037
  log.dim(" Nothing was thrown away \u2014 re-apply from there, or delete it when you are done.");
2179
2038
  }
2180
2039
  } finally {
2181
- await fs10.rm(staging, { recursive: true, force: true }).catch(() => {
2040
+ await fs9.rm(staging, { recursive: true, force: true }).catch(() => {
2182
2041
  });
2183
2042
  }
2184
2043
  const remote = await readRemoteSource(apiUrl, token, meta.id);
@@ -2197,13 +2056,13 @@ async function runPull(opts) {
2197
2056
  }
2198
2057
  async function keepReplaced(cwd, log) {
2199
2058
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
2200
- const dest = path10.join(cwd, ".genex", `replaced-${stamp}`);
2059
+ const dest = path9.join(cwd, ".genex", `replaced-${stamp}`);
2201
2060
  try {
2202
- const entries = (await fs10.readdir(cwd)).filter((e) => !isMachineLocal(e));
2061
+ const entries = (await fs9.readdir(cwd)).filter((e) => !isMachineLocal(e));
2203
2062
  if (entries.length === 0) return null;
2204
- await fs10.mkdir(dest, { recursive: true });
2063
+ await fs9.mkdir(dest, { recursive: true });
2205
2064
  for (const entry of entries) {
2206
- await fs10.cp(path10.join(cwd, entry), path10.join(dest, entry), { recursive: true });
2065
+ await fs9.cp(path9.join(cwd, entry), path9.join(dest, entry), { recursive: true });
2207
2066
  }
2208
2067
  return dest;
2209
2068
  } catch (err) {
@@ -2213,19 +2072,19 @@ async function keepReplaced(cwd, log) {
2213
2072
  }
2214
2073
  }
2215
2074
  async function replaceTree(dest, src) {
2216
- for (const entry of await fs10.readdir(dest)) {
2075
+ for (const entry of await fs9.readdir(dest)) {
2217
2076
  if (isMachineLocal(entry)) continue;
2218
- await fs10.rm(path10.join(dest, entry), { recursive: true, force: true });
2077
+ await fs9.rm(path9.join(dest, entry), { recursive: true, force: true });
2219
2078
  }
2220
- for (const entry of await fs10.readdir(src)) {
2079
+ for (const entry of await fs9.readdir(src)) {
2221
2080
  if (isMachineLocal(entry)) continue;
2222
- await fs10.cp(path10.join(src, entry), path10.join(dest, entry), { recursive: true });
2081
+ await fs9.cp(path9.join(src, entry), path9.join(dest, entry), { recursive: true });
2223
2082
  }
2224
2083
  }
2225
2084
 
2226
2085
  // src/commands/rename.ts
2227
- import fs11 from "fs/promises";
2228
- import path11 from "path";
2086
+ import fs10 from "fs/promises";
2087
+ import path10 from "path";
2229
2088
  async function runRename(opts) {
2230
2089
  const log = createLogger({ quiet: opts.quiet });
2231
2090
  log.plain(c.bold("genex rename"));
@@ -2313,23 +2172,23 @@ async function runRename(opts) {
2313
2172
  log.info("Run `genex preview` (or `publish`) to rebuild \u2014 the new slug is baked into the bundle.");
2314
2173
  }
2315
2174
  async function rewriteSlugEnv(from, to, log, cwd = process.cwd()) {
2316
- const file = path11.join(cwd, ".env");
2175
+ const file = path10.join(cwd, ".env");
2317
2176
  let content;
2318
2177
  try {
2319
- content = await fs11.readFile(file, "utf8");
2178
+ content = await fs10.readFile(file, "utf8");
2320
2179
  } catch {
2321
2180
  return;
2322
2181
  }
2323
2182
  const re = /^(\s*VITE_GENEX_SLUG=)(.*)$/m;
2324
2183
  if (!re.test(content)) return;
2325
- await fs11.writeFile(file, content.replace(re, `$1${to}`));
2184
+ await fs10.writeFile(file, content.replace(re, `$1${to}`));
2326
2185
  log.dim(` .env: VITE_GENEX_SLUG=${to} (was ${from})`);
2327
2186
  }
2328
2187
  async function rewriteBakedSlug(from, to, log, cwd = process.cwd()) {
2329
- const file = path11.join(cwd, "src", "genex.config.ts");
2188
+ const file = path10.join(cwd, "src", "genex.config.ts");
2330
2189
  let content;
2331
2190
  try {
2332
- content = await fs11.readFile(file, "utf8");
2191
+ content = await fs10.readFile(file, "utf8");
2333
2192
  } catch {
2334
2193
  return;
2335
2194
  }
@@ -2339,7 +2198,7 @@ async function rewriteBakedSlug(from, to, log, cwd = process.cwd()) {
2339
2198
  log.warn(` src/genex.config.ts has no "${from}" literal \u2014 check its slug by hand.`);
2340
2199
  return;
2341
2200
  }
2342
- await fs11.writeFile(file, content.replace(quoted, `"${to}"`));
2201
+ await fs10.writeFile(file, content.replace(quoted, `"${to}"`));
2343
2202
  log.dim(` src/genex.config.ts: baked slug -> ${to}`);
2344
2203
  }
2345
2204
 
@@ -2470,13 +2329,13 @@ function relTime(iso) {
2470
2329
  import "child_process";
2471
2330
 
2472
2331
  // src/lib/generation-ledger.ts
2473
- import fs12 from "fs/promises";
2474
- import path12 from "path";
2475
- var ledgerPath = (cwd) => path12.join(cwd, ".genex", "generations.ndjson");
2332
+ import fs11 from "fs/promises";
2333
+ import path11 from "path";
2334
+ var ledgerPath = (cwd) => path11.join(cwd, ".genex", "generations.ndjson");
2476
2335
  async function append(cwd, event) {
2477
2336
  try {
2478
- await fs12.access(path12.join(cwd, ".genex"));
2479
- await fs12.appendFile(ledgerPath(cwd), `${JSON.stringify(event)}
2337
+ await fs11.access(path11.join(cwd, ".genex"));
2338
+ await fs11.appendFile(ledgerPath(cwd), `${JSON.stringify(event)}
2480
2339
  `, "utf8");
2481
2340
  } catch {
2482
2341
  }
@@ -2484,7 +2343,7 @@ async function append(cwd, event) {
2484
2343
  async function readLedger(cwd = process.cwd()) {
2485
2344
  let raw;
2486
2345
  try {
2487
- raw = await fs12.readFile(ledgerPath(cwd), "utf8");
2346
+ raw = await fs11.readFile(ledgerPath(cwd), "utf8");
2488
2347
  } catch {
2489
2348
  return [];
2490
2349
  }
@@ -2689,9 +2548,9 @@ function auditGenerationPlan(input) {
2689
2548
 
2690
2549
  // src/lib/deploy.ts
2691
2550
  import crypto3 from "crypto";
2692
- import fs15 from "fs/promises";
2693
- import os8 from "os";
2694
- import path14 from "path";
2551
+ import fs14 from "fs/promises";
2552
+ import os7 from "os";
2553
+ import path13 from "path";
2695
2554
 
2696
2555
  // ../../packages/mobile-scan/src/image-dims.ts
2697
2556
  function u32be(b, o) {
@@ -2955,12 +2814,12 @@ function tierFor(estVramMb) {
2955
2814
  }
2956
2815
 
2957
2816
  // src/commands/ui.ts
2958
- import fs14 from "fs/promises";
2959
- import path13 from "path";
2817
+ import fs13 from "fs/promises";
2818
+ import path12 from "path";
2960
2819
  import { PNG as PNG2 } from "pngjs";
2961
2820
 
2962
2821
  // src/lib/png-tools.ts
2963
- import fs13 from "fs/promises";
2822
+ import fs12 from "fs/promises";
2964
2823
  import { PNG } from "pngjs";
2965
2824
  var ALPHA_TRANSPARENT_MAX = 16;
2966
2825
  var isHttpUrl = (s) => /^https?:\/\//i.test(s);
@@ -2971,12 +2830,12 @@ async function loadPng(input) {
2971
2830
  if (!res.ok) throw new Error(`Couldn't fetch ${input} (HTTP ${res.status}).`);
2972
2831
  buf = Buffer.from(await res.arrayBuffer());
2973
2832
  } else {
2974
- buf = await fs13.readFile(input);
2833
+ buf = await fs12.readFile(input);
2975
2834
  }
2976
2835
  return PNG.sync.read(buf);
2977
2836
  }
2978
2837
  async function writePng(file, png) {
2979
- await fs13.writeFile(file, PNG.sync.write(png));
2838
+ await fs12.writeFile(file, PNG.sync.write(png));
2980
2839
  }
2981
2840
  function cropPng(image, box) {
2982
2841
  const out = new PNG({ width: box.w, height: box.h });
@@ -3276,7 +3135,7 @@ async function uiExtract(opts, log) {
3276
3135
  const dilatePx = opts.dilate ?? 0;
3277
3136
  const sheet = await loadPng(input);
3278
3137
  const { width: W, height: H, data } = sheet;
3279
- await fs14.mkdir(outDir, { recursive: true });
3138
+ await fs13.mkdir(outDir, { recursive: true });
3280
3139
  log.plain(c.bold("genex ui extract"));
3281
3140
  log.dim(` ${input} (${W}x${H}) \u2192 ${outDir}, ${names.length} names`);
3282
3141
  let hasTransparency = false;
@@ -3505,7 +3364,7 @@ async function uiExtract(opts, log) {
3505
3364
  rimPixels: speckle.sampled
3506
3365
  });
3507
3366
  }
3508
- const outPath = path13.join(outDir, `${name}.png`);
3367
+ const outPath = path12.join(outDir, `${name}.png`);
3509
3368
  await writePng(outPath, out);
3510
3369
  const sidecar = {
3511
3370
  name,
@@ -3524,7 +3383,7 @@ async function uiExtract(opts, log) {
3524
3383
  defringed
3525
3384
  };
3526
3385
  const { name: _n, out: _o, ...sidecarBody } = sidecar;
3527
- await fs14.writeFile(
3386
+ await fs13.writeFile(
3528
3387
  outPath.replace(/\.png$/i, "") + ".bbox.json",
3529
3388
  JSON.stringify(sidecarBody, null, 2)
3530
3389
  );
@@ -3533,8 +3392,8 @@ async function uiExtract(opts, log) {
3533
3392
  `${name}.png ${cropW}x${cropH} (ar ${sidecar.aspectRatio}) at sheet ${padX0},${padY0}`
3534
3393
  );
3535
3394
  }
3536
- const debugPath = path13.join(outDir, "extract-debug.json");
3537
- await fs14.writeFile(
3395
+ const debugPath = path12.join(outDir, "extract-debug.json");
3396
+ await fs13.writeFile(
3538
3397
  debugPath,
3539
3398
  JSON.stringify(
3540
3399
  {
@@ -3996,7 +3855,7 @@ async function uiMasks(opts, log) {
3996
3855
  const registrationTolerance = opts.registrationTolerance ?? 0.04;
3997
3856
  const edgeFlushMax = opts.edgeFlushMax ?? 0.04;
3998
3857
  const loosened = registrationTolerance > 0.04 || edgeFlushMax > 0.04 || minCoverage < 0.01 || maxCoverage > 0.85;
3999
- await fs14.mkdir(outDir, { recursive: true });
3858
+ await fs13.mkdir(outDir, { recursive: true });
4000
3859
  log.plain(c.bold("genex ui masks"));
4001
3860
  log.dim(` ${input} (${image.width}x${image.height}), ${pairs.length} pair(s) \u2192 ${outDir}`);
4002
3861
  const sheetComponents = detectSheetComponents(image, opts.minPixels ?? 2e3);
@@ -4049,11 +3908,11 @@ async function uiMasks(opts, log) {
4049
3908
  });
4050
3909
  }
4051
3910
  const overlay = makeOverlay(clean2, converted.png);
4052
- const framePath = path13.join(outDir, `${pair.name}-frame.png`);
4053
- const maskPath = path13.join(outDir, `${pair.name}-mask.png`);
4054
- const annotatedPath = path13.join(outDir, `${pair.name}-annotated-source.png`);
4055
- const overlayPath = path13.join(outDir, `${pair.name}-overlay.png`);
4056
- const metaPath = path13.join(outDir, `${pair.name}.annotated-progress.json`);
3911
+ const framePath = path12.join(outDir, `${pair.name}-frame.png`);
3912
+ const maskPath = path12.join(outDir, `${pair.name}-mask.png`);
3913
+ const annotatedPath = path12.join(outDir, `${pair.name}-annotated-source.png`);
3914
+ const overlayPath = path12.join(outDir, `${pair.name}-overlay.png`);
3915
+ const metaPath = path12.join(outDir, `${pair.name}.annotated-progress.json`);
4057
3916
  await writePng(framePath, clean2);
4058
3917
  await writePng(maskPath, converted.png);
4059
3918
  await writePng(annotatedPath, annotated);
@@ -4100,7 +3959,7 @@ async function uiMasks(opts, log) {
4100
3959
  },
4101
3960
  overlay: overlayPath
4102
3961
  };
4103
- await fs14.writeFile(metaPath, `${JSON.stringify(meta, null, 2)}
3962
+ await fs13.writeFile(metaPath, `${JSON.stringify(meta, null, 2)}
4104
3963
  `);
4105
3964
  results.push(meta);
4106
3965
  const fb = converted.bbox;
@@ -4116,8 +3975,8 @@ async function uiMasks(opts, log) {
4116
3975
  );
4117
3976
  }
4118
3977
  }
4119
- const indexPath = path13.join(outDir, "annotated-progress.json");
4120
- await fs14.writeFile(indexPath, `${JSON.stringify({ input, loosened, pairs: results }, null, 2)}
3978
+ const indexPath = path12.join(outDir, "annotated-progress.json");
3979
+ await fs13.writeFile(indexPath, `${JSON.stringify({ input, loosened, pairs: results }, null, 2)}
4121
3980
  `);
4122
3981
  log.plain("");
4123
3982
  log.success(`Wrote ${pairs.length} frame+mask pair(s) \u2192 ${outDir} (index: ${indexPath}).`);
@@ -4243,7 +4102,7 @@ async function uiTextColor(opts, log) {
4243
4102
  };
4244
4103
  process.stdout.write(`${JSON.stringify(result, null, 2)}
4245
4104
  `);
4246
- if (opts.out) await fs14.writeFile(opts.out, `${JSON.stringify(result, null, 2)}
4105
+ if (opts.out) await fs13.writeFile(opts.out, `${JSON.stringify(result, null, 2)}
4247
4106
  `);
4248
4107
  if (opts.cropPath) log.dim(` crop \u2192 ${opts.cropPath}`);
4249
4108
  }
@@ -4275,7 +4134,7 @@ async function uiTrim(opts, log) {
4275
4134
  const sidecar = computeBBoxes(trimmed);
4276
4135
  const speckleAllowed = !!(speckle && speckle.ratio > SPECKLE_MAX_RATIO);
4277
4136
  const sidecarPath = outPath.replace(/\.png$/i, "") + ".bbox.json";
4278
- await fs14.writeFile(
4137
+ await fs13.writeFile(
4279
4138
  sidecarPath,
4280
4139
  JSON.stringify(speckleAllowed ? { ...sidecar, speckleAllowed: true } : sidecar, null, 2)
4281
4140
  );
@@ -4382,7 +4241,7 @@ async function uiPlate(opts, log) {
4382
4241
  fail("No interior found \u2014 the image is fully transparent (or erode ate everything). Check --in / lower --erode.");
4383
4242
  }
4384
4243
  await writePng(outPath, out);
4385
- const name = path13.basename(outPath);
4244
+ const name = path12.basename(outPath);
4386
4245
  log.plain(c.bold("genex ui plate"));
4387
4246
  log.success(`${outPath} ${W}x${H}, interior ${(count / (W * H) * 100).toFixed(1)}% (erode ${erode}px)`);
4388
4247
  log.dim(" Wire it as the plate's silhouette (same box as the frame <img>, plate UNDER the art):");
@@ -4547,13 +4406,13 @@ async function walkFiles(dir) {
4547
4406
  const out = [];
4548
4407
  let entries;
4549
4408
  try {
4550
- entries = await fs14.readdir(dir, { withFileTypes: true });
4409
+ entries = await fs13.readdir(dir, { withFileTypes: true });
4551
4410
  } catch {
4552
4411
  return out;
4553
4412
  }
4554
4413
  for (const entry of entries) {
4555
4414
  if (entry.name === "node_modules" || entry.name.startsWith(".")) continue;
4556
- const p = path13.join(dir, entry.name);
4415
+ const p = path12.join(dir, entry.name);
4557
4416
  if (entry.isDirectory()) out.push(...await walkFiles(p));
4558
4417
  else out.push(p);
4559
4418
  }
@@ -4562,7 +4421,7 @@ async function walkFiles(dir) {
4562
4421
  async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
4563
4422
  const viewportFindings = [];
4564
4423
  try {
4565
- const indexHtml = await fs14.readFile(path13.join(cwd, "index.html"), "utf8");
4424
+ const indexHtml = await fs13.readFile(path12.join(cwd, "index.html"), "utf8");
4566
4425
  if (!/<meta[^>]+name=["']viewport["']/i.test(indexHtml)) {
4567
4426
  viewportFindings.push({
4568
4427
  kind: "viewport-meta",
@@ -4576,28 +4435,28 @@ async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
4576
4435
  }
4577
4436
  } catch {
4578
4437
  }
4579
- const absAssets = path13.resolve(cwd, assetDir);
4438
+ const absAssets = path12.resolve(cwd, assetDir);
4580
4439
  try {
4581
- if (!(await fs14.stat(absAssets)).isDirectory()) {
4440
+ if (!(await fs13.stat(absAssets)).isDirectory()) {
4582
4441
  return viewportFindings.length > 0 ? viewportFindings : null;
4583
4442
  }
4584
4443
  } catch {
4585
4444
  return viewportFindings.length > 0 ? viewportFindings : null;
4586
4445
  }
4587
- const srcFiles = (await walkFiles(path13.resolve(cwd, srcDir))).filter(
4588
- (p) => AUDIT_SRC_EXTS.has(path13.extname(p).toLowerCase())
4446
+ const srcFiles = (await walkFiles(path12.resolve(cwd, srcDir))).filter(
4447
+ (p) => AUDIT_SRC_EXTS.has(path12.extname(p).toLowerCase())
4589
4448
  );
4590
4449
  try {
4591
- for (const name of await fs14.readdir(cwd)) {
4592
- const ext = path13.extname(name).toLowerCase();
4593
- if (ext === ".html" || ext === ".css") srcFiles.push(path13.join(cwd, name));
4450
+ for (const name of await fs13.readdir(cwd)) {
4451
+ const ext = path12.extname(name).toLowerCase();
4452
+ if (ext === ".html" || ext === ".css") srcFiles.push(path12.join(cwd, name));
4594
4453
  }
4595
4454
  } catch {
4596
4455
  }
4597
4456
  const sources = [];
4598
4457
  for (const p of srcFiles) {
4599
4458
  try {
4600
- sources.push({ rel: path13.relative(cwd, p), text: await fs14.readFile(p, "utf8") });
4459
+ sources.push({ rel: path12.relative(cwd, p), text: await fs13.readFile(p, "utf8") });
4601
4460
  } catch {
4602
4461
  }
4603
4462
  }
@@ -4607,11 +4466,11 @@ async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
4607
4466
  const metaByName = /* @__PURE__ */ new Map();
4608
4467
  const bboxByPng = /* @__PURE__ */ new Map();
4609
4468
  for (const p of assetFiles) {
4610
- const base = path13.basename(p);
4469
+ const base = path12.basename(p);
4611
4470
  const metaMatch = /^(.+)\.annotated-progress\.json$/.exec(base);
4612
4471
  if (metaMatch) {
4613
4472
  try {
4614
- const meta = JSON.parse(await fs14.readFile(p, "utf8"));
4473
+ const meta = JSON.parse(await fs13.readFile(p, "utf8"));
4615
4474
  metaByName.set(metaMatch[1], {
4616
4475
  cleanCrop: meta.clean?.crop ?? null,
4617
4476
  loosened: meta.loosened === true
@@ -4622,7 +4481,7 @@ async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
4622
4481
  }
4623
4482
  if (base.endsWith(".bbox.json")) {
4624
4483
  try {
4625
- const sidecar = JSON.parse(await fs14.readFile(p, "utf8"));
4484
+ const sidecar = JSON.parse(await fs13.readFile(p, "utf8"));
4626
4485
  if (sidecar.sheetBBox) bboxByPng.set(base.replace(/\.bbox\.json$/, ".png"), sidecar.sheetBBox);
4627
4486
  } catch {
4628
4487
  }
@@ -4631,7 +4490,7 @@ async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
4631
4490
  for (const [name, meta] of metaByName) {
4632
4491
  if (!meta.cleanCrop || !referenced(`${name}-mask.png`)) continue;
4633
4492
  for (const p of assetFiles) {
4634
- const base = path13.basename(p);
4493
+ const base = path12.basename(p);
4635
4494
  if (!base.toLowerCase().endsWith(".png")) continue;
4636
4495
  if (base !== `${name}.png` && !base.startsWith(`${name}-`)) continue;
4637
4496
  if (/-mask\.png$|-frame\.png$|-overlay\.png$|-annotated-source\.png$/.test(base)) continue;
@@ -4655,7 +4514,7 @@ async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
4655
4514
  }
4656
4515
  const pngByBase = /* @__PURE__ */ new Map();
4657
4516
  for (const p of assetFiles) {
4658
- const base = path13.basename(p);
4517
+ const base = path12.basename(p);
4659
4518
  if (base.toLowerCase().endsWith(".png")) pngByBase.set(base, p);
4660
4519
  }
4661
4520
  for (const [maskBase, maskPath] of pngByBase) {
@@ -4668,8 +4527,8 @@ async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
4668
4527
  let frame;
4669
4528
  let mask;
4670
4529
  try {
4671
- frame = PNG2.sync.read(await fs14.readFile(framePath));
4672
- mask = PNG2.sync.read(await fs14.readFile(maskPath));
4530
+ frame = PNG2.sync.read(await fs13.readFile(framePath));
4531
+ mask = PNG2.sync.read(await fs13.readFile(maskPath));
4673
4532
  } catch {
4674
4533
  continue;
4675
4534
  }
@@ -4688,7 +4547,7 @@ async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
4688
4547
  if (!referenced(base)) continue;
4689
4548
  let png;
4690
4549
  try {
4691
- png = PNG2.sync.read(await fs14.readFile(p));
4550
+ png = PNG2.sync.read(await fs13.readFile(p));
4692
4551
  } catch {
4693
4552
  continue;
4694
4553
  }
@@ -4708,7 +4567,7 @@ async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
4708
4567
  }
4709
4568
  const maskReported = /* @__PURE__ */ new Set();
4710
4569
  for (const p of assetFiles) {
4711
- const m = /^(.+)\.annotated-progress\.json$/.exec(path13.basename(p));
4570
+ const m = /^(.+)\.annotated-progress\.json$/.exec(path12.basename(p));
4712
4571
  if (!m) continue;
4713
4572
  const maskBase = `${m[1]}-mask.png`;
4714
4573
  if (!referenced(maskBase)) {
@@ -4720,14 +4579,14 @@ async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
4720
4579
  }
4721
4580
  }
4722
4581
  for (const p of assetFiles) {
4723
- const base = path13.basename(p);
4582
+ const base = path12.basename(p);
4724
4583
  if (!base.toLowerCase().endsWith(".png")) continue;
4725
4584
  if (/-annotated-source\.png$|-overlay\.png$/.test(base)) continue;
4726
4585
  if (maskReported.has(base)) continue;
4727
4586
  if (!referenced(base)) {
4728
4587
  findings.push({
4729
4588
  kind: "unwired-sprite",
4730
- 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.)`
4589
+ message: `${path12.relative(cwd, p)} is on disk but never referenced in ${srcDir}/index.html/CSS \u2014 wire it, or record the one-line reason it was cut. (Computed-string references are invisible here \u2014 check.)`
4731
4590
  });
4732
4591
  }
4733
4592
  }
@@ -4779,78 +4638,6 @@ async function uiAudit(opts, log) {
4779
4638
  if (opts.strict || hard.length > 0) process.exitCode = 1;
4780
4639
  }
4781
4640
 
4782
- // src/lib/terms.ts
4783
- import readline2 from "readline";
4784
- var TERMS_ERROR_CODE = "terms_acceptance_required";
4785
- async function isTermsRefusal(res) {
4786
- if (res.status !== 403) return false;
4787
- try {
4788
- const body = await res.clone().json();
4789
- return body?.error === TERMS_ERROR_CODE;
4790
- } catch {
4791
- return false;
4792
- }
4793
- }
4794
- function reportTermsRefusal(log, authUrl) {
4795
- log.error("Your account needs to accept the current Terms before publishing.");
4796
- log.dim(" Nothing was deployed, and nothing on your game changed.");
4797
- if (!inHostedSession()) {
4798
- log.dim(` Run ${c.cyan("npx genex accept")} to read them and agree here.`);
4799
- }
4800
- log.dim(` Open ${c.cyan(`${getAuthUrl(authUrl)}/terms`)} and agree, then try again.`);
4801
- }
4802
- function askOnStdin(question) {
4803
- const rl = readline2.createInterface({ input: process.stdin, output: process.stdout });
4804
- return new Promise((resolve) => {
4805
- rl.question(question, (answer) => {
4806
- rl.close();
4807
- resolve(answer);
4808
- });
4809
- });
4810
- }
4811
- async function runAccept(opts) {
4812
- const { token, log } = opts;
4813
- const interactive = opts.interactive ?? Boolean(process.stdin.isTTY);
4814
- const apiUrl = getApiUrl(opts.apiUrl);
4815
- const web = getAuthUrl();
4816
- const status = await apiFetch(`${apiUrl}/api/legal/status`, {
4817
- headers: { Authorization: `Bearer ${token}` }
4818
- }).catch(() => null);
4819
- if (status?.ok) {
4820
- const body = await status.json().catch(() => null);
4821
- if (body?.accepted) {
4822
- log.success("Already accepted \u2014 nothing to do.");
4823
- return true;
4824
- }
4825
- log.plain("Before publishing, please read and agree to:");
4826
- for (const doc of body?.documents ?? []) log.plain(` ${doc.title} ${c.cyan(doc.url)}`);
4827
- log.plain("");
4828
- }
4829
- if (!interactive) {
4830
- log.error("Accepting the Terms needs a person, so this cannot run unattended.");
4831
- log.dim(" Agreeing is a contract, and an agent cannot do it on your behalf.");
4832
- log.dim(` Open ${c.cyan(`${web}/terms`)} and agree there, then re-run your command.`);
4833
- return false;
4834
- }
4835
- const ask = opts.ask ?? askOnStdin;
4836
- const answer = (await ask("Type 'agree' to accept: ")).trim().toLowerCase();
4837
- if (answer !== "agree") {
4838
- log.error("Not accepted \u2014 nothing was recorded.");
4839
- return false;
4840
- }
4841
- const res = await apiFetch(`${apiUrl}/api/legal/accept`, {
4842
- method: "POST",
4843
- headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" },
4844
- body: JSON.stringify({ surface: "cli" })
4845
- }).catch(() => null);
4846
- if (!res?.ok) {
4847
- log.error(`Couldn't record your acceptance (HTTP ${res?.status ?? "no response"}).`);
4848
- return false;
4849
- }
4850
- log.success("Accepted. Re-run your command.");
4851
- return true;
4852
- }
4853
-
4854
4641
  // src/lib/deploy.ts
4855
4642
  function printMobilePreflight(files, log) {
4856
4643
  try {
@@ -4914,7 +4701,7 @@ async function printUiAuditPreflight(log) {
4914
4701
  }
4915
4702
  async function printPipelineStatePreflight(log, cwd = process.cwd()) {
4916
4703
  try {
4917
- const design = await fs15.readFile(path14.join(cwd, "DESIGN.md"), "utf8").catch(() => "");
4704
+ const design = await fs14.readFile(path13.join(cwd, "DESIGN.md"), "utf8").catch(() => "");
4918
4705
  const warnings = [];
4919
4706
  if (design.length > 0 && !/##\s*build plan & status/i.test(design)) {
4920
4707
  warnings.push(
@@ -4922,7 +4709,7 @@ async function printPipelineStatePreflight(log, cwd = process.cwd()) {
4922
4709
  );
4923
4710
  }
4924
4711
  if (!/player character:/i.test(design)) {
4925
- const hasCharacter = await fs15.access(path14.join(cwd, "public", "assets", "meshy-character.json")).then(() => true, () => false);
4712
+ const hasCharacter = await fs14.access(path13.join(cwd, "public", "assets", "meshy-character.json")).then(() => true, () => false);
4926
4713
  if (!hasCharacter && await loadsPlayerBody(cwd)) {
4927
4714
  warnings.push(
4928
4715
  `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).`
@@ -4937,12 +4724,12 @@ async function printPipelineStatePreflight(log, cwd = process.cwd()) {
4937
4724
  async function loadsPlayerBody(cwd) {
4938
4725
  const BODY_LOADERS = /\bloadPlayerCharacter\s*\(|\bloadVrm(?:Clone)?\s*\(/;
4939
4726
  try {
4940
- const entries = await fs15.readdir(path14.join(cwd, "src"), { recursive: true });
4727
+ const entries = await fs14.readdir(path13.join(cwd, "src"), { recursive: true });
4941
4728
  for (const rel of entries) {
4942
4729
  if (rel.includes("node_modules")) continue;
4943
- if (rel.split(path14.sep)[0] === "controllers" || rel.startsWith("controllers/")) continue;
4730
+ if (rel.split(path13.sep)[0] === "controllers" || rel.startsWith("controllers/")) continue;
4944
4731
  if (!/\.(ts|tsx|js|jsx)$/.test(rel)) continue;
4945
- const text = await fs15.readFile(path14.join(cwd, "src", rel), "utf8").catch(() => "");
4732
+ const text = await fs14.readFile(path13.join(cwd, "src", rel), "utf8").catch(() => "");
4946
4733
  if (BODY_LOADERS.test(text)) return true;
4947
4734
  }
4948
4735
  } catch {
@@ -4979,9 +4766,9 @@ async function deployGame(ctx, opts, log) {
4979
4766
  }
4980
4767
  log.success("Built.");
4981
4768
  }
4982
- const distDir = path14.join(cwd, "dist");
4769
+ const distDir = path13.join(cwd, "dist");
4983
4770
  const siteDir = await isDir(distDir) ? distDir : cwd;
4984
- const rel = path14.relative(cwd, siteDir) || ".";
4771
+ const rel = path13.relative(cwd, siteDir) || ".";
4985
4772
  if (siteDir === cwd) await writeGitignore(cwd, log);
4986
4773
  const files = await collectFiles(siteDir);
4987
4774
  if (files.length === 0) {
@@ -5070,7 +4857,7 @@ async function deployGame(ctx, opts, log) {
5070
4857
  }
5071
4858
  async function hasBuildScript(cwd) {
5072
4859
  try {
5073
- const pkg = JSON.parse(await fs15.readFile(path14.join(cwd, "package.json"), "utf8"));
4860
+ const pkg = JSON.parse(await fs14.readFile(path13.join(cwd, "package.json"), "utf8"));
5074
4861
  return Boolean(pkg.scripts?.build);
5075
4862
  } catch {
5076
4863
  return false;
@@ -5079,12 +4866,12 @@ async function hasBuildScript(cwd) {
5079
4866
  async function collectFiles(root) {
5080
4867
  const out = [];
5081
4868
  const walk2 = async (dir, prefix) => {
5082
- for (const e of await fs15.readdir(dir, { withFileTypes: true })) {
4869
+ for (const e of await fs14.readdir(dir, { withFileTypes: true })) {
5083
4870
  const relPath = prefix ? `${prefix}/${e.name}` : e.name;
5084
4871
  if (e.isDirectory()) {
5085
- if (!EXCLUDE_DIRS.has(e.name)) await walk2(path14.join(dir, e.name), relPath);
4872
+ if (!EXCLUDE_DIRS.has(e.name)) await walk2(path13.join(dir, e.name), relPath);
5086
4873
  } else if (e.isFile() && !isSecretEnvFile(e.name)) {
5087
- out.push({ relPath, bytes: await fs15.readFile(path14.join(dir, e.name)) });
4874
+ out.push({ relPath, bytes: await fs14.readFile(path13.join(dir, e.name)) });
5088
4875
  }
5089
4876
  }
5090
4877
  };
@@ -5337,7 +5124,7 @@ async function pushWorktree(cwd, pushUrl, managed, log, branch = "main", onCommi
5337
5124
  log.error("Couldn't save your game's source \u2014 please try again.");
5338
5125
  return false;
5339
5126
  };
5340
- const gitDir = await fs15.mkdtemp(path14.join(os8.tmpdir(), "genex-source-"));
5127
+ const gitDir = await fs14.mkdtemp(path13.join(os7.tmpdir(), "genex-source-"));
5341
5128
  const base = { GIT_DIR: gitDir };
5342
5129
  if (urlHasEmbeddedCredentials(pushUrl)) {
5343
5130
  base.GIT_CONFIG_COUNT = "1";
@@ -5352,12 +5139,12 @@ async function pushWorktree(cwd, pushUrl, managed, log, branch = "main", onCommi
5352
5139
  };
5353
5140
  try {
5354
5141
  if ((await run("git", ["init", "-q"], base)).code !== 0) return failed();
5355
- await fs15.writeFile(
5356
- path14.join(gitDir, "info", "exclude"),
5142
+ await fs14.writeFile(
5143
+ path13.join(gitDir, "info", "exclude"),
5357
5144
  // .env* are secrets — never publish them; `!` keeps the non-secret template.
5358
5145
  ["node_modules/", "dist/", ".git/", ".genex/", ".env", ".env.*", "!.env.example", ""].join("\n")
5359
5146
  );
5360
- const env = { ...base, GIT_WORK_TREE: cwd, GIT_INDEX_FILE: path14.join(gitDir, "index-source") };
5147
+ const env = { ...base, GIT_WORK_TREE: cwd, GIT_INDEX_FILE: path13.join(gitDir, "index-source") };
5361
5148
  let lfs = (await run("git", ["lfs", "version"], base)).code !== 0 ? false : true;
5362
5149
  if (!lfs) {
5363
5150
  log.step("Installing git-lfs (keeps large binary assets out of the source push)\u2026");
@@ -5409,7 +5196,7 @@ async function pushWorktree(cwd, pushUrl, managed, log, branch = "main", onCommi
5409
5196
  } catch {
5410
5197
  return failed();
5411
5198
  } finally {
5412
- await fs15.rm(gitDir, { recursive: true, force: true }).catch(() => {
5199
+ await fs14.rm(gitDir, { recursive: true, force: true }).catch(() => {
5413
5200
  });
5414
5201
  }
5415
5202
  }
@@ -5445,7 +5232,7 @@ async function fetchPushUrl(ctx, log, expectStagingCommit) {
5445
5232
  return null;
5446
5233
  }
5447
5234
  if (await isTermsRefusal(res)) {
5448
- reportTermsRefusal(log);
5235
+ if (!printedStructuredError(res)) reportTermsRefusal(log);
5449
5236
  return null;
5450
5237
  }
5451
5238
  if (!res.ok) {
@@ -5463,7 +5250,7 @@ async function fetchPushUrl(ctx, log, expectStagingCommit) {
5463
5250
  }
5464
5251
  async function isDir(p) {
5465
5252
  try {
5466
- return (await fs15.stat(p)).isDirectory();
5253
+ return (await fs14.stat(p)).isDirectory();
5467
5254
  } catch {
5468
5255
  return false;
5469
5256
  }
@@ -5945,11 +5732,11 @@ async function promoteBuild(apiUrl, projectId, token, log) {
5945
5732
  }
5946
5733
 
5947
5734
  // src/lib/detect-features.ts
5948
- import fs16 from "fs/promises";
5949
- import path15 from "path";
5735
+ import fs15 from "fs/promises";
5736
+ import path14 from "path";
5950
5737
  async function detectEmbedSdkVersion(cwd = process.cwd()) {
5951
5738
  try {
5952
- const raw = await fs16.readFile(path15.join(cwd, "package.json"), "utf8");
5739
+ const raw = await fs15.readFile(path14.join(cwd, "package.json"), "utf8");
5953
5740
  const pkg = JSON.parse(raw);
5954
5741
  const version = pkg.dependencies?.["@genex-ai/embed-sdk"] ?? pkg.devDependencies?.["@genex-ai/embed-sdk"];
5955
5742
  return typeof version === "string" && version ? version : null;
@@ -5959,7 +5746,7 @@ async function detectEmbedSdkVersion(cwd = process.cwd()) {
5959
5746
  }
5960
5747
  async function detectMultiplayer(cwd = process.cwd()) {
5961
5748
  try {
5962
- const raw = await fs16.readFile(path15.join(cwd, "package.json"), "utf8");
5749
+ const raw = await fs15.readFile(path14.join(cwd, "package.json"), "utf8");
5963
5750
  const pkg = JSON.parse(raw);
5964
5751
  return Boolean(
5965
5752
  pkg.dependencies?.["@genex-ai/multiplayer"] ?? pkg.devDependencies?.["@genex-ai/multiplayer"]
@@ -5971,7 +5758,7 @@ async function detectMultiplayer(cwd = process.cwd()) {
5971
5758
  async function detectMatchmaking(log, cwd = process.cwd()) {
5972
5759
  let pkg;
5973
5760
  try {
5974
- pkg = JSON.parse(await fs16.readFile(path15.join(cwd, "package.json"), "utf8"));
5761
+ pkg = JSON.parse(await fs15.readFile(path14.join(cwd, "package.json"), "utf8"));
5975
5762
  } catch (err) {
5976
5763
  log.dim(` (skipping matchmaking \u2014 couldn't read package.json: ${String(err)})`);
5977
5764
  return null;
@@ -5989,15 +5776,15 @@ async function detectMatchmaking(log, cwd = process.cwd()) {
5989
5776
  var TOUCH_KIT_MARKERS = /controllers\/touch\/|controllers\/character\/touch-joystick|TouchJoystick|VirtualButton|DragZone|RotateOverlay/;
5990
5777
  async function detectMobileControls(cwd = process.cwd()) {
5991
5778
  try {
5992
- const raw = await fs16.readFile(path15.join(cwd, "package.json"), "utf8");
5779
+ const raw = await fs15.readFile(path14.join(cwd, "package.json"), "utf8");
5993
5780
  const pkg = JSON.parse(raw);
5994
5781
  if (pkg.genex?.mobileControls === true) return true;
5995
5782
  } catch {
5996
5783
  }
5997
- const srcDir = path15.join(cwd, "src");
5784
+ const srcDir = path14.join(cwd, "src");
5998
5785
  let entries;
5999
5786
  try {
6000
- entries = await fs16.readdir(srcDir, { recursive: true });
5787
+ entries = await fs15.readdir(srcDir, { recursive: true });
6001
5788
  } catch {
6002
5789
  return false;
6003
5790
  }
@@ -6005,7 +5792,7 @@ async function detectMobileControls(cwd = process.cwd()) {
6005
5792
  if (rel.includes("node_modules")) continue;
6006
5793
  if (!/\.(ts|js|mts|mjs|tsx|jsx)$/.test(rel)) continue;
6007
5794
  try {
6008
- const content = await fs16.readFile(path15.join(srcDir, rel), "utf8");
5795
+ const content = await fs15.readFile(path14.join(srcDir, rel), "utf8");
6009
5796
  if (TOUCH_KIT_MARKERS.test(content)) return true;
6010
5797
  } catch {
6011
5798
  }
@@ -6014,10 +5801,10 @@ async function detectMobileControls(cwd = process.cwd()) {
6014
5801
  }
6015
5802
  var GAME_STATE_CALLS = /savePlayerState|saveWorldState|submitScore/;
6016
5803
  async function detectGameStateUsage(cwd = process.cwd()) {
6017
- const srcDir = path15.join(cwd, "src");
5804
+ const srcDir = path14.join(cwd, "src");
6018
5805
  let entries;
6019
5806
  try {
6020
- entries = await fs16.readdir(srcDir, { recursive: true });
5807
+ entries = await fs15.readdir(srcDir, { recursive: true });
6021
5808
  } catch {
6022
5809
  return false;
6023
5810
  }
@@ -6025,7 +5812,7 @@ async function detectGameStateUsage(cwd = process.cwd()) {
6025
5812
  if (rel.includes("node_modules")) continue;
6026
5813
  if (!/\.(ts|js|mts|mjs|tsx|jsx)$/.test(rel)) continue;
6027
5814
  try {
6028
- const content = await fs16.readFile(path15.join(srcDir, rel), "utf8");
5815
+ const content = await fs15.readFile(path14.join(srcDir, rel), "utf8");
6029
5816
  if (GAME_STATE_CALLS.test(content)) return true;
6030
5817
  } catch {
6031
5818
  }
@@ -6076,19 +5863,19 @@ async function detectSurfaceScan(cwd = process.cwd()) {
6076
5863
  deferredAudioContext: [],
6077
5864
  usesThree: false
6078
5865
  };
6079
- const srcDir = path15.join(cwd, "src");
5866
+ const srcDir = path14.join(cwd, "src");
6080
5867
  let entries;
6081
5868
  try {
6082
- entries = await fs16.readdir(srcDir, { recursive: true });
5869
+ entries = await fs15.readdir(srcDir, { recursive: true });
6083
5870
  } catch {
6084
5871
  return found;
6085
5872
  }
6086
5873
  for (const nativeRel of entries) {
6087
5874
  if (nativeRel.includes("node_modules")) continue;
6088
5875
  if (!/\.(ts|js|mts|mjs|tsx|jsx)$/.test(nativeRel)) continue;
6089
- const raw = await fs16.readFile(path15.join(srcDir, nativeRel), "utf8").catch(() => "");
5876
+ const raw = await fs15.readFile(path14.join(srcDir, nativeRel), "utf8").catch(() => "");
6090
5877
  if (!raw) continue;
6091
- const rel = nativeRel.split(path15.sep).join("/");
5878
+ const rel = nativeRel.split(path14.sep).join("/");
6092
5879
  const content = raw.replace(/\/\*[\s\S]*?\*\//g, (m2) => m2.replace(/[^\n]/g, " ")).replace(/(^|[^:])\/\/[^\n]*/gm, (m2, p1) => p1 + " ".repeat(m2.length - p1.length));
6093
5880
  const repeatRe = /\.repeat\.set\(\s*(-?\d+(?:\.\d+)?)\s*,\s*(-?\d+(?:\.\d+)?)\s*\)/g;
6094
5881
  let m;
@@ -6169,25 +5956,25 @@ async function detectGenerationAudit(cwd = process.cwd()) {
6169
5956
  let haystack = "";
6170
5957
  const read = async (file) => {
6171
5958
  try {
6172
- haystack += await fs16.readFile(file, "utf8");
5959
+ haystack += await fs15.readFile(file, "utf8");
6173
5960
  } catch {
6174
5961
  }
6175
5962
  };
6176
5963
  try {
6177
- for (const entry of await fs16.readdir(cwd, { withFileTypes: true })) {
5964
+ for (const entry of await fs15.readdir(cwd, { withFileTypes: true })) {
6178
5965
  if (entry.isFile() && /\.(ts|tsx|js|jsx|css|html|json)$/.test(entry.name)) {
6179
- await read(path15.join(cwd, entry.name));
5966
+ await read(path14.join(cwd, entry.name));
6180
5967
  }
6181
5968
  }
6182
5969
  } catch {
6183
5970
  }
6184
5971
  for (const sub of ["src", "public"]) {
6185
5972
  try {
6186
- const entries = await fs16.readdir(path15.join(cwd, sub), { recursive: true });
5973
+ const entries = await fs15.readdir(path14.join(cwd, sub), { recursive: true });
6187
5974
  for (const rel of entries) {
6188
5975
  if (rel.includes("node_modules")) continue;
6189
5976
  if (!/\.(ts|tsx|js|jsx|css|html|json|txt)$/.test(rel)) continue;
6190
- await read(path15.join(cwd, sub, rel));
5977
+ await read(path14.join(cwd, sub, rel));
6191
5978
  }
6192
5979
  } catch {
6193
5980
  }
@@ -6334,7 +6121,7 @@ async function borrowEvidence(meta, cwd) {
6334
6121
  } catch {
6335
6122
  return true;
6336
6123
  }
6337
- const gitConfig = await fs16.readFile(path15.join(cwd, ".git", "config"), "utf8").catch(() => "");
6124
+ const gitConfig = await fs15.readFile(path14.join(cwd, ".git", "config"), "utf8").catch(() => "");
6338
6125
  for (const m of gitConfig.matchAll(/url\s*=\s*(\S+)/g)) {
6339
6126
  try {
6340
6127
  const u = new URL(m[1]);
@@ -6342,7 +6129,7 @@ async function borrowEvidence(meta, cwd) {
6342
6129
  } catch {
6343
6130
  }
6344
6131
  }
6345
- const readme = await fs16.readFile(path15.join(cwd, "README.md"), "utf8").catch(() => "");
6132
+ const readme = await fs15.readFile(path14.join(cwd, "README.md"), "utf8").catch(() => "");
6346
6133
  return readme.includes(host) || /\b(upstream|originally by|ported from|borrowed from)\b/i.test(readme);
6347
6134
  }
6348
6135
 
@@ -6604,11 +6391,12 @@ async function runAcceptCommand(opts) {
6604
6391
  process.exitCode = 1;
6605
6392
  return;
6606
6393
  }
6607
- const meta = await readProject();
6394
+ const [meta, workspace] = await Promise.all([readProject(), readWorkspace()]);
6395
+ const apiUrl = meta?.apiUrl ?? workspace?.apiUrl;
6608
6396
  const ok = await runAccept({
6609
6397
  token,
6610
6398
  log,
6611
- ...meta?.apiUrl ? { apiUrl: meta.apiUrl } : {}
6399
+ ...apiUrl ? { apiUrl } : {}
6612
6400
  });
6613
6401
  if (!ok) process.exitCode = 1;
6614
6402
  }
@@ -6822,8 +6610,8 @@ async function runRollback(opts) {
6822
6610
  }
6823
6611
 
6824
6612
  // src/commands/generate.ts
6825
- import fs18 from "fs/promises";
6826
- import path17 from "path";
6613
+ import fs17 from "fs/promises";
6614
+ import path16 from "path";
6827
6615
  import { PNG as PNG4 } from "pngjs";
6828
6616
 
6829
6617
  // src/lib/glass.ts
@@ -6904,8 +6692,8 @@ function solveMagentaGlass(source) {
6904
6692
  }
6905
6693
 
6906
6694
  // src/lib/download-assets.ts
6907
- import fs17 from "fs/promises";
6908
- import path16 from "path";
6695
+ import fs16 from "fs/promises";
6696
+ import path15 from "path";
6909
6697
  var RUNG_ROLE = /@\d+$/;
6910
6698
  function isPrimaryRole(role) {
6911
6699
  return !RUNG_ROLE.test(role);
@@ -6925,7 +6713,7 @@ async function downloadAssets(files, opts) {
6925
6713
  const result = { saved: [], failures: [] };
6926
6714
  if (wanted.length === 0) return result;
6927
6715
  try {
6928
- await fs17.mkdir(opts.outDir, { recursive: true });
6716
+ await fs16.mkdir(opts.outDir, { recursive: true });
6929
6717
  } catch (err) {
6930
6718
  result.failures.push(
6931
6719
  `couldn't create ${opts.outDir} (${err instanceof Error ? err.message : String(err)})`
@@ -6934,12 +6722,12 @@ async function downloadAssets(files, opts) {
6934
6722
  }
6935
6723
  const multi = wanted.length > 1;
6936
6724
  for (const file of wanted) {
6937
- const dest = path16.join(opts.outDir, localAssetName(opts.kind, opts.prompt, opts.id, file, multi));
6725
+ const dest = path15.join(opts.outDir, localAssetName(opts.kind, opts.prompt, opts.id, file, multi));
6938
6726
  try {
6939
6727
  const res = await fetch(file.url);
6940
6728
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
6941
6729
  const buf = Buffer.from(await res.arrayBuffer());
6942
- await fs17.writeFile(dest, buf);
6730
+ await fs16.writeFile(dest, buf);
6943
6731
  result.saved.push({ role: file.role, path: dest, url: file.url, bytes: buf.byteLength });
6944
6732
  } catch (err) {
6945
6733
  result.failures.push(
@@ -6994,7 +6782,7 @@ function mockLaneRefusal(kind, provider) {
6994
6782
  }
6995
6783
 
6996
6784
  // src/lib/open.ts
6997
- import { spawn as spawn4 } from "child_process";
6785
+ import { spawn as spawn3 } from "child_process";
6998
6786
  function tokenize(cmd) {
6999
6787
  return cmd.trim().split(/\s+/).filter(Boolean);
7000
6788
  }
@@ -7023,7 +6811,7 @@ function openUrl(url) {
7023
6811
  }
7024
6812
  }
7025
6813
  try {
7026
- const child = spawn4(command, args, { stdio: "ignore", detached: true });
6814
+ const child = spawn3(command, args, { stdio: "ignore", detached: true });
7027
6815
  child.on("error", () => {
7028
6816
  });
7029
6817
  child.unref();
@@ -7083,7 +6871,7 @@ var isRemoteRef = (value) => /^(https?:\/\/|data:)/i.test(value);
7083
6871
  async function inlineLocalImage(filePath, flag) {
7084
6872
  let bytes;
7085
6873
  try {
7086
- bytes = await fs18.readFile(filePath);
6874
+ bytes = await fs17.readFile(filePath);
7087
6875
  } catch {
7088
6876
  return { ok: false, error: `Couldn't read the ${flag} file at ${filePath}.` };
7089
6877
  }
@@ -7093,7 +6881,7 @@ async function inlineLocalImage(filePath, flag) {
7093
6881
  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.`
7094
6882
  };
7095
6883
  }
7096
- const mime = IMAGE_MIME_BY_EXT[path17.extname(filePath).toLowerCase()] ?? "image/png";
6884
+ const mime = IMAGE_MIME_BY_EXT[path16.extname(filePath).toLowerCase()] ?? "image/png";
7097
6885
  return { ok: true, dataUri: `data:${mime};base64,${bytes.toString("base64")}` };
7098
6886
  }
7099
6887
  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.";
@@ -7251,7 +7039,7 @@ async function runGenerate(kind, opts) {
7251
7039
  let typedPrompt = opts.prompt?.trim();
7252
7040
  if (!typedPrompt && kind === "model" && opts.imageUrl) {
7253
7041
  const ref = opts.imageUrl.startsWith("data:") ? "local image" : opts.imageUrl;
7254
- typedPrompt = `from image: ${path17.basename(ref).slice(0, 120)}`;
7042
+ typedPrompt = `from image: ${path16.basename(ref).slice(0, 120)}`;
7255
7043
  }
7256
7044
  if (!typedPrompt) {
7257
7045
  log.error(`Missing prompt. Usage: ${c.cyan(`genex ${kind} "<prompt>"`)}`);
@@ -7295,7 +7083,7 @@ async function runGenerate(kind, opts) {
7295
7083
  return;
7296
7084
  }
7297
7085
  try {
7298
- const bytes = await fs18.readFile(opts.inpaintUrl);
7086
+ const bytes = await fs17.readFile(opts.inpaintUrl);
7299
7087
  opts = { ...opts, inpaintUrl: `data:image/png;base64,${bytes.toString("base64")}` };
7300
7088
  } catch {
7301
7089
  log.error(`Couldn't read the --inpaint mask at ${opts.inpaintUrl}.`);
@@ -7480,7 +7268,7 @@ async function reportGlassTerminal(view, outDir, log, json) {
7480
7268
  return;
7481
7269
  }
7482
7270
  await recordTerminal(view.id, "completed", files.map((f) => f.url));
7483
- await fs18.mkdir(outDir, { recursive: true });
7271
+ await fs17.mkdir(outDir, { recursive: true });
7484
7272
  const solved = [];
7485
7273
  for (let i = 0; i < files.length; i++) {
7486
7274
  const f = files[i];
@@ -7512,8 +7300,8 @@ async function reportGlassTerminal(view, outDir, log, json) {
7512
7300
  });
7513
7301
  continue;
7514
7302
  }
7515
- const outPath = path17.join(outDir, `glass-${i + 1}.png`);
7516
- await fs18.writeFile(outPath, PNG4.sync.write(r.png));
7303
+ const outPath = path16.join(outDir, `glass-${i + 1}.png`);
7304
+ await fs17.writeFile(outPath, PNG4.sync.write(r.png));
7517
7305
  solved.push({
7518
7306
  path: outPath,
7519
7307
  url: f.url,
@@ -8279,8 +8067,8 @@ async function toRow(e, v, cwd) {
8279
8067
  }
8280
8068
 
8281
8069
  // src/commands/controller.ts
8282
- import fs20 from "fs/promises";
8283
- import path19 from "path";
8070
+ import fs19 from "fs/promises";
8071
+ import path18 from "path";
8284
8072
 
8285
8073
  // ../../packages/meshy-animation-catalog/src/index.ts
8286
8074
  import { createHash } from "crypto";
@@ -17410,9 +17198,9 @@ function searchMeshyAnimations(query, options = {}) {
17410
17198
  }
17411
17199
 
17412
17200
  // src/lib/anims.ts
17413
- import fs19 from "fs/promises";
17414
- import path18 from "path";
17415
- var ANIMS_DEST = path18.join("public", "assets", "anims");
17201
+ import fs18 from "fs/promises";
17202
+ import path17 from "path";
17203
+ var ANIMS_DEST = path17.join("public", "assets", "anims");
17416
17204
  var HIDDEN_TAG = "reference";
17417
17205
  async function runAnims(opts) {
17418
17206
  const log = createLogger({ quiet: opts.quiet });
@@ -17428,7 +17216,7 @@ async function runAnims(opts) {
17428
17216
  printCatalog(log, manifest, selectors);
17429
17217
  return;
17430
17218
  }
17431
- const controllerMarker = path18.join(root, "src", "controllers", "character");
17219
+ const controllerMarker = path17.join(root, "src", "controllers", "character");
17432
17220
  if (!await exists2(controllerMarker)) {
17433
17221
  log.error(
17434
17222
  `No character controller in this game (missing ${c.cyan("src/controllers/character/")}).`
@@ -17437,11 +17225,11 @@ async function runAnims(opts) {
17437
17225
  process.exitCode = 1;
17438
17226
  return;
17439
17227
  }
17440
- const destDir = path18.join(root, ANIMS_DEST);
17441
- const gameManifestPath = path18.join(destDir, "manifest.json");
17228
+ const destDir = path17.join(root, ANIMS_DEST);
17229
+ const gameManifestPath = path17.join(destDir, "manifest.json");
17442
17230
  if (opts.reset) {
17443
- await fs19.rm(destDir, { recursive: true, force: true });
17444
- log.step(`Cleared ${c.cyan(ANIMS_DEST + path18.sep)} (--reset)`);
17231
+ await fs18.rm(destDir, { recursive: true, force: true });
17232
+ log.step(`Cleared ${c.cyan(ANIMS_DEST + path17.sep)} (--reset)`);
17445
17233
  }
17446
17234
  if (selectors.length === 0) {
17447
17235
  const installed = await readGameManifest(gameManifestPath);
@@ -17479,35 +17267,35 @@ async function runAnims(opts) {
17479
17267
  }
17480
17268
  }
17481
17269
  const wanted = [...byName.values()].sort((a, b) => a.name.localeCompare(b.name));
17482
- const cacheDir = path18.join(
17270
+ const cacheDir = path17.join(
17483
17271
  opts.cacheDir ?? getAnimsCacheDir(),
17484
17272
  `${manifest.library}-v${manifest.version}`
17485
17273
  );
17486
- await fs19.mkdir(cacheDir, { recursive: true });
17487
- await fs19.mkdir(destDir, { recursive: true });
17274
+ await fs18.mkdir(cacheDir, { recursive: true });
17275
+ await fs18.mkdir(destDir, { recursive: true });
17488
17276
  const base = getAnimsBase(opts.animsBase);
17489
17277
  let installedCount = 0;
17490
17278
  let presentCount = 0;
17491
17279
  let addedBytes = 0;
17492
17280
  const failures = [];
17493
17281
  for (const entry of wanted) {
17494
- const dest = path18.join(destDir, entry.file);
17282
+ const dest = path17.join(destDir, entry.file);
17495
17283
  if (await hasSize(dest, entry.bytes)) {
17496
17284
  presentCount++;
17497
17285
  continue;
17498
17286
  }
17499
17287
  try {
17500
- const cached = path18.join(cacheDir, entry.file);
17288
+ const cached = path17.join(cacheDir, entry.file);
17501
17289
  if (!await hasSize(cached, entry.bytes)) {
17502
17290
  const res = await fetch(base + entry.file);
17503
17291
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
17504
17292
  const buf = Buffer.from(await res.arrayBuffer());
17505
- await fs19.writeFile(cached, buf);
17293
+ await fs18.writeFile(cached, buf);
17506
17294
  }
17507
- await fs19.copyFile(cached, dest);
17295
+ await fs18.copyFile(cached, dest);
17508
17296
  installedCount++;
17509
17297
  addedBytes += entry.bytes;
17510
- log.dim(` ${path18.join(ANIMS_DEST, entry.file)} (${formatMb(entry.bytes)})`);
17298
+ log.dim(` ${path17.join(ANIMS_DEST, entry.file)} (${formatMb(entry.bytes)})`);
17511
17299
  } catch (err) {
17512
17300
  failures.push(`${entry.name} (${err instanceof Error ? err.message : String(err)})`);
17513
17301
  }
@@ -17523,13 +17311,13 @@ async function runAnims(opts) {
17523
17311
  version: manifest.version,
17524
17312
  clips: [...union].sort((a, b) => a.localeCompare(b))
17525
17313
  };
17526
- await fs19.writeFile(gameManifestPath, JSON.stringify(gameManifest, null, 2) + "\n");
17314
+ await fs18.writeFile(gameManifestPath, JSON.stringify(gameManifest, null, 2) + "\n");
17527
17315
  log.plain("");
17528
17316
  const parts = [`${installedCount} clip${installedCount === 1 ? "" : "s"} installed`];
17529
17317
  if (presentCount > 0) parts.push(`${presentCount} already present`);
17530
17318
  if (bundledSkips > 0) parts.push(`${bundledSkips} already bundled in animation-library.glb`);
17531
17319
  log.success(
17532
- `${parts.join(", ")} \u2192 ${c.cyan(ANIMS_DEST + path18.sep)}${addedBytes > 0 ? ` (+${formatMb(addedBytes)})` : ""}`
17320
+ `${parts.join(", ")} \u2192 ${c.cyan(ANIMS_DEST + path17.sep)}${addedBytes > 0 ? ` (+${formatMb(addedBytes)})` : ""}`
17533
17321
  );
17534
17322
  for (const [selector, entries] of resolved) {
17535
17323
  const names = entries.filter((e) => !coreNames.has(e.name)).map((e) => e.name);
@@ -17561,8 +17349,8 @@ async function loadManifest(baseOverride) {
17561
17349
  }
17562
17350
  } catch {
17563
17351
  }
17564
- const snapshotPath = path18.join(getTemplatesDir(), "controllers", "assets", "anims-manifest.json");
17565
- const manifest = JSON.parse(await fs19.readFile(snapshotPath, "utf8"));
17352
+ const snapshotPath = path17.join(getTemplatesDir(), "controllers", "assets", "anims-manifest.json");
17353
+ const manifest = JSON.parse(await fs18.readFile(snapshotPath, "utf8"));
17566
17354
  return { manifest, source: "snapshot" };
17567
17355
  }
17568
17356
  function resolveSelectors(manifest, selectors) {
@@ -17680,21 +17468,21 @@ function printCatalog(log, manifest, selectors) {
17680
17468
  }
17681
17469
  async function readGameManifest(file) {
17682
17470
  try {
17683
- return JSON.parse(await fs19.readFile(file, "utf8"));
17471
+ return JSON.parse(await fs18.readFile(file, "utf8"));
17684
17472
  } catch {
17685
17473
  return null;
17686
17474
  }
17687
17475
  }
17688
17476
  async function hasSize(file, bytes) {
17689
17477
  try {
17690
- return (await fs19.stat(file)).size === bytes;
17478
+ return (await fs18.stat(file)).size === bytes;
17691
17479
  } catch {
17692
17480
  return false;
17693
17481
  }
17694
17482
  }
17695
17483
  async function exists2(p) {
17696
17484
  try {
17697
- await fs19.access(p);
17485
+ await fs18.access(p);
17698
17486
  return true;
17699
17487
  } catch {
17700
17488
  return false;
@@ -17888,8 +17676,8 @@ var CONTROLLER_FILE_SETS = {
17888
17676
  ]
17889
17677
  }
17890
17678
  };
17891
- var CODE_DEST = path19.join("src", "controllers");
17892
- var ASSETS_DEST = path19.join("public", "assets");
17679
+ var CODE_DEST = path18.join("src", "controllers");
17680
+ var ASSETS_DEST = path18.join("public", "assets");
17893
17681
  async function runController(opts) {
17894
17682
  const log = createLogger({ quiet: opts.quiet });
17895
17683
  if (opts.kind?.trim() === "anims") {
@@ -17906,31 +17694,31 @@ async function runController(opts) {
17906
17694
  process.exitCode = 1;
17907
17695
  return;
17908
17696
  }
17909
- const srcDir = path19.join(getTemplatesDir(), "controllers");
17697
+ const srcDir = path18.join(getTemplatesDir(), "controllers");
17910
17698
  const root = opts.cwd ?? process.cwd();
17911
17699
  const set = CONTROLLER_FILE_SETS[kind];
17912
17700
  log.plain(c.bold(`genex controller ${kind}`));
17913
17701
  log.plain("");
17914
- log.step(`Installing the ${kind} controller into ${c.cyan(CODE_DEST + path19.sep)}`);
17702
+ log.step(`Installing the ${kind} controller into ${c.cyan(CODE_DEST + path18.sep)}`);
17915
17703
  const plan = [
17916
- ...set.code.map((rel) => ({ from: rel, rel: path19.join(CODE_DEST, rel) })),
17704
+ ...set.code.map((rel) => ({ from: rel, rel: path18.join(CODE_DEST, rel) })),
17917
17705
  ...set.assets.map((rel) => ({
17918
17706
  from: rel,
17919
- rel: path19.join(ASSETS_DEST, path19.basename(rel))
17707
+ rel: path18.join(ASSETS_DEST, path18.basename(rel))
17920
17708
  }))
17921
17709
  ];
17922
17710
  let copied = 0;
17923
17711
  let skipped = 0;
17924
17712
  try {
17925
17713
  for (const file of plan) {
17926
- const dest = path19.join(root, file.rel);
17714
+ const dest = path18.join(root, file.rel);
17927
17715
  if (!opts.force && await exists3(dest)) {
17928
17716
  skipped++;
17929
17717
  log.dim(` skipped ${file.rel} (exists \u2014 use --force to overwrite)`);
17930
17718
  continue;
17931
17719
  }
17932
- await fs20.mkdir(path19.dirname(dest), { recursive: true });
17933
- await fs20.copyFile(path19.join(srcDir, file.from), dest);
17720
+ await fs19.mkdir(path18.dirname(dest), { recursive: true });
17721
+ await fs19.copyFile(path18.join(srcDir, file.from), dest);
17934
17722
  copied++;
17935
17723
  log.dim(` ${file.rel}`);
17936
17724
  }
@@ -17983,7 +17771,7 @@ async function runController(opts) {
17983
17771
  for (const line of set.sketch) {
17984
17772
  log.dim(` ${line}`);
17985
17773
  }
17986
- if (kind === "character" && !await exists3(path19.join(root, ASSETS_DEST, "meshy-character.json"))) {
17774
+ if (kind === "character" && !await exists3(path18.join(root, ASSETS_DEST, "meshy-character.json"))) {
17987
17775
  log.plain("");
17988
17776
  log.plain(
17989
17777
  ` ${stepOffset + 3}. This game has no generated character yet \u2014 ${c.cyan(
@@ -18015,9 +17803,9 @@ async function installMeshyCharacterManifest(args) {
18015
17803
  throw new Error("The API returned an invalid Meshy character manifest.");
18016
17804
  }
18017
17805
  assertCompleteMeshyControllerPack(manifest);
18018
- const destination = path19.join(args.root, ASSETS_DEST, "meshy-character.json");
18019
- await fs20.mkdir(path19.dirname(destination), { recursive: true });
18020
- await fs20.writeFile(
17806
+ const destination = path18.join(args.root, ASSETS_DEST, "meshy-character.json");
17807
+ await fs19.mkdir(path18.dirname(destination), { recursive: true });
17808
+ await fs19.writeFile(
18021
17809
  destination,
18022
17810
  `${JSON.stringify(manifest, null, 2)}
18023
17811
  `
@@ -18155,14 +17943,14 @@ function assertCompleteMeshyControllerPack(manifest) {
18155
17943
  }
18156
17944
  async function installFallbackAvatar(args) {
18157
17945
  const { root, srcDir, log } = args;
18158
- const dest = path19.join(root, ASSETS_DEST, "avatar.vrm");
18159
- await fs20.mkdir(path19.dirname(dest), { recursive: true });
18160
- await fs20.copyFile(path19.join(srcDir, "assets", "default-avatar.vrm"), dest);
17946
+ const dest = path18.join(root, ASSETS_DEST, "avatar.vrm");
17947
+ await fs19.mkdir(path18.dirname(dest), { recursive: true });
17948
+ await fs19.copyFile(path18.join(srcDir, "assets", "default-avatar.vrm"), dest);
18161
17949
  log.dim(" public/assets/avatar.vrm (fallback avatar \u2014 bundled CC0 default)");
18162
17950
  }
18163
17951
  async function exists3(p) {
18164
17952
  try {
18165
- await fs20.access(p);
17953
+ await fs19.access(p);
18166
17954
  return true;
18167
17955
  } catch {
18168
17956
  return false;
@@ -18170,8 +17958,8 @@ async function exists3(p) {
18170
17958
  }
18171
17959
 
18172
17960
  // src/commands/character.ts
18173
- import fs21 from "fs/promises";
18174
- import path20 from "path";
17961
+ import fs20 from "fs/promises";
17962
+ import path19 from "path";
18175
17963
  function exactAnimation(selector) {
18176
17964
  const trimmed = selector.trim();
18177
17965
  if (/^-?\d+$/.test(trimmed)) return animationById(Number(trimmed));
@@ -18251,7 +18039,7 @@ function showAmbiguity(selector, candidates, log, json = false) {
18251
18039
  }
18252
18040
  process.exitCode = 1;
18253
18041
  }
18254
- var INSTALLED_MANIFEST = path20.join("public", "assets", "meshy-character.json");
18042
+ var INSTALLED_MANIFEST = path19.join("public", "assets", "meshy-character.json");
18255
18043
  async function resolveAdoptTarget(selector) {
18256
18044
  const trimmed = selector?.trim();
18257
18045
  if (trimmed && !trimmed.endsWith(".json")) {
@@ -18260,7 +18048,7 @@ async function resolveAdoptTarget(selector) {
18260
18048
  const file = trimmed ?? INSTALLED_MANIFEST;
18261
18049
  let raw;
18262
18050
  try {
18263
- raw = await fs21.readFile(file, "utf8");
18051
+ raw = await fs20.readFile(file, "utf8");
18264
18052
  } catch {
18265
18053
  return {
18266
18054
  ok: false,
@@ -18743,22 +18531,22 @@ async function context2(opts) {
18743
18531
  const project = await readProject();
18744
18532
  return { token, apiUrl: getApiUrl(opts.apiUrl ?? project?.apiUrl) };
18745
18533
  }
18746
- async function readVideo(path27, log) {
18534
+ async function readVideo(path26, log) {
18747
18535
  let bytes;
18748
18536
  try {
18749
- bytes = await readFile(path27);
18537
+ bytes = await readFile(path26);
18750
18538
  } catch {
18751
- log.error(`Can't read ${path27}.`);
18539
+ log.error(`Can't read ${path26}.`);
18752
18540
  return null;
18753
18541
  }
18754
18542
  if (bytes.byteLength > MAX_VIDEO_BYTES) {
18755
- log.error(`${basename(path27)} is ${(bytes.byteLength / 1e6).toFixed(0)} MB \u2014 the limit is ${MAX_VIDEO_BYTES / 1e6} MB.`);
18543
+ log.error(`${basename(path26)} is ${(bytes.byteLength / 1e6).toFixed(0)} MB \u2014 the limit is ${MAX_VIDEO_BYTES / 1e6} MB.`);
18756
18544
  return null;
18757
18545
  }
18758
18546
  return bytes;
18759
18547
  }
18760
- async function uploadVideo(apiUrl, token, characterId, path27, bytes, log) {
18761
- const contentType = /\.mov$/i.test(path27) ? "video/quicktime" : "video/mp4";
18548
+ async function uploadVideo(apiUrl, token, characterId, path26, bytes, log) {
18549
+ const contentType = /\.mov$/i.test(path26) ? "video/quicktime" : "video/mp4";
18762
18550
  const minted = await apiFetch(
18763
18551
  `${apiUrl}/api/characters/${encodeURIComponent(characterId)}/motions/video-url`,
18764
18552
  {
@@ -18773,7 +18561,7 @@ async function uploadVideo(apiUrl, token, characterId, path27, bytes, log) {
18773
18561
  return null;
18774
18562
  }
18775
18563
  const { uploadUrl, videoUrl } = await minted.json();
18776
- log.dim(` uploading ${basename(path27)} (${(bytes.byteLength / 1e6).toFixed(1)} MB)\u2026`);
18564
+ log.dim(` uploading ${basename(path26)} (${(bytes.byteLength / 1e6).toFixed(1)} MB)\u2026`);
18777
18565
  const put = await fetch(uploadUrl, {
18778
18566
  method: "PUT",
18779
18567
  headers: { "Content-Type": contentType, "Content-Length": String(bytes.byteLength) },
@@ -19089,8 +18877,8 @@ function rank(items, query) {
19089
18877
  }
19090
18878
 
19091
18879
  // src/commands/motion.ts
19092
- import fs22 from "fs/promises";
19093
- import path21 from "path";
18880
+ import fs21 from "fs/promises";
18881
+ import path20 from "path";
19094
18882
 
19095
18883
  // src/lib/motion/npz.ts
19096
18884
  import zlib from "zlib";
@@ -20341,7 +20129,7 @@ async function motionGen(opts, log) {
20341
20129
  }
20342
20130
  if (opts.constraintsPath !== void 0) {
20343
20131
  try {
20344
- const raw = await fs22.readFile(opts.constraintsPath, "utf8");
20132
+ const raw = await fs21.readFile(opts.constraintsPath, "utf8");
20345
20133
  generationOptions.constraints = JSON.parse(raw);
20346
20134
  } catch {
20347
20135
  log.error(`Couldn't read the --constraints JSON at ${opts.constraintsPath}.`);
@@ -20365,10 +20153,10 @@ async function motionGen(opts, log) {
20365
20153
  async function expandTakes(selectors) {
20366
20154
  const out = [];
20367
20155
  for (const sel of selectors) {
20368
- const st = await fs22.stat(sel).catch(() => null);
20156
+ const st = await fs21.stat(sel).catch(() => null);
20369
20157
  if (st?.isDirectory()) {
20370
- const names = await fs22.readdir(sel);
20371
- for (const n of names.sort()) if (n.endsWith(".npz")) out.push(path21.join(sel, n));
20158
+ const names = await fs21.readdir(sel);
20159
+ for (const n of names.sort()) if (n.endsWith(".npz")) out.push(path20.join(sel, n));
20372
20160
  } else if (st?.isFile()) {
20373
20161
  out.push(sel);
20374
20162
  } else {
@@ -20403,7 +20191,7 @@ async function motionVerify(opts, log) {
20403
20191
  let gates = DEFAULT_GATES;
20404
20192
  if (opts.gatesPath) {
20405
20193
  try {
20406
- gates = mergeGates(DEFAULT_GATES, JSON.parse(await fs22.readFile(opts.gatesPath, "utf8")));
20194
+ gates = mergeGates(DEFAULT_GATES, JSON.parse(await fs21.readFile(opts.gatesPath, "utf8")));
20407
20195
  } catch {
20408
20196
  log.error(`Couldn't read the --gates JSON at ${opts.gatesPath}.`);
20409
20197
  process.exitCode = 1;
@@ -20425,9 +20213,9 @@ async function motionVerify(opts, log) {
20425
20213
  }
20426
20214
  const reports = [];
20427
20215
  for (const file of files) {
20428
- const stem = path21.basename(file).replace(/\.npz$/, "");
20216
+ const stem = path20.basename(file).replace(/\.npz$/, "");
20429
20217
  try {
20430
- reports.push(analyzeTake(stem, await fs22.readFile(file), gates));
20218
+ reports.push(analyzeTake(stem, await fs21.readFile(file), gates));
20431
20219
  } catch (err) {
20432
20220
  reports.push({
20433
20221
  take: stem,
@@ -20465,7 +20253,7 @@ async function motionCompile(opts, log) {
20465
20253
  let cfg = DEFAULT_MOTION_CONFIG;
20466
20254
  if (opts.configPath) {
20467
20255
  try {
20468
- const patch = JSON.parse(await fs22.readFile(opts.configPath, "utf8"));
20256
+ const patch = JSON.parse(await fs21.readFile(opts.configPath, "utf8"));
20469
20257
  cfg = { ...DEFAULT_MOTION_CONFIG, ...patch, idleLoop: { ...DEFAULT_MOTION_CONFIG.idleLoop, ...patch.idleLoop } };
20470
20258
  } catch {
20471
20259
  log.error(`Couldn't read the --config JSON at ${opts.configPath}.`);
@@ -20483,16 +20271,16 @@ async function motionCompile(opts, log) {
20483
20271
  }
20484
20272
  const inputs = [];
20485
20273
  for (const file of files) {
20486
- const stem = path21.basename(file).replace(/\.npz$/, "");
20274
+ const stem = path20.basename(file).replace(/\.npz$/, "");
20487
20275
  try {
20488
- inputs.push({ stem, take: loadTake(await fs22.readFile(file)) });
20276
+ inputs.push({ stem, take: loadTake(await fs21.readFile(file)) });
20489
20277
  } catch (err) {
20490
20278
  log.error(`${stem}: ${err instanceof Error ? err.message : String(err)}`);
20491
20279
  process.exitCode = 1;
20492
20280
  return;
20493
20281
  }
20494
20282
  }
20495
- const setName = opts.set ?? path21.basename(opts.out).replace(/\.json$/, "");
20283
+ const setName = opts.set ?? path20.basename(opts.out).replace(/\.json$/, "");
20496
20284
  let result;
20497
20285
  try {
20498
20286
  result = compileSet(inputs, setName, cfg);
@@ -20507,9 +20295,9 @@ async function motionCompile(opts, log) {
20507
20295
  process.exitCode = 1;
20508
20296
  return;
20509
20297
  }
20510
- await fs22.mkdir(path21.dirname(path21.resolve(opts.out)), { recursive: true });
20298
+ await fs21.mkdir(path20.dirname(path20.resolve(opts.out)), { recursive: true });
20511
20299
  const json = JSON.stringify(result.data);
20512
- await fs22.writeFile(opts.out, json);
20300
+ await fs21.writeFile(opts.out, json);
20513
20301
  if (opts.json) {
20514
20302
  writeJson({ out: opts.out, set: setName, tracks: Object.keys(result.data.gaits), bytes: json.length });
20515
20303
  return;
@@ -20527,9 +20315,9 @@ var MOTION_RUNTIME_FILES = [
20527
20315
  var MOTION_PRESETS = {
20528
20316
  rifle: ["sets/rifle.json", "sets/jumps.json"]
20529
20317
  };
20530
- var MOTION_DEST = path21.join("src", "motion");
20318
+ var MOTION_DEST = path20.join("src", "motion");
20531
20319
  async function motionInstall(opts, log) {
20532
- const srcDir = path21.join(getTemplatesDir(), "motion");
20320
+ const srcDir = path20.join(getTemplatesDir(), "motion");
20533
20321
  const root = opts.cwd ?? process.cwd();
20534
20322
  const preset = opts.set;
20535
20323
  if (preset !== void 0 && !MOTION_PRESETS[preset]) {
@@ -20540,21 +20328,21 @@ async function motionInstall(opts, log) {
20540
20328
  const files = [...MOTION_RUNTIME_FILES, ...preset ? MOTION_PRESETS[preset] : []];
20541
20329
  log.plain(c.bold(`genex motion install${preset ? ` --set ${preset}` : ""}`));
20542
20330
  log.plain("");
20543
- log.step(`Vendoring the motion runtime into ${c.cyan(MOTION_DEST + path21.sep)}`);
20331
+ log.step(`Vendoring the motion runtime into ${c.cyan(MOTION_DEST + path20.sep)}`);
20544
20332
  let copied = 0, skipped = 0;
20545
20333
  try {
20546
20334
  for (const rel of files) {
20547
- const dest = path21.join(root, MOTION_DEST, rel);
20548
- const exists5 = await fs22.access(dest).then(() => true, () => false);
20335
+ const dest = path20.join(root, MOTION_DEST, rel);
20336
+ const exists5 = await fs21.access(dest).then(() => true, () => false);
20549
20337
  if (!opts.force && exists5) {
20550
20338
  skipped++;
20551
- log.dim(` skipped ${path21.join(MOTION_DEST, rel)} (exists \u2014 use --force to overwrite)`);
20339
+ log.dim(` skipped ${path20.join(MOTION_DEST, rel)} (exists \u2014 use --force to overwrite)`);
20552
20340
  continue;
20553
20341
  }
20554
- await fs22.mkdir(path21.dirname(dest), { recursive: true });
20555
- await fs22.copyFile(path21.join(srcDir, rel), dest);
20342
+ await fs21.mkdir(path20.dirname(dest), { recursive: true });
20343
+ await fs21.copyFile(path20.join(srcDir, rel), dest);
20556
20344
  copied++;
20557
- log.dim(` ${path21.join(MOTION_DEST, rel)}`);
20345
+ log.dim(` ${path20.join(MOTION_DEST, rel)}`);
20558
20346
  }
20559
20347
  } catch (err) {
20560
20348
  log.error(`Copy failed: ${String(err)}`);
@@ -20595,7 +20383,7 @@ async function motionConstraints(opts, log) {
20595
20383
  }
20596
20384
  const doc = directionConstraint(dir, speed, duration);
20597
20385
  const out = opts.out ?? "constraints.json";
20598
- await fs22.writeFile(out, JSON.stringify(doc));
20386
+ await fs21.writeFile(out, JSON.stringify(doc));
20599
20387
  if (opts.json) {
20600
20388
  writeJson({ out, dir: dirName, speed, duration, waypoints: doc[0].frame_indices.length });
20601
20389
  return;
@@ -20632,14 +20420,14 @@ async function runMotion(opts) {
20632
20420
  }
20633
20421
 
20634
20422
  // src/commands/blender.ts
20635
- import fs23 from "fs/promises";
20636
- import path22 from "path";
20423
+ import fs22 from "fs/promises";
20424
+ import path21 from "path";
20637
20425
  var SUBS2 = ["demo", "exec", "snap", "scene", "import", "export", "reset", "mcp", "serve"];
20638
20426
  var DEFAULT_OUT_DIR = "assets/blender";
20639
20427
  async function writeB64(dir, name, b64) {
20640
- await fs23.mkdir(dir, { recursive: true });
20641
- const p = path22.join(dir, name);
20642
- await fs23.writeFile(p, Buffer.from(b64, "base64"));
20428
+ await fs22.mkdir(dir, { recursive: true });
20429
+ const p = path21.join(dir, name);
20430
+ await fs22.writeFile(p, Buffer.from(b64, "base64"));
20643
20431
  return p;
20644
20432
  }
20645
20433
  function reportScene(log, s) {
@@ -20665,7 +20453,7 @@ async function runBlender(opts) {
20665
20453
  return serveLocalBlender({ port, log });
20666
20454
  }
20667
20455
  if (sub === "mcp") {
20668
- const { runBlenderMcp } = await import("./blender-mcp-6GQRUQWF.js");
20456
+ const { runBlenderMcp } = await import("./blender-mcp-SBNHGDIX.js");
20669
20457
  return runBlenderMcp();
20670
20458
  }
20671
20459
  let base = blenderEndpoint();
@@ -20685,7 +20473,7 @@ async function runBlender(opts) {
20685
20473
  log.plain(rest.join("\n"));
20686
20474
  return 1;
20687
20475
  }
20688
- const outDir = path22.resolve(opts.outDir ?? DEFAULT_OUT_DIR);
20476
+ const outDir = path21.resolve(opts.outDir ?? DEFAULT_OUT_DIR);
20689
20477
  const mode = opts.mode;
20690
20478
  if (mode !== void 0 && !isRenderMode(mode)) {
20691
20479
  log.error(`Unknown --mode ${mode}. Use one of: ${RENDER_MODES.join(", ")}.`);
@@ -20725,14 +20513,14 @@ async function runBlender(opts) {
20725
20513
  return 0;
20726
20514
  }
20727
20515
  case "export": {
20728
- const target = opts.out ?? path22.join(outDir, "scene.glb");
20516
+ const target = opts.out ?? path21.join(outDir, "scene.glb");
20729
20517
  const r = await blenderCall(base, "/export", { path: "/tmp/genex-export.glb" });
20730
20518
  if (!r.glbBase64) {
20731
20519
  log.error(`/export answered with no GLB bytes${r.uploaded ? " (it was uploaded, not inlined)" : ""}`);
20732
20520
  return 1;
20733
20521
  }
20734
- await fs23.mkdir(path22.dirname(target), { recursive: true });
20735
- await fs23.writeFile(target, Buffer.from(r.glbBase64, "base64"));
20522
+ await fs22.mkdir(path21.dirname(target), { recursive: true });
20523
+ await fs22.writeFile(target, Buffer.from(r.glbBase64, "base64"));
20736
20524
  log.success(`Exported ${r.bytes ?? 0} bytes`);
20737
20525
  log.plain(` ${c.cyan(target)}`);
20738
20526
  return 0;
@@ -20765,12 +20553,12 @@ async function runBlender(opts) {
20765
20553
  return 1;
20766
20554
  }
20767
20555
  try {
20768
- script = await fs23.readFile(opts.input, "utf8");
20556
+ script = await fs22.readFile(opts.input, "utf8");
20769
20557
  } catch {
20770
20558
  log.error(`Can't read ${opts.input}`);
20771
20559
  return 1;
20772
20560
  }
20773
- label = path22.basename(opts.input);
20561
+ label = path21.basename(opts.input);
20774
20562
  }
20775
20563
  const r = await blenderCall(base, "/exec", { script, ...mode ? { mode } : {}, sheet: { formats: SHEET_FORMATS } });
20776
20564
  if (r.stdout?.trim()) log.plain(r.stdout.trimEnd());
@@ -20889,9 +20677,9 @@ print(f"castle: {n} objects")
20889
20677
  `;
20890
20678
 
20891
20679
  // src/commands/asset-new.ts
20892
- import fs24 from "fs";
20680
+ import fs23 from "fs";
20893
20681
  import fsp from "fs/promises";
20894
- import path23 from "path";
20682
+ import path22 from "path";
20895
20683
  import { pathToFileURL } from "url";
20896
20684
  var EXTRA_FILES = [
20897
20685
  "genex-asset.example.json",
@@ -20985,7 +20773,7 @@ Every milestone ends with \`npm run verify\`, then \`npx genex preview --no-buil
20985
20773
  }
20986
20774
  async function runAssetNew(options) {
20987
20775
  const log = createLogger();
20988
- const cwd = options.dir ? path23.resolve(options.dir) : process.cwd();
20776
+ const cwd = options.dir ? path22.resolve(options.dir) : process.cwd();
20989
20777
  const slug = options.assetSlug;
20990
20778
  if (!slug) {
20991
20779
  log.error('Usage: genex asset new <slug> [--title "<Name>"] [--dims WxDxH] [--tri-band LOW-HIGH] [--holder "<your name>"] [--out <dir>]');
@@ -20995,14 +20783,14 @@ async function runAssetNew(options) {
20995
20783
  log.error(`The slug must be kebab-case (letters, digits, dashes), got: ${slug}`);
20996
20784
  return 1;
20997
20785
  }
20998
- const templateDir = path23.join(getTemplatesDir(), "asset-viewer");
20999
- if (!fs24.existsSync(templateDir)) {
20786
+ const templateDir = path22.join(getTemplatesDir(), "asset-viewer");
20787
+ if (!fs23.existsSync(templateDir)) {
21000
20788
  log.error(`Vendored asset-viewer template not found at ${templateDir} \u2014 reinstall the CLI.`);
21001
20789
  return 1;
21002
20790
  }
21003
- const manifestTools = await import(pathToFileURL(path23.join(templateDir, "tools", "emit-manifest.mjs")).href);
20791
+ const manifestTools = await import(pathToFileURL(path22.join(templateDir, "tools", "emit-manifest.mjs")).href);
21004
20792
  const { SHARED_FILES, PARITY_FILENAME, hashSharedFiles, pascalCase } = manifestTools;
21005
- const lockPath = path23.join(templateDir, "shared-files.sha256.json");
20793
+ const lockPath = path22.join(templateDir, "shared-files.sha256.json");
21006
20794
  const lock = JSON.parse(await fsp.readFile(lockPath, "utf8"));
21007
20795
  const actual = hashSharedFiles(templateDir);
21008
20796
  const drifted = SHARED_FILES.filter((rel) => lock.files[rel] !== actual[rel]);
@@ -21015,8 +20803,8 @@ async function runAssetNew(options) {
21015
20803
  const triBand = parseBand(options.triBand ?? "500-8000");
21016
20804
  const holder = options.holder?.trim() || HOLDER_PLACEHOLDER;
21017
20805
  const year = (/* @__PURE__ */ new Date()).getUTCFullYear();
21018
- const outDir = path23.resolve(cwd, options.out ?? slug);
21019
- if (fs24.existsSync(outDir) && fs24.readdirSync(outDir).length > 0 && !options.force) {
20806
+ const outDir = path22.resolve(cwd, options.out ?? slug);
20807
+ if (fs23.existsSync(outDir) && fs23.readdirSync(outDir).length > 0 && !options.force) {
21020
20808
  log.error(`${outDir} already holds files. Nothing was touched \u2014 pass --force to write into it anyway.`);
21021
20809
  return 1;
21022
20810
  }
@@ -21044,24 +20832,24 @@ async function runAssetNew(options) {
21044
20832
  };
21045
20833
  await fsp.mkdir(outDir, { recursive: true });
21046
20834
  for (const rel of [...SHARED_FILES, ...EXTRA_FILES]) {
21047
- const to = path23.join(outDir, rel);
21048
- await fsp.mkdir(path23.dirname(to), { recursive: true });
21049
- await fsp.copyFile(path23.join(templateDir, rel), to);
20835
+ const to = path22.join(outDir, rel);
20836
+ await fsp.mkdir(path22.dirname(to), { recursive: true });
20837
+ await fsp.copyFile(path22.join(templateDir, rel), to);
21050
20838
  }
21051
- const pkg = fillTemplate(await fsp.readFile(path23.join(templateDir, "package.json"), "utf8"), {
20839
+ const pkg = fillTemplate(await fsp.readFile(path22.join(templateDir, "package.json"), "utf8"), {
21052
20840
  slug,
21053
20841
  name,
21054
20842
  version
21055
20843
  });
21056
- await fsp.writeFile(path23.join(outDir, "package.json"), pkg, "utf8");
21057
- await fsp.writeFile(path23.join(outDir, "asset.config.json"), JSON.stringify(config, null, 2) + "\n", "utf8");
21058
- await fsp.writeFile(path23.join(outDir, ".gitignore"), GITIGNORE, "utf8");
20844
+ await fsp.writeFile(path22.join(outDir, "package.json"), pkg, "utf8");
20845
+ await fsp.writeFile(path22.join(outDir, "asset.config.json"), JSON.stringify(config, null, 2) + "\n", "utf8");
20846
+ await fsp.writeFile(path22.join(outDir, ".gitignore"), GITIGNORE, "utf8");
21059
20847
  await fsp.writeFile(
21060
- path23.join(outDir, "DESIGN.md"),
20848
+ path22.join(outDir, "DESIGN.md"),
21061
20849
  designDoc({ name, slug, sizeMeters, triBand, holder }),
21062
20850
  "utf8"
21063
20851
  );
21064
- const placeholder = await fsp.readFile(path23.join(templateDir, "src", "asset", "PLACEHOLDER.ts"), "utf8");
20852
+ const placeholder = await fsp.readFile(path22.join(templateDir, "src", "asset", "PLACEHOLDER.ts"), "utf8");
21065
20853
  const seeded = seedAssetSource(placeholder, {
21066
20854
  slug,
21067
20855
  name,
@@ -21072,8 +20860,8 @@ async function runAssetNew(options) {
21072
20860
  pascalCase
21073
20861
  });
21074
20862
  const assetFilename = `${slug.replace(/-([a-z0-9])/g, (_, ch) => ch.toUpperCase())}.ts`;
21075
- await fsp.mkdir(path23.join(outDir, "src", "asset"), { recursive: true });
21076
- await fsp.writeFile(path23.join(outDir, "src", "asset", assetFilename), seeded, "utf8");
20863
+ await fsp.mkdir(path22.join(outDir, "src", "asset"), { recursive: true });
20864
+ await fsp.writeFile(path22.join(outDir, "src", "asset", assetFilename), seeded, "utf8");
21077
20865
  const copied = hashSharedFiles(outDir);
21078
20866
  const mismatched = SHARED_FILES.filter((rel) => copied[rel] !== lock.files[rel]);
21079
20867
  if (mismatched.length) {
@@ -21081,7 +20869,7 @@ async function runAssetNew(options) {
21081
20869
  return 1;
21082
20870
  }
21083
20871
  await fsp.writeFile(
21084
- path23.join(outDir, PARITY_FILENAME),
20872
+ path22.join(outDir, PARITY_FILENAME),
21085
20873
  JSON.stringify(
21086
20874
  {
21087
20875
  note: "Recorded by `genex asset new`. Gate G13 recomputes these; do not hand-edit the viewer.",
@@ -21125,12 +20913,12 @@ async function runAssetNew(options) {
21125
20913
  }
21126
20914
 
21127
20915
  // src/commands/tools.ts
21128
- import path26 from "path";
20916
+ import path25 from "path";
21129
20917
 
21130
20918
  // src/lib/local-install.ts
21131
- import fs25 from "fs/promises";
21132
- import path24 from "path";
21133
- import { spawn as spawn5 } from "child_process";
20919
+ import fs24 from "fs/promises";
20920
+ import path23 from "path";
20921
+ import { spawn as spawn4 } from "child_process";
21134
20922
  var CLI_PACKAGE = "@genex-ai/cli-demo";
21135
20923
  var FULL_NAME_FALLBACK = `npx ${CLI_PACKAGE}@${CLI_CHANNEL}`;
21136
20924
  var LOCKFILES = [
@@ -21142,17 +20930,17 @@ var LOCKFILES = [
21142
20930
  ];
21143
20931
  async function exists4(p) {
21144
20932
  try {
21145
- await fs25.access(p);
20933
+ await fs24.access(p);
21146
20934
  return true;
21147
20935
  } catch {
21148
20936
  return false;
21149
20937
  }
21150
20938
  }
21151
20939
  async function detectPackageManager(cwd) {
21152
- let dir = path24.resolve(cwd);
20940
+ let dir = path23.resolve(cwd);
21153
20941
  for (; ; ) {
21154
20942
  try {
21155
- const raw = await fs25.readFile(path24.join(dir, "package.json"), "utf8");
20943
+ const raw = await fs24.readFile(path23.join(dir, "package.json"), "utf8");
21156
20944
  const pm = JSON.parse(raw).packageManager;
21157
20945
  if (typeof pm === "string") {
21158
20946
  const name = pm.split("@")[0];
@@ -21161,18 +20949,18 @@ async function detectPackageManager(cwd) {
21161
20949
  } catch {
21162
20950
  }
21163
20951
  for (const [file, pm] of LOCKFILES) {
21164
- if (await exists4(path24.join(dir, file))) return pm;
20952
+ if (await exists4(path23.join(dir, file))) return pm;
21165
20953
  }
21166
- const parent = path24.dirname(dir);
20954
+ const parent = path23.dirname(dir);
21167
20955
  if (parent === dir) return "npm";
21168
20956
  dir = parent;
21169
20957
  }
21170
20958
  }
21171
20959
  async function findLocalCli(cwd) {
21172
- let dir = path24.resolve(cwd);
20960
+ let dir = path23.resolve(cwd);
21173
20961
  for (; ; ) {
21174
- if (await exists4(path24.join(dir, "node_modules", CLI_PACKAGE, "package.json"))) return dir;
21175
- const parent = path24.dirname(dir);
20962
+ if (await exists4(path23.join(dir, "node_modules", CLI_PACKAGE, "package.json"))) return dir;
20963
+ const parent = path23.dirname(dir);
21176
20964
  if (parent === dir) return null;
21177
20965
  dir = parent;
21178
20966
  }
@@ -21190,7 +20978,7 @@ function installArgs(pm, spec) {
21190
20978
  }
21191
20979
  }
21192
20980
  function manifestName(cwd) {
21193
- const slug = path24.basename(path24.resolve(cwd)).toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 100);
20981
+ const slug = path23.basename(path23.resolve(cwd)).toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 100);
21194
20982
  return slug || "genex-tools-workspace";
21195
20983
  }
21196
20984
  function isSourceRun(moduleUrl = import.meta.url) {
@@ -21207,7 +20995,7 @@ async function spawnInstall(command, args, cwd) {
21207
20995
  resolve({ ok, detail });
21208
20996
  };
21209
20997
  try {
21210
- const child = spawn5(command, args, {
20998
+ const child = spawn4(command, args, {
21211
20999
  cwd,
21212
21000
  // npm/pnpm/yarn are .cmd shims on Windows; the args carry no user input.
21213
21001
  shell: process.platform === "win32",
@@ -21248,10 +21036,10 @@ async function ensureLocalCli(log, cwd = process.cwd(), opts = {}) {
21248
21036
  return;
21249
21037
  }
21250
21038
  const pm = await detectPackageManager(cwd);
21251
- const hadManifest = await exists4(path24.join(cwd, "package.json"));
21039
+ const hadManifest = await exists4(path23.join(cwd, "package.json"));
21252
21040
  if (!hadManifest) {
21253
- await fs25.writeFile(
21254
- path24.join(cwd, "package.json"),
21041
+ await fs24.writeFile(
21042
+ path23.join(cwd, "package.json"),
21255
21043
  JSON.stringify({ name: manifestName(cwd), private: true }, null, 2) + "\n"
21256
21044
  );
21257
21045
  await ensureIgnored(cwd, "node_modules/");
@@ -21271,20 +21059,20 @@ async function ensureLocalCli(log, cwd = process.cwd(), opts = {}) {
21271
21059
  }
21272
21060
  }
21273
21061
  async function ensureIgnored(dir, entry) {
21274
- const file = path24.join(dir, ".gitignore");
21062
+ const file = path23.join(dir, ".gitignore");
21275
21063
  let content = "";
21276
21064
  try {
21277
- content = await fs25.readFile(file, "utf8");
21065
+ content = await fs24.readFile(file, "utf8");
21278
21066
  } catch {
21279
21067
  }
21280
21068
  if (content.split("\n").some((l) => l.trim() === entry)) return;
21281
21069
  let next = content;
21282
21070
  if (next.length > 0 && !next.endsWith("\n")) next += "\n";
21283
- await fs25.writeFile(file, next + entry + "\n");
21071
+ await fs24.writeFile(file, next + entry + "\n");
21284
21072
  }
21285
21073
 
21286
21074
  // src/commands/doctor.ts
21287
- import path25 from "path";
21075
+ import path24 from "path";
21288
21076
  var LANE_ORDER = [
21289
21077
  "model",
21290
21078
  "image",
@@ -21346,9 +21134,15 @@ async function runDoctor(opts = {}) {
21346
21134
  rows.push(authRow(authState, email, opts.envPath));
21347
21135
  let credits = null;
21348
21136
  let lanes = null;
21137
+ let legal = null;
21349
21138
  if (authState === "ok" && token) {
21350
- [credits, lanes] = await Promise.all([fetchCredits(apiUrl, token), fetchLanes(apiUrl, token)]);
21139
+ [credits, lanes, legal] = await Promise.all([
21140
+ fetchCredits(apiUrl, token),
21141
+ fetchLanes(apiUrl, token),
21142
+ fetchLegalStatus(apiUrl, token)
21143
+ ]);
21351
21144
  }
21145
+ rows.push(termsRow(authState, legal));
21352
21146
  rows.push(creditsRow(authState, credits));
21353
21147
  if (opts.json) {
21354
21148
  process.stdout.write(
@@ -21358,6 +21152,7 @@ async function runDoctor(opts = {}) {
21358
21152
  node: process.versions.node,
21359
21153
  cli: { installed, latest },
21360
21154
  auth: { state: authState, email, apiUrl },
21155
+ terms: legal,
21361
21156
  credits,
21362
21157
  lanes: lanes ? { paused: lanes.paused, lanes: lanes.lanes } : null,
21363
21158
  ok: !rows.some((r) => r.bad)
@@ -21459,6 +21254,17 @@ function authRow(state, email, envPath) {
21459
21254
  };
21460
21255
  }
21461
21256
  }
21257
+ function termsRow(state, info) {
21258
+ if (state !== "ok") return { label: "Terms", value: "\u2014 (sign in first)" };
21259
+ if (!info) return { label: "Terms", value: "couldn't be read from this server" };
21260
+ if (info.accepted) return { label: "Terms", value: `accepted (${info.acceptedVersion ?? info.required})` };
21261
+ return {
21262
+ label: "Terms",
21263
+ value: "changed since you last agreed \u2014 every generate and publish is refused",
21264
+ bad: true,
21265
+ fix: `Open ${acceptUrl(info.acceptUrl)} and agree \u2014 one click; any refused command then continues on its own.`
21266
+ };
21267
+ }
21462
21268
  function creditsRow(state, info) {
21463
21269
  if (state !== "ok") return { label: "Credits", value: "\u2014 (sign in first)" };
21464
21270
  if (!info) return { label: "Credits", value: "couldn't be read from this server" };
@@ -21510,9 +21316,21 @@ async function fetchCredits(apiUrl, token) {
21510
21316
  return null;
21511
21317
  }
21512
21318
  }
21319
+ async function fetchLegalStatus(apiUrl, token) {
21320
+ try {
21321
+ const res = await apiFetch(`${apiUrl}/api/legal/status`, {
21322
+ headers: { Authorization: `Bearer ${token}` },
21323
+ signal: AbortSignal.timeout(6e3)
21324
+ });
21325
+ if (!res.ok) return null;
21326
+ return await res.json();
21327
+ } catch {
21328
+ return null;
21329
+ }
21330
+ }
21513
21331
  async function firstSkillsMarker() {
21514
21332
  for (const target of resolveAgentTargets()) {
21515
- const marker = await readSkillsMarker(path25.join(target.baseDir, "skills"));
21333
+ const marker = await readSkillsMarker(path24.join(target.baseDir, "skills"));
21516
21334
  if (marker) return marker;
21517
21335
  }
21518
21336
  return null;
@@ -21551,8 +21369,8 @@ async function runTools(opts) {
21551
21369
  let totalNew = 0;
21552
21370
  let totalUpdated = 0;
21553
21371
  for (const t of targets) {
21554
- const dest = path26.join(t.baseDir, "skills");
21555
- const { copied, updated } = await copyTemplates(path26.join(templatesDir, "skills"), dest, {
21372
+ const dest = path25.join(t.baseDir, "skills");
21373
+ const { copied, updated } = await copyTemplates(path25.join(templatesDir, "skills"), dest, {
21556
21374
  filter: (rel) => skillFamilyFilter("tools")(`skills/${rel}`)
21557
21375
  });
21558
21376
  await pruneRemovedSkills(dest, log);
@@ -21607,7 +21425,6 @@ async function runTools(opts) {
21607
21425
  await runDoctor({ apiUrl: opts.apiUrl, envPath: opts.envPath, quiet: opts.quiet });
21608
21426
  log.plain("");
21609
21427
  log.success("Genex Tools is ready. \u{1F9F0}");
21610
- log.plain(` Try it: ${c.cyan('npx genex image "weathered treasure map, hand-inked" --transparent')}`);
21611
21428
  log.dim(" Assets land in ./assets/ as files you own \u2014 wire the local path into your game.");
21612
21429
  }
21613
21430
 
@@ -22825,8 +22642,9 @@ async function main() {
22825
22642
  log.plain(getCliVersion());
22826
22643
  return;
22827
22644
  }
22828
- const mode = await workspaceMode();
22829
- setWorkspaceHeader(mode === "tools" ? "tools" : null);
22645
+ const workspace = await resolveWorkspace();
22646
+ const mode = workspace.mode;
22647
+ setWorkspaceHeader(workspaceHeaderLabel(workspace));
22830
22648
  if (parsed.help || parsed.command === void 0) {
22831
22649
  const perCommand = parsed.command === void 0 ? void 0 : COMMAND_HELP[parsed.command];
22832
22650
  log.plain(perCommand ?? (mode === "tools" ? HELP_TOOLS : HELP));