@watasu/sdk 0.1.6 → 0.1.24

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
@@ -16,8 +16,8 @@ Set `WATASU_API_KEY` before using the SDK.
16
16
  import { Sandbox } from '@watasu/sdk'
17
17
 
18
18
  const sbx = await Sandbox.create()
19
- await sbx.files.write('/home/user/a.js', 'console.log(2 + 2)')
20
- const result = await sbx.commands.run('node /home/user/a.js')
19
+ await sbx.filesystem.write('/home/user/a.js', 'console.log(2 + 2)')
20
+ const result = await sbx.process.startAndWait('node /home/user/a.js')
21
21
  console.log(result.stdout)
22
22
  console.log(await sbx.isRunning())
23
23
  await sbx.kill()
@@ -26,11 +26,49 @@ await sbx.kill()
26
26
  `Sandbox.create` and `Sandbox.connect` return only after the Watasu API supplies
27
27
  a usable data-plane session. The SDK does not poll sandbox readiness.
28
28
 
29
+ ```ts
30
+ await sbx.betaPause()
31
+ await sbx.resume({ timeoutMs: 300_000 })
32
+ ```
33
+
34
+ ## MCP Gateway
35
+
36
+ ```ts
37
+ const sbx = await Sandbox.create({
38
+ mcp: {
39
+ github: {
40
+ command: 'github-mcp-server',
41
+ args: ['stdio'],
42
+ env: { GITHUB_TOKEN: process.env.GITHUB_TOKEN },
43
+ },
44
+ },
45
+ })
46
+
47
+ console.log(sbx.getMcpUrl())
48
+ console.log(await sbx.getMcpToken())
49
+ ```
50
+
51
+ ## Listing Sandboxes
52
+
53
+ ```ts
54
+ import { Sandbox } from '@watasu/sdk'
55
+
56
+ const paginator = Sandbox.list({
57
+ query: { metadata: { purpose: 'ci' }, state: ['running'] },
58
+ limit: 20,
59
+ })
60
+
61
+ for (const sandbox of await paginator.listItems()) {
62
+ console.log(sandbox.sandboxId, sandbox.state)
63
+ }
64
+ ```
65
+
29
66
  ## Git, Watch, PTY, And Signed File URLs
30
67
 
31
68
  ```ts
32
69
  const sbx = await Sandbox.create()
33
70
 
71
+ await sbx.git.init('/workspace/new-project', { initialBranch: 'main' })
34
72
  await sbx.git.clone('https://github.com/acme/project.git', {
35
73
  path: '/workspace/project',
36
74
  branch: 'main',
@@ -52,17 +90,26 @@ await sbx.git.push('/workspace/project', {
52
90
  branch: 'feature/docs',
53
91
  setUpstream: true,
54
92
  })
93
+ const remoteUrl = await sbx.git.remoteGet('/workspace/project', 'origin')
94
+ await sbx.git.restore('/workspace/project', { paths: ['README.md'] })
95
+ await sbx.git.reset('/workspace/project', { mode: 'hard', target: 'HEAD' })
55
96
 
56
- const watcher = await sbx.files.watchDir('/workspace/project', (event) => {
97
+ await sbx.filesystem.writeFiles([
98
+ { path: '/workspace/project/a.txt', data: 'alpha' },
99
+ { path: '/workspace/project/b.bin', data: new Uint8Array([0, 1, 2]) },
100
+ ])
101
+
102
+ const watcher = sbx.filesystem.watchDir('/workspace/project')
103
+ watcher.addEventListener((event) => {
57
104
  console.log(event.type, event.path)
58
- }, { recursive: true })
105
+ })
106
+ await watcher.start({ recursive: true })
59
107
 
60
- const terminal = await sbx.pty.create({
61
- cols: 100,
62
- rows: 30,
108
+ const terminal = await sbx.terminal.start({
109
+ size: { cols: 100, rows: 30 },
63
110
  onData: (data) => process.stdout.write(data),
64
111
  })
65
- await terminal.sendStdin('echo hello\n')
112
+ await terminal.sendData('echo hello\n')
66
113
 
67
114
  const uploadUrl = await sbx.uploadUrl('/workspace/input.bin')
68
115
  const downloadUrl = await sbx.downloadUrl('/workspace/output.bin')
@@ -72,6 +119,54 @@ await terminal.kill()
72
119
  await sbx.kill()
73
120
  ```
74
121
 
122
+ ## Network Policy
123
+
124
+ ```ts
125
+ const sbx = await Sandbox.create({
126
+ network: {
127
+ allowOut: ['pypi.org:443'],
128
+ denyOut: ['169.254.169.254'],
129
+ },
130
+ })
131
+
132
+ await sbx.updateNetwork({
133
+ allowInternetAccess: false,
134
+ allowPackageRegistryAccess: true,
135
+ allowOut: ['pypi.org:443', 'registry.npmjs.org:443'],
136
+ })
137
+ ```
138
+
139
+ ## Template Builds
140
+
141
+ ```ts
142
+ import { Template } from '@watasu/sdk'
143
+
144
+ const template = Template()
145
+ .fromPythonImage('3.12')
146
+ .copy('requirements.txt', '/workspace/requirements.txt')
147
+ .aptInstall(['git'])
148
+ .pipInstall(['pytest'])
149
+ .setEnvs({ PIP_DISABLE_PIP_VERSION_CHECK: '1' })
150
+ .runCmd('echo ready')
151
+
152
+ const build = await Template.buildInBackground(template, 'python-ci:stable', {
153
+ tags: ['stable'],
154
+ cpuCount: 2,
155
+ memoryMB: 2048,
156
+ })
157
+ const status = await Template.getBuildStatus(build)
158
+
159
+ await Template.assignTags('python-ci:stable', ['prod'])
160
+ console.log(await Template.exists('python-ci'))
161
+ ```
162
+
163
+ Template names resolve server-side. `python-ci` starts the latest ready build;
164
+ `python-ci:stable` starts the tagged build.
165
+
166
+ `Template({ fileContextPath: process.cwd() }).fromDockerfile('Dockerfile')`
167
+ parses common `FROM`, `WORKDIR`, `COPY`, `RUN`, `ENV`, `CMD`, and `ENTRYPOINT`
168
+ instructions into Watasu's package-spec builder.
169
+
75
170
  ## Metrics And Snapshots
76
171
 
77
172
  ```ts
@@ -81,6 +176,7 @@ const sbx = await Sandbox.create()
81
176
  const metrics = await sbx.getMetrics()
82
177
  const snapshot = await sbx.createSnapshot({ name: 'ready' })
83
178
  const snapshots = await sbx.listSnapshots().nextItems()
179
+ const allSnapshots = await Sandbox.listSnapshots({ limit: 100 }).nextItems()
84
180
  const restored = await sbx.restore({ snapshotId: snapshot.snapshotId })
85
181
  await sbx.deleteSnapshot(snapshot.snapshotId)
86
182
  await sbx.kill()
@@ -29,14 +29,26 @@ export interface ProcessInfo {
29
29
  export interface CommandStartOpts {
30
30
  /** Return a `CommandHandle` immediately instead of waiting for exit. */
31
31
  background?: boolean;
32
+ /** Executable to start directly. When omitted, `cmd` strings run through a login shell. */
33
+ cmd?: string;
34
+ /** Arguments for `cmd` when starting a direct executable. */
35
+ args?: string[];
32
36
  cwd?: string;
37
+ /** Deprecated alias for `cwd`. */
38
+ rootDir?: string;
33
39
  user?: string;
34
40
  envs?: Record<string, string>;
41
+ /** Alias for `envs`. */
42
+ envVars?: Record<string, string>;
35
43
  onStdout?: (data: string) => void | Promise<void>;
36
44
  onStderr?: (data: string) => void | Promise<void>;
37
45
  onPty?: (data: Uint8Array) => void | Promise<void>;
46
+ onExit?: (exitCode: number) => void | Promise<void>;
38
47
  stdin?: boolean;
39
48
  timeoutMs?: number;
49
+ /** Alias for `timeoutMs`. */
50
+ timeout?: number;
51
+ processID?: string;
40
52
  requestTimeoutMs?: number;
41
53
  }
42
54
  /** Live handle for one sandbox process stream. */
@@ -48,28 +60,31 @@ export declare class CommandHandle implements Partial<CommandResult> {
48
60
  private readonly onStdout?;
49
61
  private readonly onStderr?;
50
62
  private readonly onPty?;
63
+ private readonly onExit?;
51
64
  private _stdout;
52
65
  private _stderr;
53
66
  private result?;
54
67
  private readonly pending;
55
- constructor(pid: number | string, socket: ProcessSocket, handleKill: () => Promise<boolean>, events: AsyncIterable<ProcessFrame>, onStdout?: ((data: string) => void | Promise<void>) | undefined, onStderr?: ((data: string) => void | Promise<void>) | undefined, onPty?: ((data: Uint8Array) => void | Promise<void>) | undefined);
68
+ constructor(pid: number | string, socket: ProcessSocket, handleKill: () => Promise<boolean>, events: AsyncIterable<ProcessFrame>, onStdout?: ((data: string) => void | Promise<void>) | undefined, onStderr?: ((data: string) => void | Promise<void>) | undefined, onPty?: ((data: Uint8Array) => void | Promise<void>) | undefined, onExit?: ((exitCode: number) => void | Promise<void>) | undefined);
56
69
  get stdout(): string;
57
70
  get stderr(): string;
58
71
  get exitCode(): number | undefined;
59
72
  get error(): string | undefined;
60
73
  /** Wait until the process exits and return captured output. */
61
- wait(): Promise<CommandResult>;
74
+ wait(timeoutMs?: number): Promise<CommandResult>;
62
75
  /** Kill the process. */
63
76
  kill(): Promise<boolean>;
64
77
  /** Send stdin bytes or text to the process. */
65
78
  sendStdin(data: string | Uint8Array): Promise<void>;
79
+ /** Close the stdin stream and signal EOF to the process. */
80
+ closeStdin(): Promise<void>;
66
81
  /** Resize the attached PTY stream when this handle was created as a PTY. */
67
82
  resize(size: {
68
83
  cols: number;
69
84
  rows: number;
70
85
  }): Promise<void>;
71
86
  /** Detach the local stream without killing the process. */
72
- disconnect(): void;
87
+ disconnect(): Promise<void>;
73
88
  private handleEvents;
74
89
  }
75
90
  /** Command runner for a sandbox data-plane session. */
@@ -78,6 +93,8 @@ export declare class Commands {
78
93
  private readonly config;
79
94
  private readonly sandboxEnvs;
80
95
  constructor(dataPlane: DataPlaneClient, config: ConnectionConfig, sandboxEnvs?: Record<string, string>);
96
+ /** Whether this runtime supports stdin EOF frames. */
97
+ get supportsStdinClose(): boolean;
81
98
  /** List processes currently known by the sandbox runtime. */
82
99
  list(opts?: {
83
100
  requestTimeoutMs?: number;
@@ -90,11 +107,16 @@ export declare class Commands {
90
107
  sendStdin(pid: number | string, data: string | Uint8Array, opts?: {
91
108
  requestTimeoutMs?: number;
92
109
  }): Promise<void>;
110
+ /** Attach to a process and close stdin, signalling EOF. */
111
+ closeStdin(pid: number | string, opts?: {
112
+ requestTimeoutMs?: number;
113
+ }): Promise<void>;
93
114
  run(cmd: string, opts: CommandStartOpts & {
94
115
  background: true;
95
116
  }): Promise<CommandHandle>;
96
117
  run(cmd: string, opts?: CommandStartOpts): Promise<CommandResult>;
97
118
  /** Reconnect to a live process stream by pid. */
98
119
  connect(pid: number | string, opts?: CommandStartOpts): Promise<CommandHandle>;
99
- private start;
120
+ /** Start a command and return a live handle immediately. */
121
+ start(cmd: string, opts?: CommandStartOpts): Promise<CommandHandle>;
100
122
  }
package/dist/commands.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { ProcessSocket, base64DecodeBytes, base64DecodeText } from './processSocket.js';
2
- import { SandboxError } from './errors.js';
2
+ import { SandboxError, TimeoutError } from './errors.js';
3
3
  /** Error thrown by `CommandHandle.wait()` when a process exits non-zero. */
4
4
  export class CommandExitError extends SandboxError {
5
5
  result;
@@ -22,11 +22,12 @@ export class CommandHandle {
22
22
  onStdout;
23
23
  onStderr;
24
24
  onPty;
25
+ onExit;
25
26
  _stdout = '';
26
27
  _stderr = '';
27
28
  result;
28
29
  pending;
29
- constructor(pid, socket, handleKill, events, onStdout, onStderr, onPty) {
30
+ constructor(pid, socket, handleKill, events, onStdout, onStderr, onPty, onExit) {
30
31
  this.pid = pid;
31
32
  this.socket = socket;
32
33
  this.handleKill = handleKill;
@@ -34,6 +35,7 @@ export class CommandHandle {
34
35
  this.onStdout = onStdout;
35
36
  this.onStderr = onStderr;
36
37
  this.onPty = onPty;
38
+ this.onExit = onExit;
37
39
  this.pending = this.handleEvents();
38
40
  }
39
41
  get stdout() { return this._stdout; }
@@ -41,8 +43,8 @@ export class CommandHandle {
41
43
  get exitCode() { return this.result?.exitCode; }
42
44
  get error() { return this.result?.error; }
43
45
  /** Wait until the process exits and return captured output. */
44
- async wait() {
45
- await this.pending;
46
+ async wait(timeoutMs) {
47
+ await waitFor(this.pending, timeoutMs);
46
48
  if (!this.result)
47
49
  throw new SandboxError('Command ended without an exit event');
48
50
  if (this.result.exitCode !== 0)
@@ -57,12 +59,16 @@ export class CommandHandle {
57
59
  async sendStdin(data) {
58
60
  this.socket.sendStdin(data);
59
61
  }
62
+ /** Close the stdin stream and signal EOF to the process. */
63
+ async closeStdin() {
64
+ this.socket.closeStdin();
65
+ }
60
66
  /** Resize the attached PTY stream when this handle was created as a PTY. */
61
67
  async resize(size) {
62
68
  this.socket.sendJson({ type: 'resize', cols: size.cols, rows: size.rows });
63
69
  }
64
70
  /** Detach the local stream without killing the process. */
65
- disconnect() {
71
+ async disconnect() {
66
72
  this.socket.close();
67
73
  }
68
74
  async handleEvents() {
@@ -88,12 +94,14 @@ export class CommandHandle {
88
94
  await this.onPty?.(bytes);
89
95
  }
90
96
  else if (type === 'exit') {
97
+ const exitCode = Number(frame.exit_code ?? frame.exitCode ?? 0);
91
98
  this.result = {
92
- exitCode: Number(frame.exit_code ?? frame.exitCode ?? 0),
99
+ exitCode,
93
100
  error: typeof frame.error === 'string' ? frame.error : undefined,
94
101
  stdout: this._stdout,
95
102
  stderr: this._stderr,
96
103
  };
104
+ await this.onExit?.(exitCode);
97
105
  return;
98
106
  }
99
107
  else if (type === 'error') {
@@ -116,6 +124,10 @@ export class Commands {
116
124
  this.config = config;
117
125
  this.sandboxEnvs = sandboxEnvs;
118
126
  }
127
+ /** Whether this runtime supports stdin EOF frames. */
128
+ get supportsStdinClose() {
129
+ return true;
130
+ }
119
131
  /** List processes currently known by the sandbox runtime. */
120
132
  async list(opts = {}) {
121
133
  const payload = await this.dataPlane.getJson('/runtime/v1/process', opts);
@@ -137,7 +149,17 @@ export class Commands {
137
149
  await handle.sendStdin(data);
138
150
  }
139
151
  finally {
140
- handle.disconnect();
152
+ await handle.disconnect();
153
+ }
154
+ }
155
+ /** Attach to a process and close stdin, signalling EOF. */
156
+ async closeStdin(pid, opts = {}) {
157
+ const handle = await this.connect(pid, opts);
158
+ try {
159
+ await handle.closeStdin();
160
+ }
161
+ finally {
162
+ await handle.disconnect();
141
163
  }
142
164
  }
143
165
  /** Run a shell command over the WebSocket process runtime. */
@@ -145,7 +167,7 @@ export class Commands {
145
167
  const handle = await this.start(cmd, opts);
146
168
  if (opts.background)
147
169
  return handle;
148
- return handle.wait();
170
+ return handle.wait(opts.timeoutMs ?? opts.timeout);
149
171
  }
150
172
  /** Reconnect to a live process stream by pid. */
151
173
  async connect(pid, opts = {}) {
@@ -154,27 +176,44 @@ export class Commands {
154
176
  const actualPid = framePid(first) ?? pid;
155
177
  return new CommandHandle(actualPid, socket, () => this.kill(actualPid), socket, opts.onStdout, opts.onStderr, opts.onPty);
156
178
  }
157
- async start(cmd, opts) {
179
+ /** Start a command and return a live handle immediately. */
180
+ async start(cmd, opts = {}) {
158
181
  const socket = await new ProcessSocket(this.dataPlane.baseUrl, this.dataPlane.token, '/runtime/v1/process', opts.requestTimeoutMs ?? this.config.requestTimeoutMs).connect();
159
- const environment = { ...this.sandboxEnvs, ...(opts.envs ?? {}) };
182
+ const environment = { ...this.sandboxEnvs, ...(opts.envVars ?? opts.envs ?? {}) };
183
+ const processConfig = processStartConfig(cmd, opts);
160
184
  socket.sendJson({
161
185
  type: 'start',
162
- cmd: '/bin/bash',
163
- args: ['-l', '-c', cmd],
164
- cwd: opts.cwd,
186
+ id: opts.processID,
187
+ cmd: processConfig.cmd,
188
+ args: processConfig.args,
189
+ cwd: opts.cwd ?? opts.rootDir,
165
190
  user: opts.user,
166
191
  environment,
167
192
  envs: environment,
168
193
  stdin: opts.stdin ?? false,
169
- timeout_ms: opts.timeoutMs ?? 60_000,
194
+ timeout_ms: opts.timeoutMs ?? opts.timeout ?? 60_000,
170
195
  });
171
196
  const first = await nextStarted(socket);
172
197
  const pid = framePid(first);
173
198
  if (pid === undefined)
174
199
  throw new SandboxError('process started frame did not include pid');
175
- return new CommandHandle(pid, socket, () => this.kill(pid), withFirst(first, socket), opts.onStdout, opts.onStderr, opts.onPty);
200
+ return new CommandHandle(pid, socket, () => this.kill(pid), withFirst(first, socket), opts.onStdout, opts.onStderr, opts.onPty, opts.onExit);
176
201
  }
177
202
  }
203
+ function processStartConfig(cmd, opts) {
204
+ if (opts.args !== undefined || opts.cmd !== undefined) {
205
+ return { cmd: opts.cmd ?? cmd, args: opts.args ?? [] };
206
+ }
207
+ return { cmd: '/bin/bash', args: ['-l', '-c', cmd] };
208
+ }
209
+ function waitFor(promise, timeoutMs) {
210
+ if (timeoutMs === undefined || timeoutMs <= 0)
211
+ return promise;
212
+ return new Promise((resolve, reject) => {
213
+ const timer = setTimeout(() => reject(new TimeoutError()), timeoutMs);
214
+ promise.then(resolve, reject).finally(() => clearTimeout(timer));
215
+ });
216
+ }
178
217
  async function nextStarted(events) {
179
218
  for await (const frame of events) {
180
219
  if (frame.type === 'started')
package/dist/errors.d.ts CHANGED
@@ -7,6 +7,9 @@ export declare class AuthenticationError extends SandboxError {
7
7
  export declare class NotFoundError extends SandboxError {
8
8
  constructor(message?: string);
9
9
  }
10
+ export declare class ConflictError extends SandboxError {
11
+ constructor(message?: string);
12
+ }
10
13
  export declare class TimeoutError extends SandboxError {
11
14
  constructor(message?: string);
12
15
  }
package/dist/errors.js CHANGED
@@ -16,6 +16,12 @@ export class NotFoundError extends SandboxError {
16
16
  this.name = 'NotFoundError';
17
17
  }
18
18
  }
19
+ export class ConflictError extends SandboxError {
20
+ constructor(message = 'Conflict') {
21
+ super(message);
22
+ this.name = 'ConflictError';
23
+ }
24
+ }
19
25
  export class TimeoutError extends SandboxError {
20
26
  constructor(message = 'Request timed out') {
21
27
  super(message);
@@ -76,6 +82,8 @@ export function errorFromResponse(status, payload) {
76
82
  return new AuthenticationError(message);
77
83
  if (status === 404)
78
84
  return new NotFoundError(message);
85
+ if (status === 409)
86
+ return new ConflictError(message);
79
87
  if (status === 408 || status === 504)
80
88
  return new TimeoutError(message);
81
89
  if (status === 422 || status === 400)
@@ -21,6 +21,11 @@ export interface EntryInfo {
21
21
  metadata?: Record<string, string>;
22
22
  }
23
23
  export type WriteInfo = EntryInfo;
24
+ export type WriteData = string | Uint8Array | ArrayBuffer | Blob | ReadableStream<Uint8Array>;
25
+ export interface WriteEntry {
26
+ path: string;
27
+ data: WriteData;
28
+ }
24
29
  export interface FilesystemEvent {
25
30
  type: 'create' | 'write' | 'modify' | 'remove' | 'delete' | 'rename' | string;
26
31
  path: string;
@@ -33,6 +38,17 @@ export interface WatchOpts {
33
38
  requestTimeoutMs?: number;
34
39
  onExit?: (error?: Error) => void | Promise<void>;
35
40
  }
41
+ export interface FilesystemRequestOpts {
42
+ requestTimeoutMs?: number;
43
+ user?: string;
44
+ }
45
+ export interface FilesystemReadOpts extends FilesystemRequestOpts {
46
+ gzip?: boolean;
47
+ }
48
+ export interface FilesystemWriteOpts extends FilesystemRequestOpts {
49
+ gzip?: boolean;
50
+ metadata?: Record<string, string>;
51
+ }
36
52
  /** Live filesystem watcher. Call `stop()` to close the local watch stream. */
37
53
  export declare class WatchHandle {
38
54
  private readonly socket;
@@ -46,22 +62,45 @@ export declare class WatchHandle {
46
62
  wait(): Promise<void>;
47
63
  private pump;
48
64
  }
65
+ /** Lazy filesystem watcher. Add listeners, then call `start()`. */
66
+ export declare class FilesystemWatcher {
67
+ private readonly dataPlane;
68
+ private readonly path;
69
+ private readonly opts;
70
+ private handle?;
71
+ private listeners;
72
+ constructor(dataPlane: DataPlaneClient, path: string, opts?: WatchOpts);
73
+ start(opts?: WatchOpts): Promise<void>;
74
+ stop(): Promise<void>;
75
+ addEventListener(listener: (event: FilesystemEvent) => void | Promise<void>): () => boolean;
76
+ wait(): Promise<void>;
77
+ }
49
78
  /** Filesystem helper for a sandbox data-plane session. */
50
79
  export declare class Filesystem {
51
80
  private readonly dataPlane;
52
81
  constructor(dataPlane: DataPlaneClient);
53
- /** Read a file as UTF-8 text, bytes, or a one-chunk async byte stream. */
54
- read(path: string, opts?: {
55
- format?: 'text' | 'bytes' | 'stream';
56
- requestTimeoutMs?: number;
57
- gzip?: boolean;
58
- }): Promise<string | Uint8Array | AsyncIterable<Uint8Array>>;
59
- /** Write UTF-8 text or bytes to a file. */
60
- write(path: string, data: string | Uint8Array, opts?: {
61
- requestTimeoutMs?: number;
62
- gzip?: boolean;
63
- metadata?: Record<string, string>;
64
- }): Promise<WriteInfo>;
82
+ /** Read file content as text, bytes, a `Blob`, or a `ReadableStream`. */
83
+ read(path: string, opts?: FilesystemReadOpts & {
84
+ format?: 'text';
85
+ }): Promise<string>;
86
+ read(path: string, opts: FilesystemReadOpts & {
87
+ format: 'bytes';
88
+ }): Promise<Uint8Array>;
89
+ read(path: string, opts: FilesystemReadOpts & {
90
+ format: 'blob';
91
+ }): Promise<Blob>;
92
+ read(path: string, opts: FilesystemReadOpts & {
93
+ format: 'stream';
94
+ }): Promise<ReadableStream<Uint8Array>>;
95
+ /** Read a file as raw bytes. */
96
+ readBytes(path: string, opts?: FilesystemReadOpts): Promise<Uint8Array>;
97
+ /** Write UTF-8 text, bytes, browser data objects, or a batch of file entries. */
98
+ write(path: string, data: WriteData, opts?: FilesystemWriteOpts): Promise<WriteInfo>;
99
+ write(files: WriteEntry[], opts?: FilesystemWriteOpts): Promise<WriteInfo[]>;
100
+ /** Write raw bytes to a file. */
101
+ writeBytes(path: string, data: Uint8Array | ArrayBuffer, opts?: FilesystemWriteOpts): Promise<WriteInfo>;
102
+ /** Write several files in one runtime API call. */
103
+ writeFiles(files: WriteEntry[], opts?: FilesystemWriteOpts): Promise<WriteInfo[]>;
65
104
  /** List directory entries below `path`. */
66
105
  list(path: string, opts?: {
67
106
  requestTimeoutMs?: number;
@@ -88,5 +127,6 @@ export declare class Filesystem {
88
127
  requestTimeoutMs?: number;
89
128
  }): Promise<boolean>;
90
129
  /** Start watching a directory for filesystem events. */
91
- watchDir(path: string, onEvent: (event: FilesystemEvent) => void | Promise<void>, opts?: WatchOpts): Promise<WatchHandle>;
130
+ watchDir(path: string): FilesystemWatcher;
131
+ watchDir(path: string, onEvent: (event: FilesystemEvent) => void | Promise<void>, opts?: WatchOpts): Promise<FilesystemWatcher>;
92
132
  }