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,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;