@hraness/slopcamera 3.2.8 → 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 (150) hide show
  1. package/README.md +29 -9
  2. package/apps/desktop/application/context.ts +6 -0
  3. package/apps/desktop/application/default-registry.ts +26 -0
  4. package/apps/desktop/application/operation.ts +14 -1
  5. package/apps/desktop/application/operations/index.ts +4 -0
  6. package/apps/desktop/application/operations/spatial-behavior.ts +151 -0
  7. package/apps/desktop/application/operations/spatial-direction.ts +222 -0
  8. package/apps/desktop/application/operations/spatial-rendered-audit.ts +174 -0
  9. package/apps/desktop/application/operations/spatial-review.ts +194 -0
  10. package/apps/desktop/application/operations/spatial-scene.ts +25 -1
  11. package/apps/desktop/application/registry.ts +2 -2
  12. package/apps/desktop/application/spatial-asset-admission.ts +201 -0
  13. package/apps/desktop/application/spatial-assets.ts +160 -31
  14. package/apps/desktop/application/spatial-render.ts +217 -20
  15. package/apps/desktop/application/spatial-rendered-audit.ts +273 -0
  16. package/apps/desktop/application/spatial-review.ts +361 -0
  17. package/apps/desktop/application/spatial-spz.ts +48 -3
  18. package/apps/desktop/application/spatial-world-import.ts +26 -5
  19. package/apps/desktop/application/spatial-world-metadata.ts +105 -0
  20. package/apps/desktop/cli/args.ts +514 -11
  21. package/apps/desktop/cli/capability-manifest.ts +374 -0
  22. package/apps/desktop/cli/cinema-renderer.ts +608 -0
  23. package/apps/desktop/cli/command-host-resources.ts +29 -1
  24. package/apps/desktop/cli/commands.ts +1188 -4
  25. package/apps/desktop/cli/gateway-review-provider.ts +334 -0
  26. package/apps/desktop/cli/help.ts +172 -6
  27. package/apps/desktop/cli/main.ts +3 -3
  28. package/apps/desktop/cli/portable-surface.ts +6 -1
  29. package/apps/desktop/cli/spatial-asset-service.ts +124 -0
  30. package/apps/desktop/cli/spatial-design-service.ts +237 -0
  31. package/apps/desktop/cli/spatial-generate-service.ts +370 -0
  32. package/apps/desktop/cli/spatial-scene-service.ts +268 -4
  33. package/apps/desktop/cli/workflow-code.ts +1 -1
  34. package/apps/desktop/code/semantic-builder.ts +24 -0
  35. package/apps/desktop/contracts/cinema.ts +736 -0
  36. package/apps/desktop/contracts/index.ts +1 -0
  37. package/apps/desktop/contracts/spatial-asset.ts +16 -0
  38. package/apps/desktop/contracts/spatial-world.ts +26 -3
  39. package/apps/desktop/core/cinema-plan.ts +1320 -0
  40. package/apps/desktop/core/index.ts +1 -0
  41. package/apps/desktop/core/storage.ts +75 -0
  42. package/apps/desktop/dist/cli/main.js +725 -273
  43. package/apps/desktop/html-overlay/spatial.ts +619 -57
  44. package/apps/desktop/workflows/cinematic-world.ts +127 -0
  45. package/apps/desktop/workflows/index.ts +6 -0
  46. package/dist/cli.js +25 -429
  47. package/dist/code/advanced.js +1 -269
  48. package/dist/code/index.js +16 -2239
  49. package/dist/generate.js +1 -23
  50. package/dist/host-resources.js +1 -25
  51. package/dist/index-0bbesn4m.js +35 -0
  52. package/dist/index-296qhpf2.js +3 -0
  53. package/dist/index-3cxf2c58.js +3 -0
  54. package/dist/index-77fjfg9f.js +3 -0
  55. package/dist/index-80a36bc0.js +3 -0
  56. package/dist/index-a3vvs3rf.js +3 -0
  57. package/dist/index-b3geqczn.js +5 -0
  58. package/dist/index-cgpkyjqa.js +3 -0
  59. package/dist/index-f0ptv8dm.js +7 -0
  60. package/dist/index-grk47tgs.js +3 -0
  61. package/dist/index-qry58nj2.js +10 -0
  62. package/dist/index.js +1 -226
  63. package/dist/operations.js +1 -28
  64. package/dist/skill-install-gx7afxsd.js +2 -0
  65. package/dist/vectorize/worker.js +1 -141
  66. package/dist/workflow.js +1 -15
  67. package/docs/README.md +5 -1
  68. package/package.json +7 -5
  69. package/skills/slopcamera/SKILL.md +6 -1
  70. package/skills/slopcamera/references/directed-scenes.md +22 -2
  71. package/skills/slopcamera/references/gateway-media.md +15 -1
  72. package/skills/slopcamera/references/image-galleries.md +166 -0
  73. package/skills/slopcamera/references/install.md +1 -1
  74. package/skills/slopcamera/references/parametric-design.md +14 -0
  75. package/skills/slopcamera/references/scene-building.md +32 -0
  76. package/skills/slopcamera/references/social-collage-banners.md +204 -0
  77. package/skills/slopcamera/references/workflows-sdk.md +3 -3
  78. package/skills/slopcamera/scripts/compose-social-collage-banner.ts +728 -0
  79. package/src/capability-manifest.ts +361 -0
  80. package/src/cli.ts +222 -4
  81. package/src/code/canonical-json.ts +9 -0
  82. package/src/generate.ts +2 -2
  83. package/src/icon.ts +1111 -0
  84. package/src/image-gallery.ts +1049 -0
  85. package/src/index.ts +11 -0
  86. package/src/mcp/boundary.ts +11 -11
  87. package/src/mcp/index.ts +3 -0
  88. package/src/mcp/server.ts +2 -1
  89. package/src/mcp/tools.ts +1642 -130
  90. package/src/mcp/types.ts +19 -0
  91. package/src/operations.ts +412 -0
  92. package/src/portable-capability-manifest.ts +67 -0
  93. package/src/scene-gallery.ts +184 -0
  94. package/src/spatial-scene/asset-admission.ts +183 -0
  95. package/src/spatial-scene/audit-rendered.ts +692 -0
  96. package/src/spatial-scene/audit.ts +617 -0
  97. package/src/spatial-scene/behavior-audit.ts +252 -0
  98. package/src/spatial-scene/behavior-bake.ts +233 -0
  99. package/src/spatial-scene/behavior-fns.ts +585 -0
  100. package/src/spatial-scene/behavior-gallery.ts +109 -0
  101. package/src/spatial-scene/behavior-stdlib.ts +323 -0
  102. package/src/spatial-scene/behavior-trace.ts +325 -0
  103. package/src/spatial-scene/behavior.ts +618 -0
  104. package/src/spatial-scene/build.ts +632 -0
  105. package/src/spatial-scene/camera-rig.ts +125 -0
  106. package/src/spatial-scene/camera-track.ts +4 -2
  107. package/src/spatial-scene/character.ts +124 -0
  108. package/src/spatial-scene/contracts.ts +75 -7
  109. package/src/spatial-scene/design-templates.ts +351 -0
  110. package/src/spatial-scene/design.ts +327 -0
  111. package/src/spatial-scene/direction-compile.ts +584 -0
  112. package/src/spatial-scene/direction.ts +122 -0
  113. package/src/spatial-scene/effects.ts +149 -0
  114. package/src/spatial-scene/evaluate.ts +109 -29
  115. package/src/spatial-scene/gallery.ts +277 -0
  116. package/src/spatial-scene/generate.ts +239 -0
  117. package/src/spatial-scene/geometry-native.ts +165 -0
  118. package/src/spatial-scene/geometry.ts +1216 -0
  119. package/src/spatial-scene/gltf.ts +600 -77
  120. package/src/spatial-scene/identity.ts +55 -2
  121. package/src/spatial-scene/index.ts +36 -0
  122. package/src/spatial-scene/inspect.ts +35 -6
  123. package/src/spatial-scene/material-lighting.ts +609 -0
  124. package/src/spatial-scene/math.ts +21 -4
  125. package/src/spatial-scene/motion-evidence.ts +88 -0
  126. package/src/spatial-scene/parametric.ts +765 -0
  127. package/src/spatial-scene/particle-preparation.ts +183 -0
  128. package/src/spatial-scene/particle.ts +195 -0
  129. package/src/spatial-scene/patch.ts +109 -15
  130. package/src/spatial-scene/performance.ts +1613 -0
  131. package/src/spatial-scene/probe.ts +137 -0
  132. package/src/spatial-scene/recipe-pack.ts +86 -0
  133. package/src/spatial-scene/render-effects.ts +240 -0
  134. package/src/spatial-scene/review.ts +639 -0
  135. package/src/spatial-scene/simulation-bake.ts +206 -0
  136. package/src/spatial-scene/simulation.ts +187 -0
  137. package/src/spatial-scene/solve.ts +366 -0
  138. package/src/spatial-scene/temporal-audit.ts +354 -0
  139. package/src/version.ts +1 -1
  140. package/dist/index-42zsesc1.js +0 -2257
  141. package/dist/index-7131pg1c.js +0 -1820
  142. package/dist/index-7308egqr.js +0 -48
  143. package/dist/index-8txs6fkn.js +0 -712
  144. package/dist/index-fava6pge.js +0 -192
  145. package/dist/index-h1k0fnjq.js +0 -2363
  146. package/dist/index-r7gdhmsp.js +0 -424
  147. package/dist/index-sh6xbav6.js +0 -1219
  148. package/dist/index-z1w83f81.js +0 -4
  149. package/dist/index-zfnddgay.js +0 -2029
  150. package/dist/skill-install-dh0ntqba.js +0 -10
@@ -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;
@@ -387,6 +448,16 @@ export type CliCommand =
387
448
  readonly project: string;
388
449
  readonly width: number;
389
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)
390
461
  | ({
391
462
  readonly apply: boolean;
392
463
  readonly candidate: string | undefined;
@@ -809,6 +880,12 @@ function exactPositionals(parsed: ParsedOptions, count: number, usage: string):
809
880
 
810
881
  const JSON_SPEC = { "--json": "flag" } as const;
811
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
+
812
889
  function parseDoctor(argv: readonly string[]): CliCommand {
813
890
  const parsed = parseOptions(argv, JSON_SPEC);
814
891
  exactPositionals(parsed, 0, "slopcamera doctor [--json]");
@@ -911,7 +988,10 @@ function parseAiProviderOptions(argv: readonly string[]): CliCommand {
911
988
  }
912
989
 
913
990
  function parseAiImage(argv: readonly string[]): CliCommand {
914
- 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
+ }
915
995
  const parsed = parseOptions(argv.slice(1), {
916
996
  ...JSON_SPEC,
917
997
  "--allow-cloud-upload": "flag",
@@ -969,6 +1049,145 @@ function parseAiImage(argv: readonly string[]): CliCommand {
969
1049
  };
970
1050
  }
971
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
+
972
1191
  function parseAiVideo(argv: readonly string[]): CliCommand {
973
1192
  if (argv[0] !== "generate") fail("Usage: slopcamera ai video generate --model <id> [options]");
974
1193
  const parsed = parseOptions(argv.slice(1), {
@@ -1131,11 +1350,12 @@ function parseAi(argv: readonly string[]): CliCommand {
1131
1350
  case "models": return parseAiModels(argv.slice(1));
1132
1351
  case "provider-options": return parseAiProviderOptions(argv.slice(1));
1133
1352
  case "image": return parseAiImage(argv.slice(1));
1353
+ case "scene": return parseAiScene(argv.slice(1));
1134
1354
  case "video": return parseAiVideo(argv.slice(1));
1135
1355
  case "speech": return parseAiSpeech(argv.slice(1));
1136
1356
  case "transcribe": return parseAiTranscribe(argv.slice(1));
1137
1357
  case undefined:
1138
- 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]");
1139
1359
  }
1140
1360
  }
1141
1361
 
@@ -1742,7 +1962,65 @@ function parseProject(argv: readonly string[]): CliCommand {
1742
1962
  width: strictEvenPositiveInteger(optionString(parsed, "--width"), "--width", 1_920),
1743
1963
  };
1744
1964
  }
1745
- 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> ...");
1746
2024
  }
1747
2025
 
1748
2026
  function parseInspect(argv: readonly string[]): CliCommand {
@@ -2990,9 +3268,77 @@ function parseSpatialWorldArgs(argv: readonly string[]): SpatialWorldCommand {
2990
3268
  return fail("Usage: slopcamera scene world import --input <request.json> --source-root <directory> --output-root <artifact-directory>");
2991
3269
  }
2992
3270
 
2993
- 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 {
2994
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 {
3338
+ const action = argv[0];
3339
+ if (action === "design") return parseSpatialDesignArgs(argv.slice(1));
2995
3340
  if (action === "world") return parseSpatialWorldArgs(argv.slice(1));
3341
+ if (action === "asset") return parseSpatialAssetArgs(argv.slice(1));
2996
3342
  if (action === "project") {
2997
3343
  const projectAction = argv[1];
2998
3344
  if (projectAction === "snapshot") {
@@ -3022,6 +3368,11 @@ function parseSpatialSceneArgs(argv: readonly string[]): SpatialSceneCommand | S
3022
3368
  const [path] = exactPositionals(parsed, 1, `slopcamera scene ${action} <scene.json> [--json]`);
3023
3369
  return { kind: "spatial-scene", action: action === "check" ? "inspect" : action, path: path!, json: optionFlag(parsed, "--json") };
3024
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
+ }
3025
3376
  if (action === "patch") {
3026
3377
  const parsed = parseOptions(argv.slice(1), { ...JSON_SPEC, "--patch": "value", "--output": "value" });
3027
3378
  const [path] = exactPositionals(parsed, 1, "slopcamera scene patch <scene.json> --patch <patch.json> --output <new-scene.json>");
@@ -3043,6 +3394,54 @@ function parseSpatialSceneArgs(argv: readonly string[]): SpatialSceneCommand | S
3043
3394
  if (camera === undefined || time === undefined || !/^\d+$/u.test(time) || !Number.isSafeInteger(Number(time))) fail("scene evaluate requires --camera and integer --time-us.");
3044
3395
  return { kind: "spatial-scene", action, path: path!, camera, timeUs: Number(time), json: optionFlag(parsed, "--json") };
3045
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
+ }
3046
3445
  if (action === "plan" || action === "render") {
3047
3446
  const parsed = parseOptions(argv.slice(1), { ...JSON_SPEC, "--request": "value", "--assets": "value", "--profile": "value" });
3048
3447
  const [path] = exactPositionals(parsed, 1, `slopcamera scene ${action} <scene.json> --request <request.json> [--assets <bindings.json>]`);
@@ -3051,7 +3450,110 @@ function parseSpatialSceneArgs(argv: readonly string[]): SpatialSceneCommand | S
3051
3450
  const executionProfile = spatialCliExecutionProfile(optionString(parsed, "--profile"));
3052
3451
  return { kind: "spatial-scene", action, path: path!, request, ...(assets === undefined ? {} : { assets }), ...(executionProfile === undefined ? {} : { executionProfile }), json: optionFlag(parsed, "--json") };
3053
3452
  }
3054
- 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> ...");
3055
3557
  }
3056
3558
 
3057
3559
  export function parseCliArgs(argv: readonly string[]): CliCommand {
@@ -3079,6 +3581,7 @@ export function parseCliArgs(argv: readonly string[]): CliCommand {
3079
3581
  case "direct": return parseDirectingArgs(argv.slice(1));
3080
3582
  case "scene": return parseSpatialSceneArgs(argv.slice(1));
3081
3583
  case "operations": return parseOperations(argv.slice(1));
3584
+ case "capabilities": return parseCapabilities(argv.slice(1));
3082
3585
  case "diagram": return parseDiagram(argv.slice(1));
3083
3586
  case "image": return parseImage(argv.slice(1));
3084
3587
  case "workflows": return parseWorkflows(argv.slice(1));