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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (424) hide show
  1. package/dist/cli/serve.js +4 -4
  2. package/dist/cli/serve.js.map +1 -1
  3. package/dist/conformance/.generated/can-i-use-browser.d.ts.map +1 -1
  4. package/dist/conformance/.generated/can-i-use-browser.js +1 -1
  5. package/dist/conformance/.generated/can-i-use-browser.js.map +1 -1
  6. package/dist/conformance/.generated/can-i-use.d.ts.map +1 -1
  7. package/dist/conformance/.generated/can-i-use.js +2 -2
  8. package/dist/conformance/.generated/can-i-use.js.map +1 -1
  9. package/dist/serve/bundler.d.ts +13 -8
  10. package/dist/serve/bundler.d.ts.map +1 -1
  11. package/dist/serve/bundler.js +31 -36
  12. package/dist/serve/bundler.js.map +1 -1
  13. package/dist/serve/docs-ui/docs/_rhythm/index.html +1 -1
  14. package/dist/serve/docs-ui/docs/agent/set-up-your-agent/index.html +1 -1
  15. package/dist/serve/docs-ui/docs/agent/watch-and-review/index.html +1 -1
  16. package/dist/serve/docs-ui/docs/agent/work-with-an-agent/index.html +1 -1
  17. package/dist/serve/docs-ui/docs/ai-compat/index.html +1 -1
  18. package/dist/serve/docs-ui/docs/api-reference/index.html +2 -2
  19. package/dist/serve/docs-ui/docs/api-reference.md +1 -1
  20. package/dist/serve/docs-ui/docs/app-compat/index.html +1 -1
  21. package/dist/serve/docs-ui/docs/auth-compat/index.html +1 -1
  22. package/dist/serve/docs-ui/docs/build/ai-logic/index.html +44 -22
  23. package/dist/serve/docs-ui/docs/build/ai-logic.md +69 -21
  24. package/dist/serve/docs-ui/docs/build/authentication/index.html +1 -1
  25. package/dist/serve/docs-ui/docs/build/cloud-firestore/index.html +1 -1
  26. package/dist/serve/docs-ui/docs/build/cloud-messaging/index.html +1 -1
  27. package/dist/serve/docs-ui/docs/build/cloud-storage/index.html +1 -1
  28. package/dist/serve/docs-ui/docs/build/realtime-database/index.html +1 -1
  29. package/dist/serve/docs-ui/docs/conformance-scores/index.html +1 -1
  30. package/dist/serve/docs-ui/docs/create-pyric-reference-api/index.html +1 -1
  31. package/dist/serve/docs-ui/docs/database-compat/index.html +1 -1
  32. package/dist/serve/docs-ui/docs/firestore-compat/index.html +1 -1
  33. package/dist/serve/docs-ui/docs/functions-rtdb-compat/index.html +1 -1
  34. package/dist/serve/docs-ui/docs/get-started/how-the-swap-works/index.html +2 -1
  35. package/dist/serve/docs-ui/docs/get-started/how-the-swap-works.md +2 -0
  36. package/dist/serve/docs-ui/docs/get-started/start-building/index.html +3 -2
  37. package/dist/serve/docs-ui/docs/get-started/start-building.md +3 -1
  38. package/dist/serve/docs-ui/docs/index.json +349 -29
  39. package/dist/serve/docs-ui/docs/messaging-compat/index.html +1 -1
  40. package/dist/serve/docs-ui/docs/observe/resolve-runtime-status/index.html +28 -0
  41. package/dist/serve/docs-ui/docs/observe/resolve-runtime-status.md +47 -0
  42. package/dist/serve/docs-ui/docs/observe/see-whats-happening/index.html +1 -1
  43. package/dist/serve/docs-ui/docs/observe/shape-your-data/index.html +1 -1
  44. package/dist/serve/docs-ui/docs/overview/index.html +1 -1
  45. package/dist/serve/docs-ui/docs/pyric-admin-app-reference-api/index.html +1 -1
  46. package/dist/serve/docs-ui/docs/pyric-admin-auth-reference-api/index.html +1 -1
  47. package/dist/serve/docs-ui/docs/pyric-admin-database-reference-api/index.html +1 -1
  48. package/dist/serve/docs-ui/docs/pyric-admin-firestore-reference-api/index.html +1 -1
  49. package/dist/serve/docs-ui/docs/pyric-admin-messaging-reference-api/index.html +916 -0
  50. package/dist/serve/docs-ui/docs/pyric-admin-messaging-reference-api.md +706 -0
  51. package/dist/serve/docs-ui/docs/pyric-admin-storage-reference-api/index.html +1 -1
  52. package/dist/serve/docs-ui/docs/pyric-ai-reference-api/index.html +1 -1
  53. package/dist/serve/docs-ui/docs/pyric-ai-scripting-reference-api/index.html +1 -1
  54. package/dist/serve/docs-ui/docs/pyric-app-reference-api/index.html +1 -1
  55. package/dist/serve/docs-ui/docs/pyric-auth-reference-api/index.html +1 -1
  56. package/dist/serve/docs-ui/docs/pyric-cli-assurance-browser-reference-api/index.html +1 -1
  57. package/dist/serve/docs-ui/docs/pyric-cli-assurance-reference-api/index.html +1 -1
  58. package/dist/serve/docs-ui/docs/pyric-cli-bridge-client-reference-api/index.html +1 -1
  59. package/dist/serve/docs-ui/docs/pyric-cli-bridge-reference-api/index.html +1 -1
  60. package/dist/serve/docs-ui/docs/pyric-cli-conformance-browser-reference-api/index.html +1 -1
  61. package/dist/serve/docs-ui/docs/pyric-cli-conformance-docs-reference-api/index.html +1 -1
  62. package/dist/serve/docs-ui/docs/pyric-cli-conformance-reference-api/index.html +1 -1
  63. package/dist/serve/docs-ui/docs/pyric-cli-credentials-node-reference-api/index.html +1 -1
  64. package/dist/serve/docs-ui/docs/pyric-cli-discover-reference-api/index.html +1 -1
  65. package/dist/serve/docs-ui/docs/pyric-cli-register-reference-api/index.html +1 -1
  66. package/dist/serve/docs-ui/docs/pyric-cli-remote-reference-api/index.html +1 -1
  67. package/dist/serve/docs-ui/docs/pyric-cli-serve-worker-reference-api/index.html +168 -76
  68. package/dist/serve/docs-ui/docs/pyric-cli-serve-worker-reference-api.md +156 -73
  69. package/dist/serve/docs-ui/docs/pyric-cli-verify-reference-api/index.html +1 -1
  70. package/dist/serve/docs-ui/docs/pyric-cli-vite-reference-api/index.html +11 -8
  71. package/dist/serve/docs-ui/docs/pyric-cli-vite-reference-api.md +17 -4
  72. package/dist/serve/docs-ui/docs/pyric-database-reference-api/index.html +1 -1
  73. package/dist/serve/docs-ui/docs/pyric-firestore-reference-api/index.html +1 -1
  74. package/dist/serve/docs-ui/docs/pyric-messaging-reference-api/index.html +1 -1
  75. package/dist/serve/docs-ui/docs/pyric-messaging-sw-reference-api/index.html +1 -1
  76. package/dist/serve/docs-ui/docs/pyric-rules-reference-api/index.html +1 -1
  77. package/dist/serve/docs-ui/docs/pyric-sandbox-database-reference-api/index.html +1 -1
  78. package/dist/serve/docs-ui/docs/pyric-sandbox-firestore-reference-api/index.html +1 -1
  79. package/dist/serve/docs-ui/docs/pyric-sandbox-reference-api/index.html +37 -14
  80. package/dist/serve/docs-ui/docs/pyric-sandbox-reference-api.md +28 -8
  81. package/dist/serve/docs-ui/docs/pyric-storage-reference-api/index.html +1 -1
  82. package/dist/serve/docs-ui/docs/pyric-ui-agents-reference-api/index.html +1 -1
  83. package/dist/serve/docs-ui/docs/pyric-ui-auth-hooks-reference-api/index.html +1 -1
  84. package/dist/serve/docs-ui/docs/pyric-ui-auth-reference-api/index.html +1 -1
  85. package/dist/serve/docs-ui/docs/pyric-ui-events-hooks-reference-api/index.html +1 -1
  86. package/dist/serve/docs-ui/docs/pyric-ui-events-reference-api/index.html +1 -1
  87. package/dist/serve/docs-ui/docs/pyric-ui-firestore-hooks-reference-api/index.html +1 -1
  88. package/dist/serve/docs-ui/docs/pyric-ui-firestore-reference-api/index.html +1 -1
  89. package/dist/serve/docs-ui/docs/pyric-ui-primitives-reference-api/index.html +1 -1
  90. package/dist/serve/docs-ui/docs/pyric-ui-rtdb-reference-api/index.html +1 -1
  91. package/dist/serve/docs-ui/docs/pyric-ui-rules-hooks-reference-api/index.html +1 -1
  92. package/dist/serve/docs-ui/docs/pyric-ui-rules-reference-api/index.html +1 -1
  93. package/dist/serve/docs-ui/docs/pyric-ui-storage-hooks-reference-api/index.html +1 -1
  94. package/dist/serve/docs-ui/docs/pyric-ui-storage-reference-api/index.html +1 -1
  95. package/dist/serve/docs-ui/docs/pyric-ui-traffic-hooks-reference-api/index.html +1 -1
  96. package/dist/serve/docs-ui/docs/pyric-ui-traffic-reference-api/index.html +1 -1
  97. package/dist/serve/docs-ui/docs/rules-compat/index.html +1 -1
  98. package/dist/serve/docs-ui/docs/secure/audit-your-rules/index.html +1 -1
  99. package/dist/serve/docs-ui/docs/secure/firestore-rules-limits/index.html +1 -1
  100. package/dist/serve/docs-ui/docs/secure/read-a-denial/index.html +1 -1
  101. package/dist/serve/docs-ui/docs/secure/rtdb-rules-in-typescript/index.html +1 -1
  102. package/dist/serve/docs-ui/docs/secure/rules-standard-library/index.html +1 -1
  103. package/dist/serve/docs-ui/docs/secure/secure-it-with-rules/index.html +1 -1
  104. package/dist/serve/docs-ui/docs/secure/simulate-and-lint/index.html +1 -1
  105. package/dist/serve/docs-ui/docs/secure/write-a-rules-test-suite/index.html +1 -1
  106. package/dist/serve/docs-ui/docs/ship/ship-to-production/index.html +1 -1
  107. package/dist/serve/docs-ui/docs/ship/test-in-node/index.html +1 -1
  108. package/dist/serve/docs-ui/docs/storage-compat/index.html +1 -1
  109. package/dist/serve/docs-ui/docs/trust/how-we-know-it-matches-firebase/index.html +1 -1
  110. package/dist/serve/docs-ui/docs/trust/versioning-and-compatibility/index.html +1 -1
  111. package/dist/serve/docs-ui/docs/tutorial/index.html +429 -0
  112. package/dist/serve/docs-ui/docs/tutorial.md +701 -0
  113. package/dist/serve/docs-ui/llms.txt +3 -1
  114. package/dist/serve/entries/init.d.ts.map +1 -1
  115. package/dist/serve/entries/init.js +3 -0
  116. package/dist/serve/entries/init.js.map +1 -1
  117. package/dist/serve/entries/runtime.d.ts.map +1 -1
  118. package/dist/serve/entries/runtime.js +20 -5
  119. package/dist/serve/entries/runtime.js.map +1 -1
  120. package/dist/serve/entries/worker-runtime.d.ts +2 -2
  121. package/dist/serve/entries/worker-runtime.d.ts.map +1 -1
  122. package/dist/serve/entries/worker-runtime.js +45 -10
  123. package/dist/serve/entries/worker-runtime.js.map +1 -1
  124. package/dist/serve/runtime/chip-config.d.ts +19 -0
  125. package/dist/serve/runtime/chip-config.d.ts.map +1 -0
  126. package/dist/serve/runtime/chip-config.js +19 -0
  127. package/dist/serve/runtime/chip-config.js.map +1 -0
  128. package/dist/serve/runtime/chip-install.d.ts +10 -0
  129. package/dist/serve/runtime/chip-install.d.ts.map +1 -0
  130. package/dist/serve/runtime/chip-install.js +15 -0
  131. package/dist/serve/runtime/chip-install.js.map +1 -0
  132. package/dist/serve/runtime/chip.d.ts +17 -0
  133. package/dist/serve/runtime/chip.d.ts.map +1 -0
  134. package/dist/serve/runtime/chip.js +257 -0
  135. package/dist/serve/runtime/chip.js.map +1 -0
  136. package/dist/serve/runtime/manifest.d.ts +22 -0
  137. package/dist/serve/runtime/manifest.d.ts.map +1 -0
  138. package/dist/serve/runtime/manifest.js +20 -0
  139. package/dist/serve/runtime/manifest.js.map +1 -0
  140. package/dist/serve/runtime/status.d.ts +49 -0
  141. package/dist/serve/runtime/status.d.ts.map +1 -0
  142. package/dist/serve/runtime/status.js +164 -0
  143. package/dist/serve/runtime/status.js.map +1 -0
  144. package/dist/serve/runtime/worker-connection.d.ts +4 -0
  145. package/dist/serve/runtime/worker-connection.d.ts.map +1 -0
  146. package/dist/serve/runtime/worker-connection.js +11 -0
  147. package/dist/serve/runtime/worker-connection.js.map +1 -0
  148. package/dist/serve/runtime/worker-generation.d.ts +14 -0
  149. package/dist/serve/runtime/worker-generation.d.ts.map +1 -0
  150. package/dist/serve/runtime/worker-generation.js +56 -0
  151. package/dist/serve/runtime/worker-generation.js.map +1 -0
  152. package/dist/serve/runtime/worker-replacement.d.ts +18 -0
  153. package/dist/serve/runtime/worker-replacement.d.ts.map +1 -0
  154. package/dist/serve/runtime/worker-replacement.js +29 -0
  155. package/dist/serve/runtime/worker-replacement.js.map +1 -0
  156. package/dist/serve/standalone-assets.d.ts +2 -2
  157. package/dist/serve/standalone-assets.d.ts.map +1 -1
  158. package/dist/serve/standalone-assets.js +1 -1
  159. package/dist/serve/standalone-assets.js.map +1 -1
  160. package/dist/serve/studio-ui/assets/{RulesCodeEditor-CxVYcZXw.js → RulesCodeEditor-BDlH-5lp.js} +1 -1
  161. package/dist/serve/studio-ui/assets/{index-cpSfgQ7H.js → index-Bf9SSE8L.js} +5 -5
  162. package/dist/serve/studio-ui/assets/{seed-DeuFKi4J.js → seed-D_UjxfOU.js} +1 -1
  163. package/dist/serve/studio-ui/index.html +1 -1
  164. package/dist/serve/vite-ai-config.d.ts +25 -0
  165. package/dist/serve/vite-ai-config.d.ts.map +1 -0
  166. package/dist/serve/vite-ai-config.js +45 -0
  167. package/dist/serve/vite-ai-config.js.map +1 -0
  168. package/dist/serve/vite-plugin.d.ts +17 -36
  169. package/dist/serve/vite-plugin.d.ts.map +1 -1
  170. package/dist/serve/vite-plugin.js +38 -57
  171. package/dist/serve/vite-plugin.js.map +1 -1
  172. package/dist/serve/vite-rules-source.d.ts +8 -0
  173. package/dist/serve/vite-rules-source.d.ts.map +1 -0
  174. package/dist/serve/vite-rules-source.js +23 -0
  175. package/dist/serve/vite-rules-source.js.map +1 -0
  176. package/dist/serve/vite-worker-runtime.d.ts +20 -0
  177. package/dist/serve/vite-worker-runtime.d.ts.map +1 -0
  178. package/dist/serve/vite-worker-runtime.js +25 -0
  179. package/dist/serve/vite-worker-runtime.js.map +1 -0
  180. package/dist/serve/worker/client/connection.d.ts +7 -2
  181. package/dist/serve/worker/client/connection.d.ts.map +1 -1
  182. package/dist/serve/worker/client/connection.js +13 -16
  183. package/dist/serve/worker/client/connection.js.map +1 -1
  184. package/dist/serve/worker/client/core.d.ts +7 -0
  185. package/dist/serve/worker/client/core.d.ts.map +1 -1
  186. package/dist/serve/worker/client/core.js +28 -0
  187. package/dist/serve/worker/client/core.js.map +1 -1
  188. package/dist/serve/worker/client/runtime-control.d.ts +8 -0
  189. package/dist/serve/worker/client/runtime-control.d.ts.map +1 -0
  190. package/dist/serve/worker/client/runtime-control.js +11 -0
  191. package/dist/serve/worker/client/runtime-control.js.map +1 -0
  192. package/dist/serve/worker/entry.js +44 -2
  193. package/dist/serve/worker/entry.js.map +1 -1
  194. package/dist/serve/worker/host/core.js +1 -1
  195. package/dist/serve/worker/host/core.js.map +1 -1
  196. package/dist/serve/worker/host/firestore-writes.d.ts.map +1 -1
  197. package/dist/serve/worker/host/firestore-writes.js +3 -2
  198. package/dist/serve/worker/host/firestore-writes.js.map +1 -1
  199. package/dist/serve/worker/host/studio.js +1 -1
  200. package/dist/serve/worker/host/studio.js.map +1 -1
  201. package/dist/serve/worker/host-context.d.ts +1 -1
  202. package/dist/serve/worker/host-context.d.ts.map +1 -1
  203. package/dist/serve/worker/index.d.ts +2 -1
  204. package/dist/serve/worker/index.d.ts.map +1 -1
  205. package/dist/serve/worker/index.js +2 -1
  206. package/dist/serve/worker/index.js.map +1 -1
  207. package/dist/serve/worker/protocol.d.ts +15 -1
  208. package/dist/serve/worker/protocol.d.ts.map +1 -1
  209. package/dist/serve/worker/protocol.js +2 -2
  210. package/dist/serve/worker/protocol.js.map +1 -1
  211. package/dist/serve/worker/retirement.d.ts +21 -0
  212. package/dist/serve/worker/retirement.d.ts.map +1 -0
  213. package/dist/serve/worker/retirement.js +97 -0
  214. package/dist/serve/worker/retirement.js.map +1 -0
  215. package/dist/serve/worker/serve-init.d.ts.map +1 -1
  216. package/dist/serve/worker/serve-init.js +5 -6
  217. package/dist/serve/worker/serve-init.js.map +1 -1
  218. package/dist/vite.d.ts +1 -0
  219. package/dist/vite.d.ts.map +1 -1
  220. package/dist/vite.js.map +1 -1
  221. package/package.json +6 -4
  222. package/src/assurance/.generated/conformance-verdicts.ts +1090 -0
  223. package/src/assurance/attachment.ts +211 -0
  224. package/src/assurance/browser.ts +80 -0
  225. package/src/assurance/campaign.ts +496 -0
  226. package/src/assurance/capabilities.ts +446 -0
  227. package/src/assurance/cases.ts +43 -0
  228. package/src/assurance/index.ts +75 -0
  229. package/src/assurance/runner.ts +885 -0
  230. package/src/assurance/tool-names.ts +15 -0
  231. package/src/assurance/tools.ts +830 -0
  232. package/src/assurance/types.ts +352 -0
  233. package/src/assurance/validation.ts +434 -0
  234. package/src/bridge/client/bridge.ts +550 -0
  235. package/src/bridge/client/dispatch.ts +137 -0
  236. package/src/bridge/client.ts +45 -0
  237. package/src/bridge/protocol.ts +351 -0
  238. package/src/bridge/server/audit.ts +53 -0
  239. package/src/bridge/server/bridge.ts +597 -0
  240. package/src/bridge/server/headless.ts +160 -0
  241. package/src/bridge/server/json-schema-to-zod.ts +108 -0
  242. package/src/bridge/server/local-bridge.ts +66 -0
  243. package/src/bridge/server/logger.ts +50 -0
  244. package/src/bridge/server/mcp-contract.ts +93 -0
  245. package/src/bridge/server/mcp.ts +129 -0
  246. package/src/bridge/server/peer.ts +232 -0
  247. package/src/bridge/server/standalone.ts +347 -0
  248. package/src/bridge/server/tool-metadata.ts +99 -0
  249. package/src/bridge/server.ts +28 -0
  250. package/src/cli/can-i-use.ts +50 -0
  251. package/src/cli/cli.test.ts +493 -0
  252. package/src/cli/database-rules.ts +329 -0
  253. package/src/cli/dev-runner.ts +282 -0
  254. package/src/cli/firebase-json.ts +100 -0
  255. package/src/cli/firestore-indexes.ts +70 -0
  256. package/src/cli/index.ts +425 -0
  257. package/src/cli/init.ts +260 -0
  258. package/src/cli/mcp-proxy.ts +196 -0
  259. package/src/cli/parse-args.ts +80 -0
  260. package/src/cli/rules.ts +259 -0
  261. package/src/cli/scope.ts +67 -0
  262. package/src/cli/serve.ts +996 -0
  263. package/src/cli/service-commands.ts +73 -0
  264. package/src/cli/snapshot.ts +164 -0
  265. package/src/cli/storage-rules.ts +172 -0
  266. package/src/cli/verify.ts +416 -0
  267. package/src/conformance/.generated/can-i-use-browser.ts +113 -0
  268. package/src/conformance/.generated/can-i-use.ts +117 -0
  269. package/src/conformance/.generated/conformance-docs.ts +14 -0
  270. package/src/conformance/browser.ts +24 -0
  271. package/src/conformance/can-i-use-tool.ts +51 -0
  272. package/src/conformance/can-i-use.ts +18 -0
  273. package/src/conformance/docs.ts +8 -0
  274. package/src/conformance/index.ts +20 -0
  275. package/src/conformance/tools.ts +16 -0
  276. package/src/credentials/core/memoize-ttl.ts +142 -0
  277. package/src/credentials/core/types.ts +10 -0
  278. package/src/credentials/node/from-adc.ts +96 -0
  279. package/src/credentials/node/from-service-account.ts +133 -0
  280. package/src/credentials/node/index.ts +6 -0
  281. package/src/discover/concurrency.ts +127 -0
  282. package/src/discover/crawler-adapter.ts +142 -0
  283. package/src/discover/crawler.ts +1127 -0
  284. package/src/discover/credential-free.ts +27 -0
  285. package/src/discover/findCollectionGroup.ts +131 -0
  286. package/src/discover/firestore-source.ts +59 -0
  287. package/src/discover/index.ts +5 -0
  288. package/src/discover/merge.ts +523 -0
  289. package/src/discover/session.ts +402 -0
  290. package/src/discover/tools.ts +201 -0
  291. package/src/discover/types.ts +187 -0
  292. package/src/discover/wire.ts +324 -0
  293. package/src/functions-rtdb/child.ts +382 -0
  294. package/src/functions-rtdb/delivery.ts +7 -0
  295. package/src/functions-rtdb/discovery.ts +136 -0
  296. package/src/functions-rtdb/event.ts +64 -0
  297. package/src/functions-rtdb/execution.ts +111 -0
  298. package/src/functions-rtdb/in-memory-delivery.ts +40 -0
  299. package/src/functions-rtdb/project.ts +74 -0
  300. package/src/functions-rtdb/projection.ts +104 -0
  301. package/src/functions-rtdb/reference-pattern.ts +20 -0
  302. package/src/functions-rtdb/remote-delivery.ts +23 -0
  303. package/src/pkg-version.ts +49 -0
  304. package/src/register/esm-exports.ts +63 -0
  305. package/src/register/hooks.ts +30 -0
  306. package/src/register/index.ts +150 -0
  307. package/src/register/mapping.ts +33 -0
  308. package/src/remote/index.ts +1050 -0
  309. package/src/rtdb/crawl-snapshot.ts +118 -0
  310. package/src/rtdb/inspection.ts +137 -0
  311. package/src/rtdb/load-rules-document.ts +56 -0
  312. package/src/rtdb/rules-generation-tool.ts +38 -0
  313. package/src/rtdb/rules-json.ts +29 -0
  314. package/src/serve/activity-guard.ts +29 -0
  315. package/src/serve/activity-route.ts +164 -0
  316. package/src/serve/activity-warning.ts +26 -0
  317. package/src/serve/bridge-mount.ts +200 -0
  318. package/src/serve/bundler.ts +519 -0
  319. package/src/serve/capture-store.ts +65 -0
  320. package/src/serve/discovery.ts +199 -0
  321. package/src/serve/entries/ai.ts +292 -0
  322. package/src/serve/entries/app-backend.ts +4 -0
  323. package/src/serve/entries/app-client.ts +26 -0
  324. package/src/serve/entries/app-session-store.ts +47 -0
  325. package/src/serve/entries/app.ts +11 -0
  326. package/src/serve/entries/auth-helper-core.ts +181 -0
  327. package/src/serve/entries/auth-helper-dom.ts +134 -0
  328. package/src/serve/entries/auth-helper-runtime.ts +20 -0
  329. package/src/serve/entries/auth.ts +255 -0
  330. package/src/serve/entries/bridge-url.ts +31 -0
  331. package/src/serve/entries/database.ts +129 -0
  332. package/src/serve/entries/firestore.ts +232 -0
  333. package/src/serve/entries/init.ts +49 -0
  334. package/src/serve/entries/keepalive.ts +46 -0
  335. package/src/serve/entries/messaging-sw.ts +75 -0
  336. package/src/serve/entries/messaging.ts +96 -0
  337. package/src/serve/entries/runtime.ts +556 -0
  338. package/src/serve/entries/session-store.ts +97 -0
  339. package/src/serve/entries/storage.ts +92 -0
  340. package/src/serve/entries/tab-sync-wiring.ts +274 -0
  341. package/src/serve/entries/worker-runtime.ts +135 -0
  342. package/src/serve/init-payload.ts +42 -0
  343. package/src/serve/namespace.ts +675 -0
  344. package/src/serve/open-browser.ts +68 -0
  345. package/src/serve/rules.ts +272 -0
  346. package/src/serve/runtime/chip-config.ts +30 -0
  347. package/src/serve/runtime/chip-install.ts +27 -0
  348. package/src/serve/runtime/chip.ts +282 -0
  349. package/src/serve/runtime/manifest.ts +37 -0
  350. package/src/serve/runtime/status.ts +216 -0
  351. package/src/serve/runtime/worker-connection.ts +14 -0
  352. package/src/serve/runtime/worker-generation.ts +75 -0
  353. package/src/serve/runtime/worker-replacement.ts +42 -0
  354. package/src/serve/sandbox-marker.ts +31 -0
  355. package/src/serve/server.ts +428 -0
  356. package/src/serve/standalone-assets.ts +180 -0
  357. package/src/serve/state-store.ts +166 -0
  358. package/src/serve/studio/disk-project-store.ts +185 -0
  359. package/src/serve/studio/disk-workspace.ts +162 -0
  360. package/src/serve/studio/index.ts +20 -0
  361. package/src/serve/studio/routes.ts +237 -0
  362. package/src/serve/studio/store-types.ts +48 -0
  363. package/src/serve/studio/studio-storage.test.ts +289 -0
  364. package/src/serve/vite-ai-config.ts +77 -0
  365. package/src/serve/vite-plugin.ts +1075 -0
  366. package/src/serve/vite-rules-source.ts +28 -0
  367. package/src/serve/vite-worker-runtime.ts +51 -0
  368. package/src/serve/worker/activity-bootstrap.ts +24 -0
  369. package/src/serve/worker/client/admin-firestore.ts +38 -0
  370. package/src/serve/worker/client/ai.ts +124 -0
  371. package/src/serve/worker/client/auth.ts +489 -0
  372. package/src/serve/worker/client/connection.ts +227 -0
  373. package/src/serve/worker/client/core.ts +384 -0
  374. package/src/serve/worker/client/disconnect.ts +45 -0
  375. package/src/serve/worker/client/firestore-reads.ts +181 -0
  376. package/src/serve/worker/client/firestore-refs.ts +226 -0
  377. package/src/serve/worker/client/firestore-writes.ts +232 -0
  378. package/src/serve/worker/client/handles.ts +91 -0
  379. package/src/serve/worker/client/messaging.ts +119 -0
  380. package/src/serve/worker/client/presence.ts +174 -0
  381. package/src/serve/worker/client/rtdb.ts +311 -0
  382. package/src/serve/worker/client/rules.ts +63 -0
  383. package/src/serve/worker/client/runtime-control.ts +22 -0
  384. package/src/serve/worker/client/service-worker-connection.ts +59 -0
  385. package/src/serve/worker/client/snapshots.ts +86 -0
  386. package/src/serve/worker/client/storage.ts +195 -0
  387. package/src/serve/worker/client/studio.ts +88 -0
  388. package/src/serve/worker/client.ts +57 -0
  389. package/src/serve/worker/durable-persistence.ts +137 -0
  390. package/src/serve/worker/entry.ts +223 -0
  391. package/src/serve/worker/host/admin-firestore.ts +88 -0
  392. package/src/serve/worker/host/connection.ts +179 -0
  393. package/src/serve/worker/host/core.ts +387 -0
  394. package/src/serve/worker/host/dispatch.ts +316 -0
  395. package/src/serve/worker/host/firestore-reads.ts +117 -0
  396. package/src/serve/worker/host/firestore-writes.ts +456 -0
  397. package/src/serve/worker/host/presence.ts +312 -0
  398. package/src/serve/worker/host/rtdb.ts +136 -0
  399. package/src/serve/worker/host/rules.ts +128 -0
  400. package/src/serve/worker/host/storage.ts +312 -0
  401. package/src/serve/worker/host/studio.ts +75 -0
  402. package/src/serve/worker/host/subscriptions.ts +238 -0
  403. package/src/serve/worker/host-ai.ts +165 -0
  404. package/src/serve/worker/host-auth.ts +469 -0
  405. package/src/serve/worker/host-context.ts +273 -0
  406. package/src/serve/worker/host-events.ts +94 -0
  407. package/src/serve/worker/host-messaging.ts +240 -0
  408. package/src/serve/worker/host.ts +47 -0
  409. package/src/serve/worker/index.ts +196 -0
  410. package/src/serve/worker/presence-timing.ts +15 -0
  411. package/src/serve/worker/protocol.ts +1124 -0
  412. package/src/serve/worker/retirement.ts +125 -0
  413. package/src/serve/worker/serve-init.ts +605 -0
  414. package/src/serve/worker/service-worker-channel.ts +33 -0
  415. package/src/serve/worker/service-worker-relay.ts +86 -0
  416. package/src/serve/writer-lock.ts +53 -0
  417. package/src/verify/cases.ts +233 -0
  418. package/src/verify/fixture.ts +258 -0
  419. package/src/verify/index.ts +519 -0
  420. package/src/verify/tools.ts +108 -0
  421. package/src/version/compat-target.ts +17 -0
  422. package/src/vite.ts +18 -0
  423. package/dist/serve/docs-ui/docs/pyric-firestore-values-reference-api/index.html +0 -31
  424. package/dist/serve/docs-ui/docs/pyric-firestore-values-reference-api.md +0 -29
@@ -0,0 +1,402 @@
1
+ /**
2
+ * In-process session store for `firestore_discover_paths` continuations.
3
+ *
4
+ * Phase 3.3 locked the session API: the agent receives an opaque
5
+ * `continuation` token between batches and the server holds the
6
+ * partial-schema state. This module owns the storage layer; Item 4.2
7
+ * wires it into the crawler.
8
+ *
9
+ * Hard caps (per prerequisite 0.G — bounded session memory):
10
+ * - `maxSessions` = 8 — total live sessions
11
+ * - `maxSessionBytes` = 32 MB — per-session payload (well above
12
+ * Phase 0.4's 11.64 MB worst case)
13
+ * - `ttlMs` = 30 min — idle TTL (Phase 3.3 lock)
14
+ *
15
+ * Token format: `disc_` + base64url(16-byte ULID). ULID gives natural
16
+ * monotonic ordering (timestamp-prefixed) and avoids a runtime
17
+ * dependency on the `ulid` package.
18
+ *
19
+ * Error model — never throws on expected paths (per Item 4 acceptance
20
+ * criteria). Returns `{ ok: false, error: { code, message, recoveryHint } }`:
21
+ * - `SESSION_EXPIRED` — token unknown OR past TTL (per 0.C)
22
+ * - `SESSION_EVICTED` — token was capacity-evicted by an LRU
23
+ * bump from a concurrent agent (per 0.G)
24
+ * - `SESSION_PAYLOAD_TOO_LARGE` — state would exceed `maxSessionBytes`
25
+ * - `SESSION_MALFORMED_TOKEN` — wrong prefix or undecodable b64
26
+ */
27
+ 'use strict';
28
+
29
+ /**
30
+ * Default RNG — Web Crypto's `getRandomValues`. Works in modern
31
+ * browsers AND Node 19+ (webcrypto on globalThis) AND Bun, so this
32
+ * module evaluates cleanly in any environment without a top-level
33
+ * `node:crypto` import.
34
+ *
35
+ * Tests + Node consumers that want a deterministic seam can still
36
+ * override via `SessionStoreOptions.randomBytes`.
37
+ */
38
+ function defaultRandomBytes(n: number): Uint8Array {
39
+ const buf = new Uint8Array(n);
40
+ globalThis.crypto.getRandomValues(buf);
41
+ return buf;
42
+ }
43
+
44
+ // ─── Defaults (per prerequisite 0.G + Phase 3.3) ──────────────────────────
45
+
46
+ export const DEFAULT_MAX_SESSIONS = 8;
47
+ export const DEFAULT_MAX_SESSION_BYTES = 32 * 1024 * 1024; // 32 MB
48
+ export const DEFAULT_TTL_MS = 30 * 60 * 1000; // 30 min idle
49
+ /**
50
+ * How many recently-evicted tokens to remember so a returning agent can
51
+ * be told *why* their session is gone (`SESSION_EVICTED`) rather than
52
+ * the generic `SESSION_EXPIRED`. Bounded ring buffer; older entries get
53
+ * overwritten and degrade silently to `SESSION_EXPIRED`.
54
+ */
55
+ const DEFAULT_EVICTION_LOG_SIZE = 64;
56
+
57
+ const TOKEN_PREFIX = 'disc_';
58
+
59
+ // ─── Types ────────────────────────────────────────────────────────────────
60
+
61
+ export type SessionErrorCode =
62
+ | 'SESSION_EXPIRED'
63
+ | 'SESSION_EVICTED'
64
+ | 'SESSION_PAYLOAD_TOO_LARGE'
65
+ | 'SESSION_MALFORMED_TOKEN';
66
+
67
+ export interface SessionError {
68
+ code: SessionErrorCode;
69
+ message: string;
70
+ recoveryHint: string;
71
+ }
72
+
73
+ /**
74
+ * A live session record. `state` is opaque to the store — Item 4.2 will
75
+ * instantiate `SessionStore` with the concrete crawler-state type.
76
+ */
77
+ export interface SessionRecord<TState> {
78
+ /** Raw ULID — internal id; not the token surfaced to agents. */
79
+ readonly id: string;
80
+ /** Opaque continuation handle: `disc_<base64url-ulid>`. */
81
+ readonly token: string;
82
+ readonly createdAt: number;
83
+ /** Last get/update; drives both LRU eviction and TTL. */
84
+ lastAccessedAt: number;
85
+ /** Caller-reported payload size for byte-cap enforcement. */
86
+ bytes: number;
87
+ state: TState;
88
+ }
89
+
90
+ export interface SessionStoreOptions {
91
+ maxSessions?: number;
92
+ maxSessionBytes?: number;
93
+ ttlMs?: number;
94
+ /** Test seam — defaults to `Date.now`. */
95
+ now?: () => number;
96
+ /** Test seam — defaults to `crypto.randomBytes`. */
97
+ randomBytes?: (n: number) => Uint8Array;
98
+ evictionLogSize?: number;
99
+ }
100
+
101
+ /** Discriminated-union result so callers don't have to try/catch. */
102
+ export type SessionResult<T> =
103
+ | { ok: true; value: T }
104
+ | { ok: false; error: SessionError };
105
+
106
+ // ─── ULID + token codec ───────────────────────────────────────────────────
107
+
108
+ /**
109
+ * 16-byte ULID: 6 bytes big-endian ms timestamp, 10 bytes randomness.
110
+ * Sortable by creation time, collision-free at 80 bits randomness.
111
+ */
112
+ function generateUlidBytes(now: number, rand: (n: number) => Uint8Array): Uint8Array {
113
+ const bytes = new Uint8Array(16);
114
+ let ts = Math.max(0, Math.floor(now));
115
+ for (let i = 5; i >= 0; i--) {
116
+ bytes[i] = ts & 0xff;
117
+ ts = Math.floor(ts / 256);
118
+ }
119
+ const r = rand(10);
120
+ bytes.set(r, 6);
121
+ return bytes;
122
+ }
123
+
124
+ function bytesToBase64Url(bytes: Uint8Array): string {
125
+ let bin = '';
126
+ for (let i = 0; i < bytes.length; i++) bin += String.fromCharCode(bytes[i]!);
127
+ return btoa(bin).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
128
+ }
129
+
130
+ function base64UrlToBytes(s: string): Uint8Array | null {
131
+ if (!/^[A-Za-z0-9_-]*$/.test(s)) return null;
132
+ let b64 = s.replace(/-/g, '+').replace(/_/g, '/');
133
+ while (b64.length % 4) b64 += '=';
134
+ try {
135
+ const bin = atob(b64);
136
+ const out = new Uint8Array(bin.length);
137
+ for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);
138
+ return out;
139
+ } catch {
140
+ return null;
141
+ }
142
+ }
143
+
144
+ function bytesToHex(bytes: Uint8Array): string {
145
+ let s = '';
146
+ for (let i = 0; i < bytes.length; i++) {
147
+ s += bytes[i]!.toString(16).padStart(2, '0');
148
+ }
149
+ return s;
150
+ }
151
+
152
+ /**
153
+ * Encode a 16-byte ULID into a `disc_<base64url>` token. Internal — used
154
+ * by `SessionStore.create`.
155
+ */
156
+ export function encodeToken(ulidBytes: Uint8Array): string {
157
+ if (ulidBytes.length !== 16) {
158
+ throw new Error(`encodeToken: expected 16-byte ULID, got ${ulidBytes.length}`);
159
+ }
160
+ return TOKEN_PREFIX + bytesToBase64Url(ulidBytes);
161
+ }
162
+
163
+ /**
164
+ * Decode a `disc_<base64url>` token. Returns `null` on any malformation
165
+ * — caller maps null to `SESSION_MALFORMED_TOKEN`.
166
+ */
167
+ export function decodeToken(token: string): { id: string } | null {
168
+ if (typeof token !== 'string' || !token.startsWith(TOKEN_PREFIX)) return null;
169
+ const tail = token.slice(TOKEN_PREFIX.length);
170
+ if (tail.length === 0) return null;
171
+ const bytes = base64UrlToBytes(tail);
172
+ if (bytes === null || bytes.length !== 16) return null;
173
+ return { id: bytesToHex(bytes) };
174
+ }
175
+
176
+ // ─── Session store ────────────────────────────────────────────────────────
177
+
178
+ /**
179
+ * In-process LRU session store with TTL sweep and per-session byte cap.
180
+ *
181
+ * Eviction policy:
182
+ * 1. On every `create`/`get`/`update`, sweep TTL-expired sessions first.
183
+ * Their tokens land in the eviction log as `SESSION_EXPIRED`.
184
+ * 2. If `create` would exceed `maxSessions`, evict the LRU
185
+ * (oldest-by-`lastAccessedAt`). Its token lands in the eviction log
186
+ * as `SESSION_EVICTED` so the displaced agent gets a meaningful
187
+ * error on its next call.
188
+ * 3. `update` rejects with `SESSION_PAYLOAD_TOO_LARGE` if the new
189
+ * `bytes` exceeds `maxSessionBytes` (per-session cap, not aggregate).
190
+ *
191
+ * The eviction log is a bounded ring buffer; once it overflows, evicted
192
+ * tokens degrade silently to `SESSION_EXPIRED` (still actionable — the
193
+ * recoveryHint is the same: re-issue without continuation).
194
+ */
195
+ export class SessionStore<TState> {
196
+ private readonly maxSessions: number;
197
+ private readonly maxSessionBytes: number;
198
+ private readonly ttlMs: number;
199
+ private readonly evictionLogSize: number;
200
+ private readonly now: () => number;
201
+ private readonly rand: (n: number) => Uint8Array;
202
+
203
+ private readonly sessions = new Map<string, SessionRecord<TState>>();
204
+ /** id → eviction reason. Bounded — see `recordEviction`. */
205
+ private readonly evictionLog = new Map<string, SessionErrorCode>();
206
+
207
+ constructor(opts: SessionStoreOptions = {}) {
208
+ this.maxSessions = opts.maxSessions ?? DEFAULT_MAX_SESSIONS;
209
+ this.maxSessionBytes = opts.maxSessionBytes ?? DEFAULT_MAX_SESSION_BYTES;
210
+ this.ttlMs = opts.ttlMs ?? DEFAULT_TTL_MS;
211
+ this.evictionLogSize = opts.evictionLogSize ?? DEFAULT_EVICTION_LOG_SIZE;
212
+ this.now = opts.now ?? Date.now;
213
+ this.rand = opts.randomBytes ?? defaultRandomBytes;
214
+
215
+ if (!Number.isInteger(this.maxSessions) || this.maxSessions < 1) {
216
+ throw new RangeError(
217
+ `SessionStore: maxSessions must be a positive integer, got ${this.maxSessions}`,
218
+ );
219
+ }
220
+ if (!Number.isInteger(this.maxSessionBytes) || this.maxSessionBytes < 1) {
221
+ throw new RangeError(
222
+ `SessionStore: maxSessionBytes must be a positive integer, got ${this.maxSessionBytes}`,
223
+ );
224
+ }
225
+ if (!Number.isInteger(this.ttlMs) || this.ttlMs < 1) {
226
+ throw new RangeError(
227
+ `SessionStore: ttlMs must be a positive integer, got ${this.ttlMs}`,
228
+ );
229
+ }
230
+ }
231
+
232
+ /** Live session count. */
233
+ get size(): number {
234
+ return this.sessions.size;
235
+ }
236
+
237
+ /**
238
+ * Create a new session. Always succeeds unless `bytes` exceeds the
239
+ * per-session byte cap. On cap-hit, evicts the LRU session — the
240
+ * displaced token will report `SESSION_EVICTED` on its next access.
241
+ */
242
+ create(state: TState, bytes: number): SessionResult<SessionRecord<TState>> {
243
+ if (bytes > this.maxSessionBytes) {
244
+ return {
245
+ ok: false,
246
+ error: {
247
+ code: 'SESSION_PAYLOAD_TOO_LARGE',
248
+ message: `Session payload (${bytes} bytes) exceeds cap (${this.maxSessionBytes} bytes)`,
249
+ recoveryHint:
250
+ 'Reduce per-batch result size (lower maxSamples or rootFilter to a smaller subtree)',
251
+ },
252
+ };
253
+ }
254
+ const now = this.now();
255
+ this.sweepExpired(now);
256
+ if (this.sessions.size >= this.maxSessions) {
257
+ this.evictLru();
258
+ }
259
+ const ulidBytes = generateUlidBytes(now, this.rand);
260
+ const id = bytesToHex(ulidBytes);
261
+ const token = encodeToken(ulidBytes);
262
+ const record: SessionRecord<TState> = {
263
+ id,
264
+ token,
265
+ createdAt: now,
266
+ lastAccessedAt: now,
267
+ bytes,
268
+ state,
269
+ };
270
+ this.sessions.set(id, record);
271
+ return { ok: true, value: record };
272
+ }
273
+
274
+ /**
275
+ * Look up a session by token. Touches `lastAccessedAt` on success so
276
+ * subsequent reads keep the session warm. On expired/malformed/evicted
277
+ * tokens returns the appropriate structured error.
278
+ */
279
+ get(token: string): SessionResult<SessionRecord<TState>> {
280
+ const decoded = decodeToken(token);
281
+ if (decoded === null) {
282
+ return {
283
+ ok: false,
284
+ error: {
285
+ code: 'SESSION_MALFORMED_TOKEN',
286
+ message: `Continuation token is malformed (expected ${TOKEN_PREFIX}<base64url>)`,
287
+ recoveryHint: 'Re-issue without continuation to start fresh',
288
+ },
289
+ };
290
+ }
291
+ const now = this.now();
292
+ this.sweepExpired(now);
293
+ const record = this.sessions.get(decoded.id);
294
+ if (!record) return this.lookupEvicted(decoded.id);
295
+ record.lastAccessedAt = now;
296
+ return { ok: true, value: record };
297
+ }
298
+
299
+ /**
300
+ * Replace the state of an existing session. Same lookup/error model
301
+ * as `get`, plus per-session-bytes enforcement on the new payload.
302
+ */
303
+ update(token: string, state: TState, bytes: number): SessionResult<SessionRecord<TState>> {
304
+ const lookup = this.get(token);
305
+ if (!lookup.ok) return lookup;
306
+ if (bytes > this.maxSessionBytes) {
307
+ return {
308
+ ok: false,
309
+ error: {
310
+ code: 'SESSION_PAYLOAD_TOO_LARGE',
311
+ message: `Updated session payload (${bytes} bytes) exceeds cap (${this.maxSessionBytes} bytes)`,
312
+ recoveryHint:
313
+ 'Crawl produced more state than the per-session cap allows; surface partial result and stop',
314
+ },
315
+ };
316
+ }
317
+ const record = lookup.value;
318
+ record.state = state;
319
+ record.bytes = bytes;
320
+ // `get` already touched lastAccessedAt; no need to re-stamp.
321
+ return { ok: true, value: record };
322
+ }
323
+
324
+ /** Best-effort delete; returns true if a session was removed. */
325
+ delete(token: string): boolean {
326
+ const decoded = decodeToken(token);
327
+ if (decoded === null) return false;
328
+ return this.sessions.delete(decoded.id);
329
+ }
330
+
331
+ /**
332
+ * Drop sessions whose `lastAccessedAt + ttlMs` is in the past.
333
+ * Returns the number of sessions evicted. Public for tests +
334
+ * future scheduled-sweep usage; `create`/`get`/`update` all call it
335
+ * lazily so callers don't normally need to.
336
+ */
337
+ sweepExpired(now: number = this.now()): number {
338
+ let count = 0;
339
+ const cutoff = now - this.ttlMs;
340
+ for (const [id, record] of this.sessions) {
341
+ if (record.lastAccessedAt < cutoff) {
342
+ this.sessions.delete(id);
343
+ this.recordEviction(id, 'SESSION_EXPIRED');
344
+ count++;
345
+ }
346
+ }
347
+ return count;
348
+ }
349
+
350
+ // ─── internals ───────────────────────────────────────────────────────
351
+
352
+ private evictLru(): void {
353
+ let oldestId: string | null = null;
354
+ let oldestTs = Infinity;
355
+ for (const [id, record] of this.sessions) {
356
+ if (record.lastAccessedAt < oldestTs) {
357
+ oldestTs = record.lastAccessedAt;
358
+ oldestId = id;
359
+ }
360
+ }
361
+ if (oldestId !== null) {
362
+ this.sessions.delete(oldestId);
363
+ this.recordEviction(oldestId, 'SESSION_EVICTED');
364
+ }
365
+ }
366
+
367
+ private recordEviction(id: string, reason: SessionErrorCode): void {
368
+ if (this.evictionLog.size >= this.evictionLogSize) {
369
+ // Drop the oldest entry — Map iteration is insertion-ordered.
370
+ const firstKey = this.evictionLog.keys().next().value;
371
+ if (firstKey !== undefined) this.evictionLog.delete(firstKey);
372
+ }
373
+ this.evictionLog.set(id, reason);
374
+ }
375
+
376
+ private lookupEvicted(id: string): SessionResult<SessionRecord<TState>> {
377
+ const reason = this.evictionLog.get(id);
378
+ if (reason === 'SESSION_EVICTED') {
379
+ return {
380
+ ok: false,
381
+ error: {
382
+ code: 'SESSION_EVICTED',
383
+ message:
384
+ 'Continuation was evicted because the session-store capacity is full (a concurrent agent bumped you).',
385
+ recoveryHint:
386
+ 'Retry the discover_paths call without the continuation; consider serializing concurrent crawls',
387
+ },
388
+ };
389
+ }
390
+ // SESSION_EXPIRED covers both TTL-expired and never-existed tokens.
391
+ // Both surface the same recovery action: re-issue without continuation.
392
+ return {
393
+ ok: false,
394
+ error: {
395
+ code: 'SESSION_EXPIRED',
396
+ message:
397
+ 'Continuation is unknown or past the 30-minute TTL. The server has discarded the partial state.',
398
+ recoveryHint: 'Re-issue without continuation to start fresh',
399
+ },
400
+ };
401
+ }
402
+ }
@@ -0,0 +1,201 @@
1
+ /**
2
+ * `createFirestoreDiscoverTools({ resolveDb })` — `ToolHandler[]` for
3
+ * the discover-paths feature, browser-safe (no firebase-admin import).
4
+ *
5
+ * Two handlers:
6
+ * - `firestore_discover_paths` — BFS sample every collection
7
+ * reachable from the root, return per-templatePath schema +
8
+ * cost counters. Supports `dryRun` cost preview and
9
+ * `continuation` resume.
10
+ * - `firestore_find_collection_group` — bounded
11
+ * `collectionGroup(id).select().limit(N).get()` to answer "where
12
+ * does collection ID X appear?".
13
+ *
14
+ * Session lifetime: per-factory-call. Each `createFirestoreDiscoverTools`
15
+ * invocation creates its own `SessionStore` for continuation tokens.
16
+ * Per the in-progress agents work, a single agent process should
17
+ * therefore reuse one factory instance for the duration of a session.
18
+ *
19
+ * Per F2 / F4: identity is a value (the resolver), lifecycle is
20
+ * per-dispatch. `resolveDb()` runs inside each tool execute so hosts
21
+ * can swap the underlying CrawlerFirestore (e.g. admin → sandbox)
22
+ * between calls without re-wiring.
23
+ *
24
+ * Output shape: `crawl()`'s native `finalizedSchemas: Map<...>` flattens
25
+ * to a `schemas: Record<templatePath, ...>` shape for JSON
26
+ * serialization across the tool boundary. `discovered` (raw refs) is
27
+ * dropped — agents consume the finalized schemas.
28
+ */
29
+ import type { ToolHandler } from '@inbrowser/agent';
30
+ import { crawl, type FullCrawlResult } from './crawler.js';
31
+ import { findCollectionGroup } from './findCollectionGroup.js';
32
+ import { SessionStore } from './session.js';
33
+ import type { PersistedCrawlState } from './crawler.js';
34
+ import type { CollectionSchema, DiscoverEvent } from './types.js';
35
+ import type {
36
+ CollectionGroupCapableFirestore,
37
+ CrawlerFirestore,
38
+ } from './firestore-source.js';
39
+
40
+ export interface FirestoreDiscoverToolDeps {
41
+ /**
42
+ * Resolver returning the CrawlerFirestore to scan. Called per
43
+ * dispatch (F4). For `firestore_find_collection_group` the returned
44
+ * Firestore must also satisfy {@link CollectionGroupCapableFirestore}.
45
+ */
46
+ resolveDb(): CrawlerFirestore & CollectionGroupCapableFirestore;
47
+ }
48
+
49
+ /** JSON-serializable shape returned by `firestore_discover_paths`. */
50
+ export interface DiscoverPathsToolResult {
51
+ /** Per-templatePath finalized schemas, keyed by templatePath. */
52
+ schemas: Record<string, CollectionSchema>;
53
+ events: DiscoverEvent[];
54
+ /** `listCollections` + `listDocuments` calls — cumulative across batches. */
55
+ listOps: number;
56
+ /** `.get()` calls during sampling — cumulative across batches. */
57
+ readOps: number;
58
+ /** Opaque resume handle iff the crawl paused at a payload boundary. */
59
+ continuation?: string;
60
+ /** True iff the crawl finished. Equivalent to `continuation === undefined`. */
61
+ complete: boolean;
62
+ /** Present iff this was a `dryRun: true` preview. */
63
+ dryRunCostEstimate?: FullCrawlResult['dryRunCostEstimate'];
64
+ }
65
+
66
+ interface DiscoverPathsArgs {
67
+ maxDepth?: number;
68
+ maxConcurrency?: number;
69
+ maxSamples?: number;
70
+ stopOnStable?: number;
71
+ maxBatchBytes?: number;
72
+ rootPrefix?: string;
73
+ dryRun?: boolean;
74
+ dryRunSubtreeMultiplier?: number;
75
+ continuation?: string;
76
+ }
77
+
78
+ interface FindCollectionGroupArgs {
79
+ collectionId: string;
80
+ limit?: number;
81
+ }
82
+
83
+ export function createFirestoreDiscoverTools(
84
+ deps: FirestoreDiscoverToolDeps,
85
+ ): ToolHandler[] {
86
+ const { resolveDb } = deps;
87
+ // Per-factory session store — see header note on lifetime.
88
+ const sessions = new SessionStore<PersistedCrawlState>();
89
+
90
+ return [
91
+ {
92
+ name: 'firestore_discover_paths',
93
+ description:
94
+ 'Sample every collection reachable from the database root and return per-templatePath schemas (field types, presence, enums, examples) plus discovered subcollection paths. Use this to understand a Firestore database\'s shape before generating client code, security rules, or analytics queries. ' +
95
+ 'Cost is bounded: structure walk is one listCollections/listDocuments per collection; sampling is min(maxSamples, available) per templatePath with adaptive early-exit. Cumulative cost is reported as listOps + readOps. ' +
96
+ 'Set dryRun: true for an informational cost preview that issues exactly one root listCollections RPC and returns a heuristic projection — no documents are read. Large databases pause at payload boundaries and return a continuation token; pass it back as the next call\'s continuation to resume.',
97
+ parameters: {
98
+ type: 'object',
99
+ properties: {
100
+ maxDepth: { type: 'integer', minimum: 1, description: 'Max BFS depth from root collections. Default 10 — runaway guard.' },
101
+ maxConcurrency: { type: 'integer', minimum: 1, description: 'In-flight RPC cap. Default 32.' },
102
+ maxSamples: { type: 'integer', minimum: 1, description: 'Hard cap on docs sampled per templatePath. Default 50.' },
103
+ stopOnStable: { type: 'integer', minimum: 1, description: 'Adaptive early-exit: stop sampling a templatePath after this many consecutive no-change merges. Default 8. Set higher than maxSamples to disable.' },
104
+ maxBatchBytes: { type: 'integer', minimum: 1024, description: 'Pause when persisted state exceeds this many bytes. Default 1 MB. Only effective when continuations are in play.' },
105
+ rootPrefix: { type: 'string', description: 'Restrict the crawl to root collections whose ID starts with this prefix.' },
106
+ dryRun: { type: 'boolean', description: 'Cost-preview mode. Issues exactly one root listCollections RPC and returns a heuristic projection. No documents are read. Cannot be combined with a continuation token.' },
107
+ dryRunSubtreeMultiplier: { type: 'integer', minimum: 1, description: 'Multiplier in the dryRun projection: roots × this = projected total collections. Default 3 (conservative).' },
108
+ continuation: { type: 'string', description: 'Resume a paused crawl. Use the continuation token from a prior result. Pass nothing to start fresh.' },
109
+ },
110
+ },
111
+ async execute(args) {
112
+ const params = (args ?? {}) as DiscoverPathsArgs;
113
+ try {
114
+ const db = resolveDb();
115
+ const rootFilter = params.rootPrefix
116
+ ? (id: string) => id.startsWith(params.rootPrefix!)
117
+ : undefined;
118
+ const result = await crawl(
119
+ db,
120
+ {
121
+ maxDepth: params.maxDepth,
122
+ maxConcurrency: params.maxConcurrency,
123
+ maxSamples: params.maxSamples,
124
+ stopOnStable: params.stopOnStable,
125
+ maxBatchBytes: params.maxBatchBytes,
126
+ rootFilter,
127
+ dryRun: params.dryRun,
128
+ dryRunSubtreeMultiplier: params.dryRunSubtreeMultiplier,
129
+ continuation: params.continuation,
130
+ },
131
+ sessions,
132
+ );
133
+ const schemas: Record<string, CollectionSchema> = {};
134
+ for (const [templatePath, schema] of result.finalizedSchemas) {
135
+ schemas[templatePath] = schema;
136
+ }
137
+ const data: DiscoverPathsToolResult = {
138
+ schemas,
139
+ events: result.events,
140
+ listOps: result.listOps,
141
+ readOps: result.readOps,
142
+ complete: result.complete,
143
+ ...(result.continuation !== undefined && { continuation: result.continuation }),
144
+ ...(result.dryRunCostEstimate !== undefined && {
145
+ dryRunCostEstimate: result.dryRunCostEstimate,
146
+ }),
147
+ };
148
+ return {
149
+ ok: true,
150
+ summary: `Discovered ${Object.keys(schemas).length} template path(s); ${result.readOps} read(s), ${result.listOps} list(s)${result.complete ? '' : ' — paused, resume with continuation'}`,
151
+ data,
152
+ };
153
+ } catch (e) {
154
+ const message = e instanceof Error ? e.message : String(e);
155
+ return {
156
+ ok: false,
157
+ summary: `firestore_discover_paths failed: ${message}`,
158
+ data: { success: false, error: { code: 'DISCOVER_FAILED', message, recoverable: false } },
159
+ };
160
+ }
161
+ },
162
+ },
163
+
164
+ {
165
+ name: 'firestore_find_collection_group',
166
+ description:
167
+ 'Find every collection-group host of a given collection ID. Answers "where does collection ID X appear in the database?" by issuing one collectionGroup(id).select().limit(N).get() — cost is exactly N reads (default 100). ' +
168
+ 'Returns hosts in template-path form (e.g. users/{userId}/posts) with the per-host sampleDocCount from the N-doc draw. Coverage of all hosts is statistical: with K hosts, N=100 covers ~22 distinct hosts with high confidence (coupon-collector). The result\'s limitWasReached flag signals whether to raise the cap. ' +
169
+ 'Use this as a cold-start alternative to firestore_discover_paths when you already know the collection ID and just need the parent paths.',
170
+ parameters: {
171
+ type: 'object',
172
+ properties: {
173
+ collectionId: { type: 'string', minLength: 1, description: 'The leaf collection ID to find (e.g. "posts", "moves").' },
174
+ limit: { type: 'integer', minimum: 1, description: 'Max docs to fetch from the collection group. Default 100. Each doc costs one read; raise this if limitWasReached is true and you suspect more hosts.' },
175
+ },
176
+ required: ['collectionId'],
177
+ },
178
+ async execute(args) {
179
+ const params = args as FindCollectionGroupArgs;
180
+ try {
181
+ const db = resolveDb();
182
+ const result = await findCollectionGroup(db, params.collectionId, {
183
+ limit: params.limit,
184
+ });
185
+ return {
186
+ ok: true,
187
+ summary: `Found ${result.hosts.length} host(s) for '${params.collectionId}'${result.limitWasReached ? ' (limit reached — raise to find more)' : ''}`,
188
+ data: result,
189
+ };
190
+ } catch (e) {
191
+ const message = e instanceof Error ? e.message : String(e);
192
+ return {
193
+ ok: false,
194
+ summary: `firestore_find_collection_group failed: ${message}`,
195
+ data: { success: false, error: { code: 'FIND_COLLECTION_GROUP_FAILED', message, recoverable: false } },
196
+ };
197
+ }
198
+ },
199
+ },
200
+ ];
201
+ }