matchlock-sdk 0.1.22
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 +57 -0
- package/dist/builder.d.ts +37 -0
- package/dist/builder.js +199 -0
- package/dist/client.d.ts +65 -0
- package/dist/client.js +966 -0
- package/dist/errors.d.ts +20 -0
- package/dist/errors.js +39 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +33 -0
- package/dist/types.d.ts +153 -0
- package/dist/types.js +23 -0
- package/package.json +33 -0
package/README.md
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# Matchlock TypeScript SDK
|
|
2
|
+
|
|
3
|
+
TypeScript client for [Matchlock](https://github.com/jingkaihe/matchlock), with feature parity across the existing Go and Python SDKs.
|
|
4
|
+
|
|
5
|
+
## Requirements
|
|
6
|
+
|
|
7
|
+
- Node.js 22+
|
|
8
|
+
- `matchlock` CLI installed and available on `PATH` (or configured with `binaryPath`)
|
|
9
|
+
|
|
10
|
+
## Install (from source)
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
cd sdk/typescript
|
|
14
|
+
npm install
|
|
15
|
+
npm run build
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Quick Start
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
import { Client, Sandbox } from "matchlock-sdk";
|
|
22
|
+
|
|
23
|
+
const sandbox = new Sandbox("alpine:latest")
|
|
24
|
+
.withCPUs(2)
|
|
25
|
+
.withMemory(1024)
|
|
26
|
+
.allowHost("api.openai.com")
|
|
27
|
+
.addSecret("API_KEY", process.env.API_KEY ?? "", "api.openai.com");
|
|
28
|
+
|
|
29
|
+
const client = new Client();
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
await client.launch(sandbox);
|
|
33
|
+
const result = await client.exec("echo hello from sandbox");
|
|
34
|
+
console.log(result.stdout);
|
|
35
|
+
} finally {
|
|
36
|
+
await client.close();
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Highlights
|
|
41
|
+
|
|
42
|
+
- Fluent sandbox builder (`Sandbox`) with network, secrets, mounts, env, VFS hooks, image config
|
|
43
|
+
- JSON-RPC `create`, `exec`, `exec_stream`, `write_file`, `read_file`, `list_files`, `port_forward`, `cancel`, `close`
|
|
44
|
+
- Streaming stdout/stderr via `execStream`
|
|
45
|
+
- Local VFS callbacks (`hook`, `dangerousHook`, `mutateHook`, `actionHook`)
|
|
46
|
+
- Port forwarding API parity (`portForward`, `portForwardWithAddresses`)
|
|
47
|
+
- Lifecycle control (`close`, `remove`, `vmId`)
|
|
48
|
+
|
|
49
|
+
## Development
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
cd sdk/typescript
|
|
53
|
+
npm install
|
|
54
|
+
npm run typecheck
|
|
55
|
+
npm test
|
|
56
|
+
npm run build
|
|
57
|
+
```
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { CreateOptions, ImageConfig, MountConfig, VFSInterceptionConfig } from "./types";
|
|
2
|
+
export declare function cloneCreateOptions(opts: CreateOptions): CreateOptions;
|
|
3
|
+
export declare class Sandbox {
|
|
4
|
+
private readonly opts;
|
|
5
|
+
constructor(image: string);
|
|
6
|
+
withPrivileged(): Sandbox;
|
|
7
|
+
withCPUs(cpus: number): Sandbox;
|
|
8
|
+
withMemory(mb: number): Sandbox;
|
|
9
|
+
withDiskSize(mb: number): Sandbox;
|
|
10
|
+
withTimeout(seconds: number): Sandbox;
|
|
11
|
+
withWorkspace(path: string): Sandbox;
|
|
12
|
+
withVFSInterception(config: VFSInterceptionConfig): Sandbox;
|
|
13
|
+
withEnv(name: string, value: string): Sandbox;
|
|
14
|
+
withEnvMap(env: Record<string, string>): Sandbox;
|
|
15
|
+
allowHost(...hosts: string[]): Sandbox;
|
|
16
|
+
addHost(host: string, ip: string): Sandbox;
|
|
17
|
+
blockPrivateIPs(): Sandbox;
|
|
18
|
+
withBlockPrivateIPs(enabled: boolean): Sandbox;
|
|
19
|
+
allowPrivateIPs(): Sandbox;
|
|
20
|
+
unsetBlockPrivateIPs(): Sandbox;
|
|
21
|
+
addSecret(name: string, value: string, ...hosts: string[]): Sandbox;
|
|
22
|
+
withDNSServers(...servers: string[]): Sandbox;
|
|
23
|
+
withHostname(hostname: string): Sandbox;
|
|
24
|
+
withNetworkMTU(mtu: number): Sandbox;
|
|
25
|
+
withPortForward(localPort: number, remotePort: number): Sandbox;
|
|
26
|
+
withPortForwardAddresses(...addresses: string[]): Sandbox;
|
|
27
|
+
mount(guestPath: string, config: MountConfig): Sandbox;
|
|
28
|
+
mountHostDir(guestPath: string, hostPath: string): Sandbox;
|
|
29
|
+
mountHostDirReadonly(guestPath: string, hostPath: string): Sandbox;
|
|
30
|
+
mountMemory(guestPath: string): Sandbox;
|
|
31
|
+
mountOverlay(guestPath: string, hostPath: string): Sandbox;
|
|
32
|
+
withUser(user: string): Sandbox;
|
|
33
|
+
withEntrypoint(...entrypoint: string[]): Sandbox;
|
|
34
|
+
withImageConfig(config: ImageConfig | undefined | null): Sandbox;
|
|
35
|
+
options(): CreateOptions;
|
|
36
|
+
}
|
|
37
|
+
export declare function createSandbox(image: string): Sandbox;
|
package/dist/builder.js
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Sandbox = void 0;
|
|
4
|
+
exports.cloneCreateOptions = cloneCreateOptions;
|
|
5
|
+
exports.createSandbox = createSandbox;
|
|
6
|
+
function cloneCreateOptions(opts) {
|
|
7
|
+
return {
|
|
8
|
+
...opts,
|
|
9
|
+
allowedHosts: opts.allowedHosts ? [...opts.allowedHosts] : undefined,
|
|
10
|
+
addHosts: opts.addHosts ? opts.addHosts.map((x) => ({ ...x })) : undefined,
|
|
11
|
+
mounts: opts.mounts
|
|
12
|
+
? Object.fromEntries(Object.entries(opts.mounts).map(([k, v]) => [k, { ...v }]))
|
|
13
|
+
: undefined,
|
|
14
|
+
env: opts.env ? { ...opts.env } : undefined,
|
|
15
|
+
secrets: opts.secrets
|
|
16
|
+
? opts.secrets.map((s) => ({ ...s, hosts: s.hosts ? [...s.hosts] : undefined }))
|
|
17
|
+
: undefined,
|
|
18
|
+
vfsInterception: opts.vfsInterception
|
|
19
|
+
? {
|
|
20
|
+
emitEvents: opts.vfsInterception.emitEvents,
|
|
21
|
+
rules: opts.vfsInterception.rules
|
|
22
|
+
? opts.vfsInterception.rules.map((r) => ({ ...r, ops: r.ops ? [...r.ops] : undefined }))
|
|
23
|
+
: undefined,
|
|
24
|
+
}
|
|
25
|
+
: undefined,
|
|
26
|
+
dnsServers: opts.dnsServers ? [...opts.dnsServers] : undefined,
|
|
27
|
+
portForwards: opts.portForwards
|
|
28
|
+
? opts.portForwards.map((pf) => ({ ...pf }))
|
|
29
|
+
: undefined,
|
|
30
|
+
portForwardAddresses: opts.portForwardAddresses
|
|
31
|
+
? [...opts.portForwardAddresses]
|
|
32
|
+
: undefined,
|
|
33
|
+
imageConfig: opts.imageConfig
|
|
34
|
+
? {
|
|
35
|
+
...opts.imageConfig,
|
|
36
|
+
entrypoint: opts.imageConfig.entrypoint
|
|
37
|
+
? [...opts.imageConfig.entrypoint]
|
|
38
|
+
: undefined,
|
|
39
|
+
cmd: opts.imageConfig.cmd ? [...opts.imageConfig.cmd] : undefined,
|
|
40
|
+
env: opts.imageConfig.env ? { ...opts.imageConfig.env } : undefined,
|
|
41
|
+
}
|
|
42
|
+
: undefined,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
class Sandbox {
|
|
46
|
+
opts;
|
|
47
|
+
constructor(image) {
|
|
48
|
+
this.opts = { image };
|
|
49
|
+
}
|
|
50
|
+
withPrivileged() {
|
|
51
|
+
this.opts.privileged = true;
|
|
52
|
+
return this;
|
|
53
|
+
}
|
|
54
|
+
withCPUs(cpus) {
|
|
55
|
+
this.opts.cpus = cpus;
|
|
56
|
+
return this;
|
|
57
|
+
}
|
|
58
|
+
withMemory(mb) {
|
|
59
|
+
this.opts.memoryMb = mb;
|
|
60
|
+
return this;
|
|
61
|
+
}
|
|
62
|
+
withDiskSize(mb) {
|
|
63
|
+
this.opts.diskSizeMb = mb;
|
|
64
|
+
return this;
|
|
65
|
+
}
|
|
66
|
+
withTimeout(seconds) {
|
|
67
|
+
this.opts.timeoutSeconds = seconds;
|
|
68
|
+
return this;
|
|
69
|
+
}
|
|
70
|
+
withWorkspace(path) {
|
|
71
|
+
this.opts.workspace = path;
|
|
72
|
+
return this;
|
|
73
|
+
}
|
|
74
|
+
withVFSInterception(config) {
|
|
75
|
+
this.opts.vfsInterception = config;
|
|
76
|
+
return this;
|
|
77
|
+
}
|
|
78
|
+
withEnv(name, value) {
|
|
79
|
+
this.opts.env = this.opts.env ?? {};
|
|
80
|
+
this.opts.env[name] = value;
|
|
81
|
+
return this;
|
|
82
|
+
}
|
|
83
|
+
withEnvMap(env) {
|
|
84
|
+
this.opts.env = this.opts.env ?? {};
|
|
85
|
+
Object.assign(this.opts.env, env);
|
|
86
|
+
return this;
|
|
87
|
+
}
|
|
88
|
+
allowHost(...hosts) {
|
|
89
|
+
this.opts.allowedHosts = this.opts.allowedHosts ?? [];
|
|
90
|
+
this.opts.allowedHosts.push(...hosts);
|
|
91
|
+
return this;
|
|
92
|
+
}
|
|
93
|
+
addHost(host, ip) {
|
|
94
|
+
this.opts.addHosts = this.opts.addHosts ?? [];
|
|
95
|
+
this.opts.addHosts.push({ host, ip });
|
|
96
|
+
return this;
|
|
97
|
+
}
|
|
98
|
+
blockPrivateIPs() {
|
|
99
|
+
return this.withBlockPrivateIPs(true);
|
|
100
|
+
}
|
|
101
|
+
withBlockPrivateIPs(enabled) {
|
|
102
|
+
this.opts.blockPrivateIPs = enabled;
|
|
103
|
+
this.opts.blockPrivateIPsSet = true;
|
|
104
|
+
return this;
|
|
105
|
+
}
|
|
106
|
+
allowPrivateIPs() {
|
|
107
|
+
return this.withBlockPrivateIPs(false);
|
|
108
|
+
}
|
|
109
|
+
unsetBlockPrivateIPs() {
|
|
110
|
+
this.opts.blockPrivateIPs = false;
|
|
111
|
+
this.opts.blockPrivateIPsSet = false;
|
|
112
|
+
return this;
|
|
113
|
+
}
|
|
114
|
+
addSecret(name, value, ...hosts) {
|
|
115
|
+
this.opts.secrets = this.opts.secrets ?? [];
|
|
116
|
+
const secret = { name, value, hosts };
|
|
117
|
+
this.opts.secrets.push(secret);
|
|
118
|
+
return this;
|
|
119
|
+
}
|
|
120
|
+
withDNSServers(...servers) {
|
|
121
|
+
this.opts.dnsServers = this.opts.dnsServers ?? [];
|
|
122
|
+
this.opts.dnsServers.push(...servers);
|
|
123
|
+
return this;
|
|
124
|
+
}
|
|
125
|
+
withHostname(hostname) {
|
|
126
|
+
this.opts.hostname = hostname;
|
|
127
|
+
return this;
|
|
128
|
+
}
|
|
129
|
+
withNetworkMTU(mtu) {
|
|
130
|
+
this.opts.networkMtu = mtu;
|
|
131
|
+
return this;
|
|
132
|
+
}
|
|
133
|
+
withPortForward(localPort, remotePort) {
|
|
134
|
+
this.opts.portForwards = this.opts.portForwards ?? [];
|
|
135
|
+
this.opts.portForwards.push({ localPort, remotePort });
|
|
136
|
+
return this;
|
|
137
|
+
}
|
|
138
|
+
withPortForwardAddresses(...addresses) {
|
|
139
|
+
this.opts.portForwardAddresses = this.opts.portForwardAddresses ?? [];
|
|
140
|
+
this.opts.portForwardAddresses.push(...addresses);
|
|
141
|
+
return this;
|
|
142
|
+
}
|
|
143
|
+
mount(guestPath, config) {
|
|
144
|
+
this.opts.mounts = this.opts.mounts ?? {};
|
|
145
|
+
this.opts.mounts[guestPath] = { ...config };
|
|
146
|
+
return this;
|
|
147
|
+
}
|
|
148
|
+
mountHostDir(guestPath, hostPath) {
|
|
149
|
+
return this.mount(guestPath, { type: "host_fs", hostPath });
|
|
150
|
+
}
|
|
151
|
+
mountHostDirReadonly(guestPath, hostPath) {
|
|
152
|
+
return this.mount(guestPath, { type: "host_fs", hostPath, readonly: true });
|
|
153
|
+
}
|
|
154
|
+
mountMemory(guestPath) {
|
|
155
|
+
return this.mount(guestPath, { type: "memory" });
|
|
156
|
+
}
|
|
157
|
+
mountOverlay(guestPath, hostPath) {
|
|
158
|
+
return this.mount(guestPath, { type: "overlay", hostPath });
|
|
159
|
+
}
|
|
160
|
+
withUser(user) {
|
|
161
|
+
this.opts.imageConfig = this.opts.imageConfig ?? {};
|
|
162
|
+
this.opts.imageConfig.user = user;
|
|
163
|
+
return this;
|
|
164
|
+
}
|
|
165
|
+
withEntrypoint(...entrypoint) {
|
|
166
|
+
this.opts.imageConfig = this.opts.imageConfig ?? {};
|
|
167
|
+
this.opts.imageConfig.entrypoint = [...entrypoint];
|
|
168
|
+
return this;
|
|
169
|
+
}
|
|
170
|
+
withImageConfig(config) {
|
|
171
|
+
if (!config) {
|
|
172
|
+
return this;
|
|
173
|
+
}
|
|
174
|
+
this.opts.imageConfig = this.opts.imageConfig ?? {};
|
|
175
|
+
if (config.user) {
|
|
176
|
+
this.opts.imageConfig.user = config.user;
|
|
177
|
+
}
|
|
178
|
+
if (config.workingDir) {
|
|
179
|
+
this.opts.imageConfig.workingDir = config.workingDir;
|
|
180
|
+
}
|
|
181
|
+
if (config.entrypoint && config.entrypoint.length > 0) {
|
|
182
|
+
this.opts.imageConfig.entrypoint = [...config.entrypoint];
|
|
183
|
+
}
|
|
184
|
+
if (config.cmd && config.cmd.length > 0) {
|
|
185
|
+
this.opts.imageConfig.cmd = [...config.cmd];
|
|
186
|
+
}
|
|
187
|
+
if (config.env && Object.keys(config.env).length > 0) {
|
|
188
|
+
this.opts.imageConfig.env = { ...config.env };
|
|
189
|
+
}
|
|
190
|
+
return this;
|
|
191
|
+
}
|
|
192
|
+
options() {
|
|
193
|
+
return cloneCreateOptions(this.opts);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
exports.Sandbox = Sandbox;
|
|
197
|
+
function createSandbox(image) {
|
|
198
|
+
return new Sandbox(image);
|
|
199
|
+
}
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { Sandbox } from "./builder";
|
|
2
|
+
import { type BinaryLike, type Config, type CreateOptions, type ExecOptions, type ExecResult, type ExecStreamOptions, type ExecStreamResult, type FileInfo, type PortForwardBinding, type RequestOptions, type StreamWriter } from "./types";
|
|
3
|
+
export declare function defaultConfig(config?: Config): Required<Config>;
|
|
4
|
+
export declare class Client {
|
|
5
|
+
private readonly config;
|
|
6
|
+
private process;
|
|
7
|
+
private requestID;
|
|
8
|
+
private pending;
|
|
9
|
+
private writeLock;
|
|
10
|
+
private readBuffer;
|
|
11
|
+
private vmIDValue;
|
|
12
|
+
private lastVMID;
|
|
13
|
+
private closed;
|
|
14
|
+
private closing;
|
|
15
|
+
private vfsHooks;
|
|
16
|
+
private vfsMutateHooks;
|
|
17
|
+
private vfsActionHooks;
|
|
18
|
+
private vfsHookActive;
|
|
19
|
+
constructor(config?: Config);
|
|
20
|
+
get vmId(): string;
|
|
21
|
+
start(): Promise<void>;
|
|
22
|
+
close(timeoutSeconds?: number): Promise<void>;
|
|
23
|
+
remove(): Promise<void>;
|
|
24
|
+
launch(sandbox: Sandbox): Promise<string>;
|
|
25
|
+
create(opts?: CreateOptions): Promise<string>;
|
|
26
|
+
private resolveCreateBlockPrivateIPs;
|
|
27
|
+
private buildCreateNetworkParams;
|
|
28
|
+
exec(command: string, options?: ExecOptions): Promise<ExecResult>;
|
|
29
|
+
execWithDir(command: string, workingDir?: string, options?: RequestOptions): Promise<ExecResult>;
|
|
30
|
+
execStream(command: string, options?: ExecStreamOptions): Promise<ExecStreamResult>;
|
|
31
|
+
execStreamWithDir(command: string, workingDir?: string, stdout?: StreamWriter, stderr?: StreamWriter, options?: RequestOptions): Promise<ExecStreamResult>;
|
|
32
|
+
writeFile(path: string, content: BinaryLike, options?: RequestOptions): Promise<void>;
|
|
33
|
+
writeFileMode(path: string, content: BinaryLike, mode: number, options?: RequestOptions): Promise<void>;
|
|
34
|
+
readFile(path: string, options?: RequestOptions): Promise<Buffer>;
|
|
35
|
+
listFiles(path: string, options?: RequestOptions): Promise<FileInfo[]>;
|
|
36
|
+
portForward(...specs: string[]): Promise<PortForwardBinding[]>;
|
|
37
|
+
portForwardWithAddresses(addresses: string[] | undefined, ...specs: string[]): Promise<PortForwardBinding[]>;
|
|
38
|
+
private portForwardMappings;
|
|
39
|
+
private parsePortForwards;
|
|
40
|
+
private parsePortForward;
|
|
41
|
+
private parsePort;
|
|
42
|
+
private sendRequest;
|
|
43
|
+
private sendCancelRequest;
|
|
44
|
+
private enqueueWrite;
|
|
45
|
+
private processReadBuffer;
|
|
46
|
+
private handleMessage;
|
|
47
|
+
private handleNotification;
|
|
48
|
+
private handleVFSFileEventNotification;
|
|
49
|
+
private handleVFSFileEvent;
|
|
50
|
+
private runVFSSafeHooksForEvent;
|
|
51
|
+
private runSingleVFSHook;
|
|
52
|
+
private matchesVFSHook;
|
|
53
|
+
private applyLocalWriteMutations;
|
|
54
|
+
private applyLocalActionHooks;
|
|
55
|
+
private compileVFSHooks;
|
|
56
|
+
private validateLocalAfterRule;
|
|
57
|
+
private ruleToWire;
|
|
58
|
+
private setLocalVFSHooks;
|
|
59
|
+
private validateAddHost;
|
|
60
|
+
private isValidIP;
|
|
61
|
+
private writeStreamChunk;
|
|
62
|
+
private isRunning;
|
|
63
|
+
private waitForProcessExit;
|
|
64
|
+
private handleProcessClosed;
|
|
65
|
+
}
|