ai-account-switch 1.9.0 → 1.11.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.
Binary file
package/CLAUDE.md ADDED
@@ -0,0 +1,338 @@
1
+ # CLAUDE.md
2
+
3
+ 本文件为 Claude Code (claude.ai/code) 在此仓库中工作时提供指导。
4
+
5
+ ## 项目概述
6
+
7
+ AI Account Switch (ais) 是一个跨平台的命令行工具,用于在项目级别管理和切换不同的 AI 服务账号(Claude、Codex、CCR、Droids)。它会自动为各种 AI 工具生成配置文件,并提供 CLI 和 Web UI 两种界面。
8
+
9
+ ## 开发命令
10
+
11
+ ### 测试
12
+ ```bash
13
+ # 本地运行 CLI 工具
14
+ npm test
15
+
16
+ # 全局链接以便测试
17
+ npm link
18
+ ```
19
+
20
+ ### 安装
21
+ ```bash
22
+ # 安装依赖
23
+ npm install
24
+
25
+ # 开发时全局链接
26
+ npm link
27
+ ```
28
+
29
+ ## 架构设计
30
+
31
+ ### 核心组件
32
+
33
+ **ConfigManager (`src/config.js`)**
34
+ - 中央配置管理类
35
+ - 处理全局配置 (`~/.ai-account-switch/config.json`) 和项目配置 (`.ais-project-config`)
36
+ - 实现智能目录检测(类似 git 向上搜索)
37
+ - 管理账号 ID、MCP 服务器和模型组
38
+ - 关键方法:
39
+ - `findProjectRoot()` - 向上搜索项目配置
40
+ - `readGlobalConfig()` / `saveGlobalConfig()` - 全局账号存储
41
+ - `readProjectConfig()` / `saveProjectConfig()` - 项目特定设置
42
+ - `generateClaudeConfig()` - 创建 `.claude/settings.local.json`
43
+ - `generateCodexProfile()` - 创建 Codex TOML profiles
44
+ - `generateCCRConfig()` - 更新 CCR router 配置
45
+ - `generateDroidsConfig()` - 创建 Droids JSON 配置
46
+
47
+ **命令模块 (`src/commands/`)**
48
+ - 模块化命令结构,单一职责
49
+ - `account.js` - 账号 CRUD 操作(添加、列表、使用、删除、导出)
50
+ - `model.js` - Claude 账号的模型组管理
51
+ - `mcp.js` - MCP (Model Context Protocol) 服务器管理
52
+ - `env.js` - 环境变量管理(列表、添加、设置、删除、显示、清空、编辑)
53
+ - `utility.js` - 诊断和辅助命令(doctor、paths)
54
+ - `helpers.js` - 共享工具函数(maskApiKey、验证)
55
+ - `index.js` - 统一导出接口
56
+
57
+ **Web UI 服务器 (`src/ui-server.js`)**
58
+ - 内嵌 HTML/CSS/JS 的 HTTP 服务器
59
+ - 提供账号管理的单页应用
60
+ - 账号、MCP 和环境变量操作的 REST API 端点
61
+ - 使用随机高位端口(49152-65535)避免冲突
62
+ - 支持双语界面(中文/英文)
63
+
64
+ **CLI 入口点 (`src/index.js`)**
65
+ - 基于 Commander.js 的 CLI 界面
66
+ - 定义所有命令和子命令
67
+ - 双语帮助文本(中文/英文)
68
+
69
+ ### 生成的配置文件
70
+
71
+ **Claude Code 集成**
72
+ - `.claude/settings.local.json` - 项目级 Claude 配置
73
+ - 包含 `env.ANTHROPIC_AUTH_TOKEN` 和 `env.ANTHROPIC_BASE_URL`
74
+ - 包含账号配置中的自定义环境变量
75
+ - 自动添加到 `.gitignore`
76
+
77
+ **Codex 集成**
78
+ - `~/.codex/config.toml` - 全局 Codex profiles
79
+ - `.codex-profile` - 项目级 profile 引用
80
+ - 使用 OpenAI 兼容的 `wire_api = "chat"` 格式
81
+ - 支持三种 wire API 模式:chat、responses、env
82
+ - 自动为仅域名的 URL 添加 `/v1`
83
+
84
+ **CCR (Claude Code Router) 集成**
85
+ - `~/.claude-code-router/config.json` - CCR 配置
86
+ - 更新 Providers 和 Router 部分
87
+ - 配置更改后自动重启 CCR router
88
+ - 路由到本地 CCR 端点(默认端口 3456)
89
+
90
+ **Droids 集成**
91
+ - `.droids/config.json` - 项目级 Droids 配置
92
+ - 简单的模型配置结构
93
+
94
+ **MCP 集成**
95
+ - 在 `~/.ai-account-switch/config.json` 中全局管理
96
+ - 项目级启用状态在项目配置中跟踪
97
+ - 同步到 `.claude/settings.local.json` 的 `mcpServers` 键下
98
+ - 支持 stdio、sse 和 http 服务器类型
99
+ - 支持三种作用范围(scope):
100
+ - `local`(默认):仅当前项目可用,配置存储在全局,启用状态在项目配置中
101
+ - `project`:与项目成员共享,配置存储在项目配置的 `projectMcpServers` 中
102
+ - `user`:当前用户所有项目可用,配置存储在全局,自动在所有项目中启用
103
+
104
+ **环境变量管理 (env)**
105
+ - 项目级别:存储在 `.claude/settings.local.json` 的 `env` 属性中
106
+ - 用户级别:存储在 `~/.claude/settings.json` 的 `env` 属性中(跨平台兼容)
107
+ - 支持的命令:
108
+ - `ais env list` - 列出所有环境变量(项目和用户级别)
109
+ - `ais env add` - 交互式添加环境变量
110
+ - `ais env set <key> <value>` - 直接设置环境变量
111
+ - `ais env remove` / `ais env rm` - 交互式删除环境变量
112
+ - `ais env unset <key>` - 直接删除环境变量
113
+ - `ais env show <key>` - 显示特定环境变量的值
114
+ - `ais env clear` - 清空所有环境变量
115
+ - `ais env edit` - 交互式编辑环境变量
116
+ - 配置合并:更新环境变量时保留其他配置设置(permissions、statusLine、enabledPlugins)
117
+ - Web UI 支持:提供环境变量的可视化管理和编辑
118
+
119
+ ### 账号类型及其行为
120
+
121
+ **Claude**
122
+ - 标准 Anthropic API 集成
123
+ - 支持包含多个模型配置的复杂模型组
124
+ - 环境变量传递给 Claude Code
125
+
126
+ **Codex**
127
+ - 在 `~/.codex/config.toml` 中生成 TOML profiles
128
+ - 创建项目级 `.codex-profile` 文件
129
+ - 处理重复 profile 清理
130
+ - 智能 URL 处理(需要时自动添加 `/v1`)
131
+ - 支持 wire_api 模式选择(chat/responses/env)
132
+
133
+ **CCR (Claude Code Router)**
134
+ - 同时更新 Provider 和 Router 配置
135
+ - 需要三种模型类型:default、background、think
136
+ - 在 Provider 配置中自动去重模型
137
+ - 通过 `ccr restart` 自动重启 CCR router
138
+
139
+ **Droids**
140
+ - 简单的配置结构
141
+ - 可选的模型规格
142
+ - 项目级 `.droids/config.json`
143
+
144
+ ### 关键设计模式
145
+
146
+ **智能目录检测**
147
+ - 类似 git 的 `.git` 目录搜索
148
+ - `findProjectRoot()` 向上遍历目录树
149
+ - 允许命令在任何子目录中工作
150
+ - 如果未找到项目配置则回退到当前目录
151
+
152
+ **账号 ID 系统**
153
+ - 自动递增的数字 ID(从 1 开始)
154
+ - 允许通过 `ais use 1` 快速切换而不是完整名称
155
+ - 向后兼容现有账号
156
+ - 首次运行时在迁移期间分配 ID
157
+
158
+ **模型组**
159
+ - 每个 Claude 账号可以有多个模型组
160
+ - 活动模型组决定使用哪些模型
161
+ - 切换模型组会自动更新 Claude 配置
162
+ - 支持 DEFAULT_MODEL 作为未指定模型的回退
163
+
164
+ **MCP 服务器管理**
165
+ - 全局服务器定义,项目级启用
166
+ - 与 `.mcp.json` 双向同步(导入/导出)
167
+ - 通过 spawn/HTTP 请求进行连接测试
168
+ - 三种服务器类型,不同的配置模式
169
+ - 支持三种作用范围(scope):
170
+ - `local`:仅当前项目可用,服务器定义在全局配置,启用状态在项目配置中
171
+ - `project`:与项目成员共享,服务器定义存储在项目配置的 `projectMcpServers` 中
172
+ - `user`:当前用户所有项目可用,服务器定义在全局配置,自动在所有项目中启用
173
+
174
+ **配置隔离**
175
+ - 全局账号存储在用户主目录
176
+ - 项目配置从不包含敏感数据(仅账号引用)
177
+ - 自动更新 `.gitignore` 以保护敏感文件
178
+ - 通过 `os.homedir()` 和 `path.join()` 处理跨平台路径
179
+
180
+ ## 重要实现细节
181
+
182
+ ### Wire API 模式处理(Codex)
183
+ 添加 Codex 账号时,工具支持三种 wire API 模式:
184
+ - `chat`(默认)- OpenAI 兼容的聊天格式
185
+ - `responses` - Anthropic responses 格式
186
+ - `env` - 使用环境变量进行身份验证
187
+
188
+ 模式存储在账号配置中,并在生成 Codex profiles 时应用。
189
+
190
+ ### URL 规范化(Codex)
191
+ 工具智能处理 API URL:
192
+ - 仅域名的 URL(如 `https://api.example.com`)→ 自动添加 `/v1`
193
+ - 带路径的 URL(如 `https://api.example.com/v2`)→ 保持原样
194
+ - 这可以防止常见的 Cloudflare 400 错误
195
+
196
+ ### CCR Router 集成
197
+ 使用 CCR 账号时:
198
+ 1. 使用 Provider 和 Router 配置更新 `~/.claude-code-router/config.json`
199
+ 2. 从配置中动态读取 CCR 端口(默认 3456)
200
+ 3. 生成指向 `http://127.0.0.1:{port}` 的 `.claude/settings.local.json`
201
+ 4. 尝试通过 `ccr restart` 命令重启 CCR
202
+ 5. 如果未找到 CCR CLI,提供清晰的后续步骤
203
+
204
+ ### Doctor 命令
205
+ `ais doctor` 命令通过检查以下内容诊断配置问题:
206
+ - 当前目录和项目根目录
207
+ - AIS 项目配置是否存在
208
+ - Claude Code 配置(`.claude/settings.local.json`)
209
+ - 全局 Claude 配置(`~/.claude/settings.json`)
210
+ - 账号类型特定的配置(Codex TOML、CCR JSON、Droids JSON)
211
+ - 提供可操作的修复建议
212
+
213
+ ### Web UI 架构
214
+ UI 服务器将所有 HTML/CSS/JS 嵌入单个文件:
215
+ - 无外部依赖或构建步骤
216
+ - 为根路径提供静态 HTML
217
+ - 数据操作的 REST API 端点
218
+ - 自动端口冲突解决
219
+ - 启动时自动打开浏览器
220
+
221
+ ### 环境变量管理实现细节
222
+ **跨平台路径处理**
223
+ - 使用 `ConfigManager.getClaudeUserConfigPath()` 方法获取用户配置文件路径
224
+ - 优先级顺序:`~/.claude/settings.json` > 平台特定路径 > `~/.claude.json`(遗留)
225
+ - Windows 支持:`%APPDATA%\claude\settings.json`
226
+ - macOS/Linux 支持:`~/.config/claude/settings.json`
227
+
228
+ **配置合并策略**
229
+ - 读取现有配置文件,保留非 `env` 属性设置
230
+ - 仅更新 `env` 属性,不覆盖 `permissions`、`statusLine`、`enabledPlugins` 等
231
+ - 确保 `env` 属性存在,避免 `undefined` 错误
232
+
233
+ **命令模式**
234
+ - 交互式命令(add、remove、edit):使用 Inquirer.js 提供友好的用户界面
235
+ - 非交互式命令(set、unset、show):适合脚本和自动化
236
+ - 支持通过 `--level` 选项指定项目或用户级别
237
+
238
+ **安全性考虑**
239
+ - 显示环境变量时自动屏蔽包含 `KEY` 或 `TOKEN` 的变量值
240
+ - 支持在 Web UI 中安全地编辑敏感变量
241
+
242
+ ## 测试注意事项
243
+
244
+ 测试或修改时:
245
+ - 测试跨平台行为(macOS、Linux、Windows)
246
+ - 验证 `.gitignore` 更新正常工作
247
+ - 从各种子目录测试目录检测
248
+ - 确保账号 ID 迁移适用于现有配置
249
+ - 测试所有账号类型(Claude、Codex、CCR、Droids)
250
+ - 验证 MCP 服务器连接测试
251
+ - 在不同浏览器中测试 Web UI
252
+ - 测试环境变量命令的跨平台路径解析
253
+ - 验证环境变量配置合并不会丢失其他设置
254
+ - 测试环境变量在项目和用户级别的正确隔离
255
+
256
+ ## 常见陷阱
257
+
258
+ **Codex Profile 重复**
259
+ - 写入新 profile 之前必须清理旧 profile
260
+ - 使用正则表达式匹配包括嵌套内容的整个 profile 部分
261
+ - 使用各种 TOML 结构进行测试
262
+
263
+ **CCR Router 重启**
264
+ - CCR CLI 必须已安装并在 PATH 中
265
+ - 优雅处理未找到 `ccr` 命令的情况
266
+ - 提供手动重启的清晰说明
267
+
268
+ **路径处理**
269
+ - 始终使用 `path.join()` 以实现跨平台兼容性
270
+ - 使用 `os.homedir()` 而不是 `~` 扩展
271
+ - 正确处理路径中的空格
272
+
273
+ **配置合并**
274
+ - 更新配置时,保留现有的非 AIS 设置
275
+ - 不要覆盖用户的自定义权限或其他设置
276
+ - 仅更新 AIS 管理的特定部分
277
+
278
+ **环境变量配置处理**
279
+ - 读取配置时确保 `env` 属性存在,避免 `undefined` 错误
280
+ - 写入配置时必须先读取现有配置,只合并 `env` 属性
281
+ - 使用跨平台兼容的路径方法 `ConfigManager.getClaudeUserConfigPath()`
282
+ - 不同操作系统的用户配置文件路径可能不同(Windows 使用 APPDATA,macOS/Linux 使用 .config)
283
+
284
+ **终端颜色使用规范**
285
+ ⚠️ **重要:所有 CLI 输出必须严格遵守以下颜色规范**
286
+
287
+ | 颜色 | 用途 | 示例 |
288
+ |------|------|------|
289
+ | `chalk.green` | ✅ 成功操作、可用状态、完成标记 | `console.log(chalk.green('✓ Account added successfully!'))` |
290
+ | `chalk.red` | ❌ 错误、失败、不可用状态 | `console.error(chalk.red('✗ Error:'), error.message)` |
291
+ | `chalk.yellow` | ⚠️ 警告、取消操作、未配置、信息提示 | `console.log(chalk.yellow('⚠ No account set'))` |
292
+ | `chalk.cyan` | 📋 **标签**(如 "Path:", "Config:", "Level:")、信息标题、变量名 | `` `${chalk.cyan('Path:')} ${path}` `` |
293
+ | `chalk.yellow` | **变量值** | `` `${chalk.cyan(key)} = ${chalk.yellow(value)}` `` |
294
+ | `chalk.gray` | 辅助信息、次要内容、bullet 点 | `console.log(chalk.gray(' • Server name'))` |
295
+ | `chalk.bold` | 🔍 标题、章节标题 | `console.log(chalk.bold('COMMANDS:'))` |
296
+ | `chalk.green.bold` | 活动状态标记 | `console.log(chalk.green.bold('● Active'))` |
297
+
298
+ **格式规范:**
299
+ - **标签格式**:`` `${chalk.cyan('Label:')} value` ``(标签用 cyan,值用默认颜色)
300
+ - **错误格式**:`console.error(chalk.red('✗ Error:'), error.message)`
301
+ - **成功格式**:`console.log(chalk.green('✓ Success message'))`
302
+ - **警告格式**:`console.log(chalk.yellow('⚠ Warning message'))`
303
+ - **标题格式**:`console.log(chalk.bold('Section Title:'))`
304
+
305
+ **反面示例(禁止使用):**
306
+ ```javascript
307
+ // ❌ 错误:标签不应该用 gray
308
+ console.log(chalk.gray(` Config: ${configPath}`));
309
+
310
+ // ❌ 错误:标签不应该用 bold
311
+ console.log(chalk.bold('Level:'), 'Project');
312
+
313
+ // ❌ 错误:不应该整行用一种颜色
314
+ console.log(chalk.cyan(` Config file: ${configPath}`));
315
+ ```
316
+
317
+ **正确示例:**
318
+ ```javascript
319
+ // ✅ 正确:标签用 cyan,值用默认颜色
320
+ console.log(` ${chalk.cyan('Config:')} ${configPath}`);
321
+
322
+ // ✅ 正确:标题用 cyan
323
+ console.log(chalk.cyan(`\n📋 Environment Variables\n`));
324
+
325
+ // ✅ 正确:变量名用 cyan,值用 yellow
326
+ console.log(` ${chalk.gray('•')} ${chalk.cyan(key)} = ${chalk.yellow(value)}`);
327
+ ```
328
+
329
+ ## 文件命名约定
330
+
331
+ - 全局配置:`~/.ai-account-switch/config.json`
332
+ - 项目配置:`.ais-project-config`
333
+ - Claude 配置:`.claude/settings.local.json`
334
+ - Claude 用户配置:`~/.claude/settings.json`(跨平台:Windows `%APPDATA%\claude\settings.json`,macOS/Linux `~/.config/claude/settings.json`)
335
+ - Codex profile:`.codex-profile`
336
+ - Codex 全局:`~/.codex/config.toml`
337
+ - CCR 配置:`~/.claude-code-router/config.json`
338
+ - Droids 配置:`.droids/config.json`
package/README.md CHANGED
@@ -1024,7 +1024,9 @@ ais mcp remove filesystem
1024
1024
  MIT License - 欢迎在你的项目中使用此工具!
1025
1025
 
1026
1026
  ## 更新日志
1027
-
1027
+ ### v1.9.1
1028
+ - **codex帐号添加env 配置方式**:
1029
+ - codex 支持env 临时export user-api-key 的方式认证.
1028
1030
  ### v1.9.0
1029
1031
  - **账号ID功能**:
1030
1032
  - 为每个账号自动分配唯一数字ID(从1开始递增)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-account-switch",
3
- "version": "1.9.0",
3
+ "version": "1.11.0",
4
4
  "description": "A cross-platform CLI tool to manage and switch Claude/Codex/Droids account configurations",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -37,10 +37,10 @@
37
37
  },
38
38
  "repository": {
39
39
  "type": "git",
40
- "url": "https://github.com/yourusername/ai-agent-user-swith.git"
40
+ "url": "https://github.com/DeanWanghewei/ai-agent-user-swith.git"
41
41
  },
42
42
  "bugs": {
43
- "url": "https://github.com/yourusername/ai-agent-user-swith/issues"
43
+ "url": "https://github.com/DeanWanghewei/ai-agent-user-swith/issues"
44
44
  },
45
- "homepage": "https://github.com/yourusername/ai-agent-user-swith#readme"
45
+ "homepage": "https://github.com/DeanWanghewei/ai-agent-user-swith#readme"
46
46
  }
@@ -102,6 +102,10 @@ async function addAccount(name, options) {
102
102
  {
103
103
  name: "responses - Use API key in auth.json (requires_openai_auth) (在 auth.json 中使用 API key)",
104
104
  value: WIRE_API_MODES.RESPONSES
105
+ },
106
+ {
107
+ name: "env - Use API key from environment variable (从环境变量获取 API key)",
108
+ value: WIRE_API_MODES.ENV
105
109
  }
106
110
  ],
107
111
  default: DEFAULT_WIRE_API
@@ -125,6 +129,34 @@ async function addAccount(name, options) {
125
129
  `\n✓ Selected wire_api mode (已选择模式): ${wireApiSelection}\n`
126
130
  )
127
131
  );
132
+
133
+ // If env mode is selected, prompt for environment variable name
134
+ let envKeyName = null;
135
+ if (wireApiSelection === WIRE_API_MODES.ENV) {
136
+ const envKeyAnswer = await inquirer.prompt([
137
+ {
138
+ type: "input",
139
+ name: "envKey",
140
+ message: "Enter environment variable name for API key (请输入 API key 的环境变量名称):",
141
+ default: "AIS_USER_API_KEY",
142
+ validate: (input) => {
143
+ if (!input.trim()) {
144
+ return "Environment variable name is required (环境变量名称不能为空)";
145
+ }
146
+ if (!/^[A-Z_][A-Z0-9_]*$/.test(input.trim())) {
147
+ return "Invalid variable name. Use uppercase letters, numbers, and underscores (e.g., MY_API_KEY) (变量名无效。请使用大写字母、数字和下划线,例如: MY_API_KEY)";
148
+ }
149
+ return true;
150
+ }
151
+ }
152
+ ]);
153
+ envKeyName = envKeyAnswer.envKey.trim();
154
+ console.log(
155
+ chalk.cyan(
156
+ `\n✓ Environment variable (环境变量): ${envKeyName}\n`
157
+ )
158
+ );
159
+ }
128
160
  } else if (typeAnswer.type === "Droids") {
129
161
  console.log(
130
162
  chalk.cyan("\n📝 Droids Configuration Tips (Droids 配置提示):")
@@ -210,6 +242,10 @@ async function addAccount(name, options) {
210
242
  // Add wire_api selection for Codex accounts
211
243
  if (typeAnswer.type === "Codex" && wireApiSelection) {
212
244
  accountData.wireApi = wireApiSelection;
245
+ // Add environment variable name for env mode
246
+ if (wireApiSelection === WIRE_API_MODES.ENV && envKeyName) {
247
+ accountData.envKey = envKeyName;
248
+ }
213
249
  }
214
250
 
215
251
  // Handle custom environment variables
@@ -514,6 +550,22 @@ async function addAccount(name, options) {
514
550
  " Your API key will be stored in ~/.codex/auth.json (API key 将存储在 ~/.codex/auth.json)\n"
515
551
  )
516
552
  );
553
+ } else if (accountData.wireApi === WIRE_API_MODES.ENV) {
554
+ console.log(
555
+ chalk.yellow(
556
+ " ⚠ Note: This account uses 'env' mode (此账号使用 'env' 模式)"
557
+ )
558
+ );
559
+ console.log(
560
+ chalk.white(
561
+ ` You need to export the environment variable before using Codex (使用 Codex 前需要导出环境变量):`
562
+ )
563
+ );
564
+ console.log(
565
+ chalk.cyan(
566
+ ` export ${accountData.envKey}="${accountData.apiKey}"\n`
567
+ )
568
+ );
517
569
  } else {
518
570
  console.log(
519
571
  chalk.cyan(
@@ -790,6 +842,22 @@ async function useAccount(nameOrId) {
790
842
  `✓ API key stored in ~/.codex/auth.json (API key 已存储在 ~/.codex/auth.json)`
791
843
  )
792
844
  );
845
+ } else if (account.wireApi === WIRE_API_MODES.ENV) {
846
+ console.log(
847
+ chalk.yellow(
848
+ `✓ Wire API mode: ${WIRE_API_MODES.ENV} (使用 ${WIRE_API_MODES.ENV} 模式)`
849
+ )
850
+ );
851
+ console.log(
852
+ chalk.green(
853
+ `\n✓ Copy and run this command (复制并执行此命令):`
854
+ )
855
+ );
856
+ console.log(
857
+ chalk.cyan.bold(
858
+ ` export ${account.envKey || 'AIS_USER_API_KEY'}="${account.apiKey}" && codex --profile ${profileName}`
859
+ )
860
+ );
793
861
  } else {
794
862
  console.log(
795
863
  chalk.cyan(