@genex-ai/cli-demo 1.11.1-dev.539 → 1.11.2-dev.540
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
|
@@ -6160,7 +6160,8 @@ async function detectSurfaceScan(cwd = process.cwd()) {
|
|
|
6160
6160
|
squarePoints: [],
|
|
6161
6161
|
depthRange: [],
|
|
6162
6162
|
mediaElementAudio: [],
|
|
6163
|
-
deferredAudioContext: []
|
|
6163
|
+
deferredAudioContext: [],
|
|
6164
|
+
usesThree: false
|
|
6164
6165
|
};
|
|
6165
6166
|
const srcDir = path17.join(cwd, "src");
|
|
6166
6167
|
let entries;
|
|
@@ -6208,6 +6209,7 @@ async function detectSurfaceScan(cwd = process.cwd()) {
|
|
|
6208
6209
|
ratio
|
|
6209
6210
|
});
|
|
6210
6211
|
}
|
|
6212
|
+
if (/\bfrom\s*["']three(?:\/[^"']*)?["']/.test(content)) found.usesThree = true;
|
|
6211
6213
|
const threeAudioImport = /import\s+(?:type\s+)?\{[^}]*\bAudio\b[^}]*\}\s+from\s*["']three["']/.test(
|
|
6212
6214
|
content
|
|
6213
6215
|
);
|
|
@@ -6376,8 +6378,9 @@ function surfaceNudges(log, s) {
|
|
|
6376
6378
|
}
|
|
6377
6379
|
if (s.mediaElementAudio.length) {
|
|
6378
6380
|
const list = s.mediaElementAudio.map((d) => `${d.where} \u2014 ${d.detail}`).join("; ");
|
|
6381
|
+
const fix = s.usesThree ? `THREE.Audio/PositionalAudio on one AudioListener have none of this \u2014 genex-ai-sfx ships the pooled one-shot, the live-value loop, and the decode-during-the-menu preload.` : `Plain WebAudio has none of this \u2014 one AudioContext (created inside the first user gesture), fetch + decodeAudioData during the menu, an AudioBufferSourceNode per one-shot, and a GainNode per live value. A music bed that only plays/pauses MAY stay an <audio> element; anything whose volume or rate tracks gameplay must not.`;
|
|
6379
6382
|
log.warn(
|
|
6380
|
-
`HTML <audio> elements are carrying this game's sound: ${list}. On iPhone an <audio> element is a handle on the operating system's media pipeline in ANOTHER PROCESS: every write to .volume or .playbackRate is a cross-process call, and play() is slow and asynchronous. A sound driven from a live value \u2014 wheels from speed, engine from RPM \u2014 writes 60-120 of those a second and stutters the whole game, while one-shots arrive late. It disguises itself as a graphics problem perfectly: only while moving, identical at every quality setting, desktop fine.
|
|
6383
|
+
`HTML <audio> elements are carrying this game's sound: ${list}. On iPhone an <audio> element is a handle on the operating system's media pipeline in ANOTHER PROCESS: every write to .volume or .playbackRate is a cross-process call, and play() is slow and asynchronous. A sound driven from a live value \u2014 wheels from speed, engine from RPM \u2014 writes 60-120 of those a second and stutters the whole game, while one-shots arrive late. It disguises itself as a graphics problem perfectly: only while moving, identical at every quality setting, desktop fine. ${fix}`
|
|
6381
6384
|
);
|
|
6382
6385
|
}
|
|
6383
6386
|
if (s.deferredAudioContext.length) {
|
|
@@ -7405,6 +7408,20 @@ async function runGenerate(kind, opts) {
|
|
|
7405
7408
|
}
|
|
7406
7409
|
opts = { ...opts, frameUrl: inlined.dataUri };
|
|
7407
7410
|
}
|
|
7411
|
+
for (const [field, flag] of [
|
|
7412
|
+
["firstFrameUrl", "--start-frame/--first-frame"],
|
|
7413
|
+
["lastFrameUrl", "--last-frame"]
|
|
7414
|
+
]) {
|
|
7415
|
+
const ref = opts[field];
|
|
7416
|
+
if (kind !== "video" || !ref || isRemoteRef(ref)) continue;
|
|
7417
|
+
const inlined = await inlineLocalImage(ref, flag);
|
|
7418
|
+
if (!inlined.ok) {
|
|
7419
|
+
log.error(inlined.error);
|
|
7420
|
+
process.exitCode = 1;
|
|
7421
|
+
return;
|
|
7422
|
+
}
|
|
7423
|
+
opts = { ...opts, [field]: inlined.dataUri };
|
|
7424
|
+
}
|
|
7408
7425
|
if (kind === "video" && opts.refs?.length) {
|
|
7409
7426
|
const refs = [];
|
|
7410
7427
|
for (const ref of opts.refs) {
|
|
@@ -21886,8 +21903,13 @@ ${c.bold("Options for the generators (`model` `sfx` `music` `voice` `texture` `i
|
|
|
21886
21903
|
it, model default).
|
|
21887
21904
|
--frame <url|file> (video) animate from THIS frame back to itself (seamless
|
|
21888
21905
|
loop) \u2014 an asset URL or a local image file (\u22644 MB).
|
|
21889
|
-
--
|
|
21890
|
-
|
|
21906
|
+
--start-frame <url|file> (video) continue from this exact frame, no end
|
|
21907
|
+
anchor \u2014 the clip-chaining primitive: extract the last
|
|
21908
|
+
frame of the previous clip (ffmpeg) and pass it here.
|
|
21909
|
+
Anchors are compositional guidance, not pixel-pinning;
|
|
21910
|
+
chain from REAL extracted frames so drift can't stack.
|
|
21911
|
+
--first-frame <url|file> (video) start frame; alone it means --start-frame \u2026
|
|
21912
|
+
--last-frame <url|file> (video) \u2026 add this to also anchor the end frame.
|
|
21891
21913
|
--ref <url|file> (video, repeatable \xD79) subject/style reference images \u2014
|
|
21892
21914
|
cite them in the prompt as "Image 1"\u2026"Image N"; keeps
|
|
21893
21915
|
the same character/style across many clips. Cannot be
|
|
@@ -22086,7 +22108,8 @@ ${c.bold("Generate")} typ
|
|
|
22086
22108
|
--transparent \u2026with a real alpha channel (sprites, icons, logos).
|
|
22087
22109
|
--edit <path|url> \u2026edit an existing image with the prompt.
|
|
22088
22110
|
--remove-bg / --bg-mode <m> \u2026cut the background (sprite|glyph|sheet|matte).
|
|
22089
|
-
genex video "<prompt>" A video (mp4). --loop, --frame <img
|
|
22111
|
+
genex video "<prompt>" A video (mp4). --loop, --frame <img>, ${cr("video")}
|
|
22112
|
+
--start-frame <img> (continue a clip).
|
|
22090
22113
|
genex texture "<prompt>" A tiling PBR texture set. --terrain. ${cr("texture")}
|
|
22091
22114
|
genex sfx "<prompt>" A sound effect (mp3). --duration. ${cr("sfx")}
|
|
22092
22115
|
genex music "<prompt>" A looping instrumental track (mp3). ${cr("music")}
|
|
@@ -22146,6 +22169,51 @@ ${c.bold("Examples")}
|
|
|
22146
22169
|
Hosting, publishing, multiplayer, remixing and custom domains are the Genex
|
|
22147
22170
|
platform \u2014 build a game there from one prompt: ${c.cyan("https://genex.games")}
|
|
22148
22171
|
`;
|
|
22172
|
+
var COMMAND_HELP = {
|
|
22173
|
+
video: `${c.bold("genex video")} \u2014 generate a video clip (mp4, MiniMax H3 Max \u2014 the flagship; plain H3 for seamless loops and 2k/4k; native stereo audio on every non-loop clip).
|
|
22174
|
+
|
|
22175
|
+
${c.bold("Modes")}
|
|
22176
|
+
genex video "<prompt>" text-to-video (16:9).
|
|
22177
|
+
genex video "<prompt>" --start-frame <img> continue from this frame \u2014 the
|
|
22178
|
+
clip-chaining primitive.
|
|
22179
|
+
genex video "<prompt>" --frame <img> animate from the frame back to
|
|
22180
|
+
itself (seamless loop; menus).
|
|
22181
|
+
genex video "<prompt>" --first-frame <img> --last-frame <img>
|
|
22182
|
+
two-frame motion (both anchored).
|
|
22183
|
+
genex video "<prompt>" --ref <img> [--ref \u2026\xD79] same subject/style across many
|
|
22184
|
+
clips ("Image 1"\u2026"Image N").
|
|
22185
|
+
genex video "<prompt>" --loop prompt-only seamless loop (no
|
|
22186
|
+
conditioning frame; Luma Ray-2).
|
|
22187
|
+
|
|
22188
|
+
${c.bold("Flags")}
|
|
22189
|
+
--start-frame <url|file> continue-from anchor; local files inline (\u22644 MB).
|
|
22190
|
+
--frame / --first-frame / --last-frame <url|file> ditto.
|
|
22191
|
+
--ref <url|file> repeatable \xD79; not combinable with frames/--loop.
|
|
22192
|
+
--duration <s> 5-15 (integer seconds; H3's own band). Default 5,
|
|
22193
|
+
loops/pairs 8. Longer scenes: chain clips.
|
|
22194
|
+
--resolution <r> 480p | 768p (default, top native) | 2k | 4k.
|
|
22195
|
+
--no-wait enqueue only; attach later with genex wait <id>.
|
|
22196
|
+
|
|
22197
|
+
Aspect: text/ref clips are 16:9. Frame-conditioned clips FOLLOW THE SOURCE
|
|
22198
|
+
IMAGE \u2014 a square image (genex image --aspect square) yields a square video.
|
|
22199
|
+
|
|
22200
|
+
${c.bold("Chaining clips (playable films, cutscene sequences)")}
|
|
22201
|
+
Frame anchors are COMPOSITIONAL GUIDANCE, not pixel-pinning \u2014 the model
|
|
22202
|
+
repaints the frame (measured ~21 dB vs the supplied image; identical would be
|
|
22203
|
+
~35-40 dB). Chains still hold if every clip starts from the PREVIOUS CLIP'S
|
|
22204
|
+
REAL last frame, because drift never accumulates:
|
|
22205
|
+
ffmpeg -sseof -0.2 -i prev.mp4 -update 1 -q:v 1 last.png
|
|
22206
|
+
genex video "<what changes next>" --start-frame last.png
|
|
22207
|
+
Cut clip-to-clip (or crossfade ~200 ms to hide the residual repaint). Keep
|
|
22208
|
+
in-game <video> muted by default \u2014 every H3 clip carries audio.
|
|
22209
|
+
|
|
22210
|
+
${c.bold("Content filter")}
|
|
22211
|
+
The provider rejects anatomy-invasion phrasing (cables/wires/needles entering
|
|
22212
|
+
a body) as body horror \u2014 describe the object or machine instead: "a
|
|
22213
|
+
statue-like figure threaded into the wall" passes where "cables entering her
|
|
22214
|
+
spine" fails. Rejections are never retryable; reword and re-run.
|
|
22215
|
+
`
|
|
22216
|
+
};
|
|
22149
22217
|
function parseArgs(argv) {
|
|
22150
22218
|
const parsed = {
|
|
22151
22219
|
command: void 0,
|
|
@@ -22183,6 +22251,7 @@ function parseArgs(argv) {
|
|
|
22183
22251
|
"--voice",
|
|
22184
22252
|
"--voice-id",
|
|
22185
22253
|
"--frame",
|
|
22254
|
+
"--start-frame",
|
|
22186
22255
|
"--first-frame",
|
|
22187
22256
|
"--last-frame",
|
|
22188
22257
|
"--ref",
|
|
@@ -22716,6 +22785,7 @@ function applyValueFlag(options, flag, value) {
|
|
|
22716
22785
|
case "--video":
|
|
22717
22786
|
options.video = value;
|
|
22718
22787
|
break;
|
|
22788
|
+
case "--start-frame":
|
|
22719
22789
|
case "--first-frame":
|
|
22720
22790
|
options.firstFrameUrl = value;
|
|
22721
22791
|
break;
|
|
@@ -22777,7 +22847,8 @@ async function main() {
|
|
|
22777
22847
|
const mode = await workspaceMode();
|
|
22778
22848
|
setWorkspaceHeader(mode === "tools" ? "tools" : null);
|
|
22779
22849
|
if (parsed.help || parsed.command === void 0) {
|
|
22780
|
-
|
|
22850
|
+
const perCommand = parsed.command === void 0 ? void 0 : COMMAND_HELP[parsed.command];
|
|
22851
|
+
log.plain(perCommand ?? (mode === "tools" ? HELP_TOOLS : HELP));
|
|
22781
22852
|
return;
|
|
22782
22853
|
}
|
|
22783
22854
|
const isBlenderMcp = parsed.command === "blender" && parsed.options.name === "mcp";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@genex-ai/cli-demo",
|
|
3
|
-
"version": "1.11.
|
|
3
|
+
"version": "1.11.2-dev.540",
|
|
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": {
|
|
@@ -146,8 +146,19 @@ See `$genex-threejs-multiplayer` for the `shared` channel rules and the room API
|
|
|
146
146
|
- `--frame <url|file>` — one generated image (or a local file ≤4 MB) as BOTH
|
|
147
147
|
first and last frame — the seamless-loop mode (motion must return to its
|
|
148
148
|
start; the seam is mathematically exact).
|
|
149
|
-
- `--
|
|
150
|
-
|
|
149
|
+
- `--start-frame <url|file>` — continue from this frame, no end anchor — the
|
|
150
|
+
clip-chaining primitive (see below). Local files inline like `--frame`.
|
|
151
|
+
- `--first-frame <url|file>` / `--last-frame <url|file>` — two-frame motion
|
|
152
|
+
between two stills (a genuine state change — a door opens, day turns to
|
|
153
|
+
night). `--first-frame` alone means `--start-frame`.
|
|
154
|
+
|
|
155
|
+
**Frame anchors are compositional guidance, not pixel-pinning.** The model
|
|
156
|
+
repaints every anchor — the clip's first frame lands near the supplied image
|
|
157
|
+
(same scene, same composition) but not ON it, and regenerating doesn't change
|
|
158
|
+
that; it is how the mode works. Plan for it: chains stay coherent when each
|
|
159
|
+
clip starts from the previous clip's REAL frame (below), and a supplied
|
|
160
|
+
`--last-frame` steers where motion ends rather than guaranteeing the exact
|
|
161
|
+
pixels.
|
|
151
162
|
- `--ref <url|file>` — repeatable, up to 9 subject/style reference images.
|
|
152
163
|
Cite each in the prompt by its order: "Image 1 is the hero — she walks into
|
|
153
164
|
frame…". This is how the SAME character or art style holds across many clips
|
|
@@ -168,8 +179,21 @@ evidence detective game, a video-book. The pattern that makes it hold together:
|
|
|
168
179
|
- **Branching scenes**: generate one clip per story node at build time, ship
|
|
169
180
|
the mp4 URLs in a scene-graph JSON, play them full-screen with DOM choice
|
|
170
181
|
buttons; preload the clips reachable from the current node while it plays.
|
|
171
|
-
- **Visual continuity
|
|
172
|
-
|
|
182
|
+
- **Visual continuity — chain from REAL frames**: extract the previous clip's
|
|
183
|
+
actual last frame and seed the next clip with it:
|
|
184
|
+
|
|
185
|
+
```bash
|
|
186
|
+
ffmpeg -sseof -0.2 -i prev.mp4 -update 1 -q:v 1 last.png
|
|
187
|
+
npx genex video "she turns and walks toward the far door" --start-frame last.png
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
Never chain from the image you WISHED the clip ended on — anchors are
|
|
191
|
+
repainted (above), so the wish and the clip disagree and every join jumps.
|
|
192
|
+
Chaining from the real frame keeps drift from accumulating; the residual
|
|
193
|
+
repaint at each cut is small, and a ~200 ms crossfade between the two
|
|
194
|
+
`<video>` elements hides it entirely. Write chained prompts as CHANGE ONLY
|
|
195
|
+
("she turns…", "the light flickers out") — the start frame already says
|
|
196
|
+
everything else.
|
|
173
197
|
- **Cast consistency**: give every scene the same `--ref` images of your
|
|
174
198
|
protagonist and key locations — the single biggest quality lever for any
|
|
175
199
|
multi-clip story.
|
|
@@ -181,6 +205,12 @@ evidence detective game, a video-book. The pattern that makes it hold together:
|
|
|
181
205
|
- **"Not authorized"** — run `npx @genex-ai/cli-demo@dev init` first (it writes your `GENEX_TOKEN`).
|
|
182
206
|
- **"Prompt rejected"** — the provider's content-safety filter blocked the prompt.
|
|
183
207
|
This is non-retryable; retrying the same wording fails again. Rewrite the prompt.
|
|
208
|
+
The measured false-positive class is anatomy being pierced or entered
|
|
209
|
+
(cables/wires/needles into a body — biomech and cyberpunk vocabulary trips
|
|
210
|
+
it). Describe the object or machine instead of the anatomy: "a statue-like
|
|
211
|
+
figure threaded into the wall" passes where "cables entering her spine"
|
|
212
|
+
fails. Word it that way on the FIRST try — each rejection still ends a
|
|
213
|
+
billed-then-refunded round trip.
|
|
184
214
|
- **Nothing plays / black surface** — the first `video.play()` must run inside a user
|
|
185
215
|
gesture (click/keydown); confirm it's called and its promise rejection is logged.
|
|
186
216
|
- **Tainted-source / security error** — set `video.crossOrigin = "anonymous"` before
|
|
@@ -33,7 +33,22 @@ npx genex video "camera drifts slowly forward, banners stirring" --frame ./asset
|
|
|
33
33
|
```
|
|
34
34
|
|
|
35
35
|
- `--frame <path|url>` — animate FROM this frame back to itself, i.e. a seamless loop. Local file (≤ 4 MB) or a previous generation's URL.
|
|
36
|
-
- `--
|
|
36
|
+
- `--start-frame <path|url>` — continue from this frame, no end anchor — the clip-chaining primitive.
|
|
37
|
+
- `--first-frame <path|url>` / `--last-frame <path|url>` — a two-frame motion between them. All frame anchors take local files.
|
|
38
|
+
|
|
39
|
+
Frame anchors are **compositional guidance, not pixel-pinning** — the model
|
|
40
|
+
repaints the frame (same scene and composition, not the same pixels), and
|
|
41
|
+
regenerating doesn't change that. Chained clips (a cutscene sequence, a
|
|
42
|
+
branching video story) stay coherent when each clip starts from the previous
|
|
43
|
+
clip's REAL last frame, so drift never accumulates:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
ffmpeg -sseof -0.2 -i ./assets/prev.mp4 -update 1 -q:v 1 last.png
|
|
47
|
+
npx genex video "she turns and walks toward the far door" --start-frame last.png
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Write chained prompts as CHANGE ONLY — the start frame already says everything
|
|
51
|
+
else. Cut clip-to-clip, or crossfade ~200 ms to hide the residual repaint.
|
|
37
52
|
|
|
38
53
|
## Options
|
|
39
54
|
|
|
@@ -61,5 +76,6 @@ command bills a NEW clip** — it is never a way to check on one already running
|
|
|
61
76
|
## Troubleshooting
|
|
62
77
|
|
|
63
78
|
- **It failed** — video fails server-side more often than any other lane, and every attempt is minutes. The CLI counts failures in this folder and tells you when to stop: after the second, use a still image instead and say so in one plain line. A third attempt bills the same and returns the same.
|
|
79
|
+
- **"Prompt rejected"** — the content-safety filter; never retryable with the same wording. The measured false-positive class is anatomy being pierced or entered (cables/wires/needles into a body — biomech vocabulary trips it). Describe the object or machine instead: "a statue-like figure threaded into the wall" passes where "cables entering her spine" fails.
|
|
64
80
|
- **Out of credits** — the error prints balance, price and refill date. Relay it; don't retry.
|
|
65
81
|
- **Anything else** — `npx genex doctor` reports sign-in, credits, and whether the video lane is live.
|