defmodule Mix.Tasks.Tui do @shortdoc "启动 CMDC TUI 终端界面" @moduledoc """ 启动 CMDC TUI 终端界面。 ## 用法 # 纯输入测试(无 Agent) mix tui # 连接 LLM Agent(无工具) LLM_API_KEY=sk-xxx LLM_BASE_URL=https://... mix tui # 连接 LLM Agent(带工具) LLM_API_KEY=sk-xxx LLM_TOOLS=shell,read_file,list_dir mix tui ## 环境变量 - `LLM_API_KEY` — LLM API 密钥(必须,连接 Agent 时) - `LLM_BASE_URL` — LLM API 地址(可选,默认 Anthropic 官方) - `LLM_MODEL_ID` — 模型 ID(默认 MiniMax-M2.7) - `LLM_SYSTEM_PROMPT` — 系统提示词(默认 "你是一个 AI 助手。") - `LLM_TOOLS` — 启用的工具列表,逗号分隔(可选) - `LLM_WORKING_DIR` — Agent 工作目录(默认当前目录) ## 可用工具 shell, read_file, write_file, edit_file, list_dir, glob, grep, ask_user, write_todos, compact_conversation, task """ use Mix.Task @tool_map %{ "shell" => CMDC.Tool.Shell, "read_file" => CMDC.Tool.ReadFile, "write_file" => CMDC.Tool.WriteFile, "edit_file" => CMDC.Tool.EditFile, "list_dir" => CMDC.Tool.ListDir, "glob" => CMDC.Tool.Glob, "grep" => CMDC.Tool.Grep, "ask_user" => CMDC.Tool.AskUser, "write_todos" => CMDC.Tool.WriteTodos, "compact_conversation" => CMDC.Tool.CompactConversation, "task" => CMDC.Tool.Task } @impl true def run(_args) do Mix.Task.run("app.start") api_key = System.get_env("LLM_API_KEY") base_url = System.get_env("LLM_BASE_URL") model_id = System.get_env("LLM_MODEL_ID", "MiniMax-M2.7") system_prompt = System.get_env("LLM_SYSTEM_PROMPT", "你是一个 AI 助手。") tools_env = System.get_env("LLM_TOOLS", "") working_dir = System.get_env("LLM_WORKING_DIR", File.cwd!()) opts = if api_key do model = if base_url do %{provider: :anthropic, id: model_id, base_url: base_url} else "anthropic:#{model_id}" end tools = parse_tools(tools_env) agent_opts = [ model: model, system_prompt: system_prompt, provider_opts: [api_key: api_key], working_dir: working_dir ] ++ if(tools != [], do: [tools: tools], else: []) case CMDC.create_agent(agent_opts) do {:ok, session} -> tool_names = Enum.map(tools, & &1.name()) tool_info = if tools != [], do: " | tools: #{Enum.join(tool_names, ",")}", else: "" IO.puts("Agent ready: #{model_id}#{tool_info}") [session: session, session_id: "live", model_name: model_id] {:error, reason} -> Mix.shell().error("Agent 创建失败: #{inspect(reason)}") System.halt(1) end else [] end CmdcTui.start(opts) end defp parse_tools(""), do: [] defp parse_tools(tools_str) do tools_str |> String.split(",", trim: true) |> Enum.map(&String.trim/1) |> Enum.flat_map(fn name -> case Map.get(@tool_map, name) do nil -> IO.puts("Warning: unknown tool '#{name}', skipping. Available: #{available_tools()}") [] mod -> [mod] end end) end defp available_tools, do: @tool_map |> Map.keys() |> Enum.sort() |> Enum.join(", ") end