@bitmagic/cli 0.1.18 → 0.1.20
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/README.md +58 -1
- package/dist/cli.d.ts +10 -0
- package/dist/cli.js +2 -0
- package/dist/cli.js.map +1 -1
- package/dist/commands/edit.d.ts +10 -0
- package/dist/commands/edit.js +147 -0
- package/dist/commands/edit.js.map +1 -0
- package/dist/commands/generate.d.ts +15 -0
- package/dist/commands/generate.js +87 -0
- package/dist/commands/generate.js.map +1 -1
- package/dist/editor/journal.d.ts +108 -0
- package/dist/editor/journal.js +214 -0
- package/dist/editor/journal.js.map +1 -0
- package/dist/editor/save.d.ts +57 -0
- package/dist/editor/save.js +144 -0
- package/dist/editor/save.js.map +1 -0
- package/dist/editor/server.d.ts +24 -0
- package/dist/editor/server.js +306 -0
- package/dist/editor/server.js.map +1 -0
- package/dist/editor/shell-page.d.ts +38 -0
- package/dist/editor/shell-page.js +455 -0
- package/dist/editor/shell-page.js.map +1 -0
- package/dist/generate/prop.d.ts +47 -0
- package/dist/generate/prop.js +208 -0
- package/dist/generate/prop.js.map +1 -0
- package/dist/generate/stream.d.ts +6 -0
- package/dist/generate/stream.js +7 -2
- package/dist/generate/stream.js.map +1 -1
- package/dist/scaffold/project-files.js +42 -0
- package/dist/scaffold/project-files.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The editor sidecar: a loopback HTTP server that gives the shell page the two things a browser
|
|
3
|
+
* cannot do for itself — read the project's game data off disk, and write scene edits back to
|
|
4
|
+
* `src/work/world.json`.
|
|
5
|
+
*
|
|
6
|
+
* It is deliberately NOT part of the project's vite server. Vite serves `<projectRoot>` and its
|
|
7
|
+
* config is a file the creator owns (`vite.config.js`, scaffolded once and never upgraded), so
|
|
8
|
+
* putting a save endpoint there would freeze this protocol at whatever version the project was
|
|
9
|
+
* scaffolded with. Running it from the CLI instead means `bitmagic edit` ships its own shell and
|
|
10
|
+
* its own endpoints, and an old project gets the new editor by upgrading the CLI alone.
|
|
11
|
+
*
|
|
12
|
+
* Bound to 127.0.0.1 for the obvious reason: it writes files in the creator's project. It must not
|
|
13
|
+
* be reachable off the machine. CORS is answered explicitly because the shell (this origin) and the
|
|
14
|
+
* game (vite's origin) are different ports — only the shell talks to these endpoints, but the
|
|
15
|
+
* preflight has to succeed either way.
|
|
16
|
+
*/
|
|
17
|
+
import * as fs from 'fs';
|
|
18
|
+
import * as http from 'http';
|
|
19
|
+
import { CliError } from '../errors.js';
|
|
20
|
+
import { applyModificationsToWorld } from '../forge/apply-modifications.js';
|
|
21
|
+
import { readProjectGameData } from '../forge/browser-host.js';
|
|
22
|
+
import { projectWorldJsonPath } from '../forge/run-pipeline.js';
|
|
23
|
+
import { renderEditorShell } from './shell-page.js';
|
|
24
|
+
import { buildSceneModifications } from './save.js';
|
|
25
|
+
import { deriveSceneEvents, EditorJournal } from './journal.js';
|
|
26
|
+
import { findPropAsset } from '../generate/prop.js';
|
|
27
|
+
/** Bodies are a scene snapshot, not an upload — a megabyte is already generous. */
|
|
28
|
+
const MAX_BODY_BYTES = 8 * 1024 * 1024;
|
|
29
|
+
function readBody(req) {
|
|
30
|
+
return new Promise((resolve, reject) => {
|
|
31
|
+
let body = '';
|
|
32
|
+
req.on('data', (chunk) => {
|
|
33
|
+
body += String(chunk);
|
|
34
|
+
if (body.length > MAX_BODY_BYTES) {
|
|
35
|
+
reject(new Error('Request body too large'));
|
|
36
|
+
req.destroy();
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
req.on('end', () => resolve(body));
|
|
40
|
+
req.on('error', reject);
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
function writeCors(res) {
|
|
44
|
+
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
45
|
+
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
|
46
|
+
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
|
|
47
|
+
}
|
|
48
|
+
function sendJson(res, status, body) {
|
|
49
|
+
res.writeHead(status, { 'Content-Type': 'application/json' }).end(JSON.stringify(body));
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Modification time of world.json, or 0 when it cannot be read.
|
|
53
|
+
*
|
|
54
|
+
* This is how the shell tells the creator's own saves apart from an edit their agent made while
|
|
55
|
+
* the editor was open. The server records the mtime it produced on every write; a value that does
|
|
56
|
+
* not match is someone else's write, and the shell offers a reload rather than silently continuing
|
|
57
|
+
* against a scene that no longer matches the file.
|
|
58
|
+
*/
|
|
59
|
+
function worldMtimeMs(worldPath) {
|
|
60
|
+
try {
|
|
61
|
+
return fs.statSync(worldPath).mtimeMs;
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
return 0;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
function isRecord(value) {
|
|
68
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* The command the agent should run to fulfil an HQ request. One definition, quoted for a shell,
|
|
72
|
+
* so the journal line, the HTTP reply and the editor's own confirmation cannot drift apart.
|
|
73
|
+
*/
|
|
74
|
+
export function hqCommand(assetId, prompt) {
|
|
75
|
+
const quoted = prompt.replace(/"/g, '\\"');
|
|
76
|
+
return `bitmagic generate prop --asset ${assetId} --prompt "${quoted}" --json`;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Whether `bitmagic generate prop` could actually regenerate this asset. Answered by the command's
|
|
80
|
+
* own precondition (`findPropAsset`), so the two cannot disagree about what is regenerable.
|
|
81
|
+
*/
|
|
82
|
+
function hasFitBox(worldPath, assetId) {
|
|
83
|
+
try {
|
|
84
|
+
findPropAsset(JSON.parse(fs.readFileSync(worldPath, 'utf-8')), assetId);
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
function readWorld(worldPath) {
|
|
92
|
+
const parsed = JSON.parse(fs.readFileSync(worldPath, 'utf-8'));
|
|
93
|
+
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
|
|
94
|
+
throw new CliError(`${worldPath} does not contain a world object.`);
|
|
95
|
+
}
|
|
96
|
+
return parsed;
|
|
97
|
+
}
|
|
98
|
+
export async function startEditorServer(options) {
|
|
99
|
+
const log = options.log ?? (() => { });
|
|
100
|
+
const worldPath = projectWorldJsonPath(options.root);
|
|
101
|
+
const shell = renderEditorShell({ gamePort: options.gamePort, gameId: options.gameId });
|
|
102
|
+
const journal = new EditorJournal({ root: options.root, log });
|
|
103
|
+
/** The mtime our own last write produced. Anything else is an external edit. */
|
|
104
|
+
let lastWrittenMtimeMs = worldMtimeMs(worldPath);
|
|
105
|
+
const handleSave = async (req, res) => {
|
|
106
|
+
let payload;
|
|
107
|
+
try {
|
|
108
|
+
payload = JSON.parse(await readBody(req));
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
sendJson(res, 400, { ok: false, error: 'Request body must be JSON' });
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
if (payload?.status?.unlocked !== true) {
|
|
115
|
+
// Not an error: the engine reports the scene locked until the world has loaded, and the
|
|
116
|
+
// shell's poll can land in that window.
|
|
117
|
+
sendJson(res, 200, { ok: true, applied: 0, skipped: 'scene editing is locked' });
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
let modifications;
|
|
121
|
+
let events;
|
|
122
|
+
try {
|
|
123
|
+
// One read serves both: the modification builder needs the levels registry, and the journal
|
|
124
|
+
// needs the pre-edit transforms to report what each object moved FROM.
|
|
125
|
+
const before = readWorld(worldPath);
|
|
126
|
+
modifications = buildSceneModifications(payload, before);
|
|
127
|
+
events = deriveSceneEvents(before, payload);
|
|
128
|
+
}
|
|
129
|
+
catch (error) {
|
|
130
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
131
|
+
log(`[editor] could not read ${worldPath}: ${message}`);
|
|
132
|
+
sendJson(res, 500, { ok: false, error: message });
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
if (modifications.length === 0) {
|
|
136
|
+
sendJson(res, 200, { ok: true, applied: 0, worldMtimeMs: lastWrittenMtimeMs });
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
try {
|
|
140
|
+
const outcome = applyModificationsToWorld(worldPath, modifications);
|
|
141
|
+
lastWrittenMtimeMs = worldMtimeMs(worldPath);
|
|
142
|
+
// Journalled AFTER the write lands, never before: an agent reading a `object.moved` line
|
|
143
|
+
// must be able to trust that world.json already says so.
|
|
144
|
+
journal.append(...events);
|
|
145
|
+
sendJson(res, 200, {
|
|
146
|
+
ok: true,
|
|
147
|
+
applied: outcome.applied,
|
|
148
|
+
summary: outcome.summary,
|
|
149
|
+
worldMtimeMs: lastWrittenMtimeMs,
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
catch (error) {
|
|
153
|
+
// `applyModificationsToWorld` writes nothing when it throws, so world.json still holds
|
|
154
|
+
// whatever it had. Report it rather than swallowing: the shell surfaces it, because a save
|
|
155
|
+
// that silently did nothing is the worst outcome for an autosaving editor.
|
|
156
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
157
|
+
log(`[editor] save refused: ${message}`);
|
|
158
|
+
sendJson(res, 422, { ok: false, error: message });
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
/**
|
|
162
|
+
* The "Generate high-quality version" button.
|
|
163
|
+
*
|
|
164
|
+
* The editor deliberately does not run the generation. Producing the mesh means calling the
|
|
165
|
+
* Asset Forger with credentials that are server-side only and never reach a creator's machine,
|
|
166
|
+
* so the editor's job is to capture the human's INTENT precisely and hand it to the agent, which
|
|
167
|
+
* is the thing in this lane that runs long, paid jobs.
|
|
168
|
+
*
|
|
169
|
+
* The confirmed text is saved onto the asset before anything else. That mirrors the Creator, and
|
|
170
|
+
* for the same reason: a forger placeholder often carries no description at all, and the
|
|
171
|
+
* description the human just wrote is worth keeping whether or not a generation ever follows.
|
|
172
|
+
*/
|
|
173
|
+
const handleHqRequest = async (req, res) => {
|
|
174
|
+
let body;
|
|
175
|
+
try {
|
|
176
|
+
body = JSON.parse(await readBody(req));
|
|
177
|
+
}
|
|
178
|
+
catch {
|
|
179
|
+
sendJson(res, 400, { ok: false, error: 'Request body must be JSON' });
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
const { assetId, prompt } = body;
|
|
183
|
+
if (typeof assetId !== 'string' || typeof prompt !== 'string' || prompt.trim() === '') {
|
|
184
|
+
sendJson(res, 400, { ok: false, error: 'assetId and a non-empty prompt are required' });
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
const assetName = typeof body.assetName === 'string' && body.assetName !== '' ? body.assetName : assetId;
|
|
188
|
+
const description = prompt.trim();
|
|
189
|
+
try {
|
|
190
|
+
applyModificationsToWorld(worldPath, [{
|
|
191
|
+
type: 'updateRoot',
|
|
192
|
+
path: ['assets'],
|
|
193
|
+
predicate: (item) => isRecord(item) && item.id === assetId,
|
|
194
|
+
// An updater, not a replacement: the asset carries fitBox, sourceGlbUrl and the rest, and
|
|
195
|
+
// the generation the agent runs later depends on every one of them.
|
|
196
|
+
value: (item) => ({ ...(isRecord(item) ? item : {}), description }),
|
|
197
|
+
}]);
|
|
198
|
+
lastWrittenMtimeMs = worldMtimeMs(worldPath);
|
|
199
|
+
}
|
|
200
|
+
catch (error) {
|
|
201
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
202
|
+
log(`[editor] could not save the description for ${assetId}: ${message}`);
|
|
203
|
+
sendJson(res, 422, { ok: false, error: message });
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
// Only forge-baked assets carry a fitBox, and without one there is no envelope to voxelize
|
|
207
|
+
// to — `bitmagic generate prop` refuses. The engine offers its button on any asset flagged
|
|
208
|
+
// `placeholder`, so that combination is reachable, and handing the agent a command that cannot
|
|
209
|
+
// succeed is worse than saying so. The description is kept either way: it is what the human
|
|
210
|
+
// meant this object to be, and the asset agent reads it.
|
|
211
|
+
const generatable = hasFitBox(worldPath, assetId);
|
|
212
|
+
if (!generatable) {
|
|
213
|
+
log(`[editor] ${assetName} has no fitBox — description saved, but it cannot be regenerated.`);
|
|
214
|
+
sendJson(res, 200, {
|
|
215
|
+
ok: true,
|
|
216
|
+
canGenerate: false,
|
|
217
|
+
reason: 'This asset has no fitBox, so there is no size to generate it to. '
|
|
218
|
+
+ 'Only forge-baked assets can be regenerated.',
|
|
219
|
+
worldMtimeMs: lastWrittenMtimeMs,
|
|
220
|
+
});
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
journal.append({
|
|
224
|
+
event: 'hq.requested',
|
|
225
|
+
assetId,
|
|
226
|
+
assetName,
|
|
227
|
+
prompt: description,
|
|
228
|
+
command: hqCommand(assetId, description),
|
|
229
|
+
});
|
|
230
|
+
sendJson(res, 200, {
|
|
231
|
+
ok: true,
|
|
232
|
+
canGenerate: true,
|
|
233
|
+
command: hqCommand(assetId, description),
|
|
234
|
+
worldMtimeMs: lastWrittenMtimeMs,
|
|
235
|
+
});
|
|
236
|
+
};
|
|
237
|
+
const server = http.createServer((req, res) => {
|
|
238
|
+
void (async () => {
|
|
239
|
+
writeCors(res);
|
|
240
|
+
if (req.method === 'OPTIONS') {
|
|
241
|
+
res.writeHead(204).end();
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
const url = (req.url ?? '/').split('?')[0];
|
|
245
|
+
if (req.method === 'GET' && (url === '/' || url === '/index.html')) {
|
|
246
|
+
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' }).end(shell);
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
if (req.method === 'GET' && url === '/api/game-data') {
|
|
250
|
+
try {
|
|
251
|
+
sendJson(res, 200, {
|
|
252
|
+
gameId: options.gameId,
|
|
253
|
+
// The same merge the forge browser uses: `GameEngine.loadGame` reads
|
|
254
|
+
// `worldProfileData` from world.json and `gameGenre` from game.json, and refuses to
|
|
255
|
+
// load with either missing.
|
|
256
|
+
gameData: readProjectGameData(options.root, { gameId: options.gameId }),
|
|
257
|
+
worldMtimeMs: worldMtimeMs(worldPath),
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
catch (error) {
|
|
261
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
262
|
+
sendJson(res, 500, { ok: false, error: message });
|
|
263
|
+
}
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
if (req.method === 'GET' && url === '/api/state') {
|
|
267
|
+
sendJson(res, 200, {
|
|
268
|
+
worldMtimeMs: worldMtimeMs(worldPath),
|
|
269
|
+
lastWrittenMtimeMs,
|
|
270
|
+
});
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
if (req.method === 'POST' && url === '/api/scene/save') {
|
|
274
|
+
await handleSave(req, res);
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
if (req.method === 'POST' && url === '/api/editor/hq-request') {
|
|
278
|
+
await handleHqRequest(req, res);
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
sendJson(res, 404, { ok: false, error: `No route for ${req.method ?? '?'} ${url}` });
|
|
282
|
+
})();
|
|
283
|
+
});
|
|
284
|
+
await new Promise((resolve, reject) => {
|
|
285
|
+
server.once('error', (error) => {
|
|
286
|
+
reject(error.code === 'EADDRINUSE'
|
|
287
|
+
? new CliError(`Port ${options.port} is already in use, so the editor cannot start. `
|
|
288
|
+
+ 'Stop whatever is using it, or pass `--editor-port`.')
|
|
289
|
+
: new CliError(`The editor server could not start: ${error.message}`));
|
|
290
|
+
});
|
|
291
|
+
server.listen(options.port, '127.0.0.1', resolve);
|
|
292
|
+
});
|
|
293
|
+
const url = `http://localhost:${options.port}/`;
|
|
294
|
+
journal.append({
|
|
295
|
+
event: 'session.started',
|
|
296
|
+
gameId: options.gameId,
|
|
297
|
+
gameUrl: `http://localhost:${options.gamePort}/`,
|
|
298
|
+
editorUrl: url,
|
|
299
|
+
});
|
|
300
|
+
return {
|
|
301
|
+
url,
|
|
302
|
+
journalPath: journal.path,
|
|
303
|
+
close: () => new Promise((resolve) => server.close(() => resolve())),
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/editor/server.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The editor shell: the page that plays the Creator's role for a CLI-lane project.
|
|
3
|
+
*
|
|
4
|
+
* The whole feature rests on one fact — the visual editor is ALREADY in every scaffolded project.
|
|
5
|
+
* `VENDORED_DIRS` in `scaffold/project.ts` ships `engine/editor/` and `engine/debug/`, and
|
|
6
|
+
* `GameEngine` constructs `EditorManager` unconditionally, building the transform gizmo, the object
|
|
7
|
+
* inspector and the scene hierarchy into a hidden `#debug-container`. What the pro lane lacked was
|
|
8
|
+
* a parent frame speaking the Creator's `postMessage` protocol. This page is that frame, and
|
|
9
|
+
* nothing under `game/` changes to support it.
|
|
10
|
+
*
|
|
11
|
+
* Rendered from TypeScript rather than shipped as a `.html` asset because the package builds with
|
|
12
|
+
* plain `tsc`, which copies no static files. `smoke/harness.ts` and `scaffold/project-files.ts`
|
|
13
|
+
* both do the same.
|
|
14
|
+
*
|
|
15
|
+
* ── Four things that fail silently if changed ────────────────────────────────────────────────
|
|
16
|
+
*
|
|
17
|
+
* 1. `?source=creator` on the iframe URL. Without it `isCreatorMode` is false
|
|
18
|
+
* (`game/src/engine/CreatorMode.ts`), the engine never registers its message listener, and
|
|
19
|
+
* every message below is discarded with no error anywhere.
|
|
20
|
+
* 2. `GAME_TEMPLATE_READY` must arrive before `LOAD_GAME` is posted. The engine registers its
|
|
21
|
+
* listener only after `await initI18n()`, and a `LOAD_GAME` landing before that is DROPPED,
|
|
22
|
+
* not queued — the symptom is a game that never loads, pointing at the wrong culprit.
|
|
23
|
+
* 3. `REQUEST_ASSETS` / `ADD_OBJECT` / `MARK_OBJECT_MODIFIED` use a FLAT envelope
|
|
24
|
+
* (`{ type, assets }`), not the `{ type, data }` one the rest of the protocol uses. That is
|
|
25
|
+
* the Creator's existing shape (`useIframeMessages.ts:1149`, `:332`) and the engine reads the
|
|
26
|
+
* fields off the message directly.
|
|
27
|
+
* 4. The autosave poll, rather than an event. `TransformControlsManager`'s mouseUp reaches
|
|
28
|
+
* `EditorManager.commitTransformChange()`, which only mutates a `Set` — the engine posts
|
|
29
|
+
* nothing. Polling `CHECK_SCENE_CHANGES` is what the Creator does too, just on tab switch
|
|
30
|
+
* instead of on a timer, and it covers drags, deletes, adds and inspector edits with one path.
|
|
31
|
+
*/
|
|
32
|
+
export interface EditorShellOptions {
|
|
33
|
+
/** Port vite serves the game on. */
|
|
34
|
+
gamePort: number;
|
|
35
|
+
/** The game this project owns. */
|
|
36
|
+
gameId: string;
|
|
37
|
+
}
|
|
38
|
+
export declare function renderEditorShell(options: EditorShellOptions): string;
|