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.
@@ -1,451 +1,146 @@
1
- require 'json'
2
- require 'yaml'
1
+ # frozen_string_literal: true
2
+
3
3
  require 'fileutils'
4
- require 'etc'
5
- require 'open3'
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
- 清理指定AI工具的所有配置信息。
14
-
15
- 支持清理:
19
+ 清理 claude / gemini / codex 各自在本地产生的缓存目录与配置文件。
16
20
 
17
- * Claude 相关配置
21
+ 支持范围:
22
+ * claude → ~/.claude、~/.claude.json、~/.config/claude
23
+ * gemini → ~/.gemini、~/.config/gemini
24
+ * codex → ~/.codex、~/.config/codex
25
+ * all → 上述全部
18
26
 
19
- * Gemini 相关配置
27
+ 使用示例:
20
28
 
21
- * OpenAI 相关配置
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
- $ easyai clean # 清理Claude配置(默认)
37
+ $ easyai setup --reset
38
+ DESC
26
39
 
27
- $ easyai clean gemini # 清理Gemini配置
40
+ VALID_TOOLS = %w[claude gemini codex all].freeze
28
41
 
29
- $ easyai clean openai # 清理OpenAI配置
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
- $ easyai clean all # 清理所有配置
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
- @tool_name = argv.shift_argument || 'claude'
43
- @force = argv.flag?('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
- valid_tools = %w[claude gemini openai all]
51
- unless valid_tools.include?(@tool_name.downcase)
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
- puts "🧹 准备清理 #{@tool_name == 'all' ? '所有AI工具' : @tool_name.upcase} 的配置信息"
59
- puts
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
- private
86
-
87
- def preview_cleanup
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
- def preview_openai_cleanup
167
- puts "📌 OpenAI 配置:"
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
- def clean_openai
200
- puts "🧹 清理OpenAI配置..."
201
- clean_local_config('openai_token')
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
- def clean_shell_config_claude
252
- shell_files = get_shell_files
253
-
254
- shell_files.each do |shell_file|
255
- next unless File.exist?(shell_file)
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
- def clean_keychain_claude
278
- return unless Base::SystemInfo.macos?
99
+ failures = perform_delete(existing)
279
100
 
280
- account_name = Base::SystemInfo.current_user
281
- keychain_entries = get_claude_keychain_entries
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
- # 清理所有找到的 Claude 相关条目
296
- keychain_entries.each do |service_name|
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
- def has_claude_json_config?(claude_file)
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
- def has_claude_proxy_config?(shell_file)
324
- return false unless File.exist?(shell_file)
325
-
326
- content = File.read(shell_file)
327
- content.include?('claude_proxy') || content.include?('unclaude_proxy')
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
- def get_claude_keychain_entries
331
- return [] unless Base::SystemInfo.macos?
332
-
333
- entries = []
334
- account_name = Base::SystemInfo.current_user
335
-
336
- # 定义所有可能的 Claude 相关服务名
337
- # 基于 authclaude.rb 和常见的 Claude 应用服务名
338
- possible_services = [
339
- "Claude Code-credentials",
340
- "Claude Code",
341
- "Claude-API",
342
- "claude-api",
343
- "claude-credentials",
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 remove_claude_proxy_config(lines, shell_file)
395
- # 只清理 claude_proxy unclaude_proxy 相关配置
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
- require_relative '../base/system_info'
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 < Command
6
- self.summary = '运行 Gemini CLI'
7
+ class Gemini < AIToolBase
8
+ self.summary = '启动 Gemini 命令行(多平台支持)'
7
9
  self.description = <<-DESC
8
- 启动 Google Gemini CLI 工具。
9
-
10
- 主要功能:
11
-
12
- * 自动配置 API 密钥
13
-
14
- * 支持交互式对话
15
-
16
- * 透传所有参数
10
+ 启动 Google Gemini CLI,从 ~/.easyai/config.json 读取平台配置,并将凭证 / 代理仅注入子进程。
17
11
 
18
- 使用示例:
12
+ 使用示例:
19
13
 
20
- $ easyai gemini # 启动交互式 Gemini
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
- $ easyai gemini chat # 开始聊天会话
20
+ self.arguments = [
21
+ CLAide::Argument.new('CONFIG.json', false),
22
+ CLAide::Argument.new('ARGS', false, true)
23
+ ]
23
24
 
24
- $ easyai gemini --help # 查看 Gemini 帮助
25
- DESC
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 run
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
- private
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