@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/dist/sandbox.js CHANGED
@@ -1,35 +1,96 @@
1
1
  import { Commands } from './commands.js';
2
2
  import { ConnectionConfig, SESSION_OPERATION_REQUEST_TIMEOUT_MS } from './connectionConfig.js';
3
3
  import { DataPlaneClient, ControlClient } from './transport.js';
4
- import { NotFoundError, SandboxError, unsupported } from './errors.js';
4
+ import { ConflictError, FileNotFoundError, NotFoundError, SandboxError, unsupported } from './errors.js';
5
5
  import { Filesystem } from './filesystem.js';
6
6
  import { Git } from './git.js';
7
7
  import { Pty } from './pty.js';
8
+ import { ProcessManager } from './process.js';
9
+ import { TerminalManager } from './terminal.js';
10
+ /** Paginator for listing sandbox snapshots. */
8
11
  export class SnapshotPaginator {
9
- loadItems;
10
- consumed = false;
12
+ opts;
11
13
  hasNext = true;
12
14
  nextToken;
13
- constructor(loadItems) {
14
- this.loadItems = loadItems;
15
+ constructor(opts = {}) {
16
+ this.opts = opts;
17
+ this.nextToken = opts.nextToken;
15
18
  }
16
- async nextItems() {
17
- if (this.consumed)
19
+ /** Fetch the next page of snapshot metadata. */
20
+ async nextItems(opts = {}) {
21
+ if (!this.hasNext)
18
22
  throw new SandboxError('No more snapshots to fetch');
19
- this.consumed = true;
20
- this.hasNext = false;
21
- return this.loadItems();
23
+ const config = new ConnectionConfig({ ...this.opts, ...opts });
24
+ const control = new ControlClient(config);
25
+ const payload = await control.get(snapshotListPath(this.opts, this.nextToken), {
26
+ requestTimeoutMs: opts.requestTimeoutMs,
27
+ });
28
+ this.nextToken = stringValue(payload.next_token ?? payload.nextToken);
29
+ this.hasNext = this.nextToken !== undefined;
30
+ const snapshots = Array.isArray(payload.snapshots)
31
+ ? payload.snapshots
32
+ : Array.isArray(payload.sandbox_checkpoints) ? payload.sandbox_checkpoints : [];
33
+ return snapshots.map((item) => snapshotInfo(record(item)));
34
+ }
35
+ /** Drain all remaining pages into one list. */
36
+ async listItems(opts = {}) {
37
+ const items = [];
38
+ while (this.hasNext)
39
+ items.push(...await this.nextItems(opts));
40
+ return items;
41
+ }
42
+ }
43
+ /** Paginator for listing sandboxes. */
44
+ export class SandboxPaginator {
45
+ opts;
46
+ hasNext = true;
47
+ nextToken;
48
+ constructor(opts = {}) {
49
+ this.opts = opts;
50
+ this.nextToken = opts.nextToken;
51
+ }
52
+ /** Fetch the next page of sandbox metadata. */
53
+ async nextItems(opts = {}) {
54
+ if (!this.hasNext)
55
+ throw new SandboxError('No more sandboxes to fetch');
56
+ const config = new ConnectionConfig({ ...this.opts, ...opts });
57
+ const control = new ControlClient(config);
58
+ const payload = await control.get(sandboxListPath(this.opts, this.nextToken), {
59
+ requestTimeoutMs: opts.requestTimeoutMs,
60
+ });
61
+ this.nextToken = stringValue(payload.next_token ?? payload.nextToken);
62
+ this.hasNext = this.nextToken !== undefined;
63
+ const sandboxes = Array.isArray(payload.sandboxes) ? payload.sandboxes : [];
64
+ return sandboxes.map((item) => sandboxInfo(record(item)));
65
+ }
66
+ /** Drain all remaining pages into one list. */
67
+ async listItems(opts = {}) {
68
+ const items = [];
69
+ while (this.hasNext)
70
+ items.push(...await this.nextItems(opts));
71
+ return items;
22
72
  }
23
73
  }
24
74
  /** Running Watasu sandbox with ready `files` and `commands` helpers. */
25
75
  export class Sandbox {
26
76
  /** Default template slug used when create is called without a template. */
27
77
  static defaultTemplate = 'base';
78
+ /** Default template slug used by MCP creation once Watasu supports it. */
79
+ static defaultMcpTemplate = 'mcp-gateway';
80
+ /** Default sandbox lifetime in milliseconds. */
81
+ static defaultSandboxTimeoutMs = 300_000;
28
82
  files;
83
+ filesystem;
29
84
  commands;
85
+ process;
30
86
  pty;
87
+ terminal;
31
88
  git;
89
+ cwd;
90
+ envVars;
32
91
  sandboxId;
92
+ mcpPort = 50005;
93
+ mcpToken;
33
94
  config;
34
95
  control;
35
96
  envs;
@@ -40,34 +101,42 @@ export class Sandbox {
40
101
  this.config = opts.connectionConfig;
41
102
  this.control = opts.control ?? new ControlClient(this.config);
42
103
  this.envs = opts.envs ?? {};
104
+ this.envVars = this.envs;
43
105
  this.sandbox = opts.sandbox ?? {};
44
106
  const dataPlane = dataPlaneFromSession(opts.session, this.config);
45
107
  this.dataPlane = dataPlane;
46
108
  this.files = new Filesystem(dataPlane);
109
+ this.filesystem = this.files;
47
110
  this.commands = new Commands(dataPlane, this.config, this.envs);
111
+ this.process = new ProcessManager(this.commands);
48
112
  this.pty = new Pty(dataPlane, this.config);
113
+ this.terminal = new TerminalManager(this.pty);
49
114
  this.git = new Git(dataPlane);
50
115
  }
116
+ /** Sandbox id alias used by SDK-compatible code. */
117
+ get id() {
118
+ return this.sandboxId;
119
+ }
51
120
  /** Create a sandbox and return it only after the API supplies a data-plane session. */
52
121
  static async create(templateOrOpts, opts = {}) {
122
+ const sandboxOpts = typeof templateOrOpts === 'string' ? opts : templateOrOpts ?? {};
53
123
  const template = typeof templateOrOpts === 'string'
54
124
  ? templateOrOpts
55
- : templateOrOpts?.template ?? Sandbox.defaultTemplate;
56
- const sandboxOpts = typeof templateOrOpts === 'string' ? opts : templateOrOpts ?? {};
57
- if (sandboxOpts.mcp !== undefined)
58
- unsupported('mcp');
125
+ : templateOrOpts?.template ?? (sandboxOpts.mcp === undefined ? Sandbox.defaultTemplate : undefined);
59
126
  if (sandboxOpts.volumeMounts !== undefined)
60
127
  unsupported('volumeMounts');
61
128
  const config = new ConnectionConfig(sandboxOpts);
62
129
  const control = new ControlClient(config);
63
130
  const sandboxPayload = {
64
- template_id: template,
65
131
  timeout: Math.ceil((sandboxOpts.timeoutMs ?? 300_000) / 1000),
66
132
  metadata: sandboxOpts.metadata ?? {},
67
133
  env_vars: sandboxOpts.envs ?? {},
68
134
  secure: sandboxOpts.secure ?? true,
69
135
  allow_internet_access: sandboxOpts.allowInternetAccess ?? true,
70
136
  };
137
+ putIfPresent(sandboxPayload, 'template_id', template);
138
+ putIfPresent(sandboxPayload, 'mcp', sandboxOpts.mcp);
139
+ Object.assign(sandboxPayload, networkUpdatePayload(sandboxOpts.network));
71
140
  putIfPresent(sandboxPayload, 'team', sandboxOpts.team);
72
141
  const response = await control.post('/sandboxes', {
73
142
  json: sandboxPayload,
@@ -77,7 +146,7 @@ export class Sandbox {
77
146
  const sandboxId = sandbox.id ?? sandbox.sandbox_id;
78
147
  if (sandboxId === undefined)
79
148
  throw new SandboxError('create response did not include sandbox id');
80
- return new Sandbox({
149
+ const sandboxInstance = new Sandbox({
81
150
  sandboxId: String(sandboxId),
82
151
  connectionConfig: config,
83
152
  control,
@@ -85,13 +154,14 @@ export class Sandbox {
85
154
  sandbox,
86
155
  envs: sandboxOpts.envs,
87
156
  });
157
+ return sandboxInstance;
88
158
  }
89
159
  /** Connect to an existing sandbox and return it with a fresh data-plane session. */
90
160
  static async connect(sandboxId, opts = {}) {
91
161
  const config = new ConnectionConfig(opts);
92
162
  const control = new ControlClient(config);
93
163
  const info = await control.get(`/sandboxes/${sandboxId}`);
94
- const response = await control.post(`/sandboxes/${sandboxId}/connect`, {
164
+ const response = await control.post(`/sandboxes/${sandboxId}/resume`, {
95
165
  json: opts.timeoutMs ? { timeout: Math.ceil(opts.timeoutMs / 1000) } : {},
96
166
  requestTimeoutMs: sessionOperationRequestTimeout(config, opts),
97
167
  });
@@ -103,9 +173,14 @@ export class Sandbox {
103
173
  sandbox: record(response.sandbox ?? info.sandbox ?? {}),
104
174
  });
105
175
  }
176
+ static async reconnect(sandboxOrOpts, opts = {}) {
177
+ if (typeof sandboxOrOpts === 'string')
178
+ return this.connect(sandboxOrOpts, opts);
179
+ return this.connect(sandboxOrOpts.sandboxID, sandboxOrOpts);
180
+ }
106
181
  /** Refresh this sandbox's data-plane session in place. */
107
182
  async connect(opts = {}) {
108
- const response = await this.control.post(`/sandboxes/${this.sandboxId}/connect`, {
183
+ const response = await this.control.post(`/sandboxes/${this.sandboxId}/resume`, {
109
184
  json: opts.timeoutMs ? { timeout: Math.ceil(opts.timeoutMs / 1000) } : {},
110
185
  requestTimeoutMs: sessionOperationRequestTimeout(this.config, opts),
111
186
  });
@@ -113,14 +188,41 @@ export class Sandbox {
113
188
  const dataPlane = dataPlaneFromSession(response.session, this.config);
114
189
  this.dataPlane = dataPlane;
115
190
  this.files = new Filesystem(dataPlane);
191
+ this.filesystem = this.files;
116
192
  this.commands = new Commands(dataPlane, this.config, this.envs);
193
+ this.process = new ProcessManager(this.commands);
117
194
  this.pty = new Pty(dataPlane, this.config);
195
+ this.terminal = new TerminalManager(this.pty);
118
196
  this.git = new Git(dataPlane);
119
197
  return this;
120
198
  }
199
+ /** Resume a paused sandbox by id. */
200
+ static async resume(sandboxId, opts = {}) {
201
+ await Sandbox.connect(sandboxId, opts);
202
+ return true;
203
+ }
204
+ /** Pause a sandbox by id. Returns false when it was already paused. */
205
+ static async betaPause(sandboxId, opts = {}) {
206
+ const control = new ControlClient(new ConnectionConfig(opts));
207
+ try {
208
+ await control.post(`/sandboxes/${sandboxId}/pause`, {
209
+ requestTimeoutMs: opts.requestTimeoutMs,
210
+ });
211
+ return true;
212
+ }
213
+ catch (error) {
214
+ if (error instanceof ConflictError)
215
+ return false;
216
+ throw error;
217
+ }
218
+ }
219
+ /** Alias for `betaPause`. */
220
+ static async pause(sandboxId, opts = {}) {
221
+ return this.betaPause(sandboxId, opts);
222
+ }
121
223
  /** Destroy a sandbox by id. */
122
224
  static async kill(sandboxId, opts = {}) {
123
- const control = new ControlClient(new ConnectionConfig(opts));
225
+ const control = new ControlClient(new ConnectionConfig(typeof opts === 'string' ? { apiKey: opts } : opts));
124
226
  await control.delete(`/sandboxes/${sandboxId}`);
125
227
  return true;
126
228
  }
@@ -132,6 +234,18 @@ export class Sandbox {
132
234
  });
133
235
  return metricsList(payload.metrics ?? payload);
134
236
  }
237
+ /** Atomically replace a sandbox's network egress policy by id. */
238
+ static async updateNetwork(sandboxId, network, opts = {}) {
239
+ await this.putNetwork(sandboxId, network, opts);
240
+ }
241
+ static async putNetwork(sandboxId, network, opts = {}) {
242
+ const control = new ControlClient(new ConnectionConfig(opts));
243
+ const response = await control.put(`/sandboxes/${sandboxId}/network`, {
244
+ json: networkUpdatePayload(network),
245
+ requestTimeoutMs: opts.requestTimeoutMs,
246
+ });
247
+ return response.sandbox === undefined ? undefined : record(response.sandbox);
248
+ }
135
249
  /** Deprecated alias for `getInfo`. */
136
250
  static async getFullInfo(sandboxId, opts = {}) {
137
251
  return this.getInfo(sandboxId, opts);
@@ -145,16 +259,9 @@ export class Sandbox {
145
259
  });
146
260
  return snapshotInfo(record(payload.sandbox_checkpoint ?? payload.snapshot ?? payload));
147
261
  }
148
- /** List checkpoints for one sandbox using snapshot naming. */
149
- static listSnapshots(sandboxId, opts = {}) {
150
- return new SnapshotPaginator(async () => {
151
- const control = new ControlClient(new ConnectionConfig(opts));
152
- const payload = await control.get(`/sandboxes/${sandboxId}/checkpoints`, {
153
- requestTimeoutMs: opts.requestTimeoutMs,
154
- });
155
- const snapshots = Array.isArray(payload.sandbox_checkpoints) ? payload.sandbox_checkpoints : [];
156
- return snapshots.map((item) => snapshotInfo(record(item)));
157
- });
262
+ /** List snapshots visible to the configured API key. */
263
+ static listSnapshots(opts = {}) {
264
+ return new SnapshotPaginator(opts);
158
265
  }
159
266
  /** Delete a snapshot by id. Returns `false` when the snapshot does not exist. */
160
267
  static async deleteSnapshot(snapshotId, opts = {}) {
@@ -204,6 +311,10 @@ export class Sandbox {
204
311
  json: { timeout: Math.ceil(timeoutMs / 1000) },
205
312
  });
206
313
  }
314
+ /** Keep the sandbox alive for `duration` milliseconds. */
315
+ async keepAlive(duration) {
316
+ await this.setTimeout(duration);
317
+ }
207
318
  /** Fetch control-plane metadata for a sandbox by id. */
208
319
  static async getInfo(sandboxId, opts = {}) {
209
320
  const control = new ControlClient(new ConnectionConfig(opts));
@@ -233,7 +344,7 @@ export class Sandbox {
233
344
  }
234
345
  /** List checkpoints for this sandbox using snapshot naming. */
235
346
  listSnapshots(opts = {}) {
236
- return Sandbox.listSnapshots(this.sandboxId, { ...this.configOptions(), ...opts });
347
+ return Sandbox.listSnapshots({ ...this.configOptions(), ...opts, sandboxId: this.sandboxId });
237
348
  }
238
349
  /** Restore a checkpoint into a new sandbox and return its control-plane info. */
239
350
  async restore(opts = {}) {
@@ -254,12 +365,10 @@ export class Sandbox {
254
365
  });
255
366
  return sandboxInfo(record(response.sandbox ?? response));
256
367
  }
257
- /** List sandboxes visible to the configured API key. */
258
- static async list(opts = {}) {
259
- const control = new ControlClient(new ConnectionConfig(opts));
260
- const payload = await control.get(opts.team ? `/sandboxes?team=${encodeURIComponent(opts.team)}` : '/sandboxes');
261
- const sandboxes = Array.isArray(payload.sandboxes) ? payload.sandboxes : [];
262
- return sandboxes.map((item) => sandboxInfo(record(item)));
368
+ /** Return a paginator for sandboxes visible to the configured API key. */
369
+ static list(opts = {}) {
370
+ const listOpts = typeof opts === 'string' ? { apiKey: opts } : opts;
371
+ return new SandboxPaginator(listOpts);
263
372
  }
264
373
  /** Return the public hostname for an exposed sandbox port. */
265
374
  getHost(port) {
@@ -270,8 +379,39 @@ export class Sandbox {
270
379
  throw new SandboxError('port response did not include host or url');
271
380
  return `p${port}-${routeToken}.sandbox.${this.config.dataPlaneDomain}`;
272
381
  }
382
+ /** Return the public hostname for the sandbox or an exposed sandbox port. */
383
+ getHostname(port) {
384
+ if (port !== undefined)
385
+ return this.getHost(port);
386
+ return new URL(this.dataPlane.baseUrl).host;
387
+ }
388
+ /** Return the conventional MCP URL for this sandbox. */
389
+ getMcpUrl() {
390
+ return `https://${this.getHost(this.mcpPort)}/mcp`;
391
+ }
392
+ /** Return the MCP gateway token when the sandbox contains one. */
393
+ async getMcpToken() {
394
+ if (this.mcpToken !== undefined)
395
+ return this.mcpToken;
396
+ try {
397
+ const token = await this.files.read('/etc/mcp-gateway/.token', { user: 'root' });
398
+ this.mcpToken = String(token).trim() || undefined;
399
+ return this.mcpToken;
400
+ }
401
+ catch (error) {
402
+ if (error instanceof FileNotFoundError || error instanceof NotFoundError)
403
+ return undefined;
404
+ throw error;
405
+ }
406
+ }
407
+ /** Return a protocol string for a secure or insecure sandbox URL. */
408
+ getProtocol(baseProtocol = 'http', secure = true) {
409
+ return `${baseProtocol}${secure ? 's' : ''}`;
410
+ }
411
+ /** Close the local SDK attachment. This does not destroy the sandbox. */
412
+ async close() { }
273
413
  /** Get a signed URL that accepts a POST upload for a sandbox file path. */
274
- async uploadUrl(path, opts = {}) {
414
+ async uploadUrl(path = '', opts = {}) {
275
415
  const fileUrl = await this.fileUrl('/upload_url', path, opts);
276
416
  return fileUrl.url;
277
417
  }
@@ -281,17 +421,31 @@ export class Sandbox {
281
421
  return fileUrl.url;
282
422
  }
283
423
  /** Get signed upload URL metadata for a sandbox file path. */
284
- async uploadUrlInfo(path, opts = {}) {
424
+ async uploadUrlInfo(path = '', opts = {}) {
285
425
  return this.fileUrl('/upload_url', path, opts);
286
426
  }
287
427
  /** Get signed download URL metadata for a sandbox file path. */
288
428
  async downloadUrlInfo(path, opts = {}) {
289
429
  return this.fileUrl('/download_url', path, opts);
290
430
  }
291
- updateNetwork(..._args) { unsupported('Sandbox.updateNetwork'); }
292
- pause() { unsupported('Sandbox.pause'); }
293
- betaPause() { unsupported('Sandbox.betaPause'); }
294
- resume() { unsupported('Sandbox.resume'); }
431
+ /** Atomically replace this sandbox's network egress policy. */
432
+ async updateNetwork(network, opts = {}) {
433
+ const sandbox = await Sandbox.putNetwork(this.sandboxId, network, { ...this.configOptions(), ...opts });
434
+ this.sandbox = sandbox ?? this.sandbox;
435
+ }
436
+ /** Pause this sandbox. Returns false when it was already paused. */
437
+ async betaPause(opts = {}) {
438
+ return Sandbox.betaPause(this.sandboxId, { ...this.configOptions(), ...opts });
439
+ }
440
+ /** Alias for `betaPause`. */
441
+ async pause(opts = {}) {
442
+ return this.betaPause(opts);
443
+ }
444
+ /** Resume this sandbox and refresh its data-plane session. */
445
+ async resume(opts = {}) {
446
+ await this.connect(opts);
447
+ return true;
448
+ }
295
449
  async fileUrl(route, path, opts) {
296
450
  const payload = await this.control.post(`/sandboxes/${this.sandboxId}/files${route}`, {
297
451
  json: compactRecord({
@@ -324,6 +478,36 @@ function dataPlaneFromSession(session, config) {
324
478
  }
325
479
  return new DataPlaneClient(url, token, config);
326
480
  }
481
+ function sandboxListPath(opts, nextToken) {
482
+ const params = new URLSearchParams();
483
+ if (opts.team)
484
+ params.set('team', opts.team);
485
+ if (opts.limit !== undefined)
486
+ params.set('limit', String(opts.limit));
487
+ if (nextToken)
488
+ params.set('next_token', nextToken);
489
+ if (opts.query?.metadata) {
490
+ for (const [key, value] of Object.entries(opts.query.metadata)) {
491
+ params.append(`query[metadata][${key}]`, value);
492
+ }
493
+ }
494
+ for (const state of opts.query?.state ?? []) {
495
+ params.append('query[state][]', state);
496
+ }
497
+ const query = params.toString();
498
+ return query ? `/sandboxes?${query}` : '/sandboxes';
499
+ }
500
+ function snapshotListPath(opts, nextToken) {
501
+ const params = new URLSearchParams();
502
+ if (opts.sandboxId)
503
+ params.set('sandbox_id', opts.sandboxId);
504
+ if (opts.limit !== undefined)
505
+ params.set('limit', String(opts.limit));
506
+ if (nextToken)
507
+ params.set('next_token', nextToken);
508
+ const query = params.toString();
509
+ return query ? `/sandbox_snapshots?${query}` : '/sandbox_snapshots';
510
+ }
327
511
  function fileUrlInfo(payload) {
328
512
  return {
329
513
  method: String(payload.method ?? ''),
@@ -413,6 +597,26 @@ function putIfPresent(target, key, value) {
413
597
  if (value !== undefined && value !== null)
414
598
  target[key] = value;
415
599
  }
600
+ function networkUpdatePayload(network) {
601
+ if (network === undefined)
602
+ return {};
603
+ if (typeof network !== 'object' || network === null)
604
+ unsupported('network callable rules');
605
+ if (network.rules !== undefined)
606
+ unsupported('network rules');
607
+ if (network.maskRequestHost !== undefined)
608
+ unsupported('network request host masking');
609
+ return compactRecord({
610
+ allow_out: network.allowOut,
611
+ deny_out: network.denyOut,
612
+ allow_internet_access: network.allowInternetAccess,
613
+ allow_package_registry_access: network.allowPackageRegistryAccess,
614
+ allow_public_traffic: network.allowPublicTraffic,
615
+ egress_profile: network.egressProfile,
616
+ egress_profiles: network.egressProfiles,
617
+ network_class: network.networkClass,
618
+ });
619
+ }
416
620
  function record(value) {
417
621
  return value && typeof value === 'object' ? value : {};
418
622
  }
@@ -0,0 +1,232 @@
1
+ import { ConnectionOpts } from './connectionConfig.js';
2
+ export type TemplateBuildStatus = 'building' | 'waiting' | 'ready' | 'error';
3
+ export interface BuildInfo {
4
+ /** First template alias. Prefer `name` for new code. */
5
+ alias: string;
6
+ /** Template name passed to the build call. */
7
+ name: string;
8
+ /** Tags assigned to this build. */
9
+ tags: string[];
10
+ /** Template identifier. */
11
+ templateId: string;
12
+ /** Build identifier. */
13
+ buildId: string;
14
+ }
15
+ export interface LogEntry {
16
+ timestamp?: Date;
17
+ level: string;
18
+ message: string;
19
+ }
20
+ export interface BuildStatusReason {
21
+ message: string;
22
+ step?: string;
23
+ logEntries: LogEntry[];
24
+ }
25
+ export interface TemplateBuildStatusResponse {
26
+ buildID: string;
27
+ templateID: string;
28
+ status: TemplateBuildStatus;
29
+ logEntries: LogEntry[];
30
+ logs: string[];
31
+ reason?: BuildStatusReason;
32
+ }
33
+ export interface TemplateTagInfo {
34
+ buildId: string;
35
+ tags: string[];
36
+ }
37
+ export interface TemplateTag {
38
+ tag: string;
39
+ buildId: string;
40
+ createdAt: Date;
41
+ }
42
+ export interface TemplateOptions {
43
+ fileContextPath?: string;
44
+ fileIgnorePatterns?: string[];
45
+ }
46
+ export interface CopyOptions {
47
+ forceUpload?: true;
48
+ user?: string;
49
+ mode?: number;
50
+ resolveSymlinks?: boolean;
51
+ }
52
+ export interface BasicBuildOptions {
53
+ alias?: string;
54
+ tags?: string[];
55
+ cpuCount?: number;
56
+ memoryMB?: number;
57
+ skipCache?: boolean;
58
+ onBuildLogs?: (entry: LogEntry) => void;
59
+ team?: string;
60
+ }
61
+ export type BuildOptions = ConnectionOpts & BasicBuildOptions;
62
+ export type GetBuildStatusOptions = ConnectionOpts & {
63
+ logsOffset?: number;
64
+ };
65
+ export type TemplateClass = TemplateBase;
66
+ export type CopyItem = {
67
+ src: string | string[];
68
+ dest: string;
69
+ forceUpload?: true;
70
+ user?: string;
71
+ mode?: number;
72
+ resolveSymlinks?: boolean;
73
+ };
74
+ export type TemplateBuilder = TemplateBase;
75
+ export type TemplateFinal = TemplateBase;
76
+ export type TemplateFromImage = TemplateBase;
77
+ export type ReadyCommand = string | ReadyCmd;
78
+ interface TemplateFileSpec {
79
+ path: string;
80
+ content_b64: string;
81
+ source_path?: string;
82
+ mode?: number;
83
+ user?: string;
84
+ }
85
+ interface BuildSpec {
86
+ base?: string;
87
+ packages?: Record<string, string[]>;
88
+ files?: TemplateFileSpec[];
89
+ setup?: string[];
90
+ env?: Record<string, string>;
91
+ start_cmd?: string;
92
+ ready_cmd?: string;
93
+ }
94
+ /** Ready-check command wrapper accepted by template builders. */
95
+ export declare class ReadyCmd {
96
+ private readonly cmd;
97
+ constructor(cmd: string);
98
+ /** Return the shell command used as the ready check. */
99
+ getCmd(): string;
100
+ }
101
+ /** Return a ready check that waits for a TCP port to listen. */
102
+ export declare function waitForPort(port: number): ReadyCmd;
103
+ /** Return a ready check that waits for a URL to return an HTTP status code. */
104
+ export declare function waitForURL(url: string, statusCode?: number): ReadyCmd;
105
+ /** Alias for `waitForURL`. */
106
+ export declare const waitForUrl: typeof waitForURL;
107
+ /** Return a ready check that waits for a process name. */
108
+ export declare function waitForProcess(processName: string): ReadyCmd;
109
+ /** Return a ready check that waits for a file to exist. */
110
+ export declare function waitForFile(filename: string): ReadyCmd;
111
+ /** Return a ready check that waits for a fixed duration in milliseconds. */
112
+ export declare function waitForTimeout(timeout: number): ReadyCmd;
113
+ /** Chainable template builder for Watasu package-spec template builds. */
114
+ export declare class TemplateBase {
115
+ private base;
116
+ private packages;
117
+ private files;
118
+ private setup;
119
+ private env;
120
+ private currentWorkdir;
121
+ private currentUser;
122
+ private startCmd;
123
+ private readyCmd;
124
+ private force;
125
+ private readonly fileContextPath;
126
+ private readonly fileIgnorePatterns;
127
+ constructor(options?: TemplateOptions);
128
+ static build(template: TemplateClass, name: string, options?: Omit<BuildOptions, 'alias'>): Promise<BuildInfo>;
129
+ static build(template: TemplateClass, options: BuildOptions): Promise<BuildInfo>;
130
+ static buildInBackground(template: TemplateClass, name: string, options?: Omit<BuildOptions, 'alias'>): Promise<BuildInfo>;
131
+ static buildInBackground(template: TemplateClass, options: BuildOptions): Promise<BuildInfo>;
132
+ static getBuildStatus(data: Pick<BuildInfo, 'templateId' | 'buildId'>, options?: GetBuildStatusOptions): Promise<TemplateBuildStatusResponse>;
133
+ static exists(name: string, options?: ConnectionOpts): Promise<boolean>;
134
+ static aliasExists(alias: string, options?: ConnectionOpts): Promise<boolean>;
135
+ static assignTags(targetName: string, tags: string | string[], options?: ConnectionOpts): Promise<TemplateTagInfo>;
136
+ static removeTags(name: string, tags: string | string[], options?: ConnectionOpts): Promise<void>;
137
+ static getTags(templateId: string, options?: ConnectionOpts): Promise<TemplateTag[]>;
138
+ static toJSON(template: TemplateClass): Promise<string>;
139
+ static toDockerfile(template: TemplateClass): string;
140
+ fromDebianImage(_variant?: string): TemplateBuilder;
141
+ fromUbuntuImage(_variant?: string): TemplateBuilder;
142
+ fromPythonImage(_version?: string): TemplateBuilder;
143
+ fromNodeImage(_variant?: string): TemplateBuilder;
144
+ fromBunImage(_variant?: string): TemplateBuilder;
145
+ fromBaseImage(): TemplateBuilder;
146
+ fromImage(_baseImage: string, _credentials?: {
147
+ username: string;
148
+ password: string;
149
+ }): TemplateBuilder;
150
+ fromAWSRegistry(_image: string, _credentials: {
151
+ accessKeyId: string;
152
+ secretAccessKey: string;
153
+ region: string;
154
+ }): TemplateBuilder;
155
+ fromGCPRegistry(_image: string, _credentials: {
156
+ serviceAccountJSON: object | string;
157
+ }): TemplateBuilder;
158
+ fromTemplate(template: string): TemplateBuilder;
159
+ fromDockerfile(dockerfileContentOrPath: string): TemplateBuilder;
160
+ copy(src: string | string[], dest: string, options?: CopyOptions): TemplateBuilder;
161
+ copyItems(items: CopyItem[]): TemplateBuilder;
162
+ remove(path: string | string[], options?: {
163
+ force?: boolean;
164
+ recursive?: boolean;
165
+ user?: string;
166
+ }): TemplateBuilder;
167
+ rename(src: string, dest: string, options?: {
168
+ force?: boolean;
169
+ user?: string;
170
+ }): TemplateBuilder;
171
+ makeDir(path: string | string[], options?: {
172
+ mode?: number;
173
+ user?: string;
174
+ }): TemplateBuilder;
175
+ makeSymlink(src: string, dest: string, options?: {
176
+ force?: boolean;
177
+ user?: string;
178
+ }): TemplateBuilder;
179
+ runCmd(command: string | string[], options?: {
180
+ user?: string;
181
+ }): TemplateBuilder;
182
+ setWorkdir(workdir: string): TemplateBuilder;
183
+ setUser(user: string): TemplateBuilder;
184
+ pipInstall(packages?: string | string[], options?: {
185
+ g?: boolean;
186
+ }): TemplateBuilder;
187
+ npmInstall(packages?: string | string[], options?: {
188
+ g?: boolean;
189
+ dev?: boolean;
190
+ }): TemplateBuilder;
191
+ bunInstall(packages?: string | string[], options?: {
192
+ g?: boolean;
193
+ dev?: boolean;
194
+ }): TemplateBuilder;
195
+ aptInstall(packages: string | string[], _options?: {
196
+ noInstallRecommends?: boolean;
197
+ fixMissing?: boolean;
198
+ }): TemplateBuilder;
199
+ addMcpServer(servers: string | string[]): TemplateBuilder;
200
+ gitClone(url: string, path?: string, options?: {
201
+ branch?: string;
202
+ depth?: number;
203
+ user?: string;
204
+ }): TemplateBuilder;
205
+ setStartCmd(startCommand: string, readyCommand: ReadyCommand): TemplateFinal;
206
+ setReadyCmd(readyCommand: ReadyCommand): TemplateFinal;
207
+ setEnvs(envs: Record<string, string>): TemplateBuilder;
208
+ skipCache(): TemplateBuilder;
209
+ toBuildSpec(): BuildSpec;
210
+ private addPackages;
211
+ private addCopySource;
212
+ private addFileSpec;
213
+ private resolveContextPath;
214
+ private ignored;
215
+ private commandWithContext;
216
+ private toDockerfile;
217
+ }
218
+ export interface TemplateFactory {
219
+ (options?: TemplateOptions): TemplateFromImage;
220
+ build: typeof TemplateBase.build;
221
+ buildInBackground: typeof TemplateBase.buildInBackground;
222
+ getBuildStatus: typeof TemplateBase.getBuildStatus;
223
+ exists: typeof TemplateBase.exists;
224
+ aliasExists: typeof TemplateBase.aliasExists;
225
+ assignTags: typeof TemplateBase.assignTags;
226
+ removeTags: typeof TemplateBase.removeTags;
227
+ getTags: typeof TemplateBase.getTags;
228
+ toJSON: typeof TemplateBase.toJSON;
229
+ toDockerfile: typeof TemplateBase.toDockerfile;
230
+ }
231
+ export declare const Template: TemplateFactory;
232
+ export {};