@camstack/types 0.1.39 → 0.1.41

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 (323) hide show
  1. package/dist/addon/base-addon.d.ts +14 -2
  2. package/dist/addon/base-addon.d.ts.map +1 -1
  3. package/dist/addon/durable-state.d.ts +33 -0
  4. package/dist/addon/durable-state.d.ts.map +1 -0
  5. package/dist/capabilities/accessories.cap.d.ts +56 -8
  6. package/dist/capabilities/accessories.cap.d.ts.map +1 -1
  7. package/dist/capabilities/addon-settings.cap.d.ts +1 -0
  8. package/dist/capabilities/addon-settings.cap.d.ts.map +1 -1
  9. package/dist/capabilities/addons.cap.d.ts +187 -1
  10. package/dist/capabilities/addons.cap.d.ts.map +1 -1
  11. package/dist/capabilities/advanced-notifier.cap.d.ts +4 -0
  12. package/dist/capabilities/advanced-notifier.cap.d.ts.map +1 -1
  13. package/dist/capabilities/air-quality-sensor.cap.d.ts +56 -0
  14. package/dist/capabilities/air-quality-sensor.cap.d.ts.map +1 -0
  15. package/dist/capabilities/alarm-panel.cap.d.ts +154 -0
  16. package/dist/capabilities/alarm-panel.cap.d.ts.map +1 -0
  17. package/dist/capabilities/alerts.cap.d.ts +5 -5
  18. package/dist/capabilities/ambient-light-sensor.cap.d.ts +39 -0
  19. package/dist/capabilities/ambient-light-sensor.cap.d.ts.map +1 -0
  20. package/dist/capabilities/audio-analyzer.cap.d.ts +2 -2
  21. package/dist/capabilities/automation-control.cap.d.ts +65 -0
  22. package/dist/capabilities/automation-control.cap.d.ts.map +1 -0
  23. package/dist/capabilities/battery.cap.d.ts +30 -1
  24. package/dist/capabilities/battery.cap.d.ts.map +1 -1
  25. package/dist/capabilities/binary.cap.d.ts +40 -0
  26. package/dist/capabilities/binary.cap.d.ts.map +1 -0
  27. package/dist/capabilities/brightness.cap.d.ts +3 -2
  28. package/dist/capabilities/brightness.cap.d.ts.map +1 -1
  29. package/dist/capabilities/broker.cap.d.ts +291 -0
  30. package/dist/capabilities/broker.cap.d.ts.map +1 -0
  31. package/dist/capabilities/button.cap.d.ts +27 -0
  32. package/dist/capabilities/button.cap.d.ts.map +1 -0
  33. package/dist/capabilities/camera-credentials.cap.d.ts +1 -0
  34. package/dist/capabilities/camera-credentials.cap.d.ts.map +1 -1
  35. package/dist/capabilities/camera-pipeline-config.cap.d.ts +43 -0
  36. package/dist/capabilities/camera-pipeline-config.cap.d.ts.map +1 -0
  37. package/dist/capabilities/camera-streams.cap.d.ts +147 -10
  38. package/dist/capabilities/camera-streams.cap.d.ts.map +1 -1
  39. package/dist/capabilities/capability-definition.d.ts +15 -0
  40. package/dist/capabilities/capability-definition.d.ts.map +1 -1
  41. package/dist/capabilities/carbon-monoxide.cap.d.ts +33 -0
  42. package/dist/capabilities/carbon-monoxide.cap.d.ts.map +1 -0
  43. package/dist/capabilities/climate-control.cap.d.ts +183 -0
  44. package/dist/capabilities/climate-control.cap.d.ts.map +1 -0
  45. package/dist/capabilities/color.cap.d.ts +186 -0
  46. package/dist/capabilities/color.cap.d.ts.map +1 -0
  47. package/dist/capabilities/connectivity.cap.d.ts +37 -0
  48. package/dist/capabilities/connectivity.cap.d.ts.map +1 -0
  49. package/dist/capabilities/consumables.cap.d.ts +105 -0
  50. package/dist/capabilities/consumables.cap.d.ts.map +1 -0
  51. package/dist/capabilities/contact.cap.d.ts +41 -0
  52. package/dist/capabilities/contact.cap.d.ts.map +1 -0
  53. package/dist/capabilities/control.cap.d.ts +167 -0
  54. package/dist/capabilities/control.cap.d.ts.map +1 -0
  55. package/dist/capabilities/cover.cap.d.ts +104 -0
  56. package/dist/capabilities/cover.cap.d.ts.map +1 -0
  57. package/dist/capabilities/custom-actions.d.ts +6 -0
  58. package/dist/capabilities/custom-actions.d.ts.map +1 -1
  59. package/dist/capabilities/decoder.cap.d.ts +1 -1
  60. package/dist/capabilities/device-adoption.cap.d.ts +203 -0
  61. package/dist/capabilities/device-adoption.cap.d.ts.map +1 -0
  62. package/dist/capabilities/device-discovery.cap.d.ts +44 -143
  63. package/dist/capabilities/device-discovery.cap.d.ts.map +1 -1
  64. package/dist/capabilities/device-manager.cap.d.ts +469 -0
  65. package/dist/capabilities/device-manager.cap.d.ts.map +1 -1
  66. package/dist/capabilities/device-ops.cap.d.ts +30 -0
  67. package/dist/capabilities/device-ops.cap.d.ts.map +1 -1
  68. package/dist/capabilities/device-provider.cap.d.ts +36 -0
  69. package/dist/capabilities/device-provider.cap.d.ts.map +1 -1
  70. package/dist/capabilities/device-status.cap.d.ts +1 -0
  71. package/dist/capabilities/device-status.cap.d.ts.map +1 -1
  72. package/dist/capabilities/doorbell.cap.d.ts +1 -0
  73. package/dist/capabilities/doorbell.cap.d.ts.map +1 -1
  74. package/dist/capabilities/enum-sensor.cap.d.ts +60 -0
  75. package/dist/capabilities/enum-sensor.cap.d.ts.map +1 -0
  76. package/dist/capabilities/event-emitter.cap.d.ts +79 -0
  77. package/dist/capabilities/event-emitter.cap.d.ts.map +1 -0
  78. package/dist/capabilities/fan-control.cap.d.ts +100 -0
  79. package/dist/capabilities/fan-control.cap.d.ts.map +1 -0
  80. package/dist/capabilities/feature-probe.cap.d.ts +1 -0
  81. package/dist/capabilities/feature-probe.cap.d.ts.map +1 -1
  82. package/dist/capabilities/flood.cap.d.ts +37 -0
  83. package/dist/capabilities/flood.cap.d.ts.map +1 -0
  84. package/dist/capabilities/gas.cap.d.ts +34 -0
  85. package/dist/capabilities/gas.cap.d.ts.map +1 -0
  86. package/dist/capabilities/humidifier.cap.d.ts +82 -0
  87. package/dist/capabilities/humidifier.cap.d.ts.map +1 -0
  88. package/dist/capabilities/humidity-sensor.cap.d.ts +43 -0
  89. package/dist/capabilities/humidity-sensor.cap.d.ts.map +1 -0
  90. package/dist/capabilities/image.cap.d.ts +44 -0
  91. package/dist/capabilities/image.cap.d.ts.map +1 -0
  92. package/dist/capabilities/index.d.ts +103 -15
  93. package/dist/capabilities/index.d.ts.map +1 -1
  94. package/dist/capabilities/integrations.cap.d.ts +12 -0
  95. package/dist/capabilities/integrations.cap.d.ts.map +1 -1
  96. package/dist/capabilities/intercom.cap.d.ts +49 -9
  97. package/dist/capabilities/intercom.cap.d.ts.map +1 -1
  98. package/dist/capabilities/lawn-mower-control.cap.d.ts +83 -0
  99. package/dist/capabilities/lawn-mower-control.cap.d.ts.map +1 -0
  100. package/dist/capabilities/local-network.cap.d.ts +1 -1
  101. package/dist/capabilities/lock-control.cap.d.ts +92 -0
  102. package/dist/capabilities/lock-control.cap.d.ts.map +1 -0
  103. package/dist/capabilities/mask-shape.d.ts +99 -0
  104. package/dist/capabilities/mask-shape.d.ts.map +1 -0
  105. package/dist/capabilities/media-player.cap.d.ts +201 -0
  106. package/dist/capabilities/media-player.cap.d.ts.map +1 -0
  107. package/dist/capabilities/mesh-network.cap.d.ts +3 -3
  108. package/dist/capabilities/motion-trigger.cap.d.ts +1 -0
  109. package/dist/capabilities/motion-trigger.cap.d.ts.map +1 -1
  110. package/dist/capabilities/motion-zones.cap.d.ts +95 -13
  111. package/dist/capabilities/motion-zones.cap.d.ts.map +1 -1
  112. package/dist/capabilities/mqtt-broker.cap.d.ts +3 -3
  113. package/dist/capabilities/native-object-detection.cap.d.ts +74 -3
  114. package/dist/capabilities/native-object-detection.cap.d.ts.map +1 -1
  115. package/dist/capabilities/network-access.cap.d.ts +7 -7
  116. package/dist/capabilities/network-quality.cap.d.ts +5 -0
  117. package/dist/capabilities/network-quality.cap.d.ts.map +1 -1
  118. package/dist/capabilities/notifier.cap.d.ts +173 -0
  119. package/dist/capabilities/notifier.cap.d.ts.map +1 -0
  120. package/dist/capabilities/numeric-sensor.cap.d.ts +52 -0
  121. package/dist/capabilities/numeric-sensor.cap.d.ts.map +1 -0
  122. package/dist/capabilities/oauth-integration.cap.d.ts +4 -2
  123. package/dist/capabilities/oauth-integration.cap.d.ts.map +1 -1
  124. package/dist/capabilities/osd.cap.d.ts +1 -0
  125. package/dist/capabilities/osd.cap.d.ts.map +1 -1
  126. package/dist/capabilities/pipeline-analytics.cap.d.ts +69 -22
  127. package/dist/capabilities/pipeline-analytics.cap.d.ts.map +1 -1
  128. package/dist/capabilities/pipeline-executor.cap.d.ts +2 -2
  129. package/dist/capabilities/pipeline-orchestrator.cap.d.ts +1 -1
  130. package/dist/capabilities/pipeline-runner.cap.d.ts +4 -2
  131. package/dist/capabilities/pipeline-runner.cap.d.ts.map +1 -1
  132. package/dist/capabilities/platform-probe.cap.d.ts +25 -0
  133. package/dist/capabilities/platform-probe.cap.d.ts.map +1 -1
  134. package/dist/capabilities/power-meter.cap.d.ts +65 -0
  135. package/dist/capabilities/power-meter.cap.d.ts.map +1 -0
  136. package/dist/capabilities/presence.cap.d.ts +79 -0
  137. package/dist/capabilities/presence.cap.d.ts.map +1 -0
  138. package/dist/capabilities/pressure-sensor.cap.d.ts +43 -0
  139. package/dist/capabilities/pressure-sensor.cap.d.ts.map +1 -0
  140. package/dist/capabilities/privacy-mask.cap.d.ts +207 -0
  141. package/dist/capabilities/privacy-mask.cap.d.ts.map +1 -0
  142. package/dist/capabilities/ptz-autotrack.cap.d.ts +1 -0
  143. package/dist/capabilities/ptz-autotrack.cap.d.ts.map +1 -1
  144. package/dist/capabilities/ptz.cap.d.ts +1 -0
  145. package/dist/capabilities/ptz.cap.d.ts.map +1 -1
  146. package/dist/capabilities/reboot.cap.d.ts +1 -0
  147. package/dist/capabilities/reboot.cap.d.ts.map +1 -1
  148. package/dist/capabilities/recording.cap.d.ts +238 -32
  149. package/dist/capabilities/recording.cap.d.ts.map +1 -1
  150. package/dist/capabilities/restreamer.cap.d.ts +2 -2
  151. package/dist/capabilities/schemas/detection-shared.d.ts +14 -1
  152. package/dist/capabilities/schemas/detection-shared.d.ts.map +1 -1
  153. package/dist/capabilities/schemas/orchestrator-metrics.d.ts +2 -2
  154. package/dist/capabilities/schemas/streaming-shared.d.ts +64 -1
  155. package/dist/capabilities/schemas/streaming-shared.d.ts.map +1 -1
  156. package/dist/capabilities/script-runner.cap.d.ts +62 -0
  157. package/dist/capabilities/script-runner.cap.d.ts.map +1 -0
  158. package/dist/capabilities/settings-store.cap.d.ts +25 -0
  159. package/dist/capabilities/settings-store.cap.d.ts.map +1 -1
  160. package/dist/capabilities/smoke.cap.d.ts +38 -0
  161. package/dist/capabilities/smoke.cap.d.ts.map +1 -0
  162. package/dist/capabilities/snapshot-provider.cap.d.ts +1 -1
  163. package/dist/capabilities/snapshot.cap.d.ts +1 -0
  164. package/dist/capabilities/snapshot.cap.d.ts.map +1 -1
  165. package/dist/capabilities/sso-bridge.cap.d.ts +3 -3
  166. package/dist/capabilities/storage-evictable.cap.d.ts +54 -0
  167. package/dist/capabilities/storage-evictable.cap.d.ts.map +1 -0
  168. package/dist/capabilities/storage-provider.cap.d.ts +31 -186
  169. package/dist/capabilities/storage-provider.cap.d.ts.map +1 -1
  170. package/dist/capabilities/storage.cap.d.ts +35 -226
  171. package/dist/capabilities/storage.cap.d.ts.map +1 -1
  172. package/dist/capabilities/stream-broker.cap.d.ts +309 -25
  173. package/dist/capabilities/stream-broker.cap.d.ts.map +1 -1
  174. package/dist/capabilities/stream-catalog.cap.d.ts +85 -0
  175. package/dist/capabilities/stream-catalog.cap.d.ts.map +1 -0
  176. package/dist/capabilities/stream-params.cap.d.ts +27 -26
  177. package/dist/capabilities/stream-params.cap.d.ts.map +1 -1
  178. package/dist/capabilities/switch.cap.d.ts +1 -0
  179. package/dist/capabilities/switch.cap.d.ts.map +1 -1
  180. package/dist/capabilities/tamper.cap.d.ts +33 -0
  181. package/dist/capabilities/tamper.cap.d.ts.map +1 -0
  182. package/dist/capabilities/temperature-sensor.cap.d.ts +51 -0
  183. package/dist/capabilities/temperature-sensor.cap.d.ts.map +1 -0
  184. package/dist/capabilities/update.cap.d.ts +49 -0
  185. package/dist/capabilities/update.cap.d.ts.map +1 -0
  186. package/dist/capabilities/user-management.cap.d.ts +20 -20
  187. package/dist/capabilities/vacuum-control.cap.d.ts +229 -0
  188. package/dist/capabilities/vacuum-control.cap.d.ts.map +1 -0
  189. package/dist/capabilities/valve.cap.d.ts +93 -0
  190. package/dist/capabilities/valve.cap.d.ts.map +1 -0
  191. package/dist/capabilities/vibration.cap.d.ts +33 -0
  192. package/dist/capabilities/vibration.cap.d.ts.map +1 -0
  193. package/dist/capabilities/water-heater.cap.d.ts +78 -0
  194. package/dist/capabilities/water-heater.cap.d.ts.map +1 -0
  195. package/dist/capabilities/weather.cap.d.ts +76 -0
  196. package/dist/capabilities/weather.cap.d.ts.map +1 -0
  197. package/dist/capabilities/webrtc-session.cap.d.ts +128 -0
  198. package/dist/capabilities/webrtc-session.cap.d.ts.map +1 -1
  199. package/dist/constants.d.ts +0 -2
  200. package/dist/constants.d.ts.map +1 -1
  201. package/dist/device/base-device-provider.d.ts +11 -0
  202. package/dist/device/base-device-provider.d.ts.map +1 -1
  203. package/dist/device/base-device.d.ts +52 -0
  204. package/dist/device/base-device.d.ts.map +1 -1
  205. package/dist/device/device-context.d.ts +29 -0
  206. package/dist/device/device-context.d.ts.map +1 -1
  207. package/dist/device/device-link-transform.d.ts +6 -0
  208. package/dist/device/device-link-transform.d.ts.map +1 -0
  209. package/dist/device/device-management.d.ts +116 -4
  210. package/dist/device/device-management.d.ts.map +1 -1
  211. package/dist/device/device-type.d.ts +214 -3
  212. package/dist/device/device-type.d.ts.map +1 -1
  213. package/dist/device/device.d.ts +25 -0
  214. package/dist/device/device.d.ts.map +1 -1
  215. package/dist/device/index.d.ts +5 -1
  216. package/dist/device/index.d.ts.map +1 -1
  217. package/dist/device/path-util.d.ts +13 -0
  218. package/dist/device/path-util.d.ts.map +1 -0
  219. package/dist/device/schema-fields.d.ts +12 -0
  220. package/dist/device/schema-fields.d.ts.map +1 -0
  221. package/dist/device/source-info.d.ts +87 -0
  222. package/dist/device/source-info.d.ts.map +1 -0
  223. package/dist/device/system-mirror.d.ts +6 -2
  224. package/dist/device/system-mirror.d.ts.map +1 -1
  225. package/dist/encode-profile.d.ts +126 -0
  226. package/dist/encode-profile.d.ts.map +1 -0
  227. package/dist/enums/event-category.d.ts +92 -7
  228. package/dist/enums/event-category.d.ts.map +1 -1
  229. package/dist/generated/addon-api.d.ts +12821 -8025
  230. package/dist/generated/addon-api.d.ts.map +1 -1
  231. package/dist/generated/cap-status-types.d.ts +87 -1
  232. package/dist/generated/cap-status-types.d.ts.map +1 -1
  233. package/dist/generated/capability-router-map.d.ts +142 -7
  234. package/dist/generated/capability-router-map.d.ts.map +1 -1
  235. package/dist/generated/device-local-state.d.ts +123 -0
  236. package/dist/generated/device-local-state.d.ts.map +1 -1
  237. package/dist/generated/device-proxy.d.ts +132 -5
  238. package/dist/generated/device-proxy.d.ts.map +1 -1
  239. package/dist/generated/method-access-map.d.ts +1 -1
  240. package/dist/generated/method-access-map.d.ts.map +1 -1
  241. package/dist/generated/provider-kind-map.d.ts +1 -1
  242. package/dist/generated/provider-kind-map.d.ts.map +1 -1
  243. package/dist/generated/system-proxy.d.ts +10 -8
  244. package/dist/generated/system-proxy.d.ts.map +1 -1
  245. package/dist/health/wiring-health.d.ts +136 -0
  246. package/dist/health/wiring-health.d.ts.map +1 -0
  247. package/dist/{index-CMM1Y9W6.js → index-BSA_TBea.js} +4774 -923
  248. package/dist/index-BSA_TBea.js.map +1 -0
  249. package/dist/{index-QRlzao1I.mjs → index-Bpj3ScIH.mjs} +5142 -1291
  250. package/dist/index-Bpj3ScIH.mjs.map +1 -0
  251. package/dist/index.d.ts +23 -3
  252. package/dist/index.d.ts.map +1 -1
  253. package/dist/index.js +1498 -119
  254. package/dist/index.js.map +1 -1
  255. package/dist/index.mjs +1806 -427
  256. package/dist/index.mjs.map +1 -1
  257. package/dist/interfaces/addon-data-plane.d.ts +76 -0
  258. package/dist/interfaces/addon-data-plane.d.ts.map +1 -0
  259. package/dist/interfaces/addon.d.ts +61 -1
  260. package/dist/interfaces/addon.d.ts.map +1 -1
  261. package/dist/interfaces/advanced-notifier.d.ts +4 -0
  262. package/dist/interfaces/advanced-notifier.d.ts.map +1 -1
  263. package/dist/interfaces/capability.d.ts +59 -1
  264. package/dist/interfaces/capability.d.ts.map +1 -1
  265. package/dist/interfaces/config-ui.d.ts +70 -6
  266. package/dist/interfaces/config-ui.d.ts.map +1 -1
  267. package/dist/interfaces/device-capabilities/camera.d.ts +2 -1
  268. package/dist/interfaces/device-capabilities/camera.d.ts.map +1 -1
  269. package/dist/interfaces/event-bus.d.ts +105 -4
  270. package/dist/interfaces/event-bus.d.ts.map +1 -1
  271. package/dist/interfaces/kernel-abstractions.d.ts +10 -1
  272. package/dist/interfaces/kernel-abstractions.d.ts.map +1 -1
  273. package/dist/interfaces/network-quality.d.ts +8 -0
  274. package/dist/interfaces/network-quality.d.ts.map +1 -1
  275. package/dist/interfaces/pipeline-executor-capability.d.ts +2 -2
  276. package/dist/interfaces/pipeline-orchestrator-capability.d.ts +2 -1
  277. package/dist/interfaces/pipeline-orchestrator-capability.d.ts.map +1 -1
  278. package/dist/interfaces/pipeline-runner-capability.d.ts +7 -0
  279. package/dist/interfaces/pipeline-runner-capability.d.ts.map +1 -1
  280. package/dist/interfaces/recording-config.d.ts +91 -0
  281. package/dist/interfaces/recording-config.d.ts.map +1 -0
  282. package/dist/interfaces/rtp-egress.d.ts +2 -3
  283. package/dist/interfaces/rtp-egress.d.ts.map +1 -1
  284. package/dist/interfaces/storage-location-declaration.d.ts +34 -0
  285. package/dist/interfaces/storage-location-declaration.d.ts.map +1 -0
  286. package/dist/interfaces/storage-location.d.ts +18 -74
  287. package/dist/interfaces/storage-location.d.ts.map +1 -1
  288. package/dist/interfaces/storage.d.ts +15 -4
  289. package/dist/interfaces/storage.d.ts.map +1 -1
  290. package/dist/interfaces/stream-broker.d.ts +39 -4
  291. package/dist/interfaces/stream-broker.d.ts.map +1 -1
  292. package/dist/interfaces/timezones.d.ts +59 -0
  293. package/dist/interfaces/timezones.d.ts.map +1 -0
  294. package/dist/node.d.ts +3 -0
  295. package/dist/node.d.ts.map +1 -1
  296. package/dist/node.js +50 -8
  297. package/dist/node.js.map +1 -1
  298. package/dist/node.mjs +51 -9
  299. package/dist/node.mjs.map +1 -1
  300. package/dist/readiness/readiness-registry.d.ts.map +1 -1
  301. package/dist/schemas/auth-records.d.ts +4 -4
  302. package/dist/storage/filesystem-storage-provider.d.ts +2 -1
  303. package/dist/storage/filesystem-storage-provider.d.ts.map +1 -1
  304. package/dist/stream-selection.d.ts +85 -0
  305. package/dist/stream-selection.d.ts.map +1 -0
  306. package/dist/types/detection.d.ts +3 -3
  307. package/dist/types/io.d.ts +13 -1
  308. package/dist/types/io.d.ts.map +1 -1
  309. package/dist/util/location-match.d.ts +15 -0
  310. package/dist/util/location-match.d.ts.map +1 -0
  311. package/dist/utils/canonical-hash.d.ts +20 -0
  312. package/dist/utils/canonical-hash.d.ts.map +1 -0
  313. package/dist/utils/export-reconciler.d.ts +101 -0
  314. package/dist/utils/export-reconciler.d.ts.map +1 -0
  315. package/dist/utils/privacy-grid-raster.d.ts +48 -0
  316. package/dist/utils/privacy-grid-raster.d.ts.map +1 -0
  317. package/dist/utils/sleep.d.ts +36 -0
  318. package/dist/utils/sleep.d.ts.map +1 -0
  319. package/package.json +1 -1
  320. package/dist/capabilities/recording-engine.cap.d.ts +0 -477
  321. package/dist/capabilities/recording-engine.cap.d.ts.map +0 -1
  322. package/dist/index-CMM1Y9W6.js.map +0 -1
  323. package/dist/index-QRlzao1I.mjs.map +0 -1
package/dist/node.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"node.js","sources":["../src/deps/binary-downloader.ts","../src/deps/ffmpeg-downloader.ts","../src/deps/python-downloader.ts","../src/storage/filesystem-storage-provider.ts"],"sourcesContent":["import { existsSync, mkdirSync, chmodSync, createWriteStream, unlinkSync } from 'node:fs'\nimport { join } from 'node:path'\nimport { pipeline } from 'node:stream/promises'\nimport { Readable } from 'node:stream'\nimport { execFileSync } from 'node:child_process'\nimport type { IScopedLogger } from '../index.js'\n\nexport interface PlatformInfo {\n readonly platform: NodeJS.Platform\n readonly arch: NodeJS.Architecture\n}\n\nexport function getPlatformInfo(): PlatformInfo {\n return {\n platform: process.platform,\n arch: process.arch,\n }\n}\n\nexport function buildBinaryPath(dataDir: string, name: string, platform?: string): string {\n const ext = (platform ?? process.platform) === 'win32' ? '.exe' : ''\n return join(dataDir, 'deps', `${name}${ext}`)\n}\n\n/** Check if a binary exists in PATH */\nexport function findInPath(name: string): string | null {\n try {\n execFileSync(name, ['--version'], { stdio: 'pipe', timeout: 5000 })\n return name\n } catch {\n return null\n }\n}\n\nexport interface DownloadOptions {\n readonly name: string\n readonly url: string\n readonly targetDir: string\n readonly targetName: string\n readonly logger: IScopedLogger\n readonly isArchive?: boolean\n readonly archiveFormat?: 'zip' | 'tar.gz' | 'tar.xz'\n /** Relative path within archive to the binary (e.g., 'ffmpeg-6.1/bin/ffmpeg') */\n readonly archiveInnerPath?: string\n}\n\n/**\n * Download a binary to the target directory.\n * Handles archives (zip, tar.gz, tar.xz) and raw binaries.\n */\nexport async function downloadBinary(opts: DownloadOptions): Promise<string> {\n const { name, url, targetDir, targetName, logger, isArchive, archiveFormat, archiveInnerPath } = opts\n const targetPath = join(targetDir, targetName)\n\n if (existsSync(targetPath)) {\n logger.debug('Binary already exists', { meta: { name, targetPath } })\n return targetPath\n }\n\n mkdirSync(targetDir, { recursive: true })\n\n logger.info('Downloading binary', { meta: { name, url } })\n const response = await fetch(url, { redirect: 'follow' })\n if (!response.ok || !response.body) {\n throw new Error(`Failed to download ${name}: ${response.status} ${response.statusText}`)\n }\n\n if (isArchive) {\n const ext = archiveFormat ?? 'tar.gz'\n const tmpArchive = join(targetDir, `${name}-download.${ext}`)\n const nodeStream = Readable.fromWeb(response.body as ReadableStream)\n await pipeline(nodeStream, createWriteStream(tmpArchive))\n\n // Extract\n const tmpExtractDir = join(targetDir, `${name}-extract`)\n mkdirSync(tmpExtractDir, { recursive: true })\n\n if (ext === 'zip') {\n try {\n execFileSync('unzip', ['-o', '-q', tmpArchive, '-d', tmpExtractDir], { stdio: 'pipe' })\n } catch {\n execFileSync('tar', ['-xf', tmpArchive, '-C', tmpExtractDir], { stdio: 'pipe' })\n }\n } else if (ext === 'tar.xz') {\n execFileSync('tar', ['-xJf', tmpArchive, '-C', tmpExtractDir], { stdio: 'pipe' })\n } else {\n execFileSync('tar', ['-xzf', tmpArchive, '-C', tmpExtractDir], { stdio: 'pipe' })\n }\n\n // Copy binary from archive\n if (archiveInnerPath) {\n const { copyFileSync } = await import('node:fs')\n const sourcePath = join(tmpExtractDir, archiveInnerPath)\n if (!existsSync(sourcePath)) {\n throw new Error(`Binary not found in archive at ${archiveInnerPath}`)\n }\n copyFileSync(sourcePath, targetPath)\n }\n\n // Cleanup\n unlinkSync(tmpArchive)\n const { rmSync } = await import('node:fs')\n rmSync(tmpExtractDir, { recursive: true, force: true })\n } else {\n // Direct binary download\n const nodeStream = Readable.fromWeb(response.body as ReadableStream)\n await pipeline(nodeStream, createWriteStream(targetPath))\n }\n\n chmodSync(targetPath, 0o755)\n logger.info('Binary downloaded', { meta: { name, targetPath } })\n return targetPath\n}\n\n/**\n * Ensure a binary is available. Checks:\n * 1. Target path (already downloaded)\n * 2. System PATH\n * 3. Download from URL\n */\nexport async function ensureBinary(opts: {\n readonly name: string\n readonly targetDir: string\n readonly downloadUrl: string\n readonly logger: IScopedLogger\n readonly isArchive?: boolean\n readonly archiveFormat?: 'zip' | 'tar.gz' | 'tar.xz'\n readonly archiveInnerPath?: string\n}): Promise<string> {\n const ext = process.platform === 'win32' ? '.exe' : ''\n const targetName = `${opts.name}${ext}`\n const targetPath = join(opts.targetDir, targetName)\n\n // 1. Already downloaded\n if (existsSync(targetPath)) {\n opts.logger.debug('Binary found at target path', { meta: { name: opts.name, targetPath } })\n return targetPath\n }\n\n // 2. System PATH\n const inPath = findInPath(opts.name)\n if (inPath) {\n opts.logger.info('Binary found in system PATH', { meta: { name: opts.name } })\n return inPath\n }\n\n // 3. Download\n return downloadBinary({\n name: opts.name,\n url: opts.downloadUrl,\n targetDir: opts.targetDir,\n targetName,\n logger: opts.logger,\n isArchive: opts.isArchive,\n archiveFormat: opts.archiveFormat,\n archiveInnerPath: opts.archiveInnerPath,\n })\n}\n","/**\n * Download ffmpeg static build for the current platform.\n *\n * Sources:\n * - Linux: https://johnvansickle.com/ffmpeg/ (static builds)\n * - macOS: https://evermeet.cx/ffmpeg/ or homebrew\n *\n * Using BtbN's GitHub releases as they cover both platforms:\n * https://github.com/BtbN/FFmpeg-Builds/releases\n */\nimport { join } from 'node:path'\nimport type { IScopedLogger } from '../index.js'\nimport { ensureBinary } from './binary-downloader.js'\n\nconst FFMPEG_VERSION = '7.1'\n\nexport function getFfmpegDownloadUrl(platform: string, arch: string): string {\n switch (platform) {\n case 'linux': {\n const archMap: Record<string, string> = { x64: 'amd64', arm64: 'arm64' }\n const a = archMap[arch]\n if (!a) throw new Error(`Unsupported Linux architecture: ${arch}`)\n // John Van Sickle static builds — well-tested, widely used\n return `https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-${a}-static.tar.xz`\n }\n case 'darwin': {\n const archMap: Record<string, string> = { arm64: 'arm64', x64: 'amd64' }\n const a = archMap[arch]\n if (!a) throw new Error(`Unsupported macOS architecture: ${arch}`)\n // macOS static builds from osxexperts (maintained, universal builds)\n return `https://www.osxexperts.net/ffmpeg${FFMPEG_VERSION.replace('.', '')}arm.zip`\n }\n default:\n throw new Error(`Unsupported platform: ${platform}`)\n }\n}\n\nexport function getFfmpegArchiveInfo(platform: string): {\n isArchive: boolean\n archiveFormat: 'zip' | 'tar.gz' | 'tar.xz'\n archiveInnerPath: string\n} {\n switch (platform) {\n case 'linux':\n return {\n isArchive: true,\n archiveFormat: 'tar.xz',\n archiveInnerPath: `ffmpeg-${FFMPEG_VERSION}-amd64-static/ffmpeg`,\n }\n case 'darwin':\n return {\n isArchive: true,\n archiveFormat: 'zip',\n archiveInnerPath: 'ffmpeg',\n }\n default:\n throw new Error(`Unsupported platform: ${platform}`)\n }\n}\n\n/**\n * Ensure ffmpeg binary is available.\n * Checks: deps dir → system PATH → download.\n */\nexport async function ensureFfmpeg(dataDir: string, logger: IScopedLogger): Promise<string> {\n const depsDir = join(dataDir, 'deps')\n const platform = process.platform\n const arch = process.arch\n\n const archiveInfo = getFfmpegArchiveInfo(platform)\n\n return ensureBinary({\n name: 'ffmpeg',\n targetDir: depsDir,\n downloadUrl: getFfmpegDownloadUrl(platform, arch),\n logger,\n ...archiveInfo,\n })\n}\n","/**\n * Download portable Python (headless) from bjia56/portable-python.\n *\n * Source: https://github.com/bjia56/portable-python\n *\n * Advantages over python-build-standalone:\n * - macOS universal2 binary (arm64+x86_64 in one)\n * - Lighter (~24-28MB headless zip)\n * - glibc 2.17 minimum (better compat)\n * - Headless variant excludes tkinter/UI (ideal for ML inference)\n *\n * The zip's top-level dir is platform-specific (e.g.\n * `python-headless-3.12.12-darwin-universal2/`); after extraction\n * we rename it to a stable `python/` so consumers can rely on\n * `<dataDir>/deps/python/bin/python3`.\n */\nimport { join, basename } from 'node:path'\nimport { existsSync, mkdirSync, chmodSync, readFileSync, writeFileSync, readdirSync, renameSync, rmSync } from 'node:fs'\nimport { execFileSync } from 'node:child_process'\nimport { createHash } from 'node:crypto'\nimport { pipeline } from 'node:stream/promises'\nimport { Readable } from 'node:stream'\nimport { createWriteStream } from 'node:fs'\nimport type { IScopedLogger } from '../index.js'\nimport { errMsg } from '../index.js'\n\nexport const PYTHON_VERSION = '3.12.12'\nconst PP_BASE = `https://github.com/bjia56/portable-python/releases/download/cpython-v${PYTHON_VERSION}-build.0`\n\nexport function getPythonDownloadUrl(platform: string, arch: string): string {\n switch (platform) {\n case 'linux': {\n const archMap: Record<string, string> = { x64: 'x86_64', arm64: 'aarch64' }\n const a = archMap[arch]\n if (!a) throw new Error(`Unsupported Linux architecture for Python: ${arch}`)\n return `${PP_BASE}/python-headless-${PYTHON_VERSION}-linux-${a}.zip`\n }\n case 'darwin': {\n // universal2 covers both arm64 and x86_64\n return `${PP_BASE}/python-headless-${PYTHON_VERSION}-darwin-universal2.zip`\n }\n default:\n throw new Error(`Unsupported platform for portable Python: ${platform}`)\n }\n}\n\n/**\n * Ensure the embedded portable Python is available.\n *\n * ALWAYS returns the path to the embedded interpreter under\n * `<dataDir>/deps/python/bin/python3` — downloads it on first call\n * and reuses it thereafter. The system PATH is intentionally NOT\n * consulted: addons rely on `installPythonRequirements()` to manage\n * deps inside the embedded site-packages, and a system interpreter\n * (different version, missing modules) silently breaks that contract.\n *\n * Returns null only if the download/extract step fails.\n */\nexport async function ensurePython(dataDir: string, logger: IScopedLogger): Promise<string | null> {\n const pythonDir = join(dataDir, 'deps', 'python')\n const pythonBin = join(pythonDir, 'bin', 'python3')\n\n // 1. Already downloaded\n if (existsSync(pythonBin)) {\n logger.debug('Portable Python found', { meta: { pythonBin } })\n return pythonBin\n }\n\n // 2. Download portable python (no system PATH fallback — by design)\n logger.info('Downloading portable Python (headless)', { meta: { version: PYTHON_VERSION } })\n\n const depsDir = join(dataDir, 'deps')\n mkdirSync(depsDir, { recursive: true })\n\n const url = getPythonDownloadUrl(process.platform, process.arch)\n const tmpArchive = join(depsDir, 'python-download.zip')\n\n logger.info('Python download source', { meta: { url } })\n\n const response = await fetch(url, { redirect: 'follow' })\n if (!response.ok || !response.body) {\n logger.warn('Failed to download Python — ML detection will not be available', { meta: { status: response.status } })\n return null\n }\n\n const nodeStream = Readable.fromWeb(response.body as ReadableStream)\n await pipeline(nodeStream, createWriteStream(tmpArchive))\n\n // Extract — zip's top-level dir is e.g. `python-headless-3.12.12-darwin-universal2/`\n // (varies per platform/arch).\n try {\n execFileSync('unzip', ['-o', '-q', tmpArchive, '-d', depsDir], { stdio: 'pipe' })\n } catch {\n // Fallback: some minimal Linux installs don't have unzip\n execFileSync('python3', ['-m', 'zipfile', '-e', tmpArchive, depsDir], { stdio: 'pipe' })\n }\n\n // Cleanup the archive\n const { unlinkSync } = await import('node:fs')\n unlinkSync(tmpArchive)\n\n // Locate the extracted directory and normalize it to `<depsDir>/python/`.\n // The zip's top-level name varies by platform (e.g.\n // `python-headless-3.12.12-darwin-universal2/`), so we look for any\n // `python-*` subdir that contains the expected `bin/python3` and rename it.\n if (!existsSync(pythonBin)) {\n const candidates = readdirSync(depsDir).filter((name) =>\n name.startsWith('python-') &&\n existsSync(join(depsDir, name, 'bin', 'python3')),\n )\n if (candidates.length === 0) {\n logger.warn('Python extraction succeeded but no python3 binary found in any subdir', {\n meta: { depsDir },\n })\n return null\n }\n const extractedRoot = join(depsDir, candidates[0]!)\n // Drop any stale `python/` (e.g. from a previous failed install)\n // before renaming so the move always succeeds.\n if (existsSync(pythonDir)) {\n rmSync(pythonDir, { recursive: true, force: true })\n }\n renameSync(extractedRoot, pythonDir)\n }\n\n if (!existsSync(pythonBin)) {\n logger.warn('Python extraction succeeded but binary not found at expected path', {\n meta: { pythonBin },\n })\n return null\n }\n\n chmodSync(pythonBin, 0o755)\n\n // On macOS the bjia56 build is universal2 — the host arch is decided\n // at spawn time based on the parent process. That makes pip wheels\n // and runtime spawns silently disagree when the server is launched\n // from a Rosetta shell once and a native shell next time (e.g. pip\n // installs `numpy x86_64`, then the next boot under arm64 fails with\n // `incompatible architecture`). Lock the slice deterministically by\n // wrapping `python3` in a shell stub that always invokes the matching\n // host arch via `/usr/bin/arch`. Idempotent: detected via the renamed\n // `.real` binary.\n if (process.platform === 'darwin') {\n const realBin = join(pythonDir, 'bin', 'python3.real')\n if (!existsSync(realBin)) {\n renameSync(pythonBin, realBin)\n const archFlag = process.arch === 'arm64' ? '-arm64' : '-x86_64'\n writeFileSync(\n pythonBin,\n `#!/bin/sh\\nexec /usr/bin/arch ${archFlag} \"${realBin}\" \"$@\"\\n`,\n )\n chmodSync(pythonBin, 0o755)\n // Mirror the same wrapping for the `python` symlink so consumers\n // that use the un-versioned name also lock the arch.\n const pyAlt = join(pythonDir, 'bin', 'python')\n if (existsSync(pyAlt)) {\n try {\n rmSync(pyAlt, { force: true })\n } catch {\n // ignore\n }\n writeFileSync(\n pyAlt,\n `#!/bin/sh\\nexec /usr/bin/arch ${archFlag} \"${realBin}\" \"$@\"\\n`,\n )\n chmodSync(pyAlt, 0o755)\n }\n logger.info('Locked python3 to host arch via shell stub', {\n meta: { archFlag, realBin },\n })\n }\n }\n\n logger.info('Portable Python installed', { meta: { version: PYTHON_VERSION, pythonBin } })\n return pythonBin\n}\n\n/**\n * Install Python packages into the portable Python environment.\n */\nexport async function installPythonPackages(\n pythonPath: string,\n packages: readonly string[],\n logger: IScopedLogger,\n): Promise<void> {\n if (packages.length === 0) return\n\n logger.info('Installing Python packages', { meta: { packages } })\n try {\n execFileSync(pythonPath, ['-m', 'pip', 'install', '--quiet', ...packages], {\n stdio: 'pipe',\n timeout: 300_000, // 5 minutes for large packages like torch\n })\n logger.info('Python packages installed successfully')\n } catch (err) {\n logger.error('Failed to install Python packages', { meta: { error: errMsg(err) } })\n throw err\n }\n}\n\n/**\n * Install a pip requirements file into the embedded Python.\n *\n * Idempotent: keyed on (requirements file basename + sha256 of its\n * contents). The marker is written under\n * `<python-install-dir>/.requirements-installed/` so it lives and\n * dies with the python install — re-downloading python wipes both\n * the site-packages and the markers, forcing a fresh re-install.\n *\n * The marker is updated only after `pip install` succeeds. A failed\n * install never writes a marker, so the next call retries.\n */\nexport async function installPythonRequirements(\n pythonPath: string,\n requirementsFile: string,\n logger: IScopedLogger,\n): Promise<void> {\n if (!existsSync(requirementsFile)) {\n throw new Error(`Requirements file not found: ${requirementsFile}`)\n }\n\n const contents = readFileSync(requirementsFile)\n const hash = createHash('sha256').update(contents).digest('hex').slice(0, 16)\n const fileBase = basename(requirementsFile).replace(/\\.txt$/i, '')\n\n // Markers live under the python install dir → wiped together when\n // the embedded interpreter is re-downloaded.\n const pythonRoot = join(pythonPath, '..', '..') // <root>/bin/python3 → <root>\n const markerDir = join(pythonRoot, '.requirements-installed')\n const markerPath = join(markerDir, `${fileBase}-${hash}.marker`)\n\n if (existsSync(markerPath)) {\n logger.debug('Python requirements already installed', { meta: { requirementsFile, hash } })\n return\n }\n\n logger.info('Installing Python requirements', { meta: { requirementsFile, hash } })\n try {\n execFileSync(\n pythonPath,\n ['-m', 'pip', 'install', '--quiet', '--disable-pip-version-check', '-r', requirementsFile],\n {\n stdio: 'pipe',\n timeout: 600_000, // 10 minutes — coremltools + tensorflow can be slow\n },\n )\n } catch (err) {\n logger.error('Failed to install Python requirements', {\n meta: { requirementsFile, error: errMsg(err) },\n })\n throw err\n }\n\n mkdirSync(markerDir, { recursive: true })\n writeFileSync(markerPath, `${requirementsFile}\\n${new Date().toISOString()}\\n`)\n logger.info('Python requirements installed', { meta: { requirementsFile, hash } })\n}\n","import * as fs from 'node:fs'\nimport * as path from 'node:path'\nimport type {\n IStorageProvider,\n StorageLocationType,\n StorageResolveInput,\n StorageWriteInput,\n StorageReadInput,\n StorageExistsInput,\n StorageListInput,\n StorageDeleteInput,\n StorageAvailableSpaceInput,\n} from '../index.js'\n\n// Inline constants to avoid runtime dependency on @camstack/types\n// (types is external in tsup bundle, may not be resolvable from data/addons/)\nconst STORAGE_LOCATION_TYPES: readonly StorageLocationType[] = [\n 'data', 'media', 'recordings',\n 'recordings-high', 'recordings-low', 'recordings-clips', 'event-images',\n 'models', 'addons-data', 'cache', 'logs', 'backups',\n]\n\nconst DEFAULT_LOCATION_SUBDIRS: Readonly<Record<StorageLocationType, string>> = {\n 'data': 'db',\n 'media': 'media',\n 'recordings': 'recordings',\n 'recordings-high': 'recordings-high',\n 'recordings-low': 'recordings-low',\n 'recordings-clips': 'recordings-clips',\n 'event-images': 'event-images',\n 'models': 'models',\n 'addons-data': 'addons-data',\n 'cache': '/tmp/camstack-cache',\n 'logs': 'logs',\n 'backups': 'backups',\n}\n\n/**\n * Filesystem storage provider — serves all location types from a local directory tree.\n *\n * Default layout:\n * {rootPath}/recordings-high/\n * {rootPath}/recordings-low/\n * {rootPath}/recordings-clips/\n * {rootPath}/event-images/\n * {rootPath}/models/\n * {rootPath}/addons-data/\n * {rootPath}/logs/\n * /tmp/camstack-cache/ (cache is always local)\n *\n * Individual location paths can be overridden.\n */\nexport class FilesystemStorageProvider implements IStorageProvider {\n readonly id = 'local'\n readonly name = 'Local Filesystem'\n readonly supportedLocations: readonly StorageLocationType[] = [...STORAGE_LOCATION_TYPES]\n\n private readonly rootPath: string\n private readonly locationPaths: Map<StorageLocationType, string>\n\n constructor(rootPath: string, overrides?: Partial<Record<StorageLocationType, string>>) {\n this.rootPath = path.resolve(rootPath)\n this.locationPaths = new Map()\n\n for (const loc of STORAGE_LOCATION_TYPES) {\n const override = overrides?.[loc]\n if (override) {\n this.locationPaths.set(loc, path.resolve(override))\n } else {\n const subdir = DEFAULT_LOCATION_SUBDIRS[loc]\n // Absolute paths (like /tmp/camstack-cache) are used as-is\n this.locationPaths.set(\n loc,\n path.isAbsolute(subdir) ? subdir : path.join(this.rootPath, subdir),\n )\n }\n }\n }\n\n async resolve({ location, relativePath }: StorageResolveInput): Promise<string> {\n const base = this.locationPaths.get(location)\n if (!base) throw new Error(`Unknown storage location: ${location}`)\n return path.join(base, relativePath)\n }\n\n async write({ location, relativePath, data }: StorageWriteInput): Promise<void> {\n const filePath = await this.resolve({ location, relativePath })\n await fs.promises.mkdir(path.dirname(filePath), { recursive: true })\n\n if (Buffer.isBuffer(data)) {\n await fs.promises.writeFile(filePath, data)\n } else {\n // Stream\n const writeStream = fs.createWriteStream(filePath)\n await new Promise<void>((resolve, reject) => {\n (data as NodeJS.ReadableStream).pipe(writeStream)\n writeStream.on('finish', resolve)\n writeStream.on('error', reject)\n })\n }\n }\n\n async read({ location, relativePath }: StorageReadInput): Promise<Buffer> {\n return fs.promises.readFile(await this.resolve({ location, relativePath }))\n }\n\n async exists({ location, relativePath }: StorageExistsInput): Promise<boolean> {\n try {\n await fs.promises.access(await this.resolve({ location, relativePath }))\n return true\n } catch {\n return false\n }\n }\n\n async list({ location, prefix }: StorageListInput): Promise<readonly string[]> {\n const base = this.locationPaths.get(location)\n if (!base) return []\n const dir = prefix ? path.join(base, prefix) : base\n try {\n const entries = await fs.promises.readdir(dir, { withFileTypes: true })\n return entries.map(e => (prefix ? `${prefix}/${e.name}` : e.name))\n } catch {\n return []\n }\n }\n\n async delete({ location, relativePath }: StorageDeleteInput): Promise<void> {\n const filePath = await this.resolve({ location, relativePath })\n await fs.promises.rm(filePath, { force: true })\n }\n\n async getAvailableSpace({ location }: StorageAvailableSpaceInput): Promise<number | null> {\n const base = this.locationPaths.get(location)\n if (!base) return null\n // If the directory doesn't exist yet (lazy creation), walk up to\n // the nearest existing ancestor so statfs can still report free\n // space on the mount point. This keeps the storage dashboard\n // working on a fresh install where no file has been written yet.\n try {\n let target = base\n while (!fs.existsSync(target)) {\n const parent = path.dirname(target)\n if (!parent || parent === target) return null\n target = parent\n }\n const stats = await fs.promises.statfs(target)\n return stats.bavail * stats.bsize\n } catch {\n return null\n }\n }\n\n\n /** Get the resolved path for a location type */\n getLocationPath(location: StorageLocationType): string {\n const p = this.locationPaths.get(location)\n if (!p) throw new Error(`Unknown storage location: ${location}`)\n return p\n }\n\n /** Get the root path */\n getRootPath(): string {\n return this.rootPath\n }\n}\n\nexport default FilesystemStorageProvider\n"],"names":["join","execFileSync","existsSync","mkdirSync","Readable","pipeline","createWriteStream","unlinkSync","chmodSync","readdirSync","rmSync","renameSync","writeFileSync","errMsg","readFileSync","createHash","basename","path","fs"],"mappings":"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"}
1
+ {"version":3,"file":"node.js","sources":["../src/deps/binary-downloader.ts","../src/deps/ffmpeg-downloader.ts","../src/deps/python-downloader.ts","../src/storage/filesystem-storage-provider.ts","../src/utils/canonical-hash.ts","../src/utils/export-reconciler.ts"],"sourcesContent":["import { existsSync, mkdirSync, chmodSync, createWriteStream, unlinkSync } from 'node:fs'\nimport { join } from 'node:path'\nimport { pipeline } from 'node:stream/promises'\nimport { Readable } from 'node:stream'\nimport { execFileSync } from 'node:child_process'\nimport type { IScopedLogger } from '../index.js'\n\nexport interface PlatformInfo {\n readonly platform: NodeJS.Platform\n readonly arch: NodeJS.Architecture\n}\n\nexport function getPlatformInfo(): PlatformInfo {\n return {\n platform: process.platform,\n arch: process.arch,\n }\n}\n\nexport function buildBinaryPath(dataDir: string, name: string, platform?: string): string {\n const ext = (platform ?? process.platform) === 'win32' ? '.exe' : ''\n return join(dataDir, 'deps', `${name}${ext}`)\n}\n\n/** Check if a binary exists in PATH */\nexport function findInPath(name: string): string | null {\n try {\n execFileSync(name, ['--version'], { stdio: 'pipe', timeout: 5000 })\n return name\n } catch {\n return null\n }\n}\n\nexport interface DownloadOptions {\n readonly name: string\n readonly url: string\n readonly targetDir: string\n readonly targetName: string\n readonly logger: IScopedLogger\n readonly isArchive?: boolean\n readonly archiveFormat?: 'zip' | 'tar.gz' | 'tar.xz'\n /** Relative path within archive to the binary (e.g., 'ffmpeg-6.1/bin/ffmpeg') */\n readonly archiveInnerPath?: string\n}\n\n/**\n * Download a binary to the target directory.\n * Handles archives (zip, tar.gz, tar.xz) and raw binaries.\n */\nexport async function downloadBinary(opts: DownloadOptions): Promise<string> {\n const { name, url, targetDir, targetName, logger, isArchive, archiveFormat, archiveInnerPath } = opts\n const targetPath = join(targetDir, targetName)\n\n if (existsSync(targetPath)) {\n logger.debug('Binary already exists', { meta: { name, targetPath } })\n return targetPath\n }\n\n mkdirSync(targetDir, { recursive: true })\n\n logger.info('Downloading binary', { meta: { name, url } })\n const response = await fetch(url, { redirect: 'follow' })\n if (!response.ok || !response.body) {\n throw new Error(`Failed to download ${name}: ${response.status} ${response.statusText}`)\n }\n\n if (isArchive) {\n const ext = archiveFormat ?? 'tar.gz'\n const tmpArchive = join(targetDir, `${name}-download.${ext}`)\n const nodeStream = Readable.fromWeb(response.body as ReadableStream)\n await pipeline(nodeStream, createWriteStream(tmpArchive))\n\n // Extract\n const tmpExtractDir = join(targetDir, `${name}-extract`)\n mkdirSync(tmpExtractDir, { recursive: true })\n\n if (ext === 'zip') {\n try {\n execFileSync('unzip', ['-o', '-q', tmpArchive, '-d', tmpExtractDir], { stdio: 'pipe' })\n } catch {\n execFileSync('tar', ['-xf', tmpArchive, '-C', tmpExtractDir], { stdio: 'pipe' })\n }\n } else if (ext === 'tar.xz') {\n execFileSync('tar', ['-xJf', tmpArchive, '-C', tmpExtractDir], { stdio: 'pipe' })\n } else {\n execFileSync('tar', ['-xzf', tmpArchive, '-C', tmpExtractDir], { stdio: 'pipe' })\n }\n\n // Copy binary from archive\n if (archiveInnerPath) {\n const { copyFileSync } = await import('node:fs')\n const sourcePath = join(tmpExtractDir, archiveInnerPath)\n if (!existsSync(sourcePath)) {\n throw new Error(`Binary not found in archive at ${archiveInnerPath}`)\n }\n copyFileSync(sourcePath, targetPath)\n }\n\n // Cleanup\n unlinkSync(tmpArchive)\n const { rmSync } = await import('node:fs')\n rmSync(tmpExtractDir, { recursive: true, force: true })\n } else {\n // Direct binary download\n const nodeStream = Readable.fromWeb(response.body as ReadableStream)\n await pipeline(nodeStream, createWriteStream(targetPath))\n }\n\n chmodSync(targetPath, 0o755)\n logger.info('Binary downloaded', { meta: { name, targetPath } })\n return targetPath\n}\n\n/**\n * Ensure a binary is available. Checks:\n * 1. Target path (already downloaded)\n * 2. System PATH\n * 3. Download from URL\n */\nexport async function ensureBinary(opts: {\n readonly name: string\n readonly targetDir: string\n readonly downloadUrl: string\n readonly logger: IScopedLogger\n readonly isArchive?: boolean\n readonly archiveFormat?: 'zip' | 'tar.gz' | 'tar.xz'\n readonly archiveInnerPath?: string\n}): Promise<string> {\n const ext = process.platform === 'win32' ? '.exe' : ''\n const targetName = `${opts.name}${ext}`\n const targetPath = join(opts.targetDir, targetName)\n\n // 1. Already downloaded\n if (existsSync(targetPath)) {\n opts.logger.debug('Binary found at target path', { meta: { name: opts.name, targetPath } })\n return targetPath\n }\n\n // 2. System PATH\n const inPath = findInPath(opts.name)\n if (inPath) {\n opts.logger.info('Binary found in system PATH', { meta: { name: opts.name } })\n return inPath\n }\n\n // 3. Download\n return downloadBinary({\n name: opts.name,\n url: opts.downloadUrl,\n targetDir: opts.targetDir,\n targetName,\n logger: opts.logger,\n isArchive: opts.isArchive,\n archiveFormat: opts.archiveFormat,\n archiveInnerPath: opts.archiveInnerPath,\n })\n}\n","/**\n * Download ffmpeg static build for the current platform.\n *\n * Sources:\n * - Linux: https://johnvansickle.com/ffmpeg/ (static builds)\n * - macOS: https://evermeet.cx/ffmpeg/ or homebrew\n *\n * Using BtbN's GitHub releases as they cover both platforms:\n * https://github.com/BtbN/FFmpeg-Builds/releases\n */\nimport { join } from 'node:path'\nimport type { IScopedLogger } from '../index.js'\nimport { ensureBinary } from './binary-downloader.js'\n\nconst FFMPEG_VERSION = '7.1'\n\nexport function getFfmpegDownloadUrl(platform: string, arch: string): string {\n switch (platform) {\n case 'linux': {\n const archMap: Record<string, string> = { x64: 'amd64', arm64: 'arm64' }\n const a = archMap[arch]\n if (!a) throw new Error(`Unsupported Linux architecture: ${arch}`)\n // John Van Sickle static builds — well-tested, widely used\n return `https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-${a}-static.tar.xz`\n }\n case 'darwin': {\n const archMap: Record<string, string> = { arm64: 'arm64', x64: 'amd64' }\n const a = archMap[arch]\n if (!a) throw new Error(`Unsupported macOS architecture: ${arch}`)\n // macOS static builds from osxexperts (maintained, universal builds)\n return `https://www.osxexperts.net/ffmpeg${FFMPEG_VERSION.replace('.', '')}arm.zip`\n }\n default:\n throw new Error(`Unsupported platform: ${platform}`)\n }\n}\n\nexport function getFfmpegArchiveInfo(platform: string): {\n isArchive: boolean\n archiveFormat: 'zip' | 'tar.gz' | 'tar.xz'\n archiveInnerPath: string\n} {\n switch (platform) {\n case 'linux':\n return {\n isArchive: true,\n archiveFormat: 'tar.xz',\n archiveInnerPath: `ffmpeg-${FFMPEG_VERSION}-amd64-static/ffmpeg`,\n }\n case 'darwin':\n return {\n isArchive: true,\n archiveFormat: 'zip',\n archiveInnerPath: 'ffmpeg',\n }\n default:\n throw new Error(`Unsupported platform: ${platform}`)\n }\n}\n\n/**\n * Ensure ffmpeg binary is available.\n * Checks: deps dir → system PATH → download.\n */\nexport async function ensureFfmpeg(dataDir: string, logger: IScopedLogger): Promise<string> {\n const depsDir = join(dataDir, 'deps')\n const platform = process.platform\n const arch = process.arch\n\n const archiveInfo = getFfmpegArchiveInfo(platform)\n\n return ensureBinary({\n name: 'ffmpeg',\n targetDir: depsDir,\n downloadUrl: getFfmpegDownloadUrl(platform, arch),\n logger,\n ...archiveInfo,\n })\n}\n","/**\n * Download portable Python (headless) from bjia56/portable-python.\n *\n * Source: https://github.com/bjia56/portable-python\n *\n * Advantages over python-build-standalone:\n * - macOS universal2 binary (arm64+x86_64 in one)\n * - Lighter (~24-28MB headless zip)\n * - glibc 2.17 minimum (better compat)\n * - Headless variant excludes tkinter/UI (ideal for ML inference)\n *\n * The zip's top-level dir is platform-specific (e.g.\n * `python-headless-3.12.12-darwin-universal2/`); after extraction\n * we rename it to a stable `python/` so consumers can rely on\n * `<dataDir>/deps/python/bin/python3`.\n */\nimport { join, basename } from 'node:path'\nimport { existsSync, mkdirSync, chmodSync, readFileSync, writeFileSync, readdirSync, renameSync, rmSync } from 'node:fs'\nimport { execFileSync } from 'node:child_process'\nimport { createHash } from 'node:crypto'\nimport { pipeline } from 'node:stream/promises'\nimport { Readable } from 'node:stream'\nimport { createWriteStream } from 'node:fs'\nimport type { IScopedLogger } from '../index.js'\nimport { errMsg } from '../index.js'\n\nexport const PYTHON_VERSION = '3.12.12'\nconst PP_BASE = `https://github.com/bjia56/portable-python/releases/download/cpython-v${PYTHON_VERSION}-build.0`\n\nexport function getPythonDownloadUrl(platform: string, arch: string): string {\n switch (platform) {\n case 'linux': {\n const archMap: Record<string, string> = { x64: 'x86_64', arm64: 'aarch64' }\n const a = archMap[arch]\n if (!a) throw new Error(`Unsupported Linux architecture for Python: ${arch}`)\n return `${PP_BASE}/python-headless-${PYTHON_VERSION}-linux-${a}.zip`\n }\n case 'darwin': {\n // universal2 covers both arm64 and x86_64\n return `${PP_BASE}/python-headless-${PYTHON_VERSION}-darwin-universal2.zip`\n }\n default:\n throw new Error(`Unsupported platform for portable Python: ${platform}`)\n }\n}\n\n/**\n * Ensure the embedded portable Python is available.\n *\n * ALWAYS returns the path to the embedded interpreter under\n * `<dataDir>/deps/python/bin/python3` — downloads it on first call\n * and reuses it thereafter. The system PATH is intentionally NOT\n * consulted: addons rely on `installPythonRequirements()` to manage\n * deps inside the embedded site-packages, and a system interpreter\n * (different version, missing modules) silently breaks that contract.\n *\n * Returns null only if the download/extract step fails.\n */\nexport async function ensurePython(dataDir: string, logger: IScopedLogger): Promise<string | null> {\n const pythonDir = join(dataDir, 'deps', 'python')\n const pythonBin = join(pythonDir, 'bin', 'python3')\n\n // 1. Already downloaded\n if (existsSync(pythonBin)) {\n logger.debug('Portable Python found', { meta: { pythonBin } })\n return pythonBin\n }\n\n // 2. Download portable python (no system PATH fallback — by design)\n logger.info('Downloading portable Python (headless)', { meta: { version: PYTHON_VERSION } })\n\n const depsDir = join(dataDir, 'deps')\n mkdirSync(depsDir, { recursive: true })\n\n const url = getPythonDownloadUrl(process.platform, process.arch)\n const tmpArchive = join(depsDir, 'python-download.zip')\n\n logger.info('Python download source', { meta: { url } })\n\n const response = await fetch(url, { redirect: 'follow' })\n if (!response.ok || !response.body) {\n logger.warn('Failed to download Python — ML detection will not be available', { meta: { status: response.status } })\n return null\n }\n\n const nodeStream = Readable.fromWeb(response.body as ReadableStream)\n await pipeline(nodeStream, createWriteStream(tmpArchive))\n\n // Extract — zip's top-level dir is e.g. `python-headless-3.12.12-darwin-universal2/`\n // (varies per platform/arch).\n try {\n execFileSync('unzip', ['-o', '-q', tmpArchive, '-d', depsDir], { stdio: 'pipe' })\n } catch {\n // Fallback: some minimal Linux installs don't have unzip\n execFileSync('python3', ['-m', 'zipfile', '-e', tmpArchive, depsDir], { stdio: 'pipe' })\n }\n\n // Cleanup the archive\n const { unlinkSync } = await import('node:fs')\n unlinkSync(tmpArchive)\n\n // Locate the extracted directory and normalize it to `<depsDir>/python/`.\n // The zip's top-level name varies by platform (e.g.\n // `python-headless-3.12.12-darwin-universal2/`), so we look for any\n // `python-*` subdir that contains the expected `bin/python3` and rename it.\n if (!existsSync(pythonBin)) {\n const candidates = readdirSync(depsDir).filter((name) =>\n name.startsWith('python-') &&\n existsSync(join(depsDir, name, 'bin', 'python3')),\n )\n if (candidates.length === 0) {\n logger.warn('Python extraction succeeded but no python3 binary found in any subdir', {\n meta: { depsDir },\n })\n return null\n }\n const extractedRoot = join(depsDir, candidates[0]!)\n // Drop any stale `python/` (e.g. from a previous failed install)\n // before renaming so the move always succeeds.\n if (existsSync(pythonDir)) {\n rmSync(pythonDir, { recursive: true, force: true })\n }\n renameSync(extractedRoot, pythonDir)\n }\n\n if (!existsSync(pythonBin)) {\n logger.warn('Python extraction succeeded but binary not found at expected path', {\n meta: { pythonBin },\n })\n return null\n }\n\n chmodSync(pythonBin, 0o755)\n\n // On macOS the bjia56 build is universal2 — the host arch is decided\n // at spawn time based on the parent process. That makes pip wheels\n // and runtime spawns silently disagree when the server is launched\n // from a Rosetta shell once and a native shell next time (e.g. pip\n // installs `numpy x86_64`, then the next boot under arm64 fails with\n // `incompatible architecture`). Lock the slice deterministically by\n // wrapping `python3` in a shell stub that always invokes the matching\n // host arch via `/usr/bin/arch`. Idempotent: detected via the renamed\n // `.real` binary.\n if (process.platform === 'darwin') {\n const realBin = join(pythonDir, 'bin', 'python3.real')\n if (!existsSync(realBin)) {\n renameSync(pythonBin, realBin)\n const archFlag = process.arch === 'arm64' ? '-arm64' : '-x86_64'\n writeFileSync(\n pythonBin,\n `#!/bin/sh\\nexec /usr/bin/arch ${archFlag} \"${realBin}\" \"$@\"\\n`,\n )\n chmodSync(pythonBin, 0o755)\n // Mirror the same wrapping for the `python` symlink so consumers\n // that use the un-versioned name also lock the arch.\n const pyAlt = join(pythonDir, 'bin', 'python')\n if (existsSync(pyAlt)) {\n try {\n rmSync(pyAlt, { force: true })\n } catch {\n // ignore\n }\n writeFileSync(\n pyAlt,\n `#!/bin/sh\\nexec /usr/bin/arch ${archFlag} \"${realBin}\" \"$@\"\\n`,\n )\n chmodSync(pyAlt, 0o755)\n }\n logger.info('Locked python3 to host arch via shell stub', {\n meta: { archFlag, realBin },\n })\n }\n }\n\n logger.info('Portable Python installed', { meta: { version: PYTHON_VERSION, pythonBin } })\n return pythonBin\n}\n\n/**\n * Install Python packages into the portable Python environment.\n */\nexport async function installPythonPackages(\n pythonPath: string,\n packages: readonly string[],\n logger: IScopedLogger,\n): Promise<void> {\n if (packages.length === 0) return\n\n logger.info('Installing Python packages', { meta: { packages } })\n try {\n execFileSync(pythonPath, ['-m', 'pip', 'install', '--quiet', ...packages], {\n stdio: 'pipe',\n timeout: 300_000, // 5 minutes for large packages like torch\n })\n logger.info('Python packages installed successfully')\n } catch (err) {\n logger.error('Failed to install Python packages', { meta: { error: errMsg(err) } })\n throw err\n }\n}\n\n/**\n * Install a pip requirements file into the embedded Python.\n *\n * Idempotent: keyed on (requirements file basename + sha256 of its\n * contents). The marker is written under\n * `<python-install-dir>/.requirements-installed/` so it lives and\n * dies with the python install — re-downloading python wipes both\n * the site-packages and the markers, forcing a fresh re-install.\n *\n * The marker is updated only after `pip install` succeeds. A failed\n * install never writes a marker, so the next call retries.\n */\nexport async function installPythonRequirements(\n pythonPath: string,\n requirementsFile: string,\n logger: IScopedLogger,\n): Promise<void> {\n if (!existsSync(requirementsFile)) {\n throw new Error(`Requirements file not found: ${requirementsFile}`)\n }\n\n const contents = readFileSync(requirementsFile)\n const hash = createHash('sha256').update(contents).digest('hex').slice(0, 16)\n const fileBase = basename(requirementsFile).replace(/\\.txt$/i, '')\n\n // Markers live under the python install dir → wiped together when\n // the embedded interpreter is re-downloaded.\n const pythonRoot = join(pythonPath, '..', '..') // <root>/bin/python3 → <root>\n const markerDir = join(pythonRoot, '.requirements-installed')\n const markerPath = join(markerDir, `${fileBase}-${hash}.marker`)\n\n if (existsSync(markerPath)) {\n logger.debug('Python requirements already installed', { meta: { requirementsFile, hash } })\n return\n }\n\n logger.info('Installing Python requirements', { meta: { requirementsFile, hash } })\n try {\n execFileSync(\n pythonPath,\n ['-m', 'pip', 'install', '--quiet', '--disable-pip-version-check', '-r', requirementsFile],\n {\n stdio: 'pipe',\n timeout: 600_000, // 10 minutes — coremltools + tensorflow can be slow\n },\n )\n } catch (err) {\n logger.error('Failed to install Python requirements', {\n meta: { requirementsFile, error: errMsg(err) },\n })\n throw err\n }\n\n mkdirSync(markerDir, { recursive: true })\n writeFileSync(markerPath, `${requirementsFile}\\n${new Date().toISOString()}\\n`)\n logger.info('Python requirements installed', { meta: { requirementsFile, hash } })\n}\n","import * as fs from 'node:fs'\nimport * as path from 'node:path'\nimport type {\n IStorageProvider,\n StorageLocationType,\n StorageResolveInput,\n StorageWriteInput,\n StorageReadInput,\n StorageExistsInput,\n StorageListInput,\n StorageDeleteInput,\n StorageAvailableSpaceInput,\n} from '../index.js'\n\n// Inline constants to avoid runtime dependency on @camstack/types\n// (types is external in tsup bundle, may not be resolvable from data/addons/)\nconst STORAGE_LOCATION_TYPES: readonly StorageLocationType[] = [\n 'data', 'media', 'recordings',\n 'recordings-high', 'recordings-low', 'recordings-clips', 'event-images',\n 'models', 'addons-data', 'cache', 'logs', 'backups',\n]\n\nconst DEFAULT_LOCATION_SUBDIRS: Readonly<Record<StorageLocationType, string>> = {\n 'data': 'db',\n 'media': 'media',\n 'recordings': 'recordings',\n 'recordings-high': 'recordings-high',\n 'recordings-low': 'recordings-low',\n 'recordings-clips': 'recordings-clips',\n 'event-images': 'event-images',\n 'models': 'models',\n 'addons-data': 'addons-data',\n 'cache': '/tmp/camstack-cache',\n 'logs': 'logs',\n 'backups': 'backups',\n}\n\n/**\n * Filesystem storage provider — serves all location types from a local directory tree.\n *\n * Default layout:\n * {rootPath}/recordings-high/\n * {rootPath}/recordings-low/\n * {rootPath}/recordings-clips/\n * {rootPath}/event-images/\n * {rootPath}/models/\n * {rootPath}/addons-data/\n * {rootPath}/logs/\n * /tmp/camstack-cache/ (cache is always local)\n *\n * Individual location paths can be overridden.\n */\nexport class FilesystemStorageProvider implements IStorageProvider {\n readonly id = 'local'\n readonly name = 'Local Filesystem'\n readonly supportedLocations: readonly StorageLocationType[] = [...STORAGE_LOCATION_TYPES]\n\n private readonly rootPath: string\n private readonly locationPaths: Map<StorageLocationType, string>\n\n constructor(rootPath: string, overrides?: Partial<Record<StorageLocationType, string>>) {\n this.rootPath = path.resolve(rootPath)\n this.locationPaths = new Map()\n\n for (const loc of STORAGE_LOCATION_TYPES) {\n const override = overrides?.[loc]\n if (override) {\n this.locationPaths.set(loc, path.resolve(override))\n } else {\n const subdir = DEFAULT_LOCATION_SUBDIRS[loc] ?? loc\n // Absolute paths (like /tmp/camstack-cache) are used as-is\n this.locationPaths.set(\n loc,\n path.isAbsolute(subdir) ? subdir : path.join(this.rootPath, subdir),\n )\n }\n }\n }\n\n async resolve({ location, relativePath }: StorageResolveInput): Promise<string> {\n // Known preset types use their mapped path; any other (addon-declared)\n // location id resolves to `<rootPath>/<id>`, matching the orchestrator's\n // `localData/<id>` default — so new declared locations (eventMedia,\n // recordingsLow, …) work without a hardcoded entry here.\n const base = this.locationPaths.get(location) ?? path.join(this.rootPath, location)\n return path.join(base, relativePath)\n }\n\n async write({ location, relativePath, data }: StorageWriteInput): Promise<void> {\n const filePath = await this.resolve({ location, relativePath })\n await fs.promises.mkdir(path.dirname(filePath), { recursive: true })\n\n if (Buffer.isBuffer(data)) {\n await fs.promises.writeFile(filePath, data)\n } else {\n // Stream\n const writeStream = fs.createWriteStream(filePath)\n await new Promise<void>((resolve, reject) => {\n (data as NodeJS.ReadableStream).pipe(writeStream)\n writeStream.on('finish', resolve)\n writeStream.on('error', reject)\n })\n }\n }\n\n async read({ location, relativePath }: StorageReadInput): Promise<Buffer> {\n return fs.promises.readFile(await this.resolve({ location, relativePath }))\n }\n\n async exists({ location, relativePath }: StorageExistsInput): Promise<boolean> {\n try {\n await fs.promises.access(await this.resolve({ location, relativePath }))\n return true\n } catch {\n return false\n }\n }\n\n async list({ location, prefix }: StorageListInput): Promise<readonly string[]> {\n const base = this.locationPaths.get(location)\n if (!base) return []\n const dir = prefix ? path.join(base, prefix) : base\n try {\n const entries = await fs.promises.readdir(dir, { withFileTypes: true })\n return entries.map(e => (prefix ? `${prefix}/${e.name}` : e.name))\n } catch {\n return []\n }\n }\n\n async delete({ location, relativePath }: StorageDeleteInput): Promise<void> {\n const filePath = await this.resolve({ location, relativePath })\n await fs.promises.rm(filePath, { force: true })\n }\n\n async getAvailableSpace({ location }: StorageAvailableSpaceInput): Promise<number | null> {\n const base = this.locationPaths.get(location)\n if (!base) return null\n // If the directory doesn't exist yet (lazy creation), walk up to\n // the nearest existing ancestor so statfs can still report free\n // space on the mount point. This keeps the storage dashboard\n // working on a fresh install where no file has been written yet.\n try {\n let target = base\n while (!fs.existsSync(target)) {\n const parent = path.dirname(target)\n if (!parent || parent === target) return null\n target = parent\n }\n const stats = await fs.promises.statfs(target)\n return stats.bavail * stats.bsize\n } catch {\n return null\n }\n }\n\n\n /** Get the resolved path for a location type (addon-declared ids fall back\n * to `<rootPath>/<id>`). */\n getLocationPath(location: StorageLocationType): string {\n return this.locationPaths.get(location) ?? path.join(this.rootPath, location)\n }\n\n /** Get the root path */\n getRootPath(): string {\n return this.rootPath\n }\n}\n\nexport default FilesystemStorageProvider\n","import { createHash } from 'node:crypto'\n\n/**\n * Deterministic SHA-256 hash of an arbitrary serialisable value. The\n * canonical form sorts object keys alphabetically at every depth so two\n * structurally-equal inputs with different key insertion orders produce\n * the same hash. Returns a 64-char lowercase hex digest.\n *\n * Used by export adapters (Alexa, HAP) to short-circuit re-discovery /\n * accessory-rebuild work when the upstream shape is byte-identical to\n * the last applied state — preventing user-visible \"re-discovery\"\n * notifications on every addon-runner respawn. Each respawn re-fires\n * `DeviceBindingsChanged` for every cap registration, which without\n * this guard would propagate redundant pushes.\n *\n * Note: this is a SYMPTOMATIC fix layered on top of the binding-change\n * subscription. The proper fix is a single \"device ready\" lifecycle\n * barrier so exports react only when the full cap set has landed —\n * tracked separately for post-HA-integration work.\n */\nexport function canonicalHash(value: unknown): string {\n const canonical = JSON.stringify(value, replaceWithSortedKeys)\n return createHash('sha256').update(canonical ?? '').digest('hex')\n}\n\nfunction replaceWithSortedKeys(_key: string, value: unknown): unknown {\n if (value && typeof value === 'object' && !Array.isArray(value)) {\n const obj = value as Record<string, unknown>\n const out: Record<string, unknown> = {}\n for (const k of Object.keys(obj).sort()) out[k] = obj[k]\n return out\n }\n return value\n}\n","import { canonicalHash } from './canonical-hash.js'\n\n/**\n * Pure, stateless primitives shared by the camera export addons (Alexa, HAP)\n * to advertise devices to external ecosystems without re-discovery storms.\n *\n * Spec: docs/superpowers/specs/2026-06-01-alexa-hap-export-reconciler-design.md\n *\n * Two functions, both pure (no I/O, no clock, no randomness, no event\n * subscriptions) so they are exhaustively unit-testable in isolation:\n *\n * 1) `canonicalDeviceFingerprint(shape)` — a stable hash of the\n * EXPORT-RELEVANT device shape (coarse type + the persisted\n * `DeviceFeature` set). Order-independent and de-duplicated, so an\n * unchanged device produces an identical fingerprint across restarts.\n * Identity (`deviceId`) and volatile runtime values (online, battery%,\n * name, location) are deliberately excluded.\n *\n * 2) `diffExportTargets(desired, lastAdvertised)` — a 3-way set diff keyed\n * by `deviceId`: what to add, what to update (fingerprint changed), what\n * to remove (no longer desired). Each exporter feeds its own persisted\n * per-target sync state in and applies the resulting delta to its\n * platform.\n *\n * The wiring — event subscription, boot scan, platform apply, persistence —\n * stays in each exporter; only the fingerprint + diff are shared.\n */\n\n/** The export-relevant projection of a device, fed into the fingerprint. */\nexport interface DeviceExportShape {\n /** Numeric device id. Identity only — NOT part of the fingerprint. */\n readonly deviceId: number\n /**\n * Coarse driver classification (e.g. `'camera' | 'battery-camera' | 'nvr'`),\n * sourced from the `feature-probe` slice's `deviceType`. Part of the shape:\n * the same feature set on a different device class can map to a different\n * export surface.\n */\n readonly deviceType: string\n /**\n * Export-relevant `DeviceFeature` values (e.g. `'pan-tilt-zoom'`,\n * `'two-way-audio'`, `'doorbell-button'`, `'motion-trigger'`,\n * `'battery-operated'`, `'ptz-autotrack'`). Sourced from the PERSISTED\n * `PersistedDeviceMeta.features`, so it survives restart and is stable\n * without a re-probe. Hashed order-independently + de-duplicated.\n */\n readonly features: readonly string[]\n}\n\n/**\n * Compute the stable 64-char lowercase-hex fingerprint of a device's\n * export-relevant shape. Two structurally-equal shapes (any feature order,\n * any duplicates, any deviceId) hash identically.\n */\nexport function canonicalDeviceFingerprint(shape: DeviceExportShape): string {\n const features = [...new Set(shape.features)].sort()\n return canonicalHash({ deviceType: shape.deviceType, features })\n}\n\n/** Per-export-target sync state for one device, persisted addon-side. */\nexport interface ExportTargetEntry {\n /** Fingerprint last advertised to this target for this device. */\n readonly fingerprint: string\n /**\n * Platform-specific external ids (Alexa endpoint ids, HAP accessory UUIDs).\n * Opaque to the diff — carried so the exporter can address what to update or\n * delete on its platform.\n */\n readonly externalIds: readonly string[]\n}\n\n/** The 3-way delta produced by `diffExportTargets`. */\nexport interface ExportDelta<T> {\n /** Desired devices with no last-advertised entry — publish fresh. */\n readonly toAdd: readonly T[]\n /** Desired devices whose fingerprint differs from last-advertised — re-advertise. */\n readonly toUpdate: readonly T[]\n /** `deviceId`s present in last-advertised but absent from desired — retract. */\n readonly toRemove: readonly number[]\n}\n\n/**\n * Pure 3-way diff of desired export state against the persisted\n * last-advertised state, keyed by `deviceId`.\n *\n * - `desired` — one entry per device to advertise this cycle, each carrying\n * the freshly-computed `fingerprint`.\n * - `lastAdvertised` — the exporter's persisted per-target sync state.\n *\n * Neither input is mutated.\n */\nexport function diffExportTargets<T extends { deviceId: number; fingerprint: string }>(\n desired: readonly T[],\n lastAdvertised: ReadonlyMap<number, ExportTargetEntry>,\n): ExportDelta<T> {\n const toAdd: T[] = []\n const toUpdate: T[] = []\n const desiredIds = new Set<number>()\n\n for (const entry of desired) {\n desiredIds.add(entry.deviceId)\n const previous = lastAdvertised.get(entry.deviceId)\n if (previous === undefined) {\n toAdd.push(entry)\n } else if (previous.fingerprint !== entry.fingerprint) {\n toUpdate.push(entry)\n }\n // unchanged fingerprint → no delta\n }\n\n const toRemove: number[] = []\n for (const deviceId of lastAdvertised.keys()) {\n if (!desiredIds.has(deviceId)) toRemove.push(deviceId)\n }\n\n return { toAdd, toUpdate, toRemove }\n}\n\n/**\n * Resolve the fingerprint an exporter should feed into `diffExportTargets`\n * for one device this cycle.\n *\n * When a device is NOT yet probe-ready, carry forward its last-advertised\n * (persisted) fingerprint so it nets to NO CHANGE in `diffExportTargets`\n * (neither update nor remove) — never advertise a partial mid-probe shape. A\n * brand-new un-probed device with no persisted entry falls back to `fresh` so\n * it can advertise once.\n */\nexport function resolveExportFingerprint(input: {\n ready: boolean\n fresh: string\n persisted: string | undefined\n}): string {\n if (input.ready) return input.fresh\n return input.persisted ?? input.fresh\n}\n"],"names":["join","execFileSync","existsSync","mkdirSync","Readable","pipeline","createWriteStream","unlinkSync","chmodSync","readdirSync","rmSync","renameSync","writeFileSync","errMsg","readFileSync","createHash","basename","path","fs"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAYO,SAAS,kBAAgC;AAC9C,SAAO;AAAA,IACL,UAAU,QAAQ;AAAA,IAClB,MAAM,QAAQ;AAAA,EAAA;AAElB;AAEO,SAAS,gBAAgB,SAAiB,MAAc,UAA2B;AACxF,QAAM,OAAO,YAAY,QAAQ,cAAc,UAAU,SAAS;AAClE,SAAOA,KAAAA,KAAK,SAAS,QAAQ,GAAG,IAAI,GAAG,GAAG,EAAE;AAC9C;AAGO,SAAS,WAAW,MAA6B;AACtD,MAAI;AACFC,oCAAa,MAAM,CAAC,WAAW,GAAG,EAAE,OAAO,QAAQ,SAAS,KAAM;AAClE,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAkBA,eAAsB,eAAe,MAAwC;AAC3E,QAAM,EAAE,MAAM,KAAK,WAAW,YAAY,QAAQ,WAAW,eAAe,iBAAA,IAAqB;AACjG,QAAM,aAAaD,KAAAA,KAAK,WAAW,UAAU;AAE7C,MAAIE,GAAAA,WAAW,UAAU,GAAG;AAC1B,WAAO,MAAM,yBAAyB,EAAE,MAAM,EAAE,MAAM,WAAA,GAAc;AACpE,WAAO;AAAA,EACT;AAEAC,KAAAA,UAAU,WAAW,EAAE,WAAW,KAAA,CAAM;AAExC,SAAO,KAAK,sBAAsB,EAAE,MAAM,EAAE,MAAM,IAAA,GAAO;AACzD,QAAM,WAAW,MAAM,MAAM,KAAK,EAAE,UAAU,UAAU;AACxD,MAAI,CAAC,SAAS,MAAM,CAAC,SAAS,MAAM;AAClC,UAAM,IAAI,MAAM,sBAAsB,IAAI,KAAK,SAAS,MAAM,IAAI,SAAS,UAAU,EAAE;AAAA,EACzF;AAEA,MAAI,WAAW;AACb,UAAM,MAAM,iBAAiB;AAC7B,UAAM,aAAaH,KAAAA,KAAK,WAAW,GAAG,IAAI,aAAa,GAAG,EAAE;AAC5D,UAAM,aAAaI,YAAAA,SAAS,QAAQ,SAAS,IAAsB;AACnE,UAAMC,kBAAS,YAAYC,GAAAA,kBAAkB,UAAU,CAAC;AAGxD,UAAM,gBAAgBN,KAAAA,KAAK,WAAW,GAAG,IAAI,UAAU;AACvDG,OAAAA,UAAU,eAAe,EAAE,WAAW,KAAA,CAAM;AAE5C,QAAI,QAAQ,OAAO;AACjB,UAAI;AACFF,2BAAAA,aAAa,SAAS,CAAC,MAAM,MAAM,YAAY,MAAM,aAAa,GAAG,EAAE,OAAO,OAAA,CAAQ;AAAA,MACxF,QAAQ;AACNA,wCAAa,OAAO,CAAC,OAAO,YAAY,MAAM,aAAa,GAAG,EAAE,OAAO,QAAQ;AAAA,MACjF;AAAA,IACF,WAAW,QAAQ,UAAU;AAC3BA,sCAAa,OAAO,CAAC,QAAQ,YAAY,MAAM,aAAa,GAAG,EAAE,OAAO,QAAQ;AAAA,IAClF,OAAO;AACLA,sCAAa,OAAO,CAAC,QAAQ,YAAY,MAAM,aAAa,GAAG,EAAE,OAAO,QAAQ;AAAA,IAClF;AAGA,QAAI,kBAAkB;AACpB,YAAM,EAAE,aAAA,IAAiB,MAAM,OAAO,SAAS;AAC/C,YAAM,aAAaD,KAAAA,KAAK,eAAe,gBAAgB;AACvD,UAAI,CAACE,GAAAA,WAAW,UAAU,GAAG;AAC3B,cAAM,IAAI,MAAM,kCAAkC,gBAAgB,EAAE;AAAA,MACtE;AACA,mBAAa,YAAY,UAAU;AAAA,IACrC;AAGAK,OAAAA,WAAW,UAAU;AACrB,UAAM,EAAE,OAAA,IAAW,MAAM,OAAO,SAAS;AACzC,WAAO,eAAe,EAAE,WAAW,MAAM,OAAO,MAAM;AAAA,EACxD,OAAO;AAEL,UAAM,aAAaH,YAAAA,SAAS,QAAQ,SAAS,IAAsB;AACnE,UAAMC,kBAAS,YAAYC,GAAAA,kBAAkB,UAAU,CAAC;AAAA,EAC1D;AAEAE,KAAAA,UAAU,YAAY,GAAK;AAC3B,SAAO,KAAK,qBAAqB,EAAE,MAAM,EAAE,MAAM,WAAA,GAAc;AAC/D,SAAO;AACT;AAQA,eAAsB,aAAa,MAQf;AAClB,QAAM,MAAM,QAAQ,aAAa,UAAU,SAAS;AACpD,QAAM,aAAa,GAAG,KAAK,IAAI,GAAG,GAAG;AACrC,QAAM,aAAaR,KAAAA,KAAK,KAAK,WAAW,UAAU;AAGlD,MAAIE,GAAAA,WAAW,UAAU,GAAG;AAC1B,SAAK,OAAO,MAAM,+BAA+B,EAAE,MAAM,EAAE,MAAM,KAAK,MAAM,WAAA,EAAW,CAAG;AAC1F,WAAO;AAAA,EACT;AAGA,QAAM,SAAS,WAAW,KAAK,IAAI;AACnC,MAAI,QAAQ;AACV,SAAK,OAAO,KAAK,+BAA+B,EAAE,MAAM,EAAE,MAAM,KAAK,KAAA,GAAQ;AAC7E,WAAO;AAAA,EACT;AAGA,SAAO,eAAe;AAAA,IACpB,MAAM,KAAK;AAAA,IACX,KAAK,KAAK;AAAA,IACV,WAAW,KAAK;AAAA,IAChB;AAAA,IACA,QAAQ,KAAK;AAAA,IACb,WAAW,KAAK;AAAA,IAChB,eAAe,KAAK;AAAA,IACpB,kBAAkB,KAAK;AAAA,EAAA,CACxB;AACH;AC/IA,MAAM,iBAAiB;AAEhB,SAAS,qBAAqB,UAAkB,MAAsB;AAC3E,UAAQ,UAAA;AAAA,IACN,KAAK,SAAS;AACZ,YAAM,UAAkC,EAAE,KAAK,SAAS,OAAO,QAAA;AAC/D,YAAM,IAAI,QAAQ,IAAI;AACtB,UAAI,CAAC,EAAG,OAAM,IAAI,MAAM,mCAAmC,IAAI,EAAE;AAEjE,aAAO,4DAA4D,CAAC;AAAA,IACtE;AAAA,IACA,KAAK,UAAU;AACb,YAAM,UAAkC,EAAE,OAAO,SAAS,KAAK,QAAA;AAC/D,YAAM,IAAI,QAAQ,IAAI;AACtB,UAAI,CAAC,EAAG,OAAM,IAAI,MAAM,mCAAmC,IAAI,EAAE;AAEjE,aAAO,oCAAoC,eAAe,QAAQ,KAAK,EAAE,CAAC;AAAA,IAC5E;AAAA,IACA;AACE,YAAM,IAAI,MAAM,yBAAyB,QAAQ,EAAE;AAAA,EAAA;AAEzD;AAEO,SAAS,qBAAqB,UAInC;AACA,UAAQ,UAAA;AAAA,IACN,KAAK;AACH,aAAO;AAAA,QACL,WAAW;AAAA,QACX,eAAe;AAAA,QACf,kBAAkB,UAAU,cAAc;AAAA,MAAA;AAAA,IAE9C,KAAK;AACH,aAAO;AAAA,QACL,WAAW;AAAA,QACX,eAAe;AAAA,QACf,kBAAkB;AAAA,MAAA;AAAA,IAEtB;AACE,YAAM,IAAI,MAAM,yBAAyB,QAAQ,EAAE;AAAA,EAAA;AAEzD;AAMA,eAAsB,aAAa,SAAiB,QAAwC;AAC1F,QAAM,UAAUF,KAAAA,KAAK,SAAS,MAAM;AACpC,QAAM,WAAW,QAAQ;AACzB,QAAM,OAAO,QAAQ;AAErB,QAAM,cAAc,qBAAqB,QAAQ;AAEjD,SAAO,aAAa;AAAA,IAClB,MAAM;AAAA,IACN,WAAW;AAAA,IACX,aAAa,qBAAqB,UAAU,IAAI;AAAA,IAChD;AAAA,IACA,GAAG;AAAA,EAAA,CACJ;AACH;ACpDO,MAAM,iBAAiB;AAC9B,MAAM,UAAU,wEAAwE,cAAc;AAE/F,SAAS,qBAAqB,UAAkB,MAAsB;AAC3E,UAAQ,UAAA;AAAA,IACN,KAAK,SAAS;AACZ,YAAM,UAAkC,EAAE,KAAK,UAAU,OAAO,UAAA;AAChE,YAAM,IAAI,QAAQ,IAAI;AACtB,UAAI,CAAC,EAAG,OAAM,IAAI,MAAM,8CAA8C,IAAI,EAAE;AAC5E,aAAO,GAAG,OAAO,oBAAoB,cAAc,UAAU,CAAC;AAAA,IAChE;AAAA,IACA,KAAK,UAAU;AAEb,aAAO,GAAG,OAAO,oBAAoB,cAAc;AAAA,IACrD;AAAA,IACA;AACE,YAAM,IAAI,MAAM,6CAA6C,QAAQ,EAAE;AAAA,EAAA;AAE7E;AAcA,eAAsB,aAAa,SAAiB,QAA+C;AACjG,QAAM,YAAYA,KAAAA,KAAK,SAAS,QAAQ,QAAQ;AAChD,QAAM,YAAYA,KAAAA,KAAK,WAAW,OAAO,SAAS;AAGlD,MAAIE,GAAAA,WAAW,SAAS,GAAG;AACzB,WAAO,MAAM,yBAAyB,EAAE,MAAM,EAAE,UAAA,GAAa;AAC7D,WAAO;AAAA,EACT;AAGA,SAAO,KAAK,0CAA0C,EAAE,MAAM,EAAE,SAAS,eAAA,GAAkB;AAE3F,QAAM,UAAUF,KAAAA,KAAK,SAAS,MAAM;AACpCG,KAAAA,UAAU,SAAS,EAAE,WAAW,KAAA,CAAM;AAEtC,QAAM,MAAM,qBAAqB,QAAQ,UAAU,QAAQ,IAAI;AAC/D,QAAM,aAAaH,KAAAA,KAAK,SAAS,qBAAqB;AAEtD,SAAO,KAAK,0BAA0B,EAAE,MAAM,EAAE,IAAA,GAAO;AAEvD,QAAM,WAAW,MAAM,MAAM,KAAK,EAAE,UAAU,UAAU;AACxD,MAAI,CAAC,SAAS,MAAM,CAAC,SAAS,MAAM;AAClC,WAAO,KAAK,kEAAkE,EAAE,MAAM,EAAE,QAAQ,SAAS,OAAA,GAAU;AACnH,WAAO;AAAA,EACT;AAEA,QAAM,aAAaI,YAAAA,SAAS,QAAQ,SAAS,IAAsB;AACnE,QAAMC,kBAAS,YAAYC,GAAAA,kBAAkB,UAAU,CAAC;AAIxD,MAAI;AACFL,uBAAAA,aAAa,SAAS,CAAC,MAAM,MAAM,YAAY,MAAM,OAAO,GAAG,EAAE,OAAO,OAAA,CAAQ;AAAA,EAClF,QAAQ;AAENA,uBAAAA,aAAa,WAAW,CAAC,MAAM,WAAW,MAAM,YAAY,OAAO,GAAG,EAAE,OAAO,OAAA,CAAQ;AAAA,EACzF;AAGA,QAAM,EAAE,WAAA,IAAe,MAAM,OAAO,SAAS;AAC7C,aAAW,UAAU;AAMrB,MAAI,CAACC,GAAAA,WAAW,SAAS,GAAG;AAC1B,UAAM,aAAaO,GAAAA,YAAY,OAAO,EAAE;AAAA,MAAO,CAAC,SAC9C,KAAK,WAAW,SAAS,KACzBP,cAAWF,KAAAA,KAAK,SAAS,MAAM,OAAO,SAAS,CAAC;AAAA,IAAA;AAElD,QAAI,WAAW,WAAW,GAAG;AAC3B,aAAO,KAAK,yEAAyE;AAAA,QACnF,MAAM,EAAE,QAAA;AAAA,MAAQ,CACjB;AACD,aAAO;AAAA,IACT;AACA,UAAM,gBAAgBA,KAAAA,KAAK,SAAS,WAAW,CAAC,CAAE;AAGlD,QAAIE,GAAAA,WAAW,SAAS,GAAG;AACzBQ,SAAAA,OAAO,WAAW,EAAE,WAAW,MAAM,OAAO,MAAM;AAAA,IACpD;AACAC,OAAAA,WAAW,eAAe,SAAS;AAAA,EACrC;AAEA,MAAI,CAACT,GAAAA,WAAW,SAAS,GAAG;AAC1B,WAAO,KAAK,qEAAqE;AAAA,MAC/E,MAAM,EAAE,UAAA;AAAA,IAAU,CACnB;AACD,WAAO;AAAA,EACT;AAEAM,KAAAA,UAAU,WAAW,GAAK;AAW1B,MAAI,QAAQ,aAAa,UAAU;AACjC,UAAM,UAAUR,KAAAA,KAAK,WAAW,OAAO,cAAc;AACrD,QAAI,CAACE,GAAAA,WAAW,OAAO,GAAG;AACxBS,SAAAA,WAAW,WAAW,OAAO;AAC7B,YAAM,WAAW,QAAQ,SAAS,UAAU,WAAW;AACvDC,SAAAA;AAAAA,QACE;AAAA,QACA;AAAA,qBAAiC,QAAQ,KAAK,OAAO;AAAA;AAAA,MAAA;AAEvDJ,SAAAA,UAAU,WAAW,GAAK;AAG1B,YAAM,QAAQR,KAAAA,KAAK,WAAW,OAAO,QAAQ;AAC7C,UAAIE,GAAAA,WAAW,KAAK,GAAG;AACrB,YAAI;AACFQ,aAAAA,OAAO,OAAO,EAAE,OAAO,KAAA,CAAM;AAAA,QAC/B,QAAQ;AAAA,QAER;AACAE,WAAAA;AAAAA,UACE;AAAA,UACA;AAAA,qBAAiC,QAAQ,KAAK,OAAO;AAAA;AAAA,QAAA;AAEvDJ,WAAAA,UAAU,OAAO,GAAK;AAAA,MACxB;AACA,aAAO,KAAK,8CAA8C;AAAA,QACxD,MAAM,EAAE,UAAU,QAAA;AAAA,MAAQ,CAC3B;AAAA,IACH;AAAA,EACF;AAEA,SAAO,KAAK,6BAA6B,EAAE,MAAM,EAAE,SAAS,gBAAgB,UAAA,GAAa;AACzF,SAAO;AACT;AAKA,eAAsB,sBACpB,YACA,UACA,QACe;AACf,MAAI,SAAS,WAAW,EAAG;AAE3B,SAAO,KAAK,8BAA8B,EAAE,MAAM,EAAE,SAAA,GAAY;AAChE,MAAI;AACFP,oCAAa,YAAY,CAAC,MAAM,OAAO,WAAW,WAAW,GAAG,QAAQ,GAAG;AAAA,MACzE,OAAO;AAAA,MACP,SAAS;AAAA;AAAA,IAAA,CACV;AACD,WAAO,KAAK,wCAAwC;AAAA,EACtD,SAAS,KAAK;AACZ,WAAO,MAAM,qCAAqC,EAAE,MAAM,EAAE,OAAOY,MAAAA,OAAO,GAAG,EAAA,GAAK;AAClF,UAAM;AAAA,EACR;AACF;AAcA,eAAsB,0BACpB,YACA,kBACA,QACe;AACf,MAAI,CAACX,GAAAA,WAAW,gBAAgB,GAAG;AACjC,UAAM,IAAI,MAAM,gCAAgC,gBAAgB,EAAE;AAAA,EACpE;AAEA,QAAM,WAAWY,GAAAA,aAAa,gBAAgB;AAC9C,QAAM,OAAOC,YAAAA,WAAW,QAAQ,EAAE,OAAO,QAAQ,EAAE,OAAO,KAAK,EAAE,MAAM,GAAG,EAAE;AAC5E,QAAM,WAAWC,KAAAA,SAAS,gBAAgB,EAAE,QAAQ,WAAW,EAAE;AAIjE,QAAM,aAAahB,KAAAA,KAAK,YAAY,MAAM,IAAI;AAC9C,QAAM,YAAYA,KAAAA,KAAK,YAAY,yBAAyB;AAC5D,QAAM,aAAaA,KAAAA,KAAK,WAAW,GAAG,QAAQ,IAAI,IAAI,SAAS;AAE/D,MAAIE,GAAAA,WAAW,UAAU,GAAG;AAC1B,WAAO,MAAM,yCAAyC,EAAE,MAAM,EAAE,kBAAkB,KAAA,GAAQ;AAC1F;AAAA,EACF;AAEA,SAAO,KAAK,kCAAkC,EAAE,MAAM,EAAE,kBAAkB,KAAA,GAAQ;AAClF,MAAI;AACFD,uBAAAA;AAAAA,MACE;AAAA,MACA,CAAC,MAAM,OAAO,WAAW,WAAW,+BAA+B,MAAM,gBAAgB;AAAA,MACzF;AAAA,QACE,OAAO;AAAA,QACP,SAAS;AAAA;AAAA,MAAA;AAAA,IACX;AAAA,EAEJ,SAAS,KAAK;AACZ,WAAO,MAAM,yCAAyC;AAAA,MACpD,MAAM,EAAE,kBAAkB,OAAOY,MAAAA,OAAO,GAAG,EAAA;AAAA,IAAE,CAC9C;AACD,UAAM;AAAA,EACR;AAEAV,KAAAA,UAAU,WAAW,EAAE,WAAW,KAAA,CAAM;AACxCS,mBAAc,YAAY,GAAG,gBAAgB;AAAA,GAAK,oBAAI,KAAA,GAAO,YAAA,CAAa;AAAA,CAAI;AAC9E,SAAO,KAAK,iCAAiC,EAAE,MAAM,EAAE,kBAAkB,KAAA,GAAQ;AACnF;ACjPA,MAAM,yBAAyD;AAAA,EAC7D;AAAA,EAAQ;AAAA,EAAS;AAAA,EACjB;AAAA,EAAmB;AAAA,EAAkB;AAAA,EAAoB;AAAA,EACzD;AAAA,EAAU;AAAA,EAAe;AAAA,EAAS;AAAA,EAAQ;AAC5C;AAEA,MAAM,2BAA0E;AAAA,EAC9E,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,cAAc;AAAA,EACd,mBAAmB;AAAA,EACnB,kBAAkB;AAAA,EAClB,oBAAoB;AAAA,EACpB,gBAAgB;AAAA,EAChB,UAAU;AAAA,EACV,eAAe;AAAA,EACf,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,WAAW;AACb;AAiBO,MAAM,0BAAsD;AAAA,EACxD,KAAK;AAAA,EACL,OAAO;AAAA,EACP,qBAAqD,CAAC,GAAG,sBAAsB;AAAA,EAEvE;AAAA,EACA;AAAA,EAEjB,YAAY,UAAkB,WAA0D;AACtF,SAAK,WAAWK,gBAAK,QAAQ,QAAQ;AACrC,SAAK,oCAAoB,IAAA;AAEzB,eAAW,OAAO,wBAAwB;AACxC,YAAM,WAAW,YAAY,GAAG;AAChC,UAAI,UAAU;AACZ,aAAK,cAAc,IAAI,KAAKA,gBAAK,QAAQ,QAAQ,CAAC;AAAA,MACpD,OAAO;AACL,cAAM,SAAS,yBAAyB,GAAG,KAAK;AAEhD,aAAK,cAAc;AAAA,UACjB;AAAA,UACAA,gBAAK,WAAW,MAAM,IAAI,SAASA,gBAAK,KAAK,KAAK,UAAU,MAAM;AAAA,QAAA;AAAA,MAEtE;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,QAAQ,EAAE,UAAU,gBAAsD;AAK9E,UAAM,OAAO,KAAK,cAAc,IAAI,QAAQ,KAAKA,gBAAK,KAAK,KAAK,UAAU,QAAQ;AAClF,WAAOA,gBAAK,KAAK,MAAM,YAAY;AAAA,EACrC;AAAA,EAEA,MAAM,MAAM,EAAE,UAAU,cAAc,QAA0C;AAC9E,UAAM,WAAW,MAAM,KAAK,QAAQ,EAAE,UAAU,cAAc;AAC9D,UAAMC,cAAG,SAAS,MAAMD,gBAAK,QAAQ,QAAQ,GAAG,EAAE,WAAW,MAAM;AAEnE,QAAI,OAAO,SAAS,IAAI,GAAG;AACzB,YAAMC,cAAG,SAAS,UAAU,UAAU,IAAI;AAAA,IAC5C,OAAO;AAEL,YAAM,cAAcA,cAAG,kBAAkB,QAAQ;AACjD,YAAM,IAAI,QAAc,CAAC,SAAS,WAAW;AAC1C,aAA+B,KAAK,WAAW;AAChD,oBAAY,GAAG,UAAU,OAAO;AAChC,oBAAY,GAAG,SAAS,MAAM;AAAA,MAChC,CAAC;AAAA,IACH;AAAA,EACF;AAAA,EAEA,MAAM,KAAK,EAAE,UAAU,gBAAmD;AACxE,WAAOA,cAAG,SAAS,SAAS,MAAM,KAAK,QAAQ,EAAE,UAAU,aAAA,CAAc,CAAC;AAAA,EAC5E;AAAA,EAEA,MAAM,OAAO,EAAE,UAAU,gBAAsD;AAC7E,QAAI;AACF,YAAMA,cAAG,SAAS,OAAO,MAAM,KAAK,QAAQ,EAAE,UAAU,aAAA,CAAc,CAAC;AACvE,aAAO;AAAA,IACT,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AAAA,EAEA,MAAM,KAAK,EAAE,UAAU,UAAwD;AAC7E,UAAM,OAAO,KAAK,cAAc,IAAI,QAAQ;AAC5C,QAAI,CAAC,KAAM,QAAO,CAAA;AAClB,UAAM,MAAM,SAASD,gBAAK,KAAK,MAAM,MAAM,IAAI;AAC/C,QAAI;AACF,YAAM,UAAU,MAAMC,cAAG,SAAS,QAAQ,KAAK,EAAE,eAAe,MAAM;AACtE,aAAO,QAAQ,IAAI,CAAA,MAAM,SAAS,GAAG,MAAM,IAAI,EAAE,IAAI,KAAK,EAAE,IAAK;AAAA,IACnE,QAAQ;AACN,aAAO,CAAA;AAAA,IACT;AAAA,EACF;AAAA,EAEA,MAAM,OAAO,EAAE,UAAU,gBAAmD;AAC1E,UAAM,WAAW,MAAM,KAAK,QAAQ,EAAE,UAAU,cAAc;AAC9D,UAAMA,cAAG,SAAS,GAAG,UAAU,EAAE,OAAO,MAAM;AAAA,EAChD;AAAA,EAEA,MAAM,kBAAkB,EAAE,YAAgE;AACxF,UAAM,OAAO,KAAK,cAAc,IAAI,QAAQ;AAC5C,QAAI,CAAC,KAAM,QAAO;AAKlB,QAAI;AACF,UAAI,SAAS;AACb,aAAO,CAACA,cAAG,WAAW,MAAM,GAAG;AAC7B,cAAM,SAASD,gBAAK,QAAQ,MAAM;AAClC,YAAI,CAAC,UAAU,WAAW,OAAQ,QAAO;AACzC,iBAAS;AAAA,MACX;AACA,YAAM,QAAQ,MAAMC,cAAG,SAAS,OAAO,MAAM;AAC7C,aAAO,MAAM,SAAS,MAAM;AAAA,IAC9B,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AAAA;AAAA;AAAA,EAKA,gBAAgB,UAAuC;AACrD,WAAO,KAAK,cAAc,IAAI,QAAQ,KAAKD,gBAAK,KAAK,KAAK,UAAU,QAAQ;AAAA,EAC9E;AAAA;AAAA,EAGA,cAAsB;AACpB,WAAO,KAAK;AAAA,EACd;AACF;ACnJO,SAAS,cAAc,OAAwB;AACpD,QAAM,YAAY,KAAK,UAAU,OAAO,qBAAqB;AAC7D,SAAOF,YAAAA,WAAW,QAAQ,EAAE,OAAO,aAAa,EAAE,EAAE,OAAO,KAAK;AAClE;AAEA,SAAS,sBAAsB,MAAc,OAAyB;AACpE,MAAI,SAAS,OAAO,UAAU,YAAY,CAAC,MAAM,QAAQ,KAAK,GAAG;AAC/D,UAAM,MAAM;AACZ,UAAM,MAA+B,CAAA;AACrC,eAAW,KAAK,OAAO,KAAK,GAAG,EAAE,KAAA,EAAQ,KAAI,CAAC,IAAI,IAAI,CAAC;AACvD,WAAO;AAAA,EACT;AACA,SAAO;AACT;ACqBO,SAAS,2BAA2B,OAAkC;AAC3E,QAAM,WAAW,CAAC,GAAG,IAAI,IAAI,MAAM,QAAQ,CAAC,EAAE,KAAA;AAC9C,SAAO,cAAc,EAAE,YAAY,MAAM,YAAY,UAAU;AACjE;AAkCO,SAAS,kBACd,SACA,gBACgB;AAChB,QAAM,QAAa,CAAA;AACnB,QAAM,WAAgB,CAAA;AACtB,QAAM,iCAAiB,IAAA;AAEvB,aAAW,SAAS,SAAS;AAC3B,eAAW,IAAI,MAAM,QAAQ;AAC7B,UAAM,WAAW,eAAe,IAAI,MAAM,QAAQ;AAClD,QAAI,aAAa,QAAW;AAC1B,YAAM,KAAK,KAAK;AAAA,IAClB,WAAW,SAAS,gBAAgB,MAAM,aAAa;AACrD,eAAS,KAAK,KAAK;AAAA,IACrB;AAAA,EAEF;AAEA,QAAM,WAAqB,CAAA;AAC3B,aAAW,YAAY,eAAe,QAAQ;AAC5C,QAAI,CAAC,WAAW,IAAI,QAAQ,EAAG,UAAS,KAAK,QAAQ;AAAA,EACvD;AAEA,SAAO,EAAE,OAAO,UAAU,SAAA;AAC5B;AAYO,SAAS,yBAAyB,OAI9B;AACT,MAAI,MAAM,MAAO,QAAO,MAAM;AAC9B,SAAO,MAAM,aAAa,MAAM;AAClC;;;;;;;;;;;;;;;;;;"}
package/dist/node.mjs CHANGED
@@ -6,7 +6,7 @@ import { pipeline } from "node:stream/promises";
6
6
  import { Readable } from "node:stream";
7
7
  import { execFileSync } from "node:child_process";
8
8
  import { createHash } from "node:crypto";
9
- import { e as errMsg } from "./index-QRlzao1I.mjs";
9
+ import { e as errMsg } from "./index-Bpj3ScIH.mjs";
10
10
  import "zod";
11
11
  function getPlatformInfo() {
12
12
  return {
@@ -345,7 +345,7 @@ class FilesystemStorageProvider {
345
345
  if (override) {
346
346
  this.locationPaths.set(loc, path.resolve(override));
347
347
  } else {
348
- const subdir = DEFAULT_LOCATION_SUBDIRS[loc];
348
+ const subdir = DEFAULT_LOCATION_SUBDIRS[loc] ?? loc;
349
349
  this.locationPaths.set(
350
350
  loc,
351
351
  path.isAbsolute(subdir) ? subdir : path.join(this.rootPath, subdir)
@@ -354,8 +354,7 @@ class FilesystemStorageProvider {
354
354
  }
355
355
  }
356
356
  async resolve({ location, relativePath }) {
357
- const base = this.locationPaths.get(location);
358
- if (!base) throw new Error(`Unknown storage location: ${location}`);
357
+ const base = this.locationPaths.get(location) ?? path.join(this.rootPath, location);
359
358
  return path.join(base, relativePath);
360
359
  }
361
360
  async write({ location, relativePath, data }) {
@@ -414,21 +413,63 @@ class FilesystemStorageProvider {
414
413
  return null;
415
414
  }
416
415
  }
417
- /** Get the resolved path for a location type */
416
+ /** Get the resolved path for a location type (addon-declared ids fall back
417
+ * to `<rootPath>/<id>`). */
418
418
  getLocationPath(location) {
419
- const p = this.locationPaths.get(location);
420
- if (!p) throw new Error(`Unknown storage location: ${location}`);
421
- return p;
419
+ return this.locationPaths.get(location) ?? path.join(this.rootPath, location);
422
420
  }
423
421
  /** Get the root path */
424
422
  getRootPath() {
425
423
  return this.rootPath;
426
424
  }
427
425
  }
426
+ function canonicalHash(value) {
427
+ const canonical = JSON.stringify(value, replaceWithSortedKeys);
428
+ return createHash("sha256").update(canonical ?? "").digest("hex");
429
+ }
430
+ function replaceWithSortedKeys(_key, value) {
431
+ if (value && typeof value === "object" && !Array.isArray(value)) {
432
+ const obj = value;
433
+ const out = {};
434
+ for (const k of Object.keys(obj).sort()) out[k] = obj[k];
435
+ return out;
436
+ }
437
+ return value;
438
+ }
439
+ function canonicalDeviceFingerprint(shape) {
440
+ const features = [...new Set(shape.features)].sort();
441
+ return canonicalHash({ deviceType: shape.deviceType, features });
442
+ }
443
+ function diffExportTargets(desired, lastAdvertised) {
444
+ const toAdd = [];
445
+ const toUpdate = [];
446
+ const desiredIds = /* @__PURE__ */ new Set();
447
+ for (const entry of desired) {
448
+ desiredIds.add(entry.deviceId);
449
+ const previous = lastAdvertised.get(entry.deviceId);
450
+ if (previous === void 0) {
451
+ toAdd.push(entry);
452
+ } else if (previous.fingerprint !== entry.fingerprint) {
453
+ toUpdate.push(entry);
454
+ }
455
+ }
456
+ const toRemove = [];
457
+ for (const deviceId of lastAdvertised.keys()) {
458
+ if (!desiredIds.has(deviceId)) toRemove.push(deviceId);
459
+ }
460
+ return { toAdd, toUpdate, toRemove };
461
+ }
462
+ function resolveExportFingerprint(input) {
463
+ if (input.ready) return input.fresh;
464
+ return input.persisted ?? input.fresh;
465
+ }
428
466
  export {
429
467
  FilesystemStorageProvider,
430
468
  PYTHON_VERSION,
431
469
  buildBinaryPath,
470
+ canonicalDeviceFingerprint,
471
+ canonicalHash,
472
+ diffExportTargets,
432
473
  downloadBinary,
433
474
  ensureBinary,
434
475
  ensureFfmpeg,
@@ -438,6 +479,7 @@ export {
438
479
  getPlatformInfo,
439
480
  getPythonDownloadUrl,
440
481
  installPythonPackages,
441
- installPythonRequirements
482
+ installPythonRequirements,
483
+ resolveExportFingerprint
442
484
  };
443
485
  //# sourceMappingURL=node.mjs.map
package/dist/node.mjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"node.mjs","sources":["../src/deps/binary-downloader.ts","../src/deps/ffmpeg-downloader.ts","../src/deps/python-downloader.ts","../src/storage/filesystem-storage-provider.ts"],"sourcesContent":["import { existsSync, mkdirSync, chmodSync, createWriteStream, unlinkSync } from 'node:fs'\nimport { join } from 'node:path'\nimport { pipeline } from 'node:stream/promises'\nimport { Readable } from 'node:stream'\nimport { execFileSync } from 'node:child_process'\nimport type { IScopedLogger } from '../index.js'\n\nexport interface PlatformInfo {\n readonly platform: NodeJS.Platform\n readonly arch: NodeJS.Architecture\n}\n\nexport function getPlatformInfo(): PlatformInfo {\n return {\n platform: process.platform,\n arch: process.arch,\n }\n}\n\nexport function buildBinaryPath(dataDir: string, name: string, platform?: string): string {\n const ext = (platform ?? process.platform) === 'win32' ? '.exe' : ''\n return join(dataDir, 'deps', `${name}${ext}`)\n}\n\n/** Check if a binary exists in PATH */\nexport function findInPath(name: string): string | null {\n try {\n execFileSync(name, ['--version'], { stdio: 'pipe', timeout: 5000 })\n return name\n } catch {\n return null\n }\n}\n\nexport interface DownloadOptions {\n readonly name: string\n readonly url: string\n readonly targetDir: string\n readonly targetName: string\n readonly logger: IScopedLogger\n readonly isArchive?: boolean\n readonly archiveFormat?: 'zip' | 'tar.gz' | 'tar.xz'\n /** Relative path within archive to the binary (e.g., 'ffmpeg-6.1/bin/ffmpeg') */\n readonly archiveInnerPath?: string\n}\n\n/**\n * Download a binary to the target directory.\n * Handles archives (zip, tar.gz, tar.xz) and raw binaries.\n */\nexport async function downloadBinary(opts: DownloadOptions): Promise<string> {\n const { name, url, targetDir, targetName, logger, isArchive, archiveFormat, archiveInnerPath } = opts\n const targetPath = join(targetDir, targetName)\n\n if (existsSync(targetPath)) {\n logger.debug('Binary already exists', { meta: { name, targetPath } })\n return targetPath\n }\n\n mkdirSync(targetDir, { recursive: true })\n\n logger.info('Downloading binary', { meta: { name, url } })\n const response = await fetch(url, { redirect: 'follow' })\n if (!response.ok || !response.body) {\n throw new Error(`Failed to download ${name}: ${response.status} ${response.statusText}`)\n }\n\n if (isArchive) {\n const ext = archiveFormat ?? 'tar.gz'\n const tmpArchive = join(targetDir, `${name}-download.${ext}`)\n const nodeStream = Readable.fromWeb(response.body as ReadableStream)\n await pipeline(nodeStream, createWriteStream(tmpArchive))\n\n // Extract\n const tmpExtractDir = join(targetDir, `${name}-extract`)\n mkdirSync(tmpExtractDir, { recursive: true })\n\n if (ext === 'zip') {\n try {\n execFileSync('unzip', ['-o', '-q', tmpArchive, '-d', tmpExtractDir], { stdio: 'pipe' })\n } catch {\n execFileSync('tar', ['-xf', tmpArchive, '-C', tmpExtractDir], { stdio: 'pipe' })\n }\n } else if (ext === 'tar.xz') {\n execFileSync('tar', ['-xJf', tmpArchive, '-C', tmpExtractDir], { stdio: 'pipe' })\n } else {\n execFileSync('tar', ['-xzf', tmpArchive, '-C', tmpExtractDir], { stdio: 'pipe' })\n }\n\n // Copy binary from archive\n if (archiveInnerPath) {\n const { copyFileSync } = await import('node:fs')\n const sourcePath = join(tmpExtractDir, archiveInnerPath)\n if (!existsSync(sourcePath)) {\n throw new Error(`Binary not found in archive at ${archiveInnerPath}`)\n }\n copyFileSync(sourcePath, targetPath)\n }\n\n // Cleanup\n unlinkSync(tmpArchive)\n const { rmSync } = await import('node:fs')\n rmSync(tmpExtractDir, { recursive: true, force: true })\n } else {\n // Direct binary download\n const nodeStream = Readable.fromWeb(response.body as ReadableStream)\n await pipeline(nodeStream, createWriteStream(targetPath))\n }\n\n chmodSync(targetPath, 0o755)\n logger.info('Binary downloaded', { meta: { name, targetPath } })\n return targetPath\n}\n\n/**\n * Ensure a binary is available. Checks:\n * 1. Target path (already downloaded)\n * 2. System PATH\n * 3. Download from URL\n */\nexport async function ensureBinary(opts: {\n readonly name: string\n readonly targetDir: string\n readonly downloadUrl: string\n readonly logger: IScopedLogger\n readonly isArchive?: boolean\n readonly archiveFormat?: 'zip' | 'tar.gz' | 'tar.xz'\n readonly archiveInnerPath?: string\n}): Promise<string> {\n const ext = process.platform === 'win32' ? '.exe' : ''\n const targetName = `${opts.name}${ext}`\n const targetPath = join(opts.targetDir, targetName)\n\n // 1. Already downloaded\n if (existsSync(targetPath)) {\n opts.logger.debug('Binary found at target path', { meta: { name: opts.name, targetPath } })\n return targetPath\n }\n\n // 2. System PATH\n const inPath = findInPath(opts.name)\n if (inPath) {\n opts.logger.info('Binary found in system PATH', { meta: { name: opts.name } })\n return inPath\n }\n\n // 3. Download\n return downloadBinary({\n name: opts.name,\n url: opts.downloadUrl,\n targetDir: opts.targetDir,\n targetName,\n logger: opts.logger,\n isArchive: opts.isArchive,\n archiveFormat: opts.archiveFormat,\n archiveInnerPath: opts.archiveInnerPath,\n })\n}\n","/**\n * Download ffmpeg static build for the current platform.\n *\n * Sources:\n * - Linux: https://johnvansickle.com/ffmpeg/ (static builds)\n * - macOS: https://evermeet.cx/ffmpeg/ or homebrew\n *\n * Using BtbN's GitHub releases as they cover both platforms:\n * https://github.com/BtbN/FFmpeg-Builds/releases\n */\nimport { join } from 'node:path'\nimport type { IScopedLogger } from '../index.js'\nimport { ensureBinary } from './binary-downloader.js'\n\nconst FFMPEG_VERSION = '7.1'\n\nexport function getFfmpegDownloadUrl(platform: string, arch: string): string {\n switch (platform) {\n case 'linux': {\n const archMap: Record<string, string> = { x64: 'amd64', arm64: 'arm64' }\n const a = archMap[arch]\n if (!a) throw new Error(`Unsupported Linux architecture: ${arch}`)\n // John Van Sickle static builds — well-tested, widely used\n return `https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-${a}-static.tar.xz`\n }\n case 'darwin': {\n const archMap: Record<string, string> = { arm64: 'arm64', x64: 'amd64' }\n const a = archMap[arch]\n if (!a) throw new Error(`Unsupported macOS architecture: ${arch}`)\n // macOS static builds from osxexperts (maintained, universal builds)\n return `https://www.osxexperts.net/ffmpeg${FFMPEG_VERSION.replace('.', '')}arm.zip`\n }\n default:\n throw new Error(`Unsupported platform: ${platform}`)\n }\n}\n\nexport function getFfmpegArchiveInfo(platform: string): {\n isArchive: boolean\n archiveFormat: 'zip' | 'tar.gz' | 'tar.xz'\n archiveInnerPath: string\n} {\n switch (platform) {\n case 'linux':\n return {\n isArchive: true,\n archiveFormat: 'tar.xz',\n archiveInnerPath: `ffmpeg-${FFMPEG_VERSION}-amd64-static/ffmpeg`,\n }\n case 'darwin':\n return {\n isArchive: true,\n archiveFormat: 'zip',\n archiveInnerPath: 'ffmpeg',\n }\n default:\n throw new Error(`Unsupported platform: ${platform}`)\n }\n}\n\n/**\n * Ensure ffmpeg binary is available.\n * Checks: deps dir → system PATH → download.\n */\nexport async function ensureFfmpeg(dataDir: string, logger: IScopedLogger): Promise<string> {\n const depsDir = join(dataDir, 'deps')\n const platform = process.platform\n const arch = process.arch\n\n const archiveInfo = getFfmpegArchiveInfo(platform)\n\n return ensureBinary({\n name: 'ffmpeg',\n targetDir: depsDir,\n downloadUrl: getFfmpegDownloadUrl(platform, arch),\n logger,\n ...archiveInfo,\n })\n}\n","/**\n * Download portable Python (headless) from bjia56/portable-python.\n *\n * Source: https://github.com/bjia56/portable-python\n *\n * Advantages over python-build-standalone:\n * - macOS universal2 binary (arm64+x86_64 in one)\n * - Lighter (~24-28MB headless zip)\n * - glibc 2.17 minimum (better compat)\n * - Headless variant excludes tkinter/UI (ideal for ML inference)\n *\n * The zip's top-level dir is platform-specific (e.g.\n * `python-headless-3.12.12-darwin-universal2/`); after extraction\n * we rename it to a stable `python/` so consumers can rely on\n * `<dataDir>/deps/python/bin/python3`.\n */\nimport { join, basename } from 'node:path'\nimport { existsSync, mkdirSync, chmodSync, readFileSync, writeFileSync, readdirSync, renameSync, rmSync } from 'node:fs'\nimport { execFileSync } from 'node:child_process'\nimport { createHash } from 'node:crypto'\nimport { pipeline } from 'node:stream/promises'\nimport { Readable } from 'node:stream'\nimport { createWriteStream } from 'node:fs'\nimport type { IScopedLogger } from '../index.js'\nimport { errMsg } from '../index.js'\n\nexport const PYTHON_VERSION = '3.12.12'\nconst PP_BASE = `https://github.com/bjia56/portable-python/releases/download/cpython-v${PYTHON_VERSION}-build.0`\n\nexport function getPythonDownloadUrl(platform: string, arch: string): string {\n switch (platform) {\n case 'linux': {\n const archMap: Record<string, string> = { x64: 'x86_64', arm64: 'aarch64' }\n const a = archMap[arch]\n if (!a) throw new Error(`Unsupported Linux architecture for Python: ${arch}`)\n return `${PP_BASE}/python-headless-${PYTHON_VERSION}-linux-${a}.zip`\n }\n case 'darwin': {\n // universal2 covers both arm64 and x86_64\n return `${PP_BASE}/python-headless-${PYTHON_VERSION}-darwin-universal2.zip`\n }\n default:\n throw new Error(`Unsupported platform for portable Python: ${platform}`)\n }\n}\n\n/**\n * Ensure the embedded portable Python is available.\n *\n * ALWAYS returns the path to the embedded interpreter under\n * `<dataDir>/deps/python/bin/python3` — downloads it on first call\n * and reuses it thereafter. The system PATH is intentionally NOT\n * consulted: addons rely on `installPythonRequirements()` to manage\n * deps inside the embedded site-packages, and a system interpreter\n * (different version, missing modules) silently breaks that contract.\n *\n * Returns null only if the download/extract step fails.\n */\nexport async function ensurePython(dataDir: string, logger: IScopedLogger): Promise<string | null> {\n const pythonDir = join(dataDir, 'deps', 'python')\n const pythonBin = join(pythonDir, 'bin', 'python3')\n\n // 1. Already downloaded\n if (existsSync(pythonBin)) {\n logger.debug('Portable Python found', { meta: { pythonBin } })\n return pythonBin\n }\n\n // 2. Download portable python (no system PATH fallback — by design)\n logger.info('Downloading portable Python (headless)', { meta: { version: PYTHON_VERSION } })\n\n const depsDir = join(dataDir, 'deps')\n mkdirSync(depsDir, { recursive: true })\n\n const url = getPythonDownloadUrl(process.platform, process.arch)\n const tmpArchive = join(depsDir, 'python-download.zip')\n\n logger.info('Python download source', { meta: { url } })\n\n const response = await fetch(url, { redirect: 'follow' })\n if (!response.ok || !response.body) {\n logger.warn('Failed to download Python — ML detection will not be available', { meta: { status: response.status } })\n return null\n }\n\n const nodeStream = Readable.fromWeb(response.body as ReadableStream)\n await pipeline(nodeStream, createWriteStream(tmpArchive))\n\n // Extract — zip's top-level dir is e.g. `python-headless-3.12.12-darwin-universal2/`\n // (varies per platform/arch).\n try {\n execFileSync('unzip', ['-o', '-q', tmpArchive, '-d', depsDir], { stdio: 'pipe' })\n } catch {\n // Fallback: some minimal Linux installs don't have unzip\n execFileSync('python3', ['-m', 'zipfile', '-e', tmpArchive, depsDir], { stdio: 'pipe' })\n }\n\n // Cleanup the archive\n const { unlinkSync } = await import('node:fs')\n unlinkSync(tmpArchive)\n\n // Locate the extracted directory and normalize it to `<depsDir>/python/`.\n // The zip's top-level name varies by platform (e.g.\n // `python-headless-3.12.12-darwin-universal2/`), so we look for any\n // `python-*` subdir that contains the expected `bin/python3` and rename it.\n if (!existsSync(pythonBin)) {\n const candidates = readdirSync(depsDir).filter((name) =>\n name.startsWith('python-') &&\n existsSync(join(depsDir, name, 'bin', 'python3')),\n )\n if (candidates.length === 0) {\n logger.warn('Python extraction succeeded but no python3 binary found in any subdir', {\n meta: { depsDir },\n })\n return null\n }\n const extractedRoot = join(depsDir, candidates[0]!)\n // Drop any stale `python/` (e.g. from a previous failed install)\n // before renaming so the move always succeeds.\n if (existsSync(pythonDir)) {\n rmSync(pythonDir, { recursive: true, force: true })\n }\n renameSync(extractedRoot, pythonDir)\n }\n\n if (!existsSync(pythonBin)) {\n logger.warn('Python extraction succeeded but binary not found at expected path', {\n meta: { pythonBin },\n })\n return null\n }\n\n chmodSync(pythonBin, 0o755)\n\n // On macOS the bjia56 build is universal2 — the host arch is decided\n // at spawn time based on the parent process. That makes pip wheels\n // and runtime spawns silently disagree when the server is launched\n // from a Rosetta shell once and a native shell next time (e.g. pip\n // installs `numpy x86_64`, then the next boot under arm64 fails with\n // `incompatible architecture`). Lock the slice deterministically by\n // wrapping `python3` in a shell stub that always invokes the matching\n // host arch via `/usr/bin/arch`. Idempotent: detected via the renamed\n // `.real` binary.\n if (process.platform === 'darwin') {\n const realBin = join(pythonDir, 'bin', 'python3.real')\n if (!existsSync(realBin)) {\n renameSync(pythonBin, realBin)\n const archFlag = process.arch === 'arm64' ? '-arm64' : '-x86_64'\n writeFileSync(\n pythonBin,\n `#!/bin/sh\\nexec /usr/bin/arch ${archFlag} \"${realBin}\" \"$@\"\\n`,\n )\n chmodSync(pythonBin, 0o755)\n // Mirror the same wrapping for the `python` symlink so consumers\n // that use the un-versioned name also lock the arch.\n const pyAlt = join(pythonDir, 'bin', 'python')\n if (existsSync(pyAlt)) {\n try {\n rmSync(pyAlt, { force: true })\n } catch {\n // ignore\n }\n writeFileSync(\n pyAlt,\n `#!/bin/sh\\nexec /usr/bin/arch ${archFlag} \"${realBin}\" \"$@\"\\n`,\n )\n chmodSync(pyAlt, 0o755)\n }\n logger.info('Locked python3 to host arch via shell stub', {\n meta: { archFlag, realBin },\n })\n }\n }\n\n logger.info('Portable Python installed', { meta: { version: PYTHON_VERSION, pythonBin } })\n return pythonBin\n}\n\n/**\n * Install Python packages into the portable Python environment.\n */\nexport async function installPythonPackages(\n pythonPath: string,\n packages: readonly string[],\n logger: IScopedLogger,\n): Promise<void> {\n if (packages.length === 0) return\n\n logger.info('Installing Python packages', { meta: { packages } })\n try {\n execFileSync(pythonPath, ['-m', 'pip', 'install', '--quiet', ...packages], {\n stdio: 'pipe',\n timeout: 300_000, // 5 minutes for large packages like torch\n })\n logger.info('Python packages installed successfully')\n } catch (err) {\n logger.error('Failed to install Python packages', { meta: { error: errMsg(err) } })\n throw err\n }\n}\n\n/**\n * Install a pip requirements file into the embedded Python.\n *\n * Idempotent: keyed on (requirements file basename + sha256 of its\n * contents). The marker is written under\n * `<python-install-dir>/.requirements-installed/` so it lives and\n * dies with the python install — re-downloading python wipes both\n * the site-packages and the markers, forcing a fresh re-install.\n *\n * The marker is updated only after `pip install` succeeds. A failed\n * install never writes a marker, so the next call retries.\n */\nexport async function installPythonRequirements(\n pythonPath: string,\n requirementsFile: string,\n logger: IScopedLogger,\n): Promise<void> {\n if (!existsSync(requirementsFile)) {\n throw new Error(`Requirements file not found: ${requirementsFile}`)\n }\n\n const contents = readFileSync(requirementsFile)\n const hash = createHash('sha256').update(contents).digest('hex').slice(0, 16)\n const fileBase = basename(requirementsFile).replace(/\\.txt$/i, '')\n\n // Markers live under the python install dir → wiped together when\n // the embedded interpreter is re-downloaded.\n const pythonRoot = join(pythonPath, '..', '..') // <root>/bin/python3 → <root>\n const markerDir = join(pythonRoot, '.requirements-installed')\n const markerPath = join(markerDir, `${fileBase}-${hash}.marker`)\n\n if (existsSync(markerPath)) {\n logger.debug('Python requirements already installed', { meta: { requirementsFile, hash } })\n return\n }\n\n logger.info('Installing Python requirements', { meta: { requirementsFile, hash } })\n try {\n execFileSync(\n pythonPath,\n ['-m', 'pip', 'install', '--quiet', '--disable-pip-version-check', '-r', requirementsFile],\n {\n stdio: 'pipe',\n timeout: 600_000, // 10 minutes — coremltools + tensorflow can be slow\n },\n )\n } catch (err) {\n logger.error('Failed to install Python requirements', {\n meta: { requirementsFile, error: errMsg(err) },\n })\n throw err\n }\n\n mkdirSync(markerDir, { recursive: true })\n writeFileSync(markerPath, `${requirementsFile}\\n${new Date().toISOString()}\\n`)\n logger.info('Python requirements installed', { meta: { requirementsFile, hash } })\n}\n","import * as fs from 'node:fs'\nimport * as path from 'node:path'\nimport type {\n IStorageProvider,\n StorageLocationType,\n StorageResolveInput,\n StorageWriteInput,\n StorageReadInput,\n StorageExistsInput,\n StorageListInput,\n StorageDeleteInput,\n StorageAvailableSpaceInput,\n} from '../index.js'\n\n// Inline constants to avoid runtime dependency on @camstack/types\n// (types is external in tsup bundle, may not be resolvable from data/addons/)\nconst STORAGE_LOCATION_TYPES: readonly StorageLocationType[] = [\n 'data', 'media', 'recordings',\n 'recordings-high', 'recordings-low', 'recordings-clips', 'event-images',\n 'models', 'addons-data', 'cache', 'logs', 'backups',\n]\n\nconst DEFAULT_LOCATION_SUBDIRS: Readonly<Record<StorageLocationType, string>> = {\n 'data': 'db',\n 'media': 'media',\n 'recordings': 'recordings',\n 'recordings-high': 'recordings-high',\n 'recordings-low': 'recordings-low',\n 'recordings-clips': 'recordings-clips',\n 'event-images': 'event-images',\n 'models': 'models',\n 'addons-data': 'addons-data',\n 'cache': '/tmp/camstack-cache',\n 'logs': 'logs',\n 'backups': 'backups',\n}\n\n/**\n * Filesystem storage provider — serves all location types from a local directory tree.\n *\n * Default layout:\n * {rootPath}/recordings-high/\n * {rootPath}/recordings-low/\n * {rootPath}/recordings-clips/\n * {rootPath}/event-images/\n * {rootPath}/models/\n * {rootPath}/addons-data/\n * {rootPath}/logs/\n * /tmp/camstack-cache/ (cache is always local)\n *\n * Individual location paths can be overridden.\n */\nexport class FilesystemStorageProvider implements IStorageProvider {\n readonly id = 'local'\n readonly name = 'Local Filesystem'\n readonly supportedLocations: readonly StorageLocationType[] = [...STORAGE_LOCATION_TYPES]\n\n private readonly rootPath: string\n private readonly locationPaths: Map<StorageLocationType, string>\n\n constructor(rootPath: string, overrides?: Partial<Record<StorageLocationType, string>>) {\n this.rootPath = path.resolve(rootPath)\n this.locationPaths = new Map()\n\n for (const loc of STORAGE_LOCATION_TYPES) {\n const override = overrides?.[loc]\n if (override) {\n this.locationPaths.set(loc, path.resolve(override))\n } else {\n const subdir = DEFAULT_LOCATION_SUBDIRS[loc]\n // Absolute paths (like /tmp/camstack-cache) are used as-is\n this.locationPaths.set(\n loc,\n path.isAbsolute(subdir) ? subdir : path.join(this.rootPath, subdir),\n )\n }\n }\n }\n\n async resolve({ location, relativePath }: StorageResolveInput): Promise<string> {\n const base = this.locationPaths.get(location)\n if (!base) throw new Error(`Unknown storage location: ${location}`)\n return path.join(base, relativePath)\n }\n\n async write({ location, relativePath, data }: StorageWriteInput): Promise<void> {\n const filePath = await this.resolve({ location, relativePath })\n await fs.promises.mkdir(path.dirname(filePath), { recursive: true })\n\n if (Buffer.isBuffer(data)) {\n await fs.promises.writeFile(filePath, data)\n } else {\n // Stream\n const writeStream = fs.createWriteStream(filePath)\n await new Promise<void>((resolve, reject) => {\n (data as NodeJS.ReadableStream).pipe(writeStream)\n writeStream.on('finish', resolve)\n writeStream.on('error', reject)\n })\n }\n }\n\n async read({ location, relativePath }: StorageReadInput): Promise<Buffer> {\n return fs.promises.readFile(await this.resolve({ location, relativePath }))\n }\n\n async exists({ location, relativePath }: StorageExistsInput): Promise<boolean> {\n try {\n await fs.promises.access(await this.resolve({ location, relativePath }))\n return true\n } catch {\n return false\n }\n }\n\n async list({ location, prefix }: StorageListInput): Promise<readonly string[]> {\n const base = this.locationPaths.get(location)\n if (!base) return []\n const dir = prefix ? path.join(base, prefix) : base\n try {\n const entries = await fs.promises.readdir(dir, { withFileTypes: true })\n return entries.map(e => (prefix ? `${prefix}/${e.name}` : e.name))\n } catch {\n return []\n }\n }\n\n async delete({ location, relativePath }: StorageDeleteInput): Promise<void> {\n const filePath = await this.resolve({ location, relativePath })\n await fs.promises.rm(filePath, { force: true })\n }\n\n async getAvailableSpace({ location }: StorageAvailableSpaceInput): Promise<number | null> {\n const base = this.locationPaths.get(location)\n if (!base) return null\n // If the directory doesn't exist yet (lazy creation), walk up to\n // the nearest existing ancestor so statfs can still report free\n // space on the mount point. This keeps the storage dashboard\n // working on a fresh install where no file has been written yet.\n try {\n let target = base\n while (!fs.existsSync(target)) {\n const parent = path.dirname(target)\n if (!parent || parent === target) return null\n target = parent\n }\n const stats = await fs.promises.statfs(target)\n return stats.bavail * stats.bsize\n } catch {\n return null\n }\n }\n\n\n /** Get the resolved path for a location type */\n getLocationPath(location: StorageLocationType): string {\n const p = this.locationPaths.get(location)\n if (!p) throw new Error(`Unknown storage location: ${location}`)\n return p\n }\n\n /** Get the root path */\n getRootPath(): string {\n return this.rootPath\n }\n}\n\nexport default FilesystemStorageProvider\n"],"names":["rmSync","unlinkSync"],"mappings":"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"}
1
+ {"version":3,"file":"node.mjs","sources":["../src/deps/binary-downloader.ts","../src/deps/ffmpeg-downloader.ts","../src/deps/python-downloader.ts","../src/storage/filesystem-storage-provider.ts","../src/utils/canonical-hash.ts","../src/utils/export-reconciler.ts"],"sourcesContent":["import { existsSync, mkdirSync, chmodSync, createWriteStream, unlinkSync } from 'node:fs'\nimport { join } from 'node:path'\nimport { pipeline } from 'node:stream/promises'\nimport { Readable } from 'node:stream'\nimport { execFileSync } from 'node:child_process'\nimport type { IScopedLogger } from '../index.js'\n\nexport interface PlatformInfo {\n readonly platform: NodeJS.Platform\n readonly arch: NodeJS.Architecture\n}\n\nexport function getPlatformInfo(): PlatformInfo {\n return {\n platform: process.platform,\n arch: process.arch,\n }\n}\n\nexport function buildBinaryPath(dataDir: string, name: string, platform?: string): string {\n const ext = (platform ?? process.platform) === 'win32' ? '.exe' : ''\n return join(dataDir, 'deps', `${name}${ext}`)\n}\n\n/** Check if a binary exists in PATH */\nexport function findInPath(name: string): string | null {\n try {\n execFileSync(name, ['--version'], { stdio: 'pipe', timeout: 5000 })\n return name\n } catch {\n return null\n }\n}\n\nexport interface DownloadOptions {\n readonly name: string\n readonly url: string\n readonly targetDir: string\n readonly targetName: string\n readonly logger: IScopedLogger\n readonly isArchive?: boolean\n readonly archiveFormat?: 'zip' | 'tar.gz' | 'tar.xz'\n /** Relative path within archive to the binary (e.g., 'ffmpeg-6.1/bin/ffmpeg') */\n readonly archiveInnerPath?: string\n}\n\n/**\n * Download a binary to the target directory.\n * Handles archives (zip, tar.gz, tar.xz) and raw binaries.\n */\nexport async function downloadBinary(opts: DownloadOptions): Promise<string> {\n const { name, url, targetDir, targetName, logger, isArchive, archiveFormat, archiveInnerPath } = opts\n const targetPath = join(targetDir, targetName)\n\n if (existsSync(targetPath)) {\n logger.debug('Binary already exists', { meta: { name, targetPath } })\n return targetPath\n }\n\n mkdirSync(targetDir, { recursive: true })\n\n logger.info('Downloading binary', { meta: { name, url } })\n const response = await fetch(url, { redirect: 'follow' })\n if (!response.ok || !response.body) {\n throw new Error(`Failed to download ${name}: ${response.status} ${response.statusText}`)\n }\n\n if (isArchive) {\n const ext = archiveFormat ?? 'tar.gz'\n const tmpArchive = join(targetDir, `${name}-download.${ext}`)\n const nodeStream = Readable.fromWeb(response.body as ReadableStream)\n await pipeline(nodeStream, createWriteStream(tmpArchive))\n\n // Extract\n const tmpExtractDir = join(targetDir, `${name}-extract`)\n mkdirSync(tmpExtractDir, { recursive: true })\n\n if (ext === 'zip') {\n try {\n execFileSync('unzip', ['-o', '-q', tmpArchive, '-d', tmpExtractDir], { stdio: 'pipe' })\n } catch {\n execFileSync('tar', ['-xf', tmpArchive, '-C', tmpExtractDir], { stdio: 'pipe' })\n }\n } else if (ext === 'tar.xz') {\n execFileSync('tar', ['-xJf', tmpArchive, '-C', tmpExtractDir], { stdio: 'pipe' })\n } else {\n execFileSync('tar', ['-xzf', tmpArchive, '-C', tmpExtractDir], { stdio: 'pipe' })\n }\n\n // Copy binary from archive\n if (archiveInnerPath) {\n const { copyFileSync } = await import('node:fs')\n const sourcePath = join(tmpExtractDir, archiveInnerPath)\n if (!existsSync(sourcePath)) {\n throw new Error(`Binary not found in archive at ${archiveInnerPath}`)\n }\n copyFileSync(sourcePath, targetPath)\n }\n\n // Cleanup\n unlinkSync(tmpArchive)\n const { rmSync } = await import('node:fs')\n rmSync(tmpExtractDir, { recursive: true, force: true })\n } else {\n // Direct binary download\n const nodeStream = Readable.fromWeb(response.body as ReadableStream)\n await pipeline(nodeStream, createWriteStream(targetPath))\n }\n\n chmodSync(targetPath, 0o755)\n logger.info('Binary downloaded', { meta: { name, targetPath } })\n return targetPath\n}\n\n/**\n * Ensure a binary is available. Checks:\n * 1. Target path (already downloaded)\n * 2. System PATH\n * 3. Download from URL\n */\nexport async function ensureBinary(opts: {\n readonly name: string\n readonly targetDir: string\n readonly downloadUrl: string\n readonly logger: IScopedLogger\n readonly isArchive?: boolean\n readonly archiveFormat?: 'zip' | 'tar.gz' | 'tar.xz'\n readonly archiveInnerPath?: string\n}): Promise<string> {\n const ext = process.platform === 'win32' ? '.exe' : ''\n const targetName = `${opts.name}${ext}`\n const targetPath = join(opts.targetDir, targetName)\n\n // 1. Already downloaded\n if (existsSync(targetPath)) {\n opts.logger.debug('Binary found at target path', { meta: { name: opts.name, targetPath } })\n return targetPath\n }\n\n // 2. System PATH\n const inPath = findInPath(opts.name)\n if (inPath) {\n opts.logger.info('Binary found in system PATH', { meta: { name: opts.name } })\n return inPath\n }\n\n // 3. Download\n return downloadBinary({\n name: opts.name,\n url: opts.downloadUrl,\n targetDir: opts.targetDir,\n targetName,\n logger: opts.logger,\n isArchive: opts.isArchive,\n archiveFormat: opts.archiveFormat,\n archiveInnerPath: opts.archiveInnerPath,\n })\n}\n","/**\n * Download ffmpeg static build for the current platform.\n *\n * Sources:\n * - Linux: https://johnvansickle.com/ffmpeg/ (static builds)\n * - macOS: https://evermeet.cx/ffmpeg/ or homebrew\n *\n * Using BtbN's GitHub releases as they cover both platforms:\n * https://github.com/BtbN/FFmpeg-Builds/releases\n */\nimport { join } from 'node:path'\nimport type { IScopedLogger } from '../index.js'\nimport { ensureBinary } from './binary-downloader.js'\n\nconst FFMPEG_VERSION = '7.1'\n\nexport function getFfmpegDownloadUrl(platform: string, arch: string): string {\n switch (platform) {\n case 'linux': {\n const archMap: Record<string, string> = { x64: 'amd64', arm64: 'arm64' }\n const a = archMap[arch]\n if (!a) throw new Error(`Unsupported Linux architecture: ${arch}`)\n // John Van Sickle static builds — well-tested, widely used\n return `https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-${a}-static.tar.xz`\n }\n case 'darwin': {\n const archMap: Record<string, string> = { arm64: 'arm64', x64: 'amd64' }\n const a = archMap[arch]\n if (!a) throw new Error(`Unsupported macOS architecture: ${arch}`)\n // macOS static builds from osxexperts (maintained, universal builds)\n return `https://www.osxexperts.net/ffmpeg${FFMPEG_VERSION.replace('.', '')}arm.zip`\n }\n default:\n throw new Error(`Unsupported platform: ${platform}`)\n }\n}\n\nexport function getFfmpegArchiveInfo(platform: string): {\n isArchive: boolean\n archiveFormat: 'zip' | 'tar.gz' | 'tar.xz'\n archiveInnerPath: string\n} {\n switch (platform) {\n case 'linux':\n return {\n isArchive: true,\n archiveFormat: 'tar.xz',\n archiveInnerPath: `ffmpeg-${FFMPEG_VERSION}-amd64-static/ffmpeg`,\n }\n case 'darwin':\n return {\n isArchive: true,\n archiveFormat: 'zip',\n archiveInnerPath: 'ffmpeg',\n }\n default:\n throw new Error(`Unsupported platform: ${platform}`)\n }\n}\n\n/**\n * Ensure ffmpeg binary is available.\n * Checks: deps dir → system PATH → download.\n */\nexport async function ensureFfmpeg(dataDir: string, logger: IScopedLogger): Promise<string> {\n const depsDir = join(dataDir, 'deps')\n const platform = process.platform\n const arch = process.arch\n\n const archiveInfo = getFfmpegArchiveInfo(platform)\n\n return ensureBinary({\n name: 'ffmpeg',\n targetDir: depsDir,\n downloadUrl: getFfmpegDownloadUrl(platform, arch),\n logger,\n ...archiveInfo,\n })\n}\n","/**\n * Download portable Python (headless) from bjia56/portable-python.\n *\n * Source: https://github.com/bjia56/portable-python\n *\n * Advantages over python-build-standalone:\n * - macOS universal2 binary (arm64+x86_64 in one)\n * - Lighter (~24-28MB headless zip)\n * - glibc 2.17 minimum (better compat)\n * - Headless variant excludes tkinter/UI (ideal for ML inference)\n *\n * The zip's top-level dir is platform-specific (e.g.\n * `python-headless-3.12.12-darwin-universal2/`); after extraction\n * we rename it to a stable `python/` so consumers can rely on\n * `<dataDir>/deps/python/bin/python3`.\n */\nimport { join, basename } from 'node:path'\nimport { existsSync, mkdirSync, chmodSync, readFileSync, writeFileSync, readdirSync, renameSync, rmSync } from 'node:fs'\nimport { execFileSync } from 'node:child_process'\nimport { createHash } from 'node:crypto'\nimport { pipeline } from 'node:stream/promises'\nimport { Readable } from 'node:stream'\nimport { createWriteStream } from 'node:fs'\nimport type { IScopedLogger } from '../index.js'\nimport { errMsg } from '../index.js'\n\nexport const PYTHON_VERSION = '3.12.12'\nconst PP_BASE = `https://github.com/bjia56/portable-python/releases/download/cpython-v${PYTHON_VERSION}-build.0`\n\nexport function getPythonDownloadUrl(platform: string, arch: string): string {\n switch (platform) {\n case 'linux': {\n const archMap: Record<string, string> = { x64: 'x86_64', arm64: 'aarch64' }\n const a = archMap[arch]\n if (!a) throw new Error(`Unsupported Linux architecture for Python: ${arch}`)\n return `${PP_BASE}/python-headless-${PYTHON_VERSION}-linux-${a}.zip`\n }\n case 'darwin': {\n // universal2 covers both arm64 and x86_64\n return `${PP_BASE}/python-headless-${PYTHON_VERSION}-darwin-universal2.zip`\n }\n default:\n throw new Error(`Unsupported platform for portable Python: ${platform}`)\n }\n}\n\n/**\n * Ensure the embedded portable Python is available.\n *\n * ALWAYS returns the path to the embedded interpreter under\n * `<dataDir>/deps/python/bin/python3` — downloads it on first call\n * and reuses it thereafter. The system PATH is intentionally NOT\n * consulted: addons rely on `installPythonRequirements()` to manage\n * deps inside the embedded site-packages, and a system interpreter\n * (different version, missing modules) silently breaks that contract.\n *\n * Returns null only if the download/extract step fails.\n */\nexport async function ensurePython(dataDir: string, logger: IScopedLogger): Promise<string | null> {\n const pythonDir = join(dataDir, 'deps', 'python')\n const pythonBin = join(pythonDir, 'bin', 'python3')\n\n // 1. Already downloaded\n if (existsSync(pythonBin)) {\n logger.debug('Portable Python found', { meta: { pythonBin } })\n return pythonBin\n }\n\n // 2. Download portable python (no system PATH fallback — by design)\n logger.info('Downloading portable Python (headless)', { meta: { version: PYTHON_VERSION } })\n\n const depsDir = join(dataDir, 'deps')\n mkdirSync(depsDir, { recursive: true })\n\n const url = getPythonDownloadUrl(process.platform, process.arch)\n const tmpArchive = join(depsDir, 'python-download.zip')\n\n logger.info('Python download source', { meta: { url } })\n\n const response = await fetch(url, { redirect: 'follow' })\n if (!response.ok || !response.body) {\n logger.warn('Failed to download Python — ML detection will not be available', { meta: { status: response.status } })\n return null\n }\n\n const nodeStream = Readable.fromWeb(response.body as ReadableStream)\n await pipeline(nodeStream, createWriteStream(tmpArchive))\n\n // Extract — zip's top-level dir is e.g. `python-headless-3.12.12-darwin-universal2/`\n // (varies per platform/arch).\n try {\n execFileSync('unzip', ['-o', '-q', tmpArchive, '-d', depsDir], { stdio: 'pipe' })\n } catch {\n // Fallback: some minimal Linux installs don't have unzip\n execFileSync('python3', ['-m', 'zipfile', '-e', tmpArchive, depsDir], { stdio: 'pipe' })\n }\n\n // Cleanup the archive\n const { unlinkSync } = await import('node:fs')\n unlinkSync(tmpArchive)\n\n // Locate the extracted directory and normalize it to `<depsDir>/python/`.\n // The zip's top-level name varies by platform (e.g.\n // `python-headless-3.12.12-darwin-universal2/`), so we look for any\n // `python-*` subdir that contains the expected `bin/python3` and rename it.\n if (!existsSync(pythonBin)) {\n const candidates = readdirSync(depsDir).filter((name) =>\n name.startsWith('python-') &&\n existsSync(join(depsDir, name, 'bin', 'python3')),\n )\n if (candidates.length === 0) {\n logger.warn('Python extraction succeeded but no python3 binary found in any subdir', {\n meta: { depsDir },\n })\n return null\n }\n const extractedRoot = join(depsDir, candidates[0]!)\n // Drop any stale `python/` (e.g. from a previous failed install)\n // before renaming so the move always succeeds.\n if (existsSync(pythonDir)) {\n rmSync(pythonDir, { recursive: true, force: true })\n }\n renameSync(extractedRoot, pythonDir)\n }\n\n if (!existsSync(pythonBin)) {\n logger.warn('Python extraction succeeded but binary not found at expected path', {\n meta: { pythonBin },\n })\n return null\n }\n\n chmodSync(pythonBin, 0o755)\n\n // On macOS the bjia56 build is universal2 — the host arch is decided\n // at spawn time based on the parent process. That makes pip wheels\n // and runtime spawns silently disagree when the server is launched\n // from a Rosetta shell once and a native shell next time (e.g. pip\n // installs `numpy x86_64`, then the next boot under arm64 fails with\n // `incompatible architecture`). Lock the slice deterministically by\n // wrapping `python3` in a shell stub that always invokes the matching\n // host arch via `/usr/bin/arch`. Idempotent: detected via the renamed\n // `.real` binary.\n if (process.platform === 'darwin') {\n const realBin = join(pythonDir, 'bin', 'python3.real')\n if (!existsSync(realBin)) {\n renameSync(pythonBin, realBin)\n const archFlag = process.arch === 'arm64' ? '-arm64' : '-x86_64'\n writeFileSync(\n pythonBin,\n `#!/bin/sh\\nexec /usr/bin/arch ${archFlag} \"${realBin}\" \"$@\"\\n`,\n )\n chmodSync(pythonBin, 0o755)\n // Mirror the same wrapping for the `python` symlink so consumers\n // that use the un-versioned name also lock the arch.\n const pyAlt = join(pythonDir, 'bin', 'python')\n if (existsSync(pyAlt)) {\n try {\n rmSync(pyAlt, { force: true })\n } catch {\n // ignore\n }\n writeFileSync(\n pyAlt,\n `#!/bin/sh\\nexec /usr/bin/arch ${archFlag} \"${realBin}\" \"$@\"\\n`,\n )\n chmodSync(pyAlt, 0o755)\n }\n logger.info('Locked python3 to host arch via shell stub', {\n meta: { archFlag, realBin },\n })\n }\n }\n\n logger.info('Portable Python installed', { meta: { version: PYTHON_VERSION, pythonBin } })\n return pythonBin\n}\n\n/**\n * Install Python packages into the portable Python environment.\n */\nexport async function installPythonPackages(\n pythonPath: string,\n packages: readonly string[],\n logger: IScopedLogger,\n): Promise<void> {\n if (packages.length === 0) return\n\n logger.info('Installing Python packages', { meta: { packages } })\n try {\n execFileSync(pythonPath, ['-m', 'pip', 'install', '--quiet', ...packages], {\n stdio: 'pipe',\n timeout: 300_000, // 5 minutes for large packages like torch\n })\n logger.info('Python packages installed successfully')\n } catch (err) {\n logger.error('Failed to install Python packages', { meta: { error: errMsg(err) } })\n throw err\n }\n}\n\n/**\n * Install a pip requirements file into the embedded Python.\n *\n * Idempotent: keyed on (requirements file basename + sha256 of its\n * contents). The marker is written under\n * `<python-install-dir>/.requirements-installed/` so it lives and\n * dies with the python install — re-downloading python wipes both\n * the site-packages and the markers, forcing a fresh re-install.\n *\n * The marker is updated only after `pip install` succeeds. A failed\n * install never writes a marker, so the next call retries.\n */\nexport async function installPythonRequirements(\n pythonPath: string,\n requirementsFile: string,\n logger: IScopedLogger,\n): Promise<void> {\n if (!existsSync(requirementsFile)) {\n throw new Error(`Requirements file not found: ${requirementsFile}`)\n }\n\n const contents = readFileSync(requirementsFile)\n const hash = createHash('sha256').update(contents).digest('hex').slice(0, 16)\n const fileBase = basename(requirementsFile).replace(/\\.txt$/i, '')\n\n // Markers live under the python install dir → wiped together when\n // the embedded interpreter is re-downloaded.\n const pythonRoot = join(pythonPath, '..', '..') // <root>/bin/python3 → <root>\n const markerDir = join(pythonRoot, '.requirements-installed')\n const markerPath = join(markerDir, `${fileBase}-${hash}.marker`)\n\n if (existsSync(markerPath)) {\n logger.debug('Python requirements already installed', { meta: { requirementsFile, hash } })\n return\n }\n\n logger.info('Installing Python requirements', { meta: { requirementsFile, hash } })\n try {\n execFileSync(\n pythonPath,\n ['-m', 'pip', 'install', '--quiet', '--disable-pip-version-check', '-r', requirementsFile],\n {\n stdio: 'pipe',\n timeout: 600_000, // 10 minutes — coremltools + tensorflow can be slow\n },\n )\n } catch (err) {\n logger.error('Failed to install Python requirements', {\n meta: { requirementsFile, error: errMsg(err) },\n })\n throw err\n }\n\n mkdirSync(markerDir, { recursive: true })\n writeFileSync(markerPath, `${requirementsFile}\\n${new Date().toISOString()}\\n`)\n logger.info('Python requirements installed', { meta: { requirementsFile, hash } })\n}\n","import * as fs from 'node:fs'\nimport * as path from 'node:path'\nimport type {\n IStorageProvider,\n StorageLocationType,\n StorageResolveInput,\n StorageWriteInput,\n StorageReadInput,\n StorageExistsInput,\n StorageListInput,\n StorageDeleteInput,\n StorageAvailableSpaceInput,\n} from '../index.js'\n\n// Inline constants to avoid runtime dependency on @camstack/types\n// (types is external in tsup bundle, may not be resolvable from data/addons/)\nconst STORAGE_LOCATION_TYPES: readonly StorageLocationType[] = [\n 'data', 'media', 'recordings',\n 'recordings-high', 'recordings-low', 'recordings-clips', 'event-images',\n 'models', 'addons-data', 'cache', 'logs', 'backups',\n]\n\nconst DEFAULT_LOCATION_SUBDIRS: Readonly<Record<StorageLocationType, string>> = {\n 'data': 'db',\n 'media': 'media',\n 'recordings': 'recordings',\n 'recordings-high': 'recordings-high',\n 'recordings-low': 'recordings-low',\n 'recordings-clips': 'recordings-clips',\n 'event-images': 'event-images',\n 'models': 'models',\n 'addons-data': 'addons-data',\n 'cache': '/tmp/camstack-cache',\n 'logs': 'logs',\n 'backups': 'backups',\n}\n\n/**\n * Filesystem storage provider — serves all location types from a local directory tree.\n *\n * Default layout:\n * {rootPath}/recordings-high/\n * {rootPath}/recordings-low/\n * {rootPath}/recordings-clips/\n * {rootPath}/event-images/\n * {rootPath}/models/\n * {rootPath}/addons-data/\n * {rootPath}/logs/\n * /tmp/camstack-cache/ (cache is always local)\n *\n * Individual location paths can be overridden.\n */\nexport class FilesystemStorageProvider implements IStorageProvider {\n readonly id = 'local'\n readonly name = 'Local Filesystem'\n readonly supportedLocations: readonly StorageLocationType[] = [...STORAGE_LOCATION_TYPES]\n\n private readonly rootPath: string\n private readonly locationPaths: Map<StorageLocationType, string>\n\n constructor(rootPath: string, overrides?: Partial<Record<StorageLocationType, string>>) {\n this.rootPath = path.resolve(rootPath)\n this.locationPaths = new Map()\n\n for (const loc of STORAGE_LOCATION_TYPES) {\n const override = overrides?.[loc]\n if (override) {\n this.locationPaths.set(loc, path.resolve(override))\n } else {\n const subdir = DEFAULT_LOCATION_SUBDIRS[loc] ?? loc\n // Absolute paths (like /tmp/camstack-cache) are used as-is\n this.locationPaths.set(\n loc,\n path.isAbsolute(subdir) ? subdir : path.join(this.rootPath, subdir),\n )\n }\n }\n }\n\n async resolve({ location, relativePath }: StorageResolveInput): Promise<string> {\n // Known preset types use their mapped path; any other (addon-declared)\n // location id resolves to `<rootPath>/<id>`, matching the orchestrator's\n // `localData/<id>` default — so new declared locations (eventMedia,\n // recordingsLow, …) work without a hardcoded entry here.\n const base = this.locationPaths.get(location) ?? path.join(this.rootPath, location)\n return path.join(base, relativePath)\n }\n\n async write({ location, relativePath, data }: StorageWriteInput): Promise<void> {\n const filePath = await this.resolve({ location, relativePath })\n await fs.promises.mkdir(path.dirname(filePath), { recursive: true })\n\n if (Buffer.isBuffer(data)) {\n await fs.promises.writeFile(filePath, data)\n } else {\n // Stream\n const writeStream = fs.createWriteStream(filePath)\n await new Promise<void>((resolve, reject) => {\n (data as NodeJS.ReadableStream).pipe(writeStream)\n writeStream.on('finish', resolve)\n writeStream.on('error', reject)\n })\n }\n }\n\n async read({ location, relativePath }: StorageReadInput): Promise<Buffer> {\n return fs.promises.readFile(await this.resolve({ location, relativePath }))\n }\n\n async exists({ location, relativePath }: StorageExistsInput): Promise<boolean> {\n try {\n await fs.promises.access(await this.resolve({ location, relativePath }))\n return true\n } catch {\n return false\n }\n }\n\n async list({ location, prefix }: StorageListInput): Promise<readonly string[]> {\n const base = this.locationPaths.get(location)\n if (!base) return []\n const dir = prefix ? path.join(base, prefix) : base\n try {\n const entries = await fs.promises.readdir(dir, { withFileTypes: true })\n return entries.map(e => (prefix ? `${prefix}/${e.name}` : e.name))\n } catch {\n return []\n }\n }\n\n async delete({ location, relativePath }: StorageDeleteInput): Promise<void> {\n const filePath = await this.resolve({ location, relativePath })\n await fs.promises.rm(filePath, { force: true })\n }\n\n async getAvailableSpace({ location }: StorageAvailableSpaceInput): Promise<number | null> {\n const base = this.locationPaths.get(location)\n if (!base) return null\n // If the directory doesn't exist yet (lazy creation), walk up to\n // the nearest existing ancestor so statfs can still report free\n // space on the mount point. This keeps the storage dashboard\n // working on a fresh install where no file has been written yet.\n try {\n let target = base\n while (!fs.existsSync(target)) {\n const parent = path.dirname(target)\n if (!parent || parent === target) return null\n target = parent\n }\n const stats = await fs.promises.statfs(target)\n return stats.bavail * stats.bsize\n } catch {\n return null\n }\n }\n\n\n /** Get the resolved path for a location type (addon-declared ids fall back\n * to `<rootPath>/<id>`). */\n getLocationPath(location: StorageLocationType): string {\n return this.locationPaths.get(location) ?? path.join(this.rootPath, location)\n }\n\n /** Get the root path */\n getRootPath(): string {\n return this.rootPath\n }\n}\n\nexport default FilesystemStorageProvider\n","import { createHash } from 'node:crypto'\n\n/**\n * Deterministic SHA-256 hash of an arbitrary serialisable value. The\n * canonical form sorts object keys alphabetically at every depth so two\n * structurally-equal inputs with different key insertion orders produce\n * the same hash. Returns a 64-char lowercase hex digest.\n *\n * Used by export adapters (Alexa, HAP) to short-circuit re-discovery /\n * accessory-rebuild work when the upstream shape is byte-identical to\n * the last applied state — preventing user-visible \"re-discovery\"\n * notifications on every addon-runner respawn. Each respawn re-fires\n * `DeviceBindingsChanged` for every cap registration, which without\n * this guard would propagate redundant pushes.\n *\n * Note: this is a SYMPTOMATIC fix layered on top of the binding-change\n * subscription. The proper fix is a single \"device ready\" lifecycle\n * barrier so exports react only when the full cap set has landed —\n * tracked separately for post-HA-integration work.\n */\nexport function canonicalHash(value: unknown): string {\n const canonical = JSON.stringify(value, replaceWithSortedKeys)\n return createHash('sha256').update(canonical ?? '').digest('hex')\n}\n\nfunction replaceWithSortedKeys(_key: string, value: unknown): unknown {\n if (value && typeof value === 'object' && !Array.isArray(value)) {\n const obj = value as Record<string, unknown>\n const out: Record<string, unknown> = {}\n for (const k of Object.keys(obj).sort()) out[k] = obj[k]\n return out\n }\n return value\n}\n","import { canonicalHash } from './canonical-hash.js'\n\n/**\n * Pure, stateless primitives shared by the camera export addons (Alexa, HAP)\n * to advertise devices to external ecosystems without re-discovery storms.\n *\n * Spec: docs/superpowers/specs/2026-06-01-alexa-hap-export-reconciler-design.md\n *\n * Two functions, both pure (no I/O, no clock, no randomness, no event\n * subscriptions) so they are exhaustively unit-testable in isolation:\n *\n * 1) `canonicalDeviceFingerprint(shape)` — a stable hash of the\n * EXPORT-RELEVANT device shape (coarse type + the persisted\n * `DeviceFeature` set). Order-independent and de-duplicated, so an\n * unchanged device produces an identical fingerprint across restarts.\n * Identity (`deviceId`) and volatile runtime values (online, battery%,\n * name, location) are deliberately excluded.\n *\n * 2) `diffExportTargets(desired, lastAdvertised)` — a 3-way set diff keyed\n * by `deviceId`: what to add, what to update (fingerprint changed), what\n * to remove (no longer desired). Each exporter feeds its own persisted\n * per-target sync state in and applies the resulting delta to its\n * platform.\n *\n * The wiring — event subscription, boot scan, platform apply, persistence —\n * stays in each exporter; only the fingerprint + diff are shared.\n */\n\n/** The export-relevant projection of a device, fed into the fingerprint. */\nexport interface DeviceExportShape {\n /** Numeric device id. Identity only — NOT part of the fingerprint. */\n readonly deviceId: number\n /**\n * Coarse driver classification (e.g. `'camera' | 'battery-camera' | 'nvr'`),\n * sourced from the `feature-probe` slice's `deviceType`. Part of the shape:\n * the same feature set on a different device class can map to a different\n * export surface.\n */\n readonly deviceType: string\n /**\n * Export-relevant `DeviceFeature` values (e.g. `'pan-tilt-zoom'`,\n * `'two-way-audio'`, `'doorbell-button'`, `'motion-trigger'`,\n * `'battery-operated'`, `'ptz-autotrack'`). Sourced from the PERSISTED\n * `PersistedDeviceMeta.features`, so it survives restart and is stable\n * without a re-probe. Hashed order-independently + de-duplicated.\n */\n readonly features: readonly string[]\n}\n\n/**\n * Compute the stable 64-char lowercase-hex fingerprint of a device's\n * export-relevant shape. Two structurally-equal shapes (any feature order,\n * any duplicates, any deviceId) hash identically.\n */\nexport function canonicalDeviceFingerprint(shape: DeviceExportShape): string {\n const features = [...new Set(shape.features)].sort()\n return canonicalHash({ deviceType: shape.deviceType, features })\n}\n\n/** Per-export-target sync state for one device, persisted addon-side. */\nexport interface ExportTargetEntry {\n /** Fingerprint last advertised to this target for this device. */\n readonly fingerprint: string\n /**\n * Platform-specific external ids (Alexa endpoint ids, HAP accessory UUIDs).\n * Opaque to the diff — carried so the exporter can address what to update or\n * delete on its platform.\n */\n readonly externalIds: readonly string[]\n}\n\n/** The 3-way delta produced by `diffExportTargets`. */\nexport interface ExportDelta<T> {\n /** Desired devices with no last-advertised entry — publish fresh. */\n readonly toAdd: readonly T[]\n /** Desired devices whose fingerprint differs from last-advertised — re-advertise. */\n readonly toUpdate: readonly T[]\n /** `deviceId`s present in last-advertised but absent from desired — retract. */\n readonly toRemove: readonly number[]\n}\n\n/**\n * Pure 3-way diff of desired export state against the persisted\n * last-advertised state, keyed by `deviceId`.\n *\n * - `desired` — one entry per device to advertise this cycle, each carrying\n * the freshly-computed `fingerprint`.\n * - `lastAdvertised` — the exporter's persisted per-target sync state.\n *\n * Neither input is mutated.\n */\nexport function diffExportTargets<T extends { deviceId: number; fingerprint: string }>(\n desired: readonly T[],\n lastAdvertised: ReadonlyMap<number, ExportTargetEntry>,\n): ExportDelta<T> {\n const toAdd: T[] = []\n const toUpdate: T[] = []\n const desiredIds = new Set<number>()\n\n for (const entry of desired) {\n desiredIds.add(entry.deviceId)\n const previous = lastAdvertised.get(entry.deviceId)\n if (previous === undefined) {\n toAdd.push(entry)\n } else if (previous.fingerprint !== entry.fingerprint) {\n toUpdate.push(entry)\n }\n // unchanged fingerprint → no delta\n }\n\n const toRemove: number[] = []\n for (const deviceId of lastAdvertised.keys()) {\n if (!desiredIds.has(deviceId)) toRemove.push(deviceId)\n }\n\n return { toAdd, toUpdate, toRemove }\n}\n\n/**\n * Resolve the fingerprint an exporter should feed into `diffExportTargets`\n * for one device this cycle.\n *\n * When a device is NOT yet probe-ready, carry forward its last-advertised\n * (persisted) fingerprint so it nets to NO CHANGE in `diffExportTargets`\n * (neither update nor remove) — never advertise a partial mid-probe shape. A\n * brand-new un-probed device with no persisted entry falls back to `fresh` so\n * it can advertise once.\n */\nexport function resolveExportFingerprint(input: {\n ready: boolean\n fresh: string\n persisted: string | undefined\n}): string {\n if (input.ready) return input.fresh\n return input.persisted ?? input.fresh\n}\n"],"names":["rmSync","unlinkSync"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"readiness-registry.d.ts","sourceRoot":"","sources":["../../src/readiness/readiness-registry.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"readiness-registry.d.ts","sourceRoot":"","sources":["../../src/readiness/readiness-registry.ts"],"names":[],"mappings":"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"}
@@ -33,15 +33,15 @@ export declare const MethodAccessSchema: z.ZodEnum<{
33
33
  }>;
34
34
  export type MethodAccess = z.infer<typeof MethodAccessSchema>;
35
35
  export declare const CapScopeSchema: z.ZodEnum<{
36
- system: "system";
37
36
  device: "device";
37
+ system: "system";
38
38
  }>;
39
39
  export type CapScope = z.infer<typeof CapScopeSchema>;
40
40
  export declare const TokenScopeSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
41
41
  type: z.ZodLiteral<"category">;
42
42
  target: z.ZodEnum<{
43
- system: "system";
44
43
  device: "device";
44
+ system: "system";
45
45
  }>;
46
46
  access: z.ZodArray<z.ZodEnum<{
47
47
  view: "view";
@@ -84,8 +84,8 @@ export declare const UserRecordSchema: z.ZodObject<{
84
84
  scopes: z.ZodDefault<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
85
85
  type: z.ZodLiteral<"category">;
86
86
  target: z.ZodEnum<{
87
- system: "system";
88
87
  device: "device";
88
+ system: "system";
89
89
  }>;
90
90
  access: z.ZodArray<z.ZodEnum<{
91
91
  view: "view";
@@ -142,8 +142,8 @@ export declare const ScopedTokenSchema: z.ZodObject<{
142
142
  scopes: z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
143
143
  type: z.ZodLiteral<"category">;
144
144
  target: z.ZodEnum<{
145
- system: "system";
146
145
  device: "device";
146
+ system: "system";
147
147
  }>;
148
148
  access: z.ZodArray<z.ZodEnum<{
149
149
  view: "view";
@@ -28,7 +28,8 @@ export declare class FilesystemStorageProvider implements IStorageProvider {
28
28
  list({ location, prefix }: StorageListInput): Promise<readonly string[]>;
29
29
  delete({ location, relativePath }: StorageDeleteInput): Promise<void>;
30
30
  getAvailableSpace({ location }: StorageAvailableSpaceInput): Promise<number | null>;
31
- /** Get the resolved path for a location type */
31
+ /** Get the resolved path for a location type (addon-declared ids fall back
32
+ * to `<rootPath>/<id>`). */
32
33
  getLocationPath(location: StorageLocationType): string;
33
34
  /** Get the root path */
34
35
  getRootPath(): string;
@@ -1 +1 @@
1
- {"version":3,"file":"filesystem-storage-provider.d.ts","sourceRoot":"","sources":["../../src/storage/filesystem-storage-provider.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,gBAAgB,EAChB,mBAAmB,EACnB,mBAAmB,EACnB,iBAAiB,EACjB,gBAAgB,EAChB,kBAAkB,EAClB,gBAAgB,EAChB,kBAAkB,EAClB,0BAA0B,EAC3B,MAAM,aAAa,CAAA;AAyBpB;;;;;;;;;;;;;;GAcG;AACH,qBAAa,yBAA0B,YAAW,gBAAgB;IAChE,QAAQ,CAAC,EAAE,WAAU;IACrB,QAAQ,CAAC,IAAI,sBAAqB;IAClC,QAAQ,CAAC,kBAAkB,EAAE,SAAS,mBAAmB,EAAE,CAA8B;IAEzF,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAQ;IACjC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAkC;gBAEpD,QAAQ,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,mBAAmB,EAAE,MAAM,CAAC,CAAC;IAmBhF,OAAO,CAAC,EAAE,QAAQ,EAAE,YAAY,EAAE,EAAE,mBAAmB,GAAG,OAAO,CAAC,MAAM,CAAC;IAMzE,KAAK,CAAC,EAAE,QAAQ,EAAE,YAAY,EAAE,IAAI,EAAE,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;IAiBzE,IAAI,CAAC,EAAE,QAAQ,EAAE,YAAY,EAAE,EAAE,gBAAgB,GAAG,OAAO,CAAC,MAAM,CAAC;IAInE,MAAM,CAAC,EAAE,QAAQ,EAAE,YAAY,EAAE,EAAE,kBAAkB,GAAG,OAAO,CAAC,OAAO,CAAC;IASxE,IAAI,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,gBAAgB,GAAG,OAAO,CAAC,SAAS,MAAM,EAAE,CAAC;IAYxE,MAAM,CAAC,EAAE,QAAQ,EAAE,YAAY,EAAE,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC;IAKrE,iBAAiB,CAAC,EAAE,QAAQ,EAAE,EAAE,0BAA0B,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAsBzF,gDAAgD;IAChD,eAAe,CAAC,QAAQ,EAAE,mBAAmB,GAAG,MAAM;IAMtD,wBAAwB;IACxB,WAAW,IAAI,MAAM;CAGtB;AAED,eAAe,yBAAyB,CAAA"}
1
+ {"version":3,"file":"filesystem-storage-provider.d.ts","sourceRoot":"","sources":["../../src/storage/filesystem-storage-provider.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,gBAAgB,EAChB,mBAAmB,EACnB,mBAAmB,EACnB,iBAAiB,EACjB,gBAAgB,EAChB,kBAAkB,EAClB,gBAAgB,EAChB,kBAAkB,EAClB,0BAA0B,EAC3B,MAAM,aAAa,CAAA;AAyBpB;;;;;;;;;;;;;;GAcG;AACH,qBAAa,yBAA0B,YAAW,gBAAgB;IAChE,QAAQ,CAAC,EAAE,WAAU;IACrB,QAAQ,CAAC,IAAI,sBAAqB;IAClC,QAAQ,CAAC,kBAAkB,EAAE,SAAS,mBAAmB,EAAE,CAA8B;IAEzF,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAQ;IACjC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAkC;gBAEpD,QAAQ,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,mBAAmB,EAAE,MAAM,CAAC,CAAC;IAmBhF,OAAO,CAAC,EAAE,QAAQ,EAAE,YAAY,EAAE,EAAE,mBAAmB,GAAG,OAAO,CAAC,MAAM,CAAC;IASzE,KAAK,CAAC,EAAE,QAAQ,EAAE,YAAY,EAAE,IAAI,EAAE,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;IAiBzE,IAAI,CAAC,EAAE,QAAQ,EAAE,YAAY,EAAE,EAAE,gBAAgB,GAAG,OAAO,CAAC,MAAM,CAAC;IAInE,MAAM,CAAC,EAAE,QAAQ,EAAE,YAAY,EAAE,EAAE,kBAAkB,GAAG,OAAO,CAAC,OAAO,CAAC;IASxE,IAAI,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,gBAAgB,GAAG,OAAO,CAAC,SAAS,MAAM,EAAE,CAAC;IAYxE,MAAM,CAAC,EAAE,QAAQ,EAAE,YAAY,EAAE,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC;IAKrE,iBAAiB,CAAC,EAAE,QAAQ,EAAE,EAAE,0BAA0B,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAsBzF;iCAC6B;IAC7B,eAAe,CAAC,QAAQ,EAAE,mBAAmB,GAAG,MAAM;IAItD,wBAAwB;IACxB,WAAW,IAAI,MAAM;CAGtB;AAED,eAAe,yBAAyB,CAAA"}