@genex-ai/cli-demo 1.25.0-dev.616 → 1.25.1-dev.617
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js
CHANGED
|
@@ -5774,11 +5774,87 @@ async function promoteBuild(apiUrl, projectId, token, log) {
|
|
|
5774
5774
|
}
|
|
5775
5775
|
|
|
5776
5776
|
// src/lib/detect-features.ts
|
|
5777
|
+
import fs16 from "fs/promises";
|
|
5778
|
+
import path15 from "path";
|
|
5779
|
+
|
|
5780
|
+
// src/lib/download-assets.ts
|
|
5777
5781
|
import fs15 from "fs/promises";
|
|
5778
5782
|
import path14 from "path";
|
|
5783
|
+
var RUNG_ROLE = /@\d+$/;
|
|
5784
|
+
function isPrimaryRole(role) {
|
|
5785
|
+
return !RUNG_ROLE.test(role);
|
|
5786
|
+
}
|
|
5787
|
+
function primaryFiles(files) {
|
|
5788
|
+
return files.filter((f) => isPrimaryRole(f.role));
|
|
5789
|
+
}
|
|
5790
|
+
function localAssetStem(kind, prompt, id) {
|
|
5791
|
+
const slug = prompt.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40).replace(/-+$/, "");
|
|
5792
|
+
return `${slug || kind}-${id.slice(0, 8)}`;
|
|
5793
|
+
}
|
|
5794
|
+
function localAssetName(kind, prompt, id, file, multi) {
|
|
5795
|
+
const base = localAssetStem(kind, prompt, id);
|
|
5796
|
+
const role = multi ? `-${file.role.replace(/^(texture|image|character|model|model_anim|model-anim)-/, "")}` : "";
|
|
5797
|
+
const ext = file.ext.replace(/^\./, "") || "bin";
|
|
5798
|
+
return `${base}${role}.${ext}`;
|
|
5799
|
+
}
|
|
5800
|
+
async function downloadAssets(files, opts) {
|
|
5801
|
+
const wanted = primaryFiles(files);
|
|
5802
|
+
const result = { saved: [], failures: [] };
|
|
5803
|
+
if (wanted.length === 0) return result;
|
|
5804
|
+
try {
|
|
5805
|
+
await fs15.mkdir(opts.outDir, { recursive: true });
|
|
5806
|
+
} catch (err) {
|
|
5807
|
+
result.failures.push(
|
|
5808
|
+
`couldn't create ${opts.outDir} (${err instanceof Error ? err.message : String(err)})`
|
|
5809
|
+
);
|
|
5810
|
+
return result;
|
|
5811
|
+
}
|
|
5812
|
+
const multi = wanted.length > 1;
|
|
5813
|
+
for (const file of wanted) {
|
|
5814
|
+
const dest = path14.join(opts.outDir, localAssetName(opts.kind, opts.prompt, opts.id, file, multi));
|
|
5815
|
+
try {
|
|
5816
|
+
const res = await fetch(file.url);
|
|
5817
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
5818
|
+
const buf = Buffer.from(await res.arrayBuffer());
|
|
5819
|
+
await fs15.writeFile(dest, buf);
|
|
5820
|
+
result.saved.push({ role: file.role, path: dest, url: file.url, bytes: buf.byteLength });
|
|
5821
|
+
} catch (err) {
|
|
5822
|
+
result.failures.push(
|
|
5823
|
+
`${file.role}: ${err instanceof Error ? err.message : String(err)} \u2014 the asset is still live at ${file.url}`
|
|
5824
|
+
);
|
|
5825
|
+
}
|
|
5826
|
+
}
|
|
5827
|
+
return result;
|
|
5828
|
+
}
|
|
5829
|
+
function formatBytes(bytes) {
|
|
5830
|
+
if (bytes >= 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
5831
|
+
if (bytes >= 1024) return `${Math.round(bytes / 1024)} KB`;
|
|
5832
|
+
return `${bytes} B`;
|
|
5833
|
+
}
|
|
5834
|
+
var DEFAULT_ASSET_DIR = "./assets";
|
|
5835
|
+
function localDeliveryFor(mode, opts, prompt) {
|
|
5836
|
+
if (mode !== "tools" || opts.noDownload) return void 0;
|
|
5837
|
+
return { outDir: opts.outDir ?? DEFAULT_ASSET_DIR, prompt };
|
|
5838
|
+
}
|
|
5839
|
+
async function undeliveredFiles(files, opts) {
|
|
5840
|
+
const wanted = primaryFiles(files);
|
|
5841
|
+
const multi = wanted.length > 1;
|
|
5842
|
+
const missing = [];
|
|
5843
|
+
for (const file of wanted) {
|
|
5844
|
+
const dest = path14.join(opts.outDir, localAssetName(opts.kind, opts.prompt, opts.id, file, multi));
|
|
5845
|
+
try {
|
|
5846
|
+
await fs15.access(dest);
|
|
5847
|
+
} catch {
|
|
5848
|
+
missing.push(file);
|
|
5849
|
+
}
|
|
5850
|
+
}
|
|
5851
|
+
return missing;
|
|
5852
|
+
}
|
|
5853
|
+
|
|
5854
|
+
// src/lib/detect-features.ts
|
|
5779
5855
|
async function detectEmbedSdkVersion(cwd = process.cwd()) {
|
|
5780
5856
|
try {
|
|
5781
|
-
const raw = await
|
|
5857
|
+
const raw = await fs16.readFile(path15.join(cwd, "package.json"), "utf8");
|
|
5782
5858
|
const pkg = JSON.parse(raw);
|
|
5783
5859
|
const version = pkg.dependencies?.["@genex-ai/embed-sdk"] ?? pkg.devDependencies?.["@genex-ai/embed-sdk"];
|
|
5784
5860
|
return typeof version === "string" && version ? version : null;
|
|
@@ -5788,7 +5864,7 @@ async function detectEmbedSdkVersion(cwd = process.cwd()) {
|
|
|
5788
5864
|
}
|
|
5789
5865
|
async function detectMultiplayer(cwd = process.cwd()) {
|
|
5790
5866
|
try {
|
|
5791
|
-
const raw = await
|
|
5867
|
+
const raw = await fs16.readFile(path15.join(cwd, "package.json"), "utf8");
|
|
5792
5868
|
const pkg = JSON.parse(raw);
|
|
5793
5869
|
return Boolean(
|
|
5794
5870
|
pkg.dependencies?.["@genex-ai/multiplayer"] ?? pkg.devDependencies?.["@genex-ai/multiplayer"]
|
|
@@ -5800,7 +5876,7 @@ async function detectMultiplayer(cwd = process.cwd()) {
|
|
|
5800
5876
|
async function detectMatchmaking(log, cwd = process.cwd()) {
|
|
5801
5877
|
let pkg;
|
|
5802
5878
|
try {
|
|
5803
|
-
pkg = JSON.parse(await
|
|
5879
|
+
pkg = JSON.parse(await fs16.readFile(path15.join(cwd, "package.json"), "utf8"));
|
|
5804
5880
|
} catch (err) {
|
|
5805
5881
|
log.dim(` (skipping matchmaking \u2014 couldn't read package.json: ${String(err)})`);
|
|
5806
5882
|
return null;
|
|
@@ -5818,15 +5894,15 @@ async function detectMatchmaking(log, cwd = process.cwd()) {
|
|
|
5818
5894
|
var TOUCH_KIT_MARKERS = /controllers\/touch\/|controllers\/character\/touch-joystick|TouchJoystick|VirtualButton|DragZone|RotateOverlay/;
|
|
5819
5895
|
async function detectMobileControls(cwd = process.cwd()) {
|
|
5820
5896
|
try {
|
|
5821
|
-
const raw = await
|
|
5897
|
+
const raw = await fs16.readFile(path15.join(cwd, "package.json"), "utf8");
|
|
5822
5898
|
const pkg = JSON.parse(raw);
|
|
5823
5899
|
if (pkg.genex?.mobileControls === true) return true;
|
|
5824
5900
|
} catch {
|
|
5825
5901
|
}
|
|
5826
|
-
const srcDir =
|
|
5902
|
+
const srcDir = path15.join(cwd, "src");
|
|
5827
5903
|
let entries;
|
|
5828
5904
|
try {
|
|
5829
|
-
entries = await
|
|
5905
|
+
entries = await fs16.readdir(srcDir, { recursive: true });
|
|
5830
5906
|
} catch {
|
|
5831
5907
|
return false;
|
|
5832
5908
|
}
|
|
@@ -5834,7 +5910,7 @@ async function detectMobileControls(cwd = process.cwd()) {
|
|
|
5834
5910
|
if (rel.includes("node_modules")) continue;
|
|
5835
5911
|
if (!/\.(ts|js|mts|mjs|tsx|jsx)$/.test(rel)) continue;
|
|
5836
5912
|
try {
|
|
5837
|
-
const content = await
|
|
5913
|
+
const content = await fs16.readFile(path15.join(srcDir, rel), "utf8");
|
|
5838
5914
|
if (TOUCH_KIT_MARKERS.test(content)) return true;
|
|
5839
5915
|
} catch {
|
|
5840
5916
|
}
|
|
@@ -5843,10 +5919,10 @@ async function detectMobileControls(cwd = process.cwd()) {
|
|
|
5843
5919
|
}
|
|
5844
5920
|
var GAME_STATE_CALLS = /savePlayerState|saveWorldState|submitScore/;
|
|
5845
5921
|
async function detectGameStateUsage(cwd = process.cwd()) {
|
|
5846
|
-
const srcDir =
|
|
5922
|
+
const srcDir = path15.join(cwd, "src");
|
|
5847
5923
|
let entries;
|
|
5848
5924
|
try {
|
|
5849
|
-
entries = await
|
|
5925
|
+
entries = await fs16.readdir(srcDir, { recursive: true });
|
|
5850
5926
|
} catch {
|
|
5851
5927
|
return false;
|
|
5852
5928
|
}
|
|
@@ -5854,7 +5930,7 @@ async function detectGameStateUsage(cwd = process.cwd()) {
|
|
|
5854
5930
|
if (rel.includes("node_modules")) continue;
|
|
5855
5931
|
if (!/\.(ts|js|mts|mjs|tsx|jsx)$/.test(rel)) continue;
|
|
5856
5932
|
try {
|
|
5857
|
-
const content = await
|
|
5933
|
+
const content = await fs16.readFile(path15.join(srcDir, rel), "utf8");
|
|
5858
5934
|
if (GAME_STATE_CALLS.test(content)) return true;
|
|
5859
5935
|
} catch {
|
|
5860
5936
|
}
|
|
@@ -5904,20 +5980,20 @@ var LOOP_START = /\b(?:requestAnimationFrame|setAnimationLoop)\s*\((?!\s*null\b)
|
|
|
5904
5980
|
var INIT_EMBED_CALL = /\binitEmbed\s*\(/;
|
|
5905
5981
|
async function detectEmbedBoot(cwd = process.cwd()) {
|
|
5906
5982
|
const found = { awaitsIdentity: [], callsInitEmbed: false, rendererAfterAwait: [], loopAfterAwait: [] };
|
|
5907
|
-
const srcDir =
|
|
5983
|
+
const srcDir = path15.join(cwd, "src");
|
|
5908
5984
|
let entries;
|
|
5909
5985
|
try {
|
|
5910
|
-
entries = await
|
|
5986
|
+
entries = await fs16.readdir(srcDir, { recursive: true });
|
|
5911
5987
|
} catch {
|
|
5912
5988
|
return found;
|
|
5913
5989
|
}
|
|
5914
5990
|
for (const nativeRel of entries) {
|
|
5915
5991
|
if (nativeRel.includes("node_modules")) continue;
|
|
5916
|
-
if (nativeRel.split(
|
|
5992
|
+
if (nativeRel.split(path15.sep)[0] === "controllers") continue;
|
|
5917
5993
|
if (!/\.(ts|js|mts|mjs|tsx|jsx)$/.test(nativeRel)) continue;
|
|
5918
|
-
const raw = await
|
|
5994
|
+
const raw = await fs16.readFile(path15.join(srcDir, nativeRel), "utf8").catch(() => "");
|
|
5919
5995
|
if (!raw) continue;
|
|
5920
|
-
const rel = nativeRel.split(
|
|
5996
|
+
const rel = nativeRel.split(path15.sep).join("/");
|
|
5921
5997
|
const content = blankComments(raw);
|
|
5922
5998
|
if (INIT_EMBED_CALL.test(content)) found.callsInitEmbed = true;
|
|
5923
5999
|
const awaits = [];
|
|
@@ -6012,19 +6088,19 @@ async function detectSurfaceScan(cwd = process.cwd()) {
|
|
|
6012
6088
|
deferredAudioContext: [],
|
|
6013
6089
|
usesThree: false
|
|
6014
6090
|
};
|
|
6015
|
-
const srcDir =
|
|
6091
|
+
const srcDir = path15.join(cwd, "src");
|
|
6016
6092
|
let entries;
|
|
6017
6093
|
try {
|
|
6018
|
-
entries = await
|
|
6094
|
+
entries = await fs16.readdir(srcDir, { recursive: true });
|
|
6019
6095
|
} catch {
|
|
6020
6096
|
return found;
|
|
6021
6097
|
}
|
|
6022
6098
|
for (const nativeRel of entries) {
|
|
6023
6099
|
if (nativeRel.includes("node_modules")) continue;
|
|
6024
6100
|
if (!/\.(ts|js|mts|mjs|tsx|jsx)$/.test(nativeRel)) continue;
|
|
6025
|
-
const raw = await
|
|
6101
|
+
const raw = await fs16.readFile(path15.join(srcDir, nativeRel), "utf8").catch(() => "");
|
|
6026
6102
|
if (!raw) continue;
|
|
6027
|
-
const rel = nativeRel.split(
|
|
6103
|
+
const rel = nativeRel.split(path15.sep).join("/");
|
|
6028
6104
|
const content = blankComments(raw);
|
|
6029
6105
|
const repeatRe = /\.repeat\.set\(\s*(-?\d+(?:\.\d+)?)\s*,\s*(-?\d+(?:\.\d+)?)\s*\)/g;
|
|
6030
6106
|
let m;
|
|
@@ -6116,28 +6192,28 @@ async function detectGenerationAudit(cwd = process.cwd()) {
|
|
|
6116
6192
|
let haystack = "";
|
|
6117
6193
|
const read = async (file) => {
|
|
6118
6194
|
try {
|
|
6119
|
-
const raw = await
|
|
6120
|
-
const ext =
|
|
6195
|
+
const raw = await fs16.readFile(file, "utf8");
|
|
6196
|
+
const ext = path15.extname(file).toLowerCase();
|
|
6121
6197
|
if (ext === ".txt") return;
|
|
6122
6198
|
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;
|
|
6123
6199
|
} catch {
|
|
6124
6200
|
}
|
|
6125
6201
|
};
|
|
6126
6202
|
try {
|
|
6127
|
-
for (const entry of await
|
|
6203
|
+
for (const entry of await fs16.readdir(cwd, { withFileTypes: true })) {
|
|
6128
6204
|
if (entry.isFile() && /\.(ts|tsx|js|jsx|css|html|json)$/.test(entry.name)) {
|
|
6129
|
-
await read(
|
|
6205
|
+
await read(path15.join(cwd, entry.name));
|
|
6130
6206
|
}
|
|
6131
6207
|
}
|
|
6132
6208
|
} catch {
|
|
6133
6209
|
}
|
|
6134
6210
|
for (const sub of ["src", "public"]) {
|
|
6135
6211
|
try {
|
|
6136
|
-
const entries = await
|
|
6212
|
+
const entries = await fs16.readdir(path15.join(cwd, sub), { recursive: true });
|
|
6137
6213
|
for (const rel of entries) {
|
|
6138
6214
|
if (rel.includes("node_modules")) continue;
|
|
6139
6215
|
if (!/\.(ts|tsx|js|jsx|css|html|json|txt)$/.test(rel)) continue;
|
|
6140
|
-
await read(
|
|
6216
|
+
await read(path15.join(cwd, sub, rel));
|
|
6141
6217
|
}
|
|
6142
6218
|
} catch {
|
|
6143
6219
|
}
|
|
@@ -6149,20 +6225,21 @@ async function detectGenerationAudit(cwd = process.cwd()) {
|
|
|
6149
6225
|
});
|
|
6150
6226
|
const now = Date.now();
|
|
6151
6227
|
const stale = (e) => e.status === "queued" && now - Date.parse(e.queuedAt) > UNPICKED_AFTER_MS;
|
|
6228
|
+
const wired = (e) => haystack.includes(e.id) || haystack.includes(localAssetStem(e.kind, e.prompt, e.id));
|
|
6152
6229
|
return {
|
|
6153
|
-
unwired: audited.filter((e) => e.status === "completed" && !
|
|
6154
|
-
unpicked: audited.filter((e) => stale(e) && !
|
|
6230
|
+
unwired: audited.filter((e) => e.status === "completed" && !wired(e)).map(ref),
|
|
6231
|
+
unpicked: audited.filter((e) => stale(e) && !wired(e)).map(ref),
|
|
6155
6232
|
// Wired in but never picked up: the deterministic URL let the agent wire
|
|
6156
6233
|
// the asset before it finished, so nobody ever learned whether it DID
|
|
6157
6234
|
// finish — a provider-side failure (credits, moderation) leaves a dead URL
|
|
6158
6235
|
// that renders as a silently missing asset. Kept separate from `unpicked`
|
|
6159
6236
|
// because the fix is a status check, not wiring.
|
|
6160
|
-
unresolved: audited.filter((e) => stale(e) &&
|
|
6237
|
+
unresolved: audited.filter((e) => stale(e) && wired(e)).map(ref),
|
|
6161
6238
|
// Known-failed AND still wired: the worst case, and it must keep firing
|
|
6162
6239
|
// AFTER the status check converges the ledger to "failed" (otherwise the
|
|
6163
6240
|
// audit self-erases the moment the agent runs the recommended command and
|
|
6164
6241
|
// the dead URL ships silently anyway).
|
|
6165
|
-
deadWired: audited.filter((e) => e.status === "failed" &&
|
|
6242
|
+
deadWired: audited.filter((e) => e.status === "failed" && wired(e)).map(ref),
|
|
6166
6243
|
// One regex covers the HTML/JSX attribute and the DOM-built input alike,
|
|
6167
6244
|
// since `.type = "range"` contains `type = "range"`.
|
|
6168
6245
|
musicNoSlider: audited.some((e) => e.kind === "music" && e.status === "completed") && !/type\s*=\s*["'`]range["'`]/.test(haystack)
|
|
@@ -6307,7 +6384,7 @@ async function borrowEvidence(meta, cwd) {
|
|
|
6307
6384
|
} catch {
|
|
6308
6385
|
return true;
|
|
6309
6386
|
}
|
|
6310
|
-
const gitConfig = await
|
|
6387
|
+
const gitConfig = await fs16.readFile(path15.join(cwd, ".git", "config"), "utf8").catch(() => "");
|
|
6311
6388
|
for (const m of gitConfig.matchAll(/url\s*=\s*(\S+)/g)) {
|
|
6312
6389
|
try {
|
|
6313
6390
|
const u = new URL(m[1]);
|
|
@@ -6315,7 +6392,7 @@ async function borrowEvidence(meta, cwd) {
|
|
|
6315
6392
|
} catch {
|
|
6316
6393
|
}
|
|
6317
6394
|
}
|
|
6318
|
-
const readme = await
|
|
6395
|
+
const readme = await fs16.readFile(path15.join(cwd, "README.md"), "utf8").catch(() => "");
|
|
6319
6396
|
return readme.includes(host) || /\b(upstream|originally by|ported from|borrowed from)\b/i.test(readme);
|
|
6320
6397
|
}
|
|
6321
6398
|
|
|
@@ -6964,63 +7041,6 @@ function ceilingVerdict(input) {
|
|
|
6964
7041
|
return { allow: true };
|
|
6965
7042
|
}
|
|
6966
7043
|
|
|
6967
|
-
// src/lib/download-assets.ts
|
|
6968
|
-
import fs16 from "fs/promises";
|
|
6969
|
-
import path15 from "path";
|
|
6970
|
-
var RUNG_ROLE = /@\d+$/;
|
|
6971
|
-
function isPrimaryRole(role) {
|
|
6972
|
-
return !RUNG_ROLE.test(role);
|
|
6973
|
-
}
|
|
6974
|
-
function primaryFiles(files) {
|
|
6975
|
-
return files.filter((f) => isPrimaryRole(f.role));
|
|
6976
|
-
}
|
|
6977
|
-
function localAssetName(kind, prompt, id, file, multi) {
|
|
6978
|
-
const slug = prompt.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40).replace(/-+$/, "");
|
|
6979
|
-
const base = `${slug || kind}-${id.slice(0, 8)}`;
|
|
6980
|
-
const role = multi ? `-${file.role.replace(/^(texture|image|character|model|model_anim|model-anim)-/, "")}` : "";
|
|
6981
|
-
const ext = file.ext.replace(/^\./, "") || "bin";
|
|
6982
|
-
return `${base}${role}.${ext}`;
|
|
6983
|
-
}
|
|
6984
|
-
async function downloadAssets(files, opts) {
|
|
6985
|
-
const wanted = primaryFiles(files);
|
|
6986
|
-
const result = { saved: [], failures: [] };
|
|
6987
|
-
if (wanted.length === 0) return result;
|
|
6988
|
-
try {
|
|
6989
|
-
await fs16.mkdir(opts.outDir, { recursive: true });
|
|
6990
|
-
} catch (err) {
|
|
6991
|
-
result.failures.push(
|
|
6992
|
-
`couldn't create ${opts.outDir} (${err instanceof Error ? err.message : String(err)})`
|
|
6993
|
-
);
|
|
6994
|
-
return result;
|
|
6995
|
-
}
|
|
6996
|
-
const multi = wanted.length > 1;
|
|
6997
|
-
for (const file of wanted) {
|
|
6998
|
-
const dest = path15.join(opts.outDir, localAssetName(opts.kind, opts.prompt, opts.id, file, multi));
|
|
6999
|
-
try {
|
|
7000
|
-
const res = await fetch(file.url);
|
|
7001
|
-
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
7002
|
-
const buf = Buffer.from(await res.arrayBuffer());
|
|
7003
|
-
await fs16.writeFile(dest, buf);
|
|
7004
|
-
result.saved.push({ role: file.role, path: dest, url: file.url, bytes: buf.byteLength });
|
|
7005
|
-
} catch (err) {
|
|
7006
|
-
result.failures.push(
|
|
7007
|
-
`${file.role}: ${err instanceof Error ? err.message : String(err)} \u2014 the asset is still live at ${file.url}`
|
|
7008
|
-
);
|
|
7009
|
-
}
|
|
7010
|
-
}
|
|
7011
|
-
return result;
|
|
7012
|
-
}
|
|
7013
|
-
function formatBytes(bytes) {
|
|
7014
|
-
if (bytes >= 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
7015
|
-
if (bytes >= 1024) return `${Math.round(bytes / 1024)} KB`;
|
|
7016
|
-
return `${bytes} B`;
|
|
7017
|
-
}
|
|
7018
|
-
var DEFAULT_ASSET_DIR = "./assets";
|
|
7019
|
-
function localDeliveryFor(mode, opts, prompt) {
|
|
7020
|
-
if (mode !== "tools" || opts.noDownload) return void 0;
|
|
7021
|
-
return { outDir: opts.outDir ?? DEFAULT_ASSET_DIR, prompt };
|
|
7022
|
-
}
|
|
7023
|
-
|
|
7024
7044
|
// src/lib/lanes.ts
|
|
7025
7045
|
var LANE_CREDITS = /* @__PURE__ */ new Set(["ok", "exhausted", "unknown"]);
|
|
7026
7046
|
async function fetchLanes(apiUrl, token, timeoutMs = 4e3) {
|
|
@@ -7276,6 +7296,15 @@ function applySkyboxGuard(prompt, raw) {
|
|
|
7276
7296
|
return { prompt: guarded, guarded: true, nouns };
|
|
7277
7297
|
}
|
|
7278
7298
|
var MODEL_TEXTURE_TIERS = ["standard", "detailed", "none"];
|
|
7299
|
+
var LOW_POLY_FACE_LIMIT = { min: 1e3, max: 2e4 };
|
|
7300
|
+
var LOW_POLY_QUAD_FACE_LIMIT = { min: 500, max: 1e4 };
|
|
7301
|
+
var QUAD_FACE_LIMIT_MAX = 15e4;
|
|
7302
|
+
function faceLimitBand(o) {
|
|
7303
|
+
if (o.lowPoly && o.quad) return { ...LOW_POLY_QUAD_FACE_LIMIT, why: "for --low-poly with --quad" };
|
|
7304
|
+
if (o.lowPoly) return { ...LOW_POLY_FACE_LIMIT, why: "for --low-poly" };
|
|
7305
|
+
if (o.quad) return { min: 1e3, max: QUAD_FACE_LIMIT_MAX, why: "for --quad" };
|
|
7306
|
+
return { min: 1e3, max: 2e6, why: "for a raw mesh" };
|
|
7307
|
+
}
|
|
7279
7308
|
function buildGenOptions(kind, opts) {
|
|
7280
7309
|
const options = { ...opts.generationOptions };
|
|
7281
7310
|
if (kind === "model" && opts.imageUrl) options.imageUrl = opts.imageUrl;
|
|
@@ -7286,7 +7315,15 @@ function buildGenOptions(kind, opts) {
|
|
|
7286
7315
|
if (opts.quad) options.quad = true;
|
|
7287
7316
|
if (opts.lowPoly) options.smartLowPoly = true;
|
|
7288
7317
|
if (opts.parts) options.generateParts = true;
|
|
7289
|
-
if (opts.faceLimit !== void 0)
|
|
7318
|
+
if (opts.faceLimit !== void 0) {
|
|
7319
|
+
const band = faceLimitBand({ lowPoly: !!opts.lowPoly, quad: !!opts.quad });
|
|
7320
|
+
if (opts.faceLimit < band.min || opts.faceLimit > band.max) {
|
|
7321
|
+
throw new Error(
|
|
7322
|
+
`--face-limit ${opts.faceLimit} is outside the ${band.min}-${band.max} band ${band.why} \u2014 Tripo refuses anything else for that topology. Drop --face-limit to take ${band.max}, or name a value in the band.`
|
|
7323
|
+
);
|
|
7324
|
+
}
|
|
7325
|
+
options.faceLimit = opts.faceLimit;
|
|
7326
|
+
}
|
|
7290
7327
|
if (opts.autoSize) options.autoSize = true;
|
|
7291
7328
|
}
|
|
7292
7329
|
if (kind === "texture" && opts.terrain) options.terrain = true;
|
|
@@ -7481,7 +7518,7 @@ async function runGenerate(kind, opts) {
|
|
|
7481
7518
|
log.dim(` ${prompt}`);
|
|
7482
7519
|
if (skyboxGuard?.guarded) log.dim(" \u21B3 environment-only guard applied (--raw to disable)");
|
|
7483
7520
|
log.plain("");
|
|
7484
|
-
{
|
|
7521
|
+
if (mode !== "tools") {
|
|
7485
7522
|
const counts = unshippedCounts(await readLedgerRows());
|
|
7486
7523
|
const verdict = ceilingVerdict({ kind, counts, config: ceilingConfig() });
|
|
7487
7524
|
if (!verdict.allow) {
|
|
@@ -8326,6 +8363,7 @@ async function runModelAnimate(opts) {
|
|
|
8326
8363
|
}
|
|
8327
8364
|
|
|
8328
8365
|
// src/commands/wait.ts
|
|
8366
|
+
import path18 from "path";
|
|
8329
8367
|
var TERMINAL2 = /* @__PURE__ */ new Set(["completed", "failed"]);
|
|
8330
8368
|
async function runWait(opts) {
|
|
8331
8369
|
if (opts.all) return runWaitAll(opts);
|
|
@@ -8448,8 +8486,26 @@ async function runWaitAll(opts) {
|
|
|
8448
8486
|
if (e.status === "queued" && v?.status === "completed") landed.push({ kind: e.kind, view: v });
|
|
8449
8487
|
rows.push(await toRow(e, v, cwd));
|
|
8450
8488
|
}
|
|
8489
|
+
const delivered = [];
|
|
8490
|
+
if (!opts.noDownload && await workspaceMode(cwd) === "tools") {
|
|
8491
|
+
for (const e of ledger) {
|
|
8492
|
+
const v = views.get(e.id);
|
|
8493
|
+
if (v?.status !== "completed" || !v.files?.length) continue;
|
|
8494
|
+
const local = localDeliveryFor("tools", opts, e.prompt || e.kind);
|
|
8495
|
+
if (!local) continue;
|
|
8496
|
+
const outDir = path18.join(path18.relative(process.cwd(), cwd) || ".", local.outDir);
|
|
8497
|
+
const target = { kind: e.kind, prompt: local.prompt, id: e.id, outDir };
|
|
8498
|
+
const missing = await undeliveredFiles(v.files, target);
|
|
8499
|
+
if (missing.length === 0) continue;
|
|
8500
|
+
delivered.push({ kind: e.kind, id: e.id, result: await downloadAssets(missing, target) });
|
|
8501
|
+
}
|
|
8502
|
+
}
|
|
8451
8503
|
if (opts.json) {
|
|
8452
|
-
writeJson({
|
|
8504
|
+
writeJson({
|
|
8505
|
+
generations: rows,
|
|
8506
|
+
// Additive: what THIS refresh wrote to disk. Absent when nothing was.
|
|
8507
|
+
...delivered.length > 0 ? { saved: delivered.flatMap((d) => d.result.saved.map((f) => ({ id: d.id, kind: d.kind, ...f }))) } : {}
|
|
8508
|
+
});
|
|
8453
8509
|
return;
|
|
8454
8510
|
}
|
|
8455
8511
|
log.plain(c.bold("genex wait --all"));
|
|
@@ -8484,6 +8540,14 @@ async function runWaitAll(opts) {
|
|
|
8484
8540
|
if (count("running") + count("queued") > 0) {
|
|
8485
8541
|
log.dim(` Attach to any single one with: ${c.cyan("npx genex wait <id>")}`);
|
|
8486
8542
|
}
|
|
8543
|
+
if (delivered.length > 0) {
|
|
8544
|
+
log.plain("");
|
|
8545
|
+
log.plain(` ${c.bold("Picked up")} \u2014 finished generations that were not in your project yet:`);
|
|
8546
|
+
for (const d of delivered) {
|
|
8547
|
+
log.plain(` ${c.dim(`${d.kind} ${d.id}`)}`);
|
|
8548
|
+
reportLocalFiles(d.result, log, false);
|
|
8549
|
+
}
|
|
8550
|
+
}
|
|
8487
8551
|
if (landed.length > 0) {
|
|
8488
8552
|
log.plain("");
|
|
8489
8553
|
log.plain(` ${c.bold("Just landed")} \u2014 how to use each one:`);
|
|
@@ -8525,7 +8589,7 @@ async function toRow(e, v, cwd) {
|
|
|
8525
8589
|
|
|
8526
8590
|
// src/commands/controller.ts
|
|
8527
8591
|
import fs20 from "fs/promises";
|
|
8528
|
-
import
|
|
8592
|
+
import path20 from "path";
|
|
8529
8593
|
|
|
8530
8594
|
// ../../packages/meshy-animation-catalog/src/index.ts
|
|
8531
8595
|
import { createHash } from "crypto";
|
|
@@ -17656,8 +17720,8 @@ function searchMeshyAnimations(query, options = {}) {
|
|
|
17656
17720
|
|
|
17657
17721
|
// src/lib/anims.ts
|
|
17658
17722
|
import fs19 from "fs/promises";
|
|
17659
|
-
import
|
|
17660
|
-
var ANIMS_DEST =
|
|
17723
|
+
import path19 from "path";
|
|
17724
|
+
var ANIMS_DEST = path19.join("public", "assets", "anims");
|
|
17661
17725
|
var HIDDEN_TAG = "reference";
|
|
17662
17726
|
async function runAnims(opts) {
|
|
17663
17727
|
const log = createLogger({ quiet: opts.quiet });
|
|
@@ -17673,7 +17737,7 @@ async function runAnims(opts) {
|
|
|
17673
17737
|
printCatalog(log, manifest, selectors);
|
|
17674
17738
|
return;
|
|
17675
17739
|
}
|
|
17676
|
-
const controllerMarker =
|
|
17740
|
+
const controllerMarker = path19.join(root, "src", "controllers", "character");
|
|
17677
17741
|
if (!await exists2(controllerMarker)) {
|
|
17678
17742
|
log.error(
|
|
17679
17743
|
`No character controller in this game (missing ${c.cyan("src/controllers/character/")}).`
|
|
@@ -17682,11 +17746,11 @@ async function runAnims(opts) {
|
|
|
17682
17746
|
process.exitCode = 1;
|
|
17683
17747
|
return;
|
|
17684
17748
|
}
|
|
17685
|
-
const destDir =
|
|
17686
|
-
const gameManifestPath =
|
|
17749
|
+
const destDir = path19.join(root, ANIMS_DEST);
|
|
17750
|
+
const gameManifestPath = path19.join(destDir, "manifest.json");
|
|
17687
17751
|
if (opts.reset) {
|
|
17688
17752
|
await fs19.rm(destDir, { recursive: true, force: true });
|
|
17689
|
-
log.step(`Cleared ${c.cyan(ANIMS_DEST +
|
|
17753
|
+
log.step(`Cleared ${c.cyan(ANIMS_DEST + path19.sep)} (--reset)`);
|
|
17690
17754
|
}
|
|
17691
17755
|
if (selectors.length === 0) {
|
|
17692
17756
|
const installed = await readGameManifest(gameManifestPath);
|
|
@@ -17724,7 +17788,7 @@ async function runAnims(opts) {
|
|
|
17724
17788
|
}
|
|
17725
17789
|
}
|
|
17726
17790
|
const wanted = [...byName.values()].sort((a, b) => a.name.localeCompare(b.name));
|
|
17727
|
-
const cacheDir =
|
|
17791
|
+
const cacheDir = path19.join(
|
|
17728
17792
|
opts.cacheDir ?? getAnimsCacheDir(),
|
|
17729
17793
|
`${manifest.library}-v${manifest.version}`
|
|
17730
17794
|
);
|
|
@@ -17736,13 +17800,13 @@ async function runAnims(opts) {
|
|
|
17736
17800
|
let addedBytes = 0;
|
|
17737
17801
|
const failures = [];
|
|
17738
17802
|
for (const entry of wanted) {
|
|
17739
|
-
const dest =
|
|
17803
|
+
const dest = path19.join(destDir, entry.file);
|
|
17740
17804
|
if (await hasSize(dest, entry.bytes)) {
|
|
17741
17805
|
presentCount++;
|
|
17742
17806
|
continue;
|
|
17743
17807
|
}
|
|
17744
17808
|
try {
|
|
17745
|
-
const cached =
|
|
17809
|
+
const cached = path19.join(cacheDir, entry.file);
|
|
17746
17810
|
if (!await hasSize(cached, entry.bytes)) {
|
|
17747
17811
|
const res = await fetch(base + entry.file);
|
|
17748
17812
|
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
@@ -17752,7 +17816,7 @@ async function runAnims(opts) {
|
|
|
17752
17816
|
await fs19.copyFile(cached, dest);
|
|
17753
17817
|
installedCount++;
|
|
17754
17818
|
addedBytes += entry.bytes;
|
|
17755
|
-
log.dim(` ${
|
|
17819
|
+
log.dim(` ${path19.join(ANIMS_DEST, entry.file)} (${formatMb(entry.bytes)})`);
|
|
17756
17820
|
} catch (err) {
|
|
17757
17821
|
failures.push(`${entry.name} (${err instanceof Error ? err.message : String(err)})`);
|
|
17758
17822
|
}
|
|
@@ -17774,7 +17838,7 @@ async function runAnims(opts) {
|
|
|
17774
17838
|
if (presentCount > 0) parts.push(`${presentCount} already present`);
|
|
17775
17839
|
if (bundledSkips > 0) parts.push(`${bundledSkips} already bundled in animation-library.glb`);
|
|
17776
17840
|
log.success(
|
|
17777
|
-
`${parts.join(", ")} \u2192 ${c.cyan(ANIMS_DEST +
|
|
17841
|
+
`${parts.join(", ")} \u2192 ${c.cyan(ANIMS_DEST + path19.sep)}${addedBytes > 0 ? ` (+${formatMb(addedBytes)})` : ""}`
|
|
17778
17842
|
);
|
|
17779
17843
|
for (const [selector, entries] of resolved) {
|
|
17780
17844
|
const names = entries.filter((e) => !coreNames.has(e.name)).map((e) => e.name);
|
|
@@ -17806,7 +17870,7 @@ async function loadManifest(baseOverride) {
|
|
|
17806
17870
|
}
|
|
17807
17871
|
} catch {
|
|
17808
17872
|
}
|
|
17809
|
-
const snapshotPath =
|
|
17873
|
+
const snapshotPath = path19.join(getTemplatesDir(), "controllers", "assets", "anims-manifest.json");
|
|
17810
17874
|
const manifest = JSON.parse(await fs19.readFile(snapshotPath, "utf8"));
|
|
17811
17875
|
return { manifest, source: "snapshot" };
|
|
17812
17876
|
}
|
|
@@ -18140,8 +18204,8 @@ var CONTROLLER_FILE_SETS = {
|
|
|
18140
18204
|
]
|
|
18141
18205
|
}
|
|
18142
18206
|
};
|
|
18143
|
-
var CODE_DEST =
|
|
18144
|
-
var ASSETS_DEST =
|
|
18207
|
+
var CODE_DEST = path20.join("src", "controllers");
|
|
18208
|
+
var ASSETS_DEST = path20.join("public", "assets");
|
|
18145
18209
|
async function runController(opts) {
|
|
18146
18210
|
const log = createLogger({ quiet: opts.quiet });
|
|
18147
18211
|
if (opts.kind?.trim() === "anims") {
|
|
@@ -18158,31 +18222,31 @@ async function runController(opts) {
|
|
|
18158
18222
|
process.exitCode = 1;
|
|
18159
18223
|
return;
|
|
18160
18224
|
}
|
|
18161
|
-
const srcDir =
|
|
18225
|
+
const srcDir = path20.join(getTemplatesDir(), "controllers");
|
|
18162
18226
|
const root = opts.cwd ?? process.cwd();
|
|
18163
18227
|
const set = CONTROLLER_FILE_SETS[kind];
|
|
18164
18228
|
log.plain(c.bold(`genex controller ${kind}`));
|
|
18165
18229
|
log.plain("");
|
|
18166
|
-
log.step(`Installing the ${kind} controller into ${c.cyan(CODE_DEST +
|
|
18230
|
+
log.step(`Installing the ${kind} controller into ${c.cyan(CODE_DEST + path20.sep)}`);
|
|
18167
18231
|
const plan = [
|
|
18168
|
-
...set.code.map((rel) => ({ from: rel, rel:
|
|
18232
|
+
...set.code.map((rel) => ({ from: rel, rel: path20.join(CODE_DEST, rel) })),
|
|
18169
18233
|
...set.assets.map((rel) => ({
|
|
18170
18234
|
from: rel,
|
|
18171
|
-
rel:
|
|
18235
|
+
rel: path20.join(ASSETS_DEST, path20.basename(rel))
|
|
18172
18236
|
}))
|
|
18173
18237
|
];
|
|
18174
18238
|
let copied = 0;
|
|
18175
18239
|
let skipped = 0;
|
|
18176
18240
|
try {
|
|
18177
18241
|
for (const file of plan) {
|
|
18178
|
-
const dest =
|
|
18242
|
+
const dest = path20.join(root, file.rel);
|
|
18179
18243
|
if (!opts.force && await exists3(dest)) {
|
|
18180
18244
|
skipped++;
|
|
18181
18245
|
log.dim(` skipped ${file.rel} (exists \u2014 use --force to overwrite)`);
|
|
18182
18246
|
continue;
|
|
18183
18247
|
}
|
|
18184
|
-
await fs20.mkdir(
|
|
18185
|
-
await fs20.copyFile(
|
|
18248
|
+
await fs20.mkdir(path20.dirname(dest), { recursive: true });
|
|
18249
|
+
await fs20.copyFile(path20.join(srcDir, file.from), dest);
|
|
18186
18250
|
copied++;
|
|
18187
18251
|
log.dim(` ${file.rel}`);
|
|
18188
18252
|
}
|
|
@@ -18235,7 +18299,7 @@ async function runController(opts) {
|
|
|
18235
18299
|
for (const line of set.sketch) {
|
|
18236
18300
|
log.dim(` ${line}`);
|
|
18237
18301
|
}
|
|
18238
|
-
if (kind === "character" && !await exists3(
|
|
18302
|
+
if (kind === "character" && !await exists3(path20.join(root, ASSETS_DEST, "meshy-character.json"))) {
|
|
18239
18303
|
log.plain("");
|
|
18240
18304
|
log.plain(
|
|
18241
18305
|
` ${stepOffset + 3}. This game has no generated character yet \u2014 ${c.cyan(
|
|
@@ -18267,8 +18331,8 @@ async function installMeshyCharacterManifest(args) {
|
|
|
18267
18331
|
throw new Error("The API returned an invalid Meshy character manifest.");
|
|
18268
18332
|
}
|
|
18269
18333
|
assertCompleteMeshyControllerPack(manifest);
|
|
18270
|
-
const destination =
|
|
18271
|
-
await fs20.mkdir(
|
|
18334
|
+
const destination = path20.join(args.root, ASSETS_DEST, "meshy-character.json");
|
|
18335
|
+
await fs20.mkdir(path20.dirname(destination), { recursive: true });
|
|
18272
18336
|
await fs20.writeFile(
|
|
18273
18337
|
destination,
|
|
18274
18338
|
`${JSON.stringify(manifest, null, 2)}
|
|
@@ -18414,9 +18478,9 @@ function assertCompleteMeshyControllerPack(manifest) {
|
|
|
18414
18478
|
}
|
|
18415
18479
|
async function installFallbackAvatar(args) {
|
|
18416
18480
|
const { root, srcDir, log } = args;
|
|
18417
|
-
const dest =
|
|
18418
|
-
await fs20.mkdir(
|
|
18419
|
-
await fs20.copyFile(
|
|
18481
|
+
const dest = path20.join(root, ASSETS_DEST, "avatar.vrm");
|
|
18482
|
+
await fs20.mkdir(path20.dirname(dest), { recursive: true });
|
|
18483
|
+
await fs20.copyFile(path20.join(srcDir, "assets", "default-avatar.vrm"), dest);
|
|
18420
18484
|
log.dim(" public/assets/avatar.vrm (fallback avatar \u2014 bundled CC0 default)");
|
|
18421
18485
|
}
|
|
18422
18486
|
async function exists3(p) {
|
|
@@ -18430,7 +18494,7 @@ async function exists3(p) {
|
|
|
18430
18494
|
|
|
18431
18495
|
// src/commands/character.ts
|
|
18432
18496
|
import fs21 from "fs/promises";
|
|
18433
|
-
import
|
|
18497
|
+
import path21 from "path";
|
|
18434
18498
|
function exactAnimation(selector) {
|
|
18435
18499
|
const trimmed = selector.trim();
|
|
18436
18500
|
if (/^-?\d+$/.test(trimmed)) return animationById(Number(trimmed));
|
|
@@ -18510,7 +18574,7 @@ function showAmbiguity(selector, candidates, log, json = false) {
|
|
|
18510
18574
|
}
|
|
18511
18575
|
process.exitCode = 1;
|
|
18512
18576
|
}
|
|
18513
|
-
var INSTALLED_MANIFEST =
|
|
18577
|
+
var INSTALLED_MANIFEST = path21.join("public", "assets", "meshy-character.json");
|
|
18514
18578
|
async function resolveAdoptTarget(selector) {
|
|
18515
18579
|
const trimmed = selector?.trim();
|
|
18516
18580
|
if (trimmed && !trimmed.endsWith(".json")) {
|
|
@@ -18639,7 +18703,7 @@ async function runCharacterImport(opts) {
|
|
|
18639
18703
|
opts,
|
|
18640
18704
|
ctx,
|
|
18641
18705
|
kind: "character",
|
|
18642
|
-
prompt: `Import ${
|
|
18706
|
+
prompt: `Import ${path21.basename(filePath)} as a rigged character`,
|
|
18643
18707
|
createPath: "/api/characters/import",
|
|
18644
18708
|
body,
|
|
18645
18709
|
quote: price,
|
|
@@ -19119,22 +19183,22 @@ async function context2(opts) {
|
|
|
19119
19183
|
const project = await readProject();
|
|
19120
19184
|
return { token, apiUrl: getApiUrl(opts.apiUrl ?? project?.apiUrl) };
|
|
19121
19185
|
}
|
|
19122
|
-
async function readVideo(
|
|
19186
|
+
async function readVideo(path28, log) {
|
|
19123
19187
|
let bytes;
|
|
19124
19188
|
try {
|
|
19125
|
-
bytes = await readFile(
|
|
19189
|
+
bytes = await readFile(path28);
|
|
19126
19190
|
} catch {
|
|
19127
|
-
log.error(`Can't read ${
|
|
19191
|
+
log.error(`Can't read ${path28}.`);
|
|
19128
19192
|
return null;
|
|
19129
19193
|
}
|
|
19130
19194
|
if (bytes.byteLength > MAX_VIDEO_BYTES) {
|
|
19131
|
-
log.error(`${basename(
|
|
19195
|
+
log.error(`${basename(path28)} is ${(bytes.byteLength / 1e6).toFixed(0)} MB \u2014 the limit is ${MAX_VIDEO_BYTES / 1e6} MB.`);
|
|
19132
19196
|
return null;
|
|
19133
19197
|
}
|
|
19134
19198
|
return bytes;
|
|
19135
19199
|
}
|
|
19136
|
-
async function uploadVideo(apiUrl, token, characterId,
|
|
19137
|
-
const contentType = /\.mov$/i.test(
|
|
19200
|
+
async function uploadVideo(apiUrl, token, characterId, path28, bytes, log) {
|
|
19201
|
+
const contentType = /\.mov$/i.test(path28) ? "video/quicktime" : "video/mp4";
|
|
19138
19202
|
const minted = await apiFetch(
|
|
19139
19203
|
`${apiUrl}/api/characters/${encodeURIComponent(characterId)}/motions/video-url`,
|
|
19140
19204
|
{
|
|
@@ -19149,7 +19213,7 @@ async function uploadVideo(apiUrl, token, characterId, path27, bytes, log) {
|
|
|
19149
19213
|
return null;
|
|
19150
19214
|
}
|
|
19151
19215
|
const { uploadUrl, videoUrl } = await minted.json();
|
|
19152
|
-
log.dim(` uploading ${basename(
|
|
19216
|
+
log.dim(` uploading ${basename(path28)} (${(bytes.byteLength / 1e6).toFixed(1)} MB)\u2026`);
|
|
19153
19217
|
const put = await fetch(uploadUrl, {
|
|
19154
19218
|
method: "PUT",
|
|
19155
19219
|
headers: { "Content-Type": contentType, "Content-Length": String(bytes.byteLength) },
|
|
@@ -19466,7 +19530,7 @@ function rank(items, query) {
|
|
|
19466
19530
|
|
|
19467
19531
|
// src/commands/motion.ts
|
|
19468
19532
|
import fs22 from "fs/promises";
|
|
19469
|
-
import
|
|
19533
|
+
import path22 from "path";
|
|
19470
19534
|
|
|
19471
19535
|
// src/lib/motion/npz.ts
|
|
19472
19536
|
import zlib from "zlib";
|
|
@@ -20744,7 +20808,7 @@ async function expandTakes(selectors) {
|
|
|
20744
20808
|
const st = await fs22.stat(sel).catch(() => null);
|
|
20745
20809
|
if (st?.isDirectory()) {
|
|
20746
20810
|
const names = await fs22.readdir(sel);
|
|
20747
|
-
for (const n of names.sort()) if (n.endsWith(".npz")) out.push(
|
|
20811
|
+
for (const n of names.sort()) if (n.endsWith(".npz")) out.push(path22.join(sel, n));
|
|
20748
20812
|
} else if (st?.isFile()) {
|
|
20749
20813
|
out.push(sel);
|
|
20750
20814
|
} else {
|
|
@@ -20801,7 +20865,7 @@ async function motionVerify(opts, log) {
|
|
|
20801
20865
|
}
|
|
20802
20866
|
const reports = [];
|
|
20803
20867
|
for (const file of files) {
|
|
20804
|
-
const stem =
|
|
20868
|
+
const stem = path22.basename(file).replace(/\.npz$/, "");
|
|
20805
20869
|
try {
|
|
20806
20870
|
reports.push(analyzeTake(stem, await fs22.readFile(file), gates));
|
|
20807
20871
|
} catch (err) {
|
|
@@ -20859,7 +20923,7 @@ async function motionCompile(opts, log) {
|
|
|
20859
20923
|
}
|
|
20860
20924
|
const inputs = [];
|
|
20861
20925
|
for (const file of files) {
|
|
20862
|
-
const stem =
|
|
20926
|
+
const stem = path22.basename(file).replace(/\.npz$/, "");
|
|
20863
20927
|
try {
|
|
20864
20928
|
inputs.push({ stem, take: loadTake(await fs22.readFile(file)) });
|
|
20865
20929
|
} catch (err) {
|
|
@@ -20868,7 +20932,7 @@ async function motionCompile(opts, log) {
|
|
|
20868
20932
|
return;
|
|
20869
20933
|
}
|
|
20870
20934
|
}
|
|
20871
|
-
const setName = opts.set ??
|
|
20935
|
+
const setName = opts.set ?? path22.basename(opts.out).replace(/\.json$/, "");
|
|
20872
20936
|
let result;
|
|
20873
20937
|
try {
|
|
20874
20938
|
result = compileSet(inputs, setName, cfg);
|
|
@@ -20883,7 +20947,7 @@ async function motionCompile(opts, log) {
|
|
|
20883
20947
|
process.exitCode = 1;
|
|
20884
20948
|
return;
|
|
20885
20949
|
}
|
|
20886
|
-
await fs22.mkdir(
|
|
20950
|
+
await fs22.mkdir(path22.dirname(path22.resolve(opts.out)), { recursive: true });
|
|
20887
20951
|
const json = JSON.stringify(result.data);
|
|
20888
20952
|
await fs22.writeFile(opts.out, json);
|
|
20889
20953
|
if (opts.json) {
|
|
@@ -20903,9 +20967,9 @@ var MOTION_RUNTIME_FILES = [
|
|
|
20903
20967
|
var MOTION_PRESETS = {
|
|
20904
20968
|
rifle: ["sets/rifle.json", "sets/jumps.json"]
|
|
20905
20969
|
};
|
|
20906
|
-
var MOTION_DEST =
|
|
20970
|
+
var MOTION_DEST = path22.join("src", "motion");
|
|
20907
20971
|
async function motionInstall(opts, log) {
|
|
20908
|
-
const srcDir =
|
|
20972
|
+
const srcDir = path22.join(getTemplatesDir(), "motion");
|
|
20909
20973
|
const root = opts.cwd ?? process.cwd();
|
|
20910
20974
|
const preset = opts.set;
|
|
20911
20975
|
if (preset !== void 0 && !MOTION_PRESETS[preset]) {
|
|
@@ -20916,21 +20980,21 @@ async function motionInstall(opts, log) {
|
|
|
20916
20980
|
const files = [...MOTION_RUNTIME_FILES, ...preset ? MOTION_PRESETS[preset] : []];
|
|
20917
20981
|
log.plain(c.bold(`genex motion install${preset ? ` --set ${preset}` : ""}`));
|
|
20918
20982
|
log.plain("");
|
|
20919
|
-
log.step(`Vendoring the motion runtime into ${c.cyan(MOTION_DEST +
|
|
20983
|
+
log.step(`Vendoring the motion runtime into ${c.cyan(MOTION_DEST + path22.sep)}`);
|
|
20920
20984
|
let copied = 0, skipped = 0;
|
|
20921
20985
|
try {
|
|
20922
20986
|
for (const rel of files) {
|
|
20923
|
-
const dest =
|
|
20987
|
+
const dest = path22.join(root, MOTION_DEST, rel);
|
|
20924
20988
|
const exists5 = await fs22.access(dest).then(() => true, () => false);
|
|
20925
20989
|
if (!opts.force && exists5) {
|
|
20926
20990
|
skipped++;
|
|
20927
|
-
log.dim(` skipped ${
|
|
20991
|
+
log.dim(` skipped ${path22.join(MOTION_DEST, rel)} (exists \u2014 use --force to overwrite)`);
|
|
20928
20992
|
continue;
|
|
20929
20993
|
}
|
|
20930
|
-
await fs22.mkdir(
|
|
20931
|
-
await fs22.copyFile(
|
|
20994
|
+
await fs22.mkdir(path22.dirname(dest), { recursive: true });
|
|
20995
|
+
await fs22.copyFile(path22.join(srcDir, rel), dest);
|
|
20932
20996
|
copied++;
|
|
20933
|
-
log.dim(` ${
|
|
20997
|
+
log.dim(` ${path22.join(MOTION_DEST, rel)}`);
|
|
20934
20998
|
}
|
|
20935
20999
|
} catch (err) {
|
|
20936
21000
|
log.error(`Copy failed: ${String(err)}`);
|
|
@@ -21009,12 +21073,12 @@ async function runMotion(opts) {
|
|
|
21009
21073
|
|
|
21010
21074
|
// src/commands/blender.ts
|
|
21011
21075
|
import fs23 from "fs/promises";
|
|
21012
|
-
import
|
|
21076
|
+
import path23 from "path";
|
|
21013
21077
|
var SUBS2 = ["demo", "exec", "snap", "scene", "import", "export", "reset", "mcp", "serve", "seat", "release"];
|
|
21014
21078
|
var DEFAULT_OUT_DIR = "assets/blender";
|
|
21015
21079
|
async function writeB64(dir, name, b64) {
|
|
21016
21080
|
await fs23.mkdir(dir, { recursive: true });
|
|
21017
|
-
const p =
|
|
21081
|
+
const p = path23.join(dir, name);
|
|
21018
21082
|
await fs23.writeFile(p, Buffer.from(b64, "base64"));
|
|
21019
21083
|
return p;
|
|
21020
21084
|
}
|
|
@@ -21099,7 +21163,7 @@ async function runBlender(opts) {
|
|
|
21099
21163
|
log.plain(rest.join("\n"));
|
|
21100
21164
|
return 1;
|
|
21101
21165
|
}
|
|
21102
|
-
const outDir =
|
|
21166
|
+
const outDir = path23.resolve(opts.outDir ?? DEFAULT_OUT_DIR);
|
|
21103
21167
|
const mode = opts.mode;
|
|
21104
21168
|
if (mode !== void 0 && !isRenderMode(mode)) {
|
|
21105
21169
|
log.error(`Unknown --mode ${mode}. Use one of: ${RENDER_MODES.join(", ")}.`);
|
|
@@ -21139,13 +21203,13 @@ async function runBlender(opts) {
|
|
|
21139
21203
|
return 0;
|
|
21140
21204
|
}
|
|
21141
21205
|
case "export": {
|
|
21142
|
-
const target = opts.out ??
|
|
21206
|
+
const target = opts.out ?? path23.join(outDir, "scene.glb");
|
|
21143
21207
|
const r = await blenderCall(base, "/export", { path: "/tmp/genex-export.glb" });
|
|
21144
21208
|
if (!r.glbBase64) {
|
|
21145
21209
|
log.error(`/export answered with no GLB bytes${r.uploaded ? " (it was uploaded, not inlined)" : ""}`);
|
|
21146
21210
|
return 1;
|
|
21147
21211
|
}
|
|
21148
|
-
await fs23.mkdir(
|
|
21212
|
+
await fs23.mkdir(path23.dirname(target), { recursive: true });
|
|
21149
21213
|
await fs23.writeFile(target, Buffer.from(r.glbBase64, "base64"));
|
|
21150
21214
|
log.success(`Exported ${r.bytes ?? 0} bytes`);
|
|
21151
21215
|
log.plain(` ${c.cyan(target)}`);
|
|
@@ -21184,7 +21248,7 @@ async function runBlender(opts) {
|
|
|
21184
21248
|
log.error(`Can't read ${opts.input}`);
|
|
21185
21249
|
return 1;
|
|
21186
21250
|
}
|
|
21187
|
-
label =
|
|
21251
|
+
label = path23.basename(opts.input);
|
|
21188
21252
|
}
|
|
21189
21253
|
const r = await blenderCall(base, "/exec", { script, ...mode ? { mode } : {}, sheet: { formats: SHEET_FORMATS } });
|
|
21190
21254
|
if (r.stdout?.trim()) log.plain(r.stdout.trimEnd());
|
|
@@ -21305,7 +21369,7 @@ print(f"castle: {n} objects")
|
|
|
21305
21369
|
// src/commands/asset-new.ts
|
|
21306
21370
|
import fs24 from "fs";
|
|
21307
21371
|
import fsp from "fs/promises";
|
|
21308
|
-
import
|
|
21372
|
+
import path24 from "path";
|
|
21309
21373
|
import { pathToFileURL } from "url";
|
|
21310
21374
|
var EXTRA_FILES = [
|
|
21311
21375
|
"genex-asset.example.json",
|
|
@@ -21399,7 +21463,7 @@ Every milestone ends with \`npm run verify\`, then \`npx genex preview --no-buil
|
|
|
21399
21463
|
}
|
|
21400
21464
|
async function runAssetNew(options) {
|
|
21401
21465
|
const log = createLogger();
|
|
21402
|
-
const cwd = options.dir ?
|
|
21466
|
+
const cwd = options.dir ? path24.resolve(options.dir) : process.cwd();
|
|
21403
21467
|
const slug = options.assetSlug;
|
|
21404
21468
|
if (!slug) {
|
|
21405
21469
|
log.error('Usage: genex asset new <slug> [--title "<Name>"] [--dims WxDxH] [--tri-band LOW-HIGH] [--holder "<your name>"] [--out <dir>]');
|
|
@@ -21409,14 +21473,14 @@ async function runAssetNew(options) {
|
|
|
21409
21473
|
log.error(`The slug must be kebab-case (letters, digits, dashes), got: ${slug}`);
|
|
21410
21474
|
return 1;
|
|
21411
21475
|
}
|
|
21412
|
-
const templateDir =
|
|
21476
|
+
const templateDir = path24.join(getTemplatesDir(), "asset-viewer");
|
|
21413
21477
|
if (!fs24.existsSync(templateDir)) {
|
|
21414
21478
|
log.error(`Vendored asset-viewer template not found at ${templateDir} \u2014 reinstall the CLI.`);
|
|
21415
21479
|
return 1;
|
|
21416
21480
|
}
|
|
21417
|
-
const manifestTools = await import(pathToFileURL(
|
|
21481
|
+
const manifestTools = await import(pathToFileURL(path24.join(templateDir, "tools", "emit-manifest.mjs")).href);
|
|
21418
21482
|
const { SHARED_FILES, PARITY_FILENAME, hashSharedFiles, pascalCase } = manifestTools;
|
|
21419
|
-
const lockPath =
|
|
21483
|
+
const lockPath = path24.join(templateDir, "shared-files.sha256.json");
|
|
21420
21484
|
const lock = JSON.parse(await fsp.readFile(lockPath, "utf8"));
|
|
21421
21485
|
const actual = hashSharedFiles(templateDir);
|
|
21422
21486
|
const drifted = SHARED_FILES.filter((rel) => lock.files[rel] !== actual[rel]);
|
|
@@ -21429,7 +21493,7 @@ async function runAssetNew(options) {
|
|
|
21429
21493
|
const triBand = parseBand(options.triBand ?? "500-8000");
|
|
21430
21494
|
const holder = options.holder?.trim() || HOLDER_PLACEHOLDER;
|
|
21431
21495
|
const year = (/* @__PURE__ */ new Date()).getUTCFullYear();
|
|
21432
|
-
const outDir =
|
|
21496
|
+
const outDir = path24.resolve(cwd, options.out ?? slug);
|
|
21433
21497
|
if (fs24.existsSync(outDir) && fs24.readdirSync(outDir).length > 0 && !options.force) {
|
|
21434
21498
|
log.error(`${outDir} already holds files. Nothing was touched \u2014 pass --force to write into it anyway.`);
|
|
21435
21499
|
return 1;
|
|
@@ -21458,24 +21522,24 @@ async function runAssetNew(options) {
|
|
|
21458
21522
|
};
|
|
21459
21523
|
await fsp.mkdir(outDir, { recursive: true });
|
|
21460
21524
|
for (const rel of [...SHARED_FILES, ...EXTRA_FILES]) {
|
|
21461
|
-
const to =
|
|
21462
|
-
await fsp.mkdir(
|
|
21463
|
-
await fsp.copyFile(
|
|
21525
|
+
const to = path24.join(outDir, rel);
|
|
21526
|
+
await fsp.mkdir(path24.dirname(to), { recursive: true });
|
|
21527
|
+
await fsp.copyFile(path24.join(templateDir, rel), to);
|
|
21464
21528
|
}
|
|
21465
|
-
const pkg = fillTemplate(await fsp.readFile(
|
|
21529
|
+
const pkg = fillTemplate(await fsp.readFile(path24.join(templateDir, "package.json"), "utf8"), {
|
|
21466
21530
|
slug,
|
|
21467
21531
|
name,
|
|
21468
21532
|
version
|
|
21469
21533
|
});
|
|
21470
|
-
await fsp.writeFile(
|
|
21471
|
-
await fsp.writeFile(
|
|
21472
|
-
await fsp.writeFile(
|
|
21534
|
+
await fsp.writeFile(path24.join(outDir, "package.json"), pkg, "utf8");
|
|
21535
|
+
await fsp.writeFile(path24.join(outDir, "asset.config.json"), JSON.stringify(config, null, 2) + "\n", "utf8");
|
|
21536
|
+
await fsp.writeFile(path24.join(outDir, ".gitignore"), GITIGNORE, "utf8");
|
|
21473
21537
|
await fsp.writeFile(
|
|
21474
|
-
|
|
21538
|
+
path24.join(outDir, "DESIGN.md"),
|
|
21475
21539
|
designDoc({ name, slug, sizeMeters, triBand, holder }),
|
|
21476
21540
|
"utf8"
|
|
21477
21541
|
);
|
|
21478
|
-
const placeholder = await fsp.readFile(
|
|
21542
|
+
const placeholder = await fsp.readFile(path24.join(templateDir, "src", "asset", "PLACEHOLDER.ts"), "utf8");
|
|
21479
21543
|
const seeded = seedAssetSource(placeholder, {
|
|
21480
21544
|
slug,
|
|
21481
21545
|
name,
|
|
@@ -21486,8 +21550,8 @@ async function runAssetNew(options) {
|
|
|
21486
21550
|
pascalCase
|
|
21487
21551
|
});
|
|
21488
21552
|
const assetFilename = `${slug.replace(/-([a-z0-9])/g, (_, ch) => ch.toUpperCase())}.ts`;
|
|
21489
|
-
await fsp.mkdir(
|
|
21490
|
-
await fsp.writeFile(
|
|
21553
|
+
await fsp.mkdir(path24.join(outDir, "src", "asset"), { recursive: true });
|
|
21554
|
+
await fsp.writeFile(path24.join(outDir, "src", "asset", assetFilename), seeded, "utf8");
|
|
21491
21555
|
const copied = hashSharedFiles(outDir);
|
|
21492
21556
|
const mismatched = SHARED_FILES.filter((rel) => copied[rel] !== lock.files[rel]);
|
|
21493
21557
|
if (mismatched.length) {
|
|
@@ -21495,7 +21559,7 @@ async function runAssetNew(options) {
|
|
|
21495
21559
|
return 1;
|
|
21496
21560
|
}
|
|
21497
21561
|
await fsp.writeFile(
|
|
21498
|
-
|
|
21562
|
+
path24.join(outDir, PARITY_FILENAME),
|
|
21499
21563
|
JSON.stringify(
|
|
21500
21564
|
{
|
|
21501
21565
|
note: "Recorded by `genex asset new`. Gate G13 recomputes these; do not hand-edit the viewer.",
|
|
@@ -21539,11 +21603,11 @@ async function runAssetNew(options) {
|
|
|
21539
21603
|
}
|
|
21540
21604
|
|
|
21541
21605
|
// src/commands/tools.ts
|
|
21542
|
-
import
|
|
21606
|
+
import path27 from "path";
|
|
21543
21607
|
|
|
21544
21608
|
// src/lib/local-install.ts
|
|
21545
21609
|
import fs25 from "fs/promises";
|
|
21546
|
-
import
|
|
21610
|
+
import path25 from "path";
|
|
21547
21611
|
import { spawn as spawn4 } from "child_process";
|
|
21548
21612
|
var CLI_PACKAGE = "@genex-ai/cli-demo";
|
|
21549
21613
|
var FULL_NAME_FALLBACK = `npx ${CLI_PACKAGE}@${CLI_CHANNEL}`;
|
|
@@ -21563,10 +21627,10 @@ async function exists4(p) {
|
|
|
21563
21627
|
}
|
|
21564
21628
|
}
|
|
21565
21629
|
async function detectPackageManager(cwd) {
|
|
21566
|
-
let dir =
|
|
21630
|
+
let dir = path25.resolve(cwd);
|
|
21567
21631
|
for (; ; ) {
|
|
21568
21632
|
try {
|
|
21569
|
-
const raw = await fs25.readFile(
|
|
21633
|
+
const raw = await fs25.readFile(path25.join(dir, "package.json"), "utf8");
|
|
21570
21634
|
const pm = JSON.parse(raw).packageManager;
|
|
21571
21635
|
if (typeof pm === "string") {
|
|
21572
21636
|
const name = pm.split("@")[0];
|
|
@@ -21575,18 +21639,18 @@ async function detectPackageManager(cwd) {
|
|
|
21575
21639
|
} catch {
|
|
21576
21640
|
}
|
|
21577
21641
|
for (const [file, pm] of LOCKFILES) {
|
|
21578
|
-
if (await exists4(
|
|
21642
|
+
if (await exists4(path25.join(dir, file))) return pm;
|
|
21579
21643
|
}
|
|
21580
|
-
const parent =
|
|
21644
|
+
const parent = path25.dirname(dir);
|
|
21581
21645
|
if (parent === dir) return "npm";
|
|
21582
21646
|
dir = parent;
|
|
21583
21647
|
}
|
|
21584
21648
|
}
|
|
21585
21649
|
async function findLocalCli(cwd) {
|
|
21586
|
-
let dir =
|
|
21650
|
+
let dir = path25.resolve(cwd);
|
|
21587
21651
|
for (; ; ) {
|
|
21588
|
-
if (await exists4(
|
|
21589
|
-
const parent =
|
|
21652
|
+
if (await exists4(path25.join(dir, "node_modules", CLI_PACKAGE, "package.json"))) return dir;
|
|
21653
|
+
const parent = path25.dirname(dir);
|
|
21590
21654
|
if (parent === dir) return null;
|
|
21591
21655
|
dir = parent;
|
|
21592
21656
|
}
|
|
@@ -21604,7 +21668,7 @@ function installArgs(pm, spec) {
|
|
|
21604
21668
|
}
|
|
21605
21669
|
}
|
|
21606
21670
|
function manifestName(cwd) {
|
|
21607
|
-
const slug =
|
|
21671
|
+
const slug = path25.basename(path25.resolve(cwd)).toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 100);
|
|
21608
21672
|
return slug || "genex-tools-workspace";
|
|
21609
21673
|
}
|
|
21610
21674
|
function isSourceRun(moduleUrl = import.meta.url) {
|
|
@@ -21662,10 +21726,10 @@ async function ensureLocalCli(log, cwd = process.cwd(), opts = {}) {
|
|
|
21662
21726
|
return;
|
|
21663
21727
|
}
|
|
21664
21728
|
const pm = await detectPackageManager(cwd);
|
|
21665
|
-
const hadManifest = await exists4(
|
|
21729
|
+
const hadManifest = await exists4(path25.join(cwd, "package.json"));
|
|
21666
21730
|
if (!hadManifest) {
|
|
21667
21731
|
await fs25.writeFile(
|
|
21668
|
-
|
|
21732
|
+
path25.join(cwd, "package.json"),
|
|
21669
21733
|
JSON.stringify({ name: manifestName(cwd), private: true }, null, 2) + "\n"
|
|
21670
21734
|
);
|
|
21671
21735
|
await ensureIgnored(cwd, "node_modules/");
|
|
@@ -21685,7 +21749,7 @@ async function ensureLocalCli(log, cwd = process.cwd(), opts = {}) {
|
|
|
21685
21749
|
}
|
|
21686
21750
|
}
|
|
21687
21751
|
async function ensureIgnored(dir, entry) {
|
|
21688
|
-
const file =
|
|
21752
|
+
const file = path25.join(dir, ".gitignore");
|
|
21689
21753
|
let content = "";
|
|
21690
21754
|
try {
|
|
21691
21755
|
content = await fs25.readFile(file, "utf8");
|
|
@@ -21698,7 +21762,7 @@ async function ensureIgnored(dir, entry) {
|
|
|
21698
21762
|
}
|
|
21699
21763
|
|
|
21700
21764
|
// src/commands/doctor.ts
|
|
21701
|
-
import
|
|
21765
|
+
import path26 from "path";
|
|
21702
21766
|
var LANE_ORDER = [
|
|
21703
21767
|
"model",
|
|
21704
21768
|
"image",
|
|
@@ -21979,7 +22043,7 @@ async function fetchLegalStatus(apiUrl, token) {
|
|
|
21979
22043
|
}
|
|
21980
22044
|
async function firstSkillsMarker() {
|
|
21981
22045
|
for (const target of resolveAgentTargets()) {
|
|
21982
|
-
const marker = await readSkillsMarker(
|
|
22046
|
+
const marker = await readSkillsMarker(path26.join(target.baseDir, "skills"));
|
|
21983
22047
|
if (marker) return marker;
|
|
21984
22048
|
}
|
|
21985
22049
|
return null;
|
|
@@ -22018,8 +22082,8 @@ async function runTools(opts) {
|
|
|
22018
22082
|
let totalNew = 0;
|
|
22019
22083
|
let totalUpdated = 0;
|
|
22020
22084
|
for (const t of targets) {
|
|
22021
|
-
const dest =
|
|
22022
|
-
const { copied, updated } = await copyTemplates(
|
|
22085
|
+
const dest = path27.join(t.baseDir, "skills");
|
|
22086
|
+
const { copied, updated } = await copyTemplates(path27.join(templatesDir, "skills"), dest, {
|
|
22023
22087
|
filter: (rel) => skillFamilyFilter("tools")(`skills/${rel}`)
|
|
22024
22088
|
});
|
|
22025
22089
|
await pruneRemovedSkills(dest, log);
|
|
@@ -22316,8 +22380,12 @@ ${c.bold("Options for the generators (`model` `sfx` `music` `voice` `texture` `i
|
|
|
22316
22380
|
--geometry <tier> (model) standard (default) | detailed (+20 credits, hero pieces).
|
|
22317
22381
|
--quad (model) quad-dominant mesh (+5; face limit \u2264150000).
|
|
22318
22382
|
--low-poly (model) smart low-poly topology (+10) \u2014 game-ready meshes.
|
|
22383
|
+
Holds --face-limit to 1000-20000 (500-10000 with --quad);
|
|
22384
|
+
omit it to take 20000. Runs a post-process after the mesh:
|
|
22385
|
+
allow up to 30 minutes.
|
|
22319
22386
|
--parts (model) separated, named parts at generation (+20).
|
|
22320
|
-
--face-limit <n> (model) cap on the raw mesh, 1000-2000000 (default 150000
|
|
22387
|
+
--face-limit <n> (model) cap on the raw mesh, 1000-2000000 (default 150000;
|
|
22388
|
+
see --low-poly for its band).
|
|
22321
22389
|
--auto-size (model) scale to real-world metres by AI estimate.
|
|
22322
22390
|
--granularity <g> (model segment) part granularity: simple | balanced |
|
|
22323
22391
|
detailed (default balanced).
|
|
@@ -23303,8 +23371,8 @@ function applyValueFlag(options, flag, value) {
|
|
|
23303
23371
|
break;
|
|
23304
23372
|
case "--face-limit": {
|
|
23305
23373
|
const n = Number(value);
|
|
23306
|
-
if (!Number.isInteger(n) || n <
|
|
23307
|
-
throw new Error(`Invalid --face-limit value: ${value} (expected 1000-2000000)`);
|
|
23374
|
+
if (!Number.isInteger(n) || n < 500 || n > 2e6) {
|
|
23375
|
+
throw new Error(`Invalid --face-limit value: ${value} (expected 1000-2000000; 500-10000 with --low-poly --quad)`);
|
|
23308
23376
|
}
|
|
23309
23377
|
options.faceLimit = n;
|
|
23310
23378
|
break;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@genex-ai/cli-demo",
|
|
3
|
-
"version": "1.25.
|
|
3
|
+
"version": "1.25.1-dev.617",
|
|
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": {
|
|
@@ -283,7 +283,9 @@ scene is a ghost: players and objects pass straight through it.
|
|
|
283
283
|
the camera sits on; never for a crate), `--quad` (+5, quad-dominant mesh
|
|
284
284
|
for anything you will deform or edit further; face limit ≤150000),
|
|
285
285
|
`--low-poly` (+10, smart low-poly topology — the game-ready choice for
|
|
286
|
-
props that appear in numbers
|
|
286
|
+
props that appear in numbers; it holds `--face-limit` to 1000-20000,
|
|
287
|
+
500-10000 with `--quad` — omit the flag to take 20000 — and runs a
|
|
288
|
+
post-process after the mesh, so allow up to 30 minutes), `--parts` (+20, separated named parts at
|
|
287
289
|
generation — cheaper than `model segment` when you know up front you need
|
|
288
290
|
doors, wheels, magazines), `--face-limit <n>` (1000-2000000, default
|
|
289
291
|
150000; the raw cap — the game still loads the @2048/@1024 rungs),
|
|
@@ -42,14 +42,14 @@ npx genex model rig <model-id> --type quadruped
|
|
|
42
42
|
npx genex model animate <rig-id> --preset walk,run
|
|
43
43
|
```
|
|
44
44
|
|
|
45
|
-
- **`segment`** — one GLB
|
|
45
|
+
- **`segment`** — one GLB split into addressable parts: doors, turrets, magazines, destructibles. `--granularity simple|balanced|detailed`. The parts are named by index (`tripo_part_0`, `tripo_part_1`, …), not by what they are — find the one you want by its bounds (the sails are the part with the widest extent, the door the lowest thin one), then keep that index in your manifest.
|
|
46
46
|
- **`rig`** — a skeleton for any mesh, across 7 body plans: `biped`, `quadruped`, `hexapod`, `octopod`, `avian`, `serpentine`, `aquatic`. The plan is auto-detected; `--type` picks it. A mesh that cannot be rigged is refused and refunded before the paid step.
|
|
47
47
|
- **`animate`** — retarget ready-made clips onto a rig, billed per clip. biped: `idle|walk|run|dive|climb|jump|slash|shoot|hurt|fall|turn`; quadruped/hexapod/octopod: `walk`; serpentine/aquatic: `march`.
|
|
48
48
|
|
|
49
49
|
## Options
|
|
50
50
|
|
|
51
51
|
- `--image <path|url>` — build the model from a reference image (local file ≤ 4 MB, or a previous generation's URL). The prompt becomes optional.
|
|
52
|
-
- **Quality knobs** (Tripo H3.1, each priced in the quote — pick per asset, say it in one line): `--texture standard|detailed|none` (detailed default, +10 over standard; none = geometry only), `--geometry detailed` (+20, hero pieces only), `--quad` (+5, for meshes you will edit; face limit ≤150000), `--low-poly` (+10, game-ready topology for props in numbers), `--parts` (+20, named parts at generation), `--face-limit <n>` (1000-2000000, default 150000), `--auto-size` (real-world metres).
|
|
52
|
+
- **Quality knobs** (Tripo H3.1, each priced in the quote — pick per asset, say it in one line): `--texture standard|detailed|none` (detailed default, +10 over standard; none = geometry only), `--geometry detailed` (+20, hero pieces only), `--quad` (+5, for meshes you will edit; face limit ≤150000), `--low-poly` (+10, game-ready topology for props in numbers — it holds `--face-limit` to 1000-20000, 500-10000 with `--quad`; omit the flag to take 20000; it runs a post-process after the mesh, so allow up to 30 minutes), `--parts` (+20, named parts at generation), `--face-limit <n>` (1000-2000000, default 150000; see `--low-poly` for its band), `--auto-size` (real-world metres).
|
|
53
53
|
- `--out-dir <dir>` — where the file lands (default `./assets`).
|
|
54
54
|
- `--no-download` — print the URL only.
|
|
55
55
|
- `--no-wait` — enqueue and return; pick it up with `npx genex wait <id>`.
|
|
@@ -65,9 +65,21 @@ Live prices and your balance: `npx genex doctor`.
|
|
|
65
65
|
|
|
66
66
|
Models take the longest of any lane. `--no-wait` is the normal way to run
|
|
67
67
|
several at once: enqueue them all, keep building, then `npx genex wait --all`
|
|
68
|
-
for one status line each
|
|
69
|
-
|
|
70
|
-
|
|
68
|
+
for one status line each — in a tools workspace it also downloads every
|
|
69
|
+
finished model that is not in `./assets` yet, so one call after a break (or a
|
|
70
|
+
restarted session) delivers the whole batch. `npx genex wait <id>` picks one
|
|
71
|
+
up. **Re-running the model command bills a NEW model** — never use it as a
|
|
72
|
+
status check. Five or so in flight at a time is the provider's comfortable
|
|
73
|
+
concurrency; a burst beyond that waits on the server side rather than failing.
|
|
74
|
+
|
|
75
|
+
## Placing a model
|
|
76
|
+
|
|
77
|
+
A generated GLB has no shared "front": one building's door faces −x, the next
|
|
78
|
+
one's +z. Do not guess and do not spend a render per side — read the mesh once
|
|
79
|
+
on load (bounding box, and where the detail is: the door, the counter, the
|
|
80
|
+
opening) or check it in a viewer, then record a per-model `front` (a yaw in
|
|
81
|
+
your manifest) beside its path and apply it when you place it. Ask for a facing
|
|
82
|
+
in the prompt too (`"…front toward +Z"`) — it helps, it does not guarantee.
|
|
71
83
|
|
|
72
84
|
## Troubleshooting
|
|
73
85
|
|