easyai 1.2.0 → 1.3.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.
@@ -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
@@ -1,3 +1,3 @@
1
1
  module EasyAI
2
- VERSION = '1.2.0'
2
+ VERSION = '1.3.0'
3
3
  end
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.2.0
4
+ version: 1.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Wade
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 1980-01-02 00:00:00.000000000 Z
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: 0.2.0
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ version: 0.2.0
64
+ type: :runtime
65
+ prerelease: false
66
+ version_requirements: !ruby/object:Gem::Requirement
67
+ requirements:
68
+ - - "~>"
69
+ - !ruby/object:Gem::Version
70
+ version: 0.2.0
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ version: 0.2.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/jpslogin.rb
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.9
169
+ rubygems_version: 3.6.3
147
170
  specification_version: 4
148
171
  summary: Easy AI CLI tool wrapper
149
172
  test_files: []