easyai 1.2.1 → 1.4.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/CLAUDE.md +87 -220
- data/easyai.gemspec +1 -0
- data/lib/easyai/auth/authclaude.rb +260 -135
- data/lib/easyai/auth/jpsloginhelper.rb +98 -0
- data/lib/easyai/base/cross_platform.rb +149 -0
- data/lib/easyai/base/file_crypto.rb +50 -9
- data/lib/easyai/base/system_keychain.rb +52 -18
- data/lib/easyai/command/claude.rb +76 -41
- data/lib/easyai/command/clean.rb +56 -20
- data/lib/easyai/command/gemini.rb +2 -6
- data/lib/easyai/command/gpt.rb +17 -11
- data/lib/easyai/command/setup.rb +494 -0
- data/lib/easyai/config/config.rb +284 -595
- data/lib/easyai/config/easyai_config.rb +258 -0
- data/lib/easyai/version.rb +1 -1
- data/lib/easyai.rb +32 -3
- metadata +27 -4
- data/lib/easyai/auth/jpslogin.rb +0 -655
@@ -0,0 +1,258 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'fileutils'
|
4
|
+
require 'open3'
|
5
|
+
require 'json'
|
6
|
+
require 'tmpdir'
|
7
|
+
require_relative '../base/file_crypto'
|
8
|
+
require_relative '../base/system_keychain'
|
9
|
+
|
10
|
+
module EasyAI
|
11
|
+
# EasyAIConfig 类负责配置仓库的下载、更新和文件解密
|
12
|
+
class EasyAIConfig
|
13
|
+
REPO_URL = "https://gitee.com/goodtools/EasyAISetting.git"
|
14
|
+
EASYAI_DIR = File.expand_path('~/.easyai')
|
15
|
+
CONFIG_REPO_DIR = File.join(EASYAI_DIR, 'EasyAISetting')
|
16
|
+
|
17
|
+
class << self
|
18
|
+
# 初始化配置仓库
|
19
|
+
def initialize(options = {})
|
20
|
+
verbose = options[:verbose] || false
|
21
|
+
ensure_repo_ready(verbose)
|
22
|
+
# 确保临时目录存在
|
23
|
+
@temp_dir ||= File.join(Dir.tmpdir, "easyai_#{Process.pid}")
|
24
|
+
FileUtils.mkdir_p(@temp_dir) unless Dir.exist?(@temp_dir)
|
25
|
+
end
|
26
|
+
|
27
|
+
# 核心接口:获取解密后的配置内容
|
28
|
+
# @param filename [String] 配置文件名
|
29
|
+
# @param options [Hash] 选项
|
30
|
+
# @return [Hash, nil] 解密后的配置内容
|
31
|
+
def get_config(filename, options = {})
|
32
|
+
return nil unless filename
|
33
|
+
|
34
|
+
ensure_repo_ready(options[:verbose])
|
35
|
+
filename = normalize_filename(filename)
|
36
|
+
|
37
|
+
# 查找加密文件的实际路径(支持子目录)
|
38
|
+
encrypted_path = find_encrypted_file(filename)
|
39
|
+
return nil unless encrypted_path
|
40
|
+
|
41
|
+
# 解密到临时目录
|
42
|
+
temp_decrypted_path = get_temp_path(filename)
|
43
|
+
|
44
|
+
# 解密文件
|
45
|
+
return nil unless decrypt_file(encrypted_path, temp_decrypted_path, options[:verbose])
|
46
|
+
|
47
|
+
# 读取并解析配置
|
48
|
+
result = read_json_file(temp_decrypted_path, options[:verbose])
|
49
|
+
|
50
|
+
# 立即清理临时文件
|
51
|
+
FileUtils.rm_f(temp_decrypted_path) if File.exist?(temp_decrypted_path)
|
52
|
+
|
53
|
+
result
|
54
|
+
end
|
55
|
+
|
56
|
+
# 获取配置文件路径(用于需要文件路径的场景)
|
57
|
+
def get_config_path(filename, options = {})
|
58
|
+
return nil unless filename
|
59
|
+
|
60
|
+
ensure_repo_ready(options[:verbose])
|
61
|
+
filename = normalize_filename(filename)
|
62
|
+
|
63
|
+
# 查找加密文件的实际路径(支持子目录)
|
64
|
+
encrypted_path = find_encrypted_file(filename)
|
65
|
+
return nil unless encrypted_path
|
66
|
+
|
67
|
+
# 解密到临时目录
|
68
|
+
temp_decrypted_path = get_temp_path(filename)
|
69
|
+
|
70
|
+
# 解密文件
|
71
|
+
return nil unless decrypt_file(encrypted_path, temp_decrypted_path, options[:verbose])
|
72
|
+
|
73
|
+
File.exist?(temp_decrypted_path) ? temp_decrypted_path : nil
|
74
|
+
end
|
75
|
+
|
76
|
+
# 清理所有临时文件
|
77
|
+
def cleanup(verbose = false)
|
78
|
+
if @temp_dir && Dir.exist?(@temp_dir)
|
79
|
+
FileUtils.rm_rf(@temp_dir)
|
80
|
+
puts "✓ 已清理临时目录" if verbose
|
81
|
+
end
|
82
|
+
@temp_dir = nil
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
86
|
+
|
87
|
+
# 确保配置仓库就绪
|
88
|
+
def ensure_repo_ready(verbose = false)
|
89
|
+
FileUtils.mkdir_p(EASYAI_DIR) unless Dir.exist?(EASYAI_DIR)
|
90
|
+
|
91
|
+
if Dir.exist?(CONFIG_REPO_DIR)
|
92
|
+
update_repo(verbose)
|
93
|
+
else
|
94
|
+
download_repo(verbose)
|
95
|
+
end
|
96
|
+
|
97
|
+
# 确保临时目录存在
|
98
|
+
@temp_dir ||= File.join(Dir.tmpdir, "easyai_#{Process.pid}")
|
99
|
+
FileUtils.mkdir_p(@temp_dir) unless Dir.exist?(@temp_dir)
|
100
|
+
end
|
101
|
+
|
102
|
+
# 标准化文件名
|
103
|
+
def normalize_filename(filename)
|
104
|
+
filename.end_with?('.json') ? filename : "#{filename}.json"
|
105
|
+
end
|
106
|
+
|
107
|
+
# 查找加密文件(支持子目录)
|
108
|
+
def find_encrypted_file(filename)
|
109
|
+
# 直接在根目录查找
|
110
|
+
root_path = File.join(CONFIG_REPO_DIR, "#{filename}.encrypted")
|
111
|
+
return root_path if File.exist?(root_path)
|
112
|
+
|
113
|
+
# 在子目录中查找(保持原有路径)
|
114
|
+
if filename.include?('/')
|
115
|
+
sub_path = File.join(CONFIG_REPO_DIR, "#{filename}.encrypted")
|
116
|
+
return sub_path if File.exist?(sub_path)
|
117
|
+
end
|
118
|
+
|
119
|
+
nil
|
120
|
+
end
|
121
|
+
|
122
|
+
# 获取临时文件路径
|
123
|
+
def get_temp_path(filename)
|
124
|
+
# 保持文件名结构,替换 / 为 _
|
125
|
+
safe_filename = filename.gsub('/', '_')
|
126
|
+
File.join(@temp_dir, safe_filename)
|
127
|
+
end
|
128
|
+
|
129
|
+
# 读取 JSON 文件
|
130
|
+
def read_json_file(file_path, verbose = false)
|
131
|
+
return nil unless File.exist?(file_path)
|
132
|
+
|
133
|
+
begin
|
134
|
+
content = File.read(file_path)
|
135
|
+
JSON.parse(content)
|
136
|
+
rescue JSON::ParserError => e
|
137
|
+
puts "✗ 解析配置文件失败: #{e.message}" if verbose
|
138
|
+
nil
|
139
|
+
rescue => e
|
140
|
+
puts "✗ 读取配置文件失败: #{e.message}" if verbose
|
141
|
+
nil
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
# 下载配置仓库
|
146
|
+
def download_repo(verbose = false)
|
147
|
+
# 根据测试环境变量选择分支
|
148
|
+
branch = get_target_branch
|
149
|
+
puts "正在下载配置仓库 (#{branch} 分支)..." if verbose
|
150
|
+
|
151
|
+
cmd = "git clone --depth 1 --branch #{branch} #{REPO_URL} #{CONFIG_REPO_DIR} 2>&1"
|
152
|
+
_, _, status = Open3.capture3(cmd)
|
153
|
+
|
154
|
+
if status.success?
|
155
|
+
puts "✓ 配置仓库下载成功 (#{branch} 分支)" if verbose
|
156
|
+
true
|
157
|
+
else
|
158
|
+
puts "✗ 配置仓库下载失败,请检查网络连接" if verbose
|
159
|
+
false
|
160
|
+
end
|
161
|
+
rescue => e
|
162
|
+
puts "✗ 下载配置仓库失败: #{e.message}" if verbose
|
163
|
+
false
|
164
|
+
end
|
165
|
+
|
166
|
+
# 获取目标分支
|
167
|
+
def get_target_branch
|
168
|
+
# 检查是否有测试环境变量
|
169
|
+
if ENV['EASYAI_TEST_PASSWORD'] || ENV['EASYAI_DEV_MODE']
|
170
|
+
'dev'
|
171
|
+
else
|
172
|
+
'master'
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
# 更新配置仓库
|
177
|
+
def update_repo(verbose = false)
|
178
|
+
return false unless Dir.exist?(CONFIG_REPO_DIR)
|
179
|
+
|
180
|
+
target_branch = get_target_branch
|
181
|
+
|
182
|
+
Dir.chdir(CONFIG_REPO_DIR) do
|
183
|
+
# 重置本地更改
|
184
|
+
system("git reset --hard HEAD > /dev/null 2>&1")
|
185
|
+
|
186
|
+
# 获取当前分支
|
187
|
+
current_branch = `git branch --show-current`.chomp
|
188
|
+
|
189
|
+
# 如果需要切换分支
|
190
|
+
if current_branch != target_branch
|
191
|
+
puts "切换到 #{target_branch} 分支..." if verbose
|
192
|
+
system("git fetch origin #{target_branch} > /dev/null 2>&1")
|
193
|
+
system("git checkout #{target_branch} > /dev/null 2>&1")
|
194
|
+
end
|
195
|
+
|
196
|
+
# 拉取最新更新
|
197
|
+
_, _, status = Open3.capture3("git pull --force 2>&1")
|
198
|
+
|
199
|
+
if status.success?
|
200
|
+
puts "✓ 配置仓库已更新 (#{target_branch} 分支)" if verbose
|
201
|
+
else
|
202
|
+
puts "⚠️ 配置更新失败,使用现有配置" if verbose
|
203
|
+
end
|
204
|
+
end
|
205
|
+
true
|
206
|
+
rescue => e
|
207
|
+
puts "⚠️ 更新配置时出错: #{e.message}" if verbose
|
208
|
+
true
|
209
|
+
end
|
210
|
+
|
211
|
+
# 解密文件
|
212
|
+
def decrypt_file(encrypted_path, decrypted_path, verbose = false)
|
213
|
+
puts "正在解密: #{File.basename(encrypted_path)}..." if verbose
|
214
|
+
|
215
|
+
# 智能密码获取:环境变量 > 系统钥匙串 > 用户输入
|
216
|
+
password = get_decryption_password(verbose)
|
217
|
+
return false unless password
|
218
|
+
|
219
|
+
# 使用静态方法解密文件
|
220
|
+
Base::FileCrypto.decrypt_file(encrypted_path, password[:value], decrypted_path)
|
221
|
+
File.chmod(0600, decrypted_path)
|
222
|
+
|
223
|
+
# 如果密码来自用户输入且解密成功,保存到钥匙串
|
224
|
+
if password[:from_user_input]
|
225
|
+
Base::SystemKeychain.store_password(password[:value])
|
226
|
+
end
|
227
|
+
|
228
|
+
true
|
229
|
+
rescue => e
|
230
|
+
puts "✗ 解密失败: #{e.message}" if verbose
|
231
|
+
false
|
232
|
+
end
|
233
|
+
|
234
|
+
# 智能密码获取
|
235
|
+
def get_decryption_password(verbose = false)
|
236
|
+
# 1. 优先使用环境变量(用于测试和自动化)
|
237
|
+
if ENV['EASYAI_TEST_PASSWORD']
|
238
|
+
puts "使用环境变量密码" if verbose
|
239
|
+
return { value: ENV['EASYAI_TEST_PASSWORD'], from_user_input: false }
|
240
|
+
end
|
241
|
+
|
242
|
+
# 2. 尝试从系统钥匙串获取
|
243
|
+
stored_password = Base::SystemKeychain.get_stored_password
|
244
|
+
if stored_password && !stored_password.empty?
|
245
|
+
puts "使用系统存储的密码" if verbose
|
246
|
+
return { value: stored_password, from_user_input: false }
|
247
|
+
end
|
248
|
+
|
249
|
+
# 3. 提示用户输入
|
250
|
+
puts "请输入解密密码(成功后将自动保存)" if verbose
|
251
|
+
password = Base::FileCrypto.get_password("请输入解密密码: ")
|
252
|
+
return nil unless password && !password.empty?
|
253
|
+
|
254
|
+
{ value: password, from_user_input: true }
|
255
|
+
end
|
256
|
+
end
|
257
|
+
end
|
258
|
+
end
|
data/lib/easyai/version.rb
CHANGED
data/lib/easyai.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'easyai/version'
|
2
2
|
require 'easyai/config/config'
|
3
|
+
require 'easyai/config/easyai_config'
|
3
4
|
require 'easyai/command'
|
4
5
|
require 'easyai/command/claude'
|
5
6
|
require 'easyai/command/gemini'
|
@@ -7,8 +8,10 @@ require 'easyai/command/gpt'
|
|
7
8
|
require 'easyai/command/utils'
|
8
9
|
require 'easyai/command/clean'
|
9
10
|
require 'easyai/command/update'
|
11
|
+
require 'easyai/command/setup'
|
10
12
|
require 'easyai/base/version_checker'
|
11
13
|
require 'easyai/base/update_notifier'
|
14
|
+
require 'easyai/base/cross_platform'
|
12
15
|
|
13
16
|
module EasyAI
|
14
17
|
# EasyAI 应用程序主类
|
@@ -18,17 +21,23 @@ module EasyAI
|
|
18
21
|
require 'claide'
|
19
22
|
require 'colored2'
|
20
23
|
coerced_argv = CLAide::ARGV.coerce(argv)
|
21
|
-
|
24
|
+
|
22
25
|
# 如果不是帮助或版本查询,显示启动标志
|
23
26
|
unless coerced_argv.flag?('help') || coerced_argv.flag?('version')
|
24
27
|
show_banner
|
25
28
|
end
|
26
|
-
|
29
|
+
|
27
30
|
# 在运行命令前进行版本检查
|
28
31
|
check_version_before_run(argv)
|
29
|
-
|
32
|
+
|
33
|
+
# 设置退出时清理
|
34
|
+
setup_cleanup_handler
|
35
|
+
|
30
36
|
# 运行命令
|
31
37
|
EasyAI::Command.run(argv)
|
38
|
+
ensure
|
39
|
+
# 确保清理临时文件
|
40
|
+
cleanup_temp_files
|
32
41
|
end
|
33
42
|
|
34
43
|
private
|
@@ -87,5 +96,25 @@ module EasyAI
|
|
87
96
|
puts "⚠️ 版本检查出错: #{e.message}".yellow
|
88
97
|
puts e.backtrace.join("\n") if ENV['EASYAI_DEBUG']
|
89
98
|
end
|
99
|
+
|
100
|
+
# 设置清理处理器
|
101
|
+
def setup_cleanup_handler
|
102
|
+
# 注册信号处理器,确保 Ctrl+C 等信号时也能清理
|
103
|
+
%w[INT TERM].each do |signal|
|
104
|
+
Signal.trap(signal) do
|
105
|
+
cleanup_temp_files
|
106
|
+
exit(130) # 标准的 Ctrl+C 退出码
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
# 清理临时文件
|
112
|
+
def cleanup_temp_files
|
113
|
+
verbose = ENV['EASYAI_DEBUG'] || ENV['EASYAI_VERBOSE']
|
114
|
+
EasyAIConfig.cleanup(verbose)
|
115
|
+
rescue => e
|
116
|
+
# 清理失败不影响退出
|
117
|
+
puts "⚠️ 清理临时文件失败: #{e.message}" if ENV['EASYAI_DEBUG']
|
118
|
+
end
|
90
119
|
end
|
91
120
|
end
|
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.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Wade
|
8
8
|
bindir: bin
|
9
9
|
cert_chain: []
|
10
|
-
date:
|
10
|
+
date: 2025-09-22 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
12
|
- !ruby/object:Gem::Dependency
|
13
13
|
name: claide
|
@@ -51,6 +51,26 @@ dependencies:
|
|
51
51
|
- - "~>"
|
52
52
|
- !ruby/object:Gem::Version
|
53
53
|
version: '1.9'
|
54
|
+
- !ruby/object:Gem::Dependency
|
55
|
+
name: jpsclient
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - "~>"
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: 1.0.0
|
61
|
+
- - ">="
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: 1.0.0
|
64
|
+
type: :runtime
|
65
|
+
prerelease: false
|
66
|
+
version_requirements: !ruby/object:Gem::Requirement
|
67
|
+
requirements:
|
68
|
+
- - "~>"
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
version: 1.0.0
|
71
|
+
- - ">="
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: 1.0.0
|
54
74
|
- !ruby/object:Gem::Dependency
|
55
75
|
name: bundler
|
56
76
|
requirement: !ruby/object:Gem::Requirement
|
@@ -108,7 +128,8 @@ files:
|
|
108
128
|
- easyai.gemspec
|
109
129
|
- lib/easyai.rb
|
110
130
|
- lib/easyai/auth/authclaude.rb
|
111
|
-
- lib/easyai/auth/
|
131
|
+
- lib/easyai/auth/jpsloginhelper.rb
|
132
|
+
- lib/easyai/base/cross_platform.rb
|
112
133
|
- lib/easyai/base/file_crypto.rb
|
113
134
|
- lib/easyai/base/system_keychain.rb
|
114
135
|
- lib/easyai/base/update_notifier.rb
|
@@ -118,12 +139,14 @@ files:
|
|
118
139
|
- lib/easyai/command/clean.rb
|
119
140
|
- lib/easyai/command/gemini.rb
|
120
141
|
- lib/easyai/command/gpt.rb
|
142
|
+
- lib/easyai/command/setup.rb
|
121
143
|
- lib/easyai/command/update.rb
|
122
144
|
- lib/easyai/command/utils.rb
|
123
145
|
- lib/easyai/command/utils/decry.rb
|
124
146
|
- lib/easyai/command/utils/encry.rb
|
125
147
|
- lib/easyai/command/utils/export.rb
|
126
148
|
- lib/easyai/config/config.rb
|
149
|
+
- lib/easyai/config/easyai_config.rb
|
127
150
|
- lib/easyai/version.rb
|
128
151
|
homepage: https://github.com/wade/easyai
|
129
152
|
licenses:
|
@@ -143,7 +166,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
143
166
|
- !ruby/object:Gem::Version
|
144
167
|
version: '0'
|
145
168
|
requirements: []
|
146
|
-
rubygems_version: 3.6.
|
169
|
+
rubygems_version: 3.6.3
|
147
170
|
specification_version: 4
|
148
171
|
summary: Easy AI CLI tool wrapper
|
149
172
|
test_files: []
|