@hraness/slopcamera 3.2.4

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 (441) hide show
  1. package/LICENSE +21 -0
  2. package/NOTICE.md +60 -0
  3. package/PRIVACY.md +46 -0
  4. package/README.md +311 -0
  5. package/SECURITY.md +21 -0
  6. package/apps/desktop/README.md +966 -0
  7. package/apps/desktop/analysis/protocol.ts +223 -0
  8. package/apps/desktop/application/capability-binding.ts +594 -0
  9. package/apps/desktop/application/context.ts +97 -0
  10. package/apps/desktop/application/creative-iteration.ts +1385 -0
  11. package/apps/desktop/application/default-registry.ts +146 -0
  12. package/apps/desktop/application/directing-contract.ts +218 -0
  13. package/apps/desktop/application/directing-plan.ts +178 -0
  14. package/apps/desktop/application/errors.ts +18 -0
  15. package/apps/desktop/application/gateway-port.ts +867 -0
  16. package/apps/desktop/application/html-overlay-browser-runtime.ts +687 -0
  17. package/apps/desktop/application/html-overlay-integrity.ts +337 -0
  18. package/apps/desktop/application/html-overlay-renderer.ts +45 -0
  19. package/apps/desktop/application/index.ts +13 -0
  20. package/apps/desktop/application/operation-completion-checkpoint.ts +333 -0
  21. package/apps/desktop/application/operation-effects.ts +94 -0
  22. package/apps/desktop/application/operation.ts +224 -0
  23. package/apps/desktop/application/operations/analysis/faces.ts +244 -0
  24. package/apps/desktop/application/operations/analysis/music.ts +248 -0
  25. package/apps/desktop/application/operations/analysis/project-auto-zooms.ts +302 -0
  26. package/apps/desktop/application/operations/analysis/project-inactivity.ts +381 -0
  27. package/apps/desktop/application/operations/analysis/scenes.ts +272 -0
  28. package/apps/desktop/application/operations/analysis/shared.ts +202 -0
  29. package/apps/desktop/application/operations/derive/edit-batch.ts +967 -0
  30. package/apps/desktop/application/operations/derive/follow-faces.ts +454 -0
  31. package/apps/desktop/application/operations/edit/bind-candidate-revision.ts +91 -0
  32. package/apps/desktop/application/operations/edit/create-candidate-revision.ts +377 -0
  33. package/apps/desktop/application/operations/edit/create-revision.ts +255 -0
  34. package/apps/desktop/application/operations/edit/freeze-revision.ts +182 -0
  35. package/apps/desktop/application/operations/gateway/image.ts +41 -0
  36. package/apps/desktop/application/operations/gateway/index.ts +17 -0
  37. package/apps/desktop/application/operations/gateway/shared.ts +122 -0
  38. package/apps/desktop/application/operations/gateway/speech.ts +41 -0
  39. package/apps/desktop/application/operations/gateway/transcription.ts +42 -0
  40. package/apps/desktop/application/operations/gateway/video.ts +41 -0
  41. package/apps/desktop/application/operations/index.ts +31 -0
  42. package/apps/desktop/application/operations/iteration/create-candidate.ts +240 -0
  43. package/apps/desktop/application/operations/iteration/create-matrix.ts +117 -0
  44. package/apps/desktop/application/operations/iteration/select.ts +155 -0
  45. package/apps/desktop/application/operations/iteration/shared.ts +250 -0
  46. package/apps/desktop/application/operations/media/audio-effects.ts +376 -0
  47. package/apps/desktop/application/operations/media/binding.ts +79 -0
  48. package/apps/desktop/application/operations/media/capabilities.ts +112 -0
  49. package/apps/desktop/application/operations/media/color-grade.ts +343 -0
  50. package/apps/desktop/application/operations/media/html-overlay.ts +802 -0
  51. package/apps/desktop/application/operations/media/index.ts +8 -0
  52. package/apps/desktop/application/operations/media/ingest.ts +303 -0
  53. package/apps/desktop/application/operations/media/overlay.ts +729 -0
  54. package/apps/desktop/application/operations/media/shared.ts +845 -0
  55. package/apps/desktop/application/operations/media/transform-platform.ts +120 -0
  56. package/apps/desktop/application/operations/project/commit-edits.ts +2054 -0
  57. package/apps/desktop/application/operations/project/promote-selection.ts +495 -0
  58. package/apps/desktop/application/operations/project/snapshot.ts +95 -0
  59. package/apps/desktop/application/operations/recording/index.ts +5 -0
  60. package/apps/desktop/application/operations/recording/pause.ts +34 -0
  61. package/apps/desktop/application/operations/recording/resume.ts +34 -0
  62. package/apps/desktop/application/operations/recording/shared.ts +199 -0
  63. package/apps/desktop/application/operations/recording/start.ts +169 -0
  64. package/apps/desktop/application/operations/recording/stop.ts +34 -0
  65. package/apps/desktop/application/operations/render/bind-candidate-output.ts +324 -0
  66. package/apps/desktop/application/operations/render/materialize-selection.ts +322 -0
  67. package/apps/desktop/application/operations/render/project-caption-plan.ts +287 -0
  68. package/apps/desktop/application/operations/render/project-plan.ts +939 -0
  69. package/apps/desktop/application/operations/render/project-spatial-receipt.ts +70 -0
  70. package/apps/desktop/application/operations/render/project.ts +1922 -0
  71. package/apps/desktop/application/operations/shared.ts +67 -0
  72. package/apps/desktop/application/operations/slopcamera-portable.ts +1188 -0
  73. package/apps/desktop/application/operations/slopcamera-visuals.ts +784 -0
  74. package/apps/desktop/application/operations/spatial-project.ts +209 -0
  75. package/apps/desktop/application/operations/spatial-render.ts +415 -0
  76. package/apps/desktop/application/operations/spatial-scene.ts +97 -0
  77. package/apps/desktop/application/operations/studio.ts +143 -0
  78. package/apps/desktop/application/output-publication-lease.ts +163 -0
  79. package/apps/desktop/application/project-publication-lease.ts +233 -0
  80. package/apps/desktop/application/project-store.ts +196 -0
  81. package/apps/desktop/application/receipts.ts +533 -0
  82. package/apps/desktop/application/registry.ts +182 -0
  83. package/apps/desktop/application/spatial-assets.ts +675 -0
  84. package/apps/desktop/application/spatial-compositor-cadence.ts +23 -0
  85. package/apps/desktop/application/spatial-project-authority.ts +35 -0
  86. package/apps/desktop/application/spatial-project-lease.ts +126 -0
  87. package/apps/desktop/application/spatial-project-render-preparation.ts +225 -0
  88. package/apps/desktop/application/spatial-project-store.ts +286 -0
  89. package/apps/desktop/application/spatial-render-projection.ts +201 -0
  90. package/apps/desktop/application/spatial-render.ts +677 -0
  91. package/apps/desktop/application/spatial-spz.ts +44 -0
  92. package/apps/desktop/application/spatial-world-import.ts +135 -0
  93. package/apps/desktop/application/spatial-world-provenance.ts +46 -0
  94. package/apps/desktop/application/studio-port.ts +58 -0
  95. package/apps/desktop/application/verified-receipt-reconciliation.ts +1574 -0
  96. package/apps/desktop/capture/protocol.ts +689 -0
  97. package/apps/desktop/cli/analyzer.ts +406 -0
  98. package/apps/desktop/cli/args.ts +3181 -0
  99. package/apps/desktop/cli/asset-ingest.ts +747 -0
  100. package/apps/desktop/cli/atomic-render-effects.ts +210 -0
  101. package/apps/desktop/cli/atomic-render-platform.ts +205 -0
  102. package/apps/desktop/cli/atomic-render.ts +50 -0
  103. package/apps/desktop/cli/audio-analysis.ts +252 -0
  104. package/apps/desktop/cli/bounded-map.ts +39 -0
  105. package/apps/desktop/cli/bundle-service.ts +256 -0
  106. package/apps/desktop/cli/capabilities.ts +212 -0
  107. package/apps/desktop/cli/capture-bundle.ts +1117 -0
  108. package/apps/desktop/cli/command-host-resources.ts +251 -0
  109. package/apps/desktop/cli/commands.ts +6836 -0
  110. package/apps/desktop/cli/directing-blob.ts +291 -0
  111. package/apps/desktop/cli/directing-media.ts +517 -0
  112. package/apps/desktop/cli/directing-quote.ts +89 -0
  113. package/apps/desktop/cli/directing-service.ts +288 -0
  114. package/apps/desktop/cli/emoji-assets.ts +538 -0
  115. package/apps/desktop/cli/errors.ts +29 -0
  116. package/apps/desktop/cli/face-analysis-service.ts +940 -0
  117. package/apps/desktop/cli/filter-script.ts +76 -0
  118. package/apps/desktop/cli/gateway-application-port.ts +1142 -0
  119. package/apps/desktop/cli/gateway-credential.ts +81 -0
  120. package/apps/desktop/cli/gateway-media-artifacts.ts +723 -0
  121. package/apps/desktop/cli/gateway-media-catalog.ts +922 -0
  122. package/apps/desktop/cli/gateway-media-service.ts +3106 -0
  123. package/apps/desktop/cli/gateway-media-signature.ts +122 -0
  124. package/apps/desktop/cli/gateway-provider-options.ts +562 -0
  125. package/apps/desktop/cli/gateway-scene-provider.ts +391 -0
  126. package/apps/desktop/cli/help.ts +501 -0
  127. package/apps/desktop/cli/html-overlay-gpu.ts +97 -0
  128. package/apps/desktop/cli/html-overlay-renderer.ts +2478 -0
  129. package/apps/desktop/cli/io.ts +290 -0
  130. package/apps/desktop/cli/main.ts +123 -0
  131. package/apps/desktop/cli/media-effects-service.ts +885 -0
  132. package/apps/desktop/cli/media-ingest-model.ts +364 -0
  133. package/apps/desktop/cli/media-ingest-platform.ts +227 -0
  134. package/apps/desktop/cli/media-ingest-program.ts +200 -0
  135. package/apps/desktop/cli/media-ingest.ts +30 -0
  136. package/apps/desktop/cli/music-analysis-service.ts +434 -0
  137. package/apps/desktop/cli/mutation-lock.ts +530 -0
  138. package/apps/desktop/cli/native-assets.d.ts +12 -0
  139. package/apps/desktop/cli/native-media-runtime.macos.ts +7 -0
  140. package/apps/desktop/cli/overlay-playback.ts +177 -0
  141. package/apps/desktop/cli/paths.ts +265 -0
  142. package/apps/desktop/cli/portable-surface.ts +242 -0
  143. package/apps/desktop/cli/project-camera-receipt.ts +112 -0
  144. package/apps/desktop/cli/project-face-camera.ts +394 -0
  145. package/apps/desktop/cli/project-inactivity-service.ts +552 -0
  146. package/apps/desktop/cli/project-media-integrity.ts +121 -0
  147. package/apps/desktop/cli/project-renderer.ts +1526 -0
  148. package/apps/desktop/cli/project-service.ts +519 -0
  149. package/apps/desktop/cli/project-state-transaction.ts +520 -0
  150. package/apps/desktop/cli/query-limits.ts +1 -0
  151. package/apps/desktop/cli/recording-controller.ts +1567 -0
  152. package/apps/desktop/cli/recording-daemon.ts +931 -0
  153. package/apps/desktop/cli/recording-ref.ts +56 -0
  154. package/apps/desktop/cli/renamed-environment.ts +6 -0
  155. package/apps/desktop/cli/renderer.ts +1439 -0
  156. package/apps/desktop/cli/run-cli-test-helper.ts +60 -0
  157. package/apps/desktop/cli/scene-analysis-service.ts +879 -0
  158. package/apps/desktop/cli/spatial-compositor-verifier.ts +213 -0
  159. package/apps/desktop/cli/spatial-project-service.ts +90 -0
  160. package/apps/desktop/cli/spatial-scene-service.ts +105 -0
  161. package/apps/desktop/cli/spatial-world-service.ts +39 -0
  162. package/apps/desktop/cli/speech-analysis-service.ts +682 -0
  163. package/apps/desktop/cli/studio-assemble.ts +58 -0
  164. package/apps/desktop/cli/studio-command.ts +60 -0
  165. package/apps/desktop/cli/studio-custody.ts +60 -0
  166. package/apps/desktop/cli/studio-encode.ts +216 -0
  167. package/apps/desktop/cli/studio-exr.ts +52 -0
  168. package/apps/desktop/cli/studio-files.ts +179 -0
  169. package/apps/desktop/cli/studio-output-validation.ts +162 -0
  170. package/apps/desktop/cli/studio-process.ts +145 -0
  171. package/apps/desktop/cli/studio-runtime.ts +77 -0
  172. package/apps/desktop/cli/studio-scaffold.ts +70 -0
  173. package/apps/desktop/cli/studio-service.ts +301 -0
  174. package/apps/desktop/cli/studio-spatial-asset.ts +248 -0
  175. package/apps/desktop/cli/studio-template-names.ts +3 -0
  176. package/apps/desktop/cli/studio-workflow.ts +19 -0
  177. package/apps/desktop/cli/time.ts +25 -0
  178. package/apps/desktop/cli/workflow-code.ts +584 -0
  179. package/apps/desktop/cli/workflow-runs.ts +576 -0
  180. package/apps/desktop/code/advanced.ts +139 -0
  181. package/apps/desktop/code/application-node-planner.ts +1288 -0
  182. package/apps/desktop/code/compiler.ts +329 -0
  183. package/apps/desktop/code/contracts.ts +13 -0
  184. package/apps/desktop/code/creative-recipes.ts +94 -0
  185. package/apps/desktop/code/define-workflow.ts +115 -0
  186. package/apps/desktop/code/editing.ts +284 -0
  187. package/apps/desktop/code/file-candidate-provenance.ts +420 -0
  188. package/apps/desktop/code/file-candidate.ts +69 -0
  189. package/apps/desktop/code/graph-builder.ts +10 -0
  190. package/apps/desktop/code/host-resource-policy.ts +55 -0
  191. package/apps/desktop/code/plan-contracts.ts +159 -0
  192. package/apps/desktop/code/planning.ts +361 -0
  193. package/apps/desktop/code/public.ts +134 -0
  194. package/apps/desktop/code/reference-projection.ts +62 -0
  195. package/apps/desktop/code/run-contracts.ts +466 -0
  196. package/apps/desktop/code/run-store.ts +2478 -0
  197. package/apps/desktop/code/runtime-identity.ts +328 -0
  198. package/apps/desktop/code/scheduler.ts +3067 -0
  199. package/apps/desktop/code/semantic-builder.ts +1436 -0
  200. package/apps/desktop/code/source-bundle.ts +1641 -0
  201. package/apps/desktop/code/source-typecheck.ts +211 -0
  202. package/apps/desktop/code/testing.ts +79 -0
  203. package/apps/desktop/code/worker-client.ts +1675 -0
  204. package/apps/desktop/code/worker-effects.ts +141 -0
  205. package/apps/desktop/code/worker-entry.ts +435 -0
  206. package/apps/desktop/code/worker-lease-guardian.ts +179 -0
  207. package/apps/desktop/code/worker-process-identity.ts +484 -0
  208. package/apps/desktop/code/worker-protocol.ts +211 -0
  209. package/apps/desktop/code/workflow-effects.ts +132 -0
  210. package/apps/desktop/contracts/analysis.ts +949 -0
  211. package/apps/desktop/contracts/edit.ts +576 -0
  212. package/apps/desktop/contracts/index.ts +9 -0
  213. package/apps/desktop/contracts/media-effects.ts +139 -0
  214. package/apps/desktop/contracts/project-render.ts +644 -0
  215. package/apps/desktop/contracts/project.ts +740 -0
  216. package/apps/desktop/contracts/recording.ts +1434 -0
  217. package/apps/desktop/contracts/render.ts +334 -0
  218. package/apps/desktop/contracts/runtime.ts +155 -0
  219. package/apps/desktop/contracts/spatial-compositor.ts +58 -0
  220. package/apps/desktop/contracts/spatial-project.ts +159 -0
  221. package/apps/desktop/contracts/spatial-world.ts +48 -0
  222. package/apps/desktop/contracts/video-effects.ts +120 -0
  223. package/apps/desktop/core/alignment-apply.ts +400 -0
  224. package/apps/desktop/core/audio-alignment.ts +466 -0
  225. package/apps/desktop/core/auto-zoom.ts +209 -0
  226. package/apps/desktop/core/canonical-json.ts +14 -0
  227. package/apps/desktop/core/emoji.ts +210 -0
  228. package/apps/desktop/core/events.ts +409 -0
  229. package/apps/desktop/core/face-framing.ts +533 -0
  230. package/apps/desktop/core/face-tracks.ts +326 -0
  231. package/apps/desktop/core/inactivity.ts +137 -0
  232. package/apps/desktop/core/index.ts +30 -0
  233. package/apps/desktop/core/intervals.ts +123 -0
  234. package/apps/desktop/core/montage.ts +236 -0
  235. package/apps/desktop/core/music-analysis.ts +518 -0
  236. package/apps/desktop/core/plan.ts +244 -0
  237. package/apps/desktop/core/project-auto-zoom.ts +75 -0
  238. package/apps/desktop/core/project-camera-spatial.ts +676 -0
  239. package/apps/desktop/core/project-camera.ts +403 -0
  240. package/apps/desktop/core/project-captions.ts +888 -0
  241. package/apps/desktop/core/project-inactivity.ts +125 -0
  242. package/apps/desktop/core/project-metadata.ts +927 -0
  243. package/apps/desktop/core/project-plan.ts +375 -0
  244. package/apps/desktop/core/project-render-plan.ts +303 -0
  245. package/apps/desktop/core/project-time.ts +469 -0
  246. package/apps/desktop/core/render-plan.ts +442 -0
  247. package/apps/desktop/core/scene-cache.ts +174 -0
  248. package/apps/desktop/core/scene-sampling.ts +448 -0
  249. package/apps/desktop/core/spatial-compositor.ts +22 -0
  250. package/apps/desktop/core/spatial-durability.ts +99 -0
  251. package/apps/desktop/core/spatial-project.ts +169 -0
  252. package/apps/desktop/core/spatial-shot-clock.ts +32 -0
  253. package/apps/desktop/core/speech-analysis.ts +333 -0
  254. package/apps/desktop/core/storage.ts +970 -0
  255. package/apps/desktop/core/time-map.ts +91 -0
  256. package/apps/desktop/core/time.ts +61 -0
  257. package/apps/desktop/core/video-effects.ts +660 -0
  258. package/apps/desktop/dist/cli/NebulaSans-Bold-26se8aek.otf +0 -0
  259. package/apps/desktop/dist/cli/NebulaSans-Bold-bcz7y08t.woff2 +0 -0
  260. package/apps/desktop/dist/cli/NebulaSans-Book-5ax05zvn.woff2 +0 -0
  261. package/apps/desktop/dist/cli/NebulaSans-Book-8cenzchw.otf +0 -0
  262. package/apps/desktop/dist/cli/main.js +5086 -0
  263. package/apps/desktop/html-overlay/catalog.ts +171 -0
  264. package/apps/desktop/html-overlay/contracts.ts +372 -0
  265. package/apps/desktop/html-overlay/execution-profile.ts +77 -0
  266. package/apps/desktop/html-overlay/index.ts +98 -0
  267. package/apps/desktop/html-overlay/libraries.ts +263 -0
  268. package/apps/desktop/html-overlay/random.ts +49 -0
  269. package/apps/desktop/html-overlay/runtime.ts +813 -0
  270. package/apps/desktop/html-overlay/scaffolds.ts +792 -0
  271. package/apps/desktop/html-overlay/spark-runtime.ts +88 -0
  272. package/apps/desktop/html-overlay/spatial-splat-runtime.ts +60 -0
  273. package/apps/desktop/html-overlay/spatial.ts +720 -0
  274. package/apps/desktop/studio/assets/README.md +39 -0
  275. package/apps/desktop/studio/assets/contracts.ts +129 -0
  276. package/apps/desktop/studio/assets/network.ts +64 -0
  277. package/apps/desktop/studio/assets/poly-haven.ts +148 -0
  278. package/apps/desktop/studio/drivers/blender_driver.py +466 -0
  279. package/apps/desktop/studio/drivers/cadquery_driver.py +176 -0
  280. package/apps/desktop/studio/education/driver.py +308 -0
  281. package/apps/desktop/studio/education/lesson.py +180 -0
  282. package/apps/desktop/studio/education/toolkit.py +249 -0
  283. package/apps/desktop/workflows/chaptered-demo.ts +48 -0
  284. package/apps/desktop/workflows/creative-iteration.ts +107 -0
  285. package/apps/desktop/workflows/creative-selection.ts +92 -0
  286. package/apps/desktop/workflows/fragments.ts +486 -0
  287. package/apps/desktop/workflows/index.ts +93 -0
  288. package/apps/desktop/workflows/polished-screen-demo.ts +128 -0
  289. package/apps/desktop/workflows/scene-direction.ts +12 -0
  290. package/apps/desktop/workflows/social-variants.ts +75 -0
  291. package/apps/desktop/workflows/talking-head-cleanup.ts +53 -0
  292. package/dist/NebulaSans-Bold-26se8aek.otf +0 -0
  293. package/dist/NebulaSans-Bold-bcz7y08t.woff2 +0 -0
  294. package/dist/NebulaSans-Book-5ax05zvn.woff2 +0 -0
  295. package/dist/NebulaSans-Book-8cenzchw.otf +0 -0
  296. package/dist/cli.js +479 -0
  297. package/dist/code/advanced.js +270 -0
  298. package/dist/code/index.js +2240 -0
  299. package/dist/generate.js +24 -0
  300. package/dist/host-resources.js +26 -0
  301. package/dist/index-42zsesc1.js +2257 -0
  302. package/dist/index-7308egqr.js +48 -0
  303. package/dist/index-8txs6fkn.js +712 -0
  304. package/dist/index-mcy8z0br.js +192 -0
  305. package/dist/index-p63wavx0.js +2029 -0
  306. package/dist/index-r7gdhmsp.js +424 -0
  307. package/dist/index-sh6xbav6.js +1219 -0
  308. package/dist/index-z1w83f81.js +4 -0
  309. package/dist/index-z7239b4h.js +2363 -0
  310. package/dist/index-zfeg9hmw.js +2013 -0
  311. package/dist/index.js +241 -0
  312. package/dist/operations.js +29 -0
  313. package/dist/skill-install-dh0ntqba.js +10 -0
  314. package/dist/vectorize/worker.js +142 -0
  315. package/dist/workflow.js +16 -0
  316. package/docs/README.md +38 -0
  317. package/docs/studio.md +215 -0
  318. package/examples/studio/blender/README.md +26 -0
  319. package/examples/studio/blender/character.py +152 -0
  320. package/examples/studio/blender/cloth.py +101 -0
  321. package/examples/studio/blender/color_chart.py +29 -0
  322. package/examples/studio/blender/fluid.py +78 -0
  323. package/examples/studio/blender/import_model.py +74 -0
  324. package/examples/studio/blender/product.py +53 -0
  325. package/examples/studio/blender/shaded_street.py +481 -0
  326. package/examples/studio/blender/studio_scene.py +147 -0
  327. package/examples/studio/cadquery/README.md +13 -0
  328. package/examples/studio/cadquery/bracket.py +36 -0
  329. package/examples/studio/cadquery/import_step.py +12 -0
  330. package/examples/studio/education/README.md +46 -0
  331. package/examples/studio/education/lesson.json +99 -0
  332. package/examples/studio/education/scene.py +7 -0
  333. package/examples/studio/hybrid-scene.ts +96 -0
  334. package/examples/studio/native-workflow.ts +14 -0
  335. package/examples/studio/vgpu/README.md +64 -0
  336. package/examples/studio/vgpu/heat-field.mjs +290 -0
  337. package/package.json +304 -0
  338. package/packages/scene/src/index.ts +1 -0
  339. package/packages/scene/src/scene.ts +215 -0
  340. package/schema/diagram.schema.json +334 -0
  341. package/skills/slopcamera/SKILL.md +45 -0
  342. package/skills/slopcamera/agents/openai.yaml +4 -0
  343. package/skills/slopcamera/references/customization.md +77 -0
  344. package/skills/slopcamera/references/diagrams.md +153 -0
  345. package/skills/slopcamera/references/directed-scenes.md +42 -0
  346. package/skills/slopcamera/references/directing-video.md +18 -0
  347. package/skills/slopcamera/references/educational-video.md +26 -0
  348. package/skills/slopcamera/references/gateway-media.md +134 -0
  349. package/skills/slopcamera/references/install.md +62 -0
  350. package/skills/slopcamera/references/native-studio.md +31 -0
  351. package/skills/slopcamera/references/reference-led-3d.md +140 -0
  352. package/skills/slopcamera/references/rubber-stamp-examples/poster-example-1.jpg +0 -0
  353. package/skills/slopcamera/references/rubber-stamp-examples/poster-example-2.jpg +0 -0
  354. package/skills/slopcamera/references/rubber-stamp-examples/stamp-style-1.png +0 -0
  355. package/skills/slopcamera/references/rubber-stamp-examples/stamp-style-2.png +0 -0
  356. package/skills/slopcamera/references/rubber-stamp-field-notes.md +144 -0
  357. package/skills/slopcamera/references/vectorization.md +29 -0
  358. package/skills/slopcamera/references/video-projects.md +187 -0
  359. package/skills/slopcamera/references/visual-communication.md +120 -0
  360. package/skills/slopcamera/references/workflows-sdk.md +45 -0
  361. package/skills/slopcamera/scripts/compose-rubber-stamp-field-note.ts +229 -0
  362. package/src/artifacts.ts +106 -0
  363. package/src/assets/fonts/nebula-sans/LICENSE.txt +96 -0
  364. package/src/assets/fonts/nebula-sans/NebulaSans-Bold.otf +0 -0
  365. package/src/assets/fonts/nebula-sans/NebulaSans-Bold.woff2 +0 -0
  366. package/src/assets/fonts/nebula-sans/NebulaSans-Book.otf +0 -0
  367. package/src/assets/fonts/nebula-sans/NebulaSans-Book.woff2 +0 -0
  368. package/src/assets/fonts/nebula-sans/PROVENANCE.md +21 -0
  369. package/src/assets.d.ts +9 -0
  370. package/src/cli.ts +601 -0
  371. package/src/cloud-errors.ts +26 -0
  372. package/src/code/advanced.ts +10 -0
  373. package/src/code/boundary.ts +21 -0
  374. package/src/code/canonical-json.ts +129 -0
  375. package/src/code/compiler.ts +953 -0
  376. package/src/code/contracts.ts +576 -0
  377. package/src/code/define-workflow.ts +334 -0
  378. package/src/code/errors.ts +41 -0
  379. package/src/code/graph-builder.ts +945 -0
  380. package/src/code/index.ts +121 -0
  381. package/src/code/json-snapshot.ts +899 -0
  382. package/src/code/json-utf8.ts +88 -0
  383. package/src/code/portable-builder.ts +136 -0
  384. package/src/code/projection.ts +378 -0
  385. package/src/code/public-operations.ts +450 -0
  386. package/src/code/runtime.ts +580 -0
  387. package/src/code/sha256.ts +36 -0
  388. package/src/config.ts +180 -0
  389. package/src/desktop.ts +253 -0
  390. package/src/fs.ts +10 -0
  391. package/src/generate.ts +704 -0
  392. package/src/host-resource-posix.ts +161 -0
  393. package/src/host-resources.ts +1749 -0
  394. package/src/icons.ts +58 -0
  395. package/src/index.ts +200 -0
  396. package/src/label-layout.ts +130 -0
  397. package/src/layout.ts +413 -0
  398. package/src/lint.ts +127 -0
  399. package/src/mcp/boundary.ts +354 -0
  400. package/src/mcp/index.ts +33 -0
  401. package/src/mcp/server.ts +272 -0
  402. package/src/mcp/tools.ts +963 -0
  403. package/src/mcp/types.ts +71 -0
  404. package/src/operations.ts +775 -0
  405. package/src/parse.ts +861 -0
  406. package/src/process-environment.ts +17 -0
  407. package/src/render.ts +437 -0
  408. package/src/skill-install.ts +49 -0
  409. package/src/spatial-scene/authoring.ts +27 -0
  410. package/src/spatial-scene/camera-track.ts +83 -0
  411. package/src/spatial-scene/contracts.ts +277 -0
  412. package/src/spatial-scene/evaluate.ts +145 -0
  413. package/src/spatial-scene/gltf.ts +558 -0
  414. package/src/spatial-scene/identity.ts +260 -0
  415. package/src/spatial-scene/index.ts +10 -0
  416. package/src/spatial-scene/inspect.ts +68 -0
  417. package/src/spatial-scene/math.ts +280 -0
  418. package/src/spatial-scene/patch.ts +131 -0
  419. package/src/spatial-scene/time.ts +53 -0
  420. package/src/studio/contracts.ts +129 -0
  421. package/src/studio/index.ts +2 -0
  422. package/src/studio/plan.ts +156 -0
  423. package/src/studio/shared.ts +41 -0
  424. package/src/theme.ts +68 -0
  425. package/src/tldr.ts +474 -0
  426. package/src/types.ts +212 -0
  427. package/src/vectorize/archive.ts +101 -0
  428. package/src/vectorize/command.ts +797 -0
  429. package/src/vectorize/index.ts +27 -0
  430. package/src/vectorize/limits.ts +61 -0
  431. package/src/vectorize/metrics.ts +351 -0
  432. package/src/vectorize/pixels.ts +323 -0
  433. package/src/vectorize/supervisor.ts +339 -0
  434. package/src/vectorize/svg.ts +188 -0
  435. package/src/vectorize/tool.ts +542 -0
  436. package/src/vectorize/types.ts +113 -0
  437. package/src/vectorize/vectorize.ts +634 -0
  438. package/src/vectorize/worker-protocol.ts +40 -0
  439. package/src/vectorize/worker.ts +209 -0
  440. package/src/version.ts +1 -0
  441. package/src/workflow.ts +409 -0
@@ -0,0 +1,252 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { constants } from "node:fs";
3
+ import { lstat, open, rm } from "node:fs/promises";
4
+ import { join } from "node:path";
5
+
6
+ import {
7
+ AnalysisSubjectSchema,
8
+ type AudioAlignmentAnalysisV1,
9
+ type AnalysisSubject,
10
+ type ProjectAssetV1,
11
+ type ProjectPlacementV1,
12
+ type VideoProjectV1,
13
+ } from "../contracts";
14
+ import { canonicalJsonSha256, hashPlacementSync, type EnvelopeSeries } from "../core";
15
+ import { CliError } from "./errors";
16
+ import type { ProcessRunner } from "./io";
17
+ import { ensurePrivateDirectory } from "./paths";
18
+ import { resolveVerifiedProjectMedia } from "./project-media-integrity";
19
+
20
+ const ALIGNMENT_SAMPLE_RATE_HZ = 8_000;
21
+ const ALIGNMENT_HOP_SAMPLES = 400;
22
+ const MAXIMUM_PCM_BYTES = 32 * 1024 * 1024 * 1024;
23
+
24
+ function parseSubjectReference(reference: string): { readonly assetId: string; readonly streamId: string } {
25
+ const separator = reference.indexOf(":");
26
+ if (separator <= 0 || separator === reference.length - 1 || reference.indexOf(":", separator + 1) !== -1) {
27
+ throw new CliError("usage", `Media stream must use <asset-id>:<stream-id>: ${reference}`);
28
+ }
29
+ return { assetId: reference.slice(0, separator), streamId: reference.slice(separator + 1) };
30
+ }
31
+
32
+ export function resolveAudioAnalysisSubject(
33
+ project: VideoProjectV1,
34
+ reference: string,
35
+ ): { readonly asset: ProjectAssetV1; readonly stream: Extract<ProjectAssetV1["streams"][number], { readonly kind: "audio" }>; readonly subject: AnalysisSubject } {
36
+ const selected = parseSubjectReference(reference);
37
+ const asset = project.assets.find(candidate => candidate.assetId === selected.assetId);
38
+ if (asset === undefined) throw new CliError("not-found", `Unknown project asset: ${selected.assetId}`);
39
+ const stream = asset.streams.find(candidate => candidate.streamId === selected.streamId);
40
+ if (stream?.kind !== "audio") throw new CliError("not-found", `Unknown audio stream on ${selected.assetId}: ${selected.streamId}`);
41
+ const subject = AnalysisSubjectSchema.parse({
42
+ assetId: asset.assetId,
43
+ integritySha256: canonicalJsonSha256({ assetDurationUs: asset.durationUs, stream }),
44
+ streamId: stream.streamId,
45
+ });
46
+ return { asset, stream, subject };
47
+ }
48
+
49
+ export function alignmentInputDigest(input: {
50
+ readonly config: AudioAlignmentAnalysisV1["config"];
51
+ readonly reference: AnalysisSubject;
52
+ readonly referencePlacement: ProjectPlacementV1;
53
+ readonly target: AnalysisSubject;
54
+ readonly targetPlacement: ProjectPlacementV1;
55
+ }): string {
56
+ return canonicalJsonSha256({
57
+ config: input.config,
58
+ reference: input.reference,
59
+ referencePlacement: {
60
+ assetId: input.referencePlacement.assetId,
61
+ assetRange: input.referencePlacement.assetRange,
62
+ placementId: input.referencePlacement.placementId,
63
+ syncMapSha256: hashPlacementSync(input.referencePlacement),
64
+ },
65
+ target: input.target,
66
+ targetPlacement: {
67
+ assetId: input.targetPlacement.assetId,
68
+ assetRange: input.targetPlacement.assetRange,
69
+ placementId: input.targetPlacement.placementId,
70
+ },
71
+ });
72
+ }
73
+
74
+ function seconds(microseconds: number): string {
75
+ return (microseconds / 1_000_000).toFixed(6).replace(/0+$/u, "").replace(/\.$/u, "");
76
+ }
77
+
78
+ export async function decodeAlignmentPcm(
79
+ projectDirectory: string,
80
+ repositoryRoot: string,
81
+ ffmpeg: string,
82
+ runner: ProcessRunner,
83
+ stream: Extract<ProjectAssetV1["streams"][number], { readonly kind: "audio" }>,
84
+ maximumOutputBytes = MAXIMUM_PCM_BYTES,
85
+ ): Promise<string> {
86
+ if (!Number.isSafeInteger(maximumOutputBytes) || maximumOutputBytes < Float32Array.BYTES_PER_ELEMENT) {
87
+ throw new CliError("usage", "Decoded alignment PCM byte limit must be a positive safe integer.");
88
+ }
89
+ const assetDurationUs = stream.segments.at(-1)?.assetRange.endUs ?? 0;
90
+ const expectedSamples = Math.round(assetDurationUs * ALIGNMENT_SAMPLE_RATE_HZ / 1_000_000);
91
+ const expectedBytes = expectedSamples * Float32Array.BYTES_PER_ELEMENT;
92
+ if (!Number.isSafeInteger(expectedBytes) || expectedBytes <= 0 || expectedBytes > maximumOutputBytes) {
93
+ throw new CliError(
94
+ "invalid-data",
95
+ `Decoded alignment PCM would exceed its ${maximumOutputBytes}-byte resource bound.`,
96
+ { expectedBytes, maximumOutputBytes },
97
+ );
98
+ }
99
+ const cacheRoot = join(projectDirectory, "analysis", "cache");
100
+ await ensurePrivateDirectory(join(projectDirectory, "analysis"));
101
+ await ensurePrivateDirectory(cacheRoot);
102
+ const output = join(cacheRoot, `.alignment-${randomUUID()}.f32le`);
103
+ const inputArguments: string[] = [];
104
+ const filters: string[] = [];
105
+ const labels: string[] = [];
106
+ let priorEndUs = 0;
107
+ for (const [index, segment] of stream.segments.entries()) {
108
+ if (segment.assetRange.startUs > priorEndUs) {
109
+ const gap = `gap_${index}`;
110
+ filters.push(`anullsrc=r=${ALIGNMENT_SAMPLE_RATE_HZ}:cl=mono:d=${seconds(segment.assetRange.startUs - priorEndUs)}[${gap}]`);
111
+ labels.push(gap);
112
+ }
113
+ inputArguments.push("-i", await resolveVerifiedProjectMedia({
114
+ expected: { bytes: segment.bytes, sha256: segment.sha256 },
115
+ label: `Audio segment ${segment.path}:${segment.streamIndex}`,
116
+ path: segment.path,
117
+ repositoryRoot,
118
+ }));
119
+ const label = `audio_${index}`;
120
+ filters.push(
121
+ `[${index}:${segment.streamIndex}]atrim=start=${seconds(segment.fileRange.startUs)}:end=${seconds(segment.fileRange.endUs)},asetpts=PTS-STARTPTS,aresample=${ALIGNMENT_SAMPLE_RATE_HZ},aformat=sample_fmts=flt:channel_layouts=mono[${label}]`,
122
+ );
123
+ labels.push(label);
124
+ priorEndUs = segment.assetRange.endUs;
125
+ }
126
+ if (labels.length === 0) throw new CliError("invalid-data", `Audio stream ${stream.streamId} has no media segments.`);
127
+ filters.push(`${labels.map(label => `[${label}]`).join("")}concat=n=${labels.length}:v=0:a=1[decoded]`);
128
+ const result = await runner.run([
129
+ ffmpeg,
130
+ "-hide_banner", "-nostdin", "-y",
131
+ ...inputArguments,
132
+ "-filter_complex", filters.join(";"),
133
+ "-map", "[decoded]",
134
+ "-f", "f32le",
135
+ "-ac", "1",
136
+ "-ar", String(ALIGNMENT_SAMPLE_RATE_HZ),
137
+ "-t", seconds(assetDurationUs),
138
+ "-fs", String(maximumOutputBytes),
139
+ output,
140
+ ], { maxOutputBytes: 1_000_000 });
141
+ if (result.exitCode !== 0) {
142
+ await rm(output, { force: true });
143
+ throw new CliError("subprocess", `FFmpeg audio decode failed: ${result.stderr.trim().slice(-4_000) || `exit ${result.exitCode}`}`);
144
+ }
145
+ const details = await lstat(output);
146
+ const actualSamples = details.size / Float32Array.BYTES_PER_ELEMENT;
147
+ const sampleTolerance = Math.max(2, stream.segments.length * 2);
148
+ if (
149
+ !details.isFile()
150
+ || details.isSymbolicLink()
151
+ || details.size <= 0
152
+ || details.size > maximumOutputBytes
153
+ || details.size % Float32Array.BYTES_PER_ELEMENT !== 0
154
+ || Math.abs(actualSamples - expectedSamples) > sampleTolerance
155
+ ) {
156
+ await rm(output, { force: true });
157
+ throw new CliError(
158
+ "invalid-data",
159
+ "FFmpeg alignment decode produced unsafe, oversized, or incomplete PCM timeline coverage.",
160
+ { actualSamples, expectedSamples, sampleTolerance },
161
+ );
162
+ }
163
+ return output;
164
+ }
165
+
166
+ export async function readAlignmentEnvelope(path: string): Promise<EnvelopeSeries> {
167
+ const handle = await open(path, constants.O_RDONLY | constants.O_NOFOLLOW);
168
+ const values: number[] = [];
169
+ const buffer = Buffer.allocUnsafe(256 * 1024);
170
+ let carry = Buffer.alloc(0);
171
+ let samplesInHop = 0;
172
+ let squareSum = 0;
173
+ let differenceSum = 0;
174
+ let priorSample = 0;
175
+ let totalBytes = 0;
176
+ try {
177
+ while (true) {
178
+ const read = await handle.read(buffer, 0, buffer.length, null);
179
+ if (read.bytesRead === 0) break;
180
+ totalBytes += read.bytesRead;
181
+ if (totalBytes > MAXIMUM_PCM_BYTES) throw new CliError("invalid-data", "Decoded alignment PCM exceeds its 32 GiB bound.");
182
+ const bytes = carry.length === 0
183
+ ? buffer.subarray(0, read.bytesRead)
184
+ : Buffer.concat([carry, buffer.subarray(0, read.bytesRead)]);
185
+ const usable = bytes.length - bytes.length % 4;
186
+ for (let offset = 0; offset < usable; offset += 4) {
187
+ const sample = bytes.readFloatLE(offset);
188
+ if (!Number.isFinite(sample)) throw new CliError("invalid-data", "Decoded alignment PCM contains a non-finite sample.");
189
+ squareSum += sample * sample;
190
+ differenceSum += Math.abs(sample - priorSample);
191
+ priorSample = sample;
192
+ samplesInHop += 1;
193
+ if (samplesInHop === ALIGNMENT_HOP_SAMPLES) {
194
+ const rms = Math.sqrt(squareSum / samplesInHop);
195
+ values.push(rms + differenceSum / samplesInHop);
196
+ samplesInHop = 0;
197
+ squareSum = 0;
198
+ differenceSum = 0;
199
+ }
200
+ }
201
+ carry = Buffer.from(bytes.subarray(usable));
202
+ }
203
+ if (carry.length !== 0) throw new CliError("invalid-data", "Decoded alignment PCM has a partial float sample.");
204
+ if (samplesInHop > 0) values.push(Math.sqrt(squareSum / samplesInHop) + differenceSum / samplesInHop);
205
+ } finally {
206
+ await handle.close();
207
+ }
208
+ return {
209
+ hopUs: Math.round(ALIGNMENT_HOP_SAMPLES * 1_000_000 / ALIGNMENT_SAMPLE_RATE_HZ),
210
+ startUs: 0,
211
+ values,
212
+ };
213
+ }
214
+
215
+ export async function withAlignmentEnvelopes<T>(
216
+ options: {
217
+ readonly ffmpeg: string;
218
+ readonly projectDirectory: string;
219
+ readonly reference: Extract<ProjectAssetV1["streams"][number], { readonly kind: "audio" }>;
220
+ readonly repositoryRoot: string;
221
+ readonly runner: ProcessRunner;
222
+ readonly target: Extract<ProjectAssetV1["streams"][number], { readonly kind: "audio" }>;
223
+ },
224
+ callback: (reference: EnvelopeSeries, target: EnvelopeSeries) => Promise<T> | T,
225
+ ): Promise<T> {
226
+ const paths: string[] = [];
227
+ try {
228
+ const referencePath = await decodeAlignmentPcm(
229
+ options.projectDirectory,
230
+ options.repositoryRoot,
231
+ options.ffmpeg,
232
+ options.runner,
233
+ options.reference,
234
+ );
235
+ paths.push(referencePath);
236
+ const targetPath = await decodeAlignmentPcm(
237
+ options.projectDirectory,
238
+ options.repositoryRoot,
239
+ options.ffmpeg,
240
+ options.runner,
241
+ options.target,
242
+ );
243
+ paths.push(targetPath);
244
+ const [reference, target] = await Promise.all([
245
+ readAlignmentEnvelope(referencePath),
246
+ readAlignmentEnvelope(targetPath),
247
+ ]);
248
+ return await callback(reference, target);
249
+ } finally {
250
+ await Promise.all(paths.map(async path => await rm(path, { force: true })));
251
+ }
252
+ }
@@ -0,0 +1,39 @@
1
+ import { CliError } from "./errors";
2
+
3
+ /** Map asynchronous work with a fixed process-safe concurrency ceiling. */
4
+ export async function mapBounded<Value, Result>(
5
+ values: readonly Value[],
6
+ concurrency: number,
7
+ operation: (value: Value, index: number) => Promise<Result>,
8
+ ): Promise<readonly Result[]> {
9
+ if (!Number.isSafeInteger(concurrency) || concurrency < 1 || concurrency > 64) {
10
+ throw new CliError("invalid-data", "Bounded-map concurrency must be an integer from 1 through 64.");
11
+ }
12
+ if (values.length === 0) return [];
13
+
14
+ const results = new Array<Result>(values.length);
15
+ let nextIndex = 0;
16
+ let failed = false;
17
+ let firstFailure: unknown;
18
+ const worker = async (): Promise<void> => {
19
+ while (!failed) {
20
+ const index = nextIndex;
21
+ nextIndex += 1;
22
+ if (index >= values.length) return;
23
+ try {
24
+ results[index] = await operation(values[index]!, index);
25
+ } catch (error) {
26
+ if (!failed) {
27
+ failed = true;
28
+ firstFailure = error;
29
+ }
30
+ }
31
+ }
32
+ };
33
+ await Promise.all(Array.from(
34
+ { length: Math.min(concurrency, values.length) },
35
+ async () => await worker(),
36
+ ));
37
+ if (failed) throw firstFailure;
38
+ return results;
39
+ }
@@ -0,0 +1,256 @@
1
+ import { createHash } from "node:crypto";
2
+ import { createReadStream } from "node:fs";
3
+ import { createInterface } from "node:readline";
4
+ import {
5
+ parseRecordingEventV1,
6
+ type EditPlanV1,
7
+ type RecordingEventV1,
8
+ type RecordingManifestV1,
9
+ } from "../contracts";
10
+ import {
11
+ createNodeBundleFileSystem,
12
+ hashEditPlan,
13
+ loadEditPlan,
14
+ loadRecordingManifest,
15
+ type BundleFileSystem,
16
+ } from "../core";
17
+ import { CliError } from "./errors";
18
+ import { resolveSafePath } from "./paths";
19
+ import { MAX_EVENT_QUERY_LIMIT } from "./query-limits";
20
+ import { resolveRecordingDirectory, type RecordingDirectory } from "./recording-ref";
21
+
22
+ export const CURRENT_EDIT_PLAN_PATH = "edits/current.json";
23
+
24
+ export interface OpenRecording {
25
+ readonly directory: RecordingDirectory;
26
+ readonly fileSystem: BundleFileSystem;
27
+ readonly manifest: RecordingManifestV1;
28
+ }
29
+
30
+ function compareEventOrder(left: RecordingEventV1, right: RecordingEventV1): number {
31
+ return left.sourceTimeUs - right.sourceTimeUs || left.sequence - right.sequence;
32
+ }
33
+
34
+ /** Retain the earliest N events with the latest retained event at heap[0]. */
35
+ function retainEarliestEvent(
36
+ heap: RecordingEventV1[],
37
+ event: RecordingEventV1,
38
+ limit: number,
39
+ ): void {
40
+ if (heap.length < limit) {
41
+ heap.push(event);
42
+ let index = heap.length - 1;
43
+ while (index > 0) {
44
+ const parent = Math.floor((index - 1) / 2);
45
+ if (compareEventOrder(heap[parent]!, heap[index]!) >= 0) break;
46
+ [heap[parent], heap[index]] = [heap[index]!, heap[parent]!];
47
+ index = parent;
48
+ }
49
+ return;
50
+ }
51
+ if (compareEventOrder(event, heap[0]!) >= 0) return;
52
+ heap[0] = event;
53
+ let index = 0;
54
+ for (;;) {
55
+ const left = index * 2 + 1;
56
+ if (left >= heap.length) return;
57
+ const right = left + 1;
58
+ let latest = left;
59
+ if (right < heap.length && compareEventOrder(heap[right]!, heap[left]!) > 0) latest = right;
60
+ if (compareEventOrder(heap[index]!, heap[latest]!) >= 0) return;
61
+ [heap[index], heap[latest]] = [heap[latest]!, heap[index]!];
62
+ index = latest;
63
+ }
64
+ }
65
+
66
+ export async function openRecording(artifactRoot: string, reference: string): Promise<OpenRecording> {
67
+ const directory = await resolveRecordingDirectory(artifactRoot, reference);
68
+ const fileSystem = createNodeBundleFileSystem(directory.path);
69
+ try {
70
+ const manifest = await loadRecordingManifest(fileSystem);
71
+ if (manifest.recordingId !== directory.id) {
72
+ throw new CliError(
73
+ "invalid-data",
74
+ `Bundle directory ${directory.id} contains manifest for ${manifest.recordingId}.`,
75
+ );
76
+ }
77
+ return { directory, fileSystem, manifest };
78
+ } catch (error) {
79
+ if (error instanceof CliError) throw error;
80
+ throw new CliError("invalid-data", `Could not load ${directory.id}/manifest.json: ${String(error)}`);
81
+ }
82
+ }
83
+
84
+ export async function loadCurrentPlan(recording: OpenRecording): Promise<EditPlanV1> {
85
+ try {
86
+ return await loadEditPlan(recording.fileSystem, CURRENT_EDIT_PLAN_PATH);
87
+ } catch (error) {
88
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") {
89
+ throw new CliError(
90
+ "not-found",
91
+ `Recording ${recording.manifest.recordingId} has no edit plan. Run: slopcamera edit ${recording.manifest.recordingId} init`,
92
+ );
93
+ }
94
+ throw error;
95
+ }
96
+ }
97
+
98
+ export async function tryLoadCurrentPlan(recording: OpenRecording): Promise<EditPlanV1 | null> {
99
+ try {
100
+ return await loadEditPlan(recording.fileSystem, CURRENT_EDIT_PLAN_PATH);
101
+ } catch (error) {
102
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") return null;
103
+ throw error;
104
+ }
105
+ }
106
+
107
+ export async function loadRecordingEvents(
108
+ recording: OpenRecording,
109
+ options: {
110
+ readonly endUs?: number;
111
+ readonly limit?: number;
112
+ readonly startUs?: number;
113
+ readonly types?: readonly RecordingEventV1["type"][];
114
+ } = {},
115
+ ): Promise<readonly RecordingEventV1[]> {
116
+ if (
117
+ options.limit !== undefined
118
+ && (!Number.isSafeInteger(options.limit) || options.limit <= 0 || options.limit > MAX_EVENT_QUERY_LIMIT)
119
+ ) {
120
+ throw new CliError(
121
+ "usage",
122
+ `Event query limit must be a positive integer no greater than ${MAX_EVENT_QUERY_LIMIT}.`,
123
+ );
124
+ }
125
+ const startUs = options.startUs ?? 0;
126
+ const endUs = options.endUs ?? Number.MAX_SAFE_INTEGER;
127
+ if (!Number.isSafeInteger(startUs) || !Number.isSafeInteger(endUs) || startUs < 0 || endUs < startUs) {
128
+ throw new CliError("usage", "Event query source-time bounds are invalid.");
129
+ }
130
+ const acceptedTypes = options.types === undefined ? null : new Set(options.types);
131
+ const events: RecordingEventV1[] = [];
132
+ const streams = recording.manifest.eventStreams.filter((stream) =>
133
+ options.types === undefined || stream.eventKinds.some((kind) => options.types!.includes(kind))
134
+ );
135
+ for (const stream of streams) {
136
+ const path = await resolveSafePath(recording.directory.path, stream.path);
137
+ const input = createReadStream(path);
138
+ const hash = createHash("sha256");
139
+ let bytes = 0;
140
+ input.on("data", (chunk: Buffer | string) => {
141
+ const data = typeof chunk === "string" ? Buffer.from(chunk) : chunk;
142
+ bytes += data.length;
143
+ hash.update(data);
144
+ });
145
+ const lines = createInterface({ crlfDelay: Number.POSITIVE_INFINITY, input });
146
+ let lineNumber = 0;
147
+ let parsedCount = 0;
148
+ let priorSequence = -1;
149
+ let priorSourceTimeUs = -1;
150
+ try {
151
+ for await (const line of lines) {
152
+ lineNumber += 1;
153
+ if (line.trim() === "") continue;
154
+ if (Buffer.byteLength(line) > 1_048_576) {
155
+ throw new CliError("invalid-data", `${stream.path}:${lineNumber} exceeds the 1 MiB event-line bound.`);
156
+ }
157
+ let value: unknown;
158
+ try {
159
+ value = JSON.parse(line) as unknown;
160
+ } catch (error) {
161
+ throw new CliError("invalid-data", `${stream.path}:${lineNumber} is not valid JSON: ${String(error)}`);
162
+ }
163
+ let event: RecordingEventV1;
164
+ try {
165
+ event = parseRecordingEventV1(value);
166
+ } catch (error) {
167
+ throw new CliError("invalid-data", `${stream.path}:${lineNumber} is not a recording event: ${String(error)}`);
168
+ }
169
+ parsedCount += 1;
170
+ if (event.sequence <= priorSequence) {
171
+ throw new CliError("invalid-data", `${stream.path}:${lineNumber} does not increase event sequence.`);
172
+ }
173
+ if (event.sourceTimeUs < priorSourceTimeUs) {
174
+ throw new CliError("invalid-data", `${stream.path}:${lineNumber} moves source time backward.`);
175
+ }
176
+ if (event.sourceTimeUs < stream.startUs || event.sourceTimeUs > stream.endUs) {
177
+ throw new CliError("invalid-data", `${stream.path}:${lineNumber} lies outside its manifest interval.`);
178
+ }
179
+ priorSequence = event.sequence;
180
+ priorSourceTimeUs = event.sourceTimeUs;
181
+ if (event.sourceTimeUs < startUs || event.sourceTimeUs >= endUs) continue;
182
+ if (acceptedTypes !== null && !acceptedTypes.has(event.type)) continue;
183
+ if (options.limit === undefined) events.push(event);
184
+ else retainEarliestEvent(events, event, options.limit);
185
+ }
186
+ } catch (error) {
187
+ input.destroy();
188
+ if (error instanceof CliError) throw error;
189
+ throw new CliError("invalid-data", `Could not read event stream ${stream.path}: ${String(error)}`);
190
+ }
191
+ if (parsedCount !== stream.recordCount) {
192
+ throw new CliError(
193
+ "invalid-data",
194
+ `${stream.path} contains ${parsedCount} event(s), but its manifest declares ${stream.recordCount}.`,
195
+ );
196
+ }
197
+ if (stream.integrity.state === "verified") {
198
+ const sha256 = hash.digest("hex");
199
+ if (bytes !== stream.integrity.bytes || sha256 !== stream.integrity.sha256) {
200
+ throw new CliError("invalid-data", `Event stream integrity check failed for ${stream.path}.`);
201
+ }
202
+ }
203
+ }
204
+ return events.sort(compareEventOrder);
205
+ }
206
+
207
+ export function recordingSummary(
208
+ recording: OpenRecording,
209
+ plan: EditPlanV1 | null,
210
+ ): Readonly<Record<string, unknown>> {
211
+ const manifest = recording.manifest;
212
+ return {
213
+ createdAt: manifest.createdAt,
214
+ diagnostics: manifest.diagnostics,
215
+ durationUs: manifest.timeline.durationUs,
216
+ edit: plan === null
217
+ ? null
218
+ : {
219
+ cuts: Math.max(0, plan.keep.length - 1),
220
+ hash: hashEditPlan(plan),
221
+ overlays: plan.overlays.length,
222
+ planId: plan.planId,
223
+ speedRanges: plan.speed.length,
224
+ zooms: plan.zooms.length,
225
+ },
226
+ eventStreams: manifest.eventStreams.map((stream) => ({
227
+ eventKinds: stream.eventKinds,
228
+ eventStreamId: stream.eventStreamId,
229
+ path: stream.path,
230
+ recordCount: stream.recordCount,
231
+ })),
232
+ path: recording.directory.path,
233
+ recordingId: manifest.recordingId,
234
+ state: manifest.state,
235
+ tracks: manifest.tracks.map((track) => ({
236
+ enabled: track.enabled,
237
+ kind: track.kind,
238
+ label: track.label,
239
+ segmentCount: track.segments.length,
240
+ trackId: track.trackId,
241
+ })),
242
+ updatedAt: manifest.updatedAt,
243
+ };
244
+ }
245
+
246
+ export function bundleRelativePath(recording: OpenRecording, absolutePath: string): string {
247
+ const prefix = `${recording.directory.path}/`;
248
+ if (!absolutePath.startsWith(prefix)) {
249
+ throw new CliError("unsafe-path", `Path is outside ${recording.manifest.recordingId}: ${absolutePath}`);
250
+ }
251
+ return absolutePath.slice(prefix.length);
252
+ }
253
+
254
+ export function defaultPlanId(manifest: RecordingManifestV1): string {
255
+ return `plan_${manifest.recordingId.slice(4)}`;
256
+ }