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
- Variables
- func DefaultKeystoreDir() (string, error)
- func GetBoxfile(ctx context.Context, opts *ConnectOptions) (string, error)
- func SetBoxfile(ctx context.Context, manifest string, opts *ConnectOptions) (string, error)
- func SupportedLanguages() []string
- type APIError
- type ConnectOptions
- type CreateOptions
- type GetOrCreateOptions
- type Job
- type JobHandle
- type ProcessInfo
- type RunOptions
- type RunResult
- type Sandbox
- func (s *Sandbox) Close(ctx context.Context) error
- func (s *Sandbox) Detach()
- func (s *Sandbox) Download(ctx context.Context, remotePath, localPath string) error
- func (s *Sandbox) Info() SandboxInfo
- func (s *Sandbox) Jobs(ctx context.Context) ([]Job, error)
- func (s *Sandbox) Kill(ctx context.Context) error
- func (s *Sandbox) PrivateKeyPEM() []byte
- func (s *Sandbox) Procs(ctx context.Context) (*SandboxProcs, error)
- func (s *Sandbox) ReadFile(ctx context.Context, remotePath string) ([]byte, error)
- func (s *Sandbox) Restart(ctx context.Context) error
- func (s *Sandbox) Run(ctx context.Context, cmd string, opts *RunOptions) (*RunResult, error)
- func (s *Sandbox) RunCode(ctx context.Context, language, code string, opts *RunOptions) (*RunResult, error)
- func (s *Sandbox) SSHCommand() string
- func (s *Sandbox) SSHHost() string
- func (s *Sandbox) SSHPort() int
- func (s *Sandbox) Spawn(ctx context.Context, cmd string, opts *SpawnOptions) (*JobHandle, error)
- func (s *Sandbox) Start(ctx context.Context) error
- func (s *Sandbox) Stats(ctx context.Context) (*SandboxStats, error)
- func (s *Sandbox) Status() string
- func (s *Sandbox) TerminalURL(ctx context.Context) (string, error)
- func (s *Sandbox) UUID() string
- func (s *Sandbox) Upload(ctx context.Context, localPath, remotePath string) error
- func (s *Sandbox) WriteFile(ctx context.Context, remotePath string, data []byte) error
- type SandboxInfo
- type SandboxProcs
- type SandboxStats
- type SpawnOptions
Constants ¶
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.
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 ¶
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
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
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.
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
IsRunning reports whether the job's process is still alive (kill -0 probe on the box).
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
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 ¶
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) 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
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
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 ¶
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 ¶
ReadFile reads remotePath from the box and returns its contents. Requires the sandbox to be running.
func (*Sandbox) Restart ¶ added in v0.2.0
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 ¶
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 ¶
SSHCommand returns a ready-to-copy "ssh -p PORT root@HOST" one-liner (empty while the box is unreachable).
func (*Sandbox) Spawn ¶ added in v0.2.0
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 ¶
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 ¶
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
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.
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.