easyai 1.2.0 → 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.
@@ -6,20 +6,29 @@ require 'fileutils'
6
6
  module EasyAI
7
7
  module Auth
8
8
  class AuthClaude
9
+ # 常量定义
10
+ SERVICE_NAME = "Claude Code-credentials"
11
+ TOKEN_KEY = "CLAUDE_CODE_OAUTH_TOKEN"
12
+ ALT_TOKEN_KEY = "ANTHROPIC_AUTH_TOKEN"
13
+ CLAUDE_JSON_PATH = File.expand_path("~/.claude.json")
14
+
9
15
  def self.configure(config, user_name = nil)
10
16
  return false unless config
11
-
17
+
18
+ # 第一步:清理系统环境,确保干净的运行环境
19
+ clean_system_environment
20
+
12
21
  # 检查认证配置优先级
13
- is_macos = RUBY_PLATFORM.include?('darwin')
14
- keychain_exists = is_macos && config["key_chain"] && config["key_chain"]["claudeAiOauth"] && config["key_chain"]["service_name"]
15
- env_token_exists = config["env"] && config["env"]["CLAUDE_CODE_OAUTH_TOKEN"]
22
+ keychain_exists = macos? && config["key_chain"] && config["key_chain"]["claudeAiOauth"] && config["key_chain"]["service_name"]
23
+ # 检查两种可能的令牌键
24
+ env_token_exists = config["env"] && (config["env"][TOKEN_KEY] || config["env"][ALT_TOKEN_KEY])
16
25
 
17
26
  # 如果使用 Keychain,需要将令牌从 Keychain 读取到配置中
18
27
  if keychain_exists
19
28
  token_from_keychain = get_token_from_keychain(config["key_chain"]["service_name"])
20
29
  if token_from_keychain
21
30
  config["env"] ||= {}
22
- config["env"]["CLAUDE_CODE_OAUTH_TOKEN"] = token_from_keychain
31
+ config["env"][TOKEN_KEY] = token_from_keychain
23
32
  end
24
33
  end
25
34
 
@@ -29,15 +38,28 @@ module EasyAI
29
38
  clean_env_variables
30
39
  elsif env_token_exists
31
40
  verify_token_config(config)
32
- clean_keychain if is_macos
41
+ clean_keychain if macos?
33
42
  else
34
- token = prompt_for_token
35
- return false if token.nil? || token.empty?
36
-
43
+ # 先检查环境变量中是否已有令牌
44
+ token = check_env_tokens
45
+
46
+ if token.nil? || token.empty?
47
+ # 环境变量中没有,才提示用户输入
48
+ token = prompt_for_token
49
+ return false if token.nil? || token.empty?
50
+ else
51
+ log_verbose(" ✓ 使用环境变量中的令牌")
52
+ end
53
+
37
54
  # 将令牌添加到配置中进行处理
55
+ # 如果配置中已经有 ANTHROPIC_AUTH_TOKEN,使用该键;否则使用 CLAUDE_CODE_OAUTH_TOKEN
38
56
  config["env"] ||= {}
39
- config["env"]["CLAUDE_CODE_OAUTH_TOKEN"] = token
40
-
57
+ if config["env"][ALT_TOKEN_KEY]
58
+ config["env"][ALT_TOKEN_KEY] = token
59
+ else
60
+ config["env"][TOKEN_KEY] = token
61
+ end
62
+
41
63
  verify_token_config(config)
42
64
  clean_keychain
43
65
  end
@@ -50,18 +72,103 @@ module EasyAI
50
72
 
51
73
  true
52
74
  end
53
-
75
+
54
76
  private
55
-
77
+
78
+ # 辅助方法
79
+ def self.macos?
80
+ RUBY_PLATFORM.include?('darwin')
81
+ end
82
+
83
+ def self.current_user
84
+ @current_user ||= Etc.getlogin || ENV['USER'] || ENV['LOGNAME']
85
+ end
86
+
87
+ def self.log_verbose(message)
88
+ puts message if ENV['EASYAI_DEBUG'] || ENV['EASYAI_VERBOSE']
89
+ end
90
+
91
+ def self.log_debug(message)
92
+ puts message if ENV['EASYAI_DEBUG']
93
+ end
94
+
95
+ # 通用的 shell 文件处理方法
96
+ def self.process_shell_files(operation_name)
97
+ shell_files = get_shell_files
98
+
99
+ shell_files.each do |shell_file|
100
+ next unless File.exist?(shell_file)
101
+
102
+ begin
103
+ content = File.read(shell_file)
104
+ lines = content.split("\n", -1)
105
+ lines.pop if lines.last == ""
106
+
107
+ original_count = lines.length
108
+ lines = yield(lines, shell_file)
109
+ removed_count = original_count - lines.length
110
+
111
+ if removed_count > 0
112
+ File.write(shell_file, lines.join("\n") + "\n")
113
+ log_verbose(" ✓ 已从 #{File.basename(shell_file)} 中清理 #{removed_count} 行#{operation_name}")
114
+ end
115
+ rescue => e
116
+ log_debug(" ⚠ 处理 #{shell_file} 失败: #{e.message}")
117
+ end
118
+ end
119
+ end
120
+
121
+ def self.clean_system_environment
122
+ log_verbose("🧹 清理系统环境...")
123
+
124
+ # 1. 清理 ~/.claude.json 中的 oauthAccount
125
+ clean_claude_json_oauth
126
+
127
+ # 2. 清理 Keychain(仅 macOS)
128
+ clean_keychain if macos?
129
+
130
+ # 3. 清理环境变量中的 token
131
+ clean_env_variables
132
+
133
+ # 4. 清理环境变量中的代理设置
134
+ clean_proxy_variables
135
+
136
+ log_verbose(" ✓ 系统环境清理完成")
137
+ end
138
+
139
+ def self.clean_claude_json_oauth
140
+ return unless File.exist?(CLAUDE_JSON_PATH)
141
+
142
+ begin
143
+ content = File.read(CLAUDE_JSON_PATH)
144
+ config = JSON.parse(content)
145
+
146
+ # 删除 oauthAccount 字段
147
+ if config.delete("oauthAccount")
148
+ # 写回文件
149
+ File.write(CLAUDE_JSON_PATH, JSON.pretty_generate(config))
150
+ log_verbose(" ✓ 已清理 ~/.claude.json 中的 oauthAccount")
151
+ end
152
+ rescue JSON::ParserError => e
153
+ log_debug(" ⚠ 解析 ~/.claude.json 失败: #{e.message}")
154
+ rescue => e
155
+ log_debug(" ⚠ 清理 ~/.claude.json 失败: #{e.message}")
156
+ end
157
+ end
158
+
159
+ def self.clean_proxy_variables
160
+ process_shell_files("代理配置") do |lines, shell_file|
161
+ remove_all_proxy_config(lines, shell_file)
162
+ end
163
+ end
164
+
56
165
  def self.get_token_from_keychain(service_name)
57
- return nil unless RUBY_PLATFORM.include?('darwin')
58
-
59
- account_name = Etc.getlogin || ENV['USER'] || ENV['LOGNAME']
60
-
166
+ return nil unless macos?
167
+
61
168
  cmd = [
62
169
  "security",
63
170
  "find-generic-password",
64
- "-a", account_name,
171
+ "-a", current_user,
65
172
  "-s", service_name,
66
173
  "-w"
67
174
  ]
@@ -86,32 +193,30 @@ module EasyAI
86
193
  credentials = {
87
194
  "claudeAiOauth" => config["key_chain"]["claudeAiOauth"]
88
195
  }
89
-
196
+
90
197
  service_name = config["key_chain"]["service_name"]
91
- account_name = Etc.getlogin || ENV['USER'] || ENV['LOGNAME']
92
- label = service_name
93
198
  password = credentials.to_json
94
-
199
+
95
200
  cmd = [
96
201
  "security",
97
202
  "add-generic-password",
98
- "-a", account_name,
203
+ "-a", current_user,
99
204
  "-s", service_name,
100
- "-l", label,
205
+ "-l", service_name,
101
206
  "-w", password,
102
207
  "-U",
103
208
  "-T", ""
104
209
  ]
105
-
210
+
106
211
  system(*cmd)
107
-
212
+
108
213
  if $?.success?
109
- verify_keychain_entry(account_name, service_name)
214
+ verify_keychain_entry(current_user, service_name)
110
215
  else
111
216
  puts "✗ 保存凭证到 Keychain 失败"
112
217
  return false
113
218
  end
114
-
219
+
115
220
  true
116
221
  end
117
222
 
@@ -129,9 +234,10 @@ module EasyAI
129
234
  end
130
235
 
131
236
  def self.verify_token_config(config)
132
- token = config["env"]["CLAUDE_CODE_OAUTH_TOKEN"]
237
+ # 检查两种可能的令牌键
238
+ token = config["env"][TOKEN_KEY] || config["env"][ALT_TOKEN_KEY]
133
239
  return false unless token
134
-
240
+
135
241
  # 只验证令牌存在且不为空,不写入任何配置文件
136
242
  if token && !token.strip.empty?
137
243
  # 清理任何现有的环境配置文件中的令牌定义
@@ -144,27 +250,25 @@ module EasyAI
144
250
  end
145
251
 
146
252
  def self.update_claude_json(config)
147
- user_claude_file = File.expand_path("~/.claude.json")
148
-
149
253
  begin
150
254
  # 读取现有文件或创建空哈希
151
- if File.exist?(user_claude_file)
152
- user_config = JSON.parse(File.read(user_claude_file))
255
+ if File.exist?(CLAUDE_JSON_PATH)
256
+ user_config = JSON.parse(File.read(CLAUDE_JSON_PATH))
153
257
  else
154
258
  user_config = {}
155
259
  end
156
-
260
+
157
261
  # 合并 claude_json 内容,过滤掉代理相关字段
158
262
  claude_config = config["claude_json"].dup
159
263
  claude_config.delete("claude_proxy")
160
264
  claude_config.delete("HTTP_PROXY")
161
265
  claude_config.delete("HTTPS_PROXY")
162
-
266
+
163
267
  user_config.merge!(claude_config)
164
-
268
+
165
269
  # 写回文件
166
- File.write(user_claude_file, JSON.pretty_generate(user_config))
167
-
270
+ File.write(CLAUDE_JSON_PATH, JSON.pretty_generate(user_config))
271
+
168
272
  rescue JSON::ParserError => e
169
273
  puts "✗ 解析现有 ~/.claude.json 失败: #{e.message}"
170
274
  false
@@ -191,7 +295,7 @@ module EasyAI
191
295
  lines.pop if lines.last == ""
192
296
 
193
297
  # 移除所有现有的代理相关配置
194
- lines = remove_proxy_config(lines, shell_file)
298
+ lines = remove_all_proxy_config(lines, shell_file)
195
299
 
196
300
  # 只添加代理别名/函数,不直接设置环境变量
197
301
  lines = add_proxy_aliases(lines, shell_file, http_proxy, https_proxy)
@@ -208,10 +312,17 @@ module EasyAI
208
312
  puts "✅ 代理别名已配置 | claude_proxy / unclaude_proxy"
209
313
  end
210
314
 
315
+ # 检查环境变量中的令牌
316
+ def self.check_env_tokens
317
+ # 优先使用 CLAUDE_CODE_OAUTH_TOKEN,其次是 ANTHROPIC_AUTH_TOKEN
318
+ token = ENV[TOKEN_KEY] || ENV[ALT_TOKEN_KEY]
319
+ token&.strip&.empty? ? nil : token
320
+ end
321
+
211
322
  def self.prompt_for_token
212
323
  puts "请输入您的 CLAUDE_CODE_OAUTH_TOKEN:"
213
324
  print "> "
214
-
325
+
215
326
  begin
216
327
  # 简化输入处理,直接使用 STDIN
217
328
  STDIN.gets&.chomp
@@ -223,49 +334,28 @@ module EasyAI
223
334
  end
224
335
 
225
336
  def self.clean_env_variables
226
- shell_files = get_shell_files
227
-
228
- shell_files.each do |shell_file|
229
- next unless File.exist?(shell_file)
230
-
231
- begin
232
- content = File.read(shell_file)
233
- lines = content.split("\n", -1)
234
- lines.pop if lines.last == ""
235
-
236
- patterns = get_token_patterns(shell_file)
237
- original_count = lines.length
238
- lines.reject! { |line| patterns.any? { |pattern| line.match(pattern) } }
239
- removed_count = original_count - lines.length
240
-
241
- if removed_count > 0
242
- File.write(shell_file, lines.join("\n") + "\n")
243
- end
244
- rescue => e
245
- puts " ✗ 清理 #{shell_file} 失败: #{e.message}"
246
- end
337
+ process_shell_files("令牌配置") do |lines, shell_file|
338
+ patterns = get_token_patterns(shell_file)
339
+ lines.reject { |line| patterns.any? { |pattern| line.match(pattern) } }
247
340
  end
248
341
  end
249
342
 
250
343
  def self.clean_keychain
251
- is_macos = RUBY_PLATFORM.include?('darwin')
252
-
253
- if is_macos
254
- service_name = "Claude Code-credentials"
255
- account_name = Etc.getlogin || ENV['USER'] || ENV['LOGNAME']
256
-
257
- cmd = [
258
- "security",
259
- "delete-generic-password",
260
- "-a", account_name,
261
- "-s", service_name
262
- ]
263
-
264
- stdout, stderr, status = Open3.capture3(*cmd)
265
-
266
- unless status.success? || stderr.include?("could not be found")
267
- puts " 警告: 清理 Keychain 失败: #{stderr}"
268
- end
344
+ return unless macos?
345
+
346
+ cmd = [
347
+ "security",
348
+ "delete-generic-password",
349
+ "-a", current_user,
350
+ "-s", SERVICE_NAME
351
+ ]
352
+
353
+ stdout, stderr, status = Open3.capture3(*cmd)
354
+
355
+ if status.success?
356
+ log_verbose(" ✓ 已清理 Keychain 中的 Claude Code 凭证")
357
+ elsif !stderr.include?("could not be found")
358
+ log_debug(" ⚠ 清理 Keychain 失败: #{stderr}")
269
359
  end
270
360
  end
271
361
 
@@ -273,77 +363,95 @@ module EasyAI
273
363
  shell_files = []
274
364
  current_shell = ENV['SHELL']&.split('/')&.last || 'bash'
275
365
 
276
- case current_shell
277
- when 'zsh'
278
- shell_files << File.expand_path("~/.zshrc")
279
- when 'bash'
280
- bash_profile = File.expand_path("~/.bash_profile")
281
- bashrc = File.expand_path("~/.bashrc")
282
- shell_files << (File.exist?(bash_profile) ? bash_profile : bashrc)
283
- when 'fish'
284
- fish_config = File.expand_path("~/.config/fish/config.fish")
285
- shell_files << fish_config
286
-
287
- # 确保 fish 配置目录存在
288
- fish_dir = File.dirname(fish_config)
289
- unless File.exist?(fish_dir)
290
- FileUtils.mkdir_p(fish_dir)
291
- puts " 创建目录: #{fish_dir}"
366
+ if Gem.win_platform?
367
+ # Windows 平台配置
368
+ powershell_profile = File.expand_path("~/Documents/WindowsPowerShell/Microsoft.PowerShell_profile.ps1")
369
+ powershell_core_profile = File.expand_path("~/Documents/PowerShell/Microsoft.PowerShell_profile.ps1")
370
+
371
+ # 检查哪个 PowerShell 配置文件存在或需要创建
372
+ if File.exist?(File.dirname(powershell_core_profile))
373
+ shell_files << powershell_core_profile
374
+ elsif File.exist?(File.dirname(powershell_profile))
375
+ shell_files << powershell_profile
376
+ else
377
+ # 默认使用 PowerShell Core 配置
378
+ ps_dir = File.dirname(powershell_core_profile)
379
+ FileUtils.mkdir_p(ps_dir) unless File.exist?(ps_dir)
380
+ shell_files << powershell_core_profile
292
381
  end
293
382
  else
294
- shell_files << File.expand_path("~/.profile")
383
+ # Unix/Linux/macOS 平台配置
384
+ case current_shell
385
+ when 'zsh'
386
+ shell_files << File.expand_path("~/.zshrc")
387
+ when 'bash'
388
+ bash_profile = File.expand_path("~/.bash_profile")
389
+ bashrc = File.expand_path("~/.bashrc")
390
+ shell_files << (File.exist?(bash_profile) ? bash_profile : bashrc)
391
+ when 'fish'
392
+ fish_config = File.expand_path("~/.config/fish/config.fish")
393
+ shell_files << fish_config
394
+
395
+ # 确保 fish 配置目录存在
396
+ fish_dir = File.dirname(fish_config)
397
+ unless File.exist?(fish_dir)
398
+ FileUtils.mkdir_p(fish_dir)
399
+ puts " 创建目录: #{fish_dir}"
400
+ end
401
+ else
402
+ shell_files << File.expand_path("~/.profile")
403
+ end
404
+
405
+ # 总是添加 ~/.profile 作为备份
406
+ profile_file = File.expand_path("~/.profile")
407
+ shell_files << profile_file unless shell_files.include?(profile_file)
295
408
  end
296
409
 
297
- # 总是添加 ~/.profile 作为备份
298
- profile_file = File.expand_path("~/.profile")
299
- shell_files << profile_file unless shell_files.include?(profile_file)
300
-
301
410
  shell_files
302
411
  end
303
412
 
304
413
  def self.get_token_patterns(shell_file)
305
- key = "CLAUDE_CODE_OAUTH_TOKEN"
306
-
307
414
  if shell_file.include?("config.fish")
308
415
  [
309
- /^set\s+-gx\s+#{Regexp.escape(key)}\s/,
310
- /^set\s+-x\s+#{Regexp.escape(key)}\s/,
311
- /^#set\s+-gx\s+#{Regexp.escape(key)}\s/
416
+ /^set\s+-gx\s+#{Regexp.escape(TOKEN_KEY)}\s/,
417
+ /^set\s+-x\s+#{Regexp.escape(TOKEN_KEY)}\s/,
418
+ /^#set\s+-gx\s+#{Regexp.escape(TOKEN_KEY)}\s/
312
419
  ]
313
420
  else
314
421
  [
315
- /^export\s+#{Regexp.escape(key)}=/,
316
- /^#{Regexp.escape(key)}=/,
317
- /^#export\s+#{Regexp.escape(key)}=/
422
+ /^export\s+#{Regexp.escape(TOKEN_KEY)}=/,
423
+ /^#{Regexp.escape(TOKEN_KEY)}=/,
424
+ /^#export\s+#{Regexp.escape(TOKEN_KEY)}=/
318
425
  ]
319
426
  end
320
427
  end
321
-
428
+
322
429
  def self.get_export_line(shell_file, token)
323
- key = "CLAUDE_CODE_OAUTH_TOKEN"
324
-
325
430
  if shell_file.include?("config.fish")
326
- "set -gx #{key} \"#{token}\""
431
+ "set -gx #{TOKEN_KEY} \"#{token}\""
327
432
  else
328
- "export #{key}=\"#{token}\""
433
+ "export #{TOKEN_KEY}=\"#{token}\""
329
434
  end
330
435
  end
331
436
 
332
- def self.remove_proxy_config(lines, shell_file)
437
+ def self.remove_all_proxy_config(lines, shell_file)
438
+ # 更全面的代理配置清理,包括所有可能的代理设置
333
439
  if shell_file.include?("config.fish")
334
- # Fish shell 代理配置清理 - 移除直接的环境变量设置和代理相关的别名/函数
440
+ # Fish shell 代理配置清理
335
441
  lines.reject! { |line|
336
- line.match(/^set\s+-[gx]+\s+(HTTP_PROXY|HTTPS_PROXY|http_proxy|https_proxy)\s/) ||
337
- line.match(/^export\s+(HTTP_PROXY|HTTPS_PROXY|http_proxy|https_proxy)=/) ||
338
- line.match(/^alias\s+claude_proxy=/) ||
339
- line.match(/^alias\s+unclaude_proxy=/) ||
340
- line.match(/^function\s+(claude_proxy|unclaude_proxy)/)
442
+ # 清理环境变量设置
443
+ line.match(/^set\s+-[gx]+\s+(HTTP_PROXY|HTTPS_PROXY|http_proxy|https_proxy|ALL_PROXY|all_proxy|NO_PROXY|no_proxy)\s/) ||
444
+ line.match(/^export\s+(HTTP_PROXY|HTTPS_PROXY|http_proxy|https_proxy|ALL_PROXY|all_proxy|NO_PROXY|no_proxy)=/) ||
445
+ # 清理别名
446
+ line.match(/^alias\s+(claude_proxy|unclaude_proxy|proxy|unproxy)=/) ||
447
+ # 清理函数
448
+ line.match(/^function\s+(claude_proxy|unclaude_proxy|proxy|unproxy)/)
341
449
  }
342
-
450
+
343
451
  # 移除函数块
344
452
  in_function = false
345
- lines.select do |line|
346
- if line.match(/^function\s+(claude_proxy|unclaude_proxy)/)
453
+ lines = lines.select do |line|
454
+ if line.match(/^function\s+(claude_proxy|unclaude_proxy|proxy|unproxy)/)
347
455
  in_function = true
348
456
  false
349
457
  elsif in_function && line.strip == "end"
@@ -356,19 +464,25 @@ module EasyAI
356
464
  end
357
465
  end
358
466
  else
359
- # Bash/zsh 代理配置清理 - 移除直接的环境变量设置和代理相关的别名/函数
467
+ # Bash/zsh 代理配置清理
360
468
  lines.reject! { |line|
361
- line.match(/^export\s+(HTTP_PROXY|HTTPS_PROXY|http_proxy|https_proxy)=/) ||
362
- line.match(/^(HTTP_PROXY|HTTPS_PROXY|http_proxy|https_proxy)=/) ||
363
- line.match(/^alias\s+(claude_proxy|unclaude_proxy)=/) ||
364
- line.match(/^(claude_proxy|unclaude_proxy)\(\)/)
469
+ # 清理环境变量设置
470
+ line.match(/^export\s+(HTTP_PROXY|HTTPS_PROXY|http_proxy|https_proxy|ALL_PROXY|all_proxy|NO_PROXY|no_proxy)=/) ||
471
+ line.match(/^(HTTP_PROXY|HTTPS_PROXY|http_proxy|https_proxy|ALL_PROXY|all_proxy|NO_PROXY|no_proxy)=/) ||
472
+ # 清理别名
473
+ line.match(/^alias\s+(claude_proxy|unclaude_proxy|proxy|unproxy)=/) ||
474
+ # 清理函数
475
+ line.match(/^(claude_proxy|unclaude_proxy|proxy|unproxy)\(\)/) ||
476
+ # 清理函数定义的另一种格式
477
+ line.match(/^function\s+(claude_proxy|unclaude_proxy|proxy|unproxy)/)
365
478
  }
366
-
479
+
367
480
  # 移除函数块
368
481
  in_function = false
369
482
  function_depth = 0
370
- lines.select do |line|
371
- if line.match(/^(claude_proxy|unclaude_proxy)\(\)/)
483
+ lines = lines.select do |line|
484
+ if line.match(/^(claude_proxy|unclaude_proxy|proxy|unproxy)\(\)/) ||
485
+ line.match(/^function\s+(claude_proxy|unclaude_proxy|proxy|unproxy)/)
372
486
  in_function = true
373
487
  function_depth = 0
374
488
  false
@@ -376,16 +490,27 @@ module EasyAI
376
490
  function_depth += 1 if line.include?("{")
377
491
  if line.include?("}")
378
492
  function_depth -= 1
379
- in_function = false if function_depth <= 0
493
+ if function_depth <= 0
494
+ in_function = false
495
+ false
496
+ else
497
+ false
498
+ end
499
+ else
500
+ false
380
501
  end
381
- false
382
502
  else
383
503
  true
384
504
  end
385
505
  end
386
506
  end
507
+
508
+ lines
387
509
  end
388
-
510
+
511
+ # remove_proxy_config 方法已被 remove_all_proxy_config 替代
512
+ # remove_all_proxy_config 提供更全面的清理功能,包括 ALL_PROXY 和 NO_PROXY
513
+
389
514
  def self.add_proxy_aliases(lines, shell_file, http_proxy, https_proxy)
390
515
  if shell_file.include?("config.fish")
391
516
  # Fish shell - 只添加函数,不直接设置环境变量
@@ -0,0 +1,98 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'jpsclient'
4
+ require 'fileutils'
5
+ require 'json'
6
+ require_relative '../config/easyai_config'
7
+
8
+ module EasyAI
9
+ module Auth
10
+ class JPSLoginHelper
11
+ attr_reader :username
12
+
13
+ def initialize(options = {})
14
+ @verbose = options[:verbose] || false
15
+
16
+ # 如果传入了配置文件路径,直接使用
17
+ if options[:config_file]
18
+ @config_file_path = options[:config_file]
19
+ else
20
+ # 通过 EasyAIConfig 获取 jpsclient 配置文件路径
21
+ # 首先确保配置仓库已初始化
22
+ EasyAIConfig.initialize(verbose: @verbose)
23
+
24
+ # 获取 jpsclient 配置文件路径(会自动解密到临时目录)
25
+ @config_file_path = EasyAIConfig.get_config_path('jps_client_config', verbose: @verbose)
26
+ end
27
+
28
+ @client = nil
29
+ @username = nil
30
+ end
31
+
32
+ # 主登录入口 - 兼容原 JPSLogin 接口
33
+ def login
34
+ begin
35
+ # 检查配置文件路径
36
+ if @config_file_path.nil?
37
+ puts "✗ 未找到 jpsclient 配置文件"
38
+ puts " 请确保配置仓库已下载并解密"
39
+ return false
40
+ end
41
+
42
+ # 检查配置文件是否存在
43
+ unless File.exist?(@config_file_path)
44
+ puts "✗ 配置文件不存在: #{@config_file_path}"
45
+ puts " 请先从配置仓库下载并解密配置文件"
46
+ return false
47
+ end
48
+
49
+ @client ||= JPSClient::Client.new(config_file: @config_file_path)
50
+
51
+ # 执行登录
52
+ result = @client.do_login(force_login: false)
53
+
54
+ if result == :user_cancelled
55
+ return :user_cancelled
56
+ end
57
+
58
+ if result
59
+ # 登录成功,获取用户名
60
+ @username = get_username_from_client
61
+ return true
62
+ end
63
+
64
+ return false
65
+ rescue => e
66
+ puts "✗ JPS 登录失败: #{e.message}" if @verbose
67
+ return false
68
+ end
69
+ end
70
+
71
+ # 获取用户名 - 兼容原 JPSLogin 接口
72
+ def get_username
73
+ return @username if @username
74
+
75
+ # 如果客户端存在且已登录,尝试获取用户名
76
+ if @client
77
+ @username = get_username_from_client
78
+ end
79
+
80
+ @username
81
+ end
82
+
83
+ private
84
+
85
+ # 从客户端获取用户名
86
+ def get_username_from_client
87
+ if @client && @client.token
88
+ # 从 token 中获取用户名
89
+ token_info = @client.token
90
+ if token_info.is_a?(Hash) && token_info['username']
91
+ return token_info['username']
92
+ end
93
+ end
94
+ nil
95
+ end
96
+ end
97
+ end
98
+ end