Loading...
Loading...
Drive an Upstash Box (a remote sandboxed workspace) from the terminal with the `box` CLI. Use when asked to run commands, edit files, clone repos, run builds or tests, publish a public URL, browse or screenshot a page, open a pull request or issue with a screenshot attached, schedule recurring work, run an AI agent, or do any work inside a box rather than on this machine.
npx skill4agent add upstash/skills upstash-box-cliboxboxnpm i -g @upstash/box-cli--tokenexport UPSTASH_BOX_API_KEY=box_...--box <id>$BOX_ID.boxbox 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--sizebox create --no-replbox 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--init-commandbox create --no-repl --keep-alive # stays up when idle
box create --no-repl --keep-alive --init-command "npm ci" # startup scriptbox 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=valuepsowner/repo/skill-name--envbox env set--envpaused.boxcd--boxbox statusbox 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 terminalbox createbox connect--no-replbox from-snapshot--no-replbox 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>--boxbox exec -- npm install
box exec -C repo -- npm test
box exec --json -- node -e 'console.log(1)' # {stdout, stderr, exit_code}shbox execSyntax error: redirection unexpectedbox files write - bash -cssh <box-id>@us-east-1.box.upstash.comboxuserbox exec -- 'sudo npm install -g @upstash/docs7' # EACCES without sudobox exec -- npm test && ...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 3000box code - --lang python < script.py
box code 'console.log(1 + 1)' --lang jsbox 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 withhttpnode127.0.0.1box public-url0.0.0.0--host 0.0.0.0-H 0.0.0.0--keep-alivebox delete --yesbox public-url 3000 --basic-auth/workspace/homebox 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-box exec -- grep -rn TODO srcclone-Cbox 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"--body-file-box git execgitbox create --no-repl --git-token $GITHUB_TOKEN--attach#-Cbox 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'-Cwarning--jsonwarningbox 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.txtbox runbox status runs # id, type, status, duration, cost
box status logs --limit 50
box cancel <run-id> # ids come from status runsbox cancel--browserhttp://localhost:3000box execbox 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 tabbox browser actobserve()actcontentgotoscreenshotclosebox 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.mp4box browser open--tab <id>screenshotecho '{"type":"object","properties":{"price":{"type":"string"}},"required":["price"]}' > s.json
box browser extract "the listed price" --schema s.jsonrequiredbox browser screenshot --full-page -o page.png127.0.0.1:9222box files writeshbox execbox 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 browserscreenshot -ofiles uploadbox 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 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 harnessbox 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 --envps--box--json--tokenbox 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--jsonconnectinit-democompletionbox files list --json | jq -r '.[].name'
box get "$(cat .box)" --jsonbox init-demobox completion