@genex-ai/cli-demo 1.10.3-dev.535 → 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/blender-mcp-OXFN3RVI.js +210 -0
- package/dist/chunk-ZHIP7LCA.js +178 -0
- package/dist/index.js +628 -452
- package/package.json +1 -1
- package/templates/skills/genex-ai-menu/SKILL.md +15 -11
- package/templates/skills/genex-ai-video/SKILL.md +42 -15
- package/templates/skills/genex-tool-video/SKILL.md +8 -2
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
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
357
|
-
await
|
|
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
|
|
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
|
|
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
|
|
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
|
|
410
|
-
await
|
|
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
|
|
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
|
|
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
|
|
823
|
-
import
|
|
751
|
+
import fs3 from "fs/promises";
|
|
752
|
+
import path3 from "path";
|
|
824
753
|
function getProjectMetadataPath(cwd = process.cwd()) {
|
|
825
|
-
return
|
|
754
|
+
return path3.join(cwd, ".genex", "project.json");
|
|
826
755
|
}
|
|
827
756
|
function getWorkspacePath(cwd = process.cwd()) {
|
|
828
|
-
return
|
|
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
|
|
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
|
|
841
|
-
await
|
|
842
|
-
await
|
|
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
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
897
|
-
await
|
|
898
|
-
await
|
|
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
|
|
964
|
-
import
|
|
892
|
+
import fs9 from "fs/promises";
|
|
893
|
+
import path9 from "path";
|
|
965
894
|
|
|
966
895
|
// src/lib/copy-templates.ts
|
|
967
|
-
import
|
|
968
|
-
import
|
|
896
|
+
import fs4 from "fs/promises";
|
|
897
|
+
import path4 from "path";
|
|
969
898
|
function isGenexManaged(rel) {
|
|
970
|
-
return rel.split(
|
|
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
|
|
907
|
+
const entries = await fs4.readdir(src, { withFileTypes: true });
|
|
979
908
|
for (const entry of entries) {
|
|
980
|
-
const srcPath =
|
|
981
|
-
const destPath =
|
|
982
|
-
const rel =
|
|
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
|
|
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
|
|
1000
|
-
await
|
|
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
|
|
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
|
|
1016
|
-
import
|
|
1017
|
-
import
|
|
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 =
|
|
1010
|
+
const agentsPath = path5.join(projectDir, "AGENTS.md");
|
|
1082
1011
|
let existing = null;
|
|
1083
1012
|
try {
|
|
1084
|
-
existing = await
|
|
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
|
|
1019
|
+
await fs5.writeFile(agentsPath, next, "utf8");
|
|
1091
1020
|
changed = true;
|
|
1092
1021
|
}
|
|
1093
|
-
const claudePath =
|
|
1022
|
+
const claudePath = path5.join(projectDir, "CLAUDE.md");
|
|
1094
1023
|
let claude = null;
|
|
1095
1024
|
try {
|
|
1096
|
-
claude = await
|
|
1025
|
+
claude = await fs5.readFile(claudePath, "utf8");
|
|
1097
1026
|
} catch {
|
|
1098
1027
|
claude = null;
|
|
1099
1028
|
}
|
|
1100
1029
|
if (claude === null) {
|
|
1101
|
-
await
|
|
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
|
|
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 =
|
|
1047
|
+
async function healAncestorContracts(projectDir, stopDir = os3.homedir()) {
|
|
1119
1048
|
const findings = [];
|
|
1120
|
-
const stop =
|
|
1121
|
-
let dir =
|
|
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
|
|
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 =
|
|
1063
|
+
const agentsPath = path5.join(dir, "AGENTS.md");
|
|
1135
1064
|
let content = null;
|
|
1136
1065
|
try {
|
|
1137
|
-
content = await
|
|
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
|
|
1075
|
+
await fs5.unlink(agentsPath);
|
|
1147
1076
|
try {
|
|
1148
|
-
const claudePath =
|
|
1149
|
-
if ((await
|
|
1150
|
-
await
|
|
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 =
|
|
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 =
|
|
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
|
|
1189
|
-
import
|
|
1190
|
-
import
|
|
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
|
|
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
|
|
1257
|
-
await
|
|
1258
|
-
|
|
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
|
|
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 =
|
|
1256
|
+
const target = path6.join(skillsDir, name);
|
|
1328
1257
|
try {
|
|
1329
|
-
await
|
|
1258
|
+
await fs6.access(target);
|
|
1330
1259
|
} catch {
|
|
1331
1260
|
continue;
|
|
1332
1261
|
}
|
|
1333
1262
|
try {
|
|
1334
|
-
await
|
|
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 =
|
|
1278
|
+
const skillsDir = path6.join(target.baseDir, "skills");
|
|
1350
1279
|
for (const name of TOOL_SKILLS) {
|
|
1351
|
-
const dir =
|
|
1280
|
+
const dir = path6.join(skillsDir, name);
|
|
1352
1281
|
try {
|
|
1353
|
-
await
|
|
1282
|
+
await fs6.access(dir);
|
|
1354
1283
|
} catch {
|
|
1355
1284
|
continue;
|
|
1356
1285
|
}
|
|
1357
1286
|
try {
|
|
1358
|
-
await
|
|
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 =
|
|
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 :
|
|
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 =
|
|
1318
|
+
const home = os4.homedir();
|
|
1390
1319
|
const [realCwd, realHome] = await Promise.all([
|
|
1391
|
-
|
|
1392
|
-
|
|
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 =
|
|
1326
|
+
const skillsDir = path6.join(home, dirName, "skills");
|
|
1398
1327
|
let entries = [];
|
|
1399
1328
|
try {
|
|
1400
|
-
entries = await
|
|
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
|
|
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
|
-
|
|
1415
|
-
|
|
1343
|
+
path6.join("agents", "genex-helper.md"),
|
|
1344
|
+
path6.join("commands", "genex-status.md")
|
|
1416
1345
|
]) {
|
|
1417
1346
|
try {
|
|
1418
|
-
await
|
|
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
|
|
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
|
|
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
|
|
1529
|
-
await
|
|
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
|
|
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
|
|
1586
|
-
await
|
|
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
|
|
1686
|
-
import
|
|
1614
|
+
import fs7 from "fs/promises";
|
|
1615
|
+
import path7 from "path";
|
|
1687
1616
|
async function writeGitignore(dir, log) {
|
|
1688
|
-
const file =
|
|
1617
|
+
const file = path7.join(dir, ".gitignore");
|
|
1689
1618
|
let content = "";
|
|
1690
1619
|
try {
|
|
1691
|
-
content = await
|
|
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
|
|
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 =
|
|
1660
|
+
const file = path7.join(dir, ".gitattributes");
|
|
1732
1661
|
let content = "";
|
|
1733
1662
|
try {
|
|
1734
|
-
content = await
|
|
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
|
|
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
|
|
1750
|
-
import
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
1844
|
-
await writeIfAbsent(
|
|
1845
|
-
await writeIfAbsent(
|
|
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(
|
|
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
|
|
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 :
|
|
1934
|
-
const dest = t.full ? t.baseDir :
|
|
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(
|
|
1941
|
-
await writeSkillsMarker(
|
|
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() ||
|
|
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
|
|
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(
|
|
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
|
|
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
|
|
2152
|
-
|
|
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:
|
|
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
|
|
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:
|
|
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
|
|
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
|
|
2333
|
-
const fresh =
|
|
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
|
|
2337
|
-
for (const entry of await
|
|
2338
|
-
await
|
|
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
|
|
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 =
|
|
2280
|
+
const file = path11.join(cwd, ".env");
|
|
2352
2281
|
let content;
|
|
2353
2282
|
try {
|
|
2354
|
-
content = await
|
|
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
|
|
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
|
|
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
|
|
2422
|
-
import
|
|
2423
|
-
import
|
|
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
|
|
2483
|
-
const fresh =
|
|
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
|
|
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(
|
|
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
|
|
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 =
|
|
2446
|
+
const dest = path12.join(cwd, ".genex", `replaced-${stamp}`);
|
|
2518
2447
|
try {
|
|
2519
|
-
const entries = (await
|
|
2448
|
+
const entries = (await fs12.readdir(cwd)).filter((e) => !isMachineLocal(e));
|
|
2520
2449
|
if (entries.length === 0) return null;
|
|
2521
|
-
await
|
|
2450
|
+
await fs12.mkdir(dest, { recursive: true });
|
|
2522
2451
|
for (const entry of entries) {
|
|
2523
|
-
await
|
|
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
|
|
2462
|
+
for (const entry of await fs12.readdir(dest)) {
|
|
2534
2463
|
if (isMachineLocal(entry)) continue;
|
|
2535
|
-
await
|
|
2464
|
+
await fs12.rm(path12.join(dest, entry), { recursive: true, force: true });
|
|
2536
2465
|
}
|
|
2537
|
-
for (const entry of await
|
|
2466
|
+
for (const entry of await fs12.readdir(src)) {
|
|
2538
2467
|
if (isMachineLocal(entry)) continue;
|
|
2539
|
-
await
|
|
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
|
|
2545
|
-
import
|
|
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 =
|
|
2562
|
+
const file = path13.join(cwd, ".env");
|
|
2634
2563
|
let content;
|
|
2635
2564
|
try {
|
|
2636
|
-
content = await
|
|
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
|
|
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 =
|
|
2575
|
+
const file = path13.join(cwd, "src", "genex.config.ts");
|
|
2647
2576
|
let content;
|
|
2648
2577
|
try {
|
|
2649
|
-
content = await
|
|
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
|
|
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
|
|
2790
|
-
import
|
|
2791
|
-
import
|
|
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
|
|
3056
|
-
import
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
3634
|
-
await
|
|
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
|
|
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 =
|
|
4150
|
-
const maskPath =
|
|
4151
|
-
const annotatedPath =
|
|
4152
|
-
const overlayPath =
|
|
4153
|
-
const metaPath =
|
|
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
|
|
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 =
|
|
4217
|
-
await
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
4605
|
+
const absAssets = path14.resolve(cwd, assetDir);
|
|
4677
4606
|
try {
|
|
4678
|
-
if (!(await
|
|
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(
|
|
4685
|
-
(p) => AUDIT_SRC_EXTS.has(
|
|
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
|
|
4689
|
-
const ext =
|
|
4690
|
-
if (ext === ".html" || ext === ".css") srcFiles.push(
|
|
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:
|
|
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 =
|
|
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
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
|
4769
|
-
mask = PNG2.sync.read(await
|
|
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
|
|
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(
|
|
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 =
|
|
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: `${
|
|
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
|
|
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
|
|
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
|
|
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(
|
|
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
|
|
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 =
|
|
5079
|
-
const siteDir = await
|
|
5080
|
-
const rel =
|
|
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
|
|
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
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
5441
|
-
|
|
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:
|
|
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
|
|
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
|
|
5476
|
+
async function isDir(p) {
|
|
5548
5477
|
try {
|
|
5549
|
-
return (await
|
|
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
|
-
|
|
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
|
|
6041
|
-
await
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
6084
|
+
const srcDir = path17.join(cwd, "src");
|
|
6156
6085
|
let entries;
|
|
6157
6086
|
try {
|
|
6158
|
-
entries = await
|
|
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
|
|
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 =
|
|
6104
|
+
const srcDir = path17.join(cwd, "src");
|
|
6176
6105
|
let entries;
|
|
6177
6106
|
try {
|
|
6178
|
-
entries = await
|
|
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
|
|
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 =
|
|
6165
|
+
const srcDir = path17.join(cwd, "src");
|
|
6237
6166
|
let entries;
|
|
6238
6167
|
try {
|
|
6239
|
-
entries = await
|
|
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
|
|
6175
|
+
const raw = await fs18.readFile(path17.join(srcDir, nativeRel), "utf8").catch(() => "");
|
|
6247
6176
|
if (!raw) continue;
|
|
6248
|
-
const rel = nativeRel.split(
|
|
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
|
|
6257
|
+
haystack += await fs18.readFile(file, "utf8");
|
|
6329
6258
|
} catch {
|
|
6330
6259
|
}
|
|
6331
6260
|
};
|
|
6332
6261
|
try {
|
|
6333
|
-
for (const entry of await
|
|
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(
|
|
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
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
6981
|
-
import
|
|
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
|
|
7063
|
-
import
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
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[
|
|
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.";
|
|
@@ -7397,6 +7326,7 @@ function buildGenOptions(kind, opts) {
|
|
|
7397
7326
|
if (opts.frameUrl) options.frameUrl = opts.frameUrl;
|
|
7398
7327
|
if (opts.firstFrameUrl) options.firstFrameUrl = opts.firstFrameUrl;
|
|
7399
7328
|
if (opts.lastFrameUrl) options.lastFrameUrl = opts.lastFrameUrl;
|
|
7329
|
+
if (opts.refs?.length) options.referenceImageUrls = opts.refs;
|
|
7400
7330
|
}
|
|
7401
7331
|
return options;
|
|
7402
7332
|
}
|
|
@@ -7405,7 +7335,7 @@ async function runGenerate(kind, opts) {
|
|
|
7405
7335
|
let typedPrompt = opts.prompt?.trim();
|
|
7406
7336
|
if (!typedPrompt && kind === "model" && opts.imageUrl) {
|
|
7407
7337
|
const ref = opts.imageUrl.startsWith("data:") ? "local image" : opts.imageUrl;
|
|
7408
|
-
typedPrompt = `from image: ${
|
|
7338
|
+
typedPrompt = `from image: ${path19.basename(ref).slice(0, 120)}`;
|
|
7409
7339
|
}
|
|
7410
7340
|
if (!typedPrompt) {
|
|
7411
7341
|
log.error(`Missing prompt. Usage: ${c.cyan(`genex ${kind} "<prompt>"`)}`);
|
|
@@ -7449,7 +7379,7 @@ async function runGenerate(kind, opts) {
|
|
|
7449
7379
|
return;
|
|
7450
7380
|
}
|
|
7451
7381
|
try {
|
|
7452
|
-
const bytes = await
|
|
7382
|
+
const bytes = await fs20.readFile(opts.inpaintUrl);
|
|
7453
7383
|
opts = { ...opts, inpaintUrl: `data:image/png;base64,${bytes.toString("base64")}` };
|
|
7454
7384
|
} catch {
|
|
7455
7385
|
log.error(`Couldn't read the --inpaint mask at ${opts.inpaintUrl}.`);
|
|
@@ -7475,6 +7405,23 @@ async function runGenerate(kind, opts) {
|
|
|
7475
7405
|
}
|
|
7476
7406
|
opts = { ...opts, frameUrl: inlined.dataUri };
|
|
7477
7407
|
}
|
|
7408
|
+
if (kind === "video" && opts.refs?.length) {
|
|
7409
|
+
const refs = [];
|
|
7410
|
+
for (const ref of opts.refs) {
|
|
7411
|
+
if (isRemoteRef(ref)) {
|
|
7412
|
+
refs.push(ref);
|
|
7413
|
+
continue;
|
|
7414
|
+
}
|
|
7415
|
+
const inlined = await inlineLocalImage(ref, "--ref");
|
|
7416
|
+
if (!inlined.ok) {
|
|
7417
|
+
log.error(inlined.error);
|
|
7418
|
+
process.exitCode = 1;
|
|
7419
|
+
return;
|
|
7420
|
+
}
|
|
7421
|
+
refs.push(inlined.dataUri);
|
|
7422
|
+
}
|
|
7423
|
+
opts = { ...opts, refs };
|
|
7424
|
+
}
|
|
7478
7425
|
if (kind === "model" && opts.imageUrl && !isRemoteRef(opts.imageUrl)) {
|
|
7479
7426
|
const inlined = await inlineLocalImage(opts.imageUrl, "--image");
|
|
7480
7427
|
if (!inlined.ok) {
|
|
@@ -7603,7 +7550,7 @@ async function reportGlassTerminal(view, outDir, log, json) {
|
|
|
7603
7550
|
return;
|
|
7604
7551
|
}
|
|
7605
7552
|
await recordTerminal(view.id, "completed", files.map((f) => f.url));
|
|
7606
|
-
await
|
|
7553
|
+
await fs20.mkdir(outDir, { recursive: true });
|
|
7607
7554
|
const solved = [];
|
|
7608
7555
|
for (let i = 0; i < files.length; i++) {
|
|
7609
7556
|
const f = files[i];
|
|
@@ -7635,8 +7582,8 @@ async function reportGlassTerminal(view, outDir, log, json) {
|
|
|
7635
7582
|
});
|
|
7636
7583
|
continue;
|
|
7637
7584
|
}
|
|
7638
|
-
const outPath =
|
|
7639
|
-
await
|
|
7585
|
+
const outPath = path19.join(outDir, `glass-${i + 1}.png`);
|
|
7586
|
+
await fs20.writeFile(outPath, PNG4.sync.write(r.png));
|
|
7640
7587
|
solved.push({
|
|
7641
7588
|
path: outPath,
|
|
7642
7589
|
url: f.url,
|
|
@@ -8402,8 +8349,8 @@ async function toRow(e, v, cwd) {
|
|
|
8402
8349
|
}
|
|
8403
8350
|
|
|
8404
8351
|
// src/commands/controller.ts
|
|
8405
|
-
import
|
|
8406
|
-
import
|
|
8352
|
+
import fs22 from "fs/promises";
|
|
8353
|
+
import path21 from "path";
|
|
8407
8354
|
|
|
8408
8355
|
// ../../packages/meshy-animation-catalog/src/index.ts
|
|
8409
8356
|
import { createHash } from "crypto";
|
|
@@ -17533,9 +17480,9 @@ function searchMeshyAnimations(query, options = {}) {
|
|
|
17533
17480
|
}
|
|
17534
17481
|
|
|
17535
17482
|
// src/lib/anims.ts
|
|
17536
|
-
import
|
|
17537
|
-
import
|
|
17538
|
-
var ANIMS_DEST =
|
|
17483
|
+
import fs21 from "fs/promises";
|
|
17484
|
+
import path20 from "path";
|
|
17485
|
+
var ANIMS_DEST = path20.join("public", "assets", "anims");
|
|
17539
17486
|
var HIDDEN_TAG = "reference";
|
|
17540
17487
|
async function runAnims(opts) {
|
|
17541
17488
|
const log = createLogger({ quiet: opts.quiet });
|
|
@@ -17551,7 +17498,7 @@ async function runAnims(opts) {
|
|
|
17551
17498
|
printCatalog(log, manifest, selectors);
|
|
17552
17499
|
return;
|
|
17553
17500
|
}
|
|
17554
|
-
const controllerMarker =
|
|
17501
|
+
const controllerMarker = path20.join(root, "src", "controllers", "character");
|
|
17555
17502
|
if (!await exists2(controllerMarker)) {
|
|
17556
17503
|
log.error(
|
|
17557
17504
|
`No character controller in this game (missing ${c.cyan("src/controllers/character/")}).`
|
|
@@ -17560,11 +17507,11 @@ async function runAnims(opts) {
|
|
|
17560
17507
|
process.exitCode = 1;
|
|
17561
17508
|
return;
|
|
17562
17509
|
}
|
|
17563
|
-
const destDir =
|
|
17564
|
-
const gameManifestPath =
|
|
17510
|
+
const destDir = path20.join(root, ANIMS_DEST);
|
|
17511
|
+
const gameManifestPath = path20.join(destDir, "manifest.json");
|
|
17565
17512
|
if (opts.reset) {
|
|
17566
|
-
await
|
|
17567
|
-
log.step(`Cleared ${c.cyan(ANIMS_DEST +
|
|
17513
|
+
await fs21.rm(destDir, { recursive: true, force: true });
|
|
17514
|
+
log.step(`Cleared ${c.cyan(ANIMS_DEST + path20.sep)} (--reset)`);
|
|
17568
17515
|
}
|
|
17569
17516
|
if (selectors.length === 0) {
|
|
17570
17517
|
const installed = await readGameManifest(gameManifestPath);
|
|
@@ -17602,35 +17549,35 @@ async function runAnims(opts) {
|
|
|
17602
17549
|
}
|
|
17603
17550
|
}
|
|
17604
17551
|
const wanted = [...byName.values()].sort((a, b) => a.name.localeCompare(b.name));
|
|
17605
|
-
const cacheDir =
|
|
17552
|
+
const cacheDir = path20.join(
|
|
17606
17553
|
opts.cacheDir ?? getAnimsCacheDir(),
|
|
17607
17554
|
`${manifest.library}-v${manifest.version}`
|
|
17608
17555
|
);
|
|
17609
|
-
await
|
|
17610
|
-
await
|
|
17556
|
+
await fs21.mkdir(cacheDir, { recursive: true });
|
|
17557
|
+
await fs21.mkdir(destDir, { recursive: true });
|
|
17611
17558
|
const base = getAnimsBase(opts.animsBase);
|
|
17612
17559
|
let installedCount = 0;
|
|
17613
17560
|
let presentCount = 0;
|
|
17614
17561
|
let addedBytes = 0;
|
|
17615
17562
|
const failures = [];
|
|
17616
17563
|
for (const entry of wanted) {
|
|
17617
|
-
const dest =
|
|
17564
|
+
const dest = path20.join(destDir, entry.file);
|
|
17618
17565
|
if (await hasSize(dest, entry.bytes)) {
|
|
17619
17566
|
presentCount++;
|
|
17620
17567
|
continue;
|
|
17621
17568
|
}
|
|
17622
17569
|
try {
|
|
17623
|
-
const cached =
|
|
17570
|
+
const cached = path20.join(cacheDir, entry.file);
|
|
17624
17571
|
if (!await hasSize(cached, entry.bytes)) {
|
|
17625
17572
|
const res = await fetch(base + entry.file);
|
|
17626
17573
|
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
17627
17574
|
const buf = Buffer.from(await res.arrayBuffer());
|
|
17628
|
-
await
|
|
17575
|
+
await fs21.writeFile(cached, buf);
|
|
17629
17576
|
}
|
|
17630
|
-
await
|
|
17577
|
+
await fs21.copyFile(cached, dest);
|
|
17631
17578
|
installedCount++;
|
|
17632
17579
|
addedBytes += entry.bytes;
|
|
17633
|
-
log.dim(` ${
|
|
17580
|
+
log.dim(` ${path20.join(ANIMS_DEST, entry.file)} (${formatMb(entry.bytes)})`);
|
|
17634
17581
|
} catch (err) {
|
|
17635
17582
|
failures.push(`${entry.name} (${err instanceof Error ? err.message : String(err)})`);
|
|
17636
17583
|
}
|
|
@@ -17646,13 +17593,13 @@ async function runAnims(opts) {
|
|
|
17646
17593
|
version: manifest.version,
|
|
17647
17594
|
clips: [...union].sort((a, b) => a.localeCompare(b))
|
|
17648
17595
|
};
|
|
17649
|
-
await
|
|
17596
|
+
await fs21.writeFile(gameManifestPath, JSON.stringify(gameManifest, null, 2) + "\n");
|
|
17650
17597
|
log.plain("");
|
|
17651
17598
|
const parts = [`${installedCount} clip${installedCount === 1 ? "" : "s"} installed`];
|
|
17652
17599
|
if (presentCount > 0) parts.push(`${presentCount} already present`);
|
|
17653
17600
|
if (bundledSkips > 0) parts.push(`${bundledSkips} already bundled in animation-library.glb`);
|
|
17654
17601
|
log.success(
|
|
17655
|
-
`${parts.join(", ")} \u2192 ${c.cyan(ANIMS_DEST +
|
|
17602
|
+
`${parts.join(", ")} \u2192 ${c.cyan(ANIMS_DEST + path20.sep)}${addedBytes > 0 ? ` (+${formatMb(addedBytes)})` : ""}`
|
|
17656
17603
|
);
|
|
17657
17604
|
for (const [selector, entries] of resolved) {
|
|
17658
17605
|
const names = entries.filter((e) => !coreNames.has(e.name)).map((e) => e.name);
|
|
@@ -17684,8 +17631,8 @@ async function loadManifest(baseOverride) {
|
|
|
17684
17631
|
}
|
|
17685
17632
|
} catch {
|
|
17686
17633
|
}
|
|
17687
|
-
const snapshotPath =
|
|
17688
|
-
const manifest = JSON.parse(await
|
|
17634
|
+
const snapshotPath = path20.join(getTemplatesDir(), "controllers", "assets", "anims-manifest.json");
|
|
17635
|
+
const manifest = JSON.parse(await fs21.readFile(snapshotPath, "utf8"));
|
|
17689
17636
|
return { manifest, source: "snapshot" };
|
|
17690
17637
|
}
|
|
17691
17638
|
function resolveSelectors(manifest, selectors) {
|
|
@@ -17803,21 +17750,21 @@ function printCatalog(log, manifest, selectors) {
|
|
|
17803
17750
|
}
|
|
17804
17751
|
async function readGameManifest(file) {
|
|
17805
17752
|
try {
|
|
17806
|
-
return JSON.parse(await
|
|
17753
|
+
return JSON.parse(await fs21.readFile(file, "utf8"));
|
|
17807
17754
|
} catch {
|
|
17808
17755
|
return null;
|
|
17809
17756
|
}
|
|
17810
17757
|
}
|
|
17811
17758
|
async function hasSize(file, bytes) {
|
|
17812
17759
|
try {
|
|
17813
|
-
return (await
|
|
17760
|
+
return (await fs21.stat(file)).size === bytes;
|
|
17814
17761
|
} catch {
|
|
17815
17762
|
return false;
|
|
17816
17763
|
}
|
|
17817
17764
|
}
|
|
17818
17765
|
async function exists2(p) {
|
|
17819
17766
|
try {
|
|
17820
|
-
await
|
|
17767
|
+
await fs21.access(p);
|
|
17821
17768
|
return true;
|
|
17822
17769
|
} catch {
|
|
17823
17770
|
return false;
|
|
@@ -18011,8 +17958,8 @@ var CONTROLLER_FILE_SETS = {
|
|
|
18011
17958
|
]
|
|
18012
17959
|
}
|
|
18013
17960
|
};
|
|
18014
|
-
var CODE_DEST =
|
|
18015
|
-
var ASSETS_DEST =
|
|
17961
|
+
var CODE_DEST = path21.join("src", "controllers");
|
|
17962
|
+
var ASSETS_DEST = path21.join("public", "assets");
|
|
18016
17963
|
async function runController(opts) {
|
|
18017
17964
|
const log = createLogger({ quiet: opts.quiet });
|
|
18018
17965
|
if (opts.kind?.trim() === "anims") {
|
|
@@ -18029,31 +17976,31 @@ async function runController(opts) {
|
|
|
18029
17976
|
process.exitCode = 1;
|
|
18030
17977
|
return;
|
|
18031
17978
|
}
|
|
18032
|
-
const srcDir =
|
|
17979
|
+
const srcDir = path21.join(getTemplatesDir(), "controllers");
|
|
18033
17980
|
const root = opts.cwd ?? process.cwd();
|
|
18034
17981
|
const set = CONTROLLER_FILE_SETS[kind];
|
|
18035
17982
|
log.plain(c.bold(`genex controller ${kind}`));
|
|
18036
17983
|
log.plain("");
|
|
18037
|
-
log.step(`Installing the ${kind} controller into ${c.cyan(CODE_DEST +
|
|
17984
|
+
log.step(`Installing the ${kind} controller into ${c.cyan(CODE_DEST + path21.sep)}`);
|
|
18038
17985
|
const plan = [
|
|
18039
|
-
...set.code.map((rel) => ({ from: rel, rel:
|
|
17986
|
+
...set.code.map((rel) => ({ from: rel, rel: path21.join(CODE_DEST, rel) })),
|
|
18040
17987
|
...set.assets.map((rel) => ({
|
|
18041
17988
|
from: rel,
|
|
18042
|
-
rel:
|
|
17989
|
+
rel: path21.join(ASSETS_DEST, path21.basename(rel))
|
|
18043
17990
|
}))
|
|
18044
17991
|
];
|
|
18045
17992
|
let copied = 0;
|
|
18046
17993
|
let skipped = 0;
|
|
18047
17994
|
try {
|
|
18048
17995
|
for (const file of plan) {
|
|
18049
|
-
const dest =
|
|
17996
|
+
const dest = path21.join(root, file.rel);
|
|
18050
17997
|
if (!opts.force && await exists3(dest)) {
|
|
18051
17998
|
skipped++;
|
|
18052
17999
|
log.dim(` skipped ${file.rel} (exists \u2014 use --force to overwrite)`);
|
|
18053
18000
|
continue;
|
|
18054
18001
|
}
|
|
18055
|
-
await
|
|
18056
|
-
await
|
|
18002
|
+
await fs22.mkdir(path21.dirname(dest), { recursive: true });
|
|
18003
|
+
await fs22.copyFile(path21.join(srcDir, file.from), dest);
|
|
18057
18004
|
copied++;
|
|
18058
18005
|
log.dim(` ${file.rel}`);
|
|
18059
18006
|
}
|
|
@@ -18106,7 +18053,7 @@ async function runController(opts) {
|
|
|
18106
18053
|
for (const line of set.sketch) {
|
|
18107
18054
|
log.dim(` ${line}`);
|
|
18108
18055
|
}
|
|
18109
|
-
if (kind === "character" && !await exists3(
|
|
18056
|
+
if (kind === "character" && !await exists3(path21.join(root, ASSETS_DEST, "meshy-character.json"))) {
|
|
18110
18057
|
log.plain("");
|
|
18111
18058
|
log.plain(
|
|
18112
18059
|
` ${stepOffset + 3}. This game has no generated character yet \u2014 ${c.cyan(
|
|
@@ -18138,9 +18085,9 @@ async function installMeshyCharacterManifest(args) {
|
|
|
18138
18085
|
throw new Error("The API returned an invalid Meshy character manifest.");
|
|
18139
18086
|
}
|
|
18140
18087
|
assertCompleteMeshyControllerPack(manifest);
|
|
18141
|
-
const destination =
|
|
18142
|
-
await
|
|
18143
|
-
await
|
|
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(
|
|
18144
18091
|
destination,
|
|
18145
18092
|
`${JSON.stringify(manifest, null, 2)}
|
|
18146
18093
|
`
|
|
@@ -18278,14 +18225,14 @@ function assertCompleteMeshyControllerPack(manifest) {
|
|
|
18278
18225
|
}
|
|
18279
18226
|
async function installFallbackAvatar(args) {
|
|
18280
18227
|
const { root, srcDir, log } = args;
|
|
18281
|
-
const dest =
|
|
18282
|
-
await
|
|
18283
|
-
await
|
|
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);
|
|
18284
18231
|
log.dim(" public/assets/avatar.vrm (fallback avatar \u2014 bundled CC0 default)");
|
|
18285
18232
|
}
|
|
18286
18233
|
async function exists3(p) {
|
|
18287
18234
|
try {
|
|
18288
|
-
await
|
|
18235
|
+
await fs22.access(p);
|
|
18289
18236
|
return true;
|
|
18290
18237
|
} catch {
|
|
18291
18238
|
return false;
|
|
@@ -18293,8 +18240,8 @@ async function exists3(p) {
|
|
|
18293
18240
|
}
|
|
18294
18241
|
|
|
18295
18242
|
// src/commands/character.ts
|
|
18296
|
-
import
|
|
18297
|
-
import
|
|
18243
|
+
import fs23 from "fs/promises";
|
|
18244
|
+
import path22 from "path";
|
|
18298
18245
|
function exactAnimation(selector) {
|
|
18299
18246
|
const trimmed = selector.trim();
|
|
18300
18247
|
if (/^-?\d+$/.test(trimmed)) return animationById(Number(trimmed));
|
|
@@ -18374,7 +18321,7 @@ function showAmbiguity(selector, candidates, log, json = false) {
|
|
|
18374
18321
|
}
|
|
18375
18322
|
process.exitCode = 1;
|
|
18376
18323
|
}
|
|
18377
|
-
var INSTALLED_MANIFEST =
|
|
18324
|
+
var INSTALLED_MANIFEST = path22.join("public", "assets", "meshy-character.json");
|
|
18378
18325
|
async function resolveAdoptTarget(selector) {
|
|
18379
18326
|
const trimmed = selector?.trim();
|
|
18380
18327
|
if (trimmed && !trimmed.endsWith(".json")) {
|
|
@@ -18383,7 +18330,7 @@ async function resolveAdoptTarget(selector) {
|
|
|
18383
18330
|
const file = trimmed ?? INSTALLED_MANIFEST;
|
|
18384
18331
|
let raw;
|
|
18385
18332
|
try {
|
|
18386
|
-
raw = await
|
|
18333
|
+
raw = await fs23.readFile(file, "utf8");
|
|
18387
18334
|
} catch {
|
|
18388
18335
|
return {
|
|
18389
18336
|
ok: false,
|
|
@@ -19212,8 +19159,8 @@ function rank(items, query) {
|
|
|
19212
19159
|
}
|
|
19213
19160
|
|
|
19214
19161
|
// src/commands/motion.ts
|
|
19215
|
-
import
|
|
19216
|
-
import
|
|
19162
|
+
import fs24 from "fs/promises";
|
|
19163
|
+
import path23 from "path";
|
|
19217
19164
|
|
|
19218
19165
|
// src/lib/motion/npz.ts
|
|
19219
19166
|
import zlib from "zlib";
|
|
@@ -20464,7 +20411,7 @@ async function motionGen(opts, log) {
|
|
|
20464
20411
|
}
|
|
20465
20412
|
if (opts.constraintsPath !== void 0) {
|
|
20466
20413
|
try {
|
|
20467
|
-
const raw = await
|
|
20414
|
+
const raw = await fs24.readFile(opts.constraintsPath, "utf8");
|
|
20468
20415
|
generationOptions.constraints = JSON.parse(raw);
|
|
20469
20416
|
} catch {
|
|
20470
20417
|
log.error(`Couldn't read the --constraints JSON at ${opts.constraintsPath}.`);
|
|
@@ -20488,10 +20435,10 @@ async function motionGen(opts, log) {
|
|
|
20488
20435
|
async function expandTakes(selectors) {
|
|
20489
20436
|
const out = [];
|
|
20490
20437
|
for (const sel of selectors) {
|
|
20491
|
-
const st = await
|
|
20438
|
+
const st = await fs24.stat(sel).catch(() => null);
|
|
20492
20439
|
if (st?.isDirectory()) {
|
|
20493
|
-
const names = await
|
|
20494
|
-
for (const n of names.sort()) if (n.endsWith(".npz")) out.push(
|
|
20440
|
+
const names = await fs24.readdir(sel);
|
|
20441
|
+
for (const n of names.sort()) if (n.endsWith(".npz")) out.push(path23.join(sel, n));
|
|
20495
20442
|
} else if (st?.isFile()) {
|
|
20496
20443
|
out.push(sel);
|
|
20497
20444
|
} else {
|
|
@@ -20526,7 +20473,7 @@ async function motionVerify(opts, log) {
|
|
|
20526
20473
|
let gates = DEFAULT_GATES;
|
|
20527
20474
|
if (opts.gatesPath) {
|
|
20528
20475
|
try {
|
|
20529
|
-
gates = mergeGates(DEFAULT_GATES, JSON.parse(await
|
|
20476
|
+
gates = mergeGates(DEFAULT_GATES, JSON.parse(await fs24.readFile(opts.gatesPath, "utf8")));
|
|
20530
20477
|
} catch {
|
|
20531
20478
|
log.error(`Couldn't read the --gates JSON at ${opts.gatesPath}.`);
|
|
20532
20479
|
process.exitCode = 1;
|
|
@@ -20548,9 +20495,9 @@ async function motionVerify(opts, log) {
|
|
|
20548
20495
|
}
|
|
20549
20496
|
const reports = [];
|
|
20550
20497
|
for (const file of files) {
|
|
20551
|
-
const stem =
|
|
20498
|
+
const stem = path23.basename(file).replace(/\.npz$/, "");
|
|
20552
20499
|
try {
|
|
20553
|
-
reports.push(analyzeTake(stem, await
|
|
20500
|
+
reports.push(analyzeTake(stem, await fs24.readFile(file), gates));
|
|
20554
20501
|
} catch (err) {
|
|
20555
20502
|
reports.push({
|
|
20556
20503
|
take: stem,
|
|
@@ -20588,7 +20535,7 @@ async function motionCompile(opts, log) {
|
|
|
20588
20535
|
let cfg = DEFAULT_MOTION_CONFIG;
|
|
20589
20536
|
if (opts.configPath) {
|
|
20590
20537
|
try {
|
|
20591
|
-
const patch = JSON.parse(await
|
|
20538
|
+
const patch = JSON.parse(await fs24.readFile(opts.configPath, "utf8"));
|
|
20592
20539
|
cfg = { ...DEFAULT_MOTION_CONFIG, ...patch, idleLoop: { ...DEFAULT_MOTION_CONFIG.idleLoop, ...patch.idleLoop } };
|
|
20593
20540
|
} catch {
|
|
20594
20541
|
log.error(`Couldn't read the --config JSON at ${opts.configPath}.`);
|
|
@@ -20606,16 +20553,16 @@ async function motionCompile(opts, log) {
|
|
|
20606
20553
|
}
|
|
20607
20554
|
const inputs = [];
|
|
20608
20555
|
for (const file of files) {
|
|
20609
|
-
const stem =
|
|
20556
|
+
const stem = path23.basename(file).replace(/\.npz$/, "");
|
|
20610
20557
|
try {
|
|
20611
|
-
inputs.push({ stem, take: loadTake(await
|
|
20558
|
+
inputs.push({ stem, take: loadTake(await fs24.readFile(file)) });
|
|
20612
20559
|
} catch (err) {
|
|
20613
20560
|
log.error(`${stem}: ${err instanceof Error ? err.message : String(err)}`);
|
|
20614
20561
|
process.exitCode = 1;
|
|
20615
20562
|
return;
|
|
20616
20563
|
}
|
|
20617
20564
|
}
|
|
20618
|
-
const setName = opts.set ??
|
|
20565
|
+
const setName = opts.set ?? path23.basename(opts.out).replace(/\.json$/, "");
|
|
20619
20566
|
let result;
|
|
20620
20567
|
try {
|
|
20621
20568
|
result = compileSet(inputs, setName, cfg);
|
|
@@ -20630,9 +20577,9 @@ async function motionCompile(opts, log) {
|
|
|
20630
20577
|
process.exitCode = 1;
|
|
20631
20578
|
return;
|
|
20632
20579
|
}
|
|
20633
|
-
await
|
|
20580
|
+
await fs24.mkdir(path23.dirname(path23.resolve(opts.out)), { recursive: true });
|
|
20634
20581
|
const json = JSON.stringify(result.data);
|
|
20635
|
-
await
|
|
20582
|
+
await fs24.writeFile(opts.out, json);
|
|
20636
20583
|
if (opts.json) {
|
|
20637
20584
|
writeJson({ out: opts.out, set: setName, tracks: Object.keys(result.data.gaits), bytes: json.length });
|
|
20638
20585
|
return;
|
|
@@ -20650,9 +20597,9 @@ var MOTION_RUNTIME_FILES = [
|
|
|
20650
20597
|
var MOTION_PRESETS = {
|
|
20651
20598
|
rifle: ["sets/rifle.json", "sets/jumps.json"]
|
|
20652
20599
|
};
|
|
20653
|
-
var MOTION_DEST =
|
|
20600
|
+
var MOTION_DEST = path23.join("src", "motion");
|
|
20654
20601
|
async function motionInstall(opts, log) {
|
|
20655
|
-
const srcDir =
|
|
20602
|
+
const srcDir = path23.join(getTemplatesDir(), "motion");
|
|
20656
20603
|
const root = opts.cwd ?? process.cwd();
|
|
20657
20604
|
const preset = opts.set;
|
|
20658
20605
|
if (preset !== void 0 && !MOTION_PRESETS[preset]) {
|
|
@@ -20663,21 +20610,21 @@ async function motionInstall(opts, log) {
|
|
|
20663
20610
|
const files = [...MOTION_RUNTIME_FILES, ...preset ? MOTION_PRESETS[preset] : []];
|
|
20664
20611
|
log.plain(c.bold(`genex motion install${preset ? ` --set ${preset}` : ""}`));
|
|
20665
20612
|
log.plain("");
|
|
20666
|
-
log.step(`Vendoring the motion runtime into ${c.cyan(MOTION_DEST +
|
|
20613
|
+
log.step(`Vendoring the motion runtime into ${c.cyan(MOTION_DEST + path23.sep)}`);
|
|
20667
20614
|
let copied = 0, skipped = 0;
|
|
20668
20615
|
try {
|
|
20669
20616
|
for (const rel of files) {
|
|
20670
|
-
const dest =
|
|
20671
|
-
const exists5 = await
|
|
20617
|
+
const dest = path23.join(root, MOTION_DEST, rel);
|
|
20618
|
+
const exists5 = await fs24.access(dest).then(() => true, () => false);
|
|
20672
20619
|
if (!opts.force && exists5) {
|
|
20673
20620
|
skipped++;
|
|
20674
|
-
log.dim(` skipped ${
|
|
20621
|
+
log.dim(` skipped ${path23.join(MOTION_DEST, rel)} (exists \u2014 use --force to overwrite)`);
|
|
20675
20622
|
continue;
|
|
20676
20623
|
}
|
|
20677
|
-
await
|
|
20678
|
-
await
|
|
20624
|
+
await fs24.mkdir(path23.dirname(dest), { recursive: true });
|
|
20625
|
+
await fs24.copyFile(path23.join(srcDir, rel), dest);
|
|
20679
20626
|
copied++;
|
|
20680
|
-
log.dim(` ${
|
|
20627
|
+
log.dim(` ${path23.join(MOTION_DEST, rel)}`);
|
|
20681
20628
|
}
|
|
20682
20629
|
} catch (err) {
|
|
20683
20630
|
log.error(`Copy failed: ${String(err)}`);
|
|
@@ -20718,7 +20665,7 @@ async function motionConstraints(opts, log) {
|
|
|
20718
20665
|
}
|
|
20719
20666
|
const doc = directionConstraint(dir, speed, duration);
|
|
20720
20667
|
const out = opts.out ?? "constraints.json";
|
|
20721
|
-
await
|
|
20668
|
+
await fs24.writeFile(out, JSON.stringify(doc));
|
|
20722
20669
|
if (opts.json) {
|
|
20723
20670
|
writeJson({ out, dir: dirName, speed, duration, waypoints: doc[0].frame_indices.length });
|
|
20724
20671
|
return;
|
|
@@ -20754,6 +20701,214 @@ async function runMotion(opts) {
|
|
|
20754
20701
|
}
|
|
20755
20702
|
}
|
|
20756
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
|
+
|
|
20757
20912
|
// src/commands/asset-new.ts
|
|
20758
20913
|
import fs26 from "fs";
|
|
20759
20914
|
import fsp from "fs/promises";
|
|
@@ -21725,13 +21880,18 @@ ${c.bold("Options for the generators (`model` `sfx` `music` `voice` `texture` `i
|
|
|
21725
21880
|
--bg-mode <mode> (image) background-removal model: sprite | glyph | sheet |
|
|
21726
21881
|
matte (BiRefNet soft alpha \u2014 hair/glow/smoke edges).
|
|
21727
21882
|
--loop (video) generate a seamless loop.
|
|
21728
|
-
--resolution <r> (video)
|
|
21729
|
-
|
|
21730
|
-
|
|
21883
|
+
--resolution <r> (video) 480p | 768p | 2k | 4k (default 768p \u2014 the top
|
|
21884
|
+
native mode; 2k/4k are upscales of the same base and
|
|
21885
|
+
cost more; 480p is the cost opt-down; loop clips ignore
|
|
21886
|
+
it, model default).
|
|
21731
21887
|
--frame <url|file> (video) animate from THIS frame back to itself (seamless
|
|
21732
21888
|
loop) \u2014 an asset URL or a local image file (\u22644 MB).
|
|
21733
21889
|
--first-frame <url> (video) start frame for a two-frame motion \u2026
|
|
21734
21890
|
--last-frame <url> (video) \u2026 and its end frame.
|
|
21891
|
+
--ref <url|file> (video, repeatable \xD79) subject/style reference images \u2014
|
|
21892
|
+
cite them in the prompt as "Image 1"\u2026"Image N"; keeps
|
|
21893
|
+
the same character/style across many clips. Cannot be
|
|
21894
|
+
combined with --frame/--loop.
|
|
21735
21895
|
--no-wait Enqueue only; don't block waiting for the asset. Prints
|
|
21736
21896
|
the id that "genex wait <id>" picks the result up with.
|
|
21737
21897
|
--no-download (Genex Tools workspaces) print the asset URL only instead
|
|
@@ -22025,11 +22185,14 @@ function parseArgs(argv) {
|
|
|
22025
22185
|
"--frame",
|
|
22026
22186
|
"--first-frame",
|
|
22027
22187
|
"--last-frame",
|
|
22188
|
+
"--ref",
|
|
22028
22189
|
"--resolution",
|
|
22029
22190
|
// `genex model` mesh-lane value flags (AG-908).
|
|
22030
22191
|
"--image",
|
|
22031
22192
|
"--granularity",
|
|
22032
22193
|
"--preset",
|
|
22194
|
+
// `genex blender` (M1 spike): which shading the contact sheet uses.
|
|
22195
|
+
"--mode",
|
|
22033
22196
|
// `genex ui` string flags (the numeric ones come from UI_NUMBER_FLAGS).
|
|
22034
22197
|
"--in",
|
|
22035
22198
|
"--out-dir",
|
|
@@ -22530,14 +22693,17 @@ function applyValueFlag(options, flag, value) {
|
|
|
22530
22693
|
options.bgMode = value;
|
|
22531
22694
|
break;
|
|
22532
22695
|
case "--resolution":
|
|
22533
|
-
if (!["720p", "1080p"].includes(value)) {
|
|
22534
|
-
throw new Error(`Invalid --resolution value: ${value} (expected
|
|
22696
|
+
if (!["480p", "768p", "2k", "4k", "720p", "1080p"].includes(value)) {
|
|
22697
|
+
throw new Error(`Invalid --resolution value: ${value} (expected 480p|768p|2k|4k)`);
|
|
22535
22698
|
}
|
|
22536
22699
|
options.resolution = value;
|
|
22537
22700
|
break;
|
|
22538
22701
|
case "--frame":
|
|
22539
22702
|
options.frameUrl = value;
|
|
22540
22703
|
break;
|
|
22704
|
+
case "--ref":
|
|
22705
|
+
options.refs = [...options.refs ?? [], value];
|
|
22706
|
+
break;
|
|
22541
22707
|
case "--image":
|
|
22542
22708
|
options.imageUrl = value;
|
|
22543
22709
|
break;
|
|
@@ -22556,6 +22722,9 @@ function applyValueFlag(options, flag, value) {
|
|
|
22556
22722
|
case "--last-frame":
|
|
22557
22723
|
options.lastFrameUrl = value;
|
|
22558
22724
|
break;
|
|
22725
|
+
case "--mode":
|
|
22726
|
+
options.mode = value;
|
|
22727
|
+
break;
|
|
22559
22728
|
case "--in":
|
|
22560
22729
|
options.input = value;
|
|
22561
22730
|
break;
|
|
@@ -22611,7 +22780,8 @@ async function main() {
|
|
|
22611
22780
|
log.plain(mode === "tools" ? HELP_TOOLS : HELP);
|
|
22612
22781
|
return;
|
|
22613
22782
|
}
|
|
22614
|
-
const
|
|
22783
|
+
const isBlenderMcp = parsed.command === "blender" && parsed.options.name === "mcp";
|
|
22784
|
+
const quietUpdates = Boolean(parsed.options.quiet || parsed.options.json || isBlenderMcp);
|
|
22615
22785
|
const updateLog = createLogger({ quiet: quietUpdates });
|
|
22616
22786
|
if (parsed.command !== "init" && parsed.command !== "tools") {
|
|
22617
22787
|
await syncSkills(updateLog, mode);
|
|
@@ -22782,6 +22952,12 @@ async function main() {
|
|
|
22782
22952
|
case "ui":
|
|
22783
22953
|
await runUi({ ...parsed.options, sub: parsed.options.name });
|
|
22784
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;
|
|
22785
22961
|
// PARKED (AG-824 D12) — still dispatches, deliberately unlisted in --help
|
|
22786
22962
|
// and gone from the skills, the same way `genex explore` is parked.
|
|
22787
22963
|
//
|