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
@@ -0,0 +1,149 @@
|
|
1
|
+
module EasyAI
|
2
|
+
module CrossPlatform
|
3
|
+
# 跨平台检测命令是否存在
|
4
|
+
def self.command_exists?(command)
|
5
|
+
if Gem.win_platform?
|
6
|
+
system("where #{command} >nul 2>&1")
|
7
|
+
else
|
8
|
+
system("which #{command} > /dev/null 2>&1")
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
# 跨平台执行命令,临时清除环境变量
|
13
|
+
def self.run_without_env(cmd, env_vars = [])
|
14
|
+
if env_vars.empty?
|
15
|
+
return system(cmd)
|
16
|
+
end
|
17
|
+
|
18
|
+
if Gem.win_platform?
|
19
|
+
# Windows: 保存并清除环境变量
|
20
|
+
old_env = {}
|
21
|
+
env_vars.each do |key|
|
22
|
+
old_env[key] = ENV[key]
|
23
|
+
ENV.delete(key)
|
24
|
+
end
|
25
|
+
|
26
|
+
begin
|
27
|
+
result = system(cmd)
|
28
|
+
ensure
|
29
|
+
# 恢复环境变量
|
30
|
+
old_env.each { |k, v| ENV[k] = v if v }
|
31
|
+
end
|
32
|
+
|
33
|
+
result
|
34
|
+
else
|
35
|
+
# Unix/Linux/macOS: 使用 env -u
|
36
|
+
env_unset = env_vars.map { |v| "-u #{v}" }.join(' ')
|
37
|
+
system("env #{env_unset} #{cmd}")
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# 跨平台执行命令并获取输出,临时清除环境变量
|
42
|
+
def self.capture_without_env(cmd, env_vars = [])
|
43
|
+
if env_vars.empty?
|
44
|
+
return `#{cmd}`
|
45
|
+
end
|
46
|
+
|
47
|
+
if Gem.win_platform?
|
48
|
+
# Windows: 保存并清除环境变量
|
49
|
+
old_env = {}
|
50
|
+
env_vars.each do |key|
|
51
|
+
old_env[key] = ENV[key]
|
52
|
+
ENV.delete(key)
|
53
|
+
end
|
54
|
+
|
55
|
+
begin
|
56
|
+
output = `#{cmd}`
|
57
|
+
ensure
|
58
|
+
# 恢复环境变量
|
59
|
+
old_env.each { |k, v| ENV[k] = v if v }
|
60
|
+
end
|
61
|
+
|
62
|
+
output
|
63
|
+
else
|
64
|
+
# Unix/Linux/macOS: 使用 env -u
|
65
|
+
env_unset = env_vars.map { |v| "-u #{v}" }.join(' ')
|
66
|
+
`env #{env_unset} #{cmd}`
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# 获取空设备路径
|
71
|
+
def self.null_device
|
72
|
+
Gem.win_platform? ? 'nul' : '/dev/null'
|
73
|
+
end
|
74
|
+
|
75
|
+
# 跨平台设置文件权限
|
76
|
+
def self.set_file_permissions(file_path, mode = 0600)
|
77
|
+
return unless File.exist?(file_path)
|
78
|
+
|
79
|
+
if !Gem.win_platform?
|
80
|
+
begin
|
81
|
+
File.chmod(mode, file_path)
|
82
|
+
rescue => e
|
83
|
+
# 忽略权限设置错误,某些文件系统可能不支持
|
84
|
+
end
|
85
|
+
end
|
86
|
+
# Windows 使用默认的文件权限(通常只有当前用户可访问)
|
87
|
+
end
|
88
|
+
|
89
|
+
# 获取 Shell 配置文件路径
|
90
|
+
def self.shell_config_files
|
91
|
+
files = []
|
92
|
+
|
93
|
+
if Gem.win_platform?
|
94
|
+
# Windows PowerShell 配置
|
95
|
+
powershell_profile = File.expand_path("~/Documents/WindowsPowerShell/Microsoft.PowerShell_profile.ps1")
|
96
|
+
powershell_core_profile = File.expand_path("~/Documents/PowerShell/Microsoft.PowerShell_profile.ps1")
|
97
|
+
|
98
|
+
files << powershell_core_profile if File.exist?(File.dirname(powershell_core_profile))
|
99
|
+
files << powershell_profile if File.exist?(File.dirname(powershell_profile))
|
100
|
+
else
|
101
|
+
# Unix/Linux/macOS Shell 配置
|
102
|
+
current_shell = ENV['SHELL']&.split('/')&.last || 'bash'
|
103
|
+
|
104
|
+
case current_shell
|
105
|
+
when 'zsh'
|
106
|
+
files << File.expand_path("~/.zshrc")
|
107
|
+
when 'bash'
|
108
|
+
bash_profile = File.expand_path("~/.bash_profile")
|
109
|
+
bashrc = File.expand_path("~/.bashrc")
|
110
|
+
files << (File.exist?(bash_profile) ? bash_profile : bashrc)
|
111
|
+
when 'fish'
|
112
|
+
fish_config = File.expand_path("~/.config/fish/config.fish")
|
113
|
+
files << fish_config
|
114
|
+
else
|
115
|
+
files << File.expand_path("~/.profile")
|
116
|
+
end
|
117
|
+
|
118
|
+
# 添加 ~/.profile 作为备份
|
119
|
+
profile_file = File.expand_path("~/.profile")
|
120
|
+
files << profile_file unless files.include?(profile_file)
|
121
|
+
end
|
122
|
+
|
123
|
+
files
|
124
|
+
end
|
125
|
+
|
126
|
+
# 检测操作系统平台
|
127
|
+
def self.platform
|
128
|
+
if Gem.win_platform?
|
129
|
+
:windows
|
130
|
+
elsif RUBY_PLATFORM.include?('darwin')
|
131
|
+
:macos
|
132
|
+
elsif RUBY_PLATFORM.include?('linux')
|
133
|
+
:linux
|
134
|
+
else
|
135
|
+
:unknown
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
# 平台特定的命令格式化
|
140
|
+
def self.format_command(cmd)
|
141
|
+
if Gem.win_platform?
|
142
|
+
# Windows 特殊处理
|
143
|
+
cmd.gsub('2>/dev/null', '2>nul').gsub('>/dev/null', '>nul')
|
144
|
+
else
|
145
|
+
cmd
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
@@ -3,6 +3,7 @@ require 'base64'
|
|
3
3
|
require 'digest'
|
4
4
|
require 'io/console'
|
5
5
|
require 'fileutils'
|
6
|
+
require 'json'
|
6
7
|
|
7
8
|
module EasyAI
|
8
9
|
module Base
|
@@ -35,22 +36,27 @@ module EasyAI
|
|
35
36
|
def self.encrypt_file(file_path, password, output_path = nil)
|
36
37
|
raise "文件不存在: #{file_path}" unless File.exist?(file_path)
|
37
38
|
raise "指定路径不是文件: #{file_path}" unless File.file?(file_path)
|
38
|
-
|
39
|
+
|
40
|
+
# 如果是 JSON 文件,验证其有效性
|
41
|
+
if file_path.downcase.end_with?('.json')
|
42
|
+
validate_json_file(file_path)
|
43
|
+
end
|
44
|
+
|
39
45
|
# 如果没有指定输出路径,使用原文件路径加 .encrypted 后缀
|
40
46
|
output_path ||= "#{file_path}.encrypted"
|
41
|
-
|
47
|
+
|
42
48
|
# 生成密钥
|
43
49
|
key = generate_key(password)
|
44
|
-
|
50
|
+
|
45
51
|
# 读取文件内容
|
46
52
|
file_content = File.read(file_path)
|
47
|
-
|
53
|
+
|
48
54
|
# 加密内容
|
49
55
|
encrypted_content = aes_128_ecb_encrypt(key, file_content)
|
50
|
-
|
56
|
+
|
51
57
|
# 写入加密文件
|
52
58
|
File.write(output_path, encrypted_content)
|
53
|
-
|
59
|
+
|
54
60
|
output_path
|
55
61
|
rescue => e
|
56
62
|
raise "加密文件失败: #{e.message}"
|
@@ -82,6 +88,10 @@ module EasyAI
|
|
82
88
|
FileUtils.mkdir_p(output_file_dir) unless File.exist?(output_file_dir)
|
83
89
|
|
84
90
|
begin
|
91
|
+
# 如果是 JSON 文件,先验证
|
92
|
+
if file_path.downcase.end_with?('.json')
|
93
|
+
validate_json_file(file_path)
|
94
|
+
end
|
85
95
|
encrypt_file(file_path, password, output_file_path)
|
86
96
|
encrypted_files << output_file_path
|
87
97
|
rescue => e
|
@@ -180,6 +190,37 @@ module EasyAI
|
|
180
190
|
raise "解密目录失败: #{e.message}"
|
181
191
|
end
|
182
192
|
|
193
|
+
# 验证 JSON 文件的有效性
|
194
|
+
def self.validate_json_file(file_path)
|
195
|
+
begin
|
196
|
+
content = File.read(file_path)
|
197
|
+
# 尝试解析 JSON
|
198
|
+
JSON.parse(content)
|
199
|
+
rescue JSON::ParserError => e
|
200
|
+
# 提取错误位置信息
|
201
|
+
error_msg = e.message
|
202
|
+
puts "\n⚠️ 警告: JSON 文件格式无效"
|
203
|
+
puts " 文件: #{File.basename(file_path)}"
|
204
|
+
puts " 路径: #{file_path}"
|
205
|
+
puts " 错误: #{error_msg}"
|
206
|
+
puts "\n是否继续加密这个无效的 JSON 文件?(y/n)"
|
207
|
+
print "> "
|
208
|
+
|
209
|
+
begin
|
210
|
+
response = STDIN.gets&.chomp&.downcase
|
211
|
+
unless response == 'y' || response == 'yes'
|
212
|
+
raise "用户取消了对无效 JSON 文件的加密"
|
213
|
+
end
|
214
|
+
puts "继续加密文件..."
|
215
|
+
rescue Interrupt
|
216
|
+
raise "用户中断操作"
|
217
|
+
end
|
218
|
+
rescue => e
|
219
|
+
# 文件读取错误等其他问题
|
220
|
+
raise "验证 JSON 文件失败: #{e.message}"
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
183
224
|
# 获取用户密码输入
|
184
225
|
def self.get_password(prompt = "请输入密码: ")
|
185
226
|
# 如果设置了环境变量,直接使用(主要用于测试和脚本自动化)
|
@@ -188,17 +229,17 @@ module EasyAI
|
|
188
229
|
puts "使用环境变量密码"
|
189
230
|
return password
|
190
231
|
end
|
191
|
-
|
232
|
+
|
192
233
|
begin
|
193
234
|
print prompt
|
194
235
|
password = STDIN.noecho(&:gets)&.chomp
|
195
236
|
puts # 换行
|
196
|
-
|
237
|
+
|
197
238
|
if password.nil? || password.empty?
|
198
239
|
puts "❌ 密码不能为空"
|
199
240
|
return nil
|
200
241
|
end
|
201
|
-
|
242
|
+
|
202
243
|
password
|
203
244
|
rescue Interrupt
|
204
245
|
puts "\n\n用户取消输入"
|
@@ -69,51 +69,75 @@ module EasyAI
|
|
69
69
|
|
70
70
|
# macOS Keychain 密码存储
|
71
71
|
def self.store_password_macos(password)
|
72
|
+
# 检查 security 命令是否可用
|
73
|
+
unless system('which security > /dev/null 2>&1')
|
74
|
+
puts "⚠ macOS Keychain 不可用,将使用文件存储"
|
75
|
+
return store_password_linux(password)
|
76
|
+
end
|
77
|
+
|
72
78
|
# 首先尝试更新现有条目
|
73
79
|
update_cmd = "security add-generic-password -a '#{ACCOUNT_NAME}' -s '#{SERVICE_NAME}' -w '#{password}' -U 2>/dev/null"
|
74
80
|
success = system(update_cmd)
|
75
|
-
|
81
|
+
|
76
82
|
unless success
|
77
83
|
# 如果更新失败,尝试添加新条目
|
78
84
|
add_cmd = "security add-generic-password -a '#{ACCOUNT_NAME}' -s '#{SERVICE_NAME}' -w '#{password}' 2>/dev/null"
|
79
85
|
success = system(add_cmd)
|
80
86
|
end
|
81
|
-
|
87
|
+
|
82
88
|
if success
|
83
89
|
puts "✓ 密码已安全存储到 macOS Keychain"
|
84
90
|
else
|
85
|
-
puts "⚠ 无法存储密码到 macOS Keychain"
|
91
|
+
puts "⚠ 无法存储密码到 macOS Keychain,尝试使用文件存储"
|
92
|
+
return store_password_linux(password)
|
86
93
|
end
|
87
|
-
|
94
|
+
|
88
95
|
success
|
89
96
|
rescue => e
|
90
|
-
puts "⚠ Keychain 存储出错: #{e.message}"
|
91
|
-
|
97
|
+
puts "⚠ Keychain 存储出错: #{e.message},将使用文件存储"
|
98
|
+
store_password_linux(password)
|
92
99
|
end
|
93
100
|
|
94
101
|
# macOS Keychain 密码获取
|
95
102
|
def self.get_password_macos
|
103
|
+
# 检查 security 命令是否可用
|
104
|
+
unless system('which security > /dev/null 2>&1')
|
105
|
+
return get_password_linux
|
106
|
+
end
|
107
|
+
|
96
108
|
cmd = "security find-generic-password -a '#{ACCOUNT_NAME}' -s '#{SERVICE_NAME}' -w 2>/dev/null"
|
97
109
|
password = `#{cmd}`.chomp
|
98
|
-
|
99
|
-
|
110
|
+
|
111
|
+
if password.empty? || $?.exitstatus != 0
|
112
|
+
# 如果 Keychain 中没有,尝试从文件读取
|
113
|
+
return get_password_linux
|
114
|
+
end
|
115
|
+
|
100
116
|
password
|
101
117
|
rescue => e
|
102
|
-
|
118
|
+
get_password_linux
|
103
119
|
end
|
104
120
|
|
105
121
|
# macOS Keychain 密码删除
|
106
122
|
def self.delete_password_macos
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
if
|
111
|
-
|
123
|
+
success = false
|
124
|
+
|
125
|
+
# 尝试从 Keychain 删除
|
126
|
+
if system('which security > /dev/null 2>&1')
|
127
|
+
cmd = "security delete-generic-password -a '#{ACCOUNT_NAME}' -s '#{SERVICE_NAME}' 2>/dev/null"
|
128
|
+
success = system(cmd)
|
129
|
+
|
130
|
+
if success
|
131
|
+
puts "✓ 已从 macOS Keychain 删除存储的密码"
|
132
|
+
end
|
112
133
|
end
|
113
|
-
|
134
|
+
|
135
|
+
# 同时尝试删除文件存储
|
136
|
+
delete_password_linux
|
137
|
+
|
114
138
|
success
|
115
139
|
rescue => e
|
116
|
-
|
140
|
+
delete_password_linux
|
117
141
|
end
|
118
142
|
|
119
143
|
# Windows 凭据管理器密码存储
|
@@ -134,7 +158,10 @@ module EasyAI
|
|
134
158
|
require 'base64'
|
135
159
|
encoded_password = Base64.strict_encode64(password)
|
136
160
|
File.write(keyring_file, encoded_password)
|
137
|
-
|
161
|
+
# Windows 不支持 Unix 权限模型,但文件默认仅当前用户可访问
|
162
|
+
unless Gem.win_platform?
|
163
|
+
File.chmod(0600, keyring_file) rescue nil
|
164
|
+
end
|
138
165
|
rescue => e
|
139
166
|
# 忽略文件写入错误
|
140
167
|
end
|
@@ -215,7 +242,14 @@ module EasyAI
|
|
215
242
|
require 'base64'
|
216
243
|
encoded_password = Base64.strict_encode64(password)
|
217
244
|
File.write(keyring_file, encoded_password)
|
218
|
-
|
245
|
+
# 设置文件权限(仅在支持的平台上)
|
246
|
+
if !Gem.win_platform?
|
247
|
+
begin
|
248
|
+
File.chmod(0600, keyring_file)
|
249
|
+
rescue => e
|
250
|
+
# 忽略权限设置错误,某些文件系统可能不支持
|
251
|
+
end
|
252
|
+
end
|
219
253
|
|
220
254
|
puts "✓ 密码已存储到本地加密文件"
|
221
255
|
true
|
@@ -10,9 +10,13 @@ module EasyAI
|
|
10
10
|
self.summary = '运行 Claude CLI'
|
11
11
|
self.description = <<-DESC
|
12
12
|
启动 Anthropic Claude CLI 工具。
|
13
|
-
|
13
|
+
|
14
14
|
主要功能:
|
15
15
|
|
16
|
+
* 支持多种认证平台 (Claude/Kimi/Deepseek)
|
17
|
+
|
18
|
+
* 智能选择最优认证方式
|
19
|
+
|
16
20
|
* 支持远程配置下载
|
17
21
|
|
18
22
|
* 自动配置环境变量
|
@@ -21,28 +25,32 @@ module EasyAI
|
|
21
25
|
|
22
26
|
使用示例:
|
23
27
|
|
24
|
-
$ easyai claude
|
28
|
+
$ easyai claude # 自动选择认证平台
|
25
29
|
|
26
|
-
$ easyai claude
|
30
|
+
$ easyai claude --platform # 交互式选择认证平台
|
27
31
|
|
28
|
-
$ easyai claude
|
32
|
+
$ easyai claude ./config.json # 使用本地配置文件
|
29
33
|
|
30
|
-
$ easyai claude --
|
34
|
+
$ easyai claude --verbose # 显示详细信息
|
35
|
+
|
36
|
+
$ easyai claude --no-keychain # 禁用密码存储
|
31
37
|
DESC
|
32
38
|
|
33
39
|
def self.options
|
34
40
|
[
|
41
|
+
['--platform', '选择认证平台(交互式选择)'],
|
35
42
|
['--no-keychain', '禁用自动密码存储'],
|
36
43
|
['--verbose', '显示详细信息']
|
37
44
|
].concat(super)
|
38
45
|
end
|
39
46
|
|
40
47
|
def initialize(argv)
|
48
|
+
@platform_flag = argv.flag?('platform')
|
41
49
|
@no_keychain = argv.flag?('no-keychain')
|
42
50
|
@verbose_mode = argv.flag?('verbose')
|
43
|
-
|
51
|
+
|
44
52
|
super
|
45
|
-
|
53
|
+
|
46
54
|
# 获取剩余参数
|
47
55
|
remaining_args = @argv.remainder!
|
48
56
|
|
@@ -77,7 +85,8 @@ module EasyAI
|
|
77
85
|
options = {
|
78
86
|
no_keychain: @no_keychain,
|
79
87
|
verbose: @verbose_mode,
|
80
|
-
tool_type: "claude" # 指定使用 claude 组的配置
|
88
|
+
tool_type: "claude", # 指定使用 claude 组的配置
|
89
|
+
platform_flag: @platform_flag # 传递 platform 标志
|
81
90
|
}
|
82
91
|
remote_config = ConfigManager.download_user_config(nil, options)
|
83
92
|
|
@@ -158,12 +167,8 @@ module EasyAI
|
|
158
167
|
end
|
159
168
|
|
160
169
|
def claude_available?
|
161
|
-
#
|
162
|
-
|
163
|
-
system('where claude >nul 2>&1')
|
164
|
-
else
|
165
|
-
system('which claude > /dev/null 2>&1')
|
166
|
-
end
|
170
|
+
# 使用统一的跨平台命令检测
|
171
|
+
EasyAI::CrossPlatform.command_exists?('claude')
|
167
172
|
end
|
168
173
|
|
169
174
|
def load_local_config(config_path)
|
@@ -193,19 +198,34 @@ module EasyAI
|
|
193
198
|
puts "\n📋 环境配置:" if @verbose_mode
|
194
199
|
puts "─" * 60 if @verbose_mode
|
195
200
|
|
196
|
-
#
|
197
|
-
if config && config['env']
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
201
|
+
# 从配置中提取所有环境变量 - 动态处理 env 中的所有变量
|
202
|
+
if config && config['env']
|
203
|
+
config['env'].each do |key, value|
|
204
|
+
# 将所有环境变量都添加到 env 中
|
205
|
+
env[key] = value.to_s
|
206
|
+
|
207
|
+
# 根据变量名称智能显示
|
208
|
+
if key.include?('TOKEN') || key.include?('KEY') || key.include?('SECRET') || key.include?('PASSWORD')
|
209
|
+
# 敏感信息:显示部分内容
|
210
|
+
value_preview = value.to_s.length > 15 ? "#{value.to_s[0..15]}..." : value.to_s
|
211
|
+
print_status("🔑 #{key}", value_preview)
|
212
|
+
puts " 长度: #{value.to_s.length} 字符" if @verbose_mode
|
213
|
+
elsif key.include?('URL') || key.include?('ENDPOINT')
|
214
|
+
# URL类:显示完整内容
|
215
|
+
print_status("🌐 #{key}", value.to_s)
|
216
|
+
elsif key.include?('MODEL')
|
217
|
+
# 模型配置:显示完整内容
|
218
|
+
print_status("🤖 #{key}", value.to_s)
|
219
|
+
elsif key.include?('TIMEOUT')
|
220
|
+
# 超时配置:显示完整内容
|
221
|
+
print_status("⏱️ #{key}", value.to_s)
|
222
|
+
elsif key.include?('PROXY')
|
223
|
+
# 代理配置:显示部分内容
|
224
|
+
print_status("🔄 #{key}", value.to_s)
|
225
|
+
else
|
226
|
+
# 其他变量:根据 verbose 模式决定是否显示
|
227
|
+
print_status("📝 #{key}", value.to_s) if @verbose_mode
|
228
|
+
end
|
209
229
|
end
|
210
230
|
end
|
211
231
|
|
@@ -227,27 +247,27 @@ module EasyAI
|
|
227
247
|
end
|
228
248
|
|
229
249
|
if proxy_urls.any?
|
230
|
-
#
|
250
|
+
# 统一显示方式:简化代理显示,隐藏密码和部分IP
|
231
251
|
simplified_proxies = proxy_urls.uniq.map do |url|
|
232
252
|
uri = URI(url) rescue nil
|
233
|
-
if uri
|
234
|
-
|
253
|
+
if uri
|
254
|
+
# 隐藏IP地址的前两段,显示后两段
|
255
|
+
masked_host = uri.host
|
256
|
+
if uri.host =~ /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/
|
257
|
+
masked_host = "*.*.#{$3}.#{$4}"
|
258
|
+
end
|
259
|
+
|
260
|
+
if uri.password
|
261
|
+
"#{uri.scheme}://***@#{masked_host}:#{uri.port}"
|
262
|
+
else
|
263
|
+
"#{uri.scheme}://#{masked_host}:#{uri.port}"
|
264
|
+
end
|
235
265
|
else
|
236
266
|
url
|
237
267
|
end
|
238
268
|
end
|
239
269
|
print_status("🌐 代理已配置", simplified_proxies.join(', '))
|
240
270
|
|
241
|
-
if @verbose_mode
|
242
|
-
# verbose 模式:显示更多状态信息(但不显示敏感内容)
|
243
|
-
puts " 代理类型: #{proxy_urls.uniq.count == 1 ? '统一代理' : '分离代理'}"
|
244
|
-
proxy_urls.uniq.each do |url|
|
245
|
-
uri = URI(url) rescue nil
|
246
|
-
if uri
|
247
|
-
puts " 代理服务器: #{uri.host}:#{uri.port}"
|
248
|
-
end
|
249
|
-
end
|
250
|
-
end
|
251
271
|
end
|
252
272
|
end
|
253
273
|
|
@@ -259,21 +279,26 @@ module EasyAI
|
|
259
279
|
|
260
280
|
|
261
281
|
def export_environment_variables(env)
|
262
|
-
#
|
263
|
-
|
264
|
-
ENV['CLAUDE_CODE_OAUTH_TOKEN'] = env['CLAUDE_CODE_OAUTH_TOKEN']
|
265
|
-
print_success("令牌已设置到环境变量") if @verbose_mode
|
266
|
-
end
|
267
|
-
|
268
|
-
# 导出代理相关环境变量
|
269
|
-
proxy_vars = ['HTTP_PROXY', 'HTTPS_PROXY', 'http_proxy', 'https_proxy']
|
282
|
+
# 导出所有环境变量(仅在内存中,不写入文件)
|
283
|
+
env_exported = false
|
270
284
|
proxy_exported = false
|
271
|
-
|
272
|
-
|
273
|
-
|
285
|
+
|
286
|
+
env.each do |key, value|
|
287
|
+
# 跳过 PATH 等系统关键变量,避免覆盖
|
288
|
+
next if ['PATH', 'HOME', 'USER', 'SHELL'].include?(key)
|
289
|
+
|
290
|
+
# 设置环境变量
|
291
|
+
ENV[key] = value
|
292
|
+
|
293
|
+
# 记录导出状态
|
294
|
+
if key.include?('PROXY')
|
274
295
|
proxy_exported = true
|
296
|
+
else
|
297
|
+
env_exported = true
|
275
298
|
end
|
276
299
|
end
|
300
|
+
|
301
|
+
print_success("环境变量已设置") if env_exported && @verbose_mode
|
277
302
|
print_success("代理已设置到环境变量") if proxy_exported && @verbose_mode
|
278
303
|
end
|
279
304
|
|