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.
@@ -9,6 +9,37 @@ function maskApiKey(apiKey) {
9
9
  return `${apiKey.substring(0, 4)}****${apiKey.substring(apiKey.length - 4)}`;
10
10
  }
11
11
 
12
+ /**
13
+ * Mask sensitive value for display
14
+ * Shows first 2 chars + fixed 6 stars + last 2 chars for sensitive variables
15
+ * 用于显示敏感环境变量值,前2字符+固定6星号+后2字符
16
+ * @param {string} key - Variable name to check if sensitive
17
+ * @param {string} value - Value to mask
18
+ * @returns {string} Masked value or original value if not sensitive
19
+ */
20
+ function maskEnvValue(key, value) {
21
+ if (!key || !value) return value;
22
+
23
+ // Check if variable name contains sensitive keywords
24
+ const isSensitive = key.includes('KEY') || key.includes('TOKEN') || key.includes('SECRET') || key.includes('PASSWORD');
25
+
26
+ if (!isSensitive) {
27
+ return value;
28
+ }
29
+
30
+ // For sensitive values, show first 2 + fixed 6 stars + last 2
31
+ const strValue = String(value);
32
+ if (strValue.length <= 4) {
33
+ // If value is too short, show all stars
34
+ return '*'.repeat(strValue.length);
35
+ }
36
+
37
+ const firstTwo = strValue.substring(0, 2);
38
+ const lastTwo = strValue.substring(strValue.length - 2);
39
+
40
+ return firstTwo + '******' + lastTwo;
41
+ }
42
+
12
43
  /**
13
44
  * Validate account by testing API key
14
45
  * 验证账号的 API key 是否有效
@@ -31,5 +62,6 @@ async function validateAccount(apiKey, apiUrl) {
31
62
 
32
63
  module.exports = {
33
64
  maskApiKey,
65
+ maskEnvValue,
34
66
  validateAccount
35
67
  };
@@ -42,6 +42,17 @@ const {
42
42
  testMcpServer
43
43
  } = require('./mcp');
44
44
 
45
+ const {
46
+ listEnv,
47
+ addEnv,
48
+ setEnv,
49
+ removeEnv,
50
+ unsetEnv,
51
+ showEnv,
52
+ clearEnv,
53
+ editEnv
54
+ } = require('./env');
55
+
45
56
  module.exports = {
46
57
  // Account management commands
47
58
  addAccount,
@@ -74,5 +85,15 @@ module.exports = {
74
85
  disableMcpServer,
75
86
  showEnabledMcpServers,
76
87
  syncMcpConfig,
77
- testMcpServer
88
+ testMcpServer,
89
+
90
+ // Environment variable management commands
91
+ listEnv,
92
+ addEnv,
93
+ setEnv,
94
+ removeEnv,
95
+ unsetEnv,
96
+ showEnv,
97
+ clearEnv,
98
+ editEnv
78
99
  };
@@ -10,6 +10,8 @@ const config = new ConfigManager();
10
10
  */
11
11
  async function addMcpServer(name) {
12
12
  try {
13
+ const { MCP_SCOPES, DEFAULT_MCP_SCOPE } = require('../config');
14
+
13
15
  if (!name) {
14
16
  const { serverName } = await inquirer.prompt([{
15
17
  type: 'input',
@@ -79,8 +81,22 @@ async function addMcpServer(name) {
79
81
  const { description } = await inquirer.prompt([{ type: 'input', name: 'description', message: 'Enter description:', default: '' }]);
80
82
  serverData.description = description;
81
83
 
84
+ // Ask for default scope
85
+ const { scope } = await inquirer.prompt([{
86
+ type: 'list',
87
+ name: 'scope',
88
+ message: 'Select default scope (默认作用范围):',
89
+ choices: [
90
+ { name: 'local - Only current project (仅当前项目)', value: MCP_SCOPES.LOCAL },
91
+ { name: 'project - Share with project members (与项目成员共享)', value: MCP_SCOPES.PROJECT },
92
+ { name: 'user - All projects for current user (当前用户所有项目)', value: MCP_SCOPES.USER }
93
+ ],
94
+ default: DEFAULT_MCP_SCOPE
95
+ }]);
96
+ serverData.scope = scope;
97
+
82
98
  config.addMcpServer(name, serverData);
83
- console.log(chalk.green(`✓ MCP server '${name}' added successfully!`));
99
+ console.log(chalk.green(`✓ MCP server '${name}' added successfully with scope: ${scope}!`));
84
100
 
85
101
  // Auto-test the server
86
102
  console.log(chalk.cyan('\nTesting server availability...'));
@@ -101,7 +117,7 @@ async function addMcpServer(name) {
101
117
  */
102
118
  async function listMcpServers() {
103
119
  try {
104
- const servers = config.getAllMcpServers();
120
+ const servers = config.getAllAvailableMcpServers();
105
121
  const projectServers = config.getEnabledMcpServers();
106
122
 
107
123
  if (Object.keys(servers).length === 0) {
@@ -110,12 +126,16 @@ async function listMcpServers() {
110
126
  }
111
127
 
112
128
  console.log(chalk.bold.cyan('\n📋 Available MCP servers:\n'));
113
- console.log(' Name Type Active Description');
114
- console.log(' ─────────────────────────────────────────────────────────────');
115
-
116
- Object.values(servers).forEach(server => {
117
- const isActive = projectServers.includes(server.name) ? chalk.green('✓') : ' ';
118
- console.log(` ${server.name.padEnd(12)} ${server.type.padEnd(7)} ${isActive} ${server.description || ''}`);
129
+ console.log(' Name Type Active Scope Description');
130
+ console.log(' ───────────────────────────────────────────────────────────────────────');
131
+
132
+ Object.entries(servers).forEach(([key, server]) => {
133
+ const name = server.name || key;
134
+ const type = server.type || 'unknown';
135
+ const isActive = projectServers.includes(name) ? chalk.green('✓') : ' ';
136
+ const scope = server.scope || 'local';
137
+ const scopeDisplay = scope.padEnd(10);
138
+ console.log(` ${name.padEnd(12)} ${type.padEnd(7)} ${isActive} ${scopeDisplay} ${server.description || ''}`);
119
139
  });
120
140
 
121
141
  const activeCount = projectServers.length;
@@ -133,7 +153,7 @@ async function listMcpServers() {
133
153
  async function showMcpServer(name) {
134
154
  try {
135
155
  if (!name) {
136
- const servers = config.getAllMcpServers();
156
+ const servers = config.getAllAvailableMcpServers();
137
157
  if (Object.keys(servers).length === 0) {
138
158
  console.log(chalk.yellow('No MCP servers configured'));
139
159
  return;
@@ -155,6 +175,7 @@ async function showMcpServer(name) {
155
175
 
156
176
  console.log(chalk.bold.cyan(`\n📋 MCP Server: ${name}\n`));
157
177
  console.log(chalk.bold('Type:'), server.type);
178
+ console.log(chalk.bold('Scope:'), server.scope || 'local');
158
179
  console.log(chalk.bold('Description:'), server.description || 'N/A');
159
180
 
160
181
  if (server.command) console.log(chalk.bold('Command:'), server.command);
@@ -322,8 +343,17 @@ async function removeMcpServer(name) {
322
343
  /**
323
344
  * Enable MCP server for current project
324
345
  */
325
- async function enableMcpServer(name) {
346
+ async function enableMcpServer(name, options = {}) {
326
347
  try {
348
+ const { MCP_SCOPES, DEFAULT_MCP_SCOPE } = require('../config');
349
+ let scope = options.scope || DEFAULT_MCP_SCOPE;
350
+
351
+ // Validate scope
352
+ if (!Object.values(MCP_SCOPES).includes(scope)) {
353
+ console.log(chalk.red(`✗ Invalid scope '${scope}'. Valid scopes: local, project, user`));
354
+ return;
355
+ }
356
+
327
357
  if (!name) {
328
358
  const servers = config.getAllMcpServers();
329
359
  const enabled = config.getEnabledMcpServers();
@@ -341,12 +371,38 @@ async function enableMcpServer(name) {
341
371
  choices: available
342
372
  }]);
343
373
  name = serverName;
374
+
375
+ // Ask for scope if not provided
376
+ if (!options.scope) {
377
+ const { selectedScope } = await inquirer.prompt([{
378
+ type: 'list',
379
+ name: 'selectedScope',
380
+ message: 'Select scope (作用范围):',
381
+ choices: [
382
+ { name: 'local - Only current project (仅当前项目)', value: MCP_SCOPES.LOCAL },
383
+ { name: 'project - Share with project members via .mcp.json (通过 .mcp.json 与项目成员共享)', value: MCP_SCOPES.PROJECT },
384
+ { name: 'user - All projects for current user (当前用户所有项目)', value: MCP_SCOPES.USER }
385
+ ],
386
+ default: DEFAULT_MCP_SCOPE
387
+ }]);
388
+ scope = selectedScope;
389
+ }
344
390
  }
345
391
 
346
- if (config.enableProjectMcpServer(name)) {
392
+ if (config.enableProjectMcpServer(name, scope)) {
347
393
  config.syncMcpConfig();
348
- console.log(chalk.green(`✓ MCP server '${name}' activated for current project`));
394
+ console.log(chalk.green(`✓ MCP server '${name}' activated for current project with scope: ${scope}`));
349
395
  console.log(chalk.green('✓ Claude configuration updated'));
396
+
397
+ // Show scope-specific information
398
+ if (scope === MCP_SCOPES.LOCAL) {
399
+ console.log(chalk.cyan(' Scope: local - Only available in this project'));
400
+ } else if (scope === MCP_SCOPES.PROJECT) {
401
+ console.log(chalk.cyan(' Scope: project - Configuration stored in project, shared with team members'));
402
+ console.log(chalk.gray(' Note: Make sure to commit .ais-project-config to share with your team'));
403
+ } else if (scope === MCP_SCOPES.USER) {
404
+ console.log(chalk.cyan(' Scope: user - Available to all your projects'));
405
+ }
350
406
  } else {
351
407
  console.log(chalk.red(`✗ MCP server '${name}' not found`));
352
408
  }
@@ -408,7 +464,9 @@ async function showEnabledMcpServers() {
408
464
  enabled.forEach(name => {
409
465
  const server = servers[name];
410
466
  if (server) {
411
- console.log(` ${server.name.padEnd(12)} ${server.type.padEnd(7)} ${server.description || ''}`);
467
+ const serverName = server.name || name;
468
+ const type = server.type || 'unknown';
469
+ console.log(` ${String(serverName).padEnd(12)} ${String(type).padEnd(7)} ${server.description || ''}`);
412
470
  }
413
471
  });
414
472
 
package/src/config.js CHANGED
@@ -5,7 +5,8 @@ const os = require('os');
5
5
  // Constants for wire API modes
6
6
  const WIRE_API_MODES = {
7
7
  CHAT: 'chat',
8
- RESPONSES: 'responses'
8
+ RESPONSES: 'responses',
9
+ ENV: 'env'
9
10
  };
10
11
 
11
12
  const DEFAULT_WIRE_API = WIRE_API_MODES.CHAT;
@@ -18,6 +19,15 @@ const ACCOUNT_TYPES = {
18
19
  DROIDS: 'Droids'
19
20
  };
20
21
 
22
+ // Constants for MCP server scopes
23
+ const MCP_SCOPES = {
24
+ LOCAL: 'local', // Only available in current project
25
+ PROJECT: 'project', // Shared with project members via .mcp.json
26
+ USER: 'user' // Available to all projects for current user (global)
27
+ };
28
+
29
+ const DEFAULT_MCP_SCOPE = MCP_SCOPES.LOCAL;
30
+
21
31
  /**
22
32
  * Cross-platform configuration manager
23
33
  * Stores global accounts in user home directory
@@ -698,23 +708,20 @@ class ConfigManager {
698
708
 
699
709
  // Update auth.json with API key
700
710
  this.updateCodexAuthJson(account.apiKey);
711
+ } else if (wireApi === WIRE_API_MODES.ENV) {
712
+ // Env mode: use environment variable for authentication
713
+ profileConfig += `wire_api = "${WIRE_API_MODES.CHAT}"\n`;
714
+ const envKey = account.envKey || 'AIS_USER_API_KEY';
715
+ profileConfig += `env_key = "${envKey}"\n`;
716
+
717
+ // Clear auth.json to ensure env mode is used
718
+ this.clearCodexAuthJson();
701
719
  }
702
720
  }
703
721
 
704
722
  // Remove all old profiles with the same name (including duplicates)
705
- // Pattern 1: Remove profiles with AIS comment header
706
- const commentedProfilePattern = new RegExp(
707
- `# AIS Profile for project: [^\\n]*${escapedProjectName}[^\\n]*\\n\\[profiles\\.${escapedProfileName}\\]\\n(?:[^\\[].*\\n)*`,
708
- 'g'
709
- );
710
- existingConfig = existingConfig.replace(commentedProfilePattern, '');
711
-
712
- // Pattern 2: Remove standalone profiles without comment
713
- const standaloneProfilePattern = new RegExp(
714
- `\\n\\[profiles\\.${escapedProfileName}\\]\\n(?:[^\\[].*\\n)*(?=\\n\\[|$)`,
715
- 'g'
716
- );
717
- existingConfig = existingConfig.replace(standaloneProfilePattern, '');
723
+ // Use line-by-line parsing for more reliable cleanup
724
+ existingConfig = this._removeProfileFromConfig(existingConfig, profileName);
718
725
 
719
726
  // Append new profile
720
727
  const newConfig = existingConfig.trimEnd() + '\n' + profileConfig;
@@ -787,6 +794,68 @@ class ConfigManager {
787
794
  }
788
795
  }
789
796
 
797
+ /**
798
+ * Remove a profile from TOML config string
799
+ * Uses line-by-line parsing for reliable removal of all instances
800
+ * @private
801
+ * @param {string} configContent - The TOML config content
802
+ * @param {string} profileName - The profile name to remove (e.g., "ais_myproject")
803
+ * @returns {string} Cleaned config content
804
+ */
805
+ _removeProfileFromConfig(configContent, profileName) {
806
+ const lines = configContent.split('\n');
807
+ const cleanedLines = [];
808
+ let skipUntilNextSection = false;
809
+ const profileSectionHeader = `[profiles.${profileName}]`;
810
+
811
+ for (let i = 0; i < lines.length; i++) {
812
+ const line = lines[i];
813
+ const trimmedLine = line.trim();
814
+
815
+ // Check if this is the profile section we want to remove
816
+ if (trimmedLine === profileSectionHeader) {
817
+ skipUntilNextSection = true;
818
+
819
+ // Remove the AIS comment line before it if present
820
+ if (cleanedLines.length > 0) {
821
+ const lastLine = cleanedLines[cleanedLines.length - 1].trim();
822
+ if (lastLine.startsWith('# AIS Profile for project:')) {
823
+ cleanedLines.pop();
824
+ }
825
+ }
826
+
827
+ // Remove trailing empty lines before the profile
828
+ while (cleanedLines.length > 0 && cleanedLines[cleanedLines.length - 1].trim() === '') {
829
+ cleanedLines.pop();
830
+ }
831
+
832
+ continue; // Skip the profile header line
833
+ }
834
+
835
+ // If we're in skip mode, check if we've reached the next section
836
+ if (skipUntilNextSection) {
837
+ // A new section starts with '[' at the beginning (after trimming)
838
+ if (trimmedLine.startsWith('[')) {
839
+ skipUntilNextSection = false;
840
+ // Don't skip this line - it's the start of a new section
841
+ } else {
842
+ // Skip this line as it belongs to the profile we're removing
843
+ continue;
844
+ }
845
+ }
846
+
847
+ cleanedLines.push(line);
848
+ }
849
+
850
+ // Join lines and clean up excessive empty lines
851
+ let result = cleanedLines.join('\n');
852
+
853
+ // Replace 3+ consecutive newlines with just 2 (one blank line)
854
+ result = result.replace(/\n{3,}/g, '\n\n');
855
+
856
+ return result;
857
+ }
858
+
790
859
  /**
791
860
  * Clear OPENAI_API_KEY in ~/.codex/auth.json for chat mode
792
861
  * @deprecated This method is no longer called automatically.
@@ -923,6 +992,12 @@ class ConfigManager {
923
992
  addMcpServer(name, serverData) {
924
993
  const config = this.readGlobalConfig();
925
994
  if (!config.mcpServers) config.mcpServers = {};
995
+
996
+ // Set default scope if not specified
997
+ if (!serverData.scope) {
998
+ serverData.scope = DEFAULT_MCP_SCOPE;
999
+ }
1000
+
926
1001
  config.mcpServers[name] = {
927
1002
  ...serverData,
928
1003
  createdAt: config.mcpServers[name]?.createdAt || new Date().toISOString(),
@@ -1043,9 +1118,11 @@ class ConfigManager {
1043
1118
  }
1044
1119
 
1045
1120
  /**
1046
- * Enable MCP server for current project
1121
+ * Enable MCP server for current project with scope
1122
+ * @param {string} serverName - Name of the MCP server
1123
+ * @param {string} scope - Scope: 'local', 'project', or 'user'
1047
1124
  */
1048
- enableProjectMcpServer(serverName) {
1125
+ enableProjectMcpServer(serverName, scope = DEFAULT_MCP_SCOPE) {
1049
1126
  const server = this.getMcpServer(serverName);
1050
1127
  if (!server) return false;
1051
1128
 
@@ -1063,11 +1140,42 @@ class ConfigManager {
1063
1140
  const data = fs.readFileSync(projectConfigFile, 'utf8');
1064
1141
  const projectConfig = JSON.parse(data);
1065
1142
 
1066
- if (!projectConfig.enabledMcpServers) projectConfig.enabledMcpServers = [];
1067
- if (!projectConfig.enabledMcpServers.includes(serverName)) {
1068
- projectConfig.enabledMcpServers.push(serverName);
1069
- fs.writeFileSync(projectConfigFile, JSON.stringify(projectConfig, null, 2), 'utf8');
1143
+ // Update server scope in global config
1144
+ const globalConfig = this.readGlobalConfig();
1145
+ if (globalConfig.mcpServers[serverName]) {
1146
+ globalConfig.mcpServers[serverName].scope = scope;
1147
+ this.saveGlobalConfig(globalConfig);
1070
1148
  }
1149
+
1150
+ // Handle different scopes
1151
+ if (scope === MCP_SCOPES.LOCAL) {
1152
+ // Local scope: only enable for current project
1153
+ if (!projectConfig.enabledMcpServers) projectConfig.enabledMcpServers = [];
1154
+ if (!projectConfig.enabledMcpServers.includes(serverName)) {
1155
+ projectConfig.enabledMcpServers.push(serverName);
1156
+ }
1157
+ } else if (scope === MCP_SCOPES.PROJECT) {
1158
+ // Project scope: store in project config for sharing
1159
+ if (!projectConfig.projectMcpServers) projectConfig.projectMcpServers = {};
1160
+ projectConfig.projectMcpServers[serverName] = {
1161
+ ...server,
1162
+ scope: MCP_SCOPES.PROJECT
1163
+ };
1164
+
1165
+ // Also add to enabled list
1166
+ if (!projectConfig.enabledMcpServers) projectConfig.enabledMcpServers = [];
1167
+ if (!projectConfig.enabledMcpServers.includes(serverName)) {
1168
+ projectConfig.enabledMcpServers.push(serverName);
1169
+ }
1170
+ } else if (scope === MCP_SCOPES.USER) {
1171
+ // User scope: mark as globally enabled
1172
+ if (!projectConfig.enabledMcpServers) projectConfig.enabledMcpServers = [];
1173
+ if (!projectConfig.enabledMcpServers.includes(serverName)) {
1174
+ projectConfig.enabledMcpServers.push(serverName);
1175
+ }
1176
+ }
1177
+
1178
+ fs.writeFileSync(projectConfigFile, JSON.stringify(projectConfig, null, 2), 'utf8');
1071
1179
  return true;
1072
1180
  } catch (error) {
1073
1181
  throw new Error(`Failed to enable MCP server: ${error.message}`);
@@ -1108,13 +1216,58 @@ class ConfigManager {
1108
1216
 
1109
1217
  /**
1110
1218
  * Get enabled MCP servers for current project
1219
+ * Includes local, project, and user-scoped servers
1111
1220
  */
1112
1221
  getEnabledMcpServers() {
1113
- return this.getProjectMcpServers();
1222
+ const projectServers = this.getProjectMcpServers();
1223
+ const globalServers = this.getAllMcpServers();
1224
+
1225
+ // Add user-scoped servers that are globally enabled
1226
+ const userScopedServers = Object.keys(globalServers).filter(name =>
1227
+ globalServers[name].scope === MCP_SCOPES.USER && !projectServers.includes(name)
1228
+ );
1229
+
1230
+ return [...projectServers, ...userScopedServers];
1231
+ }
1232
+
1233
+ /**
1234
+ * Get all available MCP servers including project-scoped ones
1235
+ */
1236
+ getAllAvailableMcpServers() {
1237
+ const globalServers = this.getAllMcpServers();
1238
+ const projectRoot = this.findProjectRoot();
1239
+
1240
+ if (!projectRoot) {
1241
+ return globalServers;
1242
+ }
1243
+
1244
+ try {
1245
+ const projectConfigFile = path.join(projectRoot, this.projectConfigFilename);
1246
+ if (!fs.existsSync(projectConfigFile)) {
1247
+ return globalServers;
1248
+ }
1249
+
1250
+ const data = fs.readFileSync(projectConfigFile, 'utf8');
1251
+ const projectConfig = JSON.parse(data);
1252
+
1253
+ // Merge global and project servers
1254
+ const allServers = { ...globalServers };
1255
+
1256
+ if (projectConfig.projectMcpServers) {
1257
+ Object.entries(projectConfig.projectMcpServers).forEach(([name, server]) => {
1258
+ allServers[name] = server;
1259
+ });
1260
+ }
1261
+
1262
+ return allServers;
1263
+ } catch (error) {
1264
+ return globalServers;
1265
+ }
1114
1266
  }
1115
1267
 
1116
1268
  /**
1117
1269
  * Get Claude Code user config path (cross-platform)
1270
+ * Priority: ~/.claude/settings.json > platform-specific paths > legacy paths
1118
1271
  */
1119
1272
  getClaudeUserConfigPath() {
1120
1273
  const platform = process.platform;
@@ -1122,12 +1275,30 @@ class ConfigManager {
1122
1275
 
1123
1276
  if (!home) return null;
1124
1277
 
1125
- // Try common locations
1126
- const locations = [
1127
- path.join(home, '.claude.json'),
1128
- path.join(home, '.config', 'claude', 'config.json')
1129
- ];
1278
+ // Priority order for Claude user config
1279
+ const locations = [];
1280
+
1281
+ // Primary location: ~/.claude/settings.json (modern Claude Code)
1282
+ locations.push(path.join(home, '.claude', 'settings.json'));
1130
1283
 
1284
+ // Platform-specific locations
1285
+ if (platform === 'win32') {
1286
+ // Windows: %APPDATA%\claude\settings.json
1287
+ const appData = process.env.APPDATA;
1288
+ if (appData) {
1289
+ locations.push(path.join(appData, 'claude', 'settings.json'));
1290
+ locations.push(path.join(appData, 'claude', 'config.json'));
1291
+ }
1292
+ } else {
1293
+ // macOS/Linux: ~/.config/claude/settings.json
1294
+ locations.push(path.join(home, '.config', 'claude', 'settings.json'));
1295
+ locations.push(path.join(home, '.config', 'claude', 'config.json'));
1296
+ }
1297
+
1298
+ // Legacy fallback: ~/.claude.json
1299
+ locations.push(path.join(home, '.claude.json'));
1300
+
1301
+ // Return first existing location
1131
1302
  for (const loc of locations) {
1132
1303
  if (fs.existsSync(loc)) {
1133
1304
  return loc;
@@ -1353,14 +1524,22 @@ class ConfigManager {
1353
1524
 
1354
1525
  // Get enabled MCP servers
1355
1526
  const enabledServers = this.getEnabledMcpServers();
1356
- const allServers = this.getAllMcpServers();
1527
+ const allServers = this.getAllAvailableMcpServers();
1528
+
1529
+ // Filter servers by scope:
1530
+ // - Only 'project' scoped servers should be in .mcp.json (shared with team)
1531
+ // - 'local' and 'user' scoped servers should NOT be in .mcp.json
1532
+ const projectScopedServers = enabledServers.filter(serverName => {
1533
+ const server = allServers[serverName];
1534
+ return server && server.scope === MCP_SCOPES.PROJECT;
1535
+ });
1357
1536
 
1358
- if (enabledServers.length > 0) {
1537
+ if (projectScopedServers.length > 0) {
1359
1538
  const mcpConfig = {
1360
1539
  mcpServers: {}
1361
1540
  };
1362
1541
 
1363
- enabledServers.forEach(serverName => {
1542
+ projectScopedServers.forEach(serverName => {
1364
1543
  const server = allServers[serverName];
1365
1544
  if (server) {
1366
1545
  const serverConfig = {};
@@ -1395,7 +1574,7 @@ class ConfigManager {
1395
1574
 
1396
1575
  fs.writeFileSync(mcpConfigFile, JSON.stringify(mcpConfig, null, 2), 'utf8');
1397
1576
  } else {
1398
- // Remove .mcp.json if no servers are enabled
1577
+ // Remove .mcp.json if no project-scoped servers are enabled
1399
1578
  if (fs.existsSync(mcpConfigFile)) {
1400
1579
  fs.unlinkSync(mcpConfigFile);
1401
1580
  }
@@ -1410,3 +1589,5 @@ module.exports = ConfigManager;
1410
1589
  module.exports.WIRE_API_MODES = WIRE_API_MODES;
1411
1590
  module.exports.DEFAULT_WIRE_API = DEFAULT_WIRE_API;
1412
1591
  module.exports.ACCOUNT_TYPES = ACCOUNT_TYPES;
1592
+ module.exports.MCP_SCOPES = MCP_SCOPES;
1593
+ module.exports.DEFAULT_MCP_SCOPE = DEFAULT_MCP_SCOPE;
package/src/index.js CHANGED
@@ -27,7 +27,15 @@ const {
27
27
  disableMcpServer,
28
28
  showEnabledMcpServers,
29
29
  syncMcpConfig,
30
- testMcpServer
30
+ testMcpServer,
31
+ listEnv,
32
+ addEnv,
33
+ setEnv,
34
+ removeEnv,
35
+ unsetEnv,
36
+ showEnv,
37
+ clearEnv,
38
+ editEnv
31
39
  } = require('./commands');
32
40
 
33
41
  // Package info
@@ -167,6 +175,7 @@ mcpCommand
167
175
  mcpCommand
168
176
  .command('enable [name]')
169
177
  .description('Activate MCP server for current project (为当前项目激活 MCP 服务器)')
178
+ .option('-s, --scope <scope>', 'Scope: local (default), project, or user (作用范围: local(默认), project, user)')
170
179
  .action(enableMcpServer);
171
180
 
172
181
  mcpCommand
@@ -189,6 +198,58 @@ mcpCommand
189
198
  .description('Test MCP server availability (测试 MCP 服务器可用性)')
190
199
  .action(testMcpServer);
191
200
 
201
+ // Environment variable management commands
202
+ const envCommand = program
203
+ .command('env')
204
+ .description('Manage Claude environment variables (管理 Claude 环境变量)');
205
+
206
+ envCommand
207
+ .command('list')
208
+ .alias('ls')
209
+ .description('List all environment variables from project and user configs (列出项目和用户配置中的所有环境变量)')
210
+ .action(listEnv);
211
+
212
+ envCommand
213
+ .command('add')
214
+ .description('Add or update an environment variable interactively (交互式添加或更新环境变量)')
215
+ .action(addEnv);
216
+
217
+ envCommand
218
+ .command('set <key> <value>')
219
+ .description('Set an environment variable (non-interactive) (设置环境变量,非交互式)')
220
+ .option('-l, --level <level>', 'Configuration level: project or user (default: user) (配置级别: project 或 user,默认: user)')
221
+ .action(setEnv);
222
+
223
+ envCommand
224
+ .command('remove')
225
+ .alias('rm')
226
+ .description('Remove an environment variable interactively (交互式删除环境变量)')
227
+ .action(removeEnv);
228
+
229
+ envCommand
230
+ .command('unset <key>')
231
+ .description('Remove an environment variable by key (non-interactive) (通过键名删除环境变量,非交互式)')
232
+ .option('-l, --level <level>', 'Configuration level: project or user (default: user) (配置级别: project 或 user,默认: user)')
233
+ .action(unsetEnv);
234
+
235
+ envCommand
236
+ .command('show <key>')
237
+ .description('Show an environment variable value (显示环境变量值)')
238
+ .option('-l, --level <level>', 'Configuration level: project or user (if not specified, searches both) (配置级别: project 或 user,未指定则搜索两者)')
239
+ .action(showEnv);
240
+
241
+ envCommand
242
+ .command('clear')
243
+ .description('Clear all environment variables at a level (清空某个级别的所有环境变量)')
244
+ .option('-l, --level <level>', 'Configuration level: project or user (default: user) (配置级别: project 或 user,默认: user)')
245
+ .action(clearEnv);
246
+
247
+ envCommand
248
+ .command('edit')
249
+ .description('Edit environment variables interactively (交互式编辑环境变量)')
250
+ .option('-l, --level <level>', 'Configuration level: project or user (default: user) (配置级别: project 或 user,默认: user)')
251
+ .action(editEnv);
252
+
192
253
  // Help command
193
254
  program
194
255
  .command('help')
@@ -211,6 +272,7 @@ program
211
272
  console.log(' ui Start web-based account manager UI (启动基于 Web 的账号管理界面)');
212
273
  console.log(' model Manage model groups (管理模型组)');
213
274
  console.log(' mcp Manage MCP servers (管理 MCP 服务器)');
275
+ console.log(' env Manage Claude environment variables (管理 Claude 环境变量)');
214
276
  console.log(' help Display this help message (显示此帮助信息)');
215
277
  console.log(' version Show version number (显示版本号)\n');
216
278