easyai 1.0.2 → 1.0.4
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 +154 -5
- data/README.md +38 -57
- data/bin/easyai +5 -5
- data/easyai.gemspec +1 -0
- data/lib/easyai/auth/authclaude.rb +440 -0
- data/lib/easyai/auth/jpslogin.rb +384 -0
- data/lib/easyai/base/file_crypto.rb +214 -0
- data/lib/easyai/base/system_keychain.rb +240 -0
- data/lib/easyai/base/update_notifier.rb +129 -0
- data/lib/easyai/base/version_checker.rb +329 -0
- data/lib/easyai/command/claude.rb +278 -0
- data/lib/easyai/command/clean.rb +453 -0
- data/lib/easyai/command/gemini.rb +58 -0
- data/lib/easyai/command/gpt.rb +58 -0
- data/lib/easyai/command/update.rb +210 -0
- data/lib/easyai/command/utils/decry.rb +102 -0
- data/lib/easyai/command/utils/encry.rb +102 -0
- data/lib/easyai/command/utils.rb +32 -0
- data/lib/easyai/command.rb +56 -0
- data/lib/easyai/config/config.rb +550 -0
- data/lib/easyai/version.rb +1 -1
- data/lib/easyai.rb +67 -55
- metadata +31 -4
- data/lib/easyai/claude.rb +0 -61
- data/lib/easyai/gemini.rb +0 -61
- data/lib/easyai/gpt.rb +0 -60
@@ -0,0 +1,550 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'tmpdir'
|
3
|
+
require 'fileutils'
|
4
|
+
require 'open3'
|
5
|
+
require_relative '../base/file_crypto'
|
6
|
+
require_relative '../base/system_keychain'
|
7
|
+
require_relative '../auth/jpslogin'
|
8
|
+
|
9
|
+
module EasyAI
|
10
|
+
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
|
+
|
18
|
+
# 管理配置仓库:如果存在则更新,不存在则下载
|
19
|
+
def self.manage_config_repo
|
20
|
+
# 确保 ~/.easyai 目录存在
|
21
|
+
FileUtils.mkdir_p(EASYAI_DIR) unless Dir.exist?(EASYAI_DIR)
|
22
|
+
|
23
|
+
if Dir.exist?(CONFIG_REPO_DIR)
|
24
|
+
update_config_repo
|
25
|
+
else
|
26
|
+
download_config_repo
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# 更新现有的配置仓库
|
31
|
+
def self.update_config_repo
|
32
|
+
begin
|
33
|
+
# 检查是否是有效的 git 仓库
|
34
|
+
unless Dir.exist?(File.join(CONFIG_REPO_DIR, '.git'))
|
35
|
+
FileUtils.rm_rf(CONFIG_REPO_DIR)
|
36
|
+
return download_config_repo
|
37
|
+
end
|
38
|
+
|
39
|
+
# 切换到仓库目录并拉取更新
|
40
|
+
Dir.chdir(CONFIG_REPO_DIR) do
|
41
|
+
# 重置本地更改
|
42
|
+
reset_success = system('git reset --hard HEAD', out: File::NULL, err: File::NULL)
|
43
|
+
unless reset_success
|
44
|
+
FileUtils.rm_rf(CONFIG_REPO_DIR)
|
45
|
+
return download_config_repo
|
46
|
+
end
|
47
|
+
|
48
|
+
# 拉取最新更新,捕获错误信息
|
49
|
+
stdout, stderr, status = Open3.capture3('git pull origin master')
|
50
|
+
|
51
|
+
unless status.success?
|
52
|
+
puts "⚠️ 配置更新失败,使用缓存配置" if stderr && !stderr.empty?
|
53
|
+
FileUtils.rm_rf(CONFIG_REPO_DIR)
|
54
|
+
return download_config_repo
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
return true
|
59
|
+
|
60
|
+
rescue => e
|
61
|
+
FileUtils.rm_rf(CONFIG_REPO_DIR) if Dir.exist?(CONFIG_REPO_DIR)
|
62
|
+
return download_config_repo
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# 下载配置仓库到固定位置
|
67
|
+
def self.download_config_repo
|
68
|
+
puts "正在下载配置仓库到 #{CONFIG_REPO_DIR}..."
|
69
|
+
|
70
|
+
begin
|
71
|
+
# 克隆仓库到固定位置,捕获错误信息
|
72
|
+
stdout, stderr, status = Open3.capture3("git clone --quiet #{REPO_URL} #{CONFIG_REPO_DIR}")
|
73
|
+
|
74
|
+
unless status.success?
|
75
|
+
puts "✗ 配置仓库下载失败"
|
76
|
+
puts " 错误信息: #{stderr.chomp}" if stderr && !stderr.empty?
|
77
|
+
puts " 请检查网络连接、git 安装状态"
|
78
|
+
return false
|
79
|
+
end
|
80
|
+
|
81
|
+
puts "✓ 配置仓库下载成功"
|
82
|
+
return true
|
83
|
+
|
84
|
+
rescue => e
|
85
|
+
puts "✗ 下载配置仓库失败: #{e.message}"
|
86
|
+
return false
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# 从本地配置仓库加载配置
|
91
|
+
def self.load_from_local_repo(user_name = nil, options = {})
|
92
|
+
# 首先管理配置仓库
|
93
|
+
return nil unless manage_config_repo
|
94
|
+
|
95
|
+
# 设置全局选项
|
96
|
+
@@no_keychain = options[:no_keychain] || false
|
97
|
+
|
98
|
+
puts "正在从本地配置仓库加载配置..."
|
99
|
+
|
100
|
+
begin
|
101
|
+
# 检查 index.json 文件,支持加密版本
|
102
|
+
index_file = File.join(CONFIG_REPO_DIR, "index.json")
|
103
|
+
encrypted_index_file = File.join(CONFIG_REPO_DIR, "index.json.encrypted")
|
104
|
+
|
105
|
+
# 优先尝试加密的 index 文件
|
106
|
+
if File.exist?(encrypted_index_file)
|
107
|
+
users = parse_encrypted_index_file(encrypted_index_file)
|
108
|
+
elsif File.exist?(index_file)
|
109
|
+
users = parse_index_file(index_file)
|
110
|
+
else
|
111
|
+
puts "未找到 index.json 或 index.json.encrypted,使用默认配置"
|
112
|
+
config_file = File.join(CONFIG_REPO_DIR, "claude_setting.json")
|
113
|
+
encrypted_config_file = File.join(CONFIG_REPO_DIR, "claude_setting.json.encrypted")
|
114
|
+
|
115
|
+
if File.exist?(encrypted_config_file)
|
116
|
+
return load_encrypted_config_file(encrypted_config_file)
|
117
|
+
elsif File.exist?(config_file)
|
118
|
+
return load_config_file(config_file)
|
119
|
+
else
|
120
|
+
puts "✗ 未找到配置文件"
|
121
|
+
return nil
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
return nil if users.nil?
|
126
|
+
|
127
|
+
if users.empty?
|
128
|
+
puts "✗ index.json 中未找到用户"
|
129
|
+
return nil
|
130
|
+
end
|
131
|
+
|
132
|
+
# 获取用户名:优先使用提供的用户名,否则通过 JPS 登录获取
|
133
|
+
if user_name
|
134
|
+
selected_user = user_name
|
135
|
+
else
|
136
|
+
# 统一使用 JPS 登录获取用户名
|
137
|
+
selected_user = get_username_from_jps
|
138
|
+
if selected_user.nil? || selected_user.strip.empty?
|
139
|
+
puts "✗ JPS 登录失败,无法获取用户名"
|
140
|
+
return nil
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
# 查找用户配置文件
|
145
|
+
config_filename = find_user_config(users, selected_user.strip)
|
146
|
+
|
147
|
+
if config_filename.nil?
|
148
|
+
puts "✗ 用户 '#{selected_user}' 未找到"
|
149
|
+
puts " 可用用户: #{users.keys.join(', ')}"
|
150
|
+
return nil
|
151
|
+
end
|
152
|
+
|
153
|
+
# 加载配置文件,支持加密版本
|
154
|
+
config_filename += '.json' unless config_filename.end_with?('.json')
|
155
|
+
config_file = File.join(CONFIG_REPO_DIR, config_filename)
|
156
|
+
encrypted_config_file = File.join(CONFIG_REPO_DIR, "#{config_filename}.encrypted")
|
157
|
+
|
158
|
+
puts "✓ 正在为 #{selected_user} 加载配置"
|
159
|
+
|
160
|
+
# 优先尝试加密文件
|
161
|
+
if File.exist?(encrypted_config_file)
|
162
|
+
load_encrypted_config_file(encrypted_config_file)
|
163
|
+
elsif File.exist?(config_file)
|
164
|
+
load_config_file(config_file)
|
165
|
+
else
|
166
|
+
puts "✗ 配置文件未找到: #{config_filename}"
|
167
|
+
return nil
|
168
|
+
end
|
169
|
+
|
170
|
+
rescue => e
|
171
|
+
puts "✗ 加载本地配置失败: #{e.message}"
|
172
|
+
nil
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
def self.download_user_config(user_name = nil, options = {})
|
177
|
+
# 优先尝试使用本地配置仓库
|
178
|
+
config = load_from_local_repo(user_name, options)
|
179
|
+
return config if config
|
180
|
+
|
181
|
+
# 如果本地仓库失败,回退到临时下载方式
|
182
|
+
puts "本地配置仓库不可用,使用临时下载..."
|
183
|
+
download_user_config_temp(user_name, options)
|
184
|
+
end
|
185
|
+
|
186
|
+
# 原有的临时下载方式(作为备用方案)
|
187
|
+
def self.download_user_config_temp(user_name = nil, options = {})
|
188
|
+
puts "正在获取配置文件..."
|
189
|
+
|
190
|
+
# 设置全局选项
|
191
|
+
@@no_keychain = options[:no_keychain] || false
|
192
|
+
|
193
|
+
begin
|
194
|
+
# 创建临时目录
|
195
|
+
temp_dir = Dir.mktmpdir("EasyAISetting")
|
196
|
+
|
197
|
+
# 克隆仓库
|
198
|
+
clone_cmd = "git clone --depth 1 --quiet #{REPO_URL} #{temp_dir}"
|
199
|
+
success = system(clone_cmd, out: File::NULL, err: File::NULL)
|
200
|
+
|
201
|
+
unless success
|
202
|
+
puts "✗ 配置文件获取失败"
|
203
|
+
puts " 请检查网络连接、git 安装,或使用本地配置文件"
|
204
|
+
cleanup_temp_dir(temp_dir)
|
205
|
+
return nil
|
206
|
+
end
|
207
|
+
|
208
|
+
puts "✓ 配置文件获取成功"
|
209
|
+
|
210
|
+
# 检查 index.json 文件,支持加密版本
|
211
|
+
index_file = File.join(temp_dir, "index.json")
|
212
|
+
encrypted_index_file = File.join(temp_dir, "index.json.encrypted")
|
213
|
+
|
214
|
+
# 优先尝试加密的 index 文件
|
215
|
+
if File.exist?(encrypted_index_file)
|
216
|
+
users = parse_encrypted_index_file(encrypted_index_file)
|
217
|
+
elsif File.exist?(index_file)
|
218
|
+
users = parse_index_file(index_file)
|
219
|
+
else
|
220
|
+
puts "未找到 index.json 或 index.json.encrypted,使用默认配置"
|
221
|
+
config_file = File.join(temp_dir, "claude_setting.json")
|
222
|
+
encrypted_config_file = File.join(temp_dir, "claude_setting.json.encrypted")
|
223
|
+
|
224
|
+
if File.exist?(encrypted_config_file)
|
225
|
+
config_content = load_encrypted_config_file(encrypted_config_file)
|
226
|
+
elsif File.exist?(config_file)
|
227
|
+
config_content = load_config_file(config_file)
|
228
|
+
else
|
229
|
+
puts "✗ 未找到配置文件"
|
230
|
+
cleanup_temp_dir(temp_dir)
|
231
|
+
return nil
|
232
|
+
end
|
233
|
+
|
234
|
+
cleanup_temp_dir(temp_dir)
|
235
|
+
return config_content
|
236
|
+
end
|
237
|
+
|
238
|
+
return nil if users.nil?
|
239
|
+
|
240
|
+
if users.empty?
|
241
|
+
puts "✗ index.json 中未找到用户"
|
242
|
+
cleanup_temp_dir(temp_dir)
|
243
|
+
return nil
|
244
|
+
end
|
245
|
+
|
246
|
+
# 获取用户名:优先使用提供的用户名,否则通过 JPS 登录获取
|
247
|
+
if user_name
|
248
|
+
selected_user = user_name
|
249
|
+
else
|
250
|
+
# 统一使用 JPS 登录获取用户名
|
251
|
+
selected_user = get_username_from_jps
|
252
|
+
if selected_user.nil? || selected_user.strip.empty?
|
253
|
+
puts "✗ JPS 登录失败,无法获取用户名"
|
254
|
+
cleanup_temp_dir(temp_dir)
|
255
|
+
return nil
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
# 查找用户配置文件
|
260
|
+
config_filename = find_user_config(users, selected_user.strip)
|
261
|
+
|
262
|
+
if config_filename.nil?
|
263
|
+
puts "✗ 用户 '#{selected_user}' 未找到"
|
264
|
+
puts " 可用用户: #{users.keys.join(', ')}"
|
265
|
+
cleanup_temp_dir(temp_dir)
|
266
|
+
return nil
|
267
|
+
end
|
268
|
+
|
269
|
+
# 加载配置文件,支持加密版本
|
270
|
+
config_filename += '.json' unless config_filename.end_with?('.json')
|
271
|
+
config_file = File.join(temp_dir, config_filename)
|
272
|
+
encrypted_config_file = File.join(temp_dir, "#{config_filename}.encrypted")
|
273
|
+
|
274
|
+
puts "✓ 正在为 #{selected_user} 加载配置"
|
275
|
+
|
276
|
+
# 优先尝试加密文件
|
277
|
+
if File.exist?(encrypted_config_file)
|
278
|
+
config_content = load_encrypted_config_file(encrypted_config_file)
|
279
|
+
elsif File.exist?(config_file)
|
280
|
+
config_content = load_config_file(config_file)
|
281
|
+
else
|
282
|
+
puts "✗ 配置文件未找到: #{config_filename}"
|
283
|
+
cleanup_temp_dir(temp_dir)
|
284
|
+
return nil
|
285
|
+
end
|
286
|
+
|
287
|
+
cleanup_temp_dir(temp_dir)
|
288
|
+
config_content
|
289
|
+
|
290
|
+
rescue => e
|
291
|
+
puts "✗ 配置获取失败: #{e.message}"
|
292
|
+
cleanup_temp_dir(temp_dir) if temp_dir
|
293
|
+
nil
|
294
|
+
end
|
295
|
+
end
|
296
|
+
|
297
|
+
private
|
298
|
+
|
299
|
+
def self.cleanup_temp_dir(temp_dir)
|
300
|
+
return unless temp_dir && Dir.exist?(temp_dir)
|
301
|
+
FileUtils.rm_rf(temp_dir)
|
302
|
+
end
|
303
|
+
|
304
|
+
def self.parse_index_file(index_file)
|
305
|
+
begin
|
306
|
+
index_content = File.read(index_file)
|
307
|
+
JSON.parse(index_content)
|
308
|
+
rescue JSON::ParserError => e
|
309
|
+
puts "✗ 解析 index.json 失败: #{e.message}"
|
310
|
+
nil
|
311
|
+
rescue => e
|
312
|
+
puts "✗ 读取 index.json 失败: #{e.message}"
|
313
|
+
nil
|
314
|
+
end
|
315
|
+
end
|
316
|
+
|
317
|
+
# 通过 JPS 登录获取用户名
|
318
|
+
def self.get_username_from_jps
|
319
|
+
begin
|
320
|
+
jps_login = Auth::JPSLogin.new
|
321
|
+
|
322
|
+
if jps_login.login
|
323
|
+
username = jps_login.get_username
|
324
|
+
if username && !username.empty?
|
325
|
+
puts "👤 用户: #{username}"
|
326
|
+
return username
|
327
|
+
end
|
328
|
+
end
|
329
|
+
rescue => e
|
330
|
+
puts "❌ JPS 登录失败: #{e.message}"
|
331
|
+
end
|
332
|
+
|
333
|
+
return nil
|
334
|
+
end
|
335
|
+
|
336
|
+
def self.find_user_config(users, user_input_clean)
|
337
|
+
users.each do |name, filename|
|
338
|
+
if name.downcase == user_input_clean.downcase
|
339
|
+
return filename
|
340
|
+
end
|
341
|
+
end
|
342
|
+
nil
|
343
|
+
end
|
344
|
+
|
345
|
+
def self.load_config_file(config_file)
|
346
|
+
begin
|
347
|
+
config_content = File.read(config_file)
|
348
|
+
JSON.parse(config_content)
|
349
|
+
rescue JSON::ParserError => e
|
350
|
+
puts "✗ 解析配置文件失败: #{e.message}"
|
351
|
+
nil
|
352
|
+
rescue => e
|
353
|
+
puts "✗ 读取配置文件失败: #{e.message}"
|
354
|
+
nil
|
355
|
+
end
|
356
|
+
end
|
357
|
+
|
358
|
+
def self.load_encrypted_config_file(encrypted_config_file)
|
359
|
+
puts "正在解密配置文件..."
|
360
|
+
|
361
|
+
# 使用新的密码验证机制
|
362
|
+
result = get_and_validate_password(encrypted_config_file, "请输入解密密码: ")
|
363
|
+
return nil unless result
|
364
|
+
|
365
|
+
begin
|
366
|
+
# 解析 JSON
|
367
|
+
JSON.parse(result[:content])
|
368
|
+
rescue JSON::ParserError => e
|
369
|
+
puts "✗ 解析解密后的配置文件失败: #{e.message}"
|
370
|
+
puts "文件可能已损坏或不是有效的JSON格式"
|
371
|
+
nil
|
372
|
+
end
|
373
|
+
end
|
374
|
+
|
375
|
+
def self.parse_encrypted_index_file(encrypted_index_file)
|
376
|
+
puts "正在解密 index.json..."
|
377
|
+
|
378
|
+
# 使用新的密码验证机制
|
379
|
+
result = get_and_validate_password(encrypted_index_file, "请输入解密密码: ")
|
380
|
+
return nil unless result
|
381
|
+
|
382
|
+
begin
|
383
|
+
# 解析 JSON
|
384
|
+
JSON.parse(result[:content])
|
385
|
+
rescue JSON::ParserError => e
|
386
|
+
puts "✗ 解析解密后的 index.json 失败: #{e.message}"
|
387
|
+
puts "文件可能已损坏或不是有效的JSON格式"
|
388
|
+
nil
|
389
|
+
end
|
390
|
+
end
|
391
|
+
|
392
|
+
# 智能密码获取和验证:包含错误处理和重试机制
|
393
|
+
def self.get_and_validate_password(encrypted_file_path, prompt, max_retries = 3)
|
394
|
+
retries = 0
|
395
|
+
|
396
|
+
loop do
|
397
|
+
# 获取密码(系统存储或用户输入)
|
398
|
+
password_info = get_decryption_password_basic(prompt, retries > 0)
|
399
|
+
return nil unless password_info
|
400
|
+
|
401
|
+
# 尝试解密验证密码
|
402
|
+
begin
|
403
|
+
decrypted_content = decrypt_file_content(encrypted_file_path, password_info[:password])
|
404
|
+
|
405
|
+
# 解密成功,保存密码(如果不是从系统获取且未禁用存储)
|
406
|
+
unless password_info[:from_system] || @@no_keychain
|
407
|
+
if Base::SystemKeychain.store_password(password_info[:password])
|
408
|
+
puts "✓ 密码已安全存储,下次使用将自动获取"
|
409
|
+
end
|
410
|
+
end
|
411
|
+
|
412
|
+
return { content: decrypted_content, password_info: password_info }
|
413
|
+
|
414
|
+
rescue => e
|
415
|
+
retries += 1
|
416
|
+
|
417
|
+
# 分析错误类型并提供针对性提示
|
418
|
+
error_type = analyze_decryption_error(e)
|
419
|
+
|
420
|
+
# 处理系统密码错误
|
421
|
+
if password_info[:from_system]
|
422
|
+
puts "✗ 系统存储的密码无效"
|
423
|
+
puts " 错误详情:#{e.message}" if error_type[:show_details]
|
424
|
+
puts " 错误原因:#{error_type[:reason]}"
|
425
|
+
puts "正在清除无效的系统存储密码..."
|
426
|
+
Base::SystemKeychain.delete_stored_password
|
427
|
+
puts "✓ 已清除系统存储的无效密码"
|
428
|
+
puts "请重新输入正确的解密密码"
|
429
|
+
else
|
430
|
+
puts "✗ 解密失败"
|
431
|
+
puts " 错误详情:#{e.message}" if error_type[:show_details]
|
432
|
+
puts " 错误原因:#{error_type[:reason]}"
|
433
|
+
puts " 建议:#{error_type[:suggestion]}"
|
434
|
+
end
|
435
|
+
|
436
|
+
# 检查是否达到最大重试次数
|
437
|
+
if retries >= max_retries
|
438
|
+
puts "\n已达到最大重试次数 (#{max_retries})"
|
439
|
+
puts "可能的解决方案:"
|
440
|
+
puts " 1. 确认密码是否正确"
|
441
|
+
puts " 2. 检查文件是否完整下载"
|
442
|
+
puts " 3. 联系管理员确认密码是否已更改"
|
443
|
+
|
444
|
+
if ask_user_continue?
|
445
|
+
retries = 0 # 重置重试次数,继续尝试
|
446
|
+
puts "\n继续尝试解密..."
|
447
|
+
else
|
448
|
+
puts "用户选择退出解密"
|
449
|
+
return nil
|
450
|
+
end
|
451
|
+
else
|
452
|
+
remaining = max_retries - retries
|
453
|
+
puts "还可以重试 #{remaining} 次 (#{retries}/#{max_retries})"
|
454
|
+
puts "提示:按 Ctrl+C 可随时退出\n"
|
455
|
+
end
|
456
|
+
end
|
457
|
+
end
|
458
|
+
end
|
459
|
+
|
460
|
+
# 基础密码获取:先从系统获取,失败则提示用户输入
|
461
|
+
def self.get_decryption_password_basic(prompt, is_retry = false)
|
462
|
+
# 如果是重试,或者禁用了密码存储,直接提示用户输入
|
463
|
+
if is_retry || @@no_keychain
|
464
|
+
if @@no_keychain
|
465
|
+
puts "已禁用自动密码存储,请手动输入密码"
|
466
|
+
end
|
467
|
+
password = Base::FileCrypto.get_password(prompt)
|
468
|
+
return password ? { password: password, from_system: false } : nil
|
469
|
+
end
|
470
|
+
|
471
|
+
# 首先尝试从系统获取存储的密码
|
472
|
+
stored_password = Base::SystemKeychain.get_stored_password
|
473
|
+
|
474
|
+
if stored_password && !stored_password.empty?
|
475
|
+
puts "使用系统存储的密码进行解密..."
|
476
|
+
return { password: stored_password, from_system: true }
|
477
|
+
end
|
478
|
+
|
479
|
+
# 如果没有存储的密码,提示用户输入
|
480
|
+
password = Base::FileCrypto.get_password(prompt)
|
481
|
+
return password ? { password: password, from_system: false } : nil
|
482
|
+
end
|
483
|
+
|
484
|
+
# 分析解密错误类型,提供针对性建议
|
485
|
+
def self.analyze_decryption_error(error)
|
486
|
+
case error.message
|
487
|
+
when /bad decrypt/i, /wrong final block length/i, /invalid padding/i
|
488
|
+
{
|
489
|
+
reason: "密码不正确或文件已损坏",
|
490
|
+
suggestion: "请确认密码是否正确,或检查文件完整性",
|
491
|
+
show_details: false
|
492
|
+
}
|
493
|
+
when /not supported by device/i
|
494
|
+
{
|
495
|
+
reason: "系统不支持此加密算法或文件格式错误",
|
496
|
+
suggestion: "请检查文件是否为正确的加密文件",
|
497
|
+
show_details: true
|
498
|
+
}
|
499
|
+
when /no such file/i, /cannot open/i
|
500
|
+
{
|
501
|
+
reason: "文件不存在或无法访问",
|
502
|
+
suggestion: "请检查文件路径和权限",
|
503
|
+
show_details: false
|
504
|
+
}
|
505
|
+
when /invalid base64/i
|
506
|
+
{
|
507
|
+
reason: "文件格式错误或已损坏",
|
508
|
+
suggestion: "请重新下载配置文件",
|
509
|
+
show_details: false
|
510
|
+
}
|
511
|
+
else
|
512
|
+
{
|
513
|
+
reason: "未知的解密错误",
|
514
|
+
suggestion: "请尝试重新下载文件或联系技术支持",
|
515
|
+
show_details: true
|
516
|
+
}
|
517
|
+
end
|
518
|
+
end
|
519
|
+
|
520
|
+
# 询问用户是否继续尝试
|
521
|
+
def self.ask_user_continue?
|
522
|
+
puts "\n是否继续尝试解密?"
|
523
|
+
puts " y/yes - 继续尝试"
|
524
|
+
puts " n/no - 退出解密 (默认)"
|
525
|
+
print "> "
|
526
|
+
|
527
|
+
begin
|
528
|
+
response = STDIN.gets&.chomp&.downcase
|
529
|
+
return ['y', 'yes'].include?(response)
|
530
|
+
rescue Interrupt
|
531
|
+
puts "\n\n用户中断操作"
|
532
|
+
return false
|
533
|
+
rescue => e
|
534
|
+
puts "无法读取用户输入: #{e.message}"
|
535
|
+
return false
|
536
|
+
end
|
537
|
+
end
|
538
|
+
|
539
|
+
def self.decrypt_file_content(encrypted_file_path, password)
|
540
|
+
# 生成密钥
|
541
|
+
key = Base::FileCrypto.generate_key(password)
|
542
|
+
|
543
|
+
# 读取加密文件内容
|
544
|
+
encrypted_content = File.read(encrypted_file_path)
|
545
|
+
|
546
|
+
# 解密内容
|
547
|
+
Base::FileCrypto.aes_128_ecb_decrypt(key, encrypted_content)
|
548
|
+
end
|
549
|
+
end
|
550
|
+
end
|
data/lib/easyai/version.rb
CHANGED
data/lib/easyai.rb
CHANGED
@@ -1,75 +1,87 @@
|
|
1
|
-
require 'claide'
|
2
|
-
require 'yaml'
|
3
|
-
require 'fileutils'
|
4
|
-
require 'colored2'
|
5
1
|
require 'easyai/version'
|
2
|
+
require 'easyai/config/config'
|
3
|
+
require 'easyai/command'
|
4
|
+
require 'easyai/command/claude'
|
5
|
+
require 'easyai/command/gemini'
|
6
|
+
require 'easyai/command/gpt'
|
7
|
+
require 'easyai/command/utils'
|
8
|
+
require 'easyai/command/clean'
|
9
|
+
require 'easyai/command/update'
|
10
|
+
require 'easyai/base/version_checker'
|
6
11
|
|
7
12
|
module EasyAI
|
8
|
-
|
9
|
-
class
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
def self.options
|
16
|
-
[
|
17
|
-
['--config', '显示配置文件路径'],
|
18
|
-
['--setup', '交互式设置配置']
|
19
|
-
].concat(super)
|
20
|
-
end
|
21
|
-
|
22
|
-
def initialize(argv)
|
23
|
-
@config_flag = argv.flag?('config')
|
24
|
-
@setup_flag = argv.flag?('setup')
|
25
|
-
super
|
26
|
-
end
|
27
|
-
|
28
|
-
def run
|
29
|
-
if @config_flag
|
30
|
-
puts config_file_path
|
31
|
-
return
|
32
|
-
end
|
13
|
+
# EasyAI 应用程序主类
|
14
|
+
class EasyAIApp
|
15
|
+
def run(argv)
|
16
|
+
# 解析参数以确定是否显示启动标志
|
17
|
+
require 'claide'
|
18
|
+
require 'colored2'
|
19
|
+
coerced_argv = CLAide::ARGV.coerce(argv)
|
33
20
|
|
34
|
-
|
35
|
-
|
36
|
-
|
21
|
+
# 如果不是帮助或版本查询,显示启动标志
|
22
|
+
unless coerced_argv.flag?('help') || coerced_argv.flag?('version')
|
23
|
+
show_banner
|
37
24
|
end
|
38
25
|
|
39
|
-
|
26
|
+
# 在运行命令前进行版本检查
|
27
|
+
check_version_before_run(argv)
|
28
|
+
|
29
|
+
# 运行命令
|
30
|
+
EasyAI::Command.run(argv)
|
40
31
|
end
|
41
32
|
|
42
33
|
private
|
43
34
|
|
44
|
-
|
45
|
-
|
35
|
+
# 显示启动标志
|
36
|
+
def show_banner
|
37
|
+
require 'colored2'
|
38
|
+
|
39
|
+
# 简化的标题
|
40
|
+
title = "EasyAI v#{VERSION}"
|
41
|
+
box_width = 60
|
42
|
+
|
43
|
+
# 美化的启动标志
|
44
|
+
puts
|
45
|
+
puts "╔#{'═' * box_width}╗".cyan
|
46
|
+
puts "║#{title.center(box_width).green}║".cyan
|
47
|
+
puts "╚#{'═' * box_width}╝".cyan
|
48
|
+
puts
|
46
49
|
end
|
47
50
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
+
# 检查版本和更新
|
52
|
+
def check_version_before_run(argv)
|
53
|
+
# 解析参数以确定是否需要跳过版本检查
|
54
|
+
require 'claide'
|
55
|
+
require 'colored2'
|
56
|
+
coerced_argv = CLAide::ARGV.coerce(argv)
|
51
57
|
|
52
|
-
|
58
|
+
# 如果是以下情况,跳过版本检查:
|
59
|
+
# 1. 请求帮助信息 (--help)
|
60
|
+
# 2. 显示版本信息 (--version)
|
61
|
+
# 3. 主动检查更新 (--check-update)
|
62
|
+
skip_check = coerced_argv.flag?('help') ||
|
63
|
+
coerced_argv.flag?('version') ||
|
64
|
+
coerced_argv.flag?('check-update')
|
53
65
|
|
54
|
-
|
55
|
-
|
66
|
+
if ENV['EASYAI_DEBUG']
|
67
|
+
puts "📊 版本检查状态:".cyan
|
68
|
+
puts " • 当前版本: v#{EasyAI::VERSION}".green
|
69
|
+
puts " • 跳过检查: #{skip_check ? '是(--help/--version)'.yellow : '否'.cyan}"
|
70
|
+
end
|
56
71
|
|
57
|
-
|
58
|
-
config['gemini_token'] = STDIN.gets.chomp
|
72
|
+
return if skip_check
|
59
73
|
|
60
|
-
|
61
|
-
|
74
|
+
# 强制更新检查(会阻塞执行)
|
75
|
+
Base::VersionChecker.check_force_update!
|
62
76
|
|
63
|
-
|
64
|
-
|
65
|
-
config['http_proxy'] = proxy unless proxy.empty?
|
77
|
+
# 异步更新检查(不阻塞执行)
|
78
|
+
Base::VersionChecker.check_async
|
66
79
|
|
67
|
-
|
68
|
-
|
80
|
+
puts "✅ #{'版本检查完成'.green}" if ENV['EASYAI_DEBUG']
|
81
|
+
rescue => e
|
82
|
+
# 版本检查失败不应该影响命令执行
|
83
|
+
puts "⚠️ 版本检查出错: #{e.message}".yellow
|
84
|
+
puts e.backtrace.join("\n") if ENV['EASYAI_DEBUG']
|
69
85
|
end
|
70
86
|
end
|
71
|
-
end
|
72
|
-
|
73
|
-
require 'easyai/claude'
|
74
|
-
require 'easyai/gemini'
|
75
|
-
require 'easyai/gpt'
|
87
|
+
end
|