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 🙏

© 2025 – Pkg Stats / Ryan Hefner

acp-factory

v0.0.2

Published

A library for spawning and managing agents through the Agent Client Protocol (ACP)

Readme

acp-factory

A TypeScript library for spawning and managing AI agents through the Agent Client Protocol (ACP).

Installation

npm install acp-factory

Prerequisites

  • Node.js 18+
  • Claude Code installed and authenticated (run claude once to set up)
  • Or set ANTHROPIC_API_KEY environment variable

Quick Start

import { AgentFactory } from "acp-factory";

// Spawn a Claude Code agent
const agent = await AgentFactory.spawn("claude-code", {
  permissionMode: "auto-approve",
});

// Create a session
const session = await agent.createSession(process.cwd());

// Send a prompt and stream responses
for await (const update of session.prompt("What files are in this directory?")) {
  if (update.sessionUpdate === "agent_message_chunk") {
    if (update.content.type === "text") {
      process.stdout.write(update.content.text);
    }
  }
}

// Clean up
await agent.close();

API Reference

AgentFactory

Static class for managing agent types and spawning agents.

// List available agents
AgentFactory.listAgents(); // ["claude-code"]

// Register a custom agent
AgentFactory.register("my-agent", {
  command: "npx",
  args: ["my-agent-acp"],
  env: { MY_VAR: "value" },
});

// Spawn an agent
const agent = await AgentFactory.spawn("claude-code", options);

AgentHandle

Represents a running agent process.

// Agent capabilities
agent.capabilities; // { loadSession: true, ... }

// Create a new session
const session = await agent.createSession("/path/to/cwd", {
  mode: "code",           // Optional: initial mode
  mcpServers: [],         // Optional: MCP servers to connect
});

// Load an existing session
const session = await agent.loadSession(sessionId, "/path/to/cwd");

// Fork an existing session (experimental)
const forkedSession = await agent.forkSession(sessionId);

// Close the agent
await agent.close();

// Check if running
agent.isRunning(); // true/false

Session

High-level interface for interacting with an agent session.

// Session properties
session.id;      // Session ID
session.modes;   // Available modes ["code", "ask", ...]
session.models;  // Available models ["claude-sonnet-4-...", ...]

// Send a prompt (returns async iterable)
for await (const update of session.prompt("Hello!")) {
  // Handle updates
}

// Cancel the current prompt
await session.cancel();

// Set the session mode
await session.setMode("ask");

// Interrupt and redirect with new context
for await (const update of session.interruptWith("Focus on tests only")) {
  // Handle updates from new prompt
}

// Fork the session (experimental)
const forkedSession = await session.fork();

Session Updates

The prompt() method yields SessionUpdate objects:

for await (const update of session.prompt("Hello")) {
  switch (update.sessionUpdate) {
    case "agent_message_chunk":
      // Text or image content from the agent
      if (update.content.type === "text") {
        process.stdout.write(update.content.text);
      }
      break;

    case "tool_call":
      // Agent is calling a tool
      console.log(`Tool: ${update.title}`);
      break;

    case "tool_call_update":
      // Tool call status changed
      if (update.status === "completed") {
        console.log("Tool completed");
      }
      break;

    case "agent_thought_chunk":
      // Agent's thinking (if enabled)
      break;
  }
}

Permission Modes

Control how permission requests are handled:

// Auto-approve all requests (default)
await AgentFactory.spawn("claude-code", {
  permissionMode: "auto-approve",
});

// Auto-deny all requests
await AgentFactory.spawn("claude-code", {
  permissionMode: "auto-deny",
});

// Use a callback handler
await AgentFactory.spawn("claude-code", {
  permissionMode: "callback",
  onPermissionRequest: async (request) => {
    // Return permission response
    return {
      outcome: { outcome: "selected", optionId: "allow" },
    };
  },
});

// Interactive mode - permissions emitted as session updates
await AgentFactory.spawn("claude-code", {
  permissionMode: "interactive",
});

Interactive Permissions

With permissionMode: "interactive", permission requests appear as session updates:

import { type ExtendedSessionUpdate } from "acp-factory";

for await (const update of session.prompt("Write a file")) {
  if (update.sessionUpdate === "permission_request") {
    // Show options to user
    console.log(`Permission needed: ${update.toolCall.title}`);
    update.options.forEach((opt, i) => {
      console.log(`${i + 1}. ${opt.name}`);
    });

    // Respond to the request
    session.respondToPermission(update.requestId, update.options[0].optionId);
    // Or cancel it
    // session.cancelPermission(update.requestId);
  }
}

Interrupting and Redirecting

Interrupt an agent mid-execution and redirect with new context:

const promptIterator = session.prompt("Analyze this codebase")[Symbol.asyncIterator]();

while (true) {
  const { done, value } = await promptIterator.next();
  if (done) break;

  handleUpdate(value);

  if (userWantsToRedirect) {
    // Interrupt and provide new direction
    for await (const update of session.interruptWith("Focus only on the tests")) {
      handleUpdate(update);
    }
    break;
  }
}

Forking Sessions

Fork a session to create an independent copy with shared history:

// Original session continues normally
const session = await agent.createSession(process.cwd());
await consumePrompt(session.prompt("Analyze the codebase"));

// Fork to generate summary without affecting original
const summarySession = await session.fork();
for await (const update of summarySession.prompt("Summarize our conversation")) {
  // This doesn't affect the original session
}

// Original session can continue independently
for await (const update of session.prompt("Now implement the feature")) {
  // Continues from original context
}

Custom File and Terminal Handlers

Override default file operations or provide terminal support:

const agent = await AgentFactory.spawn("claude-code", {
  // Custom file handling
  onFileRead: async (path) => {
    return await myCustomRead(path);
  },
  onFileWrite: async (path, content) => {
    await myCustomWrite(path, content);
  },

  // Terminal support (all handlers required)
  onTerminalCreate: async (params) => {
    const id = createTerminal(params.command, params.args);
    return { terminalId: id };
  },
  onTerminalOutput: async (terminalId) => {
    return getTerminalOutput(terminalId);
  },
  onTerminalKill: async (terminalId) => {
    killTerminal(terminalId);
  },
  onTerminalRelease: async (terminalId) => {
    releaseTerminal(terminalId);
  },
  onTerminalWaitForExit: async (terminalId) => {
    return await waitForExit(terminalId);
  },
});

Examples

See the examples directory:

Run examples with:

npx tsx examples/basic-usage.ts

Type Exports

The library exports all necessary types:

import {
  // Core classes
  AgentFactory,
  AgentHandle,
  Session,

  // Configuration types
  type AgentConfig,
  type SpawnOptions,
  type SessionOptions,
  type PermissionMode,

  // Session update types
  type SessionUpdate,
  type ExtendedSessionUpdate,
  type PermissionRequestUpdate,

  // Content types
  type ContentBlock,
  type TextContent,
  type ImageContent,

  // Tool types
  type ToolCall,
  type ToolCallUpdate,

  // Permission types
  type PermissionOption,
  type RequestPermissionRequest,
  type RequestPermissionResponse,
} from "acp-factory";

License

MIT