withruntime 0.5.1 → 0.6.0

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.
Files changed (45) hide show
  1. package/LICENSE +55 -0
  2. package/README.md +3 -2
  3. package/dist/cli-extras.d.ts +24 -0
  4. package/dist/cli-extras.js +205 -0
  5. package/dist/cli.d.ts +20 -0
  6. package/dist/cli.js +401 -28
  7. package/dist/e2b/filesystem.d.ts +26 -1
  8. package/dist/e2b/filesystem.js +30 -2
  9. package/dist/e2b/index.d.ts +1 -1
  10. package/dist/e2b/index.js +1 -1
  11. package/dist/errors.js +2 -0
  12. package/dist/identity.d.ts +27 -0
  13. package/dist/identity.js +56 -0
  14. package/dist/index.d.ts +14 -0
  15. package/dist/index.js +7 -0
  16. package/dist/mcp.js +16 -3
  17. package/dist/network-cli.d.ts +15 -0
  18. package/dist/network-cli.js +323 -0
  19. package/dist/products/desktop.d.ts +41 -4
  20. package/dist/products/desktop.js +73 -3
  21. package/dist/products/index.d.ts +12 -0
  22. package/dist/products/index.js +12 -0
  23. package/dist/products/interpreter.d.ts +5 -3
  24. package/dist/products/interpreter.js +2 -1
  25. package/dist/products/mcp.d.ts +79 -0
  26. package/dist/products/mcp.js +37 -0
  27. package/dist/products/mounts.d.ts +49 -0
  28. package/dist/products/mounts.js +21 -0
  29. package/dist/products/network-products.d.ts +167 -0
  30. package/dist/products/network-products.js +82 -0
  31. package/dist/products/network.d.ts +1 -1
  32. package/dist/products/sso.d.ts +44 -0
  33. package/dist/products/sso.js +8 -0
  34. package/dist/products/volumes.d.ts +68 -5
  35. package/dist/products/volumes.js +52 -4
  36. package/dist/products/watch.d.ts +91 -0
  37. package/dist/products/watch.js +135 -0
  38. package/dist/sandbox.d.ts +32 -2
  39. package/dist/sandbox.js +93 -16
  40. package/dist/snapshots.d.ts +11 -1
  41. package/dist/transport.d.ts +1 -1
  42. package/dist/transport.js +4 -1
  43. package/dist/wireguard.d.ts +7 -0
  44. package/dist/wireguard.js +13 -0
  45. package/package.json +7 -3
@@ -0,0 +1,82 @@
1
+ const enc = encodeURIComponent;
2
+ /** `runtime.domains`: serve a sandbox's port at your own hostname, with HTTPS. */
3
+ export function domains(t) {
4
+ return {
5
+ /** Claims the hostname and answers the DNS records to set. Calling it again
6
+ * with another sandbox or port points the name there. */
7
+ add: (input, options) => t.json({ method: "POST", path: "/v1/domains", body: input, ...options }),
8
+ /** Checks the TXT record now; the name goes live when it matches. */
9
+ verify: (hostname, options) => t.json({
10
+ method: "POST",
11
+ path: `/v1/domains/${enc(hostname)}:verify`,
12
+ ...options,
13
+ }),
14
+ get: (hostname, options) => t.json({ method: "GET", path: `/v1/domains/${enc(hostname)}`, ...options }),
15
+ list: async (options) => (await t.json({ method: "GET", path: "/v1/domains", ...options })).data,
16
+ remove: (hostname, options) => t.json({
17
+ method: "DELETE",
18
+ path: `/v1/domains/${enc(hostname)}`,
19
+ ...options,
20
+ }),
21
+ };
22
+ }
23
+ /** `runtime.ports`: a public TCP port to a port of a sandbox. */
24
+ export function ports(t) {
25
+ return {
26
+ /** Returns the same public port when called again for the same sandbox and port. */
27
+ open: (input, options) => t.json({
28
+ method: "POST",
29
+ path: "/v1/ports",
30
+ body: input,
31
+ ...options,
32
+ }),
33
+ list: async (filter = {}, options) => (await t.json({
34
+ method: "GET",
35
+ path: `/v1/ports${filter.sandboxId ? `?sandboxId=${enc(filter.sandboxId)}` : ""}`,
36
+ ...options,
37
+ })).data,
38
+ close: (portId, options) => t.json({
39
+ method: "DELETE",
40
+ path: `/v1/ports/${enc(portId)}`,
41
+ ...options,
42
+ }),
43
+ };
44
+ }
45
+ /** `runtime.addresses`: a dedicated outbound address every sandbox of the
46
+ * account sends from, for allow-lists. */
47
+ export function addresses(t) {
48
+ return {
49
+ reserve: (input = {}, options) => t.json({ method: "POST", path: "/v1/addresses", body: input, ...options }),
50
+ list: async (options) => (await t.json({ method: "GET", path: "/v1/addresses", ...options }))
51
+ .data,
52
+ release: (addressId, options) => t.json({
53
+ method: "DELETE",
54
+ path: `/v1/addresses/${enc(addressId)}`,
55
+ ...options,
56
+ }),
57
+ };
58
+ }
59
+ /** `runtime.tunnel`: a WireGuard tunnel from your own network into your sandboxes. */
60
+ export function tunnel(t) {
61
+ return {
62
+ get: (options) => t.json({ method: "GET", path: "/v1/tunnel", ...options }),
63
+ /** Idempotent: returns the tunnel you have. */
64
+ create: (input = {}, options) => t.json({ method: "POST", path: "/v1/tunnel", body: input, ...options }),
65
+ delete: (options) => t.json({ method: "DELETE", path: "/v1/tunnel", ...options }),
66
+ /** Leave publicKey out and Runtime generates the pair; the private key is
67
+ * then in `config`, shown once. */
68
+ addPeer: (input, options) => t.json({
69
+ method: "POST",
70
+ path: "/v1/tunnel/peers",
71
+ body: input,
72
+ ...options,
73
+ }),
74
+ rotatePeer: (peerId, input = {}, options) => t.json({
75
+ method: "POST",
76
+ path: `/v1/tunnel/peers/${enc(peerId)}:rotate`,
77
+ body: input,
78
+ ...options,
79
+ }),
80
+ removePeer: (peerId, options) => t.json({ method: "DELETE", path: `/v1/tunnel/peers/${enc(peerId)}`, ...options }),
81
+ };
82
+ }
@@ -24,7 +24,7 @@ export type NetworkPolicy = Required<NetworkRules> & {
24
24
  /** Ports reachable without a connect entry. */
25
25
  ports: number[];
26
26
  connectAllowed: boolean;
27
- /** paid, granted, not_paid, revoked or suspended. */
27
+ /** paid, granted, not_paid, revoked, suspended, or trial (a trial sandbox reaches ports 80 and 443 only). */
28
28
  connectReason: string;
29
29
  forbiddenPorts: number[];
30
30
  updatedAt: string | null;
@@ -0,0 +1,44 @@
1
+ import type { RequestOptions, Transport } from "../transport.js";
2
+ /** One identity provider of the account. */
3
+ export type SsoConnection = {
4
+ id: string;
5
+ /** Names the connection in its sign-in URLs. */
6
+ providerId: string;
7
+ protocol: "oidc" | "saml";
8
+ provider: "okta" | "entra" | "google" | "other";
9
+ /** The email domain it answers for. */
10
+ domain: string;
11
+ /** When DNS proved the domain; null until then, and nobody signs in until then. */
12
+ domainVerifiedAt: string | null;
13
+ /** The TXT record that proves the domain. */
14
+ verification: {
15
+ type: "TXT";
16
+ name: string;
17
+ value: string;
18
+ };
19
+ defaultRole: "admin" | "developer" | "billing";
20
+ /** Members other than owners sign in only through it. API keys are unaffected. */
21
+ requireSso: boolean;
22
+ createdAt: string;
23
+ };
24
+ export type SsoStatus = {
25
+ connections: SsoConnection[];
26
+ scim: {
27
+ tokens: number;
28
+ users: number;
29
+ activeUsers: number;
30
+ groups: {
31
+ id: string;
32
+ displayName: string;
33
+ role: string | null;
34
+ }[];
35
+ };
36
+ /** Where an owner changes single sign-on. */
37
+ manage: string;
38
+ };
39
+ /** `runtime.sso.get()`: the account's single sign-on and SCIM directory sync.
40
+ * Read only; an owner changes it on the website. Needs a key made by an owner
41
+ * or admin. */
42
+ export declare function sso(t: Transport): {
43
+ get: (options?: RequestOptions) => Promise<SsoStatus>;
44
+ };
@@ -0,0 +1,8 @@
1
+ /** `runtime.sso.get()`: the account's single sign-on and SCIM directory sync.
2
+ * Read only; an owner changes it on the website. Needs a key made by an owner
3
+ * or admin. */
4
+ export function sso(t) {
5
+ return {
6
+ get: (options) => t.json({ method: "GET", path: "/v1/sso", ...options }),
7
+ };
8
+ }
@@ -13,8 +13,16 @@ export type Volume = {
13
13
  filesystem: "ext4";
14
14
  usedMiB: number | null;
15
15
  measuredAt: string | null;
16
- /** Always false: a volume lives on one host and is not copied off it. */
17
- backedUp: false;
16
+ /** True once a backup of it has been copied off its host and checked. */
17
+ backedUp: boolean;
18
+ /** Daily backups, how long each is kept, and when the newest was taken. */
19
+ backups: {
20
+ daily: boolean;
21
+ retentionDays: number;
22
+ lastReadyAt: string | null;
23
+ };
24
+ /** The backup this volume was restored from, if any. */
25
+ restoredFrom: string | null;
18
26
  attachments: {
19
27
  sandboxId: string;
20
28
  mode: "rw" | "snapshot";
@@ -26,15 +34,42 @@ export type Volume = {
26
34
  readyAt: string | null;
27
35
  };
28
36
  export type CreateVolume = {
29
- sizeMiB: number;
37
+ /** Required unless restoring: a restore is the backup's size. */
38
+ sizeMiB?: number;
39
+ /** A ready backup to restore into the new volume, on any host in its region. */
40
+ fromBackup?: string;
30
41
  name?: string;
31
42
  labels?: Record<string, string>;
32
43
  region?: string;
33
44
  };
45
+ export type VolumeBackupState = "pending" | "ready" | "failed" | "deleting" | "deleted";
46
+ /** A point-in-time copy of a volume, kept off its host. */
47
+ export type VolumeBackup = {
48
+ id: string;
49
+ kind: "volume-backup";
50
+ status: string;
51
+ state: VolumeBackupState;
52
+ name: string | null;
53
+ labels: Record<string, string>;
54
+ region: string;
55
+ volumeId: string;
56
+ sizeMiB: number;
57
+ trigger: "manual" | "daily";
58
+ retentionDays: number;
59
+ logicalBytes: number | null;
60
+ storedBytes: number | null;
61
+ meteredBytes: number | null;
62
+ error: string | null;
63
+ createdAt: string;
64
+ readyAt: string | null;
65
+ expiresAt: string;
66
+ };
34
67
  /** Persistent disks: create once, attach when creating a sandbox with
35
- * `volumes: [{ volumeId: v.id, path: "/data" }]`. Not backed up off its host. */
68
+ * `volumes: [{ volumeId: v.id, path: "/data" }]`. Backed up off their host
69
+ * daily unless that is turned off; restore one with `create({ fromBackup })`. */
36
70
  export declare function volumes(t: Transport): {
37
- /** Create and wait (up to 10 s) until it is ready. */
71
+ /** Create and wait (up to 10 s) until it is ready. With `fromBackup`, a
72
+ * restore; it is ready once the backup is downloaded and checked. */
38
73
  create: (input: CreateVolume, options?: RequestOptions) => Promise<Volume>;
39
74
  get: (id: string, options?: RequestOptions) => Promise<Volume>;
40
75
  list: (query?: {
@@ -44,4 +79,32 @@ export declare function volumes(t: Transport): {
44
79
  cursor?: string;
45
80
  }, options?: RequestOptions) => Promise<Page<Volume>>;
46
81
  delete: (id: string, options?: RequestOptions) => Promise<Volume>;
82
+ /** Back a volume up now, off its host, and wait (up to a minute, or
83
+ * `wait` seconds) for it to be ready. */
84
+ backup: (id: string, input?: {
85
+ name?: string;
86
+ labels?: Record<string, string>;
87
+ retentionDays?: number;
88
+ }, options?: RequestOptions & {
89
+ wait?: number;
90
+ }) => Promise<VolumeBackup>;
91
+ /** Daily backups on or off, and how many days each is kept. */
92
+ setBackupPolicy: (id: string, input: {
93
+ daily?: boolean;
94
+ retentionDays?: number;
95
+ }, options?: RequestOptions) => Promise<Volume>;
96
+ /** Backups, newest first; `{ volumeId }` for one volume's. */
97
+ backups: (query?: {
98
+ volumeId?: string;
99
+ state?: Exclude<VolumeBackupState, "deleted">;
100
+ limit?: number;
101
+ cursor?: string;
102
+ }, options?: RequestOptions) => Promise<Page<VolumeBackup>>;
103
+ getBackup: (id: string, options?: RequestOptions) => Promise<VolumeBackup>;
104
+ deleteBackup: (id: string, options?: RequestOptions) => Promise<VolumeBackup>;
105
+ /** A new volume from a backup: `create({ fromBackup: id })`. */
106
+ restore: (backupId: string, input?: {
107
+ name?: string;
108
+ labels?: Record<string, string>;
109
+ }, options?: RequestOptions) => Promise<Volume>;
47
110
  };
@@ -1,8 +1,10 @@
1
1
  import { Page } from "../page.js";
2
+ const enc = encodeURIComponent;
2
3
  /** Persistent disks: create once, attach when creating a sandbox with
3
- * `volumes: [{ volumeId: v.id, path: "/data" }]`. Not backed up off its host. */
4
+ * `volumes: [{ volumeId: v.id, path: "/data" }]`. Backed up off their host
5
+ * daily unless that is turned off; restore one with `create({ fromBackup })`. */
4
6
  export function volumes(t) {
5
- const get = (id, options) => t.json({ method: "GET", path: `/v1/volumes/${encodeURIComponent(id)}`, ...options });
7
+ const get = (id, options) => t.json({ method: "GET", path: `/v1/volumes/${enc(id)}`, ...options });
6
8
  const list = async (query = {}, options) => {
7
9
  const page = await t.json({
8
10
  method: "GET",
@@ -12,16 +14,62 @@ export function volumes(t) {
12
14
  });
13
15
  return new Page(page.data, page.nextCursor, (cursor) => list({ ...query, cursor }, options));
14
16
  };
17
+ const backups = async (query = {}, options) => {
18
+ const page = await t.json({
19
+ method: "GET",
20
+ path: "/v1/volume-backups",
21
+ query,
22
+ ...options,
23
+ });
24
+ return new Page(page.data, page.nextCursor, (cursor) => backups({ ...query, cursor }, options));
25
+ };
15
26
  return {
16
- /** Create and wait (up to 10 s) until it is ready. */
27
+ /** Create and wait (up to 10 s) until it is ready. With `fromBackup`, a
28
+ * restore; it is ready once the backup is downloaded and checked. */
17
29
  create: (input, options) => t.json({ method: "POST", path: "/v1/volumes", body: input, wait: 10, ...options }),
18
30
  get,
19
31
  list,
20
32
  delete: (id, options) => t.json({
21
33
  method: "POST",
22
- path: `/v1/volumes/${encodeURIComponent(id)}:delete`,
34
+ path: `/v1/volumes/${enc(id)}:delete`,
23
35
  body: {},
24
36
  ...options,
25
37
  }),
38
+ /** Back a volume up now, off its host, and wait (up to a minute, or
39
+ * `wait` seconds) for it to be ready. */
40
+ backup: (id, input = {}, options = {}) => {
41
+ const { wait = 60, ...rest } = options;
42
+ return t.json({
43
+ method: "POST",
44
+ path: `/v1/volumes/${enc(id)}:backup`,
45
+ body: input,
46
+ wait,
47
+ ...rest,
48
+ });
49
+ },
50
+ /** Daily backups on or off, and how many days each is kept. */
51
+ setBackupPolicy: (id, input, options) => t.json({
52
+ method: "POST",
53
+ path: `/v1/volumes/${enc(id)}:backup-policy`,
54
+ body: input,
55
+ ...options,
56
+ }),
57
+ /** Backups, newest first; `{ volumeId }` for one volume's. */
58
+ backups,
59
+ getBackup: (id, options) => t.json({ method: "GET", path: `/v1/volume-backups/${enc(id)}`, ...options }),
60
+ deleteBackup: (id, options) => t.json({
61
+ method: "POST",
62
+ path: `/v1/volume-backups/${enc(id)}:delete`,
63
+ body: {},
64
+ ...options,
65
+ }),
66
+ /** A new volume from a backup: `create({ fromBackup: id })`. */
67
+ restore: (backupId, input = {}, options) => t.json({
68
+ method: "POST",
69
+ path: "/v1/volumes",
70
+ body: { ...input, fromBackup: backupId },
71
+ wait: 10,
72
+ ...options,
73
+ }),
26
74
  };
27
75
  }
@@ -0,0 +1,91 @@
1
+ import type { RequestOptions, Transport } from "../transport.js";
2
+ export type FileEventType = "create" | "write" | "remove" | "rename" | "chmod";
3
+ export type FileEvent = {
4
+ type: FileEventType;
5
+ /** Absolute path in the sandbox. */
6
+ path: string;
7
+ isDir: boolean;
8
+ /** rename: where it was. */
9
+ oldPath?: string;
10
+ /** write: how many writes were folded into this one event. */
11
+ count?: number;
12
+ };
13
+ export type WatchNotice = {
14
+ k: "overflow";
15
+ dropped: number | null;
16
+ reason: "kernel" | "rate";
17
+ } | {
18
+ k: "limit";
19
+ watches: number;
20
+ } | {
21
+ k: "lost";
22
+ bytes: number;
23
+ };
24
+ export type WatchOptions = Omit<RequestOptions, "timeoutMs"> & {
25
+ recursive?: boolean;
26
+ /** Only these event types; all five by default. */
27
+ events?: FileEventType[];
28
+ /** Globs relative to the path: only matching paths are reported. */
29
+ include?: string[];
30
+ /** Globs relative to the path. An excluded directory is not watched at all. */
31
+ exclude?: string[];
32
+ /** Events are gathered this long and delivered together. Default 100. */
33
+ batchMs?: number;
34
+ /** The watch ends by itself after running this long. Default 1 hour. */
35
+ timeoutMs?: number;
36
+ maxWatches?: number;
37
+ /** A batch at a time, rather than one call per event. */
38
+ onBatch?: (events: FileEvent[]) => void | Promise<void>;
39
+ /** Events were lost (a flood past the rate cap, the kernel's queue, or a
40
+ * reader that fell more than 1 MiB behind): rescan what you care about. */
41
+ onNotice?: (notice: WatchNotice) => void;
42
+ /** The watch stopped: "stopped", "timeout", "root-removed", or "paused"
43
+ * (the sandbox paused; call `resume()` once it runs again: nothing is lost). */
44
+ onExit?: (reason: string) => void;
45
+ };
46
+ export type WatchInfo = {
47
+ id: string;
48
+ path: string;
49
+ processId: string;
50
+ state: string;
51
+ startedAt: number;
52
+ };
53
+ /** A running watch. Events arrive through the callbacks until `stop()`. */
54
+ export declare class WatchHandle {
55
+ #private;
56
+ private readonly t;
57
+ private readonly base;
58
+ readonly id: string;
59
+ readonly path: string;
60
+ private readonly options;
61
+ private readonly onEvent?;
62
+ cursor: number;
63
+ constructor(t: Transport, base: string, id: string, path: string, cursor: number, options: WatchOptions, onEvent?: ((event: FileEvent) => void | Promise<void>) | undefined);
64
+ /** Settles when the watch stops delivering (stopped, ended or paused). */
65
+ get done(): Promise<void>;
66
+ /** Start delivering again from where it left off, after a pause. */
67
+ resume(): void;
68
+ /** Stop the watch in the sandbox, and stop delivering. */
69
+ stop(options?: RequestOptions): Promise<void>;
70
+ /** @internal */
71
+ begin(): this;
72
+ }
73
+ /** `sbx.files.watch(path, onEvent, options)`: E2B's `watchDir`, with
74
+ * filters, batches, and a watch that survives a pause without losing events. */
75
+ export declare function watchDirectory(t: Transport, sandboxId: string, path: string, onEvent?: (event: FileEvent) => void | Promise<void>, options?: WatchOptions): Promise<WatchHandle>;
76
+ export declare function sandboxWatches(t: Transport, sandboxId: string): {
77
+ list: (options?: RequestOptions) => Promise<WatchInfo[]>;
78
+ /** Events after a cursor, without a stream: for polling. */
79
+ read: (id: string, cursor?: number, input?: {
80
+ waitMs?: number;
81
+ }, options?: RequestOptions) => Promise<{
82
+ events: FileEvent[];
83
+ notices: Record<string, unknown>[];
84
+ nextCursor: number;
85
+ lostBytes: number;
86
+ ended: boolean;
87
+ }>;
88
+ stop: (id: string, options?: RequestOptions) => Promise<{
89
+ stopped: boolean;
90
+ }>;
91
+ };
@@ -0,0 +1,135 @@
1
+ /** A running watch. Events arrive through the callbacks until `stop()`. */
2
+ export class WatchHandle {
3
+ t;
4
+ base;
5
+ id;
6
+ path;
7
+ options;
8
+ onEvent;
9
+ cursor;
10
+ #stopped = false;
11
+ #following;
12
+ #abort = new AbortController();
13
+ constructor(t, base, id, path, cursor, options, onEvent) {
14
+ this.t = t;
15
+ this.base = base;
16
+ this.id = id;
17
+ this.path = path;
18
+ this.options = options;
19
+ this.onEvent = onEvent;
20
+ this.cursor = cursor;
21
+ }
22
+ /** Settles when the watch stops delivering (stopped, ended or paused). */
23
+ get done() {
24
+ return this.#following ?? Promise.resolve();
25
+ }
26
+ /** Start delivering again from where it left off, after a pause. */
27
+ resume() {
28
+ if (this.#stopped)
29
+ throw new Error("This watch was stopped");
30
+ this.#abort = new AbortController();
31
+ this.begin();
32
+ }
33
+ /** Stop the watch in the sandbox, and stop delivering. */
34
+ async stop(options) {
35
+ if (this.#stopped)
36
+ return;
37
+ this.#stopped = true;
38
+ this.#abort.abort();
39
+ await this.t
40
+ .json({ method: "DELETE", path: `${this.base}/${encodeURIComponent(this.id)}`, ...options })
41
+ .catch(() => undefined);
42
+ await this.#following?.catch(() => undefined);
43
+ this.options.onExit?.("stopped");
44
+ }
45
+ async #follow() {
46
+ for (;;) {
47
+ let next = "exit";
48
+ try {
49
+ for await (const event of this.t.events({
50
+ method: "GET",
51
+ path: `${this.base}/${encodeURIComponent(this.id)}/events`,
52
+ query: { cursor: this.cursor, follow: true },
53
+ signal: this.#abort.signal,
54
+ timeoutMs: 150_000,
55
+ })) {
56
+ this.cursor = event.cursor ?? this.cursor;
57
+ if (event.k === "events") {
58
+ if (this.options.onBatch)
59
+ await this.options.onBatch(event.events);
60
+ if (this.onEvent)
61
+ for (const item of event.events)
62
+ await this.onEvent(item);
63
+ }
64
+ else if (event.k === "continue")
65
+ next = "continue";
66
+ else if (event.k === "end" || event.k === "paused") {
67
+ if (!this.#stopped)
68
+ this.options.onExit?.(event.k === "paused" ? "paused" : event.reason);
69
+ return;
70
+ }
71
+ else if (event.k === "failure")
72
+ throw new Error(`${event.code}: ${event.message}`);
73
+ else
74
+ this.options.onNotice?.(event);
75
+ }
76
+ }
77
+ catch (error) {
78
+ if (this.#stopped || this.#abort.signal.aborted)
79
+ return;
80
+ throw error;
81
+ }
82
+ if (next !== "continue" || this.#stopped)
83
+ return;
84
+ }
85
+ }
86
+ /** @internal */
87
+ begin() {
88
+ this.#following = this.#follow();
89
+ // An error surfaces through `done`; nothing is left unhandled meanwhile.
90
+ this.#following.catch(() => undefined);
91
+ return this;
92
+ }
93
+ }
94
+ /** `sbx.files.watch(path, onEvent, options)`: E2B's `watchDir`, with
95
+ * filters, batches, and a watch that survives a pause without losing events. */
96
+ export function watchDirectory(t, sandboxId, path, onEvent, options = {}) {
97
+ const base = `/v1/sandboxes/${encodeURIComponent(sandboxId)}/files/watches`;
98
+ const { recursive, events, include, exclude, batchMs, timeoutMs, maxWatches, idempotencyKey, signal, } = options;
99
+ return t
100
+ .json({
101
+ method: "POST",
102
+ path: base,
103
+ body: {
104
+ path,
105
+ ...(recursive !== undefined ? { recursive } : {}),
106
+ ...(events ? { events } : {}),
107
+ ...(include ? { include } : {}),
108
+ ...(exclude ? { exclude } : {}),
109
+ ...(batchMs !== undefined ? { batchMs } : {}),
110
+ ...(timeoutMs !== undefined ? { timeoutMs } : {}),
111
+ ...(maxWatches !== undefined ? { maxWatches } : {}),
112
+ },
113
+ ...(idempotencyKey ? { idempotencyKey } : {}),
114
+ ...(signal ? { signal } : {}),
115
+ })
116
+ .then((watch) => new WatchHandle(t, base, watch.id, watch.path, watch.cursor ?? 0, options, onEvent).begin());
117
+ }
118
+ export function sandboxWatches(t, sandboxId) {
119
+ const base = `/v1/sandboxes/${encodeURIComponent(sandboxId)}/files/watches`;
120
+ return {
121
+ list: async (options) => (await t.json({ method: "GET", path: base, ...options })).data,
122
+ /** Events after a cursor, without a stream: for polling. */
123
+ read: (id, cursor = 0, input = {}, options) => t.json({
124
+ method: "GET",
125
+ path: `${base}/${encodeURIComponent(id)}/events`,
126
+ query: { cursor, ...(input.waitMs !== undefined ? { waitMs: input.waitMs } : {}) },
127
+ ...options,
128
+ }),
129
+ stop: (id, options) => t.json({
130
+ method: "DELETE",
131
+ path: `${base}/${encodeURIComponent(id)}`,
132
+ ...options,
133
+ }),
134
+ };
135
+ }
package/dist/sandbox.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { Page } from "./page.js";
2
+ import { type FileEvent, type WatchHandle, type WatchOptions } from "./products/watch.js";
2
3
  import type { RequestOptions, Transport } from "./transport.js";
3
4
  import type { CommandResult, ExecOptions, FileEntry, OutputEvent, KeepAliveOptions, ProcessInfo, SandboxInfo, SandboxSettings } from "./types.js";
4
5
  import { type SandboxExtensions } from "./products/index.js";
@@ -175,12 +176,41 @@ export declare class Files {
175
176
  private readonly t;
176
177
  private readonly sandboxId;
177
178
  constructor(t: Transport, sandboxId: string);
179
+ /** Watch a directory: `onEvent` gets create, write, remove, rename and
180
+ * chmod events until `handle.stop()`. E2B's `watchDir`, plus filters,
181
+ * batches, and a watch that loses nothing across a pause:
182
+ *
183
+ * const watch = await sbx.files.watch("/workspace", (e) => console.log(e.type, e.path),
184
+ * { recursive: true, exclude: ["node_modules"] });
185
+ * ...
186
+ * await watch.stop();
187
+ */
188
+ watch(path: string, onEvent?: (event: FileEvent) => void | Promise<void>, options?: WatchOptions): Promise<WatchHandle>;
189
+ /** The watches running in the sandbox, and polling reads by cursor. */
190
+ get watches(): {
191
+ list: (options?: RequestOptions) => Promise<import("./products/watch.js").WatchInfo[]>;
192
+ read: (id: string, cursor?: number, input?: {
193
+ waitMs?: number;
194
+ }, options?: RequestOptions) => Promise<{
195
+ events: FileEvent[];
196
+ notices: Record<string, unknown>[];
197
+ nextCursor: number;
198
+ lostBytes: number;
199
+ ended: boolean;
200
+ }>;
201
+ stop: (id: string, options?: RequestOptions) => Promise<{
202
+ stopped: boolean;
203
+ }>;
204
+ };
178
205
  /** A file's bytes, any size. */
179
206
  read(path: string, options?: RequestOptions): Promise<Uint8Array>;
180
207
  readText(path: string, options?: RequestOptions): Promise<string>;
181
208
  /** Writes a file of any size, atomically, making parent directories. Large
182
- * ones go in parallel 1 MiB chunks checked against their SHA-256. */
183
- write(path: string, data: string | Uint8Array, options?: RequestOptions): Promise<{
209
+ * ones go in parallel 1 MiB chunks checked against their SHA-256. `mode`
210
+ * sets its permissions (0o755 for a program); 0o644 when left out. */
211
+ write(path: string, data: string | Uint8Array, writeOptions?: RequestOptions & {
212
+ mode?: number;
213
+ }): Promise<{
184
214
  path: string;
185
215
  size: number;
186
216
  }>;