@genex-ai/cli-demo 1.36.19-dev.1076 → 1.36.20-dev.1078

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
@@ -10853,13 +10853,77 @@ async function ensureRealDir(dir, label) {
10853
10853
  }
10854
10854
  }
10855
10855
 
10856
+ // src/lib/web-version.ts
10857
+ var NATIVE_PAGE_WOULD_BE_REPLACED = "native_page_would_be_replaced";
10858
+ function isNativePageRefusal(body) {
10859
+ return body?.error === NATIVE_PAGE_WOULD_BE_REPLACED || body?.code === NATIVE_PAGE_WOULD_BE_REPLACED;
10860
+ }
10861
+ function explainNativePageRefusal(log, command, uploaded = false) {
10862
+ log.error("This game's page currently shows its Mac/Windows/Linux app \u2014 nothing was changed.");
10863
+ log.dim(" Making this web build live would replace that page: visitors would get this web");
10864
+ log.dim(" build instead of the app.");
10865
+ if (uploaded) log.dim(" The build was uploaded to the preview channel only; players never see it there.");
10866
+ log.dim(" Re-run with --replace-native-page ONLY if this folder really is the game's web version:");
10867
+ log.dim(` npx genex ${command} --replace-native-page`);
10868
+ }
10869
+ var REFUSALS = {
10870
+ no_native_build: [
10871
+ "This game has no Mac, Windows or Linux build players get, so removing its web version would leave nothing to play \u2014 nothing was changed.",
10872
+ " A native build waiting for review does not count yet.",
10873
+ " To take the game out of the gallery, unpublish it; to remove it entirely, delete it from the dashboard."
10874
+ ],
10875
+ game_taken_down: [
10876
+ "This game was taken down by moderation; its build is kept for the review \u2014 nothing was changed."
10877
+ ],
10878
+ web_removal_incomplete: [
10879
+ "The removal stopped partway: the web build no longer serves, but it is not fully gone yet.",
10880
+ " Run `npx genex web remove --yes` again to finish."
10881
+ ],
10882
+ release_conflict: ["Another release of this game is in progress \u2014 nothing was changed. Try again in a moment."]
10883
+ };
10884
+ async function removeWebVersion(ctx, log) {
10885
+ let res;
10886
+ try {
10887
+ res = await apiFetch(`${ctx.apiUrl}/api/games/${ctx.projectId}/web`, {
10888
+ method: "DELETE",
10889
+ headers: { Authorization: `Bearer ${ctx.token}` }
10890
+ });
10891
+ } catch (err) {
10892
+ log.error(`Couldn't reach the API at ${ctx.apiUrl}: ${String(err)}`);
10893
+ return null;
10894
+ }
10895
+ const body = await res.json().catch(() => null);
10896
+ if (res.ok) return { removed: body?.removed === true, web: false };
10897
+ const words2 = body?.error ? REFUSALS[body.error] : void 0;
10898
+ if (words2 && body?.error) {
10899
+ const [first, ...rest] = words2;
10900
+ if (first) log.error(first);
10901
+ for (const line of rest) log.dim(line);
10902
+ return { refused: body.error };
10903
+ }
10904
+ if (res.status === 401) {
10905
+ log.error("Not authorized (HTTP 401) \u2014 your sign-in expired or was revoked. Run `genex link <your-game>`.");
10906
+ return null;
10907
+ }
10908
+ if (res.status === 404) {
10909
+ log.error("That game wasn't found on your account (HTTP 404), or this Genex server can't remove a web version yet.");
10910
+ return null;
10911
+ }
10912
+ log.error(`Couldn't remove the web version (HTTP ${res.status})${body?.error ? `: ${body.error}` : ""}.`);
10913
+ return null;
10914
+ }
10915
+
10856
10916
  // src/lib/promote.ts
10857
- async function promoteBuild(apiUrl, projectId, token, log) {
10917
+ async function promoteBuild(apiUrl, projectId, token, log, opts = {}) {
10858
10918
  let res;
10859
10919
  try {
10860
10920
  res = await apiFetch(`${apiUrl}/api/games/${projectId}/promote`, {
10861
10921
  method: "POST",
10862
- headers: { Authorization: `Bearer ${token}` }
10922
+ headers: {
10923
+ Authorization: `Bearer ${token}`,
10924
+ ...opts.replaceNativePage ? { "Content-Type": "application/json" } : {}
10925
+ },
10926
+ ...opts.replaceNativePage ? { body: JSON.stringify({ replaceNativePage: true }) } : {}
10863
10927
  });
10864
10928
  } catch (err) {
10865
10929
  log.error(`Couldn't reach the API at ${apiUrl}: ${String(err)}`);
@@ -10867,6 +10931,11 @@ async function promoteBuild(apiUrl, projectId, token, log) {
10867
10931
  }
10868
10932
  if (res.status === 409) {
10869
10933
  const body2 = await res.json().catch(() => null);
10934
+ if (isNativePageRefusal(body2)) {
10935
+ const command = opts.command ?? "promote";
10936
+ explainNativePageRefusal(log, command, command === "publish");
10937
+ return null;
10938
+ }
10870
10939
  if (body2?.code === "live_is_newer") {
10871
10940
  log.error("The live build is newer than your preview \u2014 nothing was changed.");
10872
10941
  log.dim(" This happens after the live version was restored, or after a later publish.");
@@ -10999,7 +11068,10 @@ async function runPublish(opts) {
10999
11068
  return;
11000
11069
  }
11001
11070
  log.step("Making it live\u2026");
11002
- if (!await promoteBuild(apiUrl, meta.id, token, log)) {
11071
+ if (!await promoteBuild(apiUrl, meta.id, token, log, {
11072
+ replaceNativePage: opts.replaceNativePage,
11073
+ command: "publish"
11074
+ })) {
11003
11075
  process.exitCode = 1;
11004
11076
  return;
11005
11077
  }
@@ -11026,6 +11098,7 @@ async function runPublish(opts) {
11026
11098
  if (opts.license) body.license = opts.license;
11027
11099
  if (opts.purchaseUrl) body.purchaseUrl = opts.purchaseUrl;
11028
11100
  if (opts.productName) body.productName = opts.productName;
11101
+ if (opts.replaceNativePage) body.replaceNativePage = true;
11029
11102
  body.matchmaking = detections.matchmaking ?? null;
11030
11103
  body.gameStateUsed = detections.gameStateUsed;
11031
11104
  body.mobileControls = detections.mobileControls;
@@ -11047,6 +11120,11 @@ async function runPublish(opts) {
11047
11120
  const listedOnDisk = await readProject();
11048
11121
  await writeProject({ ...listedOnDisk ?? meta, status: "published" });
11049
11122
  const listed = await res.json().catch(() => null);
11123
+ if (listed?.project?.commitSha === null && listed.project.stagingCommitSha) {
11124
+ log.warn("Listed with its Mac/Windows/Linux app \u2014 your preview was not made live over the app's page.");
11125
+ log.dim(" Re-run with --replace-native-page ONLY if this folder really is the game's web version:");
11126
+ log.dim(" npx genex publish --no-push --replace-native-page");
11127
+ }
11050
11128
  if (opts.regenerateCover && listed?.project?.coverAiEnabled === false) {
11051
11129
  log.dim(" --regenerate-cover did nothing: this stand doesn't make AI covers.");
11052
11130
  log.dim(" The cover is a real frame of the game \u2014 set one with `npx genex cover <file>`.");
@@ -11086,7 +11164,10 @@ async function runPromote(opts) {
11086
11164
  return;
11087
11165
  }
11088
11166
  log.step("Making your previewed build live\u2026");
11089
- const result = await promoteBuild(apiUrl, meta.id, token, log);
11167
+ const result = await promoteBuild(apiUrl, meta.id, token, log, {
11168
+ replaceNativePage: opts.replaceNativePage,
11169
+ command: "promote"
11170
+ });
11090
11171
  if (!result) {
11091
11172
  process.exitCode = 1;
11092
11173
  return;
@@ -30714,6 +30795,75 @@ async function runNative(opts) {
30714
30795
  if (!await run5(opts, log)) process.exitCode = 1;
30715
30796
  }
30716
30797
 
30798
+ // src/commands/web.ts
30799
+ function loggerFor3(opts) {
30800
+ if (!opts.json) return createLogger({ quiet: opts.quiet });
30801
+ const err = (s) => {
30802
+ if (!opts.quiet) writeSafely(process.stderr, `${s}
30803
+ `);
30804
+ };
30805
+ return {
30806
+ info: (m) => err(`i ${m}`),
30807
+ success: (m) => err(`\u2713 ${m}`),
30808
+ warn: (m) => err(`! ${m}`),
30809
+ error: (m) => writeSafely(process.stderr, `\u2717 ${m}
30810
+ `),
30811
+ step: (m) => err(`\u203A ${m}`),
30812
+ dim: err,
30813
+ plain: err
30814
+ };
30815
+ }
30816
+ async function runRemove2(opts, log) {
30817
+ if (inHostedSession()) {
30818
+ log.error("`genex web remove` is not available inside a Genex chat session \u2014 it changes what players get.");
30819
+ return false;
30820
+ }
30821
+ if (!opts.yes) {
30822
+ log.warn("This removes the game's web version and keeps its Mac/Windows/Linux apps.");
30823
+ log.dim(" The game's page goes back to showing the apps, and its play address stops serving a web build.");
30824
+ log.dim(" Both web builds go: the live one and the preview. They cannot be restored; publish again to bring one back.");
30825
+ log.dim(" The apps, the gallery listing, likes and the cover are not touched.");
30826
+ log.dim(" Refused when the game has no app players get \u2014 unpublish or delete such a game instead.");
30827
+ log.dim(` Re-run with --yes to do it: ${c.cyan("npx genex web remove --yes")}`);
30828
+ return false;
30829
+ }
30830
+ const meta = await readProject();
30831
+ if (!meta) {
30832
+ log.error("No genex project here. Run `genex link <game>` in this directory first.");
30833
+ return false;
30834
+ }
30835
+ const apiUrl = opts.apiUrl ?? getApiUrl(meta.apiUrl);
30836
+ const token = opts.token ?? await readUserToken(opts.envPath, apiUrl);
30837
+ if (!token) {
30838
+ log.error("Not authorized. Run `genex link <your-game>` to sign in.");
30839
+ return false;
30840
+ }
30841
+ log.step("Removing the web version\u2026");
30842
+ const result = await removeWebVersion({ apiUrl, projectId: meta.id, token }, log);
30843
+ if (!result) return false;
30844
+ if ("refused" in result) {
30845
+ if (opts.json) console.log(JSON.stringify({ error: result.refused }));
30846
+ return false;
30847
+ }
30848
+ if (opts.json) console.log(JSON.stringify(result));
30849
+ if (result.removed) log.success("Removed the web version. The game's page shows its apps again.");
30850
+ else log.success("This game has no web version on Genex \u2014 nothing to remove.");
30851
+ return true;
30852
+ }
30853
+ var SUBCOMMANDS3 = {
30854
+ remove: runRemove2
30855
+ };
30856
+ async function runWeb(opts) {
30857
+ const log = loggerFor3(opts);
30858
+ const run5 = opts.name ? SUBCOMMANDS3[opts.name] : void 0;
30859
+ if (!run5) {
30860
+ log.error("Unknown web command. Use `genex web remove [--yes] [--json]`.");
30861
+ process.exitCode = 1;
30862
+ return;
30863
+ }
30864
+ if (!await run5(opts, log)) process.exitCode = 1;
30865
+ }
30866
+
30717
30867
  // src/lib/costs.ts
30718
30868
  var TYPICAL_CREDITS = {
30719
30869
  model: 35,
@@ -30826,6 +30976,9 @@ ${c.bold("Usage")}
30826
30976
  genex promote [options] Make the previewed build live for players. No
30827
30977
  rebuild \u2014 the exact build you previewed.
30828
30978
  genex publish [options] Build + push + make live, then list it in the gallery.
30979
+ Both refuse to replace a page that shows only the
30980
+ game's Mac/Windows/Linux app unless given
30981
+ --replace-native-page.
30829
30982
  genex accept Read the current Terms, Privacy and Content
30830
30983
  policies and agree, so publishing is allowed.
30831
30984
  Needs a real terminal: agreeing is a contract,
@@ -30838,6 +30991,9 @@ ${c.bold("Usage")}
30838
30991
  "publish" takes a .zip, a .app or the build's
30839
30992
  folder and makes it what desktop players install
30840
30993
  for its platform and processor, live at once.
30994
+ genex web remove [--yes] [--json] Remove this game's web version and keep its
30995
+ Mac/Windows/Linux apps: the page shows the apps
30996
+ again. Refused for a game with no app.
30841
30997
  genex cover [<file>] Show this game's cover and where it came from, or
30842
30998
  make <file> (PNG/JPEG/WebP, \u22648 MB) the cover: one
30843
30999
  real frame of the game \u2014 its signature moment,
@@ -31189,6 +31345,9 @@ ${c.bold("Options for `preview` / `publish`")}
31189
31345
  --purchase-url <url> (operator) External purchase URL \u2014 marks the game a
31190
31346
  paid-product demo ("Get \u2026" replaces Remix). Admin-only.
31191
31347
  --product-name <name> (operator) Product name for the "Get {name}" button.
31348
+ --replace-native-page (publish, promote) Let this web build replace a page that
31349
+ shows only the game's Mac/Windows/Linux app. Use it ONLY when
31350
+ this folder really is the game's web version.
31192
31351
  --api-url <url> (publish) Override the API base URL.
31193
31352
  --env <path> Token env file (default: ~/.genex/env).
31194
31353
 
@@ -31676,6 +31835,24 @@ ${c.bold("Content filter")}
31676
31835
  a body) as body horror \u2014 describe the object or machine instead: "a
31677
31836
  statue-like figure threaded into the wall" passes where "cables entering her
31678
31837
  spine" fails. Rejections are never retryable; reword and re-run.
31838
+ `,
31839
+ web: `${c.bold("genex web")} \u2014 this game's web version, beside its Mac/Windows/Linux apps.
31840
+
31841
+ ${c.bold("Usage")}
31842
+ genex web remove Explain what removing the web version does, and stop.
31843
+ genex web remove --yes Remove it: both web builds (live and preview) go, and
31844
+ the game's page shows its apps again. The apps, the
31845
+ gallery listing, likes and the cover stay.
31846
+ --json One JSON object on stdout ({"removed": true, "web": false},
31847
+ or {"error": "<code>"} when refused).
31848
+
31849
+ ${c.bold("Refused when")}
31850
+ The game has no app players get (a build waiting for review does not count): removing
31851
+ the web version would leave nothing to play \u2014 unpublish or delete the game instead.
31852
+
31853
+ ${c.bold("Notes")}
31854
+ \`genex publish\` and \`genex promote\` never replace a page that shows only the apps
31855
+ unless given --replace-native-page.
31679
31856
  `,
31680
31857
  native: `${c.bold("genex native")} \u2014 a Unity, Godot or Unreal build players install from the Genex desktop app.
31681
31858
 
@@ -32032,6 +32209,12 @@ function parseArgs(argv) {
32032
32209
  case "--gallery":
32033
32210
  parsed.options.gallery = true;
32034
32211
  break;
32212
+ // `genex publish|promote --replace-native-page`: the owner's word that this web
32213
+ // build may replace a page that shows only the game's apps. Its own flag, never
32214
+ // a reuse of --yes/--force, because the flag must say what it consents to.
32215
+ case "--replace-native-page":
32216
+ parsed.options.replaceNativePage = true;
32217
+ break;
32035
32218
  // A pair rather than one flag taking a value: `--resellable false` reads
32036
32219
  // as a filename to every shell user who has ever mistyped it, and the two
32037
32220
  // states have to be distinguishable from ABSENT so `shop set` can leave
@@ -32855,6 +33038,9 @@ async function main() {
32855
33038
  case "native":
32856
33039
  await runNative(parsed.options);
32857
33040
  break;
33041
+ case "web":
33042
+ await runWeb(parsed.options);
33043
+ break;
32858
33044
  case "cover":
32859
33045
  await runCover({ ...parsed.options, imagePath: parsed.options.name });
32860
33046
  break;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@genex-ai/cli-demo",
3
- "version": "1.36.19-dev.1076",
3
+ "version": "1.36.20-dev.1078",
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": {
@@ -104,5 +104,12 @@ Ask once per round of work, in one line, and keep working while you wait.
104
104
  - **`preview` says the folder is behind another machine** (`stale_source`):
105
105
  `npx genex pull` takes the newer draft, then redo the change — never
106
106
  `--force` over somebody else's work.
107
+ - **`promote` or `publish` says the page shows the game's Mac/Windows/Linux
108
+ app** (`native_page_would_be_replaced`): the game ships as desktop apps, and
109
+ this web build would replace the page that offers them. Nothing changed. Ask
110
+ the user whether this folder really is the game's web version; only on a
111
+ clear yes, re-run the same command with `--replace-native-page`. A web
112
+ version that went up by mistake comes off with `npx genex web remove --yes`
113
+ (the apps stay).
107
114
  - **Out of credits or a lane down**: `npx genex doctor` says which; publishing
108
115
  itself costs nothing.