fraim-framework 2.0.82 → 2.0.83

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.
@@ -1,18 +1,54 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
2
35
  var __importDefault = (this && this.__importDefault) || function (mod) {
3
36
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
37
  };
5
38
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.setupCommand = exports.runSetup = void 0;
39
+ exports.setupCommandInitialization = exports.setupCommand = exports.runSetup = void 0;
40
+ // Refactored setup.ts - Generic provider system with zero hardcoded provider knowledge
7
41
  const commander_1 = require("commander");
8
42
  const chalk_1 = __importDefault(require("chalk"));
9
43
  const prompts_1 = __importDefault(require("prompts"));
10
44
  const fs_1 = __importDefault(require("fs"));
11
45
  const path_1 = __importDefault(require("path"));
12
- const token_validator_1 = require("../setup/token-validator");
13
46
  const auto_mcp_setup_1 = require("../setup/auto-mcp-setup");
14
47
  const version_utils_1 = require("../utils/version-utils");
15
48
  const platform_detection_1 = require("../utils/platform-detection");
49
+ const get_provider_client_1 = require("../api/get-provider-client");
50
+ const provider_prompts_1 = require("../setup/provider-prompts");
51
+ const provider_registry_1 = require("../providers/provider-registry");
16
52
  const init_project_1 = require("./init-project");
17
53
  const script_sync_utils_1 = require("../utils/script-sync-utils");
18
54
  const promptForFraimKey = async () => {
@@ -31,9 +67,7 @@ const promptForFraimKey = async () => {
31
67
  validate: (value) => {
32
68
  if (!value)
33
69
  return 'FRAIM key is required';
34
- if ((0, token_validator_1.isValidTokenFormat)(value, 'fraim'))
35
- return true;
36
- return 'Please enter a valid FRAIM key (starts with fraim_)';
70
+ return true;
37
71
  }
38
72
  });
39
73
  if (!keyResponse.key) {
@@ -52,16 +86,8 @@ const promptForFraimKey = async () => {
52
86
  attempts++;
53
87
  continue;
54
88
  }
55
- // Validate key
56
- const isValid = await (0, token_validator_1.validateFraimKey)(keyResponse.key);
57
- if (isValid) {
58
- console.log(chalk_1.default.green('✅ FRAIM key validated\n'));
59
- return keyResponse.key;
60
- }
61
- else {
62
- console.log(chalk_1.default.red('❌ Invalid FRAIM key\n'));
63
- attempts++;
64
- }
89
+ console.log(chalk_1.default.green('✅ FRAIM key accepted\n'));
90
+ return keyResponse.key;
65
91
  }
66
92
  console.log(chalk_1.default.red('\n❌ Maximum attempts reached. Setup cancelled.'));
67
93
  console.log(chalk_1.default.gray('Please ensure you have a valid FRAIM key and try again.'));
@@ -83,7 +109,7 @@ const promptForMode = async () => {
83
109
  {
84
110
  title: 'Split Mode',
85
111
  value: 'split',
86
- description: 'Separate platforms for code hosting and issue tracking (e.g., GitHub + Jira)'
112
+ description: 'Separate platforms for code hosting and issue tracking'
87
113
  },
88
114
  {
89
115
  title: 'Conversational Mode',
@@ -104,8 +130,7 @@ const promptForMode = async () => {
104
130
  }
105
131
  else if (response.mode === 'split') {
106
132
  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'));
133
+ console.log(chalk_1.default.gray(' You can use different platforms for code hosting and issue tracking.\n'));
109
134
  }
110
135
  else {
111
136
  console.log(chalk_1.default.blue('\n✓ Integrated mode selected'));
@@ -113,242 +138,13 @@ const promptForMode = async () => {
113
138
  }
114
139
  return response.mode;
115
140
  };
116
- const promptForPlatforms = async () => {
117
- console.log(chalk_1.default.blue('\n🔧 Platform Selection'));
118
- console.log(chalk_1.default.gray('Which platforms do you want to integrate with?\n'));
119
- const response = await (0, prompts_1.default)({
120
- type: 'multiselect',
121
- name: 'platforms',
122
- message: 'Select platforms (Space to select, Enter to confirm)',
123
- choices: [
124
- { title: 'GitHub', value: 'github', selected: true },
125
- { title: 'Azure DevOps', value: 'ado', selected: false },
126
- { title: 'GitLab', value: 'gitlab', selected: false },
127
- { title: 'Jira', value: 'jira', selected: false }
128
- ],
129
- min: 1
130
- });
131
- if (!response.platforms || response.platforms.length === 0) {
132
- console.log(chalk_1.default.yellow('\nℹ️ No platforms selected, defaulting to GitHub'));
133
- return { github: true, ado: false, gitlab: false, jira: false };
134
- }
135
- const platforms = {
136
- github: response.platforms.includes('github'),
137
- ado: response.platforms.includes('ado'),
138
- gitlab: response.platforms.includes('gitlab'),
139
- jira: response.platforms.includes('jira')
140
- };
141
- console.log(chalk_1.default.blue('\n✓ Selected platforms:'));
142
- if (platforms.github)
143
- console.log(chalk_1.default.gray(' - GitHub'));
144
- if (platforms.ado)
145
- console.log(chalk_1.default.gray(' - Azure DevOps'));
146
- if (platforms.gitlab)
147
- console.log(chalk_1.default.gray(' - GitLab'));
148
- if (platforms.jira)
149
- console.log(chalk_1.default.gray(' - Jira'));
150
- console.log();
151
- return platforms;
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
- };
196
- const promptForADOToken = async () => {
197
- console.log(chalk_1.default.blue('\n🔧 Azure DevOps Integration Setup'));
198
- console.log('FRAIM requires an Azure DevOps Personal Access Token for ADO integration.\n');
199
- console.log(chalk_1.default.yellow('🔑 Why ADO token is required:'));
200
- console.log(chalk_1.default.gray(' • Create and manage work items'));
201
- console.log(chalk_1.default.gray(' • Access repository information'));
202
- console.log(chalk_1.default.gray(' • Perform git operations'));
203
- console.log(chalk_1.default.gray(' • Enable full development workflow automation\n'));
204
- const hasToken = await (0, prompts_1.default)({
205
- type: 'confirm',
206
- name: 'hasToken',
207
- message: 'Do you have an Azure DevOps Personal Access Token?',
208
- initial: false
209
- });
210
- if (!hasToken.hasToken) {
211
- console.log(chalk_1.default.yellow('\n📝 To create an Azure DevOps Personal Access Token:'));
212
- console.log(chalk_1.default.gray(' 1. Go to https://dev.azure.com/{your-org}/_usersSettings/tokens'));
213
- console.log(chalk_1.default.gray(' 2. Click "New Token"'));
214
- console.log(chalk_1.default.gray(' 3. Select these scopes:'));
215
- console.log(chalk_1.default.gray(' • Code (Read & Write)'));
216
- console.log(chalk_1.default.gray(' • Work Items (Read & Write)'));
217
- console.log(chalk_1.default.gray(' • Project and Team (Read)'));
218
- console.log(chalk_1.default.gray(' 4. Set expiration (recommend 1 year)'));
219
- console.log(chalk_1.default.gray(' 5. Click "Create" and copy the token immediately'));
220
- console.log(chalk_1.default.yellow(' ⚠️ You won\'t be able to see the token again!\n'));
221
- }
222
- let token = null;
223
- let attempts = 0;
224
- const maxAttempts = 3;
225
- while (!token && attempts < maxAttempts) {
226
- const tokenResponse = await (0, prompts_1.default)({
227
- type: 'password',
228
- name: 'token',
229
- message: attempts === 0
230
- ? 'Enter your Azure DevOps PAT'
231
- : `Enter your Azure DevOps PAT (attempt ${attempts + 1}/${maxAttempts})`,
232
- validate: (value) => {
233
- if (!value)
234
- return 'ADO token is required';
235
- if (value.length < 20)
236
- return 'Token seems too short';
237
- return true;
238
- }
239
- });
240
- if (!tokenResponse.token) {
241
- console.log(chalk_1.default.red('\n❌ ADO token is required for Azure DevOps integration.'));
242
- attempts++;
243
- if (attempts < maxAttempts) {
244
- const retry = await (0, prompts_1.default)({
245
- type: 'confirm',
246
- name: 'retry',
247
- message: 'Would you like to try entering the token again?',
248
- initial: true
249
- });
250
- if (!retry.retry)
251
- break;
252
- }
253
- continue;
254
- }
255
- token = tokenResponse.token;
256
- console.log(chalk_1.default.green('✅ Azure DevOps token received\n'));
257
- return token; // Token is definitely set here
258
- }
259
- throw new Error('Failed to get Azure DevOps token');
260
- };
261
- const promptForGitLabToken = async () => {
262
- console.log(chalk_1.default.blue('\nGitLab Integration Setup'));
263
- console.log('FRAIM requires a GitLab token for GitLab issue/repository MCP integration.\n');
264
- const tokenResponse = await (0, prompts_1.default)({
265
- type: 'password',
266
- name: 'token',
267
- message: 'Enter your GitLab token',
268
- validate: (value) => {
269
- if (!value)
270
- return 'GitLab token is required';
271
- if (!(0, token_validator_1.isValidTokenFormat)(value, 'gitlab'))
272
- return 'Please enter a valid GitLab token (typically starts with glpat-)';
273
- return true;
274
- }
275
- });
276
- if (!tokenResponse.token) {
277
- throw new Error('GitLab token is required');
278
- }
279
- console.log(chalk_1.default.green('GitLab token received\n'));
280
- return tokenResponse.token;
281
- };
282
- const promptForJiraCredentials = async () => {
283
- console.log(chalk_1.default.blue('\nJira Integration Setup'));
284
- console.log('FRAIM requires Jira credentials for MCP integration.\n');
285
- // Prompt for base URL
286
- const urlResponse = await (0, prompts_1.default)({
287
- type: 'text',
288
- name: 'baseUrl',
289
- message: 'Enter your Jira base URL (e.g., https://company.atlassian.net)',
290
- validate: (value) => {
291
- if (!value)
292
- return 'Jira base URL is required';
293
- // Basic URL validation
294
- try {
295
- new URL(value.startsWith('http') ? value : `https://${value}`);
296
- return true;
297
- }
298
- catch {
299
- return 'Please enter a valid URL';
300
- }
301
- }
302
- });
303
- if (!urlResponse.baseUrl) {
304
- throw new Error('Jira base URL is required');
305
- }
306
- // Prompt for email
307
- const emailResponse = await (0, prompts_1.default)({
308
- type: 'text',
309
- name: 'email',
310
- message: 'Enter your Jira account email',
311
- validate: (value) => {
312
- if (!value)
313
- return 'Jira email is required';
314
- if (!value.includes('@'))
315
- return 'Please enter a valid email address';
316
- return true;
317
- }
318
- });
319
- if (!emailResponse.email) {
320
- throw new Error('Jira email is required');
321
- }
322
- // Prompt for API token
323
- const tokenResponse = await (0, prompts_1.default)({
324
- type: 'password',
325
- name: 'token',
326
- message: 'Enter your Jira API token',
327
- validate: (value) => {
328
- if (!value)
329
- return 'Jira API token is required';
330
- if (!(0, token_validator_1.isValidTokenFormat)(value, 'jira'))
331
- return 'Please enter a valid Jira token';
332
- return true;
333
- }
334
- });
335
- if (!tokenResponse.token) {
336
- throw new Error('Jira API token is required');
337
- }
338
- console.log(chalk_1.default.green('Jira credentials received\n'));
339
- return {
340
- baseUrl: urlResponse.baseUrl,
341
- email: emailResponse.email,
342
- token: tokenResponse.token
343
- };
344
- };
345
- const saveGlobalConfig = (fraimKey, mode, tokens, jiraConfig) => {
141
+ const saveGlobalConfig = (fraimKey, mode, tokens, configs) => {
346
142
  const globalConfigDir = (0, script_sync_utils_1.getUserFraimDir)();
347
143
  const globalConfigPath = path_1.default.join(globalConfigDir, 'config.json');
348
144
  if (!fs_1.default.existsSync(globalConfigDir)) {
349
145
  fs_1.default.mkdirSync(globalConfigDir, { recursive: true });
350
146
  }
351
- // Read existing config to preserve any existing tokens
147
+ // Read existing config to preserve any existing data
352
148
  let existingConfig = {};
353
149
  if (fs_1.default.existsSync(globalConfigPath)) {
354
150
  try {
@@ -358,19 +154,23 @@ const saveGlobalConfig = (fraimKey, mode, tokens, jiraConfig) => {
358
154
  // Ignore parse errors, will create new config
359
155
  }
360
156
  }
157
+ // Merge provider configs (e.g., jiraConfig)
158
+ const providerConfigs = { ...(existingConfig.providerConfigs || {}) };
159
+ Object.entries(configs).forEach(([providerId, config]) => {
160
+ providerConfigs[`${providerId}Config`] = {
161
+ ...(providerConfigs[`${providerId}Config`] || {}),
162
+ ...config
163
+ };
164
+ });
361
165
  const config = {
362
166
  version: (0, version_utils_1.getFraimVersion)(),
363
167
  apiKey: fraimKey,
364
168
  mode: mode,
365
169
  tokens: {
366
- // Preserve existing tokens and merge with new ones
367
170
  ...(existingConfig.tokens || {}),
368
171
  ...tokens
369
172
  },
370
- jiraConfig: jiraConfig && (jiraConfig.baseUrl || jiraConfig.email) ? {
371
- ...(existingConfig.jiraConfig || {}),
372
- ...jiraConfig
373
- } : existingConfig.jiraConfig,
173
+ providerConfigs,
374
174
  configuredAt: new Date().toISOString(),
375
175
  userPreferences: {
376
176
  autoSync: true,
@@ -380,38 +180,102 @@ const saveGlobalConfig = (fraimKey, mode, tokens, jiraConfig) => {
380
180
  fs_1.default.writeFileSync(globalConfigPath, JSON.stringify(config, null, 2));
381
181
  console.log(chalk_1.default.green('✅ Global FRAIM configuration saved'));
382
182
  };
183
+ // Parse CLI options into generic format using provider registry from server
184
+ // Tokens/config are optional - will prompt if not provided
185
+ const parseLegacyOptions = async (options, fraimKey) => {
186
+ const requestedProviders = [];
187
+ const providedTokens = {};
188
+ const providedConfigs = {};
189
+ // Use provider registry (with fallback) instead of direct client call
190
+ const { getAllProviderIds, getProviderConfigRequirements } = await Promise.resolve().then(() => __importStar(require('../providers/provider-registry')));
191
+ const providerIds = await getAllProviderIds();
192
+ // Dynamically check for provider flags from server registry
193
+ for (const providerId of providerIds) {
194
+ // Check for provider flag (e.g., --github)
195
+ if (options[providerId]) {
196
+ requestedProviders.push(providerId);
197
+ }
198
+ // Check for token option (e.g., --github-token)
199
+ const tokenKey = `${providerId}Token`;
200
+ if (options[tokenKey]) {
201
+ providedTokens[providerId] = options[tokenKey];
202
+ }
203
+ // Check for provider-specific config options
204
+ const configReqs = await getProviderConfigRequirements(providerId);
205
+ if (configReqs.length > 0) {
206
+ const config = {};
207
+ let hasAnyConfig = false;
208
+ configReqs.forEach(req => {
209
+ // Use custom CLI option name if provided, otherwise convert key to camelCase
210
+ const optionSuffix = req.cliOptionName
211
+ ? req.cliOptionName.charAt(0).toUpperCase() + req.cliOptionName.slice(1)
212
+ : req.key.charAt(0).toUpperCase() + req.key.slice(1);
213
+ const optionKey = `${providerId}${optionSuffix}`;
214
+ if (options[optionKey]) {
215
+ config[req.key] = options[optionKey];
216
+ hasAnyConfig = true;
217
+ }
218
+ });
219
+ if (hasAnyConfig) {
220
+ providedConfigs[providerId] = config;
221
+ }
222
+ }
223
+ }
224
+ return { requestedProviders, providedTokens, providedConfigs };
225
+ };
383
226
  const runSetup = async (options) => {
384
227
  console.log(chalk_1.default.blue('🚀 Welcome to FRAIM! Let\'s get you set up.\n'));
385
228
  // Determine if this is an update (adding platforms to existing setup)
386
229
  const globalConfigPath = path_1.default.join((0, script_sync_utils_1.getUserFraimDir)(), 'config.json');
387
- const isUpdate = fs_1.default.existsSync(globalConfigPath) && (options.github || options.ado || options.gitlab || options.jira);
230
+ const isUpdate = fs_1.default.existsSync(globalConfigPath);
388
231
  let fraimKey;
389
232
  let mode;
390
233
  const tokens = {};
391
- let jiraConfig = {};
234
+ const configs = {};
235
+ let requestedProviders = [];
236
+ let providedTokens = {};
237
+ let providedConfigs = {};
392
238
  if (isUpdate) {
393
239
  // Update existing setup - add platforms
394
240
  console.log(chalk_1.default.blue('📝 Updating existing FRAIM configuration...\n'));
395
241
  try {
396
242
  const existingConfig = JSON.parse(fs_1.default.readFileSync(globalConfigPath, 'utf8'));
397
243
  fraimKey = existingConfig.apiKey;
244
+ // Now we can parse options with the FRAIM key
245
+ const parsed = await parseLegacyOptions(options, fraimKey);
246
+ requestedProviders = parsed.requestedProviders;
247
+ providedTokens = parsed.providedTokens;
248
+ providedConfigs = parsed.providedConfigs;
249
+ // Only proceed with update if providers were requested
250
+ if (requestedProviders.length === 0) {
251
+ console.log(chalk_1.default.yellow('⚠️ No providers specified for update.'));
252
+ console.log(chalk_1.default.gray('Use --github, --gitlab, --jira, etc. to add providers.'));
253
+ return;
254
+ }
398
255
  mode = existingConfig.mode || 'integrated';
399
256
  // Preserve existing tokens
400
257
  if (existingConfig.tokens) {
401
- if (existingConfig.tokens.github)
402
- tokens.github = existingConfig.tokens.github;
403
- if (existingConfig.tokens.ado)
404
- tokens.ado = existingConfig.tokens.ado;
405
- if (existingConfig.tokens.gitlab)
406
- tokens.gitlab = existingConfig.tokens.gitlab;
407
- if (existingConfig.tokens.jira)
408
- tokens.jira = existingConfig.tokens.jira;
258
+ Object.assign(tokens, existingConfig.tokens);
259
+ }
260
+ // Preserve existing configs
261
+ if (existingConfig.providerConfigs) {
262
+ Object.entries(existingConfig.providerConfigs).forEach(([key, value]) => {
263
+ const providerId = key.replace('Config', '');
264
+ configs[providerId] = value;
265
+ });
409
266
  }
410
267
  console.log(chalk_1.default.gray(` Current mode: ${mode}`));
411
- console.log(chalk_1.default.gray(` Existing tokens: ${tokens.github ? 'GitHub yes' : ''} ${tokens.ado ? 'ADO yes' : ''} ${tokens.gitlab ? 'GitLab yes' : ''} ${tokens.jira ? 'Jira yes' : ''}\n`));
268
+ // Show existing tokens
269
+ const { getProvider } = await Promise.resolve().then(() => __importStar(require('../providers/provider-registry')));
270
+ const providerNames = await Promise.all(Object.keys(tokens).map(async (id) => {
271
+ const provider = await getProvider(id);
272
+ return provider?.displayName || id;
273
+ }));
274
+ console.log(chalk_1.default.gray(` Existing tokens: ${providerNames.join(', ') || 'none'}\n`));
412
275
  }
413
276
  catch (e) {
414
277
  console.log(chalk_1.default.red('❌ Failed to read existing configuration'));
278
+ console.error('Error details:', e);
415
279
  process.exit(1);
416
280
  }
417
281
  }
@@ -424,136 +288,43 @@ const runSetup = async (options) => {
424
288
  console.log(chalk_1.default.gray(' • Sync FRAIM scripts to your system\n'));
425
289
  // Get FRAIM key
426
290
  fraimKey = options.key || await promptForFraimKey();
427
- if (!(0, token_validator_1.isValidTokenFormat)(fraimKey, 'fraim')) {
428
- console.log(chalk_1.default.red('❌ Invalid FRAIM key format. Key must start with fraim_'));
429
- process.exit(1);
430
- }
431
- console.log(chalk_1.default.blue('🔑 Validating FRAIM key...'));
432
- const isValid = await (0, token_validator_1.validateFraimKey)(fraimKey);
433
- if (!isValid) {
434
- console.log(chalk_1.default.red('❌ Invalid FRAIM key'));
435
- process.exit(1);
436
- }
437
- console.log(chalk_1.default.green('✅ FRAIM key validated\n'));
291
+ console.log(chalk_1.default.green('✅ FRAIM key accepted\n'));
438
292
  // Ask for mode preference
439
293
  mode = await promptForMode();
440
294
  }
441
295
  // Handle platform tokens based on mode
442
296
  if (mode === 'integrated') {
443
- let needGitHub = options.github || false;
444
- let needADO = options.ado || false;
445
- let needGitLab = options.gitlab || false;
446
- let needJira = options.jira || false;
447
- // If no specific platform flags and not an update, ask user
448
- if (!isUpdate && !needGitHub && !needADO && !needGitLab && !needJira) {
449
- const platforms = await promptForPlatforms();
450
- needGitHub = platforms.github;
451
- needADO = platforms.ado;
452
- needGitLab = platforms.gitlab;
453
- needJira = platforms.jira;
454
- }
455
- // Get GitHub token if needed
456
- if (needGitHub && !tokens.github) {
457
- if (options.githubToken) {
458
- if (!(0, token_validator_1.isValidTokenFormat)(options.githubToken, 'github')) {
459
- console.log(chalk_1.default.red('❌ Invalid GitHub token format'));
460
- process.exit(1);
461
- }
462
- console.log(chalk_1.default.blue('🔍 Validating GitHub token...'));
463
- const isValid = await (0, token_validator_1.validateGitHubToken)(options.githubToken);
464
- if (!isValid) {
465
- console.log(chalk_1.default.red('❌ Invalid GitHub token'));
466
- process.exit(1);
467
- }
468
- tokens.github = options.githubToken;
469
- console.log(chalk_1.default.green('✅ GitHub token validated\n'));
470
- }
471
- else {
472
- try {
473
- tokens.github = await (0, auto_mcp_setup_1.promptForGitHubToken)();
474
- }
475
- catch (e) {
476
- // promptForGitHubToken calls process.exit on failure
477
- process.exit(1);
478
- }
479
- }
480
- }
481
- // Get ADO token if needed
482
- if (needADO && !tokens.ado) {
483
- if (options.adoToken) {
484
- tokens.ado = options.adoToken;
485
- console.log(chalk_1.default.green('✅ Azure DevOps token received\n'));
486
- }
487
- else {
297
+ let providersToSetup = requestedProviders;
298
+ // If no specific providers requested and not an update, ask user
299
+ if (!isUpdate && providersToSetup.length === 0) {
300
+ providersToSetup = await (0, provider_prompts_1.promptForProviders)((0, get_provider_client_1.getProviderClient)());
301
+ }
302
+ // Get credentials for each provider
303
+ for (const providerId of providersToSetup) {
304
+ if (!tokens[providerId]) {
488
305
  try {
489
- tokens.ado = await promptForADOToken();
306
+ // Use provided tokens if available, otherwise prompt
307
+ const creds = await (0, provider_prompts_1.promptForProviderCredentials)((0, get_provider_client_1.getProviderClient)(), providerId, providedTokens[providerId], providedConfigs[providerId]);
308
+ tokens[providerId] = creds.token;
309
+ if (creds.config) {
310
+ configs[providerId] = creds.config;
311
+ }
490
312
  }
491
313
  catch (e) {
492
- console.log(chalk_1.default.red('❌ Failed to get Azure DevOps token'));
314
+ const providerName = await (0, provider_registry_1.getProviderDisplayName)(providerId);
315
+ console.log(chalk_1.default.red(`❌ Failed to get ${providerName} credentials`));
493
316
  process.exit(1);
494
317
  }
495
318
  }
496
319
  }
497
- // Get GitLab token if needed
498
- if (needGitLab && !tokens.gitlab) {
499
- if (options.gitlabToken) {
500
- if (!(0, token_validator_1.isValidTokenFormat)(options.gitlabToken, 'gitlab')) {
501
- console.log(chalk_1.default.red('Invalid GitLab token format'));
502
- process.exit(1);
503
- }
504
- tokens.gitlab = options.gitlabToken;
505
- }
506
- else {
507
- try {
508
- tokens.gitlab = await promptForGitLabToken();
509
- }
510
- catch (e) {
511
- console.log(chalk_1.default.red('Failed to get GitLab token'));
512
- process.exit(1);
513
- }
514
- }
515
- }
516
- // Get Jira credentials if needed
517
- if (needJira && !tokens.jira) {
518
- if (options.jiraToken) {
519
- if (!(0, token_validator_1.isValidTokenFormat)(options.jiraToken, 'jira')) {
520
- console.log(chalk_1.default.red('Invalid Jira token format'));
521
- process.exit(1);
522
- }
523
- tokens.jira = options.jiraToken;
524
- // Use provided URL and email if available, otherwise prompt
525
- if (options.jiraUrl && options.jiraEmail) {
526
- jiraConfig.baseUrl = options.jiraUrl;
527
- jiraConfig.email = options.jiraEmail;
528
- console.log(chalk_1.default.green('✅ Jira credentials received\n'));
529
- }
530
- else {
531
- console.log(chalk_1.default.yellow('⚠️ Jira token provided but missing URL or email'));
532
- const jiraCredentials = await promptForJiraCredentials();
533
- jiraConfig.baseUrl = jiraCredentials.baseUrl;
534
- jiraConfig.email = jiraCredentials.email;
535
- }
536
- }
537
- else {
538
- try {
539
- const jiraCredentials = await promptForJiraCredentials();
540
- tokens.jira = jiraCredentials.token;
541
- jiraConfig.baseUrl = jiraCredentials.baseUrl;
542
- jiraConfig.email = jiraCredentials.email;
543
- }
544
- catch (e) {
545
- console.log(chalk_1.default.red('Failed to get Jira credentials'));
546
- process.exit(1);
547
- }
548
- }
549
- }
550
- if (!tokens.github && !tokens.ado && !tokens.gitlab && !tokens.jira) {
320
+ if (Object.keys(tokens).length === 0) {
551
321
  console.log(chalk_1.default.yellow('⚠️ No platform tokens configured.'));
552
322
  console.log(chalk_1.default.gray(' You can add them later with:'));
553
- console.log(chalk_1.default.cyan(' fraim setup --github'));
554
- console.log(chalk_1.default.cyan(' fraim setup --ado'));
555
- console.log(chalk_1.default.cyan(' fraim setup --gitlab'));
556
- console.log(chalk_1.default.cyan(' fraim setup --jira\n'));
323
+ const allProviderIds = await (0, provider_registry_1.getAllProviderIds)();
324
+ allProviderIds.forEach(id => {
325
+ console.log(chalk_1.default.cyan(` fraim setup --${id}`));
326
+ });
327
+ console.log();
557
328
  }
558
329
  }
559
330
  else if (mode === 'split') {
@@ -561,182 +332,70 @@ const runSetup = async (options) => {
561
332
  console.log(chalk_1.default.blue('\n🔀 Split Mode Configuration'));
562
333
  console.log(chalk_1.default.gray('Configure separate platforms for code hosting and issue tracking.\n'));
563
334
  // 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);
335
+ const codeRepoProvider = await (0, provider_prompts_1.promptForSingleProvider)((0, get_provider_client_1.getProviderClient)(), 'code');
336
+ // Get code repository credentials
337
+ if (!tokens[codeRepoProvider]) {
338
+ try {
339
+ // Use provided tokens if available, otherwise prompt
340
+ const creds = await (0, provider_prompts_1.promptForProviderCredentials)((0, get_provider_client_1.getProviderClient)(), codeRepoProvider, providedTokens[codeRepoProvider], providedConfigs[codeRepoProvider]);
341
+ tokens[codeRepoProvider] = creds.token;
342
+ if (creds.config) {
343
+ configs[codeRepoProvider] = creds.config;
610
344
  }
611
- tokens.gitlab = options.gitlabToken;
612
345
  }
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
- }
346
+ catch (e) {
347
+ const providerName = await (0, provider_registry_1.getProviderDisplayName)(codeRepoProvider);
348
+ console.log(chalk_1.default.red(`❌ Failed to get ${providerName} credentials`));
349
+ process.exit(1);
621
350
  }
622
351
  }
623
352
  // 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;
353
+ const issueProvider = await (0, provider_prompts_1.promptForSingleProvider)((0, get_provider_client_1.getProviderClient)(), 'issues');
354
+ // Get issue tracking credentials (if different from code repo)
355
+ if (!tokens[issueProvider]) {
356
+ try {
357
+ // Use provided tokens if available, otherwise prompt
358
+ const creds = await (0, provider_prompts_1.promptForProviderCredentials)((0, get_provider_client_1.getProviderClient)(), issueProvider, providedTokens[issueProvider], providedConfigs[issueProvider]);
359
+ tokens[issueProvider] = creds.token;
360
+ if (creds.config) {
361
+ configs[issueProvider] = creds.config;
701
362
  }
702
363
  }
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
- }
364
+ catch (e) {
365
+ const providerName = await (0, provider_registry_1.getProviderDisplayName)(issueProvider);
366
+ console.log(chalk_1.default.red(`❌ Failed to get ${providerName} credentials`));
367
+ process.exit(1);
714
368
  }
715
369
  }
716
- console.log(chalk_1.default.green(`\n✅ Split mode configured: ${codeRepoPlatform} (code) + ${issueTrackingPlatform} (issues)\n`));
370
+ const codeRepoName = await (0, provider_registry_1.getProviderDisplayName)(codeRepoProvider);
371
+ const issueName = await (0, provider_registry_1.getProviderDisplayName)(issueProvider);
372
+ console.log(chalk_1.default.green(`\n✅ Split mode configured: ${codeRepoName} (code) + ${issueName} (issues)\n`));
717
373
  }
718
374
  else {
719
375
  console.log(chalk_1.default.gray('ℹ️ Conversational mode: No platform tokens needed\n'));
720
376
  }
721
377
  // Save global configuration
722
378
  console.log(chalk_1.default.blue('💾 Saving global configuration...'));
723
- saveGlobalConfig(fraimKey, mode, tokens, jiraConfig);
379
+ saveGlobalConfig(fraimKey, mode, tokens, configs);
724
380
  // Configure MCP servers (only on initial setup)
725
381
  if (!isUpdate) {
726
382
  console.log(chalk_1.default.blue('\n🔌 Configuring MCP servers...'));
727
- // For conversational mode, we still configure MCP but without GitHub token requirement
728
- // The FRAIM MCP server works without GitHub token, other servers can be optional
729
- const mcpTokens = {
730
- github: tokens.github || '',
731
- gitlab: tokens.gitlab || '',
732
- jira: tokens.jira || ''
733
- };
734
- if (mode === 'conversational' && !mcpTokens.github && !mcpTokens.gitlab && !mcpTokens.jira) {
735
- console.log(chalk_1.default.yellow('ℹ️ Conversational mode: Configuring MCP servers without GitHub integration'));
736
- console.log(chalk_1.default.gray(' FRAIM workflows will work, but GitHub-specific features will be unavailable\n'));
383
+ // Convert to legacy format for MCP config generator
384
+ const mcpTokens = {};
385
+ Object.entries(tokens).forEach(([id, token]) => {
386
+ mcpTokens[id] = token;
387
+ });
388
+ if (mode === 'conversational' && Object.keys(mcpTokens).length === 0) {
389
+ console.log(chalk_1.default.yellow('ℹ️ Conversational mode: Configuring MCP servers without platform integration'));
390
+ console.log(chalk_1.default.gray(' FRAIM workflows will work, but platform-specific features will be unavailable\n'));
737
391
  }
738
392
  try {
739
- await (0, auto_mcp_setup_1.autoConfigureMCP)(fraimKey, mcpTokens, options.ide ? [options.ide] : undefined, jiraConfig);
393
+ // Build providerConfigs map from configs
394
+ const providerConfigsMap = {};
395
+ Object.entries(configs).forEach(([providerId, config]) => {
396
+ providerConfigsMap[providerId] = config;
397
+ });
398
+ await (0, auto_mcp_setup_1.autoConfigureMCP)(fraimKey, mcpTokens, options.ide ? [options.ide] : undefined, providerConfigsMap);
740
399
  }
741
400
  catch (e) {
742
401
  console.log(chalk_1.default.yellow('⚠️ MCP configuration encountered issues'));
@@ -755,39 +414,64 @@ const runSetup = async (options) => {
755
414
  // Show summary
756
415
  console.log(chalk_1.default.green('\n🎯 Setup complete!'));
757
416
  console.log(chalk_1.default.gray(` Mode: ${mode}`));
758
- if (mode === 'integrated') {
759
- console.log(chalk_1.default.gray(` Platforms: ${tokens.github ? 'GitHub yes' : 'GitHub no'} ${tokens.ado ? 'ADO yes' : 'ADO no'} ${tokens.gitlab ? 'GitLab yes' : 'GitLab no'} ${tokens.jira ? 'Jira yes' : 'Jira no'}`));
417
+ if (mode !== 'conversational') {
418
+ const configuredProviders = await Promise.all(Object.keys(tokens).map(async (id) => await (0, provider_registry_1.getProviderDisplayName)(id)));
419
+ console.log(chalk_1.default.gray(` Platforms: ${configuredProviders.join(', ') || 'none'}`));
760
420
  }
761
421
  console.log(chalk_1.default.cyan('\n📝 For future projects:'));
762
422
  console.log(chalk_1.default.cyan(' 1. cd into any project directory'));
763
423
  console.log(chalk_1.default.cyan(' 2. Run: fraim init-project'));
764
- console.log(chalk_1.default.cyan(' 3. Ask your AI agent (Claude Desktop / Cowork, Cursor, etc.) "list fraim workflows"'));
765
- if (mode === 'integrated' && (!tokens.github || !tokens.ado || !tokens.gitlab || !tokens.jira)) {
766
- console.log(chalk_1.default.gray('\n💡 To add more platforms later:'));
767
- if (!tokens.github)
768
- console.log(chalk_1.default.gray(' fraim setup --github'));
769
- if (!tokens.ado)
770
- console.log(chalk_1.default.gray(' fraim setup --ado'));
771
- if (!tokens.gitlab)
772
- console.log(chalk_1.default.gray(' fraim setup --gitlab'));
773
- if (!tokens.jira)
774
- console.log(chalk_1.default.gray(' fraim setup --jira'));
424
+ console.log(chalk_1.default.cyan(' 3. Ask your AI agent "list fraim workflows"'));
425
+ if (mode === 'integrated') {
426
+ const allProviderIds = await (0, provider_registry_1.getAllProviderIds)();
427
+ const unconfiguredProviders = allProviderIds.filter(id => !tokens[id]);
428
+ if (unconfiguredProviders.length > 0) {
429
+ console.log(chalk_1.default.gray('\n💡 To add more platforms later:'));
430
+ unconfiguredProviders.forEach(id => {
431
+ console.log(chalk_1.default.gray(` fraim setup --${id}`));
432
+ });
433
+ }
775
434
  }
776
435
  };
777
436
  exports.runSetup = runSetup;
778
437
  exports.setupCommand = new commander_1.Command('setup')
779
438
  .description('Complete global FRAIM setup with platform configuration')
780
439
  .option('--key <key>', 'FRAIM API key')
781
- .option('--github-token <token>', 'GitHub Personal Access Token')
782
- .option('--ado-token <token>', 'Azure DevOps Personal Access Token')
783
- .option('--gitlab-token <token>', 'GitLab Personal Access Token')
784
- .option('--jira-token <token>', 'Jira API token')
785
- .option('--jira-url <url>', 'Jira base URL (e.g., https://company.atlassian.net)')
786
- .option('--jira-email <email>', 'Jira account email')
787
- .option('--github', 'Add/update GitHub integration')
788
- .option('--ado', 'Add/update Azure DevOps integration')
789
- .option('--gitlab', 'Add/update GitLab integration')
790
- .option('--jira', 'Add/update Jira integration')
791
- .option('--all', 'Configure all detected IDEs (deprecated)')
792
- .option('--ide <ides>', 'Configure specific IDEs (deprecated)')
793
- .action(exports.runSetup);
440
+ .option('--ide <ides>', 'Configure specific IDEs');
441
+ // Track initialization promise for CLI entry point
442
+ exports.setupCommandInitialization = null;
443
+ // Dynamically add provider options from registry (async initialization)
444
+ exports.setupCommandInitialization = (async () => {
445
+ try {
446
+ const allProviderIds = await (0, provider_registry_1.getAllProviderIds)();
447
+ for (const providerId of allProviderIds) {
448
+ const provider = await (0, provider_registry_1.getProvider)(providerId);
449
+ if (!provider)
450
+ continue;
451
+ // Add provider flag (e.g., --github)
452
+ exports.setupCommand.option(`--${providerId}`, `Add/update ${provider.displayName} integration`);
453
+ // Add token option (e.g., --github-token) - primarily for testing/automation
454
+ exports.setupCommand.option(`--${providerId}-token <token>`, `${provider.displayName} token (optional - will prompt if not provided)`);
455
+ // Add config options if provider requires them
456
+ const configReqs = await (0, provider_registry_1.getProviderConfigRequirements)(providerId);
457
+ configReqs.forEach(req => {
458
+ // Use custom CLI option name if provided, otherwise convert key to kebab-case
459
+ const optionSuffix = req.cliOptionName || req.key.replace(/([A-Z])/g, '-$1').toLowerCase();
460
+ const optionName = `${providerId}-${optionSuffix}`;
461
+ exports.setupCommand.option(`--${optionName} <value>`, `${req.description} (optional - will prompt if not provided)`);
462
+ });
463
+ }
464
+ }
465
+ catch (error) {
466
+ // If we can't fetch providers (e.g., no config yet), that's okay
467
+ // The command will still work, just without dynamic options
468
+ }
469
+ })();
470
+ // Wrap the action to ensure initialization completes first
471
+ exports.setupCommand.action(async (options) => {
472
+ // Wait for dynamic options to be registered
473
+ if (exports.setupCommandInitialization) {
474
+ await exports.setupCommandInitialization;
475
+ }
476
+ return (0, exports.runSetup)(options);
477
+ });