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,283 +0,0 @@
|
|
|
1
|
-
require 'rbconfig'
|
|
2
|
-
require 'fileutils'
|
|
3
|
-
require_relative 'system_info'
|
|
4
|
-
|
|
5
|
-
module EasyAI
|
|
6
|
-
module Base
|
|
7
|
-
module SystemKeychain
|
|
8
|
-
SERVICE_NAME = "easyai"
|
|
9
|
-
ACCOUNT_NAME = "config_password"
|
|
10
|
-
|
|
11
|
-
# 跨平台密码存储
|
|
12
|
-
def self.store_password(password)
|
|
13
|
-
case SystemInfo.platform
|
|
14
|
-
when :macos
|
|
15
|
-
store_password_macos(password)
|
|
16
|
-
when :windows
|
|
17
|
-
store_password_windows(password)
|
|
18
|
-
when :linux
|
|
19
|
-
store_password_linux(password)
|
|
20
|
-
else
|
|
21
|
-
puts "⚠ 当前平台不支持密码自动存储,将在会话中临时保存"
|
|
22
|
-
false
|
|
23
|
-
end
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
# 跨平台密码获取
|
|
27
|
-
def self.get_stored_password
|
|
28
|
-
case SystemInfo.platform
|
|
29
|
-
when :macos
|
|
30
|
-
get_password_macos
|
|
31
|
-
when :windows
|
|
32
|
-
get_password_windows
|
|
33
|
-
when :linux
|
|
34
|
-
get_password_linux
|
|
35
|
-
else
|
|
36
|
-
nil
|
|
37
|
-
end
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
# 跨平台密码删除
|
|
41
|
-
def self.delete_stored_password
|
|
42
|
-
case SystemInfo.platform
|
|
43
|
-
when :macos
|
|
44
|
-
delete_password_macos
|
|
45
|
-
when :windows
|
|
46
|
-
delete_password_windows
|
|
47
|
-
when :linux
|
|
48
|
-
delete_password_linux
|
|
49
|
-
else
|
|
50
|
-
true
|
|
51
|
-
end
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
# 检测操作系统平台(已迁移到 SystemInfo)
|
|
55
|
-
def self.detect_platform
|
|
56
|
-
SystemInfo.platform
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
private
|
|
60
|
-
|
|
61
|
-
# macOS Keychain 密码存储
|
|
62
|
-
def self.store_password_macos(password)
|
|
63
|
-
# 检查 security 命令是否可用
|
|
64
|
-
unless system('which security > /dev/null 2>&1')
|
|
65
|
-
puts "⚠ macOS Keychain 不可用,将使用文件存储"
|
|
66
|
-
return store_password_linux(password)
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
# 首先尝试更新现有条目
|
|
70
|
-
update_cmd = "security add-generic-password -a '#{ACCOUNT_NAME}' -s '#{SERVICE_NAME}' -w '#{password}' -U 2>/dev/null"
|
|
71
|
-
success = system(update_cmd)
|
|
72
|
-
|
|
73
|
-
unless success
|
|
74
|
-
# 如果更新失败,尝试添加新条目
|
|
75
|
-
add_cmd = "security add-generic-password -a '#{ACCOUNT_NAME}' -s '#{SERVICE_NAME}' -w '#{password}' 2>/dev/null"
|
|
76
|
-
success = system(add_cmd)
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
if success
|
|
80
|
-
puts "✓ 密码已安全存储到 macOS Keychain"
|
|
81
|
-
else
|
|
82
|
-
puts "⚠ 无法存储密码到 macOS Keychain,尝试使用文件存储"
|
|
83
|
-
return store_password_linux(password)
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
success
|
|
87
|
-
rescue => e
|
|
88
|
-
puts "⚠ Keychain 存储出错: #{e.message},将使用文件存储"
|
|
89
|
-
store_password_linux(password)
|
|
90
|
-
end
|
|
91
|
-
|
|
92
|
-
# macOS Keychain 密码获取
|
|
93
|
-
def self.get_password_macos
|
|
94
|
-
# 检查 security 命令是否可用
|
|
95
|
-
unless system('which security > /dev/null 2>&1')
|
|
96
|
-
return get_password_linux
|
|
97
|
-
end
|
|
98
|
-
|
|
99
|
-
cmd = "security find-generic-password -a '#{ACCOUNT_NAME}' -s '#{SERVICE_NAME}' -w 2>/dev/null"
|
|
100
|
-
password = `#{cmd}`.chomp
|
|
101
|
-
|
|
102
|
-
if password.empty? || $?.exitstatus != 0
|
|
103
|
-
# 如果 Keychain 中没有,尝试从文件读取
|
|
104
|
-
return get_password_linux
|
|
105
|
-
end
|
|
106
|
-
|
|
107
|
-
password
|
|
108
|
-
rescue => e
|
|
109
|
-
get_password_linux
|
|
110
|
-
end
|
|
111
|
-
|
|
112
|
-
# macOS Keychain 密码删除
|
|
113
|
-
def self.delete_password_macos
|
|
114
|
-
success = false
|
|
115
|
-
|
|
116
|
-
# 尝试从 Keychain 删除
|
|
117
|
-
if system('which security > /dev/null 2>&1')
|
|
118
|
-
cmd = "security delete-generic-password -a '#{ACCOUNT_NAME}' -s '#{SERVICE_NAME}' 2>/dev/null"
|
|
119
|
-
success = system(cmd)
|
|
120
|
-
|
|
121
|
-
if success
|
|
122
|
-
puts "✓ 已从 macOS Keychain 删除存储的密码"
|
|
123
|
-
end
|
|
124
|
-
end
|
|
125
|
-
|
|
126
|
-
# 同时尝试删除文件存储
|
|
127
|
-
delete_password_linux
|
|
128
|
-
|
|
129
|
-
success
|
|
130
|
-
rescue => e
|
|
131
|
-
delete_password_linux
|
|
132
|
-
end
|
|
133
|
-
|
|
134
|
-
# Windows 凭据管理器密码存储
|
|
135
|
-
def self.store_password_windows(password)
|
|
136
|
-
# 使用 cmdkey 命令存储密码
|
|
137
|
-
target_name = "#{SERVICE_NAME}_#{ACCOUNT_NAME}"
|
|
138
|
-
cmd = "cmdkey /add:#{target_name} /user:#{ACCOUNT_NAME} /pass:\"#{password}\""
|
|
139
|
-
|
|
140
|
-
success = system(cmd + " >nul 2>&1")
|
|
141
|
-
|
|
142
|
-
# 同时保存到文件作为备用
|
|
143
|
-
keyring_dir = File.expand_path('~/.easyai/.keyring')
|
|
144
|
-
FileUtils.mkdir_p(keyring_dir) unless Dir.exist?(keyring_dir)
|
|
145
|
-
|
|
146
|
-
keyring_file = File.join(keyring_dir, 'windows_credential')
|
|
147
|
-
|
|
148
|
-
begin
|
|
149
|
-
require 'base64'
|
|
150
|
-
encoded_password = Base64.strict_encode64(password)
|
|
151
|
-
File.write(keyring_file, encoded_password)
|
|
152
|
-
# Windows 不支持 Unix 权限模型,但文件默认仅当前用户可访问
|
|
153
|
-
unless Gem.win_platform?
|
|
154
|
-
File.chmod(0600, keyring_file) rescue nil
|
|
155
|
-
end
|
|
156
|
-
rescue => e
|
|
157
|
-
# 忽略文件写入错误
|
|
158
|
-
end
|
|
159
|
-
|
|
160
|
-
if success
|
|
161
|
-
puts "✓ 密码已安全存储到 Windows 凭据管理器"
|
|
162
|
-
else
|
|
163
|
-
puts "⚠ 无法存储密码到 Windows 凭据管理器"
|
|
164
|
-
end
|
|
165
|
-
|
|
166
|
-
success
|
|
167
|
-
rescue => e
|
|
168
|
-
puts "⚠ Windows 凭据存储出错: #{e.message}"
|
|
169
|
-
false
|
|
170
|
-
end
|
|
171
|
-
|
|
172
|
-
# Windows 凭据管理器密码获取
|
|
173
|
-
def self.get_password_windows
|
|
174
|
-
target_name = "#{SERVICE_NAME}_#{ACCOUNT_NAME}"
|
|
175
|
-
|
|
176
|
-
# 首先检查凭据是否存在
|
|
177
|
-
check_cmd = "cmdkey /list:#{target_name} 2>nul"
|
|
178
|
-
check_output = `#{check_cmd}`
|
|
179
|
-
return nil unless check_output.include?(target_name)
|
|
180
|
-
|
|
181
|
-
# 使用备用文件存储方式
|
|
182
|
-
# 因为 Windows 凭据管理器的密码获取需要特殊权限或 CredentialManager 模块
|
|
183
|
-
# 我们使用加密文件作为备用方案
|
|
184
|
-
keyring_file = File.expand_path('~/.easyai/.keyring/windows_credential')
|
|
185
|
-
|
|
186
|
-
if File.exist?(keyring_file)
|
|
187
|
-
begin
|
|
188
|
-
require 'base64'
|
|
189
|
-
encoded_password = File.read(keyring_file).chomp
|
|
190
|
-
Base64.strict_decode64(encoded_password)
|
|
191
|
-
rescue => e
|
|
192
|
-
nil
|
|
193
|
-
end
|
|
194
|
-
else
|
|
195
|
-
# 提示用户重新输入密码
|
|
196
|
-
nil
|
|
197
|
-
end
|
|
198
|
-
rescue => e
|
|
199
|
-
nil
|
|
200
|
-
end
|
|
201
|
-
|
|
202
|
-
# Windows 凭据管理器密码删除
|
|
203
|
-
def self.delete_password_windows
|
|
204
|
-
target_name = "#{SERVICE_NAME}_#{ACCOUNT_NAME}"
|
|
205
|
-
cmd = "cmdkey /delete:#{target_name}"
|
|
206
|
-
|
|
207
|
-
success = system(cmd + " >nul 2>&1")
|
|
208
|
-
|
|
209
|
-
# 同时删除备用文件
|
|
210
|
-
keyring_file = File.expand_path('~/.easyai/.keyring/windows_credential')
|
|
211
|
-
if File.exist?(keyring_file)
|
|
212
|
-
File.delete(keyring_file) rescue nil
|
|
213
|
-
end
|
|
214
|
-
|
|
215
|
-
if success
|
|
216
|
-
puts "✓ 已从 Windows 凭据管理器删除存储的密码"
|
|
217
|
-
end
|
|
218
|
-
|
|
219
|
-
success
|
|
220
|
-
rescue => e
|
|
221
|
-
false
|
|
222
|
-
end
|
|
223
|
-
|
|
224
|
-
# Linux 密码存储(使用文件加密存储作为备用方案)
|
|
225
|
-
def self.store_password_linux(password)
|
|
226
|
-
keyring_dir = File.expand_path('~/.easyai/.keyring')
|
|
227
|
-
FileUtils.mkdir_p(keyring_dir) unless Dir.exist?(keyring_dir)
|
|
228
|
-
|
|
229
|
-
keyring_file = File.join(keyring_dir, 'config_key')
|
|
230
|
-
|
|
231
|
-
begin
|
|
232
|
-
# 使用简单的 Base64 编码存储(在实际生产中应该使用更安全的方法)
|
|
233
|
-
require 'base64'
|
|
234
|
-
encoded_password = Base64.strict_encode64(password)
|
|
235
|
-
File.write(keyring_file, encoded_password)
|
|
236
|
-
# 设置文件权限(仅在支持的平台上)
|
|
237
|
-
if !Gem.win_platform?
|
|
238
|
-
begin
|
|
239
|
-
File.chmod(0600, keyring_file)
|
|
240
|
-
rescue => e
|
|
241
|
-
# 忽略权限设置错误,某些文件系统可能不支持
|
|
242
|
-
end
|
|
243
|
-
end
|
|
244
|
-
|
|
245
|
-
puts "✓ 密码已存储到本地加密文件"
|
|
246
|
-
true
|
|
247
|
-
rescue => e
|
|
248
|
-
puts "⚠ Linux 密码存储出错: #{e.message}"
|
|
249
|
-
false
|
|
250
|
-
end
|
|
251
|
-
end
|
|
252
|
-
|
|
253
|
-
# Linux 密码获取
|
|
254
|
-
def self.get_password_linux
|
|
255
|
-
keyring_file = File.expand_path('~/.easyai/.keyring/config_key')
|
|
256
|
-
return nil unless File.exist?(keyring_file)
|
|
257
|
-
|
|
258
|
-
begin
|
|
259
|
-
require 'base64'
|
|
260
|
-
encoded_password = File.read(keyring_file).chomp
|
|
261
|
-
Base64.strict_decode64(encoded_password)
|
|
262
|
-
rescue => e
|
|
263
|
-
nil
|
|
264
|
-
end
|
|
265
|
-
end
|
|
266
|
-
|
|
267
|
-
# Linux 密码删除
|
|
268
|
-
def self.delete_password_linux
|
|
269
|
-
keyring_file = File.expand_path('~/.easyai/.keyring/config_key')
|
|
270
|
-
|
|
271
|
-
if File.exist?(keyring_file)
|
|
272
|
-
File.delete(keyring_file)
|
|
273
|
-
puts "✓ 已删除本地存储的密码"
|
|
274
|
-
true
|
|
275
|
-
else
|
|
276
|
-
true
|
|
277
|
-
end
|
|
278
|
-
rescue => e
|
|
279
|
-
false
|
|
280
|
-
end
|
|
281
|
-
end
|
|
282
|
-
end
|
|
283
|
-
end
|
data/lib/easyai/command/gpt.rb
DELETED
|
@@ -1,259 +0,0 @@
|
|
|
1
|
-
require 'json'
|
|
2
|
-
require 'uri'
|
|
3
|
-
require 'colored2'
|
|
4
|
-
require_relative '../config/config'
|
|
5
|
-
require_relative '../base/system_info'
|
|
6
|
-
|
|
7
|
-
module EasyAI
|
|
8
|
-
class Command
|
|
9
|
-
class GPT < Command
|
|
10
|
-
self.summary = '运行 OpenAI CLI'
|
|
11
|
-
self.description = <<-DESC
|
|
12
|
-
启动 OpenAI GPT CLI 工具。
|
|
13
|
-
|
|
14
|
-
主要功能:
|
|
15
|
-
|
|
16
|
-
* 支持远程配置下载
|
|
17
|
-
|
|
18
|
-
* 自动配置 API 密钥
|
|
19
|
-
|
|
20
|
-
* 支持本地配置文件
|
|
21
|
-
|
|
22
|
-
* 透传所有参数
|
|
23
|
-
|
|
24
|
-
使用示例:
|
|
25
|
-
|
|
26
|
-
$ easyai gpt # 启动 OpenAI CLI
|
|
27
|
-
|
|
28
|
-
$ easyai gpt ./config.json # 使用本地配置文件
|
|
29
|
-
|
|
30
|
-
$ easyai gpt api chat.completions # 调用 Chat API
|
|
31
|
-
|
|
32
|
-
$ easyai gpt --help # 查看 OpenAI 帮助
|
|
33
|
-
|
|
34
|
-
$ easyai gpt --verbose # 显示详细信息
|
|
35
|
-
|
|
36
|
-
$ easyai gpt --no-keychain # 禁用密码存储
|
|
37
|
-
DESC
|
|
38
|
-
|
|
39
|
-
def self.options
|
|
40
|
-
[
|
|
41
|
-
['--no-keychain', '禁用自动密码存储'],
|
|
42
|
-
['--verbose', '显示详细信息']
|
|
43
|
-
].concat(super)
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
def initialize(argv)
|
|
47
|
-
@no_keychain = argv.flag?('no-keychain')
|
|
48
|
-
@verbose_mode = argv.flag?('verbose')
|
|
49
|
-
|
|
50
|
-
super
|
|
51
|
-
|
|
52
|
-
# 获取剩余参数
|
|
53
|
-
remaining_args = @argv.remainder!
|
|
54
|
-
|
|
55
|
-
# 检查第一个参数是否是配置文件
|
|
56
|
-
if remaining_args.first && File.exist?(remaining_args.first) && remaining_args.first.end_with?('.json')
|
|
57
|
-
@config_file = remaining_args.shift
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
# 剩余的参数传递给 openai
|
|
61
|
-
@gpt_args = remaining_args
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
def validate!
|
|
65
|
-
super
|
|
66
|
-
help! '未找到 OpenAI CLI。请安装:pip install openai' unless gpt_available?
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
def run
|
|
70
|
-
begin
|
|
71
|
-
# 首先尝试获取配置
|
|
72
|
-
remote_config = nil
|
|
73
|
-
|
|
74
|
-
if @config_file
|
|
75
|
-
# 使用指定的本地配置文件
|
|
76
|
-
print_status("📁 使用本地配置", File.basename(@config_file))
|
|
77
|
-
remote_config = load_local_config(@config_file)
|
|
78
|
-
print_success("配置加载成功") if remote_config
|
|
79
|
-
else
|
|
80
|
-
# 从远程下载配置,传递选项(指定工具类型为 gpt)
|
|
81
|
-
print_status("🔄 获取远程配置", "默认用户")
|
|
82
|
-
options = {
|
|
83
|
-
no_keychain: @no_keychain,
|
|
84
|
-
verbose: @verbose_mode,
|
|
85
|
-
tool_type: "gpt" # 指定使用 gpt 组的配置
|
|
86
|
-
}
|
|
87
|
-
remote_config = ConfigManager.download_user_config(nil, options)
|
|
88
|
-
|
|
89
|
-
# 处理用户取消授权的情况
|
|
90
|
-
if remote_config == :user_cancelled
|
|
91
|
-
print_error("用户取消了授权登录")
|
|
92
|
-
exit 0
|
|
93
|
-
end
|
|
94
|
-
|
|
95
|
-
print_success("配置加载成功") if remote_config
|
|
96
|
-
end
|
|
97
|
-
|
|
98
|
-
# 如果远程配置获取失败,回退到本地配置
|
|
99
|
-
if remote_config.nil?
|
|
100
|
-
print_warning("使用本地配置")
|
|
101
|
-
remote_config = load_local_yaml_config
|
|
102
|
-
|
|
103
|
-
# 如果本地配置也为空,提示用户先进行设置
|
|
104
|
-
if remote_config.empty?
|
|
105
|
-
print_error("未找到有效配置")
|
|
106
|
-
puts " 请先运行: #{'easyai --setup'.yellow}"
|
|
107
|
-
exit 1
|
|
108
|
-
end
|
|
109
|
-
end
|
|
110
|
-
|
|
111
|
-
# 构建环境变量
|
|
112
|
-
env = build_environment(remote_config)
|
|
113
|
-
|
|
114
|
-
# 运行 OpenAI CLI
|
|
115
|
-
print_status("🚀 启动 OpenAI", "openai #{@gpt_args.join(' ')}")
|
|
116
|
-
exec(env, 'openai', *@gpt_args)
|
|
117
|
-
|
|
118
|
-
rescue Interrupt
|
|
119
|
-
puts "\n已取消"
|
|
120
|
-
exit 0
|
|
121
|
-
rescue => e
|
|
122
|
-
print_error("运行失败: #{e.message}")
|
|
123
|
-
puts e.backtrace if @verbose_mode
|
|
124
|
-
exit 1
|
|
125
|
-
end
|
|
126
|
-
end
|
|
127
|
-
|
|
128
|
-
private
|
|
129
|
-
|
|
130
|
-
def gpt_available?
|
|
131
|
-
# 跨平台命令检测
|
|
132
|
-
Base::SystemInfo.which_command('openai')
|
|
133
|
-
end
|
|
134
|
-
|
|
135
|
-
def load_local_config(config_file)
|
|
136
|
-
begin
|
|
137
|
-
content = File.read(config_file)
|
|
138
|
-
JSON.parse(content)
|
|
139
|
-
rescue JSON::ParserError => e
|
|
140
|
-
print_error("配置文件格式错误: #{e.message}")
|
|
141
|
-
nil
|
|
142
|
-
rescue => e
|
|
143
|
-
print_error("读取配置文件失败: #{e.message}")
|
|
144
|
-
nil
|
|
145
|
-
end
|
|
146
|
-
end
|
|
147
|
-
|
|
148
|
-
def load_local_yaml_config
|
|
149
|
-
config_file = File.expand_path('~/.easyai/config.yml')
|
|
150
|
-
return {} unless File.exist?(config_file)
|
|
151
|
-
|
|
152
|
-
begin
|
|
153
|
-
require 'yaml'
|
|
154
|
-
YAML.load_file(config_file) || {}
|
|
155
|
-
rescue => e
|
|
156
|
-
print_error("读取本地配置失败: #{e.message}")
|
|
157
|
-
{}
|
|
158
|
-
end
|
|
159
|
-
end
|
|
160
|
-
|
|
161
|
-
def build_environment(config)
|
|
162
|
-
env = ENV.to_h
|
|
163
|
-
|
|
164
|
-
# 设置 OpenAI API 密钥
|
|
165
|
-
if config["env"] && config["env"]["OPENAI_API_KEY"]
|
|
166
|
-
env["OPENAI_API_KEY"] = config["env"]["OPENAI_API_KEY"]
|
|
167
|
-
|
|
168
|
-
# 统一显示方式:不显示完整的 API 密钥
|
|
169
|
-
print_status("🔑 API 密钥", mask_token(config["env"]["OPENAI_API_KEY"]))
|
|
170
|
-
|
|
171
|
-
if @verbose_mode
|
|
172
|
-
# verbose 模式:显示更多状态信息(但不显示敏感内容)
|
|
173
|
-
puts " 密钥长度: #{config["env"]["OPENAI_API_KEY"].length} 字符"
|
|
174
|
-
end
|
|
175
|
-
end
|
|
176
|
-
|
|
177
|
-
# 设置代理(如果配置中有)
|
|
178
|
-
if config["gpt_proxy"]
|
|
179
|
-
proxy_urls = []
|
|
180
|
-
|
|
181
|
-
if config["gpt_proxy"]["HTTP_PROXY"]
|
|
182
|
-
env["HTTP_PROXY"] = config["gpt_proxy"]["HTTP_PROXY"]
|
|
183
|
-
env["http_proxy"] = config["gpt_proxy"]["HTTP_PROXY"]
|
|
184
|
-
proxy_urls << config["gpt_proxy"]["HTTP_PROXY"]
|
|
185
|
-
end
|
|
186
|
-
|
|
187
|
-
if config["gpt_proxy"]["HTTPS_PROXY"]
|
|
188
|
-
env["HTTPS_PROXY"] = config["gpt_proxy"]["HTTPS_PROXY"]
|
|
189
|
-
env["https_proxy"] = config["gpt_proxy"]["HTTPS_PROXY"]
|
|
190
|
-
proxy_urls << config["gpt_proxy"]["HTTPS_PROXY"] unless proxy_urls.include?(config["gpt_proxy"]["HTTPS_PROXY"])
|
|
191
|
-
end
|
|
192
|
-
|
|
193
|
-
if proxy_urls.any?
|
|
194
|
-
# 统一显示方式:简化代理显示
|
|
195
|
-
simplified_proxies = proxy_urls.map { |url| mask_url(url) }
|
|
196
|
-
print_status("🌐 代理已配置", simplified_proxies.join(', '))
|
|
197
|
-
|
|
198
|
-
end
|
|
199
|
-
end
|
|
200
|
-
|
|
201
|
-
env
|
|
202
|
-
end
|
|
203
|
-
|
|
204
|
-
def mask_token(token)
|
|
205
|
-
return nil unless token
|
|
206
|
-
return "空令牌" if token.empty?
|
|
207
|
-
|
|
208
|
-
if token.length > 10
|
|
209
|
-
"#{token[0..5]}...#{token[-4..-1]}"
|
|
210
|
-
else
|
|
211
|
-
"*" * token.length
|
|
212
|
-
end
|
|
213
|
-
end
|
|
214
|
-
|
|
215
|
-
def mask_url(url)
|
|
216
|
-
return url unless url
|
|
217
|
-
|
|
218
|
-
begin
|
|
219
|
-
uri = URI(url)
|
|
220
|
-
|
|
221
|
-
# 隐藏IP地址的前两段,显示后两段
|
|
222
|
-
masked_host = uri.host
|
|
223
|
-
if uri.host =~ /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/
|
|
224
|
-
masked_host = "*.*.#{$3}.#{$4}"
|
|
225
|
-
end
|
|
226
|
-
|
|
227
|
-
if uri.password
|
|
228
|
-
"#{uri.scheme}://***@#{masked_host}:#{uri.port}"
|
|
229
|
-
else
|
|
230
|
-
"#{uri.scheme}://#{masked_host}:#{uri.port}"
|
|
231
|
-
end
|
|
232
|
-
rescue URI::InvalidURIError
|
|
233
|
-
url
|
|
234
|
-
end
|
|
235
|
-
end
|
|
236
|
-
|
|
237
|
-
def print_status(icon_text, detail = nil)
|
|
238
|
-
if detail
|
|
239
|
-
icon_width = 5
|
|
240
|
-
puts sprintf("%-#{icon_width}s %s", icon_text, detail.cyan)
|
|
241
|
-
else
|
|
242
|
-
puts icon_text
|
|
243
|
-
end
|
|
244
|
-
end
|
|
245
|
-
|
|
246
|
-
def print_success(message)
|
|
247
|
-
puts " ✓ #{message}".green
|
|
248
|
-
end
|
|
249
|
-
|
|
250
|
-
def print_warning(message)
|
|
251
|
-
puts " ⚠️ #{message}".yellow
|
|
252
|
-
end
|
|
253
|
-
|
|
254
|
-
def print_error(message)
|
|
255
|
-
puts "✗ #{message}".red
|
|
256
|
-
end
|
|
257
|
-
end
|
|
258
|
-
end
|
|
259
|
-
end
|