fraim-framework 2.0.82 ā 2.0.84
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 +16 -3
- package/dist/src/cli/api/get-provider-client.js +41 -0
- package/dist/src/cli/api/provider-client.js +107 -0
- package/dist/src/cli/commands/add-ide.js +145 -78
- package/dist/src/cli/commands/add-provider.js +61 -221
- package/dist/src/cli/commands/doctor.js +4 -1
- package/dist/src/cli/commands/init-project.js +24 -35
- package/dist/src/cli/commands/setup.js +287 -566
- package/dist/src/cli/commands/test-mcp.js +35 -1
- package/dist/src/cli/doctor/check-runner.js +6 -3
- package/dist/src/cli/doctor/checks/global-setup-checks.js +5 -6
- package/dist/src/cli/doctor/checks/ide-config-checks.js +44 -23
- package/dist/src/cli/doctor/checks/mcp-connectivity-checks.js +324 -146
- package/dist/src/cli/doctor/checks/scripts-checks.js +2 -2
- package/dist/src/cli/fraim.js +42 -3
- package/dist/src/cli/mcp/ide-formats.js +243 -0
- package/dist/src/cli/mcp/mcp-server-builder.js +48 -0
- package/dist/src/cli/mcp/mcp-server-registry.js +161 -0
- package/dist/src/cli/mcp/types.js +3 -0
- package/dist/src/cli/providers/local-provider-registry.js +145 -0
- package/dist/src/cli/providers/provider-registry.js +230 -0
- package/dist/src/cli/setup/auto-mcp-setup.js +68 -119
- package/dist/src/cli/setup/mcp-config-generator.js +64 -321
- package/dist/src/cli/setup/provider-prompts.js +300 -0
- package/dist/src/core/utils/workflow-parser.js +5 -1
- package/dist/src/local-mcp-server/stdio-server.js +30 -30
- package/package.json +6 -3
- package/dist/src/cli/commands/install.js +0 -86
- package/dist/src/cli/setup/token-validator.js +0 -57
package/README.md
CHANGED
|
@@ -228,11 +228,24 @@ R - Retrospectives: Continuous learning from experience
|
|
|
228
228
|
**Why Git Bash on Windows?** All FRAIM scripts use Unix-style paths and Bash commands. Git Bash ensures consistent behavior across platforms.
|
|
229
229
|
|
|
230
230
|
### **Install & Initialize**
|
|
231
|
+
|
|
232
|
+
**Recommended: Use npx (no installation needed)**
|
|
233
|
+
```bash
|
|
234
|
+
npx fraim-framework@latest setup --key=<your-fraim-key>
|
|
235
|
+
|
|
236
|
+
# Optional: Create alias for convenience
|
|
237
|
+
echo 'alias fraim="npx fraim-framework"' >> ~/.bashrc
|
|
238
|
+
source ~/.bashrc
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
**Alternative: Global install**
|
|
231
242
|
```bash
|
|
232
243
|
npm install -g fraim-framework
|
|
233
|
-
fraim setup
|
|
244
|
+
fraim setup --key=<your-fraim-key>
|
|
234
245
|
```
|
|
235
246
|
|
|
247
|
+
> **š” Why npx?** Works with any Node version (16+), no conflicts when switching Node versions, always uses correct dependencies, and identical functionality to global install. Perfect for users with nvm, volta, or multiple Node versions.
|
|
248
|
+
|
|
236
249
|
The setup command supports three modes:
|
|
237
250
|
|
|
238
251
|
**Conversational Mode**: AI workflows only, no platform integration required
|
|
@@ -282,7 +295,7 @@ fraim setup --jira # Add Jira integration
|
|
|
282
295
|
fraim init-project # Initialize FRAIM in current project
|
|
283
296
|
|
|
284
297
|
# Testing and validation
|
|
285
|
-
fraim test-mcp
|
|
298
|
+
fraim doctor --test-mcp # Test MCP server connections
|
|
286
299
|
fraim doctor # Diagnose configuration issues
|
|
287
300
|
|
|
288
301
|
# Sync and maintenance
|
|
@@ -325,7 +338,7 @@ FRAIM uses the official Model Context Protocol (MCP) server for Jira integration
|
|
|
325
338
|
**Troubleshooting**:
|
|
326
339
|
```bash
|
|
327
340
|
# Test Jira MCP connection
|
|
328
|
-
fraim test-mcp
|
|
341
|
+
fraim doctor --test-mcp
|
|
329
342
|
|
|
330
343
|
# Reconfigure Jira integration
|
|
331
344
|
fraim setup --jira
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.getProviderClient = getProviderClient;
|
|
7
|
+
// Helper to get provider client with FRAIM key from config
|
|
8
|
+
const provider_client_1 = require("./provider-client");
|
|
9
|
+
const fs_1 = __importDefault(require("fs"));
|
|
10
|
+
const path_1 = __importDefault(require("path"));
|
|
11
|
+
const os_1 = __importDefault(require("os"));
|
|
12
|
+
/**
|
|
13
|
+
* Get user FRAIM directory
|
|
14
|
+
*/
|
|
15
|
+
function getUserFraimDir() {
|
|
16
|
+
return process.env.FRAIM_USER_DIR || path_1.default.join(os_1.default.homedir(), '.fraim');
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Get provider client using FRAIM key from global config
|
|
20
|
+
* Throws error if no config found - caller should handle and use local fallback
|
|
21
|
+
*/
|
|
22
|
+
function getProviderClient() {
|
|
23
|
+
const globalConfigPath = path_1.default.join(getUserFraimDir(), 'config.json');
|
|
24
|
+
if (!fs_1.default.existsSync(globalConfigPath)) {
|
|
25
|
+
throw new Error('No FRAIM configuration found');
|
|
26
|
+
}
|
|
27
|
+
try {
|
|
28
|
+
const config = JSON.parse(fs_1.default.readFileSync(globalConfigPath, 'utf8'));
|
|
29
|
+
const fraimKey = config.apiKey;
|
|
30
|
+
if (!fraimKey) {
|
|
31
|
+
throw new Error('FRAIM API key not found in config');
|
|
32
|
+
}
|
|
33
|
+
// Use FRAIM_REMOTE_URL if set (for tests or custom deployments)
|
|
34
|
+
// Otherwise ProviderClient will use its default
|
|
35
|
+
const serverUrl = process.env.FRAIM_REMOTE_URL || undefined;
|
|
36
|
+
return new provider_client_1.ProviderClient(fraimKey, serverUrl);
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
throw new Error(`Failed to load FRAIM configuration: ${error.message}`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.ProviderClient = void 0;
|
|
7
|
+
// CLI client for fetching provider metadata from server
|
|
8
|
+
const axios_1 = __importDefault(require("axios"));
|
|
9
|
+
class ProviderClient {
|
|
10
|
+
constructor(fraimKey, serverUrl) {
|
|
11
|
+
this.serverUrl = serverUrl || process.env.FRAIM_REMOTE_URL || 'https://fraim.wellnessatwork.me';
|
|
12
|
+
this.client = axios_1.default.create({
|
|
13
|
+
baseURL: this.serverUrl,
|
|
14
|
+
headers: {
|
|
15
|
+
'x-api-key': fraimKey,
|
|
16
|
+
'Content-Type': 'application/json'
|
|
17
|
+
},
|
|
18
|
+
timeout: 10000 // 10 second timeout
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Get all providers from server
|
|
23
|
+
*/
|
|
24
|
+
async getAllProviders() {
|
|
25
|
+
try {
|
|
26
|
+
const response = await this.client.get('/api/providers');
|
|
27
|
+
return response.data.providers;
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
if (error.response) {
|
|
31
|
+
// Server responded with error status
|
|
32
|
+
throw new Error(`Failed to fetch providers: ${error.response.status} - ${JSON.stringify(error.response.data)}`);
|
|
33
|
+
}
|
|
34
|
+
else if (error.request) {
|
|
35
|
+
// Request was made but no response
|
|
36
|
+
throw new Error(`Failed to fetch providers: No response from server`);
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
// Something else happened
|
|
40
|
+
throw new Error(`Failed to fetch providers: ${error.message}`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Get a specific provider by ID
|
|
46
|
+
*/
|
|
47
|
+
async getProvider(id) {
|
|
48
|
+
const providers = await this.getAllProviders();
|
|
49
|
+
return providers.find(p => p.id === id) || null;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Get provider config schema
|
|
53
|
+
*/
|
|
54
|
+
async getProviderSchema(id) {
|
|
55
|
+
try {
|
|
56
|
+
const response = await this.client.get(`/api/providers/${id}/schema`);
|
|
57
|
+
return response.data;
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
if (error.response?.status === 404) {
|
|
61
|
+
throw new Error(`Provider '${id}' not found`);
|
|
62
|
+
}
|
|
63
|
+
throw new Error(`Failed to fetch provider schema: ${error.message}`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Validate provider config
|
|
68
|
+
*/
|
|
69
|
+
async validateProviderConfig(id, config) {
|
|
70
|
+
try {
|
|
71
|
+
const response = await this.client.post(`/api/providers/${id}/validate`, config);
|
|
72
|
+
return response.data;
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
throw new Error(`Failed to validate provider config: ${error.message}`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Get all provider IDs
|
|
80
|
+
*/
|
|
81
|
+
async getAllProviderIds() {
|
|
82
|
+
const providers = await this.getAllProviders();
|
|
83
|
+
return providers.map(p => p.id);
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Get providers with a specific capability
|
|
87
|
+
*/
|
|
88
|
+
async getProvidersWithCapability(capability) {
|
|
89
|
+
const providers = await this.getAllProviders();
|
|
90
|
+
return providers.filter(p => p.capabilities.includes(capability));
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Check if a provider has a specific capability
|
|
94
|
+
*/
|
|
95
|
+
async providerHasCapability(providerId, capability) {
|
|
96
|
+
const provider = await this.getProvider(providerId);
|
|
97
|
+
return provider ? provider.capabilities.includes(capability) : false;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Check if provider requires additional config
|
|
101
|
+
*/
|
|
102
|
+
async requiresAdditionalConfig(providerId) {
|
|
103
|
+
const provider = await this.getProvider(providerId);
|
|
104
|
+
return provider?.hasAdditionalConfig || false;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
exports.ProviderClient = ProviderClient;
|
|
@@ -1,9 +1,42 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
2
35
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
36
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
37
|
};
|
|
5
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.
|
|
39
|
+
exports.saveProviderTokenToConfig = exports.loadGlobalConfig = exports.addIDECommand = exports.runAddIDE = void 0;
|
|
7
40
|
const commander_1 = require("commander");
|
|
8
41
|
const chalk_1 = __importDefault(require("chalk"));
|
|
9
42
|
const prompts_1 = __importDefault(require("prompts"));
|
|
@@ -13,20 +46,52 @@ const ide_detector_1 = require("../setup/ide-detector");
|
|
|
13
46
|
const mcp_config_generator_1 = require("../setup/mcp-config-generator");
|
|
14
47
|
const codex_local_config_1 = require("../setup/codex-local-config");
|
|
15
48
|
const script_sync_utils_1 = require("../utils/script-sync-utils");
|
|
16
|
-
const
|
|
49
|
+
const mcp_server_registry_1 = require("../mcp/mcp-server-registry");
|
|
50
|
+
const get_provider_client_1 = require("../api/get-provider-client");
|
|
51
|
+
const provider_prompts_1 = require("../setup/provider-prompts");
|
|
52
|
+
const provider_registry_1 = require("../providers/provider-registry");
|
|
53
|
+
const loadGlobalConfig = async () => {
|
|
17
54
|
const globalConfigPath = path_1.default.join((0, script_sync_utils_1.getUserFraimDir)(), 'config.json');
|
|
18
55
|
if (!fs_1.default.existsSync(globalConfigPath)) {
|
|
19
56
|
return null;
|
|
20
57
|
}
|
|
21
58
|
try {
|
|
22
59
|
const config = JSON.parse(fs_1.default.readFileSync(globalConfigPath, 'utf8'));
|
|
60
|
+
// Support both old and new token format
|
|
61
|
+
const tokens = config.tokens || {};
|
|
62
|
+
// Backward compatibility: map old format to new
|
|
63
|
+
// Only try to fetch provider IDs if we have a valid FRAIM key
|
|
64
|
+
if (config.apiKey) {
|
|
65
|
+
try {
|
|
66
|
+
const client = (0, get_provider_client_1.getProviderClient)();
|
|
67
|
+
const providerIds = await client.getAllProviderIds();
|
|
68
|
+
for (const id of providerIds) {
|
|
69
|
+
const oldKey = `${id}Token`;
|
|
70
|
+
if (config[oldKey] && !tokens[id]) {
|
|
71
|
+
tokens[id] = config[oldKey];
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
catch (e) {
|
|
76
|
+
// If provider client fails (network error, invalid key, etc.),
|
|
77
|
+
// skip backward compatibility mapping and use local fallback
|
|
78
|
+
// This is fine - the config will still work with the new format
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
// Load all provider configs
|
|
82
|
+
const providerConfigs = {};
|
|
83
|
+
// New format: providerConfigs object
|
|
84
|
+
if (config.providerConfigs) {
|
|
85
|
+
Object.entries(config.providerConfigs).forEach(([key, value]) => {
|
|
86
|
+
const providerId = key.replace('Config', '');
|
|
87
|
+
providerConfigs[providerId] = value;
|
|
88
|
+
});
|
|
89
|
+
}
|
|
23
90
|
return {
|
|
24
91
|
fraimKey: config.apiKey,
|
|
25
|
-
|
|
26
|
-
gitlabToken: config.tokens?.gitlab,
|
|
27
|
-
jiraToken: config.tokens?.jira,
|
|
92
|
+
tokens,
|
|
28
93
|
mode: config.mode,
|
|
29
|
-
|
|
94
|
+
providerConfigs
|
|
30
95
|
};
|
|
31
96
|
}
|
|
32
97
|
catch (e) {
|
|
@@ -34,71 +99,65 @@ const loadGlobalConfig = () => {
|
|
|
34
99
|
}
|
|
35
100
|
};
|
|
36
101
|
exports.loadGlobalConfig = loadGlobalConfig;
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
102
|
+
const promptForProviderTokenIfNeeded = async (providerId, isOptional = false) => {
|
|
103
|
+
const client = (0, get_provider_client_1.getProviderClient)();
|
|
104
|
+
const provider = await client.getProvider(providerId);
|
|
105
|
+
if (!provider)
|
|
106
|
+
return '';
|
|
107
|
+
if (isOptional) {
|
|
108
|
+
console.log(chalk_1.default.yellow(`\nš ${provider.displayName} token (optional for conversational mode)`));
|
|
109
|
+
console.log(chalk_1.default.gray(`${provider.displayName} token enables ${provider.displayName}-specific MCP features.\n`));
|
|
41
110
|
const wantsToken = await (0, prompts_1.default)({
|
|
42
111
|
type: 'confirm',
|
|
43
112
|
name: 'addToken',
|
|
44
|
-
message:
|
|
113
|
+
message: `Do you want to add a ${provider.displayName} token?`,
|
|
45
114
|
initial: false
|
|
46
115
|
});
|
|
47
116
|
if (!wantsToken.addToken) {
|
|
48
|
-
console.log(chalk_1.default.blue(
|
|
117
|
+
console.log(chalk_1.default.blue(`ā¹ļø Skipping ${provider.displayName} token - ${provider.displayName} MCP server will not be configured`));
|
|
49
118
|
return '';
|
|
50
119
|
}
|
|
51
120
|
}
|
|
52
121
|
else {
|
|
53
|
-
console.log(chalk_1.default.yellow(
|
|
54
|
-
console.log(chalk_1.default.gray(
|
|
122
|
+
console.log(chalk_1.default.yellow(`\nš ${provider.displayName} token needed for MCP configuration`));
|
|
123
|
+
console.log(chalk_1.default.gray(`This is required for ${provider.displayName} MCP servers to function properly.\n`));
|
|
55
124
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
return 'GitHub token is required';
|
|
63
|
-
if (!value && isConversationalMode)
|
|
64
|
-
return true; // Allow empty in conversational mode
|
|
65
|
-
if (value.startsWith('ghp_') || value.startsWith('github_pat_'))
|
|
66
|
-
return true;
|
|
67
|
-
return 'Please enter a valid GitHub token (starts with ghp_ or github_pat_)';
|
|
68
|
-
}
|
|
69
|
-
});
|
|
70
|
-
if (!tokenResponse.token) {
|
|
71
|
-
if (isConversationalMode) {
|
|
72
|
-
console.log(chalk_1.default.blue('ā¹ļø No GitHub token provided - GitHub MCP server will not be configured'));
|
|
125
|
+
try {
|
|
126
|
+
return await (0, provider_prompts_1.promptForProviderToken)(client, providerId);
|
|
127
|
+
}
|
|
128
|
+
catch (error) {
|
|
129
|
+
if (isOptional) {
|
|
130
|
+
console.log(chalk_1.default.blue(`ā¹ļø No ${provider.displayName} token provided - ${provider.displayName} MCP server will not be configured`));
|
|
73
131
|
return '';
|
|
74
132
|
}
|
|
75
|
-
console.log(chalk_1.default.red(
|
|
133
|
+
console.log(chalk_1.default.red(`${provider.displayName} token is required. Exiting.`));
|
|
76
134
|
process.exit(1);
|
|
77
135
|
}
|
|
78
|
-
return tokenResponse.token;
|
|
79
136
|
};
|
|
80
|
-
const
|
|
137
|
+
const saveProviderTokenToConfig = async (providerId, token) => {
|
|
81
138
|
const globalConfigPath = path_1.default.join((0, script_sync_utils_1.getUserFraimDir)(), 'config.json');
|
|
82
139
|
if (fs_1.default.existsSync(globalConfigPath)) {
|
|
83
140
|
try {
|
|
84
141
|
const config = JSON.parse(fs_1.default.readFileSync(globalConfigPath, 'utf8'));
|
|
85
|
-
// Use new tokens structure
|
|
86
|
-
if (config.tokens) {
|
|
87
|
-
config.tokens
|
|
88
|
-
}
|
|
89
|
-
else {
|
|
90
|
-
config.githubToken = githubToken;
|
|
142
|
+
// Use new tokens structure
|
|
143
|
+
if (!config.tokens) {
|
|
144
|
+
config.tokens = {};
|
|
91
145
|
}
|
|
146
|
+
config.tokens[providerId] = token;
|
|
92
147
|
fs_1.default.writeFileSync(globalConfigPath, JSON.stringify(config, null, 2));
|
|
93
|
-
|
|
148
|
+
const { getProvider } = await Promise.resolve().then(() => __importStar(require('../providers/provider-registry')));
|
|
149
|
+
const provider = await getProvider(providerId);
|
|
150
|
+
console.log(chalk_1.default.green(`ā
${provider?.displayName || providerId} token saved to global config`));
|
|
94
151
|
}
|
|
95
152
|
catch (e) {
|
|
96
|
-
|
|
153
|
+
const { getProvider } = await Promise.resolve().then(() => __importStar(require('../providers/provider-registry')));
|
|
154
|
+
const provider = await getProvider(providerId);
|
|
155
|
+
console.log(chalk_1.default.yellow(`ā ļø Could not save ${provider?.displayName || providerId} token to config`));
|
|
97
156
|
}
|
|
98
157
|
}
|
|
99
158
|
};
|
|
100
|
-
exports.
|
|
101
|
-
const configureIDEMCP = async (ide, fraimKey, tokens,
|
|
159
|
+
exports.saveProviderTokenToConfig = saveProviderTokenToConfig;
|
|
160
|
+
const configureIDEMCP = async (ide, fraimKey, tokens, providerConfigs) => {
|
|
102
161
|
const configPath = (0, ide_detector_1.expandPath)(ide.configPath);
|
|
103
162
|
console.log(chalk_1.default.blue(`š§ Configuring ${ide.name}...`));
|
|
104
163
|
// Create backup if config exists
|
|
@@ -127,14 +186,18 @@ const configureIDEMCP = async (ide, fraimKey, tokens, jiraConfig) => {
|
|
|
127
186
|
}
|
|
128
187
|
}
|
|
129
188
|
if (ide.configFormat === 'toml') {
|
|
130
|
-
// Handle TOML format (Codex)
|
|
189
|
+
// Handle TOML format (e.g., Codex, Zed)
|
|
131
190
|
let existingTomlContent = '';
|
|
132
191
|
if (fs_1.default.existsSync(configPath)) {
|
|
133
192
|
existingTomlContent = fs_1.default.readFileSync(configPath, 'utf8');
|
|
134
193
|
console.log(chalk_1.default.gray(` š Found existing TOML config`));
|
|
135
194
|
}
|
|
136
|
-
const newTomlContent = (0, mcp_config_generator_1.generateMCPConfig)(ide.configType, fraimKey, tokens,
|
|
137
|
-
const
|
|
195
|
+
const newTomlContent = await (0, mcp_config_generator_1.generateMCPConfig)(ide.configType, fraimKey, tokens, providerConfigs);
|
|
196
|
+
const { getAllMCPServerIds } = await Promise.resolve().then(() => __importStar(require('../mcp/mcp-server-registry')));
|
|
197
|
+
const baseServerIds = getAllMCPServerIds();
|
|
198
|
+
// Add provider server IDs from tokens
|
|
199
|
+
const providerServerIds = Object.keys(tokens).filter(id => tokens[id]);
|
|
200
|
+
const serversToAdd = [...baseServerIds, ...providerServerIds];
|
|
138
201
|
const mergeResult = (0, mcp_config_generator_1.mergeTomlMCPServers)(existingTomlContent, newTomlContent, serversToAdd);
|
|
139
202
|
fs_1.default.writeFileSync(configPath, mergeResult.content);
|
|
140
203
|
mergeResult.addedServers.forEach(server => {
|
|
@@ -149,7 +212,7 @@ const configureIDEMCP = async (ide, fraimKey, tokens, jiraConfig) => {
|
|
|
149
212
|
}
|
|
150
213
|
else {
|
|
151
214
|
// For JSON configs - intelligent merging
|
|
152
|
-
const newConfig = (0, mcp_config_generator_1.generateMCPConfig)(ide.configType, fraimKey, tokens,
|
|
215
|
+
const newConfig = await (0, mcp_config_generator_1.generateMCPConfig)(ide.configType, fraimKey, tokens, providerConfigs);
|
|
153
216
|
const newMCPServers = newConfig[serversKey] || newConfig.mcpServers || {};
|
|
154
217
|
// Merge MCP servers intelligently
|
|
155
218
|
const mergedMCPServers = { ...existingMCPServers };
|
|
@@ -180,10 +243,11 @@ const configureIDEMCP = async (ide, fraimKey, tokens, jiraConfig) => {
|
|
|
180
243
|
});
|
|
181
244
|
}
|
|
182
245
|
console.log(chalk_1.default.green(`ā
Updated ${configPath}`));
|
|
246
|
+
// Handle IDE-specific local config (e.g., Codex needs project-level config)
|
|
183
247
|
if (ide.configType === 'codex') {
|
|
184
248
|
const localResult = (0, codex_local_config_1.ensureCodexLocalConfig)(process.cwd());
|
|
185
249
|
const status = localResult.created ? 'Created' : localResult.updated ? 'Updated' : 'Verified';
|
|
186
|
-
console.log(chalk_1.default.green(` ā
${status} local
|
|
250
|
+
console.log(chalk_1.default.green(` ā
${status} local ${ide.name} config: ${localResult.path}`));
|
|
187
251
|
}
|
|
188
252
|
};
|
|
189
253
|
const listSupportedIDEs = () => {
|
|
@@ -210,18 +274,18 @@ const promptForIDESelection = async (availableIDEs, tokens) => {
|
|
|
210
274
|
console.log(chalk_1.default.white(` ${index + 1}. ${ide.name} ${statusIcon} (${statusText})`));
|
|
211
275
|
});
|
|
212
276
|
console.log(chalk_1.default.blue('\nFRAIM will add these MCP servers:'));
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
277
|
+
// Show base servers
|
|
278
|
+
for (const server of mcp_server_registry_1.BASE_MCP_SERVERS) {
|
|
279
|
+
console.log(chalk_1.default.gray(` ⢠${server.id} (${server.description})`));
|
|
280
|
+
}
|
|
281
|
+
// Show provider servers (only if tokens exist)
|
|
282
|
+
const allProviders = await (0, provider_registry_1.getAllProviders)();
|
|
283
|
+
for (const provider of allProviders) {
|
|
284
|
+
const hasToken = tokens?.[provider.id];
|
|
285
|
+
if (hasToken && provider.mcpServer) {
|
|
286
|
+
console.log(chalk_1.default.gray(` - ${provider.id} (${provider.description})`));
|
|
287
|
+
}
|
|
223
288
|
}
|
|
224
|
-
console.log(chalk_1.default.gray(' - playwright (browser automation)'));
|
|
225
289
|
const response = await (0, prompts_1.default)({
|
|
226
290
|
type: 'text',
|
|
227
291
|
name: 'selection',
|
|
@@ -253,29 +317,32 @@ const runAddIDE = async (options) => {
|
|
|
253
317
|
}
|
|
254
318
|
console.log(chalk_1.default.blue('š§ FRAIM IDE Configuration\n'));
|
|
255
319
|
// Load existing configuration
|
|
256
|
-
const globalConfig = loadGlobalConfig();
|
|
320
|
+
const globalConfig = await loadGlobalConfig();
|
|
257
321
|
if (!globalConfig || !globalConfig.fraimKey) {
|
|
258
322
|
console.log(chalk_1.default.red('ā No FRAIM configuration found.'));
|
|
259
|
-
console.log(chalk_1.default.yellow('š” Please run "fraim setup" first to configure your FRAIM
|
|
323
|
+
console.log(chalk_1.default.yellow('š” Please run "fraim setup" first to configure your FRAIM keys.'));
|
|
260
324
|
process.exit(1);
|
|
261
325
|
}
|
|
262
|
-
|
|
263
|
-
const platformTokens = {
|
|
264
|
-
github: globalConfig.githubToken,
|
|
265
|
-
gitlab: globalConfig.gitlabToken,
|
|
266
|
-
jira: globalConfig.jiraToken
|
|
267
|
-
};
|
|
326
|
+
const platformTokens = globalConfig.tokens || {};
|
|
268
327
|
const isConversationalMode = globalConfig.mode === 'conversational';
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
328
|
+
// Check if any provider tokens exist
|
|
329
|
+
const allProviderIds = await (0, provider_registry_1.getAllProviderIds)();
|
|
330
|
+
const hasAnyToken = allProviderIds.some(id => platformTokens[id]);
|
|
331
|
+
if (!hasAnyToken && !isConversationalMode) {
|
|
332
|
+
console.log(chalk_1.default.yellow('ā ļø No provider tokens found in configuration.'));
|
|
333
|
+
// Prompt for first integrated provider as default
|
|
334
|
+
const integratedProviders = await (0, provider_registry_1.getProvidersWithCapability)('integrated');
|
|
335
|
+
const defaultProviderId = integratedProviders[0]?.id;
|
|
336
|
+
if (defaultProviderId) {
|
|
337
|
+
const token = await promptForProviderTokenIfNeeded(defaultProviderId, false);
|
|
338
|
+
if (token) {
|
|
339
|
+
await saveProviderTokenToConfig(defaultProviderId, token);
|
|
340
|
+
platformTokens[defaultProviderId] = token;
|
|
341
|
+
}
|
|
275
342
|
}
|
|
276
343
|
}
|
|
277
|
-
if (isConversationalMode && !
|
|
278
|
-
console.log(chalk_1.default.blue('ā¹ļø Conversational mode: Configuring MCP without
|
|
344
|
+
if (isConversationalMode && !hasAnyToken) {
|
|
345
|
+
console.log(chalk_1.default.blue('ā¹ļø Conversational mode: Configuring MCP without platform integration\n'));
|
|
279
346
|
}
|
|
280
347
|
else {
|
|
281
348
|
console.log(chalk_1.default.green('ā
Using existing FRAIM configuration\n'));
|
|
@@ -324,7 +391,7 @@ const runAddIDE = async (options) => {
|
|
|
324
391
|
};
|
|
325
392
|
for (const ide of idesToConfigure) {
|
|
326
393
|
try {
|
|
327
|
-
await configureIDEMCP(ide, globalConfig.fraimKey, platformTokens, globalConfig.
|
|
394
|
+
await configureIDEMCP(ide, globalConfig.fraimKey, platformTokens, globalConfig.providerConfigs);
|
|
328
395
|
results.successful.push(ide.name);
|
|
329
396
|
}
|
|
330
397
|
catch (error) {
|
|
@@ -351,7 +418,7 @@ const runAddIDE = async (options) => {
|
|
|
351
418
|
console.log(chalk_1.default.blue('\nš Next steps:'));
|
|
352
419
|
console.log(chalk_1.default.cyan(' 1. Restart your configured IDEs'));
|
|
353
420
|
console.log(chalk_1.default.cyan(' 2. Ask your AI agent: "list fraim workflows"'));
|
|
354
|
-
console.log(chalk_1.default.blue('\nš” Use "fraim test-mcp" to verify the configuration.'));
|
|
421
|
+
console.log(chalk_1.default.blue('\nš” Use "fraim doctor --test-mcp" to verify the configuration.'));
|
|
355
422
|
}
|
|
356
423
|
};
|
|
357
424
|
exports.runAddIDE = runAddIDE;
|