@genex-ai/cli-demo 1.4.0 → 1.4.1-dev.376

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
@@ -8,7 +8,7 @@ import fs from "fs";
8
8
  import os from "os";
9
9
  import path from "path";
10
10
  import { fileURLToPath } from "url";
11
- var RAW_CHANNEL = "latest";
11
+ var RAW_CHANNEL = "dev";
12
12
  var CLI_CHANNEL = RAW_CHANNEL === "dev" ? "dev" : "latest";
13
13
  var STANDS = {
14
14
  prod: { api: "https://api.genex.games", dashboard: "https://genex.games" },
@@ -4890,6 +4890,157 @@ async function runMakeRemixable(opts) {
4890
4890
  }
4891
4891
  }
4892
4892
 
4893
+ // src/commands/domain.ts
4894
+ var SUBCOMMANDS = ["add", "list", "verify", "remove"];
4895
+ function statusWord(d) {
4896
+ if (d.status === "active") return c.green("live");
4897
+ if (d.status === "failed") return c.red("stopped");
4898
+ return c.dim("waiting for DNS");
4899
+ }
4900
+ async function runDomain(opts) {
4901
+ const log = createLogger({ quiet: opts.quiet });
4902
+ const sub = (opts.name ?? "list").trim();
4903
+ if (!SUBCOMMANDS.includes(sub)) {
4904
+ log.error(`Unknown subcommand \`${sub}\`. Use: ${SUBCOMMANDS.join(", ")}.`);
4905
+ process.exitCode = 1;
4906
+ return;
4907
+ }
4908
+ const needsHost = sub !== "list";
4909
+ const hostname = opts.hostname?.trim();
4910
+ if (needsHost && !hostname) {
4911
+ log.error(`\`genex domain ${sub}\` needs a hostname, e.g. ${c.cyan(`genex domain ${sub} play.yourdomain.com`)}.`);
4912
+ process.exitCode = 1;
4913
+ return;
4914
+ }
4915
+ const meta = await readProject();
4916
+ if (!meta?.id) {
4917
+ log.error("This folder isn't linked to a game.");
4918
+ log.dim(` Run ${c.cyan("genex link")} (or ${c.cyan("genex list")} to find the slug) first.`);
4919
+ process.exitCode = 1;
4920
+ return;
4921
+ }
4922
+ const apiUrl = getApiUrl(opts.apiUrl ?? meta.apiUrl);
4923
+ let token = opts.token ?? await readUserToken(opts.envPath);
4924
+ if (!token) {
4925
+ if (opts.noAuth) {
4926
+ log.error("Not signed in. Re-run without --no-auth to connect.");
4927
+ process.exitCode = 1;
4928
+ return;
4929
+ }
4930
+ log.plain("Not signed in \u2014 connecting\u2026");
4931
+ try {
4932
+ token = await authorize(apiUrl, getAuthUrl(opts.authUrl), {
4933
+ log,
4934
+ inlineWaitMs: opts.timeoutSec ? opts.timeoutSec * 1e3 : void 0
4935
+ });
4936
+ } catch (err) {
4937
+ if (err instanceof AuthPendingError) {
4938
+ printAuthHandoff(log, err);
4939
+ log.dim(`Then re-run ${c.cyan(`genex domain ${sub}`)}.`);
4940
+ return;
4941
+ }
4942
+ log.error(`Sign-in didn't complete: ${err instanceof Error ? err.message : String(err)}`);
4943
+ process.exitCode = 1;
4944
+ return;
4945
+ }
4946
+ await writeUserToken(token, opts.envPath);
4947
+ }
4948
+ const base = `${apiUrl}/api/projects/${encodeURIComponent(meta.id)}/domains`;
4949
+ const auth = { Authorization: `Bearer ${token}` };
4950
+ const call = (url, init2) => apiFetch(url, { ...init2, headers: { ...auth, "Content-Type": "application/json", ...init2?.headers ?? {} } });
4951
+ let res;
4952
+ try {
4953
+ if (sub === "list") res = await call(base);
4954
+ else if (sub === "add") res = await call(base, { method: "POST", body: JSON.stringify({ hostname }) });
4955
+ else if (sub === "verify")
4956
+ res = await call(`${base}/${encodeURIComponent(hostname)}/verify`, { method: "POST" });
4957
+ else res = await call(`${base}/${encodeURIComponent(hostname)}`, { method: "DELETE" });
4958
+ } catch (err) {
4959
+ log.error(`Couldn't reach the API at ${apiUrl}.`);
4960
+ log.dim(` ${String(err)}`);
4961
+ process.exitCode = 1;
4962
+ return;
4963
+ }
4964
+ if (!res.ok) {
4965
+ if (await printedStructuredError(res)) {
4966
+ process.exitCode = 1;
4967
+ return;
4968
+ }
4969
+ const body = await res.json().catch(() => null);
4970
+ if (res.status === 503) {
4971
+ log.error("Custom domains aren't available on this Genex environment yet.");
4972
+ } else if (res.status === 404) {
4973
+ log.error(sub === "list" || sub === "add" ? "That game wasn't found on this account." : `${hostname} isn't connected to this game.`);
4974
+ } else {
4975
+ log.error(body?.message ?? body?.error ?? `Request failed (${res.status}).`);
4976
+ }
4977
+ process.exitCode = 1;
4978
+ return;
4979
+ }
4980
+ const data = await res.json();
4981
+ if (opts.json) {
4982
+ log.plain(JSON.stringify(data, null, 2));
4983
+ return;
4984
+ }
4985
+ if (sub === "list") {
4986
+ const rows = data.domains ?? [];
4987
+ if (rows.length === 0) {
4988
+ log.plain("No domains connected to this game.");
4989
+ log.dim(` ${c.cyan("genex domain add play.yourdomain.com")} to connect one.`);
4990
+ return;
4991
+ }
4992
+ for (const d of rows) log.plain(` ${d.hostname.padEnd(34)} ${statusWord(d)}`);
4993
+ return;
4994
+ }
4995
+ if (sub === "add") {
4996
+ if (data.supported === false) {
4997
+ log.plain(String(data.message ?? "That domain's DNS host doesn't support one-click setup."));
4998
+ const manual = data.manual;
4999
+ const records = Array.isArray(manual?.records) ? manual.records : [];
5000
+ if (records.length > 0) {
5001
+ log.plain("");
5002
+ log.plain(" Add these two records at your DNS host:");
5003
+ log.plain("");
5004
+ for (const r of records) {
5005
+ log.plain(` ${c.cyan(String(r.type ?? ""))} ${String(r.name ?? "")}`);
5006
+ log.plain(` ${String(r.value ?? "")}`);
5007
+ }
5008
+ log.plain("");
5009
+ if (manual?.apexHint) {
5010
+ log.dim(" At a root domain your host may call the first one ALIAS or ANAME.");
5011
+ }
5012
+ log.dim(` Then run: genex domain verify ${String(data.hostname ?? "")}`);
5013
+ log.dim(" DNS can take a few minutes to spread.");
5014
+ return;
5015
+ }
5016
+ log.dim(" Your game stays reachable at its usual address.");
5017
+ return;
5018
+ }
5019
+ const applyUrl = String(data.applyUrl ?? "");
5020
+ const providerName = String(data.providerName ?? "your DNS host");
5021
+ log.success(`${data.hostname} can be connected through ${providerName}.`);
5022
+ log.plain("");
5023
+ log.plain(` Approve the DNS change here: ${c.cyan(applyUrl)}`);
5024
+ log.plain("");
5025
+ log.dim(" One click writes the records. Nothing to copy or paste.");
5026
+ if (!applyUrl.startsWith("https://")) {
5027
+ log.warn("That DNS host returned a setup link we could not verify \u2014 not opening it.");
5028
+ } else if (!opts.noOpen) {
5029
+ openBrowser(applyUrl, () => log.dim(" (Couldn't open a browser \u2014 use the link above.)"));
5030
+ }
5031
+ log.dim(` Then ${c.cyan(`genex domain verify ${String(data.hostname)}`)} once you have approved it.`);
5032
+ return;
5033
+ }
5034
+ if (sub === "verify") {
5035
+ const status = String(data.status ?? "");
5036
+ if (status === "active") log.success(`${hostname} is live.`);
5037
+ else if (status === "failed") log.error(`${hostname} is stopped \u2014 its verification record is missing.`);
5038
+ else log.plain(`${hostname} isn't verified yet \u2014 DNS changes can take a few minutes to spread.`);
5039
+ return;
5040
+ }
5041
+ log.success(`${hostname} disconnected.`);
5042
+ }
5043
+
4893
5044
  // src/lib/promote.ts
4894
5045
  async function promoteBuild(apiUrl, projectId, token, log) {
4895
5046
  let res;
@@ -19018,6 +19169,10 @@ ${c.bold("Usage")}
19018
19169
  genex publish [options] Build + push + make live, then list it in the gallery.
19019
19170
  genex make-remixable [options] Make THIS game remixable by everyone \u2014 migrates a
19020
19171
  private source onto a public managed genex repo.
19172
+ genex domain <sub> [host] Play this game on a domain you own:
19173
+ add | list | verify | remove. "add" opens a
19174
+ one-click approval at your DNS host \u2014 no
19175
+ records to copy or paste.
19021
19176
  genex model "<prompt>" [options] Generate a 3D model (GLB); prints a public asset URL.
19022
19177
  genex skybox "<prompt>" [options] Generate a skybox (equirect); prints a public asset URL.
19023
19178
  genex sfx "<prompt>" [options] Generate a sound effect (mp3); prints a public asset URL.
@@ -19259,6 +19414,8 @@ ${c.bold("Examples")}
19259
19414
  genex publish
19260
19415
  genex publish --categories games,vfx
19261
19416
  genex make-remixable
19417
+ genex domain add play.mygame.com
19418
+ genex domain list
19262
19419
  genex publish --no-push --title "My Game"
19263
19420
  genex model "weathered wooden barrel with iron bands"
19264
19421
  genex skybox "golden hour over a misty mountain range"
@@ -19382,6 +19539,9 @@ function parseArgs(argv) {
19382
19539
  case "--no-auth":
19383
19540
  parsed.options.noAuth = true;
19384
19541
  break;
19542
+ case "--no-open":
19543
+ parsed.options.noOpen = true;
19544
+ break;
19385
19545
  case "--no-push":
19386
19546
  parsed.options.noPush = true;
19387
19547
  break;
@@ -19516,6 +19676,12 @@ function parseArgs(argv) {
19516
19676
  } else {
19517
19677
  (parsed.options.selectors ??= []).push(arg);
19518
19678
  }
19679
+ } else if (parsed.command === "domain") {
19680
+ if (!parsed.options.hostname) parsed.options.hostname = arg;
19681
+ else {
19682
+ parsed.error = `Unexpected argument: ${arg}`;
19683
+ return parsed;
19684
+ }
19519
19685
  } else if (parsed.command === "animations") {
19520
19686
  parsed.options.query = parsed.options.query ? `${parsed.options.query} ${arg}` : arg;
19521
19687
  } else {
@@ -19851,6 +20017,9 @@ async function main() {
19851
20017
  case "make-remixable":
19852
20018
  await runMakeRemixable(parsed.options);
19853
20019
  break;
20020
+ case "domain":
20021
+ await runDomain(parsed.options);
20022
+ break;
19854
20023
  case "controller":
19855
20024
  await runController({ ...parsed.options, kind: parsed.options.name });
19856
20025
  break;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@genex-ai/cli-demo",
3
- "version": "1.4.0",
3
+ "version": "1.4.1-dev.376",
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": {
@@ -116,7 +116,7 @@ scene.background = texture; // keep the raw texture for the visible sky
116
116
 
117
117
  ## Troubleshooting
118
118
 
119
- - **"Not authorized"** — run `npx @genex-ai/cli-demo@latest init` first (it writes your `GENEX_TOKEN`).
119
+ - **"Not authorized"** — run `npx @genex-ai/cli-demo@dev init` first (it writes your `GENEX_TOKEN`).
120
120
  - **"Out of credits" (`insufficient_credits`)** — the account has no credits left for
121
121
  this skybox generation. Tell the user the facts the CLI printed: their balance, this
122
122
  generation's cost, and when their credits refill. Then offer to continue the build
@@ -225,7 +225,7 @@ first one is the one a screenshot of the whole arena will not show you.
225
225
 
226
226
  ## Troubleshooting
227
227
 
228
- - **"Not authorized"** — run `npx @genex-ai/cli-demo@latest init` first (it writes your `GENEX_TOKEN`).
228
+ - **"Not authorized"** — run `npx @genex-ai/cli-demo@dev init` first (it writes your `GENEX_TOKEN`).
229
229
  - **"Out of credits" (`insufficient_credits`)** — the account has no credits left for
230
230
  this texture generation. Tell the user the facts the CLI printed: their balance,
231
231
  this generation's cost, and when their credits refill. Then offer to continue the
@@ -151,7 +151,7 @@ set belongs to `$genex-ai-hud` — both build on `npx genex image`/`video`.
151
151
 
152
152
  ## Troubleshooting
153
153
 
154
- - **"Not authorized"** — run `npx @genex-ai/cli-demo@latest init` first (it writes your `GENEX_TOKEN`).
154
+ - **"Not authorized"** — run `npx @genex-ai/cli-demo@dev init` first (it writes your `GENEX_TOKEN`).
155
155
  - **"Prompt rejected"** — the provider's content-safety filter blocked the prompt.
156
156
  This is non-retryable; retrying the same wording fails again. Rewrite the prompt.
157
157
  - **Nothing plays / black surface** — the first `video.play()` must run inside a user
@@ -159,7 +159,7 @@ and re-link the clone to the same live game:
159
159
  ```bash
160
160
  git clone <the game's repo url> my-game && cd my-game
161
161
  npm install
162
- npx @genex-ai/cli-demo@latest link <slug> # slug = the name in the play URL
162
+ npx @genex-ai/cli-demo@dev link <slug> # slug = the name in the play URL
163
163
  ```
164
164
 
165
165
  Don't know the slug? **`npx genex list`** prints every game on your account —
@@ -186,7 +186,7 @@ Safe to run any time — genex-owned skills are refreshed to the latest version,
186
186
  and your own files are never touched:
187
187
 
188
188
  ```bash
189
- npx @genex-ai/cli-demo@latest init
189
+ npx @genex-ai/cli-demo@dev init
190
190
  ```
191
191
 
192
192
  Use `--force` only if you intentionally want your own existing files overwritten
@@ -134,9 +134,13 @@ everything twice.
134
134
  visible; the lock may only ever engage from the Play/Resume click or a
135
135
  gameplay canvas click (the phase binding `setPaused(phase !== "playing")` is
136
136
  what guarantees this — check it rides `setPhase`, not the render loop).
137
- Headless caveat: `requestPointerLock` throws in headless Chromium —
138
- assert the wiring and the unlocked cue in a screenshot, and say plainly that
139
- the lock itself needs one manual click (do the both-axes look check there).
137
+ Headless caveat, measured on Chromium 151: `requestPointerLock` does NOT
138
+ throw it locks, with or without a user gesture, so the lock and the
139
+ unlocked cue ARE yours to assert headless. What does not survive is the
140
+ both-axes look check: synthesised mouse movement cancels to a net zero
141
+ delta, so turning right then left proves nothing about direction. Assert the
142
+ wiring and the cue in a screenshot, and say plainly that confirming which way
143
+ the view turns needs one manual pass with a real mouse.
140
144
  7. **Ask the scene the three things the screenshot cannot answer** (below). Run it
141
145
  once, in the same browser you already have open.
142
146
 
@@ -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
  ```