interface ConnectionOpts { apiKey?: string; apiUrl?: string; timeoutMs?: number; requestTimeoutMs?: number; } declare class ConnectionConfig { readonly apiKey: string; readonly apiUrl: string; readonly timeoutMs: number; constructor(opts?: ConnectionOpts); get headers(): Record; } /** A process this sandbox is currently running. */ interface ProcessInfo { pid: number; cmd: string[]; /** When the process started, unix milliseconds. */ startedAt: number; } /** Signals accepted by {@link Process.kill}. */ type ProcessSignal = 'SIGTERM' | 'SIGKILL' | 'SIGINT' | 'SIGHUP' | 'SIGQUIT' | 'SIGUSR1' | 'SIGUSR2'; /** * The result of a process execution inside a Lizard microVM. */ interface ProcessResult { stdout: string; stderr: string; exitCode: number; } interface ProcessOpts { timeoutMs?: number; envs?: Record; user?: string; workdir?: string; /** Called with each stdout line as it is produced, rather than at the end. */ onStdout?: (data: string) => void; /** Called with each stderr line as it is produced, rather than at the end. */ onStderr?: (data: string) => void; /** * Called once with the process's pid, before any output. Gives a streaming * `exec` something to pass to {@link Process.kill}. */ onPid?: (pid: number) => void; } /** * Runs processes inside a Lizard sandbox microVM. * * Access via `sandbox.process`. */ declare class Process { private readonly sandboxId; private readonly config; constructor(sandboxId: string, config: ConnectionConfig); /** * Execute a command inside the microVM and wait for it to complete. * * The command runs in a shell inside the Lizard sandbox and returns * stdout, stderr, and the exit code when it finishes. * * @param cmd Shell command to run inside the microVM. * @param opts Optional execution options — environment variables, working * directory, user, and timeout. * * @example * ```ts * const result = await sandbox.process.exec('node index.js') * console.log(result.stdout) * ``` * * @example Run with a custom working directory and env vars: * ```ts * const result = await sandbox.process.exec('npm test', { * workdir: '/app', * envs: { NODE_ENV: 'test' }, * }) * ``` */ exec(cmd: string, opts?: ProcessOpts): Promise; /** * Read an SSE exec stream, handing each line to the caller as it arrives while * still accumulating the full result. Events are `{stream, line}` for output and * `{exitCode}` at the end. */ /** * List the processes this sandbox is currently running. * * Only processes started through `exec` — not every process in the guest. * Those are the ones you can act on; the rest are the image's own business. * * @example * ```ts * for (const p of await sandbox.process.list()) { * console.log(p.pid, p.cmd.join(' ')) * } * ``` */ list(): Promise; /** * Signal a running process. Defaults to `SIGTERM`. * * The signal goes to the process group, so a shell's children die with it — * killing `sh -c 'sleep 100'` otherwise leaves the sleep running. * * The pid comes from {@link list}, or from the `pid` event at the start of a * streaming `exec`. * * @example Stop a long build: * ```ts * const [build] = await sandbox.process.list() * await sandbox.process.kill(build.pid) * ``` */ kill(pid: number, signal?: ProcessSignal): Promise; private consumeStream; } interface FsOpts { user?: string; } /** A filesystem change reported by a {@link Watcher}. */ interface FsEvent { type: 'create' | 'write' | 'remove' | 'rename' | 'chmod'; /** The entry's name within its directory. */ name: string; /** Full path inside the sandbox. */ path: string; } interface FileInfo { name: string; path: string; type: 'file' | 'dir' | 'symlink'; size: number; /** Permission bits as a string, e.g. `-rw-r--r--`. Absent on older sandboxes. */ mode?: string; /** Last modification time, unix milliseconds. Absent on older sandboxes. */ modTime?: number; } /** * Read and write files inside a Lizard sandbox microVM. * * Access via `sandbox.fs`. */ declare class Fs { private readonly sandboxId; private readonly config; constructor(sandboxId: string, config: ConnectionConfig); /** * Write a file into the microVM filesystem. * * Creates parent directories automatically if they don't exist. * * @example * ```ts * await sandbox.fs.write('/app/index.js', 'console.log("hello")') * ``` * * @example Write binary data: * ```ts * await sandbox.fs.write('/app/data.bin', buffer) * ``` */ write(path: string, data: string | Uint8Array, opts?: FsOpts): Promise; /** * Read a file from the microVM filesystem. * * @returns The file contents as a UTF-8 string. * * @example * ```ts * const content = await sandbox.fs.read('/app/index.js') * ``` */ read(path: string, opts?: FsOpts): Promise; /** * List files and directories at the given path inside the microVM. * * @example * ```ts * const entries = await sandbox.fs.list('/app') * ``` */ list(path: string, opts?: FsOpts): Promise; /** * Remove a file or directory from the microVM filesystem. */ remove(path: string, opts?: FsOpts): Promise; /** * Create a directory (and any missing parents) inside the microVM. */ makeDir(path: string, opts?: FsOpts): Promise; /** * Metadata for a single path — size, type, permissions, modification time. * * Saves listing a parent directory and filtering it just to answer "does this * exist, and how big is it". * * @example * ```ts * const info = await sandbox.fs.stat('/app/out.bin') * console.log(info.size, info.type) * ``` * * Throws `NotFoundError` if the path does not exist. Sandboxes created before * this shipped run a guest agent without it and throw `LizardError` (501) — * recreate the sandbox to use it. */ stat(path: string, opts?: FsOpts): Promise; /** * Move or rename a path. Creates the destination's parent directories. * * @example * ```ts * await sandbox.fs.move('/tmp/build.log', '/app/logs/build.log') * ``` * * Sandboxes created before this shipped run a guest agent without it and throw * `LizardError` (501) — recreate the sandbox to use it. */ move(from: string, to: string): Promise; /** * Watch a directory for changes. * * Polling rather than a push stream: the events cross two proxy hops, and a * long-lived stream through both is exactly what breaks first. Call * {@link Watcher.getEvents} on whatever interval suits you — each call drains * everything queued since the last one, so nothing is missed between polls. * * Remember to {@link Watcher.close} it; an abandoned watcher keeps queueing * events in the guest until the sandbox ends (bounded, but wasted). * * @example * ```ts * const w = await sandbox.fs.watch('/app', { recursive: true }) * setInterval(async () => { * for (const e of await w.getEvents()) console.log(e.type, e.path) * }, 1000) * ``` * * Sandboxes created before this shipped run a guest agent without it and throw * `LizardError` (501) — recreate the sandbox to use it. */ watch(path: string, opts?: { recursive?: boolean; }): Promise; private execInternal; } /** * A handle to a directory watch inside a sandbox. Created by {@link Fs.watch}. */ declare class Watcher { private readonly sandboxId; private readonly config; /** Server-side id for this watch. */ readonly watcherId: string; constructor(sandboxId: string, config: ConnectionConfig, /** Server-side id for this watch. */ watcherId: string); /** * Drain every change since the last call. Returns an empty array when nothing * has happened — that is not an error, just a quiet interval. */ getEvents(): Promise; /** Stop watching and release the guest-side queue. */ close(): Promise; } interface SandboxInfo { sandboxId: string; template: string; startedAt: string; endAt: string; /** Region the sandbox runs in — the volume's region when one is attached. */ region?: string; status?: string; cpus?: number; memoryMb?: number; metadata?: Record; } interface SandboxOpts extends ConnectionOpts { /** * The project this sandbox belongs to — its ID, slug, or name. Required: * a sandbox must be attributed to a project so its CPU, RAM, egress, and * storage are billed. Prefer the {@link Lizard} client, which pins a project * for you. Ignored when {@link SandboxOpts.projectId} is set. */ project?: string; /** Exact project ID — skips resolving {@link SandboxOpts.project}. */ projectId?: string; template?: string; metadata?: Record; envs?: Record; timeoutMs?: number; /** * Region to run the sandbox in, e.g. `'us-east-1'`. * * Leave it unset when attaching a volume: a volume is node-local, so the server * places the sandbox in the volume's own region. Setting it to a region the volume * is not in is rejected with a 400 rather than silently moved — that combination * cannot be satisfied. * * Defaults to the platform's default sandbox region. */ region?: string; /** Attach a persistent volume by id, mounted at `/workspace`. */ volumeId?: string; /** * Attach a persistent volume by name, mounted at `/workspace`. A volume's name is its * key inside a project, so this is usually what you want — see {@link Volume}. * Requires the sandbox's project to be given as an exact `projectId`. */ volumeName?: string; /** * A `liz_` API key to write into the sandbox, so `lizard` works inside it. The CLI is * preinstalled in every template; this is what authenticates it. * * The key must belong to the caller — the server verifies that and silently skips the * injection otherwise. * * SECURITY: anything running in the sandbox can read this key, and sandboxes run * untrusted code. Pass a WORKSPACE-SCOPED key rather than a full-access one. Scopes are * enforced end to end, so a scoped key that escapes is bounded to that one workspace. * * @example * ```ts * const sandbox = await Sandbox.create('codex', { * projectId: 'proj_123', * lizardToken: process.env.LIZARD_WORKSPACE_KEY, // scoped to one workspace * }) * await sandbox.process.exec('lizard volume list') * ``` */ lizardToken?: string; } /** * Low-level HTTP client for the Lizard sandbox API. * Extended by the `Sandbox` class — you typically don't use this directly. */ declare class SandboxClient { protected static createSandbox(template: string, timeoutMs: number, opts?: SandboxOpts): Promise<{ sandboxId: string; }>; protected static killSandbox(sandboxId: string, opts?: ConnectionOpts): Promise; protected static pauseSandbox(sandboxId: string, opts?: ConnectionOpts): Promise; protected static resumeSandbox(sandboxId: string, opts?: ConnectionOpts): Promise; protected static listSandboxes(opts?: ConnectionOpts): Promise; protected static getSandboxInfo(sandboxId: string, opts?: ConnectionOpts): Promise; protected static setTimeoutSandbox(sandboxId: string, timeoutMs: number, opts?: ConnectionOpts): Promise; protected static exposeSandboxPort(sandboxId: string, port: number, opts?: ConnectionOpts): Promise<{ hostname: string; url: string; }>; } /** * A Lizard sandbox — an isolated Linux environment that starts in under a second. * * Each sandbox is a full Linux environment with its own filesystem, network, and * process namespace, restored from a pre-warmed template snapshot. * * Sandboxes are **ephemeral**: killing one, or letting it hit its timeout, discards * everything written inside it. State that has to outlive a sandbox belongs on a * {@link Volume}, which is a separate disk you mount at `/workspace` and re-attach to a * later sandbox. * * @example Basic usage: * ```ts * import { Sandbox } from '@lizard-build/sdk' * * const sandbox = await Sandbox.create('base', { project: 'my-project' }) * await sandbox.fs.write('/app/index.js', 'console.log("hello world")') * const result = await sandbox.process.exec('node /app/index.js') * console.log(result.stdout) // "hello world" * await sandbox.kill() * ``` * * @example Carry work across sandboxes with a volume: * ```ts * const vol = await Volume.getOrCreate(projectId, 'agent-scratch', { sizeGb: 10 }) * * const first = await Sandbox.create('codex', { projectId, volumeName: 'agent-scratch' }) * await first.process.exec('echo "notes" > /workspace/notes.txt') * await first.kill() * * // A different sandbox, the same disk. No region to thread through: the sandbox * // is placed wherever the volume already lives. * const second = await Sandbox.create('codex', { projectId, volumeName: 'agent-scratch' }) * console.log(await second.fs.read('/workspace/notes.txt')) // "notes" * ``` */ declare class Sandbox extends SandboxClient { protected static readonly defaultTemplate: string; protected static readonly defaultTimeoutMs: number; /** * Unique identifier of this sandbox microVM. */ readonly sandboxId: string; /** * Read and write files inside the microVM filesystem. * * @example * ```ts * await sandbox.fs.write('/app/main.py', 'print("hello")') * const src = await sandbox.fs.read('/app/main.py') * ``` */ readonly fs: Fs; /** * Execute processes inside the microVM. * * @example * ```ts * const { stdout } = await sandbox.process.exec('python main.py') * ``` */ readonly process: Process; protected readonly connectionConfig: ConnectionConfig; constructor(opts: { sandboxId: string; } & ConnectionOpts); /** * Create a new Lizard sandbox from the default `base` template. * * @example * ```ts * const sandbox = await Sandbox.create({ project: 'my-project' }) * ``` */ static create(opts?: SandboxOpts): Promise; /** * Create a new Lizard sandbox from the specified template. * * Available templates: `base` (Debian + Node.js 26) and `code-interpreter-v1` * (Python 3.14 + Node.js 26). Custom templates can be built and pushed via * `lizard push`. * * @param template Name of the sandbox template to boot from. * * @example * ```ts * const sandbox = await Sandbox.create('base', { project: 'my-project' }) * const sandbox = await Sandbox.create('code-interpreter-v1', { project: 'my-project', timeoutMs: 10 * 60 * 1000 }) * ``` */ static create(template: string, opts?: SandboxOpts): Promise; /** * Connect to an existing sandbox by its ID. * * Verifies the sandbox exists and is reachable, then returns a handle to it. * Throws `NotFoundError` if it has been killed or has expired. * * This used to call `resume` first, on the assumption that a sandbox you are * reconnecting to might be paused. Sandboxes are pods now and pause/resume is a * `501` on every one of them, so that call turned every `connect()` into an * error against a perfectly healthy sandbox. Connecting does not need to change * a sandbox's state, so it no longer tries to. * * @example * ```ts * const sandbox = await Sandbox.connect('sandbox_abc123') * ``` */ static connect(sandboxId: string, opts?: ConnectionOpts): Promise; /** * List all running sandboxes for the authenticated account. * * @example * ```ts * const sandboxes = await Sandbox.list() * ``` */ static list(opts?: ConnectionOpts): Promise; /** * Kill the sandbox and release its resources immediately. * * @returns `true` if the microVM was terminated, `false` if it was already gone. */ kill(opts?: ConnectionOpts): Promise; /** * Pause the sandbox by freezing it in place. * * @deprecated Not implemented for the current runtime — always throws * `LizardError` with HTTP 501. Sandboxes run as pods, and the equivalent is a CRIU * checkpoint of the pod, which is not built. To park work across a gap, put it on a * {@link Volume} and create a fresh sandbox on that volume later; the volume is the * part that is meant to outlive a sandbox. */ pause(opts?: ConnectionOpts): Promise; /** * Resume a paused sandbox. * * @deprecated Not implemented for the current runtime — always throws * `LizardError` with HTTP 501. See {@link pause}. `Sandbox.connect()` no longer * calls this, so reconnecting to a running sandbox works without it. */ resume(opts?: ConnectionOpts): Promise; /** * Get metadata and status information about this sandbox. */ getInfo(opts?: ConnectionOpts): Promise; /** * Extend or reduce the sandbox timeout. * * @param timeoutMs New timeout in milliseconds measured from now. */ setTimeout(timeoutMs: number, opts?: ConnectionOpts): Promise; /** * Get the public HTTPS URL for a port exposed inside the sandbox. * * Useful for accessing HTTP servers started inside the microVM from your * agent or tests without additional tunneling. * * @example * ```ts * await sandbox.process.exec('npx -y serve -p 3000 &') * const url = sandbox.getHost(3000) * // https://{sandboxId}-3000.sandbox.{region}.onlizard.com * ``` */ getHost(port: number, opts?: ConnectionOpts): Promise; private resolveOpts; } /** Shared HTTP helper for platform (non-sandbox) API calls. */ declare class PlatformClient { readonly config: ConnectionConfig; constructor(opts: ConnectionOpts); get(path: string): Promise; post(path: string, body?: unknown): Promise; patch(path: string, body: unknown): Promise; delete(path: string, body?: unknown): Promise; postForm(path: string, form: FormData): Promise; /** Stream SSE events from a path, calling handler for each data line. */ streamSse(path: string): AsyncGenerator; } interface Project { id: string; name: string; slug: string; workspaceId: string; createdAt?: string; } interface CreateProjectOpts { workspaceId: string; name: string; } declare class ProjectsAPI { private readonly client; constructor(client: PlatformClient); /** List all projects the API key can access. */ list(opts?: { workspaceId?: string; }): Promise; /** Get a project by ID. */ get(id: string): Promise; /** Create a new project. */ create(opts: CreateProjectOpts): Promise; /** Update a project's name. */ update(id: string, opts: { name?: string; }): Promise; /** Delete a project. */ delete(id: string): Promise; } interface Service { id: string; name: string; projectId: string; status: 'none' | 'running' | 'crashed' | 'stopped'; deployStatus: 'idle' | 'building' | 'deploying' | 'restarting' | 'failed' | 'deleting'; domain?: string; region?: string; sourceType?: 'github' | 'upload'; repoUrl?: string; branch?: string; startCommand?: string; buildCommand?: string; containerPort?: number; } interface CreateServiceOpts { projectId: string; name: string; sourceType?: 'github' | 'upload'; repoUrl?: string; branch?: string; startCommand?: string; buildCommand?: string; containerPort?: number; region?: string; /** Set to 0 for worker mode (no HTTP listener). */ port?: number; } interface ScaleOpts { replicas?: number; cpuMillis?: number; memoryMi?: number; storageMi?: number; } interface LogLine { level: 'info' | 'error' | 'warn' | 'debug'; message: string; ts: number; service?: string; replica?: string; } interface DeployEvent { event: 'log' | 'done' | 'error' | 'deployed' | 'failed' | 'deploying'; line?: string; message?: string; status?: string; url?: string | null; } /** * Handle for a running deploy — stream its logs or await completion. * * @example * ```ts * const deploy = await lizard.services.upload({ projectId, source: fs.readFileSync('app.tar.gz') }) * for await (const line of deploy.logs()) console.log(line) * const result = await deploy.wait() * console.log('deployed to', result.url) * ``` */ declare class DeployHandle { private readonly _serviceId; private readonly _buildId; private readonly _client; constructor(client: PlatformClient, serviceId: string, buildId?: string); get serviceId(): string; /** Stream build + runtime log lines until the deploy finishes. */ logs(): AsyncGenerator; /** * Poll until the deploy reaches a terminal state. * @returns `{ url, status }` on success; throws `LizardError` on failure. */ wait(opts?: { timeoutMs?: number; pollMs?: number; }): Promise<{ url: string | null; status: string; }>; } declare class ServicesAPI { private readonly client; constructor(client: PlatformClient); /** List all services in a project. */ list(opts: { projectId: string; }): Promise; /** Get a service by ID. */ get(id: string): Promise; /** * Create a service and start a deploy from a git repo. * Returns a DeployHandle to track progress. */ deploy(opts: CreateServiceOpts & { waitForDeploy?: boolean; }): Promise; /** * Upload a tarball and deploy it. * * @param opts.source - A `Buffer`, `Blob`, or `Uint8Array` of a `.tar.gz` file. */ upload(opts: { projectId: string; name: string; source: Blob | string; startCommand?: string; buildCommand?: string; port?: number; region?: string; }): Promise; /** Trigger a redeploy (rebuild from current source). */ redeploy(id: string): Promise; /** Restart a service without rebuilding. */ restart(id: string): Promise; /** Scale a service. */ scale(id: string, opts: ScaleOpts): Promise; /** * Get recent log lines. For a live tail, use the WebSocket API instead. * * @param opts.limit - Max lines to return (default 200, max 1000). */ logs(id: string, opts?: { limit?: number; since?: string; }): Promise; /** Execute a command inside the running service container. */ exec(id: string, cmd: string, opts?: { timeoutMs?: number; }): Promise<{ stdout: string; stderr: string; exitCode: number; }>; /** Update service configuration. */ update(id: string, opts: Partial>): Promise; /** Delete a service. */ delete(id: string): Promise; } type AddonType = 'postgres' | 'mysql' | 'mongodb' | 'redis' | 's3'; interface Addon { id: string; name: string; type: AddonType; projectId: string; status: 'none' | 'running' | 'crashed' | 'stopped'; deployStatus: string; version?: string; /** Exposed connection variables (e.g. DATABASE_URL, REDIS_URL). */ env?: Record; } interface CreateAddonOpts { projectId: string; type: AddonType; name?: string; version?: string; /** vCPU count */ vcpu?: number; /** Memory in MB */ memoryMb?: number; /** Storage in GB */ storageGb?: number; } declare class AddonsAPI { private readonly client; constructor(client: PlatformClient); /** List all addons in a project. */ list(opts: { projectId: string; }): Promise; /** Get an addon by ID. */ get(projectId: string, addonId: string): Promise; /** * Create a new managed addon (database, cache, or object store). * * @example * ```ts * const pg = await lizard.addons.create({ projectId, type: 'postgres' }) * // Inject into a service: * await lizard.secrets.set({ serviceId: svcId, key: 'DATABASE_URL', value: `${{${pg.name}.DATABASE_URL}}` }) * ``` */ create(opts: CreateAddonOpts): Promise; /** Delete an addon. */ delete(projectId: string, addonId: string): Promise; /** Resize an addon (CPU / memory / storage). */ resize(projectId: string, addonId: string, opts: { vcpu?: number; memoryMb?: number; storageGb?: number; }): Promise; /** Restart an addon VM. */ redeploy(projectId: string, addonId: string): Promise; } interface Secret { key: string; value: string; } interface SetSecretOpts { key: string; value: string; /** Target a specific service. Omit for project-scope. */ serviceId?: string; /** If true, set at project scope (shared across all services). */ global?: boolean; } interface ListSecretsOpts { serviceId?: string; } declare class SecretsAPI { private readonly client; constructor(client: PlatformClient); /** * List secrets for a project or service. * * @param projectId - The project ID. * @param opts.serviceId - If given, list service-scoped secrets instead of project secrets. */ list(projectId: string, opts?: ListSecretsOpts): Promise; /** * Set one or more secrets. * * ```ts * // Service-scoped secret (default): * await lizard.secrets.set(projectId, { key: 'DATABASE_URL', value: '${{postgres.DATABASE_URL}}', serviceId }) * * // Project-scoped (shared across all services): * await lizard.secrets.set(projectId, { key: 'LOG_LEVEL', value: 'info', global: true }) * ``` */ set(projectId: string, secrets: SetSecretOpts | SetSecretOpts[]): Promise; /** Delete a secret by key. */ delete(projectId: string, opts: { key: string; serviceId?: string; }): Promise; } interface DomainInfo { domain: string; /** 'pending' until DNS propagates and the platform verifies the CNAME/TXT record. */ verified: boolean; txtRecord?: string; cnameTarget?: string; } declare class DomainsAPI { private readonly client; constructor(client: PlatformClient); /** * Show the current domain(s) for a service. * The platform-assigned `.onlizard.com` subdomain is always present. */ list(serviceId: string): Promise; /** * Attach a custom domain to a service. * Returns the DNS records to add before calling `verify()`. * * @example * ```ts * const info = await lizard.domains.add(serviceId, 'api.example.com') * console.log('Add CNAME:', info.cnameTarget) * ``` */ add(serviceId: string, domain: string): Promise; /** * Verify that DNS records have propagated and activate the domain. * Call after adding the CNAME or TXT record returned by `add()`. */ verify(serviceId: string, domain: string): Promise; } type MetricRange = '1h' | '6h' | '24h' | '7d' | '14d' | '30d'; interface MetricPoint { t: number; value: number; } interface ServiceMetrics { cpu: MetricPoint[]; memory: MetricPoint[]; networkRx: MetricPoint[]; networkTx: MetricPoint[]; diskRead: MetricPoint[]; diskWrite: MetricPoint[]; } interface CostMetrics { compute: number; egress: number; storage: number; total: number; currency: string; } declare class MetricsAPI { private readonly client; constructor(client: PlatformClient); /** Get CPU metrics for a service or addon. */ cpu(id: string, range?: MetricRange): Promise; /** Get memory metrics for a service or addon. */ memory(id: string, range?: MetricRange): Promise; /** Get network I/O metrics for a service. */ network(id: string, range?: MetricRange): Promise<{ rx: MetricPoint[]; tx: MetricPoint[]; }>; /** Get disk I/O metrics for a service. */ disk(id: string, range?: MetricRange): Promise<{ read: MetricPoint[]; write: MetricPoint[]; }>; /** * Get cost breakdown for a project over the given range. * Costs are in USD cents unless the `currency` field says otherwise. */ cost(projectId: string, range?: MetricRange): Promise; /** * Convenience: fetch all metric types for a service in one call. */ all(id: string, range?: MetricRange): Promise; } interface Workspace { id: string; name: string; slug: string; /** `owner` | `admin` | `member` — the calling account's role in this workspace. */ role?: string; /** True for the account's own workspace, which cannot be deleted. */ isPersonal?: boolean; projectCount?: number; plan?: string; createdAt?: number | string; } interface CreateWorkspaceOpts { name: string; } /** * Workspaces — the top of the ownership tree: a workspace holds projects, a project * holds services, sandboxes and volumes. * * This is the missing first step of per-user provisioning. Handing each of your users * their own workspace, plus an API key scoped to it, is what keeps them isolated from * one another while all of it bills to your account: * * ```ts * const ws = await lizard.workspaces.create({ name: `user-${userId}` }) * const prj = await lizard.projects.create({ workspaceId: ws.id, name: 'default' }) * const key = await lizard.apiKeys.create({ name: `user-${userId}`, workspaces: [ws.id] }) * // hand key.key to that user — it reaches nothing outside ws * ``` * * @see {@link ApiKeysAPI} for the scoping half of that flow. */ declare class WorkspacesAPI { private readonly client; constructor(client: PlatformClient); /** * List workspaces the calling credential can see. * * A scoped API key sees only what it is scoped to: a workspace-scoped key returns * that one workspace, and a project-scoped key returns the workspace containing its * project. A full key returns every workspace the account belongs to. */ list(): Promise; /** Create a workspace. The caller becomes its owner. */ create(opts: CreateWorkspaceOpts): Promise; /** * Delete a workspace. * * Empty-only by default — the server refuses while any project or sandbox remains, * so this cannot quietly destroy a user's work. Pass `{ force: true }` to delete a * workspace and everything in it; that is irreversible. */ delete(id: string, opts?: { force?: boolean; }): Promise; /** * Find a workspace by name, slug, or id, or `null` if there is no such workspace. * Matching is exact; id wins, then slug, then name. */ find(nameOrSlugOrId: string): Promise; } interface ApiKeyScope { type: 'workspace' | 'project'; id: string; /** Present on read; the server resolves the scoped resource's name for display. */ name?: string; } interface ApiKey { id: string; name: string; /** A masked fragment, e.g. `liz_abc…xyz`. The full key is only ever returned once. */ keyPreview?: string; scopes: ApiKeyScope[]; createdAt?: number | string | null; lastUsedAt?: number | string | null; } interface CreatedApiKey extends ApiKey { /** * The full `liz_` secret. **Returned exactly once, by this create call** — it is * stored hashed and no later request can read it back. Hand it to its owner or * persist it here, or it is gone. */ key: string; } interface CreateApiKeyOpts { name: string; /** Workspace ids this key may reach. */ workspaces?: string[]; /** Project ids this key may reach. */ projects?: string[]; /** Raw scope list, if you would rather build it yourself than use the two above. */ scopes?: ApiKeyScope[]; } /** * API keys, including the scoped keys that make per-user isolation possible. * * **A key with no scope has full access** to every workspace and project the creating * account can reach. Pass `workspaces` or `projects` to bound it. Scopes are enforced * server-side on every route, so a scoped key that leaks — out of a sandbox, a log, a * user's machine — reaches only what it was scoped to. * * A scoped key cannot mint a broader key: that check is server-side and exact, so * handing a user a workspace-scoped key is not a step away from full access. * * @example Give each of your users their own isolated workspace * ```ts * const ws = await lizard.workspaces.create({ name: `user-${userId}` }) * const prj = await lizard.projects.create({ workspaceId: ws.id, name: 'default' }) * const key = await lizard.apiKeys.create({ name: `user-${userId}`, workspaces: [ws.id] }) * * // key.key is visible only here. Store it now. * await db.users.update(userId, { lizardKey: key.key }) * ``` * * @example Let a sandbox use the CLI as that user, and nothing more * ```ts * const sandbox = await Sandbox.create('codex', { * projectId: prj.id, * lizardToken: key.key, // scoped — safe to expose to code in the sandbox * }) * await sandbox.process.exec('lizard volume list') * ``` */ declare class ApiKeysAPI { private readonly client; constructor(client: PlatformClient); /** List the account's API keys. Previews only — full keys are never returned here. */ list(): Promise; /** * Create an API key. * * The response is the only place the full key appears. Omitting every scope creates * a full-access key; the server rejects an attempt to create one from a key that is * itself scoped. */ create(opts: CreateApiKeyOpts): Promise; /** Revoke a key by id. It stops working immediately, everywhere. */ delete(id: string): Promise; } interface Region { id: string; name?: string; label?: string; country?: string; city?: string; /** False for a region that exists but is not accepting new workloads. */ available?: boolean; isDefault?: boolean; } /** * The regions workloads can be placed in. * * Region ids are what `Sandbox.create({ region })` and `Volume.create({ region })` * take, so this is how you discover a valid value rather than hardcoding one. * * @example * ```ts * const regions = await lizard.regions.list() * console.log(regions.map(r => r.id)) // ['eu-west-lim-a', 'us-east-1', ...] * ``` */ declare class RegionsAPI { private readonly client; constructor(client: PlatformClient); /** List every region, including ones not currently accepting workloads. */ list(): Promise; } interface Balance { plan: string; status: 'active' | 'grace' | 'frozen' | string; /** Current balance in cents. Negative means the account is in debt. */ balanceCents: number; overdraftLimitCents: number | null; availableCents: number | null; /** Current burn rate in cents per hour, across every running workload. */ hourlyRateCents: number; /** Hours of runway left at the current rate, or `null` when nothing is running. */ runwayHours: number | null; expiringCents: number; expiringAt: number | null; neverFreeze?: boolean; invoicedMonthly?: boolean; email?: string | null; } interface Transaction { id: string; kind: string; amountCents: number; balanceAfterCents?: number; description?: string; createdAt: number; } interface TransactionPage { items: Transaction[]; nextCursor: string | null; } interface ListTransactionsOpts { /** 1-100, default 20. */ limit?: number; /** `nextCursor` from a previous page. */ cursor?: string; /** Include the daily usage-deduction rows, which are otherwise omitted as noise. */ includeUsage?: boolean; } /** * Account balance and usage. * * Billing is **account-scoped, not workspace-scoped**: every workspace you create for * a user bills to the account that owns the key. That is what makes per-user * workspaces a safe pattern — your users get isolation, you keep one bill — and also * what makes {@link Balance.runwayHours} worth watching before you provision more. * * **Requires an unscoped key.** A scoped key is refused with 403 * `ACCOUNT_SCOPE_REQUIRED`, because there is no workspace-scoped view of one shared * balance, ledger and set of saved cards — and because a scoped key is meant to be * handed to an end user, who should not be reading your card details or spending * against them. For per-workspace spend, use {@link MetricsAPI.cost} instead. */ declare class BillingAPI { private readonly client; constructor(client: PlatformClient); /** Current balance, status, burn rate, and runway. */ balance(): Promise; /** A page of balance transactions, newest first. */ transactions(opts?: ListTransactionsOpts): Promise; /** Cost summary for the current billing period, broken down by resource. */ summary(): Promise; /** Live (not-yet-invoiced) usage accumulating right now. */ live(): Promise; } interface VolumeInfo { id: string; projectId: string; name: string; sizeGb: number; sizeMb?: number; /** Region the volume's node lives in. A sandbox mounting it runs here too. */ region?: string; status: string; attachedTo?: string | null; createdAt: number; } interface CreateVolumeOpts extends ConnectionOpts { sizeGb?: number; /** * Region to place the volume in, e.g. `'us-east-1'`. A volume is node-local, so * this also fixes where any sandbox mounting it must run — `Sandbox.create` takes * the volume's region automatically when you don't name one, so you normally set * the region here or nowhere. * * Defaults to the platform's default region. */ region?: string; } /** * A persistent volume that outlives sandboxes. * * A volume's **name** is its key inside a project: names are unique per project, and * every method here accepts either a name or the generated id wherever a volume is * addressed. Prefer the name — it is the thing you chose and can reconstruct, while * the id only exists after the first create. * * Names are slugs: lowercase letters, digits and dashes, starting and ending with a * letter or digit, up to 64 characters. * * Mount one to a sandbox via `Sandbox.create({ volumeName: 'my-data' })`. */ declare class Volume { /** The volume's generated id. Stable, but you rarely need it — address by name. */ readonly volumeId: string; /** The volume's name: unique within its project, and usable anywhere `volumeId` is. */ readonly name?: string; private readonly config; constructor(opts: { volumeId: string; name?: string; } & ConnectionOpts); /** * Create a volume. Throws `ConflictError` (409) if the project already has one with * this name — use {@link getOrCreate} when you want "make sure this exists" instead. */ static create(projectId: string, name: string, opts?: CreateVolumeOpts): Promise; /** * Return the project's volume with this name, creating it first if it does not * exist yet. This is the reason a volume's name is its key: an agent that wants * "the scratch disk for this task" no longer has to store an id between runs. * * An existing volume is returned as-is — `sizeGb` applies only to a fresh create * and never resizes one that is already there. */ static getOrCreate(projectId: string, name: string, opts?: CreateVolumeOpts): Promise; /** Look a volume up by name or by id. */ static get(projectId: string, nameOrId: string, opts?: ConnectionOpts): Promise; static list(projectId: string, opts?: ConnectionOpts): Promise; /** Delete a volume by name or by id, without constructing one first. */ static delete(projectId: string, nameOrId: string, opts?: ConnectionOpts): Promise; getInfo(projectId: string): Promise; delete(projectId: string): Promise; } interface LizardOpts extends ConnectionOpts { /** * The project every sandbox created through this client belongs to — its ID, * slug, or name. Required for sandbox operations; optional for platform * management (projects, services, addons, etc.). */ project?: string; } /** * The Lizard client — entry point for sandboxes and platform management. * * @example Sandbox usage (backward-compatible) * ```ts * const lizard = new Lizard({ project: 'my-project' }) * const sandbox = await lizard.create('base') * await sandbox.process.exec('echo hello') * await sandbox.kill() * ``` * * @example Platform management * ```ts * const lizard = new Lizard({ apiKey: process.env.LIZARD_API_KEY }) * * // Deploy from git * const deploy = await lizard.services.deploy({ projectId, name: 'api', repoUrl: '...', branch: 'main' }) * const result = await deploy.wait() * console.log('Deployed to', result.url) * * // Add a Postgres addon and wire it to the service * const pg = await lizard.addons.create({ projectId, type: 'postgres' }) * await lizard.secrets.set(projectId, { * serviceId: result.serviceId, * key: 'DATABASE_URL', * value: `${{${pg.name}.DATABASE_URL}}`, * }) * ``` */ declare class Lizard { private readonly config; private readonly projectRef; private _platform; /** Create, list and delete workspaces — the top of the ownership tree. */ readonly workspaces: WorkspacesAPI; /** Mint and revoke API keys, including keys scoped to one workspace or project. */ readonly apiKeys: ApiKeysAPI; /** List the regions workloads can be placed in. */ readonly regions: RegionsAPI; /** Account balance, burn rate and transactions. */ readonly billing: BillingAPI; /** Manage projects. */ readonly projects: ProjectsAPI; /** Deploy and manage services. */ readonly services: ServicesAPI; /** Manage addons (postgres, redis, s3, mysql, mongodb). */ readonly addons: AddonsAPI; /** Manage secrets and environment variables. */ readonly secrets: SecretsAPI; /** Manage custom domains. */ readonly domains: DomainsAPI; /** Query CPU, memory, network, disk, and cost metrics. */ readonly metrics: MetricsAPI; constructor(opts: LizardOpts); /** Resolve the client's project reference to a stable project ID (cached). */ projectId(): Promise; private connectionOpts; /** * Create a new sandbox in this client's project. * @requires `project` to be set in the constructor. */ create(template?: string, opts?: Omit): Promise; /** Connect to an existing sandbox by ID. Throws `NotFoundError` if it is gone. */ connect(sandboxId: string, opts?: ConnectionOpts): Promise; /** List running sandboxes for the authenticated account. */ list(opts?: ConnectionOpts): Promise; /** * Persistent volumes in this client's project. * * The same calls as the static {@link Volume} methods, minus the `projectId` * argument — the client already knows it. * * @requires `project` to be set in the constructor. * * @example * ```ts * const lizard = new Lizard({ project: 'my-project' }) * const vol = await lizard.volumes.getOrCreate('scratch', { sizeGb: 10 }) * const sb = await lizard.create('codex', { volumeName: 'scratch' }) * ``` */ readonly volumes: { create: (name: string, opts?: CreateVolumeOpts) => Promise; getOrCreate: (name: string, opts?: CreateVolumeOpts) => Promise; get: (nameOrId: string, opts?: ConnectionOpts) => Promise; list: (opts?: ConnectionOpts) => Promise; delete: (nameOrId: string, opts?: ConnectionOpts) => Promise; }; /** * The account this credential belongs to — the SDK's `lizard whoami`. * * A **scoped** key gets identity only: `id`, `username`, `avatarUrl`, `scoped: true` * and the key's own `scopes`. The account's email, balance, plan and billing status * are withheld — they belong to the account, not to the key holder, and a scoped key * is meant to be handed to an end user or injected into a sandbox. An unscoped key * or a session sees the full account. * * Reading back `scopes` is the cheapest way to answer "what can this key reach". */ whoami(): Promise; /** The underlying HTTP client, for endpoints this SDK does not wrap yet. */ get platform(): PlatformClient; } /** * The account behind a credential — see {@link Lizard.whoami}. * * Everything past `avatarUrl` is present only for an unscoped key or a session; a * scoped key gets `scoped: true` and `scopes` in their place. */ interface Account { id: string; username: string; avatarUrl?: string | null; /** True when the calling key is scoped, meaning the account fields below are absent. */ scoped?: boolean; /** What the calling key may reach. Present when `scoped` is true. */ scopes?: Array<{ type: 'workspace' | 'project'; id: string; }>; email?: string | null; plan?: string; billingStatus?: string; balanceCents?: number; } /** * Resolve a project reference — its ID, slug, or name — to the stable project * ID the API bills against. Exact-ID refs skip the network entirely. * * @throws {LizardError} when the reference matches no project the key can see. */ declare function resolveProjectId(ref: string, config: ConnectionConfig): Promise; declare class LizardError extends Error { constructor(message: string); } declare class AuthenticationError extends LizardError { constructor(message?: string); } declare class NotFoundError extends LizardError { constructor(message?: string); } /** * The resource already exists — most often a volume whose name is already taken in * the project. Volume names are the key inside a project, so `Volume.create` refuses * to make a second one; catch this, or call `Volume.getOrCreate` instead. */ declare class ConflictError extends LizardError { constructor(message?: string); } declare class TimeoutError extends LizardError { constructor(message?: string); } /** A single output item produced during code execution. */ type OutputItem = { type: 'stdout'; data: string; ts: number; } | { type: 'stderr'; data: string; ts: number; } | { type: 'result'; mime: string; data: string; } | { type: 'error'; name: string; message: string; traceback: string; }; /** Error thrown when executed code raises an exception. */ declare class ExecutionError extends Error { readonly name: string; readonly traceback: string; constructor(name: string, message: string, traceback: string); } /** Full result of a runCode() call. */ declare class Execution { /** All stdout text, concatenated. */ stdout: string; /** All stderr text, concatenated. */ stderr: string; /** Rich output items (images, JSON, HTML, plain values). */ results: OutputItem[]; /** Execution error if the code threw an exception. */ error?: ExecutionError; /** Monotonically increasing counter for this context. */ executionCount: number; get success(): boolean; get text(): string; } /** An isolated stateful execution context. */ type CodeContext = { id: string; language: string; cwd: string; }; type RunCodeLanguage = 'python' | 'javascript' | 'bash' | (string & Record); interface RunCodeOpts { /** Language to run in. Defaults to python. */ language?: RunCodeLanguage; /** Use a specific context instead of the per-language default. */ context?: CodeContext; /** Extra environment variables available to the code. */ envs?: Record; /** Max time to wait for the code to finish, in ms. Default: 60_000. */ timeoutMs?: number; onStdout?: (data: string) => void; onStderr?: (data: string) => void; onResult?: (item: OutputItem) => void; onError?: (err: ExecutionError) => void; } interface CreateContextOpts { language?: RunCodeLanguage; cwd?: string; } /** * A Lizard sandbox with built-in stateful code execution. * * Extends the base Sandbox with `runCode()` — executes code in a persistent * kernel so variables and imports survive between calls. * * Supports Python, JavaScript (Node.js), and Bash out of the box. * * @example * ```ts * import { CodeSandbox } from 'lizard/code-interpreter' * * const sandbox = await CodeSandbox.create({ project: 'my-project' }) * * await sandbox.runCode('x = 42') * const result = await sandbox.runCode('print(x * 2)') * console.log(result.stdout) // "84\n" * * await sandbox.kill() * ``` * * @example Run JavaScript: * ```ts * const result = await sandbox.runCode('1 + 1', { language: 'javascript' }) * console.log(result.results[0].data) // "2" * ``` */ declare class CodeSandbox extends Sandbox { protected static readonly defaultTemplate = "code-interpreter-v1"; private get serverUrl(); /** * Execute code in a persistent kernel. * * Variables, imports, and function definitions from previous calls are * available in subsequent calls within the same context. * * @param code Source code to run. * @param opts Language, context, env vars, timeout, and streaming callbacks. * * @returns Execution result with stdout, stderr, results, and any error. * * @throws {ExecutionError} if you pass neither language nor context and no * default context exists — which cannot happen in normal usage. * * @example * ```ts * const result = await sandbox.runCode(` * import math * print(math.sqrt(144)) * `) * console.log(result.stdout) // "12.0\n" * ``` */ runCode(code: string, opts?: RunCodeOpts): Promise; /** * Create a new isolated execution context. * * Each context maintains its own variable namespace and process state. * Useful for running multiple independent sessions in the same sandbox. * * @example * ```ts * const ctx = await sandbox.createContext({ language: 'python' }) * await sandbox.runCode('x = 10', { context: ctx }) * await sandbox.runCode('print(x)', { context: ctx }) // prints 10 * ``` */ createContext(opts?: CreateContextOpts): Promise; /** * List all active execution contexts in this sandbox. */ listContexts(): Promise; /** * Delete an execution context and free its resources. */ deleteContext(context: CodeContext | string): Promise; /** * Restart a context, clearing all variables and state. */ restartContext(context: CodeContext | string): Promise; static create(opts?: SandboxOpts): Promise; static create(template: string, opts?: SandboxOpts): Promise; static connect(sandboxId: string, opts?: ConnectionOpts): Promise; } export { type Account, type Addon, type AddonType, AddonsAPI, type ApiKey, type ApiKeyScope, ApiKeysAPI, AuthenticationError, type Balance, BillingAPI, type CodeContext, CodeSandbox, ConflictError, type ConnectionOpts, type CostMetrics, type CreateAddonOpts, type CreateApiKeyOpts, type CreateContextOpts, type CreateProjectOpts, type CreateServiceOpts, type CreateVolumeOpts, type CreateWorkspaceOpts, type CreatedApiKey, type DeployEvent, DeployHandle, type DomainInfo, DomainsAPI, Execution, ExecutionError, type FileInfo, type FsOpts, type ListTransactionsOpts, Lizard, LizardError, type LizardOpts, type LogLine, type MetricPoint, type MetricRange, MetricsAPI, NotFoundError, PlatformClient, type ProcessOpts, type ProcessResult, type Project, ProjectsAPI, type Region, RegionsAPI, type RunCodeLanguage, type RunCodeOpts, Sandbox, type SandboxInfo, type SandboxOpts, type ScaleOpts, type Secret, SecretsAPI, type Service, type ServiceMetrics, ServicesAPI, type SetSecretOpts, TimeoutError, type Transaction, type TransactionPage, Volume, type VolumeInfo, type Workspace, WorkspacesAPI, resolveProjectId };