fraim 2.0.271 ā 2.0.272
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/dist/src/cli/commands/add-ide.js +10 -131
- package/dist/src/cli/commands/add-provider.js +32 -268
- package/dist/src/cli/commands/login.js +5 -5
- package/dist/src/cli/commands/setup.js +15 -52
- package/dist/src/cli/commands/sync.js +111 -80
- package/dist/src/cli/fraim.js +1 -44
- package/dist/src/cli/mcp/ide-formats.js +1 -1
- package/dist/src/cli/mcp/mcp-server-registry.js +3 -3
- package/dist/src/cli/providers/local-provider-registry.js +4 -4
- package/dist/src/cli/setup/auto-mcp-setup.js +4 -13
- package/dist/src/cli/utils/remote-sync.js +41 -25
- package/dist/src/first-run/types.js +1 -1
- package/dist/src/local-mcp-server/learning-usage-command.js +408 -0
- package/dist/src/local-mcp-server/learning-usage-store.js +2 -2
- package/dist/src/services/provider-service.js +4 -4
- package/package.json +1 -1
|
@@ -36,7 +36,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
36
36
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
37
|
};
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
-
exports.
|
|
39
|
+
exports.loadGlobalConfig = exports.addIDECommand = exports.runAddIDE = void 0;
|
|
40
40
|
const commander_1 = require("commander");
|
|
41
41
|
const chalk_1 = __importDefault(require("chalk"));
|
|
42
42
|
const prompts_1 = __importDefault(require("prompts"));
|
|
@@ -48,9 +48,6 @@ const mcp_config_generator_1 = require("../setup/mcp-config-generator");
|
|
|
48
48
|
const claude_code_telemetry_1 = require("../setup/claude-code-telemetry");
|
|
49
49
|
const script_sync_utils_1 = require("../utils/script-sync-utils");
|
|
50
50
|
const mcp_server_registry_1 = require("../mcp/mcp-server-registry");
|
|
51
|
-
const get_provider_client_1 = require("../api/get-provider-client");
|
|
52
|
-
const provider_prompts_1 = require("../setup/provider-prompts");
|
|
53
|
-
const provider_registry_1 = require("../providers/provider-registry");
|
|
54
51
|
const user_config_1 = require("../utils/user-config");
|
|
55
52
|
const resolveGlobalConfigPath = () => {
|
|
56
53
|
const primary = path_1.default.join((0, script_sync_utils_1.getUserFraimDir)(), 'config.json');
|
|
@@ -84,41 +81,9 @@ const loadGlobalConfig = async () => {
|
|
|
84
81
|
const config = JSON.parse(fs_1.default.readFileSync(globalConfigPath, 'utf8'));
|
|
85
82
|
if (!config.apiKey)
|
|
86
83
|
return null;
|
|
87
|
-
// Support both old and new token format
|
|
88
|
-
const tokens = config.tokens || {};
|
|
89
|
-
// Backward compatibility: map old format to new
|
|
90
|
-
// Only try to fetch provider IDs if we have a valid FRAIM key
|
|
91
|
-
if (config.apiKey) {
|
|
92
|
-
try {
|
|
93
|
-
const client = (0, get_provider_client_1.getProviderClient)();
|
|
94
|
-
const providerIds = await client.getAllProviderIds();
|
|
95
|
-
for (const id of providerIds) {
|
|
96
|
-
const oldKey = `${id}Token`;
|
|
97
|
-
if (config[oldKey] && !tokens[id]) {
|
|
98
|
-
tokens[id] = config[oldKey];
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
catch (e) {
|
|
103
|
-
// If provider client fails (network error, invalid key, etc.),
|
|
104
|
-
// skip backward compatibility mapping and use local fallback
|
|
105
|
-
// This is fine - the config will still work with the new format
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
// Load all provider configs
|
|
109
|
-
const providerConfigs = {};
|
|
110
|
-
// New format: providerConfigs object
|
|
111
|
-
if (config.providerConfigs) {
|
|
112
|
-
Object.entries(config.providerConfigs).forEach(([key, value]) => {
|
|
113
|
-
const providerId = key.replace('Config', '');
|
|
114
|
-
providerConfigs[providerId] = value;
|
|
115
|
-
});
|
|
116
|
-
}
|
|
117
84
|
return {
|
|
118
85
|
fraimKey: config.apiKey,
|
|
119
|
-
tokens,
|
|
120
86
|
mode: config.mode,
|
|
121
|
-
providerConfigs
|
|
122
87
|
};
|
|
123
88
|
}
|
|
124
89
|
catch (e) {
|
|
@@ -126,65 +91,7 @@ const loadGlobalConfig = async () => {
|
|
|
126
91
|
}
|
|
127
92
|
};
|
|
128
93
|
exports.loadGlobalConfig = loadGlobalConfig;
|
|
129
|
-
const
|
|
130
|
-
const client = (0, get_provider_client_1.getProviderClient)();
|
|
131
|
-
const provider = await client.getProvider(providerId);
|
|
132
|
-
if (!provider)
|
|
133
|
-
return '';
|
|
134
|
-
if (isOptional) {
|
|
135
|
-
console.log(chalk_1.default.yellow(`\nš ${provider.displayName} token (optional for conversational mode)`));
|
|
136
|
-
console.log(chalk_1.default.gray(`${provider.displayName} token enables ${provider.displayName}-specific MCP features.\n`));
|
|
137
|
-
const wantsToken = await (0, prompts_1.default)({
|
|
138
|
-
type: 'confirm',
|
|
139
|
-
name: 'addToken',
|
|
140
|
-
message: `Do you want to add a ${provider.displayName} token?`,
|
|
141
|
-
initial: false
|
|
142
|
-
});
|
|
143
|
-
if (!wantsToken.addToken) {
|
|
144
|
-
console.log(chalk_1.default.blue(`ā¹ļø Skipping ${provider.displayName} token - ${provider.displayName} MCP server will not be configured`));
|
|
145
|
-
return '';
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
else {
|
|
149
|
-
console.log(chalk_1.default.yellow(`\nš ${provider.displayName} token needed for MCP configuration`));
|
|
150
|
-
console.log(chalk_1.default.gray(`This is required for ${provider.displayName} MCP servers to function properly.\n`));
|
|
151
|
-
}
|
|
152
|
-
try {
|
|
153
|
-
return await (0, provider_prompts_1.promptForProviderToken)(client, providerId);
|
|
154
|
-
}
|
|
155
|
-
catch (error) {
|
|
156
|
-
if (isOptional) {
|
|
157
|
-
console.log(chalk_1.default.blue(`ā¹ļø No ${provider.displayName} token provided - ${provider.displayName} MCP server will not be configured`));
|
|
158
|
-
return '';
|
|
159
|
-
}
|
|
160
|
-
console.log(chalk_1.default.red(`${provider.displayName} token is required. Exiting.`));
|
|
161
|
-
process.exit(1);
|
|
162
|
-
}
|
|
163
|
-
};
|
|
164
|
-
const saveProviderTokenToConfig = async (providerId, token) => {
|
|
165
|
-
const globalConfigPath = resolveGlobalConfigPath();
|
|
166
|
-
if (fs_1.default.existsSync(globalConfigPath)) {
|
|
167
|
-
try {
|
|
168
|
-
const config = JSON.parse(fs_1.default.readFileSync(globalConfigPath, 'utf8'));
|
|
169
|
-
// Use new tokens structure
|
|
170
|
-
if (!config.tokens) {
|
|
171
|
-
config.tokens = {};
|
|
172
|
-
}
|
|
173
|
-
config.tokens[providerId] = token;
|
|
174
|
-
fs_1.default.writeFileSync(globalConfigPath, JSON.stringify(config, null, 2));
|
|
175
|
-
const { getProvider } = await Promise.resolve().then(() => __importStar(require('../providers/provider-registry')));
|
|
176
|
-
const provider = await getProvider(providerId);
|
|
177
|
-
console.log(chalk_1.default.green(`ā
${provider?.displayName || providerId} token saved to global config`));
|
|
178
|
-
}
|
|
179
|
-
catch (e) {
|
|
180
|
-
const { getProvider } = await Promise.resolve().then(() => __importStar(require('../providers/provider-registry')));
|
|
181
|
-
const provider = await getProvider(providerId);
|
|
182
|
-
console.log(chalk_1.default.yellow(`ā ļø Could not save ${provider?.displayName || providerId} token to config`));
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
};
|
|
186
|
-
exports.saveProviderTokenToConfig = saveProviderTokenToConfig;
|
|
187
|
-
const configureIDEMCP = async (ide, fraimKey, tokens, providerConfigs) => {
|
|
94
|
+
const configureIDEMCP = async (ide, fraimKey) => {
|
|
188
95
|
const configPath = (0, ide_detector_1.expandPath)(ide.configPath);
|
|
189
96
|
console.log(chalk_1.default.blue(`š§ Configuring ${ide.name}...`));
|
|
190
97
|
// Create backup if config exists
|
|
@@ -219,13 +126,10 @@ const configureIDEMCP = async (ide, fraimKey, tokens, providerConfigs) => {
|
|
|
219
126
|
existingTomlContent = fs_1.default.readFileSync(configPath, 'utf8');
|
|
220
127
|
console.log(chalk_1.default.gray(` š Found existing TOML config`));
|
|
221
128
|
}
|
|
222
|
-
const newTomlContent = await (0, mcp_config_generator_1.generateMCPConfig)(ide.configType, fraimKey,
|
|
129
|
+
const newTomlContent = await (0, mcp_config_generator_1.generateMCPConfig)(ide.configType, fraimKey, {});
|
|
223
130
|
const { getAllMCPServerIds } = await Promise.resolve().then(() => __importStar(require('../mcp/mcp-server-registry')));
|
|
224
131
|
const baseServerIds = getAllMCPServerIds();
|
|
225
|
-
|
|
226
|
-
const providerServerIds = Object.keys(tokens).filter(id => tokens[id]);
|
|
227
|
-
const serversToAdd = [...baseServerIds, ...providerServerIds];
|
|
228
|
-
const mergeResult = (0, mcp_config_generator_1.mergeTomlMCPServers)(existingTomlContent, newTomlContent, serversToAdd);
|
|
132
|
+
const mergeResult = (0, mcp_config_generator_1.mergeTomlMCPServers)(existingTomlContent, newTomlContent, baseServerIds);
|
|
229
133
|
fs_1.default.writeFileSync(configPath, mergeResult.content);
|
|
230
134
|
mergeResult.addedServers.forEach(server => {
|
|
231
135
|
console.log(chalk_1.default.green(` ā
Added ${server} MCP server`));
|
|
@@ -239,14 +143,14 @@ const configureIDEMCP = async (ide, fraimKey, tokens, providerConfigs) => {
|
|
|
239
143
|
}
|
|
240
144
|
else {
|
|
241
145
|
// For JSON configs - intelligent merging
|
|
242
|
-
const newConfig = await (0, mcp_config_generator_1.generateMCPConfig)(ide.configType, fraimKey,
|
|
146
|
+
const newConfig = await (0, mcp_config_generator_1.generateMCPConfig)(ide.configType, fraimKey, {});
|
|
243
147
|
const newMCPServers = newConfig[serversKey] || newConfig.mcpServers || {};
|
|
244
148
|
// Merge MCP servers intelligently
|
|
245
149
|
const mergedMCPServers = { ...existingMCPServers };
|
|
246
150
|
const addedServers = [];
|
|
247
151
|
const updatedServers = [];
|
|
248
152
|
const skippedServers = [];
|
|
249
|
-
const alwaysUpdateServers = new Set(['fraim'
|
|
153
|
+
const alwaysUpdateServers = new Set(['fraim']);
|
|
250
154
|
for (const [serverName, serverConfig] of Object.entries(newMCPServers)) {
|
|
251
155
|
if (!existingMCPServers[serverName]) {
|
|
252
156
|
mergedMCPServers[serverName] = serverConfig;
|
|
@@ -302,7 +206,7 @@ const listSupportedIDEs = () => {
|
|
|
302
206
|
console.log(chalk_1.default.yellow(' Gemini aliases: gemini, gemini-cli, gemini cli'));
|
|
303
207
|
console.log(chalk_1.default.yellow(' GitHub Copilot CLI aliases: copilot, copilot-cli, github copilot cli'));
|
|
304
208
|
};
|
|
305
|
-
const promptForIDESelection = async (availableIDEs
|
|
209
|
+
const promptForIDESelection = async (availableIDEs) => {
|
|
306
210
|
console.log(chalk_1.default.green(`ā
Found ${availableIDEs.length} IDEs that can be configured:\n`));
|
|
307
211
|
availableIDEs.forEach((ide, index) => {
|
|
308
212
|
const configExists = fs_1.default.existsSync((0, ide_detector_1.expandPath)(ide.configPath));
|
|
@@ -315,14 +219,6 @@ const promptForIDESelection = async (availableIDEs, tokens) => {
|
|
|
315
219
|
for (const server of mcp_server_registry_1.BASE_MCP_SERVERS) {
|
|
316
220
|
console.log(chalk_1.default.gray(` ⢠${server.id} (${server.description})`));
|
|
317
221
|
}
|
|
318
|
-
// Show provider servers (only if tokens exist)
|
|
319
|
-
const allProviders = await (0, provider_registry_1.getAllProviders)();
|
|
320
|
-
for (const provider of allProviders) {
|
|
321
|
-
const hasToken = tokens?.[provider.id];
|
|
322
|
-
if (hasToken && provider.mcpServer) {
|
|
323
|
-
console.log(chalk_1.default.gray(` - ${provider.id} (${provider.description})`));
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
222
|
const response = await (0, prompts_1.default)({
|
|
327
223
|
type: 'text',
|
|
328
224
|
name: 'selection',
|
|
@@ -360,25 +256,8 @@ const runAddIDE = async (options) => {
|
|
|
360
256
|
console.log(chalk_1.default.yellow('š” Please run "fraim setup" first to configure your FRAIM keys.'));
|
|
361
257
|
process.exit(1);
|
|
362
258
|
}
|
|
363
|
-
const platformTokens = globalConfig.tokens || {};
|
|
364
259
|
const isConversationalMode = globalConfig.mode === 'conversational';
|
|
365
|
-
|
|
366
|
-
const allProviderIds = await (0, provider_registry_1.getAllProviderIds)();
|
|
367
|
-
const hasAnyToken = allProviderIds.some(id => platformTokens[id]);
|
|
368
|
-
if (!hasAnyToken && !isConversationalMode && !options.skipTokenPrompts) {
|
|
369
|
-
console.log(chalk_1.default.yellow('ā ļø No provider tokens found in configuration.'));
|
|
370
|
-
// Prompt for first integrated provider as default
|
|
371
|
-
const integratedProviders = await (0, provider_registry_1.getProvidersWithCapability)('integrated');
|
|
372
|
-
const defaultProviderId = integratedProviders[0]?.id;
|
|
373
|
-
if (defaultProviderId) {
|
|
374
|
-
const token = await promptForProviderTokenIfNeeded(defaultProviderId, false);
|
|
375
|
-
if (token) {
|
|
376
|
-
await saveProviderTokenToConfig(defaultProviderId, token);
|
|
377
|
-
platformTokens[defaultProviderId] = token;
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
if (isConversationalMode && !hasAnyToken) {
|
|
260
|
+
if (isConversationalMode) {
|
|
382
261
|
console.log(chalk_1.default.blue('ā¹ļø Conversational mode: Configuring MCP without platform integration\n'));
|
|
383
262
|
}
|
|
384
263
|
else {
|
|
@@ -415,7 +294,7 @@ const runAddIDE = async (options) => {
|
|
|
415
294
|
}
|
|
416
295
|
else {
|
|
417
296
|
// Interactive selection
|
|
418
|
-
idesToConfigure = await promptForIDESelection(detectedIDEs
|
|
297
|
+
idesToConfigure = await promptForIDESelection(detectedIDEs);
|
|
419
298
|
}
|
|
420
299
|
}
|
|
421
300
|
if (idesToConfigure.length === 0) {
|
|
@@ -429,7 +308,7 @@ const runAddIDE = async (options) => {
|
|
|
429
308
|
};
|
|
430
309
|
for (const ide of idesToConfigure) {
|
|
431
310
|
try {
|
|
432
|
-
await configureIDEMCP(ide, globalConfig.fraimKey
|
|
311
|
+
await configureIDEMCP(ide, globalConfig.fraimKey);
|
|
433
312
|
results.successful.push(ide.name);
|
|
434
313
|
(0, user_config_1.addInstalledIde)((0, ide_detector_1.getAdapterConfigType)(ide));
|
|
435
314
|
}
|
|
@@ -1,275 +1,39 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.addProviderCommand = exports.runAddProvider = void 0;
|
|
3
|
+
exports.addProviderCommand = exports.runAddProvider = exports.explainAgentManagedProviderSetup = void 0;
|
|
7
4
|
const commander_1 = require("commander");
|
|
8
|
-
const
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
const
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
fs_1.default.writeFileSync(globalConfigPath, JSON.stringify(config, null, 2));
|
|
35
|
-
};
|
|
36
|
-
const forEachInstalledIDE = async (callback) => {
|
|
37
|
-
const detectedIDEs = (0, ide_detector_1.detectInstalledIDEs)();
|
|
38
|
-
if (detectedIDEs.length === 0) {
|
|
39
|
-
console.log(chalk_1.default.yellow('ā ļø No IDEs detected. MCP configs not updated.'));
|
|
40
|
-
return;
|
|
41
|
-
}
|
|
42
|
-
console.log(chalk_1.default.blue(`\nš§ Updating ${detectedIDEs.length} IDE configuration(s)...\n`));
|
|
43
|
-
for (const ide of detectedIDEs) {
|
|
44
|
-
try {
|
|
45
|
-
await callback(ide, (0, ide_detector_1.expandPath)(ide.configPath));
|
|
46
|
-
}
|
|
47
|
-
catch (error) {
|
|
48
|
-
console.log(chalk_1.default.red(`ā Failed to update ${ide.name}: ${error instanceof Error ? error.message : 'Unknown error'}`));
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
};
|
|
52
|
-
const writeOAuthProviderToIDEConfigs = async (provider, mcpUrl) => {
|
|
53
|
-
const urlOnlyEntry = { type: 'http', url: mcpUrl };
|
|
54
|
-
await forEachInstalledIDE(async (ide, configPath) => {
|
|
55
|
-
if (provider === 'github' && ide.configType === 'copilot-cli') {
|
|
56
|
-
console.log(chalk_1.default.gray(`Skipped ${ide.name} GitHub MCP entry (built into Copilot CLI)`));
|
|
57
|
-
return;
|
|
58
|
-
}
|
|
59
|
-
if (ide.configFormat === 'json') {
|
|
60
|
-
let ideConfig = {};
|
|
61
|
-
if (fs_1.default.existsSync(configPath)) {
|
|
62
|
-
ideConfig = JSON.parse(fs_1.default.readFileSync(configPath, 'utf8'));
|
|
63
|
-
}
|
|
64
|
-
const serversKey = ide.configType === 'vscode' ? 'servers' : 'mcpServers';
|
|
65
|
-
if (!ideConfig[serversKey]) {
|
|
66
|
-
ideConfig[serversKey] = {};
|
|
67
|
-
}
|
|
68
|
-
ideConfig[serversKey][provider] = ide.configType === 'copilot-cli'
|
|
69
|
-
? { ...urlOnlyEntry, tools: ['*'] }
|
|
70
|
-
: urlOnlyEntry;
|
|
71
|
-
fs_1.default.writeFileSync(configPath, JSON.stringify(ideConfig, null, 2));
|
|
72
|
-
console.log(chalk_1.default.green(`ā
Updated ${ide.name} config`));
|
|
73
|
-
}
|
|
74
|
-
else if (ide.configFormat === 'toml') {
|
|
75
|
-
console.log(chalk_1.default.yellow(`ā ļø ${ide.name} uses TOML format ā update manually: add [tools.${provider}] url = "${mcpUrl}"`));
|
|
76
|
-
}
|
|
77
|
-
});
|
|
78
|
-
};
|
|
79
|
-
const updateIDEConfigs = async (provider, fraimKey, token, providerConfig) => {
|
|
80
|
-
const tokenConfig = { [provider]: token };
|
|
81
|
-
const providerConfigs = providerConfig ? { [provider]: providerConfig } : {};
|
|
82
|
-
await forEachInstalledIDE(async (ide, configPath) => {
|
|
83
|
-
if (ide.configFormat === 'json') {
|
|
84
|
-
let ideConfig = {};
|
|
85
|
-
if (fs_1.default.existsSync(configPath)) {
|
|
86
|
-
ideConfig = JSON.parse(fs_1.default.readFileSync(configPath, 'utf8'));
|
|
87
|
-
}
|
|
88
|
-
const mcpConfig = await (0, mcp_config_generator_1.generateMCPConfig)(ide.configType, fraimKey, tokenConfig, providerConfigs);
|
|
89
|
-
const serversKey = ide.configType === 'vscode' ? 'servers' : 'mcpServers';
|
|
90
|
-
if (!ideConfig[serversKey]) {
|
|
91
|
-
ideConfig[serversKey] = {};
|
|
92
|
-
}
|
|
93
|
-
const providerServerKey = provider === 'ado' ? 'ado' : provider;
|
|
94
|
-
if (mcpConfig[serversKey] && mcpConfig[serversKey][providerServerKey]) {
|
|
95
|
-
ideConfig[serversKey][providerServerKey] = mcpConfig[serversKey][providerServerKey];
|
|
96
|
-
fs_1.default.writeFileSync(configPath, JSON.stringify(ideConfig, null, 2));
|
|
97
|
-
console.log(chalk_1.default.green(`ā
Updated ${ide.name} config`));
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
else if (ide.configFormat === 'toml') {
|
|
101
|
-
const mcpConfigToml = await (0, mcp_config_generator_1.generateMCPConfig)(ide.configType, fraimKey, tokenConfig, providerConfigs);
|
|
102
|
-
if (fs_1.default.existsSync(configPath)) {
|
|
103
|
-
const existingContent = fs_1.default.readFileSync(configPath, 'utf8');
|
|
104
|
-
const serversToMerge = [provider === 'ado' ? 'ado' : provider];
|
|
105
|
-
const mergeResult = (0, mcp_config_generator_2.mergeTomlMCPServers)(existingContent, mcpConfigToml, serversToMerge);
|
|
106
|
-
fs_1.default.writeFileSync(configPath, mergeResult.content);
|
|
107
|
-
console.log(chalk_1.default.green(`ā
Updated ${ide.name} config`));
|
|
108
|
-
}
|
|
109
|
-
else {
|
|
110
|
-
console.log(chalk_1.default.yellow(`ā ļø ${ide.name} config not found, skipping`));
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
});
|
|
114
|
-
};
|
|
115
|
-
const offerModeSwitch = async (config) => {
|
|
116
|
-
if (config.mode === 'split') {
|
|
117
|
-
console.log(chalk_1.default.gray('\nAlready in split mode.'));
|
|
118
|
-
return;
|
|
119
|
-
}
|
|
120
|
-
console.log(chalk_1.default.blue('\nš Mode Switch Available'));
|
|
121
|
-
console.log(chalk_1.default.gray('You can now use split mode (separate platforms for code and issues).'));
|
|
122
|
-
console.log(chalk_1.default.gray('Example: GitHub for code, Jira for issue tracking\n'));
|
|
123
|
-
// Skip prompt if FRAIM_AUTO_SWITCH_MODE is set (for testing)
|
|
124
|
-
const shouldSwitch = process.env.FRAIM_AUTO_SWITCH_MODE === 'true';
|
|
125
|
-
if (!shouldSwitch && !process.env.FRAIM_AUTO_SWITCH_MODE) {
|
|
126
|
-
const response = await (0, prompts_1.default)({
|
|
127
|
-
type: 'confirm',
|
|
128
|
-
name: 'switchMode',
|
|
129
|
-
message: 'Switch to split mode?',
|
|
130
|
-
initial: false
|
|
131
|
-
});
|
|
132
|
-
if (response.switchMode) {
|
|
133
|
-
config.mode = 'split';
|
|
134
|
-
saveGlobalConfig(config);
|
|
135
|
-
console.log(chalk_1.default.green('ā
Switched to split mode'));
|
|
136
|
-
console.log(chalk_1.default.gray('Run "fraim init-project" in your projects to use split mode'));
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
else if (shouldSwitch) {
|
|
140
|
-
config.mode = 'split';
|
|
141
|
-
saveGlobalConfig(config);
|
|
142
|
-
console.log(chalk_1.default.green('ā
Switched to split mode'));
|
|
143
|
-
console.log(chalk_1.default.gray('Run "fraim init-project" in your projects to use split mode'));
|
|
144
|
-
}
|
|
145
|
-
};
|
|
146
|
-
const runAddProvider = async (provider, options) => {
|
|
147
|
-
console.log(chalk_1.default.blue(`\nš§ Adding ${provider.toUpperCase()} provider...\n`));
|
|
148
|
-
// Check global setup exists
|
|
149
|
-
const config = loadGlobalConfig();
|
|
150
|
-
if (!config) {
|
|
151
|
-
console.log(chalk_1.default.red('ā Global FRAIM setup not found.'));
|
|
152
|
-
console.log(chalk_1.default.yellow('Please run: fraim setup --key=<your-fraim-key>'));
|
|
153
|
-
process.exit(1);
|
|
154
|
-
}
|
|
155
|
-
// Initialize tokens object if it doesn't exist
|
|
156
|
-
if (!config.tokens) {
|
|
157
|
-
config.tokens = {};
|
|
158
|
-
}
|
|
159
|
-
// Check if provider already configured
|
|
160
|
-
const providerName = await (0, provider_registry_1.getProviderDisplayName)(provider);
|
|
161
|
-
const hasExistingConfig = config.tokens[provider] ||
|
|
162
|
-
(provider === 'jira' && config.providerConfigs?.jiraConfig?.baseUrl) ||
|
|
163
|
-
(provider === 'ado' && config.providerConfigs?.adoConfig?.organization);
|
|
164
|
-
if (hasExistingConfig) {
|
|
165
|
-
const displayInfo = provider === 'jira' && config.providerConfigs?.jiraConfig?.baseUrl
|
|
166
|
-
? ` (${config.providerConfigs.jiraConfig.baseUrl})`
|
|
167
|
-
: provider === 'ado' && config.providerConfigs?.adoConfig?.organization
|
|
168
|
-
? ` (${config.providerConfigs.adoConfig.organization})`
|
|
169
|
-
: '';
|
|
170
|
-
console.log(chalk_1.default.yellow(`ā ļø ${providerName} is already configured${displayInfo}`));
|
|
171
|
-
// Skip prompt if FRAIM_FORCE_OVERWRITE is set (for testing)
|
|
172
|
-
if (!process.env.FRAIM_FORCE_OVERWRITE) {
|
|
173
|
-
const response = await (0, prompts_1.default)({
|
|
174
|
-
type: 'confirm',
|
|
175
|
-
name: 'overwrite',
|
|
176
|
-
message: `Overwrite existing ${providerName} configuration?`,
|
|
177
|
-
initial: false
|
|
178
|
-
});
|
|
179
|
-
if (!response.overwrite) {
|
|
180
|
-
console.log(chalk_1.default.gray('Cancelled.'));
|
|
181
|
-
process.exit(0);
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
// Check prerequisites for the provider's MCP server command
|
|
186
|
-
const providerDefCheck = await (0, provider_registry_1.getProvider)(provider);
|
|
187
|
-
if (providerDefCheck?.mcpServer?.type === 'stdio' && providerDefCheck.mcpServer.command && !process.env.FRAIM_SKIP_PREREQ_CHECK) {
|
|
188
|
-
const cmd = providerDefCheck.mcpServer.command;
|
|
189
|
-
// Only check non-standard commands (npx/node are assumed to be available)
|
|
190
|
-
if (!['npx', 'node', 'npm'].includes(cmd)) {
|
|
191
|
-
try {
|
|
192
|
-
(0, child_process_1.execSync)(`${cmd} --version`, { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] });
|
|
193
|
-
}
|
|
194
|
-
catch {
|
|
195
|
-
const installInstructions = {
|
|
196
|
-
uvx: 'Install uv first: https://docs.astral.sh/uv/getting-started/installation/\n' +
|
|
197
|
-
' macOS/Linux: curl -LsSf https://astral.sh/uv/install.sh | sh\n' +
|
|
198
|
-
' Windows: powershell -c "irm https://astral.sh/uv/install.ps1 | iex"',
|
|
199
|
-
};
|
|
200
|
-
console.log(chalk_1.default.red(`\nā "${cmd}" is required for ${providerName} but was not found on your system.\n`));
|
|
201
|
-
console.log(chalk_1.default.yellow(installInstructions[cmd] || `Please install "${cmd}" and try again.`));
|
|
202
|
-
console.log(chalk_1.default.gray('\nAfter installing, restart your terminal and run this command again.'));
|
|
203
|
-
process.exit(1);
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
// Detect OAuth-first providers: HTTP MCP with no authHeaderTemplate means the IDE
|
|
208
|
-
// handles OAuth natively. Skip credential collection and write a URL-only IDE entry.
|
|
209
|
-
const providerDefOAuth = await (0, provider_registry_1.getProvider)(provider);
|
|
210
|
-
if (providerDefOAuth?.mcpServer?.type === 'http' &&
|
|
211
|
-
!providerDefOAuth.mcpServer.authHeaderTemplate) {
|
|
212
|
-
const mcpUrl = providerDefOAuth.mcpServer.url;
|
|
213
|
-
await writeOAuthProviderToIDEConfigs(provider, mcpUrl);
|
|
214
|
-
console.log(chalk_1.default.green(`\nā
${providerName} configured for IDE-native OAuth.`));
|
|
215
|
-
console.log(chalk_1.default.cyan('\nš” Next steps:'));
|
|
216
|
-
console.log(chalk_1.default.gray(' 1. Restart your IDE(s) to load the new MCP server'));
|
|
217
|
-
console.log(chalk_1.default.gray(' 2. On first use, your IDE will prompt you to sign in to ' + providerName));
|
|
218
|
-
return;
|
|
219
|
-
}
|
|
220
|
-
// Get credentials using generic prompt system
|
|
221
|
-
try {
|
|
222
|
-
// Build provided tokens/configs from CLI options
|
|
223
|
-
const providedTokens = {};
|
|
224
|
-
const providedConfigs = {};
|
|
225
|
-
if (options.token) {
|
|
226
|
-
providedTokens[provider] = options.token;
|
|
227
|
-
}
|
|
228
|
-
// Handle provider-specific config options (e.g., Jira URL/email or ADO organization)
|
|
229
|
-
if (options.url || options.email || options.organization) {
|
|
230
|
-
providedConfigs[provider] = {
|
|
231
|
-
...(options.url && { baseUrl: options.url.replace(/^https?:\/\//, '').replace(/\/$/, '') }),
|
|
232
|
-
...(options.email && { email: options.email }),
|
|
233
|
-
...(options.organization && { organization: options.organization })
|
|
234
|
-
};
|
|
235
|
-
}
|
|
236
|
-
// Get credentials using generic prompt system
|
|
237
|
-
const client = (0, get_provider_client_1.getProviderClient)();
|
|
238
|
-
const creds = await (0, provider_prompts_1.promptForProviderCredentials)(client, provider, providedTokens[provider], providedConfigs[provider]);
|
|
239
|
-
// Write provider MCP entry directly to IDE configs (token not stored in global config)
|
|
240
|
-
await updateIDEConfigs(provider, config.apiKey, creds.token, creds.config);
|
|
241
|
-
// Offer mode switch if adding a provider with issues capability
|
|
242
|
-
const providerDef = await (0, provider_registry_1.getProvider)(provider);
|
|
243
|
-
if (providerDef?.capabilities.includes('issues') && provider === 'jira') {
|
|
244
|
-
await offerModeSwitch(config);
|
|
245
|
-
}
|
|
246
|
-
console.log(chalk_1.default.green('\nš Provider added successfully!'));
|
|
247
|
-
console.log(chalk_1.default.cyan('\nš” Next steps:'));
|
|
248
|
-
console.log(chalk_1.default.gray(' 1. Restart your IDE(s) to load new MCP servers'));
|
|
249
|
-
if (provider === 'jira' && config.mode === 'split') {
|
|
250
|
-
console.log(chalk_1.default.gray(' 2. Run "fraim init-project" in your projects to use split mode'));
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
catch (error) {
|
|
254
|
-
console.log(chalk_1.default.red(`\nā Failed to add provider: ${error instanceof Error ? error.message : 'Unknown error'}`));
|
|
255
|
-
process.exit(1);
|
|
256
|
-
}
|
|
5
|
+
const MAX_PROVIDER_LABEL_LENGTH = 80;
|
|
6
|
+
const safeProviderLabel = (provider) => {
|
|
7
|
+
const sanitized = provider
|
|
8
|
+
.replace(/\p{C}/gu, '')
|
|
9
|
+
.trim()
|
|
10
|
+
.slice(0, MAX_PROVIDER_LABEL_LENGTH);
|
|
11
|
+
return sanitized || 'this provider';
|
|
12
|
+
};
|
|
13
|
+
const explainAgentManagedProviderSetup = (provider) => {
|
|
14
|
+
const safeProvider = safeProviderLabel(provider);
|
|
15
|
+
return {
|
|
16
|
+
provider: safeProvider,
|
|
17
|
+
changed: false,
|
|
18
|
+
exitCode: 1,
|
|
19
|
+
message: [
|
|
20
|
+
'Provider setup is managed by your FRAIM agent.',
|
|
21
|
+
`Ask your agent: Use the FRAIM connect-mcp skill to connect ${safeProvider}.`,
|
|
22
|
+
'The legacy fraim add-provider command made no changes.',
|
|
23
|
+
].join('\n'),
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
exports.explainAgentManagedProviderSetup = explainAgentManagedProviderSetup;
|
|
27
|
+
const runAddProvider = (provider) => {
|
|
28
|
+
const explanation = (0, exports.explainAgentManagedProviderSetup)(provider);
|
|
29
|
+
console.error(explanation.message);
|
|
30
|
+
return explanation;
|
|
257
31
|
};
|
|
258
32
|
exports.runAddProvider = runAddProvider;
|
|
259
33
|
exports.addProviderCommand = new commander_1.Command('add-provider')
|
|
260
|
-
.description(
|
|
261
|
-
.argument('<provider>',
|
|
262
|
-
.
|
|
263
|
-
|
|
264
|
-
.
|
|
265
|
-
.option('--url <url>', 'Instance URL (for providers that require it)')
|
|
266
|
-
.option('--no-validate', 'Skip token validation')
|
|
267
|
-
.action(async (provider, options) => {
|
|
268
|
-
if (!(await (0, provider_registry_1.isValidProviderId)(provider))) {
|
|
269
|
-
const allProviderIds = await (0, provider_registry_1.getAllProviderIds)();
|
|
270
|
-
console.log(chalk_1.default.red(`ā Invalid provider: ${provider}`));
|
|
271
|
-
console.log(chalk_1.default.yellow(`Valid providers: ${allProviderIds.join(', ')}`));
|
|
272
|
-
process.exit(1);
|
|
273
|
-
}
|
|
274
|
-
await (0, exports.runAddProvider)(provider, options);
|
|
34
|
+
.description('Deprecated: provider setup is agent-managed through the FRAIM connect-mcp skill')
|
|
35
|
+
.argument('<provider>', 'Provider to connect through your FRAIM agent')
|
|
36
|
+
.action((provider) => {
|
|
37
|
+
const result = (0, exports.runAddProvider)(provider);
|
|
38
|
+
process.exitCode = result.exitCode;
|
|
275
39
|
});
|
|
@@ -10,10 +10,10 @@ exports.loginCommand = new commander_1.Command('login')
|
|
|
10
10
|
.description('Login to platforms (GitHub, etc.)');
|
|
11
11
|
exports.loginCommand
|
|
12
12
|
.command('github')
|
|
13
|
-
.description('
|
|
13
|
+
.description('Explain agent-managed GitHub MCP setup')
|
|
14
14
|
.action(async () => {
|
|
15
|
-
console.log(chalk_1.default.blue('\nš” GitHub
|
|
16
|
-
console.log(chalk_1.default.gray('
|
|
17
|
-
console.log(chalk_1.default.cyan('\n
|
|
18
|
-
console.log(chalk_1.default.gray('
|
|
15
|
+
console.log(chalk_1.default.blue('\nš” GitHub MCP setup is managed by your FRAIM agent.'));
|
|
16
|
+
console.log(chalk_1.default.gray('Ask your agent:'));
|
|
17
|
+
console.log(chalk_1.default.cyan('\n Use the FRAIM connect-mcp skill to connect github.\n'));
|
|
18
|
+
console.log(chalk_1.default.gray('The agent will follow current GitHub and host guidance and verify the connection.'));
|
|
19
19
|
});
|