"""
Dundun CLI 主入口
集成会话管理、Agent 系统、命令处理等功能

注意：此模块需要通过 pip install -e . 安装后使用
"""

import asyncio
import os
import sys
from typing import Optional, Dict, Any, Tuple
from pathlib import Path

import typer
from dotenv import load_dotenv

# ============================================================================
# 环境配置
# ============================================================================

# 加载环境变量 - 按优先级从多个位置加载
def _load_env_files():
    """加载 .env 文件"""
    # 当前工作目录
    cwd_env = Path.cwd() / ".env"
    if cwd_env.exists():
        load_dotenv(cwd_env)
        return

    # 默认调用（尝试自动查找）
    load_dotenv()

_load_env_files()

from core.config import load_config, load_settings
from core.logger import log_event, set_log_side
from rich.text import Text

# 接口
from interface.commands import get_command_handler
from interface.renderer import get_tui, get_streaming_renderer, DundunApp

# 事件处理器（新增）
from interface.event_dispatcher import EventDispatcher
from interface.render_handler import RenderHandler
from interface.interaction_handler import InteractionHandler

# WebSocket 客户端
from interface.websocket_client import WebSocketClient

# 应用状态管理
try:
    from main import get_app_state
except ImportError:
    raise ImportError("无法导入 main 模块，请确保项目已正确安装")


# ============================================================================
# 全局状态
# ============================================================================

# 标记连接错误，用于在 Textual 退出后抛出异常
_connection_error: Optional[Exception] = None


app = typer.Typer(
    help="Dundun Agent - AI 编程助手",
    add_completion=False,
    no_args_is_help=False
)


class RuntimeContext:
    """运行时上下文（从服务端获取，用于展示）"""

    def __init__(self):
        self.agent_type: str = "general"
        self.model_name: str = ""
        self.provider: str = ""
        self.available_models: list = []

    def update(self, **kwargs):
        """更新运行时上下文"""
        for key, value in kwargs.items():
            if hasattr(self, key):
                setattr(self, key, value)


class CLIContext:
    """CLI 上下文（纯客户端状态）"""

    def __init__(self):
        # 会话 ID
        self.session_id: Optional[str] = None
        # WebSocket 客户端
        self.ws_client: Optional[WebSocketClient] = None
        # 服务端 URL（用于 HTTP 调用）
        self.server_url: str = ""
        # 调试模式
        self.debug_mode: bool = False
        # 运行时上下文（从服务端获取）
        self.runtime: RuntimeContext = RuntimeContext()

    def to_dict(self) -> Dict[str, Any]:
        """转换为字典（用于命令处理）"""
        return {
            "session_id": self.session_id,
            "current_agent": self.runtime.agent_type,
            "model_name": self.runtime.model_name,
            "provider": self.runtime.provider,
            "available_models": self.runtime.available_models,
            "debug_mode": self.debug_mode,
            "server_url": self.server_url,
        }


async def process_user_input(
    ctx: CLIContext,
    user_input: str,
    tui,
    command_handler,
    renderer,
) -> bool:
    """
    处理用户输入

    Returns:
        bool: 是否继续主循环
    """
    # 检查是否是命令
    if user_input.startswith("/"):
        result = await command_handler.execute(user_input, ctx.to_dict())

        # 命令不存在（返回 None），作为普通 prompt 处理
        if result is None:
            pass  # 继续作为普通 prompt 处理

        elif result:
            # 如果需要填充输入框（命令需要参数但没有提供）
            if result.fill_input and tui._app:
                tui._app.fill_input(result.fill_input)
                return True

            # 打印命令结果
            if result.message:
                tui.print_command_result(result.message, result.success)

            # 处理命令数据
            if result.data:
                # 作为用户输入处理（如 /init 命令）
                if "as_user_input" in result.data:
                    user_message = result.data["as_user_input"]
                    # 记录日志
                    log_event("init_command_triggered", session_id=ctx.session_id)
                    # 设置为处理状态
                    app_state = get_app_state()
                    app_state.set_processing_state()
                    # 发送消息到服务器
                    await ctx.ws_client.send_message(user_message)
                    renderer.start_processing()
                    return True

                # 切换 Agent
                if "switch_agent" in result.data:
                    new_agent_name = result.data["switch_agent"]
                    # WebSocket 模式：发送切换 Agent 消息
                    await ctx.ws_client.switch_agent(new_agent_name)
                    ctx.runtime.agent_type = new_agent_name
                    tui.print_success(f"已切换到 {new_agent_name} 模式")
                    # 更新状态栏
                    tui.update_status_bar(model=ctx.runtime.model_name, agent=new_agent_name)

                # 切换模型
                if "switch_model" in result.data:
                    model_name = result.data["switch_model"]
                    await ctx.ws_client.switch_model(model_name)
                    ctx.runtime.model_name = model_name
                    msg = Text()
                    msg.append("⏺", style="bold green")
                    msg.append(f" 已切换到模型 {model_name}")
                    tui._write_safe(msg)
                    # 更新状态栏
                    tui.update_status_bar(model=model_name, agent=ctx.runtime.agent_type)

                # 切换会话
                if "switch_session_id" in result.data:
                    await _switch_to_session(ctx, result.data["switch_session_id"])
                    return True

                # 新建会话
                if "new_session_id" in result.data:
                    ctx.session_id = result.data["new_session_id"]
                    # 更新状态栏
                    tui.update_status_bar(session_id=ctx.session_id)

                # 调试模式
                if "debug_mode" in result.data:
                    ctx.debug_mode = result.data["debug_mode"]
                    tui.debug_mode = ctx.debug_mode

                # 查询 TODO
                if "query_todo" in result.data:
                    await ctx.ws_client.query_todo()

                # 查询工具
                if "query_tools" in result.data:
                    await ctx.ws_client.query_tools()

                # 初始化项目
                if "init" in result.data:
                    force = result.data.get("force", False)
                    await ctx.ws_client.init_project(force)

                # 查询 skills
                if "query_skills" in result.data:
                    await ctx.ws_client.query_skills()

                # 请求压缩上下文
                if "compact" in result.data:
                    # 启动手动压缩的 spinner 动画
                    renderer.on_manual_compact_start()
                    await ctx.ws_client.context_compact()

                # 新建会话并清空输出
                if "new_session" in result.data:
                    # 断开旧连接
                    if ctx.ws_client:
                        await ctx.ws_client.disconnect(reason="new_session")
                    # 让服务端生成新 session_id（设置为空）
                    ctx.session_id = ""
                    # 重新连接（使用空 session_id，让服务端生成）
                    ws_url = ctx.ws_client.url if ctx.ws_client else ""
                    ctx.ws_client = WebSocketClient(ws_url, ctx.session_id, ctx.runtime.agent_type)
                    await ctx.ws_client.connect()
                    ctx._reregister_ws_events()
                    asyncio.create_task(ctx.ws_client.receive_loop())
                    # 清空输出和欢迎信息会在 connected 事件中处理

            # 检查是否退出
            if result.should_exit:
                return False

        return True

    # 空输入
    if not user_input.strip():
        return True

    # 正常对话
    try:
        app_state = get_app_state()

        # 设置为处理状态
        app_state.set_processing_state()

        # 记录用户输入
        log_event("user_input", session_id=ctx.session_id, input=user_input)
        log_event("process_start", agent=ctx.runtime.agent_type, input=user_input)

        # WebSocket 模式：发送消息到服务器
        await ctx.ws_client.send_message(user_input)

        # 通知 renderer 开始处理，阻止下一次输入
        renderer.start_processing()

        # 响应会通过 WebSocket 事件处理器接收


    except KeyboardInterrupt:
        tui.print_warning("已中断")
        # 恢复到输入状态
        get_app_state().set_input_state()
    except Exception as e:
        # 执行错误，记录日志并打印
        import traceback
        error_details = traceback.format_exception(type(e), e, e.__traceback__)
        error_msg = "".join(error_details)
        log_event("user_input_error", error=str(e), traceback=error_msg)
        tui.print_error(f"执行错误: {str(e)}")
        if ctx.debug_mode:
            import traceback as tb
            tb.print_exc()
        # 恢复到输入状态
        get_app_state().set_input_state()

    # 注意：不要在这里设置 input_state，因为 WebSocket 是异步的
    # 状态会在 handle_response_end_event 中被设回 input
    return True


async def _switch_to_session(ctx: CLIContext, session_id: str):
    """切换到指定会话"""

    # 断开旧连接
    if ctx.ws_client:
        await ctx.ws_client.disconnect(reason="switch_session")

    # 切换到新会话
    ctx.session_id = session_id

    # 重新连接
    ws_url = ctx.ws_client.url if ctx.ws_client else ""
    ctx.ws_client = WebSocketClient(ws_url, ctx.session_id, ctx.runtime.agent_type)
    await ctx.ws_client.connect()
    ctx._reregister_ws_events()
    asyncio.create_task(ctx.ws_client.receive_loop())

    # 欢迎信息和状态栏会在 connected 事件中更新


async def main_loop(ctx: CLIContext):
    """主循环"""
    tui = get_tui()
    renderer = get_streaming_renderer()
    command_handler = get_command_handler()
    app_state = get_app_state()

    # WebSocket 事件处理器已在连接时注册

    # 主循环
    loop_count = 0

    while True:
        loop_count += 1
        if ctx.debug_mode:
            tui._write_safe(Text(f"--- 循环 #{loop_count} ---", style="dim"))

        try:
            # 等待 Agent 处理完成后才允许输入
            if ctx.debug_mode:
                tui._write_safe(Text("等待 input_ready...", style="dim"))
            await renderer.input_ready.wait()
            if ctx.debug_mode:
                tui._write_safe(Text("input_ready 已设置，可以获取输入", style="dim"))

            # 设置为用户输入状态
            app_state.set_input_state()

            # 获取用户输入（通过 Textual Input 组件）
            user_input = await tui.get_user_input_async()

            # Textual 模式下不需要清除行（Input 在独立区域）

            # 如果有输入内容，重新渲染为斜体
            if user_input and user_input.strip():
                tui.render_user_input(user_input)

            if ctx.debug_mode:
                tui._write_safe(Text(f"收到输入: {repr(user_input)}", style="dim"))

            # 处理输入
            should_continue = await process_user_input(
                ctx, user_input, tui, command_handler, renderer
            )

            if ctx.debug_mode:
                tui._write_safe(Text(f"should_continue: {should_continue}", style="dim"))

            if not should_continue:
                if ctx.debug_mode:
                    tui._write_safe(Text("退出主循环", style="dim"))
                break

        except asyncio.CancelledError:
            if ctx.debug_mode:
                tui._write_safe(Text("输入任务被取消", style="dim"))
            continue
        except KeyboardInterrupt:
            pass
        except EOFError:
            if ctx.debug_mode:
                tui._write_safe(Text("收到 EOFError，退出", style="dim"))
            break
        except ConnectionError as e:
            # 连接相关错误需要退出，记录日志并退出
            log_event("connection_error", error=str(e))
            tui.print_error(f"连接错误: {e}")
            break
        except Exception as e:
            # 其他未知错误，记录完整日志并退出
            import traceback
            error_details = traceback.format_exception(type(e), e, e.__traceback__)
            error_msg = "".join(error_details)
            log_event("unknown_error", error=str(e), traceback=error_msg)
            tui.print_error(f"未知错误: {str(e)}")
            print(f"\n✗ Error: 未知错误: {e}", file=sys.stderr, flush=True)
            print(f"详细信息:\n{error_msg}", file=sys.stderr, flush=True)
            if ctx.debug_mode:
                import traceback as tb
                tb.print_exc()
            # 退出主循环
            break


async def setup_websocket_handlers(ctx: CLIContext, tui, renderer):
    """
    设置 WebSocket 事件处理器 - 使用事件分发器

    职责：
    - 将 WebSocket 事件分发给 RenderHandler 和 InteractionHandler
    - 处理权限请求和交互请求（需要用户输入）
    - 处理连接断开和错误
    """
    from core.logger import log_event

    # 获取应用状态
    app_state = get_app_state()

    # 创建事件处理器
    render_handler = RenderHandler(ctx.ws_client, tui, renderer, app_state)
    interaction_handler = InteractionHandler(ctx.ws_client, tui, renderer, app_state)

    # 创建事件分发器
    event_dispatcher = EventDispatcher(render_handler, interaction_handler)

    def _is_subagent_session_id(session_id: Any) -> bool:
        """判断 session_id 是否为子 Agent 会话：<parent>.<8hex>"""
        if not isinstance(session_id, str) or "." not in session_id:
            return False
        last = session_id.rsplit(".", 1)[-1]
        return len(last) == 8 and all(c in "0123456789abcdef" for c in last.lower())

    def _maybe_route_subtask_event(event: Dict[str, Any]) -> bool:
        """如果是子任务事件则转发并返回 True（调用方应短路原逻辑）。"""
        # subtask_session_id 在 event.data 内部（由 task_tool 转发时添加）
        data = event.get("data") or {}
        subtask_id = data.get("subtask_session_id")
        if not subtask_id:
            return False
        # 仅当该 subtask_id 已在子任务状态表中时才路由
        if subtask_id in renderer._subtask_states:
            renderer.on_subtask_process(subtask_id, event)
            return True
        return False

    # 触发上下文信息更新的事件白名单
    _CONTEXT_UPDATE_EVENTS = {"tool_call_end", "text_chunk_complete", "model_switched", "agent_switched", "connected", "compact_result"}

    async def _route_event(event: Dict[str, Any]):
        """统一事件路由入口：在这里做子任务转发与按 type 分发。"""
        event_type = event.get("type", "")
        log_event("client_received_event", event_type=event_type, data=event.get("data"))

        if event_type == "connected":
            data = event.get("data", {})
            server_session_id = data.get("session_id", "")
            working_path = data.get("working_path", "")
            if server_session_id:
                ctx.session_id = server_session_id
                if ctx.ws_client:
                    ctx.ws_client.session_id = server_session_id
                # 更新全局工作路径变量
                if working_path:
                    import interface.app
                    interface.app.SERVER_WORKING_PATH = working_path
                tui.update_status_bar(
                    session_id=ctx.session_id,
                    model=ctx.runtime.model_name,
                    agent=ctx.runtime.agent_type,
                )

            # 异步预加载会话列表（不阻塞 UI）
            try:
                from interface.widgets import UserInput
                user_input = tui._app.query_one("#user-input", UserInput)
                asyncio.create_task(user_input._fetch_sessions())
            except Exception:
                pass

        # 生命周期事件和交互类事件不应被子任务路由"吞掉"
        if event_type not in {"permission_request", "interaction_request", "subtask_started", "subtask_completed"}:
            if _maybe_route_subtask_event(event):
                return

        # 使用事件分发器分发
        await event_dispatcher.dispatch(event)

        # 白名单事件触发上下文信息更新
        if event_type in _CONTEXT_UPDATE_EVENTS:
            await _update_context_info()

    async def _update_context_info():
        """通过 HTTP 查询上下文信息并更新状态栏"""
        try:
            data = await ctx.ws_client.get_context_info()
            token_count = data.get("token_count", 0)
            max_tokens = data.get("max_tokens", 0)
            percent = data.get("percent", 0)
            from interface.renderer import get_tui
            t = get_tui()
            if t and t._app:
                t._app.update_status_bar(
                    context_tokens=token_count,
                    context_max=max_tokens,
                    context_percent=percent,
                )
        except Exception as e:
            log_event("context_info_fetch_error", error=str(e))

    # ========== 注册事件处理器 ==========

    def _register_all_ws_events():
        """统一注册所有服务端事件，然后由 _route_event 进行路由。"""
        # 允许在切换/重连后更新处理器的 ws_client 引用
        render_handler.ws_client = ctx.ws_client
        interaction_handler.ws_client = ctx.ws_client
        ctx.ws_client.on_disconnect(interaction_handler.handle_disconnect)
        ctx.ws_client.on_error(interaction_handler.handle_ws_error)

        for event_type in event_dispatcher.event_types:
            ctx.ws_client.on(event_type, _route_event)

    _register_all_ws_events()

    # 返回注册函数，供切换客户端时重新注册
    return _register_all_ws_events


async def async_run(
    config_path: str,
    model: Optional[str],
    agent_name: str,
    session_id: Optional[str],
    debug: bool,
    use_websocket: bool = False,
    ws_url: str = "ws://localhost:8000",
):
    """异步运行 - 在 Textual App 内部执行"""
    # 预先测试连接，失败则直接退出
    # 注意：不预先测试连接，让服务端生成 session_id
    # 连接测试会在 WebSocketClient.connect() 中进行

    tui = get_tui()
    tui.debug_mode = debug

    # 业务逻辑协程（在 Textual App 事件循环内运行）
    async def _run_business_logic(textual_app: DundunApp):
        """业务逻辑"""
        # 等待 App 完全挂载
        await asyncio.sleep(0.1)

        # 先展示欢迎信息（connected 事件到达后会刷新）
        tui.print_welcome()

        # 创建上下文
        ctx = CLIContext()
        ctx.debug_mode = debug

        # 1. 设置日志标识
        set_log_side("CLIENT")

        # 2. 加载配置
        try:
            config = load_config(config_path)

            # 确定当前模型名：--model 参数 > setting.json > config.provider.default
            if model:
                model_name_key = model
            else:
                settings = load_settings()
                saved_model = settings.get("default_model")
                if saved_model:
                    model_name_key = saved_model
                else:
                    model_name_key = config.provider.default
            if not model_name_key and config.provider.models:
                model_name_key = next(iter(config.provider.models))

            # 设置初始环境信息
            if model_name_key:
                model_config = config.provider.models.get(model_name_key)
                if model_config:
                    ctx.runtime.provider = model_config.provider
                    ctx.runtime.model_name = model_name_key
                else:
                    ctx.runtime.model_name = model_name_key
                    ctx.runtime.provider = ""
            else:
                ctx.runtime.model_name = ""
                ctx.runtime.provider = ""
            ctx.runtime.agent_type = agent_name
            ctx.runtime.available_models = list(config.provider.models.keys())

        except Exception as e:
            # 配置加载失败，打印错误并记录日志，然后退出
            import traceback
            error_details = traceback.format_exception(type(e), e, e.__traceback__)
            error_msg = "".join(error_details)
            tui.print_error(f"配置加载失败: {e}")
            log_event("config_load_error", error=str(e), traceback=error_msg)
            # 打印详细错误信息
            print(f"\n✗ Error: 配置加载失败: {e}", file=sys.stderr, flush=True)
            print(f"详细信息:\n{error_msg}", file=sys.stderr, flush=True)
            # 强制退出程序
            os._exit(1)

        # 3. 会话 ID 由服务端生成，客户端先不设置
        # 客户端会在收到 connected 事件后从服务端获取 session_id
        ctx.session_id = ""  # 临时为空，连接成功后更新

        # 4. 连接 WebSocket 服务器
        if not use_websocket:
            tui.print_error("本地模式已废弃，请使用: python -m src.main")
            return

        try:
            ctx.ws_client = WebSocketClient(ws_url, ctx.session_id, ctx.runtime.agent_type)
            # 设置 HTTP 服务端地址（用于 HTTP API 调用）
            ctx.server_url = ws_url.replace("ws://", "http://").replace("wss://", "https://")
            await ctx.ws_client.connect()
            log_event("client_connected", session_id=ctx.session_id)

            # 先注册事件处理器，再启动接收循环（确保 connected 事件能被处理）
            renderer = get_streaming_renderer()
            ctx._reregister_ws_events = await setup_websocket_handlers(ctx, tui, renderer)

            # 启动接收循环（在后台运行）
            receive_task = asyncio.create_task(ctx.ws_client.receive_loop())
            log_event("websocket_connected", session_id=ctx.session_id)

            # 设置 server_url 到 UserInput（用于命令菜单获取会话列表）
            try:
                from interface.widgets import UserInput
                user_input = tui._app.query_one("#user-input", UserInput)
                user_input._server_url = ctx.server_url
            except Exception:
                pass

        except Exception as e:
            # 连接失败，打印完整错误信息并优雅退出
            import traceback
            error_details = traceback.format_exception(type(e), e, e.__traceback__)
            error_msg = "".join(error_details)
            print(f"\n✗ Error: 连接服务器失败: {e}", file=sys.stderr, flush=True)
            print(f"详细信息:\n{error_msg}", file=sys.stderr, flush=True)

            # 显示错误信息到 TUI（如果 App 已启动）
            try:
                tui.print_error(f"连接服务器失败: {e}")
            except Exception:
                pass

            # 使用 Event 来通知 App 退出
            app = tui.app
            if app and hasattr(app, '_business_done'):
                # 设置错误状态
                app._business_error = e
                # 触发退出
                app.exit()
            else:
                # 如果 App 还没准备好，强制退出
                os._exit(1)
            return
    
        # 设置 Ctrl+C 中断回调（通过 Textual action_quit 覆盖）
        app_state = get_app_state()
        renderer = get_streaming_renderer()

        def handle_interrupt():
            """处理 Ctrl+C 中断（由 Textual action_quit 调用）"""
            current_app_state = get_app_state()
            log_event("ctrl_c_interrupt", is_processing=current_app_state.is_processing)

            if current_app_state.is_processing:
                # 处理状态下，发送中断请求
                current_app_state.request_interrupt()
                # 立即停止所有动画和 live 行（防止残留和重复显示）
                renderer._cleanup_all()
                tui.print_info("正在中断...")
                asyncio.create_task(ctx.ws_client.interrupt())
                log_event("interrupt_requested_by_user")
            else:
                # 输入状态下，提示用户使用 /exit
                tui.print_info("使用 /exit 退出")

        textual_app.set_interrupt_callback(handle_interrupt)
        log_event("interrupt_callback_registered")

        # 设置模型列表和状态栏初始值
        textual_app.update_status_bar(
            model=ctx.runtime.model_name,
            agent=ctx.runtime.agent_type,
        )
        textual_app.update_available_models(ctx.runtime.available_models)

        # 5. 运行主循环
        try:
            await main_loop(ctx)
        finally:
            # 清理
            if ctx.ws_client:
                await ctx.ws_client.disconnect(reason="shutdown")
                log_event("client_disconnected", session_id=ctx.session_id)

        # 退出 Textual App
        textual_app.exit()

    # 检查是否有连接错误需要抛出
    if _connection_error:
        raise _connection_error

    # 创建带业务逻辑回调的 Textual App 子类
    class _AppWithLogic(DundunApp):
        def __init__(self):
            super().__init__()
            self._business_error: Optional[Exception] = None

        def on_mount(self) -> None:
            super().on_mount()
            # 创建事件用于等待业务逻辑完成
            self._business_done = asyncio.Event()
            task = asyncio.create_task(_run_business_logic(self))
            # 任务完成后检查是否有异常
            task.add_done_callback(self._check_task_exception)

        def _check_task_exception(self, task: asyncio.Task):
            """检查业务逻辑任务是否有异常"""
            try:
                task.result()
            except Exception as e:
                self._business_error = e
                # 连接错误立即强制退出
                if "连接" in str(e) or isinstance(e, ConnectionError):
                    print(f"\n✗ Error: 连接服务器失败: {e}", file=sys.stderr, flush=True)
                    os._exit(1)
            finally:
                # 标记业务逻辑完成
                if hasattr(self, '_business_done'):
                    self._business_done.set()

    textual_app = _AppWithLogic()
    tui.app = textual_app

    # 运行 Textual App（这会接管终端）
    # 启用鼠标支持，以便滚轮可滚动主输出区（大多数终端可通过 Shift+拖拽 进行文本选择）
    await textual_app.run_async()

    # 等待业务逻辑任务完成
    await textual_app._business_done.wait()

    # 检查业务逻辑是否有未捕获的异常，有则重新抛出
    if textual_app._business_error:
        raise textual_app._business_error


@app.callback(invoke_without_command=True)
def main(
    ctx: typer.Context,
    config_path: str = typer.Option(
        "config.json",
        "--config", "-c",
        help="配置文件名"
    ),
    model: str = typer.Option(
        None,
        "--model", "-m",
        help="覆盖配置中的模型"
    ),
    agent: str = typer.Option(
        "build",
        "--agent", "-a",
        help="Agent 类型: build, plan, general, explore"
    ),
    session: str = typer.Option(
        None,
        "--session", "-s",
        help="指定会话 ID"
    ),
    debug: bool = typer.Option(
        False,
        "--debug", "-d",
        help="调试模式"
    ),
    websocket: bool = typer.Option(
        False,
        "--websocket", "-w",
        help="使用 WebSocket 模式连接到服务器"
    ),
    ws_url: str = typer.Option(
        "ws://localhost:8000",
        "--ws-url",
        help="WebSocket 服务器地址"
    ),
):
    """
    Dundun Agent - AI 编程助手

    直接运行 dundun 启动会话，或使用子命令。

    Agent 类型:
    - general: 通用助手（默认）
    - build: 代码开发专用
    - plan: 规划模式（只读）
    - explore: 代码探索
    """
    # 如果调用了子命令，不执行默认行为
    if ctx.invoked_subcommand is not None:
        return

    asyncio.run(async_run(
        config_path=config_path,
        model=model,
        agent_name=agent,
        session_id=session,
        debug=debug,
        use_websocket=websocket,
        ws_url=ws_url,
    ))


@app.command(name="version")
def version():
    """显示版本信息"""
    tui = get_tui()
    tui._write_safe(Text.from_markup("[bold]Dundun Agent[/bold] v1.1.0"))
    tui._write_safe(Text.from_markup("[dim]Python 版本的 AI 编程助手[/dim]"))


if __name__ == "__main__":
    app()
