@genex-ai/cli-demo 1.33.0-dev.690 → 1.34.0-dev.692
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
|
@@ -1925,7 +1925,8 @@ async function readLedger(cwd = process.cwd()) {
|
|
|
1925
1925
|
prompt: typeof e.prompt === "string" ? e.prompt : "",
|
|
1926
1926
|
...typeof e.tail === "string" ? { promptTail: e.tail } : {},
|
|
1927
1927
|
queuedAt: typeof e.at === "string" ? e.at : "",
|
|
1928
|
-
status: "queued"
|
|
1928
|
+
status: "queued",
|
|
1929
|
+
...typeof e.out === "string" && e.out ? { outDir: e.out } : {}
|
|
1929
1930
|
});
|
|
1930
1931
|
}
|
|
1931
1932
|
} else if (e.t === "done" && typeof e.id === "string") {
|
|
@@ -1949,7 +1950,8 @@ async function recordQueued(id, kind, prompt, cwd = process.cwd(), extra = {}) {
|
|
|
1949
1950
|
prompt: prompt.slice(0, 120),
|
|
1950
1951
|
...prompt.length > 120 ? { tail: prompt.slice(-120) } : {},
|
|
1951
1952
|
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1952
|
-
...typeof credits === "number" && Number.isFinite(credits) && credits >= 0 ? { credits } : {}
|
|
1953
|
+
...typeof credits === "number" && Number.isFinite(credits) && credits >= 0 ? { credits } : {},
|
|
1954
|
+
...typeof extra.outDir === "string" && extra.outDir.trim() ? { out: extra.outDir.trim() } : {}
|
|
1953
1955
|
});
|
|
1954
1956
|
}
|
|
1955
1957
|
async function recordTerminal(id, status, urls, cwd = process.cwd()) {
|
|
@@ -2018,9 +2020,10 @@ function localAssetStem(kind, prompt, id) {
|
|
|
2018
2020
|
}
|
|
2019
2021
|
function localAssetName(kind, prompt, id, file, multi) {
|
|
2020
2022
|
const base = localAssetStem(kind, prompt, id);
|
|
2021
|
-
const
|
|
2023
|
+
const label = file.label ? `-${file.label.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 60)}` : "";
|
|
2024
|
+
const role = multi && !label ? `-${file.role.replace(/^(texture|image|character|model|model_anim|model-anim)-/, "")}` : "";
|
|
2022
2025
|
const ext = file.ext.replace(/^\./, "") || "bin";
|
|
2023
|
-
return `${base}${role}.${ext}`;
|
|
2026
|
+
return `${base}${label}${role}.${ext}`;
|
|
2024
2027
|
}
|
|
2025
2028
|
async function downloadAssets(files, opts) {
|
|
2026
2029
|
const wanted = primaryFiles(files);
|
|
@@ -7981,6 +7984,15 @@ function oneLineError(error, max = RAW_ERROR_MAX_CHARS) {
|
|
|
7981
7984
|
}
|
|
7982
7985
|
function describeGenerationFailure(kind, error) {
|
|
7983
7986
|
if (!isProviderBillingError(error)) {
|
|
7987
|
+
if (error && /both failed native controller QA/.test(error)) {
|
|
7988
|
+
return {
|
|
7989
|
+
billing: false,
|
|
7990
|
+
lines: [
|
|
7991
|
+
c.red(error),
|
|
7992
|
+
` For a rigged body from any mesh: ${c.cyan('npx genex model "<prompt>" --face-limit 20000')} then ${c.cyan("npx genex character import <that .glb>")} \u2014 Uthana auto-rigs it, and \`character animate\` gives it every move.`
|
|
7993
|
+
]
|
|
7994
|
+
};
|
|
7995
|
+
}
|
|
7984
7996
|
return { billing: false, lines: error ? [c.red(error)] : [] };
|
|
7985
7997
|
}
|
|
7986
7998
|
return {
|
|
@@ -8021,11 +8033,16 @@ function unshippedCounts(rows) {
|
|
|
8021
8033
|
break;
|
|
8022
8034
|
}
|
|
8023
8035
|
}
|
|
8036
|
+
const failed = /* @__PURE__ */ new Set();
|
|
8037
|
+
for (const r of rows) {
|
|
8038
|
+
if (r?.t === "done" && r.status === "failed" && typeof r.id === "string") failed.add(r.id);
|
|
8039
|
+
}
|
|
8024
8040
|
let total = 0;
|
|
8025
8041
|
let character = 0;
|
|
8026
8042
|
for (let i = lastShip + 1; i < rows.length; i += 1) {
|
|
8027
8043
|
const r = rows[i];
|
|
8028
8044
|
if (r?.t !== "q") continue;
|
|
8045
|
+
if (typeof r.id === "string" && failed.has(r.id)) continue;
|
|
8029
8046
|
total += 1;
|
|
8030
8047
|
if (typeof r.kind === "string" && CHARACTER_KINDS.has(r.kind)) character += 1;
|
|
8031
8048
|
}
|
|
@@ -8737,7 +8754,10 @@ async function runGenerate(kind, opts) {
|
|
|
8737
8754
|
process.exitCode = 1;
|
|
8738
8755
|
return;
|
|
8739
8756
|
}
|
|
8740
|
-
await recordQueued(id, kind, prompt, void 0, {
|
|
8757
|
+
await recordQueued(id, kind, prompt, void 0, {
|
|
8758
|
+
credits: creditsQuoted ?? opts.quotedCredits,
|
|
8759
|
+
...opts.outDir ? { outDir: opts.outDir } : {}
|
|
8760
|
+
});
|
|
8741
8761
|
if (opts.noWait) {
|
|
8742
8762
|
if (opts.json) {
|
|
8743
8763
|
writeJson({
|
|
@@ -8862,14 +8882,18 @@ async function reportGlassTerminal(view, outDir, log, json) {
|
|
|
8862
8882
|
}
|
|
8863
8883
|
}
|
|
8864
8884
|
async function awaitAndReport(apiUrl, token, id, kind, log, open = false, json = false, local) {
|
|
8865
|
-
if (!json) log.step(
|
|
8885
|
+
if (!json) log.step(waitCopy(kind));
|
|
8886
|
+
const started = Date.now();
|
|
8866
8887
|
const view = await waitForTerminalView(
|
|
8867
8888
|
apiUrl,
|
|
8868
8889
|
token,
|
|
8869
8890
|
id,
|
|
8870
8891
|
kind,
|
|
8871
8892
|
json ? () => {
|
|
8872
|
-
} : (p) =>
|
|
8893
|
+
} : (p) => {
|
|
8894
|
+
const minutes = Math.floor((Date.now() - started) / 6e4);
|
|
8895
|
+
log.dim(` ${p}%${minutes >= 1 ? ` (${minutes} min)` : ""}`);
|
|
8896
|
+
}
|
|
8873
8897
|
);
|
|
8874
8898
|
if (!view) {
|
|
8875
8899
|
if (json) writeJson({ kind, id, status: "failed", error: "Timed out waiting for the generation." });
|
|
@@ -8879,6 +8903,25 @@ async function awaitAndReport(apiUrl, token, id, kind, log, open = false, json =
|
|
|
8879
8903
|
}
|
|
8880
8904
|
await reportTerminal(kind, view, log, open, json, local);
|
|
8881
8905
|
}
|
|
8906
|
+
function waitCopy(kind) {
|
|
8907
|
+
switch (kind) {
|
|
8908
|
+
case "character_motion":
|
|
8909
|
+
return "Generating motion clips\u2026 (each clip is its own provider job \u2014 a batch takes several minutes, a 16-clip walk + run set ten or more; keep building and check back with genex wait --all)";
|
|
8910
|
+
case "character":
|
|
8911
|
+
case "character_concept":
|
|
8912
|
+
case "character_preview":
|
|
8913
|
+
return "Generating\u2026 (character stages take several minutes server-side)";
|
|
8914
|
+
case "model":
|
|
8915
|
+
case "model_segment":
|
|
8916
|
+
case "model_rig":
|
|
8917
|
+
case "model_animation":
|
|
8918
|
+
return "Generating\u2026 (a mesh takes a few minutes; --low-poly up to thirty)";
|
|
8919
|
+
case "video":
|
|
8920
|
+
return "Generating\u2026 (a clip takes a minute or two)";
|
|
8921
|
+
default:
|
|
8922
|
+
return "Generating\u2026 (this can take up to a minute)";
|
|
8923
|
+
}
|
|
8924
|
+
}
|
|
8882
8925
|
async function waitForTerminalView(apiUrl, token, id, kind, onProgress = () => {
|
|
8883
8926
|
}) {
|
|
8884
8927
|
const timeoutMs = waitTimeoutFor(kind);
|
|
@@ -8934,6 +8977,7 @@ async function reportTerminal(kind, view, log, open = false, json = false, local
|
|
|
8934
8977
|
if (!json) reportMocked(view, log);
|
|
8935
8978
|
return;
|
|
8936
8979
|
}
|
|
8980
|
+
const batch = motionBatchOutcome(view);
|
|
8937
8981
|
if (json) {
|
|
8938
8982
|
writeJson({
|
|
8939
8983
|
kind,
|
|
@@ -8945,12 +8989,25 @@ async function reportTerminal(kind, view, log, open = false, json = false, local
|
|
|
8945
8989
|
...delivered ? { saved: delivered.saved, downloadFailures: delivered.failures } : {},
|
|
8946
8990
|
...kind === "character" ? {
|
|
8947
8991
|
characterId: view.id,
|
|
8948
|
-
nextCommand: `genex controller character --character ${view.id}
|
|
8992
|
+
nextCommand: `genex controller character --character ${view.id}`,
|
|
8993
|
+
...rigFallbackOf(view) ? { rigProvider: "uthana", rigFallback: rigFallbackOf(view) } : {}
|
|
8994
|
+
} : {},
|
|
8995
|
+
// A motion batch that lost a clip is `completed` (what landed is real)
|
|
8996
|
+
// AND `partial: true` — the flag a machine reader must check.
|
|
8997
|
+
...batch ? {
|
|
8998
|
+
characterId: batch.characterId,
|
|
8999
|
+
partial: batch.partial,
|
|
9000
|
+
clipsRequested: batch.requested,
|
|
9001
|
+
clipsInstalled: batch.installed,
|
|
9002
|
+
...batch.failures.length > 0 ? { failures: batch.failures } : {},
|
|
9003
|
+
...batch.fallbacks.length > 0 ? { fallbacks: batch.fallbacks } : {},
|
|
9004
|
+
...batch.refundedCredits ? { refundedCredits: batch.refundedCredits } : {}
|
|
8949
9005
|
} : {}
|
|
8950
9006
|
});
|
|
8951
9007
|
return;
|
|
8952
9008
|
}
|
|
8953
9009
|
log.plain("");
|
|
9010
|
+
if (batch?.partial) reportPartialBatch(batch, log);
|
|
8954
9011
|
if (delivered) {
|
|
8955
9012
|
reportLocalFiles(delivered, log, false);
|
|
8956
9013
|
} else {
|
|
@@ -8977,8 +9034,70 @@ async function reportTerminal(kind, view, log, open = false, json = false, local
|
|
|
8977
9034
|
log.plain(` ${files[0].url}`);
|
|
8978
9035
|
log.plain("");
|
|
8979
9036
|
}
|
|
9037
|
+
if (batch && !batch.partial && batch.fallbacks.length > 0) reportFallbacks(batch, log);
|
|
9038
|
+
if (kind === "character") {
|
|
9039
|
+
const fallback = rigFallbackOf(view);
|
|
9040
|
+
if (fallback) {
|
|
9041
|
+
log.plain(
|
|
9042
|
+
` ${c.yellow("\u26A0")} Meshy's rig failed pose QA in both rest poses (${fallback.poseMode} mesh kept), so ${c.bold("Uthana auto-rigged it instead")}: no controller pack and no catalog clips on this body. Give it moves with ${c.cyan(`genex character animate ${view.id} --locomotion`)} and plain-word verbs.`
|
|
9043
|
+
);
|
|
9044
|
+
}
|
|
9045
|
+
}
|
|
8980
9046
|
printHint(kind, view, files, log, delivered !== null);
|
|
8981
9047
|
}
|
|
9048
|
+
function motionBatchOutcome(view) {
|
|
9049
|
+
if (view.kind !== "character_motion" && view.kind !== void 0) return null;
|
|
9050
|
+
const meta = metadataOf(view);
|
|
9051
|
+
const requested = typeof meta.clipsRequested === "number" ? meta.clipsRequested : void 0;
|
|
9052
|
+
const installed = typeof meta.clipsInstalled === "number" ? meta.clipsInstalled : void 0;
|
|
9053
|
+
if (requested === void 0 || installed === void 0) return null;
|
|
9054
|
+
const failures = Array.isArray(meta.failures) ? meta.failures.filter((f) => typeof f === "object" && f !== null && typeof f.clip === "string").map((f) => ({ clip: f.clip, error: typeof f.error === "string" ? f.error : "" })) : [];
|
|
9055
|
+
const fallbacks = Array.isArray(meta.fallbacks) ? meta.fallbacks.filter((f) => typeof f === "object" && f !== null && typeof f.clip === "string") : [];
|
|
9056
|
+
return {
|
|
9057
|
+
...typeof meta.characterId === "string" ? { characterId: meta.characterId } : {},
|
|
9058
|
+
// Servers before 2026-09-13 sent no `outcome`; a non-empty failure list
|
|
9059
|
+
// says the same thing.
|
|
9060
|
+
partial: meta.outcome === "partial" || failures.length > 0,
|
|
9061
|
+
requested,
|
|
9062
|
+
installed,
|
|
9063
|
+
failures,
|
|
9064
|
+
fallbacks,
|
|
9065
|
+
...typeof meta.refundedCredits === "number" ? { refundedCredits: meta.refundedCredits } : {}
|
|
9066
|
+
};
|
|
9067
|
+
}
|
|
9068
|
+
function reportPartialBatch(batch, log) {
|
|
9069
|
+
const missing = batch.requested - batch.installed;
|
|
9070
|
+
log.plain(
|
|
9071
|
+
`${c.yellow("\u26A0")} ${c.bold(`${batch.installed} of ${batch.requested} clips landed`)} \u2014 ${missing} did not:`
|
|
9072
|
+
);
|
|
9073
|
+
for (const f of batch.failures) {
|
|
9074
|
+
log.plain(` \xB7 ${c.bold(f.clip)}${f.error ? c.dim(` \u2014 ${oneLineError(f.error)}`) : ""}`);
|
|
9075
|
+
}
|
|
9076
|
+
const verbs = batch.failures.map((f) => `"${f.clip}"`).join(" ");
|
|
9077
|
+
if (batch.characterId && verbs) {
|
|
9078
|
+
log.plain(` Re-run only the missing ones: ${c.cyan(`genex character animate ${batch.characterId} ${verbs}`)}`);
|
|
9079
|
+
}
|
|
9080
|
+
if (batch.refundedCredits) log.dim(` ${batch.refundedCredits} credits refunded for what did not land.`);
|
|
9081
|
+
if (batch.fallbacks.length > 0) reportFallbacks(batch, log);
|
|
9082
|
+
log.plain("");
|
|
9083
|
+
}
|
|
9084
|
+
function reportFallbacks(batch, log) {
|
|
9085
|
+
for (const f of batch.fallbacks) {
|
|
9086
|
+
log.plain(
|
|
9087
|
+
` ${c.yellow("\u21B3")} ${c.bold(f.clip)} was ${f.to === "text" ? "described" : f.to} instead of ${f.from === "video" ? "filmed" : f.from}${f.reason ? c.dim(` (${oneLineError(f.reason)})`) : ""} \u2014 watch it on the character before calling it done.`
|
|
9088
|
+
);
|
|
9089
|
+
}
|
|
9090
|
+
}
|
|
9091
|
+
function rigFallbackOf(view) {
|
|
9092
|
+
const meta = metadataOf(view);
|
|
9093
|
+
const fallback = meta.rigFallback;
|
|
9094
|
+
if (!fallback || typeof fallback !== "object") return null;
|
|
9095
|
+
const { poseMode, failures } = fallback;
|
|
9096
|
+
return {
|
|
9097
|
+
poseMode: typeof poseMode === "string" ? poseMode : "a-pose",
|
|
9098
|
+
failures: Array.isArray(failures) ? failures.filter((f) => typeof f === "string") : []
|
|
9099
|
+
};
|
|
9100
|
+
}
|
|
8982
9101
|
function reportLocalFiles(result, log, json) {
|
|
8983
9102
|
if (json) return;
|
|
8984
9103
|
if (result.saved.length > 0) {
|
|
@@ -9161,7 +9280,9 @@ function assetHint(kind, view, url) {
|
|
|
9161
9280
|
character_preview: "Show all four preview views to the user and wait for explicit approval before finalizing.",
|
|
9162
9281
|
character: `Install the controller and current manifest with genex controller character --character ${view.id}.`,
|
|
9163
9282
|
character_animation: "The action is now part of the character's current manifest; refresh the local controller manifest before testing.",
|
|
9164
|
-
|
|
9283
|
+
// The CHARACTER's id, not this motion job's: the hint printed the job id
|
|
9284
|
+
// for four months and the command it named did not resolve (2026-09-13).
|
|
9285
|
+
character_motion: `The clips are part of the character's current manifest \u2014 install them with genex controller character --character ${stringValue(metadataOf(view).characterId) ?? "<character-id>"}, then play the game and watch the motion on the real character before calling it done.`,
|
|
9165
9286
|
model_segment: `One GLB with NAMED parts \u2014 load it through the quality kit (loadModelWithFallback, as for a plain model), then getObjectByName / traverse scene.children to move, detach, or swap a part. Part names print above when the provider reports them.`,
|
|
9166
9287
|
model_rig: `Rigged GLB (Tripo skeleton) \u2014 load it through the quality kit (loadModelWithFallback); give it motion with genex model animate ${view.id} --preset walk, or drive the bones in code.`,
|
|
9167
9288
|
model_animation: `Animated GLB \u2014 load it through the quality kit (loadModelWithFallback) and play its clips via THREE.AnimationMixer (gltf.animations).`
|
|
@@ -9590,8 +9711,12 @@ async function runWait(opts) {
|
|
|
9590
9711
|
return;
|
|
9591
9712
|
}
|
|
9592
9713
|
const kind = view.kind ?? "image";
|
|
9593
|
-
const
|
|
9594
|
-
const local = localDeliveryFor(
|
|
9714
|
+
const ledgerEntry = (await readLedger(opts.cwd ?? process.cwd())).find((e) => e.id === id);
|
|
9715
|
+
const local = localDeliveryFor(
|
|
9716
|
+
await workspaceMode(),
|
|
9717
|
+
{ ...opts, outDir: opts.outDir ?? ledgerEntry?.outDir },
|
|
9718
|
+
ledgerEntry?.prompt || kind
|
|
9719
|
+
);
|
|
9595
9720
|
log.plain(c.bold("genex wait"));
|
|
9596
9721
|
log.dim(` ${kind} ${id}`);
|
|
9597
9722
|
log.plain("");
|
|
@@ -9603,6 +9728,10 @@ async function runWait(opts) {
|
|
|
9603
9728
|
log.dim(
|
|
9604
9729
|
` \u23F3 Character stages take several minutes server-side \u2014 you don't have to sit here. Keep building and check back with ${c.cyan("genex wait --all")}.`
|
|
9605
9730
|
);
|
|
9731
|
+
} else if (kind === "character_motion") {
|
|
9732
|
+
log.dim(
|
|
9733
|
+
` \u23F3 Motion batches take several minutes server-side (each clip is its own job; a 16-clip walk + run set ten or more) \u2014 you don't have to sit here. Keep building and check back with ${c.cyan("genex wait --all")}.`
|
|
9734
|
+
);
|
|
9606
9735
|
}
|
|
9607
9736
|
await awaitAndReport(apiUrl, token, id, kind, log, opts.open, opts.json, local);
|
|
9608
9737
|
}
|
|
@@ -9667,7 +9796,7 @@ async function runWaitAll(opts) {
|
|
|
9667
9796
|
for (const e of ledger) {
|
|
9668
9797
|
const v = views.get(e.id);
|
|
9669
9798
|
if (v?.status !== "completed" || !v.files?.length) continue;
|
|
9670
|
-
const local = localDeliveryFor("tools", opts, e.prompt || e.kind);
|
|
9799
|
+
const local = localDeliveryFor("tools", { ...opts, outDir: opts.outDir ?? e.outDir }, e.prompt || e.kind);
|
|
9671
9800
|
if (!local) continue;
|
|
9672
9801
|
const outDir = path25.join(path25.relative(process.cwd(), cwd) || ".", local.outDir);
|
|
9673
9802
|
const target = { kind: e.kind, prompt: local.prompt, id: e.id, outDir };
|
|
@@ -9687,21 +9816,27 @@ async function runWaitAll(opts) {
|
|
|
9687
9816
|
log.plain(c.bold("genex wait --all"));
|
|
9688
9817
|
log.plain("");
|
|
9689
9818
|
for (const r of rows) {
|
|
9690
|
-
const mark = r.status === "done" ? c.green("\u2713") : r.status === "failed" ? c.red("\u2717") : c.yellow("\u2026");
|
|
9691
|
-
const label = r.status === "running" && typeof r.progress === "number" ? `running ${r.progress}%` : r.status;
|
|
9819
|
+
const mark = r.partial ? c.yellow("\u26A0") : r.status === "done" ? c.green("\u2713") : r.status === "failed" ? c.red("\u2717") : c.yellow("\u2026");
|
|
9820
|
+
const label = r.status === "running" && typeof r.progress === "number" ? `running ${r.progress}%` : r.partial ? "partial" : r.status;
|
|
9692
9821
|
log.plain(` ${mark} ${label.padEnd(12)} ${r.kind.padEnd(8)} ${r.id} ${c.dim(`"${r.prompt.slice(0, 56)}"`)}`);
|
|
9822
|
+
if (r.partial) {
|
|
9823
|
+
log.plain(
|
|
9824
|
+
` ${c.yellow(`${r.partial.installed} of ${r.partial.requested} clips landed`)} \u2014 missing: ${r.partial.failed.join(", ")}. ${c.dim(`genex wait ${r.id}`)} names the fix.`
|
|
9825
|
+
);
|
|
9826
|
+
}
|
|
9693
9827
|
if (r.status === "failed" && r.error) {
|
|
9694
9828
|
for (const line of describeGenerationFailure(r.kind, r.error).lines) log.plain(` ${line}`);
|
|
9695
9829
|
}
|
|
9696
9830
|
if (r.status === "done" && r.mocked) log.dim(` ${MOCKED_LINE}`);
|
|
9697
9831
|
}
|
|
9698
9832
|
const count = (s) => rows.filter((r) => r.status === s).length;
|
|
9833
|
+
const partial = rows.filter((r) => r.partial).length;
|
|
9699
9834
|
const billingFailed = rows.filter(
|
|
9700
9835
|
(r) => r.status === "failed" && isProviderBillingError(r.error)
|
|
9701
9836
|
).length;
|
|
9702
9837
|
log.plain("");
|
|
9703
9838
|
log.plain(
|
|
9704
|
-
` ${count("done")} done \xB7 ${count("running")} running \xB7 ${count("queued")} queued \xB7 ${count("failed")} failed`
|
|
9839
|
+
` ${count("done")} done${partial > 0 ? ` (${partial} partial)` : ""} \xB7 ${count("running")} running \xB7 ${count("queued")} queued \xB7 ${count("failed")} failed`
|
|
9705
9840
|
);
|
|
9706
9841
|
if (billingFailed > 0) {
|
|
9707
9842
|
log.dim(
|
|
@@ -9750,8 +9885,15 @@ async function toRow(e, v, cwd) {
|
|
|
9750
9885
|
});
|
|
9751
9886
|
}
|
|
9752
9887
|
switch (v.status) {
|
|
9753
|
-
case "completed":
|
|
9754
|
-
|
|
9888
|
+
case "completed": {
|
|
9889
|
+
const batch = motionBatchOutcome(v);
|
|
9890
|
+
return {
|
|
9891
|
+
...base,
|
|
9892
|
+
status: "done",
|
|
9893
|
+
...v.mocked === true ? { mocked: true } : {},
|
|
9894
|
+
...batch?.partial ? { partial: { installed: batch.installed, requested: batch.requested, failed: batch.failures.map((f) => f.clip) } } : {}
|
|
9895
|
+
};
|
|
9896
|
+
}
|
|
9755
9897
|
case "failed":
|
|
9756
9898
|
return { ...base, status: "failed", error: v.error };
|
|
9757
9899
|
case "processing":
|
|
@@ -19952,7 +20094,7 @@ async function postWorkflow(url, token, body) {
|
|
|
19952
20094
|
}
|
|
19953
20095
|
async function runWorkflow(args) {
|
|
19954
20096
|
const log = createLogger({ quiet: args.opts.quiet || args.opts.json });
|
|
19955
|
-
{
|
|
20097
|
+
if (await workspaceMode() !== "tools") {
|
|
19956
20098
|
const counts = unshippedCounts(await readLedgerRows());
|
|
19957
20099
|
const verdict = ceilingVerdict({ kind: args.kind, counts, config: ceilingConfig() });
|
|
19958
20100
|
if (!verdict.allow) {
|
|
@@ -19981,7 +20123,8 @@ async function runWorkflow(args) {
|
|
|
19981
20123
|
return;
|
|
19982
20124
|
}
|
|
19983
20125
|
await recordQueued(created.id, args.kind, args.prompt, void 0, {
|
|
19984
|
-
credits: created.creditsQuoted ?? args.quotedCredits
|
|
20126
|
+
credits: created.creditsQuoted ?? args.quotedCredits,
|
|
20127
|
+
...args.opts.outDir ? { outDir: args.opts.outDir } : {}
|
|
19985
20128
|
});
|
|
19986
20129
|
if (args.opts.noWait) {
|
|
19987
20130
|
const nextCommand = `genex wait ${created.id}${args.opts.json ? " --json" : ""}`;
|
|
@@ -20376,7 +20519,7 @@ async function runCharacterAnimate(opts) {
|
|
|
20376
20519
|
// src/commands/character-motion.ts
|
|
20377
20520
|
import { readFile } from "fs/promises";
|
|
20378
20521
|
import { basename } from "path";
|
|
20379
|
-
var VIDEO_SECONDS = [
|
|
20522
|
+
var VIDEO_SECONDS = [5, 6, 7, 8];
|
|
20380
20523
|
var MAX_VIDEO_BYTES = 200 * 1024 * 1024;
|
|
20381
20524
|
async function context2(opts) {
|
|
20382
20525
|
const token = opts.token ?? await readUserToken(opts.envPath);
|
|
@@ -20569,28 +20712,44 @@ async function runCharacterMotions(opts) {
|
|
|
20569
20712
|
return;
|
|
20570
20713
|
}
|
|
20571
20714
|
const { manifest } = await response.json();
|
|
20715
|
+
const isMovementSlot = (slot) => /^(walk|run|crouch)\.[a-z-]+$/.test(slot);
|
|
20716
|
+
const slotsOf = (key) => Object.entries(manifest.locomotion.slots).filter(([, value]) => value === key).map(([slot]) => slot);
|
|
20572
20717
|
if (opts.json) {
|
|
20573
20718
|
writeJson({
|
|
20574
20719
|
kind: "character_motion",
|
|
20575
20720
|
stage: "list",
|
|
20576
20721
|
characterId,
|
|
20577
|
-
|
|
20578
|
-
|
|
20579
|
-
|
|
20580
|
-
|
|
20581
|
-
|
|
20582
|
-
|
|
20583
|
-
|
|
20722
|
+
...manifest.rig ? { rig: manifest.rig } : {},
|
|
20723
|
+
// Additive since 1.34: the body's URL and the per-clip URL the manifest
|
|
20724
|
+
// already carried, so a local downloader needs no second endpoint.
|
|
20725
|
+
...manifest.model?.url ? { modelUrl: manifest.model.url } : {},
|
|
20726
|
+
...manifest.model?.skeletonSignature ? { skeletonSignature: manifest.model.skeletonSignature } : {},
|
|
20727
|
+
clips: manifest.clips.map((clip) => {
|
|
20728
|
+
const slots = slotsOf(clip.key);
|
|
20729
|
+
return {
|
|
20730
|
+
key: clip.key,
|
|
20731
|
+
name: clip.name,
|
|
20732
|
+
provider: clip.provider ?? "meshy",
|
|
20733
|
+
duration: clip.duration,
|
|
20734
|
+
...clip.url ? { url: clip.url } : {},
|
|
20735
|
+
...typeof clip.loop === "boolean" ? { loop: clip.loop } : {},
|
|
20736
|
+
...typeof clip.nominalSpeed === "number" ? { nominalSpeed: clip.nominalSpeed } : {},
|
|
20737
|
+
slots,
|
|
20738
|
+
role: slots.some(isMovementSlot) ? "locomotion" : "action"
|
|
20739
|
+
};
|
|
20740
|
+
})
|
|
20584
20741
|
});
|
|
20585
20742
|
return;
|
|
20586
20743
|
}
|
|
20587
20744
|
log.plain(c.bold(`${manifest.clips.length} clips on ${characterId}`));
|
|
20588
20745
|
for (const clip of manifest.clips) {
|
|
20589
|
-
const slots =
|
|
20746
|
+
const slots = slotsOf(clip.key);
|
|
20590
20747
|
const where = slots.length > 0 ? ` \u2192 ${slots.join(", ")}` : "";
|
|
20591
20748
|
const source = clip.provider === "uthana" ? c.dim(" generated") : "";
|
|
20592
|
-
|
|
20749
|
+
const role = slots.some(isMovementSlot) ? "" : c.dim(" action");
|
|
20750
|
+
log.plain(` ${clip.key}${source}${role}${where}`);
|
|
20593
20751
|
}
|
|
20752
|
+
log.dim(` Per-clip URLs and the body's URL: ${c.cyan(`genex character motions ${characterId} --json`)}`);
|
|
20594
20753
|
}
|
|
20595
20754
|
|
|
20596
20755
|
// src/commands/animations.ts
|
|
@@ -23388,6 +23547,7 @@ var TYPICAL_CREDITS = {
|
|
|
23388
23547
|
character_import: 18
|
|
23389
23548
|
// Uthana auto-rig of your own GLB; the upload itself is free
|
|
23390
23549
|
};
|
|
23550
|
+
var CREATURE_TYPICAL_CREDITS = 46;
|
|
23391
23551
|
|
|
23392
23552
|
// src/index.ts
|
|
23393
23553
|
var TEXTURE_FLAG_VALUES = ["standard", "detailed", "none", "2k", "4k", "8k"];
|
|
@@ -23524,7 +23684,7 @@ ${c.bold("Usage")}
|
|
|
23524
23684
|
prints the plan before anything is charged.
|
|
23525
23685
|
--locomotion generates the 8-way walk + run set;
|
|
23526
23686
|
--video ./clip.mp4 uses your own footage;
|
|
23527
|
-
--duration
|
|
23687
|
+
--duration 5-8 lengthens the reference video for
|
|
23528
23688
|
multi-beat moves; --action <id-or-query> still
|
|
23529
23689
|
picks a catalog clip.
|
|
23530
23690
|
genex character motions <id> List the clips installed on that character.
|
|
@@ -23620,8 +23780,8 @@ ${c.bold("Options for the generators (`model` `sfx` `music` `voice` `texture` `i
|
|
|
23620
23780
|
per-plan lists above); each clip is billed.
|
|
23621
23781
|
--terrain (texture) seamless tiling surface for terrain/ground.
|
|
23622
23782
|
--duration <sec> (sfx, video, music) target clip length in seconds;
|
|
23623
|
-
(character/creature animate)
|
|
23624
|
-
verbs that route to AI video (default
|
|
23783
|
+
(character/creature animate) 5-8 s of reference video for
|
|
23784
|
+
verbs that route to AI video (default 5) \u2014 longer gives a
|
|
23625
23785
|
multi-beat move room to land instead of rushing it.
|
|
23626
23786
|
(music: 10-300, default 90 \u2014 a looping gameplay track).
|
|
23627
23787
|
--transparent (image) transparent background (alpha) \u2014 for decals/stickers.
|
|
@@ -23901,11 +24061,14 @@ ${c.bold("Characters")}
|
|
|
23901
24061
|
The rigged, game-ready character. ${cr("character_finalize")}
|
|
23902
24062
|
genex character "<brief>" --direct-text
|
|
23903
24063
|
One shot, no approval steps. ${cr("character")}
|
|
23904
|
-
genex creature "<desc>" A rigged enemy (biped-shaped bodies). ${
|
|
24064
|
+
genex creature "<desc>" A rigged enemy (biped-shaped bodies). ${String(CREATURE_TYPICAL_CREDITS).padStart(3)} cr
|
|
24065
|
+
genex character import <file.glb> Your own humanoid mesh (or a genex model), ${cr("character_import")}
|
|
24066
|
+
auto-rigged by Uthana; the upload is free.
|
|
23905
24067
|
genex character animate <id> "<verb>" \u2026
|
|
23906
24068
|
Any move, in plain words. --locomotion ${cr("character_motion")} each
|
|
23907
|
-
for the 8-way walk+run set
|
|
23908
|
-
./clip.mp4 to use
|
|
24069
|
+
for the 8-way walk+run set ("walk forward"
|
|
24070
|
+
is one clip); --video ./clip.mp4 to use
|
|
24071
|
+
your own footage.
|
|
23909
24072
|
genex character motions <id> List the clips installed on a character.
|
|
23910
24073
|
genex animations search "<intent>" Search the ready-made animation library. free
|
|
23911
24074
|
|
|
@@ -23949,6 +24112,74 @@ Hosting, publishing, multiplayer, remixing and custom domains are the Genex
|
|
|
23949
24112
|
platform \u2014 build a game there from one prompt: ${c.cyan("https://genex.games")}
|
|
23950
24113
|
`;
|
|
23951
24114
|
var COMMAND_HELP = {
|
|
24115
|
+
character: `${c.bold("genex character")} \u2014 a rigged humanoid body, and every move it makes.
|
|
24116
|
+
|
|
24117
|
+
${c.bold("Make one")}
|
|
24118
|
+
genex character "<brief>" Three concept candidates (guided flow, step 1).
|
|
24119
|
+
genex character preview <concept-id> --candidate <1|2|3> --user-approved
|
|
24120
|
+
A 3D preview of the chosen one (step 2).
|
|
24121
|
+
genex character finalize <preview-id> --user-approved --approve-remesh <faces>
|
|
24122
|
+
The rigged, game-ready character (step 3).
|
|
24123
|
+
genex character "<brief>" --direct-text One shot, no approval steps.
|
|
24124
|
+
genex creature "<desc>" The one-shot lane with enemy defaults (no controller pack).
|
|
24125
|
+
genex character import <file.glb> [--height <m>] [--no-fingers]
|
|
24126
|
+
Your own humanoid mesh \u2014 or a \`genex model\` GLB \u2014
|
|
24127
|
+
auto-rigged by Uthana. T- or A-pose, feet on the
|
|
24128
|
+
ground, facing +Z, .glb only. The upload is free.
|
|
24129
|
+
The result takes verbs, --locomotion and --video;
|
|
24130
|
+
the Meshy catalog and controller pack do not apply.
|
|
24131
|
+
genex character adopt [<id>] (game workspaces) claim a remixed game's character.
|
|
24132
|
+
|
|
24133
|
+
${c.bold("Knobs (one shot / preview / finalize)")}
|
|
24134
|
+
--polycount <faces> \xB7 --approve-remesh <faces> 10000-100000 (10k crowds, 20-30k a player body).
|
|
24135
|
+
--texture 2k|4k|8k \xB7 --no-ultra \xB7 --pose a-pose|t-pose \xB7 --height <metres>
|
|
24136
|
+
|
|
24137
|
+
${c.bold("Animate it")}
|
|
24138
|
+
genex character animate <id> "<verb>" ["<verb>" \u2026]
|
|
24139
|
+
One clip per verb, in plain words. The server
|
|
24140
|
+
prints the plan (route and clip count per verb)
|
|
24141
|
+
before anything is charged.
|
|
24142
|
+
genex character animate <id> --locomotion The 8-way walk + run set (16 clips).
|
|
24143
|
+
genex character animate <id> "walk forward" One movement clip: <walk|run> [direction].
|
|
24144
|
+
"walk" alone is the 8-direction walk set.
|
|
24145
|
+
--video ./clip.mp4 Use your own footage for that one verb.
|
|
24146
|
+
--duration 5-8 Reference-video length for verbs the platform
|
|
24147
|
+
films first (default 5).
|
|
24148
|
+
--action <id-or-query> A ready-made library clip instead (Meshy rigs).
|
|
24149
|
+
genex character motions <id> [--json] What is installed: key, verb, slot, role; --json
|
|
24150
|
+
adds the per-clip and body URLs.
|
|
24151
|
+
genex animations search "<intent>" The ready-made library. Free.
|
|
24152
|
+
|
|
24153
|
+
${c.bold("When a batch does not fully land")}
|
|
24154
|
+
A motion batch that lost a clip completes with what landed and says so \u2014 "22 of 23
|
|
24155
|
+
clips landed \u2014 missing: \u2026" with the exact re-run command; --json carries partial:true.
|
|
24156
|
+
A verb the video model refuses is described instead of filmed, and the batch says which.
|
|
24157
|
+
A Meshy rig refused by pose QA in both rest poses is auto-rigged by Uthana instead: no
|
|
24158
|
+
controller pack, so give it moves with \`character animate <id> --locomotion\`.
|
|
24159
|
+
`,
|
|
24160
|
+
model: `${c.bold("genex model")} \u2014 a 3D model (GLB), and the mesh lanes on top of one.
|
|
24161
|
+
|
|
24162
|
+
${c.bold("Usage")}
|
|
24163
|
+
genex model "<prompt>" [--image <path|url>] A GLB from text, or built FROM a reference image.
|
|
24164
|
+
genex model import <file.glb> A mesh you already have becomes a model of yours.
|
|
24165
|
+
Free; .glb only, \u2264 64 MB. Then segment / rig /
|
|
24166
|
+
animate / character import take its id.
|
|
24167
|
+
genex model segment <id> [--granularity simple|balanced|detailed]
|
|
24168
|
+
One GLB split into named parts (tripo_part_N).
|
|
24169
|
+
genex model rig <id> [--type biped|quadruped|hexapod|octopod|avian|serpentine|aquatic]
|
|
24170
|
+
A skeleton for any mesh; the plan is auto-detected.
|
|
24171
|
+
genex model animate <rig-id> --preset walk[,run,\u2026]
|
|
24172
|
+
Ready-made clips retargeted onto that rig, per clip.
|
|
24173
|
+
|
|
24174
|
+
${c.bold("Quality knobs")}
|
|
24175
|
+
--texture standard|detailed|none \xB7 --geometry detailed \xB7 --quad \xB7 --low-poly
|
|
24176
|
+
--face-limit <n> \xB7 --parts \xB7 --auto-size Each priced in the quote; say it in one line.
|
|
24177
|
+
|
|
24178
|
+
${c.bold("Common")}
|
|
24179
|
+
--out-dir <dir> \xB7 --no-wait (then genex wait <id>) \xB7 --no-download \xB7 --json
|
|
24180
|
+
A humanoid \`genex model\` result is the input to \`genex character import\` \u2014 the route to a
|
|
24181
|
+
rigged body when the character lane's pose QA refuses a mesh.
|
|
24182
|
+
`,
|
|
23952
24183
|
budget: `${c.bold("genex budget")} \u2014 this build's asset allowance, and the live numbers to plan against.
|
|
23953
24184
|
|
|
23954
24185
|
${c.bold("Usage")}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@genex-ai/cli-demo",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.34.0-dev.692",
|
|
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": {
|
|
@@ -41,7 +41,7 @@ Pick per role and say so in one line:
|
|
|
41
41
|
distance, 20000-30000 for a third-person player body, 50000+ only for a
|
|
42
42
|
close-up hero. Moves no cost.
|
|
43
43
|
- `--texture 2k|4k|8k` (preview / one shot): 8k is +5 credits, for close-ups.
|
|
44
|
-
- `--no-ultra` (preview / one shot): −
|
|
44
|
+
- `--no-ultra` (preview / one shot): −6 credits, less surface detail — stand-ins
|
|
45
45
|
and crowd enemies.
|
|
46
46
|
- `--pose a-pose|t-pose` (one shot): the preferred rest pose.
|
|
47
47
|
- `--height <metres>`: 0.5-3, default 1.7.
|
|
@@ -72,6 +72,13 @@ Uthana-rigged body: verbs, `--locomotion` and `--video` work; the Meshy
|
|
|
72
72
|
catalog and controller pack do not. Non-biped bodies: `npx genex model
|
|
73
73
|
import` + `npx genex model rig`.
|
|
74
74
|
|
|
75
|
+
The same door takes a `npx genex model` result: a humanoid mesh from the
|
|
76
|
+
model lane, imported here, is a rigged character without the character
|
|
77
|
+
lane's pose QA. If `npx genex character` reports that Meshy's rig failed
|
|
78
|
+
pose QA in both rest poses, the platform already did this for you — the
|
|
79
|
+
character lands rigged by Uthana, with no controller pack, and the terminal
|
|
80
|
+
says so. Animate it exactly like an import.
|
|
81
|
+
|
|
75
82
|
## Animating it
|
|
76
83
|
|
|
77
84
|
Say what the character should DO, in plain words — one clip per verb:
|
|
@@ -84,15 +91,34 @@ npx genex character motions <character-id> # what is inst
|
|
|
84
91
|
```
|
|
85
92
|
|
|
86
93
|
- `--locomotion` — the full 8-direction walk and run set, 16 clips.
|
|
94
|
+
- A movement request by name: `"walk forward"` is ONE clip in the `walk.forward`
|
|
95
|
+
slot, `"run"` alone is the 8-direction run set, `"strafe"` is walk left + right.
|
|
96
|
+
A boss that only walks at the player needs `"walk forward"`, not the set.
|
|
87
97
|
- `--video <file>` — use your own footage as the reference.
|
|
88
|
-
- `--duration
|
|
98
|
+
- `--duration 5-8` — a longer reference video for a multi-beat move, so it lands instead of rushing. Bills per second.
|
|
89
99
|
- `--action <id|query>` — pick a ready-made clip from the library instead of generating.
|
|
90
100
|
|
|
91
101
|
`npx genex creature animate <id> "<verb>"` is the same lane for an enemy.
|
|
92
102
|
|
|
93
|
-
Free and spend-free: the command prints its PLAN before anything is charged
|
|
103
|
+
Free and spend-free: the command prints its PLAN before anything is charged —
|
|
104
|
+
one line per request naming its route and clip count. READ IT: a phrase that
|
|
105
|
+
reads as movement expands to a set, and a stance or an attack is one clip.
|
|
94
106
|
Show that plan to the user when the cost matters.
|
|
95
107
|
|
|
108
|
+
Derive the list from the game's states first — idle, the moves it actually
|
|
109
|
+
walks, each attack, hurt, defeat — and request those. A full set nobody's
|
|
110
|
+
controller plays is generation time and load time spent on nothing.
|
|
111
|
+
|
|
112
|
+
## When a batch does not fully land
|
|
113
|
+
|
|
114
|
+
A batch is not all-or-nothing. Clips that landed are installed and paid for;
|
|
115
|
+
the terminal says `N of M clips landed — missing: …` with the exact re-run
|
|
116
|
+
command for the missing verbs, and the credits for what did not land come
|
|
117
|
+
back. `--json` carries `partial: true` and the `failures` list; `wait --all`
|
|
118
|
+
marks the row `partial`. A verb the video model would not film is described
|
|
119
|
+
instead, and the batch names it — watch that clip on the character before
|
|
120
|
+
calling it done.
|
|
121
|
+
|
|
96
122
|
## The ready-made library
|
|
97
123
|
|
|
98
124
|
```bash
|
|
@@ -106,20 +132,24 @@ look here first — a library clip costs nothing to generate.
|
|
|
106
132
|
|
|
107
133
|
Typical: **32** concept · **41** preview · **29** finalize · **64** one-shot
|
|
108
134
|
character · **46** creature · **18** import (Uthana auto-rig; the upload is
|
|
109
|
-
free) · **46 per clip** for a
|
|
110
|
-
|
|
135
|
+
free) · **46 per clip** for a described move, less for a movement clip and a
|
|
136
|
+
little more for one filmed first · **free** for a library search
|
|
137
|
+
(1 credit = $0.01). `--texture 8k` adds 5, `--no-ultra`
|
|
111
138
|
takes 6 off. Live prices and your balance: `npx genex doctor`.
|
|
112
139
|
|
|
113
140
|
## Waiting
|
|
114
141
|
|
|
115
|
-
Character stages take SEVERAL MINUTES server-side
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
142
|
+
Character stages take SEVERAL MINUTES server-side, and so does a motion
|
|
143
|
+
batch — each clip is its own provider job; a 16-clip set is ten minutes or
|
|
144
|
+
more. Do not sit in a foreground wait: pass `--no-wait`, keep building, then
|
|
145
|
+
`npx genex wait --all` for one status line each and `npx genex wait <id>` to
|
|
146
|
+
pick one up. **Re-running a character command bills a NEW character.**
|
|
119
147
|
|
|
120
148
|
## Troubleshooting
|
|
121
149
|
|
|
122
150
|
- **A four-legged prompt came back upright** — the rig here is biped-shaped only. Generate the body with `npx genex model` and rig it with `npx genex model rig --type quadruped` (see `$genex-tool-model`), or ship it static and animate in code.
|
|
151
|
+
- **"Rigged by Uthana instead" on a `character` / `creature` result** — Meshy's rig failed pose QA in both rest poses (armored and heavily clothed bodies do), so the platform auto-rigged the mesh. Nothing to redo: give it moves with `npx genex character animate <id> --locomotion` and plain-word verbs; the Meshy catalog does not apply.
|
|
152
|
+
- **`character animate` says the character is still generating** — wait for it first (`npx genex wait <id>`); the animate door only opens on a finished rig.
|
|
123
153
|
- **Rigs rest facing +Z.** Set yaw explicitly when you place one, and never mirror a skinned mesh with a negative scale — it inverts the whole thing silently.
|
|
124
154
|
- **Out of credits** — the error prints balance, price and refill date. Relay it; don't retry.
|
|
125
155
|
- **Anything else** — `npx genex doctor`.
|
|
@@ -60,10 +60,11 @@ credits get wasted.
|
|
|
60
60
|
|
|
61
61
|
## Credits
|
|
62
62
|
|
|
63
|
-
One balance across everything
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
63
|
+
One balance across everything, and ONE source for what things cost:
|
|
64
|
+
**`npx genex doctor`** (or `npx genex budget`) prints the live price per lane
|
|
65
|
+
and your balance. Prices are set server-side and change without a release, so
|
|
66
|
+
no card types them — the numbers in `npx genex --help` are typical defaults,
|
|
67
|
+
and a quote printed by a command is the exact charge.
|
|
67
68
|
|
|
68
69
|
- A FAILED generation is refunded automatically. You never pay for something that did not arrive.
|
|
69
70
|
- Out of credits is a plain refusal that prints your balance, the price, and the date credits refill. Relay those facts to the user and carry on with something else — do not retry.
|