@vgai/sdk 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.
Files changed (51) hide show
  1. package/package.json +27 -0
  2. package/src/cinematic/capabilities-operations.ts +128 -0
  3. package/src/cinematic/cue-operations.ts +198 -0
  4. package/src/cinematic/gsap-operations.ts +126 -0
  5. package/src/cinematic/index.ts +59 -0
  6. package/src/cinematic/preview-operations.ts +279 -0
  7. package/src/cinematic/preview-transport.ts +244 -0
  8. package/src/cinematic/render-operations.ts +409 -0
  9. package/src/cinematic/render-transport.ts +238 -0
  10. package/src/cinematic/theatre-operations.ts +306 -0
  11. package/src/editor/camera-operations.ts +169 -0
  12. package/src/editor/console-operations.ts +87 -0
  13. package/src/editor/hierarchy-operations.ts +95 -0
  14. package/src/editor/index.ts +62 -0
  15. package/src/editor/open-operations.ts +209 -0
  16. package/src/editor/screenshot-operations.ts +99 -0
  17. package/src/editor/selection-operations.ts +144 -0
  18. package/src/editor/session-operations.ts +73 -0
  19. package/src/editor/source-location-operations.ts +106 -0
  20. package/src/editor/transport.ts +647 -0
  21. package/src/errors.ts +72 -0
  22. package/src/http/http-projection.ts +349 -0
  23. package/src/http/index.ts +11 -0
  24. package/src/index.ts +67 -0
  25. package/src/mcp/index.ts +16 -0
  26. package/src/mcp/mcp-projection.ts +288 -0
  27. package/src/operations.ts +83 -0
  28. package/src/play/control-operations.ts +205 -0
  29. package/src/play/debug-command-operations.ts +245 -0
  30. package/src/play/index.ts +66 -0
  31. package/src/play/input-operations.ts +316 -0
  32. package/src/play/lifecycle-operations.ts +271 -0
  33. package/src/play/log-operations.ts +279 -0
  34. package/src/play/run-ticks-operations.ts +141 -0
  35. package/src/play/state-operations.ts +210 -0
  36. package/src/play/status-operations.ts +160 -0
  37. package/src/play/transport.ts +728 -0
  38. package/src/project/asset-operations.ts +243 -0
  39. package/src/project/component-operations.ts +337 -0
  40. package/src/project/discovery-operations.ts +269 -0
  41. package/src/project/entity-operations.ts +366 -0
  42. package/src/project/index.ts +55 -0
  43. package/src/project/input-map-operations.ts +233 -0
  44. package/src/project/manifest-operations.ts +355 -0
  45. package/src/project/scene-operations.ts +426 -0
  46. package/src/project/shared.ts +299 -0
  47. package/src/registry.ts +285 -0
  48. package/src/render/capabilities/ffmpeg.ts +141 -0
  49. package/src/render/index.ts +15 -0
  50. package/src/render/render-cinematic.ts +1847 -0
  51. package/src/types.ts +101 -0
package/src/errors.ts ADDED
@@ -0,0 +1,72 @@
1
+ import type { z } from 'zod';
2
+
3
+ /**
4
+ * Core, registry-level error codes — always available regardless of which
5
+ * operation was dispatched. Operation-specific codes are declared per
6
+ * operation on `OperationDefinition.errors` (see `registry.ts`).
7
+ */
8
+ export const CORE_ERROR_CODES = {
9
+ /** `dispatch()` was called with a name no operation is registered under. */
10
+ OPERATION_NOT_FOUND: 'OPERATION_NOT_FOUND',
11
+ /** Input failed the operation's Zod input schema. */
12
+ INVALID_INPUT: 'INVALID_INPUT',
13
+ /** The impl's return value failed the operation's Zod result schema, or a
14
+ * thrown OperationError's `data` failed its declared error-code schema —
15
+ * either way, an implementation bug, not a caller bug. */
16
+ INVALID_OUTPUT: 'INVALID_OUTPUT',
17
+ /** The impl threw something that isn't a declared, structured error. */
18
+ INTERNAL_ERROR: 'INTERNAL_ERROR',
19
+ } as const;
20
+
21
+ export type CoreErrorCode = (typeof CORE_ERROR_CODES)[keyof typeof CORE_ERROR_CODES];
22
+
23
+ /** A single Zod validation failure, reduced to a machine-readable shape. */
24
+ export interface StructuredIssue {
25
+ path: (string | number | symbol)[];
26
+ message: string;
27
+ code: string;
28
+ }
29
+
30
+ /**
31
+ * The one error shape every `dispatch()` failure normalizes into. `code` is
32
+ * always machine-readable and always present — nothing in this codebase may
33
+ * identify an operation failure by parsing `message` prose (§8 B1 AC).
34
+ */
35
+ export interface StructuredOperationError {
36
+ code: string;
37
+ message: string;
38
+ /** Present for INVALID_INPUT / INVALID_OUTPUT — the exact failing path(s). */
39
+ issues?: StructuredIssue[];
40
+ /** Present when the failing code declares a data schema (or the impl attached data). */
41
+ data?: unknown;
42
+ }
43
+
44
+ /**
45
+ * The only way an operation `impl` should signal an EXPECTED, contractual
46
+ * failure (as opposed to an unexpected bug/exception). `code` must be one
47
+ * the operation's `OperationDefinition.errors` declares — `dispatch()`
48
+ * cross-checks it and validates `data` against that code's schema, so an
49
+ * undeclared code or mismatched data is itself normalized into a structured
50
+ * error rather than silently forwarded (see `registry.ts`'s
51
+ * `normalizeThrown`).
52
+ */
53
+ export class OperationError extends Error {
54
+ readonly code: string;
55
+ readonly data: unknown;
56
+
57
+ constructor(code: string, message: string, data?: unknown) {
58
+ super(message);
59
+ this.name = 'OperationError';
60
+ this.code = code;
61
+ this.data = data;
62
+ }
63
+ }
64
+
65
+ /** Reduce a `ZodError`'s issues to the structured, serializable shape above. */
66
+ export function toStructuredIssues(issues: readonly z.ZodIssue[]): StructuredIssue[] {
67
+ return issues.map((issue) => ({
68
+ path: [...issue.path],
69
+ message: issue.message,
70
+ code: issue.code,
71
+ }));
72
+ }
@@ -0,0 +1,349 @@
1
+ /**
2
+ * HTTP projection of the operation registry (B8, §8 B8).
3
+ *
4
+ * The SAME `OperationRegistry` that backs the SDK (`dispatch`) and the CLI
5
+ * (`packages/vgai-cli/src/oclif/*`, B6) is projected here as a small HTTP
6
+ * server. Routes are GENERATED from `registry.listOperations()` — one route
7
+ * per operation — never hand-authored per op: adding an operation to the SDK
8
+ * gives it an HTTP route for free, exactly as it gives it a CLI subcommand
9
+ * for free (B6).
10
+ *
11
+ * ROUTE SHAPE — I project each operation as its own route, dotted-name under a
12
+ * base path (so `project.scene.read` is `POST /op/project.scene.read`):
13
+ * - `POST <basePath>/<name>` — every operation. The JSON request BODY is the
14
+ * operation input; it is validated by the op's Zod input schema INSIDE
15
+ * `dispatch` (this module never re-implements validation — it hands the
16
+ * body straight to the registry), so a bad body comes back as the SAME
17
+ * `INVALID_INPUT` structured error the SDK and CLI produce.
18
+ * - `GET <basePath>/<name>` — read-only operations only (`mutates: false`).
19
+ * Input is taken from the `?input=<url-encoded-json>` query param (or `{}`
20
+ * when absent). A GET against a mutating op is refused with a structured
21
+ * `METHOD_NOT_ALLOWED` (a projection-level error — see `dispatchRoute`),
22
+ * never silently executed.
23
+ * - `GET <basePath>` — a machine-readable MANIFEST of every route: name,
24
+ * allowed methods, summary/description, the Zod-derived JSON Schema for the
25
+ * input, and the declared error codes. This is the HTTP analogue of MCP's
26
+ * `tools/list` and is built from the very same `listOperations()` summaries.
27
+ *
28
+ * OUTCOME SHAPE: every response body is the registry's own
29
+ * `OperationOutcome` — `{ ok: true, data }` or `{ ok: false, error }` with a
30
+ * machine-readable `error.code` — byte-for-byte the shape the SDK returns and
31
+ * the CLI prints under `--json`. The HTTP STATUS is a secondary, best-effort
32
+ * signal derived from that code (`httpStatusForOutcome`); the authoritative,
33
+ * cross-projection-identical fact is always `error.code` in the body (§8 B8
34
+ * AC: "HTTP and MCP return the same structured error codes as SDK and CLI").
35
+ *
36
+ * CONTEXT: an operation's `OperationContext` (projectRoot, editorUrl, an
37
+ * injected editor transport for tests, ...) is produced per request by the
38
+ * caller-supplied `createContext(req)` (or the static `context`), so the same
39
+ * server can serve one project or resolve the project per request — the SDK
40
+ * package itself stays free of any cwd/env policy (that lives in the CLI).
41
+ */
42
+
43
+ import type { IncomingMessage, Server, ServerResponse } from 'node:http';
44
+ import { createServer } from 'node:http';
45
+ import { z } from 'zod';
46
+ import { CORE_ERROR_CODES } from '../errors.js';
47
+ import { operations as defaultRegistry } from '../index.js';
48
+ import type { OperationOutcome, OperationRegistry, OperationSummary } from '../registry.js';
49
+ import type { OperationContext } from '../types.js';
50
+
51
+ /** Options for building the HTTP projection handler/server. */
52
+ export interface HttpProjectionOptions {
53
+ /** Registry to project. Defaults to the shared `operations` registry (same one the CLI uses). */
54
+ registry?: OperationRegistry;
55
+ /**
56
+ * URL prefix every operation route lives under. Default `/op`, so
57
+ * `project.scene.read` is `POST /op/project.scene.read`. No trailing slash.
58
+ */
59
+ basePath?: string;
60
+ /** Static context handed to every `dispatch`. Ignored when `createContext` is given. */
61
+ context?: OperationContext;
62
+ /** Per-request context factory (wins over `context`) — e.g. resolve `projectRoot` from a header. */
63
+ createContext?: (req: IncomingMessage) => OperationContext | Promise<OperationContext>;
64
+ }
65
+
66
+ /** One entry of the `GET <basePath>` manifest — the HTTP analogue of an MCP tool descriptor. */
67
+ export interface HttpRouteDescriptor {
68
+ name: string;
69
+ methods: string[];
70
+ path: string;
71
+ summary: string;
72
+ description: string;
73
+ mutates: boolean;
74
+ supportsDryRun: boolean;
75
+ host: string;
76
+ requires: OperationSummary['requires'];
77
+ permission: OperationSummary['permission'];
78
+ /** Zod-derived JSON Schema for the request body / `?input=` — same conversion MCP tool schemas use. */
79
+ inputSchema: unknown;
80
+ errorCodes: string[];
81
+ }
82
+
83
+ /** Convert an operation's Zod input schema to JSON Schema — the repo's established `z.toJSONSchema` call (see `scripts/generate-schema.ts`). */
84
+ export function inputJsonSchema(summary: OperationSummary): unknown {
85
+ return z.toJSONSchema(summary.input, { unrepresentable: 'any' });
86
+ }
87
+
88
+ /** Build the `GET <basePath>` manifest from `listOperations()` — routes are derived, never hand-written. */
89
+ export function buildRouteManifest(
90
+ registry: OperationRegistry,
91
+ basePath: string,
92
+ ): HttpRouteDescriptor[] {
93
+ return registry
94
+ .listOperations()
95
+ .map((op) => ({
96
+ name: op.name,
97
+ methods: op.mutates ? ['POST'] : ['GET', 'POST'],
98
+ path: `${basePath}/${op.name}`,
99
+ summary: op.summary,
100
+ description: op.description,
101
+ mutates: op.mutates,
102
+ supportsDryRun: op.supportsDryRun,
103
+ host: op.host,
104
+ requires: op.requires,
105
+ permission: op.permission,
106
+ inputSchema: inputJsonSchema(op),
107
+ errorCodes: op.errors.map((e) => e.code),
108
+ }))
109
+ .sort((a, b) => a.name.localeCompare(b.name));
110
+ }
111
+
112
+ /** A projection-level (not `dispatch`) failure, still shaped as an `OperationOutcome` so every response body is uniform. */
113
+ function failure(code: string, message: string, data?: unknown): OperationOutcome {
114
+ return { ok: false, error: { code, message, ...(data !== undefined ? { data } : {}) } };
115
+ }
116
+
117
+ /**
118
+ * Map an `OperationOutcome` to an HTTP status. Best-effort ONLY — the
119
+ * authoritative, cross-projection-identical signal is `error.code` in the
120
+ * body, not this status. Success is 200; a handful of well-known core codes
121
+ * get their conventional status; every other structured failure defaults to
122
+ * 400 (a caller-actionable problem) rather than 500.
123
+ */
124
+ export function httpStatusForOutcome(outcome: OperationOutcome): number {
125
+ if (outcome.ok) return 200;
126
+ switch (outcome.error.code) {
127
+ case CORE_ERROR_CODES.OPERATION_NOT_FOUND:
128
+ return 404;
129
+ case 'METHOD_NOT_ALLOWED':
130
+ return 405;
131
+ case CORE_ERROR_CODES.INTERNAL_ERROR:
132
+ case CORE_ERROR_CODES.INVALID_OUTPUT:
133
+ return 500;
134
+ case 'EDITOR_NOT_RUNNING':
135
+ case 'NO_PROJECT_ROOT':
136
+ return 503;
137
+ default:
138
+ return 400;
139
+ }
140
+ }
141
+
142
+ interface ParsedRoute {
143
+ name: string;
144
+ method: string;
145
+ /** Raw input object parsed from body (POST) or `?input=` (GET); `undefined` when none supplied. */
146
+ input: unknown;
147
+ /** A parse error to short-circuit with, if the body/query was malformed. */
148
+ parseError?: string;
149
+ }
150
+
151
+ /** Read a request body as a UTF-8 string, capped so a projection server can't be trivially flooded. */
152
+ function readBody(req: IncomingMessage, limitBytes = 5_000_000): Promise<string> {
153
+ return new Promise((resolvePromise, reject) => {
154
+ const chunks: Buffer[] = [];
155
+ let total = 0;
156
+ req.on('data', (chunk: Buffer) => {
157
+ total += chunk.length;
158
+ if (total > limitBytes) {
159
+ reject(new Error(`request body exceeds ${limitBytes} bytes`));
160
+ req.destroy();
161
+ return;
162
+ }
163
+ chunks.push(chunk);
164
+ });
165
+ req.on('end', () => resolvePromise(Buffer.concat(chunks).toString('utf-8')));
166
+ req.on('error', (err) => reject(err));
167
+ });
168
+ }
169
+
170
+ /** Parse the input object for a route from either the POST body or the GET `?input=` query param. */
171
+ async function parseRouteInput(
172
+ req: IncomingMessage,
173
+ method: string,
174
+ url: URL,
175
+ ): Promise<{ input: unknown } | { parseError: string }> {
176
+ if (method === 'GET') {
177
+ const raw = url.searchParams.get('input');
178
+ if (raw === null) return { input: {} };
179
+ try {
180
+ return { input: JSON.parse(raw) };
181
+ } catch (err) {
182
+ return {
183
+ parseError: `?input= is not valid JSON: ${err instanceof Error ? err.message : err}`,
184
+ };
185
+ }
186
+ }
187
+ // POST (and any other method that carries a body).
188
+ let body: string;
189
+ try {
190
+ body = await readBody(req);
191
+ } catch (err) {
192
+ return { parseError: err instanceof Error ? err.message : String(err) };
193
+ }
194
+ const trimmed = body.trim();
195
+ if (trimmed === '') return { input: {} };
196
+ try {
197
+ return { input: JSON.parse(trimmed) };
198
+ } catch (err) {
199
+ return {
200
+ parseError: `request body is not valid JSON: ${err instanceof Error ? err.message : err}`,
201
+ };
202
+ }
203
+ }
204
+
205
+ /**
206
+ * The pure routing core, exported for tests: given a method, pathname and
207
+ * already-parsed input, resolve the target operation and dispatch it —
208
+ * returning the `OperationOutcome` and the HTTP status, with no `http`
209
+ * objects involved. `serveHttpRequest` is the thin I/O wrapper around this.
210
+ */
211
+ export async function dispatchRoute(
212
+ registry: OperationRegistry,
213
+ basePath: string,
214
+ route: ParsedRoute,
215
+ ctx: OperationContext,
216
+ ): Promise<OperationOutcome> {
217
+ if (route.parseError !== undefined) {
218
+ // A malformed body is a caller input problem — surface it as the SAME
219
+ // INVALID_INPUT code the SDK/CLI use for a schema rejection.
220
+ return failure(CORE_ERROR_CODES.INVALID_INPUT, route.parseError);
221
+ }
222
+
223
+ const def = registry.getOperation(route.name);
224
+ if (!def) {
225
+ return failure(
226
+ CORE_ERROR_CODES.OPERATION_NOT_FOUND,
227
+ `No operation is registered as "${route.name}".`,
228
+ { name: route.name },
229
+ );
230
+ }
231
+
232
+ if (route.method === 'GET' && def.mutates) {
233
+ return failure(
234
+ 'METHOD_NOT_ALLOWED',
235
+ `"${route.name}" mutates state; use POST ${basePath}/${route.name}.`,
236
+ { name: route.name, allowed: ['POST'] },
237
+ );
238
+ }
239
+
240
+ return registry.dispatch(route.name, route.input, ctx);
241
+ }
242
+
243
+ /** Send an `OperationOutcome` as a JSON response with the derived status. */
244
+ function sendOutcome(res: ServerResponse, outcome: OperationOutcome): void {
245
+ const body = `${JSON.stringify(outcome)}\n`;
246
+ res.writeHead(httpStatusForOutcome(outcome), { 'content-type': 'application/json' });
247
+ res.end(body);
248
+ }
249
+
250
+ /** Resolve the per-request `OperationContext` from the options (static or factory). */
251
+ async function resolveHttpContext(
252
+ options: HttpProjectionOptions,
253
+ req: IncomingMessage,
254
+ ): Promise<OperationContext> {
255
+ if (options.createContext) return options.createContext(req);
256
+ if (options.context) return options.context;
257
+ return {};
258
+ }
259
+
260
+ /** The full request lifecycle, factored out of the returned closure to keep each step flat. */
261
+ async function handleProjectionRequest(
262
+ options: HttpProjectionOptions,
263
+ registry: OperationRegistry,
264
+ basePath: string,
265
+ req: IncomingMessage,
266
+ res: ServerResponse,
267
+ ): Promise<void> {
268
+ const url = new URL(req.url ?? '/', 'http://localhost');
269
+ const method = (req.method ?? 'GET').toUpperCase();
270
+ const pathname = url.pathname.replace(/\/$/, '') || '/';
271
+
272
+ // GET <basePath> -> the route manifest (tools/list analogue).
273
+ if (pathname === basePath && method === 'GET') {
274
+ const manifest = buildRouteManifest(registry, basePath);
275
+ res.writeHead(200, { 'content-type': 'application/json' });
276
+ res.end(`${JSON.stringify({ ok: true, data: { basePath, operations: manifest } })}\n`);
277
+ return;
278
+ }
279
+
280
+ const prefix = `${basePath}/`;
281
+ if (!pathname.startsWith(prefix)) {
282
+ sendOutcome(
283
+ res,
284
+ failure(
285
+ CORE_ERROR_CODES.OPERATION_NOT_FOUND,
286
+ `No route matches ${method} ${pathname}. Operation routes live under ${prefix}.`,
287
+ ),
288
+ );
289
+ return;
290
+ }
291
+
292
+ const name = pathname.slice(prefix.length);
293
+ const parsed = await parseRouteInput(req, method, url);
294
+ const route: ParsedRoute =
295
+ 'parseError' in parsed
296
+ ? { name, method, input: undefined, parseError: parsed.parseError }
297
+ : { name, method, input: parsed.input };
298
+
299
+ let ctx: OperationContext;
300
+ try {
301
+ ctx = await resolveHttpContext(options, req);
302
+ } catch (err) {
303
+ sendOutcome(
304
+ res,
305
+ failure(
306
+ CORE_ERROR_CODES.INTERNAL_ERROR,
307
+ `createContext threw: ${err instanceof Error ? err.message : err}`,
308
+ ),
309
+ );
310
+ return;
311
+ }
312
+
313
+ sendOutcome(res, await dispatchRoute(registry, basePath, route, ctx));
314
+ }
315
+
316
+ /**
317
+ * Handle one HTTP request against the projection. Exported so it can be
318
+ * mounted on an existing `http.Server`/framework, but `createHttpProjectionServer`
319
+ * is the turnkey path.
320
+ */
321
+ export function createHttpProjectionHandler(
322
+ options: HttpProjectionOptions = {},
323
+ ): (req: IncomingMessage, res: ServerResponse) => void {
324
+ const registry = options.registry ?? defaultRegistry;
325
+ const basePath = (options.basePath ?? '/op').replace(/\/$/, '');
326
+
327
+ return (req: IncomingMessage, res: ServerResponse): void => {
328
+ handleProjectionRequest(options, registry, basePath, req, res).catch((err) => {
329
+ // Last-resort guard: nothing above should throw, but never leave a
330
+ // socket hanging — a hung projection would be worse than a 500.
331
+ if (!res.headersSent) {
332
+ sendOutcome(
333
+ res,
334
+ failure(
335
+ CORE_ERROR_CODES.INTERNAL_ERROR,
336
+ `unhandled projection error: ${err instanceof Error ? err.message : err}`,
337
+ ),
338
+ );
339
+ } else {
340
+ res.end();
341
+ }
342
+ });
343
+ };
344
+ }
345
+
346
+ /** Build (but do not start) an `http.Server` that serves the registry projection. Call `.listen(port)`. */
347
+ export function createHttpProjectionServer(options: HttpProjectionOptions = {}): Server {
348
+ return createServer(createHttpProjectionHandler(options));
349
+ }
@@ -0,0 +1,11 @@
1
+ /** HTTP projection of the operation registry (B8, §8 B8). See `./http-projection.ts`. */
2
+ export {
3
+ buildRouteManifest,
4
+ createHttpProjectionHandler,
5
+ createHttpProjectionServer,
6
+ dispatchRoute,
7
+ type HttpProjectionOptions,
8
+ type HttpRouteDescriptor,
9
+ httpStatusForOutcome,
10
+ inputJsonSchema,
11
+ } from './http-projection.js';
package/src/index.ts ADDED
@@ -0,0 +1,67 @@
1
+ export * from './cinematic/index.js';
2
+ export * from './editor/index.js';
3
+ export {
4
+ CORE_ERROR_CODES,
5
+ type CoreErrorCode,
6
+ OperationError,
7
+ type StructuredIssue,
8
+ type StructuredOperationError,
9
+ toStructuredIssues,
10
+ } from './errors.js';
11
+ export * from './http/index.js';
12
+ export * from './mcp/index.js';
13
+ export {
14
+ ProjectStatusInput,
15
+ ProjectStatusResult,
16
+ projectStatus,
17
+ registerBuiltinOperations,
18
+ } from './operations.js';
19
+ export * from './play/index.js';
20
+ export * from './project/index.js';
21
+ export {
22
+ defineOperation,
23
+ type ErrorDefinition,
24
+ type OperationDefinition,
25
+ type OperationOutcome,
26
+ OperationRegistry,
27
+ type OperationSummary,
28
+ } from './registry.js';
29
+ export * from './render/index.js';
30
+ export {
31
+ type ExecutionHost,
32
+ type ExecutionRequirements,
33
+ OPERATION_NAMESPACES,
34
+ type OperationContext,
35
+ type OperationNamespace,
36
+ type PermissionMetadata,
37
+ type PermissionRisk,
38
+ } from './types.js';
39
+
40
+ import { registerCinematicOperations } from './cinematic/index.js';
41
+ import { registerEditorOperations } from './editor/index.js';
42
+ import { registerBuiltinOperations } from './operations.js';
43
+ import { registerPlayOperations } from './play/index.js';
44
+ import { registerProjectOperations } from './project/index.js';
45
+ import { OperationRegistry } from './registry.js';
46
+
47
+ /**
48
+ * Build a fresh registry preloaded with B1's sample operations plus B2's
49
+ * real `project.*` operations, B3's real `editor.*` operations, B4's real
50
+ * `play.*` operations, and B5's real `cinematic.*` operations. Deliberately
51
+ * separate register calls (not one function grown to cover all five) —
52
+ * `registerBuiltinOperations` is B1's own registration function, and its
53
+ * registry test asserts exactly what it registers; growing it here would
54
+ * break that assertion for a reason unrelated to what it actually tests.
55
+ */
56
+ export function createOperationRegistry(): OperationRegistry {
57
+ const registry = new OperationRegistry();
58
+ registerBuiltinOperations(registry);
59
+ registerProjectOperations(registry);
60
+ registerEditorOperations(registry);
61
+ registerPlayOperations(registry);
62
+ registerCinematicOperations(registry);
63
+ return registry;
64
+ }
65
+
66
+ /** Convenience default registry — the same one CLI/HTTP/MCP projections (B6-B8) will build on. */
67
+ export const operations = createOperationRegistry();
@@ -0,0 +1,16 @@
1
+ /** MCP projection of the operation registry (B8, §8 B8). See `./mcp-projection.ts`. */
2
+ export {
3
+ createMcpProjection,
4
+ isResourceEligible,
5
+ type JsonRpcRequest,
6
+ type JsonRpcResponse,
7
+ MCP_RESOURCE_PREFIX,
8
+ type McpProjection,
9
+ type McpProjectionOptions,
10
+ type McpResource,
11
+ type McpTool,
12
+ operationToResource,
13
+ operationToTool,
14
+ outcomeToToolResult,
15
+ toolInputSchema,
16
+ } from './mcp-projection.js';