npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

agent-neckbeard

v1.1.16

Published

Deploy AI agents to E2B and Daytona sandboxes

Readme

neckbeard

Neckbeard deploys your agent code into an E2B or Daytona sandbox and runs it behind a small persistent HTTP server inside that sandbox.

The intent is simple: your production process should not run the agent. The sandbox runs the agent, keeps its filesystem/process state, and exposes a private /invoke endpoint that neckbeard calls for each turn.

This is designed to work from short-lived request handlers and long-running workers, including Vercel Functions, Hatchet workers, and Trigger.dev tasks. See docs/CONSUMER_ENVIRONMENTS.md before changing lifecycle, timeout, streaming, or cleanup behavior for those environments.

What This Does

You write an agent:

import { Agent } from 'agent-neckbeard';
import { query } from '@anthropic-ai/claude-agent-sdk';
import { z } from 'zod';

const agent = new Agent({
  sandbox: {
    provider: 'e2b',
    template: 'code-interpreter-v1',
  },
  inputSchema: z.object({ topic: z.string() }),
  outputSchema: z.object({
    title: z.string(),
    summary: z.string(),
    keyPoints: z.array(z.string()),
  }),
  envs: {
    ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY,
  },
  run: async (input) => {
    for await (const message of query({
      prompt: `Research "${input.topic}" and return JSON`,
      options: { maxTurns: 10 },
    })) {
      if (message.type === 'result') {
        return JSON.parse(message.result ?? '{}');
      }
    }
    throw new Error('Claude did not return a result');
  },
});

const sandboxId = await agent.deploy();
const result = await agent.run({ topic: 'TypeScript generics' }, { sandboxId });

deploy() bundles your agent, uploads agent.mjs and the embedded runtime server to the sandbox, starts node server.mjs, waits for /health, and returns the sandbox ID.

run() validates input locally, POSTs it to the sandbox server, reads SSE harness events, validates the final output, and returns the existing { ok, executionId, output | error } result shape.

By default, the package infers the file to bundle from the new Agent(...) call stack. In frameworks that bundle server code before runtime, set sourceFile to the real agent module so deploy() does not rebundle a framework output chunk:

new Agent({
  sourceFile: path.resolve(process.cwd(), 'packages/my-agent/src/agent.ts'),
  // ...
});

For Next.js/Vercel-style deployments, keep agent-neckbeard external to the server bundle and make sure the referenced source file is included in the production artifact:

// next.config.ts
import { withNeckbeardNextConfig } from 'agent-neckbeard';

const nextConfig = withNeckbeardNextConfig({
  // your existing Next config
}, {
  routeGlob: '/api/my-agent',
  sourceFiles: ['./packages/my-agent/src/agent.ts'],
});

export default nextConfig;

The helper traces files imported by those source entries by default, including package entry metadata that deploy() needs when it rebundles the agent at runtime. For Next 13 configs that still use experimental.serverComponentsExternalPackages, the helper automatically uses the matching experimental tracing fields when it sees that shape. Set outputFileTracingRoot when the agent source is outside the app root.

This repo tests that shape locally with npm run test:compat:next, which builds and runs a Dockerized Next App Router fixture without pushing to Vercel. The fixture also checks the runtime env contract, Node major, and traced standalone files so source-level tests do not mask deployment artifact problems.

Setup

npm install agent-neckbeard
export E2B_API_KEY=your-key
export DAYTONA_API_KEY=your-key
export ANTHROPIC_API_KEY=your-key

Pick the sandbox backend explicitly with sandbox.provider. The public sandbox.template field maps to an E2B template or a Daytona snapshot.

Daytona sandboxes can also receive network policy options supported by the Daytona SDK. Use domainAllowList for domains and wildcard domains, or networkAllowList for comma-separated CIDR ranges:

new Agent({
  sandbox: {
    provider: 'daytona',
    template: 'daytona-small',
    domainAllowList: '*.example.com',
  },
  // ...
});

Set networkBlockAll: true only when you want to block all outbound network access. Daytona rejects networkBlockAll: true combined with non-empty allow lists.

Run-Output Agents

Use run when each invocation should produce one validated output:

new Agent({
  sandbox: {
    provider: 'e2b',
    template: 'code-interpreter-v1',
  },
  inputSchema: z.object({ prompt: z.string() }),
  outputSchema: z.object({ answer: z.string() }),
  run: async (input, ctx) => {
    ctx.logger.info(`Handling ${ctx.executionId}`);
    return { answer: input.prompt.toUpperCase() };
  },
});

The run handler receives a context object with executionId, AbortSignal, environment variables, sandbox paths, and a logger:

run: async (input, ctx) => {
  const notesPath = `${ctx.sandbox.homeDir}/data/notes.txt`;
  const cwd = ctx.sandbox.agentDir;
  // ...
}

Use ctx.sandbox.homeDir and ctx.sandbox.agentDir instead of hardcoding /home/user if you want the same agent code to work on both providers.

Streaming Agents

Use server.handler when you want progress frames while the sandbox work is still running:

const agent = new Agent({
  sandbox: {
    provider: 'daytona',
    template: 'daytona-small',
  },
  inputSchema: z.object({ prompt: z.string() }),
  server: {
    frameSchema: z.object({
      type: z.enum(['status', 'token', 'done']),
      text: z.string().optional(),
    }),
    handler: async ({ input, emit }) => {
      await emit({ type: 'status', text: `Starting ${input.prompt}` });
      await emit({ type: 'token', text: 'hello' });
      await emit({ type: 'done' });
    },
  },
});

const sandboxId = await agent.deploy();

for await (const frame of agent.invoke({ prompt: 'hi' }, { sandboxId })) {
  console.log(frame);
}

Internally, streaming uses Server-Sent Events from the sandbox /invoke endpoint. Neckbeard wraps user frames in harness events so errors, completion, and run-output results are handled consistently.

Reusing Sandboxes

Persist the sandbox ID if another process needs to call the same running sandbox server:

const sandboxId = await agent.deploy();
await saveSandboxId(sandboxId);

// Later:
const savedSandboxId = await loadSandboxId();
const result = await agent.run({ topic: 'hello' }, { sandboxId: savedSandboxId });

Neckbeard rehydrates the sandbox endpoint and provider traffic token from the sandbox ID using the configured provider SDK. E2B traffic tokens are sent as the e2b-traffic-access-token header. Daytona preview tokens are sent as the DAYTONA_SANDBOX_AUTH_KEY query parameter. Consumers only need to store the sandbox ID and configure the same provider/API key in the process that reconnects.

Environment Changes with Reused Sandboxes

envs are applied when Neckbeard creates the sandbox and starts the sandbox server. If the host app was missing a required value such as ANTHROPIC_API_KEY when the sandbox was created, the sandbox can remain healthy but unauthenticated even after you fix the host environment and redeploy the app.

Updating Vercel, Render, or 1Password environment variables does not mutate already-running sandboxes. Kill the old sandbox or deploy a fresh one and replace the persisted sandbox ID. Consumers that keep long-lived sandbox IDs should treat authentication failures such as Not logged in or Please run /login as a signal to discard the sandbox after verifying the host environment is now correct.

Direct Sandbox Access

Use SandboxClient when host code needs provider-neutral filesystem or command access without running an agent invocation:

import { SandboxClient } from 'agent-neckbeard';

const sandbox = await SandboxClient.connect({
  provider: 'e2b',
  sandboxId,
  timeoutMs: 30 * 60 * 1000,
});

await sandbox.makeDir('/home/user/documents');
await sandbox.writeFile('/home/user/documents/input.txt', 'hello');
const result = await sandbox.runCommand('ls documents', {
  cwd: '/home/user',
  timeoutMs: 60_000,
});

SandboxClient.create() and SandboxClient.connect() support E2B and Daytona. E2B supports setTimeout() for sandbox lifetime extension; providers without that lifecycle primitive throw a ConfigurationError.

Configuration

new Agent({
  sandbox: {
    provider: 'e2b' | 'daytona',
    template: string,
    // Daytona only:
    networkBlockAll?: boolean,
    networkAllowList?: string,
    domainAllowList?: string,
  },
  inputSchema: ZodSchema,
  maxDuration?: number,
  dependencies?: {
    apt?: string[],
    commands?: string[],
  },
  files?: [{ url, path }],
  claudeDir?: string,
  envs?: Record<string, string | undefined>,
  // Run-output agents require:
  outputSchema: ZodSchema,
  run: (input, ctx) => Promise<output>,
  // Streaming agents require this instead of outputSchema/run:
  server: {
    port?: number,
    frameSchema: ZodSchema,
    handler: ({ input, ctx, signal, emit }) => Promise<void>,
  },
});

The files option downloads files into the sandbox before the server starts. Relative paths resolve from the sandbox home directory: /home/user on E2B and /home/daytona on Daytona.

The claudeDir option uploads a local .claude/ directory to the sandbox, enabling Claude Agent SDK skills. Point it at a directory containing .claude/skills/*/SKILL.md files.

The envs option passes environment variables to the sandbox. Undefined values are filtered out:

const agent = new Agent({
  sandbox: {
    provider: 'e2b',
    template: 'code-interpreter-v1',
  },
  envs: {
    ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY,
    MY_API_KEY: process.env.MY_API_KEY,
  },
  // ...
});

Some packages cannot be bundled because they spawn child processes or have native modules. The Claude Agent SDK is like this. These packages are automatically marked external and installed with npm in the sandbox.

Cleanup

const sandboxId = await agent.deploy();

try {
  const result = await agent.run(input, { sandboxId });
} finally {
  await agent.kill(sandboxId);
}

kill() accepts a sandbox ID. If omitted, it kills the sandbox cached on the agent instance.

Testing

npm run check

The local check runs typecheck, repository lint checks, formatting hygiene checks, unit tests, generated runtime verification, CJS build verification, and package dry-run validation.

Jest drives both the fast local suite and the live smoke suites. Each live suite automatically skips when its provider key is not set:

npm run test:e2e:e2b
npm run test:e2e:daytona

Optional environment variables:

  • NECKBEARD_E2E_TEMPLATE to override the default code-interpreter-v1 test template
  • NECKBEARD_DAYTONA_TEMPLATE to override the default Daytona smoke snapshot (daytona-small)
  • NECKBEARD_E2E_TIMEOUT_MS to increase the live test timeout for slower environments

License

MIT