easyai 1.7.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/AGENTS.md +10 -8
- data/CLAUDE.md +211 -126
- data/README.md +176 -36
- data/easyai.gemspec +12 -9
- data/lib/easyai/base/secret_masker.rb +39 -0
- data/lib/easyai/base/system_info.rb +17 -203
- data/lib/easyai/command/ai_tool_base.rb +218 -0
- data/lib/easyai/command/backup/claude.rb +124 -0
- data/lib/easyai/command/backup.rb +23 -0
- data/lib/easyai/command/claude.rb +72 -357
- data/lib/easyai/command/clean.rb +90 -395
- data/lib/easyai/command/codex.rb +39 -0
- data/lib/easyai/command/gemini.rb +23 -41
- data/lib/easyai/command/restore/claude.rb +150 -0
- data/lib/easyai/command/restore.rb +24 -0
- data/lib/easyai/command/setup.rb +487 -378
- data/lib/easyai/command/update.rb +39 -188
- data/lib/easyai/command/utils.rb +2 -7
- data/lib/easyai/command.rb +1 -3
- data/lib/easyai/config/local_config.rb +161 -0
- data/lib/easyai/version.rb +1 -1
- data/lib/easyai.rb +29 -35
- metadata +20 -37
- data/lib/easyai/auth/authclaude.rb +0 -519
- data/lib/easyai/auth/jpsloginhelper.rb +0 -98
- data/lib/easyai/base/system_keychain.rb +0 -283
- data/lib/easyai/command/gpt.rb +0 -259
- data/lib/easyai/command/utils/export.rb +0 -263
- data/lib/easyai/config/config.rb +0 -357
- data/lib/easyai/config/easyai_config.rb +0 -258
|
@@ -1,263 +0,0 @@
|
|
|
1
|
-
require 'json'
|
|
2
|
-
require 'etc'
|
|
3
|
-
require 'open3'
|
|
4
|
-
require 'fileutils'
|
|
5
|
-
require_relative '../../config/config'
|
|
6
|
-
require_relative '../../auth/authclaude'
|
|
7
|
-
require_relative '../../base/system_info'
|
|
8
|
-
|
|
9
|
-
module EasyAI
|
|
10
|
-
class Command
|
|
11
|
-
class Utils < Command
|
|
12
|
-
class Export < Utils
|
|
13
|
-
self.summary = '导出 AI 配置信息'
|
|
14
|
-
self.description = <<-DESC
|
|
15
|
-
导出当前系统的 AI 配置信息到 JSON 文件。
|
|
16
|
-
|
|
17
|
-
主要功能:
|
|
18
|
-
|
|
19
|
-
* 从 ~/.claude.json 读取配置
|
|
20
|
-
|
|
21
|
-
* 从 Keychain 读取凭证(macOS)
|
|
22
|
-
|
|
23
|
-
* 从环境变量读取令牌
|
|
24
|
-
|
|
25
|
-
* 自动生成文件名(使用邮箱地址)
|
|
26
|
-
|
|
27
|
-
使用示例:
|
|
28
|
-
|
|
29
|
-
$ easyai utils export # 自动生成文件名
|
|
30
|
-
|
|
31
|
-
$ easyai utils export --output=config.json # 指定输出文件名
|
|
32
|
-
|
|
33
|
-
$ easyai utils export --verbose # 显示详细信息
|
|
34
|
-
DESC
|
|
35
|
-
|
|
36
|
-
def self.options
|
|
37
|
-
[
|
|
38
|
-
['--output=FILE', '指定输出文件名'],
|
|
39
|
-
['--verbose', '显示详细信息']
|
|
40
|
-
].concat(super)
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
def initialize(argv)
|
|
44
|
-
@output_file = argv.option('output')
|
|
45
|
-
@verbose = argv.flag?('verbose')
|
|
46
|
-
super
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
def run
|
|
50
|
-
puts "正在导出 AI 配置信息..."
|
|
51
|
-
puts "" if @verbose
|
|
52
|
-
|
|
53
|
-
# 收集配置信息
|
|
54
|
-
export_config = {}
|
|
55
|
-
|
|
56
|
-
# 1. 读取 ~/.claude.json
|
|
57
|
-
user_claude_file = File.expand_path("~/.claude.json")
|
|
58
|
-
|
|
59
|
-
if File.exist?(user_claude_file)
|
|
60
|
-
print_status("📄 读取配置", user_claude_file) if @verbose
|
|
61
|
-
|
|
62
|
-
begin
|
|
63
|
-
user_config = JSON.parse(File.read(user_claude_file))
|
|
64
|
-
|
|
65
|
-
# 排除 projects 字段
|
|
66
|
-
claude_config = user_config.reject { |key, _| key == "projects" }
|
|
67
|
-
|
|
68
|
-
# 添加到导出配置
|
|
69
|
-
export_config["claude_json"] = claude_config
|
|
70
|
-
|
|
71
|
-
print_success("读取 ~/.claude.json 成功") if @verbose
|
|
72
|
-
|
|
73
|
-
# 如果未指定输出文件名,尝试从配置中获取邮箱地址
|
|
74
|
-
if @output_file.nil? && claude_config["oauthAccount"]
|
|
75
|
-
email = claude_config["oauthAccount"]["emailAddress"]
|
|
76
|
-
if email && !email.empty?
|
|
77
|
-
# 使用邮箱地址作为文件名(替换特殊字符)
|
|
78
|
-
safe_filename = email.gsub(/[@.]/, '_')
|
|
79
|
-
@output_file = "#{safe_filename}.json"
|
|
80
|
-
puts " 使用邮箱生成文件名: #{@output_file}" if @verbose
|
|
81
|
-
end
|
|
82
|
-
end
|
|
83
|
-
|
|
84
|
-
rescue JSON::ParserError => e
|
|
85
|
-
print_error("解析 ~/.claude.json 失败: #{e.message}")
|
|
86
|
-
exit 1
|
|
87
|
-
rescue => e
|
|
88
|
-
print_error("读取 ~/.claude.json 失败: #{e.message}")
|
|
89
|
-
exit 1
|
|
90
|
-
end
|
|
91
|
-
else
|
|
92
|
-
print_warning("未找到 ~/.claude.json 文件")
|
|
93
|
-
end
|
|
94
|
-
|
|
95
|
-
# 2. 从 Keychain 读取凭证(macOS)
|
|
96
|
-
if Base::SystemInfo.macos?
|
|
97
|
-
print_status("🔐 读取 Keychain", "Claude Code-credentials") if @verbose
|
|
98
|
-
|
|
99
|
-
keychain_data = read_keychain_credentials
|
|
100
|
-
if keychain_data
|
|
101
|
-
export_config["key_chain"] = {
|
|
102
|
-
"service_name" => "Claude Code-credentials",
|
|
103
|
-
"claudeAiOauth" => keychain_data
|
|
104
|
-
}
|
|
105
|
-
print_success("读取 Keychain 凭证成功") if @verbose
|
|
106
|
-
else
|
|
107
|
-
print_warning("未找到 Keychain 凭证(可选)") if @verbose
|
|
108
|
-
end
|
|
109
|
-
else
|
|
110
|
-
puts " ⚠️ 非 macOS 系统,跳过 Keychain 读取" if @verbose
|
|
111
|
-
end
|
|
112
|
-
|
|
113
|
-
# 3. 从环境变量读取令牌(始终包含 env 字段)
|
|
114
|
-
token_value = ENV['CLAUDE_CODE_OAUTH_TOKEN'] || ""
|
|
115
|
-
|
|
116
|
-
export_config["env"] = {
|
|
117
|
-
"CLAUDE_CODE_OAUTH_TOKEN" => token_value
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
if token_value && !token_value.empty?
|
|
121
|
-
print_status("🔑 读取环境变量", "CLAUDE_CODE_OAUTH_TOKEN") if @verbose
|
|
122
|
-
print_success("读取环境变量成功") if @verbose
|
|
123
|
-
else
|
|
124
|
-
print_warning("CLAUDE_CODE_OAUTH_TOKEN 为空(将导出空值)") if @verbose
|
|
125
|
-
end
|
|
126
|
-
|
|
127
|
-
# 4. 读取代理配置(始终包含 claude_proxy 字段)
|
|
128
|
-
proxy_config = {}
|
|
129
|
-
|
|
130
|
-
# 检查 HTTP_PROXY 相关环境变量
|
|
131
|
-
http_proxy = ENV['HTTP_PROXY'] || ENV['http_proxy'] || ""
|
|
132
|
-
proxy_config['HTTP_PROXY'] = http_proxy
|
|
133
|
-
|
|
134
|
-
# 检查 HTTPS_PROXY 相关环境变量
|
|
135
|
-
https_proxy = ENV['HTTPS_PROXY'] || ENV['https_proxy'] || ""
|
|
136
|
-
proxy_config['HTTPS_PROXY'] = https_proxy
|
|
137
|
-
|
|
138
|
-
# 始终添加 claude_proxy 字段
|
|
139
|
-
export_config["claude_proxy"] = proxy_config
|
|
140
|
-
|
|
141
|
-
# 显示状态信息
|
|
142
|
-
if @verbose
|
|
143
|
-
has_http = !http_proxy.empty?
|
|
144
|
-
has_https = !https_proxy.empty?
|
|
145
|
-
|
|
146
|
-
if has_http || has_https
|
|
147
|
-
print_status("🌐 读取代理配置", "环境变量")
|
|
148
|
-
print_success("读取代理配置成功")
|
|
149
|
-
puts " HTTP_PROXY: #{has_http ? mask_url(http_proxy) : '(空)'}"
|
|
150
|
-
puts " HTTPS_PROXY: #{has_https ? mask_url(https_proxy) : '(空)'}"
|
|
151
|
-
else
|
|
152
|
-
print_warning("代理配置为空(将导出空值)")
|
|
153
|
-
end
|
|
154
|
-
end
|
|
155
|
-
|
|
156
|
-
# 检查是否收集到任何配置
|
|
157
|
-
if export_config.empty?
|
|
158
|
-
print_error("未找到任何配置信息可导出")
|
|
159
|
-
puts " 请确保已配置 Claude 或运行 'easyai --setup'"
|
|
160
|
-
exit 1
|
|
161
|
-
end
|
|
162
|
-
|
|
163
|
-
# 设置默认输出文件名
|
|
164
|
-
@output_file ||= "claude_setting.json"
|
|
165
|
-
|
|
166
|
-
# 写入文件
|
|
167
|
-
puts "" if @verbose
|
|
168
|
-
print_status("💾 写入文件", @output_file)
|
|
169
|
-
|
|
170
|
-
begin
|
|
171
|
-
# 美化 JSON 输出
|
|
172
|
-
json_output = JSON.pretty_generate(export_config)
|
|
173
|
-
|
|
174
|
-
# 写入文件
|
|
175
|
-
File.write(@output_file, json_output)
|
|
176
|
-
|
|
177
|
-
print_success("配置成功导出到 #{@output_file}")
|
|
178
|
-
|
|
179
|
-
# 显示导出内容摘要
|
|
180
|
-
if @verbose
|
|
181
|
-
puts "\n导出内容摘要:"
|
|
182
|
-
puts " ✓ claude.json 配置" if export_config["claude_json"]
|
|
183
|
-
puts " ✓ Keychain 凭证" if export_config["key_chain"]
|
|
184
|
-
puts " ✓ 环境变量令牌" if export_config["env"]
|
|
185
|
-
puts " ✓ 代理配置" if export_config["claude_proxy"]
|
|
186
|
-
end
|
|
187
|
-
|
|
188
|
-
rescue => e
|
|
189
|
-
print_error("写入文件失败: #{e.message}")
|
|
190
|
-
exit 1
|
|
191
|
-
end
|
|
192
|
-
end
|
|
193
|
-
|
|
194
|
-
private
|
|
195
|
-
|
|
196
|
-
def claude_available?
|
|
197
|
-
system('which claude > /dev/null 2>&1')
|
|
198
|
-
end
|
|
199
|
-
|
|
200
|
-
def read_keychain_credentials
|
|
201
|
-
return nil unless Base::SystemInfo.macos?
|
|
202
|
-
|
|
203
|
-
service_name = "Claude Code-credentials"
|
|
204
|
-
account_name = Base::SystemInfo.current_user
|
|
205
|
-
|
|
206
|
-
# 获取 Keychain 中的密码
|
|
207
|
-
cmd = [
|
|
208
|
-
"security",
|
|
209
|
-
"find-generic-password",
|
|
210
|
-
"-a", account_name,
|
|
211
|
-
"-s", service_name,
|
|
212
|
-
"-w" # 只输出密码
|
|
213
|
-
]
|
|
214
|
-
|
|
215
|
-
stdout, stderr, status = Open3.capture3(*cmd)
|
|
216
|
-
|
|
217
|
-
if status.success? && !stdout.strip.empty?
|
|
218
|
-
# 尝试解析 JSON 格式的凭证
|
|
219
|
-
begin
|
|
220
|
-
credentials = JSON.parse(stdout.strip)
|
|
221
|
-
return credentials["claudeAiOauth"]
|
|
222
|
-
rescue JSON::ParserError
|
|
223
|
-
# 如果不是 JSON,直接返回字符串
|
|
224
|
-
return stdout.strip
|
|
225
|
-
end
|
|
226
|
-
end
|
|
227
|
-
|
|
228
|
-
nil
|
|
229
|
-
end
|
|
230
|
-
|
|
231
|
-
def mask_url(url)
|
|
232
|
-
# 隐藏代理 URL 中的敏感信息
|
|
233
|
-
return url unless @verbose
|
|
234
|
-
|
|
235
|
-
if url =~ /^(https?:\/\/)([^:]+):([^@]+)@(.+)$/
|
|
236
|
-
protocol, user, pass, rest = $1, $2, $3, $4
|
|
237
|
-
masked_pass = '*' * [pass.length, 8].min
|
|
238
|
-
"#{protocol}#{user}:#{masked_pass}@#{rest}"
|
|
239
|
-
else
|
|
240
|
-
url
|
|
241
|
-
end
|
|
242
|
-
end
|
|
243
|
-
|
|
244
|
-
def print_status(icon_text, detail)
|
|
245
|
-
icon_width = 5 # 图标部分的固定宽度(包括空格)
|
|
246
|
-
puts sprintf("%-#{icon_width}s %s", icon_text, detail.cyan)
|
|
247
|
-
end
|
|
248
|
-
|
|
249
|
-
def print_success(message)
|
|
250
|
-
puts " ✓ #{message}".green
|
|
251
|
-
end
|
|
252
|
-
|
|
253
|
-
def print_warning(message)
|
|
254
|
-
puts " ⚠️ #{message}".yellow
|
|
255
|
-
end
|
|
256
|
-
|
|
257
|
-
def print_error(message)
|
|
258
|
-
puts "✗ #{message}".red
|
|
259
|
-
end
|
|
260
|
-
end
|
|
261
|
-
end
|
|
262
|
-
end
|
|
263
|
-
end
|
data/lib/easyai/config/config.rb
DELETED
|
@@ -1,357 +0,0 @@
|
|
|
1
|
-
require 'json'
|
|
2
|
-
require 'fileutils'
|
|
3
|
-
require_relative '../auth/jpsloginhelper'
|
|
4
|
-
require_relative 'easyai_config'
|
|
5
|
-
|
|
6
|
-
module EasyAI
|
|
7
|
-
# ConfigManager 类负责业务配置逻辑和用户认证
|
|
8
|
-
class ConfigManager
|
|
9
|
-
attr_reader :verbose, :tool_type, :platform
|
|
10
|
-
|
|
11
|
-
def initialize(options = {})
|
|
12
|
-
@verbose = options[:verbose] || false
|
|
13
|
-
@tool_type = options[:tool_type]
|
|
14
|
-
@platform = options[:platform]
|
|
15
|
-
@platform_flag = options[:platform_flag] || false
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
# 类方法兼容接口
|
|
19
|
-
def self.download_user_config(user_name = nil, options = {})
|
|
20
|
-
manager = new(options)
|
|
21
|
-
manager.download_user_config(user_name)
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
# 主要接口:下载用户配置
|
|
25
|
-
def download_user_config(user_name = nil)
|
|
26
|
-
puts "正在加载配置..." if @verbose
|
|
27
|
-
log_config_info if @verbose
|
|
28
|
-
|
|
29
|
-
# 确保配置仓库就绪
|
|
30
|
-
EasyAIConfig.initialize(verbose: @verbose)
|
|
31
|
-
|
|
32
|
-
# 获取 index 配置
|
|
33
|
-
users = get_index_config
|
|
34
|
-
return nil unless users
|
|
35
|
-
return nil if users_empty?(users)
|
|
36
|
-
|
|
37
|
-
# 获取用户名
|
|
38
|
-
selected_user = user_name || get_username_from_jps
|
|
39
|
-
return handle_user_result(selected_user) unless selected_user.is_a?(String)
|
|
40
|
-
|
|
41
|
-
# 处理平台选择
|
|
42
|
-
if @platform_flag && @tool_type == "claude"
|
|
43
|
-
@platform = select_platform_for_user(users, selected_user.strip)
|
|
44
|
-
return nil unless @platform
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
# 查找并加载配置
|
|
48
|
-
config_filename = find_user_config(users, selected_user.strip)
|
|
49
|
-
return nil unless config_filename
|
|
50
|
-
|
|
51
|
-
puts "✓ 正在为 #{selected_user} 加载配置" if @verbose
|
|
52
|
-
get_user_config_content(config_filename)
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
private
|
|
56
|
-
|
|
57
|
-
def log_config_info
|
|
58
|
-
puts " 工具类型: #{@tool_type || '自动'}" if @tool_type
|
|
59
|
-
puts " 认证平台: #{@platform || (@platform_flag ? '交互式选择' : '自动选择')}" if @tool_type == "claude"
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
def get_index_config
|
|
63
|
-
# 尝试获取 index 配置,优先加密版本
|
|
64
|
-
users = EasyAIConfig.get_config('index', verbose: @verbose)
|
|
65
|
-
return users if users
|
|
66
|
-
|
|
67
|
-
# 回退到默认配置
|
|
68
|
-
puts "未找到 index.json,使用默认配置" if @verbose
|
|
69
|
-
EasyAIConfig.get_config('claude_setting', verbose: @verbose)
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
def handle_user_result(result)
|
|
73
|
-
case result
|
|
74
|
-
when :user_cancelled
|
|
75
|
-
puts "✗ 用户取消了授权登录"
|
|
76
|
-
:user_cancelled
|
|
77
|
-
when nil
|
|
78
|
-
puts "✗ JPS 登录失败,无法获取用户名"
|
|
79
|
-
nil
|
|
80
|
-
else
|
|
81
|
-
result
|
|
82
|
-
end
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
def get_user_config_content(config_filename)
|
|
86
|
-
# 获取配置文件路径
|
|
87
|
-
config_path = EasyAIConfig.get_config_path(config_filename, verbose: @verbose)
|
|
88
|
-
return nil unless config_path
|
|
89
|
-
|
|
90
|
-
# 读取配置内容
|
|
91
|
-
begin
|
|
92
|
-
config_content = File.read(config_path)
|
|
93
|
-
config = JSON.parse(config_content)
|
|
94
|
-
|
|
95
|
-
# 添加配置文件路径信息(用于判断认证类型)
|
|
96
|
-
config['_config_path'] = config_filename if config
|
|
97
|
-
|
|
98
|
-
# 删除解密后的文件
|
|
99
|
-
if File.exist?(config_path) && config_path.end_with?('.json')
|
|
100
|
-
encrypted_path = "#{config_path}.encrypted"
|
|
101
|
-
# 只有存在对应的加密文件时才删除解密文件
|
|
102
|
-
if File.exist?(encrypted_path)
|
|
103
|
-
FileUtils.rm_f(config_path)
|
|
104
|
-
puts " 已清理临时解密文件: #{File.basename(config_path)}" if @verbose
|
|
105
|
-
end
|
|
106
|
-
end
|
|
107
|
-
|
|
108
|
-
config
|
|
109
|
-
rescue JSON::ParserError => e
|
|
110
|
-
puts "✗ 解析配置文件失败: #{e.message}" if @verbose
|
|
111
|
-
nil
|
|
112
|
-
rescue => e
|
|
113
|
-
puts "✗ 读取配置文件失败: #{e.message}" if @verbose
|
|
114
|
-
nil
|
|
115
|
-
end
|
|
116
|
-
end
|
|
117
|
-
|
|
118
|
-
# JPS 登录获取用户名
|
|
119
|
-
def get_username_from_jps
|
|
120
|
-
jps_login = Auth::JPSLoginHelper.new(verbose: @verbose)
|
|
121
|
-
login_result = jps_login.login
|
|
122
|
-
|
|
123
|
-
return :user_cancelled if login_result == :user_cancelled
|
|
124
|
-
return nil unless login_result
|
|
125
|
-
|
|
126
|
-
username = jps_login.get_username
|
|
127
|
-
if username && !username.empty?
|
|
128
|
-
puts "👤 用户: #{username}" if @verbose
|
|
129
|
-
username
|
|
130
|
-
else
|
|
131
|
-
nil
|
|
132
|
-
end
|
|
133
|
-
rescue => e
|
|
134
|
-
puts "❌ JPS 登录失败: #{e.message}" if @verbose
|
|
135
|
-
nil
|
|
136
|
-
end
|
|
137
|
-
|
|
138
|
-
# 检查用户列表是否为空
|
|
139
|
-
def users_empty?(users)
|
|
140
|
-
return true if users.nil? || users.empty?
|
|
141
|
-
|
|
142
|
-
# 处理新的分组格式
|
|
143
|
-
if users.is_a?(Hash) && (users.key?("claude") || users.key?("gemini"))
|
|
144
|
-
%w[claude gemini gpt].each do |group|
|
|
145
|
-
next unless users[group]
|
|
146
|
-
|
|
147
|
-
if group == "claude" && users[group].is_a?(Hash)
|
|
148
|
-
# Claude 需要检查认证类型
|
|
149
|
-
users[group].each do |auth_type, auth_users|
|
|
150
|
-
next unless auth_users.is_a?(Hash)
|
|
151
|
-
auth_users.each do |name, filename|
|
|
152
|
-
return false if !filename.nil? && !filename.empty?
|
|
153
|
-
end
|
|
154
|
-
end
|
|
155
|
-
elsif !users[group].empty?
|
|
156
|
-
return false
|
|
157
|
-
end
|
|
158
|
-
end
|
|
159
|
-
true
|
|
160
|
-
else
|
|
161
|
-
users.empty?
|
|
162
|
-
end
|
|
163
|
-
end
|
|
164
|
-
|
|
165
|
-
# 查找用户配置文件
|
|
166
|
-
def find_user_config(users, user_input_clean)
|
|
167
|
-
# 处理新的分组格式
|
|
168
|
-
if users.is_a?(Hash) && (users.key?("claude") || users.key?("gemini"))
|
|
169
|
-
if @tool_type == "claude" && users["claude"].is_a?(Hash)
|
|
170
|
-
find_claude_user_config(users["claude"], user_input_clean)
|
|
171
|
-
elsif @tool_type && users[@tool_type]
|
|
172
|
-
find_tool_user_config(users[@tool_type], user_input_clean)
|
|
173
|
-
else
|
|
174
|
-
# 未指定工具类型,按优先级查找
|
|
175
|
-
find_any_user_config(users, user_input_clean)
|
|
176
|
-
end
|
|
177
|
-
else
|
|
178
|
-
# 兼容旧格式
|
|
179
|
-
find_legacy_user_config(users, user_input_clean)
|
|
180
|
-
end
|
|
181
|
-
end
|
|
182
|
-
|
|
183
|
-
def find_tool_user_config(tool_users, user_input_clean)
|
|
184
|
-
return nil unless tool_users.is_a?(Hash)
|
|
185
|
-
|
|
186
|
-
tool_users.each do |name, filename|
|
|
187
|
-
if name.downcase == user_input_clean.downcase
|
|
188
|
-
return normalize_config_filename(filename)
|
|
189
|
-
end
|
|
190
|
-
end
|
|
191
|
-
nil
|
|
192
|
-
end
|
|
193
|
-
|
|
194
|
-
def find_any_user_config(users, user_input_clean)
|
|
195
|
-
%w[claude gemini gpt].each do |group|
|
|
196
|
-
next unless users[group]
|
|
197
|
-
|
|
198
|
-
if group == "claude" && users[group].is_a?(Hash)
|
|
199
|
-
config = find_claude_user_config(users[group], user_input_clean)
|
|
200
|
-
return config if config
|
|
201
|
-
elsif users[group].is_a?(Hash)
|
|
202
|
-
config = find_tool_user_config(users[group], user_input_clean)
|
|
203
|
-
return config if config
|
|
204
|
-
end
|
|
205
|
-
end
|
|
206
|
-
nil
|
|
207
|
-
end
|
|
208
|
-
|
|
209
|
-
def find_legacy_user_config(users, user_input_clean)
|
|
210
|
-
users.each do |name, filename|
|
|
211
|
-
if name.downcase == user_input_clean.downcase
|
|
212
|
-
return normalize_config_filename(filename)
|
|
213
|
-
end
|
|
214
|
-
end
|
|
215
|
-
nil
|
|
216
|
-
end
|
|
217
|
-
|
|
218
|
-
def normalize_config_filename(filename)
|
|
219
|
-
filename = "#{filename}.json" unless filename.end_with?('.json')
|
|
220
|
-
filename
|
|
221
|
-
end
|
|
222
|
-
|
|
223
|
-
# 专门处理 Claude 的用户配置查找
|
|
224
|
-
def find_claude_user_config(claude_config, user_input_clean)
|
|
225
|
-
auth_priority = ["claude_auth", "aliqwen_auth", "kimi_auth", "deepseek_auth"]
|
|
226
|
-
|
|
227
|
-
if @platform
|
|
228
|
-
# 指定了平台
|
|
229
|
-
return find_claude_config_by_platform(claude_config, @platform, user_input_clean, auth_priority)
|
|
230
|
-
else
|
|
231
|
-
# 未指定平台,按优先级查找
|
|
232
|
-
return find_claude_config_auto(claude_config, user_input_clean, auth_priority)
|
|
233
|
-
end
|
|
234
|
-
end
|
|
235
|
-
|
|
236
|
-
def find_claude_config_by_platform(claude_config, platform, user_input_clean, auth_priority)
|
|
237
|
-
unless auth_priority.include?(platform)
|
|
238
|
-
puts "✗ 认证平台 '#{platform}' 不存在" if @verbose
|
|
239
|
-
puts " 支持的平台: #{auth_priority.join(', ')}" if @verbose
|
|
240
|
-
return nil
|
|
241
|
-
end
|
|
242
|
-
|
|
243
|
-
config = find_in_auth_type(claude_config, platform, user_input_clean)
|
|
244
|
-
if config.nil? && @verbose
|
|
245
|
-
puts "✗ 用户 '#{user_input_clean}' 在 #{platform} 中未找到配置"
|
|
246
|
-
puts " 请联系管理员添加配置或尝试其他认证平台"
|
|
247
|
-
elsif config && @verbose
|
|
248
|
-
puts "✓ 使用认证平台: #{platform}"
|
|
249
|
-
end
|
|
250
|
-
config
|
|
251
|
-
end
|
|
252
|
-
|
|
253
|
-
def find_claude_config_auto(claude_config, user_input_clean, auth_priority)
|
|
254
|
-
auth_priority.each do |auth_type|
|
|
255
|
-
config = find_in_auth_type(claude_config, auth_type, user_input_clean)
|
|
256
|
-
if config
|
|
257
|
-
puts "✓ 自动选择认证平台: #{auth_type}" if @verbose
|
|
258
|
-
return config
|
|
259
|
-
end
|
|
260
|
-
end
|
|
261
|
-
|
|
262
|
-
if @verbose
|
|
263
|
-
puts "✗ 用户 '#{user_input_clean}' 未找到任何配置"
|
|
264
|
-
puts " 已尝试: #{auth_priority.join(', ')}"
|
|
265
|
-
end
|
|
266
|
-
nil
|
|
267
|
-
end
|
|
268
|
-
|
|
269
|
-
# 在特定认证类型中查找用户配置
|
|
270
|
-
def find_in_auth_type(claude_config, auth_type, user_input_clean)
|
|
271
|
-
return nil unless claude_config[auth_type].is_a?(Hash)
|
|
272
|
-
|
|
273
|
-
claude_config[auth_type].each do |name, filename|
|
|
274
|
-
if name.downcase == user_input_clean.downcase && !filename.nil? && !filename.empty?
|
|
275
|
-
filename = normalize_config_filename(filename)
|
|
276
|
-
return "claude/#{auth_type}/#{filename}"
|
|
277
|
-
end
|
|
278
|
-
end
|
|
279
|
-
nil
|
|
280
|
-
end
|
|
281
|
-
|
|
282
|
-
# 让用户选择可用的认证平台
|
|
283
|
-
def select_platform_for_user(users, user_input_clean)
|
|
284
|
-
return nil unless users["claude"].is_a?(Hash)
|
|
285
|
-
|
|
286
|
-
claude_config = users["claude"]
|
|
287
|
-
available_platforms = []
|
|
288
|
-
|
|
289
|
-
# 检查用户在哪些平台有配置
|
|
290
|
-
["claude_auth", "aliqwen_auth", "kimi_auth", "deepseek_auth"].each do |auth_type|
|
|
291
|
-
if find_in_auth_type(claude_config, auth_type, user_input_clean)
|
|
292
|
-
available_platforms << auth_type
|
|
293
|
-
end
|
|
294
|
-
end
|
|
295
|
-
|
|
296
|
-
# 如果没有可用平台
|
|
297
|
-
if available_platforms.empty?
|
|
298
|
-
puts "✗ 用户 '#{user_input_clean}' 在任何认证平台中都没有配置"
|
|
299
|
-
return nil
|
|
300
|
-
end
|
|
301
|
-
|
|
302
|
-
# 如果只有一个平台,直接使用
|
|
303
|
-
if available_platforms.length == 1
|
|
304
|
-
selected = available_platforms.first
|
|
305
|
-
puts "✓ 自动选择唯一可用平台: #{selected}" if @verbose
|
|
306
|
-
return selected
|
|
307
|
-
end
|
|
308
|
-
|
|
309
|
-
# 多个平台,让用户选择
|
|
310
|
-
puts "\n🔐 请选择认证平台:"
|
|
311
|
-
puts "─" * 40
|
|
312
|
-
|
|
313
|
-
available_platforms.each_with_index do |platform, index|
|
|
314
|
-
platform_name = case platform
|
|
315
|
-
when "claude_auth" then "Claude 官方认证"
|
|
316
|
-
when "aliqwen_auth" then "阿里通义千问认证"
|
|
317
|
-
when "kimi_auth" then "Kimi 认证"
|
|
318
|
-
when "deepseek_auth" then "Deepseek 认证"
|
|
319
|
-
else platform
|
|
320
|
-
end
|
|
321
|
-
puts " #{index + 1}. #{platform_name} (#{platform})"
|
|
322
|
-
end
|
|
323
|
-
|
|
324
|
-
puts "─" * 40
|
|
325
|
-
|
|
326
|
-
loop do
|
|
327
|
-
print "请输入选择 (1-#{available_platforms.length},输入 q 退出): "
|
|
328
|
-
|
|
329
|
-
begin
|
|
330
|
-
choice = STDIN.gets&.chomp
|
|
331
|
-
|
|
332
|
-
# 用户选择退出
|
|
333
|
-
if choice.nil? || choice.downcase == 'q'
|
|
334
|
-
puts "✗ 用户取消选择"
|
|
335
|
-
return nil
|
|
336
|
-
end
|
|
337
|
-
|
|
338
|
-
# 验证输入
|
|
339
|
-
choice_num = choice.to_i
|
|
340
|
-
if choice_num >= 1 && choice_num <= available_platforms.length
|
|
341
|
-
selected = available_platforms[choice_num - 1]
|
|
342
|
-
puts "✓ 已选择: #{selected}" if @verbose
|
|
343
|
-
return selected
|
|
344
|
-
else
|
|
345
|
-
puts "✗ 无效的选择,请输入 1-#{available_platforms.length} 之间的数字"
|
|
346
|
-
end
|
|
347
|
-
rescue Interrupt
|
|
348
|
-
puts "\n\n✗ 用户中断操作"
|
|
349
|
-
return nil
|
|
350
|
-
rescue => e
|
|
351
|
-
puts "✗ 输入错误: #{e.message}"
|
|
352
|
-
return nil
|
|
353
|
-
end
|
|
354
|
-
end
|
|
355
|
-
end
|
|
356
|
-
end
|
|
357
|
-
end
|