sandbin

CLI

A third way to talk to sandbin, alongside the browser playground and the raw HTTP API — run code straight from a terminal, with no server required by default.

Install

git clone https://github.com/ayazdoruck/sandbin.git
cd sandbin
npm install
npm link

npm link puts sandbin on PATH. Without it, node bin/sandbin.mjs ... works identically.

Two modes, one command

Local (the default). No server, no port — bin/sandbin.mjs imports sandbox.mjs directly and calls run() itself:

sandbin run script.py

Remote, with --server. Submits over the exact same HTTP + WebSocket protocol the browser frontend speaks against a running instance, local or not:

sandbin run script.py --server localhost:8080

Local is the default on purpose: the more common case for someone who already has the repo checked out is "run this file through the real sandbox right now," which needs nothing running and no port to pick. Local mode needs the same host requirements as npm start itself — see Architecture.

Getting code in

Exactly one source, never ambiguous about which wins:

sandbin run script.py                    # from a file
sandbin run -l python -e 'print(1 + 1)'  # inline
cat script.sh | sandbin run -l bash      # piped stdin

Language is inferred from the file extension (.py, .sh/.bash, .js/.mjs/.cjs, .c, .go) when a file is given. -l/--language overrides it, or supplies it outright for -e or piped input.

Output and exit codes

Stdout and stderr stream live, each to their own real stream, as chunk events arrive — in local mode via run()'s own onChunk, in remote mode via the same WebSocket messages the frontend renders. A summary prints to stderr once it finishes:

$ sandbin run -l python -e 'print("hi")'
hi

verdict      ok
duration     14ms
cpu          6ms
peak memory  3.9 MB
exit code    0

The process exits 0 for verdict ok, 1 otherwise — deliberately not a passthrough of the guest's own exit code. A guest that calls sys.exit(3) still means the CLI itself succeeded at running it and observing that; the verdict (printed alongside duration, CPU and peak memory) is what actually failed. Safe to use directly in shell scripts and CI:

sandbin run deploy-check.py || echo "check failed, see verdict above"
A compile failure needed a second look

run()'s onChunk only ever covers the execute phase — the compile phase (c, go) never gets the callback at all. A compile failure was streaming nothing and then printing a bare verdict compile_error with zero indication of what the compiler actually said. The CLI now prints the result's own captured stderr in full for exactly the two verdicts where the streamed callback wouldn't have shown it (compile_error, setup_failed) — not a change to sandbox.mjs itself, the diagnostic was already there, the CLI just wasn't showing it.

If the connection drops mid-run: --reconnect

--server mode is two steps — POST /runs accepts the job and hands back a runId, then a WebSocket streams it. Those two steps can be separated by an arbitrary amount of time and an arbitrary number of dropped connections without losing the run itself: the server keeps executing regardless of whether anything is attached to watch it, and GET /runs/:runId/stream can be attached to at any point — while it's still running, it replays every buffered chunk/stats event and then keeps streaming live; once it's finished, it immediately replays the final result and closes. The CLI now prints the runId the moment a run is accepted, specifically so a caller has something to reconnect with if the socket dies before finished arrives:

$ sandbin run server.js --server localhost:8080
sandbin: run 7e2b1c4a-...-91fd accepted — if this connection drops, reconnect with: sandbin run --server localhost:8080 --reconnect 7e2b1c4a-...-91fd
(connection drops here)
sandbin: connection to run 7e2b1c4a-...-91fd closed before it finished
sandbin: the run may still be in progress — reconnect with: sandbin run --server localhost:8080 --reconnect 7e2b1c4a-...-91fd

$ sandbin run --server localhost:8080 --reconnect 7e2b1c4a-...-91fd
... streams the rest, or the final result if it already finished ...

Two things worth being precise about, since they matter for unattended callers specifically. First, --reconnect attaches to an existing run — it never submits a new one, so it's safe to retry without risking a duplicate job. Second, a plain sandbin run retry is not idempotent: POST /runs has no idempotency key or dedup concept, so re-running the original command after a drop submits a genuinely independent sandbox job rather than resuming the first one. If a caller can't hold onto the printed runId, that's the boundary it's up against today.

Other commands

CommandDoes
sandbin languageslists what this host can run — reads local IMAGES directly, so go only appears if a toolchain was actually found
sandbin keys createwraps POST /keys against a running server
sandbin keys status <key>wraps GET /keys/:key
sandbin --helpevery flag, including the limit overrides below

Flags

FlagMeaning
-l, --language <lang>override or supply the language
-e, --eval <code>inline code instead of a file
-i, --stdin <file>file whose contents become the guest's stdin (- reads this process's own stdin)
-s, --server <url>submit to a running server instead of running locally (env SANDBIN_SERVER)
-k, --key <key>API key sent as X-Sandbin-Key (env SANDBIN_KEY)
--reconnect <runId>skip submission, reattach to a run already in flight on --server
--jsonprint the full result object once, instead of streaming
--memory / --cpu / --timeout / --pidsoverride the matching resource limit

--json skips live streaming entirely and prints the whole result object — verdict, stdout, stderr, durationMs, cpuMs, peakBytes, and the rest — once, for scripting:

sandbin run server.js --json | node -pe "JSON.parse(require('fs').readFileSync(0)).verdict"

Tested as a real subprocess

src/cli-test.mjs spawns the actual bin/sandbin.mjs with node, not by importing its functions — both against run() directly (local) and against a real createServer() on an ephemeral port (remote), asserting on captured stdout/stderr/exit code exactly the way a real caller would observe them. See Testing.