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,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
|