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.
@@ -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, githubToken) => {
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, githubToken);
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, githubToken);
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, githubToken) => {
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 (githubToken) {
212
- console.log(chalk_1.default.gray(' github (GitHub API access)'));
213
+ if (tokens?.github) {
214
+ console.log(chalk_1.default.gray(' - github (GitHub API access)'));
213
215
  }
214
- console.log(chalk_1.default.gray(' • playwright (browser automation)'));
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, githubToken);
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, githubToken || '');
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
- provider: 'ado',
80
- organization: detection.repository.organization,
81
- project: detection.repository.project,
82
- name: detection.repository.name
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(' GitHub'));
133
+ console.log(chalk_1.default.gray(' - GitHub'));
130
134
  if (platforms.ado)
131
- console.log(chalk_1.default.gray(' Azure DevOps'));
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 ' : ''} ${tokens.ado ? 'ADO ' : ''}\n`));
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
- if (!tokens.github && !tokens.ado) {
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\n'));
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 mcpGitHubToken = tokens.github || '';
358
- if (mode === 'conversational' && !mcpGitHubToken) {
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, mcpGitHubToken, options.ide ? [options.ide] : undefined);
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 ' : 'GitHub '} ${tokens.ado ? 'ADO ' : 'ADO '}`));
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 promptForIDESelection = async (detectedIDEs, githubToken) => {
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 (githubToken) {
28
- console.log(chalk_1.default.gray(' github (GitHub API access)'));
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, githubToken) => {
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, githubToken);
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, githubToken);
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, githubToken, selectedIDEs) => {
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, githubToken);
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, githubToken);
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, githubToken) => {
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
- // Only add GitHub server if token is provided
104
- if (githubToken) {
115
+ if (tokens.github) {
105
116
  servers.github = {
106
- serverUrl: "https://api.githubcopilot.com/mcp/",
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 ${githubToken}`
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, githubToken) => {
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
- // Only add GitHub server if token is provided
134
- if (githubToken) {
161
+ if (tokens.github) {
135
162
  servers.github = {
136
163
  type: "http",
137
- url: "https://api.githubcopilot.com/mcp/",
164
+ url: GITHUB_MCP_URL,
138
165
  headers: {
139
- Authorization: `Bearer ${githubToken}`
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, githubToken) => {
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
- // Only add GitHub server if token is provided
165
- if (githubToken) {
210
+ if (tokens.github) {
166
211
  servers.github = {
167
- url: "https://api.githubcopilot.com/mcp/",
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 ${githubToken}`
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, githubToken) => {
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
- // Only add GitHub server if token is provided
185
- if (githubToken) {
245
+ if (tokens.github) {
246
+ const escapedGithubToken = escapeTomlString(tokens.github);
186
247
  config += `
187
248
  [mcp_servers.github]
188
- url = "https://api.githubcopilot.com/mcp/"
249
+ url = "${GITHUB_MCP_URL}"
189
250
  http_headers = { Authorization = "Bearer ${escapedGithubToken}" }
190
251
  `;
191
252
  }
192
- config += `
193
- [mcp_servers.playwright]
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, githubToken) => {
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 (githubToken) {
307
+ if (tokens.github) {
230
308
  servers.github = {
231
309
  type: 'http',
232
- url: 'https://api.githubcopilot.com/mcp/',
310
+ url: GITHUB_MCP_URL,
233
311
  headers: {
234
- Authorization: `Bearer ${githubToken}`
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, githubToken) => {
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
- // Only add GitHub server if token is provided
260
- if (githubToken) {
356
+ if (tokens.github) {
261
357
  servers.github = {
262
358
  command: "npx",
263
- args: ["-y", "@modelcontextprotocol/server-fetch", "https://api.githubcopilot.com/mcp/"],
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
- GITHUB_TOKEN: githubToken
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, githubToken) => {
386
+ const generateMCPConfig = (configType, fraimKey, tokenInput) => {
273
387
  switch (configType) {
274
388
  case 'standard':
275
- return (0, exports.generateStandardMCPServers)(fraimKey, githubToken);
389
+ return (0, exports.generateStandardMCPServers)(fraimKey, tokenInput);
276
390
  case 'claude':
277
- return (0, exports.generateClaudeMCPServers)(fraimKey, githubToken);
391
+ return (0, exports.generateClaudeMCPServers)(fraimKey, tokenInput);
278
392
  case 'kiro':
279
- return (0, exports.generateKiroMCPServers)(fraimKey, githubToken);
393
+ return (0, exports.generateKiroMCPServers)(fraimKey, tokenInput);
280
394
  case 'vscode':
281
- return (0, exports.generateVSCodeMCPServers)(fraimKey, githubToken);
395
+ return (0, exports.generateVSCodeMCPServers)(fraimKey, tokenInput);
282
396
  case 'codex':
283
- return (0, exports.generateCodexMCPServers)(fraimKey, githubToken);
397
+ return (0, exports.generateCodexMCPServers)(fraimKey, tokenInput);
284
398
  case 'windsurf':
285
- return (0, exports.generateWindsurfMCPServers)(fraimKey, githubToken);
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
- if (url.includes('dev.azure.com') || url.includes('visualstudio.com')) {
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?.provider === 'github' && repositoryConfig.owner && repositoryConfig.name) {
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(`📋 Constructed repo URL from config: ${repoUrl}`);
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(`📋 Constructed repo URL from config: ${repoUrl}`);
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('⚠️ No git repository found and no config available');
452
+ this.log('No git repository found and no config available');
439
453
  return null;
440
454
  }
441
- // Parse owner and name from URL
455
+ // Parse repository identity from URL
442
456
  let name = '';
457
+ let owner = '';
443
458
  let organization = '';
444
459
  let project = '';
445
- let owner = '';
446
- // Handle GitHub URLs: https://github.com/owner/repo.git or git@github.com:owner/repo.git
447
- const githubHttpsMatch = repoUrl.match(/github\.com[\/:]([^\/]+)\/([^\/\.]+)/);
448
- const adoMatch = repoUrl.match(/dev\.azure\.com\/([^\/]+)\/([^\/]+)\/_git\/([^\/]+)/);
449
- if (githubHttpsMatch) {
450
- owner = githubHttpsMatch[1];
451
- name = githubHttpsMatch[2];
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(`✅ Detected repo info: ${repoLabel}`);
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.77",
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": {