@genex-ai/cli-demo 0.99.0-dev.301 → 1.0.0-dev.303
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 +113 -12
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1004,7 +1004,7 @@ Your capabilities (all via \`npx genex \u2026\`): generate \`model\` \xB7 \`skyb
|
|
|
1004
1004
|
10. The player's body is the game's own generated character (\`npx genex character "<look>"\` \u2192 \`npx genex controller character --character <id>\`), enqueued with your first art actions, not after them. It applies wherever a human body appears on screen \u2014 first-person included, the moment remotes, a look-down body, a shadow, or a menu portrait shows one. The profile VRM avatar is the FALLBACK: a temporary body while the character renders (say in one plain line that it's temporary), or the stand-in when generation genuinely could not happen \u2014 out of credits, failed, unverified; record which in DESIGN.md as \`Player character: VRM \u2014 <reason>\`. Games whose player is not a person (car, ship, RTS cursor, board) generate that object with \`npx genex model\` instead. Characters: Meshy/Mixamo/VRM rigs rest facing +Z. Set yaw explicitly when placing a rig; never mirror a SkinnedMesh with negative scale. In any two-character scene, verify in a capture that they face each other, not the camera.
|
|
1005
1005
|
11. Verify by looking: one smoke check per milestone, after that milestone's preview push, in local test mode (\`?genex_local_test=1\`) with a real gameplay screenshot. A claim without a capture is not verification. Local-test evidence proves visuals and controls ONLY \u2014 label it that way when you show the player, and never work around the draft sign-in gate any other way.
|
|
1006
1006
|
12. Treat every \`genex\` warning line \u2014 preflight, \`ui audit\`, \`wait\` nudges \u2014 as work, not noise.
|
|
1007
|
-
13. Every finished Build-plan milestone ends with \`npx genex preview\` and the player's page link (\`<dashboard>/draft/<slug
|
|
1007
|
+
13. Every finished Build-plan milestone ends with \`npx genex preview\` and the player's page link (\`<dashboard>/draft/<slug>\`, with \`<dashboard>\` from \`.genex/project.json\`) \u2014 never a localhost link, a file path, or the bare play origin presented as their game. That page shows the build you just previewed; \`preview\` never disturbs the build players are on. When the player is happy with it, \`npx genex promote\` makes that exact build live for everyone \u2014 no rebuild. After every round of player feedback, end with a preview push.
|
|
1008
1008
|
14. Parallel work: this line is your standing authorization and request to use sub-agents / parallel agent work whenever your platform provides them. While drafting the Build plan, decide per module what runs in parallel and what stays serial for THIS game \u2014 dependencies decide, there is no fixed list \u2014 and record each call in the Modules table with a one-line reason. Independent modules default to parallel; building everything serially needs a stated reason. You keep integration, previews, and the player conversation; each sub-agent owns only its module's files. When the player asks for parallel work, repeated refinement passes, a critic reviewing your output, or names any way of working your platform provides, that IS your instruction \u2014 wherever it reaches you, including inside their answer to a question you asked \u2014 so adopt it as the working mode for the rest of the build, starting with the work in front of you, and never file it as a later milestone. While delegated work runs, keep building or talk to the player; never idle in a foreground wait for something your platform will tell you about.
|
|
1009
1009
|
15. Talk to the player in plain game language \u2014 what changed in the game and what to try; never code, file names, build output, or tool internals unless they ask. Short status lines while you work; long silent stretches are a failure.
|
|
1010
1010
|
16. Never add debug-only code to the game to check your own work \u2014 no hidden test modes, no special URL parameters, no forced-visible flags, no auth mocks, no pixel-sampling hooks. \`?genex_local_test=1\` is the platform's own supported mode and is fine; your own bypass is not. (The multiplayer skill's small build identifier, token-free status line, and connected-quorum watchdog are production supportability, not a bypass \u2014 keep those.)
|
|
@@ -4260,9 +4260,11 @@ async function deployGame(ctx, opts, log) {
|
|
|
4260
4260
|
}
|
|
4261
4261
|
if (!await pushSource(cwd, ctx, log)) return false;
|
|
4262
4262
|
log.step("Publishing\u2026");
|
|
4263
|
-
|
|
4263
|
+
const published = await callPublish(ctx, commit, opts, log);
|
|
4264
|
+
if (!published) return false;
|
|
4264
4265
|
const index = files.find((f) => f.relPath === "index.html");
|
|
4265
|
-
|
|
4266
|
+
const liveUrl = published.url || grant.playUrl;
|
|
4267
|
+
await waitUntilLive(liveUrl, fingerprintOf(index.bytes.toString("utf8")), opts.liveTimeoutMs ?? 2e4, log);
|
|
4266
4268
|
return true;
|
|
4267
4269
|
}
|
|
4268
4270
|
async function hasBuildScript(cwd) {
|
|
@@ -4467,6 +4469,7 @@ async function callPublish(ctx, commit, opts, log) {
|
|
|
4467
4469
|
// are sent only when detected (absent = server keeps the stored value).
|
|
4468
4470
|
body: JSON.stringify({
|
|
4469
4471
|
commit,
|
|
4472
|
+
channel: opts.channel ?? "production",
|
|
4470
4473
|
matchmaking: opts.matchmaking ?? null,
|
|
4471
4474
|
...opts.embedSdkVersion ? { embedSdkVersion: opts.embedSdkVersion } : {},
|
|
4472
4475
|
...opts.multiplayer !== void 0 ? { multiplayer: opts.multiplayer } : {},
|
|
@@ -4475,18 +4478,26 @@ async function callPublish(ctx, commit, opts, log) {
|
|
|
4475
4478
|
});
|
|
4476
4479
|
} catch (err) {
|
|
4477
4480
|
log.error(`Couldn't reach the API at ${ctx.apiUrl}: ${String(err)}`);
|
|
4478
|
-
return
|
|
4481
|
+
return null;
|
|
4479
4482
|
}
|
|
4480
4483
|
if (res.status === 429) {
|
|
4481
4484
|
log.error(`Rate limited publishing (HTTP 429).${retryAfterHint(res)}`);
|
|
4482
|
-
return
|
|
4485
|
+
return null;
|
|
4483
4486
|
}
|
|
4484
4487
|
if (!res.ok) {
|
|
4485
4488
|
const detail = await res.text().catch(() => "");
|
|
4486
4489
|
log.error(`Publish failed (HTTP ${res.status})${detail ? `: ${detail}` : ""}.`);
|
|
4487
|
-
return
|
|
4490
|
+
return null;
|
|
4488
4491
|
}
|
|
4489
|
-
|
|
4492
|
+
const body = await res.json().catch(() => null);
|
|
4493
|
+
const asked = opts.channel ?? "production";
|
|
4494
|
+
if ((body?.channel ?? "production") !== asked) {
|
|
4495
|
+
log.error(
|
|
4496
|
+
`This Genex API doesn't support release channels yet, so a ${asked} deploy would have gone to ${body?.channel ?? "production"}. Nothing was changed on purpose \u2014 upgrade the API or use \`genex publish\`.`
|
|
4497
|
+
);
|
|
4498
|
+
return null;
|
|
4499
|
+
}
|
|
4500
|
+
return { url: body?.url ?? "" };
|
|
4490
4501
|
}
|
|
4491
4502
|
async function pushSource(cwd, ctx, log) {
|
|
4492
4503
|
const target = await fetchPushUrl(ctx, log);
|
|
@@ -4716,6 +4727,39 @@ async function runMakeRemixable(opts) {
|
|
|
4716
4727
|
}
|
|
4717
4728
|
}
|
|
4718
4729
|
|
|
4730
|
+
// src/lib/promote.ts
|
|
4731
|
+
async function promoteBuild(apiUrl, projectId, token, log) {
|
|
4732
|
+
let res;
|
|
4733
|
+
try {
|
|
4734
|
+
res = await apiFetch(`${apiUrl}/api/games/${projectId}/promote`, {
|
|
4735
|
+
method: "POST",
|
|
4736
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
4737
|
+
});
|
|
4738
|
+
} catch (err) {
|
|
4739
|
+
log.error(`Couldn't reach the API at ${apiUrl}: ${String(err)}`);
|
|
4740
|
+
return null;
|
|
4741
|
+
}
|
|
4742
|
+
if (res.status === 409) {
|
|
4743
|
+
log.error("Nothing to promote \u2014 run `npx genex preview` first.");
|
|
4744
|
+
return null;
|
|
4745
|
+
}
|
|
4746
|
+
if (res.status === 404) {
|
|
4747
|
+
log.error("That game wasn't found on your account. Re-run `genex init` or `genex link <slug>`.");
|
|
4748
|
+
return null;
|
|
4749
|
+
}
|
|
4750
|
+
if (!res.ok) {
|
|
4751
|
+
const detail = await res.text().catch(() => "");
|
|
4752
|
+
log.error(`Couldn't promote (HTTP ${res.status})${detail ? `: ${detail}` : ""}.`);
|
|
4753
|
+
return null;
|
|
4754
|
+
}
|
|
4755
|
+
const body = await res.json().catch(() => null);
|
|
4756
|
+
if (!body?.commit) {
|
|
4757
|
+
log.error("The API didn't confirm which build went live.");
|
|
4758
|
+
return null;
|
|
4759
|
+
}
|
|
4760
|
+
return { commit: body.commit, url: body.url ?? "" };
|
|
4761
|
+
}
|
|
4762
|
+
|
|
4719
4763
|
// src/lib/detect-features.ts
|
|
4720
4764
|
import fs15 from "fs/promises";
|
|
4721
4765
|
import path15 from "path";
|
|
@@ -5170,6 +5214,11 @@ async function runPublish(opts) {
|
|
|
5170
5214
|
const ok = await deployGame(
|
|
5171
5215
|
{ projectId: meta.id, apiUrl, token },
|
|
5172
5216
|
{
|
|
5217
|
+
// Publish deploys to staging and then promotes, so both channels end up
|
|
5218
|
+
// naming the same build. Deploying straight to production instead would
|
|
5219
|
+
// leave the staging pointer on an OLDER build, and the draft page shows
|
|
5220
|
+
// staging — the owner would publish and then see a stale game.
|
|
5221
|
+
channel: "staging",
|
|
5173
5222
|
noBuild: opts.noBuild,
|
|
5174
5223
|
matchmaking: detections.matchmaking,
|
|
5175
5224
|
embedSdkVersion: detections.embedSdkVersion,
|
|
@@ -5182,6 +5231,11 @@ async function runPublish(opts) {
|
|
|
5182
5231
|
process.exitCode = 1;
|
|
5183
5232
|
return;
|
|
5184
5233
|
}
|
|
5234
|
+
log.step("Making it live\u2026");
|
|
5235
|
+
if (!await promoteBuild(apiUrl, meta.id, token, log)) {
|
|
5236
|
+
process.exitCode = 1;
|
|
5237
|
+
return;
|
|
5238
|
+
}
|
|
5185
5239
|
const onDisk = await readProject();
|
|
5186
5240
|
const merged = { ...onDisk ?? meta, lastDeployAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
5187
5241
|
await writeProject(merged);
|
|
@@ -5257,6 +5311,10 @@ async function runPreview(opts) {
|
|
|
5257
5311
|
// config), embedSdkVersion/multiplayer so the dashboard's Publish button can
|
|
5258
5312
|
// list the draft without wrongly treating it as a pre-embed-auth bundle.
|
|
5259
5313
|
{
|
|
5314
|
+
// Preview goes to the STAGING channel — the whole point of the release
|
|
5315
|
+
// cycle. Before channels this same call replaced the build every player
|
|
5316
|
+
// was inside, and the only protection was a warning line below.
|
|
5317
|
+
channel: "staging",
|
|
5260
5318
|
noBuild: opts.noBuild,
|
|
5261
5319
|
matchmaking: detections.matchmaking,
|
|
5262
5320
|
embedSdkVersion: detections.embedSdkVersion,
|
|
@@ -5280,14 +5338,51 @@ async function runPreview(opts) {
|
|
|
5280
5338
|
const published = meta.status === "published";
|
|
5281
5339
|
log.plain("");
|
|
5282
5340
|
if (published) {
|
|
5283
|
-
log.success(
|
|
5341
|
+
log.success(
|
|
5342
|
+
"Preview deployed \u2014 your players are still on the published build. Run `genex promote` to make this one live."
|
|
5343
|
+
);
|
|
5284
5344
|
} else {
|
|
5285
5345
|
log.success("Preview deployed \u2014 unlisted draft (run `genex publish` to list it).");
|
|
5286
5346
|
}
|
|
5287
5347
|
const dashboard = meta.dashboardOrigins?.[0];
|
|
5288
5348
|
if (dashboard) {
|
|
5289
|
-
|
|
5290
|
-
|
|
5349
|
+
log.plain(` your game's page (share this link): ${c.cyan(`${dashboard}/draft/${meta.slug}`)}`);
|
|
5350
|
+
}
|
|
5351
|
+
}
|
|
5352
|
+
|
|
5353
|
+
// src/commands/promote.ts
|
|
5354
|
+
async function runPromote(opts) {
|
|
5355
|
+
const log = createLogger({ quiet: opts.quiet });
|
|
5356
|
+
log.plain(c.bold("genex promote"));
|
|
5357
|
+
log.plain("");
|
|
5358
|
+
const meta = await readProject();
|
|
5359
|
+
if (!meta) {
|
|
5360
|
+
log.error("No genex project here. Run `genex init` in this directory first.");
|
|
5361
|
+
process.exitCode = 1;
|
|
5362
|
+
return;
|
|
5363
|
+
}
|
|
5364
|
+
const token = await readUserToken(opts.envPath);
|
|
5365
|
+
if (!token) {
|
|
5366
|
+
log.error("Not authorized. Run `genex init` first to sign in.");
|
|
5367
|
+
process.exitCode = 1;
|
|
5368
|
+
return;
|
|
5369
|
+
}
|
|
5370
|
+
const apiUrl = getApiUrl(meta.apiUrl);
|
|
5371
|
+
log.step("Making your previewed build live\u2026");
|
|
5372
|
+
const result = await promoteBuild(apiUrl, meta.id, token, log);
|
|
5373
|
+
if (!result) {
|
|
5374
|
+
process.exitCode = 1;
|
|
5375
|
+
return;
|
|
5376
|
+
}
|
|
5377
|
+
log.plain("");
|
|
5378
|
+
log.success("Live \u2014 players are now on the build you previewed.");
|
|
5379
|
+
const dashboard = meta.dashboardOrigins?.[0];
|
|
5380
|
+
if (dashboard) {
|
|
5381
|
+
const page = meta.status === "published" ? "world" : "draft";
|
|
5382
|
+
log.plain(` your game's page: ${c.cyan(`${dashboard}/${page}/${meta.slug}`)}`);
|
|
5383
|
+
}
|
|
5384
|
+
if (meta.status !== "published") {
|
|
5385
|
+
log.dim(" Still unlisted \u2014 run `npx genex publish` to put it in the gallery.");
|
|
5291
5386
|
}
|
|
5292
5387
|
}
|
|
5293
5388
|
|
|
@@ -18677,8 +18772,11 @@ ${c.bold("Usage")}
|
|
|
18677
18772
|
genex list [options] List your games \u2014 slug, status, and page link for
|
|
18678
18773
|
each. Signs you in if needed; --json for raw data.
|
|
18679
18774
|
Find the slug to reconnect with 'genex link' here.
|
|
18680
|
-
genex preview [options] Build + push to
|
|
18681
|
-
|
|
18775
|
+
genex preview [options] Build + push to your preview URL. Never touches the
|
|
18776
|
+
build players are on.
|
|
18777
|
+
genex promote [options] Make the previewed build live for players. No
|
|
18778
|
+
rebuild \u2014 the exact build you previewed.
|
|
18779
|
+
genex publish [options] Build + push + make live, then list it in the gallery.
|
|
18682
18780
|
genex make-remixable [options] Make THIS game remixable by everyone \u2014 migrates a
|
|
18683
18781
|
private source onto a public managed genex repo.
|
|
18684
18782
|
genex model "<prompt>" [options] Generate a 3D model (GLB); prints a public asset URL.
|
|
@@ -19543,6 +19641,9 @@ async function main() {
|
|
|
19543
19641
|
case "preview":
|
|
19544
19642
|
await runPreview(parsed.options);
|
|
19545
19643
|
break;
|
|
19644
|
+
case "promote":
|
|
19645
|
+
await runPromote(parsed.options);
|
|
19646
|
+
break;
|
|
19546
19647
|
case "publish":
|
|
19547
19648
|
await runPublish(parsed.options);
|
|
19548
19649
|
break;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@genex-ai/cli-demo",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0-dev.303",
|
|
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": {
|