easyai 1.7.0 → 2.0.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/AGENTS.md +10 -8
- data/CLAUDE.md +211 -126
- data/README.md +176 -36
- data/easyai.gemspec +12 -9
- data/lib/easyai/base/secret_masker.rb +39 -0
- data/lib/easyai/base/system_info.rb +17 -203
- data/lib/easyai/command/ai_tool_base.rb +218 -0
- data/lib/easyai/command/backup/claude.rb +124 -0
- data/lib/easyai/command/backup.rb +23 -0
- data/lib/easyai/command/claude.rb +72 -357
- data/lib/easyai/command/clean.rb +90 -395
- data/lib/easyai/command/codex.rb +39 -0
- data/lib/easyai/command/gemini.rb +23 -41
- data/lib/easyai/command/restore/claude.rb +150 -0
- data/lib/easyai/command/restore.rb +24 -0
- data/lib/easyai/command/setup.rb +487 -378
- data/lib/easyai/command/update.rb +39 -188
- data/lib/easyai/command/utils.rb +2 -7
- data/lib/easyai/command.rb +1 -3
- data/lib/easyai/config/local_config.rb +161 -0
- data/lib/easyai/version.rb +1 -1
- data/lib/easyai.rb +29 -35
- metadata +20 -37
- data/lib/easyai/auth/authclaude.rb +0 -519
- data/lib/easyai/auth/jpsloginhelper.rb +0 -98
- data/lib/easyai/base/system_keychain.rb +0 -283
- data/lib/easyai/command/gpt.rb +0 -259
- data/lib/easyai/command/utils/export.rb +0 -263
- data/lib/easyai/config/config.rb +0 -357
- data/lib/easyai/config/easyai_config.rb +0 -258
data/README.md
CHANGED
|
@@ -1,75 +1,215 @@
|
|
|
1
1
|
# EasyAI
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Claude / Gemini / Codex 三家 AI CLI 的本地化统一入口。通过单一 `~/.easyai/config.json` 管理凭证、代理与多平台切换;运行时把环境变量仅注入子进程,不污染当前 shell。
|
|
4
|
+
|
|
5
|
+
- 单机本地配置,零远程依赖
|
|
6
|
+
- 同一工具下保存多个平台,运行时一键切换
|
|
7
|
+
- 凭证只入子进程 ENV,不写入 shell rc
|
|
8
|
+
- 内置通用文件加解密工具 `utils encry / decry`
|
|
4
9
|
|
|
5
10
|
## 安装
|
|
6
11
|
|
|
7
12
|
```bash
|
|
8
|
-
gem install easyai
|
|
13
|
+
gem install easyai # Ruby >= 3.0
|
|
14
|
+
easyai setup # 首次运行:交互式生成本地配置
|
|
9
15
|
```
|
|
10
16
|
|
|
11
|
-
|
|
17
|
+
各家 AI CLI 由使用方预先安装:
|
|
18
|
+
|
|
19
|
+
| 工具 | 安装命令 |
|
|
20
|
+
|------|----------|
|
|
21
|
+
| Claude | `npm install -g @anthropic-ai/claude-code` |
|
|
22
|
+
| Gemini | `npm install -g @google/gemini-cli` |
|
|
23
|
+
| Codex | 参考 OpenAI Codex 官方文档 |
|
|
24
|
+
|
|
25
|
+
## 快速开始
|
|
26
|
+
|
|
12
27
|
```bash
|
|
13
|
-
|
|
14
|
-
|
|
28
|
+
easyai setup # 1. 选择工具 → 选择平台 → 填写凭证
|
|
29
|
+
easyai claude # 2. 启动;多平台时交互选择
|
|
30
|
+
easyai gemini --platform=google_official
|
|
31
|
+
easyai codex
|
|
15
32
|
```
|
|
16
33
|
|
|
17
|
-
##
|
|
34
|
+
## 命令参考
|
|
35
|
+
|
|
36
|
+
### 启动 AI 工具
|
|
18
37
|
|
|
19
|
-
### Claude
|
|
20
38
|
```bash
|
|
21
|
-
easyai claude
|
|
22
|
-
easyai claude
|
|
39
|
+
easyai claude # 列出 claude 平台并交互选择
|
|
40
|
+
easyai claude --platform=kimi # 显式指定平台
|
|
41
|
+
easyai claude ./adhoc.json # 一次性 JSON 覆盖(位置参数,单平台扁平 schema)
|
|
42
|
+
easyai claude --verbose # 详细信息
|
|
43
|
+
|
|
44
|
+
easyai gemini # 同上
|
|
45
|
+
easyai codex # 替代 v1.x 的 easyai gpt
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
> 一次性 JSON 是**位置参数**(不是 `--config=` 选项);该模式下 `--platform` 被忽略,未识别的位置参数透传给目标 CLI。
|
|
49
|
+
|
|
50
|
+
单平台扁平 schema(`adhoc.json` 用):
|
|
51
|
+
|
|
52
|
+
```json
|
|
53
|
+
{
|
|
54
|
+
"env": { "ANTHROPIC_AUTH_TOKEN": "sk-..." },
|
|
55
|
+
"proxy": { "HTTP_PROXY": "http://127.0.0.1:7890" }
|
|
56
|
+
}
|
|
23
57
|
```
|
|
24
58
|
|
|
25
|
-
###
|
|
59
|
+
### 配置管理
|
|
60
|
+
|
|
26
61
|
```bash
|
|
27
|
-
easyai
|
|
28
|
-
easyai
|
|
62
|
+
easyai setup # 首次:全量交互;已存在:进入 upsert 选择菜单
|
|
63
|
+
easyai setup --tool=claude # 仅配置某个工具
|
|
64
|
+
easyai setup --add=kimi --tool=claude # 追加或覆盖单平台
|
|
65
|
+
easyai setup --remove=kimi --tool=claude # 删除单平台(platforms 删空时自动清理 tool 键)
|
|
66
|
+
easyai setup --list # 脱敏概览
|
|
67
|
+
easyai setup --edit # 用 $EDITOR / $VISUAL 直接编辑 config.json
|
|
68
|
+
easyai setup --reset # 删除现有 config.json 后重走全量交互
|
|
29
69
|
```
|
|
30
70
|
|
|
31
|
-
|
|
71
|
+
> 敏感字段输入时 `IO#noecho` 不回显;`--list` 输出对 key 名包含 `TOKEN` / `KEY` / `SECRET` / `PASSWORD` 的字段做"前 4 + 后 4 + 长度"脱敏。
|
|
72
|
+
|
|
73
|
+
### 清理 AI CLI 缓存
|
|
74
|
+
|
|
32
75
|
```bash
|
|
33
|
-
easyai
|
|
34
|
-
easyai
|
|
76
|
+
easyai clean # 默认清 claude
|
|
77
|
+
easyai clean codex # 单工具
|
|
78
|
+
easyai clean all # 三家一起清
|
|
79
|
+
easyai clean all --dry-run # 仅预览将删除的路径
|
|
80
|
+
easyai clean all --force # 跳过交互确认
|
|
35
81
|
```
|
|
36
82
|
|
|
37
|
-
|
|
83
|
+
清理目标(不含 `~/.easyai/config.json`,重置 EasyAI 自身配置请用 `easyai setup --reset`):
|
|
84
|
+
|
|
85
|
+
| 工具 | 路径 |
|
|
86
|
+
|------|------|
|
|
87
|
+
| claude | `~/.claude`、`~/.claude.json`、`~/.config/claude` |
|
|
88
|
+
| gemini | `~/.gemini`、`~/.config/gemini` |
|
|
89
|
+
| codex | `~/.codex`、`~/.config/codex` |
|
|
90
|
+
|
|
91
|
+
### 自更新
|
|
38
92
|
|
|
39
|
-
### 文件加密解密
|
|
40
93
|
```bash
|
|
41
|
-
easyai
|
|
42
|
-
easyai
|
|
94
|
+
easyai update # 调用 gem update easyai
|
|
95
|
+
easyai update --verbose # 显示 gem 命令完整输出
|
|
43
96
|
```
|
|
44
97
|
|
|
45
|
-
|
|
98
|
+
`update` 子命令本身会被启动版本检查跳过,避免"过期 → 强制更新 → 更新命令本身被阻塞"的死循环。
|
|
99
|
+
|
|
100
|
+
### 通用加解密工具
|
|
101
|
+
|
|
46
102
|
```bash
|
|
47
|
-
easyai
|
|
48
|
-
easyai
|
|
103
|
+
easyai utils encry <path> # AES-128-ECB 加密单文件或目录
|
|
104
|
+
easyai utils decry <path> # 与 encry 配对解密
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
与 EasyAI 的配置体系彼此独立,可作为通用工具使用。
|
|
108
|
+
|
|
109
|
+
## 配置 schema
|
|
110
|
+
|
|
111
|
+
`~/.easyai/config.json`(v2.0):
|
|
112
|
+
|
|
113
|
+
```json
|
|
114
|
+
{
|
|
115
|
+
"version": "2.0.0",
|
|
116
|
+
"claude": {
|
|
117
|
+
"platforms": {
|
|
118
|
+
"claude_official": {
|
|
119
|
+
"env": {
|
|
120
|
+
"ANTHROPIC_AUTH_TOKEN": "sk-ant-...",
|
|
121
|
+
"ANTHROPIC_BASE_URL": "https://api.anthropic.com"
|
|
122
|
+
},
|
|
123
|
+
"proxy": {
|
|
124
|
+
"HTTP_PROXY": "http://127.0.0.1:7890",
|
|
125
|
+
"HTTPS_PROXY": "http://127.0.0.1:7890"
|
|
126
|
+
}
|
|
127
|
+
},
|
|
128
|
+
"kimi": {
|
|
129
|
+
"env": {
|
|
130
|
+
"ANTHROPIC_AUTH_TOKEN": "sk-...",
|
|
131
|
+
"ANTHROPIC_BASE_URL": "https://api.moonshot.cn/anthropic"
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
},
|
|
136
|
+
"gemini": {
|
|
137
|
+
"platforms": {
|
|
138
|
+
"google_official": { "env": { "GEMINI_API_KEY": "AIza..." } }
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
"codex": {
|
|
142
|
+
"platforms": {
|
|
143
|
+
"openai_official": { "env": { "OPENAI_API_KEY": "sk-..." } }
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
49
147
|
```
|
|
50
148
|
|
|
51
|
-
|
|
149
|
+
要点:
|
|
150
|
+
|
|
151
|
+
- `platforms` 字典即真相:runtime 不维护硬编码白名单,可手编 `config.json` 添加任何自定义平台名(仅 `setup` 内置 `KNOWN_PLATFORMS` 用于交互引导)
|
|
152
|
+
- `env` 透传不限制,可塞任意 KV(如 `ANTHROPIC_LOG=debug`)
|
|
153
|
+
- `proxy` 可省略;同时设大小写 `HTTP_PROXY` / `http_proxy`
|
|
154
|
+
- `PATH` / `HOME` / `USER` / `SHELL` 不会被 config 覆盖
|
|
155
|
+
- `save` 后自动 `chmod 600`(非 Windows)
|
|
156
|
+
|
|
157
|
+
详见 [docs/设计文档.md](docs/设计文档.md) 第 3 章。
|
|
52
158
|
|
|
53
|
-
|
|
54
|
-
- 对应的 AI CLI 工具:
|
|
55
|
-
- Claude: `npm install -g @anthropic-ai/claude-code`
|
|
56
|
-
- Gemini: `npm install -g @google/gemini-cli`
|
|
57
|
-
- OpenAI: `pip install openai`
|
|
159
|
+
## 调试与环境变量
|
|
58
160
|
|
|
59
|
-
|
|
161
|
+
| 变量 | 作用 |
|
|
162
|
+
|------|------|
|
|
163
|
+
| `EASYAI_DEBUG=1` | 启用调试日志 |
|
|
164
|
+
| `EASYAI_VERBOSE=1` | 等价于 `--verbose` |
|
|
165
|
+
| `EASYAI_SKIP_FORCE_UPDATE=1` | 跳过启动时的强制版本检查(网络不通时使用) |
|
|
60
166
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
167
|
+
## 从 v1.x 升级
|
|
168
|
+
|
|
169
|
+
v2.0 是破坏性升级:
|
|
170
|
+
|
|
171
|
+
- `easyai gpt` 重命名为 `easyai codex`(子进程命令也是 `codex`)
|
|
172
|
+
- 配置位置:`~/.easyai/config.yml` → `~/.easyai/config.json`,**不会自动迁移**,需手动 `easyai setup` 重建(保留旧 yml 不会冲突)
|
|
173
|
+
- 删除:远程加密配置仓库、JPS 登录、`easyai utils export`、`easyai setup --branch / --list-users`、Claude 官方认证环境检查、系统钥匙串密码缓存
|
|
174
|
+
- 运行时依赖 `webrick` / `jpsclient` 已删除
|
|
175
|
+
- Ruby 最低版本:`>= 3.0.0`
|
|
176
|
+
|
|
177
|
+
升级步骤:
|
|
178
|
+
|
|
179
|
+
```bash
|
|
180
|
+
gem update easyai
|
|
181
|
+
easyai setup # 重新交互配置
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
需保留 v1.x:`gem install easyai -v 1.7.0`。
|
|
65
185
|
|
|
66
186
|
## 开发
|
|
67
187
|
|
|
68
188
|
```bash
|
|
69
|
-
|
|
70
|
-
|
|
189
|
+
bundle install # 装开发依赖
|
|
190
|
+
bundle exec rspec # 运行测试套件(发布前必跑)
|
|
191
|
+
./script/install_local.sh # 本地构建并安装(冒烟)
|
|
71
192
|
```
|
|
72
193
|
|
|
194
|
+
新增功能遵循 TDD:先在 `spec/` 下补测试 → 实现 → 全绿后提交。
|
|
195
|
+
|
|
196
|
+
发布脚本:
|
|
197
|
+
|
|
198
|
+
| 脚本 | 用途 |
|
|
199
|
+
|------|------|
|
|
200
|
+
| `script/local_release.sh` | 本地完整发布:提交 → 合并到 master → 打 tag → `gem push` |
|
|
201
|
+
| `script/autoci_release.sh` | 仅本地提交 / 打 tag / 推送,由 Gitee CI 完成构建与推送 |
|
|
202
|
+
|
|
203
|
+
## 文档索引
|
|
204
|
+
|
|
205
|
+
| 文档 | 说明 |
|
|
206
|
+
|------|------|
|
|
207
|
+
| [docs/需求文档.md](docs/需求文档.md) | 功能与非功能需求(v2.0) |
|
|
208
|
+
| [docs/设计文档.md](docs/设计文档.md) | 架构、模块与关键流程(v2.0) |
|
|
209
|
+
| [docs/plan.md](docs/plan.md) | v2.0 重构实施计划 |
|
|
210
|
+
| [CLAUDE.md](CLAUDE.md) | 给 Claude Code 的工程约定 |
|
|
211
|
+
| [AGENTS.md](AGENTS.md) | 团队协作与提交规范 |
|
|
212
|
+
|
|
73
213
|
## License
|
|
74
214
|
|
|
75
|
-
MIT
|
|
215
|
+
MIT
|
data/easyai.gemspec
CHANGED
|
@@ -6,23 +6,26 @@ Gem::Specification.new do |spec|
|
|
|
6
6
|
spec.authors = ['Wade']
|
|
7
7
|
spec.email = ['wade@example.com']
|
|
8
8
|
|
|
9
|
-
spec.summary = '
|
|
10
|
-
spec.description =
|
|
9
|
+
spec.summary = 'Claude / Gemini / Codex 三家 AI CLI 的本地化统一入口'
|
|
10
|
+
spec.description = <<~DESC
|
|
11
|
+
EasyAI 是一个 Ruby CLI 包装器,为 Anthropic Claude、Google Gemini、OpenAI Codex 三家命令行工具提供统一入口。
|
|
12
|
+
v2.0 起聚焦单机本地化:无远程下发链路,通过单一 ~/.easyai/config.json 集中管理多家 CLI 与多平台凭证、代理;
|
|
13
|
+
运行时仅向子进程注入环境变量,不污染当前 shell。
|
|
14
|
+
DESC
|
|
11
15
|
spec.homepage = 'https://github.com/wade/easyai'
|
|
12
16
|
spec.license = 'MIT'
|
|
13
|
-
|
|
14
|
-
spec.required_ruby_version = '>=
|
|
15
|
-
|
|
17
|
+
|
|
18
|
+
spec.required_ruby_version = '>= 3.0.0'
|
|
19
|
+
|
|
16
20
|
spec.files = Dir['lib/**/*', 'bin/*', '*.md', '*.gemspec']
|
|
17
21
|
spec.bindir = 'bin'
|
|
18
22
|
spec.executables = ['easyai']
|
|
19
23
|
spec.require_paths = ['lib']
|
|
20
|
-
|
|
24
|
+
|
|
21
25
|
spec.add_dependency 'claide', '~> 1.0'
|
|
22
26
|
spec.add_dependency 'colored2', '~> 3.1'
|
|
23
|
-
spec.add_dependency '
|
|
24
|
-
|
|
25
|
-
|
|
27
|
+
spec.add_dependency 'base64', '~> 0.2'
|
|
28
|
+
|
|
26
29
|
spec.add_development_dependency 'bundler', '~> 2.0'
|
|
27
30
|
spec.add_development_dependency 'rake', '~> 13.0'
|
|
28
31
|
spec.add_development_dependency 'rspec', '~> 3.0'
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module EasyAI
|
|
4
|
+
module Base
|
|
5
|
+
# 敏感字段脱敏工具。
|
|
6
|
+
#
|
|
7
|
+
# 凡是 key 名包含 TOKEN / KEY / SECRET / PASSWORD(大小写不敏感)的环境变量,
|
|
8
|
+
# 在向 stdout 打印时都应通过本模块脱敏,避免 API Key 明文出现在终端历史 / 截图 / 日志。
|
|
9
|
+
#
|
|
10
|
+
# 脱敏规则:
|
|
11
|
+
# - 长度 ≤ 8:完全隐藏,仅保留长度
|
|
12
|
+
# - 长度 > 8:前 4 + "..." + 后 4 + 长度
|
|
13
|
+
module SecretMasker
|
|
14
|
+
SENSITIVE_KEY_PATTERN = /(TOKEN|KEY|SECRET|PASSWORD)/i.freeze
|
|
15
|
+
|
|
16
|
+
module_function
|
|
17
|
+
|
|
18
|
+
def sensitive_key?(key)
|
|
19
|
+
SENSITIVE_KEY_PATTERN.match?(key.to_s)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def mask(value)
|
|
23
|
+
s = value.to_s
|
|
24
|
+
len = s.length
|
|
25
|
+
return "*** (len=#{len})" if len <= 8
|
|
26
|
+
|
|
27
|
+
"#{s[0, 4]}...#{s[-4, 4]} (len=#{len})"
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# 按 key 是否敏感,决定原样返回还是脱敏返回
|
|
31
|
+
def format_value(key, value)
|
|
32
|
+
return value.to_s unless value.is_a?(String)
|
|
33
|
+
return value unless sensitive_key?(key)
|
|
34
|
+
|
|
35
|
+
mask(value)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -1,11 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'rbconfig'
|
|
2
4
|
require 'etc'
|
|
3
5
|
|
|
4
6
|
module EasyAI
|
|
5
7
|
module Base
|
|
8
|
+
# 跨平台 OS 判定与命令检测。
|
|
9
|
+
#
|
|
10
|
+
# v2.0 起仅保留单机本地化运行所需能力:
|
|
11
|
+
# - 平台判定:macos? / windows? / linux?
|
|
12
|
+
# - PATH 检测:which_command(cmd)
|
|
13
|
+
#
|
|
14
|
+
# v1.x 中用于远程模式的方法(claude_auth_check_details / region_info /
|
|
15
|
+
# is_us_region? / is_english_environment? / shell_config_files 等)已删除。
|
|
6
16
|
class SystemInfo
|
|
7
17
|
class << self
|
|
8
|
-
#
|
|
18
|
+
# 返回平台符号::macos / :windows / :linux / :bsd / :unknown
|
|
9
19
|
def platform
|
|
10
20
|
@platform ||= detect_platform
|
|
11
21
|
end
|
|
@@ -22,75 +32,7 @@ module EasyAI
|
|
|
22
32
|
platform == :linux
|
|
23
33
|
end
|
|
24
34
|
|
|
25
|
-
#
|
|
26
|
-
def current_user
|
|
27
|
-
@current_user ||= Etc.getlogin || ENV['USER'] || ENV['LOGNAME']
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
def current_shell
|
|
31
|
-
@current_shell ||= ENV['SHELL']&.split('/')&.last || 'bash'
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
def shell_config_files
|
|
35
|
-
@shell_config_files ||= detect_shell_config_files
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
# === 地区和语言检测(仅在需要时调用)===
|
|
39
|
-
def region_info
|
|
40
|
-
@region_info ||= detect_region_info
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
def is_us_region?
|
|
44
|
-
region_info[:is_us]
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
# 检查是否为英语环境
|
|
48
|
-
def is_english_environment?
|
|
49
|
-
info = region_info
|
|
50
|
-
|
|
51
|
-
case platform
|
|
52
|
-
when :macos
|
|
53
|
-
# macOS: 检查 locale 和首选语言
|
|
54
|
-
locale_english = info[:locale].to_s.start_with?('en_') || info[:locale].to_s.start_with?('en.')
|
|
55
|
-
apple_locale_english = info[:apple_locale].to_s.start_with?('en_')
|
|
56
|
-
first_lang_english = info[:apple_languages].to_s.start_with?('en')
|
|
57
|
-
|
|
58
|
-
# 任一为英语即可
|
|
59
|
-
locale_english || apple_locale_english || first_lang_english
|
|
60
|
-
when :windows
|
|
61
|
-
# Windows: 检查系统和用户地区
|
|
62
|
-
system_english = info[:system_locale].to_s.start_with?('en-')
|
|
63
|
-
user_english = info[:user_locale].to_s.start_with?('en-')
|
|
64
|
-
locale_english = info[:locale].to_s.start_with?('en_') || info[:locale].to_s.start_with?('en.')
|
|
65
|
-
|
|
66
|
-
# 任一为英语即可
|
|
67
|
-
system_english || user_english || locale_english
|
|
68
|
-
else
|
|
69
|
-
# Linux/其他: 检查 LANG 环境变量
|
|
70
|
-
locale = info[:locale].to_s
|
|
71
|
-
locale.start_with?('en_') || locale.start_with?('en.')
|
|
72
|
-
end
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
# 检查是否满足 claude_auth 的环境要求
|
|
76
|
-
def meets_claude_auth_requirements?
|
|
77
|
-
!windows? && is_us_region? && is_english_environment?
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
# 获取 claude_auth 环境检查详情
|
|
81
|
-
def claude_auth_check_details
|
|
82
|
-
{
|
|
83
|
-
os_supported: !windows?,
|
|
84
|
-
os_name: platform.to_s.capitalize,
|
|
85
|
-
is_us_region: is_us_region?,
|
|
86
|
-
region: region_info[:region],
|
|
87
|
-
is_english: is_english_environment?,
|
|
88
|
-
locale: region_info[:locale],
|
|
89
|
-
meets_requirements: meets_claude_auth_requirements?
|
|
90
|
-
}
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
# === 命令检测 ===
|
|
35
|
+
# 检测命令是否存在于 PATH。返回 true / false
|
|
94
36
|
def which_command(cmd)
|
|
95
37
|
if windows?
|
|
96
38
|
system("where #{cmd} >nul 2>&1")
|
|
@@ -113,143 +55,15 @@ module EasyAI
|
|
|
113
55
|
when /bsd/i
|
|
114
56
|
:bsd
|
|
115
57
|
else
|
|
116
|
-
# 回退到 RUBY_PLATFORM
|
|
117
58
|
case RUBY_PLATFORM
|
|
118
|
-
when /darwin/
|
|
119
|
-
|
|
120
|
-
when /
|
|
121
|
-
|
|
122
|
-
when /linux/
|
|
123
|
-
:linux
|
|
124
|
-
else
|
|
125
|
-
:unknown
|
|
126
|
-
end
|
|
127
|
-
end
|
|
128
|
-
end
|
|
129
|
-
|
|
130
|
-
def detect_shell_config_files
|
|
131
|
-
files = []
|
|
132
|
-
|
|
133
|
-
case current_shell
|
|
134
|
-
when 'zsh'
|
|
135
|
-
files << File.expand_path("~/.zshrc")
|
|
136
|
-
when 'bash'
|
|
137
|
-
bash_profile = File.expand_path("~/.bash_profile")
|
|
138
|
-
bashrc = File.expand_path("~/.bashrc")
|
|
139
|
-
files << (File.exist?(bash_profile) ? bash_profile : bashrc)
|
|
140
|
-
when 'fish'
|
|
141
|
-
fish_config = File.expand_path("~/.config/fish/config.fish")
|
|
142
|
-
files << fish_config
|
|
143
|
-
|
|
144
|
-
# 确保 fish 配置目录存在
|
|
145
|
-
fish_dir = File.dirname(fish_config)
|
|
146
|
-
unless File.exist?(fish_dir)
|
|
147
|
-
require 'fileutils'
|
|
148
|
-
FileUtils.mkdir_p(fish_dir)
|
|
59
|
+
when /darwin/ then :macos
|
|
60
|
+
when /mswin|mingw|cygwin/ then :windows
|
|
61
|
+
when /linux/ then :linux
|
|
62
|
+
else :unknown
|
|
149
63
|
end
|
|
150
|
-
else
|
|
151
|
-
files << File.expand_path("~/.profile")
|
|
152
|
-
end
|
|
153
|
-
|
|
154
|
-
# 总是添加 ~/.profile 作为备份
|
|
155
|
-
profile_file = File.expand_path("~/.profile")
|
|
156
|
-
files << profile_file unless files.include?(profile_file)
|
|
157
|
-
|
|
158
|
-
files
|
|
159
|
-
end
|
|
160
|
-
|
|
161
|
-
def detect_region_info
|
|
162
|
-
case platform
|
|
163
|
-
when :macos
|
|
164
|
-
detect_macos_region
|
|
165
|
-
when :windows
|
|
166
|
-
detect_windows_region
|
|
167
|
-
when :linux
|
|
168
|
-
detect_linux_region
|
|
169
|
-
else
|
|
170
|
-
detect_generic_region
|
|
171
64
|
end
|
|
172
65
|
end
|
|
173
|
-
|
|
174
|
-
def detect_macos_region
|
|
175
|
-
locale = ENV['LANG'] || ENV['LC_ALL'] || ''
|
|
176
|
-
apple_locale = `defaults read -g AppleLocale 2>/dev/null`.chomp
|
|
177
|
-
apple_languages = `defaults read -g AppleLanguages 2>/dev/null`.chomp
|
|
178
|
-
|
|
179
|
-
# 解析语言列表
|
|
180
|
-
first_language = apple_languages.match(/"([^"]+)"/)&.captures&.first || ''
|
|
181
|
-
|
|
182
|
-
# 判断是否为美国地区
|
|
183
|
-
is_us = apple_locale.start_with?('en_US') ||
|
|
184
|
-
locale.start_with?('en_US') ||
|
|
185
|
-
first_language.start_with?('en-US')
|
|
186
|
-
|
|
187
|
-
{
|
|
188
|
-
locale: locale,
|
|
189
|
-
apple_locale: apple_locale,
|
|
190
|
-
apple_languages: first_language,
|
|
191
|
-
region: apple_locale.split('_').last || 'Unknown',
|
|
192
|
-
is_us: is_us,
|
|
193
|
-
platform: 'macOS'
|
|
194
|
-
}
|
|
195
|
-
end
|
|
196
|
-
|
|
197
|
-
def detect_windows_region
|
|
198
|
-
locale = ENV['LANG'] || ''
|
|
199
|
-
|
|
200
|
-
# Windows PowerShell 命令获取地区
|
|
201
|
-
system_locale = `powershell -Command "Get-WinSystemLocale | Select-Object -ExpandProperty Name" 2>nul`.chomp
|
|
202
|
-
user_locale = `powershell -Command "Get-Culture | Select-Object -ExpandProperty Name" 2>nul`.chomp
|
|
203
|
-
|
|
204
|
-
# 判断是否为美国地区
|
|
205
|
-
is_us = system_locale.start_with?('en-US') ||
|
|
206
|
-
user_locale.start_with?('en-US') ||
|
|
207
|
-
locale.start_with?('en_US')
|
|
208
|
-
|
|
209
|
-
{
|
|
210
|
-
locale: locale,
|
|
211
|
-
system_locale: system_locale,
|
|
212
|
-
user_locale: user_locale,
|
|
213
|
-
region: (system_locale.split('-').last || user_locale.split('-').last || 'Unknown'),
|
|
214
|
-
is_us: is_us,
|
|
215
|
-
platform: 'Windows'
|
|
216
|
-
}
|
|
217
|
-
end
|
|
218
|
-
|
|
219
|
-
def detect_linux_region
|
|
220
|
-
locale = ENV['LANG'] || ENV['LC_ALL'] || ''
|
|
221
|
-
|
|
222
|
-
# 尝试读取 /etc/locale.conf
|
|
223
|
-
locale_conf = File.exist?('/etc/locale.conf') ? File.read('/etc/locale.conf').strip : ''
|
|
224
|
-
|
|
225
|
-
# 使用 localectl 命令
|
|
226
|
-
system_locale = `localectl status 2>/dev/null | grep "System Locale" | cut -d= -f2`.chomp
|
|
227
|
-
|
|
228
|
-
# 判断是否为美国地区
|
|
229
|
-
is_us = locale.start_with?('en_US') ||
|
|
230
|
-
system_locale.start_with?('en_US') ||
|
|
231
|
-
locale_conf.include?('en_US')
|
|
232
|
-
|
|
233
|
-
{
|
|
234
|
-
locale: locale,
|
|
235
|
-
system_locale: system_locale || locale_conf,
|
|
236
|
-
region: locale.split('_').last&.split('.').first || 'Unknown',
|
|
237
|
-
is_us: is_us,
|
|
238
|
-
platform: 'Linux'
|
|
239
|
-
}
|
|
240
|
-
end
|
|
241
|
-
|
|
242
|
-
def detect_generic_region
|
|
243
|
-
locale = ENV['LANG'] || ENV['LC_ALL'] || 'Unknown'
|
|
244
|
-
|
|
245
|
-
{
|
|
246
|
-
locale: locale,
|
|
247
|
-
region: locale.split('_').last&.split('.').first || 'Unknown',
|
|
248
|
-
is_us: locale.start_with?('en_US'),
|
|
249
|
-
platform: 'Unknown'
|
|
250
|
-
}
|
|
251
|
-
end
|
|
252
66
|
end
|
|
253
67
|
end
|
|
254
68
|
end
|
|
255
|
-
end
|
|
69
|
+
end
|