pi-cloudflare 0.1.0
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/LICENSE +674 -0
- package/README.md +67 -0
- package/bin/setup.mjs +103 -0
- package/dist/auth-flow.d.ts +15 -0
- package/dist/auth-flow.d.ts.map +1 -0
- package/dist/auth-flow.js +90 -0
- package/dist/auth-flow.js.map +1 -0
- package/dist/client.d.ts +18 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +47 -0
- package/dist/client.js.map +1 -0
- package/dist/config.d.ts +13 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +10 -0
- package/dist/config.js.map +1 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +109 -0
- package/dist/index.js.map +1 -0
- package/dist/oauth.d.ts +44 -0
- package/dist/oauth.d.ts.map +1 -0
- package/dist/oauth.js +135 -0
- package/dist/oauth.js.map +1 -0
- package/dist/proxy.d.ts +29 -0
- package/dist/proxy.d.ts.map +1 -0
- package/dist/proxy.js +39 -0
- package/dist/proxy.js.map +1 -0
- package/dist/servers.d.ts +19 -0
- package/dist/servers.d.ts.map +1 -0
- package/dist/servers.js +40 -0
- package/dist/servers.js.map +1 -0
- package/dist/token-store.d.ts +23 -0
- package/dist/token-store.d.ts.map +1 -0
- package/dist/token-store.js +47 -0
- package/dist/token-store.js.map +1 -0
- package/package.json +67 -0
- package/skills/agents-sdk/SKILL.md +221 -0
- package/skills/agents-sdk/references/browse-the-web.md +63 -0
- package/skills/agents-sdk/references/callable.md +92 -0
- package/skills/agents-sdk/references/client-sdk.md +110 -0
- package/skills/agents-sdk/references/codemode.md +110 -0
- package/skills/agents-sdk/references/configuration.md +72 -0
- package/skills/agents-sdk/references/durable-execution.md +51 -0
- package/skills/agents-sdk/references/email.md +146 -0
- package/skills/agents-sdk/references/human-in-the-loop.md +67 -0
- package/skills/agents-sdk/references/mcp.md +188 -0
- package/skills/agents-sdk/references/observability.md +44 -0
- package/skills/agents-sdk/references/queue-retries.md +79 -0
- package/skills/agents-sdk/references/routing.md +75 -0
- package/skills/agents-sdk/references/server-driven-messages.md +63 -0
- package/skills/agents-sdk/references/state-scheduling.md +171 -0
- package/skills/agents-sdk/references/streaming-chat.md +198 -0
- package/skills/agents-sdk/references/think.md +112 -0
- package/skills/agents-sdk/references/voice.md +68 -0
- package/skills/agents-sdk/references/webhooks-push.md +86 -0
- package/skills/agents-sdk/references/workflows.md +132 -0
- package/skills/cloudflare/SKILL.md +248 -0
- package/skills/cloudflare/references/agents-sdk/README.md +89 -0
- package/skills/cloudflare/references/agents-sdk/api.md +190 -0
- package/skills/cloudflare/references/agents-sdk/configuration.md +182 -0
- package/skills/cloudflare/references/agents-sdk/gotchas.md +158 -0
- package/skills/cloudflare/references/agents-sdk/patterns.md +192 -0
- package/skills/cloudflare/references/ai-gateway/README.md +175 -0
- package/skills/cloudflare/references/ai-gateway/configuration.md +111 -0
- package/skills/cloudflare/references/ai-gateway/dynamic-routing.md +82 -0
- package/skills/cloudflare/references/ai-gateway/features.md +96 -0
- package/skills/cloudflare/references/ai-gateway/sdk-integration.md +114 -0
- package/skills/cloudflare/references/ai-gateway/troubleshooting.md +88 -0
- package/skills/cloudflare/references/ai-search/README.md +138 -0
- package/skills/cloudflare/references/ai-search/api.md +87 -0
- package/skills/cloudflare/references/ai-search/configuration.md +88 -0
- package/skills/cloudflare/references/ai-search/gotchas.md +81 -0
- package/skills/cloudflare/references/ai-search/patterns.md +85 -0
- package/skills/cloudflare/references/analytics-engine/README.md +94 -0
- package/skills/cloudflare/references/analytics-engine/api.md +112 -0
- package/skills/cloudflare/references/analytics-engine/configuration.md +112 -0
- package/skills/cloudflare/references/analytics-engine/gotchas.md +85 -0
- package/skills/cloudflare/references/analytics-engine/patterns.md +83 -0
- package/skills/cloudflare/references/api/README.md +66 -0
- package/skills/cloudflare/references/api/api.md +204 -0
- package/skills/cloudflare/references/api/configuration.md +160 -0
- package/skills/cloudflare/references/api/gotchas.md +225 -0
- package/skills/cloudflare/references/api/patterns.md +204 -0
- package/skills/cloudflare/references/api-shield/README.md +44 -0
- package/skills/cloudflare/references/api-shield/api.md +141 -0
- package/skills/cloudflare/references/api-shield/configuration.md +192 -0
- package/skills/cloudflare/references/api-shield/gotchas.md +125 -0
- package/skills/cloudflare/references/api-shield/patterns.md +180 -0
- package/skills/cloudflare/references/argo-smart-routing/README.md +90 -0
- package/skills/cloudflare/references/argo-smart-routing/api.md +240 -0
- package/skills/cloudflare/references/argo-smart-routing/configuration.md +197 -0
- package/skills/cloudflare/references/argo-smart-routing/gotchas.md +111 -0
- package/skills/cloudflare/references/argo-smart-routing/patterns.md +104 -0
- package/skills/cloudflare/references/artifacts/README.md +79 -0
- package/skills/cloudflare/references/artifacts/api.md +128 -0
- package/skills/cloudflare/references/artifacts/configuration.md +92 -0
- package/skills/cloudflare/references/bindings/README.md +122 -0
- package/skills/cloudflare/references/bindings/api.md +203 -0
- package/skills/cloudflare/references/bindings/configuration.md +188 -0
- package/skills/cloudflare/references/bindings/gotchas.md +208 -0
- package/skills/cloudflare/references/bindings/patterns.md +200 -0
- package/skills/cloudflare/references/bot-management/README.md +94 -0
- package/skills/cloudflare/references/bot-management/api.md +169 -0
- package/skills/cloudflare/references/bot-management/configuration.md +163 -0
- package/skills/cloudflare/references/bot-management/gotchas.md +114 -0
- package/skills/cloudflare/references/bot-management/patterns.md +182 -0
- package/skills/cloudflare/references/browser-rendering/README.md +78 -0
- package/skills/cloudflare/references/browser-rendering/api.md +108 -0
- package/skills/cloudflare/references/browser-rendering/configuration.md +78 -0
- package/skills/cloudflare/references/browser-rendering/gotchas.md +88 -0
- package/skills/cloudflare/references/browser-rendering/patterns.md +91 -0
- package/skills/cloudflare/references/c3/README.md +111 -0
- package/skills/cloudflare/references/c3/api.md +71 -0
- package/skills/cloudflare/references/c3/configuration.md +81 -0
- package/skills/cloudflare/references/c3/gotchas.md +92 -0
- package/skills/cloudflare/references/c3/patterns.md +82 -0
- package/skills/cloudflare/references/cache-reserve/README.md +147 -0
- package/skills/cloudflare/references/cache-reserve/api.md +194 -0
- package/skills/cloudflare/references/cache-reserve/configuration.md +169 -0
- package/skills/cloudflare/references/cache-reserve/gotchas.md +132 -0
- package/skills/cloudflare/references/cache-reserve/patterns.md +197 -0
- package/skills/cloudflare/references/containers/README.md +85 -0
- package/skills/cloudflare/references/containers/api.md +187 -0
- package/skills/cloudflare/references/containers/configuration.md +188 -0
- package/skills/cloudflare/references/containers/gotchas.md +178 -0
- package/skills/cloudflare/references/containers/patterns.md +202 -0
- package/skills/cloudflare/references/cron-triggers/README.md +99 -0
- package/skills/cloudflare/references/cron-triggers/api.md +196 -0
- package/skills/cloudflare/references/cron-triggers/configuration.md +180 -0
- package/skills/cloudflare/references/cron-triggers/gotchas.md +199 -0
- package/skills/cloudflare/references/cron-triggers/patterns.md +190 -0
- package/skills/cloudflare/references/d1/README.md +133 -0
- package/skills/cloudflare/references/d1/api.md +196 -0
- package/skills/cloudflare/references/d1/configuration.md +191 -0
- package/skills/cloudflare/references/d1/gotchas.md +98 -0
- package/skills/cloudflare/references/d1/patterns.md +189 -0
- package/skills/cloudflare/references/ddos/README.md +41 -0
- package/skills/cloudflare/references/ddos/api.md +164 -0
- package/skills/cloudflare/references/ddos/configuration.md +93 -0
- package/skills/cloudflare/references/ddos/gotchas.md +107 -0
- package/skills/cloudflare/references/ddos/patterns.md +174 -0
- package/skills/cloudflare/references/do-storage/README.md +75 -0
- package/skills/cloudflare/references/do-storage/api.md +102 -0
- package/skills/cloudflare/references/do-storage/configuration.md +112 -0
- package/skills/cloudflare/references/do-storage/gotchas.md +150 -0
- package/skills/cloudflare/references/do-storage/patterns.md +194 -0
- package/skills/cloudflare/references/do-storage/testing.md +183 -0
- package/skills/cloudflare/references/durable-objects/README.md +185 -0
- package/skills/cloudflare/references/durable-objects/api.md +187 -0
- package/skills/cloudflare/references/durable-objects/configuration.md +160 -0
- package/skills/cloudflare/references/durable-objects/gotchas.md +197 -0
- package/skills/cloudflare/references/durable-objects/patterns.md +201 -0
- package/skills/cloudflare/references/email-routing/README.md +89 -0
- package/skills/cloudflare/references/email-routing/api.md +195 -0
- package/skills/cloudflare/references/email-routing/configuration.md +186 -0
- package/skills/cloudflare/references/email-routing/gotchas.md +196 -0
- package/skills/cloudflare/references/email-routing/patterns.md +229 -0
- package/skills/cloudflare/references/email-workers/README.md +151 -0
- package/skills/cloudflare/references/email-workers/api.md +237 -0
- package/skills/cloudflare/references/email-workers/configuration.md +112 -0
- package/skills/cloudflare/references/email-workers/gotchas.md +125 -0
- package/skills/cloudflare/references/email-workers/patterns.md +102 -0
- package/skills/cloudflare/references/flagship/README.md +59 -0
- package/skills/cloudflare/references/flagship/api.md +390 -0
- package/skills/cloudflare/references/flagship/configuration.md +202 -0
- package/skills/cloudflare/references/flagship/gotchas.md +178 -0
- package/skills/cloudflare/references/flagship/patterns.md +469 -0
- package/skills/cloudflare/references/graphql-api/README.md +147 -0
- package/skills/cloudflare/references/graphql-api/api.md +175 -0
- package/skills/cloudflare/references/graphql-api/configuration.md +118 -0
- package/skills/cloudflare/references/graphql-api/gotchas.md +110 -0
- package/skills/cloudflare/references/graphql-api/patterns.md +225 -0
- package/skills/cloudflare/references/hyperdrive/README.md +82 -0
- package/skills/cloudflare/references/hyperdrive/api.md +143 -0
- package/skills/cloudflare/references/hyperdrive/configuration.md +159 -0
- package/skills/cloudflare/references/hyperdrive/gotchas.md +77 -0
- package/skills/cloudflare/references/hyperdrive/patterns.md +190 -0
- package/skills/cloudflare/references/images/README.md +61 -0
- package/skills/cloudflare/references/images/api.md +96 -0
- package/skills/cloudflare/references/images/configuration.md +211 -0
- package/skills/cloudflare/references/images/gotchas.md +99 -0
- package/skills/cloudflare/references/images/patterns.md +115 -0
- package/skills/cloudflare/references/kv/README.md +89 -0
- package/skills/cloudflare/references/kv/api.md +160 -0
- package/skills/cloudflare/references/kv/configuration.md +144 -0
- package/skills/cloudflare/references/kv/gotchas.md +131 -0
- package/skills/cloudflare/references/kv/patterns.md +196 -0
- package/skills/cloudflare/references/miniflare/README.md +105 -0
- package/skills/cloudflare/references/miniflare/api.md +187 -0
- package/skills/cloudflare/references/miniflare/configuration.md +173 -0
- package/skills/cloudflare/references/miniflare/gotchas.md +160 -0
- package/skills/cloudflare/references/miniflare/patterns.md +181 -0
- package/skills/cloudflare/references/network-interconnect/README.md +99 -0
- package/skills/cloudflare/references/network-interconnect/api.md +199 -0
- package/skills/cloudflare/references/network-interconnect/configuration.md +114 -0
- package/skills/cloudflare/references/network-interconnect/gotchas.md +165 -0
- package/skills/cloudflare/references/network-interconnect/patterns.md +166 -0
- package/skills/cloudflare/references/observability/README.md +88 -0
- package/skills/cloudflare/references/observability/api.md +164 -0
- package/skills/cloudflare/references/observability/configuration.md +169 -0
- package/skills/cloudflare/references/observability/gotchas.md +115 -0
- package/skills/cloudflare/references/observability/patterns.md +105 -0
- package/skills/cloudflare/references/pages/README.md +88 -0
- package/skills/cloudflare/references/pages/api.md +204 -0
- package/skills/cloudflare/references/pages/configuration.md +201 -0
- package/skills/cloudflare/references/pages/gotchas.md +203 -0
- package/skills/cloudflare/references/pages/patterns.md +204 -0
- package/skills/cloudflare/references/pages-functions/README.md +98 -0
- package/skills/cloudflare/references/pages-functions/api.md +143 -0
- package/skills/cloudflare/references/pages-functions/configuration.md +122 -0
- package/skills/cloudflare/references/pages-functions/gotchas.md +94 -0
- package/skills/cloudflare/references/pages-functions/patterns.md +137 -0
- package/skills/cloudflare/references/pipelines/README.md +90 -0
- package/skills/cloudflare/references/pipelines/api.md +124 -0
- package/skills/cloudflare/references/pipelines/configuration.md +155 -0
- package/skills/cloudflare/references/pipelines/gotchas.md +58 -0
- package/skills/cloudflare/references/pipelines/patterns.md +130 -0
- package/skills/cloudflare/references/pulumi/README.md +100 -0
- package/skills/cloudflare/references/pulumi/api.md +200 -0
- package/skills/cloudflare/references/pulumi/configuration.md +198 -0
- package/skills/cloudflare/references/pulumi/gotchas.md +181 -0
- package/skills/cloudflare/references/pulumi/patterns.md +191 -0
- package/skills/cloudflare/references/queues/README.md +96 -0
- package/skills/cloudflare/references/queues/api.md +206 -0
- package/skills/cloudflare/references/queues/configuration.md +144 -0
- package/skills/cloudflare/references/queues/gotchas.md +206 -0
- package/skills/cloudflare/references/queues/patterns.md +220 -0
- package/skills/cloudflare/references/r2/README.md +95 -0
- package/skills/cloudflare/references/r2/api.md +200 -0
- package/skills/cloudflare/references/r2/configuration.md +165 -0
- package/skills/cloudflare/references/r2/gotchas.md +190 -0
- package/skills/cloudflare/references/r2/patterns.md +193 -0
- package/skills/cloudflare/references/r2-data-catalog/README.md +75 -0
- package/skills/cloudflare/references/r2-data-catalog/api.md +122 -0
- package/skills/cloudflare/references/r2-data-catalog/configuration.md +98 -0
- package/skills/cloudflare/references/r2-data-catalog/gotchas.md +55 -0
- package/skills/cloudflare/references/r2-data-catalog/patterns.md +122 -0
- package/skills/cloudflare/references/r2-sql/README.md +64 -0
- package/skills/cloudflare/references/r2-sql/api.md +121 -0
- package/skills/cloudflare/references/r2-sql/configuration.md +50 -0
- package/skills/cloudflare/references/r2-sql/gotchas.md +39 -0
- package/skills/cloudflare/references/r2-sql/patterns.md +118 -0
- package/skills/cloudflare/references/realtime-sfu/README.md +65 -0
- package/skills/cloudflare/references/realtime-sfu/api.md +158 -0
- package/skills/cloudflare/references/realtime-sfu/configuration.md +137 -0
- package/skills/cloudflare/references/realtime-sfu/gotchas.md +133 -0
- package/skills/cloudflare/references/realtime-sfu/patterns.md +174 -0
- package/skills/cloudflare/references/realtimekit/README.md +113 -0
- package/skills/cloudflare/references/realtimekit/api.md +212 -0
- package/skills/cloudflare/references/realtimekit/configuration.md +203 -0
- package/skills/cloudflare/references/realtimekit/gotchas.md +169 -0
- package/skills/cloudflare/references/realtimekit/patterns.md +223 -0
- package/skills/cloudflare/references/sandbox/README.md +96 -0
- package/skills/cloudflare/references/sandbox/api.md +198 -0
- package/skills/cloudflare/references/sandbox/configuration.md +143 -0
- package/skills/cloudflare/references/sandbox/gotchas.md +194 -0
- package/skills/cloudflare/references/sandbox/patterns.md +201 -0
- package/skills/cloudflare/references/secrets-store/README.md +74 -0
- package/skills/cloudflare/references/secrets-store/api.md +200 -0
- package/skills/cloudflare/references/secrets-store/configuration.md +185 -0
- package/skills/cloudflare/references/secrets-store/gotchas.md +97 -0
- package/skills/cloudflare/references/secrets-store/patterns.md +207 -0
- package/skills/cloudflare/references/smart-placement/README.md +138 -0
- package/skills/cloudflare/references/smart-placement/api.md +183 -0
- package/skills/cloudflare/references/smart-placement/configuration.md +196 -0
- package/skills/cloudflare/references/smart-placement/gotchas.md +174 -0
- package/skills/cloudflare/references/smart-placement/patterns.md +183 -0
- package/skills/cloudflare/references/snippets/README.md +68 -0
- package/skills/cloudflare/references/snippets/api.md +198 -0
- package/skills/cloudflare/references/snippets/configuration.md +227 -0
- package/skills/cloudflare/references/snippets/gotchas.md +86 -0
- package/skills/cloudflare/references/snippets/patterns.md +135 -0
- package/skills/cloudflare/references/spectrum/README.md +52 -0
- package/skills/cloudflare/references/spectrum/api.md +181 -0
- package/skills/cloudflare/references/spectrum/configuration.md +194 -0
- package/skills/cloudflare/references/spectrum/gotchas.md +145 -0
- package/skills/cloudflare/references/spectrum/patterns.md +196 -0
- package/skills/cloudflare/references/static-assets/README.md +65 -0
- package/skills/cloudflare/references/static-assets/api.md +199 -0
- package/skills/cloudflare/references/static-assets/configuration.md +186 -0
- package/skills/cloudflare/references/static-assets/gotchas.md +162 -0
- package/skills/cloudflare/references/static-assets/patterns.md +189 -0
- package/skills/cloudflare/references/stream/README.md +114 -0
- package/skills/cloudflare/references/stream/api-live.md +195 -0
- package/skills/cloudflare/references/stream/api.md +199 -0
- package/skills/cloudflare/references/stream/configuration.md +141 -0
- package/skills/cloudflare/references/stream/gotchas.md +130 -0
- package/skills/cloudflare/references/stream/patterns.md +184 -0
- package/skills/cloudflare/references/tail-workers/README.md +89 -0
- package/skills/cloudflare/references/tail-workers/api.md +200 -0
- package/skills/cloudflare/references/tail-workers/configuration.md +176 -0
- package/skills/cloudflare/references/tail-workers/gotchas.md +192 -0
- package/skills/cloudflare/references/tail-workers/patterns.md +180 -0
- package/skills/cloudflare/references/terraform/README.md +102 -0
- package/skills/cloudflare/references/terraform/api.md +178 -0
- package/skills/cloudflare/references/terraform/configuration.md +197 -0
- package/skills/cloudflare/references/terraform/gotchas.md +150 -0
- package/skills/cloudflare/references/terraform/patterns.md +174 -0
- package/skills/cloudflare/references/tunnel/README.md +129 -0
- package/skills/cloudflare/references/tunnel/api.md +193 -0
- package/skills/cloudflare/references/tunnel/configuration.md +157 -0
- package/skills/cloudflare/references/tunnel/gotchas.md +147 -0
- package/skills/cloudflare/references/tunnel/networking.md +168 -0
- package/skills/cloudflare/references/tunnel/patterns.md +192 -0
- package/skills/cloudflare/references/turn/README.md +82 -0
- package/skills/cloudflare/references/turn/api.md +239 -0
- package/skills/cloudflare/references/turn/configuration.md +179 -0
- package/skills/cloudflare/references/turn/gotchas.md +231 -0
- package/skills/cloudflare/references/turn/patterns.md +213 -0
- package/skills/cloudflare/references/turnstile/README.md +99 -0
- package/skills/cloudflare/references/turnstile/api.md +240 -0
- package/skills/cloudflare/references/turnstile/configuration.md +222 -0
- package/skills/cloudflare/references/turnstile/gotchas.md +218 -0
- package/skills/cloudflare/references/turnstile/patterns.md +193 -0
- package/skills/cloudflare/references/vectorize/README.md +133 -0
- package/skills/cloudflare/references/vectorize/api.md +88 -0
- package/skills/cloudflare/references/vectorize/configuration.md +88 -0
- package/skills/cloudflare/references/vectorize/gotchas.md +76 -0
- package/skills/cloudflare/references/vectorize/patterns.md +90 -0
- package/skills/cloudflare/references/waf/README.md +113 -0
- package/skills/cloudflare/references/waf/api.md +202 -0
- package/skills/cloudflare/references/waf/configuration.md +203 -0
- package/skills/cloudflare/references/waf/gotchas.md +204 -0
- package/skills/cloudflare/references/waf/patterns.md +197 -0
- package/skills/cloudflare/references/web-analytics/README.md +141 -0
- package/skills/cloudflare/references/web-analytics/configuration.md +76 -0
- package/skills/cloudflare/references/web-analytics/gotchas.md +82 -0
- package/skills/cloudflare/references/web-analytics/integration.md +60 -0
- package/skills/cloudflare/references/web-analytics/patterns.md +91 -0
- package/skills/cloudflare/references/workerd/README.md +78 -0
- package/skills/cloudflare/references/workerd/api.md +185 -0
- package/skills/cloudflare/references/workerd/configuration.md +183 -0
- package/skills/cloudflare/references/workerd/gotchas.md +139 -0
- package/skills/cloudflare/references/workerd/patterns.md +192 -0
- package/skills/cloudflare/references/workers/README.md +108 -0
- package/skills/cloudflare/references/workers/api.md +195 -0
- package/skills/cloudflare/references/workers/configuration.md +185 -0
- package/skills/cloudflare/references/workers/frameworks.md +197 -0
- package/skills/cloudflare/references/workers/gotchas.md +137 -0
- package/skills/cloudflare/references/workers/patterns.md +198 -0
- package/skills/cloudflare/references/workers-ai/README.md +197 -0
- package/skills/cloudflare/references/workers-ai/api.md +112 -0
- package/skills/cloudflare/references/workers-ai/configuration.md +97 -0
- package/skills/cloudflare/references/workers-ai/gotchas.md +114 -0
- package/skills/cloudflare/references/workers-ai/patterns.md +120 -0
- package/skills/cloudflare/references/workers-for-platforms/README.md +89 -0
- package/skills/cloudflare/references/workers-for-platforms/api.md +196 -0
- package/skills/cloudflare/references/workers-for-platforms/configuration.md +167 -0
- package/skills/cloudflare/references/workers-for-platforms/gotchas.md +134 -0
- package/skills/cloudflare/references/workers-for-platforms/patterns.md +188 -0
- package/skills/cloudflare/references/workers-playground/README.md +127 -0
- package/skills/cloudflare/references/workers-playground/api.md +101 -0
- package/skills/cloudflare/references/workers-playground/configuration.md +163 -0
- package/skills/cloudflare/references/workers-playground/gotchas.md +88 -0
- package/skills/cloudflare/references/workers-playground/patterns.md +132 -0
- package/skills/cloudflare/references/workers-vpc/README.md +127 -0
- package/skills/cloudflare/references/workers-vpc/api.md +202 -0
- package/skills/cloudflare/references/workers-vpc/configuration.md +147 -0
- package/skills/cloudflare/references/workers-vpc/gotchas.md +167 -0
- package/skills/cloudflare/references/workers-vpc/patterns.md +209 -0
- package/skills/cloudflare/references/workflows/README.md +77 -0
- package/skills/cloudflare/references/workflows/api.md +218 -0
- package/skills/cloudflare/references/workflows/configuration.md +152 -0
- package/skills/cloudflare/references/workflows/gotchas.md +90 -0
- package/skills/cloudflare/references/workflows/patterns.md +175 -0
- package/skills/cloudflare/references/wrangler/README.md +135 -0
- package/skills/cloudflare/references/wrangler/api.md +188 -0
- package/skills/cloudflare/references/wrangler/configuration.md +197 -0
- package/skills/cloudflare/references/wrangler/gotchas.md +197 -0
- package/skills/cloudflare/references/wrangler/patterns.md +209 -0
- package/skills/cloudflare/references/zaraz/IMPLEMENTATION_SUMMARY.md +121 -0
- package/skills/cloudflare/references/zaraz/README.md +111 -0
- package/skills/cloudflare/references/zaraz/api.md +112 -0
- package/skills/cloudflare/references/zaraz/configuration.md +90 -0
- package/skills/cloudflare/references/zaraz/gotchas.md +81 -0
- package/skills/cloudflare/references/zaraz/patterns.md +74 -0
- package/skills/cloudflare-email-service/SKILL.md +103 -0
- package/skills/cloudflare-email-service/references/cli-and-mcp.md +125 -0
- package/skills/cloudflare-email-service/references/deliverability.md +285 -0
- package/skills/cloudflare-email-service/references/rest-api.md +184 -0
- package/skills/cloudflare-email-service/references/routing.md +199 -0
- package/skills/cloudflare-email-service/references/sending.md +252 -0
- package/skills/cloudflare-one/SKILL.md +176 -0
- package/skills/cloudflare-one-migrations/SKILL.md +110 -0
- package/skills/durable-objects/SKILL.md +186 -0
- package/skills/durable-objects/references/rules.md +295 -0
- package/skills/durable-objects/references/testing.md +264 -0
- package/skills/durable-objects/references/workers.md +346 -0
- package/skills/sandbox-migrate-to-next/SKILL.md +185 -0
- package/skills/sandbox-next/SKILL.md +91 -0
- package/skills/sandbox-next/references/api-quick-ref.md +94 -0
- package/skills/sandbox-next/references/examples.md +15 -0
- package/skills/sandbox-stable/SKILL.md +110 -0
- package/skills/turnstile-spin/README.md +51 -0
- package/skills/turnstile-spin/SKILL.md +330 -0
- package/skills/turnstile-spin/references/astro.md +199 -0
- package/skills/turnstile-spin/references/hugo.md +114 -0
- package/skills/turnstile-spin/references/nextjs-app.md +261 -0
- package/skills/turnstile-spin/references/nextjs-pages.md +82 -0
- package/skills/turnstile-spin/references/sveltekit.md +191 -0
- package/skills/turnstile-spin/references/vanilla-html.md +157 -0
- package/skills/turnstile-spin/scripts/auth-probe.sh +285 -0
- package/skills/turnstile-spin/scripts/persist-skill.sh +114 -0
- package/skills/turnstile-spin/scripts/validate.sh +137 -0
- package/skills/turnstile-spin/scripts/widget-create.sh +121 -0
- package/skills/turnstile-spin/tests/validation.md +62 -0
- package/skills/workers-best-practices/SKILL.md +127 -0
- package/skills/workers-best-practices/references/review.md +174 -0
- package/skills/workers-best-practices/references/rules.md +467 -0
- package/skills/wrangler/SKILL.md +922 -0
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
# SvelteKit
|
|
2
|
+
|
|
3
|
+
For SvelteKit projects. The widget renders in the page; siteverify is called from a SvelteKit form action (or a `+server.ts` endpoint) server-side.
|
|
4
|
+
|
|
5
|
+
```svelte title="src/routes/signup/+page.svelte"
|
|
6
|
+
<script>
|
|
7
|
+
import { enhance } from "$app/forms";
|
|
8
|
+
import { onMount } from "svelte";
|
|
9
|
+
|
|
10
|
+
let turnstileContainer;
|
|
11
|
+
let signupWidgetId;
|
|
12
|
+
|
|
13
|
+
onMount(() => {
|
|
14
|
+
const render = () => {
|
|
15
|
+
signupWidgetId = window.turnstile.render(turnstileContainer, {
|
|
16
|
+
sitekey: "YOUR_SITEKEY",
|
|
17
|
+
action: "signup",
|
|
18
|
+
});
|
|
19
|
+
delete window.onSignupTurnstileLoad;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
if (window.turnstile) {
|
|
23
|
+
render();
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
window.onSignupTurnstileLoad = render;
|
|
28
|
+
const script = document.createElement("script");
|
|
29
|
+
script.src =
|
|
30
|
+
"https://challenges.cloudflare.com/turnstile/v0/api.js?onload=onSignupTurnstileLoad&render=explicit";
|
|
31
|
+
script.async = true;
|
|
32
|
+
document.head.appendChild(script);
|
|
33
|
+
|
|
34
|
+
return () => {
|
|
35
|
+
delete window.onSignupTurnstileLoad;
|
|
36
|
+
};
|
|
37
|
+
});
|
|
38
|
+
</script>
|
|
39
|
+
|
|
40
|
+
<form
|
|
41
|
+
method="POST"
|
|
42
|
+
use:enhance={() => {
|
|
43
|
+
return async ({ result, update }) => {
|
|
44
|
+
try {
|
|
45
|
+
await update();
|
|
46
|
+
} finally {
|
|
47
|
+
if (result.type !== "redirect" && signupWidgetId !== undefined) {
|
|
48
|
+
window.turnstile.reset(signupWidgetId);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
}}
|
|
53
|
+
>
|
|
54
|
+
<input name="email" type="email" required />
|
|
55
|
+
<div bind:this={turnstileContainer}></div>
|
|
56
|
+
<button type="submit">Sign up</button>
|
|
57
|
+
</form>
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Form action (canonical siteverify):
|
|
61
|
+
|
|
62
|
+
```ts title="src/routes/signup/+page.server.ts"
|
|
63
|
+
import type { Actions } from "./$types";
|
|
64
|
+
import { fail } from "@sveltejs/kit";
|
|
65
|
+
import { TURNSTILE_SECRET, TURNSTILE_HOSTNAMES } from "$env/static/private";
|
|
66
|
+
|
|
67
|
+
const expectedHostnames = new Set(
|
|
68
|
+
(TURNSTILE_HOSTNAMES ?? "")
|
|
69
|
+
.split(",")
|
|
70
|
+
.map((h) => h.trim())
|
|
71
|
+
.filter(Boolean),
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
export const actions: Actions = {
|
|
75
|
+
default: async ({ request, getClientAddress }) => {
|
|
76
|
+
const data = await request.formData();
|
|
77
|
+
const token = data.get("cf-turnstile-response");
|
|
78
|
+
if (typeof token !== "string" || expectedHostnames.size === 0) {
|
|
79
|
+
return fail(403, { error: "Verification failed" });
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const verify = await fetch("https://challenges.cloudflare.com/turnstile/v0/siteverify", {
|
|
83
|
+
method: "POST",
|
|
84
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
85
|
+
body: new URLSearchParams({
|
|
86
|
+
secret: TURNSTILE_SECRET,
|
|
87
|
+
response: token,
|
|
88
|
+
remoteip: getClientAddress(),
|
|
89
|
+
}),
|
|
90
|
+
});
|
|
91
|
+
const result = await verify.json();
|
|
92
|
+
if (
|
|
93
|
+
verify.ok !== true ||
|
|
94
|
+
result.success !== true ||
|
|
95
|
+
result.action !== "signup" ||
|
|
96
|
+
!expectedHostnames.has(result.hostname)
|
|
97
|
+
) {
|
|
98
|
+
return fail(403, { error: "Verification failed" });
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// process signup
|
|
102
|
+
return { ok: true };
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
`signup` is the stable action for this surface. Preserve an existing custom migration action and compare the returned action to the same value. Siteverify is mandatory for every widget mode, including pre-clearance. Set `TURNSTILE_HOSTNAMES` to the deployment-specific frontend hostnames; a production value must not include `localhost` or `127.0.0.1`.
|
|
108
|
+
|
|
109
|
+
In `.env`:
|
|
110
|
+
|
|
111
|
+
```text
|
|
112
|
+
TURNSTILE_SECRET=YOUR_SECRET
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
The `$env/static/private` import enforces that the secret never reaches the client bundle.
|
|
116
|
+
|
|
117
|
+
## Variant: client-side fetch to an endpoint
|
|
118
|
+
|
|
119
|
+
If you need a JSON API rather than progressive-enhancement form post, use `+server.ts`:
|
|
120
|
+
|
|
121
|
+
```ts title="src/routes/api/signup/+server.ts"
|
|
122
|
+
import type { RequestHandler } from "./$types";
|
|
123
|
+
import { TURNSTILE_SECRET, TURNSTILE_HOSTNAMES } from "$env/static/private";
|
|
124
|
+
|
|
125
|
+
const expectedHostnames = new Set(
|
|
126
|
+
(TURNSTILE_HOSTNAMES ?? "")
|
|
127
|
+
.split(",")
|
|
128
|
+
.map((h) => h.trim())
|
|
129
|
+
.filter(Boolean),
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
export const POST: RequestHandler = async ({ request, getClientAddress }) => {
|
|
133
|
+
const { token } = await request.json();
|
|
134
|
+
if (expectedHostnames.size === 0) {
|
|
135
|
+
return new Response("forbidden", { status: 403 });
|
|
136
|
+
}
|
|
137
|
+
const verify = await fetch("https://challenges.cloudflare.com/turnstile/v0/siteverify", {
|
|
138
|
+
method: "POST",
|
|
139
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
140
|
+
body: new URLSearchParams({
|
|
141
|
+
secret: TURNSTILE_SECRET,
|
|
142
|
+
response: token,
|
|
143
|
+
remoteip: getClientAddress(),
|
|
144
|
+
}),
|
|
145
|
+
});
|
|
146
|
+
const result = await verify.json();
|
|
147
|
+
if (
|
|
148
|
+
verify.ok !== true ||
|
|
149
|
+
result.success !== true ||
|
|
150
|
+
result.action !== "signup" ||
|
|
151
|
+
!expectedHostnames.has(result.hostname)
|
|
152
|
+
) {
|
|
153
|
+
return new Response("forbidden", { status: 403 });
|
|
154
|
+
}
|
|
155
|
+
// process signup
|
|
156
|
+
return new Response(JSON.stringify({ ok: true }), { status: 200 });
|
|
157
|
+
};
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
The explicit renderer above retains `signupWidgetId`. Reset it in `finally` when calling this endpoint so every completion path gets a fresh token:
|
|
161
|
+
|
|
162
|
+
```svelte
|
|
163
|
+
<script>
|
|
164
|
+
async function submit(e) {
|
|
165
|
+
e.preventDefault();
|
|
166
|
+
try {
|
|
167
|
+
const token = new FormData(e.currentTarget).get("cf-turnstile-response");
|
|
168
|
+
const res = await fetch("/api/signup", {
|
|
169
|
+
method: "POST",
|
|
170
|
+
body: JSON.stringify({ token }),
|
|
171
|
+
});
|
|
172
|
+
const result = await res.json();
|
|
173
|
+
if (!res.ok || result.ok !== true) throw new Error("Submission failed");
|
|
174
|
+
// proceed
|
|
175
|
+
} catch {
|
|
176
|
+
// surface error
|
|
177
|
+
} finally {
|
|
178
|
+
if (signupWidgetId !== undefined) {
|
|
179
|
+
window.turnstile.reset(signupWidgetId);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
</script>
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
## Substitutions
|
|
187
|
+
|
|
188
|
+
| Placeholder | Replace with |
|
|
189
|
+
| ------------------- | -------------------------------------------------------------------- |
|
|
190
|
+
| `YOUR_SITEKEY` | The widget site key from Step 8 |
|
|
191
|
+
| `YOUR_SECRET` | The secret captured in Step 8. Stays in env, never inlined. |
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# Vanilla HTML
|
|
2
|
+
|
|
3
|
+
For static sites or any project without a JS framework. The widget renders client-side; the form submits to whatever backend handles your form (a Node/PHP/Ruby/Go server, a Cloudflare Worker, a Pages Function, a third-party form host that supports server-side hooks, etc.).
|
|
4
|
+
|
|
5
|
+
```html
|
|
6
|
+
<!doctype html>
|
|
7
|
+
<html>
|
|
8
|
+
<head>
|
|
9
|
+
<script
|
|
10
|
+
src="https://challenges.cloudflare.com/turnstile/v0/api.js"
|
|
11
|
+
async
|
|
12
|
+
defer
|
|
13
|
+
></script>
|
|
14
|
+
</head>
|
|
15
|
+
<body>
|
|
16
|
+
<form action="/api/subscribe" method="POST">
|
|
17
|
+
<input name="email" type="email" required />
|
|
18
|
+
<div
|
|
19
|
+
class="cf-turnstile"
|
|
20
|
+
data-sitekey="YOUR_SITEKEY"
|
|
21
|
+
data-action="subscribe"
|
|
22
|
+
></div>
|
|
23
|
+
<button type="submit">Subscribe</button>
|
|
24
|
+
</form>
|
|
25
|
+
</body>
|
|
26
|
+
</html>
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
When the form submits, the browser includes `cf-turnstile-response` automatically. Your backend reads it and calls canonical siteverify.
|
|
30
|
+
|
|
31
|
+
## Backend (any language)
|
|
32
|
+
|
|
33
|
+
Add this to your existing `/api/subscribe` handler before the rest of its logic:
|
|
34
|
+
|
|
35
|
+
```js
|
|
36
|
+
// Node / fetch idiom
|
|
37
|
+
const expectedHostnames = new Set(
|
|
38
|
+
(process.env.TURNSTILE_HOSTNAMES ?? '')
|
|
39
|
+
.split(',')
|
|
40
|
+
.map((h) => h.trim())
|
|
41
|
+
.filter(Boolean),
|
|
42
|
+
);
|
|
43
|
+
if (expectedHostnames.size === 0) return res.status(403).end();
|
|
44
|
+
|
|
45
|
+
const token = req.body['cf-turnstile-response'];
|
|
46
|
+
const r = await fetch('https://challenges.cloudflare.com/turnstile/v0/siteverify', {
|
|
47
|
+
method: 'POST',
|
|
48
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
49
|
+
body: new URLSearchParams({
|
|
50
|
+
secret: process.env.TURNSTILE_SECRET,
|
|
51
|
+
response: token,
|
|
52
|
+
remoteip: req.ip,
|
|
53
|
+
}),
|
|
54
|
+
});
|
|
55
|
+
const result = await r.json();
|
|
56
|
+
if (
|
|
57
|
+
r.ok !== true ||
|
|
58
|
+
result.success !== true ||
|
|
59
|
+
result.action !== 'subscribe' ||
|
|
60
|
+
!expectedHostnames.has(result.hostname)
|
|
61
|
+
) {
|
|
62
|
+
return res.status(403).end();
|
|
63
|
+
}
|
|
64
|
+
// existing handler logic runs here
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Equivalent calls in other backend languages (each also compares `result.hostname` to a `TURNSTILE_HOSTNAMES` allowlist):
|
|
68
|
+
|
|
69
|
+
```ruby
|
|
70
|
+
# Ruby
|
|
71
|
+
require 'net/http'; require 'uri'; require 'json'; require 'set'
|
|
72
|
+
expected_hostnames = (ENV['TURNSTILE_HOSTNAMES'] || '').split(',').map(&:strip).reject(&:empty?).to_set
|
|
73
|
+
halt 403 if expected_hostnames.empty?
|
|
74
|
+
res = Net::HTTP.post_form(URI('https://challenges.cloudflare.com/turnstile/v0/siteverify'),
|
|
75
|
+
secret: ENV['TURNSTILE_SECRET'], response: params['cf-turnstile-response'], remoteip: request.ip)
|
|
76
|
+
result = JSON.parse(res.body)
|
|
77
|
+
halt 403 unless res.is_a?(Net::HTTPSuccess) && result['success'] == true && result['action'] == 'subscribe' && expected_hostnames.include?(result['hostname'])
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
# Python (requests)
|
|
82
|
+
expected_hostnames = {h.strip() for h in os.environ.get('TURNSTILE_HOSTNAMES', '').split(',') if h.strip()}
|
|
83
|
+
if not expected_hostnames:
|
|
84
|
+
return '', 403
|
|
85
|
+
r = requests.post('https://challenges.cloudflare.com/turnstile/v0/siteverify',
|
|
86
|
+
data={'secret': os.environ['TURNSTILE_SECRET'],
|
|
87
|
+
'response': form['cf-turnstile-response'],
|
|
88
|
+
'remoteip': request.remote_addr})
|
|
89
|
+
result = r.json()
|
|
90
|
+
if (not r.ok or result.get('success') is not True or result.get('action') != 'subscribe'
|
|
91
|
+
or result.get('hostname') not in expected_hostnames):
|
|
92
|
+
return '', 403
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
`subscribe` is the stable action for this surface. Preserve an existing custom migration action and compare the returned action to the same value. Siteverify is mandatory for every widget mode, including pre-clearance. Set `TURNSTILE_HOSTNAMES` to the deployment-specific frontend hostnames; a production value must not include `localhost` or `127.0.0.1`.
|
|
96
|
+
|
|
97
|
+
## Variant: AJAX submit instead of form action
|
|
98
|
+
|
|
99
|
+
For an AJAX flow, replace the native form and API script with explicit rendering. Keep this surface's widget ID and reset it in `finally`, which covers network, JSON, validation, and server failures as well as successful same-page completion.
|
|
100
|
+
|
|
101
|
+
```html
|
|
102
|
+
<form id="subscribe-form">
|
|
103
|
+
<input name="email" type="email" required />
|
|
104
|
+
<div id="subscribe-turnstile"></div>
|
|
105
|
+
<button type="submit">Subscribe</button>
|
|
106
|
+
</form>
|
|
107
|
+
<script>
|
|
108
|
+
let subscribeWidgetId;
|
|
109
|
+
|
|
110
|
+
window.onSubscribeTurnstileLoad = () => {
|
|
111
|
+
subscribeWidgetId = window.turnstile.render("#subscribe-turnstile", {
|
|
112
|
+
sitekey: "YOUR_SITEKEY",
|
|
113
|
+
action: "subscribe",
|
|
114
|
+
});
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
document.getElementById("subscribe-form").addEventListener("submit", async (event) => {
|
|
118
|
+
event.preventDefault();
|
|
119
|
+
try {
|
|
120
|
+
const res = await fetch("/api/subscribe", {
|
|
121
|
+
method: "POST",
|
|
122
|
+
body: new FormData(event.currentTarget),
|
|
123
|
+
});
|
|
124
|
+
const json = await res.json();
|
|
125
|
+
if (!res.ok || json.ok !== true) throw new Error("Submission failed");
|
|
126
|
+
// proceed
|
|
127
|
+
} catch {
|
|
128
|
+
// surface the error
|
|
129
|
+
} finally {
|
|
130
|
+
if (subscribeWidgetId !== undefined) {
|
|
131
|
+
window.turnstile.reset(subscribeWidgetId);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
</script>
|
|
136
|
+
<script
|
|
137
|
+
src="https://challenges.cloudflare.com/turnstile/v0/api.js?onload=onSubscribeTurnstileLoad&render=explicit"
|
|
138
|
+
async
|
|
139
|
+
defer
|
|
140
|
+
></script>
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## No backend?
|
|
144
|
+
|
|
145
|
+
If your project is pure-static (no server-side handler — just HTML served from a CDN), Spin doesn't apply. Siteverify is server-side by design. Options:
|
|
146
|
+
|
|
147
|
+
- Add a Cloudflare Pages Function (`functions/api/subscribe.js`) to host the siteverify call.
|
|
148
|
+
- Deploy a tiny Cloudflare Worker that does siteverify against your existing form host.
|
|
149
|
+
- Use a third-party form host that exposes a server-side webhook where you can wire siteverify.
|
|
150
|
+
|
|
151
|
+
## Substitutions
|
|
152
|
+
|
|
153
|
+
| Placeholder | Replace with |
|
|
154
|
+
| ------------------- | -------------------------------------------------------------------- |
|
|
155
|
+
| `YOUR_SITEKEY` | The widget site key from Step 8 |
|
|
156
|
+
| `/api/subscribe` | The path to your existing form-handling endpoint |
|
|
157
|
+
| `TURNSTILE_SECRET` | Env-var name. Value is the secret captured in Step 8, kept off disk. |
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Probes Cloudflare API auth state for the Turnstile Spin agent.
|
|
3
|
+
#
|
|
4
|
+
# Reads:
|
|
5
|
+
# $CLOUDFLARE_API_TOKEN (required)
|
|
6
|
+
# $CLOUDFLARE_ACCOUNT_ID (optional; if set, must be one of the token's accounts)
|
|
7
|
+
#
|
|
8
|
+
# Requires: bash, curl, python3. Optional: a user-approved WRANGLER_BIN for account enumeration.
|
|
9
|
+
#
|
|
10
|
+
# Outputs JSON to stdout, always exits 0. The agent reads `status`:
|
|
11
|
+
# "ok" ; selected account passed the Turnstile Edit-scope probe
|
|
12
|
+
# "missing_token" ; no token set, python3 unavailable, or account enumeration failed
|
|
13
|
+
# "missing_scope" ; token lacks Account.Turnstile:Edit on the selected account
|
|
14
|
+
# "multiple_accounts" ; token covers >1 accounts and $CLOUDFLARE_ACCOUNT_ID is unset
|
|
15
|
+
# "account_mismatch" ; $CLOUDFLARE_ACCOUNT_ID is set but is not in the token's accounts list
|
|
16
|
+
# "network_failure" ; the Edit-scope probe could not reach the Cloudflare API
|
|
17
|
+
# "upstream_failure" ; the Edit-scope probe returned an unexpected upstream response
|
|
18
|
+
#
|
|
19
|
+
# Account enumeration uses `WRANGLER_BIN whoami --json` only when WRANGLER_BIN is
|
|
20
|
+
# an approved canonical absolute path outside PROJECT_ROOT and WRANGLER_VERSION
|
|
21
|
+
# matches it exactly. Otherwise the caller must supply $CLOUDFLARE_ACCOUNT_ID.
|
|
22
|
+
#
|
|
23
|
+
# Human-readable diagnostics go to stderr.
|
|
24
|
+
|
|
25
|
+
set +x
|
|
26
|
+
set -uo pipefail
|
|
27
|
+
|
|
28
|
+
emit() {
|
|
29
|
+
echo "$1"
|
|
30
|
+
exit 0
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if ! command -v python3 >/dev/null 2>&1; then
|
|
34
|
+
echo "auth-probe: python3 is required but not found in PATH." >&2
|
|
35
|
+
emit '{"status":"missing_token","reason":"python3_not_available"}'
|
|
36
|
+
fi
|
|
37
|
+
|
|
38
|
+
token="${CLOUDFLARE_API_TOKEN:-}"
|
|
39
|
+
unset CLOUDFLARE_API_TOKEN
|
|
40
|
+
declared_account="${CLOUDFLARE_ACCOUNT_ID:-}"
|
|
41
|
+
|
|
42
|
+
if [ -z "$token" ]; then
|
|
43
|
+
echo "auth-probe: \$CLOUDFLARE_API_TOKEN is not set." >&2
|
|
44
|
+
emit '{"status":"missing_token","reason":"no_env_var"}'
|
|
45
|
+
fi
|
|
46
|
+
if [[ ! "$token" =~ ^[A-Za-z0-9_-]+$ ]]; then
|
|
47
|
+
echo "auth-probe: CLOUDFLARE_API_TOKEN has an invalid format." >&2
|
|
48
|
+
emit '{"status":"missing_token","reason":"invalid_token_format"}'
|
|
49
|
+
fi
|
|
50
|
+
|
|
51
|
+
accounts_json=""
|
|
52
|
+
account_count=0
|
|
53
|
+
|
|
54
|
+
if [ -n "${WRANGLER_BIN:-}" ]; then
|
|
55
|
+
if [[ "$WRANGLER_BIN" != /* || ! -x "$WRANGLER_BIN" ]]; then
|
|
56
|
+
echo "auth-probe: WRANGLER_BIN must be an executable absolute path." >&2
|
|
57
|
+
emit '{"status":"missing_token","reason":"invalid_wrangler_path"}'
|
|
58
|
+
fi
|
|
59
|
+
|
|
60
|
+
wrangler_bin=$(python3 -I -c 'import os,sys; print(os.path.realpath(sys.argv[1]))' "$WRANGLER_BIN")
|
|
61
|
+
if [ "$wrangler_bin" != "$WRANGLER_BIN" ]; then
|
|
62
|
+
echo "auth-probe: WRANGLER_BIN must be canonical, without symlinks." >&2
|
|
63
|
+
emit '{"status":"missing_token","reason":"noncanonical_wrangler_path"}'
|
|
64
|
+
fi
|
|
65
|
+
if [ -n "${PROJECT_ROOT:-}" ]; then
|
|
66
|
+
project_root=$(python3 -I -c 'import os,sys; print(os.path.realpath(sys.argv[1]))' "$PROJECT_ROOT")
|
|
67
|
+
if [[ "$wrangler_bin" == "$project_root" || "$wrangler_bin" == "$project_root/"* ]]; then
|
|
68
|
+
echo "auth-probe: WRANGLER_BIN must be outside PROJECT_ROOT." >&2
|
|
69
|
+
emit '{"status":"missing_token","reason":"project_local_wrangler"}'
|
|
70
|
+
fi
|
|
71
|
+
fi
|
|
72
|
+
if [ -z "${WRANGLER_VERSION:-}" ]; then
|
|
73
|
+
echo "auth-probe: WRANGLER_VERSION is required with WRANGLER_BIN." >&2
|
|
74
|
+
emit '{"status":"missing_token","reason":"missing_wrangler_version"}'
|
|
75
|
+
fi
|
|
76
|
+
|
|
77
|
+
actual_version=$(
|
|
78
|
+
"$wrangler_bin" --version 2>/dev/null |
|
|
79
|
+
python3 -I -c 'import re,sys; m=re.search(r"\b(\d+\.\d+\.\d+)\b", sys.stdin.read()); print(m.group(1) if m else "")'
|
|
80
|
+
)
|
|
81
|
+
if [ "$actual_version" != "$WRANGLER_VERSION" ]; then
|
|
82
|
+
echo "auth-probe: WRANGLER_BIN version does not match WRANGLER_VERSION." >&2
|
|
83
|
+
emit '{"status":"missing_token","reason":"wrangler_version_mismatch"}'
|
|
84
|
+
fi
|
|
85
|
+
|
|
86
|
+
whoami_json=$(CLOUDFLARE_API_TOKEN="$token" "$wrangler_bin" whoami --json 2>/dev/null || true)
|
|
87
|
+
if [ -n "$whoami_json" ] && [ "$(printf '%s' "$whoami_json" | head -c 1)" = "{" ]; then
|
|
88
|
+
accounts_json=$(printf '%s' "$whoami_json" | python3 -I -c '
|
|
89
|
+
import json, sys
|
|
90
|
+
try:
|
|
91
|
+
d = json.load(sys.stdin)
|
|
92
|
+
print(json.dumps(d.get("accounts") or []))
|
|
93
|
+
except Exception:
|
|
94
|
+
print("[]")
|
|
95
|
+
')
|
|
96
|
+
account_count=$(printf '%s' "$accounts_json" | python3 -I -c '
|
|
97
|
+
import json, sys
|
|
98
|
+
try:
|
|
99
|
+
print(len(json.load(sys.stdin)))
|
|
100
|
+
except Exception:
|
|
101
|
+
print(0)
|
|
102
|
+
')
|
|
103
|
+
fi
|
|
104
|
+
fi
|
|
105
|
+
|
|
106
|
+
if [ "$account_count" = "0" ] && [ -n "$declared_account" ]; then
|
|
107
|
+
# No wrangler, but user gave us an account. Trust it and skip enumeration.
|
|
108
|
+
accounts_json="[{\"id\":$(python3 -I -c 'import json, sys; print(json.dumps(sys.argv[1]))' "$declared_account")}]"
|
|
109
|
+
account_count=1
|
|
110
|
+
fi
|
|
111
|
+
|
|
112
|
+
if [ "$account_count" = "0" ]; then
|
|
113
|
+
echo "auth-probe: could not enumerate accounts. Export CLOUDFLARE_ACCOUNT_ID or provide an approved WRANGLER_BIN and WRANGLER_VERSION." >&2
|
|
114
|
+
emit '{"status":"missing_token","reason":"no_accounts"}'
|
|
115
|
+
fi
|
|
116
|
+
|
|
117
|
+
if [ -n "$declared_account" ]; then
|
|
118
|
+
in_list=$(printf '%s' "$accounts_json" | python3 -I -c '
|
|
119
|
+
import json, sys
|
|
120
|
+
target = sys.argv[1]
|
|
121
|
+
try:
|
|
122
|
+
accounts = json.load(sys.stdin)
|
|
123
|
+
except Exception:
|
|
124
|
+
print("false"); sys.exit(0)
|
|
125
|
+
print("true" if any((a or {}).get("id") == target for a in accounts) else "false")
|
|
126
|
+
' "$declared_account")
|
|
127
|
+
if [ "$in_list" != "true" ]; then
|
|
128
|
+
echo "auth-probe: \$CLOUDFLARE_ACCOUNT_ID ($declared_account) is not one of the token's accounts." >&2
|
|
129
|
+
emit "$(python3 -I -c '
|
|
130
|
+
import json, sys
|
|
131
|
+
declared, accounts_raw = sys.argv[1], sys.argv[2]
|
|
132
|
+
try:
|
|
133
|
+
accounts = json.loads(accounts_raw)
|
|
134
|
+
except Exception:
|
|
135
|
+
accounts = []
|
|
136
|
+
print(json.dumps({"status":"account_mismatch","declared":declared,"accounts":accounts}))
|
|
137
|
+
' "$declared_account" "$accounts_json")"
|
|
138
|
+
fi
|
|
139
|
+
account_id="$declared_account"
|
|
140
|
+
elif [ "$account_count" = "1" ]; then
|
|
141
|
+
account_id=$(printf '%s' "$accounts_json" | python3 -I -c '
|
|
142
|
+
import json, sys
|
|
143
|
+
try:
|
|
144
|
+
print(json.load(sys.stdin)[0]["id"])
|
|
145
|
+
except Exception:
|
|
146
|
+
print("")
|
|
147
|
+
')
|
|
148
|
+
if [ -z "$account_id" ]; then
|
|
149
|
+
echo "auth-probe: accounts list had one entry but no id field." >&2
|
|
150
|
+
emit '{"status":"missing_token","reason":"malformed_accounts"}'
|
|
151
|
+
fi
|
|
152
|
+
else
|
|
153
|
+
echo "auth-probe: token covers $account_count accounts; ask the user to pick one, then export \$CLOUDFLARE_ACCOUNT_ID and re-run." >&2
|
|
154
|
+
emit "$(python3 -I -c '
|
|
155
|
+
import json, sys
|
|
156
|
+
try:
|
|
157
|
+
accounts = json.loads(sys.argv[1])
|
|
158
|
+
except Exception:
|
|
159
|
+
accounts = []
|
|
160
|
+
print(json.dumps({"status":"multiple_accounts","accounts":accounts}))
|
|
161
|
+
' "$accounts_json")"
|
|
162
|
+
fi
|
|
163
|
+
|
|
164
|
+
# Edit-scope probe. A GET /challenges/widgets would authorize a Read-only
|
|
165
|
+
# token; to verify Edit specifically, POST with an intentionally invalid
|
|
166
|
+
# payload and interpret the response:
|
|
167
|
+
# 401 or 403 → token lacks Edit
|
|
168
|
+
# 200 with success:false, errors[0].code=10000 → token lacks Edit
|
|
169
|
+
# 400/422 or 200 with validation error codes → Edit scope OK
|
|
170
|
+
#
|
|
171
|
+
# The API rejects the empty-name/empty-domains payload with 400 today, so
|
|
172
|
+
# no widget is created. If validation ever loosens and the probe accidentally
|
|
173
|
+
# creates one, we detect the returned sitekey and DELETE it as a safety net
|
|
174
|
+
# so the probe stays side-effect-free.
|
|
175
|
+
account_enc=$(python3 -I -c 'import sys, urllib.parse; print(urllib.parse.quote(sys.argv[1], safe=""))' "$account_id")
|
|
176
|
+
|
|
177
|
+
if ! probe_response="$(
|
|
178
|
+
printf 'header = "Authorization: Bearer %s"\n' "$token" |
|
|
179
|
+
curl --disable --config - --silent --show-error --write-out $'\n%{http_code}' -X POST \
|
|
180
|
+
"https://api.cloudflare.com/client/v4/accounts/$account_enc/challenges/widgets" \
|
|
181
|
+
-H "Content-Type: application/json" \
|
|
182
|
+
--data '{"name":"","domains":[]}'
|
|
183
|
+
)"; then
|
|
184
|
+
echo "auth-probe: network failure probing Edit scope on account $account_id." >&2
|
|
185
|
+
emit '{"status":"network_failure","account_id":"'"$account_id"'"}'
|
|
186
|
+
fi
|
|
187
|
+
|
|
188
|
+
edit_code="${probe_response##*$'\n'}"
|
|
189
|
+
probe_body="${probe_response%$'\n'*}"
|
|
190
|
+
probe_output=$(printf '%s' "$probe_body" | python3 -I -c '
|
|
191
|
+
import json, sys
|
|
192
|
+
http_code = sys.argv[1]
|
|
193
|
+
verdict = "unknown"
|
|
194
|
+
created_sitekey = ""
|
|
195
|
+
try:
|
|
196
|
+
raw = sys.stdin.read()
|
|
197
|
+
data = json.loads(raw) if raw else {}
|
|
198
|
+
except Exception:
|
|
199
|
+
data = None
|
|
200
|
+
if isinstance(data, dict):
|
|
201
|
+
errors = data.get("errors") or []
|
|
202
|
+
if not isinstance(errors, list):
|
|
203
|
+
errors = []
|
|
204
|
+
first = (errors[0] or {}) if errors else {}
|
|
205
|
+
if not isinstance(first, dict):
|
|
206
|
+
first = {}
|
|
207
|
+
first_code = first.get("code", 0)
|
|
208
|
+
if http_code in ("401", "403"):
|
|
209
|
+
verdict = "missing_scope"
|
|
210
|
+
elif http_code == "200" and data.get("success") is False and first_code == 10000:
|
|
211
|
+
verdict = "missing_scope"
|
|
212
|
+
elif http_code in ("400", "422"):
|
|
213
|
+
verdict = "scope_ok"
|
|
214
|
+
elif http_code == "200":
|
|
215
|
+
# Any 200 that got past auth means scope is fine (whether success or not).
|
|
216
|
+
verdict = "scope_ok"
|
|
217
|
+
else:
|
|
218
|
+
verdict = f"unexpected_{http_code}"
|
|
219
|
+
# Detect accidental widget creation (safety net if API validation ever
|
|
220
|
+
# accepts the empty-name/empty-domains probe payload).
|
|
221
|
+
result = data.get("result")
|
|
222
|
+
if isinstance(result, dict) and data.get("success") is True:
|
|
223
|
+
sk = result.get("sitekey", "")
|
|
224
|
+
if isinstance(sk, str) and sk:
|
|
225
|
+
created_sitekey = sk
|
|
226
|
+
print(f"{verdict}|{created_sitekey}")
|
|
227
|
+
' "$edit_code")
|
|
228
|
+
unset probe_body probe_response
|
|
229
|
+
verdict="${probe_output%%|*}"
|
|
230
|
+
created_sitekey="${probe_output#*|}"
|
|
231
|
+
[ "$created_sitekey" = "$probe_output" ] && created_sitekey=""
|
|
232
|
+
|
|
233
|
+
# If the probe unexpectedly created a widget (API validation loosened),
|
|
234
|
+
# DELETE it so the probe stays side-effect-free.
|
|
235
|
+
if [ -n "$created_sitekey" ]; then
|
|
236
|
+
echo "auth-probe: probe unexpectedly created widget $created_sitekey; cleaning up..." >&2
|
|
237
|
+
sk_enc=$(python3 -I -c 'import sys, urllib.parse; print(urllib.parse.quote(sys.argv[1], safe=""))' "$created_sitekey")
|
|
238
|
+
cleanup_code=$(
|
|
239
|
+
printf 'header = "Authorization: Bearer %s"\n' "$token" |
|
|
240
|
+
curl --disable --config - --silent --show-error --output /dev/null --write-out "%{http_code}" -X DELETE \
|
|
241
|
+
"https://api.cloudflare.com/client/v4/accounts/$account_enc/challenges/widgets/$sk_enc" || echo "000"
|
|
242
|
+
)
|
|
243
|
+
case "$cleanup_code" in
|
|
244
|
+
2*) echo "auth-probe: cleanup DELETE for widget $created_sitekey succeeded (HTTP $cleanup_code)." >&2 ;;
|
|
245
|
+
*) echo "auth-probe: cleanup DELETE for widget $created_sitekey FAILED (HTTP $cleanup_code). Please remove it from the Turnstile dashboard manually." >&2 ;;
|
|
246
|
+
esac
|
|
247
|
+
fi
|
|
248
|
+
|
|
249
|
+
case "$verdict" in
|
|
250
|
+
scope_ok)
|
|
251
|
+
emit "$(python3 -I -c '
|
|
252
|
+
import json, sys
|
|
253
|
+
account_id, accounts_raw = sys.argv[1], sys.argv[2]
|
|
254
|
+
try:
|
|
255
|
+
accounts = json.loads(accounts_raw)
|
|
256
|
+
except Exception:
|
|
257
|
+
accounts = []
|
|
258
|
+
print(json.dumps({"status":"ok","account_id":account_id,"accounts":accounts}))
|
|
259
|
+
' "$account_id" "$accounts_json")"
|
|
260
|
+
;;
|
|
261
|
+
missing_scope)
|
|
262
|
+
echo "auth-probe: token cannot write /challenges/widgets on account $account_id (HTTP $edit_code). Missing Account.Turnstile:Edit." >&2
|
|
263
|
+
emit "$(python3 -I -c '
|
|
264
|
+
import json, sys
|
|
265
|
+
account_id, http_code = sys.argv[1], sys.argv[2]
|
|
266
|
+
try:
|
|
267
|
+
code_num = int(http_code)
|
|
268
|
+
except ValueError:
|
|
269
|
+
code_num = 0
|
|
270
|
+
print(json.dumps({"status":"missing_scope","account_id":account_id,"http_code":code_num}))
|
|
271
|
+
' "$account_id" "$edit_code")"
|
|
272
|
+
;;
|
|
273
|
+
*)
|
|
274
|
+
echo "auth-probe: unexpected response probing Edit scope on account $account_id (HTTP $edit_code)." >&2
|
|
275
|
+
emit "$(python3 -I -c '
|
|
276
|
+
import json, sys
|
|
277
|
+
account_id, http_code = sys.argv[1], sys.argv[2]
|
|
278
|
+
try:
|
|
279
|
+
code_num = int(http_code)
|
|
280
|
+
except ValueError:
|
|
281
|
+
code_num = 0
|
|
282
|
+
print(json.dumps({"status":"upstream_failure","account_id":account_id,"http_code":code_num}))
|
|
283
|
+
' "$account_id" "$edit_code")"
|
|
284
|
+
;;
|
|
285
|
+
esac
|