fraim 2.0.100

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 (70) hide show
  1. package/README.md +445 -0
  2. package/bin/fraim.js +23 -0
  3. package/dist/src/cli/api/get-provider-client.js +41 -0
  4. package/dist/src/cli/api/provider-client.js +107 -0
  5. package/dist/src/cli/commands/add-ide.js +430 -0
  6. package/dist/src/cli/commands/add-provider.js +233 -0
  7. package/dist/src/cli/commands/doctor.js +149 -0
  8. package/dist/src/cli/commands/init-project.js +301 -0
  9. package/dist/src/cli/commands/list-overridable.js +184 -0
  10. package/dist/src/cli/commands/list.js +57 -0
  11. package/dist/src/cli/commands/login.js +84 -0
  12. package/dist/src/cli/commands/mcp.js +15 -0
  13. package/dist/src/cli/commands/migrate-project-fraim.js +42 -0
  14. package/dist/src/cli/commands/override.js +177 -0
  15. package/dist/src/cli/commands/setup.js +651 -0
  16. package/dist/src/cli/commands/sync.js +162 -0
  17. package/dist/src/cli/commands/test-mcp.js +171 -0
  18. package/dist/src/cli/doctor/check-runner.js +199 -0
  19. package/dist/src/cli/doctor/checks/global-setup-checks.js +220 -0
  20. package/dist/src/cli/doctor/checks/ide-config-checks.js +250 -0
  21. package/dist/src/cli/doctor/checks/mcp-connectivity-checks.js +381 -0
  22. package/dist/src/cli/doctor/checks/project-setup-checks.js +282 -0
  23. package/dist/src/cli/doctor/checks/scripts-checks.js +157 -0
  24. package/dist/src/cli/doctor/checks/workflow-checks.js +251 -0
  25. package/dist/src/cli/doctor/reporters/console-reporter.js +96 -0
  26. package/dist/src/cli/doctor/reporters/json-reporter.js +11 -0
  27. package/dist/src/cli/doctor/types.js +6 -0
  28. package/dist/src/cli/fraim.js +100 -0
  29. package/dist/src/cli/internal/device-flow-service.js +83 -0
  30. package/dist/src/cli/mcp/ide-formats.js +243 -0
  31. package/dist/src/cli/mcp/mcp-server-builder.js +48 -0
  32. package/dist/src/cli/mcp/mcp-server-registry.js +160 -0
  33. package/dist/src/cli/mcp/types.js +3 -0
  34. package/dist/src/cli/providers/local-provider-registry.js +166 -0
  35. package/dist/src/cli/providers/provider-registry.js +230 -0
  36. package/dist/src/cli/setup/auto-mcp-setup.js +331 -0
  37. package/dist/src/cli/setup/codex-local-config.js +37 -0
  38. package/dist/src/cli/setup/first-run.js +242 -0
  39. package/dist/src/cli/setup/ide-detector.js +179 -0
  40. package/dist/src/cli/setup/mcp-config-generator.js +192 -0
  41. package/dist/src/cli/setup/provider-prompts.js +339 -0
  42. package/dist/src/cli/utils/agent-adapters.js +126 -0
  43. package/dist/src/cli/utils/digest-utils.js +47 -0
  44. package/dist/src/cli/utils/fraim-gitignore.js +40 -0
  45. package/dist/src/cli/utils/platform-detection.js +258 -0
  46. package/dist/src/cli/utils/project-bootstrap.js +93 -0
  47. package/dist/src/cli/utils/remote-sync.js +315 -0
  48. package/dist/src/cli/utils/script-sync-utils.js +221 -0
  49. package/dist/src/cli/utils/version-utils.js +32 -0
  50. package/dist/src/core/ai-mentor.js +230 -0
  51. package/dist/src/core/config-loader.js +114 -0
  52. package/dist/src/core/config-writer.js +75 -0
  53. package/dist/src/core/types.js +23 -0
  54. package/dist/src/core/utils/git-utils.js +95 -0
  55. package/dist/src/core/utils/include-resolver.js +92 -0
  56. package/dist/src/core/utils/inheritance-parser.js +288 -0
  57. package/dist/src/core/utils/job-parser.js +176 -0
  58. package/dist/src/core/utils/local-registry-resolver.js +616 -0
  59. package/dist/src/core/utils/object-utils.js +11 -0
  60. package/dist/src/core/utils/project-fraim-migration.js +103 -0
  61. package/dist/src/core/utils/project-fraim-paths.js +38 -0
  62. package/dist/src/core/utils/provider-utils.js +18 -0
  63. package/dist/src/core/utils/server-startup.js +34 -0
  64. package/dist/src/core/utils/stub-generator.js +147 -0
  65. package/dist/src/core/utils/workflow-parser.js +174 -0
  66. package/dist/src/local-mcp-server/learning-context-builder.js +229 -0
  67. package/dist/src/local-mcp-server/stdio-server.js +1698 -0
  68. package/dist/src/local-mcp-server/usage-collector.js +264 -0
  69. package/index.js +85 -0
  70. package/package.json +139 -0
@@ -0,0 +1,651 @@
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
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.setupCommandInitialization = exports.setupCommand = exports.runSetup = void 0;
40
+ // Refactored setup.ts - Generic provider system with zero hardcoded provider knowledge
41
+ const commander_1 = require("commander");
42
+ const chalk_1 = __importDefault(require("chalk"));
43
+ const prompts_1 = __importDefault(require("prompts"));
44
+ const fs_1 = __importDefault(require("fs"));
45
+ const path_1 = __importDefault(require("path"));
46
+ const auto_mcp_setup_1 = require("../setup/auto-mcp-setup");
47
+ const version_utils_1 = require("../utils/version-utils");
48
+ const platform_detection_1 = require("../utils/platform-detection");
49
+ const provider_client_1 = require("../api/provider-client");
50
+ const provider_prompts_1 = require("../setup/provider-prompts");
51
+ const provider_registry_1 = require("../providers/provider-registry");
52
+ const init_project_1 = require("./init-project");
53
+ const script_sync_utils_1 = require("../utils/script-sync-utils");
54
+ function parseModeOption(mode) {
55
+ if (mode === 'conversational' || mode === 'integrated' || mode === 'split') {
56
+ return mode;
57
+ }
58
+ console.log(chalk_1.default.red(`❌ Invalid mode "${mode}"`));
59
+ console.log(chalk_1.default.yellow('💡 Valid values: integrated, split, conversational'));
60
+ process.exit(1);
61
+ }
62
+ const promptForFraimKey = async () => {
63
+ console.log(chalk_1.default.blue('🔑 FRAIM Key Setup'));
64
+ console.log('FRAIM requires a valid API key to access workflows and features.\n');
65
+ let key = null;
66
+ let attempts = 0;
67
+ const maxAttempts = 3;
68
+ while (!key && attempts < maxAttempts) {
69
+ const keyResponse = await (0, prompts_1.default)({
70
+ type: 'password',
71
+ name: 'key',
72
+ message: attempts === 0
73
+ ? 'Enter your FRAIM key (required)'
74
+ : `Enter your FRAIM key (attempt ${attempts + 1}/${maxAttempts})`,
75
+ validate: (value) => {
76
+ if (!value)
77
+ return 'FRAIM key is required';
78
+ return true;
79
+ }
80
+ });
81
+ if (!keyResponse.key) {
82
+ console.log(chalk_1.default.red('\n❌ FRAIM key is required to proceed.'));
83
+ console.log(chalk_1.default.gray('If you need a key, please email sid.mathur@gmail.com to request one.\n'));
84
+ const retry = await (0, prompts_1.default)({
85
+ type: 'confirm',
86
+ name: 'retry',
87
+ message: 'Would you like to try entering the FRAIM key again?',
88
+ initial: true
89
+ });
90
+ if (!retry.retry) {
91
+ console.log(chalk_1.default.red('Setup cancelled. Please obtain a FRAIM key and try again.'));
92
+ process.exit(1);
93
+ }
94
+ attempts++;
95
+ continue;
96
+ }
97
+ console.log(chalk_1.default.green('✅ FRAIM key accepted\n'));
98
+ return keyResponse.key;
99
+ }
100
+ console.log(chalk_1.default.red('\n❌ Maximum attempts reached. Setup cancelled.'));
101
+ console.log(chalk_1.default.gray('Please ensure you have a valid FRAIM key and try again.'));
102
+ process.exit(1);
103
+ };
104
+ const promptForMode = async () => {
105
+ console.log(chalk_1.default.blue('\n🎯 Usage Mode Selection'));
106
+ console.log(chalk_1.default.gray('Choose how you want to use FRAIM:\n'));
107
+ const response = await (0, prompts_1.default)({
108
+ type: 'select',
109
+ name: 'mode',
110
+ message: 'Select your preferred mode',
111
+ choices: [
112
+ {
113
+ title: 'Integrated Mode',
114
+ value: 'integrated',
115
+ description: 'Single platform for both code hosting and issue tracking'
116
+ },
117
+ {
118
+ title: 'Split Mode',
119
+ value: 'split',
120
+ description: 'Separate platforms for code hosting and issue tracking'
121
+ },
122
+ {
123
+ title: 'Conversational Mode',
124
+ value: 'conversational',
125
+ description: 'AI workflows only, no platform integration required'
126
+ }
127
+ ],
128
+ initial: 0
129
+ });
130
+ if (!response.mode) {
131
+ console.log(chalk_1.default.yellow('\nℹ️ No mode selected, defaulting to Integrated Mode'));
132
+ return 'integrated';
133
+ }
134
+ if (response.mode === 'conversational') {
135
+ console.log(chalk_1.default.blue('\n✓ Conversational mode selected'));
136
+ console.log(chalk_1.default.gray(' You can use FRAIM workflows without platform credentials.'));
137
+ console.log(chalk_1.default.gray(' Platform features (issues, PRs) will be unavailable.\n'));
138
+ }
139
+ else if (response.mode === 'split') {
140
+ console.log(chalk_1.default.blue('\n✓ Split mode selected'));
141
+ console.log(chalk_1.default.gray(' You can use different platforms for code hosting and issue tracking.\n'));
142
+ }
143
+ else {
144
+ console.log(chalk_1.default.blue('\n✓ Integrated mode selected'));
145
+ console.log(chalk_1.default.gray(' Full platform integration will be available.\n'));
146
+ }
147
+ return response.mode;
148
+ };
149
+ const saveGlobalConfig = (fraimKey, mode, tokens, configs) => {
150
+ const globalConfigDir = (0, script_sync_utils_1.getUserFraimDir)();
151
+ const globalConfigPath = path_1.default.join(globalConfigDir, 'config.json');
152
+ if (!fs_1.default.existsSync(globalConfigDir)) {
153
+ fs_1.default.mkdirSync(globalConfigDir, { recursive: true });
154
+ }
155
+ // Read existing config to preserve any existing data
156
+ let existingConfig = {};
157
+ if (fs_1.default.existsSync(globalConfigPath)) {
158
+ try {
159
+ existingConfig = JSON.parse(fs_1.default.readFileSync(globalConfigPath, 'utf8'));
160
+ }
161
+ catch (e) {
162
+ // Ignore parse errors, will create new config
163
+ }
164
+ }
165
+ // Merge provider configs (e.g., jiraConfig)
166
+ const providerConfigs = { ...(existingConfig.providerConfigs || {}) };
167
+ Object.entries(configs).forEach(([providerId, config]) => {
168
+ providerConfigs[`${providerId}Config`] = {
169
+ ...(providerConfigs[`${providerId}Config`] || {}),
170
+ ...config
171
+ };
172
+ });
173
+ const config = {
174
+ version: (0, version_utils_1.getFraimVersion)(),
175
+ apiKey: fraimKey,
176
+ mode: mode,
177
+ tokens: {
178
+ ...(existingConfig.tokens || {}),
179
+ ...tokens
180
+ },
181
+ providerConfigs,
182
+ configuredAt: new Date().toISOString(),
183
+ userPreferences: {
184
+ autoSync: true,
185
+ backupConfigs: true
186
+ }
187
+ };
188
+ fs_1.default.writeFileSync(globalConfigPath, JSON.stringify(config, null, 2));
189
+ console.log(chalk_1.default.green('✅ Global FRAIM configuration saved'));
190
+ };
191
+ // Parse CLI options into generic format using provider registry from server
192
+ // Tokens/config are optional - will prompt if not provided
193
+ const parseLegacyOptions = async (options, fraimKey) => {
194
+ const requestedProviders = [];
195
+ const providedTokens = {};
196
+ const providedConfigs = {};
197
+ // Use provider registry (with fallback) instead of direct client call
198
+ const { getAllProviderIds, getProviderConfigRequirements } = await Promise.resolve().then(() => __importStar(require('../providers/provider-registry')));
199
+ const providerIds = await getAllProviderIds();
200
+ // Dynamically check for provider flags from server registry
201
+ for (const providerId of providerIds) {
202
+ // Check for provider flag (e.g., --github)
203
+ if (options[providerId]) {
204
+ requestedProviders.push(providerId);
205
+ }
206
+ // Check for token option (e.g., --github-token)
207
+ const tokenKey = `${providerId}Token`;
208
+ if (options[tokenKey]) {
209
+ providedTokens[providerId] = options[tokenKey];
210
+ }
211
+ // Check for provider-specific config options
212
+ const configReqs = await getProviderConfigRequirements(providerId);
213
+ if (configReqs.length > 0) {
214
+ const config = {};
215
+ let hasAnyConfig = false;
216
+ configReqs.forEach(req => {
217
+ // Use custom CLI option name if provided, otherwise convert key to camelCase
218
+ const optionSuffix = req.cliOptionName
219
+ ? req.cliOptionName.charAt(0).toUpperCase() + req.cliOptionName.slice(1)
220
+ : req.key.charAt(0).toUpperCase() + req.key.slice(1);
221
+ const optionKey = `${providerId}${optionSuffix}`;
222
+ if (options[optionKey]) {
223
+ config[req.key] = options[optionKey];
224
+ hasAnyConfig = true;
225
+ }
226
+ });
227
+ if (hasAnyConfig) {
228
+ providedConfigs[providerId] = config;
229
+ }
230
+ }
231
+ }
232
+ return { requestedProviders, providedTokens, providedConfigs };
233
+ };
234
+ const runSetup = async (options) => {
235
+ console.log(chalk_1.default.blue('🚀 Welcome to FRAIM! Let\'s get you set up.\n'));
236
+ // Determine if this is an update (adding platforms to existing setup)
237
+ const globalConfigPath = path_1.default.join((0, script_sync_utils_1.getUserFraimDir)(), 'config.json');
238
+ const isUpdate = fs_1.default.existsSync(globalConfigPath);
239
+ let fraimKey;
240
+ let mode;
241
+ const tokens = {};
242
+ const configs = {};
243
+ let requestedProviders = [];
244
+ let providedTokens = {};
245
+ let providedConfigs = {};
246
+ if (isUpdate) {
247
+ // Update existing setup - add platforms
248
+ console.log(chalk_1.default.blue('📝 Existing FRAIM configuration detected.\n'));
249
+ try {
250
+ const existingConfig = JSON.parse(fs_1.default.readFileSync(globalConfigPath, 'utf8'));
251
+ // Allow updating FRAIM key even without provider changes
252
+ fraimKey = options.key || existingConfig.apiKey;
253
+ // Now we can parse options with the FRAIM key
254
+ const parsed = await parseLegacyOptions(options, fraimKey);
255
+ requestedProviders = parsed.requestedProviders;
256
+ providedTokens = parsed.providedTokens;
257
+ providedConfigs = parsed.providedConfigs;
258
+ // Check if this is just a key update or a provider update
259
+ const isKeyUpdate = options.key && options.key !== existingConfig.apiKey;
260
+ const isProviderUpdate = requestedProviders.length > 0;
261
+ // If no specific changes requested, offer interactive update
262
+ if (!isKeyUpdate && !isProviderUpdate) {
263
+ console.log(chalk_1.default.gray(' Current configuration:'));
264
+ console.log(chalk_1.default.gray(` • Mode: ${existingConfig.mode || 'integrated'}`));
265
+ // Show existing tokens
266
+ const { getProvider } = await Promise.resolve().then(() => __importStar(require('../providers/provider-registry')));
267
+ if (existingConfig.tokens && Object.keys(existingConfig.tokens).length > 0) {
268
+ const providerNames = await Promise.all(Object.keys(existingConfig.tokens).map(async (id) => {
269
+ const provider = await getProvider(id);
270
+ return provider?.displayName || id;
271
+ }));
272
+ console.log(chalk_1.default.gray(` • Providers: ${providerNames.join(', ')}`));
273
+ }
274
+ else {
275
+ console.log(chalk_1.default.gray(' • Providers: none'));
276
+ }
277
+ console.log();
278
+ // Ask user what they want to do
279
+ const response = await (0, prompts_1.default)({
280
+ type: 'select',
281
+ name: 'action',
282
+ message: 'What would you like to do?',
283
+ choices: [
284
+ {
285
+ title: 'Add a provider',
286
+ value: 'add-provider',
287
+ description: 'Add a new platform integration (GitHub, GitLab, etc.)'
288
+ },
289
+ {
290
+ title: 'Update FRAIM key',
291
+ value: 'update-key',
292
+ description: 'Change your FRAIM API key'
293
+ },
294
+ {
295
+ title: 'Reconfigure from scratch',
296
+ value: 'reconfigure',
297
+ description: 'Start fresh setup (will preserve existing config as backup)'
298
+ },
299
+ {
300
+ title: 'Cancel',
301
+ value: 'cancel',
302
+ description: 'Exit without making changes'
303
+ }
304
+ ],
305
+ initial: 0
306
+ });
307
+ if (!response.action || response.action === 'cancel') {
308
+ console.log(chalk_1.default.gray('\nSetup cancelled. No changes made.'));
309
+ return;
310
+ }
311
+ if (response.action === 'add-provider') {
312
+ console.log(chalk_1.default.blue('\n📦 Adding a provider...\n'));
313
+ const providerClient = new provider_client_1.ProviderClient(fraimKey, process.env.FRAIM_REMOTE_URL || undefined);
314
+ // Prompt for which provider to add
315
+ const providersToAdd = await (0, provider_prompts_1.promptForProviders)(providerClient);
316
+ requestedProviders = providersToAdd;
317
+ }
318
+ else if (response.action === 'update-key') {
319
+ console.log(chalk_1.default.blue('\n🔑 Updating FRAIM key...\n'));
320
+ fraimKey = await promptForFraimKey();
321
+ }
322
+ else if (response.action === 'reconfigure') {
323
+ console.log(chalk_1.default.blue('\n🔄 Starting fresh setup...\n'));
324
+ // Backup existing config
325
+ const backupPath = globalConfigPath + '.backup.' + Date.now();
326
+ fs_1.default.copyFileSync(globalConfigPath, backupPath);
327
+ console.log(chalk_1.default.gray(` Backed up existing config to: ${path_1.default.basename(backupPath)}\n`));
328
+ // Treat as fresh setup
329
+ fraimKey = options.key || await promptForFraimKey();
330
+ console.log(chalk_1.default.green('✅ FRAIM key accepted\n'));
331
+ mode = options.mode ? parseModeOption(options.mode) : await promptForMode();
332
+ const parsed = await parseLegacyOptions(options, fraimKey);
333
+ requestedProviders = parsed.requestedProviders;
334
+ providedTokens = parsed.providedTokens;
335
+ providedConfigs = parsed.providedConfigs;
336
+ // Clear existing tokens/configs for fresh start
337
+ Object.keys(tokens).forEach(key => delete tokens[key]);
338
+ Object.keys(configs).forEach(key => delete configs[key]);
339
+ // Continue with fresh setup flow
340
+ const providerClient = new provider_client_1.ProviderClient(fraimKey, process.env.FRAIM_REMOTE_URL || undefined);
341
+ if (mode === 'integrated') {
342
+ let providersToSetup = requestedProviders;
343
+ if (providersToSetup.length === 0) {
344
+ providersToSetup = await (0, provider_prompts_1.promptForProviders)(providerClient);
345
+ }
346
+ for (const providerId of providersToSetup) {
347
+ try {
348
+ const creds = await (0, provider_prompts_1.promptForProviderCredentials)(providerClient, providerId, providedTokens[providerId], providedConfigs[providerId]);
349
+ tokens[providerId] = creds.token;
350
+ if (creds.config) {
351
+ configs[providerId] = creds.config;
352
+ }
353
+ }
354
+ catch (e) {
355
+ const providerName = await (0, provider_registry_1.getProviderDisplayName)(providerId);
356
+ console.log(chalk_1.default.red(`❌ Failed to get ${providerName} credentials`));
357
+ process.exit(1);
358
+ }
359
+ }
360
+ }
361
+ else if (mode === 'split') {
362
+ // Split mode setup
363
+ console.log(chalk_1.default.blue('\n🔀 Split Mode Configuration'));
364
+ console.log(chalk_1.default.gray('Configure separate platforms for code hosting and issue tracking.\n'));
365
+ const codeRepoProvider = await (0, provider_prompts_1.promptForSingleProvider)(providerClient, 'code');
366
+ const creds1 = await (0, provider_prompts_1.promptForProviderCredentials)(providerClient, codeRepoProvider, providedTokens[codeRepoProvider], providedConfigs[codeRepoProvider]);
367
+ tokens[codeRepoProvider] = creds1.token;
368
+ if (creds1.config) {
369
+ configs[codeRepoProvider] = creds1.config;
370
+ }
371
+ const issueProvider = await (0, provider_prompts_1.promptForSingleProvider)(providerClient, 'issues');
372
+ if (!tokens[issueProvider]) {
373
+ const creds2 = await (0, provider_prompts_1.promptForProviderCredentials)(providerClient, issueProvider, providedTokens[issueProvider], providedConfigs[issueProvider]);
374
+ tokens[issueProvider] = creds2.token;
375
+ if (creds2.config) {
376
+ configs[issueProvider] = creds2.config;
377
+ }
378
+ }
379
+ }
380
+ // Save and configure MCP
381
+ console.log(chalk_1.default.blue('\n💾 Saving global configuration...'));
382
+ saveGlobalConfig(fraimKey, mode, tokens, configs);
383
+ console.log(chalk_1.default.blue('\n🔌 Configuring MCP servers...'));
384
+ const mcpTokens = {};
385
+ Object.entries(tokens).forEach(([id, token]) => {
386
+ mcpTokens[id] = token;
387
+ });
388
+ const providerConfigsMap = {};
389
+ Object.entries(configs).forEach(([providerId, config]) => {
390
+ providerConfigsMap[providerId] = config;
391
+ });
392
+ await (0, auto_mcp_setup_1.autoConfigureMCP)(fraimKey, mcpTokens, options.ide ? [options.ide] : undefined, providerConfigsMap);
393
+ console.log(chalk_1.default.green('\n🎯 Reconfiguration complete!'));
394
+ return;
395
+ }
396
+ }
397
+ mode = existingConfig.mode || 'integrated';
398
+ // Preserve existing tokens
399
+ if (existingConfig.tokens) {
400
+ Object.assign(tokens, existingConfig.tokens);
401
+ }
402
+ // Preserve existing configs
403
+ if (existingConfig.providerConfigs) {
404
+ Object.entries(existingConfig.providerConfigs).forEach(([key, value]) => {
405
+ const providerId = key.replace('Config', '');
406
+ configs[providerId] = value;
407
+ });
408
+ }
409
+ }
410
+ catch (e) {
411
+ console.log(chalk_1.default.red('❌ Failed to read existing configuration'));
412
+ console.error('Error details:', e);
413
+ process.exit(1);
414
+ }
415
+ }
416
+ else {
417
+ // Initial setup
418
+ console.log(chalk_1.default.yellow('📋 This setup will:'));
419
+ console.log(chalk_1.default.gray(' • Validate your FRAIM key'));
420
+ console.log(chalk_1.default.gray(' • Choose your usage mode'));
421
+ console.log(chalk_1.default.gray(' • Configure platform integrations'));
422
+ console.log(chalk_1.default.gray(' • Sync FRAIM scripts to your system\n'));
423
+ // Get FRAIM key
424
+ fraimKey = options.key || await promptForFraimKey();
425
+ console.log(chalk_1.default.green('✅ FRAIM key accepted\n'));
426
+ // Ask for mode preference (or use explicit option)
427
+ mode = options.mode ? parseModeOption(options.mode) : await promptForMode();
428
+ // Parse provider CLI flags on first-time setup too
429
+ const parsed = await parseLegacyOptions(options, fraimKey);
430
+ requestedProviders = parsed.requestedProviders;
431
+ providedTokens = parsed.providedTokens;
432
+ providedConfigs = parsed.providedConfigs;
433
+ }
434
+ const providerClient = new provider_client_1.ProviderClient(fraimKey, process.env.FRAIM_REMOTE_URL || undefined);
435
+ // Handle platform tokens based on mode
436
+ if (mode === 'integrated') {
437
+ let providersToSetup = requestedProviders;
438
+ // If no specific providers requested and not an update, ask user
439
+ if (!isUpdate && providersToSetup.length === 0) {
440
+ providersToSetup = await (0, provider_prompts_1.promptForProviders)(providerClient);
441
+ }
442
+ // Get credentials for each provider
443
+ for (const providerId of providersToSetup) {
444
+ if (!tokens[providerId]) {
445
+ try {
446
+ // Use provided tokens if available, otherwise prompt
447
+ const creds = await (0, provider_prompts_1.promptForProviderCredentials)(providerClient, providerId, providedTokens[providerId], providedConfigs[providerId]);
448
+ tokens[providerId] = creds.token;
449
+ if (creds.config) {
450
+ configs[providerId] = creds.config;
451
+ }
452
+ }
453
+ catch (e) {
454
+ const providerName = await (0, provider_registry_1.getProviderDisplayName)(providerId);
455
+ console.log(chalk_1.default.red(`❌ Failed to get ${providerName} credentials`));
456
+ process.exit(1);
457
+ }
458
+ }
459
+ }
460
+ if (Object.keys(tokens).length === 0) {
461
+ console.log(chalk_1.default.yellow('⚠️ No platform tokens configured.'));
462
+ console.log(chalk_1.default.gray(' You can add them later with:'));
463
+ const allProviderIds = await (0, provider_registry_1.getAllProviderIds)();
464
+ allProviderIds.forEach(id => {
465
+ console.log(chalk_1.default.cyan(` fraim setup --${id}`));
466
+ });
467
+ console.log();
468
+ }
469
+ }
470
+ else if (mode === 'split') {
471
+ // Split mode: separate platforms for code repository and issue tracking
472
+ console.log(chalk_1.default.blue('\n🔀 Split Mode Configuration'));
473
+ console.log(chalk_1.default.gray('Configure separate platforms for code hosting and issue tracking.\n'));
474
+ // Get code repository platform
475
+ const codeRepoProvider = await (0, provider_prompts_1.promptForSingleProvider)(providerClient, 'code');
476
+ // Get code repository credentials
477
+ if (!tokens[codeRepoProvider]) {
478
+ try {
479
+ // Use provided tokens if available, otherwise prompt
480
+ const creds = await (0, provider_prompts_1.promptForProviderCredentials)(providerClient, codeRepoProvider, providedTokens[codeRepoProvider], providedConfigs[codeRepoProvider]);
481
+ tokens[codeRepoProvider] = creds.token;
482
+ if (creds.config) {
483
+ configs[codeRepoProvider] = creds.config;
484
+ }
485
+ }
486
+ catch (e) {
487
+ const providerName = await (0, provider_registry_1.getProviderDisplayName)(codeRepoProvider);
488
+ console.log(chalk_1.default.red(`❌ Failed to get ${providerName} credentials`));
489
+ process.exit(1);
490
+ }
491
+ }
492
+ // Get issue tracking platform
493
+ const issueProvider = await (0, provider_prompts_1.promptForSingleProvider)(providerClient, 'issues');
494
+ // Get issue tracking credentials (if different from code repo)
495
+ if (!tokens[issueProvider]) {
496
+ try {
497
+ // Use provided tokens if available, otherwise prompt
498
+ const creds = await (0, provider_prompts_1.promptForProviderCredentials)(providerClient, issueProvider, providedTokens[issueProvider], providedConfigs[issueProvider]);
499
+ tokens[issueProvider] = creds.token;
500
+ if (creds.config) {
501
+ configs[issueProvider] = creds.config;
502
+ }
503
+ }
504
+ catch (e) {
505
+ const providerName = await (0, provider_registry_1.getProviderDisplayName)(issueProvider);
506
+ console.log(chalk_1.default.red(`❌ Failed to get ${providerName} credentials`));
507
+ process.exit(1);
508
+ }
509
+ }
510
+ const codeRepoName = await (0, provider_registry_1.getProviderDisplayName)(codeRepoProvider);
511
+ const issueName = await (0, provider_registry_1.getProviderDisplayName)(issueProvider);
512
+ console.log(chalk_1.default.green(`\n✅ Split mode configured: ${codeRepoName} (code) + ${issueName} (issues)\n`));
513
+ }
514
+ else {
515
+ console.log(chalk_1.default.gray('ℹ️ Conversational mode: No platform tokens needed\n'));
516
+ }
517
+ // Save global configuration
518
+ console.log(chalk_1.default.blue('💾 Saving global configuration...'));
519
+ saveGlobalConfig(fraimKey, mode, tokens, configs);
520
+ // Configure MCP servers
521
+ if (!isUpdate) {
522
+ // Initial setup - configure all detected IDEs
523
+ console.log(chalk_1.default.blue('\n🔌 Configuring MCP servers...'));
524
+ // Convert to legacy format for MCP config generator
525
+ const mcpTokens = {};
526
+ Object.entries(tokens).forEach(([id, token]) => {
527
+ mcpTokens[id] = token;
528
+ });
529
+ if (mode === 'conversational' && Object.keys(mcpTokens).length === 0) {
530
+ console.log(chalk_1.default.yellow('ℹ️ Conversational mode: Configuring MCP servers without platform integration'));
531
+ console.log(chalk_1.default.gray(' FRAIM workflows will work, but platform-specific features will be unavailable\n'));
532
+ }
533
+ try {
534
+ // Build providerConfigs map from configs
535
+ const providerConfigsMap = {};
536
+ Object.entries(configs).forEach(([providerId, config]) => {
537
+ providerConfigsMap[providerId] = config;
538
+ });
539
+ await (0, auto_mcp_setup_1.autoConfigureMCP)(fraimKey, mcpTokens, options.ide ? [options.ide] : undefined, providerConfigsMap);
540
+ }
541
+ catch (e) {
542
+ console.log(chalk_1.default.yellow('⚠️ MCP configuration encountered issues'));
543
+ console.log(chalk_1.default.gray(' You can configure MCP manually or run setup again later\n'));
544
+ }
545
+ }
546
+ else {
547
+ // Update existing setup - refresh all IDE MCP configs with new keys
548
+ console.log(chalk_1.default.blue('\n🔄 Updating IDE MCP configurations...'));
549
+ try {
550
+ const { detectInstalledIDEs } = await Promise.resolve().then(() => __importStar(require('../setup/ide-detector')));
551
+ const installedIDEs = detectInstalledIDEs();
552
+ if (installedIDEs.length === 0) {
553
+ console.log(chalk_1.default.gray(' No IDE configurations found to update'));
554
+ }
555
+ else {
556
+ // Convert to legacy format for MCP config generator
557
+ const mcpTokens = {};
558
+ Object.entries(tokens).forEach(([id, token]) => {
559
+ mcpTokens[id] = token;
560
+ });
561
+ // Build providerConfigs map from configs
562
+ const providerConfigsMap = {};
563
+ Object.entries(configs).forEach(([providerId, config]) => {
564
+ providerConfigsMap[providerId] = config;
565
+ });
566
+ const ideNames = installedIDEs.map(ide => ide.name);
567
+ await (0, auto_mcp_setup_1.autoConfigureMCP)(fraimKey, mcpTokens, ideNames, providerConfigsMap);
568
+ console.log(chalk_1.default.green(`✅ Updated MCP configs for: ${ideNames.join(', ')}`));
569
+ }
570
+ }
571
+ catch (e) {
572
+ console.log(chalk_1.default.yellow('⚠️ Failed to update IDE MCP configurations'));
573
+ console.log(chalk_1.default.gray(' You can update them manually with: fraim add-ide <ide-name>\n'));
574
+ }
575
+ }
576
+ // Auto-run project init if we're in a git repo (only on initial setup)
577
+ if (!isUpdate) {
578
+ if ((0, platform_detection_1.isGitRepository)()) {
579
+ console.log(chalk_1.default.blue('\n📁 Git repository detected — initializing project...'));
580
+ await (0, init_project_1.runInitProject)();
581
+ }
582
+ else {
583
+ console.log(chalk_1.default.yellow('\nℹ️ Not in a git repository — skipping project initialization.'));
584
+ console.log(chalk_1.default.cyan(' To initialize a project later, cd into a repo and run: fraim init-project'));
585
+ }
586
+ }
587
+ // Show summary
588
+ console.log(chalk_1.default.green('\n🎯 Setup complete!'));
589
+ console.log(chalk_1.default.gray(` Mode: ${mode}`));
590
+ if (mode !== 'conversational') {
591
+ const configuredProviders = await Promise.all(Object.keys(tokens).map(async (id) => await (0, provider_registry_1.getProviderDisplayName)(id)));
592
+ console.log(chalk_1.default.gray(` Platforms: ${configuredProviders.join(', ') || 'none'}`));
593
+ }
594
+ console.log(chalk_1.default.cyan('\n📝 For future projects:'));
595
+ console.log(chalk_1.default.cyan(' 1. cd into any project directory'));
596
+ console.log(chalk_1.default.cyan(' 2. Run: fraim init-project'));
597
+ console.log(chalk_1.default.cyan(' 3. Tell your AI agent: "Onboard this project"'));
598
+ if (mode === 'integrated') {
599
+ const allProviderIds = await (0, provider_registry_1.getAllProviderIds)();
600
+ const unconfiguredProviders = allProviderIds.filter(id => !tokens[id]);
601
+ if (unconfiguredProviders.length > 0) {
602
+ console.log(chalk_1.default.gray('\n💡 To add more platforms later:'));
603
+ unconfiguredProviders.forEach(id => {
604
+ console.log(chalk_1.default.gray(` fraim setup --${id}`));
605
+ });
606
+ }
607
+ }
608
+ };
609
+ exports.runSetup = runSetup;
610
+ exports.setupCommand = new commander_1.Command('setup')
611
+ .description('Complete global FRAIM setup with platform configuration')
612
+ .option('--key <key>', 'FRAIM API key')
613
+ .option('--mode <mode>', 'Usage mode: integrated | split | conversational')
614
+ .option('--ide <ides>', 'Configure specific IDEs');
615
+ // Track initialization promise for CLI entry point
616
+ exports.setupCommandInitialization = null;
617
+ // Dynamically add provider options from registry (async initialization)
618
+ exports.setupCommandInitialization = (async () => {
619
+ try {
620
+ const allProviderIds = await (0, provider_registry_1.getAllProviderIds)();
621
+ for (const providerId of allProviderIds) {
622
+ const provider = await (0, provider_registry_1.getProvider)(providerId);
623
+ if (!provider)
624
+ continue;
625
+ // Add provider flag (e.g., --github)
626
+ exports.setupCommand.option(`--${providerId}`, `Add/update ${provider.displayName} integration`);
627
+ // Add token option (e.g., --github-token) - primarily for testing/automation
628
+ exports.setupCommand.option(`--${providerId}-token <token>`, `${provider.displayName} token (optional - will prompt if not provided)`);
629
+ // Add config options if provider requires them
630
+ const configReqs = await (0, provider_registry_1.getProviderConfigRequirements)(providerId);
631
+ configReqs.forEach(req => {
632
+ // Use custom CLI option name if provided, otherwise convert key to kebab-case
633
+ const optionSuffix = req.cliOptionName || req.key.replace(/([A-Z])/g, '-$1').toLowerCase();
634
+ const optionName = `${providerId}-${optionSuffix}`;
635
+ exports.setupCommand.option(`--${optionName} <value>`, `${req.description} (optional - will prompt if not provided)`);
636
+ });
637
+ }
638
+ }
639
+ catch (error) {
640
+ // If we can't fetch providers (e.g., no config yet), that's okay
641
+ // The command will still work, just without dynamic options
642
+ }
643
+ })();
644
+ // Wrap the action to ensure initialization completes first
645
+ exports.setupCommand.action(async (options) => {
646
+ // Wait for dynamic options to be registered
647
+ if (exports.setupCommandInitialization) {
648
+ await exports.setupCommandInitialization;
649
+ }
650
+ return (0, exports.runSetup)(options);
651
+ });