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