@genex-ai/cli-demo 1.34.2 → 1.34.3-dev.704
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/README.md +21 -3
- package/dist/{blender-mcp-X66ZZ4TN.js → blender-mcp-XLEK6SLM.js} +2 -2
- package/dist/{blender-serve-PVVCH3S6.js → blender-serve-GW3LTFOX.js} +1 -1
- package/dist/{chunk-5FA2WLM7.js → chunk-AQW7HUPV.js} +42 -4
- package/dist/{chunk-OJAXYQ5L.js → chunk-MRV3Q3WU.js} +100 -81
- package/dist/index.js +438 -395
- package/package.json +1 -1
- package/templates/skills/genex-getting-started/SKILL.md +2 -2
- package/templates/skills/genex-updates/SKILL.md +1 -1
package/dist/index.js
CHANGED
|
@@ -30,7 +30,6 @@ import {
|
|
|
30
30
|
reportForceRefused,
|
|
31
31
|
reportStale,
|
|
32
32
|
reportTermsRefusal,
|
|
33
|
-
restrictFilePermissions,
|
|
34
33
|
rotateRejectedEnv,
|
|
35
34
|
run,
|
|
36
35
|
runAccept,
|
|
@@ -40,15 +39,18 @@ import {
|
|
|
40
39
|
sourceTreeHash,
|
|
41
40
|
urlHasEmbeddedCredentials,
|
|
42
41
|
writeProject,
|
|
42
|
+
writeSecretFile,
|
|
43
43
|
writeUserToken,
|
|
44
44
|
writeWorkspace
|
|
45
|
-
} from "./chunk-
|
|
45
|
+
} from "./chunk-MRV3Q3WU.js";
|
|
46
46
|
import {
|
|
47
47
|
CLI_CHANNEL,
|
|
48
48
|
DEFAULT_API_URL,
|
|
49
49
|
DEFAULT_AUTH_URL,
|
|
50
|
-
|
|
50
|
+
ENV_FILE_ENV,
|
|
51
51
|
STANDS,
|
|
52
|
+
assertAuthorizationOrigin,
|
|
53
|
+
bindToken,
|
|
52
54
|
c,
|
|
53
55
|
getAnimsBase,
|
|
54
56
|
getAnimsCacheDir,
|
|
@@ -58,8 +60,10 @@ import {
|
|
|
58
60
|
getGenexDir,
|
|
59
61
|
getGenexEnvPath,
|
|
60
62
|
getTemplatesDir,
|
|
63
|
+
normalizeApiOrigin,
|
|
64
|
+
originKey,
|
|
61
65
|
resolveAgentTargets
|
|
62
|
-
} from "./chunk-
|
|
66
|
+
} from "./chunk-AQW7HUPV.js";
|
|
63
67
|
|
|
64
68
|
// src/instrument.ts
|
|
65
69
|
import * as Sentry from "@sentry/node";
|
|
@@ -169,32 +173,45 @@ import os2 from "os";
|
|
|
169
173
|
import readline from "readline";
|
|
170
174
|
import { spawn } from "child_process";
|
|
171
175
|
import { URL as URL2 } from "url";
|
|
176
|
+
import path2 from "path";
|
|
172
177
|
|
|
173
178
|
// src/lib/pending-auth.ts
|
|
174
179
|
import fs from "fs/promises";
|
|
175
180
|
import path from "path";
|
|
176
|
-
function pendingAuthPath() {
|
|
181
|
+
function pendingAuthPath(apiUrl = getApiUrl(), envPath) {
|
|
182
|
+
return path.join(`${getGenexEnvPath(envPath)}.auth`, `${originKey(apiUrl)}.json`);
|
|
183
|
+
}
|
|
184
|
+
function legacyPath() {
|
|
177
185
|
return path.join(getGenexDir(), "pending-auth.json");
|
|
178
186
|
}
|
|
179
|
-
|
|
187
|
+
function defaultProfile(envPath) {
|
|
188
|
+
return !envPath && !process.env[ENV_FILE_ENV];
|
|
189
|
+
}
|
|
190
|
+
async function readRecord(file, apiUrl) {
|
|
180
191
|
try {
|
|
181
|
-
const raw = JSON.parse(await fs.readFile(
|
|
182
|
-
if (!raw?.deviceCode || !raw.userCode || raw.apiUrl !== apiUrl) return null;
|
|
183
|
-
|
|
184
|
-
return raw;
|
|
192
|
+
const raw = JSON.parse(await fs.readFile(file, "utf8"));
|
|
193
|
+
if (!raw?.deviceCode || !raw.userCode || !raw.verifyUrl || normalizeApiOrigin(raw.apiUrl) !== normalizeApiOrigin(apiUrl) || !Number.isFinite(raw.expiresAt) || Date.now() >= raw.expiresAt) return null;
|
|
194
|
+
return { ...raw, apiUrl: normalizeApiOrigin(raw.apiUrl) };
|
|
185
195
|
} catch {
|
|
186
196
|
return null;
|
|
187
197
|
}
|
|
188
198
|
}
|
|
189
|
-
async function
|
|
190
|
-
const
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
await restrictFilePermissions(file);
|
|
199
|
+
async function readPendingAuth(apiUrl, envPath) {
|
|
200
|
+
const current = await readRecord(pendingAuthPath(apiUrl, envPath), apiUrl);
|
|
201
|
+
if (current) return current;
|
|
202
|
+
return defaultProfile(envPath) ? readRecord(legacyPath(), apiUrl) : null;
|
|
194
203
|
}
|
|
195
|
-
async function
|
|
196
|
-
|
|
204
|
+
async function writePendingAuth(pending, envPath) {
|
|
205
|
+
const apiUrl = normalizeApiOrigin(pending.apiUrl);
|
|
206
|
+
await writeSecretFile(pendingAuthPath(apiUrl, envPath), JSON.stringify({ ...pending, apiUrl }) + "\n");
|
|
207
|
+
}
|
|
208
|
+
async function clearPendingAuth(apiUrl = getApiUrl(), envPath) {
|
|
209
|
+
await fs.rm(pendingAuthPath(apiUrl, envPath), { force: true }).catch(() => {
|
|
197
210
|
});
|
|
211
|
+
if (defaultProfile(envPath) && await readRecord(legacyPath(), apiUrl)) {
|
|
212
|
+
await fs.rm(legacyPath(), { force: true }).catch(() => {
|
|
213
|
+
});
|
|
214
|
+
}
|
|
198
215
|
}
|
|
199
216
|
|
|
200
217
|
// src/lib/auth.ts
|
|
@@ -206,11 +223,15 @@ var AuthPendingError = class extends Error {
|
|
|
206
223
|
// .ts sources directly by stripping types) forbids the shorthand.
|
|
207
224
|
userCode;
|
|
208
225
|
verifyUrl;
|
|
209
|
-
|
|
226
|
+
apiUrl;
|
|
227
|
+
envPath;
|
|
228
|
+
constructor(userCode, verifyUrl, apiUrl, envPath) {
|
|
210
229
|
super("Authorization is still pending.");
|
|
211
230
|
this.name = "AuthPendingError";
|
|
212
231
|
this.userCode = userCode;
|
|
213
232
|
this.verifyUrl = verifyUrl;
|
|
233
|
+
this.apiUrl = apiUrl;
|
|
234
|
+
this.envPath = envPath;
|
|
214
235
|
}
|
|
215
236
|
};
|
|
216
237
|
function formatUserCode(code) {
|
|
@@ -218,12 +239,13 @@ function formatUserCode(code) {
|
|
|
218
239
|
}
|
|
219
240
|
var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
220
241
|
async function authorize(apiBaseUrl, authBaseUrl, options) {
|
|
221
|
-
|
|
222
|
-
const
|
|
242
|
+
apiBaseUrl = normalizeApiOrigin(apiBaseUrl);
|
|
243
|
+
const { log, inlineWaitMs = DEFAULT_INLINE_WAIT_MS, open = openBrowser, label, envPath } = options;
|
|
244
|
+
const resumed = await readPendingAuth(apiBaseUrl, options.envPath);
|
|
223
245
|
if (resumed) {
|
|
224
246
|
log.step("Picking up where the last sign-in left off\u2026");
|
|
225
247
|
printCode(log, resumed.userCode, resumed.verifyUrl);
|
|
226
|
-
return await pollForToken(apiBaseUrl, resumed, { log, inlineWaitMs });
|
|
248
|
+
return await pollForToken(apiBaseUrl, resumed, { log, inlineWaitMs, envPath: options.envPath });
|
|
227
249
|
}
|
|
228
250
|
let started;
|
|
229
251
|
try {
|
|
@@ -231,7 +253,7 @@ async function authorize(apiBaseUrl, authBaseUrl, options) {
|
|
|
231
253
|
} catch (err) {
|
|
232
254
|
if (err instanceof LegacyApiError) {
|
|
233
255
|
log.dim("This Genex API doesn't support device sign-in yet \u2014 using the legacy browser flow.");
|
|
234
|
-
return await authorizeLoopback(authBaseUrl, { log, open, timeoutMs: inlineWaitMs });
|
|
256
|
+
return bindToken(await authorizeLoopback(authBaseUrl, { log, open, timeoutMs: inlineWaitMs }), apiBaseUrl);
|
|
235
257
|
}
|
|
236
258
|
throw err;
|
|
237
259
|
}
|
|
@@ -242,7 +264,7 @@ async function authorize(apiBaseUrl, authBaseUrl, options) {
|
|
|
242
264
|
expiresAt: Date.now() + started.expiresIn * 1e3,
|
|
243
265
|
apiUrl: apiBaseUrl
|
|
244
266
|
};
|
|
245
|
-
await writePendingAuth(pending);
|
|
267
|
+
await writePendingAuth(pending, envPath);
|
|
246
268
|
printCode(log, started.userCode, started.verifyUrl);
|
|
247
269
|
let warned = false;
|
|
248
270
|
const warnManual = () => {
|
|
@@ -251,7 +273,7 @@ async function authorize(apiBaseUrl, authBaseUrl, options) {
|
|
|
251
273
|
log.dim(" (couldn't open a browser here \u2014 open the link above yourself)");
|
|
252
274
|
};
|
|
253
275
|
if (!open(started.verifyUrl, warnManual)) warnManual();
|
|
254
|
-
return await pollForToken(apiBaseUrl, pending, { log, inlineWaitMs, interval: started.interval });
|
|
276
|
+
return await pollForToken(apiBaseUrl, pending, { log, inlineWaitMs, interval: started.interval, envPath });
|
|
255
277
|
}
|
|
256
278
|
var LegacyApiError = class extends Error {
|
|
257
279
|
};
|
|
@@ -314,23 +336,28 @@ async function pollForToken(apiBaseUrl, pending, opts) {
|
|
|
314
336
|
res = new Response(null, { status: 599 });
|
|
315
337
|
}
|
|
316
338
|
if (res.status === 410) {
|
|
317
|
-
await clearPendingAuth();
|
|
339
|
+
await clearPendingAuth(apiBaseUrl, opts.envPath);
|
|
318
340
|
throw new Error("That sign-in code expired. Re-run this command for a fresh one.");
|
|
319
341
|
}
|
|
320
342
|
if (res.ok) {
|
|
321
343
|
const body = await res.json().catch(() => null);
|
|
322
344
|
if (body?.status === "approved" && body.token) {
|
|
323
|
-
await clearPendingAuth();
|
|
324
|
-
return body.token;
|
|
345
|
+
await clearPendingAuth(apiBaseUrl, opts.envPath);
|
|
346
|
+
return bindToken(body.token, apiBaseUrl);
|
|
325
347
|
}
|
|
326
348
|
if (body?.status === "denied") {
|
|
327
|
-
await clearPendingAuth();
|
|
349
|
+
await clearPendingAuth(apiBaseUrl, opts.envPath);
|
|
328
350
|
throw new Error("Sign-in was cancelled in the browser. Re-run this command to try again.");
|
|
329
351
|
}
|
|
330
352
|
if (body?.interval) intervalMs = Math.max(body.interval * 1e3, MIN_POLL_INTERVAL_MS);
|
|
331
353
|
}
|
|
332
354
|
if (Date.now() + intervalMs > deadline) {
|
|
333
|
-
throw new AuthPendingError(
|
|
355
|
+
throw new AuthPendingError(
|
|
356
|
+
pending.userCode,
|
|
357
|
+
pending.verifyUrl,
|
|
358
|
+
apiBaseUrl,
|
|
359
|
+
opts.envPath || process.env[ENV_FILE_ENV] ? getGenexEnvPath(opts.envPath) : void 0
|
|
360
|
+
);
|
|
334
361
|
}
|
|
335
362
|
if (Date.now() >= nextReassureAt) {
|
|
336
363
|
log.dim(" still waiting for you to approve\u2026");
|
|
@@ -340,22 +367,24 @@ async function pollForToken(apiBaseUrl, pending, opts) {
|
|
|
340
367
|
}
|
|
341
368
|
}
|
|
342
369
|
function printAuthHandoff(log, err) {
|
|
370
|
+
const quote2 = (value) => /^[A-Za-z0-9_./:@-]+$/.test(value) ? value : process.platform === "win32" ? "'" + value.replace(/'/g, "''") + "'" : "'" + value.replace(/'/g, "'\\''") + "'";
|
|
371
|
+
const command = "npx genex auth" + (err.apiUrl ? ` --api-url ${quote2(err.apiUrl)}` : "") + (err.envPath ? ` --env ${quote2(path2.resolve(err.envPath))}` : "");
|
|
343
372
|
log.plain("");
|
|
344
373
|
log.warn("Not approved yet \u2014 nothing is broken, the sign-in is still waiting for you.");
|
|
345
374
|
log.plain(
|
|
346
375
|
[
|
|
347
376
|
` ${c.cyan("\u2192")} Approve at ${c.cyan(err.verifyUrl)} (code ${c.bold(formatUserCode(err.userCode))})`,
|
|
348
|
-
` ${c.cyan("\u2192")} Then run ${c.cyan(
|
|
377
|
+
` ${c.cyan("\u2192")} Then run ${c.cyan(command)} \u2014 it picks up exactly where this left off.`,
|
|
349
378
|
""
|
|
350
379
|
].join("\n")
|
|
351
380
|
);
|
|
352
381
|
}
|
|
353
382
|
async function resumeAuthorization(apiBaseUrl, options) {
|
|
354
|
-
const pending = await readPendingAuth(apiBaseUrl);
|
|
383
|
+
const pending = await readPendingAuth(apiBaseUrl, options.envPath);
|
|
355
384
|
if (!pending) return null;
|
|
356
385
|
const { log, inlineWaitMs = DEFAULT_INLINE_WAIT_MS } = options;
|
|
357
386
|
printCode(log, pending.userCode, pending.verifyUrl);
|
|
358
|
-
return await pollForToken(apiBaseUrl, pending, { log, inlineWaitMs });
|
|
387
|
+
return await pollForToken(apiBaseUrl, pending, { log, inlineWaitMs, envPath: options.envPath });
|
|
359
388
|
}
|
|
360
389
|
var SUCCESS_HTML = `<!doctype html><html><head><meta charset="utf-8"><title>Genex</title>
|
|
361
390
|
<style>body{font-family:system-ui,sans-serif;background:#0b0b0f;color:#eaeaea;display:grid;place-items:center;height:100vh;margin:0}
|
|
@@ -626,7 +655,7 @@ async function runAuth(opts) {
|
|
|
626
655
|
const authUrl = getAuthUrl(opts.authUrl);
|
|
627
656
|
const inlineWaitMs = opts.timeoutSec ? opts.timeoutSec * 1e3 : void 0;
|
|
628
657
|
if (!opts.force) {
|
|
629
|
-
const existing = await readUserToken(opts.envPath);
|
|
658
|
+
const existing = await readUserToken(opts.envPath, apiUrl);
|
|
630
659
|
if (existing) {
|
|
631
660
|
const email2 = await fetchSignedInEmail(apiUrl, existing);
|
|
632
661
|
if (email2) {
|
|
@@ -639,8 +668,8 @@ async function runAuth(opts) {
|
|
|
639
668
|
}
|
|
640
669
|
let token;
|
|
641
670
|
try {
|
|
642
|
-
token = await resumeAuthorization(apiUrl, { log, inlineWaitMs }) ?? "";
|
|
643
|
-
if (!token) token = await authorize(apiUrl, authUrl, { log, inlineWaitMs });
|
|
671
|
+
token = await resumeAuthorization(apiUrl, { log, inlineWaitMs, envPath: opts.envPath }) ?? "";
|
|
672
|
+
if (!token) token = await authorize(apiUrl, authUrl, { log, inlineWaitMs, envPath: opts.envPath });
|
|
644
673
|
} catch (err) {
|
|
645
674
|
if (err instanceof AuthPendingError) {
|
|
646
675
|
printAuthHandoff(log, err);
|
|
@@ -650,7 +679,7 @@ async function runAuth(opts) {
|
|
|
650
679
|
process.exitCode = 1;
|
|
651
680
|
return;
|
|
652
681
|
}
|
|
653
|
-
const { path: tokenPath } = await writeUserToken(token, opts.envPath);
|
|
682
|
+
const { path: tokenPath } = await writeUserToken(token, opts.envPath, apiUrl);
|
|
654
683
|
log.success(`Connected. Saved your token to ${c.cyan(tokenPath)}.`);
|
|
655
684
|
const email = await fetchSignedInEmail(apiUrl, token);
|
|
656
685
|
if (email) log.plain(` signed in as ${c.cyan(email)}`);
|
|
@@ -660,13 +689,13 @@ async function runAuth(opts) {
|
|
|
660
689
|
|
|
661
690
|
// src/commands/remix.ts
|
|
662
691
|
import fs18 from "fs/promises";
|
|
663
|
-
import
|
|
692
|
+
import path18 from "path";
|
|
664
693
|
import crypto4 from "crypto";
|
|
665
694
|
import { spawn as spawn4 } from "child_process";
|
|
666
695
|
|
|
667
696
|
// src/lib/game-config.ts
|
|
668
697
|
import fs2 from "fs/promises";
|
|
669
|
-
import
|
|
698
|
+
import path3 from "path";
|
|
670
699
|
var DEFAULT_DASHBOARD_ORIGIN = new URL(DEFAULT_AUTH_URL).origin;
|
|
671
700
|
function renderGenexConfig(slug) {
|
|
672
701
|
return `// src/genex.config.ts \u2014 written by \`genex init\`. DO NOT hardcode URLs here.
|
|
@@ -759,20 +788,20 @@ async function ensureEnvVar(file, key, value, log) {
|
|
|
759
788
|
return true;
|
|
760
789
|
}
|
|
761
790
|
async function writeGameConfigFiles(meta, log, cwd = process.cwd()) {
|
|
762
|
-
await fs2.mkdir(
|
|
763
|
-
await writeIfAbsent(
|
|
764
|
-
await writeIfAbsent(
|
|
791
|
+
await fs2.mkdir(path3.join(cwd, "src"), { recursive: true });
|
|
792
|
+
await writeIfAbsent(path3.join(cwd, "src", "genex.config.ts"), renderGenexConfig(meta.slug), log);
|
|
793
|
+
await writeIfAbsent(path3.join(cwd, ".env"), renderSlugEnv(meta.slug), log);
|
|
765
794
|
const overrides = renderDevOverrides(meta);
|
|
766
795
|
if (overrides) {
|
|
767
|
-
await writeIfAbsent(
|
|
796
|
+
await writeIfAbsent(path3.join(cwd, ".env.development.local"), overrides, log);
|
|
768
797
|
}
|
|
769
798
|
}
|
|
770
799
|
|
|
771
800
|
// src/lib/ssh.ts
|
|
772
801
|
import fs3 from "fs/promises";
|
|
773
|
-
import
|
|
802
|
+
import path4 from "path";
|
|
774
803
|
async function writeGitignore(dir, log) {
|
|
775
|
-
const file =
|
|
804
|
+
const file = path4.join(dir, ".gitignore");
|
|
776
805
|
let content = "";
|
|
777
806
|
try {
|
|
778
807
|
content = await fs3.readFile(file, "utf8");
|
|
@@ -815,7 +844,7 @@ var LFS_PATTERNS = [
|
|
|
815
844
|
"*.webm"
|
|
816
845
|
];
|
|
817
846
|
async function writeGitattributes(dir, log) {
|
|
818
|
-
const file =
|
|
847
|
+
const file = path4.join(dir, ".gitattributes");
|
|
819
848
|
let content = "";
|
|
820
849
|
try {
|
|
821
850
|
content = await fs3.readFile(file, "utf8");
|
|
@@ -834,12 +863,12 @@ async function writeGitattributes(dir, log) {
|
|
|
834
863
|
|
|
835
864
|
// src/lib/remix-profile.ts
|
|
836
865
|
import fs8 from "fs/promises";
|
|
837
|
-
import
|
|
866
|
+
import path9 from "path";
|
|
838
867
|
|
|
839
868
|
// src/lib/agents-contract.ts
|
|
840
869
|
import fs4 from "fs/promises";
|
|
841
870
|
import os3 from "os";
|
|
842
|
-
import
|
|
871
|
+
import path5 from "path";
|
|
843
872
|
var CONTRACT_BEGIN = "<!-- genex:contract:begin (managed by genex \u2014 edits inside this block are overwritten on sync) -->";
|
|
844
873
|
var CONTRACT_END = "<!-- genex:contract:end -->";
|
|
845
874
|
var GENEX_CONTRACT_BLOCK = `${CONTRACT_BEGIN}
|
|
@@ -933,7 +962,7 @@ ${block}
|
|
|
933
962
|
async function writeAgentsContract(projectDir, contractBlock = GENEX_CONTRACT_BLOCK) {
|
|
934
963
|
let changed = false;
|
|
935
964
|
try {
|
|
936
|
-
const agentsPath =
|
|
965
|
+
const agentsPath = path5.join(projectDir, "AGENTS.md");
|
|
937
966
|
let existing = null;
|
|
938
967
|
try {
|
|
939
968
|
existing = await fs4.readFile(agentsPath, "utf8");
|
|
@@ -945,7 +974,7 @@ async function writeAgentsContract(projectDir, contractBlock = GENEX_CONTRACT_BL
|
|
|
945
974
|
await fs4.writeFile(agentsPath, next, "utf8");
|
|
946
975
|
changed = true;
|
|
947
976
|
}
|
|
948
|
-
const claudePath =
|
|
977
|
+
const claudePath = path5.join(projectDir, "CLAUDE.md");
|
|
949
978
|
let claude = null;
|
|
950
979
|
try {
|
|
951
980
|
claude = await fs4.readFile(claudePath, "utf8");
|
|
@@ -972,21 +1001,21 @@ async function writeToolsContract(projectDir, opts = {}) {
|
|
|
972
1001
|
}
|
|
973
1002
|
async function healAncestorContracts(projectDir, stopDir = os3.homedir()) {
|
|
974
1003
|
const findings = [];
|
|
975
|
-
const stop =
|
|
976
|
-
let dir =
|
|
1004
|
+
const stop = path5.resolve(stopDir);
|
|
1005
|
+
let dir = path5.dirname(path5.resolve(projectDir));
|
|
977
1006
|
for (let depth = 0; depth < 20; depth++) {
|
|
978
1007
|
try {
|
|
979
1008
|
let isWorkspace = false;
|
|
980
1009
|
for (const marker of ["project.json", "workspace.json"]) {
|
|
981
1010
|
try {
|
|
982
|
-
await fs4.access(
|
|
1011
|
+
await fs4.access(path5.join(dir, ".genex", marker));
|
|
983
1012
|
isWorkspace = true;
|
|
984
1013
|
break;
|
|
985
1014
|
} catch {
|
|
986
1015
|
}
|
|
987
1016
|
}
|
|
988
1017
|
if (!isWorkspace) {
|
|
989
|
-
const agentsPath =
|
|
1018
|
+
const agentsPath = path5.join(dir, "AGENTS.md");
|
|
990
1019
|
let content = null;
|
|
991
1020
|
try {
|
|
992
1021
|
content = await fs4.readFile(agentsPath, "utf8");
|
|
@@ -1000,7 +1029,7 @@ async function healAncestorContracts(projectDir, stopDir = os3.homedir()) {
|
|
|
1000
1029
|
if (remainder.trim() === "") {
|
|
1001
1030
|
await fs4.unlink(agentsPath);
|
|
1002
1031
|
try {
|
|
1003
|
-
const claudePath =
|
|
1032
|
+
const claudePath = path5.join(dir, "CLAUDE.md");
|
|
1004
1033
|
if ((await fs4.readFile(claudePath, "utf8")).trim() === CLAUDE_IMPORT_LINE) {
|
|
1005
1034
|
await fs4.unlink(claudePath);
|
|
1006
1035
|
}
|
|
@@ -1015,7 +1044,7 @@ async function healAncestorContracts(projectDir, stopDir = os3.homedir()) {
|
|
|
1015
1044
|
} catch {
|
|
1016
1045
|
}
|
|
1017
1046
|
if (dir === stop) break;
|
|
1018
|
-
const parent =
|
|
1047
|
+
const parent = path5.dirname(dir);
|
|
1019
1048
|
if (parent === dir) break;
|
|
1020
1049
|
dir = parent;
|
|
1021
1050
|
}
|
|
@@ -1024,7 +1053,7 @@ async function healAncestorContracts(projectDir, stopDir = os3.homedir()) {
|
|
|
1024
1053
|
async function healAncestorContractsAndReport(log, projectDir) {
|
|
1025
1054
|
try {
|
|
1026
1055
|
for (const f of await healAncestorContracts(projectDir)) {
|
|
1027
|
-
const file =
|
|
1056
|
+
const file = path5.join(f.dir, "AGENTS.md");
|
|
1028
1057
|
if (f.removed) {
|
|
1029
1058
|
log.plain(
|
|
1030
1059
|
`\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.`
|
|
@@ -1041,9 +1070,9 @@ async function healAncestorContractsAndReport(log, projectDir) {
|
|
|
1041
1070
|
|
|
1042
1071
|
// src/lib/copy-templates.ts
|
|
1043
1072
|
import fs5 from "fs/promises";
|
|
1044
|
-
import
|
|
1073
|
+
import path6 from "path";
|
|
1045
1074
|
function isGenexManaged(rel) {
|
|
1046
|
-
return rel.split(
|
|
1075
|
+
return rel.split(path6.sep).some((seg) => seg.startsWith("genex"));
|
|
1047
1076
|
}
|
|
1048
1077
|
async function copyTemplates(srcDir, destDir, opts = {}) {
|
|
1049
1078
|
const result = { copied: [], updated: [], skipped: [] };
|
|
@@ -1053,9 +1082,9 @@ async function copyTemplates(srcDir, destDir, opts = {}) {
|
|
|
1053
1082
|
async function walk(rootSrc, src, dest, opts, result) {
|
|
1054
1083
|
const entries = await fs5.readdir(src, { withFileTypes: true });
|
|
1055
1084
|
for (const entry of entries) {
|
|
1056
|
-
const srcPath =
|
|
1057
|
-
const destPath =
|
|
1058
|
-
const rel =
|
|
1085
|
+
const srcPath = path6.join(src, entry.name);
|
|
1086
|
+
const destPath = path6.join(dest, entry.name);
|
|
1087
|
+
const rel = path6.relative(rootSrc, srcPath);
|
|
1059
1088
|
if (opts.exclude?.includes(rel)) continue;
|
|
1060
1089
|
if (opts.filter && !opts.filter(rel)) continue;
|
|
1061
1090
|
if (entry.isDirectory()) {
|
|
@@ -1072,7 +1101,7 @@ async function walk(rootSrc, src, dest, opts, result) {
|
|
|
1072
1101
|
result.skipped.push(rel);
|
|
1073
1102
|
continue;
|
|
1074
1103
|
}
|
|
1075
|
-
await fs5.mkdir(
|
|
1104
|
+
await fs5.mkdir(path6.dirname(destPath), { recursive: true });
|
|
1076
1105
|
await fs5.copyFile(srcPath, destPath);
|
|
1077
1106
|
result.copied.push(rel);
|
|
1078
1107
|
if (present) result.updated.push(rel);
|
|
@@ -1090,11 +1119,11 @@ async function exists(p) {
|
|
|
1090
1119
|
// src/lib/updates.ts
|
|
1091
1120
|
import fs7 from "fs/promises";
|
|
1092
1121
|
import os4 from "os";
|
|
1093
|
-
import
|
|
1122
|
+
import path8 from "path";
|
|
1094
1123
|
|
|
1095
1124
|
// src/lib/workspace.ts
|
|
1096
1125
|
import fs6 from "fs/promises";
|
|
1097
|
-
import
|
|
1126
|
+
import path7 from "path";
|
|
1098
1127
|
async function resolveWorkspace(cwd = process.cwd()) {
|
|
1099
1128
|
const project = await readProject(cwd);
|
|
1100
1129
|
const slug = project && typeof project.slug === "string" && project.slug.trim() ? project.slug.trim() : null;
|
|
@@ -1148,7 +1177,7 @@ function reportPlatformRefused(log, command) {
|
|
|
1148
1177
|
}
|
|
1149
1178
|
async function hasRemixProfile(cwd = process.cwd()) {
|
|
1150
1179
|
try {
|
|
1151
|
-
const pkg = JSON.parse(await fs6.readFile(
|
|
1180
|
+
const pkg = JSON.parse(await fs6.readFile(path7.join(cwd, "package.json"), "utf8"));
|
|
1152
1181
|
return pkg.genex?.agentProfile === "remix";
|
|
1153
1182
|
} catch {
|
|
1154
1183
|
return false;
|
|
@@ -1173,7 +1202,7 @@ function isNewerVersion(a, b) {
|
|
|
1173
1202
|
var SKILLS_VERSION_MARKER = "genex-skills-version.json";
|
|
1174
1203
|
async function readSkillsMarker(skillsDir) {
|
|
1175
1204
|
try {
|
|
1176
|
-
const raw = await fs7.readFile(
|
|
1205
|
+
const raw = await fs7.readFile(path8.join(skillsDir, SKILLS_VERSION_MARKER), "utf8");
|
|
1177
1206
|
const parsed = JSON.parse(raw);
|
|
1178
1207
|
return typeof parsed.version === "string" ? parsed.version : null;
|
|
1179
1208
|
} catch {
|
|
@@ -1183,7 +1212,7 @@ async function readSkillsMarker(skillsDir) {
|
|
|
1183
1212
|
async function writeSkillsMarker(skillsDir, version = getCliVersion()) {
|
|
1184
1213
|
await fs7.mkdir(skillsDir, { recursive: true });
|
|
1185
1214
|
await fs7.writeFile(
|
|
1186
|
-
|
|
1215
|
+
path8.join(skillsDir, SKILLS_VERSION_MARKER),
|
|
1187
1216
|
JSON.stringify({ version, syncedAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2) + "\n"
|
|
1188
1217
|
);
|
|
1189
1218
|
}
|
|
@@ -1259,7 +1288,7 @@ var REMOVED_SKILLS = [
|
|
|
1259
1288
|
async function pruneRemovedSkills(skillsDir, log) {
|
|
1260
1289
|
const removed = [];
|
|
1261
1290
|
for (const name of REMOVED_SKILLS) {
|
|
1262
|
-
const target =
|
|
1291
|
+
const target = path8.join(skillsDir, name);
|
|
1263
1292
|
try {
|
|
1264
1293
|
await fs7.access(target);
|
|
1265
1294
|
} catch {
|
|
@@ -1279,11 +1308,11 @@ async function pruneRemovedSkills(skillsDir, log) {
|
|
|
1279
1308
|
return removed.length > 0;
|
|
1280
1309
|
}
|
|
1281
1310
|
async function syncSkillsForTarget(target, templatesDir = getTemplatesDir(), version = getCliVersion(), log, family = "game", hosted = false) {
|
|
1282
|
-
const skillsDir =
|
|
1311
|
+
const skillsDir = path8.join(target.baseDir, "skills");
|
|
1283
1312
|
if (!await hasGenexSkills(skillsDir)) return false;
|
|
1284
1313
|
if (family === "remix") await pruneRemixWorkflow(target.baseDir);
|
|
1285
1314
|
if (await readSkillsMarker(skillsDir) === version) return false;
|
|
1286
|
-
const src = target.full ? templatesDir :
|
|
1315
|
+
const src = target.full ? templatesDir : path8.join(templatesDir, "skills");
|
|
1287
1316
|
const dest = target.full ? target.baseDir : skillsDir;
|
|
1288
1317
|
const familyFilter = skillFamilyFilter(family, { hosted });
|
|
1289
1318
|
const filter = target.full ? familyFilter : (rel) => familyFilter(`skills/${rel}`);
|
|
@@ -1299,13 +1328,13 @@ async function cleanupLegacyGlobalSkills(log) {
|
|
|
1299
1328
|
try {
|
|
1300
1329
|
const home = os4.homedir();
|
|
1301
1330
|
const [realCwd, realHome] = await Promise.all([
|
|
1302
|
-
fs7.realpath(process.cwd()).catch(() =>
|
|
1303
|
-
fs7.realpath(home).catch(() =>
|
|
1331
|
+
fs7.realpath(process.cwd()).catch(() => path8.resolve(process.cwd())),
|
|
1332
|
+
fs7.realpath(home).catch(() => path8.resolve(home))
|
|
1304
1333
|
]);
|
|
1305
1334
|
if (realCwd === realHome) return false;
|
|
1306
1335
|
let removed = false;
|
|
1307
1336
|
for (const dirName of [".claude", ".codex", ".cursor"]) {
|
|
1308
|
-
const skillsDir =
|
|
1337
|
+
const skillsDir = path8.join(home, dirName, "skills");
|
|
1309
1338
|
let entries = [];
|
|
1310
1339
|
try {
|
|
1311
1340
|
entries = await fs7.readdir(skillsDir);
|
|
@@ -1315,18 +1344,18 @@ async function cleanupLegacyGlobalSkills(log) {
|
|
|
1315
1344
|
for (const name of entries) {
|
|
1316
1345
|
if (!name.startsWith("genex-")) continue;
|
|
1317
1346
|
try {
|
|
1318
|
-
await fs7.rm(
|
|
1347
|
+
await fs7.rm(path8.join(skillsDir, name), { recursive: true });
|
|
1319
1348
|
removed = true;
|
|
1320
1349
|
} catch {
|
|
1321
1350
|
}
|
|
1322
1351
|
}
|
|
1323
1352
|
}
|
|
1324
1353
|
for (const rel of [
|
|
1325
|
-
|
|
1326
|
-
|
|
1354
|
+
path8.join("agents", "genex-helper.md"),
|
|
1355
|
+
path8.join("commands", "genex-status.md")
|
|
1327
1356
|
]) {
|
|
1328
1357
|
try {
|
|
1329
|
-
await fs7.rm(
|
|
1358
|
+
await fs7.rm(path8.join(home, ".claude", rel));
|
|
1330
1359
|
removed = true;
|
|
1331
1360
|
} catch {
|
|
1332
1361
|
}
|
|
@@ -1387,7 +1416,7 @@ var PUBLISHED_PACKAGES = [
|
|
|
1387
1416
|
var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
1388
1417
|
var REGISTRY_TIMEOUT_MS = 1500;
|
|
1389
1418
|
function getUpdateCachePath() {
|
|
1390
|
-
return
|
|
1419
|
+
return path8.join(getGenexDir(), "update-check.json");
|
|
1391
1420
|
}
|
|
1392
1421
|
function isCacheFresh(cache, nowMs) {
|
|
1393
1422
|
const at = Date.parse(cache.checkedAt);
|
|
@@ -1449,7 +1478,7 @@ function startUpdateCheck() {
|
|
|
1449
1478
|
}
|
|
1450
1479
|
async function installedPackageVersion(cwd, name) {
|
|
1451
1480
|
try {
|
|
1452
|
-
const raw = await fs7.readFile(
|
|
1481
|
+
const raw = await fs7.readFile(path8.join(cwd, "node_modules", name, "package.json"), "utf8");
|
|
1453
1482
|
const pkg = JSON.parse(raw);
|
|
1454
1483
|
return typeof pkg.version === "string" ? pkg.version : null;
|
|
1455
1484
|
} catch {
|
|
@@ -1496,23 +1525,23 @@ async function recordNotified(cache, announced, cachePath) {
|
|
|
1496
1525
|
notified: { ...cache.notified ?? {}, ...announced },
|
|
1497
1526
|
notifiedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1498
1527
|
};
|
|
1499
|
-
await fs7.mkdir(
|
|
1528
|
+
await fs7.mkdir(path8.dirname(cachePath), { recursive: true });
|
|
1500
1529
|
await fs7.writeFile(cachePath, JSON.stringify(next, null, 2) + "\n");
|
|
1501
1530
|
} catch {
|
|
1502
1531
|
}
|
|
1503
1532
|
}
|
|
1504
1533
|
async function pruneRemixWorkflow(baseDir) {
|
|
1505
1534
|
for (const name of REMIX_EXCLUDED_SKILLS) {
|
|
1506
|
-
await fs7.rm(
|
|
1535
|
+
await fs7.rm(path8.join(baseDir, "skills", name), { recursive: true, force: true });
|
|
1507
1536
|
}
|
|
1508
1537
|
for (const rel of ["agents/genex-helper.md", "commands/genex-status.md"]) {
|
|
1509
|
-
await fs7.rm(
|
|
1538
|
+
await fs7.rm(path8.join(baseDir, rel), { force: true });
|
|
1510
1539
|
}
|
|
1511
1540
|
}
|
|
1512
1541
|
|
|
1513
1542
|
// src/lib/remix-profile.ts
|
|
1514
1543
|
async function persistRemixProfile(cwd, apiUrl) {
|
|
1515
|
-
const file =
|
|
1544
|
+
const file = path9.join(cwd, "package.json");
|
|
1516
1545
|
let pkg = {};
|
|
1517
1546
|
try {
|
|
1518
1547
|
pkg = JSON.parse(await fs8.readFile(file, "utf8"));
|
|
@@ -1526,25 +1555,25 @@ async function persistRemixProfile(cwd, apiUrl) {
|
|
|
1526
1555
|
}
|
|
1527
1556
|
async function installRemixProfile(cwd, apiUrl, log) {
|
|
1528
1557
|
await persistRemixProfile(cwd, apiUrl);
|
|
1529
|
-
if (
|
|
1558
|
+
if (path9.resolve(cwd) !== path9.resolve(process.cwd())) throw new Error("Remix profile must be installed from its workspace.");
|
|
1530
1559
|
const family = skillFamilyFilter("remix");
|
|
1531
1560
|
for (const target of resolveAgentTargets()) {
|
|
1532
|
-
const src = target.full ? getTemplatesDir() :
|
|
1533
|
-
const dest = target.full ? target.baseDir :
|
|
1561
|
+
const src = target.full ? getTemplatesDir() : path9.join(getTemplatesDir(), "skills");
|
|
1562
|
+
const dest = target.full ? target.baseDir : path9.join(target.baseDir, "skills");
|
|
1534
1563
|
await pruneRemixWorkflow(target.baseDir);
|
|
1535
1564
|
await copyTemplates(src, dest, {
|
|
1536
1565
|
exclude: ["controllers", "motion", "asset-viewer", "blender-service"],
|
|
1537
1566
|
filter: target.full ? family : (rel) => family(`skills/${rel}`)
|
|
1538
1567
|
});
|
|
1539
|
-
await pruneRemovedSkills(
|
|
1540
|
-
await writeSkillsMarker(
|
|
1568
|
+
await pruneRemovedSkills(path9.join(target.baseDir, "skills"), log);
|
|
1569
|
+
await writeSkillsMarker(path9.join(target.baseDir, "skills"));
|
|
1541
1570
|
}
|
|
1542
1571
|
await writeRemixContract(cwd);
|
|
1543
1572
|
}
|
|
1544
1573
|
|
|
1545
1574
|
// src/lib/local-install.ts
|
|
1546
1575
|
import fs9 from "fs/promises";
|
|
1547
|
-
import
|
|
1576
|
+
import path10 from "path";
|
|
1548
1577
|
import { spawn as spawn2 } from "child_process";
|
|
1549
1578
|
var CLI_PACKAGE = "@genex-ai/cli-demo";
|
|
1550
1579
|
var FULL_NAME_FALLBACK = `npx ${CLI_PACKAGE}@${CLI_CHANNEL}`;
|
|
@@ -1564,10 +1593,10 @@ async function exists2(p) {
|
|
|
1564
1593
|
}
|
|
1565
1594
|
}
|
|
1566
1595
|
async function detectPackageManager(cwd, opts = {}) {
|
|
1567
|
-
let dir =
|
|
1596
|
+
let dir = path10.resolve(cwd);
|
|
1568
1597
|
for (; ; ) {
|
|
1569
1598
|
try {
|
|
1570
|
-
const raw = await fs9.readFile(
|
|
1599
|
+
const raw = await fs9.readFile(path10.join(dir, "package.json"), "utf8");
|
|
1571
1600
|
const pm = JSON.parse(raw).packageManager;
|
|
1572
1601
|
if (typeof pm === "string") {
|
|
1573
1602
|
const name = pm.split("@")[0];
|
|
@@ -1576,18 +1605,18 @@ async function detectPackageManager(cwd, opts = {}) {
|
|
|
1576
1605
|
} catch {
|
|
1577
1606
|
}
|
|
1578
1607
|
for (const [file, pm] of LOCKFILES) {
|
|
1579
|
-
if (await exists2(
|
|
1608
|
+
if (await exists2(path10.join(dir, file))) return pm;
|
|
1580
1609
|
}
|
|
1581
|
-
const parent =
|
|
1610
|
+
const parent = path10.dirname(dir);
|
|
1582
1611
|
if (opts.walkParents === false || parent === dir) return "npm";
|
|
1583
1612
|
dir = parent;
|
|
1584
1613
|
}
|
|
1585
1614
|
}
|
|
1586
1615
|
async function findLocalCli(cwd) {
|
|
1587
|
-
let dir =
|
|
1616
|
+
let dir = path10.resolve(cwd);
|
|
1588
1617
|
for (; ; ) {
|
|
1589
|
-
if (await exists2(
|
|
1590
|
-
const parent =
|
|
1618
|
+
if (await exists2(path10.join(dir, "node_modules", CLI_PACKAGE, "package.json"))) return dir;
|
|
1619
|
+
const parent = path10.dirname(dir);
|
|
1591
1620
|
if (parent === dir) return null;
|
|
1592
1621
|
dir = parent;
|
|
1593
1622
|
}
|
|
@@ -1605,7 +1634,7 @@ function installArgs(pm, spec) {
|
|
|
1605
1634
|
}
|
|
1606
1635
|
}
|
|
1607
1636
|
function manifestName(cwd) {
|
|
1608
|
-
const slug =
|
|
1637
|
+
const slug = path10.basename(path10.resolve(cwd)).toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 100);
|
|
1609
1638
|
return slug || "genex-tools-workspace";
|
|
1610
1639
|
}
|
|
1611
1640
|
function isSourceRun(moduleUrl = import.meta.url) {
|
|
@@ -1663,10 +1692,10 @@ async function ensureLocalCli(log, cwd = process.cwd(), opts = {}) {
|
|
|
1663
1692
|
return;
|
|
1664
1693
|
}
|
|
1665
1694
|
const pm = await detectPackageManager(cwd);
|
|
1666
|
-
const hadManifest = await exists2(
|
|
1695
|
+
const hadManifest = await exists2(path10.join(cwd, "package.json"));
|
|
1667
1696
|
if (!hadManifest) {
|
|
1668
1697
|
await fs9.writeFile(
|
|
1669
|
-
|
|
1698
|
+
path10.join(cwd, "package.json"),
|
|
1670
1699
|
JSON.stringify({ name: manifestName(cwd), private: true }, null, 2) + "\n"
|
|
1671
1700
|
);
|
|
1672
1701
|
await ensureIgnored(cwd, "node_modules/");
|
|
@@ -1686,7 +1715,7 @@ async function ensureLocalCli(log, cwd = process.cwd(), opts = {}) {
|
|
|
1686
1715
|
}
|
|
1687
1716
|
}
|
|
1688
1717
|
async function ensureIgnored(dir, entry) {
|
|
1689
|
-
const file =
|
|
1718
|
+
const file = path10.join(dir, ".gitignore");
|
|
1690
1719
|
let content = "";
|
|
1691
1720
|
try {
|
|
1692
1721
|
content = await fs9.readFile(file, "utf8");
|
|
@@ -1700,7 +1729,7 @@ async function ensureIgnored(dir, entry) {
|
|
|
1700
1729
|
|
|
1701
1730
|
// src/lib/remix-archive.ts
|
|
1702
1731
|
import fs10 from "fs/promises";
|
|
1703
|
-
import
|
|
1732
|
+
import path11 from "path";
|
|
1704
1733
|
var MAX_SOURCE_ARCHIVE_BYTES = 2 * 1024 * 1024 * 1024;
|
|
1705
1734
|
var MAX_ENTRIES = 12e3;
|
|
1706
1735
|
var CHUNK_BYTES = 64 * 1024;
|
|
@@ -1850,8 +1879,8 @@ async function extractSourceArchive(file, dest, rootFolder) {
|
|
|
1850
1879
|
if (crc !== e.crc) badArchive("Source archive checksum mismatch. Retry the same remix command.");
|
|
1851
1880
|
if (e.symlink) {
|
|
1852
1881
|
const target = linkTargets.get(e.name) ?? "";
|
|
1853
|
-
if (!target || target.includes("\\") || target.includes("\0") ||
|
|
1854
|
-
const resolved =
|
|
1882
|
+
if (!target || target.includes("\\") || target.includes("\0") || path11.posix.isAbsolute(target) || target.includes(":")) badArchive("Source archive link points outside the source.");
|
|
1883
|
+
const resolved = path11.posix.normalize(path11.posix.join(path11.posix.dirname(e.name), target));
|
|
1855
1884
|
if (resolved === ".." || resolved.startsWith("../") || excludedSourcePath(resolved) || !entries.some((f) => f.name === resolved && !f.symlink || f.name.startsWith(resolved + "/"))) badArchive("Source archive link has an unsafe or missing target.");
|
|
1856
1885
|
}
|
|
1857
1886
|
}
|
|
@@ -1861,12 +1890,12 @@ async function extractSourceArchive(file, dest, rootFolder) {
|
|
|
1861
1890
|
excluded.push(e.name);
|
|
1862
1891
|
continue;
|
|
1863
1892
|
}
|
|
1864
|
-
const target =
|
|
1893
|
+
const target = path11.join(dest, e.name);
|
|
1865
1894
|
if (e.dir) {
|
|
1866
1895
|
await fs10.mkdir(target, { recursive: true });
|
|
1867
1896
|
continue;
|
|
1868
1897
|
}
|
|
1869
|
-
await fs10.mkdir(
|
|
1898
|
+
await fs10.mkdir(path11.dirname(target), { recursive: true });
|
|
1870
1899
|
const output = await fs10.open(target, "wx", e.mode & 73 ? 493 : 420);
|
|
1871
1900
|
try {
|
|
1872
1901
|
for (let offset = 0; offset < e.size; offset += CHUNK_BYTES) await output.writeFile(await readAt(handle, e.dataOffset + offset, Math.min(CHUNK_BYTES, e.size - offset)));
|
|
@@ -1880,8 +1909,8 @@ async function extractSourceArchive(file, dest, rootFolder) {
|
|
|
1880
1909
|
excluded.push(e.name);
|
|
1881
1910
|
continue;
|
|
1882
1911
|
}
|
|
1883
|
-
await fs10.mkdir(
|
|
1884
|
-
await fs10.symlink(linkTargets.get(e.name),
|
|
1912
|
+
await fs10.mkdir(path11.dirname(path11.join(dest, e.name)), { recursive: true });
|
|
1913
|
+
await fs10.symlink(linkTargets.get(e.name), path11.join(dest, e.name));
|
|
1885
1914
|
files.push(e.name);
|
|
1886
1915
|
}
|
|
1887
1916
|
return { files, excluded };
|
|
@@ -1895,8 +1924,8 @@ import "child_process";
|
|
|
1895
1924
|
|
|
1896
1925
|
// src/lib/generation-ledger.ts
|
|
1897
1926
|
import fs11 from "fs/promises";
|
|
1898
|
-
import
|
|
1899
|
-
var ledgerPath = (cwd) =>
|
|
1927
|
+
import path12 from "path";
|
|
1928
|
+
var ledgerPath = (cwd) => path12.join(cwd, ".genex", "generations.ndjson");
|
|
1900
1929
|
async function appendAdmissionRow(cwd, event) {
|
|
1901
1930
|
const file = await fs11.open(ledgerPath(cwd), "a");
|
|
1902
1931
|
try {
|
|
@@ -1923,7 +1952,7 @@ async function readAdmissionRows(cwd) {
|
|
|
1923
1952
|
}
|
|
1924
1953
|
async function append(cwd, event) {
|
|
1925
1954
|
try {
|
|
1926
|
-
await fs11.access(
|
|
1955
|
+
await fs11.access(path12.join(cwd, ".genex"));
|
|
1927
1956
|
await fs11.appendFile(ledgerPath(cwd), `${JSON.stringify(event)}
|
|
1928
1957
|
`, "utf8");
|
|
1929
1958
|
} catch {
|
|
@@ -2013,11 +2042,11 @@ async function countOutcomes(kind, cwd = process.cwd()) {
|
|
|
2013
2042
|
|
|
2014
2043
|
// src/lib/detect-features.ts
|
|
2015
2044
|
import fs13 from "fs/promises";
|
|
2016
|
-
import
|
|
2045
|
+
import path14 from "path";
|
|
2017
2046
|
|
|
2018
2047
|
// src/lib/download-assets.ts
|
|
2019
2048
|
import fs12 from "fs/promises";
|
|
2020
|
-
import
|
|
2049
|
+
import path13 from "path";
|
|
2021
2050
|
var RUNG_ROLE = /@\d+$/;
|
|
2022
2051
|
function isPrimaryRole(role) {
|
|
2023
2052
|
return !RUNG_ROLE.test(role);
|
|
@@ -2050,7 +2079,7 @@ async function downloadAssets(files, opts) {
|
|
|
2050
2079
|
}
|
|
2051
2080
|
const multi = wanted.length > 1;
|
|
2052
2081
|
for (const file of wanted) {
|
|
2053
|
-
const dest =
|
|
2082
|
+
const dest = path13.join(opts.outDir, localAssetName(opts.kind, opts.prompt, opts.id, file, multi));
|
|
2054
2083
|
try {
|
|
2055
2084
|
const res = await fetch(file.url);
|
|
2056
2085
|
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
@@ -2080,7 +2109,7 @@ async function undeliveredFiles(files, opts) {
|
|
|
2080
2109
|
const multi = wanted.length > 1;
|
|
2081
2110
|
const missing = [];
|
|
2082
2111
|
for (const file of wanted) {
|
|
2083
|
-
const dest =
|
|
2112
|
+
const dest = path13.join(opts.outDir, localAssetName(opts.kind, opts.prompt, opts.id, file, multi));
|
|
2084
2113
|
try {
|
|
2085
2114
|
await fs12.access(dest);
|
|
2086
2115
|
} catch {
|
|
@@ -2093,7 +2122,7 @@ async function undeliveredFiles(files, opts) {
|
|
|
2093
2122
|
// src/lib/detect-features.ts
|
|
2094
2123
|
async function detectEmbedSdkVersion(cwd = process.cwd()) {
|
|
2095
2124
|
try {
|
|
2096
|
-
const raw = await fs13.readFile(
|
|
2125
|
+
const raw = await fs13.readFile(path14.join(cwd, "package.json"), "utf8");
|
|
2097
2126
|
const pkg = JSON.parse(raw);
|
|
2098
2127
|
const version = pkg.dependencies?.["@genex-ai/embed-sdk"] ?? pkg.devDependencies?.["@genex-ai/embed-sdk"];
|
|
2099
2128
|
return typeof version === "string" && version ? version : null;
|
|
@@ -2103,7 +2132,7 @@ async function detectEmbedSdkVersion(cwd = process.cwd()) {
|
|
|
2103
2132
|
}
|
|
2104
2133
|
async function detectMultiplayer(cwd = process.cwd()) {
|
|
2105
2134
|
try {
|
|
2106
|
-
const raw = await fs13.readFile(
|
|
2135
|
+
const raw = await fs13.readFile(path14.join(cwd, "package.json"), "utf8");
|
|
2107
2136
|
const pkg = JSON.parse(raw);
|
|
2108
2137
|
return Boolean(
|
|
2109
2138
|
pkg.dependencies?.["@genex-ai/multiplayer"] ?? pkg.devDependencies?.["@genex-ai/multiplayer"]
|
|
@@ -2115,7 +2144,7 @@ async function detectMultiplayer(cwd = process.cwd()) {
|
|
|
2115
2144
|
async function detectMatchmaking(log, cwd = process.cwd()) {
|
|
2116
2145
|
let pkg;
|
|
2117
2146
|
try {
|
|
2118
|
-
pkg = JSON.parse(await fs13.readFile(
|
|
2147
|
+
pkg = JSON.parse(await fs13.readFile(path14.join(cwd, "package.json"), "utf8"));
|
|
2119
2148
|
} catch (err) {
|
|
2120
2149
|
log.dim(` (skipping matchmaking \u2014 couldn't read package.json: ${String(err)})`);
|
|
2121
2150
|
return null;
|
|
@@ -2133,12 +2162,12 @@ async function detectMatchmaking(log, cwd = process.cwd()) {
|
|
|
2133
2162
|
var TOUCH_KIT_MARKERS = /controllers\/touch\/|controllers\/character\/touch-joystick|TouchJoystick|VirtualButton|DragZone|RotateOverlay/;
|
|
2134
2163
|
async function detectMobileControls(cwd = process.cwd()) {
|
|
2135
2164
|
try {
|
|
2136
|
-
const raw = await fs13.readFile(
|
|
2165
|
+
const raw = await fs13.readFile(path14.join(cwd, "package.json"), "utf8");
|
|
2137
2166
|
const pkg = JSON.parse(raw);
|
|
2138
2167
|
if (pkg.genex?.mobileControls === true) return true;
|
|
2139
2168
|
} catch {
|
|
2140
2169
|
}
|
|
2141
|
-
const srcDir =
|
|
2170
|
+
const srcDir = path14.join(cwd, "src");
|
|
2142
2171
|
let entries;
|
|
2143
2172
|
try {
|
|
2144
2173
|
entries = await fs13.readdir(srcDir, { recursive: true });
|
|
@@ -2149,7 +2178,7 @@ async function detectMobileControls(cwd = process.cwd()) {
|
|
|
2149
2178
|
if (rel.includes("node_modules")) continue;
|
|
2150
2179
|
if (!/\.(ts|js|mts|mjs|tsx|jsx)$/.test(rel)) continue;
|
|
2151
2180
|
try {
|
|
2152
|
-
const content = await fs13.readFile(
|
|
2181
|
+
const content = await fs13.readFile(path14.join(srcDir, rel), "utf8");
|
|
2153
2182
|
if (TOUCH_KIT_MARKERS.test(content)) return true;
|
|
2154
2183
|
} catch {
|
|
2155
2184
|
}
|
|
@@ -2158,7 +2187,7 @@ async function detectMobileControls(cwd = process.cwd()) {
|
|
|
2158
2187
|
}
|
|
2159
2188
|
var GAME_STATE_CALLS = /savePlayerState|saveWorldState|submitScore/;
|
|
2160
2189
|
async function detectGameStateUsage(cwd = process.cwd()) {
|
|
2161
|
-
const srcDir =
|
|
2190
|
+
const srcDir = path14.join(cwd, "src");
|
|
2162
2191
|
let entries;
|
|
2163
2192
|
try {
|
|
2164
2193
|
entries = await fs13.readdir(srcDir, { recursive: true });
|
|
@@ -2169,7 +2198,7 @@ async function detectGameStateUsage(cwd = process.cwd()) {
|
|
|
2169
2198
|
if (rel.includes("node_modules")) continue;
|
|
2170
2199
|
if (!/\.(ts|js|mts|mjs|tsx|jsx)$/.test(rel)) continue;
|
|
2171
2200
|
try {
|
|
2172
|
-
const content = await fs13.readFile(
|
|
2201
|
+
const content = await fs13.readFile(path14.join(srcDir, rel), "utf8");
|
|
2173
2202
|
if (GAME_STATE_CALLS.test(content)) return true;
|
|
2174
2203
|
} catch {
|
|
2175
2204
|
}
|
|
@@ -2219,7 +2248,7 @@ var LOOP_START = /\b(?:requestAnimationFrame|setAnimationLoop)\s*\((?!\s*null\b)
|
|
|
2219
2248
|
var INIT_EMBED_CALL = /\binitEmbed\s*\(/;
|
|
2220
2249
|
async function detectEmbedBoot(cwd = process.cwd()) {
|
|
2221
2250
|
const found = { awaitsIdentity: [], callsInitEmbed: false, rendererAfterAwait: [], loopAfterAwait: [] };
|
|
2222
|
-
const srcDir =
|
|
2251
|
+
const srcDir = path14.join(cwd, "src");
|
|
2223
2252
|
let entries;
|
|
2224
2253
|
try {
|
|
2225
2254
|
entries = await fs13.readdir(srcDir, { recursive: true });
|
|
@@ -2228,11 +2257,11 @@ async function detectEmbedBoot(cwd = process.cwd()) {
|
|
|
2228
2257
|
}
|
|
2229
2258
|
for (const nativeRel of entries) {
|
|
2230
2259
|
if (nativeRel.includes("node_modules")) continue;
|
|
2231
|
-
if (nativeRel.split(
|
|
2260
|
+
if (nativeRel.split(path14.sep)[0] === "controllers") continue;
|
|
2232
2261
|
if (!/\.(ts|js|mts|mjs|tsx|jsx)$/.test(nativeRel)) continue;
|
|
2233
|
-
const raw = await fs13.readFile(
|
|
2262
|
+
const raw = await fs13.readFile(path14.join(srcDir, nativeRel), "utf8").catch(() => "");
|
|
2234
2263
|
if (!raw) continue;
|
|
2235
|
-
const rel = nativeRel.split(
|
|
2264
|
+
const rel = nativeRel.split(path14.sep).join("/");
|
|
2236
2265
|
const content = blankComments(raw);
|
|
2237
2266
|
if (INIT_EMBED_CALL.test(content)) found.callsInitEmbed = true;
|
|
2238
2267
|
const awaits = [];
|
|
@@ -2327,7 +2356,7 @@ async function detectSurfaceScan(cwd = process.cwd()) {
|
|
|
2327
2356
|
deferredAudioContext: [],
|
|
2328
2357
|
usesThree: false
|
|
2329
2358
|
};
|
|
2330
|
-
const srcDir =
|
|
2359
|
+
const srcDir = path14.join(cwd, "src");
|
|
2331
2360
|
let entries;
|
|
2332
2361
|
try {
|
|
2333
2362
|
entries = await fs13.readdir(srcDir, { recursive: true });
|
|
@@ -2337,9 +2366,9 @@ async function detectSurfaceScan(cwd = process.cwd()) {
|
|
|
2337
2366
|
for (const nativeRel of entries) {
|
|
2338
2367
|
if (nativeRel.includes("node_modules")) continue;
|
|
2339
2368
|
if (!/\.(ts|js|mts|mjs|tsx|jsx)$/.test(nativeRel)) continue;
|
|
2340
|
-
const raw = await fs13.readFile(
|
|
2369
|
+
const raw = await fs13.readFile(path14.join(srcDir, nativeRel), "utf8").catch(() => "");
|
|
2341
2370
|
if (!raw) continue;
|
|
2342
|
-
const rel = nativeRel.split(
|
|
2371
|
+
const rel = nativeRel.split(path14.sep).join("/");
|
|
2343
2372
|
const content = blankComments(raw);
|
|
2344
2373
|
const repeatRe = /\.repeat\.set\(\s*(-?\d+(?:\.\d+)?)\s*,\s*(-?\d+(?:\.\d+)?)\s*\)/g;
|
|
2345
2374
|
let m;
|
|
@@ -2432,7 +2461,7 @@ async function detectGenerationAudit(cwd = process.cwd()) {
|
|
|
2432
2461
|
const read = async (file) => {
|
|
2433
2462
|
try {
|
|
2434
2463
|
const raw = await fs13.readFile(file, "utf8");
|
|
2435
|
-
const ext =
|
|
2464
|
+
const ext = path14.extname(file).toLowerCase();
|
|
2436
2465
|
if (ext === ".txt") return;
|
|
2437
2466
|
haystack += ext === ".ts" || ext === ".tsx" || ext === ".js" || ext === ".jsx" || ext === ".css" ? blankComments(raw) : ext === ".html" ? raw.replace(/<!--[\s\S]*?-->/g, (m) => m.replace(/[^\n]/g, " ")) : raw;
|
|
2438
2467
|
} catch {
|
|
@@ -2441,18 +2470,18 @@ async function detectGenerationAudit(cwd = process.cwd()) {
|
|
|
2441
2470
|
try {
|
|
2442
2471
|
for (const entry of await fs13.readdir(cwd, { withFileTypes: true })) {
|
|
2443
2472
|
if (entry.isFile() && /\.(ts|tsx|js|jsx|css|html|json)$/.test(entry.name)) {
|
|
2444
|
-
await read(
|
|
2473
|
+
await read(path14.join(cwd, entry.name));
|
|
2445
2474
|
}
|
|
2446
2475
|
}
|
|
2447
2476
|
} catch {
|
|
2448
2477
|
}
|
|
2449
2478
|
for (const sub of ["src", "public"]) {
|
|
2450
2479
|
try {
|
|
2451
|
-
const entries = await fs13.readdir(
|
|
2480
|
+
const entries = await fs13.readdir(path14.join(cwd, sub), { recursive: true });
|
|
2452
2481
|
for (const rel of entries) {
|
|
2453
2482
|
if (rel.includes("node_modules")) continue;
|
|
2454
2483
|
if (!/\.(ts|tsx|js|jsx|css|html|json|txt)$/.test(rel)) continue;
|
|
2455
|
-
await read(
|
|
2484
|
+
await read(path14.join(cwd, sub, rel));
|
|
2456
2485
|
}
|
|
2457
2486
|
} catch {
|
|
2458
2487
|
}
|
|
@@ -2623,7 +2652,7 @@ async function borrowEvidence(meta, cwd) {
|
|
|
2623
2652
|
} catch {
|
|
2624
2653
|
return true;
|
|
2625
2654
|
}
|
|
2626
|
-
const gitConfig = await fs13.readFile(
|
|
2655
|
+
const gitConfig = await fs13.readFile(path14.join(cwd, ".git", "config"), "utf8").catch(() => "");
|
|
2627
2656
|
for (const m of gitConfig.matchAll(/url\s*=\s*(\S+)/g)) {
|
|
2628
2657
|
try {
|
|
2629
2658
|
const u = new URL(m[1]);
|
|
@@ -2631,7 +2660,7 @@ async function borrowEvidence(meta, cwd) {
|
|
|
2631
2660
|
} catch {
|
|
2632
2661
|
}
|
|
2633
2662
|
}
|
|
2634
|
-
const readme = await fs13.readFile(
|
|
2663
|
+
const readme = await fs13.readFile(path14.join(cwd, "README.md"), "utf8").catch(() => "");
|
|
2635
2664
|
return readme.includes(host) || /\b(upstream|originally by|ported from|borrowed from)\b/i.test(readme);
|
|
2636
2665
|
}
|
|
2637
2666
|
|
|
@@ -2816,11 +2845,11 @@ function auditGenerationPlan(input) {
|
|
|
2816
2845
|
import crypto2 from "crypto";
|
|
2817
2846
|
import fs17 from "fs/promises";
|
|
2818
2847
|
import os5 from "os";
|
|
2819
|
-
import
|
|
2848
|
+
import path17 from "path";
|
|
2820
2849
|
|
|
2821
2850
|
// src/lib/source-size.ts
|
|
2822
2851
|
import fs14 from "fs/promises";
|
|
2823
|
-
import
|
|
2852
|
+
import path15 from "path";
|
|
2824
2853
|
var FALLBACK_SOURCE_LIMITS = {
|
|
2825
2854
|
maxTotalBytes: 2 * 1024 * 1024 * 1024,
|
|
2826
2855
|
maxFiles: 5e3
|
|
@@ -2862,7 +2891,7 @@ async function measurePayload(cwd, staged) {
|
|
|
2862
2891
|
for (const file of staged) {
|
|
2863
2892
|
let size = 0;
|
|
2864
2893
|
try {
|
|
2865
|
-
size = (await fs14.stat(
|
|
2894
|
+
size = (await fs14.stat(path15.join(cwd, file))).size;
|
|
2866
2895
|
} catch {
|
|
2867
2896
|
size = 0;
|
|
2868
2897
|
}
|
|
@@ -3135,7 +3164,7 @@ function tierFor(estVramMb) {
|
|
|
3135
3164
|
|
|
3136
3165
|
// src/commands/ui.ts
|
|
3137
3166
|
import fs16 from "fs/promises";
|
|
3138
|
-
import
|
|
3167
|
+
import path16 from "path";
|
|
3139
3168
|
import { PNG as PNG2 } from "pngjs";
|
|
3140
3169
|
|
|
3141
3170
|
// src/lib/png-tools.ts
|
|
@@ -3684,7 +3713,7 @@ async function uiExtract(opts, log) {
|
|
|
3684
3713
|
rimPixels: speckle.sampled
|
|
3685
3714
|
});
|
|
3686
3715
|
}
|
|
3687
|
-
const outPath =
|
|
3716
|
+
const outPath = path16.join(outDir, `${name}.png`);
|
|
3688
3717
|
await writePng(outPath, out);
|
|
3689
3718
|
const sidecar = {
|
|
3690
3719
|
name,
|
|
@@ -3712,7 +3741,7 @@ async function uiExtract(opts, log) {
|
|
|
3712
3741
|
`${name}.png ${cropW}x${cropH} (ar ${sidecar.aspectRatio}) at sheet ${padX0},${padY0}`
|
|
3713
3742
|
);
|
|
3714
3743
|
}
|
|
3715
|
-
const debugPath =
|
|
3744
|
+
const debugPath = path16.join(outDir, "extract-debug.json");
|
|
3716
3745
|
await fs16.writeFile(
|
|
3717
3746
|
debugPath,
|
|
3718
3747
|
JSON.stringify(
|
|
@@ -4228,11 +4257,11 @@ async function uiMasks(opts, log) {
|
|
|
4228
4257
|
});
|
|
4229
4258
|
}
|
|
4230
4259
|
const overlay = makeOverlay(clean2, converted.png);
|
|
4231
|
-
const framePath =
|
|
4232
|
-
const maskPath =
|
|
4233
|
-
const annotatedPath =
|
|
4234
|
-
const overlayPath =
|
|
4235
|
-
const metaPath =
|
|
4260
|
+
const framePath = path16.join(outDir, `${pair.name}-frame.png`);
|
|
4261
|
+
const maskPath = path16.join(outDir, `${pair.name}-mask.png`);
|
|
4262
|
+
const annotatedPath = path16.join(outDir, `${pair.name}-annotated-source.png`);
|
|
4263
|
+
const overlayPath = path16.join(outDir, `${pair.name}-overlay.png`);
|
|
4264
|
+
const metaPath = path16.join(outDir, `${pair.name}.annotated-progress.json`);
|
|
4236
4265
|
await writePng(framePath, clean2);
|
|
4237
4266
|
await writePng(maskPath, converted.png);
|
|
4238
4267
|
await writePng(annotatedPath, annotated);
|
|
@@ -4295,7 +4324,7 @@ async function uiMasks(opts, log) {
|
|
|
4295
4324
|
);
|
|
4296
4325
|
}
|
|
4297
4326
|
}
|
|
4298
|
-
const indexPath =
|
|
4327
|
+
const indexPath = path16.join(outDir, "annotated-progress.json");
|
|
4299
4328
|
await fs16.writeFile(indexPath, `${JSON.stringify({ input, loosened, pairs: results }, null, 2)}
|
|
4300
4329
|
`);
|
|
4301
4330
|
log.plain("");
|
|
@@ -4561,7 +4590,7 @@ async function uiPlate(opts, log) {
|
|
|
4561
4590
|
fail("No interior found \u2014 the image is fully transparent (or erode ate everything). Check --in / lower --erode.");
|
|
4562
4591
|
}
|
|
4563
4592
|
await writePng(outPath, out);
|
|
4564
|
-
const name =
|
|
4593
|
+
const name = path16.basename(outPath);
|
|
4565
4594
|
log.plain(c.bold("genex ui plate"));
|
|
4566
4595
|
log.success(`${outPath} ${W}x${H}, interior ${(count / (W * H) * 100).toFixed(1)}% (erode ${erode}px)`);
|
|
4567
4596
|
log.dim(" Wire it as the plate's silhouette (same box as the frame <img>, plate UNDER the art):");
|
|
@@ -4732,7 +4761,7 @@ async function walkFiles(dir) {
|
|
|
4732
4761
|
}
|
|
4733
4762
|
for (const entry of entries) {
|
|
4734
4763
|
if (entry.name === "node_modules" || entry.name.startsWith(".")) continue;
|
|
4735
|
-
const p =
|
|
4764
|
+
const p = path16.join(dir, entry.name);
|
|
4736
4765
|
if (entry.isDirectory()) out.push(...await walkFiles(p));
|
|
4737
4766
|
else out.push(p);
|
|
4738
4767
|
}
|
|
@@ -4741,7 +4770,7 @@ async function walkFiles(dir) {
|
|
|
4741
4770
|
async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
|
|
4742
4771
|
const viewportFindings = [];
|
|
4743
4772
|
try {
|
|
4744
|
-
const indexHtml = await fs16.readFile(
|
|
4773
|
+
const indexHtml = await fs16.readFile(path16.join(cwd, "index.html"), "utf8");
|
|
4745
4774
|
if (!/<meta[^>]+name=["']viewport["']/i.test(indexHtml)) {
|
|
4746
4775
|
viewportFindings.push({
|
|
4747
4776
|
kind: "viewport-meta",
|
|
@@ -4755,7 +4784,7 @@ async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
|
|
|
4755
4784
|
}
|
|
4756
4785
|
} catch {
|
|
4757
4786
|
}
|
|
4758
|
-
const absAssets =
|
|
4787
|
+
const absAssets = path16.resolve(cwd, assetDir);
|
|
4759
4788
|
try {
|
|
4760
4789
|
if (!(await fs16.stat(absAssets)).isDirectory()) {
|
|
4761
4790
|
return viewportFindings.length > 0 ? viewportFindings : null;
|
|
@@ -4763,20 +4792,20 @@ async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
|
|
|
4763
4792
|
} catch {
|
|
4764
4793
|
return viewportFindings.length > 0 ? viewportFindings : null;
|
|
4765
4794
|
}
|
|
4766
|
-
const srcFiles = (await walkFiles(
|
|
4767
|
-
(p) => AUDIT_SRC_EXTS.has(
|
|
4795
|
+
const srcFiles = (await walkFiles(path16.resolve(cwd, srcDir))).filter(
|
|
4796
|
+
(p) => AUDIT_SRC_EXTS.has(path16.extname(p).toLowerCase())
|
|
4768
4797
|
);
|
|
4769
4798
|
try {
|
|
4770
4799
|
for (const name of await fs16.readdir(cwd)) {
|
|
4771
|
-
const ext =
|
|
4772
|
-
if (ext === ".html" || ext === ".css") srcFiles.push(
|
|
4800
|
+
const ext = path16.extname(name).toLowerCase();
|
|
4801
|
+
if (ext === ".html" || ext === ".css") srcFiles.push(path16.join(cwd, name));
|
|
4773
4802
|
}
|
|
4774
4803
|
} catch {
|
|
4775
4804
|
}
|
|
4776
4805
|
const sources = [];
|
|
4777
4806
|
for (const p of srcFiles) {
|
|
4778
4807
|
try {
|
|
4779
|
-
sources.push({ rel:
|
|
4808
|
+
sources.push({ rel: path16.relative(cwd, p), text: await fs16.readFile(p, "utf8") });
|
|
4780
4809
|
} catch {
|
|
4781
4810
|
}
|
|
4782
4811
|
}
|
|
@@ -4786,7 +4815,7 @@ async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
|
|
|
4786
4815
|
const metaByName = /* @__PURE__ */ new Map();
|
|
4787
4816
|
const bboxByPng = /* @__PURE__ */ new Map();
|
|
4788
4817
|
for (const p of assetFiles) {
|
|
4789
|
-
const base =
|
|
4818
|
+
const base = path16.basename(p);
|
|
4790
4819
|
const metaMatch = /^(.+)\.annotated-progress\.json$/.exec(base);
|
|
4791
4820
|
if (metaMatch) {
|
|
4792
4821
|
try {
|
|
@@ -4810,7 +4839,7 @@ async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
|
|
|
4810
4839
|
for (const [name, meta] of metaByName) {
|
|
4811
4840
|
if (!meta.cleanCrop || !referenced(`${name}-mask.png`)) continue;
|
|
4812
4841
|
for (const p of assetFiles) {
|
|
4813
|
-
const base =
|
|
4842
|
+
const base = path16.basename(p);
|
|
4814
4843
|
if (!base.toLowerCase().endsWith(".png")) continue;
|
|
4815
4844
|
if (base !== `${name}.png` && !base.startsWith(`${name}-`)) continue;
|
|
4816
4845
|
if (/-mask\.png$|-frame\.png$|-overlay\.png$|-annotated-source\.png$/.test(base)) continue;
|
|
@@ -4834,7 +4863,7 @@ async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
|
|
|
4834
4863
|
}
|
|
4835
4864
|
const pngByBase = /* @__PURE__ */ new Map();
|
|
4836
4865
|
for (const p of assetFiles) {
|
|
4837
|
-
const base =
|
|
4866
|
+
const base = path16.basename(p);
|
|
4838
4867
|
if (base.toLowerCase().endsWith(".png")) pngByBase.set(base, p);
|
|
4839
4868
|
}
|
|
4840
4869
|
for (const [maskBase, maskPath] of pngByBase) {
|
|
@@ -4887,7 +4916,7 @@ async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
|
|
|
4887
4916
|
}
|
|
4888
4917
|
const maskReported = /* @__PURE__ */ new Set();
|
|
4889
4918
|
for (const p of assetFiles) {
|
|
4890
|
-
const m = /^(.+)\.annotated-progress\.json$/.exec(
|
|
4919
|
+
const m = /^(.+)\.annotated-progress\.json$/.exec(path16.basename(p));
|
|
4891
4920
|
if (!m) continue;
|
|
4892
4921
|
const maskBase = `${m[1]}-mask.png`;
|
|
4893
4922
|
if (!referenced(maskBase)) {
|
|
@@ -4899,14 +4928,14 @@ async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
|
|
|
4899
4928
|
}
|
|
4900
4929
|
}
|
|
4901
4930
|
for (const p of assetFiles) {
|
|
4902
|
-
const base =
|
|
4931
|
+
const base = path16.basename(p);
|
|
4903
4932
|
if (!base.toLowerCase().endsWith(".png")) continue;
|
|
4904
4933
|
if (/-annotated-source\.png$|-overlay\.png$/.test(base)) continue;
|
|
4905
4934
|
if (maskReported.has(base)) continue;
|
|
4906
4935
|
if (!referenced(base)) {
|
|
4907
4936
|
findings.push({
|
|
4908
4937
|
kind: "unwired-sprite",
|
|
4909
|
-
message: `${
|
|
4938
|
+
message: `${path16.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.)`
|
|
4910
4939
|
});
|
|
4911
4940
|
}
|
|
4912
4941
|
}
|
|
@@ -5021,7 +5050,7 @@ async function printUiAuditPreflight(log) {
|
|
|
5021
5050
|
}
|
|
5022
5051
|
async function printPipelineStatePreflight(log, cwd = process.cwd()) {
|
|
5023
5052
|
try {
|
|
5024
|
-
const design = await fs17.readFile(
|
|
5053
|
+
const design = await fs17.readFile(path17.join(cwd, "DESIGN.md"), "utf8").catch(() => "");
|
|
5025
5054
|
const warnings = [];
|
|
5026
5055
|
if (design.length > 0 && !/##\s*build plan & status/i.test(design)) {
|
|
5027
5056
|
warnings.push(
|
|
@@ -5029,7 +5058,7 @@ async function printPipelineStatePreflight(log, cwd = process.cwd()) {
|
|
|
5029
5058
|
);
|
|
5030
5059
|
}
|
|
5031
5060
|
if (!/player character:/i.test(design)) {
|
|
5032
|
-
const hasCharacter = await fs17.access(
|
|
5061
|
+
const hasCharacter = await fs17.access(path17.join(cwd, "public", "assets", "meshy-character.json")).then(() => true, () => false);
|
|
5033
5062
|
if (!hasCharacter && await loadsPlayerBody(cwd)) {
|
|
5034
5063
|
warnings.push(
|
|
5035
5064
|
`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).`
|
|
@@ -5046,12 +5075,12 @@ async function printPipelineStatePreflight(log, cwd = process.cwd()) {
|
|
|
5046
5075
|
async function loadsPlayerBody(cwd) {
|
|
5047
5076
|
const BODY_LOADERS = /\bloadPlayerCharacter\s*\(|\bloadVrm(?:Clone)?\s*\(/;
|
|
5048
5077
|
try {
|
|
5049
|
-
const entries = await fs17.readdir(
|
|
5078
|
+
const entries = await fs17.readdir(path17.join(cwd, "src"), { recursive: true });
|
|
5050
5079
|
for (const rel of entries) {
|
|
5051
5080
|
if (rel.includes("node_modules")) continue;
|
|
5052
|
-
if (rel.split(
|
|
5081
|
+
if (rel.split(path17.sep)[0] === "controllers" || rel.startsWith("controllers/")) continue;
|
|
5053
5082
|
if (!/\.(ts|tsx|js|jsx)$/.test(rel)) continue;
|
|
5054
|
-
const text = await fs17.readFile(
|
|
5083
|
+
const text = await fs17.readFile(path17.join(cwd, "src", rel), "utf8").catch(() => "");
|
|
5055
5084
|
if (BODY_LOADERS.test(text)) return true;
|
|
5056
5085
|
}
|
|
5057
5086
|
} catch {
|
|
@@ -5088,9 +5117,9 @@ async function deployGame(ctx, opts, log) {
|
|
|
5088
5117
|
}
|
|
5089
5118
|
log.success("Built.");
|
|
5090
5119
|
}
|
|
5091
|
-
const distDir =
|
|
5120
|
+
const distDir = path17.join(cwd, "dist");
|
|
5092
5121
|
const siteDir = await isDir(distDir) ? distDir : cwd;
|
|
5093
|
-
const rel =
|
|
5122
|
+
const rel = path17.relative(cwd, siteDir) || ".";
|
|
5094
5123
|
if (siteDir === cwd) await writeGitignore(cwd, log);
|
|
5095
5124
|
const files = await collectFiles(siteDir);
|
|
5096
5125
|
if (files.length === 0) {
|
|
@@ -5181,7 +5210,7 @@ async function deployGame(ctx, opts, log) {
|
|
|
5181
5210
|
}
|
|
5182
5211
|
async function hasBuildScript(cwd) {
|
|
5183
5212
|
try {
|
|
5184
|
-
const pkg = JSON.parse(await fs17.readFile(
|
|
5213
|
+
const pkg = JSON.parse(await fs17.readFile(path17.join(cwd, "package.json"), "utf8"));
|
|
5185
5214
|
return Boolean(pkg.scripts?.build);
|
|
5186
5215
|
} catch {
|
|
5187
5216
|
return false;
|
|
@@ -5193,9 +5222,9 @@ async function collectFiles(root) {
|
|
|
5193
5222
|
for (const e of await fs17.readdir(dir, { withFileTypes: true })) {
|
|
5194
5223
|
const relPath = prefix ? `${prefix}/${e.name}` : e.name;
|
|
5195
5224
|
if (e.isDirectory()) {
|
|
5196
|
-
if (!EXCLUDE_DIRS.has(e.name)) await walk2(
|
|
5225
|
+
if (!EXCLUDE_DIRS.has(e.name)) await walk2(path17.join(dir, e.name), relPath);
|
|
5197
5226
|
} else if (e.isFile() && !isSecretEnvFile(e.name)) {
|
|
5198
|
-
out.push({ relPath, bytes: await fs17.readFile(
|
|
5227
|
+
out.push({ relPath, bytes: await fs17.readFile(path17.join(dir, e.name)) });
|
|
5199
5228
|
}
|
|
5200
5229
|
}
|
|
5201
5230
|
};
|
|
@@ -5468,7 +5497,7 @@ async function pushWorktree(cwd, pushUrl, managed, log, branch = "main", onCommi
|
|
|
5468
5497
|
log.error("Couldn't save your game's source \u2014 please try again.");
|
|
5469
5498
|
return "failed";
|
|
5470
5499
|
};
|
|
5471
|
-
const gitDir = await fs17.mkdtemp(
|
|
5500
|
+
const gitDir = await fs17.mkdtemp(path17.join(os5.tmpdir(), "genex-source-"));
|
|
5472
5501
|
const base = { GIT_DIR: gitDir };
|
|
5473
5502
|
if (urlHasEmbeddedCredentials(pushUrl)) {
|
|
5474
5503
|
base.GIT_CONFIG_COUNT = "1";
|
|
@@ -5483,8 +5512,8 @@ async function pushWorktree(cwd, pushUrl, managed, log, branch = "main", onCommi
|
|
|
5483
5512
|
};
|
|
5484
5513
|
try {
|
|
5485
5514
|
if ((await run("git", ["init", "-q"], base)).code !== 0) return failed();
|
|
5486
|
-
await fs17.writeFile(
|
|
5487
|
-
const env = { ...base, GIT_WORK_TREE: cwd, GIT_INDEX_FILE:
|
|
5515
|
+
await fs17.writeFile(path17.join(gitDir, "info", "exclude"), excludeFile());
|
|
5516
|
+
const env = { ...base, GIT_WORK_TREE: cwd, GIT_INDEX_FILE: path17.join(gitDir, "index-source") };
|
|
5488
5517
|
let lfs = (await run("git", ["lfs", "version"], base)).code !== 0 ? false : true;
|
|
5489
5518
|
if (!lfs) {
|
|
5490
5519
|
log.step("Installing git-lfs (keeps large binary assets out of the source push)\u2026");
|
|
@@ -5737,7 +5766,8 @@ async function runPreview(opts) {
|
|
|
5737
5766
|
process.exitCode = 1;
|
|
5738
5767
|
return;
|
|
5739
5768
|
}
|
|
5740
|
-
const
|
|
5769
|
+
const apiUrl = getApiUrl(meta.apiUrl);
|
|
5770
|
+
const token = await readUserToken(opts.envPath, apiUrl);
|
|
5741
5771
|
if (!token) {
|
|
5742
5772
|
log.error("Not authorized. Run `genex init` first to sign in.");
|
|
5743
5773
|
process.exitCode = 1;
|
|
@@ -5746,7 +5776,6 @@ async function runPreview(opts) {
|
|
|
5746
5776
|
const detections = await detectFeatures(log);
|
|
5747
5777
|
advisoryNudges(log, detections);
|
|
5748
5778
|
await exploreReuseNudge(log, meta);
|
|
5749
|
-
const apiUrl = getApiUrl(meta.apiUrl);
|
|
5750
5779
|
const ok = await deployGame(
|
|
5751
5780
|
{ projectId: meta.id, apiUrl, token, slug: meta.slug },
|
|
5752
5781
|
// Detections reach the server on every preview too: matchmaking so a draft's
|
|
@@ -5823,7 +5852,7 @@ function loggerFor(opts) {
|
|
|
5823
5852
|
}
|
|
5824
5853
|
async function readState(directory) {
|
|
5825
5854
|
try {
|
|
5826
|
-
const marker =
|
|
5855
|
+
const marker = path18.join(directory, ".genex", "remix-operation.json");
|
|
5827
5856
|
if ((await fs18.lstat(marker)).isSymbolicLink()) return null;
|
|
5828
5857
|
const value = JSON.parse(await fs18.readFile(marker, "utf8"));
|
|
5829
5858
|
return value.schema === 1 && value.directory === directory && typeof value.operationId === "string" && /^[a-zA-Z0-9_-]{8,128}$/.test(value.operationId) && value.source && /^[a-f0-9]{40}$/.test(value.source.sourceCommitSha) ? value : null;
|
|
@@ -5832,11 +5861,11 @@ async function readState(directory) {
|
|
|
5832
5861
|
}
|
|
5833
5862
|
}
|
|
5834
5863
|
async function saveState(state) {
|
|
5835
|
-
const dir =
|
|
5864
|
+
const dir = path18.join(state.directory, ".genex");
|
|
5836
5865
|
await fs18.mkdir(dir, { recursive: true, mode: 448 });
|
|
5837
|
-
const temp =
|
|
5866
|
+
const temp = path18.join(dir, "remix-operation.next.json");
|
|
5838
5867
|
await fs18.writeFile(temp, JSON.stringify(state, null, 2) + "\n", { mode: 384 });
|
|
5839
|
-
await fs18.rename(temp,
|
|
5868
|
+
await fs18.rename(temp, path18.join(dir, "remix-operation.json"));
|
|
5840
5869
|
}
|
|
5841
5870
|
async function checkDestination(directory) {
|
|
5842
5871
|
try {
|
|
@@ -5844,7 +5873,7 @@ async function checkDestination(directory) {
|
|
|
5844
5873
|
if (!stat.isDirectory() || stat.isSymbolicLink()) throw new RemixError("destination_occupied", "The destination is not a fresh directory. Choose a new folder.");
|
|
5845
5874
|
const entries = await fs18.readdir(directory);
|
|
5846
5875
|
if (!entries.length) return null;
|
|
5847
|
-
const genex = await fs18.lstat(
|
|
5876
|
+
const genex = await fs18.lstat(path18.join(directory, ".genex")).catch(() => null);
|
|
5848
5877
|
const state = genex?.isDirectory() && !genex.isSymbolicLink() ? await readState(directory) : null;
|
|
5849
5878
|
if (state) return state;
|
|
5850
5879
|
throw new RemixError("destination_occupied", "The destination already contains files. Choose a fresh folder; remix never clears an existing project.");
|
|
@@ -5855,7 +5884,7 @@ async function checkDestination(directory) {
|
|
|
5855
5884
|
}
|
|
5856
5885
|
async function assertReferenceParents(cwd) {
|
|
5857
5886
|
for (const rel of [".genex", ".genex/refs"]) {
|
|
5858
|
-
const stat = await fs18.lstat(
|
|
5887
|
+
const stat = await fs18.lstat(path18.join(cwd, rel)).catch((err) => {
|
|
5859
5888
|
if (err.code === "ENOENT") return null;
|
|
5860
5889
|
throw err;
|
|
5861
5890
|
});
|
|
@@ -5863,16 +5892,16 @@ async function assertReferenceParents(cwd) {
|
|
|
5863
5892
|
}
|
|
5864
5893
|
}
|
|
5865
5894
|
async function archiveSourceInstructions(directory) {
|
|
5866
|
-
const archive =
|
|
5895
|
+
const archive = path18.join(directory, ".genex", "source-instructions");
|
|
5867
5896
|
const rootArtifacts = /* @__PURE__ */ new Set([".claude", ".codex", ".cursor", ".agents", ".mcp.json", ".cursorrules", "DESIGN.md"]);
|
|
5868
5897
|
const instructionNames = /* @__PURE__ */ new Set(["AGENTS.md", "CLAUDE.md", "GEMINI.md"]);
|
|
5869
5898
|
const visit = async (dir) => {
|
|
5870
5899
|
for (const entry of await fs18.readdir(dir, { withFileTypes: true })) {
|
|
5871
5900
|
if (entry.name === ".genex" || entry.name === ".git" || entry.name === "node_modules") continue;
|
|
5872
|
-
const from =
|
|
5901
|
+
const from = path18.join(dir, entry.name), rel = path18.relative(directory, from);
|
|
5873
5902
|
if (instructionNames.has(entry.name) || dir === directory && rootArtifacts.has(entry.name)) {
|
|
5874
|
-
const to =
|
|
5875
|
-
await fs18.mkdir(
|
|
5903
|
+
const to = path18.join(archive, rel);
|
|
5904
|
+
await fs18.mkdir(path18.dirname(to), { recursive: true });
|
|
5876
5905
|
if (await fs18.lstat(to).catch(() => null)) throw new RemixError("destination_changed", `Source instructions at ${rel} changed during preparation; both copies were retained.`);
|
|
5877
5906
|
await fs18.rename(from, to);
|
|
5878
5907
|
} else if (entry.isDirectory()) await visit(from);
|
|
@@ -5881,7 +5910,7 @@ async function archiveSourceInstructions(directory) {
|
|
|
5881
5910
|
await visit(directory);
|
|
5882
5911
|
}
|
|
5883
5912
|
async function acquireLock(directory) {
|
|
5884
|
-
const lock =
|
|
5913
|
+
const lock = path18.join(directory, ".genex", "remix.lock");
|
|
5885
5914
|
for (let attempt = 0; attempt < 2; attempt++) {
|
|
5886
5915
|
try {
|
|
5887
5916
|
const handle = await fs18.open(lock, "wx", 384);
|
|
@@ -5983,13 +6012,13 @@ function pinRemixDependencies(pkg, version) {
|
|
|
5983
6012
|
return warnings;
|
|
5984
6013
|
}
|
|
5985
6014
|
async function installRemixDependencies(cwd, pm, version, log, runner = childRun) {
|
|
5986
|
-
const pkgPath =
|
|
6015
|
+
const pkgPath = path18.join(cwd, "package.json");
|
|
5987
6016
|
const pkg = JSON.parse(await fs18.readFile(pkgPath, "utf8"));
|
|
5988
6017
|
const warnings = pinRemixDependencies(pkg, version);
|
|
5989
6018
|
await fs18.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
|
|
5990
6019
|
log.step(`Restoring dependencies with ${pm}; pinning ${CLI_PACKAGE}@${version} (${CLI_CHANNEL}).`);
|
|
5991
6020
|
const args = pm === "npm" ? ["install", "--workspaces=false", "--no-audit", "--no-fund"] : pm === "pnpm" ? ["install", "--ignore-workspace", "--no-frozen-lockfile"] : ["install"];
|
|
5992
|
-
if (pm === "yarn") await fs18.writeFile(
|
|
6021
|
+
if (pm === "yarn") await fs18.writeFile(path18.join(cwd, "yarn.lock"), "", { flag: "wx" }).catch((err) => {
|
|
5993
6022
|
if (err.code !== "EEXIST") throw err;
|
|
5994
6023
|
});
|
|
5995
6024
|
await runner(cwd, pm, args);
|
|
@@ -6023,7 +6052,7 @@ function rebindRemixConfig(content, slug) {
|
|
|
6023
6052
|
async function configureIdentity(state, log) {
|
|
6024
6053
|
const cwd = state.directory, meta = state.project;
|
|
6025
6054
|
await writeProject(meta, cwd);
|
|
6026
|
-
const configFile =
|
|
6055
|
+
const configFile = path18.join(cwd, "src", "genex.config.ts");
|
|
6027
6056
|
const config = await fs18.readFile(configFile, "utf8").catch((err) => {
|
|
6028
6057
|
if (err.code === "ENOENT") return null;
|
|
6029
6058
|
throw err;
|
|
@@ -6032,7 +6061,7 @@ async function configureIdentity(state, log) {
|
|
|
6032
6061
|
await writeGameConfigFiles(meta, log, cwd);
|
|
6033
6062
|
await writeGitignore(cwd, log);
|
|
6034
6063
|
await installRemixProfile(cwd, state.apiUrl, log);
|
|
6035
|
-
const manifestFile =
|
|
6064
|
+
const manifestFile = path18.join(cwd, "package.json");
|
|
6036
6065
|
const manifest = JSON.parse(await fs18.readFile(manifestFile, "utf8"));
|
|
6037
6066
|
manifest.genex.remixSource = { projectId: state.source.projectId, slug: state.source.slug, sourceCommitSha: state.source.sourceCommitSha, version: state.source.version, versionCertainty: state.source.versionCertainty };
|
|
6038
6067
|
await fs18.writeFile(manifestFile, JSON.stringify(manifest, null, 2) + "\n");
|
|
@@ -6041,9 +6070,9 @@ async function configureIdentity(state, log) {
|
|
|
6041
6070
|
const scan = async (dir) => {
|
|
6042
6071
|
for (const entry of await fs18.readdir(dir, { withFileTypes: true })) {
|
|
6043
6072
|
if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
|
|
6044
|
-
const file =
|
|
6073
|
+
const file = path18.join(dir, entry.name);
|
|
6045
6074
|
if (entry.isDirectory()) await scan(file);
|
|
6046
|
-
else if (entry.isFile() && /\.(?:[cm]?[jt]sx?|html)$/.test(entry.name) && (await fs18.stat(file)).size < 1024 * 1024 && quoted.test(await fs18.readFile(file, "utf8"))) possible.push(
|
|
6075
|
+
else if (entry.isFile() && /\.(?:[cm]?[jt]sx?|html)$/.test(entry.name) && (await fs18.stat(file)).size < 1024 * 1024 && quoted.test(await fs18.readFile(file, "utf8"))) possible.push(path18.relative(cwd, file));
|
|
6047
6076
|
}
|
|
6048
6077
|
};
|
|
6049
6078
|
await scan(cwd);
|
|
@@ -6068,14 +6097,14 @@ async function runRemix(opts, deps = {}) {
|
|
|
6068
6097
|
const apiUrl = getApiUrl(opts.apiUrl), dashboardUrl = new URL(getAuthUrl(opts.authUrl)).origin;
|
|
6069
6098
|
if (opts.sourceOnly && !opts.destination) await assertReferenceParents(originalCwd);
|
|
6070
6099
|
const selected = parseRemixSource(opts.remixSource, apiUrl, dashboardUrl);
|
|
6071
|
-
let directory = opts.destination ?
|
|
6100
|
+
let directory = opts.destination ? path18.resolve(originalCwd, opts.destination) : !opts.sourceOnly ? path18.resolve(originalCwd, `${selected.slug}-remix`) : void 0;
|
|
6072
6101
|
if (directory) state = await checkDestination(directory);
|
|
6073
6102
|
resumed = state !== null;
|
|
6074
6103
|
if (state && (state.apiUrl !== apiUrl || state.sourceOnly !== Boolean(opts.sourceOnly) || ![state.requestedSlug, state.source.slug].includes(selected.slug) || opts.operationId && state.operationId !== opts.operationId || opts.name && state.name !== opts.name || selected.version && selected.version !== state.source.version)) throw new RemixError("operation_mismatch", "This folder belongs to a different remix operation. Resume with its original source/options or choose a fresh folder.");
|
|
6075
|
-
let token = await readUserToken(opts.envPath);
|
|
6104
|
+
let token = await readUserToken(opts.envPath, apiUrl);
|
|
6076
6105
|
if (!token) {
|
|
6077
|
-
token = await authorize(apiUrl, dashboardUrl, { log, inlineWaitMs: opts.timeoutSec ? opts.timeoutSec * 1e3 : void 0 });
|
|
6078
|
-
await writeUserToken(token, opts.envPath);
|
|
6106
|
+
token = await authorize(apiUrl, dashboardUrl, { envPath: opts.envPath, log, inlineWaitMs: opts.timeoutSec ? opts.timeoutSec * 1e3 : void 0 });
|
|
6107
|
+
await writeUserToken(token, opts.envPath, apiUrl);
|
|
6079
6108
|
}
|
|
6080
6109
|
const headers = { Authorization: `Bearer ${token}` };
|
|
6081
6110
|
if (!state) {
|
|
@@ -6086,7 +6115,7 @@ async function runRemix(opts, deps = {}) {
|
|
|
6086
6115
|
log.step(`Resolving source for ${selected.slug}\u2026`);
|
|
6087
6116
|
const body = await responseJson(await request(`${apiUrl}/api/projects/${encodeURIComponent(selected.slug)}/source?${query}`, { headers, redirect: "error" }));
|
|
6088
6117
|
validateSource(body, Boolean(opts.sourceOnly));
|
|
6089
|
-
directory ??=
|
|
6118
|
+
directory ??= path18.resolve(originalCwd, ".genex", "refs", `${body.source.slug}-${body.source.sourceCommitSha.slice(0, 12)}`);
|
|
6090
6119
|
const existing = await checkDestination(directory);
|
|
6091
6120
|
if (existing) {
|
|
6092
6121
|
if (!existing.sourceOnly || !opts.sourceOnly || existing.apiUrl !== apiUrl || existing.source.sourceCommitSha !== body.source.sourceCommitSha || existing.source.projectId !== body.source.projectId) throw new RemixError("destination_occupied", "This reference destination already belongs to another operation. Choose a fresh folder.");
|
|
@@ -6096,18 +6125,18 @@ async function runRemix(opts, deps = {}) {
|
|
|
6096
6125
|
state = { schema: 1, operationId: opts.operationId ?? crypto4.randomUUID(), directory, apiUrl, dashboardUrl, requestedSlug: selected.slug, source: body.source, sourceToken: body.sourceToken, sourceOnly: Boolean(opts.sourceOnly), cliVersion: getCliVersion(), name: opts.name?.trim() || `${body.source.title.slice(0, 74)} remix`, stage: "download", warnings: [...body.source.warnings] };
|
|
6097
6126
|
await fs18.mkdir(directory, { recursive: true });
|
|
6098
6127
|
if ((await fs18.readdir(directory)).length) throw new RemixError("destination_occupied", "The destination changed during setup. Choose a fresh folder.");
|
|
6099
|
-
await fs18.mkdir(
|
|
6128
|
+
await fs18.mkdir(path18.join(directory, ".genex"), { mode: 448 });
|
|
6100
6129
|
await saveState(state);
|
|
6101
6130
|
}
|
|
6102
6131
|
}
|
|
6103
6132
|
unlock = await acquireLock(state.directory);
|
|
6104
6133
|
state = await readState(state.directory) ?? state;
|
|
6105
6134
|
if (resumed && state.stage === "download") await renewSource(state, request, headers);
|
|
6106
|
-
const staging =
|
|
6135
|
+
const staging = path18.join(state.directory, ".genex", "remix-stage");
|
|
6107
6136
|
if (state.stage === "download") {
|
|
6108
6137
|
await fs18.rm(staging, { recursive: true, force: true });
|
|
6109
6138
|
await fs18.mkdir(staging, { recursive: true });
|
|
6110
|
-
const archive =
|
|
6139
|
+
const archive = path18.join(staging, "source.zip"), sourceDir = path18.join(staging, "source");
|
|
6111
6140
|
await fs18.mkdir(sourceDir);
|
|
6112
6141
|
const archiveUrl = new URL(state.source.archiveUrl, state.apiUrl);
|
|
6113
6142
|
if (archiveUrl.origin !== new URL(state.apiUrl).origin || !archiveUrl.pathname.startsWith("/api/projects/")) throw new RemixError("invalid_source_response", "Source archive must be served by the selected Genex API.");
|
|
@@ -6123,12 +6152,12 @@ async function runRemix(opts, deps = {}) {
|
|
|
6123
6152
|
if (!state.sourceOnly) {
|
|
6124
6153
|
let pkg;
|
|
6125
6154
|
try {
|
|
6126
|
-
pkg = JSON.parse(await fs18.readFile(
|
|
6155
|
+
pkg = JSON.parse(await fs18.readFile(path18.join(sourceDir, "package.json"), "utf8"));
|
|
6127
6156
|
} catch (error) {
|
|
6128
|
-
const staticEntry = await fs18.stat(
|
|
6157
|
+
const staticEntry = await fs18.stat(path18.join(sourceDir, "index.html")).catch(() => null);
|
|
6129
6158
|
if (error.code !== "ENOENT" || !staticEntry?.isFile()) throw new RemixError("source_unavailable", "Source has neither a readable package.json nor a static index.html. Use --source-only to inspect it.");
|
|
6130
6159
|
pkg = { name: manifestName(state.directory), private: true };
|
|
6131
|
-
await fs18.writeFile(
|
|
6160
|
+
await fs18.writeFile(path18.join(sourceDir, "package.json"), JSON.stringify(pkg, null, 2) + "\n");
|
|
6132
6161
|
state.warnings.push("This static game had no package.json; added a minimal private manifest for the local CLI. Its source and index.html remain unchanged.");
|
|
6133
6162
|
}
|
|
6134
6163
|
if (!pkg || typeof pkg !== "object" || Array.isArray(pkg)) throw new RemixError("source_unavailable", "Source package.json is not a project manifest.");
|
|
@@ -6139,7 +6168,7 @@ async function runRemix(opts, deps = {}) {
|
|
|
6139
6168
|
}
|
|
6140
6169
|
if (state.stage === "transfer") {
|
|
6141
6170
|
for (const entry of state.transferEntries ?? []) {
|
|
6142
|
-
const from =
|
|
6171
|
+
const from = path18.join(staging, "source", entry), to = path18.join(state.directory, entry);
|
|
6143
6172
|
if (await fs18.lstat(from).catch(() => null)) {
|
|
6144
6173
|
if (await fs18.lstat(to).catch(() => null)) throw new RemixError("destination_changed", `A file appeared at ${entry} during source preparation. It was left untouched.`);
|
|
6145
6174
|
await fs18.rename(from, to);
|
|
@@ -6223,7 +6252,7 @@ async function runRemix(opts, deps = {}) {
|
|
|
6223
6252
|
|
|
6224
6253
|
// src/commands/init.ts
|
|
6225
6254
|
import fs19 from "fs/promises";
|
|
6226
|
-
import
|
|
6255
|
+
import path19 from "path";
|
|
6227
6256
|
|
|
6228
6257
|
// src/lib/printed.ts
|
|
6229
6258
|
var printed = /* @__PURE__ */ new WeakSet();
|
|
@@ -6331,8 +6360,8 @@ async function runInit(opts) {
|
|
|
6331
6360
|
let totalNew = 0;
|
|
6332
6361
|
let totalUpdated = 0;
|
|
6333
6362
|
for (const t of targets) {
|
|
6334
|
-
const src = t.full ? templatesDir :
|
|
6335
|
-
const dest = t.full ? t.baseDir :
|
|
6363
|
+
const src = t.full ? templatesDir : path19.join(templatesDir, "skills");
|
|
6364
|
+
const dest = t.full ? t.baseDir : path19.join(t.baseDir, "skills");
|
|
6336
6365
|
const family = remixing ? skillFamilyFilter("remix") : converting ? skillFamilyFilter("tools", { hosted: true }) : skillFamilyFilter("game");
|
|
6337
6366
|
if (remixing) await pruneRemixWorkflow(t.baseDir);
|
|
6338
6367
|
const { copied, updated } = await copyTemplates(src, dest, {
|
|
@@ -6340,8 +6369,8 @@ async function runInit(opts) {
|
|
|
6340
6369
|
exclude: ["controllers", "motion", "asset-viewer", "blender-service"],
|
|
6341
6370
|
filter: t.full ? family : (rel) => family(`skills/${rel}`)
|
|
6342
6371
|
});
|
|
6343
|
-
await pruneRemovedSkills(
|
|
6344
|
-
await writeSkillsMarker(
|
|
6372
|
+
await pruneRemovedSkills(path19.join(t.baseDir, "skills"), log);
|
|
6373
|
+
await writeSkillsMarker(path19.join(t.baseDir, "skills"));
|
|
6345
6374
|
const added = copied.length - updated.length;
|
|
6346
6375
|
totalNew += added;
|
|
6347
6376
|
totalUpdated += updated.length;
|
|
@@ -6364,11 +6393,12 @@ async function runInit(opts) {
|
|
|
6364
6393
|
}
|
|
6365
6394
|
const authBaseUrl = getAuthUrl(opts.authUrl);
|
|
6366
6395
|
const authApiUrl = getApiUrl(opts.apiUrl);
|
|
6367
|
-
let token = await readUserToken(opts.envPath);
|
|
6396
|
+
let token = await readUserToken(opts.envPath, authApiUrl);
|
|
6368
6397
|
let savedToken = Boolean(token);
|
|
6369
6398
|
if (!token) {
|
|
6370
6399
|
try {
|
|
6371
6400
|
token = await authorize(authApiUrl, authBaseUrl, {
|
|
6401
|
+
envPath: opts.envPath,
|
|
6372
6402
|
log,
|
|
6373
6403
|
inlineWaitMs: opts.timeoutSec ? opts.timeoutSec * 1e3 : void 0
|
|
6374
6404
|
});
|
|
@@ -6382,8 +6412,8 @@ async function runInit(opts) {
|
|
|
6382
6412
|
log.dim("Your workspace files were installed. Re-run `genex init` to finish authorizing.");
|
|
6383
6413
|
throw markPrinted(err);
|
|
6384
6414
|
}
|
|
6385
|
-
const { path: tokenPath } = await writeUserToken(token, opts.envPath);
|
|
6386
|
-
log.success(`Authorized. Saved your token to ${c.cyan(tokenPath)}
|
|
6415
|
+
const { path: tokenPath } = await writeUserToken(token, opts.envPath, authApiUrl);
|
|
6416
|
+
log.success(`Authorized. Saved your token to ${c.cyan(tokenPath)}.`);
|
|
6387
6417
|
log.plain("");
|
|
6388
6418
|
savedToken = false;
|
|
6389
6419
|
}
|
|
@@ -6398,7 +6428,7 @@ async function runInit(opts) {
|
|
|
6398
6428
|
if (email) log.plain(` signed in as ${c.cyan(email)}`);
|
|
6399
6429
|
};
|
|
6400
6430
|
await echoIdentity();
|
|
6401
|
-
const projectName = opts.name?.trim() ||
|
|
6431
|
+
const projectName = opts.name?.trim() || path19.basename(process.cwd());
|
|
6402
6432
|
const create = (bearer) => createDraftProject({
|
|
6403
6433
|
apiUrl,
|
|
6404
6434
|
token: bearer,
|
|
@@ -6413,10 +6443,11 @@ async function runInit(opts) {
|
|
|
6413
6443
|
if (!created.meta && created.unauthorized && savedToken) {
|
|
6414
6444
|
log.plain("");
|
|
6415
6445
|
log.warn("Your saved sign-in was rejected \u2014 reconnecting in the browser\u2026");
|
|
6416
|
-
const aside = await rotateRejectedEnv(opts.envPath);
|
|
6446
|
+
const aside = await rotateRejectedEnv(opts.envPath, authApiUrl);
|
|
6417
6447
|
if (aside) log.dim(` moved the rejected credential to ${c.cyan(aside)}`);
|
|
6418
6448
|
try {
|
|
6419
6449
|
token = await authorize(authApiUrl, authBaseUrl, {
|
|
6450
|
+
envPath: opts.envPath,
|
|
6420
6451
|
log,
|
|
6421
6452
|
inlineWaitMs: opts.timeoutSec ? opts.timeoutSec * 1e3 : void 0
|
|
6422
6453
|
});
|
|
@@ -6430,8 +6461,8 @@ async function runInit(opts) {
|
|
|
6430
6461
|
log.dim("Your workspace files were installed. Re-run `genex init` to finish authorizing.");
|
|
6431
6462
|
throw markPrinted(err);
|
|
6432
6463
|
}
|
|
6433
|
-
const { path: tokenPath } = await writeUserToken(token, opts.envPath);
|
|
6434
|
-
log.success(`Authorized. Saved your token to ${c.cyan(tokenPath)}
|
|
6464
|
+
const { path: tokenPath } = await writeUserToken(token, opts.envPath, authApiUrl);
|
|
6465
|
+
log.success(`Authorized. Saved your token to ${c.cyan(tokenPath)}.`);
|
|
6435
6466
|
await echoIdentity();
|
|
6436
6467
|
created = await create(token);
|
|
6437
6468
|
}
|
|
@@ -6449,7 +6480,7 @@ async function runInit(opts) {
|
|
|
6449
6480
|
if (await writeToolsContract(process.cwd(), { hosted: true })) {
|
|
6450
6481
|
log.dim(" wrote the Genex Tools publishing rules into AGENTS.md (managed block)");
|
|
6451
6482
|
}
|
|
6452
|
-
await ensureEnvVar(
|
|
6483
|
+
await ensureEnvVar(path19.join(process.cwd(), ".env"), "VITE_GENEX_SLUG", meta.slug, log);
|
|
6453
6484
|
await reportCliEvent(apiUrl, token, "tools_converted");
|
|
6454
6485
|
}
|
|
6455
6486
|
if (!remixing) warnPreexisting(log, preexisting);
|
|
@@ -6478,7 +6509,7 @@ async function runInit(opts) {
|
|
|
6478
6509
|
// src/commands/link.ts
|
|
6479
6510
|
import fs20 from "fs/promises";
|
|
6480
6511
|
import os6 from "os";
|
|
6481
|
-
import
|
|
6512
|
+
import path20 from "path";
|
|
6482
6513
|
async function runLink(opts) {
|
|
6483
6514
|
const log = createLogger({ quiet: opts.quiet });
|
|
6484
6515
|
log.plain(c.bold("genex link"));
|
|
@@ -6499,10 +6530,11 @@ async function runLink(opts) {
|
|
|
6499
6530
|
}
|
|
6500
6531
|
const authBaseUrl = getAuthUrl(opts.authUrl);
|
|
6501
6532
|
const apiUrl = getApiUrl(opts.apiUrl);
|
|
6502
|
-
let token = await readUserToken(opts.envPath);
|
|
6533
|
+
let token = await readUserToken(opts.envPath, apiUrl);
|
|
6503
6534
|
if (!token) {
|
|
6504
6535
|
try {
|
|
6505
6536
|
token = await authorize(apiUrl, authBaseUrl, {
|
|
6537
|
+
envPath: opts.envPath,
|
|
6506
6538
|
log,
|
|
6507
6539
|
inlineWaitMs: opts.timeoutSec ? opts.timeoutSec * 1e3 : void 0
|
|
6508
6540
|
});
|
|
@@ -6514,7 +6546,7 @@ async function runLink(opts) {
|
|
|
6514
6546
|
}
|
|
6515
6547
|
throw err;
|
|
6516
6548
|
}
|
|
6517
|
-
const { path: tokenPath } = await writeUserToken(token, opts.envPath);
|
|
6549
|
+
const { path: tokenPath } = await writeUserToken(token, opts.envPath, apiUrl);
|
|
6518
6550
|
log.success(`Authorized. Saved your token to ${c.cyan(tokenPath)}.`);
|
|
6519
6551
|
log.plain("");
|
|
6520
6552
|
}
|
|
@@ -6573,13 +6605,13 @@ async function downloadSource(apiUrl, token, project, log) {
|
|
|
6573
6605
|
return false;
|
|
6574
6606
|
}
|
|
6575
6607
|
log.step(`Downloading the ${grant.sourceRef === "preview" ? "draft" : "published"} source\u2026`);
|
|
6576
|
-
const staging = await fs20.mkdtemp(
|
|
6577
|
-
const fresh =
|
|
6608
|
+
const staging = await fs20.mkdtemp(path20.join(os6.tmpdir(), "genex-link-"));
|
|
6609
|
+
const fresh = path20.join(staging, "source");
|
|
6578
6610
|
try {
|
|
6579
6611
|
if (!await cloneSource(grant, fresh, log)) return false;
|
|
6580
|
-
await fs20.rm(
|
|
6612
|
+
await fs20.rm(path20.join(fresh, ".git"), { recursive: true, force: true });
|
|
6581
6613
|
for (const entry of await fs20.readdir(fresh)) {
|
|
6582
|
-
await fs20.cp(
|
|
6614
|
+
await fs20.cp(path20.join(fresh, entry), path20.join(process.cwd(), entry), {
|
|
6583
6615
|
recursive: true,
|
|
6584
6616
|
force: true
|
|
6585
6617
|
});
|
|
@@ -6592,7 +6624,7 @@ async function downloadSource(apiUrl, token, project, log) {
|
|
|
6592
6624
|
}
|
|
6593
6625
|
}
|
|
6594
6626
|
async function ensureSlugEnv(slug, log, cwd = process.cwd()) {
|
|
6595
|
-
const file =
|
|
6627
|
+
const file = path20.join(cwd, ".env");
|
|
6596
6628
|
let content;
|
|
6597
6629
|
try {
|
|
6598
6630
|
content = await fs20.readFile(file, "utf8");
|
|
@@ -6663,7 +6695,7 @@ async function listOwnSlugs(apiUrl, token, log) {
|
|
|
6663
6695
|
|
|
6664
6696
|
// src/commands/pull.ts
|
|
6665
6697
|
import fs21 from "fs/promises";
|
|
6666
|
-
import
|
|
6698
|
+
import path21 from "path";
|
|
6667
6699
|
import os7 from "os";
|
|
6668
6700
|
function isMachineLocal(entry) {
|
|
6669
6701
|
if (entry === ".genex" || entry === "node_modules" || entry === ".git") return true;
|
|
@@ -6682,13 +6714,13 @@ async function runPull(opts) {
|
|
|
6682
6714
|
process.exitCode = 1;
|
|
6683
6715
|
return;
|
|
6684
6716
|
}
|
|
6685
|
-
const
|
|
6717
|
+
const apiUrl = getApiUrl(opts.apiUrl ?? meta.apiUrl);
|
|
6718
|
+
const token = await readUserToken(opts.envPath, apiUrl);
|
|
6686
6719
|
if (!token) {
|
|
6687
6720
|
log.error(`Not signed in. Run ${c.cyan("npx genex auth")} first.`);
|
|
6688
6721
|
process.exitCode = 1;
|
|
6689
6722
|
return;
|
|
6690
6723
|
}
|
|
6691
|
-
const apiUrl = getApiUrl(opts.apiUrl ?? meta.apiUrl);
|
|
6692
6724
|
if (!opts.force) {
|
|
6693
6725
|
const tree2 = await sourceTreeHash(cwd);
|
|
6694
6726
|
if (!meta.sourceTree) {
|
|
@@ -6723,19 +6755,19 @@ async function runPull(opts) {
|
|
|
6723
6755
|
process.exitCode = 1;
|
|
6724
6756
|
return;
|
|
6725
6757
|
}
|
|
6726
|
-
const staging = await fs21.mkdtemp(
|
|
6727
|
-
const fresh =
|
|
6758
|
+
const staging = await fs21.mkdtemp(path21.join(os7.tmpdir(), "genex-pull-"));
|
|
6759
|
+
const fresh = path21.join(staging, "source");
|
|
6728
6760
|
try {
|
|
6729
6761
|
if (!await cloneSource(grant, fresh, log)) {
|
|
6730
6762
|
process.exitCode = 1;
|
|
6731
6763
|
return;
|
|
6732
6764
|
}
|
|
6733
|
-
await fs21.rm(
|
|
6765
|
+
await fs21.rm(path21.join(fresh, ".git"), { recursive: true, force: true });
|
|
6734
6766
|
const kept = await keepReplaced(cwd, log);
|
|
6735
6767
|
await replaceTree(cwd, fresh);
|
|
6736
6768
|
if (kept) {
|
|
6737
6769
|
log.plain("");
|
|
6738
|
-
log.info(`What was here is kept at ${c.cyan(
|
|
6770
|
+
log.info(`What was here is kept at ${c.cyan(path21.relative(cwd, kept) || kept)}`);
|
|
6739
6771
|
log.dim(" Nothing was thrown away \u2014 re-apply from there, or delete it when you are done.");
|
|
6740
6772
|
}
|
|
6741
6773
|
} finally {
|
|
@@ -6759,13 +6791,13 @@ async function runPull(opts) {
|
|
|
6759
6791
|
}
|
|
6760
6792
|
async function keepReplaced(cwd, log) {
|
|
6761
6793
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
6762
|
-
const dest =
|
|
6794
|
+
const dest = path21.join(cwd, ".genex", `replaced-${stamp}`);
|
|
6763
6795
|
try {
|
|
6764
6796
|
const entries = (await fs21.readdir(cwd)).filter((e) => !isMachineLocal(e));
|
|
6765
6797
|
if (entries.length === 0) return null;
|
|
6766
6798
|
await fs21.mkdir(dest, { recursive: true });
|
|
6767
6799
|
for (const entry of entries) {
|
|
6768
|
-
await fs21.cp(
|
|
6800
|
+
await fs21.cp(path21.join(cwd, entry), path21.join(dest, entry), { recursive: true });
|
|
6769
6801
|
}
|
|
6770
6802
|
return dest;
|
|
6771
6803
|
} catch (err) {
|
|
@@ -6777,17 +6809,17 @@ async function keepReplaced(cwd, log) {
|
|
|
6777
6809
|
async function replaceTree(dest, src) {
|
|
6778
6810
|
for (const entry of await fs21.readdir(dest)) {
|
|
6779
6811
|
if (isMachineLocal(entry)) continue;
|
|
6780
|
-
await fs21.rm(
|
|
6812
|
+
await fs21.rm(path21.join(dest, entry), { recursive: true, force: true });
|
|
6781
6813
|
}
|
|
6782
6814
|
for (const entry of await fs21.readdir(src)) {
|
|
6783
6815
|
if (isMachineLocal(entry)) continue;
|
|
6784
|
-
await fs21.cp(
|
|
6816
|
+
await fs21.cp(path21.join(src, entry), path21.join(dest, entry), { recursive: true });
|
|
6785
6817
|
}
|
|
6786
6818
|
}
|
|
6787
6819
|
|
|
6788
6820
|
// src/commands/rename.ts
|
|
6789
6821
|
import fs22 from "fs/promises";
|
|
6790
|
-
import
|
|
6822
|
+
import path22 from "path";
|
|
6791
6823
|
async function runRename(opts) {
|
|
6792
6824
|
const log = createLogger({ quiet: opts.quiet });
|
|
6793
6825
|
log.plain(c.bold("genex rename"));
|
|
@@ -6804,13 +6836,13 @@ async function runRename(opts) {
|
|
|
6804
6836
|
process.exitCode = 1;
|
|
6805
6837
|
return;
|
|
6806
6838
|
}
|
|
6807
|
-
const
|
|
6839
|
+
const apiUrl = getApiUrl(opts.apiUrl ?? meta.apiUrl);
|
|
6840
|
+
const token = await readUserToken(opts.envPath, apiUrl);
|
|
6808
6841
|
if (!token) {
|
|
6809
6842
|
log.error("Not signed in. Run `genex auth` first.");
|
|
6810
6843
|
process.exitCode = 1;
|
|
6811
6844
|
return;
|
|
6812
6845
|
}
|
|
6813
|
-
const apiUrl = getApiUrl(opts.apiUrl ?? meta.apiUrl);
|
|
6814
6846
|
const from = meta.slug;
|
|
6815
6847
|
if (meta.status === "published" && !opts.yes) {
|
|
6816
6848
|
log.warn(`This retires ${c.cyan(from)} for good \u2014 it will redirect here, and nobody can reuse it.`);
|
|
@@ -6875,7 +6907,7 @@ async function runRename(opts) {
|
|
|
6875
6907
|
log.info("Run `genex preview` (or `publish`) to rebuild \u2014 the new slug is baked into the bundle.");
|
|
6876
6908
|
}
|
|
6877
6909
|
async function rewriteSlugEnv(from, to, log, cwd = process.cwd()) {
|
|
6878
|
-
const file =
|
|
6910
|
+
const file = path22.join(cwd, ".env");
|
|
6879
6911
|
let content;
|
|
6880
6912
|
try {
|
|
6881
6913
|
content = await fs22.readFile(file, "utf8");
|
|
@@ -6888,7 +6920,7 @@ async function rewriteSlugEnv(from, to, log, cwd = process.cwd()) {
|
|
|
6888
6920
|
log.dim(` .env: VITE_GENEX_SLUG=${to} (was ${from})`);
|
|
6889
6921
|
}
|
|
6890
6922
|
async function rewriteBakedSlug(from, to, log, cwd = process.cwd()) {
|
|
6891
|
-
const file =
|
|
6923
|
+
const file = path22.join(cwd, "src", "genex.config.ts");
|
|
6892
6924
|
let content;
|
|
6893
6925
|
try {
|
|
6894
6926
|
content = await fs22.readFile(file, "utf8");
|
|
@@ -6919,7 +6951,7 @@ async function runList(opts) {
|
|
|
6919
6951
|
const meta = await readProject();
|
|
6920
6952
|
const apiUrl = getApiUrl(opts.apiUrl ?? meta?.apiUrl);
|
|
6921
6953
|
const dashOrigin = (meta?.dashboardOrigins?.[0] ?? getAuthUrl(opts.authUrl)).replace(/\/+$/, "");
|
|
6922
|
-
let token = opts.token ?? await readUserToken(opts.envPath);
|
|
6954
|
+
let token = opts.token ?? await readUserToken(opts.envPath, apiUrl);
|
|
6923
6955
|
if (!token) {
|
|
6924
6956
|
if (opts.noAuth) {
|
|
6925
6957
|
log.error("Not signed in. Re-run without --no-auth to connect.");
|
|
@@ -6928,7 +6960,9 @@ async function runList(opts) {
|
|
|
6928
6960
|
}
|
|
6929
6961
|
log.plain("Not signed in \u2014 connecting\u2026");
|
|
6930
6962
|
try {
|
|
6963
|
+
assertAuthorizationOrigin(apiUrl, getApiUrl(opts.apiUrl));
|
|
6931
6964
|
token = await authorize(apiUrl, getAuthUrl(opts.authUrl), {
|
|
6965
|
+
envPath: opts.envPath,
|
|
6932
6966
|
log,
|
|
6933
6967
|
inlineWaitMs: opts.timeoutSec ? opts.timeoutSec * 1e3 : void 0
|
|
6934
6968
|
});
|
|
@@ -6942,7 +6976,7 @@ async function runList(opts) {
|
|
|
6942
6976
|
process.exitCode = 1;
|
|
6943
6977
|
return;
|
|
6944
6978
|
}
|
|
6945
|
-
const { path: tokenPath } = await writeUserToken(token, opts.envPath);
|
|
6979
|
+
const { path: tokenPath } = await writeUserToken(token, opts.envPath, apiUrl);
|
|
6946
6980
|
log.success(`Authorized. Saved your token to ${c.cyan(tokenPath)}.`);
|
|
6947
6981
|
log.plain("");
|
|
6948
6982
|
}
|
|
@@ -6954,7 +6988,9 @@ async function runList(opts) {
|
|
|
6954
6988
|
if (res.status === 401 && !opts.token && !opts.noAuth) {
|
|
6955
6989
|
log.plain("Your saved sign-in was rejected \u2014 reconnecting\u2026");
|
|
6956
6990
|
try {
|
|
6991
|
+
assertAuthorizationOrigin(apiUrl, getApiUrl(opts.apiUrl));
|
|
6957
6992
|
token = await authorize(apiUrl, getAuthUrl(opts.authUrl), {
|
|
6993
|
+
envPath: opts.envPath,
|
|
6958
6994
|
log,
|
|
6959
6995
|
inlineWaitMs: opts.timeoutSec ? opts.timeoutSec * 1e3 : void 0
|
|
6960
6996
|
});
|
|
@@ -6968,7 +7004,7 @@ async function runList(opts) {
|
|
|
6968
7004
|
process.exitCode = 1;
|
|
6969
7005
|
return;
|
|
6970
7006
|
}
|
|
6971
|
-
await writeUserToken(token, opts.envPath);
|
|
7007
|
+
await writeUserToken(token, opts.envPath, apiUrl);
|
|
6972
7008
|
res = await fetchProjects(token);
|
|
6973
7009
|
}
|
|
6974
7010
|
} catch (err) {
|
|
@@ -7039,13 +7075,13 @@ async function runMakeRemixable(opts) {
|
|
|
7039
7075
|
process.exitCode = 1;
|
|
7040
7076
|
return;
|
|
7041
7077
|
}
|
|
7042
|
-
const
|
|
7078
|
+
const apiUrl = getApiUrl(opts.apiUrl ?? meta.apiUrl);
|
|
7079
|
+
const token = await readUserToken(opts.envPath, apiUrl);
|
|
7043
7080
|
if (!token) {
|
|
7044
7081
|
log.error("Not authorized. Run `genex init` first to sign in.");
|
|
7045
7082
|
process.exitCode = 1;
|
|
7046
7083
|
return;
|
|
7047
7084
|
}
|
|
7048
|
-
const apiUrl = getApiUrl(opts.apiUrl ?? meta.apiUrl);
|
|
7049
7085
|
log.step("Preparing a public repo for your game\u2026");
|
|
7050
7086
|
let res;
|
|
7051
7087
|
try {
|
|
@@ -7127,7 +7163,7 @@ async function runDomain(opts) {
|
|
|
7127
7163
|
return;
|
|
7128
7164
|
}
|
|
7129
7165
|
const apiUrl = getApiUrl(opts.apiUrl ?? meta.apiUrl);
|
|
7130
|
-
let token = opts.token ?? await readUserToken(opts.envPath);
|
|
7166
|
+
let token = opts.token ?? await readUserToken(opts.envPath, apiUrl);
|
|
7131
7167
|
if (!token) {
|
|
7132
7168
|
if (opts.noAuth) {
|
|
7133
7169
|
log.error("Not signed in. Re-run without --no-auth to connect.");
|
|
@@ -7136,7 +7172,9 @@ async function runDomain(opts) {
|
|
|
7136
7172
|
}
|
|
7137
7173
|
log.plain("Not signed in \u2014 connecting\u2026");
|
|
7138
7174
|
try {
|
|
7175
|
+
assertAuthorizationOrigin(apiUrl, getApiUrl(opts.apiUrl));
|
|
7139
7176
|
token = await authorize(apiUrl, getAuthUrl(opts.authUrl), {
|
|
7177
|
+
envPath: opts.envPath,
|
|
7140
7178
|
log,
|
|
7141
7179
|
inlineWaitMs: opts.timeoutSec ? opts.timeoutSec * 1e3 : void 0
|
|
7142
7180
|
});
|
|
@@ -7150,7 +7188,7 @@ async function runDomain(opts) {
|
|
|
7150
7188
|
process.exitCode = 1;
|
|
7151
7189
|
return;
|
|
7152
7190
|
}
|
|
7153
|
-
await writeUserToken(token, opts.envPath);
|
|
7191
|
+
await writeUserToken(token, opts.envPath, apiUrl);
|
|
7154
7192
|
}
|
|
7155
7193
|
const base = `${apiUrl}/api/projects/${encodeURIComponent(meta.id)}/domains`;
|
|
7156
7194
|
const auth = { Authorization: `Bearer ${token}` };
|
|
@@ -7274,7 +7312,7 @@ async function runShop(opts) {
|
|
|
7274
7312
|
return;
|
|
7275
7313
|
}
|
|
7276
7314
|
const apiUrl = getApiUrl(opts.apiUrl ?? meta.apiUrl);
|
|
7277
|
-
let token = opts.token ?? await readUserToken(opts.envPath);
|
|
7315
|
+
let token = opts.token ?? await readUserToken(opts.envPath, apiUrl);
|
|
7278
7316
|
if (!token) {
|
|
7279
7317
|
if (opts.noAuth) {
|
|
7280
7318
|
log.error("Not signed in. Re-run without --no-auth to connect.");
|
|
@@ -7283,7 +7321,9 @@ async function runShop(opts) {
|
|
|
7283
7321
|
}
|
|
7284
7322
|
log.plain("Not signed in \u2014 connecting\u2026");
|
|
7285
7323
|
try {
|
|
7324
|
+
assertAuthorizationOrigin(apiUrl, getApiUrl(opts.apiUrl));
|
|
7286
7325
|
token = await authorize(apiUrl, getAuthUrl(opts.authUrl), {
|
|
7326
|
+
envPath: opts.envPath,
|
|
7287
7327
|
log,
|
|
7288
7328
|
inlineWaitMs: opts.timeoutSec ? opts.timeoutSec * 1e3 : void 0
|
|
7289
7329
|
});
|
|
@@ -7297,7 +7337,7 @@ async function runShop(opts) {
|
|
|
7297
7337
|
process.exitCode = 1;
|
|
7298
7338
|
return;
|
|
7299
7339
|
}
|
|
7300
|
-
await writeUserToken(token, opts.envPath);
|
|
7340
|
+
await writeUserToken(token, opts.envPath, apiUrl);
|
|
7301
7341
|
}
|
|
7302
7342
|
const auth = { Authorization: `Bearer ${token}`, "Content-Type": "application/json" };
|
|
7303
7343
|
const call = (url, init2) => apiFetch(url, { ...init2, headers: { ...auth, ...init2?.headers ?? {} } });
|
|
@@ -7522,12 +7562,6 @@ async function runPublish(opts) {
|
|
|
7522
7562
|
const log = createLogger({ quiet: opts.quiet });
|
|
7523
7563
|
log.plain(c.bold("genex publish"));
|
|
7524
7564
|
log.plain("");
|
|
7525
|
-
const token = opts.token ?? await readUserToken(opts.envPath);
|
|
7526
|
-
if (!token) {
|
|
7527
|
-
log.error("Not authorized. Run `genex init` first to sign in.");
|
|
7528
|
-
process.exitCode = 1;
|
|
7529
|
-
return;
|
|
7530
|
-
}
|
|
7531
7565
|
const meta = await readProject();
|
|
7532
7566
|
if (!meta) {
|
|
7533
7567
|
log.error("No genex project here. Run `genex init` in this directory first.");
|
|
@@ -7535,6 +7569,12 @@ async function runPublish(opts) {
|
|
|
7535
7569
|
return;
|
|
7536
7570
|
}
|
|
7537
7571
|
const apiUrl = getApiUrl(opts.apiUrl ?? meta.apiUrl);
|
|
7572
|
+
const token = opts.token ?? await readUserToken(opts.envPath, apiUrl);
|
|
7573
|
+
if (!token) {
|
|
7574
|
+
log.error("Not authorized. Run `genex init` first to sign in.");
|
|
7575
|
+
process.exitCode = 1;
|
|
7576
|
+
return;
|
|
7577
|
+
}
|
|
7538
7578
|
const detections = await detectFeatures(log);
|
|
7539
7579
|
advisoryNudges(log, detections);
|
|
7540
7580
|
if (!opts.noPush) {
|
|
@@ -7630,13 +7670,13 @@ async function runPromote(opts) {
|
|
|
7630
7670
|
process.exitCode = 1;
|
|
7631
7671
|
return;
|
|
7632
7672
|
}
|
|
7633
|
-
const
|
|
7673
|
+
const apiUrl = getApiUrl(meta.apiUrl);
|
|
7674
|
+
const token = await readUserToken(opts.envPath, apiUrl);
|
|
7634
7675
|
if (!token) {
|
|
7635
7676
|
log.error("Not authorized. Run `genex init` first to sign in.");
|
|
7636
7677
|
process.exitCode = 1;
|
|
7637
7678
|
return;
|
|
7638
7679
|
}
|
|
7639
|
-
const apiUrl = getApiUrl(meta.apiUrl);
|
|
7640
7680
|
log.step("Making your previewed build live\u2026");
|
|
7641
7681
|
const result = await promoteBuild(apiUrl, meta.id, token, log);
|
|
7642
7682
|
if (!result) {
|
|
@@ -7661,14 +7701,14 @@ async function runAcceptCommand(opts) {
|
|
|
7661
7701
|
const log = createLogger({ quiet: opts.quiet });
|
|
7662
7702
|
log.plain(c.bold("genex accept"));
|
|
7663
7703
|
log.plain("");
|
|
7664
|
-
const
|
|
7704
|
+
const [meta, workspace] = await Promise.all([readProject(), readWorkspace()]);
|
|
7705
|
+
const apiUrl = getApiUrl(meta?.apiUrl ?? workspace?.apiUrl);
|
|
7706
|
+
const token = await readUserToken(opts.envPath, apiUrl);
|
|
7665
7707
|
if (!token) {
|
|
7666
7708
|
log.error(`Not signed in. Run ${c.cyan("npx genex auth")} first.`);
|
|
7667
7709
|
process.exitCode = 1;
|
|
7668
7710
|
return;
|
|
7669
7711
|
}
|
|
7670
|
-
const [meta, workspace] = await Promise.all([readProject(), readWorkspace()]);
|
|
7671
|
-
const apiUrl = meta?.apiUrl ?? workspace?.apiUrl;
|
|
7672
7712
|
const ok = await runAccept({
|
|
7673
7713
|
token,
|
|
7674
7714
|
log,
|
|
@@ -7686,13 +7726,13 @@ async function runSave(opts) {
|
|
|
7686
7726
|
process.exitCode = 1;
|
|
7687
7727
|
return;
|
|
7688
7728
|
}
|
|
7689
|
-
const
|
|
7729
|
+
const apiUrl = meta.apiUrl ?? getApiUrl();
|
|
7730
|
+
const token = await readUserToken(opts.envPath, apiUrl);
|
|
7690
7731
|
if (!token) {
|
|
7691
7732
|
log.error("Not signed in.");
|
|
7692
7733
|
process.exitCode = 1;
|
|
7693
7734
|
return;
|
|
7694
7735
|
}
|
|
7695
|
-
const apiUrl = meta.apiUrl ?? getApiUrl();
|
|
7696
7736
|
let res;
|
|
7697
7737
|
try {
|
|
7698
7738
|
res = await apiFetch(`${apiUrl}/api/projects/${meta.id}/push-token`, {
|
|
@@ -7859,13 +7899,13 @@ async function runRollback(opts) {
|
|
|
7859
7899
|
process.exitCode = 1;
|
|
7860
7900
|
return;
|
|
7861
7901
|
}
|
|
7862
|
-
const
|
|
7902
|
+
const apiUrl = getApiUrl(meta.apiUrl);
|
|
7903
|
+
const token = await readUserToken(opts.envPath, apiUrl);
|
|
7863
7904
|
if (!token) {
|
|
7864
7905
|
log.error("Not authorized. Run `genex init` first to sign in.");
|
|
7865
7906
|
process.exitCode = 1;
|
|
7866
7907
|
return;
|
|
7867
7908
|
}
|
|
7868
|
-
const apiUrl = getApiUrl(meta.apiUrl);
|
|
7869
7909
|
const previous = await readPreviousVersion(apiUrl, meta.id, token, log);
|
|
7870
7910
|
if (!previous) {
|
|
7871
7911
|
process.exitCode = 1;
|
|
@@ -7896,7 +7936,7 @@ async function runRollback(opts) {
|
|
|
7896
7936
|
|
|
7897
7937
|
// src/commands/generate.ts
|
|
7898
7938
|
import fs25 from "fs/promises";
|
|
7899
|
-
import
|
|
7939
|
+
import path25 from "path";
|
|
7900
7940
|
import { PNG as PNG4 } from "pngjs";
|
|
7901
7941
|
|
|
7902
7942
|
// src/lib/glass.ts
|
|
@@ -8079,12 +8119,12 @@ function ceilingVerdict(input) {
|
|
|
8079
8119
|
|
|
8080
8120
|
// src/lib/generation-admission.ts
|
|
8081
8121
|
import fs24 from "fs/promises";
|
|
8082
|
-
import
|
|
8122
|
+
import path24 from "path";
|
|
8083
8123
|
import { createHash, randomUUID } from "crypto";
|
|
8084
8124
|
|
|
8085
8125
|
// src/lib/asset-budget.ts
|
|
8086
8126
|
import fs23 from "fs/promises";
|
|
8087
|
-
import
|
|
8127
|
+
import path23 from "path";
|
|
8088
8128
|
var DEFAULT_ASSET_BUDGET_SHARE = 0.5;
|
|
8089
8129
|
var DEFAULT_ASSET_BUDGET_CAP = 500;
|
|
8090
8130
|
function envShare(raw, fallback) {
|
|
@@ -8111,7 +8151,7 @@ function allowanceFor(input) {
|
|
|
8111
8151
|
if (!Number.isFinite(spendable) || spendable <= 0) return 0;
|
|
8112
8152
|
return Math.max(0, Math.min(Math.floor(spendable * config.share), config.cap));
|
|
8113
8153
|
}
|
|
8114
|
-
var storePath = (cwd) =>
|
|
8154
|
+
var storePath = (cwd) => path23.join(cwd, ".genex", "asset-budget.json");
|
|
8115
8155
|
async function readBudgetStore(cwd = process.cwd()) {
|
|
8116
8156
|
try {
|
|
8117
8157
|
const raw = await fs23.readFile(storePath(cwd), "utf8");
|
|
@@ -8133,7 +8173,7 @@ async function readBudgetStore(cwd = process.cwd()) {
|
|
|
8133
8173
|
}
|
|
8134
8174
|
async function writeBudgetStore(cwd, store) {
|
|
8135
8175
|
try {
|
|
8136
|
-
await fs23.access(
|
|
8176
|
+
await fs23.access(path23.join(cwd, ".genex"));
|
|
8137
8177
|
await fs23.writeFile(storePath(cwd), `${JSON.stringify(store, null, 2)}
|
|
8138
8178
|
`, "utf8");
|
|
8139
8179
|
return true;
|
|
@@ -8249,7 +8289,7 @@ async function assetBudgetGate(input) {
|
|
|
8249
8289
|
|
|
8250
8290
|
// src/lib/generation-admission.ts
|
|
8251
8291
|
async function withAdmissionLock(cwd, work, waitMs = 3e4) {
|
|
8252
|
-
const lock =
|
|
8292
|
+
const lock = path24.join(cwd, ".genex", "generation-admission.lock");
|
|
8253
8293
|
const deadline = Date.now() + waitMs;
|
|
8254
8294
|
let file;
|
|
8255
8295
|
while (!file) {
|
|
@@ -8524,7 +8564,7 @@ async function inlineLocalImage(filePath, flag) {
|
|
|
8524
8564
|
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.`
|
|
8525
8565
|
};
|
|
8526
8566
|
}
|
|
8527
|
-
const mime = IMAGE_MIME_BY_EXT[
|
|
8567
|
+
const mime = IMAGE_MIME_BY_EXT[path25.extname(filePath).toLowerCase()] ?? "image/png";
|
|
8528
8568
|
return { ok: true, dataUri: `data:${mime};base64,${bytes.toString("base64")}` };
|
|
8529
8569
|
}
|
|
8530
8570
|
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.";
|
|
@@ -8711,7 +8751,7 @@ async function runGenerate(kind, opts) {
|
|
|
8711
8751
|
let typedPrompt = opts.prompt?.trim();
|
|
8712
8752
|
if (!typedPrompt && kind === "model" && opts.imageUrl) {
|
|
8713
8753
|
const ref = opts.imageUrl.startsWith("data:") ? "local image" : opts.imageUrl;
|
|
8714
|
-
typedPrompt = `from image: ${
|
|
8754
|
+
typedPrompt = `from image: ${path25.basename(ref).slice(0, 120)}`;
|
|
8715
8755
|
}
|
|
8716
8756
|
if (kind === "model" && opts.texture !== void 0 && !MODEL_TEXTURE_TIERS.includes(opts.texture)) {
|
|
8717
8757
|
log.error(`--texture ${opts.texture} is a character texture size. \`genex model\` takes a texture TIER: ${MODEL_TEXTURE_TIERS.join("|")} (default detailed).`);
|
|
@@ -8831,14 +8871,14 @@ async function runGenerate(kind, opts) {
|
|
|
8831
8871
|
`Voice text is ${prompt.length} chars \u2014 the server clamps at 1000 (billing follows the clamp). Split longer copy into separate lines.`
|
|
8832
8872
|
);
|
|
8833
8873
|
}
|
|
8834
|
-
const
|
|
8874
|
+
const meta = await readProject();
|
|
8875
|
+
const apiUrl = getApiUrl(opts.apiUrl ?? meta?.apiUrl);
|
|
8876
|
+
const token = opts.token ?? await readUserToken(opts.envPath, apiUrl);
|
|
8835
8877
|
if (!token) {
|
|
8836
8878
|
log.error("Not authorized. Run `genex init` first to sign in.");
|
|
8837
8879
|
process.exitCode = 1;
|
|
8838
8880
|
return;
|
|
8839
8881
|
}
|
|
8840
|
-
const meta = await readProject();
|
|
8841
|
-
const apiUrl = getApiUrl(opts.apiUrl ?? meta?.apiUrl);
|
|
8842
8882
|
const mode = await workspaceMode();
|
|
8843
8883
|
if (mode === "tools") {
|
|
8844
8884
|
const lane = laneFor(await fetchLanes(apiUrl, token), kind);
|
|
@@ -8961,7 +9001,7 @@ async function reportGlassTerminal(view, outDir, log, json) {
|
|
|
8961
9001
|
});
|
|
8962
9002
|
continue;
|
|
8963
9003
|
}
|
|
8964
|
-
const outPath =
|
|
9004
|
+
const outPath = path25.join(outDir, `glass-${i + 1}.png`);
|
|
8965
9005
|
await fs25.writeFile(outPath, PNG4.sync.write(r.png));
|
|
8966
9006
|
solved.push({
|
|
8967
9007
|
path: outPath,
|
|
@@ -9582,7 +9622,7 @@ function writeJson(value) {
|
|
|
9582
9622
|
|
|
9583
9623
|
// src/commands/model-sub.ts
|
|
9584
9624
|
import fs26 from "fs/promises";
|
|
9585
|
-
import
|
|
9625
|
+
import path26 from "path";
|
|
9586
9626
|
var MODEL_SUBCOMMANDS = ["segment", "rig", "animate", "import"];
|
|
9587
9627
|
function apiErrorMessage(data, fallback) {
|
|
9588
9628
|
if (typeof data !== "object" || data === null) return fallback;
|
|
@@ -9597,7 +9637,7 @@ async function importModelFile(args) {
|
|
|
9597
9637
|
const { log } = args;
|
|
9598
9638
|
const filePath = args.filePath.trim();
|
|
9599
9639
|
if (!/\.glb$/i.test(filePath)) {
|
|
9600
|
-
log.error(`\`import\` takes a .glb file (binary glTF 2.0). Export ${
|
|
9640
|
+
log.error(`\`import\` takes a .glb file (binary glTF 2.0). Export ${path26.basename(filePath) || "the model"} as GLB first \u2014 Blender: File \u2192 Export \u2192 glTF 2.0, format "glTF Binary".`);
|
|
9601
9641
|
return null;
|
|
9602
9642
|
}
|
|
9603
9643
|
let bytes;
|
|
@@ -9608,18 +9648,18 @@ async function importModelFile(args) {
|
|
|
9608
9648
|
return null;
|
|
9609
9649
|
}
|
|
9610
9650
|
if (bytes.byteLength > MODEL_IMPORT_MAX_BYTES) {
|
|
9611
|
-
log.error(`${
|
|
9651
|
+
log.error(`${path26.basename(filePath)} is ${(bytes.byteLength / 1e6).toFixed(1)} MB; imports are capped at ${MODEL_IMPORT_MAX_BYTES / 1024 / 1024} MB. Shrink its textures (they are usually the bulk) and try again.`);
|
|
9612
9652
|
return null;
|
|
9613
9653
|
}
|
|
9614
9654
|
if (bytes.byteLength < 12 || bytes.readUInt32LE(0) !== GLB_MAGIC) {
|
|
9615
|
-
log.error(`${
|
|
9655
|
+
log.error(`${path26.basename(filePath)} is not a GLB (no glTF magic). A .gltf + .bin pair must be exported as one binary .glb.`);
|
|
9616
9656
|
return null;
|
|
9617
9657
|
}
|
|
9618
9658
|
const headers = { "Content-Type": "application/json", Authorization: `Bearer ${args.token}` };
|
|
9619
9659
|
const minted = await apiFetch(`${args.apiUrl}/api/generations/import`, {
|
|
9620
9660
|
method: "POST",
|
|
9621
9661
|
headers,
|
|
9622
|
-
body: JSON.stringify({ filename:
|
|
9662
|
+
body: JSON.stringify({ filename: path26.basename(filePath), bytes: bytes.byteLength, contentType: "model/gltf-binary" })
|
|
9623
9663
|
});
|
|
9624
9664
|
if (printedStructuredError(minted)) return null;
|
|
9625
9665
|
if (!minted.ok) {
|
|
@@ -9628,7 +9668,7 @@ async function importModelFile(args) {
|
|
|
9628
9668
|
return null;
|
|
9629
9669
|
}
|
|
9630
9670
|
const { id, uploadUrl, url } = await minted.json();
|
|
9631
|
-
log.dim(` uploading ${
|
|
9671
|
+
log.dim(` uploading ${path26.basename(filePath)} (${(bytes.byteLength / 1e6).toFixed(1)} MB)\u2026`);
|
|
9632
9672
|
const put = await fetch(uploadUrl, {
|
|
9633
9673
|
method: "PUT",
|
|
9634
9674
|
headers: { "Content-Type": "model/gltf-binary", "Content-Length": String(bytes.byteLength) },
|
|
@@ -9659,14 +9699,14 @@ async function runModelImport(opts) {
|
|
|
9659
9699
|
process.exitCode = 1;
|
|
9660
9700
|
return;
|
|
9661
9701
|
}
|
|
9662
|
-
const
|
|
9702
|
+
const project = await readProject();
|
|
9703
|
+
const apiUrl = getApiUrl(opts.apiUrl ?? project?.apiUrl);
|
|
9704
|
+
const token = opts.token ?? await readUserToken(opts.envPath, apiUrl);
|
|
9663
9705
|
if (!token) {
|
|
9664
9706
|
log.error("Not authorized. Run `genex init` first to sign in.");
|
|
9665
9707
|
process.exitCode = 1;
|
|
9666
9708
|
return;
|
|
9667
9709
|
}
|
|
9668
|
-
const project = await readProject();
|
|
9669
|
-
const apiUrl = getApiUrl(opts.apiUrl ?? project?.apiUrl);
|
|
9670
9710
|
const imported = await importModelFile({ apiUrl, token, filePath, log });
|
|
9671
9711
|
if (!imported) {
|
|
9672
9712
|
process.exitCode = 1;
|
|
@@ -9776,7 +9816,7 @@ async function runModelAnimate(opts) {
|
|
|
9776
9816
|
}
|
|
9777
9817
|
|
|
9778
9818
|
// src/commands/wait.ts
|
|
9779
|
-
import
|
|
9819
|
+
import path27 from "path";
|
|
9780
9820
|
var TERMINAL2 = /* @__PURE__ */ new Set(["completed", "failed"]);
|
|
9781
9821
|
async function runWait(opts) {
|
|
9782
9822
|
if (opts.all) return runWaitAll(opts);
|
|
@@ -9789,14 +9829,14 @@ async function runWait(opts) {
|
|
|
9789
9829
|
process.exitCode = 1;
|
|
9790
9830
|
return;
|
|
9791
9831
|
}
|
|
9792
|
-
const
|
|
9832
|
+
const meta = await readProject();
|
|
9833
|
+
const apiUrl = getApiUrl(opts.apiUrl ?? meta?.apiUrl);
|
|
9834
|
+
const token = opts.token ?? await readUserToken(opts.envPath, apiUrl);
|
|
9793
9835
|
if (!token) {
|
|
9794
9836
|
log.error("Not authorized. Run `genex init` first to sign in.");
|
|
9795
9837
|
process.exitCode = 1;
|
|
9796
9838
|
return;
|
|
9797
9839
|
}
|
|
9798
|
-
const meta = await readProject();
|
|
9799
|
-
const apiUrl = getApiUrl(opts.apiUrl ?? meta?.apiUrl);
|
|
9800
9840
|
let view;
|
|
9801
9841
|
try {
|
|
9802
9842
|
const res = await apiFetch(`${apiUrl}/api/generations/${id}`, {
|
|
@@ -9862,14 +9902,14 @@ async function runWaitAll(opts) {
|
|
|
9862
9902
|
);
|
|
9863
9903
|
return;
|
|
9864
9904
|
}
|
|
9865
|
-
const
|
|
9905
|
+
const meta = await readProject(cwd);
|
|
9906
|
+
const apiUrl = getApiUrl(opts.apiUrl ?? meta?.apiUrl);
|
|
9907
|
+
const token = opts.token ?? await readUserToken(opts.envPath, apiUrl);
|
|
9866
9908
|
if (!token) {
|
|
9867
9909
|
log.error("Not authorized. Run `genex init` first to sign in.");
|
|
9868
9910
|
process.exitCode = 1;
|
|
9869
9911
|
return;
|
|
9870
9912
|
}
|
|
9871
|
-
const meta = await readProject(cwd);
|
|
9872
|
-
const apiUrl = getApiUrl(opts.apiUrl ?? meta?.apiUrl);
|
|
9873
9913
|
const views = /* @__PURE__ */ new Map();
|
|
9874
9914
|
try {
|
|
9875
9915
|
const res = await apiFetch(`${apiUrl}/api/generations`, {
|
|
@@ -9915,7 +9955,7 @@ async function runWaitAll(opts) {
|
|
|
9915
9955
|
if (v?.status !== "completed" || !v.files?.length) continue;
|
|
9916
9956
|
const local = localDeliveryFor("tools", { ...opts, outDir: opts.outDir ?? e.outDir }, e.prompt || e.kind);
|
|
9917
9957
|
if (!local) continue;
|
|
9918
|
-
const outDir =
|
|
9958
|
+
const outDir = path27.join(path27.relative(process.cwd(), cwd) || ".", local.outDir);
|
|
9919
9959
|
const target = { kind: e.kind, prompt: local.prompt, id: e.id, outDir };
|
|
9920
9960
|
const missing = await undeliveredFiles(v.files, target);
|
|
9921
9961
|
if (missing.length === 0) continue;
|
|
@@ -10024,7 +10064,7 @@ async function toRow(e, v, cwd) {
|
|
|
10024
10064
|
|
|
10025
10065
|
// src/commands/controller.ts
|
|
10026
10066
|
import fs28 from "fs/promises";
|
|
10027
|
-
import
|
|
10067
|
+
import path29 from "path";
|
|
10028
10068
|
|
|
10029
10069
|
// ../../packages/meshy-animation-catalog/src/index.ts
|
|
10030
10070
|
import { createHash as createHash2 } from "crypto";
|
|
@@ -19155,8 +19195,8 @@ function searchMeshyAnimations(query, options = {}) {
|
|
|
19155
19195
|
|
|
19156
19196
|
// src/lib/anims.ts
|
|
19157
19197
|
import fs27 from "fs/promises";
|
|
19158
|
-
import
|
|
19159
|
-
var ANIMS_DEST =
|
|
19198
|
+
import path28 from "path";
|
|
19199
|
+
var ANIMS_DEST = path28.join("public", "assets", "anims");
|
|
19160
19200
|
var HIDDEN_TAG = "reference";
|
|
19161
19201
|
async function runAnims(opts) {
|
|
19162
19202
|
const log = createLogger({ quiet: opts.quiet });
|
|
@@ -19172,7 +19212,7 @@ async function runAnims(opts) {
|
|
|
19172
19212
|
printCatalog(log, manifest, selectors);
|
|
19173
19213
|
return;
|
|
19174
19214
|
}
|
|
19175
|
-
const controllerMarker =
|
|
19215
|
+
const controllerMarker = path28.join(root, "src", "controllers", "character");
|
|
19176
19216
|
if (!await exists3(controllerMarker)) {
|
|
19177
19217
|
log.error(
|
|
19178
19218
|
`No character controller in this game (missing ${c.cyan("src/controllers/character/")}).`
|
|
@@ -19181,11 +19221,11 @@ async function runAnims(opts) {
|
|
|
19181
19221
|
process.exitCode = 1;
|
|
19182
19222
|
return;
|
|
19183
19223
|
}
|
|
19184
|
-
const destDir =
|
|
19185
|
-
const gameManifestPath =
|
|
19224
|
+
const destDir = path28.join(root, ANIMS_DEST);
|
|
19225
|
+
const gameManifestPath = path28.join(destDir, "manifest.json");
|
|
19186
19226
|
if (opts.reset) {
|
|
19187
19227
|
await fs27.rm(destDir, { recursive: true, force: true });
|
|
19188
|
-
log.step(`Cleared ${c.cyan(ANIMS_DEST +
|
|
19228
|
+
log.step(`Cleared ${c.cyan(ANIMS_DEST + path28.sep)} (--reset)`);
|
|
19189
19229
|
}
|
|
19190
19230
|
if (selectors.length === 0) {
|
|
19191
19231
|
const installed = await readGameManifest(gameManifestPath);
|
|
@@ -19223,7 +19263,7 @@ async function runAnims(opts) {
|
|
|
19223
19263
|
}
|
|
19224
19264
|
}
|
|
19225
19265
|
const wanted = [...byName.values()].sort((a, b) => a.name.localeCompare(b.name));
|
|
19226
|
-
const cacheDir =
|
|
19266
|
+
const cacheDir = path28.join(
|
|
19227
19267
|
opts.cacheDir ?? getAnimsCacheDir(),
|
|
19228
19268
|
`${manifest.library}-v${manifest.version}`
|
|
19229
19269
|
);
|
|
@@ -19235,13 +19275,13 @@ async function runAnims(opts) {
|
|
|
19235
19275
|
let addedBytes = 0;
|
|
19236
19276
|
const failures = [];
|
|
19237
19277
|
for (const entry of wanted) {
|
|
19238
|
-
const dest =
|
|
19278
|
+
const dest = path28.join(destDir, entry.file);
|
|
19239
19279
|
if (await hasSize(dest, entry.bytes)) {
|
|
19240
19280
|
presentCount++;
|
|
19241
19281
|
continue;
|
|
19242
19282
|
}
|
|
19243
19283
|
try {
|
|
19244
|
-
const cached =
|
|
19284
|
+
const cached = path28.join(cacheDir, entry.file);
|
|
19245
19285
|
if (!await hasSize(cached, entry.bytes)) {
|
|
19246
19286
|
const res = await fetch(base + entry.file);
|
|
19247
19287
|
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
@@ -19251,7 +19291,7 @@ async function runAnims(opts) {
|
|
|
19251
19291
|
await fs27.copyFile(cached, dest);
|
|
19252
19292
|
installedCount++;
|
|
19253
19293
|
addedBytes += entry.bytes;
|
|
19254
|
-
log.dim(` ${
|
|
19294
|
+
log.dim(` ${path28.join(ANIMS_DEST, entry.file)} (${formatMb(entry.bytes)})`);
|
|
19255
19295
|
} catch (err) {
|
|
19256
19296
|
failures.push(`${entry.name} (${err instanceof Error ? err.message : String(err)})`);
|
|
19257
19297
|
}
|
|
@@ -19273,7 +19313,7 @@ async function runAnims(opts) {
|
|
|
19273
19313
|
if (presentCount > 0) parts.push(`${presentCount} already present`);
|
|
19274
19314
|
if (bundledSkips > 0) parts.push(`${bundledSkips} already bundled in animation-library.glb`);
|
|
19275
19315
|
log.success(
|
|
19276
|
-
`${parts.join(", ")} \u2192 ${c.cyan(ANIMS_DEST +
|
|
19316
|
+
`${parts.join(", ")} \u2192 ${c.cyan(ANIMS_DEST + path28.sep)}${addedBytes > 0 ? ` (+${formatMb(addedBytes)})` : ""}`
|
|
19277
19317
|
);
|
|
19278
19318
|
for (const [selector, entries] of resolved) {
|
|
19279
19319
|
const names = entries.filter((e) => !coreNames.has(e.name)).map((e) => e.name);
|
|
@@ -19305,7 +19345,7 @@ async function loadManifest(baseOverride) {
|
|
|
19305
19345
|
}
|
|
19306
19346
|
} catch {
|
|
19307
19347
|
}
|
|
19308
|
-
const snapshotPath =
|
|
19348
|
+
const snapshotPath = path28.join(getTemplatesDir(), "controllers", "assets", "anims-manifest.json");
|
|
19309
19349
|
const manifest = JSON.parse(await fs27.readFile(snapshotPath, "utf8"));
|
|
19310
19350
|
return { manifest, source: "snapshot" };
|
|
19311
19351
|
}
|
|
@@ -19639,8 +19679,8 @@ var CONTROLLER_FILE_SETS = {
|
|
|
19639
19679
|
]
|
|
19640
19680
|
}
|
|
19641
19681
|
};
|
|
19642
|
-
var CODE_DEST =
|
|
19643
|
-
var ASSETS_DEST =
|
|
19682
|
+
var CODE_DEST = path29.join("src", "controllers");
|
|
19683
|
+
var ASSETS_DEST = path29.join("public", "assets");
|
|
19644
19684
|
async function runController(opts) {
|
|
19645
19685
|
const log = createLogger({ quiet: opts.quiet });
|
|
19646
19686
|
if (opts.kind?.trim() === "anims") {
|
|
@@ -19657,31 +19697,31 @@ async function runController(opts) {
|
|
|
19657
19697
|
process.exitCode = 1;
|
|
19658
19698
|
return;
|
|
19659
19699
|
}
|
|
19660
|
-
const srcDir =
|
|
19700
|
+
const srcDir = path29.join(getTemplatesDir(), "controllers");
|
|
19661
19701
|
const root = opts.cwd ?? process.cwd();
|
|
19662
19702
|
const set = CONTROLLER_FILE_SETS[kind];
|
|
19663
19703
|
log.plain(c.bold(`genex controller ${kind}`));
|
|
19664
19704
|
log.plain("");
|
|
19665
|
-
log.step(`Installing the ${kind} controller into ${c.cyan(CODE_DEST +
|
|
19705
|
+
log.step(`Installing the ${kind} controller into ${c.cyan(CODE_DEST + path29.sep)}`);
|
|
19666
19706
|
const plan = [
|
|
19667
|
-
...set.code.map((rel) => ({ from: rel, rel:
|
|
19707
|
+
...set.code.map((rel) => ({ from: rel, rel: path29.join(CODE_DEST, rel) })),
|
|
19668
19708
|
...set.assets.map((rel) => ({
|
|
19669
19709
|
from: rel,
|
|
19670
|
-
rel:
|
|
19710
|
+
rel: path29.join(ASSETS_DEST, path29.basename(rel))
|
|
19671
19711
|
}))
|
|
19672
19712
|
];
|
|
19673
19713
|
let copied = 0;
|
|
19674
19714
|
let skipped = 0;
|
|
19675
19715
|
try {
|
|
19676
19716
|
for (const file of plan) {
|
|
19677
|
-
const dest =
|
|
19717
|
+
const dest = path29.join(root, file.rel);
|
|
19678
19718
|
if (!opts.force && await exists4(dest)) {
|
|
19679
19719
|
skipped++;
|
|
19680
19720
|
log.dim(` skipped ${file.rel} (exists \u2014 use --force to overwrite)`);
|
|
19681
19721
|
continue;
|
|
19682
19722
|
}
|
|
19683
|
-
await fs28.mkdir(
|
|
19684
|
-
await fs28.copyFile(
|
|
19723
|
+
await fs28.mkdir(path29.dirname(dest), { recursive: true });
|
|
19724
|
+
await fs28.copyFile(path29.join(srcDir, file.from), dest);
|
|
19685
19725
|
copied++;
|
|
19686
19726
|
log.dim(` ${file.rel}`);
|
|
19687
19727
|
}
|
|
@@ -19697,7 +19737,7 @@ async function runController(opts) {
|
|
|
19697
19737
|
if (kind === "character") {
|
|
19698
19738
|
if (opts.character) {
|
|
19699
19739
|
try {
|
|
19700
|
-
const token = opts.token !== void 0 ? opts.token : await readUserToken();
|
|
19740
|
+
const token = opts.token !== void 0 ? opts.token : await readUserToken(void 0, getApiUrl(opts.apiUrl));
|
|
19701
19741
|
if (!token) throw new Error("Not authorized. Run `genex init` before installing a Meshy character.");
|
|
19702
19742
|
await installMeshyCharacterManifest({
|
|
19703
19743
|
root,
|
|
@@ -19734,7 +19774,7 @@ async function runController(opts) {
|
|
|
19734
19774
|
for (const line of set.sketch) {
|
|
19735
19775
|
log.dim(` ${line}`);
|
|
19736
19776
|
}
|
|
19737
|
-
if (kind === "character" && !await exists4(
|
|
19777
|
+
if (kind === "character" && !await exists4(path29.join(root, ASSETS_DEST, "meshy-character.json"))) {
|
|
19738
19778
|
log.plain("");
|
|
19739
19779
|
log.plain(
|
|
19740
19780
|
` ${stepOffset + 3}. This game has no generated character yet \u2014 ${c.cyan(
|
|
@@ -19766,8 +19806,8 @@ async function installMeshyCharacterManifest(args) {
|
|
|
19766
19806
|
throw new Error("The API returned an invalid Meshy character manifest.");
|
|
19767
19807
|
}
|
|
19768
19808
|
assertCompleteMeshyControllerPack(manifest);
|
|
19769
|
-
const destination =
|
|
19770
|
-
await fs28.mkdir(
|
|
19809
|
+
const destination = path29.join(args.root, ASSETS_DEST, "meshy-character.json");
|
|
19810
|
+
await fs28.mkdir(path29.dirname(destination), { recursive: true });
|
|
19771
19811
|
await fs28.writeFile(
|
|
19772
19812
|
destination,
|
|
19773
19813
|
`${JSON.stringify(manifest, null, 2)}
|
|
@@ -19913,9 +19953,9 @@ function assertCompleteMeshyControllerPack(manifest) {
|
|
|
19913
19953
|
}
|
|
19914
19954
|
async function installFallbackAvatar(args) {
|
|
19915
19955
|
const { root, srcDir, log } = args;
|
|
19916
|
-
const dest =
|
|
19917
|
-
await fs28.mkdir(
|
|
19918
|
-
await fs28.copyFile(
|
|
19956
|
+
const dest = path29.join(root, ASSETS_DEST, "avatar.vrm");
|
|
19957
|
+
await fs28.mkdir(path29.dirname(dest), { recursive: true });
|
|
19958
|
+
await fs28.copyFile(path29.join(srcDir, "assets", "default-avatar.vrm"), dest);
|
|
19919
19959
|
log.dim(" public/assets/avatar.vrm (fallback avatar \u2014 bundled CC0 default)");
|
|
19920
19960
|
}
|
|
19921
19961
|
async function exists4(p) {
|
|
@@ -19929,7 +19969,7 @@ async function exists4(p) {
|
|
|
19929
19969
|
|
|
19930
19970
|
// src/commands/character.ts
|
|
19931
19971
|
import fs29 from "fs/promises";
|
|
19932
|
-
import
|
|
19972
|
+
import path30 from "path";
|
|
19933
19973
|
function exactAnimation(selector) {
|
|
19934
19974
|
const trimmed = selector.trim();
|
|
19935
19975
|
if (/^-?\d+$/.test(trimmed)) return animationById(Number(trimmed));
|
|
@@ -19955,10 +19995,11 @@ function resolveActionSelectors(selectors) {
|
|
|
19955
19995
|
return { ok: true, actionIds: [...new Set(actionIds)] };
|
|
19956
19996
|
}
|
|
19957
19997
|
async function context(opts) {
|
|
19958
|
-
const token = opts.token ?? await readUserToken(opts.envPath);
|
|
19959
|
-
if (!token) return null;
|
|
19960
19998
|
const project = await readProject();
|
|
19961
|
-
|
|
19999
|
+
const apiUrl = getApiUrl(opts.apiUrl ?? project?.apiUrl);
|
|
20000
|
+
const token = opts.token ?? await readUserToken(opts.envPath, apiUrl);
|
|
20001
|
+
if (!token) return null;
|
|
20002
|
+
return { token, apiUrl };
|
|
19962
20003
|
}
|
|
19963
20004
|
var CHARACTER_BRIEF_MAX_CHARS = 600;
|
|
19964
20005
|
function apiErrorMessage2(data, fallback) {
|
|
@@ -20009,7 +20050,7 @@ function showAmbiguity(selector, candidates, log, json = false) {
|
|
|
20009
20050
|
}
|
|
20010
20051
|
process.exitCode = 1;
|
|
20011
20052
|
}
|
|
20012
|
-
var INSTALLED_MANIFEST =
|
|
20053
|
+
var INSTALLED_MANIFEST = path30.join("public", "assets", "meshy-character.json");
|
|
20013
20054
|
async function resolveAdoptTarget(selector) {
|
|
20014
20055
|
const trimmed = selector?.trim();
|
|
20015
20056
|
if (trimmed && !trimmed.endsWith(".json")) {
|
|
@@ -20141,7 +20182,7 @@ async function runCharacterImport(opts) {
|
|
|
20141
20182
|
opts,
|
|
20142
20183
|
ctx,
|
|
20143
20184
|
kind: "character",
|
|
20144
|
-
prompt: `Import ${
|
|
20185
|
+
prompt: `Import ${path30.basename(filePath)} as a rigged character`,
|
|
20145
20186
|
createPath: "/api/characters/import",
|
|
20146
20187
|
body,
|
|
20147
20188
|
quote: price,
|
|
@@ -20611,27 +20652,28 @@ import { basename } from "path";
|
|
|
20611
20652
|
var VIDEO_SECONDS = [5, 6, 7, 8];
|
|
20612
20653
|
var MAX_VIDEO_BYTES = 200 * 1024 * 1024;
|
|
20613
20654
|
async function context2(opts) {
|
|
20614
|
-
const token = opts.token ?? await readUserToken(opts.envPath);
|
|
20615
|
-
if (!token) return null;
|
|
20616
20655
|
const project = await readProject();
|
|
20617
|
-
|
|
20656
|
+
const apiUrl = getApiUrl(opts.apiUrl ?? project?.apiUrl);
|
|
20657
|
+
const token = opts.token ?? await readUserToken(opts.envPath, apiUrl);
|
|
20658
|
+
if (!token) return null;
|
|
20659
|
+
return { token, apiUrl };
|
|
20618
20660
|
}
|
|
20619
|
-
async function readVideo(
|
|
20661
|
+
async function readVideo(path37, log) {
|
|
20620
20662
|
let bytes;
|
|
20621
20663
|
try {
|
|
20622
|
-
bytes = await readFile(
|
|
20664
|
+
bytes = await readFile(path37);
|
|
20623
20665
|
} catch {
|
|
20624
|
-
log.error(`Can't read ${
|
|
20666
|
+
log.error(`Can't read ${path37}.`);
|
|
20625
20667
|
return null;
|
|
20626
20668
|
}
|
|
20627
20669
|
if (bytes.byteLength > MAX_VIDEO_BYTES) {
|
|
20628
|
-
log.error(`${basename(
|
|
20670
|
+
log.error(`${basename(path37)} is ${(bytes.byteLength / 1e6).toFixed(0)} MB \u2014 the limit is ${MAX_VIDEO_BYTES / 1e6} MB.`);
|
|
20629
20671
|
return null;
|
|
20630
20672
|
}
|
|
20631
20673
|
return bytes;
|
|
20632
20674
|
}
|
|
20633
|
-
async function uploadVideo(apiUrl, token, characterId,
|
|
20634
|
-
const contentType = /\.mov$/i.test(
|
|
20675
|
+
async function uploadVideo(apiUrl, token, characterId, path37, bytes, log) {
|
|
20676
|
+
const contentType = /\.mov$/i.test(path37) ? "video/quicktime" : "video/mp4";
|
|
20635
20677
|
const minted = await apiFetch(
|
|
20636
20678
|
`${apiUrl}/api/characters/${encodeURIComponent(characterId)}/motions/video-url`,
|
|
20637
20679
|
{
|
|
@@ -20646,7 +20688,7 @@ async function uploadVideo(apiUrl, token, characterId, path36, bytes, log) {
|
|
|
20646
20688
|
return null;
|
|
20647
20689
|
}
|
|
20648
20690
|
const { uploadUrl, videoUrl } = await minted.json();
|
|
20649
|
-
log.dim(` uploading ${basename(
|
|
20691
|
+
log.dim(` uploading ${basename(path37)} (${(bytes.byteLength / 1e6).toFixed(1)} MB)\u2026`);
|
|
20650
20692
|
const put = await fetch(uploadUrl, {
|
|
20651
20693
|
method: "PUT",
|
|
20652
20694
|
headers: { "Content-Type": contentType, "Content-Length": String(bytes.byteLength) },
|
|
@@ -20979,7 +21021,7 @@ function rank(items, query) {
|
|
|
20979
21021
|
|
|
20980
21022
|
// src/commands/motion.ts
|
|
20981
21023
|
import fs30 from "fs/promises";
|
|
20982
|
-
import
|
|
21024
|
+
import path31 from "path";
|
|
20983
21025
|
|
|
20984
21026
|
// src/lib/motion/npz.ts
|
|
20985
21027
|
import zlib from "zlib";
|
|
@@ -22257,7 +22299,7 @@ async function expandTakes(selectors) {
|
|
|
22257
22299
|
const st = await fs30.stat(sel).catch(() => null);
|
|
22258
22300
|
if (st?.isDirectory()) {
|
|
22259
22301
|
const names = await fs30.readdir(sel);
|
|
22260
|
-
for (const n of names.sort()) if (n.endsWith(".npz")) out.push(
|
|
22302
|
+
for (const n of names.sort()) if (n.endsWith(".npz")) out.push(path31.join(sel, n));
|
|
22261
22303
|
} else if (st?.isFile()) {
|
|
22262
22304
|
out.push(sel);
|
|
22263
22305
|
} else {
|
|
@@ -22314,7 +22356,7 @@ async function motionVerify(opts, log) {
|
|
|
22314
22356
|
}
|
|
22315
22357
|
const reports = [];
|
|
22316
22358
|
for (const file of files) {
|
|
22317
|
-
const stem =
|
|
22359
|
+
const stem = path31.basename(file).replace(/\.npz$/, "");
|
|
22318
22360
|
try {
|
|
22319
22361
|
reports.push(analyzeTake(stem, await fs30.readFile(file), gates));
|
|
22320
22362
|
} catch (err) {
|
|
@@ -22372,7 +22414,7 @@ async function motionCompile(opts, log) {
|
|
|
22372
22414
|
}
|
|
22373
22415
|
const inputs = [];
|
|
22374
22416
|
for (const file of files) {
|
|
22375
|
-
const stem =
|
|
22417
|
+
const stem = path31.basename(file).replace(/\.npz$/, "");
|
|
22376
22418
|
try {
|
|
22377
22419
|
inputs.push({ stem, take: loadTake(await fs30.readFile(file)) });
|
|
22378
22420
|
} catch (err) {
|
|
@@ -22381,7 +22423,7 @@ async function motionCompile(opts, log) {
|
|
|
22381
22423
|
return;
|
|
22382
22424
|
}
|
|
22383
22425
|
}
|
|
22384
|
-
const setName = opts.set ??
|
|
22426
|
+
const setName = opts.set ?? path31.basename(opts.out).replace(/\.json$/, "");
|
|
22385
22427
|
let result;
|
|
22386
22428
|
try {
|
|
22387
22429
|
result = compileSet(inputs, setName, cfg);
|
|
@@ -22396,7 +22438,7 @@ async function motionCompile(opts, log) {
|
|
|
22396
22438
|
process.exitCode = 1;
|
|
22397
22439
|
return;
|
|
22398
22440
|
}
|
|
22399
|
-
await fs30.mkdir(
|
|
22441
|
+
await fs30.mkdir(path31.dirname(path31.resolve(opts.out)), { recursive: true });
|
|
22400
22442
|
const json = JSON.stringify(result.data);
|
|
22401
22443
|
await fs30.writeFile(opts.out, json);
|
|
22402
22444
|
if (opts.json) {
|
|
@@ -22416,9 +22458,9 @@ var MOTION_RUNTIME_FILES = [
|
|
|
22416
22458
|
var MOTION_PRESETS = {
|
|
22417
22459
|
rifle: ["sets/rifle.json", "sets/jumps.json"]
|
|
22418
22460
|
};
|
|
22419
|
-
var MOTION_DEST =
|
|
22461
|
+
var MOTION_DEST = path31.join("src", "motion");
|
|
22420
22462
|
async function motionInstall(opts, log) {
|
|
22421
|
-
const srcDir =
|
|
22463
|
+
const srcDir = path31.join(getTemplatesDir(), "motion");
|
|
22422
22464
|
const root = opts.cwd ?? process.cwd();
|
|
22423
22465
|
const preset = opts.set;
|
|
22424
22466
|
if (preset !== void 0 && !MOTION_PRESETS[preset]) {
|
|
@@ -22429,21 +22471,21 @@ async function motionInstall(opts, log) {
|
|
|
22429
22471
|
const files = [...MOTION_RUNTIME_FILES, ...preset ? MOTION_PRESETS[preset] : []];
|
|
22430
22472
|
log.plain(c.bold(`genex motion install${preset ? ` --set ${preset}` : ""}`));
|
|
22431
22473
|
log.plain("");
|
|
22432
|
-
log.step(`Vendoring the motion runtime into ${c.cyan(MOTION_DEST +
|
|
22474
|
+
log.step(`Vendoring the motion runtime into ${c.cyan(MOTION_DEST + path31.sep)}`);
|
|
22433
22475
|
let copied = 0, skipped = 0;
|
|
22434
22476
|
try {
|
|
22435
22477
|
for (const rel of files) {
|
|
22436
|
-
const dest =
|
|
22478
|
+
const dest = path31.join(root, MOTION_DEST, rel);
|
|
22437
22479
|
const exists5 = await fs30.access(dest).then(() => true, () => false);
|
|
22438
22480
|
if (!opts.force && exists5) {
|
|
22439
22481
|
skipped++;
|
|
22440
|
-
log.dim(` skipped ${
|
|
22482
|
+
log.dim(` skipped ${path31.join(MOTION_DEST, rel)} (exists \u2014 use --force to overwrite)`);
|
|
22441
22483
|
continue;
|
|
22442
22484
|
}
|
|
22443
|
-
await fs30.mkdir(
|
|
22444
|
-
await fs30.copyFile(
|
|
22485
|
+
await fs30.mkdir(path31.dirname(dest), { recursive: true });
|
|
22486
|
+
await fs30.copyFile(path31.join(srcDir, rel), dest);
|
|
22445
22487
|
copied++;
|
|
22446
|
-
log.dim(` ${
|
|
22488
|
+
log.dim(` ${path31.join(MOTION_DEST, rel)}`);
|
|
22447
22489
|
}
|
|
22448
22490
|
} catch (err) {
|
|
22449
22491
|
log.error(`Copy failed: ${String(err)}`);
|
|
@@ -22522,12 +22564,12 @@ async function runMotion(opts) {
|
|
|
22522
22564
|
|
|
22523
22565
|
// src/commands/blender.ts
|
|
22524
22566
|
import fs31 from "fs/promises";
|
|
22525
|
-
import
|
|
22567
|
+
import path32 from "path";
|
|
22526
22568
|
var SUBS2 = ["demo", "exec", "snap", "scene", "import", "export", "reset", "mcp", "serve", "seat", "release"];
|
|
22527
22569
|
var DEFAULT_OUT_DIR = "assets/blender";
|
|
22528
22570
|
async function writeB64(dir, name, b64) {
|
|
22529
22571
|
await fs31.mkdir(dir, { recursive: true });
|
|
22530
|
-
const p =
|
|
22572
|
+
const p = path32.join(dir, name);
|
|
22531
22573
|
await fs31.writeFile(p, Buffer.from(b64, "base64"));
|
|
22532
22574
|
return p;
|
|
22533
22575
|
}
|
|
@@ -22545,7 +22587,7 @@ async function runBlender(opts) {
|
|
|
22545
22587
|
return 1;
|
|
22546
22588
|
}
|
|
22547
22589
|
if (sub === "serve") {
|
|
22548
|
-
const { serveLocalBlender } = await import("./blender-serve-
|
|
22590
|
+
const { serveLocalBlender } = await import("./blender-serve-GW3LTFOX.js");
|
|
22549
22591
|
const port = Number(process.env.GENEX_BLENDER_PORT ?? 8088);
|
|
22550
22592
|
log.step(`Starting a local Blender service on port ${port}`);
|
|
22551
22593
|
log.plain(
|
|
@@ -22554,7 +22596,7 @@ async function runBlender(opts) {
|
|
|
22554
22596
|
return serveLocalBlender({ port, log });
|
|
22555
22597
|
}
|
|
22556
22598
|
if (sub === "mcp") {
|
|
22557
|
-
const { runBlenderMcp } = await import("./blender-mcp-
|
|
22599
|
+
const { runBlenderMcp } = await import("./blender-mcp-XLEK6SLM.js");
|
|
22558
22600
|
return runBlenderMcp();
|
|
22559
22601
|
}
|
|
22560
22602
|
if (sub === "seat") {
|
|
@@ -22612,7 +22654,7 @@ async function runBlender(opts) {
|
|
|
22612
22654
|
log.plain(rest.join("\n"));
|
|
22613
22655
|
return 1;
|
|
22614
22656
|
}
|
|
22615
|
-
const outDir =
|
|
22657
|
+
const outDir = path32.resolve(opts.outDir ?? DEFAULT_OUT_DIR);
|
|
22616
22658
|
const mode = opts.mode;
|
|
22617
22659
|
if (mode !== void 0 && !isRenderMode(mode)) {
|
|
22618
22660
|
log.error(`Unknown --mode ${mode}. Use one of: ${RENDER_MODES.join(", ")}.`);
|
|
@@ -22652,13 +22694,13 @@ async function runBlender(opts) {
|
|
|
22652
22694
|
return 0;
|
|
22653
22695
|
}
|
|
22654
22696
|
case "export": {
|
|
22655
|
-
const target = opts.out ??
|
|
22697
|
+
const target = opts.out ?? path32.join(outDir, "scene.glb");
|
|
22656
22698
|
const r = await blenderCall(base, "/export", { path: "/tmp/genex-export.glb" });
|
|
22657
22699
|
if (!r.glbBase64) {
|
|
22658
22700
|
log.error(`/export answered with no GLB bytes${r.uploaded ? " (it was uploaded, not inlined)" : ""}`);
|
|
22659
22701
|
return 1;
|
|
22660
22702
|
}
|
|
22661
|
-
await fs31.mkdir(
|
|
22703
|
+
await fs31.mkdir(path32.dirname(target), { recursive: true });
|
|
22662
22704
|
await fs31.writeFile(target, Buffer.from(r.glbBase64, "base64"));
|
|
22663
22705
|
log.success(`Exported ${r.bytes ?? 0} bytes`);
|
|
22664
22706
|
log.plain(` ${c.cyan(target)}`);
|
|
@@ -22697,7 +22739,7 @@ async function runBlender(opts) {
|
|
|
22697
22739
|
log.error(`Can't read ${opts.input}`);
|
|
22698
22740
|
return 1;
|
|
22699
22741
|
}
|
|
22700
|
-
label =
|
|
22742
|
+
label = path32.basename(opts.input);
|
|
22701
22743
|
}
|
|
22702
22744
|
const r = await blenderCall(base, "/exec", { script, ...mode ? { mode } : {}, sheet: { formats: SHEET_FORMATS } });
|
|
22703
22745
|
if (r.stdout?.trim()) log.plain(r.stdout.trimEnd());
|
|
@@ -22818,7 +22860,7 @@ print(f"castle: {n} objects")
|
|
|
22818
22860
|
// src/commands/asset-new.ts
|
|
22819
22861
|
import fs32 from "fs";
|
|
22820
22862
|
import fsp from "fs/promises";
|
|
22821
|
-
import
|
|
22863
|
+
import path33 from "path";
|
|
22822
22864
|
import { pathToFileURL } from "url";
|
|
22823
22865
|
var EXTRA_FILES = [
|
|
22824
22866
|
"genex-asset.example.json",
|
|
@@ -22912,7 +22954,7 @@ Every milestone ends with \`npm run verify\`, then \`npx genex preview --no-buil
|
|
|
22912
22954
|
}
|
|
22913
22955
|
async function runAssetNew(options) {
|
|
22914
22956
|
const log = createLogger();
|
|
22915
|
-
const cwd = options.dir ?
|
|
22957
|
+
const cwd = options.dir ? path33.resolve(options.dir) : process.cwd();
|
|
22916
22958
|
const slug = options.assetSlug;
|
|
22917
22959
|
if (!slug) {
|
|
22918
22960
|
log.error('Usage: genex asset new <slug> [--title "<Name>"] [--dims WxDxH] [--tri-band LOW-HIGH] [--holder "<your name>"] [--out <dir>]');
|
|
@@ -22922,14 +22964,14 @@ async function runAssetNew(options) {
|
|
|
22922
22964
|
log.error(`The slug must be kebab-case (letters, digits, dashes), got: ${slug}`);
|
|
22923
22965
|
return 1;
|
|
22924
22966
|
}
|
|
22925
|
-
const templateDir =
|
|
22967
|
+
const templateDir = path33.join(getTemplatesDir(), "asset-viewer");
|
|
22926
22968
|
if (!fs32.existsSync(templateDir)) {
|
|
22927
22969
|
log.error(`Vendored asset-viewer template not found at ${templateDir} \u2014 reinstall the CLI.`);
|
|
22928
22970
|
return 1;
|
|
22929
22971
|
}
|
|
22930
|
-
const manifestTools = await import(pathToFileURL(
|
|
22972
|
+
const manifestTools = await import(pathToFileURL(path33.join(templateDir, "tools", "emit-manifest.mjs")).href);
|
|
22931
22973
|
const { SHARED_FILES, PARITY_FILENAME, hashSharedFiles, pascalCase } = manifestTools;
|
|
22932
|
-
const lockPath =
|
|
22974
|
+
const lockPath = path33.join(templateDir, "shared-files.sha256.json");
|
|
22933
22975
|
const lock = JSON.parse(await fsp.readFile(lockPath, "utf8"));
|
|
22934
22976
|
const actual = hashSharedFiles(templateDir);
|
|
22935
22977
|
const drifted = SHARED_FILES.filter((rel) => lock.files[rel] !== actual[rel]);
|
|
@@ -22942,7 +22984,7 @@ async function runAssetNew(options) {
|
|
|
22942
22984
|
const triBand = parseBand(options.triBand ?? "500-8000");
|
|
22943
22985
|
const holder = options.holder?.trim() || HOLDER_PLACEHOLDER;
|
|
22944
22986
|
const year = (/* @__PURE__ */ new Date()).getUTCFullYear();
|
|
22945
|
-
const outDir =
|
|
22987
|
+
const outDir = path33.resolve(cwd, options.out ?? slug);
|
|
22946
22988
|
if (fs32.existsSync(outDir) && fs32.readdirSync(outDir).length > 0 && !options.force) {
|
|
22947
22989
|
log.error(`${outDir} already holds files. Nothing was touched \u2014 pass --force to write into it anyway.`);
|
|
22948
22990
|
return 1;
|
|
@@ -22971,24 +23013,24 @@ async function runAssetNew(options) {
|
|
|
22971
23013
|
};
|
|
22972
23014
|
await fsp.mkdir(outDir, { recursive: true });
|
|
22973
23015
|
for (const rel of [...SHARED_FILES, ...EXTRA_FILES]) {
|
|
22974
|
-
const to =
|
|
22975
|
-
await fsp.mkdir(
|
|
22976
|
-
await fsp.copyFile(
|
|
23016
|
+
const to = path33.join(outDir, rel);
|
|
23017
|
+
await fsp.mkdir(path33.dirname(to), { recursive: true });
|
|
23018
|
+
await fsp.copyFile(path33.join(templateDir, rel), to);
|
|
22977
23019
|
}
|
|
22978
|
-
const pkg = fillTemplate(await fsp.readFile(
|
|
23020
|
+
const pkg = fillTemplate(await fsp.readFile(path33.join(templateDir, "package.json"), "utf8"), {
|
|
22979
23021
|
slug,
|
|
22980
23022
|
name,
|
|
22981
23023
|
version
|
|
22982
23024
|
});
|
|
22983
|
-
await fsp.writeFile(
|
|
22984
|
-
await fsp.writeFile(
|
|
22985
|
-
await fsp.writeFile(
|
|
23025
|
+
await fsp.writeFile(path33.join(outDir, "package.json"), pkg, "utf8");
|
|
23026
|
+
await fsp.writeFile(path33.join(outDir, "asset.config.json"), JSON.stringify(config, null, 2) + "\n", "utf8");
|
|
23027
|
+
await fsp.writeFile(path33.join(outDir, ".gitignore"), GITIGNORE, "utf8");
|
|
22986
23028
|
await fsp.writeFile(
|
|
22987
|
-
|
|
23029
|
+
path33.join(outDir, "DESIGN.md"),
|
|
22988
23030
|
designDoc({ name, slug, sizeMeters, triBand, holder }),
|
|
22989
23031
|
"utf8"
|
|
22990
23032
|
);
|
|
22991
|
-
const placeholder = await fsp.readFile(
|
|
23033
|
+
const placeholder = await fsp.readFile(path33.join(templateDir, "src", "asset", "PLACEHOLDER.ts"), "utf8");
|
|
22992
23034
|
const seeded = seedAssetSource(placeholder, {
|
|
22993
23035
|
slug,
|
|
22994
23036
|
name,
|
|
@@ -22999,8 +23041,8 @@ async function runAssetNew(options) {
|
|
|
22999
23041
|
pascalCase
|
|
23000
23042
|
});
|
|
23001
23043
|
const assetFilename = `${slug.replace(/-([a-z0-9])/g, (_, ch) => ch.toUpperCase())}.ts`;
|
|
23002
|
-
await fsp.mkdir(
|
|
23003
|
-
await fsp.writeFile(
|
|
23044
|
+
await fsp.mkdir(path33.join(outDir, "src", "asset"), { recursive: true });
|
|
23045
|
+
await fsp.writeFile(path33.join(outDir, "src", "asset", assetFilename), seeded, "utf8");
|
|
23004
23046
|
const copied = hashSharedFiles(outDir);
|
|
23005
23047
|
const mismatched = SHARED_FILES.filter((rel) => copied[rel] !== lock.files[rel]);
|
|
23006
23048
|
if (mismatched.length) {
|
|
@@ -23008,7 +23050,7 @@ async function runAssetNew(options) {
|
|
|
23008
23050
|
return 1;
|
|
23009
23051
|
}
|
|
23010
23052
|
await fsp.writeFile(
|
|
23011
|
-
|
|
23053
|
+
path33.join(outDir, PARITY_FILENAME),
|
|
23012
23054
|
JSON.stringify(
|
|
23013
23055
|
{
|
|
23014
23056
|
note: "Recorded by `genex asset new`. Gate G13 recomputes these; do not hand-edit the viewer.",
|
|
@@ -23052,10 +23094,10 @@ async function runAssetNew(options) {
|
|
|
23052
23094
|
}
|
|
23053
23095
|
|
|
23054
23096
|
// src/commands/tools.ts
|
|
23055
|
-
import
|
|
23097
|
+
import path35 from "path";
|
|
23056
23098
|
|
|
23057
23099
|
// src/commands/doctor.ts
|
|
23058
|
-
import
|
|
23100
|
+
import path34 from "path";
|
|
23059
23101
|
var LANE_ORDER = [
|
|
23060
23102
|
"model",
|
|
23061
23103
|
"image",
|
|
@@ -23114,7 +23156,7 @@ async function runDoctor(opts = {}) {
|
|
|
23114
23156
|
fix: latest && isNewerVersion(latest, installed) ? "npm i -D @genex-ai/cli-demo@latest (apply at a safe moment, never mid-task)" : void 0
|
|
23115
23157
|
});
|
|
23116
23158
|
const apiUrl = getApiUrl(opts.apiUrl ?? workspace?.apiUrl ?? project?.apiUrl);
|
|
23117
|
-
const token = await readUserToken(opts.envPath);
|
|
23159
|
+
const token = await readUserToken(opts.envPath, apiUrl);
|
|
23118
23160
|
let email = null;
|
|
23119
23161
|
let authState = "none";
|
|
23120
23162
|
if (token) {
|
|
@@ -23336,7 +23378,7 @@ async function fetchLegalStatus(apiUrl, token) {
|
|
|
23336
23378
|
}
|
|
23337
23379
|
async function firstSkillsMarker() {
|
|
23338
23380
|
for (const target of resolveAgentTargets()) {
|
|
23339
|
-
const marker = await readSkillsMarker(
|
|
23381
|
+
const marker = await readSkillsMarker(path34.join(target.baseDir, "skills"));
|
|
23340
23382
|
if (marker) return marker;
|
|
23341
23383
|
}
|
|
23342
23384
|
return null;
|
|
@@ -23387,8 +23429,8 @@ async function runTools(opts) {
|
|
|
23387
23429
|
let totalNew = 0;
|
|
23388
23430
|
let totalUpdated = 0;
|
|
23389
23431
|
for (const t of targets) {
|
|
23390
|
-
const dest =
|
|
23391
|
-
const { copied, updated } = await copyTemplates(
|
|
23432
|
+
const dest = path35.join(t.baseDir, "skills");
|
|
23433
|
+
const { copied, updated } = await copyTemplates(path35.join(templatesDir, "skills"), dest, {
|
|
23392
23434
|
filter: (rel) => skillFamilyFilter("tools")(`skills/${rel}`)
|
|
23393
23435
|
});
|
|
23394
23436
|
await pruneRemovedSkills(dest, log);
|
|
@@ -23416,10 +23458,11 @@ async function runTools(opts) {
|
|
|
23416
23458
|
await ensureLocalCli(log, process.cwd(), { skip: opts.noInstall });
|
|
23417
23459
|
log.plain("");
|
|
23418
23460
|
const authBaseUrl = getAuthUrl(opts.authUrl);
|
|
23419
|
-
let token = await readUserToken(opts.envPath);
|
|
23461
|
+
let token = await readUserToken(opts.envPath, apiUrl);
|
|
23420
23462
|
if (!token) {
|
|
23421
23463
|
try {
|
|
23422
23464
|
token = await authorize(apiUrl, authBaseUrl, {
|
|
23465
|
+
envPath: opts.envPath,
|
|
23423
23466
|
log,
|
|
23424
23467
|
inlineWaitMs: opts.timeoutSec ? opts.timeoutSec * 1e3 : void 0
|
|
23425
23468
|
});
|
|
@@ -23435,8 +23478,8 @@ async function runTools(opts) {
|
|
|
23435
23478
|
log.dim("The workspace is set up. Re-run `genex tools` to finish authorizing.");
|
|
23436
23479
|
throw markPrinted(err);
|
|
23437
23480
|
}
|
|
23438
|
-
const { path: tokenPath } = await writeUserToken(token, opts.envPath);
|
|
23439
|
-
log.success(`Authorized. Saved your token to ${c.cyan(tokenPath)}
|
|
23481
|
+
const { path: tokenPath } = await writeUserToken(token, opts.envPath, apiUrl);
|
|
23482
|
+
log.success(`Authorized. Saved your token to ${c.cyan(tokenPath)}.`);
|
|
23440
23483
|
log.plain("");
|
|
23441
23484
|
}
|
|
23442
23485
|
await reportToolsInit(apiUrl, token);
|
|
@@ -23448,7 +23491,7 @@ async function runTools(opts) {
|
|
|
23448
23491
|
|
|
23449
23492
|
// src/commands/budget.ts
|
|
23450
23493
|
import fs33 from "fs/promises";
|
|
23451
|
-
import
|
|
23494
|
+
import path36 from "path";
|
|
23452
23495
|
var BUDGET_SOURCE_LINE = "Live from your account; prices can change without a deploy.";
|
|
23453
23496
|
var BUDGET_APPROVAL_REQUIRED = "STOP: raising the asset allowance requires the player's explicit approval. Re-run with --user-approved only after they agreed to the number.";
|
|
23454
23497
|
function internalKind(kind) {
|
|
@@ -23456,7 +23499,7 @@ function internalKind(kind) {
|
|
|
23456
23499
|
}
|
|
23457
23500
|
async function isGenexProject(cwd) {
|
|
23458
23501
|
try {
|
|
23459
|
-
await fs33.access(
|
|
23502
|
+
await fs33.access(path36.join(cwd, ".genex"));
|
|
23460
23503
|
return true;
|
|
23461
23504
|
} catch {
|
|
23462
23505
|
return false;
|
|
@@ -23491,13 +23534,13 @@ async function runBudget(opts = {}) {
|
|
|
23491
23534
|
fail3(`--assets takes a whole number of credits, 0 or more (got ${String(opts.assets)}).`);
|
|
23492
23535
|
return;
|
|
23493
23536
|
}
|
|
23494
|
-
const
|
|
23537
|
+
const meta = await readProject(cwd);
|
|
23538
|
+
const apiUrl = getApiUrl(opts.apiUrl ?? meta?.apiUrl);
|
|
23539
|
+
const token = opts.token ?? await readUserToken(opts.envPath, apiUrl);
|
|
23495
23540
|
if (!token) {
|
|
23496
23541
|
fail3("Not authorized. Run `genex init` first to sign in.");
|
|
23497
23542
|
return;
|
|
23498
23543
|
}
|
|
23499
|
-
const meta = await readProject(cwd);
|
|
23500
|
-
const apiUrl = getApiUrl(opts.apiUrl ?? meta?.apiUrl);
|
|
23501
23544
|
const snapshot = await fetchCreditsSnapshot(apiUrl, token);
|
|
23502
23545
|
let store;
|
|
23503
23546
|
let persisted = true;
|