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

claude-code-controller

v0.6.1

Published

Programmatic API to pilot Claude Code agents via the internal teams/inbox/tasks filesystem protocol

Readme

Three lines of code. Ask Claude a question, get an answer. Spin up persistent agents for multi-turn conversations. Orchestrate entire teams of agents working in parallel. All running real Claude Code — the same one you use in your terminal every day.


Quick Start

npm install claude-code-controller

Prerequisite: Claude Code CLI v2.1.34+

One-liner — ask a question, get an answer

import { claude } from "claude-code-controller";

const answer = await claude("What does this project do?", { model: "sonnet" });
console.log(answer);

That's it. No initialization, no boilerplate, no sleep() hacks. The agent is created, responds, and cleans up automatically.

Persistent agent — multi-turn conversations

const agent = await claude.agent({ model: "sonnet", cwd: "/my/project" });

const review = await agent.ask("Review src/auth.ts for security issues");
const fixes = await agent.ask("Now fix the issues you found");

await agent.close();

The agent retains full context between calls. Ask follow-up questions, build on previous answers, iterate on a task — like pairing with a colleague.

Multi-agent session — parallel teams

const session = await claude.session({ model: "sonnet" });

const reviewer = await session.agent("reviewer", { model: "opus" });
const coder = await session.agent("coder");

const issues = await reviewer.ask("Review src/ for security vulnerabilities");
await coder.ask(`Fix these issues:\n${issues}`);

await session.close();

Each agent has its own process, its own context, its own model. They work in parallel on your codebase. One reviews, another codes, another writes tests — all at the same time.

Auto-cleanup with await using

{
  await using agent = await claude.agent({ model: "sonnet" });
  const answer = await agent.ask("What is 2+2?");
} // agent.close() called automatically

Why this instead of the Agent SDK?

This runs real Claude Code processes. Not a wrapper around the API. Not a simplified -p mode. Actual Claude Code — the same one you use in your terminal every day.

That means:

  • Uses your Claude Code subscription — No separate API key needed. No usage-based billing surprise. If you have a Max plan, your agents run on it.
  • Day 0 features — When Anthropic ships a new Claude Code feature (new tools, new models, better context handling), you get it immediately. No library update needed. No waiting for SDK support.
  • Full tool access — Bash, Read, Write, Edit, Glob, Grep, WebSearch, Task sub-agents... everything Claude Code can do, your agents can do.
  • Real terminal environment — Agents run in a PTY. They can install packages, run tests, use git, call APIs. They work in your actual project directory.
  • Battle-tested agent loop — Claude Code's agent loop is production-hardened. You get all of that for free: retries, error handling, tool orchestration, context management.

Features

  • 3 lines of code — From zero to a working agent in seconds. No boilerplate, no two-step initialization.
  • Multi-turn conversations — Persistent agents that remember context across ask() calls.
  • Multi-agent sessions — Orchestrate teams of agents working in parallel on the same codebase.
  • Permission control — Four presets from full-access to ask-before-every-tool. Auto-approve, allowlists, or inline callbacks.
  • First-class API key support — Pass apiKey and baseUrl directly. No env var wiring.
  • REST API — Control everything over HTTP. Works from any language, any platform.
  • Web Dashboard — Real-time monitoring, agent management, and interactive approvals from your browser.
  • Task management — Create tasks, assign them to agents, track progress, define blocking dependencies.
  • await using support — Automatic cleanup with Symbol.asyncDispose.
  • Any provider — Point agents at any Anthropic-compatible endpoint. Per-agent overrides.
  • Your subscription — Runs on your existing Claude Code plan. No separate API costs.

Permission Control

Every agent gets a permission preset that controls what tools it can use without asking.

| Preset | Behavior | |--------|----------| | "full" (default) | All tools, no approval needed | | "edit" | Auto-approve read/write/bash | | "plan" | Read-only exploration | | "ask" | Fires events for every tool use |

// Full access (default)
const agent = await claude.agent({ permissions: "full" });

// Read-only explorer
const agent = await claude.agent({ permissions: "plan" });

// Ask-mode with auto-approve for safe tools
const agent = await claude.agent({
  permissions: "ask",
  autoApprove: ["Read", "Glob", "Grep"],
});

// Auto-approve everything (YOLO mode)
const agent = await claude.agent({
  permissions: "ask",
  autoApprove: true,
});

Inline callbacks

Handle permission and plan requests with simple callbacks:

const agent = await claude.agent({
  permissions: "ask",
  onPermission: (req) => {
    console.log(`Tool: ${req.toolName} — ${req.description}`);
    req.toolName === "Bash" ? req.reject() : req.approve();
  },
  onPlan: (req) => {
    console.log("Plan:", req.planContent);
    req.approve();
  },
});

Event-based

Or use the event emitter pattern for more control:

const agent = await claude.agent({ permissions: "ask" });

agent.on("permission", (req) => {
  const safe = ["Read", "Glob", "Grep"].includes(req.toolName);
  safe ? req.approve() : req.reject();
});

agent.on("plan", (req) => {
  req.approve();
});

agent.on("message", (text) => console.log(text));
agent.on("error", (err) => console.error(err));
agent.on("exit", (code) => console.log("Agent exited:", code));

REST API

Control Claude Code agents from any language, any platform — just HTTP.

Start a server

import { createApi } from "claude-code-controller/api";

const app = createApi();
Bun.serve({ port: 3000, fetch: app.fetch });

One-liner over HTTP

curl -X POST http://localhost:3000/ask \
  -H "Content-Type: application/json" \
  -d '{"prompt": "What does this project do?", "model": "sonnet"}'
# → { "response": "This project is a..." }

Endpoints

Ask (one-liner)

| Method | Endpoint | Description | |--------|----------|-------------| | POST | /ask | Send a prompt, get a response. Creates an ephemeral agent. |

Session

| Method | Endpoint | Description | |--------|----------|-------------| | GET | /health | Server health & uptime | | GET | /session | Current session info | | POST | /session/init | Initialize a new controller session | | POST | /session/shutdown | Shut down the controller |

Agents

| Method | Endpoint | Description | |--------|----------|-------------| | GET | /agents | List all agents | | POST | /agents | Spawn a new agent | | GET | /agents/:name | Get agent details | | POST | /agents/:name/messages | Send a message to an agent | | POST | /agents/:name/kill | Force-kill an agent | | POST | /agents/:name/shutdown | Request graceful shutdown | | POST | /agents/:name/approve | Approve or reject a plan or permission request |

Tasks

| Method | Endpoint | Description | |--------|----------|-------------| | GET | /tasks | List all tasks | | POST | /tasks | Create a new task | | GET | /tasks/:id | Get task details | | PATCH | /tasks/:id | Update a task | | DELETE | /tasks/:id | Delete a task | | POST | /tasks/:id/assign | Assign task to an agent |

Actions & Broadcasting

| Method | Endpoint | Description | |--------|----------|-------------| | GET | /actions | All pending actions (approvals, idle agents, unassigned tasks) | | POST | /broadcast | Send a message to all agents |

API Examples

Initialize a session with API key:

curl -X POST http://localhost:3000/session/init \
  -H "Content-Type: application/json" \
  -d '{"teamName": "my-team", "apiKey": "sk-ant-...", "baseUrl": "https://api.example.com"}'

Spawn an agent with permissions:

curl -X POST http://localhost:3000/agents \
  -H "Content-Type: application/json" \
  -d '{"name": "reviewer", "model": "opus", "permissions": "edit"}'

Send a message:

curl -X POST http://localhost:3000/agents/reviewer/messages \
  -H "Content-Type: application/json" \
  -d '{"message": "Review src/auth.ts for security vulnerabilities"}'

Create and assign a task:

# Create
curl -X POST http://localhost:3000/tasks \
  -H "Content-Type: application/json" \
  -d '{"subject": "Fix login bug", "description": "Users cannot login with SSO"}'

# Assign
curl -X POST http://localhost:3000/tasks/1/assign \
  -H "Content-Type: application/json" \
  -d '{"agent": "reviewer"}'

Web Dashboard

A built-in web UI for real-time agent management — no code required.

cd web && bun install

Development:

bun run dev          # backend on :3456
bun run dev:vite     # frontend on :5174

Production:

bun run build && bun run start   # everything on :3456

The dashboard gives you:

  • Session management — Initialize with API key, base URL, team name, and working directory
  • Agent spawning — Configure name, type, model, and permission level from a dropdown
  • Live message feed — Real-time messages via WebSocket
  • Approval prompts — Interactive plan and permission approval banners
  • Agent controls — Shutdown or kill agents individually

Real-World Examples

Parallel Code Review

import { claude } from "claude-code-controller";

const session = await claude.session({ model: "sonnet" });

const [security, perf, style] = await Promise.all([
  session.agent("security", { model: "opus" }),
  session.agent("perf"),
  session.agent("style", { model: "haiku" }),
]);

const reviews = await Promise.all([
  security.ask("Review src/ for security vulnerabilities"),
  perf.ask("Review src/ for performance issues"),
  style.ask("Review src/ for code style issues"),
]);

console.log("Security:", reviews[0]);
console.log("Performance:", reviews[1]);
console.log("Style:", reviews[2]);

await session.close();

Task-Based Workflow

import { claude, Session } from "claude-code-controller";

const session = await claude.session({ model: "sonnet" });
const worker = await session.agent("worker");

// Create and assign a task via the underlying controller
const taskId = await session.controller.createTask({
  subject: "Add input validation",
  description: "Add zod validation to all API route handlers in src/routes/",
  owner: "worker",
});

const result = await session.controller.waitForTask(taskId, 120_000);
console.log(`Task ${result.status}: ${result.subject}`);

await session.close();

Custom API Provider

const agent = await claude.agent({
  model: "sonnet",
  apiKey: "sk-ant-...",
  baseUrl: "https://your-proxy.example.com/api/anthropic",
});

const answer = await agent.ask("What framework is this project using?");
await agent.close();

Selective Permission Control

const agent = await claude.agent({
  permissions: "ask",
  onPermission: (req) => {
    const safe = ["Read", "Glob", "Grep", "Task"].includes(req.toolName);
    const review = ["Bash", "Write", "Edit"].includes(req.toolName);

    if (safe) {
      req.approve();
    } else if (review) {
      console.log(`[REVIEW] ${req.toolName}: ${req.description}`);
      req.approve(); // or implement your own review logic
    } else {
      req.reject();
    }
  },
});

Advanced: Full Controller

The simplified API is built on top of ClaudeCodeController — a full-featured class that gives you direct access to the teammate protocol. Use it when you need low-level control over team configuration, inbox polling, or process management.

import { ClaudeCodeController } from "claude-code-controller";

const ctrl = new ClaudeCodeController({
  teamName: "my-team",
  cwd: "/path/to/project",
  env: {
    ANTHROPIC_BASE_URL: "https://your-proxy.example.com",
    ANTHROPIC_AUTH_TOKEN: "sk-ant-...",
  },
  logLevel: "info",
});

await ctrl.init();

Spawning Agents

const agent = await ctrl.spawnAgent({
  name: "coder",
  type: "general-purpose",
  model: "sonnet",
  permissionMode: "bypassPermissions",
  env: { MY_VAR: "value" },
});

AgentHandle

await agent.send("Analyze the codebase structure.");
const response = await agent.receive({ timeout: 30_000 });

const answer = await agent.ask("What framework is this project using?", {
  timeout: 60_000,
});

agent.isRunning;         // boolean
agent.pid;               // process ID
await agent.shutdown();  // graceful
await agent.kill();      // force

Messaging

await ctrl.send("agent-name", "Your instructions here");
await ctrl.broadcast("Everyone stop and report status.");
const msg = await ctrl.receiveAny({ timeout: 30_000 });

Task Management

const taskId = await ctrl.createTask({
  subject: "Add input validation",
  description: "Add zod schemas to all API endpoints",
  owner: "coder",
});

await ctrl.assignTask(taskId, "coder");
const task = await ctrl.waitForTask(taskId, 120_000);

Events

ctrl.on("message", (agent, msg) => console.log(`[${agent}] ${msg.text}`));
ctrl.on("plan:approval_request", (agent, msg) => {
  ctrl.sendPlanApproval(agent, msg.requestId, true);
});
ctrl.on("permission:request", (agent, msg) => {
  ctrl.sendPermissionResponse(agent, msg.requestId, true);
});
ctrl.on("agent:spawned", (name, pid) => console.log(`${name} started`));
ctrl.on("agent:exited", (name, code) => console.log(`${name} exited`));
ctrl.on("idle", (name) => console.log(`${name} is idle`));
ctrl.on("task:completed", (task) => console.log(`Done: ${task.subject}`));

How It Works

Claude Code has an internal "teammate" protocol that uses the filesystem for communication. This library creates the required files, spawns real Claude Code CLI processes via PTY, and communicates with them through inbox files. Agents think they're in a normal team and behave naturally.

~/.claude/
├── teams/{teamName}/
│   ├── config.json                    # Team membership & config
│   └── inboxes/
│       ├── controller.json            # Messages TO controller FROM agents
│       ├── agent-1.json               # Messages TO agent-1 FROM controller
│       └── agent-2.json               # Messages TO agent-2 FROM controller
└── tasks/{teamName}/
    ├── 1.json                         # Task files
    └── 2.json

Architecture:

ClaudeCodeController
├── TeamManager        → Team config CRUD
├── TaskManager        → Task lifecycle management
├── ProcessManager     → PTY-based process spawning
├── InboxPoller        → Polls controller inbox for agent messages
└── AgentHandle[]      → Per-agent convenience wrappers

The flow:

  1. Spawn — Calls claude --teammate-mode auto --agent-id name@team ... via a PTY wrapper
  2. Register — Agent is registered in the team config with its role, model, and permissions
  3. Communicate — Controller writes to inboxes/{agent}.json, agent writes to inboxes/controller.json
  4. Poll — InboxPoller reads the controller inbox every 500ms and fires events
  5. Lock — All file operations use proper-lockfile to prevent corruption from concurrent access

Development

bun install          # install deps
bun test             # run tests
bun run typecheck    # type check
bun run build        # build for distribution

License

MIT