@lizard-build/sdk 0.1.30 → 0.1.32

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/dist/index.d.mts CHANGED
@@ -25,7 +25,9 @@ interface ProcessOpts {
25
25
  envs?: Record<string, string>;
26
26
  user?: string;
27
27
  workdir?: string;
28
+ /** Called with each stdout line as it is produced, rather than at the end. */
28
29
  onStdout?: (data: string) => void;
30
+ /** Called with each stderr line as it is produced, rather than at the end. */
29
31
  onStderr?: (data: string) => void;
30
32
  }
31
33
  /**
@@ -62,6 +64,12 @@ declare class Process {
62
64
  * ```
63
65
  */
64
66
  exec(cmd: string, opts?: ProcessOpts): Promise<ProcessResult>;
67
+ /**
68
+ * Read an SSE exec stream, handing each line to the caller as it arrives while
69
+ * still accumulating the full result. Events are `{stream, line}` for output and
70
+ * `{exitCode}` at the end.
71
+ */
72
+ private consumeStream;
65
73
  }
66
74
 
67
75
  interface FsOpts {
@@ -70,8 +78,12 @@ interface FsOpts {
70
78
  interface FileInfo {
71
79
  name: string;
72
80
  path: string;
73
- type: 'file' | 'dir';
81
+ type: 'file' | 'dir' | 'symlink';
74
82
  size: number;
83
+ /** Permission bits as a string, e.g. `-rw-r--r--`. Absent on older sandboxes. */
84
+ mode?: string;
85
+ /** Last modification time, unix milliseconds. Absent on older sandboxes. */
86
+ modTime?: number;
75
87
  }
76
88
  /**
77
89
  * Read and write files inside a Lizard sandbox microVM.
@@ -126,6 +138,35 @@ declare class Fs {
126
138
  * Create a directory (and any missing parents) inside the microVM.
127
139
  */
128
140
  makeDir(path: string, opts?: FsOpts): Promise<void>;
141
+ /**
142
+ * Metadata for a single path — size, type, permissions, modification time.
143
+ *
144
+ * Saves listing a parent directory and filtering it just to answer "does this
145
+ * exist, and how big is it".
146
+ *
147
+ * @example
148
+ * ```ts
149
+ * const info = await sandbox.fs.stat('/app/out.bin')
150
+ * console.log(info.size, info.type)
151
+ * ```
152
+ *
153
+ * Throws `NotFoundError` if the path does not exist. Sandboxes created before
154
+ * this shipped run a guest agent without it and throw `LizardError` (501) —
155
+ * recreate the sandbox to use it.
156
+ */
157
+ stat(path: string, opts?: FsOpts): Promise<FileInfo>;
158
+ /**
159
+ * Move or rename a path. Creates the destination's parent directories.
160
+ *
161
+ * @example
162
+ * ```ts
163
+ * await sandbox.fs.move('/tmp/build.log', '/app/logs/build.log')
164
+ * ```
165
+ *
166
+ * Sandboxes created before this shipped run a guest agent without it and throw
167
+ * `LizardError` (501) — recreate the sandbox to use it.
168
+ */
169
+ move(from: string, to: string): Promise<void>;
129
170
  private execInternal;
130
171
  }
131
172
 
package/dist/index.d.ts CHANGED
@@ -25,7 +25,9 @@ interface ProcessOpts {
25
25
  envs?: Record<string, string>;
26
26
  user?: string;
27
27
  workdir?: string;
28
+ /** Called with each stdout line as it is produced, rather than at the end. */
28
29
  onStdout?: (data: string) => void;
30
+ /** Called with each stderr line as it is produced, rather than at the end. */
29
31
  onStderr?: (data: string) => void;
30
32
  }
31
33
  /**
@@ -62,6 +64,12 @@ declare class Process {
62
64
  * ```
63
65
  */
64
66
  exec(cmd: string, opts?: ProcessOpts): Promise<ProcessResult>;
67
+ /**
68
+ * Read an SSE exec stream, handing each line to the caller as it arrives while
69
+ * still accumulating the full result. Events are `{stream, line}` for output and
70
+ * `{exitCode}` at the end.
71
+ */
72
+ private consumeStream;
65
73
  }
66
74
 
67
75
  interface FsOpts {
@@ -70,8 +78,12 @@ interface FsOpts {
70
78
  interface FileInfo {
71
79
  name: string;
72
80
  path: string;
73
- type: 'file' | 'dir';
81
+ type: 'file' | 'dir' | 'symlink';
74
82
  size: number;
83
+ /** Permission bits as a string, e.g. `-rw-r--r--`. Absent on older sandboxes. */
84
+ mode?: string;
85
+ /** Last modification time, unix milliseconds. Absent on older sandboxes. */
86
+ modTime?: number;
75
87
  }
76
88
  /**
77
89
  * Read and write files inside a Lizard sandbox microVM.
@@ -126,6 +138,35 @@ declare class Fs {
126
138
  * Create a directory (and any missing parents) inside the microVM.
127
139
  */
128
140
  makeDir(path: string, opts?: FsOpts): Promise<void>;
141
+ /**
142
+ * Metadata for a single path — size, type, permissions, modification time.
143
+ *
144
+ * Saves listing a parent directory and filtering it just to answer "does this
145
+ * exist, and how big is it".
146
+ *
147
+ * @example
148
+ * ```ts
149
+ * const info = await sandbox.fs.stat('/app/out.bin')
150
+ * console.log(info.size, info.type)
151
+ * ```
152
+ *
153
+ * Throws `NotFoundError` if the path does not exist. Sandboxes created before
154
+ * this shipped run a guest agent without it and throw `LizardError` (501) —
155
+ * recreate the sandbox to use it.
156
+ */
157
+ stat(path: string, opts?: FsOpts): Promise<FileInfo>;
158
+ /**
159
+ * Move or rename a path. Creates the destination's parent directories.
160
+ *
161
+ * @example
162
+ * ```ts
163
+ * await sandbox.fs.move('/tmp/build.log', '/app/logs/build.log')
164
+ * ```
165
+ *
166
+ * Sandboxes created before this shipped run a guest agent without it and throw
167
+ * `LizardError` (501) — recreate the sandbox to use it.
168
+ */
169
+ move(from: string, to: string): Promise<void>;
129
170
  private execInternal;
130
171
  }
131
172
 
package/dist/index.js CHANGED
@@ -163,9 +163,10 @@ var Process = class {
163
163
  * ```
164
164
  */
165
165
  async exec(cmd, opts) {
166
+ const streaming = Boolean(opts?.onStdout || opts?.onStderr);
166
167
  const res = await fetch(`${this.config.apiUrl}/api/sandboxes/${this.sandboxId}/exec`, {
167
168
  method: "POST",
168
- headers: this.config.headers,
169
+ headers: streaming ? { ...this.config.headers, Accept: "text/event-stream" } : this.config.headers,
169
170
  body: JSON.stringify({
170
171
  cmd,
171
172
  envs: opts?.envs,
@@ -176,7 +177,51 @@ var Process = class {
176
177
  signal: AbortSignal.timeout(opts?.timeoutMs ?? 6e4)
177
178
  });
178
179
  if (!res.ok) await handleApiError(res);
179
- return res.json();
180
+ if (!streaming) return res.json();
181
+ return this.consumeStream(res, opts);
182
+ }
183
+ /**
184
+ * Read an SSE exec stream, handing each line to the caller as it arrives while
185
+ * still accumulating the full result. Events are `{stream, line}` for output and
186
+ * `{exitCode}` at the end.
187
+ */
188
+ async consumeStream(res, opts) {
189
+ const reader = res.body?.getReader();
190
+ if (!reader) return { stdout: "", stderr: "", exitCode: 0 };
191
+ const decoder = new TextDecoder();
192
+ let buf = "";
193
+ let stdout = "";
194
+ let stderr = "";
195
+ let exitCode = 0;
196
+ const handle = (raw) => {
197
+ if (!raw.startsWith("data:")) return;
198
+ let ev;
199
+ try {
200
+ ev = JSON.parse(raw.slice(5).trim());
201
+ } catch {
202
+ return;
203
+ }
204
+ if (ev.line !== void 0) {
205
+ if (ev.stream === "stderr") {
206
+ stderr += ev.line + "\n";
207
+ opts.onStderr?.(ev.line);
208
+ } else {
209
+ stdout += ev.line + "\n";
210
+ opts.onStdout?.(ev.line);
211
+ }
212
+ }
213
+ if (ev.exitCode !== void 0) exitCode = ev.exitCode;
214
+ };
215
+ for (; ; ) {
216
+ const { done, value } = await reader.read();
217
+ if (done) break;
218
+ buf += decoder.decode(value, { stream: true });
219
+ const lines = buf.split("\n");
220
+ buf = lines.pop() ?? "";
221
+ for (const l of lines) handle(l);
222
+ }
223
+ if (buf) handle(buf);
224
+ return { stdout: stdout.trimEnd(), stderr: stderr.trimEnd(), exitCode };
180
225
  }
181
226
  };
182
227
 
@@ -261,6 +306,49 @@ var Fs = class {
261
306
  async makeDir(path, opts) {
262
307
  await this.execInternal(`mkdir -p ${JSON.stringify(path)}`, opts?.user);
263
308
  }
309
+ /**
310
+ * Metadata for a single path — size, type, permissions, modification time.
311
+ *
312
+ * Saves listing a parent directory and filtering it just to answer "does this
313
+ * exist, and how big is it".
314
+ *
315
+ * @example
316
+ * ```ts
317
+ * const info = await sandbox.fs.stat('/app/out.bin')
318
+ * console.log(info.size, info.type)
319
+ * ```
320
+ *
321
+ * Throws `NotFoundError` if the path does not exist. Sandboxes created before
322
+ * this shipped run a guest agent without it and throw `LizardError` (501) —
323
+ * recreate the sandbox to use it.
324
+ */
325
+ async stat(path, opts) {
326
+ const url = new URL(`${this.config.apiUrl}/api/sandboxes/${this.sandboxId}/files/stat`);
327
+ url.searchParams.set("path", path);
328
+ if (opts?.user) url.searchParams.set("user", opts.user);
329
+ const res = await fetch(url.toString(), { headers: this.config.headers });
330
+ if (!res.ok) await handleApiError(res);
331
+ return res.json();
332
+ }
333
+ /**
334
+ * Move or rename a path. Creates the destination's parent directories.
335
+ *
336
+ * @example
337
+ * ```ts
338
+ * await sandbox.fs.move('/tmp/build.log', '/app/logs/build.log')
339
+ * ```
340
+ *
341
+ * Sandboxes created before this shipped run a guest agent without it and throw
342
+ * `LizardError` (501) — recreate the sandbox to use it.
343
+ */
344
+ async move(from, to) {
345
+ const res = await fetch(`${this.config.apiUrl}/api/sandboxes/${this.sandboxId}/files/move`, {
346
+ method: "POST",
347
+ headers: this.config.headers,
348
+ body: JSON.stringify({ from, to })
349
+ });
350
+ if (!res.ok) await handleApiError(res);
351
+ }
264
352
  async execInternal(cmd, user) {
265
353
  const res = await fetch(`${this.config.apiUrl}/api/sandboxes/${this.sandboxId}/exec`, {
266
354
  method: "POST",
package/dist/index.mjs CHANGED
@@ -127,9 +127,10 @@ var Process = class {
127
127
  * ```
128
128
  */
129
129
  async exec(cmd, opts) {
130
+ const streaming = Boolean(opts?.onStdout || opts?.onStderr);
130
131
  const res = await fetch(`${this.config.apiUrl}/api/sandboxes/${this.sandboxId}/exec`, {
131
132
  method: "POST",
132
- headers: this.config.headers,
133
+ headers: streaming ? { ...this.config.headers, Accept: "text/event-stream" } : this.config.headers,
133
134
  body: JSON.stringify({
134
135
  cmd,
135
136
  envs: opts?.envs,
@@ -140,7 +141,51 @@ var Process = class {
140
141
  signal: AbortSignal.timeout(opts?.timeoutMs ?? 6e4)
141
142
  });
142
143
  if (!res.ok) await handleApiError(res);
143
- return res.json();
144
+ if (!streaming) return res.json();
145
+ return this.consumeStream(res, opts);
146
+ }
147
+ /**
148
+ * Read an SSE exec stream, handing each line to the caller as it arrives while
149
+ * still accumulating the full result. Events are `{stream, line}` for output and
150
+ * `{exitCode}` at the end.
151
+ */
152
+ async consumeStream(res, opts) {
153
+ const reader = res.body?.getReader();
154
+ if (!reader) return { stdout: "", stderr: "", exitCode: 0 };
155
+ const decoder = new TextDecoder();
156
+ let buf = "";
157
+ let stdout = "";
158
+ let stderr = "";
159
+ let exitCode = 0;
160
+ const handle = (raw) => {
161
+ if (!raw.startsWith("data:")) return;
162
+ let ev;
163
+ try {
164
+ ev = JSON.parse(raw.slice(5).trim());
165
+ } catch {
166
+ return;
167
+ }
168
+ if (ev.line !== void 0) {
169
+ if (ev.stream === "stderr") {
170
+ stderr += ev.line + "\n";
171
+ opts.onStderr?.(ev.line);
172
+ } else {
173
+ stdout += ev.line + "\n";
174
+ opts.onStdout?.(ev.line);
175
+ }
176
+ }
177
+ if (ev.exitCode !== void 0) exitCode = ev.exitCode;
178
+ };
179
+ for (; ; ) {
180
+ const { done, value } = await reader.read();
181
+ if (done) break;
182
+ buf += decoder.decode(value, { stream: true });
183
+ const lines = buf.split("\n");
184
+ buf = lines.pop() ?? "";
185
+ for (const l of lines) handle(l);
186
+ }
187
+ if (buf) handle(buf);
188
+ return { stdout: stdout.trimEnd(), stderr: stderr.trimEnd(), exitCode };
144
189
  }
145
190
  };
146
191
 
@@ -225,6 +270,49 @@ var Fs = class {
225
270
  async makeDir(path, opts) {
226
271
  await this.execInternal(`mkdir -p ${JSON.stringify(path)}`, opts?.user);
227
272
  }
273
+ /**
274
+ * Metadata for a single path — size, type, permissions, modification time.
275
+ *
276
+ * Saves listing a parent directory and filtering it just to answer "does this
277
+ * exist, and how big is it".
278
+ *
279
+ * @example
280
+ * ```ts
281
+ * const info = await sandbox.fs.stat('/app/out.bin')
282
+ * console.log(info.size, info.type)
283
+ * ```
284
+ *
285
+ * Throws `NotFoundError` if the path does not exist. Sandboxes created before
286
+ * this shipped run a guest agent without it and throw `LizardError` (501) —
287
+ * recreate the sandbox to use it.
288
+ */
289
+ async stat(path, opts) {
290
+ const url = new URL(`${this.config.apiUrl}/api/sandboxes/${this.sandboxId}/files/stat`);
291
+ url.searchParams.set("path", path);
292
+ if (opts?.user) url.searchParams.set("user", opts.user);
293
+ const res = await fetch(url.toString(), { headers: this.config.headers });
294
+ if (!res.ok) await handleApiError(res);
295
+ return res.json();
296
+ }
297
+ /**
298
+ * Move or rename a path. Creates the destination's parent directories.
299
+ *
300
+ * @example
301
+ * ```ts
302
+ * await sandbox.fs.move('/tmp/build.log', '/app/logs/build.log')
303
+ * ```
304
+ *
305
+ * Sandboxes created before this shipped run a guest agent without it and throw
306
+ * `LizardError` (501) — recreate the sandbox to use it.
307
+ */
308
+ async move(from, to) {
309
+ const res = await fetch(`${this.config.apiUrl}/api/sandboxes/${this.sandboxId}/files/move`, {
310
+ method: "POST",
311
+ headers: this.config.headers,
312
+ body: JSON.stringify({ from, to })
313
+ });
314
+ if (!res.ok) await handleApiError(res);
315
+ }
228
316
  async execInternal(cmd, user) {
229
317
  const res = await fetch(`${this.config.apiUrl}/api/sandboxes/${this.sandboxId}/exec`, {
230
318
  method: "POST",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lizard-build/sdk",
3
- "version": "0.1.30",
3
+ "version": "0.1.32",
4
4
  "description": "Firecracker microVM sandboxes for AI agents — boot Linux in milliseconds, run code, write files, expose ports over HTTPS, then pause a session with memory intact and resume it later. TypeScript SDK for Lizard Sandboxes (lizard.build).",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",