Ctrl K
ComunidadGuías…

rex/cli.py

Rama master · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
"""
Command-line interface for Rex AI Assistant.

This module provides the main CLI entry point with subcommands:
    rex doctor       - Run environment diagnostics
    rex chat         - Start interactive chat (default)
    rex version      - Show version information
    rex tools        - List registered tools and their status
    rex run-workflow - Run a workflow from a JSON file
    rex approvals    - List and manage pending approvals
    rex workflows    - List workflows
    rex memory       - Manage working and long-term memory
    rex kb           - Manage knowledge base documents
    rex scheduler    - List and manage scheduler jobs
    rex email        - Manage email
    rex calendar     - Manage calendar
    rex reminders    - Manage reminders
    rex cues         - Manage follow-up cues
    rex browser      - Browser automation
    rex os           - OS automation
    rex gh           - GitHub integration
    rex code         - VS Code operations
    rex msg          - Messaging (SMS)
    rex notify       - Notifications
    rex pc           - Remote Windows computer control (agent API, client-only foundation)
    rex wp           - WordPress site monitoring (read-only)
    rex wc           - WooCommerce monitoring + approval-gated write actions
    rex voice-id     - Voice speaker identity enrollment, calibration, and status
    rex ha           - Home Assistant integration commands (TTS test)
    rex usage        - Show LLM usage summary (total requests, tokens, by model)
    rex background   - Control the persistent background runtime (operator use)

Usage:
    rex [command] [options]

If no command is specified, the chat interface is started.

Command implementations live in ``rex/commands/`` (one module per domain,
see US-REM-027). This module keeps parser registration, the ``main`` entry
point, and backward-compatible re-exports: ``rex.cli.<name>`` remains the
canonical import and monkeypatch target for every command handler, service
getter, and helper.
"""

from __future__ import annotations

import argparse
import sys
from collections.abc import Sequence
from pathlib import Path  # noqa: F401  (re-export: patched as rex.cli.Path in tests)

from rex.compat.python_compat import (
    DEFAULT_INSTALL_LABEL,
    is_supported_python,
    unsupported_python_message,
)
from rex.exception_handler import wrap_entrypoint
from rex.startup_validation import check_startup_env

if not is_supported_python(sys.version_info):
    raise SystemExit(
        unsupported_python_message(sys.version_info, install_target=DEFAULT_INSTALL_LABEL)
    )

from rex.commands import (  # noqa: E402
    background,
    commerce,
    core,
    dashboard,
    devtools,
    email_calendar,
    ha,
    identity,
    memory,
    messaging,
    mobile,
    pc,
    reminders,
    scheduler,
    workflows,
)
from rex.commands._epilog import CLI_EPILOG  # noqa: E402

# ---------------------------------------------------------------------------
# Backward-compatible re-exports (US-REM-027).
# ``rex.cli.<name>`` is the stable public surface: tests and external code
# import handlers from here and monkeypatch service getters here. Command
# modules resolve these names through ``rex.cli`` at call time.
# ---------------------------------------------------------------------------
from rex.commands._helpers import (  # noqa: E402,F401
    _get_version,
    _load_calendar_resolver_safe,
    _load_email_config_safe,
    _load_email_resolver_safe,
    _parse_datetime_strict,
    _parse_ttl,
    _resolve_cli_user,
)
from rex.commands._services import (  # noqa: E402,F401
    get_browser_service,
    get_calendar_service,
    get_computer_service,
    get_cue_store,
    get_email_service,
    get_github_service,
    get_os_service,
    get_reminder_service,
    get_scheduler,
    get_vscode_service,
    initialize_scheduler_system,
)
from rex.commands.background import cmd_background  # noqa: E402,F401
from rex.commands.commerce import (  # noqa: E402,F401
    _WC_WRITE_HELP,
    _cmd_wc_coupon_create,
    _cmd_wc_coupon_disable,
    _cmd_wc_order_set_status,
    _resolve_wc_initiated_by,
    cmd_wc,
    cmd_wp,
)
from rex.commands.core import (  # noqa: E402,F401
    cmd_chat,
    cmd_doctor,
    cmd_integrations,
    cmd_tools,
    cmd_version,
)
from rex.commands.dashboard import (  # noqa: E402,F401
    cmd_history,
    cmd_quick_actions,
    cmd_shopping,
    cmd_usage,
)
from rex.commands.devtools import cmd_browser, cmd_code, cmd_gh, cmd_os  # noqa: E402,F401
from rex.commands.email_calendar import (  # noqa: E402,F401
    _cmd_calendar_accounts,
    _cmd_calendar_test_connection,
    _cmd_email_accounts,
    _cmd_email_send,
    _cmd_email_test_connection,
    cmd_calendar,
    cmd_email,
)
from rex.commands.ha import (  # noqa: E402,F401
    _cmd_ha_approve,
    _cmd_ha_tts,
    _cmd_ha_tts_test,
    cmd_ha,
)
from rex.commands.identity import cmd_identify, cmd_voice_id, cmd_whoami  # noqa: E402,F401
from rex.commands.memory import cmd_kb, cmd_memory, cmd_remember  # noqa: E402,F401
from rex.commands.messaging import cmd_msg, cmd_notify  # noqa: E402,F401
from rex.commands.mobile import cmd_mobile_api, cmd_mobile_user  # noqa: E402,F401
from rex.commands.pc import cmd_pc  # noqa: E402,F401
from rex.commands.reminders import cmd_cues, cmd_reminders  # noqa: E402,F401
from rex.commands.scheduler import cmd_scheduler  # noqa: E402,F401
from rex.commands.workflows import (  # noqa: E402,F401
    cmd_approvals,
    cmd_executor_resume,
    cmd_plan,
    cmd_run_workflow,
    cmd_workflows,
)


def create_parser() -> argparse.ArgumentParser:
    """Create the argument parser with all subcommands."""
    parser = argparse.ArgumentParser(
        prog="rex",
        description="Rex AI Assistant - Voice-activated AI assistant with speech recognition and synthesis",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog=CLI_EPILOG,
    )

    parser.add_argument("-v", "--verbose", action="store_true", help="Enable verbose output")
    parser.add_argument(
        "--debug",
        action="store_true",
        help="Enable DEBUG logging across all modules (equivalent to REX_DEBUG=1)",
    )
    parser.add_argument("--version", action="version", version=f"%(prog)s {_get_version()}")

    subparsers = parser.add_subparsers(
        title="commands",
        description="Available commands",
        dest="command",
        metavar="COMMAND",
    )

    # Registration order is preserved from the original single-module parser
    # so that `rex --help` output is unchanged (US-REM-027).
    core.register(subparsers)
    workflows.register(subparsers)
    memory.register(subparsers)
    scheduler.register(subparsers)
    email_calendar.register(subparsers)
    identity.register(subparsers)
    reminders.register(subparsers)
    messaging.register(subparsers)
    devtools.register(subparsers)
    pc.register(subparsers)
    commerce.register(subparsers)
    ha.register(subparsers)
    identity.register_voice_id(subparsers)
    dashboard.register(subparsers)
    mobile.register(subparsers)
    background.register(subparsers)

    return parser


@wrap_entrypoint
def main(argv: Sequence[str] | None = None) -> int:
    """Main entry point for the Rex CLI."""
    check_startup_env()

    # Keep legacy-environment diagnostics on the user-facing CLI path without
    # forcing rex.config to initialize during package/background imports.
    from rex.config_manager import get_legacy_env_warnings

    for warning in get_legacy_env_warnings()[:3]:
        print(warning, file=sys.stderr)

    from rex.first_run import maybe_print_welcome

    maybe_print_welcome()

    parser = create_parser()
    args = parser.parse_args(argv)

    # Apply --debug flag: set env var so load_config() picks it up, then
    # immediately raise the root log level so all subsequent imports log verbosely.
    if getattr(args, "debug", False):
        import os

        os.environ["REX_DEBUG"] = "1"
        from rex.logging_utils import set_global_level

        set_global_level(__import__("logging").DEBUG)

    if args.command is None:
        if getattr(args, "verbose", False):
            parser.print_help()
            return 0
        args.func = cmd_chat
        args.verbose = getattr(args, "verbose", False)

    return args.func(args)  # type: ignore[no-any-return]


if __name__ == "__main__":
    sys.exit(main())