fraim-framework 2.0.82 → 2.0.84

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.
Files changed (29) hide show
  1. package/README.md +16 -3
  2. package/dist/src/cli/api/get-provider-client.js +41 -0
  3. package/dist/src/cli/api/provider-client.js +107 -0
  4. package/dist/src/cli/commands/add-ide.js +145 -78
  5. package/dist/src/cli/commands/add-provider.js +61 -221
  6. package/dist/src/cli/commands/doctor.js +4 -1
  7. package/dist/src/cli/commands/init-project.js +24 -35
  8. package/dist/src/cli/commands/setup.js +287 -566
  9. package/dist/src/cli/commands/test-mcp.js +35 -1
  10. package/dist/src/cli/doctor/check-runner.js +6 -3
  11. package/dist/src/cli/doctor/checks/global-setup-checks.js +5 -6
  12. package/dist/src/cli/doctor/checks/ide-config-checks.js +44 -23
  13. package/dist/src/cli/doctor/checks/mcp-connectivity-checks.js +324 -146
  14. package/dist/src/cli/doctor/checks/scripts-checks.js +2 -2
  15. package/dist/src/cli/fraim.js +42 -3
  16. package/dist/src/cli/mcp/ide-formats.js +243 -0
  17. package/dist/src/cli/mcp/mcp-server-builder.js +48 -0
  18. package/dist/src/cli/mcp/mcp-server-registry.js +161 -0
  19. package/dist/src/cli/mcp/types.js +3 -0
  20. package/dist/src/cli/providers/local-provider-registry.js +145 -0
  21. package/dist/src/cli/providers/provider-registry.js +230 -0
  22. package/dist/src/cli/setup/auto-mcp-setup.js +68 -119
  23. package/dist/src/cli/setup/mcp-config-generator.js +64 -321
  24. package/dist/src/cli/setup/provider-prompts.js +300 -0
  25. package/dist/src/core/utils/workflow-parser.js +5 -1
  26. package/dist/src/local-mcp-server/stdio-server.js +30 -30
  27. package/package.json +6 -3
  28. package/dist/src/cli/commands/install.js +0 -86
  29. package/dist/src/cli/setup/token-validator.js +0 -57
@@ -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,106 @@ 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
- fraimKey = existingConfig.apiKey;
243
+ // Allow updating FRAIM key even without provider changes
244
+ fraimKey = options.key || existingConfig.apiKey;
245
+ // Now we can parse options with the FRAIM key
246
+ const parsed = await parseLegacyOptions(options, fraimKey);
247
+ requestedProviders = parsed.requestedProviders;
248
+ providedTokens = parsed.providedTokens;
249
+ providedConfigs = parsed.providedConfigs;
250
+ // Check if this is just a key update or a provider update
251
+ const isKeyUpdate = options.key && options.key !== existingConfig.apiKey;
252
+ const isProviderUpdate = requestedProviders.length > 0;
253
+ // Only proceed if there's something to update
254
+ if (!isKeyUpdate && !isProviderUpdate) {
255
+ console.log(chalk_1.default.yellow('⚠️ No changes specified.'));
256
+ console.log(chalk_1.default.gray('Use --key to update FRAIM key, or --github, --gitlab, etc. to add providers.'));
257
+ return;
258
+ }
398
259
  mode = existingConfig.mode || 'integrated';
399
260
  // Preserve existing tokens
400
261
  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;
262
+ Object.assign(tokens, existingConfig.tokens);
263
+ }
264
+ // Preserve existing configs
265
+ if (existingConfig.providerConfigs) {
266
+ Object.entries(existingConfig.providerConfigs).forEach(([key, value]) => {
267
+ const providerId = key.replace('Config', '');
268
+ configs[providerId] = value;
269
+ });
409
270
  }
410
271
  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`));
272
+ // Show existing tokens
273
+ const { getProvider } = await Promise.resolve().then(() => __importStar(require('../providers/provider-registry')));
274
+ const providerNames = await Promise.all(Object.keys(tokens).map(async (id) => {
275
+ const provider = await getProvider(id);
276
+ return provider?.displayName || id;
277
+ }));
278
+ console.log(chalk_1.default.gray(` Existing tokens: ${providerNames.join(', ') || 'none'}\n`));
412
279
  }
413
280
  catch (e) {
414
281
  console.log(chalk_1.default.red('❌ Failed to read existing configuration'));
282
+ console.error('Error details:', e);
415
283
  process.exit(1);
416
284
  }
417
285
  }
@@ -424,136 +292,43 @@ const runSetup = async (options) => {
424
292
  console.log(chalk_1.default.gray(' • Sync FRAIM scripts to your system\n'));
425
293
  // Get FRAIM key
426
294
  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'));
295
+ console.log(chalk_1.default.green('✅ FRAIM key accepted\n'));
438
296
  // Ask for mode preference
439
297
  mode = await promptForMode();
440
298
  }
441
299
  // Handle platform tokens based on mode
442
300
  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 {
488
- try {
489
- tokens.ado = await promptForADOToken();
490
- }
491
- catch (e) {
492
- console.log(chalk_1.default.red('❌ Failed to get Azure DevOps token'));
493
- process.exit(1);
494
- }
495
- }
496
- }
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 {
301
+ let providersToSetup = requestedProviders;
302
+ // If no specific providers requested and not an update, ask user
303
+ if (!isUpdate && providersToSetup.length === 0) {
304
+ providersToSetup = await (0, provider_prompts_1.promptForProviders)((0, get_provider_client_1.getProviderClient)());
305
+ }
306
+ // Get credentials for each provider
307
+ for (const providerId of providersToSetup) {
308
+ if (!tokens[providerId]) {
507
309
  try {
508
- tokens.gitlab = await promptForGitLabToken();
310
+ // Use provided tokens if available, otherwise prompt
311
+ const creds = await (0, provider_prompts_1.promptForProviderCredentials)((0, get_provider_client_1.getProviderClient)(), providerId, providedTokens[providerId], providedConfigs[providerId]);
312
+ tokens[providerId] = creds.token;
313
+ if (creds.config) {
314
+ configs[providerId] = creds.config;
315
+ }
509
316
  }
510
317
  catch (e) {
511
- console.log(chalk_1.default.red('Failed to get GitLab token'));
318
+ const providerName = await (0, provider_registry_1.getProviderDisplayName)(providerId);
319
+ console.log(chalk_1.default.red(`❌ Failed to get ${providerName} credentials`));
512
320
  process.exit(1);
513
321
  }
514
322
  }
515
323
  }
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) {
324
+ if (Object.keys(tokens).length === 0) {
551
325
  console.log(chalk_1.default.yellow('⚠️ No platform tokens configured.'));
552
326
  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'));
327
+ const allProviderIds = await (0, provider_registry_1.getAllProviderIds)();
328
+ allProviderIds.forEach(id => {
329
+ console.log(chalk_1.default.cyan(` fraim setup --${id}`));
330
+ });
331
+ console.log();
557
332
  }
558
333
  }
559
334
  else if (mode === 'split') {
@@ -561,188 +336,109 @@ const runSetup = async (options) => {
561
336
  console.log(chalk_1.default.blue('\n🔀 Split Mode Configuration'));
562
337
  console.log(chalk_1.default.gray('Configure separate platforms for code hosting and issue tracking.\n'));
563
338
  // 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);
339
+ const codeRepoProvider = await (0, provider_prompts_1.promptForSingleProvider)((0, get_provider_client_1.getProviderClient)(), 'code');
340
+ // Get code repository credentials
341
+ if (!tokens[codeRepoProvider]) {
342
+ try {
343
+ // Use provided tokens if available, otherwise prompt
344
+ const creds = await (0, provider_prompts_1.promptForProviderCredentials)((0, get_provider_client_1.getProviderClient)(), codeRepoProvider, providedTokens[codeRepoProvider], providedConfigs[codeRepoProvider]);
345
+ tokens[codeRepoProvider] = creds.token;
346
+ if (creds.config) {
347
+ configs[codeRepoProvider] = creds.config;
610
348
  }
611
- tokens.gitlab = options.gitlabToken;
612
349
  }
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
- }
350
+ catch (e) {
351
+ const providerName = await (0, provider_registry_1.getProviderDisplayName)(codeRepoProvider);
352
+ console.log(chalk_1.default.red(`❌ Failed to get ${providerName} credentials`));
353
+ process.exit(1);
621
354
  }
622
355
  }
623
356
  // 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;
357
+ const issueProvider = await (0, provider_prompts_1.promptForSingleProvider)((0, get_provider_client_1.getProviderClient)(), 'issues');
358
+ // Get issue tracking credentials (if different from code repo)
359
+ if (!tokens[issueProvider]) {
360
+ try {
361
+ // Use provided tokens if available, otherwise prompt
362
+ const creds = await (0, provider_prompts_1.promptForProviderCredentials)((0, get_provider_client_1.getProviderClient)(), issueProvider, providedTokens[issueProvider], providedConfigs[issueProvider]);
363
+ tokens[issueProvider] = creds.token;
364
+ if (creds.config) {
365
+ configs[issueProvider] = creds.config;
701
366
  }
702
367
  }
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
- }
368
+ catch (e) {
369
+ const providerName = await (0, provider_registry_1.getProviderDisplayName)(issueProvider);
370
+ console.log(chalk_1.default.red(`❌ Failed to get ${providerName} credentials`));
371
+ process.exit(1);
714
372
  }
715
373
  }
716
- console.log(chalk_1.default.green(`\n✅ Split mode configured: ${codeRepoPlatform} (code) + ${issueTrackingPlatform} (issues)\n`));
374
+ const codeRepoName = await (0, provider_registry_1.getProviderDisplayName)(codeRepoProvider);
375
+ const issueName = await (0, provider_registry_1.getProviderDisplayName)(issueProvider);
376
+ console.log(chalk_1.default.green(`\n✅ Split mode configured: ${codeRepoName} (code) + ${issueName} (issues)\n`));
717
377
  }
718
378
  else {
719
379
  console.log(chalk_1.default.gray('ℹ️ Conversational mode: No platform tokens needed\n'));
720
380
  }
721
381
  // Save global configuration
722
382
  console.log(chalk_1.default.blue('💾 Saving global configuration...'));
723
- saveGlobalConfig(fraimKey, mode, tokens, jiraConfig);
724
- // Configure MCP servers (only on initial setup)
383
+ saveGlobalConfig(fraimKey, mode, tokens, configs);
384
+ // Configure MCP servers
725
385
  if (!isUpdate) {
386
+ // Initial setup - configure all detected IDEs
726
387
  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'));
388
+ // Convert to legacy format for MCP config generator
389
+ const mcpTokens = {};
390
+ Object.entries(tokens).forEach(([id, token]) => {
391
+ mcpTokens[id] = token;
392
+ });
393
+ if (mode === 'conversational' && Object.keys(mcpTokens).length === 0) {
394
+ console.log(chalk_1.default.yellow('ℹ️ Conversational mode: Configuring MCP servers without platform integration'));
395
+ console.log(chalk_1.default.gray(' FRAIM workflows will work, but platform-specific features will be unavailable\n'));
737
396
  }
738
397
  try {
739
- await (0, auto_mcp_setup_1.autoConfigureMCP)(fraimKey, mcpTokens, options.ide ? [options.ide] : undefined, jiraConfig);
398
+ // Build providerConfigs map from configs
399
+ const providerConfigsMap = {};
400
+ Object.entries(configs).forEach(([providerId, config]) => {
401
+ providerConfigsMap[providerId] = config;
402
+ });
403
+ await (0, auto_mcp_setup_1.autoConfigureMCP)(fraimKey, mcpTokens, options.ide ? [options.ide] : undefined, providerConfigsMap);
740
404
  }
741
405
  catch (e) {
742
406
  console.log(chalk_1.default.yellow('⚠️ MCP configuration encountered issues'));
743
407
  console.log(chalk_1.default.gray(' You can configure MCP manually or run setup again later\n'));
744
408
  }
745
- // Auto-run project init if we're in a git repo
409
+ }
410
+ else {
411
+ // Update existing setup - refresh all IDE MCP configs with new keys
412
+ console.log(chalk_1.default.blue('\n🔄 Updating IDE MCP configurations...'));
413
+ try {
414
+ const { detectInstalledIDEs } = await Promise.resolve().then(() => __importStar(require('../setup/ide-detector')));
415
+ const installedIDEs = detectInstalledIDEs();
416
+ if (installedIDEs.length === 0) {
417
+ console.log(chalk_1.default.gray(' No IDE configurations found to update'));
418
+ }
419
+ else {
420
+ // Convert to legacy format for MCP config generator
421
+ const mcpTokens = {};
422
+ Object.entries(tokens).forEach(([id, token]) => {
423
+ mcpTokens[id] = token;
424
+ });
425
+ // Build providerConfigs map from configs
426
+ const providerConfigsMap = {};
427
+ Object.entries(configs).forEach(([providerId, config]) => {
428
+ providerConfigsMap[providerId] = config;
429
+ });
430
+ const ideNames = installedIDEs.map(ide => ide.name);
431
+ await (0, auto_mcp_setup_1.autoConfigureMCP)(fraimKey, mcpTokens, ideNames, providerConfigsMap);
432
+ console.log(chalk_1.default.green(`✅ Updated MCP configs for: ${ideNames.join(', ')}`));
433
+ }
434
+ }
435
+ catch (e) {
436
+ console.log(chalk_1.default.yellow('⚠️ Failed to update IDE MCP configurations'));
437
+ console.log(chalk_1.default.gray(' You can update them manually with: fraim add-ide <ide-name>\n'));
438
+ }
439
+ }
440
+ // Auto-run project init if we're in a git repo (only on initial setup)
441
+ if (!isUpdate) {
746
442
  if ((0, platform_detection_1.isGitRepository)()) {
747
443
  console.log(chalk_1.default.blue('\n📁 Git repository detected — initializing project...'));
748
444
  await (0, init_project_1.runInitProject)();
@@ -755,39 +451,64 @@ const runSetup = async (options) => {
755
451
  // Show summary
756
452
  console.log(chalk_1.default.green('\n🎯 Setup complete!'));
757
453
  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'}`));
454
+ if (mode !== 'conversational') {
455
+ const configuredProviders = await Promise.all(Object.keys(tokens).map(async (id) => await (0, provider_registry_1.getProviderDisplayName)(id)));
456
+ console.log(chalk_1.default.gray(` Platforms: ${configuredProviders.join(', ') || 'none'}`));
760
457
  }
761
458
  console.log(chalk_1.default.cyan('\n📝 For future projects:'));
762
459
  console.log(chalk_1.default.cyan(' 1. cd into any project directory'));
763
460
  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'));
461
+ console.log(chalk_1.default.cyan(' 3. Ask your AI agent "list fraim workflows"'));
462
+ if (mode === 'integrated') {
463
+ const allProviderIds = await (0, provider_registry_1.getAllProviderIds)();
464
+ const unconfiguredProviders = allProviderIds.filter(id => !tokens[id]);
465
+ if (unconfiguredProviders.length > 0) {
466
+ console.log(chalk_1.default.gray('\n💡 To add more platforms later:'));
467
+ unconfiguredProviders.forEach(id => {
468
+ console.log(chalk_1.default.gray(` fraim setup --${id}`));
469
+ });
470
+ }
775
471
  }
776
472
  };
777
473
  exports.runSetup = runSetup;
778
474
  exports.setupCommand = new commander_1.Command('setup')
779
475
  .description('Complete global FRAIM setup with platform configuration')
780
476
  .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);
477
+ .option('--ide <ides>', 'Configure specific IDEs');
478
+ // Track initialization promise for CLI entry point
479
+ exports.setupCommandInitialization = null;
480
+ // Dynamically add provider options from registry (async initialization)
481
+ exports.setupCommandInitialization = (async () => {
482
+ try {
483
+ const allProviderIds = await (0, provider_registry_1.getAllProviderIds)();
484
+ for (const providerId of allProviderIds) {
485
+ const provider = await (0, provider_registry_1.getProvider)(providerId);
486
+ if (!provider)
487
+ continue;
488
+ // Add provider flag (e.g., --github)
489
+ exports.setupCommand.option(`--${providerId}`, `Add/update ${provider.displayName} integration`);
490
+ // Add token option (e.g., --github-token) - primarily for testing/automation
491
+ exports.setupCommand.option(`--${providerId}-token <token>`, `${provider.displayName} token (optional - will prompt if not provided)`);
492
+ // Add config options if provider requires them
493
+ const configReqs = await (0, provider_registry_1.getProviderConfigRequirements)(providerId);
494
+ configReqs.forEach(req => {
495
+ // Use custom CLI option name if provided, otherwise convert key to kebab-case
496
+ const optionSuffix = req.cliOptionName || req.key.replace(/([A-Z])/g, '-$1').toLowerCase();
497
+ const optionName = `${providerId}-${optionSuffix}`;
498
+ exports.setupCommand.option(`--${optionName} <value>`, `${req.description} (optional - will prompt if not provided)`);
499
+ });
500
+ }
501
+ }
502
+ catch (error) {
503
+ // If we can't fetch providers (e.g., no config yet), that's okay
504
+ // The command will still work, just without dynamic options
505
+ }
506
+ })();
507
+ // Wrap the action to ensure initialization completes first
508
+ exports.setupCommand.action(async (options) => {
509
+ // Wait for dynamic options to be registered
510
+ if (exports.setupCommandInitialization) {
511
+ await exports.setupCommandInitialization;
512
+ }
513
+ return (0, exports.runSetup)(options);
514
+ });