@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,199 @@
1
+ /**
2
+ * Discovery of a RUNNING `pyric dev --bridge` — shared by the stdio MCP
3
+ * proxy (`cli/mcp-proxy.ts`) and the Node remote-sandbox client
4
+ * (`remote/index.ts`). Extracted here so both speak the SAME pointer +
5
+ * identity-pinning rules instead of drifting copies.
6
+ *
7
+ * Strategy: the `.pyric/serve.json` pointer serve writes in the project cwd
8
+ * (exact + project-correct) first, then a health probe across the scan
9
+ * window as a fallback. Degrades LEGIBLY: if no serve is found, or the
10
+ * pointed server's identity can't be matched, callers get `null` (plus a
11
+ * `log` diagnostic) — never a silent wrong-server hit.
12
+ *
13
+ * IDENTITY — the discovery pointer records the bridge's `instanceId`; a
14
+ * server is accepted only if its `/__pyric/health` reports the SAME id. Two
15
+ * sandboxes can collide on one port across loopback families (IPv4 `*:P` +
16
+ * `[::1]:P`); without this, a client locks onto whichever family answers
17
+ * first while the browser is on the other — split-brain.
18
+ */
19
+ import { existsSync, readFileSync } from 'node:fs';
20
+ import { join } from 'node:path';
21
+
22
+ /** Ports probed when the pointer is absent — serve's default scan window PLUS
23
+ * the vite-plugin default (5174), which the plain 3473+ window would miss (so a
24
+ * vite-only project was never found by scan). The standalone `pyric bridge`
25
+ * serves `/health` (not `/__pyric/health`) and writes no pointer, so it is
26
+ * registered directly via `claude mcp add`, not discovered here. */
27
+ export const SCAN_PORTS = [3473, 3474, 3475, 3476, 3477, 5174];
28
+ export const POINTER = join('.pyric', 'serve.json');
29
+
30
+ export interface HealthLite {
31
+ mode?: string;
32
+ instanceId?: string;
33
+ /** Whether a browser tab is currently connected as the sandbox peer. */
34
+ sandboxConnected?: boolean;
35
+ }
36
+
37
+ export interface Discovered {
38
+ mcpUrl: string;
39
+ /**
40
+ * The CANONICAL display URL — what a human/browser should OPEN. Comes from
41
+ * the serve-written pointer when present (`http://localhost:<port>` for the
42
+ * default host, the explicit `--host` otherwise); falls back to
43
+ * `http://localhost:<port>`. NEVER a literal loopback address: to a browser
44
+ * `localhost` and `127.0.0.1` are DIFFERENT ORIGINS (different
45
+ * SharedWorkers, so different sandboxes), and serve's banner/auto-open use
46
+ * `localhost` — guidance built from this field must land on the SAME origin.
47
+ * Node-side connectivity uses `base`/`mcpUrl` instead.
48
+ */
49
+ url: string;
50
+ /** `http://<family>:<port>` the server actually answered on. */
51
+ base: string;
52
+ /** Identity pinned at discovery. Null only when talking to an older server
53
+ * that predates the instanceId field (matching is then skipped). */
54
+ instanceId: string | null;
55
+ source: string;
56
+ }
57
+
58
+ /**
59
+ * Probe BOTH loopback families on a port and return the base that answers.
60
+ *
61
+ * Hostname-based URLs are a trap here: serve writes `http://localhost:...`
62
+ * for humans (browsers dual-stack fine), but `localhost` resolution differs
63
+ * by runtime — node/undici prefers IPv6 `::1`, and a serve under one runtime
64
+ * may bind `127.0.0.1`-only while under another binds `::1`-only. So a
65
+ * discovery client never trusts the hostname: it takes the PORT and tries
66
+ * explicit `127.0.0.1` and `[::1]`, using whichever the server is actually on.
67
+ */
68
+ export function basesForPort(port: number): string[] {
69
+ return [`http://127.0.0.1:${port}`, `http://[::1]:${port}`];
70
+ }
71
+
72
+ export async function probeHealth(base: string): Promise<HealthLite | null> {
73
+ try {
74
+ const res = await fetch(`${base}/__pyric/health`, { signal: AbortSignal.timeout(1000) });
75
+ if (res.status !== 200) return null;
76
+ const body = (await res.json()) as HealthLite;
77
+ return body.mode === 'sandbox' ? body : null;
78
+ } catch {
79
+ return null;
80
+ }
81
+ }
82
+
83
+ /**
84
+ * First loopback base on `port` whose health reports a sandbox bridge. With an
85
+ * `expectedInstanceId`, returns ONLY a family whose health identity matches —
86
+ * so when two sandboxes collide on one port across families, the client locks
87
+ * onto the one the pointer names, not merely the first to answer. (An older
88
+ * server with no `instanceId` field can't be identity-checked; matching is
89
+ * skipped in that case so the pointer still resolves.)
90
+ */
91
+ export async function healthyBase(
92
+ port: number,
93
+ expectedInstanceId?: string | null,
94
+ ): Promise<{ base: string; instanceId: string | null } | null> {
95
+ for (const base of basesForPort(port)) {
96
+ const health = await probeHealth(base);
97
+ if (!health) continue;
98
+ const id = health.instanceId ?? null;
99
+ // Healthy but the WRONG server (the cross-family squatter): skip, try next.
100
+ if (expectedInstanceId && id !== expectedInstanceId) continue;
101
+ return { base, instanceId: id };
102
+ }
103
+ return null;
104
+ }
105
+
106
+ /** Best-effort port extraction from a pointer url/mcpUrl. */
107
+ function portOf(u: string | undefined): number | null {
108
+ const m = u?.match(/:(\d{2,5})(?:\/|$)/);
109
+ return m ? Number(m[1]) : null;
110
+ }
111
+
112
+ /**
113
+ * Canonical display URL for a serve on `port`. Prefers the pointer's own
114
+ * `url` (serve writes `http://<requested host>:<port>` — `localhost` for the
115
+ * default, the explicit `--host` otherwise) so guidance shares the origin the
116
+ * banner/auto-open used; falls back to `http://localhost:<port>` (browsers
117
+ * resolve `localhost` dual-stack, so it reaches either loopback family).
118
+ */
119
+ export function canonicalServeUrl(port: number, pointerUrl?: string): string {
120
+ if (pointerUrl) {
121
+ try {
122
+ const u = new URL(pointerUrl);
123
+ if (u.protocol === 'http:' || u.protocol === 'https:') {
124
+ return `${u.protocol}//${u.host}`;
125
+ }
126
+ } catch {
127
+ /* malformed pointer url — fall through to the localhost default */
128
+ }
129
+ }
130
+ return `http://localhost:${port}`;
131
+ }
132
+
133
+ /** Find the running serve: pointer first (in `cwd`), then a port scan. The
134
+ * pointer gives the PORT and (when present) the identity; the family is
135
+ * resolved by probing, so the returned base always uses the address the
136
+ * server is actually reachable on. */
137
+ export async function discoverServe(
138
+ cwd: string,
139
+ log: (m: string) => void = () => {},
140
+ // Injectable so discovery can be tested hermetically — the default scan probes
141
+ // real localhost ports, which a test environment can't guarantee are free.
142
+ scanPorts: number[] = SCAN_PORTS,
143
+ ): Promise<Discovered | null> {
144
+ const pointerPath = join(cwd, POINTER);
145
+ if (existsSync(pointerPath)) {
146
+ try {
147
+ const p = JSON.parse(readFileSync(pointerPath, 'utf8')) as {
148
+ url?: string;
149
+ mcpUrl?: string;
150
+ port?: number;
151
+ instanceId?: string;
152
+ };
153
+ const port = p.port ?? portOf(p.mcpUrl) ?? portOf(p.url);
154
+ if (port) {
155
+ const expectedId = typeof p.instanceId === 'string' && p.instanceId ? p.instanceId : null;
156
+ const hit = await healthyBase(port, expectedId);
157
+ if (hit) {
158
+ return {
159
+ mcpUrl: `${hit.base}/__pyric/mcp`,
160
+ url: canonicalServeUrl(port, p.url),
161
+ base: hit.base,
162
+ instanceId: hit.instanceId,
163
+ source: `pointer ${POINTER}`,
164
+ };
165
+ }
166
+ // The pointer named a specific identity we could NOT find on its port:
167
+ // a different sandbox may be squatting it (cross-family collision) or
168
+ // the server stopped. Do NOT scan into a possibly-wrong server — that
169
+ // split-brain is exactly what this identity check prevents. Fail legibly.
170
+ if (expectedId) {
171
+ log(
172
+ `pointer ${POINTER} names a server (instanceId ${expectedId.slice(0, 8)}…) ` +
173
+ `that isn't answering on port ${port} — another sandbox may be squatting the ` +
174
+ `port on the other loopback family, or the server stopped. Not falling back to ` +
175
+ `a blind port scan (it could hit the wrong sandbox). Restart your dev server, ` +
176
+ `and open the exact URL it prints (http://localhost:<port> by default) — every ` +
177
+ `page must share that ONE origin, or the browser splits into separate sandboxes.`,
178
+ );
179
+ return null;
180
+ }
181
+ }
182
+ } catch {
183
+ /* stale/corrupt pointer — fall through to scan */
184
+ }
185
+ }
186
+ for (const port of scanPorts) {
187
+ const hit = await healthyBase(port);
188
+ if (hit) {
189
+ return {
190
+ mcpUrl: `${hit.base}/__pyric/mcp`,
191
+ url: canonicalServeUrl(port),
192
+ base: hit.base,
193
+ instanceId: hit.instanceId,
194
+ source: `port scan (:${port})`,
195
+ };
196
+ }
197
+ }
198
+ return null;
199
+ }
@@ -0,0 +1,292 @@
1
+ /**
2
+ * The bundle the import map serves for `firebase/ai`.
3
+ *
4
+ * DUAL-PATH (cdd-deltas #98): the FULL `pyric/ai` mirror surface
5
+ * (GenerativeModel / ChatSession / Schema / enums) always runs in-page —
6
+ * but ANSWERING follows the sandbox. When a SharedWorker is available, the
7
+ * broker + engines live in the WORKER HOST, in-process with the ONE shared
8
+ * sandbox (#98.1): `getAI` plugs a port-forwarding AnswerEngine into the
9
+ * in-page mirror, so every `generateContent` / `streamGenerateContent` /
10
+ * `countTokens` rides the worker protocol's `ai.*` ops (#98.3) and the
11
+ * worker broker's `service: 'ai'` events land on the shared event stream
12
+ * Studio consumes. Otherwise the mirror runs whole against the in-page
13
+ * sandbox — the unchanged fallback. Branch picked ONCE at load (`useWorker`).
14
+ *
15
+ * OPENAI ENGINE IN THE BROWSER (#98.2): an `engine: { kind: 'openai' }`
16
+ * config with no `baseUrl` targets serve's same-origin `/__pyric/ai-proxy`
17
+ * route (which forwards to the configured upstream, default
18
+ * `http://localhost:11434/v1`) — so a localhost Ollama needs no CORS setup
19
+ * on EITHER path. A custom AnswerEngine OBJECT can't cross the port and is
20
+ * rejected in worker mode rather than creating a second page-local backend.
21
+ * Direct/in-process `pyric/ai` sandboxes still accept custom engines.
22
+ *
23
+ * Engine config is per-sandbox (#98.4) and first-call-wins at the worker
24
+ * host. Each app has its own in-page port wrapper so deleting one app cannot
25
+ * disconnect a sibling; all wrappers still forward to that one worker broker.
26
+ * Browser-bundled by `../bundler.ts`; never imported by node-side.
27
+ */
28
+ import * as ipAi from 'pyric/ai';
29
+ import { aiErrorFromEnvelope } from 'pyric/ai/internal';
30
+ import { getAI as pyricGetAI } from 'pyric/ai';
31
+ import { createTransportAI } from 'pyric/ai/internal';
32
+ import { FirebaseError, getApp, type FirebaseApp } from 'pyric/app';
33
+ import { isSandboxAppDeleted } from 'pyric/app/internal';
34
+ import type { AiEngineConfigWire } from '../worker/protocol.js';
35
+ import { aiGenerateContent, aiCountTokens, aiStreamGenerateContent } from '../worker/client.js';
36
+ import { useWorker } from './worker-runtime.js';
37
+ import { workerClientForApp } from './app-client.js';
38
+
39
+ // ── Mirror surface — path-independent (the mirror always runs in-page) ────
40
+ export {
41
+ AIError,
42
+ AIErrorCode,
43
+ Backend,
44
+ BackendType,
45
+ GoogleAIBackend,
46
+ VertexAIBackend,
47
+ AIModel,
48
+ ChatSession,
49
+ ChatSessionBase,
50
+ GenerativeModel,
51
+ AnyOfSchema,
52
+ ArraySchema,
53
+ BooleanSchema,
54
+ IntegerSchema,
55
+ NumberSchema,
56
+ ObjectSchema,
57
+ Schema,
58
+ StringSchema,
59
+ BlockReason,
60
+ FinishReason,
61
+ FunctionCallingMode,
62
+ HarmBlockMethod,
63
+ HarmBlockThreshold,
64
+ HarmCategory,
65
+ HarmProbability,
66
+ HarmSeverity,
67
+ ImageConfigAspectRatio,
68
+ ImageConfigImageSize,
69
+ Language,
70
+ Modality,
71
+ Outcome,
72
+ POSSIBLE_ROLES,
73
+ ResponseModality,
74
+ SchemaType,
75
+ ThinkingLevel,
76
+ URLRetrievalStatus,
77
+ getGenerativeModel,
78
+ } from 'pyric/ai';
79
+
80
+ type EngineOption = NonNullable<ipAi.AIOptions['engine']>;
81
+ type AnswerTransport = Parameters<typeof createTransportAI>[2];
82
+
83
+ /** The serve proxy route the browser openai engine defaults to (#98.2). */
84
+ const AI_PROXY_PATH = '/__pyric/ai-proxy';
85
+
86
+ /** A custom AnswerEngine object (vs a plain, worker-cloneable EngineConfig). */
87
+ function isAnswerEngine(engine: EngineOption): boolean {
88
+ return typeof (engine as { generateContent?: unknown }).generateContent === 'function';
89
+ }
90
+
91
+ /**
92
+ * Derive the JSON-safe wire config that crosses the port from the app's
93
+ * `AIOptions.engine`. Predicate script matchers are functions — structured
94
+ * clone rejects them LOUDLY at send time (never silently dropped).
95
+ */
96
+ function toEngineWire(engine: EngineOption | undefined): AiEngineConfigWire | undefined {
97
+ if (!engine || isAnswerEngine(engine)) return undefined;
98
+ const config = engine as Exclude<EngineOption, { generateContent: unknown }> & { kind: string };
99
+ if (config.kind === 'openai') {
100
+ const openai = config as Extract<EngineOption, { kind: 'openai' }>;
101
+ return {
102
+ kind: 'openai',
103
+ baseUrl: openai.baseUrl ?? AI_PROXY_PATH,
104
+ ...(openai.model !== undefined ? { model: openai.model } : {}),
105
+ ...(openai.modelMap !== undefined ? { modelMap: openai.modelMap } : {}),
106
+ };
107
+ }
108
+ const scripted = config as Extract<EngineOption, { kind: 'scripted' }>;
109
+ return {
110
+ kind: 'scripted',
111
+ ...(scripted.script !== undefined
112
+ ? { script: scripted.script as unknown as Array<Record<string, unknown>> }
113
+ : {}),
114
+ };
115
+ }
116
+
117
+ /**
118
+ * The plugin-level engine (`@pyric/cli/vite`'s `ai.engine`) for the IN-PAGE
119
+ * path, injected as a synchronous global BEFORE any app code runs (the plugin's
120
+ * transformIndexHtml — a classic inline script). The served `getAI` is
121
+ * synchronous and can't await init.json, so the worker path's init.json channel
122
+ * (→ ctx.aiEngine) has this page-side twin. Undefined outside the plugin.
123
+ */
124
+ function pluginEngineWire(): AiEngineConfigWire | undefined {
125
+ return (globalThis as { __PYRIC_AI_ENGINE__?: AiEngineConfigWire }).__PYRIC_AI_ENGINE__;
126
+ }
127
+
128
+ /**
129
+ * Resolve a plugin wire engine to the mirror's `EngineOption` — an openai
130
+ * config with no `baseUrl` targets the serve proxy (mirrors resolveEngineConfig
131
+ * host-side and toEngineWire's inverse).
132
+ */
133
+ function wireToEngineOption(wire: AiEngineConfigWire): EngineOption {
134
+ if (wire.kind === 'openai') {
135
+ return {
136
+ kind: 'openai',
137
+ baseUrl: wire.baseUrl ?? AI_PROXY_PATH,
138
+ ...(wire.model !== undefined ? { model: wire.model } : {}),
139
+ ...(wire.modelMap !== undefined ? { modelMap: wire.modelMap } : {}),
140
+ } as EngineOption;
141
+ }
142
+ return {
143
+ kind: 'scripted',
144
+ ...(wire.script !== undefined
145
+ ? { script: wire.script as unknown as Extract<EngineOption, { kind: 'scripted' }>['script'] }
146
+ : {}),
147
+ } as EngineOption;
148
+ }
149
+
150
+ /** In-page path: default an openai config's absent baseUrl to the proxy. */
151
+ function withProxyDefault(options?: ipAi.AIOptions): ipAi.AIOptions | undefined {
152
+ const engine = options?.engine;
153
+ if (!engine || isAnswerEngine(engine)) return options;
154
+ const config = engine as { kind?: string; baseUrl?: string };
155
+ if (config.kind === 'openai' && config.baseUrl === undefined) {
156
+ return { ...options, engine: { ...config, baseUrl: AI_PROXY_PATH } as EngineOption };
157
+ }
158
+ return options;
159
+ }
160
+
161
+ function aiErrorFromWire(err: unknown, modelResource: string, op: string): unknown {
162
+ const envelope = (err as { aiEnvelope?: Parameters<typeof aiErrorFromEnvelope>[0] } | null)
163
+ ?.aiEnvelope;
164
+ return envelope ? aiErrorFromEnvelope(envelope, modelResource, op) : err;
165
+ }
166
+
167
+ // ── The port-forwarding AnswerEngine (worker path) ─────────────────────────
168
+
169
+ function portEngine(db: ReturnType<typeof workerClientForApp>, engineWire: AiEngineConfigWire | undefined): AnswerTransport {
170
+ const params = (model: string, request: Record<string, unknown>) => ({
171
+ model,
172
+ request,
173
+ ...(engineWire !== undefined ? { engine: engineWire } : {}),
174
+ });
175
+ const engine = {
176
+ async generateContent(req: Record<string, unknown>, model: string): Promise<Record<string, unknown>> {
177
+ try {
178
+ return await aiGenerateContent(db, params(model, req));
179
+ } catch (err) {
180
+ throw aiErrorFromWire(err, model, 'generateContent');
181
+ }
182
+ },
183
+ streamGenerateContent(req: Record<string, unknown>, model: string): AsyncIterable<Record<string, unknown>> {
184
+ const inner = aiStreamGenerateContent(db, params(model, req));
185
+ return (async function* mapped(): AsyncGenerator<Record<string, unknown>> {
186
+ try {
187
+ for await (const chunk of inner) yield chunk;
188
+ } catch (err) {
189
+ throw aiErrorFromWire(err, model, 'streamGenerateContent');
190
+ }
191
+ })();
192
+ },
193
+ async countTokens(req: Record<string, unknown>, model: string): Promise<Record<string, unknown>> {
194
+ try {
195
+ return await aiCountTokens(db, params(model, req));
196
+ } catch (err) {
197
+ throw aiErrorFromWire(err, model, 'countTokens');
198
+ }
199
+ },
200
+ };
201
+ return engine as unknown as AnswerTransport;
202
+ }
203
+
204
+ /**
205
+ * Firebase constructs an AI handle for a deleted app, but that observation
206
+ * does not authorize resurrecting the app against an in-page sandbox. Keep
207
+ * the handle constructible while every model operation stays tombstoned.
208
+ */
209
+ function deletedAppEngine(): AnswerTransport {
210
+ return {
211
+ async generateContent(): Promise<never> {
212
+ throw deletedAppError();
213
+ },
214
+ streamGenerateContent(): AsyncIterable<never> {
215
+ return (async function* tombstoned(): AsyncGenerator<never> {
216
+ throw deletedAppError();
217
+ })();
218
+ },
219
+ async countTokens(): Promise<never> {
220
+ throw deletedAppError();
221
+ },
222
+ } as AnswerTransport;
223
+ }
224
+
225
+ function deletedAppError(): FirebaseError {
226
+ return new FirebaseError(
227
+ 'app/app-deleted',
228
+ 'Firebase App has already been deleted.',
229
+ );
230
+ }
231
+
232
+ // ── getAI — worker-forwarded answering or whole-mirror in-page ─────────────
233
+ function mirrorGetAI(
234
+ app: unknown,
235
+ options?: ipAi.AIOptions,
236
+ ): ipAi.AI {
237
+ const resolved = (app ?? getApp()) as FirebaseApp;
238
+ const ai = pyricGetAI(resolved, options);
239
+ const target = (ai as unknown as {
240
+ [ipAi.TARGET_SYMBOL]?: { assertAlive?: () => void };
241
+ })[ipAi.TARGET_SYMBOL];
242
+ if (target) {
243
+ // Served entries are separately bundled, so the in-page AI mirror cannot
244
+ // rely on sharing pyric/app's private adapter singleton. Stamp the target
245
+ // with the served registry's per-app deletion guard instead. The target is
246
+ // per app handle even though its broker is shared by the sandbox.
247
+ target.assertAlive = () => {
248
+ if (isSandboxAppDeleted(resolved)) throw deletedAppError();
249
+ };
250
+ }
251
+ return ai;
252
+ }
253
+
254
+ export const getAI = (
255
+ useWorker
256
+ ? (app?: unknown, options?: ipAi.AIOptions) => {
257
+ const resolved = (app ?? getApp()) as FirebaseApp;
258
+ const engine = options?.engine;
259
+ const assertAlive = () => {
260
+ if (isSandboxAppDeleted(resolved)) throw deletedAppError();
261
+ };
262
+ if (isSandboxAppDeleted(resolved)) {
263
+ return createTransportAI(
264
+ resolved,
265
+ options,
266
+ deletedAppEngine(),
267
+ assertAlive,
268
+ );
269
+ }
270
+ if (engine && isAnswerEngine(engine)) {
271
+ throw new ipAi.AIError(
272
+ ipAi.AIErrorCode.UNSUPPORTED,
273
+ 'Custom AnswerEngine objects cannot run in SharedWorker mode. ' +
274
+ "Use a structured { kind: 'scripted' } or { kind: 'openai' } engine config.",
275
+ );
276
+ }
277
+ return createTransportAI(
278
+ resolved,
279
+ options,
280
+ portEngine(workerClientForApp(resolved), toEngineWire(engine)),
281
+ assertAlive,
282
+ );
283
+ }
284
+ : (app?: unknown, options?: ipAi.AIOptions) => {
285
+ // Plugin-level engine wins over an app-code `getAI` engine (mirrors the
286
+ // worker host's ctx.aiEngine precedence). Absent ⇒ app options as-is.
287
+ const pluginWire = pluginEngineWire();
288
+ return pluginWire
289
+ ? mirrorGetAI(app, { ...options, engine: wireToEngineOption(pluginWire) })
290
+ : mirrorGetAI(app, withProxyDefault(options));
291
+ }
292
+ ) as typeof pyricGetAI;
@@ -0,0 +1,4 @@
1
+ /** Realm-local placeholder attached to app handles; served services use the worker transport. */
2
+ import { initializeSandbox } from 'pyric/sandbox';
3
+
4
+ export const sandbox = initializeSandbox();
@@ -0,0 +1,26 @@
1
+ /** Per-app SharedWorker ports: distinct Firebase service containers, one worker backend. */
2
+ import type { FirebaseApp } from 'pyric/app';
3
+ import { registerAppCleanup } from 'pyric/app/internal';
4
+ import type { ClientDb } from '../worker/client.js';
5
+ import type { InboundMessage } from '../worker/protocol.js';
6
+ import { disconnectClient } from '../worker/client/disconnect.js';
7
+ import { openWorkerDb } from './worker-runtime.js';
8
+
9
+ const clients = new WeakMap<FirebaseApp, ClientDb>();
10
+
11
+ export function workerClientForApp(app: FirebaseApp): ClientDb {
12
+ const existing = clients.get(app);
13
+ if (existing) return existing;
14
+ let client: ClientDb | undefined;
15
+ registerAppCleanup(app, async () => {
16
+ clients.delete(app);
17
+ if (client) await disconnectClient(client);
18
+ });
19
+ client = openWorkerDb(app.name);
20
+ client.port.postMessage({
21
+ t: 'appConfig',
22
+ options: app.options as Record<string, unknown>,
23
+ } satisfies InboundMessage);
24
+ clients.set(app, client);
25
+ return client;
26
+ }
@@ -0,0 +1,47 @@
1
+ /** App-owned browser auth-session stores for the served Firebase adapters. */
2
+ import type { FirebaseApp } from 'pyric/app';
3
+ import { registerAppCleanup } from 'pyric/app/internal';
4
+ import { SessionStore, type SessionStores } from './session-store.js';
5
+
6
+ export interface AppSessionStores {
7
+ forApp(app: FirebaseApp): SessionStore;
8
+ }
9
+
10
+ /**
11
+ * Build an app-identity-keyed store registry. The durable key inside each
12
+ * SessionStore remains app-name-keyed, so deleting/recreating an app can
13
+ * restore the same session while its in-memory mode/callback state is fresh.
14
+ */
15
+ export function createAppSessionStores(stores: SessionStores): AppSessionStores {
16
+ const byApp = new WeakMap<FirebaseApp, SessionStore>();
17
+ return {
18
+ forApp(app) {
19
+ const existing = byApp.get(app);
20
+ if (existing) return existing;
21
+ const store = new SessionStore(stores, app.name);
22
+ byApp.set(app, store);
23
+ registerAppCleanup(app, () => { byApp.delete(app); });
24
+ return store;
25
+ },
26
+ };
27
+ }
28
+
29
+ function memoryStorage(): SessionStores['local'] {
30
+ const values = new Map<string, string>();
31
+ return {
32
+ getItem: (key) => values.get(key) ?? null,
33
+ setItem: (key, value) => { values.set(key, value); },
34
+ removeItem: (key) => { values.delete(key); },
35
+ };
36
+ }
37
+
38
+ const browserStores: SessionStores =
39
+ typeof localStorage !== 'undefined' && typeof sessionStorage !== 'undefined'
40
+ ? { local: localStorage, session: sessionStorage }
41
+ : { local: memoryStorage(), session: memoryStorage() };
42
+
43
+ const servedStores = createAppSessionStores(browserStores);
44
+
45
+ export function sessionStoreForApp(app: FirebaseApp): SessionStore {
46
+ return servedStores.forApp(app);
47
+ }
@@ -0,0 +1,11 @@
1
+ /** Served `firebase/app`: the exact Pyric client registry over the page runtime. */
2
+ import { bindAppRegistrySandbox } from 'pyric/app/internal';
3
+ import { sandbox } from './app-backend.js';
4
+
5
+ // Keep this entry free of dynamic imports: ServiceWorkerGlobalScope supports
6
+ // static module imports but rejects dynamic import(). The attached sandbox is
7
+ // only the app handle's realm-local placeholder; served service calls route to
8
+ // the SharedWorker (directly in Window, via BroadcastChannel in a real SW).
9
+ bindAppRegistrySandbox(sandbox);
10
+
11
+ export * from 'pyric/app';