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,331 @@
|
|
|
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.autoConfigureMCP = exports.validateSetupResults = exports.promptForGitHubToken = exports.promptForIDESelection = void 0;
|
|
40
|
+
const fs_1 = __importDefault(require("fs"));
|
|
41
|
+
const path_1 = __importDefault(require("path"));
|
|
42
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
43
|
+
const prompts_1 = __importDefault(require("prompts"));
|
|
44
|
+
const ide_detector_1 = require("./ide-detector");
|
|
45
|
+
const mcp_config_generator_1 = require("./mcp-config-generator");
|
|
46
|
+
const codex_local_config_1 = require("./codex-local-config");
|
|
47
|
+
const provider_registry_1 = require("../providers/provider-registry");
|
|
48
|
+
const normalizePlatformTokens = (tokenInput) => {
|
|
49
|
+
if (!tokenInput)
|
|
50
|
+
return {};
|
|
51
|
+
if (typeof tokenInput === 'string') {
|
|
52
|
+
return tokenInput ? { github: tokenInput } : {};
|
|
53
|
+
}
|
|
54
|
+
return tokenInput;
|
|
55
|
+
};
|
|
56
|
+
const promptForIDESelection = async (detectedIDEs, tokenInput) => {
|
|
57
|
+
const tokens = normalizePlatformTokens(tokenInput);
|
|
58
|
+
console.log(chalk_1.default.green(`✅ Found ${detectedIDEs.length} IDEs that can be configured with FRAIM:\n`));
|
|
59
|
+
detectedIDEs.forEach((ide, index) => {
|
|
60
|
+
const configExists = fs_1.default.existsSync((0, ide_detector_1.expandPath)(ide.configPath));
|
|
61
|
+
const statusIcon = configExists ? '📝' : '📄';
|
|
62
|
+
const statusText = configExists ? 'has config' : 'new config';
|
|
63
|
+
console.log(chalk_1.default.white(` ${index + 1}. ${ide.name} ${statusIcon} (${statusText})`));
|
|
64
|
+
console.log(chalk_1.default.gray(` Config: ${ide.configPath}`));
|
|
65
|
+
});
|
|
66
|
+
console.log(chalk_1.default.blue('FRAIM will add these MCP servers to selected IDEs:'));
|
|
67
|
+
console.log(chalk_1.default.gray(' • fraim (required for FRAIM jobs)'));
|
|
68
|
+
console.log(chalk_1.default.gray(' • git (version control integration)'));
|
|
69
|
+
// Show configured provider MCP servers dynamically
|
|
70
|
+
const allProviderIds = await (0, provider_registry_1.getAllProviderIds)();
|
|
71
|
+
for (const providerId of allProviderIds) {
|
|
72
|
+
if (tokens[providerId]) {
|
|
73
|
+
const provider = await (0, provider_registry_1.getProvider)(providerId);
|
|
74
|
+
if (provider) {
|
|
75
|
+
console.log(chalk_1.default.gray(` - ${providerId} (${provider.displayName} API access)`));
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
console.log(chalk_1.default.gray(' • playwright (browser automation)'));
|
|
80
|
+
console.log(chalk_1.default.yellow('\n💡 Existing MCP servers will be preserved - only missing servers will be added.'));
|
|
81
|
+
const response = await (0, prompts_1.default)({
|
|
82
|
+
type: 'text',
|
|
83
|
+
name: 'selection',
|
|
84
|
+
message: 'Configure FRAIM for which IDEs? (Enter \'all\' or numbers like \'1,3\')',
|
|
85
|
+
initial: 'all',
|
|
86
|
+
validate: (value) => {
|
|
87
|
+
if (value.toLowerCase() === 'all')
|
|
88
|
+
return true;
|
|
89
|
+
if (value.toLowerCase() === 'none' || value.toLowerCase() === 'skip')
|
|
90
|
+
return true;
|
|
91
|
+
const numbers = value.split(',').map(n => parseInt(n.trim()));
|
|
92
|
+
const valid = numbers.every(n => n >= 1 && n <= detectedIDEs.length && !isNaN(n));
|
|
93
|
+
return valid || 'Please enter "all", "none", or valid numbers (e.g., "1,3")';
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
if (response.selection.toLowerCase() === 'all') {
|
|
97
|
+
return detectedIDEs;
|
|
98
|
+
}
|
|
99
|
+
if (response.selection.toLowerCase() === 'none' || response.selection.toLowerCase() === 'skip') {
|
|
100
|
+
return [];
|
|
101
|
+
}
|
|
102
|
+
const selectedIndices = response.selection.split(',').map((n) => parseInt(n.trim()) - 1);
|
|
103
|
+
return selectedIndices.map((i) => detectedIDEs[i]).filter((ide) => ide !== undefined);
|
|
104
|
+
};
|
|
105
|
+
exports.promptForIDESelection = promptForIDESelection;
|
|
106
|
+
// Re-export promptForProviderToken for backward compatibility
|
|
107
|
+
// This maintains the same interface but uses the generic system
|
|
108
|
+
const promptForGitHubToken = async () => {
|
|
109
|
+
const { promptForProviderToken } = await Promise.resolve().then(() => __importStar(require('./provider-prompts')));
|
|
110
|
+
const { getProviderClient } = await Promise.resolve().then(() => __importStar(require('../api/get-provider-client')));
|
|
111
|
+
const client = getProviderClient();
|
|
112
|
+
return promptForProviderToken(client, 'github');
|
|
113
|
+
};
|
|
114
|
+
exports.promptForGitHubToken = promptForGitHubToken;
|
|
115
|
+
const backupConfig = (configPath) => {
|
|
116
|
+
if (fs_1.default.existsSync(configPath)) {
|
|
117
|
+
const backupPath = `${configPath}.fraim-backup-${Date.now()}`;
|
|
118
|
+
fs_1.default.copyFileSync(configPath, backupPath);
|
|
119
|
+
console.log(chalk_1.default.gray(` 📁 Backup created: ${backupPath}`));
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
const testMCPConnection = async (ide) => {
|
|
123
|
+
// This is a placeholder for future MCP connection testing
|
|
124
|
+
// For now, we'll just check if the config file was created successfully
|
|
125
|
+
const configPath = (0, ide_detector_1.expandPath)(ide.configPath);
|
|
126
|
+
return fs_1.default.existsSync(configPath);
|
|
127
|
+
};
|
|
128
|
+
const validateSetupResults = async (configuredIDEs) => {
|
|
129
|
+
if (configuredIDEs.length === 0)
|
|
130
|
+
return;
|
|
131
|
+
console.log(chalk_1.default.blue('\n🔍 Validating setup results...'));
|
|
132
|
+
const validationResults = await Promise.all(configuredIDEs.map(async (ide) => {
|
|
133
|
+
const isValid = await testMCPConnection(ide);
|
|
134
|
+
return { ide: ide.name, valid: isValid };
|
|
135
|
+
}));
|
|
136
|
+
const validIDEs = validationResults.filter(r => r.valid);
|
|
137
|
+
const invalidIDEs = validationResults.filter(r => !r.valid);
|
|
138
|
+
if (validIDEs.length > 0) {
|
|
139
|
+
console.log(chalk_1.default.green(`✅ ${validIDEs.length} IDEs configured successfully:`));
|
|
140
|
+
validIDEs.forEach(({ ide }) => {
|
|
141
|
+
console.log(chalk_1.default.green(` • ${ide}`));
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
if (invalidIDEs.length > 0) {
|
|
145
|
+
console.log(chalk_1.default.red(`❌ ${invalidIDEs.length} IDEs may have configuration issues:`));
|
|
146
|
+
invalidIDEs.forEach(({ ide }) => {
|
|
147
|
+
console.log(chalk_1.default.red(` • ${ide}`));
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
exports.validateSetupResults = validateSetupResults;
|
|
152
|
+
const configureIDEMCP = async (ide, fraimKey, tokenInput, providerConfigs) => {
|
|
153
|
+
const tokens = normalizePlatformTokens(tokenInput);
|
|
154
|
+
const configPath = (0, ide_detector_1.expandPath)(ide.configPath);
|
|
155
|
+
console.log(chalk_1.default.blue(`🔧 Configuring ${ide.name}...`));
|
|
156
|
+
// Create backup
|
|
157
|
+
backupConfig(configPath);
|
|
158
|
+
// Ensure directory exists
|
|
159
|
+
const configDir = path_1.default.dirname(configPath);
|
|
160
|
+
if (!fs_1.default.existsSync(configDir)) {
|
|
161
|
+
fs_1.default.mkdirSync(configDir, { recursive: true });
|
|
162
|
+
console.log(chalk_1.default.gray(` 📁 Created directory: ${configDir}`));
|
|
163
|
+
}
|
|
164
|
+
let existingConfig = {};
|
|
165
|
+
let existingMCPServers = {};
|
|
166
|
+
const serversKey = ide.configType === 'vscode' ? 'servers' : 'mcpServers';
|
|
167
|
+
if (fs_1.default.existsSync(configPath) && ide.configFormat === 'json') {
|
|
168
|
+
try {
|
|
169
|
+
existingConfig = JSON.parse(fs_1.default.readFileSync(configPath, 'utf8'));
|
|
170
|
+
existingMCPServers = existingConfig[serversKey] || {};
|
|
171
|
+
console.log(chalk_1.default.gray(` 📋 Found existing config with ${Object.keys(existingMCPServers).length} MCP servers`));
|
|
172
|
+
}
|
|
173
|
+
catch (e) {
|
|
174
|
+
console.log(chalk_1.default.yellow(` ⚠️ Could not parse existing ${ide.name} config, creating new one`));
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
if (ide.configFormat === 'toml') {
|
|
178
|
+
// For TOML (Codex), we need to handle differently
|
|
179
|
+
let existingTomlContent = '';
|
|
180
|
+
if (fs_1.default.existsSync(configPath)) {
|
|
181
|
+
existingTomlContent = fs_1.default.readFileSync(configPath, 'utf8');
|
|
182
|
+
console.log(chalk_1.default.gray(` 📋 Found existing TOML config`));
|
|
183
|
+
}
|
|
184
|
+
const newTomlContent = await (0, mcp_config_generator_1.generateMCPConfig)(ide.configType, fraimKey, tokens, providerConfigs);
|
|
185
|
+
const { getAllMCPServerIds } = await Promise.resolve().then(() => __importStar(require('../mcp/mcp-server-registry')));
|
|
186
|
+
const serversToAdd = getAllMCPServerIds();
|
|
187
|
+
const mergeResult = (0, mcp_config_generator_1.mergeTomlMCPServers)(existingTomlContent, newTomlContent, serversToAdd);
|
|
188
|
+
fs_1.default.writeFileSync(configPath, mergeResult.content);
|
|
189
|
+
mergeResult.addedServers.forEach(server => {
|
|
190
|
+
console.log(chalk_1.default.green(` ✅ Added ${server} MCP server`));
|
|
191
|
+
});
|
|
192
|
+
mergeResult.replacedServers.forEach(server => {
|
|
193
|
+
console.log(chalk_1.default.green(` ✅ Updated ${server} MCP server`));
|
|
194
|
+
});
|
|
195
|
+
mergeResult.skippedServers.forEach(server => {
|
|
196
|
+
console.log(chalk_1.default.gray(` ⏭️ Skipped ${server} (already configured or not requested)`));
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
// For JSON configs - intelligent merging
|
|
201
|
+
const newConfig = await (0, mcp_config_generator_1.generateMCPConfig)(ide.configType, fraimKey, tokens, providerConfigs);
|
|
202
|
+
const newMCPServers = newConfig[serversKey] || newConfig.mcpServers || {};
|
|
203
|
+
// Merge MCP servers intelligently
|
|
204
|
+
const mergedMCPServers = { ...existingMCPServers };
|
|
205
|
+
const addedServers = [];
|
|
206
|
+
const updatedServers = [];
|
|
207
|
+
const skippedServers = [];
|
|
208
|
+
// Servers that should always be updated (not skipped)
|
|
209
|
+
const alwaysUpdateServers = new Set(['fraim', 'github', 'gitlab', 'jira', 'ado', 'linear']);
|
|
210
|
+
for (const [serverName, serverConfig] of Object.entries(newMCPServers)) {
|
|
211
|
+
if (!existingMCPServers[serverName]) {
|
|
212
|
+
mergedMCPServers[serverName] = serverConfig;
|
|
213
|
+
addedServers.push(serverName);
|
|
214
|
+
}
|
|
215
|
+
else if (alwaysUpdateServers.has(serverName)) {
|
|
216
|
+
// Always update these servers to ensure keys/tokens are current
|
|
217
|
+
mergedMCPServers[serverName] = serverConfig;
|
|
218
|
+
updatedServers.push(serverName);
|
|
219
|
+
}
|
|
220
|
+
else {
|
|
221
|
+
skippedServers.push(serverName);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
// Merge with existing config (VS Code uses "servers", others use "mcpServers")
|
|
225
|
+
const mergedConfig = {
|
|
226
|
+
...existingConfig,
|
|
227
|
+
[serversKey]: mergedMCPServers
|
|
228
|
+
};
|
|
229
|
+
// Write updated config
|
|
230
|
+
fs_1.default.writeFileSync(configPath, JSON.stringify(mergedConfig, null, 2));
|
|
231
|
+
addedServers.forEach(server => {
|
|
232
|
+
console.log(chalk_1.default.green(` ✅ Added ${server} MCP server`));
|
|
233
|
+
});
|
|
234
|
+
updatedServers.forEach(server => {
|
|
235
|
+
console.log(chalk_1.default.blue(` 🔄 Updated ${server} MCP server`));
|
|
236
|
+
});
|
|
237
|
+
skippedServers.forEach(server => {
|
|
238
|
+
console.log(chalk_1.default.gray(` ⏭️ Skipped ${server} (already exists)`));
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
console.log(chalk_1.default.green(`✅ Updated ${configPath}`));
|
|
242
|
+
if (ide.configType === 'codex') {
|
|
243
|
+
const localResult = (0, codex_local_config_1.ensureCodexLocalConfig)(process.cwd());
|
|
244
|
+
const status = localResult.created ? 'Created' : localResult.updated ? 'Updated' : 'Verified';
|
|
245
|
+
console.log(chalk_1.default.green(` ✅ ${status} local Codex config: ${localResult.path}`));
|
|
246
|
+
}
|
|
247
|
+
};
|
|
248
|
+
const autoConfigureMCP = async (fraimKey, tokenInput, selectedIDEs, providerConfigs) => {
|
|
249
|
+
const tokens = normalizePlatformTokens(tokenInput);
|
|
250
|
+
const detectedIDEs = (0, ide_detector_1.detectInstalledIDEs)();
|
|
251
|
+
if (detectedIDEs.length === 0) {
|
|
252
|
+
console.log(chalk_1.default.yellow('⚠️ No supported IDEs detected.'));
|
|
253
|
+
console.log(chalk_1.default.gray('Supported IDEs: Claude, Antigravity, Kiro, Cursor, VSCode, Codex, Windsurf'));
|
|
254
|
+
console.log(chalk_1.default.blue('\n💡 You can install an IDE and run setup again later.'));
|
|
255
|
+
console.log(chalk_1.default.gray(' Or continue with manual MCP configuration.'));
|
|
256
|
+
const continueAnyway = await (0, prompts_1.default)({
|
|
257
|
+
type: 'confirm',
|
|
258
|
+
name: 'continue',
|
|
259
|
+
message: 'Continue setup without IDE configuration?',
|
|
260
|
+
initial: true
|
|
261
|
+
});
|
|
262
|
+
if (!continueAnyway.continue) {
|
|
263
|
+
console.log(chalk_1.default.red('Setup cancelled. Install an IDE and run setup again.'));
|
|
264
|
+
process.exit(1);
|
|
265
|
+
}
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
let idesToConfigure;
|
|
269
|
+
if (selectedIDEs && selectedIDEs.length > 0) {
|
|
270
|
+
// Use command line specified IDEs
|
|
271
|
+
idesToConfigure = detectedIDEs.filter(ide => selectedIDEs.some(selected => ide.name.toLowerCase().includes(selected.toLowerCase())));
|
|
272
|
+
if (idesToConfigure.length === 0) {
|
|
273
|
+
console.log(chalk_1.default.yellow(`⚠️ No IDEs found matching: ${selectedIDEs.join(', ')}`));
|
|
274
|
+
console.log(chalk_1.default.gray('Available IDEs:'));
|
|
275
|
+
detectedIDEs.forEach(ide => console.log(chalk_1.default.gray(` • ${ide.name}`)));
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
else {
|
|
280
|
+
// Interactive selection
|
|
281
|
+
idesToConfigure = await (0, exports.promptForIDESelection)(detectedIDEs, tokens);
|
|
282
|
+
}
|
|
283
|
+
if (idesToConfigure.length === 0) {
|
|
284
|
+
console.log(chalk_1.default.yellow('⚠️ No IDEs selected for configuration.'));
|
|
285
|
+
console.log(chalk_1.default.blue('💡 You can run "fraim setup" again later to configure IDEs.'));
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
console.log(chalk_1.default.blue(`\n🚀 Configuring ${idesToConfigure.length} IDEs...`));
|
|
289
|
+
const results = {
|
|
290
|
+
successful: [],
|
|
291
|
+
failed: []
|
|
292
|
+
};
|
|
293
|
+
for (const ide of idesToConfigure) {
|
|
294
|
+
try {
|
|
295
|
+
await configureIDEMCP(ide, fraimKey, tokens, providerConfigs);
|
|
296
|
+
results.successful.push(ide.name);
|
|
297
|
+
}
|
|
298
|
+
catch (error) {
|
|
299
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
300
|
+
results.failed.push({ ide: ide.name, error: errorMessage });
|
|
301
|
+
console.log(chalk_1.default.red(`❌ Failed to configure ${ide.name}: ${errorMessage}`));
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
// Summary
|
|
305
|
+
console.log(chalk_1.default.green(`\n🎉 Setup Summary:`));
|
|
306
|
+
console.log(chalk_1.default.green(` ✅ Successfully configured: ${results.successful.length} IDEs`));
|
|
307
|
+
if (results.successful.length > 0) {
|
|
308
|
+
results.successful.forEach(ide => {
|
|
309
|
+
console.log(chalk_1.default.green(` • ${ide}`));
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
if (results.failed.length > 0) {
|
|
313
|
+
console.log(chalk_1.default.red(` ❌ Failed to configure: ${results.failed.length} IDEs`));
|
|
314
|
+
results.failed.forEach(({ ide, error }) => {
|
|
315
|
+
console.log(chalk_1.default.red(` • ${ide}: ${error}`));
|
|
316
|
+
});
|
|
317
|
+
console.log(chalk_1.default.yellow('\n💡 You can try running setup again or configure these IDEs manually.'));
|
|
318
|
+
}
|
|
319
|
+
if (results.successful.length > 0) {
|
|
320
|
+
console.log(chalk_1.default.blue('\n🔄 Please restart your configured IDEs to load the new MCP servers.'));
|
|
321
|
+
// Validate setup results
|
|
322
|
+
const successfulIDEs = idesToConfigure.filter(ide => results.successful.includes(ide.name));
|
|
323
|
+
await (0, exports.validateSetupResults)(successfulIDEs);
|
|
324
|
+
console.log(chalk_1.default.blue('\n🎯 Next steps:'));
|
|
325
|
+
console.log(chalk_1.default.cyan(' 1. Restart your configured IDEs'));
|
|
326
|
+
console.log(chalk_1.default.cyan(' 2. Go to any project directory'));
|
|
327
|
+
console.log(chalk_1.default.cyan(' 3. Run: npx fraim-framework@latest init-project'));
|
|
328
|
+
console.log(chalk_1.default.cyan(' 4. Tell your AI agent: "Onboard this project"'));
|
|
329
|
+
}
|
|
330
|
+
};
|
|
331
|
+
exports.autoConfigureMCP = autoConfigureMCP;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.ensureCodexLocalConfig = void 0;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const mcp_config_generator_1 = require("./mcp-config-generator");
|
|
10
|
+
const escapeTomlString = (value) => value.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
|
11
|
+
const buildFraimCwdBlock = (projectRoot) => `
|
|
12
|
+
[mcp_servers.fraim]
|
|
13
|
+
cwd = "${escapeTomlString(projectRoot)}"
|
|
14
|
+
`;
|
|
15
|
+
const ensureCodexLocalConfig = (projectRoot) => {
|
|
16
|
+
const codexDir = path_1.default.join(projectRoot, '.codex');
|
|
17
|
+
const codexConfigPath = path_1.default.join(codexDir, 'config.toml');
|
|
18
|
+
if (!fs_1.default.existsSync(codexDir)) {
|
|
19
|
+
fs_1.default.mkdirSync(codexDir, { recursive: true });
|
|
20
|
+
}
|
|
21
|
+
const hadExistingFile = fs_1.default.existsSync(codexConfigPath);
|
|
22
|
+
const existingContent = hadExistingFile ? fs_1.default.readFileSync(codexConfigPath, 'utf8') : '';
|
|
23
|
+
const generatedContent = buildFraimCwdBlock(projectRoot);
|
|
24
|
+
const mergeResult = (0, mcp_config_generator_1.mergeTomlMCPServers)(existingContent, generatedContent, ['fraim']);
|
|
25
|
+
const normalizedExisting = existingContent.replace(/\r\n/g, '\n');
|
|
26
|
+
const normalizedMerged = mergeResult.content.replace(/\r\n/g, '\n');
|
|
27
|
+
const shouldWrite = !hadExistingFile || normalizedExisting !== normalizedMerged;
|
|
28
|
+
if (shouldWrite) {
|
|
29
|
+
fs_1.default.writeFileSync(codexConfigPath, mergeResult.content);
|
|
30
|
+
}
|
|
31
|
+
return {
|
|
32
|
+
path: codexConfigPath,
|
|
33
|
+
created: !hadExistingFile,
|
|
34
|
+
updated: shouldWrite && hadExistingFile
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
exports.ensureCodexLocalConfig = ensureCodexLocalConfig;
|
|
@@ -0,0 +1,242 @@
|
|
|
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.runFirstRunExperience = void 0;
|
|
40
|
+
const prompts_1 = __importDefault(require("prompts"));
|
|
41
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
42
|
+
const fs_1 = __importDefault(require("fs"));
|
|
43
|
+
const path_1 = __importDefault(require("path"));
|
|
44
|
+
const os_1 = __importDefault(require("os"));
|
|
45
|
+
const script_sync_utils_1 = require("../utils/script-sync-utils");
|
|
46
|
+
const project_fraim_paths_1 = require("../../core/utils/project-fraim-paths");
|
|
47
|
+
const loadGlobalConfig = () => {
|
|
48
|
+
const globalConfigPath = path_1.default.join((0, script_sync_utils_1.getUserFraimDir)(), 'config.json');
|
|
49
|
+
if (!fs_1.default.existsSync(globalConfigPath)) {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
try {
|
|
53
|
+
const config = JSON.parse(fs_1.default.readFileSync(globalConfigPath, 'utf8'));
|
|
54
|
+
return {
|
|
55
|
+
fraimKey: config.apiKey,
|
|
56
|
+
githubToken: config.githubToken
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
catch (e) {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
const checkMCPConfigurations = () => {
|
|
64
|
+
const sources = [];
|
|
65
|
+
// Check Kiro MCP config
|
|
66
|
+
const kiroConfigPath = path_1.default.join(os_1.default.homedir(), '.kiro', 'settings', 'mcp.json');
|
|
67
|
+
if (fs_1.default.existsSync(kiroConfigPath)) {
|
|
68
|
+
try {
|
|
69
|
+
const kiroConfig = JSON.parse(fs_1.default.readFileSync(kiroConfigPath, 'utf8'));
|
|
70
|
+
if (kiroConfig.mcpServers?.fraim) {
|
|
71
|
+
sources.push('Kiro');
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
catch (e) {
|
|
75
|
+
// Ignore parsing errors
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
// Check Claude Desktop / Cowork config
|
|
79
|
+
const claudeConfigPath = path_1.default.join(os_1.default.homedir(), 'AppData', 'Roaming', 'Claude', 'claude_desktop_config.json');
|
|
80
|
+
if (fs_1.default.existsSync(claudeConfigPath)) {
|
|
81
|
+
try {
|
|
82
|
+
const claudeConfig = JSON.parse(fs_1.default.readFileSync(claudeConfigPath, 'utf8'));
|
|
83
|
+
if (claudeConfig.mcpServers?.fraim) {
|
|
84
|
+
sources.push('Claude Desktop / Cowork');
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
catch (e) {
|
|
88
|
+
// Ignore parsing errors
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
// Check macOS Claude Desktop / Cowork config path
|
|
92
|
+
const claudeMacPath = path_1.default.join(os_1.default.homedir(), 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json');
|
|
93
|
+
if (fs_1.default.existsSync(claudeMacPath)) {
|
|
94
|
+
try {
|
|
95
|
+
const claudeConfig = JSON.parse(fs_1.default.readFileSync(claudeMacPath, 'utf8'));
|
|
96
|
+
if (claudeConfig.mcpServers?.fraim) {
|
|
97
|
+
sources.push('Claude Desktop / Cowork (macOS)');
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
catch (e) {
|
|
101
|
+
// Ignore parsing errors
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
// Check VS Code MCP config (uses "servers" key)
|
|
105
|
+
const vscodePaths = [
|
|
106
|
+
path_1.default.join(os_1.default.homedir(), 'AppData', 'Roaming', 'Code', 'User', 'mcp.json'),
|
|
107
|
+
path_1.default.join(os_1.default.homedir(), 'Library', 'Application Support', 'Code', 'User', 'mcp.json'),
|
|
108
|
+
path_1.default.join(os_1.default.homedir(), '.config', 'Code', 'User', 'mcp.json')
|
|
109
|
+
];
|
|
110
|
+
for (const vscodePath of vscodePaths) {
|
|
111
|
+
if (fs_1.default.existsSync(vscodePath)) {
|
|
112
|
+
try {
|
|
113
|
+
const vscodeConfig = JSON.parse(fs_1.default.readFileSync(vscodePath, 'utf8'));
|
|
114
|
+
if (vscodeConfig.servers?.fraim) {
|
|
115
|
+
sources.push('VSCode');
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
catch (e) {
|
|
120
|
+
// Ignore parsing errors
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return {
|
|
125
|
+
found: sources.length > 0,
|
|
126
|
+
sources
|
|
127
|
+
};
|
|
128
|
+
};
|
|
129
|
+
const runFirstRunExperience = async () => {
|
|
130
|
+
// Skip interactive setup in CI/Test environments
|
|
131
|
+
if (process.env.CI === 'true' || process.env.TEST_MODE === 'true') {
|
|
132
|
+
console.log(chalk_1.default.yellow('ℹ️ Skipping interactive first-run experience (CI/TEST_MODE detected)'));
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
console.log(chalk_1.default.blue('\n👋 Welcome to FRAIM! Let\'s get you set up.\n'));
|
|
136
|
+
// Check for existing configuration
|
|
137
|
+
const globalConfig = loadGlobalConfig();
|
|
138
|
+
const mcpCheck = checkMCPConfigurations();
|
|
139
|
+
if (globalConfig && globalConfig.fraimKey) {
|
|
140
|
+
console.log(chalk_1.default.green('✅ Found existing FRAIM configuration'));
|
|
141
|
+
if (mcpCheck.found) {
|
|
142
|
+
console.log(chalk_1.default.green(`✅ Found FRAIM MCP configuration in: ${mcpCheck.sources.join(', ')}`));
|
|
143
|
+
console.log(chalk_1.default.blue('🎉 You\'re all set! FRAIM is ready to use.\n'));
|
|
144
|
+
}
|
|
145
|
+
else {
|
|
146
|
+
console.log(chalk_1.default.yellow('⚠️ FRAIM key found but no MCP configuration detected.'));
|
|
147
|
+
console.log(chalk_1.default.cyan('💡 Consider running "fraim add-ide" to configure your IDE.\n'));
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
else if (mcpCheck.found) {
|
|
151
|
+
console.log(chalk_1.default.green(`✅ Found FRAIM MCP configuration in: ${mcpCheck.sources.join(', ')}`));
|
|
152
|
+
console.log(chalk_1.default.yellow('⚠️ But no global FRAIM configuration found.'));
|
|
153
|
+
console.log(chalk_1.default.cyan('💡 Your MCP setup looks good! Skipping key setup.\n'));
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
// No existing configuration found - ask for FRAIM key
|
|
157
|
+
console.log(chalk_1.default.yellow('🔍 No existing FRAIM configuration detected.\n'));
|
|
158
|
+
let response;
|
|
159
|
+
try {
|
|
160
|
+
response = await (0, prompts_1.default)({
|
|
161
|
+
type: 'text',
|
|
162
|
+
name: 'fraimKey',
|
|
163
|
+
message: 'Do you have a FRAIM key? (Input key or press Enter to skip)',
|
|
164
|
+
validate: (value) => true // Optional input
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
catch (e) {
|
|
168
|
+
console.warn(chalk_1.default.yellow('\n⚠️ Interactive prompts experienced an issue. Skipping setup.\n'));
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
if (response.fraimKey && response.fraimKey.trim().length > 0) {
|
|
172
|
+
const key = response.fraimKey.trim();
|
|
173
|
+
console.log(chalk_1.default.green('\n✅ Key received.'));
|
|
174
|
+
console.log(chalk_1.default.yellow('\nPlease add the following to your IDE\'s MCP config (e.g. claude_desktop_config.json):'));
|
|
175
|
+
const mcpConfig = {
|
|
176
|
+
mcpServers: {
|
|
177
|
+
fraim: {
|
|
178
|
+
serverUrl: "https://fraim.wellnessatwork.me",
|
|
179
|
+
headers: {
|
|
180
|
+
"x-api-key": key
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
console.log(chalk_1.default.cyan(JSON.stringify(mcpConfig, null, 2)));
|
|
186
|
+
console.log('\n');
|
|
187
|
+
}
|
|
188
|
+
else {
|
|
189
|
+
console.log(chalk_1.default.yellow('\nℹ️ If you need a key, please email sid.mathur@gmail.com to request one.\n'));
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
// 2. Ask about Architecture Document
|
|
193
|
+
const archResponse = await (0, prompts_1.default)({
|
|
194
|
+
type: 'confirm',
|
|
195
|
+
name: 'hasArchDoc',
|
|
196
|
+
message: 'Do you have an architecture document for this project?',
|
|
197
|
+
initial: false
|
|
198
|
+
});
|
|
199
|
+
if (!archResponse.hasArchDoc) {
|
|
200
|
+
console.log(chalk_1.default.yellow('\n💡 To create an architecture document, please ask your IDE Agent:'));
|
|
201
|
+
console.log(chalk_1.default.cyan(' "Run the bootstrap/create-architecture workflow"'));
|
|
202
|
+
console.log(chalk_1.default.gray(' (This ensures your agent understands your codebase structure)\n'));
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
const pathResponse = await (0, prompts_1.default)({
|
|
206
|
+
type: 'text',
|
|
207
|
+
name: 'archDocPath',
|
|
208
|
+
message: 'Please provide the relative path to your architecture document:',
|
|
209
|
+
initial: 'docs/architecture.md',
|
|
210
|
+
validate: (value) => value.length > 0 ? true : 'Path cannot be empty'
|
|
211
|
+
});
|
|
212
|
+
if (pathResponse.archDocPath) {
|
|
213
|
+
const fs = await Promise.resolve().then(() => __importStar(require('fs')));
|
|
214
|
+
const path = await Promise.resolve().then(() => __importStar(require('path')));
|
|
215
|
+
const resolvedPath = path.resolve(process.cwd(), pathResponse.archDocPath);
|
|
216
|
+
if (fs.existsSync(resolvedPath)) {
|
|
217
|
+
try {
|
|
218
|
+
const configPath = (0, project_fraim_paths_1.getWorkspaceConfigPath)(process.cwd());
|
|
219
|
+
if (fs.existsSync(configPath)) {
|
|
220
|
+
const configContent = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
221
|
+
if (!configContent.customizations)
|
|
222
|
+
configContent.customizations = {};
|
|
223
|
+
configContent.customizations.architectureDoc = pathResponse.archDocPath;
|
|
224
|
+
fs.writeFileSync(configPath, JSON.stringify(configContent, null, 2));
|
|
225
|
+
console.log(chalk_1.default.green(`\n✅ Linked architecture document: ${pathResponse.archDocPath}`));
|
|
226
|
+
}
|
|
227
|
+
else {
|
|
228
|
+
console.log(chalk_1.default.red(`\n❌ ${(0, project_fraim_paths_1.getWorkspaceFraimDisplayPath)('config.json')} not found. Could not save preference.`));
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
catch (e) {
|
|
232
|
+
console.error(chalk_1.default.red('\n❌ Failed to update config:'), e);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
else {
|
|
236
|
+
console.log(chalk_1.default.yellow(`\n⚠️ File not found at ${pathResponse.archDocPath}. We'll skip linking it for now.`));
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
console.log(chalk_1.default.green(`\n✅ Jobs are installed in ${(0, project_fraim_paths_1.getWorkspaceFraimDisplayPath)('ai-employee/jobs')} and ${(0, project_fraim_paths_1.getWorkspaceFraimDisplayPath)('ai-manager/jobs')}.\n`));
|
|
241
|
+
};
|
|
242
|
+
exports.runFirstRunExperience = runFirstRunExperience;
|