@pyric/cli 0.1.0-alpha.11 → 0.1.0-alpha.13

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 (424) hide show
  1. package/dist/cli/serve.js +4 -4
  2. package/dist/cli/serve.js.map +1 -1
  3. package/dist/conformance/.generated/can-i-use-browser.d.ts.map +1 -1
  4. package/dist/conformance/.generated/can-i-use-browser.js +1 -1
  5. package/dist/conformance/.generated/can-i-use-browser.js.map +1 -1
  6. package/dist/conformance/.generated/can-i-use.d.ts.map +1 -1
  7. package/dist/conformance/.generated/can-i-use.js +2 -2
  8. package/dist/conformance/.generated/can-i-use.js.map +1 -1
  9. package/dist/serve/bundler.d.ts +13 -8
  10. package/dist/serve/bundler.d.ts.map +1 -1
  11. package/dist/serve/bundler.js +31 -36
  12. package/dist/serve/bundler.js.map +1 -1
  13. package/dist/serve/docs-ui/docs/_rhythm/index.html +1 -1
  14. package/dist/serve/docs-ui/docs/agent/set-up-your-agent/index.html +1 -1
  15. package/dist/serve/docs-ui/docs/agent/watch-and-review/index.html +1 -1
  16. package/dist/serve/docs-ui/docs/agent/work-with-an-agent/index.html +1 -1
  17. package/dist/serve/docs-ui/docs/ai-compat/index.html +1 -1
  18. package/dist/serve/docs-ui/docs/api-reference/index.html +2 -2
  19. package/dist/serve/docs-ui/docs/api-reference.md +1 -1
  20. package/dist/serve/docs-ui/docs/app-compat/index.html +1 -1
  21. package/dist/serve/docs-ui/docs/auth-compat/index.html +1 -1
  22. package/dist/serve/docs-ui/docs/build/ai-logic/index.html +44 -22
  23. package/dist/serve/docs-ui/docs/build/ai-logic.md +69 -21
  24. package/dist/serve/docs-ui/docs/build/authentication/index.html +1 -1
  25. package/dist/serve/docs-ui/docs/build/cloud-firestore/index.html +1 -1
  26. package/dist/serve/docs-ui/docs/build/cloud-messaging/index.html +1 -1
  27. package/dist/serve/docs-ui/docs/build/cloud-storage/index.html +1 -1
  28. package/dist/serve/docs-ui/docs/build/realtime-database/index.html +1 -1
  29. package/dist/serve/docs-ui/docs/conformance-scores/index.html +1 -1
  30. package/dist/serve/docs-ui/docs/create-pyric-reference-api/index.html +1 -1
  31. package/dist/serve/docs-ui/docs/database-compat/index.html +1 -1
  32. package/dist/serve/docs-ui/docs/firestore-compat/index.html +1 -1
  33. package/dist/serve/docs-ui/docs/functions-rtdb-compat/index.html +1 -1
  34. package/dist/serve/docs-ui/docs/get-started/how-the-swap-works/index.html +2 -1
  35. package/dist/serve/docs-ui/docs/get-started/how-the-swap-works.md +2 -0
  36. package/dist/serve/docs-ui/docs/get-started/start-building/index.html +3 -2
  37. package/dist/serve/docs-ui/docs/get-started/start-building.md +3 -1
  38. package/dist/serve/docs-ui/docs/index.json +349 -29
  39. package/dist/serve/docs-ui/docs/messaging-compat/index.html +1 -1
  40. package/dist/serve/docs-ui/docs/observe/resolve-runtime-status/index.html +28 -0
  41. package/dist/serve/docs-ui/docs/observe/resolve-runtime-status.md +47 -0
  42. package/dist/serve/docs-ui/docs/observe/see-whats-happening/index.html +1 -1
  43. package/dist/serve/docs-ui/docs/observe/shape-your-data/index.html +1 -1
  44. package/dist/serve/docs-ui/docs/overview/index.html +1 -1
  45. package/dist/serve/docs-ui/docs/pyric-admin-app-reference-api/index.html +1 -1
  46. package/dist/serve/docs-ui/docs/pyric-admin-auth-reference-api/index.html +1 -1
  47. package/dist/serve/docs-ui/docs/pyric-admin-database-reference-api/index.html +1 -1
  48. package/dist/serve/docs-ui/docs/pyric-admin-firestore-reference-api/index.html +1 -1
  49. package/dist/serve/docs-ui/docs/pyric-admin-messaging-reference-api/index.html +916 -0
  50. package/dist/serve/docs-ui/docs/pyric-admin-messaging-reference-api.md +706 -0
  51. package/dist/serve/docs-ui/docs/pyric-admin-storage-reference-api/index.html +1 -1
  52. package/dist/serve/docs-ui/docs/pyric-ai-reference-api/index.html +1 -1
  53. package/dist/serve/docs-ui/docs/pyric-ai-scripting-reference-api/index.html +1 -1
  54. package/dist/serve/docs-ui/docs/pyric-app-reference-api/index.html +1 -1
  55. package/dist/serve/docs-ui/docs/pyric-auth-reference-api/index.html +1 -1
  56. package/dist/serve/docs-ui/docs/pyric-cli-assurance-browser-reference-api/index.html +1 -1
  57. package/dist/serve/docs-ui/docs/pyric-cli-assurance-reference-api/index.html +1 -1
  58. package/dist/serve/docs-ui/docs/pyric-cli-bridge-client-reference-api/index.html +1 -1
  59. package/dist/serve/docs-ui/docs/pyric-cli-bridge-reference-api/index.html +1 -1
  60. package/dist/serve/docs-ui/docs/pyric-cli-conformance-browser-reference-api/index.html +1 -1
  61. package/dist/serve/docs-ui/docs/pyric-cli-conformance-docs-reference-api/index.html +1 -1
  62. package/dist/serve/docs-ui/docs/pyric-cli-conformance-reference-api/index.html +1 -1
  63. package/dist/serve/docs-ui/docs/pyric-cli-credentials-node-reference-api/index.html +1 -1
  64. package/dist/serve/docs-ui/docs/pyric-cli-discover-reference-api/index.html +1 -1
  65. package/dist/serve/docs-ui/docs/pyric-cli-register-reference-api/index.html +1 -1
  66. package/dist/serve/docs-ui/docs/pyric-cli-remote-reference-api/index.html +1 -1
  67. package/dist/serve/docs-ui/docs/pyric-cli-serve-worker-reference-api/index.html +168 -76
  68. package/dist/serve/docs-ui/docs/pyric-cli-serve-worker-reference-api.md +156 -73
  69. package/dist/serve/docs-ui/docs/pyric-cli-verify-reference-api/index.html +1 -1
  70. package/dist/serve/docs-ui/docs/pyric-cli-vite-reference-api/index.html +11 -8
  71. package/dist/serve/docs-ui/docs/pyric-cli-vite-reference-api.md +17 -4
  72. package/dist/serve/docs-ui/docs/pyric-database-reference-api/index.html +1 -1
  73. package/dist/serve/docs-ui/docs/pyric-firestore-reference-api/index.html +1 -1
  74. package/dist/serve/docs-ui/docs/pyric-messaging-reference-api/index.html +1 -1
  75. package/dist/serve/docs-ui/docs/pyric-messaging-sw-reference-api/index.html +1 -1
  76. package/dist/serve/docs-ui/docs/pyric-rules-reference-api/index.html +1 -1
  77. package/dist/serve/docs-ui/docs/pyric-sandbox-database-reference-api/index.html +1 -1
  78. package/dist/serve/docs-ui/docs/pyric-sandbox-firestore-reference-api/index.html +1 -1
  79. package/dist/serve/docs-ui/docs/pyric-sandbox-reference-api/index.html +37 -14
  80. package/dist/serve/docs-ui/docs/pyric-sandbox-reference-api.md +28 -8
  81. package/dist/serve/docs-ui/docs/pyric-storage-reference-api/index.html +1 -1
  82. package/dist/serve/docs-ui/docs/pyric-ui-agents-reference-api/index.html +1 -1
  83. package/dist/serve/docs-ui/docs/pyric-ui-auth-hooks-reference-api/index.html +1 -1
  84. package/dist/serve/docs-ui/docs/pyric-ui-auth-reference-api/index.html +1 -1
  85. package/dist/serve/docs-ui/docs/pyric-ui-events-hooks-reference-api/index.html +1 -1
  86. package/dist/serve/docs-ui/docs/pyric-ui-events-reference-api/index.html +1 -1
  87. package/dist/serve/docs-ui/docs/pyric-ui-firestore-hooks-reference-api/index.html +1 -1
  88. package/dist/serve/docs-ui/docs/pyric-ui-firestore-reference-api/index.html +1 -1
  89. package/dist/serve/docs-ui/docs/pyric-ui-primitives-reference-api/index.html +1 -1
  90. package/dist/serve/docs-ui/docs/pyric-ui-rtdb-reference-api/index.html +1 -1
  91. package/dist/serve/docs-ui/docs/pyric-ui-rules-hooks-reference-api/index.html +1 -1
  92. package/dist/serve/docs-ui/docs/pyric-ui-rules-reference-api/index.html +1 -1
  93. package/dist/serve/docs-ui/docs/pyric-ui-storage-hooks-reference-api/index.html +1 -1
  94. package/dist/serve/docs-ui/docs/pyric-ui-storage-reference-api/index.html +1 -1
  95. package/dist/serve/docs-ui/docs/pyric-ui-traffic-hooks-reference-api/index.html +1 -1
  96. package/dist/serve/docs-ui/docs/pyric-ui-traffic-reference-api/index.html +1 -1
  97. package/dist/serve/docs-ui/docs/rules-compat/index.html +1 -1
  98. package/dist/serve/docs-ui/docs/secure/audit-your-rules/index.html +1 -1
  99. package/dist/serve/docs-ui/docs/secure/firestore-rules-limits/index.html +1 -1
  100. package/dist/serve/docs-ui/docs/secure/read-a-denial/index.html +1 -1
  101. package/dist/serve/docs-ui/docs/secure/rtdb-rules-in-typescript/index.html +1 -1
  102. package/dist/serve/docs-ui/docs/secure/rules-standard-library/index.html +1 -1
  103. package/dist/serve/docs-ui/docs/secure/secure-it-with-rules/index.html +1 -1
  104. package/dist/serve/docs-ui/docs/secure/simulate-and-lint/index.html +1 -1
  105. package/dist/serve/docs-ui/docs/secure/write-a-rules-test-suite/index.html +1 -1
  106. package/dist/serve/docs-ui/docs/ship/ship-to-production/index.html +1 -1
  107. package/dist/serve/docs-ui/docs/ship/test-in-node/index.html +1 -1
  108. package/dist/serve/docs-ui/docs/storage-compat/index.html +1 -1
  109. package/dist/serve/docs-ui/docs/trust/how-we-know-it-matches-firebase/index.html +1 -1
  110. package/dist/serve/docs-ui/docs/trust/versioning-and-compatibility/index.html +1 -1
  111. package/dist/serve/docs-ui/docs/tutorial/index.html +429 -0
  112. package/dist/serve/docs-ui/docs/tutorial.md +701 -0
  113. package/dist/serve/docs-ui/llms.txt +3 -1
  114. package/dist/serve/entries/init.d.ts.map +1 -1
  115. package/dist/serve/entries/init.js +3 -0
  116. package/dist/serve/entries/init.js.map +1 -1
  117. package/dist/serve/entries/runtime.d.ts.map +1 -1
  118. package/dist/serve/entries/runtime.js +20 -5
  119. package/dist/serve/entries/runtime.js.map +1 -1
  120. package/dist/serve/entries/worker-runtime.d.ts +2 -2
  121. package/dist/serve/entries/worker-runtime.d.ts.map +1 -1
  122. package/dist/serve/entries/worker-runtime.js +45 -10
  123. package/dist/serve/entries/worker-runtime.js.map +1 -1
  124. package/dist/serve/runtime/chip-config.d.ts +19 -0
  125. package/dist/serve/runtime/chip-config.d.ts.map +1 -0
  126. package/dist/serve/runtime/chip-config.js +19 -0
  127. package/dist/serve/runtime/chip-config.js.map +1 -0
  128. package/dist/serve/runtime/chip-install.d.ts +10 -0
  129. package/dist/serve/runtime/chip-install.d.ts.map +1 -0
  130. package/dist/serve/runtime/chip-install.js +15 -0
  131. package/dist/serve/runtime/chip-install.js.map +1 -0
  132. package/dist/serve/runtime/chip.d.ts +17 -0
  133. package/dist/serve/runtime/chip.d.ts.map +1 -0
  134. package/dist/serve/runtime/chip.js +257 -0
  135. package/dist/serve/runtime/chip.js.map +1 -0
  136. package/dist/serve/runtime/manifest.d.ts +22 -0
  137. package/dist/serve/runtime/manifest.d.ts.map +1 -0
  138. package/dist/serve/runtime/manifest.js +20 -0
  139. package/dist/serve/runtime/manifest.js.map +1 -0
  140. package/dist/serve/runtime/status.d.ts +49 -0
  141. package/dist/serve/runtime/status.d.ts.map +1 -0
  142. package/dist/serve/runtime/status.js +164 -0
  143. package/dist/serve/runtime/status.js.map +1 -0
  144. package/dist/serve/runtime/worker-connection.d.ts +4 -0
  145. package/dist/serve/runtime/worker-connection.d.ts.map +1 -0
  146. package/dist/serve/runtime/worker-connection.js +11 -0
  147. package/dist/serve/runtime/worker-connection.js.map +1 -0
  148. package/dist/serve/runtime/worker-generation.d.ts +14 -0
  149. package/dist/serve/runtime/worker-generation.d.ts.map +1 -0
  150. package/dist/serve/runtime/worker-generation.js +56 -0
  151. package/dist/serve/runtime/worker-generation.js.map +1 -0
  152. package/dist/serve/runtime/worker-replacement.d.ts +18 -0
  153. package/dist/serve/runtime/worker-replacement.d.ts.map +1 -0
  154. package/dist/serve/runtime/worker-replacement.js +29 -0
  155. package/dist/serve/runtime/worker-replacement.js.map +1 -0
  156. package/dist/serve/standalone-assets.d.ts +2 -2
  157. package/dist/serve/standalone-assets.d.ts.map +1 -1
  158. package/dist/serve/standalone-assets.js +1 -1
  159. package/dist/serve/standalone-assets.js.map +1 -1
  160. package/dist/serve/studio-ui/assets/{RulesCodeEditor-CxVYcZXw.js → RulesCodeEditor-BDlH-5lp.js} +1 -1
  161. package/dist/serve/studio-ui/assets/{index-cpSfgQ7H.js → index-Bf9SSE8L.js} +5 -5
  162. package/dist/serve/studio-ui/assets/{seed-DeuFKi4J.js → seed-D_UjxfOU.js} +1 -1
  163. package/dist/serve/studio-ui/index.html +1 -1
  164. package/dist/serve/vite-ai-config.d.ts +25 -0
  165. package/dist/serve/vite-ai-config.d.ts.map +1 -0
  166. package/dist/serve/vite-ai-config.js +45 -0
  167. package/dist/serve/vite-ai-config.js.map +1 -0
  168. package/dist/serve/vite-plugin.d.ts +17 -36
  169. package/dist/serve/vite-plugin.d.ts.map +1 -1
  170. package/dist/serve/vite-plugin.js +38 -57
  171. package/dist/serve/vite-plugin.js.map +1 -1
  172. package/dist/serve/vite-rules-source.d.ts +8 -0
  173. package/dist/serve/vite-rules-source.d.ts.map +1 -0
  174. package/dist/serve/vite-rules-source.js +23 -0
  175. package/dist/serve/vite-rules-source.js.map +1 -0
  176. package/dist/serve/vite-worker-runtime.d.ts +20 -0
  177. package/dist/serve/vite-worker-runtime.d.ts.map +1 -0
  178. package/dist/serve/vite-worker-runtime.js +25 -0
  179. package/dist/serve/vite-worker-runtime.js.map +1 -0
  180. package/dist/serve/worker/client/connection.d.ts +7 -2
  181. package/dist/serve/worker/client/connection.d.ts.map +1 -1
  182. package/dist/serve/worker/client/connection.js +13 -16
  183. package/dist/serve/worker/client/connection.js.map +1 -1
  184. package/dist/serve/worker/client/core.d.ts +7 -0
  185. package/dist/serve/worker/client/core.d.ts.map +1 -1
  186. package/dist/serve/worker/client/core.js +28 -0
  187. package/dist/serve/worker/client/core.js.map +1 -1
  188. package/dist/serve/worker/client/runtime-control.d.ts +8 -0
  189. package/dist/serve/worker/client/runtime-control.d.ts.map +1 -0
  190. package/dist/serve/worker/client/runtime-control.js +11 -0
  191. package/dist/serve/worker/client/runtime-control.js.map +1 -0
  192. package/dist/serve/worker/entry.js +44 -2
  193. package/dist/serve/worker/entry.js.map +1 -1
  194. package/dist/serve/worker/host/core.js +1 -1
  195. package/dist/serve/worker/host/core.js.map +1 -1
  196. package/dist/serve/worker/host/firestore-writes.d.ts.map +1 -1
  197. package/dist/serve/worker/host/firestore-writes.js +3 -2
  198. package/dist/serve/worker/host/firestore-writes.js.map +1 -1
  199. package/dist/serve/worker/host/studio.js +1 -1
  200. package/dist/serve/worker/host/studio.js.map +1 -1
  201. package/dist/serve/worker/host-context.d.ts +1 -1
  202. package/dist/serve/worker/host-context.d.ts.map +1 -1
  203. package/dist/serve/worker/index.d.ts +2 -1
  204. package/dist/serve/worker/index.d.ts.map +1 -1
  205. package/dist/serve/worker/index.js +2 -1
  206. package/dist/serve/worker/index.js.map +1 -1
  207. package/dist/serve/worker/protocol.d.ts +15 -1
  208. package/dist/serve/worker/protocol.d.ts.map +1 -1
  209. package/dist/serve/worker/protocol.js +2 -2
  210. package/dist/serve/worker/protocol.js.map +1 -1
  211. package/dist/serve/worker/retirement.d.ts +21 -0
  212. package/dist/serve/worker/retirement.d.ts.map +1 -0
  213. package/dist/serve/worker/retirement.js +97 -0
  214. package/dist/serve/worker/retirement.js.map +1 -0
  215. package/dist/serve/worker/serve-init.d.ts.map +1 -1
  216. package/dist/serve/worker/serve-init.js +5 -6
  217. package/dist/serve/worker/serve-init.js.map +1 -1
  218. package/dist/vite.d.ts +1 -0
  219. package/dist/vite.d.ts.map +1 -1
  220. package/dist/vite.js.map +1 -1
  221. package/package.json +6 -4
  222. package/src/assurance/.generated/conformance-verdicts.ts +1090 -0
  223. package/src/assurance/attachment.ts +211 -0
  224. package/src/assurance/browser.ts +80 -0
  225. package/src/assurance/campaign.ts +496 -0
  226. package/src/assurance/capabilities.ts +446 -0
  227. package/src/assurance/cases.ts +43 -0
  228. package/src/assurance/index.ts +75 -0
  229. package/src/assurance/runner.ts +885 -0
  230. package/src/assurance/tool-names.ts +15 -0
  231. package/src/assurance/tools.ts +830 -0
  232. package/src/assurance/types.ts +352 -0
  233. package/src/assurance/validation.ts +434 -0
  234. package/src/bridge/client/bridge.ts +550 -0
  235. package/src/bridge/client/dispatch.ts +137 -0
  236. package/src/bridge/client.ts +45 -0
  237. package/src/bridge/protocol.ts +351 -0
  238. package/src/bridge/server/audit.ts +53 -0
  239. package/src/bridge/server/bridge.ts +597 -0
  240. package/src/bridge/server/headless.ts +160 -0
  241. package/src/bridge/server/json-schema-to-zod.ts +108 -0
  242. package/src/bridge/server/local-bridge.ts +66 -0
  243. package/src/bridge/server/logger.ts +50 -0
  244. package/src/bridge/server/mcp-contract.ts +93 -0
  245. package/src/bridge/server/mcp.ts +129 -0
  246. package/src/bridge/server/peer.ts +232 -0
  247. package/src/bridge/server/standalone.ts +347 -0
  248. package/src/bridge/server/tool-metadata.ts +99 -0
  249. package/src/bridge/server.ts +28 -0
  250. package/src/cli/can-i-use.ts +50 -0
  251. package/src/cli/cli.test.ts +493 -0
  252. package/src/cli/database-rules.ts +329 -0
  253. package/src/cli/dev-runner.ts +282 -0
  254. package/src/cli/firebase-json.ts +100 -0
  255. package/src/cli/firestore-indexes.ts +70 -0
  256. package/src/cli/index.ts +425 -0
  257. package/src/cli/init.ts +260 -0
  258. package/src/cli/mcp-proxy.ts +196 -0
  259. package/src/cli/parse-args.ts +80 -0
  260. package/src/cli/rules.ts +259 -0
  261. package/src/cli/scope.ts +67 -0
  262. package/src/cli/serve.ts +996 -0
  263. package/src/cli/service-commands.ts +73 -0
  264. package/src/cli/snapshot.ts +164 -0
  265. package/src/cli/storage-rules.ts +172 -0
  266. package/src/cli/verify.ts +416 -0
  267. package/src/conformance/.generated/can-i-use-browser.ts +113 -0
  268. package/src/conformance/.generated/can-i-use.ts +117 -0
  269. package/src/conformance/.generated/conformance-docs.ts +14 -0
  270. package/src/conformance/browser.ts +24 -0
  271. package/src/conformance/can-i-use-tool.ts +51 -0
  272. package/src/conformance/can-i-use.ts +18 -0
  273. package/src/conformance/docs.ts +8 -0
  274. package/src/conformance/index.ts +20 -0
  275. package/src/conformance/tools.ts +16 -0
  276. package/src/credentials/core/memoize-ttl.ts +142 -0
  277. package/src/credentials/core/types.ts +10 -0
  278. package/src/credentials/node/from-adc.ts +96 -0
  279. package/src/credentials/node/from-service-account.ts +133 -0
  280. package/src/credentials/node/index.ts +6 -0
  281. package/src/discover/concurrency.ts +127 -0
  282. package/src/discover/crawler-adapter.ts +142 -0
  283. package/src/discover/crawler.ts +1127 -0
  284. package/src/discover/credential-free.ts +27 -0
  285. package/src/discover/findCollectionGroup.ts +131 -0
  286. package/src/discover/firestore-source.ts +59 -0
  287. package/src/discover/index.ts +5 -0
  288. package/src/discover/merge.ts +523 -0
  289. package/src/discover/session.ts +402 -0
  290. package/src/discover/tools.ts +201 -0
  291. package/src/discover/types.ts +187 -0
  292. package/src/discover/wire.ts +324 -0
  293. package/src/functions-rtdb/child.ts +382 -0
  294. package/src/functions-rtdb/delivery.ts +7 -0
  295. package/src/functions-rtdb/discovery.ts +136 -0
  296. package/src/functions-rtdb/event.ts +64 -0
  297. package/src/functions-rtdb/execution.ts +111 -0
  298. package/src/functions-rtdb/in-memory-delivery.ts +40 -0
  299. package/src/functions-rtdb/project.ts +74 -0
  300. package/src/functions-rtdb/projection.ts +104 -0
  301. package/src/functions-rtdb/reference-pattern.ts +20 -0
  302. package/src/functions-rtdb/remote-delivery.ts +23 -0
  303. package/src/pkg-version.ts +49 -0
  304. package/src/register/esm-exports.ts +63 -0
  305. package/src/register/hooks.ts +30 -0
  306. package/src/register/index.ts +150 -0
  307. package/src/register/mapping.ts +33 -0
  308. package/src/remote/index.ts +1050 -0
  309. package/src/rtdb/crawl-snapshot.ts +118 -0
  310. package/src/rtdb/inspection.ts +137 -0
  311. package/src/rtdb/load-rules-document.ts +56 -0
  312. package/src/rtdb/rules-generation-tool.ts +38 -0
  313. package/src/rtdb/rules-json.ts +29 -0
  314. package/src/serve/activity-guard.ts +29 -0
  315. package/src/serve/activity-route.ts +164 -0
  316. package/src/serve/activity-warning.ts +26 -0
  317. package/src/serve/bridge-mount.ts +200 -0
  318. package/src/serve/bundler.ts +519 -0
  319. package/src/serve/capture-store.ts +65 -0
  320. package/src/serve/discovery.ts +199 -0
  321. package/src/serve/entries/ai.ts +292 -0
  322. package/src/serve/entries/app-backend.ts +4 -0
  323. package/src/serve/entries/app-client.ts +26 -0
  324. package/src/serve/entries/app-session-store.ts +47 -0
  325. package/src/serve/entries/app.ts +11 -0
  326. package/src/serve/entries/auth-helper-core.ts +181 -0
  327. package/src/serve/entries/auth-helper-dom.ts +134 -0
  328. package/src/serve/entries/auth-helper-runtime.ts +20 -0
  329. package/src/serve/entries/auth.ts +255 -0
  330. package/src/serve/entries/bridge-url.ts +31 -0
  331. package/src/serve/entries/database.ts +129 -0
  332. package/src/serve/entries/firestore.ts +232 -0
  333. package/src/serve/entries/init.ts +49 -0
  334. package/src/serve/entries/keepalive.ts +46 -0
  335. package/src/serve/entries/messaging-sw.ts +75 -0
  336. package/src/serve/entries/messaging.ts +96 -0
  337. package/src/serve/entries/runtime.ts +556 -0
  338. package/src/serve/entries/session-store.ts +97 -0
  339. package/src/serve/entries/storage.ts +92 -0
  340. package/src/serve/entries/tab-sync-wiring.ts +274 -0
  341. package/src/serve/entries/worker-runtime.ts +135 -0
  342. package/src/serve/init-payload.ts +42 -0
  343. package/src/serve/namespace.ts +675 -0
  344. package/src/serve/open-browser.ts +68 -0
  345. package/src/serve/rules.ts +272 -0
  346. package/src/serve/runtime/chip-config.ts +30 -0
  347. package/src/serve/runtime/chip-install.ts +27 -0
  348. package/src/serve/runtime/chip.ts +282 -0
  349. package/src/serve/runtime/manifest.ts +37 -0
  350. package/src/serve/runtime/status.ts +216 -0
  351. package/src/serve/runtime/worker-connection.ts +14 -0
  352. package/src/serve/runtime/worker-generation.ts +75 -0
  353. package/src/serve/runtime/worker-replacement.ts +42 -0
  354. package/src/serve/sandbox-marker.ts +31 -0
  355. package/src/serve/server.ts +428 -0
  356. package/src/serve/standalone-assets.ts +180 -0
  357. package/src/serve/state-store.ts +166 -0
  358. package/src/serve/studio/disk-project-store.ts +185 -0
  359. package/src/serve/studio/disk-workspace.ts +162 -0
  360. package/src/serve/studio/index.ts +20 -0
  361. package/src/serve/studio/routes.ts +237 -0
  362. package/src/serve/studio/store-types.ts +48 -0
  363. package/src/serve/studio/studio-storage.test.ts +289 -0
  364. package/src/serve/vite-ai-config.ts +77 -0
  365. package/src/serve/vite-plugin.ts +1075 -0
  366. package/src/serve/vite-rules-source.ts +28 -0
  367. package/src/serve/vite-worker-runtime.ts +51 -0
  368. package/src/serve/worker/activity-bootstrap.ts +24 -0
  369. package/src/serve/worker/client/admin-firestore.ts +38 -0
  370. package/src/serve/worker/client/ai.ts +124 -0
  371. package/src/serve/worker/client/auth.ts +489 -0
  372. package/src/serve/worker/client/connection.ts +227 -0
  373. package/src/serve/worker/client/core.ts +384 -0
  374. package/src/serve/worker/client/disconnect.ts +45 -0
  375. package/src/serve/worker/client/firestore-reads.ts +181 -0
  376. package/src/serve/worker/client/firestore-refs.ts +226 -0
  377. package/src/serve/worker/client/firestore-writes.ts +232 -0
  378. package/src/serve/worker/client/handles.ts +91 -0
  379. package/src/serve/worker/client/messaging.ts +119 -0
  380. package/src/serve/worker/client/presence.ts +174 -0
  381. package/src/serve/worker/client/rtdb.ts +311 -0
  382. package/src/serve/worker/client/rules.ts +63 -0
  383. package/src/serve/worker/client/runtime-control.ts +22 -0
  384. package/src/serve/worker/client/service-worker-connection.ts +59 -0
  385. package/src/serve/worker/client/snapshots.ts +86 -0
  386. package/src/serve/worker/client/storage.ts +195 -0
  387. package/src/serve/worker/client/studio.ts +88 -0
  388. package/src/serve/worker/client.ts +57 -0
  389. package/src/serve/worker/durable-persistence.ts +137 -0
  390. package/src/serve/worker/entry.ts +223 -0
  391. package/src/serve/worker/host/admin-firestore.ts +88 -0
  392. package/src/serve/worker/host/connection.ts +179 -0
  393. package/src/serve/worker/host/core.ts +387 -0
  394. package/src/serve/worker/host/dispatch.ts +316 -0
  395. package/src/serve/worker/host/firestore-reads.ts +117 -0
  396. package/src/serve/worker/host/firestore-writes.ts +456 -0
  397. package/src/serve/worker/host/presence.ts +312 -0
  398. package/src/serve/worker/host/rtdb.ts +136 -0
  399. package/src/serve/worker/host/rules.ts +128 -0
  400. package/src/serve/worker/host/storage.ts +312 -0
  401. package/src/serve/worker/host/studio.ts +75 -0
  402. package/src/serve/worker/host/subscriptions.ts +238 -0
  403. package/src/serve/worker/host-ai.ts +165 -0
  404. package/src/serve/worker/host-auth.ts +469 -0
  405. package/src/serve/worker/host-context.ts +273 -0
  406. package/src/serve/worker/host-events.ts +94 -0
  407. package/src/serve/worker/host-messaging.ts +240 -0
  408. package/src/serve/worker/host.ts +47 -0
  409. package/src/serve/worker/index.ts +196 -0
  410. package/src/serve/worker/presence-timing.ts +15 -0
  411. package/src/serve/worker/protocol.ts +1124 -0
  412. package/src/serve/worker/retirement.ts +125 -0
  413. package/src/serve/worker/serve-init.ts +605 -0
  414. package/src/serve/worker/service-worker-channel.ts +33 -0
  415. package/src/serve/worker/service-worker-relay.ts +86 -0
  416. package/src/serve/writer-lock.ts +53 -0
  417. package/src/verify/cases.ts +233 -0
  418. package/src/verify/fixture.ts +258 -0
  419. package/src/verify/index.ts +519 -0
  420. package/src/verify/tools.ts +108 -0
  421. package/src/version/compat-target.ts +17 -0
  422. package/src/vite.ts +18 -0
  423. package/dist/serve/docs-ui/docs/pyric-firestore-values-reference-api/index.html +0 -31
  424. package/dist/serve/docs-ui/docs/pyric-firestore-values-reference-api.md +0 -29
@@ -0,0 +1,519 @@
1
+ /**
2
+ * `pyric dev` SDK bundler — builds the browser-standalone ESM bundles the
3
+ * import map points `firebase/*` at (`/__pyric/sdk/*.js`).
4
+ *
5
+ * esbuild (a real dependency — the `pyric` bin runs under node via npx, so
6
+ * Bun.build is unavailable) bundles the wrapper entries in `entries/` with
7
+ * SPLITTING so the shared runtime chunk holds exactly ONE sandbox per page:
8
+ * both the auth and firestore bundles must close over the same instance.
9
+ *
10
+ * Two bundler plugins make pyric browser-standalone:
11
+ * - `pyric/*` resolver — resolves `pyric/...` specifiers from THIS package's
12
+ * dependency context (entries may live in a temp dir that lacks pyric).
13
+ * - node-builtin shims (`url`/`fs`/`path`) — still load-bearing despite the
14
+ * Phase U resolver split: `pyric/firestore` re-exports the agent tool
15
+ * factories (`firestore/index.js` → `rules/tools.js`), and tools.js
16
+ * statically imports the NODE module resolver. The browser path never
17
+ * calls into disk reads, so benign shims suffice. Droppable only when
18
+ * pyric breaks that static chain (tracked in #553).
19
+ *
20
+ * Output is cached under `~/.pyric/serve-cache/<pyric-version>-<hash>/` so a
21
+ * warm `pyric dev` start skips the (~seconds) bundle step. `--no-cache`
22
+ * bypasses.
23
+ */
24
+ import { createHash } from 'node:crypto';
25
+ import { mkdirSync, mkdtempSync, readFileSync, readdirSync, existsSync, rmSync, writeFileSync } from 'node:fs';
26
+ import { homedir } from 'node:os';
27
+ import { dirname, join, relative, sep } from 'node:path';
28
+ import { fileURLToPath } from 'node:url';
29
+ import * as esbuild from 'esbuild';
30
+
31
+ /** Modules the import map serves. Keys are the bare specifiers app code uses. */
32
+ export const SDK_MODULES = [
33
+ 'firebase/ai',
34
+ 'firebase/app',
35
+ 'firebase/auth',
36
+ 'firebase/firestore',
37
+ 'firebase/database',
38
+ 'firebase/messaging',
39
+ 'firebase/messaging/sw',
40
+ 'firebase/storage',
41
+ ] as const;
42
+
43
+ /**
44
+ * The wrapper entries shipped with @pyric/cli, located relative to this
45
+ * module: compiled `.js` siblings when running from dist (npx install), `.ts`
46
+ * sources in the workspace (tests / dev). esbuild bundles either.
47
+ */
48
+ export function defaultSdkEntries(): Record<string, string> {
49
+ const here = dirname(fileURLToPath(import.meta.url));
50
+ const pick = (name: string): string => {
51
+ const js = join(here, 'entries', `${name}.js`);
52
+ if (existsSync(js)) return js;
53
+ const ts = join(here, 'entries', `${name}.ts`);
54
+ if (existsSync(ts)) return ts;
55
+ throw new Error(`pyric dev: missing SDK entry '${name}' next to ${here}`);
56
+ };
57
+ return {
58
+ ai: pick('ai'),
59
+ app: pick('app'),
60
+ auth: pick('auth'),
61
+ firestore: pick('firestore'),
62
+ database: pick('database'),
63
+ messaging: pick('messaging'),
64
+ 'messaging-sw': pick('messaging-sw'),
65
+ storage: pick('storage'),
66
+ init: pick('init'),
67
+ };
68
+ }
69
+
70
+ /**
71
+ * Resolve the built Studio app dir (the plugin's `ui` option + the CLI's
72
+ * `--ui`). Reached by file path so @pyric/cli never imports `@pyric/studio`:
73
+ * the packaged location (`dist/serve/studio-ui`, copied at build) or, in the
74
+ * monorepo, the sibling studio build. Null when neither exists. The standalone
75
+ * binary embeds these same bytes instead (see `standalone-assets.ts`).
76
+ */
77
+ export function resolveStudioUiDir(): string | null {
78
+ const candidates = [
79
+ new URL('./studio-ui/', import.meta.url),
80
+ new URL('../../../studio/dist/app/', import.meta.url),
81
+ ];
82
+ for (const candidate of candidates) {
83
+ const dir = fileURLToPath(candidate);
84
+ if (existsSync(dir)) return dir;
85
+ }
86
+ return null;
87
+ }
88
+
89
+ /**
90
+ * Resolve the built docs site dir (site-docs). `pyric dev --ui` embeds it so
91
+ * the Studio Docs tab has local docs without the hosted site. Built with base
92
+ * `/__pyric/ui/` (see scripts/build.sh) so every page/asset/twin URL resolves
93
+ * under the CLI mount: pages at `/__pyric/ui/docs/<slug>/`, assets at
94
+ * `/__pyric/ui/_astro/*`, the search index at `/__pyric/ui/docs/index.json`,
95
+ * and the shell chrome's tab links back at `/__pyric/ui/<tab>`. The dir holds
96
+ * the site-docs `dist/` verbatim: `docs/`, `_astro/`, `index.html`, `llms.txt`.
97
+ */
98
+ export function resolveDocsUiDir(): string | null {
99
+ const candidates = [
100
+ new URL('./docs-ui/', import.meta.url),
101
+ ];
102
+ for (const candidate of candidates) {
103
+ const dir = fileURLToPath(candidate);
104
+ if (existsSync(dir)) return dir;
105
+ }
106
+ return null;
107
+ }
108
+
109
+ // ─── pyric dist discovery ─────────────────────────────────────────────
110
+
111
+ /** Locate the installed pyric package root (works in the workspace and when
112
+ * @pyric/cli is npm-installed — pyric is a direct dependency). */
113
+ export function pyricPackageRoot(): string {
114
+ // Resolve a real exported subpath, then walk up to the package root.
115
+ const entry = fileURLToPath(import.meta.resolve('pyric/firestore'));
116
+ let dir = dirname(entry);
117
+ while (dir.includes(sep) && !existsSync(join(dir, 'package.json'))) {
118
+ dir = dirname(dir);
119
+ }
120
+ const pkg = JSON.parse(readFileSync(join(dir, 'package.json'), 'utf8')) as { name?: string };
121
+ if (pkg.name !== 'pyric') {
122
+ throw new Error(`pyric dev: expected to resolve the pyric package, found '${pkg.name}' at ${dir}`);
123
+ }
124
+ return dir;
125
+ }
126
+
127
+ export function pyricVersion(root: string = pyricPackageRoot()): string {
128
+ const pkg = JSON.parse(readFileSync(join(root, 'package.json'), 'utf8')) as { version?: string };
129
+ return pkg.version ?? '0.0.0';
130
+ }
131
+
132
+ // ─── plugins ──────────────────────────────────────────────────────────
133
+
134
+ /** Resolve `pyric/*` specifiers from THIS package's own dependency context
135
+ * (`import.meta.resolve`) rather than from the entry file's directory —
136
+ * entries may live in a temp dir or the user's project, neither of which
137
+ * has pyric in scope; @pyric/cli always does (direct dependency). */
138
+ function pyricResolvePlugin(): esbuild.Plugin {
139
+ return {
140
+ name: 'pyric-serve-resolve-pyric',
141
+ setup(build) {
142
+ build.onResolve({ filter: /^pyric(\/|$)/ }, (args) => ({
143
+ path: fileURLToPath(import.meta.resolve(args.path)),
144
+ }));
145
+ },
146
+ };
147
+ }
148
+
149
+ /** The node builtins pyric's browser graph reaches for (via the rules module
150
+ * resolver). Bare and `node:`-prefixed both match. */
151
+ export const NODE_BUILTIN_RE = /^(node:)?(url|fs|path)$/;
152
+
153
+ /** Benign browser shim SOURCE per builtin (see header — droppable only when
154
+ * pyric breaks the `firestore/index → rules/tools → modules/resolver` static
155
+ * chain, #553). Exported so the Vite plugin (`./vite`) reuses the SAME shims
156
+ * through its own resolveId/load + optimizeDeps esbuild pass, instead of
157
+ * re-deriving them. */
158
+ export const NODE_BUILTIN_SHIMS: Record<string, string> = {
159
+ fs: `const no = () => { throw new Error('pyric dev: fs is not available in the browser'); };
160
+ export const readFileSync = no; export const existsSync = () => false;
161
+ export const readdirSync = no; export const writeFileSync = no; export const mkdirSync = no;
162
+ export default { readFileSync, existsSync, readdirSync, writeFileSync, mkdirSync };`,
163
+ url: `export const fileURLToPath = (u) => String(u).replace('file://', '');
164
+ export const pathToFileURL = (p) => new URL('file://' + p);
165
+ export default { fileURLToPath, pathToFileURL };`,
166
+ path: `export const join = (...p) => p.join('/');
167
+ export const dirname = (p) => p.split('/').slice(0, -1).join('/') || '/';
168
+ export const resolve = (...p) => p.join('/');
169
+ export const basename = (p) => p.split('/').pop() ?? '';
170
+ export default { join, dirname, resolve, basename };`,
171
+ };
172
+
173
+ function nodeShimPlugin(): esbuild.Plugin {
174
+ return {
175
+ name: 'pyric-serve-node-shims',
176
+ setup(build) {
177
+ build.onResolve({ filter: NODE_BUILTIN_RE }, (args) => ({
178
+ path: args.path.replace(/^node:/, ''),
179
+ namespace: 'pyric-node-shim',
180
+ }));
181
+ build.onLoad({ filter: /.*/, namespace: 'pyric-node-shim' }, (args) => ({
182
+ contents: NODE_BUILTIN_SHIMS[args.path]!,
183
+ loader: 'js',
184
+ }));
185
+ },
186
+ };
187
+ }
188
+
189
+ // ─── cache ────────────────────────────────────────────────────────────
190
+
191
+ export interface BundleOptions {
192
+ /** Entry files (the wrapper modules in `entries/`), keyed by the served
193
+ * basename (`auth` → `/__pyric/sdk/auth.js`). */
194
+ entries: Record<string, string>;
195
+ /** Bypass the cache and build an immutable, process-independent generation. */
196
+ noCache?: boolean;
197
+ /** Override the cache root (tests). Default `~/.pyric/serve-cache`. */
198
+ cacheRoot?: string;
199
+ minify?: boolean;
200
+ }
201
+
202
+ export interface BundleResult {
203
+ outDir: string;
204
+ /** Served basename → absolute file path (entry bundles + shared chunks). */
205
+ files: string[];
206
+ cached: boolean;
207
+ /** Release a bypass generation after its server stops. Cache generations
208
+ * deliberately remain durable, so their disposer is a no-op. */
209
+ dispose(): void;
210
+ }
211
+
212
+ /** Bump when bundler logic changes in a way that must invalidate caches. */
213
+ const BUNDLER_REV = 9; // 9: cache keys also cover dependency-resolution metadata
214
+
215
+ const RESOLUTION_FILES = [
216
+ 'bun.lock',
217
+ 'bun.lockb',
218
+ 'package-lock.json',
219
+ 'pnpm-lock.yaml',
220
+ 'yarn.lock',
221
+ ] as const;
222
+
223
+ function nearestPackageRoot(start: string): string | undefined {
224
+ let dir = start;
225
+ while (true) {
226
+ if (existsSync(join(dir, 'package.json'))) return dir;
227
+ const parent = dirname(dir);
228
+ if (parent === dir) return undefined;
229
+ dir = parent;
230
+ }
231
+ }
232
+
233
+ /**
234
+ * Hash package-manager identity separately from executable source bytes.
235
+ * Workspace dependency upgrades commonly change package.json/bun.lock before
236
+ * the prerelease package version moves; installed packages get the same signal
237
+ * from their consumer lockfile. Package managers own third-party byte
238
+ * integrity, so their manifests/locks are the supported cache boundary.
239
+ */
240
+ function packageResolutionHash(graphRoot: string, pyricRoot: string): string {
241
+ const h = createHash('sha256');
242
+ const cliRoot = nearestPackageRoot(graphRoot);
243
+ for (const [label, root] of [
244
+ ['cli', cliRoot],
245
+ ['pyric', pyricRoot],
246
+ ] as const) {
247
+ if (!root) continue;
248
+ const manifest = join(root, 'package.json');
249
+ if (!existsSync(manifest)) continue;
250
+ h.update(label);
251
+ h.update(readFileSync(manifest));
252
+ }
253
+
254
+ let dir = cliRoot ?? graphRoot;
255
+ while (true) {
256
+ for (const name of RESOLUTION_FILES) {
257
+ const file = join(dir, name);
258
+ if (!existsSync(file)) continue;
259
+ h.update(name);
260
+ h.update(readFileSync(file));
261
+ }
262
+ const parent = dirname(dir);
263
+ if (parent === dir) break;
264
+ dir = parent;
265
+ }
266
+ return h.digest('hex');
267
+ }
268
+
269
+ export function cacheKey(
270
+ opts: BundleOptions,
271
+ version: string,
272
+ graphRoot = dirname(dirname(fileURLToPath(import.meta.url))),
273
+ pyricRoot = pyricPackageRoot(),
274
+ ): string {
275
+ const h = createHash('sha256');
276
+ h.update(`rev${BUNDLER_REV}`);
277
+ h.update(version);
278
+ // Hash the WHOLE local CLI graph, not just entries or entries/ siblings.
279
+ // Page adapters import worker/client, worker/protocol, bridge, and verify
280
+ // leaves outside that directory; missing one can pair a stale page client
281
+ // with a freshly rebuilt worker. The installed pyric implementation is
282
+ // hashed separately: workspace and prerelease builds can change dist bytes
283
+ // without changing package.json's version.
284
+ h.update(sourceTreeHash(graphRoot));
285
+ h.update(sourceTreeHash(join(pyricRoot, 'dist')));
286
+ h.update(packageResolutionHash(graphRoot, pyricRoot));
287
+ for (const [name, file] of Object.entries(opts.entries).sort()) {
288
+ h.update(name);
289
+ h.update(readFileSync(file, 'utf8'));
290
+ }
291
+ return `${version}-${h.digest('hex').slice(0, 12)}`;
292
+ }
293
+
294
+ // ─── the bundle step ──────────────────────────────────────────────────
295
+
296
+ export async function bundleSdk(opts: BundleOptions): Promise<BundleResult> {
297
+ const root = pyricPackageRoot();
298
+ const version = pyricVersion(root);
299
+ const key = cacheKey(opts, version);
300
+ const cacheRoot = opts.cacheRoot ?? join(homedir(), '.pyric', 'serve-cache');
301
+ mkdirSync(cacheRoot, { recursive: true });
302
+ // A running server keeps serving this directory after bundleSdk returns.
303
+ // Therefore `--no-cache` must never overwrite the deterministic warm-cache
304
+ // path: another concurrent `pyric dev --no-cache` could otherwise replace
305
+ // an entry and its shared chunks between browser requests, splitting
306
+ // singleton module state (notably the app registry). Each bypass build gets
307
+ // an immutable generation; the OS-level mkdir primitive makes concurrent
308
+ // callers distinct without coordination.
309
+ const generationRoot = opts.noCache
310
+ ? mkdtempSync(join(cacheRoot, `.no-cache-${key}-`))
311
+ : join(cacheRoot, key);
312
+ const outDir = join(generationRoot, 'sdk');
313
+ const dispose = opts.noCache
314
+ ? () => rmSync(generationRoot, { recursive: true, force: true })
315
+ : () => {};
316
+
317
+ if (!opts.noCache && existsSync(join(outDir, '.complete'))) {
318
+ const files = (readdirSync(outDir) as string[])
319
+ .filter((f) => f.endsWith('.js'))
320
+ .map((f) => join(outDir, f));
321
+ return { outDir, files, cached: true, dispose };
322
+ }
323
+
324
+ mkdirSync(outDir, { recursive: true });
325
+ try {
326
+ const result = await esbuild.build({
327
+ entryPoints: Object.fromEntries(
328
+ Object.entries(opts.entries).map(([name, file]) => [name, file]),
329
+ ),
330
+ outdir: outDir,
331
+ bundle: true,
332
+ format: 'esm',
333
+ platform: 'browser',
334
+ splitting: true,
335
+ sourcemap: 'linked',
336
+ minify: opts.minify ?? true,
337
+ logLevel: 'silent',
338
+ // Provenance: any stack frame or "view source" into these bundles must
339
+ // self-identify as the sandbox shim, not the real Firebase SDK.
340
+ chunkNames: 'pyric-sandbox-[hash]',
341
+ banner: {
342
+ js: '/* pyric dev: pyric sandbox shim serving firebase/* — NOT the real Firebase SDK */',
343
+ },
344
+ plugins: [pyricResolvePlugin(), nodeShimPlugin()],
345
+ });
346
+ if (result.errors.length > 0) {
347
+ throw new Error(
348
+ `pyric dev: SDK bundle failed:\n${result.errors.map((e) => e.text).join('\n')}`,
349
+ );
350
+ }
351
+ writeFileSync(join(outDir, '.complete'), new Date().toISOString());
352
+ const files = (readdirSync(outDir) as string[])
353
+ .filter((f) => f.endsWith('.js'))
354
+ .map((f) => join(outDir, f));
355
+ return { outDir, files, cached: false, dispose };
356
+ } catch (error) {
357
+ dispose();
358
+ throw error;
359
+ }
360
+ }
361
+
362
+ // ─── the SharedWorker bundle (Phase 3c.A) ─────────────────────────────────
363
+
364
+ /**
365
+ * Locate the SharedWorker entry (`worker/entry.ts`) — compiled `.js` sibling
366
+ * from dist, `.ts` source in the workspace. Same dual-resolution as
367
+ * `defaultSdkEntries`.
368
+ */
369
+ export function workerEntryPath(): string {
370
+ const here = dirname(fileURLToPath(import.meta.url));
371
+ const js = join(here, 'worker', 'entry.js');
372
+ if (existsSync(js)) return js;
373
+ const ts = join(here, 'worker', 'entry.ts');
374
+ if (existsSync(ts)) return ts;
375
+ throw new Error(`pyric dev: missing SharedWorker entry next to ${here}`);
376
+ }
377
+
378
+ export interface WorkerBundleOptions {
379
+ /** Where to write `worker.js` — normally the SDK bundle's `outDir` so the
380
+ * existing `/__pyric/sdk/<file>` route serves it with no extra wiring. */
381
+ outDir: string;
382
+ /** Bypass + overwrite the cached worker bundle. */
383
+ noCache?: boolean;
384
+ minify?: boolean;
385
+ /** Test seam for proving epoch identity from an isolated executable graph. */
386
+ entryPath?: string;
387
+ }
388
+
389
+ const WORKER_EPOCH_PLACEHOLDER = 'PYRIC_EPOCH_HERE';
390
+ const WORKER_EPOCH_FILE = '.worker-epoch';
391
+
392
+ /** Read the executable epoch written alongside a completed worker bundle. */
393
+ function readWorkerBundleEpoch(outDir: string): string {
394
+ const epoch = readFileSync(join(outDir, WORKER_EPOCH_FILE), 'utf8').trim();
395
+ if (!/^[a-f0-9]{16}$/.test(epoch)) {
396
+ throw new Error(`pyric dev: invalid SharedWorker epoch in ${join(outDir, WORKER_EPOCH_FILE)}`);
397
+ }
398
+ return epoch;
399
+ }
400
+
401
+ /**
402
+ * Conservative worker CACHE key. The worker bundle shares the SDK `outDir`
403
+ * (keyed by SDK entries), which does not cover its host graph, so this broad
404
+ * source-tree hash decides when esbuild must run again. It is intentionally NOT
405
+ * the user-facing worker epoch: unrelated CLI changes may invalidate this cache,
406
+ * while the epoch returned by {@link bundleWorker} is derived from the executable output and only
407
+ * then decides whether a running SharedWorker needs replacement.
408
+ */
409
+ export function workerSourceHash(
410
+ pyricRoot = pyricPackageRoot(),
411
+ graphRoot = dirname(dirname(fileURLToPath(import.meta.url))),
412
+ ): string {
413
+ const h = createHash('sha256');
414
+ h.update(`rev${BUNDLER_REV}`);
415
+ h.update(pyricVersion(pyricRoot));
416
+ h.update(sourceTreeHash(join(pyricRoot, 'dist')));
417
+ h.update(sourceTreeHash(graphRoot));
418
+ h.update(packageResolutionHash(graphRoot, pyricRoot));
419
+ return h.digest('hex').slice(0, 16);
420
+ }
421
+
422
+ /** Hash every JS/TS source below a directory, including split host/client leaves. */
423
+ export function sourceTreeHash(root: string): string {
424
+ const files: string[] = [];
425
+ const visit = (dir: string): void => {
426
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
427
+ const path = join(dir, entry.name);
428
+ if (entry.isDirectory()) visit(path);
429
+ else if (entry.isFile() && (entry.name.endsWith('.ts') || entry.name.endsWith('.js'))) {
430
+ files.push(path);
431
+ }
432
+ }
433
+ };
434
+ visit(root);
435
+ const h = createHash('sha256');
436
+ for (const file of files.sort()) {
437
+ h.update(relative(root, file).split(sep).join('/'));
438
+ h.update(readFileSync(file, 'utf8'));
439
+ }
440
+ return h.digest('hex');
441
+ }
442
+
443
+ /**
444
+ * Bundle the SharedWorker entry into a single classic worker script served at
445
+ * `/__pyric/sdk/worker.js`.
446
+ *
447
+ * WHY A SEPARATE BUILD (not part of `bundleSdk`'s splitting graph): a
448
+ * SharedWorker script is loaded by URL into its OWN realm — it cannot share
449
+ * the page's runtime chunk. So it is bundled standalone: `splitting: false`,
450
+ * `format: 'iife'` (the client opens it with `new SharedWorker(url, { type:
451
+ * 'classic' })`), everything inlined into one file. It reuses the same three
452
+ * browser-standalone plugins as the SDK bundle (pyric-resolve + node-shims +
453
+ * firebase-stub) so `pyric/*`, node builtins, and the `firebase/*` peer stubs
454
+ * resolve identically.
455
+ *
456
+ * NOTE: the worker legitimately holds the whole sandbox engine — it IS the
457
+ * backend — so this bundle is large, but it loads ONCE per origin (not per
458
+ * page). Trimming node/admin dead paths is tracked as Phase 3b.
459
+ *
460
+ * The `.worker-complete` marker is independent of `bundleSdk`'s `.complete`,
461
+ * while `cacheKey()` includes the complete CLI source graph. A worker/client
462
+ * or protocol edit therefore invalidates the shared SDK generation too, so
463
+ * the page and worker halves cannot come from different source generations.
464
+ */
465
+ export interface WorkerBundleResult {
466
+ outFile: string;
467
+ epoch: string;
468
+ }
469
+
470
+ export async function bundleWorker(opts: WorkerBundleOptions): Promise<WorkerBundleResult> {
471
+ const outFile = join(opts.outDir, 'worker.js');
472
+ const marker = join(opts.outDir, '.worker-complete');
473
+ const hash = workerSourceHash();
474
+ if (
475
+ !opts.noCache &&
476
+ existsSync(outFile) &&
477
+ existsSync(marker) &&
478
+ existsSync(join(opts.outDir, WORKER_EPOCH_FILE)) &&
479
+ readFileSync(marker, 'utf8') === hash
480
+ ) {
481
+ return { outFile, epoch: readWorkerBundleEpoch(opts.outDir) };
482
+ }
483
+
484
+ mkdirSync(opts.outDir, { recursive: true });
485
+ const root = pyricPackageRoot();
486
+ const result = await esbuild.build({
487
+ entryPoints: [opts.entryPath ?? workerEntryPath()],
488
+ outfile: outFile,
489
+ bundle: true,
490
+ format: 'iife',
491
+ platform: 'browser',
492
+ splitting: false,
493
+ sourcemap: 'linked',
494
+ minify: opts.minify ?? true,
495
+ logLevel: 'silent',
496
+ // Reserve a fixed-width self-version slot. After esbuild writes the actual
497
+ // executable graph, we hash that canonical output and replace this token
498
+ // with the resulting epoch (same width, so the sourcemap stays aligned).
499
+ define: { __PYRIC_WORKER_VERSION__: JSON.stringify(WORKER_EPOCH_PLACEHOLDER) },
500
+ banner: {
501
+ js: '/* pyric dev: SharedWorker sandbox host serving firebase/* — NOT the real Firebase SDK */',
502
+ },
503
+ plugins: [pyricResolvePlugin(), nodeShimPlugin()],
504
+ });
505
+ if (result.errors.length > 0) {
506
+ throw new Error(
507
+ `pyric dev: SharedWorker bundle failed:\n${result.errors.map((e) => e.text).join('\n')}`,
508
+ );
509
+ }
510
+ const canonicalSource = readFileSync(outFile, 'utf8');
511
+ if (!canonicalSource.includes(WORKER_EPOCH_PLACEHOLDER)) {
512
+ throw new Error('pyric dev: SharedWorker bundle omitted its epoch placeholder');
513
+ }
514
+ const epoch = createHash('sha256').update(canonicalSource).digest('hex').slice(0, 16);
515
+ writeFileSync(outFile, canonicalSource.replaceAll(WORKER_EPOCH_PLACEHOLDER, epoch));
516
+ writeFileSync(join(opts.outDir, WORKER_EPOCH_FILE), epoch);
517
+ writeFileSync(marker, hash);
518
+ return { outFile, epoch };
519
+ }
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Capture store for `pyric dev --capture` — the write-side of the
3
+ * `pyric verify` loop.
4
+ *
5
+ * `pyric dev` captures the in-page sandbox session (history + snapshot +
6
+ * rules) and pushes it here whenever the sandbox changes. `pyric verify`
7
+ * (no positional arg) reads `SERVE_CAPTURE_PATH` and replays it against
8
+ * your current rules to surface real divergences. The loop is:
9
+ *
10
+ * pyric dev → use your app → pyric verify
11
+ *
12
+ * The body is stored VERBATIM (the page sends its own JSON.stringify'd
13
+ * fixture — we never re-serialize it so no wrapper-type drift is
14
+ * introduced here). The path is intentionally next to the persist store
15
+ * (.pyric/state/…) so it fits the same .gitignore pattern and feels like
16
+ * one artifact family.
17
+ */
18
+ import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
19
+ import { dirname, join } from 'node:path';
20
+
21
+ /** Relative path (from project root) where the serve capture lives.
22
+ * Mirrors `SERVE_CAPTURE_PATH` in `verify.ts` — keep in lockstep. */
23
+ export const CAPTURE_RELATIVE_PATH = join('.pyric', 'last-session.json');
24
+
25
+ export interface CaptureStore {
26
+ /** Absolute path of the capture file (`<projectDir>/.pyric/last-session.json`). */
27
+ path: string;
28
+ /**
29
+ * Write the fixture JSON verbatim. Creates `.pyric/` if absent.
30
+ * The body is the page's own JSON-serialized fixture; we store it
31
+ * as-is so `pyric verify` can hand it straight to the replay engine.
32
+ */
33
+ write(fixtureJson: string): void;
34
+ /**
35
+ * Read back the last-written fixture JSON verbatim, or `null` when the
36
+ * capture file does not exist yet (nothing captured this run). Powers
37
+ * GET /__pyric/capture, which the served SharedWorker fetches on boot to
38
+ * re-hydrate its in-memory event history after a worker death.
39
+ */
40
+ read(): string | null;
41
+ }
42
+
43
+ /**
44
+ * Create a capture store rooted at `projectDir`. Returns a tiny sink that
45
+ * mirrors the minimal shape of `state-store.ts`: a `path` for banner
46
+ * reporting and a `write` for the route handler.
47
+ */
48
+ export function createCaptureStore(projectDir: string): CaptureStore {
49
+ const path = join(projectDir, CAPTURE_RELATIVE_PATH);
50
+ return {
51
+ path,
52
+ write(fixtureJson: string): void {
53
+ mkdirSync(dirname(path), { recursive: true });
54
+ writeFileSync(path, fixtureJson);
55
+ },
56
+ read(): string | null {
57
+ try {
58
+ return readFileSync(path, 'utf8');
59
+ } catch {
60
+ // ENOENT (nothing captured yet) or any read error → treat as absent.
61
+ return null;
62
+ }
63
+ },
64
+ };
65
+ }