diff --git a/CHANGELOG.md b/CHANGELOG.md index 374433f6..3ddd4edb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,8 +21,8 @@ PyPI version — not the changelog header. `@cli` `success()/error()/warning()` helpers instead of raw `console.print` markup. Owners self-audited and self-fixed their own branches; devpulse verified each diff + re-ran each audit and committed per wave. Landed so far: spawn, - drone, flow, daemon, prax, ai_mail, backup, seedgo, memory. Remaining waves - following. + drone, flow, daemon, prax, ai_mail, backup, seedgo, memory, trigger, api. + Remaining waves following. ### Fixed diff --git a/src/aipass/api/apps/api.py b/src/aipass/api/apps/api.py index 032d7e7a..a99aecc9 100644 --- a/src/aipass/api/apps/api.py +++ b/src/aipass/api/apps/api.py @@ -149,11 +149,11 @@ def print_help(): console.print("[bold cyan]WHAT IS API?[/bold cyan]") console.print() console.print("API Branch provides:") - console.print(" [green]✓[/green] OpenRouter API client integration") - console.print(" [green]✓[/green] API key management and validation") - console.print(" [green]✓[/green] Model discovery and availability") - console.print(" [green]✓[/green] Usage tracking and statistics") - console.print(" [green]✓[/green] Connection testing and diagnostics") + console.print(" [cyan]•[/cyan] OpenRouter API client integration") + console.print(" [cyan]•[/cyan] API key management and validation") + console.print(" [cyan]•[/cyan] Model discovery and availability") + console.print(" [cyan]•[/cyan] Usage tracking and statistics") + console.print(" [cyan]•[/cyan] Connection testing and diagnostics") console.print() console.print("[bold cyan]AVAILABLE COMMANDS:[/bold cyan]") @@ -287,6 +287,14 @@ def main(): command = args[0] remaining_args = args[1:] if len(args) > 1 else [] + # Subcommand --help guard + if remaining_args and remaining_args[0] in ["--help", "-h"]: + for module in modules: + if module.handle_command(command, ["--help"]): + return 0 + print_help() + return 0 + # Log api command attempt json_handler.log_operation("api_command_attempted", {"command": command, "modules_discovered": len(modules)}) diff --git a/src/aipass/api/apps/modules/api_key.py b/src/aipass/api/apps/modules/api_key.py index 04277f11..3d99e5dc 100644 --- a/src/aipass/api/apps/modules/api_key.py +++ b/src/aipass/api/apps/modules/api_key.py @@ -323,9 +323,5 @@ if __name__ == "__main__": if handle_command(command, remaining_args): sys.exit(0) else: - console.print() - console.print(f"[red]Unknown command: {command}[/red]") - console.print() - console.print("Run [dim]drone @api --help[/dim] for available commands") - console.print() + error(f"Unknown command: {command}", suggestion="Run 'drone @api --help' for available commands") sys.exit(1) diff --git a/src/aipass/api/apps/modules/google_client.py b/src/aipass/api/apps/modules/google_client.py index 8be1bc5e..fb6b06ab 100644 --- a/src/aipass/api/apps/modules/google_client.py +++ b/src/aipass/api/apps/modules/google_client.py @@ -357,9 +357,5 @@ if __name__ == "__main__": if handle_command(command, remaining_args): sys.exit(0) else: - console.print() - console.print(f"[red]Unknown command: {command}[/red]") - console.print() - console.print("Run [dim]drone @api --help[/dim] for available commands") - console.print() + error(f"Unknown command: {command}", suggestion="Run 'drone @api --help' for available commands") sys.exit(1) diff --git a/src/aipass/api/apps/modules/openrouter_client.py b/src/aipass/api/apps/modules/openrouter_client.py index 581bf493..084079d6 100644 --- a/src/aipass/api/apps/modules/openrouter_client.py +++ b/src/aipass/api/apps/modules/openrouter_client.py @@ -29,7 +29,7 @@ if sys.platform == "win32": from typing import List from aipass.prax.apps.modules.logger import system_logger as logger -from aipass.cli.apps.modules import console, header, success, error +from aipass.cli.apps.modules import console, header, success, error, warning from aipass.api.apps.handlers.json import json_handler from aipass.api.apps.handlers.auth import keys from aipass.api.apps.handlers.openrouter import client, models @@ -286,7 +286,7 @@ def check_status(): console.print(" [cyan]Key configured:[/cyan] [green]yes[/green]") console.print(f" [cyan]Key:[/cyan] {masked}") else: - console.print(" [cyan]Key configured:[/cyan] [red]no[/red]") + warning("API key not configured") diagnosis = keys.diagnose_key("openrouter") console.print(f" [cyan]Reason:[/cyan] {diagnosis}") @@ -300,7 +300,7 @@ def check_status(): console.print(" [cyan]OpenAI SDK:[/cyan] [green]available[/green]") except ImportError: logger.warning("OpenAI SDK not installed") - console.print(" [cyan]OpenAI SDK:[/cyan] [red]missing[/red]") + warning("OpenAI SDK not installed") # Client cache stats cache_stats = client.get_cache_stats() @@ -365,9 +365,5 @@ if __name__ == "__main__": if handle_command(command, remaining_args): sys.exit(0) else: - console.print() - console.print(f"[red]Unknown command: {command}[/red]") - console.print() - console.print("Run [dim]drone @api --help[/dim] for available commands") - console.print() + error(f"Unknown command: {command}", suggestion="Run 'drone @api --help' for available commands") sys.exit(1) diff --git a/src/aipass/api/apps/modules/secrets.py b/src/aipass/api/apps/modules/secrets.py index 53eb6f01..6f4e6cc7 100644 --- a/src/aipass/api/apps/modules/secrets.py +++ b/src/aipass/api/apps/modules/secrets.py @@ -32,7 +32,7 @@ if sys.platform == "win32": from typing import Any, List, Optional, Union from aipass.prax import logger # noqa: F401 — seedgo imports standard -from aipass.cli.apps.modules import console, header +from aipass.cli.apps.modules import console, header, error from aipass.api.apps.handlers.json import json_handler from aipass.api.apps.handlers.auth import secrets as _handler @@ -156,7 +156,5 @@ if __name__ == "__main__": print_help() sys.exit(0) - console.print() - console.print(f"[red]Unknown command: {args[0]}[/red]") - console.print() + error(f"Unknown command: {args[0]}") sys.exit(1) diff --git a/src/aipass/api/apps/modules/usage_tracker.py b/src/aipass/api/apps/modules/usage_tracker.py index fd18b29f..7941ee69 100644 --- a/src/aipass/api/apps/modules/usage_tracker.py +++ b/src/aipass/api/apps/modules/usage_tracker.py @@ -288,9 +288,5 @@ if __name__ == "__main__": if handle_command(command, remaining_args): sys.exit(0) else: - console.print() - console.print(f"[red]Unknown command: {command}[/red]") - console.print() - console.print("Run [dim]drone @api --help[/dim] for available commands") - console.print() + error(f"Unknown command: {command}", suggestion="Run 'drone @api --help' for available commands") sys.exit(1) diff --git a/src/aipass/trigger/apps/modules/branch_log_events.py b/src/aipass/trigger/apps/modules/branch_log_events.py index 97fc0b05..4740b460 100644 --- a/src/aipass/trigger/apps/modules/branch_log_events.py +++ b/src/aipass/trigger/apps/modules/branch_log_events.py @@ -166,7 +166,7 @@ def handle_command(command: str, args: list) -> bool: Returns: True if command was handled, False otherwise """ - from aipass.cli.apps.modules import console + from aipass.cli.apps.modules import console, success, error # Handle module-name routing (drone @trigger branch_log_events ) if command == "branch_log_events": @@ -195,15 +195,15 @@ def handle_command(command: str, args: list) -> bool: if command == "start": if start(): - console.print("✅ Branch log watcher started") + success("Branch log watcher started") console.print(f" Monitoring: {AIPASS_PKG_ROOT}/*/logs/*.log") console.print(" Events: error_detected → AI_Mail error_handler") else: - console.print("❌ Failed to start branch log watcher") + error("Failed to start branch log watcher") console.print(" Check if watchdog package is installed") elif command == "stop": stop() - console.print("✅ Branch log watcher stopped") + success("Branch log watcher stopped") elif command == "status": info = status() console.print("Branch Log Watcher Status") @@ -213,7 +213,7 @@ def handle_command(command: str, args: list) -> bool: console.print(f" AIPASS root: {info['aipass_root']}") elif command == "reset": reset_hashes() - console.print("✅ Error deduplication hashes cleared") + success("Error deduplication hashes cleared") json_handler.log_operation("watcher_command", {"command": command}) return True diff --git a/src/aipass/trigger/apps/modules/errors.py b/src/aipass/trigger/apps/modules/errors.py index 41806837..209eaea2 100644 --- a/src/aipass/trigger/apps/modules/errors.py +++ b/src/aipass/trigger/apps/modules/errors.py @@ -323,14 +323,18 @@ def _cmd_detail(console, args: list) -> bool: def _cmd_suppress(console, args: list) -> bool: """Mark error as suppressed with optional reason.""" + from aipass.cli.apps.modules import error, warning + if not args: - console.print("[red]Missing error ID or fingerprint[/red]") - console.print("Usage: drone @trigger errors suppress [reason]") + error( + "Missing error ID or fingerprint", + suggestion="Usage: drone @trigger errors suppress [reason]", + ) return True entry = _find_by_id_or_fp(args[0]) if not entry: - console.print(f"[red]Error not found:[/red] {args[0]}") + error(f"Error not found: {args[0]}") return True reason = " ".join(args[1:]) if len(args) > 1 else "No reason provided" @@ -338,7 +342,7 @@ def _cmd_suppress(console, args: list) -> bool: if update_status(fp, "suppressed", reason): logger.info(f"[ERRORS] Suppressed {entry.get('id', '?')} ({fp[:12]}): {reason}") - console.print(f"[yellow]Suppressed[/yellow] error {entry.get('id', '?')} ({fp[:12]})") + warning(f"Suppressed error {entry.get('id', '?')} ({fp[:12]})") console.print(f" Reason: {reason}") # Phase 5: Source fix pipeline - notify source branch @@ -352,28 +356,32 @@ def _cmd_suppress(console, args: list) -> bool: update_source_fix_status(fp, "pending_fix") console.print(" [dim]Source fix email could not be sent (status: pending_fix)[/dim]") else: - console.print(f"[red]Failed to suppress error[/red] {args[0]}") + error(f"Failed to suppress error {args[0]}") return True def _cmd_resolve(console, args: list) -> bool: """Mark error as resolved.""" + from aipass.cli.apps.modules import error, success + if not args: - console.print("[red]Missing error ID or fingerprint[/red]") - console.print("Usage: drone @trigger errors resolve ") + error( + "Missing error ID or fingerprint", + suggestion="Usage: drone @trigger errors resolve ", + ) return True entry = _find_by_id_or_fp(args[0]) if not entry: - console.print(f"[red]Error not found:[/red] {args[0]}") + error(f"Error not found: {args[0]}") return True fp = entry.get("fingerprint", args[0]) if update_status(fp, "resolved"): logger.info(f"[ERRORS] Resolved {entry.get('id', '?')} ({fp[:12]})") - console.print(f"[green]Resolved[/green] error {entry.get('id', '?')} ({fp[:12]})") + success(f"Resolved error {entry.get('id', '?')} ({fp[:12]})") else: - console.print(f"[red]Failed to resolve error[/red] {args[0]}") + error(f"Failed to resolve error {args[0]}") return True @@ -433,6 +441,8 @@ def _cmd_stats(console, args: list) -> bool: def _cmd_circuit_breaker(console, args: list) -> bool: """Show or reset the circuit breaker.""" + from aipass.cli.apps.modules import error + if args and args[0] == "reset": circuit_breaker_reset() logger.info("[ERRORS] Circuit breaker manually reset to closed") @@ -459,9 +469,9 @@ def _cmd_circuit_breaker(console, args: list) -> bool: cooldown = cb.get("cooldown_seconds", 0) if opened_at > 0: remaining = max(0, cooldown - int(time.time() - opened_at)) - console.print(f" [red]Dispatch paused[/red] - {remaining}s remaining until half-open") + error(f"Dispatch paused - {remaining}s remaining until half-open") else: - console.print(" [red]Dispatch paused[/red]") + error("Dispatch paused") console.print() console.print(" [dim]Run 'drone @trigger errors circuit-breaker reset' to force close[/dim]") elif cb_st == "half_open": diff --git a/src/aipass/trigger/apps/modules/log_events.py b/src/aipass/trigger/apps/modules/log_events.py index 07070a92..8da0b875 100644 --- a/src/aipass/trigger/apps/modules/log_events.py +++ b/src/aipass/trigger/apps/modules/log_events.py @@ -142,7 +142,7 @@ def handle_command(command: str, args: list) -> bool: Returns: True if command was handled, False otherwise """ - from aipass.cli.apps.modules import console + from aipass.cli.apps.modules import console, success, error # Handle module-name routing (drone @trigger log_events ) if command == "log_events": @@ -163,13 +163,13 @@ def handle_command(command: str, args: list) -> bool: if command == "start": if start(): - console.print("✅ Log watcher started") + success("Log watcher started") console.print(f" Monitoring: {SYSTEM_LOGS_DIR}") else: - console.print("❌ Failed to start log watcher") + error("Failed to start log watcher") elif command == "stop": stop() - console.print("✅ Log watcher stopped") + success("Log watcher stopped") elif command == "status": info = status() console.print("Log Watcher Status") diff --git a/src/aipass/trigger/apps/modules/medic.py b/src/aipass/trigger/apps/modules/medic.py index 8386a82e..018fb1a6 100644 --- a/src/aipass/trigger/apps/modules/medic.py +++ b/src/aipass/trigger/apps/modules/medic.py @@ -233,34 +233,38 @@ def print_help() -> None: def _handle_mute(console, args: list) -> None: """Handle 'medic mute @branch'.""" + from aipass.cli.apps.modules import error + if not args: - console.print("[red]Missing branch name[/red] - usage: medic mute @branch") + error("Missing branch name", suggestion="Usage: medic mute @branch") return branch_name = _extract_branch_name(args[0]) if not branch_name: - console.print("[red]Missing branch name[/red] - usage: medic mute @branch") + error("Missing branch name", suggestion="Usage: medic mute @branch") return if mute_branch(branch_name): logger.info(f"[MEDIC] Muted branch: {branch_name}") console.print(f" [yellow]Muted[/yellow] @{branch_name} — errors logged but not dispatched") else: - console.print(f" [red]Failed to mute[/red] @{branch_name} — check trigger_config.json") + error(f"Failed to mute @{branch_name}", suggestion="Check trigger_config.json") def _handle_unmute(console, args: list) -> None: """Handle 'medic unmute @branch'.""" + from aipass.cli.apps.modules import error + if not args: - console.print("[red]Missing branch name[/red] - usage: medic unmute @branch") + error("Missing branch name", suggestion="Usage: medic unmute @branch") return branch_name = _extract_branch_name(args[0]) if not branch_name: - console.print("[red]Missing branch name[/red] - usage: medic unmute @branch") + error("Missing branch name", suggestion="Usage: medic unmute @branch") return if unmute_branch(branch_name): logger.info(f"[MEDIC] Unmuted branch: {branch_name}") console.print(f" [green]Unmuted[/green] @{branch_name} — dispatch resumed") else: - console.print(f" [red]Failed to unmute[/red] @{branch_name} — check trigger_config.json") + error(f"Failed to unmute @{branch_name}", suggestion="Check trigger_config.json") def _handle_status(console) -> None: @@ -298,9 +302,10 @@ def _handle_status(console) -> None: def _handle_on(console) -> None: """Handle 'medic on' — enable dispatch and start watcher.""" from rich.panel import Panel + from aipass.cli.apps.modules import error if not set_enabled(True): - console.print("[red]Failed to enable Medic[/red] - check trigger_config.json") + error("Failed to enable Medic", suggestion="Check trigger_config.json") return logger.info("[MEDIC] Medic ENABLED - error dispatch active") @@ -328,9 +333,10 @@ def _handle_on(console) -> None: def _handle_off(console) -> None: """Handle 'medic off' — disable dispatch and stop watcher.""" from rich.panel import Panel + from aipass.cli.apps.modules import error if not set_enabled(False): - console.print("[red]Failed to disable Medic[/red] - check trigger_config.json") + error("Failed to disable Medic", suggestion="Check trigger_config.json") return logger.info("[MEDIC] Medic DISABLED - error dispatch suppressed") diff --git a/src/aipass/trigger/apps/trigger.py b/src/aipass/trigger/apps/trigger.py index 4efb3a1c..ad10878d 100644 --- a/src/aipass/trigger/apps/trigger.py +++ b/src/aipass/trigger/apps/trigger.py @@ -217,6 +217,14 @@ def main(): command = args[0] remaining_args = args[1:] if len(args) > 1 else [] + # Subcommand --help guard + if remaining_args and remaining_args[0] in ["--help", "-h"]: + for module in modules: + if module.handle_command(command, ["--help"]): + return 0 + print_help(modules) + return 0 + # Route to modules if route_command(command, remaining_args, modules): return 0 @@ -236,5 +244,5 @@ if __name__ == "__main__": sys.exit(0) except Exception as e: logger.error(f"TRIGGER entry point error: {e}", exc_info=True) - console.print(f"\n❌ Error: {e}") + error(f"Error: {e}") sys.exit(1) diff --git a/src/aipass/trigger/tests/test_branch_log_events.py b/src/aipass/trigger/tests/test_branch_log_events.py index fdbea295..98a97dc1 100644 --- a/src/aipass/trigger/tests/test_branch_log_events.py +++ b/src/aipass/trigger/tests/test_branch_log_events.py @@ -246,9 +246,10 @@ def test_handle_command_start_failure_prints_error(): watcher.start_branch_log_watcher.return_value = None result = mod.handle_command("start", []) assert result is True - console = _get_console() - printed = _get_print_str_args(console) - assert any("Failed to start" in s for s in printed), f"Expected failure message in printed args: {printed}" + cli_modules = sys.modules["aipass.cli.apps.modules"] + cli_modules.error.assert_called() + err_args = [str(a) for call in cli_modules.error.call_args_list for a in call.args] + assert any("Failed to start" in s for s in err_args), f"Expected failure message in error() args: {err_args}" def test_handle_command_stop(): diff --git a/src/aipass/trigger/tests/test_errors.py b/src/aipass/trigger/tests/test_errors.py index b1fad351..6829b5bd 100644 --- a/src/aipass/trigger/tests/test_errors.py +++ b/src/aipass/trigger/tests/test_errors.py @@ -398,9 +398,10 @@ class TestHandleCommandCircuitBreaker: result = handle_command("errors", ["circuit-breaker"]) assert result is True - printed_texts = [str(c) for c in mocks["console"].print.call_args_list] - has_paused = any("paused" in text.lower() for text in printed_texts) - assert has_paused, "Expected 'paused' in open circuit breaker output" + cli_modules = sys.modules["aipass.cli.apps.modules"] + err_args = [str(a) for call in cli_modules.error.call_args_list for a in call.args] + has_paused = any("paused" in text.lower() for text in err_args) + assert has_paused, "Expected 'paused' in error() output" def test_circuit_breaker_reset(self): """circuit-breaker reset calls reset and confirms CLOSED state in output.""" @@ -631,23 +632,25 @@ class TestHandleCommandResolve: ) assert update_call[0][1] == "resolved" - # Verify confirmation message was printed - printed_texts = [str(c) for c in mocks["console"].print.call_args_list] - has_resolved = any("Resolved" in text and "e001" in text for text in printed_texts) - assert has_resolved, "Expected 'Resolved' confirmation with error ID in output" + # Verify confirmation message was routed through success() + cli_modules = sys.modules["aipass.cli.apps.modules"] + success_args = [str(a) for call in cli_modules.success.call_args_list for a in call.args] + has_resolved = any("Resolved" in text and "e001" in text for text in success_args) + assert has_resolved, "Expected 'Resolved' confirmation with error ID in success() output" def test_resolve_no_id_prints_usage(self): """resolve with no ID prints a usage hint.""" from aipass.trigger.apps.modules.errors import handle_command - mocks = _mocks() + _mocks() result = handle_command("errors", ["resolve"]) assert result is True - printed_texts = [str(c) for c in mocks["console"].print.call_args_list] - has_missing = any("missing" in text.lower() for text in printed_texts) - assert has_missing, "Expected 'missing' in resolve-no-id output" + cli_modules = sys.modules["aipass.cli.apps.modules"] + err_args = [str(a) for call in cli_modules.error.call_args_list for a in call.args] + has_missing = any("missing" in text.lower() for text in err_args) + assert has_missing, "Expected 'missing' in error() output" # --------------------------------------------------------------------------- diff --git a/src/aipass/trigger/tests/test_log_events.py b/src/aipass/trigger/tests/test_log_events.py index 296f8574..36b59b80 100644 --- a/src/aipass/trigger/tests/test_log_events.py +++ b/src/aipass/trigger/tests/test_log_events.py @@ -210,9 +210,10 @@ def test_handle_command_start_failure_prints_error(): watcher.start_log_watcher.return_value = None result = mod.handle_command("start", []) assert result is True - console = _get_console() - printed = _get_print_str_args(console) - assert any("Failed to start" in s for s in printed), f"Expected failure message in printed args: {printed}" + cli_modules = sys.modules["aipass.cli.apps.modules"] + cli_modules.error.assert_called() + err_args = [str(a) for call in cli_modules.error.call_args_list for a in call.args] + assert any("Failed to start" in s for s in err_args), f"Expected failure message in error() args: {err_args}" def test_handle_command_stop(): diff --git a/src/aipass/trigger/tests/test_medic.py b/src/aipass/trigger/tests/test_medic.py index 7ec44cf0..2fda1a28 100644 --- a/src/aipass/trigger/tests/test_medic.py +++ b/src/aipass/trigger/tests/test_medic.py @@ -177,10 +177,10 @@ def test_handle_command_on_failure_prints_error(): result = medic.handle_command("on", []) assert result is True - console = _get_console() - printed = _get_print_str_args(console) - expected_msg = "[red]Failed to enable Medic[/red] - check trigger_config.json" - assert expected_msg in printed, f"Expected exact error message '{expected_msg}' in printed args: {printed}" + cli_modules = sys.modules["aipass.cli.apps.modules"] + cli_modules.error.assert_called() + err_args = [str(a) for call in cli_modules.error.call_args_list for a in call.args] + assert any("Failed to enable Medic" in s for s in err_args), f"Expected failure message in error() args: {err_args}" # --------------------------------------------------------------------------- @@ -226,10 +226,12 @@ def test_handle_command_off_failure_prints_error(): result = medic.handle_command("off", []) assert result is True - console = _get_console() - printed = _get_print_str_args(console) - expected_msg = "[red]Failed to disable Medic[/red] - check trigger_config.json" - assert expected_msg in printed, f"Expected exact error message '{expected_msg}' in printed args: {printed}" + cli_modules = sys.modules["aipass.cli.apps.modules"] + cli_modules.error.assert_called() + err_args = [str(a) for call in cli_modules.error.call_args_list for a in call.args] + assert any("Failed to disable Medic" in s for s in err_args), ( + f"Expected failure message in error() args: {err_args}" + ) # --------------------------------------------------------------------------- @@ -354,10 +356,10 @@ def test_handle_command_mute_failure_prints_error(): medic.handle_command("mute", ["@api"]) - console = _get_console() - printed = _get_print_str_args(console) - expected = " [red]Failed to mute[/red] @api — check trigger_config.json" - assert expected in printed, f"Expected mute failure message '{expected}' in printed args: {printed}" + cli_modules = sys.modules["aipass.cli.apps.modules"] + cli_modules.error.assert_called() + err_args = [str(a) for call in cli_modules.error.call_args_list for a in call.args] + assert any("Failed to mute" in s for s in err_args), f"Expected mute failure message in error() args: {err_args}" def test_handle_command_mute_without_branch_name(): @@ -366,10 +368,10 @@ def test_handle_command_mute_without_branch_name(): result = medic.handle_command("mute", []) assert result is True - console = _get_console() - printed = _get_print_str_args(console) - expected = "[red]Missing branch name[/red] - usage: medic mute @branch" - assert expected in printed, f"Expected usage error '{expected}' in printed args: {printed}" + cli_modules = sys.modules["aipass.cli.apps.modules"] + cli_modules.error.assert_called() + err_args = [str(a) for call in cli_modules.error.call_args_list for a in call.args] + assert any("Missing branch name" in s for s in err_args), f"Expected usage error in error() args: {err_args}" # Should NOT have called mute_branch state = _get_medic_state() state.mute_branch.assert_not_called() @@ -410,10 +412,12 @@ def test_handle_command_unmute_already_unmuted(): result = medic.handle_command("unmute", ["@nonexistent"]) assert result is True - console = _get_console() - printed = _get_print_str_args(console) - expected = " [red]Failed to unmute[/red] @nonexistent — check trigger_config.json" - assert expected in printed, f"Expected unmute failure message '{expected}' in printed args: {printed}" + cli_modules = sys.modules["aipass.cli.apps.modules"] + cli_modules.error.assert_called() + err_args = [str(a) for call in cli_modules.error.call_args_list for a in call.args] + assert any("Failed to unmute" in s for s in err_args), ( + f"Expected unmute failure message in error() args: {err_args}" + ) def test_handle_command_unmute_without_branch_name(): @@ -422,10 +426,10 @@ def test_handle_command_unmute_without_branch_name(): result = medic.handle_command("unmute", []) assert result is True - console = _get_console() - printed = _get_print_str_args(console) - expected = "[red]Missing branch name[/red] - usage: medic unmute @branch" - assert expected in printed, f"Expected usage error '{expected}' in printed args: {printed}" + cli_modules = sys.modules["aipass.cli.apps.modules"] + cli_modules.error.assert_called() + err_args = [str(a) for call in cli_modules.error.call_args_list for a in call.args] + assert any("Missing branch name" in s for s in err_args), f"Expected usage error in error() args: {err_args}" state = _get_medic_state() state.unmute_branch.assert_not_called()