@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 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
+ }
@@ -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
+ }
@@ -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
@@ -0,0 +1,5 @@
1
+ export { Sandbox } from "./sandbox";
2
+ export { Filesystem } from "./filesystem";
3
+ export { Commands } from "./commands";
4
+ export { Pty } from "./pty";
5
+ export { Templates } from "./template";
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
+ }
@@ -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
+ }
@@ -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
+ }