easyai 1.2.1 → 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.
@@ -46,12 +46,8 @@ module EasyAI
46
46
  private
47
47
 
48
48
  def gemini_available?
49
- # 跨平台命令检测
50
- if RUBY_PLATFORM =~ /mswin|mingw|cygwin/
51
- system('where gemini >nul 2>&1')
52
- else
53
- system('which gemini > /dev/null 2>&1')
54
- end
49
+ # 使用统一的跨平台命令检测
50
+ EasyAI::CrossPlatform.command_exists?('gemini')
55
51
  end
56
52
  end
57
53
  end
@@ -127,12 +127,8 @@ module EasyAI
127
127
  private
128
128
 
129
129
  def gpt_available?
130
- # 跨平台命令检测
131
- if RUBY_PLATFORM =~ /mswin|mingw|cygwin/
132
- system('where openai >nul 2>&1')
133
- else
134
- system('which openai > /dev/null 2>&1')
135
- end
130
+ # 使用统一的跨平台命令检测
131
+ EasyAI::CrossPlatform.command_exists?('openai')
136
132
  end
137
133
 
138
134
  def load_local_config(config_file)
@@ -218,11 +214,21 @@ module EasyAI
218
214
  def mask_url(url)
219
215
  return url unless url
220
216
 
221
- if url =~ /^(https?:\/\/)([^:]+):([^@]+)@(.+)$/
222
- protocol, user, pass, rest = $1, $2, $3, $4
223
- masked_pass = '*' * [pass.length, 8].min
224
- "#{protocol}#{user}:#{masked_pass}@#{rest}"
225
- else
217
+ begin
218
+ uri = URI(url)
219
+
220
+ # 隐藏IP地址的前两段,显示后两段
221
+ masked_host = uri.host
222
+ if uri.host =~ /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/
223
+ masked_host = "*.*.#{$3}.#{$4}"
224
+ end
225
+
226
+ if uri.password
227
+ "#{uri.scheme}://***@#{masked_host}:#{uri.port}"
228
+ else
229
+ "#{uri.scheme}://#{masked_host}:#{uri.port}"
230
+ end
231
+ rescue URI::InvalidURIError
226
232
  url
227
233
  end
228
234
  end
@@ -0,0 +1,494 @@
1
+ require_relative '../config/easyai_config'
2
+ require_relative '../base/system_keychain'
3
+ require 'colored2'
4
+ require 'json'
5
+ require 'open3'
6
+
7
+ module EasyAI
8
+ class Command
9
+ class Setup < Command
10
+ self.summary = '初始化配置环境'
11
+ self.description = <<-DESC
12
+ 初始化 EasyAI 配置环境,自动下载并设置配置仓库。
13
+
14
+ 主要功能:
15
+ * 自动下载配置仓库
16
+ * 验证配置完整性
17
+ * 解密配置文件(如需要)
18
+ * 显示可用用户列表
19
+ * 管理密码存储
20
+
21
+ 使用示例:
22
+ $ easyai setup # 标准初始化
23
+ $ easyai setup --force # 强制重新下载配置
24
+ $ easyai setup --branch dev # 使用开发分支
25
+ $ easyai setup --verify # 仅验证配置状态
26
+ $ easyai setup --list-users # 列出可用用户
27
+ DESC
28
+
29
+ def self.options
30
+ [
31
+ ['--force', '强制重新下载配置仓库'],
32
+ ['--branch=BRANCH', '指定配置仓库分支 (默认: master)'],
33
+ ['--verify', '仅验证现有配置,不进行修改'],
34
+ ['--list-users', '列出所有可用用户'],
35
+ ['--clean', '清理所有临时解密文件'],
36
+ ['--no-password', '跳过密码设置'],
37
+ ].concat(super)
38
+ end
39
+
40
+ def initialize(argv)
41
+ @force = argv.flag?('force')
42
+ @branch = argv.option('branch') || 'master'
43
+ @verify_only = argv.flag?('verify')
44
+ @list_users = argv.flag?('list-users')
45
+ @clean = argv.flag?('clean')
46
+ @skip_password = argv.flag?('no-password')
47
+ super
48
+
49
+ @config_dir = File.expand_path('~/.easyai')
50
+ @repo_dir = File.join(@config_dir, 'EasyAISetting')
51
+ end
52
+
53
+ def run
54
+ show_welcome_banner
55
+
56
+ if @verify_only
57
+ verify_configuration
58
+ return
59
+ end
60
+
61
+ if @clean
62
+ cleanup_files
63
+ return
64
+ end
65
+
66
+ if @list_users
67
+ show_available_users
68
+ return
69
+ end
70
+
71
+ # 执行主要设置流程
72
+ perform_setup
73
+
74
+ # 显示完成信息
75
+ show_completion_info
76
+ end
77
+
78
+ private
79
+
80
+ def show_welcome_banner
81
+ puts
82
+ puts "🚀 " + "EasyAI 配置环境初始化".green.bold
83
+ puts "=" * 60
84
+ puts
85
+ end
86
+
87
+ def perform_setup
88
+ step_counter = 0
89
+
90
+ # 步骤1: 检查并创建目录
91
+ step_counter += 1
92
+ print_step(step_counter, "检查配置目录")
93
+ ensure_directories
94
+
95
+ # 步骤2: 下载或更新配置仓库
96
+ step_counter += 1
97
+ print_step(step_counter, "下载配置仓库")
98
+ if @force || !repo_exists?
99
+ download_repo
100
+ else
101
+ update_repo
102
+ end
103
+
104
+ # 步骤3: 处理加密文件
105
+ step_counter += 1
106
+ print_step(step_counter, "处理配置文件")
107
+ handle_encrypted_files unless @skip_password
108
+
109
+ # 步骤4: 验证配置
110
+ step_counter += 1
111
+ print_step(step_counter, "验证配置完整性")
112
+ verify_basic_config
113
+
114
+ # 步骤5: 显示可用用户
115
+ step_counter += 1
116
+ print_step(step_counter, "加载用户列表")
117
+ show_available_users(compact: true)
118
+ end
119
+
120
+ def print_step(number, description)
121
+ puts "\n" + "步骤 #{number}:".cyan + " #{description}"
122
+ puts "-" * 40
123
+ end
124
+
125
+ def ensure_directories
126
+ unless Dir.exist?(@config_dir)
127
+ FileUtils.mkdir_p(@config_dir)
128
+ puts " ✓ 创建配置目录: #{@config_dir}"
129
+ else
130
+ puts " ✓ 配置目录已存在: #{@config_dir}"
131
+ end
132
+ end
133
+
134
+ def repo_exists?
135
+ Dir.exist?(@repo_dir) && Dir.exist?(File.join(@repo_dir, '.git'))
136
+ end
137
+
138
+ def download_repo
139
+ if repo_exists? && @force
140
+ puts " 正在删除现有仓库..."
141
+ FileUtils.rm_rf(@repo_dir)
142
+ end
143
+
144
+ puts " 正在从 Gitee 下载配置仓库 (#{@branch} 分支)..."
145
+
146
+ # Gitee 不需要代理,临时清除代理环境变量
147
+ output = nil
148
+ success = false
149
+
150
+ if Gem.win_platform?
151
+ # Windows: 保存并清除环境变量
152
+ old_env = {}
153
+ ['HTTP_PROXY', 'HTTPS_PROXY', 'http_proxy', 'https_proxy'].each do |key|
154
+ old_env[key] = ENV[key]
155
+ ENV.delete(key)
156
+ end
157
+
158
+ begin
159
+ cmd = "git clone --depth 1 --branch #{@branch} #{EasyAIConfig::REPO_URL} #{@repo_dir} 2>&1"
160
+ puts " 执行命令: #{cmd}" if ENV['EASYAI_DEBUG']
161
+ output = `#{cmd}`
162
+ success = $?.success?
163
+ ensure
164
+ # 恢复环境变量
165
+ old_env.each { |k, v| ENV[k] = v if v }
166
+ end
167
+ else
168
+ # Unix/Linux/macOS: 使用 env -u
169
+ cmd = "env -u HTTP_PROXY -u HTTPS_PROXY -u http_proxy -u https_proxy git clone --depth 1 --branch #{@branch} #{EasyAIConfig::REPO_URL} #{@repo_dir} 2>&1"
170
+ puts " 执行命令: #{cmd}" if ENV['EASYAI_DEBUG']
171
+ output = `#{cmd}`
172
+ success = $?.success?
173
+ end
174
+
175
+ if success
176
+ puts " ✓ 配置仓库下载成功"
177
+ else
178
+ puts " ✗ 下载失败: #{output}".red
179
+ exit 1
180
+ end
181
+ end
182
+
183
+ def update_repo
184
+ puts " 检查配置仓库更新..."
185
+
186
+ Dir.chdir(@repo_dir) do
187
+ # 获取当前分支
188
+ current_branch = `git branch --show-current`.chomp
189
+
190
+ if current_branch != @branch
191
+ puts " 切换到 #{@branch} 分支..."
192
+ # 不使用代理
193
+ if Gem.win_platform?
194
+ # Windows: 临时清除环境变量
195
+ old_env = {}
196
+ ['HTTP_PROXY', 'HTTPS_PROXY', 'http_proxy', 'https_proxy'].each do |key|
197
+ old_env[key] = ENV[key]
198
+ ENV.delete(key)
199
+ end
200
+
201
+ begin
202
+ system("git fetch origin #{@branch} > nul 2>&1")
203
+ ensure
204
+ old_env.each { |k, v| ENV[k] = v if v }
205
+ end
206
+ else
207
+ system("env -u HTTP_PROXY -u HTTPS_PROXY -u http_proxy -u https_proxy git fetch origin #{@branch} > /dev/null 2>&1")
208
+ end
209
+ system("git checkout #{@branch} > /dev/null 2>&1")
210
+ end
211
+
212
+ # 拉取更新(不使用代理)
213
+ output = nil
214
+ if Gem.win_platform?
215
+ # Windows: 临时清除环境变量
216
+ old_env = {}
217
+ ['HTTP_PROXY', 'HTTPS_PROXY', 'http_proxy', 'https_proxy'].each do |key|
218
+ old_env[key] = ENV[key]
219
+ ENV.delete(key)
220
+ end
221
+
222
+ begin
223
+ output = `git pull --force 2>&1`
224
+ ensure
225
+ old_env.each { |k, v| ENV[k] = v if v }
226
+ end
227
+ else
228
+ output = `env -u HTTP_PROXY -u HTTPS_PROXY -u http_proxy -u https_proxy git pull --force 2>&1`
229
+ end
230
+
231
+ if $?.success?
232
+ if output.include?("Already up to date")
233
+ puts " ✓ 配置已是最新版本"
234
+ else
235
+ puts " ✓ 配置已更新到最新版本"
236
+ end
237
+ else
238
+ puts " ⚠️ 更新失败,使用现有配置"
239
+ end
240
+ end
241
+ end
242
+
243
+ def handle_encrypted_files
244
+ encrypted_files = Dir.glob(File.join(@repo_dir, '**/*.encrypted'))
245
+
246
+ if encrypted_files.empty?
247
+ puts " ✓ 未发现加密文件"
248
+ return
249
+ end
250
+
251
+ puts " 发现 #{encrypted_files.length} 个加密文件"
252
+
253
+ # 获取密码
254
+ password = get_setup_password
255
+ return unless password
256
+
257
+ # 测试密码
258
+ if test_password(password, encrypted_files.first)
259
+ puts " ✓ 密码验证成功"
260
+
261
+ # 保存到钥匙串
262
+ if should_save_password?
263
+ if Base::SystemKeychain.store_password(password)
264
+ puts " ✓ 密码已保存到系统钥匙串"
265
+ end
266
+ end
267
+ else
268
+ puts " ✗ 密码验证失败".red
269
+ puts " 提示: 配置文件将在使用时解密"
270
+ end
271
+ end
272
+
273
+ def get_setup_password
274
+ # 优先使用环境变量
275
+ if ENV['EASYAI_TEST_PASSWORD']
276
+ puts " 使用环境变量中的密码"
277
+ return ENV['EASYAI_TEST_PASSWORD']
278
+ end
279
+
280
+ # 检查钥匙串
281
+ stored_password = Base::SystemKeychain.get_stored_password
282
+ if stored_password && !stored_password.empty?
283
+ puts " 使用系统钥匙串中的密码"
284
+ return stored_password
285
+ end
286
+
287
+ # 提示用户输入
288
+ puts " 配置文件已加密,请输入解密密码"
289
+ print " 密码: "
290
+
291
+ begin
292
+ require 'io/console'
293
+ password = STDIN.noecho(&:gets)&.chomp
294
+ puts
295
+ password
296
+ rescue
297
+ nil
298
+ end
299
+ end
300
+
301
+ def test_password(password, test_file)
302
+ begin
303
+ require_relative '../base/file_crypto'
304
+ key = Base::FileCrypto.generate_key(password)
305
+ content = File.read(test_file)
306
+ Base::FileCrypto.aes_128_ecb_decrypt(key, content)
307
+ true
308
+ rescue
309
+ false
310
+ end
311
+ end
312
+
313
+ def should_save_password?
314
+ print " 是否保存密码到系统钥匙串?(y/n): "
315
+ response = STDIN.gets&.chomp&.downcase
316
+ response == 'y' || response == 'yes'
317
+ end
318
+
319
+ def verify_basic_config
320
+ required_files = [
321
+ 'index.json.encrypted',
322
+ 'jps_client_config.json.encrypted'
323
+ ]
324
+
325
+ missing_files = []
326
+ required_files.each do |file|
327
+ path = File.join(@repo_dir, file)
328
+ if File.exist?(path)
329
+ puts " ✓ #{file}"
330
+ else
331
+ missing_files << file
332
+ puts " ✗ #{file} (缺失)".red
333
+ end
334
+ end
335
+
336
+ if missing_files.empty?
337
+ puts " ✓ 核心配置文件完整"
338
+ else
339
+ puts " ⚠️ 缺少 #{missing_files.length} 个配置文件".yellow
340
+ end
341
+ end
342
+
343
+ def show_available_users(compact: false)
344
+ # 初始化配置
345
+ EasyAIConfig.initialize(verbose: false)
346
+
347
+ # 获取 index 配置
348
+ index_config = EasyAIConfig.get_config('index', verbose: false)
349
+
350
+ unless index_config
351
+ puts " ✗ 无法加载用户配置".red
352
+ return
353
+ end
354
+
355
+ if compact
356
+ user_count = count_users(index_config)
357
+ puts " ✓ 发现 #{user_count} 个可用用户配置"
358
+ else
359
+ display_users_table(index_config)
360
+ end
361
+ rescue => e
362
+ puts " ✗ 加载用户列表失败: #{e.message}".red
363
+ end
364
+
365
+ def count_users(config)
366
+ count = 0
367
+
368
+ if config.is_a?(Hash)
369
+ %w[claude gemini gpt].each do |tool|
370
+ next unless config[tool]
371
+
372
+ if tool == "claude" && config[tool].is_a?(Hash)
373
+ config[tool].each do |_auth_type, users|
374
+ count += users.keys.length if users.is_a?(Hash)
375
+ end
376
+ elsif config[tool].is_a?(Hash)
377
+ count += config[tool].keys.length
378
+ end
379
+ end
380
+ end
381
+
382
+ count
383
+ end
384
+
385
+ def display_users_table(config)
386
+ puts "\n可用用户列表:"
387
+ puts "=" * 60
388
+
389
+ %w[claude gemini gpt].each do |tool|
390
+ next unless config[tool]
391
+
392
+ puts "\n#{tool.upcase}:"
393
+
394
+ if tool == "claude" && config[tool].is_a?(Hash)
395
+ config[tool].each do |auth_type, users|
396
+ next unless users.is_a?(Hash)
397
+ puts " #{auth_type}:"
398
+ users.each do |name, _file|
399
+ puts " - #{name}"
400
+ end
401
+ end
402
+ elsif config[tool].is_a?(Hash)
403
+ config[tool].each do |name, _file|
404
+ puts " - #{name}"
405
+ end
406
+ end
407
+ end
408
+ end
409
+
410
+ def verify_configuration
411
+ puts "\n验证配置状态..."
412
+ puts "=" * 60
413
+
414
+ # 检查目录
415
+ puts "\n目录检查:"
416
+ puts " 配置目录: #{@config_dir} - " + (Dir.exist?(@config_dir) ? "✓".green : "✗".red)
417
+ puts " 仓库目录: #{@repo_dir} - " + (repo_exists? ? "✓".green : "✗".red)
418
+
419
+ # 检查 Git 状态
420
+ if repo_exists?
421
+ puts "\nGit 状态:"
422
+ Dir.chdir(@repo_dir) do
423
+ branch = `git branch --show-current`.chomp
424
+ puts " 当前分支: #{branch}"
425
+
426
+ status = `git status --short`
427
+ if status.empty?
428
+ puts " 工作区: 干净 ✓".green
429
+ else
430
+ puts " 工作区: 有修改 ⚠️".yellow
431
+ end
432
+ end
433
+ end
434
+
435
+ # 检查配置文件
436
+ puts "\n配置文件:"
437
+ verify_basic_config
438
+
439
+ # 检查密码
440
+ puts "\n密码状态:"
441
+ if ENV['EASYAI_TEST_PASSWORD']
442
+ puts " 环境变量密码: ✓".green
443
+ elsif Base::SystemKeychain.get_stored_password
444
+ puts " 系统钥匙串密码: ✓".green
445
+ else
446
+ puts " 未设置密码 ⚠️".yellow
447
+ end
448
+
449
+ # 尝试加载用户
450
+ show_available_users(compact: true)
451
+ end
452
+
453
+ def cleanup_files
454
+ puts "\n清理临时文件..."
455
+ puts "=" * 60
456
+
457
+ # 清理解密的 JSON 文件
458
+ json_files = Dir.glob(File.join(@repo_dir, '**/*.json'))
459
+ cleanup_count = 0
460
+
461
+ json_files.each do |file|
462
+ encrypted_file = "#{file}.encrypted"
463
+ if File.exist?(encrypted_file)
464
+ FileUtils.rm_f(file)
465
+ cleanup_count += 1
466
+ puts " 删除: #{File.basename(file)}"
467
+ end
468
+ end
469
+
470
+ if cleanup_count > 0
471
+ puts "\n ✓ 清理了 #{cleanup_count} 个临时文件".green
472
+ else
473
+ puts " ✓ 没有需要清理的文件".green
474
+ end
475
+ end
476
+
477
+ def show_completion_info
478
+ puts "\n" + "=" * 60
479
+ puts "✅ " + "配置初始化完成!".green.bold
480
+ puts
481
+ puts "您可以开始使用以下命令:"
482
+ puts " • easyai claude".cyan + " - 启动 Claude"
483
+ puts " • easyai gemini".cyan + " - 启动 Gemini"
484
+ puts " • easyai gpt".cyan + " - 启动 GPT"
485
+ puts
486
+ puts "其他有用的命令:"
487
+ puts " • easyai setup --verify".cyan + " - 验证配置状态"
488
+ puts " • easyai setup --list-users".cyan + " - 查看可用用户"
489
+ puts " • easyai setup --clean".cyan + " - 清理临时文件"
490
+ puts
491
+ end
492
+ end
493
+ end
494
+ end