devpilot-agentic-cli 1.0.2__py3-none-any.whl → 1.0.3__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
@@ -12,18 +12,93 @@ import argparse
12
12
  import asyncio
13
13
  import os
14
14
  import sys
15
+ import time
15
16
  from datetime import datetime
16
17
  from pathlib import Path
17
18
 
18
- from agent.a2a_server import app as a2a_app
19
+
20
+ def _fix_windows_console() -> None:
21
+ """
22
+ Enable ANSI/VT100 support and UTF-8 encoding in Windows terminals.
23
+
24
+ Root cause of the blank black CMD screen:
25
+ - CMD.exe does NOT enable Virtual Terminal Processing by default.
26
+ - Textual sends ANSI escape sequences to probe terminal capabilities.
27
+ - Without VT support, CMD either hangs or renders nothing.
28
+ - PowerShell has VT enabled by default → works immediately.
29
+
30
+ Fixes applied:
31
+ 1. Enable ENABLE_VIRTUAL_TERMINAL_PROCESSING via Windows API (ctypes).
32
+ 2. Switch stdout/stderr to UTF-8 so → ✓ etc don't crash in cp1252.
33
+ 3. Set TERM / COLORTERM so Textual skips slow capability negotiation.
34
+ """
35
+ if sys.platform != "win32":
36
+ return
37
+
38
+ # ── 1. Enable Virtual Terminal Processing ─────────────────────────────────
39
+ try:
40
+ import ctypes
41
+ import ctypes.wintypes
42
+ kernel32 = ctypes.windll.kernel32 # type: ignore[attr-defined]
43
+
44
+ ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004
45
+ ENABLE_PROCESSED_OUTPUT = 0x0001
46
+
47
+ for handle_id in (-10, -11): # STD_INPUT=-10, STD_OUTPUT=-11
48
+ handle = kernel32.GetStdHandle(handle_id)
49
+ if handle and handle != -1:
50
+ mode = ctypes.wintypes.DWORD(0)
51
+ if kernel32.GetConsoleMode(handle, ctypes.byref(mode)):
52
+ new_mode = (mode.value
53
+ | ENABLE_VIRTUAL_TERMINAL_PROCESSING
54
+ | ENABLE_PROCESSED_OUTPUT)
55
+ kernel32.SetConsoleMode(handle, new_mode)
56
+ except Exception:
57
+ pass # non-fatal — worst case CMD is slow, not broken
58
+
59
+ # ── 2. Switch stdout/stderr to UTF-8 ──────────────────────────────────────
60
+ # Prevents UnicodeEncodeError on → ✓ ✗ characters in cp1252 CMD sessions
61
+ try:
62
+ if hasattr(sys.stdout, "reconfigure"):
63
+ sys.stdout.reconfigure(encoding="utf-8", errors="replace") # type: ignore[union-attr]
64
+ if hasattr(sys.stderr, "reconfigure"):
65
+ sys.stderr.reconfigure(encoding="utf-8", errors="replace") # type: ignore[union-attr]
66
+ # Also tell Windows console host to use UTF-8 codepage
67
+ import ctypes
68
+ ctypes.windll.kernel32.SetConsoleOutputCP(65001) # type: ignore[attr-defined]
69
+ except Exception:
70
+ pass
71
+
72
+ # ── 3. Hint terminal type so Textual doesn't hang on capability detection ─
73
+ # Textual checks these env vars first; without them it sends probe sequences
74
+ # that CMD ignores, causing a multi-second hang.
75
+ os.environ.setdefault("TERM", "xterm-256color")
76
+ os.environ.setdefault("COLORTERM", "truecolor")
77
+
78
+
79
+ # Run immediately — must happen before any rich/textual import
80
+ _fix_windows_console()
81
+
82
+ # Fast imports only at module level — heavy libs (fastapi, mcp) are lazy-loaded
19
83
  from agent.config import Config, ConfigError
20
84
  from agent.history import HistoryManager
21
85
  from agent.loop import run_agent_loop
22
- from agent.mcp_client import MCPManager
23
86
  from agent.providers.factory import create_provider
24
87
  from agent.tools import ToolRegistry
25
88
  from agent.ui import UI
26
89
 
90
+ _T0 = time.perf_counter()
91
+
92
+ def _log_startup(msg: str) -> None:
93
+ """Print a startup progress line. Helps users see DevPilot is loading."""
94
+ elapsed = time.perf_counter() - _T0
95
+ # Use plain ASCII fallback so CMD doesn't choke on escape codes if VT failed
96
+ print(f"\r[{elapsed:.1f}s] {msg}...", end="", flush=True)
97
+
98
+ def _clear_startup() -> None:
99
+ """Clear the startup progress line once TUI takes over."""
100
+ print("\r" + " " * 60 + "\r", end="", flush=True)
101
+
27
102
 
28
103
  def parse_args() -> argparse.Namespace:
29
104
  parser = argparse.ArgumentParser(
@@ -88,7 +163,8 @@ def _ensure_api_key(config: Config, args: argparse.Namespace) -> Config:
88
163
  # Force re-run wizard if --setup flag passed
89
164
  if args.setup or not config.active_api_key:
90
165
  from agent.setup_wizard import run_setup_wizard
91
- success = run_setup_wizard(env_path=Path(".env"))
166
+ _user_env = Path.home() / ".devpilot" / ".env"
167
+ success = run_setup_wizard(env_path=_user_env)
92
168
  if not success:
93
169
  # Wizard skipped (CI/no TTY) or failed — print helpful error
94
170
  UI.print_error(
@@ -115,6 +191,7 @@ async def main_async() -> None:
115
191
  _apply_cli_overrides(args)
116
192
 
117
193
  # Load config
194
+ _log_startup("Loading configuration")
118
195
  try:
119
196
  config = Config.load()
120
197
  except ConfigError as e:
@@ -123,8 +200,11 @@ async def main_async() -> None:
123
200
 
124
201
  # Handle --setup flag (force wizard even if key exists)
125
202
  if args.setup:
203
+ _clear_startup()
126
204
  from agent.setup_wizard import run_setup_wizard
127
- run_setup_wizard(env_path=Path(".env"))
205
+ _user_env = Path.home() / ".devpilot" / ".env"
206
+ run_setup_wizard(env_path=_user_env)
207
+
128
208
  try:
129
209
  config = Config.load()
130
210
  except ConfigError as e:
@@ -142,20 +222,26 @@ async def main_async() -> None:
142
222
  sys.exit(1)
143
223
 
144
224
  # Wire up provider, context, registry
225
+ _log_startup("Initializing AI provider")
145
226
  provider = create_provider(config)
146
227
 
228
+ _log_startup("Scanning project context")
147
229
  from agent.context import RepoContext
148
230
  repo_context = RepoContext(config.workdir)
149
231
  registry = ToolRegistry(config, _context=repo_context)
150
232
 
151
- # MCP
233
+ # MCP — lazy import to avoid loading the mcp library at startup
234
+ _log_startup("Connecting MCP servers")
235
+ from agent.mcp_client import MCPManager
152
236
  mcp_config_path = Path("mcp_servers.json")
153
237
  if not mcp_config_path.exists():
154
238
  mcp_config_path = Path(__file__).parent.parent / "mcp_servers.json"
155
239
  mcp_manager = MCPManager(mcp_config_path)
156
240
  await mcp_manager.connect_all(registry)
157
241
 
158
- # A2A server
242
+ # A2A server — lazy import to avoid loading fastapi/pydantic at startup
243
+ _log_startup("Starting A2A server")
244
+ from agent.a2a_server import app as a2a_app
159
245
  a2a_app.state.config = config
160
246
  a2a_app.state.registry = registry
161
247
 
@@ -224,7 +310,9 @@ async def main_async() -> None:
224
310
  sys.exit(0)
225
311
 
226
312
  # ── Interactive TUI mode ──────────────────────────────────────────────────
313
+ _log_startup("Starting DevPilot TUI")
227
314
  from agent.tui.app import DevPilotApp
315
+ _clear_startup() # erase progress line before TUI takes over the screen
228
316
  app = DevPilotApp(
229
317
  provider=provider,
230
318
  registry=registry,
agent/config.py CHANGED
@@ -17,8 +17,19 @@ from typing import Literal
17
17
 
18
18
  from dotenv import load_dotenv
19
19
 
20
- # ── Load .env file if present (safe to call even if file is missing) ─────────
21
- load_dotenv(dotenv_path=Path(__file__).resolve().parent.parent / ".env", override=False)
20
+ # ── Persistent user config: ~/.devpilot/.env ─────────────────────────────────
21
+ # This is where the setup wizard saves configuration so it persists
22
+ # regardless of which directory `devpilot` is run from.
23
+ _USER_CONFIG_DIR = Path.home() / ".devpilot"
24
+ _USER_ENV_FILE = _USER_CONFIG_DIR / ".env"
25
+
26
+ # Load global user config first — this is where the setup wizard saves settings
27
+ load_dotenv(dotenv_path=_USER_ENV_FILE, override=False)
28
+
29
+ # Load project-local .env second — can ADD vars but won't override global config
30
+ # (override=False means global ~/.devpilot/.env always wins)
31
+ load_dotenv(dotenv_path=Path(".") / ".env", override=False)
32
+
22
33
 
23
34
 
24
35
  # ── Custom exception ─────────────────────────────────────────────────────────
@@ -32,8 +43,8 @@ class ConfigError(Exception):
32
43
  Provider = Literal["anthropic", "openai"]
33
44
 
34
45
  PROVIDER_DEFAULTS: dict[str, str] = {
35
- "anthropic": "claude-opus-4-5",
36
- "openai": "gpt-4o",
46
+ "anthropic": "claude-opus-4-5-20251101",
47
+ "openai": "gpt-5.4",
37
48
  }
38
49
 
39
50
  # Keys required per provider (checked lazily so we can build/test without keys)
@@ -43,11 +54,15 @@ REQUIRED_ENV_KEYS: dict[str, str] = {
43
54
  }
44
55
 
45
56
  # Models that support extended thinking (Anthropic only)
57
+ # Verified via Anthropic API — May 2026
46
58
  THINKING_CAPABLE_MODELS: set[str] = {
59
+ "claude-opus-4-7",
60
+ "claude-opus-4-6",
61
+ "claude-opus-4-5-20251101",
62
+ "claude-opus-4-1-20250805",
63
+ "claude-sonnet-4-5-20250929",
47
64
  "claude-3-7-sonnet-20250219",
48
65
  "claude-3-5-sonnet-20241022",
49
- "claude-opus-4-5",
50
- "claude-opus-4-20250514",
51
66
  }
52
67
 
53
68
 
agent/context.py CHANGED
@@ -145,33 +145,31 @@ class RepoContext:
145
145
  return sigs
146
146
 
147
147
  def _build_project_tree(self, max_entries: int = 200) -> list[str]:
148
+ """
149
+ Build a fast file-listing project tree.
150
+ AST signature extraction is intentionally skipped here to avoid
151
+ blocking startup — signatures are extracted lazily on file read.
152
+ """
148
153
  entries: list[str] = []
149
154
  ignores = {
150
- ".git", "node_modules", ".venv", "__pycache__", "dist",
155
+ ".git", "node_modules", ".venv", "__pycache__", "dist",
151
156
  "build", ".next", ".tox", "coverage_html_report", ".devpilot_sessions"
152
157
  }
153
158
 
154
159
  def _traverse(directory: Path, prefix: str = "") -> None:
155
160
  if len(entries) >= max_entries:
156
161
  return
157
-
158
162
  try:
159
163
  for item in sorted(directory.iterdir()):
160
164
  if item.name.startswith(".") and item.name not in (".env", ".gitignore", ".github"):
161
165
  continue
162
166
  if item.name in ignores or item.name.endswith(".egg-info"):
163
167
  continue
164
-
165
168
  if item.is_dir():
166
169
  entries.append(f"{prefix}📁 {item.name}/")
167
170
  _traverse(item, prefix + " ")
168
171
  else:
169
172
  entries.append(f"{prefix}📄 {item.name}")
170
- if item.suffix in (".py", ".js", ".ts"):
171
- sigs = self._extract_signatures(item)
172
- for sig in sigs:
173
- entries.append(f"{prefix}{sig}")
174
-
175
173
  if len(entries) >= max_entries:
176
174
  entries.append("… (truncated to ~200 items for context size)")
177
175
  break
agent/mcp_client.py CHANGED
@@ -8,6 +8,7 @@ and registers them into the ToolRegistry.
8
8
 
9
9
  import json
10
10
  import asyncio
11
+ import shutil
11
12
  from pathlib import Path
12
13
 
13
14
  from mcp.client.session import ClientSession
@@ -18,6 +19,43 @@ from agent.tools import ToolRegistry, ToolResult
18
19
  from agent.ui import UI
19
20
 
20
21
 
22
+ # ── Install hints for common MCP server commands ─────────────────────────────
23
+
24
+ _INSTALL_HINTS: dict[str, str] = {
25
+ "npx": "Node.js is not installed or not on PATH.\n"
26
+ " → Install it from https://nodejs.org/ (LTS version recommended)\n"
27
+ " → After installing, restart your terminal and try again.",
28
+ "node": "Node.js is not installed or not on PATH.\n"
29
+ " → Install it from https://nodejs.org/",
30
+ "uvx": "'uvx' (uv tool runner) is not installed.\n"
31
+ " → Install uv: https://docs.astral.sh/uv/getting-started/installation/",
32
+ "docker": "Docker is not installed or not running.\n"
33
+ " → Install Docker Desktop from https://www.docker.com/products/docker-desktop/",
34
+ "python": "Python is not on PATH (unexpected — DevPilot itself runs on Python).",
35
+ "python3":"Python 3 is not on PATH.",
36
+ }
37
+
38
+
39
+ def _check_command(command: str, server_name: str) -> bool:
40
+ """
41
+ Return True if `command` is available on PATH.
42
+ If not, print a clear, actionable error and return False.
43
+ """
44
+ if shutil.which(command) is not None:
45
+ return True
46
+
47
+ hint = _INSTALL_HINTS.get(command.lower(),
48
+ f"'{command}' was not found on your PATH.\n"
49
+ f" → Make sure it is installed and your terminal can see it."
50
+ )
51
+ UI.print_error(
52
+ f"MCP server '{server_name}' requires '{command}' but it was not found.\n"
53
+ f" {hint}\n"
54
+ f" This MCP server will be skipped. Disable it in mcp_servers.json to suppress this message."
55
+ )
56
+ return False
57
+
58
+
21
59
  class MCPManager:
22
60
  """Manages connections to multiple MCP servers."""
23
61
 
@@ -79,7 +117,14 @@ class MCPManager:
79
117
  ready_event.set()
80
118
 
81
119
  async def connect_all(self, registry: ToolRegistry) -> None:
82
- """Connect to all servers in mcp_servers.json and register tools."""
120
+ """Connect to all servers in mcp_servers.json and register tools.
121
+
122
+ Uses a non-blocking approach: MCP servers connect in background tasks.
123
+ Startup waits at most MCP_CONNECT_TIMEOUT seconds total so a slow
124
+ npx download never freezes DevPilot at launch.
125
+ """
126
+ MCP_CONNECT_TIMEOUT = 5.0 # seconds to wait before giving up and continuing
127
+
83
128
  if not self.config_path.exists():
84
129
  return
85
130
 
@@ -92,7 +137,7 @@ class MCPManager:
92
137
  return
93
138
 
94
139
  if isinstance(servers, dict):
95
- server_items = servers.items()
140
+ server_items = list(servers.items())
96
141
  else:
97
142
  server_items = [(s.get("name", f"server_{i}"), s) for i, s in enumerate(servers)]
98
143
 
@@ -100,7 +145,7 @@ class MCPManager:
100
145
  for name, server_config in server_items:
101
146
  if server_config.get("enabled", True) is False:
102
147
  continue
103
-
148
+
104
149
  command = server_config.get("command")
105
150
  args = server_config.get("args", [])
106
151
 
@@ -108,6 +153,10 @@ class MCPManager:
108
153
  UI.print_error(f"MCP server '{name}' missing 'command'. Skipping.")
109
154
  continue
110
155
 
156
+ # Pre-flight: make sure the binary is actually installed
157
+ if not _check_command(command, name):
158
+ continue
159
+
111
160
  ready_event = asyncio.Event()
112
161
  events.append(ready_event)
113
162
  task = asyncio.create_task(
@@ -116,7 +165,21 @@ class MCPManager:
116
165
  self._tasks.append(task)
117
166
 
118
167
  if events:
119
- await asyncio.gather(*(e.wait() for e in events))
168
+ # Wait for all servers to be ready, but cap total wait time.
169
+ # If any server is slow (e.g. npx downloading a package), DevPilot
170
+ # still starts promptly and the server connects in the background.
171
+ try:
172
+ await asyncio.wait_for(
173
+ asyncio.gather(*(e.wait() for e in events)),
174
+ timeout=MCP_CONNECT_TIMEOUT,
175
+ )
176
+ except asyncio.TimeoutError:
177
+ pending = sum(1 for e in events if not e.is_set())
178
+ UI.print_info(
179
+ f"{pending} MCP server(s) still connecting in background "
180
+ f"(took >{MCP_CONNECT_TIMEOUT:.0f}s). DevPilot is ready now."
181
+ )
182
+
120
183
 
121
184
  async def close(self) -> None:
122
185
  """Close all connections."""
agent/setup_wizard.py CHANGED
@@ -25,24 +25,69 @@ from rich.table import Table
25
25
 
26
26
  console = Console()
27
27
 
28
- # ── Model lists ───────────────────────────────────────────────────────────────
28
+
29
+ def _prompt_secret(prompt_text: str) -> str:
30
+ """
31
+ Prompt for a secret (API key) showing '*' for each character typed.
32
+ Works on Windows (CMD / PowerShell) where rich's password=True renders
33
+ completely blank and provides no visual feedback to the user.
34
+ Falls back to rich Prompt (password=True) on non-TTY / CI environments.
35
+ """
36
+ import sys
37
+ if not sys.stdin.isatty():
38
+ return Prompt.ask(prompt_text, password=True).strip()
39
+
40
+ import platform
41
+ if platform.system() == "Windows":
42
+ import msvcrt
43
+ console.print(f"{prompt_text}: ", end="")
44
+ chars: list[str] = []
45
+ while True:
46
+ ch = msvcrt.getwch() # read one char without echo
47
+ if ch in ("\r", "\n"): # Enter key
48
+ console.print() # move to next line
49
+ break
50
+ elif ch == "\x03": # Ctrl+C
51
+ raise KeyboardInterrupt
52
+ elif ch in ("\x08", "\x7f"): # Backspace
53
+ if chars:
54
+ chars.pop()
55
+ # erase last asterisk: backspace + space + backspace
56
+ console.print("\b \b", end="")
57
+ elif ch == "\x00" or ch == "\xe0": # special key prefix — skip next byte
58
+ msvcrt.getwch()
59
+ else:
60
+ chars.append(ch)
61
+ console.print("*", end="")
62
+ return "".join(chars).strip()
63
+ else:
64
+ # Unix: getpass already hides input cleanly
65
+ import getpass
66
+ return getpass.getpass(f"{prompt_text}: ").strip()
67
+
68
+
69
+ # ── Model lists (verified against live APIs) ──────────────────────────────────
70
+ # Last verified: May 2026
29
71
 
30
72
  _ANTHROPIC_MODELS = [
31
- ("claude-opus-4-5-20251101", "Most capable best for complex tasks"),
32
- ("claude-sonnet-4-5-20251101", "Balancedfast and capable"),
33
- ("claude-haiku-4-5-20251101", "Fastestbest for simple tasks"),
34
- ("claude-3-7-sonnet-20250219", "Extended thinking support"),
73
+ # Verified via GET https://api.anthropic.com/v1/models
74
+ ("claude-opus-4-7", "Newestmost capable Claude (latest)"),
75
+ ("claude-opus-4-5-20251101", "Claude Opus 4.5 powerful & reliable"),
76
+ ("claude-sonnet-4-5-20250929", "Claude Sonnet 4.5 — balanced speed & quality"),
77
+ ("claude-haiku-4-5-20251001", "Claude Haiku 4.5 — fastest, most affordable"),
35
78
  ]
36
79
 
37
80
  _OPENAI_MODELS = [
38
- ("gpt-4o", "Latest GPT-4o best for coding"),
39
- ("gpt-4o-mini", "Fast and cheap good for simple tasks"),
40
- ("o3", "Most powerful reasoning model"),
41
- ("o4-mini", "Fast reasoninggreat for code"),
81
+ # Verified via platform.openai.com/docs/models (May 2026)
82
+ # Note: gpt-4o, o3, o4-mini are retired as of early 2026
83
+ ("gpt-5.5", "Flagship best reasoning & coding"),
84
+ ("gpt-5.4", "Primary modelcoding & professional work"),
85
+ ("gpt-5.4-mini", "Fast & cost-efficient"),
86
+ ("gpt-5.4-nano", "Fastest — high-volume low-latency tasks"),
42
87
  ]
43
88
 
44
89
  # OpenAI-compatible third-party providers
45
- # (base_url, display_name, key_env_var, key_url, models)
90
+ # (display_name, key_url, key_env_var, base_url, models)
46
91
  _COMPATIBLE_PROVIDERS = {
47
92
  "1": (
48
93
  "Groq",
@@ -50,10 +95,12 @@ _COMPATIBLE_PROVIDERS = {
50
95
  "GROQ_API_KEY",
51
96
  "https://api.groq.com/openai/v1",
52
97
  [
53
- ("llama-3.3-70b-versatile", "Best Groq model — fast & capable"),
54
- ("llama-3.1-8b-instant", "Ultra-fast, lightweight"),
55
- ("mixtral-8x7b-32768", "Large context window"),
56
- ("gemma2-9b-it", "Google Gemma 2 fast"),
98
+ # Verified via GET https://api.groq.com/openai/v1/models
99
+ ("llama-3.3-70b-versatile", "Llama 3.3 70B — best quality (131k ctx)"),
100
+ ("meta-llama/llama-4-scout-17b-16e-instruct","Llama 4 Scout 17B — latest & fast"),
101
+ ("qwen/qwen3-32b", "Qwen3 32Bstrong reasoning"),
102
+ ("llama-3.1-8b-instant", "Llama 3.1 8B — ultra-fast"),
103
+ ("groq/compound", "Groq Compound — agentic tasks"),
57
104
  ],
58
105
  ),
59
106
  "2": (
@@ -62,9 +109,12 @@ _COMPATIBLE_PROVIDERS = {
62
109
  "TOGETHER_API_KEY",
63
110
  "https://api.together.xyz/v1",
64
111
  [
65
- ("meta-llama/Llama-3-70b-chat-hf", "Llama 3 70B best quality"),
66
- ("meta-llama/Llama-3-8b-chat-hf", "Llama 3 8Bfaster"),
67
- ("mistralai/Mixtral-8x7B-v0.1", "Mixtrallarge context"),
112
+ # Verified via Together AI docs (May 2026)
113
+ ("meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8", "Llama 4 Maverickbest quality"),
114
+ ("meta-llama/Llama-4-Scout-17B-16E-Instruct-FP8", "Llama 4 Scout fast MoE"),
115
+ ("meta-llama/Llama-3.3-70B-Instruct-Turbo", "Llama 3.3 70B Turbo — reliable"),
116
+ ("meta-llama/Meta-Llama-3.1-405B-Instruct-Turbo", "Llama 3.1 405B — most capable"),
117
+ ("meta-llama/Meta-Llama-3.1-8B-Instruct-Turbo", "Llama 3.1 8B — very fast"),
68
118
  ],
69
119
  ),
70
120
  "3": (
@@ -73,9 +123,10 @@ _COMPATIBLE_PROVIDERS = {
73
123
  "MISTRAL_API_KEY",
74
124
  "https://api.mistral.ai/v1",
75
125
  [
76
- ("mistral-large-latest", "Most capable Mistral model"),
77
- ("mistral-small-latest", "Fast and affordable"),
78
- ("codestral-latest", "Optimised for code"),
126
+ # Verified via Mistral docs — latest aliases always point to newest stable
127
+ ("mistral-large-latest", "Most capable Mistral model"),
128
+ ("mistral-small-latest", "Fast and affordable"),
129
+ ("codestral-latest", "Optimised for code generation"),
79
130
  ],
80
131
  ),
81
132
  "4": (
@@ -84,10 +135,11 @@ _COMPATIBLE_PROVIDERS = {
84
135
  "OLLAMA_API_KEY", # Ollama doesn't need a real key
85
136
  "http://localhost:11434/v1",
86
137
  [
87
- ("qwen2.5-coder:7b", "Best local coding model (recommended)"),
88
- ("codellama:13b", "Meta CodeLlama 13B"),
89
- ("deepseek-coder:6b", "DeepSeek Coder 6B"),
90
- ("llama3.2:3b", "Llama 3.2 3B very fast"),
138
+ # Run: ollama pull <model> before using
139
+ ("qwen2.5-coder:7b", "Qwen2.5-Coder 7B — best local coding (8GB VRAM)"),
140
+ ("qwen2.5-coder:32b", "Qwen2.5-Coder 32B — benchmark king (24GB VRAM)"),
141
+ ("deepseek-coder-v2:16b","DeepSeek-Coder-V2 16Bstrong coding (16GB VRAM)"),
142
+ ("llama3.3:70b", "Llama 3.3 70B — general purpose"),
91
143
  ],
92
144
  ),
93
145
  "5": (
@@ -100,6 +152,7 @@ _COMPATIBLE_PROVIDERS = {
100
152
  }
101
153
 
102
154
 
155
+
103
156
  # ── Helpers ───────────────────────────────────────────────────────────────────
104
157
 
105
158
  def _is_interactive() -> bool:
@@ -162,12 +215,17 @@ def _pick_model(models: list[tuple[str, str]], allow_custom: bool = False) -> st
162
215
  def run_setup_wizard(env_path: Path | None = None) -> bool:
163
216
  """
164
217
  Run the interactive first-run setup wizard.
218
+ Saves configuration to ~/.devpilot/.env so settings persist
219
+ regardless of which directory `devpilot` is run from.
165
220
  Returns True if setup completed, False if skipped/failed.
166
221
  """
167
222
  if not _is_interactive():
168
223
  return False
169
224
 
170
- env_path = env_path or Path(".env")
225
+ # Always save to the persistent user config directory
226
+ user_config_dir = Path.home() / ".devpilot"
227
+ user_config_dir.mkdir(parents=True, exist_ok=True)
228
+ env_path = env_path or (user_config_dir / ".env")
171
229
 
172
230
  console.print(Panel(
173
231
  "[bold cyan]Welcome to DevPilot! 🚀[/bold cyan]\n\n"
@@ -179,12 +237,12 @@ def run_setup_wizard(env_path: Path | None = None) -> bool:
179
237
 
180
238
  # ── Step 1: Choose provider ───────────────────────────────────────────────
181
239
  console.print("\n[bold]Step 1 of 3 — Choose your AI provider[/bold]\n")
182
- console.print(" [cyan]1[/cyan] Anthropic (Claude recommended)")
183
- console.print(" [cyan]2[/cyan] OpenAI (GPT-4o, o3, o4-mini)")
184
- console.print(" [cyan]3[/cyan] Groq (Llama 3, Mixtral — very fast, free tier)")
185
- console.print(" [cyan]4[/cyan] Together AI (Llama 3, Mixtral)")
186
- console.print(" [cyan]5[/cyan] Mistral AI (Mistral, Codestral)")
187
- console.print(" [cyan]6[/cyan] Ollama (local models, no API key needed)")
240
+ console.print(" [cyan]1[/cyan] Anthropic (Claude Opus 4.7, Sonnet 4.5, Haiku 4.5)")
241
+ console.print(" [cyan]2[/cyan] OpenAI (GPT-5.5, GPT-5.4, GPT-5.4-mini)")
242
+ console.print(" [cyan]3[/cyan] Groq (Llama 4 Scout, Llama 3.3 70B — very fast, free tier)")
243
+ console.print(" [cyan]4[/cyan] Together AI (Llama 4 Maverick/Scout, Llama 3.3 70B)")
244
+ console.print(" [cyan]5[/cyan] Mistral AI (Mistral Large, Codestral)")
245
+ console.print(" [cyan]6[/cyan] Ollama (local models Qwen2.5-Coder, DeepSeek, Llama)")
188
246
  console.print(" [cyan]7[/cyan] Other (any OpenAI-compatible endpoint)")
189
247
 
190
248
  choice = Prompt.ask("\nProvider", choices=["1","2","3","4","5","6","7"], default="1")
@@ -195,7 +253,7 @@ def run_setup_wizard(env_path: Path | None = None) -> bool:
195
253
  if choice == "1":
196
254
  console.print("\n[bold]Step 2 of 3 — Enter your Anthropic API key[/bold]")
197
255
  console.print(" Get one at: [link=https://console.anthropic.com/]https://console.anthropic.com/[/link]")
198
- api_key = Prompt.ask("\nANTHROPIC_API_KEY", password=True).strip()
256
+ api_key = _prompt_secret("ANTHROPIC_API_KEY")
199
257
  if not api_key:
200
258
  console.print("[red]API key cannot be empty.[/red]")
201
259
  return False
@@ -215,7 +273,7 @@ def run_setup_wizard(env_path: Path | None = None) -> bool:
215
273
  elif choice == "2":
216
274
  console.print("\n[bold]Step 2 of 3 — Enter your OpenAI API key[/bold]")
217
275
  console.print(" Get one at: [link=https://platform.openai.com/api-keys]https://platform.openai.com/api-keys[/link]")
218
- api_key = Prompt.ask("\nOPENAI_API_KEY", password=True).strip()
276
+ api_key = _prompt_secret("OPENAI_API_KEY")
219
277
  if not api_key:
220
278
  console.print("[red]API key cannot be empty.[/red]")
221
279
  return False
@@ -244,7 +302,7 @@ def run_setup_wizard(env_path: Path | None = None) -> bool:
244
302
  api_key = "ollama"
245
303
  else:
246
304
  console.print(f" Get one at: [link={key_url}]{key_url}[/link]")
247
- api_key = Prompt.ask(f"\n{key_env}", password=True).strip()
305
+ api_key = _prompt_secret(key_env)
248
306
  if not api_key:
249
307
  console.print("[red]API key cannot be empty.[/red]")
250
308
  return False
@@ -264,7 +322,7 @@ def run_setup_wizard(env_path: Path | None = None) -> bool:
264
322
  elif choice == "7":
265
323
  console.print("\n[bold]Step 2 of 3 — Custom OpenAI-compatible endpoint[/bold]")
266
324
  base_url = Prompt.ask("Base URL (e.g. https://api.example.com/v1)").strip()
267
- api_key = Prompt.ask("API key", password=True).strip()
325
+ api_key = _prompt_secret("API key")
268
326
  model = Prompt.ask("Model name (e.g. llama-3-70b)").strip()
269
327
 
270
328
  if not base_url or not model:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: devpilot-agentic-cli
3
- Version: 1.0.2
3
+ Version: 1.0.3
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
@@ -46,7 +46,7 @@ Requires-Dist: pytest-asyncio>=0.23.7; extra == "dev"
46
46
  [![Python](https://img.shields.io/pypi/pyversions/devpilot-agentic-cli)](https://pypi.org/project/devpilot-agentic-cli/)
47
47
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
48
48
 
49
- **An autonomous AI coding agent for your terminal.** DevPilot gives Claude, GPT-4o, Groq, Mistral, or any local model a full suite of tools — file read/write, bash execution, code search, git, MCP, and more — orchestrated into a self-healing agentic loop inside a rich Textual TUI.
49
+ **An autonomous AI coding agent for your terminal.** DevPilot gives Claude, GPT, Groq, Mistral, or any local model a full suite of tools — file read/write, bash execution, code search, git, MCP, and more — orchestrated into a self-healing agentic loop inside a rich Textual TUI. Works in PowerShell, CMD, and any modern terminal.
50
50
 
51
51
  ```
52
52
  ┌─ DevPilot ──────────────────────────────────────────────────────┐
@@ -76,7 +76,6 @@ Requires-Dist: pytest-asyncio>=0.23.7; extra == "dev"
76
76
  | **Git** | `git_status`, `git_commit` with surgical file staging |
77
77
  | **Documentation** | `doc_gen` (markdown), `diagram` (Mermaid) |
78
78
  | **Web search** | `web_search` via Tavily (optional) |
79
-
80
79
  | **MCP** | Connect any MCP server via `mcp_servers.json` |
81
80
  | **A2A** | Agent-to-agent task delegation over HTTP |
82
81
  | **Providers** | Anthropic, OpenAI, Groq, Together AI, Mistral, Ollama, any OpenAI-compatible endpoint |
@@ -92,7 +91,7 @@ pip install devpilot-agentic-cli
92
91
  devpilot
93
92
  ```
94
93
 
95
- That's it. On first run DevPilot launches a setup wizard — pick your provider, enter your API key, choose a model. It saves everything to a `.env` file and opens the TUI. Every subsequent run starts immediately.
94
+ That's it. On first run DevPilot launches a setup wizard — pick your provider, enter your API key, choose a model. It saves everything to `~/.devpilot/.env` and opens the TUI. Every subsequent run starts immediately.
96
95
 
97
96
  ### Optional Features
98
97
 
@@ -111,19 +110,19 @@ DevPilot works with any of these out of the box — the setup wizard walks you t
111
110
 
112
111
  | Provider | Models | Get API key |
113
112
  |---|---|---|
114
- | **Anthropic** | claude-opus-4-5, claude-sonnet-4-5, claude-haiku-4-5 | [console.anthropic.com](https://console.anthropic.com/) |
115
- | **OpenAI** | gpt-4o, gpt-4o-mini, o3, o4-mini | [platform.openai.com](https://platform.openai.com/api-keys) |
116
- | **Groq** | llama-3.3-70b, mixtral-8x7b, gemma2-9b | [console.groq.com](https://console.groq.com/keys) |
117
- | **Together AI** | Llama 3, Mixtral, and more | [api.together.xyz](https://api.together.xyz/settings/api-keys) |
118
- | **Mistral AI** | mistral-large, codestral | [console.mistral.ai](https://console.mistral.ai/api-keys/) |
119
- | **Ollama** | Any local model — no API key needed | [ollama.com](https://ollama.com/library) |
113
+ | **Anthropic** | claude-opus-4-7, claude-opus-4-5-20251101, claude-sonnet-4-5-20250929, claude-haiku-4-5-20251001 | [console.anthropic.com](https://console.anthropic.com/) |
114
+ | **OpenAI** | gpt-5.5, gpt-5.4, gpt-5.4-mini, gpt-5.4-nano | [platform.openai.com](https://platform.openai.com/api-keys) |
115
+ | **Groq** | llama-3.3-70b-versatile, llama-4-scout-17b, qwen3-32b | [console.groq.com](https://console.groq.com/keys) |
116
+ | **Together AI** | Llama 4 Maverick/Scout, Llama 3.3 70B Turbo | [api.together.xyz](https://api.together.xyz/settings/api-keys) |
117
+ | **Mistral AI** | mistral-large-latest, mistral-small-latest, codestral-latest | [console.mistral.ai](https://console.mistral.ai/api-keys/) |
118
+ | **Ollama** | qwen2.5-coder:7b, deepseek-coder-v2:16b, llama3.3:70b — no API key needed | [ollama.com](https://ollama.com/library) |
120
119
  | **Other** | Any OpenAI-compatible endpoint | — |
121
120
 
122
121
  ---
123
122
 
124
123
  ## Configuration
125
124
 
126
- All settings live in a `.env` file in your project directory. The setup wizard creates this on first run. You can edit it manually anytime — or re-run the wizard with `devpilot --setup`.
125
+ All settings live in `~/.devpilot/.env` — a persistent file in your home directory that works regardless of which folder you run `devpilot` from. The setup wizard creates this on first run. You can edit it manually anytime — or re-run the wizard with `devpilot --setup`.
127
126
 
128
127
  ### Full settings reference
129
128
 
@@ -131,9 +130,10 @@ All settings live in a `.env` file in your project directory. The setup wizard c
131
130
  |---|---|---|
132
131
  | `ANTHROPIC_API_KEY` | — | API key for Anthropic provider |
133
132
  | `OPENAI_API_KEY` | — | API key for OpenAI / Groq / Together / Mistral / Ollama |
133
+ | `GROQ_API_KEY` | — | API key for Groq (optional — can reuse as OPENAI_API_KEY) |
134
134
  | `DEVPILOT_PROVIDER` | `anthropic` | `anthropic` or `openai` |
135
- | `DEVPILOT_MODEL` | `claude-opus-4-5` | Model name |
136
- | `DEVPILOT_BASE_URL` | — | Custom endpoint, e.g. `http://localhost:11434/v1` for Ollama |
135
+ | `DEVPILOT_MODEL` | `claude-opus-4-5-20251101` | Model name |
136
+ | `DEVPILOT_BASE_URL` | — | Custom endpoint, e.g. `https://api.groq.com/openai/v1` |
137
137
  | `DEVPILOT_NO_CONFIRM` | `false` | Skip confirmation prompts (useful for CI) |
138
138
  | `DEVPILOT_MAX_ITERATIONS` | `50` | Max tool-use iterations before loop aborts |
139
139
  | `DEVPILOT_WORKDIR` | `cwd` | Root directory for file operations |
@@ -144,12 +144,12 @@ All settings live in a `.env` file in your project directory. The setup wizard c
144
144
  ### Override priority
145
145
 
146
146
  ```
147
- CLI flags → env vars → .env file → defaults
147
+ CLI flags → env vars → ~/.devpilot/.env → defaults
148
148
  ```
149
149
 
150
150
  Per-run override example:
151
151
  ```bash
152
- DEVPILOT_MODEL=claude-haiku-4-5-20251101 devpilot --task "fix typos"
152
+ DEVPILOT_MODEL=llama-3.3-70b-versatile devpilot --task "fix typos"
153
153
  ```
154
154
 
155
155
  ---
@@ -172,7 +172,6 @@ Options:
172
172
  --a2a-port PORT A2A server port (default: 8000)
173
173
  --no-a2a Disable A2A server
174
174
  --no-web-search Disable Tavily web search
175
-
176
175
  --setup Re-run the setup wizard
177
176
  ```
178
177
 
@@ -232,7 +231,7 @@ agent/
232
231
  ├── setup_wizard.py First-run interactive configuration wizard
233
232
  ├── loop.py Core agentic loop (plan → act → verify → heal)
234
233
  ├── config.py Config dataclass — all settings from env vars
235
- ├── context.py RepoContext — file awareness, AST map
234
+ ├── context.py RepoContext — file awareness, AST project map
236
235
  ├── history.py Conversation history + smart context pruning
237
236
  ├── providers/
238
237
  │ ├── anthropic_provider.py
@@ -247,7 +246,6 @@ agent/
247
246
  │ ├── doc_gen.py doc_gen
248
247
  │ ├── diagram.py diagram (Mermaid)
249
248
  │ ├── web_search.py web_search (Tavily)
250
-
251
249
  │ ├── a2a.py A2A delegation
252
250
  │ └── registry.py ToolRegistry + PermissionGuard
253
251
  └── tui/
@@ -1,13 +1,13 @@
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=YdgLLIk4vgK-gGZ-Ia-kSZ_BbgfB2WE9QX83jiGsv48,8819
5
- agent/config.py,sha256=GWS719hDNvvzgJAq9MF2FAGEVwM1AKA_B4WSDDzKEAg,10580
6
- agent/context.py,sha256=a--jOYLX5jM-8RDLV0tAvZHOx9H5M6SNxSJipzEUZe4,7323
4
+ agent/cli.py,sha256=X8GSasVVjtE9b-rEPnoy6lBf0L2Q9MJ2DX7Otb91xoA,12897
5
+ agent/config.py,sha256=b53O7kkdAUYTNCm7YLxBYiySj6ZXn2qzoWqgPN1NIUw,11244
6
+ agent/context.py,sha256=atGJgIiMAmYfcNu6k9gwUzsz5cykBAx9A8PCdgK64R0,7297
7
7
  agent/history.py,sha256=IMY3IO5TN9Sh7tJPAWE9VZYC0pkAwgPj2a_F1yA4OmE,6730
8
8
  agent/loop.py,sha256=8lTvCSAljPdmyyJ1uf0Q9qulUFNE3d4v3ZNzRae4c1w,3514
9
- agent/mcp_client.py,sha256=lasmMq602_OEirzTNO57dNEF7mWaed_A6NaNTY3voOQ,4978
10
- agent/setup_wizard.py,sha256=jCXpp1vY9HRqJLO9eC6M9T6JlHXP7LsausmhkciB0d8,12774
9
+ agent/mcp_client.py,sha256=KquR1Hp_GRA37aIFQaBrD3hYlI5ZLkF9Q5yH_x5XGbE,7764
10
+ agent/setup_wizard.py,sha256=KNDxy-0jC6erB9IGpZoPCcrgpzU523D8p1zEx8aMbC0,15655
11
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
@@ -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.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,,
31
+ devpilot_agentic_cli-1.0.3.dist-info/METADATA,sha256=xgWjgjjANVyROUmPQvTfdCzMElpEyG4tFEmGtNmAPpo,11504
32
+ devpilot_agentic_cli-1.0.3.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
33
+ devpilot_agentic_cli-1.0.3.dist-info/entry_points.txt,sha256=Zi0zH4sqSQptehekvn5CBhU452GQ23Vyja4l8C58IuY,44
34
+ devpilot_agentic_cli-1.0.3.dist-info/top_level.txt,sha256=0gvCG7PHc22NA63j3bTGi2Zc37ym9t8Pf90ZLzf1kGA,6
35
+ devpilot_agentic_cli-1.0.3.dist-info/RECORD,,