easyai 1.0.9 → 1.1.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e62a795c1d9f823731c5efa0fdd2ccccece744164374c222cf42039eeab1d394
4
- data.tar.gz: 8a937a49e3b619094a9af1beae643b9ede84f82e0f5cd38777e74e9f369711ee
3
+ metadata.gz: 60e6aec7d6d0d2475a75e26f61f21f1f5354b75ba828006293cdcdd3a6961f8a
4
+ data.tar.gz: bd0c31dccfcb0918707972a72415382b6ca778015ae67932626e4ad40933b747
5
5
  SHA512:
6
- metadata.gz: 32ac98c88d9cf7ae7045a461d49d40f187c050f6d83c484c7a00c605b38ae94527f95ffc82229f70afd50e9ca3a8f6c79c6359eabad5173dbf0c2134d7e1a5c9
7
- data.tar.gz: 2d0e460bdd981bd8b411557657b25ea7c2a62fb5f270be3af0fb20922245a4e063dff7afd8099bca1e9cf3ce6fbc440b1775081ac94b836254c52736b269ca06
6
+ metadata.gz: 31725ddd0f5c1ce5e3acda536b8c502addefc67271abb0b719521349fd3f3d840f2c41151982fbaca3044d34e6e110ef5e663155ee5c71ff636a6eb5af6b749a
7
+ data.tar.gz: 3045e64aa95c96a0ca58934f080dc6e82512121348290db4b4d8859b0af016f343893a8208f281f6be33af75afd4e2160fbc04451adf8f89ff9d62a21b202719
data/CLAUDE.md CHANGED
@@ -12,10 +12,17 @@ EasyAI 是一个 Ruby CLI 工具,作为 AI 命令行工具(Claude、Gemini
12
12
 
13
13
  代码库采用基于 CLAide 的模块化架构:
14
14
 
15
- - **主命令 (`lib/easyai.rb`)**:抽象基础命令,处理全局选项(`--setup`、`--config`)和配置管理
15
+ - **应用入口 (`lib/easyai.rb`)**:`EasyAIApp` 类负责启动标志显示、版本检查和命令执行
16
+ - **主命令 (`lib/easyai/command.rb`)**:抽象基础命令,处理全局选项和帮助信息
16
17
  - **AI 工具子命令 (`lib/easyai/command/*.rb`)**:每个 AI 工具(claude、gemini、gpt)都有自己的命令类,继承自主命令
17
18
  - **工具命令 (`lib/easyai/command/utils/`)**:实用工具集合,包括文件加密解密功能
18
- - **基础模块 (`lib/easyai/base/`)**:核心功能模块,如文件加密 (`file_crypto.rb`) 和系统钥匙串 (`system_keychain.rb`)
19
+ - **认证模块 (`lib/easyai/auth/`)**:处理 AI 工具的认证配置,如 `authclaude.rb`
20
+ - **配置管理 (`lib/easyai/config/`)**:管理配置文件的加载、保存和远程下载
21
+ - **基础模块 (`lib/easyai/base/`)**:
22
+ - `file_crypto.rb`:文件加密解密核心实现
23
+ - `system_keychain.rb`:系统钥匙串访问
24
+ - `version_checker.rb`:版本检查和更新
25
+ - `update_notifier.rb`:更新通知显示
19
26
  - **环境隔离**:使用 Ruby 的 `exec()` 替换当前进程为目标 AI 工具,在注入环境变量的同时保持交互性
20
27
  - **跨平台支持**:处理 Unix(`which`)和 Windows(`where`)之间的命令检测差异
21
28
 
@@ -37,6 +44,10 @@ gem install easyai-x.x.x.gem # 安装特定版本
37
44
  # 测试加密解密功能
38
45
  EASYAI_TEST_PASSWORD=testdir123 easyai utils encry /tmp/test_dir
39
46
  EASYAI_TEST_PASSWORD=testdir123 easyai utils decry /tmp/test_dir_encrypted
47
+
48
+ # 调试模式
49
+ EASYAI_DEBUG=1 ruby bin/easyai claude # 启用调试信息输出
50
+ EASYAI_SKIP_FORCE_UPDATE=1 easyai claude # 跳过版本更新检查
40
51
  ```
41
52
 
42
53
  ### 配置管理
@@ -48,6 +59,9 @@ easyai --setup
48
59
  easyai --config
49
60
 
50
61
  # 配置文件:~/.easyai/config.yml
62
+
63
+ # 使用本地配置文件
64
+ easyai claude ./config.json # 加载指定的 JSON 配置文件
51
65
  ```
52
66
 
53
67
  ### 工具命令
@@ -89,14 +103,42 @@ easyai clean # 清理临时文件和缓存
89
103
  - **密码管理**:支持交互式密码输入和环境变量密码(`EASYAI_TEST_PASSWORD`)
90
104
  - **错误处理**:完整的错误处理和用户友好提示
91
105
 
106
+ ### 认证优先级机制
107
+ AI 工具的认证令牌获取优先级(`lib/easyai/auth/authclaude.rb`):
108
+ 1. **远程配置**:首先尝试下载远程配置文件
109
+ 2. **本地配置**:如果指定了本地 JSON 配置文件,加载该文件
110
+ 3. **系统钥匙串**:从系统钥匙串获取(macOS Keychain)
111
+ 4. **环境变量**:从环境变量读取(如 `CLAUDE_CODE_OAUTH_TOKEN`)
112
+ 5. **交互输入**:最后提示用户输入并保存到配置文件
113
+
114
+ ### 版本更新机制
115
+ 项目包含智能版本管理系统 (`lib/easyai/base/version_checker.rb`):
116
+ - **启动检查**:在 `EasyAIApp#run` 中自动执行版本检查
117
+ - **强制更新**:`check_force_update!` 方法会阻塞执行直到检查完成
118
+ - **同步检查**:`check_sync` 方法复用已获取的版本信息
119
+ - **更新通知**:`UpdateNotifier` 模块负责显示更新提示
120
+ - **跳过机制**:
121
+ - 设置 `EASYAI_SKIP_FORCE_UPDATE=1` 环境变量跳过更新检查
122
+ - `--help`、`--version` 命令自动跳过更新检查
123
+ - **更新命令**:`easyai update` 执行 `gem update easyai`
124
+
92
125
  ## 版本管理
93
126
 
94
127
  版本在 `lib/easyai/version.rb` 中定义,自动被以下文件引用:
95
- - `easyai.gemspec` 通过 `require_relative`
96
- - 发布脚本通过正则解析
97
- - 主命令通过 `self.version = VERSION`
128
+ - `easyai.gemspec` 通过 `require_relative 'lib/easyai/version'`
129
+ - `lib/easyai/command.rb` 通过 `self.version = VERSION`
130
+ - `lib/easyai.rb` 在启动标志中使用 `VERSION`
131
+ - 发布脚本 (`test_local.sh`, `release_remote.sh`) 通过正则提取版本号
98
132
 
99
- 只需更新 `lib/easyai/version.rb` 中的版本 - 其他引用是自动的。
133
+ 只需更新 `lib/easyai/version.rb` 中的 `VERSION` 常量 - 其他引用是自动的。
134
+
135
+ ### 发布流程
136
+ `release_remote.sh` 脚本完成以下步骤:
137
+ 1. 提交所有未提交的更改
138
+ 2. 合并到 master 分支
139
+ 3. 构建和安装 gem
140
+ 4. 创建并推送 Git 标签(格式:v版本号)
141
+ 5. 发布到 RubyGems
100
142
 
101
143
  ## 依赖管理
102
144
 
@@ -104,6 +146,7 @@ easyai clean # 清理临时文件和缓存
104
146
  - **运行时依赖**:
105
147
  - `claide ~> 1.0`:CLI 框架
106
148
  - `colored2 ~> 3.1`:终端颜色输出
149
+ - `webrick ~> 1.9`:Web 服务器(用于版本检查,使用 1.9.1)
107
150
  - **开发依赖**:
108
151
  - `bundler ~> 2.0`:依赖管理
109
152
  - `rake ~> 13.0`:构建工具
@@ -117,6 +160,8 @@ easyai clean # 清理临时文件和缓存
117
160
  3. **测试流程**:使用 `./test_local.sh` 进行本地测试,确保功能正常后再发布
118
161
  4. **跨平台兼容性**:添加新功能时要考虑 Windows、Linux、macOS 的兼容性
119
162
  5. **环境隔离**:确保不影响用户的系统环境变量,所有配置都应在子进程中生效
163
+ 6. **错误处理**:所有命令都应有完善的错误处理和用户友好的错误提示
164
+ 7. **调试信息**:使用 `ENV['EASYAI_DEBUG']` 控制调试信息输出
120
165
 
121
166
  ## 中文化要求
122
167
 
@@ -183,6 +228,22 @@ DESC
183
228
 
184
229
  ## 技术实现注意事项
185
230
 
231
+ ### 启动流程
232
+ 1. **入口点**:`bin/easyai` 调用 `EasyAI::EasyAIApp.new.run(ARGV)`
233
+ 2. **启动标志**:`EasyAIApp#show_banner` 显示版本信息(跳过 --help 和 --version)
234
+ 3. **版本检查**:`EasyAIApp#check_version_before_run` 执行更新检查
235
+ - 强制更新检查:`Base::VersionChecker.check_force_update!`
236
+ - 同步检查:`Base::VersionChecker.check_sync`
237
+ - 更新通知:`Base::UpdateNotifier.maybe_show_notification`
238
+ 4. **命令执行**:`EasyAI::Command.run(argv)` 处理实际命令
239
+
240
+ ### 输出格式规范
241
+ 各命令使用统一的输出格式(参考 `lib/easyai/command/claude.rb`):
242
+ - `print_status(icon_text, detail)`:状态信息,图标左对齐,详情用 cyan 色
243
+ - `print_success(message)`:成功信息,绿色勾号
244
+ - `print_warning(message)`:警告信息,黄色警告符
245
+ - `print_error(message)`:错误信息,红色叉号
246
+
186
247
  ### CLAide 框架相关
187
248
 
188
249
  #### 1. --help 参数处理
@@ -233,6 +294,11 @@ end
233
294
  - 父类的 `validate!` 会处理 help 标志
234
295
  - 如果检测到 help 标志,会调用 `help!` 抛出异常,阻止 `run` 方法执行
235
296
 
297
+ #### 4. 抽象命令设置
298
+ 使用 `abstract_command = true` 标记抽象命令:
299
+ - `EasyAI::Command` 是抽象基类
300
+ - `EasyAI::Command::Utils` 也是抽象命令,包含子命令 Encry 和 Decry
301
+
236
302
  ### 命令类继承结构
237
303
  ```
238
304
  CLAide::Command
data/easyai.gemspec CHANGED
@@ -20,7 +20,7 @@ Gem::Specification.new do |spec|
20
20
 
21
21
  spec.add_dependency 'claide', '~> 1.0'
22
22
  spec.add_dependency 'colored2', '~> 3.1'
23
- spec.add_dependency 'webrick', '~> 1.7'
23
+ spec.add_dependency 'webrick', '~> 1.9'
24
24
 
25
25
  spec.add_development_dependency 'bundler', '~> 2.0'
26
26
  spec.add_development_dependency 'rake', '~> 13.0'
@@ -78,6 +78,16 @@ module EasyAI
78
78
 
79
79
  private
80
80
 
81
+ # 获取跨平台的 null 设备路径
82
+ def get_null_device
83
+ case RbConfig::CONFIG['host_os']
84
+ when /mswin|mingw|cygwin/
85
+ 'NUL'
86
+ else
87
+ '/dev/null'
88
+ end
89
+ end
90
+
81
91
  # 完整的授权和登录流程
82
92
  def authorize_and_login
83
93
  # 构建授权 URL
@@ -150,7 +160,8 @@ module EasyAI
150
160
  def open_browser(url)
151
161
  case RbConfig::CONFIG['host_os']
152
162
  when /mswin|mingw|cygwin/
153
- system("start", url)
163
+ # Windows: 使用双引号包围URL避免参数解析问题
164
+ system("start \"\" \"#{url}\"")
154
165
  when /darwin/
155
166
  system("open", url)
156
167
  when /linux|bsd/
@@ -175,14 +186,21 @@ module EasyAI
175
186
  begin
176
187
  server = WEBrick::HTTPServer.new(
177
188
  Port: @server_port,
178
- Logger: WEBrick::Log.new("/dev/null"),
189
+ BindAddress: '127.0.0.1',
190
+ Logger: WEBrick::Log.new(get_null_device),
179
191
  AccessLog: []
180
192
  )
181
193
  rescue Errno::EADDRINUSE
182
194
  puts "✗ 端口 #{@server_port} 仍被占用,无法启动服务器"
183
195
  return nil
196
+ rescue Errno::ENOENT
197
+ puts "✗ 启动服务器失败: 系统找不到指定的路径或文件"
198
+ return nil
184
199
  rescue => e
185
200
  puts "✗ 启动服务器失败: #{e.message}"
201
+ if RbConfig::CONFIG['host_os'] =~ /mswin|mingw|cygwin/
202
+ puts " 提示: 如果是Windows系统,请确保防火墙允许Ruby访问网络"
203
+ end
186
204
  return nil
187
205
  end
188
206
 
@@ -1,4 +1,5 @@
1
1
  require 'rbconfig'
2
+ require 'fileutils'
2
3
 
3
4
  module EasyAI
4
5
  module Base
@@ -120,15 +121,30 @@ module EasyAI
120
121
  # 使用 cmdkey 命令存储密码
121
122
  target_name = "#{SERVICE_NAME}_#{ACCOUNT_NAME}"
122
123
  cmd = "cmdkey /add:#{target_name} /user:#{ACCOUNT_NAME} /pass:\"#{password}\""
123
-
124
+
124
125
  success = system(cmd + " >nul 2>&1")
125
-
126
+
127
+ # 同时保存到文件作为备用
128
+ keyring_dir = File.expand_path('~/.easyai/.keyring')
129
+ FileUtils.mkdir_p(keyring_dir) unless Dir.exist?(keyring_dir)
130
+
131
+ keyring_file = File.join(keyring_dir, 'windows_credential')
132
+
133
+ begin
134
+ require 'base64'
135
+ encoded_password = Base64.strict_encode64(password)
136
+ File.write(keyring_file, encoded_password)
137
+ File.chmod(0600, keyring_file) rescue nil # 尝试设置权限,Windows 可能不支持
138
+ rescue => e
139
+ # 忽略文件写入错误
140
+ end
141
+
126
142
  if success
127
143
  puts "✓ 密码已安全存储到 Windows 凭据管理器"
128
144
  else
129
145
  puts "⚠ 无法存储密码到 Windows 凭据管理器"
130
146
  end
131
-
147
+
132
148
  success
133
149
  rescue => e
134
150
  puts "⚠ Windows 凭据存储出错: #{e.message}"
@@ -138,33 +154,29 @@ module EasyAI
138
154
  # Windows 凭据管理器密码获取
139
155
  def self.get_password_windows
140
156
  target_name = "#{SERVICE_NAME}_#{ACCOUNT_NAME}"
141
-
157
+
142
158
  # 首先检查凭据是否存在
143
- check_cmd = "cmdkey /list:#{target_name} >nul 2>&1"
144
- return nil unless system(check_cmd)
145
-
146
- # 使用 PowerShell 获取密码(简化版本,兼容性更好)
147
- ps_script = <<~PS
148
- try {
149
- $credential = Get-StoredCredential -Target "#{target_name}" -ErrorAction Stop
150
- $ptr = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($credential.Password)
151
- $password = [Runtime.InteropServices.Marshal]::PtrToStringAuto($ptr)
152
- [Runtime.InteropServices.Marshal]::ZeroFreeBSTR($ptr)
153
- Write-Output $password
154
- } catch {
155
- # 如果 Get-StoredCredential 不可用,尝试使用原生方法
156
- $output = & cmdkey /list:"#{target_name}" 2>$null
157
- if ($output -match "User: #{ACCOUNT_NAME}") {
158
- Write-Output "CREDENTIAL_EXISTS_BUT_CANNOT_RETRIEVE"
159
- }
160
- }
161
- PS
162
-
163
- password = `powershell -Command "#{ps_script}" 2>nul`.chomp
164
-
165
- # 如果无法获取密码但凭据存在,返回特殊标识
166
- return nil if password.empty? || password == "CREDENTIAL_EXISTS_BUT_CANNOT_RETRIEVE"
167
- password
159
+ check_cmd = "cmdkey /list:#{target_name} 2>nul"
160
+ check_output = `#{check_cmd}`
161
+ return nil unless check_output.include?(target_name)
162
+
163
+ # 使用备用文件存储方式
164
+ # 因为 Windows 凭据管理器的密码获取需要特殊权限或 CredentialManager 模块
165
+ # 我们使用加密文件作为备用方案
166
+ keyring_file = File.expand_path('~/.easyai/.keyring/windows_credential')
167
+
168
+ if File.exist?(keyring_file)
169
+ begin
170
+ require 'base64'
171
+ encoded_password = File.read(keyring_file).chomp
172
+ Base64.strict_decode64(encoded_password)
173
+ rescue => e
174
+ nil
175
+ end
176
+ else
177
+ # 提示用户重新输入密码
178
+ nil
179
+ end
168
180
  rescue => e
169
181
  nil
170
182
  end
@@ -173,13 +185,19 @@ module EasyAI
173
185
  def self.delete_password_windows
174
186
  target_name = "#{SERVICE_NAME}_#{ACCOUNT_NAME}"
175
187
  cmd = "cmdkey /delete:#{target_name}"
176
-
188
+
177
189
  success = system(cmd + " >nul 2>&1")
178
-
190
+
191
+ # 同时删除备用文件
192
+ keyring_file = File.expand_path('~/.easyai/.keyring/windows_credential')
193
+ if File.exist?(keyring_file)
194
+ File.delete(keyring_file) rescue nil
195
+ end
196
+
179
197
  if success
180
198
  puts "✓ 已从 Windows 凭据管理器删除存储的密码"
181
199
  end
182
-
200
+
183
201
  success
184
202
  rescue => e
185
203
  false
@@ -1,3 +1,3 @@
1
1
  module EasyAI
2
- VERSION = '1.0.9'
2
+ VERSION = '1.1.1'
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: easyai
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.9
4
+ version: 1.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Wade
@@ -43,14 +43,14 @@ dependencies:
43
43
  requirements:
44
44
  - - "~>"
45
45
  - !ruby/object:Gem::Version
46
- version: '1.7'
46
+ version: '1.9'
47
47
  type: :runtime
48
48
  prerelease: false
49
49
  version_requirements: !ruby/object:Gem::Requirement
50
50
  requirements:
51
51
  - - "~>"
52
52
  - !ruby/object:Gem::Version
53
- version: '1.7'
53
+ version: '1.9'
54
54
  - !ruby/object:Gem::Dependency
55
55
  name: bundler
56
56
  requirement: !ruby/object:Gem::Requirement