私有部署与观测

Artifacts

把一个 HTML 页面或 Markdown 文件上传到 cloud-artifacts 托管服务,换回一个公开可访问的 URL。 服务端由独立的 Cloudflare Worker + R2 构成,7d / 30d 前缀配合 R2 lifecycle rule 自动过期;CLI 侧通过延迟工具 artifact 直接上传。

  • Cloudflare Worker + R2
  • 7d / 30d 自动过期
  • 可自托管

这是什么

cloud-artifacts 是一个独立部署的 HTML 托管服务:向 POST /upload 提交一段 HTML,服务把它写进 R2 bucket,并返回一个形如 /<7d|30d>/<id>.html 的公开 URL。GET 该 URL 即原样返回 HTML(含其中的 <script>)。文件到期由 R2 lifecycle rule 自动删除, Worker 本身不参与过期处理。

它由三部分构成:Deno Deploy 边缘代理(生产出口,改善国内访问)、 Cloudflare Worker(鉴权 / 校验 / 读写 R2)、R2 bucket(按 prefix 分 TTL)。

在 CLI 侧,对应的能力是一个延迟工具 artifact(用户可见名 Artifact):读取本地文件并调用上述服务;支持 .html / .htm 原样上传,.md / .markdown 会先转成带样式的 HTML 再上传。 配套还有一个 /artifacts 斜杠命令与 /use-artifacts 内置技能。

i
这是一个独立服务

与 packages/remote-control-server/ 定位相同:monorepo 的 workspaces 会自动识别本包,但主 CLI 不会 import 它。CLI 只是通过 HTTP 调用它的 API。

前置条件

  • 使用公共托管服务:无需任何配置。CLI 侧的 artifact 工具内置了默认 token 与默认服务地址。
  • 自托管部署:需要一个 Cloudflare 账号,且本机已执行 npx wrangler login 登录目标账号;需启用 R2。
  • Deno Deploy 代理层(可选):由部署者另配(CNAME 指向 alias.deno.net,并在 Deno Deploy 项目里把上游设为 https://<worker>.<account>.workers.dev)。不配也可直连 Worker。
  • 自托管时的资源约定:wrangler.toml 中 R2 binding 名为 BUCKET,bucket 名为 cloud-artifacts,兼容日期 2026-06-20。

安装与部署

  • 安装依赖

    在包目录里安装;在 monorepo 根执行亦可(workspace 自动识别)。

    Terminal
    cd packages/cloud-artifacts
    bun install
  • 准备本地开发用的 TOKEN
    Terminal
    cp .dev.vars.example .dev.vars
    # 编辑 .dev.vars,填入 TOKEN(仅 wrangler dev 读取)
  • 一键初始化生产资源

    bun run setup 执行 scripts/setup.sh:创建 R2 bucket、添加两条 lifecycle rule(7d/ 删 7 天、30d/ 删 30 天),并通过 wrangler secret put TOKEN 设置生产上传密钥。

    Terminal
    bun run setup
  • 绑定自定义域名并设置 PUBLIC_URL

    在 Cloudflare Dashboard 为 Worker 绑定 Custom Domain(POST 与 GET 共用同一域名), 再把 wrangler.toml 的 [vars] PUBLIC_URL 改为该对外出口域名 (生产用 https://cloud-artifacts.qianshou-dashi.win)。

  • 部署
    Terminal
    bun run deploy
  • 本地开发:bun run dev(wrangler dev)启动本地 Miniflare + 本地 R2 模拟,默认端口 8787:

    Terminal
    bun run dev
    curl -X POST "http://localhost:8787/upload" \
      -H "Authorization: Bearer <token>" \
      -H "Content-Type: text/html" \
      --data-binary @/tmp/t.html

    运行测试:scripts/test.sh 覆盖 7 个错误用例、3 个成功用例与 R2 写入验证, 支持直连(按 status code 断言)与经 Deno Deploy 代理(按 body 的 error 字段断言)双模式:

    Terminal
    WORKER_URL=https://cloud-artifacts.qianshou-dashi.win \
    TOKEN=<your-token> \
    bash scripts/test.sh

    配置

    Worker 变量(wrangler.toml 的 [vars]):

    变量 默认值 说明
    PUBLIC_URL https://cloud-artifacts.qianshou-dashi.win 对外出口域名,用于拼接返回的 url;部署到自有域名后必须修改
    DEFAULT_TTL_DAYS 7 未传 ?ttl= 时的默认 TTL(天)
    MAX_TTL_DAYS 30 TTL 上限(天)
    MAX_BYTES 10485760 上传体积上限(10MB)

    密钥与绑定:

    项 说明
    TOKEN(secret) 上传侧唯一鉴权:Authorization: Bearer <TOKEN> 必须与之完全相等。生产用 wrangler secret put TOKEN 注入,即时生效、无需 redeploy
    BUCKET(R2 binding) 绑定到 bucket cloud-artifacts,key 形如 <7d|30d>/<id>.html
    .dev.vars 本地开发用的 TOKEN,仅 wrangler dev 读取

    CLI 侧环境变量(覆盖内置默认值,用于自托管服务):

    变量 默认值 说明
    CLAUDE_ARTIFACTS_TOKEN qianshou-dashi 上传用的 Bearer token,须与自托管服务的 TOKEN 一致
    CLAUDE_ARTIFACTS_URL https://cloud-artifacts.qianshou-dashi.win 服务基地址;getUploadUrl() 会去掉尾部斜杠后追加 /upload

    常用命令与参数

    HTTP API:

    方法与路径 说明
    POST /upload Bearer 鉴权后校验 MIME、体积与 ttl,写 R2 并返回 {id, url, expiresAt}
    GET /<7d|30d>/<id>.html 从 R2 读回,返回 text/html; charset=utf-8 与 Cache-Control: public, max-age=86400;prefix 非 7d/30d 返回 404

    POST /upload 参数:

    Header / Query 必填 说明
    Authorization: Bearer <TOKEN> 是 与 Worker secret TOKEN 完全相等
    Content-Type: text/html 是 不接受其他类型(以 text/html 开头为准)
    ?ttl=7|30 否 默认 7,只允许 7 或 30
    ?hash=<custom-id> 否 自定义 ID,须匹配 ^[A-Za-z0-9_-]{1,128}$;指定时覆盖同 ID 旧版本
    body 是 原始 HTML(--data-binary @file.html),不超过 10MB

    错误码(直连 Worker 时的 HTTP status;经 Deno Deploy 代理会统一变成 200,按 body 的 error 字段判断):

    状态码 error code 触发条件
    400 invalid_ttl ttl 非 7 或 30
    400 invalid_hash hash 不匹配 ^[A-Za-z0-9_-]{1,128}$
    401 unauthorized 缺少 Authorization 或 token 不匹配
    404 not_found 非 /upload 路径,或 GET 路径不匹配
    413 payload_too_large body 大于 10MB
    415 unsupported_media_type Content-Type 非 text/html

    CLI 工具与命令:

    入口 说明
    artifact(延迟工具) 读取本地文件并上传,返回 {id, url, expiresAt}。首次调用需 SearchExtraTools + ExecuteExtraTool 两步;携带上次的 id 作为 hash 可原地覆盖、URL 保持稳定
    /artifacts 列出本会话内上传过的制品;↑/↓ 选择、Enter 打开、c 复制 URL、Esc 退出
    /use-artifacts 内置技能,教模型何时 / 如何上传制品(可带可选关注点参数)

    工具输入参数(artifact):

    参数 类型 说明
    file_path string(必填) 本地文件的绝对路径,接受 .html / .htm / .md / .markdown
    hash string(可选) 覆盖已有制品,URL 保持稳定;须匹配 ^[A-Za-z0-9_-]{1,128}$
    ttl 7 | 30(默认 7) 制品存活天数,只能是 7 或 30

    实战示例

    用 curl 上传(默认随机 ID + 7 天):

    Terminal
    echo '<h1>hello</h1>' > /tmp/t.html
    curl -X POST "https://cloud-artifacts.qianshou-dashi.win/upload" \
      -H "Authorization: Bearer $TOKEN" \
      -H "Content-Type: text/html" \
      --data-binary @/tmp/t.html
    # {"id":"V1StGXR8_Z5jdHi6B-myT",
    #  "url":"https://cloud-artifacts.qianshou-dashi.win/7d/V1StGXR8_Z5jdHi6B-myT.html",
    #  "expiresAt":"2026-06-27T10:00:00.000Z"}

    自定义 hash + 30 天(再次上传同 hash 覆盖):

    Terminal
    curl -X POST "https://cloud-artifacts.qianshou-dashi.win/upload?ttl=30&hash=my-report" \
      -H "Authorization: Bearer $TOKEN" \
      -H "Content-Type: text/html" \
      --data-binary @/tmp/report.html

    指定 ?hash= 时的覆盖语义:先校验字符集,再删除 7d/<hash>.html 与 30d/<hash>.html 两个旧 key (R2 delete 不存在的 key 不报错),然后按 ?ttl= 写入新 key,并返回新的 expiresAt。不指定时用 nanoid(21) 随机 ID(126 bit 熵), 几乎不可能碰撞,不做碰撞检查。

    用 CLI 的 artifact 工具上传:先让模型写本地 HTML,再上传; 后续要更新时带回第一次返回的 id 作为 hash,URL 不变。

    qsdashi 会话
    # 1. 用 Write 工具写 HTML 到本地文件
    # 2. 首次上传(延迟工具需两步)
    SearchExtraTools({ query: "select:artifact" })
    ExecuteExtraTool({ tool_name: "artifact", params: { file_path: "/tmp/report.html" } })
    
    # 3. 后续更新:带回上次的 id 作为 hash,URL 保持稳定
    ExecuteExtraTool({ tool_name: "artifact", params: { file_path: "/tmp/report.html", hash: "<id-from-first-call>" } })

    查看本会话上传过的制品:

    qsdashi 会话
    /artifacts
    # ↑/↓ 选择 · Enter 打开 · c 复制 URL · Esc 退出

    常见问题排错

    !
    所有请求返回 HTTP 200 但业务出错

    经 Deno Deploy 代理时这是正常现象:代理会把上游 status code 抹平为 200,但 body 中的 {error: ...} 字段完整保留。客户端判错应以 body 的 error 字段为准,而不是 HTTP status。直连 Worker(如 *.workers.dev)时 status 正常透传。

    !
    curl 到 *.workers.dev 超时

    国内 DNS 污染与路由问题导致直连 Cloudflare 边缘节点质量差。改走 cloud-artifacts.qianshou-dashi.win 出口,或为请求挂代理。

    !
    返回的 HTML 多出一段 script 与 a 标签

    这是 Cloudflare 默认注入的 Browser Insights(RUM),不影响内容渲染。需要纯净响应时, 在 Dashboard 关闭该 Worker 的 Web Analytics。

    !
    上传 413 但文件不到 10MB

    检查 Content-Length header 是否被中间层改写;Worker 同时按 Content-Length 与 arrayBuffer().byteLength 双重校验。

    !
    ?ttl=14 返回 400

    设计如此:TTL 只允许 7 或 30,与 R2 lifecycle prefix 一一对应。

    !
    wrangler secret list 看到 TOKEN 但上传仍 401

    token 值不一致。重新执行 wrangler secret put TOKEN 设置正确值即可, 即时生效、无需 redeploy。

    *
    安全边界

    TOKEN 是上传侧唯一鉴权,泄露后任何人可上传 / 覆盖;GET 完全公开,URL 中的 hash 即唯一秘密;上传的 HTML 会被原样返回并执行其中的 <script>, 不要把它当作任意用户的上传入口。lifecycle rule 是 prefix 级全局规则,最长保留 30 天。