fraim-framework 2.0.78 → 2.0.81

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -233,6 +233,34 @@ npm install -g fraim-framework
233
233
  fraim setup # Complete setup with IDE configuration
234
234
  ```
235
235
 
236
+ The setup command supports three modes:
237
+
238
+ **Conversational Mode**: AI workflows only, no platform integration required
239
+ ```bash
240
+ fraim setup --key=<your-fraim-key>
241
+ # Select "Conversational Mode" when prompted
242
+ ```
243
+
244
+ **Integrated Mode**: Single platform for both code hosting and issue tracking
245
+ ```bash
246
+ fraim setup --key=<your-fraim-key>
247
+ # Select "Integrated Mode" when prompted
248
+ # Choose platform: GitHub, Azure DevOps, or GitLab
249
+ ```
250
+
251
+ **Split Mode**: Separate platforms for code hosting and issue tracking
252
+ ```bash
253
+ fraim setup --key=<your-fraim-key>
254
+ # Select "Split Mode" when prompted
255
+ # Choose code repository platform: GitHub, Azure DevOps, or GitLab
256
+ # Choose issue tracking platform: GitHub, Azure DevOps, GitLab, or Jira
257
+ ```
258
+
259
+ Common Split mode combinations:
260
+ - GitHub (code) + Jira (issues)
261
+ - GitLab (code) + Jira (issues)
262
+ - Azure DevOps (code) + GitHub (issues)
263
+
236
264
  ### **šŸ”§ Additional Commands**
237
265
 
238
266
  After initial setup, you can use these commands:
@@ -244,6 +272,12 @@ fraim add-ide --ide antigravity # Configure Gemini Antigravity
244
272
  fraim add-ide --all # Configure all detected IDEs
245
273
  fraim add-ide --list # List supported IDEs
246
274
 
275
+ # Add platform integrations to existing setup
276
+ fraim setup --github # Add GitHub integration
277
+ fraim setup --ado # Add Azure DevOps integration
278
+ fraim setup --gitlab # Add GitLab integration
279
+ fraim setup --jira # Add Jira integration
280
+
247
281
  # Project initialization
248
282
  fraim init-project # Initialize FRAIM in current project
249
283
 
@@ -255,7 +289,51 @@ fraim doctor # Diagnose configuration issues
255
289
  fraim sync # Sync latest workflows and rules
256
290
  ```
257
291
 
258
- **šŸ’” Pro Tip**: Use `fraim add-ide` when you install a new IDE after initial setup. It reuses your existing FRAIM and GitHub keys, making it much faster than running full setup again.
292
+ **šŸ’” Pro Tip**: Use `fraim add-ide` when you install a new IDE after initial setup. It reuses your existing FRAIM and platform tokens, making it much faster than running full setup again.
293
+
294
+ ### **šŸ”§ Jira Integration Setup**
295
+
296
+ FRAIM uses the official Model Context Protocol (MCP) server for Jira integration. The setup command automatically configures the correct format.
297
+
298
+ **Jira API Token Requirements**:
299
+ 1. Go to https://id.atlassian.com/manage-profile/security/api-tokens
300
+ 2. Click "Create API token"
301
+ 3. Give it a name (e.g., "FRAIM Integration")
302
+ 4. Copy the token (starts with ATATT3...)
303
+ 5. Use this token during `fraim setup`
304
+
305
+ **Correct MCP Configuration** (automatically generated):
306
+ ```json
307
+ {
308
+ "jira": {
309
+ "command": "uvx",
310
+ "args": ["mcp-atlassian"],
311
+ "env": {
312
+ "JIRA_URL": "https://mycompany.atlassian.net",
313
+ "JIRA_USERNAME": "user@mycompany.com",
314
+ "JIRA_API_TOKEN": "your-token-here"
315
+ }
316
+ }
317
+ }
318
+ ```
319
+
320
+ **āš ļø Common Issues**:
321
+ - **Old package name**: If you see `@modelcontextprotocol/server-jira` in your config, this package doesn't exist. Run `fraim setup --jira` to update to the correct `mcp-atlassian` package.
322
+ - **Token format**: Jira API tokens typically start with `ATATT3`. If your token doesn't match this format, verify you created an API token (not a personal access token).
323
+ - **First run slow**: The first time `uvx` runs the Jira MCP server, it downloads the package. This is normal and only happens once.
324
+
325
+ **Troubleshooting**:
326
+ ```bash
327
+ # Test Jira MCP connection
328
+ fraim test-mcp
329
+
330
+ # Reconfigure Jira integration
331
+ fraim setup --jira
332
+
333
+ # Check configuration
334
+ cat ~/.kiro/settings/mcp.json # For Kiro IDE
335
+ cat ~/Library/Application\ Support/Claude/claude_desktop_config.json # For Claude Desktop (macOS)
336
+ ```
259
337
 
260
338
 
261
339
  ## 🌟 **Why FRAIM is the Future**
package/bin/fraim-mcp.js CHANGED
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env node
2
2
 
3
3
  /**
4
4
  * FRAIM MCP Server - Smart Entry Point
package/bin/fraim.js CHANGED
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env node
2
2
 
3
3
  /**
4
4
  * FRAIM Framework CLI Entry Point
@@ -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 ide_detector_1 = require("../setup/ide-detector");
14
13
  const mcp_config_generator_1 = require("../setup/mcp-config-generator");
15
14
  const codex_local_config_1 = require("../setup/codex-local-config");
15
+ const script_sync_utils_1 = require("../utils/script-sync-utils");
16
16
  const loadGlobalConfig = () => {
17
- const globalConfigPath = path_1.default.join(os_1.default.homedir(), '.fraim', 'config.json');
17
+ const globalConfigPath = path_1.default.join((0, script_sync_utils_1.getUserFraimDir)(), 'config.json');
18
18
  if (!fs_1.default.existsSync(globalConfigPath)) {
19
19
  return null;
20
20
  }
@@ -25,7 +25,8 @@ const loadGlobalConfig = () => {
25
25
  githubToken: config.tokens?.github || config.githubToken, // Support both old and new format
26
26
  gitlabToken: config.tokens?.gitlab,
27
27
  jiraToken: config.tokens?.jira,
28
- mode: config.mode
28
+ mode: config.mode,
29
+ jiraConfig: config.jiraConfig
29
30
  };
30
31
  }
31
32
  catch (e) {
@@ -77,7 +78,7 @@ const promptForGitHubToken = async (isConversationalMode = false) => {
77
78
  return tokenResponse.token;
78
79
  };
79
80
  const saveGitHubTokenToConfig = (githubToken) => {
80
- const globalConfigPath = path_1.default.join(os_1.default.homedir(), '.fraim', 'config.json');
81
+ const globalConfigPath = path_1.default.join((0, script_sync_utils_1.getUserFraimDir)(), 'config.json');
81
82
  if (fs_1.default.existsSync(globalConfigPath)) {
82
83
  try {
83
84
  const config = JSON.parse(fs_1.default.readFileSync(globalConfigPath, 'utf8'));
@@ -97,7 +98,7 @@ const saveGitHubTokenToConfig = (githubToken) => {
97
98
  }
98
99
  };
99
100
  exports.saveGitHubTokenToConfig = saveGitHubTokenToConfig;
100
- const configureIDEMCP = async (ide, fraimKey, tokens) => {
101
+ const configureIDEMCP = async (ide, fraimKey, tokens, jiraConfig) => {
101
102
  const configPath = (0, ide_detector_1.expandPath)(ide.configPath);
102
103
  console.log(chalk_1.default.blue(`šŸ”§ Configuring ${ide.name}...`));
103
104
  // Create backup if config exists
@@ -114,10 +115,11 @@ const configureIDEMCP = async (ide, fraimKey, tokens) => {
114
115
  }
115
116
  let existingConfig = {};
116
117
  let existingMCPServers = {};
118
+ const serversKey = ide.configType === 'vscode' ? 'servers' : 'mcpServers';
117
119
  if (fs_1.default.existsSync(configPath) && ide.configFormat === 'json') {
118
120
  try {
119
121
  existingConfig = JSON.parse(fs_1.default.readFileSync(configPath, 'utf8'));
120
- existingMCPServers = existingConfig.mcpServers || {};
122
+ existingMCPServers = existingConfig[serversKey] || {};
121
123
  console.log(chalk_1.default.gray(` šŸ“‹ Found existing config with ${Object.keys(existingMCPServers).length} MCP servers`));
122
124
  }
123
125
  catch (e) {
@@ -131,7 +133,7 @@ const configureIDEMCP = async (ide, fraimKey, tokens) => {
131
133
  existingTomlContent = fs_1.default.readFileSync(configPath, 'utf8');
132
134
  console.log(chalk_1.default.gray(` šŸ“‹ Found existing TOML config`));
133
135
  }
134
- const newTomlContent = (0, mcp_config_generator_1.generateMCPConfig)(ide.configType, fraimKey, tokens);
136
+ const newTomlContent = (0, mcp_config_generator_1.generateMCPConfig)(ide.configType, fraimKey, tokens, jiraConfig);
135
137
  const serversToAdd = ['fraim', 'git', 'github', 'gitlab', 'jira', 'playwright'];
136
138
  const mergeResult = (0, mcp_config_generator_1.mergeTomlMCPServers)(existingTomlContent, newTomlContent, serversToAdd);
137
139
  fs_1.default.writeFileSync(configPath, mergeResult.content);
@@ -146,9 +148,9 @@ const configureIDEMCP = async (ide, fraimKey, tokens) => {
146
148
  });
147
149
  }
148
150
  else {
149
- // Handle JSON format
150
- const newConfig = (0, mcp_config_generator_1.generateMCPConfig)(ide.configType, fraimKey, tokens);
151
- const newMCPServers = newConfig.mcpServers || {};
151
+ // For JSON configs - intelligent merging
152
+ const newConfig = (0, mcp_config_generator_1.generateMCPConfig)(ide.configType, fraimKey, tokens, jiraConfig);
153
+ const newMCPServers = newConfig[serversKey] || newConfig.mcpServers || {};
152
154
  // Merge MCP servers intelligently
153
155
  const mergedMCPServers = { ...existingMCPServers };
154
156
  const addedServers = [];
@@ -166,7 +168,7 @@ const configureIDEMCP = async (ide, fraimKey, tokens) => {
166
168
  const mergedConfig = {
167
169
  ...existingConfig,
168
170
  ...newConfig,
169
- mcpServers: mergedMCPServers
171
+ [serversKey]: mergedMCPServers
170
172
  };
171
173
  // Write updated config
172
174
  fs_1.default.writeFileSync(configPath, JSON.stringify(mergedConfig, null, 2));
@@ -322,7 +324,7 @@ const runAddIDE = async (options) => {
322
324
  };
323
325
  for (const ide of idesToConfigure) {
324
326
  try {
325
- await configureIDEMCP(ide, globalConfig.fraimKey, platformTokens);
327
+ await configureIDEMCP(ide, globalConfig.fraimKey, platformTokens, globalConfig.jiraConfig);
326
328
  results.successful.push(ide.name);
327
329
  }
328
330
  catch (error) {
@@ -355,7 +357,7 @@ const runAddIDE = async (options) => {
355
357
  exports.runAddIDE = runAddIDE;
356
358
  exports.addIDECommand = new commander_1.Command('add-ide')
357
359
  .description('Add FRAIM configuration to additional IDEs')
358
- .option('--ide <name>', 'Configure specific IDE (claude, antigravity, kiro, cursor, vscode, codex, windsurf)')
360
+ .option('--ide <name>', 'Configure specific IDE (claude, claude-code, antigravity, kiro, cursor, vscode, codex, windsurf)')
359
361
  .option('--all', 'Configure all detected IDEs')
360
362
  .option('--list', 'List all supported IDEs and their detection status')
361
363
  .action(exports.runAddIDE);
@@ -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'));
@@ -140,6 +150,49 @@ const promptForPlatforms = async () => {
140
150
  console.log();
141
151
  return platforms;
142
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
+ };
143
196
  const promptForADOToken = async () => {
144
197
  console.log(chalk_1.default.blue('\nšŸ”§ Azure DevOps Integration Setup'));
145
198
  console.log('FRAIM requires an Azure DevOps Personal Access Token for ADO integration.\n');
@@ -226,29 +279,71 @@ const promptForGitLabToken = async () => {
226
279
  console.log(chalk_1.default.green('GitLab token received\n'));
227
280
  return tokenResponse.token;
228
281
  };
229
- const promptForJiraToken = async () => {
282
+ const promptForJiraCredentials = async () => {
230
283
  console.log(chalk_1.default.blue('\nJira Integration Setup'));
231
- console.log('FRAIM requires a Jira API token for Jira MCP integration.\n');
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
232
323
  const tokenResponse = await (0, prompts_1.default)({
233
324
  type: 'password',
234
325
  name: 'token',
235
326
  message: 'Enter your Jira API token',
236
327
  validate: (value) => {
237
328
  if (!value)
238
- return 'Jira token is required';
329
+ return 'Jira API token is required';
239
330
  if (!(0, token_validator_1.isValidTokenFormat)(value, 'jira'))
240
331
  return 'Please enter a valid Jira token';
241
332
  return true;
242
333
  }
243
334
  });
244
335
  if (!tokenResponse.token) {
245
- throw new Error('Jira token is required');
336
+ throw new Error('Jira API token is required');
246
337
  }
247
- console.log(chalk_1.default.green('Jira token received\n'));
248
- return tokenResponse.token;
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
+ };
249
344
  };
250
- const saveGlobalConfig = (fraimKey, mode, tokens) => {
251
- const globalConfigDir = path_1.default.join(os_1.default.homedir(), '.fraim');
345
+ const saveGlobalConfig = (fraimKey, mode, tokens, jiraConfig) => {
346
+ const globalConfigDir = (0, script_sync_utils_1.getUserFraimDir)();
252
347
  const globalConfigPath = path_1.default.join(globalConfigDir, 'config.json');
253
348
  if (!fs_1.default.existsSync(globalConfigDir)) {
254
349
  fs_1.default.mkdirSync(globalConfigDir, { recursive: true });
@@ -272,6 +367,10 @@ const saveGlobalConfig = (fraimKey, mode, tokens) => {
272
367
  ...(existingConfig.tokens || {}),
273
368
  ...tokens
274
369
  },
370
+ jiraConfig: jiraConfig && (jiraConfig.baseUrl || jiraConfig.email) ? {
371
+ ...(existingConfig.jiraConfig || {}),
372
+ ...jiraConfig
373
+ } : existingConfig.jiraConfig,
275
374
  configuredAt: new Date().toISOString(),
276
375
  userPreferences: {
277
376
  autoSync: true,
@@ -284,11 +383,12 @@ const saveGlobalConfig = (fraimKey, mode, tokens) => {
284
383
  const runSetup = async (options) => {
285
384
  console.log(chalk_1.default.blue('šŸš€ Welcome to FRAIM! Let\'s get you set up.\n'));
286
385
  // Determine if this is an update (adding platforms to existing setup)
287
- const globalConfigPath = path_1.default.join(os_1.default.homedir(), '.fraim', 'config.json');
386
+ const globalConfigPath = path_1.default.join((0, script_sync_utils_1.getUserFraimDir)(), 'config.json');
288
387
  const isUpdate = fs_1.default.existsSync(globalConfigPath) && (options.github || options.ado || options.gitlab || options.jira);
289
388
  let fraimKey;
290
389
  let mode;
291
390
  const tokens = {};
391
+ let jiraConfig = {};
292
392
  if (isUpdate) {
293
393
  // Update existing setup - add platforms
294
394
  console.log(chalk_1.default.blue('šŸ“ Updating existing FRAIM configuration...\n'));
@@ -413,7 +513,7 @@ const runSetup = async (options) => {
413
513
  }
414
514
  }
415
515
  }
416
- // Get Jira token if needed
516
+ // Get Jira credentials if needed
417
517
  if (needJira && !tokens.jira) {
418
518
  if (options.jiraToken) {
419
519
  if (!(0, token_validator_1.isValidTokenFormat)(options.jiraToken, 'jira')) {
@@ -421,13 +521,28 @@ const runSetup = async (options) => {
421
521
  process.exit(1);
422
522
  }
423
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
+ }
424
536
  }
425
537
  else {
426
538
  try {
427
- tokens.jira = await promptForJiraToken();
539
+ const jiraCredentials = await promptForJiraCredentials();
540
+ tokens.jira = jiraCredentials.token;
541
+ jiraConfig.baseUrl = jiraCredentials.baseUrl;
542
+ jiraConfig.email = jiraCredentials.email;
428
543
  }
429
544
  catch (e) {
430
- console.log(chalk_1.default.red('Failed to get Jira token'));
545
+ console.log(chalk_1.default.red('Failed to get Jira credentials'));
431
546
  process.exit(1);
432
547
  }
433
548
  }
@@ -441,12 +556,171 @@ const runSetup = async (options) => {
441
556
  console.log(chalk_1.default.cyan(' fraim setup --jira\n'));
442
557
  }
443
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
+ }
715
+ }
716
+ console.log(chalk_1.default.green(`\nāœ… Split mode configured: ${codeRepoPlatform} (code) + ${issueTrackingPlatform} (issues)\n`));
717
+ }
444
718
  else {
445
719
  console.log(chalk_1.default.gray('ā„¹ļø Conversational mode: No platform tokens needed\n'));
446
720
  }
447
721
  // Save global configuration
448
722
  console.log(chalk_1.default.blue('šŸ’¾ Saving global configuration...'));
449
- saveGlobalConfig(fraimKey, mode, tokens);
723
+ saveGlobalConfig(fraimKey, mode, tokens, jiraConfig);
450
724
  // Configure MCP servers (only on initial setup)
451
725
  if (!isUpdate) {
452
726
  console.log(chalk_1.default.blue('\nšŸ”Œ Configuring MCP servers...'));
@@ -462,7 +736,7 @@ const runSetup = async (options) => {
462
736
  console.log(chalk_1.default.gray(' FRAIM workflows will work, but GitHub-specific features will be unavailable\n'));
463
737
  }
464
738
  try {
465
- await (0, auto_mcp_setup_1.autoConfigureMCP)(fraimKey, mcpTokens, options.ide ? [options.ide] : undefined);
739
+ await (0, auto_mcp_setup_1.autoConfigureMCP)(fraimKey, mcpTokens, options.ide ? [options.ide] : undefined, jiraConfig);
466
740
  }
467
741
  catch (e) {
468
742
  console.log(chalk_1.default.yellow('āš ļø MCP configuration encountered issues'));
@@ -508,6 +782,8 @@ exports.setupCommand = new commander_1.Command('setup')
508
782
  .option('--ado-token <token>', 'Azure DevOps Personal Access Token')
509
783
  .option('--gitlab-token <token>', 'GitLab Personal Access Token')
510
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')
511
787
  .option('--github', 'Add/update GitHub integration')
512
788
  .option('--ado', 'Add/update Azure DevOps integration')
513
789
  .option('--gitlab', 'Add/update GitLab integration')
@@ -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 () => {