@volter/editor-live 0.5.57

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.
@@ -0,0 +1,152 @@
1
+ /**
2
+ * `editor.document` — the session binding for the scoped editor-chrome door.
3
+ *
4
+ * WHY IT IS A SEPARATE OBJECT, and why the verbs are these, is recorded once
5
+ * in the implementation's header
6
+ * (`packages/editor/src/editor-document-probe.ts`); the short version is that
7
+ * `game.page()` is play-mode-gated and rooted at the GAME container, so an
8
+ * editor surface that is not a running game could be neither read nor driven
9
+ * through the product.
10
+ *
11
+ * WHAT IT ADDRESSES is a closed VOCABULARY of named surfaces, passed as
12
+ * `{ scope }` on every verb:
13
+ *
14
+ * `document` (default) the active centre document's whole box
15
+ * `header` / `shelf` that document's toolbar strip / tool rail
16
+ * `rail` the PROPERTIES view — its tabs, sections and fields
17
+ * `outliner` the OUTLINER view — its rows and their controls
18
+ * `content` the CONTENT view — its categories and asset rows
19
+ *
20
+ * await editor.document.query('[role=tab]', { scope: 'rail' });
21
+ * await editor.document.click('[data-testid=properties-tab-modifiers]', { scope: 'rail' });
22
+ * await editor.document.click('[data-ingest-name=Cube]', { scope: 'outliner', clicks: 2 });
23
+ * await editor.document.type('Wheel', { scope: 'outliner' });
24
+ *
25
+ * It is not page automation: a selector resolving outside the named surface is
26
+ * refused by a message naming the scope and listing the others. `rail` and
27
+ * `outliner`, and `content` are editor chrome and stay readable while the Game document is
28
+ * active; `editor.hierarchy()` / `editor.inspect()` keep answering what those
29
+ * panels RESOLVED, where this door answers what they DREW.
30
+ *
31
+ * A field on `LiveEditor` rather than methods on it, so `vgai eval --list`
32
+ * shows the verbs as one named surface — the same reason `game.input`
33
+ * and `game.events` are instance fields.
34
+ */
35
+ import type { DocumentProbeResult, DocumentProbeScope, EditorClient } from '@volter/editor-sdk';
36
+ /** Which named surface a verb runs against; `document` when omitted. */
37
+ export interface DocumentScopeOption {
38
+ scope?: DocumentProbeScope;
39
+ }
40
+ /** Modifier/targeting options shared by the gesture verbs. */
41
+ export interface DocumentGestureOptions extends DocumentScopeOption {
42
+ /** Which match to drive when the selector matches several (default 0). */
43
+ index?: number;
44
+ }
45
+ export interface DocumentKeyOptions extends DocumentGestureOptions {
46
+ /** Target one element instead of whatever inside the scope has focus. */
47
+ selector?: string;
48
+ code?: string;
49
+ ctrlKey?: boolean;
50
+ metaKey?: boolean;
51
+ shiftKey?: boolean;
52
+ altKey?: boolean;
53
+ }
54
+ export interface DocumentPasteOptions extends DocumentGestureOptions {
55
+ selector?: string;
56
+ }
57
+ export interface DocumentTypeOptions extends DocumentGestureOptions {
58
+ /** The field; omitted types into whatever inside the scope has focus. */
59
+ selector?: string;
60
+ /** Replace what the field holds first (default true). */
61
+ replace?: boolean;
62
+ /** Press Enter after the text (default true). */
63
+ enter?: boolean;
64
+ }
65
+ export declare class LiveEditorDocument {
66
+ #private;
67
+ constructor(client: EditorClient);
68
+ /**
69
+ * Read matching elements inside the active document: tag, text, attributes,
70
+ * value/checked/disabled and rect. `matched` is the total before `limit`.
71
+ *
72
+ * `styles` additionally resolves named properties per match — and resolving
73
+ * is the point, because a theme token is an expression until an element
74
+ * paints it. Ask for the standard property to learn the colour a person
75
+ * sees; ask for a `--vgai-…` custom property to learn what a rule WOULD
76
+ * paint, which is the only way to measure a `:hover` colour (`:hover` is a
77
+ * browser state no synthetic event can enter, so there is deliberately no
78
+ * hover verb on this door).
79
+ *
80
+ * await editor.document.query('.vgai-tree-row', {
81
+ * styles: ['backgroundColor', '--vgai-widget-regular-hover'],
82
+ * });
83
+ */
84
+ query(selector: string, options?: DocumentScopeOption & {
85
+ limit?: number;
86
+ styles?: readonly string[];
87
+ }): Promise<DocumentProbeResult>;
88
+ /** A REAL pointer gesture (pointerdown/mousedown/focus/pointerup/mouseup/click)
89
+ * — not `element.click()`, which a `pointerdown` listener never sees. */
90
+ click(selector: string, options?: DocumentGestureOptions & {
91
+ clicks?: number;
92
+ }): Promise<DocumentProbeResult>;
93
+ /**
94
+ * TYPE into a field and commit with Enter, the way a person does — one
95
+ * character at a time through the prototype's value setter, between real
96
+ * `keydown`/`keyup`.
97
+ *
98
+ * `paste` is not a substitute: an untrusted `ClipboardEvent` performs no
99
+ * default action, so a plain `<input>` with no paste handler keeps its old
100
+ * value. Omit `selector` to type into whatever inside the scope has focus —
101
+ * which is what a rename field is, one gesture after
102
+ * `click(row, { clicks: 2 })`.
103
+ */
104
+ type(text: string, options?: DocumentTypeOptions): Promise<DocumentProbeResult>;
105
+ /**
106
+ * A real pointer DRAG across one matched element — press at `from`, move,
107
+ * release at `to`. The gesture a direct-manipulation canvas needs; a
108
+ * zero-length drag is a click at that fraction, which `click` (always the
109
+ * center) cannot place.
110
+ *
111
+ * `from`, `to` and every point in `via` are `[x, y]` FRACTIONS OF THE
112
+ * MATCHED ELEMENT'S BOX, 0..1 from its top-left — NEVER pixels and never
113
+ * page coordinates. `[0.5, 0.5]` is its center, `[1, 0]` its top-right.
114
+ * Compute a pixel target by measuring the element first: `query` answers
115
+ * its `rect`, and `(px - rect.x) / rect.width` is the fraction to pass.
116
+ */
117
+ drag(selector: string, options: DocumentGestureOptions & {
118
+ from: [number, number];
119
+ to: [number, number];
120
+ via?: [number, number][];
121
+ steps?: number;
122
+ altKey?: boolean;
123
+ ctrlKey?: boolean;
124
+ metaKey?: boolean;
125
+ shiftKey?: boolean;
126
+ }): Promise<DocumentProbeResult>;
127
+ /** A real keydown/keyup on the target, or on whatever inside the document has focus. */
128
+ key(key: string, options?: DocumentKeyOptions): Promise<DocumentProbeResult>;
129
+ /** A real `ClipboardEvent` carrying `text/plain` — the gesture nothing else
130
+ * in the product can produce. */
131
+ paste(text: string, options?: DocumentPasteOptions): Promise<DocumentProbeResult>;
132
+ /**
133
+ * Choose `value` on a `<select>` — a native dropdown's options are drawn by
134
+ * the OS, so `click` has nothing in the document to resolve, and a plain
135
+ * `element.value =` is invisible to React. Set through the prototype's own
136
+ * value setter plus `input`/`change`; `value` is the option's `value`, not
137
+ * its label. An unknown value is refused with the options it does offer.
138
+ */
139
+ select(selector: string, value: string, options?: DocumentGestureOptions): Promise<DocumentProbeResult>;
140
+ /**
141
+ * THE REPL over the open document: run `step` in the editor page against the
142
+ * object the ACTIVE document published as its context (the mesh document
143
+ * publishes its `MeshEditSession`, whose `ctx` is the bpy-shaped edit
144
+ * context — `ctx.ops.mesh.bevel({ offset: 0.1 })`, `ctx.selection`,
145
+ * `ctx.history`, `session.commit()`). Edit mode, no play. Serialized like
146
+ * `game.page`: the step's own source travels, so inline every value it
147
+ * needs and return plain data.
148
+ */
149
+ run<T = unknown>(step: (ctx: unknown, info: {
150
+ documentId: string;
151
+ }) => T | Promise<T>): Promise<T>;
152
+ }
@@ -0,0 +1,399 @@
1
+ /** Editor and document automation over the shared session command wire.
2
+ * Modeling executes in the editor tab; this client owns no model state. */
3
+ import type { ActiveDocumentCapture, AssetKind, AssetPreviewCapture, AssetPreviewOptions, AssetPreviewShotSetDefinition, AssetPreviewSource, CaptureDimensions, DocumentLookOutcome, EditorChromeCapture, EditorChromeCaptureOptions, EditorClient, EditorState, EditorView, EditorWorkspaceName, HistoryStep, InspectedFieldWrite, InspectedHierarchy, InspectedInspection, LabeledShotSetCapture, OpenedDocument, PresentedEditorView, ShadingMode, StructureOp, StructureOpOptions, StructureOpResult, ViewPreset, ViewportCapture } from '@volter/editor-sdk';
4
+ import { LiveEditorDocument } from './editor-document.js';
5
+ /** A panel `showPanel` can focus: the viewport tabs, the console, the build
6
+ * surface, or any key the editor's own static-panel registry holds
7
+ * (`hierarchy`, `assets`, `asset-library`, `inspector`, `history`, …) — which
8
+ * is why this is open: the registry, not this union, is the vocabulary. */
9
+ export type PanelName = 'viewport-edit' | 'console' | (string & {});
10
+ /**
11
+ * Lightweight extension-based `AssetKind` guess for `openAsset`'s optional
12
+ * `kind` argument. Deliberately independent of (not shared with) the
13
+ * editor's own `AssetBrowser.tsx#getAssetKind` — that function is a private,
14
+ * React-component-local helper of a package `@volter/editor-live` has no dependency
15
+ * on. Callers with an unusual extension can always pass `kind` explicitly.
16
+ */
17
+ export declare function inferAssetKind(path: string): AssetKind;
18
+ export declare class LiveEditor {
19
+ #private;
20
+ /**
21
+ * The ACTIVE center document's own DOM: read it, click it, key it, paste
22
+ * into it. The one door onto editor chrome that is not play-mode gated, and
23
+ * deliberately scoped to that document alone —
24
+ * `packages/editor/src/editor-document-probe.ts` carries the design and the
25
+ * refusal contract. Screenshotting the same subject is
26
+ * {@link LiveEditor.captureActiveDocument}, not a fifth verb here.
27
+ */
28
+ readonly document: LiveEditorDocument;
29
+ constructor(client: EditorClient);
30
+ /**
31
+ * THE BLENDER LANE'S VERBS, from `volter-editor eval`.
32
+ *
33
+ * Blender runs headless in the editor tab's worker (ARCHITECTURE-CORE, "THE
34
+ * BLENDER IN THE TAB IS BLENDER") and answers `blender-start`,
35
+ * `blender-execute`, `blender-scene-info`, `blender-object-info`,
36
+ * `blender-screenshot-view`, `blender-read-file`, `blender-write-file`,
37
+ * `blender-list-files`, `blender-stop` and `blender-status`. They were
38
+ * reachable from `@volter/editor-sdk` and through `vgai blender-mcp` but from
39
+ * no GENERAL door, so driving a session meant writing an MCP client script
40
+ * per question — the same discovery failure `eval-surface.ts`'s header
41
+ * records, in a lane that had not noticed it yet.
42
+ *
43
+ * volter-editor eval "await editor.blender('blender-execute', { code: 'import bpy; print(len(bpy.data.objects))' })"
44
+ *
45
+ * `blender-status` is the only verb that creates nothing: it answers whether
46
+ * this tab already has a session without starting one.
47
+ */
48
+ blender<T extends object = Record<string, unknown>>(type: `blender-${string}`, fields?: Record<string, unknown>): Promise<T>;
49
+ /**
50
+ * The active authoring adapter's persistence destination — where a save would
51
+ * land (`status().savePath`). A read only: a three root has no scene document
52
+ * to open, and its root is activated instead.
53
+ */
54
+ scene(): Promise<string | null>;
55
+ /**
56
+ * Make the connected human editor show the same subject/view as the agent.
57
+ * The returned URL is a compact, shareable projection — not a serialized
58
+ * workspace or document payload.
59
+ */
60
+ present(view: EditorView): Promise<PresentedEditorView>;
61
+ /** The human editor's actual active document, selection, camera and utility. */
62
+ currentView(): Promise<EditorView>;
63
+ /**
64
+ * Capture the same center document the human is currently looking at.
65
+ *
66
+ * A number is a SQUARE of that size — the default, and the right shape for
67
+ * an unstaged look at a model. `{width, height}` asks for a shaped frame, so
68
+ * a video-aspect look needs no crop afterwards. Both are bounded by the
69
+ * relay budget (64-1024 per side, total no larger than a 1024 square); see
70
+ * `@volter/editor-sdk`'s `CaptureDimensions`.
71
+ * Supply a view to present and photograph it in one editor request.
72
+ */
73
+ captureActiveDocument(size?: CaptureDimensions, view?: EditorView): Promise<ActiveDocumentCapture>;
74
+ /**
75
+ * Photograph the editor PAGE — every panel, tab strip and viewport as the
76
+ * person sees it. `vgai screenshot editor` is this verb from the shell. The
77
+ * one door for judging chrome sighted: a skin, a workspace arrangement or a
78
+ * contributed panel is looked at through this, never guessed at from DOM
79
+ * probes. The page at its own layout, `scale` output pixels per CSS pixel
80
+ * (default `devicePixelRatio`) — a 1 px border or a glyph stroke is only
81
+ * judgeable at the scale the reference it is compared against was captured
82
+ * at, and the result reports its own `size` and `scale`.
83
+ */
84
+ captureEditorChrome(options?: EditorChromeCaptureOptions): Promise<EditorChromeCapture>;
85
+ /** `'all'` -> `EditorClient.selectAll()` (mirrors `vgai select --all`); otherwise `EditorClient.select(id)` (mirrors `vgai select <entityId>`). */
86
+ select(id: string | 'all'): Promise<void>;
87
+ /** Mirrors `vgai deselect`. */
88
+ deselect(): Promise<void>;
89
+ /** No `id` -> focus the current selection (mirrors bare `vgai focus`); `id` given -> focus that entity. */
90
+ focus(id?: string): Promise<void>;
91
+ /**
92
+ * Frame the edit viewport camera on one entity. Same framing as
93
+ * `focus(id)`, but an entity id the scene does not know THROWS, naming the
94
+ * id — where `focus` quietly does nothing. Reach for this whenever the next
95
+ * step reads the viewport (`screenshot()`, an
96
+ * `assetPreview(..., { stage: 'scene' })`): a framing that silently missed
97
+ * would otherwise be indistinguishable from one that worked.
98
+ */
99
+ frame(entityId: string): Promise<void>;
100
+ /**
101
+ * Bare `frame()` frames the OPEN Object3D document's subject instead — its
102
+ * selection if it has one, else the whole model: the toolbar's own Frame
103
+ * button, reachable from a script. `fit` scales the fitted distance (1 is
104
+ * that button's tight fit, 1.5 stands back a little for a shot).
105
+ */
106
+ frame(options?: {
107
+ readonly fit?: number;
108
+ }): Promise<void>;
109
+ /**
110
+ * WATCH THE AGENT LOOK AROUND THE MODEL.
111
+ *
112
+ * Swings the open Object3D document's camera — the camera the human's tab is
113
+ * showing — around the framed subject by `azimuth`/`elevation` RADIANS,
114
+ * animated over `duration` seconds (default 0.6), and resolves when the move
115
+ * ends. This is deliberately not a jump cut: the point of the verb is that a
116
+ * person watching sees the agent walk around the thing it is working on.
117
+ *
118
+ * `await editor.orbit({ azimuth: Math.PI / 2 })` — a quarter turn to the right.
119
+ *
120
+ * There is ONE camera, and the human owns it: a drag during the move cancels
121
+ * it exactly where it is, and the resolved outcome says `cancelledBy:
122
+ * 'human'` rather than throwing. A second look verb supersedes the first.
123
+ * The move is drawn by the document's own frame loop, so a document that
124
+ * isn't being drawn (background tab, inactive panel) doesn't orbit.
125
+ */
126
+ orbit(options: {
127
+ /** RADIANS, relative to where the camera is now. `Math.PI / 2` is a
128
+ * quarter turn; degrees are not accepted and `90` is fourteen turns. */
129
+ readonly azimuth?: number;
130
+ /** RADIANS, relative to where the camera is now. */
131
+ readonly elevation?: number;
132
+ /** SECONDS the move takes (default 0.6). */
133
+ readonly duration?: number;
134
+ }): Promise<DocumentLookOutcome>;
135
+ /**
136
+ * A slow full revolution of the open document's subject — {@link orbit} with
137
+ * the turns spelled out and a constant angular rate. Resolves at the end of
138
+ * the last revolution.
139
+ */
140
+ turntable(options?: {
141
+ readonly seconds?: number;
142
+ readonly revolutions?: number;
143
+ }): Promise<DocumentLookOutcome>;
144
+ view(preset: ViewPreset): Promise<void>;
145
+ /**
146
+ * Switch the editor's NAMED WORKSPACE — `await editor.workspace('model')`.
147
+ *
148
+ * A workspace is a task-named LAYOUT MEMORY over the one dock
149
+ * (ARCHITECTURE-CORE §Editor chrome): `game` (the default, the editor's
150
+ * standing arrangement), `model`, `sculpt`, `texture`, `animate`, `look`.
151
+ * Switching is an EXPLICIT act — nothing in the editor moves chrome on its
152
+ * own, opening a document included — and this is the session door to it,
153
+ * beside `Window → Workspace` and the registered actions.
154
+ *
155
+ * Resolves once the dock has finished rebuilding, so a capture taken
156
+ * immediately after photographs the arrangement that was asked for. Each
157
+ * workspace remembers the user's own hand-tuning per project, so switching
158
+ * away and back is lossless.
159
+ */
160
+ workspace(id: EditorWorkspaceName): Promise<void>;
161
+ /**
162
+ * Apply a STYLE BUNDLE by id — the chrome's palette, material, icon set and
163
+ * region defaults in one gesture, the session door beside
164
+ * `View → <Style> Style`. A bundle the open project does not offer refuses
165
+ * and names the vocabulary; `currentView().style` reports the one worn.
166
+ */
167
+ style(id: string): Promise<void>;
168
+ /**
169
+ * Set the MATERIAL apart from the bundle that usually carries it.
170
+ * Appearance is palette × material, independent axes by ruling, so
171
+ * `style()` alone can never say whether a cost belongs to the blur or to
172
+ * the palette. This is the door that measures them apart; it answers with
173
+ * what the chrome wears afterwards (`style` is `null` when the mix matches
174
+ * no registered bundle).
175
+ */
176
+ appearance(appearance: {
177
+ readonly material?: string;
178
+ }): Promise<{
179
+ material: string;
180
+ style: string | null;
181
+ }>;
182
+ /** Focus an editor panel: a viewport tab, the console, the build surface, or
183
+ * any key the editor's static-panel registry holds — an unknown key refuses
184
+ * naming the ones it does. */
185
+ showPanel(name: PanelName): Promise<void>;
186
+ /** `kind` inferred from `path`'s extension when omitted (`inferAssetKind`) — pass it explicitly to override. */
187
+ openAsset(path: string, kind?: AssetKind): Promise<void>;
188
+ /**
189
+ * SELECT a project asset — the browser's single click, which fills the
190
+ * Inspector without opening a document. `openAsset` is the double click.
191
+ *
192
+ * This is how a project's own `asset.inspector` section is reached: select
193
+ * the file it matches, then `inspect()` lists the verbs that section
194
+ * declares and `runAction(id)` runs one. Selecting a path nothing matches
195
+ * is not an error — the Inspector shows what it has, exactly as it does
196
+ * for a human.
197
+ */
198
+ selectAsset(path: string): Promise<void>;
199
+ /**
200
+ * Captures the editor's native four-view preview. A bare string is a
201
+ * project-relative asset path (the common case); an explicit source object
202
+ * targets a path, a LIVE SCENE ENTITY (`assetPreview({ entityId }, …)`) —
203
+ * which is what makes `options.stage: 'scene'`, the entity photographed
204
+ * where it stands under the scene's own lighting, reachable from here — or
205
+ * RAW GLB BYTES (`assetPreview({ glbBase64 }, …)`), for a model that exists
206
+ * only in the calling Node process's memory and has never been written to
207
+ * disk. `stage` defaults to `'lab'`, the neutral Asset Lab staging this has
208
+ * always produced, and the bytes form is lab-only.
209
+ */
210
+ assetPreview(source: string | AssetPreviewSource, options?: AssetPreviewOptions): Promise<AssetPreviewCapture>;
211
+ /**
212
+ * The same subject photographed as a LABELED SHOT SET instead of the four
213
+ * views — a caller-supplied definition of turntable yaws and bone-anchored
214
+ * crops, rendered against the asset's own skeleton, with a contact sheet.
215
+ * Every source {@link assetPreview} takes works here, GLB bytes included:
216
+ * a shot set stages its own subject, so it needs no place to stand.
217
+ *
218
+ * Sole in-repo caller today: `project.bake.preview`'s `--orbit` lane.
219
+ */
220
+ assetPreviewShots(source: string | AssetPreviewSource, definition: AssetPreviewShotSetDefinition, options?: AssetPreviewOptions): Promise<LabeledShotSetCapture>;
221
+ grid(on: boolean): Promise<void>;
222
+ helpers(on: boolean): Promise<void>;
223
+ stats(on: boolean): Promise<void>;
224
+ shading(mode: ShadingMode): Promise<void>;
225
+ /**
226
+ * READ the inspector, as data — the serialized inspection subject
227
+ * (`editor.inspect()`; design: `docs/ARCHITECTURE-CORE.md` §Editor chrome,
228
+ * "The Inspection Model"). This is the Figma-Inspect analog: whatever a
229
+ * human would see in the inspector right now — the subject's identity, its
230
+ * verbs, and every identified section in display order, with a `fields`
231
+ * section's CURRENT VALUES at their scriptable `path`s.
232
+ *
233
+ * Reach for it whenever the next step depends on what an object actually
234
+ * IS: `await editor.select(id)` then `await editor.inspect()` answers "what
235
+ * properties does this thing have, and what are they set to" in one call,
236
+ * against the same model the panel renders — no scene-graph reads, no
237
+ * guessing at property names.
238
+ *
239
+ * When the inspector is showing NOTHING — nothing selected on a surface
240
+ * with no empty-state subject of its own, which is most of them — the answer
241
+ * is `{none: true}`, so "the human sees no inspector" and "the read failed"
242
+ * are never the same value. A surface whose empty space IS a real thing (an
243
+ * open Asset Lab document) still answers with that subject, and never with
244
+ * another surface's.
245
+ *
246
+ * A `custom` section body is a named opaque: the editor renders it with
247
+ * React, so the wire reports its identity rather than pretending to describe
248
+ * its rendering — plus, when the section can say what it DISPLAYS, a `data`
249
+ * payload in its own vocabulary (`transform` carries
250
+ * `{position, rotation, scale}`, rotation in Euler XYZ degrees).
251
+ */
252
+ inspect(): Promise<InspectedInspection>;
253
+ /** Run one verb listed by `inspect().quickActions`, through the same action
254
+ * the human Inspector button invokes. */
255
+ runAction(actionId: string): Promise<InspectedInspection>;
256
+ /**
257
+ * Run ONE command by id — the door to everything the command palette lists.
258
+ *
259
+ * ONE NAME (orchestrator ruling 2026-09-19). There were briefly TWO doors
260
+ * onto the one view-verb table — this one and `editor.viewVerb(view, verb)`,
261
+ * which addressed the same registry by its two halves. A second addressing
262
+ * of one table is a second name for one thing, and an agent reading
263
+ * `--list` had to choose between them with nothing to choose on. This door
264
+ * stays because it is strictly wider: it addresses a COMMAND ID, so under
265
+ * the frame it reaches everything the workbench knows — a `vgai.action.<id>`
266
+ * editor action, one of VS Code's own — and not only a view. A VIEW is
267
+ * reached by spelling its verb's command id:
268
+ *
269
+ * await editor.command('vgai.blender-uv-view.state')
270
+ * await editor.command('vgai.blender-uv-view.zoom', { to: 600 })
271
+ *
272
+ * await editor.command('vgai.blender-node-view.view-all')
273
+ * await editor.command('vgai.blender-node-view.look', { node: 'Principled BSDF' })
274
+ *
275
+ * Under the Code-OSS frame this is the workbench's own command service, so
276
+ * any command id works — ours and VS Code's alike. Standalone `vgai edit`
277
+ * has no command service and answers the `vgai.<view>.<verb>` shape off the
278
+ * SAME verb table the frame's commands call, refusing any other id by name.
279
+ * One table, two doors, exactly like the keymap's.
280
+ *
281
+ * Answers with whatever the command returned — a view verb's own state, or
282
+ * `null` for a command that returns nothing.
283
+ */
284
+ command(commandId: string, args?: unknown): Promise<unknown>;
285
+ /**
286
+ * RESTRUCTURE the authored tree — the hierarchy context menu's own verbs.
287
+ *
288
+ * `create`, `delete`, `duplicate`, `reparent`, `reorder`, `wrap`, `unwrap`,
289
+ * `group`, `ungroup`, `copy`, `cut`, `paste`; `extractComponent` and
290
+ * `forkComponent` are the two that write whole new files and have their own
291
+ * doors below. All of them run the SAME `authoring/consumer-actions.ts`
292
+ * helpers the menu items call, so there is one implementation of each op and
293
+ * not a second that can disagree with what a human gets.
294
+ *
295
+ * It exists because the menu is a POINTER surface: every one of these ops was
296
+ * reachable only by right-clicking a hierarchy row, which is nothing an agent
297
+ * can do — so for an ingest root, whose only authoring surface IS the editor,
298
+ * structure was closed entirely.
299
+ *
300
+ * `id`/`ids` default to the current selection. The answer carries the same
301
+ * per-edit `write` ack `setField` does, so `write.persisted` tells a saved
302
+ * restructure from a live-only one. An op the active adapter does not provide
303
+ * REJECTS by name — never a silent no-op.
304
+ */
305
+ structure(op: StructureOp, options?: StructureOpOptions): Promise<StructureOpResult>;
306
+ /**
307
+ * "Extract Component…" — lift the selected native subtree into its own
308
+ * component file (plus a story) and replace the callsite with it.
309
+ *
310
+ * Answers the action's own sentence, which NAMES both new files, because
311
+ * undo owns the callsite edit and will not remove them.
312
+ */
313
+ extractComponent(options?: {
314
+ id?: string;
315
+ name?: string;
316
+ }): Promise<string>;
317
+ /**
318
+ * "Fork Component…" — copy the selected instance's component definition to a
319
+ * new file and retarget THIS CALLSITE at it.
320
+ *
321
+ * One callsite is the unit of the edit; when that callsite sits inside a
322
+ * component rendered many times, every one of those renders now renders the
323
+ * fork.
324
+ */
325
+ forkComponent(options?: {
326
+ id?: string;
327
+ }): Promise<string>;
328
+ /**
329
+ * READ the hierarchy panel, as data — the rows a human is looking at right
330
+ * now, nested exactly as the panel nests them.
331
+ *
332
+ * The companion to {@link inspect}: that one answers "what IS the selected
333
+ * thing", this one answers "what does the tree LOOK LIKE". It is the panel's
334
+ * own output, not a fresh walk of the scene — the adapter's tree after the
335
+ * component marks fold implementation subtrees (bones, particle renderers,
336
+ * instanced pools), after the internals reveal, the document promotion, the
337
+ * child cap, the collapse state, the search filter and the selection scope.
338
+ *
339
+ * Works in play mode and edit mode; the answer says which (`playState`,
340
+ * `activeViewportTab`), because the two are different adapters and a tree
341
+ * that looks wrong is very often the wrong adapter's tree.
342
+ *
343
+ * Prefer this over `status().entities`, which is deliberately a different
344
+ * question — the RAW adapter tree, unprojected. A panel that renders the
345
+ * wrong rows looks perfectly healthy in that facet.
346
+ *
347
+ * Each row carries `childCount` (what its caret opens), `internalChildCount`
348
+ * (what is folded behind "Reveal Internals") and `expandable` (whether the
349
+ * panel draws a caret at all), so "this subtree exists but nothing in the UI
350
+ * opens it" is a fact you can read rather than one you have to notice.
351
+ *
352
+ * Rejects, naming the panel, when no hierarchy panel is mounted — an empty
353
+ * tree would be a fabricated answer about a surface nobody is being shown.
354
+ */
355
+ hierarchy(): Promise<InspectedHierarchy>;
356
+ /** Expand every branch through the Hierarchy panel's own action. */
357
+ expandHierarchyAll(): Promise<void>;
358
+ /** Collapse every branch through the same panel action. Expanding is
359
+ * persisted per project, so without this the tree's REST STATE — what a
360
+ * person sees on opening the project — is unreachable once any reader has
361
+ * expanded it. */
362
+ collapseHierarchyAll(): Promise<void>;
363
+ /**
364
+ * Write one editable field from `inspect()` by its stable path, through the
365
+ * same Inspector IO and persistence boundary the human control uses.
366
+ *
367
+ * The answer is `{ subject, write }`, and `write` is the half worth reading
368
+ * first: a write with no persistence route open still succeeds — it lands on
369
+ * the live object and journals live-only — so `write.persisted` is how you
370
+ * tell a saved edit from one that will not survive the session, without
371
+ * diffing the tree. `write.destination` is the adapter's own words for where
372
+ * it went ("live-only (not saved)" is a destination, never silence).
373
+ */
374
+ setField(path: string, value: unknown): Promise<InspectedFieldWrite>;
375
+ /**
376
+ * REMOVE one field's authored override — the revert arrow, as a command.
377
+ *
378
+ * Reach for this instead of `setField` whenever you are UNDOING an edit that
379
+ * added a property the source did not carry: `setField` can only write a
380
+ * value, so setting the default back leaves `position={[0, 0, 0]}` in the
381
+ * file where there was nothing before. Only this door restores the bytes.
382
+ *
383
+ * The answer is the same `{ subject, write }` shape, awaited past the bytes.
384
+ * It rejects with `code: 'REMOVAL_UNAVAILABLE'` when the field is not
385
+ * declared removable or the lane has no removal door — which is a missing
386
+ * seam to report, not a removal that failed.
387
+ */
388
+ removeField(path: string): Promise<InspectedFieldWrite>;
389
+ /** Open a document by its adapter-declared id, through its registered owner. */
390
+ open(id: string): Promise<OpenedDocument>;
391
+ /** Undo / redo one project transaction, through the session's own history
392
+ * queue — the same one the keyboard shortcut drives. */
393
+ undo(): Promise<HistoryStep>;
394
+ redo(): Promise<HistoryStep>;
395
+ /** Mirrors `vgai status` — the full live editor state as JSON. */
396
+ status(): Promise<EditorState>;
397
+ /** A live viewport PNG (`EditorClient.captureViewport`) — no direct CLI verb exists; this is the closest wire read. */
398
+ screenshot(size?: number): Promise<ViewportCapture>;
399
+ }
@@ -0,0 +1,23 @@
1
+ import { LiveEditor } from './editor.js';
2
+ import { LiveTools } from './tools.js';
3
+ import { type ResolvedSession, type SessionResolutionDeps } from './session.js';
4
+ export { LiveEditor, inferAssetKind, type PanelName } from './editor.js';
5
+ export { LiveEditorDocument } from './editor-document.js';
6
+ export type { DocumentGestureOptions, DocumentKeyOptions, DocumentPasteOptions } from './editor-document.js';
7
+ export { LiveTools } from './tools.js';
8
+ export { resolveSession, findProjectRootFrom } from './session.js';
9
+ export type { ResolvedSession, SessionResolutionDeps, SessionListingTransport, ProjectSessionHint } from './session.js';
10
+ export type { ActiveDocumentCapture, EditorView, PresentedEditorView } from '@volter/editor-sdk';
11
+ export interface LiveBindings {
12
+ editor: LiveEditor;
13
+ tools: LiveTools;
14
+ }
15
+ export interface LiveSession extends LiveBindings {
16
+ session: ResolvedSession;
17
+ }
18
+ /** Inspect the callable surface without connecting; calls fail on port zero. */
19
+ export declare function unconnectedBindings(): LiveBindings;
20
+ /** Attach only to the session serving the requested project's canonical path. */
21
+ export declare function connect(projectDir?: string, deps?: SessionResolutionDeps): Promise<LiveSession>;
22
+ export declare const editor: LiveEditor;
23
+ export declare const tools: LiveTools;