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

@ecs-sandbox/sdk

v0.2.1

Published

TypeScript client SDK for ecs-sandbox — provider-agnostic core with ECS lifecycle adapter

Readme

ecs-sandbox

A minimal, self-hostable sandbox container for AI agents. Turn any ECS task (or Docker container) into an isolated remote execution environment.

Why?

AI agents that interact with code repositories need isolated environments — one per project, branch, or task. Hosted platforms like E2B and Daytona solve this, but lock you into their infrastructure. ecs-sandbox gives you the same primitives (exec, filesystem, lifecycle) as a lightweight container you run on your own infra.

Architecture

┌──────────────────────────────┐
│  Your Agent                  │
│  (Strands, LangChain, etc.)  │
│                              │
│  const sandbox = await       │
│    manager.create("proj-a")  │
│  sandbox.exec("git clone")   │
│  sandbox.files.read("src/")  │
└──────────┬───────────────────┘
           │ HTTP (VPC-internal)
     ┌─────┼─────────┐
     ▼     ▼         ▼
┌────────┐┌────────┐┌────────┐
│Sandbox ││Sandbox ││Sandbox │
│Task A  ││Task B  ││Task C  │
│  :3000 ││  :3000 ││  :3000 │
└────────┘└────────┘└────────┘

Packages

| Package | Description | |---------|-------------| | @ecs-sandbox/server | The sandbox container — a Fastify HTTP server exposing exec, filesystem, and health APIs | | @ecs-sandbox/sdk | TypeScript client SDK with a provider-agnostic core and an ECS lifecycle adapter |

Installation

npm install @ecs-sandbox/sdk
docker pull finnweiler/ecs-sandbox

Quick Start

Run the sandbox container

docker run -p 3000:3000 finnweiler/ecs-sandbox

Use the SDK

import { SandboxClient } from '@ecs-sandbox/sdk';

// Connect to a running sandbox
const sandbox = new SandboxClient('http://localhost:3000');

// Execute commands
const result = await sandbox.exec('echo "hello from sandbox"');
console.log(result.stdout); // "hello from sandbox"

// Read files
const content = await sandbox.files.read('/workspace/README.md');

// Write files
await sandbox.files.write('/workspace/config.json', JSON.stringify({ key: 'value' }));

With ECS lifecycle management

import { EcsSandboxManager } from '@ecs-sandbox/sdk';

const manager = new EcsSandboxManager({
  cluster: 'my-cluster',
  taskDefinition: 'sandbox-task',
  subnets: ['subnet-abc123'],
  securityGroups: ['sg-abc123'],
  namespace: 'sandboxes.local', // Cloud Map namespace
});

// Spin up an isolated sandbox for a project
const sandbox = await manager.create('project-alpha');

// Use it
await sandbox.exec('git clone https://github.com/org/repo.git /workspace');
await sandbox.exec('cd /workspace && npm install');
const result = await sandbox.exec('cd /workspace && npm test');

// Tear it down
await manager.destroy('project-alpha');

Auto-shutdown on idle

Sandboxes can self-destruct after a period of inactivity, so a forgotten task doesn't sit around burning Fargate minutes.

const manager = new EcsSandboxManager({
  cluster: 'my-cluster',
  taskDefinition: 'sandbox-task',
  subnets: ['subnet-abc123'],
  securityGroups: ['sg-abc123'],

  idleTimeout: 300,         // SDK destroys the sandbox after 5 min of no requests
  idleTimeoutBuffer: 600,   // Server-side backup IDLE_TIMEOUT = 300 + 600 = 900s
});

const sandbox = await manager.create('project-alpha');
await sandbox.exec('npm test');
// ...5 minutes of silence later: manager.destroy('project-alpha') fires automatically.

How it works:

  • The SDK keeps a per-sandbox idle timer that resets on every non-/health request (exec, files.*, setEnv, streaming exec). On expiry it calls manager.destroy(id)StopTask.
  • The container's own IDLE_TIMEOUT env var (set to idleTimeout + idleTimeoutBuffer) acts as a backup for the case where the SDK process dies before its timer can fire. When the container exits, ECS marks the task STOPPED.
  • Long-running exec/execStream calls are not paused — by design, a command that exceeds idleTimeout will be killed mid-stream.
  • Per-call override: manager.create('id', { idleTimeout: 60 }).

Reconciling with reality

The manager keeps an in-memory map of the sandboxes it created. After a process restart, that map is empty even if tasks are still running. reconcile() syncs it back up.

const manager = new EcsSandboxManager({
  cluster: 'my-cluster',
  taskDefinition: 'sandbox-task',
  subnets: ['subnet-abc123'],
  securityGroups: ['sg-abc123'],
  autoReconcile: true, // Run reconcile() once during construction
});

await manager.ready(); // Optional: wait for the initial reconcile

// Or manually at any time:
const { adopted, pruned } = await manager.reconcile();
console.log(`Adopted ${adopted.length} orphan tasks, pruned ${pruned.length} dead entries`);

reconcile():

  • Adopts any RUNNING ECS task tagged ecs-sandbox:id=<id> that isn't in the map.
  • Prunes entries whose /health fails and whose ECS task is no longer RUNNING (the ECS check avoids false-positive prunes on transient network blips).

With Strands Agents

import { Agent } from '@strands-agents/sdk';
import { SandboxClient, createSandboxTools } from '@ecs-sandbox/sdk';

const client = new SandboxClient('http://localhost:3000');
const agent = new Agent({
  tools: [...createSandboxTools(client)],
});

await agent.invoke('Clone the repo and run the tests');

createSandboxTools returns four tools: sandbox_exec, sandbox_read_file, sandbox_write_file, and sandbox_remove_file. Requires @strands-agents/sdk and zod as peer dependencies.

With GitHub App Authentication

Authenticate the gh CLI and git inside a sandbox using a GitHub App. The private key stays in your agent's process — only short-lived installation tokens are sent to the sandbox.

import { SandboxClient, setupGitHubAuth } from '@ecs-sandbox/sdk';
import { readFileSync } from 'fs';

const client = new SandboxClient('http://localhost:3000');

const auth = await setupGitHubAuth(client, {
  appId: '123456',
  installationId: '78901234',
  privateKey: readFileSync('private-key.pem', 'utf-8'),
});

// gh and git are now authenticated inside the sandbox
await client.exec('gh repo clone org/private-repo /workspace');
await client.exec('cd /workspace && git push');

// Stop the automatic token refresh when done
auth.stop();

Tokens are refreshed automatically every 50 minutes (GitHub installation tokens expire after 1 hour). Call auth.refresh() to force an immediate refresh.

Scoping the token

By default the token grants access to every repository the installation can reach. To narrow it, pass any of repositories, repositoryIds, or permissions:

const auth = await setupGitHubAuth(client, {
  appId: '123456',
  installationId: '78901234',
  privateKey: readFileSync('private-key.pem', 'utf-8'),
  repositories: ['repo-a', 'repo-b'],
  permissions: { contents: 'read', pull_requests: 'write' },
});

repositories / repositoryIds must be a subset of the repos the installation can access, and permissions must be a subset of what the App is granted.

API Reference

Sandbox Server Endpoints

| Method | Path | Description | |--------|------|-------------| | POST | /exec | Execute a command | | POST | /exec/stream | Execute with SSE streaming | | GET | /files | Read file or list directory | | POST | /files | Write file | | DELETE | /files | Delete file or directory | | POST | /env | Set environment variables | | GET | /health | Health check |

POST /exec

// Request
{ "command": "git status", "cwd": "/workspace", "timeout": 30000 }

// Response
{ "stdout": "On branch main\n...", "stderr": "", "exitCode": 0, "durationMs": 42 }

POST /exec/stream

Same request body as /exec, returns an SSE stream:

data: {"type":"stdout","data":"Installing dependencies...\n"}
data: {"type":"stdout","data":"Done.\n"}
data: {"type":"exit","exitCode":0,"durationMs":1234}

GET /files?path=/workspace/src

// File response
{ "type": "file", "path": "/workspace/src/index.ts", "content": "...", "size": 1234 }

// Directory response
{ "type": "directory", "path": "/workspace/src", "entries": [
  { "name": "index.ts", "type": "file", "size": 1234 },
  { "name": "utils", "type": "directory" }
]}

POST /files

{ "path": "/workspace/config.json", "content": "{\"key\": \"value\"}" }

Configuration

Sandbox Server

| Env Var | Default | Description | |---------|---------|-------------| | PORT | 3000 | Server port | | WORKSPACE_DIR | /workspace | Default working directory | | AUTH_TOKEN | — | Optional bearer token for authentication | | MAX_COMMAND_TIMEOUT | 300000 | Maximum command execution time (ms) | | IDLE_TIMEOUT | 0 | Backup auto-shutdown after N seconds of no requests (excluding /health). 0 disables. Container exits → on ECS the task stops. The SDK is the primary enforcer; this is the safety net for when the SDK process is gone. |

Security

The sandbox container is designed to run in a private VPC. Do not expose it to the public internet. Use security groups to restrict access to your agent's ECS service only.

For additional security, set AUTH_TOKEN to require bearer token authentication on all requests.

License

MIT