claude-code-acp 0.1.0__tar.gz → 0.3.0__tar.gz
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.
- claude_code_acp-0.3.0/PKG-INFO +418 -0
- claude_code_acp-0.3.0/README.md +395 -0
- {claude_code_acp-0.1.0 → claude_code_acp-0.3.0}/pyproject.toml +1 -1
- {claude_code_acp-0.1.0 → claude_code_acp-0.3.0}/src/claude_code_acp/__init__.py +14 -4
- claude_code_acp-0.3.0/src/claude_code_acp/acp_client.py +374 -0
- claude_code_acp-0.3.0/src/claude_code_acp/client.py +317 -0
- {claude_code_acp-0.1.0 → claude_code_acp-0.3.0}/uv.lock +1 -1
- claude_code_acp-0.1.0/PKG-INFO +0 -111
- claude_code_acp-0.1.0/README.md +0 -88
- {claude_code_acp-0.1.0 → claude_code_acp-0.3.0}/.github/workflows/publish.yml +0 -0
- {claude_code_acp-0.1.0 → claude_code_acp-0.3.0}/.gitignore +0 -0
- {claude_code_acp-0.1.0 → claude_code_acp-0.3.0}/LICENSE +0 -0
- {claude_code_acp-0.1.0 → claude_code_acp-0.3.0}/src/claude_code_acp/__main__.py +0 -0
- {claude_code_acp-0.1.0 → claude_code_acp-0.3.0}/src/claude_code_acp/agent.py +0 -0
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: claude-code-acp
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: ACP-compatible agent for Claude Code (Python version)
|
|
5
|
+
Project-URL: Homepage, https://github.com/yazelin/claude-code-acp-py
|
|
6
|
+
Project-URL: Repository, https://github.com/yazelin/claude-code-acp-py
|
|
7
|
+
Project-URL: Issues, https://github.com/yazelin/claude-code-acp-py/issues
|
|
8
|
+
Author: yazelin
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: acp,agent,anthropic,claude,claude-code,python
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Requires-Python: >=3.10
|
|
20
|
+
Requires-Dist: agent-client-protocol>=0.7.0
|
|
21
|
+
Requires-Dist: claude-agent-sdk>=0.1.29
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
|
|
24
|
+
# Claude Code ACP (Python)
|
|
25
|
+
|
|
26
|
+
[](https://pypi.org/project/claude-code-acp/)
|
|
27
|
+
[](https://pypi.org/project/claude-code-acp/)
|
|
28
|
+
[](https://github.com/yazelin/claude-code-acp-py/blob/main/LICENSE)
|
|
29
|
+
|
|
30
|
+
**Python implementation of ACP (Agent Client Protocol) for Claude Code.**
|
|
31
|
+
|
|
32
|
+
This package bridges the [Claude Agent SDK](https://github.com/anthropics/claude-agent-sdk-python) with the [Agent Client Protocol (ACP)](https://agentclientprotocol.com/), providing two ways to use Claude:
|
|
33
|
+
|
|
34
|
+
1. **ACP Server** - Connect Claude to any ACP-compatible editor (Zed, Neovim, JetBrains, etc.)
|
|
35
|
+
2. **Python Client** - Event-driven API for building Python applications with Claude
|
|
36
|
+
|
|
37
|
+
## Features
|
|
38
|
+
|
|
39
|
+
- **Uses Claude CLI subscription** - No API key needed, uses your existing Claude subscription
|
|
40
|
+
- **Full ACP protocol support** - Compatible with Zed, Neovim, and other ACP clients
|
|
41
|
+
- **Bidirectional communication** - Permission requests, tool calls, streaming responses
|
|
42
|
+
- **Event-driven Python API** - Decorator-based handlers for easy integration
|
|
43
|
+
- **Session management** - Create, fork, resume, list sessions
|
|
44
|
+
- **Multiple permission modes** - default, acceptEdits, plan, bypassPermissions
|
|
45
|
+
|
|
46
|
+
## Installation
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
pip install claude-code-acp
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Or with uv:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
uv tool install claude-code-acp
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Requirements
|
|
59
|
+
|
|
60
|
+
- Python 3.10+
|
|
61
|
+
- Claude CLI installed and authenticated (`claude /login`)
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## Components
|
|
66
|
+
|
|
67
|
+
| Class | Type | Description |
|
|
68
|
+
|-------|------|-------------|
|
|
69
|
+
| `ClaudeAcpAgent` | ACP Server | For editors (Zed, Neovim) to connect |
|
|
70
|
+
| `ClaudeClient` | Python API | Event-driven wrapper (uses agent internally) |
|
|
71
|
+
| `AcpClient` | ACP Client | Connect to any ACP agent via subprocess |
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## Usage 1: ACP Server for Editors
|
|
76
|
+
|
|
77
|
+
Run as an ACP server to connect Claude to your editor:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
claude-code-acp
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Zed Editor
|
|
84
|
+
|
|
85
|
+
Add to your Zed `settings.json`:
|
|
86
|
+
|
|
87
|
+
```json
|
|
88
|
+
{
|
|
89
|
+
"agent_servers": {
|
|
90
|
+
"Claude Code Python": {
|
|
91
|
+
"type": "custom",
|
|
92
|
+
"command": "claude-code-acp",
|
|
93
|
+
"args": [],
|
|
94
|
+
"env": {}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Then open the Agent Panel (`Ctrl+?` / `Cmd+?`) and select "Claude Code Python" from the `+` menu.
|
|
101
|
+
|
|
102
|
+
### Other Editors
|
|
103
|
+
|
|
104
|
+
Any [ACP-compatible client](https://agentclientprotocol.com/overview/clients) can connect by spawning `claude-code-acp` as a subprocess and communicating via stdio.
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## Usage 2: Python Event-Driven API
|
|
109
|
+
|
|
110
|
+
Use `ClaudeClient` for building Python applications with Claude:
|
|
111
|
+
|
|
112
|
+
```python
|
|
113
|
+
import asyncio
|
|
114
|
+
from claude_code_acp import ClaudeClient
|
|
115
|
+
|
|
116
|
+
async def main():
|
|
117
|
+
client = ClaudeClient(cwd=".")
|
|
118
|
+
|
|
119
|
+
@client.on_text
|
|
120
|
+
async def handle_text(text: str):
|
|
121
|
+
"""Called for each text chunk from Claude."""
|
|
122
|
+
print(text, end="", flush=True)
|
|
123
|
+
|
|
124
|
+
@client.on_tool_start
|
|
125
|
+
async def handle_tool_start(tool_id: str, name: str, input: dict):
|
|
126
|
+
"""Called when Claude starts using a tool."""
|
|
127
|
+
print(f"\n🔧 {name}")
|
|
128
|
+
|
|
129
|
+
@client.on_tool_end
|
|
130
|
+
async def handle_tool_end(tool_id: str, status: str, output):
|
|
131
|
+
"""Called when a tool completes."""
|
|
132
|
+
icon = "✅" if status == "completed" else "❌"
|
|
133
|
+
print(f" {icon}")
|
|
134
|
+
|
|
135
|
+
@client.on_permission
|
|
136
|
+
async def handle_permission(name: str, input: dict) -> bool:
|
|
137
|
+
"""Called when Claude needs permission. Return True to allow."""
|
|
138
|
+
print(f"🔐 Permission requested: {name}")
|
|
139
|
+
return True # or prompt user
|
|
140
|
+
|
|
141
|
+
@client.on_complete
|
|
142
|
+
async def handle_complete():
|
|
143
|
+
"""Called when the query completes."""
|
|
144
|
+
print("\n--- Done ---")
|
|
145
|
+
|
|
146
|
+
# Send a query
|
|
147
|
+
response = await client.query("Create a hello.py file that prints Hello World")
|
|
148
|
+
print(f"\nFull response: {response}")
|
|
149
|
+
|
|
150
|
+
asyncio.run(main())
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### Event Handlers
|
|
154
|
+
|
|
155
|
+
| Decorator | Arguments | Description |
|
|
156
|
+
|-----------|-----------|-------------|
|
|
157
|
+
| `@client.on_text` | `(text: str)` | Streaming text chunks from Claude |
|
|
158
|
+
| `@client.on_thinking` | `(text: str)` | Thinking/reasoning blocks |
|
|
159
|
+
| `@client.on_tool_start` | `(tool_id, name, input)` | Tool execution started |
|
|
160
|
+
| `@client.on_tool_end` | `(tool_id, status, output)` | Tool execution completed |
|
|
161
|
+
| `@client.on_permission` | `(name, input) -> bool` | Permission request (return True/False) |
|
|
162
|
+
| `@client.on_error` | `(exception)` | Error occurred |
|
|
163
|
+
| `@client.on_complete` | `()` | Query completed |
|
|
164
|
+
|
|
165
|
+
### Client Methods
|
|
166
|
+
|
|
167
|
+
```python
|
|
168
|
+
# Start a new session
|
|
169
|
+
session_id = await client.start_session()
|
|
170
|
+
|
|
171
|
+
# Send a query (returns full response text)
|
|
172
|
+
response = await client.query("Your prompt here")
|
|
173
|
+
|
|
174
|
+
# Set permission mode
|
|
175
|
+
await client.set_mode("acceptEdits") # or "default", "plan", "bypassPermissions"
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
## Usage 3: ACP Client (Connect to Any Agent)
|
|
181
|
+
|
|
182
|
+
Use `AcpClient` to connect to any ACP-compatible agent:
|
|
183
|
+
|
|
184
|
+
```python
|
|
185
|
+
import asyncio
|
|
186
|
+
from claude_code_acp import AcpClient
|
|
187
|
+
|
|
188
|
+
async def main():
|
|
189
|
+
# Connect to claude-code-acp (Python version)
|
|
190
|
+
client = AcpClient(command="claude-code-acp")
|
|
191
|
+
|
|
192
|
+
# Or connect to the TypeScript version
|
|
193
|
+
# client = AcpClient(command="npx", args=["@zed-industries/claude-code-acp"])
|
|
194
|
+
|
|
195
|
+
# Or any other ACP agent
|
|
196
|
+
# client = AcpClient(command="my-custom-agent")
|
|
197
|
+
|
|
198
|
+
@client.on_text
|
|
199
|
+
async def handle_text(text: str):
|
|
200
|
+
print(text, end="", flush=True)
|
|
201
|
+
|
|
202
|
+
@client.on_tool_start
|
|
203
|
+
async def handle_tool(tool_id: str, name: str, input: dict):
|
|
204
|
+
print(f"\n🔧 {name}")
|
|
205
|
+
|
|
206
|
+
@client.on_permission
|
|
207
|
+
async def handle_permission(name: str, input: dict, options: list) -> str:
|
|
208
|
+
"""Return option_id: 'allow', 'reject', or 'allow_always'"""
|
|
209
|
+
print(f"🔐 Permission: {name}")
|
|
210
|
+
return "allow"
|
|
211
|
+
|
|
212
|
+
@client.on_complete
|
|
213
|
+
async def handle_complete():
|
|
214
|
+
print("\n--- Done ---")
|
|
215
|
+
|
|
216
|
+
async with client:
|
|
217
|
+
response = await client.prompt("What files are here?")
|
|
218
|
+
|
|
219
|
+
asyncio.run(main())
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
### AcpClient vs ClaudeClient
|
|
223
|
+
|
|
224
|
+
| Feature | `ClaudeClient` | `AcpClient` |
|
|
225
|
+
|---------|---------------|-------------|
|
|
226
|
+
| Uses | Claude Agent SDK directly | Any ACP agent via subprocess |
|
|
227
|
+
| Connection | In-process | Subprocess + stdio |
|
|
228
|
+
| Agents | Claude only | Any ACP-compatible agent |
|
|
229
|
+
| Use case | Simple Python apps | Multi-agent, testing, flexibility |
|
|
230
|
+
|
|
231
|
+
---
|
|
232
|
+
|
|
233
|
+
## Architecture
|
|
234
|
+
|
|
235
|
+
```
|
|
236
|
+
┌─────────────────────────────────────────────────────────────────────────────┐
|
|
237
|
+
│ Your Application │
|
|
238
|
+
├─────────────────────────────────────────────────────────────────────────────┤
|
|
239
|
+
│ │
|
|
240
|
+
│ ┌─────────────────┐ ┌─────────────────────────────┐ │
|
|
241
|
+
│ │ Zed/Neovim │ │ Python Application │ │
|
|
242
|
+
│ │ (ACP Client) │ │ │ │
|
|
243
|
+
│ └────────┬────────┘ │ client = ClaudeClient() │ │
|
|
244
|
+
│ │ │ │ │
|
|
245
|
+
│ │ ACP Protocol │ @client.on_text │ │
|
|
246
|
+
│ │ (stdio/JSON-RPC) │ async def handle(text): │ │
|
|
247
|
+
│ │ │ print(text) │ │
|
|
248
|
+
│ ▼ │ │ │
|
|
249
|
+
│ ┌────────────────────────────────────────┴─────────────────────────────┐ │
|
|
250
|
+
│ │ │ │
|
|
251
|
+
│ │ claude-code-acp (This Package) │ │
|
|
252
|
+
│ │ │ │
|
|
253
|
+
│ │ ┌─────────────────────┐ ┌─────────────────────────────────┐ │ │
|
|
254
|
+
│ │ │ ClaudeAcpAgent │ │ ClaudeClient │ │ │
|
|
255
|
+
│ │ │ (ACP Server) │ │ (Event-driven wrapper) │ │ │
|
|
256
|
+
│ │ └──────────┬──────────┘ └───────────────┬─────────────────┘ │ │
|
|
257
|
+
│ │ │ │ │ │
|
|
258
|
+
│ │ └────────────────┬────────────────┘ │ │
|
|
259
|
+
│ │ │ │ │
|
|
260
|
+
│ └───────────────────────────────┼───────────────────────────────────────┘ │
|
|
261
|
+
│ │ │
|
|
262
|
+
│ ▼ │
|
|
263
|
+
│ ┌─────────────────────────────┐ │
|
|
264
|
+
│ │ Claude Agent SDK │ │
|
|
265
|
+
│ │ (claude-agent-sdk) │ │
|
|
266
|
+
│ └──────────────┬──────────────┘ │
|
|
267
|
+
│ │ │
|
|
268
|
+
│ ▼ │
|
|
269
|
+
│ ┌─────────────────────────────┐ │
|
|
270
|
+
│ │ Claude CLI │ │
|
|
271
|
+
│ │ (Your Claude Subscription) │ │
|
|
272
|
+
│ └─────────────────────────────┘ │
|
|
273
|
+
│ │
|
|
274
|
+
└─────────────────────────────────────────────────────────────────────────────┘
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
---
|
|
278
|
+
|
|
279
|
+
## What We Built
|
|
280
|
+
|
|
281
|
+
This project combines two official SDKs to create a complete Python solution:
|
|
282
|
+
|
|
283
|
+
### Integrated Components
|
|
284
|
+
|
|
285
|
+
| Component | Source | Purpose |
|
|
286
|
+
|-----------|--------|---------|
|
|
287
|
+
| [Agent Client Protocol SDK](https://github.com/anthropics/agent-client-protocol) | Anthropic | ACP server/client protocol implementation |
|
|
288
|
+
| [Claude Agent SDK](https://github.com/anthropics/claude-agent-sdk-python) | Anthropic | Claude CLI wrapper with streaming support |
|
|
289
|
+
|
|
290
|
+
### Our Contributions
|
|
291
|
+
|
|
292
|
+
1. **ClaudeAcpAgent** (`agent.py`)
|
|
293
|
+
- Bridges Claude Agent SDK with ACP protocol
|
|
294
|
+
- Converts Claude messages to ACP session updates
|
|
295
|
+
- Handles bidirectional permission requests
|
|
296
|
+
- Session management (create, fork, resume, list)
|
|
297
|
+
|
|
298
|
+
2. **ClaudeClient** (`client.py`)
|
|
299
|
+
- Event-driven Python API with decorators
|
|
300
|
+
- Smart text deduplication for streaming
|
|
301
|
+
- Simple permission handling
|
|
302
|
+
- Clean async/await interface
|
|
303
|
+
|
|
304
|
+
3. **ACP Server Entry Point**
|
|
305
|
+
- Standalone `claude-code-acp` command
|
|
306
|
+
- Direct integration with Zed and other ACP clients
|
|
307
|
+
- No configuration needed
|
|
308
|
+
|
|
309
|
+
### Why This Package?
|
|
310
|
+
|
|
311
|
+
| Approach | API Key | Subscription | ACP Support | Event-Driven |
|
|
312
|
+
|----------|---------|--------------|-------------|--------------|
|
|
313
|
+
| Anthropic API directly | ✅ Required | ❌ | ❌ | ❌ |
|
|
314
|
+
| Claude Agent SDK | ❌ | ✅ Uses CLI | ❌ | Partial |
|
|
315
|
+
| **claude-code-acp** | ❌ | ✅ Uses CLI | ✅ Full | ✅ Full |
|
|
316
|
+
|
|
317
|
+
---
|
|
318
|
+
|
|
319
|
+
## Examples
|
|
320
|
+
|
|
321
|
+
### Simple Chat
|
|
322
|
+
|
|
323
|
+
```python
|
|
324
|
+
import asyncio
|
|
325
|
+
from claude_code_acp import ClaudeClient
|
|
326
|
+
|
|
327
|
+
async def main():
|
|
328
|
+
client = ClaudeClient()
|
|
329
|
+
|
|
330
|
+
@client.on_text
|
|
331
|
+
async def on_text(text):
|
|
332
|
+
print(text, end="")
|
|
333
|
+
|
|
334
|
+
while True:
|
|
335
|
+
user_input = input("\nYou: ")
|
|
336
|
+
if user_input.lower() == "quit":
|
|
337
|
+
break
|
|
338
|
+
await client.query(user_input)
|
|
339
|
+
|
|
340
|
+
asyncio.run(main())
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
### File Operations with Permission Control
|
|
344
|
+
|
|
345
|
+
```python
|
|
346
|
+
import asyncio
|
|
347
|
+
from claude_code_acp import ClaudeClient
|
|
348
|
+
|
|
349
|
+
async def main():
|
|
350
|
+
client = ClaudeClient(cwd="/path/to/project")
|
|
351
|
+
|
|
352
|
+
@client.on_text
|
|
353
|
+
async def on_text(text):
|
|
354
|
+
print(text, end="")
|
|
355
|
+
|
|
356
|
+
@client.on_permission
|
|
357
|
+
async def on_permission(name, input):
|
|
358
|
+
response = input(f"Allow '{name}'? [y/N]: ")
|
|
359
|
+
return response.lower() == "y"
|
|
360
|
+
|
|
361
|
+
await client.query("Refactor the main.py file to use async/await")
|
|
362
|
+
|
|
363
|
+
asyncio.run(main())
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
### Auto-approve Mode
|
|
367
|
+
|
|
368
|
+
```python
|
|
369
|
+
import asyncio
|
|
370
|
+
from claude_code_acp import ClaudeClient
|
|
371
|
+
|
|
372
|
+
async def main():
|
|
373
|
+
client = ClaudeClient(cwd=".")
|
|
374
|
+
|
|
375
|
+
# Bypass all permission checks
|
|
376
|
+
await client.set_mode("bypassPermissions")
|
|
377
|
+
|
|
378
|
+
@client.on_text
|
|
379
|
+
async def on_text(text):
|
|
380
|
+
print(text, end="")
|
|
381
|
+
|
|
382
|
+
await client.query("Create a complete Flask app with tests")
|
|
383
|
+
|
|
384
|
+
asyncio.run(main())
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
---
|
|
388
|
+
|
|
389
|
+
## Development
|
|
390
|
+
|
|
391
|
+
```bash
|
|
392
|
+
# Clone
|
|
393
|
+
git clone https://github.com/yazelin/claude-code-acp-py
|
|
394
|
+
cd claude-code-acp-py
|
|
395
|
+
|
|
396
|
+
# Install dependencies
|
|
397
|
+
uv sync
|
|
398
|
+
|
|
399
|
+
# Run locally
|
|
400
|
+
uv run claude-code-acp
|
|
401
|
+
|
|
402
|
+
# Run tests
|
|
403
|
+
uv run python -c "from claude_code_acp import ClaudeClient; print('OK')"
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
---
|
|
407
|
+
|
|
408
|
+
## Related Projects
|
|
409
|
+
|
|
410
|
+
- [claude-code-acp](https://github.com/zed-industries/claude-code-acp) - TypeScript version by Zed Industries
|
|
411
|
+
- [agent-client-protocol](https://github.com/anthropics/agent-client-protocol) - ACP specification and SDKs
|
|
412
|
+
- [claude-agent-sdk-python](https://github.com/anthropics/claude-agent-sdk-python) - Official Claude Agent SDK
|
|
413
|
+
|
|
414
|
+
---
|
|
415
|
+
|
|
416
|
+
## License
|
|
417
|
+
|
|
418
|
+
MIT
|