code-puppy 0.0.130__py3-none-any.whl → 0.0.132__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.
- code_puppy/command_line/mcp_commands.py +591 -106
- code_puppy/mcp/blocking_startup.py +404 -0
- code_puppy/mcp/captured_stdio_server.py +282 -0
- code_puppy/mcp/config_wizard.py +151 -117
- code_puppy/mcp/managed_server.py +55 -1
- code_puppy/mcp/server_registry_catalog.py +346 -46
- code_puppy/mcp/system_tools.py +214 -0
- code_puppy/messaging/__init__.py +4 -0
- code_puppy/messaging/message_queue.py +86 -0
- code_puppy/messaging/renderers.py +94 -0
- code_puppy/tui/app.py +24 -1
- code_puppy/tui/components/chat_view.py +33 -18
- code_puppy/tui/components/human_input_modal.py +171 -0
- code_puppy/tui/screens/__init__.py +3 -1
- code_puppy/tui/screens/mcp_install_wizard.py +593 -0
- {code_puppy-0.0.130.dist-info → code_puppy-0.0.132.dist-info}/METADATA +1 -1
- {code_puppy-0.0.130.dist-info → code_puppy-0.0.132.dist-info}/RECORD +21 -16
- {code_puppy-0.0.130.data → code_puppy-0.0.132.data}/data/code_puppy/models.json +0 -0
- {code_puppy-0.0.130.dist-info → code_puppy-0.0.132.dist-info}/WHEEL +0 -0
- {code_puppy-0.0.130.dist-info → code_puppy-0.0.132.dist-info}/entry_points.txt +0 -0
- {code_puppy-0.0.130.dist-info → code_puppy-0.0.132.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,593 @@
|
|
|
1
|
+
"""
|
|
2
|
+
MCP Install Wizard Screen - TUI interface for installing MCP servers.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
from typing import Dict, List, Optional
|
|
8
|
+
|
|
9
|
+
from textual import on
|
|
10
|
+
from textual.app import ComposeResult
|
|
11
|
+
from textual.containers import Container, Horizontal, Vertical
|
|
12
|
+
from textual.screen import ModalScreen
|
|
13
|
+
from textual.widgets import (
|
|
14
|
+
Button,
|
|
15
|
+
Input,
|
|
16
|
+
Label,
|
|
17
|
+
ListItem,
|
|
18
|
+
ListView,
|
|
19
|
+
Static,
|
|
20
|
+
Select,
|
|
21
|
+
TextArea
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
from code_puppy.messaging import emit_info
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class MCPInstallWizardScreen(ModalScreen):
|
|
28
|
+
"""Modal screen for installing MCP servers with full wizard support."""
|
|
29
|
+
|
|
30
|
+
def __init__(self, **kwargs):
|
|
31
|
+
super().__init__(**kwargs)
|
|
32
|
+
self.selected_server = None
|
|
33
|
+
self.env_vars = {}
|
|
34
|
+
self.step = "search" # search -> configure -> install
|
|
35
|
+
self.search_counter = 0 # Counter to ensure unique IDs
|
|
36
|
+
|
|
37
|
+
DEFAULT_CSS = """
|
|
38
|
+
MCPInstallWizardScreen {
|
|
39
|
+
align: center middle;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
#wizard-container {
|
|
43
|
+
width: 90%;
|
|
44
|
+
max-width: 100;
|
|
45
|
+
height: 80%;
|
|
46
|
+
max-height: 40;
|
|
47
|
+
background: $surface;
|
|
48
|
+
border: solid $primary;
|
|
49
|
+
padding: 1 2;
|
|
50
|
+
layout: vertical;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
#wizard-header {
|
|
54
|
+
width: 100%;
|
|
55
|
+
height: 3;
|
|
56
|
+
text-align: center;
|
|
57
|
+
color: $accent;
|
|
58
|
+
margin-bottom: 1;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
#search-container {
|
|
62
|
+
width: 100%;
|
|
63
|
+
height: auto;
|
|
64
|
+
layout: vertical;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
#search-input {
|
|
68
|
+
width: 100%;
|
|
69
|
+
margin-bottom: 1;
|
|
70
|
+
border: solid $primary;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
#results-list {
|
|
74
|
+
width: 100%;
|
|
75
|
+
height: 20;
|
|
76
|
+
border: solid $primary;
|
|
77
|
+
margin-bottom: 1;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
#config-container {
|
|
81
|
+
width: 100%;
|
|
82
|
+
height: 1fr;
|
|
83
|
+
layout: vertical;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
#server-info {
|
|
87
|
+
width: 100%;
|
|
88
|
+
height: auto;
|
|
89
|
+
max-height: 8;
|
|
90
|
+
border: solid $success;
|
|
91
|
+
padding: 1;
|
|
92
|
+
margin-bottom: 1;
|
|
93
|
+
background: $surface-lighten-1;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
#env-vars-container {
|
|
97
|
+
width: 100%;
|
|
98
|
+
height: 1fr;
|
|
99
|
+
layout: vertical;
|
|
100
|
+
border: solid $warning;
|
|
101
|
+
padding: 1;
|
|
102
|
+
margin-bottom: 1;
|
|
103
|
+
overflow-y: scroll;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
#env-var-input {
|
|
107
|
+
width: 100%;
|
|
108
|
+
margin-bottom: 1;
|
|
109
|
+
border: solid $primary;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
#button-container {
|
|
113
|
+
width: 100%;
|
|
114
|
+
height: 4;
|
|
115
|
+
layout: horizontal;
|
|
116
|
+
align: center bottom;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
#back-button, #next-button, #install-button, #cancel-button {
|
|
120
|
+
width: auto;
|
|
121
|
+
height: 3;
|
|
122
|
+
margin: 0 1;
|
|
123
|
+
min-width: 12;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.env-var-row {
|
|
127
|
+
width: 100%;
|
|
128
|
+
layout: horizontal;
|
|
129
|
+
height: 3;
|
|
130
|
+
margin-bottom: 1;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
.env-var-label {
|
|
134
|
+
width: 1fr;
|
|
135
|
+
padding: 1 0;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
.env-var-input {
|
|
139
|
+
width: 2fr;
|
|
140
|
+
border: solid $primary;
|
|
141
|
+
}
|
|
142
|
+
"""
|
|
143
|
+
|
|
144
|
+
def compose(self) -> ComposeResult:
|
|
145
|
+
"""Create the wizard layout."""
|
|
146
|
+
with Container(id="wizard-container"):
|
|
147
|
+
yield Static("🔌 MCP Server Install Wizard", id="wizard-header")
|
|
148
|
+
|
|
149
|
+
# Step 1: Search and select server
|
|
150
|
+
with Container(id="search-container"):
|
|
151
|
+
yield Input(placeholder="Search MCP servers (e.g. 'github', 'postgres')...", id="search-input")
|
|
152
|
+
yield ListView(id="results-list")
|
|
153
|
+
|
|
154
|
+
# Step 2: Configure server (hidden initially)
|
|
155
|
+
with Container(id="config-container"):
|
|
156
|
+
yield Static("Server Configuration", id="config-header")
|
|
157
|
+
yield Container(id="server-info")
|
|
158
|
+
yield Container(id="env-vars-container")
|
|
159
|
+
|
|
160
|
+
# Navigation buttons
|
|
161
|
+
with Horizontal(id="button-container"):
|
|
162
|
+
yield Button("Cancel", id="cancel-button", variant="default")
|
|
163
|
+
yield Button("Back", id="back-button", variant="default")
|
|
164
|
+
yield Button("Next", id="next-button", variant="primary")
|
|
165
|
+
yield Button("Install", id="install-button", variant="success")
|
|
166
|
+
|
|
167
|
+
def on_mount(self) -> None:
|
|
168
|
+
"""Initialize the wizard."""
|
|
169
|
+
self._show_search_step()
|
|
170
|
+
self._load_popular_servers()
|
|
171
|
+
|
|
172
|
+
# Focus the search input
|
|
173
|
+
search_input = self.query_one("#search-input", Input)
|
|
174
|
+
search_input.focus()
|
|
175
|
+
|
|
176
|
+
def _show_search_step(self) -> None:
|
|
177
|
+
"""Show the search step."""
|
|
178
|
+
self.step = "search"
|
|
179
|
+
self.query_one("#search-container").display = True
|
|
180
|
+
self.query_one("#config-container").display = False
|
|
181
|
+
|
|
182
|
+
self.query_one("#back-button").display = False
|
|
183
|
+
self.query_one("#next-button").display = True
|
|
184
|
+
self.query_one("#install-button").display = False
|
|
185
|
+
|
|
186
|
+
def _show_config_step(self) -> None:
|
|
187
|
+
"""Show the configuration step."""
|
|
188
|
+
self.step = "configure"
|
|
189
|
+
self.query_one("#search-container").display = False
|
|
190
|
+
self.query_one("#config-container").display = True
|
|
191
|
+
|
|
192
|
+
self.query_one("#back-button").display = True
|
|
193
|
+
self.query_one("#next-button").display = False
|
|
194
|
+
self.query_one("#install-button").display = True
|
|
195
|
+
|
|
196
|
+
self._setup_server_config()
|
|
197
|
+
|
|
198
|
+
def _load_popular_servers(self) -> None:
|
|
199
|
+
"""Load popular servers into the list."""
|
|
200
|
+
self.search_counter += 1
|
|
201
|
+
counter = self.search_counter
|
|
202
|
+
|
|
203
|
+
try:
|
|
204
|
+
from code_puppy.mcp.server_registry_catalog import catalog
|
|
205
|
+
servers = catalog.get_popular(10)
|
|
206
|
+
|
|
207
|
+
results_list = self.query_one("#results-list", ListView)
|
|
208
|
+
# Force clear by removing all children
|
|
209
|
+
results_list.remove_children()
|
|
210
|
+
|
|
211
|
+
if servers:
|
|
212
|
+
for i, server in enumerate(servers):
|
|
213
|
+
indicators = []
|
|
214
|
+
if server.verified:
|
|
215
|
+
indicators.append("✓")
|
|
216
|
+
if server.popular:
|
|
217
|
+
indicators.append("⭐")
|
|
218
|
+
|
|
219
|
+
display_name = f"{server.display_name} {''.join(indicators)}"
|
|
220
|
+
description = server.description[:60] + "..." if len(server.description) > 60 else server.description
|
|
221
|
+
|
|
222
|
+
item_text = f"{display_name}\n[dim]{description}[/dim]"
|
|
223
|
+
# Use counter to ensure globally unique IDs
|
|
224
|
+
item = ListItem(Static(item_text), id=f"item-{counter}-{i}")
|
|
225
|
+
item.server_data = server
|
|
226
|
+
results_list.append(item)
|
|
227
|
+
else:
|
|
228
|
+
no_servers_item = ListItem(Static("No servers found"), id=f"no-results-{counter}")
|
|
229
|
+
results_list.append(no_servers_item)
|
|
230
|
+
|
|
231
|
+
except ImportError:
|
|
232
|
+
results_list = self.query_one("#results-list", ListView)
|
|
233
|
+
results_list.remove_children()
|
|
234
|
+
error_item = ListItem(Static("[red]Server registry not available[/red]"), id=f"error-{counter}")
|
|
235
|
+
results_list.append(error_item)
|
|
236
|
+
|
|
237
|
+
@on(Input.Changed, "#search-input")
|
|
238
|
+
def on_search_changed(self, event: Input.Changed) -> None:
|
|
239
|
+
"""Handle search input changes."""
|
|
240
|
+
query = event.value.strip()
|
|
241
|
+
|
|
242
|
+
if not query:
|
|
243
|
+
self._load_popular_servers()
|
|
244
|
+
return
|
|
245
|
+
|
|
246
|
+
self.search_counter += 1
|
|
247
|
+
counter = self.search_counter
|
|
248
|
+
|
|
249
|
+
try:
|
|
250
|
+
from code_puppy.mcp.server_registry_catalog import catalog
|
|
251
|
+
servers = catalog.search(query)
|
|
252
|
+
|
|
253
|
+
results_list = self.query_one("#results-list", ListView)
|
|
254
|
+
# Force clear by removing all children
|
|
255
|
+
results_list.remove_children()
|
|
256
|
+
|
|
257
|
+
if servers:
|
|
258
|
+
for i, server in enumerate(servers[:15]): # Limit results
|
|
259
|
+
indicators = []
|
|
260
|
+
if server.verified:
|
|
261
|
+
indicators.append("✓")
|
|
262
|
+
if server.popular:
|
|
263
|
+
indicators.append("⭐")
|
|
264
|
+
|
|
265
|
+
display_name = f"{server.display_name} {''.join(indicators)}"
|
|
266
|
+
description = server.description[:60] + "..." if len(server.description) > 60 else server.description
|
|
267
|
+
|
|
268
|
+
item_text = f"{display_name}\n[dim]{description}[/dim]"
|
|
269
|
+
# Use counter to ensure globally unique IDs
|
|
270
|
+
item = ListItem(Static(item_text), id=f"item-{counter}-{i}")
|
|
271
|
+
item.server_data = server
|
|
272
|
+
results_list.append(item)
|
|
273
|
+
else:
|
|
274
|
+
no_results_item = ListItem(Static(f"No servers found for '{query}'"), id=f"no-results-{counter}")
|
|
275
|
+
results_list.append(no_results_item)
|
|
276
|
+
|
|
277
|
+
except ImportError:
|
|
278
|
+
results_list = self.query_one("#results-list", ListView)
|
|
279
|
+
results_list.remove_children()
|
|
280
|
+
error_item = ListItem(Static("[red]Server registry not available[/red]"), id=f"error-{counter}")
|
|
281
|
+
results_list.append(error_item)
|
|
282
|
+
|
|
283
|
+
@on(ListView.Selected, "#results-list")
|
|
284
|
+
def on_server_selected(self, event: ListView.Selected) -> None:
|
|
285
|
+
"""Handle server selection."""
|
|
286
|
+
if hasattr(event.item, 'server_data'):
|
|
287
|
+
self.selected_server = event.item.server_data
|
|
288
|
+
|
|
289
|
+
@on(Button.Pressed, "#next-button")
|
|
290
|
+
def on_next_clicked(self) -> None:
|
|
291
|
+
"""Handle next button click."""
|
|
292
|
+
if self.step == "search":
|
|
293
|
+
if self.selected_server:
|
|
294
|
+
self._show_config_step()
|
|
295
|
+
else:
|
|
296
|
+
# Show error - no server selected
|
|
297
|
+
pass
|
|
298
|
+
|
|
299
|
+
@on(Button.Pressed, "#back-button")
|
|
300
|
+
def on_back_clicked(self) -> None:
|
|
301
|
+
"""Handle back button click."""
|
|
302
|
+
if self.step == "configure":
|
|
303
|
+
self._show_search_step()
|
|
304
|
+
|
|
305
|
+
@on(Button.Pressed, "#install-button")
|
|
306
|
+
def on_install_clicked(self) -> None:
|
|
307
|
+
"""Handle install button click."""
|
|
308
|
+
if self.step == "configure" and self.selected_server:
|
|
309
|
+
self._install_server()
|
|
310
|
+
|
|
311
|
+
@on(Button.Pressed, "#cancel-button")
|
|
312
|
+
def on_cancel_clicked(self) -> None:
|
|
313
|
+
"""Handle cancel button click."""
|
|
314
|
+
self.dismiss({"success": False, "message": "Installation cancelled"})
|
|
315
|
+
|
|
316
|
+
def _setup_server_config(self) -> None:
|
|
317
|
+
"""Setup the server configuration step."""
|
|
318
|
+
if not self.selected_server:
|
|
319
|
+
return
|
|
320
|
+
|
|
321
|
+
# Show server info
|
|
322
|
+
server_info = self.query_one("#server-info", Container)
|
|
323
|
+
server_info.remove_children()
|
|
324
|
+
|
|
325
|
+
info_text = f"""[bold]{self.selected_server.display_name}[/bold]
|
|
326
|
+
{self.selected_server.description}
|
|
327
|
+
|
|
328
|
+
[yellow]Category:[/yellow] {self.selected_server.category}
|
|
329
|
+
[yellow]Type:[/yellow] {getattr(self.selected_server, 'type', 'stdio')}"""
|
|
330
|
+
|
|
331
|
+
# Show requirements summary
|
|
332
|
+
requirements = self.selected_server.get_requirements()
|
|
333
|
+
req_items = []
|
|
334
|
+
if requirements.required_tools:
|
|
335
|
+
req_items.append(f"Tools: {', '.join(requirements.required_tools)}")
|
|
336
|
+
if requirements.environment_vars:
|
|
337
|
+
req_items.append(f"Env vars: {len(requirements.environment_vars)}")
|
|
338
|
+
if requirements.command_line_args:
|
|
339
|
+
req_items.append(f"Config args: {len(requirements.command_line_args)}")
|
|
340
|
+
|
|
341
|
+
if req_items:
|
|
342
|
+
info_text += f"\n[yellow]Requirements:[/yellow] {' | '.join(req_items)}"
|
|
343
|
+
|
|
344
|
+
server_info.mount(Static(info_text))
|
|
345
|
+
|
|
346
|
+
# Setup configuration requirements
|
|
347
|
+
config_container = self.query_one("#env-vars-container", Container)
|
|
348
|
+
config_container.remove_children()
|
|
349
|
+
config_container.mount(Static("[bold]Server Configuration:[/bold]"))
|
|
350
|
+
|
|
351
|
+
# Add server name input
|
|
352
|
+
config_container.mount(Static("\n[bold blue]Server Name:[/bold blue]"))
|
|
353
|
+
name_row = Horizontal(classes="env-var-row")
|
|
354
|
+
config_container.mount(name_row)
|
|
355
|
+
name_row.mount(Static("🏷️ Custom name:", classes="env-var-label"))
|
|
356
|
+
name_input = Input(
|
|
357
|
+
placeholder=f"Default: {self.selected_server.name}",
|
|
358
|
+
value=self.selected_server.name,
|
|
359
|
+
classes="env-var-input",
|
|
360
|
+
id="server-name-input"
|
|
361
|
+
)
|
|
362
|
+
name_row.mount(name_input)
|
|
363
|
+
|
|
364
|
+
try:
|
|
365
|
+
# Check system requirements first
|
|
366
|
+
self._setup_system_requirements(config_container)
|
|
367
|
+
|
|
368
|
+
# Setup environment variables
|
|
369
|
+
self._setup_environment_variables(config_container)
|
|
370
|
+
|
|
371
|
+
# Setup command line arguments
|
|
372
|
+
self._setup_command_line_args(config_container)
|
|
373
|
+
|
|
374
|
+
# Show package dependencies info
|
|
375
|
+
self._setup_package_dependencies(config_container)
|
|
376
|
+
|
|
377
|
+
except Exception as e:
|
|
378
|
+
config_container.mount(Static(f"[red]Error loading configuration: {e}[/red]"))
|
|
379
|
+
|
|
380
|
+
def _setup_system_requirements(self, parent: Container) -> None:
|
|
381
|
+
"""Setup system requirements validation."""
|
|
382
|
+
required_tools = self.selected_server.get_required_tools()
|
|
383
|
+
|
|
384
|
+
if not required_tools:
|
|
385
|
+
return
|
|
386
|
+
|
|
387
|
+
parent.mount(Static("\n[bold cyan]System Tools:[/bold cyan]"))
|
|
388
|
+
|
|
389
|
+
# Import here to avoid circular imports
|
|
390
|
+
from code_puppy.mcp.system_tools import detector
|
|
391
|
+
|
|
392
|
+
tool_status = detector.detect_tools(required_tools)
|
|
393
|
+
|
|
394
|
+
for tool_name, tool_info in tool_status.items():
|
|
395
|
+
if tool_info.available:
|
|
396
|
+
status_text = f"✅ {tool_name}"
|
|
397
|
+
if tool_info.version:
|
|
398
|
+
status_text += f" ({tool_info.version})"
|
|
399
|
+
parent.mount(Static(status_text))
|
|
400
|
+
else:
|
|
401
|
+
status_text = f"❌ {tool_name} - {tool_info.error or 'Not found'}"
|
|
402
|
+
parent.mount(Static(f"[red]{status_text}[/red]"))
|
|
403
|
+
|
|
404
|
+
# Show installation suggestions
|
|
405
|
+
suggestions = detector.get_installation_suggestions(tool_name)
|
|
406
|
+
if suggestions:
|
|
407
|
+
parent.mount(Static(f"[dim] Install: {suggestions[0]}[/dim]"))
|
|
408
|
+
|
|
409
|
+
def _setup_environment_variables(self, parent: Container) -> None:
|
|
410
|
+
"""Setup environment variables inputs."""
|
|
411
|
+
env_vars = self.selected_server.get_environment_vars()
|
|
412
|
+
|
|
413
|
+
if not env_vars:
|
|
414
|
+
return
|
|
415
|
+
|
|
416
|
+
parent.mount(Static("\n[bold yellow]Environment Variables:[/bold yellow]"))
|
|
417
|
+
|
|
418
|
+
for var in env_vars:
|
|
419
|
+
# Check if already set
|
|
420
|
+
import os
|
|
421
|
+
current_value = os.environ.get(var, "")
|
|
422
|
+
|
|
423
|
+
row_container = Horizontal(classes="env-var-row")
|
|
424
|
+
parent.mount(row_container)
|
|
425
|
+
|
|
426
|
+
status_indicator = "✅" if current_value else "📝"
|
|
427
|
+
row_container.mount(Static(f"{status_indicator} {var}:", classes="env-var-label"))
|
|
428
|
+
|
|
429
|
+
env_input = Input(
|
|
430
|
+
placeholder=f"Enter {var} value..." if not current_value else "Already set",
|
|
431
|
+
value=current_value,
|
|
432
|
+
classes="env-var-input",
|
|
433
|
+
id=f"env-{var}"
|
|
434
|
+
)
|
|
435
|
+
row_container.mount(env_input)
|
|
436
|
+
|
|
437
|
+
def _setup_command_line_args(self, parent: Container) -> None:
|
|
438
|
+
"""Setup command line arguments inputs."""
|
|
439
|
+
cmd_args = self.selected_server.get_command_line_args()
|
|
440
|
+
|
|
441
|
+
if not cmd_args:
|
|
442
|
+
return
|
|
443
|
+
|
|
444
|
+
parent.mount(Static("\n[bold green]Command Line Arguments:[/bold green]"))
|
|
445
|
+
|
|
446
|
+
for arg_config in cmd_args:
|
|
447
|
+
name = arg_config.get("name", "")
|
|
448
|
+
prompt = arg_config.get("prompt", name)
|
|
449
|
+
default = arg_config.get("default", "")
|
|
450
|
+
required = arg_config.get("required", True)
|
|
451
|
+
|
|
452
|
+
row_container = Horizontal(classes="env-var-row")
|
|
453
|
+
parent.mount(row_container)
|
|
454
|
+
|
|
455
|
+
indicator = "⚡" if required else "🔧"
|
|
456
|
+
label_text = f"{indicator} {prompt}:"
|
|
457
|
+
if not required:
|
|
458
|
+
label_text += " (optional)"
|
|
459
|
+
|
|
460
|
+
row_container.mount(Static(label_text, classes="env-var-label"))
|
|
461
|
+
|
|
462
|
+
arg_input = Input(
|
|
463
|
+
placeholder=f"Default: {default}" if default else f"Enter {name}...",
|
|
464
|
+
value=default,
|
|
465
|
+
classes="env-var-input",
|
|
466
|
+
id=f"arg-{name}"
|
|
467
|
+
)
|
|
468
|
+
row_container.mount(arg_input)
|
|
469
|
+
|
|
470
|
+
def _setup_package_dependencies(self, parent: Container) -> None:
|
|
471
|
+
"""Setup package dependencies information."""
|
|
472
|
+
packages = self.selected_server.get_package_dependencies()
|
|
473
|
+
|
|
474
|
+
if not packages:
|
|
475
|
+
return
|
|
476
|
+
|
|
477
|
+
parent.mount(Static("\n[bold magenta]Package Dependencies:[/bold magenta]"))
|
|
478
|
+
|
|
479
|
+
# Import here to avoid circular imports
|
|
480
|
+
from code_puppy.mcp.system_tools import detector
|
|
481
|
+
|
|
482
|
+
package_status = detector.check_package_dependencies(packages)
|
|
483
|
+
|
|
484
|
+
for package, available in package_status.items():
|
|
485
|
+
if available:
|
|
486
|
+
parent.mount(Static(f"✅ {package} (installed)"))
|
|
487
|
+
else:
|
|
488
|
+
parent.mount(Static(f"[yellow]📦 {package} (will be installed automatically)[/yellow]"))
|
|
489
|
+
|
|
490
|
+
def _install_server(self) -> None:
|
|
491
|
+
"""Install the selected server with configuration."""
|
|
492
|
+
if not self.selected_server:
|
|
493
|
+
return
|
|
494
|
+
|
|
495
|
+
try:
|
|
496
|
+
# Collect configuration inputs
|
|
497
|
+
env_vars = {}
|
|
498
|
+
cmd_args = {}
|
|
499
|
+
server_name = self.selected_server.name # Default fallback
|
|
500
|
+
|
|
501
|
+
all_inputs = self.query(Input)
|
|
502
|
+
|
|
503
|
+
for input_widget in all_inputs:
|
|
504
|
+
if input_widget.id == "server-name-input":
|
|
505
|
+
custom_name = input_widget.value.strip()
|
|
506
|
+
if custom_name:
|
|
507
|
+
server_name = custom_name
|
|
508
|
+
elif input_widget.id and input_widget.id.startswith("env-"):
|
|
509
|
+
var_name = input_widget.id[4:] # Remove "env-" prefix
|
|
510
|
+
value = input_widget.value.strip()
|
|
511
|
+
if value:
|
|
512
|
+
env_vars[var_name] = value
|
|
513
|
+
elif input_widget.id and input_widget.id.startswith("arg-"):
|
|
514
|
+
arg_name = input_widget.id[4:] # Remove "arg-" prefix
|
|
515
|
+
value = input_widget.value.strip()
|
|
516
|
+
if value:
|
|
517
|
+
cmd_args[arg_name] = value
|
|
518
|
+
|
|
519
|
+
# Set environment variables in the current environment
|
|
520
|
+
for var, value in env_vars.items():
|
|
521
|
+
os.environ[var] = value
|
|
522
|
+
|
|
523
|
+
# Get server config with command line argument overrides
|
|
524
|
+
config_dict = self.selected_server.to_server_config(server_name, **cmd_args)
|
|
525
|
+
|
|
526
|
+
# Update the config with actual environment variable values
|
|
527
|
+
if 'env' in config_dict:
|
|
528
|
+
for env_key, env_value in config_dict['env'].items():
|
|
529
|
+
# If it's a placeholder like $GITHUB_TOKEN, replace with actual value
|
|
530
|
+
if env_value.startswith('$'):
|
|
531
|
+
var_name = env_value[1:] # Remove the $
|
|
532
|
+
if var_name in env_vars:
|
|
533
|
+
config_dict['env'][env_key] = env_vars[var_name]
|
|
534
|
+
|
|
535
|
+
# Create and register the server
|
|
536
|
+
from code_puppy.mcp import ServerConfig
|
|
537
|
+
from code_puppy.mcp.manager import get_mcp_manager
|
|
538
|
+
|
|
539
|
+
server_config = ServerConfig(
|
|
540
|
+
id=server_name,
|
|
541
|
+
name=server_name,
|
|
542
|
+
type=config_dict.pop('type'),
|
|
543
|
+
enabled=True,
|
|
544
|
+
config=config_dict
|
|
545
|
+
)
|
|
546
|
+
|
|
547
|
+
manager = get_mcp_manager()
|
|
548
|
+
server_id = manager.register_server(server_config)
|
|
549
|
+
|
|
550
|
+
if server_id:
|
|
551
|
+
# Save to mcp_servers.json
|
|
552
|
+
from code_puppy.config import MCP_SERVERS_FILE
|
|
553
|
+
|
|
554
|
+
if os.path.exists(MCP_SERVERS_FILE):
|
|
555
|
+
with open(MCP_SERVERS_FILE, 'r') as f:
|
|
556
|
+
data = json.load(f)
|
|
557
|
+
servers = data.get("mcp_servers", {})
|
|
558
|
+
else:
|
|
559
|
+
servers = {}
|
|
560
|
+
data = {"mcp_servers": servers}
|
|
561
|
+
|
|
562
|
+
servers[server_name] = config_dict
|
|
563
|
+
servers[server_name]['type'] = server_config.type
|
|
564
|
+
|
|
565
|
+
os.makedirs(os.path.dirname(MCP_SERVERS_FILE), exist_ok=True)
|
|
566
|
+
with open(MCP_SERVERS_FILE, 'w') as f:
|
|
567
|
+
json.dump(data, f, indent=2)
|
|
568
|
+
|
|
569
|
+
# Reload MCP servers
|
|
570
|
+
from code_puppy.agent import reload_mcp_servers
|
|
571
|
+
reload_mcp_servers()
|
|
572
|
+
|
|
573
|
+
self.dismiss({
|
|
574
|
+
"success": True,
|
|
575
|
+
"message": f"Successfully installed '{server_name}' from {self.selected_server.display_name}",
|
|
576
|
+
"server_name": server_name
|
|
577
|
+
})
|
|
578
|
+
else:
|
|
579
|
+
self.dismiss({
|
|
580
|
+
"success": False,
|
|
581
|
+
"message": "Failed to register server"
|
|
582
|
+
})
|
|
583
|
+
|
|
584
|
+
except Exception as e:
|
|
585
|
+
self.dismiss({
|
|
586
|
+
"success": False,
|
|
587
|
+
"message": f"Installation failed: {str(e)}"
|
|
588
|
+
})
|
|
589
|
+
|
|
590
|
+
def on_key(self, event) -> None:
|
|
591
|
+
"""Handle key events."""
|
|
592
|
+
if event.key == "escape":
|
|
593
|
+
self.on_cancel_clicked()
|
|
@@ -25,7 +25,7 @@ code_puppy/command_line/__init__.py,sha256=y7WeRemfYppk8KVbCGeAIiTuiOszIURCDjOMZ
|
|
|
25
25
|
code_puppy/command_line/command_handler.py,sha256=IhDaDa0GmpW0BCFfmkgpL_6iQBJlXEKQC9SDmpj_XeI,20730
|
|
26
26
|
code_puppy/command_line/file_path_completion.py,sha256=gw8NpIxa6GOpczUJRyh7VNZwoXKKn-yvCqit7h2y6Gg,2931
|
|
27
27
|
code_puppy/command_line/load_context_completion.py,sha256=6eZxV6Bs-EFwZjN93V8ZDZUC-6RaWxvtZk-04Wtikyw,2240
|
|
28
|
-
code_puppy/command_line/mcp_commands.py,sha256=
|
|
28
|
+
code_puppy/command_line/mcp_commands.py,sha256=X0Djmcr1ZAVZyr9DkQWb9uMrQDa4lBU28b4m_qteBvI,78406
|
|
29
29
|
code_puppy/command_line/meta_command_handler.py,sha256=02NU4Lspf5qRMPTsrGiMRLSUshZhdmS0XQA26k8vUjw,5665
|
|
30
30
|
code_puppy/command_line/model_picker_completion.py,sha256=xvwgthVmLRA9a8RJG6iFImxR2yD6rJYPJJav0YJoVCc,3599
|
|
31
31
|
code_puppy/command_line/motd.py,sha256=PEdkp3ZnydVfvd7mNJylm8YyFNUKg9jmY6uwkA1em8c,2152
|
|
@@ -33,22 +33,25 @@ code_puppy/command_line/prompt_toolkit_completion.py,sha256=BKNw-DwacZPNTKjjXlxn
|
|
|
33
33
|
code_puppy/command_line/utils.py,sha256=7eyxDHjPjPB9wGDJQQcXV_zOsGdYsFgI0SGCetVmTqE,1251
|
|
34
34
|
code_puppy/mcp/__init__.py,sha256=LJd9mGStskhXYBEp1UhtHlrAQ3rCHnfTa7KSmqtZe34,1143
|
|
35
35
|
code_puppy/mcp/async_lifecycle.py,sha256=45tw7ZcDV6LVBrTvvNkMCDhnTapgQCYcc01W8Gp9c5A,8064
|
|
36
|
+
code_puppy/mcp/blocking_startup.py,sha256=R5XjDeIORd_jY5G0OF7GPFzSiDhjFDxMRcJJ-MCTFO8,14140
|
|
37
|
+
code_puppy/mcp/captured_stdio_server.py,sha256=DHeLYCbOWwM_a47xEsacS8yz0eCQhGsUTK9JIpHhX78,9504
|
|
36
38
|
code_puppy/mcp/circuit_breaker.py,sha256=TF0ukl_d4EithPQbiq1yDfFPkCZ7bRBe0jiEx_tnMR4,8630
|
|
37
|
-
code_puppy/mcp/config_wizard.py,sha256=
|
|
39
|
+
code_puppy/mcp/config_wizard.py,sha256=d0o29ZcnGLdpV778nwTUAPH7B8lHAC2XggU-ql37Fzo,16777
|
|
38
40
|
code_puppy/mcp/dashboard.py,sha256=fHBdAVidQUWuhjMjF7hIy0SjrQlrz31zSqzqJv8H2eI,9396
|
|
39
41
|
code_puppy/mcp/error_isolation.py,sha256=xw8DGTItT8-RY5TVYzORtrtfdI1s1ZfUUT88Pr7h_pI,12270
|
|
40
42
|
code_puppy/mcp/health_monitor.py,sha256=oNgPyEwzkF61gNc7nk-FJU_yO2MERjASn4g5shcabic,20428
|
|
41
|
-
code_puppy/mcp/managed_server.py,sha256=
|
|
43
|
+
code_puppy/mcp/managed_server.py,sha256=z0uA9JFtThVxno0TZpa5nlsvRVe2tNnZrMJYymj600E,14932
|
|
42
44
|
code_puppy/mcp/manager.py,sha256=ZfYoz3nmQqrkSnDzgr9s_QvGKFCthA1bvt3gQm0W6VI,27280
|
|
43
45
|
code_puppy/mcp/registry.py,sha256=YJ-VPFjk1ZFrSftbu9bKVIWvGYX4zIk2TnrM_h_us1M,15841
|
|
44
46
|
code_puppy/mcp/retry_manager.py,sha256=B4q1MyzZQ9RZRW3FKhyqhq-ebSG8Do01j4A70zHTxgA,10988
|
|
45
|
-
code_puppy/mcp/server_registry_catalog.py,sha256=
|
|
47
|
+
code_puppy/mcp/server_registry_catalog.py,sha256=7ZoGlt5s3F3mpZUvkRdzBHNt4Ka0rL1FD7153sZ0QpA,37779
|
|
46
48
|
code_puppy/mcp/status_tracker.py,sha256=JJcAjE8mr30stEfmAhtPmTcLp1zBt5x3aynm_O4kyvY,12748
|
|
49
|
+
code_puppy/mcp/system_tools.py,sha256=djc0nLSZP9swfdxI5ZNCI3PZ1VL6DbmHad2MwfZU-r8,7610
|
|
47
50
|
code_puppy/mcp/examples/retry_example.py,sha256=L9wB1H4JTzmEx1FkSR2x4jXCyfR8AoUjSngjbRXhyCQ,7201
|
|
48
|
-
code_puppy/messaging/__init__.py,sha256=
|
|
49
|
-
code_puppy/messaging/message_queue.py,sha256=
|
|
51
|
+
code_puppy/messaging/__init__.py,sha256=h2eZ7nJblKF71_dNUIBj3vL5RDw7WGy8nh6T_EYVrcA,1176
|
|
52
|
+
code_puppy/messaging/message_queue.py,sha256=sdn_c8eM93BvaFq9JEaCc_wgCA4Yw6oU7xgPjCpGgTc,12626
|
|
50
53
|
code_puppy/messaging/queue_console.py,sha256=L4QFUsR_emAFsIRan3-rnL-F1LODF4DG6je-IlUTxNc,10864
|
|
51
|
-
code_puppy/messaging/renderers.py,sha256
|
|
54
|
+
code_puppy/messaging/renderers.py,sha256=DYwM2cdqItFoHsCxBPtY8zCORwHNFgzrNhpixrv4Lt4,16028
|
|
52
55
|
code_puppy/messaging/spinner/__init__.py,sha256=9mkXPYojafydBOAMh9ZUrB4X6uH5Iqz_-E-Obpd72ko,1365
|
|
53
56
|
code_puppy/messaging/spinner/console_spinner.py,sha256=cuOXQH99dJ1cq0l_rpCLVCGNsH-iVcUWtE6fC3kjZCg,6931
|
|
54
57
|
code_puppy/messaging/spinner/spinner_base.py,sha256=474qMrTYpNfWcprFzmhaOJEOC-2rRHpTFCLsnl54bXA,1689
|
|
@@ -62,13 +65,14 @@ code_puppy/tools/file_operations.py,sha256=WKGNSGTw3vGdDqGGUBHIPh1uCjaDLJmgIa8Ua
|
|
|
62
65
|
code_puppy/tools/token_check.py,sha256=cNrGOOKahXsnWsvh5xnMkL1NS9FjYur9QIRZGQFW-pE,1189
|
|
63
66
|
code_puppy/tools/tools_content.py,sha256=pi9ig2qahZFkUj7gBBN2TX2QldvwnqmTHrRKP8my_2k,2209
|
|
64
67
|
code_puppy/tui/__init__.py,sha256=XesAxIn32zLPOmvpR2wIDxDAnnJr81a5pBJB4cZp1Xs,321
|
|
65
|
-
code_puppy/tui/app.py,sha256=
|
|
68
|
+
code_puppy/tui/app.py,sha256=zeth1Kbj9k-3ltnBV_QZlDBmu3jwTxOJEwyvB6j8AcY,37340
|
|
66
69
|
code_puppy/tui/messages.py,sha256=zQoToWI0eWdT36NEsY6RdCFzcDfAmfvoPlHv8jiCbgo,720
|
|
67
70
|
code_puppy/tui/components/__init__.py,sha256=uj5pnk3s6SEN3SbFI0ZnzaA2KK1NNg8TfUj6U-Z732U,455
|
|
68
|
-
code_puppy/tui/components/chat_view.py,sha256=
|
|
71
|
+
code_puppy/tui/components/chat_view.py,sha256=5y4LA-93SnPKVZmfVhYG43fiwkxVHkKATuiC44d4Wew,18572
|
|
69
72
|
code_puppy/tui/components/command_history_modal.py,sha256=pUPEQvoCWa2iUnuMgNwO22y8eUbyw0HpcPH3wAosHvU,7097
|
|
70
73
|
code_puppy/tui/components/copy_button.py,sha256=E4-OJYk5YNzDf-E81NyiVGKsTRPrUX-RnQ8qFuVnabw,4375
|
|
71
74
|
code_puppy/tui/components/custom_widgets.py,sha256=pnjkB3ZNa5lwSrAXUFlhN9AHNh4uMTpSap8AdbpecKw,1986
|
|
75
|
+
code_puppy/tui/components/human_input_modal.py,sha256=y4M1PrZtU1PY9XBT_cUqFbL4noc2pYiwIacYZhPFG_M,5454
|
|
72
76
|
code_puppy/tui/components/input_area.py,sha256=R4R32eXPZ2R8KFisIbldNGq60KMk7kCxWrdbeTgJUr8,4395
|
|
73
77
|
code_puppy/tui/components/sidebar.py,sha256=nGtCiYzZalPmiFaJ4dwj2S4EJBu5wQZVzhoigYYY7U4,10369
|
|
74
78
|
code_puppy/tui/components/status_bar.py,sha256=GgznJqF8Wk6XkurBuKohLyu75eT_ucBTvl9oPcySmnM,6338
|
|
@@ -76,8 +80,9 @@ code_puppy/tui/models/__init__.py,sha256=5Eq7BMibz-z_t_v7B4H4tCdKRG41i2CaCuNQf_l
|
|
|
76
80
|
code_puppy/tui/models/chat_message.py,sha256=2fSqsl4EHKgGsi_cVKWBbFq1NQwZyledGuJ9djovtLY,477
|
|
77
81
|
code_puppy/tui/models/command_history.py,sha256=bPWr_xnyQvjG5tPg_5pwqlEzn2fR170HlvBJwAXRpAE,2895
|
|
78
82
|
code_puppy/tui/models/enums.py,sha256=1ulsei95Gxy4r1sk-m-Sm5rdmejYCGRI-YtUwJmKFfM,501
|
|
79
|
-
code_puppy/tui/screens/__init__.py,sha256=
|
|
83
|
+
code_puppy/tui/screens/__init__.py,sha256=Sa_R_caykfsa7D55Zuc9VYpFfmQZAYxBFxfn_7Qe41M,287
|
|
80
84
|
code_puppy/tui/screens/help.py,sha256=eJuPaOOCp7ZSUlecearqsuX6caxWv7NQszUh0tZJjBM,3232
|
|
85
|
+
code_puppy/tui/screens/mcp_install_wizard.py,sha256=bw16ygvulKYaz8TkYXl0C6wGsMoay4oIIHyVWXnCA2I,21641
|
|
81
86
|
code_puppy/tui/screens/settings.py,sha256=GMpv-qa08rorAE9mj3AjmqjZFPhmeJ_GWd-DBHG6iAA,10671
|
|
82
87
|
code_puppy/tui/screens/tools.py,sha256=3pr2Xkpa9Js6Yhf1A3_wQVRzFOui-KDB82LwrsdBtyk,1715
|
|
83
88
|
code_puppy/tui/tests/__init__.py,sha256=Fzb4un4eeKfaKsIa5tqI5pTuwfpS8qD7Z6W7KeqWe84,23
|
|
@@ -100,9 +105,9 @@ code_puppy/tui/tests/test_sidebar_history_navigation.py,sha256=JGiyua8A2B8dLfwiE
|
|
|
100
105
|
code_puppy/tui/tests/test_status_bar.py,sha256=nYT_FZGdmqnnbn6o0ZuOkLtNUtJzLSmtX8P72liQ5Vo,1797
|
|
101
106
|
code_puppy/tui/tests/test_timestamped_history.py,sha256=nVXt9hExZZ_8MFP-AZj4L4bB_1Eo_mc-ZhVICzTuw3I,1799
|
|
102
107
|
code_puppy/tui/tests/test_tools.py,sha256=kgzzAkK4r0DPzQwHHD4cePpVNgrHor6cFr05Pg6DBWg,2687
|
|
103
|
-
code_puppy-0.0.
|
|
104
|
-
code_puppy-0.0.
|
|
105
|
-
code_puppy-0.0.
|
|
106
|
-
code_puppy-0.0.
|
|
107
|
-
code_puppy-0.0.
|
|
108
|
-
code_puppy-0.0.
|
|
108
|
+
code_puppy-0.0.132.data/data/code_puppy/models.json,sha256=GpvtWnBKERm6T7HCZJQUIVAS5256-tZ_bFuRtnKXEsY,3128
|
|
109
|
+
code_puppy-0.0.132.dist-info/METADATA,sha256=0uc8Pfj8Q1TUbxKCS3woA3ezguSmOrMN_if4tEFQLIA,19873
|
|
110
|
+
code_puppy-0.0.132.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
111
|
+
code_puppy-0.0.132.dist-info/entry_points.txt,sha256=d8YkBvIUxF-dHNJAj-x4fPEqizbY5d_TwvYpc01U5kw,58
|
|
112
|
+
code_puppy-0.0.132.dist-info/licenses/LICENSE,sha256=31u8x0SPgdOq3izJX41kgFazWsM43zPEF9eskzqbJMY,1075
|
|
113
|
+
code_puppy-0.0.132.dist-info/RECORD,,
|