easyai 1.0.2 → 1.0.3
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/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 +17 -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,453 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'yaml'
|
3
|
+
require 'fileutils'
|
4
|
+
require 'etc'
|
5
|
+
require 'open3'
|
6
|
+
|
7
|
+
module EasyAI
|
8
|
+
class Command
|
9
|
+
class Clean < Command
|
10
|
+
self.summary = '清理AI工具配置信息'
|
11
|
+
self.description = <<-DESC
|
12
|
+
清理指定AI工具的所有配置信息。
|
13
|
+
|
14
|
+
支持清理:
|
15
|
+
|
16
|
+
* Claude 相关配置
|
17
|
+
|
18
|
+
* Gemini 相关配置
|
19
|
+
|
20
|
+
* OpenAI 相关配置
|
21
|
+
|
22
|
+
使用示例:
|
23
|
+
|
24
|
+
$ easyai clean # 清理Claude配置(默认)
|
25
|
+
|
26
|
+
$ easyai clean gemini # 清理Gemini配置
|
27
|
+
|
28
|
+
$ easyai clean openai # 清理OpenAI配置
|
29
|
+
|
30
|
+
$ easyai clean all # 清理所有配置
|
31
|
+
DESC
|
32
|
+
|
33
|
+
def self.options
|
34
|
+
[
|
35
|
+
['--force', '强制清理,不询问确认'],
|
36
|
+
['--dry-run', '预览将要清理的项目,不执行实际清理'],
|
37
|
+
].concat(super)
|
38
|
+
end
|
39
|
+
|
40
|
+
def initialize(argv)
|
41
|
+
@tool_name = argv.shift_argument || 'claude'
|
42
|
+
@force = argv.flag?('force')
|
43
|
+
@dry_run = argv.flag?('dry-run')
|
44
|
+
super
|
45
|
+
end
|
46
|
+
|
47
|
+
def validate!
|
48
|
+
super
|
49
|
+
valid_tools = %w[claude gemini openai all]
|
50
|
+
unless valid_tools.include?(@tool_name.downcase)
|
51
|
+
help! "不支持的工具名称: #{@tool_name}。支持的工具: #{valid_tools.join(', ')}"
|
52
|
+
end
|
53
|
+
@tool_name = @tool_name.downcase
|
54
|
+
end
|
55
|
+
|
56
|
+
def run
|
57
|
+
puts "🧹 准备清理 #{@tool_name == 'all' ? '所有AI工具' : @tool_name.upcase} 的配置信息"
|
58
|
+
puts
|
59
|
+
|
60
|
+
# 预览模式或确认
|
61
|
+
if @dry_run
|
62
|
+
puts "📋 预览模式 - 以下项目将被清理:"
|
63
|
+
preview_cleanup
|
64
|
+
return
|
65
|
+
end
|
66
|
+
|
67
|
+
unless @force
|
68
|
+
puts "⚠️ 警告:此操作将删除以下配置信息:"
|
69
|
+
preview_cleanup
|
70
|
+
puts
|
71
|
+
print "确认继续清理?(y/N): "
|
72
|
+
confirmation = STDIN.gets.chomp.downcase
|
73
|
+
unless confirmation == 'y' || confirmation == 'yes'
|
74
|
+
puts "❌ 清理操作已取消"
|
75
|
+
return
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
puts "🧹 开始清理配置..."
|
80
|
+
perform_cleanup
|
81
|
+
puts "✅ 清理完成"
|
82
|
+
end
|
83
|
+
|
84
|
+
private
|
85
|
+
|
86
|
+
def preview_cleanup
|
87
|
+
case @tool_name
|
88
|
+
when 'claude'
|
89
|
+
preview_claude_cleanup
|
90
|
+
when 'gemini'
|
91
|
+
preview_gemini_cleanup
|
92
|
+
when 'openai'
|
93
|
+
preview_openai_cleanup
|
94
|
+
when 'all'
|
95
|
+
preview_claude_cleanup
|
96
|
+
preview_gemini_cleanup
|
97
|
+
preview_openai_cleanup
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def perform_cleanup
|
102
|
+
case @tool_name
|
103
|
+
when 'claude'
|
104
|
+
clean_claude
|
105
|
+
when 'gemini'
|
106
|
+
clean_gemini
|
107
|
+
when 'openai'
|
108
|
+
clean_openai
|
109
|
+
when 'all'
|
110
|
+
clean_claude
|
111
|
+
clean_gemini
|
112
|
+
clean_openai
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def preview_claude_cleanup
|
117
|
+
puts "📌 Claude 配置(由 easyai claude 命令设置的项目):"
|
118
|
+
|
119
|
+
# 本地配置文件 - easyai 自己的配置,不是 authclaude.rb 设置的
|
120
|
+
config_file = File.expand_path('~/.easyai/config.yml')
|
121
|
+
if File.exist?(config_file)
|
122
|
+
config = YAML.load_file(config_file) rescue {}
|
123
|
+
if config && config['claude_token']
|
124
|
+
puts " • 本地配置文件: #{config_file} (claude_token)"
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
# Claude JSON 配置文件 - authclaude.rb 中 update_claude_json 设置的
|
129
|
+
claude_file = File.expand_path('~/.claude.json')
|
130
|
+
if File.exist?(claude_file) && has_claude_json_config?(claude_file)
|
131
|
+
puts " • Claude配置文件: #{claude_file} (由远程配置合并的内容)"
|
132
|
+
end
|
133
|
+
|
134
|
+
# Shell 配置文件中的代理别名 - authclaude.rb 中 configure_proxy 设置的
|
135
|
+
shell_files = get_shell_files
|
136
|
+
shell_files.each do |shell_file|
|
137
|
+
if File.exist?(shell_file) && has_claude_proxy_config?(shell_file)
|
138
|
+
puts " • Shell代理配置: #{shell_file} (claude_proxy, unclaude_proxy 别名)"
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
# Keychain 认证信息 - authclaude.rb 中 configure_keychain 设置的
|
143
|
+
if RUBY_PLATFORM.include?('darwin')
|
144
|
+
keychain_entries = get_claude_keychain_entries
|
145
|
+
keychain_entries.each do |service_name|
|
146
|
+
puts " • macOS Keychain: #{service_name} (Claude认证信息)"
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
puts
|
151
|
+
end
|
152
|
+
|
153
|
+
def preview_gemini_cleanup
|
154
|
+
puts "📌 Gemini 配置:"
|
155
|
+
|
156
|
+
# 本地配置文件
|
157
|
+
config_file = File.expand_path('~/.easyai/config.yml')
|
158
|
+
if File.exist?(config_file)
|
159
|
+
puts " • 本地配置文件: #{config_file} (gemini_token)"
|
160
|
+
end
|
161
|
+
|
162
|
+
puts
|
163
|
+
end
|
164
|
+
|
165
|
+
def preview_openai_cleanup
|
166
|
+
puts "📌 OpenAI 配置:"
|
167
|
+
|
168
|
+
# 本地配置文件
|
169
|
+
config_file = File.expand_path('~/.easyai/config.yml')
|
170
|
+
if File.exist?(config_file)
|
171
|
+
puts " • 本地配置文件: #{config_file} (openai_token)"
|
172
|
+
end
|
173
|
+
|
174
|
+
puts
|
175
|
+
end
|
176
|
+
|
177
|
+
def clean_claude
|
178
|
+
puts "🧹 清理Claude配置..."
|
179
|
+
|
180
|
+
# 清理本地配置文件中的Claude令牌
|
181
|
+
clean_local_config('claude_token')
|
182
|
+
|
183
|
+
# 清理 ~/.claude.json
|
184
|
+
clean_claude_json
|
185
|
+
|
186
|
+
# 清理Shell配置文件
|
187
|
+
clean_shell_config_claude
|
188
|
+
|
189
|
+
# 清理Keychain
|
190
|
+
clean_keychain_claude
|
191
|
+
end
|
192
|
+
|
193
|
+
def clean_gemini
|
194
|
+
puts "🧹 清理Gemini配置..."
|
195
|
+
clean_local_config('gemini_token')
|
196
|
+
end
|
197
|
+
|
198
|
+
def clean_openai
|
199
|
+
puts "🧹 清理OpenAI配置..."
|
200
|
+
clean_local_config('openai_token')
|
201
|
+
end
|
202
|
+
|
203
|
+
def clean_local_config(key)
|
204
|
+
config_file = File.expand_path('~/.easyai/config.yml')
|
205
|
+
return unless File.exist?(config_file)
|
206
|
+
|
207
|
+
begin
|
208
|
+
config = YAML.load_file(config_file) || {}
|
209
|
+
if config.key?(key)
|
210
|
+
config.delete(key)
|
211
|
+
File.write(config_file, config.to_yaml)
|
212
|
+
puts " ✓ 已清理本地配置文件中的 #{key}"
|
213
|
+
|
214
|
+
# 如果配置文件为空,删除整个配置目录
|
215
|
+
if config.empty?
|
216
|
+
config_dir = File.dirname(config_file)
|
217
|
+
FileUtils.rm_rf(config_dir)
|
218
|
+
puts " ✓ 已删除空配置目录: #{config_dir}"
|
219
|
+
end
|
220
|
+
end
|
221
|
+
rescue => e
|
222
|
+
puts " ✗ 清理本地配置失败: #{e.message}"
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
def clean_claude_json
|
227
|
+
claude_file = File.expand_path('~/.claude.json')
|
228
|
+
return unless File.exist?(claude_file)
|
229
|
+
|
230
|
+
begin
|
231
|
+
# 不是完全删除文件,而是恢复到只包含默认字段的状态
|
232
|
+
# 因为 Claude Code 会自动重新创建基本配置
|
233
|
+
content = JSON.parse(File.read(claude_file))
|
234
|
+
|
235
|
+
# 保留默认字段,删除由 authclaude.rb update_claude_json 方法添加的配置
|
236
|
+
default_fields = %w[installMethod autoUpdates userID fallbackAvailableWarningThreshold projects tipsHistory]
|
237
|
+
cleaned_content = content.select { |key, _| default_fields.include?(key) }
|
238
|
+
|
239
|
+
if cleaned_content != content
|
240
|
+
File.write(claude_file, JSON.pretty_generate(cleaned_content))
|
241
|
+
puts " ✓ 已清理Claude配置文件中的远程配置内容: #{claude_file}"
|
242
|
+
end
|
243
|
+
rescue JSON::ParserError => e
|
244
|
+
puts " ✗ 解析Claude配置文件失败: #{e.message}"
|
245
|
+
rescue => e
|
246
|
+
puts " ✗ 清理Claude配置文件失败: #{e.message}"
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
def clean_shell_config_claude
|
251
|
+
shell_files = get_shell_files
|
252
|
+
|
253
|
+
shell_files.each do |shell_file|
|
254
|
+
next unless File.exist?(shell_file)
|
255
|
+
|
256
|
+
begin
|
257
|
+
content = File.read(shell_file)
|
258
|
+
original_content = content.dup
|
259
|
+
lines = content.split("\n", -1)
|
260
|
+
lines.pop if lines.last == ""
|
261
|
+
|
262
|
+
# 只清理由 authclaude.rb 添加的代理配置(claude_proxy 和 unclaude_proxy)
|
263
|
+
# 不清理 CLAUDE_CODE_OAUTH_TOKEN,因为那不是 authclaude.rb 设置的
|
264
|
+
lines = remove_claude_proxy_config(lines, shell_file)
|
265
|
+
|
266
|
+
if lines.join("\n") + "\n" != original_content
|
267
|
+
File.write(shell_file, lines.join("\n") + "\n")
|
268
|
+
puts " ✓ 已清理Shell代理配置: #{shell_file}"
|
269
|
+
end
|
270
|
+
rescue => e
|
271
|
+
puts " ✗ 清理Shell配置失败 #{shell_file}: #{e.message}"
|
272
|
+
end
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
def clean_keychain_claude
|
277
|
+
return unless RUBY_PLATFORM.include?('darwin')
|
278
|
+
|
279
|
+
account_name = Etc.getlogin || ENV['USER'] || ENV['LOGNAME']
|
280
|
+
keychain_entries = get_claude_keychain_entries
|
281
|
+
|
282
|
+
if keychain_entries.empty?
|
283
|
+
# 检查默认的 Claude Code-credentials
|
284
|
+
service_name = "Claude Code-credentials"
|
285
|
+
cmd = ["security", "delete-generic-password", "-a", account_name, "-s", service_name]
|
286
|
+
stdout, stderr, status = Open3.capture3(*cmd)
|
287
|
+
|
288
|
+
if status.success?
|
289
|
+
puts " ✓ 已清理Keychain: #{service_name}"
|
290
|
+
elsif !stderr.include?("could not be found")
|
291
|
+
puts " ✗ 清理Keychain失败: #{stderr}"
|
292
|
+
end
|
293
|
+
else
|
294
|
+
# 清理所有找到的 Claude 相关条目
|
295
|
+
keychain_entries.each do |service_name|
|
296
|
+
cmd = ["security", "delete-generic-password", "-a", account_name, "-s", service_name]
|
297
|
+
stdout, stderr, status = Open3.capture3(*cmd)
|
298
|
+
|
299
|
+
if status.success?
|
300
|
+
puts " ✓ 已清理Keychain: #{service_name}"
|
301
|
+
elsif !stderr.include?("could not be found")
|
302
|
+
puts " ✗ 清理Keychain失败 #{service_name}: #{stderr}"
|
303
|
+
end
|
304
|
+
end
|
305
|
+
end
|
306
|
+
end
|
307
|
+
|
308
|
+
def has_claude_json_config?(claude_file)
|
309
|
+
return false unless File.exist?(claude_file)
|
310
|
+
|
311
|
+
begin
|
312
|
+
content = JSON.parse(File.read(claude_file))
|
313
|
+
# 检查是否有非默认的配置项(排除 Claude Code 自动生成的默认字段)
|
314
|
+
default_fields = %w[installMethod autoUpdates userID fallbackAvailableWarningThreshold projects tipsHistory]
|
315
|
+
has_custom_config = content.keys.any? { |key| !default_fields.include?(key) }
|
316
|
+
return has_custom_config
|
317
|
+
rescue JSON::ParserError, StandardError
|
318
|
+
return false
|
319
|
+
end
|
320
|
+
end
|
321
|
+
|
322
|
+
def has_claude_proxy_config?(shell_file)
|
323
|
+
return false unless File.exist?(shell_file)
|
324
|
+
|
325
|
+
content = File.read(shell_file)
|
326
|
+
content.include?('claude_proxy') || content.include?('unclaude_proxy')
|
327
|
+
end
|
328
|
+
|
329
|
+
def get_claude_keychain_entries
|
330
|
+
return [] unless RUBY_PLATFORM.include?('darwin')
|
331
|
+
|
332
|
+
entries = []
|
333
|
+
|
334
|
+
# 检查各种可能的 Claude 相关 keychain 条目
|
335
|
+
possible_services = [
|
336
|
+
"Claude Code-credentials",
|
337
|
+
# 从 authclaude.rb 中的 configure_keychain 方法可以看到,service_name 来自配置
|
338
|
+
# 但我们不知道具体的服务名,所以需要通过搜索来查找
|
339
|
+
]
|
340
|
+
|
341
|
+
account_name = Etc.getlogin || ENV['USER'] || ENV['LOGNAME']
|
342
|
+
|
343
|
+
# 搜索所有包含 "claude" 的 keychain 条目
|
344
|
+
search_cmd = ["security", "dump-keychain", "-d"]
|
345
|
+
|
346
|
+
begin
|
347
|
+
stdout, stderr, status = Open3.capture3(*search_cmd)
|
348
|
+
if status.success?
|
349
|
+
# 解析输出,查找 Claude 相关条目
|
350
|
+
stdout.lines.each do |line|
|
351
|
+
if line.include?("claude") && line.include?("svce")
|
352
|
+
# 提取服务名称
|
353
|
+
match = line.match(/"([^"]*claude[^"]*)"/)
|
354
|
+
entries << match[1] if match
|
355
|
+
end
|
356
|
+
end
|
357
|
+
end
|
358
|
+
rescue => e
|
359
|
+
# 如果搜索失败,回退到检查已知的服务名
|
360
|
+
possible_services.each do |service_name|
|
361
|
+
cmd = ["security", "find-generic-password", "-a", account_name, "-s", service_name]
|
362
|
+
if system(*cmd, out: File::NULL, err: File::NULL)
|
363
|
+
entries << service_name
|
364
|
+
end
|
365
|
+
end
|
366
|
+
end
|
367
|
+
|
368
|
+
entries.uniq
|
369
|
+
end
|
370
|
+
|
371
|
+
def get_shell_files
|
372
|
+
shell_files = []
|
373
|
+
current_shell = ENV['SHELL']&.split('/')&.last || 'bash'
|
374
|
+
|
375
|
+
case current_shell
|
376
|
+
when 'zsh'
|
377
|
+
shell_files << File.expand_path("~/.zshrc")
|
378
|
+
when 'bash'
|
379
|
+
bash_profile = File.expand_path("~/.bash_profile")
|
380
|
+
bashrc = File.expand_path("~/.bashrc")
|
381
|
+
shell_files << (File.exist?(bash_profile) ? bash_profile : bashrc)
|
382
|
+
when 'fish'
|
383
|
+
fish_config = File.expand_path("~/.config/fish/config.fish")
|
384
|
+
shell_files << fish_config
|
385
|
+
else
|
386
|
+
shell_files << File.expand_path("~/.profile")
|
387
|
+
end
|
388
|
+
|
389
|
+
# 总是添加 ~/.profile 作为备份
|
390
|
+
profile_file = File.expand_path("~/.profile")
|
391
|
+
shell_files << profile_file unless shell_files.include?(profile_file)
|
392
|
+
|
393
|
+
shell_files
|
394
|
+
end
|
395
|
+
|
396
|
+
def remove_claude_proxy_config(lines, shell_file)
|
397
|
+
# 只清理 claude_proxy 和 unclaude_proxy 相关配置
|
398
|
+
# 这是 authclaude.rb 中 configure_proxy 方法添加的内容
|
399
|
+
if shell_file.include?("config.fish")
|
400
|
+
# Fish shell - 移除代理相关的别名/函数
|
401
|
+
lines.reject! { |line|
|
402
|
+
line.match(/^alias\s+claude_proxy=/) ||
|
403
|
+
line.match(/^alias\s+unclaude_proxy=/) ||
|
404
|
+
line.match(/^function\s+(claude_proxy|unclaude_proxy)/)
|
405
|
+
}
|
406
|
+
|
407
|
+
# 移除函数块
|
408
|
+
in_function = false
|
409
|
+
lines.select do |line|
|
410
|
+
if line.match(/^function\s+(claude_proxy|unclaude_proxy)/)
|
411
|
+
in_function = true
|
412
|
+
false
|
413
|
+
elsif in_function && line.strip == "end"
|
414
|
+
in_function = false
|
415
|
+
false
|
416
|
+
elsif in_function
|
417
|
+
false
|
418
|
+
else
|
419
|
+
true
|
420
|
+
end
|
421
|
+
end
|
422
|
+
else
|
423
|
+
# Bash/zsh - 移除代理相关的别名/函数
|
424
|
+
lines.reject! { |line|
|
425
|
+
line.match(/^alias\s+(claude_proxy|unclaude_proxy)=/) ||
|
426
|
+
line.match(/^(claude_proxy|unclaude_proxy)\(\)/)
|
427
|
+
}
|
428
|
+
|
429
|
+
# 移除函数块
|
430
|
+
in_function = false
|
431
|
+
function_depth = 0
|
432
|
+
lines.select do |line|
|
433
|
+
if line.match(/^(claude_proxy|unclaude_proxy)\(\)/)
|
434
|
+
in_function = true
|
435
|
+
function_depth = 0
|
436
|
+
false
|
437
|
+
elsif in_function
|
438
|
+
function_depth += 1 if line.include?("{")
|
439
|
+
if line.include?("}")
|
440
|
+
function_depth -= 1
|
441
|
+
in_function = false if function_depth <= 0
|
442
|
+
end
|
443
|
+
false
|
444
|
+
else
|
445
|
+
true
|
446
|
+
end
|
447
|
+
end
|
448
|
+
end
|
449
|
+
end
|
450
|
+
|
451
|
+
end
|
452
|
+
end
|
453
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module EasyAI
|
2
|
+
class Command
|
3
|
+
class Gemini < Command
|
4
|
+
self.summary = '运行 Gemini CLI'
|
5
|
+
self.description = <<-DESC
|
6
|
+
启动 Google Gemini CLI 工具。
|
7
|
+
|
8
|
+
主要功能:
|
9
|
+
|
10
|
+
* 自动配置 API 密钥
|
11
|
+
|
12
|
+
* 支持交互式对话
|
13
|
+
|
14
|
+
* 透传所有参数
|
15
|
+
|
16
|
+
使用示例:
|
17
|
+
|
18
|
+
$ easyai gemini # 启动交互式 Gemini
|
19
|
+
|
20
|
+
$ easyai gemini chat # 开始聊天会话
|
21
|
+
|
22
|
+
$ easyai gemini --help # 查看 Gemini 帮助
|
23
|
+
DESC
|
24
|
+
|
25
|
+
def initialize(argv)
|
26
|
+
super
|
27
|
+
@gemini_args = @argv.remainder!
|
28
|
+
end
|
29
|
+
|
30
|
+
def validate!
|
31
|
+
super
|
32
|
+
help! '未找到 Gemini CLI。请安装:npm install -g @google/gemini-cli' unless gemini_available?
|
33
|
+
end
|
34
|
+
|
35
|
+
def run
|
36
|
+
# 直接使用环境变量,不再依赖配置文件
|
37
|
+
env = ENV.to_h
|
38
|
+
|
39
|
+
# 如果环境变量中已经设置了 API KEY,直接使用
|
40
|
+
# 用户可以通过 export GOOGLE_API_KEY=xxx 来设置
|
41
|
+
|
42
|
+
puts "正在运行: gemini #{@gemini_args.join(' ')}".blue if verbose?
|
43
|
+
exec(env, 'gemini', *@gemini_args)
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
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
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module EasyAI
|
2
|
+
class Command
|
3
|
+
class GPT < Command
|
4
|
+
self.summary = '运行 OpenAI CLI'
|
5
|
+
self.description = <<-DESC
|
6
|
+
启动 OpenAI GPT CLI 工具。
|
7
|
+
|
8
|
+
主要功能:
|
9
|
+
|
10
|
+
* 自动配置 API 密钥
|
11
|
+
|
12
|
+
* 支持 API 调用
|
13
|
+
|
14
|
+
* 透传所有参数
|
15
|
+
|
16
|
+
使用示例:
|
17
|
+
|
18
|
+
$ easyai gpt # 启动 OpenAI CLI
|
19
|
+
|
20
|
+
$ easyai gpt api chat.completions # 调用 Chat API
|
21
|
+
|
22
|
+
$ easyai gpt --help # 查看 OpenAI 帮助
|
23
|
+
DESC
|
24
|
+
|
25
|
+
def initialize(argv)
|
26
|
+
super
|
27
|
+
@gpt_args = @argv.remainder!
|
28
|
+
end
|
29
|
+
|
30
|
+
def validate!
|
31
|
+
super
|
32
|
+
help! '未找到 OpenAI CLI。请安装:pip install openai' unless gpt_available?
|
33
|
+
end
|
34
|
+
|
35
|
+
def run
|
36
|
+
# 直接使用环境变量,不再依赖配置文件
|
37
|
+
env = ENV.to_h
|
38
|
+
|
39
|
+
# 如果环境变量中已经设置了 API KEY,直接使用
|
40
|
+
# 用户可以通过 export OPENAI_API_KEY=xxx 来设置
|
41
|
+
|
42
|
+
puts "正在运行: openai #{@gpt_args.join(' ')}".blue if verbose?
|
43
|
+
exec(env, 'openai', *@gpt_args)
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def gpt_available?
|
49
|
+
# 跨平台命令检测
|
50
|
+
if RUBY_PLATFORM =~ /mswin|mingw|cygwin/
|
51
|
+
system('where openai >nul 2>&1')
|
52
|
+
else
|
53
|
+
system('which openai > /dev/null 2>&1')
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|