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.
@@ -0,0 +1,149 @@
1
+ module EasyAI
2
+ module CrossPlatform
3
+ # 跨平台检测命令是否存在
4
+ def self.command_exists?(command)
5
+ if Gem.win_platform?
6
+ system("where #{command} >nul 2>&1")
7
+ else
8
+ system("which #{command} > /dev/null 2>&1")
9
+ end
10
+ end
11
+
12
+ # 跨平台执行命令,临时清除环境变量
13
+ def self.run_without_env(cmd, env_vars = [])
14
+ if env_vars.empty?
15
+ return system(cmd)
16
+ end
17
+
18
+ if Gem.win_platform?
19
+ # Windows: 保存并清除环境变量
20
+ old_env = {}
21
+ env_vars.each do |key|
22
+ old_env[key] = ENV[key]
23
+ ENV.delete(key)
24
+ end
25
+
26
+ begin
27
+ result = system(cmd)
28
+ ensure
29
+ # 恢复环境变量
30
+ old_env.each { |k, v| ENV[k] = v if v }
31
+ end
32
+
33
+ result
34
+ else
35
+ # Unix/Linux/macOS: 使用 env -u
36
+ env_unset = env_vars.map { |v| "-u #{v}" }.join(' ')
37
+ system("env #{env_unset} #{cmd}")
38
+ end
39
+ end
40
+
41
+ # 跨平台执行命令并获取输出,临时清除环境变量
42
+ def self.capture_without_env(cmd, env_vars = [])
43
+ if env_vars.empty?
44
+ return `#{cmd}`
45
+ end
46
+
47
+ if Gem.win_platform?
48
+ # Windows: 保存并清除环境变量
49
+ old_env = {}
50
+ env_vars.each do |key|
51
+ old_env[key] = ENV[key]
52
+ ENV.delete(key)
53
+ end
54
+
55
+ begin
56
+ output = `#{cmd}`
57
+ ensure
58
+ # 恢复环境变量
59
+ old_env.each { |k, v| ENV[k] = v if v }
60
+ end
61
+
62
+ output
63
+ else
64
+ # Unix/Linux/macOS: 使用 env -u
65
+ env_unset = env_vars.map { |v| "-u #{v}" }.join(' ')
66
+ `env #{env_unset} #{cmd}`
67
+ end
68
+ end
69
+
70
+ # 获取空设备路径
71
+ def self.null_device
72
+ Gem.win_platform? ? 'nul' : '/dev/null'
73
+ end
74
+
75
+ # 跨平台设置文件权限
76
+ def self.set_file_permissions(file_path, mode = 0600)
77
+ return unless File.exist?(file_path)
78
+
79
+ if !Gem.win_platform?
80
+ begin
81
+ File.chmod(mode, file_path)
82
+ rescue => e
83
+ # 忽略权限设置错误,某些文件系统可能不支持
84
+ end
85
+ end
86
+ # Windows 使用默认的文件权限(通常只有当前用户可访问)
87
+ end
88
+
89
+ # 获取 Shell 配置文件路径
90
+ def self.shell_config_files
91
+ files = []
92
+
93
+ if Gem.win_platform?
94
+ # Windows PowerShell 配置
95
+ powershell_profile = File.expand_path("~/Documents/WindowsPowerShell/Microsoft.PowerShell_profile.ps1")
96
+ powershell_core_profile = File.expand_path("~/Documents/PowerShell/Microsoft.PowerShell_profile.ps1")
97
+
98
+ files << powershell_core_profile if File.exist?(File.dirname(powershell_core_profile))
99
+ files << powershell_profile if File.exist?(File.dirname(powershell_profile))
100
+ else
101
+ # Unix/Linux/macOS Shell 配置
102
+ current_shell = ENV['SHELL']&.split('/')&.last || 'bash'
103
+
104
+ case current_shell
105
+ when 'zsh'
106
+ files << File.expand_path("~/.zshrc")
107
+ when 'bash'
108
+ bash_profile = File.expand_path("~/.bash_profile")
109
+ bashrc = File.expand_path("~/.bashrc")
110
+ files << (File.exist?(bash_profile) ? bash_profile : bashrc)
111
+ when 'fish'
112
+ fish_config = File.expand_path("~/.config/fish/config.fish")
113
+ files << fish_config
114
+ else
115
+ files << File.expand_path("~/.profile")
116
+ end
117
+
118
+ # 添加 ~/.profile 作为备份
119
+ profile_file = File.expand_path("~/.profile")
120
+ files << profile_file unless files.include?(profile_file)
121
+ end
122
+
123
+ files
124
+ end
125
+
126
+ # 检测操作系统平台
127
+ def self.platform
128
+ if Gem.win_platform?
129
+ :windows
130
+ elsif RUBY_PLATFORM.include?('darwin')
131
+ :macos
132
+ elsif RUBY_PLATFORM.include?('linux')
133
+ :linux
134
+ else
135
+ :unknown
136
+ end
137
+ end
138
+
139
+ # 平台特定的命令格式化
140
+ def self.format_command(cmd)
141
+ if Gem.win_platform?
142
+ # Windows 特殊处理
143
+ cmd.gsub('2>/dev/null', '2>nul').gsub('>/dev/null', '>nul')
144
+ else
145
+ cmd
146
+ end
147
+ end
148
+ end
149
+ end
@@ -3,6 +3,7 @@ require 'base64'
3
3
  require 'digest'
4
4
  require 'io/console'
5
5
  require 'fileutils'
6
+ require 'json'
6
7
 
7
8
  module EasyAI
8
9
  module Base
@@ -35,22 +36,27 @@ module EasyAI
35
36
  def self.encrypt_file(file_path, password, output_path = nil)
36
37
  raise "文件不存在: #{file_path}" unless File.exist?(file_path)
37
38
  raise "指定路径不是文件: #{file_path}" unless File.file?(file_path)
38
-
39
+
40
+ # 如果是 JSON 文件,验证其有效性
41
+ if file_path.downcase.end_with?('.json')
42
+ validate_json_file(file_path)
43
+ end
44
+
39
45
  # 如果没有指定输出路径,使用原文件路径加 .encrypted 后缀
40
46
  output_path ||= "#{file_path}.encrypted"
41
-
47
+
42
48
  # 生成密钥
43
49
  key = generate_key(password)
44
-
50
+
45
51
  # 读取文件内容
46
52
  file_content = File.read(file_path)
47
-
53
+
48
54
  # 加密内容
49
55
  encrypted_content = aes_128_ecb_encrypt(key, file_content)
50
-
56
+
51
57
  # 写入加密文件
52
58
  File.write(output_path, encrypted_content)
53
-
59
+
54
60
  output_path
55
61
  rescue => e
56
62
  raise "加密文件失败: #{e.message}"
@@ -82,6 +88,10 @@ module EasyAI
82
88
  FileUtils.mkdir_p(output_file_dir) unless File.exist?(output_file_dir)
83
89
 
84
90
  begin
91
+ # 如果是 JSON 文件,先验证
92
+ if file_path.downcase.end_with?('.json')
93
+ validate_json_file(file_path)
94
+ end
85
95
  encrypt_file(file_path, password, output_file_path)
86
96
  encrypted_files << output_file_path
87
97
  rescue => e
@@ -180,6 +190,37 @@ module EasyAI
180
190
  raise "解密目录失败: #{e.message}"
181
191
  end
182
192
 
193
+ # 验证 JSON 文件的有效性
194
+ def self.validate_json_file(file_path)
195
+ begin
196
+ content = File.read(file_path)
197
+ # 尝试解析 JSON
198
+ JSON.parse(content)
199
+ rescue JSON::ParserError => e
200
+ # 提取错误位置信息
201
+ error_msg = e.message
202
+ puts "\n⚠️ 警告: JSON 文件格式无效"
203
+ puts " 文件: #{File.basename(file_path)}"
204
+ puts " 路径: #{file_path}"
205
+ puts " 错误: #{error_msg}"
206
+ puts "\n是否继续加密这个无效的 JSON 文件?(y/n)"
207
+ print "> "
208
+
209
+ begin
210
+ response = STDIN.gets&.chomp&.downcase
211
+ unless response == 'y' || response == 'yes'
212
+ raise "用户取消了对无效 JSON 文件的加密"
213
+ end
214
+ puts "继续加密文件..."
215
+ rescue Interrupt
216
+ raise "用户中断操作"
217
+ end
218
+ rescue => e
219
+ # 文件读取错误等其他问题
220
+ raise "验证 JSON 文件失败: #{e.message}"
221
+ end
222
+ end
223
+
183
224
  # 获取用户密码输入
184
225
  def self.get_password(prompt = "请输入密码: ")
185
226
  # 如果设置了环境变量,直接使用(主要用于测试和脚本自动化)
@@ -188,17 +229,17 @@ module EasyAI
188
229
  puts "使用环境变量密码"
189
230
  return password
190
231
  end
191
-
232
+
192
233
  begin
193
234
  print prompt
194
235
  password = STDIN.noecho(&:gets)&.chomp
195
236
  puts # 换行
196
-
237
+
197
238
  if password.nil? || password.empty?
198
239
  puts "❌ 密码不能为空"
199
240
  return nil
200
241
  end
201
-
242
+
202
243
  password
203
244
  rescue Interrupt
204
245
  puts "\n\n用户取消输入"
@@ -69,51 +69,75 @@ module EasyAI
69
69
 
70
70
  # macOS Keychain 密码存储
71
71
  def self.store_password_macos(password)
72
+ # 检查 security 命令是否可用
73
+ unless system('which security > /dev/null 2>&1')
74
+ puts "⚠ macOS Keychain 不可用,将使用文件存储"
75
+ return store_password_linux(password)
76
+ end
77
+
72
78
  # 首先尝试更新现有条目
73
79
  update_cmd = "security add-generic-password -a '#{ACCOUNT_NAME}' -s '#{SERVICE_NAME}' -w '#{password}' -U 2>/dev/null"
74
80
  success = system(update_cmd)
75
-
81
+
76
82
  unless success
77
83
  # 如果更新失败,尝试添加新条目
78
84
  add_cmd = "security add-generic-password -a '#{ACCOUNT_NAME}' -s '#{SERVICE_NAME}' -w '#{password}' 2>/dev/null"
79
85
  success = system(add_cmd)
80
86
  end
81
-
87
+
82
88
  if success
83
89
  puts "✓ 密码已安全存储到 macOS Keychain"
84
90
  else
85
- puts "⚠ 无法存储密码到 macOS Keychain"
91
+ puts "⚠ 无法存储密码到 macOS Keychain,尝试使用文件存储"
92
+ return store_password_linux(password)
86
93
  end
87
-
94
+
88
95
  success
89
96
  rescue => e
90
- puts "⚠ Keychain 存储出错: #{e.message}"
91
- false
97
+ puts "⚠ Keychain 存储出错: #{e.message},将使用文件存储"
98
+ store_password_linux(password)
92
99
  end
93
100
 
94
101
  # macOS Keychain 密码获取
95
102
  def self.get_password_macos
103
+ # 检查 security 命令是否可用
104
+ unless system('which security > /dev/null 2>&1')
105
+ return get_password_linux
106
+ end
107
+
96
108
  cmd = "security find-generic-password -a '#{ACCOUNT_NAME}' -s '#{SERVICE_NAME}' -w 2>/dev/null"
97
109
  password = `#{cmd}`.chomp
98
-
99
- return nil if password.empty? || $?.exitstatus != 0
110
+
111
+ if password.empty? || $?.exitstatus != 0
112
+ # 如果 Keychain 中没有,尝试从文件读取
113
+ return get_password_linux
114
+ end
115
+
100
116
  password
101
117
  rescue => e
102
- nil
118
+ get_password_linux
103
119
  end
104
120
 
105
121
  # macOS Keychain 密码删除
106
122
  def self.delete_password_macos
107
- cmd = "security delete-generic-password -a '#{ACCOUNT_NAME}' -s '#{SERVICE_NAME}' 2>/dev/null"
108
- success = system(cmd)
109
-
110
- if success
111
- puts " 已从 macOS Keychain 删除存储的密码"
123
+ success = false
124
+
125
+ # 尝试从 Keychain 删除
126
+ if system('which security > /dev/null 2>&1')
127
+ cmd = "security delete-generic-password -a '#{ACCOUNT_NAME}' -s '#{SERVICE_NAME}' 2>/dev/null"
128
+ success = system(cmd)
129
+
130
+ if success
131
+ puts "✓ 已从 macOS Keychain 删除存储的密码"
132
+ end
112
133
  end
113
-
134
+
135
+ # 同时尝试删除文件存储
136
+ delete_password_linux
137
+
114
138
  success
115
139
  rescue => e
116
- false
140
+ delete_password_linux
117
141
  end
118
142
 
119
143
  # Windows 凭据管理器密码存储
@@ -134,7 +158,10 @@ module EasyAI
134
158
  require 'base64'
135
159
  encoded_password = Base64.strict_encode64(password)
136
160
  File.write(keyring_file, encoded_password)
137
- File.chmod(0600, keyring_file) rescue nil # 尝试设置权限,Windows 可能不支持
161
+ # Windows 不支持 Unix 权限模型,但文件默认仅当前用户可访问
162
+ unless Gem.win_platform?
163
+ File.chmod(0600, keyring_file) rescue nil
164
+ end
138
165
  rescue => e
139
166
  # 忽略文件写入错误
140
167
  end
@@ -215,7 +242,14 @@ module EasyAI
215
242
  require 'base64'
216
243
  encoded_password = Base64.strict_encode64(password)
217
244
  File.write(keyring_file, encoded_password)
218
- File.chmod(0600, keyring_file) # 设置为仅用户可读写
245
+ # 设置文件权限(仅在支持的平台上)
246
+ if !Gem.win_platform?
247
+ begin
248
+ File.chmod(0600, keyring_file)
249
+ rescue => e
250
+ # 忽略权限设置错误,某些文件系统可能不支持
251
+ end
252
+ end
219
253
 
220
254
  puts "✓ 密码已存储到本地加密文件"
221
255
  true
@@ -10,9 +10,13 @@ module EasyAI
10
10
  self.summary = '运行 Claude CLI'
11
11
  self.description = <<-DESC
12
12
  启动 Anthropic Claude CLI 工具。
13
-
13
+
14
14
  主要功能:
15
15
 
16
+ * 支持多种认证平台 (Claude/Kimi/Deepseek)
17
+
18
+ * 智能选择最优认证方式
19
+
16
20
  * 支持远程配置下载
17
21
 
18
22
  * 自动配置环境变量
@@ -21,28 +25,32 @@ module EasyAI
21
25
 
22
26
  使用示例:
23
27
 
24
- $ easyai claude # 使用默认配置启动
28
+ $ easyai claude # 自动选择认证平台
29
+
30
+ $ easyai claude --platform # 交互式选择认证平台
25
31
 
26
- $ easyai claude ./config.json # 使用本地配置文件
32
+ $ easyai claude ./config.json # 使用本地配置文件
27
33
 
28
- $ easyai claude --verbose # 显示详细信息
34
+ $ easyai claude --verbose # 显示详细信息
29
35
 
30
- $ easyai claude --no-keychain # 禁用密码存储
36
+ $ easyai claude --no-keychain # 禁用密码存储
31
37
  DESC
32
38
 
33
39
  def self.options
34
40
  [
41
+ ['--platform', '选择认证平台(交互式选择)'],
35
42
  ['--no-keychain', '禁用自动密码存储'],
36
43
  ['--verbose', '显示详细信息']
37
44
  ].concat(super)
38
45
  end
39
46
 
40
47
  def initialize(argv)
48
+ @platform_flag = argv.flag?('platform')
41
49
  @no_keychain = argv.flag?('no-keychain')
42
50
  @verbose_mode = argv.flag?('verbose')
43
-
51
+
44
52
  super
45
-
53
+
46
54
  # 获取剩余参数
47
55
  remaining_args = @argv.remainder!
48
56
 
@@ -77,7 +85,8 @@ module EasyAI
77
85
  options = {
78
86
  no_keychain: @no_keychain,
79
87
  verbose: @verbose_mode,
80
- tool_type: "claude" # 指定使用 claude 组的配置
88
+ tool_type: "claude", # 指定使用 claude 组的配置
89
+ platform_flag: @platform_flag # 传递 platform 标志
81
90
  }
82
91
  remote_config = ConfigManager.download_user_config(nil, options)
83
92
 
@@ -158,12 +167,8 @@ module EasyAI
158
167
  end
159
168
 
160
169
  def claude_available?
161
- # 跨平台命令检测
162
- if RUBY_PLATFORM =~ /mswin|mingw|cygwin/
163
- system('where claude >nul 2>&1')
164
- else
165
- system('which claude > /dev/null 2>&1')
166
- end
170
+ # 使用统一的跨平台命令检测
171
+ EasyAI::CrossPlatform.command_exists?('claude')
167
172
  end
168
173
 
169
174
  def load_local_config(config_path)
@@ -193,19 +198,34 @@ module EasyAI
193
198
  puts "\n📋 环境配置:" if @verbose_mode
194
199
  puts "─" * 60 if @verbose_mode
195
200
 
196
- # 从配置中提取环境变量 - 只设置 CLAUDE_CODE_OAUTH_TOKEN
197
- if config && config['env'] && config['env']['CLAUDE_CODE_OAUTH_TOKEN']
198
- env['CLAUDE_CODE_OAUTH_TOKEN'] = config['env']['CLAUDE_CODE_OAUTH_TOKEN']
199
-
200
- # 统一显示方式:不显示完整的令牌
201
- token_preview = config['env']['CLAUDE_CODE_OAUTH_TOKEN'].length > 15 ?
202
- "#{config['env']['CLAUDE_CODE_OAUTH_TOKEN'][0..15]}..." :
203
- config['env']['CLAUDE_CODE_OAUTH_TOKEN']
204
- print_status("🔑 令牌已配置", token_preview)
205
-
206
- if @verbose_mode
207
- # verbose 模式:显示更多状态信息(但不显示敏感内容)
208
- puts " 令牌长度: #{config['env']['CLAUDE_CODE_OAUTH_TOKEN'].length} 字符"
201
+ # 从配置中提取所有环境变量 - 动态处理 env 中的所有变量
202
+ if config && config['env']
203
+ config['env'].each do |key, value|
204
+ # 将所有环境变量都添加到 env 中
205
+ env[key] = value.to_s
206
+
207
+ # 根据变量名称智能显示
208
+ if key.include?('TOKEN') || key.include?('KEY') || key.include?('SECRET') || key.include?('PASSWORD')
209
+ # 敏感信息:显示部分内容
210
+ value_preview = value.to_s.length > 15 ? "#{value.to_s[0..15]}..." : value.to_s
211
+ print_status("🔑 #{key}", value_preview)
212
+ puts " 长度: #{value.to_s.length} 字符" if @verbose_mode
213
+ elsif key.include?('URL') || key.include?('ENDPOINT')
214
+ # URL类:显示完整内容
215
+ print_status("🌐 #{key}", value.to_s)
216
+ elsif key.include?('MODEL')
217
+ # 模型配置:显示完整内容
218
+ print_status("🤖 #{key}", value.to_s)
219
+ elsif key.include?('TIMEOUT')
220
+ # 超时配置:显示完整内容
221
+ print_status("⏱️ #{key}", value.to_s)
222
+ elsif key.include?('PROXY')
223
+ # 代理配置:显示部分内容
224
+ print_status("🔄 #{key}", value.to_s)
225
+ else
226
+ # 其他变量:根据 verbose 模式决定是否显示
227
+ print_status("📝 #{key}", value.to_s) if @verbose_mode
228
+ end
209
229
  end
210
230
  end
211
231
 
@@ -227,11 +247,21 @@ module EasyAI
227
247
  end
228
248
 
229
249
  if proxy_urls.any?
230
- # 统一显示方式:简化代理显示,隐藏密码
250
+ # 统一显示方式:简化代理显示,隐藏密码和部分IP
231
251
  simplified_proxies = proxy_urls.uniq.map do |url|
232
252
  uri = URI(url) rescue nil
233
- if uri && uri.password
234
- "#{uri.scheme}://***@#{uri.host}:#{uri.port}"
253
+ if uri
254
+ # 隐藏IP地址的前两段,显示后两段
255
+ masked_host = uri.host
256
+ if uri.host =~ /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/
257
+ masked_host = "*.*.#{$3}.#{$4}"
258
+ end
259
+
260
+ if uri.password
261
+ "#{uri.scheme}://***@#{masked_host}:#{uri.port}"
262
+ else
263
+ "#{uri.scheme}://#{masked_host}:#{uri.port}"
264
+ end
235
265
  else
236
266
  url
237
267
  end
@@ -249,21 +279,26 @@ module EasyAI
249
279
 
250
280
 
251
281
  def export_environment_variables(env)
252
- # 导出 CLAUDE_CODE_OAUTH_TOKEN(仅在内存中,不写入文件)
253
- if env['CLAUDE_CODE_OAUTH_TOKEN']
254
- ENV['CLAUDE_CODE_OAUTH_TOKEN'] = env['CLAUDE_CODE_OAUTH_TOKEN']
255
- print_success("令牌已设置到环境变量") if @verbose_mode
256
- end
257
-
258
- # 导出代理相关环境变量
259
- proxy_vars = ['HTTP_PROXY', 'HTTPS_PROXY', 'http_proxy', 'https_proxy']
282
+ # 导出所有环境变量(仅在内存中,不写入文件)
283
+ env_exported = false
260
284
  proxy_exported = false
261
- proxy_vars.each do |var|
262
- if env[var]
263
- ENV[var] = env[var]
285
+
286
+ env.each do |key, value|
287
+ # 跳过 PATH 等系统关键变量,避免覆盖
288
+ next if ['PATH', 'HOME', 'USER', 'SHELL'].include?(key)
289
+
290
+ # 设置环境变量
291
+ ENV[key] = value
292
+
293
+ # 记录导出状态
294
+ if key.include?('PROXY')
264
295
  proxy_exported = true
296
+ else
297
+ env_exported = true
265
298
  end
266
299
  end
300
+
301
+ print_success("环境变量已设置") if env_exported && @verbose_mode
267
302
  print_success("代理已设置到环境变量") if proxy_exported && @verbose_mode
268
303
  end
269
304
 
@@ -140,11 +140,14 @@ module EasyAI
140
140
  end
141
141
 
142
142
  # Keychain 认证信息 - authclaude.rb 中 configure_keychain 设置的
143
- if RUBY_PLATFORM.include?('darwin')
143
+ if RUBY_PLATFORM.include?('darwin') && system('which security > /dev/null 2>&1')
144
144
  keychain_entries = get_claude_keychain_entries
145
145
  keychain_entries.each do |service_name|
146
146
  puts " • macOS Keychain: #{service_name} (Claude认证信息)"
147
147
  end
148
+ elsif !Gem.win_platform? && system('which secret-tool > /dev/null 2>&1')
149
+ # Linux Secret Service 支持
150
+ puts " • Linux Secret Service: Claude认证信息"
148
151
  end
149
152
 
150
153
  puts
@@ -274,7 +277,16 @@ module EasyAI
274
277
  end
275
278
 
276
279
  def clean_keychain_claude
277
- return unless RUBY_PLATFORM.include?('darwin')
280
+ # 检查是否是 macOS 并且 security 命令可用
281
+ if RUBY_PLATFORM.include?('darwin') && system('which security > /dev/null 2>&1')
282
+ clean_keychain_claude_macos
283
+ elsif !Gem.win_platform? && system('which secret-tool > /dev/null 2>&1')
284
+ # Linux Secret Service 支持
285
+ clean_keychain_claude_linux
286
+ end
287
+ end
288
+
289
+ def clean_keychain_claude_macos
278
290
 
279
291
  account_name = Etc.getlogin || ENV['USER'] || ENV['LOGNAME']
280
292
  keychain_entries = get_claude_keychain_entries
@@ -305,6 +317,19 @@ module EasyAI
305
317
  end
306
318
  end
307
319
 
320
+ def clean_keychain_claude_linux
321
+ # Linux Secret Service 清理
322
+ service_name = "Claude Code-credentials"
323
+ account_name = Etc.getlogin || ENV['USER'] || ENV['LOGNAME']
324
+
325
+ cmd = "secret-tool clear service '#{service_name}' username '#{account_name}' 2>/dev/null"
326
+ if system(cmd)
327
+ puts " ✓ 已清理Linux Secret Service: #{service_name}"
328
+ end
329
+ rescue => e
330
+ puts " ✗ 清理Linux Secret Service失败: #{e.message}"
331
+ end
332
+
308
333
  def has_claude_json_config?(claude_file)
309
334
  return false unless File.exist?(claude_file)
310
335
 
@@ -327,7 +352,7 @@ module EasyAI
327
352
  end
328
353
 
329
354
  def get_claude_keychain_entries
330
- return [] unless RUBY_PLATFORM.include?('darwin')
355
+ return [] unless RUBY_PLATFORM.include?('darwin') && system('which security > /dev/null 2>&1')
331
356
 
332
357
  entries = []
333
358
  account_name = Etc.getlogin || ENV['USER'] || ENV['LOGNAME']
@@ -367,26 +392,37 @@ module EasyAI
367
392
 
368
393
  def get_shell_files
369
394
  shell_files = []
370
- current_shell = ENV['SHELL']&.split('/')&.last || 'bash'
371
-
372
- case current_shell
373
- when 'zsh'
374
- shell_files << File.expand_path("~/.zshrc")
375
- when 'bash'
376
- bash_profile = File.expand_path("~/.bash_profile")
377
- bashrc = File.expand_path("~/.bashrc")
378
- shell_files << (File.exist?(bash_profile) ? bash_profile : bashrc)
379
- when 'fish'
380
- fish_config = File.expand_path("~/.config/fish/config.fish")
381
- shell_files << fish_config
395
+ if Gem.win_platform?
396
+ # Windows 平台配置
397
+ powershell_profile = File.expand_path("~/Documents/WindowsPowerShell/Microsoft.PowerShell_profile.ps1")
398
+ powershell_core_profile = File.expand_path("~/Documents/PowerShell/Microsoft.PowerShell_profile.ps1")
399
+
400
+ # 检查哪个 PowerShell 配置文件存在
401
+ shell_files << powershell_core_profile if File.exist?(powershell_core_profile)
402
+ shell_files << powershell_profile if File.exist?(powershell_profile)
382
403
  else
383
- shell_files << File.expand_path("~/.profile")
404
+ # Unix/Linux/macOS 平台配置
405
+ current_shell = ENV['SHELL']&.split('/')&.last || 'bash'
406
+
407
+ case current_shell
408
+ when 'zsh'
409
+ shell_files << File.expand_path("~/.zshrc")
410
+ when 'bash'
411
+ bash_profile = File.expand_path("~/.bash_profile")
412
+ bashrc = File.expand_path("~/.bashrc")
413
+ shell_files << (File.exist?(bash_profile) ? bash_profile : bashrc)
414
+ when 'fish'
415
+ fish_config = File.expand_path("~/.config/fish/config.fish")
416
+ shell_files << fish_config
417
+ else
418
+ shell_files << File.expand_path("~/.profile")
419
+ end
420
+
421
+ # 总是添加 ~/.profile 作为备份
422
+ profile_file = File.expand_path("~/.profile")
423
+ shell_files << profile_file unless shell_files.include?(profile_file)
384
424
  end
385
425
 
386
- # 总是添加 ~/.profile 作为备份
387
- profile_file = File.expand_path("~/.profile")
388
- shell_files << profile_file unless shell_files.include?(profile_file)
389
-
390
426
  shell_files
391
427
  end
392
428