fraim-framework 2.0.77 → 2.0.78
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 +25 -11
- package/dist/src/cli/commands/init-project.js +17 -6
- package/dist/src/cli/commands/setup.js +125 -15
- package/dist/src/cli/setup/auto-mcp-setup.js +27 -10
- package/dist/src/cli/setup/mcp-config-generator.js +153 -39
- package/dist/src/cli/setup/token-validator.js +8 -0
- package/dist/src/cli/utils/platform-detection.js +45 -0
- package/dist/src/core/config-loader.js +1 -1
- package/dist/src/core/utils/provider-utils.js +5 -1
- package/dist/src/local-mcp-server/stdio-server.js +84 -15
- package/package.json +1 -1
|
@@ -23,6 +23,8 @@ const loadGlobalConfig = () => {
|
|
|
23
23
|
return {
|
|
24
24
|
fraimKey: config.apiKey,
|
|
25
25
|
githubToken: config.tokens?.github || config.githubToken, // Support both old and new format
|
|
26
|
+
gitlabToken: config.tokens?.gitlab,
|
|
27
|
+
jiraToken: config.tokens?.jira,
|
|
26
28
|
mode: config.mode
|
|
27
29
|
};
|
|
28
30
|
}
|
|
@@ -95,7 +97,7 @@ const saveGitHubTokenToConfig = (githubToken) => {
|
|
|
95
97
|
}
|
|
96
98
|
};
|
|
97
99
|
exports.saveGitHubTokenToConfig = saveGitHubTokenToConfig;
|
|
98
|
-
const configureIDEMCP = async (ide, fraimKey,
|
|
100
|
+
const configureIDEMCP = async (ide, fraimKey, tokens) => {
|
|
99
101
|
const configPath = (0, ide_detector_1.expandPath)(ide.configPath);
|
|
100
102
|
console.log(chalk_1.default.blue(`🔧 Configuring ${ide.name}...`));
|
|
101
103
|
// Create backup if config exists
|
|
@@ -129,8 +131,8 @@ const configureIDEMCP = async (ide, fraimKey, githubToken) => {
|
|
|
129
131
|
existingTomlContent = fs_1.default.readFileSync(configPath, 'utf8');
|
|
130
132
|
console.log(chalk_1.default.gray(` 📋 Found existing TOML config`));
|
|
131
133
|
}
|
|
132
|
-
const newTomlContent = (0, mcp_config_generator_1.generateMCPConfig)(ide.configType, fraimKey,
|
|
133
|
-
const serversToAdd = ['fraim', 'git', 'github', 'playwright'];
|
|
134
|
+
const newTomlContent = (0, mcp_config_generator_1.generateMCPConfig)(ide.configType, fraimKey, tokens);
|
|
135
|
+
const serversToAdd = ['fraim', 'git', 'github', 'gitlab', 'jira', 'playwright'];
|
|
134
136
|
const mergeResult = (0, mcp_config_generator_1.mergeTomlMCPServers)(existingTomlContent, newTomlContent, serversToAdd);
|
|
135
137
|
fs_1.default.writeFileSync(configPath, mergeResult.content);
|
|
136
138
|
mergeResult.addedServers.forEach(server => {
|
|
@@ -145,7 +147,7 @@ const configureIDEMCP = async (ide, fraimKey, githubToken) => {
|
|
|
145
147
|
}
|
|
146
148
|
else {
|
|
147
149
|
// Handle JSON format
|
|
148
|
-
const newConfig = (0, mcp_config_generator_1.generateMCPConfig)(ide.configType, fraimKey,
|
|
150
|
+
const newConfig = (0, mcp_config_generator_1.generateMCPConfig)(ide.configType, fraimKey, tokens);
|
|
149
151
|
const newMCPServers = newConfig.mcpServers || {};
|
|
150
152
|
// Merge MCP servers intelligently
|
|
151
153
|
const mergedMCPServers = { ...existingMCPServers };
|
|
@@ -197,7 +199,7 @@ const listSupportedIDEs = () => {
|
|
|
197
199
|
console.log(chalk_1.default.yellow('💡 Use "fraim add-ide --ide <name>" to configure a specific IDE'));
|
|
198
200
|
console.log(chalk_1.default.yellow(' Example: fraim add-ide --ide claude'));
|
|
199
201
|
};
|
|
200
|
-
const promptForIDESelection = async (availableIDEs,
|
|
202
|
+
const promptForIDESelection = async (availableIDEs, tokens) => {
|
|
201
203
|
console.log(chalk_1.default.green(`✅ Found ${availableIDEs.length} IDEs that can be configured:\n`));
|
|
202
204
|
availableIDEs.forEach((ide, index) => {
|
|
203
205
|
const configExists = fs_1.default.existsSync((0, ide_detector_1.expandPath)(ide.configPath));
|
|
@@ -208,10 +210,16 @@ const promptForIDESelection = async (availableIDEs, githubToken) => {
|
|
|
208
210
|
console.log(chalk_1.default.blue('\nFRAIM will add these MCP servers:'));
|
|
209
211
|
console.log(chalk_1.default.gray(' • fraim (workflows and AI management)'));
|
|
210
212
|
console.log(chalk_1.default.gray(' • git (version control integration)'));
|
|
211
|
-
if (
|
|
212
|
-
console.log(chalk_1.default.gray('
|
|
213
|
+
if (tokens?.github) {
|
|
214
|
+
console.log(chalk_1.default.gray(' - github (GitHub API access)'));
|
|
213
215
|
}
|
|
214
|
-
|
|
216
|
+
if (tokens?.gitlab) {
|
|
217
|
+
console.log(chalk_1.default.gray(' - gitlab (GitLab API access)'));
|
|
218
|
+
}
|
|
219
|
+
if (tokens?.jira) {
|
|
220
|
+
console.log(chalk_1.default.gray(' - jira (Jira issue tracking)'));
|
|
221
|
+
}
|
|
222
|
+
console.log(chalk_1.default.gray(' - playwright (browser automation)'));
|
|
215
223
|
const response = await (0, prompts_1.default)({
|
|
216
224
|
type: 'text',
|
|
217
225
|
name: 'selection',
|
|
@@ -250,12 +258,18 @@ const runAddIDE = async (options) => {
|
|
|
250
258
|
process.exit(1);
|
|
251
259
|
}
|
|
252
260
|
let githubToken = globalConfig.githubToken;
|
|
261
|
+
const platformTokens = {
|
|
262
|
+
github: globalConfig.githubToken,
|
|
263
|
+
gitlab: globalConfig.gitlabToken,
|
|
264
|
+
jira: globalConfig.jiraToken
|
|
265
|
+
};
|
|
253
266
|
const isConversationalMode = globalConfig.mode === 'conversational';
|
|
254
|
-
if (!githubToken && !isConversationalMode) {
|
|
267
|
+
if (!githubToken && !platformTokens.gitlab && !platformTokens.jira && !isConversationalMode) {
|
|
255
268
|
console.log(chalk_1.default.yellow('⚠️ No GitHub token found in configuration.'));
|
|
256
269
|
githubToken = await promptForGitHubToken(false);
|
|
257
270
|
if (githubToken) {
|
|
258
271
|
saveGitHubTokenToConfig(githubToken);
|
|
272
|
+
platformTokens.github = githubToken;
|
|
259
273
|
}
|
|
260
274
|
}
|
|
261
275
|
if (isConversationalMode && !githubToken) {
|
|
@@ -295,7 +309,7 @@ const runAddIDE = async (options) => {
|
|
|
295
309
|
}
|
|
296
310
|
else {
|
|
297
311
|
// Interactive selection
|
|
298
|
-
idesToConfigure = await promptForIDESelection(detectedIDEs,
|
|
312
|
+
idesToConfigure = await promptForIDESelection(detectedIDEs, platformTokens);
|
|
299
313
|
}
|
|
300
314
|
if (idesToConfigure.length === 0) {
|
|
301
315
|
console.log(chalk_1.default.yellow('⚠️ No IDEs selected for configuration.'));
|
|
@@ -308,7 +322,7 @@ const runAddIDE = async (options) => {
|
|
|
308
322
|
};
|
|
309
323
|
for (const ide of idesToConfigure) {
|
|
310
324
|
try {
|
|
311
|
-
await configureIDEMCP(ide, globalConfig.fraimKey,
|
|
325
|
+
await configureIDEMCP(ide, globalConfig.fraimKey, platformTokens);
|
|
312
326
|
results.successful.push(ide.name);
|
|
313
327
|
}
|
|
314
328
|
catch (error) {
|
|
@@ -75,12 +75,19 @@ const runInitProject = async () => {
|
|
|
75
75
|
owner: detection.repository.owner,
|
|
76
76
|
name: detection.repository.name
|
|
77
77
|
}
|
|
78
|
-
:
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
78
|
+
: detection.provider === 'ado'
|
|
79
|
+
? {
|
|
80
|
+
provider: 'ado',
|
|
81
|
+
organization: detection.repository.organization,
|
|
82
|
+
project: detection.repository.project,
|
|
83
|
+
name: detection.repository.name
|
|
84
|
+
}
|
|
85
|
+
: {
|
|
86
|
+
provider: 'gitlab',
|
|
87
|
+
namespace: detection.repository.namespace,
|
|
88
|
+
name: detection.repository.name,
|
|
89
|
+
projectPath: detection.repository.projectPath
|
|
90
|
+
};
|
|
84
91
|
config = {
|
|
85
92
|
version: (0, version_utils_1.getFraimVersion)(),
|
|
86
93
|
project: {
|
|
@@ -99,6 +106,10 @@ const runInitProject = async () => {
|
|
|
99
106
|
console.log(chalk_1.default.gray(` Project: ${detection.repository.project}`));
|
|
100
107
|
console.log(chalk_1.default.gray(` Repository: ${detection.repository.name}`));
|
|
101
108
|
}
|
|
109
|
+
else if (detection.provider === 'gitlab') {
|
|
110
|
+
console.log(chalk_1.default.gray(` Namespace: ${detection.repository.namespace || '(none)'}`));
|
|
111
|
+
console.log(chalk_1.default.gray(` Repository: ${detection.repository.name}`));
|
|
112
|
+
}
|
|
102
113
|
}
|
|
103
114
|
else {
|
|
104
115
|
config = {
|
|
@@ -112,23 +112,31 @@ const promptForPlatforms = async () => {
|
|
|
112
112
|
message: 'Select platforms (Space to select, Enter to confirm)',
|
|
113
113
|
choices: [
|
|
114
114
|
{ title: 'GitHub', value: 'github', selected: true },
|
|
115
|
-
{ title: 'Azure DevOps', value: 'ado', selected: false }
|
|
115
|
+
{ title: 'Azure DevOps', value: 'ado', selected: false },
|
|
116
|
+
{ title: 'GitLab', value: 'gitlab', selected: false },
|
|
117
|
+
{ title: 'Jira', value: 'jira', selected: false }
|
|
116
118
|
],
|
|
117
119
|
min: 1
|
|
118
120
|
});
|
|
119
121
|
if (!response.platforms || response.platforms.length === 0) {
|
|
120
122
|
console.log(chalk_1.default.yellow('\nℹ️ No platforms selected, defaulting to GitHub'));
|
|
121
|
-
return { github: true, ado: false };
|
|
123
|
+
return { github: true, ado: false, gitlab: false, jira: false };
|
|
122
124
|
}
|
|
123
125
|
const platforms = {
|
|
124
126
|
github: response.platforms.includes('github'),
|
|
125
|
-
ado: response.platforms.includes('ado')
|
|
127
|
+
ado: response.platforms.includes('ado'),
|
|
128
|
+
gitlab: response.platforms.includes('gitlab'),
|
|
129
|
+
jira: response.platforms.includes('jira')
|
|
126
130
|
};
|
|
127
131
|
console.log(chalk_1.default.blue('\n✓ Selected platforms:'));
|
|
128
132
|
if (platforms.github)
|
|
129
|
-
console.log(chalk_1.default.gray('
|
|
133
|
+
console.log(chalk_1.default.gray(' - GitHub'));
|
|
130
134
|
if (platforms.ado)
|
|
131
|
-
console.log(chalk_1.default.gray('
|
|
135
|
+
console.log(chalk_1.default.gray(' - Azure DevOps'));
|
|
136
|
+
if (platforms.gitlab)
|
|
137
|
+
console.log(chalk_1.default.gray(' - GitLab'));
|
|
138
|
+
if (platforms.jira)
|
|
139
|
+
console.log(chalk_1.default.gray(' - Jira'));
|
|
132
140
|
console.log();
|
|
133
141
|
return platforms;
|
|
134
142
|
};
|
|
@@ -197,6 +205,48 @@ const promptForADOToken = async () => {
|
|
|
197
205
|
}
|
|
198
206
|
throw new Error('Failed to get Azure DevOps token');
|
|
199
207
|
};
|
|
208
|
+
const promptForGitLabToken = async () => {
|
|
209
|
+
console.log(chalk_1.default.blue('\nGitLab Integration Setup'));
|
|
210
|
+
console.log('FRAIM requires a GitLab token for GitLab issue/repository MCP integration.\n');
|
|
211
|
+
const tokenResponse = await (0, prompts_1.default)({
|
|
212
|
+
type: 'password',
|
|
213
|
+
name: 'token',
|
|
214
|
+
message: 'Enter your GitLab token',
|
|
215
|
+
validate: (value) => {
|
|
216
|
+
if (!value)
|
|
217
|
+
return 'GitLab token is required';
|
|
218
|
+
if (!(0, token_validator_1.isValidTokenFormat)(value, 'gitlab'))
|
|
219
|
+
return 'Please enter a valid GitLab token (typically starts with glpat-)';
|
|
220
|
+
return true;
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
if (!tokenResponse.token) {
|
|
224
|
+
throw new Error('GitLab token is required');
|
|
225
|
+
}
|
|
226
|
+
console.log(chalk_1.default.green('GitLab token received\n'));
|
|
227
|
+
return tokenResponse.token;
|
|
228
|
+
};
|
|
229
|
+
const promptForJiraToken = async () => {
|
|
230
|
+
console.log(chalk_1.default.blue('\nJira Integration Setup'));
|
|
231
|
+
console.log('FRAIM requires a Jira API token for Jira MCP integration.\n');
|
|
232
|
+
const tokenResponse = await (0, prompts_1.default)({
|
|
233
|
+
type: 'password',
|
|
234
|
+
name: 'token',
|
|
235
|
+
message: 'Enter your Jira API token',
|
|
236
|
+
validate: (value) => {
|
|
237
|
+
if (!value)
|
|
238
|
+
return 'Jira token is required';
|
|
239
|
+
if (!(0, token_validator_1.isValidTokenFormat)(value, 'jira'))
|
|
240
|
+
return 'Please enter a valid Jira token';
|
|
241
|
+
return true;
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
if (!tokenResponse.token) {
|
|
245
|
+
throw new Error('Jira token is required');
|
|
246
|
+
}
|
|
247
|
+
console.log(chalk_1.default.green('Jira token received\n'));
|
|
248
|
+
return tokenResponse.token;
|
|
249
|
+
};
|
|
200
250
|
const saveGlobalConfig = (fraimKey, mode, tokens) => {
|
|
201
251
|
const globalConfigDir = path_1.default.join(os_1.default.homedir(), '.fraim');
|
|
202
252
|
const globalConfigPath = path_1.default.join(globalConfigDir, 'config.json');
|
|
@@ -235,7 +285,7 @@ const runSetup = async (options) => {
|
|
|
235
285
|
console.log(chalk_1.default.blue('🚀 Welcome to FRAIM! Let\'s get you set up.\n'));
|
|
236
286
|
// Determine if this is an update (adding platforms to existing setup)
|
|
237
287
|
const globalConfigPath = path_1.default.join(os_1.default.homedir(), '.fraim', 'config.json');
|
|
238
|
-
const isUpdate = fs_1.default.existsSync(globalConfigPath) && (options.github || options.ado);
|
|
288
|
+
const isUpdate = fs_1.default.existsSync(globalConfigPath) && (options.github || options.ado || options.gitlab || options.jira);
|
|
239
289
|
let fraimKey;
|
|
240
290
|
let mode;
|
|
241
291
|
const tokens = {};
|
|
@@ -252,9 +302,13 @@ const runSetup = async (options) => {
|
|
|
252
302
|
tokens.github = existingConfig.tokens.github;
|
|
253
303
|
if (existingConfig.tokens.ado)
|
|
254
304
|
tokens.ado = existingConfig.tokens.ado;
|
|
305
|
+
if (existingConfig.tokens.gitlab)
|
|
306
|
+
tokens.gitlab = existingConfig.tokens.gitlab;
|
|
307
|
+
if (existingConfig.tokens.jira)
|
|
308
|
+
tokens.jira = existingConfig.tokens.jira;
|
|
255
309
|
}
|
|
256
310
|
console.log(chalk_1.default.gray(` Current mode: ${mode}`));
|
|
257
|
-
console.log(chalk_1.default.gray(` Existing tokens: ${tokens.github ? 'GitHub
|
|
311
|
+
console.log(chalk_1.default.gray(` Existing tokens: ${tokens.github ? 'GitHub yes' : ''} ${tokens.ado ? 'ADO yes' : ''} ${tokens.gitlab ? 'GitLab yes' : ''} ${tokens.jira ? 'Jira yes' : ''}\n`));
|
|
258
312
|
}
|
|
259
313
|
catch (e) {
|
|
260
314
|
console.log(chalk_1.default.red('❌ Failed to read existing configuration'));
|
|
@@ -288,11 +342,15 @@ const runSetup = async (options) => {
|
|
|
288
342
|
if (mode === 'integrated') {
|
|
289
343
|
let needGitHub = options.github || false;
|
|
290
344
|
let needADO = options.ado || false;
|
|
345
|
+
let needGitLab = options.gitlab || false;
|
|
346
|
+
let needJira = options.jira || false;
|
|
291
347
|
// If no specific platform flags and not an update, ask user
|
|
292
|
-
if (!isUpdate && !needGitHub && !needADO) {
|
|
348
|
+
if (!isUpdate && !needGitHub && !needADO && !needGitLab && !needJira) {
|
|
293
349
|
const platforms = await promptForPlatforms();
|
|
294
350
|
needGitHub = platforms.github;
|
|
295
351
|
needADO = platforms.ado;
|
|
352
|
+
needGitLab = platforms.gitlab;
|
|
353
|
+
needJira = platforms.jira;
|
|
296
354
|
}
|
|
297
355
|
// Get GitHub token if needed
|
|
298
356
|
if (needGitHub && !tokens.github) {
|
|
@@ -336,11 +394,51 @@ const runSetup = async (options) => {
|
|
|
336
394
|
}
|
|
337
395
|
}
|
|
338
396
|
}
|
|
339
|
-
|
|
397
|
+
// Get GitLab token if needed
|
|
398
|
+
if (needGitLab && !tokens.gitlab) {
|
|
399
|
+
if (options.gitlabToken) {
|
|
400
|
+
if (!(0, token_validator_1.isValidTokenFormat)(options.gitlabToken, 'gitlab')) {
|
|
401
|
+
console.log(chalk_1.default.red('Invalid GitLab token format'));
|
|
402
|
+
process.exit(1);
|
|
403
|
+
}
|
|
404
|
+
tokens.gitlab = options.gitlabToken;
|
|
405
|
+
}
|
|
406
|
+
else {
|
|
407
|
+
try {
|
|
408
|
+
tokens.gitlab = await promptForGitLabToken();
|
|
409
|
+
}
|
|
410
|
+
catch (e) {
|
|
411
|
+
console.log(chalk_1.default.red('Failed to get GitLab token'));
|
|
412
|
+
process.exit(1);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
// Get Jira token if needed
|
|
417
|
+
if (needJira && !tokens.jira) {
|
|
418
|
+
if (options.jiraToken) {
|
|
419
|
+
if (!(0, token_validator_1.isValidTokenFormat)(options.jiraToken, 'jira')) {
|
|
420
|
+
console.log(chalk_1.default.red('Invalid Jira token format'));
|
|
421
|
+
process.exit(1);
|
|
422
|
+
}
|
|
423
|
+
tokens.jira = options.jiraToken;
|
|
424
|
+
}
|
|
425
|
+
else {
|
|
426
|
+
try {
|
|
427
|
+
tokens.jira = await promptForJiraToken();
|
|
428
|
+
}
|
|
429
|
+
catch (e) {
|
|
430
|
+
console.log(chalk_1.default.red('Failed to get Jira token'));
|
|
431
|
+
process.exit(1);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
if (!tokens.github && !tokens.ado && !tokens.gitlab && !tokens.jira) {
|
|
340
436
|
console.log(chalk_1.default.yellow('⚠️ No platform tokens configured.'));
|
|
341
437
|
console.log(chalk_1.default.gray(' You can add them later with:'));
|
|
342
438
|
console.log(chalk_1.default.cyan(' fraim setup --github'));
|
|
343
|
-
console.log(chalk_1.default.cyan(' fraim setup --ado
|
|
439
|
+
console.log(chalk_1.default.cyan(' fraim setup --ado'));
|
|
440
|
+
console.log(chalk_1.default.cyan(' fraim setup --gitlab'));
|
|
441
|
+
console.log(chalk_1.default.cyan(' fraim setup --jira\n'));
|
|
344
442
|
}
|
|
345
443
|
}
|
|
346
444
|
else {
|
|
@@ -354,13 +452,17 @@ const runSetup = async (options) => {
|
|
|
354
452
|
console.log(chalk_1.default.blue('\n🔌 Configuring MCP servers...'));
|
|
355
453
|
// For conversational mode, we still configure MCP but without GitHub token requirement
|
|
356
454
|
// The FRAIM MCP server works without GitHub token, other servers can be optional
|
|
357
|
-
const
|
|
358
|
-
|
|
455
|
+
const mcpTokens = {
|
|
456
|
+
github: tokens.github || '',
|
|
457
|
+
gitlab: tokens.gitlab || '',
|
|
458
|
+
jira: tokens.jira || ''
|
|
459
|
+
};
|
|
460
|
+
if (mode === 'conversational' && !mcpTokens.github && !mcpTokens.gitlab && !mcpTokens.jira) {
|
|
359
461
|
console.log(chalk_1.default.yellow('ℹ️ Conversational mode: Configuring MCP servers without GitHub integration'));
|
|
360
462
|
console.log(chalk_1.default.gray(' FRAIM workflows will work, but GitHub-specific features will be unavailable\n'));
|
|
361
463
|
}
|
|
362
464
|
try {
|
|
363
|
-
await (0, auto_mcp_setup_1.autoConfigureMCP)(fraimKey,
|
|
465
|
+
await (0, auto_mcp_setup_1.autoConfigureMCP)(fraimKey, mcpTokens, options.ide ? [options.ide] : undefined);
|
|
364
466
|
}
|
|
365
467
|
catch (e) {
|
|
366
468
|
console.log(chalk_1.default.yellow('⚠️ MCP configuration encountered issues'));
|
|
@@ -380,18 +482,22 @@ const runSetup = async (options) => {
|
|
|
380
482
|
console.log(chalk_1.default.green('\n🎯 Setup complete!'));
|
|
381
483
|
console.log(chalk_1.default.gray(` Mode: ${mode}`));
|
|
382
484
|
if (mode === 'integrated') {
|
|
383
|
-
console.log(chalk_1.default.gray(` Platforms: ${tokens.github ? 'GitHub
|
|
485
|
+
console.log(chalk_1.default.gray(` Platforms: ${tokens.github ? 'GitHub yes' : 'GitHub no'} ${tokens.ado ? 'ADO yes' : 'ADO no'} ${tokens.gitlab ? 'GitLab yes' : 'GitLab no'} ${tokens.jira ? 'Jira yes' : 'Jira no'}`));
|
|
384
486
|
}
|
|
385
487
|
console.log(chalk_1.default.cyan('\n📝 For future projects:'));
|
|
386
488
|
console.log(chalk_1.default.cyan(' 1. cd into any project directory'));
|
|
387
489
|
console.log(chalk_1.default.cyan(' 2. Run: fraim init-project'));
|
|
388
490
|
console.log(chalk_1.default.cyan(' 3. Ask your AI agent (Claude Desktop / Cowork, Cursor, etc.) "list fraim workflows"'));
|
|
389
|
-
if (mode === 'integrated' && (!tokens.github || !tokens.ado)) {
|
|
491
|
+
if (mode === 'integrated' && (!tokens.github || !tokens.ado || !tokens.gitlab || !tokens.jira)) {
|
|
390
492
|
console.log(chalk_1.default.gray('\n💡 To add more platforms later:'));
|
|
391
493
|
if (!tokens.github)
|
|
392
494
|
console.log(chalk_1.default.gray(' fraim setup --github'));
|
|
393
495
|
if (!tokens.ado)
|
|
394
496
|
console.log(chalk_1.default.gray(' fraim setup --ado'));
|
|
497
|
+
if (!tokens.gitlab)
|
|
498
|
+
console.log(chalk_1.default.gray(' fraim setup --gitlab'));
|
|
499
|
+
if (!tokens.jira)
|
|
500
|
+
console.log(chalk_1.default.gray(' fraim setup --jira'));
|
|
395
501
|
}
|
|
396
502
|
};
|
|
397
503
|
exports.runSetup = runSetup;
|
|
@@ -400,8 +506,12 @@ exports.setupCommand = new commander_1.Command('setup')
|
|
|
400
506
|
.option('--key <key>', 'FRAIM API key')
|
|
401
507
|
.option('--github-token <token>', 'GitHub Personal Access Token')
|
|
402
508
|
.option('--ado-token <token>', 'Azure DevOps Personal Access Token')
|
|
509
|
+
.option('--gitlab-token <token>', 'GitLab Personal Access Token')
|
|
510
|
+
.option('--jira-token <token>', 'Jira API token')
|
|
403
511
|
.option('--github', 'Add/update GitHub integration')
|
|
404
512
|
.option('--ado', 'Add/update Azure DevOps integration')
|
|
513
|
+
.option('--gitlab', 'Add/update GitLab integration')
|
|
514
|
+
.option('--jira', 'Add/update Jira integration')
|
|
405
515
|
.option('--all', 'Configure all detected IDEs (deprecated)')
|
|
406
516
|
.option('--ide <ides>', 'Configure specific IDEs (deprecated)')
|
|
407
517
|
.action(exports.runSetup);
|
|
@@ -12,7 +12,16 @@ const ide_detector_1 = require("./ide-detector");
|
|
|
12
12
|
const mcp_config_generator_1 = require("./mcp-config-generator");
|
|
13
13
|
const token_validator_1 = require("./token-validator");
|
|
14
14
|
const codex_local_config_1 = require("./codex-local-config");
|
|
15
|
-
const
|
|
15
|
+
const normalizePlatformTokens = (tokenInput) => {
|
|
16
|
+
if (!tokenInput)
|
|
17
|
+
return {};
|
|
18
|
+
if (typeof tokenInput === 'string') {
|
|
19
|
+
return tokenInput ? { github: tokenInput } : {};
|
|
20
|
+
}
|
|
21
|
+
return tokenInput;
|
|
22
|
+
};
|
|
23
|
+
const promptForIDESelection = async (detectedIDEs, tokenInput) => {
|
|
24
|
+
const tokens = normalizePlatformTokens(tokenInput);
|
|
16
25
|
console.log(chalk_1.default.green(`✅ Found ${detectedIDEs.length} IDEs that can be configured with FRAIM:\n`));
|
|
17
26
|
detectedIDEs.forEach((ide, index) => {
|
|
18
27
|
const configExists = fs_1.default.existsSync((0, ide_detector_1.expandPath)(ide.configPath));
|
|
@@ -24,8 +33,14 @@ const promptForIDESelection = async (detectedIDEs, githubToken) => {
|
|
|
24
33
|
console.log(chalk_1.default.blue('FRAIM will add these MCP servers to selected IDEs:'));
|
|
25
34
|
console.log(chalk_1.default.gray(' • fraim (required for FRAIM workflows)'));
|
|
26
35
|
console.log(chalk_1.default.gray(' • git (version control integration)'));
|
|
27
|
-
if (
|
|
28
|
-
console.log(chalk_1.default.gray('
|
|
36
|
+
if (tokens.github) {
|
|
37
|
+
console.log(chalk_1.default.gray(' - github (GitHub API access)'));
|
|
38
|
+
}
|
|
39
|
+
if (tokens.gitlab) {
|
|
40
|
+
console.log(chalk_1.default.gray(' - gitlab (GitLab API access)'));
|
|
41
|
+
}
|
|
42
|
+
if (tokens.jira) {
|
|
43
|
+
console.log(chalk_1.default.gray(' - jira (Jira issue tracking)'));
|
|
29
44
|
}
|
|
30
45
|
console.log(chalk_1.default.gray(' • playwright (browser automation)'));
|
|
31
46
|
console.log(chalk_1.default.yellow('\n💡 Existing MCP servers will be preserved - only missing servers will be added.'));
|
|
@@ -197,7 +212,8 @@ const validateSetupResults = async (configuredIDEs) => {
|
|
|
197
212
|
}
|
|
198
213
|
};
|
|
199
214
|
exports.validateSetupResults = validateSetupResults;
|
|
200
|
-
const configureIDEMCP = async (ide, fraimKey,
|
|
215
|
+
const configureIDEMCP = async (ide, fraimKey, tokenInput) => {
|
|
216
|
+
const tokens = normalizePlatformTokens(tokenInput);
|
|
201
217
|
const configPath = (0, ide_detector_1.expandPath)(ide.configPath);
|
|
202
218
|
console.log(chalk_1.default.blue(`🔧 Configuring ${ide.name}...`));
|
|
203
219
|
// Create backup
|
|
@@ -228,8 +244,8 @@ const configureIDEMCP = async (ide, fraimKey, githubToken) => {
|
|
|
228
244
|
existingTomlContent = fs_1.default.readFileSync(configPath, 'utf8');
|
|
229
245
|
console.log(chalk_1.default.gray(` 📋 Found existing TOML config`));
|
|
230
246
|
}
|
|
231
|
-
const newTomlContent = (0, mcp_config_generator_1.generateMCPConfig)(ide.configType, fraimKey,
|
|
232
|
-
const serversToAdd = ['fraim', 'git', 'github', 'playwright'];
|
|
247
|
+
const newTomlContent = (0, mcp_config_generator_1.generateMCPConfig)(ide.configType, fraimKey, tokens);
|
|
248
|
+
const serversToAdd = ['fraim', 'git', 'github', 'gitlab', 'jira', 'playwright'];
|
|
233
249
|
const mergeResult = (0, mcp_config_generator_1.mergeTomlMCPServers)(existingTomlContent, newTomlContent, serversToAdd);
|
|
234
250
|
fs_1.default.writeFileSync(configPath, mergeResult.content);
|
|
235
251
|
mergeResult.addedServers.forEach(server => {
|
|
@@ -244,7 +260,7 @@ const configureIDEMCP = async (ide, fraimKey, githubToken) => {
|
|
|
244
260
|
}
|
|
245
261
|
else {
|
|
246
262
|
// For JSON configs - intelligent merging
|
|
247
|
-
const newConfig = (0, mcp_config_generator_1.generateMCPConfig)(ide.configType, fraimKey,
|
|
263
|
+
const newConfig = (0, mcp_config_generator_1.generateMCPConfig)(ide.configType, fraimKey, tokens);
|
|
248
264
|
const newMCPServers = newConfig[serversKey] || newConfig.mcpServers || {};
|
|
249
265
|
// Merge MCP servers intelligently
|
|
250
266
|
const mergedMCPServers = { ...existingMCPServers };
|
|
@@ -280,7 +296,8 @@ const configureIDEMCP = async (ide, fraimKey, githubToken) => {
|
|
|
280
296
|
console.log(chalk_1.default.green(` ✅ ${status} local Codex config: ${localResult.path}`));
|
|
281
297
|
}
|
|
282
298
|
};
|
|
283
|
-
const autoConfigureMCP = async (fraimKey,
|
|
299
|
+
const autoConfigureMCP = async (fraimKey, tokenInput, selectedIDEs) => {
|
|
300
|
+
const tokens = normalizePlatformTokens(tokenInput);
|
|
284
301
|
const detectedIDEs = (0, ide_detector_1.detectInstalledIDEs)();
|
|
285
302
|
if (detectedIDEs.length === 0) {
|
|
286
303
|
console.log(chalk_1.default.yellow('⚠️ No supported IDEs detected.'));
|
|
@@ -312,7 +329,7 @@ const autoConfigureMCP = async (fraimKey, githubToken, selectedIDEs) => {
|
|
|
312
329
|
}
|
|
313
330
|
else {
|
|
314
331
|
// Interactive selection
|
|
315
|
-
idesToConfigure = await (0, exports.promptForIDESelection)(detectedIDEs,
|
|
332
|
+
idesToConfigure = await (0, exports.promptForIDESelection)(detectedIDEs, tokens);
|
|
316
333
|
}
|
|
317
334
|
if (idesToConfigure.length === 0) {
|
|
318
335
|
console.log(chalk_1.default.yellow('⚠️ No IDEs selected for configuration.'));
|
|
@@ -326,7 +343,7 @@ const autoConfigureMCP = async (fraimKey, githubToken, selectedIDEs) => {
|
|
|
326
343
|
};
|
|
327
344
|
for (const ide of idesToConfigure) {
|
|
328
345
|
try {
|
|
329
|
-
await configureIDEMCP(ide, fraimKey,
|
|
346
|
+
await configureIDEMCP(ide, fraimKey, tokens);
|
|
330
347
|
results.successful.push(ide.name);
|
|
331
348
|
}
|
|
332
349
|
catch (error) {
|
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.generateMCPConfig = exports.generateWindsurfMCPServers = exports.generateVSCodeMCPServers = exports.generateCodexMCPServers = exports.generateKiroMCPServers = exports.generateClaudeMCPServers = exports.generateStandardMCPServers = exports.mergeTomlMCPServers = exports.extractTomlMcpServerBlock = void 0;
|
|
4
|
+
const GITHUB_MCP_URL = 'https://api.githubcopilot.com/mcp/';
|
|
5
|
+
const GITLAB_MCP_URL = 'https://gitlab.com/api/v4/mcp';
|
|
6
|
+
const JIRA_MCP_URL = 'https://mcp.atlassian.com/v1/sse';
|
|
7
|
+
const normalizeTokens = (tokenInput) => {
|
|
8
|
+
if (!tokenInput)
|
|
9
|
+
return {};
|
|
10
|
+
if (typeof tokenInput === 'string') {
|
|
11
|
+
return tokenInput ? { github: tokenInput } : {};
|
|
12
|
+
}
|
|
13
|
+
return tokenInput;
|
|
14
|
+
};
|
|
4
15
|
const escapeTomlString = (value) => value.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
|
5
16
|
const findTomlServerBlockRange = (content, server) => {
|
|
6
17
|
const lines = content.split(/\r?\n/);
|
|
@@ -82,7 +93,8 @@ const mergeTomlMCPServers = (existingContent, generatedContent, servers) => {
|
|
|
82
93
|
};
|
|
83
94
|
};
|
|
84
95
|
exports.mergeTomlMCPServers = mergeTomlMCPServers;
|
|
85
|
-
const generateStandardMCPServers = (fraimKey,
|
|
96
|
+
const generateStandardMCPServers = (fraimKey, tokenInput) => {
|
|
97
|
+
const tokens = normalizeTokens(tokenInput);
|
|
86
98
|
const servers = {
|
|
87
99
|
git: {
|
|
88
100
|
command: "npx",
|
|
@@ -100,19 +112,35 @@ const generateStandardMCPServers = (fraimKey, githubToken) => {
|
|
|
100
112
|
}
|
|
101
113
|
}
|
|
102
114
|
};
|
|
103
|
-
|
|
104
|
-
if (githubToken) {
|
|
115
|
+
if (tokens.github) {
|
|
105
116
|
servers.github = {
|
|
106
|
-
serverUrl:
|
|
117
|
+
serverUrl: GITHUB_MCP_URL,
|
|
118
|
+
headers: {
|
|
119
|
+
Authorization: `Bearer ${tokens.github}`
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
if (tokens.gitlab) {
|
|
124
|
+
servers.gitlab = {
|
|
125
|
+
serverUrl: GITLAB_MCP_URL,
|
|
126
|
+
headers: {
|
|
127
|
+
Authorization: `Bearer ${tokens.gitlab}`
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
if (tokens.jira) {
|
|
132
|
+
servers.jira = {
|
|
133
|
+
serverUrl: JIRA_MCP_URL,
|
|
107
134
|
headers: {
|
|
108
|
-
Authorization: `Bearer ${
|
|
135
|
+
Authorization: `Bearer ${tokens.jira}`
|
|
109
136
|
}
|
|
110
137
|
};
|
|
111
138
|
}
|
|
112
139
|
return { mcpServers: servers };
|
|
113
140
|
};
|
|
114
141
|
exports.generateStandardMCPServers = generateStandardMCPServers;
|
|
115
|
-
const generateClaudeMCPServers = (fraimKey,
|
|
142
|
+
const generateClaudeMCPServers = (fraimKey, tokenInput) => {
|
|
143
|
+
const tokens = normalizeTokens(tokenInput);
|
|
116
144
|
const servers = {
|
|
117
145
|
git: {
|
|
118
146
|
command: "npx",
|
|
@@ -130,20 +158,38 @@ const generateClaudeMCPServers = (fraimKey, githubToken) => {
|
|
|
130
158
|
}
|
|
131
159
|
}
|
|
132
160
|
};
|
|
133
|
-
|
|
134
|
-
if (githubToken) {
|
|
161
|
+
if (tokens.github) {
|
|
135
162
|
servers.github = {
|
|
136
163
|
type: "http",
|
|
137
|
-
url:
|
|
164
|
+
url: GITHUB_MCP_URL,
|
|
138
165
|
headers: {
|
|
139
|
-
Authorization: `Bearer ${
|
|
166
|
+
Authorization: `Bearer ${tokens.github}`
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
if (tokens.gitlab) {
|
|
171
|
+
servers.gitlab = {
|
|
172
|
+
type: 'http',
|
|
173
|
+
url: GITLAB_MCP_URL,
|
|
174
|
+
headers: {
|
|
175
|
+
Authorization: `Bearer ${tokens.gitlab}`
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
if (tokens.jira) {
|
|
180
|
+
servers.jira = {
|
|
181
|
+
type: 'http',
|
|
182
|
+
url: JIRA_MCP_URL,
|
|
183
|
+
headers: {
|
|
184
|
+
Authorization: `Bearer ${tokens.jira}`
|
|
140
185
|
}
|
|
141
186
|
};
|
|
142
187
|
}
|
|
143
188
|
return { mcpServers: servers };
|
|
144
189
|
};
|
|
145
190
|
exports.generateClaudeMCPServers = generateClaudeMCPServers;
|
|
146
|
-
const generateKiroMCPServers = (fraimKey,
|
|
191
|
+
const generateKiroMCPServers = (fraimKey, tokenInput) => {
|
|
192
|
+
const tokens = normalizeTokens(tokenInput);
|
|
147
193
|
const servers = {
|
|
148
194
|
git: {
|
|
149
195
|
command: "npx",
|
|
@@ -161,36 +207,67 @@ const generateKiroMCPServers = (fraimKey, githubToken) => {
|
|
|
161
207
|
}
|
|
162
208
|
}
|
|
163
209
|
};
|
|
164
|
-
|
|
165
|
-
if (githubToken) {
|
|
210
|
+
if (tokens.github) {
|
|
166
211
|
servers.github = {
|
|
167
|
-
url:
|
|
212
|
+
url: GITHUB_MCP_URL,
|
|
213
|
+
headers: {
|
|
214
|
+
Authorization: `Bearer ${tokens.github}`
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
if (tokens.gitlab) {
|
|
219
|
+
servers.gitlab = {
|
|
220
|
+
url: GITLAB_MCP_URL,
|
|
221
|
+
headers: {
|
|
222
|
+
Authorization: `Bearer ${tokens.gitlab}`
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
if (tokens.jira) {
|
|
227
|
+
servers.jira = {
|
|
228
|
+
url: JIRA_MCP_URL,
|
|
168
229
|
headers: {
|
|
169
|
-
Authorization: `Bearer ${
|
|
230
|
+
Authorization: `Bearer ${tokens.jira}`
|
|
170
231
|
}
|
|
171
232
|
};
|
|
172
233
|
}
|
|
173
234
|
return { mcpServers: servers };
|
|
174
235
|
};
|
|
175
236
|
exports.generateKiroMCPServers = generateKiroMCPServers;
|
|
176
|
-
const generateCodexMCPServers = (fraimKey,
|
|
237
|
+
const generateCodexMCPServers = (fraimKey, tokenInput) => {
|
|
238
|
+
const tokens = normalizeTokens(tokenInput);
|
|
177
239
|
const escapedFraimKey = escapeTomlString(fraimKey);
|
|
178
|
-
const escapedGithubToken = escapeTomlString(githubToken);
|
|
179
240
|
let config = `
|
|
180
241
|
[mcp_servers.git]
|
|
181
242
|
command = "npx"
|
|
182
|
-
args = ["-y", "@cyanheads/git-mcp-server"]
|
|
243
|
+
args = ["-y", "@cyanheads/git-mcp-server"]
|
|
183
244
|
`;
|
|
184
|
-
|
|
185
|
-
|
|
245
|
+
if (tokens.github) {
|
|
246
|
+
const escapedGithubToken = escapeTomlString(tokens.github);
|
|
186
247
|
config += `
|
|
187
248
|
[mcp_servers.github]
|
|
188
|
-
url = "
|
|
249
|
+
url = "${GITHUB_MCP_URL}"
|
|
189
250
|
http_headers = { Authorization = "Bearer ${escapedGithubToken}" }
|
|
190
251
|
`;
|
|
191
252
|
}
|
|
192
|
-
|
|
193
|
-
|
|
253
|
+
if (tokens.gitlab) {
|
|
254
|
+
const escapedGitLabToken = escapeTomlString(tokens.gitlab);
|
|
255
|
+
config += `
|
|
256
|
+
[mcp_servers.gitlab]
|
|
257
|
+
url = "${GITLAB_MCP_URL}"
|
|
258
|
+
http_headers = { Authorization = "Bearer ${escapedGitLabToken}" }
|
|
259
|
+
`;
|
|
260
|
+
}
|
|
261
|
+
if (tokens.jira) {
|
|
262
|
+
const escapedJiraToken = escapeTomlString(tokens.jira);
|
|
263
|
+
config += `
|
|
264
|
+
[mcp_servers.jira]
|
|
265
|
+
url = "${JIRA_MCP_URL}"
|
|
266
|
+
http_headers = { Authorization = "Bearer ${escapedJiraToken}" }
|
|
267
|
+
`;
|
|
268
|
+
}
|
|
269
|
+
config += `
|
|
270
|
+
[mcp_servers.playwright]
|
|
194
271
|
command = "npx"
|
|
195
272
|
args = ["-y", "@playwright/mcp"]
|
|
196
273
|
|
|
@@ -205,7 +282,8 @@ FRAIM_REMOTE_URL = "https://fraim.wellnessatwork.me"
|
|
|
205
282
|
};
|
|
206
283
|
exports.generateCodexMCPServers = generateCodexMCPServers;
|
|
207
284
|
/** VS Code uses "servers" key and requires type: "stdio" for stdio servers */
|
|
208
|
-
const generateVSCodeMCPServers = (fraimKey,
|
|
285
|
+
const generateVSCodeMCPServers = (fraimKey, tokenInput) => {
|
|
286
|
+
const tokens = normalizeTokens(tokenInput);
|
|
209
287
|
const servers = {
|
|
210
288
|
git: {
|
|
211
289
|
type: 'stdio',
|
|
@@ -226,19 +304,38 @@ const generateVSCodeMCPServers = (fraimKey, githubToken) => {
|
|
|
226
304
|
}
|
|
227
305
|
}
|
|
228
306
|
};
|
|
229
|
-
if (
|
|
307
|
+
if (tokens.github) {
|
|
230
308
|
servers.github = {
|
|
231
309
|
type: 'http',
|
|
232
|
-
url:
|
|
310
|
+
url: GITHUB_MCP_URL,
|
|
233
311
|
headers: {
|
|
234
|
-
Authorization: `Bearer ${
|
|
312
|
+
Authorization: `Bearer ${tokens.github}`
|
|
313
|
+
}
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
if (tokens.gitlab) {
|
|
317
|
+
servers.gitlab = {
|
|
318
|
+
type: 'http',
|
|
319
|
+
url: GITLAB_MCP_URL,
|
|
320
|
+
headers: {
|
|
321
|
+
Authorization: `Bearer ${tokens.gitlab}`
|
|
322
|
+
}
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
if (tokens.jira) {
|
|
326
|
+
servers.jira = {
|
|
327
|
+
type: 'http',
|
|
328
|
+
url: JIRA_MCP_URL,
|
|
329
|
+
headers: {
|
|
330
|
+
Authorization: `Bearer ${tokens.jira}`
|
|
235
331
|
}
|
|
236
332
|
};
|
|
237
333
|
}
|
|
238
334
|
return { servers };
|
|
239
335
|
};
|
|
240
336
|
exports.generateVSCodeMCPServers = generateVSCodeMCPServers;
|
|
241
|
-
const generateWindsurfMCPServers = (fraimKey,
|
|
337
|
+
const generateWindsurfMCPServers = (fraimKey, tokenInput) => {
|
|
338
|
+
const tokens = normalizeTokens(tokenInput);
|
|
242
339
|
const servers = {
|
|
243
340
|
git: {
|
|
244
341
|
command: "npx",
|
|
@@ -256,33 +353,50 @@ const generateWindsurfMCPServers = (fraimKey, githubToken) => {
|
|
|
256
353
|
}
|
|
257
354
|
}
|
|
258
355
|
};
|
|
259
|
-
|
|
260
|
-
if (githubToken) {
|
|
356
|
+
if (tokens.github) {
|
|
261
357
|
servers.github = {
|
|
262
358
|
command: "npx",
|
|
263
|
-
args: ["-y", "@modelcontextprotocol/server-fetch",
|
|
359
|
+
args: ["-y", "@modelcontextprotocol/server-fetch", GITHUB_MCP_URL],
|
|
360
|
+
env: {
|
|
361
|
+
GITHUB_TOKEN: tokens.github
|
|
362
|
+
}
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
if (tokens.gitlab) {
|
|
366
|
+
servers.gitlab = {
|
|
367
|
+
command: 'npx',
|
|
368
|
+
args: ['-y', '@modelcontextprotocol/server-fetch', GITLAB_MCP_URL],
|
|
369
|
+
env: {
|
|
370
|
+
GITLAB_TOKEN: tokens.gitlab
|
|
371
|
+
}
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
if (tokens.jira) {
|
|
375
|
+
servers.jira = {
|
|
376
|
+
command: 'npx',
|
|
377
|
+
args: ['-y', '@modelcontextprotocol/server-fetch', JIRA_MCP_URL],
|
|
264
378
|
env: {
|
|
265
|
-
|
|
379
|
+
JIRA_TOKEN: tokens.jira
|
|
266
380
|
}
|
|
267
381
|
};
|
|
268
382
|
}
|
|
269
383
|
return { mcpServers: servers };
|
|
270
384
|
};
|
|
271
385
|
exports.generateWindsurfMCPServers = generateWindsurfMCPServers;
|
|
272
|
-
const generateMCPConfig = (configType, fraimKey,
|
|
386
|
+
const generateMCPConfig = (configType, fraimKey, tokenInput) => {
|
|
273
387
|
switch (configType) {
|
|
274
388
|
case 'standard':
|
|
275
|
-
return (0, exports.generateStandardMCPServers)(fraimKey,
|
|
389
|
+
return (0, exports.generateStandardMCPServers)(fraimKey, tokenInput);
|
|
276
390
|
case 'claude':
|
|
277
|
-
return (0, exports.generateClaudeMCPServers)(fraimKey,
|
|
391
|
+
return (0, exports.generateClaudeMCPServers)(fraimKey, tokenInput);
|
|
278
392
|
case 'kiro':
|
|
279
|
-
return (0, exports.generateKiroMCPServers)(fraimKey,
|
|
393
|
+
return (0, exports.generateKiroMCPServers)(fraimKey, tokenInput);
|
|
280
394
|
case 'vscode':
|
|
281
|
-
return (0, exports.generateVSCodeMCPServers)(fraimKey,
|
|
395
|
+
return (0, exports.generateVSCodeMCPServers)(fraimKey, tokenInput);
|
|
282
396
|
case 'codex':
|
|
283
|
-
return (0, exports.generateCodexMCPServers)(fraimKey,
|
|
397
|
+
return (0, exports.generateCodexMCPServers)(fraimKey, tokenInput);
|
|
284
398
|
case 'windsurf':
|
|
285
|
-
return (0, exports.generateWindsurfMCPServers)(fraimKey,
|
|
399
|
+
return (0, exports.generateWindsurfMCPServers)(fraimKey, tokenInput);
|
|
286
400
|
default:
|
|
287
401
|
throw new Error(`Unsupported config type: ${configType}`);
|
|
288
402
|
}
|
|
@@ -44,6 +44,14 @@ const isValidTokenFormat = (token, type) => {
|
|
|
44
44
|
if (type === 'github') {
|
|
45
45
|
return token.startsWith('ghp_') || token.startsWith('github_pat_');
|
|
46
46
|
}
|
|
47
|
+
if (type === 'gitlab') {
|
|
48
|
+
// GitLab PATs commonly use glpat- prefix, but keep a permissive fallback for self-managed/token variants.
|
|
49
|
+
return token.startsWith('glpat-') || token.length >= 20;
|
|
50
|
+
}
|
|
51
|
+
if (type === 'jira') {
|
|
52
|
+
// Jira API tokens do not have a stable prefix; enforce a minimum length.
|
|
53
|
+
return token.length >= 20;
|
|
54
|
+
}
|
|
47
55
|
return false;
|
|
48
56
|
};
|
|
49
57
|
exports.isValidTokenFormat = isValidTokenFormat;
|
|
@@ -78,6 +78,15 @@ function detectPlatformFromUrl(url) {
|
|
|
78
78
|
confidence: 'high'
|
|
79
79
|
};
|
|
80
80
|
}
|
|
81
|
+
// GitLab detection
|
|
82
|
+
if (normalizedUrl.includes('gitlab.com') || normalizedUrl.includes('gitlab.')) {
|
|
83
|
+
const repository = extractGitLabInfo(url);
|
|
84
|
+
return {
|
|
85
|
+
provider: 'gitlab',
|
|
86
|
+
repository,
|
|
87
|
+
confidence: 'high'
|
|
88
|
+
};
|
|
89
|
+
}
|
|
81
90
|
// ADO detection
|
|
82
91
|
if (normalizedUrl.includes('dev.azure.com') ||
|
|
83
92
|
normalizedUrl.includes('visualstudio.com') ||
|
|
@@ -117,6 +126,35 @@ function extractGitHubInfo(url) {
|
|
|
117
126
|
defaultBranch: 'main'
|
|
118
127
|
};
|
|
119
128
|
}
|
|
129
|
+
/**
|
|
130
|
+
* Extract GitLab repository information from URL
|
|
131
|
+
*/
|
|
132
|
+
function extractGitLabInfo(url) {
|
|
133
|
+
// GitLab URL formats:
|
|
134
|
+
// HTTPS: https://gitlab.com/group/subgroup/repo.git
|
|
135
|
+
// SSH: git@gitlab.com:group/subgroup/repo.git
|
|
136
|
+
const match = url.match(/gitlab[^\/:]*[\/:]([^?\s#]+?)(?:\.git)?$/i);
|
|
137
|
+
if (match) {
|
|
138
|
+
const projectPath = match[1].replace(/^\/+/, '');
|
|
139
|
+
const segments = projectPath.split('/').filter(Boolean);
|
|
140
|
+
const name = segments[segments.length - 1];
|
|
141
|
+
const namespace = segments.slice(0, -1).join('/');
|
|
142
|
+
return {
|
|
143
|
+
provider: 'gitlab',
|
|
144
|
+
namespace: namespace || undefined,
|
|
145
|
+
name,
|
|
146
|
+
projectPath,
|
|
147
|
+
url,
|
|
148
|
+
defaultBranch: 'main'
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
// Fallback - just mark as GitLab
|
|
152
|
+
return {
|
|
153
|
+
provider: 'gitlab',
|
|
154
|
+
url,
|
|
155
|
+
defaultBranch: 'main'
|
|
156
|
+
};
|
|
157
|
+
}
|
|
120
158
|
/**
|
|
121
159
|
* Extract ADO repository information from URL
|
|
122
160
|
*/
|
|
@@ -178,6 +216,13 @@ function validateRepositoryConfig(config) {
|
|
|
178
216
|
if (!config.name)
|
|
179
217
|
errors.push('ADO repository name is required');
|
|
180
218
|
}
|
|
219
|
+
if (config.provider === 'gitlab') {
|
|
220
|
+
const hasProjectPath = typeof config.projectPath === 'string' && config.projectPath.length > 0;
|
|
221
|
+
const hasNamespaceAndName = typeof config.namespace === 'string' && config.namespace.length > 0 && typeof config.name === 'string' && config.name.length > 0;
|
|
222
|
+
if (!hasProjectPath && !hasNamespaceAndName) {
|
|
223
|
+
errors.push('GitLab repository requires projectPath or namespace + name');
|
|
224
|
+
}
|
|
225
|
+
}
|
|
181
226
|
return {
|
|
182
227
|
valid: errors.length === 0,
|
|
183
228
|
errors
|
|
@@ -95,7 +95,7 @@ function getRepositoryInfo(config) {
|
|
|
95
95
|
// Prefer new repository config
|
|
96
96
|
if (config.repository) {
|
|
97
97
|
return {
|
|
98
|
-
owner: config.repository.owner || config.repository.organization,
|
|
98
|
+
owner: config.repository.owner || config.repository.organization || config.repository.namespace,
|
|
99
99
|
name: config.repository.name,
|
|
100
100
|
provider: config.repository.provider,
|
|
101
101
|
defaultBranch: config.repository.defaultBranch
|
|
@@ -7,7 +7,11 @@ exports.detectProvider = detectProvider;
|
|
|
7
7
|
function detectProvider(url) {
|
|
8
8
|
if (!url)
|
|
9
9
|
return 'github';
|
|
10
|
-
|
|
10
|
+
const normalized = url.toLowerCase();
|
|
11
|
+
if (normalized.includes('gitlab.com') || normalized.includes('gitlab.')) {
|
|
12
|
+
return 'gitlab';
|
|
13
|
+
}
|
|
14
|
+
if (normalized.includes('dev.azure.com') || normalized.includes('visualstudio.com')) {
|
|
11
15
|
return 'ado';
|
|
12
16
|
}
|
|
13
17
|
return 'github';
|
|
@@ -422,46 +422,69 @@ class FraimLocalMCPServer {
|
|
|
422
422
|
catch (error) {
|
|
423
423
|
// If git command fails, construct URL from config if available.
|
|
424
424
|
const repositoryConfig = this.config?.repository;
|
|
425
|
-
if (repositoryConfig?.
|
|
425
|
+
if (repositoryConfig?.url) {
|
|
426
|
+
repoUrl = repositoryConfig.url;
|
|
427
|
+
this.log(`Constructed repo URL from config: ${repoUrl}`);
|
|
428
|
+
}
|
|
429
|
+
else if (repositoryConfig?.provider === 'github' && repositoryConfig.owner && repositoryConfig.name) {
|
|
426
430
|
repoUrl = `https://github.com/${repositoryConfig.owner}/${repositoryConfig.name}.git`;
|
|
427
|
-
this.log(
|
|
431
|
+
this.log(`Constructed repo URL from config: ${repoUrl}`);
|
|
428
432
|
}
|
|
429
433
|
else if (repositoryConfig?.provider === 'ado' &&
|
|
430
434
|
repositoryConfig.organization &&
|
|
431
435
|
repositoryConfig.project &&
|
|
432
436
|
repositoryConfig.name) {
|
|
433
437
|
repoUrl = `https://dev.azure.com/${repositoryConfig.organization}/${repositoryConfig.project}/_git/${repositoryConfig.name}`;
|
|
434
|
-
this.log(
|
|
438
|
+
this.log(`Constructed repo URL from config: ${repoUrl}`);
|
|
439
|
+
}
|
|
440
|
+
else if (repositoryConfig?.provider === 'gitlab') {
|
|
441
|
+
const projectPath = repositoryConfig.projectPath
|
|
442
|
+
|| (repositoryConfig.namespace && repositoryConfig.name
|
|
443
|
+
? `${repositoryConfig.namespace}/${repositoryConfig.name}`
|
|
444
|
+
: null);
|
|
445
|
+
if (projectPath) {
|
|
446
|
+
repoUrl = `https://gitlab.com/${projectPath}.git`;
|
|
447
|
+
this.log(`Constructed repo URL from config: ${repoUrl}`);
|
|
448
|
+
}
|
|
435
449
|
}
|
|
436
450
|
}
|
|
437
451
|
if (!repoUrl) {
|
|
438
|
-
this.log('
|
|
452
|
+
this.log('No git repository found and no config available');
|
|
439
453
|
return null;
|
|
440
454
|
}
|
|
441
|
-
// Parse
|
|
455
|
+
// Parse repository identity from URL
|
|
442
456
|
let name = '';
|
|
457
|
+
let owner = '';
|
|
443
458
|
let organization = '';
|
|
444
459
|
let project = '';
|
|
445
|
-
let
|
|
446
|
-
|
|
447
|
-
const
|
|
448
|
-
const adoMatch = repoUrl.match(/dev\.azure\.com\/([^\/]+)\/([^\/]+)\/_git\/([^\/]+)/);
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
460
|
+
let namespace = '';
|
|
461
|
+
let projectPath = '';
|
|
462
|
+
const githubMatch = repoUrl.match(/github\.com[\/:]([^\/]+)\/([^\/\.]+)/i);
|
|
463
|
+
const adoMatch = repoUrl.match(/dev\.azure\.com\/([^\/]+)\/([^\/]+)\/_git\/([^\/]+)/i);
|
|
464
|
+
const gitlabMatch = repoUrl.match(/gitlab[^\/:]*[\/:]([^?\s#]+?)(?:\.git)?$/i);
|
|
465
|
+
if (githubMatch) {
|
|
466
|
+
owner = githubMatch[1];
|
|
467
|
+
name = githubMatch[2];
|
|
452
468
|
}
|
|
453
469
|
else if (adoMatch) {
|
|
454
|
-
// Azure DevOps: organization and project are separate fields
|
|
455
470
|
organization = adoMatch[1];
|
|
456
471
|
project = adoMatch[2];
|
|
457
472
|
name = adoMatch[3];
|
|
458
473
|
}
|
|
474
|
+
else if (gitlabMatch) {
|
|
475
|
+
projectPath = gitlabMatch[1].replace(/^\/+/, '');
|
|
476
|
+
const segments = projectPath.split('/').filter(Boolean);
|
|
477
|
+
name = segments[segments.length - 1] || '';
|
|
478
|
+
namespace = segments.slice(0, -1).join('/');
|
|
479
|
+
}
|
|
459
480
|
else if (this.config?.repository) {
|
|
460
481
|
// Fall back to config if URL parsing fails
|
|
461
482
|
owner = this.config.repository.owner || '';
|
|
462
483
|
name = this.config.repository.name || '';
|
|
463
484
|
organization = this.config.repository.organization || '';
|
|
464
485
|
project = this.config.repository.project || '';
|
|
486
|
+
namespace = this.config.repository.namespace || '';
|
|
487
|
+
projectPath = this.config.repository.projectPath || (namespace && name ? `${namespace}/${name}` : '');
|
|
465
488
|
}
|
|
466
489
|
// Get current branch
|
|
467
490
|
let branch = '';
|
|
@@ -483,6 +506,8 @@ class FraimLocalMCPServer {
|
|
|
483
506
|
name: name || 'unknown',
|
|
484
507
|
...(organization && { organization }),
|
|
485
508
|
...(project && { project }),
|
|
509
|
+
...(namespace && { namespace }),
|
|
510
|
+
...(projectPath && { projectPath }),
|
|
486
511
|
...(branch && { branch })
|
|
487
512
|
};
|
|
488
513
|
if (owner) {
|
|
@@ -491,8 +516,8 @@ class FraimLocalMCPServer {
|
|
|
491
516
|
this.repoInfo = repoInfo;
|
|
492
517
|
const repoLabel = this.repoInfo.owner
|
|
493
518
|
? `${this.repoInfo.owner}/${this.repoInfo.name}`
|
|
494
|
-
: this.repoInfo.name;
|
|
495
|
-
this.log(
|
|
519
|
+
: this.repoInfo.projectPath || this.repoInfo.name;
|
|
520
|
+
this.log(`Detected repo info: ${repoLabel}`);
|
|
496
521
|
return this.repoInfo;
|
|
497
522
|
}
|
|
498
523
|
catch (error) {
|
|
@@ -1055,6 +1080,44 @@ class FraimLocalMCPServer {
|
|
|
1055
1080
|
request.params.arguments.sessionId = requestSessionId;
|
|
1056
1081
|
}
|
|
1057
1082
|
}
|
|
1083
|
+
normalizeRepoContext(repo) {
|
|
1084
|
+
if (!repo || typeof repo !== 'object')
|
|
1085
|
+
return repo;
|
|
1086
|
+
const normalized = { ...repo };
|
|
1087
|
+
const detectedProvider = (0, provider_utils_1.detectProvider)(normalized.url);
|
|
1088
|
+
if (typeof detectedProvider === 'string' && detectedProvider.length > 0) {
|
|
1089
|
+
normalized.provider = detectedProvider;
|
|
1090
|
+
}
|
|
1091
|
+
if (normalized.provider !== 'gitlab')
|
|
1092
|
+
return normalized;
|
|
1093
|
+
if (!normalized.projectPath && typeof normalized.url === 'string') {
|
|
1094
|
+
const gitlabMatch = normalized.url.match(/gitlab[^\/:]*[\/:]([^?\s#]+?)(?:\.git)?$/i);
|
|
1095
|
+
if (gitlabMatch) {
|
|
1096
|
+
normalized.projectPath = gitlabMatch[1].replace(/^\/+/, '');
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
if (!normalized.name && typeof normalized.projectPath === 'string') {
|
|
1100
|
+
const segments = normalized.projectPath.split('/').filter(Boolean);
|
|
1101
|
+
normalized.name = segments[segments.length - 1] || normalized.name;
|
|
1102
|
+
normalized.namespace = normalized.namespace || segments.slice(0, -1).join('/');
|
|
1103
|
+
}
|
|
1104
|
+
if (normalized.projectPath || !normalized.namespace || !normalized.name)
|
|
1105
|
+
return normalized;
|
|
1106
|
+
normalized.projectPath = `${normalized.namespace}/${normalized.name}`;
|
|
1107
|
+
return normalized;
|
|
1108
|
+
}
|
|
1109
|
+
normalizeIssueTrackingContext(issueTracking) {
|
|
1110
|
+
if (!issueTracking || typeof issueTracking !== 'object')
|
|
1111
|
+
return issueTracking;
|
|
1112
|
+
if (issueTracking.provider !== 'gitlab')
|
|
1113
|
+
return issueTracking;
|
|
1114
|
+
if (issueTracking.projectPath || !issueTracking.namespace || !issueTracking.name)
|
|
1115
|
+
return issueTracking;
|
|
1116
|
+
return {
|
|
1117
|
+
...issueTracking,
|
|
1118
|
+
projectPath: `${issueTracking.namespace}/${issueTracking.name}`
|
|
1119
|
+
};
|
|
1120
|
+
}
|
|
1058
1121
|
/**
|
|
1059
1122
|
* Proxy request to remote FRAIM server
|
|
1060
1123
|
*/
|
|
@@ -1083,6 +1146,7 @@ class FraimLocalMCPServer {
|
|
|
1083
1146
|
...args.repo, // Agent values as fallback
|
|
1084
1147
|
...detectedRepo // Detected values override (always win)
|
|
1085
1148
|
};
|
|
1149
|
+
args.repo = this.normalizeRepoContext(args.repo);
|
|
1086
1150
|
const repoLabel = args.repo.owner ? `${args.repo.owner}/${args.repo.name}` : args.repo.name;
|
|
1087
1151
|
this.log(`[req:${requestId}] Auto-detected and injected repo info: ${repoLabel}`);
|
|
1088
1152
|
}
|
|
@@ -1100,6 +1164,7 @@ class FraimLocalMCPServer {
|
|
|
1100
1164
|
}
|
|
1101
1165
|
};
|
|
1102
1166
|
}
|
|
1167
|
+
args.repo = this.normalizeRepoContext(args.repo);
|
|
1103
1168
|
const repoLabel = args.repo.owner ? `${args.repo.owner}/${args.repo.name}` : args.repo.name;
|
|
1104
1169
|
this.log(`[req:${requestId}] Using agent-provided repo info: ${repoLabel}`);
|
|
1105
1170
|
}
|
|
@@ -1109,8 +1174,12 @@ class FraimLocalMCPServer {
|
|
|
1109
1174
|
...(args.issueTracking || {}),
|
|
1110
1175
|
...configuredIssueTracking
|
|
1111
1176
|
};
|
|
1177
|
+
args.issueTracking = this.normalizeIssueTrackingContext(args.issueTracking);
|
|
1112
1178
|
this.log(`[req:${requestId}] Applied issueTracking context: ${args.issueTracking.provider || 'unknown'}`);
|
|
1113
1179
|
}
|
|
1180
|
+
else if (args.issueTracking) {
|
|
1181
|
+
args.issueTracking = this.normalizeIssueTrackingContext(args.issueTracking);
|
|
1182
|
+
}
|
|
1114
1183
|
const runtimeRepoContext = {
|
|
1115
1184
|
...args.repo,
|
|
1116
1185
|
...(args.issueTracking ? { issueTracking: args.issueTracking } : {})
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fraim-framework",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.78",
|
|
4
4
|
"description": "FRAIM v2: Framework for Rigor-based AI Management - Transform from solo developer to AI manager orchestrating production-ready code with enterprise-grade discipline",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|