ai-account-switch 1.7.1 → 1.9.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/README.md CHANGED
@@ -73,11 +73,11 @@ npm link
73
73
  | 命令 | 别名 | 描述 |
74
74
  |------|------|------|
75
75
  | `ais add [name]` | - | 添加新的账户配置 |
76
- | `ais list` | `ls` | 列出所有可用账户 |
77
- | `ais use [name]` | - | 为当前项目设置账户 |
76
+ | `ais list` | `ls` | 列出所有可用账户(含ID) |
77
+ | `ais use [name-or-id]` | - | 通过名称或ID为当前项目设置账户 |
78
78
  | `ais info` | - | 显示当前项目的账户信息 |
79
79
  | `ais current` | - | 显示当前账户名称 |
80
- | `ais remove [name]` | `rm` | 删除账户 |
80
+ | `ais remove [name-or-id]` | `rm` | 通过名称或ID删除账户 |
81
81
  | `ais model list` | `ls` | 列出当前账户的所有模型组 |
82
82
  | `ais model add [name]` | - | 添加新的模型组 |
83
83
  | `ais model use <name>` | - | 切换到不同的模型组 |
@@ -95,7 +95,7 @@ npm link
95
95
  | `ais ui` | - | 启动 Web UI 管理界面 |
96
96
  | `ais paths` | - | 显示配置文件路径 |
97
97
  | `ais doctor` | - | 诊断 Claude Code 配置问题 |
98
- | `ais export <name>` | - | 导出账户为 JSON 格式 |
98
+ | `ais export <name-or-id>` | - | 通过名称或ID导出账户为 JSON 格式 |
99
99
  | `ais help` | - | 显示帮助信息 |
100
100
  | `ais --version` | - | 显示版本号 |
101
101
 
@@ -128,7 +128,7 @@ ais add my-claude-account
128
128
 
129
129
  #### 2. 列出所有账户
130
130
 
131
- 查看所有已配置的账户:
131
+ 查看所有已配置的账户及其ID:
132
132
 
133
133
  ```bash
134
134
  ais list
@@ -136,14 +136,33 @@ ais list
136
136
  ais ls
137
137
  ```
138
138
 
139
+ 输出示例:
140
+ ```
141
+ 📋 Available Accounts (可用账号):
142
+
143
+ ● [1] my-claude-account
144
+ Type: Claude
145
+ API Key: sk-a****xyz
146
+ ...
147
+
148
+ [2] work-codex
149
+ Type: Codex
150
+ API Key: sk-b****abc
151
+ ...
152
+ ```
153
+
139
154
  当前项目激活的账户将用绿色圆点标记。
140
155
 
141
156
  #### 3. 为当前项目切换账户
142
157
 
143
- 设置当前项目使用的账户:
158
+ 设置当前项目使用的账户(可以使用账户名或ID):
144
159
 
145
160
  ```bash
161
+ # 使用账户名
146
162
  ais use my-claude-account
163
+
164
+ # 或使用ID(更快捷)
165
+ ais use 1
147
166
  ```
148
167
 
149
168
  或交互式选择:
@@ -1006,6 +1025,21 @@ MIT License - 欢迎在你的项目中使用此工具!
1006
1025
 
1007
1026
  ## 更新日志
1008
1027
 
1028
+ ### v1.9.0
1029
+ - **账号ID功能**:
1030
+ - 为每个账号自动分配唯一数字ID(从1开始递增)
1031
+ - 支持通过ID快速切换账号:`ais use 1`
1032
+ - 支持通过ID删除账号:`ais remove 2`
1033
+ - 支持通过ID导出账号:`ais export 3`
1034
+ - 账号列表展示格式优化:`[ID] AccountName`
1035
+ - ID使用黄色高亮显示,更加清晰可见
1036
+ - 完全向后兼容:已有账号自动分配ID
1037
+ - 交互式选择账号时同时显示ID和名称
1038
+ - **文档更新**:
1039
+ - 更新所有命令说明,添加ID使用示例
1040
+ - 中英文README同步更新
1041
+ - Help命令输出优化
1042
+
1009
1043
  ### v1.7.0
1010
1044
  - **MCP Web UI 管理**:
1011
1045
  - 通过 Web UI 完整管理 MCP 服务器
package/README_EN.md CHANGED
@@ -66,11 +66,11 @@ npm link
66
66
  | Command | Alias | Description |
67
67
  |---------|-------|-------------|
68
68
  | `ais add [name]` | - | Add a new account configuration |
69
- | `ais list` | `ls` | List all available accounts |
70
- | `ais use [name]` | - | Set account for current project |
69
+ | `ais list` | `ls` | List all available accounts with IDs |
70
+ | `ais use [name-or-id]` | - | Set account for current project by name or ID |
71
71
  | `ais info` | - | Show current project's account info |
72
72
  | `ais current` | - | Show current account name |
73
- | `ais remove [name]` | `rm` | Remove an account |
73
+ | `ais remove [name-or-id]` | `rm` | Remove an account by name or ID |
74
74
  | `ais model list` | `ls` | List all model groups for current account |
75
75
  | `ais model add [name]` | - | Add a new model group |
76
76
  | `ais model use <name>` | - | Switch to a different model group |
@@ -88,7 +88,7 @@ npm link
88
88
  | `ais ui` | - | Start web-based UI manager |
89
89
  | `ais paths` | - | Show configuration file paths |
90
90
  | `ais doctor` | - | Diagnose configuration issues |
91
- | `ais export <name>` | - | Export account as JSON |
91
+ | `ais export <name-or-id>` | - | Export account as JSON by name or ID |
92
92
  | `ais help` | - | Display help information |
93
93
  | `ais --version` | - | Show version number |
94
94
 
@@ -122,7 +122,7 @@ You'll be prompted to enter:
122
122
 
123
123
  #### 2. List All Accounts
124
124
 
125
- View all your configured accounts:
125
+ View all your configured accounts with their IDs:
126
126
 
127
127
  ```bash
128
128
  ais list
@@ -130,14 +130,33 @@ ais list
130
130
  ais ls
131
131
  ```
132
132
 
133
+ Example output:
134
+ ```
135
+ 📋 Available Accounts:
136
+
137
+ ● [1] my-claude-account
138
+ Type: Claude
139
+ API Key: sk-a****xyz
140
+ ...
141
+
142
+ [2] work-codex
143
+ Type: Codex
144
+ API Key: sk-b****abc
145
+ ...
146
+ ```
147
+
133
148
  The active account for the current project will be marked with a green dot.
134
149
 
135
150
  #### 3. Switch Account for Current Project
136
151
 
137
- Set which account to use in your current project:
152
+ Set which account to use in your current project (by name or ID):
138
153
 
139
154
  ```bash
155
+ # Use account by name
140
156
  ais use my-claude-account
157
+
158
+ # Or use account by ID (faster)
159
+ ais use 1
141
160
  ```
142
161
 
143
162
  Or select interactively:
@@ -997,6 +1016,21 @@ MIT License - feel free to use this tool in your projects!
997
1016
 
998
1017
  ## Changelog
999
1018
 
1019
+ ### v1.9.0
1020
+ - **Account ID Feature**:
1021
+ - Automatically assign unique numeric ID to each account (starting from 1)
1022
+ - Quick account switching by ID: `ais use 1`
1023
+ - Remove account by ID: `ais remove 2`
1024
+ - Export account by ID: `ais export 3`
1025
+ - Optimized account list display format: `[ID] AccountName`
1026
+ - ID displayed in yellow color for better visibility
1027
+ - Fully backward compatible: existing accounts automatically assigned IDs
1028
+ - Interactive selection shows both ID and name
1029
+ - **Documentation Updates**:
1030
+ - Updated all command descriptions with ID usage examples
1031
+ - Synchronized Chinese and English README
1032
+ - Optimized help command output
1033
+
1000
1034
  ### v1.7.0
1001
1035
  - **MCP Web UI Management**:
1002
1036
  - Complete MCP server management through Web UI
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-account-switch",
3
- "version": "1.7.1",
3
+ "version": "1.9.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": {
@@ -1,6 +1,7 @@
1
1
  const chalk = require("chalk");
2
2
  const inquirer = require("inquirer");
3
3
  const ConfigManager = require("../config");
4
+ const { WIRE_API_MODES, DEFAULT_WIRE_API, ACCOUNT_TYPES } = require("../config");
4
5
  const { maskApiKey } = require("./helpers");
5
6
  const { promptForModelGroup } = require("./model");
6
7
 
@@ -53,6 +54,9 @@ async function addAccount(name, options) {
53
54
  },
54
55
  ]);
55
56
 
57
+ // Store wire_api selection for Codex accounts
58
+ let wireApiSelection = null;
59
+
56
60
  // Show configuration tips based on account type
57
61
  if (typeAnswer.type === "Codex") {
58
62
  console.log(
@@ -83,6 +87,44 @@ async function addAccount(name, options) {
83
87
  " • Codex uses OpenAI-compatible API format (Codex 使用 OpenAI 兼容的 API 格式)\n"
84
88
  )
85
89
  );
90
+
91
+ // Prompt for wire_api mode selection
92
+ const wireApiAnswer = await inquirer.prompt([
93
+ {
94
+ type: "list",
95
+ name: "wireApi",
96
+ message: "Select wire_api mode (请选择 wire_api 模式):",
97
+ choices: [
98
+ {
99
+ name: "chat - Use API key in HTTP headers (OpenAI-compatible) (在 HTTP 头中使用 API key,OpenAI 兼容)",
100
+ value: WIRE_API_MODES.CHAT
101
+ },
102
+ {
103
+ name: "responses - Use API key in auth.json (requires_openai_auth) (在 auth.json 中使用 API key)",
104
+ value: WIRE_API_MODES.RESPONSES
105
+ }
106
+ ],
107
+ default: DEFAULT_WIRE_API
108
+ }
109
+ ]);
110
+
111
+ wireApiSelection = wireApiAnswer.wireApi;
112
+
113
+ // Validate input
114
+ if (!Object.values(WIRE_API_MODES).includes(wireApiSelection)) {
115
+ console.log(
116
+ chalk.yellow(
117
+ `⚠ Invalid wire_api mode, using default: ${DEFAULT_WIRE_API} (无效的模式,使用默认值)`
118
+ )
119
+ );
120
+ wireApiSelection = DEFAULT_WIRE_API;
121
+ }
122
+
123
+ console.log(
124
+ chalk.cyan(
125
+ `\n✓ Selected wire_api mode (已选择模式): ${wireApiSelection}\n`
126
+ )
127
+ );
86
128
  } else if (typeAnswer.type === "Droids") {
87
129
  console.log(
88
130
  chalk.cyan("\n📝 Droids Configuration Tips (Droids 配置提示):")
@@ -165,6 +207,11 @@ async function addAccount(name, options) {
165
207
  // Merge type into accountData
166
208
  accountData.type = typeAnswer.type;
167
209
 
210
+ // Add wire_api selection for Codex accounts
211
+ if (typeAnswer.type === "Codex" && wireApiSelection) {
212
+ accountData.wireApi = wireApiSelection;
213
+ }
214
+
168
215
  // Handle custom environment variables
169
216
  if (accountData.addCustomEnv) {
170
217
  accountData.customEnv = {};
@@ -454,6 +501,27 @@ async function addAccount(name, options) {
454
501
  )
455
502
  );
456
503
  console.log(chalk.cyan(` ais use ${name}\n`));
504
+
505
+ // Show wire_api mode specific information
506
+ if (accountData.wireApi === WIRE_API_MODES.RESPONSES) {
507
+ console.log(
508
+ chalk.yellow(
509
+ " ⚠ Note: This account uses 'responses' mode (此账号使用 'responses' 模式)"
510
+ )
511
+ );
512
+ console.log(
513
+ chalk.white(
514
+ " Your API key will be stored in ~/.codex/auth.json (API key 将存储在 ~/.codex/auth.json)\n"
515
+ )
516
+ );
517
+ } else {
518
+ console.log(
519
+ chalk.cyan(
520
+ " ✓ This account uses 'chat' mode (此账号使用 'chat' 模式)\n"
521
+ )
522
+ );
523
+ }
524
+
457
525
  console.log(
458
526
  chalk.white(
459
527
  "2. Use Codex with the generated profile (使用生成的配置文件运行 Codex):"
@@ -555,13 +623,17 @@ function listAccounts() {
555
623
  const account = accounts[name];
556
624
  const isActive = currentProject && currentProject.name === name;
557
625
  const marker = isActive ? chalk.green("● ") : " ";
626
+
627
+ // Display ID and name
628
+ const idDisplay = chalk.yellow(`[${account.id}]`);
558
629
  const nameDisplay = isActive
559
630
  ? chalk.green.bold(name)
560
631
  : chalk.cyan(name);
561
632
 
562
- console.log(`${marker}${nameDisplay}`);
633
+ console.log(`${marker}${idDisplay} ${nameDisplay}`);
563
634
  console.log(` Type: ${account.type}`);
564
635
  console.log(` API Key: ${maskApiKey(account.apiKey)}`);
636
+ if (account.apiUrl) console.log(` API URL: ${account.apiUrl}`);
565
637
  if (account.email) console.log(` Email: ${account.email}`);
566
638
  if (account.description)
567
639
  console.log(` Description: ${account.description}`);
@@ -589,6 +661,11 @@ function listAccounts() {
589
661
  ) {
590
662
  console.log(` Model: ${account.model}`);
591
663
  }
664
+ // Display wire_api configuration for Codex accounts
665
+ if (account.type === ACCOUNT_TYPES.CODEX) {
666
+ const wireApi = account.wireApi || `${DEFAULT_WIRE_API} (default)`;
667
+ console.log(` Wire API: ${wireApi}`);
668
+ }
592
669
  console.log(
593
670
  ` Created: ${new Date(account.createdAt).toLocaleString()}`
594
671
  );
@@ -613,9 +690,9 @@ function listAccounts() {
613
690
  /**
614
691
  * Switch to a specific account for current project
615
692
  */
616
- async function useAccount(name) {
617
- if (!name) {
618
- // If no name provided, show interactive selection
693
+ async function useAccount(nameOrId) {
694
+ if (!nameOrId) {
695
+ // If no name/ID provided, show interactive selection
619
696
  const accounts = config.getAllAccounts();
620
697
  const accountNames = Object.keys(accounts);
621
698
 
@@ -628,21 +705,32 @@ async function useAccount(name) {
628
705
  return;
629
706
  }
630
707
 
708
+ // Create choices with ID and name
709
+ const choices = accountNames.map(name => {
710
+ const account = accounts[name];
711
+ return {
712
+ name: `[${account.id}] ${name}`,
713
+ value: name
714
+ };
715
+ });
716
+
631
717
  const answers = await inquirer.prompt([
632
718
  {
633
719
  type: "list",
634
720
  name: "accountName",
635
721
  message: "Select an account to use (请选择要使用的账号):",
636
- choices: accountNames,
722
+ choices: choices,
637
723
  },
638
724
  ]);
639
725
 
640
- name = answers.accountName;
726
+ nameOrId = answers.accountName;
641
727
  }
642
728
 
643
- if (!config.accountExists(name)) {
729
+ // Find account by ID or name
730
+ const accountInfo = config.getAccountByIdOrName(nameOrId);
731
+ if (!accountInfo) {
644
732
  console.log(
645
- chalk.red(`✗ Account '${name}' not found. (未找到账号 '${name}'。)`)
733
+ chalk.red(`✗ Account '${nameOrId}' not found. (未找到账号 '${nameOrId}'。)`)
646
734
  );
647
735
  console.log(
648
736
  chalk.yellow(
@@ -652,6 +740,7 @@ async function useAccount(name) {
652
740
  return;
653
741
  }
654
742
 
743
+ const name = accountInfo.name;
655
744
  const success = config.setProjectAccount(name);
656
745
  if (success) {
657
746
  const fs = require("fs");
@@ -688,6 +777,27 @@ async function useAccount(name) {
688
777
  `✓ Codex profile created (Codex 配置文件已创建): ${profileName}`
689
778
  )
690
779
  );
780
+
781
+ // Display wire_api mode information
782
+ if (account.wireApi === WIRE_API_MODES.RESPONSES) {
783
+ console.log(
784
+ chalk.yellow(
785
+ `✓ Wire API mode: ${WIRE_API_MODES.RESPONSES} (使用 ${WIRE_API_MODES.RESPONSES} 模式)`
786
+ )
787
+ );
788
+ console.log(
789
+ chalk.yellow(
790
+ `✓ API key stored in ~/.codex/auth.json (API key 已存储在 ~/.codex/auth.json)`
791
+ )
792
+ );
793
+ } else {
794
+ console.log(
795
+ chalk.cyan(
796
+ `✓ Wire API mode: ${WIRE_API_MODES.CHAT} (使用 ${WIRE_API_MODES.CHAT} 模式)`
797
+ )
798
+ );
799
+ }
800
+
691
801
  console.log("");
692
802
  console.log(chalk.bold.cyan("📖 Next Steps (下一步):"));
693
803
  console.log(
@@ -825,6 +935,7 @@ function showInfo() {
825
935
  console.log(
826
936
  chalk.bold("\n📌 Current Project Account Info (当前项目账号信息):\n")
827
937
  );
938
+ console.log(`${chalk.cyan("Account ID:")} ${chalk.yellow(`[${projectAccount.id}]`)}`);
828
939
  console.log(
829
940
  `${chalk.cyan("Account Name:")} ${chalk.green.bold(
830
941
  projectAccount.name
@@ -878,6 +989,10 @@ function showInfo() {
878
989
  ) {
879
990
  console.log(`${chalk.cyan("Model:")} ${projectAccount.model}`);
880
991
  }
992
+ // Display wire_api configuration for Codex accounts
993
+ if (projectAccount.type === ACCOUNT_TYPES.CODEX && projectAccount.wireApi) {
994
+ console.log(`${chalk.cyan("Wire API:")} ${projectAccount.wireApi}`);
995
+ }
881
996
  console.log(
882
997
  `${chalk.cyan("Set At:")} ${new Date(
883
998
  projectAccount.setAt
@@ -890,8 +1005,8 @@ function showInfo() {
890
1005
  /**
891
1006
  * Remove an account
892
1007
  */
893
- async function removeAccount(name) {
894
- if (!name) {
1008
+ async function removeAccount(nameOrId) {
1009
+ if (!nameOrId) {
895
1010
  const accounts = config.getAllAccounts();
896
1011
  const accountNames = Object.keys(accounts);
897
1012
 
@@ -900,30 +1015,43 @@ async function removeAccount(name) {
900
1015
  return;
901
1016
  }
902
1017
 
1018
+ // Create choices with ID and name
1019
+ const choices = accountNames.map(name => {
1020
+ const account = accounts[name];
1021
+ return {
1022
+ name: `[${account.id}] ${name}`,
1023
+ value: name
1024
+ };
1025
+ });
1026
+
903
1027
  const answers = await inquirer.prompt([
904
1028
  {
905
1029
  type: "list",
906
1030
  name: "accountName",
907
1031
  message: "Select an account to remove (请选择要删除的账号):",
908
- choices: accountNames,
1032
+ choices: choices,
909
1033
  },
910
1034
  ]);
911
1035
 
912
- name = answers.accountName;
1036
+ nameOrId = answers.accountName;
913
1037
  }
914
1038
 
915
- if (!config.accountExists(name)) {
1039
+ // Find account by ID or name
1040
+ const accountInfo = config.getAccountByIdOrName(nameOrId);
1041
+ if (!accountInfo) {
916
1042
  console.log(
917
- chalk.red(`✗ Account '${name}' not found. (未找到账号 '${name}'。)`)
1043
+ chalk.red(`✗ Account '${nameOrId}' not found. (未找到账号 '${nameOrId}'。)`)
918
1044
  );
919
1045
  return;
920
1046
  }
921
1047
 
1048
+ const name = accountInfo.name;
1049
+
922
1050
  const { confirm } = await inquirer.prompt([
923
1051
  {
924
1052
  type: "confirm",
925
1053
  name: "confirm",
926
- message: `Are you sure you want to remove account '${name}'? (确定要删除账号 '${name}' 吗?)`,
1054
+ message: `Are you sure you want to remove account '${name}' (ID: ${accountInfo.id})? (确定要删除账号 '${name}' (ID: ${accountInfo.id}) 吗?)`,
927
1055
  default: false,
928
1056
  },
929
1057
  ]);
@@ -970,30 +1098,34 @@ function showCurrent() {
970
1098
  /**
971
1099
  * Export account configuration
972
1100
  */
973
- function exportAccount(name) {
974
- if (!name) {
1101
+ function exportAccount(nameOrId) {
1102
+ if (!nameOrId) {
975
1103
  console.log(
976
- chalk.red("Please specify an account name. (请指定账号名称。)")
1104
+ chalk.red("Please specify an account name or ID. (请指定账号名称或 ID。)")
977
1105
  );
978
1106
  console.log(
979
1107
  chalk.cyan(
980
- "Usage: ais export <account-name> (用法: ais export <账号名>)"
1108
+ "Usage: ais export <account-name-or-id> (用法: ais export <账号名或ID>)"
981
1109
  )
982
1110
  );
983
1111
  return;
984
1112
  }
985
1113
 
986
- const account = config.getAccount(name);
987
- if (!account) {
1114
+ // Find account by ID or name
1115
+ const accountInfo = config.getAccountByIdOrName(nameOrId);
1116
+ if (!accountInfo) {
988
1117
  console.log(
989
- chalk.red(`✗ Account '${name}' not found. (未找到账号 '${name}'。)`)
1118
+ chalk.red(`✗ Account '${nameOrId}' not found. (未找到账号 '${nameOrId}'。)`)
990
1119
  );
991
1120
  return;
992
1121
  }
993
1122
 
1123
+ const name = accountInfo.name;
1124
+ const { name: _, ...account } = accountInfo; // Remove the name property from accountInfo
1125
+
994
1126
  console.log(
995
1127
  chalk.bold(
996
- `\n📤 Export for account '${name}' (账号 '${name}' 的导出数据):\n`
1128
+ `\n📤 Export for account '${name}' (ID: ${account.id}) (账号 '${name}' 的导出数据):\n`
997
1129
  )
998
1130
  );
999
1131
  console.log(JSON.stringify({ [name]: account }, null, 2));
package/src/config.js CHANGED
@@ -2,6 +2,22 @@ const fs = require('fs');
2
2
  const path = require('path');
3
3
  const os = require('os');
4
4
 
5
+ // Constants for wire API modes
6
+ const WIRE_API_MODES = {
7
+ CHAT: 'chat',
8
+ RESPONSES: 'responses'
9
+ };
10
+
11
+ const DEFAULT_WIRE_API = WIRE_API_MODES.CHAT;
12
+
13
+ // Constants for account types
14
+ const ACCOUNT_TYPES = {
15
+ CLAUDE: 'Claude',
16
+ CODEX: 'Codex',
17
+ CCR: 'CCR',
18
+ DROIDS: 'Droids'
19
+ };
20
+
5
21
  /**
6
22
  * Cross-platform configuration manager
7
23
  * Stores global accounts in user home directory
@@ -30,8 +46,11 @@ class ConfigManager {
30
46
 
31
47
  // Create global config file if it doesn't exist
32
48
  if (!fs.existsSync(this.globalConfigFile)) {
33
- this.saveGlobalConfig({ accounts: {}, mcpServers: {} });
49
+ this.saveGlobalConfig({ accounts: {}, mcpServers: {}, nextAccountId: 1 });
34
50
  }
51
+
52
+ // Migrate existing accounts to have IDs
53
+ this.migrateAccountIds();
35
54
  }
36
55
 
37
56
  /**
@@ -66,9 +85,14 @@ class ConfigManager {
66
85
  readGlobalConfig() {
67
86
  try {
68
87
  const data = fs.readFileSync(this.globalConfigFile, 'utf8');
69
- return JSON.parse(data);
88
+ const config = JSON.parse(data);
89
+ // Ensure nextAccountId exists
90
+ if (!config.nextAccountId) {
91
+ config.nextAccountId = 1;
92
+ }
93
+ return config;
70
94
  } catch (error) {
71
- return { accounts: {}, mcpServers: {} };
95
+ return { accounts: {}, mcpServers: {}, nextAccountId: 1 };
72
96
  }
73
97
  }
74
98
 
@@ -79,16 +103,84 @@ class ConfigManager {
79
103
  fs.writeFileSync(this.globalConfigFile, JSON.stringify(config, null, 2), 'utf8');
80
104
  }
81
105
 
106
+ /**
107
+ * Migrate existing accounts to have IDs
108
+ * This ensures backward compatibility by assigning IDs to accounts that don't have one
109
+ */
110
+ migrateAccountIds() {
111
+ const config = this.readGlobalConfig();
112
+ let needsSave = false;
113
+
114
+ // Ensure nextAccountId exists
115
+ if (!config.nextAccountId) {
116
+ config.nextAccountId = 1;
117
+ needsSave = true;
118
+ }
119
+
120
+ // Assign IDs to accounts that don't have one
121
+ Object.keys(config.accounts || {}).forEach(name => {
122
+ if (!config.accounts[name].id) {
123
+ config.accounts[name].id = config.nextAccountId;
124
+ config.nextAccountId++;
125
+ needsSave = true;
126
+ }
127
+ });
128
+
129
+ if (needsSave) {
130
+ this.saveGlobalConfig(config);
131
+ }
132
+ }
133
+
134
+ /**
135
+ * Get account by ID or name
136
+ * @param {string|number} idOrName - Account ID or name
137
+ * @returns {Object|null} - Account object with name property, or null if not found
138
+ */
139
+ getAccountByIdOrName(idOrName) {
140
+ const accounts = this.getAllAccounts();
141
+
142
+ // Try to parse as ID (number)
143
+ const id = parseInt(idOrName, 10);
144
+ if (!isNaN(id)) {
145
+ // Search by ID
146
+ for (const [name, account] of Object.entries(accounts)) {
147
+ if (account.id === id) {
148
+ return { name, ...account };
149
+ }
150
+ }
151
+ }
152
+
153
+ // Search by name
154
+ const account = accounts[idOrName];
155
+ if (account) {
156
+ return { name: idOrName, ...account };
157
+ }
158
+
159
+ return null;
160
+ }
161
+
82
162
  /**
83
163
  * Add or update an account
84
164
  */
85
165
  addAccount(name, accountData) {
86
166
  const config = this.readGlobalConfig();
167
+
168
+ // Assign ID for new accounts
169
+ const isNewAccount = !config.accounts[name];
170
+ const accountId = isNewAccount ? config.nextAccountId : config.accounts[name].id;
171
+
87
172
  config.accounts[name] = {
88
173
  ...accountData,
174
+ id: accountId,
89
175
  createdAt: config.accounts[name]?.createdAt || new Date().toISOString(),
90
176
  updatedAt: new Date().toISOString()
91
177
  };
178
+
179
+ // Increment nextAccountId only for new accounts
180
+ if (isNewAccount) {
181
+ config.nextAccountId++;
182
+ }
183
+
92
184
  this.saveGlobalConfig(config);
93
185
  return true;
94
186
  }
@@ -587,11 +679,26 @@ class ConfigManager {
587
679
  profileConfig += `base_url = "${baseUrl}"\n`;
588
680
  }
589
681
 
590
- // Determine wire_api: use "chat" for most APIs (OpenAI-compatible)
591
- profileConfig += `wire_api = "chat"\n`;
592
-
593
- // Add authentication header
594
- profileConfig += `http_headers = { "Authorization" = "Bearer ${account.apiKey}" }\n`;
682
+ // Determine wire_api based on account configuration (default to chat for backward compatibility)
683
+ const wireApi = account.wireApi || DEFAULT_WIRE_API;
684
+
685
+ if (wireApi === WIRE_API_MODES.CHAT) {
686
+ // Chat mode: use HTTP headers for authentication
687
+ profileConfig += `wire_api = "${WIRE_API_MODES.CHAT}"\n`;
688
+ profileConfig += `http_headers = { "Authorization" = "Bearer ${account.apiKey}" }\n`;
689
+
690
+ // Note: We do NOT clear auth.json here because:
691
+ // 1. auth.json is a global file shared by all projects
692
+ // 2. Other projects may be using responses mode and need the API key
693
+ // 3. Chat mode doesn't use auth.json anyway, so no conflict exists
694
+ } else if (wireApi === WIRE_API_MODES.RESPONSES) {
695
+ // Responses mode: use auth.json for authentication
696
+ profileConfig += `wire_api = "${WIRE_API_MODES.RESPONSES}"\n`;
697
+ profileConfig += `requires_openai_auth = true\n`;
698
+
699
+ // Update auth.json with API key
700
+ this.updateCodexAuthJson(account.apiKey);
701
+ }
595
702
  }
596
703
 
597
704
  // Remove all old profiles with the same name (including duplicates)
@@ -620,6 +727,146 @@ class ConfigManager {
620
727
  fs.writeFileSync(helperScript, profileName, 'utf8');
621
728
  }
622
729
 
730
+ /**
731
+ * Read auth.json file
732
+ * @private
733
+ * @returns {Object} Parsed auth data or empty object
734
+ */
735
+ _readAuthJson(authJsonFile) {
736
+ if (!fs.existsSync(authJsonFile)) {
737
+ return {};
738
+ }
739
+
740
+ try {
741
+ const content = fs.readFileSync(authJsonFile, 'utf8');
742
+ return JSON.parse(content);
743
+ } catch (parseError) {
744
+ const chalk = require('chalk');
745
+ console.warn(
746
+ chalk.yellow(
747
+ `⚠ Warning: Could not parse existing auth.json, will create new file (警告: 无法解析现有 auth.json,将创建新文件)`
748
+ )
749
+ );
750
+ return {};
751
+ }
752
+ }
753
+
754
+ /**
755
+ * Write auth.json file atomically with proper permissions
756
+ * Uses atomic write (temp file + rename) to prevent corruption from concurrent access
757
+ * @private
758
+ * @param {string} authJsonFile - Path to auth.json
759
+ * @param {Object} authData - Auth data to write
760
+ */
761
+ _writeAuthJson(authJsonFile, authData) {
762
+ const chalk = require('chalk');
763
+ const tempFile = `${authJsonFile}.tmp.${process.pid}`;
764
+
765
+ try {
766
+ // Write to temporary file first (atomic operation)
767
+ fs.writeFileSync(tempFile, JSON.stringify(authData, null, 2), 'utf8');
768
+
769
+ // Set file permissions to 600 (owner read/write only) for security
770
+ if (process.platform !== 'win32') {
771
+ fs.chmodSync(tempFile, 0o600);
772
+ }
773
+
774
+ // Atomically rename temp file to actual file
775
+ // This is atomic on POSIX systems and prevents corruption
776
+ fs.renameSync(tempFile, authJsonFile);
777
+ } catch (error) {
778
+ // Clean up temp file if it exists
779
+ if (fs.existsSync(tempFile)) {
780
+ try {
781
+ fs.unlinkSync(tempFile);
782
+ } catch (cleanupError) {
783
+ // Ignore cleanup errors
784
+ }
785
+ }
786
+ throw error;
787
+ }
788
+ }
789
+
790
+ /**
791
+ * Clear OPENAI_API_KEY in ~/.codex/auth.json for chat mode
792
+ * @deprecated This method is no longer called automatically.
793
+ * Chat mode doesn't require clearing auth.json since it doesn't use it.
794
+ */
795
+ clearCodexAuthJson() {
796
+ const chalk = require('chalk');
797
+ const codexDir = path.join(os.homedir(), '.codex');
798
+ const authJsonFile = path.join(codexDir, 'auth.json');
799
+
800
+ try {
801
+ // Ensure .codex directory exists
802
+ if (!fs.existsSync(codexDir)) {
803
+ fs.mkdirSync(codexDir, { recursive: true });
804
+ }
805
+
806
+ // Read existing auth data
807
+ const authData = this._readAuthJson(authJsonFile);
808
+
809
+ // Clear OPENAI_API_KEY (set to empty string)
810
+ authData.OPENAI_API_KEY = "";
811
+
812
+ // Write atomically with proper permissions
813
+ this._writeAuthJson(authJsonFile, authData);
814
+
815
+ console.log(
816
+ chalk.cyan(
817
+ `✓ Cleared OPENAI_API_KEY in auth.json (chat mode) (已清空 auth.json 中的 OPENAI_API_KEY)`
818
+ )
819
+ );
820
+ } catch (error) {
821
+ console.error(
822
+ chalk.yellow(
823
+ `⚠ Warning: Failed to clear auth.json: ${error.message} (警告: 清空 auth.json 失败)`
824
+ )
825
+ );
826
+ // Don't throw error, just warn - this is not critical for chat mode
827
+ }
828
+ }
829
+
830
+ /**
831
+ * Update ~/.codex/auth.json with API key for responses mode
832
+ * @param {string} apiKey - API key to store in auth.json
833
+ * @throws {Error} If file operations fail
834
+ */
835
+ updateCodexAuthJson(apiKey) {
836
+ const chalk = require('chalk');
837
+ const codexDir = path.join(os.homedir(), '.codex');
838
+ const authJsonFile = path.join(codexDir, 'auth.json');
839
+
840
+ try {
841
+ // Ensure .codex directory exists
842
+ if (!fs.existsSync(codexDir)) {
843
+ fs.mkdirSync(codexDir, { recursive: true });
844
+ }
845
+
846
+ // Read existing auth data
847
+ const authData = this._readAuthJson(authJsonFile);
848
+
849
+ // Update OPENAI_API_KEY
850
+ authData.OPENAI_API_KEY = apiKey;
851
+
852
+ // Write atomically with proper permissions
853
+ this._writeAuthJson(authJsonFile, authData);
854
+
855
+ console.log(
856
+ chalk.green(
857
+ `✓ Updated auth.json at: ${authJsonFile} (已更新 auth.json)`
858
+ )
859
+ );
860
+ } catch (error) {
861
+ console.error(
862
+ chalk.red(
863
+ `✗ Failed to update auth.json: ${error.message} (更新 auth.json 失败)`
864
+ )
865
+ );
866
+ throw error;
867
+ }
868
+ }
869
+
623
870
  /**
624
871
  * Get current project's active account
625
872
  * Searches upwards from current directory to find project root
@@ -1160,3 +1407,6 @@ class ConfigManager {
1160
1407
  }
1161
1408
 
1162
1409
  module.exports = ConfigManager;
1410
+ module.exports.WIRE_API_MODES = WIRE_API_MODES;
1411
+ module.exports.DEFAULT_WIRE_API = DEFAULT_WIRE_API;
1412
+ module.exports.ACCOUNT_TYPES = ACCOUNT_TYPES;
package/src/index.js CHANGED
@@ -53,8 +53,8 @@ program
53
53
 
54
54
  // Use account command
55
55
  program
56
- .command('use [name]')
57
- .description('Set the account to use for the current project (设置当前项目使用的账号)')
56
+ .command('use [name-or-id]')
57
+ .description('Set the account to use for the current project by name or ID (通过名称或ID设置当前项目使用的账号)')
58
58
  .action(useAccount);
59
59
 
60
60
  // Show info command
@@ -65,9 +65,9 @@ program
65
65
 
66
66
  // Remove account command
67
67
  program
68
- .command('remove [name]')
68
+ .command('remove [name-or-id]')
69
69
  .alias('rm')
70
- .description('Remove an account (删除账号)')
70
+ .description('Remove an account by name or ID (通过名称或ID删除账号)')
71
71
  .action(removeAccount);
72
72
 
73
73
  // Show current account command
@@ -84,8 +84,8 @@ program
84
84
 
85
85
  // Export account configuration
86
86
  program
87
- .command('export <name>')
88
- .description('Export account configuration as JSON (导出账号配置为 JSON)')
87
+ .command('export <name-or-id>')
88
+ .description('Export account configuration as JSON by name or ID (通过名称或ID导出账号配置为 JSON)')
89
89
  .action(exportAccount);
90
90
 
91
91
  // Diagnostic command
@@ -199,36 +199,39 @@ program
199
199
  console.log(' ais <command> [options]\n');
200
200
 
201
201
  console.log(chalk.bold('COMMANDS (命令):'));
202
- console.log(' add [name] Add a new account configuration (with custom env vars) (添加新账号配置,支持自定义环境变量)');
203
- console.log(' list, ls List all available accounts (列出所有可用账号)');
204
- console.log(' use [name] Set the account for current project (设置当前项目使用的账号)');
205
- console.log(' info Show current project\'s account info (显示当前项目的账号信息)');
206
- console.log(' current Show current account name (显示当前账号名称)');
207
- console.log(' remove, rm Remove an account (删除账号)');
208
- console.log(' paths Show configuration file paths (显示配置文件路径)');
209
- console.log(' doctor Diagnose Claude Code configuration issues (诊断 Claude Code 配置问题)');
210
- console.log(' export <name> Export account as JSON (导出账号为 JSON)');
211
- console.log(' ui Start web-based account manager UI (启动基于 Web 的账号管理界面)');
212
- console.log(' model Manage model groups (管理模型组)');
213
- console.log(' mcp Manage MCP servers (管理 MCP 服务器)');
214
- console.log(' help Display this help message (显示此帮助信息)');
215
- console.log(' version Show version number (显示版本号)\n');
202
+ console.log(' add [name] Add a new account configuration (with custom env vars) (添加新账号配置,支持自定义环境变量)');
203
+ console.log(' list, ls List all available accounts with IDs (列出所有可用账号及其ID)');
204
+ console.log(' use [name-or-id] Set the account for current project by name or ID (通过名称或ID设置当前项目使用的账号)');
205
+ console.log(' info Show current project\'s account info (显示当前项目的账号信息)');
206
+ console.log(' current Show current account name (显示当前账号名称)');
207
+ console.log(' remove, rm [name-or-id] Remove an account by name or ID (通过名称或ID删除账号)');
208
+ console.log(' paths Show configuration file paths (显示配置文件路径)');
209
+ console.log(' doctor Diagnose Claude Code configuration issues (诊断 Claude Code 配置问题)');
210
+ console.log(' export <name-or-id> Export account as JSON by name or ID (通过名称或ID导出账号为 JSON)');
211
+ console.log(' ui Start web-based account manager UI (启动基于 Web 的账号管理界面)');
212
+ console.log(' model Manage model groups (管理模型组)');
213
+ console.log(' mcp Manage MCP servers (管理 MCP 服务器)');
214
+ console.log(' help Display this help message (显示此帮助信息)');
215
+ console.log(' version Show version number (显示版本号)\n');
216
216
 
217
217
  console.log(chalk.bold('EXAMPLES (示例):'));
218
218
  console.log(chalk.gray(' # Add a new account interactively (交互式添加新账号)'));
219
219
  console.log(' ais add\n');
220
220
  console.log(chalk.gray(' # Add a new account with a name (添加带名称的新账号)'));
221
221
  console.log(' ais add my-claude-account\n');
222
- console.log(chalk.gray(' # List all accounts (列出所有账号)'));
222
+ console.log(chalk.gray(' # List all accounts with IDs (列出所有账号及其ID)'));
223
223
  console.log(' ais list\n');
224
- console.log(chalk.gray(' # Use an account for current project (为当前项目使用某个账号)'));
224
+ console.log(chalk.gray(' # Use an account by name (通过名称使用账号)'));
225
225
  console.log(' ais use my-claude-account\n');
226
+ console.log(chalk.gray(' # Use an account by ID (通过ID使用账号)'));
227
+ console.log(' ais use 1\n');
226
228
  console.log(chalk.gray(' # Show current project info (显示当前项目信息)'));
227
229
  console.log(' ais info\n');
228
230
  console.log(chalk.gray(' # Diagnose configuration issues (诊断配置问题)'));
229
231
  console.log(' ais doctor\n');
230
- console.log(chalk.gray(' # Remove an account (删除账号)'));
231
- console.log(' ais remove my-old-account\n');
232
+ console.log(chalk.gray(' # Remove an account by name or ID (通过名称或ID删除账号)'));
233
+ console.log(' ais remove my-old-account');
234
+ console.log(' ais remove 2\n');
232
235
  console.log(chalk.gray(' # Start web UI for managing accounts (启动 Web 界面管理账号)'));
233
236
  console.log(' ais ui\n');
234
237
 
package/src/ui-server.js CHANGED
@@ -2,6 +2,7 @@ const http = require('http');
2
2
  const path = require('path');
3
3
  const fs = require('fs');
4
4
  const ConfigManager = require('./config');
5
+ const { WIRE_API_MODES, DEFAULT_WIRE_API } = require('./config');
5
6
 
6
7
  class UIServer {
7
8
  constructor(port = null) {
@@ -1491,6 +1492,18 @@ class UIServer {
1491
1492
  <label for="apiUrl" data-i18n="apiUrl">API URL (可选)</label>
1492
1493
  <input type="text" id="apiUrl" data-i18n-placeholder="apiUrlPlaceholder" placeholder="https://api.anthropic.com">
1493
1494
  </div>
1495
+ <!-- Wire API selection for Codex accounts -->
1496
+ <div class="form-group" id="wireApiGroup" style="display: none;">
1497
+ <label for="wireApi">Wire API 模式</label>
1498
+ <select id="wireApi">
1499
+ <option value="chat">chat - HTTP Headers 认证 (OpenAI 兼容)</option>
1500
+ <option value="responses">responses - auth.json 认证 (requires_openai_auth)</option>
1501
+ </select>
1502
+ <small style="color: #666; display: block; margin-top: 5px;">
1503
+ chat: API key 存储在 HTTP headers 中<br>
1504
+ responses: API key 存储在 ~/.codex/auth.json 中
1505
+ </small>
1506
+ </div>
1494
1507
  <div class="form-group">
1495
1508
  <label for="email" data-i18n="email">邮箱 (可选)</label>
1496
1509
  <input type="email" id="email" data-i18n-placeholder="emailPlaceholder" placeholder="user@example.com">
@@ -1615,6 +1628,13 @@ class UIServer {
1615
1628
  </div>
1616
1629
 
1617
1630
  <script>
1631
+ // Constants for wire API modes (injected from backend)
1632
+ const WIRE_API_MODES = {
1633
+ CHAT: '${WIRE_API_MODES.CHAT}',
1634
+ RESPONSES: '${WIRE_API_MODES.RESPONSES}'
1635
+ };
1636
+ const DEFAULT_WIRE_API = '${DEFAULT_WIRE_API}';
1637
+
1618
1638
  // i18n translations
1619
1639
  const translations = {
1620
1640
  zh: {
@@ -2021,6 +2041,12 @@ class UIServer {
2021
2041
  <div class="info-value">\${data.model}</div>
2022
2042
  </div>
2023
2043
  \` : ''}
2044
+ \${data.type === 'Codex' ? \`
2045
+ <div class="account-info">
2046
+ <div class="info-label">Wire API</div>
2047
+ <div class="info-value">\${data.wireApi || (DEFAULT_WIRE_API + ' (default)')}</div>
2048
+ </div>
2049
+ \` : ''}
2024
2050
  \${data.type === 'CCR' && data.ccrConfig ? \`
2025
2051
  <div class="account-info">
2026
2052
  <div class="info-label">CCR Provider</div>
@@ -2051,6 +2077,12 @@ class UIServer {
2051
2077
  const simpleModelGroup = document.getElementById('simpleModelGroup');
2052
2078
  const claudeModelGroup = document.getElementById('claudeModelGroup');
2053
2079
  const ccrModelGroup = document.getElementById('ccrModelGroup');
2080
+ const wireApiGroup = document.getElementById('wireApiGroup');
2081
+
2082
+ // Show/hide wire_api field for Codex accounts
2083
+ if (wireApiGroup) {
2084
+ wireApiGroup.style.display = accountType === 'Codex' ? 'block' : 'none';
2085
+ }
2054
2086
 
2055
2087
  if (accountType === 'Codex' || accountType === 'Droids') {
2056
2088
  simpleModelGroup.style.display = 'block';
@@ -2117,6 +2149,12 @@ class UIServer {
2117
2149
  if (account.type === 'Codex' || account.type === 'Droids') {
2118
2150
  // Load simple model field
2119
2151
  document.getElementById('simpleModel').value = account.model || '';
2152
+
2153
+ // Load wire_api for Codex accounts
2154
+ if (account.type === 'Codex') {
2155
+ document.getElementById('wireApi').value = account.wireApi || DEFAULT_WIRE_API;
2156
+ }
2157
+
2120
2158
  // Clear model groups and CCR config
2121
2159
  document.getElementById('modelGroupsList').innerHTML = '';
2122
2160
  modelGroupCount = 0;
@@ -2379,6 +2417,14 @@ class UIServer {
2379
2417
  if (simpleModel) {
2380
2418
  accountData.model = simpleModel;
2381
2419
  }
2420
+
2421
+ // Add wire_api for Codex accounts
2422
+ if (accountType === 'Codex') {
2423
+ const wireApi = document.getElementById('wireApi').value;
2424
+ if (wireApi) {
2425
+ accountData.wireApi = wireApi;
2426
+ }
2427
+ }
2382
2428
  } else if (accountType === 'CCR') {
2383
2429
  // Collect CCR config
2384
2430
  const providerName = document.getElementById('ccrProviderName').value.trim();
@@ -1,176 +0,0 @@
1
- # Changelog v1.7.0
2
-
3
- ## 🎉 Major Features
4
-
5
- ### MCP (Model Context Protocol) Web UI Management
6
- - **Full MCP server management through Web UI**
7
- - Add, edit, delete MCP servers
8
- - Support for stdio, sse, and http server types
9
- - Test MCP server connections
10
- - Enable/disable servers per project
11
- - Sync configuration to Claude Code
12
- - Search and filter functionality
13
- - Complete internationalization (Chinese/English)
14
-
15
- ### Backend API (9 new endpoints)
16
- - `GET /api/mcp-servers` - Get all MCP servers
17
- - `POST /api/mcp-servers` - Add new MCP server
18
- - `PUT /api/mcp-servers/:name` - Update MCP server
19
- - `DELETE /api/mcp-servers/:name` - Delete MCP server
20
- - `POST /api/mcp-servers/:name/test` - Test connection
21
- - `POST /api/mcp-servers/:name/enable` - Enable for project
22
- - `POST /api/mcp-servers/:name/disable` - Disable for project
23
- - `GET /api/mcp-servers/enabled` - Get enabled servers
24
- - `POST /api/mcp-servers/sync` - Sync configuration
25
-
26
- ### Frontend UI Enhancements
27
- - **Tab Navigation System**
28
- - Accounts management tab
29
- - MCP servers management tab
30
- - Smooth tab switching with event listeners
31
-
32
- - **MCP Server Management Interface**
33
- - Card-based layout matching existing design
34
- - Dynamic forms based on server type
35
- - Environment variables and headers support
36
- - Status indicators (enabled/disabled)
37
- - Real-time search and filtering
38
-
39
- ## 🐛 Bug Fixes
40
-
41
- ### Fixed: Account Data Not Showing
42
- - **Issue**: After adding tabs, account data disappeared
43
- - **Cause**: Incorrect HTML structure and indentation
44
- - **Fix**: Corrected tab div structure and proper indentation
45
-
46
- ### Fixed: switchTab is not defined
47
- - **Issue**: JavaScript error when clicking tabs
48
- - **Cause**: Function defined after being used in onclick
49
- - **Fix**: Replaced onclick attributes with event listeners using DOMContentLoaded
50
-
51
- ### Fixed: Incorrect Search Result Messages
52
- - **Issue**: When search has no results, showed "Add your first account" message
53
- - **Cause**: Did not distinguish between "no data" and "no search results"
54
- - **Fix**: Smart detection of filtered state vs empty state
55
- - No data: Shows "Add your first..." message
56
- - No search results: Shows "No matching results found"
57
-
58
- ## ✨ Improvements
59
-
60
- ### User Experience
61
- - Better error messages for search results
62
- - Clearer distinction between empty state and filtered state
63
- - Improved tab navigation with proper event handling
64
- - Consistent UI design across all features
65
-
66
- ### Code Quality
67
- - Modern JavaScript practices (event listeners vs onclick)
68
- - Better separation of concerns
69
- - Improved maintainability
70
- - Comprehensive error handling
71
-
72
- ### Internationalization
73
- - Complete Chinese translations for MCP features
74
- - Complete English translations for MCP features
75
- - Consistent translation keys
76
-
77
- ## 📊 Testing
78
-
79
- ### Automated Tests
80
- - 32/32 tests passing
81
- - Comprehensive coverage of all features
82
- - Verification scripts included
83
-
84
- ### Manual Testing
85
- - Test guides provided
86
- - Step-by-step instructions
87
- - Expected results documented
88
-
89
- ## 📁 New Files
90
-
91
- ### Documentation
92
- - `MCP-UI-IMPLEMENTATION.md` - Complete implementation guide
93
- - `FIXED-ISSUES.md` - Detailed problem fixes
94
- - `SEARCH-FIX.md` - Search result fix documentation
95
- - `QUICK-TEST.md` - Quick testing guide
96
- - `TEST-NOW.md` - Immediate testing instructions
97
- - `CHANGELOG-v1.7.0.md` - This file
98
-
99
- ### Testing
100
- - `verify-implementation.js` - Automated verification script
101
- - `test-ui.html` - Manual testing guide
102
-
103
- ## 🔧 Technical Details
104
-
105
- ### Modified Files
106
- - `src/ui-server.js` - Main implementation file
107
- - Added 9 API handler methods
108
- - Added MCP tab HTML structure
109
- - Added MCP modal HTML
110
- - Added MCP JavaScript functions
111
- - Added MCP translations (zh/en)
112
- - Added tab navigation CSS
113
- - Fixed switchTab function placement
114
- - Fixed search result messages
115
-
116
- ### Code Statistics
117
- - Lines added: ~1500
118
- - API endpoints: +9
119
- - JavaScript functions: +15
120
- - Translations: +40 keys
121
- - Tests: 32 automated tests
122
-
123
- ## 🎯 Migration Guide
124
-
125
- ### For Users
126
- No migration needed. All existing functionality remains unchanged.
127
-
128
- ### For Developers
129
- If you've customized the UI:
130
- 1. Tab navigation now uses event listeners instead of onclick
131
- 2. Search results have improved empty state handling
132
- 3. New MCP management APIs available
133
-
134
- ## 🚀 Upgrade Instructions
135
-
136
- ### From v1.6.x to v1.7.0
137
-
138
- 1. **Update package**:
139
- ```bash
140
- npm install -g ai-account-switch@1.7.0
141
- ```
142
-
143
- 2. **Verify installation**:
144
- ```bash
145
- ais --version
146
- # Should show: 1.7.0
147
- ```
148
-
149
- 3. **Test new features**:
150
- ```bash
151
- ais ui
152
- # Navigate to MCP Servers tab
153
- ```
154
-
155
- ## 📝 Breaking Changes
156
-
157
- None. This is a backward-compatible release.
158
-
159
- ## 🙏 Acknowledgments
160
-
161
- - Comprehensive testing and verification
162
- - User feedback incorporated
163
- - Production-ready code quality
164
-
165
- ## 📚 Resources
166
-
167
- - [README.md](README.md) - Main documentation
168
- - [MCP-UI-IMPLEMENTATION.md](MCP-UI-IMPLEMENTATION.md) - Implementation details
169
- - [QUICK-TEST.md](QUICK-TEST.md) - Testing guide
170
-
171
- ---
172
-
173
- **Release Date**: 2025-11-12
174
- **Version**: 1.7.0
175
- **Status**: Stable
176
- **Tests**: 32/32 passing