@bitmagic/cli 0.1.1 → 0.1.3
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/cli.d.ts +30 -0
- package/dist/cli.js +23 -11
- package/dist/cli.js.map +1 -1
- package/dist/commands/forge.d.ts +107 -0
- package/dist/commands/forge.js +393 -0
- package/dist/commands/forge.js.map +1 -0
- package/dist/commands/generate.js +58 -45
- package/dist/commands/generate.js.map +1 -1
- package/dist/commands/verify.js +16 -16
- package/dist/commands/verify.js.map +1 -1
- package/dist/forge/apply-modifications.d.ts +19 -0
- package/dist/forge/apply-modifications.js +184 -0
- package/dist/forge/apply-modifications.js.map +1 -0
- package/dist/forge/bake-progress.d.ts +44 -0
- package/dist/forge/bake-progress.js +77 -0
- package/dist/forge/bake-progress.js.map +1 -0
- package/dist/forge/browser-host.d.ts +100 -0
- package/dist/forge/browser-host.js +351 -0
- package/dist/forge/browser-host.js.map +1 -0
- package/dist/forge/local-store.d.ts +30 -0
- package/dist/forge/local-store.js +130 -0
- package/dist/forge/local-store.js.map +1 -0
- package/dist/forge/run-pipeline.d.ts +105 -0
- package/dist/forge/run-pipeline.js +269 -0
- package/dist/forge/run-pipeline.js.map +1 -0
- package/dist/forge/stream.d.ts +106 -0
- package/dist/forge/stream.js +286 -0
- package/dist/forge/stream.js.map +1 -0
- package/dist/forge/transport.d.ts +58 -0
- package/dist/forge/transport.js +145 -0
- package/dist/forge/transport.js.map +1 -0
- package/dist/forge/upload-proxy.d.ts +21 -0
- package/dist/forge/upload-proxy.js +134 -0
- package/dist/forge/upload-proxy.js.map +1 -0
- package/dist/generate/stream.d.ts +1 -10
- package/dist/generate/stream.js +5 -62
- package/dist/generate/stream.js.map +1 -1
- package/dist/http/sse.d.ts +40 -0
- package/dist/http/sse.js +98 -0
- package/dist/http/sse.js.map +1 -0
- package/dist/local-port.d.ts +30 -0
- package/dist/local-port.js +53 -0
- package/dist/local-port.js.map +1 -0
- package/dist/scaffold/project-files.js +89 -9
- package/dist/scaffold/project-files.js.map +1 -1
- package/package.json +3 -2
package/dist/cli.d.ts
CHANGED
|
@@ -63,6 +63,36 @@ declare const rawCommands: {
|
|
|
63
63
|
};
|
|
64
64
|
}>;
|
|
65
65
|
generate: CommandDef<ArgsDef>;
|
|
66
|
+
forge: CommandDef<{
|
|
67
|
+
readonly prompt: {
|
|
68
|
+
readonly type: "string";
|
|
69
|
+
readonly description: "Describe the level, e.g. \"a ruined desert temple with a central courtyard\".";
|
|
70
|
+
};
|
|
71
|
+
readonly name: {
|
|
72
|
+
readonly type: "string";
|
|
73
|
+
readonly description: "Name for the level. Derived from the prompt when omitted.";
|
|
74
|
+
};
|
|
75
|
+
readonly resume: {
|
|
76
|
+
readonly type: "string";
|
|
77
|
+
readonly description: "Resume a job id printed by an earlier run; completed steps are skipped.";
|
|
78
|
+
};
|
|
79
|
+
readonly city: {
|
|
80
|
+
readonly type: "boolean";
|
|
81
|
+
readonly description: "Build a city (street grid, buildings, landmarks).";
|
|
82
|
+
};
|
|
83
|
+
readonly dungeon: {
|
|
84
|
+
readonly type: "boolean";
|
|
85
|
+
readonly description: "Build an enclosed dungeon of rooms and corridors.";
|
|
86
|
+
};
|
|
87
|
+
readonly platformer: {
|
|
88
|
+
readonly type: "boolean";
|
|
89
|
+
readonly description: "Build a third-person platformer journey.";
|
|
90
|
+
};
|
|
91
|
+
readonly freeform: {
|
|
92
|
+
readonly type: "boolean";
|
|
93
|
+
readonly description: "Build a custom place (arena, fortress, camp).";
|
|
94
|
+
};
|
|
95
|
+
}>;
|
|
66
96
|
};
|
|
67
97
|
type CommandName = keyof typeof rawCommands;
|
|
68
98
|
export declare const subCommands: Record<CommandName, CommandDef<ArgsDef>>;
|
package/dist/cli.js
CHANGED
|
@@ -8,6 +8,7 @@ import { checkCommand } from './commands/check.js';
|
|
|
8
8
|
import { devCommand } from './commands/dev.js';
|
|
9
9
|
import { verifyCommand } from './commands/verify.js';
|
|
10
10
|
import { generateCommand } from './commands/generate.js';
|
|
11
|
+
import { forgeCommand } from './commands/forge.js';
|
|
11
12
|
// citty's runMain() catches every error thrown from a command's `run()` internally and calls
|
|
12
13
|
// process.exit() itself — it never rejects the promise it returns, so the `.catch()` below
|
|
13
14
|
// never runs for those. For anything that isn't citty's own internal CLIError class, its catch
|
|
@@ -18,6 +19,18 @@ import { generateCommand } from './commands/generate.js';
|
|
|
18
19
|
// "every entry of subCommands is wrapped" structurally, by reference, instead of maintaining a
|
|
19
20
|
// separate list of command names that could drift out of sync with the real map.
|
|
20
21
|
const wrappedRuns = new WeakSet();
|
|
22
|
+
/**
|
|
23
|
+
* The command's subcommand map, or `undefined` if it has none. Every `subCommands` value in this
|
|
24
|
+
* CLI is a plain synchronous object literal (never a citty Promise/function Resolvable), so
|
|
25
|
+
* narrowing it this way is safe for this codebase's actual usage even though citty's own type
|
|
26
|
+
* allows more.
|
|
27
|
+
*/
|
|
28
|
+
function subCommandsOf(command) {
|
|
29
|
+
const subs = command.subCommands;
|
|
30
|
+
if (!subs || typeof subs !== 'object')
|
|
31
|
+
return undefined;
|
|
32
|
+
return subs;
|
|
33
|
+
}
|
|
21
34
|
export function withCleanErrors(command) {
|
|
22
35
|
const originalRun = command.run;
|
|
23
36
|
const wrapped = { ...command };
|
|
@@ -43,12 +56,10 @@ export function withCleanErrors(command) {
|
|
|
43
56
|
// unrecursed, a CliError thrown from one of those would clear this command's own (nonexistent)
|
|
44
57
|
// run untouched and hit citty's default handling instead — a raw stack trace, the exact thing
|
|
45
58
|
// this wrapper exists to prevent. Recurse so every depth gets the same clean single-line
|
|
46
|
-
// treatment.
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
const rawSubCommands = command.subCommands;
|
|
51
|
-
wrapped.subCommands = Object.fromEntries(Object.entries(rawSubCommands).map(([name, sub]) => [name, withCleanErrors(sub)]));
|
|
59
|
+
// treatment.
|
|
60
|
+
const subs = subCommandsOf(command);
|
|
61
|
+
if (subs) {
|
|
62
|
+
wrapped.subCommands = Object.fromEntries(Object.entries(subs).map(([name, sub]) => [name, withCleanErrors(sub)]));
|
|
52
63
|
}
|
|
53
64
|
return wrapped;
|
|
54
65
|
}
|
|
@@ -62,11 +73,11 @@ export function isWrappedWithCleanErrors(command) {
|
|
|
62
73
|
if (command.run !== undefined) {
|
|
63
74
|
return wrappedRuns.has(command.run);
|
|
64
75
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
return
|
|
68
|
-
|
|
69
|
-
return
|
|
76
|
+
const subs = subCommandsOf(command);
|
|
77
|
+
if (!subs)
|
|
78
|
+
return false;
|
|
79
|
+
const entries = Object.values(subs);
|
|
80
|
+
return entries.length > 0 && entries.every((sub) => isWrappedWithCleanErrors(sub));
|
|
70
81
|
}
|
|
71
82
|
// Wrapping happens once here, over the whole map, rather than per-entry at each call site —
|
|
72
83
|
// a future command added to `rawCommands` is wrapped automatically, so there is no per-command
|
|
@@ -80,6 +91,7 @@ const rawCommands = {
|
|
|
80
91
|
dev: devCommand,
|
|
81
92
|
verify: verifyCommand,
|
|
82
93
|
generate: generateCommand,
|
|
94
|
+
forge: forgeCommand,
|
|
83
95
|
};
|
|
84
96
|
export const subCommands = Object.fromEntries(Object.entries(rawCommands).map(([name, command]) => [name, withCleanErrors(command)]));
|
|
85
97
|
// This module only builds `main` and exports it (along with `subCommands` and `withCleanErrors`)
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AAEtC,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { type ForgeJobInput } from '@bitmagic/world-forger/pipeline/index.js';
|
|
2
|
+
/** The four scene approaches the forger supports. Exactly one may be chosen; none = terrain. */
|
|
3
|
+
declare const MODE_FLAGS: readonly ["city", "dungeon", "platformer", "freeform"];
|
|
4
|
+
type ModeFlag = (typeof MODE_FLAGS)[number];
|
|
5
|
+
/**
|
|
6
|
+
* The level's name. Always sent EXPLICITLY to the server, even when derived: api-server's
|
|
7
|
+
* `resolveLevelName` falls back to its own derivation from the prompt when `name` is absent, and
|
|
8
|
+
* this lane also freezes an `input` artifact locally (see `seedForgeStore`). Two independent
|
|
9
|
+
* derivations of the same name would be a drift waiting to happen, so the CLI decides once and
|
|
10
|
+
* the server receives the answer rather than recomputing it.
|
|
11
|
+
*/
|
|
12
|
+
export declare function deriveLevelName(explicit: string | undefined, prompt: string): string;
|
|
13
|
+
/** The single selected mode, or undefined for plain terrain. */
|
|
14
|
+
export declare function selectMode(args: Record<string, unknown>): ModeFlag | undefined;
|
|
15
|
+
/**
|
|
16
|
+
* Resolve the frozen request for this run.
|
|
17
|
+
*
|
|
18
|
+
* On a fresh run it comes from the flags. On a resume it comes from the job's LOCAL frozen
|
|
19
|
+
* `input` artifact where one exists, so the resumed request re-sends exactly what the original
|
|
20
|
+
* sent — which also lets `--resume` stand alone, without repeating the prompt. api-server freezes
|
|
21
|
+
* its own copy and never overwrites it, so a resume that did send different flags would produce a
|
|
22
|
+
* level whose geometry came from the original design but whose name and prompt came from the new
|
|
23
|
+
* invocation. Reading the frozen copy back removes that divergence rather than documenting it.
|
|
24
|
+
*/
|
|
25
|
+
export declare function resolveJobInput(options: {
|
|
26
|
+
frozen: ForgeJobInput | null;
|
|
27
|
+
prompt: string | undefined;
|
|
28
|
+
name: string | undefined;
|
|
29
|
+
mode: ModeFlag | undefined;
|
|
30
|
+
gameId: string;
|
|
31
|
+
resumeJobId: string | null;
|
|
32
|
+
}): ForgeJobInput;
|
|
33
|
+
/** The request body for `POST /api/cli/v1/forge/stream`. */
|
|
34
|
+
export declare function buildForgeRequestBody(input: ForgeJobInput, resumeJobId: string | null): Record<string, unknown>;
|
|
35
|
+
export interface ForgeRunDeps {
|
|
36
|
+
fetch: typeof globalThis.fetch;
|
|
37
|
+
sleep: (ms: number) => Promise<void>;
|
|
38
|
+
now?: () => number;
|
|
39
|
+
/** Test-only override; production omits it and loadProjectContext walks up from process.cwd(). */
|
|
40
|
+
startDir?: string;
|
|
41
|
+
/** Test-only override; production omits it and the credential helpers read ~/.bitmagic. */
|
|
42
|
+
baseDir?: string;
|
|
43
|
+
log: (message: string) => void;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* The whole command. Failure wording is the contract here — see the file header.
|
|
47
|
+
*
|
|
48
|
+
* - Anything before step 5's apply leaves world.json untouched, and says so.
|
|
49
|
+
* - A server-side failure additionally says whether resuming can help; see
|
|
50
|
+
* `classifyForgeFailure`, which is the difference between "try again" and "change your prompt".
|
|
51
|
+
* - A failure AFTER the design and geometry landed but before (or during) the local write is the
|
|
52
|
+
* expensive case: the forge has been billed, and the message says the work is saved under a job
|
|
53
|
+
* id rather than lost.
|
|
54
|
+
*/
|
|
55
|
+
export declare function runForge(args: {
|
|
56
|
+
prompt?: string;
|
|
57
|
+
name?: string;
|
|
58
|
+
resume?: string;
|
|
59
|
+
} & Record<string, unknown>, deps?: ForgeRunDeps): Promise<void>;
|
|
60
|
+
/**
|
|
61
|
+
* Decorate a steps 3-5 failure with the job id, REGARDLESS of its class.
|
|
62
|
+
*
|
|
63
|
+
* The class check `generate.ts` uses would be wrong here, and the difference is not stylistic:
|
|
64
|
+
* steps 3-5 come from `@bitmagic/world-forger` and throw plain `Error`/`ForgeStepError`, not
|
|
65
|
+
* `CliError` — the bake timeout (`voxelize-level.ts`), the placement timeout
|
|
66
|
+
* (`place-and-persist-level.ts`), a missing artifact (`artifact-store.ts`) and the archetype
|
|
67
|
+
* timeout (`create-level-archetypes.ts`) are all third-party classes. Passing those through
|
|
68
|
+
* untouched sends them past `withCleanErrors` into citty's raw stack dump, with no job id and no
|
|
69
|
+
* world.json statement — in the exact window where the design and geometry are already paid for.
|
|
70
|
+
* A creator seeing `Error: Level voxelization timed out` and a stack has no way to know that
|
|
71
|
+
* resuming is free, and will re-run with `--prompt` and pay for a second design.
|
|
72
|
+
*
|
|
73
|
+
* `generate.ts` gets away with the class check only because nothing in its span throws a
|
|
74
|
+
* third-party error class.
|
|
75
|
+
*/
|
|
76
|
+
export declare function decorateBrowserStepError(error: unknown, jobId: string): unknown;
|
|
77
|
+
export declare const forgeCommand: import("citty").CommandDef<{
|
|
78
|
+
readonly prompt: {
|
|
79
|
+
readonly type: "string";
|
|
80
|
+
readonly description: "Describe the level, e.g. \"a ruined desert temple with a central courtyard\".";
|
|
81
|
+
};
|
|
82
|
+
readonly name: {
|
|
83
|
+
readonly type: "string";
|
|
84
|
+
readonly description: "Name for the level. Derived from the prompt when omitted.";
|
|
85
|
+
};
|
|
86
|
+
readonly resume: {
|
|
87
|
+
readonly type: "string";
|
|
88
|
+
readonly description: "Resume a job id printed by an earlier run; completed steps are skipped.";
|
|
89
|
+
};
|
|
90
|
+
readonly city: {
|
|
91
|
+
readonly type: "boolean";
|
|
92
|
+
readonly description: "Build a city (street grid, buildings, landmarks).";
|
|
93
|
+
};
|
|
94
|
+
readonly dungeon: {
|
|
95
|
+
readonly type: "boolean";
|
|
96
|
+
readonly description: "Build an enclosed dungeon of rooms and corridors.";
|
|
97
|
+
};
|
|
98
|
+
readonly platformer: {
|
|
99
|
+
readonly type: "boolean";
|
|
100
|
+
readonly description: "Build a third-person platformer journey.";
|
|
101
|
+
};
|
|
102
|
+
readonly freeform: {
|
|
103
|
+
readonly type: "boolean";
|
|
104
|
+
readonly description: "Build a custom place (arena, fortress, camp).";
|
|
105
|
+
};
|
|
106
|
+
}>;
|
|
107
|
+
export {};
|
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `bitmagic forge` — design, build and install a playable level, in one command.
|
|
3
|
+
*
|
|
4
|
+
* Five steps, split across two hosts:
|
|
5
|
+
*
|
|
6
|
+
* 1. design the scene with an LLM ┐ api-server, over `POST /api/cli/v1/forge/stream`
|
|
7
|
+
* 2. forge geometry + upload GLBs ┘ (paid, and the private half of the forger)
|
|
8
|
+
* 3. create the archetype assets ┐
|
|
9
|
+
* 4. bake the level to voxels ├ HERE, driving a headless Chrome that runs the real engine
|
|
10
|
+
* 5. place instances + persist ┘
|
|
11
|
+
*
|
|
12
|
+
* Steps 3-5 are not pure compute: they drive the game engine over `postMessage`, and the round
|
|
13
|
+
* trip through the engine is the only source of the instance ids step 5 persists. So this command
|
|
14
|
+
* builds the project, serves it, opens it in Chrome, and runs the shared pipeline against that
|
|
15
|
+
* page — then applies what step 5 returns to the project's own world.json.
|
|
16
|
+
*
|
|
17
|
+
* Every failure path states explicitly whether world.json changed. A creator (or the agent driving
|
|
18
|
+
* this CLI) who does not know has to go and look, and a forge costs enough that "did I pay for
|
|
19
|
+
* nothing, and is my project still intact?" is the first question.
|
|
20
|
+
*/
|
|
21
|
+
import { defineCommand } from 'citty';
|
|
22
|
+
import { spawn, spawnSync } from 'child_process';
|
|
23
|
+
import { createForgedWorkStore, } from '@bitmagic/world-forger/pipeline/index.js';
|
|
24
|
+
import { CliError } from '../errors.js';
|
|
25
|
+
import { loadProjectContext, projectBin } from '../project/context.js';
|
|
26
|
+
import { resolveEnvironment, resolveAuth0ClientId } from '../config/environments.js';
|
|
27
|
+
import { readDefaultEnvironment } from '../config/credentials.js';
|
|
28
|
+
import { getAccessToken } from '../auth/session.js';
|
|
29
|
+
import { waitForServer } from '../verify/wait-for-server.js';
|
|
30
|
+
import { ForgeBrowserHost, readProjectGameData } from '../forge/browser-host.js';
|
|
31
|
+
import { startUploadProxy } from '../forge/upload-proxy.js';
|
|
32
|
+
import { reserveCorsAllowedPort, CORS_ALLOWED_PORT_MIN, CORS_ALLOWED_PORT_MAX } from '../local-port.js';
|
|
33
|
+
import { createBakeProgressPrinter } from '../forge/bake-progress.js';
|
|
34
|
+
import { createLocalForgeStorage } from '../forge/local-store.js';
|
|
35
|
+
import { describeForgeFailure, requestForge } from '../forge/stream.js';
|
|
36
|
+
import { buildCliForgeDeps, createConsoleForgeLogger, readFrozenInput, runForgePipeline, seedForgeStore, } from '../forge/run-pipeline.js';
|
|
37
|
+
/** Mirrors api-server's `FORGE_JOB_ID_PATTERN` so a typo is rejected here, before a round trip. */
|
|
38
|
+
const JOB_ID_PATTERN = /^[A-Za-z0-9_-]{1,64}$/;
|
|
39
|
+
/**
|
|
40
|
+
* The command that resumes a job. One definition because `decorateBrowserStepError` decides
|
|
41
|
+
* whether to append its advice by looking for this exact string — two spellings would make that
|
|
42
|
+
* check silently miss.
|
|
43
|
+
*/
|
|
44
|
+
function resumeCommand(jobId) {
|
|
45
|
+
return `bitmagic forge --resume ${jobId}`;
|
|
46
|
+
}
|
|
47
|
+
/** The four scene approaches the forger supports. Exactly one may be chosen; none = terrain. */
|
|
48
|
+
const MODE_FLAGS = ['city', 'dungeon', 'platformer', 'freeform'];
|
|
49
|
+
/** `cityMode`, `dungeonMode`, … — the body fields `ForgeJobInput` declares. */
|
|
50
|
+
function modeField(mode) {
|
|
51
|
+
return `${mode}Mode`;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* The level's name. Always sent EXPLICITLY to the server, even when derived: api-server's
|
|
55
|
+
* `resolveLevelName` falls back to its own derivation from the prompt when `name` is absent, and
|
|
56
|
+
* this lane also freezes an `input` artifact locally (see `seedForgeStore`). Two independent
|
|
57
|
+
* derivations of the same name would be a drift waiting to happen, so the CLI decides once and
|
|
58
|
+
* the server receives the answer rather than recomputing it.
|
|
59
|
+
*/
|
|
60
|
+
export function deriveLevelName(explicit, prompt) {
|
|
61
|
+
if (explicit !== undefined && explicit.trim().length > 0)
|
|
62
|
+
return explicit.trim();
|
|
63
|
+
const derived = prompt.trim().split(/\s+/).slice(0, 6).join(' ').slice(0, 48).trim();
|
|
64
|
+
return derived.length > 0 ? derived : 'level';
|
|
65
|
+
}
|
|
66
|
+
/** The single selected mode, or undefined for plain terrain. */
|
|
67
|
+
export function selectMode(args) {
|
|
68
|
+
const chosen = MODE_FLAGS.filter((mode) => args[mode] === true);
|
|
69
|
+
if (chosen.length > 1) {
|
|
70
|
+
// No "world.json is unchanged." here: `prepareForgeRequest` appends it to every CliError
|
|
71
|
+
// raised in the pre-request span, so stating it again would print it twice.
|
|
72
|
+
throw new CliError(`Choose at most one scene type — got ${chosen.map((m) => `--${m}`).join(' and ')}. `
|
|
73
|
+
+ 'Omit them all for natural terrain.');
|
|
74
|
+
}
|
|
75
|
+
return chosen[0];
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* A free port INSIDE the CORS-allowed window, never an OS-assigned ephemeral one.
|
|
79
|
+
*
|
|
80
|
+
* The bake browser itself would not care — it runs with `--disable-web-security`. The project
|
|
81
|
+
* served here outlives the bake though: this is the same build a creator then loads with
|
|
82
|
+
* `bitmagic dev` or `bitmagic verify` in an ordinary browser, and from an out-of-window origin the
|
|
83
|
+
* CDN refuses the forged `.vwld`, the engine initialises an empty voxel world, and the level has
|
|
84
|
+
* no terrain to stand on. See local-port.ts.
|
|
85
|
+
*/
|
|
86
|
+
async function freePort() {
|
|
87
|
+
const port = await reserveCorsAllowedPort();
|
|
88
|
+
if (port === null) {
|
|
89
|
+
throw new CliError(`No free port between ${CORS_ALLOWED_PORT_MIN} and ${CORS_ALLOWED_PORT_MAX}, which is the `
|
|
90
|
+
+ 'range the asset CDN allows as an origin. Stop whatever is holding them and forge again.');
|
|
91
|
+
}
|
|
92
|
+
return port;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Resolve the frozen request for this run.
|
|
96
|
+
*
|
|
97
|
+
* On a fresh run it comes from the flags. On a resume it comes from the job's LOCAL frozen
|
|
98
|
+
* `input` artifact where one exists, so the resumed request re-sends exactly what the original
|
|
99
|
+
* sent — which also lets `--resume` stand alone, without repeating the prompt. api-server freezes
|
|
100
|
+
* its own copy and never overwrites it, so a resume that did send different flags would produce a
|
|
101
|
+
* level whose geometry came from the original design but whose name and prompt came from the new
|
|
102
|
+
* invocation. Reading the frozen copy back removes that divergence rather than documenting it.
|
|
103
|
+
*/
|
|
104
|
+
export function resolveJobInput(options) {
|
|
105
|
+
const { frozen, prompt, name, mode, gameId, resumeJobId } = options;
|
|
106
|
+
if (frozen !== null && prompt === undefined)
|
|
107
|
+
return frozen;
|
|
108
|
+
if (prompt === undefined || prompt.trim().length === 0) {
|
|
109
|
+
throw new CliError(resumeJobId === null
|
|
110
|
+
? '--prompt is required. Describe the level you want, e.g. `bitmagic forge --prompt "a ruined desert temple with a central courtyard"`.'
|
|
111
|
+
: `This machine has no saved record of job "${resumeJobId}" (it was started elsewhere, or `
|
|
112
|
+
+ '`.bitmagic/` was cleared), so --prompt is required to resume it. Pass the SAME prompt '
|
|
113
|
+
+ 'the original run used — the server forges what it froze, but the level name and '
|
|
114
|
+
+ 'description saved into world.json come from this invocation.');
|
|
115
|
+
}
|
|
116
|
+
const trimmed = prompt.trim();
|
|
117
|
+
return {
|
|
118
|
+
name: deriveLevelName(name, trimmed),
|
|
119
|
+
prompt: trimmed,
|
|
120
|
+
gameId,
|
|
121
|
+
...(mode ? { [modeField(mode)]: true } : {}),
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
/** The request body for `POST /api/cli/v1/forge/stream`. */
|
|
125
|
+
export function buildForgeRequestBody(input, resumeJobId) {
|
|
126
|
+
return {
|
|
127
|
+
...input,
|
|
128
|
+
...(resumeJobId !== null ? { jobId: resumeJobId } : {}),
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
/** Print the level the forge produced, and what to do next. */
|
|
132
|
+
function reportSummary(summary, changes, log) {
|
|
133
|
+
log('');
|
|
134
|
+
log(`Level "${summary.asset_name}" is in your project.`);
|
|
135
|
+
for (const line of changes)
|
|
136
|
+
log(` - ${line}`);
|
|
137
|
+
const { x, y, z } = summary.player_start;
|
|
138
|
+
log(`Player start: (${x.toFixed(1)}, ${y.toFixed(1)}, ${z.toFixed(1)})`);
|
|
139
|
+
if (summary.library_assets.length > 0) {
|
|
140
|
+
log(`Library assets: ${summary.library_assets.map((a) => `${a.name} x${a.instances}`).join(', ')}`);
|
|
141
|
+
}
|
|
142
|
+
if (summary.markers.length > 0) {
|
|
143
|
+
log(`Markers: ${summary.markers.map((m) => m.name).join(', ')}`);
|
|
144
|
+
}
|
|
145
|
+
log('');
|
|
146
|
+
log('Run `bitmagic dev` and walk the level to check it.');
|
|
147
|
+
}
|
|
148
|
+
function defaultDeps() {
|
|
149
|
+
return {
|
|
150
|
+
fetch: globalThis.fetch,
|
|
151
|
+
sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
|
|
152
|
+
log: (message) => console.log(message),
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
/** Build the project to completion. Every alias resolves into dist/, so serving before the first
|
|
156
|
+
* emit 404s every module — a failure that reads as "the engine is broken", not "not built". */
|
|
157
|
+
function buildProject(context, log) {
|
|
158
|
+
const tsc = projectBin(context.root, 'tsc');
|
|
159
|
+
log('Building the project…');
|
|
160
|
+
const build = spawnSync(tsc, [], { cwd: context.root, stdio: 'inherit' });
|
|
161
|
+
if (build.error) {
|
|
162
|
+
throw new CliError(`Could not run the TypeScript compiler: ${build.error.message} world.json is unchanged.`);
|
|
163
|
+
}
|
|
164
|
+
if (build.status !== 0) {
|
|
165
|
+
throw new CliError('Build failed, so the game cannot be loaded to bake the level. Fix the errors above and '
|
|
166
|
+
+ 'resume this forge — world.json is unchanged.', build.status ?? 1);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Steps 3-5: serve the project, drive a headless Chrome through it, and apply the result.
|
|
171
|
+
*
|
|
172
|
+
* Split out so the browser + server lifecycle has exactly one `finally` that owns tearing both
|
|
173
|
+
* down, however this exits — an orphaned vite on a random port and a headless Chrome holding a
|
|
174
|
+
* temp profile are both silent, and both outlive the terminal.
|
|
175
|
+
*/
|
|
176
|
+
async function runBrowserSteps(options) {
|
|
177
|
+
const { context, environment, jobId, token, deps } = options;
|
|
178
|
+
const { log } = deps;
|
|
179
|
+
buildProject(context, log);
|
|
180
|
+
const port = await freePort();
|
|
181
|
+
const vite = projectBin(context.root, 'vite');
|
|
182
|
+
let server;
|
|
183
|
+
let host;
|
|
184
|
+
let uploadProxy;
|
|
185
|
+
try {
|
|
186
|
+
server = spawn(vite, ['--port', String(port), '--strictPort'], {
|
|
187
|
+
cwd: context.root,
|
|
188
|
+
stdio: ['ignore', 'ignore', 'pipe'],
|
|
189
|
+
});
|
|
190
|
+
let viteStderr = '';
|
|
191
|
+
server.stderr?.on('data', (chunk) => {
|
|
192
|
+
viteStderr += String(chunk);
|
|
193
|
+
});
|
|
194
|
+
try {
|
|
195
|
+
await waitForServer(port, 30_000);
|
|
196
|
+
}
|
|
197
|
+
catch {
|
|
198
|
+
throw new CliError(`The project's dev server did not come up on port ${port}, so the level cannot be baked.`
|
|
199
|
+
+ (viteStderr.trim() ? `\n${viteStderr.trim()}` : '')
|
|
200
|
+
+ `\nFix that, then resume with \`${resumeCommand(jobId)}\`. world.json is unchanged.`);
|
|
201
|
+
}
|
|
202
|
+
log(`Serving the project on http://localhost:${port}/ for the bake.`);
|
|
203
|
+
uploadProxy = await startUploadProxy({
|
|
204
|
+
apiUrl: environment.apiUrl,
|
|
205
|
+
token,
|
|
206
|
+
gameId: context.metadata.gameId,
|
|
207
|
+
log,
|
|
208
|
+
});
|
|
209
|
+
host = new ForgeBrowserHost({
|
|
210
|
+
gamePort: port,
|
|
211
|
+
gameId: context.metadata.gameId,
|
|
212
|
+
gameData: readProjectGameData(context.root, context.metadata),
|
|
213
|
+
// api-server, NOT game-play-agent: the engine reads this back as `window.AI_AGENT_URL` and
|
|
214
|
+
// uses it to reach `POST /api/cli/v1/uploads/signed-url` for every GLB and .vwld it writes.
|
|
215
|
+
// The LOCAL proxy, not api-server directly. The engine calls a hardcoded
|
|
216
|
+
// `${agentUrl}/api/generate-upload-url` with no Authorization header, which api-server
|
|
217
|
+
// neither serves nor would accept unauthenticated — see forge/upload-proxy.ts.
|
|
218
|
+
agentUrl: uploadProxy.url,
|
|
219
|
+
log,
|
|
220
|
+
});
|
|
221
|
+
await host.launch();
|
|
222
|
+
await host.loadGame();
|
|
223
|
+
// The bake's own chunk counts, throttled. Subscribed before the pipeline starts so no
|
|
224
|
+
// progress is missed, and left subscribed for the whole run — steps 3 and 5 post no
|
|
225
|
+
// progress of this type, so there is nothing for it to pick up outside the bake.
|
|
226
|
+
const stopBakeProgress = host.onPageMessage(createBakeProgressPrinter(log));
|
|
227
|
+
const storage = createLocalForgeStorage(context.root);
|
|
228
|
+
const store = createForgedWorkStore(jobId, storage);
|
|
229
|
+
const forgeDeps = buildCliForgeDeps({
|
|
230
|
+
projectRoot: context.root,
|
|
231
|
+
storage,
|
|
232
|
+
transport: host.transport,
|
|
233
|
+
logger: createConsoleForgeLogger(log),
|
|
234
|
+
});
|
|
235
|
+
try {
|
|
236
|
+
const result = await runForgePipeline({
|
|
237
|
+
projectRoot: context.root,
|
|
238
|
+
store,
|
|
239
|
+
deps: forgeDeps,
|
|
240
|
+
log,
|
|
241
|
+
});
|
|
242
|
+
if (result.applied) {
|
|
243
|
+
reportSummary(result.summary, result.changes, log);
|
|
244
|
+
}
|
|
245
|
+
else {
|
|
246
|
+
log(`Level "${result.summary.asset_name}" was already applied to world.json by an earlier run.`);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
finally {
|
|
250
|
+
stopBakeProgress();
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
finally {
|
|
254
|
+
await host?.close().catch(() => {
|
|
255
|
+
// Closing a browser that already died is not a forge failure, and letting it mask the real
|
|
256
|
+
// error would be. The temp profile directory is under the OS temp dir either way.
|
|
257
|
+
});
|
|
258
|
+
server?.kill('SIGTERM');
|
|
259
|
+
await uploadProxy?.close().catch(() => {
|
|
260
|
+
// Same reasoning as the browser above: a proxy that is already down must not mask the real
|
|
261
|
+
// failure. It is a loopback listener in this process either way.
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* The whole command. Failure wording is the contract here — see the file header.
|
|
267
|
+
*
|
|
268
|
+
* - Anything before step 5's apply leaves world.json untouched, and says so.
|
|
269
|
+
* - A server-side failure additionally says whether resuming can help; see
|
|
270
|
+
* `classifyForgeFailure`, which is the difference between "try again" and "change your prompt".
|
|
271
|
+
* - A failure AFTER the design and geometry landed but before (or during) the local write is the
|
|
272
|
+
* expensive case: the forge has been billed, and the message says the work is saved under a job
|
|
273
|
+
* id rather than lost.
|
|
274
|
+
*/
|
|
275
|
+
export async function runForge(args, deps = defaultDeps()) {
|
|
276
|
+
const prepared = await prepareForgeRequest(args, deps);
|
|
277
|
+
const { context, environment, storage, input, resumeJobId, token } = prepared;
|
|
278
|
+
deps.log(resumeJobId !== null
|
|
279
|
+
? `Resuming forge job ${resumeJobId} for game ${context.metadata.gameId}.`
|
|
280
|
+
: `Forging a level for game ${context.metadata.gameId}: "${input.prompt}"`);
|
|
281
|
+
deps.log('[1-2/5] Designing the scene and forging its geometry (this runs on the server)…');
|
|
282
|
+
const outcome = await requestForge(environment, token, buildForgeRequestBody(input, resumeJobId), { fetch: deps.fetch, onProgress: (message) => deps.log(` ${message}`) });
|
|
283
|
+
if (!outcome.success) {
|
|
284
|
+
throw new CliError(describeForgeFailure(outcome));
|
|
285
|
+
}
|
|
286
|
+
const { jobId, artifact, platformerMovement } = outcome;
|
|
287
|
+
deps.log(`[1-2/5] Geometry forged (job ${jobId}).`);
|
|
288
|
+
// From here on the design and the geometry are PAID FOR and saved server-side. Every failure
|
|
289
|
+
// below must therefore point at the resume rather than at re-running from scratch.
|
|
290
|
+
await seedForgeStore(createForgedWorkStore(jobId, storage), artifact, input, platformerMovement);
|
|
291
|
+
try {
|
|
292
|
+
await runBrowserSteps({ context, environment, jobId, token, deps });
|
|
293
|
+
}
|
|
294
|
+
catch (error) {
|
|
295
|
+
throw decorateBrowserStepError(error, jobId);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
async function prepareForgeRequest(args, deps) {
|
|
299
|
+
try {
|
|
300
|
+
const context = loadProjectContext(deps.startDir);
|
|
301
|
+
const mode = selectMode(args);
|
|
302
|
+
const resumeJobId = typeof args.resume === 'string' && args.resume.length > 0 ? args.resume : null;
|
|
303
|
+
if (resumeJobId !== null && !JOB_ID_PATTERN.test(resumeJobId)) {
|
|
304
|
+
throw new CliError('--resume takes a job id of 1-64 characters (letters, digits, "_" or "-") — the one printed '
|
|
305
|
+
+ 'by the run you are resuming.');
|
|
306
|
+
}
|
|
307
|
+
const storage = createLocalForgeStorage(context.root);
|
|
308
|
+
const frozen = resumeJobId !== null ? await readFrozenInput(storage, resumeJobId) : null;
|
|
309
|
+
const input = resolveJobInput({
|
|
310
|
+
frozen,
|
|
311
|
+
prompt: args.prompt,
|
|
312
|
+
name: args.name,
|
|
313
|
+
mode,
|
|
314
|
+
gameId: context.metadata.gameId,
|
|
315
|
+
resumeJobId,
|
|
316
|
+
});
|
|
317
|
+
const environment = resolveEnvironment({ storedDefault: readDefaultEnvironment(deps.baseDir) });
|
|
318
|
+
const clientId = resolveAuth0ClientId(environment);
|
|
319
|
+
const token = await getAccessToken(environment, clientId, { fetch: deps.fetch, sleep: deps.sleep, now: deps.now }, deps.baseDir);
|
|
320
|
+
return { context, environment, storage, input, resumeJobId, token };
|
|
321
|
+
}
|
|
322
|
+
catch (error) {
|
|
323
|
+
// Only a CliError is decorated, exactly as `generate.ts` does: anything else is a genuine bug
|
|
324
|
+
// rather than a condition we designed a message for, and citty's stack trace is the right
|
|
325
|
+
// output for it. (The one plain-Error condition worth naming in this span — a `.bitmagic/`
|
|
326
|
+
// that cannot be created — raises a CliError of its own in `createLocalForgeStorage`.)
|
|
327
|
+
//
|
|
328
|
+
// Deliberately UNLIKE the steps 3-5 boundary below, which decorates every error class. The
|
|
329
|
+
// difference is what is at stake: nothing has been spent yet here, so a stack trace costs a
|
|
330
|
+
// creator only clarity, whereas after the forge is billed it costs them a second design.
|
|
331
|
+
throw error instanceof CliError
|
|
332
|
+
? new CliError(`${error.message} world.json is unchanged.`, error.exitCode)
|
|
333
|
+
: error;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* Decorate a steps 3-5 failure with the job id, REGARDLESS of its class.
|
|
338
|
+
*
|
|
339
|
+
* The class check `generate.ts` uses would be wrong here, and the difference is not stylistic:
|
|
340
|
+
* steps 3-5 come from `@bitmagic/world-forger` and throw plain `Error`/`ForgeStepError`, not
|
|
341
|
+
* `CliError` — the bake timeout (`voxelize-level.ts`), the placement timeout
|
|
342
|
+
* (`place-and-persist-level.ts`), a missing artifact (`artifact-store.ts`) and the archetype
|
|
343
|
+
* timeout (`create-level-archetypes.ts`) are all third-party classes. Passing those through
|
|
344
|
+
* untouched sends them past `withCleanErrors` into citty's raw stack dump, with no job id and no
|
|
345
|
+
* world.json statement — in the exact window where the design and geometry are already paid for.
|
|
346
|
+
* A creator seeing `Error: Level voxelization timed out` and a stack has no way to know that
|
|
347
|
+
* resuming is free, and will re-run with `--prompt` and pay for a second design.
|
|
348
|
+
*
|
|
349
|
+
* `generate.ts` gets away with the class check only because nothing in its span throws a
|
|
350
|
+
* third-party error class.
|
|
351
|
+
*/
|
|
352
|
+
export function decorateBrowserStepError(error, jobId) {
|
|
353
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
354
|
+
// Skip only when the message already carries the RESUME COMMAND (the dev-server failure prints
|
|
355
|
+
// its own), never merely the job id. The shared package puts the jobId in its own text —
|
|
356
|
+
// `artifact-store.ts` says `… missing for job <jobId> — cannot continue` — so a bare
|
|
357
|
+
// `includes(jobId)` check would silently drop the advice from exactly the third-party errors
|
|
358
|
+
// this function exists to decorate.
|
|
359
|
+
if (message.includes(resumeCommand(jobId)))
|
|
360
|
+
return error;
|
|
361
|
+
const exitCode = error instanceof CliError ? error.exitCode : 1;
|
|
362
|
+
return new CliError(`${message}\nThe scene design and geometry for this level are saved and already billed — `
|
|
363
|
+
+ `resume from them with \`${resumeCommand(jobId)}\` rather than starting a new `
|
|
364
|
+
+ 'forge. Nothing already completed will be re-run or re-billed.', exitCode);
|
|
365
|
+
}
|
|
366
|
+
export const forgeCommand = defineCommand({
|
|
367
|
+
meta: {
|
|
368
|
+
name: 'forge',
|
|
369
|
+
description: 'Design, build and install a whole playable level from a prompt.',
|
|
370
|
+
},
|
|
371
|
+
args: {
|
|
372
|
+
prompt: {
|
|
373
|
+
type: 'string',
|
|
374
|
+
description: 'Describe the level, e.g. "a ruined desert temple with a central courtyard".',
|
|
375
|
+
},
|
|
376
|
+
name: {
|
|
377
|
+
type: 'string',
|
|
378
|
+
description: 'Name for the level. Derived from the prompt when omitted.',
|
|
379
|
+
},
|
|
380
|
+
resume: {
|
|
381
|
+
type: 'string',
|
|
382
|
+
description: 'Resume a job id printed by an earlier run; completed steps are skipped.',
|
|
383
|
+
},
|
|
384
|
+
city: { type: 'boolean', description: 'Build a city (street grid, buildings, landmarks).' },
|
|
385
|
+
dungeon: { type: 'boolean', description: 'Build an enclosed dungeon of rooms and corridors.' },
|
|
386
|
+
platformer: { type: 'boolean', description: 'Build a third-person platformer journey.' },
|
|
387
|
+
freeform: { type: 'boolean', description: 'Build a custom place (arena, fortress, camp).' },
|
|
388
|
+
},
|
|
389
|
+
async run({ args }) {
|
|
390
|
+
await runForge(args);
|
|
391
|
+
},
|
|
392
|
+
});
|
|
393
|
+
//# sourceMappingURL=forge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"forge.js","sourceRoot":"","sources":["../../src/commands/forge.ts"],"names":[],"mappings":"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"}
|