code-puppy 0.0.317__py3-none-any.whl → 0.0.319__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- code_puppy/command_line/mcp/logs_command.py +173 -64
- code_puppy/command_line/model_settings_menu.py +6 -0
- code_puppy/mcp_/__init__.py +17 -0
- code_puppy/mcp_/blocking_startup.py +50 -29
- code_puppy/mcp_/managed_server.py +1 -1
- code_puppy/mcp_/mcp_logs.py +224 -0
- code_puppy/model_factory.py +48 -0
- code_puppy/models.json +1 -1
- code_puppy/plugins/claude_code_oauth/utils.py +1 -0
- {code_puppy-0.0.317.data → code_puppy-0.0.319.data}/data/code_puppy/models.json +1 -1
- {code_puppy-0.0.317.dist-info → code_puppy-0.0.319.dist-info}/METADATA +1 -1
- {code_puppy-0.0.317.dist-info → code_puppy-0.0.319.dist-info}/RECORD +16 -15
- {code_puppy-0.0.317.data → code_puppy-0.0.319.data}/data/code_puppy/models_dev_api.json +0 -0
- {code_puppy-0.0.317.dist-info → code_puppy-0.0.319.dist-info}/WHEEL +0 -0
- {code_puppy-0.0.317.dist-info → code_puppy-0.0.319.dist-info}/entry_points.txt +0 -0
- {code_puppy-0.0.317.dist-info → code_puppy-0.0.319.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,14 +1,21 @@
|
|
|
1
1
|
"""
|
|
2
|
-
MCP Logs Command - Shows
|
|
2
|
+
MCP Logs Command - Shows server logs from persistent log files.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
5
|
import logging
|
|
6
|
-
from datetime import datetime
|
|
7
6
|
from typing import List, Optional
|
|
8
7
|
|
|
9
|
-
from rich.
|
|
8
|
+
from rich.panel import Panel
|
|
9
|
+
from rich.syntax import Syntax
|
|
10
10
|
from rich.text import Text
|
|
11
11
|
|
|
12
|
+
from code_puppy.mcp_.mcp_logs import (
|
|
13
|
+
clear_logs,
|
|
14
|
+
get_log_file_path,
|
|
15
|
+
get_log_stats,
|
|
16
|
+
list_servers_with_logs,
|
|
17
|
+
read_logs,
|
|
18
|
+
)
|
|
12
19
|
from code_puppy.messaging import emit_error, emit_info
|
|
13
20
|
|
|
14
21
|
from .base import MCPCommandBase
|
|
@@ -22,105 +29,207 @@ class LogsCommand(MCPCommandBase):
|
|
|
22
29
|
"""
|
|
23
30
|
Command handler for showing MCP server logs.
|
|
24
31
|
|
|
25
|
-
Shows
|
|
32
|
+
Shows logs from persistent log files stored in ~/.code_puppy/mcp_logs/.
|
|
26
33
|
"""
|
|
27
34
|
|
|
28
35
|
def execute(self, args: List[str], group_id: Optional[str] = None) -> None:
|
|
29
36
|
"""
|
|
30
|
-
Show
|
|
37
|
+
Show logs for a server.
|
|
38
|
+
|
|
39
|
+
Usage:
|
|
40
|
+
/mcp logs - List servers with logs
|
|
41
|
+
/mcp logs <server_name> - Show last 50 lines
|
|
42
|
+
/mcp logs <server_name> 100 - Show last 100 lines
|
|
43
|
+
/mcp logs <server_name> all - Show all logs
|
|
44
|
+
/mcp logs <server_name> --clear - Clear logs for server
|
|
31
45
|
|
|
32
46
|
Args:
|
|
33
|
-
args: Command arguments
|
|
47
|
+
args: Command arguments
|
|
34
48
|
group_id: Optional message group ID for grouping related messages
|
|
35
49
|
"""
|
|
36
50
|
if group_id is None:
|
|
37
51
|
group_id = self.generate_group_id()
|
|
38
52
|
|
|
53
|
+
# No args - list servers with logs
|
|
39
54
|
if not args:
|
|
40
|
-
|
|
55
|
+
self._list_servers_with_logs(group_id)
|
|
41
56
|
return
|
|
42
57
|
|
|
43
58
|
server_name = args[0]
|
|
44
|
-
|
|
59
|
+
|
|
60
|
+
# Check for --clear flag
|
|
61
|
+
if len(args) > 1 and args[1] == "--clear":
|
|
62
|
+
self._clear_logs(server_name, group_id)
|
|
63
|
+
return
|
|
64
|
+
|
|
65
|
+
# Determine number of lines
|
|
66
|
+
lines = 50 # Default
|
|
67
|
+
show_all = False
|
|
45
68
|
|
|
46
69
|
if len(args) > 1:
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
70
|
+
if args[1].lower() == "all":
|
|
71
|
+
show_all = True
|
|
72
|
+
else:
|
|
73
|
+
try:
|
|
74
|
+
lines = int(args[1])
|
|
75
|
+
if lines <= 0:
|
|
76
|
+
emit_info(
|
|
77
|
+
"Lines must be positive, using default: 50",
|
|
78
|
+
message_group=group_id,
|
|
79
|
+
)
|
|
80
|
+
lines = 50
|
|
81
|
+
except ValueError:
|
|
50
82
|
emit_info(
|
|
51
|
-
"
|
|
83
|
+
f"Invalid number '{args[1]}', using default: 50",
|
|
52
84
|
message_group=group_id,
|
|
53
85
|
)
|
|
54
|
-
limit = 10
|
|
55
|
-
except ValueError:
|
|
56
|
-
emit_info(
|
|
57
|
-
f"Invalid limit '{args[1]}', using default: 10",
|
|
58
|
-
message_group=group_id,
|
|
59
|
-
)
|
|
60
86
|
|
|
87
|
+
self._show_logs(server_name, lines if not show_all else None, group_id)
|
|
88
|
+
|
|
89
|
+
def _list_servers_with_logs(self, group_id: str) -> None:
|
|
90
|
+
"""List all servers that have log files."""
|
|
91
|
+
servers = list_servers_with_logs()
|
|
92
|
+
|
|
93
|
+
if not servers:
|
|
94
|
+
emit_info(
|
|
95
|
+
"📋 No MCP server logs found.\n"
|
|
96
|
+
"Logs are created when servers are started.",
|
|
97
|
+
message_group=group_id,
|
|
98
|
+
)
|
|
99
|
+
return
|
|
100
|
+
|
|
101
|
+
lines = ["📋 **Servers with logs:**\n"]
|
|
102
|
+
|
|
103
|
+
for server in servers:
|
|
104
|
+
stats = get_log_stats(server)
|
|
105
|
+
size_kb = stats["total_size_bytes"] / 1024
|
|
106
|
+
size_str = (
|
|
107
|
+
f"{size_kb:.1f} KB" if size_kb < 1024 else f"{size_kb / 1024:.1f} MB"
|
|
108
|
+
)
|
|
109
|
+
rotated = (
|
|
110
|
+
f" (+{stats['rotated_count']} rotated)"
|
|
111
|
+
if stats["rotated_count"]
|
|
112
|
+
else ""
|
|
113
|
+
)
|
|
114
|
+
lines.append(
|
|
115
|
+
f" • **{server}** - {stats['line_count']} lines, {size_str}{rotated}"
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
lines.append("\n**Usage:** `/mcp logs <server_name> [lines|all]`")
|
|
119
|
+
|
|
120
|
+
emit_info("\n".join(lines), message_group=group_id)
|
|
121
|
+
|
|
122
|
+
def _show_logs(self, server_name: str, lines: Optional[int], group_id: str) -> None:
|
|
123
|
+
"""
|
|
124
|
+
Show logs for a specific server.
|
|
125
|
+
|
|
126
|
+
Args:
|
|
127
|
+
server_name: Name of the server
|
|
128
|
+
lines: Number of lines to show, or None for all
|
|
129
|
+
group_id: Message group ID
|
|
130
|
+
"""
|
|
61
131
|
try:
|
|
62
|
-
#
|
|
132
|
+
# Verify server exists in manager
|
|
63
133
|
server_id = find_server_id_by_name(self.manager, server_name)
|
|
64
134
|
if not server_id:
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
135
|
+
# Server not configured, but might have logs from before
|
|
136
|
+
stats = get_log_stats(server_name)
|
|
137
|
+
if not stats["exists"]:
|
|
138
|
+
emit_info(
|
|
139
|
+
f"Server '{server_name}' not found and has no logs.",
|
|
140
|
+
message_group=group_id,
|
|
141
|
+
)
|
|
142
|
+
suggest_similar_servers(
|
|
143
|
+
self.manager, server_name, group_id=group_id
|
|
144
|
+
)
|
|
145
|
+
return
|
|
68
146
|
|
|
69
|
-
#
|
|
70
|
-
|
|
147
|
+
# Read logs
|
|
148
|
+
log_lines = read_logs(server_name, lines=lines)
|
|
71
149
|
|
|
72
|
-
if not
|
|
150
|
+
if not log_lines:
|
|
73
151
|
emit_info(
|
|
74
|
-
f"
|
|
152
|
+
f"📋 No logs found for server: **{server_name}**\n"
|
|
153
|
+
f"Log file: `{get_log_file_path(server_name)}`",
|
|
75
154
|
message_group=group_id,
|
|
76
155
|
)
|
|
77
156
|
return
|
|
78
157
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
158
|
+
# Get stats for header
|
|
159
|
+
stats = get_log_stats(server_name)
|
|
160
|
+
total_lines = stats["line_count"]
|
|
161
|
+
showing = len(log_lines)
|
|
162
|
+
|
|
163
|
+
# Format header
|
|
164
|
+
if lines is None:
|
|
165
|
+
header = f"📋 Logs for {server_name} (all {total_lines} lines)"
|
|
166
|
+
else:
|
|
167
|
+
header = (
|
|
168
|
+
f"📋 Logs for {server_name} (last {showing} of {total_lines} lines)"
|
|
85
169
|
)
|
|
86
|
-
return
|
|
87
170
|
|
|
88
|
-
#
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
171
|
+
# Format log content with syntax highlighting
|
|
172
|
+
log_content = "\n".join(log_lines)
|
|
173
|
+
|
|
174
|
+
# Create a panel with the logs
|
|
175
|
+
syntax = Syntax(
|
|
176
|
+
log_content,
|
|
177
|
+
"log",
|
|
178
|
+
theme="monokai",
|
|
179
|
+
word_wrap=True,
|
|
180
|
+
line_numbers=False,
|
|
181
|
+
)
|
|
93
182
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
183
|
+
panel = Panel(
|
|
184
|
+
syntax,
|
|
185
|
+
title=header,
|
|
186
|
+
subtitle=f"Log file: {get_log_file_path(server_name)}",
|
|
187
|
+
border_style="dim",
|
|
97
188
|
)
|
|
98
189
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
# Color code event types
|
|
111
|
-
event_style = "cyan"
|
|
112
|
-
if "error" in event_type.lower():
|
|
113
|
-
event_style = "red"
|
|
114
|
-
elif event_type in ["started", "enabled", "registered"]:
|
|
115
|
-
event_style = "green"
|
|
116
|
-
elif event_type in ["stopped", "disabled"]:
|
|
117
|
-
event_style = "yellow"
|
|
118
|
-
|
|
119
|
-
table.add_row(
|
|
120
|
-
time_str, Text(event_type, style=event_style), details_str or "-"
|
|
190
|
+
emit_info(panel, message_group=group_id)
|
|
191
|
+
|
|
192
|
+
# Show hint for more options
|
|
193
|
+
if lines is not None and showing < total_lines:
|
|
194
|
+
emit_info(
|
|
195
|
+
Text.from_markup(
|
|
196
|
+
f"[dim]💡 Use `/mcp logs {server_name} all` to see all logs, "
|
|
197
|
+
f"or `/mcp logs {server_name} <number>` for specific count[/dim]"
|
|
198
|
+
),
|
|
199
|
+
message_group=group_id,
|
|
121
200
|
)
|
|
122
|
-
emit_info(table, message_group=group_id)
|
|
123
201
|
|
|
124
202
|
except Exception as e:
|
|
125
203
|
logger.error(f"Error getting logs for server '{server_name}': {e}")
|
|
126
204
|
emit_error(f"Error getting logs: {e}", message_group=group_id)
|
|
205
|
+
|
|
206
|
+
def _clear_logs(self, server_name: str, group_id: str) -> None:
|
|
207
|
+
"""
|
|
208
|
+
Clear logs for a specific server.
|
|
209
|
+
|
|
210
|
+
Args:
|
|
211
|
+
server_name: Name of the server
|
|
212
|
+
group_id: Message group ID
|
|
213
|
+
"""
|
|
214
|
+
try:
|
|
215
|
+
stats = get_log_stats(server_name)
|
|
216
|
+
|
|
217
|
+
if not stats["exists"] and stats["rotated_count"] == 0:
|
|
218
|
+
emit_info(
|
|
219
|
+
f"No logs to clear for server: {server_name}",
|
|
220
|
+
message_group=group_id,
|
|
221
|
+
)
|
|
222
|
+
return
|
|
223
|
+
|
|
224
|
+
# Clear the logs
|
|
225
|
+
clear_logs(server_name, include_rotated=True)
|
|
226
|
+
|
|
227
|
+
cleared_count = 1 + stats["rotated_count"]
|
|
228
|
+
emit_info(
|
|
229
|
+
f"🗑️ Cleared {cleared_count} log file(s) for **{server_name}**",
|
|
230
|
+
message_group=group_id,
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
except Exception as e:
|
|
234
|
+
logger.error(f"Error clearing logs for server '{server_name}': {e}")
|
|
235
|
+
emit_error(f"Error clearing logs: {e}", message_group=group_id)
|
|
@@ -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
|
|
code_puppy/mcp_/__init__.py
CHANGED
|
@@ -17,6 +17,15 @@ from .error_isolation import (
|
|
|
17
17
|
)
|
|
18
18
|
from .managed_server import ManagedMCPServer, ServerConfig, ServerState
|
|
19
19
|
from .manager import MCPManager, ServerInfo, get_mcp_manager
|
|
20
|
+
from .mcp_logs import (
|
|
21
|
+
clear_logs,
|
|
22
|
+
get_log_file_path,
|
|
23
|
+
get_log_stats,
|
|
24
|
+
get_mcp_logs_dir,
|
|
25
|
+
list_servers_with_logs,
|
|
26
|
+
read_logs,
|
|
27
|
+
write_log,
|
|
28
|
+
)
|
|
20
29
|
from .registry import ServerRegistry
|
|
21
30
|
from .retry_manager import RetryManager, RetryStats, get_retry_manager, retry_mcp_call
|
|
22
31
|
from .status_tracker import Event, ServerStatusTracker
|
|
@@ -46,4 +55,12 @@ __all__ = [
|
|
|
46
55
|
"MCPDashboard",
|
|
47
56
|
"MCPConfigWizard",
|
|
48
57
|
"run_add_wizard",
|
|
58
|
+
# Log management
|
|
59
|
+
"get_mcp_logs_dir",
|
|
60
|
+
"get_log_file_path",
|
|
61
|
+
"read_logs",
|
|
62
|
+
"write_log",
|
|
63
|
+
"clear_logs",
|
|
64
|
+
"list_servers_with_logs",
|
|
65
|
+
"get_log_stats",
|
|
49
66
|
]
|
|
@@ -2,14 +2,13 @@
|
|
|
2
2
|
MCP Server with blocking startup capability and stderr capture.
|
|
3
3
|
|
|
4
4
|
This module provides MCP servers that:
|
|
5
|
-
1. Capture stderr output from stdio servers
|
|
5
|
+
1. Capture stderr output from stdio servers to persistent log files
|
|
6
6
|
2. Block until fully initialized before allowing operations
|
|
7
|
-
3.
|
|
7
|
+
3. Optionally emit stderr to users (disabled by default to reduce console noise)
|
|
8
8
|
"""
|
|
9
9
|
|
|
10
10
|
import asyncio
|
|
11
11
|
import os
|
|
12
|
-
import tempfile
|
|
13
12
|
import threading
|
|
14
13
|
import uuid
|
|
15
14
|
from contextlib import asynccontextmanager
|
|
@@ -18,56 +17,73 @@ from typing import List, Optional
|
|
|
18
17
|
from mcp.client.stdio import StdioServerParameters, stdio_client
|
|
19
18
|
from pydantic_ai.mcp import MCPServerStdio
|
|
20
19
|
|
|
20
|
+
from code_puppy.mcp_.mcp_logs import get_log_file_path, rotate_log_if_needed, write_log
|
|
21
21
|
from code_puppy.messaging import emit_info
|
|
22
22
|
|
|
23
23
|
|
|
24
24
|
class StderrFileCapture:
|
|
25
|
-
"""
|
|
25
|
+
"""
|
|
26
|
+
Captures stderr to a persistent log file and optionally monitors it.
|
|
27
|
+
|
|
28
|
+
Logs are written to ~/.code_puppy/mcp_logs/<server_name>.log
|
|
29
|
+
"""
|
|
26
30
|
|
|
27
31
|
def __init__(
|
|
28
32
|
self,
|
|
29
33
|
server_name: str,
|
|
30
|
-
emit_to_user: bool =
|
|
34
|
+
emit_to_user: bool = False, # Disabled by default to reduce console noise
|
|
31
35
|
message_group: Optional[uuid.UUID] = None,
|
|
32
36
|
):
|
|
33
37
|
self.server_name = server_name
|
|
34
38
|
self.emit_to_user = emit_to_user
|
|
35
39
|
self.message_group = message_group or uuid.uuid4()
|
|
36
|
-
self.
|
|
37
|
-
self.
|
|
40
|
+
self.log_file = None
|
|
41
|
+
self.log_path = None
|
|
38
42
|
self.monitor_thread = None
|
|
39
43
|
self.stop_monitoring = threading.Event()
|
|
40
44
|
self.captured_lines = []
|
|
45
|
+
self._last_read_pos = 0
|
|
41
46
|
|
|
42
47
|
def start(self):
|
|
43
|
-
"""Start capture by
|
|
44
|
-
#
|
|
45
|
-
self.
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
self.
|
|
48
|
+
"""Start capture by opening persistent log file and monitor thread."""
|
|
49
|
+
# Rotate log if needed
|
|
50
|
+
rotate_log_if_needed(self.server_name)
|
|
51
|
+
|
|
52
|
+
# Get persistent log path
|
|
53
|
+
self.log_path = get_log_file_path(self.server_name)
|
|
54
|
+
|
|
55
|
+
# Write startup marker
|
|
56
|
+
write_log(self.server_name, "--- Server starting ---", "INFO")
|
|
49
57
|
|
|
50
|
-
#
|
|
58
|
+
# Open log file for appending stderr
|
|
59
|
+
self.log_file = open(self.log_path, "a", encoding="utf-8")
|
|
60
|
+
|
|
61
|
+
# Start monitoring thread only if we need to emit to user or capture lines
|
|
51
62
|
self.stop_monitoring.clear()
|
|
52
63
|
self.monitor_thread = threading.Thread(target=self._monitor_file)
|
|
53
64
|
self.monitor_thread.daemon = True
|
|
54
65
|
self.monitor_thread.start()
|
|
55
66
|
|
|
56
|
-
return self.
|
|
67
|
+
return self.log_file
|
|
57
68
|
|
|
58
69
|
def _monitor_file(self):
|
|
59
|
-
"""Monitor the
|
|
60
|
-
if not self.
|
|
70
|
+
"""Monitor the log file for new content."""
|
|
71
|
+
if not self.log_path:
|
|
61
72
|
return
|
|
62
73
|
|
|
63
|
-
|
|
74
|
+
# Start reading from current position (end of file before we started)
|
|
75
|
+
try:
|
|
76
|
+
self._last_read_pos = os.path.getsize(self.log_path)
|
|
77
|
+
except OSError:
|
|
78
|
+
self._last_read_pos = 0
|
|
79
|
+
|
|
64
80
|
while not self.stop_monitoring.is_set():
|
|
65
81
|
try:
|
|
66
|
-
with open(self.
|
|
67
|
-
f.seek(
|
|
82
|
+
with open(self.log_path, "r", encoding="utf-8", errors="replace") as f:
|
|
83
|
+
f.seek(self._last_read_pos)
|
|
68
84
|
new_content = f.read()
|
|
69
85
|
if new_content:
|
|
70
|
-
|
|
86
|
+
self._last_read_pos = f.tell()
|
|
71
87
|
# Process new lines
|
|
72
88
|
for line in new_content.splitlines():
|
|
73
89
|
if line.strip():
|
|
@@ -89,16 +105,21 @@ class StderrFileCapture:
|
|
|
89
105
|
if self.monitor_thread:
|
|
90
106
|
self.monitor_thread.join(timeout=1)
|
|
91
107
|
|
|
92
|
-
if self.
|
|
108
|
+
if self.log_file:
|
|
93
109
|
try:
|
|
94
|
-
self.
|
|
110
|
+
self.log_file.flush()
|
|
111
|
+
self.log_file.close()
|
|
95
112
|
except Exception:
|
|
96
113
|
pass
|
|
97
114
|
|
|
98
|
-
|
|
115
|
+
# Write shutdown marker
|
|
116
|
+
write_log(self.server_name, "--- Server stopped ---", "INFO")
|
|
117
|
+
|
|
118
|
+
# Read any remaining content for in-memory capture
|
|
119
|
+
if self.log_path and os.path.exists(self.log_path):
|
|
99
120
|
try:
|
|
100
|
-
|
|
101
|
-
|
|
121
|
+
with open(self.log_path, "r", encoding="utf-8", errors="replace") as f:
|
|
122
|
+
f.seek(self._last_read_pos)
|
|
102
123
|
content = f.read()
|
|
103
124
|
for line in content.splitlines():
|
|
104
125
|
if line.strip() and line not in self.captured_lines:
|
|
@@ -108,13 +129,13 @@ class StderrFileCapture:
|
|
|
108
129
|
f"MCP {self.server_name}: {line}",
|
|
109
130
|
message_group=self.message_group,
|
|
110
131
|
)
|
|
111
|
-
|
|
112
|
-
os.unlink(self.temp_path)
|
|
113
132
|
except Exception:
|
|
114
133
|
pass
|
|
115
134
|
|
|
135
|
+
# Note: We do NOT delete the log file - it's persistent now!
|
|
136
|
+
|
|
116
137
|
def get_captured_lines(self) -> List[str]:
|
|
117
|
-
"""Get all captured lines."""
|
|
138
|
+
"""Get all captured lines from this session."""
|
|
118
139
|
return self.captured_lines.copy()
|
|
119
140
|
|
|
120
141
|
|
|
@@ -204,7 +204,7 @@ class ManagedMCPServer:
|
|
|
204
204
|
**stdio_kwargs,
|
|
205
205
|
process_tool_call=process_tool_call,
|
|
206
206
|
tool_prefix=self.config.name,
|
|
207
|
-
emit_stderr=
|
|
207
|
+
emit_stderr=False, # Logs go to file, not console (use /mcp logs to view)
|
|
208
208
|
message_group=message_group,
|
|
209
209
|
)
|
|
210
210
|
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
"""
|
|
2
|
+
MCP Server Log Management.
|
|
3
|
+
|
|
4
|
+
This module provides persistent log file management for MCP servers.
|
|
5
|
+
Logs are stored in STATE_DIR/mcp_logs/<server_name>.log
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from datetime import datetime
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import List, Optional
|
|
11
|
+
|
|
12
|
+
from code_puppy.config import STATE_DIR
|
|
13
|
+
|
|
14
|
+
# Maximum log file size in bytes (5MB)
|
|
15
|
+
MAX_LOG_SIZE = 5 * 1024 * 1024
|
|
16
|
+
|
|
17
|
+
# Number of rotated logs to keep
|
|
18
|
+
MAX_ROTATED_LOGS = 3
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def get_mcp_logs_dir() -> Path:
|
|
22
|
+
"""
|
|
23
|
+
Get the directory for MCP server logs.
|
|
24
|
+
|
|
25
|
+
Creates the directory if it doesn't exist.
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
Path to the MCP logs directory
|
|
29
|
+
"""
|
|
30
|
+
logs_dir = Path(STATE_DIR) / "mcp_logs"
|
|
31
|
+
logs_dir.mkdir(parents=True, exist_ok=True)
|
|
32
|
+
return logs_dir
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def get_log_file_path(server_name: str) -> Path:
|
|
36
|
+
"""
|
|
37
|
+
Get the log file path for a specific server.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
server_name: Name of the MCP server
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
Path to the server's log file
|
|
44
|
+
"""
|
|
45
|
+
# Sanitize server name for filesystem
|
|
46
|
+
safe_name = "".join(c if c.isalnum() or c in "-_" else "_" for c in server_name)
|
|
47
|
+
return get_mcp_logs_dir() / f"{safe_name}.log"
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def rotate_log_if_needed(server_name: str) -> None:
|
|
51
|
+
"""
|
|
52
|
+
Rotate log file if it exceeds MAX_LOG_SIZE.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
server_name: Name of the MCP server
|
|
56
|
+
"""
|
|
57
|
+
log_path = get_log_file_path(server_name)
|
|
58
|
+
|
|
59
|
+
if not log_path.exists():
|
|
60
|
+
return
|
|
61
|
+
|
|
62
|
+
# Check if rotation is needed
|
|
63
|
+
if log_path.stat().st_size < MAX_LOG_SIZE:
|
|
64
|
+
return
|
|
65
|
+
|
|
66
|
+
logs_dir = get_mcp_logs_dir()
|
|
67
|
+
safe_name = "".join(c if c.isalnum() or c in "-_" else "_" for c in server_name)
|
|
68
|
+
|
|
69
|
+
# Remove oldest rotated log if we're at the limit
|
|
70
|
+
oldest = logs_dir / f"{safe_name}.log.{MAX_ROTATED_LOGS}"
|
|
71
|
+
if oldest.exists():
|
|
72
|
+
oldest.unlink()
|
|
73
|
+
|
|
74
|
+
# Shift existing rotated logs
|
|
75
|
+
for i in range(MAX_ROTATED_LOGS - 1, 0, -1):
|
|
76
|
+
old_path = logs_dir / f"{safe_name}.log.{i}"
|
|
77
|
+
new_path = logs_dir / f"{safe_name}.log.{i + 1}"
|
|
78
|
+
if old_path.exists():
|
|
79
|
+
old_path.rename(new_path)
|
|
80
|
+
|
|
81
|
+
# Rotate current log
|
|
82
|
+
rotated_path = logs_dir / f"{safe_name}.log.1"
|
|
83
|
+
log_path.rename(rotated_path)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def write_log(server_name: str, message: str, level: str = "INFO") -> None:
|
|
87
|
+
"""
|
|
88
|
+
Write a log message for a server.
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
server_name: Name of the MCP server
|
|
92
|
+
message: Log message to write
|
|
93
|
+
level: Log level (INFO, ERROR, WARN, DEBUG)
|
|
94
|
+
"""
|
|
95
|
+
rotate_log_if_needed(server_name)
|
|
96
|
+
|
|
97
|
+
log_path = get_log_file_path(server_name)
|
|
98
|
+
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")[:-3]
|
|
99
|
+
|
|
100
|
+
with open(log_path, "a", encoding="utf-8") as f:
|
|
101
|
+
f.write(f"[{timestamp}] [{level}] {message}\n")
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def read_logs(
|
|
105
|
+
server_name: str, lines: Optional[int] = None, include_rotated: bool = False
|
|
106
|
+
) -> List[str]:
|
|
107
|
+
"""
|
|
108
|
+
Read log lines for a server.
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
server_name: Name of the MCP server
|
|
112
|
+
lines: Number of lines to return (from end). None means all lines.
|
|
113
|
+
include_rotated: Whether to include rotated log files
|
|
114
|
+
|
|
115
|
+
Returns:
|
|
116
|
+
List of log lines (most recent last)
|
|
117
|
+
"""
|
|
118
|
+
all_lines = []
|
|
119
|
+
|
|
120
|
+
# Read rotated logs first (oldest to newest)
|
|
121
|
+
if include_rotated:
|
|
122
|
+
logs_dir = get_mcp_logs_dir()
|
|
123
|
+
safe_name = "".join(c if c.isalnum() or c in "-_" else "_" for c in server_name)
|
|
124
|
+
|
|
125
|
+
for i in range(MAX_ROTATED_LOGS, 0, -1):
|
|
126
|
+
rotated_path = logs_dir / f"{safe_name}.log.{i}"
|
|
127
|
+
if rotated_path.exists():
|
|
128
|
+
with open(rotated_path, "r", encoding="utf-8", errors="replace") as f:
|
|
129
|
+
all_lines.extend(f.read().splitlines())
|
|
130
|
+
|
|
131
|
+
# Read current log
|
|
132
|
+
log_path = get_log_file_path(server_name)
|
|
133
|
+
if log_path.exists():
|
|
134
|
+
with open(log_path, "r", encoding="utf-8", errors="replace") as f:
|
|
135
|
+
all_lines.extend(f.read().splitlines())
|
|
136
|
+
|
|
137
|
+
# Return requested number of lines
|
|
138
|
+
if lines is not None and lines > 0:
|
|
139
|
+
return all_lines[-lines:]
|
|
140
|
+
|
|
141
|
+
return all_lines
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def clear_logs(server_name: str, include_rotated: bool = True) -> None:
|
|
145
|
+
"""
|
|
146
|
+
Clear logs for a server.
|
|
147
|
+
|
|
148
|
+
Args:
|
|
149
|
+
server_name: Name of the MCP server
|
|
150
|
+
include_rotated: Whether to also clear rotated log files
|
|
151
|
+
"""
|
|
152
|
+
log_path = get_log_file_path(server_name)
|
|
153
|
+
|
|
154
|
+
if log_path.exists():
|
|
155
|
+
log_path.unlink()
|
|
156
|
+
|
|
157
|
+
if include_rotated:
|
|
158
|
+
logs_dir = get_mcp_logs_dir()
|
|
159
|
+
safe_name = "".join(c if c.isalnum() or c in "-_" else "_" for c in server_name)
|
|
160
|
+
|
|
161
|
+
for i in range(1, MAX_ROTATED_LOGS + 1):
|
|
162
|
+
rotated_path = logs_dir / f"{safe_name}.log.{i}"
|
|
163
|
+
if rotated_path.exists():
|
|
164
|
+
rotated_path.unlink()
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def list_servers_with_logs() -> List[str]:
|
|
168
|
+
"""
|
|
169
|
+
List all servers that have log files.
|
|
170
|
+
|
|
171
|
+
Returns:
|
|
172
|
+
List of server names with log files
|
|
173
|
+
"""
|
|
174
|
+
logs_dir = get_mcp_logs_dir()
|
|
175
|
+
servers = set()
|
|
176
|
+
|
|
177
|
+
for path in logs_dir.glob("*.log*"):
|
|
178
|
+
# Extract server name from filename
|
|
179
|
+
name = path.stem
|
|
180
|
+
# Remove .log suffix and rotation numbers
|
|
181
|
+
name = name.replace(".log", "").rstrip(".0123456789")
|
|
182
|
+
if name:
|
|
183
|
+
servers.add(name)
|
|
184
|
+
|
|
185
|
+
return sorted(servers)
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def get_log_stats(server_name: str) -> dict:
|
|
189
|
+
"""
|
|
190
|
+
Get statistics about a server's logs.
|
|
191
|
+
|
|
192
|
+
Args:
|
|
193
|
+
server_name: Name of the MCP server
|
|
194
|
+
|
|
195
|
+
Returns:
|
|
196
|
+
Dictionary with log statistics
|
|
197
|
+
"""
|
|
198
|
+
log_path = get_log_file_path(server_name)
|
|
199
|
+
|
|
200
|
+
stats = {
|
|
201
|
+
"exists": log_path.exists(),
|
|
202
|
+
"size_bytes": 0,
|
|
203
|
+
"line_count": 0,
|
|
204
|
+
"rotated_count": 0,
|
|
205
|
+
"total_size_bytes": 0,
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if log_path.exists():
|
|
209
|
+
stats["size_bytes"] = log_path.stat().st_size
|
|
210
|
+
stats["total_size_bytes"] = stats["size_bytes"]
|
|
211
|
+
with open(log_path, "r", encoding="utf-8", errors="replace") as f:
|
|
212
|
+
stats["line_count"] = sum(1 for _ in f)
|
|
213
|
+
|
|
214
|
+
# Count rotated logs
|
|
215
|
+
logs_dir = get_mcp_logs_dir()
|
|
216
|
+
safe_name = "".join(c if c.isalnum() or c in "-_" else "_" for c in server_name)
|
|
217
|
+
|
|
218
|
+
for i in range(1, MAX_ROTATED_LOGS + 1):
|
|
219
|
+
rotated_path = logs_dir / f"{safe_name}.log.{i}"
|
|
220
|
+
if rotated_path.exists():
|
|
221
|
+
stats["rotated_count"] += 1
|
|
222
|
+
stats["total_size_bytes"] += rotated_path.stat().st_size
|
|
223
|
+
|
|
224
|
+
return stats
|
code_puppy/model_factory.py
CHANGED
|
@@ -319,9 +319,21 @@ class ModelFactory:
|
|
|
319
319
|
http2=http2_enabled,
|
|
320
320
|
)
|
|
321
321
|
|
|
322
|
+
# Check if interleaved thinking is enabled for this model
|
|
323
|
+
# Only applies to Claude 4 models (Opus 4.5, Opus 4.1, Opus 4, Sonnet 4)
|
|
324
|
+
from code_puppy.config import get_effective_model_settings
|
|
325
|
+
|
|
326
|
+
effective_settings = get_effective_model_settings(model_name)
|
|
327
|
+
interleaved_thinking = effective_settings.get("interleaved_thinking", False)
|
|
328
|
+
|
|
329
|
+
default_headers = {}
|
|
330
|
+
if interleaved_thinking:
|
|
331
|
+
default_headers["anthropic-beta"] = "interleaved-thinking-2025-05-14"
|
|
332
|
+
|
|
322
333
|
anthropic_client = AsyncAnthropic(
|
|
323
334
|
api_key=api_key,
|
|
324
335
|
http_client=client,
|
|
336
|
+
default_headers=default_headers if default_headers else None,
|
|
325
337
|
)
|
|
326
338
|
|
|
327
339
|
# Ensure cache_control is injected at the Anthropic SDK layer
|
|
@@ -351,10 +363,21 @@ class ModelFactory:
|
|
|
351
363
|
http2=http2_enabled,
|
|
352
364
|
)
|
|
353
365
|
|
|
366
|
+
# Check if interleaved thinking is enabled for this model
|
|
367
|
+
from code_puppy.config import get_effective_model_settings
|
|
368
|
+
|
|
369
|
+
effective_settings = get_effective_model_settings(model_name)
|
|
370
|
+
interleaved_thinking = effective_settings.get("interleaved_thinking", False)
|
|
371
|
+
|
|
372
|
+
default_headers = {}
|
|
373
|
+
if interleaved_thinking:
|
|
374
|
+
default_headers["anthropic-beta"] = "interleaved-thinking-2025-05-14"
|
|
375
|
+
|
|
354
376
|
anthropic_client = AsyncAnthropic(
|
|
355
377
|
base_url=url,
|
|
356
378
|
http_client=client,
|
|
357
379
|
api_key=api_key,
|
|
380
|
+
default_headers=default_headers if default_headers else None,
|
|
358
381
|
)
|
|
359
382
|
|
|
360
383
|
# Ensure cache_control is injected at the Anthropic SDK layer
|
|
@@ -370,6 +393,31 @@ class ModelFactory:
|
|
|
370
393
|
)
|
|
371
394
|
return None
|
|
372
395
|
|
|
396
|
+
# Check if interleaved thinking is enabled (defaults to True for OAuth models)
|
|
397
|
+
from code_puppy.config import get_effective_model_settings
|
|
398
|
+
|
|
399
|
+
effective_settings = get_effective_model_settings(model_name)
|
|
400
|
+
interleaved_thinking = effective_settings.get("interleaved_thinking", True)
|
|
401
|
+
|
|
402
|
+
# Handle anthropic-beta header based on interleaved_thinking setting
|
|
403
|
+
if "anthropic-beta" in headers:
|
|
404
|
+
beta_parts = [p.strip() for p in headers["anthropic-beta"].split(",")]
|
|
405
|
+
if interleaved_thinking:
|
|
406
|
+
# Ensure interleaved-thinking is in the header
|
|
407
|
+
if "interleaved-thinking-2025-05-14" not in beta_parts:
|
|
408
|
+
beta_parts.append("interleaved-thinking-2025-05-14")
|
|
409
|
+
else:
|
|
410
|
+
# Remove interleaved-thinking from the header
|
|
411
|
+
beta_parts = [
|
|
412
|
+
p for p in beta_parts if "interleaved-thinking" not in p
|
|
413
|
+
]
|
|
414
|
+
headers["anthropic-beta"] = ",".join(beta_parts) if beta_parts else None
|
|
415
|
+
if headers.get("anthropic-beta") is None:
|
|
416
|
+
del headers["anthropic-beta"]
|
|
417
|
+
elif interleaved_thinking:
|
|
418
|
+
# No existing beta header, add one for interleaved thinking
|
|
419
|
+
headers["anthropic-beta"] = "interleaved-thinking-2025-05-14"
|
|
420
|
+
|
|
373
421
|
# Use a dedicated client wrapper that injects cache_control on /v1/messages
|
|
374
422
|
if verify is None:
|
|
375
423
|
verify = get_cert_bundle_path()
|
code_puppy/models.json
CHANGED
|
@@ -81,7 +81,7 @@
|
|
|
81
81
|
"type": "anthropic",
|
|
82
82
|
"name": "claude-opus-4-5",
|
|
83
83
|
"context_length": 200000,
|
|
84
|
-
"supported_settings": ["temperature", "extended_thinking", "budget_tokens"]
|
|
84
|
+
"supported_settings": ["temperature", "extended_thinking", "budget_tokens", "interleaved_thinking"]
|
|
85
85
|
},
|
|
86
86
|
"zai-glm-4.6-coding": {
|
|
87
87
|
"type": "zai_coding",
|
|
@@ -81,7 +81,7 @@
|
|
|
81
81
|
"type": "anthropic",
|
|
82
82
|
"name": "claude-opus-4-5",
|
|
83
83
|
"context_length": 200000,
|
|
84
|
-
"supported_settings": ["temperature", "extended_thinking", "budget_tokens"]
|
|
84
|
+
"supported_settings": ["temperature", "extended_thinking", "budget_tokens", "interleaved_thinking"]
|
|
85
85
|
},
|
|
86
86
|
"zai-glm-4.6-coding": {
|
|
87
87
|
"type": "zai_coding",
|
|
@@ -10,9 +10,9 @@ code_puppy/gemini_code_assist.py,sha256=KGS7sO5OLc83nDF3xxS-QiU6vxW9vcm6hmzilu79
|
|
|
10
10
|
code_puppy/http_utils.py,sha256=w5mWYIGIWJZJvgvMahXs9BmdidoJvGn4CASDRY88a8o,13414
|
|
11
11
|
code_puppy/keymap.py,sha256=Uzvq7HB-6inTjKox-90JWzuijztRdWqhJpfTDZVy5no,3235
|
|
12
12
|
code_puppy/main.py,sha256=82r3vZy_XcyEsenLn82BnUusaoyL3Bpm_Th_jKgqecE,273
|
|
13
|
-
code_puppy/model_factory.py,sha256=
|
|
13
|
+
code_puppy/model_factory.py,sha256=H_a5nX462Q-dhX3g3ZY7dmBCIAUOd1aOSZa4HMxF1o4,34191
|
|
14
14
|
code_puppy/model_utils.py,sha256=NU8W8NW5F7QS_PXHaLeh55Air1koUV7IVYFP7Rz3XpY,3615
|
|
15
|
-
code_puppy/models.json,sha256=
|
|
15
|
+
code_puppy/models.json,sha256=mTpmJH0UJlmX8M2KVPbxMWb99de3IxKXCWO-B23b6xo,3101
|
|
16
16
|
code_puppy/models_dev_api.json,sha256=wHjkj-IM_fx1oHki6-GqtOoCrRMR0ScK0f-Iz0UEcy8,548187
|
|
17
17
|
code_puppy/models_dev_parser.py,sha256=8ndmWrsSyKbXXpRZPXc0w6TfWMuCcgaHiMifmlaBaPc,20611
|
|
18
18
|
code_puppy/pydantic_patches.py,sha256=YecAEeCOjSIwIBu2O5vEw72atMSL37cXGrbEuukI07o,4582
|
|
@@ -56,7 +56,7 @@ code_puppy/command_line/file_path_completion.py,sha256=gw8NpIxa6GOpczUJRyh7VNZwo
|
|
|
56
56
|
code_puppy/command_line/load_context_completion.py,sha256=a3JvLDeLLSYxVgTjAdqWzS4spjv6ccCrK2LKZgVJ1IM,2202
|
|
57
57
|
code_puppy/command_line/mcp_completion.py,sha256=eKzW2O7gun7HoHekOW0XVXhNS5J2xCtK7aaWyA8bkZk,6952
|
|
58
58
|
code_puppy/command_line/model_picker_completion.py,sha256=nDnlf0qFCG2zAm_mWW2eMYwVC7eROVQrFe92hZqOKa8,6810
|
|
59
|
-
code_puppy/command_line/model_settings_menu.py,sha256
|
|
59
|
+
code_puppy/command_line/model_settings_menu.py,sha256=O5nPp_OyShFcXzpSmsCeYsnnVNrSwcTBFY9bzcayvj0,32263
|
|
60
60
|
code_puppy/command_line/motd.py,sha256=OoNxwewsckexSgJ5H5y40IawP-TzqlqY-rqFUdRbIhs,2186
|
|
61
61
|
code_puppy/command_line/pin_command_completion.py,sha256=juSvdqRpk7AdfkPy1DJx5NzfEUU5KYGlChvP0hisM18,11667
|
|
62
62
|
code_puppy/command_line/prompt_toolkit_completion.py,sha256=x4Of32g8oH9ckhx-P6BigV7HUUhhjL8xkvK03uq9HRw,27308
|
|
@@ -74,7 +74,7 @@ code_puppy/command_line/mcp/help_command.py,sha256=dU3ekOjjNKxRS-RjUXJZ7PBwmJeIe
|
|
|
74
74
|
code_puppy/command_line/mcp/install_command.py,sha256=lmUyMUWtkGuy1SOQRHjQgt8mD3t1agVMQfEL5_TOzTM,8364
|
|
75
75
|
code_puppy/command_line/mcp/install_menu.py,sha256=lUg7x43aK4NRIS3XrPhvhmcQwjyNb-rzrL-2GL6oYiw,24558
|
|
76
76
|
code_puppy/command_line/mcp/list_command.py,sha256=UKQFPlhT9qGMCyG5VKjvnSMzDDtfAhIaKU_eErgZJDg,3181
|
|
77
|
-
code_puppy/command_line/mcp/logs_command.py,sha256=
|
|
77
|
+
code_puppy/command_line/mcp/logs_command.py,sha256=IZzOadnI2ch6j4AcdjuHwJYJWKw1_K1rrCh9_aVK94k,7759
|
|
78
78
|
code_puppy/command_line/mcp/remove_command.py,sha256=hyU_tKJWfyLnmufrFVLwlF0qFEbghXBVMOvSgWvaEgA,2755
|
|
79
79
|
code_puppy/command_line/mcp/restart_command.py,sha256=w5EcDac09iCvPBAR0u2M5KSIhASqTu5uZwsjCJ4JLhk,3588
|
|
80
80
|
code_puppy/command_line/mcp/search_command.py,sha256=mDkSz_KjPbvlO9U7oYUKJlqqY4QM90gWKO2xsH2i3SA,4244
|
|
@@ -86,17 +86,18 @@ code_puppy/command_line/mcp/stop_command.py,sha256=iMzk9h6NuUDg0hhI5eDLem5VS8IwB
|
|
|
86
86
|
code_puppy/command_line/mcp/test_command.py,sha256=eV8u5KKClRK1M2_os1zA78b9TDuYUG_uEk7AfMfD2HY,3691
|
|
87
87
|
code_puppy/command_line/mcp/utils.py,sha256=0Wt4ttYgSlVvtusYmBLKXSkjAjcsDiUxcZQAoFLUNnE,3625
|
|
88
88
|
code_puppy/command_line/mcp/wizard_utils.py,sha256=M5X8RchkQujKYKORsXJnUq2kJHzmNfutIUrsHmfzi7k,11126
|
|
89
|
-
code_puppy/mcp_/__init__.py,sha256=
|
|
89
|
+
code_puppy/mcp_/__init__.py,sha256=P9bmVX5UDmzQDqHMylOxuo5Hi82E30pPSMOYw8lEx7Q,1781
|
|
90
90
|
code_puppy/mcp_/async_lifecycle.py,sha256=pTQrwQCVgjad8EJeRTSHtIeSQRgT_8r5BeLv-1SgKog,7772
|
|
91
|
-
code_puppy/mcp_/blocking_startup.py,sha256=
|
|
91
|
+
code_puppy/mcp_/blocking_startup.py,sha256=27R2wwLVDu5i19IP90KmCn_zHrvVcHVNU8d9c8EcTeI,14780
|
|
92
92
|
code_puppy/mcp_/captured_stdio_server.py,sha256=t_mnCjtiopRsyi4Aa97rFzDVxEQmb-u94sWJsj2FP8k,8925
|
|
93
93
|
code_puppy/mcp_/circuit_breaker.py,sha256=a83YwXux9h4R6zBWBUrCIqtp2ffyl7JZEoK2tErG_0I,8601
|
|
94
94
|
code_puppy/mcp_/config_wizard.py,sha256=JNNpgnSD6PFSyS3pTdEdD164oXd2VKp4VHLSz3ToH1w,16511
|
|
95
95
|
code_puppy/mcp_/dashboard.py,sha256=VtaFxLtPnbM_HL2TXRDAg6IqcM-EcFkoghGgkfhMrKI,9417
|
|
96
96
|
code_puppy/mcp_/error_isolation.py,sha256=mpPBiH17zTXPsOEAn9WmkbwQwnt4gmgiaWv87JBJbUo,12426
|
|
97
97
|
code_puppy/mcp_/health_monitor.py,sha256=n5R6EeYOYbUucUFe74qGWCU3g6Mep5UEQbLF0wbT0dU,19688
|
|
98
|
-
code_puppy/mcp_/managed_server.py,sha256=
|
|
98
|
+
code_puppy/mcp_/managed_server.py,sha256=KmrFQAQBS-XHuvkuWUltFJk2jiR0pt55gdQlI0gA2QE,14304
|
|
99
99
|
code_puppy/mcp_/manager.py,sha256=pJ4cALicTxfwG2JIjJraLLf0Mzes-cEVAKIcUwfOoKA,29172
|
|
100
|
+
code_puppy/mcp_/mcp_logs.py,sha256=o4pSHwELWIjEjqhfaMMEGrBvb159-VIgUp21E707BPo,6264
|
|
100
101
|
code_puppy/mcp_/registry.py,sha256=U_t12WQ-En-KGyZoiTYdqlhp9NkDTWafu8g5InvF2NM,15774
|
|
101
102
|
code_puppy/mcp_/retry_manager.py,sha256=evVxbtrsHNyo8UoI7zpO-NVDegibn82RLlgN8VKewA8,10665
|
|
102
103
|
code_puppy/mcp_/server_registry_catalog.py,sha256=fr_wsr99BphnaDiPLcN60t4Mp62lbt8rYNOpghKnqEA,39429
|
|
@@ -129,7 +130,7 @@ code_puppy/plugins/claude_code_oauth/__init__.py,sha256=mCcOU-wM7LNCDjr-w-WLPzom
|
|
|
129
130
|
code_puppy/plugins/claude_code_oauth/config.py,sha256=DjGySCkvjSGZds6DYErLMAi3TItt8iSLGvyJN98nSEM,2013
|
|
130
131
|
code_puppy/plugins/claude_code_oauth/register_callbacks.py,sha256=0NeX1hhkYIlVfPmjZ1xmcf1yueDAJh_FMUmvJlxSO-E,10057
|
|
131
132
|
code_puppy/plugins/claude_code_oauth/test_plugin.py,sha256=yQy4EeZl4bjrcog1d8BjknoDTRK75mRXXvkSQJYSSEM,9286
|
|
132
|
-
code_puppy/plugins/claude_code_oauth/utils.py,sha256=
|
|
133
|
+
code_puppy/plugins/claude_code_oauth/utils.py,sha256=wDaOU21zB3y6PWkuMXwE4mFjQuffyDae-vXysPTS-w8,13438
|
|
133
134
|
code_puppy/plugins/customizable_commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
134
135
|
code_puppy/plugins/customizable_commands/register_callbacks.py,sha256=zVMfIzr--hVn0IOXxIicbmgj2s-HZUgtrOc0NCDOnDw,5183
|
|
135
136
|
code_puppy/plugins/example_custom_command/README.md,sha256=5c5Zkm7CW6BDSfe3WoLU7GW6t5mjjYAbu9-_pu-b3p4,8244
|
|
@@ -158,10 +159,10 @@ code_puppy/tools/browser/browser_scripts.py,sha256=sNb8eLEyzhasy5hV4B9OjM8yIVMLV
|
|
|
158
159
|
code_puppy/tools/browser/browser_workflows.py,sha256=nitW42vCf0ieTX1gLabozTugNQ8phtoFzZbiAhw1V90,6491
|
|
159
160
|
code_puppy/tools/browser/camoufox_manager.py,sha256=RZjGOEftE5sI_tsercUyXFSZI2wpStXf-q0PdYh2G3I,8680
|
|
160
161
|
code_puppy/tools/browser/vqa_agent.py,sha256=DBn9HKloILqJSTSdNZzH_PYWT0B2h9VwmY6akFQI_uU,2913
|
|
161
|
-
code_puppy-0.0.
|
|
162
|
-
code_puppy-0.0.
|
|
163
|
-
code_puppy-0.0.
|
|
164
|
-
code_puppy-0.0.
|
|
165
|
-
code_puppy-0.0.
|
|
166
|
-
code_puppy-0.0.
|
|
167
|
-
code_puppy-0.0.
|
|
162
|
+
code_puppy-0.0.319.data/data/code_puppy/models.json,sha256=mTpmJH0UJlmX8M2KVPbxMWb99de3IxKXCWO-B23b6xo,3101
|
|
163
|
+
code_puppy-0.0.319.data/data/code_puppy/models_dev_api.json,sha256=wHjkj-IM_fx1oHki6-GqtOoCrRMR0ScK0f-Iz0UEcy8,548187
|
|
164
|
+
code_puppy-0.0.319.dist-info/METADATA,sha256=XgkIpN2_glvNl1BcGksFRRikU_PCKdR-eQkDAIkr1ms,28030
|
|
165
|
+
code_puppy-0.0.319.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
166
|
+
code_puppy-0.0.319.dist-info/entry_points.txt,sha256=Tp4eQC99WY3HOKd3sdvb22vZODRq0XkZVNpXOag_KdI,91
|
|
167
|
+
code_puppy-0.0.319.dist-info/licenses/LICENSE,sha256=31u8x0SPgdOq3izJX41kgFazWsM43zPEF9eskzqbJMY,1075
|
|
168
|
+
code_puppy-0.0.319.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|