@sitepong/sandbox
v0.0.1
Published
Typed client for SitePong sandbox VMs, managed git, and preview domains — a Freestyle-shaped surface over SitePong-owned infra.
Readme
@sitepong/sandbox
A typed client that gives SitePong's sandbox product a Freestyle-shaped surface, backed entirely by SitePong-owned infra (the hosting management API + the managed git server). Dependency-light: uses the global fetch (Node 20+), no axios.
import { SitePong } from "@sitepong/sandbox";
const sp = new SitePong({
baseUrl: "https://hosting.sitepong.com", // management API
token: process.env.SITEPONG_TOKEN!, // spa_ (account) or sbp_ (instance)
git: { // optional
baseUrl: "https://git.sitepong.com",
token: process.env.GIT_INTERNAL_TOKEN!,
},
});
const { vm } = await sp.vms.create({
name: "scratch",
slug: "scratch-1",
cpuClassId: "<cpu-class-uuid>",
cpuCores: 2,
memoryMb: 4096,
storageGb: 20,
template: "sandbox-fc",
});
await vm.fs.writeFile("index.js", "console.log('hi')");
const out = await vm.exec("node index.js");
console.log(out.stdout);
await vm.stop(); // freeze
await vm.start(); // resumeMethod → endpoint → Freestyle equivalent
All VM/lifecycle/invoke calls hit the management API (servers/hosting/src/management-api). Git calls hit the git control plane (servers/git/src/http.ts).
| SDK method | HTTP endpoint | Backing code | Freestyle equivalent |
| --- | --- | --- | --- |
| vms.create(opts) | POST /v1/projects | management-api/projects.ts handleCreateProject | freestyle.vms.create() |
| vms.list() | GET /v1/projects | projects.ts handleListProjects | listing VMs |
| vms.get(ref) | GET /v1/projects/:ref | projects.ts handleGetProject | get VM |
| vms.vm(ref) | (no call — handle) | — | address VM by id |
| vms.destroy(ref) | DELETE /v1/projects/:ref | lifecycle.ts handleDeleteProject | destroy VM |
| vm.exec(cmd, input?, opts?) | POST /v1/projects/:ref/invoke {command:"run_shell"} | invoke.ts → agent cmdRunShell | vm.exec(command, input) |
| vm.execDetached(cmd, opts?) | POST /v1/projects/:ref/invoke {command:"run_shell", input:{detach:true}} | agent cmdRunShell (detached) | (fire-and-forget exec) |
| vm.fs.readFile(path, opts?) | POST /v1/projects/:ref/invoke {command:"read_file"} | agent cmdReadFile | vm.fs.readFile() |
| vm.fs.writeFile(path, content, opts?) | POST /v1/projects/:ref/invoke {command:"write_file"} | agent cmdWriteFile | vm.fs.writeFile() |
| vm.fs.listFiles(root?, depth?) | POST /v1/projects/:ref/invoke {command:"list_files"} | agent cmdListFiles | vm.fs.ls() |
| vm.stop() (freeze) | POST /v1/projects/:ref/freeze | lifecycle.ts handleFreezeProject | vm.stop() |
| vm.start() (resume) | POST /v1/projects/:ref/resume | lifecycle.ts handleResumeProject | vm.start() |
| git.createRepo(opts) | POST /v1/git/repos | git/http.ts handleLifecycle | git.createRepo() |
| git.listRepos(ref) | GET /v1/git/repos?project_ref= | git/http.ts | git.listRepos() |
| git.deleteRepo(id) | DELETE /v1/git/repos/:id | git/http.ts | delete repo |
| git.mintToken(opts) | POST /v1/git/tokens | git/http.ts | git.mintToken() / identity tokens |
| git.revokeToken(id) | DELETE /v1/git/tokens/:id | git/http.ts | revoke token |
| domains.get(ref) | GET /v1/projects/:ref → preview_url | projects.ts previewUrlFor | read preview domain |
Not yet backed by an endpoint (stubbed — throw NotImplementedError)
These methods have their final shape so callers can write against them now, but no server endpoint exists yet. Each throws NotImplementedError with a planRef.
| SDK method | Why stubbed | Plan reference |
| --- | --- | --- |
| vm.snapshot() | The Firecracker supervisor (servers/firecrackerd/src/http.ts) only exposes /vms, /vms/:id, /vms/:id/stop, DELETE. No /vms/:id/snapshot, and the management API has no snapshot verb. | snapshot plan Phase 1 (supervisor POST /vms/:id/snapshot) + Phase 2 (control-plane wiring) |
| vm.resize(opts) | Firecracker can't hot-add vCPU/mem; resize = snapshot→restore at a new machine-config, which depends on the snapshot primitive. | snapshot plan Phase 4 |
| domains.create(opts) | Preview URLs are auto-derived from the template host_pattern; there is no endpoint to register an arbitrary hostname. Use domains.get(ref) for the auto URL. | snapshot plan Phase 4 (domains.mappings.create() parity verb) |
Note on freeze/resume: today
vm.stop()/vm.start()map to an LXC stop/start inlifecycle.ts. Once snapshot plan Phase 2 lands, the same/freezeand/resumeendpoints will snapshot-then-kill / restore, preserving running process state. The SDK surface does not change.
Persistent processes: vm.execDetached
vm.exec waits for the command to exit, so it blocks on anything that doesn't return (a server, watcher, queue worker) until the timeout. Backgrounding with setsid cmd & doesn't help — the child inherits the agent's stdout/stderr and keeps the connection open. Use vm.execDetached (run_shell with detach: true): it runs the command in its own session with stdio redirected to a log file and returns immediately with the pid. There is no timeout and no auto-kill — manage the process yourself, or use start_dev_server for the singleton dev server.
const { pid, log } = await vm.execDetached("npm run dev", { label: "web" });
// ...later, tail it: GET /v1/projects/:ref/logs?service=${log}
// ...stop it: await vm.exec(`kill -TERM ${pid}`);Auth model
- Management token (
baseUrl+token):spa_account token (org-wide, can create projects) orsbp_instance token (single project). Sent asAuthorization: Bearer <token>. - Git token (
git.baseUrl+git.token): theGIT_INTERNAL_TOKENfor the git control plane. The minted per-repo tokens (git.mintToken) are what agents embed in clone URLs (https://<token>@git.sitepong.com/<ref>/<repo>.git).
If git options are omitted, accessing sp.git throws a clear error.
Errors
SitePongApiError— non-2xx HTTP response. Carries.status,.body,.endpoint.NotImplementedError— method whose endpoint isn't built yet. Carries.planRef.
Build
npx tsc --noEmit # typecheck
npx tsup # emit dist/ (cjs + esm + d.ts)