@computesdk/sandbox-cli 0.0.0-stage → 0.1.1

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 computesdk
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,110 @@
1
- # Temporary Holding Version
1
+ # @computesdk/sandbox-cli
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ [sandbox-cli](https://github.com/Amitgb14/sandbox-cli) provider for ComputeSDK.
4
+
5
+ sandbox-cli runs every sandbox as a microVM with its own kernel, behind one HTTP
6
+ API that is the same in three places: on a Mac (the native macOS `container`
7
+ runtime), on a Linux machine you control (Firecracker under KVM), and in the
8
+ cloud. This provider talks to that API with `fetch`; it has no other
9
+ dependency.
10
+
11
+ ## Installation
12
+
13
+ ```bash
14
+ npm install @computesdk/sandbox-cli
15
+ ```
16
+
17
+ ## Setup
18
+
19
+ Point the provider at a `sandboxd` (one machine) or a `sandbox-gateway` (many
20
+ machines) and give it a token:
21
+
22
+ ```bash
23
+ export SANDBOX_ENDPOINT=https://sandbox.example.com
24
+ export SANDBOX_TOKEN=your_token
25
+ # on a gateway, optionally the organization to act in
26
+ export SANDBOX_ORG=your_org
27
+ ```
28
+
29
+ These are the same variables the `sandbox-cli` command and its SDKs read.
30
+
31
+ The endpoint must be `https://`, except for a sandboxd on the same machine:
32
+ plain `http://` is accepted only to `127.0.0.1`, `::1` or `localhost`, since every
33
+ request carries the token.
34
+
35
+ ## Usage
36
+
37
+ ```typescript
38
+ import { sandboxCli } from '@computesdk/sandbox-cli';
39
+
40
+ const compute = sandboxCli({
41
+ endpoint: process.env.SANDBOX_ENDPOINT,
42
+ token: process.env.SANDBOX_TOKEN,
43
+ });
44
+
45
+ // Create a sandbox: resolves once the VM is booted and running
46
+ const sandbox = await compute.sandbox.create();
47
+
48
+ // Run a command
49
+ const result = await sandbox.runCommand('node -v');
50
+ console.log(result.stdout);
51
+
52
+ // Live output
53
+ await sandbox.runCommand('for i in 1 2 3; do echo $i; sleep 1; done', {
54
+ onStdout: (chunk) => process.stdout.write(chunk),
55
+ });
56
+
57
+ // Files
58
+ await sandbox.filesystem.writeFile('/sandbox/home/hello.txt', 'hi');
59
+ console.log(await sandbox.filesystem.readFile('/sandbox/home/hello.txt'));
60
+
61
+ await sandbox.destroy();
62
+ ```
63
+
64
+ ## Configuration
65
+
66
+ ```typescript
67
+ interface SandboxCliConfig {
68
+ /** API endpoint; falls back to SANDBOX_ENDPOINT */
69
+ endpoint?: string;
70
+ /** Bearer token; falls back to SANDBOX_TOKEN */
71
+ token?: string;
72
+ /** On a gateway, the organization to act in; falls back to SANDBOX_ORG */
73
+ org?: string;
74
+ /** Default sandbox lifetime in milliseconds; unset leaves the server's default */
75
+ timeout?: number;
76
+ }
77
+ ```
78
+
79
+ ### Create options
80
+
81
+ | ComputeSDK option | sandbox-cli field |
82
+ |---|---|
83
+ | `image` / `templateId` | `image` (an OCI image) |
84
+ | `snapshotId` | `snapshot_id` |
85
+ | `name` | `name` |
86
+ | `envs` | `env` |
87
+ | `metadata` | `labels` (values as strings) |
88
+ | `cpus` / `vcpus` / `cpu` | `cpus` |
89
+ | `memoryMb` / `memory` / `memoryMiB` / `memMiB` | `memory_mb` |
90
+ | `diskMb` / `diskMiB` | `disk_mb` |
91
+ | `timeout` (ms) | `lifetime_secs` |
92
+
93
+ The server's policy has the last word: a request may tighten what is in force,
94
+ never loosen it, and one it cannot honour is refused rather than weakened.
95
+
96
+ ## Supported
97
+
98
+ - Sandbox lifecycle: `create`, `getById`, `list`, `destroy`
99
+ - `runCommand`, with `cwd`, `env`, `timeout` and `background`
100
+ - Live output (`onStdout` / `onStderr`) through the API's own process stream
101
+ - Filesystem: `readFile`, `writeFile`, `mkdir`, `readdir`, `exists`, `remove`
102
+ - `getInfo`
103
+
104
+ ## Limitations
105
+
106
+ - `getUrl` is not supported. A port inside a sandbox is reached through the
107
+ API's tunnel (an upgraded connection to the guest's own loopback), not a
108
+ public URL; use `sandbox-cli tunnel`.
109
+ - Commands run through `sh -c` as the sandbox user, starting in
110
+ `/sandbox/home`.
@@ -0,0 +1,62 @@
1
+ import * as computesdk from 'computesdk';
2
+ import * as _computesdk_provider from '@computesdk/provider';
3
+
4
+ /**
5
+ * sandbox-cli Provider - Factory-based Implementation
6
+ *
7
+ * sandbox-cli runs each sandbox as a microVM with its own kernel, behind one
8
+ * HTTP API (v1) that is the same on a Mac, on a self-hosted Linux machine and
9
+ * in the cloud. This provider talks to that API directly with fetch, so it
10
+ * has no SDK dependency.
11
+ */
12
+ /**
13
+ * sandbox-cli provider configuration
14
+ */
15
+ interface SandboxCliConfig {
16
+ /** API endpoint, e.g. `https://sandbox.example.com` - falls back to SANDBOX_ENDPOINT */
17
+ endpoint?: string;
18
+ /** Bearer token - falls back to SANDBOX_TOKEN */
19
+ token?: string;
20
+ /** On a gateway, the organization to act in - falls back to SANDBOX_ORG */
21
+ org?: string;
22
+ /** Default sandbox lifetime in milliseconds; unset leaves the server's default */
23
+ timeout?: number;
24
+ }
25
+ /** A sandbox as the v1 API returns it (the fields this provider reads). */
26
+ interface ApiSandbox {
27
+ id: string;
28
+ name?: string;
29
+ state: 'pending' | 'running' | 'suspended' | 'terminated';
30
+ image: string;
31
+ cpus: number;
32
+ memory_mb: number;
33
+ created_at: string;
34
+ lifetime_secs?: number;
35
+ labels?: Record<string, string>;
36
+ }
37
+ /** The native instance: the API record plus a client bound to it. */
38
+ interface SandboxCliInstance {
39
+ client: ApiClient;
40
+ sandbox: ApiSandbox;
41
+ }
42
+ declare class ApiClient {
43
+ readonly base: string;
44
+ private readonly headers;
45
+ constructor(config: SandboxCliConfig);
46
+ request(method: string, path: string, init?: {
47
+ query?: Record<string, string>;
48
+ json?: unknown;
49
+ body?: BodyInit;
50
+ signal?: AbortSignal;
51
+ }): Promise<Response>;
52
+ json<T>(method: string, path: string, payload?: unknown): Promise<T>;
53
+ sbx(id: string): string;
54
+ }
55
+ /**
56
+ * Create a sandbox-cli provider instance using the factory pattern
57
+ */
58
+ declare const sandboxCli: (config: SandboxCliConfig & _computesdk_provider.ProcessRuntimeOptions) => _computesdk_provider.Provider<SandboxCliInstance, any, any, computesdk.Volume> & {
59
+ readonly sizes: computesdk.InstanceSizeManager;
60
+ };
61
+
62
+ export { type SandboxCliConfig, type SandboxCliInstance, sandboxCli };
@@ -0,0 +1,62 @@
1
+ import * as computesdk from 'computesdk';
2
+ import * as _computesdk_provider from '@computesdk/provider';
3
+
4
+ /**
5
+ * sandbox-cli Provider - Factory-based Implementation
6
+ *
7
+ * sandbox-cli runs each sandbox as a microVM with its own kernel, behind one
8
+ * HTTP API (v1) that is the same on a Mac, on a self-hosted Linux machine and
9
+ * in the cloud. This provider talks to that API directly with fetch, so it
10
+ * has no SDK dependency.
11
+ */
12
+ /**
13
+ * sandbox-cli provider configuration
14
+ */
15
+ interface SandboxCliConfig {
16
+ /** API endpoint, e.g. `https://sandbox.example.com` - falls back to SANDBOX_ENDPOINT */
17
+ endpoint?: string;
18
+ /** Bearer token - falls back to SANDBOX_TOKEN */
19
+ token?: string;
20
+ /** On a gateway, the organization to act in - falls back to SANDBOX_ORG */
21
+ org?: string;
22
+ /** Default sandbox lifetime in milliseconds; unset leaves the server's default */
23
+ timeout?: number;
24
+ }
25
+ /** A sandbox as the v1 API returns it (the fields this provider reads). */
26
+ interface ApiSandbox {
27
+ id: string;
28
+ name?: string;
29
+ state: 'pending' | 'running' | 'suspended' | 'terminated';
30
+ image: string;
31
+ cpus: number;
32
+ memory_mb: number;
33
+ created_at: string;
34
+ lifetime_secs?: number;
35
+ labels?: Record<string, string>;
36
+ }
37
+ /** The native instance: the API record plus a client bound to it. */
38
+ interface SandboxCliInstance {
39
+ client: ApiClient;
40
+ sandbox: ApiSandbox;
41
+ }
42
+ declare class ApiClient {
43
+ readonly base: string;
44
+ private readonly headers;
45
+ constructor(config: SandboxCliConfig);
46
+ request(method: string, path: string, init?: {
47
+ query?: Record<string, string>;
48
+ json?: unknown;
49
+ body?: BodyInit;
50
+ signal?: AbortSignal;
51
+ }): Promise<Response>;
52
+ json<T>(method: string, path: string, payload?: unknown): Promise<T>;
53
+ sbx(id: string): string;
54
+ }
55
+ /**
56
+ * Create a sandbox-cli provider instance using the factory pattern
57
+ */
58
+ declare const sandboxCli: (config: SandboxCliConfig & _computesdk_provider.ProcessRuntimeOptions) => _computesdk_provider.Provider<SandboxCliInstance, any, any, computesdk.Volume> & {
59
+ readonly sizes: computesdk.InstanceSizeManager;
60
+ };
61
+
62
+ export { type SandboxCliConfig, type SandboxCliInstance, sandboxCli };
package/dist/index.js ADDED
@@ -0,0 +1,322 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ sandboxCli: () => sandboxCli
24
+ });
25
+ module.exports = __toCommonJS(index_exports);
26
+ var import_provider = require("@computesdk/provider");
27
+ var ApiError = class extends Error {
28
+ constructor(status, code, message) {
29
+ super(`sandbox-cli: ${code} (${status}): ${message}`);
30
+ this.status = status;
31
+ this.code = code;
32
+ }
33
+ };
34
+ function env(name) {
35
+ return typeof process !== "undefined" ? process.env?.[name] : void 0;
36
+ }
37
+ function fromB64(s) {
38
+ return s ? Buffer.from(s, "base64").toString("utf8") : "";
39
+ }
40
+ function isLoopback(hostname) {
41
+ return hostname === "localhost" || hostname === "[::1]" || /^127(\.\d{1,3}){3}$/.test(hostname);
42
+ }
43
+ var ApiClient = class {
44
+ constructor(config) {
45
+ const endpoint = config.endpoint || env("SANDBOX_ENDPOINT");
46
+ const token = config.token || env("SANDBOX_TOKEN");
47
+ if (!endpoint) {
48
+ throw new Error(
49
+ `Missing sandbox-cli endpoint. Provide 'endpoint' in config or set SANDBOX_ENDPOINT (the URL of a sandboxd or a gateway, e.g. https://sandbox.example.com).`
50
+ );
51
+ }
52
+ if (!token) {
53
+ throw new Error(`Missing sandbox-cli token. Provide 'token' in config or set SANDBOX_TOKEN.`);
54
+ }
55
+ const u = new URL(endpoint);
56
+ if (u.protocol !== "https:" && u.protocol !== "http:") {
57
+ throw new Error(`sandbox-cli endpoint must be http(s)://host[:port], got ${endpoint}`);
58
+ }
59
+ if (u.protocol === "http:" && !isLoopback(u.hostname)) {
60
+ throw new Error(
61
+ `sandbox-cli endpoint ${endpoint} is plain http to a non-loopback host, which would send the token unencrypted. Use https://, or http:// only to 127.0.0.1, ::1 or localhost.`
62
+ );
63
+ }
64
+ this.base = endpoint.replace(/\/+$/, "");
65
+ this.headers = { Authorization: `Bearer ${token}` };
66
+ const org = config.org || env("SANDBOX_ORG");
67
+ if (org) this.headers["X-Sandbox-Org"] = org;
68
+ }
69
+ async request(method, path, init = {}) {
70
+ let url = this.base + path;
71
+ if (init.query) url += "?" + new URLSearchParams(init.query).toString();
72
+ const headers = { ...this.headers };
73
+ let body = init.body;
74
+ if (init.json !== void 0) {
75
+ headers["Content-Type"] = "application/json";
76
+ body = JSON.stringify(init.json);
77
+ } else if (body !== void 0) {
78
+ headers["Content-Type"] = "application/octet-stream";
79
+ }
80
+ const resp = await fetch(url, { method, headers, body, signal: init.signal });
81
+ if (!resp.ok) {
82
+ const text = await resp.text();
83
+ let code = "internal";
84
+ let message = text.trim();
85
+ try {
86
+ const e = JSON.parse(text).error;
87
+ code = e.code ?? code;
88
+ message = e.message ?? message;
89
+ } catch {
90
+ }
91
+ throw new ApiError(resp.status, code, message);
92
+ }
93
+ return resp;
94
+ }
95
+ async json(method, path, payload) {
96
+ const resp = await this.request(method, path, payload === void 0 ? {} : { json: payload });
97
+ const text = await resp.text();
98
+ return text ? JSON.parse(text) : void 0;
99
+ }
100
+ sbx(id) {
101
+ return "/v1/sandboxes/" + encodeURIComponent(id);
102
+ }
103
+ };
104
+ function pick(...vals) {
105
+ return vals.find((v) => typeof v === "number" && Number.isFinite(v));
106
+ }
107
+ async function run(instance, command, options) {
108
+ const { client, sandbox } = instance;
109
+ const startTime = Date.now();
110
+ const req = { argv: ["sh", "-c", command] };
111
+ if (options?.cwd) req.cwd = options.cwd;
112
+ if (options?.env && Object.keys(options.env).length > 0) req.env = options.env;
113
+ if (options?.background) {
114
+ await client.json("POST", client.sbx(sandbox.id) + "/processes", req);
115
+ return { stdout: "", stderr: "", exitCode: 0, durationMs: Date.now() - startTime };
116
+ }
117
+ if (options?.timeout) req.timeout_secs = Math.max(1, Math.ceil(options.timeout / 1e3));
118
+ const r = await client.json(
119
+ "POST",
120
+ client.sbx(sandbox.id) + "/run",
121
+ req
122
+ );
123
+ return {
124
+ stdout: fromB64(r.stdout),
125
+ stderr: fromB64(r.stderr),
126
+ exitCode: r.timed_out && r.exit_code === 0 ? -1 : r.exit_code,
127
+ durationMs: Date.now() - startTime
128
+ };
129
+ }
130
+ var KILL_GRACE_MS = 2e3;
131
+ async function stream(instance, command, options) {
132
+ const { client, sandbox } = instance;
133
+ const startTime = Date.now();
134
+ const req = { argv: ["sh", "-c", command] };
135
+ if (options.cwd) req.cwd = options.cwd;
136
+ if (options.env && Object.keys(options.env).length > 0) req.env = options.env;
137
+ const proc = await client.json("POST", client.sbx(sandbox.id) + "/processes", req);
138
+ const procPath = `${client.sbx(sandbox.id)}/processes/${proc.pid}`;
139
+ const kill = () => client.json("POST", procPath + "/signal", { signal: "KILL" }).catch(() => {
140
+ });
141
+ const abort = new AbortController();
142
+ let timedOut = false;
143
+ let grace;
144
+ const timer = options.timeout ? setTimeout(() => {
145
+ timedOut = true;
146
+ void kill();
147
+ grace = setTimeout(() => abort.abort(), KILL_GRACE_MS);
148
+ }, options.timeout) : void 0;
149
+ const decoders = { stdout: new TextDecoder(), stderr: new TextDecoder() };
150
+ const out = { stdout: "", stderr: "" };
151
+ const emit = (s, text) => {
152
+ if (!text) return;
153
+ out[s] += text;
154
+ (s === "stdout" ? options.onStdout : options.onStderr)?.(text);
155
+ };
156
+ let exitCode;
157
+ let reader;
158
+ try {
159
+ const resp = await client.request("GET", procPath + "/output", { signal: abort.signal });
160
+ if (!resp.body) throw new Error("sandbox-cli: output stream has no body");
161
+ reader = resp.body.getReader();
162
+ const dec = new TextDecoder();
163
+ let buf = "";
164
+ for (; ; ) {
165
+ const { done, value } = await reader.read();
166
+ if (done) break;
167
+ buf += dec.decode(value, { stream: true });
168
+ let nl;
169
+ while ((nl = buf.indexOf("\n")) >= 0) {
170
+ const line = buf.slice(0, nl).trim();
171
+ buf = buf.slice(nl + 1);
172
+ if (!line) continue;
173
+ const ev = JSON.parse(line);
174
+ if (typeof ev.exit_code === "number") {
175
+ exitCode = ev.exit_code;
176
+ } else if (ev.stream === "stdout" || ev.stream === "stderr") {
177
+ emit(ev.stream, decoders[ev.stream].decode(Buffer.from(ev.data ?? "", "base64"), { stream: true }));
178
+ }
179
+ }
180
+ }
181
+ } catch (error) {
182
+ if (!(timedOut && abort.signal.aborted)) {
183
+ if (exitCode === void 0) await kill();
184
+ throw error;
185
+ }
186
+ } finally {
187
+ clearTimeout(timer);
188
+ clearTimeout(grace);
189
+ reader?.cancel().catch(() => {
190
+ });
191
+ }
192
+ emit("stdout", decoders.stdout.decode());
193
+ emit("stderr", decoders.stderr.decode());
194
+ if (exitCode === void 0 || timedOut && exitCode === 0) exitCode = -1;
195
+ return { stdout: out.stdout, stderr: out.stderr, exitCode, durationMs: Date.now() - startTime };
196
+ }
197
+ var sandboxCli = (0, import_provider.defineProvider)({
198
+ name: "sandbox-cli",
199
+ methods: {
200
+ instances: {
201
+ create: async (config, options) => {
202
+ const client = new ApiClient(config);
203
+ const o = options || {};
204
+ const req = {};
205
+ const image = o.image || o.templateId;
206
+ if (image) req.image = image;
207
+ if (o.snapshotId) req.snapshot_id = o.snapshotId;
208
+ if (o.name) req.name = o.name;
209
+ if (o.envs && Object.keys(o.envs).length > 0) req.env = o.envs;
210
+ if (o.metadata && Object.keys(o.metadata).length > 0) {
211
+ req.labels = Object.fromEntries(Object.entries(o.metadata).map(([k, v]) => [k, String(v)]));
212
+ }
213
+ const cpus = pick(o.cpus, o.vcpus, o.cpu);
214
+ if (cpus !== void 0) req.cpus = cpus;
215
+ const memoryMb = pick(o.memoryMb, o.memory, o.memoryMiB, o.memMiB);
216
+ if (memoryMb !== void 0) req.memory_mb = memoryMb;
217
+ const diskMb = pick(o.diskMb, o.diskMiB);
218
+ if (diskMb !== void 0) req.disk_mb = diskMb;
219
+ const lifetimeMs = o.timeout ?? config.timeout;
220
+ if (lifetimeMs) req.lifetime_secs = Math.max(1, Math.ceil(lifetimeMs / 1e3));
221
+ try {
222
+ const sandbox = await client.json("POST", "/v1/sandboxes", req);
223
+ return { instance: { client, sandbox }, instanceId: sandbox.id };
224
+ } catch (error) {
225
+ if (error instanceof ApiError && error.status === 401) {
226
+ throw new Error(`sandbox-cli authentication failed. Check your SANDBOX_TOKEN.`);
227
+ }
228
+ throw new Error(
229
+ `Failed to create sandbox-cli sandbox: ${error instanceof Error ? error.message : String(error)}`
230
+ );
231
+ }
232
+ },
233
+ getById: async (config, sandboxId) => {
234
+ const client = new ApiClient(config);
235
+ try {
236
+ const sandbox = await client.json("GET", client.sbx(sandboxId));
237
+ if (sandbox.state === "terminated") return null;
238
+ return { instance: { client, sandbox }, instanceId: sandbox.id };
239
+ } catch (error) {
240
+ if (error instanceof ApiError && error.status === 404) return null;
241
+ throw error;
242
+ }
243
+ },
244
+ list: async (config) => {
245
+ const client = new ApiClient(config);
246
+ const { sandboxes } = await client.json("GET", "/v1/sandboxes");
247
+ return (sandboxes ?? []).filter((s) => s.state !== "terminated").map((sandbox) => ({ instance: { client, sandbox }, instanceId: sandbox.id }));
248
+ },
249
+ destroy: async (config, sandboxId) => {
250
+ const client = new ApiClient(config);
251
+ try {
252
+ await client.json("DELETE", client.sbx(sandboxId));
253
+ } catch (error) {
254
+ if (error instanceof ApiError && error.status === 404) return;
255
+ throw error;
256
+ }
257
+ },
258
+ runCommand: run,
259
+ streamCommand: stream,
260
+ getInfo: async (instance) => {
261
+ const { client, sandbox } = instance;
262
+ const s = await client.json("GET", client.sbx(sandbox.id));
263
+ return {
264
+ id: s.id,
265
+ provider: "sandbox-cli",
266
+ // A pending sandbox is still being created and refuses commands; its
267
+ // native state stays in metadata.
268
+ status: s.state === "running" ? "running" : "stopped",
269
+ createdAt: new Date(s.created_at),
270
+ timeout: (s.lifetime_secs ?? 0) * 1e3,
271
+ metadata: { state: s.state, image: s.image, cpus: s.cpus, memoryMb: s.memory_mb, name: s.name, labels: s.labels }
272
+ };
273
+ },
274
+ getUrl: async () => {
275
+ throw new Error(
276
+ "sandbox-cli does not expose sandbox ports as URLs. Use `sandbox-cli tunnel` or the API tunnel endpoint."
277
+ );
278
+ },
279
+ filesystem: {
280
+ readFile: async (instance, path) => {
281
+ const { client, sandbox } = instance;
282
+ const resp = await client.request("GET", client.sbx(sandbox.id) + "/files", { query: { path } });
283
+ return await resp.text();
284
+ },
285
+ writeFile: async (instance, path, content) => {
286
+ const { client, sandbox } = instance;
287
+ await client.request("PUT", client.sbx(sandbox.id) + "/files", { query: { path }, body: content });
288
+ },
289
+ mkdir: async (instance, path) => {
290
+ const r = await run(instance, `mkdir -p -- ${shellQuote(path)}`);
291
+ if (r.exitCode !== 0) throw new Error(`Failed to create directory ${path}: ${r.stderr}`);
292
+ },
293
+ readdir: async (instance, path) => {
294
+ const { client, sandbox } = instance;
295
+ const resp = await client.request("GET", client.sbx(sandbox.id) + "/dirs", { query: { path } });
296
+ const { entries } = await resp.json();
297
+ return (entries ?? []).map((e) => ({
298
+ name: e.name,
299
+ type: e.type === "dir" ? "directory" : "file",
300
+ size: e.size
301
+ }));
302
+ },
303
+ exists: async (instance, path) => {
304
+ const r = await run(instance, `test -e ${shellQuote(path)}`);
305
+ return r.exitCode === 0;
306
+ },
307
+ remove: async (instance, path) => {
308
+ const r = await run(instance, `rm -rf -- ${shellQuote(path)}`);
309
+ if (r.exitCode !== 0) throw new Error(`Failed to remove ${path}: ${r.stderr}`);
310
+ }
311
+ }
312
+ }
313
+ }
314
+ });
315
+ function shellQuote(s) {
316
+ return `'${s.replace(/'/g, `'\\''`)}'`;
317
+ }
318
+ // Annotate the CommonJS export names for ESM import in node:
319
+ 0 && (module.exports = {
320
+ sandboxCli
321
+ });
322
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * sandbox-cli Provider - Factory-based Implementation\n *\n * sandbox-cli runs each sandbox as a microVM with its own kernel, behind one\n * HTTP API (v1) that is the same on a Mac, on a self-hosted Linux machine and\n * in the cloud. This provider talks to that API directly with fetch, so it\n * has no SDK dependency.\n */\n\nimport { defineProvider } from '@computesdk/provider';\n\nimport type {\n CommandResult,\n InstanceInfo,\n CreateInstanceOptions,\n FileEntry,\n RunCommandOptions,\n} from '@computesdk/provider';\n\n/**\n * sandbox-cli provider configuration\n */\nexport interface SandboxCliConfig {\n /** API endpoint, e.g. `https://sandbox.example.com` - falls back to SANDBOX_ENDPOINT */\n endpoint?: string;\n /** Bearer token - falls back to SANDBOX_TOKEN */\n token?: string;\n /** On a gateway, the organization to act in - falls back to SANDBOX_ORG */\n org?: string;\n /** Default sandbox lifetime in milliseconds; unset leaves the server's default */\n timeout?: number;\n}\n\n/** A sandbox as the v1 API returns it (the fields this provider reads). */\ninterface ApiSandbox {\n id: string;\n name?: string;\n state: 'pending' | 'running' | 'suspended' | 'terminated';\n image: string;\n cpus: number;\n memory_mb: number;\n created_at: string;\n lifetime_secs?: number;\n labels?: Record<string, string>;\n}\n\n/** The native instance: the API record plus a client bound to it. */\nexport interface SandboxCliInstance {\n client: ApiClient;\n sandbox: ApiSandbox;\n}\n\nclass ApiError extends Error {\n constructor(readonly status: number, readonly code: string, message: string) {\n super(`sandbox-cli: ${code} (${status}): ${message}`);\n }\n}\n\nfunction env(name: string): string | undefined {\n return typeof process !== 'undefined' ? process.env?.[name] : undefined;\n}\n\nfunction fromB64(s: string | null | undefined): string {\n return s ? Buffer.from(s, 'base64').toString('utf8') : '';\n}\n\n/** `URL.hostname` of a loopback address: 127.0.0.0/8, [::1] or localhost. */\nfunction isLoopback(hostname: string): boolean {\n return hostname === 'localhost' || hostname === '[::1]' || /^127(\\.\\d{1,3}){3}$/.test(hostname);\n}\n\nclass ApiClient {\n readonly base: string;\n private readonly headers: Record<string, string>;\n\n constructor(config: SandboxCliConfig) {\n const endpoint = config.endpoint || env('SANDBOX_ENDPOINT');\n const token = config.token || env('SANDBOX_TOKEN');\n if (!endpoint) {\n throw new Error(\n `Missing sandbox-cli endpoint. Provide 'endpoint' in config or set SANDBOX_ENDPOINT ` +\n `(the URL of a sandboxd or a gateway, e.g. https://sandbox.example.com).`\n );\n }\n if (!token) {\n throw new Error(`Missing sandbox-cli token. Provide 'token' in config or set SANDBOX_TOKEN.`);\n }\n const u = new URL(endpoint);\n if (u.protocol !== 'https:' && u.protocol !== 'http:') {\n throw new Error(`sandbox-cli endpoint must be http(s)://host[:port], got ${endpoint}`);\n }\n // Every request carries the bearer token, so plain http is only for a\n // sandboxd on this machine.\n if (u.protocol === 'http:' && !isLoopback(u.hostname)) {\n throw new Error(\n `sandbox-cli endpoint ${endpoint} is plain http to a non-loopback host, which would send the token ` +\n `unencrypted. Use https://, or http:// only to 127.0.0.1, ::1 or localhost.`\n );\n }\n this.base = endpoint.replace(/\\/+$/, '');\n this.headers = { Authorization: `Bearer ${token}` };\n const org = config.org || env('SANDBOX_ORG');\n if (org) this.headers['X-Sandbox-Org'] = org;\n }\n\n async request(\n method: string,\n path: string,\n init: { query?: Record<string, string>; json?: unknown; body?: BodyInit; signal?: AbortSignal } = {}\n ): Promise<Response> {\n let url = this.base + path;\n if (init.query) url += '?' + new URLSearchParams(init.query).toString();\n const headers: Record<string, string> = { ...this.headers };\n let body = init.body;\n if (init.json !== undefined) {\n headers['Content-Type'] = 'application/json';\n body = JSON.stringify(init.json);\n } else if (body !== undefined) {\n headers['Content-Type'] = 'application/octet-stream';\n }\n const resp = await fetch(url, { method, headers, body, signal: init.signal });\n if (!resp.ok) {\n const text = await resp.text();\n let code = 'internal';\n let message = text.trim();\n try {\n const e = JSON.parse(text).error;\n code = e.code ?? code;\n message = e.message ?? message;\n } catch {\n // not a JSON error body\n }\n throw new ApiError(resp.status, code, message);\n }\n return resp;\n }\n\n async json<T>(method: string, path: string, payload?: unknown): Promise<T> {\n const resp = await this.request(method, path, payload === undefined ? {} : { json: payload });\n const text = await resp.text();\n return (text ? JSON.parse(text) : undefined) as T;\n }\n\n sbx(id: string): string {\n return '/v1/sandboxes/' + encodeURIComponent(id);\n }\n}\n\n/** First defined number among the generic resource option spellings. */\nfunction pick(...vals: Array<number | undefined>): number | undefined {\n return vals.find((v) => typeof v === 'number' && Number.isFinite(v));\n}\n\n/** The v1 `/run` call: `command` goes through `sh -c`, as ComputeSDK commands are shell strings. */\nasync function run(instance: SandboxCliInstance, command: string, options?: RunCommandOptions): Promise<CommandResult> {\n const { client, sandbox } = instance;\n const startTime = Date.now();\n const req: Record<string, unknown> = { argv: ['sh', '-c', command] };\n if (options?.cwd) req.cwd = options.cwd;\n if (options?.env && Object.keys(options.env).length > 0) req.env = options.env;\n\n if (options?.background) {\n // A background process keeps running after this returns; its output stays\n // readable through the API's process list.\n await client.json('POST', client.sbx(sandbox.id) + '/processes', req);\n return { stdout: '', stderr: '', exitCode: 0, durationMs: Date.now() - startTime };\n }\n\n if (options?.timeout) req.timeout_secs = Math.max(1, Math.ceil(options.timeout / 1000));\n const r = await client.json<{ exit_code: number; stdout: string; stderr: string; timed_out: boolean }>(\n 'POST',\n client.sbx(sandbox.id) + '/run',\n req\n );\n return {\n stdout: fromB64(r.stdout),\n stderr: fromB64(r.stderr),\n exitCode: r.timed_out && r.exit_code === 0 ? -1 : r.exit_code,\n durationMs: Date.now() - startTime,\n };\n}\n\n/** How long after a timeout's KILL the output stream may take to end. */\nconst KILL_GRACE_MS = 2_000;\n\n/**\n * Live output through the API's own process stream: start the process, then\n * read `GET …/processes/{pid}/output`, one JSON object per line, until the\n * `{\"exit_code\": N}` line.\n *\n * A timeout sends KILL, which ends the stream once it lands (the guest kills\n * the whole process group). The output request has a deadline of its own as\n * well, so a kill that fails cannot leave the caller waiting on a command\n * that is still running. Any other failure - the output request, a read, a\n * callback - kills the process before the error is reported, so nothing is\n * left running without a reader.\n */\nasync function stream(instance: SandboxCliInstance, command: string, options: RunCommandOptions): Promise<CommandResult> {\n const { client, sandbox } = instance;\n const startTime = Date.now();\n const req: Record<string, unknown> = { argv: ['sh', '-c', command] };\n if (options.cwd) req.cwd = options.cwd;\n if (options.env && Object.keys(options.env).length > 0) req.env = options.env;\n\n const proc = await client.json<{ pid: number }>('POST', client.sbx(sandbox.id) + '/processes', req);\n const procPath = `${client.sbx(sandbox.id)}/processes/${proc.pid}`;\n const kill = () => client.json('POST', procPath + '/signal', { signal: 'KILL' }).catch(() => {});\n\n const abort = new AbortController();\n let timedOut = false;\n let grace: ReturnType<typeof setTimeout> | undefined;\n const timer = options.timeout\n ? setTimeout(() => {\n timedOut = true;\n void kill();\n grace = setTimeout(() => abort.abort(), KILL_GRACE_MS);\n }, options.timeout)\n : undefined;\n\n // Output arrives in byte chunks that can split a character, so each stream\n // keeps one decoder across events and is flushed at the end.\n const decoders = { stdout: new TextDecoder(), stderr: new TextDecoder() };\n const out = { stdout: '', stderr: '' };\n const emit = (s: 'stdout' | 'stderr', text: string) => {\n if (!text) return;\n out[s] += text;\n (s === 'stdout' ? options.onStdout : options.onStderr)?.(text);\n };\n\n let exitCode: number | undefined;\n let reader: ReadableStreamDefaultReader<Uint8Array> | undefined;\n try {\n const resp = await client.request('GET', procPath + '/output', { signal: abort.signal });\n if (!resp.body) throw new Error('sandbox-cli: output stream has no body');\n reader = resp.body.getReader();\n const dec = new TextDecoder();\n let buf = '';\n for (;;) {\n const { done, value } = await reader.read();\n if (done) break;\n buf += dec.decode(value, { stream: true });\n let nl: number;\n while ((nl = buf.indexOf('\\n')) >= 0) {\n const line = buf.slice(0, nl).trim();\n buf = buf.slice(nl + 1);\n if (!line) continue;\n const ev = JSON.parse(line) as { stream?: 'stdout' | 'stderr'; data?: string; exit_code?: number };\n if (typeof ev.exit_code === 'number') {\n exitCode = ev.exit_code;\n } else if (ev.stream === 'stdout' || ev.stream === 'stderr') {\n emit(ev.stream, decoders[ev.stream].decode(Buffer.from(ev.data ?? '', 'base64'), { stream: true }));\n }\n }\n }\n } catch (error) {\n if (!(timedOut && abort.signal.aborted)) {\n if (exitCode === undefined) await kill();\n throw error;\n }\n } finally {\n clearTimeout(timer);\n clearTimeout(grace);\n reader?.cancel().catch(() => {});\n }\n emit('stdout', decoders.stdout.decode());\n emit('stderr', decoders.stderr.decode());\n\n if (exitCode === undefined || (timedOut && exitCode === 0)) exitCode = -1;\n return { stdout: out.stdout, stderr: out.stderr, exitCode, durationMs: Date.now() - startTime };\n}\n\n/**\n * Create a sandbox-cli provider instance using the factory pattern\n */\nexport const sandboxCli = defineProvider<SandboxCliInstance, SandboxCliConfig>({\n name: 'sandbox-cli',\n methods: {\n instances: {\n create: async (config: SandboxCliConfig, options?: CreateInstanceOptions) => {\n const client = new ApiClient(config);\n const o = options || {};\n\n // POST /v1/sandboxes returns once the VM is booted and running, so the\n // sandbox is usable as soon as this resolves.\n const req: Record<string, unknown> = {};\n const image = o.image || o.templateId;\n if (image) req.image = image;\n if (o.snapshotId) req.snapshot_id = o.snapshotId;\n if (o.name) req.name = o.name;\n if (o.envs && Object.keys(o.envs).length > 0) req.env = o.envs;\n if (o.metadata && Object.keys(o.metadata).length > 0) {\n req.labels = Object.fromEntries(Object.entries(o.metadata).map(([k, v]) => [k, String(v)]));\n }\n const cpus = pick(o.cpus, o.vcpus, o.cpu);\n if (cpus !== undefined) req.cpus = cpus;\n const memoryMb = pick(o.memoryMb, o.memory, o.memoryMiB, o.memMiB);\n if (memoryMb !== undefined) req.memory_mb = memoryMb;\n const diskMb = pick(o.diskMb, o.diskMiB);\n if (diskMb !== undefined) req.disk_mb = diskMb;\n const lifetimeMs = o.timeout ?? config.timeout;\n if (lifetimeMs) req.lifetime_secs = Math.max(1, Math.ceil(lifetimeMs / 1000));\n\n try {\n const sandbox = await client.json<ApiSandbox>('POST', '/v1/sandboxes', req);\n return { instance: { client, sandbox }, instanceId: sandbox.id };\n } catch (error) {\n if (error instanceof ApiError && error.status === 401) {\n throw new Error(`sandbox-cli authentication failed. Check your SANDBOX_TOKEN.`);\n }\n throw new Error(\n `Failed to create sandbox-cli sandbox: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n },\n\n getById: async (config: SandboxCliConfig, sandboxId: string) => {\n const client = new ApiClient(config);\n try {\n const sandbox = await client.json<ApiSandbox>('GET', client.sbx(sandboxId));\n if (sandbox.state === 'terminated') return null;\n return { instance: { client, sandbox }, instanceId: sandbox.id };\n } catch (error) {\n if (error instanceof ApiError && error.status === 404) return null;\n throw error;\n }\n },\n\n list: async (config: SandboxCliConfig) => {\n const client = new ApiClient(config);\n const { sandboxes } = await client.json<{ sandboxes: ApiSandbox[] }>('GET', '/v1/sandboxes');\n return (sandboxes ?? [])\n .filter((s) => s.state !== 'terminated')\n .map((sandbox) => ({ instance: { client, sandbox }, instanceId: sandbox.id }));\n },\n\n destroy: async (config: SandboxCliConfig, sandboxId: string) => {\n const client = new ApiClient(config);\n try {\n await client.json('DELETE', client.sbx(sandboxId));\n } catch (error) {\n // Idempotent: the sandbox may already be gone.\n if (error instanceof ApiError && error.status === 404) return;\n throw error;\n }\n },\n\n runCommand: run,\n\n streamCommand: stream,\n\n getInfo: async (instance: SandboxCliInstance): Promise<InstanceInfo> => {\n const { client, sandbox } = instance;\n const s = await client.json<ApiSandbox>('GET', client.sbx(sandbox.id));\n return {\n id: s.id,\n provider: 'sandbox-cli',\n // A pending sandbox is still being created and refuses commands; its\n // native state stays in metadata.\n status: s.state === 'running' ? 'running' : 'stopped',\n createdAt: new Date(s.created_at),\n timeout: (s.lifetime_secs ?? 0) * 1000,\n metadata: { state: s.state, image: s.image, cpus: s.cpus, memoryMb: s.memory_mb, name: s.name, labels: s.labels },\n };\n },\n\n getUrl: async (): Promise<string> => {\n // A port is reached through the API's tunnel (an upgraded connection to\n // the guest's own loopback), never through a public URL.\n throw new Error(\n 'sandbox-cli does not expose sandbox ports as URLs. Use `sandbox-cli tunnel` or the API tunnel endpoint.'\n );\n },\n\n filesystem: {\n readFile: async (instance: SandboxCliInstance, path: string): Promise<string> => {\n const { client, sandbox } = instance;\n const resp = await client.request('GET', client.sbx(sandbox.id) + '/files', { query: { path } });\n return await resp.text();\n },\n\n writeFile: async (instance: SandboxCliInstance, path: string, content: string): Promise<void> => {\n // The API creates parent directories.\n const { client, sandbox } = instance;\n await client.request('PUT', client.sbx(sandbox.id) + '/files', { query: { path }, body: content });\n },\n\n mkdir: async (instance: SandboxCliInstance, path: string): Promise<void> => {\n const r = await run(instance, `mkdir -p -- ${shellQuote(path)}`);\n if (r.exitCode !== 0) throw new Error(`Failed to create directory ${path}: ${r.stderr}`);\n },\n\n readdir: async (instance: SandboxCliInstance, path: string): Promise<FileEntry[]> => {\n const { client, sandbox } = instance;\n const resp = await client.request('GET', client.sbx(sandbox.id) + '/dirs', { query: { path } });\n const { entries } = (await resp.json()) as { entries: Array<{ name: string; type: 'file' | 'dir'; size: number }> };\n return (entries ?? []).map((e) => ({\n name: e.name,\n type: e.type === 'dir' ? ('directory' as const) : ('file' as const),\n size: e.size,\n }));\n },\n\n exists: async (instance: SandboxCliInstance, path: string): Promise<boolean> => {\n const r = await run(instance, `test -e ${shellQuote(path)}`);\n return r.exitCode === 0;\n },\n\n remove: async (instance: SandboxCliInstance, path: string): Promise<void> => {\n // The API's DELETE removes only a file or an empty directory;\n // ComputeSDK's remove is recursive.\n const r = await run(instance, `rm -rf -- ${shellQuote(path)}`);\n if (r.exitCode !== 0) throw new Error(`Failed to remove ${path}: ${r.stderr}`);\n },\n },\n },\n },\n});\n\n/** Single-quotes a value for `sh -c`, safe for any byte sequence. */\nfunction shellQuote(s: string): string {\n return `'${s.replace(/'/g, `'\\\\''`)}'`;\n}\n"],"mappings":"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","names":[]}
package/dist/index.mjs ADDED
@@ -0,0 +1,297 @@
1
+ // src/index.ts
2
+ import { defineProvider } from "@computesdk/provider";
3
+ var ApiError = class extends Error {
4
+ constructor(status, code, message) {
5
+ super(`sandbox-cli: ${code} (${status}): ${message}`);
6
+ this.status = status;
7
+ this.code = code;
8
+ }
9
+ };
10
+ function env(name) {
11
+ return typeof process !== "undefined" ? process.env?.[name] : void 0;
12
+ }
13
+ function fromB64(s) {
14
+ return s ? Buffer.from(s, "base64").toString("utf8") : "";
15
+ }
16
+ function isLoopback(hostname) {
17
+ return hostname === "localhost" || hostname === "[::1]" || /^127(\.\d{1,3}){3}$/.test(hostname);
18
+ }
19
+ var ApiClient = class {
20
+ constructor(config) {
21
+ const endpoint = config.endpoint || env("SANDBOX_ENDPOINT");
22
+ const token = config.token || env("SANDBOX_TOKEN");
23
+ if (!endpoint) {
24
+ throw new Error(
25
+ `Missing sandbox-cli endpoint. Provide 'endpoint' in config or set SANDBOX_ENDPOINT (the URL of a sandboxd or a gateway, e.g. https://sandbox.example.com).`
26
+ );
27
+ }
28
+ if (!token) {
29
+ throw new Error(`Missing sandbox-cli token. Provide 'token' in config or set SANDBOX_TOKEN.`);
30
+ }
31
+ const u = new URL(endpoint);
32
+ if (u.protocol !== "https:" && u.protocol !== "http:") {
33
+ throw new Error(`sandbox-cli endpoint must be http(s)://host[:port], got ${endpoint}`);
34
+ }
35
+ if (u.protocol === "http:" && !isLoopback(u.hostname)) {
36
+ throw new Error(
37
+ `sandbox-cli endpoint ${endpoint} is plain http to a non-loopback host, which would send the token unencrypted. Use https://, or http:// only to 127.0.0.1, ::1 or localhost.`
38
+ );
39
+ }
40
+ this.base = endpoint.replace(/\/+$/, "");
41
+ this.headers = { Authorization: `Bearer ${token}` };
42
+ const org = config.org || env("SANDBOX_ORG");
43
+ if (org) this.headers["X-Sandbox-Org"] = org;
44
+ }
45
+ async request(method, path, init = {}) {
46
+ let url = this.base + path;
47
+ if (init.query) url += "?" + new URLSearchParams(init.query).toString();
48
+ const headers = { ...this.headers };
49
+ let body = init.body;
50
+ if (init.json !== void 0) {
51
+ headers["Content-Type"] = "application/json";
52
+ body = JSON.stringify(init.json);
53
+ } else if (body !== void 0) {
54
+ headers["Content-Type"] = "application/octet-stream";
55
+ }
56
+ const resp = await fetch(url, { method, headers, body, signal: init.signal });
57
+ if (!resp.ok) {
58
+ const text = await resp.text();
59
+ let code = "internal";
60
+ let message = text.trim();
61
+ try {
62
+ const e = JSON.parse(text).error;
63
+ code = e.code ?? code;
64
+ message = e.message ?? message;
65
+ } catch {
66
+ }
67
+ throw new ApiError(resp.status, code, message);
68
+ }
69
+ return resp;
70
+ }
71
+ async json(method, path, payload) {
72
+ const resp = await this.request(method, path, payload === void 0 ? {} : { json: payload });
73
+ const text = await resp.text();
74
+ return text ? JSON.parse(text) : void 0;
75
+ }
76
+ sbx(id) {
77
+ return "/v1/sandboxes/" + encodeURIComponent(id);
78
+ }
79
+ };
80
+ function pick(...vals) {
81
+ return vals.find((v) => typeof v === "number" && Number.isFinite(v));
82
+ }
83
+ async function run(instance, command, options) {
84
+ const { client, sandbox } = instance;
85
+ const startTime = Date.now();
86
+ const req = { argv: ["sh", "-c", command] };
87
+ if (options?.cwd) req.cwd = options.cwd;
88
+ if (options?.env && Object.keys(options.env).length > 0) req.env = options.env;
89
+ if (options?.background) {
90
+ await client.json("POST", client.sbx(sandbox.id) + "/processes", req);
91
+ return { stdout: "", stderr: "", exitCode: 0, durationMs: Date.now() - startTime };
92
+ }
93
+ if (options?.timeout) req.timeout_secs = Math.max(1, Math.ceil(options.timeout / 1e3));
94
+ const r = await client.json(
95
+ "POST",
96
+ client.sbx(sandbox.id) + "/run",
97
+ req
98
+ );
99
+ return {
100
+ stdout: fromB64(r.stdout),
101
+ stderr: fromB64(r.stderr),
102
+ exitCode: r.timed_out && r.exit_code === 0 ? -1 : r.exit_code,
103
+ durationMs: Date.now() - startTime
104
+ };
105
+ }
106
+ var KILL_GRACE_MS = 2e3;
107
+ async function stream(instance, command, options) {
108
+ const { client, sandbox } = instance;
109
+ const startTime = Date.now();
110
+ const req = { argv: ["sh", "-c", command] };
111
+ if (options.cwd) req.cwd = options.cwd;
112
+ if (options.env && Object.keys(options.env).length > 0) req.env = options.env;
113
+ const proc = await client.json("POST", client.sbx(sandbox.id) + "/processes", req);
114
+ const procPath = `${client.sbx(sandbox.id)}/processes/${proc.pid}`;
115
+ const kill = () => client.json("POST", procPath + "/signal", { signal: "KILL" }).catch(() => {
116
+ });
117
+ const abort = new AbortController();
118
+ let timedOut = false;
119
+ let grace;
120
+ const timer = options.timeout ? setTimeout(() => {
121
+ timedOut = true;
122
+ void kill();
123
+ grace = setTimeout(() => abort.abort(), KILL_GRACE_MS);
124
+ }, options.timeout) : void 0;
125
+ const decoders = { stdout: new TextDecoder(), stderr: new TextDecoder() };
126
+ const out = { stdout: "", stderr: "" };
127
+ const emit = (s, text) => {
128
+ if (!text) return;
129
+ out[s] += text;
130
+ (s === "stdout" ? options.onStdout : options.onStderr)?.(text);
131
+ };
132
+ let exitCode;
133
+ let reader;
134
+ try {
135
+ const resp = await client.request("GET", procPath + "/output", { signal: abort.signal });
136
+ if (!resp.body) throw new Error("sandbox-cli: output stream has no body");
137
+ reader = resp.body.getReader();
138
+ const dec = new TextDecoder();
139
+ let buf = "";
140
+ for (; ; ) {
141
+ const { done, value } = await reader.read();
142
+ if (done) break;
143
+ buf += dec.decode(value, { stream: true });
144
+ let nl;
145
+ while ((nl = buf.indexOf("\n")) >= 0) {
146
+ const line = buf.slice(0, nl).trim();
147
+ buf = buf.slice(nl + 1);
148
+ if (!line) continue;
149
+ const ev = JSON.parse(line);
150
+ if (typeof ev.exit_code === "number") {
151
+ exitCode = ev.exit_code;
152
+ } else if (ev.stream === "stdout" || ev.stream === "stderr") {
153
+ emit(ev.stream, decoders[ev.stream].decode(Buffer.from(ev.data ?? "", "base64"), { stream: true }));
154
+ }
155
+ }
156
+ }
157
+ } catch (error) {
158
+ if (!(timedOut && abort.signal.aborted)) {
159
+ if (exitCode === void 0) await kill();
160
+ throw error;
161
+ }
162
+ } finally {
163
+ clearTimeout(timer);
164
+ clearTimeout(grace);
165
+ reader?.cancel().catch(() => {
166
+ });
167
+ }
168
+ emit("stdout", decoders.stdout.decode());
169
+ emit("stderr", decoders.stderr.decode());
170
+ if (exitCode === void 0 || timedOut && exitCode === 0) exitCode = -1;
171
+ return { stdout: out.stdout, stderr: out.stderr, exitCode, durationMs: Date.now() - startTime };
172
+ }
173
+ var sandboxCli = defineProvider({
174
+ name: "sandbox-cli",
175
+ methods: {
176
+ instances: {
177
+ create: async (config, options) => {
178
+ const client = new ApiClient(config);
179
+ const o = options || {};
180
+ const req = {};
181
+ const image = o.image || o.templateId;
182
+ if (image) req.image = image;
183
+ if (o.snapshotId) req.snapshot_id = o.snapshotId;
184
+ if (o.name) req.name = o.name;
185
+ if (o.envs && Object.keys(o.envs).length > 0) req.env = o.envs;
186
+ if (o.metadata && Object.keys(o.metadata).length > 0) {
187
+ req.labels = Object.fromEntries(Object.entries(o.metadata).map(([k, v]) => [k, String(v)]));
188
+ }
189
+ const cpus = pick(o.cpus, o.vcpus, o.cpu);
190
+ if (cpus !== void 0) req.cpus = cpus;
191
+ const memoryMb = pick(o.memoryMb, o.memory, o.memoryMiB, o.memMiB);
192
+ if (memoryMb !== void 0) req.memory_mb = memoryMb;
193
+ const diskMb = pick(o.diskMb, o.diskMiB);
194
+ if (diskMb !== void 0) req.disk_mb = diskMb;
195
+ const lifetimeMs = o.timeout ?? config.timeout;
196
+ if (lifetimeMs) req.lifetime_secs = Math.max(1, Math.ceil(lifetimeMs / 1e3));
197
+ try {
198
+ const sandbox = await client.json("POST", "/v1/sandboxes", req);
199
+ return { instance: { client, sandbox }, instanceId: sandbox.id };
200
+ } catch (error) {
201
+ if (error instanceof ApiError && error.status === 401) {
202
+ throw new Error(`sandbox-cli authentication failed. Check your SANDBOX_TOKEN.`);
203
+ }
204
+ throw new Error(
205
+ `Failed to create sandbox-cli sandbox: ${error instanceof Error ? error.message : String(error)}`
206
+ );
207
+ }
208
+ },
209
+ getById: async (config, sandboxId) => {
210
+ const client = new ApiClient(config);
211
+ try {
212
+ const sandbox = await client.json("GET", client.sbx(sandboxId));
213
+ if (sandbox.state === "terminated") return null;
214
+ return { instance: { client, sandbox }, instanceId: sandbox.id };
215
+ } catch (error) {
216
+ if (error instanceof ApiError && error.status === 404) return null;
217
+ throw error;
218
+ }
219
+ },
220
+ list: async (config) => {
221
+ const client = new ApiClient(config);
222
+ const { sandboxes } = await client.json("GET", "/v1/sandboxes");
223
+ return (sandboxes ?? []).filter((s) => s.state !== "terminated").map((sandbox) => ({ instance: { client, sandbox }, instanceId: sandbox.id }));
224
+ },
225
+ destroy: async (config, sandboxId) => {
226
+ const client = new ApiClient(config);
227
+ try {
228
+ await client.json("DELETE", client.sbx(sandboxId));
229
+ } catch (error) {
230
+ if (error instanceof ApiError && error.status === 404) return;
231
+ throw error;
232
+ }
233
+ },
234
+ runCommand: run,
235
+ streamCommand: stream,
236
+ getInfo: async (instance) => {
237
+ const { client, sandbox } = instance;
238
+ const s = await client.json("GET", client.sbx(sandbox.id));
239
+ return {
240
+ id: s.id,
241
+ provider: "sandbox-cli",
242
+ // A pending sandbox is still being created and refuses commands; its
243
+ // native state stays in metadata.
244
+ status: s.state === "running" ? "running" : "stopped",
245
+ createdAt: new Date(s.created_at),
246
+ timeout: (s.lifetime_secs ?? 0) * 1e3,
247
+ metadata: { state: s.state, image: s.image, cpus: s.cpus, memoryMb: s.memory_mb, name: s.name, labels: s.labels }
248
+ };
249
+ },
250
+ getUrl: async () => {
251
+ throw new Error(
252
+ "sandbox-cli does not expose sandbox ports as URLs. Use `sandbox-cli tunnel` or the API tunnel endpoint."
253
+ );
254
+ },
255
+ filesystem: {
256
+ readFile: async (instance, path) => {
257
+ const { client, sandbox } = instance;
258
+ const resp = await client.request("GET", client.sbx(sandbox.id) + "/files", { query: { path } });
259
+ return await resp.text();
260
+ },
261
+ writeFile: async (instance, path, content) => {
262
+ const { client, sandbox } = instance;
263
+ await client.request("PUT", client.sbx(sandbox.id) + "/files", { query: { path }, body: content });
264
+ },
265
+ mkdir: async (instance, path) => {
266
+ const r = await run(instance, `mkdir -p -- ${shellQuote(path)}`);
267
+ if (r.exitCode !== 0) throw new Error(`Failed to create directory ${path}: ${r.stderr}`);
268
+ },
269
+ readdir: async (instance, path) => {
270
+ const { client, sandbox } = instance;
271
+ const resp = await client.request("GET", client.sbx(sandbox.id) + "/dirs", { query: { path } });
272
+ const { entries } = await resp.json();
273
+ return (entries ?? []).map((e) => ({
274
+ name: e.name,
275
+ type: e.type === "dir" ? "directory" : "file",
276
+ size: e.size
277
+ }));
278
+ },
279
+ exists: async (instance, path) => {
280
+ const r = await run(instance, `test -e ${shellQuote(path)}`);
281
+ return r.exitCode === 0;
282
+ },
283
+ remove: async (instance, path) => {
284
+ const r = await run(instance, `rm -rf -- ${shellQuote(path)}`);
285
+ if (r.exitCode !== 0) throw new Error(`Failed to remove ${path}: ${r.stderr}`);
286
+ }
287
+ }
288
+ }
289
+ }
290
+ });
291
+ function shellQuote(s) {
292
+ return `'${s.replace(/'/g, `'\\''`)}'`;
293
+ }
294
+ export {
295
+ sandboxCli
296
+ };
297
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * sandbox-cli Provider - Factory-based Implementation\n *\n * sandbox-cli runs each sandbox as a microVM with its own kernel, behind one\n * HTTP API (v1) that is the same on a Mac, on a self-hosted Linux machine and\n * in the cloud. This provider talks to that API directly with fetch, so it\n * has no SDK dependency.\n */\n\nimport { defineProvider } from '@computesdk/provider';\n\nimport type {\n CommandResult,\n InstanceInfo,\n CreateInstanceOptions,\n FileEntry,\n RunCommandOptions,\n} from '@computesdk/provider';\n\n/**\n * sandbox-cli provider configuration\n */\nexport interface SandboxCliConfig {\n /** API endpoint, e.g. `https://sandbox.example.com` - falls back to SANDBOX_ENDPOINT */\n endpoint?: string;\n /** Bearer token - falls back to SANDBOX_TOKEN */\n token?: string;\n /** On a gateway, the organization to act in - falls back to SANDBOX_ORG */\n org?: string;\n /** Default sandbox lifetime in milliseconds; unset leaves the server's default */\n timeout?: number;\n}\n\n/** A sandbox as the v1 API returns it (the fields this provider reads). */\ninterface ApiSandbox {\n id: string;\n name?: string;\n state: 'pending' | 'running' | 'suspended' | 'terminated';\n image: string;\n cpus: number;\n memory_mb: number;\n created_at: string;\n lifetime_secs?: number;\n labels?: Record<string, string>;\n}\n\n/** The native instance: the API record plus a client bound to it. */\nexport interface SandboxCliInstance {\n client: ApiClient;\n sandbox: ApiSandbox;\n}\n\nclass ApiError extends Error {\n constructor(readonly status: number, readonly code: string, message: string) {\n super(`sandbox-cli: ${code} (${status}): ${message}`);\n }\n}\n\nfunction env(name: string): string | undefined {\n return typeof process !== 'undefined' ? process.env?.[name] : undefined;\n}\n\nfunction fromB64(s: string | null | undefined): string {\n return s ? Buffer.from(s, 'base64').toString('utf8') : '';\n}\n\n/** `URL.hostname` of a loopback address: 127.0.0.0/8, [::1] or localhost. */\nfunction isLoopback(hostname: string): boolean {\n return hostname === 'localhost' || hostname === '[::1]' || /^127(\\.\\d{1,3}){3}$/.test(hostname);\n}\n\nclass ApiClient {\n readonly base: string;\n private readonly headers: Record<string, string>;\n\n constructor(config: SandboxCliConfig) {\n const endpoint = config.endpoint || env('SANDBOX_ENDPOINT');\n const token = config.token || env('SANDBOX_TOKEN');\n if (!endpoint) {\n throw new Error(\n `Missing sandbox-cli endpoint. Provide 'endpoint' in config or set SANDBOX_ENDPOINT ` +\n `(the URL of a sandboxd or a gateway, e.g. https://sandbox.example.com).`\n );\n }\n if (!token) {\n throw new Error(`Missing sandbox-cli token. Provide 'token' in config or set SANDBOX_TOKEN.`);\n }\n const u = new URL(endpoint);\n if (u.protocol !== 'https:' && u.protocol !== 'http:') {\n throw new Error(`sandbox-cli endpoint must be http(s)://host[:port], got ${endpoint}`);\n }\n // Every request carries the bearer token, so plain http is only for a\n // sandboxd on this machine.\n if (u.protocol === 'http:' && !isLoopback(u.hostname)) {\n throw new Error(\n `sandbox-cli endpoint ${endpoint} is plain http to a non-loopback host, which would send the token ` +\n `unencrypted. Use https://, or http:// only to 127.0.0.1, ::1 or localhost.`\n );\n }\n this.base = endpoint.replace(/\\/+$/, '');\n this.headers = { Authorization: `Bearer ${token}` };\n const org = config.org || env('SANDBOX_ORG');\n if (org) this.headers['X-Sandbox-Org'] = org;\n }\n\n async request(\n method: string,\n path: string,\n init: { query?: Record<string, string>; json?: unknown; body?: BodyInit; signal?: AbortSignal } = {}\n ): Promise<Response> {\n let url = this.base + path;\n if (init.query) url += '?' + new URLSearchParams(init.query).toString();\n const headers: Record<string, string> = { ...this.headers };\n let body = init.body;\n if (init.json !== undefined) {\n headers['Content-Type'] = 'application/json';\n body = JSON.stringify(init.json);\n } else if (body !== undefined) {\n headers['Content-Type'] = 'application/octet-stream';\n }\n const resp = await fetch(url, { method, headers, body, signal: init.signal });\n if (!resp.ok) {\n const text = await resp.text();\n let code = 'internal';\n let message = text.trim();\n try {\n const e = JSON.parse(text).error;\n code = e.code ?? code;\n message = e.message ?? message;\n } catch {\n // not a JSON error body\n }\n throw new ApiError(resp.status, code, message);\n }\n return resp;\n }\n\n async json<T>(method: string, path: string, payload?: unknown): Promise<T> {\n const resp = await this.request(method, path, payload === undefined ? {} : { json: payload });\n const text = await resp.text();\n return (text ? JSON.parse(text) : undefined) as T;\n }\n\n sbx(id: string): string {\n return '/v1/sandboxes/' + encodeURIComponent(id);\n }\n}\n\n/** First defined number among the generic resource option spellings. */\nfunction pick(...vals: Array<number | undefined>): number | undefined {\n return vals.find((v) => typeof v === 'number' && Number.isFinite(v));\n}\n\n/** The v1 `/run` call: `command` goes through `sh -c`, as ComputeSDK commands are shell strings. */\nasync function run(instance: SandboxCliInstance, command: string, options?: RunCommandOptions): Promise<CommandResult> {\n const { client, sandbox } = instance;\n const startTime = Date.now();\n const req: Record<string, unknown> = { argv: ['sh', '-c', command] };\n if (options?.cwd) req.cwd = options.cwd;\n if (options?.env && Object.keys(options.env).length > 0) req.env = options.env;\n\n if (options?.background) {\n // A background process keeps running after this returns; its output stays\n // readable through the API's process list.\n await client.json('POST', client.sbx(sandbox.id) + '/processes', req);\n return { stdout: '', stderr: '', exitCode: 0, durationMs: Date.now() - startTime };\n }\n\n if (options?.timeout) req.timeout_secs = Math.max(1, Math.ceil(options.timeout / 1000));\n const r = await client.json<{ exit_code: number; stdout: string; stderr: string; timed_out: boolean }>(\n 'POST',\n client.sbx(sandbox.id) + '/run',\n req\n );\n return {\n stdout: fromB64(r.stdout),\n stderr: fromB64(r.stderr),\n exitCode: r.timed_out && r.exit_code === 0 ? -1 : r.exit_code,\n durationMs: Date.now() - startTime,\n };\n}\n\n/** How long after a timeout's KILL the output stream may take to end. */\nconst KILL_GRACE_MS = 2_000;\n\n/**\n * Live output through the API's own process stream: start the process, then\n * read `GET …/processes/{pid}/output`, one JSON object per line, until the\n * `{\"exit_code\": N}` line.\n *\n * A timeout sends KILL, which ends the stream once it lands (the guest kills\n * the whole process group). The output request has a deadline of its own as\n * well, so a kill that fails cannot leave the caller waiting on a command\n * that is still running. Any other failure - the output request, a read, a\n * callback - kills the process before the error is reported, so nothing is\n * left running without a reader.\n */\nasync function stream(instance: SandboxCliInstance, command: string, options: RunCommandOptions): Promise<CommandResult> {\n const { client, sandbox } = instance;\n const startTime = Date.now();\n const req: Record<string, unknown> = { argv: ['sh', '-c', command] };\n if (options.cwd) req.cwd = options.cwd;\n if (options.env && Object.keys(options.env).length > 0) req.env = options.env;\n\n const proc = await client.json<{ pid: number }>('POST', client.sbx(sandbox.id) + '/processes', req);\n const procPath = `${client.sbx(sandbox.id)}/processes/${proc.pid}`;\n const kill = () => client.json('POST', procPath + '/signal', { signal: 'KILL' }).catch(() => {});\n\n const abort = new AbortController();\n let timedOut = false;\n let grace: ReturnType<typeof setTimeout> | undefined;\n const timer = options.timeout\n ? setTimeout(() => {\n timedOut = true;\n void kill();\n grace = setTimeout(() => abort.abort(), KILL_GRACE_MS);\n }, options.timeout)\n : undefined;\n\n // Output arrives in byte chunks that can split a character, so each stream\n // keeps one decoder across events and is flushed at the end.\n const decoders = { stdout: new TextDecoder(), stderr: new TextDecoder() };\n const out = { stdout: '', stderr: '' };\n const emit = (s: 'stdout' | 'stderr', text: string) => {\n if (!text) return;\n out[s] += text;\n (s === 'stdout' ? options.onStdout : options.onStderr)?.(text);\n };\n\n let exitCode: number | undefined;\n let reader: ReadableStreamDefaultReader<Uint8Array> | undefined;\n try {\n const resp = await client.request('GET', procPath + '/output', { signal: abort.signal });\n if (!resp.body) throw new Error('sandbox-cli: output stream has no body');\n reader = resp.body.getReader();\n const dec = new TextDecoder();\n let buf = '';\n for (;;) {\n const { done, value } = await reader.read();\n if (done) break;\n buf += dec.decode(value, { stream: true });\n let nl: number;\n while ((nl = buf.indexOf('\\n')) >= 0) {\n const line = buf.slice(0, nl).trim();\n buf = buf.slice(nl + 1);\n if (!line) continue;\n const ev = JSON.parse(line) as { stream?: 'stdout' | 'stderr'; data?: string; exit_code?: number };\n if (typeof ev.exit_code === 'number') {\n exitCode = ev.exit_code;\n } else if (ev.stream === 'stdout' || ev.stream === 'stderr') {\n emit(ev.stream, decoders[ev.stream].decode(Buffer.from(ev.data ?? '', 'base64'), { stream: true }));\n }\n }\n }\n } catch (error) {\n if (!(timedOut && abort.signal.aborted)) {\n if (exitCode === undefined) await kill();\n throw error;\n }\n } finally {\n clearTimeout(timer);\n clearTimeout(grace);\n reader?.cancel().catch(() => {});\n }\n emit('stdout', decoders.stdout.decode());\n emit('stderr', decoders.stderr.decode());\n\n if (exitCode === undefined || (timedOut && exitCode === 0)) exitCode = -1;\n return { stdout: out.stdout, stderr: out.stderr, exitCode, durationMs: Date.now() - startTime };\n}\n\n/**\n * Create a sandbox-cli provider instance using the factory pattern\n */\nexport const sandboxCli = defineProvider<SandboxCliInstance, SandboxCliConfig>({\n name: 'sandbox-cli',\n methods: {\n instances: {\n create: async (config: SandboxCliConfig, options?: CreateInstanceOptions) => {\n const client = new ApiClient(config);\n const o = options || {};\n\n // POST /v1/sandboxes returns once the VM is booted and running, so the\n // sandbox is usable as soon as this resolves.\n const req: Record<string, unknown> = {};\n const image = o.image || o.templateId;\n if (image) req.image = image;\n if (o.snapshotId) req.snapshot_id = o.snapshotId;\n if (o.name) req.name = o.name;\n if (o.envs && Object.keys(o.envs).length > 0) req.env = o.envs;\n if (o.metadata && Object.keys(o.metadata).length > 0) {\n req.labels = Object.fromEntries(Object.entries(o.metadata).map(([k, v]) => [k, String(v)]));\n }\n const cpus = pick(o.cpus, o.vcpus, o.cpu);\n if (cpus !== undefined) req.cpus = cpus;\n const memoryMb = pick(o.memoryMb, o.memory, o.memoryMiB, o.memMiB);\n if (memoryMb !== undefined) req.memory_mb = memoryMb;\n const diskMb = pick(o.diskMb, o.diskMiB);\n if (diskMb !== undefined) req.disk_mb = diskMb;\n const lifetimeMs = o.timeout ?? config.timeout;\n if (lifetimeMs) req.lifetime_secs = Math.max(1, Math.ceil(lifetimeMs / 1000));\n\n try {\n const sandbox = await client.json<ApiSandbox>('POST', '/v1/sandboxes', req);\n return { instance: { client, sandbox }, instanceId: sandbox.id };\n } catch (error) {\n if (error instanceof ApiError && error.status === 401) {\n throw new Error(`sandbox-cli authentication failed. Check your SANDBOX_TOKEN.`);\n }\n throw new Error(\n `Failed to create sandbox-cli sandbox: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n },\n\n getById: async (config: SandboxCliConfig, sandboxId: string) => {\n const client = new ApiClient(config);\n try {\n const sandbox = await client.json<ApiSandbox>('GET', client.sbx(sandboxId));\n if (sandbox.state === 'terminated') return null;\n return { instance: { client, sandbox }, instanceId: sandbox.id };\n } catch (error) {\n if (error instanceof ApiError && error.status === 404) return null;\n throw error;\n }\n },\n\n list: async (config: SandboxCliConfig) => {\n const client = new ApiClient(config);\n const { sandboxes } = await client.json<{ sandboxes: ApiSandbox[] }>('GET', '/v1/sandboxes');\n return (sandboxes ?? [])\n .filter((s) => s.state !== 'terminated')\n .map((sandbox) => ({ instance: { client, sandbox }, instanceId: sandbox.id }));\n },\n\n destroy: async (config: SandboxCliConfig, sandboxId: string) => {\n const client = new ApiClient(config);\n try {\n await client.json('DELETE', client.sbx(sandboxId));\n } catch (error) {\n // Idempotent: the sandbox may already be gone.\n if (error instanceof ApiError && error.status === 404) return;\n throw error;\n }\n },\n\n runCommand: run,\n\n streamCommand: stream,\n\n getInfo: async (instance: SandboxCliInstance): Promise<InstanceInfo> => {\n const { client, sandbox } = instance;\n const s = await client.json<ApiSandbox>('GET', client.sbx(sandbox.id));\n return {\n id: s.id,\n provider: 'sandbox-cli',\n // A pending sandbox is still being created and refuses commands; its\n // native state stays in metadata.\n status: s.state === 'running' ? 'running' : 'stopped',\n createdAt: new Date(s.created_at),\n timeout: (s.lifetime_secs ?? 0) * 1000,\n metadata: { state: s.state, image: s.image, cpus: s.cpus, memoryMb: s.memory_mb, name: s.name, labels: s.labels },\n };\n },\n\n getUrl: async (): Promise<string> => {\n // A port is reached through the API's tunnel (an upgraded connection to\n // the guest's own loopback), never through a public URL.\n throw new Error(\n 'sandbox-cli does not expose sandbox ports as URLs. Use `sandbox-cli tunnel` or the API tunnel endpoint.'\n );\n },\n\n filesystem: {\n readFile: async (instance: SandboxCliInstance, path: string): Promise<string> => {\n const { client, sandbox } = instance;\n const resp = await client.request('GET', client.sbx(sandbox.id) + '/files', { query: { path } });\n return await resp.text();\n },\n\n writeFile: async (instance: SandboxCliInstance, path: string, content: string): Promise<void> => {\n // The API creates parent directories.\n const { client, sandbox } = instance;\n await client.request('PUT', client.sbx(sandbox.id) + '/files', { query: { path }, body: content });\n },\n\n mkdir: async (instance: SandboxCliInstance, path: string): Promise<void> => {\n const r = await run(instance, `mkdir -p -- ${shellQuote(path)}`);\n if (r.exitCode !== 0) throw new Error(`Failed to create directory ${path}: ${r.stderr}`);\n },\n\n readdir: async (instance: SandboxCliInstance, path: string): Promise<FileEntry[]> => {\n const { client, sandbox } = instance;\n const resp = await client.request('GET', client.sbx(sandbox.id) + '/dirs', { query: { path } });\n const { entries } = (await resp.json()) as { entries: Array<{ name: string; type: 'file' | 'dir'; size: number }> };\n return (entries ?? []).map((e) => ({\n name: e.name,\n type: e.type === 'dir' ? ('directory' as const) : ('file' as const),\n size: e.size,\n }));\n },\n\n exists: async (instance: SandboxCliInstance, path: string): Promise<boolean> => {\n const r = await run(instance, `test -e ${shellQuote(path)}`);\n return r.exitCode === 0;\n },\n\n remove: async (instance: SandboxCliInstance, path: string): Promise<void> => {\n // The API's DELETE removes only a file or an empty directory;\n // ComputeSDK's remove is recursive.\n const r = await run(instance, `rm -rf -- ${shellQuote(path)}`);\n if (r.exitCode !== 0) throw new Error(`Failed to remove ${path}: ${r.stderr}`);\n },\n },\n },\n },\n});\n\n/** Single-quotes a value for `sh -c`, safe for any byte sequence. */\nfunction shellQuote(s: string): string {\n return `'${s.replace(/'/g, `'\\\\''`)}'`;\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,63 @@
1
1
  {
2
2
  "name": "@computesdk/sandbox-cli",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.1.1",
4
+ "description": "sandbox-cli provider for ComputeSDK - microVM sandboxes with their own kernel, behind one API on a Mac, a self-hosted Linux machine or the cloud",
5
+ "author": "",
6
+ "license": "MIT",
7
+ "main": "./dist/index.js",
8
+ "module": "./dist/index.mjs",
9
+ "types": "./dist/index.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "import": "./dist/index.mjs",
14
+ "require": "./dist/index.js"
15
+ }
16
+ },
17
+ "files": [
18
+ "dist"
19
+ ],
20
+ "dependencies": {
21
+ "computesdk": "4.1.17",
22
+ "@computesdk/provider": "2.1.23"
23
+ },
24
+ "devDependencies": {
25
+ "@types/node": "^20.0.0",
26
+ "@vitest/coverage-v8": "^1.0.0",
27
+ "eslint": "^8.37.0",
28
+ "rimraf": "^5.0.0",
29
+ "tsup": "^8.0.0",
30
+ "typescript": "^5.0.0",
31
+ "vitest": "^1.0.0",
32
+ "@computesdk/test-utils": "2.0.6"
33
+ },
34
+ "keywords": [
35
+ "computesdk",
36
+ "sandbox-cli",
37
+ "sandbox",
38
+ "microvm",
39
+ "firecracker",
40
+ "code-execution",
41
+ "provider",
42
+ "agents"
43
+ ],
44
+ "repository": {
45
+ "type": "git",
46
+ "url": "https://github.com/computesdk/computesdk.git",
47
+ "directory": "packages/sandbox-cli"
48
+ },
49
+ "homepage": "https://www.computesdk.com",
50
+ "bugs": {
51
+ "url": "https://github.com/computesdk/computesdk/issues"
52
+ },
53
+ "scripts": {
54
+ "build": "tsup",
55
+ "clean": "rimraf dist",
56
+ "dev": "tsup --watch",
57
+ "test": "vitest run",
58
+ "test:watch": "vitest watch",
59
+ "test:coverage": "vitest run --coverage",
60
+ "typecheck": "tsc --noEmit",
61
+ "lint": "eslint"
62
+ }
6
63
  }