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.
Files changed (410) hide show
  1. package/LICENSE +674 -0
  2. package/README.md +67 -0
  3. package/bin/setup.mjs +103 -0
  4. package/dist/auth-flow.d.ts +15 -0
  5. package/dist/auth-flow.d.ts.map +1 -0
  6. package/dist/auth-flow.js +90 -0
  7. package/dist/auth-flow.js.map +1 -0
  8. package/dist/client.d.ts +18 -0
  9. package/dist/client.d.ts.map +1 -0
  10. package/dist/client.js +47 -0
  11. package/dist/client.js.map +1 -0
  12. package/dist/config.d.ts +13 -0
  13. package/dist/config.d.ts.map +1 -0
  14. package/dist/config.js +10 -0
  15. package/dist/config.js.map +1 -0
  16. package/dist/index.d.ts +24 -0
  17. package/dist/index.d.ts.map +1 -0
  18. package/dist/index.js +109 -0
  19. package/dist/index.js.map +1 -0
  20. package/dist/oauth.d.ts +44 -0
  21. package/dist/oauth.d.ts.map +1 -0
  22. package/dist/oauth.js +135 -0
  23. package/dist/oauth.js.map +1 -0
  24. package/dist/proxy.d.ts +29 -0
  25. package/dist/proxy.d.ts.map +1 -0
  26. package/dist/proxy.js +39 -0
  27. package/dist/proxy.js.map +1 -0
  28. package/dist/servers.d.ts +19 -0
  29. package/dist/servers.d.ts.map +1 -0
  30. package/dist/servers.js +40 -0
  31. package/dist/servers.js.map +1 -0
  32. package/dist/token-store.d.ts +23 -0
  33. package/dist/token-store.d.ts.map +1 -0
  34. package/dist/token-store.js +47 -0
  35. package/dist/token-store.js.map +1 -0
  36. package/package.json +67 -0
  37. package/skills/agents-sdk/SKILL.md +221 -0
  38. package/skills/agents-sdk/references/browse-the-web.md +63 -0
  39. package/skills/agents-sdk/references/callable.md +92 -0
  40. package/skills/agents-sdk/references/client-sdk.md +110 -0
  41. package/skills/agents-sdk/references/codemode.md +110 -0
  42. package/skills/agents-sdk/references/configuration.md +72 -0
  43. package/skills/agents-sdk/references/durable-execution.md +51 -0
  44. package/skills/agents-sdk/references/email.md +146 -0
  45. package/skills/agents-sdk/references/human-in-the-loop.md +67 -0
  46. package/skills/agents-sdk/references/mcp.md +188 -0
  47. package/skills/agents-sdk/references/observability.md +44 -0
  48. package/skills/agents-sdk/references/queue-retries.md +79 -0
  49. package/skills/agents-sdk/references/routing.md +75 -0
  50. package/skills/agents-sdk/references/server-driven-messages.md +63 -0
  51. package/skills/agents-sdk/references/state-scheduling.md +171 -0
  52. package/skills/agents-sdk/references/streaming-chat.md +198 -0
  53. package/skills/agents-sdk/references/think.md +112 -0
  54. package/skills/agents-sdk/references/voice.md +68 -0
  55. package/skills/agents-sdk/references/webhooks-push.md +86 -0
  56. package/skills/agents-sdk/references/workflows.md +132 -0
  57. package/skills/cloudflare/SKILL.md +248 -0
  58. package/skills/cloudflare/references/agents-sdk/README.md +89 -0
  59. package/skills/cloudflare/references/agents-sdk/api.md +190 -0
  60. package/skills/cloudflare/references/agents-sdk/configuration.md +182 -0
  61. package/skills/cloudflare/references/agents-sdk/gotchas.md +158 -0
  62. package/skills/cloudflare/references/agents-sdk/patterns.md +192 -0
  63. package/skills/cloudflare/references/ai-gateway/README.md +175 -0
  64. package/skills/cloudflare/references/ai-gateway/configuration.md +111 -0
  65. package/skills/cloudflare/references/ai-gateway/dynamic-routing.md +82 -0
  66. package/skills/cloudflare/references/ai-gateway/features.md +96 -0
  67. package/skills/cloudflare/references/ai-gateway/sdk-integration.md +114 -0
  68. package/skills/cloudflare/references/ai-gateway/troubleshooting.md +88 -0
  69. package/skills/cloudflare/references/ai-search/README.md +138 -0
  70. package/skills/cloudflare/references/ai-search/api.md +87 -0
  71. package/skills/cloudflare/references/ai-search/configuration.md +88 -0
  72. package/skills/cloudflare/references/ai-search/gotchas.md +81 -0
  73. package/skills/cloudflare/references/ai-search/patterns.md +85 -0
  74. package/skills/cloudflare/references/analytics-engine/README.md +94 -0
  75. package/skills/cloudflare/references/analytics-engine/api.md +112 -0
  76. package/skills/cloudflare/references/analytics-engine/configuration.md +112 -0
  77. package/skills/cloudflare/references/analytics-engine/gotchas.md +85 -0
  78. package/skills/cloudflare/references/analytics-engine/patterns.md +83 -0
  79. package/skills/cloudflare/references/api/README.md +66 -0
  80. package/skills/cloudflare/references/api/api.md +204 -0
  81. package/skills/cloudflare/references/api/configuration.md +160 -0
  82. package/skills/cloudflare/references/api/gotchas.md +225 -0
  83. package/skills/cloudflare/references/api/patterns.md +204 -0
  84. package/skills/cloudflare/references/api-shield/README.md +44 -0
  85. package/skills/cloudflare/references/api-shield/api.md +141 -0
  86. package/skills/cloudflare/references/api-shield/configuration.md +192 -0
  87. package/skills/cloudflare/references/api-shield/gotchas.md +125 -0
  88. package/skills/cloudflare/references/api-shield/patterns.md +180 -0
  89. package/skills/cloudflare/references/argo-smart-routing/README.md +90 -0
  90. package/skills/cloudflare/references/argo-smart-routing/api.md +240 -0
  91. package/skills/cloudflare/references/argo-smart-routing/configuration.md +197 -0
  92. package/skills/cloudflare/references/argo-smart-routing/gotchas.md +111 -0
  93. package/skills/cloudflare/references/argo-smart-routing/patterns.md +104 -0
  94. package/skills/cloudflare/references/artifacts/README.md +79 -0
  95. package/skills/cloudflare/references/artifacts/api.md +128 -0
  96. package/skills/cloudflare/references/artifacts/configuration.md +92 -0
  97. package/skills/cloudflare/references/bindings/README.md +122 -0
  98. package/skills/cloudflare/references/bindings/api.md +203 -0
  99. package/skills/cloudflare/references/bindings/configuration.md +188 -0
  100. package/skills/cloudflare/references/bindings/gotchas.md +208 -0
  101. package/skills/cloudflare/references/bindings/patterns.md +200 -0
  102. package/skills/cloudflare/references/bot-management/README.md +94 -0
  103. package/skills/cloudflare/references/bot-management/api.md +169 -0
  104. package/skills/cloudflare/references/bot-management/configuration.md +163 -0
  105. package/skills/cloudflare/references/bot-management/gotchas.md +114 -0
  106. package/skills/cloudflare/references/bot-management/patterns.md +182 -0
  107. package/skills/cloudflare/references/browser-rendering/README.md +78 -0
  108. package/skills/cloudflare/references/browser-rendering/api.md +108 -0
  109. package/skills/cloudflare/references/browser-rendering/configuration.md +78 -0
  110. package/skills/cloudflare/references/browser-rendering/gotchas.md +88 -0
  111. package/skills/cloudflare/references/browser-rendering/patterns.md +91 -0
  112. package/skills/cloudflare/references/c3/README.md +111 -0
  113. package/skills/cloudflare/references/c3/api.md +71 -0
  114. package/skills/cloudflare/references/c3/configuration.md +81 -0
  115. package/skills/cloudflare/references/c3/gotchas.md +92 -0
  116. package/skills/cloudflare/references/c3/patterns.md +82 -0
  117. package/skills/cloudflare/references/cache-reserve/README.md +147 -0
  118. package/skills/cloudflare/references/cache-reserve/api.md +194 -0
  119. package/skills/cloudflare/references/cache-reserve/configuration.md +169 -0
  120. package/skills/cloudflare/references/cache-reserve/gotchas.md +132 -0
  121. package/skills/cloudflare/references/cache-reserve/patterns.md +197 -0
  122. package/skills/cloudflare/references/containers/README.md +85 -0
  123. package/skills/cloudflare/references/containers/api.md +187 -0
  124. package/skills/cloudflare/references/containers/configuration.md +188 -0
  125. package/skills/cloudflare/references/containers/gotchas.md +178 -0
  126. package/skills/cloudflare/references/containers/patterns.md +202 -0
  127. package/skills/cloudflare/references/cron-triggers/README.md +99 -0
  128. package/skills/cloudflare/references/cron-triggers/api.md +196 -0
  129. package/skills/cloudflare/references/cron-triggers/configuration.md +180 -0
  130. package/skills/cloudflare/references/cron-triggers/gotchas.md +199 -0
  131. package/skills/cloudflare/references/cron-triggers/patterns.md +190 -0
  132. package/skills/cloudflare/references/d1/README.md +133 -0
  133. package/skills/cloudflare/references/d1/api.md +196 -0
  134. package/skills/cloudflare/references/d1/configuration.md +191 -0
  135. package/skills/cloudflare/references/d1/gotchas.md +98 -0
  136. package/skills/cloudflare/references/d1/patterns.md +189 -0
  137. package/skills/cloudflare/references/ddos/README.md +41 -0
  138. package/skills/cloudflare/references/ddos/api.md +164 -0
  139. package/skills/cloudflare/references/ddos/configuration.md +93 -0
  140. package/skills/cloudflare/references/ddos/gotchas.md +107 -0
  141. package/skills/cloudflare/references/ddos/patterns.md +174 -0
  142. package/skills/cloudflare/references/do-storage/README.md +75 -0
  143. package/skills/cloudflare/references/do-storage/api.md +102 -0
  144. package/skills/cloudflare/references/do-storage/configuration.md +112 -0
  145. package/skills/cloudflare/references/do-storage/gotchas.md +150 -0
  146. package/skills/cloudflare/references/do-storage/patterns.md +194 -0
  147. package/skills/cloudflare/references/do-storage/testing.md +183 -0
  148. package/skills/cloudflare/references/durable-objects/README.md +185 -0
  149. package/skills/cloudflare/references/durable-objects/api.md +187 -0
  150. package/skills/cloudflare/references/durable-objects/configuration.md +160 -0
  151. package/skills/cloudflare/references/durable-objects/gotchas.md +197 -0
  152. package/skills/cloudflare/references/durable-objects/patterns.md +201 -0
  153. package/skills/cloudflare/references/email-routing/README.md +89 -0
  154. package/skills/cloudflare/references/email-routing/api.md +195 -0
  155. package/skills/cloudflare/references/email-routing/configuration.md +186 -0
  156. package/skills/cloudflare/references/email-routing/gotchas.md +196 -0
  157. package/skills/cloudflare/references/email-routing/patterns.md +229 -0
  158. package/skills/cloudflare/references/email-workers/README.md +151 -0
  159. package/skills/cloudflare/references/email-workers/api.md +237 -0
  160. package/skills/cloudflare/references/email-workers/configuration.md +112 -0
  161. package/skills/cloudflare/references/email-workers/gotchas.md +125 -0
  162. package/skills/cloudflare/references/email-workers/patterns.md +102 -0
  163. package/skills/cloudflare/references/flagship/README.md +59 -0
  164. package/skills/cloudflare/references/flagship/api.md +390 -0
  165. package/skills/cloudflare/references/flagship/configuration.md +202 -0
  166. package/skills/cloudflare/references/flagship/gotchas.md +178 -0
  167. package/skills/cloudflare/references/flagship/patterns.md +469 -0
  168. package/skills/cloudflare/references/graphql-api/README.md +147 -0
  169. package/skills/cloudflare/references/graphql-api/api.md +175 -0
  170. package/skills/cloudflare/references/graphql-api/configuration.md +118 -0
  171. package/skills/cloudflare/references/graphql-api/gotchas.md +110 -0
  172. package/skills/cloudflare/references/graphql-api/patterns.md +225 -0
  173. package/skills/cloudflare/references/hyperdrive/README.md +82 -0
  174. package/skills/cloudflare/references/hyperdrive/api.md +143 -0
  175. package/skills/cloudflare/references/hyperdrive/configuration.md +159 -0
  176. package/skills/cloudflare/references/hyperdrive/gotchas.md +77 -0
  177. package/skills/cloudflare/references/hyperdrive/patterns.md +190 -0
  178. package/skills/cloudflare/references/images/README.md +61 -0
  179. package/skills/cloudflare/references/images/api.md +96 -0
  180. package/skills/cloudflare/references/images/configuration.md +211 -0
  181. package/skills/cloudflare/references/images/gotchas.md +99 -0
  182. package/skills/cloudflare/references/images/patterns.md +115 -0
  183. package/skills/cloudflare/references/kv/README.md +89 -0
  184. package/skills/cloudflare/references/kv/api.md +160 -0
  185. package/skills/cloudflare/references/kv/configuration.md +144 -0
  186. package/skills/cloudflare/references/kv/gotchas.md +131 -0
  187. package/skills/cloudflare/references/kv/patterns.md +196 -0
  188. package/skills/cloudflare/references/miniflare/README.md +105 -0
  189. package/skills/cloudflare/references/miniflare/api.md +187 -0
  190. package/skills/cloudflare/references/miniflare/configuration.md +173 -0
  191. package/skills/cloudflare/references/miniflare/gotchas.md +160 -0
  192. package/skills/cloudflare/references/miniflare/patterns.md +181 -0
  193. package/skills/cloudflare/references/network-interconnect/README.md +99 -0
  194. package/skills/cloudflare/references/network-interconnect/api.md +199 -0
  195. package/skills/cloudflare/references/network-interconnect/configuration.md +114 -0
  196. package/skills/cloudflare/references/network-interconnect/gotchas.md +165 -0
  197. package/skills/cloudflare/references/network-interconnect/patterns.md +166 -0
  198. package/skills/cloudflare/references/observability/README.md +88 -0
  199. package/skills/cloudflare/references/observability/api.md +164 -0
  200. package/skills/cloudflare/references/observability/configuration.md +169 -0
  201. package/skills/cloudflare/references/observability/gotchas.md +115 -0
  202. package/skills/cloudflare/references/observability/patterns.md +105 -0
  203. package/skills/cloudflare/references/pages/README.md +88 -0
  204. package/skills/cloudflare/references/pages/api.md +204 -0
  205. package/skills/cloudflare/references/pages/configuration.md +201 -0
  206. package/skills/cloudflare/references/pages/gotchas.md +203 -0
  207. package/skills/cloudflare/references/pages/patterns.md +204 -0
  208. package/skills/cloudflare/references/pages-functions/README.md +98 -0
  209. package/skills/cloudflare/references/pages-functions/api.md +143 -0
  210. package/skills/cloudflare/references/pages-functions/configuration.md +122 -0
  211. package/skills/cloudflare/references/pages-functions/gotchas.md +94 -0
  212. package/skills/cloudflare/references/pages-functions/patterns.md +137 -0
  213. package/skills/cloudflare/references/pipelines/README.md +90 -0
  214. package/skills/cloudflare/references/pipelines/api.md +124 -0
  215. package/skills/cloudflare/references/pipelines/configuration.md +155 -0
  216. package/skills/cloudflare/references/pipelines/gotchas.md +58 -0
  217. package/skills/cloudflare/references/pipelines/patterns.md +130 -0
  218. package/skills/cloudflare/references/pulumi/README.md +100 -0
  219. package/skills/cloudflare/references/pulumi/api.md +200 -0
  220. package/skills/cloudflare/references/pulumi/configuration.md +198 -0
  221. package/skills/cloudflare/references/pulumi/gotchas.md +181 -0
  222. package/skills/cloudflare/references/pulumi/patterns.md +191 -0
  223. package/skills/cloudflare/references/queues/README.md +96 -0
  224. package/skills/cloudflare/references/queues/api.md +206 -0
  225. package/skills/cloudflare/references/queues/configuration.md +144 -0
  226. package/skills/cloudflare/references/queues/gotchas.md +206 -0
  227. package/skills/cloudflare/references/queues/patterns.md +220 -0
  228. package/skills/cloudflare/references/r2/README.md +95 -0
  229. package/skills/cloudflare/references/r2/api.md +200 -0
  230. package/skills/cloudflare/references/r2/configuration.md +165 -0
  231. package/skills/cloudflare/references/r2/gotchas.md +190 -0
  232. package/skills/cloudflare/references/r2/patterns.md +193 -0
  233. package/skills/cloudflare/references/r2-data-catalog/README.md +75 -0
  234. package/skills/cloudflare/references/r2-data-catalog/api.md +122 -0
  235. package/skills/cloudflare/references/r2-data-catalog/configuration.md +98 -0
  236. package/skills/cloudflare/references/r2-data-catalog/gotchas.md +55 -0
  237. package/skills/cloudflare/references/r2-data-catalog/patterns.md +122 -0
  238. package/skills/cloudflare/references/r2-sql/README.md +64 -0
  239. package/skills/cloudflare/references/r2-sql/api.md +121 -0
  240. package/skills/cloudflare/references/r2-sql/configuration.md +50 -0
  241. package/skills/cloudflare/references/r2-sql/gotchas.md +39 -0
  242. package/skills/cloudflare/references/r2-sql/patterns.md +118 -0
  243. package/skills/cloudflare/references/realtime-sfu/README.md +65 -0
  244. package/skills/cloudflare/references/realtime-sfu/api.md +158 -0
  245. package/skills/cloudflare/references/realtime-sfu/configuration.md +137 -0
  246. package/skills/cloudflare/references/realtime-sfu/gotchas.md +133 -0
  247. package/skills/cloudflare/references/realtime-sfu/patterns.md +174 -0
  248. package/skills/cloudflare/references/realtimekit/README.md +113 -0
  249. package/skills/cloudflare/references/realtimekit/api.md +212 -0
  250. package/skills/cloudflare/references/realtimekit/configuration.md +203 -0
  251. package/skills/cloudflare/references/realtimekit/gotchas.md +169 -0
  252. package/skills/cloudflare/references/realtimekit/patterns.md +223 -0
  253. package/skills/cloudflare/references/sandbox/README.md +96 -0
  254. package/skills/cloudflare/references/sandbox/api.md +198 -0
  255. package/skills/cloudflare/references/sandbox/configuration.md +143 -0
  256. package/skills/cloudflare/references/sandbox/gotchas.md +194 -0
  257. package/skills/cloudflare/references/sandbox/patterns.md +201 -0
  258. package/skills/cloudflare/references/secrets-store/README.md +74 -0
  259. package/skills/cloudflare/references/secrets-store/api.md +200 -0
  260. package/skills/cloudflare/references/secrets-store/configuration.md +185 -0
  261. package/skills/cloudflare/references/secrets-store/gotchas.md +97 -0
  262. package/skills/cloudflare/references/secrets-store/patterns.md +207 -0
  263. package/skills/cloudflare/references/smart-placement/README.md +138 -0
  264. package/skills/cloudflare/references/smart-placement/api.md +183 -0
  265. package/skills/cloudflare/references/smart-placement/configuration.md +196 -0
  266. package/skills/cloudflare/references/smart-placement/gotchas.md +174 -0
  267. package/skills/cloudflare/references/smart-placement/patterns.md +183 -0
  268. package/skills/cloudflare/references/snippets/README.md +68 -0
  269. package/skills/cloudflare/references/snippets/api.md +198 -0
  270. package/skills/cloudflare/references/snippets/configuration.md +227 -0
  271. package/skills/cloudflare/references/snippets/gotchas.md +86 -0
  272. package/skills/cloudflare/references/snippets/patterns.md +135 -0
  273. package/skills/cloudflare/references/spectrum/README.md +52 -0
  274. package/skills/cloudflare/references/spectrum/api.md +181 -0
  275. package/skills/cloudflare/references/spectrum/configuration.md +194 -0
  276. package/skills/cloudflare/references/spectrum/gotchas.md +145 -0
  277. package/skills/cloudflare/references/spectrum/patterns.md +196 -0
  278. package/skills/cloudflare/references/static-assets/README.md +65 -0
  279. package/skills/cloudflare/references/static-assets/api.md +199 -0
  280. package/skills/cloudflare/references/static-assets/configuration.md +186 -0
  281. package/skills/cloudflare/references/static-assets/gotchas.md +162 -0
  282. package/skills/cloudflare/references/static-assets/patterns.md +189 -0
  283. package/skills/cloudflare/references/stream/README.md +114 -0
  284. package/skills/cloudflare/references/stream/api-live.md +195 -0
  285. package/skills/cloudflare/references/stream/api.md +199 -0
  286. package/skills/cloudflare/references/stream/configuration.md +141 -0
  287. package/skills/cloudflare/references/stream/gotchas.md +130 -0
  288. package/skills/cloudflare/references/stream/patterns.md +184 -0
  289. package/skills/cloudflare/references/tail-workers/README.md +89 -0
  290. package/skills/cloudflare/references/tail-workers/api.md +200 -0
  291. package/skills/cloudflare/references/tail-workers/configuration.md +176 -0
  292. package/skills/cloudflare/references/tail-workers/gotchas.md +192 -0
  293. package/skills/cloudflare/references/tail-workers/patterns.md +180 -0
  294. package/skills/cloudflare/references/terraform/README.md +102 -0
  295. package/skills/cloudflare/references/terraform/api.md +178 -0
  296. package/skills/cloudflare/references/terraform/configuration.md +197 -0
  297. package/skills/cloudflare/references/terraform/gotchas.md +150 -0
  298. package/skills/cloudflare/references/terraform/patterns.md +174 -0
  299. package/skills/cloudflare/references/tunnel/README.md +129 -0
  300. package/skills/cloudflare/references/tunnel/api.md +193 -0
  301. package/skills/cloudflare/references/tunnel/configuration.md +157 -0
  302. package/skills/cloudflare/references/tunnel/gotchas.md +147 -0
  303. package/skills/cloudflare/references/tunnel/networking.md +168 -0
  304. package/skills/cloudflare/references/tunnel/patterns.md +192 -0
  305. package/skills/cloudflare/references/turn/README.md +82 -0
  306. package/skills/cloudflare/references/turn/api.md +239 -0
  307. package/skills/cloudflare/references/turn/configuration.md +179 -0
  308. package/skills/cloudflare/references/turn/gotchas.md +231 -0
  309. package/skills/cloudflare/references/turn/patterns.md +213 -0
  310. package/skills/cloudflare/references/turnstile/README.md +99 -0
  311. package/skills/cloudflare/references/turnstile/api.md +240 -0
  312. package/skills/cloudflare/references/turnstile/configuration.md +222 -0
  313. package/skills/cloudflare/references/turnstile/gotchas.md +218 -0
  314. package/skills/cloudflare/references/turnstile/patterns.md +193 -0
  315. package/skills/cloudflare/references/vectorize/README.md +133 -0
  316. package/skills/cloudflare/references/vectorize/api.md +88 -0
  317. package/skills/cloudflare/references/vectorize/configuration.md +88 -0
  318. package/skills/cloudflare/references/vectorize/gotchas.md +76 -0
  319. package/skills/cloudflare/references/vectorize/patterns.md +90 -0
  320. package/skills/cloudflare/references/waf/README.md +113 -0
  321. package/skills/cloudflare/references/waf/api.md +202 -0
  322. package/skills/cloudflare/references/waf/configuration.md +203 -0
  323. package/skills/cloudflare/references/waf/gotchas.md +204 -0
  324. package/skills/cloudflare/references/waf/patterns.md +197 -0
  325. package/skills/cloudflare/references/web-analytics/README.md +141 -0
  326. package/skills/cloudflare/references/web-analytics/configuration.md +76 -0
  327. package/skills/cloudflare/references/web-analytics/gotchas.md +82 -0
  328. package/skills/cloudflare/references/web-analytics/integration.md +60 -0
  329. package/skills/cloudflare/references/web-analytics/patterns.md +91 -0
  330. package/skills/cloudflare/references/workerd/README.md +78 -0
  331. package/skills/cloudflare/references/workerd/api.md +185 -0
  332. package/skills/cloudflare/references/workerd/configuration.md +183 -0
  333. package/skills/cloudflare/references/workerd/gotchas.md +139 -0
  334. package/skills/cloudflare/references/workerd/patterns.md +192 -0
  335. package/skills/cloudflare/references/workers/README.md +108 -0
  336. package/skills/cloudflare/references/workers/api.md +195 -0
  337. package/skills/cloudflare/references/workers/configuration.md +185 -0
  338. package/skills/cloudflare/references/workers/frameworks.md +197 -0
  339. package/skills/cloudflare/references/workers/gotchas.md +137 -0
  340. package/skills/cloudflare/references/workers/patterns.md +198 -0
  341. package/skills/cloudflare/references/workers-ai/README.md +197 -0
  342. package/skills/cloudflare/references/workers-ai/api.md +112 -0
  343. package/skills/cloudflare/references/workers-ai/configuration.md +97 -0
  344. package/skills/cloudflare/references/workers-ai/gotchas.md +114 -0
  345. package/skills/cloudflare/references/workers-ai/patterns.md +120 -0
  346. package/skills/cloudflare/references/workers-for-platforms/README.md +89 -0
  347. package/skills/cloudflare/references/workers-for-platforms/api.md +196 -0
  348. package/skills/cloudflare/references/workers-for-platforms/configuration.md +167 -0
  349. package/skills/cloudflare/references/workers-for-platforms/gotchas.md +134 -0
  350. package/skills/cloudflare/references/workers-for-platforms/patterns.md +188 -0
  351. package/skills/cloudflare/references/workers-playground/README.md +127 -0
  352. package/skills/cloudflare/references/workers-playground/api.md +101 -0
  353. package/skills/cloudflare/references/workers-playground/configuration.md +163 -0
  354. package/skills/cloudflare/references/workers-playground/gotchas.md +88 -0
  355. package/skills/cloudflare/references/workers-playground/patterns.md +132 -0
  356. package/skills/cloudflare/references/workers-vpc/README.md +127 -0
  357. package/skills/cloudflare/references/workers-vpc/api.md +202 -0
  358. package/skills/cloudflare/references/workers-vpc/configuration.md +147 -0
  359. package/skills/cloudflare/references/workers-vpc/gotchas.md +167 -0
  360. package/skills/cloudflare/references/workers-vpc/patterns.md +209 -0
  361. package/skills/cloudflare/references/workflows/README.md +77 -0
  362. package/skills/cloudflare/references/workflows/api.md +218 -0
  363. package/skills/cloudflare/references/workflows/configuration.md +152 -0
  364. package/skills/cloudflare/references/workflows/gotchas.md +90 -0
  365. package/skills/cloudflare/references/workflows/patterns.md +175 -0
  366. package/skills/cloudflare/references/wrangler/README.md +135 -0
  367. package/skills/cloudflare/references/wrangler/api.md +188 -0
  368. package/skills/cloudflare/references/wrangler/configuration.md +197 -0
  369. package/skills/cloudflare/references/wrangler/gotchas.md +197 -0
  370. package/skills/cloudflare/references/wrangler/patterns.md +209 -0
  371. package/skills/cloudflare/references/zaraz/IMPLEMENTATION_SUMMARY.md +121 -0
  372. package/skills/cloudflare/references/zaraz/README.md +111 -0
  373. package/skills/cloudflare/references/zaraz/api.md +112 -0
  374. package/skills/cloudflare/references/zaraz/configuration.md +90 -0
  375. package/skills/cloudflare/references/zaraz/gotchas.md +81 -0
  376. package/skills/cloudflare/references/zaraz/patterns.md +74 -0
  377. package/skills/cloudflare-email-service/SKILL.md +103 -0
  378. package/skills/cloudflare-email-service/references/cli-and-mcp.md +125 -0
  379. package/skills/cloudflare-email-service/references/deliverability.md +285 -0
  380. package/skills/cloudflare-email-service/references/rest-api.md +184 -0
  381. package/skills/cloudflare-email-service/references/routing.md +199 -0
  382. package/skills/cloudflare-email-service/references/sending.md +252 -0
  383. package/skills/cloudflare-one/SKILL.md +176 -0
  384. package/skills/cloudflare-one-migrations/SKILL.md +110 -0
  385. package/skills/durable-objects/SKILL.md +186 -0
  386. package/skills/durable-objects/references/rules.md +295 -0
  387. package/skills/durable-objects/references/testing.md +264 -0
  388. package/skills/durable-objects/references/workers.md +346 -0
  389. package/skills/sandbox-migrate-to-next/SKILL.md +185 -0
  390. package/skills/sandbox-next/SKILL.md +91 -0
  391. package/skills/sandbox-next/references/api-quick-ref.md +94 -0
  392. package/skills/sandbox-next/references/examples.md +15 -0
  393. package/skills/sandbox-stable/SKILL.md +110 -0
  394. package/skills/turnstile-spin/README.md +51 -0
  395. package/skills/turnstile-spin/SKILL.md +330 -0
  396. package/skills/turnstile-spin/references/astro.md +199 -0
  397. package/skills/turnstile-spin/references/hugo.md +114 -0
  398. package/skills/turnstile-spin/references/nextjs-app.md +261 -0
  399. package/skills/turnstile-spin/references/nextjs-pages.md +82 -0
  400. package/skills/turnstile-spin/references/sveltekit.md +191 -0
  401. package/skills/turnstile-spin/references/vanilla-html.md +157 -0
  402. package/skills/turnstile-spin/scripts/auth-probe.sh +285 -0
  403. package/skills/turnstile-spin/scripts/persist-skill.sh +114 -0
  404. package/skills/turnstile-spin/scripts/validate.sh +137 -0
  405. package/skills/turnstile-spin/scripts/widget-create.sh +121 -0
  406. package/skills/turnstile-spin/tests/validation.md +62 -0
  407. package/skills/workers-best-practices/SKILL.md +127 -0
  408. package/skills/workers-best-practices/references/review.md +174 -0
  409. package/skills/workers-best-practices/references/rules.md +467 -0
  410. 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