code-puppy 0.0.343__py3-none-any.whl → 0.0.345__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/agents/base_agent.py +37 -129
- code_puppy/cli_runner.py +0 -35
- code_puppy/command_line/add_model_menu.py +8 -9
- code_puppy/command_line/config_commands.py +0 -10
- code_puppy/command_line/mcp/catalog_server_installer.py +5 -6
- code_puppy/command_line/mcp/custom_server_form.py +54 -19
- code_puppy/command_line/mcp/custom_server_installer.py +8 -9
- code_puppy/command_line/mcp/handler.py +0 -2
- code_puppy/command_line/mcp/help_command.py +1 -5
- code_puppy/command_line/mcp/start_command.py +36 -18
- code_puppy/command_line/onboarding_slides.py +0 -1
- code_puppy/command_line/utils.py +54 -0
- code_puppy/config.py +0 -23
- code_puppy/mcp_/async_lifecycle.py +35 -4
- code_puppy/mcp_/managed_server.py +49 -20
- code_puppy/mcp_/manager.py +81 -52
- code_puppy/messaging/message_queue.py +11 -23
- code_puppy/summarization_agent.py +1 -11
- code_puppy/tools/agent_tools.py +11 -55
- code_puppy/tools/browser/vqa_agent.py +1 -7
- {code_puppy-0.0.343.dist-info → code_puppy-0.0.345.dist-info}/METADATA +1 -23
- {code_puppy-0.0.343.dist-info → code_puppy-0.0.345.dist-info}/RECORD +27 -28
- code_puppy/command_line/mcp/add_command.py +0 -170
- {code_puppy-0.0.343.data → code_puppy-0.0.345.data}/data/code_puppy/models.json +0 -0
- {code_puppy-0.0.343.data → code_puppy-0.0.345.data}/data/code_puppy/models_dev_api.json +0 -0
- {code_puppy-0.0.343.dist-info → code_puppy-0.0.345.dist-info}/WHEEL +0 -0
- {code_puppy-0.0.343.dist-info → code_puppy-0.0.345.dist-info}/entry_points.txt +0 -0
- {code_puppy-0.0.343.dist-info → code_puppy-0.0.345.dist-info}/licenses/LICENSE +0 -0
code_puppy/agents/base_agent.py
CHANGED
|
@@ -24,7 +24,6 @@ from typing import (
|
|
|
24
24
|
import mcp
|
|
25
25
|
import pydantic
|
|
26
26
|
import pydantic_ai.models
|
|
27
|
-
from dbos import DBOS, SetWorkflowID
|
|
28
27
|
from pydantic_ai import Agent as PydanticAgent
|
|
29
28
|
from pydantic_ai import (
|
|
30
29
|
BinaryContent,
|
|
@@ -35,7 +34,6 @@ from pydantic_ai import (
|
|
|
35
34
|
UsageLimitExceeded,
|
|
36
35
|
UsageLimits,
|
|
37
36
|
)
|
|
38
|
-
from pydantic_ai.durable_exec.dbos import DBOSAgent
|
|
39
37
|
from pydantic_ai.messages import (
|
|
40
38
|
ModelMessage,
|
|
41
39
|
ModelRequest,
|
|
@@ -56,7 +54,6 @@ from code_puppy.config import (
|
|
|
56
54
|
get_global_model_name,
|
|
57
55
|
get_message_limit,
|
|
58
56
|
get_protected_token_count,
|
|
59
|
-
get_use_dbos,
|
|
60
57
|
get_value,
|
|
61
58
|
)
|
|
62
59
|
from code_puppy.error_logging import log_error
|
|
@@ -1212,56 +1209,25 @@ class BaseAgent(ABC):
|
|
|
1212
1209
|
|
|
1213
1210
|
self._last_model_name = resolved_model_name
|
|
1214
1211
|
# expose for run_with_mcp
|
|
1215
|
-
# Wrap it with DBOS, but handle MCP servers separately to avoid serialization issues
|
|
1216
1212
|
global _reload_count
|
|
1217
1213
|
_reload_count += 1
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
# Register regular tools (non-MCP) on the new agent
|
|
1233
|
-
agent_tools = self.get_available_tools()
|
|
1234
|
-
register_tools_for_agent(agent_without_mcp, agent_tools)
|
|
1235
|
-
|
|
1236
|
-
# Wrap with DBOS
|
|
1237
|
-
dbos_agent = DBOSAgent(
|
|
1238
|
-
agent_without_mcp, name=f"{self.name}-{_reload_count}"
|
|
1239
|
-
)
|
|
1240
|
-
self.pydantic_agent = dbos_agent
|
|
1241
|
-
self._code_generation_agent = dbos_agent
|
|
1214
|
+
# Include filtered MCP servers in the agent
|
|
1215
|
+
p_agent = PydanticAgent(
|
|
1216
|
+
model=model,
|
|
1217
|
+
instructions=instructions,
|
|
1218
|
+
output_type=str,
|
|
1219
|
+
retries=3,
|
|
1220
|
+
toolsets=filtered_mcp_servers if filtered_mcp_servers else [],
|
|
1221
|
+
history_processors=[self.message_history_accumulator],
|
|
1222
|
+
model_settings=model_settings,
|
|
1223
|
+
)
|
|
1224
|
+
# Register regular tools on the agent
|
|
1225
|
+
agent_tools = self.get_available_tools()
|
|
1226
|
+
register_tools_for_agent(p_agent, agent_tools)
|
|
1242
1227
|
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
# Normal path without DBOS - include filtered MCP servers in the agent
|
|
1247
|
-
# Re-create agent with filtered MCP servers
|
|
1248
|
-
p_agent = PydanticAgent(
|
|
1249
|
-
model=model,
|
|
1250
|
-
instructions=instructions,
|
|
1251
|
-
output_type=str,
|
|
1252
|
-
retries=3,
|
|
1253
|
-
toolsets=filtered_mcp_servers,
|
|
1254
|
-
history_processors=[self.message_history_accumulator],
|
|
1255
|
-
model_settings=model_settings,
|
|
1256
|
-
)
|
|
1257
|
-
# Register regular tools on the agent
|
|
1258
|
-
agent_tools = self.get_available_tools()
|
|
1259
|
-
register_tools_for_agent(p_agent, agent_tools)
|
|
1260
|
-
|
|
1261
|
-
self.pydantic_agent = p_agent
|
|
1262
|
-
self._code_generation_agent = p_agent
|
|
1263
|
-
self._mcp_servers = filtered_mcp_servers
|
|
1264
|
-
self._mcp_servers = mcp_servers
|
|
1228
|
+
self.pydantic_agent = p_agent
|
|
1229
|
+
self._code_generation_agent = p_agent
|
|
1230
|
+
self._mcp_servers = filtered_mcp_servers
|
|
1265
1231
|
return self._code_generation_agent
|
|
1266
1232
|
|
|
1267
1233
|
def _create_agent_with_output_type(self, output_type: Type[Any]) -> PydanticAgent:
|
|
@@ -1275,7 +1241,7 @@ class BaseAgent(ABC):
|
|
|
1275
1241
|
output_type: The Pydantic model or type for structured output.
|
|
1276
1242
|
|
|
1277
1243
|
Returns:
|
|
1278
|
-
A configured PydanticAgent
|
|
1244
|
+
A configured PydanticAgent with the custom output_type.
|
|
1279
1245
|
"""
|
|
1280
1246
|
from code_puppy.model_utils import prepare_prompt_for_model
|
|
1281
1247
|
from code_puppy.tools import register_tools_for_agent
|
|
@@ -1302,38 +1268,19 @@ class BaseAgent(ABC):
|
|
|
1302
1268
|
global _reload_count
|
|
1303
1269
|
_reload_count += 1
|
|
1304
1270
|
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
dbos_agent = DBOSAgent(
|
|
1318
|
-
temp_agent, name=f"{self.name}-structured-{_reload_count}"
|
|
1319
|
-
)
|
|
1320
|
-
return dbos_agent
|
|
1321
|
-
else:
|
|
1322
|
-
temp_agent = PydanticAgent(
|
|
1323
|
-
model=model,
|
|
1324
|
-
instructions=instructions,
|
|
1325
|
-
output_type=output_type,
|
|
1326
|
-
retries=3,
|
|
1327
|
-
toolsets=mcp_servers,
|
|
1328
|
-
history_processors=[self.message_history_accumulator],
|
|
1329
|
-
model_settings=model_settings,
|
|
1330
|
-
)
|
|
1331
|
-
agent_tools = self.get_available_tools()
|
|
1332
|
-
register_tools_for_agent(temp_agent, agent_tools)
|
|
1333
|
-
return temp_agent
|
|
1271
|
+
temp_agent = PydanticAgent(
|
|
1272
|
+
model=model,
|
|
1273
|
+
instructions=instructions,
|
|
1274
|
+
output_type=output_type,
|
|
1275
|
+
retries=3,
|
|
1276
|
+
toolsets=mcp_servers,
|
|
1277
|
+
history_processors=[self.message_history_accumulator],
|
|
1278
|
+
model_settings=model_settings,
|
|
1279
|
+
)
|
|
1280
|
+
agent_tools = self.get_available_tools()
|
|
1281
|
+
register_tools_for_agent(temp_agent, agent_tools)
|
|
1282
|
+
return temp_agent
|
|
1334
1283
|
|
|
1335
|
-
# It's okay to decorate it with DBOS.step even if not using DBOS; the decorator is a no-op in that case.
|
|
1336
|
-
@DBOS.step()
|
|
1337
1284
|
def message_history_accumulator(self, ctx: RunContext, messages: List[Any]):
|
|
1338
1285
|
_message_history = self.get_message_history()
|
|
1339
1286
|
message_history_hashes = set([self.hash_message(m) for m in _message_history])
|
|
@@ -1841,49 +1788,14 @@ class BaseAgent(ABC):
|
|
|
1841
1788
|
|
|
1842
1789
|
usage_limits = UsageLimits(request_limit=get_message_limit())
|
|
1843
1790
|
|
|
1844
|
-
#
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
pydantic_agent._toolsets = original_toolsets + self._mcp_servers
|
|
1853
|
-
pydantic_agent._toolsets = original_toolsets + self._mcp_servers
|
|
1854
|
-
|
|
1855
|
-
try:
|
|
1856
|
-
# Set the workflow ID for DBOS context so DBOS and Code Puppy ID match
|
|
1857
|
-
with SetWorkflowID(group_id):
|
|
1858
|
-
result_ = await pydantic_agent.run(
|
|
1859
|
-
prompt_payload,
|
|
1860
|
-
message_history=self.get_message_history(),
|
|
1861
|
-
usage_limits=usage_limits,
|
|
1862
|
-
event_stream_handler=self._event_stream_handler,
|
|
1863
|
-
**kwargs,
|
|
1864
|
-
)
|
|
1865
|
-
finally:
|
|
1866
|
-
# Always restore original toolsets
|
|
1867
|
-
pydantic_agent._toolsets = original_toolsets
|
|
1868
|
-
elif get_use_dbos():
|
|
1869
|
-
# DBOS without MCP servers
|
|
1870
|
-
with SetWorkflowID(group_id):
|
|
1871
|
-
result_ = await pydantic_agent.run(
|
|
1872
|
-
prompt_payload,
|
|
1873
|
-
message_history=self.get_message_history(),
|
|
1874
|
-
usage_limits=usage_limits,
|
|
1875
|
-
event_stream_handler=self._event_stream_handler,
|
|
1876
|
-
**kwargs,
|
|
1877
|
-
)
|
|
1878
|
-
else:
|
|
1879
|
-
# Non-DBOS path (MCP servers are already included)
|
|
1880
|
-
result_ = await pydantic_agent.run(
|
|
1881
|
-
prompt_payload,
|
|
1882
|
-
message_history=self.get_message_history(),
|
|
1883
|
-
usage_limits=usage_limits,
|
|
1884
|
-
event_stream_handler=self._event_stream_handler,
|
|
1885
|
-
**kwargs,
|
|
1886
|
-
)
|
|
1791
|
+
# MCP servers are already included in the agent
|
|
1792
|
+
result_ = await pydantic_agent.run(
|
|
1793
|
+
prompt_payload,
|
|
1794
|
+
message_history=self.get_message_history(),
|
|
1795
|
+
usage_limits=usage_limits,
|
|
1796
|
+
event_stream_handler=self._event_stream_handler,
|
|
1797
|
+
**kwargs,
|
|
1798
|
+
)
|
|
1887
1799
|
return result_
|
|
1888
1800
|
except* UsageLimitExceeded as ule:
|
|
1889
1801
|
emit_info(f"Usage limit exceeded: {str(ule)}", group_id=group_id)
|
|
@@ -1899,12 +1811,8 @@ class BaseAgent(ABC):
|
|
|
1899
1811
|
)
|
|
1900
1812
|
except* asyncio.exceptions.CancelledError:
|
|
1901
1813
|
emit_info("Cancelled")
|
|
1902
|
-
if get_use_dbos():
|
|
1903
|
-
await DBOS.cancel_workflow_async(group_id)
|
|
1904
1814
|
except* InterruptedError as ie:
|
|
1905
1815
|
emit_info(f"Interrupted: {str(ie)}")
|
|
1906
|
-
if get_use_dbos():
|
|
1907
|
-
await DBOS.cancel_workflow_async(group_id)
|
|
1908
1816
|
except* Exception as other_error:
|
|
1909
1817
|
# Filter out CancelledError and UsageLimitExceeded from the exception group - let it propagate
|
|
1910
1818
|
remaining_exceptions = []
|
code_puppy/cli_runner.py
CHANGED
|
@@ -12,11 +12,9 @@ import argparse
|
|
|
12
12
|
import asyncio
|
|
13
13
|
import os
|
|
14
14
|
import sys
|
|
15
|
-
import time
|
|
16
15
|
import traceback
|
|
17
16
|
from pathlib import Path
|
|
18
17
|
|
|
19
|
-
from dbos import DBOS, DBOSConfig
|
|
20
18
|
from rich.console import Console
|
|
21
19
|
|
|
22
20
|
from code_puppy import __version__, callbacks, plugins
|
|
@@ -26,10 +24,8 @@ from code_puppy.command_line.clipboard import get_clipboard_manager
|
|
|
26
24
|
from code_puppy.config import (
|
|
27
25
|
AUTOSAVE_DIR,
|
|
28
26
|
COMMAND_HISTORY_FILE,
|
|
29
|
-
DBOS_DATABASE_URL,
|
|
30
27
|
ensure_config_exists,
|
|
31
28
|
finalize_autosave_session,
|
|
32
|
-
get_use_dbos,
|
|
33
29
|
initialize_command_history_file,
|
|
34
30
|
save_command_to_history,
|
|
35
31
|
)
|
|
@@ -287,33 +283,6 @@ async def main():
|
|
|
287
283
|
|
|
288
284
|
await callbacks.on_startup()
|
|
289
285
|
|
|
290
|
-
# Initialize DBOS if not disabled
|
|
291
|
-
if get_use_dbos():
|
|
292
|
-
# Append a Unix timestamp in ms to the version for uniqueness
|
|
293
|
-
dbos_app_version = os.environ.get(
|
|
294
|
-
"DBOS_APP_VERSION", f"{current_version}-{int(time.time() * 1000)}"
|
|
295
|
-
)
|
|
296
|
-
dbos_config: DBOSConfig = {
|
|
297
|
-
"name": "dbos-code-puppy",
|
|
298
|
-
"system_database_url": DBOS_DATABASE_URL,
|
|
299
|
-
"run_admin_server": False,
|
|
300
|
-
"conductor_key": os.environ.get(
|
|
301
|
-
"DBOS_CONDUCTOR_KEY"
|
|
302
|
-
), # Optional, if set in env, connect to conductor
|
|
303
|
-
"log_level": os.environ.get(
|
|
304
|
-
"DBOS_LOG_LEVEL", "ERROR"
|
|
305
|
-
), # Default to ERROR level to suppress verbose logs
|
|
306
|
-
"application_version": dbos_app_version, # Match DBOS app version to Code Puppy version
|
|
307
|
-
}
|
|
308
|
-
try:
|
|
309
|
-
DBOS(config=dbos_config)
|
|
310
|
-
DBOS.launch()
|
|
311
|
-
except Exception as e:
|
|
312
|
-
emit_error(f"Error initializing DBOS: {e}")
|
|
313
|
-
sys.exit(1)
|
|
314
|
-
else:
|
|
315
|
-
pass
|
|
316
|
-
|
|
317
286
|
global shutdown_flag
|
|
318
287
|
shutdown_flag = False
|
|
319
288
|
try:
|
|
@@ -338,8 +307,6 @@ async def main():
|
|
|
338
307
|
if bus_renderer:
|
|
339
308
|
bus_renderer.stop()
|
|
340
309
|
await callbacks.on_shutdown()
|
|
341
|
-
if get_use_dbos():
|
|
342
|
-
DBOS.destroy()
|
|
343
310
|
|
|
344
311
|
|
|
345
312
|
async def interactive_mode(message_renderer, initial_command: str = None) -> None:
|
|
@@ -907,8 +874,6 @@ def main_entry():
|
|
|
907
874
|
except KeyboardInterrupt:
|
|
908
875
|
# Note: Using sys.stderr for crash output - messaging system may not be available
|
|
909
876
|
sys.stderr.write(traceback.format_exc())
|
|
910
|
-
if get_use_dbos():
|
|
911
|
-
DBOS.destroy()
|
|
912
877
|
return 0
|
|
913
878
|
finally:
|
|
914
879
|
# Reset terminal on Unix-like systems (not Windows)
|
|
@@ -17,6 +17,7 @@ from prompt_toolkit.layout import Dimension, Layout, VSplit, Window
|
|
|
17
17
|
from prompt_toolkit.layout.controls import FormattedTextControl
|
|
18
18
|
from prompt_toolkit.widgets import Frame
|
|
19
19
|
|
|
20
|
+
from code_puppy.command_line.utils import safe_input
|
|
20
21
|
from code_puppy.config import EXTRA_MODELS_FILE, set_config_value
|
|
21
22
|
from code_puppy.messaging import emit_error, emit_info, emit_warning
|
|
22
23
|
from code_puppy.models_dev_parser import ModelInfo, ModelsDevRegistry, ProviderInfo
|
|
@@ -724,8 +725,8 @@ class AddModelMenu:
|
|
|
724
725
|
emit_info(f" {hint}")
|
|
725
726
|
|
|
726
727
|
try:
|
|
727
|
-
# Use
|
|
728
|
-
value =
|
|
728
|
+
# Use safe_input for cross-platform compatibility (Windows fix)
|
|
729
|
+
value = safe_input(f" Enter {env_var} (or press Enter to skip): ")
|
|
729
730
|
|
|
730
731
|
if not value:
|
|
731
732
|
emit_warning(
|
|
@@ -785,7 +786,7 @@ class AddModelMenu:
|
|
|
785
786
|
)
|
|
786
787
|
|
|
787
788
|
try:
|
|
788
|
-
model_name =
|
|
789
|
+
model_name = safe_input(" Model ID: ")
|
|
789
790
|
|
|
790
791
|
if not model_name:
|
|
791
792
|
emit_warning("No model name provided, cancelled.")
|
|
@@ -795,7 +796,7 @@ class AddModelMenu:
|
|
|
795
796
|
emit_info("\n Enter the context window size (in tokens).")
|
|
796
797
|
emit_info(" Common sizes: 8192, 32768, 128000, 200000, 1000000\n")
|
|
797
798
|
|
|
798
|
-
context_input =
|
|
799
|
+
context_input = safe_input(" Context size [128000]: ")
|
|
799
800
|
|
|
800
801
|
if not context_input:
|
|
801
802
|
context_length = 128000 # Default
|
|
@@ -1045,11 +1046,9 @@ class AddModelMenu:
|
|
|
1045
1046
|
f" It will be very limited for coding tasks."
|
|
1046
1047
|
)
|
|
1047
1048
|
try:
|
|
1048
|
-
confirm = (
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
.lower()
|
|
1052
|
-
)
|
|
1049
|
+
confirm = safe_input(
|
|
1050
|
+
"\n Are you sure you want to add this model? (y/N): "
|
|
1051
|
+
).lower()
|
|
1053
1052
|
if confirm not in ("y", "yes"):
|
|
1054
1053
|
emit_info("Model addition cancelled.")
|
|
1055
1054
|
return False
|
|
@@ -43,7 +43,6 @@ def handle_show_command(command: str) -> bool:
|
|
|
43
43
|
get_protected_token_count,
|
|
44
44
|
get_puppy_name,
|
|
45
45
|
get_temperature,
|
|
46
|
-
get_use_dbos,
|
|
47
46
|
get_yolo_mode,
|
|
48
47
|
)
|
|
49
48
|
from code_puppy.keymap import get_cancel_agent_display_name
|
|
@@ -72,7 +71,6 @@ def handle_show_command(command: str) -> bool:
|
|
|
72
71
|
[bold]default_agent:[/bold] [cyan]{default_agent}[/cyan]
|
|
73
72
|
[bold]model:[/bold] [green]{model}[/green]
|
|
74
73
|
[bold]YOLO_MODE:[/bold] {"[red]ON[/red]" if yolo_mode else "[yellow]off[/yellow]"}
|
|
75
|
-
[bold]DBOS:[/bold] {"[green]enabled[/green]" if get_use_dbos() else "[yellow]disabled[/yellow]"} (toggle: /set enable_dbos true|false)
|
|
76
74
|
[bold]auto_save_session:[/bold] {"[green]enabled[/green]" if auto_save else "[yellow]disabled[/yellow]"}
|
|
77
75
|
[bold]protected_tokens:[/bold] [cyan]{protected_tokens:,}[/cyan] recent tokens preserved
|
|
78
76
|
[bold]compaction_threshold:[/bold] [cyan]{compaction_threshold:.1%}[/cyan] context usage triggers compaction
|
|
@@ -213,14 +211,6 @@ def handle_set_command(command: str) -> bool:
|
|
|
213
211
|
)
|
|
214
212
|
return True
|
|
215
213
|
if key:
|
|
216
|
-
# Check if we're toggling DBOS enablement
|
|
217
|
-
if key == "enable_dbos":
|
|
218
|
-
emit_info(
|
|
219
|
-
Text.from_markup(
|
|
220
|
-
"[yellow]⚠️ DBOS configuration changed. Please restart Code Puppy for this change to take effect.[/yellow]"
|
|
221
|
-
)
|
|
222
|
-
)
|
|
223
|
-
|
|
224
214
|
# Validate cancel_agent_key before setting
|
|
225
215
|
if key == "cancel_agent_key":
|
|
226
216
|
from code_puppy.keymap import VALID_CANCEL_KEYS
|
|
@@ -7,6 +7,7 @@ MCP servers from the catalog.
|
|
|
7
7
|
import os
|
|
8
8
|
from typing import Dict, Optional
|
|
9
9
|
|
|
10
|
+
from code_puppy.command_line.utils import safe_input
|
|
10
11
|
from code_puppy.messaging import emit_info, emit_success, emit_warning
|
|
11
12
|
|
|
12
13
|
# Helpful hints for common environment variables
|
|
@@ -52,7 +53,7 @@ def prompt_for_server_config(manager, server) -> Optional[Dict]:
|
|
|
52
53
|
# Get custom name
|
|
53
54
|
default_name = server.name
|
|
54
55
|
try:
|
|
55
|
-
name_input =
|
|
56
|
+
name_input = safe_input(f" Server name [{default_name}]: ")
|
|
56
57
|
server_name = name_input if name_input else default_name
|
|
57
58
|
except (KeyboardInterrupt, EOFError):
|
|
58
59
|
emit_info("")
|
|
@@ -63,9 +64,7 @@ def prompt_for_server_config(manager, server) -> Optional[Dict]:
|
|
|
63
64
|
existing = find_server_id_by_name(manager, server_name)
|
|
64
65
|
if existing:
|
|
65
66
|
try:
|
|
66
|
-
override =
|
|
67
|
-
f" Server '{server_name}' exists. Override? [y/N]: "
|
|
68
|
-
).strip()
|
|
67
|
+
override = safe_input(f" Server '{server_name}' exists. Override? [y/N]: ")
|
|
69
68
|
if not override.lower().startswith("y"):
|
|
70
69
|
emit_warning("Installation cancelled")
|
|
71
70
|
return None
|
|
@@ -91,7 +90,7 @@ def prompt_for_server_config(manager, server) -> Optional[Dict]:
|
|
|
91
90
|
hint = get_env_var_hint(var)
|
|
92
91
|
if hint:
|
|
93
92
|
emit_info(f" {hint}")
|
|
94
|
-
value =
|
|
93
|
+
value = safe_input(f" Enter {var}: ")
|
|
95
94
|
if value:
|
|
96
95
|
env_vars[var] = value
|
|
97
96
|
# Save to config for future use
|
|
@@ -119,7 +118,7 @@ def prompt_for_server_config(manager, server) -> Optional[Dict]:
|
|
|
119
118
|
prompt_str += " (optional)"
|
|
120
119
|
|
|
121
120
|
try:
|
|
122
|
-
value =
|
|
121
|
+
value = safe_input(f"{prompt_str}: ")
|
|
123
122
|
if value:
|
|
124
123
|
cmd_args[name] = value
|
|
125
124
|
elif default:
|
|
@@ -43,7 +43,7 @@ CUSTOM_SERVER_EXAMPLES = {
|
|
|
43
43
|
"type": "http",
|
|
44
44
|
"url": "http://localhost:8080/mcp",
|
|
45
45
|
"headers": {
|
|
46
|
-
"Authorization": "Bearer
|
|
46
|
+
"Authorization": "Bearer $MY_API_KEY",
|
|
47
47
|
"Content-Type": "application/json"
|
|
48
48
|
},
|
|
49
49
|
"timeout": 30
|
|
@@ -52,7 +52,7 @@ CUSTOM_SERVER_EXAMPLES = {
|
|
|
52
52
|
"type": "sse",
|
|
53
53
|
"url": "http://localhost:8080/sse",
|
|
54
54
|
"headers": {
|
|
55
|
-
"Authorization": "Bearer
|
|
55
|
+
"Authorization": "Bearer $MY_API_KEY"
|
|
56
56
|
}
|
|
57
57
|
}""",
|
|
58
58
|
}
|
|
@@ -367,24 +367,59 @@ class CustomServerForm:
|
|
|
367
367
|
config_dict = json.loads(self.json_config)
|
|
368
368
|
|
|
369
369
|
try:
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
370
|
+
# In edit mode, find the existing server and update it
|
|
371
|
+
if self.edit_mode and self.original_name:
|
|
372
|
+
existing_config = self.manager.get_server_by_name(self.original_name)
|
|
373
|
+
if existing_config:
|
|
374
|
+
# Use the existing server's ID for the update
|
|
375
|
+
server_config = ServerConfig(
|
|
376
|
+
id=existing_config.id,
|
|
377
|
+
name=server_name,
|
|
378
|
+
type=server_type,
|
|
379
|
+
enabled=True,
|
|
380
|
+
config=config_dict,
|
|
381
|
+
)
|
|
382
|
+
|
|
383
|
+
# Update the server in the manager
|
|
384
|
+
success = self.manager.update_server(
|
|
385
|
+
existing_config.id, server_config
|
|
386
|
+
)
|
|
387
|
+
|
|
388
|
+
if not success:
|
|
389
|
+
self.validation_error = "Failed to update server"
|
|
390
|
+
self.status_message = "Save failed: Could not update server"
|
|
391
|
+
self.status_is_error = True
|
|
392
|
+
return False
|
|
393
|
+
|
|
394
|
+
server_id = existing_config.id
|
|
395
|
+
else:
|
|
396
|
+
# Original server not found, treat as new registration
|
|
397
|
+
server_config = ServerConfig(
|
|
398
|
+
id=server_name,
|
|
399
|
+
name=server_name,
|
|
400
|
+
type=server_type,
|
|
401
|
+
enabled=True,
|
|
402
|
+
config=config_dict,
|
|
403
|
+
)
|
|
404
|
+
server_id = self.manager.register_server(server_config)
|
|
405
|
+
else:
|
|
406
|
+
# New server - register it
|
|
407
|
+
server_config = ServerConfig(
|
|
408
|
+
id=server_name,
|
|
409
|
+
name=server_name,
|
|
410
|
+
type=server_type,
|
|
411
|
+
enabled=True,
|
|
412
|
+
config=config_dict,
|
|
385
413
|
)
|
|
386
|
-
|
|
387
|
-
|
|
414
|
+
|
|
415
|
+
# Register with manager
|
|
416
|
+
server_id = self.manager.register_server(server_config)
|
|
417
|
+
|
|
418
|
+
if not server_id:
|
|
419
|
+
self.validation_error = "Failed to register server"
|
|
420
|
+
self.status_message = "Save failed: Could not register server (name may already exist)"
|
|
421
|
+
self.status_is_error = True
|
|
422
|
+
return False
|
|
388
423
|
|
|
389
424
|
# Save to mcp_servers.json for persistence
|
|
390
425
|
if os.path.exists(MCP_SERVERS_FILE):
|
|
@@ -7,6 +7,7 @@ custom MCP servers with JSON configuration.
|
|
|
7
7
|
import json
|
|
8
8
|
import os
|
|
9
9
|
|
|
10
|
+
from code_puppy.command_line.utils import safe_input
|
|
10
11
|
from code_puppy.messaging import emit_error, emit_info, emit_success, emit_warning
|
|
11
12
|
|
|
12
13
|
# Example configurations for each server type
|
|
@@ -24,7 +25,7 @@ CUSTOM_SERVER_EXAMPLES = {
|
|
|
24
25
|
"type": "http",
|
|
25
26
|
"url": "http://localhost:8080/mcp",
|
|
26
27
|
"headers": {
|
|
27
|
-
"Authorization": "Bearer
|
|
28
|
+
"Authorization": "Bearer $MY_API_KEY",
|
|
28
29
|
"Content-Type": "application/json"
|
|
29
30
|
},
|
|
30
31
|
"timeout": 30
|
|
@@ -33,7 +34,7 @@ CUSTOM_SERVER_EXAMPLES = {
|
|
|
33
34
|
"type": "sse",
|
|
34
35
|
"url": "http://localhost:8080/sse",
|
|
35
36
|
"headers": {
|
|
36
|
-
"Authorization": "Bearer
|
|
37
|
+
"Authorization": "Bearer $MY_API_KEY"
|
|
37
38
|
}
|
|
38
39
|
}""",
|
|
39
40
|
}
|
|
@@ -58,7 +59,7 @@ def prompt_and_install_custom_server(manager) -> bool:
|
|
|
58
59
|
|
|
59
60
|
# Get server name
|
|
60
61
|
try:
|
|
61
|
-
server_name =
|
|
62
|
+
server_name = safe_input(" Server name: ")
|
|
62
63
|
if not server_name:
|
|
63
64
|
emit_warning("Server name is required")
|
|
64
65
|
return False
|
|
@@ -71,9 +72,7 @@ def prompt_and_install_custom_server(manager) -> bool:
|
|
|
71
72
|
existing = find_server_id_by_name(manager, server_name)
|
|
72
73
|
if existing:
|
|
73
74
|
try:
|
|
74
|
-
override =
|
|
75
|
-
f" Server '{server_name}' exists. Override? [y/N]: "
|
|
76
|
-
).strip()
|
|
75
|
+
override = safe_input(f" Server '{server_name}' exists. Override? [y/N]: ")
|
|
77
76
|
if not override.lower().startswith("y"):
|
|
78
77
|
emit_warning("Cancelled")
|
|
79
78
|
return False
|
|
@@ -89,7 +88,7 @@ def prompt_and_install_custom_server(manager) -> bool:
|
|
|
89
88
|
emit_info(" 3. 📡 sse - Server-Sent Events\n")
|
|
90
89
|
|
|
91
90
|
try:
|
|
92
|
-
type_choice =
|
|
91
|
+
type_choice = safe_input(" Enter choice [1-3]: ")
|
|
93
92
|
except (KeyboardInterrupt, EOFError):
|
|
94
93
|
emit_info("")
|
|
95
94
|
emit_warning("Cancelled")
|
|
@@ -115,8 +114,8 @@ def prompt_and_install_custom_server(manager) -> bool:
|
|
|
115
114
|
empty_count = 0
|
|
116
115
|
try:
|
|
117
116
|
while True:
|
|
118
|
-
line =
|
|
119
|
-
if line
|
|
117
|
+
line = safe_input("")
|
|
118
|
+
if line == "":
|
|
120
119
|
empty_count += 1
|
|
121
120
|
if empty_count >= 2:
|
|
122
121
|
break
|
|
@@ -12,7 +12,6 @@ from rich.text import Text
|
|
|
12
12
|
|
|
13
13
|
from code_puppy.messaging import emit_info
|
|
14
14
|
|
|
15
|
-
from .add_command import AddCommand
|
|
16
15
|
from .base import MCPCommandBase
|
|
17
16
|
from .edit_command import EditCommand
|
|
18
17
|
from .help_command import HelpCommand
|
|
@@ -63,7 +62,6 @@ class MCPCommandHandler(MCPCommandBase):
|
|
|
63
62
|
"restart": RestartCommand(),
|
|
64
63
|
"status": StatusCommand(),
|
|
65
64
|
"test": TestCommand(),
|
|
66
|
-
"add": AddCommand(),
|
|
67
65
|
"edit": EditCommand(),
|
|
68
66
|
"remove": RemoveCommand(),
|
|
69
67
|
"logs": LogsCommand(),
|
|
@@ -101,10 +101,6 @@ class HelpCommand(MCPCommandBase):
|
|
|
101
101
|
Text("/mcp logs", style="cyan")
|
|
102
102
|
+ Text(" <name> [limit] Show recent events (default limit: 10)")
|
|
103
103
|
)
|
|
104
|
-
help_lines.append(
|
|
105
|
-
Text("/mcp add", style="cyan")
|
|
106
|
-
+ Text(" [json] Add new server (JSON or wizard)")
|
|
107
|
-
)
|
|
108
104
|
help_lines.append(
|
|
109
105
|
Text("/mcp edit", style="cyan")
|
|
110
106
|
+ Text(" <name> Edit existing server config")
|
|
@@ -134,7 +130,7 @@ class HelpCommand(MCPCommandBase):
|
|
|
134
130
|
/mcp start-all # Start all servers at once
|
|
135
131
|
/mcp stop-all # Stop all running servers
|
|
136
132
|
/mcp edit filesystem # Edit an existing server config
|
|
137
|
-
/mcp
|
|
133
|
+
/mcp remove filesystem # Remove a server"""
|
|
138
134
|
help_lines.append(Text(examples_text, style="dim"))
|
|
139
135
|
|
|
140
136
|
# Combine all lines
|