easyai 1.4.0 → 1.5.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 +40 -0
- data/CLAUDE.md +1 -1
- data/lib/easyai/auth/authclaude.rb +4 -50
- data/lib/easyai/base/system_info.rb +255 -0
- data/lib/easyai/base/system_keychain.rb +6 -15
- data/lib/easyai/command/claude.rb +85 -2
- data/lib/easyai/command/clean.rb +23 -58
- data/lib/easyai/command/gemini.rb +4 -2
- data/lib/easyai/command/gpt.rb +3 -2
- data/lib/easyai/command/utils/export.rb +4 -3
- data/lib/easyai/config/config.rb +33 -28
- data/lib/easyai/version.rb +1 -1
- data/lib/easyai.rb +1 -1
- metadata +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3be8a56581e7cb06127691c7f1b3d19c28d038f8b5744b69af93a937cafa6f0a
|
4
|
+
data.tar.gz: a73f942ea6819a885b0e65ef176f18c97c4a550bb9b11e1c3ad98b997ed0b5a2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1eefce32542ce00cd0bf52776c66ee2d694080b72634924ec73e8ede5b2fee3be07159f06d5a7176a0fd74837d3966585602f7cbf9708496bba383e4823028f5
|
7
|
+
data.tar.gz: 0066ffe5863fcc9d4198bbf0e85d7719a4567c9b13dcb5b0129949541f504d3455a8ac42a4fcf84793e995a89366486321cbbec93d105fb038da75745a65fd05
|
data/AGENTS.md
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
# Repository Guidelines
|
2
|
+
|
3
|
+
## 项目结构与模块组织
|
4
|
+
- Ruby 源码位于 `lib/` 根目录,核心入口在 `lib/easyai.rb`,基础能力集中于 `lib/easyai/base`,命令定义在 `lib/easyai/command` 及其子目录(如 `claude.rb`、`gemini.rb`)。
|
5
|
+
- 配置读取逻辑保存在 `lib/easyai/config`,认证与第三方服务脚本位于 `lib/easyai/auth`; 版本号定义于 `lib/easyai/version.rb` 并由 `easyai.gemspec` 消费。
|
6
|
+
- CLI 启动脚本是 `bin/easyai`;打包产物默认输出在项目根目录。辅助脚本 `test_local.sh` 和 `release_remote.sh` 承担本地测试与正式发布,请保留可执行权限并在修改后记录变更。
|
7
|
+
- 未来添加测试文件时,统一放在 `spec/` 目录,可按模块建立 `spec/command`、`spec/base` 等子目录,以便快速定位;示例数据建议放入 `spec/support/fixtures`。
|
8
|
+
|
9
|
+
## 架构概览
|
10
|
+
- `EasyAI::EasyAIApp` 负责命令调度、启动横幅与版本检查,所有 CLI 子命令最终由 `EasyAI::Command` 树处理。
|
11
|
+
- 版本检测与更新提示依赖 `Base::VersionChecker` 和 `Base::UpdateNotifier`,与网络交互相关的逻辑集中于这些模块,方便替换或打桩测试。
|
12
|
+
- 新增功能时优先扩展现有命令类;若需共享逻辑,请在 `lib/easyai/base` 内创建独立模块,避免在命令类中堆积杂项。
|
13
|
+
|
14
|
+
## 构建、测试与开发命令
|
15
|
+
- `bundle install`:安装 Gemfile 中的开发依赖(Rake、RSpec 等),首次克隆或更新依赖后必须执行。
|
16
|
+
- `bundle exec rspec`:运行整个测试套件;可通过 `bundle exec rspec spec/command/claude_spec.rb` 定位单个文件调试,或使用 `--format documentation` 获取更详细输出。
|
17
|
+
- `bundle exec rake build` 或 `gem build easyai.gemspec`:构建 gem 包,常用于发布前的本地验证;`bundle exec rake install` 可直接将构建产物安装到本地。
|
18
|
+
- `./test_local.sh`:自动执行打包并安装到本地 Ruby 环境,适合冒烟回归;`./release_remote.sh` 覆盖提交流程、分支同步和 `gem push`,仅在确认要发布时运行,并保证当前分支已同步远程。
|
19
|
+
|
20
|
+
## 语言与风格规范
|
21
|
+
- 本项目要求所有代码、注释、提交信息与文档统一使用中文,PR 描述与讨论亦应遵循;如必须引用英文原文,请附中文解释,确保沟通无障碍。
|
22
|
+
- 采用 Ruby 标准两空格缩进,方法与变量命名使用 `snake_case`;常量使用 `SCREAMING_SNAKE_CASE`,模块或类名使用驼峰式。
|
23
|
+
- CLI 提示语保持简洁友好,若涉及多语言输出,应保证中文为默认选项;共享样式请复用现有 `colored2` 渲染逻辑,避免重复实现颜色方案。
|
24
|
+
- 新增命令时,参照现有文件创建 `lib/easyai/command/<name>.rb`,并在 `lib/easyai.rb` 中显式 `require`,确保自动加载链条完整;必要时补充文档说明。
|
25
|
+
|
26
|
+
## 测试准则
|
27
|
+
- 使用 RSpec 进行行为驱动测试,文件命名为 `<feature>_spec.rb`;`describe` 中强调用户视角,例如 CLI 参数解析或远程更新提示。
|
28
|
+
- 对加密、配置清理等高风险逻辑,建立 fixture(建议放在 `spec/support/fixtures`)并测试成功与失败路径;必要时模拟外部命令退出码。
|
29
|
+
- 目标是保持修改行的单元测试覆盖率不下降;若引入重要分支逻辑,请添加针对性例子并在 PR 中说明覆盖情况。
|
30
|
+
- 提交前至少运行一次 `bundle exec rspec`;若修改了打包或权限脚本,请额外执行 `./test_local.sh` 验证安装流程,并记录输出结果。
|
31
|
+
|
32
|
+
## 提交与合并请求规范
|
33
|
+
- 提交消息遵循 `feat:`、`fix:`、`refactor:`、`res x.y.z` 等前缀,小写冒号后加空格,主题控制在 72 字符以内,正文补充动机与影响面。
|
34
|
+
- PR 需概述变更动机、列出涉及的命令或配置、附上测试结果(命令行输出或截图),并关联相关 Issue;若影响用户体验,请描述回滚方案。
|
35
|
+
- 发布版本需修改 `lib/easyai/version.rb` 与 `easyai.gemspec` 中的引用,并在说明中标记兼容性影响、所需依赖版本与升级指南;合入后同步更新 README。
|
36
|
+
|
37
|
+
## 安全与配置提示
|
38
|
+
- 禁止提交 API 密钥、用户凭证及本地配置文件;可通过 `.env` 或 `.gitignore` 排除的 JSON 进行本地调试,并在 README 或本指南注明所需环境变量与示例格式。
|
39
|
+
- 检查脚本中的 `chmod`、`chown`、`chgrp` 等命令,确认对目标机器安全;新增外部依赖(例如 npm、pip 包或系统二进制)时,请在 README 与本文件同步更新使用说明,并说明安装步骤与权限要求。
|
40
|
+
- 如需访问网络服务,请遵循最小权限原则,优先使用环境变量而非硬编码;在 Pull Request 中披露安全假设与可能的风险点。
|
data/CLAUDE.md
CHANGED
@@ -2,6 +2,7 @@ require 'json'
|
|
2
2
|
require 'etc'
|
3
3
|
require 'open3'
|
4
4
|
require 'fileutils'
|
5
|
+
require_relative '../base/system_info'
|
5
6
|
|
6
7
|
module EasyAI
|
7
8
|
module Auth
|
@@ -77,11 +78,11 @@ module EasyAI
|
|
77
78
|
|
78
79
|
# 辅助方法
|
79
80
|
def self.macos?
|
80
|
-
|
81
|
+
Base::SystemInfo.macos?
|
81
82
|
end
|
82
83
|
|
83
84
|
def self.current_user
|
84
|
-
|
85
|
+
Base::SystemInfo.current_user
|
85
86
|
end
|
86
87
|
|
87
88
|
def self.log_verbose(message)
|
@@ -360,54 +361,7 @@ module EasyAI
|
|
360
361
|
end
|
361
362
|
|
362
363
|
def self.get_shell_files
|
363
|
-
|
364
|
-
current_shell = ENV['SHELL']&.split('/')&.last || 'bash'
|
365
|
-
|
366
|
-
if Gem.win_platform?
|
367
|
-
# Windows 平台配置
|
368
|
-
powershell_profile = File.expand_path("~/Documents/WindowsPowerShell/Microsoft.PowerShell_profile.ps1")
|
369
|
-
powershell_core_profile = File.expand_path("~/Documents/PowerShell/Microsoft.PowerShell_profile.ps1")
|
370
|
-
|
371
|
-
# 检查哪个 PowerShell 配置文件存在或需要创建
|
372
|
-
if File.exist?(File.dirname(powershell_core_profile))
|
373
|
-
shell_files << powershell_core_profile
|
374
|
-
elsif File.exist?(File.dirname(powershell_profile))
|
375
|
-
shell_files << powershell_profile
|
376
|
-
else
|
377
|
-
# 默认使用 PowerShell Core 配置
|
378
|
-
ps_dir = File.dirname(powershell_core_profile)
|
379
|
-
FileUtils.mkdir_p(ps_dir) unless File.exist?(ps_dir)
|
380
|
-
shell_files << powershell_core_profile
|
381
|
-
end
|
382
|
-
else
|
383
|
-
# Unix/Linux/macOS 平台配置
|
384
|
-
case current_shell
|
385
|
-
when 'zsh'
|
386
|
-
shell_files << File.expand_path("~/.zshrc")
|
387
|
-
when 'bash'
|
388
|
-
bash_profile = File.expand_path("~/.bash_profile")
|
389
|
-
bashrc = File.expand_path("~/.bashrc")
|
390
|
-
shell_files << (File.exist?(bash_profile) ? bash_profile : bashrc)
|
391
|
-
when 'fish'
|
392
|
-
fish_config = File.expand_path("~/.config/fish/config.fish")
|
393
|
-
shell_files << fish_config
|
394
|
-
|
395
|
-
# 确保 fish 配置目录存在
|
396
|
-
fish_dir = File.dirname(fish_config)
|
397
|
-
unless File.exist?(fish_dir)
|
398
|
-
FileUtils.mkdir_p(fish_dir)
|
399
|
-
puts " 创建目录: #{fish_dir}"
|
400
|
-
end
|
401
|
-
else
|
402
|
-
shell_files << File.expand_path("~/.profile")
|
403
|
-
end
|
404
|
-
|
405
|
-
# 总是添加 ~/.profile 作为备份
|
406
|
-
profile_file = File.expand_path("~/.profile")
|
407
|
-
shell_files << profile_file unless shell_files.include?(profile_file)
|
408
|
-
end
|
409
|
-
|
410
|
-
shell_files
|
364
|
+
Base::SystemInfo.shell_config_files
|
411
365
|
end
|
412
366
|
|
413
367
|
def self.get_token_patterns(shell_file)
|
@@ -0,0 +1,255 @@
|
|
1
|
+
require 'rbconfig'
|
2
|
+
require 'etc'
|
3
|
+
|
4
|
+
module EasyAI
|
5
|
+
module Base
|
6
|
+
class SystemInfo
|
7
|
+
class << self
|
8
|
+
# === 平台检测 ===
|
9
|
+
def platform
|
10
|
+
@platform ||= detect_platform
|
11
|
+
end
|
12
|
+
|
13
|
+
def macos?
|
14
|
+
platform == :macos
|
15
|
+
end
|
16
|
+
|
17
|
+
def windows?
|
18
|
+
platform == :windows
|
19
|
+
end
|
20
|
+
|
21
|
+
def linux?
|
22
|
+
platform == :linux
|
23
|
+
end
|
24
|
+
|
25
|
+
# === 用户和环境 ===
|
26
|
+
def current_user
|
27
|
+
@current_user ||= Etc.getlogin || ENV['USER'] || ENV['LOGNAME']
|
28
|
+
end
|
29
|
+
|
30
|
+
def current_shell
|
31
|
+
@current_shell ||= ENV['SHELL']&.split('/')&.last || 'bash'
|
32
|
+
end
|
33
|
+
|
34
|
+
def shell_config_files
|
35
|
+
@shell_config_files ||= detect_shell_config_files
|
36
|
+
end
|
37
|
+
|
38
|
+
# === 地区和语言检测(仅在需要时调用)===
|
39
|
+
def region_info
|
40
|
+
@region_info ||= detect_region_info
|
41
|
+
end
|
42
|
+
|
43
|
+
def is_us_region?
|
44
|
+
region_info[:is_us]
|
45
|
+
end
|
46
|
+
|
47
|
+
# 检查是否为英语环境
|
48
|
+
def is_english_environment?
|
49
|
+
info = region_info
|
50
|
+
|
51
|
+
case platform
|
52
|
+
when :macos
|
53
|
+
# macOS: 检查 locale 和首选语言
|
54
|
+
locale_english = info[:locale].to_s.start_with?('en_') || info[:locale].to_s.start_with?('en.')
|
55
|
+
apple_locale_english = info[:apple_locale].to_s.start_with?('en_')
|
56
|
+
first_lang_english = info[:apple_languages].to_s.start_with?('en')
|
57
|
+
|
58
|
+
# 任一为英语即可
|
59
|
+
locale_english || apple_locale_english || first_lang_english
|
60
|
+
when :windows
|
61
|
+
# Windows: 检查系统和用户地区
|
62
|
+
system_english = info[:system_locale].to_s.start_with?('en-')
|
63
|
+
user_english = info[:user_locale].to_s.start_with?('en-')
|
64
|
+
locale_english = info[:locale].to_s.start_with?('en_') || info[:locale].to_s.start_with?('en.')
|
65
|
+
|
66
|
+
# 任一为英语即可
|
67
|
+
system_english || user_english || locale_english
|
68
|
+
else
|
69
|
+
# Linux/其他: 检查 LANG 环境变量
|
70
|
+
locale = info[:locale].to_s
|
71
|
+
locale.start_with?('en_') || locale.start_with?('en.')
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# 检查是否满足 claude_auth 的环境要求
|
76
|
+
def meets_claude_auth_requirements?
|
77
|
+
!windows? && is_us_region? && is_english_environment?
|
78
|
+
end
|
79
|
+
|
80
|
+
# 获取 claude_auth 环境检查详情
|
81
|
+
def claude_auth_check_details
|
82
|
+
{
|
83
|
+
os_supported: !windows?,
|
84
|
+
os_name: platform.to_s.capitalize,
|
85
|
+
is_us_region: is_us_region?,
|
86
|
+
region: region_info[:region],
|
87
|
+
is_english: is_english_environment?,
|
88
|
+
locale: region_info[:locale],
|
89
|
+
meets_requirements: meets_claude_auth_requirements?
|
90
|
+
}
|
91
|
+
end
|
92
|
+
|
93
|
+
# === 命令检测 ===
|
94
|
+
def which_command(cmd)
|
95
|
+
if windows?
|
96
|
+
system("where #{cmd} >nul 2>&1")
|
97
|
+
else
|
98
|
+
system("which #{cmd} > /dev/null 2>&1")
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
private
|
103
|
+
|
104
|
+
def detect_platform
|
105
|
+
os = RbConfig::CONFIG['host_os']
|
106
|
+
case os
|
107
|
+
when /darwin/i
|
108
|
+
:macos
|
109
|
+
when /mswin|mingw|cygwin/i
|
110
|
+
:windows
|
111
|
+
when /linux/i
|
112
|
+
:linux
|
113
|
+
when /bsd/i
|
114
|
+
:bsd
|
115
|
+
else
|
116
|
+
# 回退到 RUBY_PLATFORM
|
117
|
+
case RUBY_PLATFORM
|
118
|
+
when /darwin/
|
119
|
+
:macos
|
120
|
+
when /mswin|mingw|cygwin/
|
121
|
+
:windows
|
122
|
+
when /linux/
|
123
|
+
:linux
|
124
|
+
else
|
125
|
+
:unknown
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def detect_shell_config_files
|
131
|
+
files = []
|
132
|
+
|
133
|
+
case current_shell
|
134
|
+
when 'zsh'
|
135
|
+
files << File.expand_path("~/.zshrc")
|
136
|
+
when 'bash'
|
137
|
+
bash_profile = File.expand_path("~/.bash_profile")
|
138
|
+
bashrc = File.expand_path("~/.bashrc")
|
139
|
+
files << (File.exist?(bash_profile) ? bash_profile : bashrc)
|
140
|
+
when 'fish'
|
141
|
+
fish_config = File.expand_path("~/.config/fish/config.fish")
|
142
|
+
files << fish_config
|
143
|
+
|
144
|
+
# 确保 fish 配置目录存在
|
145
|
+
fish_dir = File.dirname(fish_config)
|
146
|
+
unless File.exist?(fish_dir)
|
147
|
+
require 'fileutils'
|
148
|
+
FileUtils.mkdir_p(fish_dir)
|
149
|
+
end
|
150
|
+
else
|
151
|
+
files << File.expand_path("~/.profile")
|
152
|
+
end
|
153
|
+
|
154
|
+
# 总是添加 ~/.profile 作为备份
|
155
|
+
profile_file = File.expand_path("~/.profile")
|
156
|
+
files << profile_file unless files.include?(profile_file)
|
157
|
+
|
158
|
+
files
|
159
|
+
end
|
160
|
+
|
161
|
+
def detect_region_info
|
162
|
+
case platform
|
163
|
+
when :macos
|
164
|
+
detect_macos_region
|
165
|
+
when :windows
|
166
|
+
detect_windows_region
|
167
|
+
when :linux
|
168
|
+
detect_linux_region
|
169
|
+
else
|
170
|
+
detect_generic_region
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
def detect_macos_region
|
175
|
+
locale = ENV['LANG'] || ENV['LC_ALL'] || ''
|
176
|
+
apple_locale = `defaults read -g AppleLocale 2>/dev/null`.chomp
|
177
|
+
apple_languages = `defaults read -g AppleLanguages 2>/dev/null`.chomp
|
178
|
+
|
179
|
+
# 解析语言列表
|
180
|
+
first_language = apple_languages.match(/"([^"]+)"/)&.captures&.first || ''
|
181
|
+
|
182
|
+
# 判断是否为美国地区
|
183
|
+
is_us = apple_locale.start_with?('en_US') ||
|
184
|
+
locale.start_with?('en_US') ||
|
185
|
+
first_language.start_with?('en-US')
|
186
|
+
|
187
|
+
{
|
188
|
+
locale: locale,
|
189
|
+
apple_locale: apple_locale,
|
190
|
+
apple_languages: first_language,
|
191
|
+
region: apple_locale.split('_').last || 'Unknown',
|
192
|
+
is_us: is_us,
|
193
|
+
platform: 'macOS'
|
194
|
+
}
|
195
|
+
end
|
196
|
+
|
197
|
+
def detect_windows_region
|
198
|
+
locale = ENV['LANG'] || ''
|
199
|
+
|
200
|
+
# Windows PowerShell 命令获取地区
|
201
|
+
system_locale = `powershell -Command "Get-WinSystemLocale | Select-Object -ExpandProperty Name" 2>nul`.chomp
|
202
|
+
user_locale = `powershell -Command "Get-Culture | Select-Object -ExpandProperty Name" 2>nul`.chomp
|
203
|
+
|
204
|
+
# 判断是否为美国地区
|
205
|
+
is_us = system_locale.start_with?('en-US') ||
|
206
|
+
user_locale.start_with?('en-US') ||
|
207
|
+
locale.start_with?('en_US')
|
208
|
+
|
209
|
+
{
|
210
|
+
locale: locale,
|
211
|
+
system_locale: system_locale,
|
212
|
+
user_locale: user_locale,
|
213
|
+
region: (system_locale.split('-').last || user_locale.split('-').last || 'Unknown'),
|
214
|
+
is_us: is_us,
|
215
|
+
platform: 'Windows'
|
216
|
+
}
|
217
|
+
end
|
218
|
+
|
219
|
+
def detect_linux_region
|
220
|
+
locale = ENV['LANG'] || ENV['LC_ALL'] || ''
|
221
|
+
|
222
|
+
# 尝试读取 /etc/locale.conf
|
223
|
+
locale_conf = File.exist?('/etc/locale.conf') ? File.read('/etc/locale.conf').strip : ''
|
224
|
+
|
225
|
+
# 使用 localectl 命令
|
226
|
+
system_locale = `localectl status 2>/dev/null | grep "System Locale" | cut -d= -f2`.chomp
|
227
|
+
|
228
|
+
# 判断是否为美国地区
|
229
|
+
is_us = locale.start_with?('en_US') ||
|
230
|
+
system_locale.start_with?('en_US') ||
|
231
|
+
locale_conf.include?('en_US')
|
232
|
+
|
233
|
+
{
|
234
|
+
locale: locale,
|
235
|
+
system_locale: system_locale || locale_conf,
|
236
|
+
region: locale.split('_').last&.split('.').first || 'Unknown',
|
237
|
+
is_us: is_us,
|
238
|
+
platform: 'Linux'
|
239
|
+
}
|
240
|
+
end
|
241
|
+
|
242
|
+
def detect_generic_region
|
243
|
+
locale = ENV['LANG'] || ENV['LC_ALL'] || 'Unknown'
|
244
|
+
|
245
|
+
{
|
246
|
+
locale: locale,
|
247
|
+
region: locale.split('_').last&.split('.').first || 'Unknown',
|
248
|
+
is_us: locale.start_with?('en_US'),
|
249
|
+
platform: 'Unknown'
|
250
|
+
}
|
251
|
+
end
|
252
|
+
end
|
253
|
+
end
|
254
|
+
end
|
255
|
+
end
|
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'rbconfig'
|
2
2
|
require 'fileutils'
|
3
|
+
require_relative 'system_info'
|
3
4
|
|
4
5
|
module EasyAI
|
5
6
|
module Base
|
@@ -9,7 +10,7 @@ module EasyAI
|
|
9
10
|
|
10
11
|
# 跨平台密码存储
|
11
12
|
def self.store_password(password)
|
12
|
-
case
|
13
|
+
case SystemInfo.platform
|
13
14
|
when :macos
|
14
15
|
store_password_macos(password)
|
15
16
|
when :windows
|
@@ -24,7 +25,7 @@ module EasyAI
|
|
24
25
|
|
25
26
|
# 跨平台密码获取
|
26
27
|
def self.get_stored_password
|
27
|
-
case
|
28
|
+
case SystemInfo.platform
|
28
29
|
when :macos
|
29
30
|
get_password_macos
|
30
31
|
when :windows
|
@@ -38,7 +39,7 @@ module EasyAI
|
|
38
39
|
|
39
40
|
# 跨平台密码删除
|
40
41
|
def self.delete_stored_password
|
41
|
-
case
|
42
|
+
case SystemInfo.platform
|
42
43
|
when :macos
|
43
44
|
delete_password_macos
|
44
45
|
when :windows
|
@@ -50,19 +51,9 @@ module EasyAI
|
|
50
51
|
end
|
51
52
|
end
|
52
53
|
|
53
|
-
#
|
54
|
+
# 检测操作系统平台(已迁移到 SystemInfo)
|
54
55
|
def self.detect_platform
|
55
|
-
|
56
|
-
case os
|
57
|
-
when /darwin/i
|
58
|
-
:macos
|
59
|
-
when /mswin|mingw|cygwin/i
|
60
|
-
:windows
|
61
|
-
when /linux/i
|
62
|
-
:linux
|
63
|
-
else
|
64
|
-
:unknown
|
65
|
-
end
|
56
|
+
SystemInfo.platform
|
66
57
|
end
|
67
58
|
|
68
59
|
private
|
@@ -3,6 +3,7 @@ require 'uri'
|
|
3
3
|
require_relative '../config/config'
|
4
4
|
require_relative '../auth/authclaude'
|
5
5
|
require_relative '../base/update_notifier'
|
6
|
+
require_relative '../base/system_info'
|
6
7
|
|
7
8
|
module EasyAI
|
8
9
|
class Command
|
@@ -99,6 +100,13 @@ module EasyAI
|
|
99
100
|
print_success("配置加载成功") if remote_config
|
100
101
|
end
|
101
102
|
|
103
|
+
# 检查是否使用 claude_auth,如果是则进行环境检查
|
104
|
+
if remote_config && remote_config['_config_path']&.include?('claude_auth')
|
105
|
+
unless check_claude_auth_environment
|
106
|
+
exit 1
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
102
110
|
# 如果远程配置获取失败,回退到本地配置
|
103
111
|
if remote_config.nil?
|
104
112
|
print_warning("使用本地配置")
|
@@ -167,8 +175,83 @@ module EasyAI
|
|
167
175
|
end
|
168
176
|
|
169
177
|
def claude_available?
|
170
|
-
#
|
171
|
-
|
178
|
+
# 跨平台命令检测
|
179
|
+
Base::SystemInfo.which_command('claude')
|
180
|
+
end
|
181
|
+
|
182
|
+
# 检查 claude_auth 环境要求
|
183
|
+
def check_claude_auth_environment
|
184
|
+
check_details = Base::SystemInfo.claude_auth_check_details
|
185
|
+
|
186
|
+
puts "\n🔍 系统环境检查(Claude 官方认证)"
|
187
|
+
puts "─" * 60
|
188
|
+
|
189
|
+
# 1. 操作系统检查
|
190
|
+
if check_details[:os_supported]
|
191
|
+
puts " ✓ 操作系统: #{check_details[:os_name]}".green
|
192
|
+
else
|
193
|
+
puts " ❌ 操作系统: #{check_details[:os_name]}(不支持)".red
|
194
|
+
end
|
195
|
+
|
196
|
+
# 2. 显示地区和语言信息
|
197
|
+
region_info = Base::SystemInfo.region_info
|
198
|
+
if Base::SystemInfo.macos?
|
199
|
+
puts " 系统地区: #{region_info[:apple_locale]}"
|
200
|
+
puts " 首选语言: #{region_info[:apple_languages]}"
|
201
|
+
elsif Base::SystemInfo.windows?
|
202
|
+
puts " 系统地区: #{region_info[:system_locale]}"
|
203
|
+
puts " 用户地区: #{region_info[:user_locale]}"
|
204
|
+
else
|
205
|
+
puts " 系统语言: #{region_info[:locale]}"
|
206
|
+
end
|
207
|
+
|
208
|
+
# 3. 地区检查
|
209
|
+
if check_details[:is_us_region]
|
210
|
+
puts " ✓ 地区检查: 美国地区".green
|
211
|
+
else
|
212
|
+
puts " ❌ 地区检查: 非美国地区(#{check_details[:region]})".red
|
213
|
+
end
|
214
|
+
|
215
|
+
# 4. 语言检查
|
216
|
+
if check_details[:is_english]
|
217
|
+
puts " ✓ 语言检查: 英语".green
|
218
|
+
else
|
219
|
+
puts " ❌ 语言检查: 非英语语言".red
|
220
|
+
end
|
221
|
+
|
222
|
+
puts "─" * 60
|
223
|
+
|
224
|
+
# 如果检查失败,显示错误信息和解决方案
|
225
|
+
unless check_details[:meets_requirements]
|
226
|
+
puts
|
227
|
+
puts " ⛔ 环境检查未通过".red
|
228
|
+
puts
|
229
|
+
|
230
|
+
failure_reasons = []
|
231
|
+
failure_reasons << "Claude 官方认证不支持 Windows 系统" unless check_details[:os_supported]
|
232
|
+
failure_reasons << "Claude 官方认证要求美国地区" unless check_details[:is_us_region]
|
233
|
+
failure_reasons << "Claude 官方认证要求英语语言环境" unless check_details[:is_english]
|
234
|
+
|
235
|
+
puts " 失败原因:".yellow
|
236
|
+
failure_reasons.each_with_index do |reason, index|
|
237
|
+
puts " #{index + 1}. #{reason}".yellow
|
238
|
+
end
|
239
|
+
|
240
|
+
puts
|
241
|
+
puts " 解决方案:".cyan
|
242
|
+
puts " 1. 使用其他认证平台: easyai claude --platform".cyan
|
243
|
+
puts " 2. 切换到 macOS/Linux 系统(如使用 Windows)".cyan unless check_details[:os_supported]
|
244
|
+
puts " 3. 切换系统地区设置到美国(英语)".cyan if !check_details[:is_us_region] || !check_details[:is_english]
|
245
|
+
puts " 4. 联系管理员获取其他认证方式".cyan
|
246
|
+
puts
|
247
|
+
|
248
|
+
return false
|
249
|
+
end
|
250
|
+
|
251
|
+
puts " ✅ 所有检查通过".green
|
252
|
+
puts
|
253
|
+
|
254
|
+
true
|
172
255
|
end
|
173
256
|
|
174
257
|
def load_local_config(config_path)
|
data/lib/easyai/command/clean.rb
CHANGED
@@ -3,6 +3,7 @@ require 'yaml'
|
|
3
3
|
require 'fileutils'
|
4
4
|
require 'etc'
|
5
5
|
require 'open3'
|
6
|
+
require_relative '../base/system_info'
|
6
7
|
|
7
8
|
module EasyAI
|
8
9
|
class Command
|
@@ -140,14 +141,11 @@ module EasyAI
|
|
140
141
|
end
|
141
142
|
|
142
143
|
# Keychain 认证信息 - authclaude.rb 中 configure_keychain 设置的
|
143
|
-
if
|
144
|
+
if Base::SystemInfo.macos?
|
144
145
|
keychain_entries = get_claude_keychain_entries
|
145
146
|
keychain_entries.each do |service_name|
|
146
147
|
puts " • macOS Keychain: #{service_name} (Claude认证信息)"
|
147
148
|
end
|
148
|
-
elsif !Gem.win_platform? && system('which secret-tool > /dev/null 2>&1')
|
149
|
-
# Linux Secret Service 支持
|
150
|
-
puts " • Linux Secret Service: Claude认证信息"
|
151
149
|
end
|
152
150
|
|
153
151
|
puts
|
@@ -277,18 +275,9 @@ module EasyAI
|
|
277
275
|
end
|
278
276
|
|
279
277
|
def clean_keychain_claude
|
280
|
-
|
281
|
-
if RUBY_PLATFORM.include?('darwin') && system('which security > /dev/null 2>&1')
|
282
|
-
clean_keychain_claude_macos
|
283
|
-
elsif !Gem.win_platform? && system('which secret-tool > /dev/null 2>&1')
|
284
|
-
# Linux Secret Service 支持
|
285
|
-
clean_keychain_claude_linux
|
286
|
-
end
|
287
|
-
end
|
278
|
+
return unless Base::SystemInfo.macos?
|
288
279
|
|
289
|
-
|
290
|
-
|
291
|
-
account_name = Etc.getlogin || ENV['USER'] || ENV['LOGNAME']
|
280
|
+
account_name = Base::SystemInfo.current_user
|
292
281
|
keychain_entries = get_claude_keychain_entries
|
293
282
|
|
294
283
|
if keychain_entries.empty?
|
@@ -317,19 +306,6 @@ module EasyAI
|
|
317
306
|
end
|
318
307
|
end
|
319
308
|
|
320
|
-
def clean_keychain_claude_linux
|
321
|
-
# Linux Secret Service 清理
|
322
|
-
service_name = "Claude Code-credentials"
|
323
|
-
account_name = Etc.getlogin || ENV['USER'] || ENV['LOGNAME']
|
324
|
-
|
325
|
-
cmd = "secret-tool clear service '#{service_name}' username '#{account_name}' 2>/dev/null"
|
326
|
-
if system(cmd)
|
327
|
-
puts " ✓ 已清理Linux Secret Service: #{service_name}"
|
328
|
-
end
|
329
|
-
rescue => e
|
330
|
-
puts " ✗ 清理Linux Secret Service失败: #{e.message}"
|
331
|
-
end
|
332
|
-
|
333
309
|
def has_claude_json_config?(claude_file)
|
334
310
|
return false unless File.exist?(claude_file)
|
335
311
|
|
@@ -352,10 +328,10 @@ module EasyAI
|
|
352
328
|
end
|
353
329
|
|
354
330
|
def get_claude_keychain_entries
|
355
|
-
return [] unless
|
331
|
+
return [] unless Base::SystemInfo.macos?
|
356
332
|
|
357
333
|
entries = []
|
358
|
-
account_name =
|
334
|
+
account_name = Base::SystemInfo.current_user
|
359
335
|
|
360
336
|
# 定义所有可能的 Claude 相关服务名
|
361
337
|
# 基于 authclaude.rb 和常见的 Claude 应用服务名
|
@@ -392,37 +368,26 @@ module EasyAI
|
|
392
368
|
|
393
369
|
def get_shell_files
|
394
370
|
shell_files = []
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
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
|
403
383
|
else
|
404
|
-
|
405
|
-
current_shell = ENV['SHELL']&.split('/')&.last || 'bash'
|
406
|
-
|
407
|
-
case current_shell
|
408
|
-
when 'zsh'
|
409
|
-
shell_files << File.expand_path("~/.zshrc")
|
410
|
-
when 'bash'
|
411
|
-
bash_profile = File.expand_path("~/.bash_profile")
|
412
|
-
bashrc = File.expand_path("~/.bashrc")
|
413
|
-
shell_files << (File.exist?(bash_profile) ? bash_profile : bashrc)
|
414
|
-
when 'fish'
|
415
|
-
fish_config = File.expand_path("~/.config/fish/config.fish")
|
416
|
-
shell_files << fish_config
|
417
|
-
else
|
418
|
-
shell_files << File.expand_path("~/.profile")
|
419
|
-
end
|
420
|
-
|
421
|
-
# 总是添加 ~/.profile 作为备份
|
422
|
-
profile_file = File.expand_path("~/.profile")
|
423
|
-
shell_files << profile_file unless shell_files.include?(profile_file)
|
384
|
+
shell_files << File.expand_path("~/.profile")
|
424
385
|
end
|
425
386
|
|
387
|
+
# 总是添加 ~/.profile 作为备份
|
388
|
+
profile_file = File.expand_path("~/.profile")
|
389
|
+
shell_files << profile_file unless shell_files.include?(profile_file)
|
390
|
+
|
426
391
|
shell_files
|
427
392
|
end
|
428
393
|
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require_relative '../base/system_info'
|
2
|
+
|
1
3
|
module EasyAI
|
2
4
|
class Command
|
3
5
|
class Gemini < Command
|
@@ -46,8 +48,8 @@ module EasyAI
|
|
46
48
|
private
|
47
49
|
|
48
50
|
def gemini_available?
|
49
|
-
#
|
50
|
-
|
51
|
+
# 跨平台命令检测
|
52
|
+
Base::SystemInfo.which_command('gemini')
|
51
53
|
end
|
52
54
|
end
|
53
55
|
end
|
data/lib/easyai/command/gpt.rb
CHANGED
@@ -2,6 +2,7 @@ require 'json'
|
|
2
2
|
require 'uri'
|
3
3
|
require 'colored2'
|
4
4
|
require_relative '../config/config'
|
5
|
+
require_relative '../base/system_info'
|
5
6
|
|
6
7
|
module EasyAI
|
7
8
|
class Command
|
@@ -127,8 +128,8 @@ module EasyAI
|
|
127
128
|
private
|
128
129
|
|
129
130
|
def gpt_available?
|
130
|
-
#
|
131
|
-
|
131
|
+
# 跨平台命令检测
|
132
|
+
Base::SystemInfo.which_command('openai')
|
132
133
|
end
|
133
134
|
|
134
135
|
def load_local_config(config_file)
|
@@ -4,6 +4,7 @@ require 'open3'
|
|
4
4
|
require 'fileutils'
|
5
5
|
require_relative '../../config/config'
|
6
6
|
require_relative '../../auth/authclaude'
|
7
|
+
require_relative '../../base/system_info'
|
7
8
|
|
8
9
|
module EasyAI
|
9
10
|
class Command
|
@@ -92,7 +93,7 @@ module EasyAI
|
|
92
93
|
end
|
93
94
|
|
94
95
|
# 2. 从 Keychain 读取凭证(macOS)
|
95
|
-
if
|
96
|
+
if Base::SystemInfo.macos?
|
96
97
|
print_status("🔐 读取 Keychain", "Claude Code-credentials") if @verbose
|
97
98
|
|
98
99
|
keychain_data = read_keychain_credentials
|
@@ -197,10 +198,10 @@ module EasyAI
|
|
197
198
|
end
|
198
199
|
|
199
200
|
def read_keychain_credentials
|
200
|
-
return nil unless
|
201
|
+
return nil unless Base::SystemInfo.macos?
|
201
202
|
|
202
203
|
service_name = "Claude Code-credentials"
|
203
|
-
account_name =
|
204
|
+
account_name = Base::SystemInfo.current_user
|
204
205
|
|
205
206
|
# 获取 Keychain 中的密码
|
206
207
|
cmd = [
|
data/lib/easyai/config/config.rb
CHANGED
@@ -83,12 +83,36 @@ module EasyAI
|
|
83
83
|
end
|
84
84
|
|
85
85
|
def get_user_config_content(config_filename)
|
86
|
-
#
|
87
|
-
|
88
|
-
return
|
86
|
+
# 获取配置文件路径
|
87
|
+
config_path = EasyAIConfig.get_config_path(config_filename, verbose: @verbose)
|
88
|
+
return nil unless config_path
|
89
|
+
|
90
|
+
# 读取配置内容
|
91
|
+
begin
|
92
|
+
config_content = File.read(config_path)
|
93
|
+
config = JSON.parse(config_content)
|
94
|
+
|
95
|
+
# 添加配置文件路径信息(用于判断认证类型)
|
96
|
+
config['_config_path'] = config_filename if config
|
97
|
+
|
98
|
+
# 删除解密后的文件
|
99
|
+
if File.exist?(config_path) && config_path.end_with?('.json')
|
100
|
+
encrypted_path = "#{config_path}.encrypted"
|
101
|
+
# 只有存在对应的加密文件时才删除解密文件
|
102
|
+
if File.exist?(encrypted_path)
|
103
|
+
FileUtils.rm_f(config_path)
|
104
|
+
puts " 已清理临时解密文件: #{File.basename(config_path)}" if @verbose
|
105
|
+
end
|
106
|
+
end
|
89
107
|
|
90
|
-
|
91
|
-
|
108
|
+
config
|
109
|
+
rescue JSON::ParserError => e
|
110
|
+
puts "✗ 解析配置文件失败: #{e.message}" if @verbose
|
111
|
+
nil
|
112
|
+
rescue => e
|
113
|
+
puts "✗ 读取配置文件失败: #{e.message}" if @verbose
|
114
|
+
nil
|
115
|
+
end
|
92
116
|
end
|
93
117
|
|
94
118
|
# JPS 登录获取用户名
|
@@ -198,15 +222,9 @@ module EasyAI
|
|
198
222
|
|
199
223
|
# 专门处理 Claude 的用户配置查找
|
200
224
|
def find_claude_user_config(claude_config, user_input_clean)
|
201
|
-
auth_priority = ["claude_auth", "kimi_auth", "deepseek_auth"]
|
202
|
-
|
203
|
-
# Windows 平台处理:从优先级中移除 claude_auth
|
204
|
-
if Gem.win_platform?
|
205
|
-
auth_priority = ["kimi_auth", "deepseek_auth"]
|
206
|
-
puts " ⚠ Windows 平台跳过 claude_auth,使用其他认证平台" if @verbose
|
207
|
-
end
|
225
|
+
auth_priority = ["claude_auth", "aliqwen_auth", "kimi_auth", "deepseek_auth"]
|
208
226
|
|
209
|
-
if @platform
|
227
|
+
if @platform
|
210
228
|
# 指定了平台
|
211
229
|
return find_claude_config_by_platform(claude_config, @platform, user_input_clean, auth_priority)
|
212
230
|
else
|
@@ -216,13 +234,6 @@ module EasyAI
|
|
216
234
|
end
|
217
235
|
|
218
236
|
def find_claude_config_by_platform(claude_config, platform, user_input_clean, auth_priority)
|
219
|
-
# Windows 平台特殊处理 claude_auth
|
220
|
-
if Gem.win_platform? && platform == "claude_auth"
|
221
|
-
puts "✗ Windows 平台不支持 claude_auth" if @verbose
|
222
|
-
puts " 请选择其他认证平台:kimi_auth 或 deepseek_auth" if @verbose
|
223
|
-
return nil
|
224
|
-
end
|
225
|
-
|
226
237
|
unless auth_priority.include?(platform)
|
227
238
|
puts "✗ 认证平台 '#{platform}' 不存在" if @verbose
|
228
239
|
puts " 支持的平台: #{auth_priority.join(', ')}" if @verbose
|
@@ -276,14 +287,7 @@ module EasyAI
|
|
276
287
|
available_platforms = []
|
277
288
|
|
278
289
|
# 检查用户在哪些平台有配置
|
279
|
-
|
280
|
-
|
281
|
-
# Windows 平台跳过 claude_auth
|
282
|
-
if Gem.win_platform?
|
283
|
-
platforms_to_check = ["kimi_auth", "deepseek_auth"]
|
284
|
-
end
|
285
|
-
|
286
|
-
platforms_to_check.each do |auth_type|
|
290
|
+
["claude_auth", "aliqwen_auth", "kimi_auth", "deepseek_auth"].each do |auth_type|
|
287
291
|
if find_in_auth_type(claude_config, auth_type, user_input_clean)
|
288
292
|
available_platforms << auth_type
|
289
293
|
end
|
@@ -309,6 +313,7 @@ module EasyAI
|
|
309
313
|
available_platforms.each_with_index do |platform, index|
|
310
314
|
platform_name = case platform
|
311
315
|
when "claude_auth" then "Claude 官方认证"
|
316
|
+
when "aliqwen_auth" then "阿里通义千问认证"
|
312
317
|
when "kimi_auth" then "Kimi 认证"
|
313
318
|
when "deepseek_auth" then "Deepseek 认证"
|
314
319
|
else platform
|
data/lib/easyai/version.rb
CHANGED
data/lib/easyai.rb
CHANGED
@@ -11,7 +11,7 @@ require 'easyai/command/update'
|
|
11
11
|
require 'easyai/command/setup'
|
12
12
|
require 'easyai/base/version_checker'
|
13
13
|
require 'easyai/base/update_notifier'
|
14
|
-
require 'easyai/base/
|
14
|
+
require 'easyai/base/system_info'
|
15
15
|
|
16
16
|
module EasyAI
|
17
17
|
# EasyAI 应用程序主类
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: easyai
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Wade
|
8
8
|
bindir: bin
|
9
9
|
cert_chain: []
|
10
|
-
date:
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
12
|
- !ruby/object:Gem::Dependency
|
13
13
|
name: claide
|
@@ -122,6 +122,7 @@ executables:
|
|
122
122
|
extensions: []
|
123
123
|
extra_rdoc_files: []
|
124
124
|
files:
|
125
|
+
- AGENTS.md
|
125
126
|
- CLAUDE.md
|
126
127
|
- README.md
|
127
128
|
- bin/easyai
|
@@ -131,6 +132,7 @@ files:
|
|
131
132
|
- lib/easyai/auth/jpsloginhelper.rb
|
132
133
|
- lib/easyai/base/cross_platform.rb
|
133
134
|
- lib/easyai/base/file_crypto.rb
|
135
|
+
- lib/easyai/base/system_info.rb
|
134
136
|
- lib/easyai/base/system_keychain.rb
|
135
137
|
- lib/easyai/base/update_notifier.rb
|
136
138
|
- lib/easyai/base/version_checker.rb
|
@@ -166,7 +168,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
166
168
|
- !ruby/object:Gem::Version
|
167
169
|
version: '0'
|
168
170
|
requirements: []
|
169
|
-
rubygems_version: 3.6.
|
171
|
+
rubygems_version: 3.6.9
|
170
172
|
specification_version: 4
|
171
173
|
summary: Easy AI CLI tool wrapper
|
172
174
|
test_files: []
|