easyai 1.2.1 → 1.3.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.
@@ -1,663 +1,352 @@
1
1
  require 'json'
2
- require 'tmpdir'
3
2
  require 'fileutils'
4
- require 'open3'
5
- require_relative '../base/file_crypto'
6
- require_relative '../base/system_keychain'
7
- require_relative '../auth/jpslogin'
3
+ require_relative '../auth/jpsloginhelper'
4
+ require_relative 'easyai_config'
8
5
 
9
6
  module EasyAI
7
+ # ConfigManager 类负责业务配置逻辑和用户认证
10
8
  class ConfigManager
11
- REPO_URL = "https://gitee.com/goodtools/EasyAISetting.git"
12
- EASYAI_DIR = File.expand_path('~/.easyai')
13
- CONFIG_REPO_DIR = File.join(EASYAI_DIR, 'EasyAISetting')
14
-
15
- # 类变量初始化
16
- @@no_keychain = false
17
- @@verbose = false
18
- @@tool_type = nil
19
-
20
- # 管理配置仓库:如果存在则更新,不存在则下载
21
- def self.manage_config_repo
22
- # 确保 ~/.easyai 目录存在
23
- FileUtils.mkdir_p(EASYAI_DIR) unless Dir.exist?(EASYAI_DIR)
24
-
25
- if Dir.exist?(CONFIG_REPO_DIR)
26
- update_config_repo
27
- else
28
- download_config_repo
29
- end
30
- end
31
-
32
- # 更新现有的配置仓库
33
- def self.update_config_repo
34
- begin
35
- # 检查是否是有效的 git 仓库
36
- unless Dir.exist?(File.join(CONFIG_REPO_DIR, '.git'))
37
- FileUtils.rm_rf(CONFIG_REPO_DIR)
38
- return download_config_repo
39
- end
40
-
41
- # 切换到仓库目录并拉取更新
42
- Dir.chdir(CONFIG_REPO_DIR) do
43
- # 重置本地更改
44
- reset_success = system('git reset --hard HEAD', out: File::NULL, err: File::NULL)
45
- unless reset_success
46
- FileUtils.rm_rf(CONFIG_REPO_DIR)
47
- return download_config_repo
48
- end
49
-
50
- # 拉取最新更新,捕获错误信息
51
- stdout, stderr, status = Open3.capture3('git pull origin master')
52
-
53
- unless status.success?
54
- puts "⚠️ 配置更新失败,使用缓存配置" if stderr && !stderr.empty?
55
- FileUtils.rm_rf(CONFIG_REPO_DIR)
56
- return download_config_repo
57
- end
58
- end
59
-
60
- return true
61
-
62
- rescue => e
63
- FileUtils.rm_rf(CONFIG_REPO_DIR) if Dir.exist?(CONFIG_REPO_DIR)
64
- return download_config_repo
65
- end
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
66
16
  end
67
-
68
- # 下载配置仓库到固定位置
69
- def self.download_config_repo
70
- puts "正在下载配置仓库到 #{CONFIG_REPO_DIR}..." if @@verbose
71
-
72
- begin
73
- # 克隆仓库到固定位置,捕获错误信息
74
- stdout, stderr, status = Open3.capture3("git clone --quiet #{REPO_URL} #{CONFIG_REPO_DIR}")
75
-
76
- unless status.success?
77
- puts "✗ 配置仓库下载失败"
78
- puts " 错误信息: #{stderr.chomp}" if stderr && !stderr.empty?
79
- puts " 请检查网络连接、git 安装状态"
80
- return false
81
- end
82
-
83
- puts "✓ 配置仓库下载成功"
84
- return true
85
-
86
- rescue => e
87
- puts "✗ 下载配置仓库失败: #{e.message}"
88
- return false
89
- end
17
+
18
+ # 类方法兼容接口
19
+ def self.download_user_config(user_name = nil, options = {})
20
+ manager = new(options)
21
+ manager.download_user_config(user_name)
90
22
  end
91
-
92
- # 从本地配置仓库加载配置
93
- def self.load_from_local_repo(user_name = nil, options = {})
94
- # 首先管理配置仓库
95
- return nil unless manage_config_repo
96
-
97
- # 设置全局选项
98
- @@no_keychain = options[:no_keychain] || false
99
- @@verbose = options[:verbose] || false
100
- @@tool_type = options[:tool_type] || nil
101
-
102
- puts "正在从本地配置仓库加载配置..." if @@verbose
103
- puts " 工具类型: #{@@tool_type || '自动'}" if @@verbose && @@tool_type
104
-
105
- begin
106
- # 检查 index.json 文件,支持加密版本
107
- index_file = File.join(CONFIG_REPO_DIR, "index.json")
108
- encrypted_index_file = File.join(CONFIG_REPO_DIR, "index.json.encrypted")
109
-
110
- # 优先尝试加密的 index 文件
111
- if File.exist?(encrypted_index_file)
112
- users = parse_encrypted_index_file(encrypted_index_file)
113
- elsif File.exist?(index_file)
114
- users = parse_index_file(index_file)
115
- else
116
- puts "未找到 index.json 或 index.json.encrypted,使用默认配置"
117
- config_file = File.join(CONFIG_REPO_DIR, "claude_setting.json")
118
- encrypted_config_file = File.join(CONFIG_REPO_DIR, "claude_setting.json.encrypted")
119
-
120
- if File.exist?(encrypted_config_file)
121
- return load_encrypted_config_file(encrypted_config_file)
122
- elsif File.exist?(config_file)
123
- return load_config_file(config_file)
124
- else
125
- puts "✗ 未找到配置文件"
126
- return nil
127
- end
128
- end
129
-
130
- return nil if users.nil?
131
23
 
132
- # 检查是否有可用用户(支持新旧格式)
133
- if is_users_empty?(users)
134
- puts " index.json 中未找到用户"
135
- return nil
136
- end
137
-
138
- # 获取用户名:优先使用提供的用户名,否则通过 JPS 登录获取
139
- if user_name
140
- selected_user = user_name
141
- else
142
- # 统一使用 JPS 登录获取用户名
143
- selected_user = get_username_from_jps(@@verbose)
144
-
145
- # 处理用户取消的情况
146
- if selected_user == :user_cancelled
147
- puts "✗ 用户取消了授权登录"
148
- return :user_cancelled
149
- elsif selected_user.nil? || selected_user.strip.empty?
150
- puts "✗ JPS 登录失败,无法获取用户名"
151
- return nil
152
- end
153
- end
154
-
155
- # 查找用户配置文件(传入工具类型)
156
- config_filename = find_user_config(users, selected_user.strip, @@tool_type)
157
-
158
- if config_filename.nil?
159
- puts "✗ 用户 '#{selected_user}' 未找到"
160
- # 不显示可用用户列表,保护配置信息
161
- puts " 请联系管理员确认用户权限"
162
- return nil
163
- end
24
+ # 主要接口:下载用户配置
25
+ def download_user_config(user_name = nil)
26
+ puts "正在加载配置..." if @verbose
27
+ log_config_info if @verbose
164
28
 
165
- # 加载配置文件,支持加密版本(find_user_config 已确保包含 .json)
166
- config_file = File.join(CONFIG_REPO_DIR, config_filename)
167
- encrypted_config_file = File.join(CONFIG_REPO_DIR, "#{config_filename}.encrypted")
168
-
169
- puts "✓ 正在为 #{selected_user} 加载配置"
170
-
171
- # 优先尝试加密文件
172
- if File.exist?(encrypted_config_file)
173
- load_encrypted_config_file(encrypted_config_file)
174
- elsif File.exist?(config_file)
175
- load_config_file(config_file)
176
- else
177
- puts "✗ 配置文件未找到: #{config_filename}"
178
- return nil
179
- end
180
-
181
- rescue => e
182
- puts "✗ 加载本地配置失败: #{e.message}"
183
- nil
184
- end
185
- end
186
-
187
- def self.download_user_config(user_name = nil, options = {})
188
- # 优先尝试使用本地配置仓库
189
- config = load_from_local_repo(user_name, options)
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)
190
36
 
191
- # 如果用户取消了授权,返回特殊标识
192
- if config == :user_cancelled
193
- return :user_cancelled
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
194
45
  end
195
46
 
196
- return config if config
47
+ # 查找并加载配置
48
+ config_filename = find_user_config(users, selected_user.strip)
49
+ return nil unless config_filename
197
50
 
198
- # 如果本地仓库失败,回退到临时下载方式
199
- puts "本地配置仓库不可用,使用临时下载..."
200
- download_user_config_temp(user_name, options)
51
+ puts "✓ 正在为 #{selected_user} 加载配置" if @verbose
52
+ get_user_config_content(config_filename)
201
53
  end
202
-
203
- # 原有的临时下载方式(作为备用方案)
204
- def self.download_user_config_temp(user_name = nil, options = {})
205
- puts "正在获取配置文件..."
206
-
207
- # 设置全局选项
208
- @@no_keychain = options[:no_keychain] || false
209
- @@verbose = options[:verbose] || false
210
- @@tool_type = options[:tool_type] || nil
211
-
212
- begin
213
- # 创建临时目录
214
- temp_dir = Dir.mktmpdir("EasyAISetting")
215
-
216
- # 克隆仓库
217
- clone_cmd = "git clone --depth 1 --quiet #{REPO_URL} #{temp_dir}"
218
- success = system(clone_cmd, out: File::NULL, err: File::NULL)
219
-
220
- unless success
221
- puts "✗ 配置文件获取失败"
222
- puts " 请检查网络连接、git 安装,或使用本地配置文件"
223
- cleanup_temp_dir(temp_dir)
224
- return nil
225
- end
226
-
227
- puts "✓ 配置文件获取成功"
228
-
229
- # 检查 index.json 文件,支持加密版本
230
- index_file = File.join(temp_dir, "index.json")
231
- encrypted_index_file = File.join(temp_dir, "index.json.encrypted")
232
-
233
- # 优先尝试加密的 index 文件
234
- if File.exist?(encrypted_index_file)
235
- users = parse_encrypted_index_file(encrypted_index_file)
236
- elsif File.exist?(index_file)
237
- users = parse_index_file(index_file)
238
- else
239
- puts "未找到 index.json 或 index.json.encrypted,使用默认配置"
240
- config_file = File.join(temp_dir, "claude_setting.json")
241
- encrypted_config_file = File.join(temp_dir, "claude_setting.json.encrypted")
242
-
243
- if File.exist?(encrypted_config_file)
244
- config_content = load_encrypted_config_file(encrypted_config_file)
245
- elsif File.exist?(config_file)
246
- config_content = load_config_file(config_file)
247
- else
248
- puts "✗ 未找到配置文件"
249
- cleanup_temp_dir(temp_dir)
250
- return nil
251
- end
252
-
253
- cleanup_temp_dir(temp_dir)
254
- return config_content
255
- end
256
-
257
- return nil if users.nil?
258
54
 
259
- # 检查是否有可用用户(支持新旧格式)
260
- if is_users_empty?(users)
261
- puts "✗ index.json 中未找到用户"
262
- cleanup_temp_dir(temp_dir)
263
- return nil
264
- end
265
-
266
- # 获取用户名:优先使用提供的用户名,否则通过 JPS 登录获取
267
- if user_name
268
- selected_user = user_name
269
- else
270
- # 统一使用 JPS 登录获取用户名
271
- selected_user = get_username_from_jps(@@verbose)
55
+ private
272
56
 
273
- # 处理用户取消的情况
274
- if selected_user == :user_cancelled
275
- puts " 用户取消了授权登录"
276
- cleanup_temp_dir(temp_dir)
277
- return nil
278
- elsif selected_user.nil? || selected_user.strip.empty?
279
- puts "✗ JPS 登录失败,无法获取用户名"
280
- cleanup_temp_dir(temp_dir)
281
- return nil
282
- end
283
- end
284
-
285
- # 查找用户配置文件(传入工具类型)
286
- config_filename = find_user_config(users, selected_user.strip, @@tool_type)
287
-
288
- if config_filename.nil?
289
- puts "✗ 用户 '#{selected_user}' 未找到"
290
- # 不显示可用用户列表,保护配置信息
291
- puts " 请联系管理员确认用户权限"
292
- cleanup_temp_dir(temp_dir)
293
- return nil
294
- end
57
+ def log_config_info
58
+ puts " 工具类型: #{@tool_type || '自动'}" if @tool_type
59
+ puts " 认证平台: #{@platform || (@platform_flag ? '交互式选择' : '自动选择')}" if @tool_type == "claude"
60
+ end
295
61
 
296
- # 加载配置文件,支持加密版本(find_user_config 已确保包含 .json)
297
- config_file = File.join(temp_dir, config_filename)
298
- encrypted_config_file = File.join(temp_dir, "#{config_filename}.encrypted")
299
-
300
- puts "✓ 正在为 #{selected_user} 加载配置"
301
-
302
- # 优先尝试加密文件
303
- if File.exist?(encrypted_config_file)
304
- config_content = load_encrypted_config_file(encrypted_config_file)
305
- elsif File.exist?(config_file)
306
- config_content = load_config_file(config_file)
307
- else
308
- puts "✗ 配置文件未找到: #{config_filename}"
309
- cleanup_temp_dir(temp_dir)
310
- return nil
311
- end
312
-
313
- cleanup_temp_dir(temp_dir)
314
- config_content
315
-
316
- rescue => e
317
- puts "✗ 配置获取失败: #{e.message}"
318
- cleanup_temp_dir(temp_dir) if temp_dir
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 登录失败,无法获取用户名"
319
79
  nil
80
+ else
81
+ result
320
82
  end
321
83
  end
322
-
323
- private
324
-
325
- def self.cleanup_temp_dir(temp_dir)
326
- return unless temp_dir && Dir.exist?(temp_dir)
327
- FileUtils.rm_rf(temp_dir)
84
+
85
+ def get_user_config_content(config_filename)
86
+ # 直接使用 get_config 获取配置内容,它会自动处理解密和清理
87
+ config = EasyAIConfig.get_config(config_filename, verbose: @verbose)
88
+ return config if config
89
+
90
+ puts "✗ 无法加载配置文件: #{config_filename}" if @verbose
91
+ nil
328
92
  end
329
-
330
- def self.parse_index_file(index_file)
331
- begin
332
- index_content = File.read(index_file)
333
- JSON.parse(index_content)
334
- rescue JSON::ParserError => e
335
- puts "✗ 解析 index.json 失败: #{e.message}"
336
- nil
337
- rescue => e
338
- puts "✗ 读取 index.json 失败: #{e.message}"
93
+
94
+ # JPS 登录获取用户名
95
+ def get_username_from_jps
96
+ jps_login = Auth::JPSLoginHelper.new(verbose: @verbose)
97
+ login_result = jps_login.login
98
+
99
+ return :user_cancelled if login_result == :user_cancelled
100
+ return nil unless login_result
101
+
102
+ username = jps_login.get_username
103
+ if username && !username.empty?
104
+ puts "👤 用户: #{username}" if @verbose
105
+ username
106
+ else
339
107
  nil
340
108
  end
109
+ rescue => e
110
+ puts "❌ JPS 登录失败: #{e.message}" if @verbose
111
+ nil
341
112
  end
342
-
343
- # 通过 JPS 登录获取用户名
344
- def self.get_username_from_jps(verbose = false)
345
- begin
346
- jps_login = Auth::JPSLogin.new(verbose: verbose)
347
- login_result = jps_login.login
348
-
349
- # 处理用户主动取消的情况
350
- if login_result == :user_cancelled
351
- return :user_cancelled
352
- end
353
113
 
354
- if login_result
355
- username = jps_login.get_username
356
- if username && !username.empty?
357
- puts "👤 用户: #{username}"
358
- return username
359
- end
360
- end
361
- rescue => e
362
- puts "❌ JPS 登录失败: #{e.message}"
363
- end
114
+ # 检查用户列表是否为空
115
+ def users_empty?(users)
116
+ return true if users.nil? || users.empty?
364
117
 
365
- return nil
366
- end
367
-
368
- def self.find_user_config(users, user_input_clean, tool_type = nil)
369
- # 处理新的分组格式 {"claude": {...}, "gemini": {...}}
118
+ # 处理新的分组格式
370
119
  if users.is_a?(Hash) && (users.key?("claude") || users.key?("gemini"))
371
- # 如果指定了工具类型,只在该组中查找
372
- if tool_type && users[tool_type]
373
- users[tool_type].each do |name, filename|
374
- if name.downcase == user_input_clean.downcase
375
- # 确保文件名包含 .json 后缀
376
- filename = "#{filename}.json" unless filename.end_with?('.json')
377
- return filename
378
- end
379
- end
380
- else
381
- # 未指定工具类型时,按优先级查找(claude > gemini > 其他)
382
- %w[claude gemini gpt].each do |group|
383
- next unless users[group]
384
- users[group].each do |name, filename|
385
- if name.downcase == user_input_clean.downcase
386
- # 确保文件名包含 .json 后缀
387
- filename = "#{filename}.json" unless filename.end_with?('.json')
388
- return filename
120
+ %w[claude gemini gpt].each do |group|
121
+ next unless users[group]
122
+
123
+ if group == "claude" && users[group].is_a?(Hash)
124
+ # Claude 需要检查认证类型
125
+ users[group].each do |auth_type, auth_users|
126
+ next unless auth_users.is_a?(Hash)
127
+ auth_users.each do |name, filename|
128
+ return false if !filename.nil? && !filename.empty?
389
129
  end
390
130
  end
131
+ elsif !users[group].empty?
132
+ return false
391
133
  end
392
134
  end
135
+ true
393
136
  else
394
- # 兼容旧格式:直接的用户名映射
395
- users.each do |name, filename|
396
- if name.downcase == user_input_clean.downcase
397
- # 确保文件名包含 .json 后缀
398
- filename = "#{filename}.json" unless filename.end_with?('.json')
399
- return filename
400
- end
401
- end
137
+ users.empty?
402
138
  end
403
-
404
- nil
405
139
  end
406
140
 
407
- # 获取所有可用的用户名列表
408
- def self.get_all_user_names(users)
409
- all_names = []
410
-
141
+ # 查找用户配置文件
142
+ def find_user_config(users, user_input_clean)
411
143
  # 处理新的分组格式
412
144
  if users.is_a?(Hash) && (users.key?("claude") || users.key?("gemini"))
413
- %w[claude gemini gpt].each do |group|
414
- if users[group] && users[group].is_a?(Hash)
415
- group_names = users[group].keys
416
- all_names.concat(group_names.map { |name| "#{name} (#{group})" })
417
- end
145
+ if @tool_type == "claude" && users["claude"].is_a?(Hash)
146
+ find_claude_user_config(users["claude"], user_input_clean)
147
+ elsif @tool_type && users[@tool_type]
148
+ find_tool_user_config(users[@tool_type], user_input_clean)
149
+ else
150
+ # 未指定工具类型,按优先级查找
151
+ find_any_user_config(users, user_input_clean)
418
152
  end
419
153
  else
420
154
  # 兼容旧格式
421
- all_names = users.keys
155
+ find_legacy_user_config(users, user_input_clean)
422
156
  end
423
-
424
- all_names
425
157
  end
426
158
 
427
- # 获取特定工具的用户名列表
428
- def self.get_tool_user_names(users, tool_type)
429
- return [] unless tool_type
159
+ def find_tool_user_config(tool_users, user_input_clean)
160
+ return nil unless tool_users.is_a?(Hash)
430
161
 
431
- # 处理新的分组格式
432
- if users.is_a?(Hash) && (users.key?("claude") || users.key?("gemini"))
433
- # 新格式:返回指定工具组的用户
434
- users[tool_type].is_a?(Hash) ? users[tool_type].keys : []
435
- else
436
- # 旧格式返回所有用户(因为没有工具分组)
437
- users.is_a?(Hash) ? users.keys : []
162
+ tool_users.each do |name, filename|
163
+ if name.downcase == user_input_clean.downcase
164
+ return normalize_config_filename(filename)
165
+ end
438
166
  end
167
+ nil
439
168
  end
440
169
 
441
- # 检查用户列表是否为空
442
- def self.is_users_empty?(users)
443
- return true if users.nil?
170
+ def find_any_user_config(users, user_input_clean)
171
+ %w[claude gemini gpt].each do |group|
172
+ next unless users[group]
444
173
 
445
- # 处理新的分组格式
446
- if users.is_a?(Hash) && (users.key?("claude") || users.key?("gemini"))
447
- # 检查所有组是否都为空
448
- %w[claude gemini gpt].each do |group|
449
- return false if users[group] && !users[group].empty?
174
+ if group == "claude" && users[group].is_a?(Hash)
175
+ config = find_claude_user_config(users[group], user_input_clean)
176
+ return config if config
177
+ elsif users[group].is_a?(Hash)
178
+ config = find_tool_user_config(users[group], user_input_clean)
179
+ return config if config
450
180
  end
451
- true
452
- else
453
- # 兼容旧格式
454
- users.empty?
455
181
  end
182
+ nil
456
183
  end
457
-
458
- def self.load_config_file(config_file)
459
- begin
460
- config_content = File.read(config_file)
461
- JSON.parse(config_content)
462
- rescue JSON::ParserError => e
463
- puts "✗ 解析配置文件失败: #{e.message}"
464
- nil
465
- rescue => e
466
- puts "✗ 读取配置文件失败: #{e.message}"
467
- nil
184
+
185
+ def find_legacy_user_config(users, user_input_clean)
186
+ users.each do |name, filename|
187
+ if name.downcase == user_input_clean.downcase
188
+ return normalize_config_filename(filename)
189
+ end
468
190
  end
191
+ nil
469
192
  end
470
-
471
- def self.load_encrypted_config_file(encrypted_config_file)
472
- puts "正在解密配置文件..." if @@verbose
473
-
474
- # 使用新的密码验证机制
475
- result = get_and_validate_password(encrypted_config_file, "请输入解密密码: ")
476
- return nil unless result
477
-
478
- begin
479
- # 解析 JSON
480
- JSON.parse(result[:content])
481
- rescue JSON::ParserError => e
482
- puts "✗ 解析解密后的配置文件失败: #{e.message}"
483
- puts "文件可能已损坏或不是有效的JSON格式"
484
- nil
485
- end
193
+
194
+ def normalize_config_filename(filename)
195
+ filename = "#{filename}.json" unless filename.end_with?('.json')
196
+ filename
486
197
  end
487
-
488
- def self.parse_encrypted_index_file(encrypted_index_file)
489
- puts "正在解密 index.json..." if @@verbose
490
-
491
- # 使用新的密码验证机制
492
- result = get_and_validate_password(encrypted_index_file, "请输入解密密码: ")
493
- return nil unless result
494
-
495
- begin
496
- # 解析 JSON
497
- JSON.parse(result[:content])
498
- rescue JSON::ParserError => e
499
- puts "✗ 解析解密后的 index.json 失败: #{e.message}"
500
- puts "文件可能已损坏或不是有效的JSON格式"
501
- nil
198
+
199
+ # 专门处理 Claude 的用户配置查找
200
+ def find_claude_user_config(claude_config, user_input_clean)
201
+ auth_priority = ["claude_auth", "kimi_auth", "deepseek_auth"]
202
+
203
+ # Windows 平台处理:从优先级中移除 claude_auth
204
+ if Gem.win_platform?
205
+ auth_priority = ["kimi_auth", "deepseek_auth"]
206
+ puts " ⚠ Windows 平台跳过 claude_auth,使用其他认证平台" if @verbose
207
+ end
208
+
209
+ if @platform && !@platform.empty?
210
+ # 指定了平台
211
+ return find_claude_config_by_platform(claude_config, @platform, user_input_clean, auth_priority)
212
+ else
213
+ # 未指定平台,按优先级查找
214
+ return find_claude_config_auto(claude_config, user_input_clean, auth_priority)
502
215
  end
503
216
  end
504
-
505
- # 智能密码获取和验证:包含错误处理和重试机制
506
- def self.get_and_validate_password(encrypted_file_path, prompt, max_retries = 3)
507
- retries = 0
508
-
509
- loop do
510
- # 获取密码(系统存储或用户输入)
511
- password_info = get_decryption_password_basic(prompt, retries > 0)
512
- return nil unless password_info
513
-
514
- # 尝试解密验证密码
515
- begin
516
- decrypted_content = decrypt_file_content(encrypted_file_path, password_info[:password])
517
-
518
- # 解密成功,保存密码(如果不是从系统获取且未禁用存储)
519
- unless password_info[:from_system] || @@no_keychain
520
- if Base::SystemKeychain.store_password(password_info[:password])
521
- puts "✓ 密码已安全存储,下次使用将自动获取"
522
- end
523
- end
524
-
525
- return { content: decrypted_content, password_info: password_info }
526
-
527
- rescue => e
528
- retries += 1
529
-
530
- # 分析错误类型并提供针对性提示
531
- error_type = analyze_decryption_error(e)
532
-
533
- # 处理系统密码错误
534
- if password_info[:from_system]
535
- puts "✗ 系统存储的密码无效"
536
- puts " 错误详情:#{e.message}" if error_type[:show_details]
537
- puts " 错误原因:#{error_type[:reason]}"
538
- puts "正在清除无效的系统存储密码..."
539
- Base::SystemKeychain.delete_stored_password
540
- puts "✓ 已清除系统存储的无效密码"
541
- puts "请重新输入正确的解密密码"
542
- else
543
- puts "✗ 解密失败"
544
- puts " 错误详情:#{e.message}" if error_type[:show_details]
545
- puts " 错误原因:#{error_type[:reason]}"
546
- puts " 建议:#{error_type[:suggestion]}"
547
- end
548
-
549
- # 检查是否达到最大重试次数
550
- if retries >= max_retries
551
- puts "\n已达到最大重试次数 (#{max_retries})"
552
- puts "可能的解决方案:"
553
- puts " 1. 确认密码是否正确"
554
- puts " 2. 检查文件是否完整下载"
555
- puts " 3. 联系管理员确认密码是否已更改"
556
-
557
- if ask_user_continue?
558
- retries = 0 # 重置重试次数,继续尝试
559
- puts "\n继续尝试解密..."
560
- else
561
- puts "用户选择退出解密"
562
- return nil
563
- end
564
- else
565
- remaining = max_retries - retries
566
- puts "还可以重试 #{remaining} 次 (#{retries}/#{max_retries})"
567
- puts "提示:按 Ctrl+C 可随时退出\n"
568
- end
569
- end
217
+
218
+ def find_claude_config_by_platform(claude_config, platform, user_input_clean, auth_priority)
219
+ # Windows 平台特殊处理 claude_auth
220
+ if Gem.win_platform? && platform == "claude_auth"
221
+ puts "✗ Windows 平台不支持 claude_auth" if @verbose
222
+ puts " 请选择其他认证平台:kimi_auth 或 deepseek_auth" if @verbose
223
+ return nil
570
224
  end
225
+
226
+ unless auth_priority.include?(platform)
227
+ puts "✗ 认证平台 '#{platform}' 不存在" if @verbose
228
+ puts " 支持的平台: #{auth_priority.join(', ')}" if @verbose
229
+ return nil
230
+ end
231
+
232
+ config = find_in_auth_type(claude_config, platform, user_input_clean)
233
+ if config.nil? && @verbose
234
+ puts "✗ 用户 '#{user_input_clean}' 在 #{platform} 中未找到配置"
235
+ puts " 请联系管理员添加配置或尝试其他认证平台"
236
+ elsif config && @verbose
237
+ puts "✓ 使用认证平台: #{platform}"
238
+ end
239
+ config
571
240
  end
572
-
573
- # 基础密码获取:先从系统获取,失败则提示用户输入
574
- def self.get_decryption_password_basic(prompt, is_retry = false)
575
- # 如果是重试,或者禁用了密码存储,直接提示用户输入
576
- if is_retry || @@no_keychain
577
- if @@no_keychain
578
- puts "已禁用自动密码存储,请手动输入密码"
241
+
242
+ def find_claude_config_auto(claude_config, user_input_clean, auth_priority)
243
+ auth_priority.each do |auth_type|
244
+ config = find_in_auth_type(claude_config, auth_type, user_input_clean)
245
+ if config
246
+ puts "✓ 自动选择认证平台: #{auth_type}" if @verbose
247
+ return config
579
248
  end
580
- password = Base::FileCrypto.get_password(prompt)
581
- return password ? { password: password, from_system: false } : nil
582
249
  end
583
-
584
- # 首先尝试从系统获取存储的密码
585
- stored_password = Base::SystemKeychain.get_stored_password
586
-
587
- if stored_password && !stored_password.empty?
588
- puts "使用系统存储的密码进行解密..." if @@verbose
589
- return { password: stored_password, from_system: true }
250
+
251
+ if @verbose
252
+ puts "✗ 用户 '#{user_input_clean}' 未找到任何配置"
253
+ puts " 已尝试: #{auth_priority.join(', ')}"
590
254
  end
591
-
592
- # 如果没有存储的密码,提示用户输入
593
- password = Base::FileCrypto.get_password(prompt)
594
- return password ? { password: password, from_system: false } : nil
255
+ nil
595
256
  end
596
-
597
- # 分析解密错误类型,提供针对性建议
598
- def self.analyze_decryption_error(error)
599
- case error.message
600
- when /bad decrypt/i, /wrong final block length/i, /invalid padding/i
601
- {
602
- reason: "密码不正确或文件已损坏",
603
- suggestion: "请确认密码是否正确,或检查文件完整性",
604
- show_details: false
605
- }
606
- when /not supported by device/i
607
- {
608
- reason: "系统不支持此加密算法或文件格式错误",
609
- suggestion: "请检查文件是否为正确的加密文件",
610
- show_details: true
611
- }
612
- when /no such file/i, /cannot open/i
613
- {
614
- reason: "文件不存在或无法访问",
615
- suggestion: "请检查文件路径和权限",
616
- show_details: false
617
- }
618
- when /invalid base64/i
619
- {
620
- reason: "文件格式错误或已损坏",
621
- suggestion: "请重新下载配置文件",
622
- show_details: false
623
- }
624
- else
625
- {
626
- reason: "未知的解密错误",
627
- suggestion: "请尝试重新下载文件或联系技术支持",
628
- show_details: true
629
- }
257
+
258
+ # 在特定认证类型中查找用户配置
259
+ def find_in_auth_type(claude_config, auth_type, user_input_clean)
260
+ return nil unless claude_config[auth_type].is_a?(Hash)
261
+
262
+ claude_config[auth_type].each do |name, filename|
263
+ if name.downcase == user_input_clean.downcase && !filename.nil? && !filename.empty?
264
+ filename = normalize_config_filename(filename)
265
+ return "claude/#{auth_type}/#{filename}"
266
+ end
630
267
  end
268
+ nil
631
269
  end
632
-
633
- # 询问用户是否继续尝试
634
- def self.ask_user_continue?
635
- puts "\n是否继续尝试解密?"
636
- puts " y/yes - 继续尝试"
637
- puts " n/no - 退出解密 (默认)"
638
- print "> "
639
-
640
- begin
641
- response = STDIN.gets&.chomp&.downcase
642
- return ['y', 'yes'].include?(response)
643
- rescue Interrupt
644
- puts "\n\n用户中断操作"
645
- return false
646
- rescue => e
647
- puts "无法读取用户输入: #{e.message}"
648
- return false
270
+
271
+ # 让用户选择可用的认证平台
272
+ def select_platform_for_user(users, user_input_clean)
273
+ return nil unless users["claude"].is_a?(Hash)
274
+
275
+ claude_config = users["claude"]
276
+ available_platforms = []
277
+
278
+ # 检查用户在哪些平台有配置
279
+ platforms_to_check = ["claude_auth", "kimi_auth", "deepseek_auth"]
280
+
281
+ # Windows 平台跳过 claude_auth
282
+ if Gem.win_platform?
283
+ platforms_to_check = ["kimi_auth", "deepseek_auth"]
284
+ end
285
+
286
+ platforms_to_check.each do |auth_type|
287
+ if find_in_auth_type(claude_config, auth_type, user_input_clean)
288
+ available_platforms << auth_type
289
+ end
290
+ end
291
+
292
+ # 如果没有可用平台
293
+ if available_platforms.empty?
294
+ puts "✗ 用户 '#{user_input_clean}' 在任何认证平台中都没有配置"
295
+ return nil
296
+ end
297
+
298
+ # 如果只有一个平台,直接使用
299
+ if available_platforms.length == 1
300
+ selected = available_platforms.first
301
+ puts "✓ 自动选择唯一可用平台: #{selected}" if @verbose
302
+ return selected
303
+ end
304
+
305
+ # 多个平台,让用户选择
306
+ puts "\n🔐 请选择认证平台:"
307
+ puts "─" * 40
308
+
309
+ available_platforms.each_with_index do |platform, index|
310
+ platform_name = case platform
311
+ when "claude_auth" then "Claude 官方认证"
312
+ when "kimi_auth" then "Kimi 认证"
313
+ when "deepseek_auth" then "Deepseek 认证"
314
+ else platform
315
+ end
316
+ puts " #{index + 1}. #{platform_name} (#{platform})"
317
+ end
318
+
319
+ puts "─" * 40
320
+
321
+ loop do
322
+ print "请输入选择 (1-#{available_platforms.length},输入 q 退出): "
323
+
324
+ begin
325
+ choice = STDIN.gets&.chomp
326
+
327
+ # 用户选择退出
328
+ if choice.nil? || choice.downcase == 'q'
329
+ puts "✗ 用户取消选择"
330
+ return nil
331
+ end
332
+
333
+ # 验证输入
334
+ choice_num = choice.to_i
335
+ if choice_num >= 1 && choice_num <= available_platforms.length
336
+ selected = available_platforms[choice_num - 1]
337
+ puts "✓ 已选择: #{selected}" if @verbose
338
+ return selected
339
+ else
340
+ puts "✗ 无效的选择,请输入 1-#{available_platforms.length} 之间的数字"
341
+ end
342
+ rescue Interrupt
343
+ puts "\n\n✗ 用户中断操作"
344
+ return nil
345
+ rescue => e
346
+ puts "✗ 输入错误: #{e.message}"
347
+ return nil
348
+ end
649
349
  end
650
- end
651
-
652
- def self.decrypt_file_content(encrypted_file_path, password)
653
- # 生成密钥
654
- key = Base::FileCrypto.generate_key(password)
655
-
656
- # 读取加密文件内容
657
- encrypted_content = File.read(encrypted_file_path)
658
-
659
- # 解密内容
660
- Base::FileCrypto.aes_128_ecb_decrypt(key, encrypted_content)
661
350
  end
662
351
  end
663
352
  end