@lizard-build/sdk 0.1.40 → 0.1.42
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 +58 -258
- package/dist/index.d.mts +87 -56
- package/dist/index.d.ts +87 -56
- package/dist/index.js +83 -47
- package/dist/index.mjs +83 -47
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -1,293 +1,93 @@
|
|
|
1
|
-
# Lizard SDK
|
|
1
|
+
# Lizard TypeScript SDK
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
The current platform runs sandboxes on Kubernetes. Persistent volumes keep files across sandbox lifetimes. Pause, resume, fork and snapshot restore currently return HTTP 501; the SDK exposes those endpoints but does not promise state preservation.
|
|
6
|
-
|
|
7
|
-
Requires Node.js 18+ or Python 3.10+. The optional `LizardCLI` adapter and `billing.payX402()` / `billing.pay_x402()` require Lizard CLI 4.0.8+ on PATH.
|
|
8
|
-
|
|
9
|
-
See [CLI coverage and migration notes](https://github.com/lizard-build/lizard-sdk/blob/main/docs/cli-parity.md) for native methods, CLI-backed operations and test scope.
|
|
3
|
+
Run commands, work with files and execute code in Linux sandboxes on Kubernetes. The SDK also manages Lizard apps, databases, storage and projects.
|
|
10
4
|
|
|
11
5
|
## Install
|
|
12
6
|
|
|
13
|
-
|
|
14
|
-
# JavaScript / TypeScript
|
|
15
|
-
npm install @lizard-build/sdk
|
|
16
|
-
|
|
17
|
-
# Python
|
|
18
|
-
pip install lizard-sdk
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
## Quickstart
|
|
22
|
-
|
|
23
|
-
### JavaScript / TypeScript
|
|
24
|
-
|
|
25
|
-
```ts
|
|
26
|
-
import { Lizard } from '@lizard-build/sdk'
|
|
7
|
+
Requires Node.js 18+.
|
|
27
8
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
// apiKey defaults to the LIZARD_API_KEY env var.
|
|
31
|
-
const lizard = new Lizard({ project: 'my-project' })
|
|
32
|
-
|
|
33
|
-
// Boot a sandbox from the 'base' template (Debian + Node.js 26)
|
|
34
|
-
const sandbox = await lizard.create('base')
|
|
35
|
-
|
|
36
|
-
// Write a file directly into the sandbox filesystem
|
|
37
|
-
await sandbox.fs.write('/app/server.js', `
|
|
38
|
-
const http = require('http')
|
|
39
|
-
http.createServer((_, res) => res.end('hello from Lizard')).listen(3000)
|
|
40
|
-
`)
|
|
41
|
-
|
|
42
|
-
// Execute a process inside the sandbox
|
|
43
|
-
await sandbox.process.exec('node /app/server.js &')
|
|
44
|
-
|
|
45
|
-
// Get a public HTTPS URL for port 3000 inside the sandbox
|
|
46
|
-
const url = await sandbox.getHost(3000)
|
|
47
|
-
console.log(`Live at https://${url}`)
|
|
48
|
-
|
|
49
|
-
// Tear down the sandbox when done
|
|
50
|
-
await sandbox.kill()
|
|
9
|
+
```sh
|
|
10
|
+
npm install @lizard-build/sdk
|
|
51
11
|
```
|
|
52
12
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
```python
|
|
56
|
-
from lizard import Lizard
|
|
57
|
-
|
|
58
|
-
# A client is pinned to one project — sandboxes are billed per project, so a
|
|
59
|
-
# project is required (its ID, slug, or name). api_key defaults to LIZARD_API_KEY.
|
|
60
|
-
lizard = Lizard(project="my-project")
|
|
61
|
-
|
|
62
|
-
# Boot a Python sandbox from the 'code-interpreter-v1' template
|
|
63
|
-
sandbox = lizard.create("code-interpreter-v1")
|
|
64
|
-
|
|
65
|
-
# Write a script into the sandbox filesystem
|
|
66
|
-
sandbox.fs.write("/app/main.py", """
|
|
67
|
-
import http.server, socketserver
|
|
68
|
-
|
|
69
|
-
class Handler(http.server.SimpleHTTPRequestHandler):
|
|
70
|
-
def do_GET(self):
|
|
71
|
-
self.send_response(200)
|
|
72
|
-
self.end_headers()
|
|
73
|
-
self.wfile.write(b"hello from Lizard")
|
|
13
|
+
Create an API key and project at [lizard.build](https://lizard.build), then set:
|
|
74
14
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
""")
|
|
78
|
-
|
|
79
|
-
# Execute a process inside the sandbox
|
|
80
|
-
sandbox.process.exec_("python /app/main.py &")
|
|
81
|
-
|
|
82
|
-
print(f"Live at https://{sandbox.get_host(3000)}")
|
|
83
|
-
|
|
84
|
-
sandbox.kill()
|
|
15
|
+
```sh
|
|
16
|
+
export LIZARD_API_KEY="your-api-key"
|
|
85
17
|
```
|
|
86
18
|
|
|
87
|
-
##
|
|
88
|
-
|
|
89
|
-
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.
|
|
90
|
-
|
|
91
|
-
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.
|
|
92
|
-
|
|
93
|
-
```ts
|
|
94
|
-
const vol = await lizard.volumes.getOrCreate('agent-scratch', { sizeGb: 10 })
|
|
95
|
-
|
|
96
|
-
const first = await lizard.create('codex', { volumeName: 'agent-scratch' })
|
|
97
|
-
await first.process.exec('pip install numpy pandas && echo "notes" > /workspace/notes.txt')
|
|
98
|
-
await first.kill() // sandbox gone, /workspace survives
|
|
99
|
-
|
|
100
|
-
const second = await lizard.create('codex', { volumeName: 'agent-scratch' })
|
|
101
|
-
console.log(await second.fs.read('/workspace/notes.txt')) // "notes"
|
|
102
|
-
```
|
|
19
|
+
## Create a sandbox
|
|
103
20
|
|
|
104
|
-
|
|
105
|
-
vol = lizard.volumes.get_or_create("agent-scratch", size_gb=10)
|
|
21
|
+
Save this as `quickstart.mjs`. Replace `my-project` with your project's ID, slug or unique name, then run `node quickstart.mjs`.
|
|
106
22
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
first.kill() # sandbox gone, /workspace survives
|
|
23
|
+
```js
|
|
24
|
+
import { Lizard } from '@lizard-build/sdk'
|
|
110
25
|
|
|
111
|
-
|
|
112
|
-
|
|
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
|
+
}
|
|
113
37
|
```
|
|
114
38
|
|
|
115
|
-
|
|
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.
|
|
116
40
|
|
|
117
|
-
|
|
41
|
+
## Stream command output
|
|
118
42
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
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:
|
|
122
44
|
|
|
123
45
|
```ts
|
|
124
|
-
const
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
const key = await lizard.apiKeys.create({ name: `user-${userId}`, workspaces: [ws.id] })
|
|
129
|
-
|
|
130
|
-
// key.key is returned exactly once. Store it now.
|
|
131
|
-
await db.users.update(userId, { lizardKey: key.key })
|
|
132
|
-
```
|
|
133
|
-
|
|
134
|
-
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:
|
|
135
|
-
|
|
136
|
-
```ts
|
|
137
|
-
const sandbox = await Sandbox.create('codex', {
|
|
138
|
-
projectId: prj.id,
|
|
139
|
-
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),
|
|
140
50
|
})
|
|
141
|
-
|
|
51
|
+
if (result.exitCode !== 0) throw new Error(result.stderr)
|
|
142
52
|
```
|
|
143
53
|
|
|
144
|
-
|
|
145
|
-
ws = lizard.workspaces.create(name=f"user-{user_id}")
|
|
146
|
-
prj = lizard.projects.create(workspace_id=ws.id, name="default")
|
|
147
|
-
key = lizard.api_keys.create(name=f"user-{user_id}", workspaces=[ws.id])
|
|
148
|
-
|
|
149
|
-
sandbox = Sandbox.create("codex", project_id=prj.id, lizard_token=key.key)
|
|
150
|
-
sandbox.process.exec_("lizard volume list")
|
|
151
|
-
```
|
|
152
|
-
|
|
153
|
-
A key with **no** scope has full access to everything the creating account can reach — pass `workspaces` or `projects` unless you mean that.
|
|
154
|
-
|
|
155
|
-
### What a scoped key deliberately cannot see
|
|
54
|
+
## Run code
|
|
156
55
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
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.
|
|
160
|
-
|
|
161
|
-
For per-workspace spend, use `lizard.metrics` — that is scoped, and is what you would bill a user from.
|
|
162
|
-
|
|
163
|
-
## API
|
|
164
|
-
|
|
165
|
-
### `new Lizard({ project, apiKey?, apiUrl?, timeoutMs? })`
|
|
166
|
-
|
|
167
|
-
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.
|
|
168
|
-
|
|
169
|
-
```ts
|
|
170
|
-
const lizard = new Lizard({ project: 'my-project' })
|
|
171
|
-
const sandbox = await lizard.create('base')
|
|
172
|
-
const sandbox = await lizard.create('code-interpreter-v1', { timeoutMs: 10 * 60 * 1000 })
|
|
173
|
-
```
|
|
174
|
-
|
|
175
|
-
### `Sandbox.create(template?, opts?)`
|
|
176
|
-
|
|
177
|
-
Create a Lizard sandbox using a template such as `base` or `code-interpreter-v1`. Template availability depends on the platform and region. 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.
|
|
56
|
+
`CodeSandbox` uses the `code-interpreter-v1` template by default. Code errors appear in `result.error`; request failures throw.
|
|
178
57
|
|
|
179
58
|
```ts
|
|
180
|
-
|
|
181
|
-
|
|
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
|
+
}
|
|
182
72
|
```
|
|
183
73
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
Connect to an existing sandbox by ID. Throws `NotFoundError` if it has been killed or has expired.
|
|
187
|
-
|
|
188
|
-
### `Sandbox.list(opts?)`
|
|
189
|
-
|
|
190
|
-
List all running sandboxes for the authenticated account.
|
|
191
|
-
|
|
192
|
-
---
|
|
193
|
-
|
|
194
|
-
### Account and provisioning
|
|
195
|
-
|
|
196
|
-
| Namespace | Methods |
|
|
197
|
-
|---|---|
|
|
198
|
-
| `lizard.workspaces` | `list()`, `create({ name })`, `delete(id, { force? })`, `find(nameOrSlugOrId)` |
|
|
199
|
-
| `lizard.apiKeys` | `list()`, `create({ name, workspaces?, projects? })`, `delete(id)` |
|
|
200
|
-
| `lizard.projects` | `list({ workspaceId? })`, `get(id)`, `create({ workspaceId, name })`, `update(id, { name })`, `delete(id)` |
|
|
201
|
-
| `lizard.regions` | `list()` |
|
|
202
|
-
| `lizard.billing` | `balance()`, `transactions({ limit?, cursor?, includeUsage? })`, `summary()`, `live()` — **unscoped keys only** |
|
|
203
|
-
| `lizard.whoami()` | The account behind the credential; a scoped key gets identity and its own scopes, not the account's email or balance |
|
|
204
|
-
| `lizard.platform` | The raw HTTP client, for endpoints not wrapped yet |
|
|
205
|
-
|
|
206
|
-
`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.
|
|
207
|
-
|
|
208
|
-
In Python the namespaces are `lizard.workspaces`, `lizard.api_keys`, `lizard.regions`, `lizard.billing`, and arguments are snake_case (`workspace_id`, `include_usage`).
|
|
209
|
-
|
|
210
|
-
### Volumes
|
|
211
|
-
|
|
212
|
-
| Method | Description |
|
|
213
|
-
|---|---|
|
|
214
|
-
| `lizard.volumes.getOrCreate(name, { sizeGb?, region? })` | The volume with this name, created if absent |
|
|
215
|
-
| `lizard.volumes.create(name, { sizeGb?, region? })` | Create; throws `ConflictError` if the name is taken |
|
|
216
|
-
| `lizard.volumes.get(nameOrId)` | Look one up |
|
|
217
|
-
| `lizard.volumes.list()` | Every volume in the client's project |
|
|
218
|
-
| `lizard.volumes.delete(nameOrId)` | Delete |
|
|
219
|
-
|
|
220
|
-
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.
|
|
221
|
-
|
|
222
|
-
`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.
|
|
74
|
+
## Configuration
|
|
223
75
|
|
|
224
|
-
|
|
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.
|
|
225
77
|
|
|
226
|
-
|
|
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.
|
|
227
79
|
|
|
228
|
-
|
|
80
|
+
## Runtime limits
|
|
229
81
|
|
|
230
|
-
|
|
231
|
-
|---|---|
|
|
232
|
-
| `fs.write(path, data)` | Write a file (string or bytes) |
|
|
233
|
-
| `fs.read(path)` | Read a file as a string |
|
|
234
|
-
| `fs.list(path)` | List directory contents |
|
|
235
|
-
| `fs.remove(path)` | Delete a file or directory |
|
|
236
|
-
| `fs.makeDir(path)` | Create a directory and parents |
|
|
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.
|
|
237
83
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
Execute commands inside the sandbox.
|
|
241
|
-
|
|
242
|
-
| Method | Description |
|
|
243
|
-
|---|---|
|
|
244
|
-
| `process.exec(cmd, opts?)` | Run a command and wait for it to finish |
|
|
245
|
-
|
|
246
|
-
`exec` returns `{ stdout, stderr, exitCode }` (JS) or `ProcessResult` (Python). In Python the method is named `exec_` because `exec` is a reserved keyword.
|
|
247
|
-
|
|
248
|
-
### `sandbox.getHost(port)`
|
|
249
|
-
|
|
250
|
-
Returns a public HTTPS URL for a port listening inside the sandbox — no tunneling required.
|
|
251
|
-
|
|
252
|
-
```ts
|
|
253
|
-
await sandbox.process.exec('npx -y serve -p 3000 &')
|
|
254
|
-
const url = await sandbox.getHost(3000)
|
|
255
|
-
// https://{sandboxId}-3000.sandbox.{region}.onlizard.com
|
|
256
|
-
```
|
|
257
|
-
|
|
258
|
-
### `sandbox.pause()` / `sandbox.resume()`
|
|
259
|
-
|
|
260
|
-
**Not implemented** for the current runtime — both always fail with HTTP 501. Use a [volume](#persisting-work-across-sandboxes) to carry work across sandboxes.
|
|
261
|
-
|
|
262
|
-
### `sandbox.kill()`
|
|
263
|
-
|
|
264
|
-
Terminate the sandbox and release all resources.
|
|
265
|
-
|
|
266
|
-
### `sandbox.setTimeout(ms)`
|
|
267
|
-
|
|
268
|
-
Extend or reduce the sandbox timeout.
|
|
269
|
-
|
|
270
|
-
---
|
|
271
|
-
|
|
272
|
-
## Environment Variables
|
|
273
|
-
|
|
274
|
-
| Variable | Description |
|
|
275
|
-
|---|---|
|
|
276
|
-
| `LIZARD_API_KEY` | API key (required — get one at [lizard.build](https://lizard.build)) |
|
|
277
|
-
| `LIZARD_API_URL` | Override the API base URL (default: `https://lizard.build`) |
|
|
278
|
-
|
|
279
|
-
The `X-API-Key` header is used for all authenticated requests.
|
|
280
|
-
|
|
281
|
-
## Deploy What You Build
|
|
282
|
-
|
|
283
|
-
Once your agent has produced a working app inside a sandbox, deploy it as a persistent Lizard service — no Dockerfile needed:
|
|
284
|
-
|
|
285
|
-
```bash
|
|
286
|
-
lizard up
|
|
287
|
-
```
|
|
84
|
+
`getHost()` returns a hostname without `https://`. File writes accept UTF-8 text or valid UTF-8 bytes; `readBytes()` supports binary downloads.
|
|
288
85
|
|
|
289
|
-
|
|
86
|
+
## Learn more
|
|
290
87
|
|
|
291
|
-
|
|
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)
|
|
292
92
|
|
|
293
|
-
Apache-2.0
|
|
93
|
+
[Apache-2.0](https://github.com/lizard-build/lizard-sdk/blob/main/LICENSE)
|