@lizard-build/sdk 0.1.1 → 0.1.7
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 +195 -0
- package/dist/index.d.mts +5 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.js +18 -10
- package/dist/index.mjs +18 -10
- package/package.json +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
# Lizard SDK
|
|
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 snapshotted and resumed instantly, so long-running agent sessions survive restarts without re-running setup.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
# JavaScript / TypeScript
|
|
11
|
+
npm install @lizard-build/sdk
|
|
12
|
+
|
|
13
|
+
# Python
|
|
14
|
+
pip install lizard-sdk
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Quickstart
|
|
18
|
+
|
|
19
|
+
### JavaScript / TypeScript
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
import { Sandbox } from '@lizard-build/sdk'
|
|
23
|
+
|
|
24
|
+
// Boot a Node.js microVM from the 'node22' template
|
|
25
|
+
const sandbox = await Sandbox.create('node22')
|
|
26
|
+
|
|
27
|
+
// Write a file directly into the microVM filesystem
|
|
28
|
+
await sandbox.fs.write('/app/server.js', `
|
|
29
|
+
const http = require('http')
|
|
30
|
+
http.createServer((_, res) => res.end('hello from Lizard')).listen(3000)
|
|
31
|
+
`)
|
|
32
|
+
|
|
33
|
+
// Execute a process inside the microVM
|
|
34
|
+
await sandbox.process.exec('node /app/server.js &')
|
|
35
|
+
|
|
36
|
+
// Get a public HTTPS URL for port 3000 inside the sandbox
|
|
37
|
+
const url = sandbox.getHost(3000)
|
|
38
|
+
console.log(`Live at https://${url}`)
|
|
39
|
+
|
|
40
|
+
// Tear down the microVM when done
|
|
41
|
+
await sandbox.kill()
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Python
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
from lizard import Sandbox
|
|
48
|
+
|
|
49
|
+
# Boot a Python microVM from the 'python312' template
|
|
50
|
+
sandbox = Sandbox.create("python312")
|
|
51
|
+
|
|
52
|
+
# Write a script into the microVM filesystem
|
|
53
|
+
sandbox.fs.write("/app/main.py", """
|
|
54
|
+
import http.server, socketserver
|
|
55
|
+
|
|
56
|
+
class Handler(http.server.SimpleHTTPRequestHandler):
|
|
57
|
+
def do_GET(self):
|
|
58
|
+
self.send_response(200)
|
|
59
|
+
self.end_headers()
|
|
60
|
+
self.wfile.write(b"hello from Lizard")
|
|
61
|
+
|
|
62
|
+
with socketserver.TCPServer(("", 3000), Handler) as httpd:
|
|
63
|
+
httpd.serve_forever()
|
|
64
|
+
""")
|
|
65
|
+
|
|
66
|
+
# Execute a process inside the microVM
|
|
67
|
+
sandbox.process.exec_("python /app/main.py &")
|
|
68
|
+
|
|
69
|
+
print(f"Live at https://{sandbox.get_host(3000)}")
|
|
70
|
+
|
|
71
|
+
sandbox.kill()
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Pause and Resume
|
|
75
|
+
|
|
76
|
+
Sandboxes can be snapshotted mid-execution and resumed exactly where they left off — including installed packages, in-memory state, and running processes. This makes Lizard sandboxes well-suited for long-running AI agent workflows where you want to checkpoint and continue across separate invocations.
|
|
77
|
+
|
|
78
|
+
```ts
|
|
79
|
+
// Boot and set up the environment once
|
|
80
|
+
const sandbox = await Sandbox.create('python312')
|
|
81
|
+
await sandbox.process.exec('pip install numpy pandas scikit-learn')
|
|
82
|
+
const id = sandbox.sandboxId
|
|
83
|
+
await sandbox.pause()
|
|
84
|
+
|
|
85
|
+
// Later — resume instantly from the snapshot (no reinstall needed)
|
|
86
|
+
const resumed = await Sandbox.connect(id)
|
|
87
|
+
const result = await resumed.process.exec('python -c "import sklearn; print(sklearn.__version__)"')
|
|
88
|
+
console.log(result.stdout)
|
|
89
|
+
await resumed.kill()
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
from lizard import Sandbox
|
|
94
|
+
|
|
95
|
+
sandbox = Sandbox.create("python312")
|
|
96
|
+
sandbox.process.exec_("pip install numpy pandas scikit-learn")
|
|
97
|
+
sandbox_id = sandbox.sandbox_id
|
|
98
|
+
sandbox.pause()
|
|
99
|
+
|
|
100
|
+
# Resume later — environment is exactly as left
|
|
101
|
+
resumed = Sandbox.connect(sandbox_id)
|
|
102
|
+
result = resumed.process.exec_("python -c 'import sklearn; print(sklearn.__version__)'")
|
|
103
|
+
print(result.stdout)
|
|
104
|
+
resumed.kill()
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## API
|
|
108
|
+
|
|
109
|
+
### `Sandbox.create(template?, opts?)`
|
|
110
|
+
|
|
111
|
+
Boot a new Lizard microVM. Built-in templates: `base`, `node22`, `python312`. Custom templates can be pushed via `lizard push`.
|
|
112
|
+
|
|
113
|
+
```ts
|
|
114
|
+
const sandbox = await Sandbox.create('node22')
|
|
115
|
+
const sandbox = await Sandbox.create('python312', { timeoutMs: 10 * 60 * 1000 })
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### `Sandbox.connect(sandboxId, opts?)`
|
|
119
|
+
|
|
120
|
+
Connect to an existing sandbox by ID. If the sandbox is paused, it is automatically resumed from its last snapshot.
|
|
121
|
+
|
|
122
|
+
### `Sandbox.list(opts?)`
|
|
123
|
+
|
|
124
|
+
List all running sandboxes for the authenticated account.
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
### `sandbox.fs`
|
|
129
|
+
|
|
130
|
+
Read and write files inside the microVM filesystem.
|
|
131
|
+
|
|
132
|
+
| Method | Description |
|
|
133
|
+
|---|---|
|
|
134
|
+
| `fs.write(path, data)` | Write a file (string or bytes) |
|
|
135
|
+
| `fs.read(path)` | Read a file as a string |
|
|
136
|
+
| `fs.list(path)` | List directory contents |
|
|
137
|
+
| `fs.remove(path)` | Delete a file or directory |
|
|
138
|
+
| `fs.makeDir(path)` | Create a directory and parents |
|
|
139
|
+
|
|
140
|
+
### `sandbox.process`
|
|
141
|
+
|
|
142
|
+
Execute commands inside the microVM.
|
|
143
|
+
|
|
144
|
+
| Method | Description |
|
|
145
|
+
|---|---|
|
|
146
|
+
| `process.exec(cmd, opts?)` | Run a command and wait for it to finish |
|
|
147
|
+
|
|
148
|
+
`exec` returns `{ stdout, stderr, exitCode }` (JS) or `ProcessResult` (Python). In Python the method is named `exec_` because `exec` is a reserved keyword.
|
|
149
|
+
|
|
150
|
+
### `sandbox.getHost(port)`
|
|
151
|
+
|
|
152
|
+
Returns a public HTTPS URL for a port listening inside the microVM — no tunneling required.
|
|
153
|
+
|
|
154
|
+
```ts
|
|
155
|
+
await sandbox.process.exec('npx -y serve -p 3000 &')
|
|
156
|
+
const url = sandbox.getHost(3000)
|
|
157
|
+
// https://{sandboxId}-3000.sandbox.lizard.run
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### `sandbox.pause()` / `sandbox.resume()`
|
|
161
|
+
|
|
162
|
+
Snapshot and restore the microVM state. Useful for checkpointing long agent sessions.
|
|
163
|
+
|
|
164
|
+
### `sandbox.kill()`
|
|
165
|
+
|
|
166
|
+
Terminate the sandbox and release all resources.
|
|
167
|
+
|
|
168
|
+
### `sandbox.setTimeout(ms)`
|
|
169
|
+
|
|
170
|
+
Extend or reduce the sandbox timeout.
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
## Environment Variables
|
|
175
|
+
|
|
176
|
+
| Variable | Description |
|
|
177
|
+
|---|---|
|
|
178
|
+
| `LIZARD_API_KEY` | API key (required — get one at [lizard.run](https://lizard.run)) |
|
|
179
|
+
| `LIZARD_API_URL` | Override the API base URL (default: `https://api.lizard.run`) |
|
|
180
|
+
|
|
181
|
+
The `X-API-Key` header is used for all authenticated requests.
|
|
182
|
+
|
|
183
|
+
## Deploy What You Build
|
|
184
|
+
|
|
185
|
+
Once your agent has produced a working app inside a sandbox, deploy it as a persistent Lizard service — no Dockerfile needed:
|
|
186
|
+
|
|
187
|
+
```bash
|
|
188
|
+
lizard up
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
Your sandbox template becomes the base, your code ships as a layer on top, and Lizard manages the Firecracker microVM fleet from there.
|
|
192
|
+
|
|
193
|
+
## License
|
|
194
|
+
|
|
195
|
+
Apache-2.0
|
package/dist/index.d.mts
CHANGED
|
@@ -156,6 +156,10 @@ declare class SandboxClient {
|
|
|
156
156
|
protected static listSandboxes(opts?: ConnectionOpts): Promise<SandboxInfo[]>;
|
|
157
157
|
protected static getSandboxInfo(sandboxId: string, opts?: ConnectionOpts): Promise<SandboxInfo>;
|
|
158
158
|
protected static setTimeoutSandbox(sandboxId: string, timeoutMs: number, opts?: ConnectionOpts): Promise<void>;
|
|
159
|
+
protected static exposeSandboxPort(sandboxId: string, port: number, opts?: ConnectionOpts): Promise<{
|
|
160
|
+
hostname: string;
|
|
161
|
+
url: string;
|
|
162
|
+
}>;
|
|
159
163
|
}
|
|
160
164
|
|
|
161
165
|
/**
|
|
@@ -308,7 +312,7 @@ declare class Sandbox extends SandboxClient {
|
|
|
308
312
|
* // https://{sandboxId}-3000.sandbox.lizard.run
|
|
309
313
|
* ```
|
|
310
314
|
*/
|
|
311
|
-
getHost(port: number): string
|
|
315
|
+
getHost(port: number, opts?: ConnectionOpts): Promise<string>;
|
|
312
316
|
private resolveOpts;
|
|
313
317
|
}
|
|
314
318
|
|
package/dist/index.d.ts
CHANGED
|
@@ -156,6 +156,10 @@ declare class SandboxClient {
|
|
|
156
156
|
protected static listSandboxes(opts?: ConnectionOpts): Promise<SandboxInfo[]>;
|
|
157
157
|
protected static getSandboxInfo(sandboxId: string, opts?: ConnectionOpts): Promise<SandboxInfo>;
|
|
158
158
|
protected static setTimeoutSandbox(sandboxId: string, timeoutMs: number, opts?: ConnectionOpts): Promise<void>;
|
|
159
|
+
protected static exposeSandboxPort(sandboxId: string, port: number, opts?: ConnectionOpts): Promise<{
|
|
160
|
+
hostname: string;
|
|
161
|
+
url: string;
|
|
162
|
+
}>;
|
|
159
163
|
}
|
|
160
164
|
|
|
161
165
|
/**
|
|
@@ -308,7 +312,7 @@ declare class Sandbox extends SandboxClient {
|
|
|
308
312
|
* // https://{sandboxId}-3000.sandbox.lizard.run
|
|
309
313
|
* ```
|
|
310
314
|
*/
|
|
311
|
-
getHost(port: number): string
|
|
315
|
+
getHost(port: number, opts?: ConnectionOpts): Promise<string>;
|
|
312
316
|
private resolveOpts;
|
|
313
317
|
}
|
|
314
318
|
|
package/dist/index.js
CHANGED
|
@@ -301,6 +301,15 @@ var SandboxClient = class {
|
|
|
301
301
|
});
|
|
302
302
|
if (!res.ok) await handleApiError(res);
|
|
303
303
|
}
|
|
304
|
+
static async exposeSandboxPort(sandboxId, port, opts) {
|
|
305
|
+
const config = new ConnectionConfig(opts);
|
|
306
|
+
const res = await fetch(`${config.apiUrl}/api/sandboxes/${sandboxId}/expose/${port}`, {
|
|
307
|
+
method: "POST",
|
|
308
|
+
headers: config.headers
|
|
309
|
+
});
|
|
310
|
+
if (!res.ok) await handleApiError(res);
|
|
311
|
+
return res.json();
|
|
312
|
+
}
|
|
304
313
|
};
|
|
305
314
|
|
|
306
315
|
// src/sandbox/index.ts
|
|
@@ -399,10 +408,9 @@ var Sandbox = class extends SandboxClient {
|
|
|
399
408
|
* // https://{sandboxId}-3000.sandbox.lizard.run
|
|
400
409
|
* ```
|
|
401
410
|
*/
|
|
402
|
-
getHost(port) {
|
|
403
|
-
const
|
|
404
|
-
|
|
405
|
-
return `${this.sandboxId}-${port}.sandbox.${baseHost}`;
|
|
411
|
+
async getHost(port, opts) {
|
|
412
|
+
const { hostname } = await SandboxClient.exposeSandboxPort(this.sandboxId, port, this.resolveOpts(opts));
|
|
413
|
+
return hostname;
|
|
406
414
|
}
|
|
407
415
|
resolveOpts(opts) {
|
|
408
416
|
return { ...this.connectionConfig, ...opts };
|
|
@@ -442,7 +450,7 @@ var Execution = class {
|
|
|
442
450
|
var CODE_INTERPRETER_PORT = 8080;
|
|
443
451
|
var CodeSandbox = class extends Sandbox {
|
|
444
452
|
get serverUrl() {
|
|
445
|
-
return
|
|
453
|
+
return this.getHost(CODE_INTERPRETER_PORT).then((h) => `https://${h}`);
|
|
446
454
|
}
|
|
447
455
|
/**
|
|
448
456
|
* Execute code in a persistent kernel.
|
|
@@ -477,7 +485,7 @@ var CodeSandbox = class extends Sandbox {
|
|
|
477
485
|
const controller = new AbortController();
|
|
478
486
|
const timer = opts?.timeoutMs ? setTimeout(() => controller.abort(), opts.timeoutMs) : void 0;
|
|
479
487
|
try {
|
|
480
|
-
const res = await fetch(`${this.serverUrl}/execute`, {
|
|
488
|
+
const res = await fetch(`${await this.serverUrl}/execute`, {
|
|
481
489
|
method: "POST",
|
|
482
490
|
headers: { "Content-Type": "application/json" },
|
|
483
491
|
body: JSON.stringify(body),
|
|
@@ -537,7 +545,7 @@ var CodeSandbox = class extends Sandbox {
|
|
|
537
545
|
* ```
|
|
538
546
|
*/
|
|
539
547
|
async createContext(opts) {
|
|
540
|
-
const res = await fetch(`${this.serverUrl}/contexts`, {
|
|
548
|
+
const res = await fetch(`${await this.serverUrl}/contexts`, {
|
|
541
549
|
method: "POST",
|
|
542
550
|
headers: { "Content-Type": "application/json" },
|
|
543
551
|
body: JSON.stringify({
|
|
@@ -552,7 +560,7 @@ var CodeSandbox = class extends Sandbox {
|
|
|
552
560
|
* List all active execution contexts in this sandbox.
|
|
553
561
|
*/
|
|
554
562
|
async listContexts() {
|
|
555
|
-
const res = await fetch(`${this.serverUrl}/contexts`);
|
|
563
|
+
const res = await fetch(`${await this.serverUrl}/contexts`);
|
|
556
564
|
if (!res.ok) throw new Error(`Failed to list contexts: ${await res.text()}`);
|
|
557
565
|
return res.json();
|
|
558
566
|
}
|
|
@@ -561,7 +569,7 @@ var CodeSandbox = class extends Sandbox {
|
|
|
561
569
|
*/
|
|
562
570
|
async deleteContext(context) {
|
|
563
571
|
const id = typeof context === "string" ? context : context.id;
|
|
564
|
-
const res = await fetch(`${this.serverUrl}/contexts/${id}`, { method: "DELETE" });
|
|
572
|
+
const res = await fetch(`${await this.serverUrl}/contexts/${id}`, { method: "DELETE" });
|
|
565
573
|
if (!res.ok) throw new Error(`Failed to delete context: ${await res.text()}`);
|
|
566
574
|
}
|
|
567
575
|
/**
|
|
@@ -569,7 +577,7 @@ var CodeSandbox = class extends Sandbox {
|
|
|
569
577
|
*/
|
|
570
578
|
async restartContext(context) {
|
|
571
579
|
const id = typeof context === "string" ? context : context.id;
|
|
572
|
-
const res = await fetch(`${this.serverUrl}/contexts/${id}/restart`, { method: "POST" });
|
|
580
|
+
const res = await fetch(`${await this.serverUrl}/contexts/${id}/restart`, { method: "POST" });
|
|
573
581
|
if (!res.ok) throw new Error(`Failed to restart context: ${await res.text()}`);
|
|
574
582
|
}
|
|
575
583
|
static async create(templateOrOpts, opts) {
|
package/dist/index.mjs
CHANGED
|
@@ -270,6 +270,15 @@ var SandboxClient = class {
|
|
|
270
270
|
});
|
|
271
271
|
if (!res.ok) await handleApiError(res);
|
|
272
272
|
}
|
|
273
|
+
static async exposeSandboxPort(sandboxId, port, opts) {
|
|
274
|
+
const config = new ConnectionConfig(opts);
|
|
275
|
+
const res = await fetch(`${config.apiUrl}/api/sandboxes/${sandboxId}/expose/${port}`, {
|
|
276
|
+
method: "POST",
|
|
277
|
+
headers: config.headers
|
|
278
|
+
});
|
|
279
|
+
if (!res.ok) await handleApiError(res);
|
|
280
|
+
return res.json();
|
|
281
|
+
}
|
|
273
282
|
};
|
|
274
283
|
|
|
275
284
|
// src/sandbox/index.ts
|
|
@@ -368,10 +377,9 @@ var Sandbox = class extends SandboxClient {
|
|
|
368
377
|
* // https://{sandboxId}-3000.sandbox.lizard.run
|
|
369
378
|
* ```
|
|
370
379
|
*/
|
|
371
|
-
getHost(port) {
|
|
372
|
-
const
|
|
373
|
-
|
|
374
|
-
return `${this.sandboxId}-${port}.sandbox.${baseHost}`;
|
|
380
|
+
async getHost(port, opts) {
|
|
381
|
+
const { hostname } = await SandboxClient.exposeSandboxPort(this.sandboxId, port, this.resolveOpts(opts));
|
|
382
|
+
return hostname;
|
|
375
383
|
}
|
|
376
384
|
resolveOpts(opts) {
|
|
377
385
|
return { ...this.connectionConfig, ...opts };
|
|
@@ -411,7 +419,7 @@ var Execution = class {
|
|
|
411
419
|
var CODE_INTERPRETER_PORT = 8080;
|
|
412
420
|
var CodeSandbox = class extends Sandbox {
|
|
413
421
|
get serverUrl() {
|
|
414
|
-
return
|
|
422
|
+
return this.getHost(CODE_INTERPRETER_PORT).then((h) => `https://${h}`);
|
|
415
423
|
}
|
|
416
424
|
/**
|
|
417
425
|
* Execute code in a persistent kernel.
|
|
@@ -446,7 +454,7 @@ var CodeSandbox = class extends Sandbox {
|
|
|
446
454
|
const controller = new AbortController();
|
|
447
455
|
const timer = opts?.timeoutMs ? setTimeout(() => controller.abort(), opts.timeoutMs) : void 0;
|
|
448
456
|
try {
|
|
449
|
-
const res = await fetch(`${this.serverUrl}/execute`, {
|
|
457
|
+
const res = await fetch(`${await this.serverUrl}/execute`, {
|
|
450
458
|
method: "POST",
|
|
451
459
|
headers: { "Content-Type": "application/json" },
|
|
452
460
|
body: JSON.stringify(body),
|
|
@@ -506,7 +514,7 @@ var CodeSandbox = class extends Sandbox {
|
|
|
506
514
|
* ```
|
|
507
515
|
*/
|
|
508
516
|
async createContext(opts) {
|
|
509
|
-
const res = await fetch(`${this.serverUrl}/contexts`, {
|
|
517
|
+
const res = await fetch(`${await this.serverUrl}/contexts`, {
|
|
510
518
|
method: "POST",
|
|
511
519
|
headers: { "Content-Type": "application/json" },
|
|
512
520
|
body: JSON.stringify({
|
|
@@ -521,7 +529,7 @@ var CodeSandbox = class extends Sandbox {
|
|
|
521
529
|
* List all active execution contexts in this sandbox.
|
|
522
530
|
*/
|
|
523
531
|
async listContexts() {
|
|
524
|
-
const res = await fetch(`${this.serverUrl}/contexts`);
|
|
532
|
+
const res = await fetch(`${await this.serverUrl}/contexts`);
|
|
525
533
|
if (!res.ok) throw new Error(`Failed to list contexts: ${await res.text()}`);
|
|
526
534
|
return res.json();
|
|
527
535
|
}
|
|
@@ -530,7 +538,7 @@ var CodeSandbox = class extends Sandbox {
|
|
|
530
538
|
*/
|
|
531
539
|
async deleteContext(context) {
|
|
532
540
|
const id = typeof context === "string" ? context : context.id;
|
|
533
|
-
const res = await fetch(`${this.serverUrl}/contexts/${id}`, { method: "DELETE" });
|
|
541
|
+
const res = await fetch(`${await this.serverUrl}/contexts/${id}`, { method: "DELETE" });
|
|
534
542
|
if (!res.ok) throw new Error(`Failed to delete context: ${await res.text()}`);
|
|
535
543
|
}
|
|
536
544
|
/**
|
|
@@ -538,7 +546,7 @@ var CodeSandbox = class extends Sandbox {
|
|
|
538
546
|
*/
|
|
539
547
|
async restartContext(context) {
|
|
540
548
|
const id = typeof context === "string" ? context : context.id;
|
|
541
|
-
const res = await fetch(`${this.serverUrl}/contexts/${id}/restart`, { method: "POST" });
|
|
549
|
+
const res = await fetch(`${await this.serverUrl}/contexts/${id}/restart`, { method: "POST" });
|
|
542
550
|
if (!res.ok) throw new Error(`Failed to restart context: ${await res.text()}`);
|
|
543
551
|
}
|
|
544
552
|
static async create(templateOrOpts, opts) {
|