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 +79 -1
- package/bin/fraim-mcp.js +1 -1
- package/bin/fraim.js +1 -1
- package/dist/src/cli/commands/add-ide.js +15 -13
- package/dist/src/cli/commands/setup.js +292 -16
- package/dist/src/cli/commands/test-mcp.js +2 -2
- package/dist/src/cli/setup/auto-mcp-setup.js +5 -5
- package/dist/src/cli/setup/codex-local-config.js +3 -3
- package/dist/src/cli/setup/first-run.js +2 -1
- package/dist/src/cli/setup/ide-detector.js +1 -1
- package/dist/src/cli/setup/mcp-config-generator.js +107 -62
- package/dist/src/cli/setup/token-validator.js +2 -2
- package/index.js +1 -1
- package/package.json +116 -116
- package/dist/src/cli/commands/init.js +0 -148
- package/dist/src/cli/commands/mcp.js +0 -65
- package/dist/src/cli/commands/wizard.js +0 -35
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
|
|
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
package/bin/fraim.js
CHANGED
|
@@ -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(
|
|
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(
|
|
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
|
|
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
|
-
//
|
|
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
|
-
|
|
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: '
|
|
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
|
|
282
|
+
const promptForJiraCredentials = async () => {
|
|
230
283
|
console.log(chalk_1.default.blue('\nJira Integration Setup'));
|
|
231
|
-
console.log('FRAIM requires
|
|
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
|
|
248
|
-
return
|
|
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 =
|
|
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(
|
|
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
|
|
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
|
-
|
|
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
|
|
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(
|
|
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 () => {
|