@pyric/cli 0.1.0-alpha.18 → 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 (356) hide show
  1. package/README.md +14 -9
  2. package/dist/bridge/server/mcp-contract.d.ts +2 -2
  3. package/dist/bridge/server/mcp-contract.d.ts.map +1 -1
  4. package/dist/bridge/server/mcp-contract.js +3 -0
  5. package/dist/bridge/server/mcp-contract.js.map +1 -1
  6. package/dist/cli/index.d.ts +3 -2
  7. package/dist/cli/index.d.ts.map +1 -1
  8. package/dist/cli/index.js +51 -42
  9. package/dist/cli/index.js.map +1 -1
  10. package/dist/cli/mcp-proxy.d.ts.map +1 -1
  11. package/dist/cli/mcp-proxy.js +3 -3
  12. package/dist/cli/mcp-proxy.js.map +1 -1
  13. package/dist/cli/parse-args.d.ts +1 -1
  14. package/dist/cli/parse-args.d.ts.map +1 -1
  15. package/dist/cli/parse-args.js +51 -15
  16. package/dist/cli/parse-args.js.map +1 -1
  17. package/dist/cli/pyric-config.d.ts +24 -0
  18. package/dist/cli/pyric-config.d.ts.map +1 -0
  19. package/dist/cli/pyric-config.js +28 -0
  20. package/dist/cli/pyric-config.js.map +1 -0
  21. package/dist/cli/rules.d.ts +1 -0
  22. package/dist/cli/rules.d.ts.map +1 -1
  23. package/dist/cli/rules.js +18 -4
  24. package/dist/cli/rules.js.map +1 -1
  25. package/dist/cli/{dev-runner.d.ts → sandbox-runner.d.ts} +33 -36
  26. package/dist/cli/sandbox-runner.d.ts.map +1 -0
  27. package/dist/cli/{dev-runner.js → sandbox-runner.js} +90 -55
  28. package/dist/cli/sandbox-runner.js.map +1 -0
  29. package/dist/cli/serve.d.ts +6 -4
  30. package/dist/cli/serve.d.ts.map +1 -1
  31. package/dist/cli/serve.js +99 -40
  32. package/dist/cli/serve.js.map +1 -1
  33. package/dist/cli/service-command-records/storage-rules-resolve.d.ts +4 -0
  34. package/dist/cli/service-command-records/storage-rules-resolve.d.ts.map +1 -0
  35. package/dist/cli/service-command-records/storage-rules-resolve.js +3 -0
  36. package/dist/cli/service-command-records/storage-rules-resolve.js.map +1 -0
  37. package/dist/cli/service-commands.generated.d.ts +3 -0
  38. package/dist/cli/service-commands.generated.d.ts.map +1 -1
  39. package/dist/cli/service-commands.generated.js +2 -0
  40. package/dist/cli/service-commands.generated.js.map +1 -1
  41. package/dist/cli/snapshot.js +5 -5
  42. package/dist/cli/snapshot.js.map +1 -1
  43. package/dist/cli/verify.js +1 -1
  44. package/dist/cli/verify.js.map +1 -1
  45. package/dist/functions-rtdb/development-runtime.js +1 -1
  46. package/dist/functions-rtdb/development-runtime.js.map +1 -1
  47. package/dist/functions-rtdb/project.js +8 -8
  48. package/dist/functions-rtdb/project.js.map +1 -1
  49. package/dist/next/guard.js +1 -1
  50. package/dist/next/guard.js.map +1 -1
  51. package/dist/remote/index.d.ts +1 -1
  52. package/dist/remote/index.js +9 -9
  53. package/dist/remote/index.js.map +1 -1
  54. package/dist/serve/bundler.js +10 -10
  55. package/dist/serve/bundler.js.map +1 -1
  56. package/dist/serve/entries/ai.d.ts.map +1 -1
  57. package/dist/serve/entries/ai.js +27 -1
  58. package/dist/serve/entries/ai.js.map +1 -1
  59. package/dist/serve/entries/auth-helper-dom.js +1 -1
  60. package/dist/serve/entries/auth-helper-dom.js.map +1 -1
  61. package/dist/serve/entries/auth-helper-runtime.js +1 -1
  62. package/dist/serve/entries/auth-helper-runtime.js.map +1 -1
  63. package/dist/serve/entries/firestore.js +3 -3
  64. package/dist/serve/entries/firestore.js.map +1 -1
  65. package/dist/serve/entries/keepalive.js +1 -1
  66. package/dist/serve/entries/keepalive.js.map +1 -1
  67. package/dist/serve/entries/runtime.js +21 -14
  68. package/dist/serve/entries/runtime.js.map +1 -1
  69. package/dist/serve/entries/storage.js +1 -1
  70. package/dist/serve/entries/storage.js.map +1 -1
  71. package/dist/serve/entries/worker-runtime.js +1 -1
  72. package/dist/serve/entries/worker-runtime.js.map +1 -1
  73. package/dist/serve/init-payload.d.ts +4 -0
  74. package/dist/serve/init-payload.d.ts.map +1 -1
  75. package/dist/serve/namespace.d.ts +6 -0
  76. package/dist/serve/namespace.d.ts.map +1 -1
  77. package/dist/serve/namespace.js +36 -5
  78. package/dist/serve/namespace.js.map +1 -1
  79. package/dist/serve/rules.d.ts +3 -3
  80. package/dist/serve/rules.d.ts.map +1 -1
  81. package/dist/serve/rules.js +56 -25
  82. package/dist/serve/rules.js.map +1 -1
  83. package/dist/serve/runtime/chip.d.ts.map +1 -1
  84. package/dist/serve/runtime/chip.js +32 -1
  85. package/dist/serve/runtime/chip.js.map +1 -1
  86. package/dist/serve/runtime/manifest.d.ts +1 -1
  87. package/dist/serve/runtime/manifest.d.ts.map +1 -1
  88. package/dist/serve/runtime/manifest.js +1 -1
  89. package/dist/serve/runtime/manifest.js.map +1 -1
  90. package/dist/serve/runtime/status.d.ts.map +1 -1
  91. package/dist/serve/runtime/status.js +23 -0
  92. package/dist/serve/runtime/status.js.map +1 -1
  93. package/dist/serve/sandbox-session.d.ts +1 -0
  94. package/dist/serve/sandbox-session.d.ts.map +1 -1
  95. package/dist/serve/sandbox-session.js +10 -4
  96. package/dist/serve/sandbox-session.js.map +1 -1
  97. package/dist/serve/server.d.ts +7 -0
  98. package/dist/serve/server.d.ts.map +1 -1
  99. package/dist/serve/server.js +15 -1
  100. package/dist/serve/server.js.map +1 -1
  101. package/dist/serve/site-ui/404.html +1 -1
  102. package/dist/serve/site-ui/_astro/{RulesCodeEditor.CzwHdCi9.js → RulesCodeEditor.XtDmHOlN.js} +1 -1
  103. package/dist/serve/site-ui/_astro/_example_.B9DG9HLJ.css +1 -0
  104. package/dist/serve/site-ui/_astro/_slug_.Da5VLhW1.css +1 -0
  105. package/dist/serve/site-ui/_astro/_studio_.BnfYCKoI.css +1 -0
  106. package/dist/serve/site-ui/_astro/app.B52xr-DK.js +1 -0
  107. package/dist/serve/site-ui/_astro/app.DWBraPUw.js +12 -0
  108. package/dist/serve/site-ui/_astro/app.pMSnN6Qw.css +1 -0
  109. package/dist/serve/site-ui/_astro/chess-showcase.B72XYeoh.js +354 -0
  110. package/dist/serve/site-ui/_astro/{example-runtime.B0byNQOl.js → example-runtime.C25_lx6Z.js} +2 -26
  111. package/dist/serve/site-ui/_astro/index.Tb-31efc.css +1 -0
  112. package/dist/serve/site-ui/_astro/index.UkCBr-_0.css +1 -0
  113. package/dist/serve/site-ui/_astro/sandbox-controls.DEpnKg-O.js +1169 -0
  114. package/dist/serve/site-ui/_astro/seed.Co0yUJY5.js +20 -0
  115. package/dist/serve/site-ui/_astro/writes.rpjrFzmu.js +102 -0
  116. package/dist/serve/site-ui/auth/index.html +5 -3
  117. package/dist/serve/site-ui/david-east.jpg +0 -0
  118. package/dist/serve/site-ui/docs/_rhythm/index.html +6 -3
  119. package/dist/serve/site-ui/docs/_rhythm.md +1 -1
  120. package/dist/serve/site-ui/docs/agent/set-up-your-agent/index.html +45 -17
  121. package/dist/serve/site-ui/docs/agent/set-up-your-agent.md +20 -17
  122. package/dist/serve/site-ui/docs/agent/watch-and-review/index.html +6 -5
  123. package/dist/serve/site-ui/docs/agent/watch-and-review.md +1 -5
  124. package/dist/serve/site-ui/docs/agent/work-with-an-agent/index.html +8 -5
  125. package/dist/serve/site-ui/docs/agent/work-with-an-agent.md +4 -4
  126. package/dist/serve/site-ui/docs/ai-compat/index.html +5 -2
  127. package/dist/serve/site-ui/docs/api-reference/index.html +6 -2
  128. package/dist/serve/site-ui/docs/api-reference.md +1 -0
  129. package/dist/serve/site-ui/docs/app-compat/index.html +5 -2
  130. package/dist/serve/site-ui/docs/auth-compat/index.html +5 -2
  131. package/dist/serve/site-ui/docs/build/ai-logic/index.html +5 -2
  132. package/dist/serve/site-ui/docs/build/authentication/index.html +7 -4
  133. package/dist/serve/site-ui/docs/build/authentication.md +2 -2
  134. package/dist/serve/site-ui/docs/build/cloud-firestore/index.html +34 -53
  135. package/dist/serve/site-ui/docs/build/cloud-firestore.md +40 -36
  136. package/dist/serve/site-ui/docs/build/cloud-messaging/index.html +5 -2
  137. package/dist/serve/site-ui/docs/build/cloud-storage/index.html +6 -3
  138. package/dist/serve/site-ui/docs/build/cloud-storage.md +1 -1
  139. package/dist/serve/site-ui/docs/build/realtime-database/index.html +9 -6
  140. package/dist/serve/site-ui/docs/build/realtime-database.md +4 -4
  141. package/dist/serve/site-ui/docs/conformance-scores/index.html +5 -2
  142. package/dist/serve/site-ui/docs/create-pyric-reference-api/index.html +6 -3
  143. package/dist/serve/site-ui/docs/create-pyric-reference-api.md +1 -1
  144. package/dist/serve/site-ui/docs/database-compat/index.html +5 -2
  145. package/dist/serve/site-ui/docs/examples/chess/index.html +649 -250
  146. package/dist/serve/site-ui/docs/examples/chess.md +45 -16
  147. package/dist/serve/site-ui/docs/examples/index.html +6 -3
  148. package/dist/serve/site-ui/docs/firestore-compat/index.html +5 -2
  149. package/dist/serve/site-ui/docs/functions-rtdb-compat/index.html +5 -2
  150. package/dist/serve/site-ui/docs/get-started/how-the-swap-works/index.html +9 -6
  151. package/dist/serve/site-ui/docs/get-started/how-the-swap-works.md +5 -5
  152. package/dist/serve/site-ui/docs/get-started/nextjs/index.html +9 -6
  153. package/dist/serve/site-ui/docs/get-started/nextjs.md +4 -4
  154. package/dist/serve/site-ui/docs/get-started/start-building/index.html +41 -10
  155. package/dist/serve/site-ui/docs/get-started/start-building.md +21 -10
  156. package/dist/serve/site-ui/docs/get-started/vite/index.html +6 -3
  157. package/dist/serve/site-ui/docs/get-started/vite.md +1 -1
  158. package/dist/serve/site-ui/docs/index.json +203 -168
  159. package/dist/serve/site-ui/docs/messaging-compat/index.html +5 -2
  160. package/dist/serve/site-ui/docs/observe/resolve-runtime-status/index.html +5 -2
  161. package/dist/serve/site-ui/docs/observe/see-whats-happening/index.html +6 -4
  162. package/dist/serve/site-ui/docs/observe/see-whats-happening.md +2 -4
  163. package/dist/serve/site-ui/docs/observe/shape-your-data/index.html +9 -6
  164. package/dist/serve/site-ui/docs/observe/shape-your-data.md +4 -4
  165. package/dist/serve/site-ui/docs/overview/index.html +16 -10
  166. package/dist/serve/site-ui/docs/overview.md +19 -11
  167. package/dist/serve/site-ui/docs/pyric-admin-app-reference-api/index.html +5 -2
  168. package/dist/serve/site-ui/docs/pyric-admin-auth-reference-api/index.html +5 -2
  169. package/dist/serve/site-ui/docs/pyric-admin-database-reference-api/index.html +5 -2
  170. package/dist/serve/site-ui/docs/pyric-admin-firestore-reference-api/index.html +8 -5
  171. package/dist/serve/site-ui/docs/pyric-admin-firestore-reference-api.md +3 -3
  172. package/dist/serve/site-ui/docs/pyric-admin-messaging-reference-api/index.html +5 -2
  173. package/dist/serve/site-ui/docs/pyric-admin-reference-api/index.html +501 -0
  174. package/dist/serve/site-ui/docs/pyric-admin-reference-api.md +587 -0
  175. package/dist/serve/site-ui/docs/pyric-admin-storage-reference-api/index.html +5 -2
  176. package/dist/serve/site-ui/docs/pyric-ai-reference-api/index.html +5 -2
  177. package/dist/serve/site-ui/docs/pyric-ai-scripting-reference-api/index.html +5 -2
  178. package/dist/serve/site-ui/docs/pyric-app-reference-api/index.html +5 -2
  179. package/dist/serve/site-ui/docs/pyric-auth-reference-api/index.html +5 -2
  180. package/dist/serve/site-ui/docs/pyric-cli-assurance-browser-reference-api/index.html +5 -2
  181. package/dist/serve/site-ui/docs/pyric-cli-assurance-reference-api/index.html +5 -2
  182. package/dist/serve/site-ui/docs/pyric-cli-bridge-client-reference-api/index.html +5 -2
  183. package/dist/serve/site-ui/docs/pyric-cli-bridge-reference-api/index.html +5 -2
  184. package/dist/serve/site-ui/docs/pyric-cli-conformance-browser-reference-api/index.html +5 -2
  185. package/dist/serve/site-ui/docs/pyric-cli-conformance-docs-reference-api/index.html +5 -2
  186. package/dist/serve/site-ui/docs/pyric-cli-conformance-reference-api/index.html +5 -2
  187. package/dist/serve/site-ui/docs/pyric-cli-credentials-node-reference-api/index.html +5 -2
  188. package/dist/serve/site-ui/docs/pyric-cli-discover-reference-api/index.html +5 -2
  189. package/dist/serve/site-ui/docs/pyric-cli-next-reference-api/index.html +5 -2
  190. package/dist/serve/site-ui/docs/pyric-cli-register-app-bridge-reference-api/index.html +5 -2
  191. package/dist/serve/site-ui/docs/pyric-cli-register-reference-api/index.html +5 -2
  192. package/dist/serve/site-ui/docs/pyric-cli-remote-reference-api/index.html +6 -3
  193. package/dist/serve/site-ui/docs/pyric-cli-remote-reference-api.md +1 -1
  194. package/dist/serve/site-ui/docs/pyric-cli-serve-worker-reference-api/index.html +5 -2
  195. package/dist/serve/site-ui/docs/pyric-cli-verify-reference-api/index.html +5 -2
  196. package/dist/serve/site-ui/docs/pyric-cli-vite-reference-api/index.html +5 -2
  197. package/dist/serve/site-ui/docs/pyric-database-reference-api/index.html +166 -95
  198. package/dist/serve/site-ui/docs/pyric-database-reference-api.md +107 -1
  199. package/dist/serve/site-ui/docs/pyric-firestore-reference-api/index.html +5 -2
  200. package/dist/serve/site-ui/docs/pyric-messaging-reference-api/index.html +10 -4
  201. package/dist/serve/site-ui/docs/pyric-messaging-reference-api.md +5 -2
  202. package/dist/serve/site-ui/docs/pyric-messaging-sw-reference-api/index.html +10 -4
  203. package/dist/serve/site-ui/docs/pyric-messaging-sw-reference-api.md +5 -2
  204. package/dist/serve/site-ui/docs/pyric-rules-reference-api/index.html +5 -2
  205. package/dist/serve/site-ui/docs/pyric-sandbox-database-reference-api/index.html +5 -2
  206. package/dist/serve/site-ui/docs/pyric-sandbox-firestore-reference-api/index.html +6 -3
  207. package/dist/serve/site-ui/docs/pyric-sandbox-firestore-reference-api.md +2 -2
  208. package/dist/serve/site-ui/docs/pyric-sandbox-reference-api/index.html +5 -2
  209. package/dist/serve/site-ui/docs/pyric-storage-reference-api/index.html +5 -2
  210. package/dist/serve/site-ui/docs/pyric-ui-agents-reference-api/index.html +5 -2
  211. package/dist/serve/site-ui/docs/pyric-ui-auth-hooks-reference-api/index.html +5 -2
  212. package/dist/serve/site-ui/docs/pyric-ui-auth-reference-api/index.html +5 -2
  213. package/dist/serve/site-ui/docs/pyric-ui-events-hooks-reference-api/index.html +5 -2
  214. package/dist/serve/site-ui/docs/pyric-ui-events-reference-api/index.html +5 -2
  215. package/dist/serve/site-ui/docs/pyric-ui-firestore-hooks-reference-api/index.html +5 -2
  216. package/dist/serve/site-ui/docs/pyric-ui-firestore-reference-api/index.html +5 -2
  217. package/dist/serve/site-ui/docs/pyric-ui-primitives-reference-api/index.html +5 -2
  218. package/dist/serve/site-ui/docs/pyric-ui-rtdb-reference-api/index.html +5 -2
  219. package/dist/serve/site-ui/docs/pyric-ui-rules-hooks-reference-api/index.html +5 -2
  220. package/dist/serve/site-ui/docs/pyric-ui-rules-reference-api/index.html +5 -2
  221. package/dist/serve/site-ui/docs/pyric-ui-storage-hooks-reference-api/index.html +5 -2
  222. package/dist/serve/site-ui/docs/pyric-ui-storage-reference-api/index.html +5 -2
  223. package/dist/serve/site-ui/docs/pyric-ui-traffic-hooks-reference-api/index.html +5 -2
  224. package/dist/serve/site-ui/docs/pyric-ui-traffic-reference-api/index.html +5 -2
  225. package/dist/serve/site-ui/docs/reference/cli/index.html +153 -0
  226. package/dist/serve/site-ui/docs/reference/cli.md +114 -0
  227. package/dist/serve/site-ui/docs/rules-compat/index.html +5 -2
  228. package/dist/serve/site-ui/docs/secure/audit-your-rules/index.html +5 -2
  229. package/dist/serve/site-ui/docs/secure/firestore-rules-limits/index.html +5 -2
  230. package/dist/serve/site-ui/docs/secure/read-a-denial/index.html +6 -3
  231. package/dist/serve/site-ui/docs/secure/read-a-denial.md +1 -1
  232. package/dist/serve/site-ui/docs/secure/rtdb-rules-in-typescript/index.html +5 -2
  233. package/dist/serve/site-ui/docs/secure/rules-standard-library/index.html +5 -2
  234. package/dist/serve/site-ui/docs/secure/secure-it-with-rules/index.html +5 -2
  235. package/dist/serve/site-ui/docs/secure/simulate-and-lint/index.html +5 -2
  236. package/dist/serve/site-ui/docs/secure/write-a-rules-test-suite/index.html +5 -2
  237. package/dist/serve/site-ui/docs/ship/ship-to-production/index.html +7 -4
  238. package/dist/serve/site-ui/docs/ship/ship-to-production.md +2 -2
  239. package/dist/serve/site-ui/docs/ship/test-in-node/index.html +9 -6
  240. package/dist/serve/site-ui/docs/ship/test-in-node.md +4 -4
  241. package/dist/serve/site-ui/docs/storage-compat/index.html +5 -2
  242. package/dist/serve/site-ui/docs/trust/how-we-know-it-matches-firebase/index.html +5 -2
  243. package/dist/serve/site-ui/docs/trust/rules-standard-library/index.html +5 -2
  244. package/dist/serve/site-ui/docs/trust/versioning-and-compatibility/index.html +5 -2
  245. package/dist/serve/site-ui/docs/tutorial/index.html +10 -7
  246. package/dist/serve/site-ui/docs/tutorial.md +4 -4
  247. package/dist/serve/site-ui/examples/chess/index.html +4 -2
  248. package/dist/serve/site-ui/firestore/index.html +5 -3
  249. package/dist/serve/site-ui/hero-charcoal.jpg +0 -0
  250. package/dist/serve/site-ui/index.html +9 -7
  251. package/dist/serve/site-ui/llms.txt +4 -3
  252. package/dist/serve/site-ui/pyric-campfire-full.png +0 -0
  253. package/dist/serve/site-ui/pyric-campfire.png +0 -0
  254. package/dist/serve/site-ui/rtdb/index.html +5 -3
  255. package/dist/serve/site-ui/settings/index.html +5 -3
  256. package/dist/serve/site-ui/storage/index.html +5 -3
  257. package/dist/serve/site-ui/studio/index.html +13 -0
  258. package/dist/serve/site-ui/studio-routes.json +1 -1
  259. package/dist/serve/site-ui/traffic/index.html +5 -3
  260. package/dist/serve/studio/routes.d.ts +9 -0
  261. package/dist/serve/studio/routes.d.ts.map +1 -1
  262. package/dist/serve/studio/routes.js +47 -7
  263. package/dist/serve/studio/routes.js.map +1 -1
  264. package/dist/serve/vite-ai-config.d.ts.map +1 -1
  265. package/dist/serve/vite-ai-config.js +38 -8
  266. package/dist/serve/vite-ai-config.js.map +1 -1
  267. package/dist/serve/vite-module-swap.d.ts.map +1 -1
  268. package/dist/serve/vite-module-swap.js +0 -28
  269. package/dist/serve/vite-module-swap.js.map +1 -1
  270. package/dist/serve/vite-rules-source.d.ts.map +1 -1
  271. package/dist/serve/vite-rules-source.js +28 -7
  272. package/dist/serve/vite-rules-source.js.map +1 -1
  273. package/dist/serve/vite-sandbox-generation.d.ts +1 -1
  274. package/dist/serve/vite-sandbox-generation.d.ts.map +1 -1
  275. package/dist/serve/vite-sandbox-generation.js +1 -1
  276. package/dist/serve/vite-sandbox-generation.js.map +1 -1
  277. package/dist/serve/worker/client/disconnect.js +1 -1
  278. package/dist/serve/worker/client/disconnect.js.map +1 -1
  279. package/dist/serve/worker/client/presence.d.ts.map +1 -1
  280. package/dist/serve/worker/client/presence.js +18 -5
  281. package/dist/serve/worker/client/presence.js.map +1 -1
  282. package/dist/serve/worker/host-ai.d.ts.map +1 -1
  283. package/dist/serve/worker/host-ai.js +15 -0
  284. package/dist/serve/worker/host-ai.js.map +1 -1
  285. package/dist/serve/worker/protocol.d.ts +4 -0
  286. package/dist/serve/worker/protocol.d.ts.map +1 -1
  287. package/dist/serve/worker/protocol.js.map +1 -1
  288. package/dist/serve/worker/serve-init.d.ts.map +1 -1
  289. package/dist/serve/worker/serve-init.js +7 -1
  290. package/dist/serve/worker/serve-init.js.map +1 -1
  291. package/package.json +5 -5
  292. package/src/bridge/server/mcp-contract.ts +3 -0
  293. package/src/cli/cli.test.ts +2 -2
  294. package/src/cli/index.ts +51 -42
  295. package/src/cli/mcp-proxy.ts +10 -11
  296. package/src/cli/parse-args.ts +61 -15
  297. package/src/cli/pyric-config.ts +45 -0
  298. package/src/cli/rules.ts +31 -4
  299. package/src/cli/{dev-runner.ts → sandbox-runner.ts} +117 -72
  300. package/src/cli/serve.ts +115 -47
  301. package/src/cli/service-command-records/storage-rules-resolve.ts +4 -0
  302. package/src/cli/service-commands.generated.ts +2 -0
  303. package/src/cli/snapshot.ts +5 -5
  304. package/src/cli/verify.ts +1 -1
  305. package/src/functions-rtdb/development-runtime.ts +1 -1
  306. package/src/functions-rtdb/project.ts +8 -8
  307. package/src/next/guard.ts +1 -1
  308. package/src/remote/index.ts +9 -9
  309. package/src/serve/bundler.ts +10 -10
  310. package/src/serve/entries/ai.ts +27 -1
  311. package/src/serve/entries/auth-helper-dom.ts +1 -1
  312. package/src/serve/entries/auth-helper-runtime.ts +1 -1
  313. package/src/serve/entries/firestore.ts +3 -3
  314. package/src/serve/entries/keepalive.ts +1 -1
  315. package/src/serve/entries/runtime.ts +19 -14
  316. package/src/serve/entries/storage.ts +1 -1
  317. package/src/serve/entries/worker-runtime.ts +1 -1
  318. package/src/serve/init-payload.ts +4 -0
  319. package/src/serve/namespace.ts +44 -5
  320. package/src/serve/rules.ts +61 -26
  321. package/src/serve/runtime/chip.ts +38 -1
  322. package/src/serve/runtime/manifest.ts +1 -1
  323. package/src/serve/runtime/status.ts +24 -0
  324. package/src/serve/sandbox-session.ts +10 -4
  325. package/src/serve/server.ts +18 -1
  326. package/src/serve/studio/routes.ts +63 -7
  327. package/src/serve/vite-ai-config.ts +37 -7
  328. package/src/serve/vite-module-swap.ts +0 -28
  329. package/src/serve/vite-rules-source.ts +29 -7
  330. package/src/serve/vite-sandbox-generation.ts +1 -1
  331. package/src/serve/worker/client/disconnect.ts +1 -1
  332. package/src/serve/worker/client/presence.ts +25 -5
  333. package/src/serve/worker/host-ai.ts +15 -0
  334. package/src/serve/worker/protocol.ts +2 -1
  335. package/src/serve/worker/serve-init.ts +5 -1
  336. package/README.md.orig +0 -141
  337. package/dist/cli/dev-runner.d.ts.map +0 -1
  338. package/dist/cli/dev-runner.js.map +0 -1
  339. package/dist/serve/site-ui/_astro/_example_.jevZ7OdV.css +0 -1
  340. package/dist/serve/site-ui/_astro/_slug_.C6QfVOxJ.css +0 -1
  341. package/dist/serve/site-ui/_astro/_slug_.CXh9yTP1.css +0 -1
  342. package/dist/serve/site-ui/_astro/_studio_.B6b_G1ev.css +0 -1
  343. package/dist/serve/site-ui/_astro/app.B7pBzz5N.js +0 -11
  344. package/dist/serve/site-ui/_astro/app.BsFsMjn0.css +0 -1
  345. package/dist/serve/site-ui/_astro/app.boex5SnQ.js +0 -1
  346. package/dist/serve/site-ui/_astro/chess-showcase.DDqE-QGe.js +0 -1
  347. package/dist/serve/site-ui/_astro/chess-source-explorer.C5k9c5yK.js +0 -1
  348. package/dist/serve/site-ui/_astro/chess-v2.CFHmd3UL.js +0 -354
  349. package/dist/serve/site-ui/_astro/index.uwPvjH_1.js +0 -1
  350. package/dist/serve/site-ui/_astro/jsx-runtime.D_zvdyIk.js +0 -9
  351. package/dist/serve/site-ui/_astro/reads.Dh3z0fJy.js +0 -1
  352. package/dist/serve/site-ui/_astro/sandbox-controls.C2IqtoSw.js +0 -1252
  353. package/dist/serve/site-ui/_astro/seed.CCmj00su.js +0 -20
  354. package/dist/serve/site-ui/docs/trust/storage-rules-stdlib-research/index.html +0 -582
  355. package/dist/serve/site-ui/docs/trust/storage-rules-stdlib-research.md +0 -448
  356. package/dist/serve/site-ui/examples/firestore-first-write/index.html +0 -2
@@ -1,5 +1,6 @@
1
1
  /**
2
- * The `pyric dev` child runner "one command, not two" (adoption design):
2
+ * The `pyric sandbox` child runner starts the configured application command
3
+ * after the host is ready:
3
4
  * after the host is up, run the user's OWN dev command with the environment
4
5
  * activated so their unchanged firebase-admin/firebase imports resolve to the
5
6
  * pyric sandbox:
@@ -7,23 +8,20 @@
7
8
  * PYRIC_SANDBOX=remote:<serve url> (the activator)
8
9
  * NODE_OPTIONS += --import @pyric/cli/register (the substitution seam)
9
10
  *
10
- * Child-command precedence: explicit `pyric dev -- <cmd>` wins; else the
11
- * project package.json `dev` script (via the detected package manager); else
12
- * host-only (today's behavior). `--no-run` forces host-only; `--json`
13
- * defaults to host-only (an explicit `--` still wins — agents can opt in).
11
+ * Child-command precedence: `--no-run`, an explicit command, `--json`, the
12
+ * `pyric.json` command, then host-only. An explicit command still runs with
13
+ * `--json`.
14
14
  *
15
15
  * Everything decision-shaped here is pure and exported for the unit suite;
16
16
  * only `spawnDevChild` touches the process table.
17
17
  */
18
18
  import { spawn, type ChildProcess } from 'node:child_process';
19
- import { existsSync, readFileSync } from 'node:fs';
20
- import { join } from 'node:path';
21
19
 
22
20
  // ─── First-run race guard ───────────────────────────────────────────────────
23
21
 
24
22
  /**
25
23
  * Wait (bounded) for a browser tab to register as the bridge's sandbox peer
26
- * before the child spawns. `pyric dev` opens the tab and spawns the child in
24
+ * before the child spawns. `pyric sandbox` opens the tab and spawns the child in
27
25
  * the same breath; a child whose first line is a sandbox op otherwise races
28
26
  * the tab's boot and dies on the no-tab fail-fast — the exact first-run of
29
27
  * the two-command story. Polls `/__pyric/health` (`sandboxConnected`).
@@ -63,65 +61,74 @@ export async function waitForSandboxPeer(
63
61
 
64
62
  // ─── Child-command resolution (pure) ───────────────────────────────────────
65
63
 
66
- export type PackageManager = 'bun' | 'pnpm' | 'yarn' | 'npm';
67
-
68
- /** Lockfile sniff, mirroring how init/vendor pick their install hints. */
69
- export function detectPackageManager(cwd: string): PackageManager {
70
- if (existsSync(join(cwd, 'bun.lock')) || existsSync(join(cwd, 'bun.lockb'))) return 'bun';
71
- if (existsSync(join(cwd, 'pnpm-lock.yaml'))) return 'pnpm';
72
- if (existsSync(join(cwd, 'yarn.lock'))) return 'yarn';
73
- return 'npm';
64
+ export interface SandboxChildPlan {
65
+ /** argv to spawn — argv[0] is the executable. */
66
+ argv: string[];
67
+ /** Human-readable command for the pre-spawn line. */
68
+ label: string;
74
69
  }
75
70
 
76
- /** The project's `dev` script, or null (no package.json / no script). */
77
- export function readDevScript(cwd: string): string | null {
78
- try {
79
- const pkg = JSON.parse(readFileSync(join(cwd, 'package.json'), 'utf8')) as {
80
- scripts?: Record<string, unknown>;
81
- };
82
- const dev = pkg.scripts?.dev;
83
- return typeof dev === 'string' && dev.trim().length > 0 ? dev : null;
84
- } catch {
85
- return null;
71
+ export type DevChildPlan = SandboxChildPlan;
72
+
73
+ /**
74
+ * Tokenize a command string into an argv array, preserving quoted strings.
75
+ */
76
+ export function parseCommandString(cmd: string): string[] {
77
+ const parts: string[] = [];
78
+ const regex = /[^\s"']+|"([^"]*)"|'([^']*)'/g;
79
+ let match: RegExpExecArray | null;
80
+ while ((match = regex.exec(cmd)) !== null) {
81
+ if (match[1] !== undefined) {
82
+ parts.push(match[1]);
83
+ } else if (match[2] !== undefined) {
84
+ parts.push(match[2]);
85
+ } else {
86
+ parts.push(match[0]);
87
+ }
86
88
  }
89
+ return parts;
87
90
  }
88
91
 
89
- export interface DevChildPlan {
90
- /** argv to spawn — argv[0] is the executable, no shell. */
91
- argv: string[];
92
- /** Human-readable command for the pre-spawn line. */
93
- label: string;
92
+ export interface ResolveSandboxChildOptions {
93
+ explicitCommand?: string[] | null;
94
+ passthrough?: string[];
95
+ configCommand?: string | null;
96
+ noRun?: boolean;
97
+ json?: boolean;
94
98
  }
95
99
 
96
100
  /**
97
101
  * Decide what (if anything) to run after the host is up.
98
102
  *
99
- * Matrix:
100
- * --no-run → null, always
101
- * `-- <cmd>` → that command (wins over everything else)
102
- * --json (no `--`) → null (machine mode defaults to host-only)
103
- * package.json dev script `<pm> run dev`
104
- * none of the above → null (host-only, today's behavior)
105
- *
106
- * A `dev` script that itself invokes `pyric dev` is treated as absent —
107
- * running it would recurse forever (pyric dev → npm run dev → pyric dev …).
103
+ * Precedence:
104
+ * --no-run → null, always
105
+ * explicit CLI command (args or `--`) → that command (wins over everything else)
106
+ * --json (with no explicit command) → null (machine mode defaults to host-only)
107
+ * pyric.json `command` parsed command argv
108
+ * none of the above → null (host-only)
108
109
  */
109
- export function resolveDevChild(opts: {
110
- passthrough: string[];
111
- noRun: boolean;
112
- json: boolean;
113
- devScript: string | null;
114
- packageManager: PackageManager;
115
- }): DevChildPlan | null {
110
+ export function resolveSandboxChild(opts: ResolveSandboxChildOptions): SandboxChildPlan | null {
116
111
  if (opts.noRun) return null;
117
- if (opts.passthrough.length > 0) {
118
- return { argv: [...opts.passthrough], label: opts.passthrough.join(' ') };
112
+
113
+ let activeCommand: string[] | null = null;
114
+ if (opts.explicitCommand && opts.explicitCommand.length > 0) {
115
+ activeCommand = opts.explicitCommand;
116
+ } else if (opts.passthrough && opts.passthrough.length > 0) {
117
+ activeCommand = opts.passthrough;
118
+ }
119
+
120
+ if (activeCommand !== null) {
121
+ return { argv: [...activeCommand], label: activeCommand.join(' ') };
119
122
  }
123
+
120
124
  if (opts.json) return null;
121
- if (opts.devScript && !/(^|[\s;&|])pyric\s+dev\b/.test(opts.devScript)) {
122
- const label = `${opts.packageManager} run dev`;
123
- return { argv: [opts.packageManager, 'run', 'dev'], label };
125
+
126
+ if (opts.configCommand && opts.configCommand.trim().length > 0) {
127
+ const trimmed = opts.configCommand.trim();
128
+ const argv = parseCommandString(trimmed);
129
+ return { argv, label: trimmed };
124
130
  }
131
+
125
132
  return null;
126
133
  }
127
134
 
@@ -215,37 +222,73 @@ export function createLinePrefixer(
215
222
 
216
223
  // ─── Spawn + lifecycle ─────────────────────────────────────────────────────
217
224
 
218
- export interface DevChildHandle {
219
- /** Resolves with the exit code to propagate once the child is gone. */
225
+ /** How long a signalled child may linger before SIGKILL. */
226
+ const FORCE_KILL_AFTER_MS = 2_000;
227
+
228
+ const SHELL_OPERATORS = new Set(['&&', '||', '|', ';', '&']);
229
+
230
+ export interface SandboxChildHandle {
220
231
  exited: Promise<number>;
221
- /** Forward a signal (Ctrl-C / SIGTERM). Marks the exit as user-initiated
222
- * so the propagated code is 0, and SIGKILLs a child that lingers. */
223
232
  signal(sig: NodeJS.Signals): void;
224
233
  child: ChildProcess;
225
234
  }
226
235
 
227
- /** How long a signalled child may linger before SIGKILL. */
228
- const FORCE_KILL_AFTER_MS = 2_000;
236
+ export type DevChildHandle = SandboxChildHandle;
229
237
 
230
238
  /**
231
239
  * Spawn the child with inherited stdin and `[dev]`-prefixed stdout/stderr.
232
240
  * In `--json` mode ALL child output goes to stderr — stdout carries exactly
233
241
  * the one machine line (the serve --json contract).
234
242
  */
235
- export function spawnDevChild(
236
- plan: DevChildPlan,
243
+ export function spawnSandboxChild(
244
+ plan: SandboxChildPlan,
237
245
  opts: { cwd: string; env: NodeJS.ProcessEnv; json: boolean },
238
- ): DevChildHandle {
239
- const [command, ...args] = plan.argv;
240
- const child = spawn(command!, args, {
241
- cwd: opts.cwd,
242
- env: opts.env,
243
- stdio: ['inherit', 'pipe', 'pipe'],
244
- // Own a process group so shutdown reaches package-manager grandchildren
245
- // (for example `bun run dev` -> `node server.js`) rather than orphaning
246
- // the actual dev server when only the runner exits.
247
- detached: process.platform !== 'win32',
248
- });
246
+ ): SandboxChildHandle {
247
+ const parts = [...plan.argv];
248
+ const env: NodeJS.ProcessEnv = { ...opts.env };
249
+
250
+ // 1. Extract leading KEY=VAL assignments (e.g. PORT=8080 node server.js)
251
+ while (parts.length > 0 && /^[A-Za-z_][A-Za-z0-9_]*=/.test(parts[0]!)) {
252
+ const token = parts.shift()!;
253
+ const eq = token.indexOf('=');
254
+ const key = token.slice(0, eq);
255
+ const val = token.slice(eq + 1);
256
+ env[key] = val;
257
+ }
258
+
259
+ // 2. Check for discrete shell operators (&&, ||, |, ;) among top-level tokens
260
+ const hasShellOperators = parts.some((token) => SHELL_OPERATORS.has(token));
261
+
262
+ // 3. Warn if runtime is bun or deno
263
+ if (parts.length > 0 && (parts[0] === 'bun' || parts[0] === 'deno')) {
264
+ const runner = parts[0];
265
+ const target = opts.json ? process.stderr : process.stdout;
266
+ target.write(
267
+ ` ⚠ ${runner} detected: pyric intercepts Firebase imports via Node.js module loader hooks (NODE_OPTIONS="--import ..."). ` +
268
+ `${runner} does not evaluate Node loader hooks — SDK calls in this process will not reach the pyric sandbox. ` +
269
+ `Use \`node\` (or \`npx tsx\`) to run with the sandbox swap.\n`,
270
+ );
271
+ }
272
+
273
+ const [command, ...args] = parts;
274
+ let child: ChildProcess;
275
+ if (hasShellOperators) {
276
+ child = spawn(plan.label, [], {
277
+ cwd: opts.cwd,
278
+ env,
279
+ stdio: ['inherit', 'pipe', 'pipe'],
280
+ shell: true,
281
+ detached: process.platform !== 'win32',
282
+ });
283
+ } else {
284
+ child = spawn(command!, args, {
285
+ cwd: opts.cwd,
286
+ env,
287
+ stdio: ['inherit', 'pipe', 'pipe'],
288
+ shell: process.platform === 'win32',
289
+ detached: process.platform !== 'win32',
290
+ });
291
+ }
249
292
 
250
293
  const outTarget = opts.json ? process.stderr : process.stdout;
251
294
  const out = createLinePrefixer('[dev] ', (line) => outTarget.write(line));
@@ -267,7 +310,7 @@ export function spawnDevChild(
267
310
 
268
311
  const exited = new Promise<number>((resolve) => {
269
312
  child.once('error', (e) => {
270
- process.stderr.write(`pyric dev: failed to run \`${plan.label}\`: ${e.message}\n`);
313
+ process.stderr.write(`pyric sandbox: failed to run \`${plan.label}\`: ${e.message}\n`);
271
314
  resolve(1);
272
315
  });
273
316
  child.once('exit', (code, signal) => {
@@ -301,3 +344,5 @@ export function spawnDevChild(
301
344
  },
302
345
  };
303
346
  }
347
+
348
+ export const spawnDevChild = spawnSandboxChild;
package/src/cli/serve.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * `pyric dev` local dev server with the pyric sandbox standing in for
2
+ * `pyric sandbox` is the local server with the Pyric sandbox standing in for
3
3
  * Firebase. Feels like `firebase serve` (banner, labeled lines, port 3473,
4
4
  * SIGINT shutdown) with the sandbox-flavored extras firebase can't do: the
5
5
  * served page runs an in-browser backend, your `firestore.rules` deploy into
@@ -28,17 +28,16 @@ import { formatActivityWarning } from '../serve/activity-warning.js';
28
28
  import { consoleServeLogger, startStaticServer, stderrServeLogger, type ServeHandle } from '../serve/server.js';
29
29
  import { createBridgeMount } from '../serve/bridge-mount.js';
30
30
  import { openBrowser, shouldAutoOpen } from '../serve/open-browser.js';
31
+ import { readPyricConfig, type PyricConfig } from './pyric-config.js';
31
32
  import {
32
33
  buildChildEnv,
33
- detectPackageManager,
34
34
  formatStartupEnvExport,
35
- readDevScript,
36
35
  registerModuleUrl,
37
- resolveDevChild,
38
- spawnDevChild,
36
+ resolveSandboxChild,
37
+ spawnSandboxChild,
39
38
  waitForSandboxPeer,
40
- type DevChildHandle,
41
- } from './dev-runner.js';
39
+ type SandboxChildHandle,
40
+ } from './sandbox-runner.js';
42
41
  import {
43
42
  createFunctionsDevelopmentRuntime,
44
43
  createHttpFunctionsPeerReadiness,
@@ -60,7 +59,7 @@ interface HostingConfig {
60
59
  rewrites?: Array<{ source?: string; destination?: string }>;
61
60
  }
62
61
 
63
- /** Extract the single hosting config `pyric dev` v1 supports. Arrays
62
+ /** Extract the single hosting config `pyric sandbox` supports. Arrays
64
63
  * (multi-site) take the first entry with a warning — multi-site is out of
65
64
  * scope (plan section 6). */
66
65
  export function extractHosting(config: FirebaseJson | null): HostingConfig | null {
@@ -84,7 +83,7 @@ export interface ServeRuntime {
84
83
  payload: () => InitPayload;
85
84
  /** HTTP MCP endpoint when `--bridge` is on; null otherwise. */
86
85
  mcpUrl: string | null;
87
- /** The Studio app URL (`/__pyric/ui/`) when `--ui` is on and the build is
86
+ /** The Studio hub URL (`/__pyric/ui/studio`) when `--ui` is on and the build is
88
87
  * present; null otherwise. */
89
88
  uiUrl: string | null;
90
89
  /** Persistence summary (null when `--persist` is off). */
@@ -147,6 +146,8 @@ export async function startServe(opts: {
147
146
  * (the served project's file tree) + `/__pyric/projects` (single-project
148
147
  * store over `cwd`). The Studio app's `local` mode talks to these. */
149
148
  ui?: boolean;
149
+ /** Explicit opt-in to permissive mode (default-allow RTDB rules when unconfigured). */
150
+ permissive?: boolean;
150
151
  logger?: Parameters<typeof startStaticServer>[0]['logger'];
151
152
  }): Promise<ServeRuntime> {
152
153
  const logger = opts.logger ?? consoleServeLogger();
@@ -157,7 +158,7 @@ export async function startServe(opts: {
157
158
  // Fail fast instead of pretending to reset something.
158
159
  if (opts.fresh && !opts.persist) {
159
160
  throw new Error(
160
- 'pyric dev: --fresh requires --persist (it discards .pyric/state/state.json). ' +
161
+ 'pyric sandbox: --fresh requires --persist (it discards .pyric/state/state.json). ' +
161
162
  'Browser-stored data is cleared from Studio → Settings → Reset, or DevTools → Clear site data.',
162
163
  );
163
164
  }
@@ -172,9 +173,29 @@ export async function startServe(opts: {
172
173
  logger.note(' ⚠ no firebase.json found — serving the current directory without hosting config');
173
174
  }
174
175
 
176
+ const pyricConfig = await readPyricConfig(opts.cwd);
177
+ if (pyricConfig.rules) {
178
+ if (!config) config = {};
179
+ if (typeof pyricConfig.rules === 'string') {
180
+ config.firestore = { ...config.firestore, rules: pyricConfig.rules };
181
+ } else {
182
+ if (pyricConfig.rules.firestore) {
183
+ config.firestore = { ...config.firestore, rules: pyricConfig.rules.firestore };
184
+ }
185
+ if (pyricConfig.rules.database) {
186
+ config.database = { ...config.database, rules: pyricConfig.rules.database };
187
+ }
188
+ if (pyricConfig.rules.storage) {
189
+ const baseStorage =
190
+ typeof config.storage === 'object' && !Array.isArray(config.storage) ? config.storage : {};
191
+ config.storage = { ...baseStorage, rules: pyricConfig.rules.storage };
192
+ }
193
+ }
194
+ }
195
+
175
196
  const hosting = extractHosting(config);
176
197
  if (Array.isArray(config?.hosting) && (config.hosting as unknown[]).length > 1) {
177
- logger.note(' ⚠ multiple hosting sites configured pyric dev v1 serves the first entry only');
198
+ logger.note(' ⚠ multiple hosting sites configured. pyric sandbox serves the first entry only');
178
199
  }
179
200
  const publicDir = resolve(opts.cwd, hosting?.public ?? '.');
180
201
  if (!existsSync(publicDir)) {
@@ -184,16 +205,16 @@ export async function startServe(opts: {
184
205
  const looksLikeBuildOutput = /(^|[\\/])(dist|build|out)$/.test(publicDir);
185
206
  const hint = looksLikeBuildOutput
186
207
  ? `\n No build yet? Run \`bun run dev\` for the live sandbox dev server, ` +
187
- `or \`bun run build\` then \`pyric dev\` to preview the production build.`
208
+ `or \`bun run build\` then \`pyric sandbox\` to preview the production build.`
188
209
  : '';
189
- throw new Error(`pyric dev: hosting.public directory does not exist: ${publicDir}${hint}`);
210
+ throw new Error(`pyric sandbox: hosting.public directory does not exist: ${publicDir}${hint}`);
190
211
  }
191
212
 
192
213
  // The import map can only remap BARE `firebase/*` specifiers. A plain bundler
193
214
  // build (`vite build`) inlines the real SDK into the app chunk, leaving
194
215
  // nothing to intercept — the page would then talk to REAL Firebase endpoints
195
216
  // with the sandbox's fake credentials while the injected banner claims
196
- // otherwise. That hole is structural, so `pyric dev` REFUSES such a dist
217
+ // otherwise. That hole is structural, so `pyric sandbox` refuses such a dist
197
218
  // rather than serving it. A pyric SANDBOX build (`vite build --mode
198
219
  // development`) carries the marker and bundles pyric's in-page adapters, so it
199
220
  // is trusted and the scan is skipped (marker present → no real SDK to find).
@@ -201,14 +222,14 @@ export async function startServe(opts: {
201
222
  const inlined = scanForInlinedFirebase(publicDir);
202
223
  if (inlined.length > 0) {
203
224
  throw new Error(
204
- `pyric dev: ${inlined[0]} bundles the REAL firebase SDK, so this dist cannot be ` +
225
+ `pyric sandbox: ${inlined[0]} bundles the real Firebase SDK, so this dist cannot be ` +
205
226
  `sandboxed — its firebase/* calls would reach LIVE Google endpoints, not the ` +
206
227
  `pyric sandbox. Two ways forward:\n` +
207
- ` (a) plain \`pyric dev\` runs the child dev-server flow (the @pyric/cli/vite ` +
228
+ ` (a) \`pyric sandbox -- <command>\` runs the child server with the @pyric/cli/vite ` +
208
229
  `plugin swaps firebase/* live) — use \`bun run dev\`;\n` +
209
230
  ` (b) rebuild as a self-contained sandbox bundle and serve THAT: ` +
210
231
  `\`vite build --mode development\` (or pyric({ swapInBuild: true })) then ` +
211
- `\`pyric dev\`.\n` +
232
+ `\`pyric sandbox\`.\n` +
212
233
  `A plain \`vite build\` is your production build — deploy it, don't sandbox it.`,
213
234
  );
214
235
  }
@@ -285,15 +306,16 @@ export async function startServe(opts: {
285
306
  studio: opts.ui ? { siteUiDir } : false,
286
307
  bridgeUrl: () => mount && origin.port > 0 ? mount.wsUrl(origin) : null,
287
308
  activity: (incident) => logger.note(formatActivityWarning(incident)),
309
+ permissive: opts.permissive,
288
310
  logger,
289
311
  });
290
312
  } catch (error) {
291
313
  bundle.dispose?.();
292
314
  if (error instanceof SandboxSeedError) {
293
315
  if (error.kind === 'read') {
294
- throw new Error(`pyric dev: failed to read --seed ${error.path}: ${error.detail}`);
316
+ throw new Error(`pyric sandbox: failed to read --seed ${error.path}: ${error.detail}`);
295
317
  }
296
- throw new Error(`pyric dev: --seed must be a JSON object of "collection/doc" fields, ${error.detail}`);
318
+ throw new Error(`pyric sandbox: --seed must be a JSON object of "collection/doc" to fields, ${error.detail}`);
297
319
  }
298
320
  throw error;
299
321
  }
@@ -376,7 +398,7 @@ export async function startServe(opts: {
376
398
  };
377
399
  if (bundle.dispose) handle.server.once('close', bundle.dispose);
378
400
  handle.server.once('close', () => void closeOwnedResources());
379
- const uiUrl = siteUiDir ? `${handle.url}/__pyric/ui/` : null;
401
+ const uiUrl = siteUiDir ? `${handle.url}/__pyric/ui/studio` : null;
380
402
  const docsUrl = siteUiDir ? `${handle.url}/__pyric/ui/docs/` : null;
381
403
 
382
404
  // Hot-reload: the static adapter observes the filesystem; the session owns
@@ -547,7 +569,7 @@ export function bridgeEnabledFromFlags(flags: { get(key: string): unknown }): bo
547
569
  * A dev child also implies the bridge: the runner injects
548
570
  * `PYRIC_SANDBOX=remote:<url>` into the child, whose remote client dials the
549
571
  * `/__pyric/sandbox` WS — without the bridge mount that upgrade 404s and
550
- * plain `pyric dev -- <cmd>` (or a detected dev script) breaks its own
572
+ * `pyric sandbox -- <cmd>` breaks its own
551
573
  * child. Pure + exported for unit coverage.
552
574
  */
553
575
  export function bridgeEnabledFor(
@@ -560,7 +582,7 @@ export function bridgeEnabledFor(
560
582
 
561
583
  /**
562
584
  * Process-level last line of defense for the dev server: a single bad
563
- * request/WS interaction must never take `pyric dev` down. Node kills the
585
+ * request/WS interaction must never take `pyric sandbox` down. Node kills the
564
586
  * process on an unhandled rejection (default `--unhandled-rejections=throw`
565
587
  * since v15) and on any uncaught exception — and the serve process hosts
566
588
  * long-lived, browser-driven machinery (WS peers, SSE streams, file watchers,
@@ -578,13 +600,13 @@ export function installServeProcessGuard(
578
600
  reason instanceof Error ? (reason.stack ?? reason.message) : String(reason);
579
601
  const onRejection = (reason: unknown): void => {
580
602
  log(
581
- ` ✖ pyric dev: UNHANDLED REJECTION (the server was kept alive please report this):\n` +
603
+ ` ✖ pyric sandbox: unhandled rejection. The server was kept alive. Please report this:\n` +
582
604
  ` ${describe(reason).split('\n').join('\n ')}`,
583
605
  );
584
606
  };
585
607
  const onException = (err: unknown): void => {
586
608
  log(
587
- ` ✖ pyric dev: UNCAUGHT EXCEPTION (the server was kept alive please report this):\n` +
609
+ ` ✖ pyric sandbox: uncaught exception. The server was kept alive. Please report this:\n` +
588
610
  ` ${describe(err).split('\n').join('\n ')}`,
589
611
  );
590
612
  };
@@ -596,10 +618,49 @@ export function installServeProcessGuard(
596
618
  };
597
619
  }
598
620
 
621
+ function resolveServePort(flagPort: unknown, configPort?: number): number {
622
+ if (typeof flagPort === 'string') return Number(flagPort);
623
+ if (typeof flagPort === 'number') return flagPort;
624
+ if (typeof configPort === 'number') return configPort;
625
+ return 3473;
626
+ }
627
+
628
+ function resolveProjectIdentifier(
629
+ flagProject?: unknown,
630
+ envProject?: string,
631
+ configProject?: string,
632
+ rcDefault?: string,
633
+ ): string {
634
+ if (typeof flagProject === 'string' && flagProject.length > 0) return flagProject;
635
+ if (typeof envProject === 'string' && envProject.length > 0) return envProject;
636
+ if (typeof configProject === 'string' && configProject.length > 0) return configProject;
637
+ if (typeof rcDefault === 'string' && rcDefault.length > 0) return rcDefault;
638
+ return 'demo-project';
639
+ }
640
+
641
+ function resolveExplicitCommand(parsed: ParsedArgs): string[] | null {
642
+ if (parsed.passthrough && parsed.passthrough.length > 0) {
643
+ return parsed.passthrough;
644
+ }
645
+ if (parsed.positional.length > 0) {
646
+ return parsed.positional;
647
+ }
648
+ return null;
649
+ }
650
+
599
651
  /** CLI entry. Resolves on SIGINT/SIGTERM after a clean stop. */
600
652
  export async function runServe(parsed: ParsedArgs): Promise<number> {
653
+ const cwd = process.cwd();
654
+ let pyricConfig: PyricConfig = {};
655
+ try {
656
+ pyricConfig = await readPyricConfig(cwd);
657
+ } catch (error) {
658
+ process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
659
+ return 2;
660
+ }
661
+
601
662
  const flagPort = parsed.flags.get('port');
602
- const port = typeof flagPort === 'string' ? Number(flagPort) : 3473;
663
+ const port = resolveServePort(flagPort, pyricConfig.port);
603
664
  if (!Number.isFinite(port) || port < 0 || port > 65535) {
604
665
  process.stderr.write(`pyric: invalid --port '${flagPort}'.\n`);
605
666
  return 1;
@@ -610,7 +671,7 @@ export async function runServe(parsed: ParsedArgs): Promise<number> {
610
671
  const only = parsed.flags.get('only');
611
672
  if (typeof only === 'string' && only !== 'hosting') {
612
673
  process.stderr.write(
613
- `pyric: --only '${only}' is not supported pyric dev v1 serves hosting (with the in-page sandbox standing in for firestore/auth).\n`,
674
+ `pyric: --only '${only}' is not supported. pyric sandbox serves hosting with the in-page sandbox standing in for Firestore and Auth.\n`,
614
675
  );
615
676
  return 1;
616
677
  }
@@ -623,7 +684,6 @@ export async function runServe(parsed: ParsedArgs): Promise<number> {
623
684
  // Resolve the child plan BEFORE the server starts: a planned child implies
624
685
  // the bridge (see bridgeEnabledFor) — the child's injected PYRIC_SANDBOX
625
686
  // is useless without the /__pyric/sandbox WS mount.
626
- const cwd = process.cwd();
627
687
  let functionsProject: FunctionsRtdbProject | null;
628
688
  let functionsProjectId: string | null = null;
629
689
  try {
@@ -631,22 +691,25 @@ export async function runServe(parsed: ParsedArgs): Promise<number> {
631
691
  if (functionsProject) {
632
692
  const flagProject = parsed.flags.get('project');
633
693
  const rc = await readFirebaseRc(cwd);
634
- functionsProjectId =
635
- (typeof flagProject === 'string' ? flagProject : undefined) ??
636
- process.env.PYRIC_PROJECT ??
637
- rc?.projects?.default ??
638
- 'demo-project';
694
+ functionsProjectId = resolveProjectIdentifier(
695
+ flagProject,
696
+ process.env.PYRIC_PROJECT,
697
+ pyricConfig.project,
698
+ rc?.projects?.default,
699
+ );
639
700
  }
640
701
  } catch (error) {
641
702
  process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
642
703
  return 2;
643
704
  }
644
- const plan = resolveDevChild({
645
- passthrough: parsed.passthrough ?? [],
705
+
706
+ const explicitCommand = resolveExplicitCommand(parsed);
707
+
708
+ const plan = resolveSandboxChild({
709
+ explicitCommand,
710
+ configCommand: pyricConfig.command,
646
711
  noRun: Boolean(parsed.flags.get('no-run')),
647
712
  json,
648
- devScript: readDevScript(cwd),
649
- packageManager: detectPackageManager(cwd),
650
713
  });
651
714
  const bridgeOn = bridgeEnabledFor(parsed.flags, plan, functionsProject);
652
715
 
@@ -663,6 +726,7 @@ export async function runServe(parsed: ParsedArgs): Promise<number> {
663
726
  watch: parsed.flags.get('no-watch') ? false : undefined,
664
727
  persist: Boolean(parsed.flags.get('persist')),
665
728
  fresh: Boolean(parsed.flags.get('fresh')),
729
+ permissive: Boolean(parsed.flags.get('permissive')),
666
730
  // --ui mounts the Pyric Studio storage routes (workspace + projects).
667
731
  ui: uiOn,
668
732
  // --no-capture disables the default-on session capture. The pattern
@@ -675,7 +739,7 @@ export async function runServe(parsed: ParsedArgs): Promise<number> {
675
739
  project:
676
740
  typeof parsed.flags.get('project') === 'string'
677
741
  ? (parsed.flags.get('project') as string)
678
- : process.env.PYRIC_PROJECT ?? functionsProjectId ?? undefined,
742
+ : process.env.PYRIC_PROJECT ?? pyricConfig.project ?? functionsProjectId ?? undefined,
679
743
  });
680
744
  } catch (e) {
681
745
  process.stderr.write(`${e instanceof Error ? e.message : String(e)}\n`);
@@ -691,7 +755,7 @@ export async function runServe(parsed: ParsedArgs): Promise<number> {
691
755
  // (the banner went to stderr). Readiness probe: GET /__pyric/init.json.
692
756
  if (json) process.stdout.write(serveJsonLine(runtime) + '\n');
693
757
 
694
- // Footgun guard (hybrid-MCP plan Phase 3): plain `dev` mounts no MCP bridge,
758
+ // A host-only sandbox mounts no MCP bridge unless one is needed.
695
759
  // so a `pyric mcp-proxy` in an editor cannot attach. Nudge toward --bridge.
696
760
  if (!bridgeOn && !json) {
697
761
  process.stderr.write(
@@ -714,7 +778,7 @@ export async function runServe(parsed: ParsedArgs): Promise<number> {
714
778
  void openBrowser(targetUrl);
715
779
  }
716
780
 
717
- let devChild: DevChildHandle | null = null;
781
+ let devChild: SandboxChildHandle | null = null;
718
782
  let functionsRuntime: FunctionsDevelopmentRuntime | null = null;
719
783
  let resolveFunctionsExit!: (code: number) => void;
720
784
  const functionsExited = new Promise<number>((resolve) => { resolveFunctionsExit = resolve; });
@@ -793,7 +857,7 @@ export async function runServe(parsed: ParsedArgs): Promise<number> {
793
857
  if (result.kind === 'no-peer') {
794
858
  process.stderr.write(
795
859
  ` ⚠ functions not started — no browser tab connected after 30s. ` +
796
- `Open ${runtime.handle.url} and restart pyric dev.\n`,
860
+ `Open ${runtime.handle.url} and restart pyric sandbox.\n`,
797
861
  );
798
862
  } else if (result.kind === 'failed') {
799
863
  process.stderr.write(`${result.error.message}\n`);
@@ -810,16 +874,15 @@ export async function runServe(parsed: ParsedArgs): Promise<number> {
810
874
  for (const unsupported of ready.unsupportedTriggers) {
811
875
  process.stderr.write(
812
876
  ` ⚠ functions export ${unsupported.exportName} uses unsupported trigger ` +
813
- `${unsupported.eventType}; it will not run in pyric dev.\n`,
877
+ `${unsupported.eventType}; it will not run in pyric sandbox.\n`,
814
878
  );
815
879
  }
816
880
  }
817
881
  }
818
882
 
819
- // The child runner ("one command, not two"): run the user's own dev
820
- // command with the sandbox environment injected. Precedence: `-- <cmd>`
821
- // wins; else the package.json dev script; else host-only. --no-run forces
822
- // host-only; --json defaults to host-only (explicit `--` still wins).
883
+ // Run the explicit or configured command with the sandbox environment.
884
+ // `--no-run` forces host-only. `--json` skips the configured command, but an
885
+ // explicit command still runs.
823
886
  // (`plan` was resolved above so it could imply the bridge mount.)
824
887
  if (plan) {
825
888
  const info = json ? process.stderr : process.stdout;
@@ -838,11 +901,16 @@ export async function runServe(parsed: ParsedArgs): Promise<number> {
838
901
  ` ⚠ no browser tab connected after 30s — starting your command anyway; sandbox ops will fail until ${runtime.handle.url} is open.\n`,
839
902
  );
840
903
  }
904
+ } else if (!json) {
905
+ info.write(
906
+ ` ⓘ Auto-open is disabled (--no-open/CI). The pyric sandbox is browser-resident: ` +
907
+ `open ${runtime.handle.url} to connect if your command performs Firebase operations.\n`,
908
+ );
841
909
  }
842
910
  info.write(
843
911
  `✔ run \`${plan.label}\` — firebase-admin/firebase imports are routed to the sandbox at ${runtime.handle.url}\n`,
844
912
  );
845
- devChild = spawnDevChild(plan, {
913
+ devChild = spawnSandboxChild(plan, {
846
914
  cwd,
847
915
  json,
848
916
  env: buildChildEnv(process.env, {
@@ -895,7 +963,7 @@ export async function runServe(parsed: ParsedArgs): Promise<number> {
895
963
  if (functionsRuntime) {
896
964
  void functionsExited.then((code) => {
897
965
  if (!settled && !shuttingDown) {
898
- process.stderr.write(`pyric dev: Functions child exited unexpectedly (code ${code}).\n`);
966
+ process.stderr.write(`pyric sandbox: Functions child exited unexpectedly (code ${code}).\n`);
899
967
  finish(code === 0 ? 1 : code);
900
968
  }
901
969
  });
@@ -0,0 +1,4 @@
1
+ import { runStorageRulesResolve } from '../rules.js';
2
+ import type { ServiceCommandHandler } from '../service-commands.js';
3
+
4
+ export default runStorageRulesResolve satisfies ServiceCommandHandler;