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.
- checksums.yaml +4 -4
- data/CLAUDE.md +87 -220
- data/easyai.gemspec +1 -0
- data/lib/easyai/auth/authclaude.rb +260 -135
- data/lib/easyai/auth/jpsloginhelper.rb +98 -0
- data/lib/easyai/base/cross_platform.rb +149 -0
- data/lib/easyai/base/file_crypto.rb +50 -9
- data/lib/easyai/base/system_keychain.rb +52 -18
- data/lib/easyai/command/claude.rb +76 -51
- data/lib/easyai/command/clean.rb +56 -20
- data/lib/easyai/command/gemini.rb +2 -6
- data/lib/easyai/command/gpt.rb +17 -21
- data/lib/easyai/command/setup.rb +494 -0
- data/lib/easyai/config/config.rb +284 -595
- data/lib/easyai/config/easyai_config.rb +258 -0
- data/lib/easyai/version.rb +1 -1
- data/lib/easyai.rb +32 -3
- metadata +27 -4
- data/lib/easyai/auth/jpslogin.rb +0 -655
@@ -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
|
-
|
14
|
-
|
15
|
-
env_token_exists = config["env"] && config["env"]["
|
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"][
|
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
|
41
|
+
clean_keychain if macos?
|
33
42
|
else
|
34
|
-
|
35
|
-
|
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"][
|
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
|
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",
|
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",
|
203
|
+
"-a", current_user,
|
99
204
|
"-s", service_name,
|
100
|
-
"-l",
|
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(
|
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
|
-
|
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?(
|
152
|
-
user_config = JSON.parse(File.read(
|
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(
|
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 =
|
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
|
-
|
227
|
-
|
228
|
-
|
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
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
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
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
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
|
-
|
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(
|
310
|
-
/^set\s+-x\s+#{Regexp.escape(
|
311
|
-
/^#set\s+-gx\s+#{Regexp.escape(
|
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(
|
316
|
-
/^#{Regexp.escape(
|
317
|
-
/^#export\s+#{Regexp.escape(
|
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 #{
|
431
|
+
"set -gx #{TOKEN_KEY} \"#{token}\""
|
327
432
|
else
|
328
|
-
"export #{
|
433
|
+
"export #{TOKEN_KEY}=\"#{token}\""
|
329
434
|
end
|
330
435
|
end
|
331
436
|
|
332
|
-
def self.
|
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
|
-
|
337
|
-
line.match(/^
|
338
|
-
line.match(/^
|
339
|
-
|
340
|
-
line.match(/^
|
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
|
-
|
362
|
-
line.match(/^(HTTP_PROXY|HTTPS_PROXY|http_proxy|https_proxy)=/) ||
|
363
|
-
line.match(/^
|
364
|
-
|
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
|
-
|
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
|