noumen 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/README.md +63 -8
  2. package/dist/a2a/index.d.ts +4 -2
  3. package/dist/acp/index.d.ts +5 -3
  4. package/dist/{agent-1nFVUP9E.d.ts → agent-C3eDRsxs.d.ts} +19 -508
  5. package/dist/chunk-I5SBSOS6.js +40 -0
  6. package/dist/chunk-I5SBSOS6.js.map +1 -0
  7. package/dist/{chunk-4HW6LN6D.js → chunk-WPCYGZOE.js} +58 -1228
  8. package/dist/chunk-WPCYGZOE.js.map +1 -0
  9. package/dist/{chunk-5JN4SPI7.js → chunk-WTLK2ZAR.js} +1 -1
  10. package/dist/{chunk-HL6JCRZJ.js → chunk-XZN4QZLK.js} +4 -4
  11. package/dist/cli/index.js +10 -10
  12. package/dist/computer-BPdxSo6X.d.ts +88 -0
  13. package/dist/docker.d.ts +129 -0
  14. package/dist/docker.js +401 -0
  15. package/dist/docker.js.map +1 -0
  16. package/dist/e2b.d.ts +157 -0
  17. package/dist/e2b.js +202 -0
  18. package/dist/e2b.js.map +1 -0
  19. package/dist/freestyle.d.ts +174 -0
  20. package/dist/freestyle.js +240 -0
  21. package/dist/freestyle.js.map +1 -0
  22. package/dist/index.d.ts +9 -201
  23. package/dist/index.js +24 -48
  24. package/dist/lsp/index.d.ts +3 -2
  25. package/dist/mcp/index.d.ts +4 -3
  26. package/dist/mcp/index.js +2 -2
  27. package/dist/{provider-factory-KCLIF34X.js → provider-factory-KI7OZUY3.js} +2 -2
  28. package/dist/{resolve-4JA2BBDA.js → resolve-GDSHNMG6.js} +2 -2
  29. package/dist/sandbox-9qeMTNrD.d.ts +126 -0
  30. package/dist/server/index.d.ts +4 -2
  31. package/dist/{server-CHMxuWKq.d.ts → server-Cu9gv1dk.d.ts} +1 -1
  32. package/dist/sprites.d.ts +136 -0
  33. package/dist/sprites.js +334 -0
  34. package/dist/sprites.js.map +1 -0
  35. package/dist/ssh.d.ts +187 -0
  36. package/dist/ssh.js +392 -0
  37. package/dist/ssh.js.map +1 -0
  38. package/dist/{types-RPKUTu1k.d.ts → types-BA87bHPV.d.ts} +2 -88
  39. package/package.json +28 -1
  40. package/dist/chunk-4HW6LN6D.js.map +0 -1
  41. /package/dist/{chunk-5JN4SPI7.js.map → chunk-WTLK2ZAR.js.map} +0 -0
  42. /package/dist/{chunk-HL6JCRZJ.js.map → chunk-XZN4QZLK.js.map} +0 -0
  43. /package/dist/{provider-factory-KCLIF34X.js.map → provider-factory-KI7OZUY3.js.map} +0 -0
  44. /package/dist/{resolve-4JA2BBDA.js.map → resolve-GDSHNMG6.js.map} +0 -0
@@ -0,0 +1,334 @@
1
+ import {
2
+ createComputerProxy,
3
+ createFsProxy
4
+ } from "./chunk-I5SBSOS6.js";
5
+ import "./chunk-DGUM43GV.js";
6
+
7
+ // src/virtual/sprites-fs.ts
8
+ import * as path from "path";
9
+ var SpritesFs = class {
10
+ token;
11
+ spriteName;
12
+ baseURL;
13
+ workingDir;
14
+ constructor(opts) {
15
+ this.token = opts.token;
16
+ this.spriteName = opts.spriteName;
17
+ this.baseURL = (opts.baseURL ?? "https://api.sprites.dev").replace(
18
+ /\/$/,
19
+ ""
20
+ );
21
+ this.workingDir = opts.workingDir ?? "/home/sprite";
22
+ }
23
+ fsUrl(endpoint, params) {
24
+ const url = new URL(
25
+ `${this.baseURL}/v1/sprites/${this.spriteName}/fs${endpoint}`
26
+ );
27
+ if (params) {
28
+ for (const [k, v] of Object.entries(params)) {
29
+ url.searchParams.set(k, v);
30
+ }
31
+ }
32
+ return url.toString();
33
+ }
34
+ resolvePath(p) {
35
+ const normalizedBase = this.workingDir.endsWith("/") ? this.workingDir : this.workingDir + "/";
36
+ if (p.startsWith("/")) {
37
+ if (p !== this.workingDir && !p.startsWith(normalizedBase)) {
38
+ throw new Error(`Absolute path "${p}" is outside working directory "${this.workingDir}"`);
39
+ }
40
+ return p;
41
+ }
42
+ const resolved = path.resolve(this.workingDir, p);
43
+ if (resolved !== this.workingDir && !resolved.startsWith(normalizedBase)) {
44
+ throw new Error(`Path "${p}" escapes working directory "${this.workingDir}"`);
45
+ }
46
+ return resolved;
47
+ }
48
+ headers() {
49
+ return {
50
+ Authorization: `Bearer ${this.token}`
51
+ };
52
+ }
53
+ async readFile(filePath, _opts) {
54
+ const url = this.fsUrl("/read", { path: this.resolvePath(filePath) });
55
+ const res = await fetch(url, { headers: this.headers() });
56
+ if (!res.ok) {
57
+ throw new Error(
58
+ `SpritesFs readFile failed (${res.status}): ${await res.text()}`
59
+ );
60
+ }
61
+ return res.text();
62
+ }
63
+ async readFileBytes(filePath, maxBytes) {
64
+ const url = this.fsUrl("/read", {
65
+ path: this.resolvePath(filePath),
66
+ binary: "true"
67
+ });
68
+ const res = await fetch(url, { headers: this.headers() });
69
+ if (!res.ok) {
70
+ throw new Error(
71
+ `SpritesFs readFileBytes failed (${res.status}): ${await res.text()}`
72
+ );
73
+ }
74
+ const arrayBuf = await res.arrayBuffer();
75
+ const buf = Buffer.from(arrayBuf);
76
+ if (maxBytes !== void 0 && buf.length > maxBytes) {
77
+ return buf.subarray(0, maxBytes);
78
+ }
79
+ return buf;
80
+ }
81
+ async writeFile(filePath, content) {
82
+ const url = this.fsUrl("/write");
83
+ const res = await fetch(url, {
84
+ method: "POST",
85
+ headers: {
86
+ ...this.headers(),
87
+ "Content-Type": "application/json"
88
+ },
89
+ body: JSON.stringify({
90
+ path: this.resolvePath(filePath),
91
+ content
92
+ })
93
+ });
94
+ if (!res.ok) {
95
+ throw new Error(
96
+ `SpritesFs writeFile failed (${res.status}): ${await res.text()}`
97
+ );
98
+ }
99
+ }
100
+ /**
101
+ * @warning Not atomic. Concurrent appends may lose data due to
102
+ * read-then-write TOCTOU. The Sprites API does not expose an append primitive.
103
+ */
104
+ async appendFile(filePath, content) {
105
+ let existing = "";
106
+ try {
107
+ existing = await this.readFile(filePath);
108
+ } catch {
109
+ }
110
+ await this.writeFile(filePath, existing + content);
111
+ }
112
+ async deleteFile(filePath, opts) {
113
+ const url = this.fsUrl("/remove");
114
+ const res = await fetch(url, {
115
+ method: "POST",
116
+ headers: {
117
+ ...this.headers(),
118
+ "Content-Type": "application/json"
119
+ },
120
+ body: JSON.stringify({
121
+ path: this.resolvePath(filePath),
122
+ recursive: opts?.recursive ?? false
123
+ })
124
+ });
125
+ if (!res.ok) {
126
+ throw new Error(
127
+ `SpritesFs deleteFile failed (${res.status}): ${await res.text()}`
128
+ );
129
+ }
130
+ }
131
+ async mkdir(dirPath, opts) {
132
+ const url = this.fsUrl("/mkdir");
133
+ const res = await fetch(url, {
134
+ method: "POST",
135
+ headers: {
136
+ ...this.headers(),
137
+ "Content-Type": "application/json"
138
+ },
139
+ body: JSON.stringify({
140
+ path: this.resolvePath(dirPath),
141
+ recursive: opts?.recursive ?? false
142
+ })
143
+ });
144
+ if (!res.ok) {
145
+ throw new Error(
146
+ `SpritesFs mkdir failed (${res.status}): ${await res.text()}`
147
+ );
148
+ }
149
+ }
150
+ async readdir(dirPath, _opts) {
151
+ const url = this.fsUrl("/readdir", { path: this.resolvePath(dirPath) });
152
+ const res = await fetch(url, { headers: this.headers() });
153
+ if (!res.ok) {
154
+ throw new Error(
155
+ `SpritesFs readdir failed (${res.status}): ${await res.text()}`
156
+ );
157
+ }
158
+ const data = await res.json();
159
+ return data.map((entry) => ({
160
+ name: entry.name,
161
+ path: entry.path,
162
+ isDirectory: entry.is_dir,
163
+ isFile: !entry.is_dir,
164
+ size: entry.size
165
+ }));
166
+ }
167
+ async exists(filePath) {
168
+ try {
169
+ await this.stat(filePath);
170
+ return true;
171
+ } catch {
172
+ return false;
173
+ }
174
+ }
175
+ async stat(filePath) {
176
+ const url = this.fsUrl("/stat", { path: this.resolvePath(filePath) });
177
+ const res = await fetch(url, { headers: this.headers() });
178
+ if (!res.ok) {
179
+ throw new Error(
180
+ `SpritesFs stat failed (${res.status}): ${await res.text()}`
181
+ );
182
+ }
183
+ const data = await res.json();
184
+ return {
185
+ size: data.size,
186
+ isDirectory: data.is_dir,
187
+ isFile: !data.is_dir,
188
+ createdAt: data.created_at ? new Date(data.created_at) : void 0,
189
+ modifiedAt: data.modified_at ? new Date(data.modified_at) : void 0
190
+ };
191
+ }
192
+ };
193
+
194
+ // src/virtual/sprites-computer.ts
195
+ var SpritesComputer = class {
196
+ token;
197
+ spriteName;
198
+ baseURL;
199
+ workingDir;
200
+ constructor(opts) {
201
+ this.token = opts.token;
202
+ this.spriteName = opts.spriteName;
203
+ this.baseURL = (opts.baseURL ?? "https://api.sprites.dev").replace(
204
+ /\/$/,
205
+ ""
206
+ );
207
+ this.workingDir = opts.workingDir ?? "/home/sprite";
208
+ }
209
+ headers() {
210
+ return {
211
+ Authorization: `Bearer ${this.token}`,
212
+ "Content-Type": "application/json"
213
+ };
214
+ }
215
+ async executeCommand(command, opts) {
216
+ const cwd = opts?.cwd ?? this.workingDir;
217
+ const wrappedCommand = `cd ${this.shellEscape(cwd)} && ${command}`;
218
+ const url = `${this.baseURL}/v1/sprites/${this.spriteName}/exec`;
219
+ const res = await fetch(url, {
220
+ method: "POST",
221
+ headers: this.headers(),
222
+ body: JSON.stringify({
223
+ command: ["bash", "-c", wrappedCommand],
224
+ timeout: opts?.timeout ?? 3e4,
225
+ env: opts?.env
226
+ })
227
+ });
228
+ if (!res.ok) {
229
+ const text = await res.text();
230
+ return {
231
+ exitCode: 1,
232
+ stdout: "",
233
+ stderr: `Sprites exec failed (${res.status}): ${text}`
234
+ };
235
+ }
236
+ const data = await res.json();
237
+ return {
238
+ exitCode: data.exit_code,
239
+ stdout: data.stdout ?? "",
240
+ stderr: data.stderr ?? ""
241
+ };
242
+ }
243
+ shellEscape(s) {
244
+ return `'${s.replace(/'/g, "'\\''")}'`;
245
+ }
246
+ };
247
+
248
+ // src/virtual/sprites-sandbox.ts
249
+ function SpritesSandbox(opts) {
250
+ const baseURL = (opts.baseURL ?? "https://api.sprites.dev").replace(/\/$/, "");
251
+ const userProvidedName = opts.spriteName;
252
+ if (userProvidedName) {
253
+ const fsOpts = { ...opts, spriteName: userProvidedName };
254
+ return {
255
+ fs: new SpritesFs(fsOpts),
256
+ computer: new SpritesComputer(fsOpts),
257
+ sandboxId: () => userProvidedName
258
+ };
259
+ }
260
+ const fsProxy = createFsProxy();
261
+ const computerProxy = createComputerProxy();
262
+ let resolvedName;
263
+ let autoCreated = false;
264
+ let initPromise = null;
265
+ async function doInit(reconnectId) {
266
+ let name = reconnectId ?? `${opts.namePrefix ?? "noumen-"}${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
267
+ let needsCreate = !reconnectId;
268
+ if (reconnectId) {
269
+ const check = await fetch(`${baseURL}/v1/sprites/${reconnectId}`, {
270
+ method: "GET",
271
+ headers: { Authorization: `Bearer ${opts.token}` }
272
+ });
273
+ if (!check.ok) {
274
+ name = `${opts.namePrefix ?? "noumen-"}${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
275
+ needsCreate = true;
276
+ }
277
+ }
278
+ if (needsCreate) {
279
+ const res = await fetch(`${baseURL}/v1/sprites`, {
280
+ method: "POST",
281
+ headers: {
282
+ Authorization: `Bearer ${opts.token}`,
283
+ "Content-Type": "application/json"
284
+ },
285
+ body: JSON.stringify({ name })
286
+ });
287
+ if (!res.ok) {
288
+ throw new Error(`Sprites auto-create failed (${res.status}): ${await res.text()}`);
289
+ }
290
+ autoCreated = true;
291
+ }
292
+ resolvedName = name;
293
+ const childOpts = { ...opts, spriteName: name };
294
+ fsProxy.setTarget(new SpritesFs(childOpts));
295
+ computerProxy.setTarget(new SpritesComputer(childOpts));
296
+ }
297
+ return {
298
+ fs: fsProxy,
299
+ computer: computerProxy,
300
+ sandboxId: () => resolvedName,
301
+ init(sandboxId) {
302
+ if (!initPromise) {
303
+ initPromise = doInit(sandboxId).catch((err) => {
304
+ initPromise = null;
305
+ throw err;
306
+ });
307
+ }
308
+ return initPromise;
309
+ },
310
+ async dispose() {
311
+ if (initPromise) {
312
+ await initPromise.catch(() => {
313
+ });
314
+ }
315
+ if (!autoCreated || !resolvedName) return;
316
+ try {
317
+ const res = await fetch(`${baseURL}/v1/sprites/${resolvedName}`, {
318
+ method: "DELETE",
319
+ headers: { Authorization: `Bearer ${opts.token}` }
320
+ });
321
+ if (!res.ok && res.status !== 404) {
322
+ throw new Error(`Sprites dispose failed (${res.status}): ${await res.text()}`);
323
+ }
324
+ } catch {
325
+ }
326
+ }
327
+ };
328
+ }
329
+ export {
330
+ SpritesComputer,
331
+ SpritesFs,
332
+ SpritesSandbox
333
+ };
334
+ //# sourceMappingURL=sprites.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/virtual/sprites-fs.ts","../src/virtual/sprites-computer.ts","../src/virtual/sprites-sandbox.ts"],"sourcesContent":["import * as path from \"node:path\";\nimport type { VirtualFs, FileEntry, FileStat, ReadOptions } from \"./fs.js\";\n\nexport interface SpritesFsOptions {\n /** sprites.dev API token */\n token: string;\n /** Name of the sprite container */\n spriteName: string;\n /** Base URL for sprites API (default: https://api.sprites.dev) */\n baseURL?: string;\n /** Working directory inside the sprite (default: /home/sprite) */\n workingDir?: string;\n}\n\n/**\n * Sandboxed VirtualFs backed by a remote sprites.dev container. All file\n * operations are executed over the sprites.dev HTTP API — the agent has no\n * access to the host filesystem. This is the recommended VirtualFs for\n * production deployments and untrusted agents. See `LocalFs` for an\n * unsandboxed local alternative.\n */\nexport class SpritesFs implements VirtualFs {\n private token: string;\n private spriteName: string;\n private baseURL: string;\n private workingDir: string;\n\n constructor(opts: SpritesFsOptions) {\n this.token = opts.token;\n this.spriteName = opts.spriteName;\n this.baseURL = (opts.baseURL ?? \"https://api.sprites.dev\").replace(\n /\\/$/,\n \"\",\n );\n this.workingDir = opts.workingDir ?? \"/home/sprite\";\n }\n\n private fsUrl(endpoint: string, params?: Record<string, string>): string {\n const url = new URL(\n `${this.baseURL}/v1/sprites/${this.spriteName}/fs${endpoint}`,\n );\n if (params) {\n for (const [k, v] of Object.entries(params)) {\n url.searchParams.set(k, v);\n }\n }\n return url.toString();\n }\n\n private resolvePath(p: string): string {\n const normalizedBase = this.workingDir.endsWith(\"/\") ? this.workingDir : this.workingDir + \"/\";\n if (p.startsWith(\"/\")) {\n if (p !== this.workingDir && !p.startsWith(normalizedBase)) {\n throw new Error(`Absolute path \"${p}\" is outside working directory \"${this.workingDir}\"`);\n }\n return p;\n }\n const resolved = path.resolve(this.workingDir, p);\n if (resolved !== this.workingDir && !resolved.startsWith(normalizedBase)) {\n throw new Error(`Path \"${p}\" escapes working directory \"${this.workingDir}\"`);\n }\n return resolved;\n }\n\n private headers(): Record<string, string> {\n return {\n Authorization: `Bearer ${this.token}`,\n };\n }\n\n async readFile(filePath: string, _opts?: ReadOptions): Promise<string> {\n const url = this.fsUrl(\"/read\", { path: this.resolvePath(filePath) });\n const res = await fetch(url, { headers: this.headers() });\n if (!res.ok) {\n throw new Error(\n `SpritesFs readFile failed (${res.status}): ${await res.text()}`,\n );\n }\n return res.text();\n }\n\n async readFileBytes(filePath: string, maxBytes?: number): Promise<Buffer> {\n const url = this.fsUrl(\"/read\", {\n path: this.resolvePath(filePath),\n binary: \"true\",\n });\n const res = await fetch(url, { headers: this.headers() });\n if (!res.ok) {\n throw new Error(\n `SpritesFs readFileBytes failed (${res.status}): ${await res.text()}`,\n );\n }\n const arrayBuf = await res.arrayBuffer();\n const buf = Buffer.from(arrayBuf);\n if (maxBytes !== undefined && buf.length > maxBytes) {\n return buf.subarray(0, maxBytes);\n }\n return buf;\n }\n\n async writeFile(filePath: string, content: string): Promise<void> {\n const url = this.fsUrl(\"/write\");\n const res = await fetch(url, {\n method: \"POST\",\n headers: {\n ...this.headers(),\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({\n path: this.resolvePath(filePath),\n content,\n }),\n });\n if (!res.ok) {\n throw new Error(\n `SpritesFs writeFile failed (${res.status}): ${await res.text()}`,\n );\n }\n }\n\n /**\n * @warning Not atomic. Concurrent appends may lose data due to\n * read-then-write TOCTOU. The Sprites API does not expose an append primitive.\n */\n async appendFile(filePath: string, content: string): Promise<void> {\n let existing = \"\";\n try {\n existing = await this.readFile(filePath);\n } catch {\n // file may not exist yet\n }\n await this.writeFile(filePath, existing + content);\n }\n\n async deleteFile(\n filePath: string,\n opts?: { recursive?: boolean },\n ): Promise<void> {\n const url = this.fsUrl(\"/remove\");\n const res = await fetch(url, {\n method: \"POST\",\n headers: {\n ...this.headers(),\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({\n path: this.resolvePath(filePath),\n recursive: opts?.recursive ?? false,\n }),\n });\n if (!res.ok) {\n throw new Error(\n `SpritesFs deleteFile failed (${res.status}): ${await res.text()}`,\n );\n }\n }\n\n async mkdir(\n dirPath: string,\n opts?: { recursive?: boolean },\n ): Promise<void> {\n const url = this.fsUrl(\"/mkdir\");\n const res = await fetch(url, {\n method: \"POST\",\n headers: {\n ...this.headers(),\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({\n path: this.resolvePath(dirPath),\n recursive: opts?.recursive ?? false,\n }),\n });\n if (!res.ok) {\n throw new Error(\n `SpritesFs mkdir failed (${res.status}): ${await res.text()}`,\n );\n }\n }\n\n async readdir(\n dirPath: string,\n _opts?: { recursive?: boolean },\n ): Promise<FileEntry[]> {\n const url = this.fsUrl(\"/readdir\", { path: this.resolvePath(dirPath) });\n const res = await fetch(url, { headers: this.headers() });\n if (!res.ok) {\n throw new Error(\n `SpritesFs readdir failed (${res.status}): ${await res.text()}`,\n );\n }\n const data = (await res.json()) as Array<{\n name: string;\n path: string;\n is_dir: boolean;\n size?: number;\n }>;\n return data.map((entry) => ({\n name: entry.name,\n path: entry.path,\n isDirectory: entry.is_dir,\n isFile: !entry.is_dir,\n size: entry.size,\n }));\n }\n\n async exists(filePath: string): Promise<boolean> {\n try {\n await this.stat(filePath);\n return true;\n } catch {\n return false;\n }\n }\n\n async stat(filePath: string): Promise<FileStat> {\n const url = this.fsUrl(\"/stat\", { path: this.resolvePath(filePath) });\n const res = await fetch(url, { headers: this.headers() });\n if (!res.ok) {\n throw new Error(\n `SpritesFs stat failed (${res.status}): ${await res.text()}`,\n );\n }\n const data = (await res.json()) as {\n size: number;\n is_dir: boolean;\n created_at?: string;\n modified_at?: string;\n };\n return {\n size: data.size,\n isDirectory: data.is_dir,\n isFile: !data.is_dir,\n createdAt: data.created_at ? new Date(data.created_at) : undefined,\n modifiedAt: data.modified_at ? new Date(data.modified_at) : undefined,\n };\n }\n}\n","import type {\n VirtualComputer,\n ExecOptions,\n CommandResult,\n} from \"./computer.js\";\n\nexport interface SpritesComputerOptions {\n /** sprites.dev API token */\n token: string;\n /** Name of the sprite container */\n spriteName: string;\n /** Base URL for sprites API (default: https://api.sprites.dev) */\n baseURL?: string;\n /** Working directory inside the sprite (default: /home/sprite) */\n workingDir?: string;\n}\n\n/**\n * Sandboxed VirtualComputer that executes commands inside a remote\n * sprites.dev container. All shell execution is fully isolated — the agent\n * has no access to the host machine's processes, filesystem, or network.\n *\n * This is the recommended VirtualComputer for production deployments and\n * untrusted agents. See `LocalComputer` for an unsandboxed local alternative.\n *\n * Uses the non-interactive exec REST endpoint (POST command, receive\n * stdout/stderr/exit_code). The WebSocket exec endpoint can be used for\n * streaming/TTY use cases, but REST is sufficient for tool calls.\n */\nexport class SpritesComputer implements VirtualComputer {\n private token: string;\n private spriteName: string;\n private baseURL: string;\n private workingDir: string;\n\n constructor(opts: SpritesComputerOptions) {\n this.token = opts.token;\n this.spriteName = opts.spriteName;\n this.baseURL = (opts.baseURL ?? \"https://api.sprites.dev\").replace(\n /\\/$/,\n \"\",\n );\n this.workingDir = opts.workingDir ?? \"/home/sprite\";\n }\n\n private headers(): Record<string, string> {\n return {\n Authorization: `Bearer ${this.token}`,\n \"Content-Type\": \"application/json\",\n };\n }\n\n async executeCommand(\n command: string,\n opts?: ExecOptions,\n ): Promise<CommandResult> {\n const cwd = opts?.cwd ?? this.workingDir;\n const wrappedCommand = `cd ${this.shellEscape(cwd)} && ${command}`;\n\n const url = `${this.baseURL}/v1/sprites/${this.spriteName}/exec`;\n\n const res = await fetch(url, {\n method: \"POST\",\n headers: this.headers(),\n body: JSON.stringify({\n command: [\"bash\", \"-c\", wrappedCommand],\n timeout: opts?.timeout ?? 30_000,\n env: opts?.env,\n }),\n });\n\n if (!res.ok) {\n const text = await res.text();\n return {\n exitCode: 1,\n stdout: \"\",\n stderr: `Sprites exec failed (${res.status}): ${text}`,\n };\n }\n\n const data = (await res.json()) as {\n exit_code: number;\n stdout: string;\n stderr: string;\n };\n\n return {\n exitCode: data.exit_code,\n stdout: data.stdout ?? \"\",\n stderr: data.stderr ?? \"\",\n };\n }\n\n private shellEscape(s: string): string {\n return `'${s.replace(/'/g, \"'\\\\''\")}'`;\n }\n}\n","import type { Sandbox } from \"./sandbox.js\";\nimport { SpritesFs } from \"./sprites-fs.js\";\nimport { SpritesComputer } from \"./sprites-computer.js\";\nimport { createFsProxy, createComputerProxy } from \"./proxy.js\";\n\nexport interface SpritesSandboxOptions {\n /** sprites.dev API token. */\n token: string;\n /**\n * Name of an existing sprite container. When provided the sandbox\n * attaches to this sprite directly — no auto-creation occurs and\n * `dispose()` will **not** delete it (lifecycle is yours to manage).\n *\n * When omitted a new sprite is provisioned on the first `init()` call\n * (via `POST /v1/sprites`). The auto-created sprite is deleted when\n * `dispose()` is called, and its name is available via `sandboxId()`\n * for session persistence.\n */\n spriteName?: string;\n /** Base URL for sprites API (default: https://api.sprites.dev). */\n baseURL?: string;\n /** Working directory inside the sprite (default: /home/sprite). */\n workingDir?: string;\n /**\n * Optional prefix for auto-generated sprite names (default: \"noumen-\").\n * Only used when `spriteName` is omitted.\n */\n namePrefix?: string;\n}\n\n/**\n * Create a `Sandbox` backed by a remote sprites.dev container.\n * Full isolation — the agent has no access to the host machine.\n *\n * **Auto-creation:** When `spriteName` is omitted the sandbox is created\n * lazily on the first `init()` call via the Sprites REST API. The sprite\n * name is available through `sandboxId()` so callers can persist it in\n * session metadata for reconnection on resume. Pass the stored name back\n * through `init(storedId)` to reattach instead of creating a new sprite.\n *\n * **Explicit ID:** When `spriteName` is provided the sandbox attaches to\n * that sprite immediately on `init()`. `dispose()` is a no-op in this\n * case — the caller owns the sprite's lifecycle.\n *\n * @example\n * ```ts\n * // Auto-create — sprite provisioned on first init()\n * const sandbox = SpritesSandbox({ token: process.env.SPRITES_TOKEN! });\n *\n * // Explicit — attach to pre-existing sprite, no auto-lifecycle\n * const sandbox = SpritesSandbox({\n * token: process.env.SPRITES_TOKEN!,\n * spriteName: \"my-sprite\",\n * });\n * ```\n */\nexport function SpritesSandbox(opts: SpritesSandboxOptions): Sandbox {\n const baseURL = (opts.baseURL ?? \"https://api.sprites.dev\").replace(/\\/$/, \"\");\n const userProvidedName = opts.spriteName;\n\n if (userProvidedName) {\n const fsOpts = { ...opts, spriteName: userProvidedName };\n return {\n fs: new SpritesFs(fsOpts),\n computer: new SpritesComputer(fsOpts),\n sandboxId: () => userProvidedName,\n };\n }\n\n const fsProxy = createFsProxy();\n const computerProxy = createComputerProxy();\n let resolvedName: string | undefined;\n let autoCreated = false;\n let initPromise: Promise<void> | null = null;\n\n async function doInit(reconnectId?: string): Promise<void> {\n let name = reconnectId ?? `${opts.namePrefix ?? \"noumen-\"}${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;\n let needsCreate = !reconnectId;\n\n if (reconnectId) {\n const check = await fetch(`${baseURL}/v1/sprites/${reconnectId}`, {\n method: \"GET\",\n headers: { Authorization: `Bearer ${opts.token}` },\n });\n if (!check.ok) {\n name = `${opts.namePrefix ?? \"noumen-\"}${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;\n needsCreate = true;\n }\n }\n\n if (needsCreate) {\n const res = await fetch(`${baseURL}/v1/sprites`, {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${opts.token}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({ name }),\n });\n if (!res.ok) {\n throw new Error(`Sprites auto-create failed (${res.status}): ${await res.text()}`);\n }\n autoCreated = true;\n }\n\n resolvedName = name;\n const childOpts = { ...opts, spriteName: name };\n fsProxy.setTarget(new SpritesFs(childOpts));\n computerProxy.setTarget(new SpritesComputer(childOpts));\n }\n\n return {\n fs: fsProxy,\n computer: computerProxy,\n sandboxId: () => resolvedName,\n\n init(sandboxId?: string): Promise<void> {\n if (!initPromise) {\n initPromise = doInit(sandboxId).catch((err) => {\n initPromise = null;\n throw err;\n });\n }\n return initPromise;\n },\n\n async dispose(): Promise<void> {\n if (initPromise) {\n await initPromise.catch(() => {});\n }\n if (!autoCreated || !resolvedName) return;\n try {\n const res = await fetch(`${baseURL}/v1/sprites/${resolvedName}`, {\n method: \"DELETE\",\n headers: { Authorization: `Bearer ${opts.token}` },\n });\n if (!res.ok && res.status !== 404) {\n throw new Error(`Sprites dispose failed (${res.status}): ${await res.text()}`);\n }\n } catch {\n // Best-effort cleanup — network errors during dispose are non-fatal\n }\n },\n };\n}\n"],"mappings":"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","names":[]}
package/dist/ssh.d.ts ADDED
@@ -0,0 +1,187 @@
1
+ import { S as Sandbox } from './sandbox-9qeMTNrD.js';
2
+ import { V as VirtualComputer, E as ExecOptions, C as CommandResult, a as VirtualFs, R as ReadOptions, F as FileEntry, b as FileStat } from './computer-BPdxSo6X.js';
3
+
4
+ /**
5
+ * Minimal subset of the ssh2 Channel interface used by SshComputer / SshFs.
6
+ * Avoids a hard import of ssh2 at the module level.
7
+ */
8
+ interface SshChannel {
9
+ on(event: string, listener: (...args: any[]) => void): SshChannel;
10
+ stderr: {
11
+ on(event: string, listener: (...args: any[]) => void): unknown;
12
+ };
13
+ destroy?(): void;
14
+ }
15
+ /**
16
+ * Minimal subset of the ssh2 SFTPWrapper interface used by SshFs.
17
+ */
18
+ interface SshSftpSession {
19
+ readFile(path: string, callback: (err: Error | undefined, data: Buffer) => void): void;
20
+ readFile(path: string, opts: {
21
+ encoding: string;
22
+ }, callback: (err: Error | undefined, data: string) => void): void;
23
+ writeFile(path: string, data: string | Buffer, callback: (err: Error | undefined) => void): void;
24
+ appendFile(path: string, data: string | Buffer, callback: (err: Error | undefined) => void): void;
25
+ unlink(path: string, callback: (err: Error | undefined) => void): void;
26
+ rmdir(path: string, callback: (err: Error | undefined) => void): void;
27
+ mkdir(path: string, callback: (err: Error | undefined) => void): void;
28
+ readdir(path: string, callback: (err: Error | undefined, list: Array<{
29
+ filename: string;
30
+ longname: string;
31
+ attrs: {
32
+ size: number;
33
+ isDirectory(): boolean;
34
+ isFile(): boolean;
35
+ mtime: number;
36
+ atime: number;
37
+ };
38
+ }>) => void): void;
39
+ stat(path: string, callback: (err: Error | undefined, stats: {
40
+ size: number;
41
+ isDirectory(): boolean;
42
+ isFile(): boolean;
43
+ mtime: number;
44
+ atime: number;
45
+ }) => void): void;
46
+ open(path: string, flags: string, callback: (err: Error | undefined, handle: Buffer) => void): void;
47
+ read(handle: Buffer, buffer: Buffer, offset: number, length: number, position: number, callback: (err: Error | undefined, bytesRead: number, buf: Buffer) => void): void;
48
+ close(handle: Buffer, callback: (err: Error | undefined) => void): void;
49
+ end(): void;
50
+ }
51
+ /**
52
+ * Minimal subset of the ssh2 Client interface used by SshComputer and SshFs.
53
+ * Avoids a hard import of ssh2 at the module level.
54
+ */
55
+ interface SshClient {
56
+ exec(command: string, callback: (err: Error | undefined, channel: SshChannel) => void): void;
57
+ exec(command: string, opts: Record<string, unknown>, callback: (err: Error | undefined, channel: SshChannel) => void): void;
58
+ sftp(callback: (err: Error | undefined, sftp: SshSftpSession) => void): void;
59
+ end(): void;
60
+ on(event: string, listener: (...args: any[]) => void): this;
61
+ }
62
+ interface SshComputerOptions {
63
+ /** A connected ssh2 Client instance. */
64
+ client: SshClient;
65
+ /** Default working directory for commands (default: /). */
66
+ defaultCwd?: string;
67
+ /** Default timeout in ms for commands (default: 30000). */
68
+ defaultTimeout?: number;
69
+ }
70
+ /**
71
+ * VirtualComputer backed by command execution over SSH.
72
+ *
73
+ * Requires `ssh2` as an optional peer dependency.
74
+ * The caller is responsible for the Client lifecycle (connect, end).
75
+ */
76
+ declare class SshComputer implements VirtualComputer {
77
+ private client;
78
+ private defaultCwd;
79
+ private defaultTimeout;
80
+ constructor(opts: SshComputerOptions);
81
+ executeCommand(command: string, opts?: ExecOptions): Promise<CommandResult>;
82
+ }
83
+
84
+ interface SshSandboxOptions {
85
+ /**
86
+ * A pre-connected ssh2 Client instance. When provided the sandbox uses
87
+ * this client directly — no auto-connect occurs and `dispose()` will
88
+ * **not** call `client.end()`.
89
+ *
90
+ * When omitted, a new ssh2 Client is created and connected on the first
91
+ * `init()` call using `host`, `port`, `username`, and the provided
92
+ * credentials. The client is ended when `dispose()` is called.
93
+ */
94
+ client?: SshClient;
95
+ /**
96
+ * SSH hostname. Required when `client` is omitted; ignored when `client`
97
+ * is provided.
98
+ */
99
+ host?: string;
100
+ /** SSH port (default: 22). Only used during auto-connect. */
101
+ port?: number;
102
+ /** SSH username (default: "root"). Only used during auto-connect. */
103
+ username?: string;
104
+ /** Password for password-based authentication. Only used during auto-connect. */
105
+ password?: string;
106
+ /** Private key for key-based authentication (PEM string or Buffer). Only used during auto-connect. */
107
+ privateKey?: string | Buffer;
108
+ /** Passphrase for an encrypted private key. Only used during auto-connect. */
109
+ passphrase?: string;
110
+ /** Working directory on the remote host. */
111
+ cwd?: string;
112
+ /** Default timeout (ms) for shell commands. */
113
+ defaultTimeout?: number;
114
+ }
115
+ /**
116
+ * Create a `Sandbox` backed by a remote host over SSH.
117
+ * Requires `ssh2` as an optional peer dependency.
118
+ *
119
+ * **Auto-connect:** When `client` is omitted and `host` is provided,
120
+ * an ssh2 Client is created and connected lazily on the first `init()`
121
+ * call. The connection identifier (`host:port`) is available through
122
+ * `sandboxId()` for session persistence.
123
+ *
124
+ * **Explicit client:** When `client` is provided, `init()` binds it
125
+ * immediately. `dispose()` is a no-op — the caller owns the client's
126
+ * lifecycle.
127
+ *
128
+ * @example
129
+ * ```ts
130
+ * // Auto-connect with private key
131
+ * const sandbox = SshSandbox({
132
+ * host: "dev.example.com",
133
+ * username: "deploy",
134
+ * privateKey: fs.readFileSync("~/.ssh/id_ed25519"),
135
+ * cwd: "/home/deploy/project",
136
+ * });
137
+ *
138
+ * // Explicit client (lifecycle managed externally)
139
+ * const sandbox = SshSandbox({ client: myConnectedClient, cwd: "/workspace" });
140
+ * ```
141
+ */
142
+ declare function SshSandbox(opts: SshSandboxOptions): Sandbox;
143
+
144
+ interface SshFsOptions {
145
+ /** A connected ssh2 Client instance. */
146
+ client: SshClient;
147
+ /** Working directory for relative path resolution (default: /). */
148
+ workingDir?: string;
149
+ }
150
+ /**
151
+ * VirtualFs backed by SFTP file operations over SSH.
152
+ *
153
+ * Uses the ssh2 Client's SFTP subsystem for all file I/O. The SFTP session
154
+ * is opened lazily on the first operation and reused for the lifetime of
155
+ * the SshFs instance.
156
+ *
157
+ * Requires `ssh2` as an optional peer dependency.
158
+ * The caller is responsible for the Client lifecycle.
159
+ */
160
+ declare class SshFs implements VirtualFs {
161
+ private client;
162
+ private workingDir;
163
+ private sftpSession;
164
+ private sftpPromise;
165
+ constructor(opts: SshFsOptions);
166
+ private getSftp;
167
+ private resolvePath;
168
+ readFile(path: string, _opts?: ReadOptions): Promise<string>;
169
+ readFileBytes(path: string, maxBytes?: number): Promise<Buffer>;
170
+ writeFile(path: string, content: string): Promise<void>;
171
+ appendFile(path: string, content: string): Promise<void>;
172
+ deleteFile(path: string, opts?: {
173
+ recursive?: boolean;
174
+ }): Promise<void>;
175
+ mkdir(path: string, opts?: {
176
+ recursive?: boolean;
177
+ }): Promise<void>;
178
+ readdir(path: string, _opts?: {
179
+ recursive?: boolean;
180
+ }): Promise<FileEntry[]>;
181
+ exists(path: string): Promise<boolean>;
182
+ stat(path: string): Promise<FileStat>;
183
+ private mkdirRecursive;
184
+ private rmRecursive;
185
+ }
186
+
187
+ export { type SshChannel, type SshClient, SshComputer, type SshComputerOptions, SshFs, type SshFsOptions, SshSandbox, type SshSandboxOptions, type SshSftpSession };