diff --git a/wake_ai/cli.py b/wake_ai/cli.py index 3596de2b..a78f6f40 100644 --- a/wake_ai/cli.py +++ b/wake_ai/cli.py @@ -10,7 +10,7 @@ import rich.traceback from rich.console import Console from rich.logging import RichHandler -from wake_ai.utils.logging import get_logger, set_debug +from wake_ai.utils.logging import get_logger, set_verbose_level if TYPE_CHECKING: from wake_ai import AIWorkflow @@ -256,8 +256,13 @@ def get_class_that_defined_method(meth): @click.option( "--verbose", "-v", - is_flag=True, - help="Enable verbose logging (debug level)" + count=True, + help="Enable verbose logging (-v: info, -vv: debug, -vvv: trace)" +) +@click.option( + "--verbose-filter", + type=str, + help="Filter verbose output by tool names (comma-separated, e.g., 'Bash,Edit,Glob,Grep,MultiEdit,NotebookEdit,NotebookRead,Read,Task,TodoWrite,WebFetch,WebSearch,Write,mcp__wake,mcp__wake__function_name')" ) @click.option( "--no-progress", @@ -271,7 +276,7 @@ def get_class_that_defined_method(meth): help="List all available workflows" ) @click.pass_context -def main(ctx: click.Context, working_dir: str | None, model: str, resume: bool, execution_dir: str | None, export: str | None, no_cleanup: bool, verbose: bool, no_progress: bool, list: bool): +def main(ctx: click.Context, working_dir: str | None, model: str, resume: bool, execution_dir: str | None, export: str | None, no_cleanup: bool, verbose: int, verbose_filter: str | None, no_progress: bool, list: bool): """AI-powered smart contract security analysis. This command runs various AI workflows for smart contract analysis @@ -279,10 +284,21 @@ def main(ctx: click.Context, working_dir: str | None, model: str, resume: bool, """ rich.traceback.install(console=console) - # Set logging level based on verbose flag - if verbose: - set_debug(True) - console.print("[dim]Debug logging enabled[/dim]") + # Set logging level based on verbose count + if verbose >= 1: + set_verbose_level(verbose) + if verbose == 1: + console.print("[dim]Verbose logging enabled (info level)[/dim]") + elif verbose == 2: + console.print("[dim]Debug logging enabled[/dim]") + elif verbose >= 3: + console.print("[dim]Trace logging enabled (maximum verbosity)[/dim]") + # TODO: Implement trace level logging if needed + + if verbose_filter: + from wake_ai.utils.logging import set_verbose_filters + set_verbose_filters(verbose_filter) + console.print(f"[dim]Verbose output filtered to: {verbose_filter}[/dim]") # Handle list flag if list: diff --git a/wake_ai/core/claude.py b/wake_ai/core/claude.py index 00d1e087..15a3f58f 100644 --- a/wake_ai/core/claude.py +++ b/wake_ai/core/claude.py @@ -14,7 +14,7 @@ from pathlib import Path from typing import Dict, List, Optional, Any, Union, AsyncIterator from dataclasses import dataclass -from wake_ai.utils.logging import get_debug +from wake_ai.utils.logging import get_verbose_level from rich.console import Console @@ -37,10 +37,45 @@ # Set up logging logger = logging.getLogger(__name__) +@dataclass +class Write: + file_path: str + content: str + +@dataclass +class Read: + file_path: str + limit: int | None = None + offset: int | None = None + +@dataclass +class Bash: + command: str + description: str + +@dataclass +class TodoItem: + content: str + status: str + activeForm: str + +@dataclass +class TodoWrite: + todos: list[TodoItem] + + @classmethod + def from_dict(cls, data: dict) -> 'TodoWrite': + """Factory method to create TodoWrite from raw dictionary data.""" + todo_items = [TodoItem(**todo_dict) for todo_dict in data.get('todos', [])] + return cls(todos=todo_items) + +@dataclass +class Edit: + file_path: str + old_string: str + new_string: str + -### VERBOSE MODE CONFIGURATIONS ### -MAX_TOOL_RESULT_LINES: int = 10 -SHOW_FULL_TOOL_RESULT: bool = False COLORS = { "todo_header": "bold blue", "todo_complete": "bold green", @@ -119,11 +154,13 @@ def __init__( self.working_dir = Path(working_dir) if working_dir else Path.cwd() self.execution_dir = Path( execution_dir) if execution_dir else Path.cwd() - self.verbose = get_debug() + self.verbose = get_verbose_level() self.last_session_id = session_id self.session_history: List[str] = [] # Track all session IDs self.console = console + # distingish from tool Id to tool name + self.tool_use_id_to_name: dict[str, str] = {} if session_id: self.session_history.append(session_id) @@ -139,7 +176,7 @@ def __init__( from .utils import validate_claude_cli validate_claude_cli() - def format_todo_list(self, todos: List[Dict[str, Any]]) -> None: + def format_todo_list(self, todo_write: TodoWrite) -> None: """Display a formatted todo list with color-coded status indicators. Uses Rich styling to show todo items with appropriate icons and colors @@ -148,13 +185,12 @@ def format_todo_list(self, todos: List[Dict[str, Any]]) -> None: # Print header without text wrapping to maintain formatting self.console.print( - f" 📋 [{COLORS['todo_header']}]Todo List:[/{COLORS['todo_header']}]", + f"📋 [{COLORS['todo_header']}]Todo List:[/{COLORS['todo_header']}]", highlight=False, ) - for todo in todos: - status = todo.get("status", "pending") - content = todo.get("content", "") - todo_id = todo.get("id", "") + for todo in todo_write.todos: + status = todo.status + content = todo.content # Select appropriate visual indicators for each status type if status == "completed": @@ -168,7 +204,7 @@ def format_todo_list(self, todos: List[Dict[str, Any]]) -> None: style = COLORS["todo_pending"] self.console.print( - f" {icon} [[{style}]{todo_id}[/{style}]] {content}", highlight=False + f"{icon} [{style}] {content} [/{style}] ", highlight=False ) def print_top_and_bottom(self, content: Any, style: str) -> None: @@ -185,25 +221,33 @@ def print_top_and_bottom(self, content: Any, style: str) -> None: string_content = str(content) lines = string_content.split("\n") - if SHOW_FULL_TOOL_RESULT or len(lines) <= MAX_TOOL_RESULT_LINES * 2: + max_tool_result_lines = 0 + if self.verbose == 1: + max_tool_result_lines = 1 + elif self.verbose == 2: + max_tool_result_lines = 4 + elif self.verbose == 3: + max_tool_result_lines = 10 + + if self.verbose >= 4 or len(lines) <= max_tool_result_lines * 2: # Content is short enough to display in full for line in lines: self.console.print(line, style=style, highlight=False) else: # Content is too long, show truncated version # Display first portion - for line in lines[:MAX_TOOL_RESULT_LINES]: + for line in lines[:max_tool_result_lines]: self.console.print(line, style=style, highlight=False) # Show truncation indicator with count of omitted lines - omitted = len(lines) - MAX_TOOL_RESULT_LINES * 2 + omitted = len(lines) - max_tool_result_lines * 2 self.console.print( f"[{COLORS['truncation']}]... ({omitted} lines omitted by wake-ai) ...[/{COLORS['truncation']}]", highlight=False, ) # Display final portion - for line in lines[-MAX_TOOL_RESULT_LINES:]: + for line in lines[-max_tool_result_lines:]: self.console.print(line, style=style, highlight=False) def format_tool_use(self, block: ToolUseBlock) -> None: @@ -213,16 +257,189 @@ def format_tool_use(self, block: ToolUseBlock) -> None: todo lists, while using standard formatting for other tool types. """ + self.tool_use_id_to_name[block.id] = block.name + + # Check if this tool should be shown based on filters + from wake_ai.utils.logging import should_show_tool + if not should_show_tool(block.name): + # print(f"filtering {block.name} tool use") + return # Skip this tool + # TodoWrite gets custom formatting to display structured todo lists - if block.name == "TodoWrite" and "todos" in block.input: + if block.name == "TodoWrite": + todo_write_input = TodoWrite.from_dict(block.input) + print_str = f"Using {block.name}: " + + completed_count = 0 + in_progress_count = 0 + pending_count = 0 + + if self.verbose == 1: + next_tasks_str = "" + for todo in todo_write_input.todos: + if todo.status == "in_progress": + next_tasks_str += f"{todo.content}, " + in_progress_count += 1 + elif todo.status == "completed": + completed_count += 1 + else: # if todo.status == "pending" + pending_count += 1 + + total_count = completed_count + in_progress_count + pending_count + print_str += f"{completed_count}/{total_count} done." + + if next_tasks_str: + print_str += f" Next -> {next_tasks_str}" + + self.console.print( + f"[{COLORS['tool_use']}]{print_str}[/{COLORS['tool_use']}]" + ) + return + elif self.verbose >= 2: + self.format_todo_list(todo_write_input) + return + + elif block.name == "Bash": + bash_input = Bash(**block.input) + + print_str = f"Using {block.name}: {bash_input.command}" + + if bash_input.description and self.verbose >= 2: + print_str += f" # {bash_input.description}" + self.console.print( - f"[{COLORS['tool_use']}]Using tool: {block.name}[/{COLORS['tool_use']}]" + f"[{COLORS['tool_use']}]{print_str}[/{COLORS['tool_use']}]" ) - self.format_todo_list(block.input.get("todos", [])) - else: + return + + elif block.name == "Read": + + read_input = Read(**block.input) + + try: + path = Path(read_input.file_path).relative_to(self.execution_dir) + except: + path = Path(read_input.file_path) + + print_str = f"Using {block.name}: {path}" + + if read_input.limit: + print_str += f" limit: {read_input.limit}" + + if read_input.offset: + print_str += f" offset: {read_input.offset}" + + self.console.print( + f"[{COLORS['tool_use']}]{print_str}[/{COLORS['tool_use']}]" + ) + return + + elif block.name == "Write": + write_input = Write(**block.input) + + if self.verbose == 1: + try: + path = Path(write_input.file_path).relative_to(self.execution_dir) + except: + path = Path(write_input.file_path) + print_str = f"Using Write: {path}: {len(write_input.content.split('\n'))} lines" + self.console.print( + f"[{COLORS['tool_use']}]{print_str}[/{COLORS['tool_use']}]" + ) + return + + elif block.name == "Edit": + edit_input = Edit(**block.input) + if self.verbose == 1: + + try: + path = Path(edit_input.file_path).relative_to(self.execution_dir) + except: + path = Path(edit_input.file_path) + + print_str = f"Using Edit: {path}" + + if edit_input.old_string: + print_str += f" old_string_lines_length: {len(edit_input.old_string.split('\n'))}" + + if edit_input.new_string: + print_str += f" new_string_lines_length: {len(edit_input.new_string.split('\n'))}" + + self.console.print( + f"[{COLORS['tool_use']}]{print_str}[/{COLORS['tool_use']}]" + ) + return + + elif "mcp__" in block.name: + # Format MCP tool calls nicely + print_str = f"Using {block.name}(" + + # Format parameters + params = [] + for key, value in block.input.items(): + # I do not think this is good idea. but .... TODO + if key == 'file_path' and isinstance(value, str): + # Try to make file path relative + try: + relative_path = Path(value).relative_to(self.execution_dir) + params.append(f"{key}={relative_path}") + except (ValueError, TypeError): + params.append(f"{key}={value}") + else: + params.append(f"{key}={value}") + + print_str += ", ".join(params) + ")" + self.console.print( - f"[{COLORS['tool_use']}]Using tool: {block.name}[/{COLORS['tool_use']}]" + f"[{COLORS['tool_use']}]{print_str}[/{COLORS['tool_use']}]" ) + return + elif block.name == "Grep": + print_str = f"Using Grep(" + params = [] + for key, value in block.input.items(): + # I do not think this is good idea. but .... TODO + if key == 'file_path' and isinstance(value, str): + # Try to make file path relative + try: + relative_path = Path(value).relative_to(self.execution_dir) + params.append(f"{key}={relative_path}") + except (ValueError, TypeError): + params.append(f"{key}={value}") + else: + params.append(f"{key}={value}") + + print_str += ", ".join(params) + ")" + + self.console.print( + f"[{COLORS['tool_use']}]{print_str}[/{COLORS['tool_use']}]" + ) + return + + elif block.name == "Glob": + print_str = f"Using Glob(" + params = [] + for key, value in block.input.items(): + params.append(f"{key}={value}") + print_str += ", ".join(params) + ")" + self.console.print(f"[{COLORS['tool_use']}]{print_str}[/{COLORS['tool_use']}]") + return + elif block.name == "LS": + print_str = f"Using LS(" + params = [] + for key, value in block.input.items(): + params.append(f"{key}={value}") + print_str += ", ".join(params) + ")" + self.console.print(f"[{COLORS['tool_use']}]{print_str}[/{COLORS['tool_use']}]") + return + + # print(block) + + self.console.print( + f"[{COLORS['tool_use']}]Using tool: {block.name}[/{COLORS['tool_use']}]" + ) + + if self.verbose >= 2: # Standard tool display format for all other tool types for key, value in block.input.items(): self.console.print( @@ -238,14 +455,128 @@ def format_tool_result(self, block: ToolResultBlock) -> None: on success/error status. """ + tool_name = self.tool_use_id_to_name.get(block.tool_use_id) + + if tool_name is None: + raise ValueError(f"Tool name not found for tool use id: {block.tool_use_id}") + + from wake_ai.utils.logging import should_show_tool + if not should_show_tool(tool_name): + # print(f"filtering {tool_name} tool result") + return + # Apply error styling for failed operations, normal styling otherwise if block.is_error: header_style = COLORS["tool_error"] content_style = "red" else: + block.is_error = False header_style = COLORS["tool_result"] content_style = COLORS["tool_result_json"] + if tool_name == "TodoWrite" and (block.is_error == False) and self.verbose <= 1: # TodoWrite Result is not interesting usually + return + + elif tool_name == "Bash" and (block.is_error == False): # Bash Result is not interesting usually + # bash result should not be json. + + if self.verbose == 1: + print_str = f"Bash Result:" + if isinstance(block.content, str): # shoud be + print_str += f" {len(block.content.split('\n'))} lines" + else: + # return list but shold not happen. + print_str += f" {len(block.content or [])} items" + self.console.print(f"[{header_style}]{print_str}[/{header_style}]") + return + + elif self.verbose == 2: + # 5 lines from top to bottom, handled below + pass + elif self.verbose == 3: + # full print, handled below + pass + + elif tool_name == "Read" and (block.is_error == False): + # read result should not be json. + print_str = f"Read Result:" + + if isinstance(block.content, str): # shoud be + # just print number of lines + print_str += f" {len(block.content.split('\n'))} lines" + else: + # return list but shold not happen. + print_str += f" {len(block.content or [])} items" + + self.console.print(f"[{header_style}]{print_str}[/{header_style}]") + return + + elif tool_name == "Edit" and (block.is_error == False): + if self.verbose == 1: + print_str = f"Edit Result: success" + self.console.print(f"[{header_style}]{print_str}[/{header_style}]") + return + + elif "mcp__" in tool_name and (block.is_error == False): + if self.verbose == 1: + print_str = f"{tool_name} Result:" + if isinstance(block.content, str): + print_str += f" {len(block.content.split('\n'))} lines" + else: + # return list but shold not happen. + print_str += f" {len(block.content or [])} items" + self.console.print(f"[{header_style}]{print_str}[/{header_style}]") + return + + elif tool_name == "Write" and (block.is_error == False): + if self.verbose == 1: + print_str = f"Write Result: success" + self.console.print(f"[{header_style}]{print_str}[/{header_style}]") + return + + elif tool_name == "Grep" and (block.is_error == False): + if self.verbose == 1: + print_str = f"Grep Result:" + if isinstance(block.content, str): + print_str += f" {len(block.content.split('\n'))} lines" + else: + # return list but shold not happen. + print_str += f" {len(block.content or [])} items" + + self.console.print(f"[{header_style}]{print_str}[/{header_style}]") + return + elif tool_name == "Glob" and (block.is_error == False): + if self.verbose == 1: + print_str = f"Glob Result:" + if isinstance(block.content, str): + print_str += f" {len(block.content.split('\n'))} lines" + else: + # return list but shold not happen. + print_str += f" {len(block.content or [])} items" + self.console.print(f"[{header_style}]{print_str}[/{header_style}]") + return + + elif tool_name == "LS" and (block.is_error == False): + if self.verbose == 1: + print_str = f"LS Result:" + if isinstance(block.content, str): + print_str += f" {len(block.content.split('\n'))} lines" + else: + # return list but shold not happen. + print_str += f" {len(block.content or [])} items" + self.console.print(f"[{header_style}]{print_str}[/{header_style}]") + return + + elif block.is_error == True and isinstance(block.content, str): + print_str = f"{tool_name} Error: {block.content}" + self.console.print(f"[{header_style}][/{header_style}]") + return + elif block.is_error == True and isinstance(block.content, list): + print_str = f"{tool_name} Error: {block.content}" + self.console.print(f"[{header_style}]{print_str}[/{header_style}]") + return + + # print(block) if isinstance(block.content, str): # Attempt JSON parsing for structured display try: @@ -264,31 +595,70 @@ def format_tool_result(self, block: ToolResultBlock) -> None: self.print_top_and_bottom(block.content, style=content_style) elif isinstance(block.content, list): # Process list-type results (multiple items) - for item in block.content: - text_content = item.get("text") if hasattr( - item, "get") else None - try: - if text_content: - parsed = json.loads(text_content) - self.console.print( - f"[{header_style}]Tool Result (JSON):[/{header_style}]" - ) - self.console.print_json(json.dumps(parsed), indent=2) - else: - self.console.print( - f"[{header_style}]Tool Result:[/{header_style}]" - ) - self.print_top_and_bottom(item, style=content_style) - except json.JSONDecodeError: + # print only the first and last item if the list is more than 2 items. + content_list = block.content + if self.verbose == 1: + if len(content_list) > 2: + # Show first item + self._print_list_item(content_list[0], header_style, content_style) + + # Show truncation indicator + omitted_count = len(content_list) - 1 self.console.print( - f"[{header_style}]Tool Result:[/{header_style}]") - self.print_top_and_bottom( - text_content, style=content_style) - except Exception: + f"[{COLORS['truncation']}]... ({omitted_count} items omitted by wake-ai) ...[/{COLORS['truncation']}]", + highlight=False, + ) + + elif self.verbose == 2: + if len(content_list) > 2: + # Show first item + self._print_list_item(content_list[0], header_style, content_style) + + # Show truncation indicator + omitted_count = len(content_list) - 2 self.console.print( - f"[{header_style}]Tool Result:[/{header_style}]") - self.print_top_and_bottom(item, style=content_style) + f"[{COLORS['truncation']}]... ({omitted_count} items omitted by wake-ai) ...[/{COLORS['truncation']}]", + highlight=False, + ) + + # Show last item + self._print_list_item(content_list[-1], header_style, content_style) + else: + # Show all items for short lists + for item in content_list: + self._print_list_item(item, header_style, content_style) + + elif(self.verbose == 3): + # Show all items for short lists + for item in content_list: + self._print_list_item(item, header_style, content_style) + + def _print_list_item(self, item: Any, header_style: str, content_style: str) -> None: + """Helper method to print a single list item with proper formatting.""" + text_content = item.get("text") if hasattr(item, "get") else None + + try: + if text_content: + parsed = json.loads(text_content) + self.console.print( + f"[{header_style}]Tool Result (JSON):[/{header_style}]" + ) + self.console.print_json(json.dumps(parsed), indent=2) + else: + self.console.print( + f"[{header_style}]Tool Result:[/{header_style}]" + ) + self.print_top_and_bottom(item, style=content_style) + except json.JSONDecodeError: + self.console.print( + f"[{header_style}]Tool Result:[/{header_style}]") + self.print_top_and_bottom( + text_content, style=content_style) + except Exception: + self.console.print( + f"[{header_style}]Tool Result:[/{header_style}]") + self.print_top_and_bottom(item, style=content_style) else: # Handle empty or unsupported result types self.console.print( @@ -303,13 +673,16 @@ def handle_verbose_message(self, message: Message) -> None: """ if isinstance(message, AssistantMessage): for block in message.content: + if isinstance(block, ToolResultBlock): # Standard tool execution result + ## change according to verbose level self.format_tool_result(block) elif isinstance(block, TextBlock): # AI reasoning and explanation text self.console.print(block.text, style=COLORS["thinking"]) elif isinstance(block, ToolUseBlock): + ## change according to verbose level self.format_tool_use(block) else: self.console.print( @@ -317,22 +690,30 @@ def handle_verbose_message(self, message: Message) -> None: ) elif isinstance(message, SystemMessage): + if message.subtype == "init": - self.console.print( - f"[{COLORS['system_msg']}]System: {message.subtype}[/{COLORS['system_msg']}]" - ) - self.console.print( - f" [{COLORS['system_msg']}]CWD: {message.data.get('cwd', 'N/A')}[/{COLORS['system_msg']}]" - ) - self.console.print( - f" [{COLORS['system_msg']}]Session: {message.data.get('session_id', 'N/A')}[/{COLORS['system_msg']}]" - ) + if self.verbose <= 2: + self.console.print( + f"[{COLORS['system_msg']}]System init: cwd: {message.data.get('cwd', 'N/A')} session: {message.data.get('session_id', 'N/A')}[/{COLORS['system_msg']}]" + ) + else: + self.console.print( + f"[{COLORS['system_msg']}]System: {message.subtype}[/{COLORS['system_msg']}]" + ) + try: + print_str = json.dumps(message.data) + except: + print_str = str(message.data) + + self.console.print( + f"[{COLORS['system_msg']}]{print_str}[/{COLORS['system_msg']}]" + ) else: self.console.print( f"[{COLORS['system_msg']}]System: {message.subtype}[/{COLORS['system_msg']}]" ) self.console.print( - f" [{COLORS['system_msg']}]{message.data}[/{COLORS['system_msg']}]" + f"[{COLORS['system_msg']}]{message.data}[/{COLORS['system_msg']}]" ) elif isinstance(message, UserMessage): @@ -340,6 +721,17 @@ def handle_verbose_message(self, message: Message) -> None: if isinstance(content, ToolResultBlock): # Specialized tool result (e.g., from MCP server) self.format_tool_result(content) + elif isinstance(content, TextBlock): + if self.verbose == 1: + # just print 1 line by content.text.split('\n')[0] + texts = content.text.split('\n') + print_str = "User Text:" + print_str += texts[0] + if len(texts) > 1: + print_str += f"... with ({len(texts) - 1} lines omitted)" + self.console.print(print_str, style=COLORS["thinking"]) + else: + self.console.print(content.text, style=COLORS["thinking"]) else: self.console.print( f"[{COLORS['unknown']}]Unknown user content: {content}[/{COLORS['unknown']}]" @@ -463,7 +855,10 @@ async def query_async( result = message else: if self.verbose: - self.handle_verbose_message(message) + try: + self.handle_verbose_message(message) + except Exception as e: + logger.error(f"Error during handling verbose message: {e}") # Handle official SDK exceptions as documented in: # https://github.com/anthropics/claude-code-sdk-python diff --git a/wake_ai/utils/logging.py b/wake_ai/utils/logging.py index 9686ff48..87c4f903 100644 --- a/wake_ai/utils/logging.py +++ b/wake_ai/utils/logging.py @@ -1,8 +1,10 @@ import logging from typing import Optional -_debug: bool = False +_verbose_level: int = 0 _created_logger_names: set[str] = set() +_verbose_filters: Optional[set[str]] = None + def get_logger(name: str, override_level: Optional[int] = None) -> logging.Logger: @@ -12,16 +14,44 @@ def get_logger(name: str, override_level: Optional[int] = None) -> logging.Logge logger.setLevel(override_level) else: _created_logger_names.add(name) - logger.setLevel(logging.DEBUG if _debug else logging.INFO) + logger.setLevel(logging.DEBUG if _verbose_level else logging.INFO) return logger -def set_debug(debug: bool) -> None: - global _debug - _debug = debug +def set_verbose_level(verbose_level: int) -> None: + global _verbose_level + _verbose_level = verbose_level for name in _created_logger_names: - get_logger(name).setLevel(logging.DEBUG if _debug else logging.INFO) + get_logger(name).setLevel(logging.DEBUG if _verbose_level else logging.INFO) + + +def get_verbose_level() -> int: + return _verbose_level + + +def set_verbose_filters(filters: Optional[str]) -> None: + """Set verbose filters from comma-separated string.""" + global _verbose_filters + if filters: + _verbose_filters = {f.strip() for f in filters.split(',')} + else: + _verbose_filters = None + + +def should_show_tool(tool_name: str) -> bool: + """Check if a tool should be shown based on filters.""" + if _verbose_filters is None: + return True # No filter = show everything + + # Direct match (e.g., "Bash", "Edit", "mcp__wake__analyze_state_variables") + if tool_name in _verbose_filters: + return True + # For MCP tools, also check if the server name matches + # e.g., "mcp__wake" filter will match "mcp__wake__analyze_state_variables" + if tool_name.startswith("mcp__"): + for filter_name in _verbose_filters: + if filter_name.startswith("mcp__") and tool_name.startswith(filter_name + "__"): + return True -def get_debug() -> bool: - return _debug + return False