devpilot-agentic-cli 1.0.0__tar.gz → 1.0.2__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.
Files changed (48) hide show
  1. {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.2}/PKG-INFO +1 -1
  2. {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.2}/agent/cli.py +30 -14
  3. devpilot_agentic_cli-1.0.2/agent/mcp_client.py +130 -0
  4. {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.2}/agent/ui.py +3 -3
  5. {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.2}/devpilot_agentic_cli.egg-info/PKG-INFO +1 -1
  6. {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.2}/pyproject.toml +1 -1
  7. devpilot_agentic_cli-1.0.0/agent/mcp_client.py +0 -104
  8. {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.2}/README.md +0 -0
  9. {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.2}/agent/__init__.py +0 -0
  10. {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.2}/agent/a2a_client.py +0 -0
  11. {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.2}/agent/a2a_server.py +0 -0
  12. {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.2}/agent/config.py +0 -0
  13. {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.2}/agent/context.py +0 -0
  14. {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.2}/agent/history.py +0 -0
  15. {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.2}/agent/loop.py +0 -0
  16. {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.2}/agent/providers/__init__.py +0 -0
  17. {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.2}/agent/providers/anthropic_provider.py +0 -0
  18. {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.2}/agent/providers/base.py +0 -0
  19. {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.2}/agent/providers/factory.py +0 -0
  20. {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.2}/agent/providers/openai_provider.py +0 -0
  21. {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.2}/agent/providers/system_prompt.py +0 -0
  22. {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.2}/agent/setup_wizard.py +0 -0
  23. {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.2}/agent/tools/__init__.py +0 -0
  24. {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.2}/agent/tools/a2a.py +0 -0
  25. {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.2}/agent/tools/base.py +0 -0
  26. {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.2}/agent/tools/diagram.py +0 -0
  27. {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.2}/agent/tools/doc_gen.py +0 -0
  28. {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.2}/agent/tools/fs.py +0 -0
  29. {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.2}/agent/tools/git_ops.py +0 -0
  30. {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.2}/agent/tools/registry.py +0 -0
  31. {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.2}/agent/tools/search_code.py +0 -0
  32. {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.2}/agent/tools/shell.py +0 -0
  33. {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.2}/agent/tools/web_search.py +0 -0
  34. {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.2}/agent/tui/__init__.py +0 -0
  35. {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.2}/agent/tui/app.py +0 -0
  36. {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.2}/devpilot_agentic_cli.egg-info/SOURCES.txt +0 -0
  37. {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.2}/devpilot_agentic_cli.egg-info/dependency_links.txt +0 -0
  38. {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.2}/devpilot_agentic_cli.egg-info/entry_points.txt +0 -0
  39. {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.2}/devpilot_agentic_cli.egg-info/requires.txt +0 -0
  40. {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.2}/devpilot_agentic_cli.egg-info/top_level.txt +0 -0
  41. {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.2}/setup.cfg +0 -0
  42. {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.2}/tests/test_config.py +0 -0
  43. {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.2}/tests/test_e2e.py +0 -0
  44. {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.2}/tests/test_history.py +0 -0
  45. {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.2}/tests/test_loop.py +0 -0
  46. {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.2}/tests/test_providers.py +0 -0
  47. {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.2}/tests/test_setup_wizard.py +0 -0
  48. {devpilot_agentic_cli-1.0.0 → devpilot_agentic_cli-1.0.2}/tests/test_tools.py +0 -0
@@ -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
@@ -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:
@@ -0,0 +1,130 @@
1
+ """
2
+ agent/mcp_client.py
3
+ ───────────────────
4
+ MCP Client Integration (Sprint 3).
5
+ Connects to servers defined in mcp_servers.json, discovers tools,
6
+ and registers them into the ToolRegistry.
7
+ """
8
+
9
+ import json
10
+ import asyncio
11
+ from pathlib import Path
12
+
13
+ from mcp.client.session import ClientSession
14
+ from mcp.client.stdio import StdioServerParameters, stdio_client
15
+ from mcp.types import TextContent
16
+
17
+ from agent.tools import ToolRegistry, ToolResult
18
+ from agent.ui import UI
19
+
20
+
21
+ class MCPManager:
22
+ """Manages connections to multiple MCP servers."""
23
+
24
+ def __init__(self, config_path: Path):
25
+ self.config_path = config_path
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()
80
+
81
+ async def connect_all(self, registry: ToolRegistry) -> None:
82
+ """Connect to all servers in mcp_servers.json and register tools."""
83
+ if not self.config_path.exists():
84
+ return
85
+
86
+ try:
87
+ with open(self.config_path, "r", encoding="utf-8") as f:
88
+ data = json.load(f)
89
+ servers = data.get("mcpServers", data.get("servers", {}))
90
+ except (json.JSONDecodeError, OSError) as e:
91
+ UI.print_error(f"Failed to read mcp_servers.json: {e}")
92
+ return
93
+
94
+ if isinstance(servers, dict):
95
+ server_items = servers.items()
96
+ else:
97
+ server_items = [(s.get("name", f"server_{i}"), s) for i, s in enumerate(servers)]
98
+
99
+ events = []
100
+ for name, server_config in server_items:
101
+ if server_config.get("enabled", True) is False:
102
+ continue
103
+
104
+ command = server_config.get("command")
105
+ args = server_config.get("args", [])
106
+
107
+ if not command:
108
+ UI.print_error(f"MCP server '{name}' missing 'command'. Skipping.")
109
+ continue
110
+
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))
120
+
121
+ async def close(self) -> None:
122
+ """Close all connections."""
123
+ for task in self._tasks:
124
+ task.cancel()
125
+
126
+ if self._tasks:
127
+ await asyncio.gather(*self._tasks, return_exceptions=True)
128
+
129
+ self.sessions.clear()
130
+ self._tasks.clear()
@@ -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
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "devpilot-agentic-cli"
7
- version = "1.0.0"
7
+ version = "1.0.2"
8
8
  description = "Autonomous AI coding agent for your terminal — Claude, GPT-4o, Groq, Ollama, and more"
9
9
  authors = [{ name = "Thijesh Praveen V" }]
10
10
  requires-python = ">=3.11"
@@ -1,104 +0,0 @@
1
- """
2
- agent/mcp_client.py
3
- ───────────────────
4
- MCP Client Integration (Sprint 3).
5
- Connects to servers defined in mcp_servers.json, discovers tools,
6
- and registers them into the ToolRegistry.
7
- """
8
-
9
- import json
10
- from contextlib import AsyncExitStack
11
- from pathlib import Path
12
-
13
- from mcp.client.session import ClientSession
14
- from mcp.client.stdio import StdioServerParameters, stdio_client
15
- from mcp.types import TextContent
16
-
17
- from agent.tools import ToolRegistry, ToolResult
18
- from agent.ui import UI
19
-
20
-
21
- class MCPManager:
22
- """Manages connections to multiple MCP servers."""
23
-
24
- def __init__(self, config_path: Path):
25
- self.config_path = config_path
26
- self.exit_stack = AsyncExitStack()
27
- self.sessions: dict[str, ClientSession] = {}
28
-
29
- async def connect_all(self, registry: ToolRegistry) -> None:
30
- """Connect to all servers in mcp_servers.json and register tools."""
31
- if not self.config_path.exists():
32
- return
33
-
34
- try:
35
- with open(self.config_path, "r", encoding="utf-8") as f:
36
- data = json.load(f)
37
- servers = data.get("mcpServers", data.get("servers", {}))
38
- except (json.JSONDecodeError, OSError) as e:
39
- UI.print_error(f"Failed to read mcp_servers.json: {e}")
40
- return
41
-
42
- # Handle both list of dicts and dict of dicts formats for mcp_servers.json
43
- if isinstance(servers, dict):
44
- # In official MCP config format, it's a dict mapping name to config
45
- server_items = servers.items()
46
- else:
47
- # Fallback if it's a list
48
- server_items = [(s.get("name", f"server_{i}"), s) for i, s in enumerate(servers)]
49
-
50
- for name, server_config in server_items:
51
- if server_config.get("enabled", True) is False:
52
- continue
53
-
54
- command = server_config.get("command")
55
- args = server_config.get("args", [])
56
-
57
- if not command:
58
- UI.print_error(f"MCP server '{name}' missing 'command'. Skipping.")
59
- continue
60
-
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)
100
-
101
- async def close(self) -> None:
102
- """Close all connections."""
103
- await self.exit_stack.aclose()
104
- self.sessions.clear()