@lizard-build/cli 4.0.43 → 4.0.45
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/AGENTS.md +5 -2
- package/dist/commands/skill.d.ts +0 -2
- package/dist/commands/skill.js +90 -33
- package/dist/commands/skill.js.map +1 -1
- package/dist/index.js +13 -22
- package/dist/index.js.map +1 -1
- package/dist/lib/skills-data.generated.d.ts +2 -0
- package/dist/lib/skills-data.generated.js +6 -5
- package/dist/lib/skills-data.generated.js.map +1 -1
- package/dist/lib/updater.d.ts +1 -1
- package/dist/lib/updater.js +1 -1
- package/package.json +1 -1
- package/scripts/gen-skills.mjs +25 -20
- package/skill-data/core/SKILL.md +152 -147
- package/skill-data/core/references/deploys.md +190 -0
- package/skill-data/{sandbox/SKILL.md → core/references/sandboxes.md} +14 -70
- package/skill-data/core/references/sdk.md +215 -0
- package/src/commands/skill.ts +96 -36
- package/src/index.ts +17 -23
- package/src/lib/updater.ts +1 -1
- package/test/unit/skills.test.ts +35 -58
- package/dist/lib/skill-route.d.ts +0 -2
- package/dist/lib/skill-route.js +0 -7
- package/dist/lib/skill-route.js.map +0 -1
- package/skill-data/_shared/basics.md +0 -92
- package/src/lib/skill-route.ts +0 -7
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
# Deploys in depth
|
|
2
|
+
|
|
3
|
+
The details behind the deploy, database and secrets sections of the core guide: how builds are chosen, git and upload deploys, service fields, worker mode, secret precedence and references, addon variables, and multi-step recipes.
|
|
4
|
+
|
|
5
|
+
## Contents
|
|
6
|
+
|
|
7
|
+
- [Before you deploy](#before-you-deploy)
|
|
8
|
+
- [Platform builder](#platform-builder)
|
|
9
|
+
- [Deploying](#deploying)
|
|
10
|
+
- [Worker mode](#worker-mode)
|
|
11
|
+
- [Secrets](#secrets)
|
|
12
|
+
- [Managed addons](#managed-addons)
|
|
13
|
+
- [Composition patterns](#composition-patterns)
|
|
14
|
+
- [Logs, metrics and lizard run](#logs-metrics-and-lizard-run)
|
|
15
|
+
|
|
16
|
+
## Before you deploy
|
|
17
|
+
|
|
18
|
+
This skill documents platform behavior (build pipeline, env precedence, what knobs the API exposes). It does not describe the user's repo.
|
|
19
|
+
|
|
20
|
+
Before writing commands for a specific project:
|
|
21
|
+
|
|
22
|
+
1. Read the user's `package.json`, `Dockerfile`, `requirements.txt`, framework config — confirm what already exists before adding flags.
|
|
23
|
+
2. Don't assume scripts/conventions that aren't visible. On the lizardpack auto-detect path, `Procfile` (`web:` line, Python/Ruby) and `package.json scripts.start` (Node) ARE picked up as the start command; on the synthesized-Dockerfile path (`buildCommand`/`startCommand` set) neither is read. Ports are inferred only from `EXPOSE`, framework defaults, or an explicit `PORT` env.
|
|
24
|
+
3. When in doubt, ask the user or run `lizard <cmd> --help --json`.
|
|
25
|
+
|
|
26
|
+
## Platform builder
|
|
27
|
+
|
|
28
|
+
Builds run on the platform's build nodes (no local Docker needed). When a build fails, read logs with `lizard logs --build`.
|
|
29
|
+
|
|
30
|
+
### Build decision order
|
|
31
|
+
|
|
32
|
+
1. Repo Dockerfile (verbatim) — if `dockerfilePath` is set on the service, the platform builds that Dockerfile from the repo unchanged and ignores `buildCommand`/`startCommand` for the image. A set `startCommand` still replaces the image's CMD at run time.
|
|
33
|
+
2. Synthesized Dockerfile — if `buildCommand` and/or `startCommand` are set on the service (or passed via `lizard up`), the platform generates a Dockerfile from those commands. No lizardpack invocation.
|
|
34
|
+
3. lizardpack auto-detect — clone, run `lizardpack`. If a repo `Dockerfile` exists AND has a real build step (a `RUN <pkg-manager>` line, not just `COPY dist/`), it's used verbatim; otherwise lizardpack generates a multi-stage one. Supported: Go, Node, Python, Rust, Ruby, PHP, Java, static — first match in that order.
|
|
35
|
+
|
|
36
|
+
### What triggers a rebuild
|
|
37
|
+
|
|
38
|
+
- `git push` to the tracked branch → auto-rebuild via GitHub webhook.
|
|
39
|
+
- `lizard redeploy` / `lizard up` → explicit rebuild.
|
|
40
|
+
- Setting a `VITE_*` or `NEXT_PUBLIC_*` variable → the platform redeploys the running services that receive it, so the build bakes it in. Do NOT chain a `lizard redeploy`. See [Applying an env change](#applying-an-env-change) for which builds receive them.
|
|
41
|
+
- `service set` for build-affecting fields (`repoUrl`, `branch`, `sourceType`, `buildCommand`, `dockerfilePath`, `rootDirectory`) → auto-rebuilds running services. Do NOT chain a `lizard redeploy` after it — that queues a second, redundant build.
|
|
42
|
+
- `service set` for runtime-only fields (`startCommand`, `preDeployCommand`, `containerPort`, `watchPatterns`) → no auto-rebuild. Follow with `lizard redeploy` to apply.
|
|
43
|
+
- All other env vars / secrets → applied without a rebuild; the service restarts to pick them up.
|
|
44
|
+
|
|
45
|
+
## Deploying
|
|
46
|
+
|
|
47
|
+
First question for a new service: upload vs git repo. **Always check for a git remote before reaching for `up`** — the CLI never auto-detects one, so you must do it yourself:
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
git remote get-url origin
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
- **Exit 0 with a GitHub URL → use the git-source path below**, not `up`. Parse `owner/repo` from the URL and run `lizard add -r owner/repo`. This is strongly preferred: pushes auto-redeploy via webhook, and there's no re-upload on every change.
|
|
54
|
+
- **No remote / non-GitHub / not a git repo → use tarball upload (`lizard up`).** This is the fallback for quick local iteration or no-remote situations, not the default.
|
|
55
|
+
|
|
56
|
+
`lizard up` always works regardless of git state, which makes it the path of least resistance — resist it when a GitHub remote exists. Only fall back to upload when the remote check actually fails. (For a private repo whose remote exists but isn't yet accessible to Lizard, run `lizard git connect` to install the GitHub App, then use the git-source path — don't silently downgrade to a tarball.)
|
|
57
|
+
|
|
58
|
+
### Git-source deploy (preferred when there's a remote)
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
# One-shot for a new service from GitHub:
|
|
62
|
+
lizard add -r owner/repo --json
|
|
63
|
+
|
|
64
|
+
# Existing service: switch source to git or update branch:
|
|
65
|
+
lizard service set <svc> \
|
|
66
|
+
--set sourceType=github \
|
|
67
|
+
--set repoUrl=https://github.com/owner/repo \
|
|
68
|
+
--set branch=main \
|
|
69
|
+
--json
|
|
70
|
+
lizard redeploy --service <svc>
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
When `repoUrl` is set, pushes to the matching branch auto-redeploy via the GitHub webhook. If the service has a `rootDirectory` (monorepo subpath) or watch patterns, only matching changes trigger redeploys.
|
|
74
|
+
|
|
75
|
+
Useful `service set` fields (discover full list with `lizard service set --help --json`):
|
|
76
|
+
|
|
77
|
+
- `sourceType` = `github | upload`
|
|
78
|
+
- `repoUrl`, `branch`, `rootDirectory`
|
|
79
|
+
- `dockerfilePath` — use a specific repo Dockerfile, bypasses lizardpack auto-detect
|
|
80
|
+
- `buildCommand`
|
|
81
|
+
- `startCommand`, `preDeployCommand` — `preDeployCommand` runs before `startCommand` at the start of every container (each replica, each restart), and only when `startCommand` is also set. Make it safe to run many times and at the same time.
|
|
82
|
+
- `watchPatterns` — string array, comma-separated or JSON
|
|
83
|
+
- `containerPort` — TCP port the app listens on (defaults to 3000). Set to `0` for [worker mode](#worker-mode).
|
|
84
|
+
- `name` — rename a service (lowercase a-z, digits, hyphens; 1–40 chars). Goes through `config:apply`; the legacy `PATCH /api/apps/:id` returns 410. Refs to the old name (`${{old.KEY}}`) stop resolving: update them.
|
|
85
|
+
|
|
86
|
+
Field names are flat and match the wire schema 1:1 (and `service show` output). No `build.*` / `deploy.*` / `source.*` grouping exists in the API, DB, or node-agent.
|
|
87
|
+
|
|
88
|
+
`service set` uses optimistic concurrency via `configRevision`. On 409, re-read with `lizard service show`, reconcile, retry; `--force` overrides.
|
|
89
|
+
|
|
90
|
+
### Tarball upload (no git remote, or quick local iteration)
|
|
91
|
+
|
|
92
|
+
```
|
|
93
|
+
lizard up --json
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
- Uploads cwd as a tarball (respects `.gitignore`), forces `sourceType=upload`.
|
|
97
|
+
- Streams build logs over SSE; emits a final `{ event: "deployed", url }` on success (`{ event: "failed" }` on failure; `url` may be `null`).
|
|
98
|
+
- Flags: `--project`, `--service`, `--region`, `--build-command`, `--start-command`, `--pre-deploy-command`, `--port`, `--detach`, `--ci`.
|
|
99
|
+
- If cwd isn't linked and no `--project` is given, it links the folder to the default project (see the core guide) and deploys there as a new service. `--project <name>` uses that project, or creates it if none matches. It never creates a project from the folder name on its own.
|
|
100
|
+
- `lizard up` always switches the service to `sourceType=upload`. Do not use it to update a git-backed service — use `lizard redeploy` or push to the remote.
|
|
101
|
+
|
|
102
|
+
## Worker mode
|
|
103
|
+
|
|
104
|
+
For services that don't expose an HTTP listener (background workers, reconcilers, queue consumers, cron-style polling loops), set `containerPort=0`. The platform then:
|
|
105
|
+
|
|
106
|
+
- Skips `PORT` env injection (the worker doesn't bind anywhere).
|
|
107
|
+
- Skips the port reachability check (no `app port X unreachable` log spam, no false-positive "unhealthy" status).
|
|
108
|
+
- Skips `EXPOSE` in the synthesized Dockerfile.
|
|
109
|
+
- Skips the LB route registration on the node — nothing is served. (A generated `.onlizard.com` domain may still appear on the service; it won't respond.)
|
|
110
|
+
|
|
111
|
+
Set it one of three ways:
|
|
112
|
+
|
|
113
|
+
```
|
|
114
|
+
lizard up --port 0 # new upload-source worker
|
|
115
|
+
lizard port 0 [--service <svc>] # flip existing service to worker mode
|
|
116
|
+
lizard service set <svc> --set containerPort=0 # same, via the config:apply path
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
`lizard port` with no argument prints the current port (or `worker mode` when 0). Worker mode is a hard switch — re-deploys are needed for the port change to take effect.
|
|
120
|
+
|
|
121
|
+
Don't use worker mode for a regular HTTP service that just happens to be slow to start — worker mode hides "the listener never came up" bugs because there's nothing to check.
|
|
122
|
+
|
|
123
|
+
## Secrets
|
|
124
|
+
|
|
125
|
+
Two scopes exist. No workspace-level globals.
|
|
126
|
+
|
|
127
|
+
- Project ("global"): `lizard secrets set KEY=v [K2=v2 …] --global` → project scope (wire: `secrets.shared`)
|
|
128
|
+
- Service (default): `lizard secrets set KEY=v [K2=v2 …] [--service <svc>]` → service scope (wire: `secrets.services[<svc>]`)
|
|
129
|
+
|
|
130
|
+
`set` is variadic. Companion subcommands: `lizard secrets list|delete K1 K2|import` (import reads dotenv from stdin). When the linked service in cwd is set, plain `lizard secrets set KEY=v` writes to that service. Pass `--global` to escape to project scope.
|
|
131
|
+
|
|
132
|
+
### Precedence (last writer wins)
|
|
133
|
+
|
|
134
|
+
```
|
|
135
|
+
addon-issued env < project secrets < project env < app env < app secrets < platform vars
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
App secrets override project secrets. Platform vars (`LIZARD_SERVICE_NAME`, `LIZARD_PROJECT_ID`, `PORT`, `LIZARD_PUBLIC_DOMAIN`, `LIZARD_PRIVATE_DOMAIN`) are last and cannot be shadowed. Addon-issued env is every running Postgres and Redis addon's `DATABASE_URL` / `REDIS_URL`, injected into every service in the project.
|
|
139
|
+
|
|
140
|
+
### Secret scoping
|
|
141
|
+
|
|
142
|
+
Default to service-scope. `--global` puts the value into `process.env` of every service in the project — including ones that don't need it.
|
|
143
|
+
|
|
144
|
+
Rules:
|
|
145
|
+
|
|
146
|
+
- Check first: `lizard secrets list` (+ `--global`) before set/update — avoid creating a duplicate or shadowing an existing key (service scope wins over global; see [Precedence](#precedence-last-writer-wins)).
|
|
147
|
+
- Default — service-scope: `lizard secrets set KEY=v --service <svc>` per consumer. For addon DSNs, prefer a private reference when the consumer can reach it: `lizard secrets set DATABASE_URL='${{postgres.DATABASE_PRIVATE_URL}}' --service <svc>` (no separate `env` command — refs are interpolated at deploy time wherever they appear). A ref reads the addon's current value on each deploy of the consumer; there is no automatic credential rotation.
|
|
148
|
+
- `--global` only for non-secrets and provably-public values: `LOG_LEVEL`, `NODE_ENV`, feature flags, frontend `SENTRY_DSN`. If unsure whether a value is a secret, treat it as one. A compromised service reads its own env; broader scope = more credentials exposed for no reason.
|
|
149
|
+
|
|
150
|
+
### Applying an env change
|
|
151
|
+
|
|
152
|
+
- Runtime vars/secrets apply without rebuilding the image. The application process restarts to read its new environment; do not promise uninterrupted requests.
|
|
153
|
+
- `VITE_*` / `NEXT_PUBLIC_*` (build-time baked) → setting one redeploys the running services that receive it; a plain restart won't pick them up. Only a Dockerfile the platform generates bakes them in: lizardpack on a GitHub service, or a service with `buildCommand`/`startCommand`. A `dockerfilePath` build, or a `lizard up` upload without those overrides, gets no Lizard variables at build time, and no build gets build args except `COMMIT_SHA`. For public values there, commit them in `.env.production`, set a build command, or write them into the Dockerfile.
|
|
154
|
+
- Verify a non-secret value or the application's behavior. Do not print the full environment: it can expose credentials.
|
|
155
|
+
|
|
156
|
+
## Managed addons
|
|
157
|
+
|
|
158
|
+
Provision with `lizard add <type>`. Each addon exposes a fixed env-var set; reference by name from a consumer service via `${{<addon-name>.KEY}}`. The first addon of a given type gets the bare type as its name (so use `postgres` in the reference for the first Managed Postgres instance); subsequent ones get `{type}-{adjective}-{noun}` like `postgres-autumn-bear`. There's no type-alias fallback — a ref must use the addon's actual name. Refs match the addon's current name, so renaming it breaks existing refs: update them after a rename. Every running Postgres and Redis addon's `DATABASE_URL` / `REDIS_URL` also reaches every service in the project at the lowest precedence; a value set on the consumer overrides it.
|
|
159
|
+
|
|
160
|
+
- `postgres` — two connection URLs: prefer `DATABASE_PRIVATE_URL` when available and reachable within the same project and region (no ingress/egress charges); use `DATABASE_URL` for public access, such as local clients or external CI (network charges may apply). Also exposes `PGHOST`, `PGPORT`, `PGUSER`, `PGPASSWORD`, `PGDATABASE`, `POSTGRES_USER`, `POSTGRES_DB`, `POSTGRES_PASSWORD`.
|
|
161
|
+
- `redis` — `REDIS_URL`; services in the same project should use `REDIS_PRIVATE_URL`. No eviction policy and no append-only file are configured: if it runs out of memory, the instance restarts and recent writes can be lost. Keep data you cannot lose in Postgres.
|
|
162
|
+
- `s3` — `S3_ENDPOINT`, `S3_DEFAULT_BUCKET`, `S3_ACCESS_KEY_ID`, `S3_SECRET_ACCESS_KEY`, `S3_REGION`. Auto-creates a private bucket named `default`, and each account can store up to 16 GiB across its addons. Make a bucket public from the dashboard's S3 browser; objects in a public bucket are served without auth two ways: the gateway URL the dashboard shows, `https://s3-<region>.onlizard.com/<addonId>/<bucket>/<key>`, or the platform proxy `<dashboard-host>/api/s3/<addonId>/public/<bucket>/<key>` (the host `lizard open` launches; long-lived immutable cache headers). For AWS SDK use, set `forcePathStyle: true`. ACL flips aren't on the CLI yet, so point users at the dashboard. To push a local file into a bucket directly (no AWS SDK needed), use `lizard s3 upload <file> [--addon <name>] [--bucket <name>] [--key <key>] [--content-type <type>]` — auto-resolves the project's only S3 addon and bucket `default` if omitted; prints `{key, etag, size, url}` (`url` is `null` unless the bucket is public). `lizard s3 list|ls [--bucket <name>] [--prefix <p>]` lists objects in a bucket.
|
|
163
|
+
|
|
164
|
+
## Composition patterns
|
|
165
|
+
|
|
166
|
+
Multi-step requests follow natural chains. Return one unified response, don't farm out steps:
|
|
167
|
+
|
|
168
|
+
- First deploy from git — pick action via the setup decision flow in the core guide → `lizard add -r owner/repo` → stream build → surface URL.
|
|
169
|
+
- First deploy from local code — Setup decision flow → `lizard up` → surface URL.
|
|
170
|
+
- Add a managed database to an existing service — `lizard add postgres` → bind the consumer's `DATABASE_URL` to the private reference as described above (the service restarts with it; no redeploy needed).
|
|
171
|
+
- Add object storage to a service — `lizard add s3` → reference `${{s3.S3_ENDPOINT}}`, `${{s3.S3_DEFAULT_BUCKET}}`, `${{s3.S3_ACCESS_KEY_ID}}`, `${{s3.S3_SECRET_ACCESS_KEY}}`, `${{s3.S3_REGION}}` from the consumer service. The `default` bucket is private; to serve files without auth, have the user make it public in the dashboard's S3 browser, then objects are served at `<dashboard-host>/api/s3/<addonId>/public/default/<key>`. See [Managed addons](#managed-addons).
|
|
172
|
+
- Wire a fresh git source on an existing service — `service set --set sourceType=github --set repoUrl=… --set branch=…` → `redeploy`.
|
|
173
|
+
- Fix a failed build — `logs --build` → diagnose → fix project (user's repo) OR adjust `buildCommand` / `startCommand` via `service set` → `redeploy` → `logs` to verify.
|
|
174
|
+
- Add a custom domain — `domain <host> --service <svc>` (the hostname is a positional, there is no `add` subcommand) → surface the TXT/CNAME records to the user → `domain verify <host>` once DNS propagates. Bare `domain` shows (or auto-generates) the service's current domain. If the host is already attached to another of the user's services, the attach 409s with a reclaim hint — re-run with `--force` to move it here.
|
|
175
|
+
|
|
176
|
+
## Logs, metrics and lizard run
|
|
177
|
+
|
|
178
|
+
`lizard metrics --all` ignores the directory's linked service for this command.
|
|
179
|
+
It uses the linked project unless `--project <name>` selects another project.
|
|
180
|
+
Without `--all`, metrics defaults to the linked service, or the project overview
|
|
181
|
+
when no service is linked. Do not combine `--all` with `--service`.
|
|
182
|
+
The project overview includes `Egress` (outbound bytes per second) and `Volumes`
|
|
183
|
+
(used / total bytes). These columns use the latest sample from the past hour;
|
|
184
|
+
`Sampled` refers to CPU and memory. Missing measurements show `—`.
|
|
185
|
+
Watch mode refreshes CPU and memory every 3 seconds and I/O history every 30 seconds.
|
|
186
|
+
JSON output includes the history in each service's `series` and `timestamps` fields.
|
|
187
|
+
|
|
188
|
+
`lizard run --service <app>` injects stored values only: `${{…}}` refs arrive as literal text, and platform vars (`PORT`, `LIZARD_*`) are not set. `--service <addon>` injects the addon's real connection values, so use it to run a local command against a database. To check a service's wiring, use `lizard logs --build --service <svc>` and `lizard secrets list --service <svc> --show`; shell access into services is off.
|
|
189
|
+
|
|
190
|
+
For exact flags, `lizard <cmd> --help --json`. Other commands not shown above: `lizard git` (GitHub integration), `lizard config` (project configuration), `lizard workspace` (workspace info) — discover each with `lizard <cmd> --help --json`.
|
|
@@ -1,34 +1,20 @@
|
|
|
1
|
-
|
|
2
|
-
name: lizard-sandbox
|
|
3
|
-
description: "Creates and manages Lizard sandboxes with the `lizard` CLI and Lizard SDK: a cloud sandbox (a Firecracker microVM) to run commands and code, copy files in and out, expose ports over HTTPS, open a shell, run Claude Code or Codex, drive a desktop and browser for computer use, keep files on volumes, and pause, snapshot or restore. Use for any sandbox task: an isolated sandbox, cloud machine or code interpreter, running untrusted or AI-generated code, or giving an agent its own computer."
|
|
4
|
-
argument-hint: "[optional natural-language request]"
|
|
5
|
-
allowed-tools: Bash(lizard:*), Bash(~/.lizard/bin/lizard:*), Bash(which:*), Bash(command:*), Bash(npm install:*)
|
|
6
|
-
---
|
|
1
|
+
# Sandboxes in depth
|
|
7
2
|
|
|
8
|
-
|
|
3
|
+
The details behind the Sandboxes section of the core guide. Read the part the task needs.
|
|
9
4
|
|
|
10
|
-
|
|
5
|
+
## Contents
|
|
11
6
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
lizard sandbox exec <id> -- python3 -c 'print(2 ** 10)' # exits with the command's exit code
|
|
24
|
-
lizard sandbox files put <id> ./app.py /workspace/app.py
|
|
25
|
-
lizard sandbox exec <id> -- python3 /workspace/app.py
|
|
26
|
-
lizard sandbox expose <id> 8000 --json # an HTTPS URL for a port in the sandbox
|
|
27
|
-
lizard sandbox timeout <id> 3600000 # keep it for an hour from now
|
|
28
|
-
lizard sandbox rm <id> -y # delete it now
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
`lizard sandbox list --json` shows the project's sandboxes; `--all` shows every one you have.
|
|
7
|
+
- [Templates](#templates)
|
|
8
|
+
- [Running commands](#running-commands)
|
|
9
|
+
- [Files](#files)
|
|
10
|
+
- [Ports](#ports)
|
|
11
|
+
- [Lifetime](#lifetime)
|
|
12
|
+
- [Size and pricing](#size-and-pricing)
|
|
13
|
+
- [Interactive shell](#interactive-shell)
|
|
14
|
+
- [Coding agents](#coding-agents)
|
|
15
|
+
- [Desktop (computer use)](#desktop-computer-use)
|
|
16
|
+
- [Volumes](#volumes)
|
|
17
|
+
- [Snapshots and pause/resume](#snapshots-and-pauseresume)
|
|
32
18
|
|
|
33
19
|
## Templates
|
|
34
20
|
|
|
@@ -156,45 +142,3 @@ lizard sandbox create --volume <name> # attach at create
|
|
|
156
142
|
- `lizard sandbox snapshots --project <project>` lists a project's snapshots; `--mine` lists every snapshot you took. `sandbox snapshot-warm <id> <count>` changes capacity. `sandbox snapshot-rm <id>` deletes the snapshot and free copies, preserving already running sandboxes.
|
|
157
143
|
- `lizard sandbox pause <id>` snapshots and stops a Firecracker VM (a pod sandbox is checkpointed with CRIU), stops billing, and freezes its remaining lifetime. `sandbox resume <id>` restores the same sandbox ID, files, processes, memory, published ports, and remaining lifetime. Paused sandboxes keep no warm copies; resume includes a cold restore. Both support `--no-wait`.
|
|
158
144
|
- Disconnect clients and terminals and stop workspace writes before capturing or pausing. On a pod sandbox, active TCP connections and unsupported CRIU process state cause a clear failure. To reuse one snapshot across sandboxes that each keep their own data, snapshot a sandbox with no volume attached and pass `--volume` on each restore; a snapshot taken with a volume attached cannot take a different one.
|
|
159
|
-
|
|
160
|
-
## From code: Lizard SDK
|
|
161
|
-
|
|
162
|
-
For an app or script that creates sandboxes itself, use Lizard SDK instead of calling the CLI:
|
|
163
|
-
|
|
164
|
-
| SDK | Install | Requires |
|
|
165
|
-
|---|---|---|
|
|
166
|
-
| TypeScript | `npm install @lizard-build/sdk` | Node.js 18+ |
|
|
167
|
-
| Python | `pip install lizard-sdk` | Python 3.10+ |
|
|
168
|
-
|
|
169
|
-
The SDK reads `LIZARD_API_KEY`. Create one with `lizard keys create <name> --project <project> --json`: a key limited to one project is safest when the code runs somewhere else.
|
|
170
|
-
|
|
171
|
-
```ts
|
|
172
|
-
import { Lizard } from '@lizard-build/sdk'
|
|
173
|
-
|
|
174
|
-
const lizard = new Lizard({ project: 'my-project' })
|
|
175
|
-
const sandbox = await lizard.create('base', { timeoutMs: 300_000 })
|
|
176
|
-
try {
|
|
177
|
-
await sandbox.fs.write('/tmp/hello.txt', 'Hello from Lizard!')
|
|
178
|
-
const result = await sandbox.process.exec('cat /tmp/hello.txt')
|
|
179
|
-
if (result.exitCode !== 0) throw new Error(result.stderr)
|
|
180
|
-
console.log(result.stdout)
|
|
181
|
-
} finally {
|
|
182
|
-
await sandbox.kill()
|
|
183
|
-
}
|
|
184
|
-
```
|
|
185
|
-
|
|
186
|
-
```python
|
|
187
|
-
from lizard import Lizard
|
|
188
|
-
|
|
189
|
-
lizard = Lizard(project="my-project")
|
|
190
|
-
with lizard.create("base", timeout_ms=300_000) as sandbox:
|
|
191
|
-
sandbox.fs.write("/tmp/hello.txt", "Hello from Lizard!")
|
|
192
|
-
result = sandbox.process.exec_("cat /tmp/hello.txt")
|
|
193
|
-
if result.exit_code != 0:
|
|
194
|
-
raise RuntimeError(result.stderr)
|
|
195
|
-
print(result.stdout)
|
|
196
|
-
```
|
|
197
|
-
|
|
198
|
-
Guides: [TypeScript](https://github.com/lizard-build/lizard-sdk/blob/main/packages/js/README.md), [Python](https://github.com/lizard-build/lizard-sdk/blob/main/packages/python/README.md), [sandbox reference](https://github.com/lizard-build/lizard-sdk/blob/main/docs/sandboxes.md).
|
|
199
|
-
|
|
200
|
-
<!-- include: _shared/basics.md -->
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
# Building with Lizard (Lizard SDK)
|
|
2
|
+
|
|
3
|
+
Use Lizard SDK when the user's own code creates and drives sandboxes or manages Lizard resources: an agent framework, a code-execution feature, a backend that gives each of its users a machine. For one-off work from a terminal, use the CLI instead.
|
|
4
|
+
|
|
5
|
+
## Contents
|
|
6
|
+
|
|
7
|
+
- [Install and configure](#install-and-configure)
|
|
8
|
+
- [Quickstart](#quickstart)
|
|
9
|
+
- [Commands and output](#commands-and-output)
|
|
10
|
+
- [Files](#files)
|
|
11
|
+
- [Stateful code (CodeSandbox)](#stateful-code-codesandbox)
|
|
12
|
+
- [Ports](#ports)
|
|
13
|
+
- [Keep files (volumes)](#keep-files-volumes)
|
|
14
|
+
- [Lifecycle, pause, snapshots and fork](#lifecycle-pause-snapshots-and-fork)
|
|
15
|
+
- [Desktop (computer use)](#desktop-computer-use)
|
|
16
|
+
- [Sandboxes for your own users](#sandboxes-for-your-own-users)
|
|
17
|
+
- [Platform APIs](#platform-apis)
|
|
18
|
+
- [Billing from code](#billing-from-code)
|
|
19
|
+
- [Errors](#errors)
|
|
20
|
+
|
|
21
|
+
## Install and configure
|
|
22
|
+
|
|
23
|
+
| SDK | Install | Requires |
|
|
24
|
+
|---|---|---|
|
|
25
|
+
| TypeScript | `npm install @lizard-build/sdk` | Node.js 18+ |
|
|
26
|
+
| Python | `pip install lizard-sdk` | Python 3.10+ |
|
|
27
|
+
|
|
28
|
+
The SDK reads `LIZARD_API_KEY` and, optionally, `LIZARD_API_URL` (default `https://lizard.build`); `apiKey` / `api_key` and `apiUrl` / `api_url` override them. It does not load `.env` files. Create a key with `lizard keys create <name> --project <project> --json`: a key limited to one project is safest when the code runs somewhere else. The key is shown once.
|
|
29
|
+
|
|
30
|
+
Sandboxes and volumes need a project: `new Lizard({ project })` / `Lizard(project=...)` takes its ID, slug or unique name; use the exact ID when names are ambiguous. Platform APIs work without one.
|
|
31
|
+
|
|
32
|
+
Options for `create()` (TypeScript / Python):
|
|
33
|
+
|
|
34
|
+
| Option | Purpose |
|
|
35
|
+
|---|---|
|
|
36
|
+
| `size` | `small` (2 vCPU / 4 GB, $0.0162/h), `medium` (4 vCPU / 8 GB, $0.0324/h, default), `large` (8 vCPU / 16 GB, $0.0648/h) |
|
|
37
|
+
| `timeoutMs` / `timeout_ms` | Lifetime; default 300,000 ms (5 minutes); `0` means no limit |
|
|
38
|
+
| `envs` | Environment variables for every command in the sandbox |
|
|
39
|
+
| `metadata` | Your own string labels, returned by `getInfo()` / `get_info()` |
|
|
40
|
+
| `region` | Region ID; omit to use the platform default |
|
|
41
|
+
| `volumeName` / `volume_name`, `volumeId` / `volume_id` | Attach a persistent volume at `/workspace` |
|
|
42
|
+
| `snapshotId` / `snapshot_id` | Start from a saved snapshot |
|
|
43
|
+
| `lizardToken` / `lizard_token` | A Lizard key for tools inside the sandbox; any code there can read it |
|
|
44
|
+
|
|
45
|
+
Templates: `base` (default), `interpreter` (Python and common data libraries), `codex`, `desktop`. The SDK and CLI give a sandbox 5 minutes by default; the raw API and the dashboard give none. Commands do not reset the deadline. Pricing is flat by size, billed per second while the sandbox runs; a sandbox started from a snapshot is billed by measured usage, and volumes bill separately.
|
|
46
|
+
|
|
47
|
+
## Quickstart
|
|
48
|
+
|
|
49
|
+
```ts
|
|
50
|
+
import { Lizard } from '@lizard-build/sdk'
|
|
51
|
+
|
|
52
|
+
const lizard = new Lizard({ project: 'my-project' })
|
|
53
|
+
const sandbox = await lizard.create('base', { timeoutMs: 300_000 })
|
|
54
|
+
try {
|
|
55
|
+
await sandbox.fs.write('/tmp/hello.txt', 'Hello from Lizard!')
|
|
56
|
+
const result = await sandbox.process.exec('cat /tmp/hello.txt')
|
|
57
|
+
if (result.exitCode !== 0) throw new Error(result.stderr)
|
|
58
|
+
console.log(result.stdout)
|
|
59
|
+
} finally {
|
|
60
|
+
await sandbox.kill()
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
from lizard import Lizard
|
|
66
|
+
|
|
67
|
+
lizard = Lizard(project="my-project")
|
|
68
|
+
with lizard.create("base", timeout_ms=300_000) as sandbox:
|
|
69
|
+
sandbox.fs.write("/tmp/hello.txt", "Hello from Lizard!")
|
|
70
|
+
result = sandbox.process.exec_("cat /tmp/hello.txt")
|
|
71
|
+
if result.exit_code != 0:
|
|
72
|
+
raise RuntimeError(result.stderr)
|
|
73
|
+
print(result.stdout)
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Always kill the sandbox when done: `try/finally` in TypeScript, the `with` block in Python.
|
|
77
|
+
|
|
78
|
+
## Commands and output
|
|
79
|
+
|
|
80
|
+
`process.exec(cmd)` / `process.exec_(cmd)` returns stdout, stderr and the exit code. A nonzero exit code is a result to check; request failures throw or raise.
|
|
81
|
+
|
|
82
|
+
- `onStdout` / `onStderr` (`on_stdout` / `on_stderr`) stream output while the call still waits for the end.
|
|
83
|
+
- `timeoutMs` / `timeout_ms` bounds one command: 1–600 seconds.
|
|
84
|
+
- `envs`, `workdir` and `user` (e.g. `user: 'user'`, the template's unprivileged account) apply to that command only.
|
|
85
|
+
- `onPid` / `on_pid` gives the pid before any output; `process.list()` lists commands and `process.kill(pid, 'SIGKILL')` signals the whole process tree.
|
|
86
|
+
- An argv array runs the binary without a shell, so text from a model or a user cannot run commands.
|
|
87
|
+
|
|
88
|
+
## Files
|
|
89
|
+
|
|
90
|
+
`fs.write`, `fs.read`, `fs.readBytes` / `fs.read_bytes`, `fs.list`, `fs.stat`, `fs.makeDir` / `fs.make_dir`, `fs.move`, `fs.remove`. Use absolute paths. Writes take text or bytes; bytes are stored exactly, so read binary files back with `readBytes()` / `read_bytes()`. A missing path raises `NotFoundError`. `list()` and `stat()` entries carry `type`, `size`, `mode`, `permissions`, `owner`, `group` and `modTime`. File watching is not available yet.
|
|
91
|
+
|
|
92
|
+
## Stateful code (CodeSandbox)
|
|
93
|
+
|
|
94
|
+
`CodeSandbox` boots the `interpreter` template and runs Python in a persistent Jupyter kernel, so variables, imports and functions survive between calls. Plain `process.exec` starts a new process each time.
|
|
95
|
+
|
|
96
|
+
```ts
|
|
97
|
+
import { CodeSandbox } from '@lizard-build/sdk'
|
|
98
|
+
|
|
99
|
+
const sandbox = await CodeSandbox.create({ projectId })
|
|
100
|
+
try {
|
|
101
|
+
await sandbox.runCode('import pandas as pd\ndf = pd.DataFrame({"x": [1, 2, 3]})')
|
|
102
|
+
const run = await sandbox.runCode('df.x.sum()')
|
|
103
|
+
console.log(run.results[0].data) // "6"
|
|
104
|
+
} finally {
|
|
105
|
+
await sandbox.kill()
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
from lizard import CodeSandbox
|
|
111
|
+
|
|
112
|
+
with CodeSandbox.create(project_id=project_id) as sandbox:
|
|
113
|
+
sandbox.run_code('import pandas as pd\ndf = pd.DataFrame({"x": [1, 2, 3]})')
|
|
114
|
+
print(sandbox.run_code("df.x.sum()").results[0].data) # "6"
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
An `Execution` has `stdout`, `stderr`, `results` (the last expression as text, charts and images as base64 PNG, HTML), `error` (`name`, `message`, `traceback`) and `executionCount`. A code error is a result, not an exception. `createContext()` / `create_context()` starts a separate kernel with its own state; `restartContext` clears one. `timeoutMs` (default 60 s) bounds one call: the kernel is interrupted, keeps its state, and `error.name` is `TimeoutError`. `bash` and `javascript` run as fresh processes with no state; `javascript` needs `node`, which `interpreter` does not ship.
|
|
118
|
+
|
|
119
|
+
## Ports
|
|
120
|
+
|
|
121
|
+
Start the server on `0.0.0.0` inside the sandbox, then publish the port:
|
|
122
|
+
|
|
123
|
+
```ts
|
|
124
|
+
const { hostname, url, accessToken } = await sandbox.exposePort(3000)
|
|
125
|
+
const res = await fetch(`https://${hostname}/api`, { headers: { 'X-Lizard-Access-Token': accessToken } })
|
|
126
|
+
await sandbox.unexpose(3000)
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
```python
|
|
130
|
+
exposed = sandbox.expose_port(3000)
|
|
131
|
+
res = httpx.get(f"https://{exposed.hostname}/api", headers={"X-Lizard-Access-Token": exposed.access_token})
|
|
132
|
+
sandbox.unexpose(3000)
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
The port is private: every request needs the token in the `X-Lizard-Access-Token` header, or once as `?lizard_token=<token>` in a browser, which then keeps a cookie. `url` already carries the token, so treat it like a password. `getHost(port)` / `get_host(port)` returns only the hostname, without a scheme.
|
|
136
|
+
|
|
137
|
+
## Keep files (volumes)
|
|
138
|
+
|
|
139
|
+
Only files on an attached volume outlive the sandbox; volumes mount at `/workspace`. A volume attaches to one sandbox at a time, and the sandbox runs in the volume's region, so omit `region` when attaching one.
|
|
140
|
+
|
|
141
|
+
```ts
|
|
142
|
+
await lizard.volumes.getOrCreate('agent-files', { sizeGb: 10 })
|
|
143
|
+
const sandbox = await lizard.create('base', { volumeName: 'agent-files' })
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
```python
|
|
147
|
+
lizard.volumes.get_or_create("agent-files", size_gb=10)
|
|
148
|
+
sandbox = lizard.create("base", volume_name="agent-files")
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Names are unique within a project: `getOrCreate` returns the existing volume, `create` raises `ConflictError`. New volumes are 1–50 GB (default 5). `volumes.list()`, `volumes.get(nameOrId)` and `volumes.delete(nameOrId)` (deletes the files too). To grow a volume, use `lizard volume resize` (see the sandboxes reference).
|
|
152
|
+
|
|
153
|
+
## Lifecycle, pause, snapshots and fork
|
|
154
|
+
|
|
155
|
+
| Task | TypeScript | Python |
|
|
156
|
+
|---|---|---|
|
|
157
|
+
| Reconnect by ID | `Sandbox.connect(id)` | `Sandbox.connect(id)` |
|
|
158
|
+
| Status | `sandbox.getInfo()` | `sandbox.get_info()` |
|
|
159
|
+
| New lifetime from now (1000–2147483647 ms) | `sandbox.setTimeout(ms)` | `sandbox.set_timeout(ms)` |
|
|
160
|
+
| List a project's sandboxes | `Sandbox.list({ projectId })` | `Sandbox.list(project_id=...)` |
|
|
161
|
+
| Kill | `sandbox.kill()` | `sandbox.kill()` |
|
|
162
|
+
| Pause and resume (memory and processes kept, billing stops) | `pause()`, `resume()`, `waitForStatus(s)` | `pause()`, `resume()`, `wait_for_status(s)` |
|
|
163
|
+
| Snapshot (about 2 s, the source keeps running) | `snapshot(name)` | `snapshot(name)` |
|
|
164
|
+
| Start a sandbox from a snapshot (about 0.4 s) | `Sandbox.restore(id)` | `Sandbox.restore(id)` |
|
|
165
|
+
| Live copies of a running sandbox (1–10) | `fork({ count })` | `fork(count=...)` |
|
|
166
|
+
|
|
167
|
+
A snapshot belongs to the user who took it and restores into any project they can use, unless its scope is `workspace` or `project` (`snapshot(name, { scope })`, `Sandbox.setSnapshotScope(id, scope)`). To start many sandboxes that each keep their own files from one snapshot, take it without a volume and pass `volumeId` to each `restore`. A sandbox with a volume cannot be forked. `fork()` returns one entry per copy, with `sandbox` or `error` (for example the account's sandbox limit). Delete snapshots with `Sandbox.deleteSnapshot(id)`; sandboxes already restored keep running.
|
|
168
|
+
|
|
169
|
+
## Desktop (computer use)
|
|
170
|
+
|
|
171
|
+
The `desktop` template has a graphical desktop with Chromium on `DISPLAY=:1`. `sandbox.desktop.start({ width, height })` starts it and returns `url` (watch and control) and `viewOnlyUrl` (watch only). Both are credentials: never log them, and share `viewOnlyUrl` when someone only needs to look. Then drive it with `screenshot()` (PNG bytes), `click(x, y)`, `moveMouse`, `drag`, `type(text)`, `press('ctrl+l')`, `scroll(direction)` and `openUrl(url)`. Input never goes through a shell. On other templates these calls fail with `DESKTOP_NOT_SUPPORTED`.
|
|
172
|
+
|
|
173
|
+
## Sandboxes for your own users
|
|
174
|
+
|
|
175
|
+
Give each of your users a workspace, a project and a key scoped to that workspace. Run this on your trusted server:
|
|
176
|
+
|
|
177
|
+
```ts
|
|
178
|
+
const lizard = new Lizard({})
|
|
179
|
+
const workspace = await lizard.workspaces.create({ name: 'user-123' })
|
|
180
|
+
await lizard.projects.create({ workspaceId: workspace.id, name: 'default' })
|
|
181
|
+
const key = await lizard.apiKeys.create({ name: 'user-123', workspaces: [workspace.id] })
|
|
182
|
+
// Store key.key now: the API returns it only once.
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
```python
|
|
186
|
+
lizard = Lizard()
|
|
187
|
+
workspace = lizard.workspaces.create(name="user-123")
|
|
188
|
+
lizard.projects.create(workspace_id=workspace.id, name="default")
|
|
189
|
+
key = lizard.api_keys.create(name="user-123", workspaces=[workspace.id])
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
A key without scopes reaches everything the account can; `workspaces` or `projects` limit it, and a scoped key cannot create a broader one. Scoped keys cannot see account billing (HTTP 403 `ACCOUNT_SCOPE_REQUIRED`). `workspaces.delete()` needs an empty workspace unless you pass `force`.
|
|
193
|
+
|
|
194
|
+
## Platform APIs
|
|
195
|
+
|
|
196
|
+
The same client manages the rest of Lizard: `projects`, `services` (deploy, inspect, scale, run commands), `addons` (managed databases), `secrets`, `domains`, `storage` (S3 objects), `github`, `metrics`, `workspaces`, `apiKeys` / `api_keys`, `regions`, `billing` and `volumes`. `whoami()` returns the identity behind the key. `platform` is the raw HTTP client for endpoints without a method. A config change that saved but failed to deploy raises `ConfigApplyError`: read its `result` before retrying.
|
|
197
|
+
|
|
198
|
+
## Billing from code
|
|
199
|
+
|
|
200
|
+
`billing.subscription()` returns the plan, trial and this month's usage; `billing.startCheckout()` / `start_checkout()` returns a Stripe Checkout URL for the trial or Pro, which the user finishes in a browser. `startProNow`, `cancel` and `resume` change what the account pays: call them only when the account owner asks.
|
|
201
|
+
|
|
202
|
+
## Errors
|
|
203
|
+
|
|
204
|
+
| Error | Meaning |
|
|
205
|
+
|---|---|
|
|
206
|
+
| `PaymentRequiredError` | HTTP 402: the account must start Pro, start it now, or pay an invoice. Show `message` as is and open `url`; retry after the user pays |
|
|
207
|
+
| `AuthenticationError` | HTTP 401 or 403: bad key or no access |
|
|
208
|
+
| `NotFoundError` | HTTP 404 |
|
|
209
|
+
| `ConflictError` | HTTP 409, such as a volume name in use |
|
|
210
|
+
| `TimeoutError` | HTTP 408 or 504 |
|
|
211
|
+
| `LizardError` | Other API failures, including unsupported operations |
|
|
212
|
+
|
|
213
|
+
Each error has the HTTP `status` (`status_code` in Python) and, when the API sends one, a `code`. `kill()` returns `false` for a sandbox that is already gone. Check command exit codes and code errors separately from API errors.
|
|
214
|
+
|
|
215
|
+
Full reference: [sandboxes](https://github.com/lizard-build/lizard-sdk/blob/main/docs/sandboxes.md), [platform](https://github.com/lizard-build/lizard-sdk/blob/main/docs/platform.md), [TypeScript](https://github.com/lizard-build/lizard-sdk/blob/main/packages/js/README.md), [Python](https://github.com/lizard-build/lizard-sdk/blob/main/packages/python/README.md).
|