@opencomputer/sdk 0.4.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 +39 -0
- package/dist/commands.d.ts +19 -0
- package/dist/commands.js +51 -0
- package/dist/filesystem.d.ts +21 -0
- package/dist/filesystem.js +64 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +5 -0
- package/dist/pty.d.ts +19 -0
- package/dist/pty.js +68 -0
- package/dist/sandbox.d.ts +34 -0
- package/dist/sandbox.js +153 -0
- package/dist/template.d.ts +16 -0
- package/dist/template.js +52 -0
- package/package.json +50 -0
package/README.md
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# @opencomputer/sdk
|
|
2
|
+
|
|
3
|
+
TypeScript SDK for [OpenComputer](https://github.com/diggerhq/opensandbox) — cloud sandbox platform.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @opencomputer/sdk
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import { Sandbox } from "@opencomputer/sdk";
|
|
15
|
+
|
|
16
|
+
const sandbox = await Sandbox.create({ template: "base" });
|
|
17
|
+
|
|
18
|
+
// Execute commands
|
|
19
|
+
const result = await sandbox.commands.run("echo hello");
|
|
20
|
+
console.log(result.stdout); // "hello\n"
|
|
21
|
+
|
|
22
|
+
// Read and write files
|
|
23
|
+
await sandbox.files.write("/tmp/test.txt", "Hello, world!");
|
|
24
|
+
const content = await sandbox.files.read("/tmp/test.txt");
|
|
25
|
+
|
|
26
|
+
// Clean up
|
|
27
|
+
await sandbox.kill();
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Configuration
|
|
31
|
+
|
|
32
|
+
| Option | Env Variable | Default |
|
|
33
|
+
|-----------|------------------------|--------------------------|
|
|
34
|
+
| `apiUrl` | `OPENSANDBOX_API_URL` | `https://app.opencomputer.dev` |
|
|
35
|
+
| `apiKey` | `OPENSANDBOX_API_KEY` | (none) |
|
|
36
|
+
|
|
37
|
+
## License
|
|
38
|
+
|
|
39
|
+
MIT
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export interface ProcessResult {
|
|
2
|
+
exitCode: number;
|
|
3
|
+
stdout: string;
|
|
4
|
+
stderr: string;
|
|
5
|
+
}
|
|
6
|
+
export interface RunOpts {
|
|
7
|
+
timeout?: number;
|
|
8
|
+
env?: Record<string, string>;
|
|
9
|
+
cwd?: string;
|
|
10
|
+
}
|
|
11
|
+
export declare class Commands {
|
|
12
|
+
private apiUrl;
|
|
13
|
+
private apiKey;
|
|
14
|
+
private sandboxId;
|
|
15
|
+
private token;
|
|
16
|
+
constructor(apiUrl: string, apiKey: string, sandboxId: string, token?: string);
|
|
17
|
+
private get headers();
|
|
18
|
+
run(command: string, opts?: RunOpts): Promise<ProcessResult>;
|
|
19
|
+
}
|
package/dist/commands.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
export class Commands {
|
|
2
|
+
apiUrl;
|
|
3
|
+
apiKey;
|
|
4
|
+
sandboxId;
|
|
5
|
+
token;
|
|
6
|
+
constructor(apiUrl, apiKey, sandboxId, token = "") {
|
|
7
|
+
this.apiUrl = apiUrl;
|
|
8
|
+
this.apiKey = apiKey;
|
|
9
|
+
this.sandboxId = sandboxId;
|
|
10
|
+
this.token = token;
|
|
11
|
+
}
|
|
12
|
+
get headers() {
|
|
13
|
+
const h = { "Content-Type": "application/json" };
|
|
14
|
+
if (this.token) {
|
|
15
|
+
h["Authorization"] = `Bearer ${this.token}`;
|
|
16
|
+
}
|
|
17
|
+
else if (this.apiKey) {
|
|
18
|
+
h["X-API-Key"] = this.apiKey;
|
|
19
|
+
}
|
|
20
|
+
return h;
|
|
21
|
+
}
|
|
22
|
+
async run(command, opts = {}) {
|
|
23
|
+
const timeout = opts.timeout ?? 60;
|
|
24
|
+
const body = {
|
|
25
|
+
cmd: command,
|
|
26
|
+
timeout,
|
|
27
|
+
};
|
|
28
|
+
if (opts.env)
|
|
29
|
+
body.envs = opts.env;
|
|
30
|
+
if (opts.cwd)
|
|
31
|
+
body.cwd = opts.cwd;
|
|
32
|
+
const controller = new AbortController();
|
|
33
|
+
const timeoutId = globalThis.setTimeout(() => controller.abort(), (timeout + 5) * 1000);
|
|
34
|
+
try {
|
|
35
|
+
const resp = await fetch(`${this.apiUrl}/sandboxes/${this.sandboxId}/commands`, {
|
|
36
|
+
method: "POST",
|
|
37
|
+
headers: this.headers,
|
|
38
|
+
body: JSON.stringify(body),
|
|
39
|
+
signal: controller.signal,
|
|
40
|
+
});
|
|
41
|
+
if (!resp.ok) {
|
|
42
|
+
const text = await resp.text();
|
|
43
|
+
throw new Error(`Command failed: ${resp.status} ${text}`);
|
|
44
|
+
}
|
|
45
|
+
return await resp.json();
|
|
46
|
+
}
|
|
47
|
+
finally {
|
|
48
|
+
globalThis.clearTimeout(timeoutId);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export interface EntryInfo {
|
|
2
|
+
name: string;
|
|
3
|
+
isDir: boolean;
|
|
4
|
+
path: string;
|
|
5
|
+
size: number;
|
|
6
|
+
}
|
|
7
|
+
export declare class Filesystem {
|
|
8
|
+
private apiUrl;
|
|
9
|
+
private apiKey;
|
|
10
|
+
private sandboxId;
|
|
11
|
+
private token;
|
|
12
|
+
constructor(apiUrl: string, apiKey: string, sandboxId: string, token?: string);
|
|
13
|
+
private get headers();
|
|
14
|
+
read(path: string): Promise<string>;
|
|
15
|
+
readBytes(path: string): Promise<Uint8Array>;
|
|
16
|
+
write(path: string, content: string | Uint8Array): Promise<void>;
|
|
17
|
+
list(path?: string): Promise<EntryInfo[]>;
|
|
18
|
+
makeDir(path: string): Promise<void>;
|
|
19
|
+
remove(path: string): Promise<void>;
|
|
20
|
+
exists(path: string): Promise<boolean>;
|
|
21
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
export class Filesystem {
|
|
2
|
+
apiUrl;
|
|
3
|
+
apiKey;
|
|
4
|
+
sandboxId;
|
|
5
|
+
token;
|
|
6
|
+
constructor(apiUrl, apiKey, sandboxId, token = "") {
|
|
7
|
+
this.apiUrl = apiUrl;
|
|
8
|
+
this.apiKey = apiKey;
|
|
9
|
+
this.sandboxId = sandboxId;
|
|
10
|
+
this.token = token;
|
|
11
|
+
}
|
|
12
|
+
get headers() {
|
|
13
|
+
if (this.token)
|
|
14
|
+
return { "Authorization": `Bearer ${this.token}` };
|
|
15
|
+
return this.apiKey ? { "X-API-Key": this.apiKey } : {};
|
|
16
|
+
}
|
|
17
|
+
async read(path) {
|
|
18
|
+
const resp = await fetch(`${this.apiUrl}/sandboxes/${this.sandboxId}/files?path=${encodeURIComponent(path)}`, { headers: this.headers });
|
|
19
|
+
if (!resp.ok)
|
|
20
|
+
throw new Error(`Failed to read ${path}: ${resp.status}`);
|
|
21
|
+
return resp.text();
|
|
22
|
+
}
|
|
23
|
+
async readBytes(path) {
|
|
24
|
+
const resp = await fetch(`${this.apiUrl}/sandboxes/${this.sandboxId}/files?path=${encodeURIComponent(path)}`, { headers: this.headers });
|
|
25
|
+
if (!resp.ok)
|
|
26
|
+
throw new Error(`Failed to read ${path}: ${resp.status}`);
|
|
27
|
+
return new Uint8Array(await resp.arrayBuffer());
|
|
28
|
+
}
|
|
29
|
+
async write(path, content) {
|
|
30
|
+
const resp = await fetch(`${this.apiUrl}/sandboxes/${this.sandboxId}/files?path=${encodeURIComponent(path)}`, {
|
|
31
|
+
method: "PUT",
|
|
32
|
+
headers: this.headers,
|
|
33
|
+
body: content,
|
|
34
|
+
});
|
|
35
|
+
if (!resp.ok)
|
|
36
|
+
throw new Error(`Failed to write ${path}: ${resp.status}`);
|
|
37
|
+
}
|
|
38
|
+
async list(path = "/") {
|
|
39
|
+
const resp = await fetch(`${this.apiUrl}/sandboxes/${this.sandboxId}/files/list?path=${encodeURIComponent(path)}`, { headers: this.headers });
|
|
40
|
+
if (!resp.ok)
|
|
41
|
+
throw new Error(`Failed to list ${path}: ${resp.status}`);
|
|
42
|
+
const data = await resp.json();
|
|
43
|
+
return data ?? [];
|
|
44
|
+
}
|
|
45
|
+
async makeDir(path) {
|
|
46
|
+
const resp = await fetch(`${this.apiUrl}/sandboxes/${this.sandboxId}/files/mkdir?path=${encodeURIComponent(path)}`, { method: "POST", headers: this.headers });
|
|
47
|
+
if (!resp.ok)
|
|
48
|
+
throw new Error(`Failed to mkdir ${path}: ${resp.status}`);
|
|
49
|
+
}
|
|
50
|
+
async remove(path) {
|
|
51
|
+
const resp = await fetch(`${this.apiUrl}/sandboxes/${this.sandboxId}/files?path=${encodeURIComponent(path)}`, { method: "DELETE", headers: this.headers });
|
|
52
|
+
if (!resp.ok)
|
|
53
|
+
throw new Error(`Failed to remove ${path}: ${resp.status}`);
|
|
54
|
+
}
|
|
55
|
+
async exists(path) {
|
|
56
|
+
try {
|
|
57
|
+
const resp = await fetch(`${this.apiUrl}/sandboxes/${this.sandboxId}/files?path=${encodeURIComponent(path)}`, { headers: this.headers });
|
|
58
|
+
return resp.ok;
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { Sandbox, type SandboxOpts } from "./sandbox";
|
|
2
|
+
export { Filesystem, type EntryInfo } from "./filesystem";
|
|
3
|
+
export { Commands, type ProcessResult, type RunOpts } from "./commands";
|
|
4
|
+
export { Pty, type PtySession, type PtyOpts } from "./pty";
|
|
5
|
+
export { Templates, type TemplateInfo } from "./template";
|
package/dist/index.js
ADDED
package/dist/pty.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export interface PtyOpts {
|
|
2
|
+
cols?: number;
|
|
3
|
+
rows?: number;
|
|
4
|
+
onOutput?: (data: Uint8Array) => void;
|
|
5
|
+
}
|
|
6
|
+
export interface PtySession {
|
|
7
|
+
sessionId: string;
|
|
8
|
+
send(data: string | Uint8Array): void;
|
|
9
|
+
close(): void;
|
|
10
|
+
}
|
|
11
|
+
export declare class Pty {
|
|
12
|
+
private apiUrl;
|
|
13
|
+
private apiKey;
|
|
14
|
+
private sandboxId;
|
|
15
|
+
private token;
|
|
16
|
+
constructor(apiUrl: string, apiKey: string, sandboxId: string, token?: string);
|
|
17
|
+
private get headers();
|
|
18
|
+
create(opts?: PtyOpts): Promise<PtySession>;
|
|
19
|
+
}
|
package/dist/pty.js
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
export class Pty {
|
|
2
|
+
apiUrl;
|
|
3
|
+
apiKey;
|
|
4
|
+
sandboxId;
|
|
5
|
+
token;
|
|
6
|
+
constructor(apiUrl, apiKey, sandboxId, token = "") {
|
|
7
|
+
this.apiUrl = apiUrl;
|
|
8
|
+
this.apiKey = apiKey;
|
|
9
|
+
this.sandboxId = sandboxId;
|
|
10
|
+
this.token = token;
|
|
11
|
+
}
|
|
12
|
+
get headers() {
|
|
13
|
+
const h = { "Content-Type": "application/json" };
|
|
14
|
+
if (this.token) {
|
|
15
|
+
h["Authorization"] = `Bearer ${this.token}`;
|
|
16
|
+
}
|
|
17
|
+
else if (this.apiKey) {
|
|
18
|
+
h["X-API-Key"] = this.apiKey;
|
|
19
|
+
}
|
|
20
|
+
return h;
|
|
21
|
+
}
|
|
22
|
+
async create(opts = {}) {
|
|
23
|
+
// Create session via REST
|
|
24
|
+
const resp = await fetch(`${this.apiUrl}/sandboxes/${this.sandboxId}/pty`, {
|
|
25
|
+
method: "POST",
|
|
26
|
+
headers: this.headers,
|
|
27
|
+
body: JSON.stringify({
|
|
28
|
+
cols: opts.cols ?? 80,
|
|
29
|
+
rows: opts.rows ?? 24,
|
|
30
|
+
}),
|
|
31
|
+
});
|
|
32
|
+
if (!resp.ok) {
|
|
33
|
+
throw new Error(`Failed to create PTY: ${resp.status}`);
|
|
34
|
+
}
|
|
35
|
+
const data = await resp.json();
|
|
36
|
+
const sessionId = data.sessionID;
|
|
37
|
+
// Connect via WebSocket
|
|
38
|
+
const wsUrl = this.apiUrl
|
|
39
|
+
.replace("http://", "ws://")
|
|
40
|
+
.replace("https://", "wss://");
|
|
41
|
+
const wsEndpoint = `${wsUrl}/sandboxes/${this.sandboxId}/pty/${sessionId}`;
|
|
42
|
+
const ws = new WebSocket(wsEndpoint);
|
|
43
|
+
ws.binaryType = "arraybuffer";
|
|
44
|
+
if (opts.onOutput) {
|
|
45
|
+
const onOutput = opts.onOutput;
|
|
46
|
+
ws.onmessage = (event) => {
|
|
47
|
+
const data = event.data instanceof ArrayBuffer
|
|
48
|
+
? new Uint8Array(event.data)
|
|
49
|
+
: new TextEncoder().encode(event.data);
|
|
50
|
+
onOutput(data);
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
return {
|
|
54
|
+
sessionId,
|
|
55
|
+
send(data) {
|
|
56
|
+
if (typeof data === "string") {
|
|
57
|
+
ws.send(data);
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
ws.send(data);
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
close() {
|
|
64
|
+
ws.close();
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Filesystem } from "./filesystem";
|
|
2
|
+
import { Commands } from "./commands";
|
|
3
|
+
import { Pty } from "./pty";
|
|
4
|
+
export interface SandboxOpts {
|
|
5
|
+
template?: string;
|
|
6
|
+
timeout?: number;
|
|
7
|
+
apiKey?: string;
|
|
8
|
+
apiUrl?: string;
|
|
9
|
+
envs?: Record<string, string>;
|
|
10
|
+
metadata?: Record<string, string>;
|
|
11
|
+
}
|
|
12
|
+
export declare class Sandbox {
|
|
13
|
+
readonly sandboxId: string;
|
|
14
|
+
readonly domain: string;
|
|
15
|
+
readonly files: Filesystem;
|
|
16
|
+
readonly commands: Commands;
|
|
17
|
+
readonly pty: Pty;
|
|
18
|
+
private apiUrl;
|
|
19
|
+
private apiKey;
|
|
20
|
+
private connectUrl;
|
|
21
|
+
private token;
|
|
22
|
+
private _status;
|
|
23
|
+
private constructor();
|
|
24
|
+
get status(): string;
|
|
25
|
+
static create(opts?: SandboxOpts): Promise<Sandbox>;
|
|
26
|
+
static connect(sandboxId: string, opts?: Pick<SandboxOpts, "apiKey" | "apiUrl">): Promise<Sandbox>;
|
|
27
|
+
kill(): Promise<void>;
|
|
28
|
+
isRunning(): Promise<boolean>;
|
|
29
|
+
hibernate(): Promise<void>;
|
|
30
|
+
wake(opts?: {
|
|
31
|
+
timeout?: number;
|
|
32
|
+
}): Promise<void>;
|
|
33
|
+
setTimeout(timeout: number): Promise<void>;
|
|
34
|
+
}
|
package/dist/sandbox.js
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { Filesystem } from "./filesystem";
|
|
2
|
+
import { Commands } from "./commands";
|
|
3
|
+
import { Pty } from "./pty";
|
|
4
|
+
function resolveApiUrl(url) {
|
|
5
|
+
const base = url.replace(/\/+$/, "");
|
|
6
|
+
return base.endsWith("/api") ? base : `${base}/api`;
|
|
7
|
+
}
|
|
8
|
+
export class Sandbox {
|
|
9
|
+
sandboxId;
|
|
10
|
+
domain;
|
|
11
|
+
files;
|
|
12
|
+
commands;
|
|
13
|
+
pty;
|
|
14
|
+
apiUrl;
|
|
15
|
+
apiKey;
|
|
16
|
+
connectUrl;
|
|
17
|
+
token;
|
|
18
|
+
_status;
|
|
19
|
+
constructor(data, apiUrl, apiKey) {
|
|
20
|
+
this.sandboxId = data.sandboxID;
|
|
21
|
+
this.domain = data.domain || "";
|
|
22
|
+
this._status = data.status;
|
|
23
|
+
this.apiUrl = apiUrl;
|
|
24
|
+
this.apiKey = apiKey;
|
|
25
|
+
this.connectUrl = data.connectURL || "";
|
|
26
|
+
this.token = data.token || "";
|
|
27
|
+
// Use direct worker URL for data operations if available
|
|
28
|
+
const opsUrl = this.connectUrl || apiUrl;
|
|
29
|
+
const opsKey = this.connectUrl ? "" : apiKey;
|
|
30
|
+
const opsToken = this.connectUrl ? this.token : "";
|
|
31
|
+
this.files = new Filesystem(opsUrl, opsKey, this.sandboxId, opsToken);
|
|
32
|
+
this.commands = new Commands(opsUrl, opsKey, this.sandboxId, opsToken);
|
|
33
|
+
this.pty = new Pty(opsUrl, opsKey, this.sandboxId, opsToken);
|
|
34
|
+
}
|
|
35
|
+
get status() {
|
|
36
|
+
return this._status;
|
|
37
|
+
}
|
|
38
|
+
static async create(opts = {}) {
|
|
39
|
+
const apiUrl = resolveApiUrl(opts.apiUrl ?? process.env.OPENSANDBOX_API_URL ?? "https://app.opencomputer.dev");
|
|
40
|
+
const apiKey = opts.apiKey ?? process.env.OPENSANDBOX_API_KEY ?? "";
|
|
41
|
+
const body = {
|
|
42
|
+
templateID: opts.template ?? "base",
|
|
43
|
+
timeout: opts.timeout ?? 300,
|
|
44
|
+
};
|
|
45
|
+
if (opts.envs)
|
|
46
|
+
body.envs = opts.envs;
|
|
47
|
+
if (opts.metadata)
|
|
48
|
+
body.metadata = opts.metadata;
|
|
49
|
+
const resp = await fetch(`${apiUrl}/sandboxes`, {
|
|
50
|
+
method: "POST",
|
|
51
|
+
headers: {
|
|
52
|
+
"Content-Type": "application/json",
|
|
53
|
+
...(apiKey ? { "X-API-Key": apiKey } : {}),
|
|
54
|
+
},
|
|
55
|
+
body: JSON.stringify(body),
|
|
56
|
+
});
|
|
57
|
+
if (!resp.ok) {
|
|
58
|
+
const text = await resp.text();
|
|
59
|
+
throw new Error(`Failed to create sandbox: ${resp.status} ${text}`);
|
|
60
|
+
}
|
|
61
|
+
const data = await resp.json();
|
|
62
|
+
return new Sandbox(data, apiUrl, apiKey);
|
|
63
|
+
}
|
|
64
|
+
static async connect(sandboxId, opts = {}) {
|
|
65
|
+
const apiUrl = resolveApiUrl(opts.apiUrl ?? process.env.OPENSANDBOX_API_URL ?? "https://app.opencomputer.dev");
|
|
66
|
+
const apiKey = opts.apiKey ?? process.env.OPENSANDBOX_API_KEY ?? "";
|
|
67
|
+
const resp = await fetch(`${apiUrl}/sandboxes/${sandboxId}`, {
|
|
68
|
+
headers: apiKey ? { "X-API-Key": apiKey } : {},
|
|
69
|
+
});
|
|
70
|
+
if (!resp.ok) {
|
|
71
|
+
throw new Error(`Failed to connect to sandbox ${sandboxId}: ${resp.status}`);
|
|
72
|
+
}
|
|
73
|
+
const data = await resp.json();
|
|
74
|
+
return new Sandbox(data, apiUrl, apiKey);
|
|
75
|
+
}
|
|
76
|
+
async kill() {
|
|
77
|
+
const resp = await fetch(`${this.apiUrl}/sandboxes/${this.sandboxId}`, {
|
|
78
|
+
method: "DELETE",
|
|
79
|
+
headers: this.apiKey ? { "X-API-Key": this.apiKey } : {},
|
|
80
|
+
});
|
|
81
|
+
if (!resp.ok) {
|
|
82
|
+
throw new Error(`Failed to kill sandbox: ${resp.status}`);
|
|
83
|
+
}
|
|
84
|
+
this._status = "stopped";
|
|
85
|
+
}
|
|
86
|
+
async isRunning() {
|
|
87
|
+
try {
|
|
88
|
+
const resp = await fetch(`${this.apiUrl}/sandboxes/${this.sandboxId}`, {
|
|
89
|
+
headers: this.apiKey ? { "X-API-Key": this.apiKey } : {},
|
|
90
|
+
});
|
|
91
|
+
if (!resp.ok)
|
|
92
|
+
return false;
|
|
93
|
+
const data = await resp.json();
|
|
94
|
+
this._status = data.status;
|
|
95
|
+
return data.status === "running";
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
async hibernate() {
|
|
102
|
+
const resp = await fetch(`${this.apiUrl}/sandboxes/${this.sandboxId}/hibernate`, {
|
|
103
|
+
method: "POST",
|
|
104
|
+
headers: {
|
|
105
|
+
"Content-Type": "application/json",
|
|
106
|
+
...(this.apiKey ? { "X-API-Key": this.apiKey } : {}),
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
if (!resp.ok) {
|
|
110
|
+
const text = await resp.text();
|
|
111
|
+
throw new Error(`Failed to hibernate sandbox: ${resp.status} ${text}`);
|
|
112
|
+
}
|
|
113
|
+
this._status = "hibernated";
|
|
114
|
+
}
|
|
115
|
+
async wake(opts = {}) {
|
|
116
|
+
const resp = await fetch(`${this.apiUrl}/sandboxes/${this.sandboxId}/wake`, {
|
|
117
|
+
method: "POST",
|
|
118
|
+
headers: {
|
|
119
|
+
"Content-Type": "application/json",
|
|
120
|
+
...(this.apiKey ? { "X-API-Key": this.apiKey } : {}),
|
|
121
|
+
},
|
|
122
|
+
body: JSON.stringify({ timeout: opts.timeout ?? 300 }),
|
|
123
|
+
});
|
|
124
|
+
if (!resp.ok) {
|
|
125
|
+
const text = await resp.text();
|
|
126
|
+
throw new Error(`Failed to wake sandbox: ${resp.status} ${text}`);
|
|
127
|
+
}
|
|
128
|
+
const data = await resp.json();
|
|
129
|
+
this._status = data.status;
|
|
130
|
+
this.connectUrl = data.connectURL || "";
|
|
131
|
+
this.token = data.token || "";
|
|
132
|
+
// Rebuild ops clients with new worker connection
|
|
133
|
+
const opsUrl = this.connectUrl || this.apiUrl;
|
|
134
|
+
const opsKey = this.connectUrl ? "" : this.apiKey;
|
|
135
|
+
const opsToken = this.connectUrl ? this.token : "";
|
|
136
|
+
this.files = new Filesystem(opsUrl, opsKey, this.sandboxId, opsToken);
|
|
137
|
+
this.commands = new Commands(opsUrl, opsKey, this.sandboxId, opsToken);
|
|
138
|
+
this.pty = new Pty(opsUrl, opsKey, this.sandboxId, opsToken);
|
|
139
|
+
}
|
|
140
|
+
async setTimeout(timeout) {
|
|
141
|
+
const resp = await fetch(`${this.apiUrl}/sandboxes/${this.sandboxId}/timeout`, {
|
|
142
|
+
method: "POST",
|
|
143
|
+
headers: {
|
|
144
|
+
"Content-Type": "application/json",
|
|
145
|
+
...(this.apiKey ? { "X-API-Key": this.apiKey } : {}),
|
|
146
|
+
},
|
|
147
|
+
body: JSON.stringify({ timeout }),
|
|
148
|
+
});
|
|
149
|
+
if (!resp.ok) {
|
|
150
|
+
throw new Error(`Failed to set timeout: ${resp.status}`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface TemplateInfo {
|
|
2
|
+
templateID: string;
|
|
3
|
+
name: string;
|
|
4
|
+
tag: string;
|
|
5
|
+
status: string;
|
|
6
|
+
}
|
|
7
|
+
export declare class Templates {
|
|
8
|
+
private apiUrl;
|
|
9
|
+
private apiKey;
|
|
10
|
+
constructor(apiUrl: string, apiKey: string);
|
|
11
|
+
private get headers();
|
|
12
|
+
build(name: string, dockerfile: string): Promise<TemplateInfo>;
|
|
13
|
+
list(): Promise<TemplateInfo[]>;
|
|
14
|
+
get(name: string): Promise<TemplateInfo>;
|
|
15
|
+
delete(name: string): Promise<void>;
|
|
16
|
+
}
|
package/dist/template.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
export class Templates {
|
|
2
|
+
apiUrl;
|
|
3
|
+
apiKey;
|
|
4
|
+
constructor(apiUrl, apiKey) {
|
|
5
|
+
this.apiUrl = apiUrl;
|
|
6
|
+
this.apiKey = apiKey;
|
|
7
|
+
}
|
|
8
|
+
get headers() {
|
|
9
|
+
const h = { "Content-Type": "application/json" };
|
|
10
|
+
if (this.apiKey)
|
|
11
|
+
h["X-API-Key"] = this.apiKey;
|
|
12
|
+
return h;
|
|
13
|
+
}
|
|
14
|
+
async build(name, dockerfile) {
|
|
15
|
+
const resp = await fetch(`${this.apiUrl}/templates`, {
|
|
16
|
+
method: "POST",
|
|
17
|
+
headers: this.headers,
|
|
18
|
+
body: JSON.stringify({ name, dockerfile }),
|
|
19
|
+
});
|
|
20
|
+
if (!resp.ok) {
|
|
21
|
+
throw new Error(`Failed to build template: ${resp.status}`);
|
|
22
|
+
}
|
|
23
|
+
return resp.json();
|
|
24
|
+
}
|
|
25
|
+
async list() {
|
|
26
|
+
const resp = await fetch(`${this.apiUrl}/templates`, {
|
|
27
|
+
headers: this.headers,
|
|
28
|
+
});
|
|
29
|
+
if (!resp.ok) {
|
|
30
|
+
throw new Error(`Failed to list templates: ${resp.status}`);
|
|
31
|
+
}
|
|
32
|
+
return resp.json();
|
|
33
|
+
}
|
|
34
|
+
async get(name) {
|
|
35
|
+
const resp = await fetch(`${this.apiUrl}/templates/${name}`, {
|
|
36
|
+
headers: this.headers,
|
|
37
|
+
});
|
|
38
|
+
if (!resp.ok) {
|
|
39
|
+
throw new Error(`Failed to get template: ${resp.status}`);
|
|
40
|
+
}
|
|
41
|
+
return resp.json();
|
|
42
|
+
}
|
|
43
|
+
async delete(name) {
|
|
44
|
+
const resp = await fetch(`${this.apiUrl}/templates/${name}`, {
|
|
45
|
+
method: "DELETE",
|
|
46
|
+
headers: this.headers,
|
|
47
|
+
});
|
|
48
|
+
if (!resp.ok) {
|
|
49
|
+
throw new Error(`Failed to delete template: ${resp.status}`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@opencomputer/sdk",
|
|
3
|
+
"version": "0.4.0",
|
|
4
|
+
"description": "TypeScript SDK for OpenComputer - cloud sandbox platform",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": ["dist"],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "tsc",
|
|
17
|
+
"test": "vitest run",
|
|
18
|
+
"lint": "tsc --noEmit",
|
|
19
|
+
"prepublishOnly": "npm run build"
|
|
20
|
+
},
|
|
21
|
+
"keywords": [
|
|
22
|
+
"sandbox",
|
|
23
|
+
"opencomputer",
|
|
24
|
+
"e2b",
|
|
25
|
+
"cloud",
|
|
26
|
+
"containers",
|
|
27
|
+
"code-execution"
|
|
28
|
+
],
|
|
29
|
+
"author": "OpenComputer",
|
|
30
|
+
"license": "MIT",
|
|
31
|
+
"repository": {
|
|
32
|
+
"type": "git",
|
|
33
|
+
"url": "git+https://github.com/diggerhq/opensandbox.git",
|
|
34
|
+
"directory": "sdks/typescript"
|
|
35
|
+
},
|
|
36
|
+
"homepage": "https://github.com/diggerhq/opensandbox/tree/main/sdks/typescript",
|
|
37
|
+
"bugs": {
|
|
38
|
+
"url": "https://github.com/diggerhq/opensandbox/issues"
|
|
39
|
+
},
|
|
40
|
+
"engines": {
|
|
41
|
+
"node": ">=18"
|
|
42
|
+
},
|
|
43
|
+
"publishConfig": {
|
|
44
|
+
"access": "public"
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"typescript": "^5.4.0",
|
|
48
|
+
"vitest": "^1.6.0"
|
|
49
|
+
}
|
|
50
|
+
}
|