@pyric/cli 0.1.0-alpha.19 → 0.1.0-alpha.20

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 (317) hide show
  1. package/README.md +9 -9
  2. package/dist/cli/index.d.ts +2 -2
  3. package/dist/cli/index.d.ts.map +1 -1
  4. package/dist/cli/index.js +48 -42
  5. package/dist/cli/index.js.map +1 -1
  6. package/dist/cli/mcp-proxy.js +3 -3
  7. package/dist/cli/mcp-proxy.js.map +1 -1
  8. package/dist/cli/parse-args.d.ts +1 -1
  9. package/dist/cli/parse-args.d.ts.map +1 -1
  10. package/dist/cli/parse-args.js +51 -15
  11. package/dist/cli/parse-args.js.map +1 -1
  12. package/dist/cli/pyric-config.d.ts +24 -0
  13. package/dist/cli/pyric-config.d.ts.map +1 -0
  14. package/dist/cli/pyric-config.js +28 -0
  15. package/dist/cli/pyric-config.js.map +1 -0
  16. package/dist/cli/{dev-runner.d.ts → sandbox-runner.d.ts} +33 -36
  17. package/dist/cli/sandbox-runner.d.ts.map +1 -0
  18. package/dist/cli/{dev-runner.js → sandbox-runner.js} +90 -55
  19. package/dist/cli/sandbox-runner.js.map +1 -0
  20. package/dist/cli/serve.d.ts +6 -4
  21. package/dist/cli/serve.d.ts.map +1 -1
  22. package/dist/cli/serve.js +99 -40
  23. package/dist/cli/serve.js.map +1 -1
  24. package/dist/cli/snapshot.js +5 -5
  25. package/dist/cli/snapshot.js.map +1 -1
  26. package/dist/cli/verify.js +1 -1
  27. package/dist/cli/verify.js.map +1 -1
  28. package/dist/functions-rtdb/development-runtime.js +1 -1
  29. package/dist/functions-rtdb/development-runtime.js.map +1 -1
  30. package/dist/functions-rtdb/project.js +8 -8
  31. package/dist/functions-rtdb/project.js.map +1 -1
  32. package/dist/next/guard.js +1 -1
  33. package/dist/next/guard.js.map +1 -1
  34. package/dist/remote/index.d.ts +1 -1
  35. package/dist/remote/index.js +9 -9
  36. package/dist/remote/index.js.map +1 -1
  37. package/dist/serve/bundler.js +10 -10
  38. package/dist/serve/bundler.js.map +1 -1
  39. package/dist/serve/entries/ai.d.ts.map +1 -1
  40. package/dist/serve/entries/ai.js +27 -1
  41. package/dist/serve/entries/ai.js.map +1 -1
  42. package/dist/serve/entries/auth-helper-dom.js +1 -1
  43. package/dist/serve/entries/auth-helper-dom.js.map +1 -1
  44. package/dist/serve/entries/auth-helper-runtime.js +1 -1
  45. package/dist/serve/entries/auth-helper-runtime.js.map +1 -1
  46. package/dist/serve/entries/firestore.js +3 -3
  47. package/dist/serve/entries/firestore.js.map +1 -1
  48. package/dist/serve/entries/keepalive.js +1 -1
  49. package/dist/serve/entries/keepalive.js.map +1 -1
  50. package/dist/serve/entries/runtime.js +21 -14
  51. package/dist/serve/entries/runtime.js.map +1 -1
  52. package/dist/serve/entries/storage.js +1 -1
  53. package/dist/serve/entries/storage.js.map +1 -1
  54. package/dist/serve/entries/worker-runtime.js +1 -1
  55. package/dist/serve/entries/worker-runtime.js.map +1 -1
  56. package/dist/serve/init-payload.d.ts +4 -0
  57. package/dist/serve/init-payload.d.ts.map +1 -1
  58. package/dist/serve/namespace.d.ts +6 -0
  59. package/dist/serve/namespace.d.ts.map +1 -1
  60. package/dist/serve/namespace.js +36 -5
  61. package/dist/serve/namespace.js.map +1 -1
  62. package/dist/serve/rules.d.ts.map +1 -1
  63. package/dist/serve/rules.js +44 -21
  64. package/dist/serve/rules.js.map +1 -1
  65. package/dist/serve/runtime/chip.d.ts.map +1 -1
  66. package/dist/serve/runtime/chip.js +32 -1
  67. package/dist/serve/runtime/chip.js.map +1 -1
  68. package/dist/serve/runtime/manifest.d.ts +1 -1
  69. package/dist/serve/runtime/manifest.d.ts.map +1 -1
  70. package/dist/serve/runtime/manifest.js +1 -1
  71. package/dist/serve/runtime/manifest.js.map +1 -1
  72. package/dist/serve/runtime/status.d.ts.map +1 -1
  73. package/dist/serve/runtime/status.js +23 -0
  74. package/dist/serve/runtime/status.js.map +1 -1
  75. package/dist/serve/sandbox-session.d.ts +1 -0
  76. package/dist/serve/sandbox-session.d.ts.map +1 -1
  77. package/dist/serve/sandbox-session.js +10 -4
  78. package/dist/serve/sandbox-session.js.map +1 -1
  79. package/dist/serve/server.d.ts +7 -0
  80. package/dist/serve/server.d.ts.map +1 -1
  81. package/dist/serve/server.js +15 -1
  82. package/dist/serve/server.js.map +1 -1
  83. package/dist/serve/site-ui/404.html +1 -1
  84. package/dist/serve/site-ui/_astro/{RulesCodeEditor.Bl-XMkhD.js → RulesCodeEditor.XtDmHOlN.js} +1 -1
  85. package/dist/serve/site-ui/_astro/_slug_.Da5VLhW1.css +1 -0
  86. package/dist/serve/site-ui/_astro/_studio_.BnfYCKoI.css +1 -0
  87. package/dist/serve/site-ui/_astro/app.B52xr-DK.js +1 -0
  88. package/dist/serve/site-ui/_astro/app.DWBraPUw.js +12 -0
  89. package/dist/serve/site-ui/_astro/app.pMSnN6Qw.css +1 -0
  90. package/dist/serve/site-ui/_astro/{chess-showcase.Dh5D8kqe.js → chess-showcase.B72XYeoh.js} +1 -1
  91. package/dist/serve/site-ui/_astro/{example-runtime.CuyAjjiG.js → example-runtime.C25_lx6Z.js} +1 -1
  92. package/dist/serve/site-ui/_astro/index.Tb-31efc.css +1 -0
  93. package/dist/serve/site-ui/_astro/index.UkCBr-_0.css +1 -0
  94. package/dist/serve/site-ui/_astro/{sandbox-controls.BP4M5f1M.js → sandbox-controls.DEpnKg-O.js} +11 -11
  95. package/dist/serve/site-ui/_astro/{seed.CSUkloMi.js → seed.Co0yUJY5.js} +1 -1
  96. package/dist/serve/site-ui/_astro/{writes.C57paAj2.js → writes.rpjrFzmu.js} +7 -4
  97. package/dist/serve/site-ui/auth/index.html +5 -3
  98. package/dist/serve/site-ui/david-east.jpg +0 -0
  99. package/dist/serve/site-ui/docs/_rhythm/index.html +6 -3
  100. package/dist/serve/site-ui/docs/_rhythm.md +1 -1
  101. package/dist/serve/site-ui/docs/agent/set-up-your-agent/index.html +18 -15
  102. package/dist/serve/site-ui/docs/agent/set-up-your-agent.md +16 -16
  103. package/dist/serve/site-ui/docs/agent/watch-and-review/index.html +6 -5
  104. package/dist/serve/site-ui/docs/agent/watch-and-review.md +1 -5
  105. package/dist/serve/site-ui/docs/agent/work-with-an-agent/index.html +8 -5
  106. package/dist/serve/site-ui/docs/agent/work-with-an-agent.md +4 -4
  107. package/dist/serve/site-ui/docs/ai-compat/index.html +5 -2
  108. package/dist/serve/site-ui/docs/api-reference/index.html +6 -2
  109. package/dist/serve/site-ui/docs/api-reference.md +1 -0
  110. package/dist/serve/site-ui/docs/app-compat/index.html +5 -2
  111. package/dist/serve/site-ui/docs/auth-compat/index.html +5 -2
  112. package/dist/serve/site-ui/docs/build/ai-logic/index.html +5 -2
  113. package/dist/serve/site-ui/docs/build/authentication/index.html +7 -4
  114. package/dist/serve/site-ui/docs/build/authentication.md +2 -2
  115. package/dist/serve/site-ui/docs/build/cloud-firestore/index.html +5 -2
  116. package/dist/serve/site-ui/docs/build/cloud-messaging/index.html +5 -2
  117. package/dist/serve/site-ui/docs/build/cloud-storage/index.html +6 -3
  118. package/dist/serve/site-ui/docs/build/cloud-storage.md +1 -1
  119. package/dist/serve/site-ui/docs/build/realtime-database/index.html +9 -6
  120. package/dist/serve/site-ui/docs/build/realtime-database.md +4 -4
  121. package/dist/serve/site-ui/docs/conformance-scores/index.html +5 -2
  122. package/dist/serve/site-ui/docs/create-pyric-reference-api/index.html +6 -3
  123. package/dist/serve/site-ui/docs/create-pyric-reference-api.md +1 -1
  124. package/dist/serve/site-ui/docs/database-compat/index.html +5 -2
  125. package/dist/serve/site-ui/docs/examples/chess/index.html +5 -2
  126. package/dist/serve/site-ui/docs/examples/index.html +5 -2
  127. package/dist/serve/site-ui/docs/firestore-compat/index.html +5 -2
  128. package/dist/serve/site-ui/docs/functions-rtdb-compat/index.html +5 -2
  129. package/dist/serve/site-ui/docs/get-started/how-the-swap-works/index.html +9 -6
  130. package/dist/serve/site-ui/docs/get-started/how-the-swap-works.md +5 -5
  131. package/dist/serve/site-ui/docs/get-started/nextjs/index.html +9 -6
  132. package/dist/serve/site-ui/docs/get-started/nextjs.md +4 -4
  133. package/dist/serve/site-ui/docs/get-started/start-building/index.html +14 -9
  134. package/dist/serve/site-ui/docs/get-started/start-building.md +16 -10
  135. package/dist/serve/site-ui/docs/get-started/vite/index.html +6 -3
  136. package/dist/serve/site-ui/docs/get-started/vite.md +1 -1
  137. package/dist/serve/site-ui/docs/index.json +180 -150
  138. package/dist/serve/site-ui/docs/messaging-compat/index.html +5 -2
  139. package/dist/serve/site-ui/docs/observe/resolve-runtime-status/index.html +5 -2
  140. package/dist/serve/site-ui/docs/observe/see-whats-happening/index.html +6 -4
  141. package/dist/serve/site-ui/docs/observe/see-whats-happening.md +2 -4
  142. package/dist/serve/site-ui/docs/observe/shape-your-data/index.html +9 -6
  143. package/dist/serve/site-ui/docs/observe/shape-your-data.md +4 -4
  144. package/dist/serve/site-ui/docs/overview/index.html +16 -10
  145. package/dist/serve/site-ui/docs/overview.md +19 -11
  146. package/dist/serve/site-ui/docs/pyric-admin-app-reference-api/index.html +5 -2
  147. package/dist/serve/site-ui/docs/pyric-admin-auth-reference-api/index.html +5 -2
  148. package/dist/serve/site-ui/docs/pyric-admin-database-reference-api/index.html +5 -2
  149. package/dist/serve/site-ui/docs/pyric-admin-firestore-reference-api/index.html +8 -5
  150. package/dist/serve/site-ui/docs/pyric-admin-firestore-reference-api.md +3 -3
  151. package/dist/serve/site-ui/docs/pyric-admin-messaging-reference-api/index.html +5 -2
  152. package/dist/serve/site-ui/docs/pyric-admin-reference-api/index.html +501 -0
  153. package/dist/serve/site-ui/docs/pyric-admin-reference-api.md +587 -0
  154. package/dist/serve/site-ui/docs/pyric-admin-storage-reference-api/index.html +5 -2
  155. package/dist/serve/site-ui/docs/pyric-ai-reference-api/index.html +5 -2
  156. package/dist/serve/site-ui/docs/pyric-ai-scripting-reference-api/index.html +5 -2
  157. package/dist/serve/site-ui/docs/pyric-app-reference-api/index.html +5 -2
  158. package/dist/serve/site-ui/docs/pyric-auth-reference-api/index.html +5 -2
  159. package/dist/serve/site-ui/docs/pyric-cli-assurance-browser-reference-api/index.html +5 -2
  160. package/dist/serve/site-ui/docs/pyric-cli-assurance-reference-api/index.html +5 -2
  161. package/dist/serve/site-ui/docs/pyric-cli-bridge-client-reference-api/index.html +5 -2
  162. package/dist/serve/site-ui/docs/pyric-cli-bridge-reference-api/index.html +5 -2
  163. package/dist/serve/site-ui/docs/pyric-cli-conformance-browser-reference-api/index.html +5 -2
  164. package/dist/serve/site-ui/docs/pyric-cli-conformance-docs-reference-api/index.html +5 -2
  165. package/dist/serve/site-ui/docs/pyric-cli-conformance-reference-api/index.html +5 -2
  166. package/dist/serve/site-ui/docs/pyric-cli-credentials-node-reference-api/index.html +5 -2
  167. package/dist/serve/site-ui/docs/pyric-cli-discover-reference-api/index.html +5 -2
  168. package/dist/serve/site-ui/docs/pyric-cli-next-reference-api/index.html +5 -2
  169. package/dist/serve/site-ui/docs/pyric-cli-register-app-bridge-reference-api/index.html +5 -2
  170. package/dist/serve/site-ui/docs/pyric-cli-register-reference-api/index.html +5 -2
  171. package/dist/serve/site-ui/docs/pyric-cli-remote-reference-api/index.html +6 -3
  172. package/dist/serve/site-ui/docs/pyric-cli-remote-reference-api.md +1 -1
  173. package/dist/serve/site-ui/docs/pyric-cli-serve-worker-reference-api/index.html +5 -2
  174. package/dist/serve/site-ui/docs/pyric-cli-verify-reference-api/index.html +5 -2
  175. package/dist/serve/site-ui/docs/pyric-cli-vite-reference-api/index.html +5 -2
  176. package/dist/serve/site-ui/docs/pyric-database-reference-api/index.html +166 -95
  177. package/dist/serve/site-ui/docs/pyric-database-reference-api.md +107 -1
  178. package/dist/serve/site-ui/docs/pyric-firestore-reference-api/index.html +5 -2
  179. package/dist/serve/site-ui/docs/pyric-messaging-reference-api/index.html +10 -4
  180. package/dist/serve/site-ui/docs/pyric-messaging-reference-api.md +5 -2
  181. package/dist/serve/site-ui/docs/pyric-messaging-sw-reference-api/index.html +10 -4
  182. package/dist/serve/site-ui/docs/pyric-messaging-sw-reference-api.md +5 -2
  183. package/dist/serve/site-ui/docs/pyric-rules-reference-api/index.html +5 -2
  184. package/dist/serve/site-ui/docs/pyric-sandbox-database-reference-api/index.html +5 -2
  185. package/dist/serve/site-ui/docs/pyric-sandbox-firestore-reference-api/index.html +6 -3
  186. package/dist/serve/site-ui/docs/pyric-sandbox-firestore-reference-api.md +2 -2
  187. package/dist/serve/site-ui/docs/pyric-sandbox-reference-api/index.html +5 -2
  188. package/dist/serve/site-ui/docs/pyric-storage-reference-api/index.html +5 -2
  189. package/dist/serve/site-ui/docs/pyric-ui-agents-reference-api/index.html +5 -2
  190. package/dist/serve/site-ui/docs/pyric-ui-auth-hooks-reference-api/index.html +5 -2
  191. package/dist/serve/site-ui/docs/pyric-ui-auth-reference-api/index.html +5 -2
  192. package/dist/serve/site-ui/docs/pyric-ui-events-hooks-reference-api/index.html +5 -2
  193. package/dist/serve/site-ui/docs/pyric-ui-events-reference-api/index.html +5 -2
  194. package/dist/serve/site-ui/docs/pyric-ui-firestore-hooks-reference-api/index.html +5 -2
  195. package/dist/serve/site-ui/docs/pyric-ui-firestore-reference-api/index.html +5 -2
  196. package/dist/serve/site-ui/docs/pyric-ui-primitives-reference-api/index.html +5 -2
  197. package/dist/serve/site-ui/docs/pyric-ui-rtdb-reference-api/index.html +5 -2
  198. package/dist/serve/site-ui/docs/pyric-ui-rules-hooks-reference-api/index.html +5 -2
  199. package/dist/serve/site-ui/docs/pyric-ui-rules-reference-api/index.html +5 -2
  200. package/dist/serve/site-ui/docs/pyric-ui-storage-hooks-reference-api/index.html +5 -2
  201. package/dist/serve/site-ui/docs/pyric-ui-storage-reference-api/index.html +5 -2
  202. package/dist/serve/site-ui/docs/pyric-ui-traffic-hooks-reference-api/index.html +5 -2
  203. package/dist/serve/site-ui/docs/pyric-ui-traffic-reference-api/index.html +5 -2
  204. package/dist/serve/site-ui/docs/reference/cli/index.html +153 -0
  205. package/dist/serve/site-ui/docs/reference/cli.md +114 -0
  206. package/dist/serve/site-ui/docs/rules-compat/index.html +5 -2
  207. package/dist/serve/site-ui/docs/secure/audit-your-rules/index.html +5 -2
  208. package/dist/serve/site-ui/docs/secure/firestore-rules-limits/index.html +5 -2
  209. package/dist/serve/site-ui/docs/secure/read-a-denial/index.html +6 -3
  210. package/dist/serve/site-ui/docs/secure/read-a-denial.md +1 -1
  211. package/dist/serve/site-ui/docs/secure/rtdb-rules-in-typescript/index.html +5 -2
  212. package/dist/serve/site-ui/docs/secure/rules-standard-library/index.html +5 -2
  213. package/dist/serve/site-ui/docs/secure/secure-it-with-rules/index.html +5 -2
  214. package/dist/serve/site-ui/docs/secure/simulate-and-lint/index.html +5 -2
  215. package/dist/serve/site-ui/docs/secure/write-a-rules-test-suite/index.html +5 -2
  216. package/dist/serve/site-ui/docs/ship/ship-to-production/index.html +7 -4
  217. package/dist/serve/site-ui/docs/ship/ship-to-production.md +2 -2
  218. package/dist/serve/site-ui/docs/ship/test-in-node/index.html +9 -6
  219. package/dist/serve/site-ui/docs/ship/test-in-node.md +4 -4
  220. package/dist/serve/site-ui/docs/storage-compat/index.html +5 -2
  221. package/dist/serve/site-ui/docs/trust/how-we-know-it-matches-firebase/index.html +5 -2
  222. package/dist/serve/site-ui/docs/trust/rules-standard-library/index.html +5 -2
  223. package/dist/serve/site-ui/docs/trust/versioning-and-compatibility/index.html +5 -2
  224. package/dist/serve/site-ui/docs/tutorial/index.html +10 -7
  225. package/dist/serve/site-ui/docs/tutorial.md +4 -4
  226. package/dist/serve/site-ui/examples/chess/index.html +4 -2
  227. package/dist/serve/site-ui/firestore/index.html +5 -3
  228. package/dist/serve/site-ui/hero-charcoal.jpg +0 -0
  229. package/dist/serve/site-ui/index.html +9 -7
  230. package/dist/serve/site-ui/llms.txt +4 -3
  231. package/dist/serve/site-ui/pyric-campfire-full.png +0 -0
  232. package/dist/serve/site-ui/pyric-campfire.png +0 -0
  233. package/dist/serve/site-ui/rtdb/index.html +5 -3
  234. package/dist/serve/site-ui/settings/index.html +5 -3
  235. package/dist/serve/site-ui/storage/index.html +5 -3
  236. package/dist/serve/site-ui/studio/index.html +13 -0
  237. package/dist/serve/site-ui/studio-routes.json +1 -1
  238. package/dist/serve/site-ui/traffic/index.html +5 -3
  239. package/dist/serve/studio/routes.d.ts +9 -0
  240. package/dist/serve/studio/routes.d.ts.map +1 -1
  241. package/dist/serve/studio/routes.js +47 -7
  242. package/dist/serve/studio/routes.js.map +1 -1
  243. package/dist/serve/vite-ai-config.d.ts.map +1 -1
  244. package/dist/serve/vite-ai-config.js +38 -8
  245. package/dist/serve/vite-ai-config.js.map +1 -1
  246. package/dist/serve/vite-module-swap.d.ts.map +1 -1
  247. package/dist/serve/vite-module-swap.js +0 -28
  248. package/dist/serve/vite-module-swap.js.map +1 -1
  249. package/dist/serve/vite-sandbox-generation.d.ts +1 -1
  250. package/dist/serve/vite-sandbox-generation.d.ts.map +1 -1
  251. package/dist/serve/vite-sandbox-generation.js +1 -1
  252. package/dist/serve/vite-sandbox-generation.js.map +1 -1
  253. package/dist/serve/worker/client/disconnect.js +1 -1
  254. package/dist/serve/worker/client/disconnect.js.map +1 -1
  255. package/dist/serve/worker/client/presence.d.ts.map +1 -1
  256. package/dist/serve/worker/client/presence.js +18 -5
  257. package/dist/serve/worker/client/presence.js.map +1 -1
  258. package/dist/serve/worker/host-ai.d.ts.map +1 -1
  259. package/dist/serve/worker/host-ai.js +15 -0
  260. package/dist/serve/worker/host-ai.js.map +1 -1
  261. package/dist/serve/worker/protocol.d.ts +4 -0
  262. package/dist/serve/worker/protocol.d.ts.map +1 -1
  263. package/dist/serve/worker/protocol.js.map +1 -1
  264. package/dist/serve/worker/serve-init.d.ts.map +1 -1
  265. package/dist/serve/worker/serve-init.js +7 -1
  266. package/dist/serve/worker/serve-init.js.map +1 -1
  267. package/package.json +5 -5
  268. package/src/cli/cli.test.ts +2 -2
  269. package/src/cli/index.ts +48 -42
  270. package/src/cli/mcp-proxy.ts +4 -4
  271. package/src/cli/parse-args.ts +61 -15
  272. package/src/cli/pyric-config.ts +45 -0
  273. package/src/cli/{dev-runner.ts → sandbox-runner.ts} +117 -72
  274. package/src/cli/serve.ts +115 -47
  275. package/src/cli/snapshot.ts +5 -5
  276. package/src/cli/verify.ts +1 -1
  277. package/src/functions-rtdb/development-runtime.ts +1 -1
  278. package/src/functions-rtdb/project.ts +8 -8
  279. package/src/next/guard.ts +1 -1
  280. package/src/remote/index.ts +9 -9
  281. package/src/serve/bundler.ts +10 -10
  282. package/src/serve/entries/ai.ts +27 -1
  283. package/src/serve/entries/auth-helper-dom.ts +1 -1
  284. package/src/serve/entries/auth-helper-runtime.ts +1 -1
  285. package/src/serve/entries/firestore.ts +3 -3
  286. package/src/serve/entries/keepalive.ts +1 -1
  287. package/src/serve/entries/runtime.ts +19 -14
  288. package/src/serve/entries/storage.ts +1 -1
  289. package/src/serve/entries/worker-runtime.ts +1 -1
  290. package/src/serve/init-payload.ts +4 -0
  291. package/src/serve/namespace.ts +44 -5
  292. package/src/serve/rules.ts +47 -22
  293. package/src/serve/runtime/chip.ts +38 -1
  294. package/src/serve/runtime/manifest.ts +1 -1
  295. package/src/serve/runtime/status.ts +24 -0
  296. package/src/serve/sandbox-session.ts +10 -4
  297. package/src/serve/server.ts +18 -1
  298. package/src/serve/studio/routes.ts +63 -7
  299. package/src/serve/vite-ai-config.ts +37 -7
  300. package/src/serve/vite-module-swap.ts +0 -28
  301. package/src/serve/vite-sandbox-generation.ts +1 -1
  302. package/src/serve/worker/client/disconnect.ts +1 -1
  303. package/src/serve/worker/client/presence.ts +25 -5
  304. package/src/serve/worker/host-ai.ts +15 -0
  305. package/src/serve/worker/protocol.ts +2 -1
  306. package/src/serve/worker/serve-init.ts +5 -1
  307. package/README.md.orig +0 -145
  308. package/dist/cli/dev-runner.d.ts.map +0 -1
  309. package/dist/cli/dev-runner.js.map +0 -1
  310. package/dist/serve/site-ui/_astro/_slug_.CXh9yTP1.css +0 -1
  311. package/dist/serve/site-ui/_astro/_slug_.CupVK4TM.css +0 -1
  312. package/dist/serve/site-ui/_astro/_studio_.CfxiQWkk.css +0 -1
  313. package/dist/serve/site-ui/_astro/app.BsFsMjn0.css +0 -1
  314. package/dist/serve/site-ui/_astro/app.Ce3MTLQm.js +0 -1
  315. package/dist/serve/site-ui/_astro/app.taqzyjD0.js +0 -11
  316. package/dist/serve/site-ui/docs/trust/storage-rules-stdlib-research/index.html +0 -582
  317. package/dist/serve/site-ui/docs/trust/storage-rules-stdlib-research.md +0 -448
@@ -52,7 +52,7 @@ export function defaultSdkEntries(): Record<string, string> {
52
52
  if (existsSync(js)) return js;
53
53
  const ts = join(here, 'entries', `${name}.ts`);
54
54
  if (existsSync(ts)) return ts;
55
- throw new Error(`pyric dev: missing SDK entry '${name}' next to ${here}`);
55
+ throw new Error(`pyric sandbox: missing SDK entry '${name}' next to ${here}`);
56
56
  };
57
57
  return {
58
58
  ai: pick('ai'),
@@ -94,7 +94,7 @@ export function pyricPackageRoot(): string {
94
94
  }
95
95
  const pkg = JSON.parse(readFileSync(join(dir, 'package.json'), 'utf8')) as { name?: string };
96
96
  if (pkg.name !== 'pyric') {
97
- throw new Error(`pyric dev: expected to resolve the pyric package, found '${pkg.name}' at ${dir}`);
97
+ throw new Error(`pyric sandbox: expected to resolve the pyric package, found '${pkg.name}' at ${dir}`);
98
98
  }
99
99
  return dir;
100
100
  }
@@ -131,7 +131,7 @@ export const NODE_BUILTIN_RE = /^(node:)?(url|fs|path)$/;
131
131
  * through its own resolveId/load + optimizeDeps esbuild pass, instead of
132
132
  * re-deriving them. */
133
133
  export const NODE_BUILTIN_SHIMS: Record<string, string> = {
134
- fs: `const no = () => { throw new Error('pyric dev: fs is not available in the browser'); };
134
+ fs: `const no = () => { throw new Error('pyric sandbox: fs is not available in the browser'); };
135
135
  export const readFileSync = no; export const existsSync = () => false;
136
136
  export const readdirSync = no; export const writeFileSync = no; export const mkdirSync = no;
137
137
  export default { readFileSync, existsSync, readdirSync, writeFileSync, mkdirSync };`,
@@ -315,13 +315,13 @@ export async function bundleSdk(opts: BundleOptions): Promise<BundleResult> {
315
315
  // self-identify as the sandbox shim, not the real Firebase SDK.
316
316
  chunkNames: 'pyric-sandbox-[hash]',
317
317
  banner: {
318
- js: '/* pyric dev: pyric sandbox shim serving firebase/* NOT the real Firebase SDK */',
318
+ js: '/* pyric sandbox shim serving firebase/*. This is not the real Firebase SDK. */',
319
319
  },
320
320
  plugins: [pyricResolvePlugin(), nodeShimPlugin()],
321
321
  });
322
322
  if (result.errors.length > 0) {
323
323
  throw new Error(
324
- `pyric dev: SDK bundle failed:\n${result.errors.map((e) => e.text).join('\n')}`,
324
+ `pyric sandbox: SDK bundle failed:\n${result.errors.map((e) => e.text).join('\n')}`,
325
325
  );
326
326
  }
327
327
  writeFileSync(join(outDir, '.complete'), new Date().toISOString());
@@ -348,7 +348,7 @@ export function workerEntryPath(): string {
348
348
  if (existsSync(js)) return js;
349
349
  const ts = join(here, 'worker', 'entry.ts');
350
350
  if (existsSync(ts)) return ts;
351
- throw new Error(`pyric dev: missing SharedWorker entry next to ${here}`);
351
+ throw new Error(`pyric sandbox: missing SharedWorker entry next to ${here}`);
352
352
  }
353
353
 
354
354
  export interface WorkerBundleOptions {
@@ -371,7 +371,7 @@ const WORKER_EPOCH_FILE = '.worker-epoch';
371
371
  function readWorkerBundleEpoch(outDir: string): string {
372
372
  const epoch = readFileSync(join(outDir, WORKER_EPOCH_FILE), 'utf8').trim();
373
373
  if (!/^[a-f0-9]{16}$/.test(epoch)) {
374
- throw new Error(`pyric dev: invalid SharedWorker epoch in ${join(outDir, WORKER_EPOCH_FILE)}`);
374
+ throw new Error(`pyric sandbox: invalid SharedWorker epoch in ${join(outDir, WORKER_EPOCH_FILE)}`);
375
375
  }
376
376
  return epoch;
377
377
  }
@@ -479,18 +479,18 @@ export async function bundleWorker(opts: WorkerBundleOptions): Promise<WorkerBun
479
479
  // with the resulting epoch (same width, so the sourcemap stays aligned).
480
480
  define: { __PYRIC_WORKER_VERSION__: JSON.stringify(WORKER_EPOCH_PLACEHOLDER) },
481
481
  banner: {
482
- js: '/* pyric dev: SharedWorker sandbox host serving firebase/* NOT the real Firebase SDK */',
482
+ js: '/* pyric sandbox SharedWorker host serving firebase/*. This is not the real Firebase SDK. */',
483
483
  },
484
484
  plugins: [pyricResolvePlugin(), nodeShimPlugin()],
485
485
  });
486
486
  if (result.errors.length > 0) {
487
487
  throw new Error(
488
- `pyric dev: SharedWorker bundle failed:\n${result.errors.map((e) => e.text).join('\n')}`,
488
+ `pyric sandbox: SharedWorker bundle failed:\n${result.errors.map((e) => e.text).join('\n')}`,
489
489
  );
490
490
  }
491
491
  const canonicalSource = readFileSync(outFile, 'utf8');
492
492
  if (!canonicalSource.includes(WORKER_EPOCH_PLACEHOLDER)) {
493
- throw new Error('pyric dev: SharedWorker bundle omitted its epoch placeholder');
493
+ throw new Error('pyric sandbox: SharedWorker bundle omitted its epoch placeholder');
494
494
  }
495
495
  const epochHash = createHash('sha256').update(canonicalSource);
496
496
  if (opts.epochSalt) epochHash.update('\0').update(opts.epochSalt);
@@ -105,6 +105,16 @@ function toEngineWire(engine: EngineOption | undefined): AiEngineConfigWire | un
105
105
  ...(openai.modelMap !== undefined ? { modelMap: openai.modelMap } : {}),
106
106
  };
107
107
  }
108
+ if (config.kind === 'gemini') {
109
+ const gemini = config as Extract<EngineOption, { kind: 'gemini' }>;
110
+ const hasBaseUrl = gemini.baseUrl !== undefined;
111
+ const hasApiKey = gemini.apiKey !== undefined;
112
+ return {
113
+ kind: 'gemini',
114
+ ...(hasBaseUrl ? { baseUrl: gemini.baseUrl } : {}),
115
+ ...(hasApiKey ? { apiKey: gemini.apiKey } : {}),
116
+ };
117
+ }
108
118
  const scripted = config as Extract<EngineOption, { kind: 'scripted' }>;
109
119
  return {
110
120
  kind: 'scripted',
@@ -139,6 +149,21 @@ function wireToEngineOption(wire: AiEngineConfigWire): EngineOption {
139
149
  ...(wire.modelMap !== undefined ? { modelMap: wire.modelMap } : {}),
140
150
  } as EngineOption;
141
151
  }
152
+ const isGeminiWire = wire.kind === 'gemini';
153
+ if (isGeminiWire) {
154
+ const result: Record<string, unknown> = {
155
+ kind: 'gemini',
156
+ };
157
+ const hasBaseUrl = wire.baseUrl !== undefined;
158
+ if (hasBaseUrl) {
159
+ result.baseUrl = wire.baseUrl;
160
+ }
161
+ const hasApiKey = wire.apiKey !== undefined;
162
+ if (hasApiKey) {
163
+ result.apiKey = wire.apiKey;
164
+ }
165
+ return result as unknown as EngineOption;
166
+ }
142
167
  return {
143
168
  kind: 'scripted',
144
169
  ...(wire.script !== undefined
@@ -274,10 +299,11 @@ export const getAI = (
274
299
  "Use a structured { kind: 'scripted' } or { kind: 'openai' } engine config.",
275
300
  );
276
301
  }
302
+ const wire = toEngineWire(engine) ?? pluginEngineWire();
277
303
  return createTransportAI(
278
304
  resolved,
279
305
  options,
280
- portEngine(workerClientForApp(resolved), toEngineWire(engine)),
306
+ portEngine(workerClientForApp(resolved), wire),
281
307
  assertAlive,
282
308
  );
283
309
  }
@@ -67,7 +67,7 @@ export function mountAuthHelperDialog(helper: ServeAuthHelper): void {
67
67
  h2.textContent = `Sign in with ${provider}`;
68
68
  const p = document.createElement('p');
69
69
  p.textContent =
70
- 'pyric dev sign-in helper pick a test account or add one. Custom claims let rules using request.auth.token.* run against this identity.';
70
+ 'pyric sandbox sign-in helper. Pick a test account or add one. Custom claims let rules using request.auth.token.* run against this identity.';
71
71
  dialog.append(h2, p);
72
72
 
73
73
  if (snap.identities.length > 0) {
@@ -13,7 +13,7 @@ export function resolveServeAuthFlow(
13
13
  ): Promise<UserCredential> {
14
14
  if (!resolver) {
15
15
  return Promise.reject(
16
- new Error('pyric dev provider helper is not initialized; load /__pyric/sdk/init.js first'),
16
+ new Error('pyric sandbox provider helper is not initialized; load /__pyric/sdk/init.js first'),
17
17
  );
18
18
  }
19
19
  return kind === 'popup' ? resolver.openPopup(request) : resolver.openRedirect(request);
@@ -103,8 +103,8 @@ function acceptedNoOp(name: string): void {
103
103
  if (cacheNoticeShown) return;
104
104
  cacheNoticeShown = true;
105
105
  console.info(
106
- `[pyric dev] ${name}(): client cache/persistence settings are accepted but unused ` +
107
- 'the pyric sandbox is the source of truth. Durable state is the worker store / `pyric dev --persist`.',
106
+ `[pyric sandbox] ${name}(): client cache and persistence settings are accepted but unused. ` +
107
+ 'The Pyric sandbox is the source of truth. Durable state is the worker store or `pyric sandbox --persist`.',
108
108
  );
109
109
  }
110
110
 
@@ -184,7 +184,7 @@ export async function enableNetwork(_db?: unknown): Promise<void> {
184
184
  export async function terminate(db?: unknown): Promise<void> {
185
185
  if (useWorker) {
186
186
  console.info(
187
- '[pyric dev] terminate(): the SharedWorker-hosted sandbox is shared across every tab; ' +
187
+ '[pyric sandbox] terminate(): the SharedWorker-hosted sandbox is shared across every tab; ' +
188
188
  'it is not torn down by a single tab calling terminate().',
189
189
  );
190
190
  return;
@@ -27,7 +27,7 @@ export function keepaliveSafe(
27
27
  if (!warnedLargeState) {
28
28
  warnedLargeState = true;
29
29
  warn(
30
- `[pyric dev] persisted state is ${Math.round(bytes / 1024)}KB too large for an ` +
30
+ `[pyric sandbox] persisted state is ${Math.round(bytes / 1024)}KB. It is too large for an ` +
31
31
  'unload-time flush; the final unsaved change may be lost if you CLOSE the tab before ' +
32
32
  'the next auto-flush. Reloads and normal saves are unaffected.',
33
33
  );
@@ -140,10 +140,15 @@ if (!useWorker) try {
140
140
  diagnostics.rulesDeployed = true;
141
141
  diagnostics.rulesHash = payload.rulesHash;
142
142
  }
143
+ const db = getDatabase(sandbox);
143
144
  if (payload.databaseRules) {
144
- rtdbSandbox.setRules(getDatabase(sandbox), payload.databaseRules);
145
+ rtdbSandbox.setRules(db, payload.databaseRules);
145
146
  diagnostics.databaseRulesDeployed = true;
146
147
  diagnostics.databaseRulesHash = payload.databaseRulesHash ?? null;
148
+ } else if (payload.permissive) {
149
+ rtdbSandbox.setDefaultPolicy(db, 'allow');
150
+ } else {
151
+ rtdbSandbox.setDefaultPolicy(db, 'deny');
147
152
  }
148
153
  // Open the ONE per-sandbox storage service eagerly: the FIRST open wins the
149
154
  // rules AND the project-scoped IDB name (`pyric-storage:<projectKey>`,
@@ -212,7 +217,7 @@ if (!useWorker) try {
212
217
  readOnly = true;
213
218
  diagnostics.persistReadOnly = true;
214
219
  console.warn(
215
- '[pyric dev] another tab is the persist writer THIS tab is read-only ' +
220
+ '[pyric sandbox] another tab is the persist writer. This tab is read-only ' +
216
221
  '(your changes here will NOT be saved). Close the other tab and reload to take over.',
217
222
  );
218
223
  };
@@ -288,7 +293,7 @@ if (!useWorker) try {
288
293
  if (res.status === 423) lostWriterLock();
289
294
  else if (res.ok) diagnostics.lastFlushAt = Date.now();
290
295
  },
291
- (e) => console.warn('[pyric dev] auth state flush failed:', e),
296
+ (e) => console.warn('[pyric sandbox] auth state flush failed:', e),
292
297
  );
293
298
  }, 500);
294
299
  });
@@ -353,7 +358,7 @@ if (!useWorker) try {
353
358
  if (existing) {
354
359
  diagnostics.seedSkipped = true;
355
360
  console.info(
356
- '[pyric dev] --seed skipped: the sandbox already has restored data the fixture would ' +
361
+ '[pyric sandbox] --seed skipped because the sandbox already has restored data. The fixture would ' +
357
362
  'have overwritten it. Use `--persist --fresh` to discard existing state and re-seed.',
358
363
  );
359
364
  } else {
@@ -367,7 +372,7 @@ if (!useWorker) try {
367
372
  diagnostics.initError = e instanceof Error ? e.message : String(e);
368
373
  runtimeStatus.reportError(e, 'runtime');
369
374
  console.error(
370
- '[pyric dev] init failed the sandbox is running WITHOUT your project rules:',
375
+ '[pyric sandbox] init failed. The sandbox is running without your project rules:',
371
376
  diagnostics.initError,
372
377
  );
373
378
  }
@@ -390,7 +395,7 @@ if (!useWorker && activityTokenFromPayload) {
390
395
  // Source says `firebase/*`; what runs is the pyric sandbox — say so loudly
391
396
  // once, and explain any stack frame that points into /__pyric/sdk/. ────────
392
397
  console.info(
393
- `[pyric dev] firebase/* on this page is served by the pyric sandbox` +
398
+ `[pyric sandbox] firebase/* on this page is served by the Pyric sandbox` +
394
399
  (useWorker
395
400
  ? ' in a SharedWorker (one backend for all tabs; rules/seed/persist owned by the worker)'
396
401
  : diagnostics.rulesHash || diagnostics.databaseRulesHash || diagnostics.storageRulesHash
@@ -405,7 +410,7 @@ function explainSandboxFrame(stackOrUrl: string | undefined): boolean {
405
410
  if (!provenanceHintShown) {
406
411
  provenanceHintShown = true;
407
412
  console.info(
408
- '[pyric dev] the error above originates in the pyric sandbox shim serving firebase/*, ' +
413
+ '[pyric sandbox] the error above originates in the Pyric sandbox shim serving firebase/*, ' +
409
414
  'not the real Firebase SDK — behavior can differ where COMPAT coverage is incomplete.',
410
415
  );
411
416
  }
@@ -452,7 +457,7 @@ async function connectBridgePeer(rawUrl: string): Promise<void> {
452
457
  connectBridge(sandbox, { url });
453
458
  }
454
459
  diagnostics.bridgeConnected = true;
455
- console.info('[pyric dev] sandbox registered with the MCP bridge at', url);
460
+ console.info('[pyric sandbox] sandbox registered with the MCP bridge at', url);
456
461
  }
457
462
  if (bridgeUrlFromPayload) {
458
463
  // In-page path: bridgeUrlFromPayload came from the (already-blocking) init
@@ -461,7 +466,7 @@ if (bridgeUrlFromPayload) {
461
466
  await connectBridgePeer(bridgeUrlFromPayload);
462
467
  } catch (e) {
463
468
  runtimeStatus.reportError(e, 'runtime');
464
- console.error('[pyric dev] bridge connect failed:', e instanceof Error ? e.message : String(e));
469
+ console.error('[pyric sandbox] bridge connect failed:', e instanceof Error ? e.message : String(e));
465
470
  }
466
471
  } else if (useWorker) {
467
472
  // Worker path: the worker owns serve-init, so the page skipped the init.json
@@ -476,7 +481,7 @@ if (bridgeUrlFromPayload) {
476
481
  if (url) await connectBridgePeer(url);
477
482
  } catch (e) {
478
483
  runtimeStatus.reportError(e, 'runtime');
479
- console.error('[pyric dev] bridge connect failed:', e instanceof Error ? e.message : String(e));
484
+ console.error('[pyric sandbox] bridge connect failed:', e instanceof Error ? e.message : String(e));
480
485
  }
481
486
  })();
482
487
  }
@@ -547,10 +552,10 @@ if (!useWorker && typeof EventSource !== 'undefined') {
547
552
  if (lint.parseError) throw new Error(JSON.stringify(lint.parseError));
548
553
  diagnostics.rulesDeployed = true;
549
554
  diagnostics.rulesHash = rulesHash;
550
- console.info(`[pyric dev] firestore.rules hot-reloaded (hash ${rulesHash})`);
555
+ console.info(`[pyric sandbox] firestore.rules hot-reloaded (hash ${rulesHash})`);
551
556
  } catch (err) {
552
557
  runtimeStatus.reportError(err, 'runtime');
553
- console.error('[pyric dev] rules hot-reload failed:', err instanceof Error ? err.message : String(err));
558
+ console.error('[pyric sandbox] rules hot-reload failed:', err instanceof Error ? err.message : String(err));
554
559
  }
555
560
  });
556
561
  events.addEventListener('rtdb-rules-update', (e) => {
@@ -567,7 +572,7 @@ if (!useWorker && typeof EventSource !== 'undefined') {
567
572
  newHash = rulesHash;
568
573
  }
569
574
  diagnostics.databaseRulesHash = newHash;
570
- console.info(`[pyric dev] database.rules.json hot-reloaded (hash ${rulesHash})`);
575
+ console.info(`[pyric sandbox] database.rules.json hot-reloaded (hash ${rulesHash})`);
571
576
  } catch (err) {
572
577
  runtimeStatus.reportError(err, 'runtime');
573
578
  const isErrorInstance = err instanceof Error;
@@ -575,7 +580,7 @@ if (!useWorker && typeof EventSource !== 'undefined') {
575
580
  if (isErrorInstance) {
576
581
  errorMessage = (err as Error).message;
577
582
  }
578
- console.error('[pyric dev] database rules hot-reload failed:', errorMessage);
583
+ console.error('[pyric sandbox] database rules hot-reload failed:', errorMessage);
579
584
  }
580
585
  });
581
586
  }
@@ -66,7 +66,7 @@ export function connectStorageEmulator(
66
66
  _options?: unknown,
67
67
  ): void {
68
68
  console.info(
69
- '[pyric dev] pyric replaces the Firebase emulator; connectStorageEmulator is a no-op.',
69
+ '[pyric sandbox] Pyric replaces the Firebase emulator; connectStorageEmulator is a no-op.',
70
70
  );
71
71
  if (!useWorker) ip.connectStorageEmulator(storage as FirebaseStorage, _host, _port, _options as never);
72
72
  }
@@ -125,7 +125,7 @@ if (useWorker && typeof document !== 'undefined') {
125
125
  ? ' This is the only connected page — reload it to pick up the new worker.'
126
126
  : '';
127
127
  console.warn(
128
- `[pyric dev] the SharedWorker is running OLDER code (build ${runningVersion}) than what is now `
128
+ `[pyric sandbox] the SharedWorker is running older code (build ${runningVersion}) than what is now `
129
129
  + `served (build ${servedVersion}). A SharedWorker can't hot-update — CLOSE ALL TABS of this origin `
130
130
  + 'and reopen to load the new worker. (All tabs share one worker, so a partial reload leaves '
131
131
  + `the old code running for everyone.)${othersHint}`,
@@ -4,11 +4,15 @@ import type { AiEngineConfigWire } from './worker/protocol.js';
4
4
  export interface InitPayload {
5
5
  /** Per-server capability for the warning-only activity report endpoint. */
6
6
  activityToken?: string;
7
+ /** Per-boot session capability token required on workspace and project endpoints. */
8
+ sessionToken?: string;
7
9
  rules: string | null;
8
10
  rulesHash: string | null;
9
11
  databaseRules?: { rules: Record<string, unknown> } | null;
10
12
  databaseRulesHash?: string | null;
11
13
  databaseUrl?: string | null;
14
+ /** Explicit opt-in to permissive default access when rules are unconfigured. */
15
+ permissive?: boolean;
12
16
  /** Storage rules are installed once, before the first Storage operation. */
13
17
  storageRules: string | null;
14
18
  storageRulesHash: string | null;
@@ -18,7 +18,7 @@ import { collectBody } from '../bridge/server/peer.js';
18
18
  import { StateFileError, type StateSection, type StateStore } from './state-store.js';
19
19
  import { createWriterLock, type WriterLock } from './writer-lock.js';
20
20
  import { createStudioRoutes, type StudioRouteOptions } from './studio/index.js';
21
- import { pipeFileToResponse, type ServeLogger } from './server.js';
21
+ import { pipeFileToResponse, getHeader, isAllowedHost, isAllowedOrigin, type ServeLogger } from './server.js';
22
22
  import type { InitPayload } from './init-payload.js';
23
23
  import { handleActivity } from './activity-route.js';
24
24
  import { createSiteTreeHandler } from './site-tree.js';
@@ -107,6 +107,12 @@ export interface NamespaceOptions {
107
107
  * (disk-backed `WorkspaceStore`/`ProjectStore`, plus the SSE watch stream)
108
108
  * that `@pyric/studio`'s `local` mode talks to. */
109
109
  studio?: StudioRouteOptions;
110
+ /** Per-boot session capability token required on workspace and project endpoints. */
111
+ sessionToken?: string;
112
+ /** Bound host for DNS rebinding guard. */
113
+ boundHost?: string;
114
+ /** Extra allowed hostnames. */
115
+ allowedHosts?: string[];
110
116
  /** The OpenAI-compatible upstream `/__pyric/ai-proxy` forwards to
111
117
  * (pyric/ai — cdd-deltas #98.2). Falls back to the
112
118
  * `PYRIC_AI_PROXY_UPSTREAM` env var, then `http://localhost:11434/v1`
@@ -471,8 +477,20 @@ async function handleDenials(
471
477
  }
472
478
 
473
479
  export function createPyricNamespace(opts: NamespaceOptions) {
474
- const writerLock = createWriterLock();
475
- const studioRoutes = opts.studio ? createStudioRoutes(opts.studio) : null;
480
+ const stateWriterLock = createWriterLock();
481
+ const studioWriterLock = opts.studio?.writerLock ?? createWriterLock();
482
+ const sessionToken = opts.sessionToken ?? randomBytes(24).toString('base64url');
483
+ let studioRoutes: ((req: IncomingMessage, res: ServerResponse, url: URL) => Promise<boolean>) | null = null;
484
+ if (opts.studio) {
485
+ const studioOptions: StudioRouteOptions = {
486
+ ...opts.studio,
487
+ sessionToken,
488
+ writerLock: studioWriterLock,
489
+ boundHost: opts.boundHost,
490
+ allowedHosts: opts.allowedHosts,
491
+ };
492
+ studioRoutes = createStudioRoutes(studioOptions);
493
+ }
476
494
  const siteTree = opts.siteUiDir
477
495
  ? createSiteTreeHandler(opts.siteUiDir, opts.workerVersion)
478
496
  : null;
@@ -489,7 +507,7 @@ export function createPyricNamespace(opts: NamespaceOptions) {
489
507
  return studioRoutes(req, res, url);
490
508
  }
491
509
  if (opts.state && url.pathname === '/__pyric/state') {
492
- return handleState(opts.state!, writerLock, req, res, url).then(() => true);
510
+ return handleState(opts.state!, stateWriterLock, req, res, url).then(() => true);
493
511
  }
494
512
  if (opts.capture && url.pathname === '/__pyric/capture') {
495
513
  return handleCapture(opts.capture, req, res).then(() => true);
@@ -498,6 +516,21 @@ export function createPyricNamespace(opts: NamespaceOptions) {
498
516
  return handleActivity(opts.activity, req, res, activityToken!).then(() => true);
499
517
  }
500
518
  if (opts.events && url.pathname === '/__pyric/events') {
519
+ const hostHeader = getHeader(req, 'host');
520
+ const originHeader = getHeader(req, 'origin');
521
+ if (!isAllowedHost(hostHeader, opts.boundHost ?? 'localhost', opts.allowedHosts)) {
522
+ res.writeHead(403, { 'content-type': 'text/plain' }).end('Forbidden: host not allowed');
523
+ return true;
524
+ }
525
+ if (originHeader && !isAllowedOrigin(originHeader, opts.boundHost ?? 'localhost', opts.allowedHosts)) {
526
+ res.writeHead(403, { 'content-type': 'text/plain' }).end('Forbidden: origin mismatch');
527
+ return true;
528
+ }
529
+ const reqToken = getHeader(req, 'x-pyric-session-token') ?? url.searchParams.get('token');
530
+ if (sessionToken && reqToken && reqToken !== sessionToken) {
531
+ res.writeHead(401, { 'content-type': 'text/plain' }).end('Unauthorized: invalid session capability token');
532
+ return true;
533
+ }
501
534
  opts.events.handle(req, res);
502
535
  return true;
503
536
  }
@@ -509,7 +542,13 @@ export function createPyricNamespace(opts: NamespaceOptions) {
509
542
  }
510
543
  if (url.pathname === '/__pyric/init.json') {
511
544
  res.writeHead(200, { 'content-type': 'application/json; charset=utf-8', 'cache-control': 'no-store' });
512
- res.end(JSON.stringify({ ...opts.initPayload(), ...(activityToken ? { activityToken } : {}) }));
545
+ res.end(
546
+ JSON.stringify({
547
+ ...opts.initPayload(),
548
+ sessionToken,
549
+ ...(activityToken ? { activityToken } : {}),
550
+ }),
551
+ );
513
552
  return true;
514
553
  }
515
554
  if (url.pathname.startsWith('/__pyric/sdk/')) {
@@ -56,7 +56,7 @@ export function prepareRulesSource(raw: string, sourcePath: string): string {
56
56
  const resolved = resolveModulesBrowser(raw);
57
57
  if (!resolved.success) {
58
58
  throw new Error(
59
- `pyric dev: ${sourcePath} uses 2+modules but module resolution failed: ${resolved.error.message}`,
59
+ `pyric sandbox: ${sourcePath} uses 2+modules but module resolution failed: ${resolved.error.message}`,
60
60
  );
61
61
  }
62
62
  source = resolved.data.resolved;
@@ -65,13 +65,13 @@ export function prepareRulesSource(raw: string, sourcePath: string): string {
65
65
  if (lint.parseError) {
66
66
  const { line, column } = lint.parseError;
67
67
  throw new Error(
68
- `pyric dev: ${sourcePath} failed to parse (line ${line}, col ${column}) fix the rules before serving.`,
68
+ `pyric sandbox: ${sourcePath} failed to parse (line ${line}, col ${column}). Fix the rules before serving.`,
69
69
  );
70
70
  }
71
71
  const errors = lint.warnings.filter((w) => w.severity === 'error');
72
72
  if (errors.length > 0) {
73
73
  throw new Error(
74
- `pyric dev: ${sourcePath} has ${errors.length} rules error(s):\n` +
74
+ `pyric sandbox: ${sourcePath} has ${errors.length} rules error(s):\n` +
75
75
  errors.map((e) => ` - ${e.message}`).join('\n'),
76
76
  );
77
77
  }
@@ -98,7 +98,7 @@ export async function loadProjectRules(
98
98
  } catch (e) {
99
99
  if ((e as NodeJS.ErrnoException).code === 'ENOENT') {
100
100
  if (configured) {
101
- throw new Error(`pyric dev: firebase.json points firestore.rules at ${path}, but it does not exist.`);
101
+ throw new Error(`pyric sandbox: firebase.json points firestore.rules at ${path}, but it does not exist.`);
102
102
  }
103
103
  return { rules: null, rulesHash: null, sourcePath: null };
104
104
  }
@@ -119,7 +119,7 @@ export function prepareStorageRulesSource(raw: string, sourcePath: string): stri
119
119
  const resolved = resolveModulesBrowser(raw, { sourceFile: sourcePath });
120
120
  if (!resolved.success) {
121
121
  throw new Error(
122
- `pyric dev: ${sourcePath} uses 2+modules but module resolution failed: ${resolved.error.message}`,
122
+ `pyric sandbox: ${sourcePath} uses 2+modules but module resolution failed: ${resolved.error.message}`,
123
123
  );
124
124
  }
125
125
  source = resolved.data.resolved;
@@ -128,7 +128,7 @@ export function prepareStorageRulesSource(raw: string, sourcePath: string): stri
128
128
  parseStorageRules(source);
129
129
  } catch (e) {
130
130
  throw new Error(
131
- `pyric dev: ${sourcePath} failed to parse: ${e instanceof Error ? e.message : String(e)} fix the rules before serving.`,
131
+ `pyric sandbox: ${sourcePath} failed to parse: ${e instanceof Error ? e.message : String(e)}. Fix the rules before serving.`,
132
132
  );
133
133
  }
134
134
  return source;
@@ -159,7 +159,7 @@ export async function loadProjectStorageRules(
159
159
  } catch (e) {
160
160
  if ((e as NodeJS.ErrnoException).code === 'ENOENT') {
161
161
  if (configured) {
162
- throw new Error(`pyric dev: firebase.json points storage.rules at ${path}, but it does not exist.`);
162
+ throw new Error(`pyric sandbox: firebase.json points storage.rules at ${path}, but it does not exist.`);
163
163
  }
164
164
  return { rules: null, rulesHash: null, sourcePath: null };
165
165
  }
@@ -169,27 +169,52 @@ export async function loadProjectStorageRules(
169
169
  return { rules, rulesHash: rulesHashOf(rules), sourcePath: path };
170
170
  }
171
171
 
172
+ function normalizeDatabaseEntries(block: unknown): Array<Record<string, unknown>> {
173
+ if (!block) {
174
+ return [];
175
+ }
176
+ if (Array.isArray(block)) {
177
+ return block.filter((item): item is Record<string, unknown> => typeof item === 'object' && item !== null);
178
+ }
179
+ if (typeof block === 'object' && block !== null) {
180
+ return [block as Record<string, unknown>];
181
+ }
182
+ return [];
183
+ }
184
+
185
+ function hasConfiguredRules(entry: Record<string, unknown>): entry is Record<string, unknown> & { rules: string } {
186
+ return typeof entry.rules === 'string';
187
+ }
188
+
189
+ function hasConfiguredUrl(entry: Record<string, unknown>): entry is Record<string, unknown> & { url: string } {
190
+ return typeof entry.url === 'string';
191
+ }
192
+
172
193
  export async function loadProjectDatabaseRules(
173
194
  cwd: string,
174
195
  config: FirebaseJson | null,
175
196
  ): Promise<LoadedDatabaseRules> {
176
- const configured = config?.database?.rules;
177
- if (!configured) {
178
- return {
179
- rules: null,
180
- rulesHash: null,
181
- sourcePath: null,
182
- databaseUrl: config?.database?.url ?? null,
183
- };
184
- }
185
-
186
- const path = isAbsolute(configured) ? configured : join(cwd, configured);
197
+ const entries = normalizeDatabaseEntries(config?.database);
198
+ const configuredEntry = entries.find(hasConfiguredRules);
199
+ const configured = configuredEntry?.rules;
200
+ const rel = configured ?? 'database.rules.json';
201
+ const path = isAbsolute(rel) ? rel : join(cwd, rel);
202
+ const urlEntry = entries.find(hasConfiguredUrl);
203
+ const databaseUrl = urlEntry?.url ?? null;
187
204
  let raw: string;
188
205
  try {
189
206
  raw = await readFile(path, 'utf8');
190
207
  } catch (e) {
191
208
  if ((e as NodeJS.ErrnoException).code === 'ENOENT') {
192
- throw new Error(`pyric dev: firebase.json points database.rules at ${path}, but it does not exist.`);
209
+ if (configured) {
210
+ throw new Error(`pyric sandbox: firebase.json points database.rules at ${path}, but it does not exist.`);
211
+ }
212
+ return {
213
+ rules: null,
214
+ rulesHash: null,
215
+ sourcePath: null,
216
+ databaseUrl,
217
+ };
193
218
  }
194
219
  throw e;
195
220
  }
@@ -198,18 +223,18 @@ export async function loadProjectDatabaseRules(
198
223
  try {
199
224
  parsed = JSON.parse(stripJsonComments(raw));
200
225
  } catch (e) {
201
- throw new Error(`pyric dev: ${path} failed to parse as RTDB rules JSON: ${e instanceof Error ? e.message : String(e)}`);
226
+ throw new Error(`pyric sandbox: ${path} failed to parse as RTDB rules JSON: ${e instanceof Error ? e.message : String(e)}`);
202
227
  }
203
228
  const rules = parseRtdbRulesJson(
204
229
  parsed,
205
- () => new Error(`pyric dev: ${path} must contain a top-level "rules" object.`),
230
+ () => new Error(`pyric sandbox: ${path} must contain a top-level "rules" object.`),
206
231
  );
207
232
 
208
233
  return {
209
234
  rules,
210
235
  rulesHash: rulesHashOf(raw),
211
236
  sourcePath: path,
212
- databaseUrl: config?.database?.url ?? null,
237
+ databaseUrl,
213
238
  };
214
239
  }
215
240
 
@@ -18,6 +18,32 @@ export interface PyricRuntimeChip {
18
18
  dispose(): void;
19
19
  }
20
20
 
21
+ interface AiEngineDisplay {
22
+ primary: string;
23
+ subline: string | null;
24
+ }
25
+
26
+ function aiEngineState(): AiEngineDisplay {
27
+ const engine = (globalThis as { __PYRIC_AI_ENGINE__?: { kind?: string; model?: string } }).__PYRIC_AI_ENGINE__;
28
+ if (engine?.kind === 'gemini') {
29
+ return {
30
+ primary: 'gemini (production)',
31
+ subline: 'gemini-3.5-flash-lite → gemini-flash-lite-latest',
32
+ };
33
+ }
34
+ if (engine?.kind === 'openai') {
35
+ const modelLabel = engine.model ? ` (${engine.model})` : '';
36
+ return {
37
+ primary: `openai (proxy${modelLabel})`,
38
+ subline: null,
39
+ };
40
+ }
41
+ return {
42
+ primary: 'sandbox (scripted)',
43
+ subline: null,
44
+ };
45
+ }
46
+
21
47
  const styles = `
22
48
  :host {
23
49
  --pyric-bg: #1e1e24;
@@ -86,8 +112,11 @@ const styles = `
86
112
  .icon-button[data-copy-failed] { color: var(--pyric-error); }
87
113
  .icon { height: 15px; width: 15px; }
88
114
  .worker-state { border-top: 1px solid var(--pyric-border-soft); color: var(--pyric-muted); font-size: 10px; justify-content: space-between; min-height: 34px; padding: 7px 12px; }
115
+ .worker-state-col { border-top: 1px solid var(--pyric-border-soft); color: var(--pyric-muted); font-size: 10px; display: flex; flex-direction: column; min-height: 34px; padding: 7px 12px; }
116
+ .worker-state-row { align-items: center; display: flex; justify-content: space-between; width: 100%; }
117
+ .worker-state-subline { color: #89899f; font: 9px/1.4 ui-monospace, monospace; margin-top: 4px; overflow-wrap: anywhere; text-align: right; width: 100%; }
89
118
  .worker-state .available { color: var(--pyric-warning); }
90
- .worker-state .state-label { align-items: center; display: flex; gap: 7px; }
119
+ .worker-state .state-label, .worker-state-col .state-label { align-items: center; display: flex; gap: 7px; white-space: nowrap; }
91
120
  .mini-dot { background: var(--pyric-accent); border-radius: 50%; height: 6px; width: 6px; }
92
121
  .available .mini-dot { background: var(--pyric-warning); }
93
122
  .epochs { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
@@ -203,6 +232,7 @@ export function mountPyricRuntimeChip(options: PyricRuntimeChipOptions): PyricRu
203
232
  const epochs = snapshot.updateAvailable
204
233
  ? `${snapshot.runningEpoch?.slice(0, 8) ?? 'unknown'} → ${snapshot.servedEpoch?.slice(0, 8) ?? 'unknown'}`
205
234
  : snapshot.runningEpoch?.slice(0, 8) ?? '';
235
+ const aiState = aiEngineState();
206
236
 
207
237
  view.innerHTML = `${open ? `
208
238
  <section class="panel" role="dialog" aria-label="Pyric runtime">
@@ -214,6 +244,13 @@ export function mountPyricRuntimeChip(options: PyricRuntimeChipOptions): PyricRu
214
244
  </div>
215
245
  </header>
216
246
  <div class="worker-state"><span class="state-label${snapshot.updateAvailable ? ' available' : ''}"><span class="mini-dot"></span>${workerLabel}</span><span class="epochs">${epochs}</span></div>
247
+ <div class="worker-state-col" data-ai-status>
248
+ <div class="worker-state-row">
249
+ <span class="state-label"><span class="mini-dot"></span>AI engine</span>
250
+ <span class="epochs">${aiState.primary}</span>
251
+ </div>
252
+ ${aiState.subline ? `<div class="worker-state-subline">${aiState.subline}</div>` : ''}
253
+ </div>
217
254
  <div class="errors" data-error-viewport>${renderErrors(snapshot, Boolean(clipboard))}</div>
218
255
  <div class="actions">
219
256
  <button class="button update" type="button" data-update-worker ${snapshot.updateAvailable ? '' : 'disabled'} aria-disabled="${snapshot.updateAvailable && !snapshot.updatingWorker ? 'false' : 'true'}">${snapshot.updatingWorker ? 'Updating…' : 'Update worker'}</button>