Go SDK
One Go client for every Runtime Cloud product, with a context.Context on every call.
The module is withruntime.com/go, imported as package withruntime. It needs
Go 1.23 or later and uses only the standard library.
Terminalgo get withruntime.com/goThe client finds its key by itself. It uses RUNTIME_API_KEY when that is
set, and otherwise the connection this machine saved (any npx withruntime
command connects it, with one browser approval).
On a server, put a key from https://withruntime.com/account/keys in
RUNTIME_API_KEY from your secret manager. Never put it in source code, a URL
or a command-line argument. With no key anywhere, withruntime.New returns an
error whose code is missing_api_key and whose hint says how to get one.
Updated
On this page
Hello, sandbox#
Gopackage mainimport ( "context" "fmt" "log" "withruntime.com/go")func main() { ctx := context.Background() client, err := withruntime.New() if err != nil { log.Fatal(err) } sbx, err := client.Sandboxes.Create(ctx, &withruntime.CreateOptions{Funding: "trial"}) if err != nil { log.Fatal(err) } defer sbx.Stop(context.Background(), nil) result, err := sbx.Exec(ctx, "python3 -c 'print(6 * 7)'", &withruntime.ExecOptions{Check: true}) if err != nil { log.Fatal(err) } fmt.Print(result.Stdout)}Create returns once the sandbox is running. opts may be nil: with no
options you get the free trial while it lasts, the default region, and 2 vCPU,
4 GiB of memory and a 4 GiB disk for up to 30 minutes. Funding: "trial" never
falls back to paid credit. Every field is optional:
Gopackage mainimport ( "context" "fmt" "log" "withruntime.com/go")func main() { ctx := context.Background() client, err := withruntime.New() if err != nil { log.Fatal(err) } sbx, err := client.Sandboxes.Create(ctx, &withruntime.CreateOptions{ Name: "tests-42", Labels: map[string]string{"team": "search", "job": "42"}, VCPU: 2, MemoryMiB: 4096, DiskMiB: 8192, TimeoutSeconds: 900, OnLeaseEnd: "stop", Network: &withruntime.Network{ Internet: true, Allow: []string{"proxy.golang.org", "*.githubusercontent.com"}, }, }) if err != nil { log.Fatal(err) } defer sbx.Stop(context.Background(), nil) fmt.Println(sbx.ID(), sbx.Info().Funding, sbx.Info().ExpiresAt)}A field newer than the SDK goes in Extra, which is merged into the request.
Run commands#
Exec runs a string under bash -c. ExecArgv runs a program directly, with no
shell, which is what you want for untrusted arguments.
Gopackage mainimport ( "context" "fmt" "log" "os" "time" "withruntime.com/go")func main() { ctx := context.Background() client, err := withruntime.New() if err != nil { log.Fatal(err) } sbx, err := client.Sandboxes.Create(ctx, nil) if err != nil { log.Fatal(err) } defer sbx.Stop(context.Background(), nil) if _, err := sbx.Exec(ctx, "mkdir -p app && echo 'print(1 + 1)' > app/main.py", nil); err != nil { log.Fatal(err) } run, err := sbx.ExecArgv(ctx, []string{"python3", "main.py"}, &withruntime.ExecOptions{ Cwd: "/workspace/app", Env: map[string]string{"API_TOKEN": os.Getenv("API_TOKEN")}, Timeout: 2 * time.Minute, }) if err != nil { log.Fatal(err) } if *run.ExitCode != 0 { fmt.Println(run.Stderr) } fmt.Print(run.Stdout)}Envis how secrets reach a command. It is never echoed back, and journals record a hash, not the value. Never put a secret in the command line itself.Stdingives the command input, then closes it.- The default timeout is 60 seconds; the maximum is 24 hours. A timeout is a
result (
TimedOutis true, with the output so far), not an error. Check: truereturns a*withruntime.CommandError, with the exit code and output, on a non-zero exit or a timeout.- A result holds at most 64 KiB of
Stdoutand 64 KiB ofStderr, andStdoutTruncatedorStderrTruncatedsays when more was dropped. WithOnStdoutorOnStderr(or aTimeoutover 60 seconds) the command streams instead, and the result keeps everything it printed.
Stream output as it happens with callbacks, or range over the events:
Gopackage mainimport ( "context" "fmt" "log" "withruntime.com/go")func main() { ctx := context.Background() client, err := withruntime.New() if err != nil { log.Fatal(err) } sbx, err := client.Sandboxes.Create(ctx, nil) if err != nil { log.Fatal(err) } defer sbx.Stop(context.Background(), nil) _, err = sbx.Exec(ctx, "for i in 1 2 3; do echo line $i; sleep 1; done", &withruntime.ExecOptions{ OnStdout: func(text string) { fmt.Print(text) }, }) if err != nil { log.Fatal(err) } for event, err := range sbx.ExecStream(ctx, "node --version", nil) { if err != nil { log.Fatal(err) } switch event.Type { case "stdout": fmt.Print(event.Data) case "exit": fmt.Println("exit", *event.ExitCode) } }}A long stream resumes by itself when the server ends it, so no output is
dropped. The sandbox keeps the latest 1 MiB of a command's output; a reader that
falls further behind gets a truncated event.
Background processes#
Gopackage mainimport ( "context" "fmt" "log" "withruntime.com/go")func main() { ctx := context.Background() client, err := withruntime.New() if err != nil { log.Fatal(err) } sbx, err := client.Sandboxes.Create(ctx, nil) if err != nil { log.Fatal(err) } defer sbx.Stop(context.Background(), nil) server, err := sbx.Spawn(ctx, "python3 -m http.server 8000", &withruntime.SpawnOptions{Cwd: "/workspace"}) if err != nil { log.Fatal(err) } repl, err := sbx.SpawnArgv(ctx, []string{"python3", "-i", "-q"}, &withruntime.SpawnOptions{PipeStdin: true}) if err != nil { log.Fatal(err) } if err := repl.Write(ctx, []byte("print(21 * 2)\n"), true); err != nil { log.Fatal(err) } done, err := repl.Wait(ctx) if err != nil { log.Fatal(err) } fmt.Print(done.Stdout) processes, err := sbx.Processes(ctx) if err != nil { log.Fatal(err) } for _, process := range processes { fmt.Println(process.ID, process.State, process.Command) } if err := server.Kill(ctx, "SIGTERM"); err != nil { log.Fatal(err) }}process.Output(ctx, 0) yields every event from the start until the process
exits. A process outlives your connection; get it back with
sbx.Process(ctx, id). Write tracks input offsets, so a retried write is never
typed twice.
Files#
Gopackage mainimport ( "context" "fmt" "log" "withruntime.com/go")func main() { ctx := context.Background() client, err := withruntime.New() if err != nil { log.Fatal(err) } sbx, err := client.Sandboxes.Create(ctx, nil) if err != nil { log.Fatal(err) } defer sbx.Stop(context.Background(), nil) if err := sbx.Files.Write(ctx, "/workspace/data/input.csv", []byte("a,b\n1,2\n")); err != nil { log.Fatal(err) } text, err := sbx.Files.ReadText(ctx, "/workspace/data/input.csv") if err != nil { log.Fatal(err) } fmt.Print(text) entries, err := sbx.Files.List(ctx, "/workspace", &withruntime.FileListOptions{Depth: 2}) if err != nil { log.Fatal(err) } for _, entry := range entries { fmt.Println(entry.Type, entry.Size, entry.Path) } if err := sbx.Files.Mkdir(ctx, "/workspace/out", true); err != nil { log.Fatal(err) } if err := sbx.Files.Rename(ctx, "/workspace/data/input.csv", "/workspace/out/input.csv", false); err != nil { log.Fatal(err) } if err := sbx.Files.Upload(ctx, "./testdata", "/workspace/project"); err != nil { log.Fatal(err) } if err := sbx.Files.Download(ctx, "/workspace/out", "./out"); err != nil { log.Fatal(err) }}Write makes parent directories and replaces the file atomically; a file over
1 MiB goes in parallel chunks checked by SHA-256. Stat returns nil for a
path that does not exist. Upload and Download move a whole directory as one
gzipped archive.
Pause, wake, extend, fork and snapshot#
Gopackage mainimport ( "context" "fmt" "log" "time" "withruntime.com/go")func main() { ctx := context.Background() client, err := withruntime.New() if err != nil { log.Fatal(err) } sbx, err := client.Sandboxes.Create(ctx, &withruntime.CreateOptions{Funding: "trial"}) if err != nil { log.Fatal(err) } defer sbx.Stop(context.Background(), nil) if err := sbx.Pause(ctx, nil); err != nil { // memory and files are kept; compute billing stops log.Fatal(err) } again, err := client.Sandboxes.Get(ctx, sbx.ID()) if err != nil { log.Fatal(err) } if err := again.Wake(ctx, 20*time.Minute, nil); err != nil { log.Fatal(err) } if err := again.Extend(ctx, 10*time.Minute, nil); err != nil { log.Fatal(err) } copies, err := again.Fork(ctx, &withruntime.ForkOptions{Count: 2, Funding: "trial"}) if err != nil { log.Fatal(err) } for _, copied := range copies { fmt.Println("fork", copied.ID(), copied.State()) _ = copied.Stop(ctx, nil) } snapshot, err := again.Snapshot(ctx, &withruntime.SnapshotOptions{Name: "ready", RetentionDays: 7}) if err != nil { log.Fatal(err) } fromSnapshot, err := client.Sandboxes.Create(ctx, &withruntime.CreateOptions{Snapshot: snapshot.ID, Funding: "trial"}) if err != nil { log.Fatal(err) } _ = fromSnapshot.Stop(ctx, nil) if err := client.Snapshots.Delete(ctx, snapshot.ID); err != nil { log.Fatal(err) }}A fork copies a sandbox as it is now, with its files, memory and running
processes, on the same server; see JavaScript for what forks and
snapshots keep and how they are billed. If a copy fails, the error's
Details["startedSandboxIds"] names the copies that did start.
Sandboxes by name, and keeping one running#
Sandboxes.GetOrCreate(ctx, name, opts) answers the sandbox with that name,
woken if paused and restarted if stopped and persistent, or creates one with
opts; Info().Reused says which. KeepAlive extends the lease from your
process, so ten minutes remain, until Stop or StopKeepAlive.
Persistent: true on a paid sandbox renews the lease on the server instead.
Gopackage mainimport ( "context" "fmt" "log" "withruntime.com/go")func main() { ctx := context.Background() client, err := withruntime.New() if err != nil { log.Fatal(err) } dev, err := client.Sandboxes.GetOrCreate(ctx, "dev", &withruntime.CreateOptions{IdlePauseSeconds: 900}) if err != nil { log.Fatal(err) } fmt.Println(dev.ID(), dev.Info().Reused) dev.KeepAlive(ctx, nil) defer dev.StopKeepAlive() off := false if err := dev.Update(ctx, withruntime.SandboxSettings{AutoWake: &off}, nil); err != nil { log.Fatal(err) }}An interactive terminal#
sbx.Terminal opens a shell over a WebSocket. It is an io.ReadWriteCloser:
write what you type, read what it prints.
Gopackage mainimport ( "context" "io" "log" "os" "withruntime.com/go")func main() { ctx := context.Background() client, err := withruntime.New() if err != nil { log.Fatal(err) } sbx, err := client.Sandboxes.Create(ctx, nil) if err != nil { log.Fatal(err) } defer sbx.Stop(context.Background(), nil) term, err := sbx.Terminal(ctx, &withruntime.TerminalOptions{Cols: 120, Rows: 40}) if err != nil { log.Fatal(err) } defer term.Close() if _, err := term.Write([]byte("uname -a; exit\n")); err != nil { log.Fatal(err) } if _, err := io.Copy(os.Stdout, term); err != nil { log.Fatal(err) }}Images, volumes and snapshots#
Gopackage mainimport ( "context" "fmt" "log" "withruntime.com/go")func main() { ctx := context.Background() client, err := withruntime.New() if err != nil { log.Fatal(err) } image, err := client.Images.Build(ctx, withruntime.CreateImageOptions{ Name: "data", Recipe: &withruntime.ImageRecipe{Pip: []string{"pandas"}, Apt: []string{"jq"}}, }, func(line withruntime.ImageLogLine) { fmt.Println(line.Text) }) if err != nil { log.Fatal(err) } volume, err := client.Volumes.Create(ctx, withruntime.CreateVolumeOptions{SizeMiB: 10240, Name: "cache"}) if err != nil { log.Fatal(err) } sbx, err := client.Sandboxes.Create(ctx, &withruntime.CreateOptions{ Image: image.ID, Volumes: []withruntime.VolumeMount{{VolumeID: volume.ID, Path: "/data"}}, }) if err != nil { log.Fatal(err) } defer sbx.Stop(context.Background(), nil) result, err := sbx.Exec(ctx, "python3 -c 'import pandas; print(pandas.__version__)'", nil) if err != nil { log.Fatal(err) } fmt.Print(result.Stdout)}Images.Build waits until the image is ready and returns an *Error whose
code is image_failed when the build fails; Images.Create queues it and
returns. A Dockerfile build takes Dockerfile and ContextDir, the folder
docker build would read, with its .dockerignore; only the parts of the
folder the server does not have yet are uploaded. Images.Resolve, Tag,
Untag, Versions, FollowLogs and Images.Registries.Set for private
images do the rest; see custom images. A volume lives on one
server. Daily backups are enabled by default; BackedUp becomes true after
a backup has been copied off the host and checked. A sandbox with volumes
cannot be snapshotted.
Code interpreter, network rules and secrets#
Gopackage mainimport ( "context" "fmt" "log" "withruntime.com/go")func main() { ctx := context.Background() client, err := withruntime.New() if err != nil { log.Fatal(err) } sbx, err := client.Sandboxes.Create(ctx, nil) if err != nil { log.Fatal(err) } defer sbx.Stop(context.Background(), nil) if _, err := sbx.Interpreter.Run(ctx, "import math\nx = math.pi", nil); err != nil { log.Fatal(err) } cell, err := sbx.Interpreter.Run(ctx, "round(x * 2, 3)", nil) if err != nil { log.Fatal(err) } fmt.Println(cell.Results[0].Data["text/plain"]) policy, err := sbx.Network.Set(ctx, withruntime.Network{Internet: true, Allow: []string{"pypi.org", "*.pythonhosted.org"}}) if err != nil { log.Fatal(err) } fmt.Println(policy.Enforced, policy.Allow) secret, err := client.Secrets.Set(ctx, "OPENAI_API_KEY", withruntime.SetSecretOptions{ Value: "sk-...", Hosts: []string{"api.openai.com"}, }) if err != nil { log.Fatal(err) } fmt.Println(secret.Placeholder) // what sandboxes see in $OPENAI_API_KEY}sbx.Desktop drives a Linux desktop in the sandbox (Start, Open, Click,
Type, Press, Screenshot), as in JavaScript.
Metrics, events and webhooks#
Gopackage mainimport ( "context" "fmt" "io" "log" "net/http" "withruntime.com/go")func main() { ctx := context.Background() client, err := withruntime.New() if err != nil { log.Fatal(err) } hook, err := client.Webhooks.Create(ctx, withruntime.CreateWebhookOptions{URL: "https://example.com/hooks/runtime"}) if err != nil { log.Fatal(err) } secret := hook.Secret // shown once; keep it http.HandleFunc("/hooks/runtime", func(w http.ResponseWriter, r *http.Request) { body, _ := io.ReadAll(r.Body) event, err := withruntime.VerifyWebhook(body, r.Header.Get("Runtime-Signature"), []string{secret}, 0) if err != nil { http.Error(w, "bad signature", http.StatusUnauthorized) return } fmt.Println(event.Type) w.WriteHeader(http.StatusNoContent) }) for event, err := range client.Events.All(ctx, &withruntime.EventListOptions{Type: "sandbox.stopped"}) { if err != nil { log.Fatal(err) } fmt.Println(event.CreatedAt, event.Type) }}sbx.Metrics(ctx, "15m") returns the sandbox's measured CPU and memory, and
client.Otel.Create pushes events and metrics to an OpenTelemetry endpoint.
See metrics and webhooks.
An endpoint newer than the SDK#
client.Request calls any API path with the client's key, retries,
idempotency keys and errors:
Gopackage mainimport ( "context" "fmt" "log" "withruntime.com/go")func main() { client, err := withruntime.New() if err != nil { log.Fatal(err) } var page map[string]any if err := client.Request(context.Background(), "GET", "/v1/volumes", nil, &page); err != nil { log.Fatal(err) } fmt.Println(page["data"])}Find sandboxes again#
Gopackage mainimport ( "context" "fmt" "log" "withruntime.com/go")func main() { ctx := context.Background() client, err := withruntime.New() if err != nil { log.Fatal(err) } filter := &withruntime.ListOptions{Labels: map[string]string{"team": "search"}, State: []string{"running"}} for sbx, err := range client.Sandboxes.All(ctx, filter) { if err != nil { log.Fatal(err) } fmt.Println(sbx.ID(), sbx.State()) }}Sandboxes.List returns one page (Data, HasMore, Next); All walks every
page.
Errors and retries#
Gopackage mainimport ( "context" "errors" "fmt" "log" "withruntime.com/go")func main() { ctx := context.Background() client, err := withruntime.New() if err != nil { log.Fatal(err) } _, err = client.Sandboxes.Get(ctx, "00000000-0000-4000-8000-000000000000") var failure *withruntime.Error switch { case errors.Is(err, withruntime.ErrNotFound): fmt.Println("no such sandbox") case errors.As(err, &failure): fmt.Println(failure.Code, failure.Hint, failure.RequestID) }}Every error is a *withruntime.Error with Code, Message, Hint,
RequestID, Status and Details. errors.Is matches it against
ErrAuthentication, ErrPermissionDenied, ErrNotFound, ErrConflict,
ErrInvalidRequest, ErrRateLimited, ErrServiceUnavailable and
ErrConnection, the same classes as the JavaScript and Python SDKs.
Every write carries an idempotency key, made for you and kept across the
client's own retries. Transport failures, 429, 502, 503 and 504 are retried
with the same key, so a retry never makes two sandboxes or runs a command
twice. Retryable() says whether trying the same call again may work.
A create waits for room. When every trial slot is taken (trial_busy), the
account is at its limit (quota_exceeded) or the region is full
(no_capacity), Sandboxes.Create waits and sends the same request again, for
up to two minutes. A burst of CI jobs past the limit queues instead of failing.
Set withruntime.WithWaitForCapacity on the client, or WaitForCapacity on one
create; zero fails at once.
Read-only keys and daily limits#
An owner, admin or developer can make a read-only key and set a daily spending
limit on a key at
API keys. client.Limits.Get(ctx)
reads both; past the limit, a create, wake or extension returns an error whose
code is spending_limit_reached, and it is not retried. See
security.
Configuration#
Gopackage mainimport ( "context" "fmt" "log" "os" "time" "withruntime.com/go")func main() { client, err := withruntime.New( withruntime.WithAPIKey(os.Getenv("MY_RUNTIME_KEY")), withruntime.WithTimeout(2*time.Minute), withruntime.WithMaxRetries(4), withruntime.WithWaitForCapacity(10*time.Minute), ) if err != nil { log.Fatal(err) } me, err := client.Me(context.Background()) if err != nil { log.Fatal(err) } fmt.Println(me.OrgID)}RUNTIME_API_URLpoints the client at another API origin, asWithBaseURLdoes.- Code inside a Runtime sandbox calls Runtime's API at
http://runtime.internal(Runtime's API from inside a sandbox). - The client is safe for concurrent use. Make one and share it: it keeps its
connections open and holds at most 32 calls in flight
(
WithMaxConnections). - It honours
HTTPS_PROXYandNO_PROXY.WithHTTPClientsupplies your own*http.Client. - Money is integer microdollars: 1,000,000 is one US dollar.
client.Usage(ctx)returns the account's balance as exact decimal strings.
Domains, public ports and private networking#
client.Domains, Ports, Addresses and Tunnel are account products.
Domains and public ports name the sandbox they serve. Tunnel manages a
WireGuard network; sbx.OpenTunnel and sbx.PortForward open authenticated TCP
connections from your process into one sandbox.
When using OpenTunnel directly, read responses while writing requests. Each
stream keeps at most 1 MiB of unread response data. If its reader falls behind
that bound, reads return tunnel_receive_overflow after the buffered data;
other streams keep working. PortForward drains both directions for you.
Gopackage mainimport ( "context" "fmt" "log" "withruntime.com/go")func main() { ctx := context.Background() client, err := withruntime.New() if err != nil { log.Fatal(err) } sbx, err := client.Sandboxes.Get(ctx, "11111111-2222-4333-8444-555555555555") if err != nil { log.Fatal(err) } domain, err := client.Domains.Add(ctx, withruntime.AddDomainOptions{ Hostname: "app.example.com", SandboxID: sbx.ID(), Port: 3000, }) if err != nil { log.Fatal(err) } fmt.Println(domain.Records) // Publish these at your DNS provider, then call Domains.Verify. address, err := client.Addresses.Reserve(ctx, 4) if err != nil { log.Fatal(err) } fmt.Println(address.Address, address.Funded, address.FundedUntil) forward, err := sbx.PortForward(ctx, 5432, "127.0.0.1:0") if err != nil { log.Fatal(err) } defer forward.Close() fmt.Println(forward.Addr()) // Connect your local database client here.}Ports.Open returns Connect, the public address and port. Tunnel.Create
creates the account's WireGuard network; AddPeer and RotatePeer return a
configuration holding a generated private key once. Save Config even when
ConfigReady is false: it is a draft whose gateway public key must be filled
from Tunnel.Get before use. A generated-key request is never automatically
retried. To keep the private key entirely on your machine, supply PublicKey.
Dedicated addresses and WireGuard networks expose Funded, FundedUntil,
RateMicros and RateUnit. Exhausted credit disables traffic while retaining
the reservation until you release it. SSO.Get reads sign-on and directory
status and the website address where an owner manages it.
Backup and restore volumes#
Volumes.Backup requests a checked copy off the host. Backups lists copies;
GetBackup and DeleteBackup read or remove one. SetBackupPolicy changes
daily backups or retention. Restore makes a new volume from a ready backup.
Gopackage mainimport ( "context" "fmt" "log" "withruntime.com/go")func main() { ctx := context.Background() client, err := withruntime.New() if err != nil { log.Fatal(err) } backup, err := client.Volumes.Backup(ctx, "11111111-2222-4333-8444-555555555555", nil) if err != nil { log.Fatal(err) } if backup.State != "ready" { fmt.Println("Backup is still running; poll Volumes.GetBackup:", backup.ID) return } volume, err := client.Volumes.Restore(ctx, backup.ID, &withruntime.CreateVolumeOptions{Name: "restored"}) if err != nil { log.Fatal(err) } fmt.Println(volume.ID, volume.State, volume.RestoredFrom)}Permissions and file watching#
Pass FileWriteOptions to write permissions with the content; a pointer keeps
mode 000 distinct from the default. Current guest images require only
write_file. Older images require exec for the SDK's chmod fallback.
Files.Chmod sets an existing file's permissions through the same exec grant.
A supplied write key identifies the whole upload: each mutation phase has its
own stable key, and a repeated completed upload returns without sending its
chunks again.
Gopackage mainimport ( "context" "fmt" "log" "withruntime.com/go")func main() { ctx := context.Background() client, err := withruntime.New() if err != nil { log.Fatal(err) } sbx, err := client.Sandboxes.Get(ctx, "11111111-2222-4333-8444-555555555555") if err != nil { log.Fatal(err) } mode := uint32(0o755) if err := sbx.Files.Write(ctx, "/workspace/run.sh", []byte("#!/bin/sh\necho ready\n"), &withruntime.FileWriteOptions{Mode: &mode}); err != nil { log.Fatal(err) } watch, err := sbx.Files.Watch(ctx, "/workspace", &withruntime.WatchOptions{Recursive: true, Exclude: []string{"node_modules"}}) if err != nil { log.Fatal(err) } defer watch.Stop(context.Background()) for batch, err := range watch.Events(ctx) { if err != nil { log.Fatal(err) } fmt.Println(batch.Kind, batch.Events, batch.Reason, watch.Cursor()) }}Events reconnects on a continuation and preserves its cursor. A pause ends
iteration without stopping the guest watch; after waking the sandbox, call
Events again. overflow and lost notices mean you should rescan. Breaking
iteration closes its HTTP response. Stop also stops the guest watch.
Files.Watches.List and Read support finding watches and polling instead.
Bucket mounts, MCP servers and recordings#
sbx.Mounts.Add mounts a customer bucket using the name of a Runtime secret,
with List and Remove for lifecycle management. The sandbox never receives
the bucket key. client.MCP.Catalog lists available servers; sbx.MCP.Start,
Ready, Get and Stop manage servers running in that sandbox.
sbx.Desktop.Recordings has Start, Get, List, Stop, Download and
Delete; downloaded bytes are MP4.
Gopackage mainimport ( "context" "fmt" "log" "time" "withruntime.com/go")func main() { ctx := context.Background() client, err := withruntime.New() if err != nil { log.Fatal(err) } sbx, err := client.Sandboxes.Get(ctx, "11111111-2222-4333-8444-555555555555") if err != nil { log.Fatal(err) } catalog, err := client.MCP.Catalog(ctx) if err != nil { log.Fatal(err) } fmt.Println(catalog) _, err = sbx.MCP.Start(ctx, withruntime.MCPStartOptions{Servers: []withruntime.MCPServerRequest{{ID: "github", Secrets: map[string]string{"GITHUB_PERSONAL_ACCESS_TOKEN": "GITHUB_TOKEN"}}}}) if err != nil { log.Fatal(err) } gateway, err := sbx.MCP.Ready(ctx, 5*time.Minute) if err != nil { log.Fatal(err) } fmt.Println(gateway.Servers, gateway.Warnings) // Connect with gateway.Headers; keep its token private.}Try it on your own work. Sign up, then paste the setup prompt into your coding agent.