@sandbox-engine/sdk 0.1.3 → 0.2.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.
- package/dist/index.d.mts +113 -2
- package/dist/index.d.ts +113 -2
- package/dist/index.js +192 -13
- package/dist/index.mjs +190 -13
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -8,7 +8,7 @@ declare class HttpClient {
|
|
|
8
8
|
private headers;
|
|
9
9
|
get<T>(path: string, query?: Record<string, string>): Promise<T>;
|
|
10
10
|
post<T>(path: string, body?: unknown, timeoutMs?: number): Promise<T>;
|
|
11
|
-
delete(path: string, query?: Record<string, string>): Promise<
|
|
11
|
+
delete<T = void>(path: string, query?: Record<string, string>): Promise<T>;
|
|
12
12
|
openWebSocket(path: string): WebSocket;
|
|
13
13
|
}
|
|
14
14
|
|
|
@@ -30,6 +30,52 @@ interface SandboxOptions {
|
|
|
30
30
|
*/
|
|
31
31
|
dockerfile?: string;
|
|
32
32
|
timeout?: number;
|
|
33
|
+
/** Environment variables injected into the container for the entire session. */
|
|
34
|
+
env?: Record<string, string>;
|
|
35
|
+
}
|
|
36
|
+
interface SandboxConnectOptions {
|
|
37
|
+
serverUrl?: string;
|
|
38
|
+
apiKey?: string;
|
|
39
|
+
}
|
|
40
|
+
interface SandboxListOptions {
|
|
41
|
+
serverUrl?: string;
|
|
42
|
+
apiKey?: string;
|
|
43
|
+
}
|
|
44
|
+
interface SandboxSummary {
|
|
45
|
+
id: string;
|
|
46
|
+
status: string;
|
|
47
|
+
template: string;
|
|
48
|
+
createdAt: string;
|
|
49
|
+
timeout: number;
|
|
50
|
+
expiresAt: string | null;
|
|
51
|
+
agentUrl: string;
|
|
52
|
+
ports: Record<string, number>;
|
|
53
|
+
}
|
|
54
|
+
interface StreamLogsOptions {
|
|
55
|
+
onStdout?: (line: string) => void;
|
|
56
|
+
onStderr?: (line: string) => void;
|
|
57
|
+
}
|
|
58
|
+
interface StartProcessOptions {
|
|
59
|
+
command?: string;
|
|
60
|
+
cmd?: string;
|
|
61
|
+
args?: string[];
|
|
62
|
+
cwd?: string;
|
|
63
|
+
env?: Record<string, string>;
|
|
64
|
+
processId?: string;
|
|
65
|
+
}
|
|
66
|
+
interface BackgroundProcessInfo {
|
|
67
|
+
id: string;
|
|
68
|
+
pid: number;
|
|
69
|
+
command: string;
|
|
70
|
+
status: 'running' | 'exited';
|
|
71
|
+
exitCode: number | null;
|
|
72
|
+
}
|
|
73
|
+
interface WaitForPortOptions {
|
|
74
|
+
host?: string;
|
|
75
|
+
timeout?: number;
|
|
76
|
+
}
|
|
77
|
+
interface WaitForLogOptions {
|
|
78
|
+
timeout?: number;
|
|
33
79
|
}
|
|
34
80
|
interface ProxyEnvResult {
|
|
35
81
|
proxyBase: string;
|
|
@@ -66,6 +112,23 @@ interface FileWriteManyResult {
|
|
|
66
112
|
error?: string;
|
|
67
113
|
}>;
|
|
68
114
|
}
|
|
115
|
+
interface FileStat {
|
|
116
|
+
path: string;
|
|
117
|
+
size: number;
|
|
118
|
+
mtime: string;
|
|
119
|
+
isDirectory: boolean;
|
|
120
|
+
isFile: boolean;
|
|
121
|
+
}
|
|
122
|
+
interface FileWatchEvent {
|
|
123
|
+
type: 'change' | 'rename' | 'error';
|
|
124
|
+
eventType: 'create' | 'modify' | 'delete';
|
|
125
|
+
path: string;
|
|
126
|
+
data?: string;
|
|
127
|
+
}
|
|
128
|
+
interface FileWatchOptions {
|
|
129
|
+
recursive?: boolean;
|
|
130
|
+
onEvent: (event: FileWatchEvent) => void;
|
|
131
|
+
}
|
|
69
132
|
interface PortMapping {
|
|
70
133
|
containerPort: number;
|
|
71
134
|
hostPort: number;
|
|
@@ -90,6 +153,12 @@ declare class Filesystem {
|
|
|
90
153
|
writeMany(files: FileWriteEntry[]): Promise<FileWriteManyResult>;
|
|
91
154
|
rename(oldPath: string, newPath: string): Promise<void>;
|
|
92
155
|
delete(filePath: string): Promise<void>;
|
|
156
|
+
stat(filePath: string): Promise<FileStat>;
|
|
157
|
+
readBytes(filePath: string): Promise<Uint8Array>;
|
|
158
|
+
writeBytes(filePath: string, data: Uint8Array | Buffer): Promise<void>;
|
|
159
|
+
watch(dirPath: string, opts: FileWatchOptions): Promise<{
|
|
160
|
+
close: () => void;
|
|
161
|
+
}>;
|
|
93
162
|
}
|
|
94
163
|
|
|
95
164
|
declare class Process extends EventEmitter {
|
|
@@ -99,6 +168,40 @@ declare class Process extends EventEmitter {
|
|
|
99
168
|
wait(): Promise<ExecResult>;
|
|
100
169
|
}
|
|
101
170
|
|
|
171
|
+
declare class BackgroundProcess {
|
|
172
|
+
private readonly sandboxId;
|
|
173
|
+
private readonly client;
|
|
174
|
+
readonly id: string;
|
|
175
|
+
readonly pid: number;
|
|
176
|
+
readonly command: string;
|
|
177
|
+
constructor(sandboxId: string, client: HttpClient, info: BackgroundProcessInfo);
|
|
178
|
+
private path;
|
|
179
|
+
getStatus(): Promise<BackgroundProcessInfo>;
|
|
180
|
+
getLogs(): Promise<{
|
|
181
|
+
stdout: string;
|
|
182
|
+
stderr: string;
|
|
183
|
+
}>;
|
|
184
|
+
kill(): Promise<void>;
|
|
185
|
+
waitForExit(timeout?: number): Promise<{
|
|
186
|
+
exitCode: number;
|
|
187
|
+
}>;
|
|
188
|
+
waitForPort(port: number, opts?: WaitForPortOptions): Promise<void>;
|
|
189
|
+
waitForLog(pattern: string | RegExp, opts?: WaitForLogOptions): Promise<string>;
|
|
190
|
+
streamLogs(opts?: StreamLogsOptions): {
|
|
191
|
+
close: () => void;
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
declare class ProcessManager {
|
|
195
|
+
private readonly sandboxId;
|
|
196
|
+
private readonly client;
|
|
197
|
+
constructor(sandboxId: string, client: HttpClient);
|
|
198
|
+
start(opts: StartProcessOptions): Promise<BackgroundProcess>;
|
|
199
|
+
list(): Promise<BackgroundProcessInfo[]>;
|
|
200
|
+
get(processId: string): Promise<BackgroundProcess>;
|
|
201
|
+
kill(processId: string): Promise<void>;
|
|
202
|
+
killAll(): Promise<number>;
|
|
203
|
+
}
|
|
204
|
+
|
|
102
205
|
declare class Terminal extends EventEmitter {
|
|
103
206
|
private ws;
|
|
104
207
|
constructor(ws: WebSocket);
|
|
@@ -121,8 +224,16 @@ declare class Sandbox {
|
|
|
121
224
|
private readonly client;
|
|
122
225
|
readonly id: string;
|
|
123
226
|
readonly fs: Filesystem;
|
|
227
|
+
readonly processes: ProcessManager;
|
|
124
228
|
private constructor();
|
|
229
|
+
private static resolveClient;
|
|
230
|
+
static list(opts?: SandboxListOptions): Promise<SandboxSummary[]>;
|
|
125
231
|
static create(opts?: SandboxOptions): Promise<Sandbox>;
|
|
232
|
+
/**
|
|
233
|
+
* Reconnect to an existing sandbox by ID.
|
|
234
|
+
* Works after server restart if the container is still running (auto-adopt).
|
|
235
|
+
*/
|
|
236
|
+
static connect(sandboxId: string, opts?: SandboxConnectOptions): Promise<Sandbox>;
|
|
126
237
|
exec(cmd: string, opts?: ExecOptions): Promise<ExecResult>;
|
|
127
238
|
exec(cmd: string, opts: ExecOptions & {
|
|
128
239
|
stream: true;
|
|
@@ -141,4 +252,4 @@ declare class Sandbox {
|
|
|
141
252
|
close(): Promise<void>;
|
|
142
253
|
}
|
|
143
254
|
|
|
144
|
-
export { type ExecOptions, type ExecResult, type FileEntry, type FileWriteEntry, type FileWriteManyResult, Filesystem, type PortMapping, Process, type ProxyEnvResult, Sandbox, type SandboxOptions, Terminal, type WsMessage, type WsMessageType };
|
|
255
|
+
export { BackgroundProcess, type BackgroundProcessInfo, type ExecOptions, type ExecResult, type FileEntry, type FileStat, type FileWatchEvent, type FileWatchOptions, type FileWriteEntry, type FileWriteManyResult, Filesystem, type PortMapping, Process, ProcessManager, type ProxyEnvResult, Sandbox, type SandboxConnectOptions, type SandboxListOptions, type SandboxOptions, type SandboxSummary, type StartProcessOptions, type StreamLogsOptions, Terminal, type WaitForLogOptions, type WaitForPortOptions, type WsMessage, type WsMessageType };
|
package/dist/index.d.ts
CHANGED
|
@@ -8,7 +8,7 @@ declare class HttpClient {
|
|
|
8
8
|
private headers;
|
|
9
9
|
get<T>(path: string, query?: Record<string, string>): Promise<T>;
|
|
10
10
|
post<T>(path: string, body?: unknown, timeoutMs?: number): Promise<T>;
|
|
11
|
-
delete(path: string, query?: Record<string, string>): Promise<
|
|
11
|
+
delete<T = void>(path: string, query?: Record<string, string>): Promise<T>;
|
|
12
12
|
openWebSocket(path: string): WebSocket;
|
|
13
13
|
}
|
|
14
14
|
|
|
@@ -30,6 +30,52 @@ interface SandboxOptions {
|
|
|
30
30
|
*/
|
|
31
31
|
dockerfile?: string;
|
|
32
32
|
timeout?: number;
|
|
33
|
+
/** Environment variables injected into the container for the entire session. */
|
|
34
|
+
env?: Record<string, string>;
|
|
35
|
+
}
|
|
36
|
+
interface SandboxConnectOptions {
|
|
37
|
+
serverUrl?: string;
|
|
38
|
+
apiKey?: string;
|
|
39
|
+
}
|
|
40
|
+
interface SandboxListOptions {
|
|
41
|
+
serverUrl?: string;
|
|
42
|
+
apiKey?: string;
|
|
43
|
+
}
|
|
44
|
+
interface SandboxSummary {
|
|
45
|
+
id: string;
|
|
46
|
+
status: string;
|
|
47
|
+
template: string;
|
|
48
|
+
createdAt: string;
|
|
49
|
+
timeout: number;
|
|
50
|
+
expiresAt: string | null;
|
|
51
|
+
agentUrl: string;
|
|
52
|
+
ports: Record<string, number>;
|
|
53
|
+
}
|
|
54
|
+
interface StreamLogsOptions {
|
|
55
|
+
onStdout?: (line: string) => void;
|
|
56
|
+
onStderr?: (line: string) => void;
|
|
57
|
+
}
|
|
58
|
+
interface StartProcessOptions {
|
|
59
|
+
command?: string;
|
|
60
|
+
cmd?: string;
|
|
61
|
+
args?: string[];
|
|
62
|
+
cwd?: string;
|
|
63
|
+
env?: Record<string, string>;
|
|
64
|
+
processId?: string;
|
|
65
|
+
}
|
|
66
|
+
interface BackgroundProcessInfo {
|
|
67
|
+
id: string;
|
|
68
|
+
pid: number;
|
|
69
|
+
command: string;
|
|
70
|
+
status: 'running' | 'exited';
|
|
71
|
+
exitCode: number | null;
|
|
72
|
+
}
|
|
73
|
+
interface WaitForPortOptions {
|
|
74
|
+
host?: string;
|
|
75
|
+
timeout?: number;
|
|
76
|
+
}
|
|
77
|
+
interface WaitForLogOptions {
|
|
78
|
+
timeout?: number;
|
|
33
79
|
}
|
|
34
80
|
interface ProxyEnvResult {
|
|
35
81
|
proxyBase: string;
|
|
@@ -66,6 +112,23 @@ interface FileWriteManyResult {
|
|
|
66
112
|
error?: string;
|
|
67
113
|
}>;
|
|
68
114
|
}
|
|
115
|
+
interface FileStat {
|
|
116
|
+
path: string;
|
|
117
|
+
size: number;
|
|
118
|
+
mtime: string;
|
|
119
|
+
isDirectory: boolean;
|
|
120
|
+
isFile: boolean;
|
|
121
|
+
}
|
|
122
|
+
interface FileWatchEvent {
|
|
123
|
+
type: 'change' | 'rename' | 'error';
|
|
124
|
+
eventType: 'create' | 'modify' | 'delete';
|
|
125
|
+
path: string;
|
|
126
|
+
data?: string;
|
|
127
|
+
}
|
|
128
|
+
interface FileWatchOptions {
|
|
129
|
+
recursive?: boolean;
|
|
130
|
+
onEvent: (event: FileWatchEvent) => void;
|
|
131
|
+
}
|
|
69
132
|
interface PortMapping {
|
|
70
133
|
containerPort: number;
|
|
71
134
|
hostPort: number;
|
|
@@ -90,6 +153,12 @@ declare class Filesystem {
|
|
|
90
153
|
writeMany(files: FileWriteEntry[]): Promise<FileWriteManyResult>;
|
|
91
154
|
rename(oldPath: string, newPath: string): Promise<void>;
|
|
92
155
|
delete(filePath: string): Promise<void>;
|
|
156
|
+
stat(filePath: string): Promise<FileStat>;
|
|
157
|
+
readBytes(filePath: string): Promise<Uint8Array>;
|
|
158
|
+
writeBytes(filePath: string, data: Uint8Array | Buffer): Promise<void>;
|
|
159
|
+
watch(dirPath: string, opts: FileWatchOptions): Promise<{
|
|
160
|
+
close: () => void;
|
|
161
|
+
}>;
|
|
93
162
|
}
|
|
94
163
|
|
|
95
164
|
declare class Process extends EventEmitter {
|
|
@@ -99,6 +168,40 @@ declare class Process extends EventEmitter {
|
|
|
99
168
|
wait(): Promise<ExecResult>;
|
|
100
169
|
}
|
|
101
170
|
|
|
171
|
+
declare class BackgroundProcess {
|
|
172
|
+
private readonly sandboxId;
|
|
173
|
+
private readonly client;
|
|
174
|
+
readonly id: string;
|
|
175
|
+
readonly pid: number;
|
|
176
|
+
readonly command: string;
|
|
177
|
+
constructor(sandboxId: string, client: HttpClient, info: BackgroundProcessInfo);
|
|
178
|
+
private path;
|
|
179
|
+
getStatus(): Promise<BackgroundProcessInfo>;
|
|
180
|
+
getLogs(): Promise<{
|
|
181
|
+
stdout: string;
|
|
182
|
+
stderr: string;
|
|
183
|
+
}>;
|
|
184
|
+
kill(): Promise<void>;
|
|
185
|
+
waitForExit(timeout?: number): Promise<{
|
|
186
|
+
exitCode: number;
|
|
187
|
+
}>;
|
|
188
|
+
waitForPort(port: number, opts?: WaitForPortOptions): Promise<void>;
|
|
189
|
+
waitForLog(pattern: string | RegExp, opts?: WaitForLogOptions): Promise<string>;
|
|
190
|
+
streamLogs(opts?: StreamLogsOptions): {
|
|
191
|
+
close: () => void;
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
declare class ProcessManager {
|
|
195
|
+
private readonly sandboxId;
|
|
196
|
+
private readonly client;
|
|
197
|
+
constructor(sandboxId: string, client: HttpClient);
|
|
198
|
+
start(opts: StartProcessOptions): Promise<BackgroundProcess>;
|
|
199
|
+
list(): Promise<BackgroundProcessInfo[]>;
|
|
200
|
+
get(processId: string): Promise<BackgroundProcess>;
|
|
201
|
+
kill(processId: string): Promise<void>;
|
|
202
|
+
killAll(): Promise<number>;
|
|
203
|
+
}
|
|
204
|
+
|
|
102
205
|
declare class Terminal extends EventEmitter {
|
|
103
206
|
private ws;
|
|
104
207
|
constructor(ws: WebSocket);
|
|
@@ -121,8 +224,16 @@ declare class Sandbox {
|
|
|
121
224
|
private readonly client;
|
|
122
225
|
readonly id: string;
|
|
123
226
|
readonly fs: Filesystem;
|
|
227
|
+
readonly processes: ProcessManager;
|
|
124
228
|
private constructor();
|
|
229
|
+
private static resolveClient;
|
|
230
|
+
static list(opts?: SandboxListOptions): Promise<SandboxSummary[]>;
|
|
125
231
|
static create(opts?: SandboxOptions): Promise<Sandbox>;
|
|
232
|
+
/**
|
|
233
|
+
* Reconnect to an existing sandbox by ID.
|
|
234
|
+
* Works after server restart if the container is still running (auto-adopt).
|
|
235
|
+
*/
|
|
236
|
+
static connect(sandboxId: string, opts?: SandboxConnectOptions): Promise<Sandbox>;
|
|
126
237
|
exec(cmd: string, opts?: ExecOptions): Promise<ExecResult>;
|
|
127
238
|
exec(cmd: string, opts: ExecOptions & {
|
|
128
239
|
stream: true;
|
|
@@ -141,4 +252,4 @@ declare class Sandbox {
|
|
|
141
252
|
close(): Promise<void>;
|
|
142
253
|
}
|
|
143
254
|
|
|
144
|
-
export { type ExecOptions, type ExecResult, type FileEntry, type FileWriteEntry, type FileWriteManyResult, Filesystem, type PortMapping, Process, type ProxyEnvResult, Sandbox, type SandboxOptions, Terminal, type WsMessage, type WsMessageType };
|
|
255
|
+
export { BackgroundProcess, type BackgroundProcessInfo, type ExecOptions, type ExecResult, type FileEntry, type FileStat, type FileWatchEvent, type FileWatchOptions, type FileWriteEntry, type FileWriteManyResult, Filesystem, type PortMapping, Process, ProcessManager, type ProxyEnvResult, Sandbox, type SandboxConnectOptions, type SandboxListOptions, type SandboxOptions, type SandboxSummary, type StartProcessOptions, type StreamLogsOptions, Terminal, type WaitForLogOptions, type WaitForPortOptions, type WsMessage, type WsMessageType };
|
package/dist/index.js
CHANGED
|
@@ -30,8 +30,10 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
30
30
|
// src/index.ts
|
|
31
31
|
var index_exports = {};
|
|
32
32
|
__export(index_exports, {
|
|
33
|
+
BackgroundProcess: () => BackgroundProcess,
|
|
33
34
|
Filesystem: () => Filesystem,
|
|
34
35
|
Process: () => Process,
|
|
36
|
+
ProcessManager: () => ProcessManager,
|
|
35
37
|
Sandbox: () => Sandbox,
|
|
36
38
|
Terminal: () => Terminal
|
|
37
39
|
});
|
|
@@ -86,9 +88,15 @@ var HttpClient = class {
|
|
|
86
88
|
signal: AbortSignal.timeout(3e4)
|
|
87
89
|
});
|
|
88
90
|
if (!res.ok && res.status !== 404) {
|
|
89
|
-
const
|
|
90
|
-
throw new Error(`DELETE ${path} failed (${res.status}): ${
|
|
91
|
+
const text2 = await res.text();
|
|
92
|
+
throw new Error(`DELETE ${path} failed (${res.status}): ${text2}`);
|
|
93
|
+
}
|
|
94
|
+
if (res.status === 204 || res.headers.get("content-length") === "0") {
|
|
95
|
+
return void 0;
|
|
91
96
|
}
|
|
97
|
+
const text = await res.text();
|
|
98
|
+
if (!text) return void 0;
|
|
99
|
+
return JSON.parse(text);
|
|
92
100
|
}
|
|
93
101
|
openWebSocket(path) {
|
|
94
102
|
const wsUrl = this.baseUrl.replace(/^http/, "ws") + path;
|
|
@@ -141,6 +149,49 @@ var Filesystem = class {
|
|
|
141
149
|
async delete(filePath) {
|
|
142
150
|
await this.client.delete(`/api/sandboxes/${this.sandboxId}/files`, { path: filePath });
|
|
143
151
|
}
|
|
152
|
+
async stat(filePath) {
|
|
153
|
+
return this.client.get(
|
|
154
|
+
`/api/sandboxes/${this.sandboxId}/files/stat`,
|
|
155
|
+
{ path: filePath }
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
async readBytes(filePath) {
|
|
159
|
+
const res = await this.client.get(
|
|
160
|
+
`/api/sandboxes/${this.sandboxId}/files/bytes`,
|
|
161
|
+
{ path: filePath }
|
|
162
|
+
);
|
|
163
|
+
return Uint8Array.from(Buffer.from(res.data, "base64"));
|
|
164
|
+
}
|
|
165
|
+
async writeBytes(filePath, data) {
|
|
166
|
+
await this.client.post(`/api/sandboxes/${this.sandboxId}/files/bytes`, {
|
|
167
|
+
path: filePath,
|
|
168
|
+
data: Buffer.from(data).toString("base64")
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
async watch(dirPath, opts) {
|
|
172
|
+
const ws = this.client.openWebSocket(`/api/sandboxes/${this.sandboxId}/files/watch`);
|
|
173
|
+
await new Promise((resolve, reject) => {
|
|
174
|
+
ws.once("open", () => {
|
|
175
|
+
ws.send(JSON.stringify({ path: dirPath, recursive: opts.recursive ?? false }));
|
|
176
|
+
resolve();
|
|
177
|
+
});
|
|
178
|
+
ws.once("error", reject);
|
|
179
|
+
});
|
|
180
|
+
ws.on("message", (raw) => {
|
|
181
|
+
try {
|
|
182
|
+
const event = JSON.parse(raw.toString());
|
|
183
|
+
opts.onEvent(event);
|
|
184
|
+
} catch {
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
return {
|
|
188
|
+
close: () => {
|
|
189
|
+
if (ws.readyState === ws.OPEN || ws.readyState === ws.CONNECTING) {
|
|
190
|
+
ws.close();
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
}
|
|
144
195
|
};
|
|
145
196
|
|
|
146
197
|
// src/process.ts
|
|
@@ -186,6 +237,112 @@ var Process = class extends import_node_events.EventEmitter {
|
|
|
186
237
|
}
|
|
187
238
|
};
|
|
188
239
|
|
|
240
|
+
// src/background-process.ts
|
|
241
|
+
var BackgroundProcess = class {
|
|
242
|
+
constructor(sandboxId, client, info) {
|
|
243
|
+
this.sandboxId = sandboxId;
|
|
244
|
+
this.client = client;
|
|
245
|
+
this.id = info.id;
|
|
246
|
+
this.pid = info.pid;
|
|
247
|
+
this.command = info.command;
|
|
248
|
+
}
|
|
249
|
+
sandboxId;
|
|
250
|
+
client;
|
|
251
|
+
id;
|
|
252
|
+
pid;
|
|
253
|
+
command;
|
|
254
|
+
path(suffix) {
|
|
255
|
+
return `/api/sandboxes/${this.sandboxId}/processes/${this.id}${suffix}`;
|
|
256
|
+
}
|
|
257
|
+
async getStatus() {
|
|
258
|
+
return this.client.get(this.path(""));
|
|
259
|
+
}
|
|
260
|
+
async getLogs() {
|
|
261
|
+
return this.client.get(this.path("/logs"));
|
|
262
|
+
}
|
|
263
|
+
async kill() {
|
|
264
|
+
await this.client.delete(this.path(""));
|
|
265
|
+
}
|
|
266
|
+
async waitForExit(timeout) {
|
|
267
|
+
return this.client.post(this.path("/wait-exit"), { timeout }, (timeout ?? 3e5) + 5e3);
|
|
268
|
+
}
|
|
269
|
+
async waitForPort(port, opts = {}) {
|
|
270
|
+
await this.client.post(
|
|
271
|
+
this.path("/wait-for-port"),
|
|
272
|
+
{ port, host: opts.host, timeout: opts.timeout },
|
|
273
|
+
(opts.timeout ?? 3e4) + 5e3
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
async waitForLog(pattern, opts = {}) {
|
|
277
|
+
const patternStr = pattern instanceof RegExp ? pattern.source : pattern;
|
|
278
|
+
const res = await this.client.post(
|
|
279
|
+
this.path("/wait-for-log"),
|
|
280
|
+
{ pattern: patternStr, timeout: opts.timeout },
|
|
281
|
+
(opts.timeout ?? 3e4) + 5e3
|
|
282
|
+
);
|
|
283
|
+
return res.match;
|
|
284
|
+
}
|
|
285
|
+
streamLogs(opts = {}) {
|
|
286
|
+
const ws = this.client.openWebSocket(
|
|
287
|
+
`/api/sandboxes/${this.sandboxId}/processes/logs/stream`
|
|
288
|
+
);
|
|
289
|
+
ws.on("open", () => {
|
|
290
|
+
ws.send(JSON.stringify({ processId: this.id }));
|
|
291
|
+
});
|
|
292
|
+
ws.on("message", (raw) => {
|
|
293
|
+
try {
|
|
294
|
+
const msg = JSON.parse(raw.toString());
|
|
295
|
+
if (msg.type === "stdout") opts.onStdout?.(msg.data ?? "");
|
|
296
|
+
else if (msg.type === "stderr") opts.onStderr?.(msg.data ?? "");
|
|
297
|
+
} catch {
|
|
298
|
+
}
|
|
299
|
+
});
|
|
300
|
+
return {
|
|
301
|
+
close: () => {
|
|
302
|
+
if (ws.readyState === ws.OPEN || ws.readyState === ws.CONNECTING) {
|
|
303
|
+
ws.close();
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
};
|
|
309
|
+
var ProcessManager = class {
|
|
310
|
+
constructor(sandboxId, client) {
|
|
311
|
+
this.sandboxId = sandboxId;
|
|
312
|
+
this.client = client;
|
|
313
|
+
}
|
|
314
|
+
sandboxId;
|
|
315
|
+
client;
|
|
316
|
+
async start(opts) {
|
|
317
|
+
const info = await this.client.post(
|
|
318
|
+
`/api/sandboxes/${this.sandboxId}/processes`,
|
|
319
|
+
opts
|
|
320
|
+
);
|
|
321
|
+
return new BackgroundProcess(this.sandboxId, this.client, info);
|
|
322
|
+
}
|
|
323
|
+
async list() {
|
|
324
|
+
const res = await this.client.get(
|
|
325
|
+
`/api/sandboxes/${this.sandboxId}/processes`
|
|
326
|
+
);
|
|
327
|
+
return res.processes;
|
|
328
|
+
}
|
|
329
|
+
async get(processId) {
|
|
330
|
+
const info = await this.client.get(
|
|
331
|
+
`/api/sandboxes/${this.sandboxId}/processes/${processId}`
|
|
332
|
+
);
|
|
333
|
+
return new BackgroundProcess(this.sandboxId, this.client, info);
|
|
334
|
+
}
|
|
335
|
+
async kill(processId) {
|
|
336
|
+
await this.client.delete(`/api/sandboxes/${this.sandboxId}/processes/${processId}`);
|
|
337
|
+
}
|
|
338
|
+
async killAll() {
|
|
339
|
+
const res = await this.client.delete(
|
|
340
|
+
`/api/sandboxes/${this.sandboxId}/processes`
|
|
341
|
+
);
|
|
342
|
+
return res?.killed ?? 0;
|
|
343
|
+
}
|
|
344
|
+
};
|
|
345
|
+
|
|
189
346
|
// src/terminal.ts
|
|
190
347
|
var import_node_events2 = require("events");
|
|
191
348
|
var Terminal = class extends import_node_events2.EventEmitter {
|
|
@@ -227,26 +384,46 @@ var Sandbox = class _Sandbox {
|
|
|
227
384
|
this.client = client;
|
|
228
385
|
this.id = id;
|
|
229
386
|
this.fs = new Filesystem(id, client);
|
|
387
|
+
this.processes = new ProcessManager(id, client);
|
|
230
388
|
}
|
|
231
389
|
client;
|
|
232
390
|
id;
|
|
233
391
|
fs;
|
|
392
|
+
processes;
|
|
393
|
+
static resolveClient(opts = {}) {
|
|
394
|
+
const serverUrl = opts.serverUrl ?? process.env.SANDBOX_SERVER_URL ?? "http://localhost:4000";
|
|
395
|
+
const apiKey = opts.apiKey ?? process.env.SANDBOX_API_KEY ?? "dev-api-key";
|
|
396
|
+
return new HttpClient(serverUrl, apiKey);
|
|
397
|
+
}
|
|
398
|
+
static async list(opts = {}) {
|
|
399
|
+
const client = _Sandbox.resolveClient(opts);
|
|
400
|
+
return client.get("/api/sandboxes");
|
|
401
|
+
}
|
|
234
402
|
static async create(opts = {}) {
|
|
235
|
-
const
|
|
236
|
-
serverUrl = process.env.SANDBOX_SERVER_URL ?? "http://localhost:4000",
|
|
237
|
-
apiKey = process.env.SANDBOX_API_KEY ?? "dev-api-key",
|
|
238
|
-
template,
|
|
239
|
-
dockerfile,
|
|
240
|
-
timeout
|
|
241
|
-
} = opts;
|
|
242
|
-
const client = new HttpClient(serverUrl, apiKey);
|
|
403
|
+
const client = _Sandbox.resolveClient(opts);
|
|
243
404
|
const data = await client.post("/api/sandboxes", {
|
|
244
|
-
template,
|
|
245
|
-
dockerfile,
|
|
246
|
-
timeout
|
|
405
|
+
template: opts.template,
|
|
406
|
+
dockerfile: opts.dockerfile,
|
|
407
|
+
timeout: opts.timeout,
|
|
408
|
+
env: opts.env
|
|
247
409
|
});
|
|
248
410
|
return new _Sandbox(client, data.id);
|
|
249
411
|
}
|
|
412
|
+
/**
|
|
413
|
+
* Reconnect to an existing sandbox by ID.
|
|
414
|
+
* Works after server restart if the container is still running (auto-adopt).
|
|
415
|
+
*/
|
|
416
|
+
static async connect(sandboxId, opts = {}) {
|
|
417
|
+
const client = _Sandbox.resolveClient(opts);
|
|
418
|
+
const data = await client.post(
|
|
419
|
+
`/api/sandboxes/${sandboxId}/connect`,
|
|
420
|
+
{}
|
|
421
|
+
);
|
|
422
|
+
if (data.status === "stopped") {
|
|
423
|
+
throw new Error(`Sandbox ${sandboxId} is stopped`);
|
|
424
|
+
}
|
|
425
|
+
return new _Sandbox(client, data.id);
|
|
426
|
+
}
|
|
250
427
|
async exec(cmd, opts = {}) {
|
|
251
428
|
const { args, cwd, env, timeout, stream } = opts;
|
|
252
429
|
if (stream) {
|
|
@@ -325,8 +502,10 @@ var Sandbox = class _Sandbox {
|
|
|
325
502
|
};
|
|
326
503
|
// Annotate the CommonJS export names for ESM import in node:
|
|
327
504
|
0 && (module.exports = {
|
|
505
|
+
BackgroundProcess,
|
|
328
506
|
Filesystem,
|
|
329
507
|
Process,
|
|
508
|
+
ProcessManager,
|
|
330
509
|
Sandbox,
|
|
331
510
|
Terminal
|
|
332
511
|
});
|
package/dist/index.mjs
CHANGED
|
@@ -47,9 +47,15 @@ var HttpClient = class {
|
|
|
47
47
|
signal: AbortSignal.timeout(3e4)
|
|
48
48
|
});
|
|
49
49
|
if (!res.ok && res.status !== 404) {
|
|
50
|
-
const
|
|
51
|
-
throw new Error(`DELETE ${path} failed (${res.status}): ${
|
|
50
|
+
const text2 = await res.text();
|
|
51
|
+
throw new Error(`DELETE ${path} failed (${res.status}): ${text2}`);
|
|
52
|
+
}
|
|
53
|
+
if (res.status === 204 || res.headers.get("content-length") === "0") {
|
|
54
|
+
return void 0;
|
|
52
55
|
}
|
|
56
|
+
const text = await res.text();
|
|
57
|
+
if (!text) return void 0;
|
|
58
|
+
return JSON.parse(text);
|
|
53
59
|
}
|
|
54
60
|
openWebSocket(path) {
|
|
55
61
|
const wsUrl = this.baseUrl.replace(/^http/, "ws") + path;
|
|
@@ -102,6 +108,49 @@ var Filesystem = class {
|
|
|
102
108
|
async delete(filePath) {
|
|
103
109
|
await this.client.delete(`/api/sandboxes/${this.sandboxId}/files`, { path: filePath });
|
|
104
110
|
}
|
|
111
|
+
async stat(filePath) {
|
|
112
|
+
return this.client.get(
|
|
113
|
+
`/api/sandboxes/${this.sandboxId}/files/stat`,
|
|
114
|
+
{ path: filePath }
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
async readBytes(filePath) {
|
|
118
|
+
const res = await this.client.get(
|
|
119
|
+
`/api/sandboxes/${this.sandboxId}/files/bytes`,
|
|
120
|
+
{ path: filePath }
|
|
121
|
+
);
|
|
122
|
+
return Uint8Array.from(Buffer.from(res.data, "base64"));
|
|
123
|
+
}
|
|
124
|
+
async writeBytes(filePath, data) {
|
|
125
|
+
await this.client.post(`/api/sandboxes/${this.sandboxId}/files/bytes`, {
|
|
126
|
+
path: filePath,
|
|
127
|
+
data: Buffer.from(data).toString("base64")
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
async watch(dirPath, opts) {
|
|
131
|
+
const ws = this.client.openWebSocket(`/api/sandboxes/${this.sandboxId}/files/watch`);
|
|
132
|
+
await new Promise((resolve, reject) => {
|
|
133
|
+
ws.once("open", () => {
|
|
134
|
+
ws.send(JSON.stringify({ path: dirPath, recursive: opts.recursive ?? false }));
|
|
135
|
+
resolve();
|
|
136
|
+
});
|
|
137
|
+
ws.once("error", reject);
|
|
138
|
+
});
|
|
139
|
+
ws.on("message", (raw) => {
|
|
140
|
+
try {
|
|
141
|
+
const event = JSON.parse(raw.toString());
|
|
142
|
+
opts.onEvent(event);
|
|
143
|
+
} catch {
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
return {
|
|
147
|
+
close: () => {
|
|
148
|
+
if (ws.readyState === ws.OPEN || ws.readyState === ws.CONNECTING) {
|
|
149
|
+
ws.close();
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
}
|
|
105
154
|
};
|
|
106
155
|
|
|
107
156
|
// src/process.ts
|
|
@@ -147,6 +196,112 @@ var Process = class extends EventEmitter {
|
|
|
147
196
|
}
|
|
148
197
|
};
|
|
149
198
|
|
|
199
|
+
// src/background-process.ts
|
|
200
|
+
var BackgroundProcess = class {
|
|
201
|
+
constructor(sandboxId, client, info) {
|
|
202
|
+
this.sandboxId = sandboxId;
|
|
203
|
+
this.client = client;
|
|
204
|
+
this.id = info.id;
|
|
205
|
+
this.pid = info.pid;
|
|
206
|
+
this.command = info.command;
|
|
207
|
+
}
|
|
208
|
+
sandboxId;
|
|
209
|
+
client;
|
|
210
|
+
id;
|
|
211
|
+
pid;
|
|
212
|
+
command;
|
|
213
|
+
path(suffix) {
|
|
214
|
+
return `/api/sandboxes/${this.sandboxId}/processes/${this.id}${suffix}`;
|
|
215
|
+
}
|
|
216
|
+
async getStatus() {
|
|
217
|
+
return this.client.get(this.path(""));
|
|
218
|
+
}
|
|
219
|
+
async getLogs() {
|
|
220
|
+
return this.client.get(this.path("/logs"));
|
|
221
|
+
}
|
|
222
|
+
async kill() {
|
|
223
|
+
await this.client.delete(this.path(""));
|
|
224
|
+
}
|
|
225
|
+
async waitForExit(timeout) {
|
|
226
|
+
return this.client.post(this.path("/wait-exit"), { timeout }, (timeout ?? 3e5) + 5e3);
|
|
227
|
+
}
|
|
228
|
+
async waitForPort(port, opts = {}) {
|
|
229
|
+
await this.client.post(
|
|
230
|
+
this.path("/wait-for-port"),
|
|
231
|
+
{ port, host: opts.host, timeout: opts.timeout },
|
|
232
|
+
(opts.timeout ?? 3e4) + 5e3
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
async waitForLog(pattern, opts = {}) {
|
|
236
|
+
const patternStr = pattern instanceof RegExp ? pattern.source : pattern;
|
|
237
|
+
const res = await this.client.post(
|
|
238
|
+
this.path("/wait-for-log"),
|
|
239
|
+
{ pattern: patternStr, timeout: opts.timeout },
|
|
240
|
+
(opts.timeout ?? 3e4) + 5e3
|
|
241
|
+
);
|
|
242
|
+
return res.match;
|
|
243
|
+
}
|
|
244
|
+
streamLogs(opts = {}) {
|
|
245
|
+
const ws = this.client.openWebSocket(
|
|
246
|
+
`/api/sandboxes/${this.sandboxId}/processes/logs/stream`
|
|
247
|
+
);
|
|
248
|
+
ws.on("open", () => {
|
|
249
|
+
ws.send(JSON.stringify({ processId: this.id }));
|
|
250
|
+
});
|
|
251
|
+
ws.on("message", (raw) => {
|
|
252
|
+
try {
|
|
253
|
+
const msg = JSON.parse(raw.toString());
|
|
254
|
+
if (msg.type === "stdout") opts.onStdout?.(msg.data ?? "");
|
|
255
|
+
else if (msg.type === "stderr") opts.onStderr?.(msg.data ?? "");
|
|
256
|
+
} catch {
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
return {
|
|
260
|
+
close: () => {
|
|
261
|
+
if (ws.readyState === ws.OPEN || ws.readyState === ws.CONNECTING) {
|
|
262
|
+
ws.close();
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
var ProcessManager = class {
|
|
269
|
+
constructor(sandboxId, client) {
|
|
270
|
+
this.sandboxId = sandboxId;
|
|
271
|
+
this.client = client;
|
|
272
|
+
}
|
|
273
|
+
sandboxId;
|
|
274
|
+
client;
|
|
275
|
+
async start(opts) {
|
|
276
|
+
const info = await this.client.post(
|
|
277
|
+
`/api/sandboxes/${this.sandboxId}/processes`,
|
|
278
|
+
opts
|
|
279
|
+
);
|
|
280
|
+
return new BackgroundProcess(this.sandboxId, this.client, info);
|
|
281
|
+
}
|
|
282
|
+
async list() {
|
|
283
|
+
const res = await this.client.get(
|
|
284
|
+
`/api/sandboxes/${this.sandboxId}/processes`
|
|
285
|
+
);
|
|
286
|
+
return res.processes;
|
|
287
|
+
}
|
|
288
|
+
async get(processId) {
|
|
289
|
+
const info = await this.client.get(
|
|
290
|
+
`/api/sandboxes/${this.sandboxId}/processes/${processId}`
|
|
291
|
+
);
|
|
292
|
+
return new BackgroundProcess(this.sandboxId, this.client, info);
|
|
293
|
+
}
|
|
294
|
+
async kill(processId) {
|
|
295
|
+
await this.client.delete(`/api/sandboxes/${this.sandboxId}/processes/${processId}`);
|
|
296
|
+
}
|
|
297
|
+
async killAll() {
|
|
298
|
+
const res = await this.client.delete(
|
|
299
|
+
`/api/sandboxes/${this.sandboxId}/processes`
|
|
300
|
+
);
|
|
301
|
+
return res?.killed ?? 0;
|
|
302
|
+
}
|
|
303
|
+
};
|
|
304
|
+
|
|
150
305
|
// src/terminal.ts
|
|
151
306
|
import { EventEmitter as EventEmitter2 } from "events";
|
|
152
307
|
var Terminal = class extends EventEmitter2 {
|
|
@@ -188,26 +343,46 @@ var Sandbox = class _Sandbox {
|
|
|
188
343
|
this.client = client;
|
|
189
344
|
this.id = id;
|
|
190
345
|
this.fs = new Filesystem(id, client);
|
|
346
|
+
this.processes = new ProcessManager(id, client);
|
|
191
347
|
}
|
|
192
348
|
client;
|
|
193
349
|
id;
|
|
194
350
|
fs;
|
|
351
|
+
processes;
|
|
352
|
+
static resolveClient(opts = {}) {
|
|
353
|
+
const serverUrl = opts.serverUrl ?? process.env.SANDBOX_SERVER_URL ?? "http://localhost:4000";
|
|
354
|
+
const apiKey = opts.apiKey ?? process.env.SANDBOX_API_KEY ?? "dev-api-key";
|
|
355
|
+
return new HttpClient(serverUrl, apiKey);
|
|
356
|
+
}
|
|
357
|
+
static async list(opts = {}) {
|
|
358
|
+
const client = _Sandbox.resolveClient(opts);
|
|
359
|
+
return client.get("/api/sandboxes");
|
|
360
|
+
}
|
|
195
361
|
static async create(opts = {}) {
|
|
196
|
-
const
|
|
197
|
-
serverUrl = process.env.SANDBOX_SERVER_URL ?? "http://localhost:4000",
|
|
198
|
-
apiKey = process.env.SANDBOX_API_KEY ?? "dev-api-key",
|
|
199
|
-
template,
|
|
200
|
-
dockerfile,
|
|
201
|
-
timeout
|
|
202
|
-
} = opts;
|
|
203
|
-
const client = new HttpClient(serverUrl, apiKey);
|
|
362
|
+
const client = _Sandbox.resolveClient(opts);
|
|
204
363
|
const data = await client.post("/api/sandboxes", {
|
|
205
|
-
template,
|
|
206
|
-
dockerfile,
|
|
207
|
-
timeout
|
|
364
|
+
template: opts.template,
|
|
365
|
+
dockerfile: opts.dockerfile,
|
|
366
|
+
timeout: opts.timeout,
|
|
367
|
+
env: opts.env
|
|
208
368
|
});
|
|
209
369
|
return new _Sandbox(client, data.id);
|
|
210
370
|
}
|
|
371
|
+
/**
|
|
372
|
+
* Reconnect to an existing sandbox by ID.
|
|
373
|
+
* Works after server restart if the container is still running (auto-adopt).
|
|
374
|
+
*/
|
|
375
|
+
static async connect(sandboxId, opts = {}) {
|
|
376
|
+
const client = _Sandbox.resolveClient(opts);
|
|
377
|
+
const data = await client.post(
|
|
378
|
+
`/api/sandboxes/${sandboxId}/connect`,
|
|
379
|
+
{}
|
|
380
|
+
);
|
|
381
|
+
if (data.status === "stopped") {
|
|
382
|
+
throw new Error(`Sandbox ${sandboxId} is stopped`);
|
|
383
|
+
}
|
|
384
|
+
return new _Sandbox(client, data.id);
|
|
385
|
+
}
|
|
211
386
|
async exec(cmd, opts = {}) {
|
|
212
387
|
const { args, cwd, env, timeout, stream } = opts;
|
|
213
388
|
if (stream) {
|
|
@@ -285,8 +460,10 @@ var Sandbox = class _Sandbox {
|
|
|
285
460
|
}
|
|
286
461
|
};
|
|
287
462
|
export {
|
|
463
|
+
BackgroundProcess,
|
|
288
464
|
Filesystem,
|
|
289
465
|
Process,
|
|
466
|
+
ProcessManager,
|
|
290
467
|
Sandbox,
|
|
291
468
|
Terminal
|
|
292
469
|
};
|
package/package.json
CHANGED