fraim-framework 2.0.76 ā 2.0.78
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/cli/commands/add-ide.js +25 -11
- package/dist/src/cli/commands/init-project.js +42 -40
- package/dist/src/cli/commands/setup.js +125 -15
- package/dist/src/cli/setup/auto-mcp-setup.js +27 -10
- package/dist/src/cli/setup/mcp-config-generator.js +153 -39
- package/dist/src/cli/setup/token-validator.js +8 -0
- package/dist/src/cli/utils/platform-detection.js +45 -0
- package/dist/src/core/config-loader.js +4 -1
- package/dist/src/core/utils/provider-utils.js +5 -1
- package/dist/src/local-mcp-server/stdio-server.js +237 -59
- package/package.json +1 -1
|
@@ -23,6 +23,8 @@ const loadGlobalConfig = () => {
|
|
|
23
23
|
return {
|
|
24
24
|
fraimKey: config.apiKey,
|
|
25
25
|
githubToken: config.tokens?.github || config.githubToken, // Support both old and new format
|
|
26
|
+
gitlabToken: config.tokens?.gitlab,
|
|
27
|
+
jiraToken: config.tokens?.jira,
|
|
26
28
|
mode: config.mode
|
|
27
29
|
};
|
|
28
30
|
}
|
|
@@ -95,7 +97,7 @@ const saveGitHubTokenToConfig = (githubToken) => {
|
|
|
95
97
|
}
|
|
96
98
|
};
|
|
97
99
|
exports.saveGitHubTokenToConfig = saveGitHubTokenToConfig;
|
|
98
|
-
const configureIDEMCP = async (ide, fraimKey,
|
|
100
|
+
const configureIDEMCP = async (ide, fraimKey, tokens) => {
|
|
99
101
|
const configPath = (0, ide_detector_1.expandPath)(ide.configPath);
|
|
100
102
|
console.log(chalk_1.default.blue(`š§ Configuring ${ide.name}...`));
|
|
101
103
|
// Create backup if config exists
|
|
@@ -129,8 +131,8 @@ const configureIDEMCP = async (ide, fraimKey, githubToken) => {
|
|
|
129
131
|
existingTomlContent = fs_1.default.readFileSync(configPath, 'utf8');
|
|
130
132
|
console.log(chalk_1.default.gray(` š Found existing TOML config`));
|
|
131
133
|
}
|
|
132
|
-
const newTomlContent = (0, mcp_config_generator_1.generateMCPConfig)(ide.configType, fraimKey,
|
|
133
|
-
const serversToAdd = ['fraim', 'git', 'github', 'playwright'];
|
|
134
|
+
const newTomlContent = (0, mcp_config_generator_1.generateMCPConfig)(ide.configType, fraimKey, tokens);
|
|
135
|
+
const serversToAdd = ['fraim', 'git', 'github', 'gitlab', 'jira', 'playwright'];
|
|
134
136
|
const mergeResult = (0, mcp_config_generator_1.mergeTomlMCPServers)(existingTomlContent, newTomlContent, serversToAdd);
|
|
135
137
|
fs_1.default.writeFileSync(configPath, mergeResult.content);
|
|
136
138
|
mergeResult.addedServers.forEach(server => {
|
|
@@ -145,7 +147,7 @@ const configureIDEMCP = async (ide, fraimKey, githubToken) => {
|
|
|
145
147
|
}
|
|
146
148
|
else {
|
|
147
149
|
// Handle JSON format
|
|
148
|
-
const newConfig = (0, mcp_config_generator_1.generateMCPConfig)(ide.configType, fraimKey,
|
|
150
|
+
const newConfig = (0, mcp_config_generator_1.generateMCPConfig)(ide.configType, fraimKey, tokens);
|
|
149
151
|
const newMCPServers = newConfig.mcpServers || {};
|
|
150
152
|
// Merge MCP servers intelligently
|
|
151
153
|
const mergedMCPServers = { ...existingMCPServers };
|
|
@@ -197,7 +199,7 @@ const listSupportedIDEs = () => {
|
|
|
197
199
|
console.log(chalk_1.default.yellow('š” Use "fraim add-ide --ide <name>" to configure a specific IDE'));
|
|
198
200
|
console.log(chalk_1.default.yellow(' Example: fraim add-ide --ide claude'));
|
|
199
201
|
};
|
|
200
|
-
const promptForIDESelection = async (availableIDEs,
|
|
202
|
+
const promptForIDESelection = async (availableIDEs, tokens) => {
|
|
201
203
|
console.log(chalk_1.default.green(`ā
Found ${availableIDEs.length} IDEs that can be configured:\n`));
|
|
202
204
|
availableIDEs.forEach((ide, index) => {
|
|
203
205
|
const configExists = fs_1.default.existsSync((0, ide_detector_1.expandPath)(ide.configPath));
|
|
@@ -208,10 +210,16 @@ const promptForIDESelection = async (availableIDEs, githubToken) => {
|
|
|
208
210
|
console.log(chalk_1.default.blue('\nFRAIM will add these MCP servers:'));
|
|
209
211
|
console.log(chalk_1.default.gray(' ⢠fraim (workflows and AI management)'));
|
|
210
212
|
console.log(chalk_1.default.gray(' ⢠git (version control integration)'));
|
|
211
|
-
if (
|
|
212
|
-
console.log(chalk_1.default.gray('
|
|
213
|
+
if (tokens?.github) {
|
|
214
|
+
console.log(chalk_1.default.gray(' - github (GitHub API access)'));
|
|
213
215
|
}
|
|
214
|
-
|
|
216
|
+
if (tokens?.gitlab) {
|
|
217
|
+
console.log(chalk_1.default.gray(' - gitlab (GitLab API access)'));
|
|
218
|
+
}
|
|
219
|
+
if (tokens?.jira) {
|
|
220
|
+
console.log(chalk_1.default.gray(' - jira (Jira issue tracking)'));
|
|
221
|
+
}
|
|
222
|
+
console.log(chalk_1.default.gray(' - playwright (browser automation)'));
|
|
215
223
|
const response = await (0, prompts_1.default)({
|
|
216
224
|
type: 'text',
|
|
217
225
|
name: 'selection',
|
|
@@ -250,12 +258,18 @@ const runAddIDE = async (options) => {
|
|
|
250
258
|
process.exit(1);
|
|
251
259
|
}
|
|
252
260
|
let githubToken = globalConfig.githubToken;
|
|
261
|
+
const platformTokens = {
|
|
262
|
+
github: globalConfig.githubToken,
|
|
263
|
+
gitlab: globalConfig.gitlabToken,
|
|
264
|
+
jira: globalConfig.jiraToken
|
|
265
|
+
};
|
|
253
266
|
const isConversationalMode = globalConfig.mode === 'conversational';
|
|
254
|
-
if (!githubToken && !isConversationalMode) {
|
|
267
|
+
if (!githubToken && !platformTokens.gitlab && !platformTokens.jira && !isConversationalMode) {
|
|
255
268
|
console.log(chalk_1.default.yellow('ā ļø No GitHub token found in configuration.'));
|
|
256
269
|
githubToken = await promptForGitHubToken(false);
|
|
257
270
|
if (githubToken) {
|
|
258
271
|
saveGitHubTokenToConfig(githubToken);
|
|
272
|
+
platformTokens.github = githubToken;
|
|
259
273
|
}
|
|
260
274
|
}
|
|
261
275
|
if (isConversationalMode && !githubToken) {
|
|
@@ -295,7 +309,7 @@ const runAddIDE = async (options) => {
|
|
|
295
309
|
}
|
|
296
310
|
else {
|
|
297
311
|
// Interactive selection
|
|
298
|
-
idesToConfigure = await promptForIDESelection(detectedIDEs,
|
|
312
|
+
idesToConfigure = await promptForIDESelection(detectedIDEs, platformTokens);
|
|
299
313
|
}
|
|
300
314
|
if (idesToConfigure.length === 0) {
|
|
301
315
|
console.log(chalk_1.default.yellow('ā ļø No IDEs selected for configuration.'));
|
|
@@ -308,7 +322,7 @@ const runAddIDE = async (options) => {
|
|
|
308
322
|
};
|
|
309
323
|
for (const ide of idesToConfigure) {
|
|
310
324
|
try {
|
|
311
|
-
await configureIDEMCP(ide, globalConfig.fraimKey,
|
|
325
|
+
await configureIDEMCP(ide, globalConfig.fraimKey, platformTokens);
|
|
312
326
|
results.successful.push(ide.name);
|
|
313
327
|
}
|
|
314
328
|
catch (error) {
|
|
@@ -24,42 +24,38 @@ const checkGlobalSetup = () => {
|
|
|
24
24
|
const config = JSON.parse(fs_1.default.readFileSync(globalConfigPath, 'utf8'));
|
|
25
25
|
return {
|
|
26
26
|
exists: true,
|
|
27
|
-
mode: config.mode || 'integrated',
|
|
27
|
+
mode: config.mode || 'integrated',
|
|
28
28
|
tokens: config.tokens || {}
|
|
29
29
|
};
|
|
30
30
|
}
|
|
31
|
-
catch
|
|
31
|
+
catch {
|
|
32
32
|
return { exists: true, mode: 'integrated', tokens: {} };
|
|
33
33
|
}
|
|
34
34
|
};
|
|
35
35
|
const runInitProject = async () => {
|
|
36
|
-
console.log(chalk_1.default.blue('
|
|
37
|
-
// Check if global setup exists
|
|
36
|
+
console.log(chalk_1.default.blue('Initializing FRAIM project...'));
|
|
38
37
|
const globalSetup = checkGlobalSetup();
|
|
39
38
|
if (!globalSetup.exists) {
|
|
40
|
-
console.log(chalk_1.default.red('
|
|
39
|
+
console.log(chalk_1.default.red('Global FRAIM setup not found.'));
|
|
41
40
|
console.log(chalk_1.default.yellow('Please run global setup first:'));
|
|
42
41
|
console.log(chalk_1.default.cyan(' fraim setup'));
|
|
43
42
|
process.exit(1);
|
|
44
43
|
}
|
|
45
|
-
const mode = globalSetup.mode;
|
|
46
|
-
const tokens = globalSetup.tokens || {};
|
|
47
|
-
console.log(chalk_1.default.gray(` Mode: ${mode === 'conversational' ? 'Conversational' : 'Integrated'} (from global config)`));
|
|
48
44
|
const projectRoot = process.cwd();
|
|
49
45
|
const fraimDir = path_1.default.join(projectRoot, '.fraim');
|
|
50
46
|
const configPath = path_1.default.join(fraimDir, 'config.json');
|
|
51
47
|
if (!fs_1.default.existsSync(fraimDir)) {
|
|
52
48
|
fs_1.default.mkdirSync(fraimDir, { recursive: true });
|
|
53
|
-
console.log(chalk_1.default.green('
|
|
49
|
+
console.log(chalk_1.default.green('Created .fraim directory'));
|
|
54
50
|
}
|
|
55
51
|
else {
|
|
56
|
-
console.log(chalk_1.default.yellow('
|
|
52
|
+
console.log(chalk_1.default.yellow('.fraim directory already exists'));
|
|
57
53
|
}
|
|
58
54
|
if (!fs_1.default.existsSync(configPath)) {
|
|
55
|
+
const projectName = path_1.default.basename(projectRoot);
|
|
56
|
+
const preferredMode = globalSetup.mode || 'integrated';
|
|
59
57
|
let config;
|
|
60
|
-
if (
|
|
61
|
-
// Conversational mode - no platform integration
|
|
62
|
-
const projectName = path_1.default.basename(projectRoot);
|
|
58
|
+
if (preferredMode === 'conversational') {
|
|
63
59
|
config = {
|
|
64
60
|
version: (0, version_utils_1.getFraimVersion)(),
|
|
65
61
|
project: {
|
|
@@ -67,77 +63,83 @@ const runInitProject = async () => {
|
|
|
67
63
|
},
|
|
68
64
|
customizations: {}
|
|
69
65
|
};
|
|
70
|
-
console.log(chalk_1.default.blue('
|
|
66
|
+
console.log(chalk_1.default.blue(' conversational mode: no platform integration'));
|
|
71
67
|
console.log(chalk_1.default.gray(` Project: ${projectName}`));
|
|
72
68
|
}
|
|
73
69
|
else {
|
|
74
|
-
// Integrated mode - try to detect platform
|
|
75
70
|
const detection = (0, platform_detection_1.detectPlatformFromGit)();
|
|
76
71
|
if (detection.provider !== 'unknown' && detection.repository) {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
72
|
+
const issueTracking = detection.provider === 'github'
|
|
73
|
+
? {
|
|
74
|
+
provider: 'github',
|
|
75
|
+
owner: detection.repository.owner,
|
|
76
|
+
name: detection.repository.name
|
|
77
|
+
}
|
|
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
|
+
};
|
|
85
91
|
config = {
|
|
86
92
|
version: (0, version_utils_1.getFraimVersion)(),
|
|
87
93
|
project: {
|
|
88
|
-
name: detection.repository.name
|
|
94
|
+
name: detection.repository.name || projectName
|
|
89
95
|
},
|
|
90
96
|
repository: detection.repository,
|
|
97
|
+
issueTracking,
|
|
91
98
|
customizations: {}
|
|
92
99
|
};
|
|
93
100
|
console.log(chalk_1.default.blue(` Platform: ${detection.provider.toUpperCase()}`));
|
|
94
101
|
if (detection.provider === 'github') {
|
|
95
102
|
console.log(chalk_1.default.gray(` Repository: ${detection.repository.owner}/${detection.repository.name}`));
|
|
96
|
-
console.log(chalk_1.default.gray(` Token: ${hasToken ? 'ā Configured' : 'ā Missing'}`));
|
|
97
103
|
}
|
|
98
104
|
else if (detection.provider === 'ado') {
|
|
99
105
|
console.log(chalk_1.default.gray(` Organization: ${detection.repository.organization}`));
|
|
100
106
|
console.log(chalk_1.default.gray(` Project: ${detection.repository.project}`));
|
|
101
107
|
console.log(chalk_1.default.gray(` Repository: ${detection.repository.name}`));
|
|
102
|
-
|
|
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}`));
|
|
103
112
|
}
|
|
104
113
|
}
|
|
105
114
|
else {
|
|
106
|
-
// No git remote detected - warn user and fallback to conversational-style config
|
|
107
|
-
console.log(chalk_1.default.yellow(' ā ļø No git remote detected'));
|
|
108
|
-
console.log(chalk_1.default.yellow(' Integrated mode requires a git remote for platform features.'));
|
|
109
|
-
console.log(chalk_1.default.gray(' Falling back to conversational mode configuration...'));
|
|
110
|
-
const repoName = path_1.default.basename(projectRoot);
|
|
111
115
|
config = {
|
|
112
116
|
version: (0, version_utils_1.getFraimVersion)(),
|
|
113
117
|
project: {
|
|
114
|
-
name:
|
|
118
|
+
name: projectName
|
|
115
119
|
},
|
|
116
120
|
customizations: {}
|
|
117
121
|
};
|
|
118
|
-
console.log(chalk_1.default.
|
|
122
|
+
console.log(chalk_1.default.yellow(' No git remote detected. Falling back to conversational mode.'));
|
|
119
123
|
}
|
|
120
124
|
}
|
|
121
125
|
fs_1.default.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
122
|
-
console.log(chalk_1.default.green('
|
|
126
|
+
console.log(chalk_1.default.green('Created .fraim/config.json'));
|
|
123
127
|
}
|
|
124
|
-
|
|
125
|
-
['workflows'].forEach(dir => {
|
|
128
|
+
['workflows'].forEach((dir) => {
|
|
126
129
|
const dirPath = path_1.default.join(fraimDir, dir);
|
|
127
130
|
if (!fs_1.default.existsSync(dirPath)) {
|
|
128
131
|
fs_1.default.mkdirSync(dirPath, { recursive: true });
|
|
129
|
-
console.log(chalk_1.default.green(
|
|
132
|
+
console.log(chalk_1.default.green(`Created .fraim/${dir}`));
|
|
130
133
|
}
|
|
131
134
|
});
|
|
132
|
-
// Sync workflows from registry
|
|
133
135
|
await (0, sync_1.runSync)({});
|
|
134
136
|
const codexAvailable = (0, ide_detector_1.detectInstalledIDEs)().some((ide) => ide.configType === 'codex');
|
|
135
137
|
if (codexAvailable) {
|
|
136
138
|
const codexLocalResult = (0, codex_local_config_1.ensureCodexLocalConfig)(projectRoot);
|
|
137
139
|
const status = codexLocalResult.created ? 'Created' : codexLocalResult.updated ? 'Updated' : 'Verified';
|
|
138
|
-
console.log(chalk_1.default.green(
|
|
140
|
+
console.log(chalk_1.default.green(`${status} project Codex config at ${codexLocalResult.path}`));
|
|
139
141
|
}
|
|
140
|
-
console.log(chalk_1.default.green('\
|
|
142
|
+
console.log(chalk_1.default.green('\nFRAIM project initialized!'));
|
|
141
143
|
console.log(chalk_1.default.cyan('Try: Ask your AI agent "list fraim workflows"'));
|
|
142
144
|
};
|
|
143
145
|
exports.runInitProject = runInitProject;
|
|
@@ -112,23 +112,31 @@ const promptForPlatforms = async () => {
|
|
|
112
112
|
message: 'Select platforms (Space to select, Enter to confirm)',
|
|
113
113
|
choices: [
|
|
114
114
|
{ title: 'GitHub', value: 'github', selected: true },
|
|
115
|
-
{ title: 'Azure DevOps', value: 'ado', selected: false }
|
|
115
|
+
{ title: 'Azure DevOps', value: 'ado', selected: false },
|
|
116
|
+
{ title: 'GitLab', value: 'gitlab', selected: false },
|
|
117
|
+
{ title: 'Jira', value: 'jira', selected: false }
|
|
116
118
|
],
|
|
117
119
|
min: 1
|
|
118
120
|
});
|
|
119
121
|
if (!response.platforms || response.platforms.length === 0) {
|
|
120
122
|
console.log(chalk_1.default.yellow('\nā¹ļø No platforms selected, defaulting to GitHub'));
|
|
121
|
-
return { github: true, ado: false };
|
|
123
|
+
return { github: true, ado: false, gitlab: false, jira: false };
|
|
122
124
|
}
|
|
123
125
|
const platforms = {
|
|
124
126
|
github: response.platforms.includes('github'),
|
|
125
|
-
ado: response.platforms.includes('ado')
|
|
127
|
+
ado: response.platforms.includes('ado'),
|
|
128
|
+
gitlab: response.platforms.includes('gitlab'),
|
|
129
|
+
jira: response.platforms.includes('jira')
|
|
126
130
|
};
|
|
127
131
|
console.log(chalk_1.default.blue('\nā Selected platforms:'));
|
|
128
132
|
if (platforms.github)
|
|
129
|
-
console.log(chalk_1.default.gray('
|
|
133
|
+
console.log(chalk_1.default.gray(' - GitHub'));
|
|
130
134
|
if (platforms.ado)
|
|
131
|
-
console.log(chalk_1.default.gray('
|
|
135
|
+
console.log(chalk_1.default.gray(' - Azure DevOps'));
|
|
136
|
+
if (platforms.gitlab)
|
|
137
|
+
console.log(chalk_1.default.gray(' - GitLab'));
|
|
138
|
+
if (platforms.jira)
|
|
139
|
+
console.log(chalk_1.default.gray(' - Jira'));
|
|
132
140
|
console.log();
|
|
133
141
|
return platforms;
|
|
134
142
|
};
|
|
@@ -197,6 +205,48 @@ const promptForADOToken = async () => {
|
|
|
197
205
|
}
|
|
198
206
|
throw new Error('Failed to get Azure DevOps token');
|
|
199
207
|
};
|
|
208
|
+
const promptForGitLabToken = async () => {
|
|
209
|
+
console.log(chalk_1.default.blue('\nGitLab Integration Setup'));
|
|
210
|
+
console.log('FRAIM requires a GitLab token for GitLab issue/repository MCP integration.\n');
|
|
211
|
+
const tokenResponse = await (0, prompts_1.default)({
|
|
212
|
+
type: 'password',
|
|
213
|
+
name: 'token',
|
|
214
|
+
message: 'Enter your GitLab token',
|
|
215
|
+
validate: (value) => {
|
|
216
|
+
if (!value)
|
|
217
|
+
return 'GitLab token is required';
|
|
218
|
+
if (!(0, token_validator_1.isValidTokenFormat)(value, 'gitlab'))
|
|
219
|
+
return 'Please enter a valid GitLab token (typically starts with glpat-)';
|
|
220
|
+
return true;
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
if (!tokenResponse.token) {
|
|
224
|
+
throw new Error('GitLab token is required');
|
|
225
|
+
}
|
|
226
|
+
console.log(chalk_1.default.green('GitLab token received\n'));
|
|
227
|
+
return tokenResponse.token;
|
|
228
|
+
};
|
|
229
|
+
const promptForJiraToken = async () => {
|
|
230
|
+
console.log(chalk_1.default.blue('\nJira Integration Setup'));
|
|
231
|
+
console.log('FRAIM requires a Jira API token for Jira MCP integration.\n');
|
|
232
|
+
const tokenResponse = await (0, prompts_1.default)({
|
|
233
|
+
type: 'password',
|
|
234
|
+
name: 'token',
|
|
235
|
+
message: 'Enter your Jira API token',
|
|
236
|
+
validate: (value) => {
|
|
237
|
+
if (!value)
|
|
238
|
+
return 'Jira token is required';
|
|
239
|
+
if (!(0, token_validator_1.isValidTokenFormat)(value, 'jira'))
|
|
240
|
+
return 'Please enter a valid Jira token';
|
|
241
|
+
return true;
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
if (!tokenResponse.token) {
|
|
245
|
+
throw new Error('Jira token is required');
|
|
246
|
+
}
|
|
247
|
+
console.log(chalk_1.default.green('Jira token received\n'));
|
|
248
|
+
return tokenResponse.token;
|
|
249
|
+
};
|
|
200
250
|
const saveGlobalConfig = (fraimKey, mode, tokens) => {
|
|
201
251
|
const globalConfigDir = path_1.default.join(os_1.default.homedir(), '.fraim');
|
|
202
252
|
const globalConfigPath = path_1.default.join(globalConfigDir, 'config.json');
|
|
@@ -235,7 +285,7 @@ const runSetup = async (options) => {
|
|
|
235
285
|
console.log(chalk_1.default.blue('š Welcome to FRAIM! Let\'s get you set up.\n'));
|
|
236
286
|
// Determine if this is an update (adding platforms to existing setup)
|
|
237
287
|
const globalConfigPath = path_1.default.join(os_1.default.homedir(), '.fraim', 'config.json');
|
|
238
|
-
const isUpdate = fs_1.default.existsSync(globalConfigPath) && (options.github || options.ado);
|
|
288
|
+
const isUpdate = fs_1.default.existsSync(globalConfigPath) && (options.github || options.ado || options.gitlab || options.jira);
|
|
239
289
|
let fraimKey;
|
|
240
290
|
let mode;
|
|
241
291
|
const tokens = {};
|
|
@@ -252,9 +302,13 @@ const runSetup = async (options) => {
|
|
|
252
302
|
tokens.github = existingConfig.tokens.github;
|
|
253
303
|
if (existingConfig.tokens.ado)
|
|
254
304
|
tokens.ado = existingConfig.tokens.ado;
|
|
305
|
+
if (existingConfig.tokens.gitlab)
|
|
306
|
+
tokens.gitlab = existingConfig.tokens.gitlab;
|
|
307
|
+
if (existingConfig.tokens.jira)
|
|
308
|
+
tokens.jira = existingConfig.tokens.jira;
|
|
255
309
|
}
|
|
256
310
|
console.log(chalk_1.default.gray(` Current mode: ${mode}`));
|
|
257
|
-
console.log(chalk_1.default.gray(` Existing tokens: ${tokens.github ? 'GitHub
|
|
311
|
+
console.log(chalk_1.default.gray(` Existing tokens: ${tokens.github ? 'GitHub yes' : ''} ${tokens.ado ? 'ADO yes' : ''} ${tokens.gitlab ? 'GitLab yes' : ''} ${tokens.jira ? 'Jira yes' : ''}\n`));
|
|
258
312
|
}
|
|
259
313
|
catch (e) {
|
|
260
314
|
console.log(chalk_1.default.red('ā Failed to read existing configuration'));
|
|
@@ -288,11 +342,15 @@ const runSetup = async (options) => {
|
|
|
288
342
|
if (mode === 'integrated') {
|
|
289
343
|
let needGitHub = options.github || false;
|
|
290
344
|
let needADO = options.ado || false;
|
|
345
|
+
let needGitLab = options.gitlab || false;
|
|
346
|
+
let needJira = options.jira || false;
|
|
291
347
|
// If no specific platform flags and not an update, ask user
|
|
292
|
-
if (!isUpdate && !needGitHub && !needADO) {
|
|
348
|
+
if (!isUpdate && !needGitHub && !needADO && !needGitLab && !needJira) {
|
|
293
349
|
const platforms = await promptForPlatforms();
|
|
294
350
|
needGitHub = platforms.github;
|
|
295
351
|
needADO = platforms.ado;
|
|
352
|
+
needGitLab = platforms.gitlab;
|
|
353
|
+
needJira = platforms.jira;
|
|
296
354
|
}
|
|
297
355
|
// Get GitHub token if needed
|
|
298
356
|
if (needGitHub && !tokens.github) {
|
|
@@ -336,11 +394,51 @@ const runSetup = async (options) => {
|
|
|
336
394
|
}
|
|
337
395
|
}
|
|
338
396
|
}
|
|
339
|
-
|
|
397
|
+
// Get GitLab token if needed
|
|
398
|
+
if (needGitLab && !tokens.gitlab) {
|
|
399
|
+
if (options.gitlabToken) {
|
|
400
|
+
if (!(0, token_validator_1.isValidTokenFormat)(options.gitlabToken, 'gitlab')) {
|
|
401
|
+
console.log(chalk_1.default.red('Invalid GitLab token format'));
|
|
402
|
+
process.exit(1);
|
|
403
|
+
}
|
|
404
|
+
tokens.gitlab = options.gitlabToken;
|
|
405
|
+
}
|
|
406
|
+
else {
|
|
407
|
+
try {
|
|
408
|
+
tokens.gitlab = await promptForGitLabToken();
|
|
409
|
+
}
|
|
410
|
+
catch (e) {
|
|
411
|
+
console.log(chalk_1.default.red('Failed to get GitLab token'));
|
|
412
|
+
process.exit(1);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
// Get Jira token if needed
|
|
417
|
+
if (needJira && !tokens.jira) {
|
|
418
|
+
if (options.jiraToken) {
|
|
419
|
+
if (!(0, token_validator_1.isValidTokenFormat)(options.jiraToken, 'jira')) {
|
|
420
|
+
console.log(chalk_1.default.red('Invalid Jira token format'));
|
|
421
|
+
process.exit(1);
|
|
422
|
+
}
|
|
423
|
+
tokens.jira = options.jiraToken;
|
|
424
|
+
}
|
|
425
|
+
else {
|
|
426
|
+
try {
|
|
427
|
+
tokens.jira = await promptForJiraToken();
|
|
428
|
+
}
|
|
429
|
+
catch (e) {
|
|
430
|
+
console.log(chalk_1.default.red('Failed to get Jira token'));
|
|
431
|
+
process.exit(1);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
if (!tokens.github && !tokens.ado && !tokens.gitlab && !tokens.jira) {
|
|
340
436
|
console.log(chalk_1.default.yellow('ā ļø No platform tokens configured.'));
|
|
341
437
|
console.log(chalk_1.default.gray(' You can add them later with:'));
|
|
342
438
|
console.log(chalk_1.default.cyan(' fraim setup --github'));
|
|
343
|
-
console.log(chalk_1.default.cyan(' fraim setup --ado
|
|
439
|
+
console.log(chalk_1.default.cyan(' fraim setup --ado'));
|
|
440
|
+
console.log(chalk_1.default.cyan(' fraim setup --gitlab'));
|
|
441
|
+
console.log(chalk_1.default.cyan(' fraim setup --jira\n'));
|
|
344
442
|
}
|
|
345
443
|
}
|
|
346
444
|
else {
|
|
@@ -354,13 +452,17 @@ const runSetup = async (options) => {
|
|
|
354
452
|
console.log(chalk_1.default.blue('\nš Configuring MCP servers...'));
|
|
355
453
|
// For conversational mode, we still configure MCP but without GitHub token requirement
|
|
356
454
|
// The FRAIM MCP server works without GitHub token, other servers can be optional
|
|
357
|
-
const
|
|
358
|
-
|
|
455
|
+
const mcpTokens = {
|
|
456
|
+
github: tokens.github || '',
|
|
457
|
+
gitlab: tokens.gitlab || '',
|
|
458
|
+
jira: tokens.jira || ''
|
|
459
|
+
};
|
|
460
|
+
if (mode === 'conversational' && !mcpTokens.github && !mcpTokens.gitlab && !mcpTokens.jira) {
|
|
359
461
|
console.log(chalk_1.default.yellow('ā¹ļø Conversational mode: Configuring MCP servers without GitHub integration'));
|
|
360
462
|
console.log(chalk_1.default.gray(' FRAIM workflows will work, but GitHub-specific features will be unavailable\n'));
|
|
361
463
|
}
|
|
362
464
|
try {
|
|
363
|
-
await (0, auto_mcp_setup_1.autoConfigureMCP)(fraimKey,
|
|
465
|
+
await (0, auto_mcp_setup_1.autoConfigureMCP)(fraimKey, mcpTokens, options.ide ? [options.ide] : undefined);
|
|
364
466
|
}
|
|
365
467
|
catch (e) {
|
|
366
468
|
console.log(chalk_1.default.yellow('ā ļø MCP configuration encountered issues'));
|
|
@@ -380,18 +482,22 @@ const runSetup = async (options) => {
|
|
|
380
482
|
console.log(chalk_1.default.green('\nšÆ Setup complete!'));
|
|
381
483
|
console.log(chalk_1.default.gray(` Mode: ${mode}`));
|
|
382
484
|
if (mode === 'integrated') {
|
|
383
|
-
console.log(chalk_1.default.gray(` Platforms: ${tokens.github ? 'GitHub
|
|
485
|
+
console.log(chalk_1.default.gray(` Platforms: ${tokens.github ? 'GitHub yes' : 'GitHub no'} ${tokens.ado ? 'ADO yes' : 'ADO no'} ${tokens.gitlab ? 'GitLab yes' : 'GitLab no'} ${tokens.jira ? 'Jira yes' : 'Jira no'}`));
|
|
384
486
|
}
|
|
385
487
|
console.log(chalk_1.default.cyan('\nš For future projects:'));
|
|
386
488
|
console.log(chalk_1.default.cyan(' 1. cd into any project directory'));
|
|
387
489
|
console.log(chalk_1.default.cyan(' 2. Run: fraim init-project'));
|
|
388
490
|
console.log(chalk_1.default.cyan(' 3. Ask your AI agent (Claude Desktop / Cowork, Cursor, etc.) "list fraim workflows"'));
|
|
389
|
-
if (mode === 'integrated' && (!tokens.github || !tokens.ado)) {
|
|
491
|
+
if (mode === 'integrated' && (!tokens.github || !tokens.ado || !tokens.gitlab || !tokens.jira)) {
|
|
390
492
|
console.log(chalk_1.default.gray('\nš” To add more platforms later:'));
|
|
391
493
|
if (!tokens.github)
|
|
392
494
|
console.log(chalk_1.default.gray(' fraim setup --github'));
|
|
393
495
|
if (!tokens.ado)
|
|
394
496
|
console.log(chalk_1.default.gray(' fraim setup --ado'));
|
|
497
|
+
if (!tokens.gitlab)
|
|
498
|
+
console.log(chalk_1.default.gray(' fraim setup --gitlab'));
|
|
499
|
+
if (!tokens.jira)
|
|
500
|
+
console.log(chalk_1.default.gray(' fraim setup --jira'));
|
|
395
501
|
}
|
|
396
502
|
};
|
|
397
503
|
exports.runSetup = runSetup;
|
|
@@ -400,8 +506,12 @@ exports.setupCommand = new commander_1.Command('setup')
|
|
|
400
506
|
.option('--key <key>', 'FRAIM API key')
|
|
401
507
|
.option('--github-token <token>', 'GitHub Personal Access Token')
|
|
402
508
|
.option('--ado-token <token>', 'Azure DevOps Personal Access Token')
|
|
509
|
+
.option('--gitlab-token <token>', 'GitLab Personal Access Token')
|
|
510
|
+
.option('--jira-token <token>', 'Jira API token')
|
|
403
511
|
.option('--github', 'Add/update GitHub integration')
|
|
404
512
|
.option('--ado', 'Add/update Azure DevOps integration')
|
|
513
|
+
.option('--gitlab', 'Add/update GitLab integration')
|
|
514
|
+
.option('--jira', 'Add/update Jira integration')
|
|
405
515
|
.option('--all', 'Configure all detected IDEs (deprecated)')
|
|
406
516
|
.option('--ide <ides>', 'Configure specific IDEs (deprecated)')
|
|
407
517
|
.action(exports.runSetup);
|
|
@@ -12,7 +12,16 @@ const ide_detector_1 = require("./ide-detector");
|
|
|
12
12
|
const mcp_config_generator_1 = require("./mcp-config-generator");
|
|
13
13
|
const token_validator_1 = require("./token-validator");
|
|
14
14
|
const codex_local_config_1 = require("./codex-local-config");
|
|
15
|
-
const
|
|
15
|
+
const normalizePlatformTokens = (tokenInput) => {
|
|
16
|
+
if (!tokenInput)
|
|
17
|
+
return {};
|
|
18
|
+
if (typeof tokenInput === 'string') {
|
|
19
|
+
return tokenInput ? { github: tokenInput } : {};
|
|
20
|
+
}
|
|
21
|
+
return tokenInput;
|
|
22
|
+
};
|
|
23
|
+
const promptForIDESelection = async (detectedIDEs, tokenInput) => {
|
|
24
|
+
const tokens = normalizePlatformTokens(tokenInput);
|
|
16
25
|
console.log(chalk_1.default.green(`ā
Found ${detectedIDEs.length} IDEs that can be configured with FRAIM:\n`));
|
|
17
26
|
detectedIDEs.forEach((ide, index) => {
|
|
18
27
|
const configExists = fs_1.default.existsSync((0, ide_detector_1.expandPath)(ide.configPath));
|
|
@@ -24,8 +33,14 @@ const promptForIDESelection = async (detectedIDEs, githubToken) => {
|
|
|
24
33
|
console.log(chalk_1.default.blue('FRAIM will add these MCP servers to selected IDEs:'));
|
|
25
34
|
console.log(chalk_1.default.gray(' ⢠fraim (required for FRAIM workflows)'));
|
|
26
35
|
console.log(chalk_1.default.gray(' ⢠git (version control integration)'));
|
|
27
|
-
if (
|
|
28
|
-
console.log(chalk_1.default.gray('
|
|
36
|
+
if (tokens.github) {
|
|
37
|
+
console.log(chalk_1.default.gray(' - github (GitHub API access)'));
|
|
38
|
+
}
|
|
39
|
+
if (tokens.gitlab) {
|
|
40
|
+
console.log(chalk_1.default.gray(' - gitlab (GitLab API access)'));
|
|
41
|
+
}
|
|
42
|
+
if (tokens.jira) {
|
|
43
|
+
console.log(chalk_1.default.gray(' - jira (Jira issue tracking)'));
|
|
29
44
|
}
|
|
30
45
|
console.log(chalk_1.default.gray(' ⢠playwright (browser automation)'));
|
|
31
46
|
console.log(chalk_1.default.yellow('\nš” Existing MCP servers will be preserved - only missing servers will be added.'));
|
|
@@ -197,7 +212,8 @@ const validateSetupResults = async (configuredIDEs) => {
|
|
|
197
212
|
}
|
|
198
213
|
};
|
|
199
214
|
exports.validateSetupResults = validateSetupResults;
|
|
200
|
-
const configureIDEMCP = async (ide, fraimKey,
|
|
215
|
+
const configureIDEMCP = async (ide, fraimKey, tokenInput) => {
|
|
216
|
+
const tokens = normalizePlatformTokens(tokenInput);
|
|
201
217
|
const configPath = (0, ide_detector_1.expandPath)(ide.configPath);
|
|
202
218
|
console.log(chalk_1.default.blue(`š§ Configuring ${ide.name}...`));
|
|
203
219
|
// Create backup
|
|
@@ -228,8 +244,8 @@ const configureIDEMCP = async (ide, fraimKey, githubToken) => {
|
|
|
228
244
|
existingTomlContent = fs_1.default.readFileSync(configPath, 'utf8');
|
|
229
245
|
console.log(chalk_1.default.gray(` š Found existing TOML config`));
|
|
230
246
|
}
|
|
231
|
-
const newTomlContent = (0, mcp_config_generator_1.generateMCPConfig)(ide.configType, fraimKey,
|
|
232
|
-
const serversToAdd = ['fraim', 'git', 'github', 'playwright'];
|
|
247
|
+
const newTomlContent = (0, mcp_config_generator_1.generateMCPConfig)(ide.configType, fraimKey, tokens);
|
|
248
|
+
const serversToAdd = ['fraim', 'git', 'github', 'gitlab', 'jira', 'playwright'];
|
|
233
249
|
const mergeResult = (0, mcp_config_generator_1.mergeTomlMCPServers)(existingTomlContent, newTomlContent, serversToAdd);
|
|
234
250
|
fs_1.default.writeFileSync(configPath, mergeResult.content);
|
|
235
251
|
mergeResult.addedServers.forEach(server => {
|
|
@@ -244,7 +260,7 @@ const configureIDEMCP = async (ide, fraimKey, githubToken) => {
|
|
|
244
260
|
}
|
|
245
261
|
else {
|
|
246
262
|
// For JSON configs - intelligent merging
|
|
247
|
-
const newConfig = (0, mcp_config_generator_1.generateMCPConfig)(ide.configType, fraimKey,
|
|
263
|
+
const newConfig = (0, mcp_config_generator_1.generateMCPConfig)(ide.configType, fraimKey, tokens);
|
|
248
264
|
const newMCPServers = newConfig[serversKey] || newConfig.mcpServers || {};
|
|
249
265
|
// Merge MCP servers intelligently
|
|
250
266
|
const mergedMCPServers = { ...existingMCPServers };
|
|
@@ -280,7 +296,8 @@ const configureIDEMCP = async (ide, fraimKey, githubToken) => {
|
|
|
280
296
|
console.log(chalk_1.default.green(` ā
${status} local Codex config: ${localResult.path}`));
|
|
281
297
|
}
|
|
282
298
|
};
|
|
283
|
-
const autoConfigureMCP = async (fraimKey,
|
|
299
|
+
const autoConfigureMCP = async (fraimKey, tokenInput, selectedIDEs) => {
|
|
300
|
+
const tokens = normalizePlatformTokens(tokenInput);
|
|
284
301
|
const detectedIDEs = (0, ide_detector_1.detectInstalledIDEs)();
|
|
285
302
|
if (detectedIDEs.length === 0) {
|
|
286
303
|
console.log(chalk_1.default.yellow('ā ļø No supported IDEs detected.'));
|
|
@@ -312,7 +329,7 @@ const autoConfigureMCP = async (fraimKey, githubToken, selectedIDEs) => {
|
|
|
312
329
|
}
|
|
313
330
|
else {
|
|
314
331
|
// Interactive selection
|
|
315
|
-
idesToConfigure = await (0, exports.promptForIDESelection)(detectedIDEs,
|
|
332
|
+
idesToConfigure = await (0, exports.promptForIDESelection)(detectedIDEs, tokens);
|
|
316
333
|
}
|
|
317
334
|
if (idesToConfigure.length === 0) {
|
|
318
335
|
console.log(chalk_1.default.yellow('ā ļø No IDEs selected for configuration.'));
|
|
@@ -326,7 +343,7 @@ const autoConfigureMCP = async (fraimKey, githubToken, selectedIDEs) => {
|
|
|
326
343
|
};
|
|
327
344
|
for (const ide of idesToConfigure) {
|
|
328
345
|
try {
|
|
329
|
-
await configureIDEMCP(ide, fraimKey,
|
|
346
|
+
await configureIDEMCP(ide, fraimKey, tokens);
|
|
330
347
|
results.successful.push(ide.name);
|
|
331
348
|
}
|
|
332
349
|
catch (error) {
|