mcpField
U
upstash-box-cli

upstash-box-cli

github.com/upstash云与 DevOps34582 次安装1 天前更新
代码仓库

关于 upstash-box-cli

upstash-box-cli 是用于操作 Upstash Box 远程容器的命令行工具,适合需要隔离环境运行命令、管理文件和 Git 仓库的开发工作。它也支持在容器中运行浏览器和 Agent,并可生成公开预览链接。

主要功能

  1. 01创建、暂停和恢复远程容器
  2. 02执行命令并管理容器文件
  3. 03克隆仓库并执行 Git 操作
  4. 04在容器中控制 Chromium 浏览器
  5. 05生成容器端口的公开 URL
  6. 06创建快照并委派任务给 Agent

适用场景

  1. 01在远程容器中安装依赖并运行测试
  2. 02在隔离环境中开发并发布预览链接
  3. 03使用容器浏览器检查网页并截取画面
  4. 04将任务交给容器内配置的 Agent

MCP 相关

创建容器时可通过 --mcp 或 --mcp-file 配置 MCP 服务,并可合并账户级 MCP 服务设置。

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/upstash/skills --skill upstash-box-cli

安装后在 Claude Code 中输入/upstash-box-cli确认已加载。

详细介绍

box 操作的是远程容器,而不是这台机器。本地文件和 Shell 工具在本地运行;任何必须在 box 内完成的操作都要通过 box 执行。

安装

npm i -g @upstash/box-cli

身份验证

每条命令都需要 API 密钥,否则会失败并显示“API token required”。设置一次即可,也可以在任何单条命令中传入 --token。前往 https://console.upstash.com/box 创建一个密钥。

export UPSTASH_BOX_API_KEY=box_...

选择 box

解析顺序为 --box <id>、$BOX_ID,然后是最近的 .box 文件(向上搜索)。创建一个并将其固定到工作目录:

box create --no-repl --runtime node                    # prints the id, writes .box
box create --no-repl --runtime node --clone-repo https://github.com/org/repo
box list                                               # find an existing box
box use <box-id>                                       # pin one to this directory
box status                                             # id, where it came from, state

--keep-alive、--browser、--env 和 --size 只能在创建时选择;要更改其中任何一项,都必须新建 box。无法调整大小。

默认使用普通的 box create --no-repl。普通 box 空闲时会暂停,并在下次执行命令时恢复,这适用于几乎所有工作场景:

box create --no-repl --browser             # provision a headless Chromium
box create --no-repl --env KEY=VAL         # env for this box (repeatable)
box create --no-repl --size medium         # small (default), medium, large

只有在某些内容必须在空闲期间持续运行时,才添加 --keep-alive:例如,你准备通过预览 URL 访问的分离服务器,或需要在命令之间持续运行的任务。它会阻止 box 暂停,因此在你暂停或删除 box 之前,box 会持续产生费用。不带此选项时,--init-command 会被拒绝:

box create --no-repl --keep-alive                            # stays up when idle
box create --no-repl --keep-alive --init-command "npm ci"    # startup script

其余创建时选项同样无法在之后更改:

box create --no-repl --skill upstash/skills/redis      # repeatable
box create --no-repl --mcp docs=@org/mcp-server        # or name=https://url
box create --no-repl --mcp-file servers.json           # for args and headers
box create --no-repl --network-policy deny-all         # or allow-all, or custom
box create --no-repl --network-policy custom --allow-domain api.example.com
box create --no-repl --attach-headers-file headers.json

--attach-headers-file 用于存放以主机模式为键的 JSON 对象({"api.stripe.com": {"Authorization": "Bearer ..."}}),这些请求头会注入来自 box 的匹配出站请求。也可以使用 --attach-header host:Name=value,但其值会出现在 ps 和 Shell 历史记录中,因此任何机密信息都应优先使用文件。

技能 ID 由三部分组成:owner/repo/skill-name。格式不正确时,服务端只会发出警告,因此 box 启动后会静默地缺少该技能。

--env 是针对每个 box 的。box env set 则作用于账户级别:它会合并到之后创建的每个 box 中,不会影响已存在的 box。同一键的账户级值会被每个 box 的 --env 覆盖,因此账户级值只会补充 box 中未设置的内容。账户级技能和 MCP 服务也以相同方式合并。

paused 并不表示出错;下条命令会恢复 box。

查找 .box 文件时,会从工作目录向上遍历,因此 cd 到另一个项目可能会静默地拾取那里之前留下的固定配置,并在错误的 box 上运行。在会操作多个 box 的会话中,务必显式传入 --box;box status 会显示解析到的 box 及其来源。

工作完成后请清理。只要 box 存在,就会产生费用:

box pause                   # keeps the workspace, resumes on the next command
box resume                  # rarely needed; any command resumes a paused box
box delete --yes            # irreversible; --yes is required without a terminal

运行 box create 或 box connect 时,务必带上 --no-repl:否则它们会打开交互式 REPL 并一直挂起。box from-snapshot 也需要 --no-repl。

box snapshot                                # snapshot this box, prints the id
box snapshot list
box from-snapshot <snapshot-id> --no-repl   # restore into a new box, pinned
box snapshot delete <snapshot-id>

运行命令

将远程命令放在 -- 之后,否则其选项会被解析为 box 自身的选项。

box exec -- npm install
box exec -C repo -- npm test
box exec --json -- node -e 'console.log(1)'   # {stdout, stderr, exit_code}

远程 Shell 是 sh,不是 bash。在 box exec 中使用 heredoc 会失败,并显示 Syntax error: redirection unexpected;请改用 box files write - 写入文件,或者在 box 中有 bash 时用 bash -c 包装命令。

如需交互式 Shell,请直接通过 SSH 登录。box ID 是用户名,Box API 密钥是密码:

ssh <box-id>@us-east-1.box.upstash.com

命令以 boxuser 身份运行,因此全局 npm 安装需要 sudo,且无需密码:

box exec -- 'sudo npm install -g @upstash/docs7'   # EACCES without sudo

如果只传入一个参数,它会作为 Shell 表达式原样发送,因此管道和重定向都能正常工作。如果传入多个参数,它们会分别作为 argv 并单独引用,因此含空格的参数仍会作为一个参数处理。

远程命令的退出码会原样传回,因此 box exec -- npm test && ... 可以正常串联执行。退出码 125 表示 CLI 本身出错(box 错误、选项错误),绝不是远程命令返回的状态码。

后台服务器会随启动它的命令一起终止。请将其分离运行:

box exec -- '( npm run dev > dev.log 2>&1 & )'
box public-url 3000                               # prints the public URL
box public-url list
box public-url delete 3000

内联代码:当使用 Shell 单行命令不如编写程序时:

box code - --lang python < script.py
box code 'console.log(1 + 1)' --lang js

构建内容并返回链接

“给我做一个贪吃蛇游戏,用 Upstash Box”意味着在 Box 中构建游戏、在其中运行,然后回复一个用户可以打开的 URL。要完成整个流程;不要只停留在编写文件。

box create --no-repl --runtime node --keep-alive   # writes .box; stays up for the URL

box files write index.html - <<'HTML'
<!doctype html><meta charset="utf-8"><title>Snake</title>
<canvas id="c" width="400" height="400"></canvas>
<script>/* the game */</script>
HTML

box files write server.js - <<'JS'
const http = require("http"), fs = require("fs");
http.createServer((_, res) => {
  res.writeHead(200, { "Content-Type": "text/html" });
  res.end(fs.readFileSync("index.html"));
}).listen(3000, "0.0.0.0");   // the default binds ::, which the check below misses
JS

box exec -- '( node server.js > server.log 2>&1 & )'          # detached, or it dies
box exec -- 'sleep 1; ss -ltn | grep -q "0.0.0.0:3000" && echo up'
box public-url 3000                                           # the link to reply with

使用 Node 自带的 http 模块,而不是依赖包:无需安装,无需联网获取,并且可在纯净的 node 运行时中工作。

发布前检查端口,并确认它绑定到什么地址,而不只是确认它能响应。在 127.0.0.1 上运行的服务会响应从 Box 内部发出的 curl,但仍然无法发布:代理通过地址访问容器,因此 box public-url 会返回 502。这就是为什么上面的检查会查找 0.0.0.0,而不是向 localhost 发送 curl;后者恰好会在失败的情况下通过。

大多数开发服务器需要指定参数:Vite 和许多其他服务器使用 --host 0.0.0.0,有些使用 -H 0.0.0.0,还有少数完全无法绑定到回环地址之外。

--keep-alive 是让链接持续有效的关键。没有它,Box 会在空闲时暂停,分离运行的服务器也会随之停止,几分钟后你提供的 URL 就会开始返回错误。

回复时直接给出 URL,不要只说“它正在运行”。说明 Box 会持续产生费用,直到执行 box delete --yes;并且任何获得该 URL 的人都可以公开访问它——box public-url 3000 --basic-auth 会在其前面加上身份验证。

文件

路径相对于 /workspace/home。

box files list src
box files read src/index.ts
box files write src/app.ts -    < local.ts    # - reads stdin: use this for code
box files write notes.txt "short text"
box files stat src/index.ts
box files mkdir -p a/b/c
box files rename old.ts new.ts
box files remove build -r                      # a directory needs -r
box files upload ./local.zip /workspace/home/local.zip
box files download repo                        # a folder lands in ./repo
box files download logs/app.log -o ./app.log   # a file; -o names the destination

使用 - 和 stdin 写入代码。将源代码作为参数传入会被 shell 错误处理。

要搜索,请使用 Box 自带的工具:box exec -- grep -rn TODO src。

Git

克隆操作会将仓库放入一个以仓库命名的目录中;除 clone 外,每个 git 子命令都需要通过 -C 指定该目录。否则 git 会在工作区根目录运行,而那里不是仓库。

box git clone https://github.com/org/repo
box git clone https://github.com/org/repo -C my-app   # -C is the destination here
box git status -C repo
box git diff -C repo
box git config -C repo --name "Bot" --email [email protected]
box git checkout -C repo feature/x             # creates the branch if missing
box git exec -C repo -- add -A
box git commit -C repo -m "message"
box git push -C repo                           # pushes the checked-out branch
box git create-pr -C repo --title "Fix the thing" --base main
box git create-pr -C repo --title "Fix the thing" --body-file notes.md
box git create-issue -C repo --title "Search returns nothing"

内容超过一句话时请使用 --body-file:值得认真撰写的正文经不起 shell 引号处理。- 会从 stdin 读取。

box git exec 接收不带前导 git 的 git 参数,并透传 git 的退出码。

私有仓库和 PR 需要在创建时提供令牌:box create --no-repl --git-token $GITHUB_TOKEN。

将屏幕截图附加到拉取请求或问题

--attach 会将图片或视频上传到新建的拉取请求或问题,也可以重复使用以附加多个文件。图片的替代文本写在 # 后面。视频会显示为播放器,不接受替代文本。

路径会在 Box 内读取,并且相对于 -C。浏览器截图会写入运行 CLI 的机器,而不是 Box,因此必须先上传。这个上传步骤经常被遗漏:

box browser screenshot -o /tmp/shot.png          # lands here, not in the box
box files upload /tmp/shot.png repo/shot.png     # now it is in the repository
box git create-issue -C repo \
  --title "Search returns nothing" \
  --body 'Reproduced on staging.

![what I saw](./shot.png)' \
  --attach 'shot.png#the empty result list'

正文中的 ![alt](./shot.png) 引用会被改写为指向已上传的资源,因此图片会显示在问题中,而不是指向仅存在于 Box 内的路径。

以下四条规则会强制执行;任何一条不满足都会在创建内容前返回 400:扩展名必须是 png、jpg、jpeg、gif、webp、mp4、mov 或 webm;文件最多 50 个;路径必须位于 -C 目录内;视频不能带有替代文本。

部分附件上传成功、其他附件上传失败时,内容仍会创建,并照常返回其 URL,同时附带 warning。文本输出会将警告单独打印一行,--json 则会在 warning 字段中包含警告。报告问题已提交且证据已附加前,请先检查此警告。

Agent

如果创建 Box 时指定了 Agent,可以交给它一项任务:

box create --no-repl --agent-harness claude-code --agent-model anthropic/claude-sonnet-5
box run "Fix the failing test in src/auth.test.ts"
box run - < prompt.txt

文本会发送到 stdout,工具调用会发送到 stderr。建议使用上面的命令自行完成工作;box run 用于将整个任务委派给 Box 自带的 Agent。

监视和停止工作

box status runs                    # id, type, status, duration, cost
box status logs --limit 50
box cancel <run-id>                # ids come from status runs

由其他进程启动的运行任务无法通过其他方式停止:box cancel 接收任务 ID,因此可以从新的 shell 中中断耗时较长的 Agent 运行或构建。

浏览器

仅适用于使用 --browser 创建的 box。Chromium 在 box 内运行,因此无需公开 URL 即可访问你的应用 http://localhost:3000。只有控制路径位于外部:这些命令通过 API 访问 Chromium,因此没有对应的 box exec 写法。在 box 内运行的脚本仍可通过 CDP 直接与 Chromium 通信,这是本节末尾介绍的变通方法。

box browser open https://example.com    # prints the tab id
box browser tabs
box browser content                     # title, url, text, links
box browser screenshot -o page.png
box browser goto https://example.com/login
box browser act "click the login button"
box browser close
box browser cdp-url                     # drive it with Playwright instead
box browser observe "what can I click here?"
box browser live-url                    # a URL for a human to watch the tab

每次调用 box browser act 都会计费:它接收文字指令,并需要模型读取页面。SDK 可以免费重放 observe() 的结果,但 CLI 只接受字符串形式,因此循环调用 act 时,每次都需要调用一次模型。content、goto、screenshot 和 close 均不计费。

需要展示实际过程而非文字描述时,可以录制:

box browser recordings start --max-seconds 120
box browser recordings stop
box browser recordings list
box browser recordings get <recording-id>
box browser recordings download <recording-id> -o session.mp4

Chromium 会在首次使用时启动,因此第一次运行 box browser open 会比之后慢;在它至少运行过一次之前,任何直接与 CDP 通信的操作都会失败。

只有一个标签页时,--tab <id> 是可选的;有多个标签页后则必须指定。screenshot 会将内容写入文件,因为标准输出用于输出文本;该文件保存在本机,而非 box 中。要将截图添加到拉取请求或议题,请参阅“将截图附加到拉取请求或议题”。

使用扁平 JSON Schema 文件从页面提取结构化数据:

echo '{"type":"object","properties":{"price":{"type":"string"}},"required":["price"]}' > s.json
box browser extract "the listed price" --schema s.json

未在 required 中列出的属性均为可选属性。不支持嵌套对象。

直接在 box 中截取

只要图像可以保存在本机,box browser screenshot --full-page -o page.png 就是最简便的方式。不过,它会将图像写入运行 CLI 的本机,因此还需要上传才能将图像放进 box。

在 box 内部,Chromium 的 CDP 会在 127.0.0.1:9222 上开放且无需令牌,因此在 box 中运行的脚本可以一步完成截图和写入,还能裁剪为单个元素,而 CLI 不支持此功能:

请使用 box files write 编写脚本,而不是将脚本直接内联:远程 shell 使用 sh,通过 box exec 引用程序时容易出错。

box files write shot.mjs - <<'JS'
const targets = await (await fetch("http://127.0.0.1:9222/json")).json();
const page = targets.find((t) => t.type === "page");
const ws = new WebSocket(page.webSocketDebuggerUrl);
await new Promise((r) => (ws.onopen = r));

let id = 0;
const pending = new Map();
ws.onmessage = (m) => {
  const msg = JSON.parse(m.data);
  pending.get(msg.id)?.(msg.result);
  pending.delete(msg.id);
};
const send = (method, params = {}) =>
  new Promise((resolve) => {
    const callId = ++id;
    pending.set(callId, resolve);
    ws.send(JSON.stringify({ id: callId, method, params }));
  });

// captureBeyondViewport only permits capture outside the viewport; the clip is
// what makes it the whole page. cssContentSize is in CSS pixels, which is what
// clip expects.
const metrics = await send("Page.getLayoutMetrics");
const size = metrics.cssContentSize ?? metrics.contentSize;
const { data } = await send("Page.captureScreenshot", {
  format: "png",
  captureBeyondViewport: true,
  clip: { x: 0, y: 0, width: size.width, height: size.height, scale: 1 },
});

const fs = await import("node:fs");
fs.writeFileSync("shot.png", Buffer.from(data, "base64"));
ws.close();
JS

box exec -- 'node shot.mjs'      # shot.png is now in the box

clip 可以将截图范围扩展为整页,而不是仅限视口;--full-page 的作用相同。若要裁剪为某个元素,调用方式相同,只需将该元素的边界框作为裁剪范围;CLI 没有对应的标志。Node.js 的全局 fetch 和 WebSocket 就已足够,无需安装任何内容,但必须先运行一次 box browser 命令启动 Chromium。

只有在图像需要保留在 box 中,或需要截取 CLI 无法完成的内容时,这种方式才值得采用。否则,使用 screenshot -o 后再运行 files upload 更简便。

定时任务

box 上的 cron 使用 UTC。容器内的任何内容都无法注册定时任务。

box schedule exec --cron '0 9 * * *' -- npm run backup
box schedule agent --cron '@daily' "summarise yesterday's errors"
box schedule list
box schedule get <schedule-id>            # includes run and failure counts
box schedule pause <schedule-id>
box schedule resume <schedule-id>
box schedule update <schedule-id> --cron '0 10 * * *'
box schedule delete <schedule-id>

update 只会更改你指定的内容,因此设置 cron 时不会更改命令。

Box 配置

box skills add upstash-redis-js        # skills available to the box's agent
box skills list
box skills remove upstash-redis-js
box config model anthropic/claude-sonnet-5
box config init-command set "npm ci"   # keep-alive boxes only; runs on start
box config init-command get
box config init-command delete
box config network deny-all            # or allow-all, or custom
box config network custom --allow-domain api.example.com
box config harness --command my-agent  # a custom agent harness

账户级设置适用于之后创建的 box,而非当前这个 box:

box env set KEY VAL                    # applies to boxes created after this
box env list
box env delete KEY
box env set-all A=1 B=2                # replaces every var, does not merge
box labels add staging                 # then: box list --label staging
box labels list
box labels remove staging

box env set 和 box create --env 都会将值作为参数接收,因此通过任一种方式传入的密钥都会显示在 ps 中,并留在 shell 历史记录里。它们都不是密钥管理机制;请勿在其中存放真实凭据,而应使用由 box 自行获取的令牌。

标志参考

以下标志在上面的操作指南中未提及。每个命令也都支持全局标志 --box、--json 和 --token。

box create --no-repl --git-user-name N --git-user-email E   # commit identity
box create --no-repl --agent-api-key stored                 # key saved in the console
box create --no-repl --no-use                               # do not write .box
box init-demo --directory my-demo                           # scaffold elsewhere

box exec -C /srv/app -- npm test         # -C/--cwd: working directory
box run --timeout 600 -q "..."           # -q/--quiet: no tool-call logs on stderr
box code --timeout 120 "..."             # both take --timeout in seconds

box files read --offset 0 --length 65536 big.log   # a slice; 8 MiB per read
box files read --encoding base64 logo.png          # binary out
box files write --encoding base64 logo.png -       # binary in
box files remove -r build/                         # -r required for a directory
box files mkdir -p a/b/c                           # -p creates missing parents
box files stat --follow link                       # resolve a final symlink

box git clone --branch main --depth 1 <url>        # shallow, single branch
box git clone --github-token $TOKEN <url>          # private repository
box git commit -m "msg" --author-name N --author-email E
box git push --branch feature/x                    # names the branch to push

box public-url 3000 --bearer-token       # or --basic-auth; both generate credentials
box use --unset                          # drop this directory's .box, never a parent's

box schedule agent --cron "0 9 * * *" --model <m> --timeout 300 --webhook-url <url> "..."
box schedule update <id> --timeout 0     # 0 clears the timeout; --prompt, --cron, --model too

box config network custom --allow-domain a.test --allow-cidr 10.0.0.0/8 --deny-cidr 10.1.0.0/16
box config harness --command my-agent --arg --verbose   # --arg repeatable, sent before the prompt

-C 在 box exec 中表示工作目录(--cwd),在每个 box git 和 box schedule 子命令中表示仓库目录(--folder)。

输出

数据输出到 stdout,诊断信息输出到 stderr,因此可以安全地通过管道传递。对于所有返回数据的命令,--json 都会将结果以纯 JSON 格式输出,不带任何包装。会打开 REPL 或打印 shell 脚本的命令(connect、init-demo、completion)会拒绝自动化调用方,而不是向其返回提示词:

box files list --json | jq -r '.[].name'
box get "$(cat .box)" --json

box init-demo 和 box completion 虽然存在,但它们是为人而非 Agent 设计的:前者会创建本地演示项目的脚手架,后者会打印 shell 补全脚本。

评论

支持 Markdown