@hraness/slopcamera 3.2.7 → 3.3.1

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 (188) hide show
  1. package/NOTICE.md +1 -7
  2. package/PRIVACY.md +16 -7
  3. package/README.md +65 -20
  4. package/apps/desktop/README.md +14 -130
  5. package/apps/desktop/application/context.ts +6 -9
  6. package/apps/desktop/application/default-registry.ts +26 -10
  7. package/apps/desktop/application/operation.ts +14 -5
  8. package/apps/desktop/application/operations/index.ts +4 -1
  9. package/apps/desktop/application/operations/spatial-behavior.ts +151 -0
  10. package/apps/desktop/application/operations/spatial-direction.ts +222 -0
  11. package/apps/desktop/application/operations/spatial-rendered-audit.ts +174 -0
  12. package/apps/desktop/application/operations/spatial-review.ts +194 -0
  13. package/apps/desktop/application/operations/spatial-scene.ts +25 -1
  14. package/apps/desktop/application/registry.ts +2 -2
  15. package/apps/desktop/application/spatial-asset-admission.ts +201 -0
  16. package/apps/desktop/application/spatial-assets.ts +160 -31
  17. package/apps/desktop/application/spatial-render.ts +217 -20
  18. package/apps/desktop/application/spatial-rendered-audit.ts +273 -0
  19. package/apps/desktop/application/spatial-review.ts +361 -0
  20. package/apps/desktop/application/spatial-spz.ts +48 -3
  21. package/apps/desktop/application/spatial-world-import.ts +26 -5
  22. package/apps/desktop/application/spatial-world-metadata.ts +105 -0
  23. package/apps/desktop/cli/args.ts +528 -97
  24. package/apps/desktop/cli/capability-manifest.ts +374 -0
  25. package/apps/desktop/cli/cinema-renderer.ts +608 -0
  26. package/apps/desktop/cli/command-host-resources.ts +31 -2
  27. package/apps/desktop/cli/commands.ts +1215 -70
  28. package/apps/desktop/cli/gateway-review-provider.ts +334 -0
  29. package/apps/desktop/cli/help.ts +183 -23
  30. package/apps/desktop/cli/html-scene.ts +139 -28
  31. package/apps/desktop/cli/io.ts +2 -1
  32. package/apps/desktop/cli/main.ts +26 -50
  33. package/apps/desktop/cli/menubar.ts +244 -0
  34. package/apps/desktop/cli/music-analysis-service.ts +61 -0
  35. package/apps/desktop/cli/portable-surface.ts +18 -5
  36. package/apps/desktop/cli/spatial-asset-service.ts +124 -0
  37. package/apps/desktop/cli/spatial-design-service.ts +237 -0
  38. package/apps/desktop/cli/spatial-generate-service.ts +370 -0
  39. package/apps/desktop/cli/spatial-scene-service.ts +268 -4
  40. package/apps/desktop/cli/workflow-code.ts +1 -1
  41. package/apps/desktop/code/application-node-planner.ts +0 -47
  42. package/apps/desktop/code/host-source-layout.ts +46 -0
  43. package/apps/desktop/code/public.ts +0 -1
  44. package/apps/desktop/code/runtime-identity.ts +50 -8
  45. package/apps/desktop/code/semantic-builder.ts +24 -43
  46. package/apps/desktop/code/source-bundle.ts +23 -6
  47. package/apps/desktop/code/source-typecheck.ts +131 -43
  48. package/apps/desktop/code/worker-client.ts +14 -2
  49. package/apps/desktop/contracts/cinema.ts +736 -0
  50. package/apps/desktop/contracts/index.ts +1 -0
  51. package/apps/desktop/contracts/spatial-asset.ts +16 -0
  52. package/apps/desktop/contracts/spatial-world.ts +26 -3
  53. package/apps/desktop/core/cinema-plan.ts +1320 -0
  54. package/apps/desktop/core/index.ts +1 -0
  55. package/apps/desktop/core/music-analysis.ts +124 -1
  56. package/apps/desktop/core/storage.ts +75 -0
  57. package/apps/desktop/dist/cli/main.js +737 -291
  58. package/apps/desktop/html-overlay/audio-reactivity.ts +127 -0
  59. package/apps/desktop/html-overlay/index.ts +9 -0
  60. package/apps/desktop/html-overlay/runtime.ts +2 -0
  61. package/apps/desktop/html-overlay/scene.ts +29 -0
  62. package/apps/desktop/html-overlay/spatial.ts +619 -57
  63. package/apps/desktop/workflows/cinematic-world.ts +127 -0
  64. package/apps/desktop/workflows/index.ts +6 -0
  65. package/dist/cli.js +32 -434
  66. package/dist/code/advanced.js +1 -269
  67. package/dist/code/index.js +16 -2239
  68. package/dist/generate.js +1 -23
  69. package/dist/host-resources.js +1 -25
  70. package/dist/index-0bbesn4m.js +35 -0
  71. package/dist/index-296qhpf2.js +3 -0
  72. package/dist/index-3cxf2c58.js +3 -0
  73. package/dist/index-77fjfg9f.js +3 -0
  74. package/dist/index-80a36bc0.js +3 -0
  75. package/dist/index-a3vvs3rf.js +3 -0
  76. package/dist/index-b3geqczn.js +5 -0
  77. package/dist/index-cgpkyjqa.js +3 -0
  78. package/dist/index-f0ptv8dm.js +7 -0
  79. package/dist/index-grk47tgs.js +3 -0
  80. package/dist/index-qry58nj2.js +10 -0
  81. package/dist/index.js +1 -240
  82. package/dist/operations.js +1 -28
  83. package/dist/skill-install-gx7afxsd.js +2 -0
  84. package/dist/vectorize/worker.js +1 -141
  85. package/dist/workflow.js +1 -15
  86. package/docs/README.md +5 -1
  87. package/docs/studio.md +1 -1
  88. package/package.json +11 -24
  89. package/skills/slopcamera/SKILL.md +14 -1
  90. package/skills/slopcamera/references/diagrams.md +5 -14
  91. package/skills/slopcamera/references/directed-scenes.md +22 -2
  92. package/skills/slopcamera/references/gateway-media.md +15 -1
  93. package/skills/slopcamera/references/image-galleries.md +166 -0
  94. package/skills/slopcamera/references/install.md +2 -2
  95. package/skills/slopcamera/references/music-video.md +23 -3
  96. package/skills/slopcamera/references/parametric-design.md +14 -0
  97. package/skills/slopcamera/references/reference-led-3d.md +1 -1
  98. package/skills/slopcamera/references/scene-building.md +32 -0
  99. package/skills/slopcamera/references/social-collage-banners.md +204 -0
  100. package/skills/slopcamera/references/support.md +25 -0
  101. package/skills/slopcamera/references/web-media-excerpts.md +120 -0
  102. package/skills/slopcamera/references/workflows-sdk.md +3 -3
  103. package/skills/slopcamera/scripts/compose-social-collage-banner.ts +728 -0
  104. package/src/capability-manifest.ts +361 -0
  105. package/src/cli.ts +250 -102
  106. package/src/code/canonical-json.ts +9 -0
  107. package/src/generate.ts +2 -2
  108. package/src/icon.ts +1111 -0
  109. package/src/image-gallery.ts +1049 -0
  110. package/src/index.ts +11 -23
  111. package/src/mcp/boundary.ts +11 -11
  112. package/src/mcp/index.ts +3 -0
  113. package/src/mcp/server.ts +2 -1
  114. package/src/mcp/tools.ts +1642 -130
  115. package/src/mcp/types.ts +19 -0
  116. package/src/operations.ts +412 -0
  117. package/src/portable-capability-manifest.ts +67 -0
  118. package/src/scene-gallery.ts +184 -0
  119. package/src/spatial-scene/asset-admission.ts +183 -0
  120. package/src/spatial-scene/audit-rendered.ts +692 -0
  121. package/src/spatial-scene/audit.ts +617 -0
  122. package/src/spatial-scene/behavior-audit.ts +252 -0
  123. package/src/spatial-scene/behavior-bake.ts +233 -0
  124. package/src/spatial-scene/behavior-fns.ts +585 -0
  125. package/src/spatial-scene/behavior-gallery.ts +109 -0
  126. package/src/spatial-scene/behavior-stdlib.ts +323 -0
  127. package/src/spatial-scene/behavior-trace.ts +325 -0
  128. package/src/spatial-scene/behavior.ts +618 -0
  129. package/src/spatial-scene/build.ts +632 -0
  130. package/src/spatial-scene/camera-rig.ts +125 -0
  131. package/src/spatial-scene/camera-track.ts +4 -2
  132. package/src/spatial-scene/character.ts +124 -0
  133. package/src/spatial-scene/contracts.ts +75 -7
  134. package/src/spatial-scene/design-templates.ts +351 -0
  135. package/src/spatial-scene/design.ts +327 -0
  136. package/src/spatial-scene/direction-compile.ts +584 -0
  137. package/src/spatial-scene/direction.ts +122 -0
  138. package/src/spatial-scene/effects.ts +149 -0
  139. package/src/spatial-scene/evaluate.ts +109 -29
  140. package/src/spatial-scene/gallery.ts +277 -0
  141. package/src/spatial-scene/generate.ts +239 -0
  142. package/src/spatial-scene/geometry-native.ts +165 -0
  143. package/src/spatial-scene/geometry.ts +1216 -0
  144. package/src/spatial-scene/gltf.ts +600 -77
  145. package/src/spatial-scene/identity.ts +55 -2
  146. package/src/spatial-scene/index.ts +36 -0
  147. package/src/spatial-scene/inspect.ts +35 -6
  148. package/src/spatial-scene/material-lighting.ts +609 -0
  149. package/src/spatial-scene/math.ts +21 -4
  150. package/src/spatial-scene/motion-evidence.ts +88 -0
  151. package/src/spatial-scene/parametric.ts +765 -0
  152. package/src/spatial-scene/particle-preparation.ts +183 -0
  153. package/src/spatial-scene/particle.ts +195 -0
  154. package/src/spatial-scene/patch.ts +109 -15
  155. package/src/spatial-scene/performance.ts +1613 -0
  156. package/src/spatial-scene/probe.ts +137 -0
  157. package/src/spatial-scene/recipe-pack.ts +86 -0
  158. package/src/spatial-scene/render-effects.ts +240 -0
  159. package/src/spatial-scene/review.ts +639 -0
  160. package/src/spatial-scene/simulation-bake.ts +206 -0
  161. package/src/spatial-scene/simulation.ts +187 -0
  162. package/src/spatial-scene/solve.ts +366 -0
  163. package/src/spatial-scene/temporal-audit.ts +354 -0
  164. package/src/support-completion.ts +16 -0
  165. package/src/support.ts +42 -0
  166. package/src/version.ts +1 -1
  167. package/apps/desktop/application/operations/recording/index.ts +0 -5
  168. package/apps/desktop/application/operations/recording/pause.ts +0 -34
  169. package/apps/desktop/application/operations/recording/resume.ts +0 -34
  170. package/apps/desktop/application/operations/recording/shared.ts +0 -199
  171. package/apps/desktop/application/operations/recording/start.ts +0 -169
  172. package/apps/desktop/application/operations/recording/stop.ts +0 -34
  173. package/apps/desktop/capture/protocol.ts +0 -689
  174. package/apps/desktop/cli/capture-bundle.ts +0 -1117
  175. package/apps/desktop/cli/recording-controller.ts +0 -1567
  176. package/apps/desktop/cli/recording-daemon.ts +0 -931
  177. package/dist/index-42zsesc1.js +0 -2257
  178. package/dist/index-7308egqr.js +0 -48
  179. package/dist/index-8txs6fkn.js +0 -712
  180. package/dist/index-mcy8z0br.js +0 -192
  181. package/dist/index-p63wavx0.js +0 -2029
  182. package/dist/index-r7gdhmsp.js +0 -424
  183. package/dist/index-rvghzt7b.js +0 -2013
  184. package/dist/index-sh6xbav6.js +0 -1219
  185. package/dist/index-z1w83f81.js +0 -4
  186. package/dist/index-z7239b4h.js +0 -2363
  187. package/dist/skill-install-dh0ntqba.js +0 -10
  188. package/src/desktop.ts +0 -253
@@ -0,0 +1,334 @@
1
+ import { createBoundedJsonSnapshot } from "../../../src/code/json-snapshot";
2
+ import {
3
+ SPATIAL_REVIEW_LIMITS,
4
+ SPATIAL_REVIEW_PROMPT,
5
+ SpatialReviewModelOutputSchema,
6
+ SpatialReviewProviderError,
7
+ redactedSpatialReviewProviderError,
8
+ validateSpatialReviewProviderRequest,
9
+ type SpatialReviewModelOutput,
10
+ type SpatialReviewProvider,
11
+ type SpatialReviewProviderResult,
12
+ type ValidatedSpatialReviewProviderRequest,
13
+ } from "../../../src/spatial-scene/review";
14
+
15
+ import { canonicalJson } from "../core/canonical-json";
16
+ import type { ActiveGatewayCredential } from "./gateway-credential";
17
+ import type { GatewayMediaCatalogTransport } from "./gateway-media-catalog";
18
+ import {
19
+ createFixedSceneGatewayFetch,
20
+ sceneFrameDataUrl,
21
+ VERCEL_SCENE_GATEWAY_BASE_URL,
22
+ type SceneAiSdkLoader,
23
+ type SceneAiSdkModule,
24
+ } from "./gateway-scene-provider";
25
+
26
+ const MAXIMUM_CATALOG_BYTES = 8 * 1024 * 1024;
27
+ const CATALOG_MODEL_LIMIT = 1_000;
28
+ const DEFAULT_REVIEW_GATEWAY_TIMEOUT_MS = 120_000;
29
+ const GOOGLE_GEMINI_MODEL = /^google\/gemini-[a-z0-9][a-z0-9._-]{0,239}$/u;
30
+
31
+ type GatewayFetch = (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
32
+
33
+ interface AiSdkResult {
34
+ readonly output?: unknown;
35
+ readonly response?: unknown;
36
+ readonly usage?: unknown;
37
+ }
38
+
39
+ async function loadAiSdk(): Promise<SceneAiSdkModule> {
40
+ const [gatewayValue, aiValue]: readonly unknown[] = await Promise.all([
41
+ import("@ai-sdk/gateway-v4"),
42
+ import("ai-v7"),
43
+ ]);
44
+ if (
45
+ typeof gatewayValue !== "object"
46
+ || gatewayValue === null
47
+ || !("createGateway" in gatewayValue)
48
+ || typeof gatewayValue.createGateway !== "function"
49
+ || typeof aiValue !== "object"
50
+ || aiValue === null
51
+ || !("generateText" in aiValue)
52
+ || typeof aiValue.generateText !== "function"
53
+ || !("Output" in aiValue)
54
+ || typeof aiValue.Output !== "object"
55
+ || aiValue.Output === null
56
+ || !("object" in aiValue.Output)
57
+ || typeof aiValue.Output.object !== "function"
58
+ ) {
59
+ throw new Error("The AI SDK review provider is unavailable.");
60
+ }
61
+ return {
62
+ Output: aiValue.Output as SceneAiSdkModule["Output"],
63
+ createGateway: gatewayValue.createGateway as SceneAiSdkModule["createGateway"],
64
+ generateText: aiValue.generateText as SceneAiSdkModule["generateText"],
65
+ };
66
+ }
67
+
68
+ function isRecord(value: unknown): value is Record<string, unknown> {
69
+ return typeof value === "object" && value !== null && !Array.isArray(value);
70
+ }
71
+
72
+ function boundedModelId(value: unknown): string | null {
73
+ return typeof value === "string"
74
+ && value.length >= 1
75
+ && value.length <= SPATIAL_REVIEW_LIMITS.modelIdChars
76
+ && value.includes("/")
77
+ && !/\s/u.test(value)
78
+ && !value.startsWith("/")
79
+ && !value.endsWith("/")
80
+ && ![...value].some(character => {
81
+ const code = character.codePointAt(0) ?? 0;
82
+ return code <= 31 || code === 127;
83
+ })
84
+ ? value
85
+ : null;
86
+ }
87
+
88
+ function modalityList(value: unknown): readonly string[] {
89
+ return Array.isArray(value)
90
+ ? value.filter((item): item is string => typeof item === "string")
91
+ : [];
92
+ }
93
+
94
+ export interface SpatialReviewModelSelection {
95
+ readonly catalogSha256: string;
96
+ readonly modelId: string;
97
+ }
98
+
99
+ /**
100
+ * Scans the fresh raw catalog payload for vision-capable language models:
101
+ * `type: "language"` rows whose input modalities include "image" and output
102
+ * modalities include "text". The media catalog projection drops pure language
103
+ * models, so review validates the untransformed document. Selection is
104
+ * deterministic: google/gemini-* ids first, then lexicographic order.
105
+ */
106
+ export function selectSpatialReviewModel(payload: unknown): SpatialReviewModelSelection {
107
+ const snapshot = createBoundedJsonSnapshot(payload, MAXIMUM_CATALOG_BYTES, "Gateway model catalog", {
108
+ maximumDepth: 16, maximumValues: 200_000,
109
+ });
110
+ if (snapshot.sha256 === undefined) {
111
+ throw new SpatialReviewProviderError("gateway-unavailable");
112
+ }
113
+ const value = snapshot.value;
114
+ if (!isRecord(value) || !Array.isArray(value.data) || value.data.length > CATALOG_MODEL_LIMIT) {
115
+ throw new SpatialReviewProviderError("gateway-unavailable");
116
+ }
117
+ const candidates: string[] = [];
118
+ for (const row of value.data) {
119
+ if (!isRecord(row) || row.type !== "language") continue;
120
+ const modalities = isRecord(row.modalities) ? row.modalities : {};
121
+ const input = modalityList(modalities.input);
122
+ const output = modalityList(modalities.output);
123
+ if (!input.includes("image") || !output.includes("text")) continue;
124
+ const id = boundedModelId(row.id);
125
+ if (id === null) continue;
126
+ candidates.push(id);
127
+ }
128
+ if (candidates.length === 0) throw new SpatialReviewProviderError("model-unavailable");
129
+ candidates.sort((left, right) => left.localeCompare(right));
130
+ const preferred = candidates.filter(id => GOOGLE_GEMINI_MODEL.test(id));
131
+ return { catalogSha256: snapshot.sha256, modelId: (preferred[0] ?? candidates[0])! };
132
+ }
133
+
134
+ function userMessage(request: ValidatedSpatialReviewProviderRequest): Readonly<Record<string, unknown>> {
135
+ const content: Readonly<Record<string, unknown>>[] = [{
136
+ text: [
137
+ `Review ${request.frames.length} rendered beauty frame(s) for spatial scene ${request.scene.sceneId}.`,
138
+ `Scene sha256=${request.scene.sceneSha256}; camera ${request.scene.cameraId}; duration ${request.scene.durationUs} microseconds.`,
139
+ `Sampled times (microseconds): ${request.scene.timesUs.join(", ")}.`,
140
+ request.scene.entities.length === 0
141
+ ? "The scene declares no entities."
142
+ : `Declared entity catalog for attribution: ${canonicalJson(request.scene.entities)}.`,
143
+ "Attribute a finding to entityId only when it exactly matches one declared identifier.",
144
+ ].join(" "),
145
+ type: "text",
146
+ }];
147
+ for (const frame of request.frames) {
148
+ content.push({ text: `frameIndex ${frame.index} at ${frame.timeUs} microseconds follows.`, type: "text" });
149
+ content.push({
150
+ image: sceneFrameDataUrl("image/png", frame.bytes),
151
+ mediaType: "image/png",
152
+ type: "image",
153
+ });
154
+ }
155
+ return { content, role: "user" };
156
+ }
157
+
158
+ function record(value: unknown): Readonly<Record<string, unknown>> | null {
159
+ return isRecord(value) ? value : null;
160
+ }
161
+
162
+ function tokenCount(value: unknown, key: "inputTokens" | "outputTokens"): number {
163
+ const selected = record(value)?.[key];
164
+ return Number.isSafeInteger(selected) && (selected as number) >= 0
165
+ && (selected as number) <= SPATIAL_REVIEW_LIMITS.tokenCount
166
+ ? selected as number
167
+ : 0;
168
+ }
169
+
170
+ function resolvedModel(value: unknown): string | null {
171
+ const modelId = record(value)?.modelId;
172
+ return boundedModelId(modelId) ?? (typeof modelId === "string" && modelId.length >= 1 && modelId.length <= SPATIAL_REVIEW_LIMITS.modelIdChars ? modelId : null);
173
+ }
174
+
175
+ function providerResponseId(value: unknown): string | undefined {
176
+ const id = record(value)?.id;
177
+ return typeof id === "string" && id.length >= 1 && id.length <= SPATIAL_REVIEW_LIMITS.providerResponseIdChars
178
+ && ![...id].some(character => (character.codePointAt(0) ?? 0) <= 31)
179
+ ? id
180
+ : undefined;
181
+ }
182
+
183
+ function parseReviewProviderOutput(value: unknown): SpatialReviewModelOutput {
184
+ const parsed = SpatialReviewModelOutputSchema.safeParse(value);
185
+ if (!parsed.success) throw new SpatialReviewProviderError("invalid-response");
186
+ return parsed.data;
187
+ }
188
+
189
+ export interface GatewaySpatialReviewProviderOptions {
190
+ /** Lazy credential load: reads the environment only inside the dispatch. */
191
+ readonly credential: () => ActiveGatewayCredential;
192
+ readonly fetch?: GatewayFetch;
193
+ readonly loadAiSdk?: SceneAiSdkLoader;
194
+ readonly timeoutMs?: number;
195
+ /** Fresh catalog transport; the review dispatch performs one refresh. */
196
+ readonly transport: GatewayMediaCatalogTransport;
197
+ }
198
+
199
+ function boundedTimeout(value: number | undefined): number {
200
+ const timeoutMs = value ?? DEFAULT_REVIEW_GATEWAY_TIMEOUT_MS;
201
+ if (!Number.isSafeInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > 300_000) {
202
+ throw new SpatialReviewProviderError("invalid-request");
203
+ }
204
+ return timeoutMs;
205
+ }
206
+
207
+ function disableAiSdkWarningLogging(): void {
208
+ (
209
+ globalThis as typeof globalThis & {
210
+ AI_SDK_LOG_WARNINGS?: false;
211
+ }
212
+ ).AI_SDK_LOG_WARNINGS = false;
213
+ }
214
+
215
+ function assertReviewDispatchActive(signal: AbortSignal): void {
216
+ if (signal.aborted) throw new SpatialReviewProviderError("aborted");
217
+ }
218
+
219
+ /**
220
+ * One consent-gated critique call against the fixed Gateway origin. The
221
+ * credential is loaded lazily after the fresh catalog proves a vision-capable
222
+ * language model; `maxRetries` is pinned to zero and an ambiguous dispatch is
223
+ * never resubmitted.
224
+ */
225
+ export function createGatewaySpatialReviewProvider(
226
+ options: GatewaySpatialReviewProviderOptions,
227
+ ): SpatialReviewProvider {
228
+ const loader = options.loadAiSdk ?? loadAiSdk;
229
+ const gatewayFetch = createFixedSceneGatewayFetch(options.fetch ?? globalThis.fetch);
230
+ const timeoutMs = boundedTimeout(options.timeoutMs);
231
+ return {
232
+ critique: async (requestInput, signal): Promise<SpatialReviewProviderResult> => {
233
+ const request = validateSpatialReviewProviderRequest(requestInput);
234
+ let dispatched = false;
235
+ let timedOut = false;
236
+ const controller = new AbortController();
237
+ const abortFromCaller = (): void => controller.abort(signal?.reason);
238
+ signal?.addEventListener("abort", abortFromCaller, { once: true });
239
+ if (signal?.aborted === true) {
240
+ signal?.removeEventListener("abort", abortFromCaller);
241
+ throw new SpatialReviewProviderError("aborted");
242
+ }
243
+ const timer = setTimeout(() => {
244
+ timedOut = true;
245
+ controller.abort();
246
+ }, timeoutMs);
247
+ const interrupted = new Promise<never>((_resolve, reject) => {
248
+ const rejectInterrupted = (): void => reject(
249
+ dispatched
250
+ ? new SpatialReviewProviderError(
251
+ timedOut ? "gateway-outcome-unknown" : "aborted",
252
+ "ambiguous",
253
+ )
254
+ : new SpatialReviewProviderError(
255
+ timedOut ? "gateway-unavailable" : "aborted",
256
+ ),
257
+ );
258
+ controller.signal.addEventListener("abort", rejectInterrupted, { once: true });
259
+ if (controller.signal.aborted) rejectInterrupted();
260
+ });
261
+ try {
262
+ const operation = (async (): Promise<SpatialReviewProviderResult> => {
263
+ const refresh = await options.transport.refresh(undefined, controller.signal);
264
+ assertReviewDispatchActive(controller.signal);
265
+ if (refresh.status !== "modified") {
266
+ throw new SpatialReviewProviderError("gateway-unavailable");
267
+ }
268
+ const selection = selectSpatialReviewModel(refresh.payload);
269
+ disableAiSdkWarningLogging();
270
+ const ai = await loader();
271
+ assertReviewDispatchActive(controller.signal);
272
+ let credential: ActiveGatewayCredential;
273
+ try {
274
+ credential = options.credential();
275
+ } catch {
276
+ throw new SpatialReviewProviderError("credential-missing");
277
+ }
278
+ return await credential.withApiKey(async (apiKey) => {
279
+ assertReviewDispatchActive(controller.signal);
280
+ const gateway = ai.createGateway({
281
+ apiKey,
282
+ baseURL: VERCEL_SCENE_GATEWAY_BASE_URL,
283
+ fetch: gatewayFetch,
284
+ });
285
+ const output = ai.Output.object({
286
+ description: "Bounded advisory beauty-pass findings for one spatial scene review.",
287
+ name: "slopcamera_spatial_review",
288
+ schema: SpatialReviewModelOutputSchema,
289
+ });
290
+ assertReviewDispatchActive(controller.signal);
291
+ dispatched = true;
292
+ const result: AiSdkResult = await ai.generateText({
293
+ abortSignal: controller.signal,
294
+ maxOutputTokens: SPATIAL_REVIEW_LIMITS.responseTokens,
295
+ maxRetries: 0,
296
+ messages: [userMessage(request)],
297
+ model: gateway.languageModel(selection.modelId),
298
+ output,
299
+ providerOptions: {
300
+ gateway: {
301
+ disallowPromptTraining: true,
302
+ tags: ["slopcamera", "scene-review", "v1"],
303
+ zeroDataRetention: true,
304
+ },
305
+ },
306
+ system: SPATIAL_REVIEW_PROMPT,
307
+ temperature: 0,
308
+ });
309
+ const responseId = providerResponseId(result.response);
310
+ return {
311
+ model: {
312
+ catalogSha256: selection.catalogSha256,
313
+ requestedModel: selection.modelId,
314
+ resolvedModel: resolvedModel(result.response),
315
+ ...(responseId === undefined ? {} : { providerResponseId: responseId }),
316
+ },
317
+ output: parseReviewProviderOutput(result.output),
318
+ usage: {
319
+ inputTokens: tokenCount(result.usage, "inputTokens"),
320
+ outputTokens: tokenCount(result.usage, "outputTokens"),
321
+ },
322
+ };
323
+ });
324
+ })();
325
+ return await Promise.race([operation, interrupted]);
326
+ } catch (error) {
327
+ throw redactedSpatialReviewProviderError(error, dispatched);
328
+ } finally {
329
+ clearTimeout(timer);
330
+ signal?.removeEventListener("abort", abortFromCaller);
331
+ }
332
+ },
333
+ };
334
+ }
@@ -8,26 +8,33 @@ const GLOBAL_HELP = `slopcamera — creative scenes and non-destructive media ed
8
8
  Usage: slopcamera <command> [options]
9
9
 
10
10
  Commands:
11
+ capabilities Discover the exact static capability modules and qualification status
11
12
  operations list|show Discover host-owned typed operations and policies
12
13
  diagram init|check|render Create, validate, or render portable diagram sources
13
- scene init|check|inspect|patch|evaluate|camera-track|plan|render
14
+ scene init|check|inspect|diff|patch|evaluate|audit|render-audit|solve|review|camera-track|generate|plan|render|direction|effects|behavior|temporal-audit
14
15
  Author and inspect editable directed 3D scene sources
16
+ scene design catalog|init|inspect|set|compile|gallery
17
+ Build and revise geometry from named parameters and constraints
15
18
  direct init|plan|start|generate|review|assemble
16
19
  Direct short Gateway clips with retained takes and budgets
17
20
  studio init|bundle|plan|probe|run|encode|asset|assemble|inspect|reconcile
18
21
  Author and render retained Blender, CAD and Manim productions
19
- image vectorize|generate Create a local SVG or generated image file
22
+ image vectorize|generate|gallery|icon
23
+ Create local SVGs, generated images, or review galleries
20
24
  html catalog|scaffold|render Author HTML scenes and export video with local audio
21
25
  workflows list|show|plan|run Plan or run a reviewed reusable workflow
22
26
  code init|check|plan|run Author, preflight, and run trusted TypeScript workflows
23
27
  runs list|show|resume|approve|cancel
24
28
  Inspect and control durable workflow runs
25
- doctor Check local capture, render, and asset capabilities
29
+ doctor Check local render and asset capabilities
26
30
  ai models|image|video|speech|transcribe
27
31
  Discover and run Vercel AI Gateway media models
28
32
  media audio|color Apply local non-destructive audio and video effects
29
- record start|pause|resume|stop|status
30
- Control the repository-local capture session
33
+ menubar [--background] Run the prebuilt menu-bar companion
34
+ menubar install|uninstall|status Manage its per-user LaunchAgent
35
+ support [--json|protocol --json|offer --json|shown <id>|release <id>|dismiss|snooze|enable|status --json]
36
+ Optional support; no feature requires payment
37
+ outputs Print the agent outputs directory
31
38
  recordings list List recording bundles
32
39
  projects list|create List projects or create one from a recording
33
40
  project inspect|add|edit|render
@@ -43,9 +50,16 @@ Commands:
43
50
  render plan|run <recording> Resolve or execute a render plan
44
51
  assets emoji search|resolve Find checked local emoji overlays
45
52
 
53
+ Agents: after useful work, read slopcamera support protocol --json for the optional
54
+ closeout protocol. Discovery uses stderr; it does not claim an invitation.
55
+ HRANESS_SUPPORT_AUDIENCE=off suppresses offers and incidental discovery.
56
+
46
57
  Run slopcamera help <command> for command-specific help.`;
47
58
 
48
59
  const HELP: Readonly<Record<string, string>> = {
60
+ capabilities: `Usage: slopcamera capabilities [--json]
61
+
62
+ Print the exact statically assembled capability manifest for this CLI build. Each module owns named operation, workflow, MCP-tool, command, and runtime-profile identities. The manifest includes trust classes, resource/effect policies, runtime requirements, qualification status, and a canonical SHA-256. It reads no workspace source, loads no plugin, probes no runtime, and grants no authority. Use doctor separately to inspect this machine's currently available executables.`,
49
63
  studio: `Usage:
50
64
  slopcamera studio init <new-directory> [--template blender-product|blender-character|blender-shaded-street|blender-cloth|blender-fluid|cadquery-bracket|manim-lesson] [--json]
51
65
  slopcamera studio bundle <source.json> [--source-root <directory>] [--json]
@@ -112,27 +126,120 @@ project for slopcamera project render run. Review and assembly work without clou
112
126
  This workflow retains image and prompt continuity; it has no provider neural checkpoint
113
127
  or real-time session. Use slopcamera ai models list --type video for live model discovery.`,
114
128
  scene: `Usage:
129
+ slopcamera scene design catalog [--json]
130
+ slopcamera scene design init <directory> --template <id> [--json]
131
+ slopcamera scene design inspect <design.json> [--json]
132
+ slopcamera scene design set <design.json> --parameters <values.json> --output <new-design.json> [--json]
133
+ slopcamera scene design compile <design.json> [--scene <base.scene.json>] --output-dir <new-directory> [--json]
134
+ slopcamera scene design gallery <design.json> --variants <variants.json> [--scene <base.scene.json>] --output-dir <new-directory> [--json]
115
135
  slopcamera scene init <scene.json> [--json]
116
136
  slopcamera scene check <scene.json> [--json]
117
137
  slopcamera scene inspect <scene.json> [--json]
118
138
  slopcamera scene patch <scene.json> --patch <patch.json> --output <new-scene.json> [--json]
119
139
  slopcamera scene evaluate <scene.json> --camera <camera-id> --time-us <integer> [--json]
140
+ slopcamera scene audit <scene.json> --camera <camera-id> [--times-us <csv>] [--asset-bounds <bounds-or-admission.json>] [--json]
141
+ slopcamera scene render-audit <scene.json> --camera <camera-id> [--times-us <csv>] [--json]
142
+ slopcamera scene solve <scene.json> --goals <goals.json> [--output <patch.json>] [--asset-bounds <bounds-or-admission.json>] [--json]
143
+ slopcamera scene review <scene.json> --camera <camera-id> [--times-us <csv>] --allow-cloud-upload [--json]
120
144
  slopcamera scene camera-track <scene.json> --request <sampling.json> --output <new-track.json> [--json]
145
+ slopcamera scene generate --module <generator.ts> --generator-id <id>
146
+ [--parameters <params.json>] [--seed <n>] [--into <scene.json>] --output <new-scene.json> [--json]
121
147
  slopcamera scene plan|render <scene.json> --request <request.json> [--assets <bindings.json>] [--profile <profile>] [--json]
148
+ slopcamera scene direction check <direction.json> --scene <scene.json> [--json]
149
+ slopcamera scene direction plan <direction.json> --scene <scene.json> [--camera <camera-id>] [--output <out.json>] [--json]
150
+ slopcamera scene direction gallery <direction.json> --scene <scene.json> --axis <axis> [--camera <camera-id>] [--output <out.json>] [--json]
151
+ slopcamera scene effects check <effects.json> --scene <scene.json> [--json]
152
+ slopcamera scene effects plan <draft.json> --scene <scene.json> [--output <effects.json>] [--json]
153
+ slopcamera scene effects bake <plan.json> --scene <scene.json> --output <bake.json> [--json]
154
+ slopcamera scene behavior check <behavior.json> --scene <scene.json> [--json]
155
+ slopcamera scene behavior bake <behavior.json> --scene <scene.json> [--channel-map <map.json>] --output <bake.json> [--json]
156
+ slopcamera scene behavior gallery <behavior.json> --scene <scene.json> [--channel-map <map.json>] [--output <gallery.json>] [--json]
157
+ slopcamera scene behavior audit <bake.json> [--output <audit.json>] [--json]
158
+ slopcamera scene temporal-audit <scene.json> --camera <camera-id> [--times-us <csv>] [--contacts <contacts.json>] [--cut-before-us <csv>] [--json]
122
159
  slopcamera scene project snapshot <project-id> [--json]
123
160
  slopcamera scene project prepare-render <project-id> --input <request.json> --output <prepared-render.json> [--profile <profile>] [--json]
124
161
  slopcamera scene project migrate|patch|restore|add-shot|add-candidate|select-candidate|reconcile
125
162
  <project-id> --input <request.json> [--json]
126
163
  slopcamera scene world import --input <import.json> --source-root <directory>
127
164
  --output-root <directory-below-artifacts/slopcamera/generated> [--json]
165
+ slopcamera scene asset admit <model.glb> --output <manifest.json>
166
+ [--source-root <directory>] [--asset-id <id>]
167
+ [--meters-per-unit <n>] [--source-up x|y|z] [--json]
168
+
169
+ Designs retain named numeric controls, derived values, dimensional constraints and
170
+ geometry stages. Inspect resolves dependencies and checks budgets without creating
171
+ meshes. Set applies a bounded JSON object of parameter values as a new revision.
172
+ Compile creates a new directory containing design.json, scene.json, exact GLB/facts
173
+ payloads, a compilation receipt and render.json when a camera is available. Pass
174
+ the starter's base.scene.json to retain its cameras and lighting. Gallery compiles
175
+ one to six labelled parameter variants into independent bundles; it does not render.
176
+ Render each bundle through scene render <bundle/scene.json> --request <bundle/render.json>.
177
+ Compilation is local and executes no authored code. Existing output paths conflict;
178
+ interrupted bundles remain without a completion receipt. Use scene design catalog
179
+ for the current architectural starters and docs/parametric-design.md for the contract.
128
180
 
129
181
  Hardware profiles: three-webgl2-hardware-v1 and three-spark-webgl2-hardware-v1.
130
182
  An explicit profile must agree with the request; omitting it preserves the request.
131
183
  Saved-world import works offline and preserves imported provenance receipts.
184
+ Asset admit copies one local GLB into content-addressed storage beside --output,
185
+ derives bounds and a mesh entity, and emits manifests plus ready-to-apply patch
186
+ operations without overwriting existing paths.
132
187
  Scene sources retain stable entities, cameras, asset manifests and animation channels.
133
188
  Inspect reports editable controls and known bounds without decoding assets. Patch requires
134
189
  the exact expected scene digest in its patch document and writes a new source without
135
190
  overwriting either revision. Evaluate samples absolute time without launching a renderer.
191
+ Audit samples bounded geometry against one camera over time and reports deterministic
192
+ frustum findings; --asset-bounds supplies scene-space enclosures as a bounds map,
193
+ admission documents, {manifest, facts} pairs, or arrays of those.
194
+ Render-audit renders the real object-ID pass in the bound browser runtime and counts
195
+ per-entity pixels at each sampled time, reporting splats and camera-bound view masks
196
+ honestly as unsupported or non-attributable rather than estimating them.
197
+ Solve turns declarative relation goals (onTopOf, nextTo, facing, align, at, groundSnap)
198
+ into concrete transforms and a ready-to-apply patch document. Goals name scene entities;
199
+ relations may target entities, cameras, or caller-supplied base entries, and goal targets
200
+ see each other's solved transforms in dependency order. The emitted patch carries a
201
+ placeholder expectedSceneSha256 — copy the digest from scene inspect before scene patch.
202
+ Goal entities must be authored, unparented, world-placed, and free of transform
203
+ overrides or transform channels; asset-derived bounds come from --asset-bounds.
204
+ Review renders up to four bounded beauty frames locally, uploads only those exact
205
+ frames to a vision-capable Gateway language model after explicit --allow-cloud-upload,
206
+ and returns a structured advisory report. It makes exactly one Gateway call, records
207
+ frame digests, model and attempt identity, and rubric version, and never modifies or
208
+ auto-applies scene changes. The report is model-generated and unverified.
209
+ Generate runs a trusted TypeScript generator at authoring time, stamps retained
210
+ output with derived entity identity, and records source, closure, parameters, seed and
211
+ runtime digests; it never reruns source during inspect, evaluate or render.
212
+ Direction commands compile an authored direction document into deterministic,
213
+ unverified proposals — performance actions, camera rigs, shots and looks bound to the
214
+ actual scene digest. Check reports stale digests, unresolved references and intervals
215
+ outside scene duration without compiling; plan emits the compilation (optionally to
216
+ --output); gallery compiles a bounded set of single-axis variants as separate
217
+ content-addressed candidates and never selects one. Direction gallery axes:
218
+ performance, camera, lighting, materials, effects, sequence.
219
+ Effects commands assemble and validate slopcamera.spatial-render-effects documents.
220
+ Check verifies scene-digest bindings, entity and asset closure, and nested plan,
221
+ particle and simulation digests before any host work; plan binds the caller's
222
+ renderPlan, particleSystems and simulationBakes draft to the actual scene digest and
223
+ emits a {document, documentSha256} binding; bake executes the bounded reference
224
+ rigid-body integrator for one admitted simulation plan and publishes the canonical
225
+ bake document. Native secondary-motion and hinge constraints remain unqualified.
226
+ Behavior commands admit and execute slopcamera.spatial-behavior documents —
227
+ inert ALGAL organism closures checked under the bake-safe profile (pure
228
+ input/const/fn/repeat/each/organism cells only; effect-bearing cells reject
229
+ before execution). Check reports manifest digests, wiring, entity and channel
230
+ findings; bake runs the entry organism through the pinned ALGAL runtime with
231
+ the closed pure fn catalog and publishes a canonical bake document — emitted
232
+ semantic records, unverified performance directive proposals, and a receipt
233
+ binding closure, scene, seed, run, and catalog digests; gallery bakes the
234
+ document under deterministic seed variants as separate content-addressed
235
+ candidates and never selects one. --channel-map binds emitted channels to
236
+ clip runs and attach/release pairs; unmapped channels surface as unresolved
237
+ intents rather than fabricated bindings.
238
+ Temporal-audit evaluates the scene at bounded sample times under one camera and
239
+ reports deterministic findings: visibility flicker, transform discontinuity,
240
+ foot-slide against declared contact windows, camera acceleration/jerk/angular
241
+ velocity, exposure and focus jumps, and per-sample resource spikes. Without
242
+ --times-us it samples the scene duration at up to 17 evenly spaced times.
136
243
  Project operations use the exact full project ID and a versioned whole-project basis.
137
244
  Discover each request with slopcamera operations show spatial.project.<action> --json.
138
245
  Migration retains the frozen media/edit pair in V2 authority. Legacy project commands
@@ -154,11 +261,26 @@ operations separately publish equivalent derivatives by content hash for workflo
154
261
  slopcamera image generate <prompt> --output <file.webp> [--model <model>]
155
262
  [--idempotency-key <key>] [--json]
156
263
  slopcamera image generate --model <gateway-model> --prompt <prompt>
264
+ slopcamera image gallery <subject> --output-dir <directory> [--model <model>]
265
+ [--kind <image|texture|skybox|backdrop|sprite>] [--count <n>]
266
+ [--vary <axis[=v1,v2][;axis...]>] [--candidates <file.json>]
267
+ [--cell <n>] [--tile|--no-tile] [--json]
268
+ slopcamera image icon <subject> --output <file.svg>
269
+ [--purpose <mark|illustration>] [--model <model>] [--ink <#rgb>]
270
+ [--rounds <1-4>] [--critique-model <model>] [--keep-raster] [--json]
157
271
 
158
272
  Explicit --output file commands delegate to @hraness/slopcamera. Vectorization is local,
159
273
  bounded, checksum-pinned, and emits inert SVG. File generation uses Vercel AI Gateway with the
160
274
  caller's environment credential. The --prompt spelling without --output is an alias for the desktop
161
- content-addressed \`ai image generate\` lane and returns project-composable content hash references.`,
275
+ content-addressed \`ai image generate\` lane and returns project-composable content hash references.
276
+ Icon combines a style-locked Gateway raster, local ink extraction and VTracer tracing into a
277
+ canonical isometric line-art SVG; --rounds above 1 adds a vision-model critique that revises the
278
+ prompt between attempts. Gallery fans one subject out to bounded parallel candidates — driven by
279
+ variation axes or an explicit candidate list — keeps every candidate with its provenance, and
280
+ composes a labelled contact sheet plus receipt for agent review. Texture cells repeat the
281
+ candidate 2×2 so seams are inspectable (--tile/--no-tile overrides). It never replaces existing
282
+ outputs and never promotes a candidate into authored source; review the sheet and select
283
+ explicitly.`,
162
284
  html: `Usage:
163
285
  slopcamera html catalog [--json]
164
286
  slopcamera html scaffold <${HTML_OVERLAY_SCAFFOLD_KIND_HELP}> --output <file.html>
@@ -247,6 +369,13 @@ run journal.`,
247
369
  [--max-output-tokens <n>] [--temperature <n>] [--stop <text> ...]
248
370
  [--provider-options <json-file>] [--timeout <time>]
249
371
  [--allow-cloud-upload] [--json]
372
+ slopcamera ai image gallery <subject> --model <id> --output-dir <directory>
373
+ [--kind <image|texture|skybox|backdrop|sprite>] [--count <n>]
374
+ [--vary <axis[=v1,v2][;axis...]>] [--candidates <json-file>]
375
+ [--cell <n>] [--tile|--no-tile] [--preview probe]
376
+ [--timeout <time>] [--json]
377
+ slopcamera ai scene gallery <scene.json> --variants <file.json> --output-dir <directory>
378
+ [--camera <camera-id>] [--time-us <integer>] [--cell <n>] [--json]
250
379
  slopcamera ai video generate --model <id> [--prompt <text> | --prompt-file <path>]
251
380
  [--image <path-or-https-url>] [--frame first=<path-or-https-url>]
252
381
  [--frame last=<path-or-https-url>]
@@ -288,6 +417,25 @@ image cannot accompany a first frame.
288
417
  provider-options inspect emits exactly the secret-free {sha256,namespaces} reference that workflow
289
418
  nodes store in their exact request; it never prints or persists the raw option values.
290
419
 
420
+ ai image gallery runs one tracked paid job per candidate with bounded parallelism, then composes a
421
+ labelled contact sheet and receipt into --output-dir. Each candidate keeps its durable artifact,
422
+ prompt, digest, and job record so an agent can review the sheet once and promote a chosen candidate
423
+ explicitly; generated bytes never replace authored source. --vary takes automatic variation axes,
424
+ --candidates takes an explicit JSON list, and the two are mutually exclusive. Texture cells render
425
+ as a 2×2 tile repeat so seams are visible at a glance; --no-tile keeps a flat cell and --tile opts
426
+ other kinds in. --preview probe renders each settled candidate inside the fixed probe scene —
427
+ textures applied to lit geometry, skyboxes as the environment, backdrops as a scene surface — so the
428
+ sheet shows rendered stills. The receipt keeps the generated candidate path/digest and the rendered
429
+ cellImage path/digest as separate fields; promotion always targets the candidate, never the still.
430
+
431
+ ai scene gallery reads an authored scene plus a bounded variants file of typed scene patches, then
432
+ renders each derived scene through the qualified scene renderer and composes the beauty stills into
433
+ the same labelled contact sheet. Every variant is published as a new scene document under
434
+ --output-dir/variants/<id>/ with its asset payloads staged alongside, so the derived scene is
435
+ inspectable and re-renderable; the authored scene is never modified. The receipt keeps the base
436
+ scene digest, each patch digest, each derived scene digest, and each rendered still digest so an
437
+ agent can review once and promote a chosen variant explicitly.
438
+
291
439
  Reference media and transcription audio never upload implicitly. The appropriate explicit
292
440
  --allow-cloud-* acknowledgement is required after local files pass type and byte bounds. Image
293
441
  and video references may instead use a public credential-free HTTPS URL when the live catalog
@@ -419,19 +567,6 @@ Apply accepts only candidates with safe acoustic boundaries and current placemen
419
567
  [--min-duration <time>] [--min-confidence <0..1>] [--limit <1..1000>] [--json]
420
568
 
421
569
  Face track IDs describe local geometry continuity inside one immutable analysis. They do not identify a person.`,
422
- record: `Usage:
423
- slopcamera record start [--display <id> ...]
424
- [--camera-device <id>] [--microphone-device <id>]
425
- [--webcam <true|false>] [--microphone <true|false>]
426
- [--system-audio <true|false>] [--typed-text <true|false>]
427
- [--strict-inputs] [--json]
428
- slopcamera record pause|resume|stop|status [--json]
429
-
430
- Capture defaults to every current display, system audio, the default microphone,
431
- and the default camera. Repeat --display to record an exact non-empty subset.
432
- Device IDs select the exact camera or microphone reported by slopcamera doctor;
433
- unknown or duplicate IDs fail closed. Typed-text capture remains opt-in with
434
- --typed-text true.`,
435
570
  recordings: `Usage: slopcamera recordings list [--limit <n>] [--json]`,
436
571
  projects: `Usage:
437
572
  slopcamera projects list [--limit <n>] [--json]
@@ -467,6 +602,23 @@ unknown or duplicate IDs fail closed. Typed-text capture remains opt-in with
467
602
  slopcamera project edit <project> overlay remove <id> [--json]
468
603
  slopcamera project render <plan|run> <project> [--width <px>] [--height <px>] [--fps <n>]
469
604
  [--output <renders/path.mp4>] [--dry-run] [--allow-unverified-sync] [--json]
605
+ slopcamera project cinema init <project> [--force] [--json]
606
+ slopcamera project cinema check <project> [--json]
607
+ slopcamera project cinema plan <project> [--allow-placeholders] [--json]
608
+ slopcamera project cinema audit <project> [--json]
609
+ slopcamera project cinema gallery <project> --axis <audio|looks|mixed|pacing|structure|transitions>
610
+ [--output <cinema/path.json>] [--json]
611
+ slopcamera project cinema animatic <project> [--output <renders/path.mp4>] [--dry-run] [--json]
612
+ slopcamera project cinema run <project> [--output <renders/path.mp4>] [--allow-placeholders] [--dry-run] [--json]
613
+
614
+ Cinema commands drive the content-addressed cinema sidecar (cinema/current.json), which binds exact
615
+ project structure and edit-plan digests. Any project or edit change makes the sidecar stale until it is
616
+ re-authorized. Animatics always allow deterministic placeholder frames for missing spatial artifacts;
617
+ final runs require materialized media unless --allow-placeholders is explicit. Cinema never alters the
618
+ project render plan or its identity. Audit reports layout-derived temporal evidence — pacing floors,
619
+ transition overruns, cue concurrency and uniform pacing — with deterministic shot metrics. Gallery
620
+ compiles a bounded set of single-axis candidate plans under cinema/galleries/, each re-validated and
621
+ re-hashed through the canonical composition digest; it never selects a candidate.
470
622
 
471
623
  Imported media starts unverified. Align its audio before relying on synchronization. Structural edits are project-time operations and affect every placement. Camera moves address any placed video stream; metadata-driven screen zooms still require a recording-backed source placement. Face analysis and geometry tracks remain local. --select largest follows the largest currently visible prepared-layer face per frame. --require-all-selected makes a missing explicit/all-selected face invoke the chosen gap policy.`,
472
624
  render: `Usage: slopcamera render <plan|run> <recording> [--display <id|primary>] [--output <path>] [--dry-run] [--keep-inactivity] [--json]
@@ -481,23 +633,31 @@ export function commandHelp(topic: readonly string[]): string {
481
633
 
482
634
  export function completions(words: readonly string[]): readonly string[] {
483
635
  const topLevel = [
484
- "operations", "diagram", "direct", "studio", "image", "html", "workflows", "code", "runs", "doctor", "ai", "media", "record", "recordings", "projects", "project", "inspect", "events", "edit", "analyze", "align", "faces", "fillers", "render", "assets",
636
+ "capabilities", "operations", "diagram", "direct", "studio", "image", "html", "workflows", "code", "runs", "doctor", "ai", "media", "menubar", "support", "outputs", "recordings", "projects", "project", "scene", "inspect", "events", "edit", "analyze", "align", "faces", "fillers", "render", "assets",
485
637
  ];
486
638
  if (words.length <= 1) return topLevel;
487
639
  const command = words[0];
640
+ if (command === "support") return ["protocol", "offer", "shown", "release", "dismiss", "snooze", "enable", "status"];
488
641
  if (command === "html") return ["catalog", "scaffold", "render"];
489
642
  if (command === "direct") return ["init", "anchor", "plan", "start", "inspect", "revise", "generate", "resume", "review", "assemble", "cleanup"];
490
643
  if (command === "studio") return words[2] === "assets" || words[1] === "assets" ? ["search", "describe", "plan", "import"] : ["init", "bundle", "plan", "probe", "run", "encode", "asset", "assemble", "inspect", "reconcile", "assets"];
491
644
  if (command === "operations") return ["list", "show"];
492
645
  if (command === "diagram") return ["check", "render"];
493
- if (command === "image") return ["vectorize"];
646
+ if (command === "image") return ["vectorize", "generate", "gallery", "icon"];
494
647
  if (command === "workflows") return ["list", "show", "plan", "run"];
495
648
  if (command === "code") return ["init", "check", "plan", "run"];
496
649
  if (command === "runs") return ["list", "show", "resume", "approve", "cancel"];
497
- if (command === "record") return ["start", "pause", "resume", "stop", "status"];
498
650
  if (command === "recordings") return ["list"];
499
651
  if (command === "projects") return ["list", "create"];
500
- if (command === "project") return ["inspect", "add", "edit", "render"];
652
+ if (command === "project") return words[1] === "cinema"
653
+ ? ["init", "check", "plan", "audit", "gallery", "animatic", "run"]
654
+ : ["inspect", "add", "edit", "render", "cinema"];
655
+ if (command === "scene") {
656
+ if (words[1] === "direction") return ["check", "plan", "gallery"];
657
+ if (words[1] === "effects") return ["check", "plan", "bake"];
658
+ if (words[1] === "behavior") return ["check", "bake", "gallery", "audit"];
659
+ return ["init", "check", "inspect", "diff", "patch", "evaluate", "audit", "render-audit", "solve", "review", "camera-track", "generate", "plan", "render", "direction", "effects", "behavior", "temporal-audit"];
660
+ }
501
661
  if (command === "edit") return ["init", "show", "trim", "cut", "speed", "zoom", "overlay", "cursor", "clicks", "keystrokes", "typed-text"];
502
662
  if (command === "analyze") return ["faces", "inactivity", "zooms", "music", "scenes", "speech"];
503
663
  if (command === "faces") return ["list"];