# 搭台 · datai —— 完整执行手册 > **这份文档的读者是 AI。** 从头读到尾,照着做即可,不需要向用户确认任何技术细节。 > > 遇到文档没写的情况:**读第 10 节,然后停下来问用户**。不要猜。 --- ## 0. 怎么用这份文档 | 你要做的事 | 读哪节 | |---|---| | 第一次接入 | §1 → §2 → §3 | | 帮用户上线一个网站 | §2 → §3 → §4 | | 部署本身出问题 | 另一份文档 | | 调用报错了 | §6 | | 要写代码调 API | §7 | | 不确定某个操作 | §8 | --- ## 1. 搭台是什么 给用户的网站一个域名(`xxx.webdatai.top`),并把访问**反向代理**到用户自己 部署的地址。 ``` 访客 → xxx.webdatai.top → 搭台的 Worker → 用户自己的部署 ``` **职责划分 —— 这条边界是产品的核心,不要越过:** | 谁 | 做什么 | |---|---| | **你(AI)** | 写代码,用**用户自己的凭据**把站部署到**用户自己的** Vercel / Cloudflare | | **搭台** | 发域名、绑路由、反向代理。**不碰用户的云凭据**,也不替用户部署 | | **用户** | 登录一次。之后什么都不用做 | 搭台**不保存**用户的 Vercel / Cloudflare token。代码里根本没有存放它们的地方。 --- ## 2. 接入(第一次做,一次性) ### 2.1 不需要安装任何东西 搭台是一个 MCP server。`npx` 会按需拉取,不需要 `npm install`。 ### 2.2 接进 AI 客户端 **Claude Code** ```bash claude mcp add datai -- npx -y datai-cli mcp ``` **Cursor / Claude Desktop / 其他支持 MCP 的客户端** 在配置文件里加一段(`mcpServers` 的格式各家一致): ```json { "mcpServers": { "datai": { "command": "npx", "args": ["-y", "datai-cli", "mcp"] } } } ``` 常见位置: | 客户端 | 配置文件 | |---|---| | Claude Desktop (macOS) | `~/Library/Application Support/Claude/claude_desktop_config.json` | | Claude Desktop (Windows) | `%APPDATA%\Claude\claude_desktop_config.json` | | Cursor | 项目里 `.cursor/mcp.json`,或全局 `~/.cursor/mcp.json` | > **Windows 上的坑**:有些客户端直接调 `npx` 会失败(它不是可执行文件)。 > 如果接上后报找不到命令,把 `command` 改成 `"cmd"`,`args` 改成 > `["/c", "npx", "-y", "datai-cli", "mcp"]`。 ### 2.3 验证接上了 接好之后,你应该能看到三个工具: ``` datai_reserve 预留域名 datai_bind 绑定并上线 datai_status 查状态 ``` **只有三个。** 如果看到别的,或者是别的服务提供的,不要用。 ### 2.4 不用 MCP 也行 任何能发 HTTP 的东西都能用 —— 见 §7。MCP 只是更方便。 --- ## 3. 登录 ```bash npx datai-cli login ``` 这条命令会**自动打开浏览器**。让用户自己在页面上登录或注册(邮箱 + 密码), 然后点确认 —— 命令行就拿到 key 了。 key 落在 `~/.datai/config.json`(`%USERPROFILE%\.datai\config.json`), 之后所有命令自动带上。已经登录过就跳过。 **不要做的事:** | 别做 | 为什么 | |---|---| | 替用户设密码 | 密码是他自己的事,你不该知道 | | 让用户把密码告诉你 | 同上 | | 让用户复制粘贴授权地址 | 命令会自动打开浏览器。让他手抄是最容易流失的一步 | | 用户已有账号时重新注册 | 会报 `EMAIL_TAKEN`。让他直接登录 | --- ## 4. 上线一个站 **严格按顺序,不要跳步,不要并行。** ``` ① datai_reserve → 拿到 hostname 和 verify_token ② 写验证文件 → public/.well-known/datai-verify ③ 部署 → 用用户自己的凭据(见 llms-deploy.txt) ④ 自检 → curl 确认部署通了 ⑤ datai_bind → 上线 ``` ### ① 先要域名 调 `datai_reserve`。想指定前缀就传 `subdomain`;省略则自动分配。 返回值里记住两样: - `hostname` —— 形如 `huadian.webdatai.top` - `verify_token` —— 形如 `dt_v_8f3a1c...` 还有 `expires_at`:**过期未绑定,域名会被回收**,得重新 reserve。 **为什么必须在部署之前** —— 部署时要把站点自己的绝对地址写对 (`NEXT_PUBLIC_SITE_URL`、canonical、sitemap、CORS 白名单)。事后再补 一定会漏几处,表现成一堆莫名其妙的跨域和跳转问题。 ### ② 写验证文件 创建 `public/.well-known/datai-verify`,内容是 `verify_token`,纯文本一行: ``` dt_v_8f3a1c... ``` | 要点 | | |---|---| | 路径 | 必须正好是 `/.well-known/datai-verify` | | 内容 | **纯文本**。不是 JSON,不加引号,不加换行以外的任何东西 | | 多行 | 文件里**可以有多行**。这个 target 服务过别的域名时**追加**,不要覆盖 —— 覆盖会让之前绑好的域名验证失败 | | 位置 | 静态站必须放进构建产物(通常是 `public/`),不是躺在仓库里 | ### ③ 部署 用**用户自己的凭据**部署到**用户自己的**账号。target 就是部署平台给的 默认地址,**只取主机名**: | 平台 | target 长这样 | |---|---| | Vercel | `my-shop.vercel.app` | | Cloudflare Workers | `my-shop..workers.dev` | 不要带 `https://`、不要带路径、不要带端口。 详细步骤和全部坑见 。 ### ④ 自检(别跳过) ```bash curl -sS -o /dev/null -w '%{http_code}\n' https:/// curl -sS https:///.well-known/datai-verify ``` 第一条要 `200`,第二条要**原样返回 verify_token**。 不对就修部署,**不要**去调 `datai_bind` —— 它的报错只告诉你"没找到", 不告诉你为什么没找到。部署保护、文件没进产物、地址写错,它都分不出来。 ### ⑤ 绑定 调 `datai_bind`,传 `hostname` 和 `target`。成功返回 `url`。 **幂等** —— 重复调用只改指向,可以安全重试。 --- ## 5. 三个必须知道的坑 ### 坑 1:绑定后 30–60 秒才能打开 返回 `ok: true` 之后,域名需要 30–60 秒传播。 **这不是失败。不要因此立即重试 `datai_bind`。** 想确认就调 `datai_status`。 ### 坑 2:验证文件缺失会明确挡住你 没放文件、或内容对不上,`datai_bind` 返回 `TARGET_NOT_VERIFIED`, 并在 `expected` 里给出应该是什么。 这是有意的:没有这道闸,任何人都能把自己账号上的钓鱼站绑到搭台的域名上, 让搭台替他背书。按 `next_action` 修好,重新部署,再调一次。 ### 坑 3:`TARGET_NOT_REACHABLE` 和 `TARGET_NOT_VERIFIED` 是两回事 | 错误码 | 意思 | 往哪查 | |---|---|---| | `TARGET_NOT_REACHABLE` | 这个地址在公网**打不开** | 部署没完成,或被部署保护挡着 | | `TARGET_NOT_VERIFIED` | 打得开,但**验证文件不对** | 文件没进构建产物,或内容不对 | 搞混这两个会让你在错误的方向上修很久。 --- ## 6. 报错怎么读 **每条错误都带这些字段,直接照着 `next_action` 做。** ```json { "ok": false, "code": "TARGET_NOT_VERIFIED", "message": "target 上没有找到匹配的所有权验证文件", "next_action": "把 verify_token 的内容以纯文本写到 target 的 /.well-known/datai-verify 路径下,重新部署后再次调用 bind", "retryable": true, "expected": { "token": "dt_v_8f3a..." }, "got": { "status": 404, "note": "该路径不存在" } } ``` | 字段 | 用途 | |---|---| | `code` | 机器可判断,可以拿它写分支 | | `next_action` | **一句话告诉你下一步干嘛。照做就行** | | `expected` / `got` | 期望与实际的差异,能自己 diff | | `retryable` | 重试有没有意义。**`false` 就别重试** | ### 完整错误码表 **你会遇到的(域名相关):** | code | 含义 | retryable | 怎么办 | |---|---|---|---| | `SUBDOMAIN_TAKEN` | 这个前缀被占了 | 否 | 换一个,或省略 subdomain 让搭台自动分配 | | `SUBDOMAIN_INVALID` | 前缀格式不对 | 否 | 只能小写字母、数字、连字符,3–30 位 | | `SUBDOMAIN_RESERVED` | 是保留词(`www`/`api`/`admin`…) | 否 | 换一个 | | `RESERVATION_EXPIRED` | 预留过期,域名被回收 | 是 | 重新 `datai_reserve` | | `TARGET_INVALID` | target 格式不对 | 否 | 只传主机名,不带协议/路径/端口 | | `TARGET_NOT_REACHABLE` | 部署打不开 | 是 | 先修部署 | | `TARGET_NOT_VERIFIED` | 缺验证文件或内容不对 | 是 | 按 `expected` 补上,重新部署 | | `SITE_NOT_FOUND` | 这个 hostname 不属于当前账号 | 否 | 用 `datai_status` 列出你有的站点 | | `QUOTA_EXCEEDED` | 站点数到上限 | 否 | **停下来告诉用户** | **你不该遇到、遇到说明有问题的:** | code | 含义 | retryable | |---|---|---| | `UNAUTHORIZED` | key 无效或已撤销 | 否,让用户重新 `datai login` | | `ACCOUNT_SUSPENDED` | 账号被停 | 否,**停下来告诉用户** | | `RATE_LIMITED` | 请求太频 | 是,等 `retry_after` 秒 | | `INTERNAL` | 搭台内部错误 | 是,把 `request_id` 带上报告 | **登录/注册相关的**(通常不该由你调用,除非用户明确要你在命令行里做): | code | 含义 | |---|---| | `EMAIL_INVALID` | 邮箱格式不对 | | `EMAIL_TAKEN` | 邮箱已注册,让用户去登录 | | `PASSWORD_INVALID` | 密码至少 8 位 | | `CREDENTIALS_INVALID` | 邮箱或密码不对(**刻意不区分是哪个**) | --- ## 7. API 参考 MCP 不够用时可以直接调 HTTP。 - **Base**:`https://webdatai.top` - **鉴权**:`Authorization: Bearer ` - **成功**:`{ "ok": true, ... }` - **失败**:`{ "ok": false, "code", "message", "next_action", "retryable" }` ### POST /v1/sites/reserve — 预留域名 ```json // 请求 { "subdomain": "huadian" } // 可省略 → 自动分配 // 200 { "ok": true, "hostname": "huadian.webdatai.top", "verify_token": "dt_v_8f3a1c...", "verify_path": "/.well-known/datai-verify", "expires_at": "2026-10-08T12:00:00.000Z", "next_action": "把 verify_token …" } ``` ### POST /v1/sites/bind — 绑定并上线 ```json // 请求 { "hostname": "huadian.webdatai.top", // 省略则自动分配一个新的 "target": "huadian-shop.vercel.app" } // 200 { "ok": true, "hostname": "huadian.webdatai.top", "url": "https://huadian.webdatai.top", "target": "huadian-shop.vercel.app", "verified": true, "live_at": "2026-10-07T12:00:03.000Z", "note": "绑定已生效。DNS 与缓存传播通常需要 30-60 秒…" } ``` **幂等**:同 hostname 重复调用只更新 target。 ### GET /v1/sites/{hostname} — 读一个站 ```json { "ok": true, "site": { "hostname": "huadian.webdatai.top", "target": "huadian-shop.vercel.app", // 未绑定时是 null "status": "live", // reserved | verifying | live | suspended "verified": true, "url": "https://huadian.webdatai.top", "created_at": "2026-10-07T11:59:00.000Z", "reserved_until": null } } ``` ### DELETE /v1/sites/{hostname} — 解绑 ```json { "confirm": true } ``` **少了 `confirm` 会被拒绝** —— 解绑会释放域名,任何人都能重新占用,这个摩擦是必要的。 ### GET /v1/me — 我是谁 + 我的站点 + 配额 ```json { "ok": true, "email": "you@example.com", "status": "active", "sites": [ /* 同上的 site 形状 */ ], "quota": { "used": 1, "max": 10 } } ``` ### 其余端点(通常不需要你调) | 方法 | 路径 | 说明 | |---|---|---| | POST | `/v1/auth/device` | 命令行发起登录,拿用户码 | | POST | `/v1/auth/token` | 命令行轮询取 key | | POST | `/v1/auth/approve` | **需要登录**。浏览器侧确认设备 | | POST | `/v1/auth/register` | 邮箱 + 密码注册 | | POST | `/v1/auth/login` | 邮箱 + 密码登录 | | GET | `/v1/vitals` | 健康检查 | --- ## 8. 常见操作 ### 改完代码重新部署之后 **再调一次 `datai_bind`** 即可。幂等,不会造出第二个站,target 没变时 也不需要改任何东西。 ### 换域名 1. `datai_reserve` 拿一个新域名 2. 把**新** token **追加**到验证文件里(别覆盖) 3. 重新部署 4. `datai_bind` 新域名 旧域名还在。想撤掉见下一条。 ### 解绑 / 撤掉一个站 `DELETE /v1/sites/{hostname}`,body 传 `{"confirm": true}`。 域名会被释放,任何人都能重新占用它。**用户没明确要求就别调。** ### 一个站要挂在多个域名上 可以。`datai_reserve` 拿几个,验证文件里放多行 token, 然后对每个 hostname 各 `datai_bind` 一次,指向同一个 target。 ### 看用户有哪些站 `datai_status`(不传 hostname)→ 列出全部。 --- ## 9. 绝对不要做的事 | 不要 | 为什么 | |---|---| | 因为域名一时打不开就反复 `datai_bind` | 那是传播延迟,不是失败 | | 把验证文件写成 JSON 或加引号 | 内容是纯文本,一行一个 token | | 覆盖验证文件(而不是追加) | 会让之前绑好的域名全部验证失败 | | 把用户的云凭据发给搭台 | 搭台不接受,也永远不该接受 | | 替用户设密码 / 索要密码 | 那是他自己的凭据 | | 让用户手动复制粘贴授权地址 | `datai login` 会自动开浏览器 | | 未经用户同意就解绑 | 域名会被释放,他可能还在用 | | 用别人的邮箱登录 | 那不是他的账号 | | 在 target 里带 `https://` 或路径 | 只传主机名 | | 同时 bind 多个 hostname 到同一个 target 后覆盖验证文件 | 见上 | --- ## 10. 需要停下来问用户的情况 **只有这几种。其余情况你都应该能自己走完。** | 情况 | 为什么必须问 | |---|---| | `QUOTA_EXCEEDED` | 要用户决定删哪个站,或者升级配额 | | `ACCOUNT_SUSPENDED` | 账号被停,需要人工处理 | | 用户明确要求解绑某个站 | 域名会释放,后果不可逆 | | 用户想用自己的域名(不是 `xxx.webdatai.top`) | 这个功能还没做 | | 文档没覆盖的情况 | **不要猜。猜错的代价是用户的域名或账号** | 反过来说:**下面这些不该问用户** —— - 要不要指定域名前缀(自己起一个合理的,或者省略让搭台分配) - target 填什么(那是部署平台给的,你自己知道) - 部署怎么配(读 llms-deploy.txt) - 报错怎么办(读 `next_action`)