code-puppy 0.0.169__py3-none-any.whl โ 0.0.366__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 +7 -1
- code_puppy/agents/__init__.py +8 -8
- code_puppy/agents/agent_c_reviewer.py +155 -0
- code_puppy/agents/agent_code_puppy.py +9 -2
- code_puppy/agents/agent_code_reviewer.py +90 -0
- code_puppy/agents/agent_cpp_reviewer.py +132 -0
- code_puppy/agents/agent_creator_agent.py +48 -9
- code_puppy/agents/agent_golang_reviewer.py +151 -0
- code_puppy/agents/agent_javascript_reviewer.py +160 -0
- code_puppy/agents/agent_manager.py +146 -199
- code_puppy/agents/agent_pack_leader.py +383 -0
- code_puppy/agents/agent_planning.py +163 -0
- code_puppy/agents/agent_python_programmer.py +165 -0
- code_puppy/agents/agent_python_reviewer.py +90 -0
- code_puppy/agents/agent_qa_expert.py +163 -0
- code_puppy/agents/agent_qa_kitten.py +208 -0
- code_puppy/agents/agent_security_auditor.py +181 -0
- code_puppy/agents/agent_terminal_qa.py +323 -0
- code_puppy/agents/agent_typescript_reviewer.py +166 -0
- code_puppy/agents/base_agent.py +1713 -1
- code_puppy/agents/event_stream_handler.py +350 -0
- code_puppy/agents/json_agent.py +12 -1
- code_puppy/agents/pack/__init__.py +34 -0
- code_puppy/agents/pack/bloodhound.py +304 -0
- code_puppy/agents/pack/husky.py +321 -0
- code_puppy/agents/pack/retriever.py +393 -0
- code_puppy/agents/pack/shepherd.py +348 -0
- code_puppy/agents/pack/terrier.py +287 -0
- code_puppy/agents/pack/watchdog.py +367 -0
- code_puppy/agents/prompt_reviewer.py +145 -0
- code_puppy/agents/subagent_stream_handler.py +276 -0
- code_puppy/api/__init__.py +13 -0
- code_puppy/api/app.py +169 -0
- code_puppy/api/main.py +21 -0
- code_puppy/api/pty_manager.py +446 -0
- code_puppy/api/routers/__init__.py +12 -0
- code_puppy/api/routers/agents.py +36 -0
- code_puppy/api/routers/commands.py +217 -0
- code_puppy/api/routers/config.py +74 -0
- code_puppy/api/routers/sessions.py +232 -0
- code_puppy/api/templates/terminal.html +361 -0
- code_puppy/api/websocket.py +154 -0
- code_puppy/callbacks.py +174 -4
- code_puppy/chatgpt_codex_client.py +283 -0
- code_puppy/claude_cache_client.py +586 -0
- code_puppy/cli_runner.py +916 -0
- code_puppy/command_line/add_model_menu.py +1079 -0
- code_puppy/command_line/agent_menu.py +395 -0
- code_puppy/command_line/attachments.py +395 -0
- code_puppy/command_line/autosave_menu.py +605 -0
- code_puppy/command_line/clipboard.py +527 -0
- code_puppy/command_line/colors_menu.py +520 -0
- code_puppy/command_line/command_handler.py +233 -627
- code_puppy/command_line/command_registry.py +150 -0
- code_puppy/command_line/config_commands.py +715 -0
- code_puppy/command_line/core_commands.py +792 -0
- code_puppy/command_line/diff_menu.py +863 -0
- code_puppy/command_line/load_context_completion.py +15 -22
- code_puppy/command_line/mcp/base.py +1 -4
- code_puppy/command_line/mcp/catalog_server_installer.py +175 -0
- code_puppy/command_line/mcp/custom_server_form.py +688 -0
- code_puppy/command_line/mcp/custom_server_installer.py +195 -0
- code_puppy/command_line/mcp/edit_command.py +148 -0
- code_puppy/command_line/mcp/handler.py +9 -4
- code_puppy/command_line/mcp/help_command.py +6 -5
- code_puppy/command_line/mcp/install_command.py +16 -27
- code_puppy/command_line/mcp/install_menu.py +685 -0
- code_puppy/command_line/mcp/list_command.py +3 -3
- 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 +12 -4
- code_puppy/command_line/mcp/search_command.py +17 -11
- code_puppy/command_line/mcp/start_all_command.py +22 -13
- code_puppy/command_line/mcp/start_command.py +50 -31
- code_puppy/command_line/mcp/status_command.py +6 -7
- code_puppy/command_line/mcp/stop_all_command.py +11 -8
- code_puppy/command_line/mcp/stop_command.py +11 -10
- code_puppy/command_line/mcp/test_command.py +2 -2
- code_puppy/command_line/mcp/utils.py +1 -1
- code_puppy/command_line/mcp/wizard_utils.py +22 -18
- code_puppy/command_line/mcp_completion.py +174 -0
- code_puppy/command_line/model_picker_completion.py +89 -30
- code_puppy/command_line/model_settings_menu.py +884 -0
- code_puppy/command_line/motd.py +14 -8
- code_puppy/command_line/onboarding_slides.py +179 -0
- code_puppy/command_line/onboarding_wizard.py +340 -0
- code_puppy/command_line/pin_command_completion.py +329 -0
- code_puppy/command_line/prompt_toolkit_completion.py +626 -75
- code_puppy/command_line/session_commands.py +296 -0
- code_puppy/command_line/utils.py +54 -0
- code_puppy/config.py +1181 -51
- code_puppy/error_logging.py +118 -0
- code_puppy/gemini_code_assist.py +385 -0
- code_puppy/gemini_model.py +602 -0
- code_puppy/http_utils.py +220 -104
- code_puppy/keymap.py +128 -0
- code_puppy/main.py +5 -594
- code_puppy/{mcp โ mcp_}/__init__.py +17 -0
- code_puppy/{mcp โ mcp_}/async_lifecycle.py +35 -4
- code_puppy/{mcp โ mcp_}/blocking_startup.py +70 -43
- code_puppy/{mcp โ mcp_}/captured_stdio_server.py +2 -2
- code_puppy/{mcp โ mcp_}/config_wizard.py +5 -5
- code_puppy/{mcp โ mcp_}/dashboard.py +15 -6
- code_puppy/{mcp โ mcp_}/examples/retry_example.py +4 -1
- code_puppy/{mcp โ mcp_}/managed_server.py +66 -39
- code_puppy/{mcp โ mcp_}/manager.py +146 -52
- code_puppy/mcp_/mcp_logs.py +224 -0
- code_puppy/{mcp โ mcp_}/registry.py +6 -6
- code_puppy/{mcp โ mcp_}/server_registry_catalog.py +25 -8
- code_puppy/messaging/__init__.py +199 -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 +17 -48
- code_puppy/messaging/messages.py +500 -0
- code_puppy/messaging/queue_console.py +1 -24
- code_puppy/messaging/renderers.py +43 -146
- code_puppy/messaging/rich_renderer.py +1027 -0
- code_puppy/messaging/spinner/__init__.py +33 -5
- code_puppy/messaging/spinner/console_spinner.py +92 -52
- code_puppy/messaging/spinner/spinner_base.py +29 -0
- code_puppy/messaging/subagent_console.py +461 -0
- code_puppy/model_factory.py +686 -80
- code_puppy/model_utils.py +167 -0
- code_puppy/models.json +86 -104
- code_puppy/models_dev_api.json +1 -0
- code_puppy/models_dev_parser.py +592 -0
- code_puppy/plugins/__init__.py +164 -10
- code_puppy/plugins/antigravity_oauth/__init__.py +10 -0
- code_puppy/plugins/antigravity_oauth/accounts.py +406 -0
- code_puppy/plugins/antigravity_oauth/antigravity_model.py +704 -0
- code_puppy/plugins/antigravity_oauth/config.py +42 -0
- code_puppy/plugins/antigravity_oauth/constants.py +136 -0
- code_puppy/plugins/antigravity_oauth/oauth.py +478 -0
- code_puppy/plugins/antigravity_oauth/register_callbacks.py +406 -0
- code_puppy/plugins/antigravity_oauth/storage.py +271 -0
- code_puppy/plugins/antigravity_oauth/test_plugin.py +319 -0
- code_puppy/plugins/antigravity_oauth/token.py +167 -0
- code_puppy/plugins/antigravity_oauth/transport.py +767 -0
- code_puppy/plugins/antigravity_oauth/utils.py +169 -0
- code_puppy/plugins/chatgpt_oauth/__init__.py +8 -0
- code_puppy/plugins/chatgpt_oauth/config.py +52 -0
- code_puppy/plugins/chatgpt_oauth/oauth_flow.py +328 -0
- code_puppy/plugins/chatgpt_oauth/register_callbacks.py +94 -0
- code_puppy/plugins/chatgpt_oauth/test_plugin.py +293 -0
- code_puppy/plugins/chatgpt_oauth/utils.py +489 -0
- code_puppy/plugins/claude_code_oauth/README.md +167 -0
- code_puppy/plugins/claude_code_oauth/SETUP.md +93 -0
- code_puppy/plugins/claude_code_oauth/__init__.py +6 -0
- code_puppy/plugins/claude_code_oauth/config.py +50 -0
- code_puppy/plugins/claude_code_oauth/register_callbacks.py +308 -0
- code_puppy/plugins/claude_code_oauth/test_plugin.py +283 -0
- code_puppy/plugins/claude_code_oauth/utils.py +518 -0
- code_puppy/plugins/customizable_commands/__init__.py +0 -0
- code_puppy/plugins/customizable_commands/register_callbacks.py +169 -0
- code_puppy/plugins/example_custom_command/README.md +280 -0
- code_puppy/plugins/example_custom_command/register_callbacks.py +51 -0
- code_puppy/plugins/file_permission_handler/__init__.py +4 -0
- code_puppy/plugins/file_permission_handler/register_callbacks.py +523 -0
- code_puppy/plugins/frontend_emitter/__init__.py +25 -0
- code_puppy/plugins/frontend_emitter/emitter.py +121 -0
- code_puppy/plugins/frontend_emitter/register_callbacks.py +261 -0
- code_puppy/plugins/oauth_puppy_html.py +228 -0
- code_puppy/plugins/shell_safety/__init__.py +6 -0
- code_puppy/plugins/shell_safety/agent_shell_safety.py +69 -0
- code_puppy/plugins/shell_safety/command_cache.py +156 -0
- code_puppy/plugins/shell_safety/register_callbacks.py +202 -0
- code_puppy/prompts/antigravity_system_prompt.md +1 -0
- code_puppy/prompts/codex_system_prompt.md +310 -0
- code_puppy/pydantic_patches.py +131 -0
- code_puppy/reopenable_async_client.py +8 -8
- code_puppy/round_robin_model.py +10 -15
- code_puppy/session_storage.py +294 -0
- code_puppy/status_display.py +21 -4
- code_puppy/summarization_agent.py +52 -14
- code_puppy/terminal_utils.py +418 -0
- code_puppy/tools/__init__.py +139 -6
- code_puppy/tools/agent_tools.py +548 -49
- code_puppy/tools/browser/__init__.py +37 -0
- code_puppy/tools/browser/browser_control.py +289 -0
- code_puppy/tools/browser/browser_interactions.py +545 -0
- code_puppy/tools/browser/browser_locators.py +640 -0
- code_puppy/tools/browser/browser_manager.py +316 -0
- code_puppy/tools/browser/browser_navigation.py +251 -0
- code_puppy/tools/browser/browser_screenshot.py +179 -0
- code_puppy/tools/browser/browser_scripts.py +462 -0
- code_puppy/tools/browser/browser_workflows.py +221 -0
- code_puppy/tools/browser/chromium_terminal_manager.py +259 -0
- code_puppy/tools/browser/terminal_command_tools.py +521 -0
- code_puppy/tools/browser/terminal_screenshot_tools.py +556 -0
- code_puppy/tools/browser/terminal_tools.py +525 -0
- code_puppy/tools/command_runner.py +941 -153
- code_puppy/tools/common.py +1146 -6
- code_puppy/tools/display.py +84 -0
- code_puppy/tools/file_modifications.py +288 -89
- code_puppy/tools/file_operations.py +352 -266
- code_puppy/tools/subagent_context.py +158 -0
- code_puppy/uvx_detection.py +242 -0
- code_puppy/version_checker.py +30 -11
- code_puppy-0.0.366.data/data/code_puppy/models.json +110 -0
- code_puppy-0.0.366.data/data/code_puppy/models_dev_api.json +1 -0
- {code_puppy-0.0.169.dist-info โ code_puppy-0.0.366.dist-info}/METADATA +184 -67
- code_puppy-0.0.366.dist-info/RECORD +217 -0
- {code_puppy-0.0.169.dist-info โ code_puppy-0.0.366.dist-info}/WHEEL +1 -1
- {code_puppy-0.0.169.dist-info โ code_puppy-0.0.366.dist-info}/entry_points.txt +1 -0
- code_puppy/agent.py +0 -231
- code_puppy/agents/agent_orchestrator.json +0 -26
- code_puppy/agents/runtime_manager.py +0 -272
- code_puppy/command_line/mcp/add_command.py +0 -183
- code_puppy/command_line/meta_command_handler.py +0 -153
- code_puppy/message_history_processor.py +0 -490
- code_puppy/messaging/spinner/textual_spinner.py +0 -101
- code_puppy/state_management.py +0 -200
- code_puppy/tui/__init__.py +0 -10
- code_puppy/tui/app.py +0 -986
- code_puppy/tui/components/__init__.py +0 -21
- code_puppy/tui/components/chat_view.py +0 -550
- code_puppy/tui/components/command_history_modal.py +0 -218
- code_puppy/tui/components/copy_button.py +0 -139
- code_puppy/tui/components/custom_widgets.py +0 -63
- code_puppy/tui/components/human_input_modal.py +0 -175
- code_puppy/tui/components/input_area.py +0 -167
- code_puppy/tui/components/sidebar.py +0 -309
- code_puppy/tui/components/status_bar.py +0 -182
- code_puppy/tui/messages.py +0 -27
- code_puppy/tui/models/__init__.py +0 -8
- code_puppy/tui/models/chat_message.py +0 -25
- code_puppy/tui/models/command_history.py +0 -89
- code_puppy/tui/models/enums.py +0 -24
- code_puppy/tui/screens/__init__.py +0 -15
- code_puppy/tui/screens/help.py +0 -130
- code_puppy/tui/screens/mcp_install_wizard.py +0 -803
- code_puppy/tui/screens/settings.py +0 -290
- code_puppy/tui/screens/tools.py +0 -74
- code_puppy-0.0.169.data/data/code_puppy/models.json +0 -128
- code_puppy-0.0.169.dist-info/RECORD +0 -112
- /code_puppy/{mcp โ mcp_}/circuit_breaker.py +0 -0
- /code_puppy/{mcp โ mcp_}/error_isolation.py +0 -0
- /code_puppy/{mcp โ mcp_}/health_monitor.py +0 -0
- /code_puppy/{mcp โ mcp_}/retry_manager.py +0 -0
- /code_puppy/{mcp โ mcp_}/status_tracker.py +0 -0
- /code_puppy/{mcp โ mcp_}/system_tools.py +0 -0
- {code_puppy-0.0.169.dist-info โ code_puppy-0.0.366.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
"""Callback registration for frontend event emission.
|
|
2
|
+
|
|
3
|
+
This module registers callbacks for various agent events and emits them
|
|
4
|
+
to subscribed WebSocket handlers via the emitter module.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
import time
|
|
9
|
+
from typing import Any, Dict, Optional
|
|
10
|
+
|
|
11
|
+
from code_puppy.callbacks import register_callback
|
|
12
|
+
from code_puppy.plugins.frontend_emitter.emitter import emit_event
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
async def on_pre_tool_call(
|
|
18
|
+
tool_name: str, tool_args: Dict[str, Any], context: Any = None
|
|
19
|
+
) -> None:
|
|
20
|
+
"""Emit an event when a tool call starts.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
tool_name: Name of the tool being called
|
|
24
|
+
tool_args: Arguments being passed to the tool
|
|
25
|
+
context: Optional context data for the tool call
|
|
26
|
+
"""
|
|
27
|
+
try:
|
|
28
|
+
emit_event(
|
|
29
|
+
"tool_call_start",
|
|
30
|
+
{
|
|
31
|
+
"tool_name": tool_name,
|
|
32
|
+
"tool_args": _sanitize_args(tool_args),
|
|
33
|
+
"start_time": time.time(),
|
|
34
|
+
},
|
|
35
|
+
)
|
|
36
|
+
logger.debug(f"Emitted tool_call_start for {tool_name}")
|
|
37
|
+
except Exception as e:
|
|
38
|
+
logger.error(f"Failed to emit pre_tool_call event: {e}")
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
async def on_post_tool_call(
|
|
42
|
+
tool_name: str,
|
|
43
|
+
tool_args: Dict[str, Any],
|
|
44
|
+
result: Any,
|
|
45
|
+
duration_ms: float,
|
|
46
|
+
context: Any = None,
|
|
47
|
+
) -> None:
|
|
48
|
+
"""Emit an event when a tool call completes.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
tool_name: Name of the tool that was called
|
|
52
|
+
tool_args: Arguments that were passed to the tool
|
|
53
|
+
result: The result returned by the tool
|
|
54
|
+
duration_ms: Execution time in milliseconds
|
|
55
|
+
context: Optional context data for the tool call
|
|
56
|
+
"""
|
|
57
|
+
try:
|
|
58
|
+
emit_event(
|
|
59
|
+
"tool_call_complete",
|
|
60
|
+
{
|
|
61
|
+
"tool_name": tool_name,
|
|
62
|
+
"tool_args": _sanitize_args(tool_args),
|
|
63
|
+
"duration_ms": duration_ms,
|
|
64
|
+
"success": _is_successful_result(result),
|
|
65
|
+
"result_summary": _summarize_result(result),
|
|
66
|
+
},
|
|
67
|
+
)
|
|
68
|
+
logger.debug(
|
|
69
|
+
f"Emitted tool_call_complete for {tool_name} ({duration_ms:.2f}ms)"
|
|
70
|
+
)
|
|
71
|
+
except Exception as e:
|
|
72
|
+
logger.error(f"Failed to emit post_tool_call event: {e}")
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
async def on_stream_event(
|
|
76
|
+
event_type: str, event_data: Any, agent_session_id: Optional[str] = None
|
|
77
|
+
) -> None:
|
|
78
|
+
"""Emit streaming events from the agent.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
event_type: Type of the streaming event
|
|
82
|
+
event_data: Data associated with the event
|
|
83
|
+
agent_session_id: Optional session ID of the agent emitting the event
|
|
84
|
+
"""
|
|
85
|
+
try:
|
|
86
|
+
emit_event(
|
|
87
|
+
"stream_event",
|
|
88
|
+
{
|
|
89
|
+
"event_type": event_type,
|
|
90
|
+
"event_data": _sanitize_event_data(event_data),
|
|
91
|
+
"agent_session_id": agent_session_id,
|
|
92
|
+
},
|
|
93
|
+
)
|
|
94
|
+
logger.debug(f"Emitted stream_event: {event_type}")
|
|
95
|
+
except Exception as e:
|
|
96
|
+
logger.error(f"Failed to emit stream_event: {e}")
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
async def on_invoke_agent(*args: Any, **kwargs: Any) -> None:
|
|
100
|
+
"""Emit an event when an agent is invoked.
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
*args: Positional arguments from the invoke_agent callback
|
|
104
|
+
**kwargs: Keyword arguments from the invoke_agent callback
|
|
105
|
+
"""
|
|
106
|
+
try:
|
|
107
|
+
# Extract relevant info from args/kwargs
|
|
108
|
+
agent_info = {
|
|
109
|
+
"agent_name": kwargs.get("agent_name") or (args[0] if args else None),
|
|
110
|
+
"session_id": kwargs.get("session_id"),
|
|
111
|
+
"prompt_preview": _truncate_string(
|
|
112
|
+
kwargs.get("prompt") or (args[1] if len(args) > 1 else None),
|
|
113
|
+
max_length=200,
|
|
114
|
+
),
|
|
115
|
+
}
|
|
116
|
+
emit_event("agent_invoked", agent_info)
|
|
117
|
+
logger.debug(f"Emitted agent_invoked: {agent_info.get('agent_name')}")
|
|
118
|
+
except Exception as e:
|
|
119
|
+
logger.error(f"Failed to emit invoke_agent event: {e}")
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def _sanitize_args(args: Dict[str, Any]) -> Dict[str, Any]:
|
|
123
|
+
"""Sanitize tool arguments for safe emission.
|
|
124
|
+
|
|
125
|
+
Truncates large values and removes potentially sensitive data.
|
|
126
|
+
|
|
127
|
+
Args:
|
|
128
|
+
args: The raw tool arguments
|
|
129
|
+
|
|
130
|
+
Returns:
|
|
131
|
+
Sanitized arguments safe for emission
|
|
132
|
+
"""
|
|
133
|
+
if not isinstance(args, dict):
|
|
134
|
+
return {}
|
|
135
|
+
|
|
136
|
+
sanitized: Dict[str, Any] = {}
|
|
137
|
+
for key, value in args.items():
|
|
138
|
+
if isinstance(value, str):
|
|
139
|
+
sanitized[key] = _truncate_string(value, max_length=500)
|
|
140
|
+
elif isinstance(value, (int, float, bool, type(None))):
|
|
141
|
+
sanitized[key] = value
|
|
142
|
+
elif isinstance(value, (list, dict)):
|
|
143
|
+
# Just indicate the type and length for complex types
|
|
144
|
+
sanitized[key] = f"<{type(value).__name__}[{len(value)}]>"
|
|
145
|
+
else:
|
|
146
|
+
sanitized[key] = f"<{type(value).__name__}>"
|
|
147
|
+
|
|
148
|
+
return sanitized
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def _sanitize_event_data(data: Any) -> Any:
|
|
152
|
+
"""Sanitize event data for safe emission.
|
|
153
|
+
|
|
154
|
+
Args:
|
|
155
|
+
data: The raw event data
|
|
156
|
+
|
|
157
|
+
Returns:
|
|
158
|
+
Sanitized data safe for emission
|
|
159
|
+
"""
|
|
160
|
+
if data is None:
|
|
161
|
+
return None
|
|
162
|
+
|
|
163
|
+
if isinstance(data, str):
|
|
164
|
+
return _truncate_string(data, max_length=1000)
|
|
165
|
+
|
|
166
|
+
if isinstance(data, (int, float, bool)):
|
|
167
|
+
return data
|
|
168
|
+
|
|
169
|
+
if isinstance(data, dict):
|
|
170
|
+
return {k: _sanitize_event_data(v) for k, v in list(data.items())[:20]}
|
|
171
|
+
|
|
172
|
+
if isinstance(data, (list, tuple)):
|
|
173
|
+
return [_sanitize_event_data(item) for item in data[:20]]
|
|
174
|
+
|
|
175
|
+
return f"<{type(data).__name__}>"
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def _is_successful_result(result: Any) -> bool:
|
|
179
|
+
"""Determine if a tool result indicates success.
|
|
180
|
+
|
|
181
|
+
Args:
|
|
182
|
+
result: The tool result
|
|
183
|
+
|
|
184
|
+
Returns:
|
|
185
|
+
True if the result appears successful
|
|
186
|
+
"""
|
|
187
|
+
if result is None:
|
|
188
|
+
return True # No result often means success
|
|
189
|
+
|
|
190
|
+
if isinstance(result, dict):
|
|
191
|
+
# Check for error indicators
|
|
192
|
+
if result.get("error"):
|
|
193
|
+
return False
|
|
194
|
+
if result.get("success") is False:
|
|
195
|
+
return False
|
|
196
|
+
return True
|
|
197
|
+
|
|
198
|
+
if isinstance(result, bool):
|
|
199
|
+
return result
|
|
200
|
+
|
|
201
|
+
return True # Default to success
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def _summarize_result(result: Any) -> str:
|
|
205
|
+
"""Create a brief summary of a tool result.
|
|
206
|
+
|
|
207
|
+
Args:
|
|
208
|
+
result: The tool result
|
|
209
|
+
|
|
210
|
+
Returns:
|
|
211
|
+
A string summary of the result
|
|
212
|
+
"""
|
|
213
|
+
if result is None:
|
|
214
|
+
return "<no result>"
|
|
215
|
+
|
|
216
|
+
if isinstance(result, str):
|
|
217
|
+
return _truncate_string(result, max_length=200)
|
|
218
|
+
|
|
219
|
+
if isinstance(result, dict):
|
|
220
|
+
if "error" in result:
|
|
221
|
+
return f"Error: {_truncate_string(str(result['error']), max_length=100)}"
|
|
222
|
+
if "message" in result:
|
|
223
|
+
return _truncate_string(str(result["message"]), max_length=100)
|
|
224
|
+
return f"<dict with {len(result)} keys>"
|
|
225
|
+
|
|
226
|
+
if isinstance(result, (list, tuple)):
|
|
227
|
+
return f"<{type(result).__name__}[{len(result)}]>"
|
|
228
|
+
|
|
229
|
+
return _truncate_string(str(result), max_length=200)
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def _truncate_string(value: Any, max_length: int = 100) -> Optional[str]:
|
|
233
|
+
"""Truncate a string value if it exceeds max_length.
|
|
234
|
+
|
|
235
|
+
Args:
|
|
236
|
+
value: The value to truncate (will be converted to str)
|
|
237
|
+
max_length: Maximum length before truncation
|
|
238
|
+
|
|
239
|
+
Returns:
|
|
240
|
+
Truncated string or None if value is None
|
|
241
|
+
"""
|
|
242
|
+
if value is None:
|
|
243
|
+
return None
|
|
244
|
+
|
|
245
|
+
s = str(value)
|
|
246
|
+
if len(s) > max_length:
|
|
247
|
+
return s[: max_length - 3] + "..."
|
|
248
|
+
return s
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def register() -> None:
|
|
252
|
+
"""Register all frontend emitter callbacks."""
|
|
253
|
+
register_callback("pre_tool_call", on_pre_tool_call)
|
|
254
|
+
register_callback("post_tool_call", on_post_tool_call)
|
|
255
|
+
register_callback("stream_event", on_stream_event)
|
|
256
|
+
register_callback("invoke_agent", on_invoke_agent)
|
|
257
|
+
logger.debug("Frontend emitter callbacks registered")
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
# Auto-register callbacks when this module is imported
|
|
261
|
+
register()
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
"""Shared HTML templates drenched in ridiculous puppy-fueled OAuth theatrics."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Optional, Tuple
|
|
6
|
+
|
|
7
|
+
CLAUDE_LOGO_URL = "https://voideditor.com/claude-icon.png"
|
|
8
|
+
CHATGPT_LOGO_URL = (
|
|
9
|
+
"https://freelogopng.com/images/all_img/1681038325chatgpt-logo-transparent.png"
|
|
10
|
+
)
|
|
11
|
+
GEMINI_LOGO_URL = "https://upload.wikimedia.org/wikipedia/commons/thumb/8/8a/Google_Gemini_logo.svg/512px-Google_Gemini_logo.svg.png"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def oauth_success_html(service_name: str, extra_message: Optional[str] = None) -> str:
|
|
15
|
+
"""Return an over-the-top puppy celebration HTML page with artillery effects."""
|
|
16
|
+
clean_service = service_name.strip() or "OAuth"
|
|
17
|
+
detail = f"<p class='detail'>๐พ {extra_message} ๐พ</p>" if extra_message else ""
|
|
18
|
+
projectile, rival_url, rival_alt, target_modifier = _service_targets(clean_service)
|
|
19
|
+
target_classes = "target" if not target_modifier else f"target {target_modifier}"
|
|
20
|
+
return (
|
|
21
|
+
"<!DOCTYPE html>"
|
|
22
|
+
"<html lang='en'><head><meta charset='utf-8'>"
|
|
23
|
+
"<title>Puppy Paw-ty Success</title>"
|
|
24
|
+
"<style>"
|
|
25
|
+
"html,body{margin:0;padding:0;height:100%;overflow:hidden;font-family:'Inter',-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;background:linear-gradient(135deg,#0f172a 0%,#111827 45%,#1f2937 100%);color:#e5e7eb;}"
|
|
26
|
+
"body{display:flex;align-items:center;justify-content:center;}"
|
|
27
|
+
".kennel{position:relative;width:90%;max-width:880px;padding:60px;background:rgba(15,23,42,0.72);border-radius:32px;backdrop-filter:blur(14px);box-shadow:0 30px 90px rgba(8,11,18,0.7);text-align:center;border:1px solid rgba(148,163,184,0.25);}"
|
|
28
|
+
"h1{font-size:3.4em;margin:0;color:#f1f5f9;text-shadow:0 14px 40px rgba(8,11,18,0.55);letter-spacing:1px;}"
|
|
29
|
+
"p{font-size:1.25em;margin:16px 0;color:#cbd5f5;}"
|
|
30
|
+
".detail{font-size:1.1em;opacity:0.9;}"
|
|
31
|
+
".mega{display:inline-block;font-size:1.35em;margin-top:14px;color:#f97316;}"
|
|
32
|
+
".confetti{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);width:200%;height:200%;pointer-events:none;mix-blend-mode:screen;}"
|
|
33
|
+
".confetti span{position:absolute;font-size:3.4em;animation:floaty 6s ease-in-out infinite;color:#fbbf24;}"
|
|
34
|
+
"@keyframes floaty{0%,100%{transform:translate3d(0,0,0) rotate(0deg);}35%{transform:translate3d(0,-70px,0) rotate(10deg);}65%{transform:translate3d(0,-90px,0) rotate(-12deg);}90%{transform:translate3d(0,-60px,0) rotate(6deg);}}"
|
|
35
|
+
".confetti span:nth-child(odd){animation-duration:7.2s;}"
|
|
36
|
+
".confetti span:nth-child(3n){animation-duration:8.6s;}"
|
|
37
|
+
".confetti span:nth-child(4n){animation-duration:5.9s;}"
|
|
38
|
+
".confetti span:nth-child(5n){animation-duration:9.4s;}"
|
|
39
|
+
".artillery{position:absolute;bottom:12%;left:0;width:100%;max-width:1100px;height:240px;pointer-events:none;overflow:visible;}"
|
|
40
|
+
".artillery .cannon{position:absolute;bottom:0;font-size:3.4em;color:#f97316;filter:drop-shadow(0 12px 32px rgba(249,115,22,0.45));}"
|
|
41
|
+
".artillery .cannon.left{left:4%;}"
|
|
42
|
+
".artillery .cannon.right{left:12%;transform:rotate(-4deg);}"
|
|
43
|
+
".artillery .shell{position:absolute;left:10%;font-size:2.6em;animation:strafe 2.6s ease-out infinite;color:#facc15;text-shadow:0 0 14px rgba(250,204,21,0.45);}"
|
|
44
|
+
"@keyframes strafe{0%{left:10%;opacity:1;}60%{left:72%;opacity:1;}100%{left:82%;opacity:0;}}"
|
|
45
|
+
".target{position:absolute;top:175px;right:-10%;width:220px;filter:drop-shadow(0 24px 46px rgba(8,11,18,0.72));animation:targetShake 1.9s ease-in-out infinite;}"
|
|
46
|
+
".target img{width:200px;height:auto;border-radius:18px;background:#0f172a;padding:16px;border:1px solid rgba(148,163,184,0.35);}"
|
|
47
|
+
".target.invert img{filter:brightness(1.2) saturate(1.15);background:rgba(15,23,42,0.9);}"
|
|
48
|
+
"@keyframes targetShake{0%,100%{transform:rotate(0deg) scale(1);}30%{transform:rotate(-4deg) scale(1.05);}60%{transform:rotate(3deg) scale(0.97);}85%{transform:rotate(-2deg) scale(1.04);}}"
|
|
49
|
+
".target::after{content:'';position:absolute;top:50%;left:50%;width:220px;height:220px;border-radius:50%;background:radial-gradient(circle,rgba(248,113,113,0.35)0%,rgba(248,113,113,0)70%);transform:translate(-50%,-50%) scale(0);animation:impact 2.6s ease-out infinite;opacity:0;mix-blend-mode:screen;}"
|
|
50
|
+
"@keyframes impact{0%,60%{transform:translate(-50%,-50%) scale(0);opacity:0;}70%{transform:translate(-50%,-50%) scale(1.2);opacity:1;}100%{transform:translate(-50%,-50%) scale(1.5);opacity:0;}}"
|
|
51
|
+
"</style>"
|
|
52
|
+
"</head><body>"
|
|
53
|
+
"<div class='kennel'>"
|
|
54
|
+
"<div class='confetti'>"
|
|
55
|
+
+ "".join(
|
|
56
|
+
f"<span style='left:{left}%;top:{top}%;animation-delay:{delay}s;'>{emoji}</span>"
|
|
57
|
+
for left, top, delay, emoji in _SUCCESS_PUPPIES
|
|
58
|
+
)
|
|
59
|
+
+ "</div>"
|
|
60
|
+
f"<h1>๐ถโก {clean_service} OAuth Complete โก๐ถ</h1>"
|
|
61
|
+
"<p class='mega'>Puppy squad delivered the token payload without mercy.</p>"
|
|
62
|
+
f"{detail}"
|
|
63
|
+
f"<p>๐ฃ Puppies are bombarding the {rival_alt} defenses! ๐ฃ</p>"
|
|
64
|
+
"<p>๐ This window will auto-close faster than a corgi zoomie. ๐</p>"
|
|
65
|
+
"<p class='mega'>Keep the artillery firing โ the rivals never stood a chance.</p>"
|
|
66
|
+
f"<div class='{target_classes}'><img src='{rival_url}' alt='{rival_alt}'></div>"
|
|
67
|
+
"<div class='artillery'>" + _build_artillery(projectile) + "</div>"
|
|
68
|
+
"</div>"
|
|
69
|
+
"<script>setTimeout(()=>window.close(),3500);</script>"
|
|
70
|
+
"</body></html>"
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def oauth_failure_html(service_name: str, reason: str) -> str:
|
|
75
|
+
"""Return a dramatic puppy-tragedy HTML page for OAuth sadness."""
|
|
76
|
+
clean_service = service_name.strip() or "OAuth"
|
|
77
|
+
clean_reason = reason.strip() or "Something went wrong with the treats"
|
|
78
|
+
projectile, rival_url, rival_alt, target_modifier = _service_targets(clean_service)
|
|
79
|
+
target_classes = "target" if not target_modifier else f"target {target_modifier}"
|
|
80
|
+
return (
|
|
81
|
+
"<!DOCTYPE html>"
|
|
82
|
+
"<html lang='en'><head><meta charset='utf-8'>"
|
|
83
|
+
"<title>Puppy Tears</title>"
|
|
84
|
+
"<style>"
|
|
85
|
+
"html,body{margin:0;padding:0;height:100%;overflow:hidden;font-family:'Inter',-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;background:linear-gradient(160deg,#101827 0%,#0b1120 100%);color:#e2e8f0;}"
|
|
86
|
+
"body{display:flex;align-items:center;justify-content:center;}"
|
|
87
|
+
".kennel{position:relative;width:90%;max-width:780px;padding:55px;background:rgba(10,13,23,0.78);border-radius:30px;box-shadow:0 26px 80px rgba(2,6,23,0.78);text-align:center;border:1px solid rgba(71,85,105,0.35);}"
|
|
88
|
+
"h1{font-size:3em;margin:0 0 14px;text-shadow:0 16px 36px rgba(15,23,42,0.7);color:#f87171;}"
|
|
89
|
+
"p{font-size:1.2em;margin:14px 0;line-height:1.6;color:#cbd5f5;}"
|
|
90
|
+
".howl{font-size:1.35em;margin:18px 0;color:#fda4af;}"
|
|
91
|
+
".tearstorm{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);width:190%;height:190%;pointer-events:none;mix-blend-mode:screen;}"
|
|
92
|
+
".tearstorm span{position:absolute;font-size:3.2em;animation:weep 4.8s ease-in-out infinite;color:#60a5fa;}"
|
|
93
|
+
"@keyframes weep{0%{transform:translate3d(0,-10px,0) rotate(-6deg);opacity:0.85;}35%{transform:translate3d(-20px,18px,0) rotate(8deg);opacity:1;}65%{transform:translate3d(25px,28px,0) rotate(-12deg);opacity:0.8;}100%{transform:translate3d(0,60px,0) rotate(0deg);opacity:0;}}"
|
|
94
|
+
".tearstorm span:nth-child(odd){animation-duration:5.8s;}"
|
|
95
|
+
".tearstorm span:nth-child(3n){animation-duration:6.4s;}"
|
|
96
|
+
".tearstorm span:nth-child(4n){animation-duration:7.3s;}"
|
|
97
|
+
".buttons{margin-top:26px;}"
|
|
98
|
+
".buttons a{display:inline-block;margin:6px 12px;padding:12px 28px;border-radius:999px;background:rgba(59,130,246,0.16);color:#bfdbfe;text-decoration:none;font-weight:600;border:1px solid rgba(96,165,250,0.4);transition:all 0.3s;}"
|
|
99
|
+
".buttons a:hover{background:rgba(96,165,250,0.28);transform:translateY(-2px);}"
|
|
100
|
+
".battlefield{position:absolute;bottom:-25px;left:0;width:100%;max-width:960px;height:220px;pointer-events:none;}"
|
|
101
|
+
".battlefield .shell{position:absolute;left:10%;font-size:2.4em;color:#38bdf8;text-shadow:0 0 12px rgba(56,189,248,0.45);animation:strafeSad 3s ease-out infinite;}"
|
|
102
|
+
"@keyframes strafeSad{0%{left:10%;opacity:1;}65%{left:70%;opacity:1;}100%{left:80%;opacity:0;}}"
|
|
103
|
+
".battlefield .target{position:absolute;top:16px;right:6%;width:220px;filter:drop-shadow(0 20px 44px rgba(2,6,23,0.78));animation:sway 2s ease-in-out infinite;}"
|
|
104
|
+
".battlefield .target img{width:200px;height:auto;border-radius:18px;background:#0b1120;padding:16px;border:1px solid rgba(96,165,250,0.4);}"
|
|
105
|
+
".battlefield .target.invert img{filter:brightness(1.2) saturate(1.15);background:rgba(15,23,42,0.9);}"
|
|
106
|
+
"@keyframes sway{0%,100%{transform:rotate(0deg);}40%{transform:rotate(-6deg);}70%{transform:rotate(5deg);}}"
|
|
107
|
+
"</style>"
|
|
108
|
+
"</head><body>"
|
|
109
|
+
"<div class='kennel'>"
|
|
110
|
+
"<div class='tearstorm'>"
|
|
111
|
+
+ "".join(
|
|
112
|
+
f"<span style='left:{left}%;top:{top}%;animation-delay:{delay}s;'>{emoji}</span>"
|
|
113
|
+
for left, top, delay, emoji in _FAILURE_PUPPIES
|
|
114
|
+
)
|
|
115
|
+
+ "</div>"
|
|
116
|
+
f"<h1>๐๐ถ {clean_service} OAuth Whoopsie ๐</h1>"
|
|
117
|
+
"<p class='howl'>๐ญ Puppy artillery jammed! Someone cut the firing wire.</p>"
|
|
118
|
+
f"<p>{clean_reason}</p>"
|
|
119
|
+
"<p>๐ง A thousand doggy eyes are welling up. Try again from Code Puppy! ๐ง</p>"
|
|
120
|
+
f"<p>Re-calibrate the {projectile} barrage and slam it into the {rival_alt} wall.</p>"
|
|
121
|
+
"<div class='buttons'>"
|
|
122
|
+
"<a href='https://codepuppy.dev' target='_blank'>Adopt more puppies</a>"
|
|
123
|
+
"<a href='https://github.com/code-puppy/code_puppy' target='_blank'>Send treats</a>"
|
|
124
|
+
"</div>"
|
|
125
|
+
"<div class='battlefield'>"
|
|
126
|
+
+ _build_artillery(projectile, shells_only=True)
|
|
127
|
+
+ f"<div class='{target_classes}'><img src='{rival_url}' alt='{rival_alt}'></div>"
|
|
128
|
+
+ "</div>"
|
|
129
|
+
"</div>"
|
|
130
|
+
"</body></html>"
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
_SUCCESS_PUPPIES = (
|
|
135
|
+
(5, 12, 0.0, "๐ถ"),
|
|
136
|
+
(18, 28, 0.2, "๐"),
|
|
137
|
+
(32, 6, 1.1, "๐ฉ"),
|
|
138
|
+
(46, 18, 0.5, "๐ฆฎ"),
|
|
139
|
+
(62, 9, 0.8, "๐โ๐ฆบ"),
|
|
140
|
+
(76, 22, 1.3, "๐ถ"),
|
|
141
|
+
(88, 14, 0.4, "๐บ"),
|
|
142
|
+
(12, 48, 0.6, "๐"),
|
|
143
|
+
(28, 58, 1.7, "๐ฆด"),
|
|
144
|
+
(44, 42, 0.9, "๐ฆฎ"),
|
|
145
|
+
(58, 52, 1.5, "๐พ"),
|
|
146
|
+
(72, 46, 0.3, "๐ฉ"),
|
|
147
|
+
(86, 54, 1.1, "๐โ๐ฆบ"),
|
|
148
|
+
(8, 72, 0.7, "๐ถ"),
|
|
149
|
+
(24, 80, 1.2, "๐ฉ"),
|
|
150
|
+
(40, 74, 0.2, "๐"),
|
|
151
|
+
(56, 66, 1.6, "๐ฆฎ"),
|
|
152
|
+
(70, 78, 1.0, "๐โ๐ฆบ"),
|
|
153
|
+
(84, 70, 1.4, "๐พ"),
|
|
154
|
+
(16, 90, 0.5, "๐ถ"),
|
|
155
|
+
(32, 92, 1.9, "๐ฆด"),
|
|
156
|
+
(48, 88, 1.1, "๐บ"),
|
|
157
|
+
(64, 94, 1.8, "๐ฉ"),
|
|
158
|
+
(78, 88, 0.6, "๐"),
|
|
159
|
+
(90, 82, 1.3, "๐พ"),
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
_FAILURE_PUPPIES = (
|
|
164
|
+
(8, 6, 0.0, "๐ฅบ๐ถ"),
|
|
165
|
+
(22, 18, 0.3, "๐ข๐"),
|
|
166
|
+
(36, 10, 0.6, "๐ฟ๐ฉ"),
|
|
167
|
+
(50, 20, 0.9, "๐ญ๐ฆฎ"),
|
|
168
|
+
(64, 8, 1.2, "๐ฅบ๐โ๐ฆบ"),
|
|
169
|
+
(78, 16, 1.5, "๐ข๐ถ"),
|
|
170
|
+
(12, 38, 0.4, "๐ญ๐"),
|
|
171
|
+
(28, 44, 0.7, "๐ฟ๐ฉ"),
|
|
172
|
+
(42, 34, 1.0, "๐ฅบ๐ฆฎ"),
|
|
173
|
+
(58, 46, 1.3, "๐ญ๐โ๐ฆบ"),
|
|
174
|
+
(72, 36, 1.6, "๐ข๐ถ"),
|
|
175
|
+
(86, 40, 1.9, "๐ญ๐"),
|
|
176
|
+
(16, 64, 0.5, "๐ฅบ๐ฉ"),
|
|
177
|
+
(32, 70, 0.8, "๐ญ๐ฆฎ"),
|
|
178
|
+
(48, 60, 1.1, "๐ฟ๐โ๐ฆบ"),
|
|
179
|
+
(62, 74, 1.4, "๐ฅบ๐ถ"),
|
|
180
|
+
(78, 68, 1.7, "๐ญ๐"),
|
|
181
|
+
(90, 72, 2.0, "๐ข๐ฉ"),
|
|
182
|
+
(20, 88, 0.6, "๐ฅบ๐ฆฎ"),
|
|
183
|
+
(36, 92, 0.9, "๐ญ๐โ๐ฆบ"),
|
|
184
|
+
(52, 86, 1.2, "๐ข๐ถ"),
|
|
185
|
+
(68, 94, 1.5, "๐ญ๐"),
|
|
186
|
+
(82, 90, 1.8, "๐ฟ๐ฉ"),
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
_STRAFE_SHELLS: Tuple[Tuple[float, float], ...] = (
|
|
191
|
+
(22.0, 0.0),
|
|
192
|
+
(28.0, 0.35),
|
|
193
|
+
(34.0, 0.7),
|
|
194
|
+
(26.0, 0.2),
|
|
195
|
+
(32.0, 0.55),
|
|
196
|
+
(24.0, 0.9),
|
|
197
|
+
(30.0, 1.25),
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def _build_artillery(projectile: str, *, shells_only: bool = False) -> str:
|
|
202
|
+
"""Return HTML spans for puppy artillery shells (and cannons when desired)."""
|
|
203
|
+
shell_markup = []
|
|
204
|
+
for index, (top, delay) in enumerate(_STRAFE_SHELLS):
|
|
205
|
+
duration = 2.3 + (index % 3) * 0.25
|
|
206
|
+
shell_markup.append(
|
|
207
|
+
f"<span class='shell' style='top:{top}%;animation-delay:-{delay}s;animation-duration:{duration}s;'>{projectile}๐ฅ</span>"
|
|
208
|
+
)
|
|
209
|
+
shells = "".join(shell_markup)
|
|
210
|
+
if shells_only:
|
|
211
|
+
return shells
|
|
212
|
+
|
|
213
|
+
cannons = (
|
|
214
|
+
"<span class='cannon left'>๐ถ๐งจ</span><span class='cannon right'>๐โ๐ฆบ๐ฅ</span>"
|
|
215
|
+
)
|
|
216
|
+
return cannons + shells
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def _service_targets(service_name: str) -> Tuple[str, str, str, str]:
|
|
220
|
+
"""Map service names to projectile emoji and rival logo metadata."""
|
|
221
|
+
normalized = service_name.lower()
|
|
222
|
+
if "anthropic" in normalized or "claude" in normalized:
|
|
223
|
+
return "๐โ๐ฆบ๐งจ", CLAUDE_LOGO_URL, "Claude logo", ""
|
|
224
|
+
if "chat" in normalized or "gpt" in normalized:
|
|
225
|
+
return "๐ถ๐", CHATGPT_LOGO_URL, "ChatGPT logo", "invert"
|
|
226
|
+
if "gemini" in normalized or "google" in normalized:
|
|
227
|
+
return "๐ถโจ", GEMINI_LOGO_URL, "Gemini logo", ""
|
|
228
|
+
return "๐พ๐ฅ", CHATGPT_LOGO_URL, "mystery logo", "invert"
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""Shell command safety assessment agent.
|
|
2
|
+
|
|
3
|
+
This agent provides rapid risk assessment of shell commands before execution.
|
|
4
|
+
It's designed to be ultra-lightweight with a concise prompt (<200 tokens) and
|
|
5
|
+
uses structured output for reliable parsing.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import TYPE_CHECKING, List
|
|
9
|
+
|
|
10
|
+
from code_puppy.agents.base_agent import BaseAgent
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
pass
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ShellSafetyAgent(BaseAgent):
|
|
17
|
+
"""Lightweight agent for assessing shell command safety risks.
|
|
18
|
+
|
|
19
|
+
This agent evaluates shell commands for potential risks including:
|
|
20
|
+
- File system destruction (rm -rf, dd, format, mkfs)
|
|
21
|
+
- Database operations (DROP, TRUNCATE, unfiltered UPDATE/DELETE)
|
|
22
|
+
- Privilege escalation (sudo, su, chmod 777)
|
|
23
|
+
- Network operations (wget/curl to unknown hosts)
|
|
24
|
+
- Data exfiltration patterns
|
|
25
|
+
|
|
26
|
+
The agent returns structured output with a risk level and brief reasoning.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
@property
|
|
30
|
+
def name(self) -> str:
|
|
31
|
+
"""Agent name for internal use."""
|
|
32
|
+
return "shell_safety_checker"
|
|
33
|
+
|
|
34
|
+
@property
|
|
35
|
+
def display_name(self) -> str:
|
|
36
|
+
"""User-facing display name."""
|
|
37
|
+
return "Shell Safety Checker ๐ก๏ธ"
|
|
38
|
+
|
|
39
|
+
@property
|
|
40
|
+
def description(self) -> str:
|
|
41
|
+
"""Agent description."""
|
|
42
|
+
return "Lightweight agent that assesses shell command safety risks"
|
|
43
|
+
|
|
44
|
+
def get_system_prompt(self) -> str:
|
|
45
|
+
"""Get the ultra-concise system prompt for shell safety assessment.
|
|
46
|
+
|
|
47
|
+
This prompt is kept under 200 tokens for fast inference and low cost.
|
|
48
|
+
"""
|
|
49
|
+
return """You are a shell command safety analyzer. Assess risk levels concisely.
|
|
50
|
+
|
|
51
|
+
**Risk Levels:**
|
|
52
|
+
- none: Completely safe (ls, pwd, echo, cat readonly files)
|
|
53
|
+
- low: Minimal risk (mkdir, touch, git status, read-only queries)
|
|
54
|
+
- medium: Moderate risk (file edits, package installs, service restarts)
|
|
55
|
+
- high: Significant risk (rm files, UPDATE/DELETE without WHERE, TRUNCATE, chmod dangerous permissions)
|
|
56
|
+
- critical: Severe/destructive (rm -rf, DROP TABLE/DATABASE, dd, format, mkfs, bq delete dataset, unfiltered mass deletes)
|
|
57
|
+
|
|
58
|
+
**Evaluate:**
|
|
59
|
+
- Scope (single file vs. entire system)
|
|
60
|
+
- Reversibility (can it be undone?)
|
|
61
|
+
- Data loss potential
|
|
62
|
+
- Privilege requirements
|
|
63
|
+
- Database destruction patterns
|
|
64
|
+
|
|
65
|
+
**Output:** Risk level + reasoning (max 1 sentence)."""
|
|
66
|
+
|
|
67
|
+
def get_available_tools(self) -> List[str]:
|
|
68
|
+
"""This agent uses no tools - pure reasoning only."""
|
|
69
|
+
return []
|