pocketenv-elixir
Elixir SDK for the Pocketenv sandbox platform.
Pocketenv lets you spin up isolated cloud sandbox environments on demand. This library wraps the Pocketenv XRPC API so you can manage sandboxes directly from your Elixir applications.
Installation
Add pocketenv_ex to your list of dependencies in mix.exs:
def deps do
[
{:pocketenv_ex, "~> 0.1"}
]
endmix deps.getConfiguration
config/config.exs
import Config
config :pocketenv,
token: "your-pocketenv-token",
api_url: "https://api.pocketenv.io" # optional — this is the defaultEnvironment variables
export POCKETENV_TOKEN="your-pocketenv-token"
export POCKETENV_API_URL="https://api.pocketenv.io" # optionalApplication config takes precedence over environment variables.
Quick start
Pocketenv is the entry point. It returns %Sandbox{} structs that you
pipe operations on:
{:ok, sandbox} =
Pocketenv.create_sandbox("my-sandbox")
|> Sandbox.start()
|> Sandbox.wait_until_running()
{:ok, result} = sandbox |> Sandbox.exec("echo", ["hello"])
IO.puts(result.stdout) # => "hello"
{:ok, url} = sandbox |> Sandbox.expose(3000)
IO.puts(url) # => "https://3000-my-sandbox.sbx.pocketenv.io"
{:ok, vscode_url} = sandbox |> Sandbox.vscode()
sandbox
|> Sandbox.stop()
|> Sandbox.delete()
Every Sandbox function accepts either a bare %Sandbox{}or an
{:ok, %Sandbox{}} tuple as its first argument, so you can pipe from any
previous step without manually unwrapping.
API reference
All functions return {:ok, result} on success and {:error, reason} on
failure. Every function accepts an optional :token keyword argument to
override the globally configured token for that single call.
Pocketenv — entry point
Sandboxes
| Function | Returns | Description |
|---|---|---|
Pocketenv.create_sandbox(name, opts) | {:ok, %Sandbox{}} | Create a new sandbox |
Pocketenv.get_sandbox(id, opts) | {:ok, %Sandbox{} | nil} | Fetch a sandbox by id or name |
Pocketenv.list_sandboxes(opts) | {:ok, {[%Sandbox{}], total}} | List the public sandbox catalog |
Pocketenv.list_sandboxes_by_actor(did, opts) | {:ok, {[%Sandbox{}], total}} | List all sandboxes for a user |
create_sandbox/2 options
| Option | Type | Default | Description |
|---|---|---|---|
:base | string |
official openclaw image | AT-URI of the base sandbox image |
:provider | string | "cloudflare" | "cloudflare", "daytona", "deno", "vercel", or "sprites" |
:repo | string | nil | GitHub repo URL to clone on start |
:keep_alive | boolean | nil | Keep the sandbox alive after the session ends |
:token | string | global config | Bearer token override |
list_sandboxes/1 and list_sandboxes_by_actor/2 options
| Option | Type | Default | Description |
|---|---|---|---|
:limit | integer | 30 | Max results |
:offset | integer | 0 | Pagination offset |
:token | string | global config | Bearer token override |
Actor / profile
| Function | Returns | Description |
|---|---|---|
Pocketenv.me(opts) | {:ok, %Profile{}} | Fetch the authenticated user’s profile |
Pocketenv.get_profile(did, opts) | {:ok, %Profile{}} | Fetch any user’s profile by DID or handle |
{:ok, me} = Pocketenv.me()
IO.puts("Logged in as @#{me.handle}")
{:ok, profile} = Pocketenv.get_profile("alice.bsky.social")Sandbox — operations on a sandbox
All functions take a %Sandbox{} or {:ok, %Sandbox{}} as their first
argument.
Lifecycle
| Function | Returns | Description |
|---|---|---|
Sandbox.start(sandbox, opts) | {:ok, %Sandbox{}} | Start the sandbox, re-fetches state |
Sandbox.stop(sandbox, opts) | {:ok, %Sandbox{}} | Stop the sandbox, re-fetches state |
Sandbox.delete(sandbox, opts) | {:ok, %Sandbox{}} | Delete the sandbox permanently |
Sandbox.wait_until_running(sandbox, opts) | {:ok, %Sandbox{}} |
Poll until status is :running |
start/2 and stop/2 re-fetch the sandbox after the API call so the
returned struct always has the latest status. delete/2 returns the last
known state.
wait_until_running/2 options
| Option | Type | Default | Description |
|---|---|---|---|
:timeout_ms | integer | 60_000 | Total wait time in ms |
:interval_ms | integer | 2_000 | Polling interval in ms |
:token | string | global config | Bearer token override |
Commands
{:ok, result} = sandbox |> Sandbox.exec("mix", ["test", "--trace"])
IO.puts(result.stdout)
IO.puts(result.stderr)
IO.inspect(result.exit_code)| Function | Returns | Description |
|---|---|---|
Sandbox.exec(sandbox, cmd, args \\ [], opts) | {:ok, %ExecResult{}} | Run a shell command inside the sandbox |
Ports
{:ok, url} = sandbox |> Sandbox.expose(4000, description: "Phoenix")
{:ok, ports} = sandbox |> Sandbox.list_ports()
{:ok, _} = sandbox |> Sandbox.unexpose(4000)| Function | Returns | Description |
|---|---|---|
Sandbox.expose(sandbox, port, opts) | {:ok, url | nil} | Expose a port publicly |
Sandbox.unexpose(sandbox, port, opts) | {:ok, %Sandbox{}} | Remove an exposed port |
Sandbox.list_ports(sandbox, opts) | {:ok, [%Port{}]} | List all exposed ports |
VS Code
{:ok, url} = sandbox |> Sandbox.vscode()
IO.puts("Open VS Code at: #{url}")| Function | Returns | Description |
|---|---|---|
Sandbox.vscode(sandbox, opts) | {:ok, url | nil} | Expose VS Code Server and return its URL |
If VS Code is already exposed the existing URL is returned immediately.
Types
%Sandbox{}
The central type of the SDK. Returned by Pocketenv.create_sandbox/2,
Pocketenv.get_sandbox/2, and all Sandbox.* lifecycle functions.
%Sandbox{
id: String.t() | nil,
name: String.t() | nil,
provider: String.t() | nil,
base_sandbox: String.t() | nil,
display_name: String.t() | nil,
uri: String.t() | nil,
description: String.t() | nil,
topics: [String.t()] | nil,
logo: String.t() | nil,
readme: String.t() | nil,
repo: String.t() | nil,
vcpus: integer() | nil,
memory: integer() | nil,
disk: integer() | nil,
installs: integer(),
status: :running | :stopped | :unknown,
started_at: String.t() | nil,
created_at: String.t() | nil,
owner: %Sandbox.Types.Profile{} | nil
}%Sandbox.Types.ExecResult{}
Returned by Sandbox.exec/4.
%Sandbox.Types.ExecResult{
stdout: String.t(),
stderr: String.t(),
exit_code: integer()
}%Sandbox.Types.Port{}
Returned in the list by Sandbox.list_ports/2.
%Sandbox.Types.Port{
port: integer(),
description: String.t() | nil,
preview_url: String.t() | nil
}%Sandbox.Types.Profile{}
Returned by Pocketenv.me/1 and Pocketenv.get_profile/2.
%Sandbox.Types.Profile{
id: String.t() | nil,
did: String.t(),
handle: String.t(),
display_name: String.t() | nil,
avatar: String.t() | nil,
created_at: String.t() | nil,
updated_at: String.t() | nil
}Low-level client
If you need to call an endpoint not yet covered by the high-level API,
use Pocketenv.Client directly:
{:ok, body} = Pocketenv.Client.get(
"/xrpc/io.pocketenv.sandbox.getSandbox",
params: %{"id" => "my-sandbox"},
token: "override-token"
)
{:ok, body} = Pocketenv.Client.post(
"/xrpc/io.pocketenv.sandbox.startSandbox",
%{"keepAlive" => true},
params: %{"id" => "my-sandbox"}
)Running tests
mix test
The test suite does not make real HTTP calls. Integration tests that
exercise the live API require a valid POCKETENV_TOKEN and are not
included by default.
License
MIT