关于 upstash-box-cli
upstash-box-cli 是用于操作 Upstash Box 远程容器的命令行工具,适合需要隔离环境运行命令、管理文件和 Git 仓库的开发工作。它也支持在容器中运行浏览器和 Agent,并可生成公开预览链接。
主要功能
- 01创建、暂停和恢复远程容器
- 02执行命令并管理容器文件
- 03克隆仓库并执行 Git 操作
- 04在容器中控制 Chromium 浏览器
- 05生成容器端口的公开 URL
- 06创建快照并委派任务给 Agent
适用场景
- 01在远程容器中安装依赖并运行测试
- 02在隔离环境中开发并发布预览链接
- 03使用容器浏览器检查网页并截取画面
- 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.
' \
--attach 'shot.png#the empty result list'
正文中的  引用会被改写为指向已上传的资源,因此图片会显示在问题中,而不是指向仅存在于 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 补全脚本。