code-puppy 0.0.287__py3-none-any.whl → 0.0.323__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/__init__.py +3 -1
- code_puppy/agents/agent_code_puppy.py +5 -4
- code_puppy/agents/agent_creator_agent.py +22 -18
- code_puppy/agents/agent_manager.py +2 -2
- code_puppy/agents/base_agent.py +496 -102
- code_puppy/callbacks.py +8 -0
- code_puppy/chatgpt_codex_client.py +283 -0
- code_puppy/cli_runner.py +795 -0
- code_puppy/command_line/add_model_menu.py +19 -16
- code_puppy/command_line/attachments.py +10 -5
- code_puppy/command_line/autosave_menu.py +269 -41
- code_puppy/command_line/colors_menu.py +515 -0
- code_puppy/command_line/command_handler.py +10 -24
- code_puppy/command_line/config_commands.py +106 -25
- code_puppy/command_line/core_commands.py +32 -20
- code_puppy/command_line/mcp/add_command.py +3 -16
- code_puppy/command_line/mcp/base.py +0 -3
- code_puppy/command_line/mcp/catalog_server_installer.py +15 -15
- code_puppy/command_line/mcp/custom_server_form.py +66 -5
- code_puppy/command_line/mcp/custom_server_installer.py +17 -17
- code_puppy/command_line/mcp/edit_command.py +15 -22
- code_puppy/command_line/mcp/handler.py +7 -2
- code_puppy/command_line/mcp/help_command.py +2 -2
- code_puppy/command_line/mcp/install_command.py +10 -14
- code_puppy/command_line/mcp/install_menu.py +2 -6
- code_puppy/command_line/mcp/list_command.py +2 -2
- code_puppy/command_line/mcp/logs_command.py +174 -65
- code_puppy/command_line/mcp/remove_command.py +2 -2
- code_puppy/command_line/mcp/restart_command.py +7 -2
- code_puppy/command_line/mcp/search_command.py +16 -10
- code_puppy/command_line/mcp/start_all_command.py +16 -6
- code_puppy/command_line/mcp/start_command.py +12 -10
- code_puppy/command_line/mcp/status_command.py +4 -5
- code_puppy/command_line/mcp/stop_all_command.py +5 -1
- code_puppy/command_line/mcp/stop_command.py +6 -4
- code_puppy/command_line/mcp/test_command.py +2 -2
- code_puppy/command_line/mcp/wizard_utils.py +20 -16
- code_puppy/command_line/model_settings_menu.py +53 -7
- code_puppy/command_line/motd.py +1 -1
- code_puppy/command_line/pin_command_completion.py +82 -7
- code_puppy/command_line/prompt_toolkit_completion.py +32 -9
- code_puppy/command_line/session_commands.py +11 -4
- code_puppy/config.py +217 -53
- code_puppy/error_logging.py +118 -0
- code_puppy/gemini_code_assist.py +385 -0
- code_puppy/keymap.py +126 -0
- code_puppy/main.py +5 -745
- code_puppy/mcp_/__init__.py +17 -0
- code_puppy/mcp_/blocking_startup.py +63 -36
- code_puppy/mcp_/captured_stdio_server.py +1 -1
- code_puppy/mcp_/config_wizard.py +4 -4
- code_puppy/mcp_/dashboard.py +15 -6
- code_puppy/mcp_/managed_server.py +25 -5
- code_puppy/mcp_/manager.py +65 -0
- code_puppy/mcp_/mcp_logs.py +224 -0
- code_puppy/mcp_/registry.py +6 -6
- code_puppy/messaging/__init__.py +184 -2
- code_puppy/messaging/bus.py +610 -0
- code_puppy/messaging/commands.py +167 -0
- code_puppy/messaging/markdown_patches.py +57 -0
- code_puppy/messaging/message_queue.py +3 -3
- code_puppy/messaging/messages.py +470 -0
- code_puppy/messaging/renderers.py +43 -141
- code_puppy/messaging/rich_renderer.py +900 -0
- code_puppy/messaging/spinner/console_spinner.py +39 -2
- code_puppy/model_factory.py +292 -53
- code_puppy/model_utils.py +57 -48
- code_puppy/models.json +19 -5
- code_puppy/plugins/__init__.py +152 -10
- code_puppy/plugins/chatgpt_oauth/config.py +20 -12
- code_puppy/plugins/chatgpt_oauth/oauth_flow.py +5 -6
- code_puppy/plugins/chatgpt_oauth/register_callbacks.py +3 -3
- code_puppy/plugins/chatgpt_oauth/test_plugin.py +30 -13
- code_puppy/plugins/chatgpt_oauth/utils.py +180 -65
- code_puppy/plugins/claude_code_oauth/config.py +15 -11
- code_puppy/plugins/claude_code_oauth/register_callbacks.py +28 -0
- code_puppy/plugins/claude_code_oauth/utils.py +6 -1
- code_puppy/plugins/example_custom_command/register_callbacks.py +2 -2
- code_puppy/plugins/oauth_puppy_html.py +3 -0
- code_puppy/plugins/shell_safety/agent_shell_safety.py +1 -134
- code_puppy/plugins/shell_safety/command_cache.py +156 -0
- code_puppy/plugins/shell_safety/register_callbacks.py +77 -3
- code_puppy/prompts/codex_system_prompt.md +310 -0
- code_puppy/pydantic_patches.py +131 -0
- code_puppy/session_storage.py +2 -1
- code_puppy/status_display.py +7 -5
- code_puppy/terminal_utils.py +126 -0
- code_puppy/tools/agent_tools.py +131 -70
- code_puppy/tools/browser/browser_control.py +10 -14
- code_puppy/tools/browser/browser_interactions.py +20 -28
- code_puppy/tools/browser/browser_locators.py +27 -29
- code_puppy/tools/browser/browser_navigation.py +9 -9
- code_puppy/tools/browser/browser_screenshot.py +12 -14
- code_puppy/tools/browser/browser_scripts.py +17 -29
- code_puppy/tools/browser/browser_workflows.py +24 -25
- code_puppy/tools/browser/camoufox_manager.py +22 -26
- code_puppy/tools/command_runner.py +410 -88
- code_puppy/tools/common.py +51 -38
- code_puppy/tools/file_modifications.py +98 -24
- code_puppy/tools/file_operations.py +113 -202
- code_puppy/version_checker.py +28 -13
- {code_puppy-0.0.287.data → code_puppy-0.0.323.data}/data/code_puppy/models.json +19 -5
- {code_puppy-0.0.287.dist-info → code_puppy-0.0.323.dist-info}/METADATA +3 -8
- code_puppy-0.0.323.dist-info/RECORD +168 -0
- code_puppy/tui_state.py +0 -55
- code_puppy-0.0.287.dist-info/RECORD +0 -153
- {code_puppy-0.0.287.data → code_puppy-0.0.323.data}/data/code_puppy/models_dev_api.json +0 -0
- {code_puppy-0.0.287.dist-info → code_puppy-0.0.323.dist-info}/WHEEL +0 -0
- {code_puppy-0.0.287.dist-info → code_puppy-0.0.323.dist-info}/entry_points.txt +0 -0
- {code_puppy-0.0.287.dist-info → code_puppy-0.0.323.dist-info}/licenses/LICENSE +0 -0
|
@@ -7,7 +7,9 @@ Provides interactive functionality for installing and configuring MCP servers.
|
|
|
7
7
|
import logging
|
|
8
8
|
from typing import Any, Dict, Optional
|
|
9
9
|
|
|
10
|
-
from
|
|
10
|
+
from rich.text import Text
|
|
11
|
+
|
|
12
|
+
from code_puppy.messaging import emit_error, emit_info, emit_prompt
|
|
11
13
|
|
|
12
14
|
# Configure logging
|
|
13
15
|
logger = logging.getLogger(__name__)
|
|
@@ -51,7 +53,7 @@ def run_interactive_install_wizard(manager, group_id: str) -> bool:
|
|
|
51
53
|
required_env_vars = selected_server.get_environment_vars()
|
|
52
54
|
if required_env_vars:
|
|
53
55
|
emit_info(
|
|
54
|
-
"\n[yellow]Required Environment Variables:[/yellow]",
|
|
56
|
+
Text.from_markup("\n[yellow]Required Environment Variables:[/yellow]"),
|
|
55
57
|
message_group=group_id,
|
|
56
58
|
)
|
|
57
59
|
for var in required_env_vars:
|
|
@@ -61,7 +63,8 @@ def run_interactive_install_wizard(manager, group_id: str) -> bool:
|
|
|
61
63
|
current_value = os.environ.get(var, "")
|
|
62
64
|
if current_value:
|
|
63
65
|
emit_info(
|
|
64
|
-
f" {var}: [green]Already set[/green]",
|
|
66
|
+
Text.from_markup(f" {var}: [green]Already set[/green]"),
|
|
67
|
+
message_group=group_id,
|
|
65
68
|
)
|
|
66
69
|
env_vars[var] = current_value
|
|
67
70
|
else:
|
|
@@ -73,7 +76,8 @@ def run_interactive_install_wizard(manager, group_id: str) -> bool:
|
|
|
73
76
|
required_cmd_args = selected_server.get_command_line_args()
|
|
74
77
|
if required_cmd_args:
|
|
75
78
|
emit_info(
|
|
76
|
-
"\n[yellow]Command Line Arguments:[/yellow]",
|
|
79
|
+
Text.from_markup("\n[yellow]Command Line Arguments:[/yellow]"),
|
|
80
|
+
message_group=group_id,
|
|
77
81
|
)
|
|
78
82
|
for arg_config in required_cmd_args:
|
|
79
83
|
name = arg_config.get("name", "")
|
|
@@ -101,11 +105,11 @@ def run_interactive_install_wizard(manager, group_id: str) -> bool:
|
|
|
101
105
|
)
|
|
102
106
|
|
|
103
107
|
except ImportError:
|
|
104
|
-
|
|
108
|
+
emit_error("Server catalog not available", message_group=group_id)
|
|
105
109
|
return False
|
|
106
110
|
except Exception as e:
|
|
107
111
|
logger.error(f"Error in interactive wizard: {e}")
|
|
108
|
-
|
|
112
|
+
emit_error(f"Wizard error: {e}", message_group=group_id)
|
|
109
113
|
return False
|
|
110
114
|
|
|
111
115
|
|
|
@@ -122,9 +126,7 @@ def interactive_server_selection(group_id: str):
|
|
|
122
126
|
|
|
123
127
|
servers = catalog.get_popular(10)
|
|
124
128
|
if not servers:
|
|
125
|
-
emit_info(
|
|
126
|
-
"[red]No servers available in catalog[/red]", message_group=group_id
|
|
127
|
-
)
|
|
129
|
+
emit_info("No servers available in catalog", message_group=group_id)
|
|
128
130
|
return None
|
|
129
131
|
|
|
130
132
|
emit_info("Popular MCP Servers:", message_group=group_id)
|
|
@@ -156,10 +158,10 @@ def interactive_server_selection(group_id: str):
|
|
|
156
158
|
if 0 <= index < len(servers):
|
|
157
159
|
return servers[index]
|
|
158
160
|
else:
|
|
159
|
-
|
|
161
|
+
emit_error("Invalid selection", message_group=group_id)
|
|
160
162
|
return None
|
|
161
163
|
except ValueError:
|
|
162
|
-
|
|
164
|
+
emit_error("Invalid input", message_group=group_id)
|
|
163
165
|
return None
|
|
164
166
|
|
|
165
167
|
except Exception as e:
|
|
@@ -215,7 +217,7 @@ def interactive_configure_server(
|
|
|
215
217
|
if env_vars:
|
|
216
218
|
emit_info("Environment Variables:", message_group=group_id)
|
|
217
219
|
for var, value in env_vars.items():
|
|
218
|
-
emit_info(f" {var}:
|
|
220
|
+
emit_info(f" {var}: ***", message_group=group_id)
|
|
219
221
|
|
|
220
222
|
if cmd_args:
|
|
221
223
|
emit_info("Command Line Arguments:", message_group=group_id)
|
|
@@ -234,7 +236,7 @@ def interactive_configure_server(
|
|
|
234
236
|
|
|
235
237
|
except Exception as e:
|
|
236
238
|
logger.error(f"Error configuring server: {e}")
|
|
237
|
-
|
|
239
|
+
emit_error(f"Configuration error: {e}", message_group=group_id)
|
|
238
240
|
return False
|
|
239
241
|
|
|
240
242
|
|
|
@@ -288,7 +290,7 @@ def install_server_from_catalog(
|
|
|
288
290
|
|
|
289
291
|
if not server_id:
|
|
290
292
|
emit_info(
|
|
291
|
-
"
|
|
293
|
+
"Failed to register server with manager",
|
|
292
294
|
message_group=group_id,
|
|
293
295
|
)
|
|
294
296
|
return False
|
|
@@ -314,7 +316,9 @@ def install_server_from_catalog(
|
|
|
314
316
|
json.dump(data, f, indent=2)
|
|
315
317
|
|
|
316
318
|
emit_info(
|
|
317
|
-
|
|
319
|
+
Text.from_markup(
|
|
320
|
+
f"[green]✓ Successfully installed server: {server_name}[/green]"
|
|
321
|
+
),
|
|
318
322
|
message_group=group_id,
|
|
319
323
|
)
|
|
320
324
|
emit_info(
|
|
@@ -326,5 +330,5 @@ def install_server_from_catalog(
|
|
|
326
330
|
|
|
327
331
|
except Exception as e:
|
|
328
332
|
logger.error(f"Error installing server: {e}")
|
|
329
|
-
|
|
333
|
+
emit_error(f"Installation failed: {e}", message_group=group_id)
|
|
330
334
|
return False
|
|
@@ -58,7 +58,7 @@ SETTING_DEFINITIONS: Dict[str, Dict] = {
|
|
|
58
58
|
"name": "Reasoning Effort",
|
|
59
59
|
"description": "Controls how much effort GPT-5 models spend on reasoning. Higher = more thorough but slower.",
|
|
60
60
|
"type": "choice",
|
|
61
|
-
"choices": ["low", "medium", "high"],
|
|
61
|
+
"choices": ["minimal", "low", "medium", "high", "xhigh"],
|
|
62
62
|
"default": "medium",
|
|
63
63
|
},
|
|
64
64
|
"verbosity": {
|
|
@@ -72,7 +72,7 @@ SETTING_DEFINITIONS: Dict[str, Dict] = {
|
|
|
72
72
|
"name": "Extended Thinking",
|
|
73
73
|
"description": "Enable Claude's extended thinking mode for complex reasoning tasks.",
|
|
74
74
|
"type": "boolean",
|
|
75
|
-
"default":
|
|
75
|
+
"default": True,
|
|
76
76
|
},
|
|
77
77
|
"budget_tokens": {
|
|
78
78
|
"name": "Thinking Budget (tokens)",
|
|
@@ -84,6 +84,12 @@ SETTING_DEFINITIONS: Dict[str, Dict] = {
|
|
|
84
84
|
"default": 10000,
|
|
85
85
|
"format": "{:.0f}",
|
|
86
86
|
},
|
|
87
|
+
"interleaved_thinking": {
|
|
88
|
+
"name": "Interleaved Thinking",
|
|
89
|
+
"description": "Enable thinking between tool calls (Claude 4 only: Opus 4.5, Opus 4.1, Opus 4, Sonnet 4). Adds beta header. WARNING: On Vertex/Bedrock, this FAILS for non-Claude 4 models!",
|
|
90
|
+
"type": "boolean",
|
|
91
|
+
"default": False,
|
|
92
|
+
},
|
|
87
93
|
}
|
|
88
94
|
|
|
89
95
|
|
|
@@ -93,6 +99,42 @@ def _load_all_model_names() -> List[str]:
|
|
|
93
99
|
return list(models_config.keys())
|
|
94
100
|
|
|
95
101
|
|
|
102
|
+
def _get_setting_choices(
|
|
103
|
+
setting_key: str, model_name: Optional[str] = None
|
|
104
|
+
) -> List[str]:
|
|
105
|
+
"""Get the available choices for a setting, filtered by model capabilities.
|
|
106
|
+
|
|
107
|
+
For reasoning_effort, only codex models support 'xhigh' - regular GPT-5.2
|
|
108
|
+
models are capped at 'high'.
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
setting_key: The setting name (e.g., 'reasoning_effort', 'verbosity')
|
|
112
|
+
model_name: Optional model name to filter choices for
|
|
113
|
+
|
|
114
|
+
Returns:
|
|
115
|
+
List of valid choices for this setting and model combination.
|
|
116
|
+
"""
|
|
117
|
+
setting_def = SETTING_DEFINITIONS.get(setting_key, {})
|
|
118
|
+
if setting_def.get("type") != "choice":
|
|
119
|
+
return []
|
|
120
|
+
|
|
121
|
+
base_choices = setting_def.get("choices", [])
|
|
122
|
+
|
|
123
|
+
# For reasoning_effort, filter 'xhigh' based on model support
|
|
124
|
+
if setting_key == "reasoning_effort" and model_name:
|
|
125
|
+
models_config = ModelFactory.load_config()
|
|
126
|
+
model_config = models_config.get(model_name, {})
|
|
127
|
+
|
|
128
|
+
# Check if model supports xhigh reasoning
|
|
129
|
+
supports_xhigh = model_config.get("supports_xhigh_reasoning", False)
|
|
130
|
+
|
|
131
|
+
if not supports_xhigh:
|
|
132
|
+
# Remove xhigh from choices for non-codex models
|
|
133
|
+
return [c for c in base_choices if c != "xhigh"]
|
|
134
|
+
|
|
135
|
+
return base_choices
|
|
136
|
+
|
|
137
|
+
|
|
96
138
|
class ModelSettingsMenu:
|
|
97
139
|
"""Interactive TUI for model settings configuration.
|
|
98
140
|
|
|
@@ -427,7 +469,8 @@ class ModelSettingsMenu:
|
|
|
427
469
|
if setting_def.get("type") == "choice":
|
|
428
470
|
lines.append(("bold", " Options:"))
|
|
429
471
|
lines.append(("", "\n"))
|
|
430
|
-
|
|
472
|
+
# Get filtered choices based on model capabilities
|
|
473
|
+
choices = _get_setting_choices(setting_key, self.selected_model)
|
|
431
474
|
lines.append(
|
|
432
475
|
(
|
|
433
476
|
"fg:ansibrightblack",
|
|
@@ -514,8 +557,11 @@ class ModelSettingsMenu:
|
|
|
514
557
|
if current is not None:
|
|
515
558
|
self.edit_value = current
|
|
516
559
|
elif setting_def.get("type") == "choice":
|
|
517
|
-
# For choice settings, start with the default
|
|
518
|
-
|
|
560
|
+
# For choice settings, start with the default (using filtered choices)
|
|
561
|
+
choices = _get_setting_choices(setting_key, self.selected_model)
|
|
562
|
+
self.edit_value = setting_def.get(
|
|
563
|
+
"default", choices[0] if choices else None
|
|
564
|
+
)
|
|
519
565
|
elif setting_def.get("type") == "boolean":
|
|
520
566
|
# For boolean settings, start with the default
|
|
521
567
|
self.edit_value = setting_def.get("default", False)
|
|
@@ -541,8 +587,8 @@ class ModelSettingsMenu:
|
|
|
541
587
|
setting_def = SETTING_DEFINITIONS[setting_key]
|
|
542
588
|
|
|
543
589
|
if setting_def.get("type") == "choice":
|
|
544
|
-
# Cycle through choices
|
|
545
|
-
choices =
|
|
590
|
+
# Cycle through filtered choices based on model capabilities
|
|
591
|
+
choices = _get_setting_choices(setting_key, self.selected_model)
|
|
546
592
|
current_idx = (
|
|
547
593
|
choices.index(self.edit_value) if self.edit_value in choices else 0
|
|
548
594
|
)
|
code_puppy/command_line/motd.py
CHANGED
|
@@ -1,9 +1,84 @@
|
|
|
1
|
+
import json
|
|
1
2
|
from typing import Iterable
|
|
2
3
|
|
|
3
4
|
from prompt_toolkit.completion import Completer, Completion
|
|
4
5
|
from prompt_toolkit.document import Document
|
|
5
6
|
|
|
6
7
|
|
|
8
|
+
def _get_json_agents_for_model(model_name: str) -> list:
|
|
9
|
+
"""Get JSON agents that have this model pinned in their JSON file."""
|
|
10
|
+
try:
|
|
11
|
+
from code_puppy.agents.json_agent import discover_json_agents
|
|
12
|
+
|
|
13
|
+
pinned = []
|
|
14
|
+
json_agents = discover_json_agents()
|
|
15
|
+
for agent_name, agent_path in json_agents.items():
|
|
16
|
+
try:
|
|
17
|
+
with open(agent_path, "r") as f:
|
|
18
|
+
agent_data = json.load(f)
|
|
19
|
+
if agent_data.get("model") == model_name:
|
|
20
|
+
pinned.append(agent_name)
|
|
21
|
+
except Exception:
|
|
22
|
+
continue
|
|
23
|
+
return pinned
|
|
24
|
+
except Exception:
|
|
25
|
+
return []
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _get_pinned_model_for_agent(agent_name: str) -> str | None:
|
|
29
|
+
"""Get the pinned model for an agent (config or JSON)."""
|
|
30
|
+
# Check config first (for built-in agents)
|
|
31
|
+
try:
|
|
32
|
+
from code_puppy.config import get_agent_pinned_model
|
|
33
|
+
|
|
34
|
+
pinned = get_agent_pinned_model(agent_name)
|
|
35
|
+
if pinned:
|
|
36
|
+
return pinned
|
|
37
|
+
except Exception:
|
|
38
|
+
pass
|
|
39
|
+
|
|
40
|
+
# Check if it's a JSON agent with a model key
|
|
41
|
+
try:
|
|
42
|
+
from code_puppy.agents.json_agent import discover_json_agents
|
|
43
|
+
|
|
44
|
+
json_agents = discover_json_agents()
|
|
45
|
+
if agent_name in json_agents:
|
|
46
|
+
with open(json_agents[agent_name], "r") as f:
|
|
47
|
+
agent_data = json.load(f)
|
|
48
|
+
return agent_data.get("model")
|
|
49
|
+
except Exception:
|
|
50
|
+
pass
|
|
51
|
+
|
|
52
|
+
return None
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _get_model_display_meta(model_name: str) -> str:
|
|
56
|
+
"""Get display meta for a model showing pinned agents."""
|
|
57
|
+
try:
|
|
58
|
+
from code_puppy.config import get_agents_pinned_to_model
|
|
59
|
+
|
|
60
|
+
pinned_agents = get_agents_pinned_to_model(model_name)
|
|
61
|
+
pinned_agents.extend(_get_json_agents_for_model(model_name))
|
|
62
|
+
pinned_agents = list(set(pinned_agents)) # Deduplicate
|
|
63
|
+
|
|
64
|
+
if pinned_agents:
|
|
65
|
+
agents_str = ", ".join(pinned_agents[:2])
|
|
66
|
+
if len(pinned_agents) > 2:
|
|
67
|
+
agents_str += "..."
|
|
68
|
+
return f"Pinned: [{agents_str}]"
|
|
69
|
+
except Exception:
|
|
70
|
+
pass
|
|
71
|
+
return "Model"
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _get_agent_display_meta(agent_name: str) -> str:
|
|
75
|
+
"""Get display meta for an agent showing pinned model."""
|
|
76
|
+
pinned_model = _get_pinned_model_for_agent(agent_name)
|
|
77
|
+
if pinned_model:
|
|
78
|
+
return f"→ {pinned_model}"
|
|
79
|
+
return "default"
|
|
80
|
+
|
|
81
|
+
|
|
7
82
|
def load_agent_names():
|
|
8
83
|
"""Load all available agent names (both built-in and JSON agents)."""
|
|
9
84
|
agents = set()
|
|
@@ -86,7 +161,7 @@ class PinCompleter(Completer):
|
|
|
86
161
|
agent_name,
|
|
87
162
|
start_position=-len(command_part),
|
|
88
163
|
display=agent_name,
|
|
89
|
-
display_meta=
|
|
164
|
+
display_meta=_get_agent_display_meta(agent_name),
|
|
90
165
|
)
|
|
91
166
|
|
|
92
167
|
# Case 2: Completing first argument (agent name)
|
|
@@ -115,7 +190,7 @@ class PinCompleter(Completer):
|
|
|
115
190
|
model_name,
|
|
116
191
|
start_position=0, # Insert at cursor position
|
|
117
192
|
display=model_name,
|
|
118
|
-
display_meta=
|
|
193
|
+
display_meta=_get_model_display_meta(model_name),
|
|
119
194
|
)
|
|
120
195
|
else:
|
|
121
196
|
# Still typing agent name, show agent completions
|
|
@@ -128,7 +203,7 @@ class PinCompleter(Completer):
|
|
|
128
203
|
agent_name,
|
|
129
204
|
start_position=start_pos,
|
|
130
205
|
display=agent_name,
|
|
131
|
-
display_meta=
|
|
206
|
+
display_meta=_get_agent_display_meta(agent_name),
|
|
132
207
|
)
|
|
133
208
|
|
|
134
209
|
# Case 3: Completing second argument (model name)
|
|
@@ -152,7 +227,7 @@ class PinCompleter(Completer):
|
|
|
152
227
|
model_name,
|
|
153
228
|
start_position=0,
|
|
154
229
|
display=model_name,
|
|
155
|
-
display_meta=
|
|
230
|
+
display_meta=_get_model_display_meta(model_name),
|
|
156
231
|
)
|
|
157
232
|
else:
|
|
158
233
|
# Filter based on what the user has typed
|
|
@@ -174,7 +249,7 @@ class PinCompleter(Completer):
|
|
|
174
249
|
model_name,
|
|
175
250
|
start_position=start_pos,
|
|
176
251
|
display=model_name,
|
|
177
|
-
display_meta=
|
|
252
|
+
display_meta=_get_model_display_meta(model_name),
|
|
178
253
|
)
|
|
179
254
|
|
|
180
255
|
# Case 4: Handle special case when user selected (unpin)
|
|
@@ -233,7 +308,7 @@ class UnpinCompleter(Completer):
|
|
|
233
308
|
agent_name,
|
|
234
309
|
start_position=-len(command_part),
|
|
235
310
|
display=agent_name,
|
|
236
|
-
display_meta=
|
|
311
|
+
display_meta=_get_agent_display_meta(agent_name),
|
|
237
312
|
)
|
|
238
313
|
elif len(tokens) == 1:
|
|
239
314
|
# Filter agent names based on partial input
|
|
@@ -247,7 +322,7 @@ class UnpinCompleter(Completer):
|
|
|
247
322
|
agent_name,
|
|
248
323
|
start_position=start_pos,
|
|
249
324
|
display=agent_name,
|
|
250
|
-
display_meta=
|
|
325
|
+
display_meta=_get_agent_display_meta(agent_name),
|
|
251
326
|
)
|
|
252
327
|
else:
|
|
253
328
|
# No completion for additional arguments
|
|
@@ -98,10 +98,9 @@ class SafeFileHistory(FileHistory):
|
|
|
98
98
|
except (UnicodeEncodeError, UnicodeDecodeError, OSError) as e:
|
|
99
99
|
# If we still can't write, log the error but don't crash
|
|
100
100
|
# This can happen with particularly malformed input
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
)
|
|
101
|
+
# Note: Using sys.stderr here intentionally - this is a low-level
|
|
102
|
+
# warning that shouldn't use the messaging system
|
|
103
|
+
sys.stderr.write(f"Warning: Could not save to command history: {e}\n")
|
|
105
104
|
|
|
106
105
|
|
|
107
106
|
class SetCompleter(Completer):
|
|
@@ -375,13 +374,20 @@ class AgentCompleter(Completer):
|
|
|
375
374
|
return
|
|
376
375
|
|
|
377
376
|
# Filter and yield agent completions
|
|
377
|
+
try:
|
|
378
|
+
from code_puppy.command_line.pin_command_completion import (
|
|
379
|
+
_get_agent_display_meta,
|
|
380
|
+
)
|
|
381
|
+
except ImportError:
|
|
382
|
+
_get_agent_display_meta = lambda x: "default" # noqa: E731
|
|
383
|
+
|
|
378
384
|
for agent_name in agent_names:
|
|
379
385
|
if agent_name.lower().startswith(text_after_trigger.lower()):
|
|
380
386
|
yield Completion(
|
|
381
387
|
agent_name,
|
|
382
388
|
start_position=start_position,
|
|
383
389
|
display=agent_name,
|
|
384
|
-
display_meta=
|
|
390
|
+
display_meta=_get_agent_display_meta(agent_name),
|
|
385
391
|
)
|
|
386
392
|
|
|
387
393
|
|
|
@@ -576,12 +582,26 @@ async def get_input_with_combined_completion(
|
|
|
576
582
|
# Ctrl+X keybinding - exit with KeyboardInterrupt for shell command cancellation
|
|
577
583
|
@bindings.add(Keys.ControlX)
|
|
578
584
|
def _(event):
|
|
579
|
-
|
|
585
|
+
try:
|
|
586
|
+
event.app.exit(exception=KeyboardInterrupt)
|
|
587
|
+
except Exception:
|
|
588
|
+
# Ignore "Return value already set" errors when exit was already called
|
|
589
|
+
# This happens when user presses multiple exit keys in quick succession
|
|
590
|
+
pass
|
|
580
591
|
|
|
581
592
|
# Escape keybinding - exit with KeyboardInterrupt
|
|
582
593
|
@bindings.add(Keys.Escape)
|
|
583
594
|
def _(event):
|
|
584
|
-
|
|
595
|
+
try:
|
|
596
|
+
event.app.exit(exception=KeyboardInterrupt)
|
|
597
|
+
except Exception:
|
|
598
|
+
# Ignore "Return value already set" errors when exit was already called
|
|
599
|
+
pass
|
|
600
|
+
|
|
601
|
+
# NOTE: We intentionally do NOT override Ctrl+C here.
|
|
602
|
+
# prompt_toolkit's default Ctrl+C handler properly resets the terminal state on Windows.
|
|
603
|
+
# Overriding it with event.app.exit(exception=KeyboardInterrupt) can leave the terminal
|
|
604
|
+
# in a bad state where characters cannot be typed. Let prompt_toolkit handle Ctrl+C natively.
|
|
585
605
|
|
|
586
606
|
# Toggle multiline with Alt+M
|
|
587
607
|
@bindings.add(Keys.Escape, "m")
|
|
@@ -589,14 +609,17 @@ async def get_input_with_combined_completion(
|
|
|
589
609
|
multiline["enabled"] = not multiline["enabled"]
|
|
590
610
|
status = "ON" if multiline["enabled"] else "OFF"
|
|
591
611
|
# Print status for user feedback (version-agnostic)
|
|
592
|
-
|
|
612
|
+
# Note: Using sys.stdout here for immediate feedback during input
|
|
613
|
+
sys.stdout.write(f"[multiline] {status}\n")
|
|
614
|
+
sys.stdout.flush()
|
|
593
615
|
|
|
594
616
|
# Also toggle multiline with F2 (more reliable across platforms)
|
|
595
617
|
@bindings.add("f2")
|
|
596
618
|
def _(event):
|
|
597
619
|
multiline["enabled"] = not multiline["enabled"]
|
|
598
620
|
status = "ON" if multiline["enabled"] else "OFF"
|
|
599
|
-
|
|
621
|
+
sys.stdout.write(f"[multiline] {status}\n")
|
|
622
|
+
sys.stdout.flush()
|
|
600
623
|
|
|
601
624
|
# Newline insert bindings — robust and explicit
|
|
602
625
|
# Ctrl+J (line feed) works in virtually all terminals; mark eager so it wins
|
|
@@ -246,6 +246,8 @@ def handle_dump_context_command(command: str) -> bool:
|
|
|
246
246
|
)
|
|
247
247
|
def handle_load_context_command(command: str) -> bool:
|
|
248
248
|
"""Load message history from a file."""
|
|
249
|
+
from rich.text import Text
|
|
250
|
+
|
|
249
251
|
from code_puppy.agents.agent_manager import get_current_agent
|
|
250
252
|
from code_puppy.config import rotate_autosave_id
|
|
251
253
|
from code_puppy.messaging import emit_error, emit_info, emit_success, emit_warning
|
|
@@ -278,12 +280,17 @@ def handle_load_context_command(command: str) -> bool:
|
|
|
278
280
|
# Rotate autosave id to avoid overwriting any existing autosave
|
|
279
281
|
try:
|
|
280
282
|
new_id = rotate_autosave_id()
|
|
281
|
-
autosave_info =
|
|
283
|
+
autosave_info = Text.from_markup(
|
|
284
|
+
f"\n[dim]Autosave session rotated to: {new_id}[/dim]"
|
|
285
|
+
)
|
|
282
286
|
except Exception:
|
|
283
|
-
autosave_info = ""
|
|
287
|
+
autosave_info = Text("")
|
|
284
288
|
|
|
285
|
-
|
|
289
|
+
# Build the success message with proper Text concatenation
|
|
290
|
+
success_msg = Text(
|
|
286
291
|
f"✅ Context loaded: {len(history)} messages ({total_tokens} tokens)\n"
|
|
287
|
-
f"📁 From: {session_path}
|
|
292
|
+
f"📁 From: {session_path}"
|
|
288
293
|
)
|
|
294
|
+
success_msg.append_text(autosave_info)
|
|
295
|
+
emit_success(success_msg)
|
|
289
296
|
return True
|