turnstile-spin
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseTurnstile Spin skill
Turnstile Spin 技能
Turns the prompt "set up Turnstile" into a working end-to-end integration: a widget, a deployed managed siteverify Worker, frontend snippets at every chosen insertion point, and a real validation pass before reporting success.
You are the agent. Run the wizard below by invoking the scripts under and branching on their JSON output. The scripts hold the deterministic logic (API calls, retry/error handling); your job is orchestration, codebase reading, confirmation, and the frontend edits.
scripts/Canonical instructions live at . If the docs page and this file disagree, trust the docs page.
developers.cloudflare.com/turnstile/spin将“设置Turnstile”的需求转化为可运行的端到端集成:一个小部件、一个已部署的托管siteverify Worker、在所有选定插入点的前端代码片段,以及在报告成功前的真实验证流程。
你是执行该任务的Agent。通过调用下的脚本并根据其JSON输出进行分支,来运行以下向导。脚本包含确定性逻辑(API调用、重试/错误处理);你的工作是编排流程、读取代码库、确认操作以及编辑前端代码。
scripts/标准说明位于。如果本文档与该页面内容不一致,以官方文档为准。
developers.cloudflare.com/turnstile/spinWhen to load this skill
何时加载此技能
Load when the user's prompt mentions any of:
- "Turnstile", "CAPTCHA", "bot protection"
- "siteverify", "cf-turnstile-response"
- "protect this form", "stop bot signups", "spam signups"
- A specific signup, login, or contact form combined with "Cloudflare" or "bot"
Do not load for unrelated Cloudflare tasks (Workers, Pages, R2, etc.) unless Turnstile is also mentioned.
当用户的提示包含以下任意内容时加载:
- "Turnstile"、"CAPTCHA"、"bot protection"
- "siteverify"、"cf-turnstile-response"
- "protect this form"、"stop bot signups"、"spam signups"
- 特定的注册、登录或联系表单,同时提及"Cloudflare"或"bot"
除非同时提到Turnstile,否则不要为无关的Cloudflare任务(Workers、Pages、R2等)加载此技能。
Conversation flow
对话流程
The user pasted the prompt. You are in a multi-step dialog. Detect what you can, ask only when you have to, confirm before every irreversible step. Each numbered moment is one agent message. Items marked [wait for user] require a user response.
-
Brief acknowledge. One sentence: "I'll run Turnstile setup end to end. That's: check auth, scan the codebase, create the widget, deploy the Worker, wire the frontend, validate. Proceed?" [wait for user] Do NOT present a plan yet. Auth + scan come first.
-
Wrangler check.. If missing, ask once: "Install wrangler with
npx wrangler --version(Node project) ornpm install --save-dev wrangler(other)? Proceed?" [wait for user] If install is blocked entirely (corporate policy, blocked npm), fall back to driving Steps 4-5 vianpm install -g wrangleragainstcurl.api.cloudflare.com/client/v4/ -
Auth + scope probe (FIRST irreversible action). Run. Branch on
scripts/auth-probe.sh:status- : continue to Step 4. The script already picked the account (single-account token, or one matching
ok).$CLOUDFLARE_ACCOUNT_ID - ,
missing_token, ormissing_scope: ask the user to create a token at https://dash.cloudflare.com/profile/api-tokens → Custom token → permissionsmissing_workers_scopeandAccount.Turnstile:Edit→ include the target account in Account Resources. Do NOT direct them toAccount.Workers Scripts:Edit. Its OAuth scope doesn't includewrangler loginorAccount.Turnstile:Edit. Offer three ways to hand the token over, cleanest first:Account.Workers Scripts:Edit- Export + relaunch (token never enters chat): then restart the agent from that terminal.
export CLOUDFLARE_API_TOKEN=<token> - Save to file (token in file with user-only perms, not in chat): , then read with
umask 077 && printf '%s' '<token>' > ~/.cf-turnstile-token.TOKEN=$(cat ~/.cf-turnstile-token) - Paste in chat (fastest, but token lands in conversation log; user should rotate it after if the log is ever shared).
If the user picks option 3 (paste in chat), you can use the wait to run Steps 5, 6, 7 (Domain, Codebase scan, Insertion plan). Options 1 and 2 will restart your session, so do not pre-fetch state in those cases. When auth is established, re-run , then continue to Step 8.
auth-probe.sh
- Export + relaunch (token never enters chat):
- : the token covers more than one account and
multiple_accountsis unset. Present the numbered$CLOUDFLARE_ACCOUNT_IDlist. [wait for user] Then exportaccountsand re-runCLOUDFLARE_ACCOUNT_ID=<chosen>.auth-probe.sh - :
account_mismatchis set but isn't one of the token's accounts. Show the$CLOUDFLARE_ACCOUNT_IDlist and ask the user to eitheraccountsor set it to one of those IDs.unset CLOUDFLARE_ACCOUNT_ID
-
Account selection. Ifreturned
auth-probe.shafter aokround-trip, this is already done. Otherwise the script picked the single account silently and you continue to Step 5.multiple_accounts -
Domain. Always includeand
localhost. For production, scan127.0.0.1package.json,homepage,wrangler.toml,README.md, git remote. Confirm: "I'll register forAGENTS.md,localhost, and127.0.0.1. OK?" [wait for user] If no production domain is found, ask.<domain> -
Codebase scan. Detect framework + insertion candidates silently.
-
Insertion plan. Show the candidate list with/
[recommended]markers; ask the user to confirm (numbers, "all", "recommended", or a list). [wait for user] If an existing CAPTCHA was detected, present a migration plan instead (see "Migrating from another CAPTCHA").[skip by default] -
Widget creation. Run. Report the sitekey. The secret stays in env; never write it to disk.
scripts/widget-create.sh --account-id <id> --name <name> --domains <list> --mode managed -
Worker deploy. Runwith
scripts/worker-deploy.sh --name turnstile-siteverify-<project-slug>exported. Report the Worker URL onWIDGET_SECRET. Onstatus: ok, the Worker deployed butset_secret_failedis not set on it; surface the error, then retry withTURNSTILE_SECRET_KEYbefore running validation.echo "$WIDGET_SECRET" | npx wrangler secret put TURNSTILE_SECRET_KEY --name <returned worker_name> -
Frontend edits. State the contract: "I'll add the widget + gate the existing submit handler on. The existing handler logic stays the same." Ask "yes" / "show". [wait for user] If "show", print unified diffs and ask again. Do NOT propose alternate behavior (mail delivery, custom backends).
success === true -
Validation. Run. Report each check as it passes. If any fails, surface the error and stop. [wait for user if anything fails]
scripts/validate.sh -
Persist skill. Ask: "Save the Spin skill toso I can reuse it on follow-up tasks?" Default yes. [wait for user] Then run
.claude/skills/turnstile-spin/SKILL.md.scripts/persist-skill.sh --path <agent-specific-path> -
Final report. Print the structured summary: what was created, what was validated, what to do next.
用户已提交请求。你将进入多步骤对话流程。尽可能自动检测信息,仅在必要时提问,在执行每一个不可逆步骤前确认。每个编号环节对应一条Agent消息。标记为**[等待用户回复]**的项需要用户回应。
-
简要确认。用一句话说明:"我将端到端运行Turnstile设置流程,步骤包括:检查权限、扫描代码库、创建小部件、部署Worker、对接前端、验证。是否继续?" [等待用户回复] 此时不要展示详细计划,先执行权限检查和代码扫描。
-
Wrangler检查。执行。如果未安装,询问一次:"是否安装wrangler?Node项目请用
npx wrangler --version,其他环境请用npm install --save-dev wrangler?是否继续?" [等待用户回复] 如果完全无法安装(如公司政策限制、npm被屏蔽),则改用npm install -g wrangler调用curl来完成步骤4-5。api.cloudflare.com/client/v4/ -
权限验证与范围探测(第一个不可逆操作)。运行。根据
scripts/auth-probe.sh分支处理:status- :继续步骤4。脚本已自动选择账户(单账户令牌,或匹配
ok的账户)。$CLOUDFLARE_ACCOUNT_ID - 、
missing_token或missing_scope:请用户在https://dash.cloudflare.com/profile/api-tokens 创建自定义令牌,权限需包含missing_workers_scope和Account.Turnstile:Edit,并在账户资源中包含目标账户。不要引导用户使用Account.Workers Scripts:Edit,其OAuth权限不包含wrangler login或Account.Turnstile:Edit。提供三种安全的令牌提交方式,按优先级排序:Account.Workers Scripts:Edit- 导出后重启(令牌不会进入聊天记录):执行,然后从该终端重启Agent。
export CLOUDFLARE_API_TOKEN=<token> - 保存到文件(令牌存储在仅用户可访问的文件中,不会进入聊天记录):执行,之后通过
umask 077 && printf '%s' '<token>' > ~/.cf-turnstile-token读取。TOKEN=$(cat ~/.cf-turnstile-token) - 粘贴到聊天框(最快,但令牌会进入对话日志;如果日志被分享,用户应在之后轮换令牌)。
如果用户选择选项3(粘贴到聊天框),你可以利用等待时间执行步骤5、6、7(域名确认、代码库扫描、插入计划)。选项1和2会重启会话,因此不要提前获取状态。权限验证通过后,重新运行,然后继续步骤8。
auth-probe.sh
- 导出后重启(令牌不会进入聊天记录):执行
- :令牌覆盖多个账户且
multiple_accounts未设置。展示编号的$CLOUDFLARE_ACCOUNT_ID列表。[等待用户回复] 然后执行accounts并重新运行export CLOUDFLARE_ACCOUNT_ID=<chosen>。auth-probe.sh - :
account_mismatch已设置,但不在令牌覆盖的账户范围内。展示$CLOUDFLARE_ACCOUNT_ID列表,询问用户是accounts还是将其设置为列表中的某个ID。unset CLOUDFLARE_ACCOUNT_ID
-
账户选择。如果经过流程后
multiple_accounts返回auth-probe.sh,则此步骤已完成。否则脚本已自动选择唯一账户,直接继续步骤5。ok -
域名确认。始终包含和
localhost。对于生产环境,扫描127.0.0.1的package.json、homepage、wrangler.toml、README.md、git远程仓库。确认:"我将为AGENTS.md、localhost和127.0.0.1注册。是否确认?" [等待用户回复] 如果未找到生产域名,询问用户。<domain> -
代码库扫描。自动检测框架和候选插入点。
-
插入计划。展示候选列表,标记/
[推荐];请用户确认(可选择编号、"全部"、"推荐"或自定义列表)。[等待用户回复] 如果检测到已存在的CAPTCHA,则改为展示迁移计划(见"从其他CAPTCHA迁移")。[默认跳过] -
创建小部件。运行。报告sitekey。密钥仅保留在环境变量中;绝不要写入磁盘。
scripts/widget-create.sh --account-id <id> --name <name> --domains <list> --mode managed -
部署Worker。导出后运行
WIDGET_SECRET。当scripts/worker-deploy.sh --name turnstile-siteverify-<project-slug>时报告Worker URL。如果status: ok,说明Worker已部署但未设置set_secret_failed;显示错误,然后通过TURNSTILE_SECRET_KEY重试,之后再运行验证。echo "$WIDGET_SECRET" | npx wrangler secret put TURNSTILE_SECRET_KEY --name <returned worker_name> -
前端编辑。说明规则:"我将添加小部件,并在时触发现有提交处理程序。现有处理程序逻辑保持不变。" 询问用户选择"确认" / "查看差异"。[等待用户回复] 如果选择"查看差异",打印统一差异并再次询问。不要提议替代方案(如邮件发送、自定义后端)。
success === true -
验证。运行。每通过一项检查就进行报告。如果任何检查失败,显示错误并停止。[如果失败则等待用户回复]
scripts/validate.sh -
保存技能。询问:"是否将Spin技能保存到,以便后续任务复用?" 默认选择是。[等待用户回复] 然后运行
.claude/skills/turnstile-spin/SKILL.md。scripts/persist-skill.sh --path <agent-specific-path> -
最终报告。打印结构化摘要:已创建的内容、已验证的项以及下一步操作建议。
Things you must NOT do
绝对禁止的操作
- Do not write the Turnstile secret to disk. Only pass it via stdin to (the worker-deploy.sh script handles this).
wrangler secret put - Do not skip validation.
- Do not overwrite files without showing a diff.
- Do not deploy a Worker to a different account than the widget was created in.
- Do not call siteverify from the browser. Always: browser → user's Worker → siteverify.
- Do not use or install global packages without asking.
sudo
- 不要将Turnstile密钥写入磁盘。仅通过标准输入传递给(worker-deploy.sh脚本会处理此操作)。
wrangler secret put - 不要跳过验证步骤。
- 不要在未展示差异的情况下覆盖文件。
- 不要将Worker部署到与小部件创建账户不同的账户。
- 不要从浏览器调用siteverify。必须遵循:浏览器 → 用户的Worker → siteverify。
- 不要使用或未经询问就安装全局包。
sudo
Hard scope boundary: DO NOT ask the user about
严格范围边界:不要询问用户以下内容
Spin validates the Turnstile token via a managed Worker before the user's existing form handler runs. Everything else is out of scope:
- Email / SMS / notification delivery. Leave the existing submit handler alone (just gate it on ). Don't propose Resend, Mailchannels, SMTP, mailto.
success === true - Custom Worker code. Deploy the stock Worker template bundled at . Don't write a new Worker. Don't add features (rate limiting, custom routing, third-party integrations).
templates/worker/ - Database / payment / OAuth / form persistence. Out of scope.
- Frontend framework migration, refactoring, or styling. Edit only what's needed.
- reCAPTCHA v3 score thresholds. Turnstile returns .
success: true/false - Pre-clearance-only setups. If , siteverify is optional and Spin doesn't apply. Redirect the user and exit.
clearance_level !== no_clearance
Spin通过托管Worker在用户现有表单处理程序运行前验证Turnstile令牌。除此之外的所有内容均超出范围:
- 邮件/SMS/通知发送。保留现有提交处理程序(仅在时触发)。不要提议Resend、Mailchannels、SMTP、mailto等方案。
success === true - 自定义Worker代码。部署中的标准Worker模板。不要编写新的Worker,不要添加额外功能(如速率限制、自定义路由、第三方集成)。
templates/worker/ - 数据库/支付/OAuth/表单持久化。超出范围。
- 前端框架迁移、重构或样式修改。仅编辑必要内容。
- reCAPTCHA v3分数阈值。Turnstile仅返回。
success: true/false - 仅预授权设置。如果,siteverify为可选操作,Spin不适用。引导用户查看相关文档并退出。
clearance_level !== no_clearance
Recovery flow: respect existing widget configuration
恢复流程:兼容现有小部件配置
If the user tells you they already have a Turnstile widget set up and want to wire siteverify to it without rotating the sitekey (e.g. "I have a sitekey but siteverify never worked", "set up Spin against my existing widget "):
<sitekey>- Skip Step 8 (widget creation). The sitekey already exists; get it from the user.
- Fetch the widget metadata via . Branch on
scripts/fetch-secret.sh --account-id <id> --sitekey <key>:status- : read
ok,secret, andclearance_levelfrom the response. Confirmdomainsincludes the user's production hostname; if not, surface the gap before proceeding.domains - : tell the user to add
missing_read_scopeto the token, or fall back to asking them to paste the secret. In the paste path, you do not haveAccount.Turnstile:Readorclearance_level; ask the user to confirm both.domains
- Check from the response (or the user's answer):
clearance_level- : standard recovery (deploy Worker, wire siteverify).
no_clearance - anything else: ask whether they want siteverify on top of pre-clearance, or exit per the scope boundary.
- Continue from Step 9 (Worker deploy). Site key does not change. Dashboard's column flips from
DeploymenttoManualon the first request carryingSpin.data-action="turnstile-spin-v1" - Never recreate the widget to get a fresh secret. That breaks the existing sitekey everywhere it's deployed.
如果用户告知已设置Turnstile小部件,希望对接siteverify但不更换sitekey(例如:"我有sitekey但siteverify无法工作"、"针对我现有的小部件设置Spin"):
<sitekey>- 跳过步骤8(创建小部件)。sitekey已存在,向用户获取。
- 通过获取小部件元数据。根据
scripts/fetch-secret.sh --account-id <id> --sitekey <key>分支处理:status- :从响应中读取
ok、secret和clearance_level。确认domains包含用户的生产主机名;如果不包含,在继续前告知用户。domains - :告知用户为令牌添加
missing_read_scope权限,或改为让用户粘贴密钥。如果用户选择粘贴密钥,你将无法获取Account.Turnstile:Read和clearance_level,需询问用户确认这两项信息。domains
- 检查响应(或用户回答)中的:
clearance_level- :执行标准恢复流程(部署Worker、对接siteverify)。
no_clearance - 其他值:询问用户是否要在预授权基础上添加siteverify,或根据范围边界退出。
- 从步骤9(部署Worker)继续。sitekey保持不变。当第一个携带的请求发送后,控制台的
data-action="turnstile-spin-v1"列会从Deployment变为Manual。Spin - 绝不要重新创建小部件来获取新密钥。这会破坏所有已部署该sitekey的现有集成。
The frontend-edit contract
前端编辑规则
When wiring an existing form to the Worker (Step 10), the contract is: gate, don't replace. The user's existing submit handler keeps doing what it did. Spin only adds a validation step before it.
js
form.addEventListener("submit", async (e) => {
e.preventDefault();
const token = /* read cf-turnstile-response */;
const result = await fetch(WORKER_URL, { method: 'POST', body: JSON.stringify({ token }) });
const data = await result.json();
if (!data.success) return; // show failure
// existing handler logic runs here, unchanged
});If the existing handler was a stub, Spin leaves it a stub gated on success. The user can replace the stub later; that's not Spin's job.
将现有表单对接Worker时(步骤10),规则是:拦截触发,而非替换。用户的现有提交处理程序保持原有功能。Spin仅在其之前添加验证步骤。
js
form.addEventListener("submit", async (e) => {
e.preventDefault();
const token = /* 读取cf-turnstile-response */;
const result = await fetch(WORKER_URL, { method: 'POST', body: JSON.stringify({ token }) });
const data = await result.json();
if (!data.success) return; // 显示失败信息
// 现有处理程序逻辑在此处运行,保持不变
});如果现有处理程序是占位代码,Spin会将其保留为受验证拦截的占位代码。用户可在之后替换占位代码;这不属于Spin的职责范围。
Migrating from another CAPTCHA
从其他CAPTCHA迁移
During the Step 6 codebase scan, also look for existing reCAPTCHA or hCaptcha. If found, switch Step 7 to a migration plan.
Detection signals:
- reCAPTCHA: ,
https://www.google.com/recaptcha/api.js,class="g-recaptcha", backend POST todata-sitekey="6L..."/recaptcha/api/siteverify - hCaptcha: ,
https://js.hcaptcha.com/1/api.js, backend POST toclass="h-captcha"https://hcaptcha.com/siteverify
Substitution:
- Replace script tags with (
https://challenges.cloudflare.com/turnstile/v0/api.js).async defer - Replace /
class="g-recaptcha"divs withclass="h-captcha", updateclass="cf-turnstile"to the new Turnstile sitekey, adddata-sitekey.data-action="turnstile-spin-v1" - Token field changes from to
g-recaptcha-response.cf-turnstile-response - Backend siteverify URL points at the Spin-deployed Worker. Drop /
RECAPTCHA_SECRETenv vars.HCAPTCHA_SECRET
Edge cases to surface to the user:
- reCAPTCHA v3 score thresholds. Turnstile has no score. Tell the user explicitly that migrated code will reject on .
success === false - reCAPTCHA Enterprise. Don't auto-migrate. Point at developers.cloudflare.com/turnstile/migration/recaptcha/.
- Custom values. Preserve any custom action the user passed to
action=asgrecaptcha.executeon the widget. Usedata-actiononly when no custom action exists.turnstile-spin-v1
在步骤6的代码库扫描中,同时查找现有的reCAPTCHA或hCaptcha。如果找到,将步骤7切换为迁移计划。
检测信号:
- reCAPTCHA:、
https://www.google.com/recaptcha/api.js、class="g-recaptcha"、后端POST请求到data-sitekey="6L..."/recaptcha/api/siteverify - hCaptcha:、
https://js.hcaptcha.com/1/api.js、后端POST请求到class="h-captcha"https://hcaptcha.com/siteverify
替换规则:
- 将脚本标签替换为(保留
https://challenges.cloudflare.com/turnstile/v0/api.js)。async defer - 将/
class="g-recaptcha"的div替换为class="h-captcha",将class="cf-turnstile"更新为新的Turnstile sitekey,添加data-sitekey。data-action="turnstile-spin-v1" - 令牌字段从改为
g-recaptcha-response。cf-turnstile-response - 后端siteverify URL指向Spin部署的Worker。删除/
RECAPTCHA_SECRET环境变量。HCAPTCHA_SECRET
需要告知用户的边缘情况:
- reCAPTCHA v3分数阈值。Turnstile没有分数。明确告知用户迁移后的代码会在时拒绝请求。
success === false - reCAPTCHA Enterprise。不要自动迁移。引导用户查看developers.cloudflare.com/turnstile/migration/recaptcha/。
- 自定义值。保留用户传递给
action=的任何自定义action,将其作为小部件的grecaptcha.execute。仅当没有自定义action时才使用data-action。turnstile-spin-v1
Edge cases
边缘情况处理
| Situation | Action |
|---|---|
| Install path: |
| Multiple Cloudflare accounts | |
| Cloudflare Pages project | Deploy the managed Worker anyway, OR suggest the Pages Plugin |
| Update widget domains via PUT, not PATCH (PATCH returns |
| Worker name conflict | |
| Token expired mid-flow | Stop, re-run |
Step 11 returns | Secret didn't propagate. Re-set: |
| Worker is deployed but secret is not set. Re-run only the secret-put using the returned |
| 场景 | 操作 |
|---|---|
| 安装方式:Node项目用 |
| 多个Cloudflare账户 | |
| Cloudflare Pages项目 | 仍部署托管Worker,或建议使用Pages Plugin |
| 通过PUT而非PATCH更新小部件域名(PATCH会返回 |
| Worker名称冲突 | |
| 令牌在流程中过期 | 停止流程,重新运行 |
步骤11返回 | 密钥未同步。重新设置: |
| Worker已部署但未设置密钥。使用返回的 |
Telemetry marker
遥测标记
Every snippet you write must include . Account-level aggregate telemetry, never per-user. Cloudflare uses it to measure activation. If the user removes the attribute, the integration still works; only the analytics segmentation is lost.
data-action="turnstile-spin-v1"你编写的所有代码片段必须包含。这是账户级别的聚合遥测,不会收集用户个人信息。Cloudflare用它来衡量激活情况。如果用户移除该属性,集成仍可正常工作;仅会丢失分析细分数据。
data-action="turnstile-spin-v1"Do not
禁止操作
- Do not write the secret to disk.
- Do not skip validation (Step 11).
- Do not propose features outside the wizard (custom Worker code, custom domains, advanced WAF rules) unless asked.
- Do not call siteverify from the browser.
- Do not deploy the Worker into a different account than the widget.
- 不要将密钥写入磁盘。
- 不要跳过验证步骤(步骤11)。
- 除非被询问,否则不要提议向导之外的功能(自定义Worker代码、自定义域名、高级WAF规则)。
- 不要从浏览器调用siteverify。
- 不要将Worker部署到与小部件创建账户不同的账户。