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.
@@ -0,0 +1,266 @@
1
+ /**
2
+ * Global Configuration Manager
3
+ * Handles global configuration operations (accounts, MCP servers in home directory)
4
+ */
5
+ const fs = require('fs');
6
+ const path = require('path');
7
+ const os = require('os');
8
+ const { CONFIG_FILES } = require('../constants');
9
+
10
+ class GlobalConfigManager {
11
+ constructor() {
12
+ this.globalConfigDir = path.join(os.homedir(), CONFIG_FILES.GLOBAL_DIR);
13
+ this.globalConfigFile = path.join(this.globalConfigDir, CONFIG_FILES.GLOBAL_CONFIG);
14
+ this.projectConfigFilename = '.ais-project-config';
15
+ this.ensureConfigExists();
16
+ }
17
+
18
+ /**
19
+ * Ensure configuration directories and files exist
20
+ */
21
+ ensureConfigExists() {
22
+ if (!fs.existsSync(this.globalConfigDir)) {
23
+ fs.mkdirSync(this.globalConfigDir, { recursive: true });
24
+ }
25
+
26
+ if (!fs.existsSync(this.globalConfigFile)) {
27
+ this.save({ accounts: {}, mcpServers: {}, nextAccountId: 1 });
28
+ }
29
+
30
+ this.migrateAccountIds();
31
+ }
32
+
33
+ /**
34
+ * Find project root by searching upwards for .ais-project-config file
35
+ */
36
+ findProjectRoot(startDir = process.cwd()) {
37
+ let currentDir = path.resolve(startDir);
38
+ const rootDir = path.parse(currentDir).root;
39
+
40
+ while (currentDir !== rootDir) {
41
+ const configPath = path.join(currentDir, this.projectConfigFilename);
42
+ if (fs.existsSync(configPath)) {
43
+ return currentDir;
44
+ }
45
+ currentDir = path.dirname(currentDir);
46
+ }
47
+
48
+ const configPath = path.join(rootDir, this.projectConfigFilename);
49
+ if (fs.existsSync(configPath)) {
50
+ return rootDir;
51
+ }
52
+
53
+ return null;
54
+ }
55
+
56
+ /**
57
+ * Read global configuration
58
+ */
59
+ read() {
60
+ try {
61
+ const data = fs.readFileSync(this.globalConfigFile, 'utf8');
62
+ const config = JSON.parse(data);
63
+ if (!config.nextAccountId) {
64
+ config.nextAccountId = 1;
65
+ }
66
+ return config;
67
+ } catch (error) {
68
+ return { accounts: {}, mcpServers: {}, nextAccountId: 1 };
69
+ }
70
+ }
71
+
72
+ /**
73
+ * Save global configuration
74
+ */
75
+ save(config) {
76
+ fs.writeFileSync(this.globalConfigFile, JSON.stringify(config, null, 2), 'utf8');
77
+ }
78
+
79
+ /**
80
+ * Migrate existing accounts to have IDs
81
+ */
82
+ migrateAccountIds() {
83
+ const config = this.read();
84
+ let needsSave = false;
85
+
86
+ if (!config.nextAccountId) {
87
+ config.nextAccountId = 1;
88
+ needsSave = true;
89
+ }
90
+
91
+ Object.keys(config.accounts || {}).forEach(name => {
92
+ if (!config.accounts[name].id) {
93
+ config.accounts[name].id = config.nextAccountId;
94
+ config.nextAccountId++;
95
+ needsSave = true;
96
+ }
97
+ });
98
+
99
+ if (needsSave) {
100
+ this.save(config);
101
+ }
102
+ }
103
+
104
+ /**
105
+ * Get account by ID or name
106
+ */
107
+ getAccountByIdOrName(idOrName) {
108
+ const accounts = this.getAllAccounts();
109
+
110
+ const id = parseInt(idOrName, 10);
111
+ if (!isNaN(id)) {
112
+ for (const [name, account] of Object.entries(accounts)) {
113
+ if (account.id === id) {
114
+ return { name, ...account };
115
+ }
116
+ }
117
+ }
118
+
119
+ const account = accounts[idOrName];
120
+ if (account) {
121
+ return { name: idOrName, ...account };
122
+ }
123
+
124
+ return null;
125
+ }
126
+
127
+ /**
128
+ * Add or update an account
129
+ */
130
+ addAccount(name, accountData) {
131
+ const config = this.read();
132
+
133
+ const isNewAccount = !config.accounts[name];
134
+ const accountId = isNewAccount ? config.nextAccountId : config.accounts[name].id;
135
+
136
+ config.accounts[name] = {
137
+ ...accountData,
138
+ id: accountId,
139
+ createdAt: config.accounts[name]?.createdAt || new Date().toISOString(),
140
+ updatedAt: new Date().toISOString()
141
+ };
142
+
143
+ if (isNewAccount) {
144
+ config.nextAccountId++;
145
+ }
146
+
147
+ this.save(config);
148
+ return true;
149
+ }
150
+
151
+ /**
152
+ * Get all accounts
153
+ */
154
+ getAllAccounts() {
155
+ const config = this.read();
156
+ return config.accounts || {};
157
+ }
158
+
159
+ /**
160
+ * Get a specific account
161
+ */
162
+ getAccount(name) {
163
+ const accounts = this.getAllAccounts();
164
+ return accounts[name] || null;
165
+ }
166
+
167
+ /**
168
+ * Remove an account
169
+ */
170
+ removeAccount(name) {
171
+ const config = this.read();
172
+ if (config.accounts[name]) {
173
+ delete config.accounts[name];
174
+ this.save(config);
175
+ return true;
176
+ }
177
+ return false;
178
+ }
179
+
180
+ /**
181
+ * Check if an account exists
182
+ */
183
+ accountExists(name) {
184
+ const accounts = this.getAllAccounts();
185
+ return !!accounts[name];
186
+ }
187
+
188
+ // MCP Server methods
189
+
190
+ /**
191
+ * Add or update an MCP server
192
+ */
193
+ addMcpServer(name, serverData) {
194
+ const config = this.read();
195
+ if (!config.mcpServers) config.mcpServers = {};
196
+
197
+ const { DEFAULT_MCP_SCOPE } = require('../constants');
198
+ if (!serverData.scope) {
199
+ serverData.scope = DEFAULT_MCP_SCOPE;
200
+ }
201
+
202
+ config.mcpServers[name] = {
203
+ ...serverData,
204
+ createdAt: config.mcpServers[name]?.createdAt || new Date().toISOString(),
205
+ updatedAt: new Date().toISOString()
206
+ };
207
+ this.save(config);
208
+ return true;
209
+ }
210
+
211
+ /**
212
+ * Get all MCP servers
213
+ */
214
+ getAllMcpServers() {
215
+ const config = this.read();
216
+ return config.mcpServers || {};
217
+ }
218
+
219
+ /**
220
+ * Get a specific MCP server
221
+ */
222
+ getMcpServer(name) {
223
+ const servers = this.getAllMcpServers();
224
+ return servers[name] || null;
225
+ }
226
+
227
+ /**
228
+ * Update an MCP server
229
+ */
230
+ updateMcpServer(name, serverData) {
231
+ const config = this.read();
232
+ if (!config.mcpServers || !config.mcpServers[name]) return false;
233
+ config.mcpServers[name] = {
234
+ ...serverData,
235
+ createdAt: config.mcpServers[name].createdAt,
236
+ updatedAt: new Date().toISOString()
237
+ };
238
+ this.save(config);
239
+ return true;
240
+ }
241
+
242
+ /**
243
+ * Remove an MCP server
244
+ */
245
+ removeMcpServer(name) {
246
+ const config = this.read();
247
+ if (config.mcpServers && config.mcpServers[name]) {
248
+ delete config.mcpServers[name];
249
+ this.save(config);
250
+ return true;
251
+ }
252
+ return false;
253
+ }
254
+
255
+ /**
256
+ * Get configuration file paths
257
+ */
258
+ getConfigPaths() {
259
+ return {
260
+ global: this.globalConfigFile,
261
+ globalDir: this.globalConfigDir
262
+ };
263
+ }
264
+ }
265
+
266
+ module.exports = GlobalConfigManager;
@@ -0,0 +1,255 @@
1
+ /**
2
+ * Project Configuration Manager
3
+ * Handles project-specific configuration operations
4
+ */
5
+ const fs = require('fs');
6
+ const path = require('path');
7
+
8
+ class ProjectConfigManager {
9
+ constructor(globalConfig) {
10
+ this.globalConfig = globalConfig;
11
+ this.projectConfigFilename = '.ais-project-config';
12
+ }
13
+
14
+ /**
15
+ * Get project configuration file path
16
+ */
17
+ getProjectConfigPath(projectRoot = process.cwd()) {
18
+ return path.join(projectRoot, this.projectConfigFilename);
19
+ }
20
+
21
+ /**
22
+ * Read project configuration
23
+ */
24
+ read(projectRoot = process.cwd()) {
25
+ const projectConfigFile = this.getProjectConfigPath(projectRoot);
26
+ if (!fs.existsSync(projectConfigFile)) {
27
+ return null;
28
+ }
29
+
30
+ try {
31
+ const data = fs.readFileSync(projectConfigFile, 'utf8');
32
+ return JSON.parse(data);
33
+ } catch (error) {
34
+ return null;
35
+ }
36
+ }
37
+
38
+ /**
39
+ * Save project configuration
40
+ */
41
+ save(projectConfig, projectRoot = process.cwd()) {
42
+ const projectConfigFile = this.getProjectConfigPath(projectRoot);
43
+ fs.writeFileSync(projectConfigFile, JSON.stringify(projectConfig, null, 2), 'utf8');
44
+ }
45
+
46
+ /**
47
+ * Set current project's active account
48
+ */
49
+ setAccount(accountName, projectRoot = process.cwd()) {
50
+ const projectConfigFile = this.getProjectConfigPath(projectRoot);
51
+
52
+ // Read existing project config to preserve enabledMcpServers
53
+ let existingConfig = {};
54
+ if (fs.existsSync(projectConfigFile)) {
55
+ try {
56
+ const data = fs.readFileSync(projectConfigFile, 'utf8');
57
+ existingConfig = JSON.parse(data);
58
+ } catch (error) {
59
+ // If parsing fails, start fresh
60
+ }
61
+ }
62
+
63
+ const projectConfig = {
64
+ activeAccount: accountName,
65
+ projectPath: projectRoot,
66
+ setAt: new Date().toISOString(),
67
+ enabledMcpServers: existingConfig.enabledMcpServers || []
68
+ };
69
+
70
+ this.save(projectConfig, projectRoot);
71
+ return true;
72
+ }
73
+
74
+ /**
75
+ * Get current project's active account with full details
76
+ */
77
+ getAccount(projectRoot = process.cwd()) {
78
+ try {
79
+ const projectConfig = this.read(projectRoot);
80
+ if (!projectConfig) {
81
+ return null;
82
+ }
83
+
84
+ // Get the full account details from global config
85
+ const account = this.globalConfig.getAccount(projectConfig.activeAccount);
86
+ if (account) {
87
+ return {
88
+ name: projectConfig.activeAccount,
89
+ ...account,
90
+ setAt: projectConfig.setAt,
91
+ projectRoot: projectRoot
92
+ };
93
+ }
94
+ return null;
95
+ } catch (error) {
96
+ return null;
97
+ }
98
+ }
99
+
100
+ // MCP Server methods for project
101
+
102
+ /**
103
+ * Get project's enabled MCP servers
104
+ */
105
+ getMcpServers(projectRoot = process.cwd()) {
106
+ try {
107
+ const projectConfig = this.read(projectRoot);
108
+ if (!projectConfig) {
109
+ return [];
110
+ }
111
+ return projectConfig.enabledMcpServers || [];
112
+ } catch (error) {
113
+ return [];
114
+ }
115
+ }
116
+
117
+ /**
118
+ * Check if MCP server is enabled in current project
119
+ */
120
+ isMcpServerEnabled(serverName, projectRoot = process.cwd()) {
121
+ try {
122
+ const projectConfig = this.read(projectRoot);
123
+ if (!projectConfig) {
124
+ return false;
125
+ }
126
+
127
+ return projectConfig.enabledMcpServers &&
128
+ projectConfig.enabledMcpServers.includes(serverName);
129
+ } catch (error) {
130
+ return false;
131
+ }
132
+ }
133
+
134
+ /**
135
+ * Enable MCP server for current project with scope
136
+ */
137
+ enableMcpServer(serverName, scope, projectRoot = process.cwd()) {
138
+ const projectConfigFile = this.getProjectConfigPath(projectRoot);
139
+ if (!fs.existsSync(projectConfigFile)) {
140
+ throw new Error('Project not configured. Run "ais use" first.');
141
+ }
142
+
143
+ const data = fs.readFileSync(projectConfigFile, 'utf8');
144
+ const projectConfig = JSON.parse(data);
145
+
146
+ // Update server scope in global config
147
+ const globalConfig = this.globalConfig.read();
148
+ if (globalConfig.mcpServers[serverName]) {
149
+ globalConfig.mcpServers[serverName].scope = scope;
150
+ this.globalConfig.save(globalConfig);
151
+ }
152
+
153
+ // Handle different scopes
154
+ const { MCP_SCOPES } = require('../constants');
155
+ if (scope === MCP_SCOPES.LOCAL) {
156
+ if (!projectConfig.enabledMcpServers) projectConfig.enabledMcpServers = [];
157
+ if (!projectConfig.enabledMcpServers.includes(serverName)) {
158
+ projectConfig.enabledMcpServers.push(serverName);
159
+ }
160
+ } else if (scope === MCP_SCOPES.PROJECT) {
161
+ if (!projectConfig.projectMcpServers) projectConfig.projectMcpServers = {};
162
+ const server = this.globalConfig.getMcpServer(serverName);
163
+ projectConfig.projectMcpServers[serverName] = {
164
+ ...server,
165
+ scope: MCP_SCOPES.PROJECT
166
+ };
167
+
168
+ if (!projectConfig.enabledMcpServers) projectConfig.enabledMcpServers = [];
169
+ if (!projectConfig.enabledMcpServers.includes(serverName)) {
170
+ projectConfig.enabledMcpServers.push(serverName);
171
+ }
172
+ } else if (scope === MCP_SCOPES.USER) {
173
+ if (!projectConfig.enabledMcpServers) projectConfig.enabledMcpServers = [];
174
+ if (!projectConfig.enabledMcpServers.includes(serverName)) {
175
+ projectConfig.enabledMcpServers.push(serverName);
176
+ }
177
+ }
178
+
179
+ this.save(projectConfig, projectRoot);
180
+ return true;
181
+ }
182
+
183
+ /**
184
+ * Disable MCP server for current project
185
+ */
186
+ disableMcpServer(serverName, projectRoot = process.cwd()) {
187
+ const projectConfig = this.read(projectRoot);
188
+ if (!projectConfig) {
189
+ throw new Error('Project not configured. Run "ais use" first.');
190
+ }
191
+
192
+ if (!projectConfig.enabledMcpServers) return false;
193
+
194
+ const index = projectConfig.enabledMcpServers.indexOf(serverName);
195
+ if (index > -1) {
196
+ projectConfig.enabledMcpServers.splice(index, 1);
197
+ this.save(projectConfig, projectRoot);
198
+ return true;
199
+ }
200
+ return false;
201
+ }
202
+
203
+ /**
204
+ * Remove MCP server from current project's enabled list
205
+ */
206
+ removeMcpServer(serverName, projectRoot = process.cwd()) {
207
+ try {
208
+ const projectConfig = this.read(projectRoot);
209
+ if (!projectConfig) {
210
+ return false;
211
+ }
212
+
213
+ if (!projectConfig.enabledMcpServers) return false;
214
+
215
+ const index = projectConfig.enabledMcpServers.indexOf(serverName);
216
+ if (index > -1) {
217
+ projectConfig.enabledMcpServers.splice(index, 1);
218
+ this.save(projectConfig, projectRoot);
219
+ return true;
220
+ }
221
+ return false;
222
+ } catch (error) {
223
+ return false;
224
+ }
225
+ }
226
+
227
+ /**
228
+ * Get all available MCP servers including project-scoped ones
229
+ */
230
+ getAllMcpServers(projectRoot = process.cwd()) {
231
+ const globalServers = this.globalConfig.getAllMcpServers();
232
+
233
+ try {
234
+ const projectConfig = this.read(projectRoot);
235
+ if (!projectConfig) {
236
+ return globalServers;
237
+ }
238
+
239
+ // Merge global and project servers
240
+ const allServers = { ...globalServers };
241
+
242
+ if (projectConfig.projectMcpServers) {
243
+ Object.entries(projectConfig.projectMcpServers).forEach(([name, server]) => {
244
+ allServers[name] = server;
245
+ });
246
+ }
247
+
248
+ return allServers;
249
+ } catch (error) {
250
+ return globalServers;
251
+ }
252
+ }
253
+ }
254
+
255
+ module.exports = ProjectConfigManager;