Big News: Socket raises $60M Series C at a $1B valuation to secure software supply chains for AI-driven development.Announcement
Sign In

@zed-industries/claude-code-acp

Package Overview
Dependencies
Maintainers
8
Versions
73
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@zed-industries/claude-code-acp - npm Package Compare versions

Comparing version
0.12.2
to
0.12.3
+10
-5
dist/acp-agent.js

@@ -11,2 +11,3 @@ import { AgentSideConnection, ndJsonStream, RequestError, } from "@agentclientprotocol/sdk";

import { randomUUID } from "node:crypto";
export const CLAUDE_CONFIG_DIR = process.env.CLAUDE ?? path.join(os.homedir(), ".claude");
// Bypass Permissions doesn't work if we are a root/sudo user

@@ -68,3 +69,5 @@ const IS_ROOT = (process.geteuid?.() ?? process.getuid?.()) === 0;

}
const sessionId = randomUUID();
// Extract options from _meta if provided
const userProvidedOptions = params._meta?.claudeCode?.options;
const sessionId = userProvidedOptions?.resume || randomUUID();
const input = new Pushable();

@@ -117,4 +120,7 @@ const mcpServers = {};

const permissionMode = "default";
// Extract options from _meta if provided
const userProvidedOptions = params._meta?.claudeCode?.options;
const extraArgs = { ...userProvidedOptions?.extraArgs };
if (userProvidedOptions?.resume === undefined) {
// Set our own session id if not resuming an existing session.
extraArgs["session-id"] = sessionId;
}
const options = {

@@ -129,4 +135,3 @@ systemPrompt,

mcpServers: { ...(userProvidedOptions?.mcpServers || {}), ...mcpServers },
// Set our own session id
extraArgs: { ...userProvidedOptions?.extraArgs, "session-id": sessionId },
extraArgs,
// If we want bypassPermissions to be an option, we have to allow it here.

@@ -133,0 +138,0 @@ // But it doesn't work in root mode, so we only activate it if it will work.

@@ -55,3 +55,6 @@ var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) {

import { z } from "zod";
import { CLAUDE_CONFIG_DIR } from "./acp-agent.js";
import * as diff from "diff";
import * as path from "node:path";
import * as fs from "node:fs/promises";
import { sleep, unreachable, extractLinesWithByteLimit } from "./utils.js";

@@ -83,2 +86,50 @@ export const SYSTEM_REMINDER = `

export function createMcpServer(agent, sessionId, clientCapabilities) {
/**
* This checks if a given path is related to internal agent persistence and if the agent should be allowed to read/write from here.
* We let the agent do normal fs operations on these paths so that it can persist its state.
* However, we block access to settings files for security reasons.
*/
function internalPath(file_path) {
return (file_path.startsWith(CLAUDE_CONFIG_DIR) &&
!file_path.startsWith(path.join(CLAUDE_CONFIG_DIR, "settings.json")) &&
!file_path.startsWith(path.join(CLAUDE_CONFIG_DIR, "session-env")));
}
async function readTextFile(input) {
if (internalPath(input.file_path)) {
const content = await fs.readFile(input.file_path, "utf8");
// eslint-disable-next-line eqeqeq
if (input.offset != null || input.limit != null) {
const lines = content.split("\n");
// Apply offset and limit if provided
const offset = input.offset ?? 1;
const limit = input.limit ?? lines.length;
// Extract the requested lines (offset is 1-based)
const startIndex = Math.max(0, offset - 1);
const endIndex = Math.min(lines.length, startIndex + limit);
const selectedLines = lines.slice(startIndex, endIndex);
return { content: selectedLines.join("\n") };
}
else {
return { content };
}
}
return agent.readTextFile({
sessionId,
path: input.file_path,
line: input.offset,
limit: input.limit,
});
}
async function writeTextFile(input) {
if (internalPath(input.file_path)) {
await fs.writeFile(input.file_path, input.content, "utf8");
}
else {
await agent.writeTextFile({
sessionId,
path: input.file_path,
content: input.content,
});
}
}
// Create MCP server

@@ -136,8 +187,3 @@ const server = new McpServer({ name: "acp", version: "1.0.0" }, { capabilities: { tools: {} } });

}
const readResponse = await agent.readTextFile({
sessionId,
path: input.file_path,
line: input.offset,
limit: input.limit,
});
const readResponse = await readTextFile(input);
if (typeof readResponse?.content !== "string") {

@@ -150,3 +196,3 @@ throw new Error(`No file contents for ${input.file_path}.`);

let readInfo = "";
if (input.offset > 1 || result.wasLimited) {
if ((input.offset && input.offset > 1) || result.wasLimited) {
readInfo = "\n\n<file-read-info>";

@@ -225,7 +271,3 @@ if (result.wasLimited) {

}
await agent.writeTextFile({
sessionId,
path: input.file_path,
content: input.content,
});
await writeTextFile(input);
return {

@@ -270,3 +312,3 @@ content: [],

.optional()
.describe("Replace all occurences of old_string (default false)"),
.describe("Replace all occurrences of old_string (default false)"),
},

@@ -293,5 +335,4 @@ annotations: {

}
const readResponse = await agent.readTextFile({
sessionId,
path: input.file_path,
const readResponse = await readTextFile({
file_path: input.file_path,
});

@@ -309,7 +350,3 @@ if (typeof readResponse?.content !== "string") {

const patch = diff.createPatch(input.file_path, readResponse.content, newContent);
await agent.writeTextFile({
sessionId,
path: input.file_path,
content: newContent,
});
await writeTextFile({ file_path: input.file_path, content: newContent });
return {

@@ -344,6 +381,3 @@ content: [

command: z.string().describe("The command to execute"),
timeout: z
.number()
.default(2 * 60 * 1000)
.describe(`Optional timeout in milliseconds (max ${2 * 60 * 1000})`),
timeout: z.number().describe(`Optional timeout in milliseconds (max ${2 * 60 * 1000})`),
description: z.string().optional()

@@ -417,3 +451,3 @@ .describe(`Clear, concise description of what this command does in 5-10 words, in active voice. Examples:

abortPromise.then(() => ({ status: "aborted", exitStatus: null })),
sleep(input.timeout).then(async () => {
sleep(input.timeout ?? 2 * 60 * 1000).then(async () => {
if (agent.backgroundTerminals[handle.id]?.status === "started") {

@@ -481,3 +515,3 @@ await handle.kill();

description: `- Retrieves output from a running or completed background bash shell
- Takes a shell_id parameter identifying the shell
- Takes a bash_id parameter identifying the shell
- Always returns only new output since the last check

@@ -487,5 +521,5 @@ - Returns stdout and stderr output along with shell status

In sessions with ${toolNames.bashOutput} always use it instead of BashOutput.`,
In sessions with ${toolNames.bashOutput} always use it for output from Bash commands instead of TaskOutput.`,
inputSchema: {
shell_id: z
bash_id: z
.string()

@@ -495,5 +529,5 @@ .describe(`The id of the background bash command as returned by \`${toolNames.bash}\``),

}, async (input) => {
const bgTerm = agent.backgroundTerminals[input.shell_id];
const bgTerm = agent.backgroundTerminals[input.bash_id];
if (!bgTerm) {
throw new Error(`Unknown shell ${input.shell_id}`);
throw new Error(`Unknown shell ${input.bash_id}`);
}

@@ -500,0 +534,0 @@ if (bgTerm.status === "started") {

@@ -6,3 +6,3 @@ {

},
"version": "0.12.2",
"version": "0.12.3",
"description": "An ACP-compatible coding agent powered by the Claude Code SDK (TypeScript)",

@@ -55,4 +55,4 @@ "main": "dist/lib.js",

"dependencies": {
"@agentclientprotocol/sdk": "0.8.0",
"@anthropic-ai/claude-agent-sdk": "0.1.61",
"@agentclientprotocol/sdk": "0.9.0",
"@anthropic-ai/claude-agent-sdk": "0.1.65",
"@modelcontextprotocol/sdk": "1.24.3",

@@ -63,5 +63,5 @@ "diff": "8.0.2"

"@anthropic-ai/sdk": "0.71.2",
"@types/node": "24.10.1",
"@typescript-eslint/eslint-plugin": "8.48.1",
"@typescript-eslint/parser": "8.48.1",
"@types/node": "25.0.0",
"@typescript-eslint/eslint-plugin": "8.49.0",
"@typescript-eslint/parser": "8.49.0",
"eslint": "9.39.1",

@@ -68,0 +68,0 @@ "eslint-config-prettier": "10.1.8",