@genex-ai/cli-demo 1.34.6 → 1.34.7-dev.719
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{blender-mcp-IPP442XH.js → blender-mcp-XLEK6SLM.js} +2 -2
- package/dist/{blender-serve-JGKITVP6.js → blender-serve-GW3LTFOX.js} +1 -1
- package/dist/{chunk-44SHBPOT.js → chunk-AQW7HUPV.js} +1 -1
- package/dist/{chunk-X5JRCQMK.js → chunk-MRV3Q3WU.js} +1 -1
- package/dist/index.js +197 -54
- package/package.json +1 -1
- package/templates/skills/genex-getting-started/SKILL.md +2 -2
- package/templates/skills/genex-threejs-game-ui/SKILL.md +2 -1
- package/templates/skills/genex-tool-publish/SKILL.md +1 -1
- package/templates/skills/genex-updates/SKILL.md +1 -1
|
@@ -7,10 +7,10 @@ import {
|
|
|
7
7
|
isRenderMode,
|
|
8
8
|
sceneSummary,
|
|
9
9
|
sheetOf
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-MRV3Q3WU.js";
|
|
11
11
|
import {
|
|
12
12
|
getCliVersion
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-AQW7HUPV.js";
|
|
14
14
|
|
|
15
15
|
// src/commands/blender-mcp.ts
|
|
16
16
|
import fs from "fs/promises";
|
|
@@ -38,7 +38,7 @@ import fs from "fs";
|
|
|
38
38
|
import os from "os";
|
|
39
39
|
import path from "path";
|
|
40
40
|
import { fileURLToPath } from "url";
|
|
41
|
-
var RAW_CHANNEL = "
|
|
41
|
+
var RAW_CHANNEL = "dev";
|
|
42
42
|
var CLI_CHANNEL = RAW_CHANNEL === "dev" ? "dev" : "latest";
|
|
43
43
|
var STANDS = {
|
|
44
44
|
prod: { api: "https://api.genex.games", dashboard: "https://genex.games" },
|
package/dist/index.js
CHANGED
|
@@ -42,7 +42,7 @@ import {
|
|
|
42
42
|
writeSecretFile,
|
|
43
43
|
writeUserToken,
|
|
44
44
|
writeWorkspace
|
|
45
|
-
} from "./chunk-
|
|
45
|
+
} from "./chunk-MRV3Q3WU.js";
|
|
46
46
|
import {
|
|
47
47
|
CLI_CHANNEL,
|
|
48
48
|
DEFAULT_API_URL,
|
|
@@ -63,7 +63,7 @@ import {
|
|
|
63
63
|
normalizeApiOrigin,
|
|
64
64
|
originKey,
|
|
65
65
|
resolveAgentTargets
|
|
66
|
-
} from "./chunk-
|
|
66
|
+
} from "./chunk-AQW7HUPV.js";
|
|
67
67
|
|
|
68
68
|
// src/instrument.ts
|
|
69
69
|
import * as Sentry from "@sentry/node";
|
|
@@ -1919,6 +1919,17 @@ async function extractSourceArchive(file, dest, rootFolder) {
|
|
|
1919
1919
|
}
|
|
1920
1920
|
}
|
|
1921
1921
|
|
|
1922
|
+
// src/lib/game-page.ts
|
|
1923
|
+
function draftPageUrl(dashboard, slug) {
|
|
1924
|
+
return `${dashboard.replace(/\/+$/, "")}/draft/${slug}`;
|
|
1925
|
+
}
|
|
1926
|
+
function publicPageUrl(dashboard, slug) {
|
|
1927
|
+
return `${dashboard.replace(/\/+$/, "")}/${slug}`;
|
|
1928
|
+
}
|
|
1929
|
+
function gamePageUrl(dashboard, slug, status) {
|
|
1930
|
+
return status === "published" ? publicPageUrl(dashboard, slug) : draftPageUrl(dashboard, slug);
|
|
1931
|
+
}
|
|
1932
|
+
|
|
1922
1933
|
// src/lib/deploy.ts
|
|
1923
1934
|
import "child_process";
|
|
1924
1935
|
|
|
@@ -5717,7 +5728,7 @@ async function createDraftProject(opts) {
|
|
|
5717
5728
|
}
|
|
5718
5729
|
log.success(`Created project ${c.cyan(project.slug)}.`);
|
|
5719
5730
|
log.plain(
|
|
5720
|
-
` \u279C Send this link to the user right now \u2014 their game's page is already live (a placeholder world until the first preview): ${c.cyan(
|
|
5731
|
+
` \u279C Send this link to the user right now \u2014 their game's page is already live (a placeholder world until the first preview): ${c.cyan(draftPageUrl(new URL(dashboardUrl).origin, project.slug))}`
|
|
5721
5732
|
);
|
|
5722
5733
|
if (project.playUrl) log.dim(` play (after preview/publish): ${project.playUrl}`);
|
|
5723
5734
|
log.dim(` dashboard: ${dashboardUrl}/dashboard`);
|
|
@@ -5819,7 +5830,7 @@ async function runPreview(opts) {
|
|
|
5819
5830
|
}
|
|
5820
5831
|
const dashboard = meta.dashboardOrigins?.[0];
|
|
5821
5832
|
if (dashboard) {
|
|
5822
|
-
log.plain(` your game's page (share this link): ${c.cyan(
|
|
5833
|
+
log.plain(` your game's page (share this link): ${c.cyan(draftPageUrl(dashboard, meta.slug))}`);
|
|
5823
5834
|
}
|
|
5824
5835
|
}
|
|
5825
5836
|
|
|
@@ -5831,7 +5842,7 @@ function parseRemixSource(input, apiUrl, dashboardUrl) {
|
|
|
5831
5842
|
try {
|
|
5832
5843
|
url = new URL(value);
|
|
5833
5844
|
} catch {
|
|
5834
|
-
throw new RemixError("invalid_source", "Use a game slug or a Genex
|
|
5845
|
+
throw new RemixError("invalid_source", "Use a game slug or a Genex game page URL (<dashboard>/<slug> or <dashboard>/draft/<slug>).");
|
|
5835
5846
|
}
|
|
5836
5847
|
if (url.username || url.password || !["https:", "http:"].includes(url.protocol)) throw new RemixError("invalid_source", "Use a Genex game page URL without credentials.");
|
|
5837
5848
|
const stand = url.origin === STANDS.dev.dashboard ? "dev" : url.origin === STANDS.prod.dashboard ? "prod" : null;
|
|
@@ -5839,8 +5850,8 @@ function parseRemixSource(input, apiUrl, dashboardUrl) {
|
|
|
5839
5850
|
const channel = stand === "dev" ? "dev" : "latest";
|
|
5840
5851
|
throw new RemixError("stand_mismatch", stand ? `This page belongs to the ${stand} stand. Run npx ${CLI_PACKAGE}@${channel} remix ${url.origin}${url.pathname} with that stand's API/auth settings.` : "This page is not on the selected Genex dashboard. Use its slug or select the intended API and auth stand explicitly.");
|
|
5841
5852
|
}
|
|
5842
|
-
const match = /^\/(world|draft)\/([a-z0-9][a-z0-9-]{0,99})\/?$/.exec(url.pathname);
|
|
5843
|
-
if (!match) throw new RemixError("invalid_source", "Use a Genex
|
|
5853
|
+
const match = /^\/(?:(world|draft)\/)?([a-z0-9][a-z0-9-]{0,99})\/?$/.exec(url.pathname);
|
|
5854
|
+
if (!match) throw new RemixError("invalid_source", "Use a Genex game page URL: <dashboard>/<slug> or <dashboard>/draft/<slug>.");
|
|
5844
5855
|
return { slug: match[2], version: match[1] === "draft" ? "draft" : "production" };
|
|
5845
5856
|
}
|
|
5846
5857
|
function loggerFor(opts) {
|
|
@@ -6901,7 +6912,7 @@ async function runRename(opts) {
|
|
|
6901
6912
|
if (project.playUrl) log.dim(` play: ${project.playUrl}`);
|
|
6902
6913
|
if (meta.status === "published") {
|
|
6903
6914
|
const dashboard = meta.dashboardOrigins?.[0];
|
|
6904
|
-
if (dashboard) log.dim(` page: ${dashboard
|
|
6915
|
+
if (dashboard) log.dim(` page: ${publicPageUrl(dashboard, project.slug)}`);
|
|
6905
6916
|
}
|
|
6906
6917
|
log.plain("");
|
|
6907
6918
|
log.info("Run `genex preview` (or `publish`) to rebuild \u2014 the new slug is baked into the bundle.");
|
|
@@ -7039,7 +7050,7 @@ async function runList(opts) {
|
|
|
7039
7050
|
log.plain("");
|
|
7040
7051
|
for (const p of projects) {
|
|
7041
7052
|
const published = p.status === "published";
|
|
7042
|
-
const pageUrl =
|
|
7053
|
+
const pageUrl = gamePageUrl(dashOrigin, p.slug, p.status);
|
|
7043
7054
|
const isHere = Boolean(meta && (meta.id === p.id || meta.slug === p.slug));
|
|
7044
7055
|
const bits = [
|
|
7045
7056
|
published ? c.green("published") : c.dim("draft"),
|
|
@@ -7128,8 +7139,7 @@ async function runMakeRemixable(opts) {
|
|
|
7128
7139
|
log.success("Your game is public \u2014 anyone can remix it now. \u{1F310}");
|
|
7129
7140
|
const dashboard = meta.dashboardOrigins?.[0];
|
|
7130
7141
|
if (dashboard) {
|
|
7131
|
-
|
|
7132
|
-
log.plain(` your game's page: ${c.cyan(`${dashboard}/${page}/${meta.slug}`)}`);
|
|
7142
|
+
log.plain(` your game's page: ${c.cyan(gamePageUrl(dashboard, meta.slug, meta.status))}`);
|
|
7133
7143
|
}
|
|
7134
7144
|
}
|
|
7135
7145
|
|
|
@@ -7648,7 +7658,7 @@ async function runPublish(opts) {
|
|
|
7648
7658
|
log.plain("");
|
|
7649
7659
|
log.success("Published. \u{1F389}");
|
|
7650
7660
|
const dashboard = meta.dashboardOrigins?.[0];
|
|
7651
|
-
if (dashboard) log.plain(` game page (share this link): ${c.cyan(
|
|
7661
|
+
if (dashboard) log.plain(` game page (share this link): ${c.cyan(publicPageUrl(dashboard, meta.slug))}`);
|
|
7652
7662
|
if (meta.playUrl) log.dim(` play: ${meta.playUrl}`);
|
|
7653
7663
|
await verifyGamePage({
|
|
7654
7664
|
apiUrl,
|
|
@@ -7687,8 +7697,7 @@ async function runPromote(opts) {
|
|
|
7687
7697
|
log.success("Live \u2014 players are now on the build you previewed.");
|
|
7688
7698
|
const dashboard = meta.dashboardOrigins?.[0];
|
|
7689
7699
|
if (dashboard) {
|
|
7690
|
-
|
|
7691
|
-
log.plain(` your game's page: ${c.cyan(`${dashboard}/${page}/${meta.slug}`)}`);
|
|
7700
|
+
log.plain(` your game's page: ${c.cyan(gamePageUrl(dashboard, meta.slug, meta.status))}`);
|
|
7692
7701
|
}
|
|
7693
7702
|
if (meta.status !== "published") {
|
|
7694
7703
|
log.dim(" Still unlisted \u2014 run `npx genex publish` to put it in the gallery.");
|
|
@@ -9046,7 +9055,7 @@ async function reportGlassTerminal(view, outDir, log, json) {
|
|
|
9046
9055
|
process.exitCode = 1;
|
|
9047
9056
|
}
|
|
9048
9057
|
}
|
|
9049
|
-
async function awaitAndReport(apiUrl, token, id, kind, log, open = false, json = false, local) {
|
|
9058
|
+
async function awaitAndReport(apiUrl, token, id, kind, log, open = false, json = false, local, bound) {
|
|
9050
9059
|
if (!json) log.step(waitCopy(kind));
|
|
9051
9060
|
const started = Date.now();
|
|
9052
9061
|
const view = await waitForTerminalView(
|
|
@@ -9058,9 +9067,11 @@ async function awaitAndReport(apiUrl, token, id, kind, log, open = false, json =
|
|
|
9058
9067
|
} : (p) => {
|
|
9059
9068
|
const minutes = Math.floor((Date.now() - started) / 6e4);
|
|
9060
9069
|
log.dim(` ${p}%${minutes >= 1 ? ` (${minutes} min)` : ""}`);
|
|
9061
|
-
}
|
|
9070
|
+
},
|
|
9071
|
+
bound?.timeoutMs
|
|
9062
9072
|
);
|
|
9063
9073
|
if (!view) {
|
|
9074
|
+
if (bound) return bound.onExpiry();
|
|
9064
9075
|
if (json) writeJson({ kind, id, status: "failed", error: "Timed out waiting for the generation." });
|
|
9065
9076
|
else log.error("Timed out waiting for the generation.");
|
|
9066
9077
|
process.exitCode = 1;
|
|
@@ -9088,11 +9099,18 @@ function waitCopy(kind) {
|
|
|
9088
9099
|
}
|
|
9089
9100
|
}
|
|
9090
9101
|
async function waitForTerminalView(apiUrl, token, id, kind, onProgress = () => {
|
|
9091
|
-
}) {
|
|
9092
|
-
const timeoutMs = waitTimeoutFor(kind);
|
|
9102
|
+
}, boundMs) {
|
|
9103
|
+
const timeoutMs = boundMs ?? waitTimeoutFor(kind);
|
|
9093
9104
|
const deadline = Date.now() + timeoutMs;
|
|
9094
9105
|
const streamed = await waitViaSSE(apiUrl, token, id, onProgress, deadline);
|
|
9095
|
-
|
|
9106
|
+
if (streamed !== "unsupported") return streamed;
|
|
9107
|
+
return poll(
|
|
9108
|
+
apiUrl,
|
|
9109
|
+
token,
|
|
9110
|
+
id,
|
|
9111
|
+
onProgress,
|
|
9112
|
+
boundMs === void 0 ? timeoutMs : Math.max(0, deadline - Date.now())
|
|
9113
|
+
);
|
|
9096
9114
|
}
|
|
9097
9115
|
async function reportTerminal(kind, view, log, open = false, json = false, local) {
|
|
9098
9116
|
if (view.status !== "completed") {
|
|
@@ -9378,7 +9396,9 @@ async function poll(apiUrl, token, id, onProgress, timeoutMs) {
|
|
|
9378
9396
|
}
|
|
9379
9397
|
} catch {
|
|
9380
9398
|
}
|
|
9381
|
-
|
|
9399
|
+
const remaining = deadline - Date.now();
|
|
9400
|
+
if (remaining <= 0) break;
|
|
9401
|
+
await sleep3(Math.min(3e3, remaining));
|
|
9382
9402
|
}
|
|
9383
9403
|
return null;
|
|
9384
9404
|
}
|
|
@@ -9825,6 +9845,8 @@ async function runModelAnimate(opts) {
|
|
|
9825
9845
|
|
|
9826
9846
|
// src/commands/wait.ts
|
|
9827
9847
|
import path27 from "path";
|
|
9848
|
+
var MAX_TIMEOUT_SEC = 2e6;
|
|
9849
|
+
var STILL_RUNNING_EXIT = 2;
|
|
9828
9850
|
var TERMINAL2 = /* @__PURE__ */ new Set(["completed", "failed"]);
|
|
9829
9851
|
async function runWait(opts) {
|
|
9830
9852
|
if (opts.all) return runWaitAll(opts);
|
|
@@ -9832,7 +9854,7 @@ async function runWait(opts) {
|
|
|
9832
9854
|
const id = opts.id?.trim();
|
|
9833
9855
|
if (!id) {
|
|
9834
9856
|
log.error(
|
|
9835
|
-
`Missing generation id. Usage: ${c.cyan("genex wait <id>")} \u2014 the id printed when you enqueued with --no-wait. For one status line per generation this project enqueued: ${c.cyan("genex wait --all")}.`
|
|
9857
|
+
`Missing generation id. Usage: ${c.cyan("genex wait <id>")} \u2014 the id printed when you enqueued with --no-wait. Add ${c.cyan("--status")} for one snapshot instead of a wait, or ${c.cyan("--timeout <seconds>")} to bound the wait. For one status line per generation this project enqueued: ${c.cyan("genex wait --all")}.`
|
|
9836
9858
|
);
|
|
9837
9859
|
process.exitCode = 1;
|
|
9838
9860
|
return;
|
|
@@ -9885,6 +9907,29 @@ async function runWait(opts) {
|
|
|
9885
9907
|
log.plain(c.bold("genex wait"));
|
|
9886
9908
|
log.dim(` ${kind} ${id}`);
|
|
9887
9909
|
log.plain("");
|
|
9910
|
+
const rowFor = (v) => viewRow(v, {
|
|
9911
|
+
id,
|
|
9912
|
+
kind,
|
|
9913
|
+
// A generation enqueued in some OTHER folder isn't in this ledger, so the
|
|
9914
|
+
// kind stands in for the prompt — the same substitution the local
|
|
9915
|
+
// delivery above makes.
|
|
9916
|
+
prompt: ledgerEntry?.prompt || kind,
|
|
9917
|
+
queuedAt: ledgerEntry?.queuedAt ?? ""
|
|
9918
|
+
});
|
|
9919
|
+
if (opts.status) {
|
|
9920
|
+
const row = rowFor(view);
|
|
9921
|
+
if (opts.json) {
|
|
9922
|
+
writeJson(row);
|
|
9923
|
+
} else {
|
|
9924
|
+
printStatusRow(row, log);
|
|
9925
|
+
log.plain("");
|
|
9926
|
+
log.dim(
|
|
9927
|
+
view.status === "failed" ? ` It failed \u2014 there is nothing to collect. See why with: ${c.cyan(`npx genex wait ${id}`)}` : TERMINAL2.has(view.status) ? ` Pick the asset up (URLs, and the files in a Genex Tools workspace) with: ${c.cyan(`npx genex wait ${id}`)}` : ` Still running server-side. Block until it lands with ${c.cyan(`npx genex wait ${id}`)}, or bound that with ${c.cyan("--timeout <seconds>")}.`
|
|
9928
|
+
);
|
|
9929
|
+
}
|
|
9930
|
+
if (!TERMINAL2.has(view.status)) process.exitCode = STILL_RUNNING_EXIT;
|
|
9931
|
+
return;
|
|
9932
|
+
}
|
|
9888
9933
|
if (TERMINAL2.has(view.status)) {
|
|
9889
9934
|
await reportTerminal(kind, view, log, opts.open, opts.json, local);
|
|
9890
9935
|
return;
|
|
@@ -9898,8 +9943,51 @@ async function runWait(opts) {
|
|
|
9898
9943
|
` \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")}.`
|
|
9899
9944
|
);
|
|
9900
9945
|
}
|
|
9946
|
+
if (opts.timeoutSec !== void 0) {
|
|
9947
|
+
const seconds = Math.min(opts.timeoutSec, MAX_TIMEOUT_SEC);
|
|
9948
|
+
const startedAt = Date.now();
|
|
9949
|
+
await awaitAndReport(apiUrl, token, id, kind, log, opts.open, opts.json, local, {
|
|
9950
|
+
timeoutMs: seconds * 1e3,
|
|
9951
|
+
onExpiry: () => reportStillRunning(apiUrl, token, id, kind, view, rowFor, log, opts, local, seconds, startedAt)
|
|
9952
|
+
});
|
|
9953
|
+
return;
|
|
9954
|
+
}
|
|
9901
9955
|
await awaitAndReport(apiUrl, token, id, kind, log, opts.open, opts.json, local);
|
|
9902
9956
|
}
|
|
9957
|
+
async function reportStillRunning(apiUrl, token, id, kind, lastKnown, rowFor, log, opts, local, seconds, startedAt) {
|
|
9958
|
+
const view = await peekGeneration(apiUrl, token, id) ?? lastKnown;
|
|
9959
|
+
if (TERMINAL2.has(view.status)) {
|
|
9960
|
+
await reportTerminal(kind, view, log, opts.open, opts.json, local);
|
|
9961
|
+
return;
|
|
9962
|
+
}
|
|
9963
|
+
const row = rowFor(view);
|
|
9964
|
+
if (opts.json) {
|
|
9965
|
+
writeJson({ ...row, timedOut: true });
|
|
9966
|
+
} else {
|
|
9967
|
+
printStatusRow(row, log);
|
|
9968
|
+
log.plain("");
|
|
9969
|
+
const waited = Math.max(0, Date.now() - startedAt) / 1e3;
|
|
9970
|
+
const shown = waited < 10 ? waited.toFixed(1) : Math.round(waited).toString();
|
|
9971
|
+
log.warn(
|
|
9972
|
+
`Stopped waiting after ${shown}s (ceiling ${seconds}s) \u2014 ${c.bold("the generation is STILL RUNNING")} server-side. Nothing was cancelled, and nothing extra was billed.`
|
|
9973
|
+
);
|
|
9974
|
+
log.dim(` Resume waiting: ${c.cyan(`npx genex wait ${id}`)}`);
|
|
9975
|
+
log.dim(` One snapshot, no waiting: ${c.cyan(`npx genex wait ${id} --status`)}`);
|
|
9976
|
+
}
|
|
9977
|
+
process.exitCode = STILL_RUNNING_EXIT;
|
|
9978
|
+
}
|
|
9979
|
+
async function peekGeneration(apiUrl, token, id) {
|
|
9980
|
+
try {
|
|
9981
|
+
const res = await apiFetch(`${apiUrl}/api/generations/${id}`, {
|
|
9982
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
9983
|
+
});
|
|
9984
|
+
if (!res.ok) return null;
|
|
9985
|
+
const { generation } = await res.json();
|
|
9986
|
+
return generation;
|
|
9987
|
+
} catch {
|
|
9988
|
+
return null;
|
|
9989
|
+
}
|
|
9990
|
+
}
|
|
9903
9991
|
async function runWaitAll(opts) {
|
|
9904
9992
|
const log = createLogger({ quiet: opts.quiet || opts.json });
|
|
9905
9993
|
const cwd = opts.cwd ?? process.cwd();
|
|
@@ -9980,20 +10068,7 @@ async function runWaitAll(opts) {
|
|
|
9980
10068
|
}
|
|
9981
10069
|
log.plain(c.bold("genex wait --all"));
|
|
9982
10070
|
log.plain("");
|
|
9983
|
-
for (const r of rows)
|
|
9984
|
-
const mark = r.partial ? c.yellow("\u26A0") : r.status === "done" ? c.green("\u2713") : r.status === "failed" ? c.red("\u2717") : c.yellow("\u2026");
|
|
9985
|
-
const label = r.status === "running" && typeof r.progress === "number" ? `running ${r.progress}%` : r.partial ? "partial" : r.status;
|
|
9986
|
-
log.plain(` ${mark} ${label.padEnd(12)} ${r.kind.padEnd(8)} ${r.id} ${c.dim(`"${r.prompt.slice(0, 56)}"`)}`);
|
|
9987
|
-
if (r.partial) {
|
|
9988
|
-
log.plain(
|
|
9989
|
-
` ${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.`
|
|
9990
|
-
);
|
|
9991
|
-
}
|
|
9992
|
-
if (r.status === "failed" && r.error) {
|
|
9993
|
-
for (const line of describeGenerationFailure(r.kind, r.error).lines) log.plain(` ${line}`);
|
|
9994
|
-
}
|
|
9995
|
-
if (r.status === "done" && r.mocked) log.dim(` ${MOCKED_LINE}`);
|
|
9996
|
-
}
|
|
10071
|
+
for (const r of rows) printStatusRow(r, log);
|
|
9997
10072
|
const count = (s) => rows.filter((r) => r.status === s).length;
|
|
9998
10073
|
const partial = rows.filter((r) => r.partial).length;
|
|
9999
10074
|
const billingFailed = rows.filter(
|
|
@@ -10037,6 +10112,39 @@ async function runWaitAll(opts) {
|
|
|
10037
10112
|
mode === "tools" || mode === "remix" ? " \u{1F4DC} Resumed or compacted? Re-read AGENTS.md before building on." : " \u{1F4DC} Resumed or compacted? Re-read AGENTS.md + DESIGN.md (continue from its Now: line) before building on."
|
|
10038
10113
|
);
|
|
10039
10114
|
}
|
|
10115
|
+
function printStatusRow(r, log) {
|
|
10116
|
+
const mark = r.partial ? c.yellow("\u26A0") : r.status === "done" ? c.green("\u2713") : r.status === "failed" ? c.red("\u2717") : c.yellow("\u2026");
|
|
10117
|
+
const label = r.status === "running" && typeof r.progress === "number" ? `running ${r.progress}%` : r.partial ? "partial" : r.status;
|
|
10118
|
+
log.plain(` ${mark} ${label.padEnd(12)} ${r.kind.padEnd(8)} ${r.id} ${c.dim(`"${r.prompt.slice(0, 56)}"`)}`);
|
|
10119
|
+
if (r.partial) {
|
|
10120
|
+
log.plain(
|
|
10121
|
+
` ${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.`
|
|
10122
|
+
);
|
|
10123
|
+
}
|
|
10124
|
+
if (r.status === "failed" && r.error) {
|
|
10125
|
+
for (const line of describeGenerationFailure(r.kind, r.error).lines) log.plain(` ${line}`);
|
|
10126
|
+
}
|
|
10127
|
+
if (r.status === "done" && r.mocked) log.dim(` ${MOCKED_LINE}`);
|
|
10128
|
+
}
|
|
10129
|
+
function viewRow(v, base) {
|
|
10130
|
+
switch (v.status) {
|
|
10131
|
+
case "completed": {
|
|
10132
|
+
const batch = motionBatchOutcome(v);
|
|
10133
|
+
return {
|
|
10134
|
+
...base,
|
|
10135
|
+
status: "done",
|
|
10136
|
+
...v.mocked === true ? { mocked: true } : {},
|
|
10137
|
+
...batch?.partial ? { partial: { installed: batch.installed, requested: batch.requested, failed: batch.failures.map((f) => f.clip) } } : {}
|
|
10138
|
+
};
|
|
10139
|
+
}
|
|
10140
|
+
case "failed":
|
|
10141
|
+
return { ...base, status: "failed", error: v.error };
|
|
10142
|
+
case "processing":
|
|
10143
|
+
return { ...base, status: "running", progress: v.progress };
|
|
10144
|
+
default:
|
|
10145
|
+
return { ...base, status: "queued" };
|
|
10146
|
+
}
|
|
10147
|
+
}
|
|
10040
10148
|
async function toRow(e, v, cwd) {
|
|
10041
10149
|
const base = { id: e.id, kind: e.kind, prompt: e.prompt, queuedAt: e.queuedAt };
|
|
10042
10150
|
if (v) {
|
|
@@ -10049,23 +10157,7 @@ async function toRow(e, v, cwd) {
|
|
|
10049
10157
|
).catch(() => {
|
|
10050
10158
|
});
|
|
10051
10159
|
}
|
|
10052
|
-
|
|
10053
|
-
case "completed": {
|
|
10054
|
-
const batch = motionBatchOutcome(v);
|
|
10055
|
-
return {
|
|
10056
|
-
...base,
|
|
10057
|
-
status: "done",
|
|
10058
|
-
...v.mocked === true ? { mocked: true } : {},
|
|
10059
|
-
...batch?.partial ? { partial: { installed: batch.installed, requested: batch.requested, failed: batch.failures.map((f) => f.clip) } } : {}
|
|
10060
|
-
};
|
|
10061
|
-
}
|
|
10062
|
-
case "failed":
|
|
10063
|
-
return { ...base, status: "failed", error: v.error };
|
|
10064
|
-
case "processing":
|
|
10065
|
-
return { ...base, status: "running", progress: v.progress };
|
|
10066
|
-
default:
|
|
10067
|
-
return { ...base, status: "queued" };
|
|
10068
|
-
}
|
|
10160
|
+
return viewRow(v, base);
|
|
10069
10161
|
}
|
|
10070
10162
|
return { ...base, status: e.status === "completed" ? "done" : e.status === "failed" ? "failed" : "queued" };
|
|
10071
10163
|
}
|
|
@@ -22666,7 +22758,7 @@ async function runBlender(opts) {
|
|
|
22666
22758
|
return 1;
|
|
22667
22759
|
}
|
|
22668
22760
|
if (sub === "serve") {
|
|
22669
|
-
const { serveLocalBlender } = await import("./blender-serve-
|
|
22761
|
+
const { serveLocalBlender } = await import("./blender-serve-GW3LTFOX.js");
|
|
22670
22762
|
const port = Number(process.env.GENEX_BLENDER_PORT ?? 8088);
|
|
22671
22763
|
log.step(`Starting a local Blender service on port ${port}`);
|
|
22672
22764
|
log.plain(
|
|
@@ -22675,7 +22767,7 @@ async function runBlender(opts) {
|
|
|
22675
22767
|
return serveLocalBlender({ port, log });
|
|
22676
22768
|
}
|
|
22677
22769
|
if (sub === "mcp") {
|
|
22678
|
-
const { runBlenderMcp } = await import("./blender-mcp-
|
|
22770
|
+
const { runBlenderMcp } = await import("./blender-mcp-XLEK6SLM.js");
|
|
22679
22771
|
return runBlenderMcp();
|
|
22680
22772
|
}
|
|
22681
22773
|
if (sub === "seat") {
|
|
@@ -23931,6 +24023,12 @@ ${c.bold("Usage")}
|
|
|
23931
24023
|
genex wait <id> Attach to a generation enqueued with --no-wait and
|
|
23932
24024
|
print its asset URL(s) when it finishes. Safe to
|
|
23933
24025
|
re-run \u2014 it never creates a new generation.
|
|
24026
|
+
--timeout <seconds> stops waiting at that ceiling:
|
|
24027
|
+
the generation keeps running, and you get its
|
|
24028
|
+
current row, the resume command, and exit 2.
|
|
24029
|
+
--status takes ONE snapshot instead of waiting at
|
|
24030
|
+
all \u2014 exit 0 if it finished, 2 if it is still going.
|
|
24031
|
+
Full page: genex wait --help.
|
|
23934
24032
|
genex wait --all One status line per generation this project has
|
|
23935
24033
|
enqueued (done/running/queued/failed) \u2014 a single
|
|
23936
24034
|
API refresh, never blocks, never bills.
|
|
@@ -24298,6 +24396,8 @@ ${c.bold("Workflow")}
|
|
|
24298
24396
|
--user-approved raises it (player's say-so first).
|
|
24299
24397
|
genex wait <id> Pick up a generation enqueued with --no-wait.
|
|
24300
24398
|
Never bills \u2014 re-running a generate command does.
|
|
24399
|
+
--timeout <seconds> bounds the wait (it keeps
|
|
24400
|
+
running; exit 2); --status is one snapshot and out.
|
|
24301
24401
|
genex wait --all One status line per generation from this folder.
|
|
24302
24402
|
genex auth [--force] Connect this machine (or switch accounts).
|
|
24303
24403
|
genex tools Re-run setup here (refresh skills + rules).
|
|
@@ -24486,6 +24586,46 @@ ${c.bold("Content filter")}
|
|
|
24486
24586
|
a body) as body horror \u2014 describe the object or machine instead: "a
|
|
24487
24587
|
statue-like figure threaded into the wall" passes where "cables entering her
|
|
24488
24588
|
spine" fails. Rejections are never retryable; reword and re-run.
|
|
24589
|
+
`,
|
|
24590
|
+
wait: `${c.bold("genex wait")} \u2014 attach to a generation already running, or just ask how it is doing.
|
|
24591
|
+
|
|
24592
|
+
${c.bold("Usage")}
|
|
24593
|
+
genex wait <id> Block until it finishes, then print its asset URL(s)
|
|
24594
|
+
(and save the files, in a Genex Tools workspace).
|
|
24595
|
+
Never creates and never bills a generation \u2014 safe
|
|
24596
|
+
to re-run.
|
|
24597
|
+
genex wait <id> --timeout <secs> The same wait, with a ceiling. On expiry it prints
|
|
24598
|
+
the generation's current row, says it is STILL
|
|
24599
|
+
RUNNING server-side, prints the command that resumes
|
|
24600
|
+
the wait, and exits 2. Nothing is cancelled.
|
|
24601
|
+
genex wait <id> --status ONE snapshot and out \u2014 no polling, no sleeping.
|
|
24602
|
+
Prints the same row. Exits 0 if the generation is
|
|
24603
|
+
terminal (completed OR failed), 2 if it is still
|
|
24604
|
+
running. Reach for this instead of hand-polling.
|
|
24605
|
+
genex wait --all One status line per generation this folder enqueued
|
|
24606
|
+
\u2014 a single API refresh, never blocks, never bills.
|
|
24607
|
+
|
|
24608
|
+
${c.bold("Exit codes")}
|
|
24609
|
+
0 finished \u2014 or, with --status, terminal (completed OR failed)
|
|
24610
|
+
1 something went wrong: unknown id, not authorized, unreachable API, or (without
|
|
24611
|
+
--status) the generation itself failed
|
|
24612
|
+
2 still running \u2014 you stopped waiting, or you only asked
|
|
24613
|
+
|
|
24614
|
+
${c.bold("Flags")}
|
|
24615
|
+
--status Wins over --timeout: it is already a zero-wait mode, so the two
|
|
24616
|
+
together is not an error, it is --status. With --all it changes
|
|
24617
|
+
nothing \u2014 that surface never blocks either way.
|
|
24618
|
+
--timeout <seconds> Bounds a blocking wait only. Ignored by --status and by --all.
|
|
24619
|
+
--json One machine-readable object: the status row for --status and for a
|
|
24620
|
+
--timeout expiry (which adds "timedOut": true), the terminal
|
|
24621
|
+
result otherwise.
|
|
24622
|
+
--open Open the finished asset in your browser.
|
|
24623
|
+
--out-dir <dir> (Genex Tools) where the saved files land. --no-download: URL only.
|
|
24624
|
+
|
|
24625
|
+
${c.bold("Why --status exists")}
|
|
24626
|
+
Without a bound this command blocks for the per-kind ceiling \u2014 up to 30 minutes on a
|
|
24627
|
+
character stage. Callers hand-polled around that, and hand-polling loses every line
|
|
24628
|
+
the wait path prints. Ask once, act on the answer, keep building.
|
|
24489
24629
|
`
|
|
24490
24630
|
};
|
|
24491
24631
|
function parseArgs(argv) {
|
|
@@ -24740,6 +24880,9 @@ function parseArgs(argv) {
|
|
|
24740
24880
|
case "--all":
|
|
24741
24881
|
parsed.options.all = true;
|
|
24742
24882
|
break;
|
|
24883
|
+
case "--status":
|
|
24884
|
+
parsed.options.status = true;
|
|
24885
|
+
break;
|
|
24743
24886
|
default: {
|
|
24744
24887
|
if (needsValue.has(arg)) {
|
|
24745
24888
|
const value = argv[i++];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@genex-ai/cli-demo",
|
|
3
|
-
"version": "1.34.
|
|
3
|
+
"version": "1.34.7-dev.719",
|
|
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": {
|
|
@@ -161,7 +161,7 @@ downloads the game too, binary assets and all:
|
|
|
161
161
|
|
|
162
162
|
```bash
|
|
163
163
|
mkdir my-game && cd my-game
|
|
164
|
-
npx @genex-ai/cli-demo@
|
|
164
|
+
npx @genex-ai/cli-demo@dev link <slug> # slug = the name in the play URL
|
|
165
165
|
npm install
|
|
166
166
|
```
|
|
167
167
|
|
|
@@ -225,7 +225,7 @@ Safe to run any time — genex-owned skills are refreshed to the latest version,
|
|
|
225
225
|
and your own files are never touched:
|
|
226
226
|
|
|
227
227
|
```bash
|
|
228
|
-
npx @genex-ai/cli-demo@
|
|
228
|
+
npx @genex-ai/cli-demo@dev init
|
|
229
229
|
```
|
|
230
230
|
|
|
231
231
|
Use `--force` only if you intentionally want your own existing files overwritten
|
|
@@ -265,7 +265,8 @@ document.addEventListener("keydown", (e) => {
|
|
|
265
265
|
non-negotiable part.
|
|
266
266
|
- **Dashboard embed:** no setup needed — the platform's game frame grants keyboard
|
|
267
267
|
lock (and pointer lock + fullscreen), so Escape-to-pause works the same inside
|
|
268
|
-
|
|
268
|
+
the game's page (`<dashboard>/<slug>`, `/draft/<slug>`) as it does standalone
|
|
269
|
+
(`<slug>.genex.technology`).
|
|
269
270
|
|
|
270
271
|
A mouse-aim game earns its keep with a **look-sensitivity slider** in the pause
|
|
271
272
|
menu — trackpads feel slower than mice, so let the player tune it. On the bundled
|
|
@@ -43,7 +43,7 @@ paths the hosted build will not), then hand over the link.
|
|
|
43
43
|
never localhost, never a file path:**
|
|
44
44
|
|
|
45
45
|
- draft: `<dashboard>/draft/<slug>`
|
|
46
|
-
- public, once published: `<dashboard
|
|
46
|
+
- public, once published: `<dashboard>/<slug>`
|
|
47
47
|
|
|
48
48
|
`<dashboard>` is the first `dashboardOrigins` entry in `.genex/project.json`
|
|
49
49
|
and `<slug>` is the `slug` there. `preview` prints the exact page link as
|
|
@@ -38,7 +38,7 @@ update, so update immediately.)
|
|
|
38
38
|
Run exactly the command the nudge printed, from the game project root:
|
|
39
39
|
|
|
40
40
|
```bash
|
|
41
|
-
npm i -D @genex-ai/cli-demo@
|
|
41
|
+
npm i -D @genex-ai/cli-demo@dev # the genex CLI (a dev dependency)
|
|
42
42
|
npm i @genex-ai/embed-sdk@latest # identity/saves SDK (ships inside the game)
|
|
43
43
|
npm i @genex-ai/multiplayer@latest # multiplayer SDK (only if the game uses it)
|
|
44
44
|
```
|