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