Coverage for merco/setup.py: 0%

128 statements  

« prev     ^ index     » next       coverage.py v7.15.0, created at 2026-07-07 14:04 +0800

1"""交互式 API 配置向导 — merco setup""" 

2 

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 

10 

11console = Console() 

12 

13OUTPUT_PATH = "./merco.json" 

14 

15 

16def run_setup_wizard() -> None: 

17 """交互式配置 API provider、key、model,写入 merco.json""" 

18 

19 from merco.core.config import PROVIDER_REGISTRY, ProviderInfo, MercoConfig 

20 

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 )) 

30 

31 # ── 步骤 1:选平台 ── 

32 provider = _pick_provider(PROVIDER_REGISTRY) 

33 

34 # ── 步骤 2:填 API key ── 

35 api_key = _ask_api_key(provider) 

36 

37 # ── 步骤 3:填 model ── 

38 model = _ask_model(provider) 

39 

40 # ── 步骤 4:填 base_url ── 

41 base_url = _ask_base_url(provider) 

42 

43 # ── 步骤 5:确认 ── 

44 _confirm_and_save(provider, api_key, model, base_url) 

45 

46 

47def _pick_provider(providers: dict) -> "ProviderInfo": 

48 """展示平台列表,让用户选择""" 

49 console.print("\n[bold]第一步:选择 AI 平台[/bold]\n") 

50 

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("说明") 

56 

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]", "自行输入", "未收录的平台") 

62 

63 console.print(table) 

64 

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 

76 

77 

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 ) 

91 

92 

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 "" # 空字符串 = 用环境变量 

106 

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]") 

115 

116 

117def _ask_model(provider: "ProviderInfo") -> str: 

118 """选择或输入模型名""" 

119 console.print(f"\n[bold]第三步:选择模型[/bold]") 

120 

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}") 

126 

127 console.print(f" [dim]或直接输入模型名(按 Enter 使用推荐 {provider.default_model})[/dim]") 

128 choice = console.input("[bold yellow]模型[/bold yellow]: ").strip() 

129 

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 

144 

145 

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]") 

159 

160 

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 

164 

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}") 

171 

172 confirm = console.input("\n[bold yellow]确认并保存?按 Enter 确认,输入 n 取消[/bold yellow]: ").strip().lower() 

173 if confirm == "n": 

174 console.print("[dim]已取消,配置未保存[/dim]") 

175 return 

176 

177 # 加载已有配置(如果存在),只更新 model 部分 

178 if Path(OUTPUT_PATH).exists(): 

179 cfg = MercoConfig.load(OUTPUT_PATH) 

180 else: 

181 cfg = MercoConfig() 

182 

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() 

189 

190 cfg.save(OUTPUT_PATH) 

191 console.print(f"\n[green]✅ 配置已保存到 {OUTPUT_PATH}[/green]") 

192 

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]") 

198 

199 console.print("[dim]运行 merco 开始使用[/dim]")