# JavaScript / TypeScript SDK

> The official @flaxcloud/sdk client for Node, edge runtimes, and the browser. Zero runtime dependencies, full TypeScript types, ESM + CommonJS.

`@flaxcloud/sdk` is the official TypeScript/JavaScript client for Flax Cloud. It wraps the
[HTTP API](./api.md) so you can drive sandboxes from Node, edge runtimes, or the browser. It has
**zero runtime dependencies** (uses the built-in `fetch`), ships ESM + CommonJS, and includes full
TypeScript types.

## Install

```bash
npm install @flaxcloud/sdk
```

Requires Node 18+ (for global `fetch`), a modern edge runtime, or a browser.

## Authenticate

Get an API key from the dashboard, then set it in the environment or pass it in:

```bash
export FLAX_API_KEY=flax_live_...
```

```ts
import { FlaxClient } from "@flaxcloud/sdk";

const flax = new FlaxClient();                      // reads FLAX_API_KEY
const explicit = new FlaxClient({ apiKey: "flax_live_..." });
```

## First sandbox

```ts
import { FlaxClient } from "@flaxcloud/sdk";

const flax = new FlaxClient();
const sb = await flax.createSandbox({ template: "python", name: "ci-run" });
try {
  console.log(sb.name, sb.id);
  const out = await sb.run("python3 -c 'print(6 * 7)'");
  console.log(out.stdout); // "42\n"
} finally {
  await sb.destroy();
}
```

On runtimes with explicit resource management (TS 5.2+, Node 20+), `await using` auto-destroys:

```ts
await using sb = await flax.createSandbox({ template: "python" });
```

Sandbox names are optional labels for humans. The immutable `sb.id` is still the value to store for
API calls, logs, and support.

## Memory limits

Sandboxes default to `512 MB`. Pass `memoryMb` when a task needs more RAM:

```ts
const sb = await flax.createSandbox({ template: "python", memoryMb: 1024 }); // 1 GB
```

Per-sandbox memory is limited by plan: Free `1 GB`, Pro `2 GB`, Builder `4 GB`, Team `8 GB`.
If you request more than your plan allows, the API rejects the create with `memory_limit` instead
of silently creating a smaller sandbox:

```ts
import { FlaxClient, FlaxQuotaError } from "@flaxcloud/sdk";

const flax = new FlaxClient();

try {
  await flax.createSandbox({ template: "python", memoryMb: 2048 });
} catch (err) {
  if (err instanceof FlaxQuotaError && err.code === "memory_limit") {
    console.log("Choose a smaller sandbox or upgrade the plan.");
  } else {
    throw err;
  }
}
```

## Commands, files, sessions, previews

The surface mirrors the [Python SDK](./sdk.md), with camelCase names and Promises:

```ts
// Commands
await sb.run("ls -la", { timeout: 30 });
await sb.codeRun("print('hi')", { language: "python" });
const job = await sb.run("sleep 30", { background: true });
await sb.wait(job);

// Streaming output
const stream = sb.runStream("for i in 1 2 3; do echo $i; sleep 1; done");
for await (const chunk of stream) process.stdout.write(chunk);
console.log(stream.exitCode);

// Files
await sb.upload("/workspace/app.py", "print('hello')");
console.log(await sb.readText("/workspace/app.py"));
await sb.listFiles("/workspace");

// Stateful session (cd/export persist)
const s = await sb.createSession();
await s.run("cd /workspace && export TOKEN=abc");
console.log((await s.run("echo $TOKEN")).stdout); // abc

// Previews
const link = await sb.createPreviewLink(8000); // shareable URL, shown once
```

## Custom images

```ts
import { FlaxImage } from "@flaxcloud/sdk";

// Registry image:
const a = await flax.createTemplate("py-slim", { image: "python:3.12-slim" });

// Declarative Dockerfile (built on the server):
const image = FlaxImage.python("3.12")
  .aptInstall(["git"])
  .pipInstall(["requests"])
  .workdir("/workspace");
const b = await flax.createTemplateFromImage("scraper", image);

// Stream build/pull logs:
const t = await flax.createTemplate("img", { image: "python:3.12-slim", wait: false });
for await (const line of flax.buildLogs(t.id)) process.stdout.write(line);
await flax.waitForTemplate(t.id);

const sb = await flax.createSandbox({ template: b.name });
```

## Filesystem snapshots

```ts
const sb = await flax.createSandbox({
  template: "python",
  env: { API_BASE: "https://example.com" },
  startupCommand: "python3 -m http.server 8000 --bind 0.0.0.0",
});
await sb.upload("/workspace/app.py", "print('ready')\n");

const snap = await sb.createSnapshot("agent-base");
await sb.destroy();

const restored = await flax.createSandbox({ snapshotId: snap.id });
console.log(await restored.readText("/workspace/app.py"));

const snapshots = await flax.listSnapshots();
await flax.deleteSnapshot(snap.id);
```

Snapshots preserve `/workspace` and sandbox configuration. They do not preserve running processes
or RAM.

## Forking

```ts
const sb = await flax.createSandbox({ template: "python", env: { CASE: "base" } });
await sb.upload("/workspace/input.txt", "baseline");

const fork = await sb.fork({ metadata: { branch: "eval-a" } });
await fork.upload("/workspace/input.txt", "changed in fork");

console.log(await sb.readText("/workspace/input.txt"));   // baseline
console.log(await fork.readText("/workspace/input.txt")); // changed in fork

const fork2 = await flax.forkSandbox(sb.id);
```

Forks copy `/workspace` plus sandbox configuration into a new independent sandbox. They do not copy
running processes, RAM, terminal sessions, preview links, or command history.

## Browser capabilities

```ts
const browser = await flax.createSandbox({ capabilities: ["browser"] });
console.log(browser.capabilities); // ["browser"]
await browser.browser.start();
const cdpUrl = await browser.browser.getCdpUrl();
const shot = await browser.browser.screenshotBytes();
```

Browser artifacts persist under `/workspace/.flax/browser/`. Browser automation is CDP-first:
connect Playwright, Puppeteer, browser-use, Stagehand, or your own CDP client to the short-lived
authenticated URL from `getCdpUrl()`.

## Errors

Every API error maps to a typed subclass of `FlaxError`:

```ts
import { FlaxQuotaError, FlaxError } from "@flaxcloud/sdk";

try {
  await flax.createSandbox({ template: "python" });
} catch (err) {
  if (err instanceof FlaxQuotaError) console.error("plan limit:", err.code);
  else if (err instanceof FlaxError) console.error(err.status, err.code, err.message);
  else throw err;
}
```

`FlaxAuthError` (401), `FlaxQuotaError` (402), `FlaxNotFoundError` (404), `FlaxConflictError`
(409), `FlaxBadRequestError` (other 4xx), `FlaxServerError` (5xx), `FlaxConnectionError` (network).

## Configuration

```ts
new FlaxClient({
  apiKey: "flax_live_...",            // else $FLAX_API_KEY
  baseUrl: "https://flaxcloud.com",   // else $FLAX_BASE_URL
  timeout: 60_000,                    // per-request timeout (ms)
  maxRetries: 2,                      // idempotent GET/PUT/DELETE only
  fetch: customFetch,                 // inject a fetch implementation
});
```

Idempotent requests are retried with exponential backoff (honoring `Retry-After`) on transient
`429/502/503/504` and network errors; `POST` is never auto-retried.
