@sandbox-engine/sdk 0.1.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/README.md +168 -0
- package/dist/index.d.mts +118 -0
- package/dist/index.d.ts +118 -0
- package/dist/index.js +295 -0
- package/dist/index.mjs +255 -0
- package/package.json +45 -0
package/README.md
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# @sandbox-engine/sdk
|
|
2
|
+
|
|
3
|
+
SDK for creating and managing isolated sandbox environments. Each sandbox is an isolated Docker container with its own filesystem, process namespace, and network — run untrusted code safely.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @sandbox-engine/sdk
|
|
9
|
+
# or
|
|
10
|
+
pnpm add @sandbox-engine/sdk
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Quick Start
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { Sandbox } from '@sandbox-engine/sdk'
|
|
17
|
+
|
|
18
|
+
const sandbox = await Sandbox.create({
|
|
19
|
+
serverUrl: 'https://your-sandbox-server.com',
|
|
20
|
+
apiKey: 'your-api-key',
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
const result = await sandbox.exec('node', { args: ['-e', 'console.log(1 + 1)'] })
|
|
24
|
+
console.log(result.stdout) // "2\n"
|
|
25
|
+
|
|
26
|
+
await sandbox.close()
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Configuration via environment variables
|
|
30
|
+
|
|
31
|
+
Instead of passing `serverUrl` and `apiKey` every time, set environment variables:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
SANDBOX_SERVER_URL=https://your-sandbox-server.com
|
|
35
|
+
SANDBOX_API_KEY=your-api-key
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
// No options needed — reads from env
|
|
40
|
+
const sandbox = await Sandbox.create()
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Custom environments (Dockerfile)
|
|
44
|
+
|
|
45
|
+
Define exactly what's installed in your sandbox using a Dockerfile snippet:
|
|
46
|
+
|
|
47
|
+
```ts
|
|
48
|
+
const sandbox = await Sandbox.create({
|
|
49
|
+
dockerfile: `
|
|
50
|
+
RUN npm install -g @anthropic-ai/claude-code typescript
|
|
51
|
+
RUN pip install numpy pandas matplotlib
|
|
52
|
+
`,
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
// First call: builds a Docker image (~seconds to minutes)
|
|
56
|
+
// Subsequent calls with same Dockerfile: instant (cached by content hash)
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
You don't need a `FROM` — the base sandbox image is automatically prepended.
|
|
60
|
+
|
|
61
|
+
## API
|
|
62
|
+
|
|
63
|
+
### `Sandbox.create(opts?)`
|
|
64
|
+
|
|
65
|
+
Creates and starts a new sandbox. Returns when the sandbox is ready.
|
|
66
|
+
|
|
67
|
+
| Option | Type | Default | Description |
|
|
68
|
+
|--------|------|---------|-------------|
|
|
69
|
+
| `serverUrl` | `string` | `$SANDBOX_SERVER_URL` or `localhost:4000` | Sandbox server URL |
|
|
70
|
+
| `apiKey` | `string` | `$SANDBOX_API_KEY` or `dev-api-key` | API key |
|
|
71
|
+
| `template` | `string` | `'base'` | Pre-built Docker image tag |
|
|
72
|
+
| `dockerfile` | `string` | — | Custom Dockerfile content |
|
|
73
|
+
| `timeout` | `number` | `300000` | TTL in ms before auto-destroy |
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
### `sandbox.exec(cmd, opts?)`
|
|
78
|
+
|
|
79
|
+
Execute a command and wait for it to finish.
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
const result = await sandbox.exec('python3', {
|
|
83
|
+
args: ['script.py'],
|
|
84
|
+
cwd: '/app',
|
|
85
|
+
env: { PYTHONPATH: '/app/lib' },
|
|
86
|
+
timeout: 30_000,
|
|
87
|
+
})
|
|
88
|
+
console.log(result.stdout) // string
|
|
89
|
+
console.log(result.stderr) // string
|
|
90
|
+
console.log(result.exitCode) // number
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
#### Streaming exec
|
|
94
|
+
|
|
95
|
+
```ts
|
|
96
|
+
const proc = await sandbox.exec('npm install', { stream: true })
|
|
97
|
+
proc.on('stdout', (data) => process.stdout.write(data))
|
|
98
|
+
proc.on('stderr', (data) => process.stderr.write(data))
|
|
99
|
+
const { exitCode } = await proc.wait()
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
### `sandbox.fs`
|
|
105
|
+
|
|
106
|
+
```ts
|
|
107
|
+
// Write a file
|
|
108
|
+
await sandbox.fs.write('/app/index.js', 'console.log("hello")')
|
|
109
|
+
|
|
110
|
+
// Read a file
|
|
111
|
+
const content = await sandbox.fs.read('/app/index.js')
|
|
112
|
+
|
|
113
|
+
// List a directory
|
|
114
|
+
const files = await sandbox.fs.list('/app')
|
|
115
|
+
// [{ name: 'index.js', path: '/app/index.js', isFile: true, isDirectory: false }]
|
|
116
|
+
|
|
117
|
+
// Delete a file or directory
|
|
118
|
+
await sandbox.fs.delete('/app/tmp')
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
### `sandbox.terminal()`
|
|
124
|
+
|
|
125
|
+
Opens an interactive PTY terminal session.
|
|
126
|
+
|
|
127
|
+
```ts
|
|
128
|
+
const term = await sandbox.terminal()
|
|
129
|
+
|
|
130
|
+
term.on('data', (data) => process.stdout.write(data))
|
|
131
|
+
term.write('ls -la\n')
|
|
132
|
+
term.resize(120, 40) // cols, rows
|
|
133
|
+
term.close()
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
### `sandbox.ports.list()`
|
|
139
|
+
|
|
140
|
+
Returns host port mappings for ports exposed by the sandbox (3000–3004).
|
|
141
|
+
|
|
142
|
+
```ts
|
|
143
|
+
const ports = await sandbox.ports.list()
|
|
144
|
+
// [{ containerPort: 3000, hostPort: 15234, url: 'http://host:15234' }]
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
### `sandbox.close()`
|
|
150
|
+
|
|
151
|
+
Destroys the sandbox and frees all resources. Always call this when done.
|
|
152
|
+
|
|
153
|
+
```ts
|
|
154
|
+
await sandbox.close()
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## Server setup
|
|
158
|
+
|
|
159
|
+
The SDK connects to a **sandbox-engine server** you deploy. See the [server repository](https://github.com/your-org/sandbox-engine) for deployment instructions.
|
|
160
|
+
|
|
161
|
+
### Quick deploy (Ubuntu VPS)
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
# On your VPS
|
|
165
|
+
git clone https://github.com/your-org/sandbox-engine /opt/sandbox-engine
|
|
166
|
+
cd /opt/sandbox-engine
|
|
167
|
+
bash scripts/setup-vps.sh
|
|
168
|
+
```
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import WebSocket from 'ws';
|
|
2
|
+
import { EventEmitter } from 'node:events';
|
|
3
|
+
|
|
4
|
+
declare class HttpClient {
|
|
5
|
+
private readonly baseUrl;
|
|
6
|
+
private readonly apiKey;
|
|
7
|
+
constructor(baseUrl: string, apiKey: string);
|
|
8
|
+
private headers;
|
|
9
|
+
get<T>(path: string, query?: Record<string, string>): Promise<T>;
|
|
10
|
+
post<T>(path: string, body?: unknown, timeoutMs?: number): Promise<T>;
|
|
11
|
+
delete(path: string, query?: Record<string, string>): Promise<void>;
|
|
12
|
+
openWebSocket(path: string): WebSocket;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface SandboxOptions {
|
|
16
|
+
serverUrl?: string;
|
|
17
|
+
apiKey?: string;
|
|
18
|
+
/**
|
|
19
|
+
* Name of a pre-built Docker image to use as the sandbox environment.
|
|
20
|
+
* Defaults to 'base' (sandbox-base:latest).
|
|
21
|
+
*/
|
|
22
|
+
template?: string;
|
|
23
|
+
/**
|
|
24
|
+
* Custom Dockerfile content. The server will build a Docker image from it
|
|
25
|
+
* (using sandbox-base:latest as the base if no FROM is specified) and cache
|
|
26
|
+
* it by content hash. Subsequent calls with the same Dockerfile skip the build.
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* dockerfile: `RUN npm install -g @anthropic-ai/claude-code`
|
|
30
|
+
*/
|
|
31
|
+
dockerfile?: string;
|
|
32
|
+
timeout?: number;
|
|
33
|
+
}
|
|
34
|
+
interface ExecOptions {
|
|
35
|
+
args?: string[];
|
|
36
|
+
cwd?: string;
|
|
37
|
+
env?: Record<string, string>;
|
|
38
|
+
timeout?: number;
|
|
39
|
+
stream?: boolean;
|
|
40
|
+
}
|
|
41
|
+
interface ExecResult {
|
|
42
|
+
stdout: string;
|
|
43
|
+
stderr: string;
|
|
44
|
+
exitCode: number;
|
|
45
|
+
}
|
|
46
|
+
interface FileEntry {
|
|
47
|
+
name: string;
|
|
48
|
+
path: string;
|
|
49
|
+
isDirectory: boolean;
|
|
50
|
+
isFile: boolean;
|
|
51
|
+
}
|
|
52
|
+
interface PortMapping {
|
|
53
|
+
containerPort: number;
|
|
54
|
+
hostPort: number;
|
|
55
|
+
url: string;
|
|
56
|
+
}
|
|
57
|
+
type WsMessageType = 'stdout' | 'stderr' | 'exit' | 'error' | 'input' | 'resize';
|
|
58
|
+
interface WsMessage {
|
|
59
|
+
type: WsMessageType;
|
|
60
|
+
data?: string;
|
|
61
|
+
exitCode?: number;
|
|
62
|
+
cols?: number;
|
|
63
|
+
rows?: number;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
declare class Filesystem {
|
|
67
|
+
private readonly sandboxId;
|
|
68
|
+
private readonly client;
|
|
69
|
+
constructor(sandboxId: string, client: HttpClient);
|
|
70
|
+
read(filePath: string): Promise<string>;
|
|
71
|
+
write(filePath: string, content: string): Promise<void>;
|
|
72
|
+
list(dirPath: string): Promise<FileEntry[]>;
|
|
73
|
+
delete(filePath: string): Promise<void>;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
declare class Process extends EventEmitter {
|
|
77
|
+
private ws;
|
|
78
|
+
constructor(ws: WebSocket);
|
|
79
|
+
kill(): void;
|
|
80
|
+
wait(): Promise<ExecResult>;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
declare class Terminal extends EventEmitter {
|
|
84
|
+
private ws;
|
|
85
|
+
constructor(ws: WebSocket);
|
|
86
|
+
write(data: string): void;
|
|
87
|
+
resize(cols: number, rows: number): void;
|
|
88
|
+
close(): void;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
interface SandboxApiResponse {
|
|
92
|
+
id: string;
|
|
93
|
+
status: string;
|
|
94
|
+
template: string;
|
|
95
|
+
createdAt: string;
|
|
96
|
+
expiresAt: string;
|
|
97
|
+
agentUrl: string;
|
|
98
|
+
ports: Record<string, number>;
|
|
99
|
+
}
|
|
100
|
+
declare class Sandbox {
|
|
101
|
+
private readonly client;
|
|
102
|
+
readonly id: string;
|
|
103
|
+
readonly fs: Filesystem;
|
|
104
|
+
private constructor();
|
|
105
|
+
static create(opts?: SandboxOptions): Promise<Sandbox>;
|
|
106
|
+
exec(cmd: string, opts?: ExecOptions): Promise<ExecResult>;
|
|
107
|
+
exec(cmd: string, opts: ExecOptions & {
|
|
108
|
+
stream: true;
|
|
109
|
+
}): Promise<Process>;
|
|
110
|
+
terminal(): Promise<Terminal>;
|
|
111
|
+
readonly ports: {
|
|
112
|
+
list: () => Promise<PortMapping[]>;
|
|
113
|
+
};
|
|
114
|
+
info(): Promise<SandboxApiResponse>;
|
|
115
|
+
close(): Promise<void>;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export { type ExecOptions, type ExecResult, type FileEntry, Filesystem, type PortMapping, Process, Sandbox, type SandboxOptions, Terminal, type WsMessage, type WsMessageType };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import WebSocket from 'ws';
|
|
2
|
+
import { EventEmitter } from 'node:events';
|
|
3
|
+
|
|
4
|
+
declare class HttpClient {
|
|
5
|
+
private readonly baseUrl;
|
|
6
|
+
private readonly apiKey;
|
|
7
|
+
constructor(baseUrl: string, apiKey: string);
|
|
8
|
+
private headers;
|
|
9
|
+
get<T>(path: string, query?: Record<string, string>): Promise<T>;
|
|
10
|
+
post<T>(path: string, body?: unknown, timeoutMs?: number): Promise<T>;
|
|
11
|
+
delete(path: string, query?: Record<string, string>): Promise<void>;
|
|
12
|
+
openWebSocket(path: string): WebSocket;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface SandboxOptions {
|
|
16
|
+
serverUrl?: string;
|
|
17
|
+
apiKey?: string;
|
|
18
|
+
/**
|
|
19
|
+
* Name of a pre-built Docker image to use as the sandbox environment.
|
|
20
|
+
* Defaults to 'base' (sandbox-base:latest).
|
|
21
|
+
*/
|
|
22
|
+
template?: string;
|
|
23
|
+
/**
|
|
24
|
+
* Custom Dockerfile content. The server will build a Docker image from it
|
|
25
|
+
* (using sandbox-base:latest as the base if no FROM is specified) and cache
|
|
26
|
+
* it by content hash. Subsequent calls with the same Dockerfile skip the build.
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* dockerfile: `RUN npm install -g @anthropic-ai/claude-code`
|
|
30
|
+
*/
|
|
31
|
+
dockerfile?: string;
|
|
32
|
+
timeout?: number;
|
|
33
|
+
}
|
|
34
|
+
interface ExecOptions {
|
|
35
|
+
args?: string[];
|
|
36
|
+
cwd?: string;
|
|
37
|
+
env?: Record<string, string>;
|
|
38
|
+
timeout?: number;
|
|
39
|
+
stream?: boolean;
|
|
40
|
+
}
|
|
41
|
+
interface ExecResult {
|
|
42
|
+
stdout: string;
|
|
43
|
+
stderr: string;
|
|
44
|
+
exitCode: number;
|
|
45
|
+
}
|
|
46
|
+
interface FileEntry {
|
|
47
|
+
name: string;
|
|
48
|
+
path: string;
|
|
49
|
+
isDirectory: boolean;
|
|
50
|
+
isFile: boolean;
|
|
51
|
+
}
|
|
52
|
+
interface PortMapping {
|
|
53
|
+
containerPort: number;
|
|
54
|
+
hostPort: number;
|
|
55
|
+
url: string;
|
|
56
|
+
}
|
|
57
|
+
type WsMessageType = 'stdout' | 'stderr' | 'exit' | 'error' | 'input' | 'resize';
|
|
58
|
+
interface WsMessage {
|
|
59
|
+
type: WsMessageType;
|
|
60
|
+
data?: string;
|
|
61
|
+
exitCode?: number;
|
|
62
|
+
cols?: number;
|
|
63
|
+
rows?: number;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
declare class Filesystem {
|
|
67
|
+
private readonly sandboxId;
|
|
68
|
+
private readonly client;
|
|
69
|
+
constructor(sandboxId: string, client: HttpClient);
|
|
70
|
+
read(filePath: string): Promise<string>;
|
|
71
|
+
write(filePath: string, content: string): Promise<void>;
|
|
72
|
+
list(dirPath: string): Promise<FileEntry[]>;
|
|
73
|
+
delete(filePath: string): Promise<void>;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
declare class Process extends EventEmitter {
|
|
77
|
+
private ws;
|
|
78
|
+
constructor(ws: WebSocket);
|
|
79
|
+
kill(): void;
|
|
80
|
+
wait(): Promise<ExecResult>;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
declare class Terminal extends EventEmitter {
|
|
84
|
+
private ws;
|
|
85
|
+
constructor(ws: WebSocket);
|
|
86
|
+
write(data: string): void;
|
|
87
|
+
resize(cols: number, rows: number): void;
|
|
88
|
+
close(): void;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
interface SandboxApiResponse {
|
|
92
|
+
id: string;
|
|
93
|
+
status: string;
|
|
94
|
+
template: string;
|
|
95
|
+
createdAt: string;
|
|
96
|
+
expiresAt: string;
|
|
97
|
+
agentUrl: string;
|
|
98
|
+
ports: Record<string, number>;
|
|
99
|
+
}
|
|
100
|
+
declare class Sandbox {
|
|
101
|
+
private readonly client;
|
|
102
|
+
readonly id: string;
|
|
103
|
+
readonly fs: Filesystem;
|
|
104
|
+
private constructor();
|
|
105
|
+
static create(opts?: SandboxOptions): Promise<Sandbox>;
|
|
106
|
+
exec(cmd: string, opts?: ExecOptions): Promise<ExecResult>;
|
|
107
|
+
exec(cmd: string, opts: ExecOptions & {
|
|
108
|
+
stream: true;
|
|
109
|
+
}): Promise<Process>;
|
|
110
|
+
terminal(): Promise<Terminal>;
|
|
111
|
+
readonly ports: {
|
|
112
|
+
list: () => Promise<PortMapping[]>;
|
|
113
|
+
};
|
|
114
|
+
info(): Promise<SandboxApiResponse>;
|
|
115
|
+
close(): Promise<void>;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export { type ExecOptions, type ExecResult, type FileEntry, Filesystem, type PortMapping, Process, Sandbox, type SandboxOptions, Terminal, type WsMessage, type WsMessageType };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
Filesystem: () => Filesystem,
|
|
34
|
+
Process: () => Process,
|
|
35
|
+
Sandbox: () => Sandbox,
|
|
36
|
+
Terminal: () => Terminal
|
|
37
|
+
});
|
|
38
|
+
module.exports = __toCommonJS(index_exports);
|
|
39
|
+
|
|
40
|
+
// src/client.ts
|
|
41
|
+
var import_ws = __toESM(require("ws"));
|
|
42
|
+
var HttpClient = class {
|
|
43
|
+
constructor(baseUrl, apiKey) {
|
|
44
|
+
this.baseUrl = baseUrl;
|
|
45
|
+
this.apiKey = apiKey;
|
|
46
|
+
}
|
|
47
|
+
baseUrl;
|
|
48
|
+
apiKey;
|
|
49
|
+
headers() {
|
|
50
|
+
return {
|
|
51
|
+
"content-type": "application/json",
|
|
52
|
+
authorization: `Bearer ${this.apiKey}`
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
async get(path, query) {
|
|
56
|
+
const qs = query ? "?" + new URLSearchParams(query).toString() : "";
|
|
57
|
+
const res = await fetch(`${this.baseUrl}${path}${qs}`, {
|
|
58
|
+
headers: this.headers(),
|
|
59
|
+
signal: AbortSignal.timeout(3e4)
|
|
60
|
+
});
|
|
61
|
+
if (!res.ok) {
|
|
62
|
+
const body = await res.text();
|
|
63
|
+
throw new Error(`GET ${path} failed (${res.status}): ${body}`);
|
|
64
|
+
}
|
|
65
|
+
return res.json();
|
|
66
|
+
}
|
|
67
|
+
async post(path, body, timeoutMs = 3e5) {
|
|
68
|
+
const res = await fetch(`${this.baseUrl}${path}`, {
|
|
69
|
+
method: "POST",
|
|
70
|
+
headers: this.headers(),
|
|
71
|
+
body: JSON.stringify(body ?? {}),
|
|
72
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
73
|
+
});
|
|
74
|
+
if (!res.ok) {
|
|
75
|
+
const text = await res.text();
|
|
76
|
+
throw new Error(`POST ${path} failed (${res.status}): ${text}`);
|
|
77
|
+
}
|
|
78
|
+
if (res.status === 204) return void 0;
|
|
79
|
+
return res.json();
|
|
80
|
+
}
|
|
81
|
+
async delete(path, query) {
|
|
82
|
+
const qs = query ? "?" + new URLSearchParams(query).toString() : "";
|
|
83
|
+
const res = await fetch(`${this.baseUrl}${path}${qs}`, {
|
|
84
|
+
method: "DELETE",
|
|
85
|
+
headers: this.headers(),
|
|
86
|
+
signal: AbortSignal.timeout(3e4)
|
|
87
|
+
});
|
|
88
|
+
if (!res.ok && res.status !== 404) {
|
|
89
|
+
const text = await res.text();
|
|
90
|
+
throw new Error(`DELETE ${path} failed (${res.status}): ${text}`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
openWebSocket(path) {
|
|
94
|
+
const wsUrl = this.baseUrl.replace(/^http/, "ws") + path;
|
|
95
|
+
return new import_ws.default(wsUrl, {
|
|
96
|
+
headers: { authorization: `Bearer ${this.apiKey}` }
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
// src/filesystem.ts
|
|
102
|
+
var Filesystem = class {
|
|
103
|
+
constructor(sandboxId, client) {
|
|
104
|
+
this.sandboxId = sandboxId;
|
|
105
|
+
this.client = client;
|
|
106
|
+
}
|
|
107
|
+
sandboxId;
|
|
108
|
+
client;
|
|
109
|
+
async read(filePath) {
|
|
110
|
+
const res = await this.client.get(
|
|
111
|
+
`/api/sandboxes/${this.sandboxId}/files`,
|
|
112
|
+
{ path: filePath }
|
|
113
|
+
);
|
|
114
|
+
return res.content;
|
|
115
|
+
}
|
|
116
|
+
async write(filePath, content) {
|
|
117
|
+
await this.client.post(`/api/sandboxes/${this.sandboxId}/files`, {
|
|
118
|
+
path: filePath,
|
|
119
|
+
content
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
async list(dirPath) {
|
|
123
|
+
const res = await this.client.get(
|
|
124
|
+
`/api/sandboxes/${this.sandboxId}/files/list`,
|
|
125
|
+
{ path: dirPath }
|
|
126
|
+
);
|
|
127
|
+
return res.files;
|
|
128
|
+
}
|
|
129
|
+
async delete(filePath) {
|
|
130
|
+
await this.client.delete(`/api/sandboxes/${this.sandboxId}/files`, { path: filePath });
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
// src/process.ts
|
|
135
|
+
var import_node_events = require("events");
|
|
136
|
+
var Process = class extends import_node_events.EventEmitter {
|
|
137
|
+
ws;
|
|
138
|
+
constructor(ws) {
|
|
139
|
+
super();
|
|
140
|
+
this.ws = ws;
|
|
141
|
+
ws.on("message", (raw) => {
|
|
142
|
+
try {
|
|
143
|
+
const msg = JSON.parse(raw.toString());
|
|
144
|
+
if (msg.type === "stdout") this.emit("stdout", msg.data ?? "");
|
|
145
|
+
else if (msg.type === "stderr") this.emit("stderr", msg.data ?? "");
|
|
146
|
+
else if (msg.type === "exit") this.emit("exit", msg.exitCode ?? 0);
|
|
147
|
+
else if (msg.type === "error") this.emit("error", new Error(msg.data));
|
|
148
|
+
} catch {
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
ws.on("error", (err) => this.emit("error", err));
|
|
152
|
+
ws.on("close", () => this.emit("close"));
|
|
153
|
+
}
|
|
154
|
+
kill() {
|
|
155
|
+
this.ws.close();
|
|
156
|
+
}
|
|
157
|
+
wait() {
|
|
158
|
+
return new Promise((resolve, reject) => {
|
|
159
|
+
let stdout = "";
|
|
160
|
+
let stderr = "";
|
|
161
|
+
let exitCode = 0;
|
|
162
|
+
this.on("stdout", (data) => {
|
|
163
|
+
stdout += data;
|
|
164
|
+
});
|
|
165
|
+
this.on("stderr", (data) => {
|
|
166
|
+
stderr += data;
|
|
167
|
+
});
|
|
168
|
+
this.on("exit", (code) => {
|
|
169
|
+
exitCode = code;
|
|
170
|
+
resolve({ stdout, stderr, exitCode });
|
|
171
|
+
});
|
|
172
|
+
this.on("error", reject);
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
// src/terminal.ts
|
|
178
|
+
var import_node_events2 = require("events");
|
|
179
|
+
var Terminal = class extends import_node_events2.EventEmitter {
|
|
180
|
+
ws;
|
|
181
|
+
constructor(ws) {
|
|
182
|
+
super();
|
|
183
|
+
this.ws = ws;
|
|
184
|
+
ws.on("message", (raw) => {
|
|
185
|
+
try {
|
|
186
|
+
const msg = JSON.parse(raw.toString());
|
|
187
|
+
if (msg.type === "stdout") this.emit("data", msg.data ?? "");
|
|
188
|
+
else if (msg.type === "exit") this.emit("exit", msg.exitCode ?? 0);
|
|
189
|
+
} catch {
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
ws.on("error", (err) => this.emit("error", err));
|
|
193
|
+
ws.on("close", () => this.emit("close"));
|
|
194
|
+
}
|
|
195
|
+
write(data) {
|
|
196
|
+
if (this.ws.readyState === this.ws.OPEN) {
|
|
197
|
+
const msg = { type: "input", data };
|
|
198
|
+
this.ws.send(JSON.stringify(msg));
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
resize(cols, rows) {
|
|
202
|
+
if (this.ws.readyState === this.ws.OPEN) {
|
|
203
|
+
const msg = { type: "resize", cols, rows };
|
|
204
|
+
this.ws.send(JSON.stringify(msg));
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
close() {
|
|
208
|
+
this.ws.close();
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
// src/sandbox.ts
|
|
213
|
+
var Sandbox = class _Sandbox {
|
|
214
|
+
constructor(client, id) {
|
|
215
|
+
this.client = client;
|
|
216
|
+
this.id = id;
|
|
217
|
+
this.fs = new Filesystem(id, client);
|
|
218
|
+
}
|
|
219
|
+
client;
|
|
220
|
+
id;
|
|
221
|
+
fs;
|
|
222
|
+
static async create(opts = {}) {
|
|
223
|
+
const {
|
|
224
|
+
serverUrl = process.env.SANDBOX_SERVER_URL ?? "http://localhost:4000",
|
|
225
|
+
apiKey = process.env.SANDBOX_API_KEY ?? "dev-api-key",
|
|
226
|
+
template,
|
|
227
|
+
dockerfile,
|
|
228
|
+
timeout
|
|
229
|
+
} = opts;
|
|
230
|
+
const client = new HttpClient(serverUrl, apiKey);
|
|
231
|
+
const data = await client.post("/api/sandboxes", {
|
|
232
|
+
template,
|
|
233
|
+
dockerfile,
|
|
234
|
+
timeout
|
|
235
|
+
});
|
|
236
|
+
return new _Sandbox(client, data.id);
|
|
237
|
+
}
|
|
238
|
+
async exec(cmd, opts = {}) {
|
|
239
|
+
const { args, cwd, env, timeout, stream } = opts;
|
|
240
|
+
if (stream) {
|
|
241
|
+
const ws = this.client.openWebSocket(`/api/sandboxes/${this.id}/exec/stream`);
|
|
242
|
+
const proc = new Process(ws);
|
|
243
|
+
await new Promise((resolve, reject) => {
|
|
244
|
+
ws.once("open", () => {
|
|
245
|
+
const req = {
|
|
246
|
+
type: "input",
|
|
247
|
+
cmd,
|
|
248
|
+
args,
|
|
249
|
+
cwd
|
|
250
|
+
};
|
|
251
|
+
ws.send(JSON.stringify(req));
|
|
252
|
+
resolve();
|
|
253
|
+
});
|
|
254
|
+
ws.once("error", reject);
|
|
255
|
+
});
|
|
256
|
+
return proc;
|
|
257
|
+
}
|
|
258
|
+
return this.client.post(`/api/sandboxes/${this.id}/exec`, {
|
|
259
|
+
cmd,
|
|
260
|
+
args,
|
|
261
|
+
cwd,
|
|
262
|
+
env,
|
|
263
|
+
timeout
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
async terminal() {
|
|
267
|
+
const ws = this.client.openWebSocket(`/api/sandboxes/${this.id}/terminal`);
|
|
268
|
+
await new Promise((resolve, reject) => {
|
|
269
|
+
ws.once("open", resolve);
|
|
270
|
+
ws.once("error", reject);
|
|
271
|
+
});
|
|
272
|
+
return new Terminal(ws);
|
|
273
|
+
}
|
|
274
|
+
ports = {
|
|
275
|
+
list: async () => {
|
|
276
|
+
const res = await this.client.get(
|
|
277
|
+
`/api/sandboxes/${this.id}/ports`
|
|
278
|
+
);
|
|
279
|
+
return res.ports;
|
|
280
|
+
}
|
|
281
|
+
};
|
|
282
|
+
async info() {
|
|
283
|
+
return this.client.get(`/api/sandboxes/${this.id}`);
|
|
284
|
+
}
|
|
285
|
+
async close() {
|
|
286
|
+
await this.client.delete(`/api/sandboxes/${this.id}`);
|
|
287
|
+
}
|
|
288
|
+
};
|
|
289
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
290
|
+
0 && (module.exports = {
|
|
291
|
+
Filesystem,
|
|
292
|
+
Process,
|
|
293
|
+
Sandbox,
|
|
294
|
+
Terminal
|
|
295
|
+
});
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
// src/client.ts
|
|
2
|
+
import WebSocket from "ws";
|
|
3
|
+
var HttpClient = class {
|
|
4
|
+
constructor(baseUrl, apiKey) {
|
|
5
|
+
this.baseUrl = baseUrl;
|
|
6
|
+
this.apiKey = apiKey;
|
|
7
|
+
}
|
|
8
|
+
baseUrl;
|
|
9
|
+
apiKey;
|
|
10
|
+
headers() {
|
|
11
|
+
return {
|
|
12
|
+
"content-type": "application/json",
|
|
13
|
+
authorization: `Bearer ${this.apiKey}`
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
async get(path, query) {
|
|
17
|
+
const qs = query ? "?" + new URLSearchParams(query).toString() : "";
|
|
18
|
+
const res = await fetch(`${this.baseUrl}${path}${qs}`, {
|
|
19
|
+
headers: this.headers(),
|
|
20
|
+
signal: AbortSignal.timeout(3e4)
|
|
21
|
+
});
|
|
22
|
+
if (!res.ok) {
|
|
23
|
+
const body = await res.text();
|
|
24
|
+
throw new Error(`GET ${path} failed (${res.status}): ${body}`);
|
|
25
|
+
}
|
|
26
|
+
return res.json();
|
|
27
|
+
}
|
|
28
|
+
async post(path, body, timeoutMs = 3e5) {
|
|
29
|
+
const res = await fetch(`${this.baseUrl}${path}`, {
|
|
30
|
+
method: "POST",
|
|
31
|
+
headers: this.headers(),
|
|
32
|
+
body: JSON.stringify(body ?? {}),
|
|
33
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
34
|
+
});
|
|
35
|
+
if (!res.ok) {
|
|
36
|
+
const text = await res.text();
|
|
37
|
+
throw new Error(`POST ${path} failed (${res.status}): ${text}`);
|
|
38
|
+
}
|
|
39
|
+
if (res.status === 204) return void 0;
|
|
40
|
+
return res.json();
|
|
41
|
+
}
|
|
42
|
+
async delete(path, query) {
|
|
43
|
+
const qs = query ? "?" + new URLSearchParams(query).toString() : "";
|
|
44
|
+
const res = await fetch(`${this.baseUrl}${path}${qs}`, {
|
|
45
|
+
method: "DELETE",
|
|
46
|
+
headers: this.headers(),
|
|
47
|
+
signal: AbortSignal.timeout(3e4)
|
|
48
|
+
});
|
|
49
|
+
if (!res.ok && res.status !== 404) {
|
|
50
|
+
const text = await res.text();
|
|
51
|
+
throw new Error(`DELETE ${path} failed (${res.status}): ${text}`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
openWebSocket(path) {
|
|
55
|
+
const wsUrl = this.baseUrl.replace(/^http/, "ws") + path;
|
|
56
|
+
return new WebSocket(wsUrl, {
|
|
57
|
+
headers: { authorization: `Bearer ${this.apiKey}` }
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
// src/filesystem.ts
|
|
63
|
+
var Filesystem = class {
|
|
64
|
+
constructor(sandboxId, client) {
|
|
65
|
+
this.sandboxId = sandboxId;
|
|
66
|
+
this.client = client;
|
|
67
|
+
}
|
|
68
|
+
sandboxId;
|
|
69
|
+
client;
|
|
70
|
+
async read(filePath) {
|
|
71
|
+
const res = await this.client.get(
|
|
72
|
+
`/api/sandboxes/${this.sandboxId}/files`,
|
|
73
|
+
{ path: filePath }
|
|
74
|
+
);
|
|
75
|
+
return res.content;
|
|
76
|
+
}
|
|
77
|
+
async write(filePath, content) {
|
|
78
|
+
await this.client.post(`/api/sandboxes/${this.sandboxId}/files`, {
|
|
79
|
+
path: filePath,
|
|
80
|
+
content
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
async list(dirPath) {
|
|
84
|
+
const res = await this.client.get(
|
|
85
|
+
`/api/sandboxes/${this.sandboxId}/files/list`,
|
|
86
|
+
{ path: dirPath }
|
|
87
|
+
);
|
|
88
|
+
return res.files;
|
|
89
|
+
}
|
|
90
|
+
async delete(filePath) {
|
|
91
|
+
await this.client.delete(`/api/sandboxes/${this.sandboxId}/files`, { path: filePath });
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
// src/process.ts
|
|
96
|
+
import { EventEmitter } from "events";
|
|
97
|
+
var Process = class extends EventEmitter {
|
|
98
|
+
ws;
|
|
99
|
+
constructor(ws) {
|
|
100
|
+
super();
|
|
101
|
+
this.ws = ws;
|
|
102
|
+
ws.on("message", (raw) => {
|
|
103
|
+
try {
|
|
104
|
+
const msg = JSON.parse(raw.toString());
|
|
105
|
+
if (msg.type === "stdout") this.emit("stdout", msg.data ?? "");
|
|
106
|
+
else if (msg.type === "stderr") this.emit("stderr", msg.data ?? "");
|
|
107
|
+
else if (msg.type === "exit") this.emit("exit", msg.exitCode ?? 0);
|
|
108
|
+
else if (msg.type === "error") this.emit("error", new Error(msg.data));
|
|
109
|
+
} catch {
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
ws.on("error", (err) => this.emit("error", err));
|
|
113
|
+
ws.on("close", () => this.emit("close"));
|
|
114
|
+
}
|
|
115
|
+
kill() {
|
|
116
|
+
this.ws.close();
|
|
117
|
+
}
|
|
118
|
+
wait() {
|
|
119
|
+
return new Promise((resolve, reject) => {
|
|
120
|
+
let stdout = "";
|
|
121
|
+
let stderr = "";
|
|
122
|
+
let exitCode = 0;
|
|
123
|
+
this.on("stdout", (data) => {
|
|
124
|
+
stdout += data;
|
|
125
|
+
});
|
|
126
|
+
this.on("stderr", (data) => {
|
|
127
|
+
stderr += data;
|
|
128
|
+
});
|
|
129
|
+
this.on("exit", (code) => {
|
|
130
|
+
exitCode = code;
|
|
131
|
+
resolve({ stdout, stderr, exitCode });
|
|
132
|
+
});
|
|
133
|
+
this.on("error", reject);
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
// src/terminal.ts
|
|
139
|
+
import { EventEmitter as EventEmitter2 } from "events";
|
|
140
|
+
var Terminal = class extends EventEmitter2 {
|
|
141
|
+
ws;
|
|
142
|
+
constructor(ws) {
|
|
143
|
+
super();
|
|
144
|
+
this.ws = ws;
|
|
145
|
+
ws.on("message", (raw) => {
|
|
146
|
+
try {
|
|
147
|
+
const msg = JSON.parse(raw.toString());
|
|
148
|
+
if (msg.type === "stdout") this.emit("data", msg.data ?? "");
|
|
149
|
+
else if (msg.type === "exit") this.emit("exit", msg.exitCode ?? 0);
|
|
150
|
+
} catch {
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
ws.on("error", (err) => this.emit("error", err));
|
|
154
|
+
ws.on("close", () => this.emit("close"));
|
|
155
|
+
}
|
|
156
|
+
write(data) {
|
|
157
|
+
if (this.ws.readyState === this.ws.OPEN) {
|
|
158
|
+
const msg = { type: "input", data };
|
|
159
|
+
this.ws.send(JSON.stringify(msg));
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
resize(cols, rows) {
|
|
163
|
+
if (this.ws.readyState === this.ws.OPEN) {
|
|
164
|
+
const msg = { type: "resize", cols, rows };
|
|
165
|
+
this.ws.send(JSON.stringify(msg));
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
close() {
|
|
169
|
+
this.ws.close();
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
// src/sandbox.ts
|
|
174
|
+
var Sandbox = class _Sandbox {
|
|
175
|
+
constructor(client, id) {
|
|
176
|
+
this.client = client;
|
|
177
|
+
this.id = id;
|
|
178
|
+
this.fs = new Filesystem(id, client);
|
|
179
|
+
}
|
|
180
|
+
client;
|
|
181
|
+
id;
|
|
182
|
+
fs;
|
|
183
|
+
static async create(opts = {}) {
|
|
184
|
+
const {
|
|
185
|
+
serverUrl = process.env.SANDBOX_SERVER_URL ?? "http://localhost:4000",
|
|
186
|
+
apiKey = process.env.SANDBOX_API_KEY ?? "dev-api-key",
|
|
187
|
+
template,
|
|
188
|
+
dockerfile,
|
|
189
|
+
timeout
|
|
190
|
+
} = opts;
|
|
191
|
+
const client = new HttpClient(serverUrl, apiKey);
|
|
192
|
+
const data = await client.post("/api/sandboxes", {
|
|
193
|
+
template,
|
|
194
|
+
dockerfile,
|
|
195
|
+
timeout
|
|
196
|
+
});
|
|
197
|
+
return new _Sandbox(client, data.id);
|
|
198
|
+
}
|
|
199
|
+
async exec(cmd, opts = {}) {
|
|
200
|
+
const { args, cwd, env, timeout, stream } = opts;
|
|
201
|
+
if (stream) {
|
|
202
|
+
const ws = this.client.openWebSocket(`/api/sandboxes/${this.id}/exec/stream`);
|
|
203
|
+
const proc = new Process(ws);
|
|
204
|
+
await new Promise((resolve, reject) => {
|
|
205
|
+
ws.once("open", () => {
|
|
206
|
+
const req = {
|
|
207
|
+
type: "input",
|
|
208
|
+
cmd,
|
|
209
|
+
args,
|
|
210
|
+
cwd
|
|
211
|
+
};
|
|
212
|
+
ws.send(JSON.stringify(req));
|
|
213
|
+
resolve();
|
|
214
|
+
});
|
|
215
|
+
ws.once("error", reject);
|
|
216
|
+
});
|
|
217
|
+
return proc;
|
|
218
|
+
}
|
|
219
|
+
return this.client.post(`/api/sandboxes/${this.id}/exec`, {
|
|
220
|
+
cmd,
|
|
221
|
+
args,
|
|
222
|
+
cwd,
|
|
223
|
+
env,
|
|
224
|
+
timeout
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
async terminal() {
|
|
228
|
+
const ws = this.client.openWebSocket(`/api/sandboxes/${this.id}/terminal`);
|
|
229
|
+
await new Promise((resolve, reject) => {
|
|
230
|
+
ws.once("open", resolve);
|
|
231
|
+
ws.once("error", reject);
|
|
232
|
+
});
|
|
233
|
+
return new Terminal(ws);
|
|
234
|
+
}
|
|
235
|
+
ports = {
|
|
236
|
+
list: async () => {
|
|
237
|
+
const res = await this.client.get(
|
|
238
|
+
`/api/sandboxes/${this.id}/ports`
|
|
239
|
+
);
|
|
240
|
+
return res.ports;
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
async info() {
|
|
244
|
+
return this.client.get(`/api/sandboxes/${this.id}`);
|
|
245
|
+
}
|
|
246
|
+
async close() {
|
|
247
|
+
await this.client.delete(`/api/sandboxes/${this.id}`);
|
|
248
|
+
}
|
|
249
|
+
};
|
|
250
|
+
export {
|
|
251
|
+
Filesystem,
|
|
252
|
+
Process,
|
|
253
|
+
Sandbox,
|
|
254
|
+
Terminal
|
|
255
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@sandbox-engine/sdk",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "SDK for creating and managing isolated sandbox environments",
|
|
5
|
+
"keywords": ["sandbox", "docker", "code-execution", "isolated", "e2b"],
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"main": "dist/index.js",
|
|
8
|
+
"module": "dist/index.mjs",
|
|
9
|
+
"types": "dist/index.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"import": {
|
|
13
|
+
"types": "./dist/index.d.mts",
|
|
14
|
+
"default": "./dist/index.mjs"
|
|
15
|
+
},
|
|
16
|
+
"require": {
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
18
|
+
"default": "./dist/index.js"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"dist",
|
|
24
|
+
"README.md"
|
|
25
|
+
],
|
|
26
|
+
"scripts": {
|
|
27
|
+
"build": "tsup",
|
|
28
|
+
"dev": "tsup --watch",
|
|
29
|
+
"test": "vitest run",
|
|
30
|
+
"prepublishOnly": "pnpm build"
|
|
31
|
+
},
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"ws": "^8.17.0"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"typescript": "^5.4.5",
|
|
37
|
+
"tsup": "^8.0.2",
|
|
38
|
+
"vitest": "^1.5.0",
|
|
39
|
+
"@types/node": "^20.12.7",
|
|
40
|
+
"@types/ws": "^8.5.10"
|
|
41
|
+
},
|
|
42
|
+
"engines": {
|
|
43
|
+
"node": ">=18.0.0"
|
|
44
|
+
}
|
|
45
|
+
}
|