@pyric/cli 0.1.0-alpha.19 → 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.
- package/README.md +64 -89
- package/dist/assurance/.generated/conformance-verdicts.d.ts +68 -1
- package/dist/assurance/.generated/conformance-verdicts.d.ts.map +1 -1
- package/dist/assurance/.generated/conformance-verdicts.js +68 -1
- package/dist/assurance/.generated/conformance-verdicts.js.map +1 -1
- package/dist/bridge/client/dispatch.d.ts +25 -19
- package/dist/bridge/client/dispatch.d.ts.map +1 -1
- package/dist/bridge/client/dispatch.js +41 -48
- package/dist/bridge/client/dispatch.js.map +1 -1
- package/dist/bridge/client/tool-family-factories.d.ts +26 -0
- package/dist/bridge/client/tool-family-factories.d.ts.map +1 -0
- package/dist/bridge/client/tool-family-factories.js +10 -0
- package/dist/bridge/client/tool-family-factories.js.map +1 -0
- package/dist/bridge/server/mcp-contract.d.ts +7 -6
- package/dist/bridge/server/mcp-contract.d.ts.map +1 -1
- package/dist/bridge/server/mcp-contract.js +9 -48
- package/dist/bridge/server/mcp-contract.js.map +1 -1
- package/dist/bridge/server/peer.d.ts +11 -1
- package/dist/bridge/server/peer.d.ts.map +1 -1
- package/dist/bridge/server/peer.js +22 -1
- package/dist/bridge/server/peer.js.map +1 -1
- package/dist/bridge/server/tool-family-factories.d.ts +30 -0
- package/dist/bridge/server/tool-family-factories.d.ts.map +1 -0
- package/dist/bridge/server/tool-family-factories.js +22 -0
- package/dist/bridge/server/tool-family-factories.js.map +1 -0
- package/dist/bridge/server/tool-metadata.d.ts +17 -18
- package/dist/bridge/server/tool-metadata.d.ts.map +1 -1
- package/dist/bridge/server/tool-metadata.js +24 -59
- package/dist/bridge/server/tool-metadata.js.map +1 -1
- package/dist/bridge/tool-families.d.ts +48 -0
- package/dist/bridge/tool-families.d.ts.map +1 -0
- package/dist/bridge/tool-families.generated.d.ts +32 -0
- package/dist/bridge/tool-families.generated.d.ts.map +1 -0
- package/dist/bridge/tool-families.generated.js +15 -0
- package/dist/bridge/tool-families.generated.js.map +1 -0
- package/dist/bridge/tool-families.js +51 -0
- package/dist/bridge/tool-families.js.map +1 -0
- package/dist/bridge/tool-family-records/conformance.d.ts +7 -0
- package/dist/bridge/tool-family-records/conformance.d.ts.map +1 -0
- package/dist/bridge/tool-family-records/conformance.js +6 -0
- package/dist/bridge/tool-family-records/conformance.js.map +1 -0
- package/dist/bridge/tool-family-records/firestore-data.d.ts +7 -0
- package/dist/bridge/tool-family-records/firestore-data.d.ts.map +1 -0
- package/dist/bridge/tool-family-records/firestore-data.js +15 -0
- package/dist/bridge/tool-family-records/firestore-data.js.map +1 -0
- package/dist/bridge/tool-family-records/firestore-inspect.d.ts +7 -0
- package/dist/bridge/tool-family-records/firestore-inspect.d.ts.map +1 -0
- package/dist/bridge/tool-family-records/firestore-inspect.js +6 -0
- package/dist/bridge/tool-family-records/firestore-inspect.js.map +1 -0
- package/dist/bridge/tool-family-records/firestore-rules.d.ts +7 -0
- package/dist/bridge/tool-family-records/firestore-rules.d.ts.map +1 -0
- package/dist/bridge/tool-family-records/firestore-rules.js +15 -0
- package/dist/bridge/tool-family-records/firestore-rules.js.map +1 -0
- package/dist/bridge/tool-family-records/firestore-simulator.d.ts +7 -0
- package/dist/bridge/tool-family-records/firestore-simulator.d.ts.map +1 -0
- package/dist/bridge/tool-family-records/firestore-simulator.js +16 -0
- package/dist/bridge/tool-family-records/firestore-simulator.js.map +1 -0
- package/dist/bridge/tool-family-records/rtdb-inspection.d.ts +7 -0
- package/dist/bridge/tool-family-records/rtdb-inspection.d.ts.map +1 -0
- package/dist/bridge/tool-family-records/rtdb-inspection.js +6 -0
- package/dist/bridge/tool-family-records/rtdb-inspection.js.map +1 -0
- package/dist/bundler/externals.d.ts +37 -0
- package/dist/bundler/externals.d.ts.map +1 -0
- package/dist/bundler/externals.js +52 -0
- package/dist/bundler/externals.js.map +1 -0
- package/dist/bundler/index.d.ts +17 -0
- package/dist/bundler/index.d.ts.map +1 -0
- package/dist/bundler/index.js +17 -0
- package/dist/bundler/index.js.map +1 -0
- package/dist/cli/index.d.ts +2 -2
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +48 -42
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/inlined-sdk-scanner.d.ts +6 -0
- package/dist/cli/inlined-sdk-scanner.d.ts.map +1 -0
- package/dist/cli/inlined-sdk-scanner.js +82 -0
- package/dist/cli/inlined-sdk-scanner.js.map +1 -0
- package/dist/cli/mcp-proxy.js +3 -3
- package/dist/cli/mcp-proxy.js.map +1 -1
- package/dist/cli/parse-args.d.ts +1 -1
- package/dist/cli/parse-args.d.ts.map +1 -1
- package/dist/cli/parse-args.js +51 -15
- package/dist/cli/parse-args.js.map +1 -1
- package/dist/cli/pyric-config.d.ts +24 -0
- package/dist/cli/pyric-config.d.ts.map +1 -0
- package/dist/cli/pyric-config.js +28 -0
- package/dist/cli/pyric-config.js.map +1 -0
- package/dist/cli/sandbox-interlock.d.ts +97 -0
- package/dist/cli/sandbox-interlock.d.ts.map +1 -0
- package/dist/cli/sandbox-interlock.js +112 -0
- package/dist/cli/sandbox-interlock.js.map +1 -0
- package/dist/cli/{dev-runner.d.ts → sandbox-runner.d.ts} +53 -48
- package/dist/cli/sandbox-runner.d.ts.map +1 -0
- package/dist/cli/{dev-runner.js → sandbox-runner.js} +105 -67
- package/dist/cli/sandbox-runner.js.map +1 -0
- package/dist/cli/serve.d.ts +12 -14
- package/dist/cli/serve.d.ts.map +1 -1
- package/dist/cli/serve.js +155 -116
- package/dist/cli/serve.js.map +1 -1
- package/dist/cli/snapshot.js +5 -5
- package/dist/cli/snapshot.js.map +1 -1
- package/dist/cli/unsupported-runtime.d.ts +27 -0
- package/dist/cli/unsupported-runtime.d.ts.map +1 -0
- package/dist/cli/unsupported-runtime.js +63 -0
- package/dist/cli/unsupported-runtime.js.map +1 -0
- package/dist/cli/verify.js +1 -1
- package/dist/cli/verify.js.map +1 -1
- package/dist/conformance/.generated/can-i-use-browser.d.ts +1 -1
- package/dist/conformance/.generated/can-i-use-browser.d.ts.map +1 -1
- package/dist/conformance/.generated/can-i-use-browser.js +1 -1
- package/dist/conformance/.generated/can-i-use-browser.js.map +1 -1
- package/dist/conformance/.generated/can-i-use.d.ts +1 -1
- package/dist/conformance/.generated/can-i-use.d.ts.map +1 -1
- package/dist/conformance/.generated/can-i-use.js +1 -1
- package/dist/conformance/.generated/can-i-use.js.map +1 -1
- package/dist/conformance/.generated/conformance-docs.d.ts.map +1 -1
- package/dist/conformance/.generated/conformance-docs.js +1 -1
- package/dist/conformance/.generated/conformance-docs.js.map +1 -1
- package/dist/functions-rtdb/child.d.ts +37 -0
- package/dist/functions-rtdb/child.d.ts.map +1 -1
- package/dist/functions-rtdb/child.js +90 -21
- package/dist/functions-rtdb/child.js.map +1 -1
- package/dist/functions-rtdb/development-runtime.d.ts +3 -0
- package/dist/functions-rtdb/development-runtime.d.ts.map +1 -1
- package/dist/functions-rtdb/development-runtime.js +2 -1
- package/dist/functions-rtdb/development-runtime.js.map +1 -1
- package/dist/functions-rtdb/project.js +8 -8
- package/dist/functions-rtdb/project.js.map +1 -1
- package/dist/google-endpoints.d.ts +67 -0
- package/dist/google-endpoints.d.ts.map +1 -0
- package/dist/google-endpoints.js +119 -0
- package/dist/google-endpoints.js.map +1 -0
- package/dist/next/guard.js +1 -1
- package/dist/next/guard.js.map +1 -1
- package/dist/next/runtime-env.d.ts.map +1 -1
- package/dist/next/runtime-env.js +5 -0
- package/dist/next/runtime-env.js.map +1 -1
- package/dist/next/with-pyric.d.ts +1 -1
- package/dist/next/with-pyric.js +1 -1
- package/dist/register/beacon.d.ts +69 -0
- package/dist/register/beacon.d.ts.map +1 -0
- package/dist/register/beacon.js +199 -0
- package/dist/register/beacon.js.map +1 -0
- package/dist/register/index.d.ts.map +1 -1
- package/dist/register/index.js +65 -6
- package/dist/register/index.js.map +1 -1
- package/dist/register/net-guard.d.ts +110 -0
- package/dist/register/net-guard.d.ts.map +1 -0
- package/dist/register/net-guard.js +462 -0
- package/dist/register/net-guard.js.map +1 -0
- package/dist/remote/index.d.ts +1 -1
- package/dist/remote/index.js +9 -9
- package/dist/remote/index.js.map +1 -1
- package/dist/serve/ai-diagnostics-relay.d.ts +63 -0
- package/dist/serve/ai-diagnostics-relay.d.ts.map +1 -0
- package/dist/serve/ai-diagnostics-relay.js +125 -0
- package/dist/serve/ai-diagnostics-relay.js.map +1 -0
- package/dist/serve/ai-proxy.d.ts +92 -0
- package/dist/serve/ai-proxy.d.ts.map +1 -0
- package/dist/serve/ai-proxy.js +269 -0
- package/dist/serve/ai-proxy.js.map +1 -0
- package/dist/serve/ai-status.d.ts +44 -0
- package/dist/serve/ai-status.d.ts.map +1 -0
- package/dist/serve/ai-status.js +77 -0
- package/dist/serve/ai-status.js.map +1 -0
- package/dist/serve/ai-terminal-blocks.d.ts +97 -0
- package/dist/serve/ai-terminal-blocks.d.ts.map +1 -0
- package/dist/serve/ai-terminal-blocks.js +128 -0
- package/dist/serve/ai-terminal-blocks.js.map +1 -0
- package/dist/serve/ai-terminal-text.d.ts +36 -0
- package/dist/serve/ai-terminal-text.d.ts.map +1 -0
- package/dist/serve/ai-terminal-text.js +48 -0
- package/dist/serve/ai-terminal-text.js.map +1 -0
- package/dist/serve/beacon-route.d.ts +53 -0
- package/dist/serve/beacon-route.d.ts.map +1 -0
- package/dist/serve/beacon-route.js +101 -0
- package/dist/serve/beacon-route.js.map +1 -0
- package/dist/serve/bundler.js +10 -10
- package/dist/serve/bundler.js.map +1 -1
- package/dist/serve/entries/active-auth-registry.d.ts +16 -0
- package/dist/serve/entries/active-auth-registry.d.ts.map +1 -0
- package/dist/serve/entries/active-auth-registry.js +48 -0
- package/dist/serve/entries/active-auth-registry.js.map +1 -0
- package/dist/serve/entries/active-auth.d.ts +18 -0
- package/dist/serve/entries/active-auth.d.ts.map +1 -0
- package/dist/serve/entries/active-auth.js +84 -0
- package/dist/serve/entries/active-auth.js.map +1 -0
- package/dist/serve/entries/ai.d.ts.map +1 -1
- package/dist/serve/entries/ai.js +27 -1
- package/dist/serve/entries/ai.js.map +1 -1
- package/dist/serve/entries/auth-helper-core.d.ts +1 -0
- package/dist/serve/entries/auth-helper-core.d.ts.map +1 -1
- package/dist/serve/entries/auth-helper-core.js +4 -0
- package/dist/serve/entries/auth-helper-core.js.map +1 -1
- package/dist/serve/entries/auth-helper-dom.d.ts.map +1 -1
- package/dist/serve/entries/auth-helper-dom.js +29 -3
- package/dist/serve/entries/auth-helper-dom.js.map +1 -1
- package/dist/serve/entries/auth-helper-runtime.js +1 -1
- package/dist/serve/entries/auth-helper-runtime.js.map +1 -1
- package/dist/serve/entries/auth.d.ts.map +1 -1
- package/dist/serve/entries/auth.js +8 -0
- package/dist/serve/entries/auth.js.map +1 -1
- package/dist/serve/entries/bridge-url.d.ts +1 -1
- package/dist/serve/entries/bridge-url.js +1 -1
- package/dist/serve/entries/firestore.js +3 -3
- package/dist/serve/entries/firestore.js.map +1 -1
- package/dist/serve/entries/init.d.ts.map +1 -1
- package/dist/serve/entries/init.js +70 -4
- package/dist/serve/entries/init.js.map +1 -1
- package/dist/serve/entries/keepalive.js +1 -1
- package/dist/serve/entries/keepalive.js.map +1 -1
- package/dist/serve/entries/runtime.d.ts.map +1 -1
- package/dist/serve/entries/runtime.js +30 -14
- package/dist/serve/entries/runtime.js.map +1 -1
- package/dist/serve/entries/storage.js +1 -1
- package/dist/serve/entries/storage.js.map +1 -1
- package/dist/serve/entries/worker-runtime.js +1 -1
- package/dist/serve/entries/worker-runtime.js.map +1 -1
- package/dist/serve/init-payload.d.ts +4 -0
- package/dist/serve/init-payload.d.ts.map +1 -1
- package/dist/serve/namespace.d.ts +20 -5
- package/dist/serve/namespace.d.ts.map +1 -1
- package/dist/serve/namespace.js +93 -129
- package/dist/serve/namespace.js.map +1 -1
- package/dist/serve/rules.d.ts.map +1 -1
- package/dist/serve/rules.js +44 -21
- package/dist/serve/rules.js.map +1 -1
- package/dist/serve/runtime/chip-config.d.ts.map +1 -1
- package/dist/serve/runtime/chip-config.js +16 -2
- package/dist/serve/runtime/chip-config.js.map +1 -1
- package/dist/serve/runtime/chip-dialog.d.ts +18 -0
- package/dist/serve/runtime/chip-dialog.d.ts.map +1 -0
- package/dist/serve/runtime/chip-dialog.js +315 -0
- package/dist/serve/runtime/chip-dialog.js.map +1 -0
- package/dist/serve/runtime/chip-install.d.ts +2 -0
- package/dist/serve/runtime/chip-install.d.ts.map +1 -1
- package/dist/serve/runtime/chip-install.js +9 -4
- package/dist/serve/runtime/chip-install.js.map +1 -1
- package/dist/serve/runtime/chip-user-search.d.ts +16 -0
- package/dist/serve/runtime/chip-user-search.d.ts.map +1 -0
- package/dist/serve/runtime/chip-user-search.js +354 -0
- package/dist/serve/runtime/chip-user-search.js.map +1 -0
- package/dist/serve/runtime/chip.d.ts +10 -0
- package/dist/serve/runtime/chip.d.ts.map +1 -1
- package/dist/serve/runtime/chip.js +149 -15
- package/dist/serve/runtime/chip.js.map +1 -1
- package/dist/serve/runtime/identity.d.ts +17 -0
- package/dist/serve/runtime/identity.d.ts.map +1 -0
- package/dist/serve/runtime/identity.js +10 -0
- package/dist/serve/runtime/identity.js.map +1 -0
- package/dist/serve/runtime/manifest.d.ts +1 -1
- package/dist/serve/runtime/manifest.d.ts.map +1 -1
- package/dist/serve/runtime/manifest.js +1 -1
- package/dist/serve/runtime/manifest.js.map +1 -1
- package/dist/serve/runtime/status.d.ts.map +1 -1
- package/dist/serve/runtime/status.js +23 -0
- package/dist/serve/runtime/status.js.map +1 -1
- package/dist/serve/sandbox-session.d.ts +8 -0
- package/dist/serve/sandbox-session.d.ts.map +1 -1
- package/dist/serve/sandbox-session.js +12 -4
- package/dist/serve/sandbox-session.js.map +1 -1
- package/dist/serve/server.d.ts +7 -0
- package/dist/serve/server.d.ts.map +1 -1
- package/dist/serve/server.js +15 -1
- package/dist/serve/server.js.map +1 -1
- package/dist/serve/site-ui/404.html +1 -1
- package/dist/serve/site-ui/_astro/{RulesCodeEditor.Bl-XMkhD.js → RulesCodeEditor.DsTAsKIa.js} +1 -1
- package/dist/serve/site-ui/_astro/_slug_.Da5VLhW1.css +1 -0
- package/dist/serve/site-ui/_astro/_studio_.BnfYCKoI.css +1 -0
- package/dist/serve/site-ui/_astro/app.BOr2kwae.js +115 -0
- package/dist/serve/site-ui/_astro/app.DhAcImFD.js +1 -0
- package/dist/serve/site-ui/_astro/app.pMSnN6Qw.css +1 -0
- package/dist/serve/site-ui/_astro/{chess-showcase.Dh5D8kqe.js → chess-showcase.Bn_pP6UJ.js} +2 -2
- package/dist/serve/site-ui/_astro/{example-runtime.CuyAjjiG.js → example-runtime.CXopTGv7.js} +1 -1
- package/dist/serve/site-ui/_astro/index.Tb-31efc.css +1 -0
- package/dist/serve/site-ui/_astro/index.UkCBr-_0.css +1 -0
- package/dist/serve/site-ui/_astro/sandbox-controls.rOuZTOUG.js +510 -0
- package/dist/serve/site-ui/_astro/{seed.CSUkloMi.js → seed.03a08f9t.js} +2 -2
- package/dist/serve/site-ui/_astro/writes.BsPBG3dp.js +653 -0
- package/dist/serve/site-ui/auth/index.html +5 -3
- package/dist/serve/site-ui/david-east.jpg +0 -0
- package/dist/serve/site-ui/docs/_rhythm/index.html +6 -3
- package/dist/serve/site-ui/docs/_rhythm.md +1 -1
- package/dist/serve/site-ui/docs/agent/set-up-your-agent/index.html +18 -15
- package/dist/serve/site-ui/docs/agent/set-up-your-agent.md +16 -16
- package/dist/serve/site-ui/docs/agent/watch-and-review/index.html +6 -5
- package/dist/serve/site-ui/docs/agent/watch-and-review.md +1 -5
- package/dist/serve/site-ui/docs/agent/work-with-an-agent/index.html +8 -5
- package/dist/serve/site-ui/docs/agent/work-with-an-agent.md +4 -4
- package/dist/serve/site-ui/docs/ai-compat/index.html +5 -2
- package/dist/serve/site-ui/docs/api-reference/index.html +13 -2
- package/dist/serve/site-ui/docs/api-reference.md +8 -0
- package/dist/serve/site-ui/docs/app-compat/index.html +5 -2
- package/dist/serve/site-ui/docs/auth-compat/index.html +7 -4
- package/dist/serve/site-ui/docs/auth-compat.md +2 -2
- package/dist/serve/site-ui/docs/build/ai-logic/index.html +10 -2
- package/dist/serve/site-ui/docs/build/ai-logic.md +6 -0
- package/dist/serve/site-ui/docs/build/authentication/index.html +7 -4
- package/dist/serve/site-ui/docs/build/authentication.md +2 -2
- package/dist/serve/site-ui/docs/build/cloud-firestore/index.html +5 -2
- package/dist/serve/site-ui/docs/build/cloud-messaging/index.html +5 -2
- package/dist/serve/site-ui/docs/build/cloud-storage/index.html +6 -3
- package/dist/serve/site-ui/docs/build/cloud-storage.md +1 -1
- package/dist/serve/site-ui/docs/build/realtime-database/index.html +9 -6
- package/dist/serve/site-ui/docs/build/realtime-database.md +4 -4
- package/dist/serve/site-ui/docs/conformance-scores/index.html +10 -3
- package/dist/serve/site-ui/docs/conformance-scores.md +5 -1
- package/dist/serve/site-ui/docs/create-pyric-reference-api/index.html +6 -3
- package/dist/serve/site-ui/docs/create-pyric-reference-api.md +1 -1
- package/dist/serve/site-ui/docs/database-compat/index.html +48 -45
- package/dist/serve/site-ui/docs/database-compat.md +43 -43
- package/dist/serve/site-ui/docs/examples/chess/index.html +5 -2
- package/dist/serve/site-ui/docs/examples/index.html +5 -2
- package/dist/serve/site-ui/docs/firestore-compat/index.html +8 -5
- package/dist/serve/site-ui/docs/firestore-compat.md +3 -3
- package/dist/serve/site-ui/docs/firestore-flutter-compat/index.html +319 -0
- package/dist/serve/site-ui/docs/firestore-flutter-compat.md +344 -0
- package/dist/serve/site-ui/docs/functions-rtdb-compat/index.html +5 -2
- package/dist/serve/site-ui/docs/get-started/backend-bundlers/index.html +105 -0
- package/dist/serve/site-ui/docs/get-started/backend-bundlers.md +165 -0
- package/dist/serve/site-ui/docs/get-started/how-the-swap-works/index.html +9 -6
- package/dist/serve/site-ui/docs/get-started/how-the-swap-works.md +5 -5
- package/dist/serve/site-ui/docs/get-started/nextjs/index.html +9 -6
- package/dist/serve/site-ui/docs/get-started/nextjs.md +4 -4
- package/dist/serve/site-ui/docs/get-started/start-building/index.html +14 -9
- package/dist/serve/site-ui/docs/get-started/start-building.md +16 -10
- package/dist/serve/site-ui/docs/get-started/vite/index.html +6 -3
- package/dist/serve/site-ui/docs/get-started/vite.md +1 -1
- package/dist/serve/site-ui/docs/index.json +3128 -1613
- package/dist/serve/site-ui/docs/messaging-compat/index.html +5 -2
- package/dist/serve/site-ui/docs/observe/resolve-runtime-status/index.html +5 -2
- package/dist/serve/site-ui/docs/observe/see-whats-happening/index.html +6 -4
- package/dist/serve/site-ui/docs/observe/see-whats-happening.md +2 -4
- package/dist/serve/site-ui/docs/observe/shape-your-data/index.html +9 -6
- package/dist/serve/site-ui/docs/observe/shape-your-data.md +4 -4
- package/dist/serve/site-ui/docs/overview/index.html +16 -10
- package/dist/serve/site-ui/docs/overview.md +19 -11
- package/dist/serve/site-ui/docs/pyric-admin-app-reference-api/index.html +5 -2
- package/dist/serve/site-ui/docs/pyric-admin-auth-reference-api/index.html +5 -2
- package/dist/serve/site-ui/docs/pyric-admin-database-reference-api/index.html +5 -2
- package/dist/serve/site-ui/docs/pyric-admin-firestore-reference-api/index.html +8 -5
- package/dist/serve/site-ui/docs/pyric-admin-firestore-reference-api.md +3 -3
- package/dist/serve/site-ui/docs/pyric-admin-messaging-reference-api/index.html +5 -2
- package/dist/serve/site-ui/docs/pyric-admin-reference-api/index.html +501 -0
- package/dist/serve/site-ui/docs/pyric-admin-reference-api.md +587 -0
- package/dist/serve/site-ui/docs/pyric-admin-storage-reference-api/index.html +5 -2
- package/dist/serve/site-ui/docs/pyric-ai-reference-api/index.html +5 -2
- package/dist/serve/site-ui/docs/pyric-ai-scripting-reference-api/index.html +5 -2
- package/dist/serve/site-ui/docs/pyric-analytics-reference-api/index.html +124 -0
- package/dist/serve/site-ui/docs/pyric-analytics-reference-api.md +287 -0
- package/dist/serve/site-ui/docs/pyric-app-check-reference-api/index.html +87 -0
- package/dist/serve/site-ui/docs/pyric-app-check-reference-api.md +199 -0
- package/dist/serve/site-ui/docs/pyric-app-reference-api/index.html +5 -2
- package/dist/serve/site-ui/docs/pyric-auth-reference-api/index.html +10 -3
- package/dist/serve/site-ui/docs/pyric-auth-reference-api.md +2 -1
- package/dist/serve/site-ui/docs/pyric-cli-assurance-browser-reference-api/index.html +5 -2
- package/dist/serve/site-ui/docs/pyric-cli-assurance-reference-api/index.html +5 -2
- package/dist/serve/site-ui/docs/pyric-cli-bridge-client-reference-api/index.html +6 -3
- package/dist/serve/site-ui/docs/pyric-cli-bridge-client-reference-api.md +1 -1
- package/dist/serve/site-ui/docs/pyric-cli-bridge-reference-api/index.html +5 -2
- package/dist/serve/site-ui/docs/pyric-cli-bundler-reference-api/index.html +56 -0
- package/dist/serve/site-ui/docs/pyric-cli-bundler-reference-api.md +72 -0
- package/dist/serve/site-ui/docs/pyric-cli-conformance-browser-reference-api/index.html +6 -2
- package/dist/serve/site-ui/docs/pyric-cli-conformance-browser-reference-api.md +1 -0
- package/dist/serve/site-ui/docs/pyric-cli-conformance-docs-reference-api/index.html +5 -2
- package/dist/serve/site-ui/docs/pyric-cli-conformance-reference-api/index.html +6 -2
- package/dist/serve/site-ui/docs/pyric-cli-conformance-reference-api.md +1 -0
- package/dist/serve/site-ui/docs/pyric-cli-credentials-node-reference-api/index.html +5 -2
- package/dist/serve/site-ui/docs/pyric-cli-discover-reference-api/index.html +5 -2
- package/dist/serve/site-ui/docs/pyric-cli-next-reference-api/index.html +6 -3
- package/dist/serve/site-ui/docs/pyric-cli-next-reference-api.md +1 -1
- package/dist/serve/site-ui/docs/pyric-cli-register-app-bridge-reference-api/index.html +5 -2
- package/dist/serve/site-ui/docs/pyric-cli-register-reference-api/index.html +5 -2
- package/dist/serve/site-ui/docs/pyric-cli-remote-reference-api/index.html +39 -11
- package/dist/serve/site-ui/docs/pyric-cli-remote-reference-api.md +17 -9
- package/dist/serve/site-ui/docs/pyric-cli-serve-worker-reference-api/index.html +137 -94
- package/dist/serve/site-ui/docs/pyric-cli-serve-worker-reference-api.md +61 -1
- package/dist/serve/site-ui/docs/pyric-cli-verify-reference-api/index.html +6 -3
- package/dist/serve/site-ui/docs/pyric-cli-verify-reference-api.md +1 -1
- package/dist/serve/site-ui/docs/pyric-cli-vite-reference-api/index.html +6 -3
- package/dist/serve/site-ui/docs/pyric-cli-vite-reference-api.md +2 -2
- package/dist/serve/site-ui/docs/pyric-database-reference-api/index.html +167 -96
- package/dist/serve/site-ui/docs/pyric-database-reference-api.md +108 -2
- package/dist/serve/site-ui/docs/pyric-firestore-lite-reference-api/index.html +559 -0
- package/dist/serve/site-ui/docs/pyric-firestore-lite-reference-api.md +1240 -0
- package/dist/serve/site-ui/docs/pyric-firestore-reference-api/index.html +44 -12
- package/dist/serve/site-ui/docs/pyric-firestore-reference-api.md +18 -8
- package/dist/serve/site-ui/docs/pyric-functions-reference-api/index.html +67 -0
- package/dist/serve/site-ui/docs/pyric-functions-reference-api.md +149 -0
- package/dist/serve/site-ui/docs/pyric-messaging-reference-api/index.html +10 -4
- package/dist/serve/site-ui/docs/pyric-messaging-reference-api.md +5 -2
- package/dist/serve/site-ui/docs/pyric-messaging-sw-reference-api/index.html +10 -4
- package/dist/serve/site-ui/docs/pyric-messaging-sw-reference-api.md +5 -2
- package/dist/serve/site-ui/docs/pyric-performance-reference-api/index.html +35 -0
- package/dist/serve/site-ui/docs/pyric-performance-reference-api.md +69 -0
- package/dist/serve/site-ui/docs/pyric-remote-config-reference-api/index.html +132 -0
- package/dist/serve/site-ui/docs/pyric-remote-config-reference-api.md +307 -0
- package/dist/serve/site-ui/docs/pyric-rules-reference-api/index.html +5 -2
- package/dist/serve/site-ui/docs/pyric-sandbox-database-reference-api/index.html +56 -18
- package/dist/serve/site-ui/docs/pyric-sandbox-database-reference-api.md +48 -9
- package/dist/serve/site-ui/docs/pyric-sandbox-firestore-reference-api/index.html +6 -3
- package/dist/serve/site-ui/docs/pyric-sandbox-firestore-reference-api.md +2 -2
- package/dist/serve/site-ui/docs/pyric-sandbox-reference-api/index.html +243 -50
- package/dist/serve/site-ui/docs/pyric-sandbox-reference-api.md +148 -31
- package/dist/serve/site-ui/docs/pyric-storage-reference-api/index.html +26 -5
- package/dist/serve/site-ui/docs/pyric-storage-reference-api.md +24 -2
- package/dist/serve/site-ui/docs/pyric-ui-agents-reference-api/index.html +30 -97
- package/dist/serve/site-ui/docs/pyric-ui-agents-reference-api.md +20 -51
- package/dist/serve/site-ui/docs/pyric-ui-auth-hooks-reference-api/index.html +5 -2
- package/dist/serve/site-ui/docs/pyric-ui-auth-reference-api/index.html +6 -3
- package/dist/serve/site-ui/docs/pyric-ui-auth-reference-api.md +1 -1
- package/dist/serve/site-ui/docs/pyric-ui-events-hooks-reference-api/index.html +5 -2
- package/dist/serve/site-ui/docs/pyric-ui-events-reference-api/index.html +5 -2
- package/dist/serve/site-ui/docs/pyric-ui-firestore-hooks-reference-api/index.html +5 -2
- package/dist/serve/site-ui/docs/pyric-ui-firestore-reference-api/index.html +6 -3
- package/dist/serve/site-ui/docs/pyric-ui-firestore-reference-api.md +1 -1
- package/dist/serve/site-ui/docs/pyric-ui-primitives-reference-api/index.html +128 -27
- package/dist/serve/site-ui/docs/pyric-ui-primitives-reference-api.md +58 -5
- package/dist/serve/site-ui/docs/pyric-ui-rtdb-reference-api/index.html +5 -2
- package/dist/serve/site-ui/docs/pyric-ui-rules-hooks-reference-api/index.html +5 -2
- package/dist/serve/site-ui/docs/pyric-ui-rules-reference-api/index.html +5 -2
- package/dist/serve/site-ui/docs/pyric-ui-storage-hooks-reference-api/index.html +5 -2
- package/dist/serve/site-ui/docs/pyric-ui-storage-reference-api/index.html +8 -6
- package/dist/serve/site-ui/docs/pyric-ui-storage-reference-api.md +3 -4
- package/dist/serve/site-ui/docs/pyric-ui-traffic-hooks-reference-api/index.html +5 -2
- package/dist/serve/site-ui/docs/pyric-ui-traffic-reference-api/index.html +5 -2
- package/dist/serve/site-ui/docs/reference/cli/index.html +156 -0
- package/dist/serve/site-ui/docs/reference/cli.md +120 -0
- package/dist/serve/site-ui/docs/rules-compat/index.html +14 -3
- package/dist/serve/site-ui/docs/rules-compat.md +9 -1
- package/dist/serve/site-ui/docs/secure/audit-your-rules/index.html +5 -2
- package/dist/serve/site-ui/docs/secure/firestore-rules-limits/index.html +5 -2
- package/dist/serve/site-ui/docs/secure/read-a-denial/index.html +6 -3
- package/dist/serve/site-ui/docs/secure/read-a-denial.md +1 -1
- package/dist/serve/site-ui/docs/secure/rtdb-rules-in-typescript/index.html +5 -2
- package/dist/serve/site-ui/docs/secure/rules-standard-library/index.html +5 -2
- package/dist/serve/site-ui/docs/secure/secure-it-with-rules/index.html +5 -2
- package/dist/serve/site-ui/docs/secure/simulate-and-lint/index.html +5 -2
- package/dist/serve/site-ui/docs/secure/write-a-rules-test-suite/index.html +5 -2
- package/dist/serve/site-ui/docs/ship/ship-to-production/index.html +7 -4
- package/dist/serve/site-ui/docs/ship/ship-to-production.md +2 -2
- package/dist/serve/site-ui/docs/ship/test-in-node/index.html +9 -6
- package/dist/serve/site-ui/docs/ship/test-in-node.md +4 -4
- package/dist/serve/site-ui/docs/storage-compat/index.html +17 -14
- package/dist/serve/site-ui/docs/storage-compat.md +12 -12
- package/dist/serve/site-ui/docs/trust/how-we-know-it-matches-firebase/index.html +5 -2
- package/dist/serve/site-ui/docs/trust/rules-standard-library/index.html +5 -2
- package/dist/serve/site-ui/docs/trust/versioning-and-compatibility/index.html +5 -2
- package/dist/serve/site-ui/docs/tutorial/index.html +10 -7
- package/dist/serve/site-ui/docs/tutorial.md +4 -4
- package/dist/serve/site-ui/examples/chess/index.html +4 -2
- package/dist/serve/site-ui/firestore/index.html +5 -3
- package/dist/serve/site-ui/hero-charcoal.jpg +0 -0
- package/dist/serve/site-ui/index.html +9 -7
- package/dist/serve/site-ui/llms.txt +13 -3
- package/dist/serve/site-ui/pyric-campfire-full.png +0 -0
- package/dist/serve/site-ui/pyric-campfire.png +0 -0
- package/dist/serve/site-ui/rtdb/index.html +5 -3
- package/dist/serve/site-ui/settings/index.html +5 -3
- package/dist/serve/site-ui/storage/index.html +5 -3
- package/dist/serve/site-ui/studio/index.html +13 -0
- package/dist/serve/site-ui/studio-routes.json +1 -1
- package/dist/serve/site-ui/traffic/index.html +5 -3
- package/dist/serve/studio/disk-workspace.d.ts.map +1 -1
- package/dist/serve/studio/disk-workspace.js +64 -2
- package/dist/serve/studio/disk-workspace.js.map +1 -1
- package/dist/serve/studio/routes.d.ts +9 -0
- package/dist/serve/studio/routes.d.ts.map +1 -1
- package/dist/serve/studio/routes.js +47 -7
- package/dist/serve/studio/routes.js.map +1 -1
- package/dist/serve/vite-ai-config.d.ts.map +1 -1
- package/dist/serve/vite-ai-config.js +38 -8
- package/dist/serve/vite-ai-config.js.map +1 -1
- package/dist/serve/vite-module-swap.d.ts.map +1 -1
- package/dist/serve/vite-module-swap.js +0 -28
- package/dist/serve/vite-module-swap.js.map +1 -1
- package/dist/serve/vite-plugin.d.ts +7 -7
- package/dist/serve/vite-plugin.js +1 -1
- package/dist/serve/vite-plugin.js.map +1 -1
- package/dist/serve/vite-sandbox-generation.d.ts +1 -1
- package/dist/serve/vite-sandbox-generation.d.ts.map +1 -1
- package/dist/serve/vite-sandbox-generation.js +13 -1
- package/dist/serve/vite-sandbox-generation.js.map +1 -1
- package/dist/serve/worker/client/auth.js +1 -2
- package/dist/serve/worker/client/auth.js.map +1 -1
- package/dist/serve/worker/client/core.d.ts +21 -1
- package/dist/serve/worker/client/core.d.ts.map +1 -1
- package/dist/serve/worker/client/core.js +79 -3
- package/dist/serve/worker/client/core.js.map +1 -1
- package/dist/serve/worker/client/disconnect.js +1 -1
- package/dist/serve/worker/client/disconnect.js.map +1 -1
- package/dist/serve/worker/client/presence.d.ts.map +1 -1
- package/dist/serve/worker/client/presence.js +18 -5
- package/dist/serve/worker/client/presence.js.map +1 -1
- package/dist/serve/worker/client.d.ts +1 -1
- package/dist/serve/worker/client.d.ts.map +1 -1
- package/dist/serve/worker/client.js +1 -1
- package/dist/serve/worker/client.js.map +1 -1
- package/dist/serve/worker/context-resolver.d.ts +30 -0
- package/dist/serve/worker/context-resolver.d.ts.map +1 -0
- package/dist/serve/worker/context-resolver.js +44 -0
- package/dist/serve/worker/context-resolver.js.map +1 -0
- package/dist/serve/worker/entry.js +23 -19
- package/dist/serve/worker/entry.js.map +1 -1
- package/dist/serve/worker/host/core.d.ts +1 -0
- package/dist/serve/worker/host/core.d.ts.map +1 -1
- package/dist/serve/worker/host/core.js +12 -6
- package/dist/serve/worker/host/core.js.map +1 -1
- package/dist/serve/worker/host/storage.js +2 -2
- package/dist/serve/worker/host-ai.d.ts.map +1 -1
- package/dist/serve/worker/host-ai.js +15 -0
- package/dist/serve/worker/host-ai.js.map +1 -1
- package/dist/serve/worker/host-auth.js +1 -2
- package/dist/serve/worker/host-auth.js.map +1 -1
- package/dist/serve/worker/index.d.ts +1 -1
- package/dist/serve/worker/index.d.ts.map +1 -1
- package/dist/serve/worker/index.js +2 -2
- package/dist/serve/worker/index.js.map +1 -1
- package/dist/serve/worker/port-lifecycle.d.ts +18 -0
- package/dist/serve/worker/port-lifecycle.d.ts.map +1 -0
- package/dist/serve/worker/port-lifecycle.js +28 -0
- package/dist/serve/worker/port-lifecycle.js.map +1 -0
- package/dist/serve/worker/protocol.d.ts +4 -0
- package/dist/serve/worker/protocol.d.ts.map +1 -1
- package/dist/serve/worker/protocol.js.map +1 -1
- package/dist/serve/worker/serve-init.d.ts.map +1 -1
- package/dist/serve/worker/serve-init.js +15 -3
- package/dist/serve/worker/serve-init.js.map +1 -1
- package/dist/verify/fixture.d.ts +1 -1
- package/package.json +17 -6
- package/src/assurance/.generated/conformance-verdicts.ts +68 -1
- package/src/bridge/client/dispatch.ts +52 -56
- package/src/bridge/client/tool-family-factories.ts +34 -0
- package/src/bridge/server/mcp-contract.ts +15 -59
- package/src/bridge/server/peer.ts +24 -1
- package/src/bridge/server/tool-family-factories.ts +40 -0
- package/src/bridge/server/tool-metadata.ts +29 -61
- package/src/bridge/tool-families.generated.ts +17 -0
- package/src/bridge/tool-families.ts +87 -0
- package/src/bridge/tool-family-records/conformance.ts +6 -0
- package/src/bridge/tool-family-records/firestore-data.ts +15 -0
- package/src/bridge/tool-family-records/firestore-inspect.ts +6 -0
- package/src/bridge/tool-family-records/firestore-rules.ts +15 -0
- package/src/bridge/tool-family-records/firestore-simulator.ts +16 -0
- package/src/bridge/tool-family-records/rtdb-inspection.ts +6 -0
- package/src/bundler/externals.ts +55 -0
- package/src/bundler/index.ts +21 -0
- package/src/cli/cli.test.ts +2 -46
- package/src/cli/index.ts +48 -42
- package/src/cli/inlined-sdk-scanner.ts +77 -0
- package/src/cli/mcp-proxy.ts +4 -4
- package/src/cli/parse-args.ts +61 -15
- package/src/cli/pyric-config.ts +45 -0
- package/src/cli/sandbox-interlock.ts +167 -0
- package/src/cli/{dev-runner.ts → sandbox-runner.ts} +142 -86
- package/src/cli/serve.ts +182 -115
- package/src/cli/snapshot.ts +5 -5
- package/src/cli/unsupported-runtime.ts +68 -0
- package/src/cli/verify.ts +1 -1
- package/src/conformance/.generated/can-i-use-browser.ts +2 -2
- package/src/conformance/.generated/can-i-use.ts +2 -2
- package/src/conformance/.generated/conformance-docs.ts +1 -1
- package/src/functions-rtdb/child.ts +115 -21
- package/src/functions-rtdb/development-runtime.ts +5 -1
- package/src/functions-rtdb/project.ts +8 -8
- package/src/google-endpoints.ts +155 -0
- package/src/next/guard.ts +1 -1
- package/src/next/runtime-env.ts +5 -0
- package/src/next/with-pyric.ts +1 -1
- package/src/register/beacon.ts +232 -0
- package/src/register/index.ts +68 -6
- package/src/register/net-guard.ts +567 -0
- package/src/remote/index.ts +9 -9
- package/src/serve/ai-diagnostics-relay.ts +243 -0
- package/src/serve/ai-proxy.ts +327 -0
- package/src/serve/ai-status.ts +109 -0
- package/src/serve/ai-terminal-blocks.ts +210 -0
- package/src/serve/ai-terminal-text.ts +52 -0
- package/src/serve/beacon-route.ts +141 -0
- package/src/serve/bundler.ts +10 -10
- package/src/serve/entries/active-auth-registry.ts +56 -0
- package/src/serve/entries/active-auth.ts +100 -0
- package/src/serve/entries/ai.ts +27 -1
- package/src/serve/entries/auth-helper-core.ts +5 -0
- package/src/serve/entries/auth-helper-dom.ts +27 -3
- package/src/serve/entries/auth-helper-runtime.ts +1 -1
- package/src/serve/entries/auth.ts +8 -0
- package/src/serve/entries/bridge-url.ts +1 -1
- package/src/serve/entries/firestore.ts +3 -3
- package/src/serve/entries/init.ts +78 -3
- package/src/serve/entries/keepalive.ts +1 -1
- package/src/serve/entries/runtime.ts +29 -14
- package/src/serve/entries/storage.ts +1 -1
- package/src/serve/entries/worker-runtime.ts +1 -1
- package/src/serve/init-payload.ts +4 -0
- package/src/serve/namespace.ts +117 -145
- package/src/serve/rules.ts +47 -22
- package/src/serve/runtime/chip-config.ts +16 -2
- package/src/serve/runtime/chip-dialog.ts +366 -0
- package/src/serve/runtime/chip-install.ts +10 -4
- package/src/serve/runtime/chip-user-search.ts +391 -0
- package/src/serve/runtime/chip.ts +177 -15
- package/src/serve/runtime/identity.ts +26 -0
- package/src/serve/runtime/manifest.ts +1 -1
- package/src/serve/runtime/status.ts +24 -0
- package/src/serve/sandbox-session.ts +19 -4
- package/src/serve/server.ts +18 -1
- package/src/serve/studio/disk-workspace.ts +76 -2
- package/src/serve/studio/routes.ts +63 -7
- package/src/serve/vite-ai-config.ts +37 -7
- package/src/serve/vite-module-swap.ts +0 -28
- package/src/serve/vite-plugin.ts +8 -8
- package/src/serve/vite-sandbox-generation.ts +16 -1
- package/src/serve/worker/client/auth.ts +1 -1
- package/src/serve/worker/client/core.ts +80 -3
- package/src/serve/worker/client/disconnect.ts +1 -1
- package/src/serve/worker/client/presence.ts +25 -5
- package/src/serve/worker/client.ts +1 -1
- package/src/serve/worker/context-resolver.ts +60 -0
- package/src/serve/worker/entry.ts +22 -18
- package/src/serve/worker/host/core.ts +9 -7
- package/src/serve/worker/host/storage.ts +2 -2
- package/src/serve/worker/host-ai.ts +15 -0
- package/src/serve/worker/host-auth.ts +1 -1
- package/src/serve/worker/index.ts +4 -1
- package/src/serve/worker/port-lifecycle.ts +45 -0
- package/src/serve/worker/protocol.ts +2 -1
- package/src/serve/worker/serve-init.ts +14 -3
- package/src/verify/fixture.ts +1 -1
- package/README.md.orig +0 -145
- package/dist/cli/dev-runner.d.ts.map +0 -1
- package/dist/cli/dev-runner.js.map +0 -1
- package/dist/serve/site-ui/_astro/_slug_.CXh9yTP1.css +0 -1
- package/dist/serve/site-ui/_astro/_slug_.CupVK4TM.css +0 -1
- package/dist/serve/site-ui/_astro/_studio_.CfxiQWkk.css +0 -1
- package/dist/serve/site-ui/_astro/app.BsFsMjn0.css +0 -1
- package/dist/serve/site-ui/_astro/app.Ce3MTLQm.js +0 -1
- package/dist/serve/site-ui/_astro/app.taqzyjD0.js +0 -11
- package/dist/serve/site-ui/_astro/sandbox-controls.BP4M5f1M.js +0 -1169
- package/dist/serve/site-ui/_astro/writes.C57paAj2.js +0 -99
- package/dist/serve/site-ui/docs/trust/storage-rules-stdlib-research/index.html +0 -582
- package/dist/serve/site-ui/docs/trust/storage-rules-stdlib-research.md +0 -448
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Terminal blocks for the AI diagnostics relayed to `POST /__pyric/denials`.
|
|
3
|
+
*
|
|
4
|
+
* Three things ride that route besides a rules denial, and each gets one
|
|
5
|
+
* record in {@link AI_TERMINAL_BLOCKS}: the loosely-typed wire payload, the
|
|
6
|
+
* formatter that renders it, and the two strings the route's throttle keys it
|
|
7
|
+
* by. Adding a fourth kind is one entry here plus its narrower in
|
|
8
|
+
* `ai-diagnostics-relay.ts`; the route itself dispatches through the table and
|
|
9
|
+
* never grows another branch.
|
|
10
|
+
*
|
|
11
|
+
* Every payload field is JSON from the served page, so nothing here trusts a
|
|
12
|
+
* type: each field is re-checked, and every remote-authored string goes
|
|
13
|
+
* through the URL redaction and terminal sanitizing in `ai-terminal-text.ts`.
|
|
14
|
+
*/
|
|
15
|
+
import type { AiDiagnosticKind } from './ai-diagnostics-relay.js';
|
|
16
|
+
import { redactUrl } from 'pyric/ai/internal';
|
|
17
|
+
import { sanitizeForTerminal } from './ai-terminal-text.js';
|
|
18
|
+
|
|
19
|
+
/** Loosely-typed mirror of the AI rejection payload POSTed by
|
|
20
|
+
* `ai-diagnostics-relay.ts` (the wire shape lives there). */
|
|
21
|
+
export interface AiRejectionPayload {
|
|
22
|
+
/** `'ai-rejection'`: what tells the route the body is NOT a rules denial. */
|
|
23
|
+
kind?: unknown;
|
|
24
|
+
/** Model resource the rejected op targeted. */
|
|
25
|
+
model?: unknown;
|
|
26
|
+
/** Resolved broker engine (`scripted` | `openai` | `gemini` | `custom`). */
|
|
27
|
+
engine?: unknown;
|
|
28
|
+
/** Wire error status (e.g. `INVALID_ARGUMENT`) and HTTP-ish code. */
|
|
29
|
+
status?: unknown;
|
|
30
|
+
code?: unknown;
|
|
31
|
+
/** The rejection reason: production's own message for shape errors. */
|
|
32
|
+
message?: unknown;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Loosely-typed mirror of the AI blocked-response payload. Unlike a
|
|
36
|
+
* rejection this carries no `message`: a filter block has no error envelope
|
|
37
|
+
* at all (HTTP 200, empty candidate), only the wire's reason enums, so the
|
|
38
|
+
* phrasing is this file's job. */
|
|
39
|
+
export interface AiBlockedPayload {
|
|
40
|
+
/** `'ai-blocked'`: what tells the route the body is a filter block. */
|
|
41
|
+
kind?: unknown;
|
|
42
|
+
/** Model resource the blocked op targeted. */
|
|
43
|
+
model?: unknown;
|
|
44
|
+
/** Resolved broker engine (`scripted` | `openai` | `gemini` | `custom`). */
|
|
45
|
+
engine?: unknown;
|
|
46
|
+
/** Candidate-level block: `SAFETY`, `RECITATION`, `BLOCKLIST`, and so on. */
|
|
47
|
+
finishReason?: unknown;
|
|
48
|
+
/** Production's own explanatory text for the block, when it sent one. */
|
|
49
|
+
finishMessage?: unknown;
|
|
50
|
+
/** Prompt-level block: `promptFeedback.blockReason`. */
|
|
51
|
+
blockReason?: unknown;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Loosely-typed mirror of the AI model-substitution payload. Not a refusal:
|
|
55
|
+
* the request SUCCEEDED, answered by a model the developer never named,
|
|
56
|
+
* which is why nothing else in the stack reports it. */
|
|
57
|
+
export interface AiModelSubstitutionPayload {
|
|
58
|
+
/** `'ai-model-substituted'`: what tells the route the body is a swap. */
|
|
59
|
+
kind?: unknown;
|
|
60
|
+
/** The model the request asked for. */
|
|
61
|
+
requestedModel?: unknown;
|
|
62
|
+
/** The model the engine actually calls. */
|
|
63
|
+
effectiveModel?: unknown;
|
|
64
|
+
/** Resolved broker engine (`openai` | `gemini` | ...). */
|
|
65
|
+
engine?: unknown;
|
|
66
|
+
/** Why it differs, e.g. `engine modelMap`, `experimental alias`. */
|
|
67
|
+
reason?: unknown;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** What the route reads off a body before it knows which kind it holds. */
|
|
71
|
+
export type AiDiagnosticPayload = AiRejectionPayload & AiBlockedPayload & AiModelSubstitutionPayload;
|
|
72
|
+
|
|
73
|
+
/** A non-empty string field, sanitized, or `null` when the wire omitted it. */
|
|
74
|
+
function readText(value: unknown): string | null {
|
|
75
|
+
if (typeof value !== 'string' || value === '') return null;
|
|
76
|
+
return sanitizeForTerminal(redactUrl(value));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** A raw (unsanitized) string field, for throttle keys that never print. */
|
|
80
|
+
function readRawText(value: unknown, fallback: string): string {
|
|
81
|
+
if (typeof value !== 'string' || value === '') return fallback;
|
|
82
|
+
return value;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* The `model: <id> (engine: <name>)` line shared by the rejection and block
|
|
87
|
+
* formatters, which report the same fact in the same place. Degrades to an
|
|
88
|
+
* engine-only line, and to nothing at all when the event carried neither.
|
|
89
|
+
*/
|
|
90
|
+
function formatModelLine(model: unknown, engine: unknown): string | null {
|
|
91
|
+
const flatModel = readText(model);
|
|
92
|
+
const flatEngine = readText(engine);
|
|
93
|
+
if (flatModel === null && flatEngine === null) return null;
|
|
94
|
+
if (flatModel === null) return ` engine: ${flatEngine}`;
|
|
95
|
+
if (flatEngine === null) return ` model: ${flatModel}`;
|
|
96
|
+
return ` model: ${flatModel} (engine: ${flatEngine})`;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Format a relayed AI broker rejection into the compact terminal block, the
|
|
101
|
+
* same ` [pyric] ...` idiom the denial relay and the ai-proxy warning print.
|
|
102
|
+
* Three lines at most: the reason production itself gives, then the model
|
|
103
|
+
* (with the resolved engine, since a rejection reads very differently for
|
|
104
|
+
* `scripted` than for a real upstream), then the wire status. Exported for
|
|
105
|
+
* unit tests.
|
|
106
|
+
*/
|
|
107
|
+
export function formatAiRejectionBlock(payload: AiRejectionPayload): string {
|
|
108
|
+
const message = readText(payload.message) ?? 'request rejected';
|
|
109
|
+
const lines = [` ⚠ [pyric] ai request rejected: ${message}`];
|
|
110
|
+
const modelLine = formatModelLine(payload.model, payload.engine);
|
|
111
|
+
if (modelLine !== null) lines.push(modelLine);
|
|
112
|
+
const status = readText(payload.status);
|
|
113
|
+
const code = typeof payload.code === 'number' ? payload.code : null;
|
|
114
|
+
if (status !== null || code !== null) {
|
|
115
|
+
const label = status ?? 'ERROR';
|
|
116
|
+
let suffix = '';
|
|
117
|
+
if (code !== null) suffix = ` (${code})`;
|
|
118
|
+
lines.push(` ${label}${suffix}`);
|
|
119
|
+
}
|
|
120
|
+
return lines.join('\n');
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Format a relayed AI filter block, in the same three lines as
|
|
125
|
+
* {@link formatAiRejectionBlock}: the wire's own reason, the model + resolved
|
|
126
|
+
* engine, then WHY there is no content. That third line matters more here
|
|
127
|
+
* than for a rejection: a block throws nothing, so the only symptom a
|
|
128
|
+
* developer sees is an empty answer, and production's `finishMessage` (when
|
|
129
|
+
* it sent one) is the whole explanation. Exported for unit tests.
|
|
130
|
+
*/
|
|
131
|
+
export function formatAiBlockedBlock(payload: AiBlockedPayload): string {
|
|
132
|
+
const finishReason = readText(payload.finishReason);
|
|
133
|
+
const blockReason = readText(payload.blockReason);
|
|
134
|
+
const reason = finishReason ?? blockReason ?? 'unknown';
|
|
135
|
+
const lines = [` ⚠ [pyric] ai response blocked: ${reason}`];
|
|
136
|
+
const modelLine = formatModelLine(payload.model, payload.engine);
|
|
137
|
+
if (modelLine !== null) lines.push(modelLine);
|
|
138
|
+
const finishMessage = readText(payload.finishMessage);
|
|
139
|
+
if (finishMessage !== null) {
|
|
140
|
+
lines.push(` ${finishMessage}`);
|
|
141
|
+
} else if (finishReason === null && blockReason !== null) {
|
|
142
|
+
lines.push(' the prompt was blocked before generation; no candidates were returned');
|
|
143
|
+
} else {
|
|
144
|
+
lines.push(' no content was returned; text() and functionCalls() throw for this finish reason');
|
|
145
|
+
}
|
|
146
|
+
return lines.join('\n');
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Format a relayed AI model substitution into ONE terminal line, a single
|
|
151
|
+
* line on purpose: there is no error, no status, and no missing content to
|
|
152
|
+
* explain. The whole fact IS the arrow, and the parenthetical says which
|
|
153
|
+
* engine did it and why (`engine modelMap`, `experimental alias`, ...).
|
|
154
|
+
* Exported for unit tests.
|
|
155
|
+
*/
|
|
156
|
+
export function formatAiModelSubstitutionBlock(payload: AiModelSubstitutionPayload): string {
|
|
157
|
+
const requested = readText(payload.requestedModel) ?? 'unknown';
|
|
158
|
+
const effective = readText(payload.effectiveModel) ?? 'unknown';
|
|
159
|
+
const engine = readText(payload.engine);
|
|
160
|
+
const reason = readText(payload.reason);
|
|
161
|
+
const attribution = [engine, reason].filter((part) => part !== null);
|
|
162
|
+
let suffix = '';
|
|
163
|
+
if (attribution.length > 0) suffix = ` (${attribution.join(', ')})`;
|
|
164
|
+
return ` ⚠ [pyric] ai model substituted: ${requested} → ${effective}${suffix}`;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* One record per AI diagnostic kind: how it prints, and the (target, reason)
|
|
169
|
+
* pair the denials route throttles it by.
|
|
170
|
+
*
|
|
171
|
+
* The prefixes on `throttleTarget` keep an AI key from colliding with a
|
|
172
|
+
* rules-denial key built from a Firestore path, or with each other: a
|
|
173
|
+
* rejection, a block, and a substitution on the same model are three
|
|
174
|
+
* different problems. `throttleReason` is each kind's identity: a block has
|
|
175
|
+
* no message of its own, so its reason enum is what collapses an agent's
|
|
176
|
+
* retry loop into one line, and a substitution's identity is the model it
|
|
177
|
+
* actually called, so a swap onto a DIFFERENT model still prints.
|
|
178
|
+
*/
|
|
179
|
+
export interface AiTerminalBlock {
|
|
180
|
+
format(payload: AiDiagnosticPayload): string;
|
|
181
|
+
throttleTarget(payload: AiDiagnosticPayload): string;
|
|
182
|
+
throttleReason(payload: AiDiagnosticPayload): string;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const AI_TERMINAL_BLOCKS: { [K in AiDiagnosticKind]: AiTerminalBlock } = {
|
|
186
|
+
'ai-rejection': {
|
|
187
|
+
format: formatAiRejectionBlock,
|
|
188
|
+
throttleTarget: (payload) => `ai-rejection ${readRawText(payload.model, '')}`,
|
|
189
|
+
throttleReason: (payload) => readRawText(payload.message, 'request rejected'),
|
|
190
|
+
},
|
|
191
|
+
'ai-blocked': {
|
|
192
|
+
format: formatAiBlockedBlock,
|
|
193
|
+
throttleTarget: (payload) => `ai-blocked ${readRawText(payload.model, '')}`,
|
|
194
|
+
throttleReason: (payload) =>
|
|
195
|
+
readRawText(payload.finishReason, readRawText(payload.blockReason, 'unknown')),
|
|
196
|
+
},
|
|
197
|
+
'ai-model-substituted': {
|
|
198
|
+
format: formatAiModelSubstitutionBlock,
|
|
199
|
+
throttleTarget: (payload) => `ai-model ${readRawText(payload.requestedModel, '')}`,
|
|
200
|
+
throttleReason: (payload) => readRawText(payload.effectiveModel, 'unknown'),
|
|
201
|
+
},
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
/** The record for a relayed body's `kind`, or `null` when the body is a rules
|
|
205
|
+
* denial (or anything else this route does not recognize). */
|
|
206
|
+
export function aiTerminalBlockFor(kind: unknown): AiTerminalBlock | null {
|
|
207
|
+
if (typeof kind !== 'string') return null;
|
|
208
|
+
if (!Object.hasOwn(AI_TERMINAL_BLOCKS, kind)) return null;
|
|
209
|
+
return AI_TERMINAL_BLOCKS[kind as AiDiagnosticKind];
|
|
210
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Terminal-safe rendering of remote-authored text.
|
|
3
|
+
*
|
|
4
|
+
* Every AI diagnostic the dev server prints quotes a string somebody else
|
|
5
|
+
* wrote: a broker rejection reason, a model server's finish message, a
|
|
6
|
+
* `Retry-After` header, a model id out of a project config. The terminal is
|
|
7
|
+
* not a sandbox, so that text is flattened onto one line, stripped of control
|
|
8
|
+
* and bidi characters (the same treatment `activity-warning.ts` gives incident
|
|
9
|
+
* text), and cut to a fixed length. Without the cut, one field of a remote
|
|
10
|
+
* payload can push the rest of a diagnostic block off the screen.
|
|
11
|
+
*
|
|
12
|
+
* Leaf module: it imports nothing, so the browser-side diagnostics relay can
|
|
13
|
+
* share it without pulling the dev server's formatting code into a served
|
|
14
|
+
* bundle. Credential masking is a separate concern and comes from
|
|
15
|
+
* `pyric/ai/internal`, which publishes the broker's own `redactUrl`.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Longest remote-authored string echoed into one terminal field, and the
|
|
20
|
+
* longest one the browser-side relay puts on the wire. Broker rejection
|
|
21
|
+
* reasons run to a few hundred characters at most, so anything past this is
|
|
22
|
+
* an upstream dumping a payload into a message field.
|
|
23
|
+
*/
|
|
24
|
+
export const AI_TERMINAL_TEXT_MAX = 512;
|
|
25
|
+
|
|
26
|
+
/** Appended when text is cut, so a truncated line never reads as complete. */
|
|
27
|
+
const TRUNCATION_MARKER = '\u2026';
|
|
28
|
+
|
|
29
|
+
/** Cut text to {@link AI_TERMINAL_TEXT_MAX}, marking the cut. */
|
|
30
|
+
export function capTerminalText(text: string): string {
|
|
31
|
+
if (text.length <= AI_TERMINAL_TEXT_MAX) return text;
|
|
32
|
+
return `${text.slice(0, AI_TERMINAL_TEXT_MAX)}${TRUNCATION_MARKER}`;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Control, bidi-override, and line-separator characters. A remote string
|
|
36
|
+
* carrying these can forge a terminal line or reverse the text around it. */
|
|
37
|
+
const TERMINAL_UNSAFE_CHARS =
|
|
38
|
+
/[\u0000-\u001f\u007f-\u009f\u061c\u200e\u200f\u2028-\u202e\u2066-\u2069\ufeff]/g;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Flatten an upstream-authored string into ONE terminal-safe line.
|
|
42
|
+
*
|
|
43
|
+
* Broker rejection reasons are production's own messages, and several are
|
|
44
|
+
* multi-line (`ai-error-empty-contents` is a bulleted list): pasted verbatim
|
|
45
|
+
* they shred the indented block into fragments. Control and bidi characters
|
|
46
|
+
* get dropped for the same reason `activity-warning.ts` drops them: the text
|
|
47
|
+
* can originate from a remote model server, and the terminal is not a sandbox.
|
|
48
|
+
*/
|
|
49
|
+
export function sanitizeForTerminal(text: string): string {
|
|
50
|
+
const flattened = text.replace(TERMINAL_UNSAFE_CHARS, ' ').replace(/\s+/g, ' ').trim();
|
|
51
|
+
return capTerminalText(flattened);
|
|
52
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `POST /__pyric/beacon`, the host half of the interception handshake.
|
|
3
|
+
*
|
|
4
|
+
* `@pyric/cli/register` posts here once per activated child, after its module
|
|
5
|
+
* hooks and net-guard are installed. Receiving it is the only positive proof
|
|
6
|
+
* the dev server has that `NODE_OPTIONS=--import @pyric/cli/register` actually
|
|
7
|
+
* reached that process; everything else about the launch is an assumption. See
|
|
8
|
+
* `register/beacon.ts` for why the child reports on two channels.
|
|
9
|
+
*
|
|
10
|
+
* Who may post, and why it is locked down
|
|
11
|
+
* ---------------------------------------
|
|
12
|
+
* The dev server is reachable from any page the developer happens to have
|
|
13
|
+
* open, so an unauthenticated route here would let a web page disarm the
|
|
14
|
+
* watchdog (post a beacon the launcher counts) and would let any page grow the
|
|
15
|
+
* server's memory with an unbounded body. Four checks close that, in order:
|
|
16
|
+
*
|
|
17
|
+
* 1. `content-type: application/json`. A cross-origin page cannot send that
|
|
18
|
+
* header on a POST without a CORS preflight, and this server answers no
|
|
19
|
+
* `OPTIONS` on this path, so the browser never sends the real request.
|
|
20
|
+
* 2. No `Origin` header and no `Sec-Fetch-Mode: cors`. A browser attaches one
|
|
21
|
+
* or both to every cross-site request it makes; a Node child attaches
|
|
22
|
+
* neither.
|
|
23
|
+
* 3. A per-launch secret in `x-pyric-beacon-token`, which the launcher put in
|
|
24
|
+
* the child's environment as `PYRIC_BEACON_TOKEN`. A page cannot read it.
|
|
25
|
+
* 4. A 4 KB body cap. A report is under 200 bytes.
|
|
26
|
+
*
|
|
27
|
+
* Everything that passes those checks is still best-effort: a malformed body
|
|
28
|
+
* gets 204 and is dropped, because the consequence of an absent beacon is a
|
|
29
|
+
* warning on the parent side and must never be a failure on the child's.
|
|
30
|
+
*/
|
|
31
|
+
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
32
|
+
import { timingSafeEqual } from 'node:crypto';
|
|
33
|
+
import { BODY_TOO_LARGE_CODE, collectBody } from '../bridge/server/peer.js';
|
|
34
|
+
import { getHeader } from './server.js';
|
|
35
|
+
import { BEACON_TOKEN_HEADER, type BeaconReport } from '../register/beacon.js';
|
|
36
|
+
|
|
37
|
+
/** Largest beacon body accepted. A report is well under 200 bytes. */
|
|
38
|
+
export const BEACON_BODY_LIMIT_BYTES = 4_096;
|
|
39
|
+
|
|
40
|
+
export interface BeaconRouteOptions {
|
|
41
|
+
/** The per-launch secret the child must present. */
|
|
42
|
+
readonly token: string;
|
|
43
|
+
/** Receives each accepted report. Absent means the route still 204s. */
|
|
44
|
+
readonly onBeacon?: (report: BeaconReport) => void;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Constant-time string comparison over the token bytes. */
|
|
48
|
+
function tokenMatches(presented: string | undefined, expected: string): boolean {
|
|
49
|
+
if (presented === undefined) return false;
|
|
50
|
+
const a = Buffer.from(presented, 'utf8');
|
|
51
|
+
const b = Buffer.from(expected, 'utf8');
|
|
52
|
+
if (a.length !== b.length) return false;
|
|
53
|
+
return timingSafeEqual(a, b);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Whether the request carries the browser markings of a cross-site fetch. */
|
|
57
|
+
function isBrowserOriginated(req: IncomingMessage): boolean {
|
|
58
|
+
if (getHeader(req, 'origin') !== undefined) return true;
|
|
59
|
+
return getHeader(req, 'sec-fetch-mode') === 'cors';
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Whether the body is declared as JSON, the header a page cannot set without
|
|
63
|
+
* a preflight this server never answers. */
|
|
64
|
+
function declaresJsonBody(req: IncomingMessage): boolean {
|
|
65
|
+
const contentType = getHeader(req, 'content-type');
|
|
66
|
+
if (contentType === undefined) return false;
|
|
67
|
+
return contentType.split(';')[0]!.trim().toLowerCase() === 'application/json';
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Read a beacon body defensively. It is JSON off a socket, and the only fields
|
|
72
|
+
* that matter are the ones the parent reports. `null` for anything that does
|
|
73
|
+
* not carry a plausible report.
|
|
74
|
+
*/
|
|
75
|
+
export function parseBeaconReport(value: unknown): BeaconReport | null {
|
|
76
|
+
if (typeof value !== 'object' || value === null) return null;
|
|
77
|
+
const raw = value as Record<string, unknown>;
|
|
78
|
+
if (typeof raw.pid !== 'number' || !Number.isFinite(raw.pid)) return null;
|
|
79
|
+
const guard = raw.guard;
|
|
80
|
+
if (guard !== 'warn' && guard !== 'block' && guard !== 'off') return null;
|
|
81
|
+
if (typeof raw.hooks !== 'boolean') return null;
|
|
82
|
+
return {
|
|
83
|
+
pid: raw.pid,
|
|
84
|
+
guard,
|
|
85
|
+
hooks: raw.hooks,
|
|
86
|
+
sandbox: typeof raw.sandbox === 'string' ? raw.sandbox : '',
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** The one terminal line confirming a child is interlocked. A beacon that
|
|
91
|
+
* reports `hooks: false` is the interesting case: the child is running the
|
|
92
|
+
* register module, and it is telling us the rewrite did not install. */
|
|
93
|
+
export function formatBeaconReceipt(report: BeaconReport): string {
|
|
94
|
+
if (!report.hooks) {
|
|
95
|
+
return (
|
|
96
|
+
` ⚠ interlock pid=${report.pid}: register loaded but module hooks did NOT install. ` +
|
|
97
|
+
`That process's firebase-admin/firebase imports are NOT routed to the sandbox ` +
|
|
98
|
+
`(Node >= 22.15 is required for full coverage). Net-guard mode ${report.guard}.`
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
return ` ⓘ interlock pid=${report.pid}: sandbox interception confirmed live (guard=${report.guard})`;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** Handle `POST /__pyric/beacon`. */
|
|
105
|
+
export async function handleBeacon(
|
|
106
|
+
opts: BeaconRouteOptions,
|
|
107
|
+
req: IncomingMessage,
|
|
108
|
+
res: ServerResponse,
|
|
109
|
+
): Promise<void> {
|
|
110
|
+
if (req.method !== 'POST') {
|
|
111
|
+
res.writeHead(405, { allow: 'POST' }).end('method not allowed');
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
if (!declaresJsonBody(req)) {
|
|
115
|
+
res.writeHead(415, { 'content-type': 'text/plain' }).end('Unsupported Media Type');
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
if (isBrowserOriginated(req)) {
|
|
119
|
+
res.writeHead(403, { 'content-type': 'text/plain' }).end('Forbidden: browser origin');
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
if (!tokenMatches(getHeader(req, BEACON_TOKEN_HEADER), opts.token)) {
|
|
123
|
+
res.writeHead(401, { 'content-type': 'text/plain' }).end('Unauthorized: invalid beacon token');
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
let body: unknown;
|
|
127
|
+
try {
|
|
128
|
+
body = await collectBody(req, BEACON_BODY_LIMIT_BYTES);
|
|
129
|
+
} catch (err) {
|
|
130
|
+
if ((err as { code?: string }).code === BODY_TOO_LARGE_CODE) {
|
|
131
|
+
res.writeHead(413, { 'content-type': 'text/plain' }).end('Payload Too Large');
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
// Malformed JSON: drop it. Proof-of-life is a side channel.
|
|
135
|
+
res.writeHead(204).end();
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
const report = parseBeaconReport(body);
|
|
139
|
+
if (report !== null) opts.onBeacon?.(report);
|
|
140
|
+
res.writeHead(204).end();
|
|
141
|
+
}
|
package/src/serve/bundler.ts
CHANGED
|
@@ -52,7 +52,7 @@ export function defaultSdkEntries(): Record<string, string> {
|
|
|
52
52
|
if (existsSync(js)) return js;
|
|
53
53
|
const ts = join(here, 'entries', `${name}.ts`);
|
|
54
54
|
if (existsSync(ts)) return ts;
|
|
55
|
-
throw new Error(`pyric
|
|
55
|
+
throw new Error(`pyric sandbox: missing SDK entry '${name}' next to ${here}`);
|
|
56
56
|
};
|
|
57
57
|
return {
|
|
58
58
|
ai: pick('ai'),
|
|
@@ -94,7 +94,7 @@ export function pyricPackageRoot(): string {
|
|
|
94
94
|
}
|
|
95
95
|
const pkg = JSON.parse(readFileSync(join(dir, 'package.json'), 'utf8')) as { name?: string };
|
|
96
96
|
if (pkg.name !== 'pyric') {
|
|
97
|
-
throw new Error(`pyric
|
|
97
|
+
throw new Error(`pyric sandbox: expected to resolve the pyric package, found '${pkg.name}' at ${dir}`);
|
|
98
98
|
}
|
|
99
99
|
return dir;
|
|
100
100
|
}
|
|
@@ -131,7 +131,7 @@ export const NODE_BUILTIN_RE = /^(node:)?(url|fs|path)$/;
|
|
|
131
131
|
* through its own resolveId/load + optimizeDeps esbuild pass, instead of
|
|
132
132
|
* re-deriving them. */
|
|
133
133
|
export const NODE_BUILTIN_SHIMS: Record<string, string> = {
|
|
134
|
-
fs: `const no = () => { throw new Error('pyric
|
|
134
|
+
fs: `const no = () => { throw new Error('pyric sandbox: fs is not available in the browser'); };
|
|
135
135
|
export const readFileSync = no; export const existsSync = () => false;
|
|
136
136
|
export const readdirSync = no; export const writeFileSync = no; export const mkdirSync = no;
|
|
137
137
|
export default { readFileSync, existsSync, readdirSync, writeFileSync, mkdirSync };`,
|
|
@@ -315,13 +315,13 @@ export async function bundleSdk(opts: BundleOptions): Promise<BundleResult> {
|
|
|
315
315
|
// self-identify as the sandbox shim, not the real Firebase SDK.
|
|
316
316
|
chunkNames: 'pyric-sandbox-[hash]',
|
|
317
317
|
banner: {
|
|
318
|
-
js: '/* pyric
|
|
318
|
+
js: '/* pyric sandbox shim serving firebase/*. This is not the real Firebase SDK. */',
|
|
319
319
|
},
|
|
320
320
|
plugins: [pyricResolvePlugin(), nodeShimPlugin()],
|
|
321
321
|
});
|
|
322
322
|
if (result.errors.length > 0) {
|
|
323
323
|
throw new Error(
|
|
324
|
-
`pyric
|
|
324
|
+
`pyric sandbox: SDK bundle failed:\n${result.errors.map((e) => e.text).join('\n')}`,
|
|
325
325
|
);
|
|
326
326
|
}
|
|
327
327
|
writeFileSync(join(outDir, '.complete'), new Date().toISOString());
|
|
@@ -348,7 +348,7 @@ export function workerEntryPath(): string {
|
|
|
348
348
|
if (existsSync(js)) return js;
|
|
349
349
|
const ts = join(here, 'worker', 'entry.ts');
|
|
350
350
|
if (existsSync(ts)) return ts;
|
|
351
|
-
throw new Error(`pyric
|
|
351
|
+
throw new Error(`pyric sandbox: missing SharedWorker entry next to ${here}`);
|
|
352
352
|
}
|
|
353
353
|
|
|
354
354
|
export interface WorkerBundleOptions {
|
|
@@ -371,7 +371,7 @@ const WORKER_EPOCH_FILE = '.worker-epoch';
|
|
|
371
371
|
function readWorkerBundleEpoch(outDir: string): string {
|
|
372
372
|
const epoch = readFileSync(join(outDir, WORKER_EPOCH_FILE), 'utf8').trim();
|
|
373
373
|
if (!/^[a-f0-9]{16}$/.test(epoch)) {
|
|
374
|
-
throw new Error(`pyric
|
|
374
|
+
throw new Error(`pyric sandbox: invalid SharedWorker epoch in ${join(outDir, WORKER_EPOCH_FILE)}`);
|
|
375
375
|
}
|
|
376
376
|
return epoch;
|
|
377
377
|
}
|
|
@@ -479,18 +479,18 @@ export async function bundleWorker(opts: WorkerBundleOptions): Promise<WorkerBun
|
|
|
479
479
|
// with the resulting epoch (same width, so the sourcemap stays aligned).
|
|
480
480
|
define: { __PYRIC_WORKER_VERSION__: JSON.stringify(WORKER_EPOCH_PLACEHOLDER) },
|
|
481
481
|
banner: {
|
|
482
|
-
js: '/* pyric
|
|
482
|
+
js: '/* pyric sandbox SharedWorker host serving firebase/*. This is not the real Firebase SDK. */',
|
|
483
483
|
},
|
|
484
484
|
plugins: [pyricResolvePlugin(), nodeShimPlugin()],
|
|
485
485
|
});
|
|
486
486
|
if (result.errors.length > 0) {
|
|
487
487
|
throw new Error(
|
|
488
|
-
`pyric
|
|
488
|
+
`pyric sandbox: SharedWorker bundle failed:\n${result.errors.map((e) => e.text).join('\n')}`,
|
|
489
489
|
);
|
|
490
490
|
}
|
|
491
491
|
const canonicalSource = readFileSync(outFile, 'utf8');
|
|
492
492
|
if (!canonicalSource.includes(WORKER_EPOCH_PLACEHOLDER)) {
|
|
493
|
-
throw new Error('pyric
|
|
493
|
+
throw new Error('pyric sandbox: SharedWorker bundle omitted its epoch placeholder');
|
|
494
494
|
}
|
|
495
495
|
const epochHash = createHash('sha256').update(canonicalSource);
|
|
496
496
|
if (opts.epochSalt) epochHash.update('\0').update(opts.epochSalt);
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
type AuthUser<Auth extends { currentUser: unknown }> = Auth['currentUser'];
|
|
2
|
+
|
|
3
|
+
export interface ActiveAuthRegistry<Auth extends { currentUser: unknown }> {
|
|
4
|
+
register(auth: Auth): () => void;
|
|
5
|
+
subscribe(listener: (user: AuthUser<Auth>) => void): () => void;
|
|
6
|
+
auths(): IterableIterator<Auth>;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/** Coordinates unique Auth handles while allowing more than one lifecycle owner. */
|
|
10
|
+
export function createActiveAuthRegistry<Auth extends { currentUser: unknown }>(
|
|
11
|
+
observe: (auth: Auth, listener: (user: AuthUser<Auth>) => void) => () => void,
|
|
12
|
+
): ActiveAuthRegistry<Auth> {
|
|
13
|
+
const registrations = new Map<Auth, { owners: number; unsubscribe: () => void }>();
|
|
14
|
+
const listeners = new Set<(user: AuthUser<Auth>) => void>();
|
|
15
|
+
|
|
16
|
+
const register = (auth: Auth): (() => void) => {
|
|
17
|
+
const existing = registrations.get(auth);
|
|
18
|
+
if (existing) {
|
|
19
|
+
existing.owners += 1;
|
|
20
|
+
} else {
|
|
21
|
+
const unsubscribe = observe(auth, (user) => {
|
|
22
|
+
for (const listener of listeners) listener(user);
|
|
23
|
+
});
|
|
24
|
+
registrations.set(auth, { owners: 1, unsubscribe });
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
let released = false;
|
|
28
|
+
return () => {
|
|
29
|
+
if (released) return;
|
|
30
|
+
released = true;
|
|
31
|
+
const registration = registrations.get(auth);
|
|
32
|
+
if (!registration) return;
|
|
33
|
+
registration.owners -= 1;
|
|
34
|
+
if (registration.owners > 0) return;
|
|
35
|
+
registrations.delete(auth);
|
|
36
|
+
registration.unsubscribe();
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const subscribe = (listener: (user: AuthUser<Auth>) => void): (() => void) => {
|
|
41
|
+
listeners.add(listener);
|
|
42
|
+
for (const auth of registrations.keys()) {
|
|
43
|
+
if (auth.currentUser) {
|
|
44
|
+
listener(auth.currentUser);
|
|
45
|
+
break;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return () => listeners.delete(listener);
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
register,
|
|
53
|
+
subscribe,
|
|
54
|
+
auths: () => registrations.keys(),
|
|
55
|
+
};
|
|
56
|
+
}
|