@lizard-build/sdk 0.1.39 → 0.1.41

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,289 +1,93 @@
1
- # Lizard SDK
1
+ # Lizard TypeScript SDK
2
2
 
3
- Firecracker microVM sandboxes for AI agents — boot a full Linux environment in milliseconds, run code, write files, and expose ports, all from your agent or CI pipeline.
4
-
5
- Each sandbox is an isolated microVM with its own filesystem, network, and process namespace. Sandboxes can be paused — vCPUs frozen, memory and running processes kept — and resumed instantly, so a long-running agent session picks up without re-running setup.
3
+ Run commands, work with files and execute code in Linux sandboxes on Kubernetes. The SDK also manages Lizard apps, databases, storage and projects.
6
4
 
7
5
  ## Install
8
6
 
9
- ```bash
10
- # JavaScript / TypeScript
11
- npm install @lizard-build/sdk
7
+ Requires Node.js 18+.
12
8
 
13
- # Python
14
- pip install lizard-sdk
9
+ ```sh
10
+ npm install @lizard-build/sdk
15
11
  ```
16
12
 
17
- ## Quickstart
18
-
19
- ### JavaScript / TypeScript
20
-
21
- ```ts
22
- import { Lizard } from '@lizard-build/sdk'
23
-
24
- // A client is pinned to one project — sandboxes are billed per project, so a
25
- // project is required. It can be the project's ID, slug, or name.
26
- // apiKey defaults to the LIZARD_API_KEY env var.
27
- const lizard = new Lizard({ project: 'my-project' })
28
-
29
- // Boot a microVM from the 'base' template (Debian + Node.js 26)
30
- const sandbox = await lizard.create('base')
31
-
32
- // Write a file directly into the microVM filesystem
33
- await sandbox.fs.write('/app/server.js', `
34
- const http = require('http')
35
- http.createServer((_, res) => res.end('hello from Lizard')).listen(3000)
36
- `)
37
-
38
- // Execute a process inside the microVM
39
- await sandbox.process.exec('node /app/server.js &')
13
+ Create an API key and project at [lizard.build](https://lizard.build), then set:
40
14
 
41
- // Get a public HTTPS URL for port 3000 inside the sandbox
42
- const url = sandbox.getHost(3000)
43
- console.log(`Live at https://${url}`)
44
-
45
- // Tear down the microVM when done
46
- await sandbox.kill()
15
+ ```sh
16
+ export LIZARD_API_KEY="your-api-key"
47
17
  ```
48
18
 
49
- ### Python
50
-
51
- ```python
52
- from lizard import Lizard
53
-
54
- # A client is pinned to one project — sandboxes are billed per project, so a
55
- # project is required (its ID, slug, or name). api_key defaults to LIZARD_API_KEY.
56
- lizard = Lizard(project="my-project")
57
-
58
- # Boot a Python microVM from the 'code-interpreter-v1' template
59
- sandbox = lizard.create("code-interpreter-v1")
60
-
61
- # Write a script into the microVM filesystem
62
- sandbox.fs.write("/app/main.py", """
63
- import http.server, socketserver
64
-
65
- class Handler(http.server.SimpleHTTPRequestHandler):
66
- def do_GET(self):
67
- self.send_response(200)
68
- self.end_headers()
69
- self.wfile.write(b"hello from Lizard")
70
-
71
- with socketserver.TCPServer(("", 3000), Handler) as httpd:
72
- httpd.serve_forever()
73
- """)
74
-
75
- # Execute a process inside the microVM
76
- sandbox.process.exec_("python /app/main.py &")
77
-
78
- print(f"Live at https://{sandbox.get_host(3000)}")
79
-
80
- sandbox.kill()
81
- ```
19
+ ## Create a sandbox
82
20
 
83
- ## Persisting Work Across Sandboxes
21
+ Save this as `quickstart.mjs`. Replace `my-project` with your project's ID, slug or unique name, then run `node quickstart.mjs`.
84
22
 
85
- Sandboxes are **ephemeral**: killing one, or letting it hit its timeout, discards everything written inside it. State that has to outlive a sandbox goes on a **volume** — a separate disk mounted at `/workspace` that a later sandbox re-attaches.
86
-
87
- A volume is node-local, so it fixes the region too. You don't thread a region through both calls: the sandbox is placed wherever its volume already lives.
88
-
89
- ```ts
90
- const vol = await lizard.volumes.getOrCreate('agent-scratch', { sizeGb: 10 })
91
-
92
- const first = await lizard.create('codex', { volumeName: 'agent-scratch' })
93
- await first.process.exec('pip install numpy pandas && echo "notes" > /workspace/notes.txt')
94
- await first.kill() // sandbox gone, /workspace survives
95
-
96
- const second = await lizard.create('codex', { volumeName: 'agent-scratch' })
97
- console.log(await second.fs.read('/workspace/notes.txt')) // "notes"
98
- ```
99
-
100
- ```python
101
- vol = lizard.volumes.get_or_create("agent-scratch", size_gb=10)
102
-
103
- first = lizard.create("codex", volume_name="agent-scratch")
104
- first.process.exec_('echo "notes" > /workspace/notes.txt')
105
- first.kill() # sandbox gone, /workspace survives
23
+ ```js
24
+ import { Lizard } from '@lizard-build/sdk'
106
25
 
107
- second = lizard.create("codex", volume_name="agent-scratch")
108
- print(second.fs.read("/workspace/notes.txt")) # "notes"
26
+ const lizard = new Lizard({ project: 'my-project' })
27
+ const sandbox = await lizard.create('base', { timeoutMs: 300_000 })
28
+
29
+ try {
30
+ await sandbox.fs.write('/tmp/hello.txt', 'Hello from Lizard!')
31
+ const result = await sandbox.process.exec('cat /tmp/hello.txt')
32
+ if (result.exitCode !== 0) throw new Error(result.stderr)
33
+ console.log(result.stdout)
34
+ } finally {
35
+ await sandbox.kill()
36
+ }
109
37
  ```
110
38
 
111
- Note that only `/workspace` survives — installed packages and in-memory state do not. Bake tooling into a template instead of reinstalling it per sandbox.
112
-
113
- > `pause()` / `resume()` exist on the client but are **not implemented** for the current runtime and always fail with HTTP 501. Use a volume.
39
+ The package includes TypeScript declarations and ESM and CommonJS builds. For CommonJS, use `const { Lizard } = require('@lizard-build/sdk')` inside a program with an async entry point.
114
40
 
115
- ## Giving Each of Your Users Their Own Workspace
41
+ ## Stream command output
116
42
 
117
- If you are building on top of Lizard and your users each need isolated resources, give each one a workspace and an API key scoped to it. They get isolation from each other; you keep one bill.
43
+ Inside the `try` block above:
118
44
 
119
45
  ```ts
120
- const lizard = new Lizard({ apiKey: process.env.LIZARD_API_KEY })
121
-
122
- const ws = await lizard.workspaces.create({ name: `user-${userId}` })
123
- const prj = await lizard.projects.create({ workspaceId: ws.id, name: 'default' })
124
- const key = await lizard.apiKeys.create({ name: `user-${userId}`, workspaces: [ws.id] })
125
-
126
- // key.key is returned exactly once. Store it now.
127
- await db.users.update(userId, { lizardKey: key.key })
128
- ```
129
-
130
- That key reaches nothing outside `ws`, and it cannot mint a broader one — the server rejects that with an exact subset check. So it is safe to hand to the user, and safe to put **inside a sandbox** so agent code can use the CLI as that user:
131
-
132
- ```ts
133
- const sandbox = await Sandbox.create('codex', {
134
- projectId: prj.id,
135
- lizardToken: key.key, // scoped — bounded if it leaks
46
+ const result = await sandbox.process.exec('printf "hello\\n"', {
47
+ timeoutMs: 30_000,
48
+ onStdout: chunk => process.stdout.write(chunk),
49
+ onStderr: chunk => process.stderr.write(chunk),
136
50
  })
137
- await sandbox.process.exec('lizard volume list') // sees only this workspace
51
+ if (result.exitCode !== 0) throw new Error(result.stderr)
138
52
  ```
139
53
 
140
- ```python
141
- ws = lizard.workspaces.create(name=f"user-{user_id}")
142
- prj = lizard.projects.create(workspace_id=ws.id, name="default")
143
- key = lizard.api_keys.create(name=f"user-{user_id}", workspaces=[ws.id])
144
-
145
- sandbox = Sandbox.create("codex", project_id=prj.id, lizard_token=key.key)
146
- sandbox.process.exec_("lizard volume list")
147
- ```
148
-
149
- A key with **no** scope has full access to everything the creating account can reach — pass `workspaces` or `projects` unless you mean that.
150
-
151
- ### What a scoped key deliberately cannot see
152
-
153
- Billing is account-level: one balance, one ledger, one set of saved cards, shared by every workspace. There is no per-workspace view of it, so a scoped key is refused outright (403 `ACCOUNT_SCOPE_REQUIRED`) on `lizard.billing.*` and on account-wide usage, and `whoami()` returns only identity plus the key's own scopes — not the account's email, balance or plan.
54
+ ## Run code
154
55
 
155
- That matters because of where these keys end up. A key you hand to a user, or inject into a sandbox with `lizardToken`, is readable by anything running there. It should not be a way to read your card details or spend against them.
156
-
157
- For per-workspace spend, use `lizard.metrics` — that is scoped, and is what you would bill a user from.
158
-
159
- ## API
160
-
161
- ### `new Lizard({ project, apiKey?, apiUrl?, timeoutMs? })`
162
-
163
- Create a client pinned to a project. Every sandbox is billed per project, so `project` is required — pass its ID, slug, or name (resolved to an ID on first use and cached). `apiKey` defaults to the `LIZARD_API_KEY` env var.
164
-
165
- ```ts
166
- const lizard = new Lizard({ project: 'my-project' })
167
- const sandbox = await lizard.create('base')
168
- const sandbox = await lizard.create('code-interpreter-v1', { timeoutMs: 10 * 60 * 1000 })
169
- ```
170
-
171
- ### `Sandbox.create(template?, opts?)`
172
-
173
- Boot a new Lizard microVM. Built-in templates: `base` (Debian + Node.js 26) and `code-interpreter-v1` (Python 3.14 + Node.js 26). Custom templates can be pushed via `lizard push`. A project is required — pass `project` (ID, slug, or name) or an exact `projectId` in `opts`, or use a `Lizard` client, which pins one for you.
174
-
175
- ```ts
176
- const sandbox = await Sandbox.create('base', { project: 'my-project' })
177
- const sandbox = await Sandbox.create('code-interpreter-v1', { project: 'my-project', timeoutMs: 10 * 60 * 1000 })
178
- ```
179
-
180
- ### `Sandbox.connect(sandboxId, opts?)`
181
-
182
- Connect to an existing sandbox by ID. Throws `NotFoundError` if it has been killed or has expired.
183
-
184
- ### `Sandbox.list(opts?)`
185
-
186
- List all running sandboxes for the authenticated account.
187
-
188
- ---
189
-
190
- ### Account and provisioning
191
-
192
- | Namespace | Methods |
193
- |---|---|
194
- | `lizard.workspaces` | `list()`, `create({ name })`, `delete(id, { force? })`, `find(nameOrSlugOrId)` |
195
- | `lizard.apiKeys` | `list()`, `create({ name, workspaces?, projects? })`, `delete(id)` |
196
- | `lizard.projects` | `list({ workspaceId? })`, `get(id)`, `create({ workspaceId, name })`, `update(id, { name })`, `delete(id)` |
197
- | `lizard.regions` | `list()` |
198
- | `lizard.billing` | `balance()`, `transactions({ limit?, cursor?, includeUsage? })`, `summary()`, `live()` — **unscoped keys only** |
199
- | `lizard.whoami()` | The account behind the credential; a scoped key gets identity and its own scopes, not the account's email or balance |
200
- | `lizard.platform` | The raw HTTP client, for endpoints not wrapped yet |
201
-
202
- `workspaces.delete()` is empty-only by default; the server refuses while any project, sandbox or volume remains. `{ force: true }` deletes the workspace and everything in it, irreversibly.
203
-
204
- In Python the namespaces are `lizard.workspaces`, `lizard.api_keys`, `lizard.regions`, `lizard.billing`, and arguments are snake_case (`workspace_id`, `include_usage`).
205
-
206
- ### Volumes
207
-
208
- | Method | Description |
209
- |---|---|
210
- | `lizard.volumes.getOrCreate(name, { sizeGb?, region? })` | The volume with this name, created if absent |
211
- | `lizard.volumes.create(name, { sizeGb?, region? })` | Create; throws `ConflictError` if the name is taken |
212
- | `lizard.volumes.get(nameOrId)` | Look one up |
213
- | `lizard.volumes.list()` | Every volume in the client's project |
214
- | `lizard.volumes.delete(nameOrId)` | Delete |
215
-
216
- A volume's **name** is its key inside a project, so an agent can reconstruct it between runs without storing an id. The static `Volume.*` forms take an explicit `projectId` as their first argument.
217
-
218
- `region` places the volume (see `lizard.regions.list()` for valid ids) and, because a volume is node-local, also fixes the region of any sandbox that mounts it. You normally set it here or nowhere.
219
-
220
- ---
221
-
222
- ### `sandbox.fs`
223
-
224
- Read and write files inside the microVM filesystem.
225
-
226
- | Method | Description |
227
- |---|---|
228
- | `fs.write(path, data)` | Write a file (string or bytes) |
229
- | `fs.read(path)` | Read a file as a string |
230
- | `fs.list(path)` | List directory contents |
231
- | `fs.remove(path)` | Delete a file or directory |
232
- | `fs.makeDir(path)` | Create a directory and parents |
233
-
234
- ### `sandbox.process`
235
-
236
- Execute commands inside the microVM.
237
-
238
- | Method | Description |
239
- |---|---|
240
- | `process.exec(cmd, opts?)` | Run a command and wait for it to finish |
241
-
242
- `exec` returns `{ stdout, stderr, exitCode }` (JS) or `ProcessResult` (Python). In Python the method is named `exec_` because `exec` is a reserved keyword.
243
-
244
- ### `sandbox.getHost(port)`
245
-
246
- Returns a public HTTPS URL for a port listening inside the microVM — no tunneling required.
56
+ `CodeSandbox` uses the `code-interpreter-v1` template by default. Code errors appear in `result.error`; request failures throw.
247
57
 
248
58
  ```ts
249
- await sandbox.process.exec('npx -y serve -p 3000 &')
250
- const url = sandbox.getHost(3000)
251
- // https://{sandboxId}-3000.sandbox.{region}.onlizard.com
59
+ import { CodeSandbox } from '@lizard-build/sdk'
60
+
61
+ const sandbox = await CodeSandbox.create({ project: 'my-project' })
62
+ try {
63
+ const result = await sandbox.runCode('print(6 * 7)', {
64
+ language: 'python',
65
+ timeoutMs: 60_000,
66
+ onStdout: chunk => process.stdout.write(chunk),
67
+ })
68
+ if (result.error) throw result.error
69
+ } finally {
70
+ await sandbox.kill()
71
+ }
252
72
  ```
253
73
 
254
- ### `sandbox.pause()` / `sandbox.resume()`
255
-
256
- **Not implemented** for the current runtime — both always fail with HTTP 501. Use a [volume](#persisting-work-across-sandboxes) to carry work across sandboxes.
257
-
258
- ### `sandbox.kill()`
259
-
260
- Terminate the sandbox and release all resources.
74
+ ## Configuration
261
75
 
262
- ### `sandbox.setTimeout(ms)`
76
+ The SDK reads `LIZARD_API_KEY` and, optionally, `LIZARD_API_URL` (default: `https://lizard.build`). You can also pass `apiKey` and `apiUrl` to the constructor or static sandbox methods.
263
77
 
264
- Extend or reduce the sandbox timeout.
78
+ Pass `timeoutMs` to `create()` to set the sandbox lifetime. The default is five minutes. `timeoutMs` on `process.exec()` or `runCode()` controls that call, not the sandbox lifetime.
265
79
 
266
- ---
80
+ ## Runtime limits
267
81
 
268
- ## Environment Variables
82
+ Sandboxes run on Kubernetes. Use volumes mounted at `/workspace` to keep files after a sandbox ends. Pause, resume, fork, snapshot creation and snapshot restore return HTTP 501 on the current backend.
269
83
 
270
- | Variable | Description |
271
- |---|---|
272
- | `LIZARD_API_KEY` | API key (required — get one at [lizard.build](https://lizard.build)) |
273
- | `LIZARD_API_URL` | Override the API base URL (default: `https://lizard.build`) |
274
-
275
- The `X-API-Key` header is used for all authenticated requests.
276
-
277
- ## Deploy What You Build
278
-
279
- Once your agent has produced a working app inside a sandbox, deploy it as a persistent Lizard service — no Dockerfile needed:
280
-
281
- ```bash
282
- lizard up
283
- ```
84
+ `getHost()` returns a hostname without `https://`. File writes accept UTF-8 text or valid UTF-8 bytes; `readBytes()` supports binary downloads.
284
85
 
285
- Your sandbox template becomes the base, your code ships as a layer on top, and Lizard manages the Firecracker microVM fleet from there.
86
+ ## Learn more
286
87
 
287
- ## License
88
+ - [Sandbox reference](https://github.com/lizard-build/lizard-sdk/blob/main/docs/sandboxes.md)
89
+ - [Platform guide](https://github.com/lizard-build/lizard-sdk/blob/main/docs/platform.md)
90
+ - [CLI coverage and migration](https://github.com/lizard-build/lizard-sdk/blob/main/docs/cli-parity.md)
91
+ - [Source and issues](https://github.com/lizard-build/lizard-sdk)
288
92
 
289
- Apache-2.0
93
+ [Apache-2.0](https://github.com/lizard-build/lizard-sdk/blob/main/LICENSE)