agent-neckbeard 0.0.5 → 1.0.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.

Potentially problematic release.


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

package/README.md CHANGED
@@ -1,177 +1,102 @@
1
- # agent-neckbeard
1
+ <img width="1024" height="1024" alt="image" src="https://github.com/user-attachments/assets/52b7f9cf-b9c7-4cae-be11-62273cd1489a" />
2
2
 
3
- Deploy AI agents to E2B sandboxes with structured input/output validation.
3
+ # neckbeard
4
4
 
5
- ## The Problem
5
+ There's a weird thing that happens when you try to deploy an AI agent.
6
6
 
7
- When building agentic workflows with the [Claude Agent SDK](https://github.com/anthropics/claude-code), the agent process needs to run **inside** a sandbox—not just the tool calls. This means:
7
+ Most people think of agents as fancy API calls. You send a prompt, you get a response. But that's not what's actually happening. The agent is running code. It's executing bash commands, writing files, installing packages. It runs for minutes at a time, maintaining state between steps. It's a process, not a request.
8
8
 
9
- - The agent can execute bash commands, write files, and perform complex multi-step tasks
10
- - All execution is isolated and secure
11
- - The agent maintains conversation state across tool calls
9
+ This creates an obvious problem: do you really want that process running on your production server?
12
10
 
13
- E2B provides sandboxed environments, but wiring up the deployment, bundling, and I/O is tedious. This package handles it for you.
11
+ Anthropic's answer is no. Their [hosting docs](https://docs.claude.com/en/docs/agent-sdk/hosting) say you should run the entire agent inside a sandbox. Not just intercept the dangerous tool calls—put the whole thing in a container where it can't escape.
14
12
 
15
- ## How It Works
13
+ That sounds simple. It's not.
16
14
 
17
- 1. **Define** an agent with typed input/output schemas
18
- 2. **Deploy** bundles your code with esbuild and uploads to E2B
19
- 3. **Run** writes input to `/input/task.json`, executes, reads from `/output/result.json`
15
+ ## The Plumbing Problem
16
+
17
+ Sandboxes like E2B give you a fresh Linux container. Your agent code lives in your repo. Bridging these two worlds is surprisingly annoying.
18
+
19
+ First, you have to get your code into the sandbox. You could bake it into a custom template, but then you're rebuilding templates every time you change a line. You could git clone on boot, but that's slow and requires auth. You could bundle and upload at runtime, which works, but now you're writing bundler configs.
20
+
21
+ Then you have to pass input. How do you get the user's prompt into a process running inside a sandbox? CLI arguments require escaping and have length limits. Environment variables have size limits. Writing to a file works, but adds boilerplate.
22
+
23
+ The worst part is output. When you run `sandbox.exec("node agent.js")`, you get back everything the process printed. SDK logs, debug output, streaming tokens, and somewhere in there, your actual result. Good luck parsing that reliably.
24
+
25
+ So you end up writing results to a file, reading it back, parsing JSON, validating the shape, handling errors. Every team building sandboxed agents writes some version of this plumbing. It's tedious.
26
+
27
+ ## What This Does
28
+
29
+ Neckbeard handles all of that so you can just write your agent:
20
30
 
21
31
  ```typescript
22
32
  import { Agent } from 'agent-neckbeard';
23
33
  import { query } from '@anthropic-ai/claude-agent-sdk';
24
34
  import { z } from 'zod';
25
35
 
26
- const summaryAgent = new Agent({
36
+ const agent = new Agent({
27
37
  id: 'summary',
28
- maxDuration: 120,
29
- inputSchema: z.object({
30
- topic: z.string(),
31
- }),
38
+ inputSchema: z.object({ topic: z.string() }),
32
39
  outputSchema: z.object({
33
40
  title: z.string(),
34
41
  summary: z.string(),
35
42
  keyPoints: z.array(z.string()),
36
43
  }),
37
- run: async (input, ctx) => {
38
- let result = { title: '', summary: '', keyPoints: [] as string[] };
39
-
44
+ run: async (input) => {
40
45
  for await (const message of query({
41
- prompt: `Research "${input.topic}" and return JSON with title, summary, keyPoints`,
42
- options: { maxTurns: 10, allowedTools: ['write', 'read'] },
43
- abortSignal: ctx.signal,
46
+ prompt: `Research "${input.topic}" and return JSON`,
47
+ options: { maxTurns: 10 },
44
48
  })) {
45
49
  if (message.type === 'result') {
46
- result = JSON.parse(message.result ?? '{}');
50
+ return JSON.parse(message.result ?? '{}');
47
51
  }
48
52
  }
49
-
50
- return result;
51
53
  },
52
54
  });
53
55
 
54
- // Deploy once
55
- await summaryAgent.deploy();
56
-
57
- // Run many times
58
- const result = await summaryAgent.run({ topic: 'TypeScript generics' });
59
- console.log(result.title); // string - validated
60
- console.log(result.keyPoints); // string[] - validated
56
+ await agent.deploy(); // bundles, uploads to E2B
57
+ const result = await agent.run({ topic: 'TypeScript generics' });
61
58
  ```
62
59
 
63
- ## Installation
60
+ `deploy()` bundles your code with esbuild and uploads it. `run()` writes input to a file, executes, reads the result back, and validates it against your schema. You don't think about file paths or stdout parsing.
64
61
 
65
- ```bash
66
- npm install agent-neckbeard
67
- ```
68
-
69
- Set your API keys:
62
+ ## Setup
70
63
 
71
64
  ```bash
72
- export E2B_API_KEY=your-e2b-api-key
73
- export ANTHROPIC_API_KEY=your-anthropic-api-key # Required for Claude Agent SDK
65
+ npm install agent-neckbeard
74
66
  ```
75
67
 
76
- ## Running Examples
77
-
78
68
  ```bash
79
- cd examples
80
- npm install
81
- npm run summary-agent
69
+ export E2B_API_KEY=your-key
70
+ export ANTHROPIC_API_KEY=your-key
82
71
  ```
83
72
 
84
- ## API
73
+ ## The Details
85
74
 
86
- ### `new Agent(config)`
75
+ The constructor takes a few options:
87
76
 
88
77
  ```typescript
89
- const agent = new Agent({
90
- id: string, // Unique identifier
91
- inputSchema: ZodSchema, // Validates input before run
92
- outputSchema: ZodSchema, // Validates output after run
93
- run: (input, ctx) => Promise, // Your agent logic
94
- maxDuration?: number, // Timeout in seconds (default: 300)
95
- sandboxId?: string, // Reuse existing sandbox (skip deploy)
96
- dependencies?: { // OS-level dependencies to install
97
- apt?: string[], // APT packages (e.g., ['curl', 'git'])
98
- npm?: string[], // Global npm packages
99
- commands?: string[], // Custom shell commands
78
+ new Agent({
79
+ id: string,
80
+ inputSchema: ZodSchema,
81
+ outputSchema: ZodSchema,
82
+ run: (input, ctx) => Promise,
83
+ maxDuration?: number, // seconds, default 300
84
+ sandboxId?: string, // reuse existing sandbox
85
+ dependencies?: {
86
+ apt?: string[],
87
+ commands?: string[],
100
88
  },
101
- });
102
- ```
103
-
104
- ### `agent.deploy()`
105
-
106
- Bundles your agent code with esbuild and uploads to a new E2B sandbox. Automatically detects the source file—no entry point needed.
107
-
108
- ```typescript
109
- await agent.deploy();
110
- console.log(agent.sandboxId); // 'sandbox-abc123'
111
- ```
112
-
113
- ### `agent.run(input)`
114
-
115
- Executes the agent in the sandbox with validated input/output.
116
-
117
- ```typescript
118
- const result = await agent.run({ topic: 'quantum computing' });
119
- // result is typed and validated against outputSchema
120
- ```
121
-
122
- ### Reusing a Sandbox
123
-
124
- If you already have a deployed sandbox, pass `sandboxId` to skip deployment:
125
-
126
- ```typescript
127
- const agent = new Agent({
128
- id: 'summary',
129
- sandboxId: 'existing-sandbox-id', // Skip deploy()
130
- inputSchema,
131
- outputSchema,
132
- run,
133
- });
134
-
135
- // No deploy needed
136
- const result = await agent.run({ topic: 'hello' });
89
+ files?: [{ url, path }], // pre-download into sandbox
90
+ })
137
91
  ```
138
92
 
139
- ## External Packages
93
+ If you already have a sandbox deployed, pass `sandboxId` and skip `deploy()`.
140
94
 
141
- Some packages can't be bundled and need to be installed in the sandbox at runtime. These include packages that:
142
- - Spawn child processes (like `@anthropic-ai/claude-agent-sdk` which spawns its CLI)
143
- - Have native modules
95
+ The `files` option downloads things into the sandbox before your agent runs—useful for models or config files. Relative paths resolve from `/home/user/`.
144
96
 
145
- These packages are automatically marked as external during bundling and installed via npm in the sandbox.
97
+ Some packages can't be bundled because they spawn child processes or have native modules. The Claude Agent SDK is like this. These get automatically marked as external and installed via npm in the sandbox.
146
98
 
147
- ```typescript
148
- import { query } from '@anthropic-ai/claude-agent-sdk';
149
-
150
- const agent = new Agent({
151
- // ...
152
- run: async (input, ctx) => {
153
- for await (const message of query({
154
- prompt: `Do something with ${input.topic}`,
155
- options: { maxTurns: 10, allowedTools: ['Bash', 'Read', 'Write'] },
156
- })) {
157
- // Handle Claude's responses
158
- }
159
- },
160
- });
161
- ```
162
-
163
- ## Context
164
-
165
- The `run` function receives a context object:
166
-
167
- ```typescript
168
- run: async (input, ctx) => {
169
- ctx.executionId // Unique ID for this execution
170
- ctx.signal // AbortSignal for cancellation
171
- ctx.env // Environment variables
172
- ctx.logger // { debug, info, warn, error }
173
- }
174
- ```
99
+ The `run` function gets a context object with an `executionId`, an `AbortSignal`, environment variables, and a logger.
175
100
 
176
101
  ## License
177
102
 
package/dist/index.d.ts CHANGED
@@ -27,11 +27,24 @@ interface SchemaLike<T> {
27
27
  interface OsDependencies {
28
28
  /** APT packages to install (e.g., ['curl', 'git']) */
29
29
  apt?: string[];
30
- /** NPM packages to install globally (e.g., ['typescript', '@anthropic-ai/claude-code']) */
31
- npm?: string[];
32
30
  /** Custom shell commands to run during setup */
33
31
  commands?: string[];
34
32
  }
33
+ /**
34
+ * Configuration for downloading a file into the sandbox filesystem.
35
+ * Files are downloaded during deploy() before the agent runs.
36
+ */
37
+ interface FileDownload {
38
+ /** URL to download the file from (supports http/https) */
39
+ url: string;
40
+ /**
41
+ * Destination path in the sandbox.
42
+ * Can be absolute (e.g., '/home/user/data/model.bin') or
43
+ * relative to /home/user/ (e.g., 'data/model.bin').
44
+ * Parent directories are created automatically.
45
+ */
46
+ path: string;
47
+ }
35
48
  /** Default dependencies - empty by default, specify what you need */
36
49
  declare const DEFAULT_DEPENDENCIES: OsDependencies;
37
50
  interface AgentConfig<TInput, TOutput> {
@@ -43,6 +56,12 @@ interface AgentConfig<TInput, TOutput> {
43
56
  sandboxId?: string;
44
57
  /** OS-level dependencies to install in the sandbox. Defaults to Claude Code. Set to {} to skip. */
45
58
  dependencies?: OsDependencies;
59
+ /**
60
+ * Files to download and initialize in the sandbox filesystem.
61
+ * Downloaded during deploy() before the agent code runs.
62
+ * Useful for pre-loading models, datasets, configuration files, etc.
63
+ */
64
+ files?: FileDownload[];
46
65
  }
47
66
  declare class Agent<TInput, TOutput> {
48
67
  readonly id: string;
@@ -50,6 +69,7 @@ declare class Agent<TInput, TOutput> {
50
69
  readonly outputSchema: SchemaLike<TOutput>;
51
70
  readonly maxDuration: number;
52
71
  readonly dependencies: OsDependencies;
72
+ readonly files: FileDownload[];
53
73
  /** @internal Used by the sandbox runner - must be public for bundled code access */
54
74
  _run: (input: TInput, ctx: AgentRunContext) => Promise<TOutput>;
55
75
  private _sourceFile;
@@ -60,4 +80,4 @@ declare class Agent<TInput, TOutput> {
60
80
  run(input: TInput): Promise<AgentRunResult<TOutput>>;
61
81
  }
62
82
 
63
- export { Agent, type AgentConfig, type AgentRunContext, type AgentRunResult, DEFAULT_DEPENDENCIES, type OsDependencies };
83
+ export { Agent, type AgentConfig, type AgentRunContext, type AgentRunResult, DEFAULT_DEPENDENCIES, type FileDownload, type OsDependencies };
package/dist/index.js CHANGED
@@ -21,6 +21,7 @@ var Agent = class {
21
21
  outputSchema;
22
22
  maxDuration;
23
23
  dependencies;
24
+ files;
24
25
  /** @internal Used by the sandbox runner - must be public for bundled code access */
25
26
  _run;
26
27
  _sourceFile;
@@ -34,6 +35,7 @@ var Agent = class {
34
35
  this._sourceFile = getCallerFile();
35
36
  this._sandboxId = config.sandboxId;
36
37
  this.dependencies = config.dependencies ?? DEFAULT_DEPENDENCIES;
38
+ this.files = config.files ?? [];
37
39
  }
38
40
  get sandboxId() {
39
41
  return this._sandboxId;
@@ -132,7 +134,7 @@ try {
132
134
  const sandbox = await Sandbox.create("base", {
133
135
  apiKey: process.env.E2B_API_KEY
134
136
  });
135
- const { apt, npm, commands } = this.dependencies;
137
+ const { apt, commands } = this.dependencies;
136
138
  if (apt && apt.length > 0) {
137
139
  const aptCmd = `apt-get update && apt-get install -y ${apt.join(" ")}`;
138
140
  const aptResult = await sandbox.commands.run(aptCmd, { timeoutMs: 3e5 });
@@ -140,13 +142,6 @@ try {
140
142
  throw new Error(`Failed to install apt packages: ${aptResult.stderr}`);
141
143
  }
142
144
  }
143
- if (npm && npm.length > 0) {
144
- const npmCmd = `npm install -g ${npm.join(" ")}`;
145
- const npmResult = await sandbox.commands.run(npmCmd, { timeoutMs: 3e5 });
146
- if (npmResult.exitCode !== 0) {
147
- throw new Error(`Failed to install npm packages: ${npmResult.stderr}`);
148
- }
149
- }
150
145
  if (commands && commands.length > 0) {
151
146
  for (const cmd of commands) {
152
147
  const cmdResult = await sandbox.commands.run(cmd, { timeoutMs: 3e5 });
@@ -155,6 +150,20 @@ try {
155
150
  }
156
151
  }
157
152
  }
153
+ if (this.files.length > 0) {
154
+ for (const file of this.files) {
155
+ const destPath = file.path.startsWith("/") ? file.path : `/home/user/${file.path}`;
156
+ const parentDir = destPath.substring(0, destPath.lastIndexOf("/"));
157
+ if (parentDir) {
158
+ await sandbox.commands.run(`mkdir -p "${parentDir}"`, { timeoutMs: 3e4 });
159
+ }
160
+ const curlCmd = `curl -fsSL -o "${destPath}" "${file.url}"`;
161
+ const downloadResult = await sandbox.commands.run(curlCmd, { timeoutMs: 3e5 });
162
+ if (downloadResult.exitCode !== 0) {
163
+ throw new Error(`Failed to download file from ${file.url} to ${destPath}: ${downloadResult.stderr}`);
164
+ }
165
+ }
166
+ }
158
167
  await sandbox.files.write("/home/user/agent.mjs", result.outputFiles[0].text);
159
168
  await sandbox.files.write("/home/user/runner.mjs", runnerCode);
160
169
  if (collectedExternals.size > 0) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-neckbeard",
3
- "version": "0.0.5",
3
+ "version": "1.0.0",
4
4
  "description": "Deploy AI agents to E2B sandboxes",
5
5
  "type": "module",
6
6
  "exports": {