agent-neckbeard 0.0.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/README.md ADDED
@@ -0,0 +1,140 @@
1
+ # agent-neckbeard
2
+
3
+ Deploy AI agents to E2B sandboxes with structured input/output validation.
4
+
5
+ ## The Problem
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:
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
12
+
13
+ E2B provides sandboxed environments, but wiring up the deployment, bundling, and I/O is tedious. This package handles it for you.
14
+
15
+ ## How It Works
16
+
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`
20
+
21
+ ```typescript
22
+ import { Agent } from 'agent-neckbeard';
23
+ import { query } from '@anthropic-ai/claude-code';
24
+ import { z } from 'zod';
25
+
26
+ const summaryAgent = new Agent({
27
+ id: 'summary',
28
+ maxDuration: 120,
29
+ inputSchema: z.object({
30
+ topic: z.string(),
31
+ }),
32
+ outputSchema: z.object({
33
+ title: z.string(),
34
+ summary: z.string(),
35
+ keyPoints: z.array(z.string()),
36
+ }),
37
+ run: async (input, ctx) => {
38
+ let result = { title: '', summary: '', keyPoints: [] as string[] };
39
+
40
+ 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,
44
+ })) {
45
+ if (message.type === 'result') {
46
+ result = JSON.parse(message.result ?? '{}');
47
+ }
48
+ }
49
+
50
+ return result;
51
+ },
52
+ });
53
+
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
61
+ ```
62
+
63
+ ## Installation
64
+
65
+ ```bash
66
+ npm install agent-neckbeard
67
+ ```
68
+
69
+ Set your E2B API key:
70
+
71
+ ```bash
72
+ export E2B_API_KEY=your-api-key
73
+ ```
74
+
75
+ ## API
76
+
77
+ ### `new Agent(config)`
78
+
79
+ ```typescript
80
+ const agent = new Agent({
81
+ id: string, // Unique identifier
82
+ inputSchema: ZodSchema, // Validates input before run
83
+ outputSchema: ZodSchema, // Validates output after run
84
+ run: (input, ctx) => Promise, // Your agent logic
85
+ maxDuration?: number, // Timeout in seconds (default: 300)
86
+ sandboxId?: string, // Reuse existing sandbox (skip deploy)
87
+ });
88
+ ```
89
+
90
+ ### `agent.deploy()`
91
+
92
+ Bundles your agent code with esbuild and uploads to a new E2B sandbox. Automatically detects the source file—no entry point needed.
93
+
94
+ ```typescript
95
+ await agent.deploy();
96
+ console.log(agent.sandboxId); // 'sandbox-abc123'
97
+ ```
98
+
99
+ ### `agent.run(input)`
100
+
101
+ Executes the agent in the sandbox with validated input/output.
102
+
103
+ ```typescript
104
+ const result = await agent.run({ topic: 'quantum computing' });
105
+ // result is typed and validated against outputSchema
106
+ ```
107
+
108
+ ### Reusing a Sandbox
109
+
110
+ If you already have a deployed sandbox, pass `sandboxId` to skip deployment:
111
+
112
+ ```typescript
113
+ const agent = new Agent({
114
+ id: 'summary',
115
+ sandboxId: 'existing-sandbox-id', // Skip deploy()
116
+ inputSchema,
117
+ outputSchema,
118
+ run,
119
+ });
120
+
121
+ // No deploy needed
122
+ const result = await agent.run({ topic: 'hello' });
123
+ ```
124
+
125
+ ## Context
126
+
127
+ The `run` function receives a context object:
128
+
129
+ ```typescript
130
+ run: async (input, ctx) => {
131
+ ctx.executionId // Unique ID for this execution
132
+ ctx.signal // AbortSignal for cancellation
133
+ ctx.env // Environment variables
134
+ ctx.logger // { debug, info, warn, error }
135
+ }
136
+ ```
137
+
138
+ ## License
139
+
140
+ MIT
@@ -0,0 +1,41 @@
1
+ /**
2
+ * agent-neckbeard - Deploy AI agents to E2B sandboxes
3
+ */
4
+ interface AgentRunContext {
5
+ executionId: string;
6
+ signal: AbortSignal;
7
+ env: Record<string, string>;
8
+ logger: {
9
+ debug: (message: string, ...args: unknown[]) => void;
10
+ info: (message: string, ...args: unknown[]) => void;
11
+ warn: (message: string, ...args: unknown[]) => void;
12
+ error: (message: string, ...args: unknown[]) => void;
13
+ };
14
+ }
15
+ interface SchemaLike<T> {
16
+ parse: (data: unknown) => T;
17
+ }
18
+ interface AgentConfig<TInput, TOutput> {
19
+ id: string;
20
+ inputSchema: SchemaLike<TInput>;
21
+ outputSchema: SchemaLike<TOutput>;
22
+ run: (input: TInput, ctx: AgentRunContext) => Promise<TOutput>;
23
+ maxDuration?: number;
24
+ sandboxId?: string;
25
+ }
26
+ declare class Agent<TInput, TOutput> {
27
+ readonly id: string;
28
+ readonly inputSchema: SchemaLike<TInput>;
29
+ readonly outputSchema: SchemaLike<TOutput>;
30
+ readonly maxDuration: number;
31
+ /** @internal Used by the sandbox runner - must be public for bundled code access */
32
+ _run: (input: TInput, ctx: AgentRunContext) => Promise<TOutput>;
33
+ private _sourceFile;
34
+ private _sandboxId?;
35
+ constructor(config: AgentConfig<TInput, TOutput>);
36
+ get sandboxId(): string | undefined;
37
+ deploy(): Promise<void>;
38
+ run(input: TInput): Promise<TOutput>;
39
+ }
40
+
41
+ export { Agent, type AgentConfig, type AgentRunContext };
package/dist/index.js ADDED
@@ -0,0 +1,114 @@
1
+ // src/index.ts
2
+ import * as esbuild from "esbuild";
3
+ import { Sandbox } from "e2b";
4
+ import { fileURLToPath } from "url";
5
+ function getCallerFile() {
6
+ const stack = new Error().stack?.split("\n") ?? [];
7
+ for (const line of stack.slice(2)) {
8
+ const match = line.match(/\((.+?):\d+:\d+\)/) || line.match(/at (.+?):\d+:\d+/);
9
+ if (match) {
10
+ let file = match[1];
11
+ if (file.startsWith("file://")) file = fileURLToPath(file);
12
+ if (!file.includes("node:") && !file.includes("agent-neckbeard")) return file;
13
+ }
14
+ }
15
+ throw new Error("Could not determine source file");
16
+ }
17
+ var Agent = class {
18
+ id;
19
+ inputSchema;
20
+ outputSchema;
21
+ maxDuration;
22
+ /** @internal Used by the sandbox runner - must be public for bundled code access */
23
+ _run;
24
+ _sourceFile;
25
+ _sandboxId;
26
+ constructor(config) {
27
+ this.id = config.id;
28
+ this.inputSchema = config.inputSchema;
29
+ this.outputSchema = config.outputSchema;
30
+ this.maxDuration = config.maxDuration ?? 300;
31
+ this._run = config.run;
32
+ this._sourceFile = getCallerFile();
33
+ this._sandboxId = config.sandboxId;
34
+ }
35
+ get sandboxId() {
36
+ return this._sandboxId;
37
+ }
38
+ async deploy() {
39
+ if (this._sandboxId) return;
40
+ const result = await esbuild.build({
41
+ entryPoints: [this._sourceFile],
42
+ bundle: true,
43
+ platform: "node",
44
+ target: "node20",
45
+ format: "esm",
46
+ write: false,
47
+ minify: true,
48
+ keepNames: true
49
+ });
50
+ const runnerCode = `
51
+ import { readFileSync, writeFileSync, mkdirSync } from 'node:fs';
52
+
53
+ mkdirSync('/input', { recursive: true });
54
+ mkdirSync('/output', { recursive: true });
55
+
56
+ const { input, executionId } = JSON.parse(readFileSync('/input/task.json', 'utf-8'));
57
+
58
+ const ctx = {
59
+ executionId,
60
+ signal: AbortSignal.timeout(${this.maxDuration * 1e3}),
61
+ env: process.env,
62
+ logger: {
63
+ debug: (msg, ...args) => console.log('[DEBUG]', msg, ...args),
64
+ info: (msg, ...args) => console.log('[INFO]', msg, ...args),
65
+ warn: (msg, ...args) => console.warn('[WARN]', msg, ...args),
66
+ error: (msg, ...args) => console.error('[ERROR]', msg, ...args),
67
+ },
68
+ };
69
+
70
+ const mod = await import('./agent.mjs');
71
+ const agent = mod.default || Object.values(mod).find(v => v instanceof Object && v.id);
72
+
73
+ try {
74
+ const validated = agent.inputSchema.parse(input);
75
+ const output = await agent._run(validated, ctx);
76
+ const validatedOutput = agent.outputSchema.parse(output);
77
+ writeFileSync('/output/result.json', JSON.stringify({ success: true, output: validatedOutput }));
78
+ } catch (error) {
79
+ writeFileSync('/output/result.json', JSON.stringify({ success: false, error: { message: error.message, stack: error.stack } }));
80
+ }
81
+ `;
82
+ const sandbox = await Sandbox.create("base", {
83
+ apiKey: process.env.E2B_API_KEY
84
+ });
85
+ await sandbox.files.write("/home/user/agent.mjs", result.outputFiles[0].text);
86
+ await sandbox.files.write("/home/user/runner.mjs", runnerCode);
87
+ this._sandboxId = sandbox.sandboxId;
88
+ }
89
+ async run(input) {
90
+ if (!this._sandboxId) {
91
+ throw new Error("Agent not deployed. Call agent.deploy() first or pass sandboxId to constructor.");
92
+ }
93
+ const validatedInput = this.inputSchema.parse(input);
94
+ const executionId = `exec_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
95
+ const sandbox = await Sandbox.connect(this._sandboxId, {
96
+ apiKey: process.env.E2B_API_KEY
97
+ });
98
+ await sandbox.files.write("/input/task.json", JSON.stringify({ input: validatedInput, executionId }));
99
+ const result = await sandbox.commands.run("node /home/user/runner.mjs", {
100
+ timeoutMs: this.maxDuration * 1e3
101
+ });
102
+ if (result.exitCode !== 0) throw new Error(`Agent failed: ${result.stderr}`);
103
+ const output = JSON.parse(await sandbox.files.read("/output/result.json"));
104
+ if (!output.success) {
105
+ const err = new Error(output.error.message);
106
+ err.stack = output.error.stack;
107
+ throw err;
108
+ }
109
+ return this.outputSchema.parse(output.output);
110
+ }
111
+ };
112
+ export {
113
+ Agent
114
+ };
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "agent-neckbeard",
3
+ "version": "0.0.1",
4
+ "description": "Deploy AI agents to E2B sandboxes",
5
+ "type": "module",
6
+ "exports": {
7
+ ".": {
8
+ "import": {
9
+ "types": "./dist/index.d.ts",
10
+ "default": "./dist/index.js"
11
+ }
12
+ }
13
+ },
14
+ "main": "./dist/index.js",
15
+ "types": "./dist/index.d.ts",
16
+ "files": ["dist"],
17
+ "engines": {
18
+ "node": ">=20.0.0"
19
+ },
20
+ "scripts": {
21
+ "build": "tsup",
22
+ "dev": "tsup --watch",
23
+ "typecheck": "tsc --noEmit"
24
+ },
25
+ "dependencies": {
26
+ "esbuild": "^0.24.0",
27
+ "e2b": "^1.0.0"
28
+ },
29
+ "peerDependencies": {
30
+ "zod": "^3.0.0"
31
+ },
32
+ "peerDependenciesMeta": {
33
+ "zod": { "optional": true }
34
+ },
35
+ "devDependencies": {
36
+ "typescript": "^5.6.0",
37
+ "tsup": "^8.3.0",
38
+ "@types/node": "^22.0.0"
39
+ },
40
+ "keywords": ["ai", "agent", "deploy", "e2b", "sandbox", "claude"],
41
+ "license": "MIT",
42
+ "author": "zacwellmer",
43
+ "repository": {
44
+ "type": "git",
45
+ "url": "git+https://github.com/zacwellmer/agent-neckbeard.git"
46
+ },
47
+ "homepage": "https://github.com/zacwellmer/agent-neckbeard#readme",
48
+ "bugs": {
49
+ "url": "https://github.com/zacwellmer/agent-neckbeard/issues"
50
+ },
51
+ "publishConfig": {
52
+ "access": "public"
53
+ }
54
+ }