@lizard-build/sdk 0.1.19 → 0.1.21

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
@@ -19,10 +19,15 @@ pip install lizard-sdk
19
19
  ### JavaScript / TypeScript
20
20
 
21
21
  ```ts
22
- import { Sandbox } from '@lizard-build/sdk'
22
+ import { Lizard } from '@lizard-build/sdk'
23
+
24
+ // A client is pinned to one project — sandboxes are billed per project, so a
25
+ // project is required. It can be the project's ID, slug, or name.
26
+ // apiKey defaults to the LIZARD_API_KEY env var.
27
+ const lizard = new Lizard({ project: 'my-project' })
23
28
 
24
29
  // Boot a microVM from the 'base' template (Debian + Node.js 20)
25
- const sandbox = await Sandbox.create('base')
30
+ const sandbox = await lizard.create('base')
26
31
 
27
32
  // Write a file directly into the microVM filesystem
28
33
  await sandbox.fs.write('/app/server.js', `
@@ -44,10 +49,14 @@ await sandbox.kill()
44
49
  ### Python
45
50
 
46
51
  ```python
47
- from lizard import Sandbox
52
+ from lizard import Lizard
53
+
54
+ # A client is pinned to one project — sandboxes are billed per project, so a
55
+ # project is required (its ID, slug, or name). api_key defaults to LIZARD_API_KEY.
56
+ lizard = Lizard(project="my-project")
48
57
 
49
58
  # Boot a Python microVM from the 'code-interpreter-v1' template
50
- sandbox = Sandbox.create("code-interpreter-v1")
59
+ sandbox = lizard.create("code-interpreter-v1")
51
60
 
52
61
  # Write a script into the microVM filesystem
53
62
  sandbox.fs.write("/app/main.py", """
@@ -76,29 +85,28 @@ sandbox.kill()
76
85
  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
86
 
78
87
  ```ts
79
- // Boot and set up the environment once
80
- const sandbox = await Sandbox.create('code-interpreter-v1')
88
+ // Boot and set up the environment once (reusing the `lizard` client from above)
89
+ const sandbox = await lizard.create('code-interpreter-v1')
81
90
  await sandbox.process.exec('pip install numpy pandas scikit-learn')
82
91
  const id = sandbox.sandboxId
83
92
  await sandbox.pause()
84
93
 
85
94
  // Later — resume instantly from the snapshot (no reinstall needed)
86
- const resumed = await Sandbox.connect(id)
95
+ const resumed = await lizard.connect(id)
87
96
  const result = await resumed.process.exec('python -c "import sklearn; print(sklearn.__version__)"')
88
97
  console.log(result.stdout)
89
98
  await resumed.kill()
90
99
  ```
91
100
 
92
101
  ```python
93
- from lizard import Sandbox
94
-
95
- sandbox = Sandbox.create("code-interpreter-v1")
102
+ # reusing the `lizard` client from above
103
+ sandbox = lizard.create("code-interpreter-v1")
96
104
  sandbox.process.exec_("pip install numpy pandas scikit-learn")
97
105
  sandbox_id = sandbox.sandbox_id
98
106
  sandbox.pause()
99
107
 
100
108
  # Resume later — environment is exactly as left
101
- resumed = Sandbox.connect(sandbox_id)
109
+ resumed = lizard.connect(sandbox_id)
102
110
  result = resumed.process.exec_("python -c 'import sklearn; print(sklearn.__version__)'")
103
111
  print(result.stdout)
104
112
  resumed.kill()
@@ -106,13 +114,23 @@ resumed.kill()
106
114
 
107
115
  ## API
108
116
 
117
+ ### `new Lizard({ project, apiKey?, apiUrl?, timeoutMs? })`
118
+
119
+ Create a client pinned to a project. Every sandbox is billed per project, so `project` is required — pass its ID, slug, or name (resolved to an ID on first use and cached). `apiKey` defaults to the `LIZARD_API_KEY` env var.
120
+
121
+ ```ts
122
+ const lizard = new Lizard({ project: 'my-project' })
123
+ const sandbox = await lizard.create('base')
124
+ const sandbox = await lizard.create('code-interpreter-v1', { timeoutMs: 10 * 60 * 1000 })
125
+ ```
126
+
109
127
  ### `Sandbox.create(template?, opts?)`
110
128
 
111
- Boot a new Lizard microVM. Built-in templates: `base` (Debian + Node.js 20) and `code-interpreter-v1` (Python 3.11 + Node.js 20). Custom templates can be pushed via `lizard push`.
129
+ Boot a new Lizard microVM. Built-in templates: `base` (Debian + Node.js 20) and `code-interpreter-v1` (Python 3.11 + Node.js 20). Custom templates can be pushed via `lizard push`. A project is required — pass `project` (ID, slug, or name) or an exact `projectId` in `opts`, or use a `Lizard` client, which pins one for you.
112
130
 
113
131
  ```ts
114
- const sandbox = await Sandbox.create('base')
115
- const sandbox = await Sandbox.create('code-interpreter-v1', { timeoutMs: 10 * 60 * 1000 })
132
+ const sandbox = await Sandbox.create('base', { project: 'my-project' })
133
+ const sandbox = await Sandbox.create('code-interpreter-v1', { project: 'my-project', timeoutMs: 10 * 60 * 1000 })
116
134
  ```
117
135
 
118
136
  ### `Sandbox.connect(sandboxId, opts?)`
package/dist/index.d.mts CHANGED
@@ -138,10 +138,14 @@ interface SandboxInfo {
138
138
  }
139
139
  interface SandboxOpts extends ConnectionOpts {
140
140
  /**
141
- * ID of the project this sandbox belongs to. Required: a sandbox must be
142
- * attributed to a project so its CPU, RAM, egress, and storage are billed.
141
+ * The project this sandbox belongs to — its ID, slug, or name. Required:
142
+ * a sandbox must be attributed to a project so its CPU, RAM, egress, and
143
+ * storage are billed. Prefer the {@link Lizard} client, which pins a project
144
+ * for you. Ignored when {@link SandboxOpts.projectId} is set.
143
145
  */
144
- projectId: string;
146
+ project?: string;
147
+ /** Exact project ID — skips resolving {@link SandboxOpts.project}. */
148
+ projectId?: string;
145
149
  template?: string;
146
150
  metadata?: Record<string, string>;
147
151
  envs?: Record<string, string>;
@@ -180,7 +184,7 @@ declare class SandboxClient {
180
184
  * ```ts
181
185
  * import { Sandbox } from 'lizard'
182
186
  *
183
- * const sandbox = await Sandbox.create('base', { projectId: 'proj_abc123' })
187
+ * const sandbox = await Sandbox.create('base', { project: 'my-project' })
184
188
  * await sandbox.fs.write('/app/index.js', 'console.log("hello world")')
185
189
  * const result = await sandbox.process.exec('node /app/index.js')
186
190
  * console.log(result.stdout) // "hello world"
@@ -189,7 +193,7 @@ declare class SandboxClient {
189
193
  *
190
194
  * @example Pause and resume a long-running session:
191
195
  * ```ts
192
- * const sandbox = await Sandbox.create('code-interpreter-v1', { projectId: 'proj_abc123' })
196
+ * const sandbox = await Sandbox.create('code-interpreter-v1', { project: 'my-project' })
193
197
  * await sandbox.process.exec('pip install numpy')
194
198
  * const id = sandbox.sandboxId
195
199
  * await sandbox.pause()
@@ -234,7 +238,7 @@ declare class Sandbox extends SandboxClient {
234
238
  *
235
239
  * @example
236
240
  * ```ts
237
- * const sandbox = await Sandbox.create({ projectId: 'proj_abc123' })
241
+ * const sandbox = await Sandbox.create({ project: 'my-project' })
238
242
  * ```
239
243
  */
240
244
  static create(opts?: SandboxOpts): Promise<Sandbox>;
@@ -249,8 +253,8 @@ declare class Sandbox extends SandboxClient {
249
253
  *
250
254
  * @example
251
255
  * ```ts
252
- * const sandbox = await Sandbox.create('base', { projectId: 'proj_abc123' })
253
- * const sandbox = await Sandbox.create('code-interpreter-v1', { projectId: 'proj_abc123', timeoutMs: 10 * 60 * 1000 })
256
+ * const sandbox = await Sandbox.create('base', { project: 'my-project' })
257
+ * const sandbox = await Sandbox.create('code-interpreter-v1', { project: 'my-project', timeoutMs: 10 * 60 * 1000 })
254
258
  * ```
255
259
  */
256
260
  static create(template: string, opts?: SandboxOpts): Promise<Sandbox>;
@@ -323,6 +327,63 @@ declare class Sandbox extends SandboxClient {
323
327
  private resolveOpts;
324
328
  }
325
329
 
330
+ interface LizardOpts extends ConnectionOpts {
331
+ /**
332
+ * The project every sandbox created through this client belongs to — its ID,
333
+ * slug, or name. Required: sandboxes are billed per project, so a project-less
334
+ * sandbox cannot be created.
335
+ */
336
+ project: string;
337
+ }
338
+ /**
339
+ * The Lizard client — the entry point for creating sandboxes.
340
+ *
341
+ * A client is pinned to one project (billing is metered per project), so every
342
+ * sandbox it creates is attributed correctly. The project reference — ID, slug,
343
+ * or name — is resolved to a project ID on first use and cached.
344
+ *
345
+ * @example
346
+ * ```ts
347
+ * import { Lizard } from '@lizard-build/sdk'
348
+ *
349
+ * const lizard = new Lizard({ project: 'my-project' }) // apiKey from LIZARD_API_KEY
350
+ *
351
+ * const sandbox = await lizard.create('base')
352
+ * await sandbox.process.exec('echo hello')
353
+ * await sandbox.kill()
354
+ * ```
355
+ */
356
+ declare class Lizard {
357
+ private readonly config;
358
+ private readonly projectRef;
359
+ constructor(opts: LizardOpts);
360
+ /** Resolve the client's project reference to a stable project ID (cached). */
361
+ projectId(): Promise<string>;
362
+ private connectionOpts;
363
+ /**
364
+ * Create a new sandbox in this client's project.
365
+ *
366
+ * @example
367
+ * ```ts
368
+ * const sandbox = await lizard.create('base')
369
+ * const sandbox = await lizard.create('code-interpreter-v1', { timeoutMs: 600_000 })
370
+ * ```
371
+ */
372
+ create(template?: string, opts?: Omit<SandboxOpts, 'project' | 'projectId'>): Promise<Sandbox>;
373
+ /** Connect to an existing sandbox by ID (resumes it if paused). */
374
+ connect(sandboxId: string, opts?: ConnectionOpts): Promise<Sandbox>;
375
+ /** List running sandboxes for the authenticated account. */
376
+ list(opts?: ConnectionOpts): Promise<SandboxInfo[]>;
377
+ }
378
+
379
+ /**
380
+ * Resolve a project reference — its ID, slug, or name — to the stable project
381
+ * ID the API bills against. Exact-ID refs skip the network entirely.
382
+ *
383
+ * @throws {LizardError} when the reference matches no project the key can see.
384
+ */
385
+ declare function resolveProjectId(ref: string, config: ConnectionConfig): Promise<string>;
386
+
326
387
  declare class LizardError extends Error {
327
388
  constructor(message: string);
328
389
  }
@@ -414,7 +475,7 @@ interface CreateContextOpts {
414
475
  * ```ts
415
476
  * import { CodeSandbox } from 'lizard/code-interpreter'
416
477
  *
417
- * const sandbox = await CodeSandbox.create({ projectId: 'proj_abc123' })
478
+ * const sandbox = await CodeSandbox.create({ project: 'my-project' })
418
479
  *
419
480
  * await sandbox.runCode('x = 42')
420
481
  * const result = await sandbox.runCode('print(x * 2)')
@@ -517,4 +578,4 @@ declare class Volume {
517
578
  delete(projectId: string): Promise<void>;
518
579
  }
519
580
 
520
- export { AuthenticationError, type CodeContext, CodeSandbox, type ConnectionOpts, type CreateContextOpts, type CreateVolumeOpts, Execution, ExecutionError, type FileInfo, type FsOpts, LizardError, NotFoundError, type ProcessOpts, type ProcessResult, type RunCodeLanguage, type RunCodeOpts, Sandbox, type SandboxInfo, type SandboxOpts, TimeoutError, Volume, type VolumeInfo };
581
+ export { AuthenticationError, type CodeContext, CodeSandbox, type ConnectionOpts, type CreateContextOpts, type CreateVolumeOpts, Execution, ExecutionError, type FileInfo, type FsOpts, Lizard, LizardError, type LizardOpts, NotFoundError, type ProcessOpts, type ProcessResult, type RunCodeLanguage, type RunCodeOpts, Sandbox, type SandboxInfo, type SandboxOpts, TimeoutError, Volume, type VolumeInfo, resolveProjectId };
package/dist/index.d.ts CHANGED
@@ -138,10 +138,14 @@ interface SandboxInfo {
138
138
  }
139
139
  interface SandboxOpts extends ConnectionOpts {
140
140
  /**
141
- * ID of the project this sandbox belongs to. Required: a sandbox must be
142
- * attributed to a project so its CPU, RAM, egress, and storage are billed.
141
+ * The project this sandbox belongs to — its ID, slug, or name. Required:
142
+ * a sandbox must be attributed to a project so its CPU, RAM, egress, and
143
+ * storage are billed. Prefer the {@link Lizard} client, which pins a project
144
+ * for you. Ignored when {@link SandboxOpts.projectId} is set.
143
145
  */
144
- projectId: string;
146
+ project?: string;
147
+ /** Exact project ID — skips resolving {@link SandboxOpts.project}. */
148
+ projectId?: string;
145
149
  template?: string;
146
150
  metadata?: Record<string, string>;
147
151
  envs?: Record<string, string>;
@@ -180,7 +184,7 @@ declare class SandboxClient {
180
184
  * ```ts
181
185
  * import { Sandbox } from 'lizard'
182
186
  *
183
- * const sandbox = await Sandbox.create('base', { projectId: 'proj_abc123' })
187
+ * const sandbox = await Sandbox.create('base', { project: 'my-project' })
184
188
  * await sandbox.fs.write('/app/index.js', 'console.log("hello world")')
185
189
  * const result = await sandbox.process.exec('node /app/index.js')
186
190
  * console.log(result.stdout) // "hello world"
@@ -189,7 +193,7 @@ declare class SandboxClient {
189
193
  *
190
194
  * @example Pause and resume a long-running session:
191
195
  * ```ts
192
- * const sandbox = await Sandbox.create('code-interpreter-v1', { projectId: 'proj_abc123' })
196
+ * const sandbox = await Sandbox.create('code-interpreter-v1', { project: 'my-project' })
193
197
  * await sandbox.process.exec('pip install numpy')
194
198
  * const id = sandbox.sandboxId
195
199
  * await sandbox.pause()
@@ -234,7 +238,7 @@ declare class Sandbox extends SandboxClient {
234
238
  *
235
239
  * @example
236
240
  * ```ts
237
- * const sandbox = await Sandbox.create({ projectId: 'proj_abc123' })
241
+ * const sandbox = await Sandbox.create({ project: 'my-project' })
238
242
  * ```
239
243
  */
240
244
  static create(opts?: SandboxOpts): Promise<Sandbox>;
@@ -249,8 +253,8 @@ declare class Sandbox extends SandboxClient {
249
253
  *
250
254
  * @example
251
255
  * ```ts
252
- * const sandbox = await Sandbox.create('base', { projectId: 'proj_abc123' })
253
- * const sandbox = await Sandbox.create('code-interpreter-v1', { projectId: 'proj_abc123', timeoutMs: 10 * 60 * 1000 })
256
+ * const sandbox = await Sandbox.create('base', { project: 'my-project' })
257
+ * const sandbox = await Sandbox.create('code-interpreter-v1', { project: 'my-project', timeoutMs: 10 * 60 * 1000 })
254
258
  * ```
255
259
  */
256
260
  static create(template: string, opts?: SandboxOpts): Promise<Sandbox>;
@@ -323,6 +327,63 @@ declare class Sandbox extends SandboxClient {
323
327
  private resolveOpts;
324
328
  }
325
329
 
330
+ interface LizardOpts extends ConnectionOpts {
331
+ /**
332
+ * The project every sandbox created through this client belongs to — its ID,
333
+ * slug, or name. Required: sandboxes are billed per project, so a project-less
334
+ * sandbox cannot be created.
335
+ */
336
+ project: string;
337
+ }
338
+ /**
339
+ * The Lizard client — the entry point for creating sandboxes.
340
+ *
341
+ * A client is pinned to one project (billing is metered per project), so every
342
+ * sandbox it creates is attributed correctly. The project reference — ID, slug,
343
+ * or name — is resolved to a project ID on first use and cached.
344
+ *
345
+ * @example
346
+ * ```ts
347
+ * import { Lizard } from '@lizard-build/sdk'
348
+ *
349
+ * const lizard = new Lizard({ project: 'my-project' }) // apiKey from LIZARD_API_KEY
350
+ *
351
+ * const sandbox = await lizard.create('base')
352
+ * await sandbox.process.exec('echo hello')
353
+ * await sandbox.kill()
354
+ * ```
355
+ */
356
+ declare class Lizard {
357
+ private readonly config;
358
+ private readonly projectRef;
359
+ constructor(opts: LizardOpts);
360
+ /** Resolve the client's project reference to a stable project ID (cached). */
361
+ projectId(): Promise<string>;
362
+ private connectionOpts;
363
+ /**
364
+ * Create a new sandbox in this client's project.
365
+ *
366
+ * @example
367
+ * ```ts
368
+ * const sandbox = await lizard.create('base')
369
+ * const sandbox = await lizard.create('code-interpreter-v1', { timeoutMs: 600_000 })
370
+ * ```
371
+ */
372
+ create(template?: string, opts?: Omit<SandboxOpts, 'project' | 'projectId'>): Promise<Sandbox>;
373
+ /** Connect to an existing sandbox by ID (resumes it if paused). */
374
+ connect(sandboxId: string, opts?: ConnectionOpts): Promise<Sandbox>;
375
+ /** List running sandboxes for the authenticated account. */
376
+ list(opts?: ConnectionOpts): Promise<SandboxInfo[]>;
377
+ }
378
+
379
+ /**
380
+ * Resolve a project reference — its ID, slug, or name — to the stable project
381
+ * ID the API bills against. Exact-ID refs skip the network entirely.
382
+ *
383
+ * @throws {LizardError} when the reference matches no project the key can see.
384
+ */
385
+ declare function resolveProjectId(ref: string, config: ConnectionConfig): Promise<string>;
386
+
326
387
  declare class LizardError extends Error {
327
388
  constructor(message: string);
328
389
  }
@@ -414,7 +475,7 @@ interface CreateContextOpts {
414
475
  * ```ts
415
476
  * import { CodeSandbox } from 'lizard/code-interpreter'
416
477
  *
417
- * const sandbox = await CodeSandbox.create({ projectId: 'proj_abc123' })
478
+ * const sandbox = await CodeSandbox.create({ project: 'my-project' })
418
479
  *
419
480
  * await sandbox.runCode('x = 42')
420
481
  * const result = await sandbox.runCode('print(x * 2)')
@@ -517,4 +578,4 @@ declare class Volume {
517
578
  delete(projectId: string): Promise<void>;
518
579
  }
519
580
 
520
- export { AuthenticationError, type CodeContext, CodeSandbox, type ConnectionOpts, type CreateContextOpts, type CreateVolumeOpts, Execution, ExecutionError, type FileInfo, type FsOpts, LizardError, NotFoundError, type ProcessOpts, type ProcessResult, type RunCodeLanguage, type RunCodeOpts, Sandbox, type SandboxInfo, type SandboxOpts, TimeoutError, Volume, type VolumeInfo };
581
+ export { AuthenticationError, type CodeContext, CodeSandbox, type ConnectionOpts, type CreateContextOpts, type CreateVolumeOpts, Execution, ExecutionError, type FileInfo, type FsOpts, Lizard, LizardError, type LizardOpts, NotFoundError, type ProcessOpts, type ProcessResult, type RunCodeLanguage, type RunCodeOpts, Sandbox, type SandboxInfo, type SandboxOpts, TimeoutError, Volume, type VolumeInfo, resolveProjectId };
package/dist/index.js CHANGED
@@ -22,11 +22,13 @@ var index_exports = {};
22
22
  __export(index_exports, {
23
23
  AuthenticationError: () => AuthenticationError,
24
24
  CodeSandbox: () => CodeSandbox,
25
+ Lizard: () => Lizard,
25
26
  LizardError: () => LizardError,
26
27
  NotFoundError: () => NotFoundError,
27
28
  Sandbox: () => Sandbox,
28
29
  TimeoutError: () => TimeoutError,
29
- Volume: () => Volume
30
+ Volume: () => Volume,
31
+ resolveProjectId: () => resolveProjectId
30
32
  });
31
33
  module.exports = __toCommonJS(index_exports);
32
34
 
@@ -91,6 +93,36 @@ async function handleApiError(res) {
91
93
  throw new LizardError(`API error ${res.status}: ${message}`);
92
94
  }
93
95
 
96
+ // src/project.ts
97
+ var projectIdCache = /* @__PURE__ */ new Map();
98
+ async function resolveProjectId(ref, config) {
99
+ const cacheKey = `${config.apiUrl}\0${ref}`;
100
+ const cached = projectIdCache.get(cacheKey);
101
+ if (cached) return cached;
102
+ const res = await fetch(`${config.apiUrl}/api/projects`, { headers: config.headers });
103
+ if (!res.ok) await handleApiError(res);
104
+ const projects = await res.json();
105
+ const lower = ref.toLowerCase();
106
+ const match = projects.find(
107
+ (p) => p.id.toLowerCase() === lower || p.slug?.toLowerCase() === lower || p.name?.toLowerCase() === lower
108
+ );
109
+ if (!match) {
110
+ const available = projects.map((p) => p.slug || p.id).join(", ") || "(none)";
111
+ throw new LizardError(
112
+ `Project "${ref}" not found. Available: ${available}. Pass the project's ID, slug, or name.`
113
+ );
114
+ }
115
+ projectIdCache.set(cacheKey, match.id);
116
+ return match.id;
117
+ }
118
+ function requireProjectRef(opts) {
119
+ if (opts?.projectId) return { projectId: opts.projectId };
120
+ if (opts?.project) return { project: opts.project };
121
+ throw new LizardError(
122
+ 'A project is required to create a sandbox. Pass `project` (its ID, slug, or name), or create sandboxes through a Lizard client: new Lizard({ project: "my-project" }).'
123
+ );
124
+ }
125
+
94
126
  // src/sandbox/process.ts
95
127
  var Process = class {
96
128
  constructor(sandboxId, config) {
@@ -233,19 +265,16 @@ var Fs = class {
233
265
  // src/sandbox/client.ts
234
266
  var SandboxClient = class {
235
267
  static async createSandbox(template, timeoutMs, opts) {
236
- if (!opts?.projectId) {
237
- throw new LizardError(
238
- "projectId is required: a sandbox must belong to a project so its usage is billed. Pass { projectId } to Sandbox.create()."
239
- );
240
- }
268
+ const ref = requireProjectRef(opts);
241
269
  const config = new ConnectionConfig(opts);
270
+ const projectId = ref.projectId ?? await resolveProjectId(ref.project, config);
242
271
  const res = await fetch(`${config.apiUrl}/api/sandboxes`, {
243
272
  method: "POST",
244
273
  headers: config.headers,
245
274
  body: JSON.stringify({
246
- projectId: opts.projectId,
247
275
  template,
248
276
  timeoutMs,
277
+ projectId,
249
278
  metadata: opts?.metadata,
250
279
  envs: opts?.envs,
251
280
  volumeId: opts?.volumeId
@@ -427,6 +456,48 @@ var Sandbox = class extends SandboxClient {
427
456
  Sandbox.defaultTemplate = "base";
428
457
  Sandbox.defaultTimeoutMs = DEFAULT_SANDBOX_TIMEOUT_MS;
429
458
 
459
+ // src/lizard.ts
460
+ var Lizard = class {
461
+ constructor(opts) {
462
+ if (!opts?.project) {
463
+ throw new LizardError(
464
+ 'A project is required. Pass project (its ID, slug, or name): new Lizard({ project: "my-project" }).'
465
+ );
466
+ }
467
+ this.config = new ConnectionConfig(opts);
468
+ this.projectRef = opts.project;
469
+ }
470
+ /** Resolve the client's project reference to a stable project ID (cached). */
471
+ async projectId() {
472
+ return resolveProjectId(this.projectRef, this.config);
473
+ }
474
+ async connectionOpts(extra) {
475
+ return { apiKey: this.config.apiKey, apiUrl: this.config.apiUrl, ...extra };
476
+ }
477
+ /**
478
+ * Create a new sandbox in this client's project.
479
+ *
480
+ * @example
481
+ * ```ts
482
+ * const sandbox = await lizard.create('base')
483
+ * const sandbox = await lizard.create('code-interpreter-v1', { timeoutMs: 600_000 })
484
+ * ```
485
+ */
486
+ async create(template, opts) {
487
+ const projectId = await this.projectId();
488
+ const sandboxOpts = { ...opts, ...await this.connectionOpts(), projectId };
489
+ return template ? Sandbox.create(template, sandboxOpts) : Sandbox.create(sandboxOpts);
490
+ }
491
+ /** Connect to an existing sandbox by ID (resumes it if paused). */
492
+ async connect(sandboxId, opts) {
493
+ return Sandbox.connect(sandboxId, await this.connectionOpts(opts));
494
+ }
495
+ /** List running sandboxes for the authenticated account. */
496
+ async list(opts) {
497
+ return Sandbox.list(await this.connectionOpts(opts));
498
+ }
499
+ };
500
+
430
501
  // src/code-interpreter/types.ts
431
502
  var ExecutionError = class extends Error {
432
503
  constructor(name, message, traceback) {
@@ -669,9 +740,11 @@ var Volume = class _Volume {
669
740
  0 && (module.exports = {
670
741
  AuthenticationError,
671
742
  CodeSandbox,
743
+ Lizard,
672
744
  LizardError,
673
745
  NotFoundError,
674
746
  Sandbox,
675
747
  TimeoutError,
676
- Volume
748
+ Volume,
749
+ resolveProjectId
677
750
  });
package/dist/index.mjs CHANGED
@@ -59,6 +59,36 @@ async function handleApiError(res) {
59
59
  throw new LizardError(`API error ${res.status}: ${message}`);
60
60
  }
61
61
 
62
+ // src/project.ts
63
+ var projectIdCache = /* @__PURE__ */ new Map();
64
+ async function resolveProjectId(ref, config) {
65
+ const cacheKey = `${config.apiUrl}\0${ref}`;
66
+ const cached = projectIdCache.get(cacheKey);
67
+ if (cached) return cached;
68
+ const res = await fetch(`${config.apiUrl}/api/projects`, { headers: config.headers });
69
+ if (!res.ok) await handleApiError(res);
70
+ const projects = await res.json();
71
+ const lower = ref.toLowerCase();
72
+ const match = projects.find(
73
+ (p) => p.id.toLowerCase() === lower || p.slug?.toLowerCase() === lower || p.name?.toLowerCase() === lower
74
+ );
75
+ if (!match) {
76
+ const available = projects.map((p) => p.slug || p.id).join(", ") || "(none)";
77
+ throw new LizardError(
78
+ `Project "${ref}" not found. Available: ${available}. Pass the project's ID, slug, or name.`
79
+ );
80
+ }
81
+ projectIdCache.set(cacheKey, match.id);
82
+ return match.id;
83
+ }
84
+ function requireProjectRef(opts) {
85
+ if (opts?.projectId) return { projectId: opts.projectId };
86
+ if (opts?.project) return { project: opts.project };
87
+ throw new LizardError(
88
+ 'A project is required to create a sandbox. Pass `project` (its ID, slug, or name), or create sandboxes through a Lizard client: new Lizard({ project: "my-project" }).'
89
+ );
90
+ }
91
+
62
92
  // src/sandbox/process.ts
63
93
  var Process = class {
64
94
  constructor(sandboxId, config) {
@@ -201,19 +231,16 @@ var Fs = class {
201
231
  // src/sandbox/client.ts
202
232
  var SandboxClient = class {
203
233
  static async createSandbox(template, timeoutMs, opts) {
204
- if (!opts?.projectId) {
205
- throw new LizardError(
206
- "projectId is required: a sandbox must belong to a project so its usage is billed. Pass { projectId } to Sandbox.create()."
207
- );
208
- }
234
+ const ref = requireProjectRef(opts);
209
235
  const config = new ConnectionConfig(opts);
236
+ const projectId = ref.projectId ?? await resolveProjectId(ref.project, config);
210
237
  const res = await fetch(`${config.apiUrl}/api/sandboxes`, {
211
238
  method: "POST",
212
239
  headers: config.headers,
213
240
  body: JSON.stringify({
214
- projectId: opts.projectId,
215
241
  template,
216
242
  timeoutMs,
243
+ projectId,
217
244
  metadata: opts?.metadata,
218
245
  envs: opts?.envs,
219
246
  volumeId: opts?.volumeId
@@ -395,6 +422,48 @@ var Sandbox = class extends SandboxClient {
395
422
  Sandbox.defaultTemplate = "base";
396
423
  Sandbox.defaultTimeoutMs = DEFAULT_SANDBOX_TIMEOUT_MS;
397
424
 
425
+ // src/lizard.ts
426
+ var Lizard = class {
427
+ constructor(opts) {
428
+ if (!opts?.project) {
429
+ throw new LizardError(
430
+ 'A project is required. Pass project (its ID, slug, or name): new Lizard({ project: "my-project" }).'
431
+ );
432
+ }
433
+ this.config = new ConnectionConfig(opts);
434
+ this.projectRef = opts.project;
435
+ }
436
+ /** Resolve the client's project reference to a stable project ID (cached). */
437
+ async projectId() {
438
+ return resolveProjectId(this.projectRef, this.config);
439
+ }
440
+ async connectionOpts(extra) {
441
+ return { apiKey: this.config.apiKey, apiUrl: this.config.apiUrl, ...extra };
442
+ }
443
+ /**
444
+ * Create a new sandbox in this client's project.
445
+ *
446
+ * @example
447
+ * ```ts
448
+ * const sandbox = await lizard.create('base')
449
+ * const sandbox = await lizard.create('code-interpreter-v1', { timeoutMs: 600_000 })
450
+ * ```
451
+ */
452
+ async create(template, opts) {
453
+ const projectId = await this.projectId();
454
+ const sandboxOpts = { ...opts, ...await this.connectionOpts(), projectId };
455
+ return template ? Sandbox.create(template, sandboxOpts) : Sandbox.create(sandboxOpts);
456
+ }
457
+ /** Connect to an existing sandbox by ID (resumes it if paused). */
458
+ async connect(sandboxId, opts) {
459
+ return Sandbox.connect(sandboxId, await this.connectionOpts(opts));
460
+ }
461
+ /** List running sandboxes for the authenticated account. */
462
+ async list(opts) {
463
+ return Sandbox.list(await this.connectionOpts(opts));
464
+ }
465
+ };
466
+
398
467
  // src/code-interpreter/types.ts
399
468
  var ExecutionError = class extends Error {
400
469
  constructor(name, message, traceback) {
@@ -636,9 +705,11 @@ var Volume = class _Volume {
636
705
  export {
637
706
  AuthenticationError,
638
707
  CodeSandbox,
708
+ Lizard,
639
709
  LizardError,
640
710
  NotFoundError,
641
711
  Sandbox,
642
712
  TimeoutError,
643
- Volume
713
+ Volume,
714
+ resolveProjectId
644
715
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lizard-build/sdk",
3
- "version": "0.1.19",
3
+ "version": "0.1.21",
4
4
  "description": "Lizard SDK — cloud sandboxes for AI agents",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",