xshellz

package module
v0.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 17, 2026 License: MIT Imports: 23 Imported by: 0

README

xshellz-go

CI Go Reference

The official Go SDK for xShellz sandboxes — spawn a real Linux box from your program, run commands in it, and throw it away (or keep it).

What is a sandbox? A sandbox is a small, private Linux machine that runs in the cloud, isolated from everyone else by a gVisor kernel — so whatever runs inside (including code an AI wrote) can't touch your computer or anyone else's. You get root, a real filesystem, and network access; when you're done you delete it.

Quickstart

1. Install the SDK

go get github.com/xshellz/xshellz-go@v0.2.0

2. Get an API key

Sign up at app.xshellz.com and create a personal access token with read and write scopes in your account settings (the API endpoint behind that page is POST /v1/auth/tokens). Then:

export XSHELLZ_API_KEY="<your token>"

3. Hello, sandbox

sbx, err := xshellz.Create(ctx, nil) // reads XSHELLZ_API_KEY
if err != nil { log.Fatal(err) }
defer sbx.Close(ctx) // destroys the box; call sbx.Detach() first to keep it

res, _ := sbx.Run(ctx, "echo hello from $(hostname)", nil)
fmt.Print(res.Stdout)

That's it — Create returns when the box is up, Run executes over SSH, and Close destroys the box.

Recipes

Run a command
res, err := sbx.Run(ctx, "python3 -c 'print(6*7)'", nil)
fmt.Print(res.Stdout)   // "42\n"
fmt.Print(res.ExitCode) // non-zero exit is DATA, not an error

Pass &xshellz.RunOptions{Cwd: "/srv", Env: map[string]string{"K": "v"}} to set a working directory or environment, and Stdout/Stderr writers to stream output live.

A permanent named box that survives restarts

GetOrCreate gives you the same box every time you run your program. The first call creates a box named "my-agent" and saves its SSH key to the local keystore (~/.xshellz/keys/my-agent.pem, chmod 0600); every later call finds the box by name, loads the key, and reattaches — starting the box first if it was idle-stopped.

sbx, err := xshellz.GetOrCreate(ctx, "my-agent", nil)
if err != nil { log.Fatal(err) }
defer sbx.Close(ctx) // a GetOrCreate box is already detached: Close just drops
                     // the SSH connection and leaves the box alive
// ...
// _ = sbx.Kill(ctx) // when you really want it gone: destroy it explicitly

A permanent box comes back already detached, so Close never destroys it — call sbx.Kill(ctx) to actually tear it down.

Security note: the keystore is a plaintext private key on disk (0600, in a 0700 dir). Anyone who can read the file can SSH into your box — delete the file to revoke local access. Set GetOrCreateOptions.KeystoreDir to relocate it, or DisableKeystore: true to manage keys yourself (then attaching to an existing box requires PrivateKeyPEM, and a missing key fails with ErrMissingKey).

Background jobs (spawn)

Spawn starts a command detached — it keeps running after Spawn returns and after your program exits, for as long as the box is up:

job, err := sbx.Spawn(ctx, "python3 -m http.server 8000", &xshellz.SpawnOptions{Name: "web"})

running, _ := job.IsRunning(ctx) // kill -0 probe
out, _ := job.Logs(ctx, 100)     // last 100 log lines (stdout+stderr)
_ = job.Stop(ctx)                // SIGTERM, then SIGKILL after ~5s grace

jobs, _ := sbx.Jobs(ctx) // list all spawned jobs + liveness

Logs live on the box under ~/.xshellz/jobs/<job-id>.log. Jobs do not survive a box Restart or stop.

Run AI-generated code safely (run_code)

Hand untrusted code to the box, not to your machine. RunCode writes the snippet to a temp file, runs the right interpreter, always deletes the file, and returns the same result type as Run:

res, err := sbx.RunCode(ctx, "python", "print(sum(range(10)))", nil)
fmt.Print(res.Stdout) // "45\n"; a traceback shows up as res.ExitCode != 0

Languages: python (python3), node, bash, ruby, php. Anything else returns ErrUnsupportedLanguage listing the supported ones.

Files up and down
_ = sbx.WriteFile(ctx, "/srv/config.json", []byte(`{"debug": true}`)) // bytes → box
data, _ := sbx.ReadFile(ctx, "/srv/report.txt")                       // box → bytes
_ = sbx.Upload(ctx, "./model.bin", "/srv/model.bin")                  // local file → box
_ = sbx.Download(ctx, "/srv/out.csv", "./out.csv")                    // box → local file

Binary-safe both ways; whole files are buffered in memory (sized for configs and artifacts, not multi-GB blobs).

Check resource usage (stats)
stats, _ := sbx.Stats(ctx)
fmt.Printf("mem %d/%d MB, cpu %.1f%%, disk %d/%d MB\n",
    stats.MemUsedMB, stats.MemAllowedMB, stats.CPUPercent,
    stats.DiskUsedMB, stats.DiskAllowedMB)

procs, _ := sbx.Procs(ctx) // top processes, SSH session count, detected agents
Open a web terminal in the browser
url, _ := sbx.TerminalURL(ctx)
fmt.Println(url) // open this in a browser for a live shell on the box

The URL embeds a signed token valid for about 1 hour — mint a fresh one each time instead of storing it.

Provisioning template (boxfile)

The account-level xshellz.box manifest is seeded into ~/xshellz.box on every newly created box — use it to preinstall dependencies so fresh boxes come up ready:

_, _ = xshellz.SetBoxfile(ctx, "apt: ffmpeg jq\npip: requests", nil)
manifest, _ := xshellz.GetBoxfile(ctx, nil)
_, _ = xshellz.SetBoxfile(ctx, "", nil) // empty clears it
Lifecycle
_ = sbx.Start(ctx)   // resume an idle-stopped box (/home preserved)
_ = sbx.Restart(ctx) // reboot a running box (/home preserved, processes are not)
sbx.Detach()         // make Close keep the box
_ = sbx.Close(ctx)   // drop SSH + destroy the box (unless detached)
_ = sbx.Kill(ctx)    // drop SSH + destroy the box unconditionally (even if detached)

Full API reference

Every public function, type, parameter, and error is documented in docs/API.md (and on pkg.go.dev).

Configuration

Env var Meaning Default
XSHELLZ_API_KEY Personal access token (read+write scopes) — (required)
XSHELLZ_API_URL Control-plane base URL https://api.xshellz.com/v1

Precedence: explicit option (APIKey/APIURL on the options struct) > environment variable > default.

Errors

All sentinels work with errors.Is; API failures also carry an *xshellz.APIError (via errors.As) with the HTTP status, machine code, and message:

Sentinel When
ErrNoAPIKey no token in options or environment
ErrAuth 401/403 — bad token, missing scope, plan not entitled, verification required (APIError.Code == "verification_required")
ErrQuota the plan's concurrent box limit is reached — Connect/GetOrCreate the existing box instead
ErrNotFound 404 — unknown UUID, or Start on a box that isn't stopped
ErrNotRunning Run/Spawn/file ops on a stopped box — call sbx.Start(ctx)
ErrMissingKey GetOrCreate found the named box but no private key (keystore file missing or keystore disabled)
ErrUnsupportedLanguage RunCode with a language it doesn't know

v0 limits and design notes

  • Free tier: 1 concurrent box, which idle-stops after ~30 minutes. Creation is throttled to 10/min. Create returns ErrQuota while a box exists — Connect/GetOrCreate it (or Close it). A stopped box resumes with sbx.Start(ctx); /home is preserved.
  • Data plane is SSH (root@ssh_host:ssh_port, fake-root inside gVisor). Host-key checking uses ssh.InsecureIgnoreHostKey: boxes are ephemeral and mint a fresh host key each spawn, so there is nothing stable to pin yet. Treat the box as untrusted compute, not a secret store.
  • File transfer rides cat over SSH exec (binary-safe both ways) instead of SFTP, so the SDK works against any sshd and keeps golang.org/x/crypto as the only dependency.
  • Command execution: Run passes your command verbatim to the remote shell; with Cwd/Env set it wraps as cd CWD && env K=V ... sh -c 'CMD'.

Local development (Docker)

No local Go toolchain needed — run gofmt + go vet + the full test suite (with the ≥80% coverage gate) in a container (golang:1.22, module and build caches persisted in named volumes so re-runs are fast):

docker compose run --rm test

The repo is mounted at /work; GOMODCACHE and GOCACHE point at named volumes, so nothing is written into the repo itself except cover.out (gitignored).

License

MIT — see LICENSE.

Documentation

Overview

Package xshellz is the official Go SDK for xShellz sandboxes: throwaway, gVisor-isolated Linux boxes you can spawn and run commands in from your own program.

The SDK talks to two planes:

  • Control plane: the xShellz REST API (https://api.xshellz.com/v1), authenticated with a personal access token (PAT). Create, list, start, and destroy sandboxes here.
  • Data plane: SSH directly to the box (as root, inside a gVisor-isolated user namespace). Create generates an in-memory ed25519 keypair per sandbox; the private key never leaves your process and the control plane only ever sees the public half.

Quickstart:

sbx, err := xshellz.Create(ctx, nil) // uses XSHELLZ_API_KEY
if err != nil {
	log.Fatal(err)
}
defer sbx.Close(ctx) // destroys the box; call sbx.Detach() to keep it

res, err := sbx.Run(ctx, "echo 42", nil)
fmt.Print(res.Stdout) // "42\n"

Security note: the SSH transport uses ssh.InsecureIgnoreHostKey — sandboxes are ephemeral and mint a fresh host key on every spawn, so there is nothing stable to pin in v0. Treat the box as untrusted compute, not a secret store.

Index

Constants

View Source
const (
	// StatusCreating means the box is being provisioned.
	StatusCreating = "creating"
	// StatusRunning means the box is up and reachable over SSH.
	StatusRunning = "running"
	// StatusStopped means the box is idle-stopped (resume it with Start).
	StatusStopped = "stopped"
)

Sandbox status values as reported by the control plane.

View Source
const DefaultAPIURL = "https://api.xshellz.com/v1"

DefaultAPIURL is the production control-plane base URL, used when neither the APIURL option nor the XSHELLZ_API_URL environment variable is set.

Variables

View Source
var (
	// ErrNoAPIKey is returned when no API key was supplied — neither in the
	// options nor via the XSHELLZ_API_KEY environment variable. Create a
	// personal access token with "read" and "write" scopes in your xShellz
	// account settings and export it as XSHELLZ_API_KEY.
	ErrNoAPIKey = errors.New("xshellz: missing API key: set XSHELLZ_API_KEY (or pass APIKey) to a personal access token with read+write scopes — create one in your xShellz account settings")

	// ErrAuth covers authentication and permission failures (HTTP 401/403):
	// an invalid or expired token, missing scopes, an account that is not
	// entitled to Agent Shells, or verification-required gates. Inspect the
	// wrapped *APIError's Code/Message for the specific reason.
	ErrAuth = errors.New("xshellz: authentication or permission denied")

	// ErrQuota is returned when the plan's concurrent sandbox limit is
	// reached (the API answers 403 "You've reached your plan's agent shell
	// limit (N)."). The free tier allows one box — Connect to the existing
	// box instead of creating another, or Close it first.
	ErrQuota = errors.New("xshellz: sandbox quota reached: your plan's concurrent box limit is used up — Connect() to the existing sandbox or Close() it first")

	// ErrNotFound is returned when the API answers 404 — the sandbox UUID
	// does not exist, is not yours, or is not in the state the call needs
	// (e.g. Start on a box that is not stopped).
	ErrNotFound = errors.New("xshellz: sandbox not found")

	// ErrNotRunning is returned by Run and the file helpers when the sandbox
	// is not in the "running" state (or has no SSH endpoint yet). Call
	// Start(ctx) to resume an idle-stopped box.
	ErrNotRunning = errors.New("xshellz: sandbox is not running (call Start to resume a stopped box)")

	// ErrMissingKey is returned by GetOrCreate when the named sandbox already
	// exists but no SSH private key for it can be found — neither an explicit
	// PrivateKeyPEM nor a keystore file. The wrapping error says where a key
	// was expected.
	ErrMissingKey = errors.New("xshellz: missing private key for existing sandbox")

	// ErrUnsupportedLanguage is returned by RunCode for a language it does
	// not know how to execute. The wrapping error lists the supported ones
	// (see SupportedLanguages).
	ErrUnsupportedLanguage = errors.New("xshellz: unsupported RunCode language")
)

Sentinel errors, usable with errors.Is. API-originated errors additionally carry an *APIError (usable with errors.As) that wraps the matching sentinel.

Functions

func DefaultKeystoreDir added in v0.2.0

func DefaultKeystoreDir() (string, error)

DefaultKeystoreDir returns the default keystore directory, ~/.xshellz/keys, resolved via os.UserHomeDir.

func GetBoxfile added in v0.2.0

func GetBoxfile(ctx context.Context, opts *ConnectOptions) (string, error)

GetBoxfile fetches the account's saved xshellz.box manifest (GET /shells/agent/boxfile) — the provisioning template seeded into ~/xshellz.box on every NEWLY created box (e.g. to preinstall packages). Returns "" when no manifest is saved.

func SetBoxfile added in v0.2.0

func SetBoxfile(ctx context.Context, manifest string, opts *ConnectOptions) (string, error)

SetBoxfile saves the account's xshellz.box manifest (PUT /shells/agent/boxfile) and returns the stored manifest. An empty manifest clears it. The manifest only applies when a NEW box is created — existing boxes are not touched.

func SupportedLanguages added in v0.2.0

func SupportedLanguages() []string

SupportedLanguages returns the languages RunCode accepts, sorted.

Types

type APIError

type APIError struct {
	// StatusCode is the HTTP status of the response.
	StatusCode int
	// Code is the machine-readable error code when the API provides one
	// (e.g. "verification_required"); empty otherwise.
	Code string
	// Message is the human-readable message from the API response.
	Message string
	// Body is the raw response body, for anything the typed fields miss.
	Body string
	// contains filtered or unexported fields
}

APIError is a control-plane error: any non-2xx response from the xShellz API. It wraps the matching sentinel (ErrAuth, ErrQuota, ErrNotFound) where one applies, so both errors.Is(err, xshellz.ErrQuota) and errors.As(err, &apiErr) work.

func (*APIError) Error

func (e *APIError) Error() string

Error implements the error interface.

func (*APIError) Unwrap

func (e *APIError) Unwrap() error

Unwrap exposes the matching sentinel error (if any) to errors.Is.

type ConnectOptions

type ConnectOptions struct {
	// APIKey is the xShellz personal access token. Falls back to the
	// XSHELLZ_API_KEY environment variable.
	APIKey string
	// APIURL overrides the control-plane base URL. Falls back to
	// XSHELLZ_API_URL, then DefaultAPIURL.
	APIURL string
}

ConnectOptions configures Connect and List. The zero value (or nil) is valid: credentials come from the environment.

type CreateOptions

type CreateOptions struct {
	// Name is an optional human-readable box name (max 64 chars).
	Name string
	// APIKey is the xShellz personal access token. Falls back to the
	// XSHELLZ_API_KEY environment variable.
	APIKey string
	// APIURL overrides the control-plane base URL (e.g. a staging API).
	// Falls back to XSHELLZ_API_URL, then DefaultAPIURL.
	APIURL string
}

CreateOptions configures Create. The zero value (or nil) is valid: the name is server-defaulted and credentials come from the environment.

type GetOrCreateOptions added in v0.2.0

type GetOrCreateOptions struct {
	// APIKey is the xShellz personal access token. Falls back to the
	// XSHELLZ_API_KEY environment variable.
	APIKey string
	// APIURL overrides the control-plane base URL. Falls back to
	// XSHELLZ_API_URL, then DefaultAPIURL.
	APIURL string
	// PrivateKeyPEM, when set, is the PEM-encoded SSH private key used to
	// attach to an already-existing box. It wins over any keystore lookup.
	PrivateKeyPEM []byte
	// KeystoreDir overrides the keystore location (default ~/.xshellz/keys).
	KeystoreDir string
	// DisableKeystore turns key persistence off entirely. GetOrCreate then
	// becomes create-only-or-error: attaching to an existing box requires an
	// explicit PrivateKeyPEM, and a newly created box's key is only available
	// in memory via Sandbox.PrivateKeyPEM.
	DisableKeystore bool
}

GetOrCreateOptions configures GetOrCreate. The zero value (or nil) is valid: credentials come from the environment and keys are persisted to the default keystore directory (~/.xshellz/keys).

type Job added in v0.2.0

type Job struct {
	// ID is the job identifier.
	ID string
	// PID is the recorded process id (0 if the pid file is missing).
	PID int
	// LogPath is the remote log file for the job.
	LogPath string
	// Running reports whether the process is still alive (kill -0 probe).
	Running bool
}

Job is one entry from Jobs: a spawned job's identity and liveness.

type JobHandle added in v0.2.0

type JobHandle struct {

	// ID is the job identifier (name-prefixed when a name was given).
	ID string
	// PID is the remote process id of the job's bash wrapper.
	PID int
	// LogPath is the remote file capturing the job's combined stdout+stderr.
	LogPath string
	// contains filtered or unexported fields
}

JobHandle is a handle to one background process started with Spawn. The job runs detached on the box (nohup) — it survives the SDK disconnecting, but not the box stopping.

func (*JobHandle) IsRunning added in v0.2.0

func (j *JobHandle) IsRunning(ctx context.Context) (bool, error)

IsRunning reports whether the job's process is still alive (kill -0 probe on the box).

func (*JobHandle) Logs added in v0.2.0

func (j *JobHandle) Logs(ctx context.Context, tailLines int) (string, error)

Logs returns the last tailLines lines of the job's log file (combined stdout+stderr). tailLines <= 0 defaults to 100.

func (*JobHandle) Stop added in v0.2.0

func (j *JobHandle) Stop(ctx context.Context) error

Stop terminates the job: SIGTERM first, then SIGKILL if the process is still alive after a ~5 second grace period. Stopping an already-dead job is a no-op.

type ProcessInfo added in v0.2.0

type ProcessInfo struct {
	// PID is the process id inside the box.
	PID int `json:"pid"`
	// Comm is the process command name.
	Comm string `json:"comm"`
	// CPU is the process's CPU usage percentage.
	CPU float64 `json:"cpu"`
	// Mem is the process's memory usage percentage.
	Mem float64 `json:"mem"`
}

ProcessInfo is one process row from Procs.

type RunOptions

type RunOptions struct {
	// Cwd is the remote working directory the command runs in.
	Cwd string
	// Env is extra environment variables for the command.
	Env map[string]string
	// Stdout, when set, receives the command's stdout as it streams (the
	// returned RunResult still captures the full output).
	Stdout io.Writer
	// Stderr, when set, receives the command's stderr as it streams.
	Stderr io.Writer
}

RunOptions configures a single Run invocation. All fields are optional.

type RunResult

type RunResult struct {
	// Stdout is the command's captured standard output.
	Stdout string
	// Stderr is the command's captured standard error.
	Stderr string
	// ExitCode is the remote exit status.
	ExitCode int
}

RunResult is the outcome of one Run. A non-zero ExitCode is data, not an error — Run only errors on transport or context failures.

type Sandbox

type Sandbox struct {
	// contains filtered or unexported fields
}

Sandbox is a live handle to one xShellz sandbox: a control-plane identity plus an SSH data-plane connection (dialed lazily on first use). A Sandbox is safe for concurrent use.

func Connect

func Connect(ctx context.Context, uuid string, privateKeyPEM []byte, opts *ConnectOptions) (*Sandbox, error)

Connect attaches to an existing sandbox by UUID, authenticating the data plane with the given PEM-encoded SSH private key (as previously returned by PrivateKeyPEM, or any key whose public half is authorized on the box). Returns ErrNotFound when no active sandbox has that UUID.

func Create

func Create(ctx context.Context, opts *CreateOptions) (*Sandbox, error)

Create spawns a new sandbox and returns a handle to it. The spawn is synchronous — the box is running when Create returns. A fresh in-memory ed25519 keypair is generated per call; keep PrivateKeyPEM if you want to Connect to the box from another process later.

Typical errors: ErrNoAPIKey (no credentials), ErrQuota (plan's concurrent box limit reached — Connect to the existing box instead), ErrAuth (bad token / missing scope / plan not entitled / verification required).

func GetOrCreate added in v0.2.0

func GetOrCreate(ctx context.Context, name string, opts *GetOrCreateOptions) (*Sandbox, error)

GetOrCreate returns a handle to the sandbox with exactly the given name, creating it when it does not exist. This is the "permanent named box" entrypoint: combined with the keystore (on by default) the same call works on every run of your program.

  • Name not found: the box is created (like Create) and, unless the keystore is disabled, its private key is persisted to KeystoreDir/<sanitized-name>.pem (0600).
  • Name found: the private key is resolved — an explicit PrivateKeyPEM wins, else the keystore file is loaded — and the handle attaches to the existing box. If the box is idle-stopped it is started first. When no key can be found, GetOrCreate fails with ErrMissingKey telling you where a key was expected.

A GetOrCreate box comes back already Detached: Close only drops the SSH connection and leaves the box (and its keystore key) intact, so a permanent box survives your program exiting. Call Kill to actually destroy it.

func (*Sandbox) Close

func (s *Sandbox) Close(ctx context.Context) error

Close tears the sandbox down: it drops the SSH connection and destroys the box (DELETE on the control plane) unless Detach was called. Close is idempotent — the second and later calls are no-ops.

func (*Sandbox) Detach

func (s *Sandbox) Detach()

Detach disables the destroy-on-Close behavior: after Detach, Close only drops the SSH connection and the box keeps running (reattach later with Connect).

func (*Sandbox) Download

func (s *Sandbox) Download(ctx context.Context, remotePath, localPath string) error

Download copies remotePath from the box to a local file (mode 0644).

func (*Sandbox) Info

func (s *Sandbox) Info() SandboxInfo

Info returns a copy of the full control-plane view of the sandbox.

func (*Sandbox) Jobs added in v0.2.0

func (s *Sandbox) Jobs(ctx context.Context) ([]Job, error)

Jobs lists the box's spawned jobs — every log file under ~/.xshellz/jobs with its recorded pid and current liveness. Log files persist after a job exits; remove them on the box when you no longer need them.

func (*Sandbox) Kill added in v0.2.0

func (s *Sandbox) Kill(ctx context.Context) error

Kill destroys the box unconditionally: it drops the SSH connection and issues the control-plane DELETE even when Detach was called (or the handle came from GetOrCreate, which detaches by default). Use it to tear down a permanent box. Kill is idempotent — a 404 (already gone) is swallowed and later calls are no-ops.

func (*Sandbox) PrivateKeyPEM

func (s *Sandbox) PrivateKeyPEM() []byte

PrivateKeyPEM returns the PEM-encoded SSH private key for this sandbox's data plane (the in-memory key generated by Create, or the key given to Connect). Persist it if you need to Connect from another process; it is never sent to the control plane.

func (*Sandbox) Procs added in v0.2.0

func (s *Sandbox) Procs(ctx context.Context) (*SandboxProcs, error)

Procs fetches the box's top processes, active SSH session count, detected agents, and disk usage. The box must be running.

func (*Sandbox) ReadFile

func (s *Sandbox) ReadFile(ctx context.Context, remotePath string) ([]byte, error)

ReadFile reads remotePath from the box and returns its contents. Requires the sandbox to be running.

func (*Sandbox) Restart added in v0.2.0

func (s *Sandbox) Restart(ctx context.Context) error

Restart reboots a running box (POST /shells/agent/{uuid}/restart): the container entrypoint re-runs and /home is preserved. Spawned jobs and any processes outside /home do NOT survive a restart. The handle refreshes its info and drops the SSH connection so the next Run redials.

func (*Sandbox) Run

func (s *Sandbox) Run(ctx context.Context, cmd string, opts *RunOptions) (*RunResult, error)

Run executes a command on the box over SSH and waits for it to finish. A non-zero exit code is returned in RunResult, not as an error; errors are reserved for transport failures, context cancellation, and a box that is not running (ErrNotRunning). When opts.Stdout/Stderr are set, output streams to them as it arrives in addition to being captured.

func (*Sandbox) RunCode added in v0.2.0

func (s *Sandbox) RunCode(ctx context.Context, language, code string, opts *RunOptions) (*RunResult, error)

RunCode executes a snippet of source code on the box and returns the same RunResult as Run — this is the "code interpreter" path for AI-generated code: the code is written to a unique temp file, run with the language's interpreter, and the temp file is always deleted afterwards.

Supported languages: "python" (python3), "node", "bash", "ruby", "php". An unknown language fails with ErrUnsupportedLanguage. As with Run, a non-zero exit code (e.g. a Python traceback) is data on the result, not an error. opts applies to the interpreter invocation (Cwd, Env, streaming).

func (*Sandbox) SSHCommand

func (s *Sandbox) SSHCommand() string

SSHCommand returns a ready-to-copy "ssh -p PORT root@HOST" one-liner (empty while the box is unreachable).

func (*Sandbox) SSHHost

func (s *Sandbox) SSHHost() string

SSHHost returns the box's public SSH hostname (empty while unreachable).

func (*Sandbox) SSHPort

func (s *Sandbox) SSHPort() int

SSHPort returns the box's public SSH port (zero while unreachable).

func (*Sandbox) Spawn added in v0.2.0

func (s *Sandbox) Spawn(ctx context.Context, cmd string, opts *SpawnOptions) (*JobHandle, error)

Spawn starts cmd as a detached background process on the box and returns a JobHandle for it. The command runs under `nohup bash -c`, with stdout and stderr redirected to ~/.xshellz/jobs/<id>.log, so it keeps running after Spawn returns (and after your program exits) for as long as the box is up.

func (*Sandbox) Start

func (s *Sandbox) Start(ctx context.Context) error

Start resumes an idle-stopped box (POST /shells/agent/{uuid}/start). The existing container is started with /home preserved. Returns ErrNotFound when the box is not in the stopped state.

func (*Sandbox) Stats added in v0.2.0

func (s *Sandbox) Stats(ctx context.Context) (*SandboxStats, error)

Stats fetches live memory/CPU/pids/disk/network usage for the box, plus the plan-allowed ceilings. The box must be running. Poll politely — this is a control-plane read that reaches into the host.

func (*Sandbox) Status

func (s *Sandbox) Status() string

Status returns the last-known control-plane status (see the Status* constants). It reflects the state at Create/Connect/Start time; it is not re-fetched automatically.

func (*Sandbox) TerminalURL added in v0.2.0

func (s *Sandbox) TerminalURL(ctx context.Context) (string, error)

TerminalURL mints a fresh signed URL to the box's browser terminal (GET /shells/agent/{uuid}/terminal). The URL embeds an HMAC token valid for about one hour — call TerminalURL again for a fresh link rather than storing one. The box must be running.

func (*Sandbox) UUID

func (s *Sandbox) UUID() string

UUID returns the sandbox's unique identifier.

func (*Sandbox) Upload

func (s *Sandbox) Upload(ctx context.Context, localPath, remotePath string) error

Upload copies a local file to remotePath on the box.

func (*Sandbox) WriteFile

func (s *Sandbox) WriteFile(ctx context.Context, remotePath string, data []byte) error

WriteFile writes data to remotePath on the box, creating parent directories as needed. Requires the sandbox to be running.

type SandboxInfo

type SandboxInfo struct {
	// UUID uniquely identifies the sandbox.
	UUID string `json:"uuid"`
	// Name is the user-supplied (or defaulted) box name.
	Name string `json:"name"`
	// Status is one of the Status* constants (plus transitional states).
	Status string `json:"status"`
	// SSHCommand is a ready-to-copy "ssh -p PORT root@HOST" one-liner; empty
	// while the box is not reachable.
	SSHCommand string `json:"ssh_command"`
	// SSHHost is the public SSH hostname; empty while not reachable.
	SSHHost string `json:"ssh_host"`
	// SSHPort is the public SSH port; zero while not reachable.
	SSHPort int `json:"ssh_port"`
	// WebTerminalReady reports whether the browser terminal can be opened.
	WebTerminalReady bool `json:"web_terminal_ready"`
	// AlwaysOn reports whether the box stays up 24/7 (paid plans).
	AlwaysOn bool `json:"always_on"`
	// TrialHoursRemaining is the metered always-on trial pool left on the
	// account (0 for paid plans or once drained).
	TrialHoursRemaining float64 `json:"trial_hours_remaining"`
	// SpawnedAt is the RFC 3339 time the box last started running; empty if
	// it never ran.
	SpawnedAt string `json:"spawned_at"`
	// CreatedAt is the RFC 3339 creation time.
	CreatedAt string `json:"created_at"`
	// Isolation is the effective OCI runtime ("runsc" = gVisor, "runc" =
	// shared host kernel).
	Isolation string `json:"isolation"`
	// Gvisor reports whether the box booted under gVisor kernel isolation.
	Gvisor bool `json:"gvisor"`
}

SandboxInfo is the control-plane view of one sandbox, as returned by List and carried inside a Sandbox. Field names mirror the snake_case wire shape.

func List

func List(ctx context.Context, opts *ConnectOptions) ([]SandboxInfo, error)

List returns the account's active sandboxes (running, stopped, or still provisioning). The wire response is a bare JSON array of sandbox objects.

type SandboxProcs added in v0.2.0

type SandboxProcs struct {
	// Procs is the box's top processes.
	Procs []ProcessInfo `json:"procs"`
	// Sessions is the number of active SSH sessions on the box.
	Sessions int `json:"sessions"`
	// Agents lists detected coding agents running inside the box.
	Agents []string `json:"agents"`
	// DiskUsedMB is the current disk usage in MB.
	DiskUsedMB int `json:"disk_used_mb"`
	// DiskAllowedMB is the plan's disk ceiling in MB.
	DiskAllowedMB int `json:"disk_allowed_mb"`
}

SandboxProcs is the process/session snapshot from Procs, mirroring the wire shape of GET /shells/agent/{uuid}/procs.

type SandboxStats added in v0.2.0

type SandboxStats struct {
	// MemUsedMB is the current memory usage in MB.
	MemUsedMB int `json:"mem_used_mb"`
	// MemLimitMB is the cgroup memory limit applied to the box in MB.
	MemLimitMB int `json:"mem_limit_mb"`
	// MemAllowedMB is the plan's memory ceiling in MB.
	MemAllowedMB int `json:"mem_allowed_mb"`
	// CPUPercent is the current CPU usage (100 = one full vCPU).
	CPUPercent float64 `json:"cpu_percent"`
	// CPUAllowedVCPUs is the plan's CPU ceiling in vCPUs.
	CPUAllowedVCPUs float64 `json:"cpu_allowed_vcpus"`
	// CPUThrottledPeriods counts cgroup CPU throttling periods (a steadily
	// climbing value means the box is CPU-bound at its cap).
	CPUThrottledPeriods int `json:"cpu_throttled_periods"`
	// PidsCurrent is the current number of processes/threads.
	PidsCurrent int `json:"pids_current"`
	// PidsAllowed is the plan's process-count ceiling.
	PidsAllowed int `json:"pids_allowed"`
	// DiskUsedMB is the current disk usage in MB.
	DiskUsedMB int `json:"disk_used_mb"`
	// DiskAllowedMB is the plan's disk ceiling in MB.
	DiskAllowedMB int `json:"disk_allowed_mb"`
	// NetRxMB is total network bytes received, in MB.
	NetRxMB int `json:"net_rx_mb"`
	// NetTxMB is total network bytes sent, in MB.
	NetTxMB int `json:"net_tx_mb"`
	// BlkReadMB is total block-device bytes read, in MB.
	BlkReadMB int `json:"blk_read_mb"`
	// BlkWriteMB is total block-device bytes written, in MB.
	BlkWriteMB int `json:"blk_write_mb"`
}

SandboxStats is the live resource snapshot from Stats. "Used" fields are current consumption; "Allowed" fields are the plan's ceilings, so you can render used/allowed without a second call. Field names mirror the snake_case wire shape of GET /shells/agent/{uuid}/stats.

type SpawnOptions added in v0.2.0

type SpawnOptions struct {
	// Name is an optional human-readable prefix for the job id (sanitized to
	// [A-Za-z0-9._-]). The final id is always suffixed with a random token.
	Name string
}

SpawnOptions configures Spawn. The zero value (or nil) is valid.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL