@sudocode-ai/claude-code-acp 0.12.10 → 0.13.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  Use [Claude Code](https://www.anthropic.com/claude-code) from [ACP-compatible](https://agentclientprotocol.com) clients such as [Zed](https://zed.dev)!
6
6
 
7
- This tool implements an ACP agent by using the official [Claude Code SDK](https://docs.anthropic.com/en/docs/claude-code/sdk/sdk-overview), supporting:
7
+ This tool implements an ACP agent by using the official [Claude Agent SDK](https://platform.claude.com/docs/en/agent-sdk/overview), supporting:
8
8
 
9
9
  - Context @-mentions
10
10
  - Images
@@ -32,20 +32,14 @@ Read the docs on [External Agent](https://zed.dev/docs/ai/external-agents) suppo
32
32
 
33
33
  ### Other clients
34
34
 
35
- - Emacs via [agent-shell.el](https://github.com/xenodium/agent-shell)
36
- - [marimo notebook](https://github.com/marimo-team/marimo)
37
- - Neovim
38
- - via [CodeCompanion.nvim](https://codecompanion.olimorris.dev/configuration/adapters#setup-claude-code-via-acp)
39
- - via [yetone/avante.nvim](https://github.com/yetone/avante.nvim)
40
-
41
- [Submit a PR](https://github.com/zed-industries/claude-code-acp/pulls) to add yours!
35
+ Or try it with any of the other [ACP compatible clients](https://agentclientprotocol.com/overview/clients)!
42
36
 
43
37
  #### Installation
44
38
 
45
39
  Install the adapter from `npm`:
46
40
 
47
41
  ```bash
48
- npm install @zed-industries/claude-code-acp
42
+ npm install -g @zed-industries/claude-code-acp
49
43
  ```
50
44
 
51
45
  You can then use `claude-code-acp` as a regular ACP agent:
@@ -0,0 +1,194 @@
1
+ import { Agent, AgentSideConnection, AuthenticateRequest, CancelNotification, ClientCapabilities, ForkSessionRequest, ForkSessionResponse, InitializeRequest, InitializeResponse, LoadSessionRequest, LoadSessionResponse, NewSessionRequest, NewSessionResponse, PromptRequest, PromptResponse, ReadTextFileRequest, ReadTextFileResponse, SessionNotification, SetSessionModelRequest, SetSessionModelResponse, SetSessionModeRequest, SetSessionModeResponse, TerminalHandle, TerminalOutputResponse, WriteTextFileRequest, WriteTextFileResponse } from "@agentclientprotocol/sdk";
2
+ import { SettingsManager } from "./settings.js";
3
+ import { CanUseTool, Options, PermissionMode, Query, SDKPartialAssistantMessage, SDKUserMessage } from "@anthropic-ai/claude-agent-sdk";
4
+ import { Pushable } from "./utils.js";
5
+ import { ContentBlockParam } from "@anthropic-ai/sdk/resources";
6
+ import { BetaContentBlock, BetaRawContentBlockDelta } from "@anthropic-ai/sdk/resources/beta.mjs";
7
+ export declare const CLAUDE_CONFIG_DIR: string;
8
+ /**
9
+ * Logger interface for customizing logging output
10
+ */
11
+ export interface Logger {
12
+ log: (...args: any[]) => void;
13
+ error: (...args: any[]) => void;
14
+ }
15
+ type Session = {
16
+ query: Query;
17
+ input: Pushable<SDKUserMessage>;
18
+ cancelled: boolean;
19
+ permissionMode: PermissionMode;
20
+ settingsManager: SettingsManager;
21
+ abortController: AbortController;
22
+ cwd: string;
23
+ /** Optional: the actual session file path (for forked sessions where filename differs from sessionId) */
24
+ sessionFilePath?: string;
25
+ };
26
+ type BackgroundTerminal = {
27
+ handle: TerminalHandle;
28
+ status: "started";
29
+ lastOutput: TerminalOutputResponse | null;
30
+ } | {
31
+ status: "aborted" | "exited" | "killed" | "timedOut";
32
+ pendingOutput: TerminalOutputResponse;
33
+ };
34
+ /**
35
+ * Extra metadata that can be given to Claude Code when creating a new session.
36
+ */
37
+ export type NewSessionMeta = {
38
+ claudeCode?: {
39
+ /**
40
+ * Options forwarded to Claude Code when starting a new session.
41
+ * Those parameters will be ignored and managed by ACP:
42
+ * - cwd
43
+ * - includePartialMessages
44
+ * - allowDangerouslySkipPermissions
45
+ * - permissionMode
46
+ * - canUseTool
47
+ * - executable
48
+ * Those parameters will be used and updated to work with ACP:
49
+ * - hooks (merged with ACP's hooks)
50
+ * - mcpServers (merged with ACP's mcpServers)
51
+ */
52
+ options?: Options;
53
+ };
54
+ };
55
+ /**
56
+ * Extra metadata that the agent provides for each tool_call / tool_update update.
57
+ */
58
+ export type ToolUpdateMeta = {
59
+ claudeCode?: {
60
+ toolName: string;
61
+ toolResponse?: unknown;
62
+ };
63
+ };
64
+ type ToolUseCache = {
65
+ [key: string]: {
66
+ type: "tool_use" | "server_tool_use" | "mcp_tool_use";
67
+ id: string;
68
+ name: string;
69
+ input: any;
70
+ };
71
+ };
72
+ export declare class ClaudeAcpAgent implements Agent {
73
+ sessions: {
74
+ [key: string]: Session;
75
+ };
76
+ client: AgentSideConnection;
77
+ toolUseCache: ToolUseCache;
78
+ backgroundTerminals: {
79
+ [key: string]: BackgroundTerminal;
80
+ };
81
+ clientCapabilities?: ClientCapabilities;
82
+ logger: Logger;
83
+ constructor(client: AgentSideConnection, logger?: Logger);
84
+ initialize(request: InitializeRequest): Promise<InitializeResponse>;
85
+ newSession(params: NewSessionRequest): Promise<NewSessionResponse>;
86
+ /**
87
+ * Fork an existing session to create a new independent session.
88
+ * This is the ACP protocol method handler for session/fork.
89
+ * Named unstable_forkSession to match SDK expectations (session/fork routes to this method).
90
+ */
91
+ unstable_forkSession(params: ForkSessionRequest): Promise<ForkSessionResponse>;
92
+ /**
93
+ * Get the directory where session files are stored for a given cwd.
94
+ */
95
+ private getSessionDirPath;
96
+ /**
97
+ * Extract the internal sessionId from a session JSONL file.
98
+ * The CLI stores the actual session ID inside the file, which may differ from the filename.
99
+ * For forked sessions, the filename is "agent-xxx" but the internal sessionId is a UUID.
100
+ */
101
+ private extractInternalSessionId;
102
+ /**
103
+ * Promote a sidechain session to a regular session by modifying the session file.
104
+ * Forked sessions have "isSidechain": true which prevents them from being resumed.
105
+ * This method changes it to false so the session can be resumed/forked again.
106
+ */
107
+ private promoteToFullSession;
108
+ /**
109
+ * Update the sessionId in all lines of a session JSONL file.
110
+ * This is used when we need to assign a new unique session ID to avoid collisions.
111
+ */
112
+ private updateSessionIdInFile;
113
+ /**
114
+ * Discover the CLI-assigned session ID by looking for new session files.
115
+ * Returns the CLI's session ID if found, or the original sessionId if not.
116
+ */
117
+ private discoverCliSessionId;
118
+ /**
119
+ * Alias for unstable_forkSession for convenience.
120
+ */
121
+ forkSession(params: ForkSessionRequest): Promise<ForkSessionResponse>;
122
+ /**
123
+ * Load an existing session to resume a previous conversation.
124
+ * This is the ACP protocol method handler for session/load.
125
+ */
126
+ loadSession(params: LoadSessionRequest): Promise<LoadSessionResponse>;
127
+ /**
128
+ * @deprecated Use loadSession instead. This is kept for backward compatibility.
129
+ */
130
+ unstable_resumeSession(params: {
131
+ sessionId: string;
132
+ _meta?: {
133
+ cwd?: string;
134
+ mcpServers?: any[];
135
+ [key: string]: unknown;
136
+ } | null;
137
+ }): Promise<LoadSessionResponse>;
138
+ authenticate(_params: AuthenticateRequest): Promise<void>;
139
+ prompt(params: PromptRequest): Promise<PromptResponse>;
140
+ cancel(params: CancelNotification): Promise<void>;
141
+ /**
142
+ * Handle extension methods from the client.
143
+ *
144
+ * Currently supports:
145
+ * - `_session/flush`: Flush a session to disk for fork-with-flush support
146
+ */
147
+ extMethod(method: string, params: Record<string, unknown>): Promise<Record<string, unknown>>;
148
+ /**
149
+ * Flush a session to disk by aborting its query subprocess.
150
+ *
151
+ * This is used by the fork-with-flush mechanism to ensure session data
152
+ * is persisted to disk before forking. When the Claude SDK subprocess
153
+ * exits (via abort), it writes the session data to:
154
+ * ~/.claude/projects/<cwd-hash>/<sessionId>.jsonl
155
+ *
156
+ * After this method completes, the session is removed from memory and
157
+ * must be reloaded via loadSession() to continue using it.
158
+ */
159
+ private handleSessionFlush;
160
+ /**
161
+ * Get the file path where Claude Code stores session data.
162
+ *
163
+ * Claude Code stores sessions at:
164
+ * ~/.claude/projects/<cwd-hash>/<sessionId>.jsonl
165
+ *
166
+ * Where <cwd-hash> is the cwd with `/` replaced by `-`
167
+ * Note: We resolve the real path to handle macOS symlinks like /var -> /private/var
168
+ */
169
+ private getSessionFilePath;
170
+ /**
171
+ * Wait for a session file to appear on disk.
172
+ *
173
+ * @param filePath - Path to the session file
174
+ * @param timeout - Maximum time to wait in milliseconds
175
+ * @returns true if file appears, false if timeout
176
+ */
177
+ private waitForSessionFile;
178
+ unstable_setSessionModel(params: SetSessionModelRequest): Promise<SetSessionModelResponse | void>;
179
+ setSessionMode(params: SetSessionModeRequest): Promise<SetSessionModeResponse>;
180
+ readTextFile(params: ReadTextFileRequest): Promise<ReadTextFileResponse>;
181
+ writeTextFile(params: WriteTextFileRequest): Promise<WriteTextFileResponse>;
182
+ canUseTool(sessionId: string): CanUseTool;
183
+ private createSession;
184
+ }
185
+ export declare function promptToClaude(prompt: PromptRequest): SDKUserMessage;
186
+ /**
187
+ * Convert an SDKAssistantMessage (Claude) to a SessionNotification (ACP).
188
+ * Only handles text, image, and thinking chunks for now.
189
+ */
190
+ export declare function toAcpNotifications(content: string | ContentBlockParam[] | BetaContentBlock[] | BetaRawContentBlockDelta[], role: "assistant" | "user", sessionId: string, toolUseCache: ToolUseCache, client: AgentSideConnection, logger: Logger): SessionNotification[];
191
+ export declare function streamEventToAcpNotifications(message: SDKPartialAssistantMessage, sessionId: string, toolUseCache: ToolUseCache, client: AgentSideConnection, logger: Logger): SessionNotification[];
192
+ export declare function runAcp(): void;
193
+ export {};
194
+ //# sourceMappingURL=acp-agent.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"acp-agent.d.ts","sourceRoot":"","sources":["../src/acp-agent.ts"],"names":[],"mappings":"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"}
package/dist/acp-agent.js CHANGED
@@ -54,6 +54,7 @@ export class ClaudeAcpAgent {
54
54
  },
55
55
  sessionCapabilities: {
56
56
  fork: {},
57
+ resume: {},
57
58
  },
58
59
  loadSession: true,
59
60
  },
@@ -84,7 +85,7 @@ export class ClaudeAcpAgent {
84
85
  // Get the session directory to track new files
85
86
  const sessionDir = this.getSessionDirPath(params.cwd);
86
87
  const beforeFiles = new Set(fs.existsSync(sessionDir)
87
- ? fs.readdirSync(sessionDir).filter(f => f.endsWith('.jsonl'))
88
+ ? fs.readdirSync(sessionDir).filter((f) => f.endsWith(".jsonl"))
88
89
  : []);
89
90
  const result = await this.createSession({
90
91
  cwd: params.cwd,
@@ -95,7 +96,7 @@ export class ClaudeAcpAgent {
95
96
  forkSession: true,
96
97
  });
97
98
  // Wait briefly for CLI to create the session file
98
- await new Promise(resolve => setTimeout(resolve, 200));
99
+ await new Promise((resolve) => setTimeout(resolve, 200));
99
100
  // Find the CLI-assigned session ID by looking for new session files
100
101
  const cliSessionId = await this.discoverCliSessionId(sessionDir, beforeFiles, result.sessionId);
101
102
  if (cliSessionId && cliSessionId !== result.sessionId) {
@@ -167,13 +168,13 @@ export class ClaudeAcpAgent {
167
168
  if (!fs.existsSync(filePath)) {
168
169
  return null;
169
170
  }
170
- const content = fs.readFileSync(filePath, 'utf-8');
171
- const firstLine = content.split('\n').find(line => line.trim().length > 0);
171
+ const content = fs.readFileSync(filePath, "utf-8");
172
+ const firstLine = content.split("\n").find((line) => line.trim().length > 0);
172
173
  if (!firstLine) {
173
174
  return null;
174
175
  }
175
176
  const parsed = JSON.parse(firstLine);
176
- if (parsed.sessionId && typeof parsed.sessionId === 'string') {
177
+ if (parsed.sessionId && typeof parsed.sessionId === "string") {
177
178
  // Verify it's a UUID format
178
179
  const isUuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(parsed.sessionId);
179
180
  if (isUuid) {
@@ -197,8 +198,8 @@ export class ClaudeAcpAgent {
197
198
  if (!fs.existsSync(filePath)) {
198
199
  return false;
199
200
  }
200
- const content = fs.readFileSync(filePath, 'utf-8');
201
- const lines = content.split('\n');
201
+ const content = fs.readFileSync(filePath, "utf-8");
202
+ const lines = content.split("\n");
202
203
  const modifiedLines = [];
203
204
  for (const line of lines) {
204
205
  if (!line.trim()) {
@@ -218,7 +219,7 @@ export class ClaudeAcpAgent {
218
219
  modifiedLines.push(line);
219
220
  }
220
221
  }
221
- fs.writeFileSync(filePath, modifiedLines.join('\n'), 'utf-8');
222
+ fs.writeFileSync(filePath, modifiedLines.join("\n"), "utf-8");
222
223
  this.logger.log(`[claude-code-acp] Promoted sidechain to full session: ${filePath}`);
223
224
  return true;
224
225
  }
@@ -236,8 +237,8 @@ export class ClaudeAcpAgent {
236
237
  if (!fs.existsSync(filePath)) {
237
238
  return false;
238
239
  }
239
- const content = fs.readFileSync(filePath, 'utf-8');
240
- const lines = content.split('\n');
240
+ const content = fs.readFileSync(filePath, "utf-8");
241
+ const lines = content.split("\n");
241
242
  const modifiedLines = [];
242
243
  for (const line of lines) {
243
244
  if (!line.trim()) {
@@ -247,7 +248,7 @@ export class ClaudeAcpAgent {
247
248
  try {
248
249
  const parsed = JSON.parse(line);
249
250
  // Update the sessionId in each line
250
- if (parsed.sessionId && typeof parsed.sessionId === 'string') {
251
+ if (parsed.sessionId && typeof parsed.sessionId === "string") {
251
252
  parsed.sessionId = newSessionId;
252
253
  }
253
254
  modifiedLines.push(JSON.stringify(parsed));
@@ -257,7 +258,7 @@ export class ClaudeAcpAgent {
257
258
  modifiedLines.push(line);
258
259
  }
259
260
  }
260
- fs.writeFileSync(filePath, modifiedLines.join('\n'), 'utf-8');
261
+ fs.writeFileSync(filePath, modifiedLines.join("\n"), "utf-8");
261
262
  this.logger.log(`[claude-code-acp] Updated session ID in file: ${filePath}`);
262
263
  return true;
263
264
  }
@@ -276,18 +277,18 @@ export class ClaudeAcpAgent {
276
277
  const agentPattern = /^agent-[a-f0-9]+\.jsonl$/;
277
278
  while (Date.now() - start < timeout) {
278
279
  if (fs.existsSync(sessionDir)) {
279
- const currentFiles = fs.readdirSync(sessionDir).filter(f => f.endsWith('.jsonl'));
280
+ const currentFiles = fs.readdirSync(sessionDir).filter((f) => f.endsWith(".jsonl"));
280
281
  // Only look for new files that match the agent-xxx pattern
281
282
  // This prevents picking up renamed UUID files from previous forks
282
- const newFiles = currentFiles.filter(f => !beforeFiles.has(f) && agentPattern.test(f));
283
+ const newFiles = currentFiles.filter((f) => !beforeFiles.has(f) && agentPattern.test(f));
283
284
  if (newFiles.length === 1) {
284
285
  // Found exactly one new agent session file - this is our fork
285
286
  this.logger.log(`[claude-code-acp] Discovered fork session file: ${newFiles[0]}`);
286
- return newFiles[0].replace('.jsonl', '');
287
+ return newFiles[0].replace(".jsonl", "");
287
288
  }
288
289
  else if (newFiles.length > 1) {
289
290
  // Multiple new agent files - try to find the most recent one
290
- let newestFile = '';
291
+ let newestFile = "";
291
292
  let newestMtime = 0;
292
293
  for (const file of newFiles) {
293
294
  const filePath = path.join(sessionDir, file);
@@ -299,11 +300,11 @@ export class ClaudeAcpAgent {
299
300
  }
300
301
  if (newestFile) {
301
302
  this.logger.log(`[claude-code-acp] Discovered fork session file (newest of ${newFiles.length}): ${newestFile}`);
302
- return newestFile.replace('.jsonl', '');
303
+ return newestFile.replace(".jsonl", "");
303
304
  }
304
305
  }
305
306
  }
306
- await new Promise(resolve => setTimeout(resolve, 100));
307
+ await new Promise((resolve) => setTimeout(resolve, 100));
307
308
  }
308
309
  // Timeout - return fallback
309
310
  this.logger.log(`[claude-code-acp] Could not discover CLI session ID, using fallback: ${fallbackId}`);
@@ -568,7 +569,7 @@ export class ClaudeAcpAgent {
568
569
  if (fs.existsSync(filePath)) {
569
570
  return true;
570
571
  }
571
- await new Promise(resolve => setTimeout(resolve, 100));
572
+ await new Promise((resolve) => setTimeout(resolve, 100));
572
573
  }
573
574
  return false;
574
575
  }
@@ -728,9 +729,18 @@ export class ClaudeAcpAgent {
728
729
  };
729
730
  }
730
731
  async createSession(params, creationOpts = {}) {
731
- // When forking, generate a new session ID (fork creates a new independent session)
732
- // When resuming (not fork), use the original session ID
733
- const sessionId = creationOpts.forkSession ? randomUUID() : (creationOpts.resume ?? randomUUID());
732
+ // We want to create a new session id unless it is resume,
733
+ // but not resume + forkSession.
734
+ let sessionId;
735
+ if (creationOpts.forkSession) {
736
+ sessionId = randomUUID();
737
+ }
738
+ else if (creationOpts.resume) {
739
+ sessionId = creationOpts.resume;
740
+ }
741
+ else {
742
+ sessionId = randomUUID();
743
+ }
734
744
  const input = new Pushable();
735
745
  const settingsManager = new SettingsManager(params.cwd, {
736
746
  logger: this.logger,
@@ -785,16 +795,21 @@ export class ClaudeAcpAgent {
785
795
  // Extract options from _meta if provided
786
796
  const userProvidedOptions = params._meta?.claudeCode?.options;
787
797
  const extraArgs = { ...userProvidedOptions?.extraArgs };
788
- if (creationOpts?.resume === undefined) {
798
+ if (creationOpts?.resume === undefined || creationOpts?.forkSession) {
789
799
  // Set our own session id if not resuming an existing session.
790
800
  // Note: For forked sessions (resume + fork), Claude CLI assigns its own session ID
791
801
  // which means chain forking (fork of a fork) is not currently supported.
792
802
  extraArgs["session-id"] = sessionId;
793
803
  }
804
+ // Configure thinking tokens from environment variable
805
+ const maxThinkingTokens = process.env.MAX_THINKING_TOKENS
806
+ ? parseInt(process.env.MAX_THINKING_TOKENS, 10)
807
+ : undefined;
794
808
  const options = {
795
809
  systemPrompt,
796
810
  settingSources: ["user", "project", "local"],
797
811
  stderr: (err) => this.logger.error(err),
812
+ ...(maxThinkingTokens !== undefined && { maxThinkingTokens }),
798
813
  ...userProvidedOptions,
799
814
  // Override certain fields that must be controlled by ACP
800
815
  cwd: params.cwd,
@@ -812,6 +827,7 @@ export class ClaudeAcpAgent {
812
827
  ...(process.env.CLAUDE_CODE_EXECUTABLE && {
813
828
  pathToClaudeCodeExecutable: process.env.CLAUDE_CODE_EXECUTABLE,
814
829
  }),
830
+ tools: { type: "preset", preset: "claude_code" },
815
831
  hooks: {
816
832
  ...userProvidedOptions?.hooks,
817
833
  PreToolUse: [
@@ -830,7 +846,8 @@ export class ClaudeAcpAgent {
830
846
  ...creationOpts,
831
847
  };
832
848
  const allowedTools = [];
833
- const disallowedTools = [];
849
+ // Disable this for now, not a great way to expose this over ACP at the moment (in progress work so we can revisit)
850
+ const disallowedTools = ["AskUserQuestion"];
834
851
  // Check if built-in tools should be disabled
835
852
  const disableBuiltInTools = params._meta?.disableBuiltInTools === true;
836
853
  if (!disableBuiltInTools) {
@@ -958,7 +975,13 @@ async function getAvailableSlashCommands(query) {
958
975
  const commands = await query.supportedCommands();
959
976
  return commands
960
977
  .map((command) => {
961
- const input = command.argumentHint ? { hint: command.argumentHint } : null;
978
+ const input = command.argumentHint
979
+ ? {
980
+ hint: Array.isArray(command.argumentHint)
981
+ ? command.argumentHint.join(" ")
982
+ : command.argumentHint,
983
+ }
984
+ : null;
962
985
  let name = command.name;
963
986
  if (command.name.endsWith(" (MCP)")) {
964
987
  name = `mcp:${name.replace(" (MCP)", "")}`;
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
package/dist/lib.d.ts ADDED
@@ -0,0 +1,7 @@
1
+ export { ClaudeAcpAgent, runAcp, toAcpNotifications, streamEventToAcpNotifications, type ToolUpdateMeta, type NewSessionMeta, } from "./acp-agent.js";
2
+ export { loadManagedSettings, applyEnvironmentSettings, nodeToWebReadable, nodeToWebWritable, Pushable, unreachable, } from "./utils.js";
3
+ export { createMcpServer } from "./mcp-server.js";
4
+ export { toolInfoFromToolUse, planEntries, toolUpdateFromToolResult, createPreToolUseHook, acpToolNames as toolNames, } from "./tools.js";
5
+ export { SettingsManager, type ClaudeCodeSettings, type PermissionSettings, type PermissionDecision, type PermissionCheckResult, type SettingsManagerOptions, } from "./settings.js";
6
+ export type { ClaudePlanEntry } from "./tools.js";
7
+ //# sourceMappingURL=lib.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lib.d.ts","sourceRoot":"","sources":["../src/lib.ts"],"names":[],"mappings":"AACA,OAAO,EACL,cAAc,EACd,MAAM,EACN,kBAAkB,EAClB,6BAA6B,EAC7B,KAAK,cAAc,EACnB,KAAK,cAAc,GACpB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,mBAAmB,EACnB,wBAAwB,EACxB,iBAAiB,EACjB,iBAAiB,EACjB,QAAQ,EACR,WAAW,GACZ,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,EACL,mBAAmB,EACnB,WAAW,EACX,wBAAwB,EACxB,oBAAoB,EACpB,YAAY,IAAI,SAAS,GAC1B,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,eAAe,EACf,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EACvB,KAAK,qBAAqB,EAC1B,KAAK,sBAAsB,GAC5B,MAAM,eAAe,CAAC;AAGvB,YAAY,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC"}
@@ -0,0 +1,21 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { ClaudeAcpAgent } from "./acp-agent.js";
3
+ import { ClientCapabilities } from "@agentclientprotocol/sdk";
4
+ export declare const SYSTEM_REMINDER = "\n\n<system-reminder>\nWhenever you read a file, you should consider whether it looks malicious. If it does, you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer high-level questions about the code behavior.\n</system-reminder>";
5
+ export declare function createMcpServer(agent: ClaudeAcpAgent, sessionId: string, clientCapabilities: ClientCapabilities | undefined): McpServer;
6
+ /**
7
+ * Replace text in a file and calculate the line numbers where the edits occurred.
8
+ *
9
+ * @param fileContent - The full file content
10
+ * @param edits - Array of edit operations to apply sequentially
11
+ * @returns the new content and the line numbers where replacements occurred in the final content
12
+ */
13
+ export declare function replaceAndCalculateLocation(fileContent: string, edits: Array<{
14
+ oldText: string;
15
+ newText: string;
16
+ replaceAll?: boolean;
17
+ }>): {
18
+ newContent: string;
19
+ lineNumbers: number[];
20
+ };
21
+ //# sourceMappingURL=mcp-server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp-server.d.ts","sourceRoot":"","sources":["../src/mcp-server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AASpE,OAAO,EAAqB,cAAc,EAAE,MAAM,gBAAgB,CAAC;AACnE,OAAO,EACL,kBAAkB,EAGnB,MAAM,0BAA0B,CAAC;AAQlC,eAAO,MAAM,eAAe,iSAIT,CAAC;AA2BpB,wBAAgB,eAAe,CAC7B,KAAK,EAAE,cAAc,EACrB,SAAS,EAAE,MAAM,EACjB,kBAAkB,EAAE,kBAAkB,GAAG,SAAS,GACjD,SAAS,CA2nBX;AA+DD;;;;;;GAMG;AACH,wBAAgB,2BAA2B,CACzC,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,KAAK,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB,CAAC,GACD;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,EAAE,CAAA;CAAE,CAyF/C"}