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.
- 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 -41
- data/lib/easyai/command/clean.rb +56 -20
- data/lib/easyai/command/gemini.rb +2 -6
- data/lib/easyai/command/gpt.rb +17 -11
- 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
@@ -46,12 +46,8 @@ module EasyAI
|
|
46
46
|
private
|
47
47
|
|
48
48
|
def gemini_available?
|
49
|
-
#
|
50
|
-
|
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
|
data/lib/easyai/command/gpt.rb
CHANGED
@@ -127,12 +127,8 @@ module EasyAI
|
|
127
127
|
private
|
128
128
|
|
129
129
|
def gpt_available?
|
130
|
-
#
|
131
|
-
|
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
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
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
|