wrenn

package module
v0.2.0 Latest Latest
Warning

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

Go to latest
Published: Jul 25, 2026 License: MIT Imports: 18 Imported by: 0

README

Wrenn Go SDK

Go Reference

The official Go client for the Wrenn API.

Wrenn is the runtime where AI engineers live — it runs AI coding agents inside persistent, isolated microVMs called capsules. This SDK covers everything reachable with a team API key: capsule lifecycle, command execution, interactive terminals, filesystem access, process management, git, metrics, snapshots, in-capsule port proxying, and a persistent Jupyter code runner.

It is a standalone module — the only contract with the Wrenn server is the public REST/WebSocket surface. Its sole dependency is gorilla/websocket.

Install

go get github.com/wrennhq/go-sdk

Requires Go 1.25 or newer.

Quick start

package main

import (
	"context"
	"fmt"
	"log"

	wrenn "github.com/wrennhq/go-sdk"
)

func main() {
	client, err := wrenn.New(wrenn.WithAPIKey("wrn_..."))
	if err != nil {
		log.Fatal(err)
	}

	ctx := context.Background()

	cap, err := client.CreateCapsule(ctx, wrenn.CreateCapsuleParams{
		Template: "minimal-ubuntu",
	})
	if err != nil {
		log.Fatal(err)
	}
	defer cap.Destroy(ctx)

	if err := cap.WaitForStatus(ctx, wrenn.StatusRunning); err != nil {
		log.Fatal(err)
	}

	res, err := cap.Exec(ctx, wrenn.ExecParams{Cmd: "echo", Args: []string{"hello"}})
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("exit=%d out=%s\n", res.ExitCode, res.Stdout)
}

Authentication

Create a team API key from the dashboard (or POST /v1/api-keys) and pass it to WithAPIKey. Keys are scoped to a single team; every capsule and snapshot you touch belongs to that team.

The API key, base URL, and proxy domain fall back to the WRENN_API_KEY, WRENN_BASE_URL, and WRENN_PROXY_DOMAIN environment variables when the matching option is omitted. Explicit options always win, so this works with WRENN_API_KEY set:

client, _ := wrenn.New()

By default the SDK talks to https://app.wrenn.dev/api. Point it at a self-hosted deployment with WithBaseURL (an invalid URL now fails loudly from New rather than silently reverting to the default):

client, _ := wrenn.New(
	wrenn.WithAPIKey("wrn_..."),
	wrenn.WithBaseURL("https://wrenn.internal/api"),
)

When talking to a control plane directly (not through the dashboard's Caddy proxy), drop the /api prefix — e.g. http://localhost:8000.

What's covered

Area Methods
Capsule lifecycle CreateCapsule, ListCapsules, GetCapsule, Capsule.Refresh / Destroy / Pause / Resume / Ping / WaitForStatus
Exec Capsule.Exec (sync, byte-faithful), Capsule.StartBackground, Capsule.ExecStream (receive-only)
Terminal (PTY) Capsule.Pty (interactive), Capsule.PtyConnect (reattach by tag)
Files WriteFile / WriteFileFrom, ReadFile / ReadFileStream, ListDir, MakeDir, Remove, Exists
Processes ListProcesses, KillProcess, AttachProcess (receive-only)
Git Capsule.Git()Clone, Init, Add, Commit, Push, Pull, Fetch, Status, Branches, CurrentBranch, CreateBranch / CheckoutBranch / DeleteBranch, Merge, RemoteAdd / RemoteGet, Reset, Restore, Log, Diff, Show, CreateTag / Tags / DeleteTag, StashSave / StashPop / StashList, SetConfig / GetConfig / ConfigureUser, DangerouslyAuthenticate
Proxy Capsule.URL(port), Client.ProxyURL, Client.ProxyRequest, Client.DialProxyWS
Observability Capsule.Metrics, Client.Stats, Client.Usage
Snapshots CreateSnapshot, ListSnapshots, RenameSnapshot, SetSnapshotVisibility, DeleteSnapshot
Storage volumes CreateVolume, ListVolumes, GetVolume, DeleteVolume, CreateCapsuleParams.VolumeIDs
Code runner coderunner.Create / coderunner.Wrap, Capsule.RunCode (persistent Jupyter kernel)

Command execution

Exec runs a command to completion and returns raw bytes. It is byte-faithful: the SDK transparently decodes the server's base64 encoding when output is not valid UTF-8, so binary output survives intact.

res, _ := cap.Exec(ctx, wrenn.ExecParams{
	Cmd:  "sh",
	Args: []string{"-c", "cat image.png"},
	Cwd:  "/work",
})
os.WriteFile("image.png", res.Stdout, 0o644)

Launch a long-running process with StartBackground and follow it later with AttachProcess:

proc, _ := cap.StartBackground(ctx, wrenn.StartBackgroundParams{Cmd: "npm", Args: []string{"run", "dev"}})
events, _ := cap.AttachProcess(ctx, proc.Tag)
for ev := range events {
	// ...
}

Streaming output

ExecStream and AttachProcess return a receive-only channel of StreamEvent — you consume output, you don't send input. For a bidirectional terminal, use Capsule.Pty. Cancel the context to stop the stream.

events, err := cap.ExecStream(ctx, wrenn.ExecStreamParams{Cmd: "npm", Args: []string{"test"}})
if err != nil {
	log.Fatal(err)
}
for ev := range events {
	switch ev.Type {
	case wrenn.EventStdout:
		fmt.Print(ev.Data)
	case wrenn.EventStderr:
		fmt.Fprint(os.Stderr, ev.Data)
	case wrenn.EventExit:
		fmt.Println("exit", ev.ExitCode)
	}
}

Streaming output travels as text over JSON, so bytes that are not valid UTF-8 may be altered. For binary-faithful output, use Capsule.Exec (base64-decoded) or Capsule.Pty (binary frames).

Interactive terminal (PTY)

Capsule.Pty opens a real pseudo-terminal inside the capsule — a two-way channel where you send keystrokes and resize events and receive byte-faithful output. Consume Events(), write with Write / Resize / Kill, and Close (or cancel the context) when done. It is safe to range over Events() from one goroutine while writing from another.

term, err := cap.Pty(ctx, wrenn.PtyParams{Cmd: "/bin/bash"})
if err != nil {
	log.Fatal(err)
}
defer term.Close()

_ = term.Write([]byte("echo hello\n"))
_ = term.Resize(120, 40)

for ev := range term.Events() {
	switch ev.Type {
	case wrenn.PtyEventOutput:
		os.Stdout.Write(ev.Data) // raw terminal bytes
	case wrenn.PtyEventExit:
		fmt.Println("exit", ev.ExitCode)
	}
}

An empty Cmd launches the guest user's default login shell. Each session has a Tag (available after the first PtyEventStarted); pass it to Capsule.PtyConnect to reattach to the same live terminal from another client. Server keepalive pings are answered automatically, so idle sessions stay open.

Files

_ = cap.WriteFile(ctx, "/work/main.go", []byte("package main\n"))
data, _ := cap.ReadFile(ctx, "/work/main.go")

entries, _ := cap.ListDir(ctx, "/work", 1)
for _, e := range entries {
	fmt.Println(e.Type, e.Name) // Type is "file", "directory", or "symlink"
}

ok, _ := cap.Exists(ctx, "/work/main.go")

Use WriteFileFrom / ReadFileStream to stream large payloads without buffering them in memory.

Git

Capsule.Git() runs the real git binary inside the capsule (git must be in the image). Commands that exit non-zero return a *GitError; use IsGitAuthError to detect authentication failures. Every command accepts a shared GitOptions (Cwd, Envs, TimeoutSec).

g := cap.Git()

_, err := g.Clone(ctx, wrenn.GitCloneParams{
	URL:        "https://github.com/org/repo.git",
	Username:   "user",
	Password:   "ghp_token", // stripped from the origin remote after cloning
	GitOptions: wrenn.GitOptions{Cwd: "/app"},
})

opts := wrenn.GitOptions{Cwd: "/app/repo"}
_ = g.ConfigureUser(ctx, "Alice", "alice@example.com", wrenn.GitConfigParams{GitOptions: opts})
_, _ = g.Add(ctx, wrenn.GitAddParams{All: true, GitOptions: opts})
_, _ = g.Commit(ctx, "initial commit", wrenn.GitCommitParams{GitOptions: opts})

status, _ := g.Status(ctx, opts)
fmt.Println(status.Branch, status.IsClean())

// Inspect history, branches, and diffs.
commits, _ := g.Log(ctx, wrenn.GitLogParams{MaxCount: 10, GitOptions: opts})
branch, _ := g.CurrentBranch(ctx, opts)
patch, _ := g.Diff(ctx, wrenn.GitDiffParams{Staged: true, GitOptions: opts})

// Tags and stash.
_, _ = g.CreateTag(ctx, "v1.0.0", wrenn.GitTagParams{Message: "release", GitOptions: opts})
_, _ = g.StashSave(ctx, wrenn.GitStashSaveParams{IncludeUntracked: true, GitOptions: opts})

Credentials passed to Push / Pull / Fetch are temporarily embedded in the remote URL and always restored afterward, even on failure — and if the restore itself fails, the returned error says so explicitly so credentials are never left lingering silently. DangerouslyAuthenticate persists credentials in plaintext inside the capsule; prefer the per-operation Username / Password fields.

Status uses git's NUL-delimited porcelain format, so paths with spaces or non-ASCII characters are returned verbatim.

Reaching services inside a capsule

Capsule.URL(port) returns the proxied URL for a port exposed inside the capsule ({port}-{id}.<domain>). The endpoint needs no extra auth — the hostname is unguessable.

resp, err := http.Get(cap.URL(8080) + "/health")

Security: a capsule proxy URL terminates inside the untrusted guest VM. Never attach your team API key to it. Use Client.ProxyRequest (HTTP) and Client.DialProxyWS (WebSocket), which send no key, for in-capsule services. Client.AuthorizedRequest / Client.DialWS attach the key and are for the Wrenn API or trusted self-hosted endpoints only — the proxy ignores the key anyway, so forwarding it has no benefit and only exposes it to code running in the capsule.

req, _ := client.ProxyRequest(ctx, http.MethodGet, cap.URL(8080)+"/health", nil)
resp, _ := client.HTTPClient().Do(req)

wsURL := client.ProxyURL(cap.ID, 9000, true)
conn, _ := client.DialProxyWS(ctx, wsURL)

Observability

m, _ := cap.Metrics(ctx, "1h")        // CPU / memory / disk samples
stats, _ := client.Stats(ctx, "24h")  // team-wide reservation + peaks
usage, _ := client.Usage(ctx, "", "") // billed CPU/RAM minutes, last 30 days

Snapshots

Capture a running or paused capsule as a reusable template, then launch new capsules from it.

cap2, _ := client.CreateSnapshot(ctx, cap.ID, "my-template")
list, _ := client.ListSnapshots(ctx, wrenn.ListSnapshotsParams{Type: "snapshot"})
_ = client.SetSnapshotVisibility(ctx, "my-template", true) // publish to other teams

Storage volumes

An external storage volume is a persistent disk that outlives the capsules it is attached to — a package cache, a model directory, a dataset. Create one, then attach it at capsule create time.

vol, _ := client.CreateVolume(ctx, wrenn.CreateVolumeParams{Name: "cache", Size: "20Gi"})

cap, _ := client.CreateCapsule(ctx, wrenn.CreateCapsuleParams{
	Template:  "minimal-ubuntu",
	VolumeIDs: []string{vol.Name}, // an ID ("vol-…") or a name ("vl-cache") works
})
_ = cap.WaitForStatus(ctx, wrenn.StatusRunning)

vol, _ = client.GetVolume(ctx, vol.ID)
fmt.Println(vol.MountPath) // "/mnt/vol-…", where the volume is mounted in the guest

A volume is created detached and is placed on a host only when it is first attached, after which it is pinned to that host for life. Volumes attach only at create time, at most four per capsule, and every one must be detached and owned by your team — attaching volumes pinned to different hosts fails, as does attaching any volume to a capsule launched from a snapshot template.

Destroying a capsule releases its volumes; the data stays. Nothing is auto-deleted, so clean up explicitly:

err := client.DeleteVolume(ctx, "vl-cache")
if wrenn.IsVolumeInUse(err) {
	// a capsule still holds it — destroy the capsule first
}

A freshly created volume is formatted on first attach, so its root is owned by root. Chown it once (sudo chown $(id -u):$(id -g) /mnt/vol-…) if you want to write to it with the file API, which runs as the capsule's default user.

Code runner (Jupyter)

The coderunner subpackage runs code in a persistent Jupyter kernel — variables, imports, and definitions survive across calls. It requires a template that runs Jupyter inside the capsule (the hosted code-runner-beta template by default) and reaches it through the keyless capsule proxy.

import "github.com/wrennhq/go-sdk/coderunner"

cap, _ := coderunner.Create(ctx, client, coderunner.CreateParams{})
defer cap.Destroy(ctx)
_ = cap.WaitForStatus(ctx, wrenn.StatusRunning)

exec, _ := cap.RunCode(ctx, "x = 40; x + 2", coderunner.RunCodeParams{})
fmt.Println(exec.Text())      // "42"
fmt.Println(exec.Logs.Stdout) // stdout chunks

Rich outputs (HTML, images, plotly, JSON, …) land on exec.Results; live callbacks (OnStdout / OnStderr / OnResult / OnError) fire as output arrives. RunCode is serialized per capsule — the kernel is a single stateful session — and self-heals if the underlying kernel dies. Only Python is supported as a language; target another kernelspec with coderunner.WithKernel.

The code runner is beta. The code-runner-beta template and its Jupyter runtime are provided by the hosted Wrenn platform; a self-hosted deployment must supply a template that runs Jupyter on port 8888.

Errors

Non-2xx responses are returned as *APIError carrying the server error code, message, and request ID. The Is* helpers unwrap wrapped errors, so they keep working after fmt.Errorf("...: %w", err).

_, err := client.GetCapsule(ctx, "cl-missing")
if wrenn.IsNotFound(err) {
	// handle 404
}

var apiErr *wrenn.APIError
if errors.As(err, &apiErr) {
	log.Printf("code=%s request=%s", apiErr.Code, apiErr.RequestID)
}

Helpers: IsNotFound, IsNotRunning, IsConflict, IsVolumeInUse, IsUnauthorized. Client-side validation failures also return *APIError (code validation_failed) so callers get one error type.

Development

Everything goes through the Makefile (make help lists targets):

make build             # compile every package
make test              # ALL tests (unit always; integration when WRENN_API_KEY is set)
make test-unit         # unit tests only — never touches a live server
make test-integration  # live integration tests (needs WRENN_API_KEY)
make test-runner       # code-runner integration tests only (needs WRENN_API_KEY)
make check             # fmt-check + vet + lint + unit tests (CI order, offline-safe)
make fmt / fmt-check / vet / lint / tidy

The integration and code-runner tests self-skip when WRENN_API_KEY is unset, so make test is always safe offline. To run them live, copy .env.example to .env and fill in your key (and, for a self-hosted server, WRENN_BASE_URL); the tests load it from the module root automatically.

cp .env.example .env
# edit .env
make test-integration
make test-runner

License

See the repository for license details.

Documentation

Overview

Package wrenn is the official Go client for the Wrenn API.

Wrenn runs AI coding agents inside persistent, isolated microVMs called capsules. This SDK covers the endpoints reachable with a team API key (the "wrn_" key created from the dashboard): capsule lifecycle, command execution, filesystem access, process management, metrics, snapshots, and external storage volumes (Client.CreateVolume, attached to a capsule via CreateCapsuleParams.VolumeIDs). It also opens interactive terminals (Capsule.Pty), wraps the git binary inside a capsule (Capsule.Git), and builds proxied URLs for services running inside a capsule (Capsule.URL). Stateful code execution in a persistent Jupyter kernel lives in the coderunner subpackage.

Getting started

client, err := wrenn.New(wrenn.WithAPIKey("wrn_..."))
if err != nil {
	log.Fatal(err)
}

cap, err := client.CreateCapsule(ctx, wrenn.CreateCapsuleParams{
	Template: "minimal-ubuntu",
})
if err != nil {
	log.Fatal(err)
}

if err := cap.WaitForStatus(ctx, wrenn.StatusRunning); err != nil {
	log.Fatal(err)
}

res, err := cap.Exec(ctx, wrenn.ExecParams{Cmd: "echo", Args: []string{"hi"}})
fmt.Println(string(res.Stdout))

Authentication

Every request carries the team API key in the X-API-Key header. The key is read from WithAPIKey or the WRENN_API_KEY environment variable; the base URL and proxy domain fall back to WRENN_BASE_URL and WRENN_PROXY_DOMAIN. Keys are scoped to a single team; all capsules and snapshots you create or read are that team's. Capsule.ExecStream is receive-only; for a bidirectional terminal (keystrokes, resize, byte-faithful output) use Capsule.Pty.

Errors

Failed API calls return an *APIError carrying the server error code, message, and request ID. Use IsNotFound, IsConflict, and friends, or inspect APIError.Code directly, to branch on specific failures.

Example
package main

import (
	"context"
	"fmt"
	"log"

	wrenn "github.com/wrennhq/go-sdk"
)

func main() {
	client, err := wrenn.New(wrenn.WithAPIKey("wrn_your_team_key"))
	if err != nil {
		log.Fatal(err)
	}

	ctx := context.Background()

	// Create a capsule and wait for it to boot.
	cap, err := client.CreateCapsule(ctx, wrenn.CreateCapsuleParams{
		Template: "minimal-ubuntu",
	})
	if err != nil {
		log.Fatal(err)
	}
	defer func() { _ = cap.Destroy(ctx) }()

	if err := cap.WaitForStatus(ctx, wrenn.StatusRunning); err != nil {
		log.Fatal(err)
	}

	// Run a command.
	res, err := cap.Exec(ctx, wrenn.ExecParams{Cmd: "echo", Args: []string{"hello"}})
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("exit=%d out=%s", res.ExitCode, res.Stdout)
}

Index

Examples

Constants

View Source
const (
	StatusPending      = "pending"
	StatusStarting     = "starting"
	StatusRunning      = "running"
	StatusPausing      = "pausing"
	StatusPaused       = "paused"
	StatusSnapshotting = "snapshotting"
	StatusResuming     = "resuming"
	StatusStopping     = "stopping"
	StatusHibernated   = "hibernated"
	StatusStopped      = "stopped"
	StatusMissing      = "missing"
	StatusError        = "error"
)

Capsule status values. The set mirrors the server's status enum, including the transient states (pausing, snapshotting, resuming, stopping) a capsule reports mid-transition and the terminal "missing" state. Status is a plain string, so unknown future values still decode.

View Source
const (
	CodeInvalidRequest      = "invalid_request"
	CodeValidationFailed    = "validation_failed"
	CodeUnauthorized        = "unauthorized"
	CodeAuthSessionRequired = "auth_session_required"
	CodeForbidden           = "forbidden"
	CodeNotFound            = "not_found"
	CodeSandboxNotFound     = "sandbox_not_found"
	CodeSandboxNotRunning   = "sandbox_not_running"
	CodeTemplateNotFound    = "template_not_found"
	CodeTemplateProtected   = "template_protected"
	CodeConflict            = "conflict"
	CodePayloadTooLarge     = "payload_too_large"
	CodeHostUnreachable     = "host_unreachable"
	CodeInternal            = "internal_error"

	// Storage volume codes. All but CodeVolumeNotFound are 409 conflicts, so
	// [IsConflict] reports true for them.
	CodeVolumeNotFound     = "volume_not_found"
	CodeVolumeInUse        = "volume_in_use"
	CodeVolumeHostMismatch = "volume_host_mismatch"
	CodeVolumeNameTaken    = "volume_name_taken"
	CodeVolumesAttached    = "volumes_attached"
)

Well-known error codes returned by the Wrenn API in APIError.Code. The list is not exhaustive — always be prepared to handle codes not enumerated here.

View Source
const (
	VolumeStatusDetached  = "detached"
	VolumeStatusAttaching = "attaching"
	VolumeStatusAttached  = "attached"
	VolumeStatusDeleting  = "deleting"
)

Volume status values. A volume is "detached" (free to attach or delete), "attaching" (reserved by a booting capsule), "attached" (in use), or "deleting" (a delete is in flight; reverts to detached if it fails). Status is a plain string, so unknown future values still decode.

View Source
const DefaultBaseURL = "https://app.wrenn.dev/api"

DefaultBaseURL is used when no base URL is supplied via WithBaseURL or the WRENN_BASE_URL environment variable.

View Source
const DefaultProxyDomain = "wrenn.dev"

DefaultProxyDomain is the host used to build capsule proxy URLs (see Capsule.URL) when the client talks to the default hosted deployment.

View Source
const Version = "0.2.0"

Version is the semantic version of this SDK. It is reported in the User-Agent header on every request (see WithUserAgent to override).

Keep this in sync with the git tag used to publish the module: tagging a release as vX.Y.Z is what `go get` resolves, and this constant should match.

Variables

This section is empty.

Functions

func IsConflict

func IsConflict(err error) bool

IsConflict reports whether err is a conflict: the generic "conflict" code (e.g. a snapshot that could not be removed from every host) or one of the volume conflicts — still attached, name already taken, volumes pinned to different hosts, or volumes attached when snapshotting.

func IsGitAuthError

func IsGitAuthError(err error) bool

IsGitAuthError reports whether err is a *GitError caused by an authentication failure against a remote.

func IsNotFound

func IsNotFound(err error) bool

IsNotFound reports whether err is a not-found error (capsule, template, volume, or generic 404). It unwraps wrapped errors.

func IsNotRunning

func IsNotRunning(err error) bool

IsNotRunning reports whether err indicates the capsule was not in the running state required by the operation.

func IsUnauthorized

func IsUnauthorized(err error) bool

IsUnauthorized reports whether err indicates a missing or invalid API key. It unwraps wrapped errors.

func IsVolumeInUse added in v0.2.0

func IsVolumeInUse(err error) bool

IsVolumeInUse reports whether err indicates the volume is still attached to a capsule. Destroy the capsule before deleting the volume.

Types

type APIError

type APIError struct {
	// StatusCode is the HTTP status of the response.
	StatusCode int
	// Code is the stable, machine-readable error code (see the Code* constants).
	Code string
	// Message is a human-readable description safe to show to users.
	Message string
	// RequestID identifies the request in server logs; quote it in bug reports.
	RequestID string
	// Retryable is true when the same request may succeed if retried later.
	Retryable bool
	// Details carries optional structured context (e.g. the offending field).
	Details map[string]any
}

APIError is a structured error returned by the Wrenn API. It mirrors the server error envelope: {"error": {"code", "message", "request_id", ...}}.

func (*APIError) Error

func (e *APIError) Error() string

type BackgroundProcess

type BackgroundProcess struct {
	// PID is the process ID inside the guest.
	PID uint32 `json:"pid"`
	// Tag is a stable handle for the process, usable as a selector in
	// [Capsule.KillProcess] and [Capsule.AttachProcess].
	Tag string `json:"tag"`
	// Cmd is the command that was started.
	Cmd string `json:"cmd"`
}

BackgroundProcess identifies a process started with Capsule.StartBackground.

type Capsule

type Capsule struct {
	ID           string            `json:"id"`
	Status       string            `json:"status"`
	Template     string            `json:"template"`
	VCPUs        int32             `json:"vcpus"`
	MemoryMB     int32             `json:"memory_mb"`
	TimeoutSec   int32             `json:"timeout_sec"`
	DiskSizeMB   int32             `json:"disk_size_mb"`
	DiskUsedMB   *int64            `json:"disk_used_mb,omitempty"`
	CreatedAt    string            `json:"created_at"`
	StartedAt    *string           `json:"started_at,omitempty"`
	LastActiveAt *string           `json:"last_active_at,omitempty"`
	LastUpdated  string            `json:"last_updated"`
	Metadata     map[string]string `json:"metadata,omitempty"`
	// contains filtered or unexported fields
}

Capsule is a handle to a Wrenn capsule (an isolated microVM). Fields describe the capsule as of the last create/get/refresh; per-capsule operations are methods. A Capsule is bound to the Client that produced it.

func (*Capsule) AttachProcess

func (cap *Capsule) AttachProcess(ctx context.Context, selector string) (<-chan StreamEvent, error)

AttachProcess streams the output of an already-running process (identified by PID or tag) as a receive-only channel of StreamEvent. This is the background-process counterpart to Capsule.ExecStream: attach to a process started with Capsule.StartBackground and follow its output.

Cancel ctx to detach. The channel closes when the process exits. The capsule must be running.

func (*Capsule) Destroy

func (cap *Capsule) Destroy(ctx context.Context) error

Destroy permanently deletes the capsule. The call is accepted asynchronously.

func (*Capsule) Exec

func (cap *Capsule) Exec(ctx context.Context, params ExecParams) (*ExecResult, error)

Exec runs a command to completion inside the capsule and returns its output. The capsule must be running.

func (*Capsule) ExecStream

func (cap *Capsule) ExecStream(ctx context.Context, params ExecStreamParams) (<-chan StreamEvent, error)

ExecStream runs a command and streams its output back as a receive-only channel of StreamEvent. The stream is receive-only: there is no interactive input. Events arrive in order — a single EventStart, any number of EventStdout/EventStderr, then a terminating EventExit (or EventError). The channel is closed when the process ends.

Cancel ctx to stop the stream early. The capsule must be running.

events, err := cap.ExecStream(ctx, wrenn.ExecStreamParams{Cmd: "npm", Args: []string{"test"}})
if err != nil {
	return err
}
for ev := range events {
	switch ev.Type {
	case wrenn.EventStdout:
		fmt.Print(ev.Data)
	case wrenn.EventExit:
		fmt.Println("exit", ev.ExitCode)
	}
}
Example
package main

import (
	"context"
	"fmt"
	"log"

	wrenn "github.com/wrennhq/go-sdk"
)

func main() {
	client, _ := wrenn.New(wrenn.WithAPIKey("wrn_your_team_key"))
	cap := client.Capsule("cl-...")

	ctx := context.Background()
	events, err := cap.ExecStream(ctx, wrenn.ExecStreamParams{
		Cmd:  "sh",
		Args: []string{"-c", "for i in 1 2 3; do echo $i; sleep 1; done"},
	})
	if err != nil {
		log.Fatal(err)
	}

	for ev := range events {
		switch ev.Type {
		case wrenn.EventStdout:
			fmt.Print(ev.Data)
		case wrenn.EventExit:
			fmt.Println("exited with", ev.ExitCode)
		}
	}
}

func (*Capsule) Exists

func (cap *Capsule) Exists(ctx context.Context, p string) (bool, error)

Exists reports whether path exists inside the capsule. It lists the parent directory and checks for a matching entry, so a missing parent (or a missing path) returns false without an error. The capsule must be running.

func (*Capsule) Git

func (cap *Capsule) Git() *Git

Git returns a handle for running git commands inside the capsule.

func (*Capsule) KillProcess

func (cap *Capsule) KillProcess(ctx context.Context, selector string, sig Signal) error

KillProcess sends sig to the process identified by selector, which may be a numeric PID or a tag. An empty sig defaults to SIGKILL. The capsule must be running.

func (*Capsule) ListDir

func (cap *Capsule) ListDir(ctx context.Context, path string, depth uint32) ([]FileEntry, error)

ListDir lists directory entries at path. Depth controls recursion (0 or 1 for a single level). The capsule must be running.

func (*Capsule) ListProcesses

func (cap *Capsule) ListProcesses(ctx context.Context) ([]Process, error)

ListProcesses returns the processes tracked inside the capsule. The capsule must be running.

func (*Capsule) MakeDir

func (cap *Capsule) MakeDir(ctx context.Context, path string) (*FileEntry, error)

MakeDir creates the directory at path, creating parent directories as needed, and returns its entry. It is not idempotent: if the path already exists it returns a conflict error (see IsConflict). The capsule must be running.

func (*Capsule) Metrics

func (cap *Capsule) Metrics(ctx context.Context, rangeTier string) (*Metrics, error)

Metrics returns resource-usage samples for the capsule. rangeTier is one of "5m", "10m", "1h", "2h", "6h", "12h", "24h"; an empty value defaults to "10m". Available while the capsule is running or paused.

func (*Capsule) Pause

func (cap *Capsule) Pause(ctx context.Context) error

Pause snapshots and suspends the capsule, freeing host resources. The updated capsule state is applied in place.

func (*Capsule) Ping

func (cap *Capsule) Ping(ctx context.Context) error

Ping refreshes the capsule's activity timer, deferring the inactivity timeout. Returns an error unless the capsule is running.

func (*Capsule) Pty

func (cap *Capsule) Pty(ctx context.Context, params PtyParams) (*PtySession, error)

Pty opens an interactive pseudo-terminal in the capsule and returns a live PtySession. The capsule must be running. Cancel ctx or call PtySession.Close to end the session.

func (*Capsule) PtyConnect

func (cap *Capsule) PtyConnect(ctx context.Context, tag string) (*PtySession, error)

PtyConnect reattaches to an existing terminal identified by tag (the Tag from a prior PtyEventStarted). Output resumes from the live terminal; there is no fresh PtyEventStarted on reconnect. The capsule must be running.

func (*Capsule) ReadFile

func (cap *Capsule) ReadFile(ctx context.Context, path string) ([]byte, error)

ReadFile reads the file at path and returns its full contents. For large files, use Capsule.ReadFileStream to avoid buffering. The capsule must be running.

func (*Capsule) ReadFileStream

func (cap *Capsule) ReadFileStream(ctx context.Context, path string) (io.ReadCloser, error)

ReadFileStream opens the file at path for streaming reads. The caller must close the returned reader. The capsule must be running.

func (*Capsule) Refresh

func (cap *Capsule) Refresh(ctx context.Context) error

Refresh reloads the capsule's fields from the API in place.

func (*Capsule) Remove

func (cap *Capsule) Remove(ctx context.Context, path string) error

Remove deletes the file or directory at path. The capsule must be running.

func (*Capsule) Resume

func (cap *Capsule) Resume(ctx context.Context) error

Resume restores a paused or hibernated capsule back to running. The updated capsule state is applied in place.

func (*Capsule) StartBackground

func (cap *Capsule) StartBackground(ctx context.Context, params StartBackgroundParams) (*BackgroundProcess, error)

StartBackground launches a long-running process and returns immediately. Its output can be streamed later with Capsule.AttachProcess using the returned PID or tag. The capsule must be running.

func (*Capsule) URL

func (cap *Capsule) URL(port int) string

URL returns the HTTP(S) URL that reaches a port exposed inside this capsule. For a WebSocket endpoint, use Client.ProxyURL with websocket set to true.

The URL points into the untrusted guest; reach it with Client.ProxyRequest (see the security note on Client.ProxyURL), never with the team API key.

func (*Capsule) WaitForStatus

func (cap *Capsule) WaitForStatus(ctx context.Context, target string) error

WaitForStatus polls the capsule until its status equals target, then returns. It returns early with an error if the capsule enters the "error" state (when that is not the target) or if ctx is cancelled. The capsule's fields are kept up to date as it polls.

func (*Capsule) WriteFile

func (cap *Capsule) WriteFile(ctx context.Context, path string, content []byte) error

WriteFile writes content to path inside the capsule, buffering the whole payload in memory. For large or streamed inputs use Capsule.WriteFileFrom. The capsule must be running.

func (*Capsule) WriteFileFrom

func (cap *Capsule) WriteFileFrom(ctx context.Context, path string, r io.Reader) error

WriteFileFrom streams content from r to path inside the capsule without buffering it in memory. The capsule must be running.

type Client

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

Client is a Wrenn API client scoped to a single team API key. It is safe for concurrent use by multiple goroutines.

func New

func New(opts ...Option) (*Client, error)

New constructs a Client. An API key is required, supplied via WithAPIKey or the WRENN_API_KEY environment variable. The base URL and proxy domain fall back to WRENN_BASE_URL and WRENN_PROXY_DOMAIN when their options are omitted. Explicit options always take precedence over environment variables.

func (*Client) AuthorizedRequest

func (c *Client) AuthorizedRequest(ctx context.Context, method, absURL string, body io.Reader) (*http.Request, error)

AuthorizedRequest builds an HTTP request to an absolute URL with the client's team API key and User-Agent headers set. Use it to call the Wrenn API itself or a trusted self-hosted endpoint.

Security: never point this at a capsule proxy URL (Capsule.URL / Client.ProxyURL). Those URLs terminate inside the untrusted guest VM, so the X-API-Key header would be delivered there in clear text and readable by any process in the capsule. The proxy needs no authentication — use Client.ProxyRequest, which sends no key, for in-capsule services.

func (*Client) Capsule

func (c *Client) Capsule(id string) *Capsule

Capsule returns a handle to an existing capsule by ID without making a request. Use it to run operations against a capsule you already know. Call Capsule.Refresh to populate its metadata.

func (*Client) CreateCapsule

func (c *Client) CreateCapsule(ctx context.Context, params CreateCapsuleParams) (*Capsule, error)

CreateCapsule provisions a new capsule. Creation is asynchronous: the returned Capsule typically starts in a pending/starting state. Use Capsule.WaitForStatus to block until it is running.

func (*Client) CreateSnapshot

func (c *Client) CreateSnapshot(ctx context.Context, capsuleID, name string) (*Capsule, error)

CreateSnapshot captures a running or paused capsule as a new template. The call is asynchronous: the capsule briefly pauses and resumes while the snapshot is registered in the background. The returned Capsule reflects the capsule's transitional state.

func (*Client) CreateVolume added in v0.2.0

func (c *Client) CreateVolume(ctx context.Context, params CreateVolumeParams) (*Volume, error)

CreateVolume provisions a new detached storage volume. Attach it to a capsule with CreateCapsuleParams.VolumeIDs.

It fails with a conflict (see IsConflict) if the team already has a volume with this name.

func (*Client) DeleteSnapshot

func (c *Client) DeleteSnapshot(ctx context.Context, name string) error

DeleteSnapshot removes a snapshot the team owns. It fails with a conflict (see IsConflict) if any host holding the files is offline. Platform and protected templates cannot be deleted.

func (*Client) DeleteVolume added in v0.2.0

func (c *Client) DeleteVolume(ctx context.Context, ref string) error

DeleteVolume permanently deletes a volume and its data. The volume must be detached: destroy any capsule using it first, or the call fails with IsVolumeInUse. Accepts an ID or a name.

func (*Client) DialProxyWS

func (c *Client) DialProxyWS(ctx context.Context, absURL string) (*websocket.Conn, error)

DialProxyWS opens a WebSocket connection to an absolute ws:// or wss:// URL WITHOUT the team API key (only the User-Agent is set). Combine it with Client.ProxyURL to reach a WebSocket service running inside a capsule; the proxy is authenticated by the unguessable capsule hostname, not the key.

func (*Client) DialWS

func (c *Client) DialWS(ctx context.Context, absURL string) (*websocket.Conn, error)

DialWS opens a WebSocket connection to an absolute ws:// or wss:// URL with the client's team API key and User-Agent headers set. Use it for the Wrenn API or a trusted self-hosted endpoint.

Security: never point this at a capsule proxy URL (Client.ProxyURL with websocket true). The connection terminates inside the untrusted guest VM, so the X-API-Key header would be readable by any process in the capsule. Use Client.DialProxyWS, which sends no key, for in-capsule WebSocket services.

func (*Client) GetCapsule

func (c *Client) GetCapsule(ctx context.Context, id string) (*Capsule, error)

GetCapsule fetches a capsule by ID.

func (*Client) GetVolume added in v0.2.0

func (c *Client) GetVolume(ctx context.Context, ref string) (*Volume, error)

GetVolume looks up a volume by ID ("vol-…") or by name ("vl-cache", or plain "cache" — the "vl-" prefix is optional). The two namespaces cannot collide, since an ID always carries the "vol-" prefix.

func (*Client) HTTPClient

func (c *Client) HTTPClient() *http.Client

HTTPClient returns the underlying *http.Client used for API requests. Pair it with Client.ProxyRequest to call an HTTP service running inside a capsule (see Capsule.URL), or with Client.AuthorizedRequest to call the Wrenn API or a trusted self-hosted endpoint.

func (*Client) ListCapsules

func (c *Client) ListCapsules(ctx context.Context) ([]*Capsule, error)

ListCapsules returns all capsules owned by the team.

func (*Client) ListSnapshots

func (c *Client) ListSnapshots(ctx context.Context, params ListSnapshotsParams) (*SnapshotList, error)

ListSnapshots returns one page of templates the team may launch.

func (*Client) ListVolumes added in v0.2.0

func (c *Client) ListVolumes(ctx context.Context) ([]*Volume, error)

ListVolumes returns all storage volumes owned by the team, newest first.

func (*Client) ProxyRequest

func (c *Client) ProxyRequest(ctx context.Context, method, absURL string, body io.Reader) (*http.Request, error)

ProxyRequest builds an HTTP request to an absolute URL WITHOUT the team API key; only the User-Agent header is set. Combine it with Client.HTTPClient and Capsule.URL to reach an HTTP service running inside a capsule. The proxy is authenticated by the unguessable capsule hostname, not the key, so the key must never enter the guest.

func (*Client) ProxyURL

func (c *Client) ProxyURL(capsuleID string, port int, websocket bool) string

ProxyURL returns the URL that reaches a port exposed inside the capsule with the given ID. Wrenn publishes in-capsule ports at {port}-{capsuleID}.{proxyDomain}; the control plane reverse-proxies requests whose Host header matches that pattern through to the guest. When websocket is true the ws:// or wss:// scheme is used, otherwise http:// or https://.

The proxied endpoint needs no separate authentication — the capsule-scoped hostname is unguessable — so the returned URL can be handed to any HTTP or WebSocket client.

Security: this URL terminates inside the untrusted guest VM. Never attach the team API key to it (do not use Client.AuthorizedRequest or Client.DialWS) — the X-API-Key header would be delivered into the capsule and readable by any process there, and the proxy ignores it anyway. Use Client.ProxyRequest or Client.DialProxyWS, which send no key.

func (*Client) RenameSnapshot

func (c *Client) RenameSnapshot(ctx context.Context, name, newName string) error

RenameSnapshot renames a snapshot the team owns. Renaming unpublishes it. Platform and protected templates cannot be renamed.

func (*Client) SetSnapshotVisibility

func (c *Client) SetSnapshotVisibility(ctx context.Context, name string, public bool) error

SetSnapshotVisibility publishes or unpublishes a snapshot the team owns. Published snapshots are launchable by other teams as "<team-slug>/<name>".

func (*Client) Stats

func (c *Client) Stats(ctx context.Context, rangeParam string) (*Stats, error)

Stats returns aggregate capsule usage for the team. rangeParam is one of "5m", "1h", "6h", "24h", "30d"; an empty value defaults to "1h".

func (*Client) Usage

func (c *Client) Usage(ctx context.Context, from, to string) (*Usage, error)

Usage returns billed resource usage for the team between from and to (inclusive), each formatted "YYYY-MM-DD". Empty values default to the last 30 days. The range cannot exceed 92 days.

type CreateCapsuleParams

type CreateCapsuleParams struct {
	// Template is the base image or snapshot name to launch (e.g.
	// "minimal-ubuntu"). Foreign public templates use "<team-slug>/<name>".
	Template string `json:"template,omitempty"`
	// VCPUs is the number of virtual CPUs.
	VCPUs int32 `json:"vcpus,omitempty"`
	// MemoryMB is the guest memory in megabytes.
	MemoryMB int32 `json:"memory_mb,omitempty"`
	// TimeoutSec is the inactivity timeout after which the capsule is
	// auto-destroyed. Zero uses the server default.
	TimeoutSec int32 `json:"timeout_sec,omitempty"`
	// Metadata is arbitrary key/value data stored with the capsule.
	Metadata map[string]string `json:"metadata,omitempty"`
	// VolumeIDs are external storage volumes to attach at boot, each given as a
	// volume ID ("vol-…") or a name ("vl-cache"). Each is mounted at
	// /mnt/<volume-id> inside the guest; read the exact path from
	// [Volume.MountPath] once the capsule is running. At most four per capsule,
	// and each volume must be detached and owned by the team.
	//
	// Volumes can only be attached at create time. If a volume is already pinned
	// to a host the capsule is scheduled onto that host, so attaching volumes
	// pinned to different hosts fails (see [IsConflict]). Not supported for
	// capsules created from a snapshot template.
	VolumeIDs []string `json:"volume_ids,omitempty"`
}

CreateCapsuleParams are the options for Client.CreateCapsule. Zero-valued resource fields fall back to server defaults.

type CreateVolumeParams added in v0.2.0

type CreateVolumeParams struct {
	// Name is optional and unique within the team. Lowercase letters, numbers,
	// and dashes only (no leading, trailing, or repeated dashes), at most 40
	// characters including the "vl-" prefix. The prefix is added if omitted, so
	// "cache" and "vl-cache" name the same volume. Leave it empty and the volume
	// is named after its own ID.
	Name string `json:"name,omitempty"`
	// Size is the human-readable size with a G/Gi/M/Mi suffix ("20Gi", "500M");
	// a bare number is read as megabytes. Fixed at creation.
	Size string `json:"size,omitempty"`
	// SizeMB is the size in megabytes, as an alternative to Size. The minimum is
	// 100; the maximum is set per deployment (20Gi by default).
	SizeMB int32 `json:"size_mb,omitempty"`
}

CreateVolumeParams are the options for Client.CreateVolume. Supply the size as either Size or SizeMB; Size wins if both are set.

type ExecParams

type ExecParams struct {
	// Cmd is the executable to run. Required.
	Cmd string `json:"cmd"`
	// Args are the command arguments.
	Args []string `json:"args,omitempty"`
	// TimeoutSec bounds the command's run time. Zero uses the server default.
	TimeoutSec int32 `json:"timeout_sec,omitempty"`
	// Envs are additional environment variables for the command.
	Envs map[string]string `json:"envs,omitempty"`
	// Cwd is the working directory to run the command in.
	Cwd string `json:"cwd,omitempty"`
}

ExecParams configures a synchronous command execution via Capsule.Exec.

type ExecResult

type ExecResult struct {
	Cmd        string
	Stdout     []byte
	Stderr     []byte
	ExitCode   int32
	DurationMs int64
}

ExecResult is the outcome of a synchronous Capsule.Exec. Stdout and Stderr are returned as raw bytes; the SDK transparently decodes the server's base64 encoding when the output is not valid UTF-8.

type ExecStreamParams

type ExecStreamParams struct {
	// Cmd is the executable to run. Required.
	Cmd string
	// Args are the command arguments.
	Args []string
}

ExecStreamParams configures a streaming command execution.

type FileEntry

type FileEntry struct {
	Name          string  `json:"name"`
	Path          string  `json:"path"`
	Type          string  `json:"type"` // "file", "directory", "symlink", ...
	Size          int64   `json:"size"`
	Mode          uint32  `json:"mode"`
	Permissions   string  `json:"permissions"`
	Owner         string  `json:"owner"`
	Group         string  `json:"group"`
	ModifiedAt    int64   `json:"modified_at"` // unix seconds
	SymlinkTarget *string `json:"symlink_target,omitempty"`
}

FileEntry describes a filesystem entry returned by Capsule.ListDir and Capsule.MakeDir.

type FileStatus

type FileStatus struct {
	// Path is the file path relative to the repository root.
	Path string
	// IndexStatus is the staged (index) status character.
	IndexStatus string
	// WorkTreeStatus is the working-tree status character.
	WorkTreeStatus string
	// RenamedFrom is the original path when the change is a rename.
	RenamedFrom string
}

FileStatus is one entry from git status --porcelain=v1.

func (FileStatus) Staged

func (f FileStatus) Staged() bool

Staged reports whether the change is staged in the index.

func (FileStatus) Status

func (f FileStatus) Status() string

Status is a normalized label: conflict, renamed, copied, deleted, added, modified, typechange, untracked, or unknown.

type Git

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

Git is a handle to git operations inside a capsule, obtained from Capsule.Git. Every method runs the real git binary in the guest through Capsule.Exec, so git must be installed in the capsule's image. Commands that exit non-zero return a *GitError; use IsGitAuthError to detect authentication failures.

func (*Git) Add

func (g *Git) Add(ctx context.Context, p GitAddParams) (*ExecResult, error)

Add stages files for commit.

func (*Git) Branches

func (g *Git) Branches(ctx context.Context, opts GitOptions) ([]GitBranch, error)

Branches lists local branches.

func (*Git) CheckoutBranch

func (g *Git) CheckoutBranch(ctx context.Context, name string, opts GitOptions) (*ExecResult, error)

CheckoutBranch checks out an existing branch.

func (*Git) Clone

func (g *Git) Clone(ctx context.Context, p GitCloneParams) (*ExecResult, error)

Clone clones a remote repository into the capsule. When Username and Password are set they are embedded for the clone and then stripped from the origin remote unless DangerouslyStoreCredentials is true.

func (*Git) Commit

func (g *Git) Commit(ctx context.Context, message string, p GitCommitParams) (*ExecResult, error)

Commit creates a commit with the given message.

func (*Git) ConfigureUser

func (g *Git) ConfigureUser(ctx context.Context, name, email string, p GitConfigParams) error

ConfigureUser sets the git user.name and user.email. Scope defaults to "global".

func (*Git) CreateBranch

func (g *Git) CreateBranch(ctx context.Context, name string, p GitCreateBranchParams) (*ExecResult, error)

CreateBranch creates and checks out a new branch.

func (*Git) CreateTag

func (g *Git) CreateTag(ctx context.Context, name string, p GitTagParams) (*ExecResult, error)

CreateTag creates a tag. With a Message it is annotated, otherwise lightweight.

func (*Git) CurrentBranch

func (g *Git) CurrentBranch(ctx context.Context, opts GitOptions) (string, error)

CurrentBranch returns the checked-out branch name, or "" when HEAD is detached (rather than an error).

func (*Git) DangerouslyAuthenticate

func (g *Git) DangerouslyAuthenticate(ctx context.Context, p GitAuthenticateParams) error

DangerouslyAuthenticate persists git credentials via the credential store.

The credentials are written in plain text to the capsule filesystem and are readable by any process in the capsule. Prefer the per-operation Username and Password fields on Git.Clone, Git.Push, and Git.Pull instead.

func (*Git) DeleteBranch

func (g *Git) DeleteBranch(ctx context.Context, name string, p GitDeleteBranchParams) (*ExecResult, error)

DeleteBranch deletes a branch.

func (*Git) DeleteTag

func (g *Git) DeleteTag(ctx context.Context, name string, opts GitOptions) (*ExecResult, error)

DeleteTag deletes a tag.

func (*Git) Diff

func (g *Git) Diff(ctx context.Context, p GitDiffParams) (string, error)

Diff returns the diff output as text.

func (*Git) Fetch

func (g *Git) Fetch(ctx context.Context, p GitFetchParams) (*ExecResult, error)

Fetch downloads objects and refs from a remote without merging them.

func (*Git) GetConfig

func (g *Git) GetConfig(ctx context.Context, key string, p GitConfigParams) (string, error)

GetConfig returns a git config value, or "" if the key is not set (rather than an error).

func (*Git) Init

func (g *Git) Init(ctx context.Context, p GitInitParams) (*ExecResult, error)

Init initializes a new git repository.

func (*Git) Log

func (g *Git) Log(ctx context.Context, p GitLogParams) ([]GitCommit, error)

Log returns the commit history as structured entries.

func (*Git) Merge

func (g *Git) Merge(ctx context.Context, p GitMergeParams) (*ExecResult, error)

Merge merges Ref into the current branch, or aborts an in-progress merge.

func (*Git) Pull

func (g *Git) Pull(ctx context.Context, p GitPullParams) (*ExecResult, error)

Pull pulls changes from a remote.

func (*Git) Push

func (g *Git) Push(ctx context.Context, p GitPushParams) (*ExecResult, error)

Push pushes commits to a remote.

func (*Git) RemoteAdd

func (g *Git) RemoteAdd(ctx context.Context, name, remoteURL string, p GitRemoteAddParams) (*ExecResult, error)

RemoteAdd adds a remote.

func (*Git) RemoteGet

func (g *Git) RemoteGet(ctx context.Context, name string, opts GitOptions) (string, error)

RemoteGet returns the URL of a remote, or "" if the remote does not exist (rather than an error). An empty name defaults to "origin".

func (*Git) Reset

func (g *Git) Reset(ctx context.Context, p GitResetParams) (*ExecResult, error)

Reset resets the current HEAD.

func (*Git) Restore

func (g *Git) Restore(ctx context.Context, paths []string, p GitRestoreParams) (*ExecResult, error)

Restore restores working-tree files or unstages changes.

func (*Git) SetConfig

func (g *Git) SetConfig(ctx context.Context, key, value string, p GitConfigParams) (*ExecResult, error)

SetConfig sets a git config value. For local scope, Cwd should be the repository root.

func (*Git) Show

func (g *Git) Show(ctx context.Context, object string, p GitShowParams) (string, error)

Show returns `git show` output for an object (commit, tag, blob). An empty object shows HEAD.

func (*Git) StashList

func (g *Git) StashList(ctx context.Context, opts GitOptions) ([]GitStashEntry, error)

StashList lists the stash entries, newest first.

func (*Git) StashPop

func (g *Git) StashPop(ctx context.Context, p GitStashPopParams) (*ExecResult, error)

StashPop applies and removes a stash entry.

func (*Git) StashSave

func (g *Git) StashSave(ctx context.Context, p GitStashSaveParams) (*ExecResult, error)

StashSave pushes the working-tree changes onto the stash.

func (*Git) Status

func (g *Git) Status(ctx context.Context, opts GitOptions) (*GitStatus, error)

Status returns the parsed repository status.

func (*Git) Tags

func (g *Git) Tags(ctx context.Context, opts GitOptions) ([]string, error)

Tags lists tag names.

type GitAddParams

type GitAddParams struct {
	// Paths are specific files to stage; empty stages the current directory
	// (or everything with All).
	Paths []string
	// All stages all changes including untracked files.
	All bool
	GitOptions
}

GitAddParams configures Git.Add.

type GitAuthenticateParams

type GitAuthenticateParams struct {
	// Username and Password are the git credentials. Required.
	Username string
	Password string
	// Host defaults to "github.com".
	Host string
	// Protocol defaults to "https".
	Protocol string
	GitOptions
}

GitAuthenticateParams configures Git.DangerouslyAuthenticate.

type GitBranch

type GitBranch struct {
	// Name is the short branch name.
	Name string
	// IsCurrent reports whether this is the checked-out branch.
	IsCurrent bool
}

GitBranch is a single local branch entry.

type GitCloneParams

type GitCloneParams struct {
	// URL is the remote repository URL. Required.
	URL string
	// Dest is the destination path; defaults to the repo name from the URL.
	Dest string
	// Branch checks out a specific branch or tag after cloning.
	Branch string
	// SingleBranch restricts the clone to just Branch (or the remote's default
	// when Branch is empty). Off by default; a shallow Depth clone already
	// narrows to one branch as git normally does.
	SingleBranch bool
	// Depth creates a shallow clone with this many commits.
	Depth int
	// Username and Password authenticate HTTP(S) clones. Password requires
	// Username.
	Username string
	Password string
	// DangerouslyStoreCredentials leaves credentials embedded in the origin
	// remote URL after cloning. Off by default: credentials are stripped.
	DangerouslyStoreCredentials bool
	GitOptions
}

GitCloneParams configures Git.Clone.

type GitCommit

type GitCommit struct {
	// Hash is the full commit SHA.
	Hash string
	// ShortHash is the abbreviated SHA.
	ShortHash string
	// AuthorName and AuthorEmail identify the author.
	AuthorName  string
	AuthorEmail string
	// Date is the author date in strict ISO 8601 (e.g. 2026-07-19T12:00:00+00:00).
	Date string
	// Subject is the first line of the commit message.
	Subject string
	// Body is the remainder of the commit message (may be empty).
	Body string
}

GitCommit is one entry from Git.Log.

type GitCommitParams

type GitCommitParams struct {
	// AllowEmpty allows creating a commit with no changes.
	AllowEmpty bool
	// AuthorName and AuthorEmail override the commit author.
	AuthorName  string
	AuthorEmail string
	GitOptions
}

GitCommitParams configures Git.Commit.

type GitConfigParams

type GitConfigParams struct {
	// Scope is one of "local", "global", or "system". [Git.SetConfig] and
	// [Git.GetConfig] default to "local"; [Git.ConfigureUser] to "global".
	Scope string
	GitOptions
}

GitConfigParams configures the git config methods.

type GitCreateBranchParams

type GitCreateBranchParams struct {
	// StartPoint is the commit or ref to branch from.
	StartPoint string
	GitOptions
}

GitCreateBranchParams configures Git.CreateBranch.

type GitDeleteBranchParams

type GitDeleteBranchParams struct {
	// Force force-deletes with -D.
	Force bool
	GitOptions
}

GitDeleteBranchParams configures Git.DeleteBranch.

type GitDiffParams

type GitDiffParams struct {
	// Staged diffs the index against HEAD (git diff --cached).
	Staged bool
	// Refs are up to two commits/branches to diff. One ref diffs it against the
	// working tree; two refs diff the pair.
	Refs []string
	// Paths restricts the diff to these paths.
	Paths []string
	// NameOnly lists only the changed file names.
	NameOnly bool
	// Stat produces a diffstat summary instead of the full patch.
	Stat bool
	GitOptions
}

GitDiffParams configures Git.Diff.

type GitError

type GitError struct {
	// Op is the short operation name (e.g. "clone", "push").
	Op string
	// Message is the failure detail, usually git's stderr.
	Message string
	// Stderr is the raw stderr output from git.
	Stderr string
	// ExitCode is git's process exit code.
	ExitCode int32
	// Auth is true when the failure looks like an authentication error.
	Auth bool
}

GitError is returned when a git command exits non-zero. It is distinct from APIError: a GitError means the SDK reached the capsule and ran git, but git itself failed.

func (*GitError) Error

func (e *GitError) Error() string

type GitFetchParams

type GitFetchParams struct {
	// Remote is the remote name; defaults to "origin" unless All is set.
	Remote string
	// Refspec optionally restricts what is fetched (e.g. a branch name).
	Refspec string
	// All fetches from every configured remote.
	All bool
	// Prune deletes remote-tracking refs that no longer exist on the remote.
	Prune bool
	// Tags fetches all tags in addition to the refspec.
	Tags bool
	// Depth deepens (or shortens) a shallow clone to this many commits.
	Depth int
	// Username and Password authenticate HTTP(S) fetches. They are embedded in
	// the remote URL for the fetch and then restored. Ignored when All is set.
	Username string
	Password string
	GitOptions
}

GitFetchParams configures Git.Fetch.

type GitInitParams

type GitInitParams struct {
	// Path is the destination path; defaults to ".".
	Path string
	// Bare creates a bare repository.
	Bare bool
	// InitialBranch names the initial branch (e.g. "main").
	InitialBranch string
	GitOptions
}

GitInitParams configures Git.Init.

type GitLogParams

type GitLogParams struct {
	// MaxCount limits the number of commits (0 = git default, all).
	MaxCount int
	// Skip skips this many commits from the top.
	Skip int
	// Ref is the starting commit, branch, or range (default HEAD).
	Ref string
	// Author filters by author (regex, as git --author).
	Author string
	// Since and Until bound the commit date range (any git date string).
	Since string
	Until string
	// Paths restricts history to commits touching these paths.
	Paths []string
	GitOptions
}

GitLogParams configures Git.Log.

type GitMergeParams

type GitMergeParams struct {
	// Ref is the branch or commit to merge into the current branch. Required
	// unless Abort is set.
	Ref string
	// NoFF forces a merge commit even when a fast-forward is possible.
	NoFF bool
	// FFOnly refuses the merge unless a fast-forward is possible.
	FFOnly bool
	// Squash stages the merge result without committing or recording the merge.
	Squash bool
	// Message overrides the merge commit message.
	Message string
	// Abort aborts an in-progress merge and restores the pre-merge state.
	Abort bool
	GitOptions
}

GitMergeParams configures Git.Merge.

type GitOptions

type GitOptions struct {
	// Cwd is the working directory (usually the repository root).
	Cwd string
	// Envs are extra environment variables merged over the git defaults.
	Envs map[string]string
	// TimeoutSec bounds the command; zero uses the per-operation default.
	TimeoutSec int32
}

GitOptions carries the execution options common to every git command.

type GitPullParams

type GitPullParams struct {
	// Remote is the remote name; defaults to "origin".
	Remote string
	// Branch is the branch to pull.
	Branch string
	// Rebase rebases instead of merging.
	Rebase bool
	// FFOnly only allows fast-forward merges.
	FFOnly bool
	// Username and Password authenticate HTTP(S) pulls. They are embedded in
	// the remote URL for the pull and then restored.
	Username string
	Password string
	GitOptions
}

GitPullParams configures Git.Pull.

type GitPushParams

type GitPushParams struct {
	// Remote is the remote name; defaults to "origin".
	Remote string
	// Branch is the branch to push; defaults to the current branch.
	Branch string
	// Force force-pushes.
	Force bool
	// SetUpstream sets the upstream tracking reference.
	SetUpstream bool
	// Username and Password authenticate HTTP(S) pushes. They are embedded in
	// the remote URL for the push and then restored.
	Username string
	Password string
	GitOptions
}

GitPushParams configures Git.Push.

type GitRemoteAddParams

type GitRemoteAddParams struct {
	// Fetch fetches immediately after adding.
	Fetch bool
	GitOptions
}

GitRemoteAddParams configures Git.RemoteAdd.

type GitResetParams

type GitResetParams struct {
	// Mode is one of soft, mixed, hard, merge, keep.
	Mode string
	// Ref is the commit, branch, or ref to reset to.
	Ref string
	// Paths are specific paths to reset.
	Paths []string
	GitOptions
}

GitResetParams configures Git.Reset.

type GitRestoreParams

type GitRestoreParams struct {
	// Staged restores the index (unstages).
	Staged bool
	// Worktree restores working-tree files. When neither Staged nor Worktree is
	// set, Worktree is assumed.
	Worktree bool
	// Source is the commit or ref to restore from.
	Source string
	GitOptions
}

GitRestoreParams configures Git.Restore.

type GitShowParams

type GitShowParams struct {
	// NameOnly lists only the changed file names.
	NameOnly bool
	// Stat produces a diffstat summary.
	Stat bool
	GitOptions
}

GitShowParams configures Git.Show.

type GitStashEntry

type GitStashEntry struct {
	// Ref is the stash reference (e.g. "stash@{0}").
	Ref string
	// Description is the stash message.
	Description string
}

GitStashEntry is one entry from Git.StashList.

type GitStashPopParams

type GitStashPopParams struct {
	// Ref selects a specific stash entry (e.g. "stash@{1}"); empty pops the
	// most recent.
	Ref string
	GitOptions
}

GitStashPopParams configures Git.StashPop.

type GitStashSaveParams

type GitStashSaveParams struct {
	// Message is an optional stash label.
	Message string
	// IncludeUntracked also stashes untracked files (git stash -u).
	IncludeUntracked bool
	// KeepIndex leaves staged changes in the index after stashing.
	KeepIndex bool
	GitOptions
}

GitStashSaveParams configures Git.StashSave.

type GitStatus

type GitStatus struct {
	// Branch is the current branch name, empty when detached.
	Branch string
	// Upstream is the upstream tracking branch, if any.
	Upstream string
	// Ahead is the number of commits ahead of upstream.
	Ahead int
	// Behind is the number of commits behind upstream.
	Behind int
	// Detached reports whether HEAD is detached.
	Detached bool
	// Files holds the per-file status entries.
	Files []FileStatus
}

GitStatus is the parsed output of git status --porcelain=v1 --branch.

func (GitStatus) HasConflicts

func (s GitStatus) HasConflicts() bool

HasConflicts reports whether at least one file has merge conflicts.

func (GitStatus) HasStaged

func (s GitStatus) HasStaged() bool

HasStaged reports whether at least one file has staged changes.

func (GitStatus) HasUntracked

func (s GitStatus) HasUntracked() bool

HasUntracked reports whether at least one file is untracked.

func (GitStatus) IsClean

func (s GitStatus) IsClean() bool

IsClean reports whether there are no changed or untracked files.

type GitTagParams

type GitTagParams struct {
	// Message, when set, creates an annotated tag carrying this message;
	// otherwise a lightweight tag is created.
	Message string
	// Ref is the commit or ref to tag; defaults to HEAD.
	Ref string
	// Force overwrites an existing tag of the same name.
	Force bool
	GitOptions
}

GitTagParams configures Git.CreateTag.

type ListSnapshotsParams

type ListSnapshotsParams struct {
	// Type filters by "base" or "snapshot"; empty returns both.
	Type string
	// Query searches over name and team slug.
	Query string
	// Page is the 1-based page number (default 1).
	Page int
	// PerPage is the page size (default 50, max 200).
	PerPage int
}

ListSnapshotsParams filters and paginates Client.ListSnapshots.

type MetricPoint

type MetricPoint struct {
	TimestampUnix int64   `json:"timestamp_unix"`
	CPUPct        float64 `json:"cpu_pct"`
	MemBytes      int64   `json:"mem_bytes"`
	DiskBytes     int64   `json:"disk_bytes"`
}

MetricPoint is a single time-series sample of capsule resource usage.

type Metrics

type Metrics struct {
	CapsuleID string        `json:"sandbox_id"`
	Range     string        `json:"range"`
	Points    []MetricPoint `json:"points"`
}

Metrics is a range of resource-usage samples for a capsule.

type Option

type Option func(*Client)

Option configures a Client.

func WithAPIKey

func WithAPIKey(key string) Option

WithAPIKey sets the team API key (the "wrn_..." value). Required.

func WithBaseURL

func WithBaseURL(raw string) Option

WithBaseURL overrides the API base URL (default DefaultBaseURL). Useful for self-hosted deployments. The path prefix is preserved, so pass the full API root, e.g. "https://wrenn.internal/api".

func WithHTTPClient

func WithHTTPClient(hc *http.Client) Option

WithHTTPClient supplies a custom *http.Client (for proxies or instrumentation). Prefer per-call context deadlines over a client-wide Timeout: a fixed timeout would abort legitimately long operations such as a synchronous Capsule.Exec of a slow command or a large Capsule.ReadFileStream.

func WithProxyDomain

func WithProxyDomain(domain string) Option

WithProxyDomain overrides the host used to build capsule proxy URLs ({port}-{capsuleID}.<domain>, see Capsule.URL). Falls back to the WRENN_PROXY_DOMAIN environment variable, then to a value derived from the base URL. Useful for self-hosted deployments with a custom proxy host.

func WithUserAgent

func WithUserAgent(ua string) Option

WithUserAgent overrides the User-Agent header sent on every request.

type Process

type Process struct {
	PID  uint32   `json:"pid"`
	Tag  string   `json:"tag,omitempty"`
	Cmd  string   `json:"cmd"`
	Args []string `json:"args,omitempty"`
}

Process describes a running process listed by Capsule.ListProcesses.

type PtyEvent

type PtyEvent struct {
	// Type is the event kind.
	Type PtyEventType
	// Data holds raw terminal output (PtyEventOutput) or an error message
	// (PtyEventError). Unlike [Capsule.ExecStream], output travels as binary
	// WebSocket frames, so it is byte-faithful — no UTF-8 mangling.
	Data []byte
	// PID is the shell's process ID, set on PtyEventStarted.
	PID uint32
	// Tag is the session handle, set on PtyEventStarted. Pass it to
	// [Capsule.PtyConnect] to reattach to the same terminal later.
	Tag string
	// ExitCode is set on PtyEventExit.
	ExitCode int32
	// Fatal reports whether a PtyEventError ended the session.
	Fatal bool
}

PtyEvent is a single event from an interactive PtySession.

type PtyEventType

type PtyEventType string

PtyEventType identifies the kind of a PtyEvent.

const (
	// PtyEventStarted is emitted once when the terminal is ready; Tag and PID
	// are set. It does not appear when reconnecting with [Capsule.PtyConnect].
	PtyEventStarted PtyEventType = "started"
	// PtyEventOutput carries a chunk of raw terminal output in Data.
	PtyEventOutput PtyEventType = "output"
	// PtyEventExit is emitted once when the shell process ends; ExitCode is set.
	PtyEventExit PtyEventType = "exit"
	// PtyEventError carries a stream-level error message in Data. When Fatal is
	// true the session has ended and the event channel closes.
	PtyEventError PtyEventType = "error"
)

type PtyParams

type PtyParams struct {
	// Cmd is the program to run in the terminal. Empty launches the guest
	// user's default login shell (resolved from /etc/passwd).
	Cmd string
	// Args are the command arguments.
	Args []string
	// Cols is the terminal width in columns. Zero defaults to 80.
	Cols uint32
	// Rows is the terminal height in rows. Zero defaults to 24.
	Rows uint32
	// Envs are additional environment variables for the shell.
	Envs map[string]string
	// Cwd is the working directory to start the shell in.
	Cwd string
	// User is the guest user to run as. Empty uses the image's default user.
	User string
}

PtyParams configures Capsule.Pty.

type PtySession

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

PtySession is a live, bidirectional pseudo-terminal inside a capsule. Unlike the receive-only Capsule.ExecStream, a PtySession accepts keystrokes (PtySession.Write), terminal resizes (PtySession.Resize), and delivers byte-faithful output. Consume PtySession.Events; write with the input methods; call PtySession.Close (or cancel the context) when done.

term, err := cap.Pty(ctx, wrenn.PtyParams{Cmd: "/bin/bash"})
if err != nil {
	return err
}
defer term.Close()

_ = term.Write([]byte("echo hello\n"))
for ev := range term.Events() {
	switch ev.Type {
	case wrenn.PtyEventOutput:
		os.Stdout.Write(ev.Data)
	case wrenn.PtyEventExit:
		return nil
	}
}

A PtySession is safe for concurrent use: one goroutine may range over Events() while another calls Write/Resize/Kill.

func (*PtySession) Close

func (s *PtySession) Close() error

Close ends the session and releases the WebSocket connection. It is safe to call more than once. Unlike relying on context cancellation, Close reliably ends a session even if the consumer has stopped reading Events().

func (*PtySession) Events

func (s *PtySession) Events() <-chan PtyEvent

Events returns the receive-only channel of terminal events. It is closed when the shell exits, a fatal error occurs, or the context is cancelled.

func (*PtySession) Kill

func (s *PtySession) Kill() error

Kill sends SIGKILL to the terminal's process, ending the session.

func (*PtySession) PID

func (s *PtySession) PID() uint32

PID returns the shell's process ID once known (after PtyEventStarted). Zero until then.

func (*PtySession) Resize

func (s *PtySession) Resize(cols, rows uint32) error

Resize changes the terminal dimensions. Both cols and rows must be positive.

func (*PtySession) Tag

func (s *PtySession) Tag() string

Tag returns the session handle once known (after PtyEventStarted, or the tag passed to Capsule.PtyConnect). Empty until then.

func (*PtySession) Write

func (s *PtySession) Write(p []byte) error

Write sends raw bytes to the terminal's stdin (keystrokes, pasted text). The bytes are delivered verbatim as a binary frame — no JSON wrapping, no base64.

type Signal

type Signal string

Signal is a process termination signal accepted by Capsule.KillProcess.

const (
	// SignalKill forcibly terminates the process (SIGKILL). Default.
	SignalKill Signal = "SIGKILL"
	// SignalTerm requests graceful termination (SIGTERM).
	SignalTerm Signal = "SIGTERM"
)

type Snapshot

type Snapshot struct {
	Name      string            `json:"name"`
	Type      string            `json:"type"` // "base" or "snapshot"
	VCPUs     *int32            `json:"vcpus,omitempty"`
	MemoryMB  *int32            `json:"memory_mb,omitempty"`
	SizeBytes int64             `json:"size_bytes"`
	CreatedAt string            `json:"created_at"`
	Platform  bool              `json:"platform"`  // platform-owned base image
	Protected bool              `json:"protected"` // cannot be renamed/deleted
	Public    bool              `json:"public"`    // published to other teams
	Owned     bool              `json:"owned"`     // owned by the caller's team
	TeamSlug  string            `json:"team_slug"`
	Metadata  map[string]string `json:"metadata,omitempty"`
}

Snapshot describes a template the team may launch: its own snapshots and base images, platform images, and public templates published by other teams.

type SnapshotList

type SnapshotList struct {
	Templates  []Snapshot `json:"templates"`
	Total      int        `json:"total"`
	Page       int        `json:"page"`
	PerPage    int        `json:"per_page"`
	TotalPages int        `json:"total_pages"`
}

SnapshotList is a page of snapshots with pagination metadata.

type StartBackgroundParams

type StartBackgroundParams struct {
	// Cmd is the executable to run. Required.
	Cmd string `json:"cmd"`
	// Args are the command arguments.
	Args []string `json:"args,omitempty"`
	// Tag is an optional stable handle for the process; the server assigns one
	// if empty.
	Tag string `json:"tag,omitempty"`
	// Envs are additional environment variables for the command.
	Envs map[string]string `json:"envs,omitempty"`
	// Cwd is the working directory to run the command in.
	Cwd string `json:"cwd,omitempty"`
}

StartBackgroundParams configures Capsule.StartBackground.

type Stats

type Stats struct {
	Range   string       `json:"range"`
	Current StatsCurrent `json:"current"`
	Peaks   StatsPeaks   `json:"peaks"`
	Series  StatsSeries  `json:"series"`
}

Stats is aggregate capsule usage for the team.

type StatsCurrent

type StatsCurrent struct {
	RunningCount     int32 `json:"running_count"`
	VCPUsReserved    int32 `json:"vcpus_reserved"`
	MemoryMBReserved int32 `json:"memory_mb_reserved"`
}

StatsCurrent is the team's live capsule reservation.

type StatsPeaks

type StatsPeaks struct {
	RunningCount int32 `json:"running_count"`
	VCPUs        int32 `json:"vcpus"`
	MemoryMB     int32 `json:"memory_mb"`
}

StatsPeaks are peak values over the requested range.

type StatsSeries

type StatsSeries struct {
	Labels   []string `json:"labels"`
	Running  []int32  `json:"running"`
	VCPUs    []int32  `json:"vcpus"`
	MemoryMB []int32  `json:"memory_mb"`
}

StatsSeries is the per-bucket time series over the requested range. All slices share the same length and index as Labels.

type StreamEvent

type StreamEvent struct {
	// Type is the event kind.
	Type StreamEventType
	// PID is set on EventStart.
	PID uint32
	// Data holds output (EventStdout/EventStderr) or a message (EventError).
	//
	// The server transmits output as text over JSON, so bytes that are not
	// valid UTF-8 may be altered in transit. For binary-faithful output, run
	// the command with [Capsule.Exec] instead.
	Data string
	// ExitCode is set on EventExit.
	ExitCode int32
}

StreamEvent is a single event from a receive-only process stream (Capsule.ExecStream or Capsule.AttachProcess).

type StreamEventType

type StreamEventType string

StreamEventType identifies the kind of a StreamEvent.

const (
	// EventStart is emitted once when the process begins; PID is set.
	EventStart StreamEventType = "start"
	// EventStdout carries a chunk of standard output in Data.
	EventStdout StreamEventType = "stdout"
	// EventStderr carries a chunk of standard error in Data.
	EventStderr StreamEventType = "stderr"
	// EventExit is emitted once when the process ends; ExitCode is set.
	EventExit StreamEventType = "exit"
	// EventError carries a stream-level error message in Data.
	EventError StreamEventType = "error"
)

type Usage

type Usage struct {
	From   string       `json:"from"`
	To     string       `json:"to"`
	Points []UsagePoint `json:"points"`
}

Usage is billed resource usage over a date range.

type UsagePoint

type UsagePoint struct {
	Date         string  `json:"date"` // YYYY-MM-DD
	CPUMinutes   float64 `json:"cpu_minutes"`
	RAMMBMinutes float64 `json:"ram_mb_minutes"`
}

UsagePoint is one day of billed resource usage.

type Volume added in v0.2.0

type Volume struct {
	// ID is the volume ID ("vol-…").
	ID string `json:"id"`
	// TeamID is the owning team.
	TeamID string `json:"team_id"`
	// Name is the "vl-"-prefixed name, unique within the team. It is accepted
	// anywhere a volume ID is (see [Client.GetVolume], CreateCapsuleParams.VolumeIDs).
	Name string `json:"name"`
	// SizeMB is the volume's size ceiling in megabytes, fixed at creation. The
	// backing storage is sparse, so it is not allocated upfront.
	SizeMB int32 `json:"size_mb"`
	// Status is one of the VolumeStatus* constants.
	Status string `json:"status"`
	// HostID is the host the volume is pinned to; nil until first attached.
	HostID *string `json:"host_id,omitempty"`
	// CapsuleID is the capsule the volume is currently attached to, if any.
	CapsuleID *string `json:"sandbox_id,omitempty"`
	// MountPath is the guest path the volume is mounted at while attached
	// (e.g. "/mnt/vol-…"). Empty while detached.
	MountPath string `json:"mount_path"`
	// CreatedAt is an RFC 3339 timestamp.
	CreatedAt string `json:"created_at"`
	// LastAttachedAt is an RFC 3339 timestamp, nil if never attached.
	LastAttachedAt *string `json:"last_attached_at,omitempty"`
}

Volume is an external storage volume: a persistent disk that outlives the capsules it is attached to. A volume is created detached and is placed on a host only when it is first attached to a capsule, at which point it is pinned to that host for the rest of its life. Volumes are never auto-deleted.

Directories

Path Synopsis
Package coderunner runs code in a persistent Jupyter kernel inside a Wrenn capsule.
Package coderunner runs code in a persistent Jupyter kernel inside a Wrenn capsule.

Jump to

Keyboard shortcuts

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