@genex-ai/cli-demo 1.10.4-dev.536 → 1.11.0-dev.538

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
@@ -1,103 +1,32 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ BLENDER_SETUP_HINT,
4
+ CLI_CHANNEL,
5
+ DEFAULT_API_URL,
6
+ DEFAULT_AUTH_URL,
7
+ ENV_FILE_ENV,
8
+ ENV_TOKEN_KEY,
9
+ RENDER_MODES,
10
+ STANDS,
11
+ blenderCall,
12
+ blenderEndpoint,
13
+ getAnimsBase,
14
+ getAnimsCacheDir,
15
+ getApiUrl,
16
+ getAuthUrl,
17
+ getCliVersion,
18
+ getGenexDir,
19
+ getGenexEnvPath,
20
+ getTemplatesDir,
21
+ isRenderMode,
22
+ resolveAgentTargets
23
+ } from "./chunk-ZHIP7LCA.js";
2
24
 
3
25
  // src/instrument.ts
4
26
  import * as Sentry from "@sentry/node";
5
27
 
6
- // src/config.ts
7
- import fs from "fs";
8
- import os from "os";
9
- import path from "path";
10
- import { fileURLToPath } from "url";
11
- var RAW_CHANNEL = "dev";
12
- var CLI_CHANNEL = RAW_CHANNEL === "dev" ? "dev" : "latest";
13
- var STANDS = {
14
- prod: { api: "https://api.genex.games", dashboard: "https://genex.games" },
15
- dev: { api: "https://api-dev.genex.games", dashboard: "https://dev.genex.games" }
16
- };
17
- var DEFAULT_AUTH_URL = CLI_CHANNEL === "dev" ? STANDS.dev.dashboard : STANDS.prod.dashboard;
18
- var DEFAULT_API_URL = CLI_CHANNEL === "dev" ? STANDS.dev.api : STANDS.prod.api;
19
- var DEFAULT_ANIMS_BASE = "https://cdn.genex.technology/anims/ual1/v1/";
20
- var ANIMS_BASE_ENV = "GENEX_ANIMS_BASE";
21
- function getAnimsBase(override) {
22
- const raw = override || process.env[ANIMS_BASE_ENV] || DEFAULT_ANIMS_BASE;
23
- return raw.replace(/\/+$/, "") + "/";
24
- }
25
- function getAnimsCacheDir() {
26
- return path.join(getGenexDir(), "cache", "anims");
27
- }
28
- var ENV_TOKEN_KEY = "GENEX_TOKEN";
29
- var AUTH_URL_ENV = "GENEX_AUTH_URL";
30
- var API_URL_ENV = "GENEX_API_URL";
31
- var ENV_FILE_ENV = "GENEX_ENV_FILE";
32
- function getAuthUrl(override) {
33
- const raw = override || process.env[AUTH_URL_ENV] || DEFAULT_AUTH_URL;
34
- return raw.replace(/\/+$/, "");
35
- }
36
- function getApiUrl(override) {
37
- const raw = override || process.env[API_URL_ENV] || DEFAULT_API_URL;
38
- return raw.replace(/\/+$/, "");
39
- }
40
- function getGenexDir() {
41
- return path.join(os.homedir(), ".genex");
42
- }
43
- function getGenexEnvPath(override) {
44
- if (override) return path.resolve(override);
45
- const fromEnv = process.env[ENV_FILE_ENV];
46
- if (fromEnv) return path.resolve(fromEnv);
47
- return path.join(getGenexDir(), "env");
48
- }
49
- function getTemplatesDir() {
50
- const here = path.dirname(fileURLToPath(import.meta.url));
51
- return path.resolve(here, "..", "templates");
52
- }
53
- function getCliVersion() {
54
- try {
55
- const here = path.dirname(fileURLToPath(import.meta.url));
56
- const pkg = JSON.parse(
57
- fs.readFileSync(path.resolve(here, "..", "package.json"), "utf8")
58
- );
59
- return pkg.version ?? "0.0.0";
60
- } catch {
61
- return "0.0.0";
62
- }
63
- }
64
- var KNOWN_AGENTS = {
65
- claude: { label: "Claude Code", dirName: ".claude", full: true },
66
- codex: { label: "Codex", dirName: ".codex", full: false },
67
- cursor: { label: "Cursor", dirName: ".cursor", full: false }
68
- };
69
- var KNOWN_AGENT_IDS = Object.keys(KNOWN_AGENTS);
70
- function resolveAgentTargets(opts = {}) {
71
- if (opts.dir) {
72
- return [{ id: "custom", label: "workspace", baseDir: path.resolve(opts.dir), full: true }];
73
- }
74
- const home = os.homedir();
75
- const projectRoot = process.cwd();
76
- let ids;
77
- if (opts.agents && opts.agents.length > 0) {
78
- ids = opts.agents.filter((id) => KNOWN_AGENTS[id]);
79
- } else {
80
- ids = KNOWN_AGENT_IDS.filter((id) => {
81
- const dirName = KNOWN_AGENTS[id].dirName;
82
- return isDir(path.join(home, dirName)) || isDir(path.join(projectRoot, dirName));
83
- });
84
- if (ids.length === 0) ids = ["claude"];
85
- }
86
- return ids.map((id) => {
87
- const def = KNOWN_AGENTS[id];
88
- return { id, label: def.label, baseDir: path.join(projectRoot, def.dirName), full: def.full };
89
- });
90
- }
91
- function isDir(p) {
92
- try {
93
- return fs.statSync(p).isDirectory();
94
- } catch {
95
- return false;
96
- }
97
- }
98
-
99
28
  // src/lib/sentry-scrub.ts
100
- import os2 from "os";
29
+ import os from "os";
101
30
  var BEARER = /\bBearer\s+[A-Za-z0-9._~+/-]+=*/gi;
102
31
  var URL_CRED = /(https?:\/\/)[^/@\s:]+(?::[^/@\s]*)?@/gi;
103
32
  var TOKENISH_KV = /((?:token|secret|password|api[_-]?key|authorization)=)[^&#\s"']+/gi;
@@ -105,7 +34,7 @@ var SENSITIVE_KEY = /token|secret|password|api[_-]?key|authorization|bearer/i;
105
34
  function scrubString(s) {
106
35
  return s.replace(URL_CRED, "$1[redacted]@").replace(BEARER, "Bearer [redacted]").replace(TOKENISH_KV, "$1[redacted]");
107
36
  }
108
- function redactHome(s, home = os2.homedir()) {
37
+ function redactHome(s, home = os.homedir()) {
109
38
  if (!home) return s;
110
39
  return s.split(home).join("~");
111
40
  }
@@ -197,7 +126,7 @@ import * as Sentry2 from "@sentry/node";
197
126
  // src/lib/auth.ts
198
127
  import http from "http";
199
128
  import crypto from "crypto";
200
- import os3 from "os";
129
+ import os2 from "os";
201
130
  import readline from "readline";
202
131
  import { spawn as spawn2 } from "child_process";
203
132
  import { URL as URL2 } from "url";
@@ -322,18 +251,18 @@ async function fetchSignedInEmail(apiUrl, token) {
322
251
  }
323
252
 
324
253
  // src/lib/pending-auth.ts
325
- import fs3 from "fs/promises";
326
- import path3 from "path";
327
-
328
- // src/lib/env.ts
329
254
  import fs2 from "fs/promises";
330
255
  import path2 from "path";
256
+
257
+ // src/lib/env.ts
258
+ import fs from "fs/promises";
259
+ import path from "path";
331
260
  import { spawn } from "child_process";
332
261
  async function writeEnvVar(envPath, key, value) {
333
262
  let content = "";
334
263
  let existed = false;
335
264
  try {
336
- content = await fs2.readFile(envPath, "utf8");
265
+ content = await fs.readFile(envPath, "utf8");
337
266
  existed = true;
338
267
  } catch {
339
268
  }
@@ -353,14 +282,14 @@ async function writeEnvVar(envPath, key, value) {
353
282
  next = prefix + assignment + "\n";
354
283
  mode = existed ? "appended" : "created";
355
284
  }
356
- await fs2.mkdir(path2.dirname(envPath), { recursive: true });
357
- await fs2.writeFile(envPath, next, { mode: 384 });
285
+ await fs.mkdir(path.dirname(envPath), { recursive: true });
286
+ await fs.writeFile(envPath, next, { mode: 384 });
358
287
  await restrictFilePermissions(envPath);
359
288
  return { mode, path: envPath };
360
289
  }
361
290
  async function restrictFilePermissions(filePath) {
362
291
  if (process.platform !== "win32") {
363
- await fs2.chmod(filePath, 384).catch(() => {
292
+ await fs.chmod(filePath, 384).catch(() => {
364
293
  });
365
294
  return;
366
295
  }
@@ -392,11 +321,11 @@ function escapeRegExp(s) {
392
321
 
393
322
  // src/lib/pending-auth.ts
394
323
  function pendingAuthPath() {
395
- return path3.join(getGenexDir(), "pending-auth.json");
324
+ return path2.join(getGenexDir(), "pending-auth.json");
396
325
  }
397
326
  async function readPendingAuth(apiUrl) {
398
327
  try {
399
- const raw = JSON.parse(await fs3.readFile(pendingAuthPath(), "utf8"));
328
+ const raw = JSON.parse(await fs2.readFile(pendingAuthPath(), "utf8"));
400
329
  if (!raw?.deviceCode || !raw.userCode || raw.apiUrl !== apiUrl) return null;
401
330
  if (Date.now() >= raw.expiresAt) return null;
402
331
  return raw;
@@ -406,12 +335,12 @@ async function readPendingAuth(apiUrl) {
406
335
  }
407
336
  async function writePendingAuth(pending) {
408
337
  const file = pendingAuthPath();
409
- await fs3.mkdir(path3.dirname(file), { recursive: true });
410
- await fs3.writeFile(file, JSON.stringify(pending, null, 2), { mode: 384 });
338
+ await fs2.mkdir(path2.dirname(file), { recursive: true });
339
+ await fs2.writeFile(file, JSON.stringify(pending, null, 2), { mode: 384 });
411
340
  await restrictFilePermissions(file);
412
341
  }
413
342
  async function clearPendingAuth() {
414
- await fs3.rm(pendingAuthPath(), { force: true }).catch(() => {
343
+ await fs2.rm(pendingAuthPath(), { force: true }).catch(() => {
415
344
  });
416
345
  }
417
346
 
@@ -495,7 +424,7 @@ async function startDeviceAuth(apiBaseUrl, label) {
495
424
  }
496
425
  function defaultLabel() {
497
426
  try {
498
- return os3.hostname();
427
+ return os2.hostname();
499
428
  } catch {
500
429
  return "";
501
430
  }
@@ -819,17 +748,17 @@ function tokenizeCommand(input) {
819
748
  }
820
749
 
821
750
  // src/lib/store.ts
822
- import fs4 from "fs/promises";
823
- import path4 from "path";
751
+ import fs3 from "fs/promises";
752
+ import path3 from "path";
824
753
  function getProjectMetadataPath(cwd = process.cwd()) {
825
- return path4.join(cwd, ".genex", "project.json");
754
+ return path3.join(cwd, ".genex", "project.json");
826
755
  }
827
756
  function getWorkspacePath(cwd = process.cwd()) {
828
- return path4.join(cwd, ".genex", "workspace.json");
757
+ return path3.join(cwd, ".genex", "workspace.json");
829
758
  }
830
759
  async function readWorkspace(cwd = process.cwd()) {
831
760
  try {
832
- const raw = await fs4.readFile(getWorkspacePath(cwd), "utf8");
761
+ const raw = await fs3.readFile(getWorkspacePath(cwd), "utf8");
833
762
  return JSON.parse(raw);
834
763
  } catch {
835
764
  return null;
@@ -837,9 +766,9 @@ async function readWorkspace(cwd = process.cwd()) {
837
766
  }
838
767
  async function writeWorkspace(meta, cwd = process.cwd()) {
839
768
  const file = getWorkspacePath(cwd);
840
- await fs4.mkdir(path4.dirname(file), { recursive: true });
841
- await fs4.writeFile(file, JSON.stringify(meta, null, 2) + "\n", { mode: 384 });
842
- await fs4.chmod(file, 384).catch(() => {
769
+ await fs3.mkdir(path3.dirname(file), { recursive: true });
770
+ await fs3.writeFile(file, JSON.stringify(meta, null, 2) + "\n", { mode: 384 });
771
+ await fs3.chmod(file, 384).catch(() => {
843
772
  });
844
773
  return { path: file };
845
774
  }
@@ -852,7 +781,7 @@ async function rotateRejectedEnv(envPath) {
852
781
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
853
782
  const aside = `${file}.rejected-${stamp}`;
854
783
  try {
855
- await fs4.rename(file, aside);
784
+ await fs3.rename(file, aside);
856
785
  return aside;
857
786
  } catch {
858
787
  return null;
@@ -862,14 +791,14 @@ async function readUserToken(envPath) {
862
791
  const fromGenex = await readTokenFromFile(getGenexEnvPath(envPath));
863
792
  if (fromGenex) return fromGenex;
864
793
  if (!envPath && !process.env[ENV_FILE_ENV]) {
865
- return readTokenFromFile(path4.join(process.cwd(), ".env"));
794
+ return readTokenFromFile(path3.join(process.cwd(), ".env"));
866
795
  }
867
796
  return null;
868
797
  }
869
798
  async function readTokenFromFile(file) {
870
799
  let content;
871
800
  try {
872
- content = await fs4.readFile(file, "utf8");
801
+ content = await fs3.readFile(file, "utf8");
873
802
  } catch {
874
803
  return null;
875
804
  }
@@ -885,7 +814,7 @@ function stripQuotes(v) {
885
814
  }
886
815
  async function readProject(cwd = process.cwd()) {
887
816
  try {
888
- const raw = await fs4.readFile(getProjectMetadataPath(cwd), "utf8");
817
+ const raw = await fs3.readFile(getProjectMetadataPath(cwd), "utf8");
889
818
  return JSON.parse(raw);
890
819
  } catch {
891
820
  return null;
@@ -893,9 +822,9 @@ async function readProject(cwd = process.cwd()) {
893
822
  }
894
823
  async function writeProject(meta, cwd = process.cwd()) {
895
824
  const file = getProjectMetadataPath(cwd);
896
- await fs4.mkdir(path4.dirname(file), { recursive: true });
897
- await fs4.writeFile(file, JSON.stringify(meta, null, 2) + "\n", { mode: 384 });
898
- await fs4.chmod(file, 384).catch(() => {
825
+ await fs3.mkdir(path3.dirname(file), { recursive: true });
826
+ await fs3.writeFile(file, JSON.stringify(meta, null, 2) + "\n", { mode: 384 });
827
+ await fs3.chmod(file, 384).catch(() => {
899
828
  });
900
829
  return { path: file };
901
830
  }
@@ -960,14 +889,14 @@ async function runAuth(opts) {
960
889
  }
961
890
 
962
891
  // src/commands/init.ts
963
- import fs10 from "fs/promises";
964
- import path10 from "path";
892
+ import fs9 from "fs/promises";
893
+ import path9 from "path";
965
894
 
966
895
  // src/lib/copy-templates.ts
967
- import fs5 from "fs/promises";
968
- import path5 from "path";
896
+ import fs4 from "fs/promises";
897
+ import path4 from "path";
969
898
  function isGenexManaged(rel) {
970
- return rel.split(path5.sep).some((seg) => seg.startsWith("genex"));
899
+ return rel.split(path4.sep).some((seg) => seg.startsWith("genex"));
971
900
  }
972
901
  async function copyTemplates(srcDir, destDir, opts = {}) {
973
902
  const result = { copied: [], updated: [], skipped: [] };
@@ -975,15 +904,15 @@ async function copyTemplates(srcDir, destDir, opts = {}) {
975
904
  return result;
976
905
  }
977
906
  async function walk(rootSrc, src, dest, opts, result) {
978
- const entries = await fs5.readdir(src, { withFileTypes: true });
907
+ const entries = await fs4.readdir(src, { withFileTypes: true });
979
908
  for (const entry of entries) {
980
- const srcPath = path5.join(src, entry.name);
981
- const destPath = path5.join(dest, entry.name);
982
- const rel = path5.relative(rootSrc, srcPath);
909
+ const srcPath = path4.join(src, entry.name);
910
+ const destPath = path4.join(dest, entry.name);
911
+ const rel = path4.relative(rootSrc, srcPath);
983
912
  if (opts.exclude?.includes(rel)) continue;
984
913
  if (opts.filter && !opts.filter(rel)) continue;
985
914
  if (entry.isDirectory()) {
986
- await fs5.mkdir(destPath, { recursive: true });
915
+ await fs4.mkdir(destPath, { recursive: true });
987
916
  await walk(rootSrc, srcPath, destPath, opts, result);
988
917
  continue;
989
918
  }
@@ -996,15 +925,15 @@ async function walk(rootSrc, src, dest, opts, result) {
996
925
  result.skipped.push(rel);
997
926
  continue;
998
927
  }
999
- await fs5.mkdir(path5.dirname(destPath), { recursive: true });
1000
- await fs5.copyFile(srcPath, destPath);
928
+ await fs4.mkdir(path4.dirname(destPath), { recursive: true });
929
+ await fs4.copyFile(srcPath, destPath);
1001
930
  result.copied.push(rel);
1002
931
  if (present) result.updated.push(rel);
1003
932
  }
1004
933
  }
1005
934
  async function exists(p) {
1006
935
  try {
1007
- await fs5.access(p);
936
+ await fs4.access(p);
1008
937
  return true;
1009
938
  } catch {
1010
939
  return false;
@@ -1012,9 +941,9 @@ async function exists(p) {
1012
941
  }
1013
942
 
1014
943
  // src/lib/agents-contract.ts
1015
- import fs6 from "fs/promises";
1016
- import os4 from "os";
1017
- import path6 from "path";
944
+ import fs5 from "fs/promises";
945
+ import os3 from "os";
946
+ import path5 from "path";
1018
947
  var CONTRACT_BEGIN = "<!-- genex:contract:begin (managed by genex \u2014 edits inside this block are overwritten on sync) -->";
1019
948
  var CONTRACT_END = "<!-- genex:contract:end -->";
1020
949
  var GENEX_CONTRACT_BLOCK = `${CONTRACT_BEGIN}
@@ -1078,32 +1007,32 @@ ${block}
1078
1007
  async function writeAgentsContract(projectDir, contractBlock = GENEX_CONTRACT_BLOCK) {
1079
1008
  let changed = false;
1080
1009
  try {
1081
- const agentsPath = path6.join(projectDir, "AGENTS.md");
1010
+ const agentsPath = path5.join(projectDir, "AGENTS.md");
1082
1011
  let existing = null;
1083
1012
  try {
1084
- existing = await fs6.readFile(agentsPath, "utf8");
1013
+ existing = await fs5.readFile(agentsPath, "utf8");
1085
1014
  } catch {
1086
1015
  existing = null;
1087
1016
  }
1088
1017
  const next = mergeContractBlock(existing, contractBlock);
1089
1018
  if (next !== existing) {
1090
- await fs6.writeFile(agentsPath, next, "utf8");
1019
+ await fs5.writeFile(agentsPath, next, "utf8");
1091
1020
  changed = true;
1092
1021
  }
1093
- const claudePath = path6.join(projectDir, "CLAUDE.md");
1022
+ const claudePath = path5.join(projectDir, "CLAUDE.md");
1094
1023
  let claude = null;
1095
1024
  try {
1096
- claude = await fs6.readFile(claudePath, "utf8");
1025
+ claude = await fs5.readFile(claudePath, "utf8");
1097
1026
  } catch {
1098
1027
  claude = null;
1099
1028
  }
1100
1029
  if (claude === null) {
1101
- await fs6.writeFile(claudePath, `${CLAUDE_IMPORT_LINE}
1030
+ await fs5.writeFile(claudePath, `${CLAUDE_IMPORT_LINE}
1102
1031
  `, "utf8");
1103
1032
  changed = true;
1104
1033
  } else if (!claude.split("\n").some((line) => line.trim() === CLAUDE_IMPORT_LINE)) {
1105
1034
  const sep = claude.endsWith("\n") ? "" : "\n";
1106
- await fs6.writeFile(claudePath, `${claude}${sep}
1035
+ await fs5.writeFile(claudePath, `${claude}${sep}
1107
1036
  ${CLAUDE_IMPORT_LINE}
1108
1037
  `, "utf8");
1109
1038
  changed = true;
@@ -1115,26 +1044,26 @@ ${CLAUDE_IMPORT_LINE}
1115
1044
  async function writeToolsContract(projectDir) {
1116
1045
  return writeAgentsContract(projectDir, GENEX_TOOLS_CONTRACT_BLOCK);
1117
1046
  }
1118
- async function healAncestorContracts(projectDir, stopDir = os4.homedir()) {
1047
+ async function healAncestorContracts(projectDir, stopDir = os3.homedir()) {
1119
1048
  const findings = [];
1120
- const stop = path6.resolve(stopDir);
1121
- let dir = path6.dirname(path6.resolve(projectDir));
1049
+ const stop = path5.resolve(stopDir);
1050
+ let dir = path5.dirname(path5.resolve(projectDir));
1122
1051
  for (let depth = 0; depth < 20; depth++) {
1123
1052
  try {
1124
1053
  let isWorkspace = false;
1125
1054
  for (const marker of ["project.json", "workspace.json"]) {
1126
1055
  try {
1127
- await fs6.access(path6.join(dir, ".genex", marker));
1056
+ await fs5.access(path5.join(dir, ".genex", marker));
1128
1057
  isWorkspace = true;
1129
1058
  break;
1130
1059
  } catch {
1131
1060
  }
1132
1061
  }
1133
1062
  if (!isWorkspace) {
1134
- const agentsPath = path6.join(dir, "AGENTS.md");
1063
+ const agentsPath = path5.join(dir, "AGENTS.md");
1135
1064
  let content = null;
1136
1065
  try {
1137
- content = await fs6.readFile(agentsPath, "utf8");
1066
+ content = await fs5.readFile(agentsPath, "utf8");
1138
1067
  } catch {
1139
1068
  }
1140
1069
  if (content !== null && CONTRACT_REGION.test(content)) {
@@ -1143,11 +1072,11 @@ async function healAncestorContracts(projectDir, stopDir = os4.homedir()) {
1143
1072
  remainder = remainder.replace(CONTRACT_REGION, "");
1144
1073
  }
1145
1074
  if (remainder.trim() === "") {
1146
- await fs6.unlink(agentsPath);
1075
+ await fs5.unlink(agentsPath);
1147
1076
  try {
1148
- const claudePath = path6.join(dir, "CLAUDE.md");
1149
- if ((await fs6.readFile(claudePath, "utf8")).trim() === CLAUDE_IMPORT_LINE) {
1150
- await fs6.unlink(claudePath);
1077
+ const claudePath = path5.join(dir, "CLAUDE.md");
1078
+ if ((await fs5.readFile(claudePath, "utf8")).trim() === CLAUDE_IMPORT_LINE) {
1079
+ await fs5.unlink(claudePath);
1151
1080
  }
1152
1081
  } catch {
1153
1082
  }
@@ -1160,7 +1089,7 @@ async function healAncestorContracts(projectDir, stopDir = os4.homedir()) {
1160
1089
  } catch {
1161
1090
  }
1162
1091
  if (dir === stop) break;
1163
- const parent = path6.dirname(dir);
1092
+ const parent = path5.dirname(dir);
1164
1093
  if (parent === dir) break;
1165
1094
  dir = parent;
1166
1095
  }
@@ -1169,7 +1098,7 @@ async function healAncestorContracts(projectDir, stopDir = os4.homedir()) {
1169
1098
  async function healAncestorContractsAndReport(log, projectDir) {
1170
1099
  try {
1171
1100
  for (const f of await healAncestorContracts(projectDir)) {
1172
- const file = path6.join(f.dir, "AGENTS.md");
1101
+ const file = path5.join(f.dir, "AGENTS.md");
1173
1102
  if (f.removed) {
1174
1103
  log.plain(
1175
1104
  `\u{1F9F9} Removed a stray genex build contract from ${file} \u2014 it belongs in each game's folder, and a stale copy above the project misleads agents.`
@@ -1185,9 +1114,9 @@ async function healAncestorContractsAndReport(log, projectDir) {
1185
1114
  }
1186
1115
 
1187
1116
  // src/lib/updates.ts
1188
- import fs7 from "fs/promises";
1189
- import os5 from "os";
1190
- import path7 from "path";
1117
+ import fs6 from "fs/promises";
1118
+ import os4 from "os";
1119
+ import path6 from "path";
1191
1120
 
1192
1121
  // src/lib/workspace.ts
1193
1122
  async function workspaceMode(cwd = process.cwd()) {
@@ -1245,7 +1174,7 @@ function isNewerVersion(a, b) {
1245
1174
  var SKILLS_VERSION_MARKER = "genex-skills-version.json";
1246
1175
  async function readSkillsMarker(skillsDir) {
1247
1176
  try {
1248
- const raw = await fs7.readFile(path7.join(skillsDir, SKILLS_VERSION_MARKER), "utf8");
1177
+ const raw = await fs6.readFile(path6.join(skillsDir, SKILLS_VERSION_MARKER), "utf8");
1249
1178
  const parsed = JSON.parse(raw);
1250
1179
  return typeof parsed.version === "string" ? parsed.version : null;
1251
1180
  } catch {
@@ -1253,15 +1182,15 @@ async function readSkillsMarker(skillsDir) {
1253
1182
  }
1254
1183
  }
1255
1184
  async function writeSkillsMarker(skillsDir, version = getCliVersion()) {
1256
- await fs7.mkdir(skillsDir, { recursive: true });
1257
- await fs7.writeFile(
1258
- path7.join(skillsDir, SKILLS_VERSION_MARKER),
1185
+ await fs6.mkdir(skillsDir, { recursive: true });
1186
+ await fs6.writeFile(
1187
+ path6.join(skillsDir, SKILLS_VERSION_MARKER),
1259
1188
  JSON.stringify({ version, syncedAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2) + "\n"
1260
1189
  );
1261
1190
  }
1262
1191
  async function hasGenexSkills(skillsDir) {
1263
1192
  try {
1264
- const entries = await fs7.readdir(skillsDir);
1193
+ const entries = await fs6.readdir(skillsDir);
1265
1194
  return entries.some((name) => name.startsWith("genex-"));
1266
1195
  } catch {
1267
1196
  return false;
@@ -1324,14 +1253,14 @@ var REMOVED_SKILLS = [
1324
1253
  async function pruneRemovedSkills(skillsDir, log) {
1325
1254
  const removed = [];
1326
1255
  for (const name of REMOVED_SKILLS) {
1327
- const target = path7.join(skillsDir, name);
1256
+ const target = path6.join(skillsDir, name);
1328
1257
  try {
1329
- await fs7.access(target);
1258
+ await fs6.access(target);
1330
1259
  } catch {
1331
1260
  continue;
1332
1261
  }
1333
1262
  try {
1334
- await fs7.rm(target, { recursive: true });
1263
+ await fs6.rm(target, { recursive: true });
1335
1264
  removed.push(name);
1336
1265
  } catch {
1337
1266
  }
@@ -1346,16 +1275,16 @@ async function pruneRemovedSkills(skillsDir, log) {
1346
1275
  async function pruneToolCards(log, overrides) {
1347
1276
  let removedAny = false;
1348
1277
  for (const target of resolveAgentTargets(overrides ?? {})) {
1349
- const skillsDir = path7.join(target.baseDir, "skills");
1278
+ const skillsDir = path6.join(target.baseDir, "skills");
1350
1279
  for (const name of TOOL_SKILLS) {
1351
- const dir = path7.join(skillsDir, name);
1280
+ const dir = path6.join(skillsDir, name);
1352
1281
  try {
1353
- await fs7.access(dir);
1282
+ await fs6.access(dir);
1354
1283
  } catch {
1355
1284
  continue;
1356
1285
  }
1357
1286
  try {
1358
- await fs7.rm(dir, { recursive: true });
1287
+ await fs6.rm(dir, { recursive: true });
1359
1288
  removedAny = true;
1360
1289
  } catch {
1361
1290
  }
@@ -1369,10 +1298,10 @@ async function pruneToolCards(log, overrides) {
1369
1298
  return removedAny;
1370
1299
  }
1371
1300
  async function syncSkillsForTarget(target, templatesDir = getTemplatesDir(), version = getCliVersion(), log, family = "game") {
1372
- const skillsDir = path7.join(target.baseDir, "skills");
1301
+ const skillsDir = path6.join(target.baseDir, "skills");
1373
1302
  if (!await hasGenexSkills(skillsDir)) return false;
1374
1303
  if (await readSkillsMarker(skillsDir) === version) return false;
1375
- const src = target.full ? templatesDir : path7.join(templatesDir, "skills");
1304
+ const src = target.full ? templatesDir : path6.join(templatesDir, "skills");
1376
1305
  const dest = target.full ? target.baseDir : skillsDir;
1377
1306
  const familyFilter = skillFamilyFilter(family);
1378
1307
  const filter = target.full ? familyFilter : (rel) => familyFilter(`skills/${rel}`);
@@ -1386,36 +1315,36 @@ async function syncSkillsForTarget(target, templatesDir = getTemplatesDir(), ver
1386
1315
  }
1387
1316
  async function cleanupLegacyGlobalSkills(log) {
1388
1317
  try {
1389
- const home = os5.homedir();
1318
+ const home = os4.homedir();
1390
1319
  const [realCwd, realHome] = await Promise.all([
1391
- fs7.realpath(process.cwd()).catch(() => path7.resolve(process.cwd())),
1392
- fs7.realpath(home).catch(() => path7.resolve(home))
1320
+ fs6.realpath(process.cwd()).catch(() => path6.resolve(process.cwd())),
1321
+ fs6.realpath(home).catch(() => path6.resolve(home))
1393
1322
  ]);
1394
1323
  if (realCwd === realHome) return false;
1395
1324
  let removed = false;
1396
1325
  for (const dirName of [".claude", ".codex", ".cursor"]) {
1397
- const skillsDir = path7.join(home, dirName, "skills");
1326
+ const skillsDir = path6.join(home, dirName, "skills");
1398
1327
  let entries = [];
1399
1328
  try {
1400
- entries = await fs7.readdir(skillsDir);
1329
+ entries = await fs6.readdir(skillsDir);
1401
1330
  } catch {
1402
1331
  continue;
1403
1332
  }
1404
1333
  for (const name of entries) {
1405
1334
  if (!name.startsWith("genex-")) continue;
1406
1335
  try {
1407
- await fs7.rm(path7.join(skillsDir, name), { recursive: true });
1336
+ await fs6.rm(path6.join(skillsDir, name), { recursive: true });
1408
1337
  removed = true;
1409
1338
  } catch {
1410
1339
  }
1411
1340
  }
1412
1341
  }
1413
1342
  for (const rel of [
1414
- path7.join("agents", "genex-helper.md"),
1415
- path7.join("commands", "genex-status.md")
1343
+ path6.join("agents", "genex-helper.md"),
1344
+ path6.join("commands", "genex-status.md")
1416
1345
  ]) {
1417
1346
  try {
1418
- await fs7.rm(path7.join(home, ".claude", rel));
1347
+ await fs6.rm(path6.join(home, ".claude", rel));
1419
1348
  removed = true;
1420
1349
  } catch {
1421
1350
  }
@@ -1473,7 +1402,7 @@ var PUBLISHED_PACKAGES = [
1473
1402
  var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
1474
1403
  var REGISTRY_TIMEOUT_MS = 1500;
1475
1404
  function getUpdateCachePath() {
1476
- return path7.join(getGenexDir(), "update-check.json");
1405
+ return path6.join(getGenexDir(), "update-check.json");
1477
1406
  }
1478
1407
  function isCacheFresh(cache, nowMs) {
1479
1408
  const at = Date.parse(cache.checkedAt);
@@ -1486,7 +1415,7 @@ function wasRecentlyNotified(cache, name, latest, nowMs) {
1486
1415
  }
1487
1416
  async function readUpdateCache() {
1488
1417
  try {
1489
- const raw = await fs7.readFile(getUpdateCachePath(), "utf8");
1418
+ const raw = await fs6.readFile(getUpdateCachePath(), "utf8");
1490
1419
  const parsed = JSON.parse(raw);
1491
1420
  if (typeof parsed.checkedAt !== "string" || typeof parsed.latest !== "object") return null;
1492
1421
  return parsed;
@@ -1525,8 +1454,8 @@ function startUpdateCheck() {
1525
1454
  ...cached?.notified ? { notified: cached.notified } : {},
1526
1455
  ...cached?.notifiedAt ? { notifiedAt: cached.notifiedAt } : {}
1527
1456
  };
1528
- await fs7.mkdir(getGenexDir(), { recursive: true });
1529
- await fs7.writeFile(getUpdateCachePath(), JSON.stringify(next, null, 2) + "\n");
1457
+ await fs6.mkdir(getGenexDir(), { recursive: true });
1458
+ await fs6.writeFile(getUpdateCachePath(), JSON.stringify(next, null, 2) + "\n");
1530
1459
  return next;
1531
1460
  } catch {
1532
1461
  return null;
@@ -1535,7 +1464,7 @@ function startUpdateCheck() {
1535
1464
  }
1536
1465
  async function installedPackageVersion(cwd, name) {
1537
1466
  try {
1538
- const raw = await fs7.readFile(path7.join(cwd, "node_modules", name, "package.json"), "utf8");
1467
+ const raw = await fs6.readFile(path6.join(cwd, "node_modules", name, "package.json"), "utf8");
1539
1468
  const pkg = JSON.parse(raw);
1540
1469
  return typeof pkg.version === "string" ? pkg.version : null;
1541
1470
  } catch {
@@ -1582,8 +1511,8 @@ async function recordNotified(cache, announced, cachePath) {
1582
1511
  notified: { ...cache.notified ?? {}, ...announced },
1583
1512
  notifiedAt: (/* @__PURE__ */ new Date()).toISOString()
1584
1513
  };
1585
- await fs7.mkdir(path7.dirname(cachePath), { recursive: true });
1586
- await fs7.writeFile(cachePath, JSON.stringify(next, null, 2) + "\n");
1514
+ await fs6.mkdir(path6.dirname(cachePath), { recursive: true });
1515
+ await fs6.writeFile(cachePath, JSON.stringify(next, null, 2) + "\n");
1587
1516
  } catch {
1588
1517
  }
1589
1518
  }
@@ -1682,13 +1611,13 @@ async function fetchProjectStatus(apiUrl, token, slug) {
1682
1611
  }
1683
1612
 
1684
1613
  // src/lib/ssh.ts
1685
- import fs8 from "fs/promises";
1686
- import path8 from "path";
1614
+ import fs7 from "fs/promises";
1615
+ import path7 from "path";
1687
1616
  async function writeGitignore(dir, log) {
1688
- const file = path8.join(dir, ".gitignore");
1617
+ const file = path7.join(dir, ".gitignore");
1689
1618
  let content = "";
1690
1619
  try {
1691
- content = await fs8.readFile(file, "utf8");
1620
+ content = await fs7.readFile(file, "utf8");
1692
1621
  } catch {
1693
1622
  }
1694
1623
  const present = new Set(content.split("\n").map((l) => l.trim()));
@@ -1698,7 +1627,7 @@ async function writeGitignore(dir, log) {
1698
1627
  if (next.length > 0 && !next.endsWith("\n")) next += "\n";
1699
1628
  if (!content.trim()) next += "# genex local metadata + secrets \u2014 never publish\n";
1700
1629
  next += toAdd.join("\n") + "\n";
1701
- await fs8.writeFile(file, next);
1630
+ await fs7.writeFile(file, next);
1702
1631
  log.dim(`Updated .gitignore (${toAdd.join(", ")}).`);
1703
1632
  }
1704
1633
  var LFS_PATTERNS = [
@@ -1728,10 +1657,10 @@ var LFS_PATTERNS = [
1728
1657
  "*.webm"
1729
1658
  ];
1730
1659
  async function writeGitattributes(dir, log) {
1731
- const file = path8.join(dir, ".gitattributes");
1660
+ const file = path7.join(dir, ".gitattributes");
1732
1661
  let content = "";
1733
1662
  try {
1734
- content = await fs8.readFile(file, "utf8");
1663
+ content = await fs7.readFile(file, "utf8");
1735
1664
  } catch {
1736
1665
  }
1737
1666
  const present = new Set(content.split("\n").map((l) => l.trim().split(/\s+/)[0]));
@@ -1741,13 +1670,13 @@ async function writeGitattributes(dir, log) {
1741
1670
  if (next.length > 0 && !next.endsWith("\n")) next += "\n";
1742
1671
  if (!content.trim()) next += "# Binary game assets go to Git LFS (R2) \u2014 keeps the source push small\n";
1743
1672
  next += toAdd.map((p) => `${p} filter=lfs diff=lfs merge=lfs -text`).join("\n") + "\n";
1744
- await fs8.writeFile(file, next);
1673
+ await fs7.writeFile(file, next);
1745
1674
  log.dim(`Updated .gitattributes (${toAdd.length} binary globs \u2192 Git LFS).`);
1746
1675
  }
1747
1676
 
1748
1677
  // src/lib/game-config.ts
1749
- import fs9 from "fs/promises";
1750
- import path9 from "path";
1678
+ import fs8 from "fs/promises";
1679
+ import path8 from "path";
1751
1680
  var DEFAULT_DASHBOARD_ORIGIN = new URL(DEFAULT_AUTH_URL).origin;
1752
1681
  function renderGenexConfig(slug) {
1753
1682
  return `// src/genex.config.ts \u2014 written by \`genex init\`. DO NOT hardcode URLs here.
@@ -1804,7 +1733,7 @@ ${overrides.join("\n")}
1804
1733
  }
1805
1734
  async function writeIfAbsent(file, content, log) {
1806
1735
  try {
1807
- await fs9.writeFile(file, content, { flag: "wx" });
1736
+ await fs8.writeFile(file, content, { flag: "wx" });
1808
1737
  log.dim(` wrote ${c.cyan(file)}`);
1809
1738
  return true;
1810
1739
  } catch (err) {
@@ -1818,7 +1747,7 @@ async function writeIfAbsent(file, content, log) {
1818
1747
  async function ensureEnvVar(file, key, value, log) {
1819
1748
  let existing = null;
1820
1749
  try {
1821
- existing = await fs9.readFile(file, "utf8");
1750
+ existing = await fs8.readFile(file, "utf8");
1822
1751
  } catch {
1823
1752
  existing = null;
1824
1753
  }
@@ -1829,23 +1758,23 @@ async function ensureEnvVar(file, key, value, log) {
1829
1758
  return false;
1830
1759
  }
1831
1760
  const sep = existing.endsWith("\n") || existing === "" ? "" : "\n";
1832
- await fs9.writeFile(file, `${existing}${sep}${key}=${value}
1761
+ await fs8.writeFile(file, `${existing}${sep}${key}=${value}
1833
1762
  `, "utf8");
1834
1763
  log.dim(` appended ${c.cyan(`${key}=${value}`)} to ${c.cyan(file)}`);
1835
1764
  return true;
1836
1765
  }
1837
- await fs9.writeFile(file, `${key}=${value}
1766
+ await fs8.writeFile(file, `${key}=${value}
1838
1767
  `, "utf8");
1839
1768
  log.dim(` wrote ${c.cyan(file)} (${key})`);
1840
1769
  return true;
1841
1770
  }
1842
1771
  async function writeGameConfigFiles(meta, log, cwd = process.cwd()) {
1843
- await fs9.mkdir(path9.join(cwd, "src"), { recursive: true });
1844
- await writeIfAbsent(path9.join(cwd, "src", "genex.config.ts"), renderGenexConfig(meta.slug), log);
1845
- await writeIfAbsent(path9.join(cwd, ".env"), renderSlugEnv(meta.slug), log);
1772
+ await fs8.mkdir(path8.join(cwd, "src"), { recursive: true });
1773
+ await writeIfAbsent(path8.join(cwd, "src", "genex.config.ts"), renderGenexConfig(meta.slug), log);
1774
+ await writeIfAbsent(path8.join(cwd, ".env"), renderSlugEnv(meta.slug), log);
1846
1775
  const overrides = renderDevOverrides(meta);
1847
1776
  if (overrides) {
1848
- await writeIfAbsent(path9.join(cwd, ".env.development.local"), overrides, log);
1777
+ await writeIfAbsent(path8.join(cwd, ".env.development.local"), overrides, log);
1849
1778
  }
1850
1779
  }
1851
1780
 
@@ -1878,7 +1807,7 @@ var TOOLING_ENTRIES = /* @__PURE__ */ new Set([
1878
1807
  ]);
1879
1808
  async function listPreexistingEntries(dir) {
1880
1809
  try {
1881
- return (await fs10.readdir(dir)).filter((name) => !TOOLING_ENTRIES.has(name)).sort();
1810
+ return (await fs9.readdir(dir)).filter((name) => !TOOLING_ENTRIES.has(name)).sort();
1882
1811
  } catch {
1883
1812
  return [];
1884
1813
  }
@@ -1930,15 +1859,15 @@ async function runInit(opts) {
1930
1859
  let totalNew = 0;
1931
1860
  let totalUpdated = 0;
1932
1861
  for (const t of targets) {
1933
- const src = t.full ? templatesDir : path10.join(templatesDir, "skills");
1934
- const dest = t.full ? t.baseDir : path10.join(t.baseDir, "skills");
1862
+ const src = t.full ? templatesDir : path9.join(templatesDir, "skills");
1863
+ const dest = t.full ? t.baseDir : path9.join(t.baseDir, "skills");
1935
1864
  const { copied, updated } = await copyTemplates(src, dest, {
1936
1865
  force: opts.force,
1937
1866
  exclude: ["controllers", "motion", "asset-viewer"],
1938
1867
  filter: t.full ? skillFamilyFilter("game") : (rel) => skillFamilyFilter("game")(`skills/${rel}`)
1939
1868
  });
1940
- await pruneRemovedSkills(path10.join(t.baseDir, "skills"), log);
1941
- await writeSkillsMarker(path10.join(t.baseDir, "skills"));
1869
+ await pruneRemovedSkills(path9.join(t.baseDir, "skills"), log);
1870
+ await writeSkillsMarker(path9.join(t.baseDir, "skills"));
1942
1871
  const added = copied.length - updated.length;
1943
1872
  totalNew += added;
1944
1873
  totalUpdated += updated.length;
@@ -1989,7 +1918,7 @@ async function runInit(opts) {
1989
1918
  if (email) log.plain(` signed in as ${c.cyan(email)}`);
1990
1919
  };
1991
1920
  await echoIdentity();
1992
- const projectName = opts.name?.trim() || path10.basename(process.cwd());
1921
+ const projectName = opts.name?.trim() || path9.basename(process.cwd());
1993
1922
  const create = (bearer) => createDraftProject({
1994
1923
  apiUrl,
1995
1924
  token: bearer,
@@ -2037,10 +1966,10 @@ async function runInit(opts) {
2037
1966
  log.dim(` saved ${c.cyan(metaPath)}`);
2038
1967
  await writeGameConfigFiles(meta, log);
2039
1968
  if (converting) {
2040
- await fs10.rm(getWorkspacePath(process.cwd()), { force: true }).catch(() => {
1969
+ await fs9.rm(getWorkspacePath(process.cwd()), { force: true }).catch(() => {
2041
1970
  });
2042
1971
  await pruneToolCards(log, { dir: opts.dir, agents: opts.agents });
2043
- await ensureEnvVar(path10.join(process.cwd(), ".env"), "VITE_GENEX_SLUG", meta.slug, log);
1972
+ await ensureEnvVar(path9.join(process.cwd(), ".env"), "VITE_GENEX_SLUG", meta.slug, log);
2044
1973
  await reportCliEvent(apiUrl, token, "tools_converted");
2045
1974
  }
2046
1975
  warnPreexisting(log, preexisting);
@@ -2065,14 +1994,14 @@ async function runInit(opts) {
2065
1994
  }
2066
1995
 
2067
1996
  // src/commands/link.ts
2068
- import fs12 from "fs/promises";
2069
- import os7 from "os";
2070
- import path12 from "path";
2071
-
2072
- // src/lib/source-sync.ts
2073
1997
  import fs11 from "fs/promises";
2074
- import path11 from "path";
2075
1998
  import os6 from "os";
1999
+ import path11 from "path";
2000
+
2001
+ // src/lib/source-sync.ts
2002
+ import fs10 from "fs/promises";
2003
+ import path10 from "path";
2004
+ import os5 from "os";
2076
2005
 
2077
2006
  // src/utils/run.ts
2078
2007
  import { spawn as spawn3 } from "child_process";
@@ -2144,12 +2073,12 @@ function reportStale(log, slug, local, remote) {
2144
2073
  log.dim(` ${c.cyan("npx genex preview --force")} \u2014 keep yours and replace theirs`);
2145
2074
  }
2146
2075
  async function sourceTreeHash(cwd) {
2147
- const gitDir = await fs11.mkdtemp(path11.join(os6.tmpdir(), "genex-tree-"));
2076
+ const gitDir = await fs10.mkdtemp(path10.join(os5.tmpdir(), "genex-tree-"));
2148
2077
  const base = { GIT_DIR: gitDir };
2149
2078
  try {
2150
2079
  if ((await run("git", ["init", "-q"], base)).code !== 0) return null;
2151
- await fs11.writeFile(
2152
- path11.join(gitDir, "info", "exclude"),
2080
+ await fs10.writeFile(
2081
+ path10.join(gitDir, "info", "exclude"),
2153
2082
  ["node_modules/", "dist/", ".git/", ".genex/", ".env", ".env.*", "!.env.example", ""].join("\n")
2154
2083
  );
2155
2084
  if ((await run("git", ["lfs", "version"], base)).code === 0) {
@@ -2161,14 +2090,14 @@ async function sourceTreeHash(cwd) {
2161
2090
  ];
2162
2091
  for (const [key, value] of filters) await run("git", ["config", key, value], base);
2163
2092
  }
2164
- const env = { ...base, GIT_WORK_TREE: cwd, GIT_INDEX_FILE: path11.join(gitDir, "index-tree") };
2093
+ const env = { ...base, GIT_WORK_TREE: cwd, GIT_INDEX_FILE: path10.join(gitDir, "index-tree") };
2165
2094
  if ((await run("git", ["add", "-A"], env)).code !== 0) return null;
2166
2095
  const tree = (await run("git", ["write-tree"], env)).out.trim();
2167
2096
  return /^[0-9a-f]{40}$/.test(tree) ? tree : null;
2168
2097
  } catch {
2169
2098
  return null;
2170
2099
  } finally {
2171
- await fs11.rm(gitDir, { recursive: true, force: true }).catch(() => {
2100
+ await fs10.rm(gitDir, { recursive: true, force: true }).catch(() => {
2172
2101
  });
2173
2102
  }
2174
2103
  }
@@ -2219,7 +2148,7 @@ async function cloneSource(grant, dest, log) {
2219
2148
  log.dim(` git clone exited ${cloned.code}`);
2220
2149
  return false;
2221
2150
  }
2222
- const repo = { ...env, GIT_DIR: path11.join(dest, ".git"), GIT_WORK_TREE: dest };
2151
+ const repo = { ...env, GIT_DIR: path10.join(dest, ".git"), GIT_WORK_TREE: dest };
2223
2152
  const filters = [
2224
2153
  ["filter.lfs.clean", "git-lfs clean -- %f"],
2225
2154
  ["filter.lfs.smudge", "git-lfs smudge -- %f"],
@@ -2315,7 +2244,7 @@ async function runLink(opts) {
2315
2244
  }
2316
2245
  async function isEmptyDir(cwd) {
2317
2246
  try {
2318
- const entries = await fs12.readdir(cwd);
2247
+ const entries = await fs11.readdir(cwd);
2319
2248
  return entries.every((e) => e === ".git" || e === ".genex" || e === ".DS_Store");
2320
2249
  } catch {
2321
2250
  return false;
@@ -2329,13 +2258,13 @@ async function downloadSource(apiUrl, token, project, log) {
2329
2258
  return false;
2330
2259
  }
2331
2260
  log.step(`Downloading the ${grant.sourceRef === "preview" ? "draft" : "published"} source\u2026`);
2332
- const staging = await fs12.mkdtemp(path12.join(os7.tmpdir(), "genex-link-"));
2333
- const fresh = path12.join(staging, "source");
2261
+ const staging = await fs11.mkdtemp(path11.join(os6.tmpdir(), "genex-link-"));
2262
+ const fresh = path11.join(staging, "source");
2334
2263
  try {
2335
2264
  if (!await cloneSource(grant, fresh, log)) return false;
2336
- await fs12.rm(path12.join(fresh, ".git"), { recursive: true, force: true });
2337
- for (const entry of await fs12.readdir(fresh)) {
2338
- await fs12.cp(path12.join(fresh, entry), path12.join(process.cwd(), entry), {
2265
+ await fs11.rm(path11.join(fresh, ".git"), { recursive: true, force: true });
2266
+ for (const entry of await fs11.readdir(fresh)) {
2267
+ await fs11.cp(path11.join(fresh, entry), path11.join(process.cwd(), entry), {
2339
2268
  recursive: true,
2340
2269
  force: true
2341
2270
  });
@@ -2343,28 +2272,28 @@ async function downloadSource(apiUrl, token, project, log) {
2343
2272
  log.success("Downloaded.");
2344
2273
  return true;
2345
2274
  } finally {
2346
- await fs12.rm(staging, { recursive: true, force: true }).catch(() => {
2275
+ await fs11.rm(staging, { recursive: true, force: true }).catch(() => {
2347
2276
  });
2348
2277
  }
2349
2278
  }
2350
2279
  async function ensureSlugEnv(slug, log, cwd = process.cwd()) {
2351
- const file = path12.join(cwd, ".env");
2280
+ const file = path11.join(cwd, ".env");
2352
2281
  let content;
2353
2282
  try {
2354
- content = await fs12.readFile(file, "utf8");
2283
+ content = await fs11.readFile(file, "utf8");
2355
2284
  } catch {
2356
2285
  return;
2357
2286
  }
2358
2287
  const re = /^(\s*VITE_GENEX_SLUG=)(.*)$/m;
2359
2288
  const m = content.match(re);
2360
2289
  if (!m) {
2361
- await fs12.appendFile(file, `${content.endsWith("\n") ? "" : "\n"}VITE_GENEX_SLUG=${slug}
2290
+ await fs11.appendFile(file, `${content.endsWith("\n") ? "" : "\n"}VITE_GENEX_SLUG=${slug}
2362
2291
  `);
2363
2292
  log.dim(` added VITE_GENEX_SLUG=${slug} to .env`);
2364
2293
  return;
2365
2294
  }
2366
2295
  if (m[2].trim() === slug) return;
2367
- await fs12.writeFile(file, content.replace(re, `$1${slug}`));
2296
+ await fs11.writeFile(file, content.replace(re, `$1${slug}`));
2368
2297
  log.dim(` updated .env: VITE_GENEX_SLUG=${slug} (was ${m[2].trim()})`);
2369
2298
  }
2370
2299
  async function fetchOwnProject(apiUrl, token, slug, log) {
@@ -2418,9 +2347,9 @@ async function listOwnSlugs(apiUrl, token, log) {
2418
2347
  }
2419
2348
 
2420
2349
  // src/commands/pull.ts
2421
- import fs13 from "fs/promises";
2422
- import path13 from "path";
2423
- import os8 from "os";
2350
+ import fs12 from "fs/promises";
2351
+ import path12 from "path";
2352
+ import os7 from "os";
2424
2353
  function isMachineLocal(entry) {
2425
2354
  if (entry === ".genex" || entry === "node_modules" || entry === ".git") return true;
2426
2355
  if (entry === ".env.example") return false;
@@ -2479,23 +2408,23 @@ async function runPull(opts) {
2479
2408
  process.exitCode = 1;
2480
2409
  return;
2481
2410
  }
2482
- const staging = await fs13.mkdtemp(path13.join(os8.tmpdir(), "genex-pull-"));
2483
- const fresh = path13.join(staging, "source");
2411
+ const staging = await fs12.mkdtemp(path12.join(os7.tmpdir(), "genex-pull-"));
2412
+ const fresh = path12.join(staging, "source");
2484
2413
  try {
2485
2414
  if (!await cloneSource(grant, fresh, log)) {
2486
2415
  process.exitCode = 1;
2487
2416
  return;
2488
2417
  }
2489
- await fs13.rm(path13.join(fresh, ".git"), { recursive: true, force: true });
2418
+ await fs12.rm(path12.join(fresh, ".git"), { recursive: true, force: true });
2490
2419
  const kept = await keepReplaced(cwd, log);
2491
2420
  await replaceTree(cwd, fresh);
2492
2421
  if (kept) {
2493
2422
  log.plain("");
2494
- log.info(`What was here is kept at ${c.cyan(path13.relative(cwd, kept) || kept)}`);
2423
+ log.info(`What was here is kept at ${c.cyan(path12.relative(cwd, kept) || kept)}`);
2495
2424
  log.dim(" Nothing was thrown away \u2014 re-apply from there, or delete it when you are done.");
2496
2425
  }
2497
2426
  } finally {
2498
- await fs13.rm(staging, { recursive: true, force: true }).catch(() => {
2427
+ await fs12.rm(staging, { recursive: true, force: true }).catch(() => {
2499
2428
  });
2500
2429
  }
2501
2430
  const remote = await readRemoteSource(apiUrl, token, meta.id);
@@ -2514,13 +2443,13 @@ async function runPull(opts) {
2514
2443
  }
2515
2444
  async function keepReplaced(cwd, log) {
2516
2445
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
2517
- const dest = path13.join(cwd, ".genex", `replaced-${stamp}`);
2446
+ const dest = path12.join(cwd, ".genex", `replaced-${stamp}`);
2518
2447
  try {
2519
- const entries = (await fs13.readdir(cwd)).filter((e) => !isMachineLocal(e));
2448
+ const entries = (await fs12.readdir(cwd)).filter((e) => !isMachineLocal(e));
2520
2449
  if (entries.length === 0) return null;
2521
- await fs13.mkdir(dest, { recursive: true });
2450
+ await fs12.mkdir(dest, { recursive: true });
2522
2451
  for (const entry of entries) {
2523
- await fs13.cp(path13.join(cwd, entry), path13.join(dest, entry), { recursive: true });
2452
+ await fs12.cp(path12.join(cwd, entry), path12.join(dest, entry), { recursive: true });
2524
2453
  }
2525
2454
  return dest;
2526
2455
  } catch (err) {
@@ -2530,19 +2459,19 @@ async function keepReplaced(cwd, log) {
2530
2459
  }
2531
2460
  }
2532
2461
  async function replaceTree(dest, src) {
2533
- for (const entry of await fs13.readdir(dest)) {
2462
+ for (const entry of await fs12.readdir(dest)) {
2534
2463
  if (isMachineLocal(entry)) continue;
2535
- await fs13.rm(path13.join(dest, entry), { recursive: true, force: true });
2464
+ await fs12.rm(path12.join(dest, entry), { recursive: true, force: true });
2536
2465
  }
2537
- for (const entry of await fs13.readdir(src)) {
2466
+ for (const entry of await fs12.readdir(src)) {
2538
2467
  if (isMachineLocal(entry)) continue;
2539
- await fs13.cp(path13.join(src, entry), path13.join(dest, entry), { recursive: true });
2468
+ await fs12.cp(path12.join(src, entry), path12.join(dest, entry), { recursive: true });
2540
2469
  }
2541
2470
  }
2542
2471
 
2543
2472
  // src/commands/rename.ts
2544
- import fs14 from "fs/promises";
2545
- import path14 from "path";
2473
+ import fs13 from "fs/promises";
2474
+ import path13 from "path";
2546
2475
  async function runRename(opts) {
2547
2476
  const log = createLogger({ quiet: opts.quiet });
2548
2477
  log.plain(c.bold("genex rename"));
@@ -2630,23 +2559,23 @@ async function runRename(opts) {
2630
2559
  log.info("Run `genex preview` (or `publish`) to rebuild \u2014 the new slug is baked into the bundle.");
2631
2560
  }
2632
2561
  async function rewriteSlugEnv(from, to, log, cwd = process.cwd()) {
2633
- const file = path14.join(cwd, ".env");
2562
+ const file = path13.join(cwd, ".env");
2634
2563
  let content;
2635
2564
  try {
2636
- content = await fs14.readFile(file, "utf8");
2565
+ content = await fs13.readFile(file, "utf8");
2637
2566
  } catch {
2638
2567
  return;
2639
2568
  }
2640
2569
  const re = /^(\s*VITE_GENEX_SLUG=)(.*)$/m;
2641
2570
  if (!re.test(content)) return;
2642
- await fs14.writeFile(file, content.replace(re, `$1${to}`));
2571
+ await fs13.writeFile(file, content.replace(re, `$1${to}`));
2643
2572
  log.dim(` .env: VITE_GENEX_SLUG=${to} (was ${from})`);
2644
2573
  }
2645
2574
  async function rewriteBakedSlug(from, to, log, cwd = process.cwd()) {
2646
- const file = path14.join(cwd, "src", "genex.config.ts");
2575
+ const file = path13.join(cwd, "src", "genex.config.ts");
2647
2576
  let content;
2648
2577
  try {
2649
- content = await fs14.readFile(file, "utf8");
2578
+ content = await fs13.readFile(file, "utf8");
2650
2579
  } catch {
2651
2580
  return;
2652
2581
  }
@@ -2656,7 +2585,7 @@ async function rewriteBakedSlug(from, to, log, cwd = process.cwd()) {
2656
2585
  log.warn(` src/genex.config.ts has no "${from}" literal \u2014 check its slug by hand.`);
2657
2586
  return;
2658
2587
  }
2659
- await fs14.writeFile(file, content.replace(quoted, `"${to}"`));
2588
+ await fs13.writeFile(file, content.replace(quoted, `"${to}"`));
2660
2589
  log.dim(` src/genex.config.ts: baked slug -> ${to}`);
2661
2590
  }
2662
2591
 
@@ -2786,9 +2715,9 @@ function relTime(iso) {
2786
2715
  // src/lib/deploy.ts
2787
2716
  import "child_process";
2788
2717
  import crypto3 from "crypto";
2789
- import fs17 from "fs/promises";
2790
- import os9 from "os";
2791
- import path16 from "path";
2718
+ import fs16 from "fs/promises";
2719
+ import os8 from "os";
2720
+ import path15 from "path";
2792
2721
 
2793
2722
  // ../../packages/mobile-scan/src/image-dims.ts
2794
2723
  function u32be(b, o) {
@@ -3052,12 +2981,12 @@ function tierFor(estVramMb) {
3052
2981
  }
3053
2982
 
3054
2983
  // src/commands/ui.ts
3055
- import fs16 from "fs/promises";
3056
- import path15 from "path";
2984
+ import fs15 from "fs/promises";
2985
+ import path14 from "path";
3057
2986
  import { PNG as PNG2 } from "pngjs";
3058
2987
 
3059
2988
  // src/lib/png-tools.ts
3060
- import fs15 from "fs/promises";
2989
+ import fs14 from "fs/promises";
3061
2990
  import { PNG } from "pngjs";
3062
2991
  var ALPHA_TRANSPARENT_MAX = 16;
3063
2992
  var isHttpUrl = (s) => /^https?:\/\//i.test(s);
@@ -3068,12 +2997,12 @@ async function loadPng(input) {
3068
2997
  if (!res.ok) throw new Error(`Couldn't fetch ${input} (HTTP ${res.status}).`);
3069
2998
  buf = Buffer.from(await res.arrayBuffer());
3070
2999
  } else {
3071
- buf = await fs15.readFile(input);
3000
+ buf = await fs14.readFile(input);
3072
3001
  }
3073
3002
  return PNG.sync.read(buf);
3074
3003
  }
3075
3004
  async function writePng(file, png) {
3076
- await fs15.writeFile(file, PNG.sync.write(png));
3005
+ await fs14.writeFile(file, PNG.sync.write(png));
3077
3006
  }
3078
3007
  function cropPng(image, box) {
3079
3008
  const out = new PNG({ width: box.w, height: box.h });
@@ -3373,7 +3302,7 @@ async function uiExtract(opts, log) {
3373
3302
  const dilatePx = opts.dilate ?? 0;
3374
3303
  const sheet = await loadPng(input);
3375
3304
  const { width: W, height: H, data } = sheet;
3376
- await fs16.mkdir(outDir, { recursive: true });
3305
+ await fs15.mkdir(outDir, { recursive: true });
3377
3306
  log.plain(c.bold("genex ui extract"));
3378
3307
  log.dim(` ${input} (${W}x${H}) \u2192 ${outDir}, ${names.length} names`);
3379
3308
  let hasTransparency = false;
@@ -3602,7 +3531,7 @@ async function uiExtract(opts, log) {
3602
3531
  rimPixels: speckle.sampled
3603
3532
  });
3604
3533
  }
3605
- const outPath = path15.join(outDir, `${name}.png`);
3534
+ const outPath = path14.join(outDir, `${name}.png`);
3606
3535
  await writePng(outPath, out);
3607
3536
  const sidecar = {
3608
3537
  name,
@@ -3621,7 +3550,7 @@ async function uiExtract(opts, log) {
3621
3550
  defringed
3622
3551
  };
3623
3552
  const { name: _n, out: _o, ...sidecarBody } = sidecar;
3624
- await fs16.writeFile(
3553
+ await fs15.writeFile(
3625
3554
  outPath.replace(/\.png$/i, "") + ".bbox.json",
3626
3555
  JSON.stringify(sidecarBody, null, 2)
3627
3556
  );
@@ -3630,8 +3559,8 @@ async function uiExtract(opts, log) {
3630
3559
  `${name}.png ${cropW}x${cropH} (ar ${sidecar.aspectRatio}) at sheet ${padX0},${padY0}`
3631
3560
  );
3632
3561
  }
3633
- const debugPath = path15.join(outDir, "extract-debug.json");
3634
- await fs16.writeFile(
3562
+ const debugPath = path14.join(outDir, "extract-debug.json");
3563
+ await fs15.writeFile(
3635
3564
  debugPath,
3636
3565
  JSON.stringify(
3637
3566
  {
@@ -4093,7 +4022,7 @@ async function uiMasks(opts, log) {
4093
4022
  const registrationTolerance = opts.registrationTolerance ?? 0.04;
4094
4023
  const edgeFlushMax = opts.edgeFlushMax ?? 0.04;
4095
4024
  const loosened = registrationTolerance > 0.04 || edgeFlushMax > 0.04 || minCoverage < 0.01 || maxCoverage > 0.85;
4096
- await fs16.mkdir(outDir, { recursive: true });
4025
+ await fs15.mkdir(outDir, { recursive: true });
4097
4026
  log.plain(c.bold("genex ui masks"));
4098
4027
  log.dim(` ${input} (${image.width}x${image.height}), ${pairs.length} pair(s) \u2192 ${outDir}`);
4099
4028
  const sheetComponents = detectSheetComponents(image, opts.minPixels ?? 2e3);
@@ -4146,11 +4075,11 @@ async function uiMasks(opts, log) {
4146
4075
  });
4147
4076
  }
4148
4077
  const overlay = makeOverlay(clean2, converted.png);
4149
- const framePath = path15.join(outDir, `${pair.name}-frame.png`);
4150
- const maskPath = path15.join(outDir, `${pair.name}-mask.png`);
4151
- const annotatedPath = path15.join(outDir, `${pair.name}-annotated-source.png`);
4152
- const overlayPath = path15.join(outDir, `${pair.name}-overlay.png`);
4153
- const metaPath = path15.join(outDir, `${pair.name}.annotated-progress.json`);
4078
+ const framePath = path14.join(outDir, `${pair.name}-frame.png`);
4079
+ const maskPath = path14.join(outDir, `${pair.name}-mask.png`);
4080
+ const annotatedPath = path14.join(outDir, `${pair.name}-annotated-source.png`);
4081
+ const overlayPath = path14.join(outDir, `${pair.name}-overlay.png`);
4082
+ const metaPath = path14.join(outDir, `${pair.name}.annotated-progress.json`);
4154
4083
  await writePng(framePath, clean2);
4155
4084
  await writePng(maskPath, converted.png);
4156
4085
  await writePng(annotatedPath, annotated);
@@ -4197,7 +4126,7 @@ async function uiMasks(opts, log) {
4197
4126
  },
4198
4127
  overlay: overlayPath
4199
4128
  };
4200
- await fs16.writeFile(metaPath, `${JSON.stringify(meta, null, 2)}
4129
+ await fs15.writeFile(metaPath, `${JSON.stringify(meta, null, 2)}
4201
4130
  `);
4202
4131
  results.push(meta);
4203
4132
  const fb = converted.bbox;
@@ -4213,8 +4142,8 @@ async function uiMasks(opts, log) {
4213
4142
  );
4214
4143
  }
4215
4144
  }
4216
- const indexPath = path15.join(outDir, "annotated-progress.json");
4217
- await fs16.writeFile(indexPath, `${JSON.stringify({ input, loosened, pairs: results }, null, 2)}
4145
+ const indexPath = path14.join(outDir, "annotated-progress.json");
4146
+ await fs15.writeFile(indexPath, `${JSON.stringify({ input, loosened, pairs: results }, null, 2)}
4218
4147
  `);
4219
4148
  log.plain("");
4220
4149
  log.success(`Wrote ${pairs.length} frame+mask pair(s) \u2192 ${outDir} (index: ${indexPath}).`);
@@ -4340,7 +4269,7 @@ async function uiTextColor(opts, log) {
4340
4269
  };
4341
4270
  process.stdout.write(`${JSON.stringify(result, null, 2)}
4342
4271
  `);
4343
- if (opts.out) await fs16.writeFile(opts.out, `${JSON.stringify(result, null, 2)}
4272
+ if (opts.out) await fs15.writeFile(opts.out, `${JSON.stringify(result, null, 2)}
4344
4273
  `);
4345
4274
  if (opts.cropPath) log.dim(` crop \u2192 ${opts.cropPath}`);
4346
4275
  }
@@ -4372,7 +4301,7 @@ async function uiTrim(opts, log) {
4372
4301
  const sidecar = computeBBoxes(trimmed);
4373
4302
  const speckleAllowed = !!(speckle && speckle.ratio > SPECKLE_MAX_RATIO);
4374
4303
  const sidecarPath = outPath.replace(/\.png$/i, "") + ".bbox.json";
4375
- await fs16.writeFile(
4304
+ await fs15.writeFile(
4376
4305
  sidecarPath,
4377
4306
  JSON.stringify(speckleAllowed ? { ...sidecar, speckleAllowed: true } : sidecar, null, 2)
4378
4307
  );
@@ -4479,7 +4408,7 @@ async function uiPlate(opts, log) {
4479
4408
  fail("No interior found \u2014 the image is fully transparent (or erode ate everything). Check --in / lower --erode.");
4480
4409
  }
4481
4410
  await writePng(outPath, out);
4482
- const name = path15.basename(outPath);
4411
+ const name = path14.basename(outPath);
4483
4412
  log.plain(c.bold("genex ui plate"));
4484
4413
  log.success(`${outPath} ${W}x${H}, interior ${(count / (W * H) * 100).toFixed(1)}% (erode ${erode}px)`);
4485
4414
  log.dim(" Wire it as the plate's silhouette (same box as the frame <img>, plate UNDER the art):");
@@ -4644,13 +4573,13 @@ async function walkFiles(dir) {
4644
4573
  const out = [];
4645
4574
  let entries;
4646
4575
  try {
4647
- entries = await fs16.readdir(dir, { withFileTypes: true });
4576
+ entries = await fs15.readdir(dir, { withFileTypes: true });
4648
4577
  } catch {
4649
4578
  return out;
4650
4579
  }
4651
4580
  for (const entry of entries) {
4652
4581
  if (entry.name === "node_modules" || entry.name.startsWith(".")) continue;
4653
- const p = path15.join(dir, entry.name);
4582
+ const p = path14.join(dir, entry.name);
4654
4583
  if (entry.isDirectory()) out.push(...await walkFiles(p));
4655
4584
  else out.push(p);
4656
4585
  }
@@ -4659,7 +4588,7 @@ async function walkFiles(dir) {
4659
4588
  async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
4660
4589
  const viewportFindings = [];
4661
4590
  try {
4662
- const indexHtml = await fs16.readFile(path15.join(cwd, "index.html"), "utf8");
4591
+ const indexHtml = await fs15.readFile(path14.join(cwd, "index.html"), "utf8");
4663
4592
  if (!/<meta[^>]+name=["']viewport["']/i.test(indexHtml)) {
4664
4593
  viewportFindings.push({
4665
4594
  kind: "viewport-meta",
@@ -4673,28 +4602,28 @@ async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
4673
4602
  }
4674
4603
  } catch {
4675
4604
  }
4676
- const absAssets = path15.resolve(cwd, assetDir);
4605
+ const absAssets = path14.resolve(cwd, assetDir);
4677
4606
  try {
4678
- if (!(await fs16.stat(absAssets)).isDirectory()) {
4607
+ if (!(await fs15.stat(absAssets)).isDirectory()) {
4679
4608
  return viewportFindings.length > 0 ? viewportFindings : null;
4680
4609
  }
4681
4610
  } catch {
4682
4611
  return viewportFindings.length > 0 ? viewportFindings : null;
4683
4612
  }
4684
- const srcFiles = (await walkFiles(path15.resolve(cwd, srcDir))).filter(
4685
- (p) => AUDIT_SRC_EXTS.has(path15.extname(p).toLowerCase())
4613
+ const srcFiles = (await walkFiles(path14.resolve(cwd, srcDir))).filter(
4614
+ (p) => AUDIT_SRC_EXTS.has(path14.extname(p).toLowerCase())
4686
4615
  );
4687
4616
  try {
4688
- for (const name of await fs16.readdir(cwd)) {
4689
- const ext = path15.extname(name).toLowerCase();
4690
- if (ext === ".html" || ext === ".css") srcFiles.push(path15.join(cwd, name));
4617
+ for (const name of await fs15.readdir(cwd)) {
4618
+ const ext = path14.extname(name).toLowerCase();
4619
+ if (ext === ".html" || ext === ".css") srcFiles.push(path14.join(cwd, name));
4691
4620
  }
4692
4621
  } catch {
4693
4622
  }
4694
4623
  const sources = [];
4695
4624
  for (const p of srcFiles) {
4696
4625
  try {
4697
- sources.push({ rel: path15.relative(cwd, p), text: await fs16.readFile(p, "utf8") });
4626
+ sources.push({ rel: path14.relative(cwd, p), text: await fs15.readFile(p, "utf8") });
4698
4627
  } catch {
4699
4628
  }
4700
4629
  }
@@ -4704,11 +4633,11 @@ async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
4704
4633
  const metaByName = /* @__PURE__ */ new Map();
4705
4634
  const bboxByPng = /* @__PURE__ */ new Map();
4706
4635
  for (const p of assetFiles) {
4707
- const base = path15.basename(p);
4636
+ const base = path14.basename(p);
4708
4637
  const metaMatch = /^(.+)\.annotated-progress\.json$/.exec(base);
4709
4638
  if (metaMatch) {
4710
4639
  try {
4711
- const meta = JSON.parse(await fs16.readFile(p, "utf8"));
4640
+ const meta = JSON.parse(await fs15.readFile(p, "utf8"));
4712
4641
  metaByName.set(metaMatch[1], {
4713
4642
  cleanCrop: meta.clean?.crop ?? null,
4714
4643
  loosened: meta.loosened === true
@@ -4719,7 +4648,7 @@ async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
4719
4648
  }
4720
4649
  if (base.endsWith(".bbox.json")) {
4721
4650
  try {
4722
- const sidecar = JSON.parse(await fs16.readFile(p, "utf8"));
4651
+ const sidecar = JSON.parse(await fs15.readFile(p, "utf8"));
4723
4652
  if (sidecar.sheetBBox) bboxByPng.set(base.replace(/\.bbox\.json$/, ".png"), sidecar.sheetBBox);
4724
4653
  } catch {
4725
4654
  }
@@ -4728,7 +4657,7 @@ async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
4728
4657
  for (const [name, meta] of metaByName) {
4729
4658
  if (!meta.cleanCrop || !referenced(`${name}-mask.png`)) continue;
4730
4659
  for (const p of assetFiles) {
4731
- const base = path15.basename(p);
4660
+ const base = path14.basename(p);
4732
4661
  if (!base.toLowerCase().endsWith(".png")) continue;
4733
4662
  if (base !== `${name}.png` && !base.startsWith(`${name}-`)) continue;
4734
4663
  if (/-mask\.png$|-frame\.png$|-overlay\.png$|-annotated-source\.png$/.test(base)) continue;
@@ -4752,7 +4681,7 @@ async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
4752
4681
  }
4753
4682
  const pngByBase = /* @__PURE__ */ new Map();
4754
4683
  for (const p of assetFiles) {
4755
- const base = path15.basename(p);
4684
+ const base = path14.basename(p);
4756
4685
  if (base.toLowerCase().endsWith(".png")) pngByBase.set(base, p);
4757
4686
  }
4758
4687
  for (const [maskBase, maskPath] of pngByBase) {
@@ -4765,8 +4694,8 @@ async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
4765
4694
  let frame;
4766
4695
  let mask;
4767
4696
  try {
4768
- frame = PNG2.sync.read(await fs16.readFile(framePath));
4769
- mask = PNG2.sync.read(await fs16.readFile(maskPath));
4697
+ frame = PNG2.sync.read(await fs15.readFile(framePath));
4698
+ mask = PNG2.sync.read(await fs15.readFile(maskPath));
4770
4699
  } catch {
4771
4700
  continue;
4772
4701
  }
@@ -4785,7 +4714,7 @@ async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
4785
4714
  if (!referenced(base)) continue;
4786
4715
  let png;
4787
4716
  try {
4788
- png = PNG2.sync.read(await fs16.readFile(p));
4717
+ png = PNG2.sync.read(await fs15.readFile(p));
4789
4718
  } catch {
4790
4719
  continue;
4791
4720
  }
@@ -4805,7 +4734,7 @@ async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
4805
4734
  }
4806
4735
  const maskReported = /* @__PURE__ */ new Set();
4807
4736
  for (const p of assetFiles) {
4808
- const m = /^(.+)\.annotated-progress\.json$/.exec(path15.basename(p));
4737
+ const m = /^(.+)\.annotated-progress\.json$/.exec(path14.basename(p));
4809
4738
  if (!m) continue;
4810
4739
  const maskBase = `${m[1]}-mask.png`;
4811
4740
  if (!referenced(maskBase)) {
@@ -4817,14 +4746,14 @@ async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
4817
4746
  }
4818
4747
  }
4819
4748
  for (const p of assetFiles) {
4820
- const base = path15.basename(p);
4749
+ const base = path14.basename(p);
4821
4750
  if (!base.toLowerCase().endsWith(".png")) continue;
4822
4751
  if (/-annotated-source\.png$|-overlay\.png$/.test(base)) continue;
4823
4752
  if (maskReported.has(base)) continue;
4824
4753
  if (!referenced(base)) {
4825
4754
  findings.push({
4826
4755
  kind: "unwired-sprite",
4827
- message: `${path15.relative(cwd, p)} is on disk but never referenced in ${srcDir}/index.html/CSS \u2014 wire it, or record the one-line reason it was cut. (Computed-string references are invisible here \u2014 check.)`
4756
+ message: `${path14.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.)`
4828
4757
  });
4829
4758
  }
4830
4759
  }
@@ -5011,7 +4940,7 @@ async function printUiAuditPreflight(log) {
5011
4940
  }
5012
4941
  async function printPipelineStatePreflight(log, cwd = process.cwd()) {
5013
4942
  try {
5014
- const design = await fs17.readFile(path16.join(cwd, "DESIGN.md"), "utf8").catch(() => "");
4943
+ const design = await fs16.readFile(path15.join(cwd, "DESIGN.md"), "utf8").catch(() => "");
5015
4944
  const warnings = [];
5016
4945
  if (design.length > 0 && !/##\s*build plan & status/i.test(design)) {
5017
4946
  warnings.push(
@@ -5019,7 +4948,7 @@ async function printPipelineStatePreflight(log, cwd = process.cwd()) {
5019
4948
  );
5020
4949
  }
5021
4950
  if (!/player character:/i.test(design)) {
5022
- const hasCharacter = await fs17.access(path16.join(cwd, "public", "assets", "meshy-character.json")).then(() => true, () => false);
4951
+ const hasCharacter = await fs16.access(path15.join(cwd, "public", "assets", "meshy-character.json")).then(() => true, () => false);
5023
4952
  if (!hasCharacter && await loadsPlayerBody(cwd)) {
5024
4953
  warnings.push(
5025
4954
  `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).`
@@ -5033,12 +4962,12 @@ async function printPipelineStatePreflight(log, cwd = process.cwd()) {
5033
4962
  async function loadsPlayerBody(cwd) {
5034
4963
  const BODY_LOADERS = /\bloadPlayerCharacter\s*\(|\bloadVrm(?:Clone)?\s*\(/;
5035
4964
  try {
5036
- const entries = await fs17.readdir(path16.join(cwd, "src"), { recursive: true });
4965
+ const entries = await fs16.readdir(path15.join(cwd, "src"), { recursive: true });
5037
4966
  for (const rel of entries) {
5038
4967
  if (rel.includes("node_modules")) continue;
5039
- if (rel.split(path16.sep)[0] === "controllers" || rel.startsWith("controllers/")) continue;
4968
+ if (rel.split(path15.sep)[0] === "controllers" || rel.startsWith("controllers/")) continue;
5040
4969
  if (!/\.(ts|tsx|js|jsx)$/.test(rel)) continue;
5041
- const text = await fs17.readFile(path16.join(cwd, "src", rel), "utf8").catch(() => "");
4970
+ const text = await fs16.readFile(path15.join(cwd, "src", rel), "utf8").catch(() => "");
5042
4971
  if (BODY_LOADERS.test(text)) return true;
5043
4972
  }
5044
4973
  } catch {
@@ -5075,9 +5004,9 @@ async function deployGame(ctx, opts, log) {
5075
5004
  }
5076
5005
  log.success("Built.");
5077
5006
  }
5078
- const distDir = path16.join(cwd, "dist");
5079
- const siteDir = await isDir2(distDir) ? distDir : cwd;
5080
- const rel = path16.relative(cwd, siteDir) || ".";
5007
+ const distDir = path15.join(cwd, "dist");
5008
+ const siteDir = await isDir(distDir) ? distDir : cwd;
5009
+ const rel = path15.relative(cwd, siteDir) || ".";
5081
5010
  if (siteDir === cwd) await writeGitignore(cwd, log);
5082
5011
  const files = await collectFiles(siteDir);
5083
5012
  if (files.length === 0) {
@@ -5166,7 +5095,7 @@ async function deployGame(ctx, opts, log) {
5166
5095
  }
5167
5096
  async function hasBuildScript(cwd) {
5168
5097
  try {
5169
- const pkg = JSON.parse(await fs17.readFile(path16.join(cwd, "package.json"), "utf8"));
5098
+ const pkg = JSON.parse(await fs16.readFile(path15.join(cwd, "package.json"), "utf8"));
5170
5099
  return Boolean(pkg.scripts?.build);
5171
5100
  } catch {
5172
5101
  return false;
@@ -5175,12 +5104,12 @@ async function hasBuildScript(cwd) {
5175
5104
  async function collectFiles(root) {
5176
5105
  const out = [];
5177
5106
  const walk2 = async (dir, prefix) => {
5178
- for (const e of await fs17.readdir(dir, { withFileTypes: true })) {
5107
+ for (const e of await fs16.readdir(dir, { withFileTypes: true })) {
5179
5108
  const relPath = prefix ? `${prefix}/${e.name}` : e.name;
5180
5109
  if (e.isDirectory()) {
5181
- if (!EXCLUDE_DIRS.has(e.name)) await walk2(path16.join(dir, e.name), relPath);
5110
+ if (!EXCLUDE_DIRS.has(e.name)) await walk2(path15.join(dir, e.name), relPath);
5182
5111
  } else if (e.isFile() && !isSecretEnvFile(e.name)) {
5183
- out.push({ relPath, bytes: await fs17.readFile(path16.join(dir, e.name)) });
5112
+ out.push({ relPath, bytes: await fs16.readFile(path15.join(dir, e.name)) });
5184
5113
  }
5185
5114
  }
5186
5115
  };
@@ -5422,7 +5351,7 @@ async function pushWorktree(cwd, pushUrl, managed, log, branch = "main", onCommi
5422
5351
  log.error("Couldn't save your game's source \u2014 please try again.");
5423
5352
  return false;
5424
5353
  };
5425
- const gitDir = await fs17.mkdtemp(path16.join(os9.tmpdir(), "genex-source-"));
5354
+ const gitDir = await fs16.mkdtemp(path15.join(os8.tmpdir(), "genex-source-"));
5426
5355
  const base = { GIT_DIR: gitDir };
5427
5356
  if (urlHasEmbeddedCredentials(pushUrl)) {
5428
5357
  base.GIT_CONFIG_COUNT = "1";
@@ -5437,12 +5366,12 @@ async function pushWorktree(cwd, pushUrl, managed, log, branch = "main", onCommi
5437
5366
  };
5438
5367
  try {
5439
5368
  if ((await run("git", ["init", "-q"], base)).code !== 0) return failed();
5440
- await fs17.writeFile(
5441
- path16.join(gitDir, "info", "exclude"),
5369
+ await fs16.writeFile(
5370
+ path15.join(gitDir, "info", "exclude"),
5442
5371
  // .env* are secrets — never publish them; `!` keeps the non-secret template.
5443
5372
  ["node_modules/", "dist/", ".git/", ".genex/", ".env", ".env.*", "!.env.example", ""].join("\n")
5444
5373
  );
5445
- const env = { ...base, GIT_WORK_TREE: cwd, GIT_INDEX_FILE: path16.join(gitDir, "index-source") };
5374
+ const env = { ...base, GIT_WORK_TREE: cwd, GIT_INDEX_FILE: path15.join(gitDir, "index-source") };
5446
5375
  let lfs = (await run("git", ["lfs", "version"], base)).code !== 0 ? false : true;
5447
5376
  if (!lfs) {
5448
5377
  log.step("Installing git-lfs (keeps large binary assets out of the source push)\u2026");
@@ -5494,7 +5423,7 @@ async function pushWorktree(cwd, pushUrl, managed, log, branch = "main", onCommi
5494
5423
  } catch {
5495
5424
  return failed();
5496
5425
  } finally {
5497
- await fs17.rm(gitDir, { recursive: true, force: true }).catch(() => {
5426
+ await fs16.rm(gitDir, { recursive: true, force: true }).catch(() => {
5498
5427
  });
5499
5428
  }
5500
5429
  }
@@ -5544,9 +5473,9 @@ async function fetchPushUrl(ctx, log, expectStagingCommit) {
5544
5473
  }
5545
5474
  return { pushUrl: data.pushUrl, managed: data.managed !== false, sourceRef: data.sourceRef ?? null };
5546
5475
  }
5547
- async function isDir2(p) {
5476
+ async function isDir(p) {
5548
5477
  try {
5549
- return (await fs17.stat(p)).isDirectory();
5478
+ return (await fs16.stat(p)).isDirectory();
5550
5479
  } catch {
5551
5480
  return false;
5552
5481
  }
@@ -6028,17 +5957,17 @@ async function promoteBuild(apiUrl, projectId, token, log) {
6028
5957
  }
6029
5958
 
6030
5959
  // src/lib/detect-features.ts
6031
- import fs19 from "fs/promises";
6032
- import path18 from "path";
6033
-
6034
- // src/lib/generation-ledger.ts
6035
5960
  import fs18 from "fs/promises";
6036
5961
  import path17 from "path";
6037
- var ledgerPath = (cwd) => path17.join(cwd, ".genex", "generations.ndjson");
5962
+
5963
+ // src/lib/generation-ledger.ts
5964
+ import fs17 from "fs/promises";
5965
+ import path16 from "path";
5966
+ var ledgerPath = (cwd) => path16.join(cwd, ".genex", "generations.ndjson");
6038
5967
  async function append(cwd, event) {
6039
5968
  try {
6040
- await fs18.access(path17.join(cwd, ".genex"));
6041
- await fs18.appendFile(ledgerPath(cwd), `${JSON.stringify(event)}
5969
+ await fs17.access(path16.join(cwd, ".genex"));
5970
+ await fs17.appendFile(ledgerPath(cwd), `${JSON.stringify(event)}
6042
5971
  `, "utf8");
6043
5972
  } catch {
6044
5973
  }
@@ -6046,7 +5975,7 @@ async function append(cwd, event) {
6046
5975
  async function readLedger(cwd = process.cwd()) {
6047
5976
  let raw;
6048
5977
  try {
6049
- raw = await fs18.readFile(ledgerPath(cwd), "utf8");
5978
+ raw = await fs17.readFile(ledgerPath(cwd), "utf8");
6050
5979
  } catch {
6051
5980
  return [];
6052
5981
  }
@@ -6107,7 +6036,7 @@ async function countOutcomes(kind, cwd = process.cwd()) {
6107
6036
  // src/lib/detect-features.ts
6108
6037
  async function detectEmbedSdkVersion(cwd = process.cwd()) {
6109
6038
  try {
6110
- const raw = await fs19.readFile(path18.join(cwd, "package.json"), "utf8");
6039
+ const raw = await fs18.readFile(path17.join(cwd, "package.json"), "utf8");
6111
6040
  const pkg = JSON.parse(raw);
6112
6041
  const version = pkg.dependencies?.["@genex-ai/embed-sdk"] ?? pkg.devDependencies?.["@genex-ai/embed-sdk"];
6113
6042
  return typeof version === "string" && version ? version : null;
@@ -6117,7 +6046,7 @@ async function detectEmbedSdkVersion(cwd = process.cwd()) {
6117
6046
  }
6118
6047
  async function detectMultiplayer(cwd = process.cwd()) {
6119
6048
  try {
6120
- const raw = await fs19.readFile(path18.join(cwd, "package.json"), "utf8");
6049
+ const raw = await fs18.readFile(path17.join(cwd, "package.json"), "utf8");
6121
6050
  const pkg = JSON.parse(raw);
6122
6051
  return Boolean(
6123
6052
  pkg.dependencies?.["@genex-ai/multiplayer"] ?? pkg.devDependencies?.["@genex-ai/multiplayer"]
@@ -6129,7 +6058,7 @@ async function detectMultiplayer(cwd = process.cwd()) {
6129
6058
  async function detectMatchmaking(log, cwd = process.cwd()) {
6130
6059
  let pkg;
6131
6060
  try {
6132
- pkg = JSON.parse(await fs19.readFile(path18.join(cwd, "package.json"), "utf8"));
6061
+ pkg = JSON.parse(await fs18.readFile(path17.join(cwd, "package.json"), "utf8"));
6133
6062
  } catch (err) {
6134
6063
  log.dim(` (skipping matchmaking \u2014 couldn't read package.json: ${String(err)})`);
6135
6064
  return null;
@@ -6147,15 +6076,15 @@ async function detectMatchmaking(log, cwd = process.cwd()) {
6147
6076
  var TOUCH_KIT_MARKERS = /controllers\/touch\/|controllers\/character\/touch-joystick|TouchJoystick|VirtualButton|DragZone|RotateOverlay/;
6148
6077
  async function detectMobileControls(cwd = process.cwd()) {
6149
6078
  try {
6150
- const raw = await fs19.readFile(path18.join(cwd, "package.json"), "utf8");
6079
+ const raw = await fs18.readFile(path17.join(cwd, "package.json"), "utf8");
6151
6080
  const pkg = JSON.parse(raw);
6152
6081
  if (pkg.genex?.mobileControls === true) return true;
6153
6082
  } catch {
6154
6083
  }
6155
- const srcDir = path18.join(cwd, "src");
6084
+ const srcDir = path17.join(cwd, "src");
6156
6085
  let entries;
6157
6086
  try {
6158
- entries = await fs19.readdir(srcDir, { recursive: true });
6087
+ entries = await fs18.readdir(srcDir, { recursive: true });
6159
6088
  } catch {
6160
6089
  return false;
6161
6090
  }
@@ -6163,7 +6092,7 @@ async function detectMobileControls(cwd = process.cwd()) {
6163
6092
  if (rel.includes("node_modules")) continue;
6164
6093
  if (!/\.(ts|js|mts|mjs|tsx|jsx)$/.test(rel)) continue;
6165
6094
  try {
6166
- const content = await fs19.readFile(path18.join(srcDir, rel), "utf8");
6095
+ const content = await fs18.readFile(path17.join(srcDir, rel), "utf8");
6167
6096
  if (TOUCH_KIT_MARKERS.test(content)) return true;
6168
6097
  } catch {
6169
6098
  }
@@ -6172,10 +6101,10 @@ async function detectMobileControls(cwd = process.cwd()) {
6172
6101
  }
6173
6102
  var GAME_STATE_CALLS = /savePlayerState|saveWorldState|submitScore/;
6174
6103
  async function detectGameStateUsage(cwd = process.cwd()) {
6175
- const srcDir = path18.join(cwd, "src");
6104
+ const srcDir = path17.join(cwd, "src");
6176
6105
  let entries;
6177
6106
  try {
6178
- entries = await fs19.readdir(srcDir, { recursive: true });
6107
+ entries = await fs18.readdir(srcDir, { recursive: true });
6179
6108
  } catch {
6180
6109
  return false;
6181
6110
  }
@@ -6183,7 +6112,7 @@ async function detectGameStateUsage(cwd = process.cwd()) {
6183
6112
  if (rel.includes("node_modules")) continue;
6184
6113
  if (!/\.(ts|js|mts|mjs|tsx|jsx)$/.test(rel)) continue;
6185
6114
  try {
6186
- const content = await fs19.readFile(path18.join(srcDir, rel), "utf8");
6115
+ const content = await fs18.readFile(path17.join(srcDir, rel), "utf8");
6187
6116
  if (GAME_STATE_CALLS.test(content)) return true;
6188
6117
  } catch {
6189
6118
  }
@@ -6233,19 +6162,19 @@ async function detectSurfaceScan(cwd = process.cwd()) {
6233
6162
  mediaElementAudio: [],
6234
6163
  deferredAudioContext: []
6235
6164
  };
6236
- const srcDir = path18.join(cwd, "src");
6165
+ const srcDir = path17.join(cwd, "src");
6237
6166
  let entries;
6238
6167
  try {
6239
- entries = await fs19.readdir(srcDir, { recursive: true });
6168
+ entries = await fs18.readdir(srcDir, { recursive: true });
6240
6169
  } catch {
6241
6170
  return found;
6242
6171
  }
6243
6172
  for (const nativeRel of entries) {
6244
6173
  if (nativeRel.includes("node_modules")) continue;
6245
6174
  if (!/\.(ts|js|mts|mjs|tsx|jsx)$/.test(nativeRel)) continue;
6246
- const raw = await fs19.readFile(path18.join(srcDir, nativeRel), "utf8").catch(() => "");
6175
+ const raw = await fs18.readFile(path17.join(srcDir, nativeRel), "utf8").catch(() => "");
6247
6176
  if (!raw) continue;
6248
- const rel = nativeRel.split(path18.sep).join("/");
6177
+ const rel = nativeRel.split(path17.sep).join("/");
6249
6178
  const content = raw.replace(/\/\*[\s\S]*?\*\//g, (m2) => m2.replace(/[^\n]/g, " ")).replace(/(^|[^:])\/\/[^\n]*/gm, (m2, p1) => p1 + " ".repeat(m2.length - p1.length));
6250
6179
  const repeatRe = /\.repeat\.set\(\s*(-?\d+(?:\.\d+)?)\s*,\s*(-?\d+(?:\.\d+)?)\s*\)/g;
6251
6180
  let m;
@@ -6325,25 +6254,25 @@ async function detectGenerationAudit(cwd = process.cwd()) {
6325
6254
  let haystack = "";
6326
6255
  const read = async (file) => {
6327
6256
  try {
6328
- haystack += await fs19.readFile(file, "utf8");
6257
+ haystack += await fs18.readFile(file, "utf8");
6329
6258
  } catch {
6330
6259
  }
6331
6260
  };
6332
6261
  try {
6333
- for (const entry of await fs19.readdir(cwd, { withFileTypes: true })) {
6262
+ for (const entry of await fs18.readdir(cwd, { withFileTypes: true })) {
6334
6263
  if (entry.isFile() && /\.(ts|tsx|js|jsx|css|html|json)$/.test(entry.name)) {
6335
- await read(path18.join(cwd, entry.name));
6264
+ await read(path17.join(cwd, entry.name));
6336
6265
  }
6337
6266
  }
6338
6267
  } catch {
6339
6268
  }
6340
6269
  for (const sub of ["src", "public"]) {
6341
6270
  try {
6342
- const entries = await fs19.readdir(path18.join(cwd, sub), { recursive: true });
6271
+ const entries = await fs18.readdir(path17.join(cwd, sub), { recursive: true });
6343
6272
  for (const rel of entries) {
6344
6273
  if (rel.includes("node_modules")) continue;
6345
6274
  if (!/\.(ts|tsx|js|jsx|css|html|json|txt)$/.test(rel)) continue;
6346
- await read(path18.join(cwd, sub, rel));
6275
+ await read(path17.join(cwd, sub, rel));
6347
6276
  }
6348
6277
  } catch {
6349
6278
  }
@@ -6489,7 +6418,7 @@ async function borrowEvidence(meta, cwd) {
6489
6418
  } catch {
6490
6419
  return true;
6491
6420
  }
6492
- const gitConfig = await fs19.readFile(path18.join(cwd, ".git", "config"), "utf8").catch(() => "");
6421
+ const gitConfig = await fs18.readFile(path17.join(cwd, ".git", "config"), "utf8").catch(() => "");
6493
6422
  for (const m of gitConfig.matchAll(/url\s*=\s*(\S+)/g)) {
6494
6423
  try {
6495
6424
  const u = new URL(m[1]);
@@ -6497,7 +6426,7 @@ async function borrowEvidence(meta, cwd) {
6497
6426
  } catch {
6498
6427
  }
6499
6428
  }
6500
- const readme = await fs19.readFile(path18.join(cwd, "README.md"), "utf8").catch(() => "");
6429
+ const readme = await fs18.readFile(path17.join(cwd, "README.md"), "utf8").catch(() => "");
6501
6430
  return readme.includes(host) || /\b(upstream|originally by|ported from|borrowed from)\b/i.test(readme);
6502
6431
  }
6503
6432
 
@@ -6977,8 +6906,8 @@ async function runRollback(opts) {
6977
6906
  }
6978
6907
 
6979
6908
  // src/commands/generate.ts
6980
- import fs21 from "fs/promises";
6981
- import path20 from "path";
6909
+ import fs20 from "fs/promises";
6910
+ import path19 from "path";
6982
6911
  import { PNG as PNG4 } from "pngjs";
6983
6912
 
6984
6913
  // src/lib/glass.ts
@@ -7059,8 +6988,8 @@ function solveMagentaGlass(source) {
7059
6988
  }
7060
6989
 
7061
6990
  // src/lib/download-assets.ts
7062
- import fs20 from "fs/promises";
7063
- import path19 from "path";
6991
+ import fs19 from "fs/promises";
6992
+ import path18 from "path";
7064
6993
  var RUNG_ROLE = /@\d+$/;
7065
6994
  function isPrimaryRole(role) {
7066
6995
  return !RUNG_ROLE.test(role);
@@ -7080,7 +7009,7 @@ async function downloadAssets(files, opts) {
7080
7009
  const result = { saved: [], failures: [] };
7081
7010
  if (wanted.length === 0) return result;
7082
7011
  try {
7083
- await fs20.mkdir(opts.outDir, { recursive: true });
7012
+ await fs19.mkdir(opts.outDir, { recursive: true });
7084
7013
  } catch (err) {
7085
7014
  result.failures.push(
7086
7015
  `couldn't create ${opts.outDir} (${err instanceof Error ? err.message : String(err)})`
@@ -7089,12 +7018,12 @@ async function downloadAssets(files, opts) {
7089
7018
  }
7090
7019
  const multi = wanted.length > 1;
7091
7020
  for (const file of wanted) {
7092
- const dest = path19.join(opts.outDir, localAssetName(opts.kind, opts.prompt, opts.id, file, multi));
7021
+ const dest = path18.join(opts.outDir, localAssetName(opts.kind, opts.prompt, opts.id, file, multi));
7093
7022
  try {
7094
7023
  const res = await fetch(file.url);
7095
7024
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
7096
7025
  const buf = Buffer.from(await res.arrayBuffer());
7097
- await fs20.writeFile(dest, buf);
7026
+ await fs19.writeFile(dest, buf);
7098
7027
  result.saved.push({ role: file.role, path: dest, url: file.url, bytes: buf.byteLength });
7099
7028
  } catch (err) {
7100
7029
  result.failures.push(
@@ -7238,7 +7167,7 @@ var isRemoteRef = (value) => /^(https?:\/\/|data:)/i.test(value);
7238
7167
  async function inlineLocalImage(filePath, flag) {
7239
7168
  let bytes;
7240
7169
  try {
7241
- bytes = await fs21.readFile(filePath);
7170
+ bytes = await fs20.readFile(filePath);
7242
7171
  } catch {
7243
7172
  return { ok: false, error: `Couldn't read the ${flag} file at ${filePath}.` };
7244
7173
  }
@@ -7248,7 +7177,7 @@ async function inlineLocalImage(filePath, flag) {
7248
7177
  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.`
7249
7178
  };
7250
7179
  }
7251
- const mime = IMAGE_MIME_BY_EXT[path20.extname(filePath).toLowerCase()] ?? "image/png";
7180
+ const mime = IMAGE_MIME_BY_EXT[path19.extname(filePath).toLowerCase()] ?? "image/png";
7252
7181
  return { ok: true, dataUri: `data:${mime};base64,${bytes.toString("base64")}` };
7253
7182
  }
7254
7183
  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.";
@@ -7406,7 +7335,7 @@ async function runGenerate(kind, opts) {
7406
7335
  let typedPrompt = opts.prompt?.trim();
7407
7336
  if (!typedPrompt && kind === "model" && opts.imageUrl) {
7408
7337
  const ref = opts.imageUrl.startsWith("data:") ? "local image" : opts.imageUrl;
7409
- typedPrompt = `from image: ${path20.basename(ref).slice(0, 120)}`;
7338
+ typedPrompt = `from image: ${path19.basename(ref).slice(0, 120)}`;
7410
7339
  }
7411
7340
  if (!typedPrompt) {
7412
7341
  log.error(`Missing prompt. Usage: ${c.cyan(`genex ${kind} "<prompt>"`)}`);
@@ -7450,7 +7379,7 @@ async function runGenerate(kind, opts) {
7450
7379
  return;
7451
7380
  }
7452
7381
  try {
7453
- const bytes = await fs21.readFile(opts.inpaintUrl);
7382
+ const bytes = await fs20.readFile(opts.inpaintUrl);
7454
7383
  opts = { ...opts, inpaintUrl: `data:image/png;base64,${bytes.toString("base64")}` };
7455
7384
  } catch {
7456
7385
  log.error(`Couldn't read the --inpaint mask at ${opts.inpaintUrl}.`);
@@ -7621,7 +7550,7 @@ async function reportGlassTerminal(view, outDir, log, json) {
7621
7550
  return;
7622
7551
  }
7623
7552
  await recordTerminal(view.id, "completed", files.map((f) => f.url));
7624
- await fs21.mkdir(outDir, { recursive: true });
7553
+ await fs20.mkdir(outDir, { recursive: true });
7625
7554
  const solved = [];
7626
7555
  for (let i = 0; i < files.length; i++) {
7627
7556
  const f = files[i];
@@ -7653,8 +7582,8 @@ async function reportGlassTerminal(view, outDir, log, json) {
7653
7582
  });
7654
7583
  continue;
7655
7584
  }
7656
- const outPath = path20.join(outDir, `glass-${i + 1}.png`);
7657
- await fs21.writeFile(outPath, PNG4.sync.write(r.png));
7585
+ const outPath = path19.join(outDir, `glass-${i + 1}.png`);
7586
+ await fs20.writeFile(outPath, PNG4.sync.write(r.png));
7658
7587
  solved.push({
7659
7588
  path: outPath,
7660
7589
  url: f.url,
@@ -8420,8 +8349,8 @@ async function toRow(e, v, cwd) {
8420
8349
  }
8421
8350
 
8422
8351
  // src/commands/controller.ts
8423
- import fs23 from "fs/promises";
8424
- import path22 from "path";
8352
+ import fs22 from "fs/promises";
8353
+ import path21 from "path";
8425
8354
 
8426
8355
  // ../../packages/meshy-animation-catalog/src/index.ts
8427
8356
  import { createHash } from "crypto";
@@ -17551,9 +17480,9 @@ function searchMeshyAnimations(query, options = {}) {
17551
17480
  }
17552
17481
 
17553
17482
  // src/lib/anims.ts
17554
- import fs22 from "fs/promises";
17555
- import path21 from "path";
17556
- var ANIMS_DEST = path21.join("public", "assets", "anims");
17483
+ import fs21 from "fs/promises";
17484
+ import path20 from "path";
17485
+ var ANIMS_DEST = path20.join("public", "assets", "anims");
17557
17486
  var HIDDEN_TAG = "reference";
17558
17487
  async function runAnims(opts) {
17559
17488
  const log = createLogger({ quiet: opts.quiet });
@@ -17569,7 +17498,7 @@ async function runAnims(opts) {
17569
17498
  printCatalog(log, manifest, selectors);
17570
17499
  return;
17571
17500
  }
17572
- const controllerMarker = path21.join(root, "src", "controllers", "character");
17501
+ const controllerMarker = path20.join(root, "src", "controllers", "character");
17573
17502
  if (!await exists2(controllerMarker)) {
17574
17503
  log.error(
17575
17504
  `No character controller in this game (missing ${c.cyan("src/controllers/character/")}).`
@@ -17578,11 +17507,11 @@ async function runAnims(opts) {
17578
17507
  process.exitCode = 1;
17579
17508
  return;
17580
17509
  }
17581
- const destDir = path21.join(root, ANIMS_DEST);
17582
- const gameManifestPath = path21.join(destDir, "manifest.json");
17510
+ const destDir = path20.join(root, ANIMS_DEST);
17511
+ const gameManifestPath = path20.join(destDir, "manifest.json");
17583
17512
  if (opts.reset) {
17584
- await fs22.rm(destDir, { recursive: true, force: true });
17585
- log.step(`Cleared ${c.cyan(ANIMS_DEST + path21.sep)} (--reset)`);
17513
+ await fs21.rm(destDir, { recursive: true, force: true });
17514
+ log.step(`Cleared ${c.cyan(ANIMS_DEST + path20.sep)} (--reset)`);
17586
17515
  }
17587
17516
  if (selectors.length === 0) {
17588
17517
  const installed = await readGameManifest(gameManifestPath);
@@ -17620,35 +17549,35 @@ async function runAnims(opts) {
17620
17549
  }
17621
17550
  }
17622
17551
  const wanted = [...byName.values()].sort((a, b) => a.name.localeCompare(b.name));
17623
- const cacheDir = path21.join(
17552
+ const cacheDir = path20.join(
17624
17553
  opts.cacheDir ?? getAnimsCacheDir(),
17625
17554
  `${manifest.library}-v${manifest.version}`
17626
17555
  );
17627
- await fs22.mkdir(cacheDir, { recursive: true });
17628
- await fs22.mkdir(destDir, { recursive: true });
17556
+ await fs21.mkdir(cacheDir, { recursive: true });
17557
+ await fs21.mkdir(destDir, { recursive: true });
17629
17558
  const base = getAnimsBase(opts.animsBase);
17630
17559
  let installedCount = 0;
17631
17560
  let presentCount = 0;
17632
17561
  let addedBytes = 0;
17633
17562
  const failures = [];
17634
17563
  for (const entry of wanted) {
17635
- const dest = path21.join(destDir, entry.file);
17564
+ const dest = path20.join(destDir, entry.file);
17636
17565
  if (await hasSize(dest, entry.bytes)) {
17637
17566
  presentCount++;
17638
17567
  continue;
17639
17568
  }
17640
17569
  try {
17641
- const cached = path21.join(cacheDir, entry.file);
17570
+ const cached = path20.join(cacheDir, entry.file);
17642
17571
  if (!await hasSize(cached, entry.bytes)) {
17643
17572
  const res = await fetch(base + entry.file);
17644
17573
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
17645
17574
  const buf = Buffer.from(await res.arrayBuffer());
17646
- await fs22.writeFile(cached, buf);
17575
+ await fs21.writeFile(cached, buf);
17647
17576
  }
17648
- await fs22.copyFile(cached, dest);
17577
+ await fs21.copyFile(cached, dest);
17649
17578
  installedCount++;
17650
17579
  addedBytes += entry.bytes;
17651
- log.dim(` ${path21.join(ANIMS_DEST, entry.file)} (${formatMb(entry.bytes)})`);
17580
+ log.dim(` ${path20.join(ANIMS_DEST, entry.file)} (${formatMb(entry.bytes)})`);
17652
17581
  } catch (err) {
17653
17582
  failures.push(`${entry.name} (${err instanceof Error ? err.message : String(err)})`);
17654
17583
  }
@@ -17664,13 +17593,13 @@ async function runAnims(opts) {
17664
17593
  version: manifest.version,
17665
17594
  clips: [...union].sort((a, b) => a.localeCompare(b))
17666
17595
  };
17667
- await fs22.writeFile(gameManifestPath, JSON.stringify(gameManifest, null, 2) + "\n");
17596
+ await fs21.writeFile(gameManifestPath, JSON.stringify(gameManifest, null, 2) + "\n");
17668
17597
  log.plain("");
17669
17598
  const parts = [`${installedCount} clip${installedCount === 1 ? "" : "s"} installed`];
17670
17599
  if (presentCount > 0) parts.push(`${presentCount} already present`);
17671
17600
  if (bundledSkips > 0) parts.push(`${bundledSkips} already bundled in animation-library.glb`);
17672
17601
  log.success(
17673
- `${parts.join(", ")} \u2192 ${c.cyan(ANIMS_DEST + path21.sep)}${addedBytes > 0 ? ` (+${formatMb(addedBytes)})` : ""}`
17602
+ `${parts.join(", ")} \u2192 ${c.cyan(ANIMS_DEST + path20.sep)}${addedBytes > 0 ? ` (+${formatMb(addedBytes)})` : ""}`
17674
17603
  );
17675
17604
  for (const [selector, entries] of resolved) {
17676
17605
  const names = entries.filter((e) => !coreNames.has(e.name)).map((e) => e.name);
@@ -17702,8 +17631,8 @@ async function loadManifest(baseOverride) {
17702
17631
  }
17703
17632
  } catch {
17704
17633
  }
17705
- const snapshotPath = path21.join(getTemplatesDir(), "controllers", "assets", "anims-manifest.json");
17706
- const manifest = JSON.parse(await fs22.readFile(snapshotPath, "utf8"));
17634
+ const snapshotPath = path20.join(getTemplatesDir(), "controllers", "assets", "anims-manifest.json");
17635
+ const manifest = JSON.parse(await fs21.readFile(snapshotPath, "utf8"));
17707
17636
  return { manifest, source: "snapshot" };
17708
17637
  }
17709
17638
  function resolveSelectors(manifest, selectors) {
@@ -17821,21 +17750,21 @@ function printCatalog(log, manifest, selectors) {
17821
17750
  }
17822
17751
  async function readGameManifest(file) {
17823
17752
  try {
17824
- return JSON.parse(await fs22.readFile(file, "utf8"));
17753
+ return JSON.parse(await fs21.readFile(file, "utf8"));
17825
17754
  } catch {
17826
17755
  return null;
17827
17756
  }
17828
17757
  }
17829
17758
  async function hasSize(file, bytes) {
17830
17759
  try {
17831
- return (await fs22.stat(file)).size === bytes;
17760
+ return (await fs21.stat(file)).size === bytes;
17832
17761
  } catch {
17833
17762
  return false;
17834
17763
  }
17835
17764
  }
17836
17765
  async function exists2(p) {
17837
17766
  try {
17838
- await fs22.access(p);
17767
+ await fs21.access(p);
17839
17768
  return true;
17840
17769
  } catch {
17841
17770
  return false;
@@ -18029,8 +17958,8 @@ var CONTROLLER_FILE_SETS = {
18029
17958
  ]
18030
17959
  }
18031
17960
  };
18032
- var CODE_DEST = path22.join("src", "controllers");
18033
- var ASSETS_DEST = path22.join("public", "assets");
17961
+ var CODE_DEST = path21.join("src", "controllers");
17962
+ var ASSETS_DEST = path21.join("public", "assets");
18034
17963
  async function runController(opts) {
18035
17964
  const log = createLogger({ quiet: opts.quiet });
18036
17965
  if (opts.kind?.trim() === "anims") {
@@ -18047,31 +17976,31 @@ async function runController(opts) {
18047
17976
  process.exitCode = 1;
18048
17977
  return;
18049
17978
  }
18050
- const srcDir = path22.join(getTemplatesDir(), "controllers");
17979
+ const srcDir = path21.join(getTemplatesDir(), "controllers");
18051
17980
  const root = opts.cwd ?? process.cwd();
18052
17981
  const set = CONTROLLER_FILE_SETS[kind];
18053
17982
  log.plain(c.bold(`genex controller ${kind}`));
18054
17983
  log.plain("");
18055
- log.step(`Installing the ${kind} controller into ${c.cyan(CODE_DEST + path22.sep)}`);
17984
+ log.step(`Installing the ${kind} controller into ${c.cyan(CODE_DEST + path21.sep)}`);
18056
17985
  const plan = [
18057
- ...set.code.map((rel) => ({ from: rel, rel: path22.join(CODE_DEST, rel) })),
17986
+ ...set.code.map((rel) => ({ from: rel, rel: path21.join(CODE_DEST, rel) })),
18058
17987
  ...set.assets.map((rel) => ({
18059
17988
  from: rel,
18060
- rel: path22.join(ASSETS_DEST, path22.basename(rel))
17989
+ rel: path21.join(ASSETS_DEST, path21.basename(rel))
18061
17990
  }))
18062
17991
  ];
18063
17992
  let copied = 0;
18064
17993
  let skipped = 0;
18065
17994
  try {
18066
17995
  for (const file of plan) {
18067
- const dest = path22.join(root, file.rel);
17996
+ const dest = path21.join(root, file.rel);
18068
17997
  if (!opts.force && await exists3(dest)) {
18069
17998
  skipped++;
18070
17999
  log.dim(` skipped ${file.rel} (exists \u2014 use --force to overwrite)`);
18071
18000
  continue;
18072
18001
  }
18073
- await fs23.mkdir(path22.dirname(dest), { recursive: true });
18074
- await fs23.copyFile(path22.join(srcDir, file.from), dest);
18002
+ await fs22.mkdir(path21.dirname(dest), { recursive: true });
18003
+ await fs22.copyFile(path21.join(srcDir, file.from), dest);
18075
18004
  copied++;
18076
18005
  log.dim(` ${file.rel}`);
18077
18006
  }
@@ -18124,7 +18053,7 @@ async function runController(opts) {
18124
18053
  for (const line of set.sketch) {
18125
18054
  log.dim(` ${line}`);
18126
18055
  }
18127
- if (kind === "character" && !await exists3(path22.join(root, ASSETS_DEST, "meshy-character.json"))) {
18056
+ if (kind === "character" && !await exists3(path21.join(root, ASSETS_DEST, "meshy-character.json"))) {
18128
18057
  log.plain("");
18129
18058
  log.plain(
18130
18059
  ` ${stepOffset + 3}. This game has no generated character yet \u2014 ${c.cyan(
@@ -18156,9 +18085,9 @@ async function installMeshyCharacterManifest(args) {
18156
18085
  throw new Error("The API returned an invalid Meshy character manifest.");
18157
18086
  }
18158
18087
  assertCompleteMeshyControllerPack(manifest);
18159
- const destination = path22.join(args.root, ASSETS_DEST, "meshy-character.json");
18160
- await fs23.mkdir(path22.dirname(destination), { recursive: true });
18161
- await fs23.writeFile(
18088
+ const destination = path21.join(args.root, ASSETS_DEST, "meshy-character.json");
18089
+ await fs22.mkdir(path21.dirname(destination), { recursive: true });
18090
+ await fs22.writeFile(
18162
18091
  destination,
18163
18092
  `${JSON.stringify(manifest, null, 2)}
18164
18093
  `
@@ -18296,14 +18225,14 @@ function assertCompleteMeshyControllerPack(manifest) {
18296
18225
  }
18297
18226
  async function installFallbackAvatar(args) {
18298
18227
  const { root, srcDir, log } = args;
18299
- const dest = path22.join(root, ASSETS_DEST, "avatar.vrm");
18300
- await fs23.mkdir(path22.dirname(dest), { recursive: true });
18301
- await fs23.copyFile(path22.join(srcDir, "assets", "default-avatar.vrm"), dest);
18228
+ const dest = path21.join(root, ASSETS_DEST, "avatar.vrm");
18229
+ await fs22.mkdir(path21.dirname(dest), { recursive: true });
18230
+ await fs22.copyFile(path21.join(srcDir, "assets", "default-avatar.vrm"), dest);
18302
18231
  log.dim(" public/assets/avatar.vrm (fallback avatar \u2014 bundled CC0 default)");
18303
18232
  }
18304
18233
  async function exists3(p) {
18305
18234
  try {
18306
- await fs23.access(p);
18235
+ await fs22.access(p);
18307
18236
  return true;
18308
18237
  } catch {
18309
18238
  return false;
@@ -18311,8 +18240,8 @@ async function exists3(p) {
18311
18240
  }
18312
18241
 
18313
18242
  // src/commands/character.ts
18314
- import fs24 from "fs/promises";
18315
- import path23 from "path";
18243
+ import fs23 from "fs/promises";
18244
+ import path22 from "path";
18316
18245
  function exactAnimation(selector) {
18317
18246
  const trimmed = selector.trim();
18318
18247
  if (/^-?\d+$/.test(trimmed)) return animationById(Number(trimmed));
@@ -18392,7 +18321,7 @@ function showAmbiguity(selector, candidates, log, json = false) {
18392
18321
  }
18393
18322
  process.exitCode = 1;
18394
18323
  }
18395
- var INSTALLED_MANIFEST = path23.join("public", "assets", "meshy-character.json");
18324
+ var INSTALLED_MANIFEST = path22.join("public", "assets", "meshy-character.json");
18396
18325
  async function resolveAdoptTarget(selector) {
18397
18326
  const trimmed = selector?.trim();
18398
18327
  if (trimmed && !trimmed.endsWith(".json")) {
@@ -18401,7 +18330,7 @@ async function resolveAdoptTarget(selector) {
18401
18330
  const file = trimmed ?? INSTALLED_MANIFEST;
18402
18331
  let raw;
18403
18332
  try {
18404
- raw = await fs24.readFile(file, "utf8");
18333
+ raw = await fs23.readFile(file, "utf8");
18405
18334
  } catch {
18406
18335
  return {
18407
18336
  ok: false,
@@ -19230,8 +19159,8 @@ function rank(items, query) {
19230
19159
  }
19231
19160
 
19232
19161
  // src/commands/motion.ts
19233
- import fs25 from "fs/promises";
19234
- import path24 from "path";
19162
+ import fs24 from "fs/promises";
19163
+ import path23 from "path";
19235
19164
 
19236
19165
  // src/lib/motion/npz.ts
19237
19166
  import zlib from "zlib";
@@ -20482,7 +20411,7 @@ async function motionGen(opts, log) {
20482
20411
  }
20483
20412
  if (opts.constraintsPath !== void 0) {
20484
20413
  try {
20485
- const raw = await fs25.readFile(opts.constraintsPath, "utf8");
20414
+ const raw = await fs24.readFile(opts.constraintsPath, "utf8");
20486
20415
  generationOptions.constraints = JSON.parse(raw);
20487
20416
  } catch {
20488
20417
  log.error(`Couldn't read the --constraints JSON at ${opts.constraintsPath}.`);
@@ -20506,10 +20435,10 @@ async function motionGen(opts, log) {
20506
20435
  async function expandTakes(selectors) {
20507
20436
  const out = [];
20508
20437
  for (const sel of selectors) {
20509
- const st = await fs25.stat(sel).catch(() => null);
20438
+ const st = await fs24.stat(sel).catch(() => null);
20510
20439
  if (st?.isDirectory()) {
20511
- const names = await fs25.readdir(sel);
20512
- for (const n of names.sort()) if (n.endsWith(".npz")) out.push(path24.join(sel, n));
20440
+ const names = await fs24.readdir(sel);
20441
+ for (const n of names.sort()) if (n.endsWith(".npz")) out.push(path23.join(sel, n));
20513
20442
  } else if (st?.isFile()) {
20514
20443
  out.push(sel);
20515
20444
  } else {
@@ -20544,7 +20473,7 @@ async function motionVerify(opts, log) {
20544
20473
  let gates = DEFAULT_GATES;
20545
20474
  if (opts.gatesPath) {
20546
20475
  try {
20547
- gates = mergeGates(DEFAULT_GATES, JSON.parse(await fs25.readFile(opts.gatesPath, "utf8")));
20476
+ gates = mergeGates(DEFAULT_GATES, JSON.parse(await fs24.readFile(opts.gatesPath, "utf8")));
20548
20477
  } catch {
20549
20478
  log.error(`Couldn't read the --gates JSON at ${opts.gatesPath}.`);
20550
20479
  process.exitCode = 1;
@@ -20566,9 +20495,9 @@ async function motionVerify(opts, log) {
20566
20495
  }
20567
20496
  const reports = [];
20568
20497
  for (const file of files) {
20569
- const stem = path24.basename(file).replace(/\.npz$/, "");
20498
+ const stem = path23.basename(file).replace(/\.npz$/, "");
20570
20499
  try {
20571
- reports.push(analyzeTake(stem, await fs25.readFile(file), gates));
20500
+ reports.push(analyzeTake(stem, await fs24.readFile(file), gates));
20572
20501
  } catch (err) {
20573
20502
  reports.push({
20574
20503
  take: stem,
@@ -20606,7 +20535,7 @@ async function motionCompile(opts, log) {
20606
20535
  let cfg = DEFAULT_MOTION_CONFIG;
20607
20536
  if (opts.configPath) {
20608
20537
  try {
20609
- const patch = JSON.parse(await fs25.readFile(opts.configPath, "utf8"));
20538
+ const patch = JSON.parse(await fs24.readFile(opts.configPath, "utf8"));
20610
20539
  cfg = { ...DEFAULT_MOTION_CONFIG, ...patch, idleLoop: { ...DEFAULT_MOTION_CONFIG.idleLoop, ...patch.idleLoop } };
20611
20540
  } catch {
20612
20541
  log.error(`Couldn't read the --config JSON at ${opts.configPath}.`);
@@ -20624,16 +20553,16 @@ async function motionCompile(opts, log) {
20624
20553
  }
20625
20554
  const inputs = [];
20626
20555
  for (const file of files) {
20627
- const stem = path24.basename(file).replace(/\.npz$/, "");
20556
+ const stem = path23.basename(file).replace(/\.npz$/, "");
20628
20557
  try {
20629
- inputs.push({ stem, take: loadTake(await fs25.readFile(file)) });
20558
+ inputs.push({ stem, take: loadTake(await fs24.readFile(file)) });
20630
20559
  } catch (err) {
20631
20560
  log.error(`${stem}: ${err instanceof Error ? err.message : String(err)}`);
20632
20561
  process.exitCode = 1;
20633
20562
  return;
20634
20563
  }
20635
20564
  }
20636
- const setName = opts.set ?? path24.basename(opts.out).replace(/\.json$/, "");
20565
+ const setName = opts.set ?? path23.basename(opts.out).replace(/\.json$/, "");
20637
20566
  let result;
20638
20567
  try {
20639
20568
  result = compileSet(inputs, setName, cfg);
@@ -20648,9 +20577,9 @@ async function motionCompile(opts, log) {
20648
20577
  process.exitCode = 1;
20649
20578
  return;
20650
20579
  }
20651
- await fs25.mkdir(path24.dirname(path24.resolve(opts.out)), { recursive: true });
20580
+ await fs24.mkdir(path23.dirname(path23.resolve(opts.out)), { recursive: true });
20652
20581
  const json = JSON.stringify(result.data);
20653
- await fs25.writeFile(opts.out, json);
20582
+ await fs24.writeFile(opts.out, json);
20654
20583
  if (opts.json) {
20655
20584
  writeJson({ out: opts.out, set: setName, tracks: Object.keys(result.data.gaits), bytes: json.length });
20656
20585
  return;
@@ -20668,9 +20597,9 @@ var MOTION_RUNTIME_FILES = [
20668
20597
  var MOTION_PRESETS = {
20669
20598
  rifle: ["sets/rifle.json", "sets/jumps.json"]
20670
20599
  };
20671
- var MOTION_DEST = path24.join("src", "motion");
20600
+ var MOTION_DEST = path23.join("src", "motion");
20672
20601
  async function motionInstall(opts, log) {
20673
- const srcDir = path24.join(getTemplatesDir(), "motion");
20602
+ const srcDir = path23.join(getTemplatesDir(), "motion");
20674
20603
  const root = opts.cwd ?? process.cwd();
20675
20604
  const preset = opts.set;
20676
20605
  if (preset !== void 0 && !MOTION_PRESETS[preset]) {
@@ -20681,21 +20610,21 @@ async function motionInstall(opts, log) {
20681
20610
  const files = [...MOTION_RUNTIME_FILES, ...preset ? MOTION_PRESETS[preset] : []];
20682
20611
  log.plain(c.bold(`genex motion install${preset ? ` --set ${preset}` : ""}`));
20683
20612
  log.plain("");
20684
- log.step(`Vendoring the motion runtime into ${c.cyan(MOTION_DEST + path24.sep)}`);
20613
+ log.step(`Vendoring the motion runtime into ${c.cyan(MOTION_DEST + path23.sep)}`);
20685
20614
  let copied = 0, skipped = 0;
20686
20615
  try {
20687
20616
  for (const rel of files) {
20688
- const dest = path24.join(root, MOTION_DEST, rel);
20689
- const exists5 = await fs25.access(dest).then(() => true, () => false);
20617
+ const dest = path23.join(root, MOTION_DEST, rel);
20618
+ const exists5 = await fs24.access(dest).then(() => true, () => false);
20690
20619
  if (!opts.force && exists5) {
20691
20620
  skipped++;
20692
- log.dim(` skipped ${path24.join(MOTION_DEST, rel)} (exists \u2014 use --force to overwrite)`);
20621
+ log.dim(` skipped ${path23.join(MOTION_DEST, rel)} (exists \u2014 use --force to overwrite)`);
20693
20622
  continue;
20694
20623
  }
20695
- await fs25.mkdir(path24.dirname(dest), { recursive: true });
20696
- await fs25.copyFile(path24.join(srcDir, rel), dest);
20624
+ await fs24.mkdir(path23.dirname(dest), { recursive: true });
20625
+ await fs24.copyFile(path23.join(srcDir, rel), dest);
20697
20626
  copied++;
20698
- log.dim(` ${path24.join(MOTION_DEST, rel)}`);
20627
+ log.dim(` ${path23.join(MOTION_DEST, rel)}`);
20699
20628
  }
20700
20629
  } catch (err) {
20701
20630
  log.error(`Copy failed: ${String(err)}`);
@@ -20736,7 +20665,7 @@ async function motionConstraints(opts, log) {
20736
20665
  }
20737
20666
  const doc = directionConstraint(dir, speed, duration);
20738
20667
  const out = opts.out ?? "constraints.json";
20739
- await fs25.writeFile(out, JSON.stringify(doc));
20668
+ await fs24.writeFile(out, JSON.stringify(doc));
20740
20669
  if (opts.json) {
20741
20670
  writeJson({ out, dir: dirName, speed, duration, waypoints: doc[0].frame_indices.length });
20742
20671
  return;
@@ -20772,6 +20701,214 @@ async function runMotion(opts) {
20772
20701
  }
20773
20702
  }
20774
20703
 
20704
+ // src/commands/blender.ts
20705
+ import fs25 from "fs/promises";
20706
+ import path24 from "path";
20707
+ var SUBS2 = ["demo", "exec", "snap", "scene", "export", "reset", "mcp"];
20708
+ var DEFAULT_OUT_DIR = "assets/blender";
20709
+ async function writeB64(dir, name, b64) {
20710
+ await fs25.mkdir(dir, { recursive: true });
20711
+ const p = path24.join(dir, name);
20712
+ await fs25.writeFile(p, Buffer.from(b64, "base64"));
20713
+ return p;
20714
+ }
20715
+ function reportScene(log, s) {
20716
+ if (!s) return;
20717
+ log.plain(
20718
+ ` ${c.dim("objects")} ${s.objectCount} ${c.dim("meshes")} ${s.meshCount} ${c.dim("tris")} ${s.totalTris} ${c.dim("materials")} ${s.materialCount} ${c.dim("radius")} ${s.bounds.radius}`
20719
+ );
20720
+ }
20721
+ async function runBlender(opts) {
20722
+ const log = createLogger();
20723
+ const sub = opts.sub;
20724
+ if (!sub || !SUBS2.includes(sub)) {
20725
+ log.error(`Usage: genex blender <${SUBS2.join("|")}>`);
20726
+ return 1;
20727
+ }
20728
+ if (sub === "mcp") {
20729
+ const { runBlenderMcp } = await import("./blender-mcp-OXFN3RVI.js");
20730
+ return runBlenderMcp();
20731
+ }
20732
+ const base = blenderEndpoint();
20733
+ if (!base) {
20734
+ const [first, ...rest] = BLENDER_SETUP_HINT.split("\n");
20735
+ log.error(first ?? "GENEX_BLENDER_URL is not set");
20736
+ log.plain(rest.join("\n"));
20737
+ return 1;
20738
+ }
20739
+ const outDir = path24.resolve(opts.outDir ?? DEFAULT_OUT_DIR);
20740
+ const mode = opts.mode;
20741
+ if (mode !== void 0 && !isRenderMode(mode)) {
20742
+ log.error(`Unknown --mode ${mode}. Use one of: ${RENDER_MODES.join(", ")}.`);
20743
+ return 1;
20744
+ }
20745
+ try {
20746
+ const health = await blenderCall(base, "/health");
20747
+ log.dim(` blender ${String(health.blender ?? "?")} at ${base}`);
20748
+ } catch (err) {
20749
+ log.error(`Cannot reach the Blender service at ${base}`);
20750
+ log.plain(` ${err instanceof Error ? err.message : String(err)}`);
20751
+ return 1;
20752
+ }
20753
+ switch (sub) {
20754
+ case "reset": {
20755
+ const r = await blenderCall(base, "/reset", {});
20756
+ log.success("Scene reset.");
20757
+ reportScene(log, r.scene);
20758
+ return 0;
20759
+ }
20760
+ case "scene": {
20761
+ const r = await blenderCall(base, "/scene");
20762
+ log.plain(JSON.stringify(r, null, 2));
20763
+ return 0;
20764
+ }
20765
+ case "snap": {
20766
+ const r = await blenderCall(base, "/render", mode ? { mode } : {});
20767
+ const used = String(r.mode ?? mode ?? "default");
20768
+ const p = await writeB64(outDir, `snap-${used}.png`, r.contactSheetPng ?? "");
20769
+ log.success(`${used} sheet in ${r.ms ?? "?"}ms`);
20770
+ log.plain(` ${c.cyan(p)}`);
20771
+ return 0;
20772
+ }
20773
+ case "export": {
20774
+ const target = opts.out ?? path24.join(outDir, "scene.glb");
20775
+ const r = await blenderCall(base, "/export", { path: "/tmp/genex-export.glb" });
20776
+ await fs25.mkdir(path24.dirname(target), { recursive: true });
20777
+ await fs25.writeFile(target, Buffer.from(r.glbBase64 ?? "", "base64"));
20778
+ log.success(`Exported ${r.bytes ?? 0} bytes`);
20779
+ log.plain(` ${c.cyan(target)}`);
20780
+ return 0;
20781
+ }
20782
+ case "exec":
20783
+ case "demo": {
20784
+ let script;
20785
+ let label;
20786
+ if (sub === "demo") {
20787
+ script = DEMO_SCRIPT;
20788
+ label = "demo";
20789
+ } else {
20790
+ if (!opts.input) {
20791
+ log.error("genex blender exec needs a script: --in <file.py>");
20792
+ return 1;
20793
+ }
20794
+ try {
20795
+ script = await fs25.readFile(opts.input, "utf8");
20796
+ } catch {
20797
+ log.error(`Can't read ${opts.input}`);
20798
+ return 1;
20799
+ }
20800
+ label = path24.basename(opts.input);
20801
+ }
20802
+ const r = await blenderCall(base, "/exec", mode ? { script, mode } : { script });
20803
+ if (r.stdout?.trim()) log.plain(r.stdout.trimEnd());
20804
+ if (r.stderr?.trim()) log.dim(r.stderr.trimEnd());
20805
+ if (r.error) {
20806
+ log.error(`${label} raised:`);
20807
+ log.plain(r.error.trimEnd());
20808
+ if (r.contactSheetPng) {
20809
+ const p = await writeB64(outDir, "contact-sheet.png", r.contactSheetPng);
20810
+ log.plain(` ${c.dim("scene at failure:")} ${c.cyan(p)}`);
20811
+ }
20812
+ return 1;
20813
+ }
20814
+ log.success(`${label} ran in ${r.ms ?? "?"}ms`);
20815
+ if (r.sceneRestored?.restored) {
20816
+ log.warn(
20817
+ `The service restarted (${r.sceneRestored.reason ?? "unknown"}) and the scene was restored from a checkpoint \u2014 your last step may be missing.`
20818
+ );
20819
+ }
20820
+ reportScene(log, r.scene);
20821
+ const written = [];
20822
+ if (r.contactSheetPng) {
20823
+ written.push(await writeB64(outDir, "contact-sheet.png", r.contactSheetPng));
20824
+ }
20825
+ if (sub === "demo") {
20826
+ const glb = await blenderCall(base, "/export", { path: "/tmp/genex-export.glb" });
20827
+ written.push(await writeB64(outDir, "castle.glb", glb.glbBase64 ?? ""));
20828
+ }
20829
+ for (const p of written) log.plain(` ${c.cyan(p)}`);
20830
+ if (sub === "demo") {
20831
+ log.plain(`
20832
+ ${c.dim("Open the sheet. If it shows a castle room, the loop works.")}`);
20833
+ }
20834
+ return 0;
20835
+ }
20836
+ }
20837
+ return 1;
20838
+ }
20839
+ var DEMO_SCRIPT = `
20840
+ import bpy
20841
+
20842
+ ROOM, WALL_H, WALL_T = 12.0, 5.0, 0.6
20843
+ bpy.ops.wm.read_factory_settings(use_empty=True)
20844
+
20845
+ def mat(name, rgb, rough=0.85):
20846
+ m = bpy.data.materials.new(name)
20847
+ m.use_nodes = True
20848
+ b = m.node_tree.nodes["Principled BSDF"]
20849
+ b.inputs["Base Color"].default_value = (*rgb, 1.0)
20850
+ b.inputs["Roughness"].default_value = rough
20851
+ # BOTH, and the second is the trap: Workbench's MATERIAL mode -- what every
20852
+ # contact sheet renders -- reads diffuse_color, NOT the Principled BSDF.
20853
+ # Setting only the BSDF gives a scene that exports with correct colours,
20854
+ # reports materialCount>0, and renders ENTIRELY GREY.
20855
+ m.diffuse_color = (*rgb, 1.0)
20856
+ return m
20857
+
20858
+ def cube(name, loc, scale, m):
20859
+ bpy.ops.mesh.primitive_cube_add(size=2.0, location=loc)
20860
+ o = bpy.context.active_object
20861
+ o.name, o.scale = name, scale
20862
+ o.data.materials.append(m)
20863
+ return o
20864
+
20865
+ stone, dark = mat("Stone", (0.46, 0.44, 0.41)), mat("StoneDark", (0.26, 0.25, 0.24))
20866
+ wood, roofm = mat("Wood", (0.24, 0.14, 0.07)), mat("Roof", (0.35, 0.12, 0.12), 0.6)
20867
+ fire = mat("Fire", (1.0, 0.45, 0.1), 0.4)
20868
+ half = ROOM / 2
20869
+
20870
+ cube("Floor", (0, 0, -0.25), (half, half, 0.25), dark)
20871
+ cube("Wall_N", (0, half, WALL_H / 2), (half, WALL_T / 2, WALL_H / 2), stone)
20872
+ cube("Wall_E", (half, 0, WALL_H / 2), (WALL_T / 2, half, WALL_H / 2), stone)
20873
+ cube("Wall_W", (-half, 0, WALL_H / 2), (WALL_T / 2, half, WALL_H / 2), stone)
20874
+ door = 2.2
20875
+ side = (ROOM - door) / 4
20876
+ for sgn in (-1, 1):
20877
+ cube(f"Wall_S{sgn}", (sgn * (door / 2 + side), -half, WALL_H / 2),
20878
+ (side, WALL_T / 2, WALL_H / 2), stone)
20879
+ cube("Lintel", (0, -half, WALL_H - 0.5), (door / 2, WALL_T / 2, 0.5), stone)
20880
+
20881
+ for i in range(9):
20882
+ x = -half + 0.6 + i * (ROOM - 1.2) / 8
20883
+ for y in (half, -half):
20884
+ cube(f"Cren_{i}_{y:.0f}", (x, y, WALL_H + 0.35), (0.35, WALL_T / 2, 0.35), stone)
20885
+
20886
+ for i, (sx, sy) in enumerate(((1, 1), (1, -1), (-1, 1), (-1, -1))):
20887
+ cx, cy = sx * half, sy * half
20888
+ bpy.ops.mesh.primitive_cylinder_add(radius=1.5, depth=WALL_H + 2.0, vertices=16,
20889
+ location=(cx, cy, (WALL_H + 2.0) / 2))
20890
+ t = bpy.context.active_object; t.name = f"Tower_{i}"; t.data.materials.append(stone)
20891
+ bpy.ops.mesh.primitive_cone_add(radius1=2.0, depth=2.4, vertices=16,
20892
+ location=(cx, cy, WALL_H + 3.2))
20893
+ r = bpy.context.active_object; r.name = f"Roof_{i}"; r.data.materials.append(roofm)
20894
+
20895
+ cube("TableTop", (0, 1.0, 1.1), (2.2, 0.9, 0.12), wood)
20896
+ for dx in (-1.9, 1.9):
20897
+ for dy in (-0.65, 0.65):
20898
+ cube(f"Leg{dx}{dy}", (dx, 1.0 + dy, 0.55), (0.12, 0.12, 0.55), wood)
20899
+
20900
+ for i, (tx, ty) in enumerate(((-half + 0.6, 2.5), (-half + 0.6, -2.5),
20901
+ (half - 0.6, 2.5), (half - 0.6, -2.5))):
20902
+ cube(f"Sconce_{i}", (tx, ty, 3.0), (0.15, 0.15, 0.4), wood)
20903
+ bpy.ops.mesh.primitive_uv_sphere_add(radius=0.32, segments=12, ring_count=8,
20904
+ location=(tx, ty, 3.5))
20905
+ f = bpy.context.active_object; f.name = f"Flame_{i}"; f.data.materials.append(fire)
20906
+
20907
+ bpy.ops.object.select_all(action="DESELECT")
20908
+ n = len(bpy.context.scene.objects)
20909
+ print(f"castle: {n} objects")
20910
+ `;
20911
+
20775
20912
  // src/commands/asset-new.ts
20776
20913
  import fs26 from "fs";
20777
20914
  import fsp from "fs/promises";
@@ -22054,6 +22191,8 @@ function parseArgs(argv) {
22054
22191
  "--image",
22055
22192
  "--granularity",
22056
22193
  "--preset",
22194
+ // `genex blender` (M1 spike): which shading the contact sheet uses.
22195
+ "--mode",
22057
22196
  // `genex ui` string flags (the numeric ones come from UI_NUMBER_FLAGS).
22058
22197
  "--in",
22059
22198
  "--out-dir",
@@ -22583,6 +22722,9 @@ function applyValueFlag(options, flag, value) {
22583
22722
  case "--last-frame":
22584
22723
  options.lastFrameUrl = value;
22585
22724
  break;
22725
+ case "--mode":
22726
+ options.mode = value;
22727
+ break;
22586
22728
  case "--in":
22587
22729
  options.input = value;
22588
22730
  break;
@@ -22638,7 +22780,8 @@ async function main() {
22638
22780
  log.plain(mode === "tools" ? HELP_TOOLS : HELP);
22639
22781
  return;
22640
22782
  }
22641
- const quietUpdates = Boolean(parsed.options.quiet || parsed.options.json);
22783
+ const isBlenderMcp = parsed.command === "blender" && parsed.options.name === "mcp";
22784
+ const quietUpdates = Boolean(parsed.options.quiet || parsed.options.json || isBlenderMcp);
22642
22785
  const updateLog = createLogger({ quiet: quietUpdates });
22643
22786
  if (parsed.command !== "init" && parsed.command !== "tools") {
22644
22787
  await syncSkills(updateLog, mode);
@@ -22809,6 +22952,12 @@ async function main() {
22809
22952
  case "ui":
22810
22953
  await runUi({ ...parsed.options, sub: parsed.options.name });
22811
22954
  break;
22955
+ // HIDDEN from --help: the M1 test surface for the Blender service spike
22956
+ // (apps/blender-service). Gated on GENEX_BLENDER_URL, which no hosted
22957
+ // stand sets, so this is a local-only verb until that service is real.
22958
+ case "blender":
22959
+ process.exitCode = await runBlender({ ...parsed.options, sub: parsed.options.name });
22960
+ break;
22812
22961
  // PARKED (AG-824 D12) — still dispatches, deliberately unlisted in --help
22813
22962
  // and gone from the skills, the same way `genex explore` is parked.
22814
22963
  //