easyai 1.0.2 → 1.0.4
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/easyai.gemspec +1 -0
- 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 +31 -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,210 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'tmpdir'
|
3
|
+
require 'colored2'
|
4
|
+
|
5
|
+
module EasyAI
|
6
|
+
class Command
|
7
|
+
class Update < Command
|
8
|
+
self.summary = '从代码仓库安装最新开发版本(仅用于测试)'
|
9
|
+
self.description = <<-DESC
|
10
|
+
从 Gitee 代码仓库下载并安装最新的开发版本。
|
11
|
+
|
12
|
+
主要功能:
|
13
|
+
|
14
|
+
* 从远程仓库拉取最新代码
|
15
|
+
|
16
|
+
* 自动构建 gem 包
|
17
|
+
|
18
|
+
* 安装到本地系统
|
19
|
+
|
20
|
+
使用示例:
|
21
|
+
|
22
|
+
$ easyai update # 安装 master 分支
|
23
|
+
|
24
|
+
$ easyai update --branch=dev # 安装指定分支
|
25
|
+
|
26
|
+
$ easyai update --keep-source # 保留源代码
|
27
|
+
|
28
|
+
$ easyai update --verbose # 显示详细输出
|
29
|
+
DESC
|
30
|
+
|
31
|
+
def self.options
|
32
|
+
[
|
33
|
+
['--branch=BRANCH', '指定要安装的分支(默认: master)'],
|
34
|
+
['--keep-source', '保留下载的源代码'],
|
35
|
+
['--verbose', '显示详细输出']
|
36
|
+
].concat(super)
|
37
|
+
end
|
38
|
+
|
39
|
+
def initialize(argv)
|
40
|
+
@branch = argv.option('branch') || 'master'
|
41
|
+
@keep_source = argv.flag?('keep-source')
|
42
|
+
@verbose = argv.flag?('verbose')
|
43
|
+
super
|
44
|
+
end
|
45
|
+
|
46
|
+
def validate!
|
47
|
+
super
|
48
|
+
help! '未找到 git 命令。请先安装 git。' unless git_available?
|
49
|
+
end
|
50
|
+
|
51
|
+
def run
|
52
|
+
puts "\n╔#{'═' * 58}╗".cyan
|
53
|
+
puts "║#{'EasyAI 开发版本更新工具'.center(58)}║".cyan
|
54
|
+
puts "╚#{'═' * 58}╝\n".cyan
|
55
|
+
|
56
|
+
puts "⚠️ 注意:此命令仅用于测试开发版本".yellow
|
57
|
+
puts "📦 生产环境请使用: #{'gem update easyai'.green}\n\n"
|
58
|
+
|
59
|
+
# 确认继续
|
60
|
+
unless confirm_update?
|
61
|
+
puts "❌ 更新已取消".red
|
62
|
+
return
|
63
|
+
end
|
64
|
+
|
65
|
+
# 创建临时目录
|
66
|
+
temp_dir = Dir.mktmpdir('easyai-update-')
|
67
|
+
|
68
|
+
begin
|
69
|
+
# 克隆仓库
|
70
|
+
clone_repository(temp_dir)
|
71
|
+
|
72
|
+
# 执行安装脚本
|
73
|
+
install_from_source(temp_dir)
|
74
|
+
|
75
|
+
# 显示成功信息
|
76
|
+
show_success_message
|
77
|
+
|
78
|
+
rescue => e
|
79
|
+
puts "\n❌ 更新失败: #{e.message}".red
|
80
|
+
puts " 请检查网络连接或稍后重试".red
|
81
|
+
exit 1
|
82
|
+
ensure
|
83
|
+
# 清理临时文件
|
84
|
+
cleanup_temp_files(temp_dir) unless @keep_source
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
private
|
89
|
+
|
90
|
+
def git_available?
|
91
|
+
system('which git > /dev/null 2>&1')
|
92
|
+
end
|
93
|
+
|
94
|
+
def confirm_update?
|
95
|
+
print "确认从 Gitee 安装开发版本?[y/N]: ".yellow
|
96
|
+
begin
|
97
|
+
response = STDIN.gets
|
98
|
+
return false unless response
|
99
|
+
response = response.chomp.downcase
|
100
|
+
response == 'y' || response == 'yes'
|
101
|
+
rescue => e
|
102
|
+
puts "\n❌ 无法读取输入: #{e.message}".red
|
103
|
+
false
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def clone_repository(temp_dir)
|
108
|
+
repo_url = 'https://gitee.com/goodtools/EasyAI.git'
|
109
|
+
clone_path = File.join(temp_dir, 'EasyAI')
|
110
|
+
|
111
|
+
puts "📥 正在下载源代码...".cyan
|
112
|
+
puts " 仓库: #{repo_url}"
|
113
|
+
puts " 分支: #{@branch}"
|
114
|
+
|
115
|
+
# 构建 git clone 命令
|
116
|
+
git_cmd = "git clone --depth 1 --branch #{@branch} #{repo_url} #{clone_path}"
|
117
|
+
git_cmd += " > /dev/null 2>&1" unless @verbose
|
118
|
+
|
119
|
+
# 执行克隆
|
120
|
+
success = system(git_cmd)
|
121
|
+
|
122
|
+
unless success
|
123
|
+
raise "无法克隆仓库。请检查网络连接和分支名称。"
|
124
|
+
end
|
125
|
+
|
126
|
+
puts " ✓ 源代码下载完成".green
|
127
|
+
|
128
|
+
clone_path
|
129
|
+
end
|
130
|
+
|
131
|
+
def install_from_source(temp_dir)
|
132
|
+
source_dir = File.join(temp_dir, 'EasyAI')
|
133
|
+
|
134
|
+
unless File.exist?(source_dir)
|
135
|
+
raise "源代码目录不存在: #{source_dir}"
|
136
|
+
end
|
137
|
+
|
138
|
+
# 检查 test_local.sh 是否存在
|
139
|
+
install_script = File.join(source_dir, 'test_local.sh')
|
140
|
+
unless File.exist?(install_script)
|
141
|
+
raise "安装脚本不存在: test_local.sh"
|
142
|
+
end
|
143
|
+
|
144
|
+
puts "\n🔧 正在构建并安装...".cyan
|
145
|
+
|
146
|
+
# 切换到源代码目录并执行安装脚本
|
147
|
+
Dir.chdir(source_dir) do
|
148
|
+
# 确保脚本可执行
|
149
|
+
File.chmod(0755, 'test_local.sh')
|
150
|
+
|
151
|
+
# 执行安装脚本
|
152
|
+
install_cmd = "./test_local.sh"
|
153
|
+
install_cmd += " > /dev/null 2>&1" unless @verbose
|
154
|
+
|
155
|
+
success = system(install_cmd)
|
156
|
+
|
157
|
+
unless success
|
158
|
+
raise "安装脚本执行失败"
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
puts " ✓ 安装完成".green
|
163
|
+
end
|
164
|
+
|
165
|
+
def cleanup_temp_files(temp_dir)
|
166
|
+
if @keep_source
|
167
|
+
puts "\n📁 源代码保留在: #{temp_dir}".cyan
|
168
|
+
else
|
169
|
+
puts "\n🧹 正在清理临时文件...".cyan
|
170
|
+
FileUtils.rm_rf(temp_dir)
|
171
|
+
puts " ✓ 清理完成".green
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
def show_success_message
|
176
|
+
# 获取安装后的版本
|
177
|
+
installed_version = get_installed_version
|
178
|
+
|
179
|
+
puts "\n" + "─" * 60
|
180
|
+
puts "✅ #{'开发版本安装成功!'.green}"
|
181
|
+
puts "─" * 60
|
182
|
+
|
183
|
+
if installed_version
|
184
|
+
puts "📌 当前版本: #{installed_version}"
|
185
|
+
end
|
186
|
+
|
187
|
+
puts "\n提示:".cyan
|
188
|
+
puts " • 这是开发版本,可能包含未发布的功能"
|
189
|
+
puts " • 如需回退到稳定版本: gem install easyai"
|
190
|
+
puts " • 查看版本信息: easyai --version"
|
191
|
+
|
192
|
+
if @branch != 'master'
|
193
|
+
puts "\n📝 已安装分支: #{@branch}".yellow
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
def get_installed_version
|
198
|
+
# 尝试获取已安装的版本
|
199
|
+
output = `easyai --version 2>&1`
|
200
|
+
if output =~ /EasyAI 版本:\s*(\S+)/
|
201
|
+
$1
|
202
|
+
else
|
203
|
+
nil
|
204
|
+
end
|
205
|
+
rescue
|
206
|
+
nil
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
require_relative '../../base/file_crypto'
|
2
|
+
|
3
|
+
module EasyAI
|
4
|
+
class Command
|
5
|
+
class Utils
|
6
|
+
class Decry < Utils
|
7
|
+
self.summary = '解密指定文件或目录'
|
8
|
+
self.description = <<-DESC
|
9
|
+
对指定的加密文件或目录进行解密处理。
|
10
|
+
|
11
|
+
支持功能:
|
12
|
+
|
13
|
+
* 单个文件解密
|
14
|
+
|
15
|
+
* 目录批量解密
|
16
|
+
|
17
|
+
* 自动恢复原文件名
|
18
|
+
|
19
|
+
使用示例:
|
20
|
+
|
21
|
+
$ easyai utils decry file.encrypted # 解密单个文件
|
22
|
+
|
23
|
+
$ easyai utils decry /path/to/dir # 解密整个目录
|
24
|
+
|
25
|
+
$ easyai utils decry file.enc out.txt # 指定输出文件
|
26
|
+
|
27
|
+
$ easyai utils decry --output=out.txt # 使用选项指定输出
|
28
|
+
DESC
|
29
|
+
|
30
|
+
def self.options
|
31
|
+
[
|
32
|
+
['--output=PATH', '指定解密后文件的输出路径'],
|
33
|
+
].concat(super)
|
34
|
+
end
|
35
|
+
|
36
|
+
def initialize(argv)
|
37
|
+
@file_path = argv.shift_argument
|
38
|
+
@output_path = argv.option('output') || argv.shift_argument
|
39
|
+
super
|
40
|
+
end
|
41
|
+
|
42
|
+
def validate!
|
43
|
+
super
|
44
|
+
help! '请指定要解密的文件或目录路径' if @file_path.nil? || @file_path.empty?
|
45
|
+
help! "路径不存在: #{@file_path}" unless File.exist?(@file_path)
|
46
|
+
end
|
47
|
+
|
48
|
+
def run
|
49
|
+
if File.file?(@file_path)
|
50
|
+
decrypt_file_mode
|
51
|
+
elsif File.directory?(@file_path)
|
52
|
+
decrypt_directory_mode
|
53
|
+
else
|
54
|
+
puts "❌ 指定路径既不是文件也不是目录: #{@file_path}"
|
55
|
+
exit 1
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def decrypt_file_mode
|
62
|
+
puts "🔓 正在解密文件: #{@file_path}"
|
63
|
+
|
64
|
+
begin
|
65
|
+
# 获取解密密码
|
66
|
+
password = Base::FileCrypto.get_password("请输入解密密码: ")
|
67
|
+
|
68
|
+
# 解密文件
|
69
|
+
output_file = Base::FileCrypto.decrypt_file(@file_path, password, @output_path)
|
70
|
+
|
71
|
+
puts "✅ 文件解密完成"
|
72
|
+
puts "📁 解密文件保存位置: #{output_file}"
|
73
|
+
|
74
|
+
rescue => e
|
75
|
+
puts "❌ 解密失败: #{e.message}"
|
76
|
+
exit 1
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def decrypt_directory_mode
|
81
|
+
puts "🔓 正在解密目录: #{@file_path}"
|
82
|
+
|
83
|
+
begin
|
84
|
+
# 获取解密密码
|
85
|
+
password = Base::FileCrypto.get_password("请输入解密密码: ")
|
86
|
+
|
87
|
+
# 解密目录
|
88
|
+
result = Base::FileCrypto.decrypt_directory(@file_path, password, @output_path)
|
89
|
+
|
90
|
+
puts "✅ 目录解密完成"
|
91
|
+
puts "📁 解密目录保存位置: #{result[:output_dir]}"
|
92
|
+
puts "📋 已解密 #{result[:decrypted_files].length} 个文件"
|
93
|
+
|
94
|
+
rescue => e
|
95
|
+
puts "❌ 解密失败: #{e.message}"
|
96
|
+
exit 1
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
require_relative '../../base/file_crypto'
|
2
|
+
|
3
|
+
module EasyAI
|
4
|
+
class Command
|
5
|
+
class Utils
|
6
|
+
class Encry < Utils
|
7
|
+
self.summary = '加密指定文件或目录'
|
8
|
+
self.description = <<-DESC
|
9
|
+
对指定文件或目录进行加密处理。
|
10
|
+
|
11
|
+
支持功能:
|
12
|
+
|
13
|
+
* 单个文件加密
|
14
|
+
|
15
|
+
* 目录批量加密
|
16
|
+
|
17
|
+
* 自定义输出路径
|
18
|
+
|
19
|
+
使用示例:
|
20
|
+
|
21
|
+
$ easyai utils encry file.txt # 加密单个文件
|
22
|
+
|
23
|
+
$ easyai utils encry /path/to/dir # 加密整个目录
|
24
|
+
|
25
|
+
$ easyai utils encry file.txt out.enc # 指定输出文件
|
26
|
+
|
27
|
+
$ easyai utils encry --output=out.enc # 使用选项指定输出
|
28
|
+
DESC
|
29
|
+
|
30
|
+
def self.options
|
31
|
+
[
|
32
|
+
['--output=PATH', '指定加密后文件的输出路径'],
|
33
|
+
].concat(super)
|
34
|
+
end
|
35
|
+
|
36
|
+
def initialize(argv)
|
37
|
+
@file_path = argv.shift_argument
|
38
|
+
@output_path = argv.option('output') || argv.shift_argument
|
39
|
+
super
|
40
|
+
end
|
41
|
+
|
42
|
+
def validate!
|
43
|
+
super
|
44
|
+
help! '请指定要加密的文件或目录路径' if @file_path.nil? || @file_path.empty?
|
45
|
+
help! "路径不存在: #{@file_path}" unless File.exist?(@file_path)
|
46
|
+
end
|
47
|
+
|
48
|
+
def run
|
49
|
+
if File.file?(@file_path)
|
50
|
+
encrypt_file_mode
|
51
|
+
elsif File.directory?(@file_path)
|
52
|
+
encrypt_directory_mode
|
53
|
+
else
|
54
|
+
puts "❌ 指定路径既不是文件也不是目录: #{@file_path}"
|
55
|
+
exit 1
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def encrypt_file_mode
|
62
|
+
puts "🔐 正在加密文件: #{@file_path}"
|
63
|
+
|
64
|
+
begin
|
65
|
+
# 获取加密密码
|
66
|
+
password = Base::FileCrypto.get_password("请输入加密密码: ")
|
67
|
+
|
68
|
+
# 加密文件
|
69
|
+
output_file = Base::FileCrypto.encrypt_file(@file_path, password, @output_path)
|
70
|
+
|
71
|
+
puts "✅ 文件加密完成"
|
72
|
+
puts "📁 加密文件保存位置: #{output_file}"
|
73
|
+
|
74
|
+
rescue => e
|
75
|
+
puts "❌ 加密失败: #{e.message}"
|
76
|
+
exit 1
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def encrypt_directory_mode
|
81
|
+
puts "🔐 正在加密目录: #{@file_path}"
|
82
|
+
|
83
|
+
begin
|
84
|
+
# 获取加密密码
|
85
|
+
password = Base::FileCrypto.get_password("请输入加密密码: ")
|
86
|
+
|
87
|
+
# 加密目录
|
88
|
+
result = Base::FileCrypto.encrypt_directory(@file_path, password, @output_path)
|
89
|
+
|
90
|
+
puts "✅ 目录加密完成"
|
91
|
+
puts "📁 加密目录保存位置: #{result[:output_dir]}"
|
92
|
+
puts "📋 已加密 #{result[:encrypted_files].length} 个文件"
|
93
|
+
|
94
|
+
rescue => e
|
95
|
+
puts "❌ 加密失败: #{e.message}"
|
96
|
+
exit 1
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module EasyAI
|
2
|
+
class Command
|
3
|
+
class Utils < Command
|
4
|
+
self.summary = '实用工具集合'
|
5
|
+
self.description = <<-DESC
|
6
|
+
提供各种实用工具。
|
7
|
+
|
8
|
+
可用命令:
|
9
|
+
|
10
|
+
* encry - 文件加密工具
|
11
|
+
|
12
|
+
* decry - 文件解密工具
|
13
|
+
|
14
|
+
使用示例:
|
15
|
+
|
16
|
+
$ easyai utils encry file.txt # 加密文件
|
17
|
+
|
18
|
+
$ easyai utils decry file.encrypted # 解密文件
|
19
|
+
DESC
|
20
|
+
|
21
|
+
self.abstract_command = true
|
22
|
+
|
23
|
+
def self.options
|
24
|
+
[].concat(super)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# 加载子命令
|
31
|
+
require_relative 'utils/encry'
|
32
|
+
require_relative 'utils/decry'
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'claide'
|
2
|
+
require 'yaml'
|
3
|
+
require 'fileutils'
|
4
|
+
require 'colored2'
|
5
|
+
require_relative 'base/system_keychain'
|
6
|
+
require_relative 'base/version_checker'
|
7
|
+
|
8
|
+
module EasyAI
|
9
|
+
class Command < CLAide::Command
|
10
|
+
|
11
|
+
self.abstract_command = true
|
12
|
+
self.command = 'easyai'
|
13
|
+
self.version = VERSION
|
14
|
+
self.description = '简化 AI 命令行工具的 CLI 包装器,支持 Claude、Gemini 和 GPT'
|
15
|
+
self.plugin_prefixes = %w(easyai)
|
16
|
+
|
17
|
+
# 定义帮助标志访问器(类似 pindo)
|
18
|
+
attr_accessor :args_help_flag
|
19
|
+
alias_method :args_help_flag?, :args_help_flag
|
20
|
+
|
21
|
+
# 根命令的选项
|
22
|
+
DEFAULT_ROOT_OPTIONS = [
|
23
|
+
['--version', '显示版本信息']
|
24
|
+
]
|
25
|
+
|
26
|
+
# 所有命令通用的选项
|
27
|
+
DEFAULT_OPTIONS = [
|
28
|
+
['--help', '显示此帮助信息']
|
29
|
+
]
|
30
|
+
|
31
|
+
def self.options
|
32
|
+
if self == Command # 根命令
|
33
|
+
DEFAULT_ROOT_OPTIONS + DEFAULT_OPTIONS
|
34
|
+
else # 子命令
|
35
|
+
DEFAULT_OPTIONS
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
|
40
|
+
def initialize(argv)
|
41
|
+
super
|
42
|
+
# 使用CLAide已经设置的help_arg,而不是重新获取
|
43
|
+
@args_help_flag = @help_arg
|
44
|
+
end
|
45
|
+
|
46
|
+
def validate!
|
47
|
+
super
|
48
|
+
help! if args_help_flag?
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.run(argv)
|
52
|
+
super(argv)
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
end
|