devpilot-agentic-cli 1.0.0__py3-none-any.whl → 1.0.2__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.
agent/cli.py CHANGED
@@ -161,14 +161,29 @@ async def main_async() -> None:
161
161
 
162
162
  if config.a2a_enabled:
163
163
  import uvicorn
164
- a2a_cfg = uvicorn.Config(
165
- app=a2a_app,
166
- host="0.0.0.0",
167
- port=config.a2a_port,
168
- log_level="error",
169
- )
170
- a2a_server = uvicorn.Server(a2a_cfg)
171
- a2a_task = asyncio.create_task(a2a_server.serve())
164
+ import socket
165
+
166
+ # Check if port is available
167
+ port_in_use = False
168
+ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
169
+ try:
170
+ s.bind(("0.0.0.0", config.a2a_port))
171
+ except OSError:
172
+ port_in_use = True
173
+
174
+ if port_in_use:
175
+ UI.print_error(f"Port {config.a2a_port} is already in use. A2A server disabled. Use --a2a-port <port> to change.")
176
+ a2a_server = None
177
+ a2a_task = None
178
+ else:
179
+ a2a_cfg = uvicorn.Config(
180
+ app=a2a_app,
181
+ host="0.0.0.0",
182
+ port=config.a2a_port,
183
+ log_level="error",
184
+ )
185
+ a2a_server = uvicorn.Server(a2a_cfg)
186
+ a2a_task = asyncio.create_task(a2a_server.serve())
172
187
  else:
173
188
  a2a_server = None
174
189
  a2a_task = None
@@ -217,12 +232,13 @@ async def main_async() -> None:
217
232
  config=config,
218
233
  repo_context=repo_context,
219
234
  )
220
- await app.run_async()
221
-
222
- if a2a_server and a2a_task:
223
- a2a_server.should_exit = True
224
- await a2a_task
225
- await mcp_manager.close()
235
+ try:
236
+ await app.run_async()
237
+ finally:
238
+ if a2a_server and a2a_task:
239
+ a2a_server.should_exit = True
240
+ await a2a_task
241
+ await mcp_manager.close()
226
242
 
227
243
 
228
244
  def main() -> None:
agent/mcp_client.py CHANGED
@@ -7,7 +7,7 @@ and registers them into the ToolRegistry.
7
7
  """
8
8
 
9
9
  import json
10
- from contextlib import AsyncExitStack
10
+ import asyncio
11
11
  from pathlib import Path
12
12
 
13
13
  from mcp.client.session import ClientSession
@@ -23,8 +23,60 @@ class MCPManager:
23
23
 
24
24
  def __init__(self, config_path: Path):
25
25
  self.config_path = config_path
26
- self.exit_stack = AsyncExitStack()
27
26
  self.sessions: dict[str, ClientSession] = {}
27
+ self._tasks: list[asyncio.Task] = []
28
+
29
+ async def _run_server(
30
+ self,
31
+ name: str,
32
+ command: str,
33
+ args: list[str],
34
+ env: dict | None,
35
+ registry: ToolRegistry,
36
+ ready_event: asyncio.Event
37
+ ) -> None:
38
+ try:
39
+ server_params = StdioServerParameters(command=command, args=args, env=env)
40
+ async with stdio_client(server_params) as (read, write):
41
+ async with ClientSession(read, write) as session:
42
+ await session.initialize()
43
+ self.sessions[name] = session
44
+
45
+ # Fetch and register tools
46
+ tools_response = await session.list_tools()
47
+ for mcp_tool in tools_response.tools:
48
+ canonical_schema = {
49
+ "name": mcp_tool.name,
50
+ "description": mcp_tool.description or "",
51
+ "input_schema": mcp_tool.inputSchema,
52
+ "_mcp_server_id": name,
53
+ }
54
+
55
+ def make_executor(session_ref: ClientSession, tool_name: str):
56
+ async def _executor(tool_input: dict) -> ToolResult:
57
+ try:
58
+ result = await session_ref.call_tool(tool_name, tool_input)
59
+ text_contents = [c.text for c in result.content if isinstance(c, TextContent)]
60
+ output = "\n".join(text_contents)
61
+ return ToolResult(output, is_error=result.isError)
62
+ except Exception as e:
63
+ return ToolResult(f"MCP execution error: {e}", is_error=True)
64
+ return _executor
65
+
66
+ registry.register_mcp_tool(canonical_schema, make_executor(session, mcp_tool.name))
67
+
68
+ UI.print_info(f"Connected to MCP server: {name} ({len(tools_response.tools)} tools)")
69
+ ready_event.set()
70
+
71
+ try:
72
+ await asyncio.Event().wait()
73
+ except asyncio.CancelledError:
74
+ pass
75
+ except Exception as e:
76
+ UI.print_error(f"Failed to connect to MCP server '{name}': {e}")
77
+ registry.deregister_mcp_tools(name)
78
+ finally:
79
+ ready_event.set()
28
80
 
29
81
  async def connect_all(self, registry: ToolRegistry) -> None:
30
82
  """Connect to all servers in mcp_servers.json and register tools."""
@@ -39,14 +91,12 @@ class MCPManager:
39
91
  UI.print_error(f"Failed to read mcp_servers.json: {e}")
40
92
  return
41
93
 
42
- # Handle both list of dicts and dict of dicts formats for mcp_servers.json
43
94
  if isinstance(servers, dict):
44
- # In official MCP config format, it's a dict mapping name to config
45
95
  server_items = servers.items()
46
96
  else:
47
- # Fallback if it's a list
48
97
  server_items = [(s.get("name", f"server_{i}"), s) for i, s in enumerate(servers)]
49
98
 
99
+ events = []
50
100
  for name, server_config in server_items:
51
101
  if server_config.get("enabled", True) is False:
52
102
  continue
@@ -58,47 +108,23 @@ class MCPManager:
58
108
  UI.print_error(f"MCP server '{name}' missing 'command'. Skipping.")
59
109
  continue
60
110
 
61
- try:
62
- server_params = StdioServerParameters(command=command, args=args, env=server_config.get("env"))
63
- stdio_transport = await self.exit_stack.enter_async_context(stdio_client(server_params))
64
- read, write = stdio_transport
65
- session = await self.exit_stack.enter_async_context(ClientSession(read, write))
66
- await session.initialize()
67
-
68
- self.sessions[name] = session
69
-
70
- # Fetch and register tools
71
- tools_response = await session.list_tools()
72
- for mcp_tool in tools_response.tools:
73
- # Convert to canonical schema format
74
- canonical_schema = {
75
- "name": mcp_tool.name,
76
- "description": mcp_tool.description or "",
77
- "input_schema": mcp_tool.inputSchema,
78
- "_mcp_server_id": name,
79
- }
80
-
81
- # Create closure for execution
82
- def make_executor(session_ref: ClientSession, tool_name: str):
83
- async def _executor(tool_input: dict) -> ToolResult:
84
- try:
85
- result = await session_ref.call_tool(tool_name, tool_input)
86
- # Flatten result text
87
- text_contents = [c.text for c in result.content if isinstance(c, TextContent)]
88
- output = "\n".join(text_contents)
89
- return ToolResult(output, is_error=result.isError)
90
- except Exception as e:
91
- return ToolResult(f"MCP execution error: {e}", is_error=True)
92
- return _executor
93
-
94
- registry.register_mcp_tool(canonical_schema, make_executor(session, mcp_tool.name))
95
-
96
- UI.print_info(f"Connected to MCP server: {name} ({len(tools_response.tools)} tools)")
97
- except Exception as e:
98
- UI.print_error(f"Failed to connect to MCP server '{name}': {e}")
99
- registry.deregister_mcp_tools(name)
111
+ ready_event = asyncio.Event()
112
+ events.append(ready_event)
113
+ task = asyncio.create_task(
114
+ self._run_server(name, command, args, server_config.get("env"), registry, ready_event)
115
+ )
116
+ self._tasks.append(task)
117
+
118
+ if events:
119
+ await asyncio.gather(*(e.wait() for e in events))
100
120
 
101
121
  async def close(self) -> None:
102
122
  """Close all connections."""
103
- await self.exit_stack.aclose()
123
+ for task in self._tasks:
124
+ task.cancel()
125
+
126
+ if self._tasks:
127
+ await asyncio.gather(*self._tasks, return_exceptions=True)
128
+
104
129
  self.sessions.clear()
130
+ self._tasks.clear()
agent/ui.py CHANGED
@@ -171,7 +171,7 @@ class UI:
171
171
  if UI._tui_app:
172
172
  UI._tui_app.post_message(ErrorEvent(msg))
173
173
  return
174
- console.print(f"[danger] {msg}[/danger]")
174
+ console.print(f"[danger]Error: {msg}[/danger]")
175
175
 
176
176
  @staticmethod
177
177
  def print_info(msg: str) -> None:
@@ -179,7 +179,7 @@ class UI:
179
179
  if UI._tui_app:
180
180
  UI._tui_app.post_message(InfoEvent(msg))
181
181
  return
182
- console.print(f"[info]ℹ️ {msg}[/info]")
182
+ console.print(f"[info]Info: {msg}[/info]")
183
183
 
184
184
  @staticmethod
185
185
  def print_success(msg: str) -> None:
@@ -187,7 +187,7 @@ class UI:
187
187
  if UI._tui_app:
188
188
  UI._tui_app.post_message(SuccessEvent(msg))
189
189
  return
190
- console.print(f"[bold green] {msg}[/bold green]")
190
+ console.print(f"[bold green]Success: {msg}[/bold green]")
191
191
 
192
192
  @staticmethod
193
193
  def print_thinking_block(thinking_text: str) -> None:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: devpilot-agentic-cli
3
- Version: 1.0.0
3
+ Version: 1.0.2
4
4
  Summary: Autonomous AI coding agent for your terminal — Claude, GPT-4o, Groq, Ollama, and more
5
5
  Author: Thijesh Praveen V
6
6
  License: MIT
@@ -1,14 +1,14 @@
1
1
  agent/__init__.py,sha256=lfJPQT9NbRRWfa9YABD9u8apcLjs3tzDVZvrOb69GBQ,25
2
2
  agent/a2a_client.py,sha256=vY4qZahkWP-DVU2smIeTz1xLj61kkDjE9Tmt_-9PKUU,3416
3
3
  agent/a2a_server.py,sha256=3rkDy8dm49_5qCa6fKYkLdmHTnHs5ocHBuZ8j32-p48,5230
4
- agent/cli.py,sha256=qhhBnJUKfzL7wlQOm9IP_S_0JdmpwESU4JMXDVZVGoU,8208
4
+ agent/cli.py,sha256=YdgLLIk4vgK-gGZ-Ia-kSZ_BbgfB2WE9QX83jiGsv48,8819
5
5
  agent/config.py,sha256=GWS719hDNvvzgJAq9MF2FAGEVwM1AKA_B4WSDDzKEAg,10580
6
6
  agent/context.py,sha256=a--jOYLX5jM-8RDLV0tAvZHOx9H5M6SNxSJipzEUZe4,7323
7
7
  agent/history.py,sha256=IMY3IO5TN9Sh7tJPAWE9VZYC0pkAwgPj2a_F1yA4OmE,6730
8
8
  agent/loop.py,sha256=8lTvCSAljPdmyyJ1uf0Q9qulUFNE3d4v3ZNzRae4c1w,3514
9
- agent/mcp_client.py,sha256=vR09S4q5UrI4Ps3Uz7ZKdK4QN0UBJ3ouCtUrdJQZIIo,4416
9
+ agent/mcp_client.py,sha256=lasmMq602_OEirzTNO57dNEF7mWaed_A6NaNTY3voOQ,4978
10
10
  agent/setup_wizard.py,sha256=jCXpp1vY9HRqJLO9eC6M9T6JlHXP7LsausmhkciB0d8,12774
11
- agent/ui.py,sha256=pOavPs73WIT-fdROsN_h4KAcyr6qQxNhBfjoSwOos6o,8276
11
+ agent/ui.py,sha256=LK8L2fTq_JIcYiVVjpVWaoYMFWXwqUcqYL4gspdAqvs,8283
12
12
  agent/providers/__init__.py,sha256=6xBg26vCBV2e3hybMT2JCHEZoRNpImkVOK-FytBW4xY,166
13
13
  agent/providers/anthropic_provider.py,sha256=i257T8T7fYuUEVvsy-3_eudbjSlxZqbn3zi7DFvukRE,6067
14
14
  agent/providers/base.py,sha256=2ppPB5ko9FrtS-U20E5rcKUQ1KAp6adnqmIRHdYONh4,5174
@@ -28,8 +28,8 @@ agent/tools/shell.py,sha256=i9nar3gyS28p6wjfAljNREUll-7RiD_LhQQ_CDkz-CM,3923
28
28
  agent/tools/web_search.py,sha256=7a533lYcRG0rwYle00xsIncfPRL1zCiS-wsL_qVxlyI,3736
29
29
  agent/tui/__init__.py,sha256=FB4u1BY9RSmYVfCM2yMeCyMmzjnb9T0bhmPp9hHH8Ys,37
30
30
  agent/tui/app.py,sha256=ZClXMLgXvohtLlYogKMpQISjEiFTHzNP6XPSeM6y4xU,18598
31
- devpilot_agentic_cli-1.0.0.dist-info/METADATA,sha256=2deUrhlyQc90YKHuHF4wpfAo8Ry6mP8x0uA2ijt-pyU,11105
32
- devpilot_agentic_cli-1.0.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
33
- devpilot_agentic_cli-1.0.0.dist-info/entry_points.txt,sha256=Zi0zH4sqSQptehekvn5CBhU452GQ23Vyja4l8C58IuY,44
34
- devpilot_agentic_cli-1.0.0.dist-info/top_level.txt,sha256=0gvCG7PHc22NA63j3bTGi2Zc37ym9t8Pf90ZLzf1kGA,6
35
- devpilot_agentic_cli-1.0.0.dist-info/RECORD,,
31
+ devpilot_agentic_cli-1.0.2.dist-info/METADATA,sha256=HGoT4GuEaEX-l_-Fp4Cu5sewyRn9X058Bqp7HHUdXlA,11105
32
+ devpilot_agentic_cli-1.0.2.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
33
+ devpilot_agentic_cli-1.0.2.dist-info/entry_points.txt,sha256=Zi0zH4sqSQptehekvn5CBhU452GQ23Vyja4l8C58IuY,44
34
+ devpilot_agentic_cli-1.0.2.dist-info/top_level.txt,sha256=0gvCG7PHc22NA63j3bTGi2Zc37ym9t8Pf90ZLzf1kGA,6
35
+ devpilot_agentic_cli-1.0.2.dist-info/RECORD,,