upstash-box-cli
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseboxboxboxboxInstall
安装
bash
npm i -g @upstash/box-clibash
npm i -g @upstash/box-cliAuthentication
身份验证
Every command needs an API key, or it fails with "API token required". Set it once,
or pass on any single command. Create one at
https://console.upstash.com/box.
--tokenbash
export UPSTASH_BOX_API_KEY=box_...每条命令都需要 API 密钥,否则会以 "API token required" 失败。设置一次,或在单条命令上传递 。在 https://console.upstash.com/box 创建一个。
--tokenbash
export UPSTASH_BOX_API_KEY=box_...Selecting a box
选择一个 box
Resolution order is , then , then the nearest file
(searched upward). Create one and pin it to the working directory:
--box <id>$BOX_ID.boxbash
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--sizeDefault to a plain . A plain box pauses when it goes idle
and resumes on the next command, which is what almost all work wants:
box create --no-replbash
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, largeAdd only when something has to survive an idle gap: a detached
server you are about to reach over a preview URL, or a job that keeps running
between commands. It stops the box pausing, so the box keeps costing money until
you pause or delete it. is rejected without it:
--keep-alive--init-commandbash
box create --no-repl --keep-alive # stays up when idle
box create --no-repl --keep-alive --init-command "npm ci" # startup scriptThe rest of the create-time options, all equally unchangeable afterwards:
bash
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{"api.stripe.com": {"Authorization": "Bearer ..."}}--attach-header host:Name=valuepsA skill id has three parts, . A malformed one is only
warned about server-side, so the box comes up with the skill silently absent.
owner/repo/skill-name--envbox env set--envpausedA file is found by walking up from the working directory, so -ing
into another project can silently pick up a pin left there earlier and run
against the wrong box. In any session touching more than one box, pass
explicitly; says which box it resolved and where that came from.
.boxcd--boxbox statusClean up when the work is done. Boxes cost money while they exist:
bash
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 terminalNever run or without : they open an
interactive REPL and will hang. takes too.
box createbox connect--no-replbox from-snapshot--no-replbash
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 <id>$BOX_ID.boxbash
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-replbash
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仅当某些内容需要在空闲间隙中保持存活时才添加 :例如你即将通过预览 URL 访问的分离服务器,或在命令之间持续运行的作业。它会阻止 box 暂停,因此 box 会持续计费,直到你暂停或删除它。没有它时 会被拒绝:
--keep-alive--init-commandbash
box create --no-repl --keep-alive # stays up when idle
box create --no-repl --keep-alive --init-command "npm ci" # startup script其余创建时选项,之后同样都不可更改:
bash
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{"api.stripe.com": {"Authorization": "Bearer ..."}}--attach-header host:Name=valuepsskill id 由三部分组成:。格式错误的 id 只会在服务端给出警告,因此 box 启动时该 skill 会静默缺失。
owner/repo/skill-name--envbox env set--envpaused.boxcd--boxbox status工作完成后请清理。box 存在期间会持续计费:
bash
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永远不要在没有 的情况下运行 或 :它们会打开交互式 REPL 并挂起。 也需要 。
--no-replbox createbox connectbox from-snapshot--no-replbash
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>Running commands
运行命令
Put the remote command after , or its flags are parsed as 's own.
--boxbash
box exec -- npm install
box exec -C repo -- npm test
box exec --json -- node -e 'console.log(1)' # {stdout, stderr, exit_code}The remote shell is , not bash. A heredoc inside fails with
; write the file with
instead, or wrap the command in when the box has bash.
shbox execSyntax error: redirection unexpectedbox files write - bash -cFor an interactive shell, ssh straight in. The box id is the user and the Box
API key is the password:
bash
ssh <box-id>@us-east-1.box.upstash.comCommands run as , so a global npm install needs sudo, which is
passwordless:
boxuserbash
box exec -- 'sudo npm install -g @upstash/docs7' # EACCES without sudoOne argument is a shell expression, sent as written, so pipes and redirection work.
Several arguments are argv and are quoted individually, so an argument containing
spaces stays one argument.
The remote command's exit code is passed through, so
chains normally. Exit code 125 means the CLI itself failed (bad box, bad flags),
never a status the remote command returned.
box exec -- npm test && ...A background server dies with the command that started it. Detach it:
bash
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 3000Inline code, when a shell one-liner would be worse than a program:
bash
box code - --lang python < script.py
box code 'console.log(1 + 1)' --lang js将远程命令放在 之后,否则其标志会被解析为 自己的标志。
--boxbash
box exec -- npm install
box exec -C repo -- npm test
box exec --json -- node -e 'console.log(1)' # {stdout, stderr, exit_code}远程 shell 是 ,不是 bash。在 中使用 heredoc 会以 失败;请改用 写入文件,或者在 box 有 bash 时用 包裹命令。
shbox execSyntax error: redirection unexpectedbox files write - bash -c如需交互式 shell,直接 ssh 进入。box id 是用户名,Box API 密钥是密码:
bash
ssh <box-id>@us-east-1.box.upstash.com命令以 身份运行,因此全局 npm install 需要 sudo,且 sudo 免密:
boxuserbash
box exec -- 'sudo npm install -g @upstash/docs7' # EACCES without sudo单个参数是 shell 表达式,按原样发送,因此管道和重定向有效。多个参数是 argv,会逐个加引号,因此包含空格的参数仍是一个参数。
远程命令的退出码会被透传,因此 可以正常串联。退出码 125 表示 CLI 自身失败(box 或标志错误),绝不是远程命令返回的状态。
box exec -- npm test && ...后台服务器会随启动它的命令一起退出。要分离它:
bash
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 一行命令不如一段程序时,可以使用内联代码:
bash
box code - --lang python < script.py
box code 'console.log(1 + 1)' --lang jsBuilding something and handing back a link
构建东西并交回链接
"Make me a snake game, use Upstash Box" is a request to build it in a box, run it
there, and reply with a URL the user can open. Do the whole thing; do not stop at
writing the file.
bash
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 withNode's own module rather than a package: no install, no network fetch, and it
works on a bare runtime.
httpnodeCheck the port before publishing it, and check what it is bound to, not just
that it answers. A server on replies to a curl from inside the box
and still cannot be published: the proxy reaches the container by address, so
returns 502. That is why the check above greps for
rather than curling localhost, which passes in exactly the case that fails.
127.0.0.1box public-url0.0.0.0Most dev servers need telling: for Vite and many others,
for some, and a few cannot be moved off loopback at all.
--host 0.0.0.0-H 0.0.0.0--keep-aliveReply with the URL itself, not just "it is running". Say that the box keeps costing
money until , and that the URL is public to anyone who has it —
puts credentials in front of it.
box delete --yesbox public-url 3000 --basic-auth“用 Upstash Box 给我做个贪吃蛇游戏”是要求在一个 box 中构建、运行,并回复一个用户可以打开的 URL。请完成整个过程;不要停留在写完文件。
bash
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 自带的 模块而不是包:无需安装、无需网络获取,并且可以在裸 运行时上工作。
httpnode在发布端口前检查它,并检查它绑定到什么地址,而不仅仅是它能否响应。绑定在 的服务器可以响应 box 内部的 curl,但仍然无法发布:代理通过地址访问容器,因此 会返回 502。这就是上面检查 grep 而不是 curl localhost 的原因——curl localhost 恰好会在失败的情况下通过。
127.0.0.1box public-url0.0.0.0大多数开发服务器需要明确指定:Vite 和许多其他服务器用 ,有些用 ,少数完全无法移出 loopback。
--host 0.0.0.0-H 0.0.0.0--keep-alive回复时给出 URL 本身,而不仅仅是“它正在运行”。要说明 box 会持续计费直到 ,并且该 URL 对任何拥有它的人都是公开的—— 可以为它加上凭据保护。
box delete --yesbox public-url 3000 --basic-authFiles
文件
Paths are relative to .
/workspace/homebash
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 destinationWrite code with and stdin. Passing source as an argument mangles it in the shell.
-To search, use the box's own tools: .
box exec -- grep -rn TODO src路径相对于 。
/workspace/homebash
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 srcGit
Git
A clone lands in a directory named after the repo, and every git verb except
needs that directory via . Without it git runs at the workspace root, which is not
a repository.
clone-Cbash
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 bot@example.com
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"Use for anything longer than a sentence: a body worth writing does
not survive shell quoting. reads stdin.
--body-file-box git execgitPrivate repos and PRs need a token at creation: .
box create --no-repl --git-token $GITHUB_TOKENclone 会落在一个以仓库命名的目录中,除 外的每个 git 动词都需要通过 指定该目录。否则 git 会在工作区根目录运行,而那里不是仓库。
clone-Cbash
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 bot@example.com
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"对于超过一句话的内容,请使用 :值得写的正文经不起 shell 引用的折腾。 从 stdin 读取。
--body-file-box git execgit私有仓库和 PR 在创建时需要 token:。
box create --no-repl --git-token $GITHUB_TOKENAttaching a screenshot to a pull request or issue
将截图附加到 pull request 或 issue
--attach#The path is read inside the box, relative to . A browser screenshot is written to
the machine running the CLI, not into the box, so it has to be uploaded first. That
upload is the step people miss:
-Cbash
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'A reference in the body is rewritten to point at the uploaded
asset, so the image renders in the issue instead of pointing at a path that exists
only inside the box.
Four rules are enforced, each a 400 before anything is created: the extension must be
png, jpg, jpeg, gif, webp, mp4, mov or webm; at most 50 files; the path must stay
inside the directory; and a video cannot carry alt text.
-CWhen some attachments upload and others fail, the item is still created and its URL
is still returned, with a alongside it. Text output prints the warning on
its own line, and carries it as the field. Check it before
reporting the issue as filed with its evidence attached.
warning--jsonwarning--attach#路径在 box 内读取,相对于 。浏览器截图会写入运行 CLI 的机器,而不是 box 内,因此必须先上传。这一步是大家容易遗漏的:
-Cbash
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'正文中的 引用会被重写为指向已上传的资产,因此图片会在 issue 中渲染,而不是指向仅存在于 box 内的路径。
有四个规则会被强制检查,任何内容创建前都会以 400 拒绝:扩展名必须是 png、jpg、jpeg、gif、webp、mp4、mov 或 webm;最多 50 个文件;路径必须保持在 目录内;视频不能携带替代文本。
-C当部分附件上传成功、部分失败时,条目仍会被创建,其 URL 仍会返回,并附带 。文本输出会在单独一行打印该警告, 会将其作为 字段携带。在报告 issue 已提交并附有证据之前,请检查它。
warning--jsonwarningAgent
Agent
If the box was created with an agent, hand it a task:
bash
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.txtText goes to stdout, tool calls to stderr. Prefer doing the work yourself with the
commands above; is for delegating a whole task to the box's own agent.
box run如果 box 是带 agent 创建的,可以交给它一个任务:
bash
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 自己的 agent。
box runWatching and stopping work
查看和停止工作
bash
box status runs # id, type, status, duration, cost
box status logs --limit 50
box cancel <run-id> # ids come from status runsA run started by another process cannot be stopped any other way:
takes the id, so a long agent run or build is interruptible from a fresh shell.
box cancelbash
box status runs # id, type, status, duration, cost
box status logs --limit 50
box cancel <run-id> # ids come from status runs由另一个进程启动的 run 无法用其他方式停止: 需要 id,因此长时间的 agent run 或构建可以从新 shell 中断。
box cancelBrowser
浏览器
Only on a box created with . Chromium runs inside the box, so it
reaches your app on with no public URL involved. What
lives outside is only the control path: these commands reach Chromium through
the API, so there is no spelling of them. A script running in the box
can still talk to Chromium directly over CDP, which is the escape hatch at the
end of this section.
--browserhttp://localhost:3000box execbash
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 tabEvery is metered: it takes an instruction in words and needs
a model to read the page. The SDK can replay an result for free,
but the CLI takes only the string form, so a loop of calls costs a model
call each time. , , and are not metered.
box browser actobserve()actcontentgotoscreenshotcloseRecordings, when you need to show what happened rather than describe it:
bash
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.mp4Chromium starts on first use, so the very first is slower
than the rest, and anything talking to CDP directly fails until it has run once.
box browser open--tab <id>screenshotPull structured data off the page with a flat JSON Schema file:
bash
echo '{"type":"object","properties":{"price":{"type":"string"}},"required":["price"]}' > s.json
box browser extract "the listed price" --schema s.jsonA property not named in is optional. Nested objects are refused.
required仅在用 创建的 box 上可用。Chromium 运行在 box 内部,因此它可以通过 访问你的应用,无需公共 URL。外部只有控制路径:这些命令通过 API 访问 Chromium,因此没有对应的 形式。在 box 内运行的脚本仍然可以通过 CDP 直接与 Chromium 通信,这是本节末尾的逃生通道。
--browserhttp://localhost:3000box execbash
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每次 都按量计费:它接收文字指令,需要模型来读取页面。SDK 可以免费重放 的结果,但 CLI 只接受字符串形式,因此循环调用 每次都会消耗一次模型调用。、、 和 不按量计费。
box browser actobserve()actcontentgotoscreenshotclose当你需要展示而不是描述发生了什么时,可以使用录制:
bash
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.mp4Chromium 在首次使用时启动,因此第一次 会比后续慢,并且任何直接与 CDP 通信的操作在它运行过一次之前都会失败。
box browser open在只有一个标签页打开时 是可选的,有多个标签页时则是必需的。 写入文件是因为 stdout 用于文本,而且该文件会落在本机而不是 box 内。要将截图放到 pull request 或 issue 上,请参阅“将截图附加到 pull request 或 issue”。
--tab <id>screenshot使用扁平的 JSON Schema 文件从页面提取结构化数据:
bash
echo '{"type":"object","properties":{"price":{"type":"string"}},"required":["price"]}' > s.json
box browser extract "the listed price" --schema s.json未在 中命名的属性是可选的。嵌套对象会被拒绝。
requiredCapturing straight into the box
直接捕获到 box 内
box browser screenshot --full-page -o page.pngChromium's CDP is open on from inside the box with no
token, so a script running there captures and writes in one step, and can clip
to a single element, which the CLI does not expose:
127.0.0.1:9222Write the script with rather than inlining it: the remote
shell is , and quoting a program through is where this goes
wrong.
box files writeshbox execbash
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 boxThe is what makes this a full-page capture rather than a viewport one;
does the same thing. An element-clipped capture is the same call
with that element's box as the clip, and that one has no CLI flag. Node's global
and are enough, so nothing has to be installed, but Chromium
must have been started once by a command first.
clip--full-pagefetchWebSocketbox browserThis is only worth it when the image should stay in the box or you need a
capture the CLI cannot make. Otherwise then is
shorter.
screenshot -ofiles uploadbox browser screenshot --full-page -o page.pngChromium 的 CDP 在 box 内部 的 上开放,无需 token,因此在其中运行的脚本可以一步完成捕获和写入,并且可以裁剪到单个元素,而 CLI 不提供此功能:
127.0.0.1:9222使用 编写脚本,而不是内联:远程 shell 是 ,通过 给程序加引号正是容易出错的地方。
box files writeshbox execbash
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 boxclip--full-pagefetchWebSocketbox browser只有当图片应该留在 box 内,或者你需要 CLI 无法生成的捕获时,这才值得。否则 然后 更简短。
screenshot -ofiles uploadSchedules
定时任务
Cron on the box, in UTC. Nothing inside the container can register one.
bash
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在 box 上运行 cron,使用 UTC 时间。容器内的任何东西都无法注册 cron。
bash
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>updateBox configuration
Box 配置
bash
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 harnessAccount-level settings, which apply to boxes you create later rather than to
this one:
bash
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 stagingBoth and take the value as an argument, so a
secret passed either way is visible in and lands in shell history. Neither
is a secrets mechanism; keep real credentials out of both and use a token the
box fetches for itself.
box env setbox create --envpsbash
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,而不是当前这个:
bash
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 stagingbox env setbox create --envpsFlag reference
标志参考
The flags the walkthroughs above do not reach. Every command also takes the
global , and .
--box--json--tokenbash
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-Cbox exec--cwdbox gitbox schedule--folder上面教程未涉及的标志。每条命令也接受全局的 、 和 。
--box--json--tokenbash
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-Cbox exec--cwdbox gitbox schedule--folderOutput
输出
Data goes to stdout, diagnostics to stderr, so piping is safe. prints the
result as JSON with no wrapper, on every command that returns data. The ones that
open a REPL or print a shell script (, , )
reject it rather than answering an automation caller with a prompt:
--jsonconnectinit-democompletionbash
box files list --json | jq -r '.[].name'
box get "$(cat .box)" --jsonbox init-demobox completion数据输出到 stdout,诊断信息输出到 stderr,因此管道是安全的。 在每条返回数据的命令上都会以 JSON 形式打印结果,不包含包装。那些打开 REPL 或打印 shell 脚本的命令(、、)会拒绝它,而不是用提示符回应自动化调用者:
--jsonconnectinit-democompletionbash
box files list --json | jq -r '.[].name'
box get "$(cat .box)" --jsonbox init-demobox completion