@vgai/editor-sdk 0.1.0 → 0.4.0-canary.20260715.0
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 +33 -3
- package/package.json +9 -1
- package/src/client.ts +143 -9
- package/src/index.ts +13 -0
- package/src/types.ts +156 -1
package/README.md
CHANGED
|
@@ -24,7 +24,10 @@ One export, `EditorClient`, plus its types (`EditorState`, `ProjectInfo`,
|
|
|
24
24
|
|
|
25
25
|
- Play control: `play`, `stop`, `pause`, `resume`, `step`
|
|
26
26
|
- Selection: `select(id | null)`, `selectMultiple`, `selectAll`
|
|
27
|
-
- Viewport: `focusEntity`, `focusSelection`, `viewPreset`
|
|
27
|
+
- Viewport: `focusEntity`, `focusSelection`, `viewPreset`, `setCamera`,
|
|
28
|
+
`captureViewport`
|
|
29
|
+
- Asset preview: `captureAssetPreview` for deterministic front, right, top,
|
|
30
|
+
and three-quarter captures of a project model or authored entity hierarchy
|
|
28
31
|
- Panels: `showViewport('scene'|'game')`, `showInspector`, `openAsset`,
|
|
29
32
|
`closeAsset`, `toggleConsole`, `toggleCommandPalette`, `showBuild`
|
|
30
33
|
- Display: `setGrid`, `setHelpers`, `setStats`, `setShadingMode`,
|
|
@@ -38,9 +41,36 @@ One export, `EditorClient`, plus its types (`EditorState`, `ProjectInfo`,
|
|
|
38
41
|
Commands throw on `{ ok: false }` responses, including the server's timeout
|
|
39
42
|
when no browser editor is connected — failures are never silently swallowed.
|
|
40
43
|
|
|
44
|
+
### Asset Lab capture
|
|
45
|
+
|
|
46
|
+
`captureAssetPreview` accepts exactly one source: an authored entity hierarchy
|
|
47
|
+
already loaded in the editor, or a same-origin project `.glb`/`.gltf` path.
|
|
48
|
+
Rendering happens in an isolated native editor scene and returns deterministic
|
|
49
|
+
front, right, top, and three-quarter PNGs plus a labeled contact sheet:
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
const sketch = await editor.captureAssetPreview(
|
|
53
|
+
{ entityId: 'storefront-sketch' },
|
|
54
|
+
{ width: 768, height: 768, background: 'neutral' },
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
const model = await editor.captureAssetPreview(
|
|
58
|
+
{ assetPath: '/models/storefront.glb' },
|
|
59
|
+
{ background: 'transparent' },
|
|
60
|
+
);
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Project model input is bounded to 64 MiB and 25 seconds. External `.gltf`
|
|
64
|
+
buffers and images must resolve on the same project origin. The thin CLI
|
|
65
|
+
equivalent writes the four views and contact sheet to disk:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
vgai asset-preview --asset /models/storefront.glb --out artifacts/storefront
|
|
69
|
+
```
|
|
70
|
+
|
|
41
71
|
## Limitations
|
|
42
72
|
|
|
43
|
-
-
|
|
44
|
-
|
|
73
|
+
- The published package exports raw TypeScript (`exports: "./src/index.ts"`),
|
|
74
|
+
so consumers need a TypeScript-aware runner/bundler such as tsx or Vite.
|
|
45
75
|
- Requires the editor dev server (`npm run dev` or `vgai edit`); there is no
|
|
46
76
|
offline mode.
|
package/package.json
CHANGED
|
@@ -2,8 +2,16 @@
|
|
|
2
2
|
"name": "@vgai/editor-sdk",
|
|
3
3
|
"author": "Volter AI, Inc.",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
|
-
"version": "0.
|
|
5
|
+
"version": "0.4.0-canary.20260715.0",
|
|
6
6
|
"type": "module",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/volter-ai/vgai-engine.git",
|
|
10
|
+
"directory": "packages/editor-sdk"
|
|
11
|
+
},
|
|
12
|
+
"publishConfig": {
|
|
13
|
+
"access": "public"
|
|
14
|
+
},
|
|
7
15
|
"files": [
|
|
8
16
|
"src"
|
|
9
17
|
],
|
package/src/client.ts
CHANGED
|
@@ -1,4 +1,23 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
AssetKind,
|
|
3
|
+
AssetPreviewCapture,
|
|
4
|
+
AssetPreviewOptions,
|
|
5
|
+
AssetPreviewSource,
|
|
6
|
+
EditorState,
|
|
7
|
+
GameCapture,
|
|
8
|
+
HelperVisibility,
|
|
9
|
+
ProjectInfo,
|
|
10
|
+
ProjectOperationCatalog,
|
|
11
|
+
ProjectOperationOutcome,
|
|
12
|
+
ProjectTemplate,
|
|
13
|
+
RecentProject,
|
|
14
|
+
ShadingMode,
|
|
15
|
+
TransformMode,
|
|
16
|
+
TransformSpace,
|
|
17
|
+
Vec3Value,
|
|
18
|
+
ViewPreset,
|
|
19
|
+
ViewportCapture,
|
|
20
|
+
} from './types.js';
|
|
2
21
|
|
|
3
22
|
const DEFAULT_URL = 'http://localhost:5173';
|
|
4
23
|
|
|
@@ -9,22 +28,30 @@ export class EditorClient {
|
|
|
9
28
|
this.baseUrl = (opts?.url ?? DEFAULT_URL).replace(/\/$/, '');
|
|
10
29
|
}
|
|
11
30
|
|
|
12
|
-
private async command
|
|
31
|
+
private async command<T extends object = Record<string, never>>(
|
|
32
|
+
body: Record<string, unknown>,
|
|
33
|
+
): Promise<T> {
|
|
13
34
|
const res = await fetch(`${this.baseUrl}/__editor/command`, {
|
|
14
35
|
method: 'POST',
|
|
15
36
|
headers: { 'Content-Type': 'application/json' },
|
|
16
37
|
body: JSON.stringify(body),
|
|
17
38
|
});
|
|
18
|
-
const data = (await res.json()) as { ok: boolean; error?: string };
|
|
39
|
+
const data = (await res.json()) as { ok: boolean; error?: string } & T;
|
|
19
40
|
if (!data.ok) {
|
|
20
41
|
throw new Error(data.error ?? `Editor command failed: ${res.status}`);
|
|
21
42
|
}
|
|
43
|
+
return data;
|
|
22
44
|
}
|
|
23
45
|
|
|
24
46
|
// --- Play control ---
|
|
25
47
|
|
|
26
|
-
|
|
27
|
-
|
|
48
|
+
/** `opts.seed` (D15/T-D15.6, objection-4 fix) — `vgai play --seed <n>`'s
|
|
49
|
+
* explicit config leg, relayed as `cmd['seed']`; `handleCommand`'s
|
|
50
|
+
* `'play'` case threads it into `enterPlayMode`'s highest-precedence seed
|
|
51
|
+
* argument (beats manifest.determinism.defaultSeed/?vgai-seed=). Omitted,
|
|
52
|
+
* boot seeding falls back to that precedence unchanged. */
|
|
53
|
+
async play(opts?: { seed?: number }): Promise<void> {
|
|
54
|
+
await this.command({ type: 'play', ...(opts?.seed !== undefined ? { seed: opts.seed } : {}) });
|
|
28
55
|
}
|
|
29
56
|
|
|
30
57
|
async stop(): Promise<void> {
|
|
@@ -71,6 +98,74 @@ export class EditorClient {
|
|
|
71
98
|
await this.command({ type: 'view-preset', preset });
|
|
72
99
|
}
|
|
73
100
|
|
|
101
|
+
async setCamera(position: Vec3Value, target: Vec3Value, fov?: number): Promise<void> {
|
|
102
|
+
await this.command({
|
|
103
|
+
type: 'set-camera',
|
|
104
|
+
position,
|
|
105
|
+
target,
|
|
106
|
+
...(fov === undefined ? {} : { fov }),
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async captureViewport(size?: number): Promise<ViewportCapture> {
|
|
111
|
+
const data = await this.command<ViewportCapture>({
|
|
112
|
+
type: 'capture-viewport',
|
|
113
|
+
...(size === undefined ? {} : { size }),
|
|
114
|
+
});
|
|
115
|
+
return { base64: data.base64, mimeType: data.mimeType };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Unit 4 (live-front-door wave) — capture the RUNNING GAME (`vgai
|
|
120
|
+
* screenshot`'s wire leg). Sends the SAME `bridge-screenshot` relay op
|
|
121
|
+
* `@vgai/probe`'s `RelayTransport.screenshot` (and therefore
|
|
122
|
+
* `game.screenshot()` on the relay path) already sends, so all three
|
|
123
|
+
* surfaces composite the identical full game stack — canvas(es) plus the
|
|
124
|
+
* HUD/react DOM layers — rather than any of them inventing a second,
|
|
125
|
+
* subtly-different capture path. Contrast {@link captureViewport}, which
|
|
126
|
+
* captures the EDITOR viewport's canvas and would silently hand back an
|
|
127
|
+
* editor-only (HUD-less, possibly not-even-playing) image.
|
|
128
|
+
*
|
|
129
|
+
* Rejects — loudly, via `command`'s own `{ok:false}` unwrap — when play
|
|
130
|
+
* mode isn't running ("not in play mode — start play before using the
|
|
131
|
+
* debug seam") or no game canvas is mounted yet. Never returns a blank or
|
|
132
|
+
* editor-only frame as a stand-in.
|
|
133
|
+
*/
|
|
134
|
+
async captureGame(): Promise<GameCapture> {
|
|
135
|
+
const data = await this.command<GameCapture>({ type: 'bridge-screenshot' });
|
|
136
|
+
const layers = data.layers;
|
|
137
|
+
return {
|
|
138
|
+
base64: data.base64,
|
|
139
|
+
mimeType: data.mimeType,
|
|
140
|
+
composite: data.composite === true,
|
|
141
|
+
...(layers && Number.isInteger(layers.canvases) && Number.isInteger(layers.domOverlays)
|
|
142
|
+
? { layers }
|
|
143
|
+
: {}),
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Capture an isolated, deterministic four-view preview through the editor's
|
|
149
|
+
* native Asset Lab. The SDK delegates rendering to the editor; it never
|
|
150
|
+
* loads, clones, or interprets Three.js assets itself.
|
|
151
|
+
*/
|
|
152
|
+
async captureAssetPreview(
|
|
153
|
+
source: AssetPreviewSource,
|
|
154
|
+
options: AssetPreviewOptions = {},
|
|
155
|
+
): Promise<AssetPreviewCapture> {
|
|
156
|
+
const data = await this.command<AssetPreviewCapture>({
|
|
157
|
+
type: 'capture-asset-preview',
|
|
158
|
+
...source,
|
|
159
|
+
...options,
|
|
160
|
+
});
|
|
161
|
+
return {
|
|
162
|
+
width: data.width,
|
|
163
|
+
height: data.height,
|
|
164
|
+
views: data.views,
|
|
165
|
+
contactSheet: data.contactSheet,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
74
169
|
// --- Panels ---
|
|
75
170
|
|
|
76
171
|
async showViewport(tab: 'scene' | 'game'): Promise<void> {
|
|
@@ -119,7 +214,7 @@ export class EditorClient {
|
|
|
119
214
|
await this.command({ type: 'set-shading-mode', mode });
|
|
120
215
|
}
|
|
121
216
|
|
|
122
|
-
async setHelperType(helperType:
|
|
217
|
+
async setHelperType(helperType: keyof HelperVisibility, enabled: boolean): Promise<void> {
|
|
123
218
|
await this.command({ type: 'set-helper-type', helperType, enabled });
|
|
124
219
|
}
|
|
125
220
|
|
|
@@ -145,7 +240,12 @@ export class EditorClient {
|
|
|
145
240
|
|
|
146
241
|
// --- Project management ---
|
|
147
242
|
|
|
148
|
-
async createProject(
|
|
243
|
+
async createProject(
|
|
244
|
+
name: string,
|
|
245
|
+
location: string,
|
|
246
|
+
template: ProjectTemplate = 'default',
|
|
247
|
+
exampleId?: string,
|
|
248
|
+
): Promise<ProjectInfo> {
|
|
149
249
|
const res = await fetch(`${this.baseUrl}/__editor/create-project`, {
|
|
150
250
|
method: 'POST',
|
|
151
251
|
headers: { 'Content-Type': 'application/json' },
|
|
@@ -185,12 +285,46 @@ export class EditorClient {
|
|
|
185
285
|
return data.projects;
|
|
186
286
|
}
|
|
187
287
|
|
|
288
|
+
// --- Project operations ---
|
|
289
|
+
|
|
290
|
+
/** List live project-local operation definitions below `src/operations/`.
|
|
291
|
+
* The editor server loads metadata in Node; operation modules never enter the
|
|
292
|
+
* editor browser merely because they were listed. */
|
|
293
|
+
async listProjectOperations(): Promise<ProjectOperationCatalog> {
|
|
294
|
+
const res = await fetch(`${this.baseUrl}/__editor/project-operations`);
|
|
295
|
+
if (!res.ok) throw new Error(`Failed to list project operations: ${res.status}`);
|
|
296
|
+
return (await res.json()) as ProjectOperationCatalog;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/** Execute one Node-hosted project operation through the existing validated
|
|
300
|
+
* operation registry. Write/destructive operations require `confirm:true`. */
|
|
301
|
+
async runProjectOperation(
|
|
302
|
+
name: string,
|
|
303
|
+
input: unknown = {},
|
|
304
|
+
options: { confirm?: boolean } = {},
|
|
305
|
+
): Promise<ProjectOperationOutcome> {
|
|
306
|
+
const res = await fetch(`${this.baseUrl}/__editor/project-operations/run`, {
|
|
307
|
+
method: 'POST',
|
|
308
|
+
headers: { 'Content-Type': 'application/json' },
|
|
309
|
+
body: JSON.stringify({ name, input, confirm: options.confirm === true }),
|
|
310
|
+
});
|
|
311
|
+
const body = (await res.json()) as ProjectOperationOutcome;
|
|
312
|
+
if (!body || typeof body !== 'object' || typeof body.ok !== 'boolean') {
|
|
313
|
+
throw new Error(`Project operation returned an invalid response (${res.status}).`);
|
|
314
|
+
}
|
|
315
|
+
return body;
|
|
316
|
+
}
|
|
317
|
+
|
|
188
318
|
// --- Logs ---
|
|
189
319
|
|
|
190
|
-
async getLogEntries(): Promise<
|
|
320
|
+
async getLogEntries(): Promise<
|
|
321
|
+
Array<{ t: number; level: string; msg: string; source?: string }>
|
|
322
|
+
> {
|
|
191
323
|
const res = await fetch(`${this.baseUrl}/__editor/log-entries`);
|
|
192
324
|
if (!res.ok) return [];
|
|
193
|
-
const data = (await res.json()) as {
|
|
325
|
+
const data = (await res.json()) as {
|
|
326
|
+
entries: Array<{ t: number; level: string; msg: string; source?: string }>;
|
|
327
|
+
};
|
|
194
328
|
return data.entries;
|
|
195
329
|
}
|
|
196
330
|
|
package/src/index.ts
CHANGED
|
@@ -1,13 +1,26 @@
|
|
|
1
1
|
export { EditorClient } from './client.js';
|
|
2
2
|
export type {
|
|
3
3
|
AssetKind,
|
|
4
|
+
AssetPreviewBackground,
|
|
5
|
+
AssetPreviewCapture,
|
|
6
|
+
AssetPreviewOptions,
|
|
7
|
+
AssetPreviewSource,
|
|
8
|
+
AssetPreviewView,
|
|
9
|
+
EditorCameraState,
|
|
10
|
+
EditorEntitySummary,
|
|
4
11
|
EditorState,
|
|
12
|
+
GameCapture,
|
|
5
13
|
HelperVisibility,
|
|
6
14
|
ProjectInfo,
|
|
15
|
+
ProjectOperationCatalog,
|
|
16
|
+
ProjectOperationCatalogEntry,
|
|
17
|
+
ProjectOperationOutcome,
|
|
7
18
|
ProjectTemplate,
|
|
8
19
|
RecentProject,
|
|
9
20
|
ShadingMode,
|
|
10
21
|
TransformMode,
|
|
11
22
|
TransformSpace,
|
|
23
|
+
Vec3Value,
|
|
12
24
|
ViewPreset,
|
|
25
|
+
ViewportCapture,
|
|
13
26
|
} from './types.js';
|
package/src/types.ts
CHANGED
|
@@ -15,10 +15,87 @@ export interface HelperVisibility {
|
|
|
15
15
|
audio: boolean;
|
|
16
16
|
splines: boolean;
|
|
17
17
|
navmesh: boolean;
|
|
18
|
+
skeletons: boolean;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface Vec3Value {
|
|
22
|
+
x: number;
|
|
23
|
+
y: number;
|
|
24
|
+
z: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface EditorCameraState {
|
|
28
|
+
position: Vec3Value;
|
|
29
|
+
target: Vec3Value;
|
|
30
|
+
fov?: number;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface EditorEntitySummary {
|
|
34
|
+
id: string;
|
|
35
|
+
name: string;
|
|
36
|
+
childIds: string[];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface ViewportCapture {
|
|
40
|
+
base64: string;
|
|
41
|
+
mimeType: 'image/png';
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Unit 4 (live-front-door wave) — the RUNNING GAME's pixels, as captured by
|
|
46
|
+
* the `bridge-screenshot` relay op (`command-listener.ts`'s
|
|
47
|
+
* `handleBridgeScreenshot`): the full play-mode game stack — canvas(es) PLUS
|
|
48
|
+
* the DOM adapter layers (for example React roots) composited by
|
|
49
|
+
* `capturePlayComposite`. Distinct from {@link ViewportCapture}, which is the
|
|
50
|
+
* EDITOR viewport's own canvas (`capture-viewport`) and knows nothing about
|
|
51
|
+
* play mode or the HUD.
|
|
52
|
+
*
|
|
53
|
+
* `composite` reports honestly whether the DOM-layer composite leg actually
|
|
54
|
+
* ran, or whether the capture degraded to a canvas-only `toDataURL` frame (no
|
|
55
|
+
* container, no DOM `Image`/`XMLSerializer`, or a rasterization failure) —
|
|
56
|
+
* never a silently HUD-less image passed off as the whole game. `layers` is
|
|
57
|
+
* only present on the composite leg.
|
|
58
|
+
*/
|
|
59
|
+
export interface GameCapture {
|
|
60
|
+
base64: string;
|
|
61
|
+
mimeType: 'image/png';
|
|
62
|
+
composite: boolean;
|
|
63
|
+
layers?: { canvases: number; domOverlays: number };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export type AssetPreviewView = 'front' | 'right' | 'top' | 'perspective';
|
|
67
|
+
export type AssetPreviewBackground = 'neutral' | 'transparent';
|
|
68
|
+
|
|
69
|
+
export type AssetPreviewSource =
|
|
70
|
+
| { assetPath: string; entityId?: never }
|
|
71
|
+
| { entityId: string; assetPath?: never };
|
|
72
|
+
|
|
73
|
+
export interface AssetPreviewOptions {
|
|
74
|
+
width?: number;
|
|
75
|
+
height?: number;
|
|
76
|
+
background?: AssetPreviewBackground;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export interface AssetPreviewCapture {
|
|
80
|
+
width: number;
|
|
81
|
+
height: number;
|
|
82
|
+
views: Array<ViewportCapture & { view: AssetPreviewView }>;
|
|
83
|
+
contactSheet: ViewportCapture & { width: number; height: number };
|
|
18
84
|
}
|
|
19
85
|
|
|
20
86
|
export interface EditorState {
|
|
21
87
|
playState: 'stopped' | 'playing' | 'paused';
|
|
88
|
+
/**
|
|
89
|
+
* Issue #175 — the REAL engine `GameLoop.liveness` behind the current play
|
|
90
|
+
* session, distinct from `playState` above (editor UI state — a store
|
|
91
|
+
* flag that never reflected whether the loop was actually ticking).
|
|
92
|
+
* `'hidden-paused'` means the T2.1 idle throttle stopped the loop because
|
|
93
|
+
* the editor tab is backgrounded — deliberate and reversible, but the
|
|
94
|
+
* game is advancing ZERO ticks right now even though `playState` still
|
|
95
|
+
* reads `'playing'`. `null` while not in play mode (no loop to report on)
|
|
96
|
+
* or against an older server that predates this field.
|
|
97
|
+
*/
|
|
98
|
+
loopLiveness?: 'running' | 'hidden-paused' | 'stopped' | null;
|
|
22
99
|
selectedEntityId: string | null;
|
|
23
100
|
selectedEntityIds: string[];
|
|
24
101
|
activeViewportTab: 'scene' | 'game';
|
|
@@ -33,6 +110,12 @@ export interface EditorState {
|
|
|
33
110
|
snapEnabled: boolean;
|
|
34
111
|
entityCount: number;
|
|
35
112
|
savePath: string | null;
|
|
113
|
+
/** Live editor persistence state. Wait for `saved` before an external scene-file write. */
|
|
114
|
+
saveState: 'saved' | 'unsaved' | 'failed';
|
|
115
|
+
/** Current editor camera pose when the viewport has bound a camera. */
|
|
116
|
+
camera?: EditorCameraState;
|
|
117
|
+
/** Flattened live authoring hierarchy, useful for agent entity discovery. */
|
|
118
|
+
entities?: EditorEntitySummary[];
|
|
36
119
|
/**
|
|
37
120
|
* Live count of editor browser tabs holding an SSE connection right now.
|
|
38
121
|
* The rest of this object is the last snapshot a browser POSTed and persists
|
|
@@ -43,6 +126,44 @@ export interface EditorState {
|
|
|
43
126
|
editorsConnected?: number;
|
|
44
127
|
/** Convenience: `editorsConnected > 0`. */
|
|
45
128
|
connected?: boolean;
|
|
129
|
+
/**
|
|
130
|
+
* Epoch ms when the server last received a state POST from a browser tab —
|
|
131
|
+
* i.e. the age of the cached snapshot above. Omitted if no tab has ever
|
|
132
|
+
* posted state this server lifetime (or since the last project switch,
|
|
133
|
+
* which clears the cache). Present regardless of `connected`, but only
|
|
134
|
+
* meaningful for interpreting staleness when `connected` is `false`.
|
|
135
|
+
*/
|
|
136
|
+
stateUpdatedAt?: number;
|
|
137
|
+
/**
|
|
138
|
+
* Validate-on-change (#103): per-file validation status for every
|
|
139
|
+
* `*.vscn.json`/`*.prefab.json`/`vgai.game.json` the dev server has seen
|
|
140
|
+
* change since it booted (or since the last project switch). Server-
|
|
141
|
+
* computed — unlike the rest of `EditorState`, it is NOT part of the
|
|
142
|
+
* browser-POSTed snapshot, so it is always current. A file appears here
|
|
143
|
+
* ONLY while it is currently failing; a clean write removes its entry
|
|
144
|
+
* (absence means "not known to be invalid", not "never checked"). Always
|
|
145
|
+
* present (`{}` when nothing is failing) so `vgai status` consumers can
|
|
146
|
+
* read it unconditionally.
|
|
147
|
+
*/
|
|
148
|
+
projectValidation?: Record<string, { errors: string[]; at: number }>;
|
|
149
|
+
/**
|
|
150
|
+
* #124: the absolute path of the project this editor server currently has
|
|
151
|
+
* open — server-computed (never part of the browser-POSTed snapshot,
|
|
152
|
+
* exactly like `projectValidation` above), so it is always current. Added
|
|
153
|
+
* so a watcher/relay holding only a port number (e.g. an agent that
|
|
154
|
+
* printed a `vgai edit` URL earlier and lost track of which project it
|
|
155
|
+
* belongs to) can identify which project that port serves without also
|
|
156
|
+
* reading the `~/.vgai/editor-sessions.json` registry file. `null` when no
|
|
157
|
+
* project is open (the in-repo "no project selected" default server
|
|
158
|
+
* state — mirrors `/__editor/project`'s own `{ project: null }` shape).
|
|
159
|
+
*/
|
|
160
|
+
projectRoot?: string | null;
|
|
161
|
+
/**
|
|
162
|
+
* #124: the open project's declared name, alongside `projectRoot` above
|
|
163
|
+
* (`vgai.game.json`'s `name`). `null` when no project is open, or the open
|
|
164
|
+
* project has no readable manifest name.
|
|
165
|
+
*/
|
|
166
|
+
projectName?: string | null;
|
|
46
167
|
}
|
|
47
168
|
|
|
48
169
|
export type ViewPreset = 'top' | 'front' | 'right' | 'perspective';
|
|
@@ -50,7 +171,8 @@ export type ShadingMode = 'solid' | 'wireframe' | 'unlit';
|
|
|
50
171
|
export type TransformMode = 'translate' | 'rotate' | 'scale';
|
|
51
172
|
export type TransformSpace = 'world' | 'local';
|
|
52
173
|
|
|
53
|
-
|
|
174
|
+
/** Template ids accepted by the editor's create-project endpoint. */
|
|
175
|
+
export type ProjectTemplate = 'default' | '2d' | 'react' | 'example';
|
|
54
176
|
|
|
55
177
|
export interface ProjectInfo {
|
|
56
178
|
path: string;
|
|
@@ -63,3 +185,36 @@ export interface RecentProject {
|
|
|
63
185
|
lastOpened: string;
|
|
64
186
|
thumbnail?: string;
|
|
65
187
|
}
|
|
188
|
+
|
|
189
|
+
export interface ProjectOperationCatalogEntry {
|
|
190
|
+
name: string;
|
|
191
|
+
summary: string;
|
|
192
|
+
description: string;
|
|
193
|
+
sourcePath: string;
|
|
194
|
+
inputSchema: unknown;
|
|
195
|
+
resultSchema: unknown;
|
|
196
|
+
errors: Array<{ code: string; summary: string; dataSchema?: unknown }>;
|
|
197
|
+
requires: Record<string, boolean | undefined>;
|
|
198
|
+
host: 'node' | 'editor-browser' | 'runtime-page';
|
|
199
|
+
mutates: boolean;
|
|
200
|
+
supportsDryRun: boolean;
|
|
201
|
+
longRunning: boolean;
|
|
202
|
+
permission: { risk: 'read' | 'write' | 'destructive'; summary: string };
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export interface ProjectOperationCatalog {
|
|
206
|
+
operations: ProjectOperationCatalogEntry[];
|
|
207
|
+
loadErrors: Array<{ sourcePath: string; message: string }>;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export type ProjectOperationOutcome =
|
|
211
|
+
| { ok: true; data: unknown }
|
|
212
|
+
| {
|
|
213
|
+
ok: false;
|
|
214
|
+
error: {
|
|
215
|
+
code: string;
|
|
216
|
+
message: string;
|
|
217
|
+
data?: unknown;
|
|
218
|
+
issues?: Array<{ path?: PropertyKey[]; message?: string; [key: string]: unknown }>;
|
|
219
|
+
};
|
|
220
|
+
};
|