ai-account-switch 1.11.0 → 1.12.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.
package/package.json CHANGED
@@ -1,46 +1,46 @@
1
1
  {
2
- "name": "ai-account-switch",
3
- "version": "1.11.0",
4
- "description": "A cross-platform CLI tool to manage and switch Claude/Codex/Droids account configurations",
5
- "main": "src/index.js",
6
- "bin": {
7
- "ais": "./bin/ais.js"
8
- },
9
- "scripts": {
10
- "test": "node src/index.js",
11
- "link": "npm link"
12
- },
13
- "keywords": [
14
- "cli",
15
- "claude",
16
- "codex",
17
- "droids",
18
- "account",
19
- "switch",
20
- "cross-platform",
21
- "claude-code",
22
- "ai",
23
- "ai-assistant"
24
- ],
25
- "author": "deanwanghewei@gmail.comnpm view ai-account-switch",
26
- "license": "MIT",
27
- "publishConfig": {
28
- "access": "public"
29
- },
30
- "dependencies": {
31
- "commander": "^11.0.0",
32
- "chalk": "^4.1.2",
33
- "inquirer": "^8.2.5"
34
- },
35
- "engines": {
36
- "node": ">=16.0.0"
37
- },
38
- "repository": {
39
- "type": "git",
40
- "url": "https://github.com/DeanWanghewei/ai-agent-user-swith.git"
41
- },
42
- "bugs": {
43
- "url": "https://github.com/DeanWanghewei/ai-agent-user-swith/issues"
44
- },
45
- "homepage": "https://github.com/DeanWanghewei/ai-agent-user-swith#readme"
46
- }
2
+ "name": "ai-account-switch",
3
+ "version": "1.12.0",
4
+ "description": "A cross-platform CLI tool to manage and switch Claude/Codex/Droids account configurations",
5
+ "main": "src/index.js",
6
+ "bin": {
7
+ "ais": "./bin/ais.js"
8
+ },
9
+ "scripts": {
10
+ "test": "node src/index.js",
11
+ "link": "npm link"
12
+ },
13
+ "keywords": [
14
+ "cli",
15
+ "claude",
16
+ "codex",
17
+ "droids",
18
+ "account",
19
+ "switch",
20
+ "cross-platform",
21
+ "claude-code",
22
+ "ai",
23
+ "ai-assistant"
24
+ ],
25
+ "author": "deanwanghewei@gmail.com",
26
+ "license": "MIT",
27
+ "publishConfig": {
28
+ "access": "public"
29
+ },
30
+ "dependencies": {
31
+ "commander": "^11.0.0",
32
+ "chalk": "^4.1.2",
33
+ "inquirer": "^8.2.5"
34
+ },
35
+ "engines": {
36
+ "node": ">=16.0.0"
37
+ },
38
+ "repository": {
39
+ "type": "git",
40
+ "url": "https://github.com/DeanWanghewei/ai-agent-user-swith.git"
41
+ },
42
+ "bugs": {
43
+ "url": "https://github.com/DeanWanghewei/ai-agent-user-swith/issues"
44
+ },
45
+ "homepage": "https://github.com/DeanWanghewei/ai-agent-user-swith#readme"
46
+ }
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Base Account Strategy
3
+ * Provides default behavior for account type-specific operations
4
+ */
5
+ const chalk = require('chalk');
6
+
7
+ class BaseAccountStrategy {
8
+ constructor(accountType) {
9
+ this.accountType = accountType;
10
+ }
11
+
12
+ showConfigTips() {
13
+ // Default: no tips
14
+ }
15
+
16
+ async collectTypeSpecificData(inquirer) {
17
+ return {};
18
+ }
19
+
20
+ showUsageInstructions(accountName) {
21
+ console.log(
22
+ chalk.bold.cyan("\n📖 Usage Instructions (使用说明):\n")
23
+ );
24
+ console.log(
25
+ chalk.white("1. Switch to this account in your project (在项目中切换到此账号):")
26
+ );
27
+ console.log(chalk.cyan(` ais use ${accountName}\n`));
28
+ }
29
+
30
+ showPostSwitchMessage(account) {
31
+ console.log(
32
+ chalk.cyan(
33
+ `✓ Configuration generated (配置已生成)`
34
+ )
35
+ );
36
+ }
37
+ }
38
+
39
+ module.exports = BaseAccountStrategy;
@@ -0,0 +1,118 @@
1
+ /**
2
+ * CCR Account Strategy
3
+ * Handles CCR-specific account operations
4
+ */
5
+ const chalk = require('chalk');
6
+ const BaseAccountStrategy = require('./base-account');
7
+
8
+ class CCRAccountStrategy extends BaseAccountStrategy {
9
+ constructor() {
10
+ super('CCR');
11
+ }
12
+
13
+ showConfigTips() {
14
+ console.log(
15
+ chalk.cyan("\n📝 CCR Configuration Tips (CCR 配置提示):")
16
+ );
17
+ console.log(
18
+ " • CCR configuration will be stored in ~/.claude-code-router/config.json"
19
+ );
20
+ console.log(
21
+ " • You need to provide Provider name and models (您需要提供 Provider 名称和模型列表)"
22
+ );
23
+ console.log(
24
+ " • Router configuration will be automatically updated (Router 配置将自动更新)\n"
25
+ );
26
+ }
27
+
28
+ async collectTypeSpecificData(inquirer) {
29
+ const ccrConfig = await inquirer.prompt([
30
+ {
31
+ type: "input",
32
+ name: "providerName",
33
+ message: "Enter Provider name (请输入 Provider 名称):",
34
+ validate: (input) =>
35
+ input.trim() !== "" || "Provider name is required (Provider 名称不能为空)",
36
+ },
37
+ {
38
+ type: "input",
39
+ name: "defaultModel",
40
+ message: "Enter default model (请输入 default 模型):",
41
+ validate: (input) =>
42
+ input.trim() !== "" || "Default model is required (默认模型不能为空)",
43
+ },
44
+ {
45
+ type: "input",
46
+ name: "backgroundModel",
47
+ message: "Enter background model (请输入 background 模型):",
48
+ validate: (input) =>
49
+ input.trim() !== "" || "Background model is required (background 模型不能为空)",
50
+ },
51
+ {
52
+ type: "input",
53
+ name: "thinkModel",
54
+ message: "Enter think model (请输入 think 模型):",
55
+ validate: (input) =>
56
+ input.trim() !== "" || "Think model is required (think 模型不能为空)",
57
+ },
58
+ ]);
59
+
60
+ const models = [
61
+ ccrConfig.defaultModel.trim(),
62
+ ccrConfig.backgroundModel.trim(),
63
+ ccrConfig.thinkModel.trim()
64
+ ];
65
+ const uniqueModels = [...new Set(models)];
66
+
67
+ return {
68
+ ccrConfig: {
69
+ providerName: ccrConfig.providerName.trim(),
70
+ models: uniqueModels,
71
+ defaultModel: ccrConfig.defaultModel.trim(),
72
+ backgroundModel: ccrConfig.backgroundModel.trim(),
73
+ thinkModel: ccrConfig.thinkModel.trim(),
74
+ }
75
+ };
76
+ }
77
+
78
+ showUsageInstructions(accountName) {
79
+ super.showUsageInstructions(accountName);
80
+ console.log(
81
+ chalk.white(
82
+ "2. CCR configuration will be updated in ~/.claude-code-router/config.json"
83
+ )
84
+ );
85
+ }
86
+
87
+ showPostSwitchMessage(account) {
88
+ console.log(
89
+ chalk.cyan(
90
+ `✓ CCR configuration updated at: ~/.claude-code-router/config.json`
91
+ )
92
+ );
93
+ console.log(
94
+ chalk.cyan(
95
+ `✓ Claude configuration generated at: .claude/settings.local.json`
96
+ )
97
+ );
98
+ console.log("");
99
+ console.log(chalk.bold.cyan("📖 Next Steps (下一步):"));
100
+ console.log(
101
+ chalk.yellow(
102
+ ` Start interactive session: ${chalk.bold("claude")}`
103
+ )
104
+ );
105
+ console.log(
106
+ chalk.white(
107
+ " This will enter project-level interactive mode (这将进入项目级交互模式)"
108
+ )
109
+ );
110
+ console.log(
111
+ chalk.white(
112
+ " Claude Code will use CCR Router to route requests (Claude Code 将使用 CCR Router 路由请求)"
113
+ )
114
+ );
115
+ }
116
+ }
117
+
118
+ module.exports = CCRAccountStrategy;
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Claude Account Strategy
3
+ * Handles Claude-specific account operations
4
+ */
5
+ const chalk = require('chalk');
6
+ const BaseAccountStrategy = require('./base-account');
7
+
8
+ class ClaudeAccountStrategy extends BaseAccountStrategy {
9
+ constructor() {
10
+ super('Claude');
11
+ }
12
+
13
+ showConfigTips() {
14
+ // No special tips needed for Claude accounts
15
+ }
16
+
17
+ async collectTypeSpecificData(inquirer) {
18
+ // Claude accounts use model groups, handled separately
19
+ return {};
20
+ }
21
+
22
+ showUsageInstructions(accountName) {
23
+ super.showUsageInstructions(accountName);
24
+ console.log(
25
+ chalk.white(
26
+ "2. Start Claude Code in your project directory (在项目目录中启动 Claude Code)"
27
+ )
28
+ );
29
+ console.log(
30
+ chalk.white(
31
+ "3. Claude Code will automatically use the project configuration (Claude Code 将自动使用项目配置)\n"
32
+ )
33
+ );
34
+ }
35
+
36
+ showPostSwitchMessage(account) {
37
+ console.log(
38
+ chalk.cyan(
39
+ `✓ Claude configuration generated at: .claude/settings.local.json`
40
+ )
41
+ );
42
+ console.log("");
43
+ console.log(chalk.bold.cyan("📖 Next Steps (下一步):"));
44
+ console.log(
45
+ chalk.yellow(
46
+ ` Start interactive session: ${chalk.bold("claude")}`
47
+ )
48
+ );
49
+ console.log(
50
+ chalk.white(
51
+ " This will enter project-level interactive mode (这将进入项目级交互模式)"
52
+ )
53
+ );
54
+ console.log(
55
+ chalk.white(
56
+ " Claude Code will automatically use the project configuration (Claude Code 将自动使用项目配置)"
57
+ )
58
+ );
59
+ }
60
+ }
61
+
62
+ module.exports = ClaudeAccountStrategy;
@@ -0,0 +1,192 @@
1
+ /**
2
+ * Codex Account Strategy
3
+ * Handles Codex-specific account operations
4
+ */
5
+ const chalk = require('chalk');
6
+ const BaseAccountStrategy = require('./base-account');
7
+ const { WIRE_API_MODES, DEFAULT_WIRE_API } = require('../constants');
8
+
9
+ class CodexAccountStrategy extends BaseAccountStrategy {
10
+ constructor() {
11
+ super('Codex');
12
+ }
13
+
14
+ showConfigTips() {
15
+ console.log(
16
+ chalk.cyan("\n📝 Codex Configuration Tips (Codex 配置提示):")
17
+ );
18
+ console.log(
19
+ chalk.gray(
20
+ " • For domain-only URLs (e.g., https://api.example.com), /v1 will be added automatically"
21
+ )
22
+ );
23
+ console.log(
24
+ chalk.gray(
25
+ " 对于仅域名的 URL (例如 https://api.example.com), 将自动添加 /v1"
26
+ )
27
+ );
28
+ console.log(
29
+ chalk.gray(
30
+ " • URLs with existing paths (e.g., https://api.example.com/v2) will remain unchanged"
31
+ )
32
+ );
33
+ console.log(
34
+ chalk.gray(
35
+ " 已有路径的 URL (例如 https://api.example.com/v2) 将保持不变"
36
+ )
37
+ );
38
+ console.log(
39
+ chalk.gray(
40
+ " • Codex uses OpenAI-compatible API format (Codex 使用 OpenAI 兼容的 API 格式)\n"
41
+ )
42
+ );
43
+ }
44
+
45
+ async collectTypeSpecificData(inquirer) {
46
+ const wireApiAnswer = await inquirer.prompt([
47
+ {
48
+ type: "list",
49
+ name: "wireApi",
50
+ message: "Select wire_api mode (请选择 wire_api 模式):",
51
+ choices: [
52
+ {
53
+ name: "chat - Use API key in HTTP headers (OpenAI-compatible)",
54
+ value: WIRE_API_MODES.CHAT
55
+ },
56
+ {
57
+ name: "responses - Use API key in auth.json (requires_openai_auth)",
58
+ value: WIRE_API_MODES.RESPONSES
59
+ },
60
+ {
61
+ name: "env - Use API key from environment variable",
62
+ value: WIRE_API_MODES.ENV
63
+ }
64
+ ],
65
+ default: DEFAULT_WIRE_API
66
+ }
67
+ ]);
68
+
69
+ const wireApiSelection = wireApiAnswer.wireApi;
70
+
71
+ if (!Object.values(WIRE_API_MODES).includes(wireApiSelection)) {
72
+ console.log(
73
+ chalk.yellow(
74
+ `⚠ Invalid wire_api mode, using default: ${DEFAULT_WIRE_API}`
75
+ )
76
+ );
77
+ return { wireApi: DEFAULT_WIRE_API };
78
+ }
79
+
80
+ console.log(
81
+ chalk.cyan(
82
+ `\n✓ Selected wire_api mode (已选择模式): ${wireApiSelection}\n`
83
+ )
84
+ );
85
+
86
+ const result = { wireApi: wireApiSelection };
87
+
88
+ if (wireApiSelection === WIRE_API_MODES.ENV) {
89
+ const envKeyAnswer = await inquirer.prompt([
90
+ {
91
+ type: "input",
92
+ name: "envKey",
93
+ message: "Enter environment variable name for API key (请输入 API key 的环境变量名称):",
94
+ default: "AIS_USER_API_KEY",
95
+ validate: (input) => {
96
+ if (!input.trim()) {
97
+ return "Environment variable name is required (环境变量名称不能为空)";
98
+ }
99
+ if (!/^[A-Z_][A-Z0-9_]*$/.test(input.trim())) {
100
+ return "Invalid variable name. Use uppercase letters, numbers, and underscores";
101
+ }
102
+ return true;
103
+ }
104
+ }
105
+ ]);
106
+ result.envKey = envKeyAnswer.envKey.trim();
107
+ console.log(
108
+ chalk.cyan(
109
+ `\n✓ Environment variable (环境变量): ${result.envKey}\n`
110
+ )
111
+ );
112
+ }
113
+
114
+ return result;
115
+ }
116
+
117
+ showUsageInstructions(accountName) {
118
+ super.showUsageInstructions(accountName);
119
+ console.log(
120
+ chalk.white(
121
+ "2. Use Codex with the generated profile (使用生成的配置文件运行 Codex):"
122
+ )
123
+ );
124
+ console.log(
125
+ chalk.cyan(` codex --profile ais_<project-name> "your prompt"\n`)
126
+ );
127
+ console.log(
128
+ chalk.white(
129
+ '3. The profile name will be shown when you run "ais use"\n'
130
+ )
131
+ );
132
+ }
133
+
134
+ showPostSwitchMessage(account) {
135
+ const fs = require('fs');
136
+ const path = require('path');
137
+ const profileFile = path.join(process.cwd(), '.codex-profile');
138
+
139
+ if (fs.existsSync(profileFile)) {
140
+ const profileName = fs.readFileSync(profileFile, 'utf8').trim();
141
+ console.log(
142
+ chalk.cyan(
143
+ `✓ Codex profile created (Codex 配置文件已创建): ${profileName}`
144
+ )
145
+ );
146
+
147
+ if (account.wireApi === WIRE_API_MODES.RESPONSES) {
148
+ console.log(
149
+ chalk.yellow(
150
+ `✓ Wire API mode: ${WIRE_API_MODES.RESPONSES}`
151
+ )
152
+ );
153
+ console.log(
154
+ chalk.yellow(
155
+ `✓ API key stored in ~/.codex/auth.json`
156
+ )
157
+ );
158
+ } else if (account.wireApi === WIRE_API_MODES.ENV) {
159
+ console.log(
160
+ chalk.yellow(
161
+ `✓ Wire API mode: ${WIRE_API_MODES.ENV}`
162
+ )
163
+ );
164
+ console.log(
165
+ chalk.green(`\n✓ Copy and run this command:`)
166
+ );
167
+ const envKey = account.envKey || 'AIS_USER_API_KEY';
168
+ console.log(
169
+ chalk.cyan.bold(
170
+ ` export ${envKey}="${account.apiKey}" && codex --profile ${profileName}`
171
+ )
172
+ );
173
+ } else {
174
+ console.log(
175
+ chalk.cyan(
176
+ `✓ Wire API mode: ${WIRE_API_MODES.CHAT}`
177
+ )
178
+ );
179
+ }
180
+
181
+ console.log("");
182
+ console.log(chalk.bold.cyan("📖 Next Steps (下一步):"));
183
+ console.log(
184
+ chalk.yellow(
185
+ ` Start interactive session: ${chalk.bold(`codex --profile ${profileName}`)}`
186
+ )
187
+ );
188
+ }
189
+ }
190
+ }
191
+
192
+ module.exports = CodexAccountStrategy;
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Droids Account Strategy
3
+ * Handles Droids-specific account operations
4
+ */
5
+ const chalk = require('chalk');
6
+ const BaseAccountStrategy = require('./base-account');
7
+
8
+ class DroidsAccountStrategy extends BaseAccountStrategy {
9
+ constructor() {
10
+ super('Droids');
11
+ }
12
+
13
+ showConfigTips() {
14
+ console.log(
15
+ chalk.cyan("\n📝 Droids Configuration Tips (Droids 配置提示):")
16
+ );
17
+ console.log(
18
+ chalk.gray(
19
+ " • Droids configuration will be stored in .droids/config.json"
20
+ )
21
+ );
22
+ console.log(
23
+ chalk.gray(
24
+ " • API URL is optional (defaults to Droids default endpoint)"
25
+ )
26
+ );
27
+ console.log(
28
+ chalk.gray(
29
+ " • You can configure custom models and settings\n"
30
+ )
31
+ );
32
+ }
33
+
34
+ async collectTypeSpecificData(inquirer) {
35
+ // Droids doesn't require additional type-specific data
36
+ // Model is optional and will be collected in the main flow
37
+ return {};
38
+ }
39
+
40
+ showUsageInstructions(accountName) {
41
+ super.showUsageInstructions(accountName);
42
+ console.log(
43
+ chalk.white(
44
+ "2. Start Droids in your project directory (在项目目录中启动 Droids)"
45
+ )
46
+ );
47
+ console.log(
48
+ chalk.white(
49
+ "3. Droids will automatically use the configuration from .droids/config.json\n"
50
+ )
51
+ );
52
+ }
53
+
54
+ showPostSwitchMessage(account) {
55
+ console.log(
56
+ chalk.cyan(
57
+ `✓ Droids configuration generated at: .droids/config.json`
58
+ )
59
+ );
60
+ console.log("");
61
+ console.log(chalk.bold.cyan("📖 Next Steps (下一步):"));
62
+ console.log(
63
+ chalk.yellow(
64
+ ` Start interactive session: ${chalk.bold("droid")}`
65
+ )
66
+ );
67
+ console.log(
68
+ chalk.white(
69
+ " This will enter project-level interactive mode (这将进入项目级交互模式)"
70
+ )
71
+ );
72
+ console.log(
73
+ chalk.white(
74
+ " Droids will automatically use the configuration from .droids/config.json"
75
+ )
76
+ );
77
+ }
78
+ }
79
+
80
+ module.exports = DroidsAccountStrategy;
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Account Strategy Factory
3
+ * Creates appropriate strategy based on account type
4
+ */
5
+ const ClaudeAccountStrategy = require('./claude-account');
6
+ const CodexAccountStrategy = require('./codex-account');
7
+ const CCRAccountStrategy = require('./ccr-account');
8
+ const DroidsAccountStrategy = require('./droids-account');
9
+ const { ACCOUNT_TYPES } = require('../constants');
10
+
11
+ function createAccountStrategy(accountType) {
12
+ const strategies = {
13
+ [ACCOUNT_TYPES.CLAUDE]: ClaudeAccountStrategy,
14
+ [ACCOUNT_TYPES.CODEX]: CodexAccountStrategy,
15
+ [ACCOUNT_TYPES.CCR]: CCRAccountStrategy,
16
+ [ACCOUNT_TYPES.DROIDS]: DroidsAccountStrategy
17
+ };
18
+
19
+ const StrategyClass = strategies[accountType] || ClaudeAccountStrategy;
20
+ return new StrategyClass();
21
+ }
22
+
23
+ module.exports = {
24
+ createAccountStrategy,
25
+ ClaudeAccountStrategy,
26
+ CodexAccountStrategy,
27
+ CCRAccountStrategy,
28
+ DroidsAccountStrategy
29
+ };