@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/package.json ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "@vgai/sdk",
3
+ "author": "Volter AI, Inc.",
4
+ "license": "Apache-2.0",
5
+ "version": "0.4.0-canary.20260715.0",
6
+ "type": "module",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/volter-ai/vgai-engine.git",
10
+ "directory": "packages/vgai-sdk"
11
+ },
12
+ "publishConfig": {
13
+ "access": "public"
14
+ },
15
+ "files": [
16
+ "src"
17
+ ],
18
+ "exports": {
19
+ ".": "./src/index.ts",
20
+ "./registry": "./src/registry.ts"
21
+ },
22
+ "dependencies": {
23
+ "@vgai/engine": "0.4.0-canary.20260715.0",
24
+ "playwright": "^1.58.2",
25
+ "zod": "^4.3.6"
26
+ }
27
+ }
@@ -0,0 +1,128 @@
1
+ /**
2
+ * `cinematic.capabilities` (B5, §8 B5 "report capture/codec capabilities").
3
+ *
4
+ * Wraps `checkFfmpegCapability` (`packages/vgai-sdk/src/render/capabilities/ffmpeg.ts`, A1) for
5
+ * the ffmpeg/ffprobe BINARY presence/version half, and adds the DECLARED
6
+ * format/codec support `packages/vgai-sdk/src/render/render-cinematic.ts` itself
7
+ * hard-codes (`buildEncodeArgs`/`requireValidBackgroundFormat`) — mp4/h264,
8
+ * webm/vp9(+alpha), and png (no video codec, no container). This is a
9
+ * DECLARATION of what the render pipeline is WRITTEN to attempt, not an
10
+ * independent `ffmpeg -codecs`/`-encoders` probe: `checkFfmpegCapability`
11
+ * itself only runs `<bin> -version` (binary presence), never inspects which
12
+ * codecs a given ffmpeg build was compiled with — so an ffmpeg binary that
13
+ * IS present but lacks `libx264`/`libvpx-vp9` still reports success here,
14
+ * and would only fail later, at actual encode time, with ffmpeg's own raw
15
+ * stderr (surfaced by `cinematic.render.status`'s error field). This mirrors
16
+ * `checkFfmpegCapability`'s own scope honestly rather than fabricating a
17
+ * codec-level guarantee it was never designed to make.
18
+ *
19
+ * `FfmpegCapabilityResult`'s failure shape
20
+ * (`{ ok: false, errors: FfmpegCapabilityError[] }`) does NOT retain
21
+ * command/version for whichever of ffmpeg/ffprobe DID succeed when the
22
+ * other is missing (`checkFfmpegCapability`'s own success values are
23
+ * discarded once any error exists — see its source) — so in that mixed
24
+ * case this op honestly reports the present-but-unprobed-for-version binary
25
+ * as `{ ok: true }` with no `command`/`version`, rather than fabricating
26
+ * either.
27
+ */
28
+
29
+ import { z } from 'zod';
30
+ import { defineOperation, type OperationRegistry } from '../registry.js';
31
+ import { checkFfmpegCapability } from '../render/capabilities/ffmpeg.js';
32
+
33
+ const BinaryCapability = z.discriminatedUnion('ok', [
34
+ z.object({
35
+ ok: z.literal(true),
36
+ command: z
37
+ .string()
38
+ .optional()
39
+ .describe('Absent when the sibling binary was missing (see module jsdoc).'),
40
+ version: z
41
+ .string()
42
+ .optional()
43
+ .describe('Absent when the sibling binary was missing (see module jsdoc).'),
44
+ }),
45
+ z.object({ ok: z.literal(false), installGuidance: z.string() }),
46
+ ]);
47
+
48
+ const CinematicCapabilitiesInput = z.object({
49
+ ffmpegBin: z.string().optional().describe('Override the ffmpeg binary name/path to probe.'),
50
+ ffprobeBin: z.string().optional().describe('Override the ffprobe binary name/path to probe.'),
51
+ });
52
+
53
+ const CinematicCapabilitiesResult = z.object({
54
+ ffmpeg: BinaryCapability,
55
+ ffprobe: BinaryCapability,
56
+ supportedFormats: z
57
+ .array(
58
+ z.object({
59
+ format: z.enum(['mp4', 'webm', 'png']),
60
+ videoCodec: z.string().optional().describe('Absent for png (no video encode step at all).'),
61
+ alpha: z
62
+ .boolean()
63
+ .describe('Whether this format/codec combination can carry an alpha channel.'),
64
+ }),
65
+ )
66
+ .describe(
67
+ 'Formats packages/vgai-sdk/src/render/render-cinematic.ts is WRITTEN to attempt — a declared ' +
68
+ 'capability list, not an independent ffmpeg -codecs probe (see module jsdoc).',
69
+ ),
70
+ warnings: z.array(z.string()),
71
+ });
72
+
73
+ const DECLARED_FORMATS = [
74
+ { format: 'mp4' as const, videoCodec: 'libx264', alpha: false },
75
+ { format: 'webm' as const, videoCodec: 'libvpx-vp9', alpha: true },
76
+ { format: 'png' as const, alpha: true },
77
+ ];
78
+
79
+ export const cinematicCapabilities = defineOperation({
80
+ name: 'cinematic.capabilities',
81
+ summary:
82
+ 'Report ffmpeg/ffprobe capture capability and the render pipeline’s declared codec/format support.',
83
+ description:
84
+ 'Wraps checkFfmpegCapability (A1) for binary presence/version, plus a DECLARED (not probed) ' +
85
+ 'format/codec list matching render-cinematic.ts’s own hard-coded encode args (see module jsdoc ' +
86
+ 'for the honest scope limit).',
87
+ input: CinematicCapabilitiesInput,
88
+ result: CinematicCapabilitiesResult,
89
+ errors: [],
90
+ requires: {},
91
+ host: 'node',
92
+ mutates: false,
93
+ supportsDryRun: false,
94
+ permission: { risk: 'read', summary: 'Runs `ffmpeg -version`/`ffprobe -version`; no writes.' },
95
+ async impl(input) {
96
+ const check = checkFfmpegCapability({
97
+ ...(input.ffmpegBin !== undefined ? { ffmpegBin: input.ffmpegBin } : {}),
98
+ ...(input.ffprobeBin !== undefined ? { ffprobeBin: input.ffprobeBin } : {}),
99
+ });
100
+
101
+ if (check.ok) {
102
+ return {
103
+ ffmpeg: { ok: true as const, ...check.ffmpeg },
104
+ ffprobe: { ok: true as const, ...check.ffprobe },
105
+ supportedFormats: DECLARED_FORMATS,
106
+ warnings: [],
107
+ };
108
+ }
109
+
110
+ const ffmpegErr = check.errors.find((e) => e.binary === 'ffmpeg');
111
+ const ffprobeErr = check.errors.find((e) => e.binary === 'ffprobe');
112
+ return {
113
+ ffmpeg: ffmpegErr
114
+ ? { ok: false as const, installGuidance: ffmpegErr.installGuidance }
115
+ : { ok: true as const },
116
+ ffprobe: ffprobeErr
117
+ ? { ok: false as const, installGuidance: ffprobeErr.installGuidance }
118
+ : { ok: true as const },
119
+ supportedFormats: DECLARED_FORMATS,
120
+ warnings: check.errors.map((e) => e.message),
121
+ };
122
+ },
123
+ });
124
+
125
+ /** Register cinematic.capabilities onto `registry`. */
126
+ export function registerCapabilitiesOperations(registry: OperationRegistry): void {
127
+ registry.register(cinematicCapabilities);
128
+ }
@@ -0,0 +1,198 @@
1
+ /**
2
+ * `cinematic.cue.list` / `cinematic.cue.validate`
3
+ * (B5, §8 B5 "list cues and validate cue ordering/IDs").
4
+ *
5
+ * `CinematicCue` (`packages/engine/src/animation/cinematic-cues.ts`) is
6
+ * ordinary TypeScript — a plain array literal with a `run`/`reverse`
7
+ * function per entry (§3.2/§5.4: "Cues must not contain an alternate
8
+ * property-animation model" — deliberately NOT a VGAI-owned serialized
9
+ * format). There is therefore no JSON/manifest this op can parse
10
+ * authoritatively; like B2's `project.xstate.discover`/`.validate`
11
+ * (`../project/discovery-operations.ts`), this is a STATIC HEURISTIC over
12
+ * source text, not a parse/execute — it can both miss cues (defined via
13
+ * spread, computed properties, or indirection) and never proves `run`/
14
+ * `reverse` themselves are correct. Named as a limit, not silently passed
15
+ * off as full validation, exactly like that module's own documented limit.
16
+ *
17
+ * The heuristic looks for the literal shape every committed cue array in
18
+ * this repo actually uses (`packages/engine/e2e/reference-cinematic/main.ts`):
19
+ * `{ id: '...', time: <number>, ... }` — `id` immediately followed by
20
+ * `time` (whitespace/newlines between fields, comma-separated, matching
21
+ * `CinematicCue`'s own field order convention).
22
+ */
23
+
24
+ import { existsSync, readFileSync } from 'node:fs';
25
+ import { isAbsolute, join } from 'node:path';
26
+ import { z } from 'zod';
27
+ import { OperationError } from '../errors.js';
28
+ import { defineOperation, type ErrorDefinition, type OperationRegistry } from '../registry.js';
29
+ import type { OperationContext } from '../types.js';
30
+ import { NO_ROOT_ERROR } from './theatre-operations.js';
31
+
32
+ export const CUE_FILE_NOT_FOUND_ERROR: ErrorDefinition = {
33
+ code: 'FILE_NOT_FOUND',
34
+ summary: 'The referenced cue module file does not exist on disk.',
35
+ data: z.object({ path: z.string() }),
36
+ };
37
+
38
+ const CUE_LITERAL_PATTERN =
39
+ /\{\s*id:\s*['"]([^'"]+)['"]\s*,\s*time:\s*(-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?)/g;
40
+
41
+ export interface HeuristicCue {
42
+ readonly id: string;
43
+ readonly time: number;
44
+ }
45
+
46
+ /**
47
+ * Extract `{ id: '...', time: <n> }`-shaped object literals from `source`,
48
+ * in the order they appear in the text (== the array's declaration/
49
+ * registration order, per `createCueRunner`'s equal-timestamp tiebreak
50
+ * convention). Best-effort text heuristic — see module jsdoc.
51
+ */
52
+ export function extractHeuristicCues(source: string): HeuristicCue[] {
53
+ const cues: HeuristicCue[] = [];
54
+ for (const match of source.matchAll(CUE_LITERAL_PATTERN)) {
55
+ const id = match[1];
56
+ const time = Number(match[2]);
57
+ if (id !== undefined && Number.isFinite(time)) cues.push({ id, time });
58
+ }
59
+ return cues;
60
+ }
61
+
62
+ const CueSourceInput = z.object({
63
+ source: z.string().optional().describe('Raw TypeScript source to scan directly.'),
64
+ path: z
65
+ .string()
66
+ .optional()
67
+ .describe('Path to a TypeScript module to read and scan (absolute, or root-relative).'),
68
+ root: z
69
+ .string()
70
+ .optional()
71
+ .describe('Base directory for a relative `path`. Defaults to ctx.projectRoot.'),
72
+ });
73
+
74
+ function resolveSource(input: z.infer<typeof CueSourceInput>, ctx: OperationContext): string {
75
+ if (input.source !== undefined) return input.source;
76
+ if (input.path === undefined) {
77
+ throw new OperationError('BAD_INPUT', 'One of `source` or `path` must be given.', {});
78
+ }
79
+ const root = input.root ?? ctx.projectRoot;
80
+ const absPath = isAbsolute(input.path) ? input.path : root ? join(root, input.path) : undefined;
81
+ if (!absPath) {
82
+ throw new OperationError(
83
+ 'NO_ROOT',
84
+ 'A relative `path` was given but neither `root` nor ctx.projectRoot is available.',
85
+ {},
86
+ );
87
+ }
88
+ if (!existsSync(absPath)) {
89
+ throw new OperationError('FILE_NOT_FOUND', `File not found: ${absPath}`, { path: absPath });
90
+ }
91
+ return readFileSync(absPath, 'utf-8');
92
+ }
93
+
94
+ const HeuristicCueSchema = z.object({ id: z.string(), time: z.number() });
95
+
96
+ // ---------------------------------------------------------------------------
97
+ // cinematic.cue.list
98
+ // ---------------------------------------------------------------------------
99
+
100
+ const CinematicCueListResult = z.object({
101
+ cues: z
102
+ .array(HeuristicCueSchema)
103
+ .describe('Every {id, time} pair found, in source declaration order.'),
104
+ });
105
+
106
+ export const cinematicCueList = defineOperation({
107
+ name: 'cinematic.cue.list',
108
+ summary: 'Best-effort static list of {id, time} cue entries in a TypeScript cue module.',
109
+ description:
110
+ 'STATIC HEURISTIC over source text, not a parse/execute (see module jsdoc) — CinematicCue is ' +
111
+ 'ordinary TypeScript (§3.2/§5.4), not a VGAI-owned serialized format.',
112
+ input: CueSourceInput,
113
+ result: CinematicCueListResult,
114
+ errors: [
115
+ NO_ROOT_ERROR,
116
+ CUE_FILE_NOT_FOUND_ERROR,
117
+ { code: 'BAD_INPUT', summary: 'Neither `source` nor `path` was given.', data: z.object({}) },
118
+ ],
119
+ requires: {},
120
+ host: 'node',
121
+ mutates: false,
122
+ supportsDryRun: false,
123
+ permission: { risk: 'read', summary: 'Reads one file’s text (or a given string); no writes.' },
124
+ async impl(input, ctx) {
125
+ const source = resolveSource(input, ctx);
126
+ return { cues: extractHeuristicCues(source) };
127
+ },
128
+ });
129
+
130
+ // ---------------------------------------------------------------------------
131
+ // cinematic.cue.validate
132
+ // ---------------------------------------------------------------------------
133
+
134
+ const CinematicCueValidateResult = z.object({
135
+ valid: z.boolean(),
136
+ issues: z.array(z.string()),
137
+ cues: z.array(HeuristicCueSchema),
138
+ });
139
+
140
+ export const cinematicCueValidate = defineOperation({
141
+ name: 'cinematic.cue.validate',
142
+ summary:
143
+ 'Validate unique cue IDs and ascending time ordering for the cues found by cinematic.cue.list’s ' +
144
+ 'same heuristic.',
145
+ description:
146
+ 'STATIC HEURISTIC (see cinematic.cue.list / module jsdoc for the same limit). Two checks, ' +
147
+ 'mirroring createCueRunner’s own runtime contract (packages/engine/src/animation/cinematic-cues.ts): ' +
148
+ '(1) no duplicate `id` (the runtime throws on this); (2) declaration order is already non-decreasing ' +
149
+ 'by `time` (createCueRunner re-sorts regardless, but declaration order drifting from evaluation order ' +
150
+ 'is a readability/authoring smell worth flagging, not a hard runtime requirement).',
151
+ input: CueSourceInput,
152
+ result: CinematicCueValidateResult,
153
+ errors: [
154
+ NO_ROOT_ERROR,
155
+ CUE_FILE_NOT_FOUND_ERROR,
156
+ { code: 'BAD_INPUT', summary: 'Neither `source` nor `path` was given.', data: z.object({}) },
157
+ ],
158
+ requires: {},
159
+ host: 'node',
160
+ mutates: false,
161
+ supportsDryRun: false,
162
+ permission: { risk: 'read', summary: 'Reads one file’s text (or a given string); no writes.' },
163
+ async impl(input, ctx) {
164
+ const source = resolveSource(input, ctx);
165
+ const cues = extractHeuristicCues(source);
166
+ const issues: string[] = [];
167
+
168
+ const seen = new Map<string, number>();
169
+ cues.forEach((cue, i) => {
170
+ if (seen.has(cue.id)) {
171
+ issues.push(
172
+ `Duplicate cue id "${cue.id}" at positions ${seen.get(cue.id)} and ${i} — ids must be unique.`,
173
+ );
174
+ } else {
175
+ seen.set(cue.id, i);
176
+ }
177
+ });
178
+
179
+ for (let i = 1; i < cues.length; i++) {
180
+ const prev = cues[i - 1]!;
181
+ const cur = cues[i]!;
182
+ if (cur.time < prev.time) {
183
+ issues.push(
184
+ `Cue "${cur.id}" (time ${cur.time}) is declared after "${prev.id}" (time ${prev.time}) but ` +
185
+ 'has an earlier time — declaration order drifts from evaluation order.',
186
+ );
187
+ }
188
+ }
189
+
190
+ return { valid: issues.length === 0, issues, cues };
191
+ },
192
+ });
193
+
194
+ /** Register cinematic.cue.list / cinematic.cue.validate onto `registry`. */
195
+ export function registerCueOperations(registry: OperationRegistry): void {
196
+ registry.register(cinematicCueList);
197
+ registry.register(cinematicCueValidate);
198
+ }
@@ -0,0 +1,126 @@
1
+ /**
2
+ * `cinematic.gsap.inspect` (B5, §8 B5 "inspect registered deterministic GSAP
3
+ * timelines").
4
+ *
5
+ * DECISION (per this unit's brief — "static/metadata where possible; named
6
+ * error if runtime-only"): this is STATIC-ONLY, by design, not a wiring gap.
7
+ * `registerGsap` (`packages/engine/src/animation/gsap-registration.ts`)
8
+ * stores its bookkeeping in a module-private
9
+ * `WeakMap<AnimationClock, WeakSet<gsap.core.Timeline>>` — deliberately
10
+ * un-enumerable (no `.list()`/registry object is exported at all, so that
11
+ * neither a disposed clock nor a disposed timeline is kept reachable/leaked
12
+ * just so something else could list it later). There is consequently no
13
+ * live surface — local or over any wire protocol — that could ever answer
14
+ * "what GSAP timelines are registered on this running clock right now",
15
+ * independent of this unit's file-ownership boundary (that WeakMap is
16
+ * `packages/engine/src/animation/gsap-registration.ts`, not
17
+ * `vgai-sdk`, and adding an enumeration API to it is out of this unit's
18
+ * scope regardless). So the honest, useful thing this op CAN do is exactly
19
+ * what B2's `project.xstate.discover` does for XState machines
20
+ * (`../project/discovery-operations.ts`) — best-effort STATIC discovery of
21
+ * source files that call `registerGsap(...)`, clearly reported as
22
+ * static-only, never a live registration count.
23
+ */
24
+
25
+ import { readFileSync, statSync } from 'node:fs';
26
+ import { join } from 'node:path';
27
+ import { z } from 'zod';
28
+ import { OperationError } from '../errors.js';
29
+ import { listProjectFiles } from '../project/shared.js';
30
+ import { defineOperation, type OperationRegistry } from '../registry.js';
31
+ import { NO_ROOT_ERROR } from './theatre-operations.js';
32
+
33
+ /** True when the file text looks like it registers a GSAP timeline for deterministic scrub/export. */
34
+ function findGsapRegistrations(text: string): {
35
+ importsGsapRegistration: boolean;
36
+ callCount: number;
37
+ } {
38
+ const importsGsapRegistration = /from\s+['"][^'"]*gsap-registration(?:\.js)?['"]/.test(text);
39
+ const callMatches = text.match(/\bregisterGsap\s*\(/g);
40
+ return { importsGsapRegistration, callCount: callMatches?.length ?? 0 };
41
+ }
42
+
43
+ const CinematicGsapInspectInput = z.object({
44
+ root: z
45
+ .string()
46
+ .optional()
47
+ .describe('Directory to scan for GSAP registration call sites. Defaults to ctx.projectRoot.'),
48
+ });
49
+
50
+ const CinematicGsapInspectResult = z.object({
51
+ root: z.string(),
52
+ staticInspectionOnly: z
53
+ .literal(true)
54
+ .describe(
55
+ 'Always true — see module jsdoc: registerGsap stores registrations in an un-enumerable ' +
56
+ 'WeakMap/WeakSet by design, so there is no live surface this (or any) op could poll instead.',
57
+ ),
58
+ registrations: z
59
+ .array(
60
+ z.object({
61
+ path: z.string(),
62
+ importsGsapRegistration: z.boolean(),
63
+ callCount: z
64
+ .number()
65
+ .describe(
66
+ 'Number of `registerGsap(` call-site TEXT matches in this file (a heuristic count, not a proof of how many timelines end up registered at runtime).',
67
+ ),
68
+ }),
69
+ )
70
+ .describe('Every *.ts/*.tsx file whose text imports gsap-registration, sorted by path.'),
71
+ });
72
+
73
+ export const cinematicGsapInspect = defineOperation({
74
+ name: 'cinematic.gsap.inspect',
75
+ summary:
76
+ 'Best-effort static discovery of source files that call registerGsap(...) to register a ' +
77
+ 'deterministic GSAP timeline.',
78
+ description:
79
+ 'STATIC-ONLY BY DESIGN, not a wiring gap — see module jsdoc. registerGsap’s bookkeeping is an ' +
80
+ 'un-enumerable WeakMap/WeakSet (packages/engine/src/animation/gsap-registration.ts), so no ' +
81
+ 'live "what is registered right now" surface exists anywhere to poll instead.',
82
+ input: CinematicGsapInspectInput,
83
+ result: CinematicGsapInspectResult,
84
+ errors: [NO_ROOT_ERROR],
85
+ requires: {},
86
+ host: 'node',
87
+ mutates: false,
88
+ supportsDryRun: false,
89
+ permission: { risk: 'read', summary: 'Reads *.ts/*.tsx file contents under root; no writes.' },
90
+ async impl(input, ctx) {
91
+ const root = input.root ?? ctx.projectRoot;
92
+ if (!root) {
93
+ throw new OperationError(
94
+ 'NO_ROOT',
95
+ 'Neither an explicit `root` input nor ctx.projectRoot was given.',
96
+ {},
97
+ );
98
+ }
99
+ const candidates = listProjectFiles(
100
+ root,
101
+ (p) => (p.endsWith('.ts') || p.endsWith('.tsx')) && !p.endsWith('.d.ts'),
102
+ );
103
+ const registrations: { path: string; importsGsapRegistration: boolean; callCount: number }[] =
104
+ [];
105
+ for (const rel of candidates) {
106
+ const abs = join(root, rel);
107
+ let text: string;
108
+ try {
109
+ if (!statSync(abs).isFile()) continue;
110
+ text = readFileSync(abs, 'utf-8');
111
+ } catch {
112
+ continue;
113
+ }
114
+ const found = findGsapRegistrations(text);
115
+ if (found.importsGsapRegistration || found.callCount > 0) {
116
+ registrations.push({ path: rel, ...found });
117
+ }
118
+ }
119
+ return { root, staticInspectionOnly: true as const, registrations };
120
+ },
121
+ });
122
+
123
+ /** Register cinematic.gsap.inspect onto `registry`. */
124
+ export function registerGsapOperations(registry: OperationRegistry): void {
125
+ registry.register(cinematicGsapInspect);
126
+ }
@@ -0,0 +1,59 @@
1
+ /**
2
+ * B5 — `cinematic.*` operations
3
+ * (docs/AI-NATIVE-AUTHORING-IMPLEMENTATION-SPEC.md §8 B5). Registered
4
+ * separately from B1's `registerBuiltinOperations` (`../operations.ts`),
5
+ * B2's `registerProjectOperations` (`../project/index.ts`), B3's
6
+ * `registerEditorOperations` (`../editor/index.ts`), and B4's
7
+ * `registerPlayOperations` (`../play/index.ts`) — the default `operations`
8
+ * singleton (`../index.ts`) calls all five.
9
+ *
10
+ * Removes B1's `cinematic.render` stand-in (`operations.ts`, which always
11
+ * threw `NOT_IMPLEMENTED`) now that this module registers the REAL
12
+ * implementation under the same name — same precedent B3 set for
13
+ * `editor.selection.get` (see `operations.ts`'s updated module jsdoc).
14
+ */
15
+
16
+ export * from './capabilities-operations.js';
17
+ export * from './cue-operations.js';
18
+ export * from './gsap-operations.js';
19
+ export * from './preview-operations.js';
20
+ export type {
21
+ CinematicOpenPreviewFn,
22
+ CinematicPreviewHandle,
23
+ CinematicPreviewSession,
24
+ } from './preview-transport.js';
25
+ export {
26
+ _clearPreviewSessionsForTests,
27
+ getCinematicOpenPreviewFn,
28
+ getPreviewSession,
29
+ } from './preview-transport.js';
30
+ export * from './render-operations.js';
31
+ export type {
32
+ CinematicRenderFn,
33
+ CinematicRenderJob,
34
+ CinematicRenderJobError,
35
+ } from './render-transport.js';
36
+ export {
37
+ _clearRenderJobsForTests,
38
+ getCinematicRenderFn,
39
+ getRenderJob,
40
+ } from './render-transport.js';
41
+ export * from './theatre-operations.js';
42
+
43
+ import type { OperationRegistry } from '../registry.js';
44
+ import { registerCapabilitiesOperations } from './capabilities-operations.js';
45
+ import { registerCueOperations } from './cue-operations.js';
46
+ import { registerGsapOperations } from './gsap-operations.js';
47
+ import { registerPreviewOperations } from './preview-operations.js';
48
+ import { registerRenderOperations } from './render-operations.js';
49
+ import { registerTheatreOperations } from './theatre-operations.js';
50
+
51
+ /** Register every B5 `cinematic.*` operation onto `registry`. */
52
+ export function registerCinematicOperations(registry: OperationRegistry): void {
53
+ registerTheatreOperations(registry);
54
+ registerCueOperations(registry);
55
+ registerGsapOperations(registry);
56
+ registerCapabilitiesOperations(registry);
57
+ registerRenderOperations(registry);
58
+ registerPreviewOperations(registry);
59
+ }