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

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 (491) hide show
  1. package/README.md +64 -89
  2. package/dist/assurance/.generated/conformance-verdicts.d.ts +68 -1
  3. package/dist/assurance/.generated/conformance-verdicts.d.ts.map +1 -1
  4. package/dist/assurance/.generated/conformance-verdicts.js +68 -1
  5. package/dist/assurance/.generated/conformance-verdicts.js.map +1 -1
  6. package/dist/bridge/client/dispatch.d.ts +25 -19
  7. package/dist/bridge/client/dispatch.d.ts.map +1 -1
  8. package/dist/bridge/client/dispatch.js +41 -48
  9. package/dist/bridge/client/dispatch.js.map +1 -1
  10. package/dist/bridge/client/tool-family-factories.d.ts +26 -0
  11. package/dist/bridge/client/tool-family-factories.d.ts.map +1 -0
  12. package/dist/bridge/client/tool-family-factories.js +10 -0
  13. package/dist/bridge/client/tool-family-factories.js.map +1 -0
  14. package/dist/bridge/server/mcp-contract.d.ts +7 -6
  15. package/dist/bridge/server/mcp-contract.d.ts.map +1 -1
  16. package/dist/bridge/server/mcp-contract.js +9 -48
  17. package/dist/bridge/server/mcp-contract.js.map +1 -1
  18. package/dist/bridge/server/peer.d.ts +11 -1
  19. package/dist/bridge/server/peer.d.ts.map +1 -1
  20. package/dist/bridge/server/peer.js +22 -1
  21. package/dist/bridge/server/peer.js.map +1 -1
  22. package/dist/bridge/server/tool-family-factories.d.ts +30 -0
  23. package/dist/bridge/server/tool-family-factories.d.ts.map +1 -0
  24. package/dist/bridge/server/tool-family-factories.js +22 -0
  25. package/dist/bridge/server/tool-family-factories.js.map +1 -0
  26. package/dist/bridge/server/tool-metadata.d.ts +17 -18
  27. package/dist/bridge/server/tool-metadata.d.ts.map +1 -1
  28. package/dist/bridge/server/tool-metadata.js +24 -59
  29. package/dist/bridge/server/tool-metadata.js.map +1 -1
  30. package/dist/bridge/tool-families.d.ts +48 -0
  31. package/dist/bridge/tool-families.d.ts.map +1 -0
  32. package/dist/bridge/tool-families.generated.d.ts +32 -0
  33. package/dist/bridge/tool-families.generated.d.ts.map +1 -0
  34. package/dist/bridge/tool-families.generated.js +15 -0
  35. package/dist/bridge/tool-families.generated.js.map +1 -0
  36. package/dist/bridge/tool-families.js +51 -0
  37. package/dist/bridge/tool-families.js.map +1 -0
  38. package/dist/bridge/tool-family-records/conformance.d.ts +7 -0
  39. package/dist/bridge/tool-family-records/conformance.d.ts.map +1 -0
  40. package/dist/bridge/tool-family-records/conformance.js +6 -0
  41. package/dist/bridge/tool-family-records/conformance.js.map +1 -0
  42. package/dist/bridge/tool-family-records/firestore-data.d.ts +7 -0
  43. package/dist/bridge/tool-family-records/firestore-data.d.ts.map +1 -0
  44. package/dist/bridge/tool-family-records/firestore-data.js +15 -0
  45. package/dist/bridge/tool-family-records/firestore-data.js.map +1 -0
  46. package/dist/bridge/tool-family-records/firestore-inspect.d.ts +7 -0
  47. package/dist/bridge/tool-family-records/firestore-inspect.d.ts.map +1 -0
  48. package/dist/bridge/tool-family-records/firestore-inspect.js +6 -0
  49. package/dist/bridge/tool-family-records/firestore-inspect.js.map +1 -0
  50. package/dist/bridge/tool-family-records/firestore-rules.d.ts +7 -0
  51. package/dist/bridge/tool-family-records/firestore-rules.d.ts.map +1 -0
  52. package/dist/bridge/tool-family-records/firestore-rules.js +15 -0
  53. package/dist/bridge/tool-family-records/firestore-rules.js.map +1 -0
  54. package/dist/bridge/tool-family-records/firestore-simulator.d.ts +7 -0
  55. package/dist/bridge/tool-family-records/firestore-simulator.d.ts.map +1 -0
  56. package/dist/bridge/tool-family-records/firestore-simulator.js +16 -0
  57. package/dist/bridge/tool-family-records/firestore-simulator.js.map +1 -0
  58. package/dist/bridge/tool-family-records/rtdb-inspection.d.ts +7 -0
  59. package/dist/bridge/tool-family-records/rtdb-inspection.d.ts.map +1 -0
  60. package/dist/bridge/tool-family-records/rtdb-inspection.js +6 -0
  61. package/dist/bridge/tool-family-records/rtdb-inspection.js.map +1 -0
  62. package/dist/bundler/externals.d.ts +37 -0
  63. package/dist/bundler/externals.d.ts.map +1 -0
  64. package/dist/bundler/externals.js +52 -0
  65. package/dist/bundler/externals.js.map +1 -0
  66. package/dist/bundler/index.d.ts +17 -0
  67. package/dist/bundler/index.d.ts.map +1 -0
  68. package/dist/bundler/index.js +17 -0
  69. package/dist/bundler/index.js.map +1 -0
  70. package/dist/cli/inlined-sdk-scanner.d.ts +6 -0
  71. package/dist/cli/inlined-sdk-scanner.d.ts.map +1 -0
  72. package/dist/cli/inlined-sdk-scanner.js +82 -0
  73. package/dist/cli/inlined-sdk-scanner.js.map +1 -0
  74. package/dist/cli/sandbox-interlock.d.ts +97 -0
  75. package/dist/cli/sandbox-interlock.d.ts.map +1 -0
  76. package/dist/cli/sandbox-interlock.js +112 -0
  77. package/dist/cli/sandbox-interlock.js.map +1 -0
  78. package/dist/cli/sandbox-runner.d.ts +20 -12
  79. package/dist/cli/sandbox-runner.d.ts.map +1 -1
  80. package/dist/cli/sandbox-runner.js +24 -21
  81. package/dist/cli/sandbox-runner.js.map +1 -1
  82. package/dist/cli/serve.d.ts +6 -10
  83. package/dist/cli/serve.d.ts.map +1 -1
  84. package/dist/cli/serve.js +57 -77
  85. package/dist/cli/serve.js.map +1 -1
  86. package/dist/cli/unsupported-runtime.d.ts +27 -0
  87. package/dist/cli/unsupported-runtime.d.ts.map +1 -0
  88. package/dist/cli/unsupported-runtime.js +63 -0
  89. package/dist/cli/unsupported-runtime.js.map +1 -0
  90. package/dist/conformance/.generated/can-i-use-browser.d.ts +1 -1
  91. package/dist/conformance/.generated/can-i-use-browser.d.ts.map +1 -1
  92. package/dist/conformance/.generated/can-i-use-browser.js +1 -1
  93. package/dist/conformance/.generated/can-i-use-browser.js.map +1 -1
  94. package/dist/conformance/.generated/can-i-use.d.ts +1 -1
  95. package/dist/conformance/.generated/can-i-use.d.ts.map +1 -1
  96. package/dist/conformance/.generated/can-i-use.js +1 -1
  97. package/dist/conformance/.generated/can-i-use.js.map +1 -1
  98. package/dist/conformance/.generated/conformance-docs.d.ts.map +1 -1
  99. package/dist/conformance/.generated/conformance-docs.js +1 -1
  100. package/dist/conformance/.generated/conformance-docs.js.map +1 -1
  101. package/dist/functions-rtdb/child.d.ts +37 -0
  102. package/dist/functions-rtdb/child.d.ts.map +1 -1
  103. package/dist/functions-rtdb/child.js +90 -21
  104. package/dist/functions-rtdb/child.js.map +1 -1
  105. package/dist/functions-rtdb/development-runtime.d.ts +3 -0
  106. package/dist/functions-rtdb/development-runtime.d.ts.map +1 -1
  107. package/dist/functions-rtdb/development-runtime.js +1 -0
  108. package/dist/functions-rtdb/development-runtime.js.map +1 -1
  109. package/dist/google-endpoints.d.ts +67 -0
  110. package/dist/google-endpoints.d.ts.map +1 -0
  111. package/dist/google-endpoints.js +119 -0
  112. package/dist/google-endpoints.js.map +1 -0
  113. package/dist/next/runtime-env.d.ts.map +1 -1
  114. package/dist/next/runtime-env.js +5 -0
  115. package/dist/next/runtime-env.js.map +1 -1
  116. package/dist/next/with-pyric.d.ts +1 -1
  117. package/dist/next/with-pyric.js +1 -1
  118. package/dist/register/beacon.d.ts +69 -0
  119. package/dist/register/beacon.d.ts.map +1 -0
  120. package/dist/register/beacon.js +199 -0
  121. package/dist/register/beacon.js.map +1 -0
  122. package/dist/register/index.d.ts.map +1 -1
  123. package/dist/register/index.js +65 -6
  124. package/dist/register/index.js.map +1 -1
  125. package/dist/register/net-guard.d.ts +110 -0
  126. package/dist/register/net-guard.d.ts.map +1 -0
  127. package/dist/register/net-guard.js +462 -0
  128. package/dist/register/net-guard.js.map +1 -0
  129. package/dist/serve/ai-diagnostics-relay.d.ts +63 -0
  130. package/dist/serve/ai-diagnostics-relay.d.ts.map +1 -0
  131. package/dist/serve/ai-diagnostics-relay.js +125 -0
  132. package/dist/serve/ai-diagnostics-relay.js.map +1 -0
  133. package/dist/serve/ai-proxy.d.ts +92 -0
  134. package/dist/serve/ai-proxy.d.ts.map +1 -0
  135. package/dist/serve/ai-proxy.js +269 -0
  136. package/dist/serve/ai-proxy.js.map +1 -0
  137. package/dist/serve/ai-status.d.ts +44 -0
  138. package/dist/serve/ai-status.d.ts.map +1 -0
  139. package/dist/serve/ai-status.js +77 -0
  140. package/dist/serve/ai-status.js.map +1 -0
  141. package/dist/serve/ai-terminal-blocks.d.ts +97 -0
  142. package/dist/serve/ai-terminal-blocks.d.ts.map +1 -0
  143. package/dist/serve/ai-terminal-blocks.js +128 -0
  144. package/dist/serve/ai-terminal-blocks.js.map +1 -0
  145. package/dist/serve/ai-terminal-text.d.ts +36 -0
  146. package/dist/serve/ai-terminal-text.d.ts.map +1 -0
  147. package/dist/serve/ai-terminal-text.js +48 -0
  148. package/dist/serve/ai-terminal-text.js.map +1 -0
  149. package/dist/serve/beacon-route.d.ts +53 -0
  150. package/dist/serve/beacon-route.d.ts.map +1 -0
  151. package/dist/serve/beacon-route.js +101 -0
  152. package/dist/serve/beacon-route.js.map +1 -0
  153. package/dist/serve/entries/active-auth-registry.d.ts +16 -0
  154. package/dist/serve/entries/active-auth-registry.d.ts.map +1 -0
  155. package/dist/serve/entries/active-auth-registry.js +48 -0
  156. package/dist/serve/entries/active-auth-registry.js.map +1 -0
  157. package/dist/serve/entries/active-auth.d.ts +18 -0
  158. package/dist/serve/entries/active-auth.d.ts.map +1 -0
  159. package/dist/serve/entries/active-auth.js +84 -0
  160. package/dist/serve/entries/active-auth.js.map +1 -0
  161. package/dist/serve/entries/auth-helper-core.d.ts +1 -0
  162. package/dist/serve/entries/auth-helper-core.d.ts.map +1 -1
  163. package/dist/serve/entries/auth-helper-core.js +4 -0
  164. package/dist/serve/entries/auth-helper-core.js.map +1 -1
  165. package/dist/serve/entries/auth-helper-dom.d.ts.map +1 -1
  166. package/dist/serve/entries/auth-helper-dom.js +28 -2
  167. package/dist/serve/entries/auth-helper-dom.js.map +1 -1
  168. package/dist/serve/entries/auth.d.ts.map +1 -1
  169. package/dist/serve/entries/auth.js +8 -0
  170. package/dist/serve/entries/auth.js.map +1 -1
  171. package/dist/serve/entries/bridge-url.d.ts +1 -1
  172. package/dist/serve/entries/bridge-url.js +1 -1
  173. package/dist/serve/entries/init.d.ts.map +1 -1
  174. package/dist/serve/entries/init.js +70 -4
  175. package/dist/serve/entries/init.js.map +1 -1
  176. package/dist/serve/entries/runtime.d.ts.map +1 -1
  177. package/dist/serve/entries/runtime.js +9 -0
  178. package/dist/serve/entries/runtime.js.map +1 -1
  179. package/dist/serve/namespace.d.ts +14 -5
  180. package/dist/serve/namespace.d.ts.map +1 -1
  181. package/dist/serve/namespace.js +57 -124
  182. package/dist/serve/namespace.js.map +1 -1
  183. package/dist/serve/runtime/chip-config.d.ts.map +1 -1
  184. package/dist/serve/runtime/chip-config.js +16 -2
  185. package/dist/serve/runtime/chip-config.js.map +1 -1
  186. package/dist/serve/runtime/chip-dialog.d.ts +18 -0
  187. package/dist/serve/runtime/chip-dialog.d.ts.map +1 -0
  188. package/dist/serve/runtime/chip-dialog.js +315 -0
  189. package/dist/serve/runtime/chip-dialog.js.map +1 -0
  190. package/dist/serve/runtime/chip-install.d.ts +2 -0
  191. package/dist/serve/runtime/chip-install.d.ts.map +1 -1
  192. package/dist/serve/runtime/chip-install.js +9 -4
  193. package/dist/serve/runtime/chip-install.js.map +1 -1
  194. package/dist/serve/runtime/chip-user-search.d.ts +16 -0
  195. package/dist/serve/runtime/chip-user-search.d.ts.map +1 -0
  196. package/dist/serve/runtime/chip-user-search.js +354 -0
  197. package/dist/serve/runtime/chip-user-search.js.map +1 -0
  198. package/dist/serve/runtime/chip.d.ts +10 -0
  199. package/dist/serve/runtime/chip.d.ts.map +1 -1
  200. package/dist/serve/runtime/chip.js +118 -15
  201. package/dist/serve/runtime/chip.js.map +1 -1
  202. package/dist/serve/runtime/identity.d.ts +17 -0
  203. package/dist/serve/runtime/identity.d.ts.map +1 -0
  204. package/dist/serve/runtime/identity.js +10 -0
  205. package/dist/serve/runtime/identity.js.map +1 -0
  206. package/dist/serve/sandbox-session.d.ts +7 -0
  207. package/dist/serve/sandbox-session.d.ts.map +1 -1
  208. package/dist/serve/sandbox-session.js +2 -0
  209. package/dist/serve/sandbox-session.js.map +1 -1
  210. package/dist/serve/site-ui/_astro/{RulesCodeEditor.XtDmHOlN.js → RulesCodeEditor.DsTAsKIa.js} +1 -1
  211. package/dist/serve/site-ui/_astro/app.BOr2kwae.js +115 -0
  212. package/dist/serve/site-ui/_astro/app.DhAcImFD.js +1 -0
  213. package/dist/serve/site-ui/_astro/{chess-showcase.B72XYeoh.js → chess-showcase.Bn_pP6UJ.js} +2 -2
  214. package/dist/serve/site-ui/_astro/{example-runtime.C25_lx6Z.js → example-runtime.CXopTGv7.js} +1 -1
  215. package/dist/serve/site-ui/_astro/sandbox-controls.rOuZTOUG.js +510 -0
  216. package/dist/serve/site-ui/_astro/{seed.Co0yUJY5.js → seed.03a08f9t.js} +2 -2
  217. package/dist/serve/site-ui/_astro/writes.BsPBG3dp.js +653 -0
  218. package/dist/serve/site-ui/auth/index.html +1 -1
  219. package/dist/serve/site-ui/docs/_rhythm/index.html +1 -1
  220. package/dist/serve/site-ui/docs/agent/set-up-your-agent/index.html +1 -1
  221. package/dist/serve/site-ui/docs/agent/watch-and-review/index.html +1 -1
  222. package/dist/serve/site-ui/docs/agent/work-with-an-agent/index.html +1 -1
  223. package/dist/serve/site-ui/docs/ai-compat/index.html +1 -1
  224. package/dist/serve/site-ui/docs/api-reference/index.html +8 -1
  225. package/dist/serve/site-ui/docs/api-reference.md +7 -0
  226. package/dist/serve/site-ui/docs/app-compat/index.html +1 -1
  227. package/dist/serve/site-ui/docs/auth-compat/index.html +3 -3
  228. package/dist/serve/site-ui/docs/auth-compat.md +2 -2
  229. package/dist/serve/site-ui/docs/build/ai-logic/index.html +6 -1
  230. package/dist/serve/site-ui/docs/build/ai-logic.md +6 -0
  231. package/dist/serve/site-ui/docs/build/authentication/index.html +1 -1
  232. package/dist/serve/site-ui/docs/build/cloud-firestore/index.html +1 -1
  233. package/dist/serve/site-ui/docs/build/cloud-messaging/index.html +1 -1
  234. package/dist/serve/site-ui/docs/build/cloud-storage/index.html +1 -1
  235. package/dist/serve/site-ui/docs/build/realtime-database/index.html +1 -1
  236. package/dist/serve/site-ui/docs/conformance-scores/index.html +6 -2
  237. package/dist/serve/site-ui/docs/conformance-scores.md +5 -1
  238. package/dist/serve/site-ui/docs/create-pyric-reference-api/index.html +1 -1
  239. package/dist/serve/site-ui/docs/database-compat/index.html +44 -44
  240. package/dist/serve/site-ui/docs/database-compat.md +43 -43
  241. package/dist/serve/site-ui/docs/examples/chess/index.html +1 -1
  242. package/dist/serve/site-ui/docs/examples/index.html +1 -1
  243. package/dist/serve/site-ui/docs/firestore-compat/index.html +4 -4
  244. package/dist/serve/site-ui/docs/firestore-compat.md +3 -3
  245. package/dist/serve/site-ui/docs/firestore-flutter-compat/index.html +319 -0
  246. package/dist/serve/site-ui/docs/firestore-flutter-compat.md +344 -0
  247. package/dist/serve/site-ui/docs/functions-rtdb-compat/index.html +1 -1
  248. package/dist/serve/site-ui/docs/get-started/backend-bundlers/index.html +105 -0
  249. package/dist/serve/site-ui/docs/get-started/backend-bundlers.md +165 -0
  250. package/dist/serve/site-ui/docs/get-started/how-the-swap-works/index.html +1 -1
  251. package/dist/serve/site-ui/docs/get-started/nextjs/index.html +1 -1
  252. package/dist/serve/site-ui/docs/get-started/start-building/index.html +1 -1
  253. package/dist/serve/site-ui/docs/get-started/vite/index.html +1 -1
  254. package/dist/serve/site-ui/docs/index.json +2967 -1482
  255. package/dist/serve/site-ui/docs/messaging-compat/index.html +1 -1
  256. package/dist/serve/site-ui/docs/observe/resolve-runtime-status/index.html +1 -1
  257. package/dist/serve/site-ui/docs/observe/see-whats-happening/index.html +1 -1
  258. package/dist/serve/site-ui/docs/observe/shape-your-data/index.html +1 -1
  259. package/dist/serve/site-ui/docs/overview/index.html +1 -1
  260. package/dist/serve/site-ui/docs/pyric-admin-app-reference-api/index.html +1 -1
  261. package/dist/serve/site-ui/docs/pyric-admin-auth-reference-api/index.html +1 -1
  262. package/dist/serve/site-ui/docs/pyric-admin-database-reference-api/index.html +1 -1
  263. package/dist/serve/site-ui/docs/pyric-admin-firestore-reference-api/index.html +1 -1
  264. package/dist/serve/site-ui/docs/pyric-admin-messaging-reference-api/index.html +1 -1
  265. package/dist/serve/site-ui/docs/pyric-admin-reference-api/index.html +1 -1
  266. package/dist/serve/site-ui/docs/pyric-admin-storage-reference-api/index.html +1 -1
  267. package/dist/serve/site-ui/docs/pyric-ai-reference-api/index.html +1 -1
  268. package/dist/serve/site-ui/docs/pyric-ai-scripting-reference-api/index.html +1 -1
  269. package/dist/serve/site-ui/docs/pyric-analytics-reference-api/index.html +124 -0
  270. package/dist/serve/site-ui/docs/pyric-analytics-reference-api.md +287 -0
  271. package/dist/serve/site-ui/docs/pyric-app-check-reference-api/index.html +87 -0
  272. package/dist/serve/site-ui/docs/pyric-app-check-reference-api.md +199 -0
  273. package/dist/serve/site-ui/docs/pyric-app-reference-api/index.html +1 -1
  274. package/dist/serve/site-ui/docs/pyric-auth-reference-api/index.html +6 -2
  275. package/dist/serve/site-ui/docs/pyric-auth-reference-api.md +2 -1
  276. package/dist/serve/site-ui/docs/pyric-cli-assurance-browser-reference-api/index.html +1 -1
  277. package/dist/serve/site-ui/docs/pyric-cli-assurance-reference-api/index.html +1 -1
  278. package/dist/serve/site-ui/docs/pyric-cli-bridge-client-reference-api/index.html +2 -2
  279. package/dist/serve/site-ui/docs/pyric-cli-bridge-client-reference-api.md +1 -1
  280. package/dist/serve/site-ui/docs/pyric-cli-bridge-reference-api/index.html +1 -1
  281. package/dist/serve/site-ui/docs/pyric-cli-bundler-reference-api/index.html +56 -0
  282. package/dist/serve/site-ui/docs/pyric-cli-bundler-reference-api.md +72 -0
  283. package/dist/serve/site-ui/docs/pyric-cli-conformance-browser-reference-api/index.html +2 -1
  284. package/dist/serve/site-ui/docs/pyric-cli-conformance-browser-reference-api.md +1 -0
  285. package/dist/serve/site-ui/docs/pyric-cli-conformance-docs-reference-api/index.html +1 -1
  286. package/dist/serve/site-ui/docs/pyric-cli-conformance-reference-api/index.html +2 -1
  287. package/dist/serve/site-ui/docs/pyric-cli-conformance-reference-api.md +1 -0
  288. package/dist/serve/site-ui/docs/pyric-cli-credentials-node-reference-api/index.html +1 -1
  289. package/dist/serve/site-ui/docs/pyric-cli-discover-reference-api/index.html +1 -1
  290. package/dist/serve/site-ui/docs/pyric-cli-next-reference-api/index.html +2 -2
  291. package/dist/serve/site-ui/docs/pyric-cli-next-reference-api.md +1 -1
  292. package/dist/serve/site-ui/docs/pyric-cli-register-app-bridge-reference-api/index.html +1 -1
  293. package/dist/serve/site-ui/docs/pyric-cli-register-reference-api/index.html +1 -1
  294. package/dist/serve/site-ui/docs/pyric-cli-remote-reference-api/index.html +34 -9
  295. package/dist/serve/site-ui/docs/pyric-cli-remote-reference-api.md +16 -8
  296. package/dist/serve/site-ui/docs/pyric-cli-serve-worker-reference-api/index.html +133 -93
  297. package/dist/serve/site-ui/docs/pyric-cli-serve-worker-reference-api.md +61 -1
  298. package/dist/serve/site-ui/docs/pyric-cli-verify-reference-api/index.html +2 -2
  299. package/dist/serve/site-ui/docs/pyric-cli-verify-reference-api.md +1 -1
  300. package/dist/serve/site-ui/docs/pyric-cli-vite-reference-api/index.html +2 -2
  301. package/dist/serve/site-ui/docs/pyric-cli-vite-reference-api.md +2 -2
  302. package/dist/serve/site-ui/docs/pyric-database-reference-api/index.html +2 -2
  303. package/dist/serve/site-ui/docs/pyric-database-reference-api.md +1 -1
  304. package/dist/serve/site-ui/docs/pyric-firestore-lite-reference-api/index.html +559 -0
  305. package/dist/serve/site-ui/docs/pyric-firestore-lite-reference-api.md +1240 -0
  306. package/dist/serve/site-ui/docs/pyric-firestore-reference-api/index.html +40 -11
  307. package/dist/serve/site-ui/docs/pyric-firestore-reference-api.md +18 -8
  308. package/dist/serve/site-ui/docs/pyric-functions-reference-api/index.html +67 -0
  309. package/dist/serve/site-ui/docs/pyric-functions-reference-api.md +149 -0
  310. package/dist/serve/site-ui/docs/pyric-messaging-reference-api/index.html +1 -1
  311. package/dist/serve/site-ui/docs/pyric-messaging-sw-reference-api/index.html +1 -1
  312. package/dist/serve/site-ui/docs/pyric-performance-reference-api/index.html +35 -0
  313. package/dist/serve/site-ui/docs/pyric-performance-reference-api.md +69 -0
  314. package/dist/serve/site-ui/docs/pyric-remote-config-reference-api/index.html +132 -0
  315. package/dist/serve/site-ui/docs/pyric-remote-config-reference-api.md +307 -0
  316. package/dist/serve/site-ui/docs/pyric-rules-reference-api/index.html +1 -1
  317. package/dist/serve/site-ui/docs/pyric-sandbox-database-reference-api/index.html +52 -17
  318. package/dist/serve/site-ui/docs/pyric-sandbox-database-reference-api.md +48 -9
  319. package/dist/serve/site-ui/docs/pyric-sandbox-firestore-reference-api/index.html +1 -1
  320. package/dist/serve/site-ui/docs/pyric-sandbox-reference-api/index.html +239 -49
  321. package/dist/serve/site-ui/docs/pyric-sandbox-reference-api.md +148 -31
  322. package/dist/serve/site-ui/docs/pyric-storage-reference-api/index.html +22 -4
  323. package/dist/serve/site-ui/docs/pyric-storage-reference-api.md +24 -2
  324. package/dist/serve/site-ui/docs/pyric-ui-agents-reference-api/index.html +26 -96
  325. package/dist/serve/site-ui/docs/pyric-ui-agents-reference-api.md +20 -51
  326. package/dist/serve/site-ui/docs/pyric-ui-auth-hooks-reference-api/index.html +1 -1
  327. package/dist/serve/site-ui/docs/pyric-ui-auth-reference-api/index.html +2 -2
  328. package/dist/serve/site-ui/docs/pyric-ui-auth-reference-api.md +1 -1
  329. package/dist/serve/site-ui/docs/pyric-ui-events-hooks-reference-api/index.html +1 -1
  330. package/dist/serve/site-ui/docs/pyric-ui-events-reference-api/index.html +1 -1
  331. package/dist/serve/site-ui/docs/pyric-ui-firestore-hooks-reference-api/index.html +1 -1
  332. package/dist/serve/site-ui/docs/pyric-ui-firestore-reference-api/index.html +2 -2
  333. package/dist/serve/site-ui/docs/pyric-ui-firestore-reference-api.md +1 -1
  334. package/dist/serve/site-ui/docs/pyric-ui-primitives-reference-api/index.html +124 -26
  335. package/dist/serve/site-ui/docs/pyric-ui-primitives-reference-api.md +58 -5
  336. package/dist/serve/site-ui/docs/pyric-ui-rtdb-reference-api/index.html +1 -1
  337. package/dist/serve/site-ui/docs/pyric-ui-rules-hooks-reference-api/index.html +1 -1
  338. package/dist/serve/site-ui/docs/pyric-ui-rules-reference-api/index.html +1 -1
  339. package/dist/serve/site-ui/docs/pyric-ui-storage-hooks-reference-api/index.html +1 -1
  340. package/dist/serve/site-ui/docs/pyric-ui-storage-reference-api/index.html +4 -5
  341. package/dist/serve/site-ui/docs/pyric-ui-storage-reference-api.md +3 -4
  342. package/dist/serve/site-ui/docs/pyric-ui-traffic-hooks-reference-api/index.html +1 -1
  343. package/dist/serve/site-ui/docs/pyric-ui-traffic-reference-api/index.html +1 -1
  344. package/dist/serve/site-ui/docs/reference/cli/index.html +6 -3
  345. package/dist/serve/site-ui/docs/reference/cli.md +8 -2
  346. package/dist/serve/site-ui/docs/rules-compat/index.html +10 -2
  347. package/dist/serve/site-ui/docs/rules-compat.md +9 -1
  348. package/dist/serve/site-ui/docs/secure/audit-your-rules/index.html +1 -1
  349. package/dist/serve/site-ui/docs/secure/firestore-rules-limits/index.html +1 -1
  350. package/dist/serve/site-ui/docs/secure/read-a-denial/index.html +1 -1
  351. package/dist/serve/site-ui/docs/secure/rtdb-rules-in-typescript/index.html +1 -1
  352. package/dist/serve/site-ui/docs/secure/rules-standard-library/index.html +1 -1
  353. package/dist/serve/site-ui/docs/secure/secure-it-with-rules/index.html +1 -1
  354. package/dist/serve/site-ui/docs/secure/simulate-and-lint/index.html +1 -1
  355. package/dist/serve/site-ui/docs/secure/write-a-rules-test-suite/index.html +1 -1
  356. package/dist/serve/site-ui/docs/ship/ship-to-production/index.html +1 -1
  357. package/dist/serve/site-ui/docs/ship/test-in-node/index.html +1 -1
  358. package/dist/serve/site-ui/docs/storage-compat/index.html +13 -13
  359. package/dist/serve/site-ui/docs/storage-compat.md +12 -12
  360. package/dist/serve/site-ui/docs/trust/how-we-know-it-matches-firebase/index.html +1 -1
  361. package/dist/serve/site-ui/docs/trust/rules-standard-library/index.html +1 -1
  362. package/dist/serve/site-ui/docs/trust/versioning-and-compatibility/index.html +1 -1
  363. package/dist/serve/site-ui/docs/tutorial/index.html +1 -1
  364. package/dist/serve/site-ui/examples/chess/index.html +1 -1
  365. package/dist/serve/site-ui/firestore/index.html +1 -1
  366. package/dist/serve/site-ui/llms.txt +9 -0
  367. package/dist/serve/site-ui/rtdb/index.html +1 -1
  368. package/dist/serve/site-ui/settings/index.html +1 -1
  369. package/dist/serve/site-ui/storage/index.html +1 -1
  370. package/dist/serve/site-ui/studio/index.html +1 -1
  371. package/dist/serve/site-ui/traffic/index.html +1 -1
  372. package/dist/serve/studio/disk-workspace.d.ts.map +1 -1
  373. package/dist/serve/studio/disk-workspace.js +64 -2
  374. package/dist/serve/studio/disk-workspace.js.map +1 -1
  375. package/dist/serve/vite-plugin.d.ts +7 -7
  376. package/dist/serve/vite-plugin.js +1 -1
  377. package/dist/serve/vite-plugin.js.map +1 -1
  378. package/dist/serve/vite-sandbox-generation.d.ts.map +1 -1
  379. package/dist/serve/vite-sandbox-generation.js +12 -0
  380. package/dist/serve/vite-sandbox-generation.js.map +1 -1
  381. package/dist/serve/worker/client/auth.js +1 -2
  382. package/dist/serve/worker/client/auth.js.map +1 -1
  383. package/dist/serve/worker/client/core.d.ts +21 -1
  384. package/dist/serve/worker/client/core.d.ts.map +1 -1
  385. package/dist/serve/worker/client/core.js +79 -3
  386. package/dist/serve/worker/client/core.js.map +1 -1
  387. package/dist/serve/worker/client.d.ts +1 -1
  388. package/dist/serve/worker/client.d.ts.map +1 -1
  389. package/dist/serve/worker/client.js +1 -1
  390. package/dist/serve/worker/client.js.map +1 -1
  391. package/dist/serve/worker/context-resolver.d.ts +30 -0
  392. package/dist/serve/worker/context-resolver.d.ts.map +1 -0
  393. package/dist/serve/worker/context-resolver.js +44 -0
  394. package/dist/serve/worker/context-resolver.js.map +1 -0
  395. package/dist/serve/worker/entry.js +23 -19
  396. package/dist/serve/worker/entry.js.map +1 -1
  397. package/dist/serve/worker/host/core.d.ts +1 -0
  398. package/dist/serve/worker/host/core.d.ts.map +1 -1
  399. package/dist/serve/worker/host/core.js +12 -6
  400. package/dist/serve/worker/host/core.js.map +1 -1
  401. package/dist/serve/worker/host/storage.js +2 -2
  402. package/dist/serve/worker/host-auth.js +1 -2
  403. package/dist/serve/worker/host-auth.js.map +1 -1
  404. package/dist/serve/worker/index.d.ts +1 -1
  405. package/dist/serve/worker/index.d.ts.map +1 -1
  406. package/dist/serve/worker/index.js +2 -2
  407. package/dist/serve/worker/index.js.map +1 -1
  408. package/dist/serve/worker/port-lifecycle.d.ts +18 -0
  409. package/dist/serve/worker/port-lifecycle.d.ts.map +1 -0
  410. package/dist/serve/worker/port-lifecycle.js +28 -0
  411. package/dist/serve/worker/port-lifecycle.js.map +1 -0
  412. package/dist/serve/worker/serve-init.d.ts.map +1 -1
  413. package/dist/serve/worker/serve-init.js +8 -2
  414. package/dist/serve/worker/serve-init.js.map +1 -1
  415. package/dist/verify/fixture.d.ts +1 -1
  416. package/package.json +16 -5
  417. package/src/assurance/.generated/conformance-verdicts.ts +68 -1
  418. package/src/bridge/client/dispatch.ts +52 -56
  419. package/src/bridge/client/tool-family-factories.ts +34 -0
  420. package/src/bridge/server/mcp-contract.ts +15 -59
  421. package/src/bridge/server/peer.ts +24 -1
  422. package/src/bridge/server/tool-family-factories.ts +40 -0
  423. package/src/bridge/server/tool-metadata.ts +29 -61
  424. package/src/bridge/tool-families.generated.ts +17 -0
  425. package/src/bridge/tool-families.ts +87 -0
  426. package/src/bridge/tool-family-records/conformance.ts +6 -0
  427. package/src/bridge/tool-family-records/firestore-data.ts +15 -0
  428. package/src/bridge/tool-family-records/firestore-inspect.ts +6 -0
  429. package/src/bridge/tool-family-records/firestore-rules.ts +15 -0
  430. package/src/bridge/tool-family-records/firestore-simulator.ts +16 -0
  431. package/src/bridge/tool-family-records/rtdb-inspection.ts +6 -0
  432. package/src/bundler/externals.ts +55 -0
  433. package/src/bundler/index.ts +21 -0
  434. package/src/cli/cli.test.ts +0 -44
  435. package/src/cli/inlined-sdk-scanner.ts +77 -0
  436. package/src/cli/sandbox-interlock.ts +167 -0
  437. package/src/cli/sandbox-runner.ts +37 -26
  438. package/src/cli/serve.ts +68 -69
  439. package/src/cli/unsupported-runtime.ts +68 -0
  440. package/src/conformance/.generated/can-i-use-browser.ts +2 -2
  441. package/src/conformance/.generated/can-i-use.ts +2 -2
  442. package/src/conformance/.generated/conformance-docs.ts +1 -1
  443. package/src/functions-rtdb/child.ts +115 -21
  444. package/src/functions-rtdb/development-runtime.ts +4 -0
  445. package/src/google-endpoints.ts +155 -0
  446. package/src/next/runtime-env.ts +5 -0
  447. package/src/next/with-pyric.ts +1 -1
  448. package/src/register/beacon.ts +232 -0
  449. package/src/register/index.ts +68 -6
  450. package/src/register/net-guard.ts +567 -0
  451. package/src/serve/ai-diagnostics-relay.ts +243 -0
  452. package/src/serve/ai-proxy.ts +327 -0
  453. package/src/serve/ai-status.ts +109 -0
  454. package/src/serve/ai-terminal-blocks.ts +210 -0
  455. package/src/serve/ai-terminal-text.ts +52 -0
  456. package/src/serve/beacon-route.ts +141 -0
  457. package/src/serve/entries/active-auth-registry.ts +56 -0
  458. package/src/serve/entries/active-auth.ts +100 -0
  459. package/src/serve/entries/auth-helper-core.ts +5 -0
  460. package/src/serve/entries/auth-helper-dom.ts +26 -2
  461. package/src/serve/entries/auth.ts +8 -0
  462. package/src/serve/entries/bridge-url.ts +1 -1
  463. package/src/serve/entries/init.ts +78 -3
  464. package/src/serve/entries/runtime.ts +10 -0
  465. package/src/serve/namespace.ts +73 -140
  466. package/src/serve/runtime/chip-config.ts +16 -2
  467. package/src/serve/runtime/chip-dialog.ts +366 -0
  468. package/src/serve/runtime/chip-install.ts +10 -4
  469. package/src/serve/runtime/chip-user-search.ts +391 -0
  470. package/src/serve/runtime/chip.ts +140 -15
  471. package/src/serve/runtime/identity.ts +26 -0
  472. package/src/serve/sandbox-session.ts +9 -0
  473. package/src/serve/studio/disk-workspace.ts +76 -2
  474. package/src/serve/vite-plugin.ts +8 -8
  475. package/src/serve/vite-sandbox-generation.ts +15 -0
  476. package/src/serve/worker/client/auth.ts +1 -1
  477. package/src/serve/worker/client/core.ts +80 -3
  478. package/src/serve/worker/client.ts +1 -1
  479. package/src/serve/worker/context-resolver.ts +60 -0
  480. package/src/serve/worker/entry.ts +22 -18
  481. package/src/serve/worker/host/core.ts +9 -7
  482. package/src/serve/worker/host/storage.ts +2 -2
  483. package/src/serve/worker/host-auth.ts +1 -1
  484. package/src/serve/worker/index.ts +4 -1
  485. package/src/serve/worker/port-lifecycle.ts +45 -0
  486. package/src/serve/worker/serve-init.ts +9 -2
  487. package/src/verify/fixture.ts +1 -1
  488. package/dist/serve/site-ui/_astro/app.B52xr-DK.js +0 -1
  489. package/dist/serve/site-ui/_astro/app.DWBraPUw.js +0 -12
  490. package/dist/serve/site-ui/_astro/sandbox-controls.DEpnKg-O.js +0 -1169
  491. package/dist/serve/site-ui/_astro/writes.rpjrFzmu.js +0 -102
@@ -0,0 +1,243 @@
1
+ import type { SandboxEvent } from 'pyric/sandbox';
2
+ import { capTerminalText } from './ai-terminal-text.js';
3
+
4
+ /**
5
+ * AI diagnostics relay: headless dev visibility for the three things the
6
+ * broker does that no app-level signal reports.
7
+ *
8
+ * The broker lands `service_mutation` events (`service: 'ai'`) on the
9
+ * sandbox's unified stream. That stream is browser-side: the worker host fans
10
+ * it out over the port for Studio, and it never reaches the dev server. An
11
+ * agent driving `pyric dev` headlessly therefore saw NOTHING.
12
+ *
13
+ * - `request_rejected`: a bad role, empty `contents`, a missing thought
14
+ * signature, or an engine that answered an error envelope.
15
+ * - `response_blocked`: the SILENT refusal. A safety / recitation /
16
+ * blocklist filter answers HTTP 200 with an empty candidate, so nothing
17
+ * throws anywhere and the app simply renders nothing.
18
+ * - `model_substituted`: not a refusal at all. The answer arrives, from a
19
+ * model the developer never asked for (an openai `modelMap` entry or
20
+ * catch-all `model`, a gemini experimental alias). Same shape of problem:
21
+ * the sandbox quietly did something other than what the code said, and
22
+ * only the terminal can say so.
23
+ *
24
+ * One record per kind in {@link AI_DIAGNOSTIC_RELAYS} pairs the sandbox `op`
25
+ * with the narrower that turns it into a wire payload. The terminal formatter
26
+ * and throttle key for each kind live in `ai-terminal-blocks.ts`, keyed by the
27
+ * same {@link AiDiagnosticKind} union, so the compiler flags a kind that is
28
+ * relayed but never rendered.
29
+ *
30
+ * This is the AI twin of the Firebase Activity Guard reporter next door
31
+ * (`activity-guard.ts`): a browser-safe subscriber shared by the SharedWorker
32
+ * and in-page planes, which fire-and-forget POSTs to the dev server. It rides
33
+ * the DENIAL relay's existing channel (`POST /__pyric/denials`, see the denial
34
+ * relay section of `namespace.ts`) rather than opening a new one: a broker
35
+ * refusal is the same category of thing as a rules denial, it wants the same
36
+ * terminal treatment, and it wants that endpoint's per-(target, reason)
37
+ * throttle, because an agent retry loop re-sends the same request over and
38
+ * over.
39
+ *
40
+ * Best-effort, exactly like the activity reporter and the denial relay: never
41
+ * throws, never awaited into app flow, and a relay failure (dev server gone,
42
+ * no `fetch`, non-pyric host) is swallowed. Diagnostics, not a correctness
43
+ * path.
44
+ */
45
+
46
+ /** The slice of the sandbox this relay needs. Live events only: replaying
47
+ * `history()` on boot would re-announce refusals the terminal already
48
+ * printed (and the activity feed's reason for taking history does not apply,
49
+ * since nothing here correlates across events). */
50
+ export interface AiDiagnosticsFeed {
51
+ subscribe(listener: (event: SandboxEvent) => void): unknown;
52
+ }
53
+
54
+ /** The `kind` discriminator on every payload this relay POSTs. It is also the
55
+ * key of the formatter table in `ai-terminal-blocks.ts`. */
56
+ export type AiDiagnosticKind = 'ai-rejection' | 'ai-blocked' | 'ai-model-substituted';
57
+
58
+ /** The sandbox events this relay reads. */
59
+ type ServiceMutation = Extract<SandboxEvent, { kind: 'service_mutation' }>;
60
+
61
+ /** Shape POSTed for a `request_rejected` event. `namespace.ts` reads it back
62
+ * loosely (`AiRejectionPayload` in `ai-terminal-blocks.ts`): the body is JSON
63
+ * from the served page, and nothing on the far side enforces this type. */
64
+ interface AiRejectionRelayPayload {
65
+ kind: 'ai-rejection';
66
+ /** Model resource the rejected op targeted (the event's `path`). */
67
+ model?: string;
68
+ /** Which engine the broker resolved to (`scripted` | `openai` | `gemini` |
69
+ * `custom`); a rejection reads very differently for each. */
70
+ engine?: string;
71
+ /** Wire error status (e.g. `INVALID_ARGUMENT`) and HTTP-ish code. */
72
+ status?: string;
73
+ code?: number;
74
+ /** The rejection reason: production's own message for shape errors. */
75
+ message: string;
76
+ }
77
+
78
+ /** Shape POSTed for a `response_blocked` event. Carries the wire's own
79
+ * vocabulary, `finishReason` (a candidate was withheld) or `blockReason`
80
+ * (the prompt was refused), rather than a pre-rendered sentence, so the
81
+ * terminal formatter owns the phrasing exactly the way it does for a
82
+ * rejection. */
83
+ interface AiBlockedRelayPayload {
84
+ kind: 'ai-blocked';
85
+ /** Model resource the blocked op targeted (the event's `path`). */
86
+ model?: string;
87
+ /** Which engine the broker resolved to. */
88
+ engine?: string;
89
+ /** Candidate-level block: `SAFETY`, `RECITATION`, `BLOCKLIST`, and so on. */
90
+ finishReason?: string;
91
+ /** Production's own explanatory text for the block, when it sent one. */
92
+ finishMessage?: string;
93
+ /** Prompt-level block: `promptFeedback.blockReason`. */
94
+ blockReason?: string;
95
+ }
96
+
97
+ /** Shape POSTed for a `model_substituted` event. Not a refusal: the answer
98
+ * arrives normally, just from a model the developer never named. */
99
+ interface AiModelSubstitutionRelayPayload {
100
+ kind: 'ai-model-substituted';
101
+ /** The model the request asked for (the event's `path`). */
102
+ requestedModel: string;
103
+ /** The model the engine actually calls (bare, no `models/` prefix). */
104
+ effectiveModel: string;
105
+ /** Which engine substituted (`openai` | `gemini` | ...). */
106
+ engine?: string;
107
+ /** Why it differs, e.g. `engine modelMap`, `experimental alias`. */
108
+ reason?: string;
109
+ }
110
+
111
+ type AiDiagnosticRelayPayload =
112
+ | AiRejectionRelayPayload
113
+ | AiBlockedRelayPayload
114
+ | AiModelSubstitutionRelayPayload;
115
+
116
+ /**
117
+ * A string field off the event's free-form `detail` record, or `undefined`
118
+ * when the emitter did not set it. `detail` is a public contract anyone can
119
+ * post onto, so every field is re-checked, and every value is length-capped
120
+ * here rather than on the far side: a model server that answers with a
121
+ * megabyte in its `finishMessage` must not push a megabyte through the
122
+ * fire-and-forget POST just to have the terminal cut it.
123
+ */
124
+ function detailText(detail: Record<string, unknown>, field: string): string | undefined {
125
+ const value = detail[field];
126
+ if (typeof value !== 'string') return undefined;
127
+ return capTerminalText(value);
128
+ }
129
+
130
+ /** The event's `path` when it names a model, or `undefined`. */
131
+ function eventModel(event: ServiceMutation): string | undefined {
132
+ if (typeof event.path !== 'string' || event.path === '') return undefined;
133
+ return capTerminalText(event.path);
134
+ }
135
+
136
+ function detailOf(event: ServiceMutation): Record<string, unknown> {
137
+ return (event.detail ?? {}) as Record<string, unknown>;
138
+ }
139
+
140
+ /** Narrow a `request_rejected` event to its relay payload. */
141
+ function toRejectionPayload(event: ServiceMutation): AiRejectionRelayPayload | null {
142
+ const detail = detailOf(event);
143
+ const payload: AiRejectionRelayPayload = {
144
+ kind: 'ai-rejection',
145
+ message: detailText(detail, 'message') ?? 'request rejected',
146
+ };
147
+ payload.model = eventModel(event);
148
+ payload.engine = detailText(detail, 'engine');
149
+ payload.status = detailText(detail, 'status');
150
+ if (typeof detail.code === 'number') payload.code = detail.code;
151
+ return payload;
152
+ }
153
+
154
+ /** Narrow a `response_blocked` event to its relay payload. */
155
+ function toBlockedPayload(event: ServiceMutation): AiBlockedRelayPayload | null {
156
+ const detail = detailOf(event);
157
+ const payload: AiBlockedRelayPayload = { kind: 'ai-blocked' };
158
+ payload.model = eventModel(event);
159
+ payload.engine = detailText(detail, 'engine');
160
+ payload.finishReason = detailText(detail, 'finishReason');
161
+ payload.finishMessage = detailText(detail, 'finishMessage');
162
+ payload.blockReason = detailText(detail, 'blockReason');
163
+ return payload;
164
+ }
165
+
166
+ /**
167
+ * Narrow a `model_substituted` event to its relay payload, and enforce the
168
+ * invariant the terminal line depends on: an effective model equal to the
169
+ * requested one is NOT a substitution and never relays. The broker already
170
+ * compares before emitting; this re-checks because the event stream is a
171
+ * public contract anyone can post onto.
172
+ */
173
+ function toModelSubstitutionPayload(
174
+ event: ServiceMutation,
175
+ ): AiModelSubstitutionRelayPayload | null {
176
+ const detail = detailOf(event);
177
+ const requestedModel = detailText(detail, 'requestedModel') ?? eventModel(event) ?? '';
178
+ const effectiveModel = detailText(detail, 'effectiveModel') ?? '';
179
+ if (requestedModel === '' || effectiveModel === '') return null;
180
+ let requestedBare = requestedModel;
181
+ if (requestedModel.startsWith('models/')) requestedBare = requestedModel.slice('models/'.length);
182
+ if (effectiveModel === requestedBare || effectiveModel === requestedModel) return null;
183
+ const payload: AiModelSubstitutionRelayPayload = {
184
+ kind: 'ai-model-substituted',
185
+ requestedModel,
186
+ effectiveModel,
187
+ };
188
+ payload.engine = detailText(detail, 'engine');
189
+ payload.reason = detailText(detail, 'reason');
190
+ return payload;
191
+ }
192
+
193
+ /** One record per relayed kind: the sandbox `op` that produces it, and the
194
+ * narrower that turns that event into a wire payload. */
195
+ interface AiDiagnosticRelay {
196
+ op: string;
197
+ fromEvent(event: ServiceMutation): AiDiagnosticRelayPayload | null;
198
+ }
199
+
200
+ const AI_DIAGNOSTIC_RELAYS: { [K in AiDiagnosticKind]: AiDiagnosticRelay } = {
201
+ 'ai-rejection': { op: 'request_rejected', fromEvent: toRejectionPayload },
202
+ 'ai-blocked': { op: 'response_blocked', fromEvent: toBlockedPayload },
203
+ 'ai-model-substituted': { op: 'model_substituted', fromEvent: toModelSubstitutionPayload },
204
+ };
205
+
206
+ /** The relay record for a sandbox `op`, or `null` for an op nothing relays. */
207
+ function relayForOp(op: string): AiDiagnosticRelay | null {
208
+ for (const relay of Object.values(AI_DIAGNOSTIC_RELAYS)) {
209
+ if (relay.op === op) return relay;
210
+ }
211
+ return null;
212
+ }
213
+
214
+ /**
215
+ * Subscribe to the sandbox event stream and relay every broker refusal,
216
+ * rejected request or blocked response, plus every silent model substitution,
217
+ * to the dev server for terminal visibility.
218
+ *
219
+ * Browser-safe (shared by the SharedWorker host and the in-page runtime);
220
+ * returns nothing to unsubscribe with, mirroring the guard next door, since
221
+ * the subscription is observational and lives for the plane's lifetime.
222
+ */
223
+ export function setupAiDiagnosticsRelay(feed: AiDiagnosticsFeed, fetchFn: typeof fetch): void {
224
+ feed.subscribe((event) => {
225
+ if (event.kind !== 'service_mutation') return;
226
+ if (event.service !== 'ai') return;
227
+ const relay = relayForOp(event.op);
228
+ if (relay === null) return;
229
+ const payload = relay.fromEvent(event);
230
+ if (payload === null) return;
231
+ try {
232
+ void fetchFn('/__pyric/denials', {
233
+ method: 'POST',
234
+ headers: { 'content-type': 'application/json' },
235
+ body: JSON.stringify(payload),
236
+ }).catch(() => {
237
+ /* Diagnostics are best-effort and must never affect app behavior. */
238
+ });
239
+ } catch {
240
+ /* An injected or unavailable fetch must never affect app behavior. */
241
+ }
242
+ });
243
+ }
@@ -0,0 +1,327 @@
1
+ /**
2
+ * `/__pyric/ai-proxy`: the same-origin passthrough to an OpenAI-compatible
3
+ * upstream, plus the terminal diagnostics that make an upstream failure
4
+ * visible to a developer with no browser console open.
5
+ *
6
+ * The route is always mounted; it only touches the network when a request
7
+ * arrives. Everything about WHERE it forwards is resolved by
8
+ * {@link resolveAiProxyUpstream}, which the startup banner (`ai-status.ts`)
9
+ * shares so the two can never disagree.
10
+ */
11
+ import type { IncomingMessage, ServerResponse } from 'node:http';
12
+ import type { ServeLogger } from './server.js';
13
+ import { redactUrl } from 'pyric/ai/internal';
14
+ import { sanitizeForTerminal } from './ai-terminal-text.js';
15
+
16
+ /** The dev server's diagnostics throttle, as this module needs it: the
17
+ * denials route owns the instance, and the proxy shares it so a failing
18
+ * upstream cannot out-shout a denied listener. */
19
+ export interface AiDiagnosticThrottle {
20
+ shouldPrint(key: string, now: number): boolean;
21
+ }
22
+
23
+ /** Default upstream: local Ollama's OpenAI-compatible endpoint. */
24
+ export const AI_PROXY_DEFAULT_UPSTREAM = 'http://localhost:11434/v1';
25
+
26
+ /** The route the dev server mounts for OpenAI-compatible traffic. */
27
+ export const AI_PROXY_ROUTE = '/__pyric/ai-proxy';
28
+
29
+ /**
30
+ * Request headers that must NOT be forwarded upstream: origin-sensitive
31
+ * browser context (`origin`/`referer`/`cookie`, since the upstream is a
32
+ * different origin and must never see the page's), hop-by-hop headers, and
33
+ * the two the proxy re-derives (`host`, `content-length`). `accept-encoding`
34
+ * is dropped so fetch negotiates its own compression and the streamed body
35
+ * needs no length fixups.
36
+ */
37
+ const AI_PROXY_STRIPPED_HEADERS = new Set([
38
+ 'host',
39
+ 'origin',
40
+ 'referer',
41
+ 'cookie',
42
+ 'connection',
43
+ 'content-length',
44
+ 'accept-encoding',
45
+ 'keep-alive',
46
+ 'proxy-authorization',
47
+ 'te',
48
+ 'trailer',
49
+ 'transfer-encoding',
50
+ 'upgrade',
51
+ ]);
52
+
53
+ /** The upstream-failure shapes the proxy surfaces to the terminal. */
54
+ type AiProxyFailure =
55
+ | { kind: 'unreachable'; target: string; latencyMs: number; cause: string }
56
+ | {
57
+ kind: 'status';
58
+ target: string;
59
+ latencyMs: number;
60
+ status: number;
61
+ /** The upstream's raw `Retry-After` header, when it sent one. */
62
+ retryAfter?: string;
63
+ }
64
+ | { kind: 'stream-abort'; target: string; latencyMs: number; cause: string };
65
+
66
+ /** Longest `Retry-After` value echoed to the terminal (delta-seconds and
67
+ * HTTP-dates are both far shorter; anything longer is upstream noise). */
68
+ const AI_PROXY_RETRY_AFTER_MAX = 64;
69
+
70
+ /**
71
+ * Render a `Retry-After` value for the terminal, or `null` when there is
72
+ * nothing worth printing.
73
+ *
74
+ * The header is remote-authored, so it is sanitized and length-capped like
75
+ * every other upstream string that reaches the terminal. Delta-seconds (the
76
+ * common form) get an `s` suffix so the wait reads as a duration; the
77
+ * HTTP-date form is echoed verbatim, since `Wed, 21 Oct 2026 07:28:00 GMTs`
78
+ * would be nonsense.
79
+ */
80
+ function formatRetryAfter(raw: string): string | null {
81
+ const clean = sanitizeForTerminal(raw).slice(0, AI_PROXY_RETRY_AFTER_MAX).trim();
82
+ if (clean === '') return null;
83
+ if (/^\d+$/.test(clean)) return `${clean}s`;
84
+ return clean;
85
+ }
86
+
87
+ /**
88
+ * Mask credentials, then flatten and cap, in that order. Both the target and
89
+ * a raw fetch error message are remote-influenced: the URL carries whatever
90
+ * the page put in the query string, and an error message can quote it back.
91
+ */
92
+ function safeUpstreamText(text: string): string {
93
+ return sanitizeForTerminal(redactUrl(text));
94
+ }
95
+
96
+ /**
97
+ * The throttle key for one upstream failure. A failing upstream fails on
98
+ * every request, so the key is the failure itself (kind, status, and the
99
+ * redacted target) rather than anything per-request: one line per window,
100
+ * exactly like a denied listener that re-fires.
101
+ */
102
+ function aiProxyThrottleKey(failure: AiProxyFailure): string {
103
+ const target = redactUrl(failure.target);
104
+ if (failure.kind === 'status') return `ai-proxy status ${failure.status} ${target}`;
105
+ return `ai-proxy ${failure.kind} ${target}`;
106
+ }
107
+
108
+ /** Print one upstream failure, unless the throttle already printed it. */
109
+ function noteAiProxyFailure(
110
+ failure: AiProxyFailure,
111
+ throttle: AiDiagnosticThrottle,
112
+ logger?: ServeLogger,
113
+ ): void {
114
+ if (logger === undefined) return;
115
+ if (!throttle.shouldPrint(aiProxyThrottleKey(failure), Date.now())) return;
116
+ logger.note(formatAiProxyWarning(failure));
117
+ }
118
+
119
+ /**
120
+ * Format an upstream failure into the compact terminal block, the same
121
+ * ` [pyric] ...` idiom the denial relay prints, so ai-proxy trouble reads
122
+ * like every other dev-server diagnostic. Two lines: what went wrong, then
123
+ * the (redacted) upstream URL with the elapsed time. Exported for unit tests.
124
+ *
125
+ * A 429 says so in words (`rate limited`): the bare number reads as one more
126
+ * failed request, when it actually means the upstream is shedding load or the
127
+ * key is out of quota. When the upstream sent a `Retry-After`, a third line
128
+ * prints the wait it asked for, and states that NOTHING here waits on the
129
+ * developer's behalf. The proxy has no retry loop (the status rides straight
130
+ * through to the caller), so the backoff is the app's decision, not pyric's.
131
+ */
132
+ export function formatAiProxyWarning(failure: AiProxyFailure): string {
133
+ const target = safeUpstreamText(failure.target);
134
+ let headline: string;
135
+ if (failure.kind === 'unreachable') {
136
+ headline = `upstream unreachable: ${safeUpstreamText(failure.cause)}`;
137
+ } else if (failure.kind === 'status') {
138
+ const rateLimited = failure.status === 429;
139
+ headline = `upstream returned ${failure.status}${rateLimited ? ' (rate limited / quota exhausted)' : ''}`;
140
+ } else {
141
+ headline = `upstream stream aborted mid-response: ${safeUpstreamText(failure.cause)}`;
142
+ }
143
+ const lines = [
144
+ ` ⚠ [pyric] ai-proxy: ${headline}`,
145
+ ` POST ${target} (${failure.latencyMs}ms)`,
146
+ ];
147
+ if (failure.kind === 'unreachable') {
148
+ lines.push(
149
+ ` set PYRIC_AI_PROXY_UPSTREAM to an OpenAI-compatible base URL (default ${AI_PROXY_DEFAULT_UPSTREAM})`,
150
+ );
151
+ }
152
+ if (failure.kind === 'status' && failure.retryAfter !== undefined) {
153
+ const wait = formatRetryAfter(failure.retryAfter);
154
+ if (wait !== null) {
155
+ lines.push(` Retry-After: ${wait}, no automatic retry; the status went to the caller`);
156
+ }
157
+ }
158
+ return lines.join('\n');
159
+ }
160
+
161
+ /**
162
+ * Resolve the upstream `/__pyric/ai-proxy` forwards to, and say WHERE that
163
+ * value came from. One resolution shared by the proxy handler (which needs
164
+ * the target) and the startup line (which needs the provenance, so a
165
+ * developer can tell a deliberate `PYRIC_AI_PROXY_UPSTREAM` from the Ollama
166
+ * default nobody chose).
167
+ */
168
+ export function resolveAiProxyUpstream(
169
+ configured: string | undefined,
170
+ ): { target: string; source: 'option' | 'env' | 'default' } {
171
+ const envUpstream = process.env.PYRIC_AI_PROXY_UPSTREAM;
172
+ let raw = AI_PROXY_DEFAULT_UPSTREAM;
173
+ let source: 'option' | 'env' | 'default' = 'default';
174
+ if (configured !== undefined) {
175
+ raw = configured;
176
+ source = 'option';
177
+ } else if (envUpstream !== undefined) {
178
+ raw = envUpstream;
179
+ source = 'env';
180
+ }
181
+ return { target: raw.replace(/\/$/, ''), source };
182
+ }
183
+
184
+ /**
185
+ * Handle `POST /__pyric/ai-proxy/<suffix>`: a same-origin passthrough to the
186
+ * configured OpenAI-compatible upstream, so the browser openai engine
187
+ * (running in the served page or the SharedWorker host) reaches a localhost
188
+ * upstream like Ollama with ZERO CORS setup (no `OLLAMA_ORIGINS`, ever).
189
+ *
190
+ * Behavior:
191
+ * - POST only (the OpenAI chat-completions surface is POST); 405 otherwise.
192
+ * - the path suffix + query ride through verbatim
193
+ * (`/__pyric/ai-proxy/chat/completions` becomes `<upstream>/chat/completions`).
194
+ * - the request body is forwarded verbatim; headers minus the
195
+ * origin-sensitive/hop-by-hop set above (`authorization` DOES forward,
196
+ * since upstreams may require a key).
197
+ * - the response body is STREAMED through chunk-by-chunk. SSE passthrough
198
+ * must never buffer, or `stream: true` completions would arrive all at
199
+ * once at the end.
200
+ * - an unreachable upstream answers 502 with a plain-text explanation.
201
+ * - every upstream failure (unreachable/timeout, non-2xx status, mid-stream
202
+ * abort) ALSO prints a structured warning on the dev server's terminal
203
+ * logger; a stopped Ollama used to fail in total silence. Diagnostics
204
+ * only: nothing here changes what the caller receives. No logger wired
205
+ * means dropped silently (same opt-out contract as the denial relay).
206
+ */
207
+ export async function handleAiProxy(
208
+ configuredUpstream: string | undefined,
209
+ throttle: AiDiagnosticThrottle,
210
+ req: IncomingMessage,
211
+ res: ServerResponse,
212
+ url: URL,
213
+ logger?: ServeLogger,
214
+ ): Promise<void> {
215
+ if (req.method !== 'POST') {
216
+ res.writeHead(405, { allow: 'POST' }).end('method not allowed');
217
+ return;
218
+ }
219
+ const upstreamBase = resolveAiProxyUpstream(configuredUpstream).target;
220
+ const suffix = url.pathname.slice(AI_PROXY_ROUTE.length);
221
+ const target = `${upstreamBase}${suffix}${url.search}`;
222
+
223
+ // Buffer the REQUEST body (small JSON payloads); the RESPONSE streams.
224
+ const raw = await new Promise<Buffer>((resolve, reject) => {
225
+ const chunks: Buffer[] = [];
226
+ req.on('data', (c: Buffer) => chunks.push(c));
227
+ req.on('end', () => resolve(Buffer.concat(chunks)));
228
+ req.on('error', reject);
229
+ });
230
+ // Copy into a plain-ArrayBuffer view; fetch's BodyInit typing rejects
231
+ // Buffer's ArrayBufferLike backing.
232
+ const body = new Uint8Array(raw.byteLength);
233
+ body.set(raw);
234
+
235
+ const headers: Record<string, string> = {};
236
+ for (const [key, value] of Object.entries(req.headers)) {
237
+ if (value === undefined) continue;
238
+ if (AI_PROXY_STRIPPED_HEADERS.has(key.toLowerCase())) continue;
239
+ headers[key] = Array.isArray(value) ? value.join(', ') : value;
240
+ }
241
+
242
+ const startedAt = Date.now();
243
+ let upstream: Response;
244
+ try {
245
+ upstream = await fetch(target, { method: 'POST', headers, body });
246
+ } catch (e) {
247
+ const cause = e instanceof Error ? e.message : String(e);
248
+ noteAiProxyFailure(
249
+ { kind: 'unreachable', target, latencyMs: Date.now() - startedAt, cause },
250
+ throttle,
251
+ logger,
252
+ );
253
+ // The 502 body quotes the same two strings the terminal block does, so it
254
+ // gets the same masking: the target carries the page's own query string,
255
+ // and a fetch error routinely echoes the URL back.
256
+ res.writeHead(502, { 'content-type': 'text/plain' });
257
+ res.end(
258
+ `pyric dev ai-proxy: upstream ${redactUrl(target)} unreachable: ${redactUrl(cause)}\n` +
259
+ 'Set PYRIC_AI_PROXY_UPSTREAM to an OpenAI-compatible base URL ' +
260
+ `(default ${AI_PROXY_DEFAULT_UPSTREAM}).`,
261
+ );
262
+ return;
263
+ }
264
+
265
+ // A refusal from a reachable upstream (bad model, missing key, rate limit)
266
+ // is just as invisible to a headless developer as a dead socket: the
267
+ // status rides through to the caller untouched, and is ALSO announced here.
268
+ // `Retry-After` rides along when the upstream sent one (429s and 503s carry
269
+ // it): it is the only place the requested backoff appears, since nothing in
270
+ // this path retries or sleeps.
271
+ if (!upstream.ok) {
272
+ const retryAfter = upstream.headers.get('retry-after');
273
+ const failure: AiProxyFailure = {
274
+ kind: 'status',
275
+ target,
276
+ latencyMs: Date.now() - startedAt,
277
+ status: upstream.status,
278
+ };
279
+ if (retryAfter !== null) failure.retryAfter = retryAfter;
280
+ noteAiProxyFailure(failure, throttle, logger);
281
+ }
282
+
283
+ const responseHeaders: Record<string, string> = { 'cache-control': 'no-store' };
284
+ const contentType = upstream.headers.get('content-type');
285
+ if (contentType) responseHeaders['content-type'] = contentType;
286
+ res.writeHead(upstream.status, responseHeaders);
287
+ if (!upstream.body) {
288
+ res.end();
289
+ return;
290
+ }
291
+ (res as ServerResponse & { flushHeaders?: () => void }).flushHeaders?.();
292
+
293
+ // Chunk-by-chunk passthrough. A dropped client cancels the upstream read
294
+ // so an abandoned SSE stream doesn't keep the upstream generating.
295
+ const reader = upstream.body.getReader();
296
+ let clientGone = false;
297
+ res.on('close', () => {
298
+ clientGone = true;
299
+ void reader.cancel().catch(() => {});
300
+ });
301
+ try {
302
+ for (;;) {
303
+ const { done, value } = await reader.read();
304
+ if (done) break;
305
+ res.write(value);
306
+ }
307
+ } catch (e) {
308
+ // Nothing to salvage for the caller: the response is already committed
309
+ // with the upstream's status, so a truncated body is all it gets. But a
310
+ // half-delivered completion is exactly the failure that used to vanish,
311
+ // so announce it, unless the CLIENT is the one who walked away (a closed
312
+ // tab cancelling an SSE stream is normal, not a fault worth a warning).
313
+ if (!clientGone) {
314
+ noteAiProxyFailure(
315
+ {
316
+ kind: 'stream-abort',
317
+ target,
318
+ latencyMs: Date.now() - startedAt,
319
+ cause: e instanceof Error ? e.message : String(e),
320
+ },
321
+ throttle,
322
+ logger,
323
+ );
324
+ }
325
+ }
326
+ res.end();
327
+ }
@@ -0,0 +1,109 @@
1
+ /**
2
+ * The AI line in the dev server's startup banner.
3
+ *
4
+ * AI is the only live service that used to boot silently: hosting, sandbox
5
+ * bundles, rules, Studio, the bridge and persistence all announce themselves,
6
+ * so "nothing about AI" read as "AI isn't part of this server" rather than
7
+ * "AI is here, unconfigured". One description ({@link describeAiStatus}),
8
+ * rendered in the two dev-server flavors, so the `pyric dev` banner and the
9
+ * Vite plugin can never disagree about what AI resolved to.
10
+ */
11
+ import type { AiEngineConfigWire } from './worker/protocol.js';
12
+ import { GEMINI_DEFAULT_BASE_URL, redactUrl } from 'pyric/ai/internal';
13
+ import { AI_PROXY_ROUTE, resolveAiProxyUpstream } from './ai-proxy.js';
14
+ import { sanitizeForTerminal } from './ai-terminal-text.js';
15
+
16
+ /**
17
+ * What the dev server itself knows about AI by the time it finishes booting.
18
+ *
19
+ * Everything here is resolved SYNCHRONOUSLY at bootstrap: the plugin's
20
+ * `ai.engine`/`ai.model` (already reduced to the worker wire shape) and the
21
+ * proxy upstream. Nothing in this shape forces a broker, a page, or an
22
+ * upstream connection into existence just to be reported. The engine is
23
+ * instantiated lazily, on the first `ai.*` op, in the page or the worker.
24
+ */
25
+ export interface AiStartupStatus {
26
+ /** Engine resolved by the dev server (the Vite plugin's `ai.engine` /
27
+ * `ai.model`). Absent means nothing server-side: the served page's own
28
+ * `getAI(...)` chooses the engine at runtime, and the server cannot know
29
+ * which (there is no CLI surface for it under `pyric dev`). */
30
+ engine?: AiEngineConfigWire;
31
+ /** `ai.mode`: `production` passes through to Google AI instead of mirroring. */
32
+ mode?: 'sandbox' | 'production';
33
+ /** Configured OpenAI-compatible upstream (`ai.proxyUpstream`). Absent falls
34
+ * back to `PYRIC_AI_PROXY_UPSTREAM`, then the local-Ollama default. */
35
+ proxyUpstream?: string;
36
+ }
37
+
38
+ /** Where the resolved proxy upstream came from, in the terminal's words. The
39
+ * provenance is the point of the line: a developer has to be able to tell a
40
+ * deliberate upstream from the Ollama default nobody chose, so every source
41
+ * names the knob that set it. */
42
+ function describeUpstreamProvenance(source: 'option' | 'env' | 'default'): string {
43
+ if (source === 'option') return ' (ai.proxyUpstream)';
44
+ if (source === 'env') return ' (PYRIC_AI_PROXY_UPSTREAM)';
45
+ return ' (default)';
46
+ }
47
+
48
+ /** The openai engine's model binding, which may not be pinned at all. */
49
+ function describeOpenAiModel(model: string | undefined): string {
50
+ if (model === undefined) return 'no model pinned';
51
+ return `model ${sanitizeForTerminal(model)}`;
52
+ }
53
+
54
+ /** Mark + body for one AI status line, in both terminal flavors. */
55
+ function describeAiStatus(status: AiStartupStatus): { mark: string; body: string } {
56
+ const upstream = resolveAiProxyUpstream(status.proxyUpstream);
57
+ const provenance = describeUpstreamProvenance(upstream.source);
58
+ const proxyChain = `${AI_PROXY_ROUTE} → ${redactUrl(upstream.target)}${provenance}`;
59
+
60
+ const engine = status.engine;
61
+ if (engine === undefined) {
62
+ // Visible absence, the `• rules no firestore.rules ...` idiom: AI
63
+ // silence used to be indistinguishable from AI-not-wired-up.
64
+ return {
65
+ mark: '•',
66
+ body: `no engine configured; the served page's getAI() picks one; ${proxyChain}`,
67
+ };
68
+ }
69
+ if (engine.kind === 'openai') {
70
+ const model = describeOpenAiModel(engine.model);
71
+ const baseUrl = engine.baseUrl ?? AI_PROXY_ROUTE;
72
+ let endpoint = proxyChain;
73
+ if (baseUrl !== AI_PROXY_ROUTE) {
74
+ endpoint = `${redactUrl(baseUrl)} (direct, bypasses ${AI_PROXY_ROUTE})`;
75
+ }
76
+ return { mark: '✔', body: `openai (${model}) → ${endpoint}` };
77
+ }
78
+ if (engine.kind === 'gemini') {
79
+ let passthrough = '';
80
+ if (status.mode === 'production') passthrough = 'production passthrough, ';
81
+ const hasKey = engine.apiKey !== undefined && engine.apiKey !== '';
82
+ let key = 'no API key; set GEMINI_API_KEY';
83
+ if (hasKey) key = 'API key set';
84
+ let endpoint = GEMINI_DEFAULT_BASE_URL;
85
+ if (engine.baseUrl !== undefined) endpoint = redactUrl(engine.baseUrl);
86
+ return { mark: '✔', body: `gemini (${passthrough}${key}) → ${endpoint}` };
87
+ }
88
+ const responses = engine.script?.length ?? 0;
89
+ return { mark: '✔', body: `scripted (${responses} canned response(s), no network)` };
90
+ }
91
+
92
+ /**
93
+ * The banner line for `pyric dev`'s aligned status column (`✔ hosting ...`).
94
+ * Exported for unit tests.
95
+ */
96
+ export function formatAiStatusLine(status: AiStartupStatus): string {
97
+ const { mark, body } = describeAiStatus(status);
98
+ return `${mark} ai ${body}`;
99
+ }
100
+
101
+ /**
102
+ * The same report in the Vite dev server's flavor (` ✔ [pyric] ...`, the
103
+ * idiom `vite-functions-development.ts` prints). Same content, so the two
104
+ * dev-server front doors never disagree about what AI resolved to.
105
+ */
106
+ export function formatAiStatusNote(status: AiStartupStatus): string {
107
+ const { mark, body } = describeAiStatus(status);
108
+ return ` ${mark} [pyric] ai: ${body}`;
109
+ }