claude-code-acp 0.3.2__py3-none-any.whl → 0.3.4__py3-none-any.whl

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.
@@ -11,7 +11,7 @@ from .agent import ClaudeAcpAgent
11
11
  from .client import ClaudeClient, ClaudeEvents
12
12
  from .acp_client import AcpClient, AcpClientEvents
13
13
 
14
- __version__ = "0.3.2"
14
+ __version__ = "0.3.4"
15
15
 
16
16
  __all__ = [
17
17
  "ClaudeAcpAgent",
@@ -10,6 +10,7 @@ from __future__ import annotations
10
10
  import asyncio
11
11
  import logging
12
12
  from dataclasses import dataclass
13
+ from pathlib import Path
13
14
  from typing import Any, Callable, Coroutine
14
15
 
15
16
  from acp.client.connection import ClientSideConnection
@@ -30,6 +31,17 @@ logger = logging.getLogger(__name__)
30
31
  __all__ = ["AcpClient", "AcpClientEvents"]
31
32
 
32
33
 
34
+ @dataclass
35
+ class TerminalProcess:
36
+ """Represents an active terminal process."""
37
+
38
+ process: asyncio.subprocess.Process
39
+ command: str
40
+ cwd: str
41
+ output_buffer: list[str]
42
+ exit_code: int | None = None
43
+
44
+
33
45
  @dataclass
34
46
  class AcpClientEvents:
35
47
  """Event handlers for ACP client."""
@@ -41,6 +53,12 @@ class AcpClientEvents:
41
53
  on_permission: Callable[[str, dict, list], Coroutine[Any, Any, str]] | None = None
42
54
  on_error: Callable[[Exception], Coroutine[Any, Any, None]] | None = None
43
55
  on_complete: Callable[[], Coroutine[Any, Any, None]] | None = None
56
+ # File operation handlers (optional - if not set, operations proceed automatically)
57
+ on_file_read: Callable[[str], Coroutine[Any, Any, str | None]] | None = None
58
+ on_file_write: Callable[[str, str], Coroutine[Any, Any, bool]] | None = None
59
+ # Terminal operation handlers (optional)
60
+ on_terminal_create: Callable[[str, str], Coroutine[Any, Any, bool]] | None = None
61
+ on_terminal_output: Callable[[str, str], Coroutine[Any, Any, None]] | None = None
44
62
 
45
63
 
46
64
  class AcpClient:
@@ -94,6 +112,9 @@ class AcpClient:
94
112
  self._session_id: str | None = None
95
113
  self._text_buffer = ""
96
114
  self._initialized = False
115
+ # Terminal management
116
+ self._terminals: dict[str, TerminalProcess] = {}
117
+ self._terminal_counter = 0
97
118
 
98
119
  # --- Event decorators ---
99
120
 
@@ -137,6 +158,55 @@ class AcpClient:
137
158
  self.events.on_complete = func
138
159
  return func
139
160
 
161
+ def on_file_read(self, func: Callable[[str], Coroutine[Any, Any, str | None]]):
162
+ """
163
+ Register handler for file read operations.
164
+
165
+ The handler receives (path) and can return:
166
+ - str: Override the file content with this value
167
+ - None: Proceed with normal file reading
168
+
169
+ This allows intercepting file reads for security or custom handling.
170
+ """
171
+ self.events.on_file_read = func
172
+ return func
173
+
174
+ def on_file_write(self, func: Callable[[str, str], Coroutine[Any, Any, bool]]):
175
+ """
176
+ Register handler for file write operations.
177
+
178
+ The handler receives (path, content) and should return:
179
+ - True: Allow the write to proceed
180
+ - False: Block the write
181
+
182
+ This allows intercepting file writes for security or confirmation prompts.
183
+ """
184
+ self.events.on_file_write = func
185
+ return func
186
+
187
+ def on_terminal_create(self, func: Callable[[str, str], Coroutine[Any, Any, bool]]):
188
+ """
189
+ Register handler for terminal creation requests.
190
+
191
+ The handler receives (command, cwd) and should return:
192
+ - True: Allow the terminal to be created
193
+ - False: Block the terminal creation
194
+
195
+ This allows intercepting shell command execution for security.
196
+ """
197
+ self.events.on_terminal_create = func
198
+ return func
199
+
200
+ def on_terminal_output(self, func: Callable[[str, str], Coroutine[Any, Any, None]]):
201
+ """
202
+ Register handler for terminal output.
203
+
204
+ The handler receives (terminal_id, output) when new output is available.
205
+ This allows displaying or logging terminal output in real-time.
206
+ """
207
+ self.events.on_terminal_output = func
208
+ return func
209
+
140
210
  # --- Connection management ---
141
211
 
142
212
  async def connect(self) -> None:
@@ -179,6 +249,15 @@ class AcpClient:
179
249
 
180
250
  async def disconnect(self) -> None:
181
251
  """Disconnect from the ACP agent."""
252
+ # Clean up all terminals
253
+ for terminal_id, terminal in list(self._terminals.items()):
254
+ try:
255
+ terminal.process.kill()
256
+ await terminal.process.wait()
257
+ except Exception:
258
+ pass
259
+ self._terminals.clear()
260
+
182
261
  if self._connection:
183
262
  await self._connection.close()
184
263
  self._connection = None
@@ -331,33 +410,268 @@ class AcpClient:
331
410
  outcome={"outcome": "selected", "option_id": selected_id}
332
411
  )
333
412
 
334
- async def write_text_file(self, **kwargs) -> None:
335
- """Handle write file requests (stub)."""
336
- pass
337
-
338
- async def read_text_file(self, **kwargs) -> dict:
339
- """Handle read file requests (stub)."""
340
- return {"content": ""}
413
+ async def write_text_file(
414
+ self,
415
+ path: str,
416
+ content: str,
417
+ **kwargs,
418
+ ) -> None:
419
+ """
420
+ Handle write file requests from the agent.
421
+
422
+ The agent requests the client to write a file to disk.
423
+ This enables the agent to create/modify files in the user's filesystem.
424
+
425
+ Args:
426
+ path: The file path to write to.
427
+ content: The content to write.
428
+ """
429
+ # Check if handler wants to intercept/block the write
430
+ if client.events.on_file_write:
431
+ allowed = await client.events.on_file_write(path, content)
432
+ if not allowed:
433
+ logger.info(f"File write blocked by handler: {path}")
434
+ return
435
+
436
+ try:
437
+ file_path = Path(path)
438
+ # Create parent directories if they don't exist
439
+ file_path.parent.mkdir(parents=True, exist_ok=True)
440
+ # Write the file
441
+ file_path.write_text(content, encoding="utf-8")
442
+ logger.debug(f"Wrote file: {path}")
443
+ except Exception as e:
444
+ logger.error(f"Failed to write file {path}: {e}")
445
+ raise
446
+
447
+ async def read_text_file(
448
+ self,
449
+ path: str,
450
+ **kwargs,
451
+ ) -> dict:
452
+ """
453
+ Handle read file requests from the agent.
454
+
455
+ The agent requests the client to read a file from disk.
456
+ This enables the agent to access files in the user's filesystem.
457
+
458
+ Args:
459
+ path: The file path to read.
460
+
461
+ Returns:
462
+ A dict with 'content' key containing the file content.
463
+ """
464
+ # Check if handler wants to override the content
465
+ if client.events.on_file_read:
466
+ override = await client.events.on_file_read(path)
467
+ if override is not None:
468
+ logger.debug(f"File read overridden by handler: {path}")
469
+ return {"content": override}
470
+
471
+ try:
472
+ file_path = Path(path)
473
+ if not file_path.exists():
474
+ logger.warning(f"File not found: {path}")
475
+ return {"content": "", "error": f"File not found: {path}"}
476
+ content = file_path.read_text(encoding="utf-8")
477
+ logger.debug(f"Read file: {path} ({len(content)} chars)")
478
+ return {"content": content}
479
+ except Exception as e:
480
+ logger.error(f"Failed to read file {path}: {e}")
481
+ return {"content": "", "error": str(e)}
482
+
483
+ async def create_terminal(
484
+ self,
485
+ command: str = "",
486
+ args: list[str] | None = None,
487
+ cwd: str | None = None,
488
+ env: dict[str, str] | None = None,
489
+ **kwargs,
490
+ ) -> dict:
491
+ """
492
+ Create a terminal and execute a command.
493
+
494
+ The agent requests the client to run a shell command.
495
+ This enables command execution in the user's environment.
496
+
497
+ Args:
498
+ command: The command to execute.
499
+ args: Command arguments.
500
+ cwd: Working directory (defaults to client cwd).
501
+ env: Additional environment variables.
502
+
503
+ Returns:
504
+ A dict with 'terminal_id' for tracking the process.
505
+ """
506
+ work_dir = cwd or client.cwd
507
+ full_command = command
508
+ if args:
509
+ full_command = f"{command} {' '.join(args)}"
510
+
511
+ # Check if handler wants to block the terminal creation
512
+ if client.events.on_terminal_create:
513
+ allowed = await client.events.on_terminal_create(full_command, work_dir)
514
+ if not allowed:
515
+ logger.info(f"Terminal creation blocked by handler: {full_command}")
516
+ return {"terminal_id": "", "error": "Terminal creation blocked"}
517
+
518
+ try:
519
+ # Create the subprocess
520
+ process = await asyncio.create_subprocess_shell(
521
+ full_command,
522
+ stdout=asyncio.subprocess.PIPE,
523
+ stderr=asyncio.subprocess.STDOUT,
524
+ cwd=work_dir,
525
+ env={**dict(env or {})} if env else None,
526
+ )
341
527
 
342
- async def create_terminal(self, **kwargs) -> dict:
343
- """Handle terminal creation (stub)."""
344
- return {"terminal_id": "stub"}
528
+ # Generate terminal ID
529
+ client._terminal_counter += 1
530
+ terminal_id = f"terminal-{client._terminal_counter}"
345
531
 
346
- async def terminal_output(self, **kwargs) -> dict:
347
- """Handle terminal output requests (stub)."""
348
- return {"output": ""}
532
+ # Store the terminal
533
+ client._terminals[terminal_id] = TerminalProcess(
534
+ process=process,
535
+ command=full_command,
536
+ cwd=work_dir,
537
+ output_buffer=[],
538
+ )
349
539
 
350
- async def release_terminal(self, **kwargs) -> None:
351
- """Handle terminal release (stub)."""
352
- pass
540
+ logger.debug(f"Created terminal {terminal_id}: {full_command}")
541
+ return {"terminal_id": terminal_id}
353
542
 
354
- async def wait_for_terminal_exit(self, **kwargs) -> dict:
355
- """Handle terminal exit wait (stub)."""
356
- return {"exit_code": 0}
543
+ except Exception as e:
544
+ logger.error(f"Failed to create terminal: {e}")
545
+ return {"terminal_id": "", "error": str(e)}
357
546
 
358
- async def kill_terminal(self, **kwargs) -> None:
359
- """Handle terminal kill (stub)."""
360
- pass
547
+ async def terminal_output(
548
+ self,
549
+ terminal_id: str = "",
550
+ **kwargs,
551
+ ) -> dict:
552
+ """
553
+ Get output from a terminal.
554
+
555
+ Args:
556
+ terminal_id: The terminal to get output from.
557
+
558
+ Returns:
559
+ A dict with 'output' containing available output.
560
+ """
561
+ terminal = client._terminals.get(terminal_id)
562
+ if not terminal:
563
+ return {"output": "", "error": f"Terminal not found: {terminal_id}"}
564
+
565
+ try:
566
+ # Try to read available output (non-blocking)
567
+ if terminal.process.stdout:
568
+ try:
569
+ # Read with a short timeout
570
+ output = await asyncio.wait_for(
571
+ terminal.process.stdout.read(4096),
572
+ timeout=0.1,
573
+ )
574
+ if output:
575
+ decoded = output.decode("utf-8", errors="replace")
576
+ terminal.output_buffer.append(decoded)
577
+
578
+ # Notify handler if registered
579
+ if client.events.on_terminal_output:
580
+ await client.events.on_terminal_output(terminal_id, decoded)
581
+
582
+ return {"output": decoded}
583
+ except asyncio.TimeoutError:
584
+ pass
585
+
586
+ # Return buffered output if no new output
587
+ if terminal.output_buffer:
588
+ return {"output": "".join(terminal.output_buffer)}
589
+ return {"output": ""}
590
+
591
+ except Exception as e:
592
+ logger.error(f"Failed to get terminal output: {e}")
593
+ return {"output": "", "error": str(e)}
594
+
595
+ async def release_terminal(
596
+ self,
597
+ terminal_id: str = "",
598
+ **kwargs,
599
+ ) -> None:
600
+ """
601
+ Release a terminal without killing it.
602
+
603
+ The terminal continues running but we stop tracking it.
604
+
605
+ Args:
606
+ terminal_id: The terminal to release.
607
+ """
608
+ if terminal_id in client._terminals:
609
+ logger.debug(f"Released terminal: {terminal_id}")
610
+ del client._terminals[terminal_id]
611
+
612
+ async def wait_for_terminal_exit(
613
+ self,
614
+ terminal_id: str = "",
615
+ **kwargs,
616
+ ) -> dict:
617
+ """
618
+ Wait for a terminal to exit and return its exit code.
619
+
620
+ Args:
621
+ terminal_id: The terminal to wait for.
622
+
623
+ Returns:
624
+ A dict with 'exit_code'.
625
+ """
626
+ terminal = client._terminals.get(terminal_id)
627
+ if not terminal:
628
+ return {"exit_code": -1, "error": f"Terminal not found: {terminal_id}"}
629
+
630
+ try:
631
+ # Read remaining output while waiting
632
+ if terminal.process.stdout:
633
+ remaining = await terminal.process.stdout.read()
634
+ if remaining:
635
+ decoded = remaining.decode("utf-8", errors="replace")
636
+ terminal.output_buffer.append(decoded)
637
+ if client.events.on_terminal_output:
638
+ await client.events.on_terminal_output(terminal_id, decoded)
639
+
640
+ # Wait for process to exit
641
+ exit_code = await terminal.process.wait()
642
+ terminal.exit_code = exit_code
643
+ logger.debug(f"Terminal {terminal_id} exited with code {exit_code}")
644
+ return {"exit_code": exit_code}
645
+
646
+ except Exception as e:
647
+ logger.error(f"Failed to wait for terminal exit: {e}")
648
+ return {"exit_code": -1, "error": str(e)}
649
+
650
+ async def kill_terminal(
651
+ self,
652
+ terminal_id: str = "",
653
+ **kwargs,
654
+ ) -> None:
655
+ """
656
+ Kill a terminal process.
657
+
658
+ Args:
659
+ terminal_id: The terminal to kill.
660
+ """
661
+ terminal = client._terminals.get(terminal_id)
662
+ if not terminal:
663
+ return
664
+
665
+ try:
666
+ terminal.process.kill()
667
+ await terminal.process.wait()
668
+ logger.debug(f"Killed terminal: {terminal_id}")
669
+ except Exception as e:
670
+ logger.error(f"Failed to kill terminal: {e}")
671
+
672
+ # Remove from tracking
673
+ if terminal_id in client._terminals:
674
+ del client._terminals[terminal_id]
361
675
 
362
676
  async def ext_method(self, method: str, params: dict) -> dict:
363
677
  """Handle extension methods."""
claude_code_acp/agent.py CHANGED
@@ -73,6 +73,7 @@ class Session:
73
73
  permission_mode: PermissionMode = "default"
74
74
  cancelled: bool = False
75
75
  tool_use_cache: dict[str, ToolUseBlock] = field(default_factory=dict)
76
+ mcp_servers: dict[str, Any] = field(default_factory=dict)
76
77
 
77
78
 
78
79
  class ClaudeAcpAgent(Agent):
@@ -141,12 +142,16 @@ class ClaudeAcpAgent(Agent):
141
142
  """Create a new Claude session."""
142
143
  session_id = str(uuid4())
143
144
 
145
+ # Convert ACP MCP servers to Claude SDK format
146
+ sdk_mcp_servers = self._convert_mcp_servers(mcp_servers)
147
+
144
148
  self._sessions[session_id] = Session(
145
149
  session_id=session_id,
146
150
  cwd=cwd,
151
+ mcp_servers=sdk_mcp_servers,
147
152
  )
148
153
 
149
- logger.info(f"New session created: {session_id} in {cwd}")
154
+ logger.info(f"New session created: {session_id} in {cwd} with {len(sdk_mcp_servers)} MCP servers")
150
155
 
151
156
  return NewSessionResponse(
152
157
  session_id=session_id,
@@ -224,11 +229,12 @@ class ClaudeAcpAgent(Agent):
224
229
 
225
230
  logger.info(f"Prompt for session {session_id}: {prompt_text[:100]}...")
226
231
 
227
- # Build Claude options with permission callback for bidirectional communication
232
+ # Build Claude options with MCP servers and permission callback
228
233
  options = ClaudeAgentOptions(
229
234
  cwd=session.cwd,
230
235
  permission_mode=session.permission_mode,
231
236
  include_partial_messages=True,
237
+ mcp_servers=session.mcp_servers if session.mcp_servers else {},
232
238
  )
233
239
 
234
240
  # Add permission callback if not bypassing permissions
@@ -237,6 +243,7 @@ class ClaudeAcpAgent(Agent):
237
243
  cwd=session.cwd,
238
244
  permission_mode=session.permission_mode,
239
245
  include_partial_messages=True,
246
+ mcp_servers=session.mcp_servers if session.mcp_servers else {},
240
247
  can_use_tool=self._create_permission_handler(session_id),
241
248
  )
242
249
 
@@ -300,6 +307,68 @@ class ClaudeAcpAgent(Agent):
300
307
 
301
308
  return "\n".join(parts)
302
309
 
310
+ def _convert_mcp_servers(
311
+ self,
312
+ acp_servers: list[HttpMcpServer | SseMcpServer | McpServerStdio],
313
+ ) -> dict[str, Any]:
314
+ """Convert ACP MCP server configs to Claude SDK format."""
315
+ sdk_servers: dict[str, Any] = {}
316
+
317
+ for i, server in enumerate(acp_servers):
318
+ if isinstance(server, dict):
319
+ server_type = server.get("type")
320
+ name = server.get("name", f"mcp-server-{i}")
321
+
322
+ if server_type == "stdio":
323
+ # Stdio MCP server
324
+ sdk_servers[name] = {
325
+ "type": "stdio",
326
+ "command": server.get("command", ""),
327
+ "args": server.get("args", []),
328
+ "env": server.get("env", {}),
329
+ }
330
+ elif server_type == "sse":
331
+ # SSE MCP server
332
+ sdk_servers[name] = {
333
+ "type": "sse",
334
+ "url": server.get("url", ""),
335
+ }
336
+ elif server_type == "http":
337
+ # HTTP MCP server
338
+ sdk_servers[name] = {
339
+ "type": "http",
340
+ "url": server.get("url", ""),
341
+ }
342
+
343
+ logger.info(f"Added MCP server: {name} ({server_type})")
344
+
345
+ elif hasattr(server, "type"):
346
+ # Pydantic model
347
+ name = getattr(server, "name", f"mcp-server-{i}")
348
+ server_type = server.type
349
+
350
+ if server_type == "stdio":
351
+ sdk_servers[name] = {
352
+ "type": "stdio",
353
+ "command": getattr(server, "command", ""),
354
+ "args": getattr(server, "args", []),
355
+ "env": getattr(server, "env", {}),
356
+ }
357
+ elif server_type == "sse":
358
+ sdk_servers[name] = {
359
+ "type": "sse",
360
+ "url": getattr(server, "url", ""),
361
+ }
362
+ elif server_type == "http":
363
+ sdk_servers[name] = {
364
+ "type": "http",
365
+ "url": getattr(server, "url", ""),
366
+ }
367
+
368
+ logger.info(f"Added MCP server: {name} ({server_type})")
369
+
370
+ return sdk_servers
371
+
303
372
  async def _handle_message(self, session_id: str, message: Message) -> None:
304
373
  """Convert and emit a Claude message as ACP updates."""
305
374
  if self._conn is None:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: claude-code-acp
3
- Version: 0.3.2
3
+ Version: 0.3.4
4
4
  Summary: ACP-compatible agent for Claude Code (Python version)
5
5
  Project-URL: Homepage, https://github.com/yazelin/claude-code-acp-py
6
6
  Project-URL: Repository, https://github.com/yazelin/claude-code-acp-py
@@ -234,6 +234,43 @@ gemini = AcpClient(command="gemini", args=["--experimental-acp"])
234
234
  ts_claude = AcpClient(command="npx", args=["@zed-industries/claude-code-acp"])
235
235
  ```
236
236
 
237
+ ### File Operation Handlers
238
+
239
+ AcpClient supports intercepting file read/write operations for security or custom handling:
240
+
241
+ ```python
242
+ @client.on_file_read
243
+ async def handle_read(path: str) -> str | None:
244
+ """Intercept file reads. Return content to override, or None to proceed."""
245
+ print(f"📖 Reading: {path}")
246
+ return None # Proceed with normal read
247
+
248
+ @client.on_file_write
249
+ async def handle_write(path: str, content: str) -> bool:
250
+ """Intercept file writes. Return True to allow, False to block."""
251
+ print(f"📝 Writing: {path}")
252
+ response = input("Allow write? [y/N]: ")
253
+ return response.lower() == "y"
254
+ ```
255
+
256
+ ### Terminal Operation Handlers
257
+
258
+ AcpClient supports intercepting terminal/shell execution for security:
259
+
260
+ ```python
261
+ @client.on_terminal_create
262
+ async def handle_terminal(command: str, cwd: str) -> bool:
263
+ """Intercept shell commands. Return True to allow, False to block."""
264
+ print(f"🖥️ Command: {command} in {cwd}")
265
+ response = input("Allow execution? [y/N]: ")
266
+ return response.lower() == "y"
267
+
268
+ @client.on_terminal_output
269
+ async def handle_output(terminal_id: str, output: str) -> None:
270
+ """Receive terminal output in real-time."""
271
+ print(output, end="")
272
+ ```
273
+
237
274
  ### AcpClient vs ClaudeClient
238
275
 
239
276
  | Feature | `ClaudeClient` | `AcpClient` |
@@ -0,0 +1,10 @@
1
+ claude_code_acp/__init__.py,sha256=JSAUndeyZn-6EYXtA0MJMNj8fMiiuYFLGftwY2ERxVw,828
2
+ claude_code_acp/__main__.py,sha256=zuAdIOmaCsDeRxj2yIl_qMx-74QFaA3nWiS8gti0og0,118
3
+ claude_code_acp/acp_client.py,sha256=p2mmRB1Z1DaEGCkBznbLeKe_zUqrZOJMGCm17IW0gkE,25064
4
+ claude_code_acp/agent.py,sha256=PuWPFhBlOSkdYCt4yuxSxI1i9gVy_FwvabkmDYyiHLA,23421
5
+ claude_code_acp/client.py,sha256=jKi6tPNqNu0GWTsXxVa1BREGE1wkm_kbIEDulXKGDHE,10680
6
+ claude_code_acp-0.3.4.dist-info/METADATA,sha256=TuTB5bpDj3UGmu06kIvJOabLYX_X_ZEu1zIR6vuoQr0,14940
7
+ claude_code_acp-0.3.4.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
8
+ claude_code_acp-0.3.4.dist-info/entry_points.txt,sha256=cc_pkg_V_1zctD5CUqjATCxglkf5-UArEMfN3q9We-A,57
9
+ claude_code_acp-0.3.4.dist-info/licenses/LICENSE,sha256=5NCM9Q9UTfsn-VyafO7htdlYyPPO8H-NHYrO5UV9sT4,1064
10
+ claude_code_acp-0.3.4.dist-info/RECORD,,
@@ -1,10 +0,0 @@
1
- claude_code_acp/__init__.py,sha256=Bn3jxHrGAiiY_tVo_OL083Y6pqGIHyoNBLFMVtcwYn4,828
2
- claude_code_acp/__main__.py,sha256=zuAdIOmaCsDeRxj2yIl_qMx-74QFaA3nWiS8gti0og0,118
3
- claude_code_acp/acp_client.py,sha256=E0e8XBco3cBV4sPi4txXpS13WPXRDodwYqHpXmCJd4A,12572
4
- claude_code_acp/agent.py,sha256=qos32gnMAilOlz6ebllkmuJZS3UgpbAtAZw58r3SYig,20619
5
- claude_code_acp/client.py,sha256=jKi6tPNqNu0GWTsXxVa1BREGE1wkm_kbIEDulXKGDHE,10680
6
- claude_code_acp-0.3.2.dist-info/METADATA,sha256=xILl5S_FH4TNyHro7VlLq65BbZSGxfr3s3LlfmzgDF4,13729
7
- claude_code_acp-0.3.2.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
8
- claude_code_acp-0.3.2.dist-info/entry_points.txt,sha256=cc_pkg_V_1zctD5CUqjATCxglkf5-UArEMfN3q9We-A,57
9
- claude_code_acp-0.3.2.dist-info/licenses/LICENSE,sha256=5NCM9Q9UTfsn-VyafO7htdlYyPPO8H-NHYrO5UV9sT4,1064
10
- claude_code_acp-0.3.2.dist-info/RECORD,,