@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.
@@ -7,10 +7,10 @@ import {
7
7
  isRenderMode,
8
8
  sceneSummary,
9
9
  sheetOf
10
- } from "./chunk-X5JRCQMK.js";
10
+ } from "./chunk-MRV3Q3WU.js";
11
11
  import {
12
12
  getCliVersion
13
- } from "./chunk-44SHBPOT.js";
13
+ } from "./chunk-AQW7HUPV.js";
14
14
 
15
15
  // src/commands/blender-mcp.ts
16
16
  import fs from "fs/promises";
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  c,
3
3
  getTemplatesDir
4
- } from "./chunk-44SHBPOT.js";
4
+ } from "./chunk-AQW7HUPV.js";
5
5
 
6
6
  // src/lib/blender-serve.ts
7
7
  import { spawn } from "child_process";
@@ -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 = "latest";
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" },
@@ -10,7 +10,7 @@ import {
10
10
  getGenexEnvPath,
11
11
  normalizeApiOrigin,
12
12
  originKey
13
- } from "./chunk-44SHBPOT.js";
13
+ } from "./chunk-AQW7HUPV.js";
14
14
 
15
15
  // src/lib/terms.ts
16
16
  import readline from "readline";
package/dist/index.js CHANGED
@@ -42,7 +42,7 @@ import {
42
42
  writeSecretFile,
43
43
  writeUserToken,
44
44
  writeWorkspace
45
- } from "./chunk-X5JRCQMK.js";
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-44SHBPOT.js";
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(`${new URL(dashboardUrl).origin}/draft/${project.slug}`)}`
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(`${dashboard}/draft/${meta.slug}`)}`);
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 /world/<slug> or /draft/<slug> page URL.");
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 /world/<slug> or /draft/<slug> page URL.");
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}/${project.slug}`);
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 = `${dashOrigin}/${published ? "world" : "draft"}/${p.slug}`;
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
- const page = meta.status === "published" ? "world" : "draft";
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(`${dashboard}/${meta.slug}`)}`);
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
- const page = meta.status === "published" ? "world" : "draft";
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
- return streamed === "unsupported" ? poll(apiUrl, token, id, onProgress, timeoutMs) : streamed;
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
- await sleep3(3e3);
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
- switch (v.status) {
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-JGKITVP6.js");
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-IPP442XH.js");
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.6",
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@latest link <slug> # slug = the name in the play URL
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@latest init
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
- `/world/` + `/draft/` as it does standalone (`<slug>.genex.technology`).
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>/world/<slug>`
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@latest # the genex CLI (a dev dependency)
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
  ```