From bd21d99ca4a2d7c2798598cc519f4450768caa99 Mon Sep 17 00:00:00 2001 From: meditationduck Date: Thu, 11 Sep 2025 14:39:09 +0200 Subject: [PATCH 1/4] =?UTF-8?q?=E2=9C=A8=20Add=20verbose=20output=20filter?= =?UTF-8?q?ing=20and=20enhance=20logging=20in=20CLI=20and=20ClaudeCodeSess?= =?UTF-8?q?ion?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Introduced a new `--verbose-filter` option in the CLI to filter verbose output by tool names. - Enhanced logging in `ClaudeCodeSession` to conditionally display tool usage and results based on the specified filters. - Added utility functions in the logging module to manage verbose filters and determine visibility of tools based on these filters. --- wake_ai/cli.py | 14 ++++++- wake_ai/core/claude.py | 90 +++++++++++++++++++++++++++++----------- wake_ai/utils/logging.py | 29 +++++++++++++ 3 files changed, 106 insertions(+), 27 deletions(-) diff --git a/wake_ai/cli.py b/wake_ai/cli.py index 3596de2b..2f163b37 100644 --- a/wake_ai/cli.py +++ b/wake_ai/cli.py @@ -259,6 +259,11 @@ def get_class_that_defined_method(meth): is_flag=True, help="Enable verbose logging (debug level)" ) +@click.option( + "--verbose-filter", + type=str, + help="Filter verbose output by tool names (comma-separated, e.g., 'Bash,Edit,mcp__wake,TodoWrite,Read,Grep')" +) @click.option( "--no-progress", is_flag=True, @@ -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: bool, 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 @@ -282,7 +287,12 @@ def main(ctx: click.Context, working_dir: str | None, model: str, resume: bool, # Set logging level based on verbose flag if verbose: set_debug(True) - console.print("[dim]Debug logging enabled[/dim]") + if verbose_filter: + from wake_ai.utils.logging import set_verbose_filters + set_verbose_filters(verbose_filter) + console.print(f"[dim]Debug logging enabled with filter: {verbose_filter}[/dim]") + else: + console.print("[dim]Debug logging enabled[/dim]") # Handle list flag if list: diff --git a/wake_ai/core/claude.py b/wake_ai/core/claude.py index 00d1e087..99080172 100644 --- a/wake_ai/core/claude.py +++ b/wake_ai/core/claude.py @@ -124,6 +124,8 @@ def __init__( 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) @@ -212,6 +214,13 @@ def format_tool_use(self, block: ToolUseBlock) -> None: Provides special formatting for TodoWrite tools to show structured 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: @@ -238,6 +247,16 @@ 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"] @@ -264,31 +283,52 @@ 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: - 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) + + # print only the first and last item if the list is more than 2 items. + content_list = block.content + 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"[{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) + + 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( diff --git a/wake_ai/utils/logging.py b/wake_ai/utils/logging.py index 9686ff48..ab608cb8 100644 --- a/wake_ai/utils/logging.py +++ b/wake_ai/utils/logging.py @@ -3,6 +3,7 @@ _debug: bool = False _created_logger_names: set[str] = set() +_verbose_filters: Optional[set[str]] = None def get_logger(name: str, override_level: Optional[int] = None) -> logging.Logger: @@ -25,3 +26,31 @@ def set_debug(debug: bool) -> None: def get_debug() -> bool: return _debug + + +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 + + return False From 9c3b492c150f17d5852fdb2ae1ad0d5b65812a02 Mon Sep 17 00:00:00 2001 From: meditationduck Date: Fri, 12 Sep 2025 10:32:59 +0200 Subject: [PATCH 2/4] =?UTF-8?q?=E2=9C=85=20format=20for=20-v?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- wake_ai/cli.py | 26 ++- wake_ai/core/claude.py | 442 +++++++++++++++++++++++++++++++++++---- wake_ai/utils/logging.py | 17 +- 3 files changed, 423 insertions(+), 62 deletions(-) diff --git a/wake_ai/cli.py b/wake_ai/cli.py index 2f163b37..a1526909 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,8 @@ 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", @@ -276,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, verbose_filter: str | None, 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 @@ -284,15 +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) + # 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]Debug logging enabled with filter: {verbose_filter}[/dim]") - else: - console.print("[dim]Debug logging enabled[/dim]") + 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 99080172..91e8191c 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,49 @@ # 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 + +@dataclass +class Grob: + pattern: 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,7 +158,7 @@ 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 @@ -141,7 +180,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 @@ -153,10 +192,9 @@ def format_todo_list(self, todos: List[Dict[str, Any]]) -> None: 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": @@ -170,7 +208,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: @@ -187,25 +225,31 @@ 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 = 10 + + if self.verbose >= 3 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: @@ -214,24 +258,190 @@ def format_tool_use(self, block: ToolUseBlock) -> None: Provides special formatting for TodoWrite tools to show structured 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") + # 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 + elif 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( @@ -254,7 +464,7 @@ def format_tool_result(self, block: ToolResultBlock) -> None: from wake_ai.utils.logging import should_show_tool if not should_show_tool(tool_name): - print(f"filtering {tool_name} tool result") + # print(f"filtering {tool_name} tool result") return # Apply error styling for failed operations, normal styling otherwise @@ -262,9 +472,113 @@ def format_tool_result(self, block: ToolResultBlock) -> None: 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 <= 2: # 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: @@ -286,20 +600,38 @@ def format_tool_result(self, block: ToolResultBlock) -> None: # print only the first and last item if the list is more than 2 items. content_list = block.content - if len(content_list) > 2: - # Show first item - self._print_list_item(content_list[0], header_style, content_style) + 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) - 2 - self.console.print( - f"[{COLORS['truncation']}]... ({omitted_count} items omitted by wake-ai) ...[/{COLORS['truncation']}]", - highlight=False, - ) + # Show truncation indicator + omitted_count = len(content_list) - 1 + self.console.print( + 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: + 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"[{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) @@ -343,13 +675,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( @@ -357,22 +692,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 == 1: + 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): @@ -380,6 +723,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']}]" diff --git a/wake_ai/utils/logging.py b/wake_ai/utils/logging.py index ab608cb8..87c4f903 100644 --- a/wake_ai/utils/logging.py +++ b/wake_ai/utils/logging.py @@ -1,11 +1,12 @@ 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: logger = logging.getLogger(name) @@ -13,19 +14,19 @@ 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_debug() -> bool: - return _debug +def get_verbose_level() -> int: + return _verbose_level def set_verbose_filters(filters: Optional[str]) -> None: From 5fb9a3c6aab12b1c43adf9ab82cea8c3823ea4cf Mon Sep 17 00:00:00 2001 From: meditationduck Date: Fri, 12 Sep 2025 11:17:29 +0200 Subject: [PATCH 3/4] =?UTF-8?q?=F0=9F=94=A7=20Refactor=20verbose=20output?= =?UTF-8?q?=20handling=20and=20improve=20console=20display?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Adjusted console print formatting for better readability. - Updated verbose level checks to refine output visibility. - Enhanced error handling during verbose message processing. - Commented out unnecessary print statements to reduce clutter. --- wake_ai/core/claude.py | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/wake_ai/core/claude.py b/wake_ai/core/claude.py index 91e8191c..034678dd 100644 --- a/wake_ai/core/claude.py +++ b/wake_ai/core/claude.py @@ -189,7 +189,7 @@ def format_todo_list(self, todo_write: TodoWrite) -> 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 todo_write.todos: @@ -208,7 +208,7 @@ def format_todo_list(self, todo_write: TodoWrite) -> None: style = COLORS["todo_pending"] self.console.print( - f" {icon} [[{style}] {content} [/{style}]] ", highlight=False + f"{icon} [{style}] {content} [/{style}] ", highlight=False ) def print_top_and_bottom(self, content: Any, style: str) -> None: @@ -229,9 +229,11 @@ def print_top_and_bottom(self, content: Any, style: str) -> None: 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 >= 3 or len(lines) <= max_tool_result_lines * 2: + 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) @@ -284,7 +286,7 @@ def format_tool_use(self, block: ToolUseBlock) -> None: in_progress_count += 1 elif todo.status == "completed": completed_count += 1 - elif todo.status == "pending": + else: # if todo.status == "pending" pending_count += 1 total_count = completed_count + in_progress_count + pending_count @@ -435,7 +437,7 @@ def format_tool_use(self, block: ToolUseBlock) -> None: self.console.print(f"[{COLORS['tool_use']}]{print_str}[/{COLORS['tool_use']}]") return - print(block) + # print(block) self.console.print( f"[{COLORS['tool_use']}]Using tool: {block.name}[/{COLORS['tool_use']}]" @@ -476,7 +478,7 @@ def format_tool_result(self, block: ToolResultBlock) -> None: header_style = COLORS["tool_result"] content_style = COLORS["tool_result_json"] - if tool_name == "TodoWrite" and (block.is_error == False) and self.verbose <= 2: # TodoWrite Result is not interesting usually + 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 @@ -578,7 +580,7 @@ def format_tool_result(self, block: ToolResultBlock) -> None: self.console.print(f"[{header_style}]{print_str}[/{header_style}]") return - print(block) + # print(block) if isinstance(block.content, str): # Attempt JSON parsing for structured display try: @@ -694,7 +696,7 @@ def handle_verbose_message(self, message: Message) -> None: elif isinstance(message, SystemMessage): if message.subtype == "init": - if self.verbose == 1: + 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']}]" ) @@ -857,7 +859,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 From 96efed1f65ccd36d13c4f68f973f8879882a8b5b Mon Sep 17 00:00:00 2001 From: meditationduck Date: Fri, 12 Sep 2025 17:12:09 +0200 Subject: [PATCH 4/4] =?UTF-8?q?=F0=9F=94=A7=20Update=20verbose=20filter=20?= =?UTF-8?q?help=20text=20and=20remove=20unused=20Grob=20class?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Enhanced the help text for the `--verbose-filter` option in the CLI to include additional tool names for better clarity. - Removed the unused `Grob` class from the `claude.py` file to clean up the codebase. --- wake_ai/cli.py | 2 +- wake_ai/core/claude.py | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/wake_ai/cli.py b/wake_ai/cli.py index a1526909..a78f6f40 100644 --- a/wake_ai/cli.py +++ b/wake_ai/cli.py @@ -262,7 +262,7 @@ def get_class_that_defined_method(meth): @click.option( "--verbose-filter", type=str, - help="Filter verbose output by tool names (comma-separated, e.g., 'Bash,Edit,mcp__wake,TodoWrite,Read,Grep')" + 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", diff --git a/wake_ai/core/claude.py b/wake_ai/core/claude.py index 034678dd..15a3f58f 100644 --- a/wake_ai/core/claude.py +++ b/wake_ai/core/claude.py @@ -75,10 +75,6 @@ class Edit: old_string: str new_string: str -@dataclass -class Grob: - pattern: str - COLORS = { "todo_header": "bold blue",