@genex-ai/cli-demo 1.4.0 → 1.4.1-dev.375
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 +170 -1
- package/package.json +1 -1
- package/templates/skills/genex-ai-skybox/SKILL.md +1 -1
- package/templates/skills/genex-ai-texture/SKILL.md +1 -1
- package/templates/skills/genex-ai-video/SKILL.md +1 -1
- package/templates/skills/genex-getting-started/SKILL.md +2 -2
- package/templates/skills/genex-threejs-visual-validation/SKILL.md +7 -3
- package/templates/skills/genex-updates/SKILL.md +1 -1
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 = "
|
|
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.
|
|
3
|
+
"version": "1.4.1-dev.375",
|
|
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@
|
|
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@
|
|
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@
|
|
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@
|
|
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@
|
|
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`
|
|
138
|
-
|
|
139
|
-
|
|
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@
|
|
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
|
```
|