@lizard-build/sdk 0.1.59 → 0.1.61

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
@@ -61,7 +61,7 @@ if (result.exitCode !== 0) throw new Error(result.stderr)
61
61
 
62
62
  ## Desktop (computer use)
63
63
 
64
- The `desktop` template runs XFCE and Chromium you can watch in a browser and an agent can drive:
64
+ The `desktop` template runs an Openbox desktop with Chromium, streamed to a browser over WebRTC (with noVNC as the fallback). You can watch it, and an agent can drive it:
65
65
 
66
66
  ```ts
67
67
  import { Sandbox } from '@lizard-build/sdk'
@@ -108,13 +108,14 @@ const sandbox = await CodeSandbox.create({ projectId: 'proj_123' });
108
108
  try {
109
109
  await sandbox.runCode('x = 21');
110
110
  const run = await sandbox.runCode('x * 2');
111
- console.log(run.results[0].data); // "42"
111
+ const out = run.results[0];
112
+ if (out?.type === 'result') console.log(out.data); // "42"
112
113
  } finally {
113
114
  await sandbox.kill();
114
115
  }
115
116
  ```
116
117
 
117
- `language: 'bash'` (and `'javascript'` where `node` is installed; the `interpreter` template does not ship it) runs each call as a fresh process with no shared state.
118
+ `language: 'bash'` and `language: 'javascript'` (the `interpreter` template ships Node 22) run each call as a fresh process with no shared state.
118
119
 
119
120
  ## Configuration
120
121
 
@@ -124,7 +125,7 @@ Pass `timeoutMs` to `create()` to set the sandbox lifetime. The default is five
124
125
 
125
126
  ## Runtime limits
126
127
 
127
- Each sandbox is a Firecracker microVM with its own kernel. Use volumes mounted at `/workspace` to keep files after a sandbox ends. `pause()`/`resume()` keep memory and running processes; `snapshot()` is ready in about 2 s and `Sandbox.restore()` starts a copy in about 0.4 s; `fork()` clones a running sandbox. File watching and sandbox log streaming are not available on Firecracker sandboxes yet, volumes cannot be resized yet, and a sandbox with a volume attached cannot be forked. See the [snapshot guide](../../docs/sandboxes.md#pause-resume-snapshots-and-fork).
128
+ Each sandbox is a Firecracker microVM with its own kernel. Use volumes mounted at `/workspace` to keep files after a sandbox ends. `pause()`/`resume()` keep memory and running processes; `snapshot()` is ready in about 2 s and `Sandbox.restore()` starts a copy in about 0.4 s; `fork()` clones a running sandbox. File watching and sandbox log streaming are not available on Firecracker sandboxes yet. Volumes can grow but not shrink, and a sandbox with a volume attached cannot be forked. See the [snapshot guide](../../docs/sandboxes.md#pause-resume-snapshots-and-fork).
128
129
 
129
130
  `getHost()` returns a hostname without `https://`; the port is private, so send `sandbox.accessToken` as the `X-Lizard-Access-Token` header (or use `exposePort()`, which also returns a browser `url` carrying the token). File writes accept text or bytes (binary is sent exactly); `readBytes()` supports binary downloads.
130
131
 
package/dist/index.d.mts CHANGED
@@ -145,7 +145,10 @@ declare class ProjectsAPI {
145
145
  }): Promise<Project[]>;
146
146
  /** Get a project by ID. */
147
147
  get(id: string): Promise<Project>;
148
- /** Create a new project. */
148
+ /**
149
+ * Create a new project. A scoped key can create one only in a workspace it
150
+ * covers; anywhere else it gets HTTP 403 with `code: 'PROJECT_OUT_OF_SCOPE'`.
151
+ */
149
152
  create(opts: CreateProjectOpts): Promise<Project>;
150
153
  /** Update a project's name. */
151
154
  update(id: string, opts: {
@@ -671,7 +674,7 @@ declare class Watcher {
671
674
  * State of a sandbox's desktop, and the URLs that stream it.
672
675
  *
673
676
  * **Both URLs are credentials.** The port is published on a public hostname, and each
674
- * URL carries the stream token and a VNC password. Anyone holding `url` can see and
677
+ * URL carries the stream token and a password. Anyone holding `url` can see and
675
678
  * control the desktop; anyone holding `viewOnlyUrl` can watch it. Do not log them or
676
679
  * put them anywhere you would not put an API key.
677
680
  */
@@ -680,9 +683,9 @@ interface DesktopInfo {
680
683
  /** Screen width in pixels, or `null` when the desktop has never been started. */
681
684
  width: number | null;
682
685
  height: number | null;
683
- /** noVNC page with full control (mouse and keyboard). Treat like a credential. */
686
+ /** Viewer page with full control (mouse and keyboard): WebRTC, with noVNC as the fallback. Treat like a credential. */
684
687
  url: string;
685
- /** noVNC page that can only watch; enforced by the VNC server, not the page. */
688
+ /** Viewer page that can only watch; enforced by the stream server in the sandbox, not the page. */
686
689
  viewOnlyUrl: string;
687
690
  }
688
691
  interface DesktopStartOpts {
@@ -702,8 +705,9 @@ interface ClickOpts {
702
705
  /**
703
706
  * A graphical desktop in the sandbox, for computer-use agents and for people to watch.
704
707
  *
705
- * Only the `desktop` template has one (Xvfb + XFCE + Chromium on `DISPLAY=:1`); on
706
- * any other template the calls fail with `code === 'DESKTOP_NOT_SUPPORTED'`.
708
+ * Only the `desktop` template has one (Xvfb + Openbox + Chromium on `DISPLAY=:1`,
709
+ * streamed over WebRTC with noVNC as the fallback); on any other template the calls
710
+ * fail with `code === 'DESKTOP_NOT_SUPPORTED'`.
707
711
  *
708
712
  * Input and screenshots run inside the sandbox with `xdotool` and `scrot`. Every
709
713
  * argument goes to the sandbox as an argv array and is never parsed by a shell, so
@@ -792,9 +796,9 @@ interface SandboxInfo {
792
796
  /** The sandbox runtime: `'firecracker'` (a microVM), `'container'`, or `null` when unknown. */
793
797
  runtime?: 'firecracker' | 'container' | null;
794
798
  pauseError?: string | null;
795
- /** Machine size (`'small' | 'medium' | 'large'`); may be `null` for a sandbox created from a private snapshot. */
799
+ /** Machine size (`'small' | 'medium' | 'large'`); may be `null` for a sandbox billed by measured usage, such as one restored from a container snapshot. */
796
800
  size?: 'small' | 'medium' | 'large' | null;
797
- /** Flat hourly price in USD for `size`, billed per second while running; may be `null` when billed by measured usage (private snapshot). */
801
+ /** Flat hourly price in USD for `size`, billed per second while running; may be `null` when billed by measured usage (a container snapshot). */
798
802
  pricePerHour?: number | null;
799
803
  cpus?: number;
800
804
  memoryMb?: number;
@@ -826,8 +830,8 @@ interface SandboxOpts extends ConnectionOpts {
826
830
  projectId?: string;
827
831
  template?: string;
828
832
  /**
829
- * Create from a private saved snapshot. The sandbox runs on the machine the
830
- * snapshot was captured on and is billed by measured usage; `size` is ignored.
833
+ * Create from a private saved snapshot. The sandbox runs at the source sandbox's
834
+ * size and is billed flat at that size; `size` is ignored.
831
835
  */
832
836
  snapshotId?: string;
833
837
  /**
@@ -1154,9 +1158,10 @@ declare class Sandbox extends SandboxClient {
1154
1158
  */
1155
1159
  exposePort(port: number, opts?: ConnectionOpts): Promise<ExposedPort>;
1156
1160
  /**
1157
- * Clone this running sandbox `count` times (1-10). Each fork is a copy of the
1161
+ * Clone this running sandbox `count` times (1-100). Each fork is a copy of the
1158
1162
  * microVM at this instant -- memory, running processes and files -- and is
1159
- * billed like its source. The source keeps running.
1163
+ * billed like its source. The source keeps running. Each copy counts as a
1164
+ * sandbox start, so the account's running-sandbox limit is the real cap.
1160
1165
  *
1161
1166
  * Returns one entry per requested fork, in order: `{ sandbox, info }`, or
1162
1167
  * `{ error }` for a copy that could not be made (e.g. the account's sandbox
@@ -1420,14 +1425,22 @@ declare class WorkspacesAPI {
1420
1425
  * project. A full key returns every workspace the account belongs to.
1421
1426
  */
1422
1427
  list(): Promise<Workspace[]>;
1423
- /** Create a workspace. The caller becomes its owner. */
1428
+ /**
1429
+ * Create a workspace. The caller becomes its owner.
1430
+ *
1431
+ * Needs a key with no scopes: a scoped key gets HTTP 403 with
1432
+ * `code: 'ACCOUNT_SCOPE_REQUIRED'`.
1433
+ */
1424
1434
  create(opts: CreateWorkspaceOpts): Promise<Workspace>;
1425
1435
  /**
1426
1436
  * Delete a workspace.
1427
1437
  *
1428
- * Empty-only by default — the server refuses while any project or sandbox remains,
1429
- * so this cannot quietly destroy a user's work. Pass `{ force: true }` to delete a
1430
- * workspace and everything in it; that is irreversible.
1438
+ * Empty-only by default — the server refuses while any project, sandbox or volume
1439
+ * remains, so this cannot quietly destroy a user's work. `{ force: true }` deletes
1440
+ * the workspace with its apps, addons and projects; that is irreversible. It does not
1441
+ * delete sandboxes, sandbox volumes or snapshots: a running sandbox keeps running with
1442
+ * no workspace left to find it in. Kill the workspace's sandboxes and delete its
1443
+ * volumes and snapshots first.
1431
1444
  */
1432
1445
  delete(id: string, opts?: {
1433
1446
  force?: boolean;
@@ -1842,9 +1855,9 @@ declare class Volume {
1842
1855
  /**
1843
1856
  * Resize a volume, by name or by id, to `sizeGb`.
1844
1857
  *
1845
- * **Firecracker volumes cannot be resized yet**: volumes created for the default
1846
- * Firecracker sandboxes throw `LizardError` (400) with `code: 'volume_not_resizable'`.
1847
- * Create the volume at the size you need, or copy the data to a new, larger one.
1858
+ * A Firecracker volume (the default) can only grow. The change is in place and
1859
+ * online: a sandbox with the volume attached keeps running and sees the new size.
1860
+ * A smaller size throws `LizardError` (400) with `code: 'volume_shrink_unsupported'`.
1848
1861
  *
1849
1862
  * On a container volume the change is in place and online: the size is a quota,
1850
1863
  * so no data is copied and a mounted sandbox sees the new size immediately. Both
@@ -1852,16 +1865,17 @@ declare class Volume {
1852
1865
  * new size free — otherwise it throws `ConflictError` with
1853
1866
  * `code: 'volume_too_full_to_shrink'`.
1854
1867
  *
1855
- * Other failures carry a `code` too: `invalid_volume_size`, `volume_not_resizable`,
1868
+ * Other failures carry a `code` too: `invalid_volume_size`, `volume_too_large`,
1869
+ * `volume_not_resizable` (an app's volume, not a sandbox volume),
1856
1870
  * `volume_resize_in_progress`, `volume_not_provisioned`,
1857
1871
  * `volume_capacity_unavailable`, and `volume_resize_timeout` (a `TimeoutError`;
1858
1872
  * the size is left unchanged).
1859
1873
  *
1860
- * @returns The volume's updated info, including `sizeEnforced`.
1874
+ * @returns The volume's updated info, including `sizeEnforced` for a container volume.
1861
1875
  */
1862
1876
  static resize(projectId: string, nameOrId: string, sizeGb: number, opts?: ConnectionOpts): Promise<VolumeInfo>;
1863
1877
  getInfo(projectId: string): Promise<VolumeInfo>;
1864
- /** Resize this volume — see the static {@link Volume.resize} (not supported for Firecracker volumes yet). */
1878
+ /** Resize this volume — see the static {@link Volume.resize} (a Firecracker volume can only grow). */
1865
1879
  resize(projectId: string, sizeGb: number): Promise<VolumeInfo>;
1866
1880
  delete(projectId: string): Promise<void>;
1867
1881
  }
@@ -1968,11 +1982,11 @@ declare class Lizard {
1968
1982
  /**
1969
1983
  * The account this credential belongs to — the SDK's `lizard whoami`.
1970
1984
  *
1971
- * A **scoped** key gets identity only: `id`, `username`, `avatarUrl`, `scoped: true`
1972
- * and the key's own `scopes`. The account's email, balance, plan and billing status
1973
- * are withheld — they belong to the account, not to the key holder, and a scoped key
1974
- * is meant to be handed to an end user or injected into a sandbox. An unscoped key
1975
- * or a session sees the full account.
1985
+ * A **scoped** key never learns whose account it is: it gets only `scoped: true` and
1986
+ * its `scopes`, each project or workspace with its name. No id, username, avatar,
1987
+ * email, balance or plan -- a scoped key is meant to be handed to someone who is not
1988
+ * the account's member, or injected into a sandbox. An unscoped key or a session
1989
+ * sees the full account.
1976
1990
  *
1977
1991
  * Reading back `scopes` is the cheapest way to answer "what can this key reach".
1978
1992
  */
@@ -1981,21 +1995,27 @@ declare class Lizard {
1981
1995
  get platform(): PlatformClient;
1982
1996
  }
1983
1997
  /**
1984
- * The account behind a credential — see {@link Lizard.whoami}.
1998
+ * The account behind a credential -- see {@link Lizard.whoami}.
1985
1999
  *
1986
- * Everything past `avatarUrl` is present only for an unscoped key or a session; a
1987
- * scoped key gets `scoped: true` and `scopes` in their place.
2000
+ * A scoped key gets only `scoped: true` and `scopes`; every other field is present only
2001
+ * for an unscoped key or a session.
1988
2002
  */
1989
2003
  interface Account {
1990
- id: string;
1991
- username: string;
2004
+ /** Absent for a scoped key. */
2005
+ id?: string;
2006
+ /** Absent for a scoped key. */
2007
+ username?: string;
1992
2008
  avatarUrl?: string | null;
1993
- /** True when the calling key is scoped, meaning the account fields below are absent. */
2009
+ /** True when the calling key is scoped, meaning the account fields are absent. */
1994
2010
  scoped?: boolean;
1995
- /** What the calling key may reach. Present when `scoped` is true. */
2011
+ /** What the calling key may reach, by name. Present when `scoped` is true. */
1996
2012
  scopes?: Array<{
1997
2013
  type: 'workspace' | 'project';
1998
2014
  id: string;
2015
+ name?: string | null;
2016
+ /** For a project scope: the workspace it belongs to. */
2017
+ workspaceId?: string | null;
2018
+ workspaceName?: string | null;
1999
2019
  }>;
2000
2020
  email?: string | null;
2001
2021
  plan?: string;
@@ -2154,7 +2174,7 @@ interface CreateContextOpts {
2154
2174
  * Limits, honestly:
2155
2175
  * - `javascript` (node) and `bash` run each call as a fresh process: no state
2156
2176
  * carries over and `results` stays empty. `node` must be installed in the
2157
- * template; the `interpreter` template does not ship it.
2177
+ * template; the `interpreter` template ships Node 22.
2158
2178
  * - On a template without Jupyter (e.g. `base`), Python also runs as a fresh
2159
2179
  * process per call.
2160
2180
  * - `stdout`/`stderr` are delivered in the chunks the kernel or process
@@ -2168,7 +2188,8 @@ interface CreateContextOpts {
2168
2188
  * const sandbox = await CodeSandbox.create({ project: 'my-project' })
2169
2189
  * await sandbox.runCode('x = 21')
2170
2190
  * const run = await sandbox.runCode('x * 2')
2171
- * console.log(run.results[0].data) // "42"
2191
+ * const out = run.results[0]
2192
+ * if (out?.type === 'result') console.log(out.data) // "42"
2172
2193
  * await sandbox.kill()
2173
2194
  * ```
2174
2195
  */
package/dist/index.d.ts CHANGED
@@ -145,7 +145,10 @@ declare class ProjectsAPI {
145
145
  }): Promise<Project[]>;
146
146
  /** Get a project by ID. */
147
147
  get(id: string): Promise<Project>;
148
- /** Create a new project. */
148
+ /**
149
+ * Create a new project. A scoped key can create one only in a workspace it
150
+ * covers; anywhere else it gets HTTP 403 with `code: 'PROJECT_OUT_OF_SCOPE'`.
151
+ */
149
152
  create(opts: CreateProjectOpts): Promise<Project>;
150
153
  /** Update a project's name. */
151
154
  update(id: string, opts: {
@@ -671,7 +674,7 @@ declare class Watcher {
671
674
  * State of a sandbox's desktop, and the URLs that stream it.
672
675
  *
673
676
  * **Both URLs are credentials.** The port is published on a public hostname, and each
674
- * URL carries the stream token and a VNC password. Anyone holding `url` can see and
677
+ * URL carries the stream token and a password. Anyone holding `url` can see and
675
678
  * control the desktop; anyone holding `viewOnlyUrl` can watch it. Do not log them or
676
679
  * put them anywhere you would not put an API key.
677
680
  */
@@ -680,9 +683,9 @@ interface DesktopInfo {
680
683
  /** Screen width in pixels, or `null` when the desktop has never been started. */
681
684
  width: number | null;
682
685
  height: number | null;
683
- /** noVNC page with full control (mouse and keyboard). Treat like a credential. */
686
+ /** Viewer page with full control (mouse and keyboard): WebRTC, with noVNC as the fallback. Treat like a credential. */
684
687
  url: string;
685
- /** noVNC page that can only watch; enforced by the VNC server, not the page. */
688
+ /** Viewer page that can only watch; enforced by the stream server in the sandbox, not the page. */
686
689
  viewOnlyUrl: string;
687
690
  }
688
691
  interface DesktopStartOpts {
@@ -702,8 +705,9 @@ interface ClickOpts {
702
705
  /**
703
706
  * A graphical desktop in the sandbox, for computer-use agents and for people to watch.
704
707
  *
705
- * Only the `desktop` template has one (Xvfb + XFCE + Chromium on `DISPLAY=:1`); on
706
- * any other template the calls fail with `code === 'DESKTOP_NOT_SUPPORTED'`.
708
+ * Only the `desktop` template has one (Xvfb + Openbox + Chromium on `DISPLAY=:1`,
709
+ * streamed over WebRTC with noVNC as the fallback); on any other template the calls
710
+ * fail with `code === 'DESKTOP_NOT_SUPPORTED'`.
707
711
  *
708
712
  * Input and screenshots run inside the sandbox with `xdotool` and `scrot`. Every
709
713
  * argument goes to the sandbox as an argv array and is never parsed by a shell, so
@@ -792,9 +796,9 @@ interface SandboxInfo {
792
796
  /** The sandbox runtime: `'firecracker'` (a microVM), `'container'`, or `null` when unknown. */
793
797
  runtime?: 'firecracker' | 'container' | null;
794
798
  pauseError?: string | null;
795
- /** Machine size (`'small' | 'medium' | 'large'`); may be `null` for a sandbox created from a private snapshot. */
799
+ /** Machine size (`'small' | 'medium' | 'large'`); may be `null` for a sandbox billed by measured usage, such as one restored from a container snapshot. */
796
800
  size?: 'small' | 'medium' | 'large' | null;
797
- /** Flat hourly price in USD for `size`, billed per second while running; may be `null` when billed by measured usage (private snapshot). */
801
+ /** Flat hourly price in USD for `size`, billed per second while running; may be `null` when billed by measured usage (a container snapshot). */
798
802
  pricePerHour?: number | null;
799
803
  cpus?: number;
800
804
  memoryMb?: number;
@@ -826,8 +830,8 @@ interface SandboxOpts extends ConnectionOpts {
826
830
  projectId?: string;
827
831
  template?: string;
828
832
  /**
829
- * Create from a private saved snapshot. The sandbox runs on the machine the
830
- * snapshot was captured on and is billed by measured usage; `size` is ignored.
833
+ * Create from a private saved snapshot. The sandbox runs at the source sandbox's
834
+ * size and is billed flat at that size; `size` is ignored.
831
835
  */
832
836
  snapshotId?: string;
833
837
  /**
@@ -1154,9 +1158,10 @@ declare class Sandbox extends SandboxClient {
1154
1158
  */
1155
1159
  exposePort(port: number, opts?: ConnectionOpts): Promise<ExposedPort>;
1156
1160
  /**
1157
- * Clone this running sandbox `count` times (1-10). Each fork is a copy of the
1161
+ * Clone this running sandbox `count` times (1-100). Each fork is a copy of the
1158
1162
  * microVM at this instant -- memory, running processes and files -- and is
1159
- * billed like its source. The source keeps running.
1163
+ * billed like its source. The source keeps running. Each copy counts as a
1164
+ * sandbox start, so the account's running-sandbox limit is the real cap.
1160
1165
  *
1161
1166
  * Returns one entry per requested fork, in order: `{ sandbox, info }`, or
1162
1167
  * `{ error }` for a copy that could not be made (e.g. the account's sandbox
@@ -1420,14 +1425,22 @@ declare class WorkspacesAPI {
1420
1425
  * project. A full key returns every workspace the account belongs to.
1421
1426
  */
1422
1427
  list(): Promise<Workspace[]>;
1423
- /** Create a workspace. The caller becomes its owner. */
1428
+ /**
1429
+ * Create a workspace. The caller becomes its owner.
1430
+ *
1431
+ * Needs a key with no scopes: a scoped key gets HTTP 403 with
1432
+ * `code: 'ACCOUNT_SCOPE_REQUIRED'`.
1433
+ */
1424
1434
  create(opts: CreateWorkspaceOpts): Promise<Workspace>;
1425
1435
  /**
1426
1436
  * Delete a workspace.
1427
1437
  *
1428
- * Empty-only by default — the server refuses while any project or sandbox remains,
1429
- * so this cannot quietly destroy a user's work. Pass `{ force: true }` to delete a
1430
- * workspace and everything in it; that is irreversible.
1438
+ * Empty-only by default — the server refuses while any project, sandbox or volume
1439
+ * remains, so this cannot quietly destroy a user's work. `{ force: true }` deletes
1440
+ * the workspace with its apps, addons and projects; that is irreversible. It does not
1441
+ * delete sandboxes, sandbox volumes or snapshots: a running sandbox keeps running with
1442
+ * no workspace left to find it in. Kill the workspace's sandboxes and delete its
1443
+ * volumes and snapshots first.
1431
1444
  */
1432
1445
  delete(id: string, opts?: {
1433
1446
  force?: boolean;
@@ -1842,9 +1855,9 @@ declare class Volume {
1842
1855
  /**
1843
1856
  * Resize a volume, by name or by id, to `sizeGb`.
1844
1857
  *
1845
- * **Firecracker volumes cannot be resized yet**: volumes created for the default
1846
- * Firecracker sandboxes throw `LizardError` (400) with `code: 'volume_not_resizable'`.
1847
- * Create the volume at the size you need, or copy the data to a new, larger one.
1858
+ * A Firecracker volume (the default) can only grow. The change is in place and
1859
+ * online: a sandbox with the volume attached keeps running and sees the new size.
1860
+ * A smaller size throws `LizardError` (400) with `code: 'volume_shrink_unsupported'`.
1848
1861
  *
1849
1862
  * On a container volume the change is in place and online: the size is a quota,
1850
1863
  * so no data is copied and a mounted sandbox sees the new size immediately. Both
@@ -1852,16 +1865,17 @@ declare class Volume {
1852
1865
  * new size free — otherwise it throws `ConflictError` with
1853
1866
  * `code: 'volume_too_full_to_shrink'`.
1854
1867
  *
1855
- * Other failures carry a `code` too: `invalid_volume_size`, `volume_not_resizable`,
1868
+ * Other failures carry a `code` too: `invalid_volume_size`, `volume_too_large`,
1869
+ * `volume_not_resizable` (an app's volume, not a sandbox volume),
1856
1870
  * `volume_resize_in_progress`, `volume_not_provisioned`,
1857
1871
  * `volume_capacity_unavailable`, and `volume_resize_timeout` (a `TimeoutError`;
1858
1872
  * the size is left unchanged).
1859
1873
  *
1860
- * @returns The volume's updated info, including `sizeEnforced`.
1874
+ * @returns The volume's updated info, including `sizeEnforced` for a container volume.
1861
1875
  */
1862
1876
  static resize(projectId: string, nameOrId: string, sizeGb: number, opts?: ConnectionOpts): Promise<VolumeInfo>;
1863
1877
  getInfo(projectId: string): Promise<VolumeInfo>;
1864
- /** Resize this volume — see the static {@link Volume.resize} (not supported for Firecracker volumes yet). */
1878
+ /** Resize this volume — see the static {@link Volume.resize} (a Firecracker volume can only grow). */
1865
1879
  resize(projectId: string, sizeGb: number): Promise<VolumeInfo>;
1866
1880
  delete(projectId: string): Promise<void>;
1867
1881
  }
@@ -1968,11 +1982,11 @@ declare class Lizard {
1968
1982
  /**
1969
1983
  * The account this credential belongs to — the SDK's `lizard whoami`.
1970
1984
  *
1971
- * A **scoped** key gets identity only: `id`, `username`, `avatarUrl`, `scoped: true`
1972
- * and the key's own `scopes`. The account's email, balance, plan and billing status
1973
- * are withheld — they belong to the account, not to the key holder, and a scoped key
1974
- * is meant to be handed to an end user or injected into a sandbox. An unscoped key
1975
- * or a session sees the full account.
1985
+ * A **scoped** key never learns whose account it is: it gets only `scoped: true` and
1986
+ * its `scopes`, each project or workspace with its name. No id, username, avatar,
1987
+ * email, balance or plan -- a scoped key is meant to be handed to someone who is not
1988
+ * the account's member, or injected into a sandbox. An unscoped key or a session
1989
+ * sees the full account.
1976
1990
  *
1977
1991
  * Reading back `scopes` is the cheapest way to answer "what can this key reach".
1978
1992
  */
@@ -1981,21 +1995,27 @@ declare class Lizard {
1981
1995
  get platform(): PlatformClient;
1982
1996
  }
1983
1997
  /**
1984
- * The account behind a credential — see {@link Lizard.whoami}.
1998
+ * The account behind a credential -- see {@link Lizard.whoami}.
1985
1999
  *
1986
- * Everything past `avatarUrl` is present only for an unscoped key or a session; a
1987
- * scoped key gets `scoped: true` and `scopes` in their place.
2000
+ * A scoped key gets only `scoped: true` and `scopes`; every other field is present only
2001
+ * for an unscoped key or a session.
1988
2002
  */
1989
2003
  interface Account {
1990
- id: string;
1991
- username: string;
2004
+ /** Absent for a scoped key. */
2005
+ id?: string;
2006
+ /** Absent for a scoped key. */
2007
+ username?: string;
1992
2008
  avatarUrl?: string | null;
1993
- /** True when the calling key is scoped, meaning the account fields below are absent. */
2009
+ /** True when the calling key is scoped, meaning the account fields are absent. */
1994
2010
  scoped?: boolean;
1995
- /** What the calling key may reach. Present when `scoped` is true. */
2011
+ /** What the calling key may reach, by name. Present when `scoped` is true. */
1996
2012
  scopes?: Array<{
1997
2013
  type: 'workspace' | 'project';
1998
2014
  id: string;
2015
+ name?: string | null;
2016
+ /** For a project scope: the workspace it belongs to. */
2017
+ workspaceId?: string | null;
2018
+ workspaceName?: string | null;
1999
2019
  }>;
2000
2020
  email?: string | null;
2001
2021
  plan?: string;
@@ -2154,7 +2174,7 @@ interface CreateContextOpts {
2154
2174
  * Limits, honestly:
2155
2175
  * - `javascript` (node) and `bash` run each call as a fresh process: no state
2156
2176
  * carries over and `results` stays empty. `node` must be installed in the
2157
- * template; the `interpreter` template does not ship it.
2177
+ * template; the `interpreter` template ships Node 22.
2158
2178
  * - On a template without Jupyter (e.g. `base`), Python also runs as a fresh
2159
2179
  * process per call.
2160
2180
  * - `stdout`/`stderr` are delivered in the chunks the kernel or process
@@ -2168,7 +2188,8 @@ interface CreateContextOpts {
2168
2188
  * const sandbox = await CodeSandbox.create({ project: 'my-project' })
2169
2189
  * await sandbox.runCode('x = 21')
2170
2190
  * const run = await sandbox.runCode('x * 2')
2171
- * console.log(run.results[0].data) // "42"
2191
+ * const out = run.results[0]
2192
+ * if (out?.type === 'result') console.log(out.data) // "42"
2172
2193
  * await sandbox.kill()
2173
2194
  * ```
2174
2195
  */
package/dist/index.js CHANGED
@@ -1258,9 +1258,10 @@ var Sandbox = class extends SandboxClient {
1258
1258
  return exposed;
1259
1259
  }
1260
1260
  /**
1261
- * Clone this running sandbox `count` times (1-10). Each fork is a copy of the
1261
+ * Clone this running sandbox `count` times (1-100). Each fork is a copy of the
1262
1262
  * microVM at this instant -- memory, running processes and files -- and is
1263
- * billed like its source. The source keeps running.
1263
+ * billed like its source. The source keeps running. Each copy counts as a
1264
+ * sandbox start, so the account's running-sandbox limit is the real cap.
1264
1265
  *
1265
1266
  * Returns one entry per requested fork, in order: `{ sandbox, info }`, or
1266
1267
  * `{ error }` for a copy that could not be made (e.g. the account's sandbox
@@ -1381,7 +1382,10 @@ var ProjectsAPI = class {
1381
1382
  get(id) {
1382
1383
  return this.client.get(`/api/projects/${id}`);
1383
1384
  }
1384
- /** Create a new project. */
1385
+ /**
1386
+ * Create a new project. A scoped key can create one only in a workspace it
1387
+ * covers; anywhere else it gets HTTP 403 with `code: 'PROJECT_OUT_OF_SCOPE'`.
1388
+ */
1385
1389
  create(opts) {
1386
1390
  return this.client.post("/api/projects", opts);
1387
1391
  }
@@ -1650,16 +1654,24 @@ var WorkspacesAPI = class {
1650
1654
  list() {
1651
1655
  return this.client.get("/api/workspaces");
1652
1656
  }
1653
- /** Create a workspace. The caller becomes its owner. */
1657
+ /**
1658
+ * Create a workspace. The caller becomes its owner.
1659
+ *
1660
+ * Needs a key with no scopes: a scoped key gets HTTP 403 with
1661
+ * `code: 'ACCOUNT_SCOPE_REQUIRED'`.
1662
+ */
1654
1663
  create(opts) {
1655
1664
  return this.client.post("/api/workspaces", opts);
1656
1665
  }
1657
1666
  /**
1658
1667
  * Delete a workspace.
1659
1668
  *
1660
- * Empty-only by default — the server refuses while any project or sandbox remains,
1661
- * so this cannot quietly destroy a user's work. Pass `{ force: true }` to delete a
1662
- * workspace and everything in it; that is irreversible.
1669
+ * Empty-only by default — the server refuses while any project, sandbox or volume
1670
+ * remains, so this cannot quietly destroy a user's work. `{ force: true }` deletes
1671
+ * the workspace with its apps, addons and projects; that is irreversible. It does not
1672
+ * delete sandboxes, sandbox volumes or snapshots: a running sandbox keeps running with
1673
+ * no workspace left to find it in. Kill the workspace's sandboxes and delete its
1674
+ * volumes and snapshots first.
1663
1675
  */
1664
1676
  delete(id, opts) {
1665
1677
  const qs = opts?.force ? "" : "?requireEmpty=true";
@@ -1853,9 +1865,9 @@ var Volume = class _Volume {
1853
1865
  /**
1854
1866
  * Resize a volume, by name or by id, to `sizeGb`.
1855
1867
  *
1856
- * **Firecracker volumes cannot be resized yet**: volumes created for the default
1857
- * Firecracker sandboxes throw `LizardError` (400) with `code: 'volume_not_resizable'`.
1858
- * Create the volume at the size you need, or copy the data to a new, larger one.
1868
+ * A Firecracker volume (the default) can only grow. The change is in place and
1869
+ * online: a sandbox with the volume attached keeps running and sees the new size.
1870
+ * A smaller size throws `LizardError` (400) with `code: 'volume_shrink_unsupported'`.
1859
1871
  *
1860
1872
  * On a container volume the change is in place and online: the size is a quota,
1861
1873
  * so no data is copied and a mounted sandbox sees the new size immediately. Both
@@ -1863,12 +1875,13 @@ var Volume = class _Volume {
1863
1875
  * new size free — otherwise it throws `ConflictError` with
1864
1876
  * `code: 'volume_too_full_to_shrink'`.
1865
1877
  *
1866
- * Other failures carry a `code` too: `invalid_volume_size`, `volume_not_resizable`,
1878
+ * Other failures carry a `code` too: `invalid_volume_size`, `volume_too_large`,
1879
+ * `volume_not_resizable` (an app's volume, not a sandbox volume),
1867
1880
  * `volume_resize_in_progress`, `volume_not_provisioned`,
1868
1881
  * `volume_capacity_unavailable`, and `volume_resize_timeout` (a `TimeoutError`;
1869
1882
  * the size is left unchanged).
1870
1883
  *
1871
- * @returns The volume's updated info, including `sizeEnforced`.
1884
+ * @returns The volume's updated info, including `sizeEnforced` for a container volume.
1872
1885
  */
1873
1886
  static async resize(projectId, nameOrId, sizeGb, opts) {
1874
1887
  const config = new ConnectionConfig(opts);
@@ -1887,7 +1900,7 @@ var Volume = class _Volume {
1887
1900
  if (!res.ok) await handleApiError(res);
1888
1901
  return res.json();
1889
1902
  }
1890
- /** Resize this volume — see the static {@link Volume.resize} (not supported for Firecracker volumes yet). */
1903
+ /** Resize this volume — see the static {@link Volume.resize} (a Firecracker volume can only grow). */
1891
1904
  async resize(projectId, sizeGb) {
1892
1905
  return _Volume.resize(projectId, this.volumeId, sizeGb, {
1893
1906
  apiKey: this.config.apiKey,
@@ -1977,11 +1990,11 @@ var Lizard = class {
1977
1990
  /**
1978
1991
  * The account this credential belongs to — the SDK's `lizard whoami`.
1979
1992
  *
1980
- * A **scoped** key gets identity only: `id`, `username`, `avatarUrl`, `scoped: true`
1981
- * and the key's own `scopes`. The account's email, balance, plan and billing status
1982
- * are withheld — they belong to the account, not to the key holder, and a scoped key
1983
- * is meant to be handed to an end user or injected into a sandbox. An unscoped key
1984
- * or a session sees the full account.
1993
+ * A **scoped** key never learns whose account it is: it gets only `scoped: true` and
1994
+ * its `scopes`, each project or workspace with its name. No id, username, avatar,
1995
+ * email, balance or plan -- a scoped key is meant to be handed to someone who is not
1996
+ * the account's member, or injected into a sandbox. An unscoped key or a session
1997
+ * sees the full account.
1985
1998
  *
1986
1999
  * Reading back `scopes` is the cheapest way to answer "what can this key reach".
1987
2000
  */
package/dist/index.mjs CHANGED
@@ -1205,9 +1205,10 @@ var Sandbox = class extends SandboxClient {
1205
1205
  return exposed;
1206
1206
  }
1207
1207
  /**
1208
- * Clone this running sandbox `count` times (1-10). Each fork is a copy of the
1208
+ * Clone this running sandbox `count` times (1-100). Each fork is a copy of the
1209
1209
  * microVM at this instant -- memory, running processes and files -- and is
1210
- * billed like its source. The source keeps running.
1210
+ * billed like its source. The source keeps running. Each copy counts as a
1211
+ * sandbox start, so the account's running-sandbox limit is the real cap.
1211
1212
  *
1212
1213
  * Returns one entry per requested fork, in order: `{ sandbox, info }`, or
1213
1214
  * `{ error }` for a copy that could not be made (e.g. the account's sandbox
@@ -1328,7 +1329,10 @@ var ProjectsAPI = class {
1328
1329
  get(id) {
1329
1330
  return this.client.get(`/api/projects/${id}`);
1330
1331
  }
1331
- /** Create a new project. */
1332
+ /**
1333
+ * Create a new project. A scoped key can create one only in a workspace it
1334
+ * covers; anywhere else it gets HTTP 403 with `code: 'PROJECT_OUT_OF_SCOPE'`.
1335
+ */
1332
1336
  create(opts) {
1333
1337
  return this.client.post("/api/projects", opts);
1334
1338
  }
@@ -1597,16 +1601,24 @@ var WorkspacesAPI = class {
1597
1601
  list() {
1598
1602
  return this.client.get("/api/workspaces");
1599
1603
  }
1600
- /** Create a workspace. The caller becomes its owner. */
1604
+ /**
1605
+ * Create a workspace. The caller becomes its owner.
1606
+ *
1607
+ * Needs a key with no scopes: a scoped key gets HTTP 403 with
1608
+ * `code: 'ACCOUNT_SCOPE_REQUIRED'`.
1609
+ */
1601
1610
  create(opts) {
1602
1611
  return this.client.post("/api/workspaces", opts);
1603
1612
  }
1604
1613
  /**
1605
1614
  * Delete a workspace.
1606
1615
  *
1607
- * Empty-only by default — the server refuses while any project or sandbox remains,
1608
- * so this cannot quietly destroy a user's work. Pass `{ force: true }` to delete a
1609
- * workspace and everything in it; that is irreversible.
1616
+ * Empty-only by default — the server refuses while any project, sandbox or volume
1617
+ * remains, so this cannot quietly destroy a user's work. `{ force: true }` deletes
1618
+ * the workspace with its apps, addons and projects; that is irreversible. It does not
1619
+ * delete sandboxes, sandbox volumes or snapshots: a running sandbox keeps running with
1620
+ * no workspace left to find it in. Kill the workspace's sandboxes and delete its
1621
+ * volumes and snapshots first.
1610
1622
  */
1611
1623
  delete(id, opts) {
1612
1624
  const qs = opts?.force ? "" : "?requireEmpty=true";
@@ -1800,9 +1812,9 @@ var Volume = class _Volume {
1800
1812
  /**
1801
1813
  * Resize a volume, by name or by id, to `sizeGb`.
1802
1814
  *
1803
- * **Firecracker volumes cannot be resized yet**: volumes created for the default
1804
- * Firecracker sandboxes throw `LizardError` (400) with `code: 'volume_not_resizable'`.
1805
- * Create the volume at the size you need, or copy the data to a new, larger one.
1815
+ * A Firecracker volume (the default) can only grow. The change is in place and
1816
+ * online: a sandbox with the volume attached keeps running and sees the new size.
1817
+ * A smaller size throws `LizardError` (400) with `code: 'volume_shrink_unsupported'`.
1806
1818
  *
1807
1819
  * On a container volume the change is in place and online: the size is a quota,
1808
1820
  * so no data is copied and a mounted sandbox sees the new size immediately. Both
@@ -1810,12 +1822,13 @@ var Volume = class _Volume {
1810
1822
  * new size free — otherwise it throws `ConflictError` with
1811
1823
  * `code: 'volume_too_full_to_shrink'`.
1812
1824
  *
1813
- * Other failures carry a `code` too: `invalid_volume_size`, `volume_not_resizable`,
1825
+ * Other failures carry a `code` too: `invalid_volume_size`, `volume_too_large`,
1826
+ * `volume_not_resizable` (an app's volume, not a sandbox volume),
1814
1827
  * `volume_resize_in_progress`, `volume_not_provisioned`,
1815
1828
  * `volume_capacity_unavailable`, and `volume_resize_timeout` (a `TimeoutError`;
1816
1829
  * the size is left unchanged).
1817
1830
  *
1818
- * @returns The volume's updated info, including `sizeEnforced`.
1831
+ * @returns The volume's updated info, including `sizeEnforced` for a container volume.
1819
1832
  */
1820
1833
  static async resize(projectId, nameOrId, sizeGb, opts) {
1821
1834
  const config = new ConnectionConfig(opts);
@@ -1834,7 +1847,7 @@ var Volume = class _Volume {
1834
1847
  if (!res.ok) await handleApiError(res);
1835
1848
  return res.json();
1836
1849
  }
1837
- /** Resize this volume — see the static {@link Volume.resize} (not supported for Firecracker volumes yet). */
1850
+ /** Resize this volume — see the static {@link Volume.resize} (a Firecracker volume can only grow). */
1838
1851
  async resize(projectId, sizeGb) {
1839
1852
  return _Volume.resize(projectId, this.volumeId, sizeGb, {
1840
1853
  apiKey: this.config.apiKey,
@@ -1924,11 +1937,11 @@ var Lizard = class {
1924
1937
  /**
1925
1938
  * The account this credential belongs to — the SDK's `lizard whoami`.
1926
1939
  *
1927
- * A **scoped** key gets identity only: `id`, `username`, `avatarUrl`, `scoped: true`
1928
- * and the key's own `scopes`. The account's email, balance, plan and billing status
1929
- * are withheld — they belong to the account, not to the key holder, and a scoped key
1930
- * is meant to be handed to an end user or injected into a sandbox. An unscoped key
1931
- * or a session sees the full account.
1940
+ * A **scoped** key never learns whose account it is: it gets only `scoped: true` and
1941
+ * its `scopes`, each project or workspace with its name. No id, username, avatar,
1942
+ * email, balance or plan -- a scoped key is meant to be handed to someone who is not
1943
+ * the account's member, or injected into a sandbox. An unscoped key or a session
1944
+ * sees the full account.
1932
1945
  *
1933
1946
  * Reading back `scopes` is the cheapest way to answer "what can this key reach".
1934
1947
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lizard-build/sdk",
3
- "version": "0.1.59",
3
+ "version": "0.1.61",
4
4
  "description": "TypeScript SDK for Lizard: Firecracker microVM sandboxes, code execution and cloud apps.",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",