@genex-ai/cli-demo 1.21.0-dev.604 → 1.22.0-dev.605

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
@@ -2394,6 +2394,26 @@ async function recordTerminal(id, status, urls, cwd = process.cwd()) {
2394
2394
  if (existing && existing.status === status) return;
2395
2395
  await append(cwd, { t: "done", id, status, ...urls?.length ? { urls } : {} });
2396
2396
  }
2397
+ async function recordShipped(commit, cwd = process.cwd()) {
2398
+ await append(cwd, { t: "ship", at: (/* @__PURE__ */ new Date()).toISOString(), ...commit ? { commit } : {} });
2399
+ }
2400
+ async function readLedgerRows(cwd = process.cwd()) {
2401
+ let raw;
2402
+ try {
2403
+ raw = await fs11.readFile(ledgerPath(cwd), "utf8");
2404
+ } catch {
2405
+ return [];
2406
+ }
2407
+ const rows = [];
2408
+ for (const line of raw.split("\n")) {
2409
+ if (!line.trim()) continue;
2410
+ try {
2411
+ rows.push(JSON.parse(line));
2412
+ } catch {
2413
+ }
2414
+ }
2415
+ return rows;
2416
+ }
2397
2417
  async function countFailed(kind, cwd = process.cwd()) {
2398
2418
  return (await readLedger(cwd)).filter((e) => e.kind === kind && e.status === "failed").length;
2399
2419
  }
@@ -4870,6 +4890,8 @@ async function deployGame(ctx, opts, log) {
4870
4890
  });
4871
4891
  }
4872
4892
  }
4893
+ await recordShipped(commit, cwd).catch(() => {
4894
+ });
4873
4895
  const index = files.find((f) => f.relPath === "index.html");
4874
4896
  const liveUrl = published.url || grant.playUrl;
4875
4897
  await waitUntilLive(liveUrl, fingerprintOf(index.bytes.toString("utf8")), opts.liveTimeoutMs ?? 2e4, log);
@@ -6885,6 +6907,63 @@ function describeGenerationFailure(kind, error) {
6885
6907
  };
6886
6908
  }
6887
6909
 
6910
+ // src/lib/generation-ceiling.ts
6911
+ var CHARACTER_KINDS = /* @__PURE__ */ new Set([
6912
+ "character",
6913
+ "character_concept",
6914
+ "character_preview",
6915
+ "character_animation",
6916
+ "character_motion"
6917
+ ]);
6918
+ var DEFAULT_CEILING = 12;
6919
+ var DEFAULT_CHARACTER_CEILING = 3;
6920
+ function envNumber(raw, fallback) {
6921
+ if (raw === void 0 || raw.trim() === "") return fallback;
6922
+ const n = Number(raw);
6923
+ return Number.isFinite(n) && n >= 0 ? Math.floor(n) : fallback;
6924
+ }
6925
+ function ceilingConfig(env = process.env) {
6926
+ return {
6927
+ ceiling: envNumber(env.GENEX_GENERATION_CEILING, DEFAULT_CEILING),
6928
+ characterCeiling: envNumber(env.GENEX_CHARACTER_CEILING, DEFAULT_CHARACTER_CEILING)
6929
+ };
6930
+ }
6931
+ function unshippedCounts(rows) {
6932
+ let lastShip = -1;
6933
+ for (let i = rows.length - 1; i >= 0; i -= 1) {
6934
+ if (rows[i]?.t === "ship") {
6935
+ lastShip = i;
6936
+ break;
6937
+ }
6938
+ }
6939
+ let total = 0;
6940
+ let character = 0;
6941
+ for (let i = lastShip + 1; i < rows.length; i += 1) {
6942
+ const r = rows[i];
6943
+ if (r?.t !== "q") continue;
6944
+ total += 1;
6945
+ if (typeof r.kind === "string" && CHARACTER_KINDS.has(r.kind)) character += 1;
6946
+ }
6947
+ return { total, character };
6948
+ }
6949
+ function ceilingVerdict(input) {
6950
+ const { kind, counts, config } = input;
6951
+ const isCharacter = CHARACTER_KINDS.has(kind);
6952
+ if (isCharacter && config.characterCeiling > 0 && counts.character >= config.characterCeiling) {
6953
+ return {
6954
+ allow: false,
6955
+ reason: `${counts.character} character generations are already waiting and none has been seen in the game yet. Run \`npx genex preview\` \u2014 put the bodies you have in front of the player, look at them, then generate the rest. (A cast fanned out before one body is on screen is how a build ends up with an asset library and an empty world. Raise or remove this with GENEX_CHARACTER_CEILING; 0 turns it off.)`
6956
+ };
6957
+ }
6958
+ if (config.ceiling > 0 && counts.total >= config.ceiling) {
6959
+ return {
6960
+ allow: false,
6961
+ reason: `${counts.total} generations are already waiting and none of them has been shipped yet. Run \`npx genex preview\` \u2014 it takes seconds, it is free, and it resets this immediately. (There is no total here: preview between batches and generate as much as the game needs. Raise or remove it with GENEX_GENERATION_CEILING; 0 turns it off.)`
6962
+ };
6963
+ }
6964
+ return { allow: true };
6965
+ }
6966
+
6888
6967
  // src/lib/download-assets.ts
6889
6968
  import fs16 from "fs/promises";
6890
6969
  import path15 from "path";
@@ -7400,6 +7479,15 @@ async function runGenerate(kind, opts) {
7400
7479
  log.dim(` ${prompt}`);
7401
7480
  if (skyboxGuard?.guarded) log.dim(" \u21B3 environment-only guard applied (--raw to disable)");
7402
7481
  log.plain("");
7482
+ {
7483
+ const counts = unshippedCounts(await readLedgerRows());
7484
+ const verdict = ceilingVerdict({ kind, counts, config: ceilingConfig() });
7485
+ if (!verdict.allow) {
7486
+ log.error(verdict.reason);
7487
+ process.exitCode = 1;
7488
+ return;
7489
+ }
7490
+ }
7403
7491
  let id;
7404
7492
  try {
7405
7493
  const res = await apiFetch(`${apiUrl}/api/generations`, {
@@ -18618,6 +18706,15 @@ async function postWorkflow(url, token, body) {
18618
18706
  }
18619
18707
  async function runWorkflow(args) {
18620
18708
  const log = createLogger({ quiet: args.opts.quiet || args.opts.json });
18709
+ {
18710
+ const counts = unshippedCounts(await readLedgerRows());
18711
+ const verdict = ceilingVerdict({ kind: args.kind, counts, config: ceilingConfig() });
18712
+ if (!verdict.allow) {
18713
+ log.error(verdict.reason);
18714
+ process.exitCode = 1;
18715
+ return;
18716
+ }
18717
+ }
18621
18718
  const created = await postWorkflow(`${args.ctx.apiUrl}${args.createPath}`, args.ctx.token, args.body);
18622
18719
  if (!created) {
18623
18720
  process.exitCode = 1;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@genex-ai/cli-demo",
3
- "version": "1.21.0-dev.604",
3
+ "version": "1.22.0-dev.605",
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": {
@@ -204,7 +204,11 @@ together with `--no-wait` while you scaffold. Before a second batch starts,
204
204
  every id in the first is collected with `npx genex wait --all`, loaded from a
205
205
  file in `src/`, and seen in a capture. A batch begun while the previous one is
206
206
  still uncollected is how a game ends up with an asset library and an empty
207
- world. Preserve every id, URL and wiring state across a compaction.
207
+ world. This is enforced, not merely advised: unshipped generations are counted
208
+ and the generate commands refuse past a ceiling, which `npx genex preview`
209
+ resets to zero. There is no total — preview between batches and generate as
210
+ much as the game needs. Preserve every id, URL and wiring state across a
211
+ compaction.
208
212
 
209
213
  ## 5. Execute by working mode
210
214
 
@@ -45,6 +45,24 @@ Skip this and the mesh is a speck or a wall — it is the single most common way
45
45
  generated asset ships broken. Rigged bodies mismeasure here; those go through
46
46
  the controller's own boot path, not this snippet.
47
47
 
48
+ ## Generate in batches you can finish
49
+
50
+ The belt has a **shipping ratchet**: generations that have not been shown to the
51
+ player are counted, and the count resets to zero every time you `npx genex
52
+ preview`. Hit it and the command refuses with the number and the fix.
53
+
54
+ It is not a spend limit and there is no total — preview between batches and
55
+ generate as much as the game needs. What it stops is one thing: fanning out a
56
+ whole cast or a shelf of props before the player has seen any of it. If you are
57
+ refused, you are not out of anything. You have work that nobody has looked at.
58
+
59
+ ```bash
60
+ npx genex model "..." --no-wait # a batch you can finish
61
+ npx genex wait --all # collect it
62
+ # wire it, then:
63
+ npx genex preview # free, seconds — and the count is zero again
64
+ ```
65
+
48
66
  ## The one rule
49
67
 
50
68
  An asset is not done when the command exits. It is done when something in