@computesdk/createos-sandbox 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 ADDED
@@ -0,0 +1,100 @@
1
+ # @computesdk/createos-sandbox
2
+
3
+ [ComputeSDK](https://github.com/computesdk/computesdk) provider for **CreateOS** —
4
+ NodeOps' createos-sandbox VM sandbox service. Thin adapter over the
5
+ official [`@nodeops-createos/sandbox`](https://www.npmjs.com/package/@nodeops-createos/sandbox).
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm install computesdk @computesdk/createos-sandbox
11
+ ```
12
+
13
+ ## Usage
14
+
15
+ ```typescript
16
+ import { compute } from "computesdk";
17
+ import { createosSandbox } from "@computesdk/createos-sandbox";
18
+
19
+ compute.setConfig({
20
+ provider: createosSandbox({
21
+ apiKey: process.env.CREATEOS_SANDBOX_API_KEY,
22
+ baseUrl: process.env.CREATEOS_SANDBOX_BASE_URL,
23
+ }),
24
+ });
25
+
26
+ const sandbox = await compute.sandbox.create({ memoryMb: 1024, image: "devbox:1" });
27
+ const { stdout } = await sandbox.runCommand("echo hello");
28
+ await sandbox.filesystem.writeFile("/tmp/x.txt", "hi");
29
+ await sandbox.destroy();
30
+ ```
31
+
32
+ ## Configuration
33
+
34
+ | Field | Env fallback | Notes |
35
+ |---|---|---|
36
+ | `apiKey` | `CREATEOS_SANDBOX_API_KEY` | Required. createos-sandbox API key. |
37
+ | `baseUrl` | `CREATEOS_SANDBOX_BASE_URL` | Control-plane URL. Optional; defaults to the production control plane. |
38
+ | `shape` | — | Default VM shape (e.g. `s-1vcpu-1gb`). |
39
+ | `rootfs` | — | Default rootfs catalog name / template. |
40
+ | `timeout` | — | Reported `getInfo().timeout` (ms). Informational. |
41
+
42
+ ## Capability mapping
43
+
44
+ createos-sandbox sizes VMs from a **shape catalog** (not free-form cpu/mem), so
45
+ `create()` fetches the live catalog (`GET /v1/shapes`) and maps ComputeSDK's
46
+ `cpus`/`memoryMb` onto the smallest shape that fits. Pass a provider-specific
47
+ `shape` to pin one exactly (skips the catalog fetch). With no size pinned, the
48
+ default is the smallest live shape with ≥1 GiB RAM. `image`/`runtime` map to
49
+ `rootfs`; `ephemeralDiskMb` is passed straight through to `disk_mib` (the
50
+ control plane validates it; `0`/omitted = the shape's default disk).
51
+
52
+ | ComputeSDK | createos-sandbox |
53
+ |---|---|
54
+ | shape selection | `GET /v1/shapes` (live catalog, nearest fit) |
55
+ | `sandbox.create` / `getById` / `list` / `destroy` | `POST/GET/DELETE /v1/sandboxes` |
56
+ | `sandbox.runCommand` | `POST /v1/sandboxes/:id/exec` (wrapped in `sh -c`) |
57
+ | `sandbox.getInfo` | `GET /v1/sandboxes/:id` |
58
+ | `sandbox.getUrl({port})` | per-sandbox ingress URL |
59
+ | `filesystem.readFile` / `writeFile` | `GET/PUT /v1/sandboxes/:id/files` |
60
+ | `filesystem.mkdir` / `readdir` / `exists` / `remove` | synthesised via `runCommand` |
61
+ | `snapshot.create` / `list` / `delete` | `pause` / list paused / `destroy` |
62
+
63
+ Template builds (`/v1/templates`) are **not** exposed through the ComputeSDK
64
+ provider surface — ComputeSDK's public `Provider` type can't carry the
65
+ createos-specific `dockerfile` create option type-safely. Build templates with
66
+ the native `@nodeops-createos/sandbox` client (`client.templates.create`)
67
+ directly.
68
+
69
+ ### Native escape hatch
70
+
71
+ ComputeSDK's core surface has no pause/resume/fork. Reach the full
72
+ `@nodeops-createos/sandbox` `Sandbox` handle via `getInstance()`:
73
+
74
+ ```typescript
75
+ const native = sandbox.getInstance();
76
+ await native.pause();
77
+ await native.resume();
78
+ const clone = await native.fork();
79
+ await native.attachDisk({ diskId: "my-bucket", mountPath: "/mnt/data" });
80
+ ```
81
+
82
+ ## Known limitations
83
+
84
+ - **`runCommand` env/cwd are synthesised** — createos-sandbox drops per-exec env
85
+ server-side, so `env`/`cwd` are injected by wrapping the command in an inline
86
+ `sh -c` script. Sandbox-level env should be set at create time (`envs`).
87
+ - **Snapshot semantics differ** — `snapshot.create` *pauses* the sandbox (the
88
+ source VM stops); the paused sandbox id IS the snapshot id. `create({ snapshotId })`
89
+ forks that paused bundle into a fresh sandbox.
90
+ - **`getUrl` needs ingress + works in-process** — sandboxes are created with
91
+ ingress enabled by default; `getUrl` is only available for sandboxes created in
92
+ this process (the SDK handle from `getById`/`list` carries no ingress template).
93
+ Ingress currently serves a non-CA TLS cert and strips the `Authorization`
94
+ header upstream — use `protocol: "http"` / `curl -k` and don't rely on HTTP auth
95
+ behind ingress.
96
+ - **`readdir` parses `ls` output** — relies on coreutils `ls` in the rootfs.
97
+
98
+ ## License
99
+
100
+ MIT
@@ -0,0 +1,62 @@
1
+ import * as _computesdk_provider from '@computesdk/provider';
2
+ import { CreateSandboxOptions, SandboxInfo, RunCommandOptions, FileEntry } from '@computesdk/provider';
3
+ import { Shape, SandboxStatus, ForkSandboxRequest, CreateSandboxRequest, Sandbox } from '@nodeops-createos/sandbox';
4
+ export { Sandbox as CreateosSandbox } from '@nodeops-createos/sandbox';
5
+
6
+ interface CreateosConfig {
7
+ /** createos-sandbox API key. Falls back to the CREATEOS_SANDBOX_API_KEY env var. */
8
+ apiKey?: string;
9
+ /** Control-plane base URL. Falls back to the CREATEOS_SANDBOX_BASE_URL env
10
+ * var, then the production control plane. */
11
+ baseUrl?: string;
12
+ /** Default shape when create options pin neither `shape` nor cpus/memoryMb. */
13
+ shape?: string;
14
+ /** Default rootfs catalog name or template id. Empty = host default. */
15
+ rootfs?: string;
16
+ /** Reported `getInfo().timeout` in ms. Informational only. */
17
+ timeout?: number;
18
+ }
19
+ /** ComputeSDK create options plus the createos-specific fields this provider
20
+ * reads off the (otherwise open) options bag. Declaring them turns the cast-
21
+ * heavy decode into a typed contract and makes unsupported fields obvious. */
22
+ interface CreateosCreateOptions extends CreateSandboxOptions {
23
+ /** Explicit shape id. Overrides cpus/memoryMb selection and skips the catalog fetch. */
24
+ shape?: string;
25
+ /** Rootfs catalog name or template id (alias of `runtime`). */
26
+ image?: string;
27
+ /** Rootfs catalog name or template id (alias of `image`). */
28
+ runtime?: string;
29
+ /** Requested RAM; mapped onto the smallest fitting shape. */
30
+ memoryMb?: number;
31
+ /** Requested vCPUs; mapped onto the smallest fitting shape. */
32
+ cpus?: number;
33
+ /** Overlay disk size (MiB). 0 / omitted = the shape's default disk. */
34
+ ephemeralDiskMb?: number;
35
+ /** Enable public ingress. Defaults to true. */
36
+ ingressEnabled?: boolean;
37
+ /** SSH public keys to inject at boot. */
38
+ sshPubkeys?: string[];
39
+ /** Egress allow-list. */
40
+ egress?: string[];
41
+ }
42
+ /** Pick the smallest live catalog shape that satisfies the requested cpu/mem.
43
+ * Returns undefined when neither is given (caller applies the default). */
44
+ declare function pickShape(shapes: Shape[], memoryMb?: number, cpus?: number): string | undefined;
45
+ /** Default shape when create() pins neither `shape` nor cpus/memoryMb: the
46
+ * smallest live shape meeting the RAM floor, else the smallest available. */
47
+ declare function defaultShape(shapes: Shape[]): string | undefined;
48
+ /** Map an createos-sandbox lifecycle state onto ComputeSDK's running|stopped|error. */
49
+ declare function mapStatus(status: SandboxStatus): SandboxInfo["status"];
50
+ /** Pure map: ComputeSDK create options → createos-sandbox fork request body. */
51
+ declare function toForkRequest(opts: CreateosCreateOptions): ForkSandboxRequest;
52
+ /** Pure map: ComputeSDK create options (+ resolved shape/rootfs) → create request. */
53
+ declare function toCreateRequest(opts: CreateosCreateOptions, shape: string, rootfs?: string): CreateSandboxRequest;
54
+ /** Wrap a command so per-call cwd/env/background work despite the server dropping
55
+ * exec env. Env *keys* are validated before being spliced into `export` —
56
+ * values are shell-quoted, but a malformed key would otherwise be raw shell. */
57
+ declare function buildScript(command: string, options?: RunCommandOptions): string;
58
+ /** Parse `ls -lA --time-style=+%s` output into ComputeSDK FileEntry rows. */
59
+ declare function parseLsOutput(stdout: string): FileEntry[];
60
+ declare const createosSandbox: (config: CreateosConfig) => _computesdk_provider.Provider<Sandbox, any, any>;
61
+
62
+ export { type CreateosConfig, type CreateosCreateOptions, buildScript, createosSandbox, defaultShape, mapStatus, parseLsOutput, pickShape, toCreateRequest, toForkRequest };
@@ -0,0 +1,62 @@
1
+ import * as _computesdk_provider from '@computesdk/provider';
2
+ import { CreateSandboxOptions, SandboxInfo, RunCommandOptions, FileEntry } from '@computesdk/provider';
3
+ import { Shape, SandboxStatus, ForkSandboxRequest, CreateSandboxRequest, Sandbox } from '@nodeops-createos/sandbox';
4
+ export { Sandbox as CreateosSandbox } from '@nodeops-createos/sandbox';
5
+
6
+ interface CreateosConfig {
7
+ /** createos-sandbox API key. Falls back to the CREATEOS_SANDBOX_API_KEY env var. */
8
+ apiKey?: string;
9
+ /** Control-plane base URL. Falls back to the CREATEOS_SANDBOX_BASE_URL env
10
+ * var, then the production control plane. */
11
+ baseUrl?: string;
12
+ /** Default shape when create options pin neither `shape` nor cpus/memoryMb. */
13
+ shape?: string;
14
+ /** Default rootfs catalog name or template id. Empty = host default. */
15
+ rootfs?: string;
16
+ /** Reported `getInfo().timeout` in ms. Informational only. */
17
+ timeout?: number;
18
+ }
19
+ /** ComputeSDK create options plus the createos-specific fields this provider
20
+ * reads off the (otherwise open) options bag. Declaring them turns the cast-
21
+ * heavy decode into a typed contract and makes unsupported fields obvious. */
22
+ interface CreateosCreateOptions extends CreateSandboxOptions {
23
+ /** Explicit shape id. Overrides cpus/memoryMb selection and skips the catalog fetch. */
24
+ shape?: string;
25
+ /** Rootfs catalog name or template id (alias of `runtime`). */
26
+ image?: string;
27
+ /** Rootfs catalog name or template id (alias of `image`). */
28
+ runtime?: string;
29
+ /** Requested RAM; mapped onto the smallest fitting shape. */
30
+ memoryMb?: number;
31
+ /** Requested vCPUs; mapped onto the smallest fitting shape. */
32
+ cpus?: number;
33
+ /** Overlay disk size (MiB). 0 / omitted = the shape's default disk. */
34
+ ephemeralDiskMb?: number;
35
+ /** Enable public ingress. Defaults to true. */
36
+ ingressEnabled?: boolean;
37
+ /** SSH public keys to inject at boot. */
38
+ sshPubkeys?: string[];
39
+ /** Egress allow-list. */
40
+ egress?: string[];
41
+ }
42
+ /** Pick the smallest live catalog shape that satisfies the requested cpu/mem.
43
+ * Returns undefined when neither is given (caller applies the default). */
44
+ declare function pickShape(shapes: Shape[], memoryMb?: number, cpus?: number): string | undefined;
45
+ /** Default shape when create() pins neither `shape` nor cpus/memoryMb: the
46
+ * smallest live shape meeting the RAM floor, else the smallest available. */
47
+ declare function defaultShape(shapes: Shape[]): string | undefined;
48
+ /** Map an createos-sandbox lifecycle state onto ComputeSDK's running|stopped|error. */
49
+ declare function mapStatus(status: SandboxStatus): SandboxInfo["status"];
50
+ /** Pure map: ComputeSDK create options → createos-sandbox fork request body. */
51
+ declare function toForkRequest(opts: CreateosCreateOptions): ForkSandboxRequest;
52
+ /** Pure map: ComputeSDK create options (+ resolved shape/rootfs) → create request. */
53
+ declare function toCreateRequest(opts: CreateosCreateOptions, shape: string, rootfs?: string): CreateSandboxRequest;
54
+ /** Wrap a command so per-call cwd/env/background work despite the server dropping
55
+ * exec env. Env *keys* are validated before being spliced into `export` —
56
+ * values are shell-quoted, but a malformed key would otherwise be raw shell. */
57
+ declare function buildScript(command: string, options?: RunCommandOptions): string;
58
+ /** Parse `ls -lA --time-style=+%s` output into ComputeSDK FileEntry rows. */
59
+ declare function parseLsOutput(stdout: string): FileEntry[];
60
+ declare const createosSandbox: (config: CreateosConfig) => _computesdk_provider.Provider<Sandbox, any, any>;
61
+
62
+ export { type CreateosConfig, type CreateosCreateOptions, buildScript, createosSandbox, defaultShape, mapStatus, parseLsOutput, pickShape, toCreateRequest, toForkRequest };
package/dist/index.js ADDED
@@ -0,0 +1,294 @@
1
+ 'use strict';
2
+
3
+ var sandbox = require('@nodeops-createos/sandbox');
4
+ var provider = require('@computesdk/provider');
5
+
6
+ // src/index.ts
7
+ var PROVIDER_NAME = "createos-sandbox";
8
+ var sandboxConfig = /* @__PURE__ */ new WeakMap();
9
+ function shellQuote(arg) {
10
+ return `'${arg.replace(/'/g, "'\\''")}'`;
11
+ }
12
+ var DEFAULT_SHAPE_MIN_MIB = 1024;
13
+ var ENV_KEY_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
14
+ var DEFAULT_BASE_URL = "https://api.sb.createos.sh";
15
+ function env(key) {
16
+ return typeof process !== "undefined" ? process.env?.[key] : void 0;
17
+ }
18
+ function resolveClient(config) {
19
+ const apiKey = config.apiKey ?? env("CREATEOS_SANDBOX_API_KEY");
20
+ if (!apiKey) {
21
+ throw new Error(
22
+ "Missing CreateOS API key. Provide 'apiKey' in config or set the CREATEOS_SANDBOX_API_KEY environment variable."
23
+ );
24
+ }
25
+ const baseUrl = config.baseUrl?.trim() || env("CREATEOS_SANDBOX_BASE_URL")?.trim() || DEFAULT_BASE_URL;
26
+ return new sandbox.CreateosSandboxClient({ apiKey, baseUrl });
27
+ }
28
+ function isNotFound(e) {
29
+ return e instanceof sandbox.CreateosSandboxNotFoundError;
30
+ }
31
+ function sortShapes(shapes) {
32
+ return shapes.toSorted((a, b) => a.mem_mib - b.mem_mib || a.vcpu - b.vcpu);
33
+ }
34
+ function pickShape(shapes, memoryMb, cpus) {
35
+ if (memoryMb == null && cpus == null) return void 0;
36
+ const sorted = sortShapes(shapes);
37
+ const fit = sorted.find(
38
+ (s) => (memoryMb == null || s.mem_mib >= memoryMb) && (cpus == null || s.vcpu >= cpus)
39
+ );
40
+ return (fit ?? sorted[sorted.length - 1])?.id;
41
+ }
42
+ function defaultShape(shapes) {
43
+ const sorted = sortShapes(shapes);
44
+ const fit = sorted.find((s) => s.mem_mib >= DEFAULT_SHAPE_MIN_MIB);
45
+ return (fit ?? sorted[0])?.id;
46
+ }
47
+ async function resolveShape(client, opts, config) {
48
+ const explicit = opts.shape ?? config.shape;
49
+ if (explicit) return explicit;
50
+ const shapes = await client.listShapes();
51
+ const picked = pickShape(shapes, opts.memoryMb, opts.cpus) ?? defaultShape(shapes);
52
+ if (!picked) {
53
+ throw new Error("CreateOS control plane returned an empty shape catalog.");
54
+ }
55
+ return picked;
56
+ }
57
+ function mapStatus(status) {
58
+ if (status === "running") return "running";
59
+ if (status === "error" || status === "failed") return "error";
60
+ return "stopped";
61
+ }
62
+ function envsOf(opts) {
63
+ const envs = opts.envs ?? opts.env;
64
+ return envs && Object.keys(envs).length > 0 ? envs : void 0;
65
+ }
66
+ function strArray(v) {
67
+ return Array.isArray(v) ? v : void 0;
68
+ }
69
+ function toForkRequest(opts) {
70
+ const req = {
71
+ ingress_enabled: opts.ingressEnabled !== void 0 ? Boolean(opts.ingressEnabled) : true
72
+ };
73
+ const ssh = strArray(opts.sshPubkeys);
74
+ if (ssh) req.ssh_pubkeys = ssh;
75
+ const egress = strArray(opts.egress);
76
+ if (egress) req.egress = egress;
77
+ const envs = envsOf(opts);
78
+ if (envs) req.envs = envs;
79
+ return req;
80
+ }
81
+ function toCreateRequest(opts, shape, rootfs) {
82
+ const req = {
83
+ shape,
84
+ ingress_enabled: opts.ingressEnabled !== void 0 ? Boolean(opts.ingressEnabled) : true
85
+ };
86
+ if (rootfs) req.rootfs = rootfs;
87
+ if (opts.name) req.name = String(opts.name);
88
+ if (typeof opts.ephemeralDiskMb === "number" && opts.ephemeralDiskMb > 0) {
89
+ req.disk_mib = opts.ephemeralDiskMb;
90
+ }
91
+ const envs = envsOf(opts);
92
+ if (envs) req.envs = envs;
93
+ const ssh = strArray(opts.sshPubkeys);
94
+ if (ssh) req.ssh_pubkeys = ssh;
95
+ const egress = strArray(opts.egress);
96
+ if (egress) req.egress = egress;
97
+ return req;
98
+ }
99
+ function buildScript(command, options) {
100
+ let script = command;
101
+ if (options?.cwd) script = `cd ${shellQuote(options.cwd)} && ${script}`;
102
+ if (options?.env && Object.keys(options.env).length > 0) {
103
+ const exports = Object.entries(options.env).map(([k, v]) => {
104
+ if (!ENV_KEY_RE.test(k)) {
105
+ throw new Error(
106
+ `Invalid environment variable name ${JSON.stringify(k)}: must match ${ENV_KEY_RE.source}.`
107
+ );
108
+ }
109
+ return `export ${k}=${shellQuote(String(v))}`;
110
+ }).join("; ");
111
+ script = `${exports}; ${script}`;
112
+ }
113
+ if (options?.background) {
114
+ script = `nohup sh -c ${shellQuote(script)} > /dev/null 2>&1 &`;
115
+ }
116
+ return script;
117
+ }
118
+ function parseLsOutput(stdout) {
119
+ const entries = [];
120
+ for (const line of stdout.split("\n")) {
121
+ if (!line || line.startsWith("total ")) continue;
122
+ const m = line.match(/^(\S+)\s+\d+\s+\S+\s+\S+\s+(\d+)\s+(\d+)\s+(.+)$/);
123
+ if (!m) continue;
124
+ const name = m[4].replace(/ -> .*$/, "");
125
+ entries.push({
126
+ name,
127
+ type: m[1].startsWith("d") ? "directory" : "file",
128
+ size: Number(m[2]),
129
+ modified: new Date(Number(m[3]) * 1e3)
130
+ });
131
+ }
132
+ return entries;
133
+ }
134
+ var createosSandbox = provider.defineProvider({
135
+ name: PROVIDER_NAME,
136
+ methods: {
137
+ sandbox: {
138
+ create: async (config, options) => {
139
+ const client = resolveClient(config);
140
+ const opts = options ?? {};
141
+ if (opts.snapshotId) {
142
+ const source = await client.getSandbox(opts.snapshotId);
143
+ const forked = await source.fork(toForkRequest(opts));
144
+ await forked.waitUntilRunning();
145
+ sandboxConfig.set(forked, config);
146
+ return { sandbox: forked, sandboxId: forked.id };
147
+ }
148
+ const shape = await resolveShape(client, opts, config);
149
+ const rootfs = opts.image ?? opts.runtime ?? config.rootfs;
150
+ const sandbox = await client.createSandbox(toCreateRequest(opts, shape, rootfs));
151
+ sandboxConfig.set(sandbox, config);
152
+ return { sandbox, sandboxId: sandbox.id };
153
+ },
154
+ getById: async (config, sandboxId) => {
155
+ const client = resolveClient(config);
156
+ try {
157
+ const sandbox = await client.getSandbox(sandboxId);
158
+ sandboxConfig.set(sandbox, config);
159
+ return { sandbox, sandboxId: sandbox.id };
160
+ } catch (e) {
161
+ if (isNotFound(e)) return null;
162
+ throw e;
163
+ }
164
+ },
165
+ list: async (config) => {
166
+ const client = resolveClient(config);
167
+ const sandboxes = await client.listSandboxes({ limit: 100 });
168
+ return sandboxes.map((sandbox) => {
169
+ sandboxConfig.set(sandbox, config);
170
+ return { sandbox, sandboxId: sandbox.id };
171
+ });
172
+ },
173
+ destroy: async (config, sandboxId) => {
174
+ const client = resolveClient(config);
175
+ try {
176
+ const sandbox = await client.getSandbox(sandboxId);
177
+ await sandbox.destroy();
178
+ } catch (e) {
179
+ if (isNotFound(e)) return;
180
+ throw e;
181
+ }
182
+ },
183
+ runCommand: async (sandbox, command, options) => {
184
+ const { result, exec_ms } = await sandbox.runCommand("sh", [
185
+ "-c",
186
+ buildScript(command, options)
187
+ ]);
188
+ const stderr = result.error ? `${result.stderr}${result.error}` : result.stderr;
189
+ return {
190
+ stdout: result.stdout,
191
+ stderr,
192
+ exitCode: result.exit_code,
193
+ durationMs: exec_ms
194
+ };
195
+ },
196
+ getInfo: async (sandbox) => {
197
+ await sandbox.refresh();
198
+ const v = sandbox.data;
199
+ return {
200
+ id: v.id,
201
+ provider: PROVIDER_NAME,
202
+ status: mapStatus(v.status),
203
+ createdAt: new Date(v.created_at),
204
+ timeout: sandboxConfig.get(sandbox)?.timeout ?? 0,
205
+ metadata: {
206
+ createosStatus: v.status,
207
+ shape: v.shape,
208
+ rootfs: v.rootfs,
209
+ region: v.region,
210
+ ip: v.ip,
211
+ ingressEnabled: v.ingress_enabled
212
+ }
213
+ };
214
+ },
215
+ getUrl: async (sandbox, options) => {
216
+ const scheme = options.protocol === "http" ? "http" : "https";
217
+ return sandbox.previewUrl(options.port, { scheme });
218
+ },
219
+ // Escape hatch: the bare native @nodeops-createos/sandbox handle
220
+ // (pause/resume/fork/disks/...).
221
+ getInstance: (sandbox) => sandbox,
222
+ filesystem: {
223
+ readFile: async (sandbox, path) => {
224
+ const buf = await sandbox.files.download(path);
225
+ return new TextDecoder().decode(buf);
226
+ },
227
+ writeFile: async (sandbox, path, content) => {
228
+ await sandbox.files.upload(path, content);
229
+ },
230
+ mkdir: async (sandbox, path, runCommand) => {
231
+ const r = await runCommand(sandbox, `mkdir -p ${shellQuote(path)}`);
232
+ if (r.exitCode !== 0) throw new Error(`mkdir ${path} failed: ${r.stderr}`);
233
+ },
234
+ readdir: async (sandbox, path, runCommand) => {
235
+ const r = await runCommand(sandbox, `ls -lA --time-style=+%s ${shellQuote(path)}`);
236
+ if (r.exitCode !== 0) throw new Error(`readdir ${path} failed: ${r.stderr}`);
237
+ return parseLsOutput(r.stdout);
238
+ },
239
+ exists: async (sandbox, path, runCommand) => {
240
+ const r = await runCommand(sandbox, `test -e ${shellQuote(path)}`);
241
+ return r.exitCode === 0;
242
+ },
243
+ remove: async (sandbox, path, runCommand) => {
244
+ const r = await runCommand(sandbox, `rm -rf ${shellQuote(path)}`);
245
+ if (r.exitCode !== 0) throw new Error(`remove ${path} failed: ${r.stderr}`);
246
+ }
247
+ }
248
+ },
249
+ snapshot: {
250
+ create: async (config, sandboxId, options) => {
251
+ const client = resolveClient(config);
252
+ const sandbox = await client.getSandbox(sandboxId);
253
+ await sandbox.pause();
254
+ await sandbox.waitUntilPaused();
255
+ return {
256
+ id: sandboxId,
257
+ provider: PROVIDER_NAME,
258
+ createdAt: /* @__PURE__ */ new Date(),
259
+ metadata: { name: options?.name, ...options?.metadata }
260
+ };
261
+ },
262
+ list: async (config, _options) => {
263
+ const client = resolveClient(config);
264
+ const all = await client.listSandboxes({ limit: 500 });
265
+ return all.filter((s) => s.status === "paused").map((s) => ({
266
+ id: s.id,
267
+ provider: PROVIDER_NAME,
268
+ createdAt: new Date(s.data.paused_at ?? s.data.created_at)
269
+ }));
270
+ },
271
+ delete: async (config, snapshotId) => {
272
+ const client = resolveClient(config);
273
+ try {
274
+ const sandbox = await client.getSandbox(snapshotId);
275
+ await sandbox.destroy();
276
+ } catch (e) {
277
+ if (isNotFound(e)) return;
278
+ throw e;
279
+ }
280
+ }
281
+ }
282
+ }
283
+ });
284
+
285
+ exports.buildScript = buildScript;
286
+ exports.createosSandbox = createosSandbox;
287
+ exports.defaultShape = defaultShape;
288
+ exports.mapStatus = mapStatus;
289
+ exports.parseLsOutput = parseLsOutput;
290
+ exports.pickShape = pickShape;
291
+ exports.toCreateRequest = toCreateRequest;
292
+ exports.toForkRequest = toForkRequest;
293
+ //# sourceMappingURL=index.js.map
294
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"names":["CreateosSandboxClient","CreateosSandboxNotFoundError","defineProvider"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * CreateOS Sandbox Provider for ComputeSDK\n *\n * VM sandboxes backed by the NodeOps createos-sandbox control plane.\n * Thin adapter over the official `@nodeops-createos/sandbox` npm package — the same\n * SDK used across the CreateOS tooling.\n *\n * Design notes:\n * - `TSandbox` is the native `@nodeops-createos/sandbox` `Sandbox` handle, so\n * `getInstance()` hands callers the full stateful API (pause / resume / fork /\n * disks / networks / bandwidth) that ComputeSDK's core surface does not model.\n * The resolving config is associated with each handle via a `WeakMap`\n * side-channel (`sandboxConfig`), so `getInfo` can honour `config` (e.g.\n * `timeout`) and its error policy without wrapping or mutating the handle.\n * - createos-sandbox sizes VMs from a fixed *shape* catalog rather than free-form\n * cpu/mem, so create() maps ComputeSDK's `cpus`/`memoryMb` onto the nearest\n * shape and honours an explicit provider-specific `shape` override.\n * - The control plane drops per-exec env server-side (env is sandbox-level,\n * set at create time), so per-command `env`/`cwd` are synthesised by\n * wrapping the command in an inline `sh -c` script.\n * - Snapshot semantics differ from running-snapshot providers (e2b/tensorlake):\n * `snapshot.create` *pauses* the sandbox (the source VM stops), and the\n * paused sandbox id IS the snapshot id. `create({ snapshotId })` forks that\n * paused bundle into a fresh sandbox.\n *\n * Error policy: provider operations fail loudly when the provider boundary is\n * broken. Only a genuine 404 (`CreateosSandboxNotFoundError`) is mapped to the\n * idempotent \"absent\" outcome (`getById` → null, `destroy`/`delete` → no-op).\n * Auth, network, validation, and server errors propagate.\n */\n\nimport { CreateosSandboxClient, CreateosSandboxNotFoundError, Sandbox } from \"@nodeops-createos/sandbox\";\nimport type {\n CreateSandboxRequest,\n ForkSandboxRequest,\n SandboxStatus,\n Shape,\n} from \"@nodeops-createos/sandbox\";\nimport { defineProvider } from \"@computesdk/provider\";\nimport type {\n CommandResult,\n CreateSandboxOptions,\n CreateSnapshotOptions,\n FileEntry,\n ListSnapshotsOptions,\n RunCommandOptions,\n SandboxInfo,\n} from \"@computesdk/provider\";\n\nconst PROVIDER_NAME = \"createos-sandbox\";\n\n/** Per-sandbox config side-channel: associates the resolving config with each\n * native handle without mutating it or wrapping `TSandbox` — so `getInstance()`\n * still returns the bare native handle (the escape hatch other providers honour),\n * while `getInfo` can still reach `config` (e.g. `timeout`). Keyed weakly, so an\n * entry is collected with its sandbox. */\nconst sandboxConfig = new WeakMap<Sandbox, CreateosConfig>();\n\n/** Quote a value into one complete, self-contained shell token. Unlike the\n * framework's `escapeShellArg` (which only neutralises metacharacters *inside*\n * surrounding double quotes), this wraps in single quotes so the token is safe\n * to splice unquoted — spaces, `;`, `&`, `|`, globs and the rest cannot split\n * the command or inject syntax. Embedded single quotes are closed/escaped/reopened. */\nfunction shellQuote(arg: string): string {\n return `'${arg.replace(/'/g, \"'\\\\''\")}'`;\n}\n\n/** The framework-supplied command runner handed to filesystem callbacks. */\ntype CommandRunner = (\n sandbox: Sandbox,\n command: string,\n options?: RunCommandOptions,\n) => Promise<CommandResult>;\n\n/** Memory floor (MiB) for the default shape when a create() pins no size. The\n * control plane names no default shape and `CreateSandboxRequest` requires\n * one, so the default is a client policy — the smallest *live* catalog shape\n * with at least this much RAM. Override per-create with `shape`/`config.shape`. */\nconst DEFAULT_SHAPE_MIN_MIB = 1024;\n\n/** POSIX environment variable name. Keys are spliced into a shell `export`, so\n * anything outside this grammar is rejected rather than emitted (injection). */\nconst ENV_KEY_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;\n\nexport interface CreateosConfig {\n /** createos-sandbox API key. Falls back to the CREATEOS_SANDBOX_API_KEY env var. */\n apiKey?: string;\n /** Control-plane base URL. Falls back to the CREATEOS_SANDBOX_BASE_URL env\n * var, then the production control plane. */\n baseUrl?: string;\n /** Default shape when create options pin neither `shape` nor cpus/memoryMb. */\n shape?: string;\n /** Default rootfs catalog name or template id. Empty = host default. */\n rootfs?: string;\n /** Reported `getInfo().timeout` in ms. Informational only. */\n timeout?: number;\n}\n\n/** ComputeSDK create options plus the createos-specific fields this provider\n * reads off the (otherwise open) options bag. Declaring them turns the cast-\n * heavy decode into a typed contract and makes unsupported fields obvious. */\nexport interface CreateosCreateOptions extends CreateSandboxOptions {\n /** Explicit shape id. Overrides cpus/memoryMb selection and skips the catalog fetch. */\n shape?: string;\n /** Rootfs catalog name or template id (alias of `runtime`). */\n image?: string;\n /** Rootfs catalog name or template id (alias of `image`). */\n runtime?: string;\n /** Requested RAM; mapped onto the smallest fitting shape. */\n memoryMb?: number;\n /** Requested vCPUs; mapped onto the smallest fitting shape. */\n cpus?: number;\n /** Overlay disk size (MiB). 0 / omitted = the shape's default disk. */\n ephemeralDiskMb?: number;\n /** Enable public ingress. Defaults to true. */\n ingressEnabled?: boolean;\n /** SSH public keys to inject at boot. */\n sshPubkeys?: string[];\n /** Egress allow-list. */\n egress?: string[];\n}\n\n/** Production control-plane base URL, used when neither `config.baseUrl` nor\n * the CREATEOS_SANDBOX_BASE_URL env var is set. */\nconst DEFAULT_BASE_URL = \"https://api.sb.createos.sh\";\n\nfunction env(key: string): string | undefined {\n return typeof process !== \"undefined\" ? process.env?.[key] : undefined;\n}\n\nfunction resolveClient(config: CreateosConfig): CreateosSandboxClient {\n const apiKey = config.apiKey ?? env(\"CREATEOS_SANDBOX_API_KEY\");\n if (!apiKey) {\n throw new Error(\n \"Missing CreateOS API key. Provide 'apiKey' in config or set the \" +\n \"CREATEOS_SANDBOX_API_KEY environment variable.\",\n );\n }\n // Precedence: explicit config > env var > production default. A blank value\n // is treated as unset so the next source applies.\n const baseUrl =\n config.baseUrl?.trim() || env(\"CREATEOS_SANDBOX_BASE_URL\")?.trim() || DEFAULT_BASE_URL;\n return new CreateosSandboxClient({ apiKey, baseUrl });\n}\n\n/** True only for a genuine \"resource does not exist\" (404). Everything else —\n * auth, network, validation, server, rate-limit — is a broken boundary. */\nfunction isNotFound(e: unknown): boolean {\n return e instanceof CreateosSandboxNotFoundError;\n}\n\n/** Sort a catalog by ascending RAM then vCPU. Selection relies on this order;\n * the control plane gives no ordering guarantee for `listShapes()`. */\nfunction sortShapes(shapes: Shape[]): Shape[] {\n return shapes.toSorted((a, b) => a.mem_mib - b.mem_mib || a.vcpu - b.vcpu);\n}\n\n/** Pick the smallest live catalog shape that satisfies the requested cpu/mem.\n * Returns undefined when neither is given (caller applies the default). */\nexport function pickShape(shapes: Shape[], memoryMb?: number, cpus?: number): string | undefined {\n if (memoryMb == null && cpus == null) return undefined;\n const sorted = sortShapes(shapes);\n const fit = sorted.find(\n (s) => (memoryMb == null || s.mem_mib >= memoryMb) && (cpus == null || s.vcpu >= cpus),\n );\n return (fit ?? sorted[sorted.length - 1])?.id;\n}\n\n/** Default shape when create() pins neither `shape` nor cpus/memoryMb: the\n * smallest live shape meeting the RAM floor, else the smallest available. */\nexport function defaultShape(shapes: Shape[]): string | undefined {\n const sorted = sortShapes(shapes);\n const fit = sorted.find((s) => s.mem_mib >= DEFAULT_SHAPE_MIN_MIB);\n return (fit ?? sorted[0])?.id;\n}\n\n/** Resolve the shape for a create() call. Fetches the live catalog only when\n * no explicit `shape` is pinned, so the explicit path never depends on\n * `/v1/shapes` being reachable. */\nasync function resolveShape(\n client: CreateosSandboxClient,\n opts: CreateosCreateOptions,\n config: CreateosConfig,\n): Promise<string> {\n const explicit = opts.shape ?? config.shape;\n if (explicit) return explicit;\n const shapes = await client.listShapes();\n const picked = pickShape(shapes, opts.memoryMb, opts.cpus) ?? defaultShape(shapes);\n if (!picked) {\n throw new Error(\"CreateOS control plane returned an empty shape catalog.\");\n }\n return picked;\n}\n\n/** Map an createos-sandbox lifecycle state onto ComputeSDK's running|stopped|error. */\nexport function mapStatus(status: SandboxStatus): SandboxInfo[\"status\"] {\n if (status === \"running\") return \"running\";\n if (status === \"error\" || status === \"failed\") return \"error\";\n return \"stopped\";\n}\n\n/** A non-empty env record from `envs` (preferred) or the legacy `env` alias. */\nfunction envsOf(opts: CreateosCreateOptions): Record<string, string> | undefined {\n const envs = opts.envs ?? (opts as { env?: Record<string, string> }).env;\n return envs && Object.keys(envs).length > 0 ? envs : undefined;\n}\n\n/** A string[] when the value is an array, else undefined. */\nfunction strArray(v: unknown): string[] | undefined {\n return Array.isArray(v) ? (v as string[]) : undefined;\n}\n\n/** Pure map: ComputeSDK create options → createos-sandbox fork request body. */\nexport function toForkRequest(opts: CreateosCreateOptions): ForkSandboxRequest {\n const req: ForkSandboxRequest = {\n ingress_enabled: opts.ingressEnabled !== undefined ? Boolean(opts.ingressEnabled) : true,\n };\n const ssh = strArray(opts.sshPubkeys);\n if (ssh) req.ssh_pubkeys = ssh;\n const egress = strArray(opts.egress);\n if (egress) req.egress = egress;\n const envs = envsOf(opts);\n if (envs) req.envs = envs;\n return req;\n}\n\n/** Pure map: ComputeSDK create options (+ resolved shape/rootfs) → create request. */\nexport function toCreateRequest(\n opts: CreateosCreateOptions,\n shape: string,\n rootfs?: string,\n): CreateSandboxRequest {\n const req: CreateSandboxRequest = {\n shape,\n ingress_enabled: opts.ingressEnabled !== undefined ? Boolean(opts.ingressEnabled) : true,\n };\n if (rootfs) req.rootfs = rootfs;\n if (opts.name) req.name = String(opts.name);\n // Pass the requested overlay-disk size straight through; the control plane\n // validates it (0 / omitted = shape default). No client-side menu.\n if (typeof opts.ephemeralDiskMb === \"number\" && opts.ephemeralDiskMb > 0) {\n req.disk_mib = opts.ephemeralDiskMb;\n }\n const envs = envsOf(opts);\n if (envs) req.envs = envs;\n const ssh = strArray(opts.sshPubkeys);\n if (ssh) req.ssh_pubkeys = ssh;\n const egress = strArray(opts.egress);\n if (egress) req.egress = egress;\n return req;\n}\n\n/** Wrap a command so per-call cwd/env/background work despite the server dropping\n * exec env. Env *keys* are validated before being spliced into `export` —\n * values are shell-quoted, but a malformed key would otherwise be raw shell. */\nexport function buildScript(command: string, options?: RunCommandOptions): string {\n let script = command;\n if (options?.cwd) script = `cd ${shellQuote(options.cwd)} && ${script}`;\n if (options?.env && Object.keys(options.env).length > 0) {\n const exports = Object.entries(options.env)\n .map(([k, v]) => {\n if (!ENV_KEY_RE.test(k)) {\n throw new Error(\n `Invalid environment variable name ${JSON.stringify(k)}: ` +\n `must match ${ENV_KEY_RE.source}.`,\n );\n }\n return `export ${k}=${shellQuote(String(v))}`;\n })\n .join(\"; \");\n script = `${exports}; ${script}`;\n }\n if (options?.background) {\n script = `nohup sh -c ${shellQuote(script)} > /dev/null 2>&1 &`;\n }\n return script;\n}\n\n/** Parse `ls -lA --time-style=+%s` output into ComputeSDK FileEntry rows. */\nexport function parseLsOutput(stdout: string): FileEntry[] {\n const entries: FileEntry[] = [];\n for (const line of stdout.split(\"\\n\")) {\n if (!line || line.startsWith(\"total \")) continue;\n const m = line.match(/^(\\S+)\\s+\\d+\\s+\\S+\\s+\\S+\\s+(\\d+)\\s+(\\d+)\\s+(.+)$/);\n if (!m) continue;\n const name = m[4]!.replace(/ -> .*$/, \"\"); // strip symlink target\n entries.push({\n name,\n type: m[1]!.startsWith(\"d\") ? \"directory\" : \"file\",\n size: Number(m[2]),\n modified: new Date(Number(m[3]) * 1000),\n });\n }\n return entries;\n}\n\nexport const createosSandbox = defineProvider<Sandbox, CreateosConfig>({\n name: PROVIDER_NAME,\n methods: {\n sandbox: {\n create: async (config: CreateosConfig, options?: CreateSandboxOptions) => {\n const client = resolveClient(config);\n const opts = (options ?? {}) as CreateosCreateOptions;\n\n // A snapshot id is a paused sandbox: fork it into a fresh sandbox.\n if (opts.snapshotId) {\n const source = await client.getSandbox(opts.snapshotId);\n const forked = await source.fork(toForkRequest(opts));\n // Surface lifecycle failures: a fork that never reaches \"running\"\n // (or hits a terminal state) must not be reported as success.\n await forked.waitUntilRunning();\n sandboxConfig.set(forked, config);\n return { sandbox: forked, sandboxId: forked.id };\n }\n\n const shape = await resolveShape(client, opts, config);\n const rootfs = opts.image ?? opts.runtime ?? config.rootfs;\n const sandbox = await client.createSandbox(toCreateRequest(opts, shape, rootfs));\n sandboxConfig.set(sandbox, config);\n return { sandbox, sandboxId: sandbox.id };\n },\n\n getById: async (config: CreateosConfig, sandboxId: string) => {\n const client = resolveClient(config);\n try {\n const sandbox = await client.getSandbox(sandboxId);\n sandboxConfig.set(sandbox, config);\n return { sandbox, sandboxId: sandbox.id };\n } catch (e) {\n if (isNotFound(e)) return null;\n throw e;\n }\n },\n\n list: async (config: CreateosConfig) => {\n const client = resolveClient(config);\n // No catch: a failed list is a broken boundary, not an empty account.\n const sandboxes = await client.listSandboxes({ limit: 100 });\n return sandboxes.map((sandbox) => {\n sandboxConfig.set(sandbox, config);\n return { sandbox, sandboxId: sandbox.id };\n });\n },\n\n destroy: async (config: CreateosConfig, sandboxId: string) => {\n const client = resolveClient(config);\n try {\n const sandbox = await client.getSandbox(sandboxId);\n await sandbox.destroy();\n } catch (e) {\n if (isNotFound(e)) return; // already gone; destroy is idempotent\n throw e;\n }\n },\n\n runCommand: async (\n sandbox: Sandbox,\n command: string,\n options?: RunCommandOptions,\n ): Promise<CommandResult> => {\n // `sh -c` (not `bash -lc`): POSIX-portable so bare rootfses without\n // bash still run. buildScript only emits POSIX constructs. Transport /\n // auth errors propagate; a command that ran reports its own exit code.\n const { result, exec_ms } = await sandbox.runCommand(\"sh\", [\n \"-c\",\n buildScript(command, options),\n ]);\n const stderr = result.error ? `${result.stderr}${result.error}` : result.stderr;\n return {\n stdout: result.stdout,\n stderr,\n exitCode: result.exit_code,\n durationMs: exec_ms,\n };\n },\n\n getInfo: async (sandbox: Sandbox): Promise<SandboxInfo> => {\n // Refresh failures propagate (no swallow): a getInfo that cannot reach\n // the control plane must surface that rather than return stale data.\n await sandbox.refresh();\n const v = sandbox.data;\n return {\n id: v.id,\n provider: PROVIDER_NAME,\n status: mapStatus(v.status),\n createdAt: new Date(v.created_at),\n timeout: sandboxConfig.get(sandbox)?.timeout ?? 0,\n metadata: {\n createosStatus: v.status,\n shape: v.shape,\n rootfs: v.rootfs,\n region: v.region,\n ip: v.ip,\n ingressEnabled: v.ingress_enabled,\n },\n };\n },\n\n getUrl: async (sandbox: Sandbox, options: { port: number; protocol?: string }) => {\n const scheme = options.protocol === \"http\" ? \"http\" : \"https\";\n return sandbox.previewUrl(options.port, { scheme });\n },\n\n // Escape hatch: the bare native @nodeops-createos/sandbox handle\n // (pause/resume/fork/disks/...).\n getInstance: (sandbox: Sandbox): Sandbox => sandbox,\n\n filesystem: {\n readFile: async (sandbox: Sandbox, path: string): Promise<string> => {\n const buf = await sandbox.files.download(path);\n return new TextDecoder().decode(buf);\n },\n writeFile: async (sandbox: Sandbox, path: string, content: string): Promise<void> => {\n await sandbox.files.upload(path, content);\n },\n mkdir: async (sandbox: Sandbox, path: string, runCommand: CommandRunner): Promise<void> => {\n const r = await runCommand(sandbox, `mkdir -p ${shellQuote(path)}`);\n if (r.exitCode !== 0) throw new Error(`mkdir ${path} failed: ${r.stderr}`);\n },\n readdir: async (sandbox: Sandbox, path: string, runCommand: CommandRunner): Promise<FileEntry[]> => {\n const r = await runCommand(sandbox, `ls -lA --time-style=+%s ${shellQuote(path)}`);\n if (r.exitCode !== 0) throw new Error(`readdir ${path} failed: ${r.stderr}`);\n return parseLsOutput(r.stdout);\n },\n exists: async (sandbox: Sandbox, path: string, runCommand: CommandRunner): Promise<boolean> => {\n const r = await runCommand(sandbox, `test -e ${shellQuote(path)}`);\n return r.exitCode === 0;\n },\n remove: async (sandbox: Sandbox, path: string, runCommand: CommandRunner): Promise<void> => {\n const r = await runCommand(sandbox, `rm -rf ${shellQuote(path)}`);\n if (r.exitCode !== 0) throw new Error(`remove ${path} failed: ${r.stderr}`);\n },\n },\n },\n\n snapshot: {\n create: async (\n config: CreateosConfig,\n sandboxId: string,\n options?: CreateSnapshotOptions,\n ) => {\n const client = resolveClient(config);\n const sandbox = await client.getSandbox(sandboxId);\n // createos-sandbox has no decoupled snapshot object: pausing IS the snapshot,\n // and the paused sandbox id is the snapshot id. This stops the source VM.\n await sandbox.pause();\n // The success claim is \"it paused\" — surface a pause that timed out or\n // entered a terminal state rather than returning a phantom snapshot.\n await sandbox.waitUntilPaused();\n return {\n id: sandboxId,\n provider: PROVIDER_NAME,\n createdAt: new Date(),\n metadata: { name: options?.name, ...options?.metadata },\n };\n },\n list: async (config: CreateosConfig, _options?: ListSnapshotsOptions) => {\n const client = resolveClient(config);\n // The typed status filter excludes \"paused\", so list all and filter.\n const all = await client.listSandboxes({ limit: 500 });\n return all\n .filter((s) => s.status === \"paused\")\n .map((s) => ({\n id: s.id,\n provider: PROVIDER_NAME,\n createdAt: new Date(s.data.paused_at ?? s.data.created_at),\n }));\n },\n delete: async (config: CreateosConfig, snapshotId: string) => {\n const client = resolveClient(config);\n try {\n const sandbox = await client.getSandbox(snapshotId);\n await sandbox.destroy();\n } catch (e) {\n if (isNotFound(e)) return; // already gone\n throw e;\n }\n },\n },\n },\n});\n\nexport type { Sandbox as CreateosSandbox } from \"@nodeops-createos/sandbox\";\n"]}
package/dist/index.mjs ADDED
@@ -0,0 +1,285 @@
1
+ import { CreateosSandboxClient, CreateosSandboxNotFoundError } from '@nodeops-createos/sandbox';
2
+ import { defineProvider } from '@computesdk/provider';
3
+
4
+ // src/index.ts
5
+ var PROVIDER_NAME = "createos-sandbox";
6
+ var sandboxConfig = /* @__PURE__ */ new WeakMap();
7
+ function shellQuote(arg) {
8
+ return `'${arg.replace(/'/g, "'\\''")}'`;
9
+ }
10
+ var DEFAULT_SHAPE_MIN_MIB = 1024;
11
+ var ENV_KEY_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
12
+ var DEFAULT_BASE_URL = "https://api.sb.createos.sh";
13
+ function env(key) {
14
+ return typeof process !== "undefined" ? process.env?.[key] : void 0;
15
+ }
16
+ function resolveClient(config) {
17
+ const apiKey = config.apiKey ?? env("CREATEOS_SANDBOX_API_KEY");
18
+ if (!apiKey) {
19
+ throw new Error(
20
+ "Missing CreateOS API key. Provide 'apiKey' in config or set the CREATEOS_SANDBOX_API_KEY environment variable."
21
+ );
22
+ }
23
+ const baseUrl = config.baseUrl?.trim() || env("CREATEOS_SANDBOX_BASE_URL")?.trim() || DEFAULT_BASE_URL;
24
+ return new CreateosSandboxClient({ apiKey, baseUrl });
25
+ }
26
+ function isNotFound(e) {
27
+ return e instanceof CreateosSandboxNotFoundError;
28
+ }
29
+ function sortShapes(shapes) {
30
+ return shapes.toSorted((a, b) => a.mem_mib - b.mem_mib || a.vcpu - b.vcpu);
31
+ }
32
+ function pickShape(shapes, memoryMb, cpus) {
33
+ if (memoryMb == null && cpus == null) return void 0;
34
+ const sorted = sortShapes(shapes);
35
+ const fit = sorted.find(
36
+ (s) => (memoryMb == null || s.mem_mib >= memoryMb) && (cpus == null || s.vcpu >= cpus)
37
+ );
38
+ return (fit ?? sorted[sorted.length - 1])?.id;
39
+ }
40
+ function defaultShape(shapes) {
41
+ const sorted = sortShapes(shapes);
42
+ const fit = sorted.find((s) => s.mem_mib >= DEFAULT_SHAPE_MIN_MIB);
43
+ return (fit ?? sorted[0])?.id;
44
+ }
45
+ async function resolveShape(client, opts, config) {
46
+ const explicit = opts.shape ?? config.shape;
47
+ if (explicit) return explicit;
48
+ const shapes = await client.listShapes();
49
+ const picked = pickShape(shapes, opts.memoryMb, opts.cpus) ?? defaultShape(shapes);
50
+ if (!picked) {
51
+ throw new Error("CreateOS control plane returned an empty shape catalog.");
52
+ }
53
+ return picked;
54
+ }
55
+ function mapStatus(status) {
56
+ if (status === "running") return "running";
57
+ if (status === "error" || status === "failed") return "error";
58
+ return "stopped";
59
+ }
60
+ function envsOf(opts) {
61
+ const envs = opts.envs ?? opts.env;
62
+ return envs && Object.keys(envs).length > 0 ? envs : void 0;
63
+ }
64
+ function strArray(v) {
65
+ return Array.isArray(v) ? v : void 0;
66
+ }
67
+ function toForkRequest(opts) {
68
+ const req = {
69
+ ingress_enabled: opts.ingressEnabled !== void 0 ? Boolean(opts.ingressEnabled) : true
70
+ };
71
+ const ssh = strArray(opts.sshPubkeys);
72
+ if (ssh) req.ssh_pubkeys = ssh;
73
+ const egress = strArray(opts.egress);
74
+ if (egress) req.egress = egress;
75
+ const envs = envsOf(opts);
76
+ if (envs) req.envs = envs;
77
+ return req;
78
+ }
79
+ function toCreateRequest(opts, shape, rootfs) {
80
+ const req = {
81
+ shape,
82
+ ingress_enabled: opts.ingressEnabled !== void 0 ? Boolean(opts.ingressEnabled) : true
83
+ };
84
+ if (rootfs) req.rootfs = rootfs;
85
+ if (opts.name) req.name = String(opts.name);
86
+ if (typeof opts.ephemeralDiskMb === "number" && opts.ephemeralDiskMb > 0) {
87
+ req.disk_mib = opts.ephemeralDiskMb;
88
+ }
89
+ const envs = envsOf(opts);
90
+ if (envs) req.envs = envs;
91
+ const ssh = strArray(opts.sshPubkeys);
92
+ if (ssh) req.ssh_pubkeys = ssh;
93
+ const egress = strArray(opts.egress);
94
+ if (egress) req.egress = egress;
95
+ return req;
96
+ }
97
+ function buildScript(command, options) {
98
+ let script = command;
99
+ if (options?.cwd) script = `cd ${shellQuote(options.cwd)} && ${script}`;
100
+ if (options?.env && Object.keys(options.env).length > 0) {
101
+ const exports = Object.entries(options.env).map(([k, v]) => {
102
+ if (!ENV_KEY_RE.test(k)) {
103
+ throw new Error(
104
+ `Invalid environment variable name ${JSON.stringify(k)}: must match ${ENV_KEY_RE.source}.`
105
+ );
106
+ }
107
+ return `export ${k}=${shellQuote(String(v))}`;
108
+ }).join("; ");
109
+ script = `${exports}; ${script}`;
110
+ }
111
+ if (options?.background) {
112
+ script = `nohup sh -c ${shellQuote(script)} > /dev/null 2>&1 &`;
113
+ }
114
+ return script;
115
+ }
116
+ function parseLsOutput(stdout) {
117
+ const entries = [];
118
+ for (const line of stdout.split("\n")) {
119
+ if (!line || line.startsWith("total ")) continue;
120
+ const m = line.match(/^(\S+)\s+\d+\s+\S+\s+\S+\s+(\d+)\s+(\d+)\s+(.+)$/);
121
+ if (!m) continue;
122
+ const name = m[4].replace(/ -> .*$/, "");
123
+ entries.push({
124
+ name,
125
+ type: m[1].startsWith("d") ? "directory" : "file",
126
+ size: Number(m[2]),
127
+ modified: new Date(Number(m[3]) * 1e3)
128
+ });
129
+ }
130
+ return entries;
131
+ }
132
+ var createosSandbox = defineProvider({
133
+ name: PROVIDER_NAME,
134
+ methods: {
135
+ sandbox: {
136
+ create: async (config, options) => {
137
+ const client = resolveClient(config);
138
+ const opts = options ?? {};
139
+ if (opts.snapshotId) {
140
+ const source = await client.getSandbox(opts.snapshotId);
141
+ const forked = await source.fork(toForkRequest(opts));
142
+ await forked.waitUntilRunning();
143
+ sandboxConfig.set(forked, config);
144
+ return { sandbox: forked, sandboxId: forked.id };
145
+ }
146
+ const shape = await resolveShape(client, opts, config);
147
+ const rootfs = opts.image ?? opts.runtime ?? config.rootfs;
148
+ const sandbox = await client.createSandbox(toCreateRequest(opts, shape, rootfs));
149
+ sandboxConfig.set(sandbox, config);
150
+ return { sandbox, sandboxId: sandbox.id };
151
+ },
152
+ getById: async (config, sandboxId) => {
153
+ const client = resolveClient(config);
154
+ try {
155
+ const sandbox = await client.getSandbox(sandboxId);
156
+ sandboxConfig.set(sandbox, config);
157
+ return { sandbox, sandboxId: sandbox.id };
158
+ } catch (e) {
159
+ if (isNotFound(e)) return null;
160
+ throw e;
161
+ }
162
+ },
163
+ list: async (config) => {
164
+ const client = resolveClient(config);
165
+ const sandboxes = await client.listSandboxes({ limit: 100 });
166
+ return sandboxes.map((sandbox) => {
167
+ sandboxConfig.set(sandbox, config);
168
+ return { sandbox, sandboxId: sandbox.id };
169
+ });
170
+ },
171
+ destroy: async (config, sandboxId) => {
172
+ const client = resolveClient(config);
173
+ try {
174
+ const sandbox = await client.getSandbox(sandboxId);
175
+ await sandbox.destroy();
176
+ } catch (e) {
177
+ if (isNotFound(e)) return;
178
+ throw e;
179
+ }
180
+ },
181
+ runCommand: async (sandbox, command, options) => {
182
+ const { result, exec_ms } = await sandbox.runCommand("sh", [
183
+ "-c",
184
+ buildScript(command, options)
185
+ ]);
186
+ const stderr = result.error ? `${result.stderr}${result.error}` : result.stderr;
187
+ return {
188
+ stdout: result.stdout,
189
+ stderr,
190
+ exitCode: result.exit_code,
191
+ durationMs: exec_ms
192
+ };
193
+ },
194
+ getInfo: async (sandbox) => {
195
+ await sandbox.refresh();
196
+ const v = sandbox.data;
197
+ return {
198
+ id: v.id,
199
+ provider: PROVIDER_NAME,
200
+ status: mapStatus(v.status),
201
+ createdAt: new Date(v.created_at),
202
+ timeout: sandboxConfig.get(sandbox)?.timeout ?? 0,
203
+ metadata: {
204
+ createosStatus: v.status,
205
+ shape: v.shape,
206
+ rootfs: v.rootfs,
207
+ region: v.region,
208
+ ip: v.ip,
209
+ ingressEnabled: v.ingress_enabled
210
+ }
211
+ };
212
+ },
213
+ getUrl: async (sandbox, options) => {
214
+ const scheme = options.protocol === "http" ? "http" : "https";
215
+ return sandbox.previewUrl(options.port, { scheme });
216
+ },
217
+ // Escape hatch: the bare native @nodeops-createos/sandbox handle
218
+ // (pause/resume/fork/disks/...).
219
+ getInstance: (sandbox) => sandbox,
220
+ filesystem: {
221
+ readFile: async (sandbox, path) => {
222
+ const buf = await sandbox.files.download(path);
223
+ return new TextDecoder().decode(buf);
224
+ },
225
+ writeFile: async (sandbox, path, content) => {
226
+ await sandbox.files.upload(path, content);
227
+ },
228
+ mkdir: async (sandbox, path, runCommand) => {
229
+ const r = await runCommand(sandbox, `mkdir -p ${shellQuote(path)}`);
230
+ if (r.exitCode !== 0) throw new Error(`mkdir ${path} failed: ${r.stderr}`);
231
+ },
232
+ readdir: async (sandbox, path, runCommand) => {
233
+ const r = await runCommand(sandbox, `ls -lA --time-style=+%s ${shellQuote(path)}`);
234
+ if (r.exitCode !== 0) throw new Error(`readdir ${path} failed: ${r.stderr}`);
235
+ return parseLsOutput(r.stdout);
236
+ },
237
+ exists: async (sandbox, path, runCommand) => {
238
+ const r = await runCommand(sandbox, `test -e ${shellQuote(path)}`);
239
+ return r.exitCode === 0;
240
+ },
241
+ remove: async (sandbox, path, runCommand) => {
242
+ const r = await runCommand(sandbox, `rm -rf ${shellQuote(path)}`);
243
+ if (r.exitCode !== 0) throw new Error(`remove ${path} failed: ${r.stderr}`);
244
+ }
245
+ }
246
+ },
247
+ snapshot: {
248
+ create: async (config, sandboxId, options) => {
249
+ const client = resolveClient(config);
250
+ const sandbox = await client.getSandbox(sandboxId);
251
+ await sandbox.pause();
252
+ await sandbox.waitUntilPaused();
253
+ return {
254
+ id: sandboxId,
255
+ provider: PROVIDER_NAME,
256
+ createdAt: /* @__PURE__ */ new Date(),
257
+ metadata: { name: options?.name, ...options?.metadata }
258
+ };
259
+ },
260
+ list: async (config, _options) => {
261
+ const client = resolveClient(config);
262
+ const all = await client.listSandboxes({ limit: 500 });
263
+ return all.filter((s) => s.status === "paused").map((s) => ({
264
+ id: s.id,
265
+ provider: PROVIDER_NAME,
266
+ createdAt: new Date(s.data.paused_at ?? s.data.created_at)
267
+ }));
268
+ },
269
+ delete: async (config, snapshotId) => {
270
+ const client = resolveClient(config);
271
+ try {
272
+ const sandbox = await client.getSandbox(snapshotId);
273
+ await sandbox.destroy();
274
+ } catch (e) {
275
+ if (isNotFound(e)) return;
276
+ throw e;
277
+ }
278
+ }
279
+ }
280
+ }
281
+ });
282
+
283
+ export { buildScript, createosSandbox, defaultShape, mapStatus, parseLsOutput, pickShape, toCreateRequest, toForkRequest };
284
+ //# sourceMappingURL=index.mjs.map
285
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["/**\n * CreateOS Sandbox Provider for ComputeSDK\n *\n * VM sandboxes backed by the NodeOps createos-sandbox control plane.\n * Thin adapter over the official `@nodeops-createos/sandbox` npm package — the same\n * SDK used across the CreateOS tooling.\n *\n * Design notes:\n * - `TSandbox` is the native `@nodeops-createos/sandbox` `Sandbox` handle, so\n * `getInstance()` hands callers the full stateful API (pause / resume / fork /\n * disks / networks / bandwidth) that ComputeSDK's core surface does not model.\n * The resolving config is associated with each handle via a `WeakMap`\n * side-channel (`sandboxConfig`), so `getInfo` can honour `config` (e.g.\n * `timeout`) and its error policy without wrapping or mutating the handle.\n * - createos-sandbox sizes VMs from a fixed *shape* catalog rather than free-form\n * cpu/mem, so create() maps ComputeSDK's `cpus`/`memoryMb` onto the nearest\n * shape and honours an explicit provider-specific `shape` override.\n * - The control plane drops per-exec env server-side (env is sandbox-level,\n * set at create time), so per-command `env`/`cwd` are synthesised by\n * wrapping the command in an inline `sh -c` script.\n * - Snapshot semantics differ from running-snapshot providers (e2b/tensorlake):\n * `snapshot.create` *pauses* the sandbox (the source VM stops), and the\n * paused sandbox id IS the snapshot id. `create({ snapshotId })` forks that\n * paused bundle into a fresh sandbox.\n *\n * Error policy: provider operations fail loudly when the provider boundary is\n * broken. Only a genuine 404 (`CreateosSandboxNotFoundError`) is mapped to the\n * idempotent \"absent\" outcome (`getById` → null, `destroy`/`delete` → no-op).\n * Auth, network, validation, and server errors propagate.\n */\n\nimport { CreateosSandboxClient, CreateosSandboxNotFoundError, Sandbox } from \"@nodeops-createos/sandbox\";\nimport type {\n CreateSandboxRequest,\n ForkSandboxRequest,\n SandboxStatus,\n Shape,\n} from \"@nodeops-createos/sandbox\";\nimport { defineProvider } from \"@computesdk/provider\";\nimport type {\n CommandResult,\n CreateSandboxOptions,\n CreateSnapshotOptions,\n FileEntry,\n ListSnapshotsOptions,\n RunCommandOptions,\n SandboxInfo,\n} from \"@computesdk/provider\";\n\nconst PROVIDER_NAME = \"createos-sandbox\";\n\n/** Per-sandbox config side-channel: associates the resolving config with each\n * native handle without mutating it or wrapping `TSandbox` — so `getInstance()`\n * still returns the bare native handle (the escape hatch other providers honour),\n * while `getInfo` can still reach `config` (e.g. `timeout`). Keyed weakly, so an\n * entry is collected with its sandbox. */\nconst sandboxConfig = new WeakMap<Sandbox, CreateosConfig>();\n\n/** Quote a value into one complete, self-contained shell token. Unlike the\n * framework's `escapeShellArg` (which only neutralises metacharacters *inside*\n * surrounding double quotes), this wraps in single quotes so the token is safe\n * to splice unquoted — spaces, `;`, `&`, `|`, globs and the rest cannot split\n * the command or inject syntax. Embedded single quotes are closed/escaped/reopened. */\nfunction shellQuote(arg: string): string {\n return `'${arg.replace(/'/g, \"'\\\\''\")}'`;\n}\n\n/** The framework-supplied command runner handed to filesystem callbacks. */\ntype CommandRunner = (\n sandbox: Sandbox,\n command: string,\n options?: RunCommandOptions,\n) => Promise<CommandResult>;\n\n/** Memory floor (MiB) for the default shape when a create() pins no size. The\n * control plane names no default shape and `CreateSandboxRequest` requires\n * one, so the default is a client policy — the smallest *live* catalog shape\n * with at least this much RAM. Override per-create with `shape`/`config.shape`. */\nconst DEFAULT_SHAPE_MIN_MIB = 1024;\n\n/** POSIX environment variable name. Keys are spliced into a shell `export`, so\n * anything outside this grammar is rejected rather than emitted (injection). */\nconst ENV_KEY_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;\n\nexport interface CreateosConfig {\n /** createos-sandbox API key. Falls back to the CREATEOS_SANDBOX_API_KEY env var. */\n apiKey?: string;\n /** Control-plane base URL. Falls back to the CREATEOS_SANDBOX_BASE_URL env\n * var, then the production control plane. */\n baseUrl?: string;\n /** Default shape when create options pin neither `shape` nor cpus/memoryMb. */\n shape?: string;\n /** Default rootfs catalog name or template id. Empty = host default. */\n rootfs?: string;\n /** Reported `getInfo().timeout` in ms. Informational only. */\n timeout?: number;\n}\n\n/** ComputeSDK create options plus the createos-specific fields this provider\n * reads off the (otherwise open) options bag. Declaring them turns the cast-\n * heavy decode into a typed contract and makes unsupported fields obvious. */\nexport interface CreateosCreateOptions extends CreateSandboxOptions {\n /** Explicit shape id. Overrides cpus/memoryMb selection and skips the catalog fetch. */\n shape?: string;\n /** Rootfs catalog name or template id (alias of `runtime`). */\n image?: string;\n /** Rootfs catalog name or template id (alias of `image`). */\n runtime?: string;\n /** Requested RAM; mapped onto the smallest fitting shape. */\n memoryMb?: number;\n /** Requested vCPUs; mapped onto the smallest fitting shape. */\n cpus?: number;\n /** Overlay disk size (MiB). 0 / omitted = the shape's default disk. */\n ephemeralDiskMb?: number;\n /** Enable public ingress. Defaults to true. */\n ingressEnabled?: boolean;\n /** SSH public keys to inject at boot. */\n sshPubkeys?: string[];\n /** Egress allow-list. */\n egress?: string[];\n}\n\n/** Production control-plane base URL, used when neither `config.baseUrl` nor\n * the CREATEOS_SANDBOX_BASE_URL env var is set. */\nconst DEFAULT_BASE_URL = \"https://api.sb.createos.sh\";\n\nfunction env(key: string): string | undefined {\n return typeof process !== \"undefined\" ? process.env?.[key] : undefined;\n}\n\nfunction resolveClient(config: CreateosConfig): CreateosSandboxClient {\n const apiKey = config.apiKey ?? env(\"CREATEOS_SANDBOX_API_KEY\");\n if (!apiKey) {\n throw new Error(\n \"Missing CreateOS API key. Provide 'apiKey' in config or set the \" +\n \"CREATEOS_SANDBOX_API_KEY environment variable.\",\n );\n }\n // Precedence: explicit config > env var > production default. A blank value\n // is treated as unset so the next source applies.\n const baseUrl =\n config.baseUrl?.trim() || env(\"CREATEOS_SANDBOX_BASE_URL\")?.trim() || DEFAULT_BASE_URL;\n return new CreateosSandboxClient({ apiKey, baseUrl });\n}\n\n/** True only for a genuine \"resource does not exist\" (404). Everything else —\n * auth, network, validation, server, rate-limit — is a broken boundary. */\nfunction isNotFound(e: unknown): boolean {\n return e instanceof CreateosSandboxNotFoundError;\n}\n\n/** Sort a catalog by ascending RAM then vCPU. Selection relies on this order;\n * the control plane gives no ordering guarantee for `listShapes()`. */\nfunction sortShapes(shapes: Shape[]): Shape[] {\n return shapes.toSorted((a, b) => a.mem_mib - b.mem_mib || a.vcpu - b.vcpu);\n}\n\n/** Pick the smallest live catalog shape that satisfies the requested cpu/mem.\n * Returns undefined when neither is given (caller applies the default). */\nexport function pickShape(shapes: Shape[], memoryMb?: number, cpus?: number): string | undefined {\n if (memoryMb == null && cpus == null) return undefined;\n const sorted = sortShapes(shapes);\n const fit = sorted.find(\n (s) => (memoryMb == null || s.mem_mib >= memoryMb) && (cpus == null || s.vcpu >= cpus),\n );\n return (fit ?? sorted[sorted.length - 1])?.id;\n}\n\n/** Default shape when create() pins neither `shape` nor cpus/memoryMb: the\n * smallest live shape meeting the RAM floor, else the smallest available. */\nexport function defaultShape(shapes: Shape[]): string | undefined {\n const sorted = sortShapes(shapes);\n const fit = sorted.find((s) => s.mem_mib >= DEFAULT_SHAPE_MIN_MIB);\n return (fit ?? sorted[0])?.id;\n}\n\n/** Resolve the shape for a create() call. Fetches the live catalog only when\n * no explicit `shape` is pinned, so the explicit path never depends on\n * `/v1/shapes` being reachable. */\nasync function resolveShape(\n client: CreateosSandboxClient,\n opts: CreateosCreateOptions,\n config: CreateosConfig,\n): Promise<string> {\n const explicit = opts.shape ?? config.shape;\n if (explicit) return explicit;\n const shapes = await client.listShapes();\n const picked = pickShape(shapes, opts.memoryMb, opts.cpus) ?? defaultShape(shapes);\n if (!picked) {\n throw new Error(\"CreateOS control plane returned an empty shape catalog.\");\n }\n return picked;\n}\n\n/** Map an createos-sandbox lifecycle state onto ComputeSDK's running|stopped|error. */\nexport function mapStatus(status: SandboxStatus): SandboxInfo[\"status\"] {\n if (status === \"running\") return \"running\";\n if (status === \"error\" || status === \"failed\") return \"error\";\n return \"stopped\";\n}\n\n/** A non-empty env record from `envs` (preferred) or the legacy `env` alias. */\nfunction envsOf(opts: CreateosCreateOptions): Record<string, string> | undefined {\n const envs = opts.envs ?? (opts as { env?: Record<string, string> }).env;\n return envs && Object.keys(envs).length > 0 ? envs : undefined;\n}\n\n/** A string[] when the value is an array, else undefined. */\nfunction strArray(v: unknown): string[] | undefined {\n return Array.isArray(v) ? (v as string[]) : undefined;\n}\n\n/** Pure map: ComputeSDK create options → createos-sandbox fork request body. */\nexport function toForkRequest(opts: CreateosCreateOptions): ForkSandboxRequest {\n const req: ForkSandboxRequest = {\n ingress_enabled: opts.ingressEnabled !== undefined ? Boolean(opts.ingressEnabled) : true,\n };\n const ssh = strArray(opts.sshPubkeys);\n if (ssh) req.ssh_pubkeys = ssh;\n const egress = strArray(opts.egress);\n if (egress) req.egress = egress;\n const envs = envsOf(opts);\n if (envs) req.envs = envs;\n return req;\n}\n\n/** Pure map: ComputeSDK create options (+ resolved shape/rootfs) → create request. */\nexport function toCreateRequest(\n opts: CreateosCreateOptions,\n shape: string,\n rootfs?: string,\n): CreateSandboxRequest {\n const req: CreateSandboxRequest = {\n shape,\n ingress_enabled: opts.ingressEnabled !== undefined ? Boolean(opts.ingressEnabled) : true,\n };\n if (rootfs) req.rootfs = rootfs;\n if (opts.name) req.name = String(opts.name);\n // Pass the requested overlay-disk size straight through; the control plane\n // validates it (0 / omitted = shape default). No client-side menu.\n if (typeof opts.ephemeralDiskMb === \"number\" && opts.ephemeralDiskMb > 0) {\n req.disk_mib = opts.ephemeralDiskMb;\n }\n const envs = envsOf(opts);\n if (envs) req.envs = envs;\n const ssh = strArray(opts.sshPubkeys);\n if (ssh) req.ssh_pubkeys = ssh;\n const egress = strArray(opts.egress);\n if (egress) req.egress = egress;\n return req;\n}\n\n/** Wrap a command so per-call cwd/env/background work despite the server dropping\n * exec env. Env *keys* are validated before being spliced into `export` —\n * values are shell-quoted, but a malformed key would otherwise be raw shell. */\nexport function buildScript(command: string, options?: RunCommandOptions): string {\n let script = command;\n if (options?.cwd) script = `cd ${shellQuote(options.cwd)} && ${script}`;\n if (options?.env && Object.keys(options.env).length > 0) {\n const exports = Object.entries(options.env)\n .map(([k, v]) => {\n if (!ENV_KEY_RE.test(k)) {\n throw new Error(\n `Invalid environment variable name ${JSON.stringify(k)}: ` +\n `must match ${ENV_KEY_RE.source}.`,\n );\n }\n return `export ${k}=${shellQuote(String(v))}`;\n })\n .join(\"; \");\n script = `${exports}; ${script}`;\n }\n if (options?.background) {\n script = `nohup sh -c ${shellQuote(script)} > /dev/null 2>&1 &`;\n }\n return script;\n}\n\n/** Parse `ls -lA --time-style=+%s` output into ComputeSDK FileEntry rows. */\nexport function parseLsOutput(stdout: string): FileEntry[] {\n const entries: FileEntry[] = [];\n for (const line of stdout.split(\"\\n\")) {\n if (!line || line.startsWith(\"total \")) continue;\n const m = line.match(/^(\\S+)\\s+\\d+\\s+\\S+\\s+\\S+\\s+(\\d+)\\s+(\\d+)\\s+(.+)$/);\n if (!m) continue;\n const name = m[4]!.replace(/ -> .*$/, \"\"); // strip symlink target\n entries.push({\n name,\n type: m[1]!.startsWith(\"d\") ? \"directory\" : \"file\",\n size: Number(m[2]),\n modified: new Date(Number(m[3]) * 1000),\n });\n }\n return entries;\n}\n\nexport const createosSandbox = defineProvider<Sandbox, CreateosConfig>({\n name: PROVIDER_NAME,\n methods: {\n sandbox: {\n create: async (config: CreateosConfig, options?: CreateSandboxOptions) => {\n const client = resolveClient(config);\n const opts = (options ?? {}) as CreateosCreateOptions;\n\n // A snapshot id is a paused sandbox: fork it into a fresh sandbox.\n if (opts.snapshotId) {\n const source = await client.getSandbox(opts.snapshotId);\n const forked = await source.fork(toForkRequest(opts));\n // Surface lifecycle failures: a fork that never reaches \"running\"\n // (or hits a terminal state) must not be reported as success.\n await forked.waitUntilRunning();\n sandboxConfig.set(forked, config);\n return { sandbox: forked, sandboxId: forked.id };\n }\n\n const shape = await resolveShape(client, opts, config);\n const rootfs = opts.image ?? opts.runtime ?? config.rootfs;\n const sandbox = await client.createSandbox(toCreateRequest(opts, shape, rootfs));\n sandboxConfig.set(sandbox, config);\n return { sandbox, sandboxId: sandbox.id };\n },\n\n getById: async (config: CreateosConfig, sandboxId: string) => {\n const client = resolveClient(config);\n try {\n const sandbox = await client.getSandbox(sandboxId);\n sandboxConfig.set(sandbox, config);\n return { sandbox, sandboxId: sandbox.id };\n } catch (e) {\n if (isNotFound(e)) return null;\n throw e;\n }\n },\n\n list: async (config: CreateosConfig) => {\n const client = resolveClient(config);\n // No catch: a failed list is a broken boundary, not an empty account.\n const sandboxes = await client.listSandboxes({ limit: 100 });\n return sandboxes.map((sandbox) => {\n sandboxConfig.set(sandbox, config);\n return { sandbox, sandboxId: sandbox.id };\n });\n },\n\n destroy: async (config: CreateosConfig, sandboxId: string) => {\n const client = resolveClient(config);\n try {\n const sandbox = await client.getSandbox(sandboxId);\n await sandbox.destroy();\n } catch (e) {\n if (isNotFound(e)) return; // already gone; destroy is idempotent\n throw e;\n }\n },\n\n runCommand: async (\n sandbox: Sandbox,\n command: string,\n options?: RunCommandOptions,\n ): Promise<CommandResult> => {\n // `sh -c` (not `bash -lc`): POSIX-portable so bare rootfses without\n // bash still run. buildScript only emits POSIX constructs. Transport /\n // auth errors propagate; a command that ran reports its own exit code.\n const { result, exec_ms } = await sandbox.runCommand(\"sh\", [\n \"-c\",\n buildScript(command, options),\n ]);\n const stderr = result.error ? `${result.stderr}${result.error}` : result.stderr;\n return {\n stdout: result.stdout,\n stderr,\n exitCode: result.exit_code,\n durationMs: exec_ms,\n };\n },\n\n getInfo: async (sandbox: Sandbox): Promise<SandboxInfo> => {\n // Refresh failures propagate (no swallow): a getInfo that cannot reach\n // the control plane must surface that rather than return stale data.\n await sandbox.refresh();\n const v = sandbox.data;\n return {\n id: v.id,\n provider: PROVIDER_NAME,\n status: mapStatus(v.status),\n createdAt: new Date(v.created_at),\n timeout: sandboxConfig.get(sandbox)?.timeout ?? 0,\n metadata: {\n createosStatus: v.status,\n shape: v.shape,\n rootfs: v.rootfs,\n region: v.region,\n ip: v.ip,\n ingressEnabled: v.ingress_enabled,\n },\n };\n },\n\n getUrl: async (sandbox: Sandbox, options: { port: number; protocol?: string }) => {\n const scheme = options.protocol === \"http\" ? \"http\" : \"https\";\n return sandbox.previewUrl(options.port, { scheme });\n },\n\n // Escape hatch: the bare native @nodeops-createos/sandbox handle\n // (pause/resume/fork/disks/...).\n getInstance: (sandbox: Sandbox): Sandbox => sandbox,\n\n filesystem: {\n readFile: async (sandbox: Sandbox, path: string): Promise<string> => {\n const buf = await sandbox.files.download(path);\n return new TextDecoder().decode(buf);\n },\n writeFile: async (sandbox: Sandbox, path: string, content: string): Promise<void> => {\n await sandbox.files.upload(path, content);\n },\n mkdir: async (sandbox: Sandbox, path: string, runCommand: CommandRunner): Promise<void> => {\n const r = await runCommand(sandbox, `mkdir -p ${shellQuote(path)}`);\n if (r.exitCode !== 0) throw new Error(`mkdir ${path} failed: ${r.stderr}`);\n },\n readdir: async (sandbox: Sandbox, path: string, runCommand: CommandRunner): Promise<FileEntry[]> => {\n const r = await runCommand(sandbox, `ls -lA --time-style=+%s ${shellQuote(path)}`);\n if (r.exitCode !== 0) throw new Error(`readdir ${path} failed: ${r.stderr}`);\n return parseLsOutput(r.stdout);\n },\n exists: async (sandbox: Sandbox, path: string, runCommand: CommandRunner): Promise<boolean> => {\n const r = await runCommand(sandbox, `test -e ${shellQuote(path)}`);\n return r.exitCode === 0;\n },\n remove: async (sandbox: Sandbox, path: string, runCommand: CommandRunner): Promise<void> => {\n const r = await runCommand(sandbox, `rm -rf ${shellQuote(path)}`);\n if (r.exitCode !== 0) throw new Error(`remove ${path} failed: ${r.stderr}`);\n },\n },\n },\n\n snapshot: {\n create: async (\n config: CreateosConfig,\n sandboxId: string,\n options?: CreateSnapshotOptions,\n ) => {\n const client = resolveClient(config);\n const sandbox = await client.getSandbox(sandboxId);\n // createos-sandbox has no decoupled snapshot object: pausing IS the snapshot,\n // and the paused sandbox id is the snapshot id. This stops the source VM.\n await sandbox.pause();\n // The success claim is \"it paused\" — surface a pause that timed out or\n // entered a terminal state rather than returning a phantom snapshot.\n await sandbox.waitUntilPaused();\n return {\n id: sandboxId,\n provider: PROVIDER_NAME,\n createdAt: new Date(),\n metadata: { name: options?.name, ...options?.metadata },\n };\n },\n list: async (config: CreateosConfig, _options?: ListSnapshotsOptions) => {\n const client = resolveClient(config);\n // The typed status filter excludes \"paused\", so list all and filter.\n const all = await client.listSandboxes({ limit: 500 });\n return all\n .filter((s) => s.status === \"paused\")\n .map((s) => ({\n id: s.id,\n provider: PROVIDER_NAME,\n createdAt: new Date(s.data.paused_at ?? s.data.created_at),\n }));\n },\n delete: async (config: CreateosConfig, snapshotId: string) => {\n const client = resolveClient(config);\n try {\n const sandbox = await client.getSandbox(snapshotId);\n await sandbox.destroy();\n } catch (e) {\n if (isNotFound(e)) return; // already gone\n throw e;\n }\n },\n },\n },\n});\n\nexport type { Sandbox as CreateosSandbox } from \"@nodeops-createos/sandbox\";\n"]}
package/package.json ADDED
@@ -0,0 +1,67 @@
1
+ {
2
+ "name": "@computesdk/createos-sandbox",
3
+ "version": "0.1.1",
4
+ "description": "CreateOS provider for ComputeSDK — NodeOps VM sandboxes with pause/resume/fork snapshots",
5
+ "author": "NodeOps Inc.",
6
+ "license": "MIT",
7
+ "engines": {
8
+ "node": ">=22"
9
+ },
10
+ "main": "./dist/index.js",
11
+ "module": "./dist/index.mjs",
12
+ "types": "./dist/index.d.ts",
13
+ "exports": {
14
+ ".": {
15
+ "types": "./dist/index.d.ts",
16
+ "import": "./dist/index.mjs",
17
+ "require": "./dist/index.js"
18
+ }
19
+ },
20
+ "files": [
21
+ "dist"
22
+ ],
23
+ "dependencies": {
24
+ "@nodeops-createos/sandbox": "^0.6.0",
25
+ "@computesdk/provider": "2.1.3"
26
+ },
27
+ "keywords": [
28
+ "code-execution",
29
+ "computesdk",
30
+ "createos",
31
+ "createos-sandbox",
32
+ "nodeops",
33
+ "provider",
34
+ "sandbox",
35
+ "snapshot"
36
+ ],
37
+ "repository": {
38
+ "type": "git",
39
+ "url": "https://github.com/computesdk/computesdk.git",
40
+ "directory": "packages/createos-sandbox"
41
+ },
42
+ "homepage": "https://www.computesdk.com",
43
+ "bugs": {
44
+ "url": "https://github.com/computesdk/computesdk/issues"
45
+ },
46
+ "devDependencies": {
47
+ "@types/node": "^20.0.0",
48
+ "@vitest/coverage-v8": "^1.0.0",
49
+ "eslint": "^8.37.0",
50
+ "rimraf": "^5.0.0",
51
+ "tsup": "^8.0.0",
52
+ "typescript": "^5.0.0",
53
+ "vitest": "^1.0.0",
54
+ "@computesdk/test-utils": "2.0.0",
55
+ "computesdk": "4.1.3"
56
+ },
57
+ "scripts": {
58
+ "build": "tsup",
59
+ "clean": "rimraf dist",
60
+ "dev": "tsup --watch",
61
+ "test": "vitest run",
62
+ "test:watch": "vitest watch",
63
+ "test:coverage": "vitest run --coverage",
64
+ "typecheck": "tsc --noEmit",
65
+ "lint": "eslint"
66
+ }
67
+ }