agent-neckbeard 1.0.3 → 1.0.4

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.

Potentially problematic release.


This version of agent-neckbeard might be problematic. Click here for more details.

package/dist/index.cjs CHANGED
@@ -31,7 +31,12 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
33
  Agent: () => Agent,
34
- DEFAULT_DEPENDENCIES: () => DEFAULT_DEPENDENCIES
34
+ ConfigurationError: () => ConfigurationError,
35
+ DEFAULT_DEPENDENCIES: () => DEFAULT_DEPENDENCIES,
36
+ DeploymentError: () => DeploymentError,
37
+ ExecutionError: () => ExecutionError,
38
+ NeckbeardError: () => NeckbeardError,
39
+ ValidationError: () => ValidationError
35
40
  });
36
41
  module.exports = __toCommonJS(index_exports);
37
42
  var import_node_url = require("url");
@@ -39,7 +44,67 @@ var import_node_fs = require("fs");
39
44
  var import_node_path = require("path");
40
45
  var getEsbuild = () => import("esbuild");
41
46
  var getE2b = () => import("e2b");
47
+ var DEFAULT_MAX_DURATION = 300;
48
+ var SANDBOX_COMMAND_TIMEOUT_MS = 3e5;
49
+ var QUICK_COMMAND_TIMEOUT_MS = 3e4;
50
+ var NODE_TARGET = "node20";
51
+ var SANDBOX_HOME = "/home/user";
52
+ var SANDBOX_PATHS = {
53
+ input: `${SANDBOX_HOME}/input`,
54
+ output: `${SANDBOX_HOME}/output`,
55
+ taskJson: `${SANDBOX_HOME}/input/task.json`,
56
+ resultJson: `${SANDBOX_HOME}/output/result.json`,
57
+ agentModule: `${SANDBOX_HOME}/agent.mjs`,
58
+ runnerModule: `${SANDBOX_HOME}/runner.mjs`,
59
+ claudeDir: `${SANDBOX_HOME}/.claude`,
60
+ packageJson: `${SANDBOX_HOME}/package.json`
61
+ };
62
+ var NeckbeardError = class extends Error {
63
+ constructor(message) {
64
+ super(message);
65
+ this.name = "NeckbeardError";
66
+ }
67
+ };
68
+ var DeploymentError = class extends NeckbeardError {
69
+ constructor(message, cause) {
70
+ super(message);
71
+ this.cause = cause;
72
+ this.name = "DeploymentError";
73
+ }
74
+ };
75
+ var ValidationError = class extends NeckbeardError {
76
+ constructor(message, cause) {
77
+ super(message);
78
+ this.cause = cause;
79
+ this.name = "ValidationError";
80
+ }
81
+ };
82
+ var ExecutionError = class extends NeckbeardError {
83
+ constructor(message, cause) {
84
+ super(message);
85
+ this.cause = cause;
86
+ this.name = "ExecutionError";
87
+ }
88
+ };
89
+ var ConfigurationError = class extends NeckbeardError {
90
+ constructor(message) {
91
+ super(message);
92
+ this.name = "ConfigurationError";
93
+ }
94
+ };
42
95
  var DEFAULT_DEPENDENCIES = {};
96
+ function requireEnv(name) {
97
+ const value = process.env[name];
98
+ if (!value) {
99
+ throw new ConfigurationError(
100
+ `${name} environment variable is required. Please set it before calling deploy() or run().`
101
+ );
102
+ }
103
+ return value;
104
+ }
105
+ function shellEscape(str) {
106
+ return `'${str.replace(/'/g, "'\\''")}'`;
107
+ }
43
108
  function getCallerFile() {
44
109
  const stack = new Error().stack?.split("\n") ?? [];
45
110
  for (const line of stack.slice(2)) {
@@ -93,7 +158,7 @@ var Agent = class {
93
158
  this.id = config.id;
94
159
  this.inputSchema = config.inputSchema;
95
160
  this.outputSchema = config.outputSchema;
96
- this.maxDuration = config.maxDuration ?? 300;
161
+ this.maxDuration = config.maxDuration ?? DEFAULT_MAX_DURATION;
97
162
  this._run = config.run;
98
163
  this._sourceFile = getCallerFile();
99
164
  this._sandboxId = config.sandboxId;
@@ -104,8 +169,29 @@ var Agent = class {
104
169
  get sandboxId() {
105
170
  return this._sandboxId;
106
171
  }
172
+ /**
173
+ * Deploys the agent to an E2B sandbox.
174
+ *
175
+ * This method bundles the agent code using esbuild, creates a new E2B sandbox,
176
+ * installs any specified OS dependencies, downloads configured files, and
177
+ * uploads the agent code to the sandbox.
178
+ *
179
+ * The sandbox ID is stored and can be accessed via the `sandboxId` property
180
+ * after deployment completes.
181
+ *
182
+ * @throws {ConfigurationError} If E2B_API_KEY environment variable is not set
183
+ * @throws {DeploymentError} If sandbox creation, dependency installation, or file upload fails
184
+ *
185
+ * @example
186
+ * ```typescript
187
+ * const agent = new Agent({ ... });
188
+ * await agent.deploy();
189
+ * console.log(`Deployed to sandbox: ${agent.sandboxId}`);
190
+ * ```
191
+ */
107
192
  async deploy() {
108
193
  if (this._sandboxId) return;
194
+ const e2bApiKey = requireEnv("E2B_API_KEY");
109
195
  const esbuild = await getEsbuild();
110
196
  const { Sandbox } = await getE2b();
111
197
  const collectedExternals = /* @__PURE__ */ new Set();
@@ -117,13 +203,20 @@ var Agent = class {
117
203
  entryPoints: [this._sourceFile],
118
204
  bundle: true,
119
205
  platform: "node",
120
- target: "node20",
206
+ target: NODE_TARGET,
121
207
  format: "esm",
122
208
  write: false,
123
209
  minify: true,
124
210
  keepNames: true,
125
211
  treeShaking: false,
126
212
  // Preserve exports for the sandbox runner to import
213
+ banner: {
214
+ js: `import { fileURLToPath as __neckbeard_fileURLToPath } from 'node:url';
215
+ import { dirname as __neckbeard_dirname } from 'node:path';
216
+ const __filename = __neckbeard_fileURLToPath(import.meta.url);
217
+ const __dirname = __neckbeard_dirname(__filename);
218
+ `
219
+ },
127
220
  plugins: [{
128
221
  name: "agent-neckbeard-externals",
129
222
  setup(build) {
@@ -196,52 +289,52 @@ try {
196
289
  }
197
290
  `;
198
291
  const sandbox = await Sandbox.create("base", {
199
- apiKey: process.env.E2B_API_KEY
292
+ apiKey: e2bApiKey
200
293
  });
201
294
  const { apt, commands } = this.dependencies;
202
295
  if (apt && apt.length > 0) {
203
296
  const aptCmd = `sudo apt-get update && sudo apt-get install -y ${apt.join(" ")}`;
204
- const aptResult = await sandbox.commands.run(aptCmd, { timeoutMs: 3e5 });
297
+ const aptResult = await sandbox.commands.run(aptCmd, { timeoutMs: SANDBOX_COMMAND_TIMEOUT_MS });
205
298
  if (aptResult.exitCode !== 0) {
206
- throw new Error(`Failed to install apt packages: ${aptResult.stderr}`);
299
+ throw new DeploymentError(`Failed to install apt packages: ${aptResult.stderr}`);
207
300
  }
208
301
  }
209
302
  if (commands && commands.length > 0) {
210
303
  for (const cmd of commands) {
211
- const cmdResult = await sandbox.commands.run(cmd, { timeoutMs: 3e5 });
304
+ const cmdResult = await sandbox.commands.run(cmd, { timeoutMs: SANDBOX_COMMAND_TIMEOUT_MS });
212
305
  if (cmdResult.exitCode !== 0) {
213
- throw new Error(`Failed to run command "${cmd}": ${cmdResult.stderr}`);
306
+ throw new DeploymentError(`Failed to run command "${cmd}": ${cmdResult.stderr}`);
214
307
  }
215
308
  }
216
309
  }
217
310
  if (this.files.length > 0) {
218
311
  for (const file of this.files) {
219
- const destPath = file.path.startsWith("/") ? file.path : `/home/user/${file.path}`;
312
+ const destPath = file.path.startsWith("/") ? file.path : `${SANDBOX_HOME}/${file.path}`;
220
313
  const parentDir = destPath.substring(0, destPath.lastIndexOf("/"));
221
314
  if (parentDir) {
222
- await sandbox.commands.run(`mkdir -p "${parentDir}"`, { timeoutMs: 3e4 });
315
+ await sandbox.commands.run(`mkdir -p ${shellEscape(parentDir)}`, { timeoutMs: QUICK_COMMAND_TIMEOUT_MS });
223
316
  }
224
- const curlCmd = `curl -fsSL -o "${destPath}" "${file.url}"`;
225
- const downloadResult = await sandbox.commands.run(curlCmd, { timeoutMs: 3e5 });
317
+ const curlCmd = `curl -fsSL -o ${shellEscape(destPath)} ${shellEscape(file.url)}`;
318
+ const downloadResult = await sandbox.commands.run(curlCmd, { timeoutMs: SANDBOX_COMMAND_TIMEOUT_MS });
226
319
  if (downloadResult.exitCode !== 0) {
227
- throw new Error(`Failed to download file from ${file.url} to ${destPath}: ${downloadResult.stderr}`);
320
+ throw new DeploymentError(`Failed to download file from ${file.url} to ${destPath}: ${downloadResult.stderr}`);
228
321
  }
229
322
  }
230
323
  }
231
324
  if (this.claudeDir) {
232
325
  const claudeFiles = readDirectoryRecursively(this.claudeDir);
233
- await sandbox.commands.run("mkdir -p /home/user/.claude", { timeoutMs: 3e4 });
326
+ await sandbox.commands.run(`mkdir -p ${SANDBOX_PATHS.claudeDir}`, { timeoutMs: QUICK_COMMAND_TIMEOUT_MS });
234
327
  for (const file of claudeFiles) {
235
- const destPath = `/home/user/.claude/${file.relativePath}`;
328
+ const destPath = `${SANDBOX_PATHS.claudeDir}/${file.relativePath}`;
236
329
  const parentDir = destPath.substring(0, destPath.lastIndexOf("/"));
237
- if (parentDir && parentDir !== "/home/user/.claude") {
238
- await sandbox.commands.run(`mkdir -p "${parentDir}"`, { timeoutMs: 3e4 });
330
+ if (parentDir && parentDir !== SANDBOX_PATHS.claudeDir) {
331
+ await sandbox.commands.run(`mkdir -p ${shellEscape(parentDir)}`, { timeoutMs: QUICK_COMMAND_TIMEOUT_MS });
239
332
  }
240
333
  await sandbox.files.write(destPath, file.content);
241
334
  }
242
335
  }
243
- await sandbox.files.write("/home/user/agent.mjs", result.outputFiles[0].text);
244
- await sandbox.files.write("/home/user/runner.mjs", runnerCode);
336
+ await sandbox.files.write(SANDBOX_PATHS.agentModule, result.outputFiles[0].text);
337
+ await sandbox.files.write(SANDBOX_PATHS.runnerModule, runnerCode);
245
338
  if (collectedExternals.size > 0) {
246
339
  const dependencies = {};
247
340
  for (const pkg of collectedExternals) {
@@ -252,42 +345,64 @@ try {
252
345
  type: "module",
253
346
  dependencies
254
347
  });
255
- await sandbox.files.write("/home/user/package.json", pkgJson);
256
- const installResult = await sandbox.commands.run("cd /home/user && npm install", { timeoutMs: 3e5 });
348
+ await sandbox.files.write(SANDBOX_PATHS.packageJson, pkgJson);
349
+ const installResult = await sandbox.commands.run(`cd ${SANDBOX_HOME} && npm install`, { timeoutMs: SANDBOX_COMMAND_TIMEOUT_MS });
257
350
  if (installResult.exitCode !== 0) {
258
- throw new Error(`Failed to install external packages: ${installResult.stderr}`);
351
+ throw new DeploymentError(`Failed to install external packages: ${installResult.stderr}`);
259
352
  }
260
353
  }
261
354
  this._sandboxId = sandbox.sandboxId;
262
355
  }
356
+ /**
357
+ * Executes the agent with the given input.
358
+ *
359
+ * The agent must be deployed before calling this method. Input is validated
360
+ * against the input schema, then the agent runs in the sandbox with the
361
+ * validated input. Output is validated against the output schema before
362
+ * being returned.
363
+ *
364
+ * @param input - The input data for the agent, must conform to inputSchema
365
+ * @returns A result object indicating success or failure with output or error
366
+ *
367
+ * @example
368
+ * ```typescript
369
+ * const result = await agent.run({ prompt: 'Hello, world!' });
370
+ * if (result.ok) {
371
+ * console.log('Output:', result.output);
372
+ * } else {
373
+ * console.error('Error:', result.error.message);
374
+ * }
375
+ * ```
376
+ */
263
377
  async run(input) {
264
378
  const executionId = `exec_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
265
379
  if (!this._sandboxId) {
266
380
  return {
267
381
  ok: false,
268
382
  executionId,
269
- error: new Error("Agent not deployed. Call agent.deploy() first or pass sandboxId to constructor.")
383
+ error: new ExecutionError("Agent not deployed. Call agent.deploy() first or pass sandboxId to constructor.")
270
384
  };
271
385
  }
272
386
  try {
387
+ const e2bApiKey = requireEnv("E2B_API_KEY");
273
388
  const { Sandbox } = await getE2b();
274
389
  const validatedInput = this.inputSchema.parse(input);
275
390
  const sandbox = await Sandbox.connect(this._sandboxId, {
276
- apiKey: process.env.E2B_API_KEY
391
+ apiKey: e2bApiKey
277
392
  });
278
- await sandbox.files.write("/home/user/input/task.json", JSON.stringify({ input: validatedInput, executionId }));
279
- const result = await sandbox.commands.run("node /home/user/runner.mjs", {
393
+ await sandbox.files.write(SANDBOX_PATHS.taskJson, JSON.stringify({ input: validatedInput, executionId }));
394
+ const result = await sandbox.commands.run(`node ${SANDBOX_PATHS.runnerModule}`, {
280
395
  timeoutMs: this.maxDuration * 1e3,
281
396
  envs: {
282
397
  ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY ?? ""
283
398
  }
284
399
  });
285
400
  if (result.exitCode !== 0) {
286
- return { ok: false, executionId, error: new Error(`Agent failed: ${result.stderr}`) };
401
+ return { ok: false, executionId, error: new ExecutionError(`Agent failed: ${result.stderr}`) };
287
402
  }
288
- const output = JSON.parse(await sandbox.files.read("/home/user/output/result.json"));
403
+ const output = JSON.parse(await sandbox.files.read(SANDBOX_PATHS.resultJson));
289
404
  if (!output.success) {
290
- const err = new Error(output.error.message);
405
+ const err = new ExecutionError(output.error.message);
291
406
  err.stack = output.error.stack;
292
407
  return { ok: false, executionId, error: err };
293
408
  }
@@ -300,5 +415,10 @@ try {
300
415
  // Annotate the CommonJS export names for ESM import in node:
301
416
  0 && (module.exports = {
302
417
  Agent,
303
- DEFAULT_DEPENDENCIES
418
+ ConfigurationError,
419
+ DEFAULT_DEPENDENCIES,
420
+ DeploymentError,
421
+ ExecutionError,
422
+ NeckbeardError,
423
+ ValidationError
304
424
  });
package/dist/index.d.cts CHANGED
@@ -1,6 +1,41 @@
1
1
  /**
2
2
  * agent-neckbeard - Deploy AI agents to E2B sandboxes
3
3
  */
4
+ /**
5
+ * Base error class for agent-neckbeard errors.
6
+ * All custom errors extend this class.
7
+ */
8
+ declare class NeckbeardError extends Error {
9
+ constructor(message: string);
10
+ }
11
+ /**
12
+ * Error thrown when sandbox deployment fails.
13
+ * This includes sandbox creation, dependency installation, and file upload failures.
14
+ */
15
+ declare class DeploymentError extends NeckbeardError {
16
+ readonly cause?: Error | undefined;
17
+ constructor(message: string, cause?: Error | undefined);
18
+ }
19
+ /**
20
+ * Error thrown when input or output schema validation fails.
21
+ */
22
+ declare class ValidationError extends NeckbeardError {
23
+ readonly cause?: Error | undefined;
24
+ constructor(message: string, cause?: Error | undefined);
25
+ }
26
+ /**
27
+ * Error thrown when agent execution fails in the sandbox.
28
+ */
29
+ declare class ExecutionError extends NeckbeardError {
30
+ readonly cause?: Error | undefined;
31
+ constructor(message: string, cause?: Error | undefined);
32
+ }
33
+ /**
34
+ * Error thrown when required environment variables are missing.
35
+ */
36
+ declare class ConfigurationError extends NeckbeardError {
37
+ constructor(message: string);
38
+ }
4
39
  interface AgentRunContext {
5
40
  executionId: string;
6
41
  signal: AbortSignal;
@@ -19,7 +54,7 @@ type AgentRunResult<TOutput> = {
19
54
  } | {
20
55
  ok: false;
21
56
  executionId: string;
22
- error: Error;
57
+ error: Error | NeckbeardError;
23
58
  };
24
59
  interface SchemaLike<T> {
25
60
  parse: (data: unknown) => T;
@@ -91,8 +126,49 @@ declare class Agent<TInput, TOutput> {
91
126
  private _sandboxId?;
92
127
  constructor(config: AgentConfig<TInput, TOutput>);
93
128
  get sandboxId(): string | undefined;
129
+ /**
130
+ * Deploys the agent to an E2B sandbox.
131
+ *
132
+ * This method bundles the agent code using esbuild, creates a new E2B sandbox,
133
+ * installs any specified OS dependencies, downloads configured files, and
134
+ * uploads the agent code to the sandbox.
135
+ *
136
+ * The sandbox ID is stored and can be accessed via the `sandboxId` property
137
+ * after deployment completes.
138
+ *
139
+ * @throws {ConfigurationError} If E2B_API_KEY environment variable is not set
140
+ * @throws {DeploymentError} If sandbox creation, dependency installation, or file upload fails
141
+ *
142
+ * @example
143
+ * ```typescript
144
+ * const agent = new Agent({ ... });
145
+ * await agent.deploy();
146
+ * console.log(`Deployed to sandbox: ${agent.sandboxId}`);
147
+ * ```
148
+ */
94
149
  deploy(): Promise<void>;
150
+ /**
151
+ * Executes the agent with the given input.
152
+ *
153
+ * The agent must be deployed before calling this method. Input is validated
154
+ * against the input schema, then the agent runs in the sandbox with the
155
+ * validated input. Output is validated against the output schema before
156
+ * being returned.
157
+ *
158
+ * @param input - The input data for the agent, must conform to inputSchema
159
+ * @returns A result object indicating success or failure with output or error
160
+ *
161
+ * @example
162
+ * ```typescript
163
+ * const result = await agent.run({ prompt: 'Hello, world!' });
164
+ * if (result.ok) {
165
+ * console.log('Output:', result.output);
166
+ * } else {
167
+ * console.error('Error:', result.error.message);
168
+ * }
169
+ * ```
170
+ */
95
171
  run(input: TInput): Promise<AgentRunResult<TOutput>>;
96
172
  }
97
173
 
98
- export { Agent, type AgentConfig, type AgentRunContext, type AgentRunResult, DEFAULT_DEPENDENCIES, type FileDownload, type OsDependencies };
174
+ export { Agent, type AgentConfig, type AgentRunContext, type AgentRunResult, ConfigurationError, DEFAULT_DEPENDENCIES, DeploymentError, ExecutionError, type FileDownload, NeckbeardError, type OsDependencies, ValidationError };
package/dist/index.d.ts CHANGED
@@ -1,6 +1,41 @@
1
1
  /**
2
2
  * agent-neckbeard - Deploy AI agents to E2B sandboxes
3
3
  */
4
+ /**
5
+ * Base error class for agent-neckbeard errors.
6
+ * All custom errors extend this class.
7
+ */
8
+ declare class NeckbeardError extends Error {
9
+ constructor(message: string);
10
+ }
11
+ /**
12
+ * Error thrown when sandbox deployment fails.
13
+ * This includes sandbox creation, dependency installation, and file upload failures.
14
+ */
15
+ declare class DeploymentError extends NeckbeardError {
16
+ readonly cause?: Error | undefined;
17
+ constructor(message: string, cause?: Error | undefined);
18
+ }
19
+ /**
20
+ * Error thrown when input or output schema validation fails.
21
+ */
22
+ declare class ValidationError extends NeckbeardError {
23
+ readonly cause?: Error | undefined;
24
+ constructor(message: string, cause?: Error | undefined);
25
+ }
26
+ /**
27
+ * Error thrown when agent execution fails in the sandbox.
28
+ */
29
+ declare class ExecutionError extends NeckbeardError {
30
+ readonly cause?: Error | undefined;
31
+ constructor(message: string, cause?: Error | undefined);
32
+ }
33
+ /**
34
+ * Error thrown when required environment variables are missing.
35
+ */
36
+ declare class ConfigurationError extends NeckbeardError {
37
+ constructor(message: string);
38
+ }
4
39
  interface AgentRunContext {
5
40
  executionId: string;
6
41
  signal: AbortSignal;
@@ -19,7 +54,7 @@ type AgentRunResult<TOutput> = {
19
54
  } | {
20
55
  ok: false;
21
56
  executionId: string;
22
- error: Error;
57
+ error: Error | NeckbeardError;
23
58
  };
24
59
  interface SchemaLike<T> {
25
60
  parse: (data: unknown) => T;
@@ -91,8 +126,49 @@ declare class Agent<TInput, TOutput> {
91
126
  private _sandboxId?;
92
127
  constructor(config: AgentConfig<TInput, TOutput>);
93
128
  get sandboxId(): string | undefined;
129
+ /**
130
+ * Deploys the agent to an E2B sandbox.
131
+ *
132
+ * This method bundles the agent code using esbuild, creates a new E2B sandbox,
133
+ * installs any specified OS dependencies, downloads configured files, and
134
+ * uploads the agent code to the sandbox.
135
+ *
136
+ * The sandbox ID is stored and can be accessed via the `sandboxId` property
137
+ * after deployment completes.
138
+ *
139
+ * @throws {ConfigurationError} If E2B_API_KEY environment variable is not set
140
+ * @throws {DeploymentError} If sandbox creation, dependency installation, or file upload fails
141
+ *
142
+ * @example
143
+ * ```typescript
144
+ * const agent = new Agent({ ... });
145
+ * await agent.deploy();
146
+ * console.log(`Deployed to sandbox: ${agent.sandboxId}`);
147
+ * ```
148
+ */
94
149
  deploy(): Promise<void>;
150
+ /**
151
+ * Executes the agent with the given input.
152
+ *
153
+ * The agent must be deployed before calling this method. Input is validated
154
+ * against the input schema, then the agent runs in the sandbox with the
155
+ * validated input. Output is validated against the output schema before
156
+ * being returned.
157
+ *
158
+ * @param input - The input data for the agent, must conform to inputSchema
159
+ * @returns A result object indicating success or failure with output or error
160
+ *
161
+ * @example
162
+ * ```typescript
163
+ * const result = await agent.run({ prompt: 'Hello, world!' });
164
+ * if (result.ok) {
165
+ * console.log('Output:', result.output);
166
+ * } else {
167
+ * console.error('Error:', result.error.message);
168
+ * }
169
+ * ```
170
+ */
95
171
  run(input: TInput): Promise<AgentRunResult<TOutput>>;
96
172
  }
97
173
 
98
- export { Agent, type AgentConfig, type AgentRunContext, type AgentRunResult, DEFAULT_DEPENDENCIES, type FileDownload, type OsDependencies };
174
+ export { Agent, type AgentConfig, type AgentRunContext, type AgentRunResult, ConfigurationError, DEFAULT_DEPENDENCIES, DeploymentError, ExecutionError, type FileDownload, NeckbeardError, type OsDependencies, ValidationError };
package/dist/index.js CHANGED
@@ -4,7 +4,67 @@ import { readdirSync, readFileSync, statSync } from "fs";
4
4
  import { join, relative } from "path";
5
5
  var getEsbuild = () => import("esbuild");
6
6
  var getE2b = () => import("e2b");
7
+ var DEFAULT_MAX_DURATION = 300;
8
+ var SANDBOX_COMMAND_TIMEOUT_MS = 3e5;
9
+ var QUICK_COMMAND_TIMEOUT_MS = 3e4;
10
+ var NODE_TARGET = "node20";
11
+ var SANDBOX_HOME = "/home/user";
12
+ var SANDBOX_PATHS = {
13
+ input: `${SANDBOX_HOME}/input`,
14
+ output: `${SANDBOX_HOME}/output`,
15
+ taskJson: `${SANDBOX_HOME}/input/task.json`,
16
+ resultJson: `${SANDBOX_HOME}/output/result.json`,
17
+ agentModule: `${SANDBOX_HOME}/agent.mjs`,
18
+ runnerModule: `${SANDBOX_HOME}/runner.mjs`,
19
+ claudeDir: `${SANDBOX_HOME}/.claude`,
20
+ packageJson: `${SANDBOX_HOME}/package.json`
21
+ };
22
+ var NeckbeardError = class extends Error {
23
+ constructor(message) {
24
+ super(message);
25
+ this.name = "NeckbeardError";
26
+ }
27
+ };
28
+ var DeploymentError = class extends NeckbeardError {
29
+ constructor(message, cause) {
30
+ super(message);
31
+ this.cause = cause;
32
+ this.name = "DeploymentError";
33
+ }
34
+ };
35
+ var ValidationError = class extends NeckbeardError {
36
+ constructor(message, cause) {
37
+ super(message);
38
+ this.cause = cause;
39
+ this.name = "ValidationError";
40
+ }
41
+ };
42
+ var ExecutionError = class extends NeckbeardError {
43
+ constructor(message, cause) {
44
+ super(message);
45
+ this.cause = cause;
46
+ this.name = "ExecutionError";
47
+ }
48
+ };
49
+ var ConfigurationError = class extends NeckbeardError {
50
+ constructor(message) {
51
+ super(message);
52
+ this.name = "ConfigurationError";
53
+ }
54
+ };
7
55
  var DEFAULT_DEPENDENCIES = {};
56
+ function requireEnv(name) {
57
+ const value = process.env[name];
58
+ if (!value) {
59
+ throw new ConfigurationError(
60
+ `${name} environment variable is required. Please set it before calling deploy() or run().`
61
+ );
62
+ }
63
+ return value;
64
+ }
65
+ function shellEscape(str) {
66
+ return `'${str.replace(/'/g, "'\\''")}'`;
67
+ }
8
68
  function getCallerFile() {
9
69
  const stack = new Error().stack?.split("\n") ?? [];
10
70
  for (const line of stack.slice(2)) {
@@ -58,7 +118,7 @@ var Agent = class {
58
118
  this.id = config.id;
59
119
  this.inputSchema = config.inputSchema;
60
120
  this.outputSchema = config.outputSchema;
61
- this.maxDuration = config.maxDuration ?? 300;
121
+ this.maxDuration = config.maxDuration ?? DEFAULT_MAX_DURATION;
62
122
  this._run = config.run;
63
123
  this._sourceFile = getCallerFile();
64
124
  this._sandboxId = config.sandboxId;
@@ -69,8 +129,29 @@ var Agent = class {
69
129
  get sandboxId() {
70
130
  return this._sandboxId;
71
131
  }
132
+ /**
133
+ * Deploys the agent to an E2B sandbox.
134
+ *
135
+ * This method bundles the agent code using esbuild, creates a new E2B sandbox,
136
+ * installs any specified OS dependencies, downloads configured files, and
137
+ * uploads the agent code to the sandbox.
138
+ *
139
+ * The sandbox ID is stored and can be accessed via the `sandboxId` property
140
+ * after deployment completes.
141
+ *
142
+ * @throws {ConfigurationError} If E2B_API_KEY environment variable is not set
143
+ * @throws {DeploymentError} If sandbox creation, dependency installation, or file upload fails
144
+ *
145
+ * @example
146
+ * ```typescript
147
+ * const agent = new Agent({ ... });
148
+ * await agent.deploy();
149
+ * console.log(`Deployed to sandbox: ${agent.sandboxId}`);
150
+ * ```
151
+ */
72
152
  async deploy() {
73
153
  if (this._sandboxId) return;
154
+ const e2bApiKey = requireEnv("E2B_API_KEY");
74
155
  const esbuild = await getEsbuild();
75
156
  const { Sandbox } = await getE2b();
76
157
  const collectedExternals = /* @__PURE__ */ new Set();
@@ -82,13 +163,20 @@ var Agent = class {
82
163
  entryPoints: [this._sourceFile],
83
164
  bundle: true,
84
165
  platform: "node",
85
- target: "node20",
166
+ target: NODE_TARGET,
86
167
  format: "esm",
87
168
  write: false,
88
169
  minify: true,
89
170
  keepNames: true,
90
171
  treeShaking: false,
91
172
  // Preserve exports for the sandbox runner to import
173
+ banner: {
174
+ js: `import { fileURLToPath as __neckbeard_fileURLToPath } from 'node:url';
175
+ import { dirname as __neckbeard_dirname } from 'node:path';
176
+ const __filename = __neckbeard_fileURLToPath(import.meta.url);
177
+ const __dirname = __neckbeard_dirname(__filename);
178
+ `
179
+ },
92
180
  plugins: [{
93
181
  name: "agent-neckbeard-externals",
94
182
  setup(build) {
@@ -161,52 +249,52 @@ try {
161
249
  }
162
250
  `;
163
251
  const sandbox = await Sandbox.create("base", {
164
- apiKey: process.env.E2B_API_KEY
252
+ apiKey: e2bApiKey
165
253
  });
166
254
  const { apt, commands } = this.dependencies;
167
255
  if (apt && apt.length > 0) {
168
256
  const aptCmd = `sudo apt-get update && sudo apt-get install -y ${apt.join(" ")}`;
169
- const aptResult = await sandbox.commands.run(aptCmd, { timeoutMs: 3e5 });
257
+ const aptResult = await sandbox.commands.run(aptCmd, { timeoutMs: SANDBOX_COMMAND_TIMEOUT_MS });
170
258
  if (aptResult.exitCode !== 0) {
171
- throw new Error(`Failed to install apt packages: ${aptResult.stderr}`);
259
+ throw new DeploymentError(`Failed to install apt packages: ${aptResult.stderr}`);
172
260
  }
173
261
  }
174
262
  if (commands && commands.length > 0) {
175
263
  for (const cmd of commands) {
176
- const cmdResult = await sandbox.commands.run(cmd, { timeoutMs: 3e5 });
264
+ const cmdResult = await sandbox.commands.run(cmd, { timeoutMs: SANDBOX_COMMAND_TIMEOUT_MS });
177
265
  if (cmdResult.exitCode !== 0) {
178
- throw new Error(`Failed to run command "${cmd}": ${cmdResult.stderr}`);
266
+ throw new DeploymentError(`Failed to run command "${cmd}": ${cmdResult.stderr}`);
179
267
  }
180
268
  }
181
269
  }
182
270
  if (this.files.length > 0) {
183
271
  for (const file of this.files) {
184
- const destPath = file.path.startsWith("/") ? file.path : `/home/user/${file.path}`;
272
+ const destPath = file.path.startsWith("/") ? file.path : `${SANDBOX_HOME}/${file.path}`;
185
273
  const parentDir = destPath.substring(0, destPath.lastIndexOf("/"));
186
274
  if (parentDir) {
187
- await sandbox.commands.run(`mkdir -p "${parentDir}"`, { timeoutMs: 3e4 });
275
+ await sandbox.commands.run(`mkdir -p ${shellEscape(parentDir)}`, { timeoutMs: QUICK_COMMAND_TIMEOUT_MS });
188
276
  }
189
- const curlCmd = `curl -fsSL -o "${destPath}" "${file.url}"`;
190
- const downloadResult = await sandbox.commands.run(curlCmd, { timeoutMs: 3e5 });
277
+ const curlCmd = `curl -fsSL -o ${shellEscape(destPath)} ${shellEscape(file.url)}`;
278
+ const downloadResult = await sandbox.commands.run(curlCmd, { timeoutMs: SANDBOX_COMMAND_TIMEOUT_MS });
191
279
  if (downloadResult.exitCode !== 0) {
192
- throw new Error(`Failed to download file from ${file.url} to ${destPath}: ${downloadResult.stderr}`);
280
+ throw new DeploymentError(`Failed to download file from ${file.url} to ${destPath}: ${downloadResult.stderr}`);
193
281
  }
194
282
  }
195
283
  }
196
284
  if (this.claudeDir) {
197
285
  const claudeFiles = readDirectoryRecursively(this.claudeDir);
198
- await sandbox.commands.run("mkdir -p /home/user/.claude", { timeoutMs: 3e4 });
286
+ await sandbox.commands.run(`mkdir -p ${SANDBOX_PATHS.claudeDir}`, { timeoutMs: QUICK_COMMAND_TIMEOUT_MS });
199
287
  for (const file of claudeFiles) {
200
- const destPath = `/home/user/.claude/${file.relativePath}`;
288
+ const destPath = `${SANDBOX_PATHS.claudeDir}/${file.relativePath}`;
201
289
  const parentDir = destPath.substring(0, destPath.lastIndexOf("/"));
202
- if (parentDir && parentDir !== "/home/user/.claude") {
203
- await sandbox.commands.run(`mkdir -p "${parentDir}"`, { timeoutMs: 3e4 });
290
+ if (parentDir && parentDir !== SANDBOX_PATHS.claudeDir) {
291
+ await sandbox.commands.run(`mkdir -p ${shellEscape(parentDir)}`, { timeoutMs: QUICK_COMMAND_TIMEOUT_MS });
204
292
  }
205
293
  await sandbox.files.write(destPath, file.content);
206
294
  }
207
295
  }
208
- await sandbox.files.write("/home/user/agent.mjs", result.outputFiles[0].text);
209
- await sandbox.files.write("/home/user/runner.mjs", runnerCode);
296
+ await sandbox.files.write(SANDBOX_PATHS.agentModule, result.outputFiles[0].text);
297
+ await sandbox.files.write(SANDBOX_PATHS.runnerModule, runnerCode);
210
298
  if (collectedExternals.size > 0) {
211
299
  const dependencies = {};
212
300
  for (const pkg of collectedExternals) {
@@ -217,42 +305,64 @@ try {
217
305
  type: "module",
218
306
  dependencies
219
307
  });
220
- await sandbox.files.write("/home/user/package.json", pkgJson);
221
- const installResult = await sandbox.commands.run("cd /home/user && npm install", { timeoutMs: 3e5 });
308
+ await sandbox.files.write(SANDBOX_PATHS.packageJson, pkgJson);
309
+ const installResult = await sandbox.commands.run(`cd ${SANDBOX_HOME} && npm install`, { timeoutMs: SANDBOX_COMMAND_TIMEOUT_MS });
222
310
  if (installResult.exitCode !== 0) {
223
- throw new Error(`Failed to install external packages: ${installResult.stderr}`);
311
+ throw new DeploymentError(`Failed to install external packages: ${installResult.stderr}`);
224
312
  }
225
313
  }
226
314
  this._sandboxId = sandbox.sandboxId;
227
315
  }
316
+ /**
317
+ * Executes the agent with the given input.
318
+ *
319
+ * The agent must be deployed before calling this method. Input is validated
320
+ * against the input schema, then the agent runs in the sandbox with the
321
+ * validated input. Output is validated against the output schema before
322
+ * being returned.
323
+ *
324
+ * @param input - The input data for the agent, must conform to inputSchema
325
+ * @returns A result object indicating success or failure with output or error
326
+ *
327
+ * @example
328
+ * ```typescript
329
+ * const result = await agent.run({ prompt: 'Hello, world!' });
330
+ * if (result.ok) {
331
+ * console.log('Output:', result.output);
332
+ * } else {
333
+ * console.error('Error:', result.error.message);
334
+ * }
335
+ * ```
336
+ */
228
337
  async run(input) {
229
338
  const executionId = `exec_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
230
339
  if (!this._sandboxId) {
231
340
  return {
232
341
  ok: false,
233
342
  executionId,
234
- error: new Error("Agent not deployed. Call agent.deploy() first or pass sandboxId to constructor.")
343
+ error: new ExecutionError("Agent not deployed. Call agent.deploy() first or pass sandboxId to constructor.")
235
344
  };
236
345
  }
237
346
  try {
347
+ const e2bApiKey = requireEnv("E2B_API_KEY");
238
348
  const { Sandbox } = await getE2b();
239
349
  const validatedInput = this.inputSchema.parse(input);
240
350
  const sandbox = await Sandbox.connect(this._sandboxId, {
241
- apiKey: process.env.E2B_API_KEY
351
+ apiKey: e2bApiKey
242
352
  });
243
- await sandbox.files.write("/home/user/input/task.json", JSON.stringify({ input: validatedInput, executionId }));
244
- const result = await sandbox.commands.run("node /home/user/runner.mjs", {
353
+ await sandbox.files.write(SANDBOX_PATHS.taskJson, JSON.stringify({ input: validatedInput, executionId }));
354
+ const result = await sandbox.commands.run(`node ${SANDBOX_PATHS.runnerModule}`, {
245
355
  timeoutMs: this.maxDuration * 1e3,
246
356
  envs: {
247
357
  ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY ?? ""
248
358
  }
249
359
  });
250
360
  if (result.exitCode !== 0) {
251
- return { ok: false, executionId, error: new Error(`Agent failed: ${result.stderr}`) };
361
+ return { ok: false, executionId, error: new ExecutionError(`Agent failed: ${result.stderr}`) };
252
362
  }
253
- const output = JSON.parse(await sandbox.files.read("/home/user/output/result.json"));
363
+ const output = JSON.parse(await sandbox.files.read(SANDBOX_PATHS.resultJson));
254
364
  if (!output.success) {
255
- const err = new Error(output.error.message);
365
+ const err = new ExecutionError(output.error.message);
256
366
  err.stack = output.error.stack;
257
367
  return { ok: false, executionId, error: err };
258
368
  }
@@ -264,5 +374,10 @@ try {
264
374
  };
265
375
  export {
266
376
  Agent,
267
- DEFAULT_DEPENDENCIES
377
+ ConfigurationError,
378
+ DEFAULT_DEPENDENCIES,
379
+ DeploymentError,
380
+ ExecutionError,
381
+ NeckbeardError,
382
+ ValidationError
268
383
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-neckbeard",
3
- "version": "1.0.3",
3
+ "version": "1.0.4",
4
4
  "description": "Deploy AI agents to E2B sandboxes",
5
5
  "type": "module",
6
6
  "exports": {
@@ -16,7 +16,9 @@
16
16
  }
17
17
  },
18
18
  "main": "./dist/index.cjs",
19
+ "module": "./dist/index.js",
19
20
  "types": "./dist/index.d.cts",
21
+ "sideEffects": false,
20
22
  "files": [
21
23
  "dist"
22
24
  ],
@@ -26,7 +28,8 @@
26
28
  "scripts": {
27
29
  "build": "tsup",
28
30
  "dev": "tsup --watch",
29
- "typecheck": "tsc --noEmit"
31
+ "typecheck": "tsc --noEmit",
32
+ "prepare": "npm run build"
30
33
  },
31
34
  "dependencies": {
32
35
  "e2b": "^1.0.0",