fraim-framework 2.0.37 → 2.0.41

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 (38) hide show
  1. package/README.md +8 -0
  2. package/dist/src/ai-manager/ai-manager.js +162 -0
  3. package/dist/src/cli/commands/init-project.js +74 -0
  4. package/dist/src/cli/commands/setup.js +176 -0
  5. package/dist/src/cli/commands/test-mcp.js +135 -0
  6. package/dist/src/cli/fraim.js +6 -0
  7. package/dist/src/cli/setup/auto-mcp-setup.js +367 -0
  8. package/dist/src/cli/setup/ide-detector.js +165 -0
  9. package/dist/src/cli/setup/mcp-config-generator.js +144 -0
  10. package/dist/src/cli/setup/token-validator.js +49 -0
  11. package/dist/src/fraim-mcp-server.js +198 -0
  12. package/dist/tests/debug-tools.js +2 -2
  13. package/dist/tests/shared-server-utils.js +57 -0
  14. package/dist/tests/test-ai-manager.js +113 -0
  15. package/dist/tests/test-client-scripts-validation.js +27 -5
  16. package/dist/tests/test-complete-setup-flow.js +110 -0
  17. package/dist/tests/test-ide-detector.js +46 -0
  18. package/dist/tests/test-improved-setup.js +121 -0
  19. package/dist/tests/test-mcp-config-generator.js +99 -0
  20. package/dist/tests/test-mcp-connection.js +58 -117
  21. package/dist/tests/test-mcp-issue-integration.js +2 -2
  22. package/dist/tests/test-mcp-lifecycle-methods.js +34 -100
  23. package/dist/tests/test-mcp-shared-server.js +308 -0
  24. package/dist/tests/test-package-size.js +21 -8
  25. package/dist/tests/test-script-location-independence.js +39 -62
  26. package/dist/tests/test-server-utils.js +32 -0
  27. package/dist/tests/test-session-rehydration.js +2 -2
  28. package/dist/tests/test-setup-integration.js +98 -0
  29. package/dist/tests/test-standalone.js +2 -2
  30. package/dist/tests/test-stub-registry.js +23 -7
  31. package/dist/tests/test-telemetry.js +2 -2
  32. package/dist/tests/test-token-validator.js +30 -0
  33. package/dist/tests/test-user-journey.js +2 -1
  34. package/package.json +3 -2
  35. package/registry/scripts/code-quality-check.sh +566 -559
  36. package/registry/scripts/prep-issue.sh +7 -0
  37. package/registry/scripts/verify-pr-comments.sh +74 -70
  38. /package/registry/stubs/workflows/{convert-to-pdf.md → marketing/convert-to-pdf.md} +0 -0
package/README.md CHANGED
@@ -255,6 +255,14 @@ FRAIM/
255
255
 
256
256
  ## šŸš€ **Get Started in 60 Seconds**
257
257
 
258
+ ### **āš ļø Prerequisites**
259
+
260
+ **Shell Requirements:**
261
+ - **Windows**: Must use Git Bash (install from https://git-scm.com/download/win)
262
+ - **macOS/Linux**: Default terminal works fine
263
+
264
+ **Why Git Bash on Windows?** All FRAIM scripts use Unix-style paths and Bash commands. Git Bash ensures consistent behavior across platforms.
265
+
258
266
  ### **Install & Initialize**
259
267
  ```bash
260
268
  npm install -g fraim-framework
@@ -0,0 +1,162 @@
1
+ "use strict";
2
+ /**
3
+ * Simplified AI Manager
4
+ *
5
+ * Single AI manager integrated into fraim-mcp-server that reads validation rules
6
+ * from pre-defined JSON files and provides structured review instructions.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.AIManager = void 0;
10
+ const fs_1 = require("fs");
11
+ const path_1 = require("path");
12
+ class AIManager {
13
+ constructor(rulesPath) {
14
+ // Default to rules directory - server-side only, not sent to client
15
+ this.rulesPath = rulesPath || (0, path_1.join)(__dirname, '..', '..', 'registry', 'ai-manager-rules');
16
+ }
17
+ /**
18
+ * Generate review instructions for a workflow phase
19
+ */
20
+ generateReviewInstructions(context) {
21
+ console.log(`šŸ¤– AI Manager: Generating review instructions for ${context.workflowType} phase`);
22
+ const rules = this.loadWorkflowRules(context.workflowType);
23
+ if (!rules) {
24
+ throw new Error(`No rules found for workflow type: ${context.workflowType}`);
25
+ }
26
+ return this.formatReviewInstructions(rules, context);
27
+ }
28
+ /**
29
+ * Evaluate agent's review report and provide decision
30
+ */
31
+ evaluateReport(report, context) {
32
+ console.log(`šŸ¤– AI Manager: Evaluating review report for ${context.workflowType} phase`);
33
+ const iterationCount = report.iterationCount || 1;
34
+ const maxIterations = 3;
35
+ if (report.pass) {
36
+ return {
37
+ action: 'PROCEED',
38
+ message: 'Review passed. Ready to submit PR to human for review.',
39
+ nextSteps: [
40
+ 'Update issue status to "status:needs-review"',
41
+ 'Remove "status:wip" label',
42
+ 'Include complete review results in evidence document',
43
+ 'Submit PR for human review with evidence'
44
+ ],
45
+ iterationCount
46
+ };
47
+ }
48
+ else {
49
+ // Check if max iterations reached
50
+ if (iterationCount >= maxIterations) {
51
+ return {
52
+ action: 'ESCALATE',
53
+ message: `Maximum iterations (${maxIterations}) reached. Escalating to human review despite failing validation.`,
54
+ nextSteps: [
55
+ 'Update issue status to "status:needs-review"',
56
+ 'Add "ai-manager:max-iterations" label to indicate escalation reason',
57
+ 'Include all iteration attempts and failure reasons in evidence document',
58
+ 'Submit PR for human review with detailed iteration history',
59
+ 'Human reviewer should focus on the recurring validation failures'
60
+ ],
61
+ iterationCount,
62
+ maxIterationsReached: true
63
+ };
64
+ }
65
+ else {
66
+ return {
67
+ action: 'ITERATE',
68
+ message: `Review failed. Address the identified issues before proceeding. (Iteration ${iterationCount}/${maxIterations})`,
69
+ nextSteps: [
70
+ 'Fix all issues identified in the failure reasons',
71
+ 'Re-run validation steps to verify fixes',
72
+ `Request new review instructions with iterationCount: ${iterationCount + 1}`,
73
+ 'Do not submit PR until review passes or max iterations reached'
74
+ ],
75
+ iterationCount
76
+ };
77
+ }
78
+ }
79
+ }
80
+ /**
81
+ * Load workflow rules from JSON file (server-side only)
82
+ */
83
+ loadWorkflowRules(workflowType) {
84
+ const rulesFile = (0, path_1.join)(this.rulesPath, `${workflowType}.json`);
85
+ if (!(0, fs_1.existsSync)(rulesFile)) {
86
+ console.warn(`āš ļø No rules file found for workflow: ${workflowType} at ${rulesFile}`);
87
+ return null;
88
+ }
89
+ try {
90
+ const content = (0, fs_1.readFileSync)(rulesFile, 'utf8');
91
+ return JSON.parse(content);
92
+ }
93
+ catch (error) {
94
+ console.error(`āŒ Error loading rules for ${workflowType}:`, error);
95
+ return null;
96
+ }
97
+ }
98
+ /**
99
+ * Format review instructions with proper template structure
100
+ */
101
+ formatReviewInstructions(rules, context) {
102
+ const instructions = `# AI Manager Review Instructions
103
+
104
+ ## Workflow: ${rules.workflowType.toUpperCase()}
105
+ **Issue:** #${context.issueNumber}
106
+ **Phase:** ${context.phase}
107
+
108
+ ## Description
109
+ ${rules.description}
110
+
111
+ ## Iteration Tracking
112
+ **IMPORTANT**: You must track your iteration count for this phase. This is your attempt number for this specific workflow phase (spec, implement, test, etc.).
113
+
114
+ - **First attempt**: Use iterationCount: 1
115
+ - **After first failure**: Use iterationCount: 2
116
+ - **After second failure**: Use iterationCount: 3
117
+ - **Maximum iterations**: 3 (after 3rd failure, work will be escalated to human review)
118
+
119
+ ## Validation Steps
120
+ Complete each step below and evaluate whether you meet the pass criteria:
121
+
122
+ ${rules.validationRules.map(rule => `
123
+ ### Step ${rule.step}: ${rule.description}
124
+ ${rule.command ? `**Command:** \`${rule.command.replace('{issue_number}', context.issueNumber)}\`` : ''}
125
+ **Pass Criteria:** ${rule.passCriteria}
126
+ **Weight:** ${rule.weight.toUpperCase()}${rule.weight === 'blocking' ? ' (Must pass to proceed)' : ' (Should pass for quality)'}
127
+ **Category:** ${rule.category}
128
+ `).join('')}
129
+
130
+ ## Grading Criteria
131
+
132
+ ### Pass Requirements
133
+ ${rules.gradingCriteria.passRequirements.map(req => `- ${req}`).join('\n')}
134
+
135
+ ### Fail Requirements
136
+ ${rules.gradingCriteria.failRequirements.map(req => `- ${req}`).join('\n')}
137
+
138
+ ## Reporting Format
139
+ ${rules.reportingFormat.instructions}
140
+
141
+ **Required Response Format:**
142
+ \`\`\`json
143
+ {
144
+ "pass": true/false,
145
+ "reasons": ["reason1", "reason2"], // Only required if pass=false
146
+ "iterationCount": 1 // REQUIRED: Your current iteration number for this phase
147
+ }
148
+ \`\`\`
149
+
150
+ ## Iteration Limits
151
+ - **Maximum 3 iterations** per workflow phase
152
+ - After 3 failed attempts, work will be **automatically escalated** to human review
153
+ - Track your attempts carefully - include iterationCount in every report
154
+ - Each workflow phase (spec, implement, test) has its own iteration counter
155
+
156
+ ## Next Steps
157
+ After completing your review, report back with the JSON response above including your iteration count. The AI Manager will evaluate your report and provide next steps based on your progress and iteration count.
158
+ `;
159
+ return instructions;
160
+ }
161
+ }
162
+ exports.AIManager = AIManager;
@@ -0,0 +1,74 @@
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.initProjectCommand = exports.runInitProject = void 0;
7
+ const commander_1 = require("commander");
8
+ const fs_1 = __importDefault(require("fs"));
9
+ const path_1 = __importDefault(require("path"));
10
+ const os_1 = __importDefault(require("os"));
11
+ const chalk_1 = __importDefault(require("chalk"));
12
+ const sync_1 = require("./sync");
13
+ const types_1 = require("../../fraim/types");
14
+ const git_utils_1 = require("../../utils/git-utils");
15
+ const version_utils_1 = require("../../utils/version-utils");
16
+ const checkGlobalSetup = () => {
17
+ const globalConfigPath = path_1.default.join(os_1.default.homedir(), '.fraim', 'config.json');
18
+ return fs_1.default.existsSync(globalConfigPath);
19
+ };
20
+ const runInitProject = async () => {
21
+ console.log(chalk_1.default.blue('šŸš€ Initializing FRAIM project...'));
22
+ // Check if global setup exists
23
+ if (!checkGlobalSetup()) {
24
+ console.log(chalk_1.default.red('āŒ Global FRAIM setup not found.'));
25
+ console.log(chalk_1.default.yellow('Please run global setup first:'));
26
+ console.log(chalk_1.default.cyan(' fraim setup --key=<your-fraim-key>'));
27
+ process.exit(1);
28
+ }
29
+ const projectRoot = process.cwd();
30
+ const fraimDir = path_1.default.join(projectRoot, '.fraim');
31
+ const configPath = path_1.default.join(fraimDir, 'config.json');
32
+ if (!fs_1.default.existsSync(fraimDir)) {
33
+ fs_1.default.mkdirSync(fraimDir, { recursive: true });
34
+ console.log(chalk_1.default.green('āœ… Created .fraim directory'));
35
+ }
36
+ else {
37
+ console.log(chalk_1.default.yellow('ā„¹ļø .fraim directory already exists'));
38
+ }
39
+ if (!fs_1.default.existsSync(configPath)) {
40
+ const remoteInfo = (0, git_utils_1.getGitRemoteInfo)();
41
+ // Git remote is optional for project init
42
+ const config = {
43
+ ...types_1.DEFAULT_FRAIM_CONFIG,
44
+ version: (0, version_utils_1.getFraimVersion)(),
45
+ project: {
46
+ ...types_1.DEFAULT_FRAIM_CONFIG.project,
47
+ name: remoteInfo.repo || path_1.default.basename(projectRoot)
48
+ },
49
+ git: {
50
+ ...types_1.DEFAULT_FRAIM_CONFIG.git,
51
+ repoOwner: remoteInfo.owner || types_1.DEFAULT_FRAIM_CONFIG.git.repoOwner,
52
+ repoName: remoteInfo.repo || path_1.default.basename(projectRoot)
53
+ }
54
+ };
55
+ fs_1.default.writeFileSync(configPath, JSON.stringify(config, null, 2));
56
+ console.log(chalk_1.default.green('āœ… Created .fraim/config.json'));
57
+ }
58
+ // Create subdirectories
59
+ ['workflows'].forEach(dir => {
60
+ const dirPath = path_1.default.join(fraimDir, dir);
61
+ if (!fs_1.default.existsSync(dirPath)) {
62
+ fs_1.default.mkdirSync(dirPath, { recursive: true });
63
+ console.log(chalk_1.default.green(`āœ… Created .fraim/${dir}`));
64
+ }
65
+ });
66
+ // Sync workflows from registry
67
+ await (0, sync_1.runSync)({});
68
+ console.log(chalk_1.default.green('\nāœ… FRAIM project initialized!'));
69
+ console.log(chalk_1.default.cyan('Try: Ask your AI agent "list fraim workflows"'));
70
+ };
71
+ exports.runInitProject = runInitProject;
72
+ exports.initProjectCommand = new commander_1.Command('init-project')
73
+ .description('Initialize FRAIM in the current project (requires global setup)')
74
+ .action(exports.runInitProject);
@@ -0,0 +1,176 @@
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.setupCommand = exports.runSetup = void 0;
7
+ const commander_1 = require("commander");
8
+ const chalk_1 = __importDefault(require("chalk"));
9
+ const prompts_1 = __importDefault(require("prompts"));
10
+ const fs_1 = __importDefault(require("fs"));
11
+ const path_1 = __importDefault(require("path"));
12
+ const os_1 = __importDefault(require("os"));
13
+ const token_validator_1 = require("../setup/token-validator");
14
+ const auto_mcp_setup_1 = require("../setup/auto-mcp-setup");
15
+ const script_sync_utils_1 = require("../../utils/script-sync-utils");
16
+ const version_utils_1 = require("../../utils/version-utils");
17
+ const promptForFraimKey = async () => {
18
+ console.log(chalk_1.default.blue('šŸ”‘ FRAIM Key Setup'));
19
+ console.log('FRAIM requires a valid API key to access workflows and features.\n');
20
+ let key = null;
21
+ let attempts = 0;
22
+ const maxAttempts = 3;
23
+ while (!key && attempts < maxAttempts) {
24
+ const keyResponse = await (0, prompts_1.default)({
25
+ type: 'password',
26
+ name: 'key',
27
+ message: attempts === 0
28
+ ? 'Enter your FRAIM key (required)'
29
+ : `Enter your FRAIM key (attempt ${attempts + 1}/${maxAttempts})`,
30
+ validate: (value) => {
31
+ if (!value)
32
+ return 'FRAIM key is required';
33
+ if ((0, token_validator_1.isValidTokenFormat)(value, 'fraim'))
34
+ return true;
35
+ return 'Please enter a valid FRAIM key (starts with fraim_)';
36
+ }
37
+ });
38
+ if (!keyResponse.key) {
39
+ console.log(chalk_1.default.red('\nāŒ FRAIM key is required to proceed.'));
40
+ console.log(chalk_1.default.gray('If you need a key, please email sid.mathur@gmail.com to request one.\n'));
41
+ const retry = await (0, prompts_1.default)({
42
+ type: 'confirm',
43
+ name: 'retry',
44
+ message: 'Would you like to try entering the FRAIM key again?',
45
+ initial: true
46
+ });
47
+ if (!retry.retry) {
48
+ console.log(chalk_1.default.red('Setup cancelled. Please obtain a FRAIM key and try again.'));
49
+ process.exit(1);
50
+ }
51
+ attempts++;
52
+ continue;
53
+ }
54
+ // Validate key
55
+ const isValid = await (0, token_validator_1.validateFraimKey)(keyResponse.key);
56
+ if (isValid) {
57
+ console.log(chalk_1.default.green('āœ… FRAIM key validated\n'));
58
+ return keyResponse.key;
59
+ }
60
+ else {
61
+ console.log(chalk_1.default.red('āŒ Invalid FRAIM key\n'));
62
+ attempts++;
63
+ }
64
+ }
65
+ console.log(chalk_1.default.red('\nāŒ Maximum attempts reached. Setup cancelled.'));
66
+ console.log(chalk_1.default.gray('Please ensure you have a valid FRAIM key and try again.'));
67
+ process.exit(1);
68
+ };
69
+ const saveGlobalConfig = (fraimKey) => {
70
+ const globalConfigDir = path_1.default.join(os_1.default.homedir(), '.fraim');
71
+ const globalConfigPath = path_1.default.join(globalConfigDir, 'config.json');
72
+ if (!fs_1.default.existsSync(globalConfigDir)) {
73
+ fs_1.default.mkdirSync(globalConfigDir, { recursive: true });
74
+ }
75
+ const config = {
76
+ version: (0, version_utils_1.getFraimVersion)(),
77
+ apiKey: fraimKey,
78
+ configuredAt: new Date().toISOString(),
79
+ userPreferences: {
80
+ autoSync: true,
81
+ backupConfigs: true
82
+ }
83
+ };
84
+ fs_1.default.writeFileSync(globalConfigPath, JSON.stringify(config, null, 2));
85
+ console.log(chalk_1.default.green('āœ… Global FRAIM configuration saved'));
86
+ };
87
+ const syncGlobalScripts = () => {
88
+ console.log(chalk_1.default.blue('šŸ”„ Syncing FRAIM scripts to user directory...'));
89
+ // Find registry path
90
+ let registryPath = path_1.default.join(__dirname, '../../../../registry');
91
+ if (!fs_1.default.existsSync(registryPath)) {
92
+ registryPath = path_1.default.join(__dirname, '../../../registry');
93
+ }
94
+ if (fs_1.default.existsSync(registryPath)) {
95
+ const syncResult = (0, script_sync_utils_1.syncScriptsToUserDirectory)(registryPath);
96
+ console.log(chalk_1.default.green(`āœ… Synced ${syncResult.synced} scripts to user directory.`));
97
+ if (syncResult.ephemeral > 0) {
98
+ console.log(chalk_1.default.gray(` ${syncResult.ephemeral} dependent scripts will use ephemeral execution.`));
99
+ }
100
+ }
101
+ else {
102
+ console.log(chalk_1.default.yellow('āš ļø Registry not found, skipping script sync.'));
103
+ }
104
+ };
105
+ const runSetup = async (options) => {
106
+ console.log(chalk_1.default.blue('šŸš€ Welcome to FRAIM! Let\'s get you set up.\n'));
107
+ // Show what we're about to do
108
+ console.log(chalk_1.default.yellow('šŸ“‹ This setup will:'));
109
+ console.log(chalk_1.default.gray(' • Validate your FRAIM and GitHub tokens'));
110
+ console.log(chalk_1.default.gray(' • Create global FRAIM configuration'));
111
+ console.log(chalk_1.default.gray(' • Detect and configure supported IDEs'));
112
+ console.log(chalk_1.default.gray(' • Sync FRAIM scripts to your system'));
113
+ console.log(chalk_1.default.gray(' • Set up MCP servers for AI integration\n'));
114
+ // Get FRAIM key
115
+ let fraimKey = options.key;
116
+ if (!fraimKey) {
117
+ fraimKey = await promptForFraimKey();
118
+ }
119
+ else {
120
+ if (!(0, token_validator_1.isValidTokenFormat)(fraimKey, 'fraim')) {
121
+ console.log(chalk_1.default.red('āŒ Invalid FRAIM key format. Key must start with fraim_'));
122
+ process.exit(1);
123
+ }
124
+ console.log(chalk_1.default.blue('šŸ”‘ Validating FRAIM key...'));
125
+ const isValid = await (0, token_validator_1.validateFraimKey)(fraimKey);
126
+ if (!isValid) {
127
+ console.log(chalk_1.default.red('āŒ Invalid FRAIM key'));
128
+ process.exit(1);
129
+ }
130
+ console.log(chalk_1.default.green('āœ… FRAIM key validated\n'));
131
+ }
132
+ // Get GitHub token
133
+ let githubToken = options.githubToken;
134
+ if (!githubToken) {
135
+ githubToken = await (0, auto_mcp_setup_1.promptForGitHubToken)();
136
+ }
137
+ else {
138
+ if (!(0, token_validator_1.isValidTokenFormat)(githubToken, 'github')) {
139
+ console.log(chalk_1.default.red('āŒ Invalid GitHub token format. Token must start with ghp_ or github_pat_'));
140
+ process.exit(1);
141
+ }
142
+ console.log(chalk_1.default.blue('šŸ” Validating GitHub token...'));
143
+ const isValid = await (0, token_validator_1.validateGitHubToken)(githubToken);
144
+ if (!isValid) {
145
+ console.log(chalk_1.default.red('āŒ Invalid GitHub token'));
146
+ process.exit(1);
147
+ }
148
+ console.log(chalk_1.default.green('āœ… GitHub token validated\n'));
149
+ }
150
+ // Save global configuration
151
+ console.log(chalk_1.default.blue('šŸ’¾ Saving global configuration...'));
152
+ saveGlobalConfig(fraimKey);
153
+ // Sync global scripts
154
+ syncGlobalScripts();
155
+ // Configure IDEs
156
+ const selectedIDEs = options.ide ? options.ide.split(',') : undefined;
157
+ const autoAll = options.all;
158
+ if (autoAll && selectedIDEs) {
159
+ console.log(chalk_1.default.yellow('āš ļø Both --all and --ide specified. Using --ide selection.'));
160
+ }
161
+ await (0, auto_mcp_setup_1.autoConfigureMCP)(fraimKey, githubToken, selectedIDEs);
162
+ console.log(chalk_1.default.green('\nšŸŽÆ Setup complete! Next steps:'));
163
+ console.log(chalk_1.default.cyan(' 1. Restart your configured IDEs'));
164
+ console.log(chalk_1.default.cyan(' 2. Go to any project directory'));
165
+ console.log(chalk_1.default.cyan(' 3. Run: fraim init-project'));
166
+ console.log(chalk_1.default.cyan(' 4. Ask your AI agent: "list fraim workflows"'));
167
+ console.log(chalk_1.default.blue('\nšŸ’” Use "fraim test-mcp" to verify your setup anytime.'));
168
+ };
169
+ exports.runSetup = runSetup;
170
+ exports.setupCommand = new commander_1.Command('setup')
171
+ .description('Complete global FRAIM setup with IDE configuration')
172
+ .option('--key <key>', 'FRAIM API key')
173
+ .option('--github-token <token>', 'GitHub Personal Access Token')
174
+ .option('--all', 'Auto-configure all detected IDEs')
175
+ .option('--ide <ides>', 'Configure specific IDEs (comma-separated: claude,cursor,kiro)')
176
+ .action(exports.runSetup);
@@ -0,0 +1,135 @@
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.testMCPCommand = exports.runTestMCP = void 0;
7
+ const commander_1 = require("commander");
8
+ const chalk_1 = __importDefault(require("chalk"));
9
+ const fs_1 = __importDefault(require("fs"));
10
+ const path_1 = __importDefault(require("path"));
11
+ const os_1 = __importDefault(require("os"));
12
+ const ide_detector_1 = require("../setup/ide-detector");
13
+ const testIDEConfig = async (ide) => {
14
+ const result = {
15
+ ide: ide.name,
16
+ configExists: false,
17
+ configValid: false,
18
+ mcpServers: [],
19
+ errors: []
20
+ };
21
+ const configPath = (0, ide_detector_1.expandPath)(ide.configPath);
22
+ if (!fs_1.default.existsSync(configPath)) {
23
+ result.errors.push('Config file does not exist');
24
+ return result;
25
+ }
26
+ result.configExists = true;
27
+ try {
28
+ if (ide.configFormat === 'json') {
29
+ const configContent = fs_1.default.readFileSync(configPath, 'utf8');
30
+ const config = JSON.parse(configContent);
31
+ if (config.mcpServers) {
32
+ result.configValid = true;
33
+ result.mcpServers = Object.keys(config.mcpServers);
34
+ }
35
+ else {
36
+ result.errors.push('No mcpServers section found');
37
+ }
38
+ }
39
+ else if (ide.configFormat === 'toml') {
40
+ const configContent = fs_1.default.readFileSync(configPath, 'utf8');
41
+ // Simple TOML parsing for MCP servers
42
+ const serverMatches = configContent.match(/\[mcp_servers\.(\w+)\]/g);
43
+ if (serverMatches) {
44
+ result.configValid = true;
45
+ result.mcpServers = serverMatches.map(match => match.replace(/\[mcp_servers\.(\w+)\]/, '$1'));
46
+ }
47
+ else {
48
+ result.errors.push('No mcp_servers sections found');
49
+ }
50
+ }
51
+ }
52
+ catch (error) {
53
+ result.errors.push(`Failed to parse config: ${error instanceof Error ? error.message : 'Unknown error'}`);
54
+ }
55
+ return result;
56
+ };
57
+ const checkGlobalSetup = () => {
58
+ const globalConfigPath = path_1.default.join(os_1.default.homedir(), '.fraim', 'config.json');
59
+ return fs_1.default.existsSync(globalConfigPath);
60
+ };
61
+ const runTestMCP = async () => {
62
+ console.log(chalk_1.default.blue('šŸ” Testing MCP configuration...\n'));
63
+ // Check global setup
64
+ if (!checkGlobalSetup()) {
65
+ console.log(chalk_1.default.red('āŒ Global FRAIM setup not found.'));
66
+ console.log(chalk_1.default.yellow('Please run: fraim setup --key=<your-fraim-key>'));
67
+ return;
68
+ }
69
+ console.log(chalk_1.default.green('āœ… Global FRAIM setup found'));
70
+ // Detect IDEs
71
+ const detectedIDEs = (0, ide_detector_1.detectInstalledIDEs)();
72
+ if (detectedIDEs.length === 0) {
73
+ console.log(chalk_1.default.yellow('āš ļø No supported IDEs detected.'));
74
+ return;
75
+ }
76
+ console.log(chalk_1.default.blue(`\nšŸ” Testing ${detectedIDEs.length} detected IDEs...\n`));
77
+ const results = await Promise.all(detectedIDEs.map(ide => testIDEConfig(ide)));
78
+ let totalConfigured = 0;
79
+ let totalWithFRAIM = 0;
80
+ for (const result of results) {
81
+ console.log(chalk_1.default.white(`šŸ“± ${result.ide}`));
82
+ if (!result.configExists) {
83
+ console.log(chalk_1.default.red(' āŒ No MCP config found'));
84
+ console.log(chalk_1.default.gray(` šŸ’” Run: fraim setup --ide=${result.ide.toLowerCase()}`));
85
+ }
86
+ else if (!result.configValid) {
87
+ console.log(chalk_1.default.yellow(' āš ļø Config exists but invalid'));
88
+ result.errors.forEach(error => {
89
+ console.log(chalk_1.default.red(` āŒ ${error}`));
90
+ });
91
+ }
92
+ else {
93
+ totalConfigured++;
94
+ console.log(chalk_1.default.green(` āœ… MCP config valid (${result.mcpServers.length} servers)`));
95
+ // Check for essential servers
96
+ const essentialServers = ['fraim', 'git', 'github', 'playwright'];
97
+ const hasEssential = essentialServers.filter(server => result.mcpServers.includes(server));
98
+ if (hasEssential.includes('fraim')) {
99
+ totalWithFRAIM++;
100
+ console.log(chalk_1.default.green(' āœ… FRAIM server configured'));
101
+ }
102
+ else {
103
+ console.log(chalk_1.default.yellow(' āš ļø FRAIM server missing'));
104
+ }
105
+ if (hasEssential.length > 1) {
106
+ console.log(chalk_1.default.green(` āœ… ${hasEssential.length - 1} additional servers: ${hasEssential.filter(s => s !== 'fraim').join(', ')}`));
107
+ }
108
+ const missingEssential = essentialServers.filter(server => !result.mcpServers.includes(server));
109
+ if (missingEssential.length > 0) {
110
+ console.log(chalk_1.default.yellow(` āš ļø Missing servers: ${missingEssential.join(', ')}`));
111
+ }
112
+ }
113
+ console.log(); // Empty line
114
+ }
115
+ // Summary
116
+ console.log(chalk_1.default.blue('šŸ“Š Summary:'));
117
+ console.log(chalk_1.default.green(` āœ… ${totalConfigured}/${detectedIDEs.length} IDEs have valid MCP configs`));
118
+ console.log(chalk_1.default.green(` āœ… ${totalWithFRAIM}/${detectedIDEs.length} IDEs have FRAIM configured`));
119
+ if (totalWithFRAIM === 0) {
120
+ console.log(chalk_1.default.red('\nāŒ No IDEs have FRAIM configured!'));
121
+ console.log(chalk_1.default.yellow('šŸ’” Run: fraim setup --key=<your-fraim-key>'));
122
+ }
123
+ else if (totalWithFRAIM < detectedIDEs.length) {
124
+ console.log(chalk_1.default.yellow(`\nāš ļø ${detectedIDEs.length - totalWithFRAIM} IDEs missing FRAIM configuration`));
125
+ console.log(chalk_1.default.yellow('šŸ’” Run: fraim setup to configure remaining IDEs'));
126
+ }
127
+ else {
128
+ console.log(chalk_1.default.green('\nšŸŽ‰ All detected IDEs have FRAIM configured!'));
129
+ console.log(chalk_1.default.blue('šŸ’” Try running: fraim init-project in any project'));
130
+ }
131
+ };
132
+ exports.runTestMCP = runTestMCP;
133
+ exports.testMCPCommand = new commander_1.Command('test-mcp')
134
+ .description('Test MCP server configurations for all detected IDEs')
135
+ .action(exports.runTestMCP);
@@ -10,6 +10,9 @@ const sync_1 = require("./commands/sync");
10
10
  const doctor_1 = require("./commands/doctor");
11
11
  const list_1 = require("./commands/list");
12
12
  const wizard_1 = require("./commands/wizard");
13
+ const setup_1 = require("./commands/setup");
14
+ const init_project_1 = require("./commands/init-project");
15
+ const test_mcp_1 = require("./commands/test-mcp");
13
16
  const fs_1 = __importDefault(require("fs"));
14
17
  const path_1 = __importDefault(require("path"));
15
18
  const program = new commander_1.Command();
@@ -42,4 +45,7 @@ program.addCommand(sync_1.syncCommand);
42
45
  program.addCommand(doctor_1.doctorCommand);
43
46
  program.addCommand(list_1.listCommand);
44
47
  program.addCommand(wizard_1.wizardCommand);
48
+ program.addCommand(setup_1.setupCommand);
49
+ program.addCommand(init_project_1.initProjectCommand);
50
+ program.addCommand(test_mcp_1.testMCPCommand);
45
51
  program.parse(process.argv);