All classes live under the Xshellz module. Every constructor-style class
method accepts the same connection options:
api_key:— personal access token; falls back toXSHELLZ_API_KEY.api_url:— control-plane base URL; falls back toXSHELLZ_API_URL, thenhttps://api.xshellz.com/v1.http:— HTTP adapter override (anything responding tocall(method, url, headers, body)and returning an object withstatusandbody); used by the test suite to fake the network.
A "CommandResult" below is Xshellz::CommandResult — a Struct with stdout
(String), stderr (String), exit_code (Integer), and ok? (true when
exit_code == 0). A non-zero exit code never raises — it is data, exactly
like a local subprocess.
Spawn a new sandbox and return it once it is running. Generates an
in-memory ed25519 keypair; the private key (sbx.private_key_openssh) never
leaves your process. With a block, the sandbox is yielded and destroyed when
the block exits (unless detach was called); the block's value is returned.
Raises AuthError (bad key/scopes/account gate), QuotaError (plan sandbox
limit), ApiError (throttle 429, host capacity 503, ...).
Sandbox.get_or_create(name, api_key: nil, api_url: nil, http: nil, private_key: nil, keystore: :default)
Find the account's sandbox named exactly name, or create it. The permanent
workflow:
- Not found → creates the box with that name and saves the generated private key into the keystore.
- Found → attaches using
private_key:if given, else the keystore's saved key. Astoppedbox isstarted first. No key anywhere →MissingKeyError(its message says which file was expected).
keystore: accepts :default (~/.xshellz/keys/), a directory String, a
Keystore instance, or false/nil to disable persistence (then only
create-or-error is possible unless private_key: is passed).
Security note: keystore files are plaintext OpenSSH private keys, 0600, in
a 0700 directory. Delete the file and kill the box to revoke.
Attach to an existing sandbox by UUID with an explicit OpenSSH private key
(the private_key_openssh of the original sandbox). Raises
SandboxNotRunningError when the UUID is not among the account's sandboxes.
All of the account's sandboxes. SandboxInfo is a Struct mirroring the wire:
uuid, name, status, ssh_command, ssh_host, ssh_port,
web_terminal_ready, always_on, trial_hours_remaining, spawned_at,
created_at, isolation, gvisor.
Account-level provisioning template (GET/PUT /v1/shells/agent/boxfile).
The manifest is seeded into ~/xshellz.box on every newly created box, so
destroy+recreate reproduces your package environment. set_boxfile(nil) (or a
blank string) clears it; max size 16 KB. Existing boxes are not modified.
| Method | Returns |
|---|---|
uuid |
sandbox UUID (String) |
name |
sandbox name (String) |
status |
"running", "stopped", ... |
ssh_host / ssh_port |
SSH endpoint |
ssh_command |
copy-paste ssh -p ... root@... line |
info |
the last-known SandboxInfo |
private_key_openssh |
this box's OpenSSH private key (persist to reconnect) |
detached? |
whether detach was called |
Run a shell command and wait. env names are validated; values are exported
in the remote shell. The optional block streams (:stdout | :stderr, chunk)
as output arrives. Raises SandboxNotRunningError when the box is not
running, CommandTimeoutError when timeout seconds elapse.
Start command as a detached background process (nohup, stdin from
/dev/null) and return immediately. Combined stdout+stderr streams to
~/.xshellz/jobs/<id>.log inside the box; a .pid file is kept alongside so
jobs can rebuild handles later. name prefixes the generated job id.
Raises Error when the process fails to start.
Handles for every job whose pid file still exists in ~/.xshellz/jobs/
(spawned by any process, any time). Probe each with running?/logs.
Write code to a temp file in the box, execute the matching interpreter, and
always delete the temp file (even on timeout/error). Languages: python
(runs python3), node, bash, ruby, php — anything else raises
UnsupportedLanguageError. Otherwise identical semantics to run.
| Method | Effect |
|---|---|
write_file(path, data) |
write a String into the box (SFTP) |
read_file(path) → String |
read a file from the box |
upload(local_path, remote_path) |
copy a local file in |
download(remote_path, local_path) |
copy a file out |
Resume an idle-stopped box (POST /{uuid}/start); same /home, same key.
Raises SandboxNotRunningError when there is no stopped box to start.
Reboot a running box (POST /{uuid}/restart): re-runs the entrypoint,
/home preserved. The SSH connection is re-established on the next
data-plane call.
Live resource usage (GET /{uuid}/stats). Struct fields (all mirroring the
wire): mem_used_mb, mem_limit_mb, mem_allowed_mb, cpu_percent,
cpu_allowed_vcpus, cpu_throttled_periods, pids_current, pids_allowed,
disk_used_mb, disk_allowed_mb, net_rx_mb, net_tx_mb, blk_read_mb,
blk_write_mb. The *_allowed_* values are your plan's ceilings.
Top processes + session info (GET /{uuid}/procs). Struct fields: procs
(Array<ProcessInfo> — pid, comm, cpu, mem), sessions (active SSH
session count), agents (detected agent process names), disk_used_mb,
disk_allowed_mb.
Mint a fresh signed web-terminal URL (GET /{uuid}/terminal) — a root shell
in the browser, no SSH client needed. The embedded token expires after
about 1 hour; call again for a fresh URL instead of storing it.
kill— destroy the box (DELETE /{uuid}); idempotent (404 swallowed).detach— keep the box alive when thecreateblock exits.refresh— re-fetchinfofrom the control plane.close— close the SSH connection only; the box keeps running.
Returned by spawn and jobs. Attributes: id, pid, log_path.
| Method | Effect |
|---|---|
running? |
live probe (kill -0 in the box) |
logs(tail_lines: 100) → String |
last N lines of the job's combined output |
stop(grace: 5) |
SIGTERM, then SIGKILL after grace seconds; returns true when SIGTERM sufficed |
Local private-key store backing get_or_create. Default directory
~/.xshellz/keys/, one <sanitized-name>.key file per sandbox, 0600.
| Method | Effect |
|---|---|
Keystore.new(dir: Keystore.default_dir) |
store rooted at dir |
path_for(name) → String |
where name's key lives |
save(name, key) → path |
persist a key (0600) |
load(name) → String | nil |
read a stored key |
delete(name) → Boolean |
remove a stored key |
All inherit Xshellz::Error < StandardError.
| Error | Raised when |
|---|---|
AuthError |
401/403 — missing/invalid token, scopes, account gates |
QuotaError |
plan sandbox limit reached or no sandbox entitlement |
MissingKeyError |
get_or_create found the box but no private key |
SandboxNotRunningError |
the box is not running (or not found) |
CommandTimeoutError |
run/run_code timeout: exceeded |
UnsupportedLanguageError |
run_code given an unknown language |
ApiError |
any other 4xx/5xx; has .status and .body |