@lizard-build/sdk 0.1.34 → 0.1.36
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 +93 -27
- package/dist/index.d.mts +422 -101
- package/dist/index.d.ts +422 -101
- package/dist/index.js +279 -113
- package/dist/index.mjs +268 -113
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -80,40 +80,74 @@ print(f"Live at https://{sandbox.get_host(3000)}")
|
|
|
80
80
|
sandbox.kill()
|
|
81
81
|
```
|
|
82
82
|
|
|
83
|
-
##
|
|
83
|
+
## Persisting Work Across Sandboxes
|
|
84
84
|
|
|
85
|
-
Sandboxes
|
|
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 `/data` that a later sandbox re-attaches.
|
|
86
86
|
|
|
87
|
-
|
|
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
88
|
|
|
89
89
|
```ts
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
await
|
|
93
|
-
|
|
94
|
-
await
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
const result = await resumed.process.exec('python -c "import sklearn; print(sklearn.__version__)"')
|
|
99
|
-
console.log(result.stdout)
|
|
100
|
-
await resumed.kill()
|
|
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" > /data/notes.txt')
|
|
94
|
+
await first.kill() // sandbox gone, /data survives
|
|
95
|
+
|
|
96
|
+
const second = await lizard.create('codex', { volumeName: 'agent-scratch' })
|
|
97
|
+
console.log(await second.fs.read('/data/notes.txt')) // "notes"
|
|
101
98
|
```
|
|
102
99
|
|
|
103
100
|
```python
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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" > /data/notes.txt')
|
|
105
|
+
first.kill() # sandbox gone, /data survives
|
|
106
|
+
|
|
107
|
+
second = lizard.create("codex", volume_name="agent-scratch")
|
|
108
|
+
print(second.fs.read("/data/notes.txt")) # "notes"
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Note that only `/data` 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.
|
|
114
|
+
|
|
115
|
+
## Giving Each of Your Users Their Own Workspace
|
|
116
|
+
|
|
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.
|
|
118
|
+
|
|
119
|
+
```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
|
|
136
|
+
})
|
|
137
|
+
await sandbox.process.exec('lizard volume list') // sees only this workspace
|
|
115
138
|
```
|
|
116
139
|
|
|
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
|
+
|
|
117
151
|
## API
|
|
118
152
|
|
|
119
153
|
### `new Lizard({ project, apiKey?, apiUrl?, timeoutMs? })`
|
|
@@ -137,7 +171,7 @@ const sandbox = await Sandbox.create('code-interpreter-v1', { project: 'my-proje
|
|
|
137
171
|
|
|
138
172
|
### `Sandbox.connect(sandboxId, opts?)`
|
|
139
173
|
|
|
140
|
-
Connect to an existing sandbox by ID.
|
|
174
|
+
Connect to an existing sandbox by ID. Throws `NotFoundError` if it has been killed or has expired.
|
|
141
175
|
|
|
142
176
|
### `Sandbox.list(opts?)`
|
|
143
177
|
|
|
@@ -145,6 +179,38 @@ List all running sandboxes for the authenticated account.
|
|
|
145
179
|
|
|
146
180
|
---
|
|
147
181
|
|
|
182
|
+
### Account and provisioning
|
|
183
|
+
|
|
184
|
+
| Namespace | Methods |
|
|
185
|
+
|---|---|
|
|
186
|
+
| `lizard.workspaces` | `list()`, `create({ name })`, `delete(id, { force? })`, `find(nameOrSlugOrId)` |
|
|
187
|
+
| `lizard.apiKeys` | `list()`, `create({ name, workspaces?, projects? })`, `delete(id)` |
|
|
188
|
+
| `lizard.projects` | `list({ workspaceId? })`, `get(id)`, `create({ workspaceId, name })`, `update(id, { name })`, `delete(id)` |
|
|
189
|
+
| `lizard.regions` | `list()` |
|
|
190
|
+
| `lizard.billing` | `balance()`, `transactions({ limit?, cursor?, includeUsage? })`, `summary()`, `live()` |
|
|
191
|
+
| `lizard.whoami()` | The account behind the credential — works for scoped keys too |
|
|
192
|
+
| `lizard.platform` | The raw HTTP client, for endpoints not wrapped yet |
|
|
193
|
+
|
|
194
|
+
`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.
|
|
195
|
+
|
|
196
|
+
In Python the namespaces are `lizard.workspaces`, `lizard.api_keys`, `lizard.regions`, `lizard.billing`, and arguments are snake_case (`workspace_id`, `include_usage`).
|
|
197
|
+
|
|
198
|
+
### Volumes
|
|
199
|
+
|
|
200
|
+
| Method | Description |
|
|
201
|
+
|---|---|
|
|
202
|
+
| `lizard.volumes.getOrCreate(name, { sizeGb?, region? })` | The volume with this name, created if absent |
|
|
203
|
+
| `lizard.volumes.create(name, { sizeGb?, region? })` | Create; throws `ConflictError` if the name is taken |
|
|
204
|
+
| `lizard.volumes.get(nameOrId)` | Look one up |
|
|
205
|
+
| `lizard.volumes.list()` | Every volume in the client's project |
|
|
206
|
+
| `lizard.volumes.delete(nameOrId)` | Delete |
|
|
207
|
+
|
|
208
|
+
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.
|
|
209
|
+
|
|
210
|
+
`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.
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
148
214
|
### `sandbox.fs`
|
|
149
215
|
|
|
150
216
|
Read and write files inside the microVM filesystem.
|
|
@@ -179,7 +245,7 @@ const url = sandbox.getHost(3000)
|
|
|
179
245
|
|
|
180
246
|
### `sandbox.pause()` / `sandbox.resume()`
|
|
181
247
|
|
|
182
|
-
|
|
248
|
+
**Not implemented** for the current runtime — both always fail with HTTP 501. Use a [volume](#persisting-work-across-sandboxes) to carry work across sandboxes.
|
|
183
249
|
|
|
184
250
|
### `sandbox.kill()`
|
|
185
251
|
|