@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
@@ -1,6 +1,10 @@
1
1
  import { CliError } from "./errors";
2
2
  import { MAX_EVENT_QUERY_LIMIT } from "./query-limits";
3
3
  import { STUDIO_TEMPLATES, type StudioTemplate } from "./studio-template-names";
4
+ import {
5
+ slopcameraGalleryKinds,
6
+ slopcameraGalleryLimits,
7
+ } from "../../../src/image-gallery";
4
8
 
5
9
  export type OverlayKind = "image" | "svg" | "gif" | "video" | "emoji";
6
10
  export type ZoomTargetKind = "rect" | "point" | "cursor" | "window" | "focused-input";
@@ -58,6 +62,12 @@ export type SpatialWorldCommand = JsonOption & {
58
62
  readonly kind: "spatial-world"; readonly action: "import";
59
63
  readonly input: string; readonly sourceRoot: string; readonly outputRoot: string;
60
64
  };
65
+ export type SpatialAssetCommand = JsonOption & {
66
+ readonly kind: "spatial-asset"; readonly action: "admit";
67
+ readonly file: string; readonly output: string;
68
+ readonly sourceRoot?: string; readonly assetId?: string;
69
+ readonly metersPerUnit: number; readonly sourceUp: "x" | "y" | "z";
70
+ };
61
71
  export type DirectingCommand = JsonOption & { readonly kind: "directing" } & (
62
72
  | { readonly action: "init"; readonly path: string }
63
73
  | { readonly action: "anchor"; readonly input: string }
@@ -69,12 +79,37 @@ export type DirectingCommand = JsonOption & { readonly kind: "directing" } & (
69
79
  | { readonly action: "resume" | "cleanup"; readonly id: string; readonly attempt: string }
70
80
  | { readonly action: "review"; readonly id: string; readonly attempt: string; readonly decision: "accepted" | "rejected"; readonly note: string }
71
81
  );
72
- export type SpatialSceneCommand = JsonOption & { readonly kind: "spatial-scene"; readonly path: string } & (
73
- | { readonly action: "init" | "inspect" }
74
- | { readonly action: "patch"; readonly patch: string; readonly output: string }
75
- | { readonly action: "evaluate"; readonly camera: string; readonly timeUs: number }
76
- | { readonly action: "camera-track"; readonly request: string; readonly output: string }
77
- | { readonly action: "plan" | "render"; readonly request: string; readonly assets?: string; readonly executionProfile?: SpatialCliExecutionProfile }
82
+ export type SpatialSceneCommand = JsonOption & { readonly kind: "spatial-scene" } & (
83
+ | { readonly action: "init" | "inspect"; readonly path: string }
84
+ | { readonly action: "diff"; readonly path: string; readonly other: string }
85
+ | { readonly action: "patch"; readonly path: string; readonly patch: string; readonly output: string }
86
+ | { readonly action: "evaluate"; readonly path: string; readonly camera: string; readonly timeUs: number }
87
+ | { readonly action: "audit"; readonly path: string; readonly camera: string; readonly timesUs?: readonly number[]; readonly assetBounds?: string }
88
+ | { readonly action: "render-audit"; readonly path: string; readonly camera: string; readonly timesUs?: readonly number[] }
89
+ | { readonly action: "solve"; readonly path: string; readonly goals: string; readonly output?: string; readonly assetBounds?: string }
90
+ | { readonly action: "review"; readonly path: string; readonly camera: string; readonly timesUs?: readonly number[]; readonly allowCloudUpload: boolean }
91
+ | { readonly action: "camera-track"; readonly path: string; readonly request: string; readonly output: string }
92
+ | { readonly action: "plan" | "render"; readonly path: string; readonly request: string; readonly assets?: string; readonly executionProfile?: SpatialCliExecutionProfile }
93
+ | { readonly action: "generate"; readonly module: string; readonly generatorId: string; readonly parameters?: string; readonly seed?: number; readonly into?: string; readonly output: string }
94
+ | { readonly action: "direction-check"; readonly direction: string; readonly scene: string }
95
+ | { readonly action: "direction-plan"; readonly direction: string; readonly scene: string; readonly camera?: string; readonly output?: string }
96
+ | { readonly action: "direction-gallery"; readonly direction: string; readonly scene: string; readonly axis: string; readonly camera?: string; readonly output?: string }
97
+ | { readonly action: "effects-check"; readonly effects: string; readonly scene: string }
98
+ | { readonly action: "effects-plan"; readonly draft: string; readonly scene: string; readonly output?: string }
99
+ | { readonly action: "effects-bake"; readonly simulation: string; readonly scene: string; readonly output: string }
100
+ | { readonly action: "behavior-check"; readonly behavior: string; readonly scene: string }
101
+ | { readonly action: "behavior-bake"; readonly behavior: string; readonly scene: string; readonly channelMap?: string; readonly output: string }
102
+ | { readonly action: "behavior-gallery"; readonly behavior: string; readonly scene: string; readonly channelMap?: string; readonly output?: string }
103
+ | { readonly action: "behavior-audit"; readonly bake: string; readonly output?: string }
104
+ | { readonly action: "temporal-audit"; readonly path: string; readonly camera: string; readonly timesUs?: readonly number[]; readonly contacts?: string; readonly cutBeforeUs?: readonly number[] }
105
+ );
106
+ export type SpatialDesignCommand = JsonOption & { readonly kind: "spatial-design" } & (
107
+ | { readonly action: "catalog" }
108
+ | { readonly action: "init"; readonly directory: string; readonly template: string }
109
+ | { readonly action: "inspect"; readonly path: string }
110
+ | { readonly action: "set"; readonly path: string; readonly parameters: string; readonly output: string }
111
+ | { readonly action: "compile"; readonly path: string; readonly scene?: string; readonly outputDir: string }
112
+ | { readonly action: "gallery"; readonly path: string; readonly scene?: string; readonly variants: string; readonly outputDir: string }
78
113
  );
79
114
  export type SpatialProjectCommand = JsonOption & {
80
115
  readonly kind: "spatial-project"; readonly project: string;
@@ -89,10 +124,13 @@ export type CliCommand =
89
124
  | StudioCommand
90
125
  | DirectingCommand
91
126
  | SpatialWorldCommand
127
+ | SpatialAssetCommand
92
128
  | SpatialSceneCommand
129
+ | SpatialDesignCommand
93
130
  | SpatialProjectCommand
94
131
  | { readonly kind: "help"; readonly topic: readonly string[] }
95
132
  | { readonly kind: "version" }
133
+ | ({ readonly kind: "capabilities" } & JsonOption)
96
134
  | ({ readonly kind: "operations-list" } & JsonOption)
97
135
  | ({ readonly kind: "operations-show"; readonly operation: string } & JsonOption)
98
136
  | ({ readonly kind: "diagram-check"; readonly path: string } & JsonOption)
@@ -194,6 +232,29 @@ export type CliCommand =
194
232
  readonly temperature: number | undefined;
195
233
  readonly timeout: string;
196
234
  } & JsonOption)
235
+ | ({
236
+ readonly candidatesFile: string | undefined;
237
+ readonly cell: number | undefined;
238
+ readonly count: number | undefined;
239
+ readonly galleryKind: string | undefined;
240
+ readonly kind: "ai-image-gallery";
241
+ readonly model: string;
242
+ readonly outputDir: string;
243
+ readonly preview: "probe" | undefined;
244
+ readonly subject: string;
245
+ readonly tiled: boolean | undefined;
246
+ readonly timeout: string;
247
+ readonly vary: string | undefined;
248
+ } & JsonOption)
249
+ | ({
250
+ readonly camera: string | undefined;
251
+ readonly cell: number | undefined;
252
+ readonly kind: "ai-scene-gallery";
253
+ readonly outputDir: string;
254
+ readonly scene: string;
255
+ readonly timeUs: number | undefined;
256
+ readonly variants: string;
257
+ } & JsonOption)
197
258
  | ({
198
259
  readonly allowCloudUpload: boolean;
199
260
  readonly aspectRatio: string | undefined;
@@ -271,6 +332,8 @@ export type CliCommand =
271
332
  readonly tint: number | undefined;
272
333
  readonly videoStreamIndex: number;
273
334
  } & JsonOption)
335
+ | ({ readonly kind: "menubar"; readonly action: "run" | "install" | "uninstall" | "status"; readonly mode: "foreground" | "background" } & JsonOption)
336
+ | ({ readonly kind: "outputs" } & JsonOption)
274
337
  | ({ readonly kind: "recordings-list"; readonly limit: number } & JsonOption)
275
338
  | ({ readonly kind: "projects-list"; readonly limit: number } & JsonOption)
276
339
  | ({
@@ -385,6 +448,16 @@ export type CliCommand =
385
448
  readonly project: string;
386
449
  readonly width: number;
387
450
  } & JsonOption)
451
+ | ({
452
+ readonly action: "init" | "check" | "plan" | "animatic" | "run" | "audit" | "gallery";
453
+ readonly allowPlaceholders: boolean;
454
+ readonly axis: string | undefined;
455
+ readonly dryRun: boolean;
456
+ readonly force: boolean;
457
+ readonly kind: "project-cinema";
458
+ readonly output: string | undefined;
459
+ readonly project: string;
460
+ } & JsonOption)
388
461
  | ({
389
462
  readonly apply: boolean;
390
463
  readonly candidate: string | undefined;
@@ -490,20 +563,6 @@ export type CliCommand =
490
563
  readonly recording: string;
491
564
  readonly to: string | undefined;
492
565
  })
493
- | ({
494
- readonly action: "start";
495
- readonly cameraDeviceId: string | undefined;
496
- readonly displays: readonly string[];
497
- readonly microphone: boolean;
498
- readonly microphoneDeviceId: string | undefined;
499
- readonly strictInputs: boolean;
500
- readonly systemAudio: boolean;
501
- readonly typedText: boolean;
502
- readonly webcam: boolean;
503
- } & JsonOption & { readonly kind: "record" })
504
- | ({ readonly action: "pause" | "resume" | "stop" | "status" } & JsonOption & {
505
- readonly kind: "record";
506
- })
507
566
  | ({
508
567
  readonly edit: EditCommand;
509
568
  readonly fps: number | undefined;
@@ -799,22 +858,6 @@ function strictBoolean(value: string | undefined, name: string, defaults: boolea
799
858
  fail(`${name} must be true or false.`);
800
859
  }
801
860
 
802
- function captureSourceId(
803
- value: string | undefined,
804
- name: string,
805
- maximumUTF8Bytes: number,
806
- ): string | undefined {
807
- if (value === undefined) return undefined;
808
- if (
809
- value.length === 0
810
- || value.includes("\0")
811
- || new TextEncoder().encode(value).byteLength > maximumUTF8Bytes
812
- ) {
813
- fail(`${name} must be a non-empty, non-NUL source ID of at most ${maximumUTF8Bytes} UTF-8 bytes.`);
814
- }
815
- return value;
816
- }
817
-
818
861
  function oneOf<T extends string>(
819
862
  value: string | undefined,
820
863
  name: string,
@@ -837,6 +880,12 @@ function exactPositionals(parsed: ParsedOptions, count: number, usage: string):
837
880
 
838
881
  const JSON_SPEC = { "--json": "flag" } as const;
839
882
 
883
+ function parseCapabilities(argv: readonly string[]): CliCommand {
884
+ const parsed = parseOptions(argv, JSON_SPEC);
885
+ exactPositionals(parsed, 0, "slopcamera capabilities [--json]");
886
+ return { json: optionFlag(parsed, "--json"), kind: "capabilities" };
887
+ }
888
+
840
889
  function parseDoctor(argv: readonly string[]): CliCommand {
841
890
  const parsed = parseOptions(argv, JSON_SPEC);
842
891
  exactPositionals(parsed, 0, "slopcamera doctor [--json]");
@@ -939,7 +988,10 @@ function parseAiProviderOptions(argv: readonly string[]): CliCommand {
939
988
  }
940
989
 
941
990
  function parseAiImage(argv: readonly string[]): CliCommand {
942
- if (argv[0] !== "generate") fail("Usage: slopcamera ai image generate --model <id> [options]");
991
+ if (argv[0] === "gallery") return parseAiImageGallery(argv.slice(1));
992
+ if (argv[0] !== "generate") {
993
+ fail("Usage: slopcamera ai image <generate|gallery> --model <id> [options]");
994
+ }
943
995
  const parsed = parseOptions(argv.slice(1), {
944
996
  ...JSON_SPEC,
945
997
  "--allow-cloud-upload": "flag",
@@ -997,6 +1049,145 @@ function parseAiImage(argv: readonly string[]): CliCommand {
997
1049
  };
998
1050
  }
999
1051
 
1052
+ function parseAiImageGallery(argv: readonly string[]): CliCommand {
1053
+ const parsed = parseOptions(argv, {
1054
+ ...JSON_SPEC,
1055
+ "--candidates": "value",
1056
+ "--cell": "value",
1057
+ "--count": "value",
1058
+ "--kind": "value",
1059
+ "--model": "value",
1060
+ "--no-tile": "flag",
1061
+ "--output-dir": "value",
1062
+ "--preview": "value",
1063
+ "--tile": "flag",
1064
+ "--timeout": "value",
1065
+ "--vary": "value",
1066
+ });
1067
+ const [subject] = exactPositionals(
1068
+ parsed,
1069
+ 1,
1070
+ "slopcamera ai image gallery <subject> --model <id> --output-dir <directory> [options]",
1071
+ );
1072
+ const model = optionString(parsed, "--model");
1073
+ if (model === undefined) fail("--model is required.");
1074
+ const outputDir = optionString(parsed, "--output-dir");
1075
+ if (outputDir === undefined || outputDir.trim() === "") {
1076
+ fail("--output-dir is required.");
1077
+ }
1078
+ const galleryKind = optionString(parsed, "--kind");
1079
+ if (
1080
+ galleryKind !== undefined
1081
+ && !(slopcameraGalleryKinds as readonly string[]).includes(galleryKind)
1082
+ ) {
1083
+ fail(`--kind must be one of: ${slopcameraGalleryKinds.join(", ")}.`);
1084
+ }
1085
+ const count = optionalStrictInteger(optionString(parsed, "--count"), "--count");
1086
+ if (count !== undefined && count > slopcameraGalleryLimits.candidates) {
1087
+ fail(`--count must be at most ${slopcameraGalleryLimits.candidates}.`);
1088
+ }
1089
+ const cell = optionalStrictInteger(optionString(parsed, "--cell"), "--cell");
1090
+ if (
1091
+ cell !== undefined
1092
+ && (
1093
+ cell < slopcameraGalleryLimits.cellEdgeMin
1094
+ || cell > slopcameraGalleryLimits.cellEdgeMax
1095
+ )
1096
+ ) {
1097
+ fail(
1098
+ `--cell must be between ${slopcameraGalleryLimits.cellEdgeMin} and ${slopcameraGalleryLimits.cellEdgeMax}.`,
1099
+ );
1100
+ }
1101
+ const candidatesFile = optionString(parsed, "--candidates");
1102
+ const vary = optionString(parsed, "--vary");
1103
+ if (candidatesFile !== undefined && (vary !== undefined || count !== undefined)) {
1104
+ fail("--candidates is mutually exclusive with --vary and --count.");
1105
+ }
1106
+ const tile = optionFlag(parsed, "--tile");
1107
+ const noTile = optionFlag(parsed, "--no-tile");
1108
+ if (tile && noTile) fail("--tile and --no-tile are mutually exclusive.");
1109
+ const preview = optionString(parsed, "--preview");
1110
+ if (preview !== undefined && preview !== "probe") {
1111
+ fail("--preview must be probe.");
1112
+ }
1113
+ if (
1114
+ preview === "probe"
1115
+ && !["texture", "skybox", "backdrop"].includes(galleryKind ?? "image")
1116
+ ) {
1117
+ fail("--preview probe requires --kind texture, skybox, or backdrop.");
1118
+ }
1119
+ return {
1120
+ candidatesFile,
1121
+ cell,
1122
+ count,
1123
+ galleryKind,
1124
+ json: optionFlag(parsed, "--json"),
1125
+ kind: "ai-image-gallery",
1126
+ model,
1127
+ outputDir,
1128
+ preview: preview as "probe" | undefined,
1129
+ subject: subject!,
1130
+ tiled: tile ? true : noTile ? false : undefined,
1131
+ timeout: optionString(parsed, "--timeout") ?? "10m",
1132
+ vary,
1133
+ };
1134
+ }
1135
+
1136
+ function parseAiScene(argv: readonly string[]): CliCommand {
1137
+ if (argv[0] !== "gallery") {
1138
+ fail("Usage: slopcamera ai scene gallery <scene.json> --variants <file.json> --output-dir <directory> [options]");
1139
+ }
1140
+ const parsed = parseOptions(argv.slice(1), {
1141
+ ...JSON_SPEC,
1142
+ "--camera": "value",
1143
+ "--cell": "value",
1144
+ "--output-dir": "value",
1145
+ "--time-us": "value",
1146
+ "--variants": "value",
1147
+ });
1148
+ const [scene] = exactPositionals(
1149
+ parsed,
1150
+ 1,
1151
+ "slopcamera ai scene gallery <scene.json> --variants <file.json> --output-dir <directory> [options]",
1152
+ );
1153
+ const variants = optionString(parsed, "--variants");
1154
+ if (variants === undefined) fail("--variants is required.");
1155
+ const outputDir = optionString(parsed, "--output-dir");
1156
+ if (outputDir === undefined || outputDir.trim() === "") {
1157
+ fail("--output-dir is required.");
1158
+ }
1159
+ const timeUsText = optionString(parsed, "--time-us");
1160
+ let timeUs: number | undefined;
1161
+ if (timeUsText !== undefined) {
1162
+ if (!/^\d+$/u.test(timeUsText) || !Number.isSafeInteger(Number(timeUsText))) {
1163
+ fail("--time-us must be a nonnegative integer microsecond time.");
1164
+ }
1165
+ timeUs = Number(timeUsText);
1166
+ }
1167
+ const cell = optionalStrictInteger(optionString(parsed, "--cell"), "--cell");
1168
+ if (
1169
+ cell !== undefined
1170
+ && (
1171
+ cell < slopcameraGalleryLimits.cellEdgeMin
1172
+ || cell > slopcameraGalleryLimits.cellEdgeMax
1173
+ )
1174
+ ) {
1175
+ fail(
1176
+ `--cell must be between ${slopcameraGalleryLimits.cellEdgeMin} and ${slopcameraGalleryLimits.cellEdgeMax}.`,
1177
+ );
1178
+ }
1179
+ return {
1180
+ camera: optionString(parsed, "--camera"),
1181
+ cell,
1182
+ json: optionFlag(parsed, "--json"),
1183
+ kind: "ai-scene-gallery",
1184
+ outputDir,
1185
+ scene: scene!,
1186
+ timeUs,
1187
+ variants,
1188
+ };
1189
+ }
1190
+
1000
1191
  function parseAiVideo(argv: readonly string[]): CliCommand {
1001
1192
  if (argv[0] !== "generate") fail("Usage: slopcamera ai video generate --model <id> [options]");
1002
1193
  const parsed = parseOptions(argv.slice(1), {
@@ -1159,11 +1350,12 @@ function parseAi(argv: readonly string[]): CliCommand {
1159
1350
  case "models": return parseAiModels(argv.slice(1));
1160
1351
  case "provider-options": return parseAiProviderOptions(argv.slice(1));
1161
1352
  case "image": return parseAiImage(argv.slice(1));
1353
+ case "scene": return parseAiScene(argv.slice(1));
1162
1354
  case "video": return parseAiVideo(argv.slice(1));
1163
1355
  case "speech": return parseAiSpeech(argv.slice(1));
1164
1356
  case "transcribe": return parseAiTranscribe(argv.slice(1));
1165
1357
  case undefined:
1166
- default: fail("Usage: slopcamera ai <models|provider-options|image|video|speech|transcribe> [options]");
1358
+ default: fail("Usage: slopcamera ai <models|provider-options|image|scene|video|speech|transcribe> [options]");
1167
1359
  }
1168
1360
  }
1169
1361
 
@@ -1770,7 +1962,65 @@ function parseProject(argv: readonly string[]): CliCommand {
1770
1962
  width: strictEvenPositiveInteger(optionString(parsed, "--width"), "--width", 1_920),
1771
1963
  };
1772
1964
  }
1773
- fail("Usage: slopcamera project <inspect|add|edit|render> ...");
1965
+ if (action === "cinema") {
1966
+ const cinemaAction = argv[1];
1967
+ if (
1968
+ cinemaAction !== "init"
1969
+ && cinemaAction !== "check"
1970
+ && cinemaAction !== "plan"
1971
+ && cinemaAction !== "animatic"
1972
+ && cinemaAction !== "run"
1973
+ && cinemaAction !== "audit"
1974
+ && cinemaAction !== "gallery"
1975
+ ) {
1976
+ fail("Usage: slopcamera project cinema <init|check|plan|animatic|run|audit|gallery> <project> [options]");
1977
+ }
1978
+ const parsed = parseOptions(argv.slice(2), {
1979
+ ...JSON_SPEC,
1980
+ "--allow-placeholders": "flag",
1981
+ "--axis": "value",
1982
+ "--dry-run": "flag",
1983
+ "--force": "flag",
1984
+ "--output": "value",
1985
+ });
1986
+ const [project] = exactPositionals(
1987
+ parsed,
1988
+ 1,
1989
+ `slopcamera project cinema ${cinemaAction} <project> [options]`,
1990
+ );
1991
+ if (optionFlag(parsed, "--dry-run") && cinemaAction !== "animatic" && cinemaAction !== "run") {
1992
+ fail("--dry-run is valid only for project cinema animatic or run.");
1993
+ }
1994
+ if (optionFlag(parsed, "--force") && cinemaAction !== "init") {
1995
+ fail("--force is valid only for project cinema init.");
1996
+ }
1997
+ const output = optionString(parsed, "--output");
1998
+ if (output !== undefined && (cinemaAction === "init" || cinemaAction === "check" || cinemaAction === "audit")) {
1999
+ fail("--output is valid only for project cinema plan, animatic, run, or gallery.");
2000
+ }
2001
+ const axis = optionString(parsed, "--axis");
2002
+ if (cinemaAction === "gallery" && axis === undefined) {
2003
+ fail("project cinema gallery requires --axis <pacing|transitions|looks|audio|structure|mixed>.");
2004
+ }
2005
+ if (axis !== undefined && cinemaAction !== "gallery") {
2006
+ fail("--axis is valid only for project cinema gallery.");
2007
+ }
2008
+ if (optionFlag(parsed, "--allow-placeholders") && (cinemaAction === "init" || cinemaAction === "check" || cinemaAction === "audit" || cinemaAction === "gallery")) {
2009
+ fail("--allow-placeholders is valid only for project cinema plan, animatic, or run.");
2010
+ }
2011
+ return {
2012
+ action: cinemaAction,
2013
+ allowPlaceholders: optionFlag(parsed, "--allow-placeholders"),
2014
+ axis,
2015
+ dryRun: optionFlag(parsed, "--dry-run"),
2016
+ force: optionFlag(parsed, "--force"),
2017
+ json: optionFlag(parsed, "--json"),
2018
+ kind: "project-cinema",
2019
+ output,
2020
+ project: project!,
2021
+ };
2022
+ }
2023
+ fail("Usage: slopcamera project <inspect|add|edit|render|cinema> ...");
1774
2024
  }
1775
2025
 
1776
2026
  function parseInspect(argv: readonly string[]): CliCommand {
@@ -1825,61 +2075,6 @@ function parseEvents(argv: readonly string[]): CliCommand {
1825
2075
  };
1826
2076
  }
1827
2077
 
1828
- function parseRecord(argv: readonly string[]): CliCommand {
1829
- const action = argv[0];
1830
- if (action === "start") {
1831
- const parsed = parseOptions(argv.slice(1), {
1832
- ...JSON_SPEC,
1833
- "--camera-device": "value",
1834
- "--display": "repeat",
1835
- "--microphone": "value",
1836
- "--microphone-device": "value",
1837
- "--strict-inputs": "flag",
1838
- "--system-audio": "value",
1839
- "--typed-text": "value",
1840
- "--webcam": "value",
1841
- });
1842
- exactPositionals(parsed, 0, "slopcamera record start [options]");
1843
- const displays = optionStrings(parsed, "--display").map((displayId) =>
1844
- captureSourceId(displayId, "--display", 64)!
1845
- );
1846
- if (displays.length > 16) fail("--display may be specified at most 16 times.");
1847
- if (new Set(displays).size !== displays.length) fail("--display IDs must be unique.");
1848
- const microphone = strictBoolean(optionString(parsed, "--microphone"), "--microphone", true);
1849
- const webcam = strictBoolean(optionString(parsed, "--webcam"), "--webcam", true);
1850
- const cameraDeviceId = captureSourceId(optionString(parsed, "--camera-device"), "--camera-device", 256);
1851
- const microphoneDeviceId = captureSourceId(
1852
- optionString(parsed, "--microphone-device"),
1853
- "--microphone-device",
1854
- 256,
1855
- );
1856
- if (!webcam && cameraDeviceId !== undefined) {
1857
- fail("--camera-device cannot be combined with --webcam false.");
1858
- }
1859
- if (!microphone && microphoneDeviceId !== undefined) {
1860
- fail("--microphone-device cannot be combined with --microphone false.");
1861
- }
1862
- return {
1863
- action,
1864
- cameraDeviceId,
1865
- displays,
1866
- json: optionFlag(parsed, "--json"),
1867
- kind: "record",
1868
- microphone,
1869
- microphoneDeviceId,
1870
- strictInputs: optionFlag(parsed, "--strict-inputs"),
1871
- systemAudio: strictBoolean(optionString(parsed, "--system-audio"), "--system-audio", true),
1872
- typedText: strictBoolean(optionString(parsed, "--typed-text"), "--typed-text", false),
1873
- webcam,
1874
- };
1875
- }
1876
- if (action === "pause" || action === "resume" || action === "stop" || action === "status") {
1877
- const parsed = parseOptions(argv.slice(1), JSON_SPEC);
1878
- exactPositionals(parsed, 0, `slopcamera record ${action} [--json]`);
1879
- return { action, json: optionFlag(parsed, "--json"), kind: "record" };
1880
- }
1881
- fail("Usage: slopcamera record <start|pause|resume|stop|status> [options]");
1882
- }
1883
2078
 
1884
2079
  const TIME_RANGE_SPEC = {
1885
2080
  "--end": "value",
@@ -3073,9 +3268,77 @@ function parseSpatialWorldArgs(argv: readonly string[]): SpatialWorldCommand {
3073
3268
  return fail("Usage: slopcamera scene world import --input <request.json> --source-root <directory> --output-root <artifact-directory>");
3074
3269
  }
3075
3270
 
3076
- function parseSpatialSceneArgs(argv: readonly string[]): SpatialSceneCommand | SpatialProjectCommand | SpatialWorldCommand {
3271
+ const SPATIAL_ASSET_ADMIT_USAGE = "slopcamera scene asset admit <model.glb> --output <manifest.json> [--source-root <directory>] [--asset-id <id>] [--meters-per-unit <n>] [--source-up x|y|z] [--json]";
3272
+
3273
+ function parseSpatialAssetArgs(argv: readonly string[]): SpatialAssetCommand {
3274
+ const action = argv[0];
3275
+ if (action !== "admit") return fail(`Usage: ${SPATIAL_ASSET_ADMIT_USAGE}`);
3276
+ const parsed = parseOptions(argv.slice(1), {
3277
+ ...JSON_SPEC, "--source-root": "value", "--asset-id": "value",
3278
+ "--meters-per-unit": "value", "--source-up": "value", "--output": "value",
3279
+ });
3280
+ const [file] = exactPositionals(parsed, 1, SPATIAL_ASSET_ADMIT_USAGE);
3281
+ const output = optionString(parsed, "--output");
3282
+ if (output === undefined) fail("scene asset admit requires --output.");
3283
+ const assetId = optionString(parsed, "--asset-id");
3284
+ if (assetId !== undefined && (assetId.length > 128 || !/^asset_[a-zA-Z0-9][a-zA-Z0-9_-]*$/u.test(assetId))) {
3285
+ fail("--asset-id must be an asset_-prefixed identifier of at most 128 characters.");
3286
+ }
3287
+ return {
3288
+ kind: "spatial-asset", action, file: file!, output,
3289
+ ...(optionString(parsed, "--source-root") === undefined ? {} : { sourceRoot: optionString(parsed, "--source-root")! }),
3290
+ ...(assetId === undefined ? {} : { assetId }),
3291
+ metersPerUnit: boundedNumber(optionString(parsed, "--meters-per-unit"), "--meters-per-unit", 0.000001, 1_000_000, 1),
3292
+ sourceUp: oneOf(optionString(parsed, "--source-up"), "--source-up", ["x", "y", "z"], "y"),
3293
+ json: optionFlag(parsed, "--json"),
3294
+ };
3295
+ }
3296
+
3297
+ function parseSpatialDesignArgs(argv: readonly string[]): SpatialDesignCommand {
3298
+ const action = argv[0];
3299
+ if (action === "catalog") {
3300
+ const parsed = parseOptions(argv.slice(1), JSON_SPEC);
3301
+ exactPositionals(parsed, 0, "slopcamera scene design catalog [--json]");
3302
+ return { kind: "spatial-design", action, json: optionFlag(parsed, "--json") };
3303
+ }
3304
+ if (action === "init") {
3305
+ const parsed = parseOptions(argv.slice(1), { ...JSON_SPEC, "--template": "value" });
3306
+ const [directory] = exactPositionals(parsed, 1, "slopcamera scene design init <directory> --template <id> [--json]");
3307
+ const template = optionString(parsed, "--template");
3308
+ if (template === undefined) return fail("scene design init requires --template; use scene design catalog.");
3309
+ return { kind: "spatial-design", action, directory: directory!, template, json: optionFlag(parsed, "--json") };
3310
+ }
3311
+ if (action === "inspect" || action === "check") {
3312
+ const parsed = parseOptions(argv.slice(1), JSON_SPEC);
3313
+ const [path] = exactPositionals(parsed, 1, "slopcamera scene design inspect <design.json> [--json]");
3314
+ return { kind: "spatial-design", action: "inspect", path: path!, json: optionFlag(parsed, "--json") };
3315
+ }
3316
+ if (action === "set") {
3317
+ const parsed = parseOptions(argv.slice(1), { ...JSON_SPEC, "--parameters": "value", "--output": "value" });
3318
+ const [path] = exactPositionals(parsed, 1, "slopcamera scene design set <design.json> --parameters <values.json> --output <new-design.json> [--json]");
3319
+ const parameters = optionString(parsed, "--parameters"), output = optionString(parsed, "--output");
3320
+ if (parameters === undefined || output === undefined) return fail("scene design set requires --parameters and --output.");
3321
+ return { kind: "spatial-design", action, path: path!, parameters, output, json: optionFlag(parsed, "--json") };
3322
+ }
3323
+ if (action === "compile" || action === "gallery") {
3324
+ const parsed = parseOptions(argv.slice(1), { ...JSON_SPEC, "--scene": "value", "--output-dir": "value", ...(action === "gallery" ? { "--variants": "value" as const } : {}) });
3325
+ const [path] = exactPositionals(parsed, 1, `slopcamera scene design ${action} <design.json> [--scene <scene.json>] --output-dir <new-directory> [--json]`);
3326
+ const scene = optionString(parsed, "--scene"), outputDir = optionString(parsed, "--output-dir");
3327
+ if (outputDir === undefined) return fail(`scene design ${action} requires --output-dir.`);
3328
+ const shared = { kind: "spatial-design" as const, path: path!, ...(scene === undefined ? {} : { scene }), outputDir, json: optionFlag(parsed, "--json") };
3329
+ if (action === "compile") return { ...shared, action };
3330
+ const variants = optionString(parsed, "--variants");
3331
+ if (variants === undefined) return fail("scene design gallery requires --variants.");
3332
+ return { ...shared, action, variants };
3333
+ }
3334
+ return fail("Usage: slopcamera scene design <catalog|init|inspect|set|compile|gallery> ...");
3335
+ }
3336
+
3337
+ function parseSpatialSceneArgs(argv: readonly string[]): SpatialSceneCommand | SpatialDesignCommand | SpatialProjectCommand | SpatialWorldCommand | SpatialAssetCommand {
3077
3338
  const action = argv[0];
3339
+ if (action === "design") return parseSpatialDesignArgs(argv.slice(1));
3078
3340
  if (action === "world") return parseSpatialWorldArgs(argv.slice(1));
3341
+ if (action === "asset") return parseSpatialAssetArgs(argv.slice(1));
3079
3342
  if (action === "project") {
3080
3343
  const projectAction = argv[1];
3081
3344
  if (projectAction === "snapshot") {
@@ -3105,6 +3368,11 @@ function parseSpatialSceneArgs(argv: readonly string[]): SpatialSceneCommand | S
3105
3368
  const [path] = exactPositionals(parsed, 1, `slopcamera scene ${action} <scene.json> [--json]`);
3106
3369
  return { kind: "spatial-scene", action: action === "check" ? "inspect" : action, path: path!, json: optionFlag(parsed, "--json") };
3107
3370
  }
3371
+ if (action === "diff") {
3372
+ const parsed = parseOptions(argv.slice(1), JSON_SPEC);
3373
+ const [path, other] = exactPositionals(parsed, 2, "slopcamera scene diff <scene-a.json> <scene-b.json> [--json]");
3374
+ return { kind: "spatial-scene", action, path: path!, other: other!, json: optionFlag(parsed, "--json") };
3375
+ }
3108
3376
  if (action === "patch") {
3109
3377
  const parsed = parseOptions(argv.slice(1), { ...JSON_SPEC, "--patch": "value", "--output": "value" });
3110
3378
  const [path] = exactPositionals(parsed, 1, "slopcamera scene patch <scene.json> --patch <patch.json> --output <new-scene.json>");
@@ -3126,6 +3394,54 @@ function parseSpatialSceneArgs(argv: readonly string[]): SpatialSceneCommand | S
3126
3394
  if (camera === undefined || time === undefined || !/^\d+$/u.test(time) || !Number.isSafeInteger(Number(time))) fail("scene evaluate requires --camera and integer --time-us.");
3127
3395
  return { kind: "spatial-scene", action, path: path!, camera, timeUs: Number(time), json: optionFlag(parsed, "--json") };
3128
3396
  }
3397
+ if (action === "audit") {
3398
+ const parsed = parseOptions(argv.slice(1), { ...JSON_SPEC, "--camera": "value", "--times-us": "value", "--asset-bounds": "value" });
3399
+ const [path] = exactPositionals(parsed, 1, "slopcamera scene audit <scene.json> --camera <camera-id> [--times-us <csv>] [--asset-bounds <bounds-or-admission.json>] [--json]");
3400
+ const camera = optionString(parsed, "--camera"), times = optionString(parsed, "--times-us"), assetBounds = optionString(parsed, "--asset-bounds");
3401
+ if (camera === undefined) fail("scene audit requires --camera.");
3402
+ let timesUs: number[] | undefined;
3403
+ if (times !== undefined) {
3404
+ if (!/^\d+(,\d+)*$/u.test(times)) return fail("scene audit --times-us must be a comma-separated list of nonnegative integers.");
3405
+ timesUs = times.split(",").map(Number);
3406
+ if (timesUs.length > 64) return fail("scene audit --times-us is bounded to 64 samples.");
3407
+ }
3408
+ return { kind: "spatial-scene", action, path: path!, camera, ...(timesUs === undefined ? {} : { timesUs }), ...(assetBounds === undefined ? {} : { assetBounds }), json: optionFlag(parsed, "--json") };
3409
+ }
3410
+ if (action === "render-audit") {
3411
+ const parsed = parseOptions(argv.slice(1), { ...JSON_SPEC, "--camera": "value", "--times-us": "value" });
3412
+ const [path] = exactPositionals(parsed, 1, "slopcamera scene render-audit <scene.json> --camera <camera-id> [--times-us <csv>] [--json]");
3413
+ const camera = optionString(parsed, "--camera"), times = optionString(parsed, "--times-us");
3414
+ if (camera === undefined) fail("scene render-audit requires --camera.");
3415
+ let timesUs: number[] | undefined;
3416
+ if (times !== undefined) {
3417
+ if (!/^\d+(,\d+)*$/u.test(times)) return fail("scene render-audit --times-us must be a comma-separated list of nonnegative integers.");
3418
+ timesUs = times.split(",").map(Number);
3419
+ if (timesUs.length > 64) return fail("scene render-audit --times-us is bounded to 64 samples.");
3420
+ }
3421
+ return { kind: "spatial-scene", action, path: path!, camera, ...(timesUs === undefined ? {} : { timesUs }), json: optionFlag(parsed, "--json") };
3422
+ }
3423
+ if (action === "solve") {
3424
+ const parsed = parseOptions(argv.slice(1), { ...JSON_SPEC, "--goals": "value", "--output": "value", "--asset-bounds": "value" });
3425
+ const [path] = exactPositionals(parsed, 1, "slopcamera scene solve <scene.json> --goals <goals.json> [--output <patch.json>] [--asset-bounds <bounds-or-admission.json>] [--json]");
3426
+ const goals = optionString(parsed, "--goals"), output = optionString(parsed, "--output"), assetBounds = optionString(parsed, "--asset-bounds");
3427
+ if (goals === undefined) fail("scene solve requires --goals.");
3428
+ return { kind: "spatial-scene", action, path: path!, goals, ...(output === undefined ? {} : { output }), ...(assetBounds === undefined ? {} : { assetBounds }), json: optionFlag(parsed, "--json") };
3429
+ }
3430
+ if (action === "review") {
3431
+ const parsed = parseOptions(argv.slice(1), { ...JSON_SPEC, "--camera": "value", "--times-us": "value", "--allow-cloud-upload": "flag" });
3432
+ const [path] = exactPositionals(parsed, 1, "slopcamera scene review <scene.json> --camera <camera-id> [--times-us <csv>] --allow-cloud-upload [--json]");
3433
+ const camera = optionString(parsed, "--camera"), times = optionString(parsed, "--times-us");
3434
+ if (camera === undefined) fail("scene review requires --camera.");
3435
+ let timesUs: number[] | undefined;
3436
+ if (times !== undefined) {
3437
+ if (!/^\d+(,\d+)*$/u.test(times)) return fail("scene review --times-us must be a comma-separated list of nonnegative integers.");
3438
+ timesUs = times.split(",").map(Number);
3439
+ if (timesUs.length > 4) return fail("scene review --times-us is bounded to 4 samples.");
3440
+ if (timesUs.some(value => !Number.isSafeInteger(value))) return fail("scene review --times-us values must be safe integers.");
3441
+ if (new Set(timesUs).size !== timesUs.length) return fail("scene review --times-us values must be unique.");
3442
+ }
3443
+ return { kind: "spatial-scene", action, path: path!, camera, ...(timesUs === undefined ? {} : { timesUs }), allowCloudUpload: optionFlag(parsed, "--allow-cloud-upload"), json: optionFlag(parsed, "--json") };
3444
+ }
3129
3445
  if (action === "plan" || action === "render") {
3130
3446
  const parsed = parseOptions(argv.slice(1), { ...JSON_SPEC, "--request": "value", "--assets": "value", "--profile": "value" });
3131
3447
  const [path] = exactPositionals(parsed, 1, `slopcamera scene ${action} <scene.json> --request <request.json> [--assets <bindings.json>]`);
@@ -3134,7 +3450,110 @@ function parseSpatialSceneArgs(argv: readonly string[]): SpatialSceneCommand | S
3134
3450
  const executionProfile = spatialCliExecutionProfile(optionString(parsed, "--profile"));
3135
3451
  return { kind: "spatial-scene", action, path: path!, request, ...(assets === undefined ? {} : { assets }), ...(executionProfile === undefined ? {} : { executionProfile }), json: optionFlag(parsed, "--json") };
3136
3452
  }
3137
- fail("Usage: slopcamera scene <init|check|inspect|patch|evaluate|camera-track|plan|render> ...");
3453
+ if (action === "generate") {
3454
+ const parsed = parseOptions(argv.slice(1), { ...JSON_SPEC, "--module": "value", "--generator-id": "value", "--parameters": "value", "--seed": "value", "--into": "value", "--output": "value" });
3455
+ exactPositionals(parsed, 0, "slopcamera scene generate --module <generator.ts> --generator-id <id> [--parameters <params.json>] [--seed <n>] [--into <scene.json>] --output <new-scene.json> [--json]");
3456
+ const modulePath = optionString(parsed, "--module"), generatorId = optionString(parsed, "--generator-id"), output = optionString(parsed, "--output");
3457
+ if (modulePath === undefined || generatorId === undefined || output === undefined) fail("scene generate requires --module, --generator-id, and --output.");
3458
+ const seed = optionalNonNegativeInteger(optionString(parsed, "--seed"), "--seed");
3459
+ if (seed !== undefined && seed > 0xffff_ffff) fail("--seed must be between 0 and 4294967295.");
3460
+ const parameters = optionString(parsed, "--parameters"), into = optionString(parsed, "--into");
3461
+ return { kind: "spatial-scene", action, module: modulePath, generatorId, output, ...(parameters === undefined ? {} : { parameters }), ...(seed === undefined ? {} : { seed }), ...(into === undefined ? {} : { into }), json: optionFlag(parsed, "--json") };
3462
+ }
3463
+ if (action === "direction") {
3464
+ const directionAction = argv[1];
3465
+ if (directionAction === "check") {
3466
+ const parsed = parseOptions(argv.slice(2), { ...JSON_SPEC, "--scene": "value" });
3467
+ const [direction] = exactPositionals(parsed, 1, "slopcamera scene direction check <direction.json> --scene <scene.json> [--json]");
3468
+ const scene = optionString(parsed, "--scene");
3469
+ if (scene === undefined) fail("scene direction check requires --scene.");
3470
+ return { kind: "spatial-scene", action: "direction-check", direction: direction!, scene, json: optionFlag(parsed, "--json") };
3471
+ }
3472
+ if (directionAction === "plan" || directionAction === "gallery") {
3473
+ const parsed = parseOptions(argv.slice(2), { ...JSON_SPEC, "--axis": "value", "--camera": "value", "--output": "value", "--scene": "value" });
3474
+ const [direction] = exactPositionals(parsed, 1, `slopcamera scene direction ${directionAction} <direction.json> --scene <scene.json>${directionAction === "gallery" ? " --axis <axis>" : ""} [--camera <camera-id>] [--output <out.json>] [--json]`);
3475
+ const scene = optionString(parsed, "--scene"), camera = optionString(parsed, "--camera"), output = optionString(parsed, "--output"), axis = optionString(parsed, "--axis");
3476
+ if (scene === undefined) fail(`scene direction ${directionAction} requires --scene.`);
3477
+ if (directionAction === "gallery" && axis === undefined) fail("scene direction gallery requires --axis.");
3478
+ return directionAction === "gallery"
3479
+ ? { kind: "spatial-scene", action: "direction-gallery", direction: direction!, scene, axis: axis!, ...(camera === undefined ? {} : { camera }), ...(output === undefined ? {} : { output }), json: optionFlag(parsed, "--json") }
3480
+ : { kind: "spatial-scene", action: "direction-plan", direction: direction!, scene, ...(camera === undefined ? {} : { camera }), ...(output === undefined ? {} : { output }), json: optionFlag(parsed, "--json") };
3481
+ }
3482
+ fail("Usage: slopcamera scene direction <check|plan|gallery> ...");
3483
+ }
3484
+ if (action === "effects") {
3485
+ const effectsAction = argv[1];
3486
+ if (effectsAction === "check") {
3487
+ const parsed = parseOptions(argv.slice(2), { ...JSON_SPEC, "--scene": "value" });
3488
+ const [effects] = exactPositionals(parsed, 1, "slopcamera scene effects check <effects.json> --scene <scene.json> [--json]");
3489
+ const scene = optionString(parsed, "--scene");
3490
+ if (scene === undefined) fail("scene effects check requires --scene.");
3491
+ return { kind: "spatial-scene", action: "effects-check", effects: effects!, scene, json: optionFlag(parsed, "--json") };
3492
+ }
3493
+ if (effectsAction === "plan") {
3494
+ const parsed = parseOptions(argv.slice(2), { ...JSON_SPEC, "--output": "value", "--scene": "value" });
3495
+ const [draft] = exactPositionals(parsed, 1, "slopcamera scene effects plan <draft.json> --scene <scene.json> [--output <effects.json>] [--json]");
3496
+ const scene = optionString(parsed, "--scene"), output = optionString(parsed, "--output");
3497
+ if (scene === undefined) fail("scene effects plan requires --scene.");
3498
+ return { kind: "spatial-scene", action: "effects-plan", draft: draft!, scene, ...(output === undefined ? {} : { output }), json: optionFlag(parsed, "--json") };
3499
+ }
3500
+ if (effectsAction === "bake") {
3501
+ const parsed = parseOptions(argv.slice(2), { ...JSON_SPEC, "--output": "value", "--scene": "value" });
3502
+ const [simulation] = exactPositionals(parsed, 1, "slopcamera scene effects bake <simulation.json> --scene <scene.json> --output <receipt.json> [--json]");
3503
+ const scene = optionString(parsed, "--scene"), output = optionString(parsed, "--output");
3504
+ if (scene === undefined || output === undefined) fail("scene effects bake requires --scene and --output.");
3505
+ return { kind: "spatial-scene", action: "effects-bake", simulation: simulation!, scene, output, json: optionFlag(parsed, "--json") };
3506
+ }
3507
+ fail("Usage: slopcamera scene effects <check|plan|bake> ...");
3508
+ }
3509
+ if (action === "behavior") {
3510
+ const behaviorAction = argv[1];
3511
+ if (behaviorAction === "check") {
3512
+ const parsed = parseOptions(argv.slice(2), { ...JSON_SPEC, "--scene": "value" });
3513
+ const [behavior] = exactPositionals(parsed, 1, "slopcamera scene behavior check <behavior.json> --scene <scene.json> [--json]");
3514
+ const scene = optionString(parsed, "--scene");
3515
+ if (scene === undefined) fail("scene behavior check requires --scene.");
3516
+ return { kind: "spatial-scene", action: "behavior-check", behavior: behavior!, scene, json: optionFlag(parsed, "--json") };
3517
+ }
3518
+ if (behaviorAction === "bake" || behaviorAction === "gallery") {
3519
+ const parsed = parseOptions(argv.slice(2), { ...JSON_SPEC, "--channel-map": "value", "--output": "value", "--scene": "value" });
3520
+ const [behavior] = exactPositionals(parsed, 1, `slopcamera scene behavior ${behaviorAction} <behavior.json> --scene <scene.json> [--channel-map <map.json>]${behaviorAction === "bake" ? " --output <bake.json>" : " [--output <gallery.json>]"} [--json]`);
3521
+ const scene = optionString(parsed, "--scene"), channelMap = optionString(parsed, "--channel-map"), output = optionString(parsed, "--output");
3522
+ if (scene === undefined) fail(`scene behavior ${behaviorAction} requires --scene.`);
3523
+ if (behaviorAction === "bake" && output === undefined) fail("scene behavior bake requires --output.");
3524
+ const shared = { kind: "spatial-scene" as const, behavior: behavior!, scene, ...(channelMap === undefined ? {} : { channelMap }), json: optionFlag(parsed, "--json") };
3525
+ return behaviorAction === "bake"
3526
+ ? { ...shared, action: "behavior-bake", output: output! }
3527
+ : { ...shared, action: "behavior-gallery", ...(output === undefined ? {} : { output }) };
3528
+ }
3529
+ if (behaviorAction === "audit") {
3530
+ const parsed = parseOptions(argv.slice(2), { ...JSON_SPEC, "--output": "value" });
3531
+ const [bake] = exactPositionals(parsed, 1, "slopcamera scene behavior audit <bake.json> [--output <audit.json>] [--json]");
3532
+ const output = optionString(parsed, "--output");
3533
+ return { kind: "spatial-scene", action: "behavior-audit", bake: bake!, ...(output === undefined ? {} : { output }), json: optionFlag(parsed, "--json") };
3534
+ }
3535
+ fail("Usage: slopcamera scene behavior <check|bake|gallery|audit> ...");
3536
+ }
3537
+ if (action === "temporal-audit") {
3538
+ const parsed = parseOptions(argv.slice(1), { ...JSON_SPEC, "--camera": "value", "--contacts": "value", "--cut-before-us": "value", "--times-us": "value" });
3539
+ const [path] = exactPositionals(parsed, 1, "slopcamera scene temporal-audit <scene.json> --camera <camera-id> [--times-us <csv>] [--contacts <contacts.json>] [--cut-before-us <csv>] [--json]");
3540
+ const camera = optionString(parsed, "--camera"), times = optionString(parsed, "--times-us"), contacts = optionString(parsed, "--contacts"), cutBefore = optionString(parsed, "--cut-before-us");
3541
+ if (camera === undefined) fail("scene temporal-audit requires --camera.");
3542
+ let timesUs: number[] | undefined;
3543
+ if (times !== undefined) {
3544
+ if (!/^\d+(,\d+)*$/u.test(times)) return fail("scene temporal-audit --times-us must be a comma-separated list of nonnegative integers.");
3545
+ timesUs = times.split(",").map(Number);
3546
+ if (timesUs.length > 64) return fail("scene temporal-audit --times-us is bounded to 64 samples.");
3547
+ }
3548
+ let cutBeforeUs: number[] | undefined;
3549
+ if (cutBefore !== undefined) {
3550
+ if (!/^\d+(,\d+)*$/u.test(cutBefore)) return fail("scene temporal-audit --cut-before-us must be a comma-separated list of nonnegative integers.");
3551
+ cutBeforeUs = cutBefore.split(",").map(Number);
3552
+ if (cutBeforeUs.length > 64) return fail("scene temporal-audit --cut-before-us is bounded to 64 values.");
3553
+ }
3554
+ return { kind: "spatial-scene", action, path: path!, camera, ...(timesUs === undefined ? {} : { timesUs }), ...(contacts === undefined ? {} : { contacts }), ...(cutBeforeUs === undefined ? {} : { cutBeforeUs }), json: optionFlag(parsed, "--json") };
3555
+ }
3556
+ fail("Usage: slopcamera scene <init|check|inspect|diff|patch|evaluate|audit|render-audit|solve|review|camera-track|generate|plan|render|direction|effects|behavior|temporal-audit|asset|project|world> ...");
3138
3557
  }
3139
3558
 
3140
3559
  export function parseCliArgs(argv: readonly string[]): CliCommand {
@@ -3162,6 +3581,7 @@ export function parseCliArgs(argv: readonly string[]): CliCommand {
3162
3581
  case "direct": return parseDirectingArgs(argv.slice(1));
3163
3582
  case "scene": return parseSpatialSceneArgs(argv.slice(1));
3164
3583
  case "operations": return parseOperations(argv.slice(1));
3584
+ case "capabilities": return parseCapabilities(argv.slice(1));
3165
3585
  case "diagram": return parseDiagram(argv.slice(1));
3166
3586
  case "image": return parseImage(argv.slice(1));
3167
3587
  case "workflows": return parseWorkflows(argv.slice(1));
@@ -3175,7 +3595,18 @@ export function parseCliArgs(argv: readonly string[]): CliCommand {
3175
3595
  case "project": return parseProject(argv.slice(1));
3176
3596
  case "inspect": return parseInspect(argv.slice(1));
3177
3597
  case "events": return parseEvents(argv.slice(1));
3178
- case "record": return parseRecord(argv.slice(1));
3598
+ case "menubar": {
3599
+ const parsed = parseOptions(argv.slice(1), { ...JSON_SPEC, "--foreground": "flag", "--background": "flag" });
3600
+ const positionals = parsed.positionals;
3601
+ if (positionals.length > 1 || (positionals[0] !== undefined && !["install", "uninstall", "status"].includes(positionals[0]))) throw new CliError("usage", "Use slopcamera menubar [--foreground|--background] | install|uninstall|status");
3602
+ if (positionals[0] !== undefined && (optionFlag(parsed, "--foreground") || optionFlag(parsed, "--background"))) throw new CliError("usage", "Choose a menu-bar action or a run mode, not both.");
3603
+ return { kind: "menubar", action: (positionals[0] as "install" | "uninstall" | "status" | undefined) ?? "run", mode: optionFlag(parsed, "--background") ? "background" : "foreground", json: optionFlag(parsed, "--json") };
3604
+ }
3605
+ case "outputs": {
3606
+ const parsed = parseOptions(argv.slice(1), JSON_SPEC);
3607
+ exactPositionals(parsed, 0, "slopcamera outputs [--json]");
3608
+ return { kind: "outputs", json: optionFlag(parsed, "--json") };
3609
+ }
3179
3610
  case "edit": return parseEdit(argv.slice(1));
3180
3611
  case "analyze": return parseAnalyze(argv.slice(1));
3181
3612
  case "faces": return parseFaces(argv.slice(1));