fraim-framework 2.0.60 ā 2.0.63
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/bin/fraim-mcp.js +25 -9
- package/dist/src/cli/commands/init-project.js +98 -26
- package/dist/src/cli/commands/init.js +81 -23
- package/dist/src/cli/commands/setup.js +275 -47
- package/dist/src/local-mcp-server/stdio-server.js +507 -30
- package/dist/src/utils/enforcement-utils.js +239 -0
- package/dist/src/utils/git-utils.js +0 -27
- package/dist/src/utils/platform-detection.js +1 -1
- package/dist/src/utils/script-sync-utils.js +6 -1
- package/dist/src/utils/validate-workflows.js +101 -0
- package/dist/src/utils/workflow-parser.js +41 -9
- package/index.js +1 -1
- package/package.json +4 -3
- package/bin/fraim.js +0 -23
- package/dist/src/cli/commands/mcp.js +0 -65
- package/dist/src/fraim/issue-tracking/ado-provider.js +0 -304
- package/dist/src/fraim/issue-tracking/factory.js +0 -63
- package/dist/src/fraim/issue-tracking/github-provider.js +0 -200
- package/dist/src/fraim/issue-tracking/types.js +0 -7
- package/dist/src/fraim/issue-tracking-config.js +0 -83
|
@@ -66,16 +66,161 @@ const promptForFraimKey = async () => {
|
|
|
66
66
|
console.log(chalk_1.default.gray('Please ensure you have a valid FRAIM key and try again.'));
|
|
67
67
|
process.exit(1);
|
|
68
68
|
};
|
|
69
|
-
const
|
|
69
|
+
const promptForMode = async () => {
|
|
70
|
+
console.log(chalk_1.default.blue('\nšÆ Usage Mode Selection'));
|
|
71
|
+
console.log(chalk_1.default.gray('Choose how you want to use FRAIM:\n'));
|
|
72
|
+
const response = await (0, prompts_1.default)({
|
|
73
|
+
type: 'select',
|
|
74
|
+
name: 'mode',
|
|
75
|
+
message: 'Select your preferred mode',
|
|
76
|
+
choices: [
|
|
77
|
+
{
|
|
78
|
+
title: 'Integrated Mode',
|
|
79
|
+
value: 'integrated',
|
|
80
|
+
description: 'Full platform integration with GitHub/ADO for issue tracking and PRs'
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
title: 'Conversational Mode',
|
|
84
|
+
value: 'conversational',
|
|
85
|
+
description: 'AI workflows only, no platform integration required'
|
|
86
|
+
}
|
|
87
|
+
],
|
|
88
|
+
initial: 0
|
|
89
|
+
});
|
|
90
|
+
if (!response.mode) {
|
|
91
|
+
console.log(chalk_1.default.yellow('\nā¹ļø No mode selected, defaulting to Integrated Mode'));
|
|
92
|
+
return 'integrated';
|
|
93
|
+
}
|
|
94
|
+
if (response.mode === 'conversational') {
|
|
95
|
+
console.log(chalk_1.default.blue('\nā Conversational mode selected'));
|
|
96
|
+
console.log(chalk_1.default.gray(' You can use FRAIM workflows without platform credentials.'));
|
|
97
|
+
console.log(chalk_1.default.gray(' Platform features (issues, PRs) will be unavailable.\n'));
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
console.log(chalk_1.default.blue('\nā Integrated mode selected'));
|
|
101
|
+
console.log(chalk_1.default.gray(' Full platform integration will be available.\n'));
|
|
102
|
+
}
|
|
103
|
+
return response.mode;
|
|
104
|
+
};
|
|
105
|
+
const promptForPlatforms = async () => {
|
|
106
|
+
console.log(chalk_1.default.blue('\nš§ Platform Selection'));
|
|
107
|
+
console.log(chalk_1.default.gray('Which platforms do you want to integrate with?\n'));
|
|
108
|
+
const response = await (0, prompts_1.default)({
|
|
109
|
+
type: 'multiselect',
|
|
110
|
+
name: 'platforms',
|
|
111
|
+
message: 'Select platforms (Space to select, Enter to confirm)',
|
|
112
|
+
choices: [
|
|
113
|
+
{ title: 'GitHub', value: 'github', selected: true },
|
|
114
|
+
{ title: 'Azure DevOps', value: 'ado', selected: false }
|
|
115
|
+
],
|
|
116
|
+
min: 1
|
|
117
|
+
});
|
|
118
|
+
if (!response.platforms || response.platforms.length === 0) {
|
|
119
|
+
console.log(chalk_1.default.yellow('\nā¹ļø No platforms selected, defaulting to GitHub'));
|
|
120
|
+
return { github: true, ado: false };
|
|
121
|
+
}
|
|
122
|
+
const platforms = {
|
|
123
|
+
github: response.platforms.includes('github'),
|
|
124
|
+
ado: response.platforms.includes('ado')
|
|
125
|
+
};
|
|
126
|
+
console.log(chalk_1.default.blue('\nā Selected platforms:'));
|
|
127
|
+
if (platforms.github)
|
|
128
|
+
console.log(chalk_1.default.gray(' ⢠GitHub'));
|
|
129
|
+
if (platforms.ado)
|
|
130
|
+
console.log(chalk_1.default.gray(' ⢠Azure DevOps'));
|
|
131
|
+
console.log();
|
|
132
|
+
return platforms;
|
|
133
|
+
};
|
|
134
|
+
const promptForADOToken = async () => {
|
|
135
|
+
console.log(chalk_1.default.blue('\nš§ Azure DevOps Integration Setup'));
|
|
136
|
+
console.log('FRAIM requires an Azure DevOps Personal Access Token for ADO integration.\n');
|
|
137
|
+
console.log(chalk_1.default.yellow('š Why ADO token is required:'));
|
|
138
|
+
console.log(chalk_1.default.gray(' ⢠Create and manage work items'));
|
|
139
|
+
console.log(chalk_1.default.gray(' ⢠Access repository information'));
|
|
140
|
+
console.log(chalk_1.default.gray(' ⢠Perform git operations'));
|
|
141
|
+
console.log(chalk_1.default.gray(' ⢠Enable full development workflow automation\n'));
|
|
142
|
+
const hasToken = await (0, prompts_1.default)({
|
|
143
|
+
type: 'confirm',
|
|
144
|
+
name: 'hasToken',
|
|
145
|
+
message: 'Do you have an Azure DevOps Personal Access Token?',
|
|
146
|
+
initial: false
|
|
147
|
+
});
|
|
148
|
+
if (!hasToken.hasToken) {
|
|
149
|
+
console.log(chalk_1.default.yellow('\nš To create an Azure DevOps Personal Access Token:'));
|
|
150
|
+
console.log(chalk_1.default.gray(' 1. Go to https://dev.azure.com/{your-org}/_usersSettings/tokens'));
|
|
151
|
+
console.log(chalk_1.default.gray(' 2. Click "New Token"'));
|
|
152
|
+
console.log(chalk_1.default.gray(' 3. Select these scopes:'));
|
|
153
|
+
console.log(chalk_1.default.gray(' ⢠Code (Read & Write)'));
|
|
154
|
+
console.log(chalk_1.default.gray(' ⢠Work Items (Read & Write)'));
|
|
155
|
+
console.log(chalk_1.default.gray(' ⢠Project and Team (Read)'));
|
|
156
|
+
console.log(chalk_1.default.gray(' 4. Set expiration (recommend 1 year)'));
|
|
157
|
+
console.log(chalk_1.default.gray(' 5. Click "Create" and copy the token immediately'));
|
|
158
|
+
console.log(chalk_1.default.yellow(' ā ļø You won\'t be able to see the token again!\n'));
|
|
159
|
+
}
|
|
160
|
+
let token = null;
|
|
161
|
+
let attempts = 0;
|
|
162
|
+
const maxAttempts = 3;
|
|
163
|
+
while (!token && attempts < maxAttempts) {
|
|
164
|
+
const tokenResponse = await (0, prompts_1.default)({
|
|
165
|
+
type: 'password',
|
|
166
|
+
name: 'token',
|
|
167
|
+
message: attempts === 0
|
|
168
|
+
? 'Enter your Azure DevOps PAT'
|
|
169
|
+
: `Enter your Azure DevOps PAT (attempt ${attempts + 1}/${maxAttempts})`,
|
|
170
|
+
validate: (value) => {
|
|
171
|
+
if (!value)
|
|
172
|
+
return 'ADO token is required';
|
|
173
|
+
if (value.length < 20)
|
|
174
|
+
return 'Token seems too short';
|
|
175
|
+
return true;
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
if (!tokenResponse.token) {
|
|
179
|
+
console.log(chalk_1.default.red('\nā ADO token is required for Azure DevOps integration.'));
|
|
180
|
+
attempts++;
|
|
181
|
+
if (attempts < maxAttempts) {
|
|
182
|
+
const retry = await (0, prompts_1.default)({
|
|
183
|
+
type: 'confirm',
|
|
184
|
+
name: 'retry',
|
|
185
|
+
message: 'Would you like to try entering the token again?',
|
|
186
|
+
initial: true
|
|
187
|
+
});
|
|
188
|
+
if (!retry.retry)
|
|
189
|
+
break;
|
|
190
|
+
}
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
token = tokenResponse.token;
|
|
194
|
+
console.log(chalk_1.default.green('ā
Azure DevOps token received\n'));
|
|
195
|
+
return token; // Token is definitely set here
|
|
196
|
+
}
|
|
197
|
+
throw new Error('Failed to get Azure DevOps token');
|
|
198
|
+
};
|
|
199
|
+
const saveGlobalConfig = (fraimKey, mode, tokens) => {
|
|
70
200
|
const globalConfigDir = path_1.default.join(os_1.default.homedir(), '.fraim');
|
|
71
201
|
const globalConfigPath = path_1.default.join(globalConfigDir, 'config.json');
|
|
72
202
|
if (!fs_1.default.existsSync(globalConfigDir)) {
|
|
73
203
|
fs_1.default.mkdirSync(globalConfigDir, { recursive: true });
|
|
74
204
|
}
|
|
205
|
+
// Read existing config to preserve any existing tokens
|
|
206
|
+
let existingConfig = {};
|
|
207
|
+
if (fs_1.default.existsSync(globalConfigPath)) {
|
|
208
|
+
try {
|
|
209
|
+
existingConfig = JSON.parse(fs_1.default.readFileSync(globalConfigPath, 'utf8'));
|
|
210
|
+
}
|
|
211
|
+
catch (e) {
|
|
212
|
+
// Ignore parse errors, will create new config
|
|
213
|
+
}
|
|
214
|
+
}
|
|
75
215
|
const config = {
|
|
76
216
|
version: (0, version_utils_1.getFraimVersion)(),
|
|
77
217
|
apiKey: fraimKey,
|
|
78
|
-
|
|
218
|
+
mode: mode,
|
|
219
|
+
tokens: {
|
|
220
|
+
// Preserve existing tokens and merge with new ones
|
|
221
|
+
...(existingConfig.tokens || {}),
|
|
222
|
+
...tokens
|
|
223
|
+
},
|
|
79
224
|
configuredAt: new Date().toISOString(),
|
|
80
225
|
userPreferences: {
|
|
81
226
|
autoSync: true,
|
|
@@ -87,6 +232,10 @@ const saveGlobalConfig = (fraimKey, githubToken) => {
|
|
|
87
232
|
};
|
|
88
233
|
const syncGlobalScripts = () => {
|
|
89
234
|
console.log(chalk_1.default.blue('š Syncing FRAIM scripts to user directory...'));
|
|
235
|
+
// Ensure user directories exist and initialize user config
|
|
236
|
+
const { ensureUserFraimDirectories } = require('../../utils/script-sync-utils');
|
|
237
|
+
ensureUserFraimDirectories();
|
|
238
|
+
console.log(chalk_1.default.green('ā
Initialized user configuration'));
|
|
90
239
|
// Find registry path
|
|
91
240
|
let registryPath = path_1.default.join(__dirname, '../../../../registry');
|
|
92
241
|
if (!fs_1.default.existsSync(registryPath)) {
|
|
@@ -105,19 +254,43 @@ const syncGlobalScripts = () => {
|
|
|
105
254
|
};
|
|
106
255
|
const runSetup = async (options) => {
|
|
107
256
|
console.log(chalk_1.default.blue('š Welcome to FRAIM! Let\'s get you set up.\n'));
|
|
108
|
-
//
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
257
|
+
// Determine if this is an update (adding platforms to existing setup)
|
|
258
|
+
const globalConfigPath = path_1.default.join(os_1.default.homedir(), '.fraim', 'config.json');
|
|
259
|
+
const isUpdate = fs_1.default.existsSync(globalConfigPath) && (options.github || options.ado);
|
|
260
|
+
let fraimKey;
|
|
261
|
+
let mode;
|
|
262
|
+
const tokens = {};
|
|
263
|
+
if (isUpdate) {
|
|
264
|
+
// Update existing setup - add platforms
|
|
265
|
+
console.log(chalk_1.default.blue('š Updating existing FRAIM configuration...\n'));
|
|
266
|
+
try {
|
|
267
|
+
const existingConfig = JSON.parse(fs_1.default.readFileSync(globalConfigPath, 'utf8'));
|
|
268
|
+
fraimKey = existingConfig.apiKey;
|
|
269
|
+
mode = existingConfig.mode || 'integrated';
|
|
270
|
+
// Preserve existing tokens
|
|
271
|
+
if (existingConfig.tokens) {
|
|
272
|
+
if (existingConfig.tokens.github)
|
|
273
|
+
tokens.github = existingConfig.tokens.github;
|
|
274
|
+
if (existingConfig.tokens.ado)
|
|
275
|
+
tokens.ado = existingConfig.tokens.ado;
|
|
276
|
+
}
|
|
277
|
+
console.log(chalk_1.default.gray(` Current mode: ${mode}`));
|
|
278
|
+
console.log(chalk_1.default.gray(` Existing tokens: ${tokens.github ? 'GitHub ā' : ''} ${tokens.ado ? 'ADO ā' : ''}\n`));
|
|
279
|
+
}
|
|
280
|
+
catch (e) {
|
|
281
|
+
console.log(chalk_1.default.red('ā Failed to read existing configuration'));
|
|
282
|
+
process.exit(1);
|
|
283
|
+
}
|
|
119
284
|
}
|
|
120
285
|
else {
|
|
286
|
+
// Initial setup
|
|
287
|
+
console.log(chalk_1.default.yellow('š This setup will:'));
|
|
288
|
+
console.log(chalk_1.default.gray(' ⢠Validate your FRAIM key'));
|
|
289
|
+
console.log(chalk_1.default.gray(' ⢠Choose your usage mode'));
|
|
290
|
+
console.log(chalk_1.default.gray(' ⢠Configure platform integrations'));
|
|
291
|
+
console.log(chalk_1.default.gray(' ⢠Sync FRAIM scripts to your system\n'));
|
|
292
|
+
// Get FRAIM key
|
|
293
|
+
fraimKey = options.key || await promptForFraimKey();
|
|
121
294
|
if (!(0, token_validator_1.isValidTokenFormat)(fraimKey, 'fraim')) {
|
|
122
295
|
console.log(chalk_1.default.red('ā Invalid FRAIM key format. Key must start with fraim_'));
|
|
123
296
|
process.exit(1);
|
|
@@ -129,49 +302,104 @@ const runSetup = async (options) => {
|
|
|
129
302
|
process.exit(1);
|
|
130
303
|
}
|
|
131
304
|
console.log(chalk_1.default.green('ā
FRAIM key validated\n'));
|
|
305
|
+
// Ask for mode preference
|
|
306
|
+
mode = await promptForMode();
|
|
132
307
|
}
|
|
133
|
-
//
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
308
|
+
// Handle platform tokens based on mode
|
|
309
|
+
if (mode === 'integrated') {
|
|
310
|
+
let needGitHub = options.github || false;
|
|
311
|
+
let needADO = options.ado || false;
|
|
312
|
+
// If no specific platform flags and not an update, ask user
|
|
313
|
+
if (!isUpdate && !needGitHub && !needADO) {
|
|
314
|
+
const platforms = await promptForPlatforms();
|
|
315
|
+
needGitHub = platforms.github;
|
|
316
|
+
needADO = platforms.ado;
|
|
142
317
|
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
318
|
+
// Get GitHub token if needed
|
|
319
|
+
if (needGitHub && !tokens.github) {
|
|
320
|
+
if (options.githubToken) {
|
|
321
|
+
if (!(0, token_validator_1.isValidTokenFormat)(options.githubToken, 'github')) {
|
|
322
|
+
console.log(chalk_1.default.red('ā Invalid GitHub token format'));
|
|
323
|
+
process.exit(1);
|
|
324
|
+
}
|
|
325
|
+
console.log(chalk_1.default.blue('š Validating GitHub token...'));
|
|
326
|
+
const isValid = await (0, token_validator_1.validateGitHubToken)(options.githubToken);
|
|
327
|
+
if (!isValid) {
|
|
328
|
+
console.log(chalk_1.default.red('ā Invalid GitHub token'));
|
|
329
|
+
process.exit(1);
|
|
330
|
+
}
|
|
331
|
+
tokens.github = options.githubToken;
|
|
332
|
+
console.log(chalk_1.default.green('ā
GitHub token validated\n'));
|
|
333
|
+
}
|
|
334
|
+
else {
|
|
335
|
+
try {
|
|
336
|
+
tokens.github = await (0, auto_mcp_setup_1.promptForGitHubToken)();
|
|
337
|
+
}
|
|
338
|
+
catch (e) {
|
|
339
|
+
// promptForGitHubToken calls process.exit on failure
|
|
340
|
+
process.exit(1);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
// Get ADO token if needed
|
|
345
|
+
if (needADO && !tokens.ado) {
|
|
346
|
+
if (options.adoToken) {
|
|
347
|
+
tokens.ado = options.adoToken;
|
|
348
|
+
console.log(chalk_1.default.green('ā
Azure DevOps token received\n'));
|
|
349
|
+
}
|
|
350
|
+
else {
|
|
351
|
+
try {
|
|
352
|
+
tokens.ado = await promptForADOToken();
|
|
353
|
+
}
|
|
354
|
+
catch (e) {
|
|
355
|
+
console.log(chalk_1.default.red('ā Failed to get Azure DevOps token'));
|
|
356
|
+
process.exit(1);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
if (!tokens.github && !tokens.ado) {
|
|
361
|
+
console.log(chalk_1.default.yellow('ā ļø No platform tokens configured.'));
|
|
362
|
+
console.log(chalk_1.default.gray(' You can add them later with:'));
|
|
363
|
+
console.log(chalk_1.default.cyan(' fraim setup --github'));
|
|
364
|
+
console.log(chalk_1.default.cyan(' fraim setup --ado\n'));
|
|
148
365
|
}
|
|
149
|
-
|
|
366
|
+
}
|
|
367
|
+
else {
|
|
368
|
+
console.log(chalk_1.default.gray('ā¹ļø Conversational mode: No platform tokens needed\n'));
|
|
150
369
|
}
|
|
151
370
|
// Save global configuration
|
|
152
371
|
console.log(chalk_1.default.blue('š¾ Saving global configuration...'));
|
|
153
|
-
saveGlobalConfig(fraimKey,
|
|
154
|
-
// Sync global scripts
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
console.log(chalk_1.default.cyan('
|
|
165
|
-
console.log(chalk_1.default.cyan('
|
|
166
|
-
console.log(chalk_1.default.cyan('
|
|
167
|
-
console.log(chalk_1.default.cyan('
|
|
168
|
-
|
|
372
|
+
saveGlobalConfig(fraimKey, mode, tokens);
|
|
373
|
+
// Sync global scripts (only on initial setup)
|
|
374
|
+
if (!isUpdate) {
|
|
375
|
+
syncGlobalScripts();
|
|
376
|
+
}
|
|
377
|
+
// Show summary
|
|
378
|
+
console.log(chalk_1.default.green('\nšÆ Configuration complete!'));
|
|
379
|
+
console.log(chalk_1.default.gray(` Mode: ${mode}`));
|
|
380
|
+
if (mode === 'integrated') {
|
|
381
|
+
console.log(chalk_1.default.gray(` Platforms: ${tokens.github ? 'GitHub ā' : 'GitHub ā'} ${tokens.ado ? 'ADO ā' : 'ADO ā'}`));
|
|
382
|
+
}
|
|
383
|
+
console.log(chalk_1.default.cyan('\nš Next steps:'));
|
|
384
|
+
console.log(chalk_1.default.cyan(' 1. Go to any project directory'));
|
|
385
|
+
console.log(chalk_1.default.cyan(' 2. Run: fraim init-project'));
|
|
386
|
+
console.log(chalk_1.default.cyan(' 3. Start using FRAIM workflows'));
|
|
387
|
+
if (mode === 'integrated' && (!tokens.github || !tokens.ado)) {
|
|
388
|
+
console.log(chalk_1.default.gray('\nš” To add more platforms later:'));
|
|
389
|
+
if (!tokens.github)
|
|
390
|
+
console.log(chalk_1.default.gray(' fraim setup --github'));
|
|
391
|
+
if (!tokens.ado)
|
|
392
|
+
console.log(chalk_1.default.gray(' fraim setup --ado'));
|
|
393
|
+
}
|
|
169
394
|
};
|
|
170
395
|
exports.runSetup = runSetup;
|
|
171
396
|
exports.setupCommand = new commander_1.Command('setup')
|
|
172
|
-
.description('Complete global FRAIM setup with
|
|
397
|
+
.description('Complete global FRAIM setup with platform configuration')
|
|
173
398
|
.option('--key <key>', 'FRAIM API key')
|
|
174
399
|
.option('--github-token <token>', 'GitHub Personal Access Token')
|
|
175
|
-
.option('--
|
|
176
|
-
.option('--
|
|
400
|
+
.option('--ado-token <token>', 'Azure DevOps Personal Access Token')
|
|
401
|
+
.option('--github', 'Add/update GitHub integration')
|
|
402
|
+
.option('--ado', 'Add/update Azure DevOps integration')
|
|
403
|
+
.option('--all', 'Configure all detected IDEs (deprecated)')
|
|
404
|
+
.option('--ide <ides>', 'Configure specific IDEs (deprecated)')
|
|
177
405
|
.action(exports.runSetup);
|