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.
- package/README.md +445 -0
- package/bin/fraim.js +23 -0
- package/dist/src/cli/api/get-provider-client.js +41 -0
- package/dist/src/cli/api/provider-client.js +107 -0
- package/dist/src/cli/commands/add-ide.js +430 -0
- package/dist/src/cli/commands/add-provider.js +233 -0
- package/dist/src/cli/commands/doctor.js +149 -0
- package/dist/src/cli/commands/init-project.js +301 -0
- package/dist/src/cli/commands/list-overridable.js +184 -0
- package/dist/src/cli/commands/list.js +57 -0
- package/dist/src/cli/commands/login.js +84 -0
- package/dist/src/cli/commands/mcp.js +15 -0
- package/dist/src/cli/commands/migrate-project-fraim.js +42 -0
- package/dist/src/cli/commands/override.js +177 -0
- package/dist/src/cli/commands/setup.js +651 -0
- package/dist/src/cli/commands/sync.js +162 -0
- package/dist/src/cli/commands/test-mcp.js +171 -0
- package/dist/src/cli/doctor/check-runner.js +199 -0
- package/dist/src/cli/doctor/checks/global-setup-checks.js +220 -0
- package/dist/src/cli/doctor/checks/ide-config-checks.js +250 -0
- package/dist/src/cli/doctor/checks/mcp-connectivity-checks.js +381 -0
- package/dist/src/cli/doctor/checks/project-setup-checks.js +282 -0
- package/dist/src/cli/doctor/checks/scripts-checks.js +157 -0
- package/dist/src/cli/doctor/checks/workflow-checks.js +251 -0
- package/dist/src/cli/doctor/reporters/console-reporter.js +96 -0
- package/dist/src/cli/doctor/reporters/json-reporter.js +11 -0
- package/dist/src/cli/doctor/types.js +6 -0
- package/dist/src/cli/fraim.js +100 -0
- package/dist/src/cli/internal/device-flow-service.js +83 -0
- package/dist/src/cli/mcp/ide-formats.js +243 -0
- package/dist/src/cli/mcp/mcp-server-builder.js +48 -0
- package/dist/src/cli/mcp/mcp-server-registry.js +160 -0
- package/dist/src/cli/mcp/types.js +3 -0
- package/dist/src/cli/providers/local-provider-registry.js +166 -0
- package/dist/src/cli/providers/provider-registry.js +230 -0
- package/dist/src/cli/setup/auto-mcp-setup.js +331 -0
- package/dist/src/cli/setup/codex-local-config.js +37 -0
- package/dist/src/cli/setup/first-run.js +242 -0
- package/dist/src/cli/setup/ide-detector.js +179 -0
- package/dist/src/cli/setup/mcp-config-generator.js +192 -0
- package/dist/src/cli/setup/provider-prompts.js +339 -0
- package/dist/src/cli/utils/agent-adapters.js +126 -0
- package/dist/src/cli/utils/digest-utils.js +47 -0
- package/dist/src/cli/utils/fraim-gitignore.js +40 -0
- package/dist/src/cli/utils/platform-detection.js +258 -0
- package/dist/src/cli/utils/project-bootstrap.js +93 -0
- package/dist/src/cli/utils/remote-sync.js +315 -0
- package/dist/src/cli/utils/script-sync-utils.js +221 -0
- package/dist/src/cli/utils/version-utils.js +32 -0
- package/dist/src/core/ai-mentor.js +230 -0
- package/dist/src/core/config-loader.js +114 -0
- package/dist/src/core/config-writer.js +75 -0
- package/dist/src/core/types.js +23 -0
- package/dist/src/core/utils/git-utils.js +95 -0
- package/dist/src/core/utils/include-resolver.js +92 -0
- package/dist/src/core/utils/inheritance-parser.js +288 -0
- package/dist/src/core/utils/job-parser.js +176 -0
- package/dist/src/core/utils/local-registry-resolver.js +616 -0
- package/dist/src/core/utils/object-utils.js +11 -0
- package/dist/src/core/utils/project-fraim-migration.js +103 -0
- package/dist/src/core/utils/project-fraim-paths.js +38 -0
- package/dist/src/core/utils/provider-utils.js +18 -0
- package/dist/src/core/utils/server-startup.js +34 -0
- package/dist/src/core/utils/stub-generator.js +147 -0
- package/dist/src/core/utils/workflow-parser.js +174 -0
- package/dist/src/local-mcp-server/learning-context-builder.js +229 -0
- package/dist/src/local-mcp-server/stdio-server.js +1698 -0
- package/dist/src/local-mcp-server/usage-collector.js +264 -0
- package/index.js +85 -0
- 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
|
+
});
|