fraim-framework 2.0.72 → 2.0.74

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/bin/fraim.js CHANGED
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env node
2
2
 
3
3
  /**
4
4
  * FRAIM Framework CLI Entry Point
@@ -0,0 +1,148 @@
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.initCommand = exports.runInit = void 0;
7
+ const commander_1 = require("commander");
8
+ const fs_1 = __importDefault(require("fs"));
9
+ const path_1 = __importDefault(require("path"));
10
+ const chalk_1 = __importDefault(require("chalk"));
11
+ const first_run_1 = require("../setup/first-run");
12
+ const sync_1 = require("./sync");
13
+ const platform_detection_1 = require("../../utils/platform-detection");
14
+ const version_utils_1 = require("../../utils/version-utils");
15
+ const script_sync_utils_1 = require("../../utils/script-sync-utils");
16
+ const runInit = async (options = {}) => {
17
+ const projectRoot = process.cwd();
18
+ const fraimDir = path_1.default.join(projectRoot, '.fraim');
19
+ const configPath = path_1.default.join(fraimDir, 'config.json');
20
+ console.log(chalk_1.default.blue('šŸš€ Initializing FRAIM...'));
21
+ if (!fs_1.default.existsSync(fraimDir)) {
22
+ fs_1.default.mkdirSync(fraimDir, { recursive: true });
23
+ console.log(chalk_1.default.green('āœ… Created .fraim directory'));
24
+ }
25
+ else {
26
+ console.log(chalk_1.default.yellow('ā„¹ļø .fraim directory already exists'));
27
+ }
28
+ if (!fs_1.default.existsSync(configPath)) {
29
+ let config;
30
+ // Try to detect platform from git remote
31
+ const detection = (0, platform_detection_1.detectPlatformFromGit)();
32
+ if (options.skipPlatform || detection.provider === 'unknown' || !detection.repository) {
33
+ // Conversational mode - no platform integration
34
+ if (!options.skipPlatform && (detection.provider === 'unknown' || !detection.repository)) {
35
+ console.log(chalk_1.default.yellow('\nā„¹ļø No git remote found or unsupported platform.'));
36
+ console.log(chalk_1.default.blue(' Initializing in conversational mode (no platform integration).'));
37
+ console.log(chalk_1.default.gray(' You can still use FRAIM workflows and AI features.'));
38
+ console.log(chalk_1.default.gray(' Platform features (issues, PRs) will be unavailable.\n'));
39
+ }
40
+ else {
41
+ console.log(chalk_1.default.blue('\n Initializing in conversational mode (platform integration skipped).\n'));
42
+ }
43
+ // Get project name from directory or git
44
+ let projectName = path_1.default.basename(projectRoot);
45
+ try {
46
+ const { execSync } = require('child_process');
47
+ const gitName = execSync('git config --get remote.origin.url', {
48
+ stdio: 'pipe',
49
+ timeout: 2000
50
+ }).toString().trim();
51
+ const match = gitName.match(/\/([^\/]+?)(\.git)?$/);
52
+ if (match) {
53
+ projectName = match[1];
54
+ }
55
+ }
56
+ catch (e) {
57
+ // Use directory name as fallback
58
+ }
59
+ config = {
60
+ version: (0, version_utils_1.getFraimVersion)(),
61
+ project: {
62
+ name: projectName
63
+ },
64
+ customizations: {
65
+ workflowsPath: '.fraim/workflows'
66
+ }
67
+ };
68
+ console.log(chalk_1.default.gray(` Project: ${projectName}`));
69
+ console.log(chalk_1.default.gray(` Mode: Conversational (no platform integration)`));
70
+ }
71
+ else {
72
+ // Integrated mode - use platform
73
+ if (!detection.repository) {
74
+ console.log(chalk_1.default.red('āŒ Error: Repository information not available'));
75
+ process.exit(1);
76
+ }
77
+ // Validate the detected repository config
78
+ const validation = (0, platform_detection_1.validateRepositoryConfig)(detection.repository);
79
+ if (!validation.valid) {
80
+ console.log(chalk_1.default.red('āŒ Error: Invalid repository configuration:'));
81
+ validation.errors.forEach(err => console.log(chalk_1.default.red(` - ${err}`)));
82
+ process.exit(1);
83
+ }
84
+ console.log(chalk_1.default.blue('\n Initializing in integrated mode (platform integration enabled).'));
85
+ console.log(chalk_1.default.gray(` Platform: ${detection.provider.toUpperCase()}`));
86
+ if (detection.provider === 'github') {
87
+ console.log(chalk_1.default.gray(` Repository: ${detection.repository.owner}/${detection.repository.name}`));
88
+ }
89
+ else if (detection.provider === 'ado') {
90
+ console.log(chalk_1.default.gray(` Organization: ${detection.repository.organization}`));
91
+ console.log(chalk_1.default.gray(` Project: ${detection.repository.project}`));
92
+ console.log(chalk_1.default.gray(` Repository: ${detection.repository.name}`));
93
+ }
94
+ console.log();
95
+ config = {
96
+ version: (0, version_utils_1.getFraimVersion)(),
97
+ project: {
98
+ name: detection.repository.name
99
+ },
100
+ repository: detection.repository,
101
+ customizations: {
102
+ workflowsPath: '.fraim/workflows'
103
+ }
104
+ };
105
+ }
106
+ fs_1.default.writeFileSync(configPath, JSON.stringify(config, null, 2));
107
+ console.log(chalk_1.default.green('āœ… Created .fraim/config.json'));
108
+ }
109
+ // Create subdirectories
110
+ ['workflows'].forEach(dir => {
111
+ const dirPath = path_1.default.join(fraimDir, dir);
112
+ if (!fs_1.default.existsSync(dirPath)) {
113
+ fs_1.default.mkdirSync(dirPath, { recursive: true });
114
+ console.log(chalk_1.default.green(`āœ… Created .fraim/${dir}`));
115
+ }
116
+ });
117
+ console.log(chalk_1.default.blue('\nšŸŽ‰ FRAIM initialized successfully!'));
118
+ // Sync workflows from registry
119
+ await (0, sync_1.runSync)({});
120
+ // Sync scripts to user directory
121
+ console.log(chalk_1.default.blue('šŸ”„ Syncing FRAIM scripts to user directory...'));
122
+ // Find registry path (same logic as sync command)
123
+ let registryPath = path_1.default.join(__dirname, '../../../../registry');
124
+ if (!fs_1.default.existsSync(registryPath)) {
125
+ registryPath = path_1.default.join(__dirname, '../../../registry');
126
+ }
127
+ if (!fs_1.default.existsSync(registryPath)) {
128
+ registryPath = path_1.default.join(projectRoot, 'registry');
129
+ }
130
+ if (fs_1.default.existsSync(registryPath)) {
131
+ const syncResult = (0, script_sync_utils_1.syncScriptsToUserDirectory)(registryPath);
132
+ console.log(chalk_1.default.green(`āœ… Synced ${syncResult.synced} self-contained scripts to user directory.`));
133
+ if (syncResult.ephemeral > 0) {
134
+ console.log(chalk_1.default.gray(` ${syncResult.ephemeral} dependent scripts will use ephemeral execution.`));
135
+ }
136
+ }
137
+ else {
138
+ console.log(chalk_1.default.yellow('āš ļø Registry not found, skipping script sync.'));
139
+ }
140
+ // Trigger First Run Experience
141
+ await (0, first_run_1.runFirstRunExperience)();
142
+ process.exit(0);
143
+ };
144
+ exports.runInit = runInit;
145
+ exports.initCommand = new commander_1.Command('init')
146
+ .description('Initialize FRAIM in the current project')
147
+ .option('--skip-platform', 'Skip platform integration (conversational mode only)')
148
+ .action((options) => (0, exports.runInit)(options));
@@ -10,7 +10,9 @@ const path_1 = __importDefault(require("path"));
10
10
  const chalk_1 = __importDefault(require("chalk"));
11
11
  exports.listOverridableCommand = new commander_1.Command('list-overridable')
12
12
  .description('List all FRAIM registry paths that can be overridden')
13
- .action(async () => {
13
+ .option('--job-category <category>', 'Filter by workflow category (e.g., product-building, customer-development, marketing)')
14
+ .option('--rules', 'Show all overridable rules')
15
+ .action(async (options) => {
14
16
  const projectRoot = process.cwd();
15
17
  const fraimDir = path_1.default.join(projectRoot, '.fraim');
16
18
  const configPath = path_1.default.join(fraimDir, 'config.json');
@@ -20,35 +22,24 @@ exports.listOverridableCommand = new commander_1.Command('list-overridable')
20
22
  console.log(chalk_1.default.red('āŒ .fraim/ directory not found. Run "fraim setup" or "fraim init-project" first.'));
21
23
  process.exit(1);
22
24
  }
23
- console.log(chalk_1.default.blue('šŸ“‹ Overridable FRAIM Registry Paths:\n'));
24
- // Define overridable categories
25
- const categories = [
26
- {
27
- name: 'Workflows',
28
- paths: [
29
- 'workflows/product-building/spec.md',
30
- 'workflows/product-building/design.md',
31
- 'workflows/product-building/implement.md',
32
- 'workflows/product-building/test.md',
33
- 'workflows/product-building/resolve.md',
34
- 'workflows/product-building/prep-issue.md'
35
- ]
36
- },
37
- {
38
- name: 'Templates',
39
- paths: [
40
- 'templates/specs/FEATURESPEC-TEMPLATE.md',
41
- 'templates/design/DESIGN-TEMPLATE.md'
42
- ]
43
- },
44
- {
45
- name: 'Rules',
46
- paths: [
47
- 'rules/coding-standards.md',
48
- 'rules/communication.md'
49
- ]
25
+ // Determine registry location (try framework root first, then node_modules)
26
+ let registryRoot = null;
27
+ const frameworkRoot = path_1.default.join(__dirname, '..', '..', '..');
28
+ const frameworkRegistry = path_1.default.join(frameworkRoot, 'registry');
29
+ if (fs_1.default.existsSync(frameworkRegistry)) {
30
+ registryRoot = frameworkRegistry;
31
+ }
32
+ else {
33
+ const nodeModulesRegistry = path_1.default.join(process.cwd(), 'node_modules', '@fraim', 'framework', 'registry');
34
+ if (fs_1.default.existsSync(nodeModulesRegistry)) {
35
+ registryRoot = nodeModulesRegistry;
50
36
  }
51
- ];
37
+ }
38
+ if (!registryRoot) {
39
+ console.log(chalk_1.default.red('āŒ Could not find FRAIM registry. Please ensure @fraim/framework is installed.'));
40
+ process.exit(1);
41
+ }
42
+ console.log(chalk_1.default.blue('šŸ“‹ Overridable FRAIM Registry Paths:\n'));
52
43
  // Get list of existing overrides
53
44
  const existingOverrides = new Set();
54
45
  if (fs_1.default.existsSync(overridesDir)) {
@@ -66,10 +57,50 @@ exports.listOverridableCommand = new commander_1.Command('list-overridable')
66
57
  };
67
58
  scanDir(overridesDir);
68
59
  }
69
- // Display categories
70
- for (const category of categories) {
71
- console.log(chalk_1.default.bold.cyan(`${category.name}:`));
72
- for (const filePath of category.paths) {
60
+ // Handle --rules flag
61
+ if (options.rules) {
62
+ const rulesDir = path_1.default.join(registryRoot, 'rules');
63
+ if (fs_1.default.existsSync(rulesDir)) {
64
+ console.log(chalk_1.default.bold.cyan('Rules:\n'));
65
+ const ruleFiles = fs_1.default.readdirSync(rulesDir)
66
+ .filter(f => f.endsWith('.md'))
67
+ .sort();
68
+ for (const file of ruleFiles) {
69
+ const filePath = `rules/${file}`;
70
+ const hasOverride = existingOverrides.has(filePath);
71
+ const status = hasOverride
72
+ ? chalk_1.default.green('[OVERRIDDEN]')
73
+ : chalk_1.default.gray('[OVERRIDABLE]');
74
+ console.log(` ${status} ${filePath}`);
75
+ }
76
+ console.log('');
77
+ }
78
+ showTips();
79
+ return;
80
+ }
81
+ // Handle --job-category flag
82
+ if (options.jobCategory) {
83
+ const category = options.jobCategory;
84
+ const workflowsDir = path_1.default.join(registryRoot, 'workflows', category);
85
+ const templatesDir = path_1.default.join(registryRoot, 'templates', category);
86
+ if (!fs_1.default.existsSync(workflowsDir)) {
87
+ console.log(chalk_1.default.red(`āŒ Category "${category}" not found.`));
88
+ console.log(chalk_1.default.gray('\nAvailable categories:'));
89
+ const categoriesDir = path_1.default.join(registryRoot, 'workflows');
90
+ const categories = fs_1.default.readdirSync(categoriesDir, { withFileTypes: true })
91
+ .filter(d => d.isDirectory())
92
+ .map(d => d.name)
93
+ .sort();
94
+ categories.forEach(c => console.log(chalk_1.default.gray(` - ${c}`)));
95
+ process.exit(1);
96
+ }
97
+ // Show workflows for this category
98
+ console.log(chalk_1.default.bold.cyan(`Workflows (${category}):\n`));
99
+ const workflowFiles = fs_1.default.readdirSync(workflowsDir)
100
+ .filter(f => f.endsWith('.md'))
101
+ .sort();
102
+ for (const file of workflowFiles) {
103
+ const filePath = `workflows/${category}/${file}`;
73
104
  const hasOverride = existingOverrides.has(filePath);
74
105
  const status = hasOverride
75
106
  ? chalk_1.default.green('[OVERRIDDEN]')
@@ -77,21 +108,95 @@ exports.listOverridableCommand = new commander_1.Command('list-overridable')
77
108
  console.log(` ${status} ${filePath}`);
78
109
  }
79
110
  console.log('');
111
+ // Show templates for this category (if they exist)
112
+ if (fs_1.default.existsSync(templatesDir)) {
113
+ console.log(chalk_1.default.bold.cyan(`Templates (${category}):\n`));
114
+ const templateFiles = fs_1.default.readdirSync(templatesDir)
115
+ .filter(f => f.endsWith('.md') || f.endsWith('.html') || f.endsWith('.csv') || f.endsWith('.yml'))
116
+ .sort();
117
+ for (const file of templateFiles) {
118
+ const filePath = `templates/${category}/${file}`;
119
+ const hasOverride = existingOverrides.has(filePath);
120
+ const status = hasOverride
121
+ ? chalk_1.default.green('[OVERRIDDEN]')
122
+ : chalk_1.default.gray('[OVERRIDABLE]');
123
+ console.log(` ${status} ${filePath}`);
124
+ }
125
+ console.log('');
126
+ }
127
+ showTips();
128
+ return;
129
+ }
130
+ // Default: Show available categories and existing overrides
131
+ console.log(chalk_1.default.bold.cyan('Available Workflow Categories:\n'));
132
+ const workflowsDir = path_1.default.join(registryRoot, 'workflows');
133
+ const categories = fs_1.default.readdirSync(workflowsDir, { withFileTypes: true })
134
+ .filter(d => d.isDirectory())
135
+ .map(d => d.name)
136
+ .sort();
137
+ // Group categories for better display
138
+ const columns = 3;
139
+ for (let i = 0; i < categories.length; i += columns) {
140
+ const row = categories.slice(i, i + columns);
141
+ const formatted = row.map(cat => chalk_1.default.gray(` • ${cat.padEnd(30)}`)).join('');
142
+ console.log(formatted);
80
143
  }
81
- // Show existing overrides not in the standard list
82
- const standardPaths = new Set(categories.flatMap(c => c.paths));
83
- const customOverrides = Array.from(existingOverrides)
84
- .filter(p => !standardPaths.has(p));
85
- if (customOverrides.length > 0) {
86
- console.log(chalk_1.default.bold.cyan('Custom Overrides:'));
87
- for (const filePath of customOverrides) {
88
- console.log(` ${chalk_1.default.green('[OVERRIDDEN]')} ${filePath}`);
144
+ console.log('');
145
+ // Show existing overrides
146
+ if (existingOverrides.size > 0) {
147
+ console.log(chalk_1.default.bold.cyan('Your Active Overrides:\n'));
148
+ // Group by type
149
+ const overridesByType = {
150
+ workflows: [],
151
+ templates: [],
152
+ rules: [],
153
+ other: []
154
+ };
155
+ for (const override of Array.from(existingOverrides).sort()) {
156
+ if (override.startsWith('workflows/')) {
157
+ overridesByType.workflows.push(override);
158
+ }
159
+ else if (override.startsWith('templates/')) {
160
+ overridesByType.templates.push(override);
161
+ }
162
+ else if (override.startsWith('rules/')) {
163
+ overridesByType.rules.push(override);
164
+ }
165
+ else {
166
+ overridesByType.other.push(override);
167
+ }
89
168
  }
90
- console.log('');
169
+ if (overridesByType.workflows.length > 0) {
170
+ console.log(chalk_1.default.gray(' Workflows:'));
171
+ overridesByType.workflows.forEach(o => console.log(` ${chalk_1.default.green('[OVERRIDDEN]')} ${o}`));
172
+ console.log('');
173
+ }
174
+ if (overridesByType.templates.length > 0) {
175
+ console.log(chalk_1.default.gray(' Templates:'));
176
+ overridesByType.templates.forEach(o => console.log(` ${chalk_1.default.green('[OVERRIDDEN]')} ${o}`));
177
+ console.log('');
178
+ }
179
+ if (overridesByType.rules.length > 0) {
180
+ console.log(chalk_1.default.gray(' Rules:'));
181
+ overridesByType.rules.forEach(o => console.log(` ${chalk_1.default.green('[OVERRIDDEN]')} ${o}`));
182
+ console.log('');
183
+ }
184
+ if (overridesByType.other.length > 0) {
185
+ console.log(chalk_1.default.gray(' Other:'));
186
+ overridesByType.other.forEach(o => console.log(` ${chalk_1.default.green('[OVERRIDDEN]')} ${o}`));
187
+ console.log('');
188
+ }
189
+ }
190
+ else {
191
+ console.log(chalk_1.default.gray('No active overrides yet.\n'));
192
+ }
193
+ showTips();
194
+ function showTips() {
195
+ console.log(chalk_1.default.gray('Tips:'));
196
+ console.log(chalk_1.default.gray(' • Use "fraim override <path> --inherit" to inherit from global'));
197
+ console.log(chalk_1.default.gray(' • Use "fraim override <path> --copy" to copy current content'));
198
+ console.log(chalk_1.default.gray(' • Use --job-category <category> to see category-specific items'));
199
+ console.log(chalk_1.default.gray(' • Use --rules to see all overridable rules'));
200
+ console.log(chalk_1.default.gray(' • Overrides are stored in .fraim/overrides/'));
91
201
  }
92
- // Show tips
93
- console.log(chalk_1.default.gray('Tips:'));
94
- console.log(chalk_1.default.gray(' • Use "fraim override <path> --inherit" to inherit from global'));
95
- console.log(chalk_1.default.gray(' • Use "fraim override <path> --copy" to copy current content'));
96
- console.log(chalk_1.default.gray(' • Overrides are stored in .fraim/overrides/'));
97
202
  });
@@ -0,0 +1,65 @@
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.mcpCommand = void 0;
7
+ const commander_1 = require("commander");
8
+ const server_1 = require("../../local-mcp-server/server");
9
+ const chalk_1 = __importDefault(require("chalk"));
10
+ exports.mcpCommand = new commander_1.Command('mcp')
11
+ .description('Start the local FRAIM MCP server')
12
+ .option('-p, --port <port>', 'Port to run the server on', '3003')
13
+ .option('--remote-only', 'Use remote server only (skip local proxy)', false)
14
+ .action(async (options) => {
15
+ try {
16
+ if (options.remoteOnly) {
17
+ console.log(chalk_1.default.yellow('āš ļø Remote-only mode not yet implemented'));
18
+ console.log(chalk_1.default.blue('ā„¹ļø Starting local MCP server instead...'));
19
+ }
20
+ const port = parseInt(options.port);
21
+ if (isNaN(port) || port < 1 || port > 65535) {
22
+ console.error(chalk_1.default.red('āŒ Invalid port number. Must be between 1 and 65535.'));
23
+ process.exit(1);
24
+ }
25
+ console.log(chalk_1.default.blue('šŸš€ Starting FRAIM Local MCP Server...'));
26
+ console.log(chalk_1.default.gray(`šŸ“” Port: ${port}`));
27
+ console.log(chalk_1.default.gray(`šŸ”„ Auto-updates: Every 5 minutes`));
28
+ console.log(chalk_1.default.gray(`šŸ“ Cache: ~/.fraim/cache`));
29
+ console.log('');
30
+ // Set the port in environment
31
+ process.env.FRAIM_LOCAL_MCP_PORT = port.toString();
32
+ // Start the server
33
+ await (0, server_1.startLocalMCPServer)();
34
+ // Server is now running, show configuration instructions
35
+ console.log('');
36
+ console.log(chalk_1.default.green('āœ… FRAIM Local MCP Server is running!'));
37
+ console.log('');
38
+ console.log(chalk_1.default.bold('šŸ“‹ Agent Configuration:'));
39
+ console.log('');
40
+ console.log(chalk_1.default.gray('Add this to your agent\'s MCP configuration:'));
41
+ console.log('');
42
+ console.log(chalk_1.default.cyan(JSON.stringify({
43
+ "mcpServers": {
44
+ "fraim": {
45
+ "command": "fraim",
46
+ "args": ["mcp"],
47
+ "env": {}
48
+ }
49
+ }
50
+ }, null, 2)));
51
+ console.log('');
52
+ console.log(chalk_1.default.bold('šŸ”— Endpoints:'));
53
+ console.log(chalk_1.default.gray(` MCP: http://localhost:${port}/mcp`));
54
+ console.log(chalk_1.default.gray(` Health: http://localhost:${port}/health`));
55
+ console.log('');
56
+ console.log(chalk_1.default.bold('šŸ›‘ To stop the server:'));
57
+ console.log(chalk_1.default.gray(' Press Ctrl+C'));
58
+ console.log('');
59
+ }
60
+ catch (error) {
61
+ console.error(chalk_1.default.red('āŒ Failed to start Local MCP Server:'));
62
+ console.error(chalk_1.default.red(error instanceof Error ? error.message : String(error)));
63
+ process.exit(1);
64
+ }
65
+ });
@@ -9,11 +9,13 @@ const fs_1 = __importDefault(require("fs"));
9
9
  const path_1 = __importDefault(require("path"));
10
10
  const chalk_1 = __importDefault(require("chalk"));
11
11
  const axios_1 = __importDefault(require("axios"));
12
+ const git_utils_1 = require("../../core/utils/git-utils");
12
13
  exports.overrideCommand = new commander_1.Command('override')
13
14
  .description('Create a local override for a FRAIM registry file')
14
15
  .argument('<path>', 'Registry path to override (e.g., workflows/product-building/spec.md)')
15
16
  .option('--inherit', 'Create override with {{ import }} directive (inherits from global)')
16
- .option('--copy', 'Copy current content from remote to local')
17
+ .option('--copy', 'Copy current content from server to local')
18
+ .option('--local', 'Fetch from local development server (port derived from git branch)')
17
19
  .action(async (registryPath, options) => {
18
20
  const projectRoot = process.cwd();
19
21
  const fraimDir = path_1.default.join(projectRoot, '.fraim');
@@ -56,48 +58,112 @@ exports.overrideCommand = new commander_1.Command('override')
56
58
  console.log(chalk_1.default.blue(`šŸ“ Creating inherited override for: ${registryPath}`));
57
59
  }
58
60
  else {
59
- // Fetch current content from remote
60
- console.log(chalk_1.default.blue(`šŸ“„ Fetching current content from remote: ${registryPath}`));
61
+ // Fetch current content from server using MCP protocol
62
+ const isLocal = options.local || false;
63
+ const serverType = isLocal ? 'local server' : 'remote server';
64
+ console.log(chalk_1.default.blue(`šŸ“„ Fetching current content from ${serverType}: ${registryPath}`));
61
65
  try {
62
66
  // Read config to get remote URL
63
67
  let config = {};
64
68
  if (fs_1.default.existsSync(configPath)) {
65
69
  config = JSON.parse(fs_1.default.readFileSync(configPath, 'utf-8'));
66
70
  }
67
- const remoteUrl = config.remoteUrl || 'https://fraim-registry.azurewebsites.net';
68
- const apiKey = config.apiKey;
69
- // Determine endpoint based on path
70
- let endpoint;
71
- if (registryPath.startsWith('workflows/')) {
72
- const workflowName = registryPath.replace('workflows/', '').replace('.md', '');
73
- endpoint = `${remoteUrl}/api/workflows/${workflowName}`;
71
+ // Determine server URL
72
+ let serverUrl;
73
+ if (isLocal) {
74
+ const localPort = process.env.FRAIM_MCP_PORT ? parseInt(process.env.FRAIM_MCP_PORT) : (0, git_utils_1.getPort)();
75
+ serverUrl = `http://localhost:${localPort}`;
74
76
  }
75
77
  else {
76
- endpoint = `${remoteUrl}/api/files/${registryPath}`;
78
+ serverUrl = config.remoteUrl || process.env.FRAIM_REMOTE_URL || 'https://fraim.wellnessatwork.me';
77
79
  }
78
- const headers = {};
80
+ const apiKey = isLocal ? 'local-dev' : (config.apiKey || process.env.FRAIM_API_KEY);
81
+ const headers = {
82
+ 'Content-Type': 'application/json'
83
+ };
79
84
  if (apiKey) {
80
85
  headers['x-api-key'] = apiKey;
81
86
  }
82
- const response = await axios_1.default.get(endpoint, {
87
+ // First, establish session with fraim_connect
88
+ const connectRequest = {
89
+ jsonrpc: '2.0',
90
+ id: 0,
91
+ method: 'tools/call',
92
+ params: {
93
+ name: 'fraim_connect',
94
+ arguments: {
95
+ agent: { name: 'fraim-cli', model: 'override-command' },
96
+ machine: {
97
+ hostname: require('os').hostname(),
98
+ platform: process.platform,
99
+ memory: require('os').totalmem(),
100
+ cpus: require('os').cpus().length
101
+ },
102
+ repo: {
103
+ url: config.repository?.url || 'https://github.com/unknown/unknown',
104
+ owner: config.repository?.owner || 'unknown',
105
+ name: config.repository?.name || 'unknown',
106
+ branch: 'main'
107
+ }
108
+ }
109
+ }
110
+ };
111
+ await axios_1.default.post(`${serverUrl}/mcp`, connectRequest, { headers, timeout: 30000 });
112
+ // Now determine MCP tool and arguments based on path
113
+ let toolName;
114
+ let toolArgs;
115
+ if (registryPath.startsWith('workflows/')) {
116
+ // Extract workflow name from path
117
+ // e.g., "workflows/product-building/spec.md" -> "spec"
118
+ const parts = registryPath.split('/');
119
+ const workflowName = parts[parts.length - 1].replace('.md', '');
120
+ toolName = 'get_fraim_workflow';
121
+ toolArgs = { workflow: workflowName };
122
+ }
123
+ else {
124
+ toolName = 'get_fraim_file';
125
+ toolArgs = { path: registryPath };
126
+ }
127
+ console.log(chalk_1.default.gray(` Using MCP tool: ${toolName}`));
128
+ // Make MCP JSON-RPC request
129
+ const mcpRequest = {
130
+ jsonrpc: '2.0',
131
+ id: 1,
132
+ method: 'tools/call',
133
+ params: {
134
+ name: toolName,
135
+ arguments: toolArgs
136
+ }
137
+ };
138
+ const response = await axios_1.default.post(`${serverUrl}/mcp`, mcpRequest, {
83
139
  headers,
84
140
  timeout: 30000
85
141
  });
86
- // Extract content from response
87
- if (response.data.content) {
88
- content = response.data.content;
142
+ // Extract content from MCP response
143
+ if (response.data.error) {
144
+ throw new Error(response.data.error.message || 'MCP request failed');
89
145
  }
90
- else if (typeof response.data === 'string') {
91
- content = response.data;
146
+ const result = response.data.result;
147
+ if (!result || !result.content || !Array.isArray(result.content)) {
148
+ throw new Error('Unexpected MCP response format');
92
149
  }
93
- else {
94
- throw new Error('Unexpected response format');
150
+ // Extract text content from MCP response
151
+ const textContent = result.content.find((c) => c.type === 'text');
152
+ if (!textContent || !textContent.text) {
153
+ throw new Error('No text content in MCP response');
95
154
  }
96
- console.log(chalk_1.default.green(`āœ… Fetched ${content.length} bytes from remote`));
155
+ content = textContent.text;
156
+ console.log(chalk_1.default.green(`āœ… Fetched ${content.length} bytes from ${serverType}`));
97
157
  }
98
158
  catch (error) {
99
- console.log(chalk_1.default.red(`āŒ Failed to fetch content from remote: ${error.message}`));
100
- console.log(chalk_1.default.gray(' Tip: Check your network connection and .fraim/config.json settings'));
159
+ console.log(chalk_1.default.red(`āŒ Failed to fetch content from ${serverType}: ${error.message}`));
160
+ if (isLocal) {
161
+ console.log(chalk_1.default.gray(` Tip: Make sure the FRAIM server is running locally (npm run start:fraim)`));
162
+ }
163
+ else {
164
+ console.log(chalk_1.default.gray(` Tip: Check your API key and network connection`));
165
+ console.log(chalk_1.default.gray(` Or use --local to fetch from a locally running server`));
166
+ }
101
167
  process.exit(1);
102
168
  }
103
169
  }
@@ -113,6 +113,10 @@ const runSync = async (options) => {
113
113
  if (!result.success) {
114
114
  console.error(chalk_1.default.red(`āŒ Remote sync failed: ${result.error}`));
115
115
  console.error(chalk_1.default.yellow('šŸ’” Check your API key and network connection.'));
116
+ if (process.env.TEST_MODE === 'true') {
117
+ console.log(chalk_1.default.yellow('āš ļø TEST_MODE: Continuing without remote sync (server may be unavailable).'));
118
+ return;
119
+ }
116
120
  process.exit(1);
117
121
  }
118
122
  console.log(chalk_1.default.green(`āœ… Successfully synced ${result.workflowsSynced} workflows and ${result.scriptsSynced} scripts from remote`));
@@ -28,12 +28,14 @@ const testIDEConfig = async (ide) => {
28
28
  if (ide.configFormat === 'json') {
29
29
  const configContent = fs_1.default.readFileSync(configPath, 'utf8');
30
30
  const config = JSON.parse(configContent);
31
- if (config.mcpServers) {
31
+ const servers = ide.configType === 'vscode' ? config.servers : config.mcpServers;
32
+ if (servers) {
32
33
  result.configValid = true;
33
- result.mcpServers = Object.keys(config.mcpServers);
34
+ result.mcpServers = Object.keys(servers);
34
35
  }
35
36
  else {
36
- result.errors.push('No mcpServers section found');
37
+ const expectedKey = ide.configType === 'vscode' ? 'servers' : 'mcpServers';
38
+ result.errors.push(`No ${expectedKey} section found`);
37
39
  }
38
40
  }
39
41
  else if (ide.configFormat === 'toml') {
@@ -0,0 +1,35 @@
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.wizardCommand = void 0;
7
+ const commander_1 = require("commander");
8
+ const chalk_1 = __importDefault(require("chalk"));
9
+ const init_js_1 = require("./init.js");
10
+ const sync_js_1 = require("./sync.js");
11
+ exports.wizardCommand = new commander_1.Command('wizard')
12
+ .description('Run the interactive setup wizard')
13
+ .action(async () => {
14
+ console.log(chalk_1.default.cyan('šŸ”® FRAIM Interactive Setup Wizard\n'));
15
+ console.log('Running V2 setup (Init + Sync)...\n');
16
+ try {
17
+ // Run initialization
18
+ try {
19
+ await (0, init_js_1.runInit)();
20
+ }
21
+ catch (err) {
22
+ console.log(chalk_1.default.yellow('āš ļø Init notice:'), err.message);
23
+ }
24
+ console.log(''); // newline
25
+ // Run sync (Force not strictly required if init is fresh, but safe)
26
+ // But if user runs wizard on existing project, maybe they want to resync
27
+ await (0, sync_js_1.runSync)({ force: false });
28
+ console.log(chalk_1.default.blue('\n✨ Wizard completed successfully!'));
29
+ console.log(chalk_1.default.gray('Tip: In the future, you can run `fraim sync` to update workflows.'));
30
+ }
31
+ catch (error) {
32
+ console.error(chalk_1.default.red('āŒ Wizard failed:'), error);
33
+ process.exit(1);
34
+ }
35
+ });
@@ -209,10 +209,11 @@ const configureIDEMCP = async (ide, fraimKey, githubToken) => {
209
209
  }
210
210
  let existingConfig = {};
211
211
  let existingMCPServers = {};
212
+ const serversKey = ide.configType === 'vscode' ? 'servers' : 'mcpServers';
212
213
  if (fs_1.default.existsSync(configPath) && ide.configFormat === 'json') {
213
214
  try {
214
215
  existingConfig = JSON.parse(fs_1.default.readFileSync(configPath, 'utf8'));
215
- existingMCPServers = existingConfig.mcpServers || {};
216
+ existingMCPServers = existingConfig[serversKey] || {};
216
217
  console.log(chalk_1.default.gray(` šŸ“‹ Found existing config with ${Object.keys(existingMCPServers).length} MCP servers`));
217
218
  }
218
219
  catch (e) {
@@ -253,7 +254,7 @@ const configureIDEMCP = async (ide, fraimKey, githubToken) => {
253
254
  else {
254
255
  // For JSON configs - intelligent merging
255
256
  const newConfig = (0, mcp_config_generator_1.generateMCPConfig)(ide.configType, fraimKey, githubToken);
256
- const newMCPServers = newConfig.mcpServers || {};
257
+ const newMCPServers = newConfig[serversKey] || newConfig.mcpServers || {};
257
258
  // Merge MCP servers intelligently
258
259
  const mergedMCPServers = { ...existingMCPServers };
259
260
  const addedServers = [];
@@ -267,11 +268,10 @@ const configureIDEMCP = async (ide, fraimKey, githubToken) => {
267
268
  skippedServers.push(serverName);
268
269
  }
269
270
  }
270
- // Merge with existing config
271
+ // Merge with existing config (VS Code uses "servers", others use "mcpServers")
271
272
  const mergedConfig = {
272
273
  ...existingConfig,
273
- ...newConfig,
274
- mcpServers: mergedMCPServers
274
+ [serversKey]: mergedMCPServers
275
275
  };
276
276
  // Write updated config
277
277
  fs_1.default.writeFileSync(configPath, JSON.stringify(mergedConfig, null, 2));
@@ -99,6 +99,26 @@ const checkMCPConfigurations = () => {
99
99
  // Ignore parsing errors
100
100
  }
101
101
  }
102
+ // Check VS Code MCP config (uses "servers" key)
103
+ const vscodePaths = [
104
+ path_1.default.join(os_1.default.homedir(), 'AppData', 'Roaming', 'Code', 'User', 'mcp.json'),
105
+ path_1.default.join(os_1.default.homedir(), 'Library', 'Application Support', 'Code', 'User', 'mcp.json'),
106
+ path_1.default.join(os_1.default.homedir(), '.config', 'Code', 'User', 'mcp.json')
107
+ ];
108
+ for (const vscodePath of vscodePaths) {
109
+ if (fs_1.default.existsSync(vscodePath)) {
110
+ try {
111
+ const vscodeConfig = JSON.parse(fs_1.default.readFileSync(vscodePath, 'utf8'));
112
+ if (vscodeConfig.servers?.fraim) {
113
+ sources.push('VSCode');
114
+ break;
115
+ }
116
+ }
117
+ catch (e) {
118
+ // Ignore parsing errors
119
+ }
120
+ }
121
+ }
102
122
  return {
103
123
  found: sources.length > 0,
104
124
  sources
@@ -108,14 +108,18 @@ exports.IDE_CONFIGS = [
108
108
  },
109
109
  {
110
110
  name: 'VSCode',
111
- configPath: '~/.vscode/settings/mcp.json',
111
+ configPath: process.platform === 'win32'
112
+ ? '~/AppData/Roaming/Code/User/mcp.json'
113
+ : process.platform === 'darwin'
114
+ ? '~/Library/Application Support/Code/User/mcp.json'
115
+ : '~/.config/Code/User/mcp.json',
112
116
  configFormat: 'json',
113
- configType: 'kiro',
117
+ configType: 'vscode',
114
118
  detectMethod: detectVSCode,
115
119
  alternativePaths: [
116
- '~/Library/Application Support/Code/User/settings/mcp.json',
117
- '~/AppData/Roaming/Code/User/settings/mcp.json',
118
- '~/.config/Code/User/settings/mcp.json'
120
+ '~/Library/Application Support/Code/User/mcp.json',
121
+ '~/AppData/Roaming/Code/User/mcp.json',
122
+ '~/.config/Code/User/mcp.json'
119
123
  ],
120
124
  description: 'Visual Studio Code'
121
125
  },
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.generateMCPConfig = exports.generateWindsurfMCPServers = exports.generateCodexMCPServers = exports.generateKiroMCPServers = exports.generateClaudeMCPServers = exports.generateStandardMCPServers = void 0;
3
+ exports.generateMCPConfig = exports.generateWindsurfMCPServers = exports.generateVSCodeMCPServers = exports.generateCodexMCPServers = exports.generateKiroMCPServers = exports.generateClaudeMCPServers = exports.generateStandardMCPServers = void 0;
4
4
  const generateStandardMCPServers = (fraimKey, githubToken) => {
5
5
  const servers = {
6
6
  git: {
@@ -121,6 +121,40 @@ FRAIM_REMOTE_URL = "https://fraim.wellnessatwork.me"
121
121
  return config;
122
122
  };
123
123
  exports.generateCodexMCPServers = generateCodexMCPServers;
124
+ /** VS Code uses "servers" key and requires type: "stdio" for stdio servers */
125
+ const generateVSCodeMCPServers = (fraimKey, githubToken) => {
126
+ const servers = {
127
+ git: {
128
+ type: 'stdio',
129
+ command: 'npx',
130
+ args: ['-y', '@cyanheads/git-mcp-server']
131
+ },
132
+ playwright: {
133
+ type: 'stdio',
134
+ command: 'npx',
135
+ args: ['-y', '@playwright/mcp']
136
+ },
137
+ fraim: {
138
+ type: 'stdio',
139
+ command: 'fraim-mcp',
140
+ env: {
141
+ FRAIM_API_KEY: fraimKey,
142
+ FRAIM_REMOTE_URL: 'https://fraim.wellnessatwork.me'
143
+ }
144
+ }
145
+ };
146
+ if (githubToken) {
147
+ servers.github = {
148
+ type: 'http',
149
+ url: 'https://api.githubcopilot.com/mcp/',
150
+ headers: {
151
+ Authorization: `Bearer ${githubToken}`
152
+ }
153
+ };
154
+ }
155
+ return { servers };
156
+ };
157
+ exports.generateVSCodeMCPServers = generateVSCodeMCPServers;
124
158
  const generateWindsurfMCPServers = (fraimKey, githubToken) => {
125
159
  const servers = {
126
160
  git: {
@@ -160,6 +194,8 @@ const generateMCPConfig = (configType, fraimKey, githubToken) => {
160
194
  return (0, exports.generateClaudeMCPServers)(fraimKey, githubToken);
161
195
  case 'kiro':
162
196
  return (0, exports.generateKiroMCPServers)(fraimKey, githubToken);
197
+ case 'vscode':
198
+ return (0, exports.generateVSCodeMCPServers)(fraimKey, githubToken);
163
199
  case 'codex':
164
200
  return (0, exports.generateCodexMCPServers)(fraimKey, githubToken);
165
201
  case 'windsurf':
package/index.js CHANGED
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env node
2
2
 
3
3
  /**
4
4
  * FRAIM Framework - Smart Entry Point
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fraim-framework",
3
- "version": "2.0.72",
3
+ "version": "2.0.74",
4
4
  "description": "FRAIM v2: Framework for Rigor-based AI Management - Transform from solo developer to AI manager orchestrating production-ready code with enterprise-grade discipline",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -25,7 +25,7 @@
25
25
  "prepublishOnly": "npm run build",
26
26
  "release": "npm version patch && npm publish",
27
27
  "test-smoke-ci": "tsx --test tests/test-genericization.ts tests/test-cli.ts tests/test-stub-registry.ts",
28
- "test-all-ci": "tsx --test tests/test-*.ts",
28
+ "test-all-ci": "node scripts/test-with-server.js",
29
29
  "validate:registry": "tsx scripts/verify-registry-paths.ts && npm run validate:workflows && npm run validate:platform-agnostic",
30
30
  "validate:workflows": "tsx scripts/validate-workflows.ts",
31
31
  "validate:platform-agnostic": "tsx scripts/validate-platform-agnostic.ts"