Coverage for merco/setup.py: 0%
128 statements
« prev ^ index » next coverage.py v7.15.0, created at 2026-07-07 14:04 +0800
« prev ^ index » next coverage.py v7.15.0, created at 2026-07-07 14:04 +0800
1"""交互式 API 配置向导 — merco setup"""
3import os
4import asyncio
5from pathlib import Path
6from rich.console import Console
7from rich.panel import Panel
8from rich.table import Table
9from rich.text import Text
11console = Console()
13OUTPUT_PATH = "./merco.json"
16def run_setup_wizard() -> None:
17 """交互式配置 API provider、key、model,写入 merco.json"""
19 from merco.core.config import PROVIDER_REGISTRY, ProviderInfo, MercoConfig
21 # ── 欢迎 ──
22 console.print(Panel(
23 "[bold]🚀 欢迎使用 Merco![/bold]\n\n"
24 "首次使用需要配置 AI 模型接口。\n"
25 "已有 API key?一分钟搞定。\n\n"
26 "[dim]按 Ctrl+C 随时退出[/dim]",
27 title="Merco Setup",
28 border_style="green",
29 ))
31 # ── 步骤 1:选平台 ──
32 provider = _pick_provider(PROVIDER_REGISTRY)
34 # ── 步骤 2:填 API key ──
35 api_key = _ask_api_key(provider)
37 # ── 步骤 3:填 model ──
38 model = _ask_model(provider)
40 # ── 步骤 4:填 base_url ──
41 base_url = _ask_base_url(provider)
43 # ── 步骤 5:确认 ──
44 _confirm_and_save(provider, api_key, model, base_url)
47def _pick_provider(providers: dict) -> "ProviderInfo":
48 """展示平台列表,让用户选择"""
49 console.print("\n[bold]第一步:选择 AI 平台[/bold]\n")
51 table = Table(show_header=True, box=None, padding=(0, 2))
52 table.add_column("#", width=3, justify="right", style="dim")
53 table.add_column("平台", style="bold")
54 table.add_column("默认模型", style="dim")
55 table.add_column("说明")
57 items = list(providers.values())
58 for i, p in enumerate(items, 1):
59 table.add_row(str(i), p.name, p.default_model, p.description)
60 # 自定义选项
61 table.add_row(str(len(items) + 1), "[dim]自定义[/dim]", "自行输入", "未收录的平台")
63 console.print(table)
65 while True:
66 try:
67 choice = console.input(f"\n[bold yellow]请选择 (1-{len(items) + 1})[/bold yellow]: ").strip()
68 idx = int(choice)
69 if 1 <= idx <= len(items):
70 return items[idx - 1]
71 if idx == len(items) + 1:
72 return _ask_custom_provider()
73 console.print("[red]无效选择,请重新输入[/red]")
74 except (ValueError, EOFError, KeyboardInterrupt):
75 raise
78def _ask_custom_provider() -> "ProviderInfo":
79 """自定义平台:让用户输入必要信息"""
80 from merco.core.config import ProviderInfo
81 console.print("\n[bold]自定义平台[/bold]")
82 name = console.input("[dim]平台名称 (如 SCNet)[/dim]: ").strip()
83 if not name:
84 name = "custom"
85 key = name.lower().replace(" ", "-")
86 return ProviderInfo(
87 key=key, name=name, base_url="", key_env="",
88 default_model="", models=[],
89 key_help="", description="自定义平台",
90 )
93def _ask_api_key(provider: "ProviderInfo") -> str:
94 """输入 API key"""
95 console.print(f"\n[bold]第二步:配置 {provider.name} API Key[/bold]")
96 if provider.key_help:
97 console.print(f"[dim]获取 Key: {provider.key_help}[/dim]")
98 if provider.key_env:
99 env_val = os.environ.get(provider.key_env, "")
100 if env_val:
101 masked = env_val[:8] + "..." if len(env_val) > 8 else env_val
102 console.print(f"[dim]环境变量 {provider.key_env} 已设置 ({masked})[/dim]")
103 use_env = console.input("[bold yellow]使用环境变量?按 Enter 确认,输入 n 手动填写[/bold yellow]: ").strip().lower()
104 if use_env != "n":
105 return "" # 空字符串 = 用环境变量
107 while True:
108 key = console.input("[bold yellow]API Key[/bold yellow]: ").strip()
109 if key:
110 return key
111 if provider.key_env:
112 console.print(f"[dim]留空将使用环境变量 {provider.key_env}[/dim]")
113 return ""
114 console.print("[red]API Key 不能为空[/red]")
117def _ask_model(provider: "ProviderInfo") -> str:
118 """选择或输入模型名"""
119 console.print(f"\n[bold]第三步:选择模型[/bold]")
121 if provider.models:
122 console.print(f"[dim]{provider.name} 已知模型:[/dim]")
123 for i, m in enumerate(provider.models, 1):
124 marker = " ← 推荐" if m == provider.default_model else ""
125 console.print(f" {i}. {m}{marker}")
127 console.print(f" [dim]或直接输入模型名(按 Enter 使用推荐 {provider.default_model})[/dim]")
128 choice = console.input("[bold yellow]模型[/bold yellow]: ").strip()
130 if not choice:
131 return provider.default_model
132 try:
133 idx = int(choice)
134 if 1 <= idx <= len(provider.models):
135 return provider.models[idx - 1]
136 except ValueError:
137 pass
138 return choice # 用户自定义输入
139 else:
140 default = provider.default_model or "gpt-4o"
141 choice = console.input(
142 f"[bold yellow]模型名 (按 Enter 使用 {default})[/bold yellow]: ").strip()
143 return choice or default
146def _ask_base_url(provider: "ProviderInfo") -> str:
147 """输入或确认 base_url"""
148 console.print(f"\n[bold]第四步:API 端点[/bold]")
149 if provider.base_url:
150 console.print(f"[dim]默认: {provider.base_url}[/dim]")
151 choice = console.input("[bold yellow]base_url (按 Enter 使用默认)[/bold yellow]: ").strip()
152 return choice or provider.base_url
153 else:
154 while True:
155 choice = console.input("[bold yellow]base_url (必填)[/bold yellow]: ").strip()
156 if choice:
157 return choice
158 console.print("[red]base_url 不能为空[/red]")
161def _confirm_and_save(provider: "ProviderInfo", api_key: str, model: str, base_url: str) -> None:
162 """确认配置并写入 merco.json"""
163 from merco.core.config import MercoConfig
165 console.print(f"\n[bold]确认配置[/bold]")
166 console.print(f" 平台: {provider.name}")
167 console.print(f" 模型: {model}")
168 console.print(f" API Key: {'(环境变量)' if not api_key else api_key[:8] + '...' if len(api_key) > 8 else api_key}")
169 console.print(f" base_url: {base_url}")
170 console.print(f" 配置文件: {OUTPUT_PATH}")
172 confirm = console.input("\n[bold yellow]确认并保存?按 Enter 确认,输入 n 取消[/bold yellow]: ").strip().lower()
173 if confirm == "n":
174 console.print("[dim]已取消,配置未保存[/dim]")
175 return
177 # 加载已有配置(如果存在),只更新 model 部分
178 if Path(OUTPUT_PATH).exists():
179 cfg = MercoConfig.load(OUTPUT_PATH)
180 else:
181 cfg = MercoConfig()
183 cfg.model.provider = provider.key
184 cfg.model.model = model
185 cfg.model.base_url = base_url
186 if api_key:
187 cfg.model.api_key = api_key
188 cfg.model.resolve()
190 cfg.save(OUTPUT_PATH)
191 console.print(f"\n[green]✅ 配置已保存到 {OUTPUT_PATH}[/green]")
193 # ── 安装内置技能 ──
194 from merco.skills.builtin import install_builtin_skills
195 installed = install_builtin_skills()
196 if installed:
197 console.print(f"[green]✅ 已安装内置技能: {', '.join(installed)}[/green]")
199 console.print("[dim]运行 merco 开始使用[/dim]")