upstash-box-cli について
upstash-box-cli は Upstash Box のリモートコンテナを操作するコマンドラインツールです。コマンド、ファイル、Git リポジトリを隔離環境で扱う開発に向き、ブラウザーやエージェントの実行、プレビュー URL の公開にも対応します。
主な機能
- 01リモートコンテナの作成・一時停止・再開
- 02コマンド実行とコンテナ内ファイルの管理
- 03リポジトリのクローンと Git 操作
- 04コンテナ内 Chromium の制御
- 05コンテナのポートに公開 URL を作成
- 06スナップショット作成とエージェントへのタスク委任
活用シーン
- 01リモートコンテナで依存関係を導入してテストを実行
- 02隔離環境で開発し、プレビュー URL を共有
- 03コンテナ内ブラウザーでページを確認してスクリーンショットを取得
- 04コンテナに設定したエージェントへタスクを委任
MCP 対応
コンテナ作成時に --mcp または --mcp-file で MCP サーバーを設定でき、アカウント単位の MCP サーバー設定も新しいコンテナに統合できます。
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ upstash/ skills --skill upstash-box-cliインストール後、Claude Code で/upstash-box-cliを入力して読み込まれたか確認します。
詳細
box はこのマシン上ではなくリモートコンテナ上で動作します。ローカルのファイルツールやシェルツールはローカルで動作し、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 は課金され続けます。これがないと --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 やシェル履歴に残るため、秘匿情報にはファイルの使用を推奨します。
スキル ID は owner/repo/skill-name の 3 つの部分からなります。不正な形式はサーバー側で警告されるだけなので、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}
リモートシェルは bash ではなく sh です。box exec 内のヒアドキュメントは Syntax error: redirection unexpected で失敗します。代わりに box files write - でファイルを書き込むか、box に bash がある場合はコマンドを bash -c でラップしてください。
対話型シェルが必要な場合は、直接 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
引数が 1 つの場合はシェル式としてそのまま送信されるため、パイプやリダイレクションが機能します。引数が複数の場合は argv であり、個別に引用されるため、スペースを含む引数も 1 つの引数のままです。
リモートコマンドの終了コードはそのまま渡されるため、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
シェルのワンライナーがプログラムよりも劣る場合のインラインコード:
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 を返します。だから上のチェックでは、localhost を curl するのではなく 0.0.0.0 を grep しているのです。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 で書きましょう。ソースを引数として渡すとシェルで壊れます。
検索するには 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 を使いましょう。書く価値のある本文はシェルのクォートに耐えられません。- は stdin を読み取ります。
box git exec は先頭の git なしで git の引数を受け取り、git の終了コードをそのまま渡します。
プライベートリポジトリと PR は作成時にトークンが必要です。box create --no-repl --git-token $GITHUB_TOKEN。
プルリクエストや issue にスクリーンショットを添付する
--attach は新しいプルリクエストや issue に画像や動画をアップロードし、複数回繰り返せます。画像の alt テキストは # の後に書きます。動画はプレーヤーとしてレンダリングされ、alt テキストは取りません。
パスはボックス内で、-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 の中にしか存在しないパスを指すのではなく、issue 内でレンダリングされます。
4 つのルールが適用され、いずれも何かが作成される前に 400 になります。拡張子は png、jpg、jpeg、gif、webp、mp4、mov、webm のいずれかであること。最大 50 ファイルまで。パスは -C ディレクトリ内に留まること。そして動画は alt テキストを持てません。
一部の添付がアップロードされ、他のものが失敗した場合でも、アイテムは作成され、その URL も返されますが、warning が併せて返されます。テキスト出力では警告が独自の行に出力され、--json では warning フィールドとして含まれます。証拠が添付された状態で issue が起票されたと報告する前に、これを確認しましょう。
エージェント
box がエージェント付きで作成された場合、タスクを渡します。
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 自身のエージェントに委任するためのものです。
作業の監視と停止
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 を受け取るため、長時間のエージェント実行やビルドも新しいシェルから中断できます。
ブラウザ
--browser を付けて作成したボックスでのみ利用できます。Chromium はボックス内部で実行されるため、公開 URL を介さずに http://localhost:3000 で自分のアプリに到達できます。外部にあるのは制御経路だけです。これらのコマンドは API を通じて Chromium に到達するため、box exec で書ける形のものはありません。ボックス内で実行されるスクリプトは、引き続き 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> はタブが 1 つ開いている間は省略可能で、複数になると必須です。screenshot は stdout がテキストを運ぶためファイルに書き出しますが、そのファイルはボックス内ではなくこのマシン上に置かれます。スクリーンショットをプルリクエストや issue に添付するには、「プルリクエストまたは issue にスクリーンショットを添付する」を参照してください。
フラットな JSON Schema ファイルでページから構造化データを取り出します:
echo '{"type":"object","properties":{"price":{"type":"string"}},"required":["price"]}' > s.json
box browser extract "the listed price" --schema s.json
required に記載されていないプロパティは任意です。ネストされたオブジェクトは拒否されます。
ボックスへ直接キャプチャする
box browser screenshot --full-page -o page.png が手軽な方法で、画像をこのマシン上に置ける場合はこれで十分です。ただし、これは CLI を実行しているマシンに書き出すため、画像をボックスに入れるにはアップロードのコストがかかります。
Chromium の CDP は、ボックス内部から 127.0.0.1:9222 でトークンなしで開いているため、そこで実行されるスクリプトはキャプチャと書き込みを 1 ステップで行え、CLI では公開されていない単一要素へのクリップも可能です:
スクリプトはインラインで書くのではなく box files write で書いてください。リモートシェルは 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 も同じことをします。要素でクリップしたキャプチャは、その要素のボックスを clip にして同じ呼び出しをするもので、こちらには CLI フラグがありません。Node のグローバルな fetch と WebSocket で十分なので、何もインストールする必要はありませんが、まず box browser コマンドで Chromium を一度起動しておく必要があります。
これが価値を持つのは、画像をボックス内に残したい場合や、CLI では作成できないキャプチャが必要な場合だけです。それ以外では、screenshot -o の後に files upload のほうが短く済みます。
スケジュール
ボックス上の 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 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 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 で見え、シェル履歴にも残ります。どちらもシークレットの仕組みではありません。本物の認証情報はどちらにも入れず、ボックス自身が取得するトークンを使用してください。
フラグリファレンス
上記のウォークスルーで触れなかったフラグです。すべてのコマンドはグローバルな --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 を開いたりシェルスクリプトを出力したりするもの(connect、init-demo、completion)は、自動化の呼び出し元にプロンプトで応答するのではなく、--json を拒否します。
box files list --json | jq -r '.[].name'
box get "$(cat .box)" --json
box init-demo と box completion は存在しますが、エージェント向けではなく人間向けです。一方はローカルのデモプロジェクトを雛形から生成し、もう一方はシェルの補完スクリプトを出力します。