fraim-framework 2.0.77 → 2.0.80

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.
@@ -9,12 +9,12 @@ const chalk_1 = __importDefault(require("chalk"));
9
9
  const prompts_1 = __importDefault(require("prompts"));
10
10
  const fs_1 = __importDefault(require("fs"));
11
11
  const path_1 = __importDefault(require("path"));
12
- const os_1 = __importDefault(require("os"));
13
12
  const token_validator_1 = require("../setup/token-validator");
14
13
  const auto_mcp_setup_1 = require("../setup/auto-mcp-setup");
15
14
  const version_utils_1 = require("../utils/version-utils");
16
15
  const platform_detection_1 = require("../utils/platform-detection");
17
16
  const init_project_1 = require("./init-project");
17
+ const script_sync_utils_1 = require("../utils/script-sync-utils");
18
18
  const promptForFraimKey = async () => {
19
19
  console.log(chalk_1.default.blue('šŸ”‘ FRAIM Key Setup'));
20
20
  console.log('FRAIM requires a valid API key to access workflows and features.\n');
@@ -78,7 +78,12 @@ const promptForMode = async () => {
78
78
  {
79
79
  title: 'Integrated Mode',
80
80
  value: 'integrated',
81
- description: 'Full platform integration with GitHub/ADO for issue tracking and PRs'
81
+ description: 'Single platform for both code hosting and issue tracking'
82
+ },
83
+ {
84
+ title: 'Split Mode',
85
+ value: 'split',
86
+ description: 'Separate platforms for code hosting and issue tracking (e.g., GitHub + Jira)'
82
87
  },
83
88
  {
84
89
  title: 'Conversational Mode',
@@ -97,6 +102,11 @@ const promptForMode = async () => {
97
102
  console.log(chalk_1.default.gray(' You can use FRAIM workflows without platform credentials.'));
98
103
  console.log(chalk_1.default.gray(' Platform features (issues, PRs) will be unavailable.\n'));
99
104
  }
105
+ else if (response.mode === 'split') {
106
+ console.log(chalk_1.default.blue('\nāœ“ Split mode selected'));
107
+ console.log(chalk_1.default.gray(' You can use different platforms for code hosting and issue tracking.'));
108
+ console.log(chalk_1.default.gray(' For example: GitHub for code + Jira for issues.\n'));
109
+ }
100
110
  else {
101
111
  console.log(chalk_1.default.blue('\nāœ“ Integrated mode selected'));
102
112
  console.log(chalk_1.default.gray(' Full platform integration will be available.\n'));
@@ -112,26 +122,77 @@ const promptForPlatforms = async () => {
112
122
  message: 'Select platforms (Space to select, Enter to confirm)',
113
123
  choices: [
114
124
  { title: 'GitHub', value: 'github', selected: true },
115
- { title: 'Azure DevOps', value: 'ado', selected: false }
125
+ { title: 'Azure DevOps', value: 'ado', selected: false },
126
+ { title: 'GitLab', value: 'gitlab', selected: false },
127
+ { title: 'Jira', value: 'jira', selected: false }
116
128
  ],
117
129
  min: 1
118
130
  });
119
131
  if (!response.platforms || response.platforms.length === 0) {
120
132
  console.log(chalk_1.default.yellow('\nā„¹ļø No platforms selected, defaulting to GitHub'));
121
- return { github: true, ado: false };
133
+ return { github: true, ado: false, gitlab: false, jira: false };
122
134
  }
123
135
  const platforms = {
124
136
  github: response.platforms.includes('github'),
125
- ado: response.platforms.includes('ado')
137
+ ado: response.platforms.includes('ado'),
138
+ gitlab: response.platforms.includes('gitlab'),
139
+ jira: response.platforms.includes('jira')
126
140
  };
127
141
  console.log(chalk_1.default.blue('\nāœ“ Selected platforms:'));
128
142
  if (platforms.github)
129
- console.log(chalk_1.default.gray(' • GitHub'));
143
+ console.log(chalk_1.default.gray(' - GitHub'));
130
144
  if (platforms.ado)
131
- console.log(chalk_1.default.gray(' • Azure DevOps'));
145
+ console.log(chalk_1.default.gray(' - Azure DevOps'));
146
+ if (platforms.gitlab)
147
+ console.log(chalk_1.default.gray(' - GitLab'));
148
+ if (platforms.jira)
149
+ console.log(chalk_1.default.gray(' - Jira'));
132
150
  console.log();
133
151
  return platforms;
134
152
  };
153
+ const promptForCodeRepository = async () => {
154
+ console.log(chalk_1.default.blue('\nšŸ“¦ Code Repository Platform'));
155
+ console.log(chalk_1.default.gray('Select the platform for code hosting:\n'));
156
+ const response = await (0, prompts_1.default)({
157
+ type: 'select',
158
+ name: 'platform',
159
+ message: 'Select code repository platform',
160
+ choices: [
161
+ { title: 'GitHub', value: 'github' },
162
+ { title: 'Azure DevOps', value: 'ado' },
163
+ { title: 'GitLab', value: 'gitlab' }
164
+ ],
165
+ initial: 0
166
+ });
167
+ if (!response.platform) {
168
+ console.log(chalk_1.default.yellow('\nā„¹ļø No platform selected, defaulting to GitHub'));
169
+ return 'github';
170
+ }
171
+ console.log(chalk_1.default.blue(`\nāœ“ Code repository: ${response.platform === 'github' ? 'GitHub' : response.platform === 'ado' ? 'Azure DevOps' : 'GitLab'}\n`));
172
+ return response.platform;
173
+ };
174
+ const promptForIssueTracking = async () => {
175
+ console.log(chalk_1.default.blue('\nšŸŽ« Issue Tracking Platform'));
176
+ console.log(chalk_1.default.gray('Select the platform for issue tracking:\n'));
177
+ const response = await (0, prompts_1.default)({
178
+ type: 'select',
179
+ name: 'platform',
180
+ message: 'Select issue tracking platform',
181
+ choices: [
182
+ { title: 'GitHub', value: 'github' },
183
+ { title: 'Azure DevOps', value: 'ado' },
184
+ { title: 'GitLab', value: 'gitlab' },
185
+ { title: 'Jira', value: 'jira' }
186
+ ],
187
+ initial: 0
188
+ });
189
+ if (!response.platform) {
190
+ console.log(chalk_1.default.yellow('\nā„¹ļø No platform selected, defaulting to GitHub'));
191
+ return 'github';
192
+ }
193
+ console.log(chalk_1.default.blue(`\nāœ“ Issue tracking: ${response.platform === 'github' ? 'GitHub' : response.platform === 'ado' ? 'Azure DevOps' : response.platform === 'gitlab' ? 'GitLab' : 'Jira'}\n`));
194
+ return response.platform;
195
+ };
135
196
  const promptForADOToken = async () => {
136
197
  console.log(chalk_1.default.blue('\nšŸ”§ Azure DevOps Integration Setup'));
137
198
  console.log('FRAIM requires an Azure DevOps Personal Access Token for ADO integration.\n');
@@ -197,8 +258,92 @@ const promptForADOToken = async () => {
197
258
  }
198
259
  throw new Error('Failed to get Azure DevOps token');
199
260
  };
200
- const saveGlobalConfig = (fraimKey, mode, tokens) => {
201
- const globalConfigDir = path_1.default.join(os_1.default.homedir(), '.fraim');
261
+ const promptForGitLabToken = async () => {
262
+ console.log(chalk_1.default.blue('\nGitLab Integration Setup'));
263
+ console.log('FRAIM requires a GitLab token for GitLab issue/repository MCP integration.\n');
264
+ const tokenResponse = await (0, prompts_1.default)({
265
+ type: 'password',
266
+ name: 'token',
267
+ message: 'Enter your GitLab token',
268
+ validate: (value) => {
269
+ if (!value)
270
+ return 'GitLab token is required';
271
+ if (!(0, token_validator_1.isValidTokenFormat)(value, 'gitlab'))
272
+ return 'Please enter a valid GitLab token (typically starts with glpat-)';
273
+ return true;
274
+ }
275
+ });
276
+ if (!tokenResponse.token) {
277
+ throw new Error('GitLab token is required');
278
+ }
279
+ console.log(chalk_1.default.green('GitLab token received\n'));
280
+ return tokenResponse.token;
281
+ };
282
+ const promptForJiraCredentials = async () => {
283
+ console.log(chalk_1.default.blue('\nJira Integration Setup'));
284
+ console.log('FRAIM requires Jira credentials for MCP integration.\n');
285
+ // Prompt for base URL
286
+ const urlResponse = await (0, prompts_1.default)({
287
+ type: 'text',
288
+ name: 'baseUrl',
289
+ message: 'Enter your Jira base URL (e.g., https://company.atlassian.net)',
290
+ validate: (value) => {
291
+ if (!value)
292
+ return 'Jira base URL is required';
293
+ // Basic URL validation
294
+ try {
295
+ new URL(value.startsWith('http') ? value : `https://${value}`);
296
+ return true;
297
+ }
298
+ catch {
299
+ return 'Please enter a valid URL';
300
+ }
301
+ }
302
+ });
303
+ if (!urlResponse.baseUrl) {
304
+ throw new Error('Jira base URL is required');
305
+ }
306
+ // Prompt for email
307
+ const emailResponse = await (0, prompts_1.default)({
308
+ type: 'text',
309
+ name: 'email',
310
+ message: 'Enter your Jira account email',
311
+ validate: (value) => {
312
+ if (!value)
313
+ return 'Jira email is required';
314
+ if (!value.includes('@'))
315
+ return 'Please enter a valid email address';
316
+ return true;
317
+ }
318
+ });
319
+ if (!emailResponse.email) {
320
+ throw new Error('Jira email is required');
321
+ }
322
+ // Prompt for API token
323
+ const tokenResponse = await (0, prompts_1.default)({
324
+ type: 'password',
325
+ name: 'token',
326
+ message: 'Enter your Jira API token',
327
+ validate: (value) => {
328
+ if (!value)
329
+ return 'Jira API token is required';
330
+ if (!(0, token_validator_1.isValidTokenFormat)(value, 'jira'))
331
+ return 'Please enter a valid Jira token';
332
+ return true;
333
+ }
334
+ });
335
+ if (!tokenResponse.token) {
336
+ throw new Error('Jira API token is required');
337
+ }
338
+ console.log(chalk_1.default.green('Jira credentials received\n'));
339
+ return {
340
+ baseUrl: urlResponse.baseUrl,
341
+ email: emailResponse.email,
342
+ token: tokenResponse.token
343
+ };
344
+ };
345
+ const saveGlobalConfig = (fraimKey, mode, tokens, jiraConfig) => {
346
+ const globalConfigDir = (0, script_sync_utils_1.getUserFraimDir)();
202
347
  const globalConfigPath = path_1.default.join(globalConfigDir, 'config.json');
203
348
  if (!fs_1.default.existsSync(globalConfigDir)) {
204
349
  fs_1.default.mkdirSync(globalConfigDir, { recursive: true });
@@ -222,6 +367,10 @@ const saveGlobalConfig = (fraimKey, mode, tokens) => {
222
367
  ...(existingConfig.tokens || {}),
223
368
  ...tokens
224
369
  },
370
+ jiraConfig: jiraConfig && (jiraConfig.baseUrl || jiraConfig.email) ? {
371
+ ...(existingConfig.jiraConfig || {}),
372
+ ...jiraConfig
373
+ } : existingConfig.jiraConfig,
225
374
  configuredAt: new Date().toISOString(),
226
375
  userPreferences: {
227
376
  autoSync: true,
@@ -234,11 +383,12 @@ const saveGlobalConfig = (fraimKey, mode, tokens) => {
234
383
  const runSetup = async (options) => {
235
384
  console.log(chalk_1.default.blue('šŸš€ Welcome to FRAIM! Let\'s get you set up.\n'));
236
385
  // Determine if this is an update (adding platforms to existing setup)
237
- 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);
386
+ const globalConfigPath = path_1.default.join((0, script_sync_utils_1.getUserFraimDir)(), 'config.json');
387
+ const isUpdate = fs_1.default.existsSync(globalConfigPath) && (options.github || options.ado || options.gitlab || options.jira);
239
388
  let fraimKey;
240
389
  let mode;
241
390
  const tokens = {};
391
+ let jiraConfig = {};
242
392
  if (isUpdate) {
243
393
  // Update existing setup - add platforms
244
394
  console.log(chalk_1.default.blue('šŸ“ Updating existing FRAIM configuration...\n'));
@@ -252,9 +402,13 @@ const runSetup = async (options) => {
252
402
  tokens.github = existingConfig.tokens.github;
253
403
  if (existingConfig.tokens.ado)
254
404
  tokens.ado = existingConfig.tokens.ado;
405
+ if (existingConfig.tokens.gitlab)
406
+ tokens.gitlab = existingConfig.tokens.gitlab;
407
+ if (existingConfig.tokens.jira)
408
+ tokens.jira = existingConfig.tokens.jira;
255
409
  }
256
410
  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`));
411
+ 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
412
  }
259
413
  catch (e) {
260
414
  console.log(chalk_1.default.red('āŒ Failed to read existing configuration'));
@@ -288,11 +442,15 @@ const runSetup = async (options) => {
288
442
  if (mode === 'integrated') {
289
443
  let needGitHub = options.github || false;
290
444
  let needADO = options.ado || false;
445
+ let needGitLab = options.gitlab || false;
446
+ let needJira = options.jira || false;
291
447
  // If no specific platform flags and not an update, ask user
292
- if (!isUpdate && !needGitHub && !needADO) {
448
+ if (!isUpdate && !needGitHub && !needADO && !needGitLab && !needJira) {
293
449
  const platforms = await promptForPlatforms();
294
450
  needGitHub = platforms.github;
295
451
  needADO = platforms.ado;
452
+ needGitLab = platforms.gitlab;
453
+ needJira = platforms.jira;
296
454
  }
297
455
  // Get GitHub token if needed
298
456
  if (needGitHub && !tokens.github) {
@@ -336,31 +494,249 @@ const runSetup = async (options) => {
336
494
  }
337
495
  }
338
496
  }
339
- if (!tokens.github && !tokens.ado) {
497
+ // Get GitLab token if needed
498
+ if (needGitLab && !tokens.gitlab) {
499
+ if (options.gitlabToken) {
500
+ if (!(0, token_validator_1.isValidTokenFormat)(options.gitlabToken, 'gitlab')) {
501
+ console.log(chalk_1.default.red('Invalid GitLab token format'));
502
+ process.exit(1);
503
+ }
504
+ tokens.gitlab = options.gitlabToken;
505
+ }
506
+ else {
507
+ try {
508
+ tokens.gitlab = await promptForGitLabToken();
509
+ }
510
+ catch (e) {
511
+ console.log(chalk_1.default.red('Failed to get GitLab token'));
512
+ process.exit(1);
513
+ }
514
+ }
515
+ }
516
+ // Get Jira credentials if needed
517
+ if (needJira && !tokens.jira) {
518
+ if (options.jiraToken) {
519
+ if (!(0, token_validator_1.isValidTokenFormat)(options.jiraToken, 'jira')) {
520
+ console.log(chalk_1.default.red('Invalid Jira token format'));
521
+ process.exit(1);
522
+ }
523
+ tokens.jira = options.jiraToken;
524
+ // Use provided URL and email if available, otherwise prompt
525
+ if (options.jiraUrl && options.jiraEmail) {
526
+ jiraConfig.baseUrl = options.jiraUrl;
527
+ jiraConfig.email = options.jiraEmail;
528
+ console.log(chalk_1.default.green('āœ… Jira credentials received\n'));
529
+ }
530
+ else {
531
+ console.log(chalk_1.default.yellow('āš ļø Jira token provided but missing URL or email'));
532
+ const jiraCredentials = await promptForJiraCredentials();
533
+ jiraConfig.baseUrl = jiraCredentials.baseUrl;
534
+ jiraConfig.email = jiraCredentials.email;
535
+ }
536
+ }
537
+ else {
538
+ try {
539
+ const jiraCredentials = await promptForJiraCredentials();
540
+ tokens.jira = jiraCredentials.token;
541
+ jiraConfig.baseUrl = jiraCredentials.baseUrl;
542
+ jiraConfig.email = jiraCredentials.email;
543
+ }
544
+ catch (e) {
545
+ console.log(chalk_1.default.red('Failed to get Jira credentials'));
546
+ process.exit(1);
547
+ }
548
+ }
549
+ }
550
+ if (!tokens.github && !tokens.ado && !tokens.gitlab && !tokens.jira) {
340
551
  console.log(chalk_1.default.yellow('āš ļø No platform tokens configured.'));
341
552
  console.log(chalk_1.default.gray(' You can add them later with:'));
342
553
  console.log(chalk_1.default.cyan(' fraim setup --github'));
343
- console.log(chalk_1.default.cyan(' fraim setup --ado\n'));
554
+ console.log(chalk_1.default.cyan(' fraim setup --ado'));
555
+ console.log(chalk_1.default.cyan(' fraim setup --gitlab'));
556
+ console.log(chalk_1.default.cyan(' fraim setup --jira\n'));
557
+ }
558
+ }
559
+ else if (mode === 'split') {
560
+ // Split mode: separate platforms for code repository and issue tracking
561
+ console.log(chalk_1.default.blue('\nšŸ”€ Split Mode Configuration'));
562
+ console.log(chalk_1.default.gray('Configure separate platforms for code hosting and issue tracking.\n'));
563
+ // Get code repository platform
564
+ const codeRepoPlatform = await promptForCodeRepository();
565
+ // Get code repository token
566
+ if (codeRepoPlatform === 'github' && !tokens.github) {
567
+ if (options.githubToken) {
568
+ if (!(0, token_validator_1.isValidTokenFormat)(options.githubToken, 'github')) {
569
+ console.log(chalk_1.default.red('āŒ Invalid GitHub token format'));
570
+ process.exit(1);
571
+ }
572
+ console.log(chalk_1.default.blue('šŸ” Validating GitHub token...'));
573
+ const isValid = await (0, token_validator_1.validateGitHubToken)(options.githubToken);
574
+ if (!isValid) {
575
+ console.log(chalk_1.default.red('āŒ Invalid GitHub token'));
576
+ process.exit(1);
577
+ }
578
+ tokens.github = options.githubToken;
579
+ console.log(chalk_1.default.green('āœ… GitHub token validated\n'));
580
+ }
581
+ else {
582
+ try {
583
+ tokens.github = await (0, auto_mcp_setup_1.promptForGitHubToken)();
584
+ }
585
+ catch (e) {
586
+ process.exit(1);
587
+ }
588
+ }
589
+ }
590
+ else if (codeRepoPlatform === 'ado' && !tokens.ado) {
591
+ if (options.adoToken) {
592
+ tokens.ado = options.adoToken;
593
+ console.log(chalk_1.default.green('āœ… Azure DevOps token received\n'));
594
+ }
595
+ else {
596
+ try {
597
+ tokens.ado = await promptForADOToken();
598
+ }
599
+ catch (e) {
600
+ console.log(chalk_1.default.red('āŒ Failed to get Azure DevOps token'));
601
+ process.exit(1);
602
+ }
603
+ }
604
+ }
605
+ else if (codeRepoPlatform === 'gitlab' && !tokens.gitlab) {
606
+ if (options.gitlabToken) {
607
+ if (!(0, token_validator_1.isValidTokenFormat)(options.gitlabToken, 'gitlab')) {
608
+ console.log(chalk_1.default.red('Invalid GitLab token format'));
609
+ process.exit(1);
610
+ }
611
+ tokens.gitlab = options.gitlabToken;
612
+ }
613
+ else {
614
+ try {
615
+ tokens.gitlab = await promptForGitLabToken();
616
+ }
617
+ catch (e) {
618
+ console.log(chalk_1.default.red('Failed to get GitLab token'));
619
+ process.exit(1);
620
+ }
621
+ }
622
+ }
623
+ // Get issue tracking platform
624
+ const issueTrackingPlatform = await promptForIssueTracking();
625
+ // Get issue tracking token (if different from code repo)
626
+ if (issueTrackingPlatform === 'github' && !tokens.github) {
627
+ if (options.githubToken) {
628
+ if (!(0, token_validator_1.isValidTokenFormat)(options.githubToken, 'github')) {
629
+ console.log(chalk_1.default.red('āŒ Invalid GitHub token format'));
630
+ process.exit(1);
631
+ }
632
+ console.log(chalk_1.default.blue('šŸ” Validating GitHub token...'));
633
+ const isValid = await (0, token_validator_1.validateGitHubToken)(options.githubToken);
634
+ if (!isValid) {
635
+ console.log(chalk_1.default.red('āŒ Invalid GitHub token'));
636
+ process.exit(1);
637
+ }
638
+ tokens.github = options.githubToken;
639
+ console.log(chalk_1.default.green('āœ… GitHub token validated\n'));
640
+ }
641
+ else {
642
+ try {
643
+ tokens.github = await (0, auto_mcp_setup_1.promptForGitHubToken)();
644
+ }
645
+ catch (e) {
646
+ process.exit(1);
647
+ }
648
+ }
649
+ }
650
+ else if (issueTrackingPlatform === 'ado' && !tokens.ado) {
651
+ if (options.adoToken) {
652
+ tokens.ado = options.adoToken;
653
+ console.log(chalk_1.default.green('āœ… Azure DevOps token received\n'));
654
+ }
655
+ else {
656
+ try {
657
+ tokens.ado = await promptForADOToken();
658
+ }
659
+ catch (e) {
660
+ console.log(chalk_1.default.red('āŒ Failed to get Azure DevOps token'));
661
+ process.exit(1);
662
+ }
663
+ }
664
+ }
665
+ else if (issueTrackingPlatform === 'gitlab' && !tokens.gitlab) {
666
+ if (options.gitlabToken) {
667
+ if (!(0, token_validator_1.isValidTokenFormat)(options.gitlabToken, 'gitlab')) {
668
+ console.log(chalk_1.default.red('Invalid GitLab token format'));
669
+ process.exit(1);
670
+ }
671
+ tokens.gitlab = options.gitlabToken;
672
+ }
673
+ else {
674
+ try {
675
+ tokens.gitlab = await promptForGitLabToken();
676
+ }
677
+ catch (e) {
678
+ console.log(chalk_1.default.red('Failed to get GitLab token'));
679
+ process.exit(1);
680
+ }
681
+ }
682
+ }
683
+ else if (issueTrackingPlatform === 'jira' && !tokens.jira) {
684
+ if (options.jiraToken) {
685
+ if (!(0, token_validator_1.isValidTokenFormat)(options.jiraToken, 'jira')) {
686
+ console.log(chalk_1.default.red('Invalid Jira token format'));
687
+ process.exit(1);
688
+ }
689
+ tokens.jira = options.jiraToken;
690
+ // Use provided URL and email if available, otherwise prompt
691
+ if (options.jiraUrl && options.jiraEmail) {
692
+ jiraConfig.baseUrl = options.jiraUrl;
693
+ jiraConfig.email = options.jiraEmail;
694
+ console.log(chalk_1.default.green('āœ… Jira credentials received\n'));
695
+ }
696
+ else {
697
+ console.log(chalk_1.default.yellow('āš ļø Jira token provided but missing URL or email'));
698
+ const jiraCredentials = await promptForJiraCredentials();
699
+ jiraConfig.baseUrl = jiraCredentials.baseUrl;
700
+ jiraConfig.email = jiraCredentials.email;
701
+ }
702
+ }
703
+ else {
704
+ try {
705
+ const jiraCredentials = await promptForJiraCredentials();
706
+ tokens.jira = jiraCredentials.token;
707
+ jiraConfig.baseUrl = jiraCredentials.baseUrl;
708
+ jiraConfig.email = jiraCredentials.email;
709
+ }
710
+ catch (e) {
711
+ console.log(chalk_1.default.red('Failed to get Jira credentials'));
712
+ process.exit(1);
713
+ }
714
+ }
344
715
  }
716
+ console.log(chalk_1.default.green(`\nāœ… Split mode configured: ${codeRepoPlatform} (code) + ${issueTrackingPlatform} (issues)\n`));
345
717
  }
346
718
  else {
347
719
  console.log(chalk_1.default.gray('ā„¹ļø Conversational mode: No platform tokens needed\n'));
348
720
  }
349
721
  // Save global configuration
350
722
  console.log(chalk_1.default.blue('šŸ’¾ Saving global configuration...'));
351
- saveGlobalConfig(fraimKey, mode, tokens);
723
+ saveGlobalConfig(fraimKey, mode, tokens, jiraConfig);
352
724
  // Configure MCP servers (only on initial setup)
353
725
  if (!isUpdate) {
354
726
  console.log(chalk_1.default.blue('\nšŸ”Œ Configuring MCP servers...'));
355
727
  // For conversational mode, we still configure MCP but without GitHub token requirement
356
728
  // The FRAIM MCP server works without GitHub token, other servers can be optional
357
- const mcpGitHubToken = tokens.github || '';
358
- if (mode === 'conversational' && !mcpGitHubToken) {
729
+ const mcpTokens = {
730
+ github: tokens.github || '',
731
+ gitlab: tokens.gitlab || '',
732
+ jira: tokens.jira || ''
733
+ };
734
+ if (mode === 'conversational' && !mcpTokens.github && !mcpTokens.gitlab && !mcpTokens.jira) {
359
735
  console.log(chalk_1.default.yellow('ā„¹ļø Conversational mode: Configuring MCP servers without GitHub integration'));
360
736
  console.log(chalk_1.default.gray(' FRAIM workflows will work, but GitHub-specific features will be unavailable\n'));
361
737
  }
362
738
  try {
363
- await (0, auto_mcp_setup_1.autoConfigureMCP)(fraimKey, mcpGitHubToken, options.ide ? [options.ide] : undefined);
739
+ await (0, auto_mcp_setup_1.autoConfigureMCP)(fraimKey, mcpTokens, options.ide ? [options.ide] : undefined, jiraConfig);
364
740
  }
365
741
  catch (e) {
366
742
  console.log(chalk_1.default.yellow('āš ļø MCP configuration encountered issues'));
@@ -380,18 +756,22 @@ const runSetup = async (options) => {
380
756
  console.log(chalk_1.default.green('\nšŸŽÆ Setup complete!'));
381
757
  console.log(chalk_1.default.gray(` Mode: ${mode}`));
382
758
  if (mode === 'integrated') {
383
- console.log(chalk_1.default.gray(` Platforms: ${tokens.github ? 'GitHub āœ“' : 'GitHub āœ—'} ${tokens.ado ? 'ADO āœ“' : 'ADO āœ—'}`));
759
+ 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
760
  }
385
761
  console.log(chalk_1.default.cyan('\nšŸ“ For future projects:'));
386
762
  console.log(chalk_1.default.cyan(' 1. cd into any project directory'));
387
763
  console.log(chalk_1.default.cyan(' 2. Run: fraim init-project'));
388
764
  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)) {
765
+ if (mode === 'integrated' && (!tokens.github || !tokens.ado || !tokens.gitlab || !tokens.jira)) {
390
766
  console.log(chalk_1.default.gray('\nšŸ’” To add more platforms later:'));
391
767
  if (!tokens.github)
392
768
  console.log(chalk_1.default.gray(' fraim setup --github'));
393
769
  if (!tokens.ado)
394
770
  console.log(chalk_1.default.gray(' fraim setup --ado'));
771
+ if (!tokens.gitlab)
772
+ console.log(chalk_1.default.gray(' fraim setup --gitlab'));
773
+ if (!tokens.jira)
774
+ console.log(chalk_1.default.gray(' fraim setup --jira'));
395
775
  }
396
776
  };
397
777
  exports.runSetup = runSetup;
@@ -400,8 +780,14 @@ exports.setupCommand = new commander_1.Command('setup')
400
780
  .option('--key <key>', 'FRAIM API key')
401
781
  .option('--github-token <token>', 'GitHub Personal Access Token')
402
782
  .option('--ado-token <token>', 'Azure DevOps Personal Access Token')
783
+ .option('--gitlab-token <token>', 'GitLab Personal Access Token')
784
+ .option('--jira-token <token>', 'Jira API token')
785
+ .option('--jira-url <url>', 'Jira base URL (e.g., https://company.atlassian.net)')
786
+ .option('--jira-email <email>', 'Jira account email')
403
787
  .option('--github', 'Add/update GitHub integration')
404
788
  .option('--ado', 'Add/update Azure DevOps integration')
789
+ .option('--gitlab', 'Add/update GitLab integration')
790
+ .option('--jira', 'Add/update Jira integration')
405
791
  .option('--all', 'Configure all detected IDEs (deprecated)')
406
792
  .option('--ide <ides>', 'Configure specific IDEs (deprecated)')
407
793
  .action(exports.runSetup);
@@ -8,8 +8,8 @@ const commander_1 = require("commander");
8
8
  const chalk_1 = __importDefault(require("chalk"));
9
9
  const fs_1 = __importDefault(require("fs"));
10
10
  const path_1 = __importDefault(require("path"));
11
- const os_1 = __importDefault(require("os"));
12
11
  const ide_detector_1 = require("../setup/ide-detector");
12
+ const script_sync_utils_1 = require("../utils/script-sync-utils");
13
13
  const testIDEConfig = async (ide) => {
14
14
  const result = {
15
15
  ide: ide.name,
@@ -57,7 +57,7 @@ const testIDEConfig = async (ide) => {
57
57
  return result;
58
58
  };
59
59
  const checkGlobalSetup = () => {
60
- const globalConfigPath = path_1.default.join(os_1.default.homedir(), '.fraim', 'config.json');
60
+ const globalConfigPath = path_1.default.join((0, script_sync_utils_1.getUserFraimDir)(), 'config.json');
61
61
  return fs_1.default.existsSync(globalConfigPath);
62
62
  };
63
63
  const runTestMCP = async () => {