easyai 1.1.1 → 1.1.3
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/lib/easyai/auth/jpslogin.rb +106 -37
- data/lib/easyai/command/claude.rb +9 -2
- data/lib/easyai/config/config.rb +41 -15
- data/lib/easyai/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 92482873797bd9a6936d1b493799244501b94440c155fe49e15b5aa889291ada
|
4
|
+
data.tar.gz: e3e619b6bd230d469dbb21fc2372e714813a504162e392680907f8fb3eec39aa
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1bb5fef07b98087e297e3db2a37f29abe34966771a58fca4c6297f7c29cb2e0136ef97cd7467d2bf8eff730bb2e693334776eeb241d7148ea94b12fdaa7f148e
|
7
|
+
data.tar.gz: 5a46f13a20d253320c6a039802cbd4caf93780bd5e1570680b51442722c8a42f9eb1b8fa607d4f8eb11ccb2fcf936764d2ad8ba70653a97af9eec2d010bd4b90
|
data/lib/easyai/auth/jpslogin.rb
CHANGED
@@ -14,7 +14,7 @@ module EasyAI
|
|
14
14
|
class JPSLogin
|
15
15
|
attr_reader :access_token, :username, :expires_at
|
16
16
|
|
17
|
-
def initialize
|
17
|
+
def initialize(options = {})
|
18
18
|
@client_id = "cli_a7bc7fe9b3d1d00b"
|
19
19
|
@server_port = 8898
|
20
20
|
@state = "client_login"
|
@@ -32,7 +32,10 @@ module EasyAI
|
|
32
32
|
@access_token = nil
|
33
33
|
@username = nil
|
34
34
|
@expires_at = nil
|
35
|
-
|
35
|
+
|
36
|
+
# 从选项中获取 verbose 标志
|
37
|
+
@verbose = options[:verbose] || false
|
38
|
+
|
36
39
|
# token 存储路径
|
37
40
|
@token_dir = File.expand_path('~/.easyai')
|
38
41
|
@token_file = File.join(@token_dir, '.jpstoken')
|
@@ -44,9 +47,14 @@ module EasyAI
|
|
44
47
|
if load_stored_token && validate_token
|
45
48
|
return true
|
46
49
|
end
|
47
|
-
|
50
|
+
|
48
51
|
puts "🔐 需要登录 JPS..."
|
49
|
-
|
52
|
+
result = authorize_and_login
|
53
|
+
|
54
|
+
# 如果用户主动取消,返回特殊标识
|
55
|
+
return :user_cancelled if result == :user_cancelled
|
56
|
+
|
57
|
+
return result
|
50
58
|
end
|
51
59
|
|
52
60
|
# 获取用户名(登录后可用)
|
@@ -93,8 +101,12 @@ module EasyAI
|
|
93
101
|
# 构建授权 URL
|
94
102
|
authorization_uri = build_authorization_uri
|
95
103
|
puts "正在打开浏览器进行飞书 OAuth 授权..."
|
96
|
-
puts "授权 URL
|
97
|
-
|
104
|
+
puts "\n授权 URL(如自动打开失败,请手动复制下面的链接到浏览器):"
|
105
|
+
puts "=" * 80
|
106
|
+
puts authorization_uri
|
107
|
+
puts "=" * 80
|
108
|
+
puts ""
|
109
|
+
|
98
110
|
# 在浏览器中打开授权 URL
|
99
111
|
open_browser(authorization_uri)
|
100
112
|
|
@@ -103,33 +115,88 @@ module EasyAI
|
|
103
115
|
|
104
116
|
# 如果自动获取失败,提示用户手动输入
|
105
117
|
if code.nil?
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
118
|
+
loop do
|
119
|
+
puts "\n自动获取授权码失败,请选择:"
|
120
|
+
puts "1. 输入授权码 (直接复制 'code=' 后面的内容)"
|
121
|
+
puts "2. 输入完整回调 URL"
|
122
|
+
puts "3. 重新打开授权网页"
|
123
|
+
puts "4. 退出"
|
124
|
+
print "> "
|
125
|
+
|
114
126
|
begin
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
127
|
+
choice = STDIN.gets&.chomp
|
128
|
+
|
129
|
+
# 处理 Ctrl+C 中断
|
130
|
+
if choice.nil?
|
131
|
+
puts "\n用户中断操作"
|
132
|
+
return :user_cancelled
|
133
|
+
end
|
134
|
+
|
135
|
+
case choice
|
136
|
+
when "1"
|
137
|
+
puts "请输入授权码:"
|
138
|
+
print "> "
|
139
|
+
code_input = STDIN.gets&.chomp
|
140
|
+
if code_input.nil?
|
141
|
+
puts "用户中断操作"
|
142
|
+
return :user_cancelled
|
143
|
+
elsif !code_input.empty?
|
144
|
+
code = code_input
|
145
|
+
break
|
146
|
+
else
|
147
|
+
puts "授权码不能为空,请重新选择"
|
148
|
+
end
|
149
|
+
when "2"
|
150
|
+
puts "请输入完整回调 URL:"
|
151
|
+
print "> "
|
152
|
+
url_input = STDIN.gets&.chomp
|
153
|
+
if url_input.nil?
|
154
|
+
puts "用户中断操作"
|
155
|
+
return :user_cancelled
|
156
|
+
elsif url_input.start_with?("http")
|
157
|
+
# 尝试从 URL 中提取 code
|
158
|
+
begin
|
159
|
+
uri = URI(url_input)
|
160
|
+
query_params = URI.decode_www_form(uri.query || '').to_h
|
161
|
+
code = query_params['code']
|
162
|
+
if code
|
163
|
+
puts "✓ 从 URL 中成功提取授权码"
|
164
|
+
break
|
165
|
+
else
|
166
|
+
puts "✗ URL 中没有找到授权码,请重新选择"
|
167
|
+
end
|
168
|
+
rescue => e
|
169
|
+
puts "✗ 无法从 URL 中提取授权码: #{e.message}"
|
170
|
+
end
|
171
|
+
else
|
172
|
+
puts "✗ 无效的 URL 格式,请重新选择"
|
173
|
+
end
|
174
|
+
when "3"
|
175
|
+
# 重新打开授权网页
|
176
|
+
puts "正在重新打开授权网页..."
|
177
|
+
open_browser(authorization_uri)
|
178
|
+
# 重新启动服务器尝试获取授权码
|
179
|
+
code = start_callback_server
|
180
|
+
if code
|
181
|
+
break
|
182
|
+
end
|
183
|
+
# 如果还是失败,继续循环让用户选择
|
184
|
+
when "4"
|
185
|
+
puts "已退出授权流程"
|
186
|
+
return :user_cancelled
|
187
|
+
else
|
188
|
+
puts "无效的选择,请输入 1-4 之间的数字"
|
189
|
+
end
|
190
|
+
rescue Interrupt
|
191
|
+
puts "\n\n用户中断操作"
|
192
|
+
return :user_cancelled
|
121
193
|
end
|
122
|
-
else
|
123
|
-
# 将输入直接作为 code
|
124
|
-
code = input unless input.empty?
|
125
194
|
end
|
126
195
|
end
|
127
196
|
|
128
197
|
if code
|
129
|
-
puts "成功获取授权码!正在使用飞书身份登录 JPS..."
|
130
198
|
if exchange_code_for_token(code)
|
131
|
-
puts "✓ JPS
|
132
|
-
puts " 用户名: #{@username}"
|
199
|
+
puts "✓ JPS 登录成功!用户名: #{@username}"
|
133
200
|
store_token
|
134
201
|
return true
|
135
202
|
end
|
@@ -181,8 +248,10 @@ module EasyAI
|
|
181
248
|
return nil
|
182
249
|
end
|
183
250
|
|
184
|
-
puts "启动本地服务器,监听端口 #{@server_port}..."
|
185
|
-
|
251
|
+
puts "启动本地服务器,监听端口 #{@server_port}..." if @verbose
|
252
|
+
puts "提示:按 Ctrl+C 可以中断并获得更多选择"
|
253
|
+
puts "🔄 正在使用飞书身份登录 JPS..."
|
254
|
+
|
186
255
|
begin
|
187
256
|
server = WEBrick::HTTPServer.new(
|
188
257
|
Port: @server_port,
|
@@ -209,19 +278,19 @@ module EasyAI
|
|
209
278
|
begin
|
210
279
|
# 解析请求参数
|
211
280
|
query_params = URI.decode_www_form(req.query_string || '').to_h
|
212
|
-
puts "接收到回调请求,参数: #{query_params.inspect}"
|
281
|
+
puts "接收到回调请求,参数: #{query_params.inspect}" if @verbose
|
213
282
|
|
214
283
|
if query_params['error']
|
215
|
-
puts "授权错误: #{query_params['error']}"
|
284
|
+
puts "授权错误: #{query_params['error']}" if @verbose
|
216
285
|
res.content_type = "text/html; charset=UTF-8"
|
217
286
|
res.body = build_error_page(query_params['error'])
|
218
287
|
elsif query_params['code']
|
219
288
|
code = query_params['code']
|
220
|
-
puts "成功获取授权码"
|
289
|
+
puts "成功获取授权码" if @verbose
|
221
290
|
res.content_type = "text/html; charset=UTF-8"
|
222
291
|
res.body = build_success_page
|
223
292
|
else
|
224
|
-
puts "未获取到授权码"
|
293
|
+
puts "未获取到授权码" if @verbose
|
225
294
|
res.content_type = "text/html; charset=UTF-8"
|
226
295
|
res.body = build_error_page("未获取到授权码")
|
227
296
|
end
|
@@ -275,14 +344,14 @@ module EasyAI
|
|
275
344
|
request['Content-Type'] = 'application/json'
|
276
345
|
request.body = request_data.to_json
|
277
346
|
|
278
|
-
puts "正在请求 JPS API: #{@api_endpoint}"
|
347
|
+
puts "正在请求 JPS API: #{@api_endpoint}" if @verbose
|
279
348
|
response = http.request(request)
|
280
|
-
|
281
|
-
puts "API 响应状态码: #{response.code}"
|
349
|
+
|
350
|
+
puts "API 响应状态码: #{response.code}" if @verbose
|
282
351
|
|
283
352
|
if response.body
|
284
353
|
result = JSON.parse(response.body)
|
285
|
-
puts "API 响应: #{result.inspect}"
|
354
|
+
puts "API 响应: #{result.inspect}" if @verbose
|
286
355
|
|
287
356
|
if result['meta'] && result['meta']['code'] == 200 && result['data']
|
288
357
|
data = result['data']
|
@@ -322,7 +391,7 @@ module EasyAI
|
|
322
391
|
}
|
323
392
|
|
324
393
|
File.write(@token_file, token_data.to_json)
|
325
|
-
puts "✓ JPS Token 和用户名已存储到 #{@token_file}"
|
394
|
+
puts "✓ JPS Token 和用户名已存储到 #{@token_file}" if @verbose
|
326
395
|
rescue => e
|
327
396
|
puts "⚠ 存储 JPS Token 失败: #{e.message}"
|
328
397
|
end
|
@@ -72,8 +72,15 @@ module EasyAI
|
|
72
72
|
else
|
73
73
|
# 从远程下载配置,传递选项
|
74
74
|
print_status("🔄 获取远程配置", "默认用户")
|
75
|
-
options = { no_keychain: @no_keychain }
|
75
|
+
options = { no_keychain: @no_keychain, verbose: @verbose_mode }
|
76
76
|
remote_config = ConfigManager.download_user_config(nil, options)
|
77
|
+
|
78
|
+
# 处理用户取消授权的情况
|
79
|
+
if remote_config == :user_cancelled
|
80
|
+
print_error("用户取消了授权登录")
|
81
|
+
exit 0
|
82
|
+
end
|
83
|
+
|
77
84
|
print_success("配置加载成功") if remote_config
|
78
85
|
end
|
79
86
|
|
@@ -81,7 +88,7 @@ module EasyAI
|
|
81
88
|
if remote_config.nil?
|
82
89
|
print_warning("使用本地配置")
|
83
90
|
remote_config = load_local_yaml_config
|
84
|
-
|
91
|
+
|
85
92
|
# 如果本地配置也为空,提示用户先进行设置
|
86
93
|
if remote_config.empty?
|
87
94
|
print_error("未找到有效配置")
|
data/lib/easyai/config/config.rb
CHANGED
@@ -14,6 +14,7 @@ module EasyAI
|
|
14
14
|
|
15
15
|
# 类变量初始化
|
16
16
|
@@no_keychain = false
|
17
|
+
@@verbose = false
|
17
18
|
|
18
19
|
# 管理配置仓库:如果存在则更新,不存在则下载
|
19
20
|
def self.manage_config_repo
|
@@ -65,7 +66,7 @@ module EasyAI
|
|
65
66
|
|
66
67
|
# 下载配置仓库到固定位置
|
67
68
|
def self.download_config_repo
|
68
|
-
puts "正在下载配置仓库到 #{CONFIG_REPO_DIR}..."
|
69
|
+
puts "正在下载配置仓库到 #{CONFIG_REPO_DIR}..." if @@verbose
|
69
70
|
|
70
71
|
begin
|
71
72
|
# 克隆仓库到固定位置,捕获错误信息
|
@@ -94,8 +95,9 @@ module EasyAI
|
|
94
95
|
|
95
96
|
# 设置全局选项
|
96
97
|
@@no_keychain = options[:no_keychain] || false
|
98
|
+
@@verbose = options[:verbose] || false
|
97
99
|
|
98
|
-
puts "正在从本地配置仓库加载配置..."
|
100
|
+
puts "正在从本地配置仓库加载配置..." if @@verbose
|
99
101
|
|
100
102
|
begin
|
101
103
|
# 检查 index.json 文件,支持加密版本
|
@@ -134,8 +136,13 @@ module EasyAI
|
|
134
136
|
selected_user = user_name
|
135
137
|
else
|
136
138
|
# 统一使用 JPS 登录获取用户名
|
137
|
-
selected_user = get_username_from_jps
|
138
|
-
|
139
|
+
selected_user = get_username_from_jps(@@verbose)
|
140
|
+
|
141
|
+
# 处理用户取消的情况
|
142
|
+
if selected_user == :user_cancelled
|
143
|
+
puts "✗ 用户取消了授权登录"
|
144
|
+
return :user_cancelled
|
145
|
+
elsif selected_user.nil? || selected_user.strip.empty?
|
139
146
|
puts "✗ JPS 登录失败,无法获取用户名"
|
140
147
|
return nil
|
141
148
|
end
|
@@ -176,8 +183,14 @@ module EasyAI
|
|
176
183
|
def self.download_user_config(user_name = nil, options = {})
|
177
184
|
# 优先尝试使用本地配置仓库
|
178
185
|
config = load_from_local_repo(user_name, options)
|
186
|
+
|
187
|
+
# 如果用户取消了授权,返回特殊标识
|
188
|
+
if config == :user_cancelled
|
189
|
+
return :user_cancelled
|
190
|
+
end
|
191
|
+
|
179
192
|
return config if config
|
180
|
-
|
193
|
+
|
181
194
|
# 如果本地仓库失败,回退到临时下载方式
|
182
195
|
puts "本地配置仓库不可用,使用临时下载..."
|
183
196
|
download_user_config_temp(user_name, options)
|
@@ -189,6 +202,7 @@ module EasyAI
|
|
189
202
|
|
190
203
|
# 设置全局选项
|
191
204
|
@@no_keychain = options[:no_keychain] || false
|
205
|
+
@@verbose = options[:verbose] || false
|
192
206
|
|
193
207
|
begin
|
194
208
|
# 创建临时目录
|
@@ -248,8 +262,14 @@ module EasyAI
|
|
248
262
|
selected_user = user_name
|
249
263
|
else
|
250
264
|
# 统一使用 JPS 登录获取用户名
|
251
|
-
selected_user = get_username_from_jps
|
252
|
-
|
265
|
+
selected_user = get_username_from_jps(@@verbose)
|
266
|
+
|
267
|
+
# 处理用户取消的情况
|
268
|
+
if selected_user == :user_cancelled
|
269
|
+
puts "✗ 用户取消了授权登录"
|
270
|
+
cleanup_temp_dir(temp_dir)
|
271
|
+
return nil
|
272
|
+
elsif selected_user.nil? || selected_user.strip.empty?
|
253
273
|
puts "✗ JPS 登录失败,无法获取用户名"
|
254
274
|
cleanup_temp_dir(temp_dir)
|
255
275
|
return nil
|
@@ -315,11 +335,17 @@ module EasyAI
|
|
315
335
|
end
|
316
336
|
|
317
337
|
# 通过 JPS 登录获取用户名
|
318
|
-
def self.get_username_from_jps
|
338
|
+
def self.get_username_from_jps(verbose = false)
|
319
339
|
begin
|
320
|
-
jps_login = Auth::JPSLogin.new
|
321
|
-
|
322
|
-
|
340
|
+
jps_login = Auth::JPSLogin.new(verbose: verbose)
|
341
|
+
login_result = jps_login.login
|
342
|
+
|
343
|
+
# 处理用户主动取消的情况
|
344
|
+
if login_result == :user_cancelled
|
345
|
+
return :user_cancelled
|
346
|
+
end
|
347
|
+
|
348
|
+
if login_result
|
323
349
|
username = jps_login.get_username
|
324
350
|
if username && !username.empty?
|
325
351
|
puts "👤 用户: #{username}"
|
@@ -329,7 +355,7 @@ module EasyAI
|
|
329
355
|
rescue => e
|
330
356
|
puts "❌ JPS 登录失败: #{e.message}"
|
331
357
|
end
|
332
|
-
|
358
|
+
|
333
359
|
return nil
|
334
360
|
end
|
335
361
|
|
@@ -356,7 +382,7 @@ module EasyAI
|
|
356
382
|
end
|
357
383
|
|
358
384
|
def self.load_encrypted_config_file(encrypted_config_file)
|
359
|
-
puts "正在解密配置文件..."
|
385
|
+
puts "正在解密配置文件..." if @@verbose
|
360
386
|
|
361
387
|
# 使用新的密码验证机制
|
362
388
|
result = get_and_validate_password(encrypted_config_file, "请输入解密密码: ")
|
@@ -373,7 +399,7 @@ module EasyAI
|
|
373
399
|
end
|
374
400
|
|
375
401
|
def self.parse_encrypted_index_file(encrypted_index_file)
|
376
|
-
puts "正在解密 index.json..."
|
402
|
+
puts "正在解密 index.json..." if @@verbose
|
377
403
|
|
378
404
|
# 使用新的密码验证机制
|
379
405
|
result = get_and_validate_password(encrypted_index_file, "请输入解密密码: ")
|
@@ -472,7 +498,7 @@ module EasyAI
|
|
472
498
|
stored_password = Base::SystemKeychain.get_stored_password
|
473
499
|
|
474
500
|
if stored_password && !stored_password.empty?
|
475
|
-
puts "使用系统存储的密码进行解密..."
|
501
|
+
puts "使用系统存储的密码进行解密..." if @@verbose
|
476
502
|
return { password: stored_password, from_system: true }
|
477
503
|
end
|
478
504
|
|
data/lib/easyai/version.rb
CHANGED