easyai 1.0.2 → 1.0.4

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.
@@ -0,0 +1,278 @@
1
+ require_relative '../config/config'
2
+ require_relative '../auth/authclaude'
3
+ require_relative '../base/update_notifier'
4
+
5
+ module EasyAI
6
+ class Command
7
+ class Claude < Command
8
+ self.summary = '运行 Claude CLI'
9
+ self.description = <<-DESC
10
+ 启动 Anthropic Claude CLI 工具。
11
+
12
+ 主要功能:
13
+
14
+ * 支持远程配置下载
15
+
16
+ * 自动配置环境变量
17
+
18
+ * 支持本地配置文件
19
+
20
+ 使用示例:
21
+
22
+ $ easyai claude # 使用默认配置启动
23
+
24
+ $ easyai claude ./config.json # 使用本地配置文件
25
+
26
+ $ easyai claude --verbose # 显示详细信息
27
+
28
+ $ easyai claude --no-keychain # 禁用密码存储
29
+ DESC
30
+
31
+ def self.options
32
+ [
33
+ ['--no-keychain', '禁用自动密码存储'],
34
+ ['--verbose', '显示详细信息(包括完整的代理和令牌)']
35
+ ].concat(super)
36
+ end
37
+
38
+ def initialize(argv)
39
+ @no_keychain = argv.flag?('no-keychain')
40
+ @verbose_mode = argv.flag?('verbose')
41
+
42
+ super
43
+
44
+ # 获取剩余参数
45
+ remaining_args = @argv.remainder!
46
+
47
+ # 检查第一个参数是否是配置文件
48
+ if remaining_args.first && File.exist?(remaining_args.first) && remaining_args.first.end_with?('.json')
49
+ @config_file = remaining_args.shift
50
+ end
51
+
52
+ # 剩余的参数传递给 claude
53
+ @claude_args = remaining_args
54
+ end
55
+
56
+ def validate!
57
+ super
58
+ help! '未找到 Claude CLI。请安装:npm install -g @anthropic-ai/claude-code' unless claude_available?
59
+ end
60
+
61
+ def run
62
+ begin
63
+ # 在非verbose模式下显示简洁的更新提醒
64
+ Base::UpdateNotifier.maybe_show_notification unless @verbose_mode
65
+
66
+ # 美化的启动信息
67
+ print_header
68
+
69
+ # 首先尝试从远程下载配置
70
+ remote_config = nil
71
+
72
+ if @config_file
73
+ # 使用指定的本地配置文件
74
+ print_status("📁 使用本地配置", File.basename(@config_file))
75
+ remote_config = load_local_config(@config_file)
76
+ print_success("配置加载成功") if remote_config
77
+ else
78
+ # 从远程下载配置,传递选项
79
+ print_status("🔄 获取远程配置", "默认用户")
80
+ options = { no_keychain: @no_keychain }
81
+ remote_config = ConfigManager.download_user_config(nil, options)
82
+ print_success("配置加载成功") if remote_config
83
+ end
84
+
85
+ # 如果远程配置获取失败,回退到本地配置
86
+ if remote_config.nil?
87
+ print_warning("使用本地配置")
88
+ remote_config = load_local_yaml_config
89
+
90
+ # 如果本地配置也为空,提示用户先进行设置
91
+ if remote_config.empty?
92
+ print_error("未找到有效配置")
93
+ puts " 请先运行: #{'easyai --setup'.yellow}"
94
+ exit 1
95
+ end
96
+ end
97
+
98
+ # 使用 Auth::AuthClaude 模块配置认证
99
+ print_status("🔐 配置认证", "Claude OAuth")
100
+ unless Auth::AuthClaude.configure(remote_config, nil)
101
+ print_error("配置认证失败")
102
+ exit 1
103
+ end
104
+ print_success("认证配置完成")
105
+
106
+ # 构建环境变量
107
+ env = build_environment(remote_config)
108
+
109
+ # 导出环境变量到当前进程(不包括 CLAUDE_CODE_OAUTH_TOKEN)
110
+ export_environment_variables(env)
111
+
112
+ # 打印分隔线
113
+ puts "─" * 60
114
+ puts "🚀 #{'Claude CLI 已启动'.green}"
115
+ puts "─" * 60
116
+ puts
117
+
118
+ # 启动 Claude
119
+ exec(env, 'claude', *@claude_args)
120
+
121
+ rescue => e
122
+ print_error("Claude 命令执行失败: #{e.message}")
123
+ puts " 请检查配置文件和网络连接"
124
+ puts " 错误详情: #{e.class.name}" if @verbose_mode
125
+ exit 1
126
+ end
127
+ end
128
+
129
+ private
130
+
131
+ def print_header
132
+ puts "\n╔#{'═' * 58}╗"
133
+ puts "║#{'EasyAI Claude CLI 启动器'.center(58)}║"
134
+ puts "╚#{'═' * 58}╝\n"
135
+ end
136
+
137
+ def print_status(icon_text, detail = nil)
138
+ if detail
139
+ puts "#{icon_text.ljust(20)} #{detail.cyan}"
140
+ else
141
+ puts icon_text
142
+ end
143
+ end
144
+
145
+ def print_success(message)
146
+ puts " ✓ #{message.green}"
147
+ end
148
+
149
+ def print_warning(message)
150
+ puts " ⚠ #{message.yellow}"
151
+ end
152
+
153
+ def print_error(message)
154
+ puts " ✗ #{message.red}"
155
+ end
156
+
157
+ def claude_available?
158
+ # 跨平台命令检测
159
+ if RUBY_PLATFORM =~ /mswin|mingw|cygwin/
160
+ system('where claude >nul 2>&1')
161
+ else
162
+ system('which claude > /dev/null 2>&1')
163
+ end
164
+ end
165
+
166
+ def load_local_config(config_path)
167
+ return nil unless File.exist?(config_path)
168
+
169
+ begin
170
+ config_content = File.read(config_path)
171
+ JSON.parse(config_content)
172
+ rescue JSON::ParserError => e
173
+ puts "解析本地配置文件失败: #{e.message}".red
174
+ nil
175
+ rescue => e
176
+ puts "读取本地配置文件失败: #{e.message}".red
177
+ nil
178
+ end
179
+ end
180
+
181
+ def load_local_yaml_config
182
+ # 不再使用本地YAML配置文件,返回空配置
183
+ # claude命令主要依赖远程配置或用户指定的JSON配置文件
184
+ {}
185
+ end
186
+
187
+ def build_environment(config)
188
+ env = ENV.to_h
189
+
190
+ puts "\n📋 环境配置:" if @verbose_mode
191
+ puts "─" * 60 if @verbose_mode
192
+
193
+ # 从配置中提取环境变量 - 只设置 CLAUDE_CODE_OAUTH_TOKEN
194
+ if config && config['env'] && config['env']['CLAUDE_CODE_OAUTH_TOKEN']
195
+ env['CLAUDE_CODE_OAUTH_TOKEN'] = config['env']['CLAUDE_CODE_OAUTH_TOKEN']
196
+
197
+ if @verbose_mode
198
+ # verbose 模式:显示完整的令牌和代理
199
+ puts "\n🔑 CLAUDE_CODE_OAUTH_TOKEN:"
200
+ puts " #{config['env']['CLAUDE_CODE_OAUTH_TOKEN'].green}"
201
+ else
202
+ # 普通模式:只显示令牌预览
203
+ token_preview = config['env']['CLAUDE_CODE_OAUTH_TOKEN'].length > 15 ?
204
+ "#{config['env']['CLAUDE_CODE_OAUTH_TOKEN'][0..15]}..." :
205
+ config['env']['CLAUDE_CODE_OAUTH_TOKEN']
206
+ print_status("🔑 令牌已配置", token_preview)
207
+ end
208
+ end
209
+
210
+ # 从代理配置中提取代理设置
211
+ if config && config['claude_proxy']
212
+ proxy_urls = []
213
+ if config['claude_proxy']['HTTP_PROXY']
214
+ env['HTTP_PROXY'] = config['claude_proxy']['HTTP_PROXY']
215
+ env['http_proxy'] = config['claude_proxy']['HTTP_PROXY']
216
+ proxy_urls << config['claude_proxy']['HTTP_PROXY']
217
+ end
218
+ if config['claude_proxy']['HTTPS_PROXY'] && config['claude_proxy']['HTTPS_PROXY'] != config['claude_proxy']['HTTP_PROXY']
219
+ env['HTTPS_PROXY'] = config['claude_proxy']['HTTPS_PROXY']
220
+ env['https_proxy'] = config['claude_proxy']['HTTPS_PROXY']
221
+ proxy_urls << config['claude_proxy']['HTTPS_PROXY']
222
+ elsif config['claude_proxy']['HTTPS_PROXY']
223
+ env['HTTPS_PROXY'] = config['claude_proxy']['HTTPS_PROXY']
224
+ env['https_proxy'] = config['claude_proxy']['HTTPS_PROXY']
225
+ end
226
+
227
+ if proxy_urls.any?
228
+ if @verbose_mode
229
+ # verbose 模式:显示完整的代理地址
230
+ puts "\n🔗 代理配置:"
231
+ proxy_urls.uniq.each do |url|
232
+ puts " #{url.green}"
233
+ end
234
+ else
235
+ # 普通模式:简化代理显示,隐藏密码
236
+ simplified_proxies = proxy_urls.uniq.map do |url|
237
+ uri = URI(url) rescue nil
238
+ if uri && uri.password
239
+ "#{uri.scheme}://***@#{uri.host}:#{uri.port}"
240
+ else
241
+ url
242
+ end
243
+ end
244
+ print_status("🌐 代理已配置", simplified_proxies.join(', '))
245
+ end
246
+ end
247
+ end
248
+
249
+ puts "─" * 60 if @verbose_mode
250
+ puts if @verbose_mode
251
+
252
+ env
253
+ end
254
+
255
+
256
+ def export_environment_variables(env)
257
+ # 导出 CLAUDE_CODE_OAUTH_TOKEN(仅在内存中,不写入文件)
258
+ if env['CLAUDE_CODE_OAUTH_TOKEN']
259
+ ENV['CLAUDE_CODE_OAUTH_TOKEN'] = env['CLAUDE_CODE_OAUTH_TOKEN']
260
+ print_success("令牌已设置到环境变量") if @verbose_mode
261
+ end
262
+
263
+ # 导出代理相关环境变量
264
+ proxy_vars = ['HTTP_PROXY', 'HTTPS_PROXY', 'http_proxy', 'https_proxy']
265
+ proxy_exported = false
266
+ proxy_vars.each do |var|
267
+ if env[var]
268
+ ENV[var] = env[var]
269
+ proxy_exported = true
270
+ end
271
+ end
272
+ print_success("代理已设置到环境变量") if proxy_exported && @verbose_mode
273
+ end
274
+
275
+
276
+ end
277
+ end
278
+ end