@volter/live 0.0.0-stage → 0.5.204

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