@genex-ai/cli-demo 1.2.3-dev.337 → 1.2.4-dev.342
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +128 -96
- package/package.json +1 -1
- package/templates/skills/genex-getting-started/SKILL.md +17 -0
package/dist/index.js
CHANGED
|
@@ -933,6 +933,7 @@ async function runAuth(opts) {
|
|
|
933
933
|
}
|
|
934
934
|
|
|
935
935
|
// src/commands/init.ts
|
|
936
|
+
import fs10 from "fs/promises";
|
|
936
937
|
import path10 from "path";
|
|
937
938
|
|
|
938
939
|
// src/lib/copy-templates.ts
|
|
@@ -1010,6 +1011,7 @@ Your capabilities (all via \`npx genex \u2026\`): generate \`model\` \xB7 \`skyb
|
|
|
1010
1011
|
16. Never add debug-only code to the game to check your own work \u2014 no hidden test modes, no special URL parameters, no forced-visible flags, no auth mocks, no pixel-sampling hooks. \`?genex_local_test=1\` is the platform's own supported mode and is fine; your own bypass is not. (The multiplayer skill's small build identifier, token-free status line, and connected-quorum watchdog are production supportability, not a bypass \u2014 keep those.)
|
|
1011
1012
|
17. Input directions match their labels: A/\u2190 moves or turns the player screen-LEFT, D/\u2192 screen-RIGHT, mouse-up looks up, and drag-pan axes share ONE convention. The cursor is either the gameplay tool (RTS, card, builder) or locked away during play \u2014 keyboard-only games included. Check it in every milestone's smoke pass.
|
|
1012
1013
|
18. v0 is a milestone, not the destination. When the ask was bigger than one loop, every milestone after v0 grows back toward the FULL ask with DESIGN.md's content lines as the checklist \u2014 a slice that previewed well never quietly becomes the game. Cosmetics never jump the queue past promised content.
|
|
1014
|
+
19. NEVER delete, empty, move, rename, or overwrite anything you did not create yourself. This folder may hold the player's own reference images, notes, sketches, or an earlier attempt \u2014 files that exist nowhere else and have no undo, no trash, no backup. A non-empty folder is normal and is NEVER something to clean up, and "start clean" is never a reason. That rules out \`rm\`/\`rm -rf\`, \`git clean\`, \`git checkout -- .\`, \`git reset --hard\` over their work, deleting to resolve a conflict or a stuck interactive prompt, and every setup tool's offer to empty a directory (\`--force\`, \`--overwrite\`, "Remove existing files") \u2014 scaffold into a fresh subfolder and copy in instead. You may add files and edit the ones you wrote. If a step genuinely cannot continue without removing something of theirs, STOP and ask, naming the exact files, and wait for a yes \u2014 "it looks like junk" is never that yes. This binds hardest during setup, where it runs fast and automatically before the player has asked for anything at all.
|
|
1013
1015
|
${CONTRACT_END}
|
|
1014
1016
|
`;
|
|
1015
1017
|
var CLAUDE_IMPORT_LINE = "@AGENTS.md";
|
|
@@ -1598,10 +1600,39 @@ async function writeGameConfigFiles(meta, log, cwd = process.cwd()) {
|
|
|
1598
1600
|
}
|
|
1599
1601
|
|
|
1600
1602
|
// src/commands/init.ts
|
|
1603
|
+
var TOOLING_ENTRIES = /* @__PURE__ */ new Set([
|
|
1604
|
+
".git",
|
|
1605
|
+
".genex",
|
|
1606
|
+
".claude",
|
|
1607
|
+
".codex",
|
|
1608
|
+
".cursor",
|
|
1609
|
+
".DS_Store",
|
|
1610
|
+
"node_modules"
|
|
1611
|
+
]);
|
|
1612
|
+
async function listPreexistingEntries(dir) {
|
|
1613
|
+
try {
|
|
1614
|
+
return (await fs10.readdir(dir)).filter((name) => !TOOLING_ENTRIES.has(name)).sort();
|
|
1615
|
+
} catch {
|
|
1616
|
+
return [];
|
|
1617
|
+
}
|
|
1618
|
+
}
|
|
1619
|
+
function warnPreexisting(log, entries) {
|
|
1620
|
+
if (entries.length === 0) return;
|
|
1621
|
+
const shown = entries.slice(0, 6).join(", ");
|
|
1622
|
+
const more = entries.length > 6 ? `, \u2026+${entries.length - 6} more` : "";
|
|
1623
|
+
log.plain("");
|
|
1624
|
+
log.warn(`This folder already had files in it before setup \u2014 they are the player's, not yours:`);
|
|
1625
|
+
log.dim(` ${shown}${more}`);
|
|
1626
|
+
log.dim(" Never clear or overwrite them to start clean. A reference image or note here may");
|
|
1627
|
+
log.dim(" exist nowhere else, and deleting it has no undo \u2014 ask before removing anything.");
|
|
1628
|
+
log.dim(' Answer any "directory is not empty" prompt with Ignore/continue \u2014 never --force,');
|
|
1629
|
+
log.dim(' --overwrite, or "Remove existing files", which empty the whole folder.');
|
|
1630
|
+
}
|
|
1601
1631
|
async function runInit(opts) {
|
|
1602
1632
|
const log = createLogger({ quiet: opts.quiet });
|
|
1603
1633
|
log.plain(c.bold("genex init"));
|
|
1604
1634
|
log.plain("");
|
|
1635
|
+
const preexisting = await listPreexistingEntries(process.cwd());
|
|
1605
1636
|
await cleanupLegacyGlobalSkills(log);
|
|
1606
1637
|
const templatesDir = getTemplatesDir();
|
|
1607
1638
|
const targets = resolveAgentTargets({ dir: opts.dir, agents: opts.agents });
|
|
@@ -1715,12 +1746,13 @@ async function runInit(opts) {
|
|
|
1715
1746
|
const { path: metaPath } = await writeProject(meta);
|
|
1716
1747
|
log.dim(` saved ${c.cyan(metaPath)}`);
|
|
1717
1748
|
await writeGameConfigFiles(meta, log);
|
|
1749
|
+
warnPreexisting(log, preexisting);
|
|
1718
1750
|
log.plain("");
|
|
1719
1751
|
log.success("All set. \u{1F680}");
|
|
1720
1752
|
}
|
|
1721
1753
|
|
|
1722
1754
|
// src/commands/link.ts
|
|
1723
|
-
import
|
|
1755
|
+
import fs11 from "fs/promises";
|
|
1724
1756
|
import path11 from "path";
|
|
1725
1757
|
async function runLink(opts) {
|
|
1726
1758
|
const log = createLogger({ quiet: opts.quiet });
|
|
@@ -1793,20 +1825,20 @@ async function ensureSlugEnv(slug, log, cwd = process.cwd()) {
|
|
|
1793
1825
|
const file = path11.join(cwd, ".env");
|
|
1794
1826
|
let content;
|
|
1795
1827
|
try {
|
|
1796
|
-
content = await
|
|
1828
|
+
content = await fs11.readFile(file, "utf8");
|
|
1797
1829
|
} catch {
|
|
1798
1830
|
return;
|
|
1799
1831
|
}
|
|
1800
1832
|
const re = /^(\s*VITE_GENEX_SLUG=)(.*)$/m;
|
|
1801
1833
|
const m = content.match(re);
|
|
1802
1834
|
if (!m) {
|
|
1803
|
-
await
|
|
1835
|
+
await fs11.appendFile(file, `${content.endsWith("\n") ? "" : "\n"}VITE_GENEX_SLUG=${slug}
|
|
1804
1836
|
`);
|
|
1805
1837
|
log.dim(` added VITE_GENEX_SLUG=${slug} to .env`);
|
|
1806
1838
|
return;
|
|
1807
1839
|
}
|
|
1808
1840
|
if (m[2].trim() === slug) return;
|
|
1809
|
-
await
|
|
1841
|
+
await fs11.writeFile(file, content.replace(re, `$1${slug}`));
|
|
1810
1842
|
log.dim(` updated .env: VITE_GENEX_SLUG=${slug} (was ${m[2].trim()})`);
|
|
1811
1843
|
}
|
|
1812
1844
|
async function fetchOwnProject(apiUrl, token, slug, log) {
|
|
@@ -1985,7 +2017,7 @@ function relTime(iso) {
|
|
|
1985
2017
|
// src/lib/deploy.ts
|
|
1986
2018
|
import { spawn as spawn3 } from "child_process";
|
|
1987
2019
|
import crypto3 from "crypto";
|
|
1988
|
-
import
|
|
2020
|
+
import fs14 from "fs/promises";
|
|
1989
2021
|
import os5 from "os";
|
|
1990
2022
|
import path13 from "path";
|
|
1991
2023
|
|
|
@@ -2250,12 +2282,12 @@ function tierFor(estVramMb) {
|
|
|
2250
2282
|
}
|
|
2251
2283
|
|
|
2252
2284
|
// src/commands/ui.ts
|
|
2253
|
-
import
|
|
2285
|
+
import fs13 from "fs/promises";
|
|
2254
2286
|
import path12 from "path";
|
|
2255
2287
|
import { PNG as PNG2 } from "pngjs";
|
|
2256
2288
|
|
|
2257
2289
|
// src/lib/png-tools.ts
|
|
2258
|
-
import
|
|
2290
|
+
import fs12 from "fs/promises";
|
|
2259
2291
|
import { PNG } from "pngjs";
|
|
2260
2292
|
var ALPHA_TRANSPARENT_MAX = 16;
|
|
2261
2293
|
var isHttpUrl = (s) => /^https?:\/\//i.test(s);
|
|
@@ -2266,12 +2298,12 @@ async function loadPng(input) {
|
|
|
2266
2298
|
if (!res.ok) throw new Error(`Couldn't fetch ${input} (HTTP ${res.status}).`);
|
|
2267
2299
|
buf = Buffer.from(await res.arrayBuffer());
|
|
2268
2300
|
} else {
|
|
2269
|
-
buf = await
|
|
2301
|
+
buf = await fs12.readFile(input);
|
|
2270
2302
|
}
|
|
2271
2303
|
return PNG.sync.read(buf);
|
|
2272
2304
|
}
|
|
2273
2305
|
async function writePng(file, png) {
|
|
2274
|
-
await
|
|
2306
|
+
await fs12.writeFile(file, PNG.sync.write(png));
|
|
2275
2307
|
}
|
|
2276
2308
|
function cropPng(image, box) {
|
|
2277
2309
|
const out = new PNG({ width: box.w, height: box.h });
|
|
@@ -2571,7 +2603,7 @@ async function uiExtract(opts, log) {
|
|
|
2571
2603
|
const dilatePx = opts.dilate ?? 0;
|
|
2572
2604
|
const sheet = await loadPng(input);
|
|
2573
2605
|
const { width: W, height: H, data } = sheet;
|
|
2574
|
-
await
|
|
2606
|
+
await fs13.mkdir(outDir, { recursive: true });
|
|
2575
2607
|
log.plain(c.bold("genex ui extract"));
|
|
2576
2608
|
log.dim(` ${input} (${W}x${H}) \u2192 ${outDir}, ${names.length} names`);
|
|
2577
2609
|
let hasTransparency = false;
|
|
@@ -2819,7 +2851,7 @@ async function uiExtract(opts, log) {
|
|
|
2819
2851
|
defringed
|
|
2820
2852
|
};
|
|
2821
2853
|
const { name: _n, out: _o, ...sidecarBody } = sidecar;
|
|
2822
|
-
await
|
|
2854
|
+
await fs13.writeFile(
|
|
2823
2855
|
outPath.replace(/\.png$/i, "") + ".bbox.json",
|
|
2824
2856
|
JSON.stringify(sidecarBody, null, 2)
|
|
2825
2857
|
);
|
|
@@ -2829,7 +2861,7 @@ async function uiExtract(opts, log) {
|
|
|
2829
2861
|
);
|
|
2830
2862
|
}
|
|
2831
2863
|
const debugPath = path12.join(outDir, "extract-debug.json");
|
|
2832
|
-
await
|
|
2864
|
+
await fs13.writeFile(
|
|
2833
2865
|
debugPath,
|
|
2834
2866
|
JSON.stringify(
|
|
2835
2867
|
{
|
|
@@ -3291,7 +3323,7 @@ async function uiMasks(opts, log) {
|
|
|
3291
3323
|
const registrationTolerance = opts.registrationTolerance ?? 0.04;
|
|
3292
3324
|
const edgeFlushMax = opts.edgeFlushMax ?? 0.04;
|
|
3293
3325
|
const loosened = registrationTolerance > 0.04 || edgeFlushMax > 0.04 || minCoverage < 0.01 || maxCoverage > 0.85;
|
|
3294
|
-
await
|
|
3326
|
+
await fs13.mkdir(outDir, { recursive: true });
|
|
3295
3327
|
log.plain(c.bold("genex ui masks"));
|
|
3296
3328
|
log.dim(` ${input} (${image.width}x${image.height}), ${pairs.length} pair(s) \u2192 ${outDir}`);
|
|
3297
3329
|
const sheetComponents = detectSheetComponents(image, opts.minPixels ?? 2e3);
|
|
@@ -3395,7 +3427,7 @@ async function uiMasks(opts, log) {
|
|
|
3395
3427
|
},
|
|
3396
3428
|
overlay: overlayPath
|
|
3397
3429
|
};
|
|
3398
|
-
await
|
|
3430
|
+
await fs13.writeFile(metaPath, `${JSON.stringify(meta, null, 2)}
|
|
3399
3431
|
`);
|
|
3400
3432
|
results.push(meta);
|
|
3401
3433
|
const fb = converted.bbox;
|
|
@@ -3412,7 +3444,7 @@ async function uiMasks(opts, log) {
|
|
|
3412
3444
|
}
|
|
3413
3445
|
}
|
|
3414
3446
|
const indexPath = path12.join(outDir, "annotated-progress.json");
|
|
3415
|
-
await
|
|
3447
|
+
await fs13.writeFile(indexPath, `${JSON.stringify({ input, loosened, pairs: results }, null, 2)}
|
|
3416
3448
|
`);
|
|
3417
3449
|
log.plain("");
|
|
3418
3450
|
log.success(`Wrote ${pairs.length} frame+mask pair(s) \u2192 ${outDir} (index: ${indexPath}).`);
|
|
@@ -3538,7 +3570,7 @@ async function uiTextColor(opts, log) {
|
|
|
3538
3570
|
};
|
|
3539
3571
|
process.stdout.write(`${JSON.stringify(result, null, 2)}
|
|
3540
3572
|
`);
|
|
3541
|
-
if (opts.out) await
|
|
3573
|
+
if (opts.out) await fs13.writeFile(opts.out, `${JSON.stringify(result, null, 2)}
|
|
3542
3574
|
`);
|
|
3543
3575
|
if (opts.cropPath) log.dim(` crop \u2192 ${opts.cropPath}`);
|
|
3544
3576
|
}
|
|
@@ -3570,7 +3602,7 @@ async function uiTrim(opts, log) {
|
|
|
3570
3602
|
const sidecar = computeBBoxes(trimmed);
|
|
3571
3603
|
const speckleAllowed = !!(speckle && speckle.ratio > SPECKLE_MAX_RATIO);
|
|
3572
3604
|
const sidecarPath = outPath.replace(/\.png$/i, "") + ".bbox.json";
|
|
3573
|
-
await
|
|
3605
|
+
await fs13.writeFile(
|
|
3574
3606
|
sidecarPath,
|
|
3575
3607
|
JSON.stringify(speckleAllowed ? { ...sidecar, speckleAllowed: true } : sidecar, null, 2)
|
|
3576
3608
|
);
|
|
@@ -3842,7 +3874,7 @@ async function walkFiles(dir) {
|
|
|
3842
3874
|
const out = [];
|
|
3843
3875
|
let entries;
|
|
3844
3876
|
try {
|
|
3845
|
-
entries = await
|
|
3877
|
+
entries = await fs13.readdir(dir, { withFileTypes: true });
|
|
3846
3878
|
} catch {
|
|
3847
3879
|
return out;
|
|
3848
3880
|
}
|
|
@@ -3857,7 +3889,7 @@ async function walkFiles(dir) {
|
|
|
3857
3889
|
async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
|
|
3858
3890
|
const viewportFindings = [];
|
|
3859
3891
|
try {
|
|
3860
|
-
const indexHtml = await
|
|
3892
|
+
const indexHtml = await fs13.readFile(path12.join(cwd, "index.html"), "utf8");
|
|
3861
3893
|
if (!/<meta[^>]+name=["']viewport["']/i.test(indexHtml)) {
|
|
3862
3894
|
viewportFindings.push({
|
|
3863
3895
|
kind: "viewport-meta",
|
|
@@ -3873,7 +3905,7 @@ async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
|
|
|
3873
3905
|
}
|
|
3874
3906
|
const absAssets = path12.resolve(cwd, assetDir);
|
|
3875
3907
|
try {
|
|
3876
|
-
if (!(await
|
|
3908
|
+
if (!(await fs13.stat(absAssets)).isDirectory()) {
|
|
3877
3909
|
return viewportFindings.length > 0 ? viewportFindings : null;
|
|
3878
3910
|
}
|
|
3879
3911
|
} catch {
|
|
@@ -3883,7 +3915,7 @@ async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
|
|
|
3883
3915
|
(p) => AUDIT_SRC_EXTS.has(path12.extname(p).toLowerCase())
|
|
3884
3916
|
);
|
|
3885
3917
|
try {
|
|
3886
|
-
for (const name of await
|
|
3918
|
+
for (const name of await fs13.readdir(cwd)) {
|
|
3887
3919
|
const ext = path12.extname(name).toLowerCase();
|
|
3888
3920
|
if (ext === ".html" || ext === ".css") srcFiles.push(path12.join(cwd, name));
|
|
3889
3921
|
}
|
|
@@ -3892,7 +3924,7 @@ async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
|
|
|
3892
3924
|
const sources = [];
|
|
3893
3925
|
for (const p of srcFiles) {
|
|
3894
3926
|
try {
|
|
3895
|
-
sources.push({ rel: path12.relative(cwd, p), text: await
|
|
3927
|
+
sources.push({ rel: path12.relative(cwd, p), text: await fs13.readFile(p, "utf8") });
|
|
3896
3928
|
} catch {
|
|
3897
3929
|
}
|
|
3898
3930
|
}
|
|
@@ -3906,7 +3938,7 @@ async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
|
|
|
3906
3938
|
const metaMatch = /^(.+)\.annotated-progress\.json$/.exec(base);
|
|
3907
3939
|
if (metaMatch) {
|
|
3908
3940
|
try {
|
|
3909
|
-
const meta = JSON.parse(await
|
|
3941
|
+
const meta = JSON.parse(await fs13.readFile(p, "utf8"));
|
|
3910
3942
|
metaByName.set(metaMatch[1], {
|
|
3911
3943
|
cleanCrop: meta.clean?.crop ?? null,
|
|
3912
3944
|
loosened: meta.loosened === true
|
|
@@ -3917,7 +3949,7 @@ async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
|
|
|
3917
3949
|
}
|
|
3918
3950
|
if (base.endsWith(".bbox.json")) {
|
|
3919
3951
|
try {
|
|
3920
|
-
const sidecar = JSON.parse(await
|
|
3952
|
+
const sidecar = JSON.parse(await fs13.readFile(p, "utf8"));
|
|
3921
3953
|
if (sidecar.sheetBBox) bboxByPng.set(base.replace(/\.bbox\.json$/, ".png"), sidecar.sheetBBox);
|
|
3922
3954
|
} catch {
|
|
3923
3955
|
}
|
|
@@ -3963,8 +3995,8 @@ async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
|
|
|
3963
3995
|
let frame;
|
|
3964
3996
|
let mask;
|
|
3965
3997
|
try {
|
|
3966
|
-
frame = PNG2.sync.read(await
|
|
3967
|
-
mask = PNG2.sync.read(await
|
|
3998
|
+
frame = PNG2.sync.read(await fs13.readFile(framePath));
|
|
3999
|
+
mask = PNG2.sync.read(await fs13.readFile(maskPath));
|
|
3968
4000
|
} catch {
|
|
3969
4001
|
continue;
|
|
3970
4002
|
}
|
|
@@ -3983,7 +4015,7 @@ async function collectUiAuditFindings(assetDir, srcDir, cwd = process.cwd()) {
|
|
|
3983
4015
|
if (!referenced(base)) continue;
|
|
3984
4016
|
let png;
|
|
3985
4017
|
try {
|
|
3986
|
-
png = PNG2.sync.read(await
|
|
4018
|
+
png = PNG2.sync.read(await fs13.readFile(p));
|
|
3987
4019
|
} catch {
|
|
3988
4020
|
continue;
|
|
3989
4021
|
}
|
|
@@ -4156,7 +4188,7 @@ async function printUiAuditPreflight(log) {
|
|
|
4156
4188
|
}
|
|
4157
4189
|
async function printPipelineStatePreflight(log, cwd = process.cwd()) {
|
|
4158
4190
|
try {
|
|
4159
|
-
const design = await
|
|
4191
|
+
const design = await fs14.readFile(path13.join(cwd, "DESIGN.md"), "utf8").catch(() => "");
|
|
4160
4192
|
const warnings = [];
|
|
4161
4193
|
if (design.length > 0 && !/##\s*build plan & status/i.test(design)) {
|
|
4162
4194
|
warnings.push(
|
|
@@ -4164,7 +4196,7 @@ async function printPipelineStatePreflight(log, cwd = process.cwd()) {
|
|
|
4164
4196
|
);
|
|
4165
4197
|
}
|
|
4166
4198
|
if (!/player character:/i.test(design)) {
|
|
4167
|
-
const hasCharacter = await
|
|
4199
|
+
const hasCharacter = await fs14.access(path13.join(cwd, "public", "assets", "meshy-character.json")).then(() => true, () => false);
|
|
4168
4200
|
if (!hasCharacter && await loadsPlayerBody(cwd)) {
|
|
4169
4201
|
warnings.push(
|
|
4170
4202
|
`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).`
|
|
@@ -4178,12 +4210,12 @@ async function printPipelineStatePreflight(log, cwd = process.cwd()) {
|
|
|
4178
4210
|
async function loadsPlayerBody(cwd) {
|
|
4179
4211
|
const BODY_LOADERS = /\bloadPlayerCharacter\s*\(|\bloadVrm(?:Clone)?\s*\(/;
|
|
4180
4212
|
try {
|
|
4181
|
-
const entries = await
|
|
4213
|
+
const entries = await fs14.readdir(path13.join(cwd, "src"), { recursive: true });
|
|
4182
4214
|
for (const rel of entries) {
|
|
4183
4215
|
if (rel.includes("node_modules")) continue;
|
|
4184
4216
|
if (rel.split(path13.sep)[0] === "controllers" || rel.startsWith("controllers/")) continue;
|
|
4185
4217
|
if (!/\.(ts|tsx|js|jsx)$/.test(rel)) continue;
|
|
4186
|
-
const text = await
|
|
4218
|
+
const text = await fs14.readFile(path13.join(cwd, "src", rel), "utf8").catch(() => "");
|
|
4187
4219
|
if (BODY_LOADERS.test(text)) return true;
|
|
4188
4220
|
}
|
|
4189
4221
|
} catch {
|
|
@@ -4276,7 +4308,7 @@ async function deployGame(ctx, opts, log) {
|
|
|
4276
4308
|
}
|
|
4277
4309
|
async function hasBuildScript(cwd) {
|
|
4278
4310
|
try {
|
|
4279
|
-
const pkg = JSON.parse(await
|
|
4311
|
+
const pkg = JSON.parse(await fs14.readFile(path13.join(cwd, "package.json"), "utf8"));
|
|
4280
4312
|
return Boolean(pkg.scripts?.build);
|
|
4281
4313
|
} catch {
|
|
4282
4314
|
return false;
|
|
@@ -4285,12 +4317,12 @@ async function hasBuildScript(cwd) {
|
|
|
4285
4317
|
async function collectFiles(root) {
|
|
4286
4318
|
const out = [];
|
|
4287
4319
|
const walk2 = async (dir, prefix) => {
|
|
4288
|
-
for (const e of await
|
|
4320
|
+
for (const e of await fs14.readdir(dir, { withFileTypes: true })) {
|
|
4289
4321
|
const relPath = prefix ? `${prefix}/${e.name}` : e.name;
|
|
4290
4322
|
if (e.isDirectory()) {
|
|
4291
4323
|
if (!EXCLUDE_DIRS.has(e.name)) await walk2(path13.join(dir, e.name), relPath);
|
|
4292
4324
|
} else if (e.isFile() && !isSecretEnvFile(e.name)) {
|
|
4293
|
-
out.push({ relPath, bytes: await
|
|
4325
|
+
out.push({ relPath, bytes: await fs14.readFile(path13.join(dir, e.name)) });
|
|
4294
4326
|
}
|
|
4295
4327
|
}
|
|
4296
4328
|
};
|
|
@@ -4528,7 +4560,7 @@ async function pushWorktree(cwd, pushUrl, managed, log, branch = "main") {
|
|
|
4528
4560
|
log.error("Couldn't save your game's source \u2014 please try again.");
|
|
4529
4561
|
return false;
|
|
4530
4562
|
};
|
|
4531
|
-
const gitDir = await
|
|
4563
|
+
const gitDir = await fs14.mkdtemp(path13.join(os5.tmpdir(), "genex-source-"));
|
|
4532
4564
|
const base = { GIT_DIR: gitDir };
|
|
4533
4565
|
if (urlHasEmbeddedCredentials(pushUrl)) {
|
|
4534
4566
|
base.GIT_CONFIG_COUNT = "1";
|
|
@@ -4543,7 +4575,7 @@ async function pushWorktree(cwd, pushUrl, managed, log, branch = "main") {
|
|
|
4543
4575
|
};
|
|
4544
4576
|
try {
|
|
4545
4577
|
if ((await run("git", ["init", "-q"], base)).code !== 0) return failed();
|
|
4546
|
-
await
|
|
4578
|
+
await fs14.writeFile(
|
|
4547
4579
|
path13.join(gitDir, "info", "exclude"),
|
|
4548
4580
|
// .env* are secrets — never publish them; `!` keeps the non-secret template.
|
|
4549
4581
|
["node_modules/", "dist/", ".git/", ".genex/", ".env", ".env.*", "!.env.example", ""].join("\n")
|
|
@@ -4599,7 +4631,7 @@ async function pushWorktree(cwd, pushUrl, managed, log, branch = "main") {
|
|
|
4599
4631
|
} catch {
|
|
4600
4632
|
return failed();
|
|
4601
4633
|
} finally {
|
|
4602
|
-
await
|
|
4634
|
+
await fs14.rm(gitDir, { recursive: true, force: true }).catch(() => {
|
|
4603
4635
|
});
|
|
4604
4636
|
}
|
|
4605
4637
|
}
|
|
@@ -4635,7 +4667,7 @@ async function fetchPushUrl(ctx, log) {
|
|
|
4635
4667
|
}
|
|
4636
4668
|
async function isDir2(p) {
|
|
4637
4669
|
try {
|
|
4638
|
-
return (await
|
|
4670
|
+
return (await fs14.stat(p)).isDirectory();
|
|
4639
4671
|
} catch {
|
|
4640
4672
|
return false;
|
|
4641
4673
|
}
|
|
@@ -4770,17 +4802,17 @@ async function promoteBuild(apiUrl, projectId, token, log) {
|
|
|
4770
4802
|
}
|
|
4771
4803
|
|
|
4772
4804
|
// src/lib/detect-features.ts
|
|
4773
|
-
import
|
|
4805
|
+
import fs16 from "fs/promises";
|
|
4774
4806
|
import path15 from "path";
|
|
4775
4807
|
|
|
4776
4808
|
// src/lib/generation-ledger.ts
|
|
4777
|
-
import
|
|
4809
|
+
import fs15 from "fs/promises";
|
|
4778
4810
|
import path14 from "path";
|
|
4779
4811
|
var ledgerPath = (cwd) => path14.join(cwd, ".genex", "generations.ndjson");
|
|
4780
4812
|
async function append(cwd, event) {
|
|
4781
4813
|
try {
|
|
4782
|
-
await
|
|
4783
|
-
await
|
|
4814
|
+
await fs15.access(path14.join(cwd, ".genex"));
|
|
4815
|
+
await fs15.appendFile(ledgerPath(cwd), `${JSON.stringify(event)}
|
|
4784
4816
|
`, "utf8");
|
|
4785
4817
|
} catch {
|
|
4786
4818
|
}
|
|
@@ -4788,7 +4820,7 @@ async function append(cwd, event) {
|
|
|
4788
4820
|
async function readLedger(cwd = process.cwd()) {
|
|
4789
4821
|
let raw;
|
|
4790
4822
|
try {
|
|
4791
|
-
raw = await
|
|
4823
|
+
raw = await fs15.readFile(ledgerPath(cwd), "utf8");
|
|
4792
4824
|
} catch {
|
|
4793
4825
|
return [];
|
|
4794
4826
|
}
|
|
@@ -4842,7 +4874,7 @@ async function countFailed(kind, cwd = process.cwd()) {
|
|
|
4842
4874
|
// src/lib/detect-features.ts
|
|
4843
4875
|
async function detectEmbedSdkVersion(cwd = process.cwd()) {
|
|
4844
4876
|
try {
|
|
4845
|
-
const raw = await
|
|
4877
|
+
const raw = await fs16.readFile(path15.join(cwd, "package.json"), "utf8");
|
|
4846
4878
|
const pkg = JSON.parse(raw);
|
|
4847
4879
|
const version = pkg.dependencies?.["@genex-ai/embed-sdk"] ?? pkg.devDependencies?.["@genex-ai/embed-sdk"];
|
|
4848
4880
|
return typeof version === "string" && version ? version : null;
|
|
@@ -4852,7 +4884,7 @@ async function detectEmbedSdkVersion(cwd = process.cwd()) {
|
|
|
4852
4884
|
}
|
|
4853
4885
|
async function detectMultiplayer(cwd = process.cwd()) {
|
|
4854
4886
|
try {
|
|
4855
|
-
const raw = await
|
|
4887
|
+
const raw = await fs16.readFile(path15.join(cwd, "package.json"), "utf8");
|
|
4856
4888
|
const pkg = JSON.parse(raw);
|
|
4857
4889
|
return Boolean(
|
|
4858
4890
|
pkg.dependencies?.["@genex-ai/multiplayer"] ?? pkg.devDependencies?.["@genex-ai/multiplayer"]
|
|
@@ -4864,7 +4896,7 @@ async function detectMultiplayer(cwd = process.cwd()) {
|
|
|
4864
4896
|
async function detectMatchmaking(log, cwd = process.cwd()) {
|
|
4865
4897
|
let pkg;
|
|
4866
4898
|
try {
|
|
4867
|
-
pkg = JSON.parse(await
|
|
4899
|
+
pkg = JSON.parse(await fs16.readFile(path15.join(cwd, "package.json"), "utf8"));
|
|
4868
4900
|
} catch (err) {
|
|
4869
4901
|
log.dim(` (skipping matchmaking \u2014 couldn't read package.json: ${String(err)})`);
|
|
4870
4902
|
return null;
|
|
@@ -4882,7 +4914,7 @@ async function detectMatchmaking(log, cwd = process.cwd()) {
|
|
|
4882
4914
|
var TOUCH_KIT_MARKERS = /controllers\/touch\/|controllers\/character\/touch-joystick|TouchJoystick|VirtualButton|DragZone|RotateOverlay/;
|
|
4883
4915
|
async function detectMobileControls(cwd = process.cwd()) {
|
|
4884
4916
|
try {
|
|
4885
|
-
const raw = await
|
|
4917
|
+
const raw = await fs16.readFile(path15.join(cwd, "package.json"), "utf8");
|
|
4886
4918
|
const pkg = JSON.parse(raw);
|
|
4887
4919
|
if (pkg.genex?.mobileControls === true) return true;
|
|
4888
4920
|
} catch {
|
|
@@ -4890,7 +4922,7 @@ async function detectMobileControls(cwd = process.cwd()) {
|
|
|
4890
4922
|
const srcDir = path15.join(cwd, "src");
|
|
4891
4923
|
let entries;
|
|
4892
4924
|
try {
|
|
4893
|
-
entries = await
|
|
4925
|
+
entries = await fs16.readdir(srcDir, { recursive: true });
|
|
4894
4926
|
} catch {
|
|
4895
4927
|
return false;
|
|
4896
4928
|
}
|
|
@@ -4898,7 +4930,7 @@ async function detectMobileControls(cwd = process.cwd()) {
|
|
|
4898
4930
|
if (rel.includes("node_modules")) continue;
|
|
4899
4931
|
if (!/\.(ts|js|mts|mjs|tsx|jsx)$/.test(rel)) continue;
|
|
4900
4932
|
try {
|
|
4901
|
-
const content = await
|
|
4933
|
+
const content = await fs16.readFile(path15.join(srcDir, rel), "utf8");
|
|
4902
4934
|
if (TOUCH_KIT_MARKERS.test(content)) return true;
|
|
4903
4935
|
} catch {
|
|
4904
4936
|
}
|
|
@@ -4910,7 +4942,7 @@ async function detectGameStateUsage(cwd = process.cwd()) {
|
|
|
4910
4942
|
const srcDir = path15.join(cwd, "src");
|
|
4911
4943
|
let entries;
|
|
4912
4944
|
try {
|
|
4913
|
-
entries = await
|
|
4945
|
+
entries = await fs16.readdir(srcDir, { recursive: true });
|
|
4914
4946
|
} catch {
|
|
4915
4947
|
return false;
|
|
4916
4948
|
}
|
|
@@ -4918,7 +4950,7 @@ async function detectGameStateUsage(cwd = process.cwd()) {
|
|
|
4918
4950
|
if (rel.includes("node_modules")) continue;
|
|
4919
4951
|
if (!/\.(ts|js|mts|mjs|tsx|jsx)$/.test(rel)) continue;
|
|
4920
4952
|
try {
|
|
4921
|
-
const content = await
|
|
4953
|
+
const content = await fs16.readFile(path15.join(srcDir, rel), "utf8");
|
|
4922
4954
|
if (GAME_STATE_CALLS.test(content)) return true;
|
|
4923
4955
|
} catch {
|
|
4924
4956
|
}
|
|
@@ -4970,14 +5002,14 @@ async function detectSurfaceScan(cwd = process.cwd()) {
|
|
|
4970
5002
|
const srcDir = path15.join(cwd, "src");
|
|
4971
5003
|
let entries;
|
|
4972
5004
|
try {
|
|
4973
|
-
entries = await
|
|
5005
|
+
entries = await fs16.readdir(srcDir, { recursive: true });
|
|
4974
5006
|
} catch {
|
|
4975
5007
|
return found;
|
|
4976
5008
|
}
|
|
4977
5009
|
for (const nativeRel of entries) {
|
|
4978
5010
|
if (nativeRel.includes("node_modules")) continue;
|
|
4979
5011
|
if (!/\.(ts|js|mts|mjs|tsx|jsx)$/.test(nativeRel)) continue;
|
|
4980
|
-
const raw = await
|
|
5012
|
+
const raw = await fs16.readFile(path15.join(srcDir, nativeRel), "utf8").catch(() => "");
|
|
4981
5013
|
if (!raw) continue;
|
|
4982
5014
|
const rel = nativeRel.split(path15.sep).join("/");
|
|
4983
5015
|
const content = raw.replace(/\/\*[\s\S]*?\*\//g, (m2) => m2.replace(/[^\n]/g, " ")).replace(/(^|[^:])\/\/[^\n]*/gm, (m2, p1) => p1 + " ".repeat(m2.length - p1.length));
|
|
@@ -5037,12 +5069,12 @@ async function detectGenerationAudit(cwd = process.cwd()) {
|
|
|
5037
5069
|
let haystack = "";
|
|
5038
5070
|
const read = async (file) => {
|
|
5039
5071
|
try {
|
|
5040
|
-
haystack += await
|
|
5072
|
+
haystack += await fs16.readFile(file, "utf8");
|
|
5041
5073
|
} catch {
|
|
5042
5074
|
}
|
|
5043
5075
|
};
|
|
5044
5076
|
try {
|
|
5045
|
-
for (const entry of await
|
|
5077
|
+
for (const entry of await fs16.readdir(cwd, { withFileTypes: true })) {
|
|
5046
5078
|
if (entry.isFile() && /\.(ts|tsx|js|jsx|css|html|json)$/.test(entry.name)) {
|
|
5047
5079
|
await read(path15.join(cwd, entry.name));
|
|
5048
5080
|
}
|
|
@@ -5051,7 +5083,7 @@ async function detectGenerationAudit(cwd = process.cwd()) {
|
|
|
5051
5083
|
}
|
|
5052
5084
|
for (const sub of ["src", "public"]) {
|
|
5053
5085
|
try {
|
|
5054
|
-
const entries = await
|
|
5086
|
+
const entries = await fs16.readdir(path15.join(cwd, sub), { recursive: true });
|
|
5055
5087
|
for (const rel of entries) {
|
|
5056
5088
|
if (rel.includes("node_modules")) continue;
|
|
5057
5089
|
if (!/\.(ts|tsx|js|jsx|css|html|json|txt)$/.test(rel)) continue;
|
|
@@ -5187,7 +5219,7 @@ async function borrowEvidence(meta, cwd) {
|
|
|
5187
5219
|
} catch {
|
|
5188
5220
|
return true;
|
|
5189
5221
|
}
|
|
5190
|
-
const gitConfig = await
|
|
5222
|
+
const gitConfig = await fs16.readFile(path15.join(cwd, ".git", "config"), "utf8").catch(() => "");
|
|
5191
5223
|
for (const m of gitConfig.matchAll(/url\s*=\s*(\S+)/g)) {
|
|
5192
5224
|
try {
|
|
5193
5225
|
const u = new URL(m[1]);
|
|
@@ -5195,7 +5227,7 @@ async function borrowEvidence(meta, cwd) {
|
|
|
5195
5227
|
} catch {
|
|
5196
5228
|
}
|
|
5197
5229
|
}
|
|
5198
|
-
const readme = await
|
|
5230
|
+
const readme = await fs16.readFile(path15.join(cwd, "README.md"), "utf8").catch(() => "");
|
|
5199
5231
|
return readme.includes(host) || /\b(upstream|originally by|ported from|borrowed from)\b/i.test(readme);
|
|
5200
5232
|
}
|
|
5201
5233
|
|
|
@@ -5445,7 +5477,7 @@ async function runPromote(opts) {
|
|
|
5445
5477
|
}
|
|
5446
5478
|
|
|
5447
5479
|
// src/commands/generate.ts
|
|
5448
|
-
import
|
|
5480
|
+
import fs17 from "fs/promises";
|
|
5449
5481
|
import path16 from "path";
|
|
5450
5482
|
import { PNG as PNG4 } from "pngjs";
|
|
5451
5483
|
|
|
@@ -5616,7 +5648,7 @@ var isRemoteRef = (value) => /^(https?:\/\/|data:)/i.test(value);
|
|
|
5616
5648
|
async function inlineLocalImage(filePath, flag) {
|
|
5617
5649
|
let bytes;
|
|
5618
5650
|
try {
|
|
5619
|
-
bytes = await
|
|
5651
|
+
bytes = await fs17.readFile(filePath);
|
|
5620
5652
|
} catch {
|
|
5621
5653
|
return { ok: false, error: `Couldn't read the ${flag} file at ${filePath}.` };
|
|
5622
5654
|
}
|
|
@@ -5822,7 +5854,7 @@ async function runGenerate(kind, opts) {
|
|
|
5822
5854
|
return;
|
|
5823
5855
|
}
|
|
5824
5856
|
try {
|
|
5825
|
-
const bytes = await
|
|
5857
|
+
const bytes = await fs17.readFile(opts.inpaintUrl);
|
|
5826
5858
|
opts = { ...opts, inpaintUrl: `data:image/png;base64,${bytes.toString("base64")}` };
|
|
5827
5859
|
} catch {
|
|
5828
5860
|
log.error(`Couldn't read the --inpaint mask at ${opts.inpaintUrl}.`);
|
|
@@ -5946,7 +5978,7 @@ async function reportGlassTerminal(view, outDir, log, json) {
|
|
|
5946
5978
|
return;
|
|
5947
5979
|
}
|
|
5948
5980
|
await recordTerminal(view.id, "completed", files.map((f) => f.url));
|
|
5949
|
-
await
|
|
5981
|
+
await fs17.mkdir(outDir, { recursive: true });
|
|
5950
5982
|
const solved = [];
|
|
5951
5983
|
for (let i = 0; i < files.length; i++) {
|
|
5952
5984
|
const f = files[i];
|
|
@@ -5979,7 +6011,7 @@ async function reportGlassTerminal(view, outDir, log, json) {
|
|
|
5979
6011
|
continue;
|
|
5980
6012
|
}
|
|
5981
6013
|
const outPath = path16.join(outDir, `glass-${i + 1}.png`);
|
|
5982
|
-
await
|
|
6014
|
+
await fs17.writeFile(outPath, PNG4.sync.write(r.png));
|
|
5983
6015
|
solved.push({
|
|
5984
6016
|
path: outPath,
|
|
5985
6017
|
url: f.url,
|
|
@@ -6573,7 +6605,7 @@ async function toRow(e, v, cwd) {
|
|
|
6573
6605
|
}
|
|
6574
6606
|
|
|
6575
6607
|
// src/commands/controller.ts
|
|
6576
|
-
import
|
|
6608
|
+
import fs19 from "fs/promises";
|
|
6577
6609
|
import path18 from "path";
|
|
6578
6610
|
|
|
6579
6611
|
// ../../packages/meshy-animation-catalog/src/index.ts
|
|
@@ -15704,7 +15736,7 @@ function searchMeshyAnimations(query, options = {}) {
|
|
|
15704
15736
|
}
|
|
15705
15737
|
|
|
15706
15738
|
// src/lib/anims.ts
|
|
15707
|
-
import
|
|
15739
|
+
import fs18 from "fs/promises";
|
|
15708
15740
|
import path17 from "path";
|
|
15709
15741
|
var ANIMS_DEST = path17.join("public", "assets", "anims");
|
|
15710
15742
|
var HIDDEN_TAG = "reference";
|
|
@@ -15734,7 +15766,7 @@ async function runAnims(opts) {
|
|
|
15734
15766
|
const destDir = path17.join(root, ANIMS_DEST);
|
|
15735
15767
|
const gameManifestPath = path17.join(destDir, "manifest.json");
|
|
15736
15768
|
if (opts.reset) {
|
|
15737
|
-
await
|
|
15769
|
+
await fs18.rm(destDir, { recursive: true, force: true });
|
|
15738
15770
|
log.step(`Cleared ${c.cyan(ANIMS_DEST + path17.sep)} (--reset)`);
|
|
15739
15771
|
}
|
|
15740
15772
|
if (selectors.length === 0) {
|
|
@@ -15777,8 +15809,8 @@ async function runAnims(opts) {
|
|
|
15777
15809
|
opts.cacheDir ?? getAnimsCacheDir(),
|
|
15778
15810
|
`${manifest.library}-v${manifest.version}`
|
|
15779
15811
|
);
|
|
15780
|
-
await
|
|
15781
|
-
await
|
|
15812
|
+
await fs18.mkdir(cacheDir, { recursive: true });
|
|
15813
|
+
await fs18.mkdir(destDir, { recursive: true });
|
|
15782
15814
|
const base = getAnimsBase(opts.animsBase);
|
|
15783
15815
|
let installedCount = 0;
|
|
15784
15816
|
let presentCount = 0;
|
|
@@ -15796,9 +15828,9 @@ async function runAnims(opts) {
|
|
|
15796
15828
|
const res = await fetch(base + entry.file);
|
|
15797
15829
|
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
15798
15830
|
const buf = Buffer.from(await res.arrayBuffer());
|
|
15799
|
-
await
|
|
15831
|
+
await fs18.writeFile(cached, buf);
|
|
15800
15832
|
}
|
|
15801
|
-
await
|
|
15833
|
+
await fs18.copyFile(cached, dest);
|
|
15802
15834
|
installedCount++;
|
|
15803
15835
|
addedBytes += entry.bytes;
|
|
15804
15836
|
log.dim(` ${path17.join(ANIMS_DEST, entry.file)} (${formatMb(entry.bytes)})`);
|
|
@@ -15817,7 +15849,7 @@ async function runAnims(opts) {
|
|
|
15817
15849
|
version: manifest.version,
|
|
15818
15850
|
clips: [...union].sort((a, b) => a.localeCompare(b))
|
|
15819
15851
|
};
|
|
15820
|
-
await
|
|
15852
|
+
await fs18.writeFile(gameManifestPath, JSON.stringify(gameManifest, null, 2) + "\n");
|
|
15821
15853
|
log.plain("");
|
|
15822
15854
|
const parts = [`${installedCount} clip${installedCount === 1 ? "" : "s"} installed`];
|
|
15823
15855
|
if (presentCount > 0) parts.push(`${presentCount} already present`);
|
|
@@ -15856,7 +15888,7 @@ async function loadManifest(baseOverride) {
|
|
|
15856
15888
|
} catch {
|
|
15857
15889
|
}
|
|
15858
15890
|
const snapshotPath = path17.join(getTemplatesDir(), "controllers", "assets", "anims-manifest.json");
|
|
15859
|
-
const manifest = JSON.parse(await
|
|
15891
|
+
const manifest = JSON.parse(await fs18.readFile(snapshotPath, "utf8"));
|
|
15860
15892
|
return { manifest, source: "snapshot" };
|
|
15861
15893
|
}
|
|
15862
15894
|
function resolveSelectors(manifest, selectors) {
|
|
@@ -15974,21 +16006,21 @@ function printCatalog(log, manifest, selectors) {
|
|
|
15974
16006
|
}
|
|
15975
16007
|
async function readGameManifest(file) {
|
|
15976
16008
|
try {
|
|
15977
|
-
return JSON.parse(await
|
|
16009
|
+
return JSON.parse(await fs18.readFile(file, "utf8"));
|
|
15978
16010
|
} catch {
|
|
15979
16011
|
return null;
|
|
15980
16012
|
}
|
|
15981
16013
|
}
|
|
15982
16014
|
async function hasSize(file, bytes) {
|
|
15983
16015
|
try {
|
|
15984
|
-
return (await
|
|
16016
|
+
return (await fs18.stat(file)).size === bytes;
|
|
15985
16017
|
} catch {
|
|
15986
16018
|
return false;
|
|
15987
16019
|
}
|
|
15988
16020
|
}
|
|
15989
16021
|
async function exists2(p) {
|
|
15990
16022
|
try {
|
|
15991
|
-
await
|
|
16023
|
+
await fs18.access(p);
|
|
15992
16024
|
return true;
|
|
15993
16025
|
} catch {
|
|
15994
16026
|
return false;
|
|
@@ -16198,8 +16230,8 @@ async function runController(opts) {
|
|
|
16198
16230
|
log.dim(` skipped ${file.rel} (exists \u2014 use --force to overwrite)`);
|
|
16199
16231
|
continue;
|
|
16200
16232
|
}
|
|
16201
|
-
await
|
|
16202
|
-
await
|
|
16233
|
+
await fs19.mkdir(path18.dirname(dest), { recursive: true });
|
|
16234
|
+
await fs19.copyFile(path18.join(srcDir, file.from), dest);
|
|
16203
16235
|
copied++;
|
|
16204
16236
|
log.dim(` ${file.rel}`);
|
|
16205
16237
|
}
|
|
@@ -16285,8 +16317,8 @@ async function installMeshyCharacterManifest(args) {
|
|
|
16285
16317
|
}
|
|
16286
16318
|
assertCompleteMeshyControllerPack(manifest);
|
|
16287
16319
|
const destination = path18.join(args.root, ASSETS_DEST, "meshy-character.json");
|
|
16288
|
-
await
|
|
16289
|
-
await
|
|
16320
|
+
await fs19.mkdir(path18.dirname(destination), { recursive: true });
|
|
16321
|
+
await fs19.writeFile(
|
|
16290
16322
|
destination,
|
|
16291
16323
|
`${JSON.stringify(manifest, null, 2)}
|
|
16292
16324
|
`
|
|
@@ -16425,13 +16457,13 @@ function assertCompleteMeshyControllerPack(manifest) {
|
|
|
16425
16457
|
async function installFallbackAvatar(args) {
|
|
16426
16458
|
const { root, srcDir, log } = args;
|
|
16427
16459
|
const dest = path18.join(root, ASSETS_DEST, "avatar.vrm");
|
|
16428
|
-
await
|
|
16429
|
-
await
|
|
16460
|
+
await fs19.mkdir(path18.dirname(dest), { recursive: true });
|
|
16461
|
+
await fs19.copyFile(path18.join(srcDir, "assets", "default-avatar.vrm"), dest);
|
|
16430
16462
|
log.dim(" public/assets/avatar.vrm (fallback avatar \u2014 bundled CC0 default)");
|
|
16431
16463
|
}
|
|
16432
16464
|
async function exists3(p) {
|
|
16433
16465
|
try {
|
|
16434
|
-
await
|
|
16466
|
+
await fs19.access(p);
|
|
16435
16467
|
return true;
|
|
16436
16468
|
} catch {
|
|
16437
16469
|
return false;
|
|
@@ -17271,7 +17303,7 @@ function rank(items, query) {
|
|
|
17271
17303
|
}
|
|
17272
17304
|
|
|
17273
17305
|
// src/commands/motion.ts
|
|
17274
|
-
import
|
|
17306
|
+
import fs20 from "fs/promises";
|
|
17275
17307
|
import path19 from "path";
|
|
17276
17308
|
|
|
17277
17309
|
// src/lib/motion/npz.ts
|
|
@@ -18523,7 +18555,7 @@ async function motionGen(opts, log) {
|
|
|
18523
18555
|
}
|
|
18524
18556
|
if (opts.constraintsPath !== void 0) {
|
|
18525
18557
|
try {
|
|
18526
|
-
const raw = await
|
|
18558
|
+
const raw = await fs20.readFile(opts.constraintsPath, "utf8");
|
|
18527
18559
|
generationOptions.constraints = JSON.parse(raw);
|
|
18528
18560
|
} catch {
|
|
18529
18561
|
log.error(`Couldn't read the --constraints JSON at ${opts.constraintsPath}.`);
|
|
@@ -18547,9 +18579,9 @@ async function motionGen(opts, log) {
|
|
|
18547
18579
|
async function expandTakes(selectors) {
|
|
18548
18580
|
const out = [];
|
|
18549
18581
|
for (const sel of selectors) {
|
|
18550
|
-
const st = await
|
|
18582
|
+
const st = await fs20.stat(sel).catch(() => null);
|
|
18551
18583
|
if (st?.isDirectory()) {
|
|
18552
|
-
const names = await
|
|
18584
|
+
const names = await fs20.readdir(sel);
|
|
18553
18585
|
for (const n of names.sort()) if (n.endsWith(".npz")) out.push(path19.join(sel, n));
|
|
18554
18586
|
} else if (st?.isFile()) {
|
|
18555
18587
|
out.push(sel);
|
|
@@ -18585,7 +18617,7 @@ async function motionVerify(opts, log) {
|
|
|
18585
18617
|
let gates = DEFAULT_GATES;
|
|
18586
18618
|
if (opts.gatesPath) {
|
|
18587
18619
|
try {
|
|
18588
|
-
gates = mergeGates(DEFAULT_GATES, JSON.parse(await
|
|
18620
|
+
gates = mergeGates(DEFAULT_GATES, JSON.parse(await fs20.readFile(opts.gatesPath, "utf8")));
|
|
18589
18621
|
} catch {
|
|
18590
18622
|
log.error(`Couldn't read the --gates JSON at ${opts.gatesPath}.`);
|
|
18591
18623
|
process.exitCode = 1;
|
|
@@ -18609,7 +18641,7 @@ async function motionVerify(opts, log) {
|
|
|
18609
18641
|
for (const file of files) {
|
|
18610
18642
|
const stem = path19.basename(file).replace(/\.npz$/, "");
|
|
18611
18643
|
try {
|
|
18612
|
-
reports.push(analyzeTake(stem, await
|
|
18644
|
+
reports.push(analyzeTake(stem, await fs20.readFile(file), gates));
|
|
18613
18645
|
} catch (err) {
|
|
18614
18646
|
reports.push({
|
|
18615
18647
|
take: stem,
|
|
@@ -18647,7 +18679,7 @@ async function motionCompile(opts, log) {
|
|
|
18647
18679
|
let cfg = DEFAULT_MOTION_CONFIG;
|
|
18648
18680
|
if (opts.configPath) {
|
|
18649
18681
|
try {
|
|
18650
|
-
const patch = JSON.parse(await
|
|
18682
|
+
const patch = JSON.parse(await fs20.readFile(opts.configPath, "utf8"));
|
|
18651
18683
|
cfg = { ...DEFAULT_MOTION_CONFIG, ...patch, idleLoop: { ...DEFAULT_MOTION_CONFIG.idleLoop, ...patch.idleLoop } };
|
|
18652
18684
|
} catch {
|
|
18653
18685
|
log.error(`Couldn't read the --config JSON at ${opts.configPath}.`);
|
|
@@ -18667,7 +18699,7 @@ async function motionCompile(opts, log) {
|
|
|
18667
18699
|
for (const file of files) {
|
|
18668
18700
|
const stem = path19.basename(file).replace(/\.npz$/, "");
|
|
18669
18701
|
try {
|
|
18670
|
-
inputs.push({ stem, take: loadTake(await
|
|
18702
|
+
inputs.push({ stem, take: loadTake(await fs20.readFile(file)) });
|
|
18671
18703
|
} catch (err) {
|
|
18672
18704
|
log.error(`${stem}: ${err instanceof Error ? err.message : String(err)}`);
|
|
18673
18705
|
process.exitCode = 1;
|
|
@@ -18689,9 +18721,9 @@ async function motionCompile(opts, log) {
|
|
|
18689
18721
|
process.exitCode = 1;
|
|
18690
18722
|
return;
|
|
18691
18723
|
}
|
|
18692
|
-
await
|
|
18724
|
+
await fs20.mkdir(path19.dirname(path19.resolve(opts.out)), { recursive: true });
|
|
18693
18725
|
const json = JSON.stringify(result.data);
|
|
18694
|
-
await
|
|
18726
|
+
await fs20.writeFile(opts.out, json);
|
|
18695
18727
|
if (opts.json) {
|
|
18696
18728
|
writeJson({ out: opts.out, set: setName, tracks: Object.keys(result.data.gaits), bytes: json.length });
|
|
18697
18729
|
return;
|
|
@@ -18727,14 +18759,14 @@ async function motionInstall(opts, log) {
|
|
|
18727
18759
|
try {
|
|
18728
18760
|
for (const rel of files) {
|
|
18729
18761
|
const dest = path19.join(root, MOTION_DEST, rel);
|
|
18730
|
-
const exists4 = await
|
|
18762
|
+
const exists4 = await fs20.access(dest).then(() => true, () => false);
|
|
18731
18763
|
if (!opts.force && exists4) {
|
|
18732
18764
|
skipped++;
|
|
18733
18765
|
log.dim(` skipped ${path19.join(MOTION_DEST, rel)} (exists \u2014 use --force to overwrite)`);
|
|
18734
18766
|
continue;
|
|
18735
18767
|
}
|
|
18736
|
-
await
|
|
18737
|
-
await
|
|
18768
|
+
await fs20.mkdir(path19.dirname(dest), { recursive: true });
|
|
18769
|
+
await fs20.copyFile(path19.join(srcDir, rel), dest);
|
|
18738
18770
|
copied++;
|
|
18739
18771
|
log.dim(` ${path19.join(MOTION_DEST, rel)}`);
|
|
18740
18772
|
}
|
|
@@ -18777,7 +18809,7 @@ async function motionConstraints(opts, log) {
|
|
|
18777
18809
|
}
|
|
18778
18810
|
const doc = directionConstraint(dir, speed, duration);
|
|
18779
18811
|
const out = opts.out ?? "constraints.json";
|
|
18780
|
-
await
|
|
18812
|
+
await fs20.writeFile(out, JSON.stringify(doc));
|
|
18781
18813
|
if (opts.json) {
|
|
18782
18814
|
writeJson({ out, dir: dirName, speed, duration, waypoints: doc[0].frame_indices.length });
|
|
18783
18815
|
return;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@genex-ai/cli-demo",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.4-dev.342",
|
|
4
4
|
"description": "Set up your project's agent workspace (.claude/.codex/.cursor in the game folder), authorize, create a game project, generate AI assets, and publish (genex CLI).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -74,6 +74,23 @@ the game is ready — players should never stare at a black screen. This is a
|
|
|
74
74
|
recommendation, not a rule: even a one-line "Loading…" overlay over the dark
|
|
75
75
|
page background is enough.
|
|
76
76
|
|
|
77
|
+
## The folder's existing files are the user's
|
|
78
|
+
|
|
79
|
+
Whatever was in this folder before setup belongs to the user — reference
|
|
80
|
+
images, notes, sketches, an earlier attempt, files an agent won't recognize.
|
|
81
|
+
They may exist nowhere else, and deleting one has no undo. So: add files, edit
|
|
82
|
+
the ones you created, and never delete, empty, move, rename, or overwrite
|
|
83
|
+
anything you didn't. A non-empty folder is normal and is never something to
|
|
84
|
+
fix, and "start clean" is never a reason.
|
|
85
|
+
|
|
86
|
+
That rules out `rm -rf`, `git clean`, `git checkout -- .`, `git reset --hard`
|
|
87
|
+
over the user's work, and every scaffolder's offer to empty the directory
|
|
88
|
+
(`--force`, `--overwrite`, "Remove existing files") — answer a "directory is
|
|
89
|
+
not empty" prompt with Ignore/continue, or scaffold into a fresh subfolder and
|
|
90
|
+
copy across what's missing. If a step genuinely cannot continue without
|
|
91
|
+
removing something of theirs, stop and ask, naming the exact files, and wait
|
|
92
|
+
for a yes. This is law 19 of the build contract in the project's `AGENTS.md`.
|
|
93
|
+
|
|
77
94
|
## Remixing an existing game
|
|
78
95
|
|
|
79
96
|
If the project folder already contains a game (a remix or any existing
|